From b5964103e25d05bd30b939b7e8d36a10c5ff8fea Mon Sep 17 00:00:00 2001 From: sakumisu <1203593632@qq.com> Date: Sat, 29 Jan 2022 23:21:40 +0800 Subject: [PATCH] add ehci and synopsys hcd driver --- common/usb_hc.h | 213 +++ port/ehci/usb_ehci.c | 2833 +++++++++++++++++++++++++++++++ port/ehci/usb_ehci.h | 1006 +++++++++++ port/synopsys/usb_hc_synopsys.c | 481 ++++++ 4 files changed, 4533 insertions(+) create mode 100644 common/usb_hc.h create mode 100644 port/ehci/usb_ehci.c create mode 100644 port/ehci/usb_ehci.h create mode 100644 port/synopsys/usb_hc_synopsys.c diff --git a/common/usb_hc.h b/common/usb_hc.h new file mode 100644 index 00000000..78ad77f0 --- /dev/null +++ b/common/usb_hc.h @@ -0,0 +1,213 @@ +/** + * @file usb_hc.h + * @brief + * + * Copyright (c) 2022 sakumisu + * + * 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. + * + */ +#ifndef _USB_HC_H +#define _USB_HC_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*usbh_asynch_callback_t)(void *arg, int nbytes); +typedef void *usbh_epinfo_t; +/** + * @brief USB Endpoint Configuration. + * + * Structure containing the USB endpoint configuration. + */ +struct usbh_endpoint_cfg { + struct usbh_hubport *hport; + /** The number associated with the EP in the device + * configuration structure + * IN EP = 0x80 | \ + * OUT EP = 0x00 | \ + */ + uint8_t ep_addr; + /** Endpoint Transfer Type. + * May be Bulk, Interrupt, Control or Isochronous + */ + uint8_t ep_type; + uint8_t ep_interval; + /** Endpoint max packet size */ + uint16_t ep_mps; +}; + +/** + * @brief USB Host Core Layer API + * @defgroup _usb_host_core_api USB Host Core API + * @{ + */ + +/** + * @brief usb host controller hardware init. + * + * @return int + */ +int usb_hc_init(void); + +/** + * @brief reset roothub port + * + * @param port port index + * @return int + */ +int usbh_reset_port(const uint8_t port); + +/** + * @brief get roothub port speed + * + * @param port port index + * @return return 0 means USB_SPEED_LOW, 1 means USB_SPEED_FULL and 2 means USB_SPEED_HIGH. + */ +uint8_t usbh_get_port_speed(const uint8_t port); + +/** + * @brief reconfig control endpoint. + * + * @param ep A memory location provided by the caller. + * @param dev_addr device address. + * @param ep_mps control endpoint max packet size. + * @param speed port speed + * @return On success will return 0, and others indicate fail. + */ +int usbh_ep0_reconfigure(usbh_epinfo_t ep, uint8_t dev_addr, uint8_t ep_mps, uint8_t speed); + +/** + * @brief Allocate and config endpoint + * + * @param ep A memory location provided by the caller in which to save the allocated endpoint info. + * @param ep_cfg Describes the endpoint info to be allocated. + * @return On success will return 0, and others indicate fail. + */ +int usbh_ep_alloc(usbh_epinfo_t *ep, const struct usbh_endpoint_cfg *ep_cfg); + +/** + * @brief Free a memory in which saves endpoint info. + * + * @param ep A memory location provided by the caller in which to free the allocated endpoint info. + * @return On success will return 0, and others indicate fail. + */ +int usbh_ep_free(usbh_epinfo_t ep); + +/** + * @brief Perform a control transfer. + * This is a blocking method; this method will not return until the transfer has completed. + * + * @param ep The control endpoint to send/receive the control request. + * @param setup Setup packet to be sent. + * @param buffer 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. + * @return On success will return 0, and others indicate fail. + */ +int usbh_control_transfer(usbh_epinfo_t ep, struct usb_setup_packet *setup, uint8_t *buffer); + +/** + * @brief Process a request to handle a transfer descriptor. This method will + * enqueue the transfer request and wait for it to complete. Only one transfer may be queued; + * This is a blocking method; this method will not return until the transfer has completed. + * + * @param ep The IN or OUT endpoint descriptor for the device endpoint on which to perform the transfer. + * @param buffer A buffer containing the data to be sent (OUT endpoint) or received (IN endpoint). + * @param buflen The length of the data to be sent or received. + * @return 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 + * + */ +int usbh_ep_bulk_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen); + +/** + * @brief Process a request to handle a transfer descriptor. This method will + * enqueue the transfer request and wait for it to complete. Only one transfer may be queued; + * This is a blocking method; this method will not return until the transfer has completed. + * + * @param ep The IN or OUT endpoint descriptor for the device endpoint on which to perform the transfer. + * @param buffer A buffer containing the data to be sent (OUT endpoint) or received (IN endpoint). + * @param buflen The length of the data to be sent or received. + * @return 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 + * + */ +int usbh_ep_intr_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen); + +/** + * @brief Process a request to handle a transfer asynchronously. This method + * will enqueue the transfer request and return immediately. Only one transfer may be queued on a given endpoint + * When the transfer completes, the callback will be invoked with the provided argument. + * + * This method is useful for receiving interrupt transfers which may come infrequently. + * + * @param ep The IN or OUT endpoint descriptor for the device endpoint on which to perform the transfer. + * @param buffer A buffer containing the data to be sent (OUT endpoint) or received (IN endpoint). + * @param buflen The length of the data to be sent or received. + * @param callback This function will be called when the transfer completes. + * @param arg The arbitrary parameter that will be passed to the callback function when the transfer completes. + * + * @return On success will return 0, and others indicate fail. + */ +int usbh_ep_bulk_async_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen, usbh_asynch_callback_t callback, void *arg); + +/** + * @brief Process a request to handle a transfer asynchronously. This method + * will enqueue the transfer request and return immediately. Only one transfer may be queued on a given endpoint + * When the transfer completes, the callback will be invoked with the provided argument. + * + * This method is useful for receiving interrupt transfers which may come infrequently. + * + * @param ep The IN or OUT endpoint descriptor for the device endpoint on which to perform the transfer. + * @param buffer A buffer containing the data to be sent (OUT endpoint) or received (IN endpoint). + * @param buflen The length of the data to be sent or received. + * @param callback This function will be called when the transfer completes. + * @param arg The arbitrary parameter that will be passed to the callback function when the transfer completes. + * + * @return On success will return 0, and others indicate fail. + */ +int usbh_ep_intr_async_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen, usbh_asynch_callback_t callback, void *arg); + +/** + * @brief Cancel any pending syncrhonous or asynchronous transfer on an endpoint. + * + * @param ep The IN or OUT endpoint descriptor for the device endpoint on which to cancel. + * @return On success will return 0, and others indicate fail. + */ +int usb_ep_cancel(usbh_epinfo_t ep); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/port/ehci/usb_ehci.c b/port/ehci/usb_ehci.c new file mode 100644 index 00000000..c015311c --- /dev/null +++ b/port/ehci/usb_ehci.c @@ -0,0 +1,2833 @@ +#include "usbh_core.h" +#include "usb_ehci.h" + +#define DEBUGASSERT(f) + +#define BL_USBOTG_HCCR_BASE (0x20072000) +#define BL_USBOTG_HCOR_BASE (0x20072000 + 0x10) + +/* Configurable number of Queue Head (QH) structures. The default is one per + * Root hub port plus one for EP0. + */ + +#ifndef CONFIG_USB_EHCI_QH_NUM +#define CONFIG_USB_EHCI_QH_NUM (CONFIG_USBHOST_RHPORTS + 1) +#endif + +/* Configurable number of Queue Element Transfer Descriptor (qTDs). The + * default is one per root hub plus three from EP0. + */ + +#ifndef CONFIG_USB_EHCI_QTD_NUM +#define CONFIG_USB_EHCI_QTD_NUM (CONFIG_USBHOST_RHPORTS + 3) +#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 *)BL_USBOTG_HCCR_BASE) + +/* Host Controller Operational Registers */ + +#define HCOR ((volatile struct ehci_hcor_s *)BL_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 usb_ehci_physramaddr(a) (a) +#define usb_ehci_virtramaddr(a) (a) + +/* Port numbers */ +// #define RHPNDX(rh) (rh) +// #define RHPORT(rh) (RHPNDX(rh) + 1) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Internal representation of the EHCI Queue Head (QH) */ + +struct usb_ehci_epinfo_s; +struct usb_ehci_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 usb_ehci_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 usb_ehci_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 usb_ehci_list_s { + struct usb_ehci_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 usb_ehci_qh_s *qh, uint32_t **bp, void *arg); +typedef int (*foreach_qtd_t)(struct usb_ehci_qtd_s *qtd, uint32_t **bp, void *arg); + +/* This structure describes one endpoint. */ + +struct usb_ehci_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 */ + + usb_osal_sem_t iocsem; /* Semaphore used to wait for transfer completion */ +#ifdef CONFIG_USBHOST_ASYNCH + usbh_asynch_callback_t callback; /* Transfer complete callback */ + void *arg; /* Argument that accompanies the callback */ +#endif + struct usbh_hubport *hport; +}; + +/* This structure retains the overall state of the USB host controller */ + +struct usb_ehci_s { + volatile bool connected; /* Connected to device */ + usb_osal_mutex_t exclsem; /* Support mutually exclusive access */ + struct usb_work work; + + struct usb_ehci_list_s *qhfree; /* List of free Queue Head (QH) structures */ + struct usb_ehci_list_s *qtdfree; /* List of free Queue Element Transfer Descriptor (qTD) */ + + struct usb_ehci_epinfo_s ep0[CONFIG_USBHOST_RHPORTS]; /* EP0 endpoint info */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Register operations ******************************************************/ + +static uint16_t usb_ehci_read16(const uint8_t *addr); +static uint32_t usb_ehci_read32(const uint8_t *addr); +#if 0 /* Not used */ +static void usb_ehci_write16(uint16_t memval, uint8_t *addr); +static void usb_ehci_write32(uint32_t memval, uint8_t *addr); +#endif + +#ifdef CONFIG_ENDIAN_BIG +static uint16_t usb_ehci_swap16(uint16_t value); +static uint32_t usb_ehci_swap32(uint32_t value); +#else +#define usb_ehci_swap16(value) (value) +#define usb_ehci_swap32(value) (value) +#endif + +/**************************************************************************** + * 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. + */ + +/* 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 +}; + +/* 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 usb_ehci_s g_ehci; + +/* The head of the asynchronous queue */ + +USB_NOCACHE_RAM_SECTION struct usb_ehci_qh_s g_asynchead __attribute__((aligned(32))); + +/* The head of the periodic queue */ + +USB_NOCACHE_RAM_SECTION struct usb_ehci_qh_s g_intrhead __attribute__((aligned(32))); + +/* Queue Head (QH) pool */ + +USB_NOCACHE_RAM_SECTION struct usb_ehci_qh_s g_qhpool[CONFIG_USB_EHCI_QH_NUM] __attribute__((aligned(32))); + +/* Queue Element Transfer Descriptor (qTD) pool */ + +USB_NOCACHE_RAM_SECTION struct usb_ehci_qtd_s g_qtdpool[CONFIG_USB_EHCI_QTD_NUM] __attribute__((aligned(32))); + +/* The frame list */ +USB_NOCACHE_RAM_SECTION uint32_t g_framelist[FRAME_LIST_SIZE] __attribute__((aligned(4096))); +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: usb_ehci_read16 + * + * Description: + * Read 16-bit little endian data + * + ****************************************************************************/ + +static uint16_t usb_ehci_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: usb_ehci_read32 + * + * Description: + * Read 32-bit little endian data + * + ****************************************************************************/ + +static inline uint32_t usb_ehci_read32(const uint8_t *addr) +{ +#ifdef CONFIG_ENDIAN_BIG + return (uint32_t)usb_ehci_read16(&addr[0]) << 16 | + (uint32_t)usb_ehci_read16(&addr[2]); +#else + return (uint32_t)usb_ehci_read16(&addr[2]) << 16 | + (uint32_t)usb_ehci_read16(&addr[0]); +#endif +} + +/**************************************************************************** + * Name: usb_ehci_swap16 + * + * Description: + * Swap bytes on a 16-bit value + * + ****************************************************************************/ + +#ifdef CONFIG_ENDIAN_BIG +static uint16_t usb_ehci_swap16(uint16_t value) +{ + return ((value >> 8) & 0xff) | ((value & 0xff) << 8); +} +#endif + +/**************************************************************************** + * Name: usb_ehci_swap32 + * + * Description: + * Swap bytes on a 32-bit value + * + ****************************************************************************/ + +#ifdef CONFIG_ENDIAN_BIG +static uint32_t usb_ehci_swap32(uint32_t value) +{ + return (uint32_t)usb_ehci_swap16((uint16_t)((value >> 16) & 0xffff)) | + (uint32_t)usb_ehci_swap16((uint16_t)(value & 0xffff)) << 16; +} +#endif + +static inline void usb_ehci_putreg(uint32_t regval, volatile uint32_t *regaddr) +{ + *regaddr = regval; +} + +static inline uint32_t usb_ehci_getreg(volatile uint32_t *regaddr) +{ + return *regaddr; +} + +/**************************************************************************** + * Name: usb_ehci_qh_alloc + * + * Description: + * Allocate a Queue Head (QH) structure by removing it from the free list + * + * Assumption: Caller holds the exclsem + * + ****************************************************************************/ + +static struct usb_ehci_qh_s *usb_ehci_qh_alloc(void) +{ + struct usb_ehci_qh_s *qh; + + /* Remove the QH structure from the freelist */ + + qh = (struct usb_ehci_qh_s *)g_ehci.qhfree; + if (qh) { + g_ehci.qhfree = ((struct usb_ehci_list_s *)qh)->flink; + memset(qh, 0, sizeof(struct usb_ehci_qh_s)); + } + + return qh; +} + +/**************************************************************************** + * Name: usb_ehci_qh_free + * + * Description: + * Free a Queue Head (QH) structure by returning it to the free list + * + * Assumption: Caller holds the exclsem + * + ****************************************************************************/ + +static void usb_ehci_qh_free(struct usb_ehci_qh_s *qh) +{ + struct usb_ehci_list_s *entry = (struct usb_ehci_list_s *)qh; + + /* Put the QH structure back into the free list */ + + entry->flink = g_ehci.qhfree; + g_ehci.qhfree = entry; +} + +/**************************************************************************** + * Name: usb_ehci_qtd_alloc + * + * Description: + * Allocate a Queue Element Transfer Descriptor (qTD) by removing it from + * the free list + * + * Assumption: Caller holds the exclsem + * + ****************************************************************************/ + +static struct usb_ehci_qtd_s *usb_ehci_qtd_alloc(void) +{ + struct usb_ehci_qtd_s *qtd; + + /* Remove the qTD from the freelist */ + + qtd = (struct usb_ehci_qtd_s *)g_ehci.qtdfree; + if (qtd) { + g_ehci.qtdfree = ((struct usb_ehci_list_s *)qtd)->flink; + memset(qtd, 0, sizeof(struct usb_ehci_qtd_s)); + } + + return qtd; +} + +/**************************************************************************** + * Name: usb_ehci_qtd_free + * + * Description: + * Free a Queue Element Transfer Descriptor (qTD) by returning it to the + * free list + * + * Assumption: Caller holds the exclsem + * + ****************************************************************************/ + +static void usb_ehci_qtd_free(struct usb_ehci_qtd_s *qtd) +{ + struct usb_ehci_list_s *entry = (struct usb_ehci_list_s *)qtd; + + /* Put the qTD back into the free list */ + + entry->flink = g_ehci.qtdfree; + g_ehci.qtdfree = entry; +} + +/**************************************************************************** + * Name: usb_ehci_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 usb_ehci_qh_foreach(struct usb_ehci_qh_s *qh, uint32_t **bp, + foreach_qh_t handler, void *arg) +{ + struct usb_ehci_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 = usb_ehci_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 (usb_ehci_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 = usb_ehci_swap32(qh->hw.hlp) & QH_HLP_MASK; + next = (struct usb_ehci_qh_s *)usb_ehci_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 0; +} + +/**************************************************************************** + * Name: usb_ehci_qtd_foreach + * + * Description: + * Give a Queue Head (QH) instance, call the handler for each qTD structure + * in the queue. + * + ****************************************************************************/ + +static int usb_ehci_qtd_foreach(struct usb_ehci_qh_s *qh, foreach_qtd_t handler, + void *arg) +{ + struct usb_ehci_qtd_s *qtd; + struct usb_ehci_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 = usb_ehci_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 usb_ehci_qtd_s *)usb_ehci_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 ((usb_ehci_swap32(qtd->hw.nqp) & QTD_NQP_T) != 0) { + /* Set the next pointer to NULL. This will terminate the loop. */ + + next = NULL; + } else { + physaddr = usb_ehci_swap32(qtd->hw.nqp) & QTD_NQP_NTEP_MASK; + next = (struct usb_ehci_qtd_s *)usb_ehci_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 0; +} + +/**************************************************************************** + * Name: usb_ehci_qtd_discard + * + * Description: + * This is a usb_ehci_qtd_foreach callback. It simply unlinks the QTD, + * updates the back pointer, and frees the QTD structure. + * + ****************************************************************************/ + +static int usb_ehci_qtd_discard(struct usb_ehci_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 */ + + usb_ehci_qtd_free(qtd); + return 0; +} + +/**************************************************************************** + * Name: usb_ehci_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 usb_ehci_qh_discard(struct usb_ehci_qh_s *qh) +{ + int ret; + + DEBUGASSERT(qh); + + /* Free all of the qTD's attached to the QH */ + + ret = usb_ehci_qtd_foreach(qh, usb_ehci_qtd_discard, NULL); + if (ret < 0) { + //usbhost_trace1(EHCI_TRACE1_QTDFOREACH_FAILED, -ret); + } + + /* Then free the QH itself */ + + usb_ehci_qh_free(qh); + return ret; +} + +/**************************************************************************** + * Name: usb_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 usb_ehci_speed(uint8_t usbspeed) +{ + DEBUGASSERT(usbspeed >= USB_SPEED_LOW && usbspeed <= USB_SPEED_HIGH); + return g_ehci_speed[usbspeed]; +} + +static struct usb_ehci_qh_s *usb_ehci_qh_create(struct usb_ehci_epinfo_s *epinfo) +{ + struct usb_ehci_qh_s *qh; + uint32_t regval; + uint8_t hubaddr; + uint8_t hubport; + struct usb_ehci_epinfo_s *ep0info; + struct usbh_hubport *rhport; + + rhport = epinfo->hport; + + while (rhport->parent != NULL) { + rhport = rhport->parent->parent; + } + ep0info = (struct usb_ehci_epinfo_s *)rhport->ep0; + /* Allocate a new queue head structure */ + + qh = usb_ehci_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 0 + */ + + regval = ((uint32_t)epinfo->devaddr << QH_EPCHAR_DEVADDR_SHIFT) | + ((uint32_t)epinfo->epno << QH_EPCHAR_ENDPT_SHIFT) | + ((uint32_t)usb_ehci_speed(epinfo->speed) << QH_EPCHAR_EPS_SHIFT) | + QH_EPCHAR_DTC | + ((uint32_t)epinfo->maxpacket << QH_EPCHAR_MAXPKT_SHIFT) | + ((uint32_t)0 << 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_ENDPOINT_TYPE_CONTROL) { + regval |= QH_EPCHAR_C; + } + + /* Save the endpoint characteristics word with the correct byte order */ + + qh->hw.epchar = usb_ehci_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 roothub port devaddr + * PORT Port number RH port index + * MULT High band width multiplier 1 + */ + + hubaddr = ep0info->devaddr; + hubport = rhport->port; + + 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_ENDPOINT_TYPE_INTERRUPT) { + /* 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 = usb_ehci_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 = usb_ehci_swap32(QH_HLP_T); + qh->hw.overlay.nqp = usb_ehci_swap32(QH_NQP_T); + qh->hw.overlay.alt = usb_ehci_swap32(QH_AQP_T); + return qh; +} + +static int usb_ehci_qtd_addbpl(struct usb_ehci_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)usb_ehci_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] = usb_ehci_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 0; +} + +static struct usb_ehci_qtd_s *usb_ehci_qtd_setupphase(struct usb_ehci_epinfo_s *epinfo, struct usb_setup_packet *setup) +{ + struct usb_ehci_qtd_s *qtd; + uint32_t regval; + int ret; + + /* Allocate a new Queue Element Transfer Descriptor (qTD) */ + + qtd = usb_ehci_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 = usb_ehci_swap32(QTD_NQP_T); + qtd->hw.alt = usb_ehci_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 8 + * TOGGLE Data Toggle 0 + */ + + regval = QTD_TOKEN_ACTIVE | QTD_TOKEN_PID_SETUP | + ((uint32_t)3 << QTD_TOKEN_CERR_SHIFT) | + ((uint32_t)8 << QTD_TOKEN_NBYTES_SHIFT); + + qtd->hw.token = usb_ehci_swap32(regval); + + /* Add the buffer data */ + ret = usb_ehci_qtd_addbpl(qtd, (uint8_t *)setup, 8); + if (ret < 0) { + //usbhost_trace1(EHCI_TRACE1_ADDBPL_FAILED, -ret); + usb_ehci_qtd_free(qtd); + return NULL; + } + + /* Add the data transfer size to the count in the epinfo structure */ + epinfo->xfrd += 8; + + return qtd; +} + +static struct usb_ehci_qtd_s *usb_ehci_qtd_dataphase(struct usb_ehci_epinfo_s *epinfo, void *buffer, int buflen, uint32_t tokenbits) +{ + struct usb_ehci_qtd_s *qtd; + uint32_t regval; + int ret; + + /* Allocate a new Queue Element Transfer Descriptor (qTD) */ + + qtd = usb_ehci_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 = usb_ehci_swap32(QTD_NQP_T); + qtd->hw.alt = usb_ehci_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 = usb_ehci_swap32(regval); + + ret = usb_ehci_qtd_addbpl(qtd, buffer, buflen); + if (ret < 0) { + //usbhost_trace1(EHCI_TRACE1_ADDBPL_FAILED, -ret); + usb_ehci_qtd_free(qtd); + return NULL; + } + + /* Add the data transfer size to the count in the epinfo structure */ + epinfo->xfrd += buflen; + + return qtd; +} + +static struct usb_ehci_qtd_s *usb_ehci_qtd_statusphase(uint32_t tokenbits) +{ + struct usb_ehci_qtd_s *qtd; + uint32_t regval; + + /* Allocate a new Queue Element Transfer Descriptor (qTD) */ + + qtd = usb_ehci_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 = usb_ehci_swap32(QTD_NQP_T); + qtd->hw.alt = usb_ehci_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 = usb_ehci_swap32(regval); + + return qtd; +} + +static void usb_ehci_qh_enqueue(struct usb_ehci_qh_s *qhead, struct usb_ehci_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; + //usb_ehci_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)usb_ehci_physramaddr((uintptr_t)qh); + qhead->hw.hlp = usb_ehci_swap32(physaddr | QH_HLP_TYP_QH); +} + +static int usb_ehci_control_setup(struct usb_ehci_epinfo_s *epinfo, struct usb_setup_packet *setup, uint8_t *buffer, uint32_t buflen) +{ + struct usb_ehci_qh_s *qh; + struct usb_ehci_qtd_s *qtd; + uint32_t tokenbits; + uintptr_t physaddr; + uint32_t *flink; + uint32_t *alt; + uint32_t toggle; + bool dirin = false; + int ret; + + /* Create and initialize a Queue Head (QH) structure for this transfer */ + qh = usb_ehci_qh_create(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, 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. + */ + + /* Allocate a new Queue Element Transfer Descriptor (qTD) for the SETUP + * phase of the request sequence. + */ + { + qtd = usb_ehci_qtd_setupphase(epinfo, setup); + if (qtd == NULL) { + //usbhost_trace1(EHCI_TRACE1_QTDSETUP_FAILED, 0); + ret = -ENOMEM; + goto errout_with_qh; + } + /* Link the new qTD to the QH head. */ + + physaddr = usb_ehci_physramaddr((uintptr_t)qtd); + *flink = usb_ehci_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) { + /* 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 ((setup->bmRequestType & 0x80) == 0x80) { + tokenbits |= QTD_TOKEN_PID_IN; + dirin = true; + } else { + tokenbits |= QTD_TOKEN_PID_OUT; + dirin = false; + } + + /* Allocate a new Queue Element Transfer Descriptor (qTD) for the data + * buffer. + */ + + qtd = usb_ehci_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 either QH head of the SETUP qTD. */ + physaddr = usb_ehci_physramaddr((uintptr_t)qtd); + *flink = usb_ehci_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; + } + } + + { + /* Extra TOKEN bits include the data toggle and the correct data PID. */ + + 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 ((setup->bmRequestType & 0x80) == 0x80) { + tokenbits |= QTD_TOKEN_PID_OUT; + } else { + /* Otherwise, this in an OUT request. We send the buffer and we expect + * to receive the NULL packet handshake. + */ + tokenbits |= QTD_TOKEN_PID_IN; + } + + /* Allocate a new Queue Element Transfer Descriptor (qTD) for the + * status + */ + qtd = usb_ehci_qtd_statusphase(tokenbits); + if (qtd == NULL) { + //usbhost_trace1(EHCI_TRACE1_QTDSTATUS_FAILED, 0); + ret = -ENOMEM; + goto errout_with_qh; + } + + /* Link the new qTD to either the SETUP or data qTD. */ + physaddr = usb_ehci_physramaddr((uintptr_t)qtd); + *flink = usb_ehci_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 = usb_ehci_swap32(physaddr); + } + } + /* Add the new QH to the head of the asynchronous queue list */ + usb_ehci_qh_enqueue(&g_asynchead, qh); + + return 0; + +errout_with_qh: + /* Clean-up after an error */ + usb_ehci_qh_discard(qh); + return ret; +} + +static int usb_ehci_bulk_setup(struct usb_ehci_epinfo_s *epinfo, uint8_t *buffer, uint32_t buflen) +{ + struct usb_ehci_qh_s *qh; + struct usb_ehci_qtd_s *qtd; + uint32_t tokenbits; + uintptr_t physaddr; + int ret; + + /* Create and initialize a Queue Head (QH) structure for this transfer */ + qh = usb_ehci_qh_create(epinfo); + if (qh == NULL) { + //usbhost_trace1(EHCI_TRACE1_QHCREATE_FAILED, 0); + return -ENOMEM; + } + + /* Initialize the QH link and get the next data toggle */ + tokenbits = (uint32_t)epinfo->toggle << QTD_TOKEN_TOGGLE_SHIFT; + ret = -EIO; + + if (buffer != NULL && buflen > 0) { + /* 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. + */ + 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 = usb_ehci_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 = usb_ehci_physramaddr((uintptr_t)qtd); + qh->hw.overlay.nqp = usb_ehci_swap32(physaddr); + } + + /* Add the new QH to the head of the asynchronous queue list */ + usb_ehci_qh_enqueue(&g_asynchead, qh); + return 0; + +errout_with_qh: + usb_ehci_qh_discard(qh); + return ret; +} + +static int usb_ehci_intr_setup(struct usb_ehci_epinfo_s *epinfo, uint8_t *buffer, uint32_t buflen) +{ + struct usb_ehci_qh_s *qh; + struct usb_ehci_qtd_s *qtd; + uint32_t tokenbits; + uintptr_t physaddr; + uint32_t regval; + int ret; + + /* Create and initialize a Queue Head (QH) structure for this transfer */ + qh = usb_ehci_qh_create(epinfo); + if (qh == NULL) { + //usbhost_trace1(EHCI_TRACE1_QHCREATE_FAILED, 0); + return -ENOMEM; + } + + /* Initialize the QH link and get the next data toggle */ + tokenbits = (uint32_t)epinfo->toggle << QTD_TOKEN_TOGGLE_SHIFT; + + /* 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. + */ + 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 = usb_ehci_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 = usb_ehci_physramaddr((uintptr_t)qtd); + qh->hw.overlay.nqp = usb_ehci_swap32(physaddr); + + /* Disable the periodic schedule */ + regval = usb_ehci_getreg(&HCOR->usbcmd); + regval &= ~EHCI_USBCMD_PSEN; + usb_ehci_putreg(regval, &HCOR->usbcmd); + + /* Add the new QH to the head of the interrupt transfer list */ + usb_ehci_qh_enqueue(&g_intrhead, qh); + + /* Re-enable the periodic schedule */ + regval |= EHCI_USBCMD_PSEN; + usb_ehci_putreg(regval, &HCOR->usbcmd); + return 0; + +errout_with_qh: + usb_ehci_qh_discard(qh); + return ret; +} + +/**************************************************************************** + * Name: usb_ehci_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 exclsem + * + ****************************************************************************/ + +static int usb_ehci_ioc_setup(struct usb_ehci_epinfo_s *epinfo) +{ + uint32_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 = usb_osal_enter_critical_section(); + if (g_ehci.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 = 0; /* We are good to go */ + } + usb_osal_leave_critical_section(flags); + return ret; +} + +/**************************************************************************** + * Name: usb_ehci_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 int usb_ehci_ioc_async_setup(struct usb_ehci_epinfo_s *epinfo, usbh_asynch_callback_t callback, void *arg) +{ + uint32_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 = usb_osal_enter_critical_section(); + if (g_ehci.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 = false; /* 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 = callback; /* No asynchronous callback */ + epinfo->arg = arg; +#endif + ret = 0; /* We are good to go */ + } + + usb_osal_leave_critical_section(flags); + return ret; +} +#endif + +/**************************************************************************** + * Name: usb_ehci_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 usb_ehci_asynch_completion(struct usb_ehci_epinfo_s *epinfo) +{ + usbh_asynch_callback_t callback; + int 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 = 0; + 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 = (int)result; + } + + callback(arg, nbytes); +} +#endif + +/**************************************************************************** + * Name: usb_ehci_ioc_wait + * + * Description: + * Wait for the IOC event. + * + * Assumption: The caller does *NOT* hold the EHCI exclsem. That would + * cause a deadlock when the bottom-half, worker thread needs to take the + * semaphore. + * + ****************************************************************************/ + +static int usb_ehci_ioc_wait(struct usb_ehci_epinfo_s *epinfo) +{ + int ret = 0; + + /* 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 = usb_osal_sem_take(epinfo->iocsem); + if (ret < 0) { + break; + } + } + + return ret < 0 ? ret : epinfo->result; +} + +/**************************************************************************** + * Name: usb_ehci_transfer_wait + * + * Description: + * Wait for an IN or OUT transfer to complete. + * + * Assumption: The caller holds the EHCI exclsem. The caller must be aware + * that the EHCI exclsem 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 int usb_ehci_transfer_wait(struct usb_ehci_epinfo_s *epinfo) +{ + int ret; + int ret2; + + /* Release the EHCI semaphore 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 exclsem + * or we will deadlock while waiting (because the working thread that + * wakes this thread up needs the exclsem). + */ + + /* REVISIT */ + + usb_osal_mutex_give(g_ehci.exclsem); + + /* Wait for the IOC completion event */ + + ret = usb_ehci_ioc_wait(epinfo); + + /* Re-acquire the EHCI semaphore. The caller expects to be holding + * this upon return. + */ + + ret2 = usb_osal_mutex_take(g_ehci.exclsem); + if (ret2 < 0) { + ret = ret2; + } + + /* Did usb_ehci_ioc_wait() or usb_osal_mutex_take()report an + * error? + */ + + if (ret < 0) { + //usbhost_trace1(EHCI_TRACE1_TRANSFER_FAILED, -ret); + epinfo->iocwait = false; + return ret; + } + + /* Transfer completed successfully. Return the number of bytes + * transferred. + */ + + return epinfo->xfrd; +} + +/**************************************************************************** + * Name: usb_ehci_qtd_ioccheck + * + * Description: + * This function is a usb_ehci_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 usb_ehci_qtd_ioccheck(struct usb_ehci_qtd_s *qtd, uint32_t **bp, + void *arg) +{ + struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)arg; + DEBUGASSERT(qtd && epinfo); + + //usb_ehci_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 -= (usb_ehci_swap32(qtd->hw.token) & QTD_TOKEN_NBYTES_MASK) >> + QTD_TOKEN_NBYTES_SHIFT; + + /* Release this QH by returning it to the free list */ + + usb_ehci_qtd_free(qtd); + + return 0; +} + +/**************************************************************************** + * Name: usb_ehci_qh_ioccheck + * + * Description: + * This function is a usb_ehci_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 usb_ehci_qh_ioccheck(struct usb_ehci_qh_s *qh, uint32_t **bp, void *arg) +{ + struct usb_ehci_epinfo_s *epinfo; + uint32_t token; + int ret; + + DEBUGASSERT(qh && bp); + + //usb_ehci_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 = usb_ehci_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 0; + } + + /* Remove all active, attached qTD structures from the inactive QH */ + ret = usb_ehci_qtd_foreach(qh, usb_ehci_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 ((usb_ehci_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 = 0; + } 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 */ + epinfo->iocwait = false; + /* TODO */ + // clang-format off + for (size_t i = 0; i < 3200; i++) { + __asm volatile("nop" ::: "memory"); + } + // clang-format on + usb_osal_sem_give(epinfo->iocsem); + } +#ifdef CONFIG_USBHOST_ASYNCH + /* No.. Is there a pending asynchronous transfer? */ + + else if (epinfo->callback != NULL) { + /* Yes.. perform the callback */ + + usb_ehci_asynch_completion(epinfo); + } +#endif + + /* Then release this QH by returning it to the free list */ + usb_ehci_qh_free(qh); + } else { + /* Otherwise, the horizontal link pointer of this QH will become the + * next back pointer. + */ + + *bp = &qh->hw.hlp; + } + + return 0; +} + +/**************************************************************************** + * Name: usb_ehci_qtd_cancel + * + * Description: + * This function is a usb_ehci_qtd_foreach() callback function. It removes + * each qTD attached to a QH. + * + ****************************************************************************/ +static int usb_ehci_qtd_cancel(struct usb_ehci_qtd_s *qtd, uint32_t **bp, + void *arg) +{ + DEBUGASSERT(qtd != NULL && bp != NULL); + + //usb_ehci_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 */ + + usb_ehci_qtd_free(qtd); + return 0; +} + +/**************************************************************************** + * Name: usb_ehci_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. + * + ****************************************************************************/ +static int usb_ehci_qh_cancel(struct usb_ehci_qh_s *qh, uint32_t **bp, void *arg) +{ + struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)arg; + uint32_t regval; + int ret; + + DEBUGASSERT(qh != NULL && bp != NULL && epinfo != NULL); + + //usb_ehci_qh_print(qh); + + /* Check if this is the QH that we are looking for */ + + if (qh->epinfo == epinfo) { + /* No... keep looking */ + + return 0; + } + + /* Disable both the asynchronous and period schedules */ + + regval = usb_ehci_getreg(&HCOR->usbcmd); + usb_ehci_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. */ + + usb_ehci_putreg(regval, &HCOR->usbcmd); + + /* Remove all active, attached qTD structures from the removed QH */ + + ret = usb_ehci_qtd_foreach(qh, usb_ehci_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. + */ + + usb_ehci_qh_free(qh); + return 1; +} + +static inline void usb_ehci_ioc_bottomhalf(void) +{ + struct usb_ehci_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 usb_ehci_qh_s *) + usb_ehci_virtramaddr(usb_ehci_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 + */ + usb_ehci_qh_foreach(qh, &bp, usb_ehci_qh_ioccheck, NULL); + } +#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 usb_ehci_qh_s *) + usb_ehci_virtramaddr(usb_ehci_swap32(*bp) & QH_HLP_MASK); + if (qh) { + /* Then traverse and operate on every QH and qTD in the asynchronous + * queue. + */ + + ret = usb_ehci_qh_foreach(qh, &bp, usb_ehci_qh_ioccheck, NULL); + if (ret < 0) { + //usbhost_trace1(EHCI_TRACE1_QHFOREACH_FAILED, -ret); + } + } +#endif +} + +extern void usbh_event_notify_handler(uint8_t event, uint8_t rhport); + +/**************************************************************************** + * Name: usb_ehci_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 usb_ehci_portsc_bottomhalf(void) +{ + uint32_t portsc; + int rhpndx; + + /* Handle root hub status change on each root port */ + + for (rhpndx = 0; rhpndx < CONFIG_USBHOST_RHPORTS; rhpndx++) { + portsc = usb_ehci_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? */ + g_ehci.connected = 1; + usbh_event_notify_handler(USBH_EVENT_ATTACHED, 1); + } else { + g_ehci.connected = 0; + usbh_event_notify_handler(USBH_EVENT_REMOVED, 1); + } + } + + /* 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) + */ + usb_ehci_putreg(portsc, &HCOR->portsc[rhpndx]); + } +} + +/**************************************************************************** + * Name: usb_ehci_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 usb_ehci_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..." + */ + + usb_ehci_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 */ + + usb_osal_msleep(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 = usb_ehci_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 = usb_ehci_getreg(&HCOR->usbcmd); + regval |= EHCI_USBCMD_HCRESET; + usb_ehci_putreg(regval, &HCOR->usbcmd); + + /* Wait for the HCReset bit to become clear */ + + do { + /* Wait five microsecondw and update the timeout counter */ + + usb_osal_msleep(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 = usb_ehci_getreg(&HCOR->usbcmd); + } while (((regval & EHCI_USBCMD_HCRESET) != 0) && (timeout < 1000000)); + + /* Return either success or a timeout */ + + return (regval & EHCI_USBCMD_HCRESET) != 0 ? -ETIMEDOUT : 0; +} + +/**************************************************************************** + * Name: usb_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 usb_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 */ + + usb_osal_msleep(5); + timeout += 5; + + /* Read the USBSTS register and check for a system error */ + + regval = usb_ehci_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) ? 0 : -ETIMEDOUT; +} + +__WEAK void usb_ehci_pre_hw_init(void) +{ +} + +__WEAK void usb_ehci_last_hw_init(void) +{ +} + +int usb_hc_init(void) +{ + int ret; + uint32_t regval; + uintptr_t physaddr1; + uintptr_t physaddr2; + + /* Initialize the list of free Queue Head (QH) structures */ + + for (uint8_t i = 0; i < CONFIG_USB_EHCI_QH_NUM; i++) { + /* Put the QH structure in a free list */ + + usb_ehci_qh_free(&g_qhpool[i]); + } + /* Initialize the list of free Queue Head (QH) structures */ + + for (uint8_t i = 0; i < CONFIG_USB_EHCI_QTD_NUM; i++) { + /* Put the QH structure in a free list */ + + usb_ehci_qtd_free(&g_qtdpool[i]); + } + + /* 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 usb_ehci_qh_s)); + physaddr1 = usb_ehci_physramaddr((uintptr_t)&g_asynchead); + g_asynchead.hw.hlp = usb_ehci_swap32(physaddr1 | QH_HLP_TYP_QH); + g_asynchead.hw.epchar = usb_ehci_swap32(QH_EPCHAR_H | + QH_EPCHAR_EPS_FULL); + g_asynchead.hw.overlay.nqp = usb_ehci_swap32(QH_NQP_T); + g_asynchead.hw.overlay.alt = usb_ehci_swap32(QH_NQP_T); + g_asynchead.hw.overlay.token = usb_ehci_swap32(QH_TOKEN_HALTED); + g_asynchead.fqp = usb_ehci_swap32(QTD_NQP_T); + + g_ehci.exclsem = usb_osal_mutex_create(); + + /* 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 usb_ehci_qh_s)); + g_intrhead.hw.hlp = usb_ehci_swap32(QH_HLP_T); + g_intrhead.hw.overlay.nqp = usb_ehci_swap32(QH_NQP_T); + g_intrhead.hw.overlay.alt = usb_ehci_swap32(QH_NQP_T); + g_intrhead.hw.overlay.token = usb_ehci_swap32(QH_TOKEN_HALTED); + g_intrhead.hw.epcaps = usb_ehci_swap32(QH_EPCAPS_SSMASK(1)); + + /* Attach the periodic QH to Period Frame List */ + + physaddr2 = usb_ehci_physramaddr((uintptr_t)&g_intrhead); + for (uint32_t i = 0; i < FRAME_LIST_SIZE; i++) { + g_framelist[i] = usb_ehci_swap32(physaddr2) | PFL_TYP_QH; + } + + /* Set the Periodic Frame List Base Address. */ + physaddr2 = usb_ehci_physramaddr((uintptr_t)g_framelist); + + usb_ehci_pre_hw_init(); + /* Host Controller Initialization. Paragraph 4.1 */ + + /* Reset the EHCI hardware */ + ret = usb_ehci_reset(); + if (ret < 0) { + //usbhost_trace1(EHCI_TRACE1_RESET_FAILED, -ret); + return -1; + } + + /* Disable all interrupts */ + usb_ehci_putreg(0, &HCOR->usbintr); + + /* Clear pending interrupts. Bits in the USBSTS register are cleared by + * writing a '1' to the corresponding bit. + */ + usb_ehci_putreg(EHCI_INT_ALLINTS, &HCOR->usbsts); + +#if defined(CONFIG_USB_EHCI_INFO_ENABLE) + /* Show the EHCI version */ + uint16_t regval16 = usb_ehci_swap16(HCCR->hciversion); + //usbhost_vtrace2(EHCI_VTRACE2_HCIVERSION, regval16 >> 8, regval16 & 0xff); + + /* Verify that the correct number of ports is reported */ + regval = usb_ehci_getreg(&HCCR->hcsparams); + uint8_t 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 = usb_ehci_getreg(&HCCR->hccparams); + //usbhost_vtrace1(EHCI_VTRACE1_HCCPARAMS, regval); +#endif + /* Set the Current Asynchronous List Address. */ + usb_ehci_putreg(usb_ehci_swap32(physaddr1), &HCOR->asynclistaddr); + /* Set the Periodic Frame List Base Address. */ + usb_ehci_putreg(usb_ehci_swap32(physaddr2), &HCOR->periodiclistbase); + + /* Enable the asynchronous schedule and, possibly enable the periodic + * schedule and set the frame list size. + */ + regval = usb_ehci_getreg(&HCOR->usbcmd); + regval &= ~(EHCI_USBCMD_HCRESET | EHCI_USBCMD_FLSIZE_MASK | + EHCI_USBCMD_PSEN | EHCI_USBCMD_ASEN | EHCI_USBCMD_IAADB); + 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 + + usb_ehci_putreg(regval, &HCOR->usbcmd); + + /* Start the host controller by setting the RUN bit in the + * USBCMD register. + */ + regval = usb_ehci_getreg(&HCOR->usbcmd); + regval |= EHCI_USBCMD_RUN; + usb_ehci_putreg(regval, &HCOR->usbcmd); + + /* Route all ports to this host controller by setting the CONFIG flag. */ + // regval = usb_ehci_getreg(&HCOR->configflag); + // regval |= EHCI_CONFIGFLAG; + // usb_ehci_putreg(regval, &HCOR->configflag); + + /* Wait for the EHCI to run (i.e., no longer report halted) */ + ret = usb_ehci_wait_usbsts(EHCI_USBSTS_HALTED, 0, 100 * 1000); + if (ret < 0) { + //usbhost_trace1(EHCI_TRACE1_RUN_FAILED, usb_ehci_getreg(&HCOR->usbsts)); + return -2; + } + + /* Enable EHCI interrupts. Interrupts are still disabled at the level of + * the interrupt controller. + */ + usb_ehci_putreg(EHCI_HANDLED_INTS, &HCOR->usbintr); + + usb_ehci_last_hw_init(); + return ret; +} + +int usbh_ep0_reconfigure(usbh_epinfo_t ep, uint8_t dev_addr, uint8_t ep_mps, uint8_t speed) +{ + struct usb_ehci_epinfo_s *epinfo; + int ret; + + epinfo = (struct usb_ehci_epinfo_s *)ep; + + DEBUGASSERT(epinfo != NULL && ep_mps < 2048); + + ret = usb_osal_mutex_take(g_ehci.exclsem); + if (ret < 0) { + return ret; + } + + epinfo->devaddr = dev_addr; + epinfo->speed = speed; + epinfo->maxpacket = ep_mps; + + usb_osal_mutex_give(g_ehci.exclsem); + + return ret; +} + +int usbh_ep_alloc(usbh_epinfo_t *ep, const struct usbh_endpoint_cfg *ep_cfg) +{ + // int ret; + struct usb_ehci_epinfo_s *epinfo; + struct usbh_hubport *hport; + + DEBUGASSERT(ep_cfg != NULL && ep_cfg->hport != NULL); + + // ret = usb_osal_mutex_take(g_ehci.exclsem); + // if (ret < 0) { + // return ret; + // } + + hport = ep_cfg->hport; + + /* new roothub ep info */ + if (((ep_cfg->ep_type & USB_ENDPOINT_TYPE_MASK) == USB_ENDPOINT_TYPE_CONTROL) && (hport->parent == NULL)) { + epinfo = &g_ehci.ep0[hport->port - 1]; + } else { + /* new exteranl hub ep info */ + epinfo = usb_malloc(sizeof(struct usb_ehci_epinfo_s)); + memset(epinfo, 0, sizeof(struct usb_ehci_epinfo_s)); + if (epinfo == NULL) { + return -1; + } + } + + epinfo->epno = ep_cfg->ep_addr & 0x7f; + epinfo->dirin = (ep_cfg->ep_addr & 0x80) ? 1 : 0; + epinfo->devaddr = hport->dev_addr; +#ifndef CONFIG_USBHOST_INT_DISABLE + epinfo->interval = ep_cfg->ep_interval; +#endif + epinfo->maxpacket = ep_cfg->ep_mps; + epinfo->xfrtype = ep_cfg->ep_type; + epinfo->speed = hport->speed; + epinfo->hport = hport; + + epinfo->iocsem = usb_osal_sem_create(0); + + *ep = epinfo; + + //usb_osal_mutex_give(g_ehci.exclsem); + + return 0; +} + +int usbh_ep_free(usbh_epinfo_t ep) +{ + int ret; + struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep; + + ret = usb_osal_mutex_take(g_ehci.exclsem); + if (ret < 0) { + return ret; + } + + usb_osal_sem_delete(epinfo->iocsem); + usb_free(epinfo); + + usb_osal_mutex_give(g_ehci.exclsem); + return 0; +} + +int usbh_control_transfer(usbh_epinfo_t ep, struct usb_setup_packet *setup, uint8_t *buffer) +{ + int nbytes; + int ret; + + struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep; + + DEBUGASSERT(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(setup || buffer); + + ret = usb_osal_mutex_take(g_ehci.exclsem); + if (ret < 0) { + return ret; + } + + /* Set the request for the IOC event well BEFORE initiating the transfer. */ + ret = usb_ehci_ioc_setup(epinfo); + if (ret != 0) { + //usbhost_trace1(EHCI_TRACE1_DEVDISCONNECTED, -ret); + goto errout_with_sem; + } + + ret = usb_ehci_control_setup(epinfo, setup, buffer, setup->wLength); + if (ret < 0) { + goto errout_with_iocwait; + } + + /* And wait for the transfer to complete */ + nbytes = usb_ehci_transfer_wait(epinfo); + usb_osal_mutex_give(g_ehci.exclsem); + return nbytes >= 0 ? 0 : (int)nbytes; + +errout_with_iocwait: + epinfo->iocwait = false; +errout_with_sem: + usb_osal_mutex_give(g_ehci.exclsem); + return ret; +} + +int usbh_ep_bulk_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen) +{ + int nbytes; + int ret; + + struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep; + + DEBUGASSERT(epinfo && buffer && buflen > 0); + + ret = usb_osal_mutex_take(g_ehci.exclsem); + if (ret < 0) { + return ret; + } + + ret = usb_ehci_ioc_setup(epinfo); + if (ret != 0) { + //usbhost_trace1(EHCI_TRACE1_DEVDISCONNECTED, -ret); + goto errout_with_sem; + } + + ret = usb_ehci_bulk_setup(epinfo, buffer, buflen); + if (ret < 0) { + goto errout_with_iocwait; + } + + /* And wait for the transfer to complete */ + nbytes = usb_ehci_transfer_wait(epinfo); + usb_osal_mutex_give(g_ehci.exclsem); + return nbytes; + +errout_with_iocwait: + epinfo->iocwait = false; +errout_with_sem: + usb_osal_mutex_give(g_ehci.exclsem); + return ret; +} + +int usbh_ep_intr_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen) +{ + int nbytes; + int ret; + + struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep; + + DEBUGASSERT(epinfo && buffer && buflen > 0); + + ret = usb_osal_mutex_take(g_ehci.exclsem); + if (ret < 0) { + return ret; + } + + ret = usb_ehci_ioc_setup(epinfo); + + if (ret != 0) { + //usbhost_trace1(EHCI_TRACE1_DEVDISCONNECTED, -ret); + goto errout_with_sem; + } + + ret = usb_ehci_intr_setup(epinfo, buffer, buflen); + if (ret < 0) { + goto errout_with_iocwait; + } + + /* And wait for the transfer to complete */ + nbytes = usb_ehci_transfer_wait(epinfo); + usb_osal_mutex_give(g_ehci.exclsem); + return nbytes; + +errout_with_iocwait: + epinfo->iocwait = false; +errout_with_sem: + usb_osal_mutex_give(g_ehci.exclsem); + return ret; +} +#ifdef CONFIG_USBHOST_ASYNCH +int usbh_ep_bulk_async_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen, usbh_asynch_callback_t callback, void *arg) +{ + int ret; + + struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep; + + DEBUGASSERT(epinfo && buffer && buflen > 0); + + ret = usb_osal_mutex_take(g_ehci.exclsem); + if (ret < 0) { + return ret; + } + + /* Set the request for the callback well BEFORE initiating the transfer. */ + ret = usb_ehci_ioc_async_setup(epinfo, callback, arg); + if (ret != 0) { + goto errout_with_sem; + } + + /* Check for errors in the setup of the transfer */ + ret = usb_ehci_bulk_setup(epinfo, buffer, buflen); + if (ret < 0) { + goto errout_with_callback; + } + + /* The transfer is in progress */ + usb_osal_mutex_give(g_ehci.exclsem); + return 0; + +errout_with_callback: + epinfo->callback = NULL; + epinfo->arg = NULL; +errout_with_sem: + usb_osal_mutex_give(&g_ehci.exclsem); + return ret; +} + +int usbh_ep_intr_async_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen, usbh_asynch_callback_t callback, void *arg) +{ + int ret; + + struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep; + + DEBUGASSERT(epinfo && buffer && buflen > 0); + + ret = usb_osal_mutex_take(g_ehci.exclsem); + if (ret < 0) { + return ret; + } + + /* Set the request for the callback well BEFORE initiating the transfer. */ + ret = usb_ehci_ioc_async_setup(epinfo, callback, arg); + if (ret != 0) { + goto errout_with_sem; + } + + /* Check for errors in the setup of the transfer */ + ret = usb_ehci_intr_setup(epinfo, buffer, buflen); + if (ret < 0) { + goto errout_with_callback; + } + + /* The transfer is in progress */ + usb_osal_mutex_give(g_ehci.exclsem); + return 0; + +errout_with_callback: + epinfo->callback = NULL; + epinfo->arg = NULL; +errout_with_sem: + usb_osal_mutex_give(g_ehci.exclsem); + return ret; +} +#endif +/**************************************************************************** + * Name: usb_ehci_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 + * + ****************************************************************************/ + +int usb_ep_cancel(usbh_epinfo_t ep) +{ + struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep; + struct usb_ehci_qh_s *qh; +#ifdef CONFIG_USBHOST_ASYNCH + usbh_asynch_callback_t callback; + void *arg; +#endif + uint32_t *bp; + uint32_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 = usb_osal_sem_take(g_ehci.exclsem); + 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 = usb_osal_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; + usb_osal_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 = 0; + goto errout_with_sem; + } + + /* Handle the cancellation according to the type of the transfer */ + + switch (epinfo->xfrtype) { + case USB_ENDPOINT_TYPE_CONTROL: + case USB_ENDPOINT_TYPE_BULK: { + /* Get the horizontal pointer from the head of the asynchronous + * queue. + */ + + bp = (uint32_t *)&g_asynchead.hw.hlp; + qh = (struct usb_ehci_qh_s *) + usb_ehci_virtramaddr(usb_ehci_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 = 0; + goto exit_terminate; + } + } break; + +#ifndef CONFIG_USBHOST_INT_DISABLE + case USB_ENDPOINT_TYPE_INTERRUPT: { + /* Get the horizontal pointer from the head of the interrupt + * queue. + */ + + bp = (uint32_t *)&g_intrhead.hw.hlp; + qh = (struct usb_ehci_qh_s *) + usb_ehci_virtramaddr(usb_ehci_swap32(*bp) & QH_HLP_MASK); + if (qh) { + /* if the queue is empty, then just claim that we successfully + * cancelled the transfer. + */ + + ret = 0; + goto exit_terminate; + } + } break; +#endif + +#ifndef CONFIG_USBHOST_ISOC_DISABLE + case USB_ENDPOINT_TYPE_ISOCHRONOUS: +#warning "Isochronous endpoint support not emplemented" +#endif + default: + //usbhost_trace1(EHCI_TRACE1_BADXFRTYPE, epinfo->xfrtype); + ret = -ENOSYS; + goto errout_with_sem; + } + + /* 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 = usb_ehci_qh_foreach(qh, &bp, usb_ehci_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); + usb_osal_sem_give(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 */ + + usb_osal_sem_give(epinfo->iocsem); +#endif + +errout_with_sem: + usb_osal_mutex_give(g_ehci.exclsem); + return ret; +} + +static void usb_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). + */ + + usb_osal_mutex_take(g_ehci.exclsem); + + /* 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); + } + + usb_ehci_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) { + usb_ehci_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) + { + + } +#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) { + //usbhost_trace1(EHCI_TRACE1_SYSERR_INTR, 0); + } + /* 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) { + //usbhost_vtrace1(EHCI_VTRACE1_AAINTR, 0); + } + /* Re-enable relevant EHCI interrupts. Interrupts should still be enabled + * at the level of the interrupt controller. + */ + + usb_ehci_putreg(EHCI_HANDLED_INTS, &HCOR->usbintr); + + /* We are done with the EHCI structures */ + usb_osal_mutex_give(g_ehci.exclsem); +} + +void usb_ehci_interrupt(void) +{ + uint32_t usbsts; + uint32_t pending; + uint32_t regval; + + /* Read Interrupt Status and mask out interrupts that are not enabled. */ + + usbsts = usb_ehci_getreg(&HCOR->usbsts); + regval = usb_ehci_getreg(&HCOR->usbintr); + + /* 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. + */ + + usb_workqueue_submit(&g_hpworkq, &g_ehci.work, usb_ehci_bottomhalf, (void *)pending, 0); + /* Disable further EHCI interrupts so that we do not overrun the work + * queue. + */ + usb_ehci_putreg(0, &HCOR->usbintr); + + /* Clear all pending status bits by writing the value of the pending + * interrupt bits back to the status register. + */ + usb_ehci_putreg(usbsts & EHCI_INT_ALLINTS, &HCOR->usbsts); + } +} \ No newline at end of file diff --git a/port/ehci/usb_ehci.h b/port/ehci/usb_ehci.h new file mode 100644 index 00000000..1393a3b8 --- /dev/null +++ b/port/ehci/usb_ehci.h @@ -0,0 +1,1006 @@ +/**************************************************************************** + * include/nuttx/usb/ehci.h + * + * 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. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_USB_EHCI_H +#define __INCLUDE_NUTTX_USB_EHCI_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* General definitions ******************************************************/ + +/* Endpoint speed values as used in endpoint characteristics field. + * NOTE: These values are *NOT* the same as the SPEED definitions in usb.h. + */ + +#define EHCI_FULL_SPEED (0) /* Full-Speed (12Mbs) */ +#define EHCI_LOW_SPEED (1) /* Low-Speed (1.5Mbs) */ +#define EHCI_HIGH_SPEED (2) /* High-Speed (480 Mb/s) */ + +#define EHCI_DIR_IN (1) /* Direction IN: Peripheral to host */ +#define EHCI_DIR_OUT (0) /* Direction OUT: Host to peripheral */ + +/* PCI Configuration Space Register Offsets *********************************/ + +/* Paragraph 2.1 */ + +/* 0x0009-0x000b: Class Code */ + +#define EHCI_PCI_CLASSC_OFFSET 0x0009 + +/* 0x0010-0x0013: Base Address to Memory-mapped Host Controller Register + * Space + */ + +#define EHCI_PCIUSBBASE_OFFSET 0x0010 + +/* 0x0060: Serial Bus Release Number */ + +#define EHCI_PCI_SBRN_OFFSET 0x0060 + +/* 0x0061: Frame Length Adjustment Register */ + +#define EHCI_PCI_FLADJ_OFFSET 0x0061 + +/* 0x0062-0x0063: Port wake capabilities register (OPTIONAL) */ + +#define EHCI_PCI_PORTWAKECAP_OFFSET 0x0062 + +/* EECP+0x0000: USB Legacy Support EHCI Extended Capability Register */ + +#define EHCI_PCI_USBLEGSUP_OFFSET 0x0000 + +/* EECP+0x0000: USB Legacy Support Control and Status Register */ + +#define EHCI_PCI_USBLEGCTLSTS_OFFSET 0x0004 + +/* Host Controller Capability Register Offsets ******************************/ + +/* Paragraph 2.2 */ + +#define EHCI_CAPLENGTH_OFFSET 0x0000 /* Core Capability Register Length */ + /* 0x0001 Reserved */ +#define EHCI_HCIVERSION_OFFSET 0x0002 /* Core Interface Version Number */ +#define EHCI_HCSPARAMS_OFFSET 0x0004 /* Core Structural Parameters */ +#define EHCI_HCCPARAMS_OFFSET 0x0008 /* Core Capability Parameters */ +#define EHCI_HCSP_PORTROUTE_OFFSET 0x000c /* Core Companion Port Route Description */ + +/* Host Controller Operational Register Offsets *****************************/ + +/* Paragraph 2.3 */ + +#define EHCI_USBCMD_OFFSET 0x0000 /* USB Command */ +#define EHCI_USBSTS_OFFSET 0x0004 /* USB Status */ +#define EHCI_USBINTR_OFFSET 0x0008 /* USB Interrupt Enable */ +#define EHCI_FRINDEX_OFFSET 0x000c /* USB Frame Index */ +#define EHCI_CTRLDSSEGMENT_OFFSET 0x0010 /* 4G Segment Selector */ +#define EHCI_PERIODICLISTBASE_OFFSET 0x0014 /* Frame List Base Address */ +#define EHCI_ASYNCLISTADDR_OFFSET 0x0018 /* Next Asynchronous List Address */ + /* 0x001c-0x003f: Reserved */ +#define EHCI_CONFIGFLAG_OFFSET 0x0040 /* Configured Flag Register */ + +/* Port Status/Control, Port 1-n */ + +#define EHCI_PORTSC_OFFSET(n) (0x0044 + ((n-1) << 2)) +#define EHCI_PORTSC1_OFFSET 0x0044 /* Port Status/Control, Port 1 */ +#define EHCI_PORTSC2_OFFSET 0x0048 /* Port Status/Control, Port 2 */ +#define EHCI_PORTSC3_OFFSET 0x004c /* Port Status/Control, Port 3 */ +#define EHCI_PORTSC4_OFFSET 0x0050 /* Port Status/Control, Port 4 */ +#define EHCI_PORTSC5_OFFSET 0x0054 /* Port Status/Control, Port 5 */ +#define EHCI_PORTSC6_OFFSET 0x0058 /* Port Status/Control, Port 6 */ +#define EHCI_PORTSC7_OFFSET 0x005c /* Port Status/Control, Port 7 */ +#define EHCI_PORTSC8_OFFSET 0x0060 /* Port Status/Control, Port 8 */ +#define EHCI_PORTSC9_OFFSET 0x0064 /* Port Status/Control, Port 9 */ +#define EHCI_PORTSC10_OFFSET 0x0068 /* Port Status/Control, Port 10 */ +#define EHCI_PORTSC11_OFFSET 0x006c /* Port Status/Control, Port 11 */ +#define EHCI_PORTSC12_OFFSET 0x0070 /* Port Status/Control, Port 12 */ +#define EHCI_PORTSC13_OFFSET 0x0074 /* Port Status/Control, Port 13 */ +#define EHCI_PORTSC14_OFFSET 0x0078 /* Port Status/Control, Port 14 */ +#define EHCI_PORTSC15_OFFSET 0x007c /* Port Status/Control, Port 15 */ + +/* Debug Register Offsets ***************************************************/ + +/* Paragraph C.3 */ + +#define EHCI_DEBUG_PCS_OFFSET 0x0000 /* Debug Port Control/Status Register */ +#define EHCI_DEBUG_USBPIDS_OFFSET 0x0004 /* Debug USB PIDs Register */ +#define EHCI_DEBUG_DATA0_OFFSET 0x0008 /* Debug Data Buffer 0 Register [31:0] */ +#define EHCI_DEBUG_DATA1_OFFSET 0x000c /* Debug Data Buffer 1 Register [63:32] */ +#define EHCI_DEBUG_DEVADDR_OFFSET 0x0010 /* Debug Device Address Register */ + +/* PCI Configuration Space Register Bit Definitions *************************/ + +/* 0x0009-0x000b: Class Code. Paragraph 2.1.2 */ + +#define EHCI_PCI_PI_SHIFT (0) /* Bits 0-7: Programming Interface */ +#define EHCI_PCI_PI_MASK (0xff << EHCI_PCI_PI_SHIFT) +# define EHCI_PCI_PI (0x20 << EHCI_PCI_PI_SHIFT) +#define EHCI_PCI_SCC_SHIFT (8) /* Bits 8-15: Sub-Class Code */ +#define EHCI_PCI_SCC_MASK (0xff << EHCI_PCI_SCC_SHIFT) +# define EHCI_PCI_SCC (0x03 << EHCI_PCI_SCC_SHIFT) +#define EHCI_PCI_BASEC_SHIFT (16) /* Base Class Code */ +#define EHCI_PCI_BASEC_MASK (0xff << EHCI_PCI_BASEC_SHIFT) +# define EHCI_PCI_BASEC (0x0c << EHCI_PCI_BASEC_SHIFT) +#define EHCI_PCI_CLASSC 0x000c0320 /* Default value (little endian) */ + +/* 0x0010-0x0013: Base Address to Memory-mapped Host Controller Register + * Space. Paragraph 2.1.3 + */ + + /* Bit 0: Reserved */ + +#define EHCI_PCIUSBBASE_TYPE_SHIFT (0) /* Bits 1-2: Type */ +#define EHCI_PCIUSBBASE_TYPE_MASK (3 << EHCI_PCIUSBBASE_TYPE_SHIFT) +# define EHCI_PCIUSBBASE_TYPE_32BIT (3 << EHCI_PCIUSBBASE_TYPE_SHIFT) /* 32-bit addressing */ +# define EHCI_PCIUSBBASE_TYPE_64BIT (3 << EHCI_PCIUSBBASE_TYPE_SHIFT) /* 64-bit addressing */ + + /* Bits 3-7: Reserved */ +#define EHCI_PCIUSBBASE_BASE_SHIFT (8) /* Bits 8-31: Base address */ +#define EHCI_PCIUSBBASE_BASE_MASK (0xffffff00) + +/* 0x0060: Serial Bus Release Number. Paragraph 2.1.4 */ + +#define EHCI_PCI_SBRN_MASK 0xff /* Bits 0-7: Serial Bus Release Number */ + +/* 0x0061: Frame Length Adjustment Register. Paragraph 2.1.5 */ + +#define EHCI_PCI_FLADJ_SHIFT (0) /* Bit 0-5: Frame Length Timing Value */ +#define EHCI_PCI_FLADJ_MASK (0x3f >> EHCI_PCI_FLADJ_SHIFT) + /* Bits 6-7: Reserved */ + +/* 0x0062-0x0063: Port wake capabilities register (OPTIONAL). + * Paragraph 2.1.6 + */ + +#define EHCI_PCI_PORTWAKECAP_MASK (0xffff) + +/* EECP+0x0000: USB Legacy Support EHCI Extended Capability Register. + * Paragraph 2.1.7 + */ + +#define EHCI_PCI_USBLEGSUP_CAPID_SHIFT (0) /* Bits 0-7 Capability ID */ +#define EHCI_PCI_USBLEGSUP_CAPID_MASK (0xff << EHCI_PCI_USBLEGSUP_CAPID_SHIFT) +#define EHCI_PCI_USBLEGSUP_NEECP_SHIFT (8) /* Bits 8-15: Next EHCI Extended Capability Pointer */ +#define EHCI_PCI_USBLEGSUP_NEECP_MASK (0xff << EHCI_PCI_USBLEGSUP_NEECP_SHIFT) +#define EHCI_PCI_USBLEGSUP_BOWN (1 << 16) /* Bit 16: HC BIOS Owned Semaphore */ + /* Bits 17-23: Reserved */ +#define EHCI_PCI_USBLEGSUP_OSOWN (1 << 24) /* Bit 24: HC OS Owned Semaphore */ + /* Bits 25-31: Reserved */ + +/* EECP+0x0000: USB Legacy Support Control and Status Register. + * Paragraph 2.1.8 + */ + +#define EHCI_PCI_USBLEGCTLSTS_USBCMPEN (1 << 0) /* Bit 0: USB SMI Enable */ +#define EHCI_PCI_USBLEGCTLSTS_USBERREN (1 << 1) /* Bit 1: SMI on USB Error Enable */ +#define EHCI_PCI_USBLEGCTLSTS_PCHEN (1 << 2) /* Bit 2: SMI on Port Change Enable */ +#define EHCI_PCI_USBLEGCTLSTS_FLREN (1 << 3) /* Bit 3: SMI on Frame List Rollover Enable */ +#define EHCI_PCI_USBLEGCTLSTS_HSEEN (1 << 4) /* Bit 4: SMI on Host System Error Enable */ +#define EHCI_PCI_USBLEGCTLSTS_AAEN (1 << 5) /* Bit 5: SMI on Async Advance Enable */ + /* Bits 6-12: Reserved */ +#define EHCI_PCI_USBLEGCTLSTS_OOEN (1 << 13) /* Bit 13: SMI on OS Ownership Enable */ +#define EHCI_PCI_USBLEGCTLSTS_PCEN (1 << 14) /* Bit 14: SMI on PCI Command Enable */ +#define EHCI_PCI_USBLEGCTLSTS_BAREN (1 << 15) /* Bit 15: SMI on BAR Enable */ +#define EHCI_PCI_USBLEGCTLSTS_USBCMP (1 << 16) /* Bit 16: SMI on USB Complete */ +#define EHCI_PCI_USBLEGCTLSTS_USBERR (1 << 17) /* Bit 17: SMI on USB Error */ +#define EHCI_PCI_USBLEGCTLSTS_PCH (1 << 18) /* Bit 18: SMI on Port Change Detect */ +#define EHCI_PCI_USBLEGCTLSTS_FLR (1 << 19) /* Bit 19: SMI on Frame List Rollover */ +#define EHCI_PCI_USBLEGCTLSTS_HSE (1 << 20) /* Bit 20: SMI on Host System Error */ +#define EHCI_PCI_USBLEGCTLSTS_AA (1 << 21) /* Bit 21: SMI on Async Advance */ + /* Bits 22-28: Reserved */ +#define EHCI_PCI_USBLEGCTLSTS_OO (1 << 29) /* Bit 29: SMI on OS Ownership Change */ +#define EHCI_PCI_USBLEGCTLSTS_PC (1 << 30) /* Bit 30: SMI on PCI Command */ +#define EHCI_PCI_USBLEGCTLSTS_BAR (1 << 31) /* Bit 31: SMI on BAR */ + +/* Host Controller Capability Register Bit Definitions **********************/ + +/* Paragraph 2.2 */ + +/* Core Capability Register Length. Paragraph 2.2.1. 8-bit length. */ + +/* Core Interface Version Number. Paragraph 2.2.2. Two byte BCD encoding */ + +/* Core Structural Parameters. Paragraph 2.2.3 */ + +#define EHCI_HCSPARAMS_NPORTS_SHIFT (0) /* Bit 0-3: Number of physical downstream ports */ +#define EHCI_HCSPARAMS_NPORTS_MASK (15 << EHCI_HCSPARAMS_NPORTS_SHIFT) +#define EHCI_HCSPARAMS_PPC (1 << 4) /* Bit 4: Port Power Control */ + /* Bits 5-6: Reserved */ +#define EHCI_HCSPARAMS_PRR (1 << 7) /* Bit 7: Port Routing Rules */ +#define EHCI_HCSPARAMS_NPCC_SHIFT (8) /* Bit 8-11: Number of Ports per Companion Controller */ +#define EHCI_HCSPARAMS_NPCC_MASK (15 << EHCI_HCSPARAMS_NPCC_SHIFT) +#define EHCI_HCSPARAMS_NCC_SHIFT (12) /* Bit 12-15: Number of Companion Controllers */ +#define EHCI_HCSPARAMS_NCC_MASK (15 << EHCI_HCSPARAMS_NCC_SHIFT) +#define EHCI_HCSPARAMS_PIND (1 << 16) /* Bit 16: Port Indicators */ + /* Bits 17-19: Reserved */ +#define EHCI_HCSPARAMS_DBGPORT_SHIFT (20) /* Bit 20-23: Debug Port Number */ +#define EHCI_HCSPARAMS_DBGPORT_MASK (15 << EHCI_HCSPARAMS_DBGPORT_SHIFT) + /* Bits 24-31: Reserved */ + +/* Core Capability Parameters. Paragraph 2.2.4 */ + +#define EHCI_HCCPARAMS_64BIT (1 << 0) /* Bit 0: 64-bit Addressing Capability */ +#define EHCI_HCCPARAMS_PFLF (1 << 1) /* Bit 1: Programmable Frame List Flag */ +#define EHCI_HCCPARAMS_ASPC (1 << 2) /* Bit 2: Asynchronous Schedule Park Capability */ + /* Bit 3: Reserved */ +#define EHCI_HCCPARAMS_IST_SHIFT (4) /* Bits 4-7: Isochronous Scheduling Threshold */ +#define EHCI_HCCPARAMS_IST_MASK (15 << EHCI_HCCPARAMS_IST_SHIFT) +#define EHCI_HCCPARAMS_EECP_SHIFT (8) /* Bits 8-15: EHCI Extended Capabilities Pointer */ +#define EHCI_HCCPARAMS_EECP_MASK (0xff << EHCI_HCCPARAMS_EECP_SHIFT) + /* Bits 16-31: Reserved */ + +/* Core Companion Port Route Description. + * Paragraph 2.2.5. 15 x 4-bit array (60 bits) + */ + +/* Host Controller Operational Register Bit Definitions *********************/ + +/* Paragraph 2.3 */ + +/* USB Command. Paragraph 2.3.1 */ + +#define EHCI_USBCMD_RUN (1 << 0) /* Bit 0: Run/Stop */ +#define EHCI_USBCMD_HCRESET (1 << 1) /* Bit 1: Host Controller Reset */ +#define EHCI_USBCMD_FLSIZE_SHIFT (2) /* Bits 2-3: Frame List Size */ +#define EHCI_USBCMD_FLSIZE_MASK (3 << EHCI_USBCMD_FLSIZE_SHIFT) +# define EHCI_USBCMD_FLSIZE_1024 (0 << EHCI_USBCMD_FLSIZE_SHIFT) /* 1024 elements (4096 bytes) */ +# define EHCI_USBCMD_FLSIZE_512 (1 << EHCI_USBCMD_FLSIZE_SHIFT) /* 512 elements (2048 bytes) */ +# define EHCI_USBCMD_FLSIZE_256 (2 << EHCI_USBCMD_FLSIZE_SHIFT) /* 256 elements (1024 bytes) */ + +#define EHCI_USBCMD_PSEN (1 << 4) /* Bit 4: Periodic Schedule Enable */ +#define EHCI_USBCMD_ASEN (1 << 5) /* Bit 5: Asynchronous Schedule Enable */ +#define EHCI_USBCMD_IAADB (1 << 6) /* Bit 6: Interrupt on Async Advance Doorbell */ +#define EHCI_USBCMD_LRESET (1 << 7) /* Bit 7: Light Host Controller Reset */ +#define EHCI_USBCMD_PARKCNT_SHIFT (8) /* Bits 8-9: Asynchronous Schedule Park Mode Count */ +#define EHCI_USBCMD_PARKCNT_MASK (3 << EHCI_USBCMD_PARKCNT_SHIFT) + /* Bit 10: Reserved */ +#define EHCI_USBCMD_PARK (1 << 11) /* Bit 11: Asynchronous Schedule Park Mode Enable */ + /* Bits 12-15: Reserved */ +#define EHCI_USBCMD_ITHRE_SHIFT (16) /* Bits 16-23: Interrupt Threshold Control */ +#define EHCI_USBCMD_ITHRE_MASK (0xff << EHCI_USBCMD_ITHRE_SHIFT) +# define EHCI_USBCMD_ITHRE_1MF (0x01 << EHCI_USBCMD_ITHRE_SHIFT) /* 1 micro-frame */ +# define EHCI_USBCMD_ITHRE_2MF (0x02 << EHCI_USBCMD_ITHRE_SHIFT) /* 2 micro-frames */ +# define EHCI_USBCMD_ITHRE_4MF (0x04 << EHCI_USBCMD_ITHRE_SHIFT) /* 4 micro-frames */ +# define EHCI_USBCMD_ITHRE_8MF (0x08 << EHCI_USBCMD_ITHRE_SHIFT) /* 8 micro-frames (default, 1 ms) */ +# define EHCI_USBCMD_ITHRE_16MF (0x10 << EHCI_USBCMD_ITHRE_SHIFT) /* 16 micro-frames (2 ms) */ +# define EHCI_USBCMD_ITHRE_32MF (0x20 << EHCI_USBCMD_ITHRE_SHIFT) /* 32 micro-frames (4 ms) */ +# define EHCI_USBCMD_ITHRE_64MF (0x40 << EHCI_USBCMD_ITHRE_SHIFT) /* 64 micro-frames (8 ms) */ + + /* Bits 24-31: Reserved */ + +/* USB Status. Paragraph 2.3.2 */ + +/* USB Interrupt Enable. Paragraph 2.3.3 */ + +#define EHCI_INT_USBINT (1 << 0) /* Bit 0: USB Interrupt */ +#define EHCI_INT_USBERRINT (1 << 1) /* Bit 1: USB Error Interrupt */ +#define EHCI_INT_PORTSC (1 << 2) /* Bit 2: Port Change Detect */ +#define EHCI_INT_FLROLL (1 << 3) /* Bit 3: Frame List Rollover */ +#define EHCI_INT_SYSERROR (1 << 4) /* Bit 4: Host System Error */ +#define EHCI_INT_AAINT (1 << 5) /* Bit 5: Interrupt on Async Advance */ +#define EHCI_INT_ALLINTS (0x3f) /* Bits 0-5: All interrupts */ + /* Bits 6-11: Reserved */ +#define EHCI_USBSTS_HALTED (1 << 12) /* Bit 12: HC Halted */ +#define EHCI_USBSTS_RECLAM (1 << 13) /* Bit 13: Reclamation */ +#define EHCI_USBSTS_PSS (1 << 14) /* Bit 14: Periodic Schedule Status */ +#define EHCI_USBSTS_ASS (1 << 15) /* Bit 15: Asynchronous Schedule Status */ + /* Bits 16-31: Reserved */ + +/* USB Frame Index. Paragraph 2.3.4 */ + +#define EHCI_FRINDEX_MASK (0x1fff) /* Bits 0-13: Frame index */ + /* Bits 14-31: Reserved */ + +/* 4G Segment Selector. + * Paragraph 2.3.5, Bits[64:32] of data structure addresses + */ + +/* Frame List Base Address. Paragraph 2.3.6 */ + + /* Bits 0-11: Reserved */ +#define EHCI_PERIODICLISTBASE_MASK (0xfffff000) /* Bits 12-31: Base Address (Low) */ + +/* Next Asynchronous List Address. Paragraph 2.3.7 */ + + /* Bits 0-4: Reserved */ +#define EHCI_ASYNCLISTADDR_MASK (0xffffffe0) /* Bits 5-31: Link Pointer Low (LPL) */ + +/* Configured Flag Register. Paragraph 2.3.8 */ + +#define EHCI_CONFIGFLAG (1 << 0) /* Bit 0: Configure Flag */ + /* Bits 1-31: Reserved */ + +/* Port Status/Control, Port 1-n. Paragraph 2.3.9 */ + +#define EHCI_PORTSC_CCS (1 << 0) /* Bit 0: Current Connect Status */ +#define EHCI_PORTSC_CSC (1 << 1) /* Bit 1: Connect Status Change */ +#define EHCI_PORTSC_PE (1 << 2) /* Bit 2: Port Enable */ +#define EHCI_PORTSC_PEC (1 << 3) /* Bit 3: Port Enable/Disable Change */ +#define EHCI_PORTSC_OCA (1 << 4) /* Bit 4: Over-current Active */ +#define EHCI_PORTSC_OCC (1 << 5) /* Bit 5: Over-current Change */ +#define EHCI_PORTSC_RESUME (1 << 6) /* Bit 6: Force Port Resume */ +#define EHCI_PORTSC_SUSPEND (1 << 7) /* Bit 7: Suspend */ +#define EHCI_PORTSC_RESET (1 << 8) /* Bit 8: Port Reset */ + /* Bit 9: Reserved */ +#define EHCI_PORTSC_LSTATUS_SHIFT (10) /* Bits 10-11: Line Status */ +#define EHCI_PORTSC_LSTATUS_MASK (3 << EHCI_PORTSC_LSTATUS_SHIFT) +# define EHCI_PORTSC_LSTATUS_SE0 (0 << EHCI_PORTSC_LSTATUS_SHIFT) /* SE0 Not Low-speed device, perform EHCI reset */ +# define EHCI_PORTSC_LSTATUS_KSTATE (1 << EHCI_PORTSC_LSTATUS_SHIFT) /* K-state Low-speed device, release ownership of port */ +# define EHCI_PORTSC_LSTATUS_JSTATE (2 << EHCI_PORTSC_LSTATUS_SHIFT) /* J-state Not Low-speed device, perform EHCI reset */ + +#define EHCI_PORTSC_PP (1 << 12) /* Bit 12: Port Power */ +#define EHCI_PORTSC_OWNER (1 << 13) /* Bit 13: Port Owner */ +#define EHCI_PORTSC_PIC_SHIFT (14) /* Bits 14-15: Port Indicator Control */ +#define EHCI_PORTSC_PIC_MASK (3 << EHCI_PORTSC_PIC_SHIFT) +# define EHCI_PORTSC_PIC_OFF (0 << EHCI_PORTSC_PIC_SHIFT) /* Port indicators are off */ +# define EHCI_PORTSC_PIC_AMBER (1 << EHCI_PORTSC_PIC_SHIFT) /* Amber */ +# define EHCI_PORTSC_PIC_GREEN (2 << EHCI_PORTSC_PIC_SHIFT) /* Green */ + +#define EHCI_PORTSC_PTC_SHIFT (16) /* Bits 16-19: Port Test Control */ +#define EHCI_PORTSC_PTC_MASK (15 << EHCI_PORTSC_PTC_SHIFT) +# define EHCI_PORTSC_PTC_DISABLED (0 << EHCI_PORTSC_PTC_SHIFT) /* Test mode not enabled */ +# define EHCI_PORTSC_PTC_JSTATE (1 << EHCI_PORTSC_PTC_SHIFT) /* Test J_STATE */ +# define EHCI_PORTSC_PTC_KSTATE (2 << EHCI_PORTSC_PTC_SHIFT) /* Test K_STATE */ +# define EHCI_PORTSC_PTC_SE0NAK (3 << EHCI_PORTSC_PTC_SHIFT) /* Test SE0_NAK */ +# define EHCI_PORTSC_PTC_PACKET (4 << EHCI_PORTSC_PTC_SHIFT) /* Test Packet */ +# define EHCI_PORTSC_PTC_ENABLE (5 << EHCI_PORTSC_PTC_SHIFT) /* Test FORCE_ENABLE */ + +#define EHCI_PORTSC_WKCCNTE (1 << 20) /* Bit 20: Wake on Connect Enable */ +#define EHCI_PORTSC_WKDSCNNTE (1 << 21) /* Bit 21: Wake on Disconnect Enable */ +#define EHCI_PORTSC_WKOCE (1 << 22) /* Bit 22: Wake on Over-current Enable */ + /* Bits 23-31: Reserved */ + +#define EHCI_PORTSC_ALLINTS (EHCI_PORTSC_CSC | EHCI_PORTSC_PEC | \ + EHCI_PORTSC_OCC | EHCI_PORTSC_RESUME) + +/* Debug Register Bit Definitions *******************************************/ + +/* Debug Port Control/Status Register. Paragraph C.3.1 */ + +#define EHCI_DEBUG_PCS_LENGTH_SHIFT (0) /* Bits 0-3: Data Length */ +#define EHCI_DEBUG_PCS_LENGTH_MASK (15 << EHCI_DEBUG_PCS_LENGTH_SHIFT) +#define EHCI_DEBUG_PCS_WRITE (1 << 4) /* Bit 6: Write/Read# */ +#define EHCI_DEBUG_PCS_GO (1 << 5) /* Bit 5: Go */ +#define EHCI_DEBUG_PCS_ERROR (1 << 6) /* Bit 6: Error/Good# */ +#define EHCI_DEBUG_PCS_EXCEPTION_SHIFT (17) /* Bits 7-9: Exception */ +#define EHCI_DEBUG_PCS_EXCEPTION_MASK (7 << EHCI_DEBUG_PCS_EXCEPTION_SHIFT) +#define EHCI_DEBUG_PCS_INUSE (1 << 10) /* Bit 10: In Use */ + /* Bits 11-15: Reserved */ +#define EHCI_DEBUG_PCS_DONE (1 << 16) /* Bit 16: Done */ + /* Bits 17-27: Reserved */ +#define EHCI_DEBUG_PCS_ENABLED (1 << 28) /* Bit 28: Enabled */ + /* Bit 29: Reserved */ +#define EHCI_DEBUG_PCS_OWNER (1 << 30) /* Bit 30: Owner */ + /* Bit 31: Reserved */ + +/* Debug USB PIDs Register. Paragraph C.3.2 */ + +#define EHCI_DEBUG_USBPIDS_TKPID_SHIFT (0) /* Bits 0-7: Token PID */ +#define EHCI_DEBUG_USBPIDS_TKPID_MASK (0xff << EHCI_DEBUG_USBPIDS_TKPID_SHIFT) +#define EHCI_DEBUG_USBPIDS_SPID_SHIFT (8) /* Bits 8-15: Sent PID */ +#define EHCI_DEBUG_USBPIDS_SPID_MASK (0xff << EHCI_DEBUG_USBPIDS_SPID_SHIFT) +#define EHCI_DEBUG_USBPIDS_RPID_SHIFT (16) /* Bits 16-23: Received PID */ +#define EHCI_DEBUG_USBPIDS_RPID_MASK (0xff << EHCI_DEBUG_USBPIDS_RPID_SHIFT) + /* Bits 24-31: Reserved */ + +/* Debug Data Buffer 0/1 Register [64:0]. + * Paragreph C.3.3. 64 bits of data. + */ + +/* Debug Device Address Register. Paragraph C.3.4 */ + +#define EHCI_DEBUG_DEVADDR_ENDPT_SHIFT (0) /* Bit 0-3: USB Endpoint */ +#define EHCI_DEBUG_DEVADDR_ENDPT_MASK (15 << EHCI_DEBUG_DEVADDR_ENDPT_SHIFT) + /* Bits 4-7: Reserved */ +#define EHCI_DEBUG_DEVADDR_ADDR_SHIFT (8) /* Bits 8-14: USB Address */ +#define EHCI_DEBUG_DEVADDR_ADDR_MASK (0x7f << EHCI_DEBUG_DEVADDR_ADDR_SHIFT) + /* Bits 15-31: Reserved */ + +/* Data Structures **********************************************************/ + +/* Paragraph 3 */ + +/* Periodic Frame List. Paragraph 3.1 */ + +#define PFL_T (1 << 0) /* Bit 0: Terminate, Link pointer invalid */ +#define PFL_TYP_SHIFT (1) /* Bits 1-2: Type */ +#define PFL_TYP_MASK (3 << PFL_TYP_SHIFT) +# define PFL_TYP_ITD (0 << PFL_TYP_SHIFT) /* Isochronous Transfer Descriptor */ +# define PFL_TYP_QH (1 << PFL_TYP_SHIFT) /* Queue Head */ +# define PFL_TYP_SITD (2 << PFL_TYP_SHIFT) /* Split Transaction Isochronous Transfer Descriptor */ +# define PFL_TYP_FSTN (3 << PFL_TYP_SHIFT) /* Frame Span Traversal Node */ + + /* Bits 3-4: zero */ +#define PFL_MASK (0xffffffe0) /* Bits 5-31: Frame List Link Pointer */ + +/* Aysnchronous List Queue Head Pointer. + * Paragraph 3.2. Circular list of queue heads + */ + +/* Isochronous (High-Speed) Transfer Descriptor (iTD). Paragraph 3.3 */ + +/* iTD Next Link Pointer. Paragraph 3.3.1 */ + +#define ITD_NLP_T (1 << 0) /* Bit 0: Terminate, Link pointer invalid */ +#define ITD_NLP_TYP_SHIFT (1) /* Bits 1-2: Type */ +#define ITD_NLP_TYP_MASK (3 << ITD_NLP_TYP_SHIFT) +# define ITD_NLP_TYP_ITD (0 << ITD_NLP_TYP_SHIFT) /* Isochronous Transfer Descriptor */ +# define ITD_NLP_TYP_QH (1 << ITD_NLP_TYP_SHIFT) /* Queue Head */ +# define ITD_NLP_TYP_SITD (2 << ITD_NLP_TYP_SHIFT) /* Split Transaction Isochronous Transfer Descriptor */ +# define ITD_NLP_TYP_FSTN (3 << ITD_NLP_TYP_SHIFT) /* Frame Span Traversal Node */ + + /* Bits 3-4: zero */ +#define ITD_NLP_MASK (0xffffffe0) /* Bits 5-31: Frame List Link Pointer */ + +/* iTD Transaction Status and Control List. Paragraph 3.3.2 */ + +#define ITD_TRAN_XOFFS_SHIFT (0) /* Bits 0-11: Transaction X offset */ +#define ITD_TRAN_XOFFS_MASK (0xfff << ITD_TRAN_XOFFS_SHIFT) +#define ITD_TRAN_PG_SHIFT (12) /* Bits 12-14: Page select */ +#define ITD_TRAN_PG_MASK (7 << ITD_TRAN_PG_SHIFT) +#define ITD_TRAN_IOC (1 << 15) /* Bit 15: Interrupt On Comp */ +#define ITD_TRAN_LENGTH_SHIFT (16) /* Bits 16-27: Transaction length */ +#define ITD_TRAN_LENGTH_MASK (0xfff << ITD_TRAN_LENGTH_SHIFT) +#define ITD_TRAN_STATUS_SHIFT (28) /* Bits 28-31: Transaction status */ +#define ITD_TRAN_STATUS_MASK (15 << ITD_TRAN_STATUS_SHIFT) +# define ITD_TRAN_STATUS_XACTERR (1 << 28) /* Bit 28: Transaction error */ +# define ITD_TRAN_STATUS_BABBLE (1 << 29) /* Bit 29: Babble Detected */ +# define ITD_TRAN_STATUS_DBERROR (1 << 30) /* Bit 30: Data Buffer Error */ +# define ITD_TRAN_STATUS_ACTIVE (1 << 31) /* Bit 28: Transaction error */ + +/* iTD Buffer Page Pointer List. Paragraph 3.3.4 */ + +/* iTD Buffer Pointer Page 0. Table 3-4 */ + +#define ITD_BUFPTR0_DEVADDR_SHIFT (0) /* Bits 0-6: Device Address */ +#define ITD_BUFPTR0_DEVADDR_MASK (0x7f << ITD_BUFPTR0_DEVADDR_SHIFT) + /* Bit 7: Reserved */ +#define ITD_BUFPTR0_ENDPT_SHIFT (8) /* Bits 8-11: Endpoint Number */ +#define ITD_BUFPTR0_ENDPT_MASK (15 << ITD_BUFPTR0_ENDPT_SHIFT) + +/* iTD Buffer Pointer Page 1. Table 3-5 */ + +#define ITD_BUFPTR1_MAXPKT_SHIFT (0) /* Bits 0-10: Maximum Packet Size */ +#define ITD_BUFPTR1_MAXPKT_MASK (0x7ff << ITD_BUFPTR1_MAXPKT_SHIFT) +#define ITD_BUFPTR1_DIRIN (1 << 11) /* Bit 11: Direction 1=IN */ +#define ITD_BUFPTR1_DIROUT (0) /* Bit 11: Direction 0=OUT */ + +/* iTD Buffer Pointer Page 2. Table 3-6 */ + +#define ITD_BUFPTR2_MULTI_SHIFT (0) /* Bits 0-1: Multi */ +#define ITD_BUFPTR2_MULTI_MASK (3 << ITD_BUFPTR2_MULTI_SHIFT) +# define ITD_BUFPTR2_MULTI_1 (1 << ITD_BUFPTR2_MULTI_SHIFT) /* One transaction per micro-frame */ +# define ITD_BUFPTR2_MULTI_2 (2 << ITD_BUFPTR2_MULTI_SHIFT) /* Two transactions per micro-frame */ +# define ITD_BUFPTR2_MULTI_3 (3 << ITD_BUFPTR2_MULTI_SHIFT) /* Three transactions per micro-frame */ + + /* Bits 2-11: Reserved */ + +/* iTD Buffer Pointer Page 3-6. Table 3-7 */ + + /* Bits 0-11: Reserved */ + +/* iTD Buffer Pointer All Pages */ + +#define ITD_BUFPTR_MASK (0xfffff000) /* Bits 12-31: Buffer Pointer */ + +/* Split Transaction Isochronous Transfer Descriptor (siTD). + * Paragraph 3.4 + */ + +/* siTD Next Link Pointer. Paragraph 3.4.1 */ + +#define SITD_NLP_T (1 << 0) /* Bit 0: Terminate, Link pointer invalid */ +#define SITD_NLP_TYP_SHIFT (1) /* Bits 1-2: Type */ +#define SITD_NLP_TYP_MASK (3 << SITD_NLP_TYP_SHIFT) +# define SITD_NLP_TYP_ITD (0 << SITD_NLP_TYP_SHIFT) /* Isochronous Transfer Descriptor */ +# define SITD_NLP_TYP_QH (1 << SITD_NLP_TYP_SHIFT) /* Queue Head */ +# define SITD_NLP_TYP_SITD (2 << SITD_NLP_TYP_SHIFT) /* Split Transaction Isochronous Transfer Descriptor */ +# define SITD_NLP_TYP_FSTN (3 << SITD_NLP_TYP_SHIFT) /* Frame Span Traversal Node */ + + /* Bits 3-4: zero */ +#define SITD_NLP_MASK (0xffffffe0) /* Bits 5-31: Frame List Link Pointer */ + +/* siTD Endpoint Capabilities/Characteristics. + * Paragraph 3.4.2 + */ + +/* Endpoint and Transaction Translator Characteristics. Table 3-9 */ + +#define SITD_EPCHAR_DEVADDR_SHIFT (0) /* Bitx 0-6: Device Address */ +#define SITD_EPCHAR_DEVADDR_MASK (0x7f << SITD_EPCHAR_DEVADDR_SHIFT) + /* Bits 7: Reserved */ +#define SITD_EPCHAR_ENDPT_SHIFT (8) /* Bitx 8-11: Endpoint Number */ +#define SITD_EPCHAR_ENDPT_MASK (15 << SITD_EPCHAR_ENDPT_SHIFT) + /* Bits 12-15: Reserved */ +#define SITD_EPCHAR_HUBADDR_SHIFT (16) /* Bitx 16-22: Hub Address */ +#define SITD_EPCHAR_HUBADDR_MASK (0x7f << SITD_EPCHAR_HUBADDR_SHIFT) + /* Bit 23: Reserved */ +#define SITD_EPCHAR_DIRIN (1 << 31) /* Bit 31: Direction 1=IN */ +#define SITD_EPCHAR_DIROUT (0) /* Bit 31: Direction 0=OUT */ + +/* Micro-frame Schedule Control. Table 3-10 */ + +#define SITD_FMSCHED_SSMASK_SHIFT (0) /* Bitx 0-7: Split Start Mask (�Frame S-mask) */ +#define SITD_FMSCHED_SSMASK_MASK (0xff << SITD_FMSCHED_SSMASK_SHIFT) +# define SITD_FMSCHED_SSMASK(n) ((n) << SITD_FMSCHED_SSMASK_SHIFT) +#define SITD_FMSCHED_SCMASK_SHIFT (8) /* Bitx 8-15: Split Completion Mask (�Frame C-Mask) */ +#define SITD_FMSCHED_SCMASK_MASK (0xff << SITD_FMSCHED_SCMASK_SHIFT) +# define SITD_FMSCHED_SCMASK(n) ((n) << SITD_FMSCHED_SCMASK_SHIFT) + /* Bits 16-31: Reserved */ + +/* siTD Transfer State. Paragraph 3.4.3 */ + +#define SITD_XFRSTATE_STATUS_SHIFT (0) /* Bits 0-7: Status */ +#define SITD_XFRSTATE_STATUS_MASK (0xff << SITD_XFRSTATE_STATUS_SHIFT) +#define SITD_XFRSTATE_CPROGMASK_SHIFT (8) /* Bits 8-15: �Frame Complete-split Progress Mask */ +#define SITD_XFRSTATE_CPROGMASK_MASK (0xff << SITD_XFRSTATE_CPROGMASK_SHIFT) +#define SITD_XFRSTATE_NBYTES_SHIFT (16) /* Bits 16-25: Total Bytes To Transfer */ +#define SITD_XFRSTATE_NBYTES_MASK (0x3ff << SITD_XFRSTATE_NBYTES_SHIFT) + /* Bits 26-29: Reserved */ +#define SITD_XFRSTATE_P (1 << 30) /* Bit 30: Page Select */ +#define SITD_XFRSTATE_IOC (1 << 31) /* Bit 31: Interrupt On Complete */ + +/* siTD Buffer Pointer List. + * Paragraph 3.4.4 + */ + +/* Page 0 */ + +#define SITD_BUFPTR0_OFFSET_SHIFT (0) /* Bits 0-11: Current Offset */ +#define SITD_BUFPTR0_OFFSET_MASK (0xff << SITD_BUFPTR0_OFFSET_SHIFT) + +/* Page 1 */ + +#define SITD_BUFPTR1_TCOUNT_SHIFT (0) /* Bits 0-2: Transaction count */ +#define SITD_BUFPTR1_TCOUNT_MASK (7 << SITD_BUFPTR1_TCOUNT_SHIFT) +#define SITD_BUFPTR1_TP_SHIFT (33) /* Bits 3-4: Transaction position */ +#define SITD_BUFPTR1_TP_MASK (3 << SITD_BUFPTR1_TP_SHIFT) +# define SITD_BUFPTR1_TP_ENTIRE (0 << SITD_BUFPTR1_TP_SHIFT) /* Entire full-speed transaction data payload. */ +# define SITD_BUFPTR1_TP_BEGIN (1 << SITD_BUFPTR1_TP_SHIFT) /* This is the first data payload */ +# define SITD_BUFPTR1_TP_MID (2 << SITD_BUFPTR1_TP_SHIFT) /* This the middle payload */ +# define SITD_BUFPTR1_TP_END (3 << SITD_BUFPTR1_TP_SHIFT) /* This is the last payload */ + + /* Bits 5-11: Reserved */ + +/* All pages */ + +#define SITD_BUFPTR_MASK (0xfffff000) /* Bits 12-31: Buffer Pointer List */ + +/* Queue Element Transfer Descriptor (qTD). + * Paragraph 3.5 + */ + +/* Next qTD Pointer. + * Paragraph 3.5.1 + */ + +#define QTD_NQP_T (1 << 0) /* Bit 0: Terminate */ + /* Bits 1-4: Reserved */ +#define QTD_NQP_NTEP_SHIFT (5) /* Bits 5-31: Next Transfer Element Pointer */ +#define QTD_NQP_NTEP_MASK (0xffffffe0) + +/* Alternate Next qTD Pointer. + * Paragraph 3.5.2 + */ + +#define QTD_AQP_T (1 << 0) /* Bit 0: Terminate */ + /* Bits 1-4: Reserved */ +#define QTD_AQP_NTEP_SHIFT (5) /* Bits 5-31: Next Transfer Element Pointer */ +#define QTD_AQP_NTEP_MASK (0xffffffe0) + +/* qTD Token. + * Paragraph 3.5.3 + */ + +#define QTD_TOKEN_STATUS_SHIFT (0) /* Bits 0-7: Status */ +#define QTD_TOKEN_STATUS_MASK (0xff << QTD_TOKEN_STATUS_SHIFT) +# define QTD_TOKEN_P (1 << 0) /* Bit 0 Ping State */ +# define QTD_TOKEN_ERR (1 << 0) /* Bit 0 Error */ +# define QTD_TOKEN_SPLITXSTATE (1 << 1) /* Bit 1 Split Transaction State */ +# define QTD_TOKEN_MMF (1 << 2) /* Bit 2 Missed Micro-Frame */ +# define QTD_TOKEN_XACTERR (1 << 3) /* Bit 3 Transaction Error */ +# define QTD_TOKEN_BABBLE (1 << 4) /* Bit 4 Babble Detected */ +# define QTD_TOKEN_DBERR (1 << 5) /* Bit 5 Data Buffer Error */ +# define QTD_TOKEN_HALTED (1 << 6) /* Bit 6 Halted */ +# define QTD_TOKEN_ACTIVE (1 << 7) /* Bit 7 Active */ +# define QTD_TOKEN_ERRORS (0x78 << QTD_TOKEN_STATUS_SHIFT) +#define QTD_TOKEN_PID_SHIFT (8) /* Bits 8-9: PID Code */ +#define QTD_TOKEN_PID_MASK (3 << QTD_TOKEN_PID_SHIFT) +# define QTD_TOKEN_PID_OUT (0 << QTD_TOKEN_PID_SHIFT) /* OUT Token generates token (E1H) */ +# define QTD_TOKEN_PID_IN (1 << QTD_TOKEN_PID_SHIFT) /* IN Token generates token (69H) */ +# define QTD_TOKEN_PID_SETUP (2 << QTD_TOKEN_PID_SHIFT) /* SETUP Token generates token (2DH) */ + +#define QTD_TOKEN_CERR_SHIFT (10) /* Bits 10-11: Error Counter */ +#define QTD_TOKEN_CERR_MASK (3 << QTD_TOKEN_CERR_SHIFT) +#define QTD_TOKEN_CPAGE_SHIFT (12) /* Bits 12-14: Current Page */ +#define QTD_TOKEN_CPAGE_MASK (7 << QTD_TOKEN_CPAGE_SHIFT) +#define QTD_TOKEN_IOC (1 << 15) /* Bit 15: Interrupt On Complete */ +#define QTD_TOKEN_NBYTES_SHIFT (16) /* Bits 16-30: Total Bytes to Transfer */ +#define QTD_TOKEN_NBYTES_MASK (0x7fff << QTD_TOKEN_NBYTES_SHIFT) +#define QTD_TOKEN_TOGGLE_SHIFT (31) /* Bit 31: Data Toggle */ +#define QTD_TOKEN_TOGGLE (1 << 31) /* Bit 31: Data Toggle */ + +/* qTD Buffer Page Pointer List. + * Paragraph 3.5.4 + */ + +/* Page 0 */ + +#define QTD_BUFPTR0_OFFFSET_SHIFT (0) /* Bits 0-11: Current Offset */ +#define QTD_BUFPTR0_OFFFSET_MASK (0xfff << QTD_BUFPTR0_OFFFSET_SHIFT) + +/* Other pages */ + + /* Bits 0-11: Reserved */ + +/* All pages */ + +#define QTD_BUFPTR_SHIFT (12) /* Bits 12-31: Buffer Pointer List */ +#define QTD_BUFPTR_MASK (0xfffff000) + +/* Queue Head. Paragraph 3.6 */ + +/* Queue Head Horizontal Link Pointer. + * Paragraph 3.6.1 + */ + +#define QH_HLP_T (1 << 0) /* Bit 0: Terminate, QH HL pointer invalid */ +#define QH_HLP_TYP_SHIFT (1) /* Bits 1-2: Type */ +#define QH_HLP_TYP_MASK (3 << QH_HLP_TYP_SHIFT) +# define QH_HLP_TYP_ITD (0 << QH_HLP_TYP_SHIFT) /* Isochronous Transfer Descriptor */ +# define QH_HLP_TYP_QH (1 << QH_HLP_TYP_SHIFT) /* Queue Head */ +# define QH_HLP_TYP_SITD (2 << QH_HLP_TYP_SHIFT) /* Split Transaction Isochronous Transfer Descriptor */ +# define QH_HLP_TYP_FSTN (3 << QH_HLP_TYP_SHIFT) /* Frame Span Traversal Node */ + + /* Bits 3-4: Reserved */ +#define QH_HLP_MASK (0xffffffe0) /* Bits 5-31: Queue Head Horizontal Link Pointer */ + +/* Endpoint Capabilities/Characteristics. Paragraph 3.6.2 */ + +/* Endpoint Characteristics: Queue Head DWord. Table 3-19 */ + +#define QH_EPCHAR_DEVADDR_SHIFT (0) /* Bitx 0-6: Device Address */ +#define QH_EPCHAR_DEVADDR_MASK (0x7f << QH_EPCHAR_DEVADDR_SHIFT) +#define QH_EPCHAR_I (1 << 7) /* Bit 7: Inactivate on Next Transaction */ +#define QH_EPCHAR_ENDPT_SHIFT (8) /* Bitx 8-11: Endpoint Number */ +#define QH_EPCHAR_ENDPT_MASK (15 << QH_EPCHAR_ENDPT_SHIFT) +#define QH_EPCHAR_EPS_SHIFT (12) /* Bitx 12-13: Endpoint Speed */ +#define QH_EPCHAR_EPS_MASK (3 << QH_EPCHAR_EPS_SHIFT) +# define QH_EPCHAR_EPS_FULL (0 << QH_EPCHAR_EPS_SHIFT) /* Full-Speed (12Mbs) */ +# define QH_EPCHAR_EPS_LOW (1 << QH_EPCHAR_EPS_SHIFT) /* Low-Speed (1.5Mbs) */ +# define QH_EPCHAR_EPS_HIGH (2 << QH_EPCHAR_EPS_SHIFT) /* High-Speed (480 Mb/s) */ + +#define QH_EPCHAR_DTC (1 << 14) /* Bit 14: Data Toggle Control */ +#define QH_EPCHAR_H (1 << 15) /* Bit 15: Head of Reclamation List Flag */ +#define QH_EPCHAR_MAXPKT_SHIFT (16) /* Bitx 16-26: Maximum Packet Length */ +#define QH_EPCHAR_MAXPKT_MASK (0x7ff << QH_EPCHAR_MAXPKT_SHIFT) +#define QH_EPCHAR_C (1 << 27) /* Bit 27: Control Endpoint Flag */ +#define QH_EPCHAR_RL_SHIFT (28) /* Bitx 28-31: Nak Count Reload */ +#define QH_EPCHAR_RL_MASK (15 << QH_EPCHAR_RL_SHIFT) + +/* Endpoint Capabilities: Queue Head DWord 2. Table 3-20 */ + +#define QH_EPCAPS_SSMASK_SHIFT (0) /* Bitx 0-7: Interrupt Schedule Mask (�Frame S-mask) */ +#define QH_EPCAPS_SSMASK_MASK (0xff << QH_EPCAPS_SSMASK_SHIFT) +# define QH_EPCAPS_SSMASK(n) ((n) << QH_EPCAPS_SSMASK_SHIFT) +#define QH_EPCAPS_SCMASK_SHIFT (8) /* Bitx 8-15: Split Completion Mask (�Frame C-Mask) */ +#define QH_EPCAPS_SCMASK_MASK (0xff << QH_EPCAPS_SCMASK_SHIFT) +# define QH_EPCAPS_SCMASK(n) ((n) << QH_EPCAPS_SCMASK_SHIFT) +#define QH_EPCAPS_HUBADDR_SHIFT (16) /* Bitx 16-22: Hub Address */ +#define QH_EPCAPS_HUBADDR_MASK (0x7f << QH_EPCAPS_HUBADDR_SHIFT) +# define QH_EPCAPS_HUBADDR(n) ((n) << QH_EPCAPS_HUBADDR_SHIFT) +#define QH_EPCAPS_PORT_SHIFT (23) /* Bit 23-29: Port Number */ +#define QH_EPCAPS_PORT_MASK (0x7f << QH_EPCAPS_PORT_SHIFT) +# define QH_EPCAPS_PORT(n) ((n) << QH_EPCAPS_PORT_SHIFT) +#define QH_EPCAPS_MULT_SHIFT (30) /* Bit 30-31: High-Bandwidth Pipe Multiplier */ +#define QH_EPCAPS_MULT_MASK (3 << QH_EPCAPS_MULT_SHIFT) +# define QH_EPCAPS_MULT(n) ((n) << QH_EPCAPS_MULT_SHIFT) + +/* Current qTD Link Pointer. Table 3-21 */ + +#define QH_CQP_NTEP_SHIFT (5) /* Bits 5-31: Next Transfer Element Pointer */ +#define QH_CQP_NTEP_MASK (0xffffffe0) + +/* Transfer Overlay. Paragraph 3.6.3 + * + * NOTES: + * 1. Same as the field of the same name in struct ehci_qtd_s + * 2. Similar to the field of the same name in struct ehci_qtd_s, but with + * some additional bitfields. + */ + +/* Next qTD Pointer (NOTE 1) */ + +#define QH_NQP_T (1 << 0) /* Bit 0: Terminate */ + /* Bits 1-4: Reserved */ +#define QH_NQP_NTEP_SHIFT (5) /* Bits 5-31: Next Transfer Element Pointer */ +#define QH_NQP_NTEP_MASK (0xffffffe0) + +/* Alternate Next qTD Pointer. Table 3.7 (NOTE 2) */ + +#define QH_AQP_T (1 << 0) /* Bit 0: Terminate */ +#define QH_AQP_NAKCNT (1) /* Bits 1-4: Nak Counter */ +#define QH_AQP_NTEP_SHIFT (5) /* Bits 5-31: Next Transfer Element Pointer */ +#define QH_AQP_NTEP_MASK (0xffffffe0) + +/* qTD Token (NOTE 1) */ + +#define QH_TOKEN_STATUS_SHIFT (0) /* Bits 0-7: Status */ +#define QH_TOKEN_STATUS_MASK (0xff << QH_TOKEN_STATUS_SHIFT) +# define QH_TOKEN_P (1 << 0) /* Bit 0 Ping State */ +# define QH_TOKEN_ERR (1 << 0) /* Bit 0 Error */ +# define QH_TOKEN_SPLITXSTATE (1 << 1) /* Bit 1 Split Transaction State */ +# define QH_TOKEN_MMF (1 << 2) /* Bit 2 Missed Micro-Frame */ +# define QH_TOKEN_XACTERR (1 << 3) /* Bit 3 Transaction Error */ +# define QH_TOKEN_BABBLE (1 << 4) /* Bit 4 Babble Detected */ +# define QH_TOKEN_DBERR (1 << 5) /* Bit 5 Data Buffer Error */ +# define QH_TOKEN_HALTED (1 << 6) /* Bit 6 Halted */ +# define QH_TOKEN_ACTIVE (1 << 7) /* Bit 7 Active */ +# define QH_TOKEN_ERRORS (0x78 << QH_TOKEN_STATUS_SHIFT) +#define QH_TOKEN_PID_SHIFT (8) /* Bits 8-9: PID Code */ +#define QH_TOKEN_PID_MASK (3 << QH_TOKEN_PID_SHIFT) +# define QH_TOKEN_PID_OUT (0 << QH_TOKEN_PID_SHIFT) /* OUT Token generates token (E1H) */ +# define QH_TOKEN_PID_IN (1 << QH_TOKEN_PID_SHIFT) /* IN Token generates token (69H) */ +# define QH_TOKEN_PID_SETUP (2 << QH_TOKEN_PID_SHIFT) /* SETUP Token generates token (2DH) */ + +#define QH_TOKEN_CERR_SHIFT (10) /* Bits 10-11: Error Counter */ +#define QH_TOKEN_CERR_MASK (3 << QH_TOKEN_CERR_SHIFT) +#define QH_TOKEN_CPAGE_SHIFT (12) /* Bits 12-14: Current Page */ +#define QH_TOKEN_CPAGE_MASK (7 << QH_TOKEN_CPAGE_SHIFT) +#define QH_TOKEN_IOC (1 << 15) /* Bit 15: Interrupt On Complete */ +#define QH_TOKEN_NBYTES_SHIFT (16) /* Bits 16-30: Total Bytes to Transfer */ +#define QH_TOKEN_NBYTES_MASK (0x7fff << QH_TOKEN_NBYTES_SHIFT) +#define QH_TOKEN_TOGGLE_SHIFT (31) /* Bit 31: Data Toggle */ +#define QH_TOKEN_TOGGLE (1 << 31) /* Bit 31: Data Toggle */ + +/* Buffer Page Pointer List (NOTE 2) */ + +/* Page 0 */ + +#define QH_BUFPTR0_OFFFSET_SHIFT (0) /* Bits 0-11: Current Offset */ +#define QH_BUFPTR0_OFFFSET_MASK (0xfff << QH_BUFPTR0_OFFFSET_SHIFT) + +/* Page 1. Table 3.22 */ + +#define QH_BUFPTR1_CPROGMASK_SHIFT (0) /* Bits 0-7: Split-transaction Complete-split Progress */ +#define QH_BUFPTR1_CPROGMASK_MASK (0xff << QH_BUFPTR1_CPROGMASK_SHIFT) + /* Bits 8-11: Reserved */ + +/* Page 2. Table 3.22 */ + +#define QH_BUFPTR2_FRAMETAG_SHIFT (0) /* Bits 0-4: Split-transaction Frame Tag */ +#define QH_BUFPTR2_FRAMETAG_MASK (31 << QH_BUFPTR2_FRAMETAG_SHIFT) +#define QH_BUFPTR2_SBYTES_SHIFT (5) /* Bits 5-11: S-bytes */ +#define QH_BUFPTR2_SBYTES_MASK (0x7f << QH_BUFPTR2_SBYTES_SHIFT) + +/* Other pages */ + + /* Bits 0-11: Reserved */ + +/* All pages */ + +#define QH_BUFPTR_SHIFT (12) /* Bits 12-31: Buffer Pointer List */ +#define QH_BUFPTR_MASK (0xfffff000) + +/* Periodic Frame Span Traversal Node (STN). Paragrap 3.7 */ + +/* FSTN Normal Path Pointer. Paragraph 3.7.1 */ + +#define FSTN_NPP_T (1 << 0) /* Bit 0: Terminate. 1=Link Pointer not valid */ +#define FSTN_NPP_TYP_SHIFT (1) /* Bits 1-2: Type */ +#define FSTN_NPP_TYP_MASK (3 << FSTN_NPP_TYP_SHIFT) +# define FSTN_NPP_TYP_ITD (0 << FSTN_NPP_TYP_SHIFT) /* Isochronous Transfer Descriptor */ +# define FSTN_NPP_TYP_QH (1 << FSTN_NPP_TYP_SHIFT) /* Queue Head */ +# define FSTN_NPP_TYP_SITD (2 << FSTN_NPP_TYP_SHIFT) /* Split Transaction Isochronous Transfer Descriptor */ +# define FSTN_NPP_TYP_FSTN (3 << FSTN_NPP_TYP_SHIFT) /* Frame Span Traversal Node */ + + /* Bits 3-4: Reserved */ +#define FSTN_NPP_NPLP_SHIFT (5) /* Bits 5-31: Normal Path Link Pointer */ +#define FSTN_NPP_NPLP_MASK (0xffffffe0) + +/* FSTN Back Path Link Pointer. Paragraph 3.7.2 */ + +#define FSTN_BPP_T (1 << 0) /* Bit 0: Terminate. 1=Link Pointer not valid */ +#define FSTN_BPP_TYP_SHIFT (1) /* Bits 1-2: Type */ +#define FSTN_BPP_TYP_MASK (3 << FSTN_BPP_TYP_SHIFT) +# define FSTN_BPP_TYP_QH (1 << FSTN_BPP_TYP_SHIFT) /* Queue Head */ + + /* Bits 3-4: Reserved */ +#define FSTN_BPP_BPLP_SHIFT (5) /* Bits 5-31: Back Path Link Pointer */ +#define FSTN_BPP_BPLP_MASK (0xffffffe0) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* Registers ****************************************************************/ + +/* Since the operational registers are not known a compile time, representing + * register blocks with structures is more convenient than using individual + * register offsets. + */ + +/* Host Controller Capability Registers. + * This register block must be positioned at a well known address. + */ + +struct ehci_hccr_s +{ + uint8_t caplength; /* 0x00: Capability Register Length */ + uint8_t reserved; + uint16_t hciversion; /* 0x02: Interface Version Number */ + uint32_t hcsparams; /* 0x04: Structural Parameters */ + uint32_t hccparams; /* 0x08: Capability Parameters */ + uint8_t hcspportrt[8]; /* 0x0c: Companion Port Route Description */ +}; + +/* Host Controller Operational Registers. + * This register block is positioned at an offset of 'caplength' from the + * beginning of the Host Controller Capability Registers. + */ + +struct ehci_hcor_s +{ + uint32_t usbcmd; /* 0x00: USB Command */ + uint32_t usbsts; /* 0x04: USB Status */ + uint32_t usbintr; /* 0x08: USB Interrupt Enable */ + uint32_t frindex; /* 0x0c: USB Frame Index */ + uint32_t ctrldssegment; /* 0x10: 4G Segment Selector */ + uint32_t periodiclistbase; /* 0x14: Frame List Base Address */ + uint32_t asynclistaddr; /* 0x18: Next Asynchronous List Address */ + // uint32_t reserved[9]; + uint32_t configflag; /* 0x40: Configured Flag Register */ + uint32_t portsc[15]; /* 0x44: Port Status/Control */ +}; + +/* USB2 Debug Port Register Interface. + * This register block is normally found via the PCI capabalities. + * In non-PCI implementions, you need apriori information about the + * location of these registers. + */ + +struct ehci_debug_s +{ + uint32_t psc; /* 0x00: Debug Port Control/Status Register */ + uint32_t pids; /* 0x04: Debug USB PIDs Register */ + uint32_t data[2]; /* 0x08: Debug Data buffer Registers */ + uint32_t addr; /* 0x10: Device Address Register */ +}; + +/* Data Structures **********************************************************/ + +/* Paragraph 3 */ + +/* Periodic Frame List. + * Paragraph 3.1. An array of pointers. + */ + +/* Aysnchronous List Queue Head Pointer. + * Paragraph 3.2. Circular list of queue heads + */ + +/* Isochronous (High-Speed) Transfer Descriptor (iTD). + * Paragraph 3.3. Must be aligned to 32-byte boundaries. + */ + +struct ehci_itd_s +{ + uint32_t nlp; /* 0x00-0x03: Next link pointer */ + uint32_t trans[8]; /* 0x04-0x23: Transaction Status and Control List */ + uint32_t bpl[7]; /* 0x24-0x3c: Buffer Page Pointer List */ +}; + +#define SIZEOF_EHCI_ITD_S (64) /* 16*sizeof(uint32_t) */ + +/* Split Transaction Isochronous Transfer Descriptor (siTD). Paragraph 3.4 */ + +struct ehci_sitd_s +{ + uint32_t nlp; /* 0x00-0x03: Next link pointer */ + uint32_t epchar; /* 0x04-0x07: Endpoint and Transaction Translator Characteristics */ + uint32_t fmsched; /* 0x08-0x0b: Micro-frame Schedule Control */ + uint32_t xfrstate; /* 0x0c-0x0f: Transfer Status and Control */ + uint32_t bpl[2]; /* 0x10-0x17: Buffer Pointer List */ + uint32_t blp; /* 0x18-0x1b: Back link pointer */ +}; + +#define SIZEOF_EHCI_SITD_S (28) /* 7*sizeof(uint32_t) */ + +/* Queue Element Transfer Descriptor (qTD). Paragraph 3.5 */ + +/* 32-bit version. See EHCI Appendix B for the 64-bit version. */ + +struct ehci_qtd_s +{ + uint32_t nqp; /* 0x00-0x03: Next qTD Pointer */ + uint32_t alt; /* 0x04-0x07: Alternate Next qTD Pointer */ + uint32_t token; /* 0x08-0x0b: qTD Token */ + uint32_t bpl[5]; /* 0x0c-0x1c: Buffer Page Pointer List */ +}; + +#define SIZEOF_EHCI_QTD_S (32) /* 8*sizeof(uint32_t) */ + +/* Queue Head. Paragraph 3.6 + * + * NOTE: + * 1. Same as the field of the same name in struct ehci_qtd_s + * 2. Similar to the field of the same name in struct ehci_qtd_s, + * but with some additional bitfields. + */ + +struct ehci_overlay_s +{ + uint32_t nqp; /* 0x00-0x03: Next qTD Pointer (NOTE 1) */ + uint32_t alt; /* 0x04-0x07: Alternate Next qTD Pointer (NOTE 2) */ + uint32_t token; /* 0x08-0x0b: qTD Token (NOTE 1) */ + uint32_t bpl[5]; /* 0x0c-0x1c: Buffer Page Pointer List (NOTE 2) */ +}; + +#define SIZEOF_EHCI_OVERLAY (32) /* 8*sizeof(uint32_t) */ + +struct ehci_qh_s +{ + uint32_t hlp; /* 0x00-0x03: Queue Head Horizontal Link Pointer */ + uint32_t epchar; /* 0x04-0x07: Endpoint Characteristics */ + uint32_t epcaps; /* 0x08-0x0b: Endpoint Capabilities */ + uint32_t cqp; /* 0x0c-0x0f: Current qTD Pointer */ + struct ehci_overlay_s overlay; /* 0x10-0x2c: Transfer overlay */ +}; + +#define SIZEOF_EHCI_QH (48) /* 4*sizeof(uint32_t) + SIZEOF_EHCI_OVERLAY */ + +/* Periodic Frame Span Traversal Node (STN). Paragrap 3.7 */ + +struct ehci_fstn_s +{ + uint32_t npp; /* 0x00-0x03: Normal Path Pointer */ + uint32_t bpp; /* 0x04-0x07: Back Path Link Pointer */ +}; + +#define SIZEOF_EHCI_FSTN_S (8) /* 2*sizeof(uint32_t) */ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#undef EXTERN +#ifdef __cplusplus +} +#endif +#endif /* __INCLUDE_NUTTX_USB_EHCI_H */ diff --git a/port/synopsys/usb_hc_synopsys.c b/port/synopsys/usb_hc_synopsys.c new file mode 100644 index 00000000..c58183ed --- /dev/null +++ b/port/synopsys/usb_hc_synopsys.c @@ -0,0 +1,481 @@ +#include "usbh_core.h" +#include "stm32f4xx_hal.h" + +#define USBH_PID_SETUP 0U +#define USBH_PID_DATA 1U + +#define USB_SNOPSYS_RETRY_COUNT 5 + +#ifndef CONFIG_USBHOST_CHANNELS +#define CONFIG_USBHOST_CHANNELS 12 /* Number of host channels */ +#endif + +/* This structure retains the state of one host channel. NOTE: Since there + * is only one channel operation active at a time, some of the fields in + * in the structure could be moved in struct stm32_ubhost_s to achieve + * some memory savings. + */ + +struct usb_synopsys_chan { + usb_osal_sem_t waitsem; /* Channel wait semaphore */ + uint8_t chidx; /* Channel index */ + bool inuse; /* True: This channel is "in use" */ +#ifdef CONFIG_USBHOST_ASYNCH + usbh_asynch_callback_t callback; /* Transfer complete callback */ + void *arg; /* Argument that accompanies the callback */ +#endif +}; + +/* A channel represents on uni-directional endpoint. So, in the case of the + * bi-directional, control endpoint, there must be two channels to represent + * the endpoint. + */ + +struct usb_synopsys_ctrlinfo { + uint8_t inndx; /* EP0 IN control channel index */ + uint8_t outndx; /* EP0 OUT control channel index */ +}; + +struct usb_synopsys_priv { + HCD_HandleTypeDef *handle; + volatile bool connected; /* Connected to device */ + volatile bool pscwait; /* True: Thread is waiting for a port event */ + usb_osal_sem_t exclsem; /* Support mutually exclusive access */ + struct usb_synopsys_chan chan[CONFIG_USBHOST_CHANNELS]; +} g_usbhost; + +/**************************************************************************** + * Name: usb_synopsys_chan_alloc + * + * Description: + * Allocate a channel. + * + ****************************************************************************/ + +static int usb_synopsys_chan_alloc(struct usb_synopsys_priv *priv) +{ + int chidx; + + /* Search the table of channels */ + + for (chidx = 0; chidx < CONFIG_USBHOST_CHANNELS; chidx++) { + /* Is this channel available? */ + if (!priv->chan[chidx].inuse) { + /* Yes... make it "in use" and return the index */ + + priv->chan[chidx].inuse = true; + return chidx; + } + } + + /* All of the channels are "in-use" */ + + return -EBUSY; +} + +/**************************************************************************** + * Name: usb_synopsys_chan_free + * + * Description: + * Free a previoiusly allocated channel. + * + ****************************************************************************/ + +static void usb_synopsys_chan_free(struct usb_synopsys_priv *priv, int chidx) +{ + /* Halt the channel */ + HAL_HCD_HC_Halt(priv->handle, chidx); + /* Mark the channel available */ + + priv->chan[chidx].inuse = false; +} + +__WEAK void usb_hc_low_level_init(void) +{ +} + +int usb_hc_init(void) +{ + memset(&g_usbhost, 0, sizeof(struct usb_synopsys_priv)); +#ifdef CONFIG_USB_HS + extern HCD_HandleTypeDef hhcd_USB_OTG_HS; + g_usbhost.handle = &hhcd_USB_OTG_HS; +#else + extern HCD_HandleTypeDef hhcd_USB_OTG_FS; + g_usbhost.handle = &hhcd_USB_OTG_FS; +#endif + + g_usbhost.exclsem = usb_osal_mutex_create(); + + for (uint8_t i = 0; i < CONFIG_USBHOST_CHANNELS; i++) { + struct usb_synopsys_chan *chan = &g_usbhost.chan[i]; + + chan->chidx = i; + + /* The waitsem semaphore is used for signaling and, hence, should not + * have priority inheritance enabled. + */ + chan->waitsem = usb_osal_sem_create(0); + } + usb_hc_low_level_init(); + HAL_HCD_Start(g_usbhost.handle); + return 0; +} + +int usbh_reset_port(const uint8_t port) +{ + HAL_HCD_ResetPort(g_usbhost.handle); + return 0; +} + +uint8_t usbh_get_port_speed(const uint8_t port) +{ + if (HAL_HCD_GetCurrentSpeed(g_usbhost.handle) == 1) { + return USB_SPEED_FULL; + } else if (HAL_HCD_GetCurrentSpeed(g_usbhost.handle) == 2) + return USB_SPEED_LOW; + else + return USB_SPEED_HIGH; +} + +int usbh_ep0_reconfigure(usbh_epinfo_t ep, uint8_t dev_addr, uint8_t ep_mps, uint8_t speed) +{ + struct usb_synopsys_ctrlinfo *ep0info; + int ret; + + ep0info = (struct usb_synopsys_ctrlinfo *)ep; + + ret = usb_osal_mutex_take(g_usbhost.exclsem); + if (ret < 0) { + return ret; + } + + if (speed == USB_SPEED_FULL) { + speed = HCD_SPEED_FULL; + } else if (speed == USB_SPEED_LOW) { + speed = HCD_SPEED_LOW; + } + + ret = HAL_HCD_HC_Init(g_usbhost.handle, ep0info->outndx, 0x00, dev_addr, speed, USB_ENDPOINT_TYPE_CONTROL, ep_mps); + ret = HAL_HCD_HC_Init(g_usbhost.handle, ep0info->inndx, 0x80, dev_addr, speed, USB_ENDPOINT_TYPE_CONTROL, ep_mps); + usb_osal_mutex_give(g_usbhost.exclsem); + return ret; +} + +int usbh_ep_alloc(usbh_epinfo_t *ep, const struct usbh_endpoint_cfg *ep_cfg) +{ + struct usb_synopsys_ctrlinfo *ep0; + struct usbh_hubport *hport; + int chidx; + int ret; + + ret = usb_osal_mutex_take(g_usbhost.exclsem); + if (ret < 0) { + return ret; + } + + hport = ep_cfg->hport; + + if ((ep_cfg->ep_type & USB_ENDPOINT_TYPE_MASK) == USB_ENDPOINT_TYPE_CONTROL) { + ep0 = usb_malloc(sizeof(struct usb_synopsys_ctrlinfo)); + + ep0->outndx = usb_synopsys_chan_alloc(&g_usbhost); + ep0->inndx = usb_synopsys_chan_alloc(&g_usbhost); + + HAL_HCD_HC_Init(g_usbhost.handle, ep0->outndx, 0x00, hport->dev_addr, hport->speed, USB_ENDPOINT_TYPE_CONTROL, ep_cfg->ep_mps); + HAL_HCD_HC_Init(g_usbhost.handle, ep0->inndx, 0x80, hport->dev_addr, hport->speed, USB_ENDPOINT_TYPE_CONTROL, ep_cfg->ep_mps); + + *ep = (usbh_epinfo_t)ep0; + + } else { + chidx = usb_synopsys_chan_alloc(&g_usbhost); + HAL_HCD_HC_Init(g_usbhost.handle, chidx, ep_cfg->ep_addr, hport->dev_addr, hport->speed, ep_cfg->ep_type, ep_cfg->ep_mps); + if ((ep_cfg->ep_type & USB_ENDPOINT_TYPE_MASK) == USB_ENDPOINT_TYPE_BULK) { + if (g_usbhost.handle->hc[chidx].ep_is_in) { + g_usbhost.handle->hc[chidx].toggle_in = 0; + } else { + g_usbhost.handle->hc[chidx].toggle_out = 0; + } + } + *ep = (usbh_epinfo_t)chidx; + } + usb_osal_mutex_give(g_usbhost.exclsem); + return 0; +} +int usbh_ep_free(usbh_epinfo_t ep) +{ + return 0; +} + +int usbh_control_transfer(usbh_epinfo_t ep, struct usb_setup_packet *setup, uint8_t *buffer) +{ + uint8_t retries; + int ret; + struct usb_synopsys_ctrlinfo *ep0info = (struct usb_synopsys_ctrlinfo *)ep; + + ret = usb_osal_mutex_take(g_usbhost.exclsem); + if (ret < 0) { + return ret; + } + for (retries = 0; retries < USB_SNOPSYS_RETRY_COUNT; retries++) { + ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle, + ep0info->outndx, /* Pipe index */ + 0, /* Direction : OUT */ + 0, /* EP type */ + USBH_PID_SETUP, /* Type Data */ + (uint8_t *)setup, /* data buffer */ + 8, /* data length */ + 0); /* do ping (HS Only)*/ + + ret = usb_osal_sem_take(g_usbhost.chan[ep0info->outndx].waitsem); + if (ret < 0) { + return ret; + } + + if (HAL_HCD_HC_GetURBState(g_usbhost.handle, ep0info->outndx) == URB_DONE) { + break; + } + } + + if (retries >= USB_SNOPSYS_RETRY_COUNT) { + goto urb_timeout; + } + if (setup->wLength && buffer) { + if (setup->bmRequestType & 0x80) { + for (retries = 0; retries < USB_SNOPSYS_RETRY_COUNT; retries++) { + ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle, + ep0info->inndx, /* Pipe index */ + 1, /* Direction : IN */ + 0, /* EP type */ + USBH_PID_DATA, /* Type Data */ + buffer, /* data buffer */ + setup->wLength, /* data length */ + 0); /* do ping (HS Only)*/ + usb_osal_sem_take(g_usbhost.chan[ep0info->inndx].waitsem); + if (ret < 0) { + return ret; + } + + if (HAL_HCD_HC_GetURBState(g_usbhost.handle, ep0info->inndx) == URB_DONE) { + break; + } + } + if (retries >= USB_SNOPSYS_RETRY_COUNT) { + goto urb_timeout; + } + for (retries = 0; retries < USB_SNOPSYS_RETRY_COUNT; retries++) { + ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle, + ep0info->outndx, /* Pipe index */ + 0, /* Direction : OUT */ + 0, /* EP type */ + USBH_PID_DATA, /* Type Data */ + NULL, /* data buffer */ + 0, /* data length */ + 0); /* do ping (HS Only)*/ + usb_osal_sem_take(g_usbhost.chan[ep0info->outndx].waitsem); + if (ret < 0) { + return ret; + } + + if (HAL_HCD_HC_GetURBState(g_usbhost.handle, ep0info->outndx) == URB_DONE) { + break; + } + } + if (retries >= USB_SNOPSYS_RETRY_COUNT) { + goto urb_timeout; + } + } else { + for (retries = 0; retries < USB_SNOPSYS_RETRY_COUNT; retries++) { + ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle, + ep0info->outndx, /* Pipe index */ + 0, /* Direction : OUT */ + 0, /* EP type */ + USBH_PID_DATA, /* Type Data */ + buffer, /* data buffer */ + setup->wLength, /* data length */ + 0); /* do ping (HS Only)*/ + + usb_osal_sem_take(g_usbhost.chan[ep0info->outndx].waitsem); + if (ret < 0) { + return ret; + } + + if (HAL_HCD_HC_GetURBState(g_usbhost.handle, ep0info->outndx) == URB_DONE) { + break; + } + } + + if (retries >= USB_SNOPSYS_RETRY_COUNT) { + goto urb_timeout; + } + for (retries = 0; retries < USB_SNOPSYS_RETRY_COUNT; retries++) { + ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle, + ep0info->inndx, /* Pipe index */ + 1, /* Direction : IN */ + 0, /* EP type */ + USBH_PID_DATA, /* Type Data */ + NULL, /* data buffer */ + 0, /* data length */ + 0); /* do ping (HS Only)*/ + usb_osal_sem_take(g_usbhost.chan[ep0info->inndx].waitsem); + if (ret < 0) { + return ret; + } + + if (HAL_HCD_HC_GetURBState(g_usbhost.handle, ep0info->inndx) == URB_DONE) { + break; + } + } + + if (retries >= USB_SNOPSYS_RETRY_COUNT) { + goto urb_timeout; + } + } + } else { + for (retries = 0; retries < USB_SNOPSYS_RETRY_COUNT; retries++) { + ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle, + ep0info->inndx, /* Pipe index */ + 1, /* Direction : IN */ + 0, /* EP type */ + USBH_PID_DATA, /* Type Data */ + NULL, /* data buffer */ + 0, /* data length */ + 0); /* do ping (HS Only)*/ + usb_osal_sem_take(g_usbhost.chan[ep0info->inndx].waitsem); + if (ret < 0) { + return ret; + } + usb_osal_msleep(10); + if (HAL_HCD_HC_GetURBState(g_usbhost.handle, ep0info->inndx) == URB_DONE) { + break; + } + } + if (retries >= USB_SNOPSYS_RETRY_COUNT) { + goto urb_timeout; + } + } + usb_osal_mutex_give(g_usbhost.exclsem); + return 0; +urb_timeout: + usb_osal_mutex_give(g_usbhost.exclsem); + return -ETIMEDOUT; +} +int usbh_ep_bulk_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen) +{ + int ret; + uint8_t chidx = (uint8_t)ep; + + if (g_usbhost.handle->hc[chidx].ep_is_in) { + ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle, + chidx, /* Pipe index */ + 1, /* Direction : IN */ + 2, /* EP type */ + USBH_PID_DATA, /* Type Data */ + buffer, /* data buffer */ + buflen, /* data length */ + 0); /* do ping (HS Only)*/ + + usb_osal_sem_take(g_usbhost.chan[chidx].waitsem); + if (ret < 0) { + return ret; + } + return HAL_HCD_HC_GetXferCount(g_usbhost.handle, chidx); + } else { + ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle, + chidx, /* Pipe index */ + 0, /* Direction : OUT */ + 2, /* EP type */ + USBH_PID_DATA, /* Type Data */ + buffer, /* data buffer */ + buflen, /* data length */ + 0); /* do ping (HS Only)*/ + usb_osal_sem_take(g_usbhost.chan[chidx].waitsem); + if (ret < 0) { + return ret; + } + return HAL_HCD_HC_GetXferCount(g_usbhost.handle, chidx); + } + return -1; +} + +int usbh_ep_intr_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen) +{ + int ret; + uint8_t chidx = (uint8_t)ep; + + if (g_usbhost.handle->hc[chidx].ep_is_in) { + ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle, + chidx, /* Pipe index */ + 1, /* Direction : IN */ + 3, /* EP type */ + USBH_PID_DATA, /* Type Data */ + buffer, /* data buffer */ + buflen, /* data length */ + 0); /* do ping (HS Only)*/ + + usb_osal_sem_take(g_usbhost.chan[chidx].waitsem); + if (ret < 0) { + return ret; + } + return HAL_HCD_HC_GetXferCount(g_usbhost.handle, chidx); + } else { + ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle, + chidx, /* Pipe index */ + 0, /* Direction : OUT */ + 3, /* EP type */ + USBH_PID_DATA, /* Type Data */ + buffer, /* data buffer */ + buflen, /* data length */ + 0); /* do ping (HS Only)*/ + usb_osal_sem_take(g_usbhost.chan[chidx].waitsem); + if (ret < 0) { + return ret; + } + return HAL_HCD_HC_GetXferCount(g_usbhost.handle, chidx); + } + return -1; +} + +int usbh_ep_bulk_async_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen, usbh_asynch_callback_t callback, void *arg) +{ + return 0; +} + +int usbh_ep_intr_async_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen, usbh_asynch_callback_t callback, void *arg) +{ + return 0; +} + +int usb_ep_cancel(usbh_epinfo_t ep) +{ + return 0; +} + +void HAL_Delay(uint32_t Delay) +{ + usb_osal_msleep(Delay); +} + +void HAL_HCD_Connect_Callback(HCD_HandleTypeDef *hhcd) +{ + g_usbhost.connected = true; + extern void usbh_event_notify_handler(uint8_t event, uint8_t rhport); + usbh_event_notify_handler(USBH_EVENT_ATTACHED, 1); +} + +/** + * @brief SOF callback. + * @param hhcd: HCD handle + * @retval None + */ +void HAL_HCD_Disconnect_Callback(HCD_HandleTypeDef *hhcd) +{ + g_usbhost.connected = false; + extern void usbh_event_notify_handler(uint8_t event, uint8_t rhport); + usbh_event_notify_handler(USBH_EVENT_REMOVED, 1); +} + +void HAL_HCD_HC_NotifyURBChange_Callback(HCD_HandleTypeDef *hhcd, uint8_t chnum, HCD_URBStateTypeDef urb_state) +{ + usb_osal_sem_give(g_usbhost.chan[chnum].waitsem); +} \ No newline at end of file