6a3c2aded6
* Simplify EINTR/ECANCEL error handling 1. Add semaphore uninterruptible wait function 2 .Replace semaphore wait loop with a single uninterruptible wait 3. Replace all sem_xxx to nxsem_xxx * Unify the void cast usage 1. Remove void cast for function because many place ignore the returned value witout cast 2. Replace void cast for variable with UNUSED macro
5470 lines
161 KiB
C
5470 lines
161 KiB
C
/****************************************************************************
|
|
* arch/arm/src/stm32h7/stm32_otghost.c
|
|
*
|
|
* Copyright (C) 2019 Gregory Nutt. All rights reserved.
|
|
* Authors: Gregory Nutt <gnutt@nuttx.org>
|
|
* David Sidrane <david_s5@nscdg.com>
|
|
* Modified for STM32H7 by Mateusz Szafoni <raiden00@railab.me>
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* 3. Neither the name NuttX nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
|
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <semaphore.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <debug.h>
|
|
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/clock.h>
|
|
#include <nuttx/signal.h>
|
|
#include <nuttx/semaphore.h>
|
|
#include <nuttx/usb/usb.h>
|
|
#include <nuttx/usb/usbhost.h>
|
|
#include <nuttx/usb/usbhost_devaddr.h>
|
|
#include <nuttx/usb/usbhost_trace.h>
|
|
|
|
#include <nuttx/irq.h>
|
|
|
|
#include "chip.h" /* Includes default GPIO settings */
|
|
#include <arch/board/board.h> /* May redefine GPIO settings */
|
|
|
|
#include "up_arch.h"
|
|
#include "up_internal.h"
|
|
|
|
#include "hardware/stm32_pwr.h"
|
|
#include "stm32_gpio.h"
|
|
#include "stm32_otg.h"
|
|
#include "stm32_usbhost.h"
|
|
|
|
#if defined(CONFIG_USBHOST) && defined(CONFIG_STM32H7_OTGFS) && defined(CONFIG_EXPERIMENTAL)
|
|
|
|
#warning OTG host not tested for STM32H7!
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
/* Configuration ************************************************************/
|
|
|
|
/* STM32 USB OTG FS Host Driver Support
|
|
*
|
|
* Pre-requisites
|
|
*
|
|
* CONFIG_USBHOST - Enable general USB host support
|
|
* CONFIG_STM32H7_OTGFS - Enable the STM32 USB OTG FS block
|
|
* CONFIG_STM32H7_SYSCFG - Needed
|
|
*
|
|
* Options:
|
|
*
|
|
* CONFIG_STM32H7_OTG_RXFIFO_SIZE - Size of the RX FIFO in 32-bit words.
|
|
* Default 128 (512 bytes)
|
|
* CONFIG_STM32H7_OTG_NPTXFIFO_SIZE - Size of the non-periodic Tx FIFO
|
|
* in 32-bit words. Default 96 (384 bytes)
|
|
* CONFIG_STM32H7_OTG_PTXFIFO_SIZE - Size of the periodic Tx FIFO in 32-bit
|
|
* words. Default 96 (384 bytes)
|
|
* CONFIG_STM32H7_OTG_DESCSIZE - Maximum size of a descriptor. Default: 128
|
|
* CONFIG_STM32H7_OTG_SOFINTR - Enable SOF interrupts. Why would you ever
|
|
* want to do that?
|
|
* CONFIG_STM32H7_USBHOST_REGDEBUG - Enable very low-level register access
|
|
* debug. Depends on CONFIG_DEBUG_FEATURES.
|
|
* CONFIG_STM32H7_USBHOST_PKTDUMP - Dump all incoming and outgoing USB
|
|
* packets. Depends on CONFIG_DEBUG_FEATURES.
|
|
*/
|
|
|
|
/* Pre-requisites (partial) */
|
|
|
|
#ifndef CONFIG_STM32H7_SYSCFG
|
|
# error "CONFIG_STM32H7_SYSCFG is required"
|
|
#endif
|
|
|
|
/* Default RxFIFO size */
|
|
|
|
#ifndef CONFIG_STM32H7_OTG_RXFIFO_SIZE
|
|
# define CONFIG_STM32H7_OTG_RXFIFO_SIZE 128
|
|
#endif
|
|
|
|
/* Default host non-periodic Tx FIFO size */
|
|
|
|
#ifndef CONFIG_STM32H7_OTG_NPTXFIFO_SIZE
|
|
# define CONFIG_STM32H7_OTG_NPTXFIFO_SIZE 96
|
|
#endif
|
|
|
|
/* Default host periodic Tx fifo size register */
|
|
|
|
#ifndef CONFIG_STM32H7_OTG_PTXFIFO_SIZE
|
|
# define CONFIG_STM32H7_OTG_PTXFIFO_SIZE 96
|
|
#endif
|
|
|
|
/* Maximum size of a descriptor */
|
|
|
|
#ifndef CONFIG_STM32H7_OTG_DESCSIZE
|
|
# define CONFIG_STM32H7_OTG_DESCSIZE 128
|
|
#endif
|
|
|
|
/* Register/packet debug depends on CONFIG_DEBUG_FEATURES */
|
|
|
|
#ifndef CONFIG_DEBUG_FEATURES
|
|
# undef CONFIG_STM32H7_USBHOST_REGDEBUG
|
|
# undef CONFIG_STM32H7_USBHOST_PKTDUMP
|
|
#endif
|
|
|
|
/* HCD Setup ****************************************************************/
|
|
|
|
/* Hardware capabilities */
|
|
|
|
#define STM32_NHOST_CHANNELS 8 /* Number of host channels */
|
|
#define STM32_MAX_PACKET_SIZE 64 /* Full speed max packet size */
|
|
#define STM32_EP0_DEF_PACKET_SIZE 8 /* EP0 default packet size */
|
|
#define STM32_EP0_MAX_PACKET_SIZE 64 /* EP0 FS max packet size */
|
|
#define STM32_MAX_TX_FIFOS 15 /* Max number of TX FIFOs */
|
|
#define STM32_MAX_PKTCOUNT 256 /* Max packet count */
|
|
#define STM32_RETRY_COUNT 3 /* Number of ctrl transfer retries */
|
|
|
|
/* Delays *******************************************************************/
|
|
|
|
#define STM32_READY_DELAY 200000 /* In loop counts */
|
|
#define STM32_FLUSH_DELAY 200000 /* In loop counts */
|
|
#define STM32_SETUP_DELAY SEC2TICK(5) /* 5 seconds in system ticks */
|
|
#define STM32_DATANAK_DELAY SEC2TICK(5) /* 5 seconds in system ticks */
|
|
|
|
/* Ever-present MIN/MAX macros */
|
|
|
|
#ifndef MIN
|
|
# define MIN(a, b) (((a) < (b)) ? (a) : (b))
|
|
#endif
|
|
|
|
#ifndef MAX
|
|
# define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
/* The following enumeration represents the various states of the USB host
|
|
* state machine (for debug purposes only)
|
|
*/
|
|
|
|
enum stm32_smstate_e
|
|
{
|
|
SMSTATE_DETACHED = 0, /* Not attached to a device */
|
|
SMSTATE_ATTACHED, /* Attached to a device */
|
|
SMSTATE_ENUM, /* Attached, enumerating */
|
|
SMSTATE_CLASS_BOUND, /* Enumeration complete, class bound */
|
|
};
|
|
|
|
/* This enumeration provides the reason for the channel halt. */
|
|
|
|
enum stm32_chreason_e
|
|
{
|
|
CHREASON_IDLE = 0, /* Inactive (initial state) */
|
|
CHREASON_FREED, /* Channel is no longer in use */
|
|
CHREASON_XFRC, /* Transfer complete */
|
|
CHREASON_NAK, /* NAK received */
|
|
CHREASON_NYET, /* NotYet received */
|
|
CHREASON_STALL, /* Endpoint stalled */
|
|
CHREASON_TXERR, /* Transfer error received */
|
|
CHREASON_DTERR, /* Data toggle error received */
|
|
CHREASON_FRMOR, /* Frame overrun */
|
|
CHREASON_CANCELLED /* Transfer cancelled */
|
|
};
|
|
|
|
/* This structure retains the state of one host channel. NOTE: Since there
|
|
* is only one channel operation active at a time, some of the fields in
|
|
* in the structure could be moved in struct stm32_ubhost_s to achieve
|
|
* some memory savings.
|
|
*/
|
|
|
|
struct stm32_chan_s
|
|
{
|
|
sem_t waitsem; /* Channel wait semaphore */
|
|
volatile uint8_t result; /* The result of the transfer */
|
|
volatile uint8_t chreason; /* Channel halt reason. See enum stm32_chreason_e */
|
|
uint8_t chidx; /* Channel index */
|
|
uint8_t epno; /* Device endpoint number (0-127) */
|
|
uint8_t eptype; /* See OTG_EPTYPE_* definitions */
|
|
uint8_t funcaddr; /* Device function address */
|
|
uint8_t speed; /* Device speed */
|
|
uint8_t interval; /* Interrupt/isochronous EP polling interval */
|
|
uint8_t pid; /* Data PID */
|
|
uint8_t npackets; /* Number of packets (for data toggle) */
|
|
bool inuse; /* True: This channel is "in use" */
|
|
volatile bool indata1; /* IN data toggle. True: DATA01 (Bulk and INTR only) */
|
|
volatile bool outdata1; /* OUT data toggle. True: DATA01 */
|
|
bool in; /* True: IN endpoint */
|
|
volatile bool waiter; /* True: Thread is waiting for a channel event */
|
|
uint16_t maxpacket; /* Max packet size */
|
|
uint16_t buflen; /* Buffer length (at start of transfer) */
|
|
volatile uint16_t xfrd; /* Bytes transferred (at end of transfer) */
|
|
volatile uint16_t inflight; /* Number of Tx bytes "in-flight" */
|
|
FAR uint8_t *buffer; /* Transfer buffer pointer */
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
usbhost_asynch_t callback; /* Transfer complete callback */
|
|
FAR void *arg; /* Argument that accompanies the callback */
|
|
#endif
|
|
};
|
|
|
|
/* A channel represents on uni-directional endpoint. So, in the case of the
|
|
* bi-directional, control endpoint, there must be two channels to represent
|
|
* the endpoint.
|
|
*/
|
|
|
|
struct stm32_ctrlinfo_s
|
|
{
|
|
uint8_t inndx; /* EP0 IN control channel index */
|
|
uint8_t outndx; /* EP0 OUT control channel index */
|
|
};
|
|
|
|
/* This structure retains the state of the USB host controller */
|
|
|
|
struct stm32_usbhost_s
|
|
{
|
|
/* Common device fields. This must be the first thing defined in the
|
|
* structure so that it is possible to simply cast from struct usbhost_s
|
|
* to structstm32_usbhost_s.
|
|
*/
|
|
|
|
struct usbhost_driver_s drvr;
|
|
|
|
/* This is the hub port description understood by class drivers */
|
|
|
|
struct usbhost_roothubport_s rhport;
|
|
|
|
/* Overall driver status */
|
|
|
|
volatile uint8_t smstate; /* The state of the USB host state machine */
|
|
uint8_t chidx; /* ID of channel waiting for space in Tx FIFO */
|
|
volatile bool connected; /* Connected to device */
|
|
volatile bool change; /* Connection change */
|
|
volatile bool pscwait; /* True: Thread is waiting for a port event */
|
|
sem_t exclsem; /* Support mutually exclusive access */
|
|
sem_t pscsem; /* Semaphore to wait for a port event */
|
|
struct stm32_ctrlinfo_s ep0; /* Root hub port EP0 description */
|
|
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
/* Used to pass external hub port events */
|
|
|
|
volatile struct usbhost_hubport_s *hport;
|
|
#endif
|
|
|
|
/* The state of each host channel */
|
|
|
|
struct stm32_chan_s chan[STM32_MAX_TX_FIFOS];
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* Register operations ******************************************************/
|
|
|
|
#ifdef CONFIG_STM32H7_USBHOST_REGDEBUG
|
|
static void stm32_printreg(uint32_t addr, uint32_t val, bool iswrite);
|
|
static void stm32_checkreg(uint32_t addr, uint32_t val, bool iswrite);
|
|
static uint32_t stm32_getreg(uint32_t addr);
|
|
static void stm32_putreg(uint32_t addr, uint32_t value);
|
|
#else
|
|
# define stm32_getreg(addr) getreg32(addr)
|
|
# define stm32_putreg(addr,val) putreg32(val,addr)
|
|
#endif
|
|
|
|
static inline void stm32_modifyreg(uint32_t addr, uint32_t clrbits,
|
|
uint32_t setbits);
|
|
|
|
#ifdef CONFIG_STM32H7_USBHOST_PKTDUMP
|
|
# define stm32_pktdump(m,b,n) lib_dumpbuffer(m,b,n)
|
|
#else
|
|
# define stm32_pktdump(m,b,n)
|
|
#endif
|
|
|
|
/* Semaphores ***************************************************************/
|
|
|
|
static void stm32_takesem(sem_t *sem);
|
|
#define stm32_givesem(s) nxsem_post(s);
|
|
|
|
/* Byte stream access helper functions **************************************/
|
|
|
|
static inline uint16_t stm32_getle16(const uint8_t *val);
|
|
|
|
/* Channel management *******************************************************/
|
|
|
|
static int stm32_chan_alloc(FAR struct stm32_usbhost_s *priv);
|
|
static inline void stm32_chan_free(FAR struct stm32_usbhost_s *priv, int chidx);
|
|
static inline void stm32_chan_freeall(FAR struct stm32_usbhost_s *priv);
|
|
static void stm32_chan_configure(FAR struct stm32_usbhost_s *priv, int chidx);
|
|
static void stm32_chan_halt(FAR struct stm32_usbhost_s *priv, int chidx,
|
|
enum stm32_chreason_e chreason);
|
|
static int stm32_chan_waitsetup(FAR struct stm32_usbhost_s *priv,
|
|
FAR struct stm32_chan_s *chan);
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
static int stm32_chan_asynchsetup(FAR struct stm32_usbhost_s *priv,
|
|
FAR struct stm32_chan_s *chan,
|
|
usbhost_asynch_t callback, FAR void *arg);
|
|
#endif
|
|
static int stm32_chan_wait(FAR struct stm32_usbhost_s *priv,
|
|
FAR struct stm32_chan_s *chan);
|
|
static void stm32_chan_wakeup(FAR struct stm32_usbhost_s *priv,
|
|
FAR struct stm32_chan_s *chan);
|
|
static int stm32_ctrlchan_alloc(FAR struct stm32_usbhost_s *priv,
|
|
uint8_t epno, uint8_t funcaddr, uint8_t speed,
|
|
FAR struct stm32_ctrlinfo_s *ctrlep);
|
|
static int stm32_ctrlep_alloc(FAR struct stm32_usbhost_s *priv,
|
|
FAR const struct usbhost_epdesc_s *epdesc,
|
|
FAR usbhost_ep_t *ep);
|
|
static int stm32_xfrep_alloc(FAR struct stm32_usbhost_s *priv,
|
|
FAR const struct usbhost_epdesc_s *epdesc,
|
|
FAR usbhost_ep_t *ep);
|
|
|
|
/* Control/data transfer logic **********************************************/
|
|
|
|
static void stm32_transfer_start(FAR struct stm32_usbhost_s *priv, int chidx);
|
|
#if 0 /* Not used */
|
|
static inline uint16_t stm32_getframe(void);
|
|
#endif
|
|
static int stm32_ctrl_sendsetup(FAR struct stm32_usbhost_s *priv,
|
|
FAR struct stm32_ctrlinfo_s *ep0,
|
|
FAR const struct usb_ctrlreq_s *req);
|
|
static int stm32_ctrl_senddata(FAR struct stm32_usbhost_s *priv,
|
|
FAR struct stm32_ctrlinfo_s *ep0,
|
|
FAR uint8_t *buffer, unsigned int buflen);
|
|
static int stm32_ctrl_recvdata(FAR struct stm32_usbhost_s *priv,
|
|
FAR struct stm32_ctrlinfo_s *ep0,
|
|
FAR uint8_t *buffer, unsigned int buflen);
|
|
static int stm32_in_setup(FAR struct stm32_usbhost_s *priv, int chidx);
|
|
static ssize_t stm32_in_transfer(FAR struct stm32_usbhost_s *priv, int chidx,
|
|
FAR uint8_t *buffer, size_t buflen);
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
static void stm32_in_next(FAR struct stm32_usbhost_s *priv,
|
|
FAR struct stm32_chan_s *chan);
|
|
static int stm32_in_asynch(FAR struct stm32_usbhost_s *priv, int chidx,
|
|
FAR uint8_t *buffer, size_t buflen,
|
|
usbhost_asynch_t callback, FAR void *arg);
|
|
#endif
|
|
static int stm32_out_setup(FAR struct stm32_usbhost_s *priv, int chidx);
|
|
static ssize_t stm32_out_transfer(FAR struct stm32_usbhost_s *priv, int chidx,
|
|
FAR uint8_t *buffer, size_t buflen);
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
static void stm32_out_next(FAR struct stm32_usbhost_s *priv,
|
|
FAR struct stm32_chan_s *chan);
|
|
static int stm32_out_asynch(FAR struct stm32_usbhost_s *priv, int chidx,
|
|
FAR uint8_t *buffer, size_t buflen,
|
|
usbhost_asynch_t callback, FAR void *arg);
|
|
#endif
|
|
|
|
/* Interrupt handling *******************************************************/
|
|
|
|
/* Lower level interrupt handlers */
|
|
|
|
static void stm32_gint_wrpacket(FAR struct stm32_usbhost_s *priv,
|
|
FAR uint8_t *buffer, int chidx, int buflen);
|
|
static inline void stm32_gint_hcinisr(FAR struct stm32_usbhost_s *priv,
|
|
int chidx);
|
|
static inline void stm32_gint_hcoutisr(FAR struct stm32_usbhost_s *priv,
|
|
int chidx);
|
|
static void stm32_gint_connected(FAR struct stm32_usbhost_s *priv);
|
|
static void stm32_gint_disconnected(FAR struct stm32_usbhost_s *priv);
|
|
|
|
/* Second level interrupt handlers */
|
|
|
|
#ifdef CONFIG_STM32H7_OTG_SOFINTR
|
|
static inline void stm32_gint_sofisr(FAR struct stm32_usbhost_s *priv);
|
|
#endif
|
|
static inline void stm32_gint_rxflvlisr(FAR struct stm32_usbhost_s *priv);
|
|
static inline void stm32_gint_nptxfeisr(FAR struct stm32_usbhost_s *priv);
|
|
static inline void stm32_gint_ptxfeisr(FAR struct stm32_usbhost_s *priv);
|
|
static inline void stm32_gint_hcisr(FAR struct stm32_usbhost_s *priv);
|
|
static inline void stm32_gint_hprtisr(FAR struct stm32_usbhost_s *priv);
|
|
static inline void stm32_gint_discisr(FAR struct stm32_usbhost_s *priv);
|
|
static inline void stm32_gint_ipxfrisr(FAR struct stm32_usbhost_s *priv);
|
|
|
|
/* First level, global interrupt handler */
|
|
|
|
static int stm32_gint_isr(int irq, FAR void *context, FAR void *arg);
|
|
|
|
/* Interrupt controls */
|
|
|
|
static void stm32_gint_enable(void);
|
|
static void stm32_gint_disable(void);
|
|
static inline void stm32_hostinit_enable(void);
|
|
static void stm32_txfe_enable(FAR struct stm32_usbhost_s *priv, int chidx);
|
|
|
|
/* USB host controller operations *******************************************/
|
|
|
|
static int stm32_wait(FAR struct usbhost_connection_s *conn,
|
|
FAR struct usbhost_hubport_s **hport);
|
|
static int stm32_rh_enumerate(FAR struct stm32_usbhost_s *priv,
|
|
FAR struct usbhost_connection_s *conn,
|
|
FAR struct usbhost_hubport_s *hport);
|
|
static int stm32_enumerate(FAR struct usbhost_connection_s *conn,
|
|
FAR struct usbhost_hubport_s *hport);
|
|
|
|
static int stm32_ep0configure(FAR struct usbhost_driver_s *drvr,
|
|
usbhost_ep_t ep0, uint8_t funcaddr, uint8_t speed,
|
|
uint16_t maxpacketsize);
|
|
static int stm32_epalloc(FAR struct usbhost_driver_s *drvr,
|
|
FAR const FAR struct usbhost_epdesc_s *epdesc,
|
|
FAR usbhost_ep_t *ep);
|
|
static int stm32_epfree(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep);
|
|
static int stm32_alloc(FAR struct usbhost_driver_s *drvr,
|
|
FAR uint8_t **buffer, FAR size_t *maxlen);
|
|
static int stm32_free(FAR struct usbhost_driver_s *drvr, FAR uint8_t *buffer);
|
|
static int stm32_ioalloc(FAR struct usbhost_driver_s *drvr,
|
|
FAR uint8_t **buffer, size_t buflen);
|
|
static int stm32_iofree(FAR struct usbhost_driver_s *drvr, FAR uint8_t *buffer);
|
|
static int stm32_ctrlin(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0,
|
|
const struct usb_ctrlreq_s *req,
|
|
FAR uint8_t *buffer);
|
|
static int stm32_ctrlout(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0,
|
|
FAR const struct usb_ctrlreq_s *req,
|
|
FAR const uint8_t *buffer);
|
|
static ssize_t stm32_transfer(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep,
|
|
FAR uint8_t *buffer, size_t buflen);
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
static int stm32_asynch(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep,
|
|
FAR uint8_t *buffer, size_t buflen,
|
|
usbhost_asynch_t callback, FAR void *arg);
|
|
#endif
|
|
static int stm32_cancel(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep);
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
static int stm32_connect(FAR struct usbhost_driver_s *drvr,
|
|
FAR struct usbhost_hubport_s *hport,
|
|
bool connected);
|
|
#endif
|
|
static void stm32_disconnect(FAR struct usbhost_driver_s *drvr,
|
|
FAR struct usbhost_hubport_s *hport);
|
|
|
|
/* Initialization ***********************************************************/
|
|
|
|
static void stm32_portreset(FAR struct stm32_usbhost_s *priv);
|
|
static void stm32_flush_txfifos(uint32_t txfnum);
|
|
static void stm32_flush_rxfifo(void);
|
|
static void stm32_vbusdrive(FAR struct stm32_usbhost_s *priv, bool state);
|
|
static void stm32_host_initialize(FAR struct stm32_usbhost_s *priv);
|
|
|
|
static inline void stm32_sw_initialize(FAR struct stm32_usbhost_s *priv);
|
|
static inline int stm32_hw_initialize(FAR struct stm32_usbhost_s *priv);
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
/* In this driver implementation, support is provided for only a single a single
|
|
* USB device. All status information can be simply retained in a single global
|
|
* instance.
|
|
*/
|
|
|
|
static struct stm32_usbhost_s g_usbhost;
|
|
|
|
/* This is the connection/enumeration interface */
|
|
|
|
static struct usbhost_connection_s g_usbconn =
|
|
{
|
|
.wait = stm32_wait,
|
|
.enumerate = stm32_enumerate,
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_printreg
|
|
*
|
|
* Description:
|
|
* Print the contents of an STM32xx register operation
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_STM32H7_USBHOST_REGDEBUG
|
|
static void stm32_printreg(uint32_t addr, uint32_t val, bool iswrite)
|
|
{
|
|
llerr("%08x%s%08x\n", addr, iswrite ? "<-" : "->", val);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_checkreg
|
|
*
|
|
* Description:
|
|
* Get the contents of an STM32 register
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_STM32H7_USBHOST_REGDEBUG
|
|
static void stm32_checkreg(uint32_t addr, uint32_t val, bool iswrite)
|
|
{
|
|
static uint32_t prevaddr = 0;
|
|
static uint32_t preval = 0;
|
|
static uint32_t count = 0;
|
|
static bool prevwrite = false;
|
|
|
|
/* Is this the same value that we read from/wrote to the same register last time?
|
|
* Are we polling the register? If so, suppress the output.
|
|
*/
|
|
|
|
if (addr == prevaddr && val == preval && prevwrite == iswrite)
|
|
{
|
|
/* Yes.. Just increment the count */
|
|
|
|
count++;
|
|
}
|
|
else
|
|
{
|
|
/* No this is a new address or value or operation. Were there any
|
|
* duplicate accesses before this one?
|
|
*/
|
|
|
|
if (count > 0)
|
|
{
|
|
/* Yes.. Just one? */
|
|
|
|
if (count == 1)
|
|
{
|
|
/* Yes.. Just one */
|
|
|
|
stm32_printreg(prevaddr, preval, prevwrite);
|
|
}
|
|
else
|
|
{
|
|
/* No.. More than one. */
|
|
|
|
llerr("[repeats %d more times]\n", count);
|
|
}
|
|
}
|
|
|
|
/* Save the new address, value, count, and operation for next time */
|
|
|
|
prevaddr = addr;
|
|
preval = val;
|
|
count = 0;
|
|
prevwrite = iswrite;
|
|
|
|
/* Show the new regisgter access */
|
|
|
|
stm32_printreg(addr, val, iswrite);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_getreg
|
|
*
|
|
* Description:
|
|
* Get the contents of an STM32 register
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_STM32H7_USBHOST_REGDEBUG
|
|
static uint32_t stm32_getreg(uint32_t addr)
|
|
{
|
|
/* Read the value from the register */
|
|
|
|
uint32_t val = getreg32(addr);
|
|
|
|
/* Check if we need to print this value */
|
|
|
|
stm32_checkreg(addr, val, false);
|
|
return val;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_putreg
|
|
*
|
|
* Description:
|
|
* Set the contents of an STM32 register to a value
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_STM32H7_USBHOST_REGDEBUG
|
|
static void stm32_putreg(uint32_t addr, uint32_t val)
|
|
{
|
|
/* Check if we need to print this value */
|
|
|
|
stm32_checkreg(addr, val, true);
|
|
|
|
/* Write the value */
|
|
|
|
putreg32(val, addr);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_modifyreg
|
|
*
|
|
* Description:
|
|
* Modify selected bits of an STM32 register.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void stm32_modifyreg(uint32_t addr, uint32_t clrbits,
|
|
uint32_t setbits)
|
|
{
|
|
stm32_putreg(addr, (((stm32_getreg(addr)) & ~clrbits) | setbits));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_takesem
|
|
*
|
|
* Description:
|
|
* This is just a wrapper to handle the annoying behavior of semaphore
|
|
* waits that return due to the receipt of a signal.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_takesem(sem_t *sem)
|
|
{
|
|
nxsem_wait_uninterruptible(sem);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_getle16
|
|
*
|
|
* Description:
|
|
* Get a (possibly unaligned) 16-bit little endian value.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline uint16_t stm32_getle16(const uint8_t *val)
|
|
{
|
|
return (uint16_t)val[1] << 8 | (uint16_t)val[0];
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_chan_alloc
|
|
*
|
|
* Description:
|
|
* Allocate a channel.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int stm32_chan_alloc(FAR struct stm32_usbhost_s *priv)
|
|
{
|
|
int chidx;
|
|
|
|
/* Search the table of channels */
|
|
|
|
for (chidx = 0; chidx < STM32_NHOST_CHANNELS; chidx++)
|
|
{
|
|
/* Is this channel available? */
|
|
|
|
if (!priv->chan[chidx].inuse)
|
|
{
|
|
/* Yes... make it "in use" and return the index */
|
|
|
|
priv->chan[chidx].inuse = true;
|
|
return chidx;
|
|
}
|
|
}
|
|
|
|
/* All of the channels are "in-use" */
|
|
|
|
return -EBUSY;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_chan_free
|
|
*
|
|
* Description:
|
|
* Free a previoiusly allocated channel.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_chan_free(FAR struct stm32_usbhost_s *priv, int chidx)
|
|
{
|
|
DEBUGASSERT((unsigned)chidx < STM32_NHOST_CHANNELS);
|
|
|
|
/* Halt the channel */
|
|
|
|
stm32_chan_halt(priv, chidx, CHREASON_FREED);
|
|
|
|
/* Mark the channel available */
|
|
|
|
priv->chan[chidx].inuse = false;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_chan_freeall
|
|
*
|
|
* Description:
|
|
* Free all channels.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void stm32_chan_freeall(FAR struct stm32_usbhost_s *priv)
|
|
{
|
|
uint8_t chidx;
|
|
|
|
/* Free all host channels */
|
|
|
|
for (chidx = 2; chidx < STM32_NHOST_CHANNELS; chidx ++)
|
|
{
|
|
stm32_chan_free(priv, chidx);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_chan_configure
|
|
*
|
|
* Description:
|
|
* Configure or re-configure a host channel. Host channels are configured
|
|
* when endpoint is allocated and EP0 (only) is re-configured with the
|
|
* max packet size or device address changes.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_chan_configure(FAR struct stm32_usbhost_s *priv, int chidx)
|
|
{
|
|
FAR struct stm32_chan_s *chan = &priv->chan[chidx];
|
|
uint32_t regval;
|
|
|
|
/* Clear any old pending interrupts for this host channel. */
|
|
|
|
stm32_putreg(STM32_OTG_HCINT(chidx), 0xffffffff);
|
|
|
|
/* Enable channel interrupts required for transfers on this channel. */
|
|
|
|
regval = 0;
|
|
|
|
switch (chan->eptype)
|
|
{
|
|
case OTG_EPTYPE_CTRL:
|
|
case OTG_EPTYPE_BULK:
|
|
{
|
|
#ifdef HAVE_USBHOST_TRACE_VERBOSE
|
|
uint16_t intrace;
|
|
uint16_t outtrace;
|
|
|
|
/* Determine the definitive trace ID to use below */
|
|
|
|
if (chan->eptype == OTG_EPTYPE_CTRL)
|
|
{
|
|
intrace = OTG_VTRACE2_CHANCONF_CTRL_IN;
|
|
outtrace = OTG_VTRACE2_CHANCONF_CTRL_OUT;
|
|
}
|
|
else
|
|
{
|
|
intrace = OTG_VTRACE2_CHANCONF_BULK_IN;
|
|
outtrace = OTG_VTRACE2_CHANCONF_BULK_OUT;
|
|
}
|
|
#endif
|
|
|
|
/* Interrupts required for CTRL and BULK endpoints */
|
|
|
|
regval |= (OTG_HCINT_XFRC | OTG_HCINT_STALL | OTG_HCINT_NAK |
|
|
OTG_HCINT_TXERR | OTG_HCINT_DTERR);
|
|
|
|
/* Additional setting for IN/OUT endpoints */
|
|
|
|
if (chan->in)
|
|
{
|
|
usbhost_vtrace2(intrace, chidx, chan->epno);
|
|
regval |= OTG_HCINT_BBERR;
|
|
}
|
|
else
|
|
{
|
|
usbhost_vtrace2(outtrace, chidx, chan->epno);
|
|
regval |= OTG_HCINT_NYET;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case OTG_EPTYPE_INTR:
|
|
{
|
|
/* Interrupts required for INTR endpoints */
|
|
|
|
regval |= (OTG_HCINT_XFRC | OTG_HCINT_STALL | OTG_HCINT_NAK |
|
|
OTG_HCINT_TXERR | OTG_HCINT_FRMOR | OTG_HCINT_DTERR);
|
|
|
|
/* Additional setting for IN endpoints */
|
|
|
|
if (chan->in)
|
|
{
|
|
usbhost_vtrace2(OTG_VTRACE2_CHANCONF_INTR_IN, chidx,
|
|
chan->epno);
|
|
regval |= OTG_HCINT_BBERR;
|
|
}
|
|
#ifdef HAVE_USBHOST_TRACE_VERBOSE
|
|
else
|
|
{
|
|
usbhost_vtrace2(OTG_VTRACE2_CHANCONF_INTR_OUT, chidx,
|
|
chan->epno);
|
|
}
|
|
#endif
|
|
}
|
|
break;
|
|
|
|
case OTG_EPTYPE_ISOC:
|
|
{
|
|
/* Interrupts required for ISOC endpoints */
|
|
|
|
regval |= (OTG_HCINT_XFRC | OTG_HCINT_ACK | OTG_HCINT_FRMOR);
|
|
|
|
/* Additional setting for IN endpoints */
|
|
|
|
if (chan->in)
|
|
{
|
|
usbhost_vtrace2(OTG_VTRACE2_CHANCONF_ISOC_IN, chidx,
|
|
chan->epno);
|
|
regval |= (OTG_HCINT_TXERR | OTG_HCINT_BBERR);
|
|
}
|
|
#ifdef HAVE_USBHOST_TRACE_VERBOSE
|
|
else
|
|
{
|
|
usbhost_vtrace2(OTG_VTRACE2_CHANCONF_ISOC_OUT, chidx,
|
|
chan->epno);
|
|
}
|
|
#endif
|
|
}
|
|
break;
|
|
}
|
|
|
|
stm32_putreg(STM32_OTG_HCINTMSK(chidx), regval);
|
|
|
|
/* Enable the top level host channel interrupt. */
|
|
|
|
stm32_modifyreg(STM32_OTG_HAINTMSK, 0, OTG_HAINT(chidx));
|
|
|
|
/* Make sure host channel interrupts are enabled. */
|
|
|
|
stm32_modifyreg(STM32_OTG_GINTMSK, 0, OTG_GINT_HC);
|
|
|
|
/* Program the HCCHAR register */
|
|
|
|
regval = ((uint32_t)chan->maxpacket << OTG_HCCHAR_MPSIZ_SHIFT) |
|
|
((uint32_t)chan->epno << OTG_HCCHAR_EPNUM_SHIFT) |
|
|
((uint32_t)chan->eptype << OTG_HCCHAR_EPTYP_SHIFT) |
|
|
((uint32_t)chan->funcaddr << OTG_HCCHAR_DAD_SHIFT);
|
|
|
|
/* Special case settings for low speed devices */
|
|
|
|
if (chan->speed == USB_SPEED_LOW)
|
|
{
|
|
regval |= OTG_HCCHAR_LSDEV;
|
|
}
|
|
|
|
/* Special case settings for IN endpoints */
|
|
|
|
if (chan->in)
|
|
{
|
|
regval |= OTG_HCCHAR_EPDIR_IN;
|
|
}
|
|
|
|
/* Special case settings for INTR endpoints */
|
|
|
|
if (chan->eptype == OTG_EPTYPE_INTR)
|
|
{
|
|
regval |= OTG_HCCHAR_ODDFRM;
|
|
}
|
|
|
|
/* Write the channel configuration */
|
|
|
|
stm32_putreg(STM32_OTG_HCCHAR(chidx), regval);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_chan_halt
|
|
*
|
|
* Description:
|
|
* Halt the channel associated with 'chidx' by setting the CHannel DISable
|
|
* (CHDIS) bit in in the HCCHAR register.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_chan_halt(FAR struct stm32_usbhost_s *priv, int chidx,
|
|
enum stm32_chreason_e chreason)
|
|
{
|
|
uint32_t hcchar;
|
|
uint32_t intmsk;
|
|
uint32_t eptype;
|
|
unsigned int avail;
|
|
|
|
/* Save the reason for the halt. We need this in the channel halt interrupt
|
|
* handling logic to know what to do next.
|
|
*/
|
|
|
|
usbhost_vtrace2(OTG_VTRACE2_CHANHALT, chidx, chreason);
|
|
|
|
priv->chan[chidx].chreason = (uint8_t)chreason;
|
|
|
|
/* "The application can disable any channel by programming the OTG_FS_HCCHARx
|
|
* register with the CHDIS and CHENA bits set to 1. This enables the OTG_FS
|
|
* host to flush the posted requests (if any) and generates a channel halted
|
|
* interrupt. The application must wait for the CHH interrupt in OTG_FS_HCINTx
|
|
* before reallocating the channel for other transactions. The OTG_FS host
|
|
* does not interrupt the transaction that has already been started on the
|
|
* USB."
|
|
*/
|
|
|
|
hcchar = stm32_getreg(STM32_OTG_HCCHAR(chidx));
|
|
hcchar |= (OTG_HCCHAR_CHDIS | OTG_HCCHAR_CHENA);
|
|
|
|
/* Get the endpoint type from the HCCHAR register */
|
|
|
|
eptype = hcchar & OTG_HCCHAR_EPTYP_MASK;
|
|
|
|
/* Check for space in the Tx FIFO to issue the halt.
|
|
*
|
|
* "Before disabling a channel, the application must ensure that there is at
|
|
* least one free space available in the non-periodic request queue (when
|
|
* disabling a non-periodic channel) or the periodic request queue (when
|
|
* disabling a periodic channel). The application can simply flush the
|
|
* posted requests when the Request queue is full (before disabling the
|
|
* channel), by programming the OTG_FS_HCCHARx register with the CHDIS bit
|
|
* set to 1, and the CHENA bit cleared to 0.
|
|
*/
|
|
|
|
if (eptype == OTG_HCCHAR_EPTYP_CTRL || eptype == OTG_HCCHAR_EPTYP_BULK)
|
|
{
|
|
/* Get the number of words available in the non-periodic Tx FIFO. */
|
|
|
|
avail = stm32_getreg(STM32_OTG_HNPTXSTS) & OTG_HNPTXSTS_NPTXFSAV_MASK;
|
|
}
|
|
else /* if (eptype == OTG_HCCHAR_EPTYP_ISOC || eptype == OTG_HCCHAR_EPTYP_INTR) */
|
|
{
|
|
/* Get the number of words available in the non-periodic Tx FIFO. */
|
|
|
|
avail = stm32_getreg(STM32_OTG_HPTXSTS) & OTG_HPTXSTS_PTXFSAVL_MASK;
|
|
}
|
|
|
|
/* Check if there is any space available in the Tx FIFO. */
|
|
|
|
if (avail == 0)
|
|
{
|
|
/* The Tx FIFO is full... disable the channel to flush the requests */
|
|
|
|
hcchar &= ~OTG_HCCHAR_CHENA;
|
|
}
|
|
|
|
/* Unmask the CHannel Halted (CHH) interrupt */
|
|
|
|
intmsk = stm32_getreg(STM32_OTG_HCINTMSK(chidx));
|
|
intmsk |= OTG_HCINT_CHH;
|
|
stm32_putreg(STM32_OTG_HCINTMSK(chidx), intmsk);
|
|
|
|
/* Halt the channel by setting CHDIS (and maybe CHENA) in the HCCHAR */
|
|
|
|
stm32_putreg(STM32_OTG_HCCHAR(chidx), hcchar);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_chan_waitsetup
|
|
*
|
|
* Description:
|
|
* Set the request for the transfer complete event well BEFORE enabling
|
|
* the transfer (as soon as we are absolutely committed to the transfer).
|
|
* We do this to minimize race conditions. This logic would have to be
|
|
* expanded if we want to have more than one packet in flight at a time!
|
|
*
|
|
* Assumptions:
|
|
* Called from a normal thread context BEFORE the transfer has been started.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int stm32_chan_waitsetup(FAR struct stm32_usbhost_s *priv,
|
|
FAR struct stm32_chan_s *chan)
|
|
{
|
|
irqstate_t flags = enter_critical_section();
|
|
int ret = -ENODEV;
|
|
|
|
/* Is the device still connected? */
|
|
|
|
if (priv->connected)
|
|
{
|
|
/* Yes.. then set waiter to indicate that we expect to be informed when
|
|
* either (1) the device is disconnected, or (2) the transfer completed.
|
|
*/
|
|
|
|
chan->waiter = true;
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
chan->callback = NULL;
|
|
chan->arg = NULL;
|
|
#endif
|
|
ret = OK;
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_chan_asynchsetup
|
|
*
|
|
* Description:
|
|
* Set the request for the transfer complete event well BEFORE enabling the
|
|
* transfer (as soon as we are absolutely committed to the to avoid transfer).
|
|
* We do this to minimize race conditions. This logic would have to be expanded
|
|
* if we want to have more than one packet in flight at a time!
|
|
*
|
|
* Assumptions:
|
|
* Might be called from the level of an interrupt handler
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
static int stm32_chan_asynchsetup(FAR struct stm32_usbhost_s *priv,
|
|
FAR struct stm32_chan_s *chan,
|
|
usbhost_asynch_t callback, FAR void *arg)
|
|
{
|
|
irqstate_t flags = enter_critical_section();
|
|
int ret = -ENODEV;
|
|
|
|
/* Is the device still connected? */
|
|
|
|
if (priv->connected)
|
|
{
|
|
/* Yes.. then set waiter to indicate that we expect to be informed when
|
|
* either (1) the device is disconnected, or (2) the transfer completed.
|
|
*/
|
|
|
|
chan->waiter = false;
|
|
chan->callback = callback;
|
|
chan->arg = arg;
|
|
ret = OK;
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_chan_wait
|
|
*
|
|
* Description:
|
|
* Wait for a transfer on a channel to complete.
|
|
*
|
|
* Assumptions:
|
|
* Called from a normal thread context
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int stm32_chan_wait(FAR struct stm32_usbhost_s *priv,
|
|
FAR struct stm32_chan_s *chan)
|
|
{
|
|
irqstate_t flags;
|
|
int ret;
|
|
|
|
/* Disable interrupts so that the following operations will be atomic. On
|
|
* the OTG FS global interrupt needs to be disabled. However, here we
|
|
* disable all interrupts to exploit that fact that interrupts will be re-
|
|
* enabled while we wait.
|
|
*/
|
|
|
|
flags = enter_critical_section();
|
|
|
|
/* Loop, testing for an end of transfer condition. The channel 'result'
|
|
* was set to EBUSY and 'waiter' was set to true before the transfer; 'waiter'
|
|
* will be set to false and 'result' will be set appropriately when the
|
|
* transfer is completed.
|
|
*/
|
|
|
|
do
|
|
{
|
|
/* Wait for the transfer to complete. NOTE the transfer may already
|
|
* completed before we get here or the transfer may complete while we
|
|
* wait here.
|
|
*/
|
|
|
|
nxsem_wait_uninterruptible(&chan->waitsem);
|
|
}
|
|
while (chan->waiter);
|
|
|
|
/* The transfer is complete re-enable interrupts and return the result */
|
|
|
|
ret = -(int)chan->result;
|
|
leave_critical_section(flags);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_chan_wakeup
|
|
*
|
|
* Description:
|
|
* A channel transfer has completed... wakeup any threads waiting for the
|
|
* transfer to complete.
|
|
*
|
|
* Assumptions:
|
|
* This function is called from the transfer complete interrupt handler for
|
|
* the channel. Interrupts are disabled.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_chan_wakeup(FAR struct stm32_usbhost_s *priv,
|
|
FAR struct stm32_chan_s *chan)
|
|
{
|
|
/* Is the transfer complete? */
|
|
|
|
if (chan->result != EBUSY)
|
|
{
|
|
/* Is there a thread waiting for this transfer to complete? */
|
|
|
|
if (chan->waiter)
|
|
{
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
/* Yes.. there should not also be a callback scheduled */
|
|
|
|
DEBUGASSERT(chan->callback == NULL);
|
|
#endif
|
|
/* Wake'em up! */
|
|
|
|
usbhost_vtrace2(chan->in ? OTG_VTRACE2_CHANWAKEUP_IN :
|
|
OTG_VTRACE2_CHANWAKEUP_OUT,
|
|
chan->epno, chan->result);
|
|
|
|
stm32_givesem(&chan->waitsem);
|
|
chan->waiter = false;
|
|
}
|
|
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
/* No.. is an asynchronous callback expected when the transfer
|
|
* completes?
|
|
*/
|
|
|
|
else if (chan->callback)
|
|
{
|
|
/* Handle continuation of IN/OUT pipes */
|
|
|
|
if (chan->in)
|
|
{
|
|
stm32_in_next(priv, chan);
|
|
}
|
|
else
|
|
{
|
|
stm32_out_next(priv, chan);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_ctrlchan_alloc
|
|
*
|
|
* Description:
|
|
* Allocate and configured channels for a control pipe.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int stm32_ctrlchan_alloc(FAR struct stm32_usbhost_s *priv,
|
|
uint8_t epno, uint8_t funcaddr, uint8_t speed,
|
|
FAR struct stm32_ctrlinfo_s *ctrlep)
|
|
{
|
|
FAR struct stm32_chan_s *chan;
|
|
int inndx;
|
|
int outndx;
|
|
|
|
outndx = stm32_chan_alloc(priv);
|
|
if (outndx < 0)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ctrlep->outndx = outndx;
|
|
chan = &priv->chan[outndx];
|
|
chan->epno = epno;
|
|
chan->in = false;
|
|
chan->eptype = OTG_EPTYPE_CTRL;
|
|
chan->funcaddr = funcaddr;
|
|
chan->speed = speed;
|
|
chan->interval = 0;
|
|
chan->maxpacket = STM32_EP0_DEF_PACKET_SIZE;
|
|
chan->indata1 = false;
|
|
chan->outdata1 = false;
|
|
|
|
/* Configure control OUT channels */
|
|
|
|
stm32_chan_configure(priv, outndx);
|
|
|
|
/* Allocate and initialize the control IN channel */
|
|
|
|
inndx = stm32_chan_alloc(priv);
|
|
if (inndx < 0)
|
|
{
|
|
stm32_chan_free(priv, outndx);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ctrlep->inndx = inndx;
|
|
chan = &priv->chan[inndx];
|
|
chan->epno = epno;
|
|
chan->in = true;
|
|
chan->eptype = OTG_EPTYPE_CTRL;
|
|
chan->funcaddr = funcaddr;
|
|
chan->speed = speed;
|
|
chan->interval = 0;
|
|
chan->maxpacket = STM32_EP0_DEF_PACKET_SIZE;
|
|
chan->indata1 = false;
|
|
chan->outdata1 = false;
|
|
|
|
/* Configure control IN channels */
|
|
|
|
stm32_chan_configure(priv, inndx);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_ctrlep_alloc
|
|
*
|
|
* Description:
|
|
* Allocate a container and channels for control pipe.
|
|
*
|
|
* Input Parameters:
|
|
* priv - The private USB host driver state.
|
|
* epdesc - Describes the endpoint to be allocated.
|
|
* ep - A memory location provided by the caller in which to receive the
|
|
* allocated endpoint descriptor.
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value
|
|
* is returned indicating the nature of the failure
|
|
*
|
|
* Assumptions:
|
|
* This function will *not* be called from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int stm32_ctrlep_alloc(FAR struct stm32_usbhost_s *priv,
|
|
FAR const struct usbhost_epdesc_s *epdesc,
|
|
FAR usbhost_ep_t *ep)
|
|
{
|
|
FAR struct usbhost_hubport_s *hport;
|
|
FAR struct stm32_ctrlinfo_s *ctrlep;
|
|
int ret;
|
|
|
|
/* Sanity check. NOTE that this method should only be called if a device is
|
|
* connected (because we need a valid low speed indication).
|
|
*/
|
|
|
|
DEBUGASSERT(epdesc->hport != NULL);
|
|
hport = epdesc->hport;
|
|
|
|
/* Allocate a container for the control endpoint */
|
|
|
|
ctrlep = (FAR struct stm32_ctrlinfo_s *)kmm_malloc(sizeof(struct stm32_ctrlinfo_s));
|
|
if (ctrlep == NULL)
|
|
{
|
|
uerr("ERROR: Failed to allocate control endpoint container\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Then allocate and configure the IN/OUT channels */
|
|
|
|
ret = stm32_ctrlchan_alloc(priv, epdesc->addr & USB_EPNO_MASK,
|
|
hport->funcaddr, hport->speed, ctrlep);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: stm32_ctrlchan_alloc failed: %d\n", ret);
|
|
kmm_free(ctrlep);
|
|
return ret;
|
|
}
|
|
|
|
/* Return a pointer to the control pipe container as the pipe "handle" */
|
|
|
|
*ep = (usbhost_ep_t)ctrlep;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_xfrep_alloc
|
|
*
|
|
* Description:
|
|
* Allocate and configure one unidirectional endpoint.
|
|
*
|
|
* Input Parameters:
|
|
* priv - The private USB host driver state.
|
|
* epdesc - Describes the endpoint to be allocated.
|
|
* ep - A memory location provided by the caller in which to receive the
|
|
* allocated endpoint descriptor.
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value
|
|
* is returned indicating the nature of the failure
|
|
*
|
|
* Assumptions:
|
|
* This function will *not* be called from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int stm32_xfrep_alloc(FAR struct stm32_usbhost_s *priv,
|
|
FAR const struct usbhost_epdesc_s *epdesc,
|
|
FAR usbhost_ep_t *ep)
|
|
{
|
|
struct usbhost_hubport_s *hport;
|
|
FAR struct stm32_chan_s *chan;
|
|
int chidx;
|
|
|
|
/* Sanity check. NOTE that this method should only be called if a device is
|
|
* connected (because we need a valid low speed indication).
|
|
*/
|
|
|
|
DEBUGASSERT(epdesc->hport != NULL);
|
|
hport = epdesc->hport;
|
|
|
|
/* Allocate a host channel for the endpoint */
|
|
|
|
chidx = stm32_chan_alloc(priv);
|
|
if (chidx < 0)
|
|
{
|
|
uerr("ERROR: Failed to allocate a host channel\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Decode the endpoint descriptor to initialize the channel data structures.
|
|
* Note: Here we depend on the fact that the endpoint point type is
|
|
* encoded in the same way in the endpoint descriptor as it is in the OTG
|
|
* HS hardware.
|
|
*/
|
|
|
|
chan = &priv->chan[chidx];
|
|
chan->epno = epdesc->addr & USB_EPNO_MASK;
|
|
chan->in = epdesc->in;
|
|
chan->eptype = epdesc->xfrtype;
|
|
chan->funcaddr = hport->funcaddr;
|
|
chan->speed = hport->speed;
|
|
chan->interval = epdesc->interval;
|
|
chan->maxpacket = epdesc->mxpacketsize;
|
|
chan->indata1 = false;
|
|
chan->outdata1 = false;
|
|
|
|
/* Then configure the endpoint */
|
|
|
|
stm32_chan_configure(priv, chidx);
|
|
|
|
/* Return the index to the allocated channel as the endpoint "handle" */
|
|
|
|
*ep = (usbhost_ep_t)chidx;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_transfer_start
|
|
*
|
|
* Description:
|
|
* Start at transfer on the select IN or OUT channel.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_transfer_start(FAR struct stm32_usbhost_s *priv, int chidx)
|
|
{
|
|
FAR struct stm32_chan_s *chan;
|
|
uint32_t regval;
|
|
unsigned int npackets;
|
|
unsigned int maxpacket;
|
|
unsigned int avail;
|
|
unsigned int wrsize;
|
|
unsigned int minsize;
|
|
|
|
/* Set up the initial state of the transfer */
|
|
|
|
chan = &priv->chan[chidx];
|
|
|
|
usbhost_vtrace2(OTG_VTRACE2_STARTTRANSFER, chidx, chan->buflen);
|
|
|
|
chan->result = EBUSY;
|
|
chan->inflight = 0;
|
|
chan->xfrd = 0;
|
|
priv->chidx = chidx;
|
|
|
|
/* Compute the expected number of packets associated to the transfer.
|
|
* If the transfer length is zero (or less than the size of one maximum
|
|
* size packet), then one packet is expected.
|
|
*/
|
|
|
|
/* If the transfer size is greater than one packet, then calculate the
|
|
* number of packets that will be received/sent, including any partial
|
|
* final packet.
|
|
*/
|
|
|
|
maxpacket = chan->maxpacket;
|
|
|
|
if (chan->buflen > maxpacket)
|
|
{
|
|
npackets = (chan->buflen + maxpacket - 1) / maxpacket;
|
|
|
|
/* Clip if the buffer length if it exceeds the maximum number of
|
|
* packets that can be transferred (this should not happen).
|
|
*/
|
|
|
|
if (npackets > STM32_MAX_PKTCOUNT)
|
|
{
|
|
npackets = STM32_MAX_PKTCOUNT;
|
|
chan->buflen = STM32_MAX_PKTCOUNT * maxpacket;
|
|
usbhost_trace2(OTG_TRACE2_CLIP, chidx, chan->buflen);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* One packet will be sent/received (might be a zero length packet) */
|
|
|
|
npackets = 1;
|
|
}
|
|
|
|
/* If it is an IN transfer, then adjust the size of the buffer UP to
|
|
* a full number of packets. Hmmm... couldn't this cause an overrun
|
|
* into unallocated memory?
|
|
*/
|
|
|
|
#if 0 /* Think about this */
|
|
if (chan->in)
|
|
{
|
|
/* Force the buffer length to an even multiple of maxpacket */
|
|
|
|
chan->buflen = npackets * maxpacket;
|
|
}
|
|
#endif
|
|
|
|
/* Save the number of packets in the transfer. We will need this in
|
|
* order to set the next data toggle correctly when the transfer
|
|
* completes.
|
|
*/
|
|
|
|
chan->npackets = (uint8_t)npackets;
|
|
|
|
/* Setup the HCTSIZn register */
|
|
|
|
regval = ((uint32_t)chan->buflen << OTG_HCTSIZ_XFRSIZ_SHIFT) |
|
|
((uint32_t)npackets << OTG_HCTSIZ_PKTCNT_SHIFT) |
|
|
((uint32_t)chan->pid << OTG_HCTSIZ_DPID_SHIFT);
|
|
stm32_putreg(STM32_OTG_HCTSIZ(chidx), regval);
|
|
|
|
/* Setup the HCCHAR register: Frame oddness and host channel enable */
|
|
|
|
regval = stm32_getreg(STM32_OTG_HCCHAR(chidx));
|
|
|
|
/* Set/clear the Odd Frame bit. Check for an even frame; if so set Odd
|
|
* Frame. This field is applicable for only periodic (isochronous and
|
|
* interrupt) channels.
|
|
*/
|
|
|
|
if ((stm32_getreg(STM32_OTG_HFNUM) & 1) == 0)
|
|
{
|
|
regval |= OTG_HCCHAR_ODDFRM;
|
|
}
|
|
else
|
|
{
|
|
regval &= ~OTG_HCCHAR_ODDFRM;
|
|
}
|
|
|
|
regval &= ~OTG_HCCHAR_CHDIS;
|
|
regval |= OTG_HCCHAR_CHENA;
|
|
stm32_putreg(STM32_OTG_HCCHAR(chidx), regval);
|
|
|
|
/* If this is an out transfer, then we need to do more.. we need to copy
|
|
* the outgoing data into the correct TxFIFO.
|
|
*/
|
|
|
|
if (!chan->in && chan->buflen > 0)
|
|
{
|
|
/* Handle non-periodic (CTRL and BULK) OUT transfers differently than
|
|
* periodic (INTR and ISOC) OUT transfers.
|
|
*/
|
|
|
|
minsize = MIN(chan->buflen, chan->maxpacket);
|
|
|
|
switch (chan->eptype)
|
|
{
|
|
case OTG_EPTYPE_CTRL: /* Non periodic transfer */
|
|
case OTG_EPTYPE_BULK:
|
|
{
|
|
/* Read the Non-periodic Tx FIFO status register */
|
|
|
|
regval = stm32_getreg(STM32_OTG_HNPTXSTS);
|
|
avail = ((regval & OTG_HNPTXSTS_NPTXFSAV_MASK) >>
|
|
OTG_HNPTXSTS_NPTXFSAV_SHIFT) << 2;
|
|
}
|
|
break;
|
|
|
|
/* Periodic transfer */
|
|
|
|
case OTG_EPTYPE_INTR:
|
|
case OTG_EPTYPE_ISOC:
|
|
{
|
|
/* Read the Non-periodic Tx FIFO status register */
|
|
|
|
regval = stm32_getreg(STM32_OTG_HPTXSTS);
|
|
avail = ((regval & OTG_HPTXSTS_PTXFSAVL_MASK) >>
|
|
OTG_HPTXSTS_PTXFSAVL_SHIFT) << 2;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
DEBUGASSERT(false);
|
|
return;
|
|
}
|
|
|
|
/* Is there space in the TxFIFO to hold the minimum size packet? */
|
|
|
|
if (minsize <= avail)
|
|
{
|
|
/* Yes.. Get the size of the biggest thing that we can put in the Tx FIFO now */
|
|
|
|
wrsize = chan->buflen;
|
|
if (wrsize > avail)
|
|
{
|
|
/* Clip the write size to the number of full, max sized packets
|
|
* that will fit in the Tx FIFO.
|
|
*/
|
|
|
|
unsigned int wrpackets = avail / chan->maxpacket;
|
|
wrsize = wrpackets * chan->maxpacket;
|
|
}
|
|
|
|
/* Write packet into the Tx FIFO. */
|
|
|
|
stm32_gint_wrpacket(priv, chan->buffer, chidx, wrsize);
|
|
}
|
|
|
|
/* Did we put the entire buffer into the Tx FIFO? */
|
|
|
|
if (chan->buflen > avail)
|
|
{
|
|
/* No, there was insufficient space to hold the entire transfer ...
|
|
* Enable the Tx FIFO interrupt to handle the transfer when the Tx
|
|
* FIFO becomes empty.
|
|
*/
|
|
|
|
stm32_txfe_enable(priv, chidx);
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_getframe
|
|
*
|
|
* Description:
|
|
* Get the current frame number. The frame number (FRNUM) field increments
|
|
* when a new SOF is transmitted on the USB, and is cleared to 0 when it
|
|
* reaches 0x3fff.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#if 0 /* Not used */
|
|
static inline uint16_t stm32_getframe(void)
|
|
{
|
|
return (uint16_t)(stm32_getreg(STM32_OTG_HFNUM) & OTG_HFNUM_FRNUM_MASK);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_ctrl_sendsetup
|
|
*
|
|
* Description:
|
|
* Send an IN/OUT SETUP packet.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int stm32_ctrl_sendsetup(FAR struct stm32_usbhost_s *priv,
|
|
FAR struct stm32_ctrlinfo_s *ep0,
|
|
FAR const struct usb_ctrlreq_s *req)
|
|
{
|
|
FAR struct stm32_chan_s *chan;
|
|
clock_t start;
|
|
clock_t elapsed;
|
|
int ret;
|
|
|
|
/* Loop while the device reports NAK (and a timeout is not exceeded */
|
|
|
|
chan = &priv->chan[ep0->outndx];
|
|
start = clock_systimer();
|
|
|
|
do
|
|
{
|
|
/* Send the SETUP packet */
|
|
|
|
chan->pid = OTG_PID_SETUP;
|
|
chan->buffer = (FAR uint8_t *)req;
|
|
chan->buflen = USB_SIZEOF_CTRLREQ;
|
|
chan->xfrd = 0;
|
|
|
|
/* Set up for the wait BEFORE starting the transfer */
|
|
|
|
ret = stm32_chan_waitsetup(priv, chan);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(OTG_TRACE1_DEVDISCONN, 0);
|
|
return ret;
|
|
}
|
|
|
|
/* Start the transfer */
|
|
|
|
stm32_transfer_start(priv, ep0->outndx);
|
|
|
|
/* Wait for the transfer to complete */
|
|
|
|
ret = stm32_chan_wait(priv, chan);
|
|
|
|
/* Return on success and for all failures other than EAGAIN. EAGAIN
|
|
* means that the device NAKed the SETUP command and that we should
|
|
* try a few more times.
|
|
*/
|
|
|
|
if (ret != -EAGAIN)
|
|
{
|
|
/* Output some debug information if the transfer failed */
|
|
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(OTG_TRACE1_TRNSFRFAILED, ret);
|
|
}
|
|
|
|
/* Return the result in any event */
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Get the elapsed time (in frames) */
|
|
|
|
elapsed = clock_systimer() - start;
|
|
}
|
|
while (elapsed < STM32_SETUP_DELAY);
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_ctrl_senddata
|
|
*
|
|
* Description:
|
|
* Send data in the data phase of an OUT control transfer. Or send status
|
|
* in the status phase of an IN control transfer
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int stm32_ctrl_senddata(FAR struct stm32_usbhost_s *priv,
|
|
FAR struct stm32_ctrlinfo_s *ep0,
|
|
FAR uint8_t *buffer, unsigned int buflen)
|
|
{
|
|
FAR struct stm32_chan_s *chan = &priv->chan[ep0->outndx];
|
|
int ret;
|
|
|
|
/* Save buffer information */
|
|
|
|
chan->buffer = buffer;
|
|
chan->buflen = buflen;
|
|
chan->xfrd = 0;
|
|
|
|
/* Set the DATA PID */
|
|
|
|
if (buflen == 0)
|
|
{
|
|
/* For status OUT stage with buflen == 0, set PID DATA1 */
|
|
|
|
chan->outdata1 = true;
|
|
}
|
|
|
|
/* Set the Data PID as per the outdata1 boolean */
|
|
|
|
chan->pid = chan->outdata1 ? OTG_PID_DATA1 : OTG_PID_DATA0;
|
|
|
|
/* Set up for the wait BEFORE starting the transfer */
|
|
|
|
ret = stm32_chan_waitsetup(priv, chan);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(OTG_TRACE1_DEVDISCONN, 0);
|
|
return ret;
|
|
}
|
|
|
|
/* Start the transfer */
|
|
|
|
stm32_transfer_start(priv, ep0->outndx);
|
|
|
|
/* Wait for the transfer to complete and return the result */
|
|
|
|
return stm32_chan_wait(priv, chan);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_ctrl_recvdata
|
|
*
|
|
* Description:
|
|
* Receive data in the data phase of an IN control transfer. Or receive status
|
|
* in the status phase of an OUT control transfer
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int stm32_ctrl_recvdata(FAR struct stm32_usbhost_s *priv,
|
|
FAR struct stm32_ctrlinfo_s *ep0,
|
|
FAR uint8_t *buffer, unsigned int buflen)
|
|
{
|
|
FAR struct stm32_chan_s *chan = &priv->chan[ep0->inndx];
|
|
int ret;
|
|
|
|
/* Save buffer information */
|
|
|
|
chan->pid = OTG_PID_DATA1;
|
|
chan->buffer = buffer;
|
|
chan->buflen = buflen;
|
|
chan->xfrd = 0;
|
|
|
|
/* Set up for the wait BEFORE starting the transfer */
|
|
|
|
ret = stm32_chan_waitsetup(priv, chan);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(OTG_TRACE1_DEVDISCONN, 0);
|
|
return ret;
|
|
}
|
|
|
|
/* Start the transfer */
|
|
|
|
stm32_transfer_start(priv, ep0->inndx);
|
|
|
|
/* Wait for the transfer to complete and return the result */
|
|
|
|
return stm32_chan_wait(priv, chan);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_in_setup
|
|
*
|
|
* Description:
|
|
* Initiate an IN transfer on an bulk, interrupt, or isochronous pipe.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int stm32_in_setup(FAR struct stm32_usbhost_s *priv, int chidx)
|
|
{
|
|
FAR struct stm32_chan_s *chan;
|
|
|
|
/* Set up for the transfer based on the direction and the endpoint type */
|
|
|
|
chan = &priv->chan[chidx];
|
|
switch (chan->eptype)
|
|
{
|
|
default:
|
|
case OTG_EPTYPE_CTRL: /* Control */
|
|
{
|
|
/* This kind of transfer on control endpoints other than EP0 are not
|
|
* currently supported
|
|
*/
|
|
|
|
return -ENOSYS;
|
|
}
|
|
|
|
case OTG_EPTYPE_ISOC: /* Isochronous */
|
|
{
|
|
/* Set up the IN data PID */
|
|
|
|
usbhost_vtrace2(OTG_VTRACE2_ISOCIN, chidx, chan->buflen);
|
|
chan->pid = OTG_PID_DATA0;
|
|
}
|
|
break;
|
|
|
|
case OTG_EPTYPE_BULK: /* Bulk */
|
|
{
|
|
/* Setup the IN data PID */
|
|
|
|
usbhost_vtrace2(OTG_VTRACE2_BULKIN, chidx, chan->buflen);
|
|
chan->pid = chan->indata1 ? OTG_PID_DATA1 : OTG_PID_DATA0;
|
|
}
|
|
break;
|
|
|
|
case OTG_EPTYPE_INTR: /* Interrupt */
|
|
{
|
|
/* Setup the IN data PID */
|
|
|
|
usbhost_vtrace2(OTG_VTRACE2_INTRIN, chidx, chan->buflen);
|
|
chan->pid = chan->indata1 ? OTG_PID_DATA1 : OTG_PID_DATA0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Start the transfer */
|
|
|
|
stm32_transfer_start(priv, chidx);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_in_transfer
|
|
*
|
|
* Description:
|
|
* Transfer 'buflen' bytes into 'buffer' from an IN channel.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static ssize_t stm32_in_transfer(FAR struct stm32_usbhost_s *priv, int chidx,
|
|
FAR uint8_t *buffer, size_t buflen)
|
|
{
|
|
FAR struct stm32_chan_s *chan;
|
|
clock_t start;
|
|
ssize_t xfrd;
|
|
int ret;
|
|
|
|
/* Loop until the transfer completes (i.e., buflen is decremented to zero)
|
|
* or a fatal error occurs any error other than a simple NAK. NAK would
|
|
* simply indicate the end of the transfer (short-transfer).
|
|
*/
|
|
|
|
chan = &priv->chan[chidx];
|
|
chan->buffer = buffer;
|
|
chan->buflen = buflen;
|
|
chan->xfrd = 0;
|
|
xfrd = 0;
|
|
|
|
start = clock_systimer();
|
|
while (chan->xfrd < chan->buflen)
|
|
{
|
|
/* Set up for the wait BEFORE starting the transfer */
|
|
|
|
ret = stm32_chan_waitsetup(priv, chan);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(OTG_TRACE1_DEVDISCONN, 0);
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
/* Set up for the transfer based on the direction and the endpoint type */
|
|
|
|
ret = stm32_in_setup(priv, chidx);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: stm32_in_setup failed: %d\n", ret);
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
/* Wait for the transfer to complete and get the result */
|
|
|
|
ret = stm32_chan_wait(priv, chan);
|
|
|
|
/* EAGAIN indicates that the device NAKed the transfer. */
|
|
|
|
if (ret < 0)
|
|
{
|
|
/* The transfer failed. If we received a NAK, return all data
|
|
* buffered so far (if any).
|
|
*/
|
|
|
|
if (ret == -EAGAIN)
|
|
{
|
|
/* Was data buffered prior to the NAK? */
|
|
|
|
if (xfrd > 0)
|
|
{
|
|
/* Yes, return the amount of data received.
|
|
*
|
|
* REVISIT: This behavior is clearly correct for CDC/ACM
|
|
* bulk transfers and HID interrupt transfers. But I am
|
|
* not so certain for MSC bulk transfers which, I think,
|
|
* could have NAKed packets in the middle of a transfer.
|
|
*/
|
|
|
|
return xfrd;
|
|
}
|
|
else
|
|
{
|
|
useconds_t delay;
|
|
|
|
/* Get the elapsed time. Has the timeout elapsed?
|
|
* if not then try again.
|
|
*/
|
|
|
|
clock_t elapsed = clock_systimer() - start;
|
|
if (elapsed >= STM32_DATANAK_DELAY)
|
|
{
|
|
/* Timeout out... break out returning the NAK as
|
|
* as a failure.
|
|
*/
|
|
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
/* Wait a bit before retrying after a NAK. */
|
|
|
|
if (chan->eptype == OTG_EPTYPE_INTR)
|
|
{
|
|
/* For interrupt (and isochronous) endpoints, the
|
|
* polling rate is determined by the bInterval field
|
|
* of the endpoint descriptor (in units of frames
|
|
* which we treat as milliseconds here).
|
|
*/
|
|
|
|
if (chan->interval > 0)
|
|
{
|
|
/* Convert the delay to units of microseconds */
|
|
|
|
delay = (useconds_t)chan->interval * 1000;
|
|
}
|
|
else
|
|
{
|
|
/* Out of range! For interrupt endpoints, the valid
|
|
* range is 1-255 frames. Assume one frame.
|
|
*/
|
|
|
|
delay = 1000;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* For Isochronous endpoints, bInterval must be 1. Bulk
|
|
* endpoints do not have a polling interval. Rather,
|
|
* the should wait until data is received.
|
|
*
|
|
* REVISIT: For bulk endpoints this 1 msec delay is only
|
|
* intended to give the CPU a break from the bulk EP tight
|
|
* polling loop. But are there performance issues?
|
|
*/
|
|
|
|
delay = 1000;
|
|
}
|
|
|
|
/* Wait for the next polling interval. For interrupt and
|
|
* isochronous endpoints, this is necessaryto assure the
|
|
* polling interval. It is used in other cases only to
|
|
* prevent the polling from consuming too much CPU bandwith.
|
|
*
|
|
* Small delays could require more resolution than is provided
|
|
* by the system timer. For example, if the system timer
|
|
* resolution is 10MS, then nxsig_usleep(1000) will actually request
|
|
* a delay 20MS (due to both quantization and rounding).
|
|
*
|
|
* REVISIT: So which is better? To ignore tiny delays and
|
|
* hog the system bandwidth? Or to wait for an excessive
|
|
* amount and destroy system throughput?
|
|
*/
|
|
|
|
if (delay > CONFIG_USEC_PER_TICK)
|
|
{
|
|
nxsig_usleep(delay - CONFIG_USEC_PER_TICK);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Some unexpected, fatal error occurred. */
|
|
|
|
usbhost_trace1(OTG_TRACE1_TRNSFRFAILED, ret);
|
|
|
|
/* Break out and return the error */
|
|
|
|
uerr("ERROR: stm32_chan_wait failed: %d\n", ret);
|
|
return (ssize_t)ret;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Successfully received another chunk of data... add that to the
|
|
* running total. Then continue reading until we read 'buflen'
|
|
* bytes of data or until the devices NAKs (implying a short
|
|
* packet).
|
|
*/
|
|
|
|
xfrd += chan->xfrd;
|
|
}
|
|
}
|
|
|
|
return xfrd;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_in_next
|
|
*
|
|
* Description:
|
|
* Initiate the next of a sequence of asynchronous transfers.
|
|
*
|
|
* Assumptions:
|
|
* This function is always called from an interrupt handler
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
static void stm32_in_next(FAR struct stm32_usbhost_s *priv,
|
|
FAR struct stm32_chan_s *chan)
|
|
{
|
|
usbhost_asynch_t callback;
|
|
FAR void *arg;
|
|
ssize_t nbytes;
|
|
int result;
|
|
int ret;
|
|
|
|
/* Is the full transfer complete? Did the last chunk transfer complete OK? */
|
|
|
|
result = -(int)chan->result;
|
|
if (chan->xfrd < chan->buflen && result == OK)
|
|
{
|
|
/* Yes.. Set up for the next transfer based on the direction and the
|
|
* endpoint type
|
|
*/
|
|
|
|
ret = stm32_in_setup(priv, chan->chidx);
|
|
if (ret >= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
uerr("ERROR: stm32_in_setup failed: %d\n", ret);
|
|
result = ret;
|
|
}
|
|
|
|
/* The transfer is complete, with or without an error */
|
|
|
|
uinfo("Transfer complete: %d\n", result);
|
|
|
|
/* Extract the callback information */
|
|
|
|
callback = chan->callback;
|
|
arg = chan->arg;
|
|
nbytes = chan->xfrd;
|
|
|
|
chan->callback = NULL;
|
|
chan->arg = NULL;
|
|
chan->xfrd = 0;
|
|
|
|
/* Then perform the callback */
|
|
|
|
if (result < 0)
|
|
{
|
|
nbytes = (ssize_t)result;
|
|
}
|
|
|
|
callback(arg, nbytes);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_in_asynch
|
|
*
|
|
* Description:
|
|
* Initiate the first of a sequence of asynchronous transfers.
|
|
*
|
|
* Assumptions:
|
|
* This function is never called from an interrupt handler
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
static int stm32_in_asynch(FAR struct stm32_usbhost_s *priv, int chidx,
|
|
FAR uint8_t *buffer, size_t buflen,
|
|
usbhost_asynch_t callback, FAR void *arg)
|
|
{
|
|
FAR struct stm32_chan_s *chan;
|
|
int ret;
|
|
|
|
/* Set up for the transfer data and callback BEFORE starting the first transfer */
|
|
|
|
chan = &priv->chan[chidx];
|
|
chan->buffer = buffer;
|
|
chan->buflen = buflen;
|
|
chan->xfrd = 0;
|
|
|
|
ret = stm32_chan_asynchsetup(priv, chan, callback, arg);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: stm32_chan_asynchsetup failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Set up for the transfer based on the direction and the endpoint type */
|
|
|
|
ret = stm32_in_setup(priv, chidx);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: stm32_in_setup failed: %d\n", ret);
|
|
}
|
|
|
|
/* And return with the transfer pending */
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_out_setup
|
|
*
|
|
* Description:
|
|
* Initiate an OUT transfer on an bulk, interrupt, or isochronous pipe.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int stm32_out_setup(FAR struct stm32_usbhost_s *priv, int chidx)
|
|
{
|
|
FAR struct stm32_chan_s *chan;
|
|
|
|
/* Set up for the transfer based on the direction and the endpoint type */
|
|
|
|
chan = &priv->chan[chidx];
|
|
switch (chan->eptype)
|
|
{
|
|
default:
|
|
case OTG_EPTYPE_CTRL: /* Control */
|
|
{
|
|
/* This kind of transfer on control endpoints other than EP0 are not
|
|
* currently supported
|
|
*/
|
|
|
|
return -ENOSYS;
|
|
}
|
|
|
|
case OTG_EPTYPE_ISOC: /* Isochronous */
|
|
{
|
|
/* Set up the OUT data PID */
|
|
|
|
usbhost_vtrace2(OTG_VTRACE2_ISOCOUT, chidx, chan->buflen);
|
|
chan->pid = OTG_PID_DATA0;
|
|
}
|
|
break;
|
|
|
|
case OTG_EPTYPE_BULK: /* Bulk */
|
|
{
|
|
/* Setup the OUT data PID */
|
|
|
|
usbhost_vtrace2(OTG_VTRACE2_BULKOUT, chidx, chan->buflen);
|
|
chan->pid = chan->outdata1 ? OTG_PID_DATA1 : OTG_PID_DATA0;
|
|
}
|
|
break;
|
|
|
|
case OTG_EPTYPE_INTR: /* Interrupt */
|
|
{
|
|
/* Setup the OUT data PID */
|
|
|
|
usbhost_vtrace2(OTG_VTRACE2_INTROUT, chidx, chan->buflen);
|
|
chan->pid = chan->outdata1 ? OTG_PID_DATA1 : OTG_PID_DATA0;
|
|
|
|
/* Toggle the OUT data PID for the next transfer */
|
|
|
|
chan->outdata1 ^= true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Start the transfer */
|
|
|
|
stm32_transfer_start(priv, chidx);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_out_transfer
|
|
*
|
|
* Description:
|
|
* Transfer the 'buflen' bytes in 'buffer' through an OUT channel.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static ssize_t stm32_out_transfer(FAR struct stm32_usbhost_s *priv, int chidx,
|
|
FAR uint8_t *buffer, size_t buflen)
|
|
{
|
|
FAR struct stm32_chan_s *chan;
|
|
clock_t start;
|
|
clock_t elapsed;
|
|
size_t xfrlen;
|
|
ssize_t xfrd;
|
|
int ret;
|
|
bool zlp;
|
|
|
|
/* Loop until the transfer completes (i.e., buflen is decremented to zero)
|
|
* or a fatal error occurs (any error other than a simple NAK)
|
|
*/
|
|
|
|
chan = &priv->chan[chidx];
|
|
start = clock_systimer();
|
|
xfrd = 0;
|
|
zlp = (buflen == 0);
|
|
|
|
while (buflen > 0 || zlp)
|
|
{
|
|
/* Transfer one packet at a time. The hardware is capable of queueing
|
|
* multiple OUT packets, but I just haven't figured out how to handle
|
|
* the case where a single OUT packet in the group is NAKed.
|
|
*/
|
|
|
|
xfrlen = MIN(chan->maxpacket, buflen);
|
|
chan->buffer = buffer;
|
|
chan->buflen = xfrlen;
|
|
chan->xfrd = 0;
|
|
|
|
/* Set up for the wait BEFORE starting the transfer */
|
|
|
|
ret = stm32_chan_waitsetup(priv, chan);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(OTG_TRACE1_DEVDISCONN, 0);
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
/* Set up for the transfer based on the direction and the endpoint type */
|
|
|
|
ret = stm32_out_setup(priv, chidx);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: stm32_out_setup failed: %d\n", ret);
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
/* Wait for the transfer to complete and get the result */
|
|
|
|
ret = stm32_chan_wait(priv, chan);
|
|
|
|
/* Handle transfer failures */
|
|
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(OTG_TRACE1_TRNSFRFAILED, ret);
|
|
|
|
/* Check for a special case: If (1) the transfer was NAKed and (2)
|
|
* no Tx FIFO empty or Rx FIFO not-empty event occurred, then we
|
|
* should be able to just flush the Rx and Tx FIFOs and try again.
|
|
* We can detect this latter case because then the transfer buffer
|
|
* pointer and buffer size will be unaltered.
|
|
*/
|
|
|
|
elapsed = clock_systimer() - start;
|
|
if (ret != -EAGAIN || /* Not a NAK condition OR */
|
|
elapsed >= STM32_DATANAK_DELAY || /* Timeout has elapsed OR */
|
|
chan->xfrd > 0) /* Data has been partially transferred */
|
|
{
|
|
/* Break out and return the error */
|
|
|
|
uerr("ERROR: stm32_chan_wait failed: %d\n", ret);
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
/* Is this flush really necessary? What does the hardware do with the
|
|
* data in the FIFO when the NAK occurs? Does it discard it?
|
|
*/
|
|
|
|
stm32_flush_txfifos(OTG_GRSTCTL_TXFNUM_HALL);
|
|
|
|
/* Get the device a little time to catch up. Then retry the transfer
|
|
* using the same buffer pointer and length.
|
|
*/
|
|
|
|
nxsig_usleep(20 * 1000);
|
|
}
|
|
else
|
|
{
|
|
/* Successfully transferred. Update the buffer pointer and length */
|
|
|
|
buffer += xfrlen;
|
|
buflen -= xfrlen;
|
|
xfrd += chan->xfrd;
|
|
zlp = false;
|
|
}
|
|
}
|
|
|
|
return xfrd;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_out_next
|
|
*
|
|
* Description:
|
|
* Initiate the next of a sequence of asynchronous transfers.
|
|
*
|
|
* Assumptions:
|
|
* This function is always called from an interrupt handler
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
static void stm32_out_next(FAR struct stm32_usbhost_s *priv,
|
|
FAR struct stm32_chan_s *chan)
|
|
{
|
|
usbhost_asynch_t callback;
|
|
FAR void *arg;
|
|
ssize_t nbytes;
|
|
int result;
|
|
int ret;
|
|
|
|
/* Is the full transfer complete? Did the last chunk transfer complete OK? */
|
|
|
|
result = -(int)chan->result;
|
|
if (chan->xfrd < chan->buflen && result == OK)
|
|
{
|
|
/* Yes.. Set up for the next transfer based on the direction and the
|
|
* endpoint type
|
|
*/
|
|
|
|
ret = stm32_out_setup(priv, chan->chidx);
|
|
if (ret >= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
uerr("ERROR: stm32_out_setup failed: %d\n", ret);
|
|
result = ret;
|
|
}
|
|
|
|
/* The transfer is complete, with or without an error */
|
|
|
|
uinfo("Transfer complete: %d\n", result);
|
|
|
|
/* Extract the callback information */
|
|
|
|
callback = chan->callback;
|
|
arg = chan->arg;
|
|
nbytes = chan->xfrd;
|
|
|
|
chan->callback = NULL;
|
|
chan->arg = NULL;
|
|
chan->xfrd = 0;
|
|
|
|
/* Then perform the callback */
|
|
|
|
if (result < 0)
|
|
{
|
|
nbytes = (ssize_t)result;
|
|
}
|
|
|
|
callback(arg, nbytes);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_out_asynch
|
|
*
|
|
* Description:
|
|
* Initiate the first of a sequence of asynchronous transfers.
|
|
*
|
|
* Assumptions:
|
|
* This function is never called from an interrupt handler
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
static int stm32_out_asynch(FAR struct stm32_usbhost_s *priv, int chidx,
|
|
FAR uint8_t *buffer, size_t buflen,
|
|
usbhost_asynch_t callback, FAR void *arg)
|
|
{
|
|
FAR struct stm32_chan_s *chan;
|
|
int ret;
|
|
|
|
/* Set up for the transfer data and callback BEFORE starting the first transfer */
|
|
|
|
chan = &priv->chan[chidx];
|
|
chan->buffer = buffer;
|
|
chan->buflen = buflen;
|
|
chan->xfrd = 0;
|
|
|
|
ret = stm32_chan_asynchsetup(priv, chan, callback, arg);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: stm32_chan_asynchsetup failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Set up for the transfer based on the direction and the endpoint type */
|
|
|
|
ret = stm32_out_setup(priv, chidx);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: stm32_out_setup failed: %d\n", ret);
|
|
}
|
|
|
|
/* And return with the transfer pending */
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_gint_wrpacket
|
|
*
|
|
* Description:
|
|
* Transfer the 'buflen' bytes in 'buffer' to the Tx FIFO associated with
|
|
* 'chidx' (non-DMA).
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_gint_wrpacket(FAR struct stm32_usbhost_s *priv,
|
|
FAR uint8_t *buffer, int chidx, int buflen)
|
|
{
|
|
FAR uint32_t *src;
|
|
uint32_t fifo;
|
|
int buflen32;
|
|
|
|
stm32_pktdump("Sending", buffer, buflen);
|
|
|
|
/* Get the number of 32-byte words associated with this byte size */
|
|
|
|
buflen32 = (buflen + 3) >> 2;
|
|
|
|
/* Get the address of the Tx FIFO associated with this channel */
|
|
|
|
fifo = STM32_OTG_DFIFO_HCH(chidx);
|
|
|
|
/* Transfer all of the data into the Tx FIFO */
|
|
|
|
src = (FAR uint32_t *)buffer;
|
|
for (; buflen32 > 0; buflen32--)
|
|
{
|
|
uint32_t data = *src++;
|
|
stm32_putreg(fifo, data);
|
|
}
|
|
|
|
/* Increment the count of bytes "in-flight" in the Tx FIFO */
|
|
|
|
priv->chan[chidx].inflight += buflen;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_gint_hcinisr
|
|
*
|
|
* Description:
|
|
* USB OTG FS host IN channels interrupt handler
|
|
*
|
|
* One the completion of the transfer, the channel result byte may be set as
|
|
* follows:
|
|
*
|
|
* OK - Transfer completed successfully
|
|
* EAGAIN - If devices NAKs the transfer or NYET occurs
|
|
* EPERM - If the endpoint stalls
|
|
* EIO - On a TX or data toggle error
|
|
* EPIPE - Frame overrun
|
|
*
|
|
* EBUSY in the result field indicates that the transfer has not completed.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void stm32_gint_hcinisr(FAR struct stm32_usbhost_s *priv,
|
|
int chidx)
|
|
{
|
|
FAR struct stm32_chan_s *chan = &priv->chan[chidx];
|
|
uint32_t regval;
|
|
uint32_t pending;
|
|
|
|
/* Read the HCINT register to get the pending HC interrupts. Read the
|
|
* HCINTMSK register to get the set of enabled HC interrupts.
|
|
*/
|
|
|
|
pending = stm32_getreg(STM32_OTG_HCINT(chidx));
|
|
regval = stm32_getreg(STM32_OTG_HCINTMSK(chidx));
|
|
|
|
/* AND the two to get the set of enabled, pending HC interrupts */
|
|
|
|
pending &= regval;
|
|
uinfo("HCINTMSK%d: %08x pending: %08x\n", chidx, regval, pending);
|
|
|
|
/* Check for a pending ACK response received/transmitted (ACK) interrupt */
|
|
|
|
if ((pending & OTG_HCINT_ACK) != 0)
|
|
{
|
|
/* Clear the pending the ACK response received/transmitted (ACK) interrupt */
|
|
|
|
stm32_putreg(STM32_OTG_HCINT(chidx), OTG_HCINT_ACK);
|
|
}
|
|
|
|
/* Check for a pending STALL response receive (STALL) interrupt */
|
|
|
|
else if ((pending & OTG_HCINT_STALL) != 0)
|
|
{
|
|
/* Clear the NAK and STALL Conditions. */
|
|
|
|
stm32_putreg(STM32_OTG_HCINT(chidx), (OTG_HCINT_NAK | OTG_HCINT_STALL));
|
|
|
|
/* Halt the channel when a STALL, TXERR, BBERR or DTERR interrupt is
|
|
* received on the channel.
|
|
*/
|
|
|
|
stm32_chan_halt(priv, chidx, CHREASON_STALL);
|
|
|
|
/* When there is a STALL, clear any pending NAK so that it is not
|
|
* processed below.
|
|
*/
|
|
|
|
pending &= ~OTG_HCINT_NAK;
|
|
}
|
|
|
|
/* Check for a pending Data Toggle ERRor (DTERR) interrupt */
|
|
|
|
else if ((pending & OTG_HCINT_DTERR) != 0)
|
|
{
|
|
/* Halt the channel when a STALL, TXERR, BBERR or DTERR interrupt is
|
|
* received on the channel.
|
|
*/
|
|
|
|
stm32_chan_halt(priv, chidx, CHREASON_DTERR);
|
|
|
|
/* Clear the NAK and data toggle error conditions */
|
|
|
|
stm32_putreg(STM32_OTG_HCINT(chidx), (OTG_HCINT_NAK | OTG_HCINT_DTERR));
|
|
}
|
|
|
|
/* Check for a pending FRaMe OverRun (FRMOR) interrupt */
|
|
|
|
if ((pending & OTG_HCINT_FRMOR) != 0)
|
|
{
|
|
/* Halt the channel -- the CHH interrupt is expected next */
|
|
|
|
stm32_chan_halt(priv, chidx, CHREASON_FRMOR);
|
|
|
|
/* Clear the FRaMe OverRun (FRMOR) condition */
|
|
|
|
stm32_putreg(STM32_OTG_HCINT(chidx), OTG_HCINT_FRMOR);
|
|
}
|
|
|
|
/* Check for a pending TransFeR Completed (XFRC) interrupt */
|
|
|
|
else if ((pending & OTG_HCINT_XFRC) != 0)
|
|
{
|
|
/* Clear the TransFeR Completed (XFRC) condition */
|
|
|
|
stm32_putreg(STM32_OTG_HCINT(chidx), OTG_HCINT_XFRC);
|
|
|
|
/* Then handle the transfer completion event based on the endpoint type */
|
|
|
|
if (chan->eptype == OTG_EPTYPE_CTRL || chan->eptype == OTG_EPTYPE_BULK)
|
|
{
|
|
/* Halt the channel -- the CHH interrupt is expected next */
|
|
|
|
stm32_chan_halt(priv, chidx, CHREASON_XFRC);
|
|
|
|
/* Clear any pending NAK condition. The 'indata1' data toggle
|
|
* should have been appropriately updated by the RxFIFO
|
|
* logic as each packet was received.
|
|
*/
|
|
|
|
stm32_putreg(STM32_OTG_HCINT(chidx), OTG_HCINT_NAK);
|
|
}
|
|
else if (chan->eptype == OTG_EPTYPE_INTR)
|
|
{
|
|
/* Force the next transfer on an ODD frame */
|
|
|
|
regval = stm32_getreg(STM32_OTG_HCCHAR(chidx));
|
|
regval |= OTG_HCCHAR_ODDFRM;
|
|
stm32_putreg(STM32_OTG_HCCHAR(chidx), regval);
|
|
|
|
/* Set the request done state */
|
|
|
|
chan->result = OK;
|
|
}
|
|
}
|
|
|
|
/* Check for a pending CHannel Halted (CHH) interrupt */
|
|
|
|
else if ((pending & OTG_HCINT_CHH) != 0)
|
|
{
|
|
/* Mask the CHannel Halted (CHH) interrupt */
|
|
|
|
regval = stm32_getreg(STM32_OTG_HCINTMSK(chidx));
|
|
regval &= ~OTG_HCINT_CHH;
|
|
stm32_putreg(STM32_OTG_HCINTMSK(chidx), regval);
|
|
|
|
/* Update the request state based on the host state machine state */
|
|
|
|
if (chan->chreason == CHREASON_XFRC)
|
|
{
|
|
/* Set the request done result */
|
|
|
|
chan->result = OK;
|
|
}
|
|
else if (chan->chreason == CHREASON_STALL)
|
|
{
|
|
/* Set the request stall result */
|
|
|
|
chan->result = EPERM;
|
|
}
|
|
else if ((chan->chreason == CHREASON_TXERR) ||
|
|
(chan->chreason == CHREASON_DTERR))
|
|
{
|
|
/* Set the request I/O error result */
|
|
|
|
chan->result = EIO;
|
|
}
|
|
else if (chan->chreason == CHREASON_NAK)
|
|
{
|
|
/* Set the NAK error result */
|
|
|
|
chan->result = EAGAIN;
|
|
}
|
|
else /* if (chan->chreason == CHREASON_FRMOR) */
|
|
{
|
|
/* Set the frame overrun error result */
|
|
|
|
chan->result = EPIPE;
|
|
}
|
|
|
|
/* Clear the CHannel Halted (CHH) condition */
|
|
|
|
stm32_putreg(STM32_OTG_HCINT(chidx), OTG_HCINT_CHH);
|
|
}
|
|
|
|
/* Check for a pending Transaction ERror (TXERR) interrupt */
|
|
|
|
else if ((pending & OTG_HCINT_TXERR) != 0)
|
|
{
|
|
/* Halt the channel when a STALL, TXERR, BBERR or DTERR interrupt is
|
|
* received on the channel.
|
|
*/
|
|
|
|
stm32_chan_halt(priv, chidx, CHREASON_TXERR);
|
|
|
|
/* Clear the Transaction ERror (TXERR) condition */
|
|
|
|
stm32_putreg(STM32_OTG_HCINT(chidx), OTG_HCINT_TXERR);
|
|
}
|
|
|
|
/* Check for a pending NAK response received (NAK) interrupt */
|
|
|
|
else if ((pending & OTG_HCINT_NAK) != 0)
|
|
{
|
|
/* For a BULK transfer, the hardware is capable of retrying
|
|
* automatically on a NAK. However, this is not always
|
|
* what we need to do. So we always halt the transfer and
|
|
* return control to high level logic in the event of a NAK.
|
|
*/
|
|
|
|
#if 1
|
|
/* Halt the interrupt channel */
|
|
|
|
if (chan->eptype == OTG_EPTYPE_INTR ||
|
|
chan->eptype == OTG_EPTYPE_BULK)
|
|
{
|
|
/* Halt the channel -- the CHH interrupt is expected next */
|
|
|
|
stm32_chan_halt(priv, chidx, CHREASON_NAK);
|
|
}
|
|
|
|
/* Re-activate CTRL and BULK channels.
|
|
* REVISIT: This can cause a lot of interrupts!
|
|
*/
|
|
|
|
else if (chan->eptype == OTG_EPTYPE_CTRL /* ||
|
|
chan->eptype == OTG_EPTYPE_BULK */)
|
|
{
|
|
/* Re-activate the channel by clearing CHDIS and assuring that
|
|
* CHENA is set
|
|
*
|
|
* TODO: set channel reason to NACK?
|
|
*/
|
|
|
|
regval = stm32_getreg(STM32_OTG_HCCHAR(chidx));
|
|
regval |= OTG_HCCHAR_CHENA;
|
|
regval &= ~OTG_HCCHAR_CHDIS;
|
|
stm32_putreg(STM32_OTG_HCCHAR(chidx), regval);
|
|
}
|
|
|
|
#else
|
|
/* Halt all transfers on the NAK -- the CHH interrupt is expected next */
|
|
|
|
stm32_chan_halt(priv, chidx, CHREASON_NAK);
|
|
#endif
|
|
|
|
/* Clear the NAK condition */
|
|
|
|
stm32_putreg(STM32_OTG_HCINT(chidx), OTG_HCINT_NAK);
|
|
}
|
|
|
|
/* Check for a transfer complete event */
|
|
|
|
stm32_chan_wakeup(priv, chan);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_gint_hcoutisr
|
|
*
|
|
* Description:
|
|
* USB OTG FS host OUT channels interrupt handler
|
|
*
|
|
* One the completion of the transfer, the channel result byte may be set as
|
|
* follows:
|
|
*
|
|
* OK - Transfer completed successfully
|
|
* EAGAIN - If devices NAKs the transfer or NYET occurs
|
|
* EPERM - If the endpoint stalls
|
|
* EIO - On a TX or data toggle error
|
|
* EPIPE - Frame overrun
|
|
*
|
|
* EBUSY in the result field indicates that the transfer has not completed.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void stm32_gint_hcoutisr(FAR struct stm32_usbhost_s *priv,
|
|
int chidx)
|
|
{
|
|
FAR struct stm32_chan_s *chan = &priv->chan[chidx];
|
|
uint32_t regval;
|
|
uint32_t pending;
|
|
|
|
/* Read the HCINT register to get the pending HC interrupts. Read the
|
|
* HCINTMSK register to get the set of enabled HC interrupts.
|
|
*/
|
|
|
|
pending = stm32_getreg(STM32_OTG_HCINT(chidx));
|
|
regval = stm32_getreg(STM32_OTG_HCINTMSK(chidx));
|
|
|
|
/* AND the two to get the set of enabled, pending HC interrupts */
|
|
|
|
pending &= regval;
|
|
uinfo("HCINTMSK%d: %08x pending: %08x\n", chidx, regval, pending);
|
|
|
|
/* Check for a pending ACK response received/transmitted (ACK) interrupt */
|
|
|
|
if ((pending & OTG_HCINT_ACK) != 0)
|
|
{
|
|
/* Clear the pending the ACK response received/transmitted (ACK) interrupt */
|
|
|
|
stm32_putreg(STM32_OTG_HCINT(chidx), OTG_HCINT_ACK);
|
|
}
|
|
|
|
/* Check for a pending FRaMe OverRun (FRMOR) interrupt */
|
|
|
|
else if ((pending & OTG_HCINT_FRMOR) != 0)
|
|
{
|
|
/* Halt the channel (probably not necessary for FRMOR) */
|
|
|
|
stm32_chan_halt(priv, chidx, CHREASON_FRMOR);
|
|
|
|
/* Clear the pending the FRaMe OverRun (FRMOR) interrupt */
|
|
|
|
stm32_putreg(STM32_OTG_HCINT(chidx), OTG_HCINT_FRMOR);
|
|
}
|
|
|
|
/* Check for a pending TransFeR Completed (XFRC) interrupt */
|
|
|
|
else if ((pending & OTG_HCINT_XFRC) != 0)
|
|
{
|
|
/* Decrement the number of bytes remaining by the number of
|
|
* bytes that were "in-flight".
|
|
*/
|
|
|
|
priv->chan[chidx].buffer += priv->chan[chidx].inflight;
|
|
priv->chan[chidx].xfrd += priv->chan[chidx].inflight;
|
|
priv->chan[chidx].inflight = 0;
|
|
|
|
/* Halt the channel -- the CHH interrupt is expected next */
|
|
|
|
stm32_chan_halt(priv, chidx, CHREASON_XFRC);
|
|
|
|
/* Clear the pending the TransFeR Completed (XFRC) interrupt */
|
|
|
|
stm32_putreg(STM32_OTG_HCINT(chidx), OTG_HCINT_XFRC);
|
|
}
|
|
|
|
/* Check for a pending STALL response receive (STALL) interrupt */
|
|
|
|
else if ((pending & OTG_HCINT_STALL) != 0)
|
|
{
|
|
/* Clear the pending the STALL response receiv (STALL) interrupt */
|
|
|
|
stm32_putreg(STM32_OTG_HCINT(chidx), OTG_HCINT_STALL);
|
|
|
|
/* Halt the channel when a STALL, TXERR, BBERR or DTERR interrupt is
|
|
* received on the channel.
|
|
*/
|
|
|
|
stm32_chan_halt(priv, chidx, CHREASON_STALL);
|
|
}
|
|
|
|
/* Check for a pending NAK response received (NAK) interrupt */
|
|
|
|
else if ((pending & OTG_HCINT_NAK) != 0)
|
|
{
|
|
/* Halt the channel -- the CHH interrupt is expected next */
|
|
|
|
stm32_chan_halt(priv, chidx, CHREASON_NAK);
|
|
|
|
/* Clear the pending the NAK response received (NAK) interrupt */
|
|
|
|
stm32_putreg(STM32_OTG_HCINT(chidx), OTG_HCINT_NAK);
|
|
}
|
|
|
|
/* Check for a pending Transaction ERror (TXERR) interrupt */
|
|
|
|
else if ((pending & OTG_HCINT_TXERR) != 0)
|
|
{
|
|
/* Halt the channel when a STALL, TXERR, BBERR or DTERR interrupt is
|
|
* received on the channel.
|
|
*/
|
|
|
|
stm32_chan_halt(priv, chidx, CHREASON_TXERR);
|
|
|
|
/* Clear the pending the Transaction ERror (TXERR) interrupt */
|
|
|
|
stm32_putreg(STM32_OTG_HCINT(chidx), OTG_HCINT_TXERR);
|
|
}
|
|
|
|
/* Check for a NYET interrupt */
|
|
|
|
#if 0 /* NYET is a reserved bit in the HCINT register */
|
|
else if ((pending & OTG_HCINT_NYET) != 0)
|
|
{
|
|
/* Halt the channel */
|
|
|
|
stm32_chan_halt(priv, chidx, CHREASON_NYET);
|
|
|
|
/* Clear the pending the NYET interrupt */
|
|
|
|
stm32_putreg(STM32_OTG_HCINT(chidx), OTG_HCINT_NYET);
|
|
}
|
|
#endif
|
|
|
|
/* Check for a pending Data Toggle ERRor (DTERR) interrupt */
|
|
|
|
else if (pending & OTG_HCINT_DTERR)
|
|
{
|
|
/* Halt the channel when a STALL, TXERR, BBERR or DTERR interrupt is
|
|
* received on the channel.
|
|
*/
|
|
|
|
stm32_chan_halt(priv, chidx, CHREASON_DTERR);
|
|
|
|
/* Clear the pending the Data Toggle ERRor (DTERR) and NAK interrupts */
|
|
|
|
stm32_putreg(STM32_OTG_HCINT(chidx), (OTG_HCINT_DTERR | OTG_HCINT_NAK));
|
|
}
|
|
|
|
/* Check for a pending CHannel Halted (CHH) interrupt */
|
|
|
|
else if ((pending & OTG_HCINT_CHH) != 0)
|
|
{
|
|
/* Mask the CHannel Halted (CHH) interrupt */
|
|
|
|
regval = stm32_getreg(STM32_OTG_HCINTMSK(chidx));
|
|
regval &= ~OTG_HCINT_CHH;
|
|
stm32_putreg(STM32_OTG_HCINTMSK(chidx), regval);
|
|
|
|
if (chan->chreason == CHREASON_XFRC)
|
|
{
|
|
/* Set the request done result */
|
|
|
|
chan->result = OK;
|
|
|
|
/* Read the HCCHAR register to get the HCCHAR register to get
|
|
* the endpoint type.
|
|
*/
|
|
|
|
regval = stm32_getreg(STM32_OTG_HCCHAR(chidx));
|
|
|
|
/* Is it a bulk endpoint? Were an odd number of packets
|
|
* transferred?
|
|
*/
|
|
|
|
if ((regval & OTG_HCCHAR_EPTYP_MASK) == OTG_HCCHAR_EPTYP_BULK &&
|
|
(chan->npackets & 1) != 0)
|
|
{
|
|
/* Yes to both... toggle the data out PID */
|
|
|
|
chan->outdata1 ^= true;
|
|
}
|
|
}
|
|
else if (chan->chreason == CHREASON_NAK ||
|
|
chan->chreason == CHREASON_NYET)
|
|
{
|
|
/* Set the try again later result */
|
|
|
|
chan->result = EAGAIN;
|
|
}
|
|
else if (chan->chreason == CHREASON_STALL)
|
|
{
|
|
/* Set the request stall result */
|
|
|
|
chan->result = EPERM;
|
|
}
|
|
else if ((chan->chreason == CHREASON_TXERR) ||
|
|
(chan->chreason == CHREASON_DTERR))
|
|
{
|
|
/* Set the I/O failure result */
|
|
|
|
chan->result = EIO;
|
|
}
|
|
else /* if (chan->chreason == CHREASON_FRMOR) */
|
|
{
|
|
/* Set the frame error result */
|
|
|
|
chan->result = EPIPE;
|
|
}
|
|
|
|
/* Clear the pending the CHannel Halted (CHH) interrupt */
|
|
|
|
stm32_putreg(STM32_OTG_HCINT(chidx), OTG_HCINT_CHH);
|
|
}
|
|
|
|
/* Check for a transfer complete event */
|
|
|
|
stm32_chan_wakeup(priv, chan);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_gint_connected
|
|
*
|
|
* Description:
|
|
* Handle a connection event.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_gint_connected(FAR struct stm32_usbhost_s *priv)
|
|
{
|
|
/* We we previously disconnected? */
|
|
|
|
if (!priv->connected)
|
|
{
|
|
/* Yes.. then now we are connected */
|
|
|
|
usbhost_vtrace1(OTG_VTRACE1_CONNECTED, 0);
|
|
priv->connected = true;
|
|
priv->change = true;
|
|
DEBUGASSERT(priv->smstate == SMSTATE_DETACHED);
|
|
|
|
/* Notify any waiters */
|
|
|
|
priv->smstate = SMSTATE_ATTACHED;
|
|
if (priv->pscwait)
|
|
{
|
|
stm32_givesem(&priv->pscsem);
|
|
priv->pscwait = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_gint_disconnected
|
|
*
|
|
* Description:
|
|
* Handle a disconnection event.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_gint_disconnected(FAR struct stm32_usbhost_s *priv)
|
|
{
|
|
/* Were we previously connected? */
|
|
|
|
if (priv->connected)
|
|
{
|
|
/* Yes.. then we no longer connected */
|
|
|
|
usbhost_vtrace1(OTG_VTRACE1_DISCONNECTED, 0);
|
|
|
|
/* Are we bound to a class driver? */
|
|
|
|
if (priv->rhport.hport.devclass)
|
|
{
|
|
/* Yes.. Disconnect the class driver */
|
|
|
|
CLASS_DISCONNECTED(priv->rhport.hport.devclass);
|
|
priv->rhport.hport.devclass = NULL;
|
|
}
|
|
|
|
/* Re-Initialize Host for new Enumeration */
|
|
|
|
priv->smstate = SMSTATE_DETACHED;
|
|
priv->connected = false;
|
|
priv->change = true;
|
|
stm32_chan_freeall(priv);
|
|
|
|
priv->rhport.hport.speed = USB_SPEED_FULL;
|
|
priv->rhport.hport.funcaddr = 0;
|
|
|
|
/* Notify any waiters that there is a change in the connection state */
|
|
|
|
if (priv->pscwait)
|
|
{
|
|
stm32_givesem(&priv->pscsem);
|
|
priv->pscwait = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_gint_sofisr
|
|
*
|
|
* Description:
|
|
* USB OTG FS start-of-frame interrupt handler
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_STM32H7_OTG_SOFINTR
|
|
static inline void stm32_gint_sofisr(FAR struct stm32_usbhost_s *priv)
|
|
{
|
|
/* Handle SOF interrupt */
|
|
|
|
#warning "Do what?"
|
|
|
|
/* Clear pending SOF interrupt */
|
|
|
|
stm32_putreg(STM32_OTG_GINTSTS, OTG_GINT_SOF);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_gint_rxflvlisr
|
|
*
|
|
* Description:
|
|
* USB OTG FS RxFIFO non-empty interrupt handler
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void stm32_gint_rxflvlisr(FAR struct stm32_usbhost_s *priv)
|
|
{
|
|
FAR uint32_t *dest;
|
|
uint32_t grxsts;
|
|
uint32_t intmsk;
|
|
uint32_t hcchar;
|
|
uint32_t hctsiz;
|
|
uint32_t fifo;
|
|
int bcnt;
|
|
int bcnt32;
|
|
int chidx;
|
|
int i;
|
|
|
|
/* Disable the RxFIFO non-empty interrupt */
|
|
|
|
intmsk = stm32_getreg(STM32_OTG_GINTMSK);
|
|
intmsk &= ~OTG_GINT_RXFLVL;
|
|
stm32_putreg(STM32_OTG_GINTMSK, intmsk);
|
|
|
|
/* Read and pop the next status from the Rx FIFO */
|
|
|
|
grxsts = stm32_getreg(STM32_OTG_GRXSTSP);
|
|
uinfo("GRXSTS: %08x\n", grxsts);
|
|
|
|
/* Isolate the channel number/index in the status word */
|
|
|
|
chidx = (grxsts & OTG_GRXSTSH_CHNUM_MASK) >> OTG_GRXSTSH_CHNUM_SHIFT;
|
|
|
|
/* Get the host channel characteristics register (HCCHAR) for this channel */
|
|
|
|
hcchar = stm32_getreg(STM32_OTG_HCCHAR(chidx));
|
|
|
|
/* Then process the interrupt according to the packet status */
|
|
|
|
switch (grxsts & OTG_GRXSTSH_PKTSTS_MASK)
|
|
{
|
|
case OTG_GRXSTSH_PKTSTS_INRECVD: /* IN data packet received */
|
|
{
|
|
/* Read the data into the host buffer. */
|
|
|
|
bcnt = (grxsts & OTG_GRXSTSH_BCNT_MASK) >> OTG_GRXSTSH_BCNT_SHIFT;
|
|
if (bcnt > 0 && priv->chan[chidx].buffer != NULL)
|
|
{
|
|
/* Transfer the packet from the Rx FIFO into the user buffer */
|
|
|
|
dest = (FAR uint32_t *)priv->chan[chidx].buffer;
|
|
fifo = STM32_OTG_DFIFO_HCH(0);
|
|
bcnt32 = (bcnt + 3) >> 2;
|
|
|
|
for (i = 0; i < bcnt32; i++)
|
|
{
|
|
*dest++ = stm32_getreg(fifo);
|
|
}
|
|
|
|
stm32_pktdump("Received", priv->chan[chidx].buffer, bcnt);
|
|
|
|
/* Toggle the IN data pid (Used by Bulk and INTR only) */
|
|
|
|
priv->chan[chidx].indata1 ^= true;
|
|
|
|
/* Manage multiple packet transfers */
|
|
|
|
priv->chan[chidx].buffer += bcnt;
|
|
priv->chan[chidx].xfrd += bcnt;
|
|
|
|
/* Check if more packets are expected */
|
|
|
|
hctsiz = stm32_getreg(STM32_OTG_HCTSIZ(chidx));
|
|
if ((hctsiz & OTG_HCTSIZ_PKTCNT_MASK) != 0)
|
|
{
|
|
/* Re-activate the channel when more packets are expected */
|
|
|
|
hcchar |= OTG_HCCHAR_CHENA;
|
|
hcchar &= ~OTG_HCCHAR_CHDIS;
|
|
stm32_putreg(STM32_OTG_HCCHAR(chidx), hcchar);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case OTG_GRXSTSH_PKTSTS_INDONE: /* IN transfer completed */
|
|
case OTG_GRXSTSH_PKTSTS_DTOGERR: /* Data toggle error */
|
|
case OTG_GRXSTSH_PKTSTS_HALTED: /* Channel halted */
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Re-enable the RxFIFO non-empty interrupt */
|
|
|
|
intmsk |= OTG_GINT_RXFLVL;
|
|
stm32_putreg(STM32_OTG_GINTMSK, intmsk);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_gint_nptxfeisr
|
|
*
|
|
* Description:
|
|
* USB OTG FS non-periodic TxFIFO empty interrupt handler
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void stm32_gint_nptxfeisr(FAR struct stm32_usbhost_s *priv)
|
|
{
|
|
FAR struct stm32_chan_s *chan;
|
|
uint32_t regval;
|
|
unsigned int wrsize;
|
|
unsigned int avail;
|
|
unsigned int chidx;
|
|
|
|
/* Recover the index of the channel that is waiting for space in the Tx
|
|
* FIFO.
|
|
*/
|
|
|
|
chidx = priv->chidx;
|
|
chan = &priv->chan[chidx];
|
|
|
|
/* Reduce the buffer size by the number of bytes that were previously placed
|
|
* in the Tx FIFO.
|
|
*/
|
|
|
|
chan->buffer += chan->inflight;
|
|
chan->xfrd += chan->inflight;
|
|
chan->inflight = 0;
|
|
|
|
/* If we have now transferred the entire buffer, then this transfer is
|
|
* complete (this case really should never happen because we disable
|
|
* the NPTXFE interrupt on the final packet).
|
|
*/
|
|
|
|
if (chan->xfrd >= chan->buflen)
|
|
{
|
|
/* Disable further Tx FIFO empty interrupts and bail. */
|
|
|
|
stm32_modifyreg(STM32_OTG_GINTMSK, OTG_GINT_NPTXFE, 0);
|
|
return;
|
|
}
|
|
|
|
/* Read the status from the top of the non-periodic TxFIFO */
|
|
|
|
regval = stm32_getreg(STM32_OTG_HNPTXSTS);
|
|
|
|
/* Extract the number of bytes available in the non-periodic Tx FIFO. */
|
|
|
|
avail = ((regval & OTG_HNPTXSTS_NPTXFSAV_MASK) >> OTG_HNPTXSTS_NPTXFSAV_SHIFT) << 2;
|
|
|
|
/* Get the size to put in the Tx FIFO now */
|
|
|
|
wrsize = chan->buflen - chan->xfrd;
|
|
|
|
/* Get minimal size packet that can be sent. Something is seriously
|
|
* configured wrong if one packet will not fit into the empty Tx FIFO.
|
|
*/
|
|
|
|
DEBUGASSERT(wrsize > 0 && avail >= MIN(wrsize, chan->maxpacket));
|
|
if (wrsize > avail)
|
|
{
|
|
/* Clip the write size to the number of full, max sized packets
|
|
* that will fit in the Tx FIFO.
|
|
*/
|
|
|
|
unsigned int wrpackets = avail / chan->maxpacket;
|
|
wrsize = wrpackets * chan->maxpacket;
|
|
}
|
|
|
|
/* Otherwise, this will be the last packet to be sent in this transaction.
|
|
* We now need to disable further NPTXFE interrupts.
|
|
*/
|
|
|
|
else
|
|
{
|
|
stm32_modifyreg(STM32_OTG_GINTMSK, OTG_GINT_NPTXFE, 0);
|
|
}
|
|
|
|
/* Write the next group of packets into the Tx FIFO */
|
|
|
|
uinfo("HNPTXSTS: %08x chidx: %d avail: %d buflen: %d xfrd: %d wrsize: %d\n",
|
|
regval, chidx, avail, chan->buflen, chan->xfrd, wrsize);
|
|
|
|
stm32_gint_wrpacket(priv, chan->buffer, chidx, wrsize);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_gint_ptxfeisr
|
|
*
|
|
* Description:
|
|
* USB OTG FS periodic TxFIFO empty interrupt handler
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void stm32_gint_ptxfeisr(FAR struct stm32_usbhost_s *priv)
|
|
{
|
|
FAR struct stm32_chan_s *chan;
|
|
uint32_t regval;
|
|
unsigned int wrsize;
|
|
unsigned int avail;
|
|
unsigned int chidx;
|
|
|
|
/* Recover the index of the channel that is waiting for space in the Tx
|
|
* FIFO.
|
|
*/
|
|
|
|
chidx = priv->chidx;
|
|
chan = &priv->chan[chidx];
|
|
|
|
/* Reduce the buffer size by the number of bytes that were previously placed
|
|
* in the Tx FIFO.
|
|
*/
|
|
|
|
chan->buffer += chan->inflight;
|
|
chan->xfrd += chan->inflight;
|
|
chan->inflight = 0;
|
|
|
|
/* If we have now transfered the entire buffer, then this transfer is
|
|
* complete (this case really should never happen because we disable
|
|
* the PTXFE interrupt on the final packet).
|
|
*/
|
|
|
|
if (chan->xfrd >= chan->buflen)
|
|
{
|
|
/* Disable further Tx FIFO empty interrupts and bail. */
|
|
|
|
stm32_modifyreg(STM32_OTG_GINTMSK, OTG_GINT_PTXFE, 0);
|
|
return;
|
|
}
|
|
|
|
/* Read the status from the top of the periodic TxFIFO */
|
|
|
|
regval = stm32_getreg(STM32_OTG_HPTXSTS);
|
|
|
|
/* Extract the number of bytes available in the periodic Tx FIFO. */
|
|
|
|
avail = ((regval & OTG_HPTXSTS_PTXFSAVL_MASK) >> OTG_HPTXSTS_PTXFSAVL_SHIFT) << 2;
|
|
|
|
/* Get the size to put in the Tx FIFO now */
|
|
|
|
wrsize = chan->buflen - chan->xfrd;
|
|
|
|
/* Get minimal size packet that can be sent. Something is seriously
|
|
* configured wrong if one packet will not fit into the empty Tx FIFO.
|
|
*/
|
|
|
|
DEBUGASSERT(wrsize && avail >= MIN(wrsize, chan->maxpacket));
|
|
if (wrsize > avail)
|
|
{
|
|
/* Clip the write size to the number of full, max sized packets
|
|
* that will fit in the Tx FIFO.
|
|
*/
|
|
|
|
unsigned int wrpackets = avail / chan->maxpacket;
|
|
wrsize = wrpackets * chan->maxpacket;
|
|
}
|
|
|
|
/* Otherwise, this will be the last packet to be sent in this transaction.
|
|
* We now need to disable further PTXFE interrupts.
|
|
*/
|
|
|
|
else
|
|
{
|
|
stm32_modifyreg(STM32_OTG_GINTMSK, OTG_GINT_PTXFE, 0);
|
|
}
|
|
|
|
/* Write the next group of packets into the Tx FIFO */
|
|
|
|
uinfo("HPTXSTS: %08x chidx: %d avail: %d buflen: %d xfrd: %d wrsize: %d\n",
|
|
regval, chidx, avail, chan->buflen, chan->xfrd, wrsize);
|
|
|
|
stm32_gint_wrpacket(priv, chan->buffer, chidx, wrsize);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_gint_hcisr
|
|
*
|
|
* Description:
|
|
* USB OTG FS host channels interrupt handler
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void stm32_gint_hcisr(FAR struct stm32_usbhost_s *priv)
|
|
{
|
|
uint32_t haint;
|
|
uint32_t hcchar;
|
|
int i = 0;
|
|
|
|
/* Read the Host all channels interrupt register and test each bit in the
|
|
* register. Each bit i, i=0...(STM32_NHOST_CHANNELS-1), corresponds to
|
|
* a pending interrupt on channel i.
|
|
*/
|
|
|
|
haint = stm32_getreg(STM32_OTG_HAINT);
|
|
for (i = 0; i < STM32_NHOST_CHANNELS; i++)
|
|
{
|
|
/* Is an interrupt pending on this channel? */
|
|
|
|
if ((haint & OTG_HAINT(i)) != 0)
|
|
{
|
|
/* Yes... read the HCCHAR register to get the direction bit */
|
|
|
|
hcchar = stm32_getreg(STM32_OTG_HCCHAR(i));
|
|
|
|
/* Was this an interrupt on an IN or an OUT channel? */
|
|
|
|
if ((hcchar & OTG_HCCHAR_EPDIR) != 0)
|
|
{
|
|
/* Handle the HC IN channel interrupt */
|
|
|
|
stm32_gint_hcinisr(priv, i);
|
|
}
|
|
else
|
|
{
|
|
/* Handle the HC OUT channel interrupt */
|
|
|
|
stm32_gint_hcoutisr(priv, i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_gint_hprtisr
|
|
*
|
|
* Description:
|
|
* USB OTG FS host port interrupt handler
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void stm32_gint_hprtisr(FAR struct stm32_usbhost_s *priv)
|
|
{
|
|
uint32_t hprt;
|
|
uint32_t newhprt;
|
|
uint32_t hcfg;
|
|
|
|
usbhost_vtrace1(OTG_VTRACE1_GINT_HPRT, 0);
|
|
|
|
/* Read the port status and control register (HPRT) */
|
|
|
|
hprt = stm32_getreg(STM32_OTG_HPRT);
|
|
|
|
/* Setup to clear the interrupt bits in GINTSTS by setting the corresponding
|
|
* bits in the HPRT. The HCINT interrupt bit is cleared when the appropriate
|
|
* status bits in the HPRT register are cleared.
|
|
*/
|
|
|
|
newhprt = hprt & ~(OTG_HPRT_PENA | OTG_HPRT_PCDET |
|
|
OTG_HPRT_PENCHNG | OTG_HPRT_POCCHNG);
|
|
|
|
/* Check for Port Overcurrent CHaNGe (POCCHNG) */
|
|
|
|
if ((hprt & OTG_HPRT_POCCHNG) != 0)
|
|
{
|
|
/* Set up to clear the POCCHNG status in the new HPRT contents. */
|
|
|
|
usbhost_vtrace1(OTG_VTRACE1_GINT_HPRT_POCCHNG, 0);
|
|
newhprt |= OTG_HPRT_POCCHNG;
|
|
}
|
|
|
|
/* Check for Port Connect DETected (PCDET). The core sets this bit when a
|
|
* device connection is detected.
|
|
*/
|
|
|
|
if ((hprt & OTG_HPRT_PCDET) != 0)
|
|
{
|
|
/* Set up to clear the PCDET status in the new HPRT contents. Then
|
|
* process the new connection event.
|
|
*/
|
|
|
|
usbhost_vtrace1(OTG_VTRACE1_GINT_HPRT_PCDET, 0);
|
|
newhprt |= OTG_HPRT_PCDET;
|
|
stm32_portreset(priv);
|
|
stm32_gint_connected(priv);
|
|
}
|
|
|
|
/* Check for Port Enable CHaNGed (PENCHNG) */
|
|
|
|
if ((hprt & OTG_HPRT_PENCHNG) != 0)
|
|
{
|
|
/* Set up to clear the PENCHNG status in the new HPRT contents. */
|
|
|
|
usbhost_vtrace1(OTG_VTRACE1_GINT_HPRT_PENCHNG, 0);
|
|
newhprt |= OTG_HPRT_PENCHNG;
|
|
|
|
/* Was the port enabled? */
|
|
|
|
if ((hprt & OTG_HPRT_PENA) != 0)
|
|
{
|
|
/* Yes.. handle the new connection event */
|
|
|
|
stm32_gint_connected(priv);
|
|
|
|
/* Check the Host ConFiGuration register (HCFG) */
|
|
|
|
hcfg = stm32_getreg(STM32_OTG_HCFG);
|
|
|
|
/* Is this a low speed or full speed connection (OTG FS does not
|
|
* support high speed)
|
|
*/
|
|
|
|
if ((hprt & OTG_HPRT_PSPD_MASK) == OTG_HPRT_PSPD_LS)
|
|
{
|
|
/* Set the Host Frame Interval Register for the 6KHz speed */
|
|
|
|
usbhost_vtrace1(OTG_VTRACE1_GINT_HPRT_LSDEV, 0);
|
|
stm32_putreg(STM32_OTG_HFIR, 6000);
|
|
|
|
/* Are we switching from FS to LS? */
|
|
|
|
if ((hcfg & OTG_HCFG_FSLSPCS_MASK) != OTG_HCFG_FSLSPCS_LS6MHz)
|
|
{
|
|
usbhost_vtrace1(OTG_VTRACE1_GINT_HPRT_FSLSSW, 0);
|
|
|
|
/* Yes... configure for LS */
|
|
|
|
hcfg &= ~OTG_HCFG_FSLSPCS_MASK;
|
|
hcfg |= OTG_HCFG_FSLSPCS_LS6MHz;
|
|
stm32_putreg(STM32_OTG_HCFG, hcfg);
|
|
|
|
/* And reset the port */
|
|
|
|
stm32_portreset(priv);
|
|
}
|
|
}
|
|
else /* if ((hprt & OTG_HPRT_PSPD_MASK) == OTG_HPRT_PSPD_FS) */
|
|
{
|
|
usbhost_vtrace1(OTG_VTRACE1_GINT_HPRT_FSDEV, 0);
|
|
stm32_putreg(STM32_OTG_HFIR, 48000);
|
|
|
|
/* Are we switching from LS to FS? */
|
|
|
|
if ((hcfg & OTG_HCFG_FSLSPCS_MASK) != OTG_HCFG_FSLSPCS_FS48MHz)
|
|
{
|
|
usbhost_vtrace1(OTG_VTRACE1_GINT_HPRT_LSFSSW, 0);
|
|
|
|
/* Yes... configure for FS */
|
|
|
|
hcfg &= ~OTG_HCFG_FSLSPCS_MASK;
|
|
hcfg |= OTG_HCFG_FSLSPCS_FS48MHz;
|
|
stm32_putreg(STM32_OTG_HCFG, hcfg);
|
|
|
|
/* And reset the port */
|
|
|
|
stm32_portreset(priv);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Clear port interrupts by setting bits in the HPRT */
|
|
|
|
stm32_putreg(STM32_OTG_HPRT, newhprt);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_gint_discisr
|
|
*
|
|
* Description:
|
|
* USB OTG FS disconnect detected interrupt handler
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void stm32_gint_discisr(FAR struct stm32_usbhost_s *priv)
|
|
{
|
|
/* Handle the disconnection event */
|
|
|
|
stm32_gint_disconnected(priv);
|
|
|
|
/* Clear the dicsonnect interrupt */
|
|
|
|
stm32_putreg(STM32_OTG_GINTSTS, OTG_GINT_DISC);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_gint_ipxfrisr
|
|
*
|
|
* Description:
|
|
* USB OTG FS incomplete periodic interrupt handler
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void stm32_gint_ipxfrisr(FAR struct stm32_usbhost_s *priv)
|
|
{
|
|
uint32_t regval;
|
|
|
|
/* CHENA : Set to enable the channel
|
|
* CHDIS : Set to stop transmitting/receiving data on a channel
|
|
*/
|
|
|
|
regval = stm32_getreg(STM32_OTG_HCCHAR(0));
|
|
regval |= (OTG_HCCHAR_CHDIS | OTG_HCCHAR_CHENA);
|
|
stm32_putreg(STM32_OTG_HCCHAR(0), regval);
|
|
|
|
/* Clear the incomplete isochronous OUT interrupt */
|
|
|
|
stm32_putreg(STM32_OTG_GINTSTS, OTG_GINT_IPXFR);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_gint_isr
|
|
*
|
|
* Description:
|
|
* USB OTG FS global interrupt handler
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int stm32_gint_isr(int irq, FAR void *context, FAR void *arg)
|
|
{
|
|
/* At present, there is only support for a single OTG FS host. Hence it is
|
|
* pre-allocated as g_usbhost. However, in most code, the private data
|
|
* structure will be referenced using the 'priv' pointer (rather than the
|
|
* global data) in order to simplify any future support for multiple devices.
|
|
*/
|
|
|
|
FAR struct stm32_usbhost_s *priv = &g_usbhost;
|
|
uint32_t pending;
|
|
|
|
/* If OTG were supported, we would need to check if we are in host or
|
|
* device mode when the global interrupt occurs. Here we support only
|
|
* host mode
|
|
*/
|
|
|
|
/* Loop while there are pending interrupts to process. This loop may save a
|
|
* little interrupt handling overhead.
|
|
*/
|
|
|
|
for (; ; )
|
|
{
|
|
/* Get the unmasked bits in the GINT status */
|
|
|
|
pending = stm32_getreg(STM32_OTG_GINTSTS);
|
|
pending &= stm32_getreg(STM32_OTG_GINTMSK);
|
|
|
|
/* Return from the interrupt when there are no further pending
|
|
* interrupts.
|
|
*/
|
|
|
|
if (pending == 0)
|
|
{
|
|
return OK;
|
|
}
|
|
|
|
/* Otherwise, process each pending, unmasked GINT interrupts */
|
|
|
|
/* Handle the start of frame interrupt */
|
|
|
|
#ifdef CONFIG_STM32H7_OTG_SOFINTR
|
|
if ((pending & OTG_GINT_SOF) != 0)
|
|
{
|
|
usbhost_vtrace1(OTG_VTRACE1_GINT_SOF, 0);
|
|
stm32_gint_sofisr(priv);
|
|
}
|
|
#endif
|
|
|
|
/* Handle the RxFIFO non-empty interrupt */
|
|
|
|
if ((pending & OTG_GINT_RXFLVL) != 0)
|
|
{
|
|
usbhost_vtrace1(OTG_VTRACE1_GINT_RXFLVL, 0);
|
|
stm32_gint_rxflvlisr(priv);
|
|
}
|
|
|
|
/* Handle the non-periodic TxFIFO empty interrupt */
|
|
|
|
if ((pending & OTG_GINT_NPTXFE) != 0)
|
|
{
|
|
usbhost_vtrace1(OTG_VTRACE1_GINT_NPTXFE, 0);
|
|
stm32_gint_nptxfeisr(priv);
|
|
}
|
|
|
|
/* Handle the periodic TxFIFO empty interrupt */
|
|
|
|
if ((pending & OTG_GINT_PTXFE) != 0)
|
|
{
|
|
usbhost_vtrace1(OTG_VTRACE1_GINT_PTXFE, 0);
|
|
stm32_gint_ptxfeisr(priv);
|
|
}
|
|
|
|
/* Handle the host channels interrupt */
|
|
|
|
if ((pending & OTG_GINT_HC) != 0)
|
|
{
|
|
usbhost_vtrace1(OTG_VTRACE1_GINT_HC, 0);
|
|
stm32_gint_hcisr(priv);
|
|
}
|
|
|
|
/* Handle the host port interrupt */
|
|
|
|
if ((pending & OTG_GINT_HPRT) != 0)
|
|
{
|
|
stm32_gint_hprtisr(priv);
|
|
}
|
|
|
|
/* Handle the disconnect detected interrupt */
|
|
|
|
if ((pending & OTG_GINT_DISC) != 0)
|
|
{
|
|
usbhost_vtrace1(OTG_VTRACE1_GINT_DISC, 0);
|
|
stm32_gint_discisr(priv);
|
|
}
|
|
|
|
/* Handle the incomplete periodic transfer */
|
|
|
|
if ((pending & OTG_GINT_IPXFR) != 0)
|
|
{
|
|
usbhost_vtrace1(OTG_VTRACE1_GINT_IPXFR, 0);
|
|
stm32_gint_ipxfrisr(priv);
|
|
}
|
|
}
|
|
|
|
/* We won't get here */
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_gint_enable and stm32_gint_disable
|
|
*
|
|
* Description:
|
|
* Respectively enable or disable the global OTG FS interrupt.
|
|
*
|
|
* Input Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_gint_enable(void)
|
|
{
|
|
uint32_t regval;
|
|
|
|
/* Set the GINTMSK bit to unmask the interrupt */
|
|
|
|
regval = stm32_getreg(STM32_OTG_GAHBCFG);
|
|
regval |= OTG_GAHBCFG_GINTMSK;
|
|
stm32_putreg(STM32_OTG_GAHBCFG, regval);
|
|
}
|
|
|
|
static void stm32_gint_disable(void)
|
|
{
|
|
uint32_t regval;
|
|
|
|
/* Clear the GINTMSK bit to mask the interrupt */
|
|
|
|
regval = stm32_getreg(STM32_OTG_GAHBCFG);
|
|
regval &= ~OTG_GAHBCFG_GINTMSK;
|
|
stm32_putreg(STM32_OTG_GAHBCFG, regval);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_hostinit_enable
|
|
*
|
|
* Description:
|
|
* Enable host interrupts.
|
|
*
|
|
* Input Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void stm32_hostinit_enable(void)
|
|
{
|
|
uint32_t regval;
|
|
|
|
/* Disable all interrupts. */
|
|
|
|
stm32_putreg(STM32_OTG_GINTMSK, 0);
|
|
|
|
/* Clear any pending interrupts. */
|
|
|
|
stm32_putreg(STM32_OTG_GINTSTS, 0xffffffff);
|
|
|
|
/* Clear any pending USB OTG Interrupts (should be done elsewhere if OTG is supported) */
|
|
|
|
stm32_putreg(STM32_OTG_GOTGINT, 0xffffffff);
|
|
|
|
/* Clear any pending USB OTG interrupts */
|
|
|
|
stm32_putreg(STM32_OTG_GINTSTS, 0xbfffffff);
|
|
|
|
/* Enable the host interrupts */
|
|
|
|
/* Common interrupts:
|
|
*
|
|
* OTG_GINT_WKUP : Resume/remote wakeup detected interrupt
|
|
* OTG_GINT_USBSUSP : USB suspend
|
|
*/
|
|
|
|
regval = (OTG_GINT_WKUP | OTG_GINT_USBSUSP);
|
|
|
|
/* If OTG were supported, we would need to enable the following as well:
|
|
*
|
|
* OTG_GINT_OTG : OTG interrupt
|
|
* OTG_GINT_SRQ : Session request/new session detected interrupt
|
|
* OTG_GINT_CIDSCHG : Connector ID status change
|
|
*/
|
|
|
|
/* Host-specific interrupts
|
|
*
|
|
* OTG_GINT_SOF : Start of frame
|
|
* OTG_GINT_RXFLVL : RxFIFO non-empty
|
|
* OTG_GINT_IISOOXFR : Incomplete isochronous OUT transfer
|
|
* OTG_GINT_HPRT : Host port interrupt
|
|
* OTG_GINT_HC : Host channels interrupt
|
|
* OTG_GINT_DISC : Disconnect detected interrupt
|
|
*/
|
|
|
|
#ifdef CONFIG_STM32H7_OTG_SOFINTR
|
|
regval |= (OTG_GINT_SOF | OTG_GINT_RXFLVL | OTG_GINT_IISOOXFR |
|
|
OTG_GINT_HPRT | OTG_GINT_HC | OTG_GINT_DISC);
|
|
#else
|
|
regval |= (OTG_GINT_RXFLVL | OTG_GINT_IPXFR | OTG_GINT_HPRT |
|
|
OTG_GINT_HC | OTG_GINT_DISC);
|
|
#endif
|
|
stm32_putreg(STM32_OTG_GINTMSK, regval);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_txfe_enable
|
|
*
|
|
* Description:
|
|
* Enable Tx FIFO empty interrupts. This is necessary when the entire
|
|
* transfer will not fit into Tx FIFO. The transfer will then be completed
|
|
* when the Tx FIFO is empty. NOTE: The Tx FIFO interrupt is disabled
|
|
* the fifo empty interrupt handler when the transfer is complete.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Driver state structure reference
|
|
* chidx - The channel that requires the Tx FIFO empty interrupt
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* Called from user task context. Interrupts must be disabled to assure
|
|
* exclusive access to the GINTMSK register.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_txfe_enable(FAR struct stm32_usbhost_s *priv, int chidx)
|
|
{
|
|
FAR struct stm32_chan_s *chan = &priv->chan[chidx];
|
|
irqstate_t flags;
|
|
uint32_t regval;
|
|
|
|
/* Disable all interrupts so that we have exclusive access to the GINTMSK
|
|
* (it would be sufficent just to disable the GINT interrupt).
|
|
*/
|
|
|
|
flags = enter_critical_section();
|
|
|
|
/* Should we enable the periodic or non-peridic Tx FIFO empty interrupts */
|
|
|
|
regval = stm32_getreg(STM32_OTG_GINTMSK);
|
|
switch (chan->eptype)
|
|
{
|
|
default:
|
|
case OTG_EPTYPE_CTRL: /* Non periodic transfer */
|
|
case OTG_EPTYPE_BULK:
|
|
regval |= OTG_GINT_NPTXFE;
|
|
break;
|
|
|
|
case OTG_EPTYPE_INTR: /* Periodic transfer */
|
|
case OTG_EPTYPE_ISOC:
|
|
regval |= OTG_GINT_PTXFE;
|
|
break;
|
|
}
|
|
|
|
/* Enable interrupts */
|
|
|
|
stm32_putreg(STM32_OTG_GINTMSK, regval);
|
|
leave_critical_section(flags);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* USB Host Controller Operations
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_wait
|
|
*
|
|
* Description:
|
|
* Wait for a device to be connected or disconnected to/from a hub port.
|
|
*
|
|
* Input Parameters:
|
|
* conn - The USB host connection instance obtained as a parameter from the call to
|
|
* the USB driver initialization logic.
|
|
* hport - The location to return the hub port descriptor that detected the
|
|
* connection related event.
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) is returned on success when a device is connected or
|
|
* disconnected. This function will not return until either (1) a device is
|
|
* connected or disconnect to/from any hub port or until (2) some failure
|
|
* occurs. On a failure, a negated errno value is returned indicating the
|
|
* nature of the failure
|
|
*
|
|
* Assumptions:
|
|
* - Called from a single thread so no mutual exclusion is required.
|
|
* - Never called from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int stm32_wait(FAR struct usbhost_connection_s *conn,
|
|
FAR struct usbhost_hubport_s **hport)
|
|
{
|
|
FAR struct stm32_usbhost_s *priv = &g_usbhost;
|
|
struct usbhost_hubport_s *connport;
|
|
irqstate_t flags;
|
|
|
|
/* Loop until a change in connection state is detected */
|
|
|
|
flags = enter_critical_section();
|
|
for (; ; )
|
|
{
|
|
/* Is there a change in the connection state of the single root hub
|
|
* port?
|
|
*/
|
|
|
|
if (priv->change)
|
|
{
|
|
connport = &priv->rhport.hport;
|
|
|
|
/* Yes. Remember the new state */
|
|
|
|
connport->connected = priv->connected;
|
|
priv->change = false;
|
|
|
|
/* And return the root hub port */
|
|
|
|
*hport = connport;
|
|
leave_critical_section(flags);
|
|
|
|
uinfo("RHport Connected: %s\n", connport->connected ? "YES" : "NO");
|
|
return OK;
|
|
}
|
|
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
/* Is a device connected to an external hub? */
|
|
|
|
if (priv->hport)
|
|
{
|
|
/* Yes.. return the external hub port */
|
|
|
|
connport = (struct usbhost_hubport_s *)priv->hport;
|
|
priv->hport = NULL;
|
|
|
|
*hport = connport;
|
|
leave_critical_section(flags);
|
|
|
|
uinfo("Hub port Connected: %s\n", connport->connected ? "YES" : "NO");
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
/* Wait for the next connection event */
|
|
|
|
priv->pscwait = true;
|
|
stm32_takesem(&priv->pscsem);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_enumerate
|
|
*
|
|
* Description:
|
|
* Enumerate the connected device. As part of this enumeration process,
|
|
* the driver will (1) get the device's configuration descriptor, (2)
|
|
* extract the class ID info from the configuration descriptor, (3) call
|
|
* usbhost_findclass() to find the class that supports this device, (4)
|
|
* call the create() method on the struct usbhost_registry_s interface
|
|
* to get a class instance, and finally (5) call the connect() method
|
|
* of the struct usbhost_class_s interface. After that, the class is in
|
|
* charge of the sequence of operations.
|
|
*
|
|
* Input Parameters:
|
|
* conn - The USB host connection instance obtained as a parameter from
|
|
* the call to the USB driver initialization logic.
|
|
* hport - The descriptor of the hub port that has the newly connected
|
|
* device.
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value is
|
|
* returned indicating the nature of the failure
|
|
*
|
|
* Assumptions:
|
|
* This function will *not* be called from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int stm32_rh_enumerate(FAR struct stm32_usbhost_s *priv,
|
|
FAR struct usbhost_connection_s *conn,
|
|
FAR struct usbhost_hubport_s *hport)
|
|
{
|
|
uint32_t regval;
|
|
int ret;
|
|
|
|
DEBUGASSERT(conn != NULL && hport != NULL && hport->port == 0);
|
|
|
|
/* Are we connected to a device? The caller should have called the wait()
|
|
* method first to be assured that a device is connected.
|
|
*/
|
|
|
|
while (!priv->connected)
|
|
{
|
|
/* No, return an error */
|
|
|
|
usbhost_trace1(OTG_TRACE1_DEVDISCONN, 0);
|
|
return -ENODEV;
|
|
}
|
|
|
|
DEBUGASSERT(priv->smstate == SMSTATE_ATTACHED);
|
|
|
|
/* USB 2.0 spec says at least 50ms delay before port reset. We wait
|
|
* 100ms.
|
|
*/
|
|
|
|
nxsig_usleep(100 * 1000);
|
|
|
|
/* Reset the host port */
|
|
|
|
stm32_portreset(priv);
|
|
|
|
/* Get the current device speed */
|
|
|
|
regval = stm32_getreg(STM32_OTG_HPRT);
|
|
if ((regval & OTG_HPRT_PSPD_MASK) == OTG_HPRT_PSPD_LS)
|
|
{
|
|
priv->rhport.hport.speed = USB_SPEED_LOW;
|
|
}
|
|
else
|
|
{
|
|
priv->rhport.hport.speed = USB_SPEED_FULL;
|
|
}
|
|
|
|
/* Allocate and initialize the root hub port EP0 channels */
|
|
|
|
ret = stm32_ctrlchan_alloc(priv, 0, 0, priv->rhport.hport.speed, &priv->ep0);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: Failed to allocate a control endpoint: %d\n", ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int stm32_enumerate(FAR struct usbhost_connection_s *conn,
|
|
FAR struct usbhost_hubport_s *hport)
|
|
{
|
|
FAR struct stm32_usbhost_s *priv = &g_usbhost;
|
|
int ret;
|
|
|
|
DEBUGASSERT(hport);
|
|
|
|
/* If this is a connection on the root hub, then we need to go to
|
|
* little more effort to get the device speed. If it is a connection
|
|
* on an external hub, then we already have that information.
|
|
*/
|
|
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
if (ROOTHUB(hport))
|
|
#endif
|
|
{
|
|
ret = stm32_rh_enumerate(priv, conn, hport);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* Then let the common usbhost_enumerate do the real enumeration. */
|
|
|
|
uinfo("Enumerate the device\n");
|
|
priv->smstate = SMSTATE_ENUM;
|
|
ret = usbhost_enumerate(hport, &hport->devclass);
|
|
|
|
/* The enumeration may fail either because of some HCD interfaces failure
|
|
* or because the device class is not supported. In either case, we just
|
|
* need to perform the disconnection operation and make ready for a new
|
|
* enumeration.
|
|
*/
|
|
|
|
if (ret < 0)
|
|
{
|
|
/* Return to the disconnected state */
|
|
|
|
uerr("ERROR: Enumeration failed: %d\n", ret);
|
|
stm32_gint_disconnected(priv);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: stm32_ep0configure
|
|
*
|
|
* Description:
|
|
* Configure endpoint 0. This method is normally used internally by the
|
|
* enumerate() method but is made available at the interface to support an
|
|
* external implementation of the enumeration logic.
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the call to
|
|
* the class create() method.
|
|
* ep0 - The (opaque) EP0 endpoint instance
|
|
* funcaddr - The USB address of the function containing the endpoint that EP0
|
|
* controls
|
|
* speed - The speed of the port USB_SPEED_LOW, _FULL, or _HIGH
|
|
* maxpacketsize - The maximum number of bytes that can be sent to or
|
|
* received from the endpoint in a single data packet
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value is
|
|
* returned indicating the nature of the failure
|
|
*
|
|
* Assumptions:
|
|
* This function will *not* be called from an interrupt handler.
|
|
*
|
|
************************************************************************************/
|
|
|
|
static int stm32_ep0configure(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0,
|
|
uint8_t funcaddr, uint8_t speed,
|
|
uint16_t maxpacketsize)
|
|
{
|
|
FAR struct stm32_usbhost_s *priv = (FAR struct stm32_usbhost_s *)drvr;
|
|
FAR struct stm32_ctrlinfo_s *ep0info = (FAR struct stm32_ctrlinfo_s *)ep0;
|
|
FAR struct stm32_chan_s *chan;
|
|
|
|
DEBUGASSERT(drvr != NULL && ep0info != NULL && funcaddr < 128 &&
|
|
maxpacketsize <= 64);
|
|
|
|
/* We must have exclusive access to the USB host hardware and state structures */
|
|
|
|
stm32_takesem(&priv->exclsem);
|
|
|
|
/* Configure the EP0 OUT channel */
|
|
|
|
chan = &priv->chan[ep0info->outndx];
|
|
chan->funcaddr = funcaddr;
|
|
chan->speed = speed;
|
|
chan->maxpacket = maxpacketsize;
|
|
|
|
stm32_chan_configure(priv, ep0info->outndx);
|
|
|
|
/* Configure the EP0 IN channel */
|
|
|
|
chan = &priv->chan[ep0info->inndx];
|
|
chan->funcaddr = funcaddr;
|
|
chan->speed = speed;
|
|
chan->maxpacket = maxpacketsize;
|
|
|
|
stm32_chan_configure(priv, ep0info->inndx);
|
|
|
|
stm32_givesem(&priv->exclsem);
|
|
return OK;
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: stm32_epalloc
|
|
*
|
|
* Description:
|
|
* Allocate and configure one endpoint.
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the call to
|
|
* the class create() method.
|
|
* epdesc - Describes the endpoint to be allocated.
|
|
* ep - A memory location provided by the caller in which to receive the
|
|
* allocated endpoint descriptor.
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value is
|
|
* returned indicating the nature of the failure
|
|
*
|
|
* Assumptions:
|
|
* This function will *not* be called from an interrupt handler.
|
|
*
|
|
************************************************************************************/
|
|
|
|
static int stm32_epalloc(FAR struct usbhost_driver_s *drvr,
|
|
FAR const struct usbhost_epdesc_s *epdesc,
|
|
FAR usbhost_ep_t *ep)
|
|
{
|
|
FAR struct stm32_usbhost_s *priv = (FAR struct stm32_usbhost_s *)drvr;
|
|
int ret;
|
|
|
|
/* Sanity check. NOTE that this method should only be called if a device is
|
|
* connected (because we need a valid low speed indication).
|
|
*/
|
|
|
|
DEBUGASSERT(drvr != 0 && epdesc != NULL && ep != NULL);
|
|
|
|
/* We must have exclusive access to the USB host hardware and state structures */
|
|
|
|
stm32_takesem(&priv->exclsem);
|
|
|
|
/* Handler control pipes differently from other endpoint types. This is
|
|
* because the normal, "transfer" endpoints are unidirectional an require
|
|
* only a single channel. Control endpoints, however, are bi-diretional
|
|
* and require two channels, one for the IN and one for the OUT direction.
|
|
*/
|
|
|
|
if (epdesc->xfrtype == OTG_EPTYPE_CTRL)
|
|
{
|
|
ret = stm32_ctrlep_alloc(priv, epdesc, ep);
|
|
}
|
|
else
|
|
{
|
|
ret = stm32_xfrep_alloc(priv, epdesc, ep);
|
|
}
|
|
|
|
stm32_givesem(&priv->exclsem);
|
|
return ret;
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: stm32_epfree
|
|
*
|
|
* Description:
|
|
* Free and endpoint previously allocated by DRVR_EPALLOC.
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the call to
|
|
* the class create() method.
|
|
* ep - The endpoint to be freed.
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value is
|
|
* returned indicating the nature of the failure
|
|
*
|
|
* Assumptions:
|
|
* This function will *not* be called from an interrupt handler.
|
|
*
|
|
************************************************************************************/
|
|
|
|
static int stm32_epfree(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep)
|
|
{
|
|
FAR struct stm32_usbhost_s *priv = (FAR struct stm32_usbhost_s *)drvr;
|
|
|
|
DEBUGASSERT(priv);
|
|
|
|
/* We must have exclusive access to the USB host hardware and state structures */
|
|
|
|
stm32_takesem(&priv->exclsem);
|
|
|
|
/* A single channel is represent by an index in the range of 0 to
|
|
* STM32_MAX_TX_FIFOS. Otherwise, the ep must be a pointer to an allocated
|
|
* control endpoint structure.
|
|
*/
|
|
|
|
if ((uintptr_t)ep < STM32_MAX_TX_FIFOS)
|
|
{
|
|
/* Halt the channel and mark the channel available */
|
|
|
|
stm32_chan_free(priv, (int)ep);
|
|
}
|
|
else
|
|
{
|
|
/* Halt both control channel and mark the channels available */
|
|
|
|
FAR struct stm32_ctrlinfo_s *ctrlep = (FAR struct stm32_ctrlinfo_s *)ep;
|
|
stm32_chan_free(priv, ctrlep->inndx);
|
|
stm32_chan_free(priv, ctrlep->outndx);
|
|
|
|
/* And free the control endpoint container */
|
|
|
|
kmm_free(ctrlep);
|
|
}
|
|
|
|
stm32_givesem(&priv->exclsem);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_alloc
|
|
*
|
|
* Description:
|
|
* Some hardware supports special memory in which request and descriptor data can
|
|
* be accessed more efficiently. This method provides a mechanism to allocate
|
|
* the request/descriptor memory. If the underlying hardware does not support
|
|
* such "special" memory, this functions may simply map to kmm_malloc.
|
|
*
|
|
* This interface was optimized under a particular assumption. It was assumed
|
|
* that the driver maintains a pool of small, pre-allocated buffers for descriptor
|
|
* traffic. NOTE that size is not an input, but an output: The size of the
|
|
* pre-allocated buffer is returned.
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the call to
|
|
* the class create() method.
|
|
* buffer - The address of a memory location provided by the caller in which to
|
|
* return the allocated buffer memory address.
|
|
* maxlen - The address of a memory location provided by the caller in which to
|
|
* return the maximum size of the allocated buffer memory.
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value is
|
|
* returned indicating the nature of the failure
|
|
*
|
|
* Assumptions:
|
|
* - Called from a single thread so no mutual exclusion is required.
|
|
* - Never called from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int stm32_alloc(FAR struct usbhost_driver_s *drvr,
|
|
FAR uint8_t **buffer, FAR size_t *maxlen)
|
|
{
|
|
FAR uint8_t *alloc;
|
|
|
|
DEBUGASSERT(drvr && buffer && maxlen);
|
|
|
|
/* There is no special memory requirement for the STM32. */
|
|
|
|
alloc = (FAR uint8_t *)kmm_malloc(CONFIG_STM32H7_OTG_DESCSIZE);
|
|
if (!alloc)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Return the allocated address and size of the descriptor buffer */
|
|
|
|
*buffer = alloc;
|
|
*maxlen = CONFIG_STM32H7_OTG_DESCSIZE;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_free
|
|
*
|
|
* Description:
|
|
* Some hardware supports special memory in which request and descriptor data can
|
|
* be accessed more efficiently. This method provides a mechanism to free that
|
|
* request/descriptor memory. If the underlying hardware does not support
|
|
* such "special" memory, this functions may simply map to kmm_free().
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the call to
|
|
* the class create() method.
|
|
* buffer - The address of the allocated buffer memory to be freed.
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value is
|
|
* returned indicating the nature of the failure
|
|
*
|
|
* Assumptions:
|
|
* - Never called from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int stm32_free(FAR struct usbhost_driver_s *drvr, FAR uint8_t *buffer)
|
|
{
|
|
/* There is no special memory requirement */
|
|
|
|
DEBUGASSERT(drvr && buffer);
|
|
kmm_free(buffer);
|
|
return OK;
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: stm32_ioalloc
|
|
*
|
|
* Description:
|
|
* Some hardware supports special memory in which larger IO buffers can
|
|
* be accessed more efficiently. This method provides a mechanism to allocate
|
|
* the request/descriptor memory. If the underlying hardware does not support
|
|
* such "special" memory, this functions may simply map to kmm_malloc.
|
|
*
|
|
* This interface differs from DRVR_ALLOC in that the buffers are variable-sized.
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the call to
|
|
* the class create() method.
|
|
* buffer - The address of a memory location provided by the caller in which to
|
|
* return the allocated buffer memory address.
|
|
* buflen - The size of the buffer required.
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value is
|
|
* returned indicating the nature of the failure
|
|
*
|
|
* Assumptions:
|
|
* This function will *not* be called from an interrupt handler.
|
|
*
|
|
************************************************************************************/
|
|
|
|
static int stm32_ioalloc(FAR struct usbhost_driver_s *drvr,
|
|
FAR uint8_t **buffer, size_t buflen)
|
|
{
|
|
FAR uint8_t *alloc;
|
|
|
|
DEBUGASSERT(drvr && buffer && buflen > 0);
|
|
|
|
/* There is no special memory requirement */
|
|
|
|
alloc = (FAR uint8_t *)kmm_malloc(buflen);
|
|
if (!alloc)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Return the allocated buffer */
|
|
|
|
*buffer = alloc;
|
|
return OK;
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: stm32_iofree
|
|
*
|
|
* Description:
|
|
* Some hardware supports special memory in which IO data can be accessed more
|
|
* efficiently. This method provides a mechanism to free that IO buffer
|
|
* memory. If the underlying hardware does not support such "special" memory,
|
|
* this functions may simply map to kmm_free().
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the call to
|
|
* the class create() method.
|
|
* buffer - The address of the allocated buffer memory to be freed.
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value is
|
|
* returned indicating the nature of the failure
|
|
*
|
|
* Assumptions:
|
|
* This function will *not* be called from an interrupt handler.
|
|
*
|
|
************************************************************************************/
|
|
|
|
static int stm32_iofree(FAR struct usbhost_driver_s *drvr, FAR uint8_t *buffer)
|
|
{
|
|
/* There is no special memory requirement */
|
|
|
|
DEBUGASSERT(drvr && buffer);
|
|
kmm_free(buffer);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_ctrlin and stm32_ctrlout
|
|
*
|
|
* Description:
|
|
* Process a IN or OUT request on the control endpoint. These methods
|
|
* will enqueue the request and wait for it to complete. Only one transfer may be
|
|
* queued; Neither these methods nor the transfer() method can be called again
|
|
* until the control transfer functions returns.
|
|
*
|
|
* These are blocking methods; these functions will not return until the
|
|
* control transfer has completed.
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the call to
|
|
* the class create() method.
|
|
* ep0 - The control endpoint to send/receive the control request.
|
|
* req - Describes the request to be sent. This request must lie in memory
|
|
* created by DRVR_ALLOC.
|
|
* buffer - A buffer used for sending the request and for returning any
|
|
* responses. This buffer must be large enough to hold the length value
|
|
* in the request description. buffer must have been allocated using DRVR_ALLOC.
|
|
*
|
|
* NOTE: On an IN transaction, req and buffer may refer to the same allocated
|
|
* memory.
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value is
|
|
* returned indicating the nature of the failure
|
|
*
|
|
* Assumptions:
|
|
* - Called from a single thread so no mutual exclusion is required.
|
|
* - Never called from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int stm32_ctrlin(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0,
|
|
FAR const struct usb_ctrlreq_s *req,
|
|
FAR uint8_t *buffer)
|
|
{
|
|
FAR struct stm32_usbhost_s *priv = (FAR struct stm32_usbhost_s *)drvr;
|
|
FAR struct stm32_ctrlinfo_s *ep0info = (FAR struct stm32_ctrlinfo_s *)ep0;
|
|
uint16_t buflen;
|
|
clock_t start;
|
|
clock_t elapsed;
|
|
int retries;
|
|
int ret;
|
|
|
|
DEBUGASSERT(priv != NULL && ep0info != NULL && req != NULL);
|
|
usbhost_vtrace2(OTG_VTRACE2_CTRLIN, req->type, req->req);
|
|
uinfo("type:%02x req:%02x value:%02x%02x index:%02x%02x len:%02x%02x\n",
|
|
req->type, req->req, req->value[1], req->value[0],
|
|
req->index[1], req->index[0], req->len[1], req->len[0]);
|
|
|
|
/* Extract values from the request */
|
|
|
|
buflen = stm32_getle16(req->len);
|
|
|
|
/* We must have exclusive access to the USB host hardware and state structures */
|
|
|
|
stm32_takesem(&priv->exclsem);
|
|
|
|
/* Loop, retrying until the retry time expires */
|
|
|
|
for (retries = 0; retries < STM32_RETRY_COUNT; retries++)
|
|
{
|
|
/* Send the SETUP request */
|
|
|
|
ret = stm32_ctrl_sendsetup(priv, ep0info, req);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(OTG_TRACE1_SENDSETUP, -ret);
|
|
continue;
|
|
}
|
|
|
|
/* Handle the IN data phase (if any) */
|
|
|
|
if (buflen > 0)
|
|
{
|
|
ret = stm32_ctrl_recvdata(priv, ep0info, buffer, buflen);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(OTG_TRACE1_RECVDATA, -ret);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Get the start time. Loop again until the timeout expires */
|
|
|
|
start = clock_systimer();
|
|
do
|
|
{
|
|
/* Handle the status OUT phase */
|
|
|
|
priv->chan[ep0info->outndx].outdata1 ^= true;
|
|
ret = stm32_ctrl_senddata(priv, ep0info, NULL, 0);
|
|
if (ret == OK)
|
|
{
|
|
/* All success transactions exit here */
|
|
|
|
stm32_givesem(&priv->exclsem);
|
|
return OK;
|
|
}
|
|
|
|
usbhost_trace1(OTG_TRACE1_SENDDATA, ret < 0 ? -ret : ret);
|
|
|
|
/* Get the elapsed time (in frames) */
|
|
|
|
elapsed = clock_systimer() - start;
|
|
}
|
|
while (elapsed < STM32_DATANAK_DELAY);
|
|
}
|
|
|
|
/* All failures exit here after all retries and timeouts have been exhausted */
|
|
|
|
stm32_givesem(&priv->exclsem);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static int stm32_ctrlout(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep0,
|
|
FAR const struct usb_ctrlreq_s *req,
|
|
FAR const uint8_t *buffer)
|
|
{
|
|
FAR struct stm32_usbhost_s *priv = (FAR struct stm32_usbhost_s *)drvr;
|
|
FAR struct stm32_ctrlinfo_s *ep0info = (FAR struct stm32_ctrlinfo_s *)ep0;
|
|
uint16_t buflen;
|
|
clock_t start;
|
|
clock_t elapsed;
|
|
int retries;
|
|
int ret;
|
|
|
|
DEBUGASSERT(priv != NULL && ep0info != NULL && req != NULL);
|
|
usbhost_vtrace2(OTG_VTRACE2_CTRLOUT, req->type, req->req);
|
|
uinfo("type:%02x req:%02x value:%02x%02x index:%02x%02x len:%02x%02x\n",
|
|
req->type, req->req, req->value[1], req->value[0],
|
|
req->index[1], req->index[0], req->len[1], req->len[0]);
|
|
|
|
/* Extract values from the request */
|
|
|
|
buflen = stm32_getle16(req->len);
|
|
|
|
/* We must have exclusive access to the USB host hardware and state structures */
|
|
|
|
stm32_takesem(&priv->exclsem);
|
|
|
|
/* Loop, retrying until the retry time expires */
|
|
|
|
for (retries = 0; retries < STM32_RETRY_COUNT; retries++)
|
|
{
|
|
/* Send the SETUP request */
|
|
|
|
ret = stm32_ctrl_sendsetup(priv, ep0info, req);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(OTG_TRACE1_SENDSETUP, -ret);
|
|
continue;
|
|
}
|
|
|
|
/* Get the start time. Loop again until the timeout expires */
|
|
|
|
start = clock_systimer();
|
|
do
|
|
{
|
|
/* Handle the data OUT phase (if any) */
|
|
|
|
if (buflen > 0)
|
|
{
|
|
/* Start DATA out transfer (only one DATA packet) */
|
|
|
|
priv->chan[ep0info->outndx].outdata1 = true;
|
|
ret = stm32_ctrl_senddata(priv, ep0info, (FAR uint8_t *)buffer,
|
|
buflen);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(OTG_TRACE1_SENDDATA, -ret);
|
|
}
|
|
}
|
|
|
|
/* Handle the status IN phase */
|
|
|
|
if (ret == OK)
|
|
{
|
|
ret = stm32_ctrl_recvdata(priv, ep0info, NULL, 0);
|
|
if (ret == OK)
|
|
{
|
|
/* All success transactins exit here */
|
|
|
|
stm32_givesem(&priv->exclsem);
|
|
return OK;
|
|
}
|
|
|
|
usbhost_trace1(OTG_TRACE1_RECVDATA, ret < 0 ? -ret : ret);
|
|
}
|
|
|
|
/* Get the elapsed time (in frames) */
|
|
|
|
elapsed = clock_systimer() - start;
|
|
}
|
|
while (elapsed < STM32_DATANAK_DELAY);
|
|
}
|
|
|
|
/* All failures exit here after all retries and timeouts have been exhausted */
|
|
|
|
stm32_givesem(&priv->exclsem);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_transfer
|
|
*
|
|
* Description:
|
|
* Process a request to handle a transfer descriptor. This method will
|
|
* enqueue the transfer request, blocking until the transfer completes. Only
|
|
* one transfer may be queued; Neither this method nor the ctrlin or
|
|
* ctrlout methods can be called again until this function returns.
|
|
*
|
|
* This is a blocking method; this functions will not return until the
|
|
* transfer has completed.
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the call to
|
|
* the class create() method.
|
|
* ep - The IN or OUT endpoint descriptor for the device endpoint on which to
|
|
* perform the transfer.
|
|
* buffer - A buffer containing the data to be sent (OUT endpoint) or received
|
|
* (IN endpoint). buffer must have been allocated using DRVR_ALLOC
|
|
* buflen - The length of the data to be sent or received.
|
|
*
|
|
* Returned Value:
|
|
* On success, a non-negative value is returned that indicates the number
|
|
* of bytes successfully transferred. On a failure, a negated errno value is
|
|
* returned that indicates the nature of the failure:
|
|
*
|
|
* EAGAIN - If devices NAKs the transfer (or NYET or other error where
|
|
* it may be appropriate to restart the entire transaction).
|
|
* EPERM - If the endpoint stalls
|
|
* EIO - On a TX or data toggle error
|
|
* EPIPE - Overrun errors
|
|
*
|
|
* Assumptions:
|
|
* - Called from a single thread so no mutual exclusion is required.
|
|
* - Never called from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static ssize_t stm32_transfer(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep,
|
|
FAR uint8_t *buffer, size_t buflen)
|
|
{
|
|
FAR struct stm32_usbhost_s *priv = (FAR struct stm32_usbhost_s *)drvr;
|
|
unsigned int chidx = (unsigned int)ep;
|
|
ssize_t nbytes;
|
|
|
|
uinfo("chidx: %d buflen: %d\n", (unsigned int)ep, buflen);
|
|
|
|
DEBUGASSERT(priv && buffer && chidx < STM32_MAX_TX_FIFOS && buflen > 0);
|
|
|
|
/* We must have exclusive access to the USB host hardware and state structures */
|
|
|
|
stm32_takesem(&priv->exclsem);
|
|
|
|
/* Handle IN and OUT transfer slightly differently */
|
|
|
|
if (priv->chan[chidx].in)
|
|
{
|
|
nbytes = stm32_in_transfer(priv, chidx, buffer, buflen);
|
|
}
|
|
else
|
|
{
|
|
nbytes = stm32_out_transfer(priv, chidx, buffer, buflen);
|
|
}
|
|
|
|
stm32_givesem(&priv->exclsem);
|
|
return nbytes;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_asynch
|
|
*
|
|
* Description:
|
|
* Process a request to handle a transfer descriptor. This method will
|
|
* enqueue the transfer request and return immediately. When the transfer
|
|
* completes, the callback will be invoked with the provided transfer.
|
|
* This method is useful for receiving interrupt transfers which may come
|
|
* infrequently.
|
|
*
|
|
* Only one transfer may be queued; Neither this method nor the ctrlin or
|
|
* ctrlout methods can be called again until the transfer completes.
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the call to
|
|
* the class create() method.
|
|
* ep - The IN or OUT endpoint descriptor for the device endpoint on which to
|
|
* perform the transfer.
|
|
* buffer - A buffer containing the data to be sent (OUT endpoint) or received
|
|
* (IN endpoint). buffer must have been allocated using DRVR_ALLOC
|
|
* buflen - The length of the data to be sent or received.
|
|
* callback - This function will be called when the transfer completes.
|
|
* arg - The arbitrary parameter that will be passed to the callback function
|
|
* when the transfer completes.
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value is
|
|
* returned indicating the nature of the failure
|
|
*
|
|
* Assumptions:
|
|
* - Called from a single thread so no mutual exclusion is required.
|
|
* - Never called from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
static int stm32_asynch(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep,
|
|
FAR uint8_t *buffer, size_t buflen,
|
|
usbhost_asynch_t callback, FAR void *arg)
|
|
{
|
|
FAR struct stm32_usbhost_s *priv = (FAR struct stm32_usbhost_s *)drvr;
|
|
unsigned int chidx = (unsigned int)ep;
|
|
int ret;
|
|
|
|
uinfo("chidx: %d buflen: %d\n", (unsigned int)ep, buflen);
|
|
|
|
DEBUGASSERT(priv && buffer && chidx < STM32_MAX_TX_FIFOS && buflen > 0);
|
|
|
|
/* We must have exclusive access to the USB host hardware and state structures */
|
|
|
|
stm32_takesem(&priv->exclsem);
|
|
|
|
/* Handle IN and OUT transfer slightly differently */
|
|
|
|
if (priv->chan[chidx].in)
|
|
{
|
|
ret = stm32_in_asynch(priv, chidx, buffer, buflen, callback, arg);
|
|
}
|
|
else
|
|
{
|
|
ret = stm32_out_asynch(priv, chidx, buffer, buflen, callback, arg);
|
|
}
|
|
|
|
stm32_givesem(&priv->exclsem);
|
|
return ret;
|
|
}
|
|
#endif /* CONFIG_USBHOST_ASYNCH */
|
|
|
|
/************************************************************************************
|
|
* Name: stm32_cancel
|
|
*
|
|
* Description:
|
|
* Cancel a pending transfer on an endpoint. Cancelled synchronous or
|
|
* asynchronous transfer will complete normally with the error -ESHUTDOWN.
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the call to
|
|
* the class create() method.
|
|
* ep - The IN or OUT endpoint descriptor for the device endpoint on which an
|
|
* asynchronous transfer should be transferred.
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value is
|
|
* returned indicating the nature of the failure.
|
|
*
|
|
************************************************************************************/
|
|
|
|
static int stm32_cancel(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep)
|
|
{
|
|
FAR struct stm32_usbhost_s *priv = (FAR struct stm32_usbhost_s *)drvr;
|
|
FAR struct stm32_chan_s *chan;
|
|
unsigned int chidx = (unsigned int)ep;
|
|
irqstate_t flags;
|
|
|
|
uinfo("chidx: %u: %d\n", chidx);
|
|
|
|
DEBUGASSERT(priv && chidx < STM32_MAX_TX_FIFOS);
|
|
chan = &priv->chan[chidx];
|
|
|
|
/* We need to disable interrupts to avoid race conditions with the asynchronous
|
|
* completion of the transfer being cancelled.
|
|
*/
|
|
|
|
flags = enter_critical_section();
|
|
|
|
/* Halt the channel */
|
|
|
|
stm32_chan_halt(priv, chidx, CHREASON_CANCELLED);
|
|
chan->result = -ESHUTDOWN;
|
|
|
|
/* Is there a thread waiting for this transfer to complete? */
|
|
|
|
if (chan->waiter)
|
|
{
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
/* Yes.. there should not also be a callback scheduled */
|
|
|
|
DEBUGASSERT(chan->callback == NULL);
|
|
#endif
|
|
|
|
/* Wake'em up! */
|
|
|
|
stm32_givesem(&chan->waitsem);
|
|
chan->waiter = false;
|
|
}
|
|
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
/* No.. is an asynchronous callback expected when the transfer
|
|
* completes?
|
|
*/
|
|
|
|
else if (chan->callback)
|
|
{
|
|
usbhost_asynch_t callback;
|
|
FAR void *arg;
|
|
|
|
/* Extract the callback information */
|
|
|
|
callback = chan->callback;
|
|
arg = chan->arg;
|
|
|
|
chan->callback = NULL;
|
|
chan->arg = NULL;
|
|
chan->xfrd = 0;
|
|
|
|
/* Then perform the callback */
|
|
|
|
callback(arg, -ESHUTDOWN);
|
|
}
|
|
#endif
|
|
|
|
leave_critical_section(flags);
|
|
return OK;
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: stm32_connect
|
|
*
|
|
* Description:
|
|
* New connections may be detected by an attached hub. This method is the
|
|
* mechanism that is used by the hub class to introduce a new connection
|
|
* and port description to the system.
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the call to
|
|
* the class create() method.
|
|
* hport - The descriptor of the hub port that detected the connection
|
|
* related event
|
|
* connected - True: device connected; false: device disconnected
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value is
|
|
* returned indicating the nature of the failure.
|
|
*
|
|
************************************************************************************/
|
|
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
static int stm32_connect(FAR struct usbhost_driver_s *drvr,
|
|
FAR struct usbhost_hubport_s *hport,
|
|
bool connected)
|
|
{
|
|
FAR struct stm32_usbhost_s *priv = (FAR struct stm32_usbhost_s *)drvr;
|
|
irqstate_t flags;
|
|
|
|
DEBUGASSERT(priv != NULL && hport != NULL);
|
|
|
|
/* Set the connected/disconnected flag */
|
|
|
|
hport->connected = connected;
|
|
uinfo("Hub port %d connected: %s\n", hport->port, connected ? "YES" : "NO");
|
|
|
|
/* Report the connection event */
|
|
|
|
flags = enter_critical_section();
|
|
priv->hport = hport;
|
|
if (priv->pscwait)
|
|
{
|
|
priv->pscwait = false;
|
|
stm32_givesem(&priv->pscsem);
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_disconnect
|
|
*
|
|
* Description:
|
|
* Called by the class when an error occurs and driver has been disconnected.
|
|
* The USB host driver should discard the handle to the class instance (it is
|
|
* stale) and not attempt any further interaction with the class driver instance
|
|
* (until a new instance is received from the create() method). The driver
|
|
* should not called the class' disconnected() method.
|
|
*
|
|
* Input Parameters:
|
|
* drvr - The USB host driver instance obtained as a parameter from the call to
|
|
* the class create() method.
|
|
* hport - The port from which the device is being disconnected. Might be a port
|
|
* on a hub.
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* - Only a single class bound to a single device is supported.
|
|
* - Never called from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_disconnect(FAR struct usbhost_driver_s *drvr,
|
|
FAR struct usbhost_hubport_s *hport)
|
|
{
|
|
DEBUGASSERT(hport != NULL);
|
|
hport->devclass = NULL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Initialization
|
|
****************************************************************************/
|
|
/****************************************************************************
|
|
* Name: stm32_portreset
|
|
*
|
|
* Description:
|
|
* Reset the USB host port.
|
|
*
|
|
* NOTE: "Before starting to drive a USB reset, the application waits for the
|
|
* OTG interrupt triggered by the debounce done bit (DBCDNE bit in
|
|
* OTG_FS_GOTGINT), which indicates that the bus is stable again after the
|
|
* electrical debounce caused by the attachment of a pull-up resistor on DP
|
|
* (FS) or DM (LS).
|
|
*
|
|
* Input Parameters:
|
|
* priv -- USB host driver private data structure.
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_portreset(FAR struct stm32_usbhost_s *priv)
|
|
{
|
|
uint32_t regval;
|
|
|
|
regval = stm32_getreg(STM32_OTG_HPRT);
|
|
regval &= ~(OTG_HPRT_PENA | OTG_HPRT_PCDET | OTG_HPRT_PENCHNG |
|
|
OTG_HPRT_POCCHNG);
|
|
regval |= OTG_HPRT_PRST;
|
|
stm32_putreg(STM32_OTG_HPRT, regval);
|
|
|
|
up_mdelay(20);
|
|
|
|
regval &= ~OTG_HPRT_PRST;
|
|
stm32_putreg(STM32_OTG_HPRT, regval);
|
|
|
|
up_mdelay(20);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_flush_txfifos
|
|
*
|
|
* Description:
|
|
* Flush the selected Tx FIFO.
|
|
*
|
|
* Input Parameters:
|
|
* txfnum -- USB host driver private data structure.
|
|
*
|
|
* Returned Value:
|
|
* None.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_flush_txfifos(uint32_t txfnum)
|
|
{
|
|
uint32_t regval;
|
|
uint32_t timeout;
|
|
|
|
/* Initiate the TX FIFO flush operation */
|
|
|
|
regval = OTG_GRSTCTL_TXFFLSH | txfnum;
|
|
stm32_putreg(STM32_OTG_GRSTCTL, regval);
|
|
|
|
/* Wait for the FLUSH to complete */
|
|
|
|
for (timeout = 0; timeout < STM32_FLUSH_DELAY; timeout++)
|
|
{
|
|
regval = stm32_getreg(STM32_OTG_GRSTCTL);
|
|
if ((regval & OTG_GRSTCTL_TXFFLSH) == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Wait for 3 PHY Clocks */
|
|
|
|
up_udelay(3);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_flush_rxfifo
|
|
*
|
|
* Description:
|
|
* Flush the Rx FIFO.
|
|
*
|
|
* Input Parameters:
|
|
* priv -- USB host driver private data structure.
|
|
*
|
|
* Returned Value:
|
|
* None.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_flush_rxfifo(void)
|
|
{
|
|
uint32_t regval;
|
|
uint32_t timeout;
|
|
|
|
/* Initiate the RX FIFO flush operation */
|
|
|
|
stm32_putreg(STM32_OTG_GRSTCTL, OTG_GRSTCTL_RXFFLSH);
|
|
|
|
/* Wait for the FLUSH to complete */
|
|
|
|
for (timeout = 0; timeout < STM32_FLUSH_DELAY; timeout++)
|
|
{
|
|
regval = stm32_getreg(STM32_OTG_GRSTCTL);
|
|
if ((regval & OTG_GRSTCTL_RXFFLSH) == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Wait for 3 PHY Clocks */
|
|
|
|
up_udelay(3);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_vbusdrive
|
|
*
|
|
* Description:
|
|
* Drive the Vbus +5V.
|
|
*
|
|
* Input Parameters:
|
|
* priv - USB host driver private data structure.
|
|
* state - True: Drive, False: Don't drive
|
|
*
|
|
* Returned Value:
|
|
* None.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_vbusdrive(FAR struct stm32_usbhost_s *priv, bool state)
|
|
{
|
|
uint32_t regval;
|
|
|
|
/* Enable/disable the external charge pump */
|
|
|
|
stm32_usbhost_vbusdrive(0, state);
|
|
|
|
/* Turn on the Host port power. */
|
|
|
|
regval = stm32_getreg(STM32_OTG_HPRT);
|
|
regval &= ~(OTG_HPRT_PENA | OTG_HPRT_PCDET | OTG_HPRT_PENCHNG |
|
|
OTG_HPRT_POCCHNG);
|
|
|
|
if (((regval & OTG_HPRT_PPWR) == 0) && state)
|
|
{
|
|
regval |= OTG_HPRT_PPWR;
|
|
stm32_putreg(STM32_OTG_HPRT, regval);
|
|
}
|
|
|
|
if (((regval & OTG_HPRT_PPWR) != 0) && !state)
|
|
{
|
|
regval &= ~OTG_HPRT_PPWR;
|
|
stm32_putreg(STM32_OTG_HPRT, regval);
|
|
}
|
|
|
|
up_mdelay(200);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_host_initialize
|
|
*
|
|
* Description:
|
|
* Initialize/re-initialize hardware for host mode operation. At present,
|
|
* this function is called only from stm32_hw_initialize(). But if OTG mode
|
|
* were supported, this function would also be called to swtich between
|
|
* host and device modes on a connector ID change interrupt.
|
|
*
|
|
* Input Parameters:
|
|
* priv -- USB host driver private data structure.
|
|
*
|
|
* Returned Value:
|
|
* None.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void stm32_host_initialize(FAR struct stm32_usbhost_s *priv)
|
|
{
|
|
uint32_t regval;
|
|
uint32_t offset;
|
|
int i;
|
|
|
|
/* Restart the PHY Clock */
|
|
|
|
stm32_putreg(STM32_OTG_PCGCCTL, 0);
|
|
|
|
/* Initialize Host Configuration (HCFG) register */
|
|
|
|
regval = stm32_getreg(STM32_OTG_HCFG);
|
|
regval &= ~OTG_HCFG_FSLSPCS_MASK;
|
|
regval |= OTG_HCFG_FSLSPCS_FS48MHz;
|
|
stm32_putreg(STM32_OTG_HCFG, regval);
|
|
|
|
/* Reset the host port */
|
|
|
|
stm32_portreset(priv);
|
|
|
|
/* Clear the FS-/LS-only support bit in the HCFG register */
|
|
|
|
regval = stm32_getreg(STM32_OTG_HCFG);
|
|
regval &= ~OTG_HCFG_FSLSS;
|
|
stm32_putreg(STM32_OTG_HCFG, regval);
|
|
|
|
/* Carve up FIFO memory for the Rx FIFO and the periodic and non-periodic
|
|
* Tx FIFOs
|
|
*/
|
|
|
|
/* Configure Rx FIFO size (GRXFSIZ) */
|
|
|
|
stm32_putreg(STM32_OTG_GRXFSIZ, CONFIG_STM32H7_OTG_RXFIFO_SIZE);
|
|
offset = CONFIG_STM32H7_OTG_RXFIFO_SIZE;
|
|
|
|
/* Setup the host non-periodic Tx FIFO size (HNPTXFSIZ) */
|
|
|
|
regval = (offset |
|
|
(CONFIG_STM32H7_OTG_NPTXFIFO_SIZE << OTG_HNPTXFSIZ_NPTXFD_SHIFT));
|
|
stm32_putreg(STM32_OTG_HNPTXFSIZ, regval);
|
|
offset += CONFIG_STM32H7_OTG_NPTXFIFO_SIZE;
|
|
|
|
/* Set up the host periodic Tx fifo size register (HPTXFSIZ) */
|
|
|
|
regval = (offset |
|
|
(CONFIG_STM32H7_OTG_PTXFIFO_SIZE << OTG_HPTXFSIZ_PTXFD_SHIFT));
|
|
stm32_putreg(STM32_OTG_HPTXFSIZ, regval);
|
|
|
|
/* If OTG were supported, we sould need to clear HNP enable bit in the
|
|
* USB_OTG control register about here.
|
|
*/
|
|
|
|
/* Flush all FIFOs */
|
|
|
|
stm32_flush_txfifos(OTG_GRSTCTL_TXFNUM_HALL);
|
|
stm32_flush_rxfifo();
|
|
|
|
/* Clear all pending HC Interrupts */
|
|
|
|
for (i = 0; i < STM32_NHOST_CHANNELS; i++)
|
|
{
|
|
stm32_putreg(STM32_OTG_HCINT(i), 0xffffffff);
|
|
stm32_putreg(STM32_OTG_HCINTMSK(i), 0);
|
|
}
|
|
|
|
/* Driver Vbus +5V (the smoke test). Should be done elsewhere in OTG
|
|
* mode.
|
|
*/
|
|
|
|
stm32_vbusdrive(priv, true);
|
|
|
|
/* Enable host interrupts */
|
|
|
|
stm32_hostinit_enable();
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_sw_initialize
|
|
*
|
|
* Description:
|
|
* One-time setup of the host driver state structure.
|
|
*
|
|
* Input Parameters:
|
|
* priv -- USB host driver private data structure.
|
|
*
|
|
* Returned Value:
|
|
* None.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void stm32_sw_initialize(FAR struct stm32_usbhost_s *priv)
|
|
{
|
|
FAR struct usbhost_driver_s *drvr;
|
|
FAR struct usbhost_hubport_s *hport;
|
|
int i;
|
|
|
|
/* Initialize the device operations */
|
|
|
|
drvr = &priv->drvr;
|
|
drvr->ep0configure = stm32_ep0configure;
|
|
drvr->epalloc = stm32_epalloc;
|
|
drvr->epfree = stm32_epfree;
|
|
drvr->alloc = stm32_alloc;
|
|
drvr->free = stm32_free;
|
|
drvr->ioalloc = stm32_ioalloc;
|
|
drvr->iofree = stm32_iofree;
|
|
drvr->ctrlin = stm32_ctrlin;
|
|
drvr->ctrlout = stm32_ctrlout;
|
|
drvr->transfer = stm32_transfer;
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
drvr->asynch = stm32_asynch;
|
|
#endif
|
|
drvr->cancel = stm32_cancel;
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
drvr->connect = stm32_connect;
|
|
#endif
|
|
drvr->disconnect = stm32_disconnect;
|
|
|
|
/* Initialize the public port representation */
|
|
|
|
hport = &priv->rhport.hport;
|
|
hport->drvr = drvr;
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
hport->parent = NULL;
|
|
#endif
|
|
hport->ep0 = (usbhost_ep_t)&priv->ep0;
|
|
hport->speed = USB_SPEED_FULL;
|
|
|
|
/* Initialize function address generation logic */
|
|
|
|
usbhost_devaddr_initialize(&priv->rhport);
|
|
|
|
/* Initialize semaphores */
|
|
|
|
nxsem_init(&priv->pscsem, 0, 0);
|
|
nxsem_init(&priv->exclsem, 0, 1);
|
|
|
|
/* The pscsem semaphore is used for signaling and, hence, should not have
|
|
* priority inheritance enabled.
|
|
*/
|
|
|
|
nxsem_setprotocol(&priv->pscsem, SEM_PRIO_NONE);
|
|
|
|
/* Initialize the driver state data */
|
|
|
|
priv->smstate = SMSTATE_DETACHED;
|
|
priv->connected = false;
|
|
priv->change = false;
|
|
|
|
/* Put all of the channels back in their initial, allocated state */
|
|
|
|
memset(priv->chan, 0, STM32_MAX_TX_FIFOS * sizeof(struct stm32_chan_s));
|
|
|
|
/* Initialize each channel */
|
|
|
|
for (i = 0; i < STM32_MAX_TX_FIFOS; i++)
|
|
{
|
|
FAR struct stm32_chan_s *chan = &priv->chan[i];
|
|
|
|
chan->chidx = i;
|
|
|
|
/* The waitsem semaphore is used for signaling and, hence, should not
|
|
* have priority inheritance enabled.
|
|
*/
|
|
|
|
nxsem_init(&chan->waitsem, 0, 0);
|
|
nxsem_setprotocol(&chan->waitsem, SEM_PRIO_NONE);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_hw_initialize
|
|
*
|
|
* Description:
|
|
* One-time setup of the host controller hardware for normal operations.
|
|
*
|
|
* Input Parameters:
|
|
* priv -- USB host driver private data structure.
|
|
*
|
|
* Returned Value:
|
|
* Zero on success; a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int stm32_hw_initialize(FAR struct stm32_usbhost_s *priv)
|
|
{
|
|
uint32_t regval;
|
|
unsigned long timeout;
|
|
|
|
/* Set the PHYSEL bit in the GUSBCFG register to select the OTG FS serial
|
|
* transceiver: "This bit is always 1 with write-only access"
|
|
*/
|
|
|
|
regval = stm32_getreg(STM32_OTG_GUSBCFG);
|
|
regval |= OTG_GUSBCFG_PHYSEL;
|
|
stm32_putreg(STM32_OTG_GUSBCFG, regval);
|
|
|
|
/* Reset after a PHY select and set Host mode. First, wait for AHB master
|
|
* IDLE state.
|
|
*/
|
|
|
|
for (timeout = 0; timeout < STM32_READY_DELAY; timeout++)
|
|
{
|
|
up_udelay(3);
|
|
regval = stm32_getreg(STM32_OTG_GRSTCTL);
|
|
if ((regval & OTG_GRSTCTL_AHBIDL) != 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Then perform the core soft reset. */
|
|
|
|
stm32_putreg(STM32_OTG_GRSTCTL, OTG_GRSTCTL_CSRST);
|
|
for (timeout = 0; timeout < STM32_READY_DELAY; timeout++)
|
|
{
|
|
regval = stm32_getreg(STM32_OTG_GRSTCTL);
|
|
if ((regval & OTG_GRSTCTL_CSRST) == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Wait for 3 PHY Clocks */
|
|
|
|
up_udelay(3);
|
|
|
|
/* Deactivate the power down */
|
|
|
|
regval = (OTG_GCCFG_PWRDWN | OTG_GCCFG_VBDEN);
|
|
stm32_putreg(STM32_OTG_GCCFG, regval);
|
|
up_mdelay(20);
|
|
|
|
/* Initialize OTG features: In order to support OTP, the HNPCAP and SRPCAP
|
|
* bits would need to be set in the GUSBCFG register about here.
|
|
*/
|
|
|
|
/* Force Host Mode */
|
|
|
|
regval = stm32_getreg(STM32_OTG_GUSBCFG);
|
|
regval &= ~OTG_GUSBCFG_FDMOD;
|
|
regval |= OTG_GUSBCFG_FHMOD;
|
|
stm32_putreg(STM32_OTG_GUSBCFG, regval);
|
|
up_mdelay(50);
|
|
|
|
/* Initialize host mode and return success */
|
|
|
|
stm32_host_initialize(priv);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: stm32_otgfshost_initialize
|
|
*
|
|
* Description:
|
|
* Initialize USB host device controller hardware.
|
|
*
|
|
* Input Parameters:
|
|
* controller -- If the device supports more than USB host controller, then
|
|
* this identifies which controller is being initialized. Normally, this
|
|
* is just zero.
|
|
*
|
|
* Returned Value:
|
|
* And instance of the USB host interface. The controlling task should
|
|
* use this interface to (1) call the wait() method to wait for a device
|
|
* to be connected, and (2) call the enumerate() method to bind the device
|
|
* to a class driver.
|
|
*
|
|
* Assumptions:
|
|
* - This function should called in the initialization sequence in order
|
|
* to initialize the USB device functionality.
|
|
* - Class drivers should be initialized prior to calling this function.
|
|
* Otherwise, there is a race condition if the device is already connected.
|
|
*
|
|
****************************************************************************/
|
|
|
|
FAR struct usbhost_connection_s *stm32_otgfshost_initialize(int controller)
|
|
{
|
|
/* At present, there is only support for a single OTG FS host. Hence it is
|
|
* pre-allocated as g_usbhost. However, in most code, the private data
|
|
* structure will be referenced using the 'priv' pointer (rather than the
|
|
* global data) in order to simplify any future support for multiple devices.
|
|
*/
|
|
|
|
FAR struct stm32_usbhost_s *priv = &g_usbhost;
|
|
uint32_t regval;
|
|
|
|
/* Sanity checks */
|
|
|
|
DEBUGASSERT(controller == 0);
|
|
|
|
/* Make sure that interrupts from the OTG FS core are disabled */
|
|
|
|
stm32_gint_disable();
|
|
|
|
/* Reset the state of the host driver */
|
|
|
|
stm32_sw_initialize(priv);
|
|
|
|
/* Configure USB voltage regulator */
|
|
|
|
regval = stm32_getreg(STM32_PWR_CR3);
|
|
|
|
/* Enable USB regulator if configured */
|
|
|
|
#ifdef CONFIG_STM32H7_OTG_USBREGEN
|
|
regval |= STM32_PWR_CR3_USBREGEN;
|
|
#else
|
|
regval &= ~STM32_PWR_CR3_USBREGEN;
|
|
#endif
|
|
|
|
/* Enable VDD33USB supply level detector */
|
|
|
|
regval |= STM32_PWR_CR3_USB33DEN;
|
|
stm32_putreg(regval, STM32_PWR_CR3);
|
|
|
|
while ((stm32_getreg(STM32_PWR_CR3) & STM32_PWR_CR3_USB33RDY) == 0)
|
|
{
|
|
}
|
|
|
|
/* Alternate function pin configuration. Here we assume that:
|
|
*
|
|
* 1. GPIOA, SYSCFG, and OTG FS peripheral clocking have already been
|
|
* enabled as part of the boot sequence.
|
|
* 2. Board-specific logic has already enabled other board specific GPIOs
|
|
* for things like soft pull-up, VBUS sensing, power controls, and over-
|
|
* current detection.
|
|
*/
|
|
|
|
/* Configure OTG FS alternate function pins for DM, DP, ID, and SOF.
|
|
*
|
|
* PIN* SIGNAL DIRECTION
|
|
* ---- ----------- ----------
|
|
* PA8 OTG_FS_SOF SOF clock output
|
|
* PA9 OTG_FS_VBUS VBUS input for device, Driven by external regulator by
|
|
* host (not an alternate function)
|
|
* PA10 OTG_FS_ID OTG ID pin (only needed in Dual mode)
|
|
* PA11 OTG_FS_DM D- I/O
|
|
* PA12 OTG_FS_DP D+ I/O
|
|
*
|
|
* *Pins may vary from device-to-device.
|
|
*/
|
|
|
|
stm32_configgpio(GPIO_OTG_DM);
|
|
stm32_configgpio(GPIO_OTG_DP);
|
|
stm32_configgpio(GPIO_OTG_ID); /* Only needed for OTG */
|
|
|
|
/* SOF output pin configuration is configurable */
|
|
|
|
#ifdef CONFIG_STM32H7_OTG_SOFOUTPUT
|
|
stm32_configgpio(GPIO_OTG_SOF);
|
|
#endif
|
|
|
|
/* Initialize the USB OTG FS core */
|
|
|
|
stm32_hw_initialize(priv);
|
|
|
|
/* Attach USB host controller interrupt handler */
|
|
|
|
if (irq_attach(STM32_IRQ_OTGFS, stm32_gint_isr, NULL) != 0)
|
|
{
|
|
usbhost_trace1(OTG_TRACE1_IRQATTACH, 0);
|
|
return NULL;
|
|
}
|
|
|
|
/* Enable USB OTG FS global interrupts */
|
|
|
|
stm32_gint_enable();
|
|
|
|
/* Enable interrupts at the interrupt controller */
|
|
|
|
up_enable_irq(STM32_IRQ_OTGFS);
|
|
return &g_usbconn;
|
|
}
|
|
|
|
#if defined(CONFIG_USBHOST_TRACE) || defined(CONFIG_DEBUG_USB)
|
|
/****************************************************************************
|
|
* Name: usbhost_trformat1 and usbhost_trformat2
|
|
*
|
|
* Description:
|
|
* This interface must be provided by platform specific logic that knows
|
|
* the HCDs encoding of USB trace data.
|
|
*
|
|
* Given an 9-bit index, return a format string suitable for use with, say,
|
|
* printf. The returned format is expected to handle two unsigned integer
|
|
* values.
|
|
*
|
|
****************************************************************************/
|
|
|
|
FAR const char *usbhost_trformat1(uint16_t id)
|
|
{
|
|
/* TODO: */
|
|
|
|
return NULL;
|
|
}
|
|
|
|
FAR const char *usbhost_trformat2(uint16_t id)
|
|
{
|
|
/* TODO: */
|
|
|
|
return NULL;
|
|
}
|
|
#endif /* CONFIG_USBHOST_TRACE && CONFIG_DEBUG_USB */
|
|
|
|
#endif /* CONFIG_USBHOST && CONFIG_STM32H7_OTGFS */
|