| /**************************************************************************** |
| * arch/arm/src/lpc43xx/lpc43_ehci.c |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <debug.h> |
| |
| #include <nuttx/irq.h> |
| #include <nuttx/arch.h> |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/wqueue.h> |
| #include <nuttx/signal.h> |
| #include <nuttx/mutex.h> |
| #include <nuttx/semaphore.h> |
| #include <nuttx/usb/usb.h> |
| #include <nuttx/usb/usbhost.h> |
| #include <nuttx/usb/ehci.h> |
| #include <nuttx/usb/usbhost_devaddr.h> |
| #include <nuttx/usb/usbhost_trace.h> |
| |
| #include <arch/board/board.h> |
| |
| #include "chip.h" |
| #include "hardware/lpc43_usb0.h" |
| #include "arm_internal.h" |
| #include "lpc43_cgu.h" |
| #include "hardware/lpc43_creg.h" |
| #include "hardware/lpc43_evntrtr.h" |
| |
| #if defined(CONFIG_LPC43_USBOTG) && defined(CONFIG_USBHOST) |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| /* Configuration ************************************************************/ |
| |
| /* Pre-requisites */ |
| |
| #if !defined(CONFIG_SCHED_WORKQUEUE) |
| # error Work queue support is required (CONFIG_SCHED_WORKQUEUE) |
| #elif !defined(CONFIG_SCHED_HPWORK) |
| # error Hi-priority work queue support is required (CONFIG_SCHED_HPWORK) |
| #endif |
| |
| /* Configurable number of Queue Head (QH) structures. The default is one per |
| * Root hub port plus one for EP0. |
| */ |
| |
| #ifndef CONFIG_LPC43_EHCI_NQHS |
| # define CONFIG_LPC43_EHCI_NQHS (LPC43_EHCI_NRHPORT + 1) |
| #endif |
| |
| /* Configurable number of Queue Element Transfer Descriptor (qTDs). The |
| * default is one per root hub plus three from EP0. |
| */ |
| |
| #ifndef CONFIG_LPC43_EHCI_NQTDS |
| # define CONFIG_LPC43_EHCI_NQTDS (LPC43_EHCI_NRHPORT + 3) |
| #endif |
| |
| /* Configurable size of a request/descriptor buffers */ |
| |
| #ifndef CONFIG_LPC43_EHCI_BUFSIZE |
| # define CONFIG_LPC43_EHCI_BUFSIZE 128 |
| #endif |
| |
| /* Debug options */ |
| |
| #ifndef CONFIG_DEBUG_USB_INFO |
| # undef CONFIG_LPC43_EHCI_REGDEBUG |
| #endif |
| |
| /* Isochronous transfers are not currently supported */ |
| |
| #undef CONFIG_USBHOST_ISOC_DISABLE |
| #define CONFIG_USBHOST_ISOC_DISABLE 1 |
| |
| /* Simplify DEBUG checks */ |
| |
| #ifndef CONFIG_DEBUG_FEATURES |
| # undef CONFIG_DEBUG_INFO |
| # undef CONFIG_DEBUG_USB |
| #endif |
| |
| /* Registers ****************************************************************/ |
| |
| /* Traditionally, NuttX specifies register locations using individual |
| * register offsets from a base address. That tradition is broken here and, |
| * instead, register blocks are represented as structures. This is done here |
| * because, in principle, EHCI operational register address may not be known |
| * at compile time; the operational registers lie at an offset specified in |
| * the 'caplength' byte of the Host Controller Capability Registers. |
| * |
| * However, for the case of the LPC43 EHCI, we know apriori that locations |
| * of these register blocks. |
| */ |
| |
| /* Host Controller Capability Registers */ |
| |
| #define HCCR ((struct ehci_hccr_s *)LPC43_USBOTG_HCCR_BASE) |
| |
| /* Host Controller Operational Registers */ |
| |
| #define HCOR ((volatile struct ehci_hcor_s *)LPC43_USBOTG_HCOR_BASE) |
| |
| /* Interrupts ***************************************************************/ |
| |
| /* This is the set of interrupts handled by this driver */ |
| |
| #define EHCI_HANDLED_INTS (EHCI_INT_USBINT | EHCI_INT_USBERRINT | \ |
| EHCI_INT_PORTSC | EHCI_INT_SYSERROR | \ |
| EHCI_INT_AAINT) |
| |
| /* The periodic frame list is a 4K-page aligned array of Frame List Link |
| * pointers. The length of the frame list may be programmable. The |
| * programmability of the periodic frame list is exported to system software |
| * via the HCCPARAMS register. If non-programmable, the length is 1024 |
| * elements. If programmable, the length can be selected by system software |
| * as one of 256, 512, or 1024 elements. |
| */ |
| |
| #define FRAME_LIST_SIZE 1024 |
| |
| /* DMA **********************************************************************/ |
| |
| /* For now, we are assuming an identity mapping between physical and virtual |
| * address spaces. |
| */ |
| |
| #define lpc43_physramaddr(a) (a) |
| #define lpc43_virtramaddr(a) (a) |
| |
| /* USB trace ****************************************************************/ |
| |
| #ifdef HAVE_USBHOST_TRACE |
| # define TR_FMT1 false |
| # define TR_FMT2 true |
| |
| # define TRENTRY(id,fmt1,string) {string} |
| |
| # 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 |
| |
| /* Port numbers */ |
| |
| #define RHPNDX(rh) ((rh)->hport.hport.port) |
| #define RHPORT(rh) (RHPNDX(rh)+1) |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| /* Internal representation of the EHCI Queue Head (QH) */ |
| |
| struct lpc43_epinfo_s; |
| struct lpc43_qh_s |
| { |
| /* Fields visible to hardware */ |
| |
| struct ehci_qh_s hw; /* Hardware representation of the queue head */ |
| |
| /* Internal fields used by the EHCI driver */ |
| |
| struct lpc43_epinfo_s *epinfo; /* Endpoint used for the transfer */ |
| uint32_t fqp; /* First qTD in the list (physical address) */ |
| uint8_t pad[8]; /* Padding to assure 32-byte alignment */ |
| }; |
| |
| /* Internal representation of the EHCI Queue Element Transfer Descriptor |
| * (qTD) |
| */ |
| |
| struct lpc43_qtd_s |
| { |
| /* Fields visible to hardware */ |
| |
| struct ehci_qtd_s hw; /* Hardware representation of the queue head */ |
| |
| /* Internal fields used by the EHCI driver */ |
| }; |
| |
| /* The following is used to manage lists of free QHs and qTDs */ |
| |
| struct lpc43_list_s |
| { |
| struct lpc43_list_s *flink; /* Link to next entry in the list */ |
| /* Variable length entry data follows */ |
| }; |
| |
| /* List traversal callout functions */ |
| |
| typedef int (*foreach_qh_t)(struct lpc43_qh_s *qh, uint32_t **bp, void *arg); |
| typedef int (*foreach_qtd_t)(struct lpc43_qtd_s *qtd, uint32_t **bp, |
| void *arg); |
| |
| /* This structure describes one endpoint. */ |
| |
| struct lpc43_epinfo_s |
| { |
| uint8_t epno:7; /* Endpoint number */ |
| uint8_t dirin:1; /* 1:IN endpoint 0:OUT endpoint */ |
| uint8_t devaddr:7; /* Device address */ |
| uint8_t toggle:1; /* Next data toggle */ |
| #ifndef CONFIG_USBHOST_INT_DISABLE |
| uint8_t interval; /* Polling interval */ |
| #endif |
| uint8_t status; /* Retained token status bits (for debug purposes) */ |
| volatile bool iocwait; /* TRUE: Thread is waiting for transfer completion */ |
| uint16_t maxpacket:11; /* Maximum packet size */ |
| uint16_t xfrtype:2; /* See USB_EP_ATTR_XFER_* definitions in usb.h */ |
| uint16_t speed:2; /* See USB_*_SPEED definitions in ehci.h */ |
| int result; /* The result of the transfer */ |
| uint32_t xfrd; /* On completion, will hold the number of bytes transferred */ |
| sem_t iocsem; /* Semaphore used to wait for transfer completion */ |
| #ifdef CONFIG_USBHOST_ASYNCH |
| usbhost_asynch_t callback; /* Transfer complete callback */ |
| void *arg; /* Argument that accompanies the callback */ |
| #endif |
| }; |
| |
| /* This structure retains the state of one root hub port */ |
| |
| struct lpc43_rhport_s |
| { |
| /* Common device fields. This must be the first thing defined in the |
| * structure so that it is possible to simply cast from struct usbhost_s |
| * to struct lpc43_rhport_s. |
| */ |
| |
| struct usbhost_driver_s drvr; |
| |
| /* Root hub port status */ |
| |
| volatile bool connected; /* Connected to device */ |
| volatile bool lowspeed; /* Low speed device attached */ |
| struct lpc43_epinfo_s ep0; /* EP0 endpoint info */ |
| |
| /* This is the hub port description understood by class drivers */ |
| |
| struct usbhost_roothubport_s hport; |
| }; |
| |
| /* This structure retains the overall state of the USB host controller */ |
| |
| struct lpc43_ehci_s |
| { |
| volatile bool pscwait; /* TRUE: Thread is waiting for port status change event */ |
| |
| mutex_t lock; /* Support mutually exclusive access */ |
| sem_t pscsem; /* Semaphore to wait for port status change events */ |
| |
| struct lpc43_epinfo_s ep0; /* Endpoint 0 */ |
| struct lpc43_list_s *qhfree; /* List of free Queue Head (QH) structures */ |
| struct lpc43_list_s *qtdfree; /* List of free Queue Element Transfer Descriptor (qTD) */ |
| struct work_s work; /* Supports interrupt bottom half */ |
| |
| #ifdef CONFIG_USBHOST_HUB |
| /* Used to pass external hub port events */ |
| |
| volatile struct usbhost_hubport_s *hport; |
| #endif |
| |
| struct usbhost_devaddr_s devgen; /* Address generation data */ |
| |
| /* Root hub ports */ |
| |
| struct lpc43_rhport_s rhport[LPC43_EHCI_NRHPORT]; |
| }; |
| |
| #ifdef HAVE_USBHOST_TRACE |
| /* USB trace codes */ |
| |
| enum usbhost_trace1codes_e |
| { |
| __TRACE1_BASEVALUE = 0, /* This will force the first value to be 1 */ |
| |
| EHCI_TRACE1_SYSTEMERROR, /* EHCI ERROR: System error */ |
| EHCI_TRACE1_QTDFOREACH_FAILED, /* EHCI ERROR: lpc43_qtd_foreach failed */ |
| EHCI_TRACE1_QHALLOC_FAILED, /* EHCI ERROR: Failed to allocate a QH */ |
| EHCI_TRACE1_BUFTOOBIG, /* EHCI ERROR: Buffer too big */ |
| EHCI_TRACE1_REQQTDALLOC_FAILED, /* EHCI ERROR: Failed to allocate request qTD */ |
| EHCI_TRACE1_ADDBPL_FAILED, /* EHCI ERROR: lpc43_qtd_addbpl failed */ |
| EHCI_TRACE1_DATAQTDALLOC_FAILED, /* EHCI ERROR: Failed to allocate data buffer qTD */ |
| EHCI_TRACE1_DEVDISCONNECTED, /* EHCI ERROR: Device disconnected */ |
| EHCI_TRACE1_QHCREATE_FAILED, /* EHCI ERROR: lpc43_qh_create failed */ |
| EHCI_TRACE1_QTDSETUP_FAILED, /* EHCI ERROR: lpc43_qtd_setupphase failed */ |
| |
| EHCI_TRACE1_QTDDATA_FAILED, /* EHCI ERROR: lpc43_qtd_dataphase failed */ |
| EHCI_TRACE1_QTDSTATUS_FAILED, /* EHCI ERROR: lpc43_qtd_statusphase failed */ |
| EHCI_TRACE1_TRANSFER_FAILED, /* EHCI ERROR: Transfer failed */ |
| EHCI_TRACE1_QHFOREACH_FAILED, /* EHCI ERROR: lpc43_qh_foreach failed: */ |
| EHCI_TRACE1_SYSERR_INTR, /* EHCI: Host System Error Interrupt */ |
| EHCI_TRACE1_USBERR_INTR, /* EHCI: USB Error Interrupt (USBERRINT) Interrupt */ |
| EHCI_TRACE1_EPALLOC_FAILED, /* EHCI ERROR: Failed to allocate EP info structure */ |
| EHCI_TRACE1_BADXFRTYPE, /* EHCI ERROR: Support for transfer type not implemented */ |
| EHCI_TRACE1_HCHALTED_TIMEOUT, /* EHCI ERROR: Timed out waiting for HCHalted */ |
| EHCI_TRACE1_QHPOOLALLOC_FAILED, /* EHCI ERROR: Failed to allocate the QH pool */ |
| |
| EHCI_TRACE1_QTDPOOLALLOC_FAILED, /* EHCI ERROR: Failed to allocate the qTD pool */ |
| EHCI_TRACE1_PERFLALLOC_FAILED, /* EHCI ERROR: Failed to allocate the periodic frame list */ |
| EHCI_TRACE1_RESET_FAILED, /* EHCI ERROR: lpc43_reset failed */ |
| EHCI_TRACE1_RUN_FAILED, /* EHCI ERROR: EHCI Failed to run */ |
| EHCI_TRACE1_IRQATTACH_FAILED, /* EHCI ERROR: Failed to attach IRQ */ |
| |
| #ifdef HAVE_USBHOST_TRACE_VERBOSE |
| EHCI_VTRACE1_PORTSC_CSC, /* EHCI Connect Status Change */ |
| EHCI_VTRACE1_PORTSC_CONNALREADY, /* EHCI Already connected */ |
| EHCI_VTRACE1_PORTSC_DISCALREADY, /* EHCI Already disconnected */ |
| EHCI_VTRACE1_TOPHALF, /* EHCI Interrupt top half */ |
| EHCI_VTRACE1_AAINTR, /* EHCI Async Advance Interrupt */ |
| |
| EHCI_VTRACE1_CLASSENUM, /* EHCI Hub port CLASS enumeration */ |
| EHCI_VTRACE1_USBINTR, /* EHCI USB Interrupt (USBINT) Interrupt */ |
| EHCI_VTRACE1_ENUM_DISCONN, /* EHCI Enumeration not connected */ |
| EHCI_VTRACE1_INITIALIZING, /* EHCI Initializing EHCI Stack */ |
| EHCI_VTRACE1_HCCPARAMS, /* EHCI HCCPARAMS */ |
| EHCI_VTRACE1_INIITIALIZED, /* EHCI USB EHCI Initialized */ |
| #endif |
| |
| __TRACE1_NSTRINGS, /* Separates the format 1 from the format 2 strings */ |
| |
| EHCI_TRACE2_EPSTALLED, /* EHCI EP Stalled */ |
| EHCI_TRACE2_EPIOERROR, /* EHCI ERROR: EP TOKEN */ |
| EHCI_TRACE2_CLASSENUM_FAILED, /* EHCI usbhost_enumerate() failed */ |
| |
| #ifdef HAVE_USBHOST_TRACE_VERBOSE |
| EHCI_VTRACE2_ASYNCXFR, /* EHCI Async transfer */ |
| EHCI_VTRACE2_INTRXFR, /* EHCI Interrupt Transfer */ |
| EHCI_VTRACE2_IOCCHECK, /* EHCI IOC */ |
| EHCI_VTRACE2_PORTSC, /* EHCI PORTSC */ |
| EHCI_VTRACE2_PORTSC_CONNECTED, /* EHCI RHPort connected */ |
| EHCI_VTRACE2_PORTSC_DISCONND, /* EHCI RHport disconnected */ |
| EHCI_VTRACE2_MONWAKEUP, /* EHCI RHPort connected wakeup */ |
| |
| EHCI_VTRACE2_EPALLOC, /* EHCI EPALLOC */ |
| EHCI_VTRACE2_CTRLINOUT, /* EHCI CTRLIN/OUT */ |
| EHCI_VTRACE2_HCIVERSION, /* EHCI HCIVERSION */ |
| EHCI_VTRACE2_HCSPARAMS, /* EHCI HCSPARAMS */ |
| #endif |
| |
| __TRACE2_NSTRINGS /* Total number of enumeration values */ |
| }; |
| |
| /* USB trace data structure */ |
| |
| struct lpc43_ehci_trace_s |
| { |
| #if 0 |
| uint16_t id; |
| bool fmt2; |
| #endif |
| const char *string; |
| }; |
| |
| #endif /* HAVE_USBHOST_TRACE */ |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| /* Register operations ******************************************************/ |
| |
| static uint16_t lpc43_read16(const uint8_t *addr); |
| static uint32_t lpc43_read32(const uint8_t *addr); |
| #if 0 /* Not used */ |
| static void lpc43_write16(uint16_t memval, uint8_t *addr); |
| static void lpc43_write32(uint32_t memval, uint8_t *addr); |
| #endif |
| |
| #ifdef CONFIG_ENDIAN_BIG |
| static uint16_t lpc43_swap16(uint16_t value); |
| static uint32_t lpc43_swap32(uint32_t value); |
| #else |
| # define lpc43_swap16(value) (value) |
| # define lpc43_swap32(value) (value) |
| #endif |
| |
| #ifdef CONFIG_LPC43_EHCI_REGDEBUG |
| static void lpc43_printreg(volatile uint32_t *regaddr, uint32_t regval, |
| bool iswrite); |
| static void lpc43_checkreg(volatile uint32_t *regaddr, uint32_t regval, |
| bool iswrite); |
| static uint32_t lpc43_getreg(volatile uint32_t *regaddr); |
| static void lpc43_putreg(uint32_t regval, volatile uint32_t *regaddr); |
| #else |
| static inline uint32_t lpc43_getreg(volatile uint32_t *regaddr); |
| static inline void lpc43_putreg(uint32_t regval, volatile uint32_t *regaddr); |
| #endif |
| static int ehci_wait_usbsts(uint32_t maskbits, uint32_t donebits, |
| unsigned int delay); |
| |
| /* Allocators ***************************************************************/ |
| |
| static struct lpc43_qh_s *lpc43_qh_alloc(void); |
| static void lpc43_qh_free(struct lpc43_qh_s *qh); |
| static struct lpc43_qtd_s *lpc43_qtd_alloc(void); |
| static void lpc43_qtd_free(struct lpc43_qtd_s *qtd); |
| |
| /* List Management **********************************************************/ |
| |
| static int lpc43_qh_foreach(struct lpc43_qh_s *qh, uint32_t **bp, |
| foreach_qh_t handler, void *arg); |
| static int lpc43_qtd_foreach(struct lpc43_qh_s *qh, foreach_qtd_t handler, |
| void *arg); |
| static int lpc43_qtd_discard(struct lpc43_qtd_s *qtd, uint32_t **bp, |
| void *arg); |
| static int lpc43_qh_discard(struct lpc43_qh_s *qh); |
| |
| /* Endpoint Transfer Handling ***********************************************/ |
| |
| #ifdef CONFIG_LPC43_EHCI_REGDEBUG |
| static void lpc43_qtd_print(struct lpc43_qtd_s *qtd); |
| static void lpc43_qh_print(struct lpc43_qh_s *qh); |
| static int lpc43_qtd_dump(struct lpc43_qtd_s *qtd, uint32_t **bp, void *arg); |
| static int lpc43_qh_dump(struct lpc43_qh_s *qh, uint32_t **bp, void *arg); |
| #else |
| # define lpc43_qtd_print(qtd) |
| # define lpc43_qh_print(qh) |
| # define lpc43_qtd_dump(qtd, bp, arg) OK |
| # define lpc43_qh_dump(qh, bp, arg) OK |
| #endif |
| |
| static inline uint8_t lpc43_ehci_speed(uint8_t usbspeed); |
| static int lpc43_ioc_setup(struct lpc43_rhport_s *rhport, |
| struct lpc43_epinfo_s *epinfo); |
| static int lpc43_ioc_wait(struct lpc43_epinfo_s *epinfo); |
| static void lpc43_qh_enqueue(struct lpc43_qh_s *qhead, |
| struct lpc43_qh_s *qh); |
| static struct lpc43_qh_s *lpc43_qh_create(struct lpc43_rhport_s *rhport, |
| struct lpc43_epinfo_s *epinfo); |
| static int lpc43_qtd_addbpl(struct lpc43_qtd_s *qtd, const void *buffer, |
| size_t buflen); |
| static struct lpc43_qtd_s *lpc43_qtd_setupphase( |
| struct lpc43_epinfo_s *epinfo, const struct usb_ctrlreq_s *req); |
| static struct lpc43_qtd_s *lpc43_qtd_dataphase(struct lpc43_epinfo_s *epinfo, |
| void *buffer, int buflen, uint32_t tokenbits); |
| static struct lpc43_qtd_s *lpc43_qtd_statusphase(uint32_t tokenbits); |
| static ssize_t lpc43_async_setup(struct lpc43_rhport_s *rhport, |
| struct lpc43_epinfo_s *epinfo, const struct usb_ctrlreq_s *req, |
| uint8_t *buffer, size_t buflen); |
| #ifndef CONFIG_USBHOST_INT_DISABLE |
| static int lpc43_intr_setup(struct lpc43_rhport_s *rhport, |
| struct lpc43_epinfo_s *epinfo, uint8_t *buffer, size_t buflen); |
| #endif |
| static ssize_t lpc43_transfer_wait(struct lpc43_epinfo_s *epinfo); |
| #ifdef CONFIG_USBHOST_ASYNCH |
| static inline int lpc43_ioc_async_setup(struct lpc43_rhport_s *rhport, |
| struct lpc43_epinfo_s *epinfo, usbhost_asynch_t callback, |
| void *arg); |
| static void lpc43_asynch_completion(struct lpc43_epinfo_s *epinfo); |
| #endif |
| |
| /* Interrupt Handling *******************************************************/ |
| |
| static int lpc43_qtd_ioccheck(struct lpc43_qtd_s *qtd, uint32_t **bp, |
| void *arg); |
| static int lpc43_qh_ioccheck(struct lpc43_qh_s *qh, uint32_t **bp, |
| void *arg); |
| #ifdef CONFIG_USBHOST_ASYNCH |
| static int lpc43_qtd_cancel(struct lpc43_qtd_s *qtd, uint32_t **bp, |
| void *arg); |
| static int lpc43_qh_cancel(struct lpc43_qh_s *qh, uint32_t **bp, void *arg); |
| #endif |
| static inline void lpc43_ioc_bottomhalf(void); |
| static inline void lpc43_portsc_bottomhalf(void); |
| static inline void lpc43_syserr_bottomhalf(void); |
| static inline void lpc43_async_advance_bottomhalf(void); |
| static void lpc43_ehci_bottomhalf(void *arg); |
| static int lpc43_ehci_interrupt(int irq, void *context, void *arg); |
| |
| /* USB Host Controller Operations *******************************************/ |
| |
| static int lpc43_wait(struct usbhost_connection_s *conn, |
| struct usbhost_hubport_s **hport); |
| static int lpc43_rh_enumerate(struct usbhost_connection_s *conn, |
| struct usbhost_hubport_s *hport); |
| static int lpc43_enumerate(struct usbhost_connection_s *conn, |
| struct usbhost_hubport_s *hport); |
| |
| static int lpc43_ep0configure(struct usbhost_driver_s *drvr, |
| usbhost_ep_t ep0, uint8_t funcaddr, uint8_t speed, |
| uint16_t maxpacketsize); |
| static int lpc43_epalloc(struct usbhost_driver_s *drvr, |
| const struct usbhost_epdesc_s *epdesc, |
| usbhost_ep_t *ep); |
| static int lpc43_epfree(struct usbhost_driver_s *drvr, usbhost_ep_t ep); |
| static int lpc43_alloc(struct usbhost_driver_s *drvr, |
| uint8_t **buffer, size_t *maxlen); |
| static int lpc43_free(struct usbhost_driver_s *drvr, |
| uint8_t *buffer); |
| static int lpc43_ioalloc(struct usbhost_driver_s *drvr, |
| uint8_t **buffer, size_t buflen); |
| static int lpc43_iofree(struct usbhost_driver_s *drvr, |
| uint8_t *buffer); |
| static int lpc43_ctrlin(struct usbhost_driver_s *drvr, usbhost_ep_t ep0, |
| const struct usb_ctrlreq_s *req, uint8_t *buffer); |
| static int lpc43_ctrlout(struct usbhost_driver_s *drvr, usbhost_ep_t ep0, |
| const struct usb_ctrlreq_s *req, const uint8_t *buffer); |
| static ssize_t lpc43_transfer(struct usbhost_driver_s *drvr, |
| usbhost_ep_t ep, uint8_t *buffer, size_t buflen); |
| #ifdef CONFIG_USBHOST_ASYNCH |
| static int lpc43_asynch(struct usbhost_driver_s *drvr, usbhost_ep_t ep, |
| uint8_t *buffer, size_t buflen, usbhost_asynch_t callback, |
| void *arg); |
| #endif |
| static int lpc43_cancel(struct usbhost_driver_s *drvr, usbhost_ep_t ep); |
| #ifdef CONFIG_USBHOST_HUB |
| static int lpc43_connect(struct usbhost_driver_s *drvr, |
| struct usbhost_hubport_s *hport, bool connected); |
| #endif |
| static void lpc43_disconnect(struct usbhost_driver_s *drvr, |
| struct usbhost_hubport_s *hport); |
| |
| /* Initialization ***********************************************************/ |
| |
| static int lpc43_reset(void); |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /* In this driver implementation, support is provided for only a single |
| * USB device. All status information can be simply retained in a single |
| * global instance. |
| */ |
| |
| static struct lpc43_ehci_s g_ehci = |
| { |
| .lock = NXMUTEX_INITIALIZER, |
| .pscsem = SEM_INITIALIZER(0), |
| .ep0.iocsem = SEM_INITIALIZER(1), |
| }; |
| |
| /* This is the connection/enumeration interface */ |
| |
| static struct usbhost_connection_s g_ehciconn = |
| { |
| .wait = lpc43_wait, |
| .enumerate = lpc43_enumerate, |
| }; |
| |
| /* Maps USB chapter 9 speed to EHCI speed */ |
| |
| static const uint8_t g_ehci_speed[4] = |
| { |
| 0, EHCI_LOW_SPEED, EHCI_FULL_SPEED, EHCI_HIGH_SPEED |
| }; |
| |
| /* The head of the asynchronous queue */ |
| |
| static struct lpc43_qh_s g_asynchead aligned_data(32); |
| |
| #ifndef CONFIG_USBHOST_INT_DISABLE |
| /* The head of the periodic queue */ |
| |
| static struct lpc43_qh_s g_intrhead aligned_data(32); |
| |
| /* The frame list */ |
| |
| #ifdef CONFIG_LPC43_EHCI_PREALLOCATE |
| static uint32_t g_framelist[FRAME_LIST_SIZE] aligned_data(4096); |
| #else |
| static uint32_t *g_framelist; |
| #endif |
| #endif /* CONFIG_USBHOST_INT_DISABLE */ |
| |
| #ifdef CONFIG_LPC43_EHCI_PREALLOCATE |
| /* Pools of pre-allocated data structures. These will all be linked into the |
| * free lists within g_ehci. These must all be aligned to 32-byte boundaries |
| */ |
| |
| /* Queue Head (QH) pool */ |
| |
| static struct lpc43_qh_s g_qhpool[CONFIG_LPC43_EHCI_NQHS] |
| aligned_data(32); |
| |
| /* Queue Element Transfer Descriptor (qTD) pool */ |
| |
| static struct lpc43_qtd_s g_qtdpool[CONFIG_LPC43_EHCI_NQTDS] |
| aligned_data(32); |
| |
| #else |
| /* Pools of dynamically data structures. These will all be linked into the |
| * free lists within g_ehci. These must all be aligned to 32-byte boundaries |
| */ |
| |
| /* Queue Head (QH) pool */ |
| |
| static struct lpc43_qh_s *g_qhpool; |
| |
| /* Queue Element Transfer Descriptor (qTD) pool */ |
| |
| static struct lpc43_qtd_s *g_qtdpool; |
| |
| #endif |
| |
| #ifdef HAVE_USBHOST_TRACE |
| /* USB trace strings */ |
| |
| static const struct lpc43_ehci_trace_s g_trace1[TRACE1_NSTRINGS] = |
| { |
| TRENTRY(EHCI_TRACE1_SYSTEMERROR, TR_FMT1, |
| "EHCI ERROR: System error: %06x\n"), |
| TRENTRY(EHCI_TRACE1_QTDFOREACH_FAILED, TR_FMT1, |
| "EHCI ERROR: lpc43_qtd_foreach failed: %d\n"), |
| TRENTRY(EHCI_TRACE1_QHALLOC_FAILED, TR_FMT1, |
| "EHCI ERROR: Failed to allocate a QH\n"), |
| TRENTRY(EHCI_TRACE1_BUFTOOBIG, TR_FMT1, |
| "EHCI ERROR: Buffer too big. Remaining %d\n"), |
| TRENTRY(EHCI_TRACE1_REQQTDALLOC_FAILED, TR_FMT1, |
| "EHCI ERROR: Failed to allocate request qTD"), |
| TRENTRY(EHCI_TRACE1_ADDBPL_FAILED, TR_FMT1, |
| "EHCI ERROR: lpc43_qtd_addbpl failed: %d\n"), |
| TRENTRY(EHCI_TRACE1_DATAQTDALLOC_FAILED, TR_FMT1, |
| "EHCI ERROR: Failed to allocate data buffer qTD, 0"), |
| TRENTRY(EHCI_TRACE1_DEVDISCONNECTED, TR_FMT1, |
| "EHCI ERROR: Device disconnected %d\n"), |
| TRENTRY(EHCI_TRACE1_QHCREATE_FAILED, TR_FMT1, |
| "EHCI ERROR: lpc43_qh_create failed\n"), |
| TRENTRY(EHCI_TRACE1_QTDSETUP_FAILED, TR_FMT1, |
| "EHCI ERROR: lpc43_qtd_setupphase failed\n"), |
| |
| TRENTRY(EHCI_TRACE1_QTDDATA_FAILED, TR_FMT1, |
| "EHCI ERROR: lpc43_qtd_dataphase failed\n"), |
| TRENTRY(EHCI_TRACE1_QTDSTATUS_FAILED, TR_FMT1, |
| "EHCI ERROR: lpc43_qtd_statusphase failed\n"), |
| TRENTRY(EHCI_TRACE1_TRANSFER_FAILED, TR_FMT1, |
| "EHCI ERROR: Transfer failed %d\n"), |
| TRENTRY(EHCI_TRACE1_QHFOREACH_FAILED, TR_FMT1, |
| "EHCI ERROR: lpc43_qh_foreach failed: %d\n"), |
| TRENTRY(EHCI_TRACE1_SYSERR_INTR, TR_FMT1, |
| "EHCI: Host System Error Interrupt\n"), |
| TRENTRY(EHCI_TRACE1_USBERR_INTR, TR_FMT1, |
| "EHCI: USB Error Interrupt (USBERRINT) Interrupt: %06x\n"), |
| TRENTRY(EHCI_TRACE1_EPALLOC_FAILED, TR_FMT1, |
| "EHCI ERROR: Failed to allocate EP info structure\n"), |
| TRENTRY(EHCI_TRACE1_BADXFRTYPE, TR_FMT1, |
| "EHCI ERROR: Support for transfer type %d not implemented\n"), |
| TRENTRY(EHCI_TRACE1_HCHALTED_TIMEOUT, TR_FMT1, |
| "EHCI ERROR: Timed out waiting for HCHalted. USBSTS: %06x\n"), |
| TRENTRY(EHCI_TRACE1_QHPOOLALLOC_FAILED, TR_FMT1, |
| "EHCI ERROR: Failed to allocate the QH pool\n"), |
| |
| TRENTRY(EHCI_TRACE1_QTDPOOLALLOC_FAILED, TR_FMT1, |
| "EHCI ERROR: Failed to allocate the qTD pool\n"), |
| TRENTRY(EHCI_TRACE1_PERFLALLOC_FAILED, TR_FMT1, |
| "EHCI ERROR: Failed to allocate the periodic frame list\n"), |
| TRENTRY(EHCI_TRACE1_RESET_FAILED, TR_FMT1, |
| "EHCI ERROR: lpc43_reset failed: %d\n"), |
| TRENTRY(EHCI_TRACE1_RUN_FAILED, TR_FMT1, |
| "EHCI ERROR: EHCI Failed to run: USBSTS=%06x\n"), |
| TRENTRY(EHCI_TRACE1_IRQATTACH_FAILED, TR_FMT1, |
| "EHCI ERROR: Failed to attach IRQ%d\n"), |
| |
| #ifdef HAVE_USBHOST_TRACE_VERBOSE |
| TRENTRY(EHCI_VTRACE1_PORTSC_CSC, TR_FMT1, |
| "EHCI Connect Status Change: %06x\n"), |
| TRENTRY(EHCI_VTRACE1_PORTSC_CONNALREADY, TR_FMT1, |
| "EHCI Already connected: %06x\n"), |
| TRENTRY(EHCI_VTRACE1_PORTSC_DISCALREADY, TR_FMT1, |
| "EHCI Already disconnected: %06x\n"), |
| TRENTRY(EHCI_VTRACE1_TOPHALF, TR_FMT1, |
| "EHCI Interrupt: %06x\n"), |
| TRENTRY(EHCI_VTRACE1_AAINTR, TR_FMT1, |
| "EHCI Async Advance Interrupt\n"), |
| |
| TRENTRY(EHCI_VTRACE1_CLASSENUM, TR_FMT1, |
| "EHCI Hub port %d: Enumerate the device\n"), |
| TRENTRY(EHCI_VTRACE1_USBINTR, TR_FMT1, |
| "EHCI USB Interrupt (USBINT) Interrupt: %06x\n"), |
| TRENTRY(EHCI_VTRACE1_ENUM_DISCONN, TR_FMT1, |
| "EHCI Enumeration not connected\n"), |
| TRENTRY(EHCI_VTRACE1_INITIALIZING, TR_FMT1, |
| "EHCI Initializing EHCI Stack\n"), |
| TRENTRY(EHCI_VTRACE1_HCCPARAMS, TR_FMT1, |
| "EHCI HCCPARAMS=%06x\n"), |
| TRENTRY(EHCI_VTRACE1_INIITIALIZED, TR_FMT1, |
| "EHCI USB EHCI Initialized\n"), |
| #endif |
| }; |
| |
| static const struct lpc43_ehci_trace_s g_trace2[TRACE2_NSTRINGS] = |
| { |
| TRENTRY(EHCI_TRACE2_EPSTALLED, TR_FMT2, |
| "EHCI EP%d Stalled: TOKEN=%04x\n"), |
| TRENTRY(EHCI_TRACE2_EPIOERROR, TR_FMT2, |
| "EHCI ERROR: EP%d TOKEN=%04x\n"), |
| TRENTRY(EHCI_TRACE2_CLASSENUM_FAILED, TR_FMT2, |
| "EHCI Hub port %d usbhost_enumerate() failed: %d\n"), |
| |
| #ifdef HAVE_USBHOST_TRACE_VERBOSE |
| TRENTRY(EHCI_VTRACE2_ASYNCXFR, TR_FMT2, |
| "EHCI Async transfer EP%d buflen=%d\n"), |
| TRENTRY(EHCI_VTRACE2_INTRXFR, TR_FMT2, |
| "EHCI Intr Transfer EP%d buflen=%d\n"), |
| TRENTRY(EHCI_VTRACE2_IOCCHECK, TR_FMT2, |
| "EHCI IOC EP%d TOKEN=%04x\n"), |
| TRENTRY(EHCI_VTRACE2_PORTSC, TR_FMT2, |
| "EHCI PORTSC%d: %04x\n"), |
| TRENTRY(EHCI_VTRACE2_PORTSC_CONNECTED, TR_FMT2, |
| "EHCI RHPort%d connected, pscwait: %d\n"), |
| TRENTRY(EHCI_VTRACE2_PORTSC_DISCONND, TR_FMT2, |
| "EHCI RHport%d disconnected, pscwait: %d\n"), |
| TRENTRY(EHCI_VTRACE2_MONWAKEUP, TR_FMT2, |
| "EHCI RHPort%d connected: %d\n"), |
| |
| TRENTRY(EHCI_VTRACE2_EPALLOC, TR_FMT2, |
| "EHCI EPALLOC: EP%d TYPE=%d\n"), |
| TRENTRY(EHCI_VTRACE2_CTRLINOUT, TR_FMT2, |
| "EHCI CTRLIN/OUT: RHPort%d req: %02x\n"), |
| TRENTRY(EHCI_VTRACE2_HCIVERSION, TR_FMT2, |
| "EHCI HCIVERSION %x.%02x\n"), |
| TRENTRY(EHCI_VTRACE2_HCSPARAMS, TR_FMT2, |
| "EHCI nports=%d, HCSPARAMS=%04x\n"), |
| #endif |
| }; |
| #endif /* HAVE_USBHOST_TRACE */ |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: lpc43_read16 |
| * |
| * Description: |
| * Read 16-bit little endian data |
| * |
| ****************************************************************************/ |
| |
| static uint16_t lpc43_read16(const uint8_t *addr) |
| { |
| #ifdef CONFIG_ENDIAN_BIG |
| return (uint16_t)addr[0] << 8 | (uint16_t)addr[1]; |
| #else |
| return (uint16_t)addr[1] << 8 | (uint16_t)addr[0]; |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_read32 |
| * |
| * Description: |
| * Read 32-bit little endian data |
| * |
| ****************************************************************************/ |
| |
| static inline uint32_t lpc43_read32(const uint8_t *addr) |
| { |
| #ifdef CONFIG_ENDIAN_BIG |
| return (uint32_t)lpc43_read16(&addr[0]) << 16 | |
| (uint32_t)lpc43_read16(&addr[2]); |
| #else |
| return (uint32_t)lpc43_read16(&addr[2]) << 16 | |
| (uint32_t)lpc43_read16(&addr[0]); |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_write16 |
| * |
| * Description: |
| * Write 16-bit little endian data |
| * |
| ****************************************************************************/ |
| |
| #if 0 /* Not used */ |
| static void lpc43_write16(uint16_t memval, uint8_t *addr) |
| { |
| #ifdef CONFIG_ENDIAN_BIG |
| addr[0] = memval & 0xff; |
| addr[1] = memval >> 8; |
| #else |
| addr[0] = memval >> 8; |
| addr[1] = memval & 0xff; |
| #endif |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: lpc43_write32 |
| * |
| * Description: |
| * Write 32-bit little endian data |
| * |
| ****************************************************************************/ |
| |
| #if 0 /* Not used */ |
| static void lpc43_write32(uint32_t memval, uint8_t *addr) |
| { |
| #ifdef CONFIG_ENDIAN_BIG |
| lpc43_write16(memval >> 16, &addr[0]); |
| lpc43_write16(memval & 0xffff, &addr[2]); |
| #else |
| lpc43_write16(memval & 0xffff, &addr[0]); |
| lpc43_write16(memval >> 16, &addr[2]); |
| #endif |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: lpc43_swap16 |
| * |
| * Description: |
| * Swap bytes on a 16-bit value |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_ENDIAN_BIG |
| static uint16_t lpc43_swap16(uint16_t value) |
| { |
| return ((value >> 8) & 0xff) | ((value & 0xff) << 8); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: lpc43_swap32 |
| * |
| * Description: |
| * Swap bytes on a 32-bit value |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_ENDIAN_BIG |
| static uint32_t lpc43_swap32(uint32_t value) |
| { |
| return (uint32_t)lpc43_swap16((uint16_t)((value >> 16) & 0xffff)) | |
| (uint32_t)lpc43_swap16((uint16_t)(value & 0xffff)) << 16; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: lpc43_printreg |
| * |
| * Description: |
| * Print the contents of a LPC43 EHCI register |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_LPC43_EHCI_REGDEBUG |
| static void lpc43_printreg(volatile uint32_t *regaddr, uint32_t regval, |
| bool iswrite) |
| { |
| uinfo("%08x%s%08x\n", (uintptr_t)regaddr, iswrite ? "<-" : "->", regval); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: lpc43_checkreg |
| * |
| * Description: |
| * Check if it is time to output debug information for accesses to a LPC43 |
| * EHCI register |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_LPC43_EHCI_REGDEBUG |
| static void lpc43_checkreg(volatile uint32_t *regaddr, uint32_t regval, |
| bool iswrite) |
| { |
| static uint32_t *prevaddr = NULL; |
| static uint32_t preval = 0; |
| static uint32_t count = 0; |
| static bool prevwrite = false; |
| |
| /* Is this the same value that we read from/wrote to the same register |
| * last time? Are we polling the register? If so, suppress the output. |
| */ |
| |
| if (regaddr == prevaddr && regval == 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 */ |
| |
| lpc43_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 = (uint32_t *)regaddr; |
| preval = regval; |
| count = 0; |
| prevwrite = iswrite; |
| |
| /* Show the new register access */ |
| |
| lpc43_printreg(regaddr, regval, iswrite); |
| } |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: lpc43_getreg |
| * |
| * Description: |
| * Get the contents of an LPC43 register |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_LPC43_EHCI_REGDEBUG |
| static uint32_t lpc43_getreg(volatile uint32_t *regaddr) |
| { |
| /* Read the value from the register */ |
| |
| uint32_t regval = *regaddr; |
| |
| /* Check if we need to print this value */ |
| |
| lpc43_checkreg(regaddr, regval, false); |
| return regval; |
| } |
| #else |
| static inline uint32_t lpc43_getreg(volatile uint32_t *regaddr) |
| { |
| return *regaddr; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: lpc43_putreg |
| * |
| * Description: |
| * Set the contents of an LPC43 register to a value |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_LPC43_EHCI_REGDEBUG |
| static void lpc43_putreg(uint32_t regval, volatile uint32_t *regaddr) |
| { |
| /* Check if we need to print this value */ |
| |
| lpc43_checkreg(regaddr, regval, true); |
| |
| /* Write the value */ |
| |
| *regaddr = regval; |
| } |
| #else |
| static inline void lpc43_putreg(uint32_t regval, volatile uint32_t *regaddr) |
| { |
| *regaddr = regval; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: ehci_wait_usbsts |
| * |
| * Description: |
| * Wait for either (1) a field in the USBSTS register to take a specific |
| * value, (2) for a timeout to occur, or (3) a error to occur. Return |
| * a value to indicate which terminated the wait. |
| * |
| ****************************************************************************/ |
| |
| static int ehci_wait_usbsts(uint32_t maskbits, uint32_t donebits, |
| unsigned int delay) |
| { |
| uint32_t regval; |
| unsigned int timeout; |
| |
| timeout = 0; |
| do |
| { |
| /* Wait 5usec before trying again */ |
| |
| up_udelay(5); |
| timeout += 5; |
| |
| /* Read the USBSTS register and check for a system error */ |
| |
| regval = lpc43_getreg(&HCOR->usbsts); |
| if ((regval & EHCI_INT_SYSERROR) != 0) |
| { |
| usbhost_trace1(EHCI_TRACE1_SYSTEMERROR, regval); |
| return -EIO; |
| } |
| |
| /* Mask out the bits of interest */ |
| |
| regval &= maskbits; |
| |
| /* Loop until the masked bits take the specified value or until a |
| * timeout occurs. |
| */ |
| } |
| while (regval != donebits && timeout < delay); |
| |
| /* We got here because either the waited for condition or a timeout |
| * occurred. Return a value to indicate which. |
| */ |
| |
| return (regval == donebits) ? OK : -ETIMEDOUT; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_qh_alloc |
| * |
| * Description: |
| * Allocate a Queue Head (QH) structure by removing it from the free list |
| * |
| * Assumption: Caller holds the lock |
| * |
| ****************************************************************************/ |
| |
| static struct lpc43_qh_s *lpc43_qh_alloc(void) |
| { |
| struct lpc43_qh_s *qh; |
| |
| /* Remove the QH structure from the freelist */ |
| |
| qh = (struct lpc43_qh_s *)g_ehci.qhfree; |
| if (qh) |
| { |
| g_ehci.qhfree = ((struct lpc43_list_s *)qh)->flink; |
| memset(qh, 0, sizeof(struct lpc43_qh_s)); |
| } |
| |
| return qh; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_qh_free |
| * |
| * Description: |
| * Free a Queue Head (QH) structure by returning it to the free list |
| * |
| * Assumption: Caller holds the lock |
| * |
| ****************************************************************************/ |
| |
| static void lpc43_qh_free(struct lpc43_qh_s *qh) |
| { |
| struct lpc43_list_s *entry = (struct lpc43_list_s *)qh; |
| |
| /* Put the QH structure back into the free list */ |
| |
| entry->flink = g_ehci.qhfree; |
| g_ehci.qhfree = entry; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_qtd_alloc |
| * |
| * Description: |
| * Allocate a Queue Element Transfer Descriptor (qTD) by removing it from |
| * the free list |
| * |
| * Assumption: Caller holds the lock |
| * |
| ****************************************************************************/ |
| |
| static struct lpc43_qtd_s *lpc43_qtd_alloc(void) |
| { |
| struct lpc43_qtd_s *qtd; |
| |
| /* Remove the qTD from the freelist */ |
| |
| qtd = (struct lpc43_qtd_s *)g_ehci.qtdfree; |
| if (qtd) |
| { |
| g_ehci.qtdfree = ((struct lpc43_list_s *)qtd)->flink; |
| memset(qtd, 0, sizeof(struct lpc43_qtd_s)); |
| } |
| |
| return qtd; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_qtd_free |
| * |
| * Description: |
| * Free a Queue Element Transfer Descriptor (qTD) by returning it to the |
| * free list |
| * |
| * Assumption: Caller holds the lock |
| * |
| ****************************************************************************/ |
| |
| static void lpc43_qtd_free(struct lpc43_qtd_s *qtd) |
| { |
| struct lpc43_list_s *entry = (struct lpc43_list_s *)qtd; |
| |
| /* Put the qTD back into the free list */ |
| |
| entry->flink = g_ehci.qtdfree; |
| g_ehci.qtdfree = entry; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_qh_foreach |
| * |
| * Description: |
| * Give the first entry in a list of Queue Head (QH) structures, call the |
| * handler for each QH structure in the list (including the one at the head |
| * of the list). |
| * |
| ****************************************************************************/ |
| |
| static int lpc43_qh_foreach(struct lpc43_qh_s *qh, uint32_t **bp, |
| foreach_qh_t handler, void *arg) |
| { |
| struct lpc43_qh_s *next; |
| uintptr_t physaddr; |
| int ret; |
| |
| DEBUGASSERT(qh && handler); |
| while (qh) |
| { |
| /* Is this the end of the list? Check the horizontal link pointer |
| * (HLP) terminate (T) bit. If T==1, then the HLP address is not |
| * valid. |
| */ |
| |
| physaddr = lpc43_swap32(qh->hw.hlp); |
| if ((physaddr & QH_HLP_T) != 0) |
| { |
| /* Set the next pointer to NULL. This will terminate the loop. */ |
| |
| next = NULL; |
| } |
| |
| /* Is the next QH the asynchronous list head which will always be at |
| * the end of the asynchronous queue? |
| */ |
| |
| else if (lpc43_virtramaddr(physaddr & QH_HLP_MASK) == |
| (uintptr_t)&g_asynchead) |
| { |
| /* That will also terminate the loop */ |
| |
| next = NULL; |
| } |
| |
| /* Otherwise, there is a QH structure after this one that describes |
| * another transaction. |
| */ |
| |
| else |
| { |
| physaddr = lpc43_swap32(qh->hw.hlp) & QH_HLP_MASK; |
| next = (struct lpc43_qh_s *)lpc43_virtramaddr(physaddr); |
| } |
| |
| /* Perform the user action on this entry. The action might result in |
| * unlinking the entry! But that is okay because we already have the |
| * next QH pointer. |
| * |
| * Notice that we do not manage the back pointer (bp). If the callout |
| * uses it, it must update it as necessary. |
| */ |
| |
| ret = handler(qh, bp, arg); |
| |
| /* If the handler returns any non-zero value, then terminate the |
| * traversal early. |
| */ |
| |
| if (ret != 0) |
| { |
| return ret; |
| } |
| |
| /* Set up to visit the next entry */ |
| |
| qh = next; |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_qtd_foreach |
| * |
| * Description: |
| * Give a Queue Head (QH) instance, call the handler for each qTD structure |
| * in the queue. |
| * |
| ****************************************************************************/ |
| |
| static int lpc43_qtd_foreach(struct lpc43_qh_s *qh, foreach_qtd_t handler, |
| void *arg) |
| { |
| struct lpc43_qtd_s *qtd; |
| struct lpc43_qtd_s *next; |
| uintptr_t physaddr; |
| uint32_t *bp; |
| int ret; |
| |
| DEBUGASSERT(qh && handler); |
| |
| /* Handle the special case where the queue is empty */ |
| |
| bp = &qh->fqp; /* Start of qTDs in original list */ |
| physaddr = lpc43_swap32(*bp); /* Physical address of first qTD in CPU order */ |
| |
| if ((physaddr & QTD_NQP_T) != 0) |
| { |
| return 0; |
| } |
| |
| /* Start with the first qTD in the list */ |
| |
| qtd = (struct lpc43_qtd_s *)lpc43_virtramaddr(physaddr); |
| next = NULL; |
| |
| /* And loop until we encounter the end of the qTD list */ |
| |
| while (qtd) |
| { |
| /* Is this the end of the list? Check the next qTD pointer (NQP) |
| * terminate (T) bit. If T==1, then the NQP address is not valid. |
| */ |
| |
| if ((lpc43_swap32(qtd->hw.nqp) & QTD_NQP_T) != 0) |
| { |
| /* Set the next pointer to NULL. This will terminate the loop. */ |
| |
| next = NULL; |
| } |
| else |
| { |
| physaddr = lpc43_swap32(qtd->hw.nqp) & QTD_NQP_NTEP_MASK; |
| next = (struct lpc43_qtd_s *)lpc43_virtramaddr(physaddr); |
| } |
| |
| /* Perform the user action on this entry. The action might result in |
| * unlinking the entry! But that is okay because we already have the |
| * next qTD pointer. |
| * |
| * Notice that we do not manage the back pointer (bp). If the call- |
| * out uses it, it must update it as necessary. |
| */ |
| |
| ret = handler(qtd, &bp, arg); |
| |
| /* If the handler returns any non-zero value, then terminate the |
| * traversal early. |
| */ |
| |
| if (ret != 0) |
| { |
| return ret; |
| } |
| |
| /* Set up to visit the next entry */ |
| |
| qtd = next; |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_qtd_discard |
| * |
| * Description: |
| * This is a lpc43_qtd_foreach callback. It simply unlinks the QTD, |
| * updates the back pointer, and frees the QTD structure. |
| * |
| ****************************************************************************/ |
| |
| static int lpc43_qtd_discard(struct lpc43_qtd_s *qtd, uint32_t **bp, |
| void *arg) |
| { |
| DEBUGASSERT(qtd && bp && *bp); |
| |
| /* Remove the qTD from the list by updating the forward pointer to skip |
| * around this qTD. We do not change that pointer because are repeatedly |
| * removing the aTD at the head of the QH list. |
| */ |
| |
| **bp = qtd->hw.nqp; |
| |
| /* Then free the qTD */ |
| |
| lpc43_qtd_free(qtd); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_qh_discard |
| * |
| * Description: |
| * Free the Queue Head (QH) and all qTD's attached to the QH. |
| * |
| * Assumptions: |
| * The QH structure itself has already been unlinked from whatever list it |
| * may have been in. |
| * |
| ****************************************************************************/ |
| |
| static int lpc43_qh_discard(struct lpc43_qh_s *qh) |
| { |
| int ret; |
| |
| DEBUGASSERT(qh); |
| |
| /* Free all of the qTD's attached to the QH */ |
| |
| ret = lpc43_qtd_foreach(qh, lpc43_qtd_discard, NULL); |
| if (ret < 0) |
| { |
| usbhost_trace1(EHCI_TRACE1_QTDFOREACH_FAILED, -ret); |
| } |
| |
| /* Then free the QH itself */ |
| |
| lpc43_qh_free(qh); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_qtd_print |
| * |
| * Description: |
| * Print the context of one qTD |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_LPC43_EHCI_REGDEBUG |
| static void lpc43_qtd_print(struct lpc43_qtd_s *qtd) |
| { |
| uinfo(" QTD[%p]:\n", qtd); |
| uinfo(" hw:\n"); |
| uinfo(" nqp: %08x alt: %08x token: %08x\n", |
| qtd->hw.nqp, qtd->hw.alt, qtd->hw.token); |
| uinfo(" bpl: %08x %08x %08x %08x %08x\n", |
| qtd->hw.bpl[0], qtd->hw.bpl[1], qtd->hw.bpl[2], |
| qtd->hw.bpl[3], qtd->hw.bpl[4]); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: lpc43_qh_print |
| * |
| * Description: |
| * Print the context of one QH |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_LPC43_EHCI_REGDEBUG |
| static void lpc43_qh_print(struct lpc43_qh_s *qh) |
| { |
| struct lpc43_epinfo_s *epinfo; |
| struct ehci_overlay_s *overlay; |
| |
| uinfo("QH[%p]:\n", qh); |
| uinfo(" hw:\n"); |
| uinfo(" hlp: %08x epchar: %08x epcaps: %08x cqp: %08x\n", |
| qh->hw.hlp, qh->hw.epchar, qh->hw.epcaps, qh->hw.cqp); |
| |
| overlay = &qh->hw.overlay; |
| uinfo(" overlay:\n"); |
| uinfo(" nqp: %08x alt: %08x token: %08x\n", |
| overlay->nqp, overlay->alt, overlay->token); |
| uinfo(" bpl: %08x %08x %08x %08x %08x\n", |
| overlay->bpl[0], overlay->bpl[1], overlay->bpl[2], |
| overlay->bpl[3], overlay->bpl[4]); |
| |
| uinfo(" fqp:\n", qh->fqp); |
| |
| epinfo = qh->epinfo; |
| uinfo(" epinfo[%p]:\n", epinfo); |
| if (epinfo) |
| { |
| uinfo(" EP%d DIR=%s FA=%08x TYPE=%d MaxPacket=%d\n", |
| epinfo->epno, epinfo->dirin ? "IN" : "OUT", epinfo->devaddr, |
| epinfo->xfrtype, epinfo->maxpacket); |
| uinfo(" Toggle=%d iocwait=%d speed=%d result=%d\n", |
| epinfo->toggle, epinfo->iocwait, epinfo->speed, epinfo->result); |
| } |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: lpc43_qtd_dump |
| * |
| * Description: |
| * This is a lpc43_qtd_foreach call-out function. It dumps the context of |
| * one qTD |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_LPC43_EHCI_REGDEBUG |
| static int lpc43_qtd_dump(struct lpc43_qtd_s *qtd, uint32_t **bp, void *arg) |
| { |
| lpc43_qtd_print(qtd); |
| return OK; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: lpc43_qh_dump |
| * |
| * Description: |
| * This is a lpc43_qh_foreach callout function. It dumps a QH structure |
| * and all of the qTD structures linked to the QH. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_LPC43_EHCI_REGDEBUG |
| static int lpc43_qh_dump(struct lpc43_qh_s *qh, uint32_t **bp, void *arg) |
| { |
| lpc43_qh_print(qh); |
| return lpc43_qtd_foreach(qh, lpc43_qtd_dump, NULL); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: lpc43_ehci_speed |
| * |
| * Description: |
| * Map a speed enumeration value per Chapter 9 of the USB specification to |
| * the speed enumeration required in the EHCI queue head. |
| * |
| ****************************************************************************/ |
| |
| static inline uint8_t lpc43_ehci_speed(uint8_t usbspeed) |
| { |
| DEBUGASSERT(usbspeed >= USB_SPEED_LOW && usbspeed <= USB_SPEED_HIGH); |
| return g_ehci_speed[usbspeed]; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_ioc_setup |
| * |
| * Description: |
| * Set the request for the IOC 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! |
| * |
| * Assumption: The caller holds tex EHCI lock |
| * |
| ****************************************************************************/ |
| |
| static int lpc43_ioc_setup(struct lpc43_rhport_s *rhport, |
| struct lpc43_epinfo_s *epinfo) |
| { |
| irqstate_t flags; |
| int ret = -ENODEV; |
| |
| DEBUGASSERT(rhport && epinfo && !epinfo->iocwait); |
| #ifdef CONFIG_USBHOST_ASYNCH |
| DEBUGASSERT(epinfo->callback == NULL); |
| #endif |
| |
| /* Is the device still connected? */ |
| |
| flags = enter_critical_section(); |
| if (rhport->connected) |
| { |
| /* Then set iocwait to indicate that we expect to be informed when |
| * either (1) the device is disconnected, or (2) the transfer |
| * completed. |
| */ |
| |
| epinfo->iocwait = true; /* We want to be awakened by IOC interrupt */ |
| epinfo->status = 0; /* No status yet */ |
| epinfo->xfrd = 0; /* Nothing transferred yet */ |
| epinfo->result = -EBUSY; /* Transfer in progress */ |
| #ifdef CONFIG_USBHOST_ASYNCH |
| epinfo->callback = NULL; /* No asynchronous callback */ |
| epinfo->arg = NULL; |
| #endif |
| ret = OK; /* We are good to go */ |
| } |
| |
| leave_critical_section(flags); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_ioc_wait |
| * |
| * Description: |
| * Wait for the IOC event. |
| * |
| * Assumption: The caller does *NOT* hold the EHCI lock. That would |
| * cause a deadlock when the bottom-half, worker thread needs to take the |
| * semaphore. |
| * |
| ****************************************************************************/ |
| |
| static int lpc43_ioc_wait(struct lpc43_epinfo_s *epinfo) |
| { |
| int ret = OK; |
| |
| /* Wait for the IOC event. Loop to handle any false alarm semaphore |
| * counts. Return an error if the task is canceled. |
| */ |
| |
| while (epinfo->iocwait) |
| { |
| ret = nxsem_wait_uninterruptible(&epinfo->iocsem); |
| if (ret < 0) |
| { |
| break; |
| } |
| } |
| |
| return ret < 0 ? ret : epinfo->result; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_qh_enqueue |
| * |
| * Description: |
| * Add a new, ready-to-go QH w/attached qTDs to the asynchronous queue. |
| * |
| * Assumptions: The caller holds the EHCI lock |
| * |
| ****************************************************************************/ |
| |
| static void lpc43_qh_enqueue(struct lpc43_qh_s *qhead, struct lpc43_qh_s *qh) |
| { |
| uintptr_t physaddr; |
| |
| /* Set the internal fqp field. When we transverse the QH list later, |
| * we need to know the correct place to start because the overlay may no |
| * longer point to the first qTD entry. |
| */ |
| |
| qh->fqp = qh->hw.overlay.nqp; |
| lpc43_qh_dump(qh, NULL, NULL); |
| |
| /* Add the new QH to the head of the asynchronous queue list. |
| * |
| * First, attach the old head as the new QH HLP and flush the new QH and |
| * its attached qTDs to RAM. |
| */ |
| |
| qh->hw.hlp = qhead->hw.hlp; |
| |
| /* Then set the new QH as the first QH in the asynchronous queue */ |
| |
| physaddr = (uintptr_t)lpc43_physramaddr((uintptr_t)qh); |
| qhead->hw.hlp = lpc43_swap32(physaddr | QH_HLP_TYP_QH); |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_qh_create |
| * |
| * Description: |
| * Create a new Queue Head (QH) |
| * |
| ****************************************************************************/ |
| |
| static struct lpc43_qh_s *lpc43_qh_create(struct lpc43_rhport_s *rhport, |
| struct lpc43_epinfo_s *epinfo) |
| { |
| struct lpc43_qh_s *qh; |
| uint32_t rhpndx; |
| uint32_t regval; |
| uint8_t hubaddr; |
| uint8_t hubport; |
| |
| /* Allocate a new queue head structure */ |
| |
| qh = lpc43_qh_alloc(); |
| if (qh == NULL) |
| { |
| usbhost_trace1(EHCI_TRACE1_QHALLOC_FAILED, 0); |
| return NULL; |
| } |
| |
| /* Save the endpoint information with the QH itself */ |
| |
| qh->epinfo = epinfo; |
| |
| /* Write QH endpoint characteristics: |
| * |
| * FIELD DESCRIPTION VALUE/SOURCE |
| * -------- ------------------------------- -------------------- |
| * DEVADDR Device address Endpoint structure |
| * I Inactivate on Next Transaction 0 |
| * ENDPT Endpoint number Endpoint structure |
| * EPS Endpoint speed Endpoint structure |
| * DTC Data toggle control 1 |
| * MAXPKT Max packet size Endpoint structure |
| * C Control endpoint Calculated |
| * RL NAK count reloaded 8 |
| */ |
| |
| regval = ((uint32_t)epinfo->devaddr << QH_EPCHAR_DEVADDR_SHIFT) | |
| ((uint32_t)epinfo->epno << QH_EPCHAR_ENDPT_SHIFT) | |
| ((uint32_t)lpc43_ehci_speed(epinfo->speed) << |
| QH_EPCHAR_EPS_SHIFT) | |
| QH_EPCHAR_DTC | |
| ((uint32_t)epinfo->maxpacket << QH_EPCHAR_MAXPKT_SHIFT) | |
| ((uint32_t)8 << QH_EPCHAR_RL_SHIFT); |
| |
| /* Paragraph 3.6.3: "Control Endpoint Flag (C). If the QH.EPS field |
| * indicates the endpoint is not a high-speed device, and the endpoint |
| * is an control endpoint, then software must set this bit to a one. |
| * Otherwise it should always set this bit to a zero." |
| */ |
| |
| if (epinfo->speed != USB_SPEED_HIGH && |
| epinfo->xfrtype == USB_EP_ATTR_XFER_CONTROL) |
| { |
| regval |= QH_EPCHAR_C; |
| } |
| |
| /* Save the endpoint characteristics word with the correct byte order */ |
| |
| qh->hw.epchar = lpc43_swap32(regval); |
| |
| /* Write QH endpoint capabilities |
| * |
| * FIELD DESCRIPTION VALUE/SOURCE |
| * -------- ------------------------------- -------------------- |
| * SSMASK Interrupt Schedule Mask Depends on epinfo->xfrtype |
| * SCMASK Split Completion Mask 0 |
| * HUBADDR Hub Address Always 0 for now |
| * PORT Port number RH port index + 1 |
| * MULT High band width multiplier 1 |
| */ |
| |
| rhpndx = RHPNDX(rhport); |
| |
| #ifdef CONFIG_USBHOST_HUB |
| /* REVISIT: Future HUB support will require the HUB port number |
| * and HUB device address to be included here: |
| * |
| * - The HUB device address is the USB device address of the USB 2.0 Hub |
| * below which a full- or low-speed device is attached. |
| * - The HUB port number is the port number on the above USB 2.0 Hub |
| * |
| * These fields are used in the split-transaction protocol. The kludge |
| * below should work for hubs connected directly to a root hub port, |
| * but would not work for devices connected to downstream hubs. |
| */ |
| |
| #warning Missing logic |
| hubaddr = rhport->ep0.devaddr; |
| hubport = rhpndx + 1; |
| #else |
| hubaddr = rhport->ep0.devaddr; |
| hubport = rhpndx + 1; |
| #endif |
| |
| regval = ((uint32_t)hubaddr << QH_EPCAPS_HUBADDR_SHIFT) | |
| ((uint32_t)hubport << QH_EPCAPS_PORT_SHIFT) | |
| ((uint32_t)1 << QH_EPCAPS_MULT_SHIFT); |
| |
| #ifndef CONFIG_USBHOST_INT_DISABLE |
| if (epinfo->xfrtype == USB_EP_ATTR_XFER_INT) |
| { |
| /* Here, the S-Mask field in the queue head is set to 1, indicating |
| * that the transaction for the endpoint should be executed on the bus |
| * during micro-frame 0 of the frame. |
| * |
| * REVISIT: The polling interval should be controlled by the which |
| * entry is the framelist holds the QH pointer for a given micro-frame |
| * and the QH pointer should be replicated for different polling rates. |
| * This implementation currently just sets all frame_list entry to |
| * all the same interrupt queue. That should work but will not give |
| * any control over polling rates. |
| */ |
| #warning REVISIT |
| |
| regval |= ((uint32_t)1 << QH_EPCAPS_SSMASK_SHIFT); |
| } |
| #endif |
| |
| qh->hw.epcaps = lpc43_swap32(regval); |
| |
| /* Mark this as the end of this list. This will be overwritten if/when the |
| * next qTD is added to the queue. |
| */ |
| |
| qh->hw.hlp = lpc43_swap32(QH_HLP_T); |
| qh->hw.overlay.nqp = lpc43_swap32(QH_NQP_T); |
| qh->hw.overlay.alt = lpc43_swap32(QH_AQP_T); |
| return qh; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_qtd_addbpl |
| * |
| * Description: |
| * Add a buffer pointer list to a qTD. |
| * |
| ****************************************************************************/ |
| |
| static int lpc43_qtd_addbpl(struct lpc43_qtd_s *qtd, const void *buffer, |
| size_t buflen) |
| { |
| uint32_t physaddr; |
| uint32_t nbytes; |
| uint32_t next; |
| int ndx; |
| |
| /* Loop, adding the aligned physical addresses of the buffer to the buffer |
| * page list. Only the first entry need not be aligned (because only the |
| * first entry has the offset field). The subsequent entries must begin on |
| * 4KB address boundaries. |
| */ |
| |
| physaddr = (uint32_t)lpc43_physramaddr((uintptr_t)buffer); |
| |
| for (ndx = 0; ndx < 5; ndx++) |
| { |
| /* Write the physical address of the buffer into the qTD buffer |
| * pointer list. |
| */ |
| |
| qtd->hw.bpl[ndx] = lpc43_swap32(physaddr); |
| |
| /* Get the next buffer pointer (in the case where we will have to |
| * transfer more then one chunk). This buffer must be aligned to a |
| * 4KB address boundary. |
| */ |
| |
| next = (physaddr + 4096) & ~4095; |
| |
| /* How many bytes were included in the last buffer? Was it the whole |
| * thing? |
| */ |
| |
| nbytes = next - physaddr; |
| if (nbytes >= buflen) |
| { |
| /* Yes... it was the whole thing. Break out of the loop early. */ |
| |
| break; |
| } |
| |
| /* Adjust the buffer length and physical address for the next time |
| * through the loop. |
| */ |
| |
| buflen -= nbytes; |
| physaddr = next; |
| } |
| |
| /* Handle the case of a huge buffer > 4*4KB = 16KB */ |
| |
| if (ndx >= 5) |
| { |
| usbhost_trace1(EHCI_TRACE1_BUFTOOBIG, buflen); |
| return -EFBIG; |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_qtd_setupphase |
| * |
| * Description: |
| * Create a SETUP phase request qTD. |
| * |
| ****************************************************************************/ |
| |
| static struct lpc43_qtd_s * |
| lpc43_qtd_setupphase(struct lpc43_epinfo_s *epinfo, |
| const struct usb_ctrlreq_s *req) |
| { |
| struct lpc43_qtd_s *qtd; |
| uint32_t regval; |
| int ret; |
| |
| /* Allocate a new Queue Element Transfer Descriptor (qTD) */ |
| |
| qtd = lpc43_qtd_alloc(); |
| if (qtd == NULL) |
| { |
| usbhost_trace1(EHCI_TRACE1_REQQTDALLOC_FAILED, 0); |
| return NULL; |
| } |
| |
| /* Mark this as the end of the list (this will be overwritten if another |
| * qTD is added after this one). |
| */ |
| |
| qtd->hw.nqp = lpc43_swap32(QTD_NQP_T); |
| qtd->hw.alt = lpc43_swap32(QTD_AQP_T); |
| |
| /* Write qTD token: |
| * |
| * FIELD DESCRIPTION VALUE/SOURCE |
| * -------- ------------------------------- -------------------- |
| * STATUS Status QTD_TOKEN_ACTIVE |
| * PID PID Code QTD_TOKEN_PID_SETUP |
| * CERR Error Counter 3 |
| * CPAGE Current Page 0 |
| * IOC Interrupt on complete 0 |
| * NBYTES Total Bytes to Transfer USB_SIZEOF_CTRLREQ |
| * TOGGLE Data Toggle 0 |
| */ |
| |
| regval = QTD_TOKEN_ACTIVE | QTD_TOKEN_PID_SETUP | |
| ((uint32_t)3 << QTD_TOKEN_CERR_SHIFT) | |
| ((uint32_t)USB_SIZEOF_CTRLREQ << QTD_TOKEN_NBYTES_SHIFT); |
| |
| qtd->hw.token = lpc43_swap32(regval); |
| |
| /* Add the buffer data */ |
| |
| ret = lpc43_qtd_addbpl(qtd, req, USB_SIZEOF_CTRLREQ); |
| if (ret < 0) |
| { |
| usbhost_trace1(EHCI_TRACE1_ADDBPL_FAILED, -ret); |
| lpc43_qtd_free(qtd); |
| return NULL; |
| } |
| |
| /* Add the data transfer size to the count in the epinfo structure */ |
| |
| epinfo->xfrd += USB_SIZEOF_CTRLREQ; |
| |
| return qtd; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_qtd_dataphase |
| * |
| * Description: |
| * Create a data transfer or SET data phase qTD. |
| * |
| ****************************************************************************/ |
| |
| static struct lpc43_qtd_s *lpc43_qtd_dataphase(struct lpc43_epinfo_s *epinfo, |
| void *buffer, int buflen, |
| uint32_t tokenbits) |
| { |
| struct lpc43_qtd_s *qtd; |
| uint32_t regval; |
| int ret; |
| |
| /* Allocate a new Queue Element Transfer Descriptor (qTD) */ |
| |
| qtd = lpc43_qtd_alloc(); |
| if (qtd == NULL) |
| { |
| usbhost_trace1(EHCI_TRACE1_DATAQTDALLOC_FAILED, 0); |
| return NULL; |
| } |
| |
| /* Mark this as the end of the list (this will be overwritten if another |
| * qTD is added after this one). |
| */ |
| |
| qtd->hw.nqp = lpc43_swap32(QTD_NQP_T); |
| qtd->hw.alt = lpc43_swap32(QTD_AQP_T); |
| |
| /* Write qTD token: |
| * |
| * FIELD DESCRIPTION VALUE/SOURCE |
| * -------- ------------------------------- -------------------- |
| * STATUS Status QTD_TOKEN_ACTIVE |
| * PID PID Code Contained in tokenbits |
| * CERR Error Counter 3 |
| * CPAGE Current Page 0 |
| * IOC Interrupt on complete Contained in tokenbits |
| * NBYTES Total Bytes to Transfer buflen |
| * TOGGLE Data Toggle Contained in tokenbits |
| */ |
| |
| regval = tokenbits | QTD_TOKEN_ACTIVE | |
| ((uint32_t)3 << QTD_TOKEN_CERR_SHIFT) | |
| ((uint32_t)buflen << QTD_TOKEN_NBYTES_SHIFT); |
| |
| qtd->hw.token = lpc43_swap32(regval); |
| |
| /* Add the buffer information to the buffer pointer list */ |
| |
| ret = lpc43_qtd_addbpl(qtd, buffer, buflen); |
| if (ret < 0) |
| { |
| usbhost_trace1(EHCI_TRACE1_ADDBPL_FAILED, -ret); |
| lpc43_qtd_free(qtd); |
| return NULL; |
| } |
| |
| /* Add the data transfer size to the count in the epinfo structure */ |
| |
| epinfo->xfrd += buflen; |
| |
| return qtd; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_qtd_statusphase |
| * |
| * Description: |
| * Create a STATUS phase request qTD. |
| * |
| ****************************************************************************/ |
| |
| static struct lpc43_qtd_s *lpc43_qtd_statusphase(uint32_t tokenbits) |
| { |
| struct lpc43_qtd_s *qtd; |
| uint32_t regval; |
| |
| /* Allocate a new Queue Element Transfer Descriptor (qTD) */ |
| |
| qtd = lpc43_qtd_alloc(); |
| if (qtd == NULL) |
| { |
| usbhost_trace1(EHCI_TRACE1_REQQTDALLOC_FAILED, 0); |
| return NULL; |
| } |
| |
| /* Mark this as the end of the list (this will be overwritten if another |
| * qTD is added after this one). |
| */ |
| |
| qtd->hw.nqp = lpc43_swap32(QTD_NQP_T); |
| qtd->hw.alt = lpc43_swap32(QTD_AQP_T); |
| |
| /* Write qTD token: |
| * |
| * FIELD DESCRIPTION VALUE/SOURCE |
| * -------- ------------------------------- -------------------- |
| * STATUS Status QTD_TOKEN_ACTIVE |
| * PID PID Code Contained in tokenbits |
| * CERR Error Counter 3 |
| * CPAGE Current Page 0 |
| * IOC Interrupt on complete QTD_TOKEN_IOC |
| * NBYTES Total Bytes to Transfer 0 |
| * TOGGLE Data Toggle Contained in tokenbits |
| */ |
| |
| regval = tokenbits | QTD_TOKEN_ACTIVE | QTD_TOKEN_IOC | |
| ((uint32_t)3 << QTD_TOKEN_CERR_SHIFT); |
| |
| qtd->hw.token = lpc43_swap32(regval); |
| return qtd; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_async_setup |
| * |
| * Description: |
| * Process a IN or OUT request on any asynchronous endpoint (bulk or |
| * control). This function will enqueue the request and wait for it to |
| * complete. Bulk data transfers differ in that req == NULL and there are |
| * not SETUP or STATUS phases. |
| * |
| * This is a blocking function; it will not return until the control |
| * transfer has completed. |
| * |
| * Assumption: The caller holds the EHCI lock. |
| * |
| * Returned Value: |
| * Zero (OK) is returned on success; a negated errno value is return on |
| * any failure. |
| * |
| ****************************************************************************/ |
| |
| static int lpc43_async_setup(struct lpc43_rhport_s *rhport, |
| struct lpc43_epinfo_s *epinfo, |
| const struct usb_ctrlreq_s *req, |
| uint8_t *buffer, size_t buflen) |
| { |
| struct lpc43_qh_s *qh; |
| struct lpc43_qtd_s *qtd; |
| uintptr_t physaddr; |
| uint32_t *flink; |
| uint32_t *alt; |
| uint32_t toggle; |
| bool dirin = false; |
| int ret; |
| |
| /* Terse output only if we are tracing */ |
| |
| #ifdef CONFIG_USBHOST_TRACE |
| usbhost_vtrace2(EHCI_VTRACE2_ASYNCXFR, epinfo->epno, buflen); |
| #else |
| uinfo("RHport%d EP%d: buffer=%p, buflen=%d, req=%p\n", |
| RHPORT(rhport), epinfo->epno, buffer, buflen, req); |
| #endif |
| |
| DEBUGASSERT(rhport && epinfo); |
| |
| /* A buffer may or may be supplied with an EP0 SETUP transfer. A buffer |
| * will always be present for normal endpoint data transfers. |
| */ |
| |
| DEBUGASSERT(req || (buffer && buflen > 0)); |
| |
| /* Create and initialize a Queue Head (QH) structure for this transfer */ |
| |
| qh = lpc43_qh_create(rhport, epinfo); |
| if (qh == NULL) |
| { |
| usbhost_trace1(EHCI_TRACE1_QHCREATE_FAILED, 0); |
| return -ENOMEM; |
| } |
| |
| /* Initialize the QH link and get the next data toggle (not used for SETUP |
| * transfers) |
| */ |
| |
| flink = &qh->hw.overlay.nqp; |
| toggle = (uint32_t)epinfo->toggle << QTD_TOKEN_TOGGLE_SHIFT; |
| ret = -EIO; |
| |
| /* Is there an EP0 SETUP request? If so, req will be non-NULL and we will |
| * queue two or three qTDs: |
| * |
| * 1) One for the SETUP phase, |
| * 2) One for the DATA phase (if there is data), and |
| * 3) One for the STATUS phase. |
| * |
| * If this is not an EP0 SETUP request, then only a data transfer will be |
| * enqueued. |
| */ |
| |
| if (req != NULL) |
| { |
| /* Allocate a new Queue Element Transfer Descriptor (qTD) for the SETUP |
| * phase of the request sequence. |
| */ |
| |
| qtd = lpc43_qtd_setupphase(epinfo, req); |
| if (qtd == NULL) |
| { |
| usbhost_trace1(EHCI_TRACE1_QTDSETUP_FAILED, 0); |
| goto errout_with_qh; |
| } |
| |
| /* Link the new qTD to the QH head. */ |
| |
| physaddr = lpc43_physramaddr((uintptr_t)qtd); |
| *flink = lpc43_swap32(physaddr); |
| |
| /* Get the new forward link pointer and data toggle */ |
| |
| flink = &qtd->hw.nqp; |
| toggle = QTD_TOKEN_TOGGLE; |
| } |
| |
| /* A buffer may or may be supplied with an EP0 SETUP transfer. A buffer |
| * will always be present for normal endpoint data transfers. |
| */ |
| |
| alt = NULL; |
| if (buffer != NULL && buflen > 0) |
| { |
| uint32_t tokenbits; |
| |
| /* Extra TOKEN bits include the data toggle, the data PID, and if |
| * there is no request, an indication to interrupt at the end of this |
| * transfer. |
| */ |
| |
| tokenbits = toggle; |
| |
| /* Get the data token direction. |
| * |
| * If this is a SETUP request, use the direction contained in the |
| * request. The IOC bit is not set. |
| */ |
| |
| if (req) |
| { |
| if ((req->type & USB_REQ_DIR_MASK) == USB_REQ_DIR_IN) |
| { |
| tokenbits |= QTD_TOKEN_PID_IN; |
| dirin = true; |
| } |
| else |
| { |
| tokenbits |= QTD_TOKEN_PID_OUT; |
| dirin = false; |
| } |
| } |
| |
| /* Otherwise, the endpoint is uni-directional. Get the direction from |
| * the epinfo structure. Since this is not an EP0 SETUP request, |
| * nothing follows the data and we want the IOC interrupt when the |
| * data transfer completes. |
| */ |
| |
| else if (epinfo->dirin) |
| { |
| tokenbits |= (QTD_TOKEN_PID_IN | QTD_TOKEN_IOC); |
| dirin = true; |
| } |
| else |
| { |
| tokenbits |= (QTD_TOKEN_PID_OUT | QTD_TOKEN_IOC); |
| dirin = false; |
| } |
| |
| /* Allocate a new Queue Element Transfer Descriptor (qTD) for the data |
| * buffer. |
| */ |
| |
| qtd = lpc43_qtd_dataphase(epinfo, buffer, buflen, tokenbits); |
| if (qtd == NULL) |
| { |
| usbhost_trace1(EHCI_TRACE1_QTDDATA_FAILED, 0); |
| goto errout_with_qh; |
| } |
| |
| /* Link the new qTD to either QH head of the SETUP qTD. */ |
| |
| physaddr = lpc43_physramaddr((uintptr_t)qtd); |
| *flink = lpc43_swap32(physaddr); |
| |
| /* Set the forward link pointer to this new qTD */ |
| |
| flink = &qtd->hw.nqp; |
| |
| /* If this was an IN transfer, then setup a pointer alternate link. |
| * The EHCI hardware will use this link if a short packet is received. |
| */ |
| |
| if (dirin) |
| { |
| alt = &qtd->hw.alt; |
| } |
| } |
| |
| /* If this is an EP0 SETUP request, then enqueue one more qTD for the |
| * STATUS phase transfer. |
| */ |
| |
| if (req != NULL) |
| { |
| /* Extra TOKEN bits include the data toggle and the correct data PID. */ |
| |
| uint32_t tokenbits = toggle; |
| |
| /* The status phase direction is the opposite of the data phase. If |
| * this is an IN request, then we received the buffer and we will send |
| * the zero length packet handshake. |
| */ |
| |
| if ((req->type & USB_REQ_DIR_MASK) == USB_REQ_DIR_IN) |
| { |
| tokenbits |= QTD_TOKEN_PID_OUT; |
| } |
| |
| /* Otherwise, this in an OUT request. We send the buffer and we expect |
| * to receive the NULL packet handshake. |
| */ |
| |
| else |
| { |
| tokenbits |= QTD_TOKEN_PID_IN; |
| } |
| |
| /* Allocate a new Queue Element Transfer Descriptor (qTD) for the |
| * status |
| */ |
| |
| qtd = lpc43_qtd_statusphase(tokenbits); |
| if (qtd == NULL) |
| { |
| usbhost_trace1(EHCI_TRACE1_QTDSTATUS_FAILED, 0); |
| goto errout_with_qh; |
| } |
| |
| /* Link the new qTD to either the SETUP or data qTD. */ |
| |
| physaddr = lpc43_physramaddr((uintptr_t)qtd); |
| *flink = lpc43_swap32(physaddr); |
| |
| /* In an IN data qTD was also enqueued, then linked the data qTD's |
| * alternate pointer to this STATUS phase qTD in order to handle short |
| * transfers. |
| */ |
| |
| if (alt) |
| { |
| *alt = lpc43_swap32(physaddr); |
| } |
| } |
| |
| /* Add the new QH to the head of the asynchronous queue list */ |
| |
| lpc43_qh_enqueue(&g_asynchead, qh); |
| return OK; |
| |
| /* Clean-up after an error */ |
| |
| errout_with_qh: |
| lpc43_qh_discard(qh); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_intr_setup |
| * |
| * Description: |
| * Process a IN or OUT request on any interrupt endpoint by inserting a qTD |
| * into the periodic frame list. |
| * |
| * Paragraph 4.10.7 "Adding Interrupt Queue Heads to the Periodic Schedule" |
| * "The link path(s) from the periodic frame list to a queue head |
| * establishes in which frames a transaction can be executed for the |
| * queue head. Queue heads are linked into the periodic schedule so they |
| * are polled at the appropriate rate. System software sets a bit in a |
| * queue head's S-Mask to indicate which micro-frame with-in a 1 |
| * millisecond period a transaction should be executed for the queue |
| * head. Software must ensure that all queue heads in the periodic |
| * schedule have S-Mask set to a non-zero value. An S-mask with a zero |
| * value in the context of the periodic schedule yields undefined |
| * results. |
| * |
| * "If the desired poll rate is greater than one frame, system software |
| * can use a combination of queue head linking and S-Mask values to |
| * spread interrupts of equal poll rates through the schedule so that the |
| * periodic bandwidth is allocated and managed in the most efficient |
| * manner possible." |
| * |
| * Paragraph 4.6 "Periodic Schedule" |
| * |
| * "The periodic schedule is used to manage all isochronous and interrupt |
| * transfer streams. The base of the periodic schedule is the periodic |
| * frame list. Software links schedule data structures to the periodic |
| * frame list to produce a graph of scheduled data structures. The graph |
| * represents an appropriate sequence of transactions on the USB. ... |
| * isochronous transfers (using iTDs and siTDs) with a period of one are |
| * linked directly to the periodic frame list. Interrupt transfers (are |
| * managed with queue heads) and isochronous streams with periods other |
| * than one are linked following the period-one iTD/siTDs. Interrupt |
| * queue heads are linked into the frame list ordered by poll rate. |
| * Longer poll rates are linked first (e.g. closest to the periodic |
| * frame list), followed by shorter poll rates, with queue heads with a |
| * poll rate of one, on the very end." |
| * |
| * Assumption: The caller holds the EHCI lock. |
| * |
| * Returned Value: |
| * Zero (OK) is returned on success; a negated errno value is return on |
| * any failure. |
| * |
| ****************************************************************************/ |
| |
| #ifndef CONFIG_USBHOST_INT_DISABLE |
| static int lpc43_intr_setup(struct lpc43_rhport_s *rhport, |
| struct lpc43_epinfo_s *epinfo, |
| uint8_t *buffer, size_t buflen) |
| { |
| struct lpc43_qh_s *qh; |
| struct lpc43_qtd_s *qtd; |
| uintptr_t physaddr; |
| uint32_t tokenbits; |
| uint32_t regval; |
| int ret; |
| |
| /* Terse output only if we are tracing */ |
| |
| #ifdef CONFIG_USBHOST_TRACE |
| usbhost_vtrace2(EHCI_VTRACE2_INTRXFR, epinfo->epno, buflen); |
| #else |
| uinfo("RHport%d EP%d: buffer=%p, buflen=%d\n", |
| RHPORT(rhport), epinfo->epno, buffer, buflen); |
| #endif |
| |
| DEBUGASSERT(rhport && epinfo && buffer && buflen > 0); |
| |
| /* Create and initialize a Queue Head (QH) structure for this transfer */ |
| |
| qh = lpc43_qh_create(rhport, epinfo); |
| if (qh == NULL) |
| { |
| usbhost_trace1(EHCI_TRACE1_QHCREATE_FAILED, 0); |
| return -ENOMEM; |
| } |
| |
| /* Extra TOKEN bits include the data toggle, the data PID, and an |
| * indication to interrupt at the end of this transfer. |
| */ |
| |
| tokenbits = (uint32_t)epinfo->toggle << QTD_TOKEN_TOGGLE_SHIFT; |
| |
| /* Get the data token direction. */ |
| |
| if (epinfo->dirin) |
| { |
| tokenbits |= (QTD_TOKEN_PID_IN | QTD_TOKEN_IOC); |
| } |
| else |
| { |
| tokenbits |= (QTD_TOKEN_PID_OUT | QTD_TOKEN_IOC); |
| } |
| |
| /* Allocate a new Queue Element Transfer Descriptor (qTD) for the data |
| * buffer. |
| */ |
| |
| qtd = lpc43_qtd_dataphase(epinfo, buffer, buflen, tokenbits); |
| if (qtd == NULL) |
| { |
| usbhost_trace1(EHCI_TRACE1_QTDDATA_FAILED, 0); |
| ret = -ENOMEM; |
| goto errout_with_qh; |
| } |
| |
| /* Link the new qTD to the QH. */ |
| |
| physaddr = lpc43_physramaddr((uintptr_t)qtd); |
| qh->hw.overlay.nqp = lpc43_swap32(physaddr); |
| |
| /* Disable the periodic schedule */ |
| |
| regval = lpc43_getreg(&HCOR->usbcmd); |
| regval &= ~EHCI_USBCMD_PSEN; |
| lpc43_putreg(regval, &HCOR->usbcmd); |
| |
| /* Add the new QH to the head of the interrupt transfer list */ |
| |
| lpc43_qh_enqueue(&g_intrhead, qh); |
| |
| /* Re-enable the periodic schedule */ |
| |
| regval |= EHCI_USBCMD_PSEN; |
| lpc43_putreg(regval, &HCOR->usbcmd); |
| return OK; |
| |
| /* Clean-up after an error */ |
| |
| errout_with_qh: |
| lpc43_qh_discard(qh); |
| return ret; |
| } |
| #endif /* CONFIG_USBHOST_INT_DISABLE */ |
| |
| /**************************************************************************** |
| * Name: lpc43_transfer_wait |
| * |
| * Description: |
| * Wait for an IN or OUT transfer to complete. |
| * |
| * Assumption: The caller holds the EHCI lock. The caller must be aware |
| * that the EHCI lock will released while waiting for the transfer to |
| * complete, but will be re-acquired when before returning. The state of |
| * EHCI resources could be very different upon return. |
| * |
| * Returned Value: |
| * On success, this function returns the number of bytes actually |
| * transferred. For control transfers, this size includes the size of the |
| * control request plus the size of the data (which could be short); for |
| * bulk transfers, this will be the number of data bytes transfers (which |
| * could be short). |
| * |
| ****************************************************************************/ |
| |
| static ssize_t lpc43_transfer_wait(struct lpc43_epinfo_s *epinfo) |
| { |
| int ret; |
| int ret2; |
| |
| /* Release the EHCI mutex while we wait. Other threads need the |
| * opportunity to access the EHCI resources while we wait. |
| * |
| * REVISIT: Is this safe? NO. This is a bug and needs rethinking. |
| * We need to lock all of the port-resources (not EHCI common) until |
| * the transfer is complete. But we can't use the common EHCI lock |
| * or we will deadlock while waiting (because the working thread that |
| * wakes this thread up needs the lock). |
| */ |
| #warning REVISIT |
| nxmutex_unlock(&g_ehci.lock); |
| |
| /* Wait for the IOC completion event */ |
| |
| ret = lpc43_ioc_wait(epinfo); |
| |
| /* Re-acquire the EHCI mutex. The caller expects to be holding |
| * this upon return. |
| */ |
| |
| ret2 = nxmutex_lock(&g_ehci.lock); |
| if (ret2 < 0) |
| { |
| ret = ret2; |
| } |
| |
| /* Did lpc43_ioc_wait() or nxmutex_lock()report an error? */ |
| |
| if (ret < 0) |
| { |
| usbhost_trace1(EHCI_TRACE1_TRANSFER_FAILED, -ret); |
| epinfo->iocwait = false; |
| return (ssize_t)ret; |
| } |
| |
| /* Transfer completed successfully. Return the number of bytes |
| * transferred. |
| */ |
| |
| return epinfo->xfrd; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_ioc_async_setup |
| * |
| * Description: |
| * Setup to receive an asynchronous notification when a transfer completes. |
| * |
| * Input Parameters: |
| * epinfo - The IN or OUT endpoint descriptor for the device endpoint on |
| * which the transfer will be performed. |
| * callback - The function to be called when the completes |
| * arg - An arbitrary argument that will be provided with the callback. |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * - Called from the interrupt level |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_USBHOST_ASYNCH |
| static inline int lpc43_ioc_async_setup(struct lpc43_rhport_s *rhport, |
| struct lpc43_epinfo_s *epinfo, |
| usbhost_asynch_t callback, |
| void *arg) |
| { |
| irqstate_t flags; |
| int ret = -ENODEV; |
| |
| DEBUGASSERT(rhport && epinfo && !epinfo->iocwait && |
| callback != NULL && epinfo->callback == NULL); |
| |
| /* Is the device still connected? */ |
| |
| flags = enter_critical_section(); |
| if (rhport->connected) |
| { |
| /* Then save callback information to used when either (1) the |
| * device is disconnected, or (2) the transfer completes. |
| */ |
| |
| epinfo->iocwait = false; /* No synchronous wakeup */ |
| epinfo->status = 0; /* No status yet */ |
| epinfo->xfrd = 0; /* Nothing transferred yet */ |
| epinfo->result = -EBUSY; /* Transfer in progress */ |
| epinfo->callback = callback; /* Asynchronous callback */ |
| epinfo->arg = arg; /* Argument that accompanies the callback */ |
| ret = OK; /* We are good to go */ |
| } |
| |
| leave_critical_section(flags); |
| return ret; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: lpc43_asynch_completion |
| * |
| * Description: |
| * This function is called at the interrupt level when an asynchronous |
| * transfer completes. It performs the pending callback. |
| * |
| * Input Parameters: |
| * epinfo - The IN or OUT endpoint descriptor for the device endpoint on |
| * which the transfer was performed. |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * - Called from the interrupt level |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_USBHOST_ASYNCH |
| static void lpc43_asynch_completion(struct lpc43_epinfo_s *epinfo) |
| { |
| usbhost_asynch_t callback; |
| ssize_t nbytes; |
| void *arg; |
| int result; |
| |
| DEBUGASSERT(epinfo != NULL && epinfo->iocwait == false && |
| epinfo->callback != NULL); |
| |
| /* Extract and reset the callback info */ |
| |
| callback = epinfo->callback; |
| arg = epinfo->arg; |
| result = epinfo->result; |
| nbytes = epinfo->xfrd; |
| |
| epinfo->callback = NULL; |
| epinfo->arg = NULL; |
| epinfo->result = OK; |
| epinfo->iocwait = false; |
| |
| /* Then perform the callback. Provide the number of bytes successfully |
| * transferred or the negated errno value in the event of a failure. |
| */ |
| |
| if (result < 0) |
| { |
| nbytes = (ssize_t)result; |
| } |
| |
| callback(arg, nbytes); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: lpc43_qtd_ioccheck |
| * |
| * Description: |
| * This function is a lpc43_qtd_foreach() callback function. It services |
| * one qTD in the asynchronous queue. It removes all of the qTD |
| * structures that are no longer active. |
| * |
| ****************************************************************************/ |
| |
| static int lpc43_qtd_ioccheck(struct lpc43_qtd_s *qtd, uint32_t **bp, |
| void *arg) |
| { |
| struct lpc43_epinfo_s *epinfo = (struct lpc43_epinfo_s *)arg; |
| DEBUGASSERT(qtd && epinfo); |
| |
| lpc43_qtd_print(qtd); |
| |
| /* Remove the qTD from the list |
| * |
| * NOTE that we don't check if the qTD is active nor do we check if there |
| * are any errors reported in the qTD. If the transfer halted due to |
| * an error, then qTDs in the list after the error qTD will still appear |
| * to be active. |
| */ |
| |
| **bp = qtd->hw.nqp; |
| |
| /* Subtract the number of bytes left untransferred. The epinfo->xfrd |
| * field is initialized to the total number of bytes to be transferred |
| * (all qTDs in the list). We subtract out the number of untransferred |
| * bytes on each transfer and the final result will be the number of bytes |
| * actually transferred. |
| */ |
| |
| epinfo->xfrd -= (lpc43_swap32(qtd->hw.token) & QTD_TOKEN_NBYTES_MASK) >> |
| QTD_TOKEN_NBYTES_SHIFT; |
| |
| /* Release this QH by returning it to the free list */ |
| |
| lpc43_qtd_free(qtd); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_qh_ioccheck |
| * |
| * Description: |
| * This function is a lpc43_qh_foreach() callback function. It services |
| * one QH in the asynchronous queue. It check all attached qTD structures |
| * and remove all of the structures that are no longer active. if all of |
| * the qTD structures are removed, then QH itself will also be removed. |
| * |
| ****************************************************************************/ |
| |
| static int lpc43_qh_ioccheck(struct lpc43_qh_s *qh, uint32_t **bp, void *arg) |
| { |
| struct lpc43_epinfo_s *epinfo; |
| uint32_t token; |
| int ret; |
| |
| DEBUGASSERT(qh && bp); |
| |
| lpc43_qh_print(qh); |
| |
| /* Get the endpoint info pointer from the extended QH data. Only the |
| * g_asynchead QH can have a NULL epinfo field. |
| */ |
| |
| epinfo = qh->epinfo; |
| DEBUGASSERT(epinfo); |
| |
| /* Paragraph 3.6.3: "The nine DWords in [the Transfer Overlay] area |
| * represent a transaction working space for the host controller. The |
| * general operational model is that the host controller can detect |
| * whether the overlay area contains a description of an active transfer. |
| * If it does not contain an active transfer, then it follows the Queue |
| * Head Horizontal Link Pointer to the next queue head. The host |
| * controller will never follow the Next Transfer Queue Element or |
| * Alternate Queue Element pointers unless it is actively attempting to |
| * advance the queue ..." |
| */ |
| |
| /* Is the qTD still active? */ |
| |
| token = lpc43_swap32(qh->hw.overlay.token); |
| usbhost_vtrace2(EHCI_VTRACE2_IOCCHECK, epinfo->epno, token); |
| |
| if ((token & QH_TOKEN_ACTIVE) != 0) |
| { |
| /* Yes... we cannot process the QH while it is still active. Return |
| * zero to visit the next QH in the list. |
| */ |
| |
| *bp = &qh->hw.hlp; |
| return OK; |
| } |
| |
| /* Remove all active, attached qTD structures from the inactive QH */ |
| |
| ret = lpc43_qtd_foreach(qh, lpc43_qtd_ioccheck, (void *)qh->epinfo); |
| if (ret < 0) |
| { |
| usbhost_trace1(EHCI_TRACE1_QTDFOREACH_FAILED, -ret); |
| } |
| |
| /* If there is no longer anything attached to the QH, then remove it from |
| * the asynchronous queue. |
| */ |
| |
| if ((lpc43_swap32(qh->fqp) & QTD_NQP_T) != 0) |
| { |
| /* Set the forward link of the previous QH to point to the next |
| * QH in the list. |
| */ |
| |
| **bp = qh->hw.hlp; |
| |
| /* Check for errors, update the data toggle */ |
| |
| if ((token & QH_TOKEN_ERRORS) == 0) |
| { |
| /* No errors.. Save the last data toggle value */ |
| |
| epinfo->toggle = (token >> QTD_TOKEN_TOGGLE_SHIFT) & 1; |
| |
| /* Report success */ |
| |
| epinfo->status = 0; |
| epinfo->result = OK; |
| } |
| else |
| { |
| /* An error occurred */ |
| |
| epinfo->status = (token & QH_TOKEN_STATUS_MASK) >> |
| QH_TOKEN_STATUS_SHIFT; |
| |
| /* The HALT condition is set on a variety of conditions: babble, |
| * error counter countdown to zero, or a STALL. If we can rule |
| * out babble (babble bit not set) and if the error counter is |
| * non-zero, then we can assume a STALL. In this case, we return |
| * -PERM to inform the class driver of the stall condition. |
| */ |
| |
| if ((token & (QH_TOKEN_BABBLE | QH_TOKEN_HALTED)) == |
| QH_TOKEN_HALTED && |
| (token & QH_TOKEN_CERR_MASK) != 0) |
| { |
| /* It is a stall, Note that the data toggle is reset |
| * after the stall. |
| */ |
| |
| usbhost_trace2(EHCI_TRACE2_EPSTALLED, epinfo->epno, token); |
| epinfo->result = -EPERM; |
| epinfo->toggle = 0; |
| } |
| else |
| { |
| /* Otherwise, it is some kind of data transfer error */ |
| |
| usbhost_trace2(EHCI_TRACE2_EPIOERROR, epinfo->epno, token); |
| epinfo->result = -EIO; |
| } |
| } |
| |
| /* Is there a thread waiting for this transfer to complete? */ |
| |
| if (epinfo->iocwait) |
| { |
| /* Yes... wake it up */ |
| |
| nxsem_post(&epinfo->iocsem); |
| epinfo->iocwait = 0; |
| } |
| |
| #ifdef CONFIG_USBHOST_ASYNCH |
| /* No.. Is there a pending asynchronous transfer? */ |
| |
| else if (epinfo->callback != NULL) |
| { |
| /* Yes.. perform the callback */ |
| |
| lpc43_asynch_completion(epinfo); |
| } |
| #endif |
| |
| /* Then release this QH by returning it to the free list */ |
| |
| lpc43_qh_free(qh); |
| } |
| else |
| { |
| /* Otherwise, the horizontal link pointer of this QH will become the |
| * next back pointer. |
| */ |
| |
| *bp = &qh->hw.hlp; |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_qtd_cancel |
| * |
| * Description: |
| * This function is a lpc43_qtd_foreach() callback function. It removes |
| * each qTD attached to a QH. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_USBHOST_ASYNCH |
| static int lpc43_qtd_cancel(struct lpc43_qtd_s *qtd, uint32_t **bp, |
| void *arg) |
| { |
| DEBUGASSERT(qtd != NULL && bp != NULL); |
| |
| lpc43_qtd_print(qtd); |
| |
| /* Remove the qTD from the list |
| * |
| * NOTE that we don't check if the qTD is active nor do we check if there |
| * are any errors reported in the qTD. If the transfer halted due to |
| * an error, then qTDs in the list after the error qTD will still appear |
| * to be active. |
| * |
| * REVISIT: There is a race condition here that needs to be resolved. |
| */ |
| |
| **bp = qtd->hw.nqp; |
| |
| /* Release this QH by returning it to the free list */ |
| |
| lpc43_qtd_free(qtd); |
| return OK; |
| } |
| #endif /* CONFIG_USBHOST_ASYNCH */ |
| |
| /**************************************************************************** |
| * Name: lpc43_qh_cancel |
| * |
| * Description: |
| * This function is a imxrt_qh_foreach() callback function. It cancels |
| * one QH in the asynchronous queue. It will remove all attached qTD |
| * structures and remove all of the structures that are no longer active. |
| * Then QH itself will also be removed. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_USBHOST_ASYNCH |
| static int lpc43_qh_cancel(struct lpc43_qh_s *qh, uint32_t **bp, void *arg) |
| { |
| struct lpc43_epinfo_s *epinfo = (struct lpc43_epinfo_s *)arg; |
| uint32_t regval; |
| int ret; |
| |
| DEBUGASSERT(qh != NULL && bp != NULL && epinfo != NULL); |
| |
| lpc43_qh_print(qh); |
| |
| /* Check if this is the QH that we are looking for */ |
| |
| if (qh->epinfo == epinfo) |
| { |
| /* No... keep looking */ |
| |
| return OK; |
| } |
| |
| /* Disable both the asynchronous and period schedules */ |
| |
| regval = lpc43_getreg(&HCOR->usbcmd); |
| lpc43_putreg(regval & ~(EHCI_USBCMD_ASEN | EHCI_USBCMD_PSEN), |
| &HCOR->usbcmd); |
| |
| /* Remove the QH from the list |
| * |
| * NOTE that we don't check if the qTD is active nor do we check if there |
| * are any errors reported in the qTD. If the transfer halted due to |
| * an error, then qTDs in the list after the error qTD will still appear |
| * to be active. |
| * |
| * REVISIT: There is a race condition here that needs to be resolved. |
| */ |
| |
| **bp = qh->hw.hlp; |
| |
| /* Re-enable the schedules (if they were enabled before. */ |
| |
| lpc43_putreg(regval, &HCOR->usbcmd); |
| |
| /* Remove all active, attached qTD structures from the removed QH */ |
| |
| ret = lpc43_qtd_foreach(qh, lpc43_qtd_cancel, NULL); |
| if (ret < 0) |
| { |
| usbhost_trace1(EHCI_TRACE1_QTDFOREACH_FAILED, -ret); |
| } |
| |
| /* Then release this QH by returning it to the free list. Return 1 |
| * to stop the traverse without an error. |
| */ |
| |
| lpc43_qh_free(qh); |
| return 1; |
| } |
| #endif /* CONFIG_USBHOST_ASYNCH */ |
| |
| /**************************************************************************** |
| * Name: lpc43_ioc_bottomhalf |
| * |
| * Description: |
| * EHCI USB Interrupt (USBINT) "Bottom Half" interrupt handler |
| * |
| * "The Host Controller sets this bit to 1 on the completion of a USB |
| * transaction, which results in the retirement of a Transfer Descriptor |
| * that had its IOC bit set. |
| * |
| * "The Host Controller also sets this bit to 1 when a short packet is |
| * detected (actual number of bytes received was less than the expected |
| * number of bytes)." |
| * |
| * Assumptions: The caller holds the EHCI lock |
| * |
| ****************************************************************************/ |
| |
| static inline void lpc43_ioc_bottomhalf(void) |
| { |
| struct lpc43_qh_s *qh; |
| uint32_t *bp; |
| int ret; |
| |
| /* Set the back pointer to the forward qTD pointer of the asynchronous |
| * queue head. |
| */ |
| |
| bp = (uint32_t *)&g_asynchead.hw.hlp; |
| qh = (struct lpc43_qh_s *) |
| lpc43_virtramaddr(lpc43_swap32(*bp) & QH_HLP_MASK); |
| |
| /* If the asynchronous queue is empty, then the forward point in the |
| * asynchronous queue head will point back to the queue head. |
| */ |
| |
| if (qh && qh != &g_asynchead) |
| { |
| /* Then traverse and operate on every QH and qTD in the asynchronous |
| * queue |
| */ |
| |
| ret = lpc43_qh_foreach(qh, &bp, lpc43_qh_ioccheck, NULL); |
| if (ret < 0) |
| { |
| usbhost_trace1(EHCI_TRACE1_QHFOREACH_FAILED, -ret); |
| } |
| } |
| |
| #ifndef CONFIG_USBHOST_INT_DISABLE |
| /* Check the Interrupt Queue */ |
| |
| /* Set the back pointer to the forward qTD pointer of the asynchronous |
| * queue head. |
| */ |
| |
| bp = (uint32_t *)&g_intrhead.hw.hlp; |
| qh = (struct lpc43_qh_s *) |
| lpc43_virtramaddr(lpc43_swap32(*bp) & QH_HLP_MASK); |
| if (qh) |
| { |
| /* Then traverse and operate on every QH and qTD in the asynchronous |
| * queue. |
| */ |
| |
| ret = lpc43_qh_foreach(qh, &bp, lpc43_qh_ioccheck, NULL); |
| if (ret < 0) |
| { |
| usbhost_trace1(EHCI_TRACE1_QHFOREACH_FAILED, -ret); |
| } |
| } |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_portsc_bottomhalf |
| * |
| * Description: |
| * EHCI Port Change Detect "Bottom Half" interrupt handler |
| * |
| * "The Host Controller sets this bit to a one when any port for which the |
| * Port Owner bit is set to zero ... has a change bit transition from a |
| * zero to a one or a Force Port Resume bit transition from a zero to a |
| * one as a result of a J-K transition detected on a suspended port. |
| * This bit will also be set as a result of the Connect Status Change |
| * being set to a one after system software has relinquished ownership of |
| * a connected port by writing a one to a port's Port Owner bit... |
| * |
| * "This bit is allowed to be maintained in the Auxiliary power well. |
| * Alternatively, it is also acceptable that on a D3 to D0 transition of |
| * the EHCI HC device, this bit is loaded with the OR of all of the PORTSC |
| * change bits (including: Force port resume, over-current change, |
| * enable/disable change and connect status change)." |
| * |
| ****************************************************************************/ |
| |
| static inline void lpc43_portsc_bottomhalf(void) |
| { |
| struct lpc43_rhport_s *rhport; |
| struct usbhost_hubport_s *hport; |
| uint32_t portsc; |
| int rhpndx; |
| |
| /* Handle root hub status change on each root port */ |
| |
| for (rhpndx = 0; rhpndx < LPC43_EHCI_NRHPORT; rhpndx++) |
| { |
| rhport = &g_ehci.rhport[rhpndx]; |
| portsc = lpc43_getreg(&HCOR->portsc[rhpndx]); |
| |
| usbhost_vtrace2(EHCI_VTRACE2_PORTSC, rhpndx + 1, portsc); |
| |
| /* Handle port connection status change (CSC) events */ |
| |
| if ((portsc & EHCI_PORTSC_CSC) != 0) |
| { |
| usbhost_vtrace1(EHCI_VTRACE1_PORTSC_CSC, portsc); |
| |
| /* Check current connect status */ |
| |
| if ((portsc & EHCI_PORTSC_CCS) != 0) |
| { |
| /* Connected ... Did we just become connected? */ |
| |
| if (!rhport->connected) |
| { |
| /* Yes.. connected. */ |
| |
| rhport->connected = true; |
| |
| usbhost_vtrace2(EHCI_VTRACE2_PORTSC_CONNECTED, |
| rhpndx + 1, g_ehci.pscwait); |
| |
| /* Notify any waiters */ |
| |
| if (g_ehci.pscwait) |
| { |
| nxsem_post(&g_ehci.pscsem); |
| g_ehci.pscwait = false; |
| } |
| } |
| else |
| { |
| usbhost_vtrace1(EHCI_VTRACE1_PORTSC_CONNALREADY, portsc); |
| } |
| } |
| else |
| { |
| /* Disconnected... Did we just become disconnected? */ |
| |
| if (rhport->connected) |
| { |
| /* Yes.. disconnect the device */ |
| |
| usbhost_vtrace2(EHCI_VTRACE2_PORTSC_DISCONND, |
| rhpndx + 1, g_ehci.pscwait); |
| |
| rhport->connected = false; |
| rhport->lowspeed = false; |
| |
| /* Are we bound to a class instance? */ |
| |
| hport = &rhport->hport.hport; |
| if (hport->devclass) |
| { |
| /* Yes.. Disconnect the class */ |
| |
| CLASS_DISCONNECTED(hport->devclass); |
| hport->devclass = NULL; |
| } |
| |
| /* Notify any waiters for the Root Hub Status change |
| * event. |
| */ |
| |
| if (g_ehci.pscwait) |
| { |
| nxsem_post(&g_ehci.pscsem); |
| g_ehci.pscwait = false; |
| } |
| } |
| else |
| { |
| usbhost_vtrace1(EHCI_VTRACE1_PORTSC_DISCALREADY, portsc); |
| } |
| } |
| } |
| |
| /* Clear all pending port interrupt sources by writing a '1' to the |
| * corresponding bit in the PORTSC register. In addition, we need |
| * to preserve the values of all R/W bits (RO bits don't matter) |
| */ |
| |
| lpc43_putreg(portsc, &HCOR->portsc[rhpndx]); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_syserr_bottomhalf |
| * |
| * Description: |
| * EHCI Host System Error "Bottom Half" interrupt handler |
| * |
| * "The Host Controller sets this bit to 1 when a serious error occurs |
| * during a host system access involving the Host Controller module. ... |
| * When this error occurs, the Host Controller clears the Run/Stop bit |
| * in the Command register to prevent further execution of the scheduled |
| * TDs." |
| * |
| ****************************************************************************/ |
| |
| static inline void lpc43_syserr_bottomhalf(void) |
| { |
| usbhost_trace1(EHCI_TRACE1_SYSERR_INTR, 0); |
| DEBUGPANIC(); |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_async_advance_bottomhalf |
| * |
| * Description: |
| * EHCI Async Advance "Bottom Half" interrupt handler |
| * |
| * "System software can force the host controller to issue an interrupt the |
| * next time the host controller advances the asynchronous schedule by |
| * writing a one to the Interrupt on Async Advance Doorbell bit in the |
| * USBCMD register. This status bit indicates the assertion of that |
| * interrupt source." |
| * |
| ****************************************************************************/ |
| |
| static inline void lpc43_async_advance_bottomhalf(void) |
| { |
| usbhost_vtrace1(EHCI_VTRACE1_AAINTR, 0); |
| |
| /* REVISIT: Could remove all tagged QH entries here */ |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_ehci_bottomhalf |
| * |
| * Description: |
| * EHCI "Bottom Half" interrupt handler |
| * |
| ****************************************************************************/ |
| |
| static void lpc43_ehci_bottomhalf(void *arg) |
| { |
| uint32_t pending = (uint32_t)arg; |
| |
| /* We need to have exclusive access to the EHCI data structures. Waiting |
| * here is not a good thing to do on the worker thread, but there is no |
| * real option (other than to reschedule and delay). |
| */ |
| |
| nxmutex_lock(&g_ehci.lock); |
| |
| /* Handle all unmasked interrupt sources */ |
| |
| /* USB Interrupt (USBINT) |
| * |
| * "The Host Controller sets this bit to 1 on the completion of a USB |
| * transaction, which results in the retirement of a Transfer Descriptor |
| * that had its IOC bit set. |
| * |
| * "The Host Controller also sets this bit to 1 when a short packet is |
| * detected (actual number of bytes received was less than the expected |
| * number of bytes)." |
| * |
| * USB Error Interrupt (USBERRINT) |
| * |
| * "The Host Controller sets this bit to 1 when completion of a USB |
| * transaction results in an error condition (e.g., error counter |
| * underflow). If the TD on which the error interrupt occurred also |
| * had its IOC bit set, both this bit and USBINT bit are set. ..." |
| * |
| * We do the same thing in either case: Traverse the asynchronous queue |
| * and remove all of the transfers that are no longer active. |
| */ |
| |
| if ((pending & (EHCI_INT_USBINT | EHCI_INT_USBERRINT)) != 0) |
| { |
| if ((pending & EHCI_INT_USBERRINT) != 0) |
| { |
| usbhost_trace1(EHCI_TRACE1_USBERR_INTR, pending); |
| } |
| else |
| { |
| usbhost_vtrace1(EHCI_VTRACE1_USBINTR, pending); |
| } |
| |
| lpc43_ioc_bottomhalf(); |
| } |
| |
| /* Port Change Detect |
| * |
| * "The Host Controller sets this bit to a one when any port for which |
| * the Port Owner bit is set to zero ... has a change bit transition |
| * from a zero to a one or a Force Port Resume bit transition from a zero |
| * to a one as a result of a J-K transition detected on a suspended port. |
| * This bit will also be set as a result of the Connect Status Change |
| * being set to a one after system software has relinquished ownership |
| * of a connected port by writing a one to a port's Port Owner bit... |
| * |
| * "This bit is allowed to be maintained in the Auxiliary power well. |
| * Alternatively, it is also acceptable that on a D3 to D0 transition |
| * of the EHCI HC device, this bit is loaded with the OR of all of the |
| * PORTSC change bits (including: Force port resume, over-current change, |
| * enable/disable change and connect status change)." |
| */ |
| |
| if ((pending & EHCI_INT_PORTSC) != 0) |
| { |
| lpc43_portsc_bottomhalf(); |
| } |
| |
| /* Frame List Rollover |
| * |
| * "The Host Controller sets this bit to a one when the Frame List Index |
| * ... rolls over from its maximum value to zero. The exact value at |
| * which the rollover occurs depends on the frame list size. For example, |
| * if the frame list size (as programmed in the Frame List Size field of |
| * the USBCMD register) is 1024, the Frame Index Register rolls over |
| * every time FRINDEX[13] toggles. Similarly, if the size is 512, the |
| * Host Controller sets this bit to a one every time FRINDEX[12] |
| * toggles." |
| */ |
| |
| #if 0 /* Not used */ |
| if ((pending & EHCI_INT_FLROLL) != 0) |
| { |
| lpc43_flroll_bottomhalf(); |
| } |
| #endif |
| |
| /* Host System Error |
| * |
| * "The Host Controller sets this bit to 1 when a serious error occurs |
| * during a host system access involving the Host Controller module. ... |
| * When this error occurs, the Host Controller clears the Run/Stop bit |
| * in the Command register to prevent further execution of the scheduled |
| * TDs." |
| */ |
| |
| if ((pending & EHCI_INT_SYSERROR) != 0) |
| { |
| lpc43_syserr_bottomhalf(); |
| } |
| |
| /* Interrupt on Async Advance |
| * |
| * "System software can force the host controller to issue an interrupt |
| * the next time the host controller advances the asynchronous schedule |
| * by writing a one to the Interrupt on Async Advance Doorbell bit in |
| * the USBCMD register. This status bit indicates the assertion of that |
| * interrupt source." |
| */ |
| |
| if ((pending & EHCI_INT_AAINT) != 0) |
| { |
| lpc43_async_advance_bottomhalf(); |
| } |
| |
| /* We are done with the EHCI structures */ |
| |
| nxmutex_unlock(&g_ehci.lock); |
| |
| /* Re-enable relevant EHCI interrupts. Interrupts should still be enabled |
| * at the level of the interrupt controller. |
| */ |
| |
| lpc43_putreg(EHCI_HANDLED_INTS, &HCOR->usbintr); |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_ehci_interrupt |
| * |
| * Description: |
| * EHCI "Top Half" interrupt handler |
| * |
| ****************************************************************************/ |
| |
| static int lpc43_ehci_interrupt(int irq, void *context, void *arg) |
| { |
| uint32_t usbsts; |
| uint32_t pending; |
| uint32_t regval; |
| |
| /* Read Interrupt Status and mask out interrupts that are not enabled. */ |
| |
| usbsts = lpc43_getreg(&HCOR->usbsts); |
| regval = lpc43_getreg(&HCOR->usbintr); |
| |
| #ifdef CONFIG_USBHOST_TRACE |
| usbhost_vtrace1(EHCI_VTRACE1_TOPHALF, usbsts & regval); |
| #else |
| uinfo("USBSTS: %08x USBINTR: %08x\n", usbsts, regval); |
| #endif |
| |
| /* Handle all unmasked interrupt sources */ |
| |
| pending = usbsts & regval; |
| if (pending != 0) |
| { |
| /* Schedule interrupt handling work for the high priority worker |
| * thread so that we are not pressed for time and so that we can |
| * interrupt with other USB threads gracefully. |
| * |
| * The worker should be available now because we implement a handshake |
| * by controlling the EHCI interrupts. |
| */ |
| |
| DEBUGASSERT(work_available(&g_ehci.work)); |
| DEBUGVERIFY(work_queue(HPWORK, &g_ehci.work, lpc43_ehci_bottomhalf, |
| (void *)pending, 0)); |
| |
| /* Disable further EHCI interrupts so that we do not overrun the work |
| * queue. |
| */ |
| |
| lpc43_putreg(0, &HCOR->usbintr); |
| |
| /* Clear all pending status bits by writing the value of the pending |
| * interrupt bits back to the status register. |
| */ |
| |
| lpc43_putreg(usbsts & EHCI_INT_ALLINTS, &HCOR->usbsts); |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_wait |
| * |
| * Description: |
| * Wait for a device to be connected or disconnected to/from a hub port. |
| * |
| * Input Parameters: |
| * conn - The USB host connection instance obtained as a parameter from the |
| * call to the USB driver initialization logic. |
| * hport - The location to return the hub port descriptor that detected the |
| * connection related event. |
| * |
| * Returned Value: |
| * Zero (OK) is returned on success when a device is connected or |
| * disconnected. This function will not return until either (1) a device is |
| * connected or disconnect to/from any hub port or until (2) some failure |
| * occurs. On a failure, a negated errno value is returned indicating the |
| * nature of the failure |
| * |
| * Assumptions: |
| * - Called from a single thread so no mutual exclusion is required. |
| * - Never called from an interrupt handler. |
| * |
| ****************************************************************************/ |
| |
| static int lpc43_wait(struct usbhost_connection_s *conn, |
| struct usbhost_hubport_s **hport) |
| { |
| irqstate_t flags; |
| int rhpndx; |
| int ret; |
| |
| /* Loop until the connection state changes on one of the root hub ports or |
| * until an error occurs. |
| */ |
| |
| flags = enter_critical_section(); |
| for (; ; ) |
| { |
| /* Check for a change in the connection state on any root hub port */ |
| |
| for (rhpndx = 0; rhpndx < LPC43_EHCI_NRHPORT; rhpndx++) |
| { |
| struct lpc43_rhport_s *rhport; |
| struct usbhost_hubport_s *connport; |
| |
| /* Has the connection state changed on the RH port? */ |
| |
| rhport = &g_ehci.rhport[rhpndx]; |
| connport = &rhport->hport.hport; |
| if (rhport->connected != connport->connected) |
| { |
| /* Yes.. Return the RH port to inform the caller which |
| * port has the connection change. |
| */ |
| |
| connport->connected = rhport->connected; |
| *hport = connport; |
| leave_critical_section(flags); |
| |
| usbhost_vtrace2(EHCI_VTRACE2_MONWAKEUP, |
| rhpndx + 1, rhport->connected); |
| return OK; |
| } |
| } |
| |
| #ifdef CONFIG_USBHOST_HUB |
| /* Is a device connected to an external hub? */ |
| |
| if (g_ehci.hport) |
| { |
| volatile struct usbhost_hubport_s *connport; |
| |
| /* Yes.. return the external hub port */ |
| |
| connport = g_ehci.hport; |
| g_ehci.hport = NULL; |
| |
| *hport = (struct usbhost_hubport_s *)connport; |
| leave_critical_section(flags); |
| |
| usbhost_vtrace2(EHCI_VTRACE2_MONWAKEUP, |
| connport->port + 1, connport->connected); |
| return OK; |
| } |
| #endif |
| |
| /* No changes on any port. Wait for a connection/disconnection event |
| * and check again |
| */ |
| |
| g_ehci.pscwait = true; |
| ret = nxsem_wait_uninterruptible(&g_ehci.pscsem); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_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 lpc43_rh_enumerate(struct usbhost_connection_s *conn, |
| struct usbhost_hubport_s *hport) |
| { |
| struct lpc43_rhport_s *rhport; |
| volatile uint32_t *regaddr; |
| uint32_t regval; |
| int rhpndx; |
| |
| DEBUGASSERT(conn != NULL && hport != NULL); |
| rhpndx = hport->port; |
| |
| DEBUGASSERT(rhpndx >= 0 && rhpndx < LPC43_EHCI_NRHPORT); |
| rhport = &g_ehci.rhport[rhpndx]; |
| |
| /* Are we connected to a device? The caller should have called the wait() |
| * method first to be assured that a device is connected. |
| */ |
| |
| while (!rhport->connected) |
| { |
| /* No, return an error */ |
| |
| usbhost_vtrace1(EHCI_VTRACE1_ENUM_DISCONN, 0); |
| return -ENODEV; |
| } |
| |
| /* USB 2.0 spec says at least 50ms delay before port reset. |
| * REVISIT: I think this is wrong. It needs to hold the port in |
| * reset for 50Msec, not wait 50Msec before resetting. |
| */ |
| |
| nxsig_usleep(100 * 1000); |
| |
| /* Paragraph 2.3.9: |
| * |
| * "Line Status ... These bits reflect the current logical levels of the |
| * D+ (bit 11) and D- (bit 10) signal lines. These bits are used for |
| * detection of low-speed USB devices prior to the port reset and enable |
| * sequence. This field is valid only when the port enable bit is zero |
| * and the current connect status bit is set to a one." |
| * |
| * Bits[11:10] USB State Interpretation |
| * ----------- --------- -------------- |
| * 00b SE0 Not Low-speed device, perform EHCI reset |
| * 10b J-state Not Low-speed device, perform EHCI reset |
| * 01b K-state Low-speed device, release ownership of port |
| * |
| * NOTE: Low-speed devices could be detected by examining the PORTSC PSPD |
| * field after resetting the device. The more conventional way here, |
| * however, also appears to work. |
| */ |
| |
| regval = lpc43_getreg(&HCOR->portsc[rhpndx]); |
| if ((regval & EHCI_PORTSC_LSTATUS_MASK) == EHCI_PORTSC_LSTATUS_KSTATE) |
| { |
| /* EHCI Paragraph 2.3.9: |
| * |
| * "Port Owner ... This bit unconditionally goes to a 0b when the |
| * Configured bit in the CONFIGFLAG register makes a 0b to 1b |
| * transition. This bit unconditionally goes to 1b whenever the |
| * Configured bit is zero. |
| * |
| * "System software uses this field to release ownership of the |
| * port to a selected host controller (in the event that the |
| * attached device is not a high-speed device). Software writes |
| * a one to this bit when the attached device is not a high-speed |
| * device. A one in this bit means that a companion host |
| * controller owns and controls the port. .... |
| * |
| * EHCI Paragraph 4.2: |
| * |
| * "When a port is routed to a companion HC, it remains under the |
| * control of the companion HC until the device is disconnected |
| * from the root por ... When a disconnect occurs, the disconnect |
| * event is detected by both the companion HC port control and the |
| * EHCI port ownership control. On the event, the port ownership |
| * is returned immediately to the EHCI controller. The companion |
| * HC stack detects the disconnect and acknowledges as it would |
| * in an ordinary standalone implementation. Subsequent connects |
| * will be detected by the EHCI port register and the process will |
| * repeat." |
| */ |
| |
| hport->speed = USB_SPEED_LOW; |
| |
| #if 0 /* The LPC43xx does not support a companion host controller */ |
| regval |= EHCI_PORTSC_OWNER; |
| lpc43_putreg(regval, &HCOR->portsc[rhpndx]); |
| |
| /* And return a failure */ |
| |
| rhport->connected = false; |
| return -EPERM; |
| #endif |
| } |
| else |
| { |
| /* Assume full-speed for now */ |
| |
| hport->speed = USB_SPEED_FULL; |
| } |
| |
| /* Put the root hub port in reset. |
| * |
| * EHCI Paragraph 2.3.9: |
| * |
| * "The HCHalted bit in the USBSTS register should be a zero before |
| * software attempts to use [the Port Reset] bit. The host controller |
| * may hold Port Reset asserted to a one when the HCHalted bit is a one. |
| */ |
| |
| DEBUGASSERT((lpc43_getreg(&HCOR->usbsts) & EHCI_USBSTS_HALTED) == 0); |
| |
| /* EHCI Paragraph 2.3.9: |
| * |
| * "When software writes a one to [the Port Reset] bit (from a zero), the |
| * bus reset sequence as defined in the USB Specification Revision 2.0 |
| * is started. Software writes a zero to this bit to terminate the bus |
| * reset sequence. Software must keep this bit at a one long enough to |
| * ensure the reset sequence, as specified in the USB Specification |
| * Revision 2.0, completes. Note: when software writes this bit to a |
| * one, it must also write a zero to the Port Enable bit." |
| */ |
| |
| regaddr = &HCOR->portsc[RHPNDX(rhport)]; |
| regval = lpc43_getreg(regaddr); |
| regval &= ~EHCI_PORTSC_PE; |
| regval |= EHCI_PORTSC_RESET; |
| lpc43_putreg(regval, regaddr); |
| |
| /* USB 2.0 "Root hubs must provide an aggregate reset period of at least |
| * 50 ms." |
| */ |
| |
| nxsig_usleep(50 * 1000); |
| |
| regval = lpc43_getreg(regaddr); |
| regval &= ~EHCI_PORTSC_RESET; |
| lpc43_putreg(regval, regaddr); |
| |
| /* Wait for the port reset to complete |
| * |
| * EHCI Paragraph 2.3.9: |
| * |
| * "Note that when software writes a zero to this bit there may be a |
| * delay before the bit status changes to a zero. The bit status will |
| * not read as a zero until after the reset has completed. If the port |
| * is in high-speed mode after reset is complete, the host controller |
| * will automatically enable this port (e.g. set the Port Enable bit |
| * to a one). A host controller must terminate the reset and stabilize |
| * the state of the port within 2 milliseconds of software transitioning |
| * this bit from a one to a zero ..." |
| */ |
| |
| while ((lpc43_getreg(regaddr) & EHCI_PORTSC_RESET) != 0); |
| nxsig_usleep(200 * 1000); |
| |
| /* Paragraph 4.2.2: |
| * |
| * "... The reset process is actually complete when software reads a zero |
| * in the PortReset bit. The EHCI Driver checks the PortEnable bit in |
| * the PORTSC register. If set to a one, the connected device is a high- |
| * speed device and EHCI Driver (root hub emulator) issues a change |
| * report to the hub driver and the hub driver continues to enumerate |
| * the attached device." |
| * |
| * "At the time the EHCI Driver receives the port reset and enable |
| * request the LineStatus bits might indicate a low-speed device. |
| * Additionally, when the port reset process is complete, the PortEnable |
| * field may indicate that a full-speed device is attached. In either |
| * case the EHCI driver sets the PortOwner bit in the PORTSC register to |
| * a one to release port ownership to a companion host controller." |
| * |
| * LPC43xx User Manual Paragraph 6.1.3: |
| * |
| * "In a standard EHCI controller design, the EHCI host controller driver |
| * detects a Full speed (FS) or Low speed (LS) device by noting if the |
| * port enable bit is set after the port reset operation. The port |
| * enable will only be set in a standard EHCI controller implementation |
| * after the port reset operation and when the host and device negotiate |
| * a High-Speed connection (i.e. Chirp completes successfully). Since |
| * this controller has an embedded Transaction Translator, the port |
| * enable will always be set after the port reset operation regardless |
| * of the result of the host device chirp result and the resulting port |
| * speed will be indicated by the PSPD field in PORTSC1." |
| */ |
| |
| regval = lpc43_getreg(&HCOR->portsc[rhpndx]); |
| |
| #if 0 /* LPC43xx detects high- vs full-speed devices using the PSPD field */ |
| if ((regval & EHCI_PORTSC_PE) != 0) |
| #else |
| if ((regval & USBDEV_PRTSC1_PSPD_MASK) == USBDEV_PRTSC1_PSPD_HS) |
| #endif |
| { |
| /* High speed device */ |
| |
| hport->speed = USB_SPEED_HIGH; |
| } |
| else if ((regval & USBDEV_PRTSC1_PSPD_MASK) == USBDEV_PRTSC1_PSPD_FS) |
| { |
| /* Low- or Full- speed device. Set the port ownership bit. |
| * |
| * EHCI Paragraph 4.2: |
| * |
| * "When a port is routed to a companion HC, it remains under the |
| * control of the companion HC until the device is disconnected |
| * from the root por ... When a disconnect occurs, the disconnect |
| * event is detected by both the companion HC port control and the |
| * EHCI port ownership control. On the event, the port ownership |
| * is returned immediately to the EHCI controller. The companion |
| * HC stack detects the disconnect and acknowledges as it would |
| * in an ordinary standalone implementation. Subsequent connects |
| * will be detected by the EHCI port register and the process will |
| * repeat." |
| */ |
| |
| DEBUGASSERT(hport->speed == USB_SPEED_FULL); |
| |
| #if 0 /* The LPC43xx does not support a companion host controller */ |
| regval |= EHCI_PORTSC_OWNER; |
| lpc43_putreg(regval, &HCOR->portsc[rhpndx]); |
| |
| /* And return a failure */ |
| |
| rhport->connected = false; |
| return -EPERM; |
| #endif |
| } |
| |
| /* Otherwise it must be a low speed device */ |
| |
| else |
| { |
| DEBUGASSERT(hport->speed == USB_SPEED_LOW); |
| DEBUGASSERT((regval & USBDEV_PRTSC1_PSPD_MASK) == |
| USBDEV_PRTSC1_PSPD_LS); |
| } |
| |
| return OK; |
| } |
| |
| static int lpc43_enumerate(struct usbhost_connection_s *conn, |
| struct usbhost_hubport_s *hport) |
| { |
| int ret; |
| |
| /* If this is a connection on the root hub, then we need to go to |
| * little more effort to get the device speed. If it is a connection |
| * on an external hub, then we already have that information. |
| */ |
| |
| DEBUGASSERT(hport); |
| #ifdef CONFIG_USBHOST_HUB |
| if (ROOTHUB(hport)) |
| #endif |
| { |
| ret = lpc43_rh_enumerate(conn, hport); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| } |
| |
| /* Then let the common usbhost_enumerate do the real enumeration. */ |
| |
| usbhost_vtrace1(EHCI_VTRACE1_CLASSENUM, hport->port); |
| ret = usbhost_enumerate(hport, &hport->devclass); |
| if (ret < 0) |
| { |
| /* Failed to enumerate */ |
| |
| usbhost_trace2(EHCI_TRACE2_CLASSENUM_FAILED, hport->port + 1, -ret); |
| |
| /* If this is a root hub port, then marking the hub port not connected |
| * will cause sam_wait() to return and we will try the connection |
| * again. |
| */ |
| |
| hport->connected = false; |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_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. |
| * funcaddr - The USB address of the function containing the endpoint that |
| * EP0 controls. A funcaddr of zero will be received if no address is |
| * yet assigned to the device. |
| * speed - The speed of the port USB_SPEED_LOW, _FULL, or _HIGH |
| * maxpacketsize - The maximum number of bytes that can be sent to or |
| * received from the endpoint in a single data packet |
| * |
| * Returned Value: |
| * On success, zero (OK) is returned. On a failure, a negated errno value |
| * is returned indicating the nature of the failure. |
| * |
| * Assumptions: |
| * This function will *not* be called from an interrupt handler. |
| * |
| ****************************************************************************/ |
| |
| static int lpc43_ep0configure(struct usbhost_driver_s *drvr, |
| usbhost_ep_t ep0, uint8_t funcaddr, |
| uint8_t speed, uint16_t maxpacketsize) |
| { |
| struct lpc43_epinfo_s *epinfo = (struct lpc43_epinfo_s *)ep0; |
| int ret; |
| |
| DEBUGASSERT(drvr != NULL && epinfo != NULL && maxpacketsize < 2048); |
| |
| /* We must have exclusive access to the EHCI data structures. */ |
| |
| ret = nxmutex_lock(&g_ehci.lock); |
| if (ret >= 0) |
| { |
| /* Remember the new device address and max packet size */ |
| |
| epinfo->devaddr = funcaddr; |
| epinfo->speed = speed; |
| epinfo->maxpacket = maxpacketsize; |
| |
| nxmutex_unlock(&g_ehci.lock); |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_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 lpc43_epalloc(struct usbhost_driver_s *drvr, |
| const struct usbhost_epdesc_s *epdesc, |
| usbhost_ep_t *ep) |
| { |
| struct lpc43_epinfo_s *epinfo; |
| struct usbhost_hubport_s *hport; |
| |
| /* 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 && epdesc->hport != NULL && |
| ep != NULL); |
| hport = epdesc->hport; |
| |
| /* Terse output only if we are tracing */ |
| |
| #ifdef CONFIG_USBHOST_TRACE |
| usbhost_vtrace2(EHCI_VTRACE2_EPALLOC, epdesc->addr, epdesc->xfrtype); |
| #else |
| uinfo("EP%d DIR=%s FA=%08x TYPE=%d Interval=%d MaxPacket=%d\n", |
| epdesc->addr, epdesc->in ? "IN" : "OUT", hport->funcaddr, |
| epdesc->xfrtype, epdesc->interval, epdesc->mxpacketsize); |
| #endif |
| |
| /* Allocate a endpoint information structure */ |
| |
| epinfo = (struct lpc43_epinfo_s *) |
| kmm_zalloc(sizeof(struct lpc43_epinfo_s)); |
| if (!epinfo) |
| { |
| usbhost_trace1(EHCI_TRACE1_EPALLOC_FAILED, 0); |
| return -ENOMEM; |
| } |
| |
| /* Initialize the endpoint container (which is really just another form of |
| * 'struct usbhost_epdesc_s', packed differently and with additional |
| * information. A cleaner design might just embed struct usbhost_epdesc_s |
| * inside of struct lpc43_epinfo_s and just memcpy here. |
| */ |
| |
| epinfo->epno = epdesc->addr; |
| epinfo->dirin = epdesc->in; |
| epinfo->devaddr = hport->funcaddr; |
| #ifndef CONFIG_USBHOST_INT_DISABLE |
| epinfo->interval = epdesc->interval; |
| #endif |
| epinfo->maxpacket = epdesc->mxpacketsize; |
| epinfo->xfrtype = epdesc->xfrtype; |
| epinfo->speed = hport->speed; |
| nxsem_init(&epinfo->iocsem, 0, 0); |
| |
| /* Success.. return an opaque reference to the endpoint information |
| * structure instance |
| */ |
| |
| *ep = (usbhost_ep_t)epinfo; |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_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 endpint 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 lpc43_epfree(struct usbhost_driver_s *drvr, usbhost_ep_t ep) |
| { |
| struct lpc43_epinfo_s *epinfo = (struct lpc43_epinfo_s *)ep; |
| |
| /* There should not be any pending, transfers */ |
| |
| DEBUGASSERT(drvr && epinfo && epinfo->iocwait == 0); |
| |
| /* Free the container */ |
| |
| kmm_free(epinfo); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_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 lpc43_alloc(struct usbhost_driver_s *drvr, |
| uint8_t **buffer, size_t *maxlen) |
| { |
| int ret = -ENOMEM; |
| DEBUGASSERT(drvr && buffer && maxlen); |
| |
| /* There is no special requirements for transfer/descriptor buffers. */ |
| |
| *buffer = kmm_malloc(CONFIG_LPC43_EHCI_BUFSIZE); |
| if (*buffer) |
| { |
| *maxlen = CONFIG_LPC43_EHCI_BUFSIZE; |
| ret = OK; |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_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 lpc43_free(struct usbhost_driver_s *drvr, uint8_t *buffer) |
| { |
| DEBUGASSERT(drvr && buffer); |
| |
| /* No special action is require to free the transfer/descriptor buffer |
| * memory |
| */ |
| |
| kmm_free(buffer); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_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 kumm_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 lpc43_ioalloc(struct usbhost_driver_s *drvr, |
| uint8_t **buffer, size_t buflen) |
| { |
| DEBUGASSERT(drvr && buffer && buflen > 0); |
| |
| /* The only special requirements for I/O buffers are they might need to be |
| * user accessible (depending on how the class driver implements its |
| * buffering). |
| */ |
| |
| *buffer = kumm_malloc(buflen); |
| return *buffer ? OK : -ENOMEM; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_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 kumm_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 lpc43_iofree(struct usbhost_driver_s *drvr, |
| uint8_t *buffer) |
| { |
| DEBUGASSERT(drvr && buffer); |
| |
| /* No special action is require to free the I/O buffer memory */ |
| |
| kumm_free(buffer); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_ctrlin and lpc43_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 lpc43_ctrlin(struct usbhost_driver_s *drvr, usbhost_ep_t ep0, |
| const struct usb_ctrlreq_s *req, |
| uint8_t *buffer) |
| { |
| struct lpc43_rhport_s *rhport = (struct lpc43_rhport_s *)drvr; |
| struct lpc43_epinfo_s *ep0info = (struct lpc43_epinfo_s *)ep0; |
| uint16_t len; |
| ssize_t nbytes; |
| int ret; |
| |
| DEBUGASSERT(rhport != NULL && ep0info != NULL && req != NULL); |
| |
| len = lpc43_read16(req->len); |
| |
| /* Terse output only if we are tracing */ |
| |
| #ifdef CONFIG_USBHOST_TRACE |
| usbhost_vtrace2(EHCI_VTRACE2_CTRLINOUT, RHPORT(rhport), req->req); |
| #else |
| uinfo("RHPort%d type: %02x req: %02x value: %02x%02x index: %02x%02x " |
| "len: %04x\n", |
| RHPORT(rhport), req->type, req->req, req->value[1], req->value[0], |
| req->index[1], req->index[0], len); |
| #endif |
| |
| /* We must have exclusive access to the EHCI hardware and data |
| * structures. |
| */ |
| |
| ret = nxmutex_lock(&g_ehci.lock); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| /* Set the request for the IOC event well BEFORE initiating the transfer. */ |
| |
| ret = lpc43_ioc_setup(rhport, ep0info); |
| if (ret != OK) |
| { |
| usbhost_trace1(EHCI_TRACE1_DEVDISCONNECTED, -ret); |
| goto errout_with_lock; |
| } |
| |
| /* Now initiate the transfer */ |
| |
| ret = lpc43_async_setup(rhport, ep0info, req, buffer, len); |
| if (ret < 0) |
| { |
| uerr("ERROR: lpc43_async_setup failed: %d\n", ret); |
| goto errout_with_iocwait; |
| } |
| |
| /* And wait for the transfer to complete */ |
| |
| nbytes = lpc43_transfer_wait(ep0info); |
| nxmutex_unlock(&g_ehci.lock); |
| return nbytes >= 0 ? OK : (int)nbytes; |
| |
| errout_with_iocwait: |
| ep0info->iocwait = false; |
| errout_with_lock: |
| nxmutex_unlock(&g_ehci.lock); |
| return ret; |
| } |
| |
| static int lpc43_ctrlout(struct usbhost_driver_s *drvr, usbhost_ep_t ep0, |
| const struct usb_ctrlreq_s *req, |
| const uint8_t *buffer) |
| { |
| /* lpc43_ctrlin can handle both directions. We just need to work around |
| * the differences in the function signatures. |
| */ |
| |
| return lpc43_ctrlin(drvr, ep0, req, (uint8_t *)buffer); |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_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 lpc43_transfer(struct usbhost_driver_s *drvr, |
| usbhost_ep_t ep, uint8_t *buffer, |
| size_t buflen) |
| { |
| struct lpc43_rhport_s *rhport = (struct lpc43_rhport_s *)drvr; |
| struct lpc43_epinfo_s *epinfo = (struct lpc43_epinfo_s *)ep; |
| ssize_t nbytes; |
| int ret; |
| |
| DEBUGASSERT(rhport && epinfo && buffer && buflen > 0); |
| |
| /* We must have exclusive access to the EHCI hardware and |
| * data structures. |
| */ |
| |
| ret = nxmutex_lock(&g_ehci.lock); |
| if (ret < 0) |
| { |
| return (ssize_t)ret; |
| } |
| |
| /* Set the request for the IOC event well BEFORE initiating the transfer. */ |
| |
| ret = lpc43_ioc_setup(rhport, epinfo); |
| if (ret != OK) |
| { |
| usbhost_trace1(EHCI_TRACE1_DEVDISCONNECTED, -ret); |
| goto errout_with_lock; |
| } |
| |
| /* Initiate the transfer */ |
| |
| switch (epinfo->xfrtype) |
| { |
| case USB_EP_ATTR_XFER_BULK: |
| ret = lpc43_async_setup(rhport, epinfo, NULL, buffer, buflen); |
| break; |
| |
| #ifndef CONFIG_USBHOST_INT_DISABLE |
| case USB_EP_ATTR_XFER_INT: |
| ret = lpc43_intr_setup(rhport, epinfo, buffer, buflen); |
| break; |
| #endif |
| |
| #ifndef CONFIG_USBHOST_ISOC_DISABLE |
| case USB_EP_ATTR_XFER_ISOC: |
| # warning "Isochronous endpoint support not emplemented" |
| #endif |
| case USB_EP_ATTR_XFER_CONTROL: |
| default: |
| usbhost_trace1(EHCI_TRACE1_BADXFRTYPE, epinfo->xfrtype); |
| ret = -ENOSYS; |
| break; |
| } |
| |
| /* Check for errors in the setup of the transfer */ |
| |
| if (ret < 0) |
| { |
| goto errout_with_iocwait; |
| } |
| |
| /* Then wait for the transfer to complete */ |
| |
| nbytes = lpc43_transfer_wait(epinfo); |
| nxmutex_unlock(&g_ehci.lock); |
| return nbytes; |
| |
| errout_with_iocwait: |
| epinfo->iocwait = false; |
| errout_with_lock: |
| nxmutex_unlock(&g_ehci.lock); |
| return (ssize_t)ret; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_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 lpc43_asynch(struct usbhost_driver_s *drvr, usbhost_ep_t ep, |
| uint8_t *buffer, size_t buflen, |
| usbhost_asynch_t callback, void *arg) |
| { |
| struct lpc43_rhport_s *rhport = (struct lpc43_rhport_s *)drvr; |
| struct lpc43_epinfo_s *epinfo = (struct lpc43_epinfo_s *)ep; |
| int ret; |
| |
| DEBUGASSERT(rhport && epinfo && buffer && buflen > 0); |
| |
| /* We must have exclusive access to the EHCI hardware and |
| * data structures. |
| */ |
| |
| ret = nxmutex_lock(&g_ehci.lock); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| /* Set the request for the callback well BEFORE initiating the transfer. */ |
| |
| ret = lpc43_ioc_async_setup(rhport, epinfo, callback, arg); |
| if (ret != OK) |
| { |
| usbhost_trace1(EHCI_TRACE1_DEVDISCONNECTED, -ret); |
| goto errout_with_lock; |
| } |
| |
| /* Initiate the transfer */ |
| |
| switch (epinfo->xfrtype) |
| { |
| case USB_EP_ATTR_XFER_BULK: |
| ret = lpc43_async_setup(rhport, epinfo, NULL, buffer, buflen); |
| break; |
| |
| #ifndef CONFIG_USBHOST_INT_DISABLE |
| case USB_EP_ATTR_XFER_INT: |
| ret = lpc43_intr_setup(rhport, epinfo, buffer, buflen); |
| break; |
| #endif |
| |
| #ifndef CONFIG_USBHOST_ISOC_DISABLE |
| case USB_EP_ATTR_XFER_ISOC: |
| # warning "Isochronous endpoint support not emplemented" |
| #endif |
| case USB_EP_ATTR_XFER_CONTROL: |
| default: |
| usbhost_trace1(EHCI_TRACE1_BADXFRTYPE, epinfo->xfrtype); |
| ret = -ENOSYS; |
| break; |
| } |
| |
| /* Check for errors in the setup of the transfer */ |
| |
| if (ret < 0) |
| { |
| goto errout_with_callback; |
| } |
| |
| /* The transfer is in progress */ |
| |
| nxmutex_unlock(&g_ehci.lock); |
| return OK; |
| |
| errout_with_callback: |
| epinfo->callback = NULL; |
| epinfo->arg = NULL; |
| errout_with_lock: |
| nxmutex_unlock(&g_ehci.lock); |
| return ret; |
| } |
| #endif /* CONFIG_USBHOST_ASYNCH */ |
| |
| /**************************************************************************** |
| * Name: lpc43_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 lpc43_cancel(struct usbhost_driver_s *drvr, usbhost_ep_t ep) |
| { |
| struct lpc43_epinfo_s *epinfo = (struct lpc43_epinfo_s *)ep; |
| struct lpc43_qh_s *qh; |
| #ifdef CONFIG_USBHOST_ASYNCH |
| usbhost_asynch_t callback; |
| void *arg; |
| #endif |
| uint32_t *bp; |
| irqstate_t flags; |
| bool iocwait; |
| int ret; |
| |
| DEBUGASSERT(epinfo); |
| |
| /* We must have exclusive access to the EHCI hardware and data structures. |
| * This will prevent servicing any transfer completion events while we |
| * perform the cancellation, but will not prevent DMA-related race |
| * conditions. |
| * |
| * REVISIT: This won't work. This function must be callable from the |
| * interrupt level. |
| */ |
| |
| ret = nxmutex_lock(&g_ehci.lock); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| /* Sample and reset all transfer termination information. This will |
| * prevent any callbacks from occurring while are performing the |
| * cancellation. The transfer may still be in progress, however, so this |
| * does not eliminate other DMA-related race conditions. |
| */ |
| |
| flags = enter_critical_section(); |
| #ifdef CONFIG_USBHOST_ASYNCH |
| callback = epinfo->callback; |
| arg = epinfo->arg; |
| #endif |
| iocwait = epinfo->iocwait; |
| |
| #ifdef CONFIG_USBHOST_ASYNCH |
| epinfo->callback = NULL; |
| epinfo->arg = NULL; |
| #endif |
| epinfo->iocwait = false; |
| |
| /* This will prevent any callbacks from occurring while are performing |
| * the cancellation. The transfer may still be in progress, however, so |
| * this does not eliminate other DMA-related race conditions. |
| */ |
| |
| epinfo->callback = NULL; |
| epinfo->arg = NULL; |
| leave_critical_section(flags); |
| |
| /* Bail if there is no transfer in progress for this endpoint */ |
| |
| #ifdef CONFIG_USBHOST_ASYNCH |
| if (callback == NULL && !iocwait) |
| #else |
| if (!iocwait) |
| #endif |
| { |
| ret = OK; |
| goto errout_with_lock; |
| } |
| |
| /* Handle the cancellation according to the type of the transfer */ |
| |
| switch (epinfo->xfrtype) |
| { |
| case USB_EP_ATTR_XFER_CONTROL: |
| case USB_EP_ATTR_XFER_BULK: |
| { |
| /* Get the horizontal pointer from the head of the asynchronous |
| * queue. |
| */ |
| |
| bp = (uint32_t *)&g_asynchead.hw.hlp; |
| qh = (struct lpc43_qh_s *) |
| lpc43_virtramaddr(lpc43_swap32(*bp) & QH_HLP_MASK); |
| |
| /* If the asynchronous queue is empty, then the forward point in |
| * the asynchronous queue head will point back to the queue |
| * head. |
| */ |
| |
| if (qh && qh != &g_asynchead) |
| { |
| /* Claim that we successfully cancelled the transfer */ |
| |
| ret = OK; |
| goto exit_terminate; |
| } |
| } |
| break; |
| |
| #ifndef CONFIG_USBHOST_INT_DISABLE |
| case USB_EP_ATTR_XFER_INT: |
| { |
| /* Get the horizontal pointer from the head of the interrupt |
| * queue. |
| */ |
| |
| bp = (uint32_t *)&g_intrhead.hw.hlp; |
| qh = (struct lpc43_qh_s *) |
| lpc43_virtramaddr(lpc43_swap32(*bp) & QH_HLP_MASK); |
| if (qh) |
| { |
| /* if the queue is empty, then just claim that we successfully |
| * cancelled the transfer. |
| */ |
| |
| ret = OK; |
| goto exit_terminate; |
| } |
| } |
| break; |
| #endif |
| |
| #ifndef CONFIG_USBHOST_ISOC_DISABLE |
| case USB_EP_ATTR_XFER_ISOC: |
| # warning "Isochronous endpoint support not emplemented" |
| #endif |
| default: |
| usbhost_trace1(EHCI_TRACE1_BADXFRTYPE, epinfo->xfrtype); |
| ret = -ENOSYS; |
| goto errout_with_lock; |
| } |
| |
| /* Find and remove the QH. There are four possibilities: |
| * |
| * 1) The transfer has already completed and the QH is no longer in the |
| * list. In this case, sam_hq_foreach will return zero |
| * 2a) The transfer is not active and still pending. It was removed from |
| * the list and sam_hq_foreach will return one. |
| * 2b) The is active but not yet complete. This is currently handled the |
| * same as 2a). REVISIT: This needs to be fixed. |
| * 3) Some bad happened and sam_hq_foreach returned an error code < 0. |
| */ |
| |
| ret = lpc43_qh_foreach(qh, &bp, lpc43_qh_cancel, epinfo); |
| if (ret < 0) |
| { |
| usbhost_trace1(EHCI_TRACE1_QTDFOREACH_FAILED, -ret); |
| } |
| |
| /* Was there a pending synchronous transfer? */ |
| |
| exit_terminate: |
| epinfo->result = -ESHUTDOWN; |
| #ifdef CONFIG_USBHOST_ASYNCH |
| if (iocwait) |
| { |
| /* Yes... wake it up */ |
| |
| DEBUGASSERT(callback == NULL); |
| nxsem_post(&epinfo->iocsem); |
| } |
| |
| /* No.. Is there a pending asynchronous transfer? */ |
| |
| else /* if (callback != NULL) */ |
| { |
| /* Yes.. perform the callback */ |
| |
| callback(arg, -ESHUTDOWN); |
| } |
| |
| #else |
| /* Wake up the waiting thread */ |
| |
| nxsem_post(&epinfo->iocsem); |
| #endif |
| |
| errout_with_lock: |
| nxmutex_unlock(&g_ehci.lock); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_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 lpc43_connect(struct usbhost_driver_s *drvr, |
| struct usbhost_hubport_s *hport, |
| bool connected) |
| { |
| irqstate_t flags; |
| |
| /* Set the connected/disconnected flag */ |
| |
| hport->connected = connected; |
| uinfo("Hub port %d connected: %s\n", |
| hport->port, connected ? "YES" : "NO"); |
| |
| /* Report the connection event */ |
| |
| flags = enter_critical_section(); |
| DEBUGASSERT(g_ehci.hport == NULL); /* REVISIT */ |
| |
| g_ehci.hport = hport; |
| if (g_ehci.pscwait) |
| { |
| g_ehci.pscwait = false; |
| nxsem_post(&g_ehci.pscsem); |
| } |
| |
| leave_critical_section(flags); |
| return OK; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: lpc43_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 lpc43_disconnect(struct usbhost_driver_s *drvr, |
| struct usbhost_hubport_s *hport) |
| { |
| DEBUGASSERT(hport != NULL); |
| hport->devclass = NULL; |
| } |
| |
| /**************************************************************************** |
| * Name: lpc43_reset |
| * |
| * Description: |
| * Set the HCRESET bit in the USBCMD register to reset the EHCI hardware. |
| * |
| * Table 2-9. USBCMD - USB Command Register Bit Definitions |
| * |
| * "Host Controller Reset (HCRESET) ... This control bit is used by |
| * software to reset the host controller. The effects of this on Root |
| * Hub registers are similar to a Chip Hardware Reset. |
| * |
| * "When software writes a one to this bit, the Host Controller resets its |
| * internal pipelines, timers, counters, state machines, etc. to their |
| * initial value. Any transaction currently in progress on USB is |
| * immediately terminated. A USB reset is not driven on downstream |
| * ports. |
| * |
| * "PCI Configuration registers are not affected by this reset. All |
| * operational registers, including port registers and port state |
| * machines are set to their initial values. Port ownership reverts |
| * to the companion host controller(s)... Software must reinitialize |
| * the host controller ... in order to return the host controller to |
| * an operational state. |
| * |
| * "This bit is set to zero by the Host Controller when the reset process |
| * is complete. Software cannot terminate the reset process early by |
| * writing a zero to this register. Software should not set this bit to |
| * a one when the HCHalted bit in the USBSTS register is a zero. |
| * Attempting to reset an actively running host controller will result |
| * in undefined behavior." |
| * |
| * Input Parameters: |
| * None. |
| * |
| * Returned Value: |
| * Zero (OK) is returned on success; A negated errno value is returned |
| * on failure. |
| * |
| * Assumptions: |
| * - Called during the initialization of the EHCI. |
| * |
| ****************************************************************************/ |
| |
| static int lpc43_reset(void) |
| { |
| uint32_t regval; |
| unsigned int timeout; |
| |
| /* Make sure that the EHCI is halted: "When [the Run/Stop] bit is set to |
| * 0, the Host Controller completes the current transaction on the USB and |
| * then halts. The HC Halted bit in the status register indicates when the |
| * Host Controller has finished the transaction and has entered the |
| * stopped state..." |
| */ |
| |
| lpc43_putreg(0, &HCOR->usbcmd); |
| |
| /* "... Software should not set [HCRESET] to a one when the HCHalted bit in |
| * the USBSTS register is a zero. Attempting to reset an actively running |
| * host controller will result in undefined behavior." |
| */ |
| |
| timeout = 0; |
| do |
| { |
| /* Wait one microsecond and update the timeout counter */ |
| |
| up_udelay(1); |
| timeout++; |
| |
| /* Get the current value of the USBSTS register. This loop will |
| * terminate when either the timeout exceeds one millisecond or when |
| * the HCHalted bit is no longer set in the USBSTS register. |
| */ |
| |
| regval = lpc43_getreg(&HCOR->usbsts); |
| } |
| while (((regval & EHCI_USBSTS_HALTED) == 0) && (timeout < 1000)); |
| |
| /* Is the EHCI still running? Did we timeout? */ |
| |
| if ((regval & EHCI_USBSTS_HALTED) == 0) |
| { |
| usbhost_trace1(EHCI_TRACE1_HCHALTED_TIMEOUT, regval); |
| return -ETIMEDOUT; |
| } |
| |
| /* Now we can set the HCReset bit in the USBCMD register to |
| * initiate the reset |
| */ |
| |
| regval = lpc43_getreg(&HCOR->usbcmd); |
| regval |= EHCI_USBCMD_HCRESET; |
| lpc43_putreg(regval, &HCOR->usbcmd); |
| |
| /* Wait for the HCReset bit to become clear */ |
| |
| do |
| { |
| /* Wait five microsecondw and update the timeout counter */ |
| |
| up_udelay(5); |
| timeout += 5; |
| |
| /* Get the current value of the USBCMD register. This loop will |
| * terminate when either the timeout exceeds one second or when the |
| * HCReset bit is no longer set in the USBSTS register. |
| */ |
| |
| regval = lpc43_getreg(&HCOR->usbcmd); |
| } |
| while (((regval & EHCI_USBCMD_HCRESET) != 0) && (timeout < 1000000)); |
| |
| /* Return either success or a timeout */ |
| |
| return (regval & EHCI_USBCMD_HCRESET) != 0 ? -ETIMEDOUT : OK; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: lpc43_ehci_initialize |
| * |
| * Description: |
| * Initialize USB EHCI host controller hardware. |
| * |
| * Input Parameters: |
| * controller -- If the device supports more than one EHCI interface, then |
| * this identifies which controller is being initialized. Normally, this |
| * is just zero. |
| * |
| * Returned Value: |
| * And instance of the USB host interface. The controlling task should |
| * use this interface to (1) call the wait() method to wait for a device |
| * to be connected, and (2) call the enumerate() method to bind the device |
| * to a class driver. |
| * |
| * Assumptions: |
| * - This function should called in the initialization sequence in order |
| * to initialize the USB device functionality. |
| * - Class drivers should be initialized prior to calling this function. |
| * Otherwise, there is a race condition if the device is already connected. |
| * |
| ****************************************************************************/ |
| |
| struct usbhost_connection_s *lpc43_ehci_initialize(int controller) |
| { |
| struct usbhost_hubport_s *hport; |
| uint32_t regval; |
| #if defined(CONFIG_DEBUG_USB) && defined(CONFIG_DEBUG_INFO) |
| uint16_t regval16; |
| unsigned int nports; |
| #endif |
| uintptr_t physaddr; |
| int ret; |
| int i; |
| |
| /* Sanity checks */ |
| |
| DEBUGASSERT(controller == 0); |
| DEBUGASSERT(((uintptr_t)&g_asynchead & 0x1f) == 0); |
| DEBUGASSERT((sizeof(struct lpc43_qh_s) & 0x1f) == 0); |
| DEBUGASSERT((sizeof(struct lpc43_qtd_s) & 0x1f) == 0); |
| |
| #ifdef CONFIG_LPC43_EHCI_PREALLOCATE |
| DEBUGASSERT(((uintptr_t)&g_qhpool & 0x1f) == 0); |
| DEBUGASSERT(((uintptr_t)&g_qtdpool & 0x1f) == 0); |
| #endif |
| |
| #ifndef CONFIG_USBHOST_INT_DISABLE |
| DEBUGASSERT(((uintptr_t)&g_intrhead & 0x1f) == 0); |
| #ifdef CONFIG_LPC43_EHCI_PREALLOCATE |
| DEBUGASSERT(((uintptr_t)g_framelist & 0xfff) == 0); |
| #endif |
| #endif /* CONFIG_USBHOST_INT_DISABLE */ |
| |
| /* Software Configuration *************************************************/ |
| |
| usbhost_vtrace1(EHCI_VTRACE1_INITIALIZING, 0); |
| |
| /* Initialize function address generation logic */ |
| |
| usbhost_devaddr_initialize(&g_ehci.devgen); |
| |
| /* Initialize the root hub port structures */ |
| |
| for (i = 0; i < LPC43_EHCI_NRHPORT; i++) |
| { |
| struct lpc43_rhport_s *rhport = &g_ehci.rhport[i]; |
| |
| /* Initialize the device operations */ |
| |
| rhport->drvr.ep0configure = lpc43_ep0configure; |
| rhport->drvr.epalloc = lpc43_epalloc; |
| rhport->drvr.epfree = lpc43_epfree; |
| rhport->drvr.alloc = lpc43_alloc; |
| rhport->drvr.free = lpc43_free; |
| rhport->drvr.ioalloc = lpc43_ioalloc; |
| rhport->drvr.iofree = lpc43_iofree; |
| rhport->drvr.ctrlin = lpc43_ctrlin; |
| rhport->drvr.ctrlout = lpc43_ctrlout; |
| rhport->drvr.transfer = lpc43_transfer; |
| #ifdef CONFIG_USBHOST_ASYNCH |
| rhport->drvr.asynch = lpc43_asynch; |
| #endif |
| rhport->drvr.cancel = lpc43_cancel; |
| #ifdef CONFIG_USBHOST_HUB |
| rhport->drvr.connect = lpc43_connect; |
| #endif |
| rhport->drvr.disconnect = lpc43_disconnect; |
| rhport->hport.pdevgen = &g_ehci.devgen; |
| |
| /* Initialize EP0 */ |
| |
| rhport->ep0.xfrtype = USB_EP_ATTR_XFER_CONTROL; |
| rhport->ep0.speed = USB_SPEED_FULL; |
| rhport->ep0.maxpacket = 8; |
| nxsem_init(&rhport->ep0.iocsem, 0, 0); |
| |
| /* Initialize the public port representation */ |
| |
| hport = &rhport->hport.hport; |
| hport->drvr = &rhport->drvr; |
| #ifdef CONFIG_USBHOST_HUB |
| hport->parent = NULL; |
| #endif |
| hport->ep0 = &rhport->ep0; |
| hport->port = i; |
| hport->speed = USB_SPEED_FULL; |
| } |
| |
| #ifndef CONFIG_LPC43_EHCI_PREALLOCATE |
| /* Allocate a pool of free Queue Head (QH) structures */ |
| |
| g_qhpool = (struct lpc43_qh_s *) |
| kmm_memalign(32, CONFIG_LPC43_EHCI_NQHS * sizeof(struct lpc43_qh_s)); |
| if (!g_qhpool) |
| { |
| usbhost_trace1(EHCI_TRACE1_QHPOOLALLOC_FAILED, 0); |
| return NULL; |
| } |
| #endif |
| |
| /* Initialize the list of free Queue Head (QH) structures */ |
| |
| for (i = 0; i < CONFIG_LPC43_EHCI_NQHS; i++) |
| { |
| /* Put the QH structure in a free list */ |
| |
| lpc43_qh_free(&g_qhpool[i]); |
| } |
| |
| #ifndef CONFIG_LPC43_EHCI_PREALLOCATE |
| /* Allocate a pool of free Transfer Descriptor (qTD) structures */ |
| |
| g_qtdpool = (struct lpc43_qtd_s *) |
| kmm_memalign(32, CONFIG_LPC43_EHCI_NQTDS * sizeof(struct lpc43_qtd_s)); |
| if (!g_qtdpool) |
| { |
| usbhost_trace1(EHCI_TRACE1_QTDPOOLALLOC_FAILED, 0); |
| kmm_free(g_qhpool); |
| return NULL; |
| } |
| #endif |
| |
| #if !defined(CONFIG_LPC43_EHCI_PREALLOCATE) && !defined(CONFIG_USBHOST_INT_DISABLE) |
| /* Allocate the periodic framelist */ |
| |
| g_framelist = (uint32_t *) |
| kmm_memalign(4096, FRAME_LIST_SIZE * sizeof(uint32_t)); |
| if (!g_framelist) |
| { |
| usbhost_trace1(EHCI_TRACE1_PERFLALLOC_FAILED, 0); |
| kmm_free(g_qhpool); |
| kmm_free(g_qtdpool); |
| return NULL; |
| } |
| #endif |
| |
| /* Initialize the list of free Transfer Descriptor (qTD) structures */ |
| |
| for (i = 0; i < CONFIG_LPC43_EHCI_NQTDS; i++) |
| { |
| /* Put the TD in a free list */ |
| |
| lpc43_qtd_free(&g_qtdpool[i]); |
| } |
| |
| /* EHCI Hardware Configuration ********************************************/ |
| |
| /* Enable USB to AHB clock and to Event router */ |
| |
| lpc43_pll0usbconfig(); |
| lpc43_pll0usbenable(); |
| |
| /* Program the controller to be the USB host controller |
| * |
| * Fixed selections: |
| * |
| * CM = Host mode |
| * ES = 0, Little endian mode. |
| * SLOM Not used in host mode. |
| * VBPS = 1, off-chip power source |
| * |
| * Configurable selections: |
| * |
| * SDIS = 1, Stream disable mode. Eliminates overruns/underruns at |
| * the expense of some performance. |
| */ |
| |
| #ifdef CONFIG_LPC43_EHCI_SDIS |
| putreg32(USBHOST_USBMODE_CM_HOST | USBHOST_USBMODE_SDIS | |
| USBHOST_USBMODE_VBPS, |
| LPC43_USBDEV_USBMODE); |
| #else |
| putreg32(USBHOST_USBMODE_CM_HOST | USBHOST_USBMODE_VBPS, |
| LPC43_USBDEV_USBMODE); |
| #endif |
| |
| /* Host Controller Initialization. Paragraph 4.1 */ |
| |
| /* Reset the EHCI hardware */ |
| |
| ret = lpc43_reset(); |
| if (ret < 0) |
| { |
| usbhost_trace1(EHCI_TRACE1_RESET_FAILED, -ret); |
| return NULL; |
| } |
| |
| /* Re-program the USB host controller. As implemented, lpc43_reset() |
| * requires the host mode setup in order to work. However, we lose the |
| * host configuration in the reset. |
| */ |
| |
| #ifdef CONFIG_LPC43_EHCI_SDIS |
| putreg32(USBHOST_USBMODE_CM_HOST | USBHOST_USBMODE_SDIS | |
| USBHOST_USBMODE_VBPS, |
| LPC43_USBDEV_USBMODE); |
| #else |
| putreg32(USBHOST_USBMODE_CM_HOST | USBHOST_USBMODE_VBPS, |
| LPC43_USBDEV_USBMODE); |
| #endif |
| |
| /* "In order to initialize the host controller, software should perform |
| * the following steps: |
| * |
| * - "Program the CTRLDSSEGMENT register with 4-Gigabyte segment where |
| * all of the interface data structures are allocated. [64-bit mode] |
| * - "Write the appropriate value to the USBINTR register to enable the |
| * appropriate interrupts. |
| * - "Write the base address of the Periodic Frame List to the |
| * PERIODICLIST BASE register. If there are no work items in the |
| * periodic schedule, all elements of the Periodic Frame List should |
| * have their T-Bits set to a one. |
| * - "Write the USBCMD register to set the desired interrupt threshold, |
| * frame list size (if applicable) and turn the host controller ON via |
| * setting the Run/Stop bit. |
| * - Write a 1 to CONFIGFLAG register to route all ports to the EHCI |
| * controller |
| * ... |
| * |
| * "At this point, the host controller is up and running and the port |
| * registers will begin reporting device connects, etc. System software |
| * can enumerate a port through the reset process (where the port is in |
| * the enabled state). At this point, the port is active with SOFs |
| * occurring down the enabled port enabled Highspeed ports, but the |
| * schedules have not yet been enabled. The EHCI Host controller will not |
| * transmit SOFs to enabled Full- or Low-speed ports." |
| */ |
| |
| regval = getreg32(LPC43_CREG0); |
| regval &= ~CREG0_USB0PHY; |
| putreg32(regval, LPC43_CREG0); |
| |
| /* Disable all interrupts */ |
| |
| lpc43_putreg(0, &HCOR->usbintr); |
| |
| /* Clear pending interrupts. Bits in the USBSTS register are cleared by |
| * writing a '1' to the corresponding bit. |
| */ |
| |
| lpc43_putreg(EHCI_INT_ALLINTS, &HCOR->usbsts); |
| |
| #if defined(CONFIG_DEBUG_USB) && defined(CONFIG_DEBUG_INFO) |
| /* Show the EHCI version */ |
| |
| regval16 = lpc43_swap16(HCCR->hciversion); |
| usbhost_vtrace2(EHCI_VTRACE2_HCIVERSION, regval16 >> 8, regval16 & 0xff); |
| |
| /* Verify that the correct number of ports is reported */ |
| |
| regval = lpc43_getreg(&HCCR->hcsparams); |
| nports = (regval & EHCI_HCSPARAMS_NPORTS_MASK) >> |
| EHCI_HCSPARAMS_NPORTS_SHIFT; |
| |
| usbhost_vtrace2(EHCI_VTRACE2_HCSPARAMS, nports, regval); |
| DEBUGASSERT(nports == LPC43_EHCI_NRHPORT); |
| |
| /* Show the HCCPARAMS register */ |
| |
| regval = lpc43_getreg(&HCCR->hccparams); |
| usbhost_vtrace1(EHCI_VTRACE1_HCCPARAMS, regval); |
| #endif |
| |
| /* Initialize the head of the asynchronous queue/reclamation list. |
| * |
| * "In order to communicate with devices via the asynchronous schedule, |
| * system software must write the ASYNDLISTADDR register with the address |
| * of a control or bulk queue head. Software must then enable the |
| * asynchronous schedule by writing a one to the Asynchronous Schedule |
| * Enable bit in the USBCMD register. In order to communicate with devices |
| * via the periodic schedule, system software must enable the periodic |
| * schedule by writing a one to the Periodic Schedule Enable bit in the |
| * USBCMD register. Note that the schedules can be turned on before the |
| * first port is reset (and enabled)." |
| */ |
| |
| memset(&g_asynchead, 0, sizeof(struct lpc43_qh_s)); |
| physaddr = lpc43_physramaddr((uintptr_t)&g_asynchead); |
| g_asynchead.hw.hlp = lpc43_swap32(physaddr | QH_HLP_TYP_QH); |
| g_asynchead.hw.epchar = lpc43_swap32(QH_EPCHAR_H | |
| QH_EPCHAR_EPS_FULL); |
| g_asynchead.hw.overlay.nqp = lpc43_swap32(QH_NQP_T); |
| g_asynchead.hw.overlay.alt = lpc43_swap32(QH_NQP_T); |
| g_asynchead.hw.overlay.token = lpc43_swap32(QH_TOKEN_HALTED); |
| g_asynchead.fqp = lpc43_swap32(QTD_NQP_T); |
| |
| /* Set the Current Asynchronous List Address. */ |
| |
| lpc43_putreg(lpc43_swap32(physaddr), &HCOR->asynclistaddr); |
| |
| #ifndef CONFIG_USBHOST_INT_DISABLE |
| /* Initialize the head of the periodic list. Since Isochronous |
| * endpoints are not not yet supported, each element of the |
| * frame list is initialized to point to the Interrupt Queue |
| * Head (g_intrhead). |
| */ |
| |
| memset(&g_intrhead, 0, sizeof(struct lpc43_qh_s)); |
| g_intrhead.hw.hlp = lpc43_swap32(QH_HLP_T); |
| g_intrhead.hw.overlay.nqp = lpc43_swap32(QH_NQP_T); |
| g_intrhead.hw.overlay.alt = lpc43_swap32(QH_NQP_T); |
| g_intrhead.hw.overlay.token = lpc43_swap32(QH_TOKEN_HALTED); |
| g_intrhead.hw.epcaps = lpc43_swap32(QH_EPCAPS_SSMASK(1)); |
| |
| /* Attach the periodic QH to Period Frame List */ |
| |
| physaddr = lpc43_physramaddr((uintptr_t)&g_intrhead); |
| for (i = 0; i < FRAME_LIST_SIZE; i++) |
| { |
| g_framelist[i] = lpc43_swap32(physaddr) | PFL_TYP_QH; |
| } |
| |
| /* Set the Periodic Frame List Base Address. */ |
| |
| physaddr = lpc43_physramaddr((uintptr_t)g_framelist); |
| lpc43_putreg(lpc43_swap32(physaddr), &HCOR->periodiclistbase); |
| #endif |
| |
| /* Enable the asynchronous schedule and, possibly enable the periodic |
| * schedule and set the frame list size. |
| */ |
| |
| regval = lpc43_getreg(&HCOR->usbcmd); |
| regval &= ~(EHCI_USBCMD_HCRESET | EHCI_USBCMD_FLSIZE_MASK | |
| EHCI_USBCMD_FLSIZE_MASK | EHCI_USBCMD_PSEN | |
| EHCI_USBCMD_IAADB | EHCI_USBCMD_LRESET); |
| regval |= EHCI_USBCMD_ASEN; |
| |
| #ifndef CONFIG_USBHOST_INT_DISABLE |
| regval |= EHCI_USBCMD_PSEN; |
| # if FRAME_LIST_SIZE == 1024 |
| regval |= EHCI_USBCMD_FLSIZE_1024; |
| # elif FRAME_LIST_SIZE == 512 |
| regval |= EHCI_USBCMD_FLSIZE_512; |
| # elif FRAME_LIST_SIZE == 512 |
| regval |= EHCI_USBCMD_FLSIZE_256; |
| # else |
| # error Unsupported frame size list size |
| # endif |
| #endif |
| |
| lpc43_putreg(regval, &HCOR->usbcmd); |
| |
| /* Start the host controller by setting the RUN bit in the |
| * USBCMD register. |
| */ |
| |
| regval = lpc43_getreg(&HCOR->usbcmd); |
| regval |= EHCI_USBCMD_RUN; |
| lpc43_putreg(regval, &HCOR->usbcmd); |
| |
| /* Route all ports to this host controller by setting the CONFIG flag. */ |
| |
| regval = lpc43_getreg(&HCOR->configflag); |
| regval |= EHCI_CONFIGFLAG; |
| lpc43_putreg(regval, &HCOR->configflag); |
| |
| /* Wait for the EHCI to run (i.e., no longer report halted) */ |
| |
| ret = ehci_wait_usbsts(EHCI_USBSTS_HALTED, 0, 100 * 1000); |
| if (ret < 0) |
| { |
| usbhost_trace1(EHCI_TRACE1_RUN_FAILED, lpc43_getreg(&HCOR->usbsts)); |
| return NULL; |
| } |
| |
| /* Interrupt Configuration ************************************************/ |
| |
| ret = irq_attach(LPC43M4_IRQ_USB0, lpc43_ehci_interrupt, NULL); |
| if (ret != 0) |
| { |
| usbhost_trace1(EHCI_TRACE1_IRQATTACH_FAILED, LPC43M4_IRQ_USB0); |
| return NULL; |
| } |
| |
| /* Enable EHCI interrupts. Interrupts are still disabled at the level of |
| * the interrupt controller. |
| */ |
| |
| lpc43_putreg(EHCI_HANDLED_INTS, &HCOR->usbintr); |
| |
| /* Drive Vbus +5V (the smoke test) |
| * |
| * REVISIT: |
| * - Should be done elsewhere in OTG mode. |
| * - Can we postpone enabling VBUS to save power? I think it can be |
| * done in lpc43_enumerate() and can probably be disabled when the |
| * port is disconnected. |
| * - Some EHCI implementations require setting the power bit in the |
| * PORTSC register to enable power. |
| */ |
| |
| /* Handle root hub status change on each root port */ |
| |
| for (i = 0; i < LPC43_EHCI_NRHPORT; i++) |
| { |
| #if 0 |
| /* Enable VBUS power for the port */ |
| |
| lpc43_usbhost_vbusdrive(i, true); |
| #endif |
| up_mdelay(25); |
| |
| /* Power up the hub. REVISIT: Is this necessary? The PP bit never |
| * gets set unless I explicitly set it here. |
| */ |
| |
| regval = lpc43_getreg(&HCOR->portsc[i]); |
| regval |= EHCI_PORTSC_PP; |
| lpc43_putreg(regval, &HCOR->portsc[i]); |
| up_mdelay(25); |
| } |
| |
| /* If there is a USB device in the slot at power up, then we will not |
| * get the status change interrupt to signal us that the device is |
| * connected. We need to set the initial connected state accordingly. |
| */ |
| |
| for (i = 0; i < LPC43_EHCI_NRHPORT; i++) |
| { |
| g_ehci.rhport[i].connected = |
| ((lpc43_getreg(&HCOR->portsc[i]) & EHCI_PORTSC_CCS) != 0); |
| } |
| |
| /* Enable interrupts at the interrupt controller */ |
| |
| up_enable_irq(LPC43M4_IRQ_USB0); /* enable USB interrupt */ |
| usbhost_vtrace1(EHCI_VTRACE1_INIITIALIZED, 0); |
| |
| return &g_ehciconn; |
| } |
| |
| /**************************************************************************** |
| * 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 |
| const char *usbhost_trformat1(uint16_t id) |
| { |
| int ndx = TRACE1_INDEX(id); |
| |
| if (ndx < TRACE1_NSTRINGS) |
| { |
| return g_trace1[ndx].string; |
| } |
| |
| return NULL; |
| } |
| |
| const char *usbhost_trformat2(uint16_t id) |
| { |
| int ndx = TRACE2_INDEX(id); |
| |
| if (ndx < TRACE2_NSTRINGS) |
| { |
| return g_trace2[ndx].string; |
| } |
| |
| return NULL; |
| } |
| #endif /* HAVE_USBHOST_TRACE */ |
| |
| #endif /* CONFIG_LPC43_USBOTG && CONFIG_USBHOST */ |