From 75f7663427c5d64718eb5232785ec2ca116a678a Mon Sep 17 00:00:00 2001 From: Gregory Nutt Date: Tue, 3 Jul 2018 18:49:51 -0600 Subject: [PATCH] This commit adds support for the Maxim M3421E USB host driver. Squashed commit of the following: drivers/usbhost/usbhost_max3421e.c: Add USB tracing support. Fix compilation errors when assertions and debug is enabled. drivers/usbhost/usbhost_max3421e.c: Fixes to get a clean compilation. drivers/usbhost/usbhost_max3421e.c: Drivers is basically code complete. drivers/usbhost/usbhost_max3421e.c: Missed a little bit of logic in the last commit. drivers/usbhost/usbhost_max3421e.c: Completes implementatin of control transfers. drivers/usbhost/usbhost_max3421e.c: Implements low-level part of packet receive. drivers/usbhost/usbhost_max3421e.c: Reorder some functions add a little more transfer-related logic. drivers/usbhost/usbhost_max3421e.c: Completes basic logic path for sending normal packets. drivers/usbhost/usbhost_max3421e.c: Correct handling of SNDFIFO double buffering. drivers/usbhost/usbhost_max3421e.c: Not necessary to set the ACKSTAT bit in host mode. Clean up some comments. drivers/usbhost/usbhost_max3421e.c: Mostly cosmetic cleanup drivers/usbhost/usbhost_max3421e.c: Revise some previous logic. Looks like the MAX3421E can handle 16 channels in host mode. A little bit of work on packet transfer logic. Copy paste error fix drivers/usbhost/usbhost_max3421e.c: Add some channel allocation logic. drivers/usbhost/usbhost_max3421e.c: Add some initialization logic. drivers/usbhost/usbhost_max3421e.c: Add logic to determine if a full or low speed device has been connected. drivers/usbhost/usbhost_max3421e.c: Add interrupt handling and bus reset logic. drivers/usbhost/usbhost_max3421e.c: Add framework for an MAX3421E host driver. Initial commit is just the STM32 OTGFS host driver with a few new SPI-related functions. --- drivers/usbhost/Kconfig | 35 + drivers/usbhost/Make.defs | 6 +- drivers/usbhost/usbhost_max3421e.c | 4700 ++++++++++++++++++++++++++++ include/nuttx/usb/max3421e.h | 138 +- 4 files changed, 4852 insertions(+), 27 deletions(-) create mode 100644 drivers/usbhost/usbhost_max3421e.c diff --git a/drivers/usbhost/Kconfig b/drivers/usbhost/Kconfig index b2cadb3812..9829051f6c 100644 --- a/drivers/usbhost/Kconfig +++ b/drivers/usbhost/Kconfig @@ -553,6 +553,41 @@ config XBOXCONTROLLER_NPOLLWAITERS endif # USBHOST_XBOXCONTROLLER +config USBHOST_MAX3421E + bool "Maxim MAX3421E FS host controller" + default n + select SPI + select SCHED_LPWORK + depends on EXPERIMENTAL + ---help--- + Enable support for the Maxim MAX3421E FS host controller + +if USBHOST_MAX3421E + +config MAX3421E_DESCSIZE + int "Max descriptor size" + default 128 + ---help--- + Maximum size of a descriptor. Default: 128 + +config MAX3421E_USBHOST_REGDEBUG + bool "MAX3421 register debug" + default n + depends on DEBUG_USB_INFO + ---help--- + Enable very low-level register access debug. Depends on + CONFIG_DEBUG_USB_INFO. + +config MAX3421E_USBHOST_PKTDUMP + bool "MAX3421 packet dump" + default n + depends on DEBUG_USB_INFO + ---help--- + Dump all incoming and outgoing USB packets. Depends on + CONFIG_DEBUG_USB_INFO. + +endif # USBHOST_MAX3421E + config USBHOST_TRACE bool "Enable USB HCD tracing for debug" default n diff --git a/drivers/usbhost/Make.defs b/drivers/usbhost/Make.defs index 105156aee3..2e723ce7b6 100644 --- a/drivers/usbhost/Make.defs +++ b/drivers/usbhost/Make.defs @@ -1,7 +1,7 @@ ############################################################################ # drivers/usbhost/Make.defs # -# Copyright (C) 2010-2015 Gregory Nutt. All rights reserved. +# Copyright (C) 2010-2015, 2018 Gregory Nutt. All rights reserved. # Author: Gregory Nutt # # Redistribution and use in source and binary forms, with or without @@ -70,6 +70,10 @@ ifeq ($(CONFIG_USBHOST_XBOXCONTROLLER),y) CSRCS += usbhost_xboxcontroller.c endif +ifeq ($(CONFIG_USBHOST_MAX3421E),y) +CSRCS += usbhost_max3421e.c +endif + # HCD debug/trace logic ifeq ($(CONFIG_USBHOST_TRACE),y) diff --git a/drivers/usbhost/usbhost_max3421e.c b/drivers/usbhost/usbhost_max3421e.c new file mode 100644 index 0000000000..a012bdf87f --- /dev/null +++ b/drivers/usbhost/usbhost_max3421e.c @@ -0,0 +1,4700 @@ +/**************************************************************************** + * drivers/usbhost/usbhost_max3421e.c + * + * Copyright (C) 2018 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * 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). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 */ + +/* Ever-present MIN/MAX macros */ + +#ifndef MIN +# define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef MAX +# define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +/* 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 */ + 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) */ + sem_t exclsem; /* 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, + MAX3421E_TRACE1_ASYNCHSETUP_FAIL1, + MAX3421E_TRACE1_ASYNCHSETUP_FAIL2, + 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_ENUMERATE_FAIL, + MAX3421E_TRACE1_INSETUP_FAIL1, + MAX3421E_TRACE1_INSETUP_FAIL2, + MAX3421E_TRACE1_INSETUP_FAIL3, + MAX3421E_TRACE1_INT_DISCONNECTED1, + MAX3421E_TRACE1_INT_DISCONNECTED2, + MAX3421E_TRACE1_IRQATTACH_FAIL, + MAX3421E_TRACE1_OUTSETUP_FAIL1, + MAX3421E_TRACE1_OUTSETUP_FAIL2, + MAX3421E_TRACE1_OUTSETUP_FAIL3, + MAX3421E_TRACE1_RECVDATA_FAIL1, + MAX3421E_TRACE1_RECVDATA_FAIL2, + MAX3421E_TRACE1_SENDDATA_FAIL1, + MAX3421E_TRACE1_SENDDATA_FAIL2, + MAX3421E_TRACE1_SENDSETUP_FAIL1, + MAX3421E_TRACE1_SENDSETUP_FAIL2, + 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_DISCONNECTED, + MAX3421E_VTRACE1_ENUMERATE, + MAX3421E_VTRACE1_HUB_CONNECTED, + MAX3421E_VTRACE1_INITIALIZED, + MAX3421E_VTRACE1_INT_CONNECTED, + MAX3421E_VTRACE1_INT_DISCONNECTED, + MAX3421E_VTRACE1_TRANSFER_COMPLETE, + +#endif + + __TRACE1_NSTRINGS, /* Separates the format 1 from the format 2 strings */ + +#ifdef HAVE_USBHOST_TRACE_VERBOSE + MAX3421E_VTRACE2_ASYNCH, + MAX3421E_VTRACE2_BULKIN, + MAX3421E_VTRACE2_BULKOUT, + MAX3421E_VTRACE2_CHANWAKEUP_IN, + MAX3421E_VTRACE2_CHANWAKEUP_OUT, + MAX3421E_VTRACE2_CTRLIN, + MAX3421E_VTRACE2_CTRLOUT, + MAX3421E_VTRACE2_HUB_CONNECTED, + 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, + MAX3421E_VTRACE2_XFRCOMPLETE, +#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 + +/* Semaphores ******************************************************************/ + +static void max3421e_takesem(sem_t *sem); +#define max3421e_givesem(s) nxsem_post(s); + +/* 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, int chidx, + 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); + +/* 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 FAR 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 +/* TODO: Improve format strings so that they are self-explanatory. */ + +static const struct max3421e_usbhost_trace_s g_trace1[TRACE1_NSTRINGS] = +{ + TRENTRY(MAX3421E_TRACE1_ALLOC_FAIL, TR_FMT1, "MAX3421E_TRACE1_ALLOC_FAIL: %u\n"), + TRENTRY(MAX3421E_TRACE1_ASYNCHSETUP_FAIL1, TR_FMT1, "MAX3421E_TRACE1_ASYNCHSETUP_FAIL1: %u\n"), + TRENTRY(MAX3421E_TRACE1_ASYNCHSETUP_FAIL2, TR_FMT1, "MAX3421E_TRACE1_ASYNCHSETUP_FAIL2: %u\n"), + TRENTRY(MAX3421E_TRACE1_BAD_JKSTATE, TR_FMT1, "MAX3421E_TRACE1_BAD_JKSTATE: %u\n"), + TRENTRY(MAX3421E_TRACE1_BADREVISION, TR_FMT1, "MAX3421E_TRACE1_BADREVISION: %02x\n"), + TRENTRY(MAX3421E_TRACE1_CHANALLOC_FAIL, TR_FMT1, "MAX3421E_TRACE1_CHANALLOC_FAIL: %u\n"), + TRENTRY(MAX3421E_TRACE1_CHANWAIT_FAIL, TR_FMT1, "MAX3421E_TRACE1_CHANWAIT_FAIL: %u\n"), + TRENTRY(MAX3421E_TRACE1_DEVDISCONN1, TR_FMT1, "MAX3421E_TRACE1_DEVDISCONN1: %u\n"), + TRENTRY(MAX3421E_TRACE1_DEVDISCONN2, TR_FMT1, "MAX3421E_TRACE1_DEVDISCONN2: %u\n"), + TRENTRY(MAX3421E_TRACE1_DEVDISCONN3, TR_FMT1, "MAX3421E_TRACE1_DEVDISCONN3: %u\n"), + TRENTRY(MAX3421E_TRACE1_DEVDISCONN4, TR_FMT1, "MAX3421E_TRACE1_DEVDISCONN4: %u\n"), + TRENTRY(MAX3421E_TRACE1_DEVDISCONN5, TR_FMT1, "MAX3421E_TRACE1_DEVDISCONN5: %u\n"), + TRENTRY(MAX3421E_TRACE1_DEVDISCONN6, TR_FMT1, "MAX3421E_TRACE1_DEVDISCONN6: %u\n"), + TRENTRY(MAX3421E_TRACE1_ENUMERATE_FAIL, TR_FMT1, "MAX3421E_TRACE1_ENUMERATE_FAIL: %u\n"), + TRENTRY(MAX3421E_TRACE1_INSETUP_FAIL1, TR_FMT1, "MAX3421E_TRACE1_INSETUP_FAIL1: %u\n"), + TRENTRY(MAX3421E_TRACE1_INSETUP_FAIL2, TR_FMT1, "MAX3421E_TRACE1_INSETUP_FAIL2: %u\n"), + TRENTRY(MAX3421E_TRACE1_INSETUP_FAIL3, TR_FMT1, "MAX3421E_TRACE1_INSETUP_FAIL3: %u\n"), + TRENTRY(MAX3421E_TRACE1_INT_DISCONNECTED1, TR_FMT1, "MAX3421E_TRACE1_INT_DISCONNECTED1: %u\n"), + TRENTRY(MAX3421E_TRACE1_INT_DISCONNECTED2, TR_FMT1, "MAX3421E_TRACE1_INT_DISCONNECTED2: %u\n"), + TRENTRY(MAX3421E_TRACE1_IRQATTACH_FAIL, TR_FMT1, "MAX3421E_TRACE1_IRQATTACH_FAIL: %u\n"), + TRENTRY(MAX3421E_TRACE1_OUTSETUP_FAIL1, TR_FMT1, "MAX3421E_TRACE1_OUTSETUP_FAIL1: %u\n"), + TRENTRY(MAX3421E_TRACE1_OUTSETUP_FAIL2, TR_FMT1, "MAX3421E_TRACE1_OUTSETUP_FAIL2: %u\n"), + TRENTRY(MAX3421E_TRACE1_OUTSETUP_FAIL3, TR_FMT1, "MAX3421E_TRACE1_OUTSETUP_FAIL3: %u\n"), + TRENTRY(MAX3421E_TRACE1_RECVDATA_FAIL1, TR_FMT1, "MAX3421E_TRACE1_RECVDATA_FAIL1: %u\n"), + TRENTRY(MAX3421E_TRACE1_RECVDATA_FAIL2, TR_FMT1, "MAX3421E_TRACE1_RECVDATA_FAIL2: %u\n"), + TRENTRY(MAX3421E_TRACE1_SENDDATA_FAIL1, TR_FMT1, "MAX3421E_TRACE1_SENDDATA_FAIL1: %u\n"), + TRENTRY(MAX3421E_TRACE1_SENDDATA_FAIL2, TR_FMT1, "MAX3421E_TRACE1_SENDDATA_FAIL2: %u\n"), + TRENTRY(MAX3421E_TRACE1_SENDSETUP_FAIL1, TR_FMT1, "MAX3421E_TRACE1_SENDSETUP_FAIL1: %u\n"), + TRENTRY(MAX3421E_TRACE1_SENDSETUP_FAIL2, TR_FMT1, "MAX3421E_TRACE1_SENDSETUP_FAIL2: %u\n"), + TRENTRY(MAX3421E_TRACE1_TRANSFER_FAILED1, TR_FMT1, "MAX3421E_TRACE1_TRANSFER_FAILED1: %u\n"), + TRENTRY(MAX3421E_TRACE1_TRANSFER_FAILED2, TR_FMT1, "MAX3421E_TRACE1_TRANSFER_FAILED2: %u\n"), + TRENTRY(MAX3421E_TRACE1_TRANSFER_FAILED3, TR_FMT1, "MAX3421E_TRACE1_TRANSFER_FAILED3: %u\n"), + +#ifdef HAVE_USBHOST_TRACE_VERBOSE + TRENTRY(MAX3421E_VTRACE1_CANCEL, TR_FMT1, "MAX3421E_VTRACE1_CANCEL: %u\n"), + TRENTRY(MAX3421E_VTRACE1_CONNECTED1, TR_FMT1, "MAX3421E_VTRACE1_CONNECTED1: %u\n"), + TRENTRY(MAX3421E_VTRACE1_CONNECTED2, TR_FMT1, "MAX3421E_VTRACE1_CONNECTED2: %u\n"), + TRENTRY(MAX3421E_VTRACE1_DISCONNECTED, TR_FMT1, "MAX3421E_VTRACE1_DISCONNECTED: %u\n"), + TRENTRY(MAX3421E_VTRACE1_ENUMERATE, TR_FMT1, "MAX3421E_VTRACE1_ENUMERATE: %u\n"), + TRENTRY(MAX3421E_VTRACE1_HUB_CONNECTED, TR_FMT1, "MAX3421E_VTRACE1_HUB_CONNECTED: %u\n"), + TRENTRY(MAX3421E_VTRACE1_INITIALIZED, TR_FMT1, "MAX3421E_VTRACE1_INITIALIZED: %u\n"), + TRENTRY(MAX3421E_VTRACE1_INT_CONNECTED, TR_FMT1, "MAX3421E_VTRACE1_INT_CONNECTED: %u\n"), + TRENTRY(MAX3421E_VTRACE1_INT_DISCONNECTED, TR_FMT1, "MAX3421E_VTRACE1_INT_DISCONNECTED: %u\n"), + TRENTRY(MAX3421E_VTRACE1_TRANSFER_COMPLETE, TR_FMT1, "MAX3421E_VTRACE1_TRANSFER_COMPLETE: %u\n"), +#endif +}; + +static const struct max3421e_usbhost_trace_s g_trace2[TRACE2_NSTRINGS] = +{ +#ifdef HAVE_USBHOST_TRACE_VERBOSE + TRENTRY(MAX3421E_VTRACE2_ASYNCH, TR_FMT2, "MAX3421E_VTRACE2_ASYNCH: %u, %u\n"), + TRENTRY(MAX3421E_VTRACE2_BULKIN, TR_FMT2, "MAX3421E_VTRACE2_BULKIN: %u, %u\n"), + TRENTRY(MAX3421E_VTRACE2_BULKOUT, TR_FMT2, "MAX3421E_VTRACE2_BULKOUT: %u, %u\n"), + TRENTRY(MAX3421E_VTRACE2_CHANWAKEUP_IN, TR_FMT2, "MAX3421E_VTRACE2_CHANWAKEUP_IN: %u, %u\n"), + TRENTRY(MAX3421E_VTRACE2_CHANWAKEUP_OUT, TR_FMT2, "MAX3421E_VTRACE2_CHANWAKEUP_OUT: %u, %u\n"), + TRENTRY(MAX3421E_VTRACE2_CTRLIN, TR_FMT2, "MAX3421E_VTRACE2_CTRLIN: %u, %u\n"), + TRENTRY(MAX3421E_VTRACE2_CTRLOUT, TR_FMT2, "MAX3421E_VTRACE2_CTRLOUT: %u, %u\n"), + TRENTRY(MAX3421E_VTRACE2_HUB_CONNECTED, TR_FMT2, "MAX3421E_VTRACE2_HUB_CONNECTED: %u, %u\n"), + TRENTRY(MAX3421E_VTRACE2_INTRIN, TR_FMT2, "MAX3421E_VTRACE2_INTRIN: %u, %u\n"), + TRENTRY(MAX3421E_VTRACE2_INTROUT, TR_FMT2, "MAX3421E_VTRACE2_INTROUT: %u, %u\n"), + TRENTRY(MAX3421E_VTRACE2_ISOCIN, TR_FMT2, "MAX3421E_VTRACE2_ISOCIN: %u, %u\n"), + TRENTRY(MAX3421E_VTRACE2_ISOCOUT, TR_FMT2, "MAX3421E_VTRACE2_ISOCOUT: %u, %u\n"), + TRENTRY(MAX3421E_VTRACE2_RECVSTATUS, TR_FMT2, "MAX3421E_VTRACE2_RECVSTATUS: %u, %u\n"), + TRENTRY(MAX3421E_VTRACE2_SENDSTATUS, TR_FMT2, "MAX3421E_VTRACE2_SENDSTATUS: %u, %u\n"), + TRENTRY(MAX3421E_VTRACE2_STARTTRANSFER1, TR_FMT2, "MAX3421E_VTRACE2_STARTTRANSFER1: %u, %u\n"), + TRENTRY(MAX3421E_VTRACE2_STARTTRANSFER2, TR_FMT2, "MAX3421E_VTRACE2_STARTTRANSFER2: %u, %u\n"), + TRENTRY(MAX3421E_VTRACE2_TRANSFER, TR_FMT2, "MAX3421E_VTRACE2_TRANSFER: %u, %u\n"), + TRENTRY(MAX3421E_VTRACE2_XFRCOMPLETE, TR_FMT2, "MAX3421E_VTRACE2_XFRCOMPLETE: %u, %u\n"), +#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); + spi = lower->spi; + + (void)SPI_LOCK(spi, true); + SPI_SETMODE(spi, lower->mode); + SPI_SETBITS(spi, 8); + (void)SPI_HWFEATURES(spi, 0); + SPI_SETFREQUENCY(spi, lower->frequency); +} + +/**************************************************************************** + * 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); + (void)SPI_LOCK(lower->spi, false); +} +/**************************************************************************** + * 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); + spi = lower->spi; + + /* Select the MAX4321E */ + + SPI_SELECT(spi, lower->devid, true); + + /* Send the read command byte */ + + cmd = max3421e_fmtcmd(priv, addr, MAX3421E_DIR_READ); + (void)SPI_SEND(spi, cmd); + + /* Read the value of the register */ + + value = SPI_SEND(spi, 0xff); + + /* De-select the MAX4321E */ + + SPI_SELECT(spi, 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); + 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, lower->devid, true); + + /* Send the write command byte */ + + cmd = max3421e_fmtcmd(priv, addr, MAX3421E_DIR_WRITE); + (void)SPI_SEND(spi, cmd); + + /* Send the new value for the register */ + + (void)SPI_SEND(spi, value); + + /* De-select the MAX4321E */ + + SPI_SELECT(spi, 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); + spi = lower->spi; + + /* Select the MAX4321E */ + + SPI_SELECT(spi, lower->devid, true); + + /* Send the read command byte */ + + cmd = max3421e_fmtcmd(priv, addr, MAX3421E_DIR_READ); + (void)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, 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); + 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, lower->devid, true); + + /* Send the wrte command byte */ + + cmd = max3421e_fmtcmd(priv, addr, MAX3421E_DIR_WRITE); + (void)SPI_SEND(spi, cmd); + + /* Send the new value for the register */ + + SPI_SNDBLOCK(spi, buffer, buflen); + + /* De-select the MAX4321E */ + + SPI_SELECT(spi, lower->devid, false); +} + +/**************************************************************************** + * Name: max3421e_takesem + * + * Description: + * This is just a wrapper to handle the annoying behavior of semaphore + * waits that return due to the receipt of a signal. + * + ****************************************************************************/ + +static void max3421e_takesem(sem_t *sem) +{ + int ret; + + do + { + /* Take the semaphore (perhaps waiting) */ + + ret = nxsem_wait(sem); + + /* The only case that an error should occur here is if the wait was + * awakened by a signal. + */ + + DEBUGASSERT(ret == OK || ret == -EINTR); + } + while (ret == -EINTR); +} + +/**************************************************************************** + * 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 to avoid transfer). + * We do this to minimize race conditions. This logic would have to be expanded + * if we want to have more than one packet in flight at a time! + * + * Assumptions: + * 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 the to avoid transfer). + * We do this to minimize race conditions. This logic would have to be expanded + * if we want to have more than one packet in flight at a time! + * + * Assumptions: + * Might be called from the level of an interrupt handler + * + ****************************************************************************/ + +#ifdef CONFIG_USBHOST_ASYNCH +static int 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(&priv->waitsem); + + /* nxsem_wait should succeed. But it is possible that we could be + * awakened by a signal too. + */ + + DEBUGASSERT(ret == OK || ret == -EINTR); + } + 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); + + max3421e_givesem(&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. + * + * 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: Current 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. + */ + + 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_getreg(priv, MAX3421E_USBHOST_HIRQ) & USBHOST_HIRQ_SNDBAVIRQ) == 1; + 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. + * + ****************************************************************************/ + +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_systimer(); + 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 type */ + + ret = max3421e_out_setup(priv, chan); + 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_systimer() - 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 and 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 complete 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: + * This function is never called from an interrupt handler + * + ****************************************************************************/ + +#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 data and callback 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 */ + + ret = max3421e_out_setup(priv, chan); + 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: + * Caller has the SPI locked + * + ****************************************************************************/ + +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_systimer(); + 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_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 */ + + 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_systimer() - 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: + * Caller has the SPI locked + * + ****************************************************************************/ + +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_send_start(priv, chan); + + /* 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: + * Caller has the SPI locked + * + ****************************************************************************/ + +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_recv_start(priv, chan); + + /* 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. + * + ****************************************************************************/ + +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_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); + + /* 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. + * + ****************************************************************************/ + +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_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); + + /* 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. + * + ****************************************************************************/ + +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. + * + ****************************************************************************/ + +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_systimer(); + 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 type */ + + ret = max3421e_in_setup(priv, chan); + 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_systimer() - 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 complete 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: + * This function is never called from an interrupt handler + * + ****************************************************************************/ + +#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 data and callback 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 */ + + ret = max3421e_in_setup(priv, chan); + 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) +{ + /* We 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) + { + max3421e_givesem(&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_DISCONNECTED, 0); + + /* Are we bound to a class driver? */ + + if (priv->rhport.hport.devclass) + { + /* Yes.. Disconnect the class driver */ + + CLASS_DISCONNECTED(priv->rhport.hport.devclass); + priv->rhport.hport.devclass = NULL; + } + + /* Re-Initialize Host for new Enumeration */ + + priv->smstate = SMSTATE_DETACHED; + priv->connected = false; + priv->change = true; + + 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) + { + max3421e_givesem(&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); + sleep(1); + + /* Check for low- or full-speed and restart SOF generation. */ + + ret = max3421e_startsof(priv); + if (ret < 0) + { + usbhost_trace1(MAX3421E_TRACE1_INT_DISCONNECTED1, -ret); + return ret; + } + + usbhost_vtrace1(MAX3421E_VTRACE1_INT_CONNECTED, 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_INT_DISCONNECTED, 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_unlock(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 further 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_putreg(priv, MAX3421E_USBHOST_HIRQ, + 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_putreg(priv, MAX3421E_USBHOST_HIRQ, + 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_putreg(priv, MAX3421E_USBHOST_HIRQ, + 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_putreg(priv, MAX3421E_USBHOST_HIRQ, + 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 */ + + (void)work_queue(LPWORK, &priv->irqwork, max3421e_irqwork, priv, 0); + return OK; +} + +/**************************************************************************** + * Name: max3421e_int_enable, max3421e_int_disable, and max3421e_int_status + * + * 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) + * + ****************************************************************************/ + +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); +} + +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); +} + +static inline uint8_t max3421e_int_status(FAR struct max3421e_usbhost_s *priv) +{ + return max3421e_getreg(priv, MAX3421E_USBHOST_HIEN) & priv->irqset; +} + +/**************************************************************************** + * 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 in 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; + irqstate_t flags; + + 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 */ + + flags = enter_critical_section(); + for (; ; ) + { + /* Is there a change in the connection state of the single root hub + * port? + */ + + if (priv->change) + { + connport = &priv->rhport.hport; + + /* Yes. Remember the new state */ + + connport->connected = priv->connected; + priv->change = false; + + /* And return the root hub port */ + + *hport = connport; + leave_critical_section(flags); + + usbhost_vtrace1(MAX3421E_VTRACE1_CONNECTED2, connport->connected); + return OK; + } + +#ifdef CONFIG_USBHOST_HUB + /* Is a device connected to an external hub? */ + + if (priv->hport) + { + /* Yes.. return the external hub port */ + + connport = (struct usbhost_hubport_s *)priv->hport; + priv->hport = NULL; + + *hport = connport; + leave_critical_section(flags); + + usbhost_vtrace1(MAX3421E_VTRACE1_HUB_CONNECTED, + connport->connected); + return OK; + } +#endif + + /* Wait for the next connection event */ + + priv->pscwait = true; + max3421e_takesem(&priv->pscsem); + } +} + +/**************************************************************************** + * 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: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +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. + */ + + while (!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); + + /* Stop SOF generation and reset the host port */ + + max3421e_busreset(priv); + sleep(1); + + /* Get the current device speed */ + + ret = max3421e_startsof(priv); + if (ret < 0) + { + usbhost_trace1(MAX3421E_TRACE1_INT_DISCONNECTED2, -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; + + /* 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. + */ +#warning REVISIT: Isnt this already done? + +#ifdef CONFIG_USBHOST_HUB + if (ROOTHUB(hport)) +#endif + { + ret = max3421e_getspeed(priv, conn, hport); + if (ret < 0) + { + return ret; + } + } + + /* Set enumeration data toggles */ + + max3421e_modifyreg(priv, MAX3421E_USBHOST_HCTL, + USBHOST_HCTL_TOGGLES_MASK, + USBHOST_HCTL_RCVTOG1 | USBHOST_HCTL_SNDTOG1); + + /* 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_modifyreg(priv, MAX3421E_USBHOST_HCTL, + USBHOST_HCTL_TOGGLES_MASK, + USBHOST_HCTL_RCVTOG0| USBHOST_HCTL_SNDTOG0); + 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; + + DEBUGASSERT(drvr != NULL && funcaddr < 128 && maxpacketsize <= 64); + + /* We must have exclusive access to the USB host hardware and state structures */ + + max3421e_takesem(&priv->exclsem); + + /* 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; + + max3421e_givesem(&priv->exclsem); + 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; + + /* 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 state structures */ + + max3421e_takesem(&priv->exclsem); + + /* Allocate a host channel for the endpoint */ + + chidx = max3421e_chan_alloc(priv); + if (chidx < 0) + { + usbhost_trace1(MAX3421E_TRACE1_CHANALLOC_FAIL, -chidx); + max3421e_givesem(&priv->exclsem); + 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; + max3421e_givesem(&priv->exclsem); + 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; + + DEBUGASSERT(priv); + + /* We must have exclusive access to the USB host hardware and state structures */ + + max3421e_takesem(&priv->exclsem); + + /* Halt the channel and mark the channel available */ + + max3421e_chan_free(priv, (intptr_t)ep); + max3421e_givesem(&priv->exclsem); + return OK; +} + +/**************************************************************************** + * 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 state structures */ + + max3421e_takesem(&priv->exclsem); + + /* Loop, retrying until the retry time expires */ + + for (retries = 0; retries < MAX3421E_RETRY_COUNT; retries++) + { + /* Send the SETUP request */ + + max3421e_lock(priv); + ret = max3421e_ctrl_sendsetup(priv, chan, req); + max3421e_unlock(priv); + + if (ret < 0) + { + usbhost_trace1(MAX3421E_TRACE1_SENDSETUP_FAIL2, -ret); + continue; + } + + /* Get the start time. Loop again until the timeout expires */ + + start = clock_systimer(); + 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) + { + max3421e_lock(priv); + ret = max3421e_ctrl_recvdata(priv, chan, buffer, buflen); + max3421e_unlock(priv); + + if (ret < 0) + { + usbhost_trace1(MAX3421E_TRACE1_RECVDATA_FAIL2, -ret); + } + } + + /* Handle the status OUT phase */ + + if (ret >= OK) + { + max3421e_lock(priv); + ret = max3421e_ctrl_sendstatus(priv, chan); + max3421e_unlock(priv); + + if (ret >= OK) + { + /* All success transactions exit here */ + + max3421e_givesem(&priv->exclsem); + return OK; + } + + usbhost_trace1(MAX3421E_TRACE1_SENDDATA_FAIL1, -ret); + } + + /* Get the elapsed time (in frames) */ + + elapsed = clock_systimer() - start; + } + while (elapsed < MAX3421E_DATANAK_DELAY); + } + + /* All failures exit here after all retries and timeouts have been exhausted */ + + max3421e_givesem(&priv->exclsem); + 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 state structures */ + + max3421e_takesem(&priv->exclsem); + + /* Loop, retrying until the retry time expires */ + + for (retries = 0; retries < MAX3421E_RETRY_COUNT; retries++) + { + /* Send the SETUP request */ + + max3421e_lock(priv); + ret = max3421e_ctrl_sendsetup(priv, chan, req); + max3421e_unlock(priv); + + if (ret < 0) + { + usbhost_trace1(MAX3421E_TRACE1_SENDSETUP_FAIL1, -ret); + continue; + } + + /* Get the start time. Loop again until the timeout expires */ + + start = clock_systimer(); + 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) */ + + max3421e_lock(priv); + ret = max3421e_ctrl_senddata(priv, chan, NULL, 0); + max3421e_unlock(priv); + + if (ret < 0) + { + usbhost_trace1(MAX3421E_TRACE1_SENDDATA_FAIL2, -ret); + } + } + + /* Handle the status IN phase */ + + if (ret >= OK) + { + max3421e_lock(priv); + ret = max3421e_ctrl_recvstatus(priv, chan); + max3421e_unlock(priv); + + if (ret >= OK) + { + /* All success transactions exit here */ + + max3421e_givesem(&priv->exclsem); + return OK; + } + + usbhost_trace1(MAX3421E_TRACE1_RECVDATA_FAIL1, -ret); + } + + /* Get the elapsed time (in frames) */ + + elapsed = clock_systimer() - start; + } + while (elapsed < MAX3421E_DATANAK_DELAY); + } + + /* All failures exit here after all retries and timeouts have been exhausted */ + + max3421e_givesem(&priv->exclsem); + 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; + + 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 state structures */ + + max3421e_takesem(&priv->exclsem); + + /* 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); + } + + max3421e_givesem(&priv->exclsem); + 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 state structures */ + + max3421e_takesem(&priv->exclsem); + + /* 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); + } + + max3421e_givesem(&priv->exclsem); + 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! */ + + max3421e_givesem(&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; + max3421e_givesem(&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); + + /* Perform the reset */ + + max3421e_modifyreg(priv, MAX3421E_USBHOST_HCTL, 0, USBHOST_HCTL_BUSRST); + while ((max3421e_getreg(priv, MAX3421E_USBHOST_HCTL) & MAX3421E_USBHOST_HCTL) == 0) + { + usleep(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) + { + 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, 0); + + /* 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 */ + + while ((max3421e_getreg(priv, MAX3421E_USBHOST_HIRQ) & + USBHOST_HIRQ_FRAMEIRQ) == 0) + { + } + + 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->exclsem, 0, 1); + nxsem_init(&priv->waitsem, 0, 0); + + /* The pscsem and waitsem semaphores are used for signaling and, hence, + * should not have + * priority inheritance enabled. + */ + + nxsem_setprotocol(&priv->pscsem, SEM_PRIO_NONE); + nxsem_setprotocol(&priv->waitsem, SEM_PRIO_NONE); + + /* Initialize the driver state data */ + + priv->lower = lower; + priv->smstate = SMSTATE_DETACHED; + priv->connected = false; + priv->irqset = 0; + priv->change = false; + + /* 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 harware 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); + + /* Reset the MAX3421E by toggling the CHIPRES bit in the USBCTRL register. */ + + 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_putreg(priv, MAX3421E_USBHOST_HIRQ, 0xff); + + priv->irqset = 0; + + /* Configure full duplex SPI, level or edge-active, rising- or falling + * edge interrupt. + */ + + regval = priv->lower->intconfig; + regval &= (USBHOST_PINCTL_INTLEVEL | USBHOST_PINCTL_POSINT); + regval |= USBHOST_PINCTL_FDUPSPI; + max3421e_putreg(priv, MAX3421E_USBHOST_PINCTL, regval); + + /* 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); + + /* Enable and clear the connection detected (CONDIRQ) interrupt */ + + max3421e_int_enable(priv, USBHOST_HIRQ_CONNIRQ); + max3421e_putreg(priv, MAX3421E_USBHOST_HIRQ, 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); + + /* Allocate and instance of the MAX4321E state structure */ + + alloc = (FAR struct usbhost_alloc_s *) + kmm_malloc(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; + } + + /* Enable interrupts at the interrupt controller */ + + 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 */ diff --git a/include/nuttx/usb/max3421e.h b/include/nuttx/usb/max3421e.h index 8af22df29b..5cf1bb75e7 100644 --- a/include/nuttx/usb/max3421e.h +++ b/include/nuttx/usb/max3421e.h @@ -4,6 +4,12 @@ * Copyright (C) 2018 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * + * 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). + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: @@ -49,6 +55,24 @@ * 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. + */ + /* Host Mode Register Addresses *********************************************/ /* The command byte contains the register address, a direction bit, and an * ACKSTAT bit: @@ -137,8 +161,14 @@ #define USBHOST_USBCTL_CHIPRES (1 << 5) #define USBHOST_CPUCTL_IE (1 << 0) -#define USBHOST_CPUCTL_PULSEWID0 (1 << 6) -#define USBHOST_CPUCTL_PULSEWID1 (1 << 7) +#define USBHOST_CPUCTL_PULSEWID_SHIFT (6) /* Bits 6-7: INT Pulsewidth */ +#define USBHOST_CPUCTL_PULSEWID_MASK (3 << USBHOST_CPUCTL_PULSEWID_SHIFT) +# define USBHOST_CPUCTL_PULSEWID0 (1 << 6) +# define USBHOST_CPUCTL_PULSEWID1 (1 << 7) +# define USBHOST_CPUCTL_PULSEWID_10p6US (0 << USBHOST_CPUCTL_PULSEWID_SHIFT) /* 10.6 uS */ +# define USBHOST_CPUCTL_PULSEWID_5p3US (1 << USBHOST_CPUCTL_PULSEWID_SHIFT) /* 5.3 uS */ +# define USBHOST_CPUCTL_PULSEWID_2p6US (2 << USBHOST_CPUCTL_PULSEWID_SHIFT) /* 2.6 uS */ +# define USBHOST_CPUCTL_PULSEWID_1p3US (4 << USBHOST_CPUCTL_PULSEWID_SHIFT) /* 1.3 uS */ #define USBHOST_PINCTL_PXA (1 << 0) #define USBHOST_PINCTL_GPXB (1 << 1) @@ -192,24 +222,48 @@ #define USBHOST_HCTL_FRMRST (1 << 1) #define USBHOST_HCTL_BUSSAMPLE (1 << 2) #define USBHOST_HCTL_SIGRSM (1 << 3) -#define USBHOST_HCTL_RCVTOG0 (1 << 4) -#define USBHOST_HCTL_RCVTOG1 (1 << 5) -#define USBHOST_HCTL_SNDTOG0 (1 << 6) -#define USBHOST_HCTL_SNDTOG1 (1 << 7) +#define USBHOST_HCTL_TOGGLES_SHIFT (4) /* Bits 4-7: Data toggles */ +#define USBHOST_HCTL_TOGGLES_MASK (15 << USBHOST_HCTL_TOGGLES_SHIFT) +# define USBHOST_HCTL_RCVTOG0 (1 << 4) +# define USBHOST_HCTL_RCVTOG1 (1 << 5) +# define USBHOST_HCTL_SNDTOG0 (1 << 6) +# define USBHOST_HCTL_SNDTOG1 (1 << 7) -#define USBHOST_HXFR_EP0 (1 << 0) -#define USBHOST_HXFR_EP1 (1 << 1) -#define USBHOST_HXFR_EP2 (1 << 2) -#define USBHOST_HXFR_EP3 (1 << 3) -#define USBHOST_HXFR_SETUP (1 << 4) -#define USBHOST_HXFR_OUTNIN (1 << 5) -#define USBHOST_HXFR_ISO (1 << 6) -#define USBHOST_HXFR_HS (1 << 7) +#define USBHOST_HXFR_EP_SHIFT (0) /* Bits 0-3: Endpoint number */ +#define USBHOST_HXFR_EP_MASK (15 << USBHOST_HXFR_EP_SHIFT) +# define USBHOST_HXFR_EP(n) ((uint8_t)(n) << USBHOST_HXFR_EP_SHIFT) +#define USBHOST_HXFR_TOKEN_SHIFT (4) /* Bits 4-7: Token */ +#define USBHOST_HXFR_TOKEN_MASK (15 << USBHOST_HXFR_EP_SHIFT) +# define USBHOST_HXFR_SETUP (1 << 4) +# define USBHOST_HXFR_OUTNIN (1 << 5) +# define USBHOST_HXFR_ISO (1 << 6) +# define USBHOST_HXFR_HS (1 << 7) +# define USBHOST_HXFR_TOKEN_IN (0) +# define USBHOST_HXFR_TOKEN_SETUP USBHOST_HXFR_SETUP +# define USBHOST_HXFR_TOKEN_OUT USBHOST_HXFR_OUTNIN +# define USBHOST_HXFR_TOKEN_INHS USBHOST_HXFR_HS +# define USBHOST_HXFR_TOKEN_OUTHS (USBHOST_HXFR_OUTNIN | USBHOST_HXFR_HS) +# define USBHOST_HXFR_TOKEN_ISOIN USBHOST_HXFR_ISO +# define USBHOST_HXFR_TOKEN_ISOOUT (USBHOST_HXFR_OUTNIN | USBHOST_HXFR_ISO) -#define USBHOST_HRSL_HRSLT0 (1 << 0) -#define USBHOST_HRSL_HRSLT1 (1 << 1) -#define USBHOST_HRSL_HRSLT2 (1 << 2) -#define USBHOST_HRSL_HRSLT3 (1 << 3) +#define USBHOST_HRSL_HRSLT_SHIFT (0) /* Bits 0-3: Host result error code */ +#define USBHOST_HRSL_HRSLT_MASK (15 << USBHOST_HRSL_HRSLT_SHIFT) +# define USBHOST_HRSL_HRSLT_SUCCESS (0 << USBHOST_HRSL_HRSLT_SHIFT) +# define USBHOST_HRSL_HRSLT_BUSY (1 << USBHOST_HRSL_HRSLT_SHIFT) +# define USBHOST_HRSL_HRSLT_BADREQ (2 << USBHOST_HRSL_HRSLT_SHIFT) +# define USBHOST_HRSL_HRSLT_UNDEF (3 << USBHOST_HRSL_HRSLT_SHIFT) +# define USBHOST_HRSL_HRSLT_NAK (4 << USBHOST_HRSL_HRSLT_SHIFT) +# define USBHOST_HRSL_HRSLT_STALL (5 << USBHOST_HRSL_HRSLT_SHIFT) +# define USBHOST_HRSL_HRSLT_TOGERR (6 << USBHOST_HRSL_HRSLT_SHIFT) +# define USBHOST_HRSL_HRSLT_WRONGPID (7 << USBHOST_HRSL_HRSLT_SHIFT) +# define USBHOST_HRSL_HRSLT_BADBC (8 << USBHOST_HRSL_HRSLT_SHIFT) +# define USBHOST_HRSL_HRSLT_PIDERR (9 << USBHOST_HRSL_HRSLT_SHIFT) +# define USBHOST_HRSL_HRSLT_PKTERR (10 << USBHOST_HRSL_HRSLT_SHIFT) +# define USBHOST_HRSL_HRSLT_CRCERR (11 << USBHOST_HRSL_HRSLT_SHIFT) +# define USBHOST_HRSL_HRSLT_KERR (12 << USBHOST_HRSL_HRSLT_SHIFT) +# define USBHOST_HRSL_HRSLT_JERR (13 << USBHOST_HRSL_HRSLT_SHIFT) +# define USBHOST_HRSL_HRSLT_TIMEOUT (14 << USBHOST_HRSL_HRSLT_SHIFT) +# define USBHOST_HRSL_HRSLT_BABBLE (15 << USBHOST_HRSL_HRSLT_SHIFT) #define USBHOST_HRSL_RCVTOGRD (1 << 4) #define USBHOST_HRSL_SNDTOGRD (1 << 5) #define USBHOST_HRSL_KSTATUS (1 << 6) @@ -312,6 +366,11 @@ * Bit 2: Unused * Bit 1: Direction (read = 0, write = 1) * Bit 0: ACKSTAT + * + * The ACKSTAT bit sets the ACKSTAT bit in the EPSTALLS (R9) register + * (peripheral mode only). The SPI master sets this bit to indicate that it + * has finished servicing a CONTROL transfer. The ACKSTAT bit is ignored in + * host mode. */ /* Read/write access to a register */ @@ -322,15 +381,26 @@ #define MAX3421E_ACKSTAT_TRUE 0x01 #define MAX3421E_ACKSTAT_FALSE 0x00 -/* Sizes and numbers of things */ +/* Sizes and numbers of things -- Peripheral mode */ -#define MAX3421E_NENDPOINTS 4 /* EP0..EP3 */ -#define MAX3421E_DBLBUF_SET 0x06 /* EP2, EP3 double buffered */ +#define MAX3421E_NENDPOINTS 4 /* EP0-EP3 */ +#define MAX3421E_ALLEP_SET 0x0f /* EP0-EP3 */ +#define MAX3421E_CONTROL_SET 0x01 /* EP0 is the only control EP */ +#define MAX3421E_BULK_SET 0x0e /* EP1-3 can be bulk EPs */ +#define MAX3421E_INTERUPT_SET 0x0e /* EP1-3 can be interrupt EPs */ +#define MAX3421E_OUTEP_SET 0x02 /* EP1 is the only OUT endpoint */ +#define MAX3421E_INEP_SET 0x0c /* EP2-3 are IN endpoints */ +#define MAX3421E_DBLBUF_SET 0x06 /* EP1-2 are double buffered */ -#define MAX3421E_SNDFIFO_SIZE 64 -#define MAX3421E_RCVFIFO_SIZE 64 #define MAX3421E_SETUPFIFO_SIZE 8 +/* Sizes and numbers of things -- Host mode */ + +#define MAX3421E_NHOST_CHANNELS 16 /* Number of host channels */ +#define MAX3421E_SNDFIFO_SIZE 64 /* Send FIFO, double-buffered */ +#define MAX3421E_RCVFIFO_SIZE 64 /* Receive FIFO, double-buffered */ +#define MAX3421E_SUDFIFO_SIZE 8 /* Setup FIFO */ + /* Value of the MODE register HOST bit */ #define MAX3421E_MODE_PERIPH 0 @@ -359,12 +429,28 @@ struct spi_dev_s; /* Forward reference */ struct max3421e_lowerhalf_s { - /* Device characterization */ + /* Device characterization. + * + * The interrupt configuration byte may have the following values: + * + * USBDEV_PINCTL_INTLEVEL=1 USBDEV_PINCTL_POSINT=xx (has no effect) + * Open-drain, low level active interrupt. In this mode the INT pin is + * open-drain, so a pull-up resistor on the INT line is necessary. + * + * USBDEV_PINCTL_INTLEVEL=0 USBDEV_PINCTL_POSINT=0 + * Push-pull, falling edge-sensitive. When POSINT=0 (and INTLEVEL=0), + * the INT pin signals pending interrupts with a negative edge. + * + * USBDEV_PINCTL_INTLEVEL=0 USBDEV_PINCTL_POSINT=1 + * Push-pull, rising edge-sensitive. When POSINT=1 (and INTLEVEL=0), + * the INT pin signals pending interrupts with a positive edge. + */ FAR struct spi_dev_s *spi; /* SPI device instance */ uint32_t frequency; /* SPI frequency < 26MHz */ enum spi_mode_e mode; /* Either SPIDEV_MODE0 or SPIDEV_MODE3 */ uint8_t devid; /* Distinguishes multiple MAX3421E on SPI bus */ + uint8_t intconfig; /* Interrupt configuration. See notes above. */ /* IRQ/GPIO access callbacks. These operations all hidden behind callbacks * to isolate the driver from differences in GPIO interrupt handling @@ -375,8 +461,8 @@ struct max3421e_lowerhalf_s * acknowledge - Acknowledge/clear any pending GPIO interrupt */ - CODE int (*attach)(FAR struct max3421e_lowerhalf_s *lower, xcpt_t isr, - FAR void *arg); + CODE int (*attach)(FAR const struct max3421e_lowerhalf_s *lower, + xcpt_t isr, FAR void *arg); CODE void (*enable)(FAR const struct max3421e_lowerhalf_s *lower, bool enable); CODE void (*acknowledge)(FAR const struct max3421e_lowerhalf_s *lower);