e6b204f438
Signed-off-by: Gustavo Henrique Nihei <gustavo.nihei@espressif.com>
4968 lines
149 KiB
C
4968 lines
149 KiB
C
/****************************************************************************
|
|
* drivers/usbhost/usbhost_max3421e.c
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/* References:
|
|
* "MAX3421E USB Peripheral/Host Controller with SPI Interface",
|
|
* 19-3953, Rev 4, Maxim Integrated, July 2013 (Datasheet).
|
|
* "MAX3421E Programming Guide", Maxim Integrated, December 2006
|
|
* (Application Note).
|
|
*/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/types.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <assert.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/mutex.h>
|
|
#include <nuttx/semaphore.h>
|
|
#include <nuttx/wqueue.h>
|
|
#include <nuttx/spi/spi.h>
|
|
#include <nuttx/usb/usb.h>
|
|
#include <nuttx/usb/usbhost.h>
|
|
#include <nuttx/usb/usbhost_devaddr.h>
|
|
#include <nuttx/usb/max3421e.h>
|
|
#include <nuttx/usb/usbhost_trace.h>
|
|
|
|
#include <nuttx/irq.h>
|
|
|
|
#ifdef CONFIG_USBHOST_MAX3421E
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
/* Configuration ************************************************************/
|
|
|
|
/* MAX3421E USB Host Driver Support
|
|
*
|
|
* Pre-requisites
|
|
*
|
|
* CONFIG_USBHOST - Enable general USB host support
|
|
* CONFIG_USBHOST_MAX3421E - Enable the MAX3421E USB host support
|
|
* CONFIG_SCHED_LPWORK - Low priority work queue support is required.
|
|
*
|
|
* Options:
|
|
*
|
|
* CONFIG_MAX3421E_DESCSIZE - Maximum size of a descriptor. Default: 128
|
|
* CONFIG_MAX3421E_USBHOST_REGDEBUG - Enable very low-level register access
|
|
* debug. Depends on CONFIG_DEBUG_USB_INFO.
|
|
* CONFIG_MAX3421E_USBHOST_PKTDUMP - Dump all incoming and outgoing USB
|
|
* packets. Depends on CONFIG_DEBUG_USB_INFO.
|
|
*/
|
|
|
|
/* Maximum size of a descriptor */
|
|
|
|
#ifndef CONFIG_MAX3421E_DESCSIZE
|
|
# define CONFIG_MAX3421E_DESCSIZE 128
|
|
#endif
|
|
|
|
/* Low-priority work queue support is required. The high priority work
|
|
* queue is not used because this driver requires SPI access and may
|
|
* block or wait for a variety of reasons.
|
|
*/
|
|
|
|
#ifndef CONFIG_SCHED_LPWORK
|
|
# warning Low priority work thread support is necessary (CONFIG_SCHED_LPWORK)
|
|
#endif
|
|
|
|
/* Register/packet debug depends on CONFIG_DEBUG_FEATURES */
|
|
|
|
#ifndef CONFIG_DEBUG_USB_INFO
|
|
# undef CONFIG_MAX3421E_USBHOST_REGDEBUG
|
|
# undef CONFIG_MAX3421E_USBHOST_PKTDUMP
|
|
#endif
|
|
|
|
/* Delays *******************************************************************/
|
|
|
|
#define MAX3421E_READY_DELAY 200000 /* In loop counts */
|
|
#define MAX3421E_FLUSH_DELAY 200000 /* In loop counts */
|
|
#define MAX3421E_SETUP_DELAY SEC2TICK(5) /* 5 seconds in system ticks */
|
|
#define MAX3421E_DATANAK_DELAY SEC2TICK(5) /* 5 seconds in system ticks */
|
|
#define MAX3421E_RETRY_COUNT 5 /* Number of tries before giving up */
|
|
|
|
#define NO_HOLDER (INVALID_PROCESS_ID)
|
|
|
|
/* Debug ********************************************************************/
|
|
|
|
#define TR_FMT1 false
|
|
#define TR_FMT2 true
|
|
|
|
#define TRENTRY(id,fmt1,string) {string}
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
/* The following enumeration represents the various states of the USB host
|
|
* state machine (for debug purposes only)
|
|
*/
|
|
|
|
enum max3421e_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 */
|
|
};
|
|
|
|
/* When a transfer completes and the HXFRDN interrupt is received, this
|
|
* informs the HXFRDN interrupt handle of the type of transfer that just
|
|
* completed.
|
|
*/
|
|
|
|
enum mx3421e_hxfrdn_e
|
|
{
|
|
HXFRDN_SETUP = 0, /* A setup transfer just completed */
|
|
HXFRDN_SNDZLP, /* A zero length IN transfer just completed */
|
|
HXFRDN_SNDFIFO, /* A normal IN transfer using SNDFIFO just completed */
|
|
HXFRDN_RCVFIFO, /* A normal OUT transfer using RCVFIFO just completed */
|
|
};
|
|
|
|
/* 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 max3421e_ubhost_s to achieve
|
|
* some memory savings.
|
|
*/
|
|
|
|
struct max3421e_chan_s
|
|
{
|
|
bool inuse; /* True: This channel is "in use" */
|
|
bool in; /* True: IN endpoint */
|
|
uint8_t chidx; /* Channel index (0-3) */
|
|
uint8_t epno; /* Device endpoint number (0-127) */
|
|
uint8_t eptype; /* See MAX3421E_EPTYPE_* definitions */
|
|
uint8_t funcaddr; /* Device function address */
|
|
uint8_t speed; /* Device speed */
|
|
uint8_t interval; /* Interrupt/isochronous EP polling interval */
|
|
uint8_t maxpacket; /* Max packet size (8 or 64) */
|
|
uint8_t toggles; /* Saved data toggles from the HCTL register */
|
|
};
|
|
|
|
/* This structure retains the state of the USB host controller */
|
|
|
|
struct max3421e_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_driver_s to struct max3421e_usbhost_s.
|
|
*/
|
|
|
|
struct usbhost_driver_s drvr;
|
|
|
|
/* This is the interface to the max3421e lower-half driver */
|
|
|
|
FAR const struct max3421e_lowerhalf_s *lower;
|
|
|
|
/* This is the hub port description understood by class drivers */
|
|
|
|
struct usbhost_roothubport_s rhport;
|
|
|
|
/* Overall driver status */
|
|
|
|
bool connected; /* Connected to device */
|
|
bool change; /* Connection change */
|
|
bool pscwait; /* True: Thread is waiting for a port event */
|
|
#ifdef CONFIG_DEBUG_ASSERTIONS
|
|
bool locked; /* The SPI bus is locked */
|
|
#endif
|
|
uint8_t smstate; /* The state of the USB host state machine */
|
|
uint8_t irqset; /* Set of enabled interrupts */
|
|
uint8_t xfrtype; /* See enum mx3421e_hxfrdn_e */
|
|
uint8_t inflight; /* Number of Tx bytes "in-flight" (<= 128) */
|
|
uint8_t result; /* The result of the transfer */
|
|
uint16_t buflen; /* Buffer length (at start of transfer) */
|
|
uint16_t xfrd; /* Bytes transferred (at end of transfer) */
|
|
rmutex_t lock; /* Support mutually exclusive access */
|
|
sem_t pscsem; /* Semaphore to wait for a port event */
|
|
sem_t waitsem; /* Channel wait semaphore */
|
|
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
|
|
struct work_s irqwork; /* Used to process interrupts */
|
|
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
/* Used to pass external hub port events */
|
|
|
|
FAR struct usbhost_hubport_s *hport;
|
|
#endif
|
|
|
|
/* The channel waiting for the next event (there will only be one in
|
|
* this design)
|
|
*/
|
|
|
|
FAR struct max3421e_chan_s *waiter;
|
|
|
|
/* The state of each host channel, one for each device endpoint. */
|
|
|
|
struct max3421e_chan_s chan[MAX3421E_NHOST_CHANNELS];
|
|
};
|
|
|
|
/* This is the MAX3421E connection structure */
|
|
|
|
struct max3421e_connection_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_connection_s to struct usbhost_connection_s.
|
|
*/
|
|
|
|
struct usbhost_connection_s conn;
|
|
|
|
/* Pointer to the associated state structure */
|
|
|
|
FAR struct max3421e_usbhost_s *priv;
|
|
};
|
|
|
|
/* Supports allocation of both structures simultaneously */
|
|
|
|
struct usbhost_alloc_s
|
|
{
|
|
struct max3421e_usbhost_s priv;
|
|
struct max3421e_connection_s conn;
|
|
};
|
|
|
|
/* Tracing support */
|
|
|
|
#ifdef HAVE_USBHOST_TRACE
|
|
struct max3421e_usbhost_trace_s
|
|
{
|
|
#if 0
|
|
uint16_t id;
|
|
bool fmt2;
|
|
#endif
|
|
FAR const char *string;
|
|
};
|
|
|
|
enum usbhost_trace1codes_e
|
|
{
|
|
__TRACE1_BASEVALUE = 0, /* This will force the first value to be 1 */
|
|
|
|
MAX3421E_TRACE1_ALLOC_FAIL,
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
MAX3421E_TRACE1_ASYNCHSETUP_FAIL1,
|
|
MAX3421E_TRACE1_ASYNCHSETUP_FAIL2,
|
|
#endif
|
|
MAX3421E_TRACE1_BAD_JKSTATE,
|
|
MAX3421E_TRACE1_BADREVISION,
|
|
MAX3421E_TRACE1_CHANALLOC_FAIL,
|
|
MAX3421E_TRACE1_CHANWAIT_FAIL,
|
|
MAX3421E_TRACE1_DEVDISCONN1,
|
|
MAX3421E_TRACE1_DEVDISCONN2,
|
|
MAX3421E_TRACE1_DEVDISCONN3,
|
|
MAX3421E_TRACE1_DEVDISCONN4,
|
|
MAX3421E_TRACE1_DEVDISCONN5,
|
|
MAX3421E_TRACE1_DEVDISCONN6,
|
|
MAX3421E_TRACE1_DEVDISCONN7,
|
|
MAX3421E_TRACE1_DEVDISCONN8,
|
|
MAX3421E_TRACE1_ENUMERATE_FAIL,
|
|
MAX3421E_TRACE1_INSETUP_FAIL1,
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
MAX3421E_TRACE1_INSETUP_FAIL2,
|
|
MAX3421E_TRACE1_INSETUP_FAIL3,
|
|
#endif
|
|
MAX3421E_TRACE1_IRQATTACH_FAIL,
|
|
MAX3421E_TRACE1_OUTSETUP_FAIL1,
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
MAX3421E_TRACE1_OUTSETUP_FAIL2,
|
|
MAX3421E_TRACE1_OUTSETUP_FAIL3,
|
|
#endif
|
|
MAX3421E_TRACE1_RECVDATA_FAIL,
|
|
MAX3421E_TRACE1_RECVSTATUS_FAIL,
|
|
MAX3421E_TRACE1_SENDDATA_FAIL,
|
|
MAX3421E_TRACE1_SENDSETUP_FAIL1,
|
|
MAX3421E_TRACE1_SENDSETUP_FAIL2,
|
|
MAX3421E_TRACE1_SENDSTATUS_FAIL,
|
|
MAX3421E_TRACE1_TRANSFER_FAILED1,
|
|
MAX3421E_TRACE1_TRANSFER_FAILED2,
|
|
MAX3421E_TRACE1_TRANSFER_FAILED3,
|
|
|
|
#ifdef HAVE_USBHOST_TRACE_VERBOSE
|
|
MAX3421E_VTRACE1_CANCEL,
|
|
MAX3421E_VTRACE1_CONNECTED1,
|
|
MAX3421E_VTRACE1_CONNECTED2,
|
|
MAX3421E_VTRACE1_CONNECTED3,
|
|
MAX3421E_VTRACE1_DISCONNECTED1,
|
|
MAX3421E_VTRACE1_DISCONNECTED2,
|
|
MAX3421E_VTRACE1_ENUMERATE,
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
MAX3421E_VTRACE1_HUB_CONNECTED,
|
|
#endif
|
|
MAX3421E_VTRACE1_INITIALIZED,
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
MAX3421E_VTRACE1_TRANSFER_COMPLETE,
|
|
#endif
|
|
|
|
#endif
|
|
|
|
__TRACE1_NSTRINGS, /* Separates the format 1 from the format 2 strings */
|
|
|
|
#ifdef HAVE_USBHOST_TRACE_VERBOSE
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
MAX3421E_VTRACE2_ASYNCH,
|
|
#endif
|
|
MAX3421E_VTRACE2_BULKIN,
|
|
MAX3421E_VTRACE2_BULKOUT,
|
|
MAX3421E_VTRACE2_CHANWAKEUP_IN,
|
|
MAX3421E_VTRACE2_CHANWAKEUP_OUT,
|
|
MAX3421E_VTRACE2_CTRLIN,
|
|
MAX3421E_VTRACE2_CTRLOUT,
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
MAX3421E_VTRACE2_HUB_CONNECTED,
|
|
#endif
|
|
MAX3421E_VTRACE2_INTRIN,
|
|
MAX3421E_VTRACE2_INTROUT,
|
|
MAX3421E_VTRACE2_ISOCIN,
|
|
MAX3421E_VTRACE2_ISOCOUT,
|
|
MAX3421E_VTRACE2_RECVSTATUS,
|
|
MAX3421E_VTRACE2_SENDSTATUS,
|
|
MAX3421E_VTRACE2_STARTTRANSFER1,
|
|
MAX3421E_VTRACE2_STARTTRANSFER2,
|
|
MAX3421E_VTRACE2_TRANSFER,
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
MAX3421E_VTRACE2_XFRCOMPLETE,
|
|
#endif
|
|
#endif
|
|
__TRACE2_NSTRINGS /* Total number of enumeration values */
|
|
};
|
|
|
|
# define TRACE1_FIRST ((int)__TRACE1_BASEVALUE + 1)
|
|
# define TRACE1_INDEX(id) ((int)(id) - TRACE1_FIRST)
|
|
# define TRACE1_NSTRINGS TRACE1_INDEX(__TRACE1_NSTRINGS)
|
|
|
|
# define TRACE2_FIRST ((int)__TRACE1_NSTRINGS + 1)
|
|
# define TRACE2_INDEX(id) ((int)(id) - TRACE2_FIRST)
|
|
# define TRACE2_NSTRINGS TRACE2_INDEX(__TRACE2_NSTRINGS)
|
|
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* SPI/Register operations **************************************************/
|
|
|
|
static void max3421e_lock(FAR struct max3421e_usbhost_s *priv);
|
|
static void max3421e_unlock(FAR struct max3421e_usbhost_s *priv);
|
|
|
|
#ifdef CONFIG_MAX3421E_USBHOST_REGDEBUG
|
|
static void max3421e_printreg(uint8_t addr, uint8_t val, bool iswrite);
|
|
static void max3421e_checkreg(uint8_t addr, uint8_t val, bool iswrite)
|
|
#endif
|
|
|
|
static inline uint8_t max3421e_fmtcmd(FAR struct max3421e_usbhost_s *priv,
|
|
uint8_t addr, uint8_t dir);
|
|
static uint32_t max3421e_getreg(FAR struct max3421e_usbhost_s *priv,
|
|
uint8_t addr);
|
|
static void max3421e_putreg(FAR struct max3421e_usbhost_s *priv,
|
|
uint8_t addr, uint8_t value);
|
|
|
|
static inline void max3421e_modifyreg(FAR struct max3421e_usbhost_s *priv,
|
|
uint8_t addr, uint8_t clrbits, uint8_t setbits);
|
|
|
|
static void max3421e_recvblock(FAR struct max3421e_usbhost_s *priv,
|
|
uint8_t addr, FAR void *buffer, size_t buflen);
|
|
static void max3421e_sndblock(FAR struct max3421e_usbhost_s *priv,
|
|
uint8_t addr, FAR const void *buffer, size_t buflen);
|
|
|
|
#ifdef CONFIG_MAX3421E_USBHOST_PKTDUMP
|
|
# define max3421e_pktdump(m,b,n) lib_dumpbuffer(m,b,n)
|
|
#else
|
|
# define max3421e_pktdump(m,b,n)
|
|
#endif
|
|
|
|
/* Byte stream access helper functions **************************************/
|
|
|
|
static inline uint16_t max3421e_getle16(const uint8_t *val);
|
|
|
|
/* Channel management *******************************************************/
|
|
|
|
static int max3421e_chan_alloc(FAR struct max3421e_usbhost_s *priv);
|
|
static inline void max3421e_chan_free(FAR struct max3421e_usbhost_s *priv,
|
|
int chidx);
|
|
static int max3421e_chan_waitsetup(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan);
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
static int max3421e_chan_asynchsetup(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan, usbhost_asynch_t callback,
|
|
FAR void *arg);
|
|
#endif
|
|
static int max3421e_chan_wait(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan);
|
|
static void max3421e_chan_wakeup(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan, int result);
|
|
|
|
/* Control/data transfer logic **********************************************/
|
|
|
|
static inline void max3421e_save_toggles(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan);
|
|
static inline void max3421e_restore_toggles(
|
|
FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan);
|
|
static int max3421e_transfer_status(FAR struct max3421e_usbhost_s *priv);
|
|
static void max3421e_transfer_terminate(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan, int result);
|
|
|
|
/* OUT transfers */
|
|
|
|
static void max3421e_put_sndfifo(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan);
|
|
static void max3421e_send_continue(FAR struct max3421e_usbhost_s *priv);
|
|
static void max3421e_send_start(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan);
|
|
static ssize_t max3421e_out_transfer(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan, FAR uint8_t *buffer,
|
|
size_t buflen);
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
static void max3421e_out_next(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan);
|
|
static int max3421e_out_asynch(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan, FAR uint8_t *buffer,
|
|
size_t buflen, usbhost_asynch_t callback, FAR void *arg);
|
|
#endif
|
|
|
|
/* Control transfers */
|
|
|
|
static int max3421e_ctrl_sendsetup(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan,
|
|
FAR const struct usb_ctrlreq_s *req);
|
|
static int max3421e_ctrl_senddata(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan,
|
|
FAR uint8_t *buffer, unsigned int buflen);
|
|
static int max3421e_ctrl_sendstatus(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan);
|
|
static int max3421e_ctrl_recvstatus(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan);
|
|
static int max3421e_in_setup(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan);
|
|
static int max3421e_out_setup(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan);
|
|
|
|
/* IN transfers */
|
|
|
|
static uint8_t max3421e_get_rcvfifo(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan);
|
|
static void max3421e_recv_restart(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan);
|
|
static void max3421e_recv_continue(FAR struct max3421e_usbhost_s *priv);
|
|
static void max3421e_recv_start(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan);
|
|
static ssize_t max3421e_in_transfer(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan, FAR uint8_t *buffer,
|
|
size_t buflen);
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
static void max3421e_in_next(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan);
|
|
static int max3421e_in_asynch(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan, FAR uint8_t *buffer,
|
|
size_t buflen, usbhost_asynch_t callback, FAR void *arg);
|
|
#endif
|
|
|
|
/* Interrupt handling *******************************************************/
|
|
|
|
static void max3421e_connect_event(FAR struct max3421e_usbhost_s *priv);
|
|
static void max3421e_disconnect_event(FAR struct max3421e_usbhost_s *priv);
|
|
static int max3421e_connected(FAR struct max3421e_usbhost_s *priv);
|
|
static void max3421e_disconnected(FAR struct max3421e_usbhost_s *priv);
|
|
static void max3421e_irqwork(FAR void *arg);
|
|
static int max3421e_interrupt(int irq, FAR void *context, FAR void *arg);
|
|
|
|
/* Interrupt controls */
|
|
|
|
static inline void max3421e_int_enable(FAR struct max3421e_usbhost_s *priv,
|
|
uint8_t irqset);
|
|
static inline void max3421e_int_disable(FAR struct max3421e_usbhost_s *priv,
|
|
uint8_t irqset);
|
|
static inline uint8_t max3421e_int_status(
|
|
FAR struct max3421e_usbhost_s *priv);
|
|
static inline void max3421e_int_clear(FAR struct max3421e_usbhost_s *priv,
|
|
uint8_t irqset);
|
|
static void max3421e_int_wait(FAR struct max3421e_usbhost_s *priv,
|
|
uint8_t irqset, unsigned int usec);
|
|
|
|
/* USB host controller operations *******************************************/
|
|
|
|
static int max3421e_wait(FAR struct usbhost_connection_s *conn,
|
|
FAR struct usbhost_hubport_s **hport);
|
|
static int max3421e_getspeed(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct usbhost_connection_s *conn,
|
|
FAR struct usbhost_hubport_s *hport);
|
|
static int max3421e_enumerate(FAR struct usbhost_connection_s *conn,
|
|
FAR struct usbhost_hubport_s *hport);
|
|
|
|
static int max3421e_ep0configure(FAR struct usbhost_driver_s *drvr,
|
|
usbhost_ep_t ep0, uint8_t funcaddr, uint8_t speed,
|
|
uint16_t maxpacketsize);
|
|
static int max3421e_epalloc(FAR struct usbhost_driver_s *drvr,
|
|
FAR const struct usbhost_epdesc_s *epdesc,
|
|
FAR usbhost_ep_t *ep);
|
|
static int max3421e_epfree(FAR struct usbhost_driver_s *drvr,
|
|
usbhost_ep_t ep);
|
|
static int max3421e_alloc(FAR struct usbhost_driver_s *drvr,
|
|
FAR uint8_t **buffer, FAR size_t *maxlen);
|
|
static int max3421e_free(FAR struct usbhost_driver_s *drvr,
|
|
FAR uint8_t *buffer);
|
|
static int max3421e_ioalloc(FAR struct usbhost_driver_s *drvr,
|
|
FAR uint8_t **buffer, size_t buflen);
|
|
static int max3421e_iofree(FAR struct usbhost_driver_s *drvr,
|
|
FAR uint8_t *buffer);
|
|
static int max3421e_ctrlin(FAR struct usbhost_driver_s *drvr,
|
|
usbhost_ep_t ep0, FAR const struct usb_ctrlreq_s *req,
|
|
FAR uint8_t *buffer);
|
|
static int max3421e_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 max3421e_transfer(FAR struct usbhost_driver_s *drvr,
|
|
usbhost_ep_t ep, FAR uint8_t *buffer, size_t buflen);
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
static int max3421e_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 max3421e_cancel(FAR struct usbhost_driver_s *drvr,
|
|
usbhost_ep_t ep);
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
static int max3421e_connect(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct usbhost_hubport_s *hport, bool connected);
|
|
#endif
|
|
static void max3421e_disconnect(FAR struct usbhost_driver_s *drvr,
|
|
FAR struct usbhost_hubport_s *hport);
|
|
|
|
/* Initialization ***********************************************************/
|
|
|
|
static void max3421e_busreset(FAR struct max3421e_usbhost_s *priv);
|
|
static int max3421e_startsof(FAR struct max3421e_usbhost_s *priv);
|
|
|
|
static inline int max3421e_sw_initialize(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_connection_s *conn,
|
|
FAR const struct max3421e_lowerhalf_s *lower);
|
|
static inline int max3421e_hw_initialize(
|
|
FAR struct max3421e_usbhost_s *priv);
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
#ifdef HAVE_USBHOST_TRACE
|
|
/* Trace/debug format strings. */
|
|
|
|
static const struct max3421e_usbhost_trace_s g_trace1[TRACE1_NSTRINGS] =
|
|
{
|
|
TRENTRY(MAX3421E_TRACE1_ALLOC_FAIL, TR_FMT1,
|
|
"INIT: Failed to allocate state structure: %u\n"),
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
TRENTRY(MAX3421E_TRACE1_ASYNCHSETUP_FAIL1, TR_FMT1,
|
|
"OUT: Asynch setup failed: %u\n"),
|
|
TRENTRY(MAX3421E_TRACE1_ASYNCHSETUP_FAIL2, TR_FMT1,
|
|
"IN: Asynch setup failed: %u\n"),
|
|
#endif
|
|
TRENTRY(MAX3421E_TRACE1_BAD_JKSTATE, TR_FMT1,
|
|
"CONNECT: Bad JK state: %02x\n"),
|
|
TRENTRY(MAX3421E_TRACE1_BADREVISION, TR_FMT1,
|
|
"INIT: Bad revision number: %02x\n"),
|
|
TRENTRY(MAX3421E_TRACE1_CHANALLOC_FAIL, TR_FMT1,
|
|
"EPALLOC: Channel allocation failed: %u\n"),
|
|
TRENTRY(MAX3421E_TRACE1_CHANWAIT_FAIL, TR_FMT1,
|
|
"OUT: Channel wait failure: %u\n"),
|
|
TRENTRY(MAX3421E_TRACE1_DEVDISCONN1, TR_FMT1,
|
|
"OUT: Disconnected during wait: %u\n"),
|
|
TRENTRY(MAX3421E_TRACE1_DEVDISCONN2, TR_FMT1,
|
|
"CTRL: Disconnected during SETUP phase: %u\n"),
|
|
TRENTRY(MAX3421E_TRACE1_DEVDISCONN3, TR_FMT1,
|
|
"CTRL OUT: Disconnected during DATA phase: %u\n"),
|
|
TRENTRY(MAX3421E_TRACE1_DEVDISCONN4, TR_FMT1,
|
|
"CTRL IN: Disconnected during DATA phase: %u"),
|
|
TRENTRY(MAX3421E_TRACE1_DEVDISCONN5, TR_FMT1,
|
|
"IN: Disconnected during wait: %u\n"),
|
|
TRENTRY(MAX3421E_TRACE1_DEVDISCONN6, TR_FMT1,
|
|
"CONNECT: Device disconnect #1: %u\n"),
|
|
TRENTRY(MAX3421E_TRACE1_DEVDISCONN7, TR_FMT1,
|
|
"CONNECT: Device disconnect #2: %u\n"),
|
|
TRENTRY(MAX3421E_TRACE1_DEVDISCONN8, TR_FMT1,
|
|
"CONNECT: Device disconnect #3: %u\n"),
|
|
TRENTRY(MAX3421E_TRACE1_ENUMERATE_FAIL, TR_FMT1,
|
|
"CONNECT: Enumeration failed: %u\n"),
|
|
TRENTRY(MAX3421E_TRACE1_INSETUP_FAIL1, TR_FMT1,
|
|
"CTRL IN: SETUP phase failure: %u\n"),
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
TRENTRY(MAX3421E_TRACE1_INSETUP_FAIL2, TR_FMT1,
|
|
"CTRL IN: Asynch SETUP phase failure #1: %u\n"),
|
|
TRENTRY(MAX3421E_TRACE1_INSETUP_FAIL3, TR_FMT1,
|
|
"CTRL IN: Asynch SETUP phase failure #2: %u\n"),
|
|
#endif
|
|
TRENTRY(MAX3421E_TRACE1_IRQATTACH_FAIL, TR_FMT1,
|
|
"INIT: Failed to attach interrupt: %u\n"),
|
|
TRENTRY(MAX3421E_TRACE1_OUTSETUP_FAIL1, TR_FMT1,
|
|
"CTRL OUT: SETUP phase failure: %u\n"),
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
TRENTRY(MAX3421E_TRACE1_OUTSETUP_FAIL2, TR_FMT1,
|
|
"CTRL OUT: Asynch SETUP phase failure #1: %u\n"),
|
|
TRENTRY(MAX3421E_TRACE1_OUTSETUP_FAIL3, TR_FMT1,
|
|
"CTRL OUT: Asynch SETUP phase failure #2: %u\n"),
|
|
#endif
|
|
TRENTRY(MAX3421E_TRACE1_RECVDATA_FAIL, TR_FMT1,
|
|
"CTRL IN: Data phase failure: %u\n"),
|
|
TRENTRY(MAX3421E_TRACE1_RECVSTATUS_FAIL, TR_FMT1,
|
|
"CTRL OUT: Status phase failure: %u\n"),
|
|
TRENTRY(MAX3421E_TRACE1_SENDDATA_FAIL, TR_FMT1,
|
|
"CTRL OUT: Data phase failure: %u\n"),
|
|
TRENTRY(MAX3421E_TRACE1_SENDSETUP_FAIL1, TR_FMT1,
|
|
"CTRL OUT: SETUP phase failure: %u\n"),
|
|
TRENTRY(MAX3421E_TRACE1_SENDSETUP_FAIL2, TR_FMT1,
|
|
"CTRL IN: SETUP phase failure: %u\n"),
|
|
TRENTRY(MAX3421E_TRACE1_SENDSTATUS_FAIL, TR_FMT1,
|
|
"CTRL IN: Status phase failure: %u\n"),
|
|
TRENTRY(MAX3421E_TRACE1_TRANSFER_FAILED1, TR_FMT1,
|
|
"OUT: Transfer wait returned failure: %u\n"),
|
|
TRENTRY(MAX3421E_TRACE1_TRANSFER_FAILED2, TR_FMT1,
|
|
"CTRL: SETUP wait returned failure: %u\n"),
|
|
TRENTRY(MAX3421E_TRACE1_TRANSFER_FAILED3, TR_FMT1,
|
|
"IN: Transfer wait returned failure: %u\n"),
|
|
|
|
#ifdef HAVE_USBHOST_TRACE_VERBOSE
|
|
TRENTRY(MAX3421E_VTRACE1_CANCEL, TR_FMT1,
|
|
"Transfer canceled: EP%u\n"),
|
|
TRENTRY(MAX3421E_VTRACE1_CONNECTED1, TR_FMT1,
|
|
"CONNECT: Connection event: %u\n"),
|
|
TRENTRY(MAX3421E_VTRACE1_CONNECTED2, TR_FMT1,
|
|
"CONNECT: Connection change detected: %u\n"),
|
|
TRENTRY(MAX3421E_VTRACE1_CONNECTED3, TR_FMT1,
|
|
"CONNECT: Connected: %u\n"),
|
|
TRENTRY(MAX3421E_VTRACE1_DISCONNECTED1, TR_FMT1,
|
|
"CONNECT: Disconnected: %u\n"),
|
|
TRENTRY(MAX3421E_VTRACE1_DISCONNECTED2, TR_FMT1,
|
|
"CONNECT: Disconnect detected: %u\n"),
|
|
TRENTRY(MAX3421E_VTRACE1_ENUMERATE, TR_FMT1,
|
|
"ENUMERATE: Start: %u\n"),
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
TRENTRY(MAX3421E_VTRACE1_HUB_CONNECTED, TR_FMT1,
|
|
"CONNECT: Hub connected: %u\n"),
|
|
#endif
|
|
TRENTRY(MAX3421E_VTRACE1_INITIALIZED, TR_FMT1,
|
|
"INIT: Hardware initialized: %u\n"),
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
TRENTRY(MAX3421E_VTRACE1_TRANSFER_COMPLETE, TR_FMT1,
|
|
"OUT: Asynch transfer complete: %u\n"),
|
|
#endif
|
|
#endif
|
|
};
|
|
|
|
static const struct max3421e_usbhost_trace_s g_trace2[TRACE2_NSTRINGS] =
|
|
{
|
|
#ifdef HAVE_USBHOST_TRACE_VERBOSE
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
TRENTRY(MAX3421E_VTRACE2_ASYNCH, TR_FMT2,
|
|
"ASYNCH: Transfer started: EP%u len=%u\n"),
|
|
#endif
|
|
TRENTRY(MAX3421E_VTRACE2_BULKIN, TR_FMT2,
|
|
"BULK IN: SETUP: Chan%u len=%u\n"),
|
|
TRENTRY(MAX3421E_VTRACE2_BULKOUT, TR_FMT2,
|
|
"BULK OUT: SETUP: Chan%u len=%u\n"),
|
|
TRENTRY(MAX3421E_VTRACE2_CHANWAKEUP_IN, TR_FMT2,
|
|
"IN: Channel wakeup: Chan%, result=%u\n"),
|
|
TRENTRY(MAX3421E_VTRACE2_CHANWAKEUP_OUT, TR_FMT2,
|
|
"OUT: Channel wakeup: Chan%u result=%u\n"),
|
|
TRENTRY(MAX3421E_VTRACE2_CTRLIN, TR_FMT2,
|
|
"CTRL IN: Start: type=%u req=%u\n"),
|
|
TRENTRY(MAX3421E_VTRACE2_CTRLOUT, TR_FMT2,
|
|
"CTRL OUT: Start: type=%u req=%u\n"),
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
TRENTRY(MAX3421E_VTRACE2_HUB_CONNECTED, TR_FMT2,
|
|
"CONNECT: Hub connected: port=%u, connected=%u\n"),
|
|
#endif
|
|
TRENTRY(MAX3421E_VTRACE2_INTRIN, TR_FMT2,
|
|
"INTR IN: SETUP: Chan%u len=%u\n"),
|
|
TRENTRY(MAX3421E_VTRACE2_INTROUT, TR_FMT2,
|
|
"INTR OUT: SETUP: Chan%u len=%u\n"),
|
|
TRENTRY(MAX3421E_VTRACE2_ISOCIN, TR_FMT2,
|
|
"ISOC IN: SETUP: Chan%u len=%u\n"),
|
|
TRENTRY(MAX3421E_VTRACE2_ISOCOUT, TR_FMT2,
|
|
"ISOC OUT: SETUP: Chan%u len=%u\n"),
|
|
TRENTRY(MAX3421E_VTRACE2_RECVSTATUS, TR_FMT2,
|
|
"CTRL OUT: Receive status: Chan% len=%u\n"),
|
|
TRENTRY(MAX3421E_VTRACE2_SENDSTATUS, TR_FMT2,
|
|
"CTRL IN: Send status: Chan% len=%u\n"),
|
|
TRENTRY(MAX3421E_VTRACE2_STARTTRANSFER1, TR_FMT2,
|
|
"OUT: Send start: Chan% len=%u\n"),
|
|
TRENTRY(MAX3421E_VTRACE2_STARTTRANSFER2, TR_FMT2,
|
|
"IN: Receive start: Chan% len=%u\n"),
|
|
TRENTRY(MAX3421E_VTRACE2_TRANSFER, TR_FMT2,
|
|
"Transfer start: EP%u len=%u\n"),
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
TRENTRY(MAX3421E_VTRACE2_XFRCOMPLETE, TR_FMT2,
|
|
"ASYNCH: Transfer complete: EP%u len=%u\n"),
|
|
#endif
|
|
#endif
|
|
};
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_lock
|
|
*
|
|
* Description:
|
|
* Lock and configure the SPI bus.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void max3421e_lock(FAR struct max3421e_usbhost_s *priv)
|
|
{
|
|
FAR const struct max3421e_lowerhalf_s *lower = priv->lower;
|
|
FAR struct spi_dev_s *spi;
|
|
|
|
DEBUGASSERT(lower != NULL && lower->spi != NULL && !priv->locked);
|
|
spi = lower->spi;
|
|
|
|
SPI_LOCK(spi, true);
|
|
SPI_SETMODE(spi, lower->mode);
|
|
SPI_SETBITS(spi, 8);
|
|
SPI_HWFEATURES(spi, 0);
|
|
SPI_SETFREQUENCY(spi, lower->frequency);
|
|
|
|
#ifdef CONFIG_DEBUG_ASSERTIONS
|
|
/* Mark the SPI bus as locked (for debug only) */
|
|
|
|
priv->locked = true;
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_unlock
|
|
*
|
|
* Description:
|
|
* Unlock the SPI bus.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void max3421e_unlock(FAR struct max3421e_usbhost_s *priv)
|
|
{
|
|
FAR const struct max3421e_lowerhalf_s *lower = priv->lower;
|
|
|
|
DEBUGASSERT(lower != NULL && lower->spi != NULL && priv->locked);
|
|
SPI_LOCK(lower->spi, false);
|
|
|
|
#ifdef CONFIG_DEBUG_ASSERTIONS
|
|
/* Mark the SPI bus as unlocked (for debug only) */
|
|
|
|
priv->locked = false;
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_printreg
|
|
*
|
|
* Description:
|
|
* Print the contents of an MAX3421Exx register operation
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_MAX3421E_USBHOST_REGDEBUG
|
|
static void max3421e_printreg(uint8_t addr, uint8_t val, bool iswrite)
|
|
{
|
|
uinfo("%02x%s%02x\n", addr, iswrite ? "<-" : "->", val);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_checkreg
|
|
*
|
|
* Description:
|
|
* Get the contents of an MAX3421E register
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_MAX3421E_USBHOST_REGDEBUG
|
|
static void max3421e_checkreg(uint8_t addr, uint8_t val, bool iswrite)
|
|
{
|
|
static uint8_t prevaddr = 0;
|
|
static uint8_t preval = 0;
|
|
static unsigned int 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 */
|
|
|
|
max3421e_printreg(prevaddr, preval, prevwrite);
|
|
}
|
|
else
|
|
{
|
|
/* No.. More than one. */
|
|
|
|
uinfo("[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 register access */
|
|
|
|
max3421e_printreg(addr, val, iswrite);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_fmtcmd
|
|
*
|
|
* Description:
|
|
* Format a command
|
|
*
|
|
* The command byte contains the register address, a direction bit, and an
|
|
* ACKSTAT bit:
|
|
*
|
|
* Bits 3-7: Command
|
|
* Bit 2: Unused
|
|
* Bit 1: Direction (read = 0, write = 1)
|
|
* Bit 0: ACKSTAT
|
|
*
|
|
* The ACKSTAT bit is ignored in host mode.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline uint8_t max3421e_fmtcmd(FAR struct max3421e_usbhost_s *priv,
|
|
uint8_t addr, uint8_t dir)
|
|
{
|
|
return addr | dir | MAX3421E_ACKSTAT_FALSE;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_getreg
|
|
*
|
|
* Description:
|
|
* Get the contents of an MAX3421E register
|
|
*
|
|
* Assumption:
|
|
* SPI bus is locked
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint32_t max3421e_getreg(FAR struct max3421e_usbhost_s *priv,
|
|
uint8_t addr)
|
|
{
|
|
FAR const struct max3421e_lowerhalf_s *lower = priv->lower;
|
|
FAR struct spi_dev_s *spi;
|
|
uint8_t cmd;
|
|
uint8_t value;
|
|
|
|
DEBUGASSERT(lower != NULL && lower->spi != NULL && priv->locked);
|
|
spi = lower->spi;
|
|
|
|
/* Select the MAX4321E */
|
|
|
|
SPI_SELECT(spi, SPIDEV_USBHOST(lower->devid), true);
|
|
|
|
/* Send the read command byte */
|
|
|
|
cmd = max3421e_fmtcmd(priv, addr, MAX3421E_DIR_READ);
|
|
SPI_SEND(spi, cmd);
|
|
|
|
/* Read the value of the register */
|
|
|
|
value = SPI_SEND(spi, 0xff);
|
|
|
|
/* De-select the MAX4321E */
|
|
|
|
SPI_SELECT(spi, SPIDEV_USBHOST(lower->devid), false);
|
|
|
|
#ifdef CONFIG_MAX3421E_USBHOST_REGDEBUG
|
|
/* Check if we need to print this value */
|
|
|
|
max3421e_checkreg(addr, value, false);
|
|
#endif
|
|
|
|
return value;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_putreg
|
|
*
|
|
* Description:
|
|
* Set the contents of an MAX3421E register to a value
|
|
*
|
|
* Assumption:
|
|
* SPI bus is locked
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void max3421e_putreg(FAR struct max3421e_usbhost_s *priv,
|
|
uint8_t addr, uint8_t value)
|
|
{
|
|
FAR const struct max3421e_lowerhalf_s *lower = priv->lower;
|
|
FAR struct spi_dev_s *spi;
|
|
uint8_t cmd;
|
|
|
|
DEBUGASSERT(lower != NULL && lower->spi != NULL && priv->locked);
|
|
spi = lower->spi;
|
|
|
|
#ifdef CONFIG_MAX3421E_USBHOST_REGDEBUG
|
|
/* Check if we need to print this value */
|
|
|
|
max3421e_checkreg(addr, val, true);
|
|
#endif
|
|
|
|
/* Select the MAX4321E */
|
|
|
|
SPI_SELECT(spi, SPIDEV_USBHOST(lower->devid), true);
|
|
|
|
/* Send the write command byte */
|
|
|
|
cmd = max3421e_fmtcmd(priv, addr, MAX3421E_DIR_WRITE);
|
|
SPI_SEND(spi, cmd);
|
|
|
|
/* Send the new value for the register */
|
|
|
|
SPI_SEND(spi, value);
|
|
|
|
/* De-select the MAX4321E */
|
|
|
|
SPI_SELECT(spi, SPIDEV_USBHOST(lower->devid), false);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_modifyreg
|
|
*
|
|
* Description:
|
|
* Modify selected bits of an MAX3421E register.
|
|
*
|
|
* Assumption:
|
|
* SPI bus is locked
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void max3421e_modifyreg(FAR struct max3421e_usbhost_s *priv,
|
|
uint8_t addr, uint8_t clrbits,
|
|
uint8_t setbits)
|
|
{
|
|
uint8_t value;
|
|
|
|
value = max3421e_getreg(priv, addr);
|
|
value &= ~clrbits;
|
|
value |= setbits;
|
|
max3421e_putreg(priv, addr, value);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_recvblock
|
|
*
|
|
* Description:
|
|
* Receive a block of data from the MAX341E.
|
|
*
|
|
* Assumption:
|
|
* SPI bus is locked
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void max3421e_recvblock(FAR struct max3421e_usbhost_s *priv,
|
|
uint8_t addr, FAR void *buffer, size_t buflen)
|
|
{
|
|
FAR const struct max3421e_lowerhalf_s *lower = priv->lower;
|
|
FAR struct spi_dev_s *spi;
|
|
uint8_t cmd;
|
|
|
|
DEBUGASSERT(lower != NULL && lower->spi != NULL && priv->locked);
|
|
spi = lower->spi;
|
|
|
|
/* Select the MAX4321E */
|
|
|
|
SPI_SELECT(spi, SPIDEV_USBHOST(lower->devid), true);
|
|
|
|
/* Send the read command byte */
|
|
|
|
cmd = max3421e_fmtcmd(priv, addr, MAX3421E_DIR_READ);
|
|
SPI_SEND(spi, cmd);
|
|
|
|
/* Read the block of values from the register(s) */
|
|
|
|
SPI_RECVBLOCK(spi, buffer, buflen);
|
|
|
|
/* De-select the MAX4321E */
|
|
|
|
SPI_SELECT(spi, SPIDEV_USBHOST(lower->devid), false);
|
|
|
|
#ifdef CONFIG_MAX3421E_USBHOST_REGDEBUG
|
|
/* Dump the block of data received */
|
|
|
|
lib_dumpbuffer("Received:", buffer, buflen);
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_sndblock
|
|
*
|
|
* Description:
|
|
* Send a block of data to the MAX341E.
|
|
*
|
|
* Assumption:
|
|
* SPI bus is locked
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void max3421e_sndblock(FAR struct max3421e_usbhost_s *priv,
|
|
uint8_t addr, FAR const void *buffer,
|
|
size_t buflen)
|
|
{
|
|
FAR const struct max3421e_lowerhalf_s *lower = priv->lower;
|
|
FAR struct spi_dev_s *spi;
|
|
uint8_t cmd;
|
|
|
|
DEBUGASSERT(lower != NULL && lower->spi != NULL && priv->locked);
|
|
spi = lower->spi;
|
|
|
|
#ifdef CONFIG_MAX3421E_USBHOST_REGDEBUG
|
|
/* Dump the block of data to be sent */
|
|
|
|
lib_dumpbuffer("Sending:", buffer, buflen);
|
|
#endif
|
|
|
|
/* Select the MAX4321E */
|
|
|
|
SPI_SELECT(spi, SPIDEV_USBHOST(lower->devid), true);
|
|
|
|
/* Send the wrte command byte */
|
|
|
|
cmd = max3421e_fmtcmd(priv, addr, MAX3421E_DIR_WRITE);
|
|
SPI_SEND(spi, cmd);
|
|
|
|
/* Send the new value for the register */
|
|
|
|
SPI_SNDBLOCK(spi, buffer, buflen);
|
|
|
|
/* De-select the MAX4321E */
|
|
|
|
SPI_SELECT(spi, SPIDEV_USBHOST(lower->devid), false);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_getle16
|
|
*
|
|
* Description:
|
|
* Get a (possibly unaligned) 16-bit little endian value.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline uint16_t max3421e_getle16(const uint8_t *val)
|
|
{
|
|
return (uint16_t)val[1] << 8 | (uint16_t)val[0];
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_chan_alloc
|
|
*
|
|
* Description:
|
|
* Allocate a channel.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int max3421e_chan_alloc(FAR struct max3421e_usbhost_s *priv)
|
|
{
|
|
int chidx;
|
|
|
|
/* Search the table of channels */
|
|
|
|
for (chidx = 0; chidx < MAX3421E_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: max3421e_chan_free
|
|
*
|
|
* Description:
|
|
* Free a previoiusly allocated channel.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void max3421e_chan_free(FAR struct max3421e_usbhost_s *priv,
|
|
int chidx)
|
|
{
|
|
DEBUGASSERT((unsigned)chidx < MAX3421E_NHOST_CHANNELS);
|
|
|
|
/* Halt the channel */
|
|
#warning Missing logic
|
|
|
|
/* Mark the channel available */
|
|
|
|
priv->chan[chidx].inuse = false;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_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 max3421e_chan_waitsetup(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan)
|
|
{
|
|
irqstate_t flags = enter_critical_section();
|
|
int ret = -ENODEV;
|
|
|
|
DEBUGASSERT(priv != NULL && chan != NULL && priv->waiter == NULL);
|
|
|
|
/* 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.
|
|
*/
|
|
|
|
priv->waiter = chan;
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
priv->callback = NULL;
|
|
priv->arg = NULL;
|
|
#endif
|
|
ret = OK;
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_chan_asynchsetup
|
|
*
|
|
* Description:
|
|
* Set the request for the transfer complete event well BEFORE enabling the
|
|
* transfer (as soon as we are absolutely committed to 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 max3421e_chan_asynchsetup(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan,
|
|
usbhost_asynch_t callback, FAR void *arg)
|
|
{
|
|
irqstate_t flags = enter_critical_section();
|
|
int ret = -ENODEV;
|
|
|
|
DEBUGASSERT(priv != NULL && chan != NULL);
|
|
|
|
/* Is the device still connected? */
|
|
|
|
if (priv->connected)
|
|
{
|
|
priv->waiter = NULL; /* No waiter */
|
|
priv->callback = callback;
|
|
priv->arg = arg;
|
|
ret = OK;
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_chan_wait
|
|
*
|
|
* Description:
|
|
* Wait for a transfer on a channel to complete.
|
|
*
|
|
* Assumptions:
|
|
* Called from a normal thread context
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int max3421e_chan_wait(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan)
|
|
{
|
|
irqstate_t flags;
|
|
int ret;
|
|
|
|
/* Disable interrupts so that the following operations will be atomic. On
|
|
* the host 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 the channel expecting the
|
|
* response before the transfer was started; 'waiter' will be nullified
|
|
* 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.
|
|
*/
|
|
|
|
ret = nxsem_wait_uninterruptible(&priv->waitsem);
|
|
if (ret < 0)
|
|
{
|
|
leave_critical_section(flags);
|
|
return ret;
|
|
}
|
|
}
|
|
while (priv->waiter != NULL);
|
|
|
|
/* The transfer is complete re-enable interrupts and return the result */
|
|
|
|
ret = -(int)priv->result;
|
|
leave_critical_section(flags);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_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 max3421e_chan_wakeup(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan,
|
|
int result)
|
|
{
|
|
/* Save the result of the operation */
|
|
|
|
DEBUGASSERT(priv->result != EBUSY);
|
|
priv->result = result;
|
|
|
|
/* Is there a thread waiting for this transfer to complete? */
|
|
|
|
if (priv->waiter != NULL)
|
|
{
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
/* Yes.. there should not also be a callback scheduled */
|
|
|
|
DEBUGASSERT(priv->callback == NULL);
|
|
#endif
|
|
/* Wake'em up! */
|
|
|
|
usbhost_vtrace2(chan->in ? MAX3421E_VTRACE2_CHANWAKEUP_IN :
|
|
MAX3421E_VTRACE2_CHANWAKEUP_OUT,
|
|
chan->chidx, priv->result);
|
|
|
|
nxsem_post(&priv->waitsem);
|
|
priv->waiter = NULL;
|
|
}
|
|
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
/* No.. is an asynchronous callback expected when the transfer completes? */
|
|
|
|
else if (priv->callback)
|
|
{
|
|
/* Handle continuation of IN/OUT pipes */
|
|
|
|
if (chan->in)
|
|
{
|
|
max3421e_in_next(priv, chan);
|
|
}
|
|
else
|
|
{
|
|
max3421e_out_next(priv, chan);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_save_toggles and max3421e_restore_toggles
|
|
*
|
|
* Description:
|
|
* Save and restore data toggles from/to the HCTL register. The MAX3421E
|
|
* will automatically update the toggles for consecutive transfers to the
|
|
* same endpoint; We need to use these only when we change endpoints.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void
|
|
max3421e_save_toggles(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan)
|
|
{
|
|
chan->toggles = max3421e_getreg(priv, MAX3421E_USBHOST_HCTL);
|
|
}
|
|
|
|
static inline void
|
|
max3421e_restore_toggles(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan)
|
|
{
|
|
max3421e_modifyreg(priv, MAX3421E_USBHOST_HCTL,
|
|
USBHOST_HCTL_TOGGLES_MASK,
|
|
chan->toggles & USBHOST_HCTL_TOGGLES_MASK);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_transfer_status
|
|
*
|
|
* Description:
|
|
* Get the end-of-transfer status from HRSLT register.
|
|
*
|
|
* REVISIT: Currently NAKs are treated as errors. A NAK on the first
|
|
* packet can probably be treated that way. But not NAKs after the
|
|
* transfer is in progress. We should also need to reset the peripheral
|
|
* in that case. Better to try and retry here within the driver.
|
|
*
|
|
* Returned value:
|
|
* OK - Transfer successful
|
|
* -EAGAIN - If devices NAKs the transfer.
|
|
* -EPERM - If the endpoint stalls
|
|
* -BUSY - The transfer is not complete
|
|
* -EIO - Other, undecoded error
|
|
*
|
|
* Assumptions:
|
|
* The SPI bus is locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int max3421e_transfer_status(FAR struct max3421e_usbhost_s *priv)
|
|
{
|
|
uint8_t regval;
|
|
int ret;
|
|
|
|
/* Get the result of the transfer from the HRSLT register */
|
|
|
|
regval = max3421e_getreg(priv, MAX3421E_USBHOST_HRSL);
|
|
|
|
/* Make the error result to something that the world knows about */
|
|
|
|
switch (regval & USBHOST_HRSL_HRSLT_MASK)
|
|
{
|
|
case USBHOST_HRSL_HRSLT_SUCCESS:
|
|
ret = OK;
|
|
break;
|
|
|
|
case USBHOST_HRSL_HRSLT_BUSY:
|
|
ret = -EBUSY;
|
|
break;
|
|
|
|
case USBHOST_HRSL_HRSLT_NAK:
|
|
ret = -EAGAIN;
|
|
break;
|
|
|
|
case USBHOST_HRSL_HRSLT_STALL:
|
|
ret = -EPERM;
|
|
break;
|
|
|
|
default:
|
|
ret = -EIO;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_transfer_terminate
|
|
*
|
|
* Description:
|
|
* Terminate a IN or OUT transfer due to an error (or because a zero-
|
|
* length OUT transfer occurred).
|
|
*
|
|
* Returned value:
|
|
* OK - Transfer successful
|
|
* -EAGAIN - If devices NAKs the transfer.
|
|
* -EPERM - If the endpoint stalls
|
|
* -BUSY - The transfer is not complete
|
|
* -EIO - Other, undecoded error
|
|
*
|
|
* Assumptions:
|
|
* The SPI bus is locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void max3421e_transfer_terminate(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan,
|
|
int result)
|
|
{
|
|
/* Disable further SNDBAV, RCVDAV or HXFRDN interrupts */
|
|
|
|
max3421e_int_disable(priv, USBHOST_HIRQ_SNDBAVIRQ |
|
|
USBHOST_HIRQ_RCVDAVIRQ |
|
|
USBHOST_HIRQ_HXFRDNIRQ);
|
|
|
|
/* Save the endpoint toggle settings.
|
|
*
|
|
* REVISIT: The MAX4321E sends fixed DATA0 and DATA1 PID tokens for the
|
|
* various stages of a CONTROL transfer, regardless of the setting of
|
|
* the internal data toggle.
|
|
*/
|
|
|
|
max3421e_save_toggles(priv, chan);
|
|
|
|
/* Wake up any waiters for the end of transfer event */
|
|
|
|
DEBUGASSERT(priv->waiter != NULL);
|
|
max3421e_chan_wakeup(priv, chan, -result);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_put_sndfifo
|
|
*
|
|
* Description:
|
|
* Copy data from the user-provided buffer into the SNDFIFO.
|
|
*
|
|
* Assumptions:
|
|
* The SPI bus is locked.
|
|
* There is data to be remaining to be sent.
|
|
* The caller has already set up for the wait event.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void max3421e_put_sndfifo(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan)
|
|
{
|
|
uint16_t committed;
|
|
uint16_t wrsize;
|
|
uint8_t maxpacket;
|
|
int i;
|
|
|
|
DEBUGASSERT(priv != NULL && chan != NULL);
|
|
|
|
/* The SNDFIFO is double buffered. We may load up to MAX3421E_SNDFIFO_SIZE
|
|
* into one buffer. After loading the SNDFIFO buffer, the write the SNDBC
|
|
* (Send Byte Count) register with the number of bytes loaded. The
|
|
* MAX3421E will clear SNDBAVIRQ (Send Buffer Available IRQ) and commit
|
|
* the FIFO to USB transmission.
|
|
*
|
|
* If the other buffer is available when SNDBC is written, the MAX3421E
|
|
* will clear SNDBAVIRQ then immediately set it to indicate
|
|
* availability of the second buffer.
|
|
*
|
|
* The CPU should load the SNDFIFO only when a SNDBAVIRQ = 1.
|
|
*/
|
|
|
|
maxpacket = chan->maxpacket;
|
|
DEBUGASSERT(maxpacket <= MAX3421E_SNDFIFO_SIZE);
|
|
|
|
committed = priv->xfrd + (uint16_t)priv->inflight;
|
|
DEBUGASSERT(committed < priv->buflen);
|
|
|
|
for (i = 0;
|
|
i < 2 && committed < priv->buflen &&
|
|
(max3421e_int_status(priv) & USBHOST_HIRQ_SNDBAVIRQ) != 0;
|
|
i++)
|
|
{
|
|
/* Get the size of the biggest thing that we can put in the current
|
|
* SNDFIFO buffer.
|
|
*/
|
|
|
|
wrsize = priv->buflen - committed;
|
|
if (wrsize > maxpacket)
|
|
{
|
|
wrsize = maxpacket;
|
|
}
|
|
|
|
/* Write packet into the SNDFIFO. */
|
|
|
|
max3421e_sndblock(priv, MAX3421E_USBHOST_SNDFIFO,
|
|
priv->buffer + committed, wrsize);
|
|
|
|
/* Write the byte count to the SNDBC register */
|
|
|
|
max3421e_putreg(priv, MAX3421E_USBHOST_SNDBC, wrsize);
|
|
|
|
/* Send the OUT token */
|
|
|
|
max3421e_putreg(priv, MAX3421E_USBHOST_HXFR,
|
|
USBHOST_HXFR_TOKEN_OUT | chan->epno);
|
|
|
|
/* Increment the count of bytes "in-flight" in the SNDFIFO */
|
|
|
|
priv->inflight += wrsize;
|
|
committed = priv->xfrd + wrsize;
|
|
}
|
|
|
|
/* Enable the SNDFIFO interrupt to handle the completion/continuation
|
|
* of transfer. Enable the HXFRDNIRQ to catch NAKs and transfer errors.
|
|
*/
|
|
|
|
priv->xfrtype = HXFRDN_SNDFIFO;
|
|
max3421e_int_enable(priv, USBHOST_HIRQ_SNDBAVIRQ | USBHOST_HIRQ_HXFRDNIRQ);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_send_continue
|
|
*
|
|
* Description:
|
|
* Continue the send operation started by max3421e_send_start(). If
|
|
* max3421e_put_sndfifo() was unable to load the entire outgoing buffer
|
|
* into the SNDFIFO, it enabled SNDBAV interrupt.
|
|
*
|
|
* When the SNDBAV interrupt occurred, max3421e_irqwork() disabled that
|
|
* interrupt and called this function in order to continue that long send
|
|
* operations.
|
|
*
|
|
* Assumptions:
|
|
* The SPI bus is locked.
|
|
* The SNDBAV interrupt has been disabled.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void max3421e_send_continue(FAR struct max3421e_usbhost_s *priv)
|
|
{
|
|
FAR struct max3421e_chan_s *chan;
|
|
int result;
|
|
uint8_t xfrd;
|
|
|
|
DEBUGASSERT(priv != NULL && priv->waiter != NULL);
|
|
chan = priv->waiter;
|
|
|
|
/* Check the result of a transfer */
|
|
|
|
result = max3421e_transfer_status(priv);
|
|
if (result < 0)
|
|
{
|
|
/* Terminate the transfer on any error. */
|
|
|
|
max3421e_transfer_terminate(priv, chan, result);
|
|
return;
|
|
}
|
|
|
|
/* Update the number of bytes transferred. We have to be a little clever
|
|
* here: We do not keep track of the number of bytes sent in each of the
|
|
* SNDFIFO buffers, rather only the outstanding number of buffered
|
|
* transferred, 'inflight'. However, we know that all transfers will be
|
|
* the max packet size (other than the last one perhaps).
|
|
*/
|
|
|
|
/* If the number inflight is strictly greater than the maxpacket size, then
|
|
* we can infer that the transfer that just completed was maxpacket size.
|
|
*/
|
|
|
|
if (priv->inflight > chan->maxpacket)
|
|
{
|
|
xfrd = chan->maxpacket;
|
|
}
|
|
|
|
/* If the number inflight is less than chan->maxpacket, then that must have
|
|
* been the last packet of the transfer.
|
|
*/
|
|
|
|
else if (priv->inflight < chan->maxpacket)
|
|
{
|
|
xfrd = priv->inflight;
|
|
DEBUGASSERT((priv->xfrd + xfrd) == priv->buflen);
|
|
}
|
|
|
|
/* If the number inflight is exactly the maxpacket size and the transfer
|
|
* is not yet finished, then the the transfer size must have been max
|
|
* packet size.
|
|
*/
|
|
|
|
else if ((priv->xfrd + chan->maxpacket) >= priv->buflen)
|
|
{
|
|
xfrd = priv->buflen - priv->xfrd;
|
|
DEBUGASSERT((priv->xfrd + xfrd) == priv->buflen);
|
|
}
|
|
|
|
/* Otherwise, the transfer must have been the max packet size */
|
|
|
|
else
|
|
{
|
|
xfrd = chan->maxpacket;
|
|
}
|
|
|
|
priv->xfrd += xfrd;
|
|
priv->inflight -= xfrd;
|
|
|
|
/* Check for the end of transfer */
|
|
|
|
if (priv->xfrd >= priv->buflen)
|
|
{
|
|
/* Successful end-of-transfer */
|
|
|
|
max3421e_transfer_terminate(priv, chan, OK);
|
|
}
|
|
else
|
|
{
|
|
/* No.. there are more bytes to be sent */
|
|
|
|
max3421e_put_sndfifo(priv, chan);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_send_start
|
|
*
|
|
* Description:
|
|
* Start at transfer on the selected IN or OUT channel.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void max3421e_send_start(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan)
|
|
{
|
|
max3421e_pktdump("Sending", priv->buffer, priv->buflen);
|
|
|
|
/* Set up the initial state of the transfer */
|
|
|
|
usbhost_vtrace2(MAX3421E_VTRACE2_STARTTRANSFER1,
|
|
chan->chidx, priv->buflen);
|
|
|
|
priv->result = EBUSY;
|
|
priv->inflight = 0;
|
|
priv->xfrd = 0;
|
|
|
|
/* Make sure the peripheral address is correct */
|
|
|
|
max3421e_putreg(priv, MAX3421E_USBHOST_PERADDR, chan->funcaddr);
|
|
|
|
/* Checkout for zero length packet */
|
|
|
|
if (priv->buflen > 0)
|
|
{
|
|
/* No.. we need to copy the outgoing data into SNDFIFO. */
|
|
|
|
max3421e_restore_toggles(priv, chan);
|
|
|
|
/* Then load the data into the SNDFIFO and start the transfer */
|
|
|
|
max3421e_put_sndfifo(priv, chan);
|
|
}
|
|
else
|
|
{
|
|
/* Write the zero byte count to the SNDBC register */
|
|
|
|
max3421e_putreg(priv, MAX3421E_USBHOST_SNDBC, 0);
|
|
|
|
/* Send the OUT token */
|
|
|
|
max3421e_putreg(priv, MAX3421E_USBHOST_HXFR,
|
|
USBHOST_HXFR_TOKEN_OUT | chan->epno);
|
|
|
|
/* Enable the HXFRDNIRQ to catch completion of the ZLP. */
|
|
|
|
priv->xfrtype = HXFRDN_SNDZLP;
|
|
max3421e_int_enable(priv, USBHOST_HIRQ_HXFRDNIRQ);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_out_transfer
|
|
*
|
|
* Description:
|
|
* Transfer the 'buflen' bytes in 'buffer' through an OUT channel.
|
|
*
|
|
* Assumptions:
|
|
* The SPI is not locked. This function is called only from the TRANSFER
|
|
* interface and must manage the SPI lock itself. The lock, for example,
|
|
* must be relinquished before waiting.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static ssize_t max3421e_out_transfer(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan,
|
|
FAR uint8_t *buffer, size_t buflen)
|
|
{
|
|
clock_t start;
|
|
clock_t elapsed;
|
|
size_t xfrlen;
|
|
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)
|
|
*/
|
|
|
|
start = clock_systime_ticks();
|
|
xfrd = 0;
|
|
|
|
while (buflen > 0)
|
|
{
|
|
/* 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);
|
|
priv->buffer = buffer;
|
|
priv->buflen = xfrlen;
|
|
priv->xfrd = 0;
|
|
|
|
/* Set up for the wait BEFORE starting the transfer */
|
|
|
|
ret = max3421e_chan_waitsetup(priv, chan);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_DEVDISCONN1, 0);
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
/* Set up for the transfer based on the direction and the endpoint */
|
|
|
|
max3421e_lock(priv);
|
|
ret = max3421e_out_setup(priv, chan);
|
|
max3421e_unlock(priv);
|
|
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_OUTSETUP_FAIL1, -ret);
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
/* Wait for the transfer to complete and get the result */
|
|
|
|
ret = max3421e_chan_wait(priv, chan);
|
|
|
|
/* Handle transfer failures */
|
|
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_TRANSFER_FAILED1, ret);
|
|
|
|
/* Check for a special case: If (1) the transfer was NAKed and (2)
|
|
* no SNDFIFO empty or Rx FIFO not-empty event occurred, then we
|
|
* should be able to just flush the Rx and SNDFIFOs and try again.
|
|
* We can detect this latter case because then the transfer buffer
|
|
* pointer and buffer size will be unaltered.
|
|
*/
|
|
|
|
elapsed = clock_systime_ticks() - start;
|
|
if (ret != -EAGAIN || /* Not a NAK condition OR */
|
|
elapsed >= MAX3421E_DATANAK_DELAY || /* Timeout has elapsed OR */
|
|
priv->xfrd > 0) /* Data has been partially transferred */
|
|
{
|
|
/* Break out and return the error */
|
|
|
|
usbhost_trace1(MAX3421E_TRACE1_CHANWAIT_FAIL, -ret);
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
/* 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/length */
|
|
|
|
buffer += xfrlen;
|
|
buflen -= xfrlen;
|
|
xfrd += priv->xfrd;
|
|
}
|
|
}
|
|
|
|
return xfrd;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_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 max3421e_out_next(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_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 OK? */
|
|
|
|
result = -(int)priv->result;
|
|
if (priv->xfrd < priv->buflen && result == OK)
|
|
{
|
|
/* Yes.. Set up for the next transfer based on the direction and the
|
|
* endpoint type
|
|
*/
|
|
|
|
ret = max3421e_out_setup(priv, chan);
|
|
if (ret >= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
usbhost_trace1(MAX3421E_TRACE1_OUTSETUP_FAIL2, -ret);
|
|
result = ret;
|
|
}
|
|
|
|
/* The transfer is complete, with or without an error */
|
|
|
|
usbhost_vtrace1(MAX3421E_VTRACE1_TRANSFER_COMPLETE, result);
|
|
|
|
/* Extract the callback information */
|
|
|
|
callback = priv->callback;
|
|
arg = priv->arg;
|
|
nbytes = priv->xfrd;
|
|
|
|
priv->callback = NULL;
|
|
priv->arg = NULL;
|
|
priv->xfrd = 0;
|
|
|
|
/* Then perform the callback */
|
|
|
|
if (result < 0)
|
|
{
|
|
nbytes = (ssize_t)result;
|
|
}
|
|
|
|
callback(arg, nbytes);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_out_asynch
|
|
*
|
|
* Description:
|
|
* Initiate the first of a sequence of asynchronous transfers.
|
|
*
|
|
* Assumptions:
|
|
* The SPI is not locked. This function is called only from the ASYNCH
|
|
* interface and must manage the SPI lock itself. The lock, for example,
|
|
* must be relinquished before waiting.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
static int max3421e_out_asynch(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan,
|
|
FAR uint8_t *buffer, size_t buflen,
|
|
usbhost_asynch_t callback, FAR void *arg)
|
|
{
|
|
int ret;
|
|
|
|
/* Set up for the transfer BEFORE starting the first transfer */
|
|
|
|
priv->buffer = buffer;
|
|
priv->buflen = buflen;
|
|
priv->xfrd = 0;
|
|
|
|
ret = max3421e_chan_asynchsetup(priv, chan, callback, arg);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_ASYNCHSETUP_FAIL1, -ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Set up for the transfer based on the direction and the endpoint type */
|
|
|
|
max3421e_lock(priv);
|
|
ret = max3421e_out_setup(priv, chan);
|
|
max3421e_unlock(priv);
|
|
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_OUTSETUP_FAIL3, -ret);
|
|
}
|
|
|
|
/* And return with the transfer pending */
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_ctrl_sendsetup
|
|
*
|
|
* Description:
|
|
* Send an IN/OUT SETUP packet.
|
|
*
|
|
* Assumptions:
|
|
* The SPI is not locked. This function is called only from the CTRLIN and
|
|
* CTRLOUT interfaces and must manage the SPI lock itself. The lock, for
|
|
* example, must be relinquished before waiting.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int max3421e_ctrl_sendsetup(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan,
|
|
FAR const struct usb_ctrlreq_s *req)
|
|
{
|
|
clock_t start;
|
|
clock_t elapsed;
|
|
int ret;
|
|
|
|
/* Loop while the device reports NAK (and a timeout is not exceeded */
|
|
|
|
start = clock_systime_ticks();
|
|
do
|
|
{
|
|
/* Send the SETUP packet */
|
|
|
|
priv->buffer = (FAR uint8_t *)req;
|
|
priv->buflen = USB_SIZEOF_CTRLREQ;
|
|
priv->inflight = 0;
|
|
priv->xfrd = 0;
|
|
priv->result = EBUSY;
|
|
|
|
/* Set up for the wait BEFORE starting the transfer */
|
|
|
|
ret = max3421e_chan_waitsetup(priv, chan);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_DEVDISCONN2, 0);
|
|
return ret;
|
|
}
|
|
|
|
/* Make sure the peripheral address is correct */
|
|
|
|
max3421e_lock(priv);
|
|
max3421e_putreg(priv, MAX3421E_USBHOST_PERADDR, chan->funcaddr);
|
|
|
|
/* Write packet into the SUDFIFO. */
|
|
|
|
max3421e_sndblock(priv, MAX3421E_USBHOST_SUDFIFO, priv->buffer,
|
|
priv->buflen);
|
|
|
|
/* Send the SETUP token (always EP0) */
|
|
|
|
max3421e_putreg(priv, MAX3421E_USBHOST_HXFR,
|
|
USBHOST_HXFR_TOKEN_SETUP);
|
|
|
|
/* Increment the count of bytes "in-flight" in the SNDFIFO */
|
|
|
|
priv->inflight = priv->buflen;
|
|
|
|
/* The MAX3421E waits 18 bit times for the device to respond or time
|
|
* out then terminates the transfer by asserting the HXFRDNIRQ and
|
|
* updating the HSRLT bits. NOTE: The USB spec says that a
|
|
* peripheral must always ACK a SETUP packet.
|
|
*/
|
|
|
|
priv->xfrtype = HXFRDN_SETUP;
|
|
max3421e_int_enable(priv, USBHOST_HIRQ_HXFRDNIRQ);
|
|
|
|
/* Wait for the transfer to complete */
|
|
|
|
max3421e_unlock(priv);
|
|
ret = max3421e_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. NOTE: The USB spec says that a peripheral
|
|
* must always ACK a SETUP packet.
|
|
*/
|
|
|
|
if (ret != -EAGAIN)
|
|
{
|
|
/* Output some debug information if the transfer failed */
|
|
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_TRANSFER_FAILED2, ret);
|
|
}
|
|
|
|
/* Return the result in any event */
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Get the elapsed time (in frames) */
|
|
|
|
elapsed = clock_systime_ticks() - start;
|
|
}
|
|
while (elapsed < MAX3421E_SETUP_DELAY);
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_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
|
|
*
|
|
* Assumptions:
|
|
* The SPI is not locked. This function is called only from the CTRLOUT
|
|
* interface and must manage the SPI lock itself. The lock, for example,
|
|
* must be relinquished before waiting.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int max3421e_ctrl_senddata(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan,
|
|
FAR uint8_t *buffer, unsigned int buflen)
|
|
{
|
|
int ret;
|
|
|
|
/* Save buffer information */
|
|
|
|
chan->in = false;
|
|
priv->xfrd = 0;
|
|
priv->buffer = buffer;
|
|
priv->buflen = buflen;
|
|
|
|
/* The MAX4321E sends fixed DATA0 and DATA1 PID tokens for the various
|
|
* stages of a CONTROL transfer, regardless of the setting of the internal
|
|
* data toggle.
|
|
*/
|
|
|
|
/* Set up for the wait BEFORE starting the transfer */
|
|
|
|
ret = max3421e_chan_waitsetup(priv, chan);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_DEVDISCONN3, 0);
|
|
return ret;
|
|
}
|
|
|
|
/* Start the transfer */
|
|
|
|
max3421e_lock(priv);
|
|
max3421e_send_start(priv, chan);
|
|
max3421e_unlock(priv);
|
|
|
|
/* Wait for the transfer to complete and return the result */
|
|
|
|
return max3421e_chan_wait(priv, chan);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_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
|
|
*
|
|
* Assumptions:
|
|
* The SPI is not locked. This function is called only from the CTRLIN
|
|
* interface and must manage the SPI lock itself. The lock, for example,
|
|
* must be relinquished before waiting.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int max3421e_ctrl_recvdata(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan,
|
|
FAR uint8_t *buffer, unsigned int buflen)
|
|
{
|
|
int ret;
|
|
|
|
/* Save buffer information */
|
|
|
|
chan->in = true;
|
|
priv->buffer = buffer;
|
|
priv->buflen = buflen;
|
|
priv->xfrd = 0;
|
|
|
|
/* The MAX4321E sends fixed DATA0 and DATA1 PID tokens for the various
|
|
* stages of a CONTROL transfer, regardless of the setting of the internal
|
|
* data toggle.
|
|
*/
|
|
|
|
/* Set up for the wait BEFORE starting the transfer */
|
|
|
|
ret = max3421e_chan_waitsetup(priv, chan);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_DEVDISCONN4, 0);
|
|
return ret;
|
|
}
|
|
|
|
/* Start the transfer */
|
|
|
|
max3421e_lock(priv);
|
|
max3421e_recv_start(priv, chan);
|
|
max3421e_unlock(priv);
|
|
|
|
/* Wait for the transfer to complete and return the result */
|
|
|
|
return max3421e_chan_wait(priv, chan);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_ctrl_sendstatus
|
|
*
|
|
* Description:
|
|
* Send status to complete the status phase of a CTRLIN transfer.
|
|
*
|
|
* Assumptions:
|
|
* The SPI is not locked. This function is called only from the CTRLIN
|
|
* interface and must manage the SPI lock itself. The lock, for example,
|
|
* must be relinquished before waiting.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int max3421e_ctrl_sendstatus(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan)
|
|
{
|
|
/* Set up the initial state of the transfer */
|
|
|
|
usbhost_vtrace2(MAX3421E_VTRACE2_SENDSTATUS, chan->chidx, priv->buflen);
|
|
|
|
priv->result = EBUSY;
|
|
priv->inflight = 0;
|
|
priv->buflen = 0;
|
|
priv->xfrd = 0;
|
|
|
|
/* Make sure the peripheral address is correct */
|
|
|
|
max3421e_lock(priv);
|
|
max3421e_putreg(priv, MAX3421E_USBHOST_PERADDR, chan->funcaddr);
|
|
|
|
/* Write the zero byte count to the SNDBC register */
|
|
|
|
max3421e_putreg(priv, MAX3421E_USBHOST_SNDBC, 0);
|
|
|
|
/* Send the HS-OUT token */
|
|
|
|
max3421e_putreg(priv, MAX3421E_USBHOST_HXFR,
|
|
USBHOST_HXFR_TOKEN_OUTHS | chan->epno);
|
|
|
|
/* Enable the HXFRDNIRQ to catch completion of the ZLP. */
|
|
|
|
priv->xfrtype = HXFRDN_SNDZLP;
|
|
max3421e_int_enable(priv, USBHOST_HIRQ_HXFRDNIRQ);
|
|
max3421e_unlock(priv);
|
|
|
|
/* Wait for the transfer to complete and return the result */
|
|
|
|
return max3421e_chan_wait(priv, chan);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_ctrl_recvstatus
|
|
*
|
|
* Description:
|
|
* Receive status to complete the status phase of a CTRLOUT transfer.
|
|
*
|
|
* Assumptions:
|
|
* The SPI is not locked. This function is called only from the CTRLOUT
|
|
* interface and must manage the SPI lock itself. The lock, for example,
|
|
* must be relinquished before waiting.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int max3421e_ctrl_recvstatus(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan)
|
|
{
|
|
max3421e_pktdump("Sending", priv->buffer, priv->buflen);
|
|
|
|
/* Set up the initial state of the transfer */
|
|
|
|
usbhost_vtrace2(MAX3421E_VTRACE2_RECVSTATUS, chan->chidx, priv->buflen);
|
|
|
|
priv->result = EBUSY;
|
|
priv->inflight = 0;
|
|
priv->buflen = 0;
|
|
priv->xfrd = 0;
|
|
|
|
/* Make sure the peripheral address is correct */
|
|
|
|
max3421e_lock(priv);
|
|
max3421e_putreg(priv, MAX3421E_USBHOST_PERADDR, chan->funcaddr);
|
|
|
|
/* Send the HS-IN token. */
|
|
|
|
max3421e_putreg(priv, MAX3421E_USBHOST_HXFR,
|
|
USBHOST_HXFR_TOKEN_INHS | chan->epno);
|
|
|
|
/* Enable the RCVFIFO interrupt to handle the completion/continuation
|
|
* of transfer. Enable the HXFRDNIRQ to catch NAKs and transfer
|
|
* errors.
|
|
*/
|
|
|
|
priv->xfrtype = HXFRDN_RCVFIFO;
|
|
max3421e_int_enable(priv, USBHOST_HIRQ_RCVDAVIRQ | USBHOST_HIRQ_HXFRDNIRQ);
|
|
max3421e_unlock(priv);
|
|
|
|
/* Wait for the transfer to complete and return the result */
|
|
|
|
return max3421e_chan_wait(priv, chan);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_in_setup
|
|
*
|
|
* Description:
|
|
* Initiate an IN transfer on an bulk, interrupt, or isochronous pipe.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int max3421e_in_setup(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan)
|
|
{
|
|
/* Set up for the transfer based on the direction and the endpoint type */
|
|
|
|
switch (chan->eptype)
|
|
{
|
|
default:
|
|
case USB_EP_ATTR_XFER_CONTROL: /* Control */
|
|
{
|
|
/* This kind of transfer on control endpoints other than EP0 are
|
|
* not currently supported
|
|
*/
|
|
|
|
return -ENOSYS;
|
|
}
|
|
|
|
case USB_EP_ATTR_XFER_ISOC: /* Isochronous */
|
|
{
|
|
/* Set up the IN DATA0 PID */
|
|
|
|
usbhost_vtrace2(MAX3421E_VTRACE2_ISOCIN,
|
|
chan->chidx, priv->buflen);
|
|
|
|
chan->toggles = USBHOST_HCTL_RCVTOG0 | USBHOST_HCTL_SNDTOG0;
|
|
}
|
|
break;
|
|
|
|
case USB_EP_ATTR_XFER_BULK: /* Bulk */
|
|
{
|
|
usbhost_vtrace2(MAX3421E_VTRACE2_BULKIN,
|
|
chan->chidx, priv->buflen);
|
|
}
|
|
break;
|
|
|
|
case USB_EP_ATTR_XFER_INT: /* Interrupt */
|
|
{
|
|
usbhost_vtrace2(MAX3421E_VTRACE2_INTRIN,
|
|
chan->chidx, priv->buflen);
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Start the transfer. */
|
|
|
|
max3421e_recv_start(priv, chan);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_out_setup
|
|
*
|
|
* Description:
|
|
* Initiate an OUT transfer on an bulk, interrupt, or isochronous pipe.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int max3421e_out_setup(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan)
|
|
{
|
|
/* Set up for the transfer based on the direction and the endpoint type */
|
|
|
|
switch (chan->eptype)
|
|
{
|
|
default:
|
|
case USB_EP_ATTR_XFER_CONTROL: /* Control */
|
|
{
|
|
/* This kind of transfer on control endpoints other than EP0 are
|
|
* not currently supported
|
|
*/
|
|
|
|
return -ENOSYS;
|
|
}
|
|
|
|
case USB_EP_ATTR_XFER_ISOC: /* Isochronous */
|
|
{
|
|
/* Set up the IN DATA0 PID */
|
|
|
|
usbhost_vtrace2(MAX3421E_VTRACE2_ISOCOUT,
|
|
chan->chidx, priv->buflen);
|
|
|
|
chan->toggles = USBHOST_HCTL_RCVTOG0 | USBHOST_HCTL_SNDTOG0;
|
|
}
|
|
break;
|
|
|
|
case USB_EP_ATTR_XFER_BULK: /* Bulk */
|
|
{
|
|
usbhost_vtrace2(MAX3421E_VTRACE2_BULKOUT,
|
|
chan->chidx, priv->buflen);
|
|
}
|
|
break;
|
|
|
|
case USB_EP_ATTR_XFER_INT: /* Interrupt */
|
|
{
|
|
usbhost_vtrace2(MAX3421E_VTRACE2_INTROUT,
|
|
chan->chidx, priv->buflen);
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Start the transfer */
|
|
|
|
max3421e_send_start(priv, chan);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_get_rcvfifo
|
|
*
|
|
* Description:
|
|
* Copy data from the RCVFIFO to the user-provided buffer.
|
|
*
|
|
* The RCVFIFO is double buffered. If another packet is available in the
|
|
* other buffer, the MAX3421E will immediately re-assert RCVDAVIRQ and we
|
|
* will catch that by interrupt handling logic.
|
|
*
|
|
* Assumptions:
|
|
* The SPI bus is locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint8_t max3421e_get_rcvfifo(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan)
|
|
{
|
|
uint16_t remaining;
|
|
uint8_t navail;
|
|
uint8_t nrcvd;
|
|
|
|
/* Get the number of bytes available in the RCVFIFO */
|
|
|
|
navail = max3421e_getreg(priv, MAX3421E_USBHOST_RCVBC);
|
|
|
|
/* Get the number that will fit into the user-provided buffer */
|
|
|
|
remaining = priv->buflen - priv->xfrd;
|
|
if (navail > remaining)
|
|
{
|
|
nrcvd = remaining;
|
|
}
|
|
else
|
|
{
|
|
nrcvd = navail;
|
|
}
|
|
|
|
/* Read the received data into the user-buffer. */
|
|
|
|
max3421e_recvblock(priv, MAX3421E_USBHOST_RCVFIFO,
|
|
priv->buffer + priv->xfrd, nrcvd);
|
|
|
|
/* Update the number of bytes transferred */
|
|
|
|
priv->xfrd += nrcvd;
|
|
|
|
/* Discard any byte remaining in the RCVFIFO */
|
|
|
|
/* REVISIT: Is this necessary? Or the MAX3421E automatically discard any
|
|
* unread data?
|
|
*/
|
|
|
|
#if 0
|
|
if (nrcvd < navail)
|
|
{
|
|
max3421e_discard()
|
|
}
|
|
#endif
|
|
|
|
return nrcvd;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_recv_restart
|
|
*
|
|
* Description:
|
|
* Start/Re-start the transfer on the selected IN or OUT channel.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void max3421e_recv_restart(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan)
|
|
{
|
|
/* Send the IN token. */
|
|
|
|
max3421e_putreg(priv, MAX3421E_USBHOST_HXFR,
|
|
USBHOST_HXFR_TOKEN_IN | chan->epno);
|
|
|
|
/* Enable the RCVFIFO interrupt to handle the completion/continuation
|
|
* of transfer. Enable the HXFRDNIRQ to catch NAKs and transfer
|
|
* errors.
|
|
*/
|
|
|
|
priv->xfrtype = HXFRDN_RCVFIFO;
|
|
max3421e_int_enable(priv, USBHOST_HIRQ_RCVDAVIRQ | USBHOST_HIRQ_HXFRDNIRQ);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_recv_continue
|
|
*
|
|
* Description:
|
|
* Continue the receive operation started by max3421e_recv_start(). This
|
|
* function is called from the interrupt handler worker when an interrupt
|
|
* indicates that new, incoming data is available in the RCVFIFO (RCVDAV)
|
|
*
|
|
* When the RCBBAV interrupt occurred, max3421e_irqwork() disabled that
|
|
* interrupt and called this function in order to handle the receipt of
|
|
* data
|
|
*
|
|
* Assumptions:
|
|
* The SPI bus is locked.
|
|
* The RCVDAV interrupt has been disabled.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void max3421e_recv_continue(FAR struct max3421e_usbhost_s *priv)
|
|
{
|
|
FAR struct max3421e_chan_s *chan;
|
|
uint8_t nrcvd;
|
|
int result;
|
|
|
|
DEBUGASSERT(priv != NULL && priv->waiter != NULL);
|
|
chan = priv->waiter;
|
|
|
|
/* Check the result of a transfer */
|
|
|
|
result = max3421e_transfer_status(priv);
|
|
if (result < 0)
|
|
{
|
|
/* Terminate the transfer on any error. */
|
|
|
|
max3421e_transfer_terminate(priv, chan, result);
|
|
return;
|
|
}
|
|
|
|
/* Transfer the data from the RCVFIFO to the user buffer */
|
|
|
|
nrcvd = max3421e_get_rcvfifo(priv, chan);
|
|
|
|
/* A partial or zero-length packet is an indication that the transfer
|
|
* completed early. Terminate the transfer in those cases OR if all
|
|
* of the requested data has been received
|
|
*/
|
|
|
|
if (nrcvd < chan->maxpacket || priv->xfrd >= priv->buflen)
|
|
{
|
|
max3421e_transfer_terminate(priv, chan, OK);
|
|
}
|
|
|
|
/* If not all of the data has been received, then setup to receive
|
|
* another packet.
|
|
*/
|
|
|
|
else
|
|
{
|
|
max3421e_recv_restart(priv, chan);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_recv_start
|
|
*
|
|
* Description:
|
|
* Start at transfer on the selected IN or OUT channel.
|
|
*
|
|
* Assumptions:
|
|
* The caller has the SPI locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void max3421e_recv_start(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan)
|
|
{
|
|
max3421e_pktdump("Sending", priv->buffer, priv->buflen);
|
|
|
|
/* Set up the initial state of the transfer */
|
|
|
|
usbhost_vtrace2(MAX3421E_VTRACE2_STARTTRANSFER2,
|
|
chan->chidx, priv->buflen);
|
|
|
|
priv->result = EBUSY;
|
|
priv->inflight = 0;
|
|
priv->xfrd = 0;
|
|
|
|
/* Make sure the peripheral address is correct */
|
|
|
|
max3421e_putreg(priv, MAX3421E_USBHOST_PERADDR, chan->funcaddr);
|
|
|
|
/* Start the transfer. */
|
|
|
|
max3421e_recv_restart(priv, chan);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_in_transfer
|
|
*
|
|
* Description:
|
|
* Transfer 'buflen' bytes into 'buffer' from an IN channel.
|
|
*
|
|
* Assumptions:
|
|
* The SPI is not locked. This function is called only from the TRANSFER
|
|
* interface and must manage the SPI lock itself. The lock, for example,
|
|
* must be relinquished before waiting.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static ssize_t max3421e_in_transfer(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan,
|
|
FAR uint8_t *buffer, size_t buflen)
|
|
{
|
|
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).
|
|
*/
|
|
|
|
priv->buffer = buffer;
|
|
priv->buflen = buflen;
|
|
priv->xfrd = 0;
|
|
xfrd = 0;
|
|
|
|
start = clock_systime_ticks();
|
|
while (priv->xfrd < priv->buflen)
|
|
{
|
|
/* Set up for the wait BEFORE starting the transfer */
|
|
|
|
ret = max3421e_chan_waitsetup(priv, chan);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_DEVDISCONN5, 0);
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
/* Set up for the transfer based on the direction and the endpoint */
|
|
|
|
max3421e_lock(priv);
|
|
ret = max3421e_in_setup(priv, chan);
|
|
max3421e_unlock(priv);
|
|
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_INSETUP_FAIL1, -ret);
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
/* Wait for the transfer to complete and get the result */
|
|
|
|
ret = max3421e_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_systime_ticks() - start;
|
|
if (elapsed >= MAX3421E_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 == USB_EP_ATTR_XFER_INT)
|
|
{
|
|
/* 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 necessary to assure the
|
|
* polling interval. It is used in other cases only to
|
|
* prevent the polling from consuming too much CPU
|
|
* bandwidth.
|
|
*
|
|
* 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(MAX3421E_TRACE1_TRANSFER_FAILED3, -ret);
|
|
|
|
/* Break out and return the error */
|
|
|
|
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 += priv->xfrd;
|
|
}
|
|
}
|
|
|
|
return xfrd;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_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 max3421e_in_next(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_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 OK? */
|
|
|
|
result = -(int)priv->result;
|
|
if (priv->xfrd < priv->buflen && result == OK)
|
|
{
|
|
/* Yes.. Set up for the next transfer based on the direction and the
|
|
* endpoint type
|
|
*/
|
|
|
|
ret = max3421e_in_setup(priv, chan);
|
|
if (ret >= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
usbhost_trace1(MAX3421E_TRACE1_INSETUP_FAIL2, -ret);
|
|
result = ret;
|
|
}
|
|
|
|
/* The transfer is complete, with or without an error */
|
|
|
|
usbhost_vtrace2(MAX3421E_VTRACE2_XFRCOMPLETE,
|
|
(unsigned int)ep, buflen);
|
|
|
|
/* Extract the callback information */
|
|
|
|
callback = priv->callback;
|
|
arg = priv->arg;
|
|
nbytes = priv->xfrd;
|
|
|
|
priv->callback = NULL;
|
|
priv->arg = NULL;
|
|
priv->xfrd = 0;
|
|
|
|
/* Then perform the callback */
|
|
|
|
if (result < 0)
|
|
{
|
|
nbytes = (ssize_t)result;
|
|
}
|
|
|
|
callback(arg, nbytes);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_in_asynch
|
|
*
|
|
* Description:
|
|
* Initiate the first of a sequence of asynchronous transfers.
|
|
*
|
|
* Assumptions:
|
|
* The SPI is not locked. This function is called only from the ASYNCH
|
|
* interface and must manage the SPI lock itself. The lock, for example,
|
|
* must be relinquished before waiting.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
static int max3421e_in_asynch(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_chan_s *chan,
|
|
FAR uint8_t *buffer, size_t buflen,
|
|
usbhost_asynch_t callback, FAR void *arg)
|
|
{
|
|
int ret;
|
|
|
|
/* Set up for the transfer BEFORE starting the first transfer */
|
|
|
|
priv->buffer = buffer;
|
|
priv->buflen = buflen;
|
|
priv->xfrd = 0;
|
|
|
|
ret = max3421e_chan_asynchsetup(priv, chan, callback, arg);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_ASYNCHSETUP_FAIL2, -ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Set up for the transfer based on the direction and the endpoint type */
|
|
|
|
max3421e_lock(priv);
|
|
ret = max3421e_in_setup(priv, chan);
|
|
max3421e_unlock(priv);
|
|
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_INSETUP_FAIL3, -ret);
|
|
}
|
|
|
|
/* And return with the transfer pending */
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_connect_event
|
|
*
|
|
* Description:
|
|
* Handle a connection event.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void max3421e_connect_event(FAR struct max3421e_usbhost_s *priv)
|
|
{
|
|
/* Were we previously disconnected? */
|
|
|
|
if (!priv->connected)
|
|
{
|
|
/* Yes.. then now we are connected */
|
|
|
|
usbhost_vtrace1(MAX3421E_VTRACE1_CONNECTED1, 0);
|
|
priv->connected = true;
|
|
priv->change = true;
|
|
DEBUGASSERT(priv->smstate == SMSTATE_DETACHED);
|
|
|
|
/* Notify any waiters */
|
|
|
|
priv->smstate = SMSTATE_ATTACHED;
|
|
if (priv->pscwait)
|
|
{
|
|
nxsem_post(&priv->pscsem);
|
|
priv->pscwait = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_disconnect_event
|
|
*
|
|
* Description:
|
|
* Handle a disconnection event.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void max3421e_disconnect_event(FAR struct max3421e_usbhost_s *priv)
|
|
{
|
|
/* Were we previously connected? */
|
|
|
|
if (priv->connected)
|
|
{
|
|
/* Yes.. then we no longer connected */
|
|
|
|
usbhost_vtrace1(MAX3421E_VTRACE1_DISCONNECTED1, 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;
|
|
|
|
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)
|
|
{
|
|
nxsem_post(&priv->pscsem);
|
|
priv->pscwait = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_connected
|
|
*
|
|
* Description:
|
|
* USB host port interrupt handler
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int max3421e_connected(FAR struct max3421e_usbhost_s *priv)
|
|
{
|
|
int ret;
|
|
|
|
/* Stop SOF generation and reset the bus */
|
|
|
|
max3421e_busreset(priv);
|
|
nxsig_sleep(1);
|
|
|
|
/* Check for low- or full-speed and restart SOF generation. */
|
|
|
|
ret = max3421e_startsof(priv);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_DEVDISCONN7, -ret);
|
|
return ret;
|
|
}
|
|
|
|
usbhost_vtrace1(MAX3421E_VTRACE1_CONNECTED3, 0);
|
|
|
|
/* Were we previously disconnected? */
|
|
|
|
max3421e_connect_event(priv);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_disconnected
|
|
*
|
|
* Description:
|
|
* USB disconnect detected interrupt handler
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void max3421e_disconnected(FAR struct max3421e_usbhost_s *priv)
|
|
{
|
|
usbhost_vtrace1(MAX3421E_VTRACE1_DISCONNECTED2, 0);
|
|
|
|
/* Disable the SOF generator */
|
|
|
|
max3421e_modifyreg(priv, MAX3421E_USBHOST_MODE, USBHOST_MODE_SOFKAENAB, 0);
|
|
|
|
/* Handle the disconnection event */
|
|
|
|
max3421e_disconnect_event(priv);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_irqwork
|
|
*
|
|
* Description:
|
|
* MAX3421E interrupt worker. Perform MAX3421E interrupt processing on the
|
|
* high priority work queue thread. Interrupts were disabled by the
|
|
* interrupt handler when the interrupt was received. This worker must
|
|
* re-enable MAX3421E interrupts when interrupt processing is complete.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void max3421e_irqwork(FAR void *arg)
|
|
{
|
|
FAR struct max3421e_usbhost_s *priv;
|
|
FAR const struct max3421e_lowerhalf_s *lower;
|
|
uint8_t pending;
|
|
int ret;
|
|
|
|
priv = (FAR struct max3421e_usbhost_s *)arg;
|
|
DEBUGASSERT(priv != NULL && priv->lower != NULL);
|
|
lower = (FAR const struct max3421e_lowerhalf_s *)priv->lower;
|
|
|
|
/* Get exclusive access to the SPI bus */
|
|
|
|
max3421e_lock(priv);
|
|
|
|
/* 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 = max3421e_int_status(priv);
|
|
priv->lower->acknowledge(lower);
|
|
|
|
/* Break out of the loop when there are no pending interrupts. */
|
|
|
|
if (pending == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
/* Possibilities:
|
|
*
|
|
* HXFRDNIRQ - Host Transfer Done Interrupt
|
|
* FRAMEIRQ - Frame Generator Interrupt
|
|
* CONNIRQ - Peripheral Connect/Disconnect Interrupt
|
|
* SUSDNIRQ - Suspend operation Done
|
|
* SNDBAVIRQ - SNDFIFO is available
|
|
* RCVDAVIRQ - RCVFIFO data available
|
|
* RSMREQIRQ - Remote Wakeup Interrupt
|
|
* BUSEVENTIRQ - Bus Reset or Bus Resume Interrupt
|
|
*
|
|
* Only CONNIRQ handled here.
|
|
*/
|
|
|
|
/* HXFRDNIRQ: Host transfer done interrupt */
|
|
|
|
if ((pending & USBHOST_HIRQ_HXFRDNIRQ) != 0 &&
|
|
(pending & USBHOST_HIRQ_SNDBAVIRQ) == 0 &&
|
|
(pending & USBHOST_HIRQ_RCVDAVIRQ) == 0)
|
|
{
|
|
int result;
|
|
|
|
/* Disable further SNDBAV (or HXFRDN) interrupts */
|
|
|
|
max3421e_int_disable(priv, USBHOST_HIRQ_HXFRDNIRQ);
|
|
|
|
/* Clear the pending HXFRDN interrupt */
|
|
|
|
max3421e_int_clear(priv, USBHOST_HIRQ_HXFRDNIRQ);
|
|
|
|
/* Check transfer status and terminate the transfer if any error
|
|
* occurred.
|
|
*/
|
|
|
|
result = max3421e_transfer_status(priv);
|
|
if (result < 0 || priv->xfrtype == HXFRDN_SETUP ||
|
|
priv->xfrtype == HXFRDN_SNDZLP)
|
|
{
|
|
FAR struct max3421e_chan_s *chan = priv->waiter;
|
|
DEBUGASSERT(chan != NULL);
|
|
|
|
max3421e_transfer_terminate(priv, chan, result);
|
|
}
|
|
}
|
|
|
|
/* CONNIRQ: Has a peripheral been connected or disconnected */
|
|
|
|
if ((pending & USBHOST_HIRQ_CONNIRQ) != 0)
|
|
{
|
|
/* Clear the pending CONNIRQ interrupt */
|
|
|
|
max3421e_int_clear(priv, USBHOST_HIRQ_CONNIRQ);
|
|
|
|
/* Check if a peripheral device has been connected */
|
|
|
|
ret = max3421e_connected(priv);
|
|
if (ret < 0)
|
|
{
|
|
/* No.. then a device must have been disconnected. */
|
|
|
|
max3421e_disconnected(priv);
|
|
}
|
|
}
|
|
|
|
/* SNDBAV: The SNDFIFO is available */
|
|
|
|
else if ((pending & USBHOST_HIRQ_SNDBAVIRQ) != 0)
|
|
{
|
|
/* Disable further SNDBAV (or HXFRDN) interrupts */
|
|
|
|
max3421e_int_disable(priv, USBHOST_HIRQ_SNDBAVIRQ |
|
|
USBHOST_HIRQ_HXFRDNIRQ);
|
|
|
|
/* Clear the pending SNDBAV interrupt */
|
|
|
|
max3421e_int_clear(priv, USBHOST_HIRQ_SNDBAVIRQ);
|
|
|
|
/* Finish long transfer, possibly re-enabling the SNDBAV
|
|
* interrupt (see max3421e_send_start)
|
|
*/
|
|
|
|
max3421e_send_continue(priv);
|
|
}
|
|
|
|
/* RCVDAVIRQ: RCVFIFO data available */
|
|
|
|
else if ((pending & USBHOST_HIRQ_RCVDAVIRQ) != 0)
|
|
{
|
|
/* Disable further RCVDAV (or HXFRDN) interrupts */
|
|
|
|
max3421e_int_disable(priv, USBHOST_HIRQ_RCVDAVIRQ |
|
|
USBHOST_HIRQ_HXFRDNIRQ);
|
|
|
|
/* Clear the pending RCVDAV interrupt. The RCVFIFO is double
|
|
* buffered. If another packet is available in the other buffer,
|
|
* the MAX3421E will immediately re-assert RCVDAVIRQ and we
|
|
* will catch that the next time through this loop.
|
|
*/
|
|
|
|
max3421e_int_clear(priv, USBHOST_HIRQ_RCVDAVIRQ);
|
|
|
|
/* Handle the receipt of data */
|
|
|
|
max3421e_recv_continue(priv);
|
|
}
|
|
}
|
|
|
|
/* Re-enable interrupts */
|
|
|
|
lower->enable(lower, true);
|
|
max3421e_unlock(priv);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_interrupt
|
|
*
|
|
* Description:
|
|
* MAX3421E interrupt handler. This interrupt handler simply defers
|
|
* interrupt processing to the high priority work queue thread. This is
|
|
* necessary because we cannot perform interrupt/DMA driven SPI accesses
|
|
* from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int max3421e_interrupt(int irq, FAR void *context, FAR void *arg)
|
|
{
|
|
FAR struct max3421e_usbhost_s *priv;
|
|
FAR const struct max3421e_lowerhalf_s *lower;
|
|
|
|
priv = (FAR struct max3421e_usbhost_s *)arg;
|
|
DEBUGASSERT(priv != NULL && priv->lower != NULL);
|
|
lower = (FAR const struct max3421e_lowerhalf_s *)priv->lower;
|
|
|
|
/* Disable further interrupts until work associated with this interrupt
|
|
* has been processed.
|
|
*/
|
|
|
|
lower->enable(lower, false);
|
|
|
|
/* And defer interrupt processing to the high priority work queue thread */
|
|
|
|
work_queue(LPWORK, &priv->irqwork, max3421e_irqwork, priv, 0);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_int_enable, max3421e_int_disable, max3421e_int_status, and
|
|
* max3421e_int_wait
|
|
*
|
|
* Description:
|
|
* Respectively enable, disable, or get status of the USB host interrupt
|
|
* (HIRQ) and a mask of enabled interrupts.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Private state data
|
|
* irqset - IRQ bits to be set (max3421e_int_status only)
|
|
*
|
|
* Returned Value:
|
|
* The current unmasks interrupt status (max3421e_int_status only)
|
|
*
|
|
****************************************************************************/
|
|
|
|
/* Enable a set of interrupts */
|
|
|
|
static inline void max3421e_int_enable(FAR struct max3421e_usbhost_s *priv,
|
|
uint8_t irqset)
|
|
{
|
|
priv->irqset |= irqset;
|
|
max3421e_putreg(priv, MAX3421E_USBHOST_HIEN, priv->irqset);
|
|
}
|
|
|
|
/* Disable a set of interrupts */
|
|
|
|
static inline void max3421e_int_disable(FAR struct max3421e_usbhost_s *priv,
|
|
uint8_t irqset)
|
|
{
|
|
priv->irqset &= ~irqset;
|
|
max3421e_putreg(priv, MAX3421E_USBHOST_HIEN, priv->irqset);
|
|
}
|
|
|
|
/* Get the set of pending interrupts */
|
|
|
|
static inline uint8_t
|
|
max3421e_int_status(FAR struct max3421e_usbhost_s *priv)
|
|
{
|
|
return max3421e_getreg(priv, MAX3421E_USBHOST_HIRQ) & priv->irqset;
|
|
}
|
|
|
|
/* Clear a set of pending interrupts */
|
|
|
|
static inline void max3421e_int_clear(FAR struct max3421e_usbhost_s *priv,
|
|
uint8_t irqset)
|
|
{
|
|
max3421e_putreg(priv, MAX3421E_USBHOST_HIRQ, irqset);
|
|
}
|
|
|
|
/* Wait until any interrupt from a set of interrupts occurs */
|
|
|
|
static void max3421e_int_wait(FAR struct max3421e_usbhost_s *priv,
|
|
uint8_t irqset, unsigned int usec)
|
|
{
|
|
uint8_t regval;
|
|
|
|
do
|
|
{
|
|
regval = max3421e_getreg(priv, MAX3421E_USBHOST_HIRQ);
|
|
regval &= irqset;
|
|
|
|
if (regval == 0 && usec > 0)
|
|
{
|
|
nxsig_usleep(usec);
|
|
}
|
|
}
|
|
while (regval == 0);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_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 max3421e_wait(FAR struct usbhost_connection_s *conn,
|
|
FAR struct usbhost_hubport_s **hport)
|
|
{
|
|
FAR struct max3421e_connection_s *maxconn;
|
|
FAR struct max3421e_usbhost_s *priv;
|
|
struct usbhost_hubport_s *connport;
|
|
int ret;
|
|
|
|
maxconn = (FAR struct max3421e_connection_s *)conn;
|
|
DEBUGASSERT(maxconn != NULL && maxconn->priv != NULL);
|
|
priv = maxconn->priv;
|
|
|
|
/* Loop until a change in connection state is detected */
|
|
|
|
for (; ; )
|
|
{
|
|
/* We must have exclusive access to USB host hardware and structures */
|
|
|
|
ret = nxrmutex_lock(&priv->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
usbhost_vtrace1(MAX3421E_VTRACE1_CONNECTED2, connport->connected);
|
|
|
|
nxrmutex_unlock(&priv->lock);
|
|
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;
|
|
|
|
usbhost_vtrace1(MAX3421E_VTRACE1_HUB_CONNECTED,
|
|
connport->connected);
|
|
|
|
nxrmutex_unlock(&priv->lock);
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
/* Wait for the next connection event */
|
|
|
|
priv->pscwait = true;
|
|
nxrmutex_unlock(&priv->lock);
|
|
ret = nxsem_wait_uninterruptible(&priv->pscsem);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_getspeed
|
|
*
|
|
* Description:
|
|
* Get the speed of the connected device.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Driver private state structure
|
|
* 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:
|
|
* The caller has the SPI bus locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int max3421e_getspeed(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct usbhost_connection_s *conn,
|
|
FAR struct usbhost_hubport_s *hport)
|
|
{
|
|
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.
|
|
*/
|
|
|
|
if (!priv->connected)
|
|
{
|
|
/* No, return an error */
|
|
|
|
usbhost_trace1(MAX3421E_TRACE1_DEVDISCONN6, 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);
|
|
|
|
/* Make sure we are still connected */
|
|
|
|
if (!priv->connected)
|
|
{
|
|
/* No, return an error */
|
|
|
|
usbhost_trace1(MAX3421E_TRACE1_DEVDISCONN6, 0);
|
|
nxrmutex_unlock(&priv->lock);
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Stop SOF generation and reset the host port */
|
|
|
|
max3421e_busreset(priv);
|
|
nxsig_sleep(1);
|
|
|
|
/* Get the current device speed */
|
|
|
|
ret = max3421e_startsof(priv);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_DEVDISCONN8, -ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_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 max3421e_enumerate(FAR struct usbhost_connection_s *conn,
|
|
FAR struct usbhost_hubport_s *hport)
|
|
{
|
|
FAR struct max3421e_connection_s *maxconn;
|
|
FAR struct max3421e_usbhost_s *priv;
|
|
int ret;
|
|
|
|
maxconn = (FAR struct max3421e_connection_s *)conn;
|
|
DEBUGASSERT(maxconn != NULL && maxconn->priv != NULL);
|
|
priv = maxconn->priv;
|
|
|
|
/* We must have exclusive access to the USB host hardware and structures */
|
|
|
|
ret = nxrmutex_lock(&priv->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* 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.
|
|
*/
|
|
|
|
max3421e_lock(priv);
|
|
|
|
#warning REVISIT: Isn't this already done?
|
|
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
if (ROOTHUB(hport))
|
|
#endif
|
|
{
|
|
ret = max3421e_getspeed(priv, conn, hport);
|
|
if (ret < 0)
|
|
{
|
|
nxrmutex_unlock(&priv->lock);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* Set enumeration data toggles */
|
|
|
|
max3421e_modifyreg(priv, MAX3421E_USBHOST_HCTL,
|
|
USBHOST_HCTL_TOGGLES_MASK,
|
|
USBHOST_HCTL_RCVTOG1 | USBHOST_HCTL_SNDTOG1);
|
|
max3421e_unlock(priv);
|
|
|
|
/* Then let the common usbhost_enumerate do the real enumeration. */
|
|
|
|
usbhost_vtrace1(MAX3421E_VTRACE1_ENUMERATE, 0);
|
|
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 */
|
|
|
|
usbhost_trace1(MAX3421E_TRACE1_ENUMERATE_FAIL, -ret);
|
|
max3421e_disconnect_event(priv);
|
|
}
|
|
|
|
/* Set post-enumeration data toggles (assuming success) */
|
|
|
|
max3421e_lock(priv);
|
|
max3421e_modifyreg(priv, MAX3421E_USBHOST_HCTL,
|
|
USBHOST_HCTL_TOGGLES_MASK,
|
|
USBHOST_HCTL_RCVTOG0 | USBHOST_HCTL_SNDTOG0);
|
|
max3421e_unlock(priv);
|
|
|
|
nxrmutex_unlock(&priv->lock);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_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 or _FULL
|
|
* 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 max3421e_ep0configure(FAR struct usbhost_driver_s *drvr,
|
|
usbhost_ep_t ep0,
|
|
uint8_t funcaddr, uint8_t speed,
|
|
uint16_t maxpacketsize)
|
|
{
|
|
FAR struct max3421e_usbhost_s *priv =
|
|
(FAR struct max3421e_usbhost_s *)drvr;
|
|
FAR struct max3421e_chan_s *chan;
|
|
int ret;
|
|
|
|
DEBUGASSERT(drvr != NULL && funcaddr < 128 && maxpacketsize <= 64);
|
|
|
|
/* We must have exclusive access to the USB host hardware and structures */
|
|
|
|
ret = nxrmutex_lock(&priv->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Configure the EP0 channel */
|
|
|
|
chan = &priv->chan[(intptr_t)ep0];
|
|
chan->funcaddr = funcaddr;
|
|
chan->speed = speed;
|
|
chan->maxpacket = maxpacketsize;
|
|
chan->toggles = USBHOST_HCTL_RCVTOG0 | USBHOST_HCTL_SNDTOG0;
|
|
|
|
nxrmutex_unlock(&priv->lock);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_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 max3421e_epalloc(FAR struct usbhost_driver_s *drvr,
|
|
FAR const struct usbhost_epdesc_s *epdesc,
|
|
FAR usbhost_ep_t *ep)
|
|
{
|
|
FAR struct max3421e_usbhost_s *priv =
|
|
(FAR struct max3421e_usbhost_s *)drvr;
|
|
struct usbhost_hubport_s *hport;
|
|
FAR struct max3421e_chan_s *chan;
|
|
int chidx;
|
|
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);
|
|
hport = epdesc->hport;
|
|
DEBUGASSERT(hport != NULL);
|
|
|
|
/* We must have exclusive access to the USB host hardware and structures */
|
|
|
|
ret = nxrmutex_lock(&priv->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Allocate a host channel for the endpoint */
|
|
|
|
chidx = max3421e_chan_alloc(priv);
|
|
if (chidx < 0)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_CHANALLOC_FAIL, -chidx);
|
|
nxrmutex_unlock(&priv->lock);
|
|
return chidx;
|
|
}
|
|
|
|
/* 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->toggles = USBHOST_HCTL_RCVTOG0 | USBHOST_HCTL_SNDTOG0;
|
|
|
|
/* Return the endpoint number as the endpoint "handle" */
|
|
|
|
*ep = (usbhost_ep_t)chidx;
|
|
nxrmutex_unlock(&priv->lock);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_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 max3421e_epfree(FAR struct usbhost_driver_s *drvr,
|
|
usbhost_ep_t ep)
|
|
{
|
|
FAR struct max3421e_usbhost_s *priv =
|
|
(FAR struct max3421e_usbhost_s *)drvr;
|
|
int ret;
|
|
|
|
DEBUGASSERT(priv);
|
|
|
|
/* We must have exclusive access to the USB host hardware and structures */
|
|
|
|
ret = nxrmutex_lock(&priv->lock);
|
|
if (ret >= 0)
|
|
{
|
|
/* Halt the channel and mark the channel available */
|
|
|
|
max3421e_chan_free(priv, (intptr_t)ep);
|
|
nxrmutex_unlock(&priv->lock);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_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 max3421e_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 MAX3421E. */
|
|
|
|
alloc = (FAR uint8_t *)kmm_malloc(CONFIG_MAX3421E_DESCSIZE);
|
|
if (!alloc)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Return the allocated address and size of the descriptor buffer */
|
|
|
|
*buffer = alloc;
|
|
*maxlen = CONFIG_MAX3421E_DESCSIZE;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_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 max3421e_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: max3421e_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 max3421e_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: max3421e_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 max3421e_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: max3421e_ctrlin and max3421e_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 max3421e_ctrlin(FAR struct usbhost_driver_s *drvr,
|
|
usbhost_ep_t ep0,
|
|
FAR const struct usb_ctrlreq_s *req,
|
|
FAR uint8_t *buffer)
|
|
{
|
|
FAR struct max3421e_usbhost_s *priv;
|
|
FAR struct max3421e_chan_s *chan;
|
|
uint16_t buflen;
|
|
clock_t start;
|
|
clock_t elapsed;
|
|
int retries;
|
|
int ret;
|
|
|
|
DEBUGASSERT(drvr != NULL && req != NULL);
|
|
priv = (FAR struct max3421e_usbhost_s *)drvr;
|
|
|
|
DEBUGASSERT((intptr_t)ep0 >= 0 && (intptr_t)ep0 < MAX3421E_NHOST_CHANNELS);
|
|
chan = &priv->chan[(intptr_t)ep0];
|
|
|
|
usbhost_vtrace2(MAX3421E_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 = max3421e_getle16(req->len);
|
|
|
|
/* We must have exclusive access to the USB host hardware and structures */
|
|
|
|
ret = nxrmutex_lock(&priv->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Loop, retrying until the retry time expires */
|
|
|
|
for (retries = 0; retries < MAX3421E_RETRY_COUNT; retries++)
|
|
{
|
|
/* Send the SETUP request */
|
|
|
|
ret = max3421e_ctrl_sendsetup(priv, chan, req);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_SENDSETUP_FAIL2, -ret);
|
|
continue;
|
|
}
|
|
|
|
/* Get the start time. Loop again until the timeout expires */
|
|
|
|
start = clock_systime_ticks();
|
|
do
|
|
{
|
|
/* Handle the IN data phase (if any) */
|
|
|
|
/* The MAX4321E sends fixed DATA0 and DATA1 PID tokens for the
|
|
* various stages of a CONTROL transfer, regardless of the
|
|
* setting of the internal data toggle.
|
|
*/
|
|
|
|
if (buflen > 0)
|
|
{
|
|
ret = max3421e_ctrl_recvdata(priv, chan, buffer, buflen);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_RECVDATA_FAIL, -ret);
|
|
}
|
|
}
|
|
|
|
/* Handle the status OUT phase */
|
|
|
|
if (ret >= OK)
|
|
{
|
|
ret = max3421e_ctrl_sendstatus(priv, chan);
|
|
if (ret >= OK)
|
|
{
|
|
/* All success transactions exit here */
|
|
|
|
nxrmutex_unlock(&priv->lock);
|
|
return OK;
|
|
}
|
|
|
|
usbhost_trace1(MAX3421E_TRACE1_SENDSTATUS_FAIL, -ret);
|
|
}
|
|
|
|
/* Get the elapsed time (in frames) */
|
|
|
|
elapsed = clock_systime_ticks() - start;
|
|
}
|
|
while (elapsed < MAX3421E_DATANAK_DELAY);
|
|
}
|
|
|
|
/* All failures exit here after all retries and timeouts are exhausted */
|
|
|
|
nxrmutex_unlock(&priv->lock);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static int max3421e_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 max3421e_usbhost_s *priv =
|
|
(FAR struct max3421e_usbhost_s *)drvr;
|
|
FAR struct max3421e_chan_s *chan;
|
|
uint16_t buflen;
|
|
clock_t start;
|
|
clock_t elapsed;
|
|
int retries;
|
|
int ret;
|
|
|
|
DEBUGASSERT(drvr != NULL && req != NULL);
|
|
priv = (FAR struct max3421e_usbhost_s *)drvr;
|
|
|
|
DEBUGASSERT((intptr_t)ep0 >= 0 && (intptr_t)ep0 < MAX3421E_NHOST_CHANNELS);
|
|
chan = &priv->chan[(intptr_t)ep0];
|
|
|
|
usbhost_vtrace2(MAX3421E_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 = max3421e_getle16(req->len);
|
|
|
|
/* We must have exclusive access to the USB host hardware and structures */
|
|
|
|
ret = nxrmutex_lock(&priv->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Loop, retrying until the retry time expires */
|
|
|
|
for (retries = 0; retries < MAX3421E_RETRY_COUNT; retries++)
|
|
{
|
|
/* Send the SETUP request */
|
|
|
|
ret = max3421e_ctrl_sendsetup(priv, chan, req);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_SENDSETUP_FAIL1, -ret);
|
|
continue;
|
|
}
|
|
|
|
/* Get the start time. Loop again until the timeout expires */
|
|
|
|
start = clock_systime_ticks();
|
|
do
|
|
{
|
|
/* Handle the data OUT phase (if any) */
|
|
|
|
/* The MAX4321E sends fixed DATA0 and DATA1 PID tokens for the
|
|
* various stages of a CONTROL transfer, regardless of the setting
|
|
* of the internal data toggle.
|
|
*/
|
|
|
|
if (buflen > 0)
|
|
{
|
|
/* Start DATA out transfer (only one DATA packet) */
|
|
|
|
ret = max3421e_ctrl_senddata(priv, chan, NULL, 0);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_SENDDATA_FAIL, -ret);
|
|
}
|
|
}
|
|
|
|
/* Handle the status IN phase */
|
|
|
|
if (ret >= OK)
|
|
{
|
|
ret = max3421e_ctrl_recvstatus(priv, chan);
|
|
if (ret >= OK)
|
|
{
|
|
/* All success transactions exit here */
|
|
|
|
nxrmutex_unlock(&priv->lock);
|
|
return OK;
|
|
}
|
|
|
|
usbhost_trace1(MAX3421E_TRACE1_RECVSTATUS_FAIL, -ret);
|
|
}
|
|
|
|
/* Get the elapsed time (in frames) */
|
|
|
|
elapsed = clock_systime_ticks() - start;
|
|
}
|
|
while (elapsed < MAX3421E_DATANAK_DELAY);
|
|
}
|
|
|
|
/* All failures exit here after all retries and timeouts are exhausted */
|
|
|
|
nxrmutex_unlock(&priv->lock);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_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 max3421e_transfer(FAR struct usbhost_driver_s *drvr,
|
|
usbhost_ep_t ep, FAR uint8_t *buffer,
|
|
size_t buflen)
|
|
{
|
|
FAR struct max3421e_usbhost_s *priv =
|
|
(FAR struct max3421e_usbhost_s *)drvr;
|
|
FAR struct max3421e_chan_s *chan;
|
|
ssize_t nbytes;
|
|
int ret;
|
|
|
|
DEBUGASSERT(priv != NULL && buffer != NULL && buflen > 0);
|
|
DEBUGASSERT((intptr_t)ep >= 0 && (intptr_t)ep < MAX3421E_NHOST_CHANNELS);
|
|
chan = &priv->chan[(intptr_t)ep];
|
|
|
|
usbhost_vtrace2(MAX3421E_VTRACE2_TRANSFER, (unsigned int)ep, buflen);
|
|
|
|
/* We must have exclusive access to the USB host hardware and structures */
|
|
|
|
ret = nxrmutex_lock(&priv->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Handle IN and OUT transfer differently */
|
|
|
|
if (chan->in)
|
|
{
|
|
nbytes = max3421e_in_transfer(priv, chan, buffer, buflen);
|
|
}
|
|
else
|
|
{
|
|
nbytes = max3421e_out_transfer(priv, chan, buffer, buflen);
|
|
}
|
|
|
|
nxrmutex_unlock(&priv->lock);
|
|
return nbytes;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_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 max3421e_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 max3421e_usbhost_s *priv =
|
|
(FAR struct max3421e_usbhost_s *)drvr;
|
|
FAR struct max3421e_chan_s *chan;
|
|
int ret;
|
|
|
|
DEBUGASSERT(priv != NULL && buffer != NULL && buflen > 0);
|
|
DEBUGASSERT((intptr_t)ep >= 0 && ep < MAX3421E_NHOST_CHANNELS);
|
|
chan = &priv->chan[(intptr_t)ep];
|
|
|
|
usbhost_vtrace2(MAX3421E_VTRACE2_ASYNCH, (unsigned int)ep, buflen);
|
|
|
|
/* We must have exclusive access to the USB host hardware and structures */
|
|
|
|
ret = nxrmutex_lock(&priv->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Handle IN and OUT transfer slightly differently */
|
|
|
|
if (chan->in)
|
|
{
|
|
ret = max3421e_in_asynch(priv, chan, buffer, buflen, callback, arg);
|
|
}
|
|
else
|
|
{
|
|
ret = max3421e_out_asynch(priv, chan, buffer, buflen, callback, arg);
|
|
}
|
|
|
|
nxrmutex_unlock(&priv->lock);
|
|
return ret;
|
|
}
|
|
#endif /* CONFIG_USBHOST_ASYNCH */
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_cancel
|
|
*
|
|
* Description:
|
|
* Cancel a pending transfer on an endpoint. Canceled 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 max3421e_cancel(FAR struct usbhost_driver_s *drvr,
|
|
usbhost_ep_t ep)
|
|
{
|
|
FAR struct max3421e_usbhost_s *priv =
|
|
(FAR struct max3421e_usbhost_s *)drvr;
|
|
FAR struct max3421e_chan_s *chan;
|
|
irqstate_t flags;
|
|
|
|
DEBUGASSERT(priv != NULL);
|
|
DEBUGASSERT((intptr_t)ep >= 0 && (intptr_t)ep < MAX3421E_NHOST_CHANNELS);
|
|
chan = &priv->chan[(intptr_t)ep];
|
|
|
|
usbhost_vtrace1(MAX3421E_VTRACE1_CANCEL, (intptr_t)ep);
|
|
|
|
/* We need to disable interrupts to avoid race conditions with the
|
|
* asynchronous completion of the transfer being canceled.
|
|
*/
|
|
|
|
flags = enter_critical_section();
|
|
|
|
/* Halt the channel */
|
|
#warning Missing logic
|
|
UNUSED(chan); /* For now */
|
|
|
|
priv->result = -ESHUTDOWN;
|
|
|
|
/* Is there a thread waiting for this transfer to complete? */
|
|
|
|
if (priv->waiter != NULL)
|
|
{
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
/* Yes.. there should not also be a callback scheduled */
|
|
|
|
DEBUGASSERT(priv->callback == NULL);
|
|
#endif
|
|
|
|
/* Wake'em up! */
|
|
|
|
nxsem_post(&priv->waitsem);
|
|
priv->waiter = NULL;
|
|
}
|
|
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
/* No.. is an asynchronous callback expected when the transfer
|
|
* completes?
|
|
*/
|
|
|
|
else if (priv->callback)
|
|
{
|
|
usbhost_asynch_t callback;
|
|
FAR void *arg;
|
|
|
|
/* Extract the callback information */
|
|
|
|
callback = priv->callback;
|
|
arg = priv->arg;
|
|
|
|
priv->callback = NULL;
|
|
priv->arg = NULL;
|
|
priv->xfrd = 0;
|
|
|
|
/* Then perform the callback */
|
|
|
|
callback(arg, -ESHUTDOWN);
|
|
}
|
|
#endif
|
|
|
|
leave_critical_section(flags);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_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 max3421e_connect(FAR struct usbhost_driver_s *drvr,
|
|
FAR struct usbhost_hubport_s *hport,
|
|
bool connected)
|
|
{
|
|
FAR struct max3421e_usbhost_s *priv =
|
|
(FAR struct max3421e_usbhost_s *)drvr;
|
|
irqstate_t flags;
|
|
|
|
DEBUGASSERT(priv != NULL && hport != NULL);
|
|
|
|
/* Set the connected/disconnected flag */
|
|
|
|
hport->connected = connected;
|
|
usbhost_vtrace2(MAX3421E_VTRACE2_HUB_CONNECTED, hport->port, connected);
|
|
|
|
/* Report the connection event */
|
|
|
|
flags = enter_critical_section();
|
|
priv->hport = hport;
|
|
if (priv->pscwait)
|
|
{
|
|
priv->pscwait = false;
|
|
nxsem_post(&priv->pscsem);
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_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 max3421e_disconnect(FAR struct usbhost_driver_s *drvr,
|
|
FAR struct usbhost_hubport_s *hport)
|
|
{
|
|
DEBUGASSERT(hport != NULL);
|
|
hport->devclass = NULL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Initialization
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_busreset
|
|
*
|
|
* Description:
|
|
* Reset the USB host port.
|
|
*
|
|
* Input Parameters:
|
|
* priv -- USB host driver private data structure.
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void max3421e_busreset(FAR struct max3421e_usbhost_s *priv)
|
|
{
|
|
/* Disable the SOF generator */
|
|
|
|
max3421e_modifyreg(priv, MAX3421E_USBHOST_MODE, USBHOST_MODE_SOFKAENAB, 0);
|
|
|
|
/* Clear any pending bus event */
|
|
|
|
max3421e_int_clear(priv, USBHOST_HIRQ_BUSEVENTIRQ);
|
|
|
|
/* Perform the reset */
|
|
|
|
max3421e_modifyreg(priv, MAX3421E_USBHOST_HCTL, 0, USBHOST_HCTL_BUSRST);
|
|
max3421e_int_wait(priv, USBHOST_HIRQ_BUSEVENTIRQ, 250);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_startsof
|
|
*
|
|
* Description:
|
|
* Called after bus reset. Determine bus speed and restart SOFs.
|
|
*
|
|
* Input Parameters:
|
|
* priv -- USB host driver private data structure.
|
|
*
|
|
* Returned Value:
|
|
* OK if successfully connect; -ENODEV if not connected.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int max3421e_startsof(FAR struct max3421e_usbhost_s *priv)
|
|
{
|
|
uint8_t clrbits;
|
|
uint8_t setbits;
|
|
uint8_t regval;
|
|
bool lowspeed;
|
|
|
|
/* Check if we are already in low- or full-speed mode */
|
|
|
|
regval = max3421e_getreg(priv, MAX3421E_USBHOST_MODE);
|
|
lowspeed = ((regval & USBHOST_MODE_SPEED) != 0);
|
|
|
|
/* Enable SAMPLEBUS */
|
|
|
|
max3421e_modifyreg(priv, MAX3421E_USBHOST_HCTL, 0,
|
|
USBHOST_HCTL_BUSSAMPLE);
|
|
|
|
while ((max3421e_getreg(priv, MAX3421E_USBHOST_HCTL) &
|
|
USBHOST_HCTL_BUSSAMPLE) == 0)
|
|
{
|
|
nxsig_usleep(5);
|
|
}
|
|
|
|
/* Check for low- or full-speed and start SOF (actually already started
|
|
* by max3421e_busreset).
|
|
*/
|
|
|
|
clrbits = 0;
|
|
setbits = USBHOST_MODE_HOST | USBHOST_MODE_SOFKAENAB |
|
|
USBHOST_MODE_DMPULLD | USBHOST_MODE_DPPULLDN;
|
|
|
|
regval = max3421e_getreg(priv, MAX3421E_USBHOST_HRSL);
|
|
switch (regval & (USBHOST_HRSL_KSTATUS | USBHOST_HRSL_JSTATUS))
|
|
{
|
|
default:
|
|
case (USBHOST_HRSL_KSTATUS | USBHOST_HRSL_JSTATUS):
|
|
|
|
/* Invalid state */
|
|
|
|
usbhost_trace1(MAX3421E_TRACE1_BAD_JKSTATE, regval);
|
|
|
|
/* Fall through */
|
|
|
|
case 0:
|
|
|
|
/* 0: Not connected */
|
|
|
|
return -ENODEV;
|
|
|
|
case USBHOST_HRSL_KSTATUS:
|
|
|
|
/* J=0, K=1: low-speed in full-speed (or vice versa) */
|
|
|
|
if (lowspeed)
|
|
{
|
|
/* Full speed in low speed */
|
|
|
|
clrbits |= USBHOST_MODE_SPEED;
|
|
}
|
|
else
|
|
{
|
|
/* Low speed in full speed */
|
|
|
|
setbits |= USBHOST_MODE_SPEED;
|
|
}
|
|
break;
|
|
|
|
case USBHOST_HRSL_JSTATUS:
|
|
|
|
/* J=1,K=0: full-speed in full-speed (or vice versa) */
|
|
|
|
if (lowspeed)
|
|
{
|
|
/* Low speed in low speed */
|
|
|
|
setbits |= USBHOST_MODE_SPEED;
|
|
}
|
|
else
|
|
{
|
|
/* Full speed in full speed */
|
|
|
|
clrbits |= USBHOST_MODE_SPEED;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Restart the SOF generator */
|
|
|
|
max3421e_modifyreg(priv, MAX3421E_USBHOST_MODE, clrbits, setbits);
|
|
|
|
/* Wait for the first SOF received and 20ms has passed */
|
|
|
|
max3421e_int_wait(priv, USBHOST_HIRQ_FRAMEIRQ, 0);
|
|
nxsig_usleep(20 * 1000);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_sw_initialize
|
|
*
|
|
* Description:
|
|
* One-time setup of the host driver state structure.
|
|
*
|
|
* Input Parameters:
|
|
* priv -- USB host driver private data structure.
|
|
* conn -- Custom USB host connection structure.
|
|
* lower -- The lower half driver instance.
|
|
*
|
|
* Returned Value:
|
|
* None.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int max3421e_sw_initialize(FAR struct max3421e_usbhost_s *priv,
|
|
FAR struct max3421e_connection_s *conn,
|
|
FAR const struct max3421e_lowerhalf_s *lower)
|
|
{
|
|
FAR struct usbhost_driver_s *drvr;
|
|
FAR struct usbhost_hubport_s *hport;
|
|
int ret;
|
|
int i;
|
|
|
|
/* Initialize the device operations */
|
|
|
|
drvr = &priv->drvr;
|
|
drvr->ep0configure = max3421e_ep0configure;
|
|
drvr->epalloc = max3421e_epalloc;
|
|
drvr->epfree = max3421e_epfree;
|
|
drvr->alloc = max3421e_alloc;
|
|
drvr->free = max3421e_free;
|
|
drvr->ioalloc = max3421e_ioalloc;
|
|
drvr->iofree = max3421e_iofree;
|
|
drvr->ctrlin = max3421e_ctrlin;
|
|
drvr->ctrlout = max3421e_ctrlout;
|
|
drvr->transfer = max3421e_transfer;
|
|
#ifdef CONFIG_USBHOST_ASYNCH
|
|
drvr->asynch = max3421e_asynch;
|
|
#endif
|
|
drvr->cancel = max3421e_cancel;
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
drvr->connect = max3421e_connect;
|
|
#endif
|
|
drvr->disconnect = max3421e_disconnect;
|
|
|
|
/* Initialize the public port representation */
|
|
|
|
hport = &priv->rhport.hport;
|
|
hport->drvr = drvr;
|
|
#ifdef CONFIG_USBHOST_HUB
|
|
hport->parent = NULL;
|
|
#endif
|
|
hport->ep0 = 0;
|
|
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->waitsem, 0, 0);
|
|
|
|
/* Initialize lock */
|
|
|
|
nxrmutex_init(&priv->lock);
|
|
|
|
/* Initialize the driver state data */
|
|
|
|
priv->lower = lower;
|
|
priv->smstate = SMSTATE_DETACHED;
|
|
priv->connected = false;
|
|
priv->irqset = 0;
|
|
priv->change = false;
|
|
priv->holder = NO_HOLDER;
|
|
|
|
/* Put all of the channels back in their initial, allocated state */
|
|
|
|
memset(priv->chan, 0,
|
|
MAX3421E_NHOST_CHANNELS * sizeof(struct max3421e_chan_s));
|
|
|
|
/* Initialize each channel */
|
|
|
|
for (i = 0; i < MAX3421E_NHOST_CHANNELS; i++)
|
|
{
|
|
FAR struct max3421e_chan_s *chan = &priv->chan[i];
|
|
|
|
chan->chidx = i;
|
|
}
|
|
|
|
/* Initialize the connection structure */
|
|
|
|
conn->conn.wait = max3421e_wait;
|
|
conn->conn.enumerate = max3421e_enumerate;
|
|
conn->priv = priv;
|
|
|
|
/* Attach USB host controller interrupt handler */
|
|
|
|
ret = lower->attach(lower, max3421e_interrupt, priv);
|
|
if (ret < 0)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_IRQATTACH_FAIL, 0);
|
|
return ret;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_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 max3421e_hw_initialize(FAR struct max3421e_usbhost_s *priv)
|
|
{
|
|
uint8_t revision;
|
|
uint8_t regval;
|
|
int ret;
|
|
|
|
/* Get exclusive access to the SPI bus */
|
|
|
|
max3421e_lock(priv);
|
|
|
|
/* Configure full duplex SPI, level or edge-active, rising- or falling
|
|
* edge interrupt.
|
|
*
|
|
* NOTE: Initially, the MAX3421E operations in half-duplex mode. MISO is
|
|
* tristated and there is no status response to commands. Writes are not
|
|
* effected: The MISO pin continues to be high impedance and the master
|
|
* continues to drive MOSI.
|
|
*
|
|
* For reads, however, after the 8-bit command, the max3421e starts driving
|
|
* the MOSI pin. The master must turn off its driver to the MOSI pin to
|
|
* avoid contention.
|
|
*/
|
|
|
|
regval = priv->lower->intconfig;
|
|
regval &= (USBHOST_PINCTL_INTLEVEL | USBHOST_PINCTL_POSINT);
|
|
regval |= USBHOST_PINCTL_FDUPSPI;
|
|
max3421e_putreg(priv, MAX3421E_USBHOST_PINCTL, regval);
|
|
|
|
/* Reset the MAX3421E by toggling the CHIPRES bit in the USBCTRL register.
|
|
*
|
|
* NOTE: The bits that control the SPI interface are not changed by
|
|
* CHIPRES: FDUPSPI, INTLEVEL, and POSINT.
|
|
*/
|
|
|
|
max3421e_putreg(priv, MAX3421E_USBHOST_USBCTL, USBHOST_USBCTL_CHIPRES);
|
|
max3421e_putreg(priv, MAX3421E_USBHOST_USBCTL, 0);
|
|
|
|
/* Wait for the oscillator to become stable */
|
|
|
|
while ((max3421e_getreg(priv, MAX3421E_USBHOST_USBIRQ) &
|
|
USBHOST_USBIRQ_OSCOKIRQ) == 0)
|
|
{
|
|
}
|
|
|
|
/* Disable interrupts, clear pending interrupts, and reset the interrupt
|
|
* state.
|
|
*/
|
|
|
|
max3421e_modifyreg(priv, MAX3421E_USBHOST_CPUCTL, USBHOST_CPUCTL_IE, 0);
|
|
max3421e_putreg(priv, MAX3421E_USBHOST_HIEN, 0);
|
|
max3421e_int_clear(priv, 0xff);
|
|
priv->irqset = 0;
|
|
|
|
/* Configure as full-speed USB host */
|
|
|
|
max3421e_modifyreg(priv, MAX3421E_USBHOST_MODE,
|
|
USBHOST_MODE_SPEED | USBHOST_MODE_SOFKAENAB,
|
|
USBHOST_MODE_HOST | USBHOST_MODE_DMPULLD |
|
|
USBHOST_MODE_DPPULLDN);
|
|
|
|
/* Clear and enable the connection detected (CONDIRQ) interrupt */
|
|
|
|
max3421e_int_clear(priv, USBHOST_HIRQ_CONNIRQ);
|
|
max3421e_int_enable(priv, USBHOST_HIRQ_CONNIRQ);
|
|
|
|
/* Enable MAX3412E interrupts */
|
|
|
|
max3421e_modifyreg(priv, MAX3421E_USBHOST_CPUCTL, 0, USBHOST_CPUCTL_IE);
|
|
|
|
usbhost_vtrace1(MAX3421E_VTRACE1_INITIALIZED, 0);
|
|
|
|
revision = max3421e_getreg(priv, MAX3421E_USBHOST_REVISION);
|
|
if (revision != USBHOST_REVISION)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_BADREVISION, revision);
|
|
max3421e_unlock(priv);
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Perform a bus reset to reconnect after a power down */
|
|
|
|
ret = max3421e_connected(priv);
|
|
if (ret < 0)
|
|
{
|
|
/* Nothing connected. */
|
|
|
|
max3421e_disconnected(priv);
|
|
}
|
|
|
|
max3421e_unlock(priv);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: max3421e_usbhost_initialize
|
|
*
|
|
* Description:
|
|
* Initialize MAX3421E as USB host controller.
|
|
*
|
|
* Input Parameters:
|
|
* lower - The interface to the lower half driver
|
|
*
|
|
* 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 *
|
|
max3421e_usbhost_initialize(FAR const struct max3421e_lowerhalf_s *lower)
|
|
{
|
|
FAR struct usbhost_alloc_s *alloc;
|
|
FAR struct max3421e_usbhost_s *priv;
|
|
FAR struct max3421e_connection_s *conn;
|
|
int ret;
|
|
|
|
DEBUGASSERT(lower != NULL && lower->spi != NULL && lower->attach != NULL &&
|
|
lower->attach != NULL && lower->acknowledge != NULL &&
|
|
lower->power != NULL);
|
|
|
|
/* Allocate and instance of the MAX4321E state structure */
|
|
|
|
alloc = (FAR struct usbhost_alloc_s *)
|
|
kmm_zalloc(sizeof(struct usbhost_alloc_s));
|
|
|
|
if (alloc < 0)
|
|
{
|
|
usbhost_trace1(MAX3421E_TRACE1_ALLOC_FAIL, 0);
|
|
return NULL;
|
|
}
|
|
|
|
priv = &alloc->priv;
|
|
conn = &alloc->conn;
|
|
|
|
/* Initialize the state of the host driver */
|
|
|
|
ret = max3421e_sw_initialize(priv, conn, lower);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_alloc;
|
|
}
|
|
|
|
/* Initialize the MAX3421E, putting it into full operational state. */
|
|
|
|
ret = max3421e_hw_initialize(priv);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_alloc;
|
|
}
|
|
|
|
/* Drive Vbus +5V (the smoke test). */
|
|
|
|
lower->power(lower, true);
|
|
|
|
/* Enable host interrupts */
|
|
|
|
lower->enable(lower, true);
|
|
return &conn->conn;
|
|
|
|
errout_with_alloc:
|
|
kmm_free(alloc);
|
|
return NULL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef HAVE_USBHOST_TRACE
|
|
FAR const char *usbhost_trformat1(uint16_t id)
|
|
{
|
|
int ndx = TRACE1_INDEX(id);
|
|
|
|
if (ndx < TRACE1_NSTRINGS)
|
|
{
|
|
return g_trace1[ndx].string;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
FAR const char *usbhost_trformat2(uint16_t id)
|
|
{
|
|
int ndx = TRACE2_INDEX(id);
|
|
|
|
if (ndx < TRACE2_NSTRINGS)
|
|
{
|
|
return g_trace2[ndx].string;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
#endif /* CONFIG_USBHOST_MAX3421E */
|