From 9b13eaf59585c662bb92168f59d236c01d94f075 Mon Sep 17 00:00:00 2001 From: Gregory Nutt Date: Wed, 8 May 2019 10:01:37 -0600 Subject: [PATCH] arm/src/lpc54xx/: Bring in the USB FS OHCI driver from LPC17. This is a quick'n'dirty port that I will finish sometime later (I hope). Currently, it is missing hardware clocking setup and is not even hooked into the build system yet. Needless to say it does not yet even compile. --- arch/arm/src/lpc17xx/lpc17_ohciram.h | 2 +- arch/arm/src/lpc54xx/Kconfig | 65 + arch/arm/src/lpc54xx/chip/lpc54_usb0_ohci.h | 129 + arch/arm/src/lpc54xx/lpc54_usb0_ohci.c | 3995 ++++++++++++++++++ arch/arm/src/lpc54xx/lpc54_usb0_ohci.h | 108 + configs/lpcxpresso-lpc54628/twm4nx/defconfig | 2 + 6 files changed, 4300 insertions(+), 1 deletion(-) create mode 100644 arch/arm/src/lpc54xx/chip/lpc54_usb0_ohci.h create mode 100644 arch/arm/src/lpc54xx/lpc54_usb0_ohci.c create mode 100644 arch/arm/src/lpc54xx/lpc54_usb0_ohci.h diff --git a/arch/arm/src/lpc17xx/lpc17_ohciram.h b/arch/arm/src/lpc17xx/lpc17_ohciram.h index bddeaa02ea..2c0bcdcaf0 100644 --- a/arch/arm/src/lpc17xx/lpc17_ohciram.h +++ b/arch/arm/src/lpc17xx/lpc17_ohciram.h @@ -150,7 +150,7 @@ # error "Insufficent TDs" #endif -/* Derived size of user trasnfer descriptor (TD) memory. */ +/* Derived size of user transfer descriptor (TD) memory. */ #define LPC17_TDFREE_SIZE (CONFIG_LP17_USBHOST_NTDS * LPC17_TD_SIZE) diff --git a/arch/arm/src/lpc54xx/Kconfig b/arch/arm/src/lpc54xx/Kconfig index 5b40181360..2bec112e09 100644 --- a/arch/arm/src/lpc54xx/Kconfig +++ b/arch/arm/src/lpc54xx/Kconfig @@ -401,6 +401,12 @@ config LPC54_EMC bool "External Memory Controller (EMC)" default n +config LPC54_OHCI + bool "USB0 OHCI" + select USBHOST + select USBHOST_HAVE_ASYNCH + default n + config LPC54_ETHERNET bool "Ethernet" default n @@ -686,6 +692,65 @@ config LPC54_SPI_WIDEDATA endmenu # SPI Master configuration +menu "USB0 OHCI Options" + depends on LPC54_OHCI + +config LPC54_OHCI_NEDS + int "Number of Endpoint Descriptors" + default 2 + ---help--- + Number of endpoint descriptors. Default: 2 + +config LPC54_OHCI_NTDS + int "Number of transfer descriptors" + default 3 + ---help--- + Number of transfer descriptors. Default: 3 + +config LPC54_OHCI_TDBUFFERS + int "Number of descriptor buffers" + default 2 + ---help--- + Number of transfer descriptor buffers. Default: 2 + +config LPC54_OHCI_TDBUFSIZE + int "Descriptor buffer size" + default 128 + ---help--- + Size of one transfer descriptor buffer. Default 128 + +config LPC54_OHCI_IOBUFSIZE + int "I/O buffer size" + default 512 + ---help--- + Size of one end-user I/O buffer. + +config LPC54_OHCI_NIOBUFFERS + int "Number of I/O buffer" + default 8 if USBHOST_HUB + default 4 if !USBHOST_HUB + ---help--- + Size of one end-user I/O buffer. + +config LPC54_OHCI_NPREALLOC + int "Max concurrent transfers" + default 8 if USBHOST_HUB + default 4 if !USBHOST_HUB + ---help--- + This number represents a number of pre-allocated structures to support + concurrent data transfers. This number limits that number of concurrent + asynchronous IN endpoint transfer that can be supported. + +config LPC54_OHCI_REGDEBUG + bool "Register level debug" + depends on DEBUG_USB_INFO + default n + ---help--- + Output detailed register-level USB host debug information. Requires + also CONFIG_DEBUG_USB_INFO. + +endmenu + menu "Ethernet configuration" depends on LPC54_ETHERNET diff --git a/arch/arm/src/lpc54xx/chip/lpc54_usb0_ohci.h b/arch/arm/src/lpc54xx/chip/lpc54_usb0_ohci.h new file mode 100644 index 0000000000..7e9d48f153 --- /dev/null +++ b/arch/arm/src/lpc54xx/chip/lpc54_usb0_ohci.h @@ -0,0 +1,129 @@ +/************************************************************************************ + * arch/arm/src/lpc54xx/chip/lpc54_usb0_ohci.h + * + * Copyright (C) 2019 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ************************************************************************************/ + +#ifndef __ARCH_ARM_SRC_LPC54XX_CHIP_LPC54_USB0_OHCI_H +#define __ARCH_ARM_SRC_LPC54XX_CHIP_LPC54_USB0_OHCI_H + +/************************************************************************************ + * Included Files + ************************************************************************************/ + +#include +#include + +#include "chip.h" +#include "chip/lpc54_memorymap.h" + +/************************************************************************************ + * Pre-processor Definitions + ************************************************************************************/ + +/* Register offsets *****************************************************************/ + +/* USB Host Controller (OHCI). See include/nuttx/usb/ohci.h */ + +/* Additional, non-standard register offsets */ + +#define LPC54_OHCI_PORTMODE_OFFSET 0x005c /* Port mode register */ + +/* Register addresses ***************************************************************/ + +/* USB Host Controller (OHCI) */ + +/* Control and status registers (section 7.1) */ + +#define LPC54_OHCI_HCIREV (LPC54_FSUSBHOST_BASE + OHCI_HCIREV_OFFSET) +#define LPC54_OHCI_CTRL (LPC54_FSUSBHOST_BASE + OHCI_CTRL_OFFSET) +#define LPC54_OHCI_CMDST (LPC54_FSUSBHOST_BASE + OHCI_CMDST_OFFSET) +#define LPC54_OHCI_INTST (LPC54_FSUSBHOST_BASE + OHCI_INTST_OFFSET) +#define LPC54_OHCI_INTEN (LPC54_FSUSBHOST_BASE + OHCI_INTEN_OFFSET) +#define LPC54_OHCI_INTDIS (LPC54_FSUSBHOST_BASE + OHCI_INTDIS_OFFSET) + +/* Memory pointers (section 7.2) */ + +#define LPC54_OHCI_HCCA (LPC54_FSUSBHOST_BASE + OHCI_HCCA_OFFSET) +#define LPC54_OHCI_PERED (LPC54_FSUSBHOST_BASE + OHCI_PERED_OFFSET) +#define LPC54_OHCI_CTRLHEADED (LPC54_FSUSBHOST_BASE + OHCI_CTRLHEADED_OFFSET) +#define LPC54_OHCI_CTRLED (LPC54_FSUSBHOST_BASE + OHCI_CTRLED_OFFSET) +#define LPC54_OHCI_BULKHEADED (LPC54_FSUSBHOST_BASE + OHCI_BULKHEADED_OFFSET) +#define LPC54_OHCI_BULKED (LPC54_FSUSBHOST_BASE + OHCI_BULKED_OFFSET) +#define LPC54_OHCI_DONEHEAD (LPC54_FSUSBHOST_BASE + OHCI_DONEHEAD_OFFSET) + +/* Frame counters (section 7.3) */ + +#define LPC54_OHCI_FMINT (LPC54_FSUSBHOST_BASE + OHCI_FMINT_OFFSET) +#define LPC54_OHCI_FMREM (LPC54_FSUSBHOST_BASE + OHCI_FMREM_OFFSET) +#define LPC54_OHCI_FMNO (LPC54_FSUSBHOST_BASE + OHCI_FMNO_OFFSET) +#define LPC54_OHCI_PERSTART (LPC54_FSUSBHOST_BASE + OHCI_PERSTART_OFFSET) + +/* Root hub ports (section 7.4) */ + +#define LPC54_OHCI_LSTHRES (LPC54_FSUSBHOST_BASE + OHCI_LSTHRES_OFFSET) +#define LPC54_OHCI_RHDESCA (LPC54_FSUSBHOST_BASE + OHCI_RHDESCA_OFFSET) +#define LPC54_OHCI_RHDESCB (LPC54_FSUSBHOST_BASE + OHCI_RHDESCB_OFFSET) +#define LPC54_OHCI_RHSTATUS (LPC54_FSUSBHOST_BASE + OHCI_RHSTATUS_OFFSET) +#define LPC54_OHCI_RHPORTST1 (LPC54_FSUSBHOST_BASE + OHCI_RHPORTST1_OFFSET) +#define LPC54_OHCI_RHPORTST2 (LPC54_FSUSBHOST_BASE + OHCI_RHPORTST2_OFFSET) + +/* Non-standard Registers */ + +#define LPC54_OHCI_PORTMODE (LPC54_FSUSBHOST_BASE + LPC54_OHCI_PORTMODE_OFFSET) + +/* Register bit definitions *********************************************************/ + +/* USB Host Controller (OHCI). See include/nuttx/usb/ohci.h */ + +/* Port mode register */ + +#define OHCI_PORTMODE_ID (1 << 0) /* Bit 0: Port ID pin value */ + /* Bits 1-7: Reserved */ +#define OHCI_PORTMODE_IDEN (1 << 8) /* Bit 8: Port ID pull-up enable */ + /* Bits 9-15: Reserved */ +#define OHCI_PORTMODE_DEVENABLE (1 << 16) /* Bit 16: Device mode enable */ + /* Bits 17-31: Reserved */ + +/************************************************************************************ + * Public Types + ************************************************************************************/ + +/************************************************************************************ + * Public Data + ************************************************************************************/ + +/************************************************************************************ + * Public Functions + ************************************************************************************/ + +#endif /* __ARCH_ARM_SRC_LPC54XX_CHIP_LPC54_USB0_OHCI_H */ diff --git a/arch/arm/src/lpc54xx/lpc54_usb0_ohci.c b/arch/arm/src/lpc54xx/lpc54_usb0_ohci.c new file mode 100644 index 0000000000..6942426e64 --- /dev/null +++ b/arch/arm/src/lpc54xx/lpc54_usb0_ohci.c @@ -0,0 +1,3995 @@ +/**************************************************************************** + * arch/arm/src/lpc54xx/lpc54_usb0_ohci.c + * + * Copyright (C) 2019 Gregory Nutt. All rights reserved. + * Authors: Gregory Nutt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include /* May redefine GPIO settings */ + +#include "up_arch.h" +#include "up_internal.h" + +#include "chip.h" +#include "chip/lpc54_usb.h" +#include "chip/lpc54_syscon.h" +#include "lpc54_gpio.h" +#include "lpc54_ohciram.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Configuration ***************************************************************/ + +/* All I/O buffers must lie in AHB SRAM because of the OHCI DMA. It might be + * okay if no I/O buffers are used *IF* the application can guarantee that all + * end-user I/O buffers reside in AHB SRAM. + */ + +#if LPC54_IOBUFFERS < 1 +# warning "No IO buffers allocated" +#endif + +#ifndef CONFIG_LPC54_OHCI_NPREALLOC +# define CONFIG_LPC54_OHCI_NPREALLOC 8 +#endif + +#ifndef CONFIG_DEBUG_USB_INFO +# undef CONFIG_LPC54_OHCI_REGDEBUG +#endif + +/* OHCI Setup ******************************************************************/ + +/* Frame Interval / Periodic Start */ + +#define BITS_PER_FRAME 12000 +#define FI (BITS_PER_FRAME-1) +#define FSMPS ((6 * (FI - 210)) / 7) +#define DEFAULT_FMINTERVAL ((FSMPS << OHCI_FMINT_FSMPS_SHIFT) | FI) +#define DEFAULT_PERSTART (((9 * BITS_PER_FRAME) / 10) - 1) + +/* CLKCTRL enable bits */ + +#define LPC54_CLKCTRL_ENABLES (USBOTG_CLK_HOSTCLK|USBOTG_CLK_PORTSELCLK|USBOTG_CLK_AHBCLK) + +/* Interrupt enable bits */ + +#ifdef CONFIG_DEBUG_USB +# define LPC54_DEBUG_INTS (OHCI_INT_SO|OHCI_INT_RD|OHCI_INT_UE|OHCI_INT_OC) +#else +# define LPC54_DEBUG_INTS 0 +#endif + +#define LPC54_NORMAL_INTS (OHCI_INT_WDH|OHCI_INT_RHSC) +#define LPC54_ALL_INTS (LPC54_NORMAL_INTS|LPC54_DEBUG_INTS) + +/* Dump GPIO registers */ + +#ifdef CONFIG_LPC54_OHCI_REGDEBUG +# define usbhost_dumpgpio() \ + do { \ + lpc54_dumpgpio(GPIO_USB_DP, "D+ P0.29; D- P0.30"); \ + lpc54_dumpgpio(GPIO_USB_UPLED, "LED P1:18; PPWR P1:19 PWRD P1:22 PVRCR P1:27"); \ + } while (0); +#else +# define usbhost_dumpgpio() +#endif + +/* Numbers and Sizes of Things *************************************************/ + +/* Fixed size of the OHCI control area */ + +#define LPC54_HCCA_SIZE 256 + +/* Fixed endpoint descriptor size. The actual size required by the hardware is only + * 16 bytes, however, we set aside an additional 16 bytes for for internal use by + * the OHCI host driver. 16-bytes is set aside because the EDs must still be + * aligned to 16-byte boundaries. + */ + +#define LPC54_ED_SIZE 32 + +/* Configurable number of user endpoint descriptors (EDs). This number excludes + * the control endpoint that is always allocated. + */ + +#ifndef CONFIG_LP17_OHCI_NEDS +# define CONFIG_LP17_OHCI_NEDS 2 +#endif + +/* Derived size of user endpoint descriptor (ED) memory. */ + +#define LPC54_EDFREE_SIZE (CONFIG_LP17_OHCI_NEDS * LPC54_ED_SIZE) + +/* Fixed transfer descriptor size. The actual size required by the hardware is only + * 16 bytes, however, we set aside an additional 16 bytes for for internal use by + * the OHCI host driver. 16-bytes is set aside because the TDs must still be + * aligned to 16-byte boundaries. + */ + +#define LPC54_TD_SIZE 32 + +/* Configurable number of user transfer descriptors (TDs). */ + +#ifndef CONFIG_LP17_OHCI_NTDS +# define CONFIG_LP17_OHCI_NTDS 3 +#endif + +#if CONFIG_LP17_OHCI_NTDS < 2 +# error "Insufficent TDs" +#endif + +/* Derived size of user transfer descriptor (TD) memory. */ + +#define LPC54_TDFREE_SIZE (CONFIG_LP17_OHCI_NTDS * LPC54_TD_SIZE) + +/* Configurable number of request/descriptor buffers (TDBUFFER) */ + +#ifndef CONFIG_LPC54_OHCI_TDBUFFERS +# define CONFIG_LPC54_OHCI_TDBUFFERS 2 +#endif + +#if CONFIG_LPC54_OHCI_TDBUFFERS < 2 +# error "At least two TD buffers are required" +#endif + +/* Configurable size of a TD buffer */ + +#if CONFIG_LPC54_OHCI_TDBUFFERS > 0 && !defined(CONFIG_LPC54_OHCI_TDBUFSIZE) +# define CONFIG_LPC54_OHCI_TDBUFSIZE 128 +#endif + +#if (CONFIG_LPC54_OHCI_TDBUFSIZE & 3) != 0 +# error "TD buffer size must be an even number of 32-bit words" +#endif + +#define LPC54_TBFREE_SIZE (CONFIG_LPC54_OHCI_TDBUFFERS * CONFIG_LPC54_OHCI_TDBUFSIZE) + +/* Configurable size of an IO buffer. The number of IO buffers will be determined + * by what is left at the end of the BANK1 memory setup aside of OHCI RAM. + */ + +#ifndef CONFIG_LPC54_OHCI_IOBUFSIZE +# define CONFIG_LPC54_OHCI_IOBUFSIZE 512 +#endif + +#if (CONFIG_LPC54_OHCI_IOBUFSIZE & 3) != 0 +# error "IO buffer size must be an even number of 32-bit words" +#endif + +/* USB Host Memory *************************************************************/ + +/* Required Alignment */ + +#define LPC54_ALIGN_SIZE 16 + +/* How many pre-allocated I/O buffers */ + +#if CONFIG_LPC54_OHCI_IOBUFSIZE > 0 && CONFIG_LPC54_OHCI_NIOBUFFERS > 0 +# define LPC54_IOBUFFERS CONFIG_LPC54_OHCI_NIOBUFFERS +# define LPC54_IOBUF_ALLOC \ + (CONFIG_LPC54_OHCI_IOBUFSIZE * CONFIG_LPC54_OHCI_NIOBUFFERS) +#else +# define LPC54_IOBUFFERS 0 +# define LPC54_IOBUF_ALLOC 0 +#endif + +/* Helper definitions */ + +#define HCCA ((struct ohci_hcca_s *)g_hcca) +#define TDTAIL ((struct lpc54_gtd_s *)g_tdtail_alloc) +#define EDCTRL ((struct lpc54_ed_s *)g_edctrl_alloc) + +/* Periodic intervals 2, 4, 8, 16,and 32 supported */ + +#define MIN_PERINTERVAL 2 +#define MAX_PERINTERVAL 32 + +/* Descriptors *****************************************************************/ + +/* TD delay interrupt value */ + +#define TD_DELAY(n) (uint32_t)((n) << GTD_STATUS_DI_SHIFT) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* This structure retains the state of the USB host controller */ + +struct lpc54_usbhost_s +{ + /* Common device fields. This must be the first thing defined in the + * structure so that it is possible to simply cast from struct usbhost_s + * to structlpc54_usbhost_s. + */ + + struct usbhost_driver_s drvr; + + /* This is the hub port description understood by class drivers */ + + struct usbhost_roothubport_s rhport; + + /* Driver status */ + + volatile bool change; /* Connection change */ + volatile bool connected; /* Connected to device */ + volatile bool pscwait; /* TRUE: Thread is waiting for a port status change */ + +#ifndef CONFIG_OHCI_INT_DISABLE + uint8_t ininterval; /* Minimum periodic IN EP polling interval: 2, 4, 6, 16, or 32 */ + uint8_t outinterval; /* Minimum periodic IN EP polling interval: 2, 4, 6, 16, or 32 */ +#endif + + sem_t exclsem; /* Support mutually exclusive access */ + sem_t pscsem; /* Semaphore to wait Writeback Done Head event */ + +#ifdef CONFIG_OHCI_HUB + /* Used to pass external hub port events */ + + volatile struct usbhost_hubport_s *hport; +#endif +}; + +/* This structure describes one asynchronous transfer */ + +struct lpc54_xfrinfo_s +{ + volatile bool wdhwait; /* Thread is waiting for WDH interrupt */ + volatile uint8_t tdstatus; /* TD control status bits from last Writeback Done Head event */ + uint8_t *buffer; /* Transfer buffer start */ + uint16_t buflen; /* Buffer length */ + uint16_t xfrd; /* Number of bytes transferred */ + +#ifdef CONFIG_OHCI_ASYNCH +#if LPC54_IOBUFFERS > 0 + /* Remember the allocated DMA buffer address so that it can be freed when + * the transfer completes. + */ + + uint8_t *alloc; /* Allocated buffer */ +#endif + + /* Retain the callback information for the asynchronous transfer + * completion. + */ + + usbhost_asynch_t callback; /* Transfer complete callback */ + void *arg; /* Argument that accompanies the callback */ +#endif +}; + +/* The OCHI expects the size of an endpoint descriptor to be 16 bytes. + * However, the size allocated for an endpoint descriptor is 32 bytes in + * lpc54_ohciram.h. This extra 16-bytes is used by the OHCI host driver in + * order to maintain additional endpoint-specific data. + */ + +struct lpc54_ed_s +{ + /* Hardware specific fields */ + + struct ohci_ed_s hw; /* 0-15 */ + + /* Software specific fields */ + + uint8_t xfrtype; /* 16: Transfer type. See SB_EP_ATTR_XFER_* in usb.h */ + uint8_t interval; /* 17: Periodic EP polling interval: 2, 4, 6, 16, or 32 */ + sem_t wdhsem; /* 18: Semaphore used to wait for Writeback Done Head event */ + /* Unused bytes may follow, depending on the size of sem_t */ + /* Pointer to structure that manages asynchronous transfers on this pipe */ + + struct lpc54_xfrinfo_s *xfrinfo; +}; + +/* The OCHI expects the size of an transfer descriptor to be 16 bytes. + * However, the size allocated for an endpoint descriptor is 32 bytes in + * lpc54_ohciram.h. This extra 16-bytes is used by the OHCI host driver in + * order to maintain additional endpoint-specific data. + */ + +struct lpc54_gtd_s +{ + /* Hardware specific fields */ + + struct ohci_gtd_s hw; + + /* Software specific fields */ + + struct lpc54_ed_s *ed; /* Pointer to parent ED */ + uint8_t pad[12]; +}; + +/* The following is used to manage lists of free EDs, TDs, and TD buffers */ + +struct lpc54_list_s +{ + struct lpc54_list_s *flink; /* Link to next buffer in the list */ + /* Variable length buffer data follows */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Register operations ********************************************************/ + +#ifdef CONFIG_LPC54_OHCI_REGDEBUG +static void lpc54_printreg(uint32_t addr, uint32_t val, bool iswrite); +static void lpc54_checkreg(uint32_t addr, uint32_t val, bool iswrite); +static uint32_t lpc54_getreg(uint32_t addr); +static void lpc54_putreg(uint32_t val, uint32_t addr); +#else +# define lpc54_getreg(addr) getreg32(addr) +# define lpc54_putreg(val,addr) putreg32(val,addr) +#endif + +/* Semaphores ******************************************************************/ + +static void lpc54_takesem(sem_t *sem); +#define lpc54_givesem(s) nxsem_post(s); + +/* Byte stream access helper functions *****************************************/ + +static inline uint16_t lpc54_getle16(const uint8_t *val); +#if 0 /* Not used */ +static void lpc54_putle16(uint8_t *dest, uint16_t val); +#endif + +/* OHCI memory pool helper functions *******************************************/ + +static inline void lpc54_edfree(struct lpc54_ed_s *ed); +static struct lpc54_gtd_s *lpc54_tdalloc(void); +static void lpc54_tdfree(struct lpc54_gtd_s *buffer); +static uint8_t *lpc54_tballoc(void); +static void lpc54_tbfree(uint8_t *buffer); +#if LPC54_IOBUFFERS > 0 +static uint8_t *lpc54_allocio(void); +static void lpc54_freeio(uint8_t *buffer); +#endif +static struct lpc54_xfrinfo_s *lpc54_alloc_xfrinfo(void); +static void lpc54_free_xfrinfo(struct lpc54_xfrinfo_s *xfrinfo); + +/* ED list helper functions ****************************************************/ + +static inline int lpc54_addctrled(struct lpc54_usbhost_s *priv, + struct lpc54_ed_s *ed); +static inline int lpc54_remctrled(struct lpc54_usbhost_s *priv, + struct lpc54_ed_s *ed); + +static inline int lpc54_addbulked(struct lpc54_usbhost_s *priv, + struct lpc54_ed_s *ed); +static inline int lpc54_rembulked(struct lpc54_usbhost_s *priv, + struct lpc54_ed_s *ed); + +#if !defined(CONFIG_OHCI_INT_DISABLE) || !defined(CONFIG_OHCI_ISOC_DISABLE) +static unsigned int lpc54_getinterval(uint8_t interval); +static void lpc54_setinttab(uint32_t value, unsigned int interval, unsigned int offset); +#endif + +static inline int lpc54_addinted(struct lpc54_usbhost_s *priv, + const struct usbhost_epdesc_s *epdesc, + struct lpc54_ed_s *ed); +static inline int lpc54_reminted(struct lpc54_usbhost_s *priv, + struct lpc54_ed_s *ed); + +static inline int lpc54_addisoced(struct lpc54_usbhost_s *priv, + const struct usbhost_epdesc_s *epdesc, + struct lpc54_ed_s *ed); +static inline int lpc54_remisoced(struct lpc54_usbhost_s *priv, + struct lpc54_ed_s *ed); + +/* Descriptor helper functions *************************************************/ + +static int lpc54_enqueuetd(struct lpc54_usbhost_s *priv, + struct lpc54_ed_s *ed, uint32_t dirpid, + uint32_t toggle, volatile uint8_t *buffer, + size_t buflen); +static int lpc54_ctrltd(struct lpc54_usbhost_s *priv, struct lpc54_ed_s *ed, + uint32_t dirpid, uint8_t *buffer, size_t buflen); + +/* Interrupt handling **********************************************************/ + +static int lpc54_usbinterrupt(int irq, void *context, FAR void *arg); + +/* USB host controller operations **********************************************/ + +static int lpc54_wait(struct usbhost_connection_s *conn, + struct usbhost_hubport_s **hport); +static int lpc54_rh_enumerate(struct usbhost_connection_s *conn, + struct usbhost_hubport_s *hport); +static int lpc54_enumerate(struct usbhost_connection_s *conn, + struct usbhost_hubport_s *hport); + +static int lpc54_ep0configure(struct usbhost_driver_s *drvr, + usbhost_ep_t ep0, uint8_t funcaddr, uint8_t speed, + uint16_t maxpacketsize); +static int lpc54_epalloc(struct usbhost_driver_s *drvr, + const struct usbhost_epdesc_s *epdesc, usbhost_ep_t *ep); +static int lpc54_epfree(struct usbhost_driver_s *drvr, usbhost_ep_t ep); +static int lpc54_alloc(struct usbhost_driver_s *drvr, + uint8_t **buffer, size_t *maxlen); +static int lpc54_free(struct usbhost_driver_s *drvr, uint8_t *buffer); +static int lpc54_ioalloc(struct usbhost_driver_s *drvr, + uint8_t **buffer, size_t buflen); +static int lpc54_iofree(struct usbhost_driver_s *drvr, uint8_t *buffer); +static int lpc54_ctrlin(struct usbhost_driver_s *drvr, usbhost_ep_t ep0, + const struct usb_ctrlreq_s *req, + uint8_t *buffer); +static int lpc54_ctrlout(struct usbhost_driver_s *drvr, usbhost_ep_t ep0, + const struct usb_ctrlreq_s *req, + const uint8_t *buffer); +static int lpc54_transfer_common(struct lpc54_usbhost_s *priv, + struct lpc54_ed_s *ed, uint8_t *buffer, + size_t buflen); +#if LPC54_IOBUFFERS > 0 +static int lpc54_dma_alloc(struct lpc54_usbhost_s *priv, + struct lpc54_ed_s *ed, uint8_t *userbuffer, + size_t buflen, uint8_t **alloc); +static void lpc54_dma_free(struct lpc54_usbhost_s *priv, + struct lpc54_ed_s *ed, uint8_t *userbuffer, + size_t buflen, uint8_t *alloc); +#endif +static ssize_t lpc54_transfer(struct usbhost_driver_s *drvr, usbhost_ep_t ep, + uint8_t *buffer, size_t buflen); +#ifdef CONFIG_OHCI_ASYNCH +static void lpc54_asynch_completion(struct lpc54_usbhost_s *priv, + struct lpc54_ed_s *ed); +static int lpc54_asynch(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep, + FAR uint8_t *buffer, size_t buflen, + usbhost_asynch_t callback, FAR void *arg); +#endif +static int lpc54_cancel(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep); +#ifdef CONFIG_OHCI_HUB +static int lpc54_connect(FAR struct usbhost_driver_s *drvr, + FAR struct usbhost_hubport_s *hport, + bool connected); +#endif +static void lpc54_disconnect(struct usbhost_driver_s *drvr, + struct usbhost_hubport_s *hport); + +/* Initialization **************************************************************/ + +static inline void lpc54_ep0init(struct lpc54_usbhost_s *priv); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* In this driver implementation, support is provided for only a single a single + * USB device. All status information can be simply retained in a single global + * instance. + */ + +static struct lpc54_usbhost_s g_usbhost; + +/* This is the connection/enumeration interface */ + +static struct usbhost_connection_s g_usbconn = +{ + .wait = lpc54_wait, + .enumerate = lpc54_enumerate, +}; + +/* Aligned static memory allocations */ + +static uint8_t g_hcca[LPC54_HCCA_SIZE] \ + __attribute__ ((aligned(LPC54_ALIGN_SIZE))); +static uint8_t g_tdtail_alloc[LPC54_TD_SIZE] \ + __attribute__ ((aligned(LPC54_ALIGN_SIZE))); +static uint8_t g_edctrl_alloc[LPC54_ED_SIZE] \ + __attribute__ ((aligned(LPC54_ALIGN_SIZE))); +static uint8_t g_edfree_alloc[LPC54_EDFREE_SIZE] \ + __attribute__ ((aligned(LPC54_ALIGN_SIZE))); +static uint8_t g_tdfree_alloc[LPC54_TDFREE_SIZE] \ + __attribute__ ((aligned(LPC54_ALIGN_SIZE))); +static uint8_t g_tbfree_alloc[LPC54_TBFREE_SIZE] \ + __attribute__ ((aligned(LPC54_ALIGN_SIZE))); + +#if LPC54_IOBUFFERS > 0 +static uint8_t g_iobuffers[LPC54_IOBUF_ALLOC] \ + __attribute__ ((aligned(LPC54_ALIGN_SIZE))); +#endif + +/* This is a free list of EDs and TD buffers */ + +static struct lpc54_list_s *g_edfree; /* List of unused EDs */ +static struct lpc54_list_s *g_tdfree; /* List of unused TDs */ +static struct lpc54_list_s *g_tbfree; /* List of unused transfer buffers */ +#if LPC54_IOBUFFERS > 0 +static struct lpc54_list_s *g_iofree; /* List of unused I/O buffers */ +#endif + +/* Pool and freelist of transfer structures */ + +static struct lpc54_list_s *g_xfrfree; +static struct lpc54_xfrinfo_s g_xfrbuffers[CONFIG_LPC54_OHCI_NPREALLOC]; + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: lpc54_printreg + * + * Description: + * Print the contents of an LPC54xx register operation + * + ****************************************************************************/ + +#ifdef CONFIG_LPC54_OHCI_REGDEBUG +static void lpc54_printreg(uint32_t addr, uint32_t val, bool iswrite) +{ + uinfo("%08x%s%08x\n", addr, iswrite ? "<-" : "->", val); +} +#endif + +/**************************************************************************** + * Name: lpc54_checkreg + * + * Description: + * Get the contents of an LPC54xx register + * + ****************************************************************************/ + +#ifdef CONFIG_LPC54_OHCI_REGDEBUG +static void lpc54_checkreg(uint32_t addr, uint32_t val, bool iswrite) +{ + static uint32_t prevaddr = 0; + static uint32_t preval = 0; + static uint32_t count = 0; + static bool prevwrite = false; + + /* Is this the same value that we read from/wrote to the same register last time? + * Are we polling the register? If so, suppress the output. + */ + + if (addr == prevaddr && val == preval && prevwrite == iswrite) + { + /* Yes.. Just increment the count */ + + count++; + } + else + { + /* No this is a new address or value or operation. Were there any + * duplicate accesses before this one? + */ + + if (count > 0) + { + /* Yes.. Just one? */ + + if (count == 1) + { + /* Yes.. Just one */ + + lpc54_printreg(prevaddr, preval, prevwrite); + } + else + { + /* No.. More than one. */ + + uinfo("[repeats %d more times]\n", count); + } + } + + /* Save the new address, value, count, and operation for next time */ + + prevaddr = addr; + preval = val; + count = 0; + prevwrite = iswrite; + + /* Show the new regisgter access */ + + lpc54_printreg(addr, val, iswrite); + } +} +#endif + +/**************************************************************************** + * Name: lpc54_getreg + * + * Description: + * Get the contents of an LPC54xx register + * + ****************************************************************************/ + +#ifdef CONFIG_LPC54_OHCI_REGDEBUG +static uint32_t lpc54_getreg(uint32_t addr) +{ + /* Read the value from the register */ + + uint32_t val = getreg32(addr); + + /* Check if we need to print this value */ + + lpc54_checkreg(addr, val, false); + return val; +} +#endif + +/**************************************************************************** + * Name: lpc54_putreg + * + * Description: + * Set the contents of an LPC54xx register to a value + * + ****************************************************************************/ + +#ifdef CONFIG_LPC54_OHCI_REGDEBUG +static void lpc54_putreg(uint32_t val, uint32_t addr) +{ + /* Check if we need to print this value */ + + lpc54_checkreg(addr, val, true); + + /* Write the value */ + + putreg32(val, addr); +} +#endif + +/**************************************************************************** + * Name: lpc54_takesem + * + * Description: + * This is just a wrapper to handle the annoying behavior of semaphore + * waits that return due to the receipt of a signal. + * + ****************************************************************************/ + +static void lpc54_takesem(sem_t *sem) +{ + int ret; + + do + { + /* Take the semaphore (perhaps waiting) */ + + ret = nxsem_wait(sem); + + /* The only case that an error should occur here is if the wait was + * awakened by a signal. + */ + + DEBUGASSERT(ret == OK || ret == -EINTR); + } + while (ret == -EINTR); +} + +/**************************************************************************** + * Name: lpc54_getle16 + * + * Description: + * Get a (possibly unaligned) 16-bit little endian value. + * + ****************************************************************************/ + +static inline uint16_t lpc54_getle16(const uint8_t *val) +{ + return (uint16_t)val[1] << 8 | (uint16_t)val[0]; +} + +/**************************************************************************** + * Name: lpc54_putle16 + * + * Description: + * Put a (possibly unaligned) 16-bit little endian value. + * + ****************************************************************************/ + +#if 0 /* Not used */ +static void lpc54_putle16(uint8_t *dest, uint16_t val) +{ + dest[0] = val & 0xff; /* Little endian means LS byte first in byte stream */ + dest[1] = val >> 8; +} +#endif + +/**************************************************************************** + * Name: lpc54_edfree + * + * Description: + * Return an endpoint descriptor to the free list + * + ****************************************************************************/ + +static inline void lpc54_edfree(struct lpc54_ed_s *ed) +{ + struct lpc54_list_s *entry = (struct lpc54_list_s *)ed; + + /* Put the ED back into the free list */ + + entry->flink = g_edfree; + g_edfree = entry; +} + +/**************************************************************************** + * Name: lpc54_tdalloc + * + * Description: + * Allocate an transfer descriptor from the free list + * + * Assumptions: + * - Never called from an interrupt handler. + * - Protected from conconcurrent access to the TD pool by the interrupt + * handler + * - Protection from re-entrance must be assured by the caller + * + ****************************************************************************/ + +static struct lpc54_gtd_s *lpc54_tdalloc(void) +{ + struct lpc54_gtd_s *ret; + irqstate_t flags; + + /* Disable interrupts momentarily so that lpc54_tdfree is not called from the + * interrupt handler. + */ + + flags = enter_critical_section(); + ret = (struct lpc54_gtd_s *)g_tdfree; + if (ret) + { + g_tdfree = ((struct lpc54_list_s *)ret)->flink; + } + + leave_critical_section(flags); + return ret; +} + +/**************************************************************************** + * Name: lpc54_tdfree + * + * Description: + * Return an transfer descriptor to the free list + * + * Assumptions: + * - Only called from the WDH interrupt handler (and during initialization). + * - Interrupts are disabled in any case. + * + ****************************************************************************/ + +static void lpc54_tdfree(struct lpc54_gtd_s *td) +{ + struct lpc54_list_s *tdfree = (struct lpc54_list_s *)td; + + /* This should not happen but just to be safe, don't free the common, pre- + * allocated tail TD. + */ + + if (tdfree != NULL && td != TDTAIL) + { + tdfree->flink = g_tdfree; + g_tdfree = tdfree; + } +} + +/**************************************************************************** + * Name: lpc54_tballoc + * + * Description: + * Allocate an request/descriptor transfer buffer from the free list + * + * Assumptions: + * - Never called from an interrupt handler. + * - Protection from re-entrance must be assured by the caller + * + ****************************************************************************/ + +static uint8_t *lpc54_tballoc(void) +{ + uint8_t *ret = (uint8_t *)g_tbfree; + if (ret) + { + g_tbfree = ((struct lpc54_list_s *)ret)->flink; + } + return ret; +} + +/**************************************************************************** + * Name: lpc54_tbfree + * + * Description: + * Return an request/descriptor transfer buffer to the free list + * + ****************************************************************************/ + +static void lpc54_tbfree(uint8_t *buffer) +{ + struct lpc54_list_s *tbfree = (struct lpc54_list_s *)buffer; + + if (tbfree) + { + tbfree->flink = g_tbfree; + g_tbfree = tbfree; + } +} + +/**************************************************************************** + * Name: lpc54_allocio + * + * Description: + * Allocate an IO buffer from the free list + * + * Assumptions: + * - Never called from an interrupt handler. + * - Protection from re-entrance must be assured by the caller + * + ****************************************************************************/ + +#if LPC54_IOBUFFERS > 0 +static uint8_t *lpc54_allocio(void) +{ + uint8_t *ret; + irqstate_t flags; + + /* lpc54_freeio() may be called from the interrupt level */ + + flags = enter_critical_section(); + ret = (uint8_t *)g_iofree; + if (ret) + { + g_iofree = ((struct lpc54_list_s *)ret)->flink; + } + + leave_critical_section(flags); + return ret; +} +#endif + +/**************************************************************************** + * Name: lpc54_freeio + * + * Description: + * Return an TD buffer to the free list + * + ****************************************************************************/ + +#if LPC54_IOBUFFERS > 0 +static void lpc54_freeio(uint8_t *buffer) +{ + struct lpc54_list_s *iofree; + irqstate_t flags; + + /* Could be called from the interrupt level */ + + flags = enter_critical_section(); + iofree = (struct lpc54_list_s *)buffer; + iofree->flink = g_iofree; + g_iofree = iofree; + leave_critical_section(flags); +} +#endif + +/**************************************************************************** + * Name: lpc54_alloc_xfrinfo + * + * Description: + * Allocate an asynchronous data structure from the free list + * + * Assumptions: + * - Never called from an interrupt handler. + * - Protection from re-entrance must be assured by the caller + * + ****************************************************************************/ + +static struct lpc54_xfrinfo_s *lpc54_alloc_xfrinfo(void) +{ + struct lpc54_xfrinfo_s *ret; + irqstate_t flags; + + /* lpc54_free_xfrinfo() may be called from the interrupt level */ + + flags = enter_critical_section(); + ret = (struct lpc54_xfrinfo_s *)g_xfrfree; + if (ret) + { + g_xfrfree = ((struct lpc54_list_s *)ret)->flink; + } + + leave_critical_section(flags); + return ret; +} + +/**************************************************************************** + * Name: lpc54_freeio + * + * Description: + * Return an TD buffer to the free list + * + ****************************************************************************/ + +static void lpc54_free_xfrinfo(struct lpc54_xfrinfo_s *xfrinfo) +{ + struct lpc54_list_s *node; + irqstate_t flags; + + /* Could be called from the interrupt level */ + + flags = enter_critical_section(); + node = (struct lpc54_list_s *)xfrinfo; + node->flink = g_xfrfree; + g_xfrfree = node; + leave_critical_section(flags); +} + +/**************************************************************************** + * Name: lpc54_addctrled + * + * Description: + * Helper function to add an ED to the control list. + * + ****************************************************************************/ + +static inline int lpc54_addctrled(struct lpc54_usbhost_s *priv, + struct lpc54_ed_s *ed) +{ + irqstate_t flags; + uint32_t regval; + + /* Disable control list processing while we modify the list */ + + flags = enter_critical_section(); + regval = lpc54_getreg(LPC54_OHCI_CTRL); + regval &= ~OHCI_CTRL_CLE; + lpc54_putreg(regval, LPC54_OHCI_CTRL); + + /* Add the new bulk ED to the head of the bulk list */ + + ed->hw.nexted = lpc54_getreg(LPC54_OHCI_CTRLHEADED); + lpc54_putreg((uint32_t)ed, LPC54_OHCI_CTRLHEADED); + + /* Re-enable control list processing. */ + + lpc54_putreg(0, LPC54_OHCI_CTRLED); + + regval = lpc54_getreg(LPC54_OHCI_CTRL); + regval |= OHCI_CTRL_CLE; + lpc54_putreg(regval, LPC54_OHCI_CTRL); + + leave_critical_section(flags); + return OK; +} + +/**************************************************************************** + * Name: lpc54_remctrled + * + * Description: + * Helper function remove an ED from the control list. + * + ****************************************************************************/ + +static inline int lpc54_remctrled(struct lpc54_usbhost_s *priv, + struct lpc54_ed_s *ed) +{ + struct lpc54_ed_s *curr; + struct lpc54_ed_s *prev; + struct lpc54_ed_s *head; + irqstate_t flags; + uint32_t regval; + + /* Disable control list processing while we modify the list */ + + flags = enter_critical_section(); + regval = lpc54_getreg(LPC54_OHCI_CTRL); + regval &= ~OHCI_CTRL_CLE; + lpc54_putreg(regval, LPC54_OHCI_CTRL); + + /* Find the ED in the control list. */ + + head = (struct lpc54_ed_s *)lpc54_getreg(LPC54_OHCI_CTRLHEADED); + for (prev = NULL, curr = head; + curr && curr != ed; + prev = curr, curr = (struct lpc54_ed_s *)curr->hw.nexted); + + /* It would be a bug if we do not find the ED in the control list. */ + + DEBUGASSERT(curr != NULL); + + /* Remove the ED from the control list */ + + if (curr != NULL) + { + /* Is this ED the first on in the control list? */ + + if (prev == NULL) + { + /* Yes... set the head of the control list to skip over this ED */ + + head = (struct lpc54_ed_s *)ed->hw.nexted; + lpc54_putreg((uint32_t)head, LPC54_OHCI_CTRLHEADED); + } + else + { + /* No.. set the forward link of the previous ED in the list + * skip over this ED. + */ + + prev->hw.nexted = ed->hw.nexted; + } + + /* Just in case the hardware happens to be processing this ed now... + * it should go back to the control list head. + */ + + ed->hw.nexted = 0; + } + + /* Re-enable control list processing if the control list is still non-empty + * after removing the ED node. + */ + + lpc54_putreg(0, LPC54_OHCI_CTRLED); + if (lpc54_getreg(LPC54_OHCI_CTRLHEADED) != 0) + { + /* If the control list is now empty, then disable it */ + + regval = lpc54_getreg(LPC54_OHCI_CTRL); + regval &= ~OHCI_CTRL_CLE; + lpc54_putreg(regval, LPC54_OHCI_CTRL); + } + + leave_critical_section(flags); + return OK; +} + +/**************************************************************************** + * Name: lpc54_addbulked + * + * Description: + * Helper function to add an ED to the bulk list. + * + ****************************************************************************/ + +static inline int lpc54_addbulked(struct lpc54_usbhost_s *priv, + struct lpc54_ed_s *ed) +{ +#ifndef CONFIG_OHCI_BULK_DISABLE + irqstate_t flags; + uint32_t regval; + + /* Disable bulk list processing while we modify the list */ + + flags = enter_critical_section(); + regval = lpc54_getreg(LPC54_OHCI_CTRL); + regval &= ~OHCI_CTRL_BLE; + lpc54_putreg(regval, LPC54_OHCI_CTRL); + + /* Add the new bulk ED to the head of the bulk list */ + + ed->hw.nexted = lpc54_getreg(LPC54_OHCI_BULKHEADED); + lpc54_putreg((uint32_t)ed, LPC54_OHCI_BULKHEADED); + + /* Re-enable bulk list processing. */ + + lpc54_putreg(0, LPC54_OHCI_BULKED); + + regval = lpc54_getreg(LPC54_OHCI_CTRL); + regval |= OHCI_CTRL_BLE; + lpc54_putreg(regval, LPC54_OHCI_CTRL); + + leave_critical_section(flags); + return OK; +#else + return -ENOSYS; +#endif +} + +/**************************************************************************** + * Name: lpc54_rembulked + * + * Description: + * Helper function remove an ED from the bulk list. + * + ****************************************************************************/ + +static inline int lpc54_rembulked(struct lpc54_usbhost_s *priv, + struct lpc54_ed_s *ed) +{ +#ifndef CONFIG_OHCI_BULK_DISABLE + struct lpc54_ed_s *curr; + struct lpc54_ed_s *prev; + struct lpc54_ed_s *head; + irqstate_t flags; + uint32_t regval; + + /* Disable bulk list processing while we modify the list */ + + flags = enter_critical_section(); + regval = lpc54_getreg(LPC54_OHCI_CTRL); + regval &= ~OHCI_CTRL_BLE; + lpc54_putreg(regval, LPC54_OHCI_CTRL); + + /* Find the ED in the bulk list. */ + + head = (struct lpc54_ed_s *)lpc54_getreg(LPC54_OHCI_BULKHEADED); + for (prev = NULL, curr = head; + curr && curr != ed; + prev = curr, curr = (struct lpc54_ed_s *)curr->hw.nexted); + + /* It would be a bug if we do not find the ED in the bulk list. */ + + DEBUGASSERT(curr != NULL); + + /* Remove the ED from the bulk list */ + + if (curr != NULL) + { + /* Is this ED the first on in the bulk list? */ + + if (prev == NULL) + { + /* Yes... set the head of the bulk list to skip over this ED */ + + head = (struct lpc54_ed_s *)ed->hw.nexted; + lpc54_putreg((uint32_t)head, LPC54_OHCI_BULKHEADED); + } + else + { + /* No.. set the forward link of the previous ED in the list + * skip over this ED. + */ + + prev->hw.nexted = ed->hw.nexted; + } + } + + /* Re-enable bulk list processing if the bulk list is still non-empty + * after removing the ED node. + */ + + lpc54_putreg(0, LPC54_OHCI_BULKED); + if (lpc54_getreg(LPC54_OHCI_BULKHEADED) != 0) + { + /* If the bulk list is now empty, then disable it */ + + regval = lpc54_getreg(LPC54_OHCI_CTRL); + regval |= OHCI_CTRL_BLE; + lpc54_putreg(regval, LPC54_OHCI_CTRL); + } + + leave_critical_section(flags); + return OK; +#else + return -ENOSYS; +#endif +} + +/**************************************************************************** + * Name: lpc54_getinterval + * + * Description: + * Convert the endpoint polling interval into a HCCA table increment + * + ****************************************************************************/ + +#if !defined(CONFIG_OHCI_INT_DISABLE) || !defined(CONFIG_OHCI_ISOC_DISABLE) +static unsigned int lpc54_getinterval(uint8_t interval) +{ + /* The bInterval field of the endpoint descriptor contains the polling interval + * for interrupt and isochronous endpoints. For other types of endpoint, this + * value should be ignored. bInterval is provided in units of 1MS frames. + */ + + if (interval < 3) + { + return 2; + } + else if (interval < 7) + { + return 4; + } + else if (interval < 15) + { + return 8; + } + else if (interval < 31) + { + return 16; + } + else + { + return 32; + } +} +#endif + +/**************************************************************************** + * Name: lpc54_setinttab + * + * Description: + * Set the interrupt table to the selected value using the provided interval + * and offset. + * + ****************************************************************************/ + +#if !defined(CONFIG_OHCI_INT_DISABLE) || !defined(CONFIG_OHCI_ISOC_DISABLE) +static void lpc54_setinttab(uint32_t value, unsigned int interval, unsigned int offset) +{ + unsigned int i; + for (i = offset; i < HCCA_INTTBL_WSIZE; i += interval) + { + HCCA->inttbl[i] = value; + } +} +#endif + +/**************************************************************************** + * Name: lpc54_addinted + * + * Description: + * Helper function to add an ED to the HCCA interrupt table. + * + * To avoid reshuffling the table so much and to keep life simple in general, + * the following rules are applied: + * + * 1. IN EDs get the even entries, OUT EDs get the odd entries. + * 2. Add IN/OUT EDs are scheduled together at the minimum interval of all + * IN/OUT EDs. + * + * This has the following consequences: + * + * 1. The minimum support polling rate is 2MS, and + * 2. Some devices may get polled at a much higher rate than they request. + * + ****************************************************************************/ + +static inline int lpc54_addinted(struct lpc54_usbhost_s *priv, + const struct usbhost_epdesc_s *epdesc, + struct lpc54_ed_s *ed) +{ +#ifndef CONFIG_OHCI_INT_DISABLE + unsigned int interval; + unsigned int offset; + uint32_t head; + uint32_t regval; + + /* Disable periodic list processing. Does this take effect immediately? Or + * at the next SOF... need to check. + */ + + regval = lpc54_getreg(LPC54_OHCI_CTRL); + regval &= ~OHCI_CTRL_PLE; + lpc54_putreg(regval, LPC54_OHCI_CTRL); + + /* Get the quantized interval value associated with this ED and save it + * in the ED. + */ + + interval = lpc54_getinterval(epdesc->interval); + ed->interval = interval; + uinfo("interval: %d->%d\n", epdesc->interval, interval); + + /* Get the offset associated with the ED direction. IN EDs get the even + * entries, OUT EDs get the odd entries. + * + * Get the new, minimum interval. Add IN/OUT EDs are scheduled together + * at the minimum interval of all IN/OUT EDs. + */ + + if (epdesc->in) + { + offset = 0; + if (priv->ininterval > interval) + { + priv->ininterval = interval; + } + else + { + interval = priv->ininterval; + } + } + else + { + offset = 1; + if (priv->outinterval > interval) + { + priv->outinterval = interval; + } + else + { + interval = priv->outinterval; + } + } + uinfo("min interval: %d offset: %d\n", interval, offset); + + /* Get the head of the first of the duplicated entries. The first offset + * entry is always guaranteed to contain the common ED list head. + */ + + head = HCCA->inttbl[offset]; + + /* Clear all current entries in the interrupt table for this direction */ + + lpc54_setinttab(0, 2, offset); + + /* Add the new ED before the old head of the periodic ED list and set the + * new ED as the head ED in all of the appropriate entries of the HCCA + * interrupt table. + */ + + ed->hw.nexted = head; + lpc54_setinttab((uint32_t)ed, interval, offset); + uinfo("head: %08x next: %08x\n", ed, head); + + /* Re-enabled periodic list processing */ + + regval = lpc54_getreg(LPC54_OHCI_CTRL); + regval |= OHCI_CTRL_PLE; + lpc54_putreg(regval, LPC54_OHCI_CTRL); + return OK; +#else + return -ENOSYS; +#endif +} + +/**************************************************************************** + * Name: lpc54_reminted + * + * Description: + * Helper function to remove an ED from the HCCA interrupt table. + * + * To avoid reshuffling the table so much and to keep life simple in general, + * the following rules are applied: + * + * 1. IN EDs get the even entries, OUT EDs get the odd entries. + * 2. Add IN/OUT EDs are scheduled together at the minimum interval of all + * IN/OUT EDs. + * + * This has the following consequences: + * + * 1. The minimum support polling rate is 2MS, and + * 2. Some devices may get polled at a much higher rate than they request. + * + ****************************************************************************/ + +static inline int lpc54_reminted(struct lpc54_usbhost_s *priv, + struct lpc54_ed_s *ed) +{ +#ifndef CONFIG_OHCI_INT_DISABLE + struct lpc54_ed_s *head; + struct lpc54_ed_s *curr; + struct lpc54_ed_s *prev; + unsigned int interval; + unsigned int offset; + uint32_t regval; + + /* Disable periodic list processing. Does this take effect immediately? Or + * at the next SOF... need to check. + */ + + regval = lpc54_getreg(LPC54_OHCI_CTRL); + regval &= ~OHCI_CTRL_PLE; + lpc54_putreg(regval, LPC54_OHCI_CTRL); + + /* Get the offset associated with the ED direction. IN EDs get the even + * entries, OUT EDs get the odd entries. + */ + + if ((ed->hw.ctrl & ED_CONTROL_D_MASK) == ED_CONTROL_D_IN) + { + offset = 0; + } + else + { + offset = 1; + } + + /* Get the head of the first of the duplicated entries. The first offset + * entry is always guaranteed to contain the common ED list head. + */ + + head = (struct lpc54_ed_s *)HCCA->inttbl[offset]; + uinfo("ed: %08x head: %08x next: %08x offset: %d\n", + ed, head, head ? head->hw.nexted : 0, offset); + + /* Find the ED to be removed in the ED list */ + + for (curr = head, prev = NULL; + curr && curr != ed; + prev = curr, curr = (struct lpc54_ed_s *)curr->hw.nexted); + + /* Hmmm.. It would be a bug if we do not find the ED in the bulk list. */ + + DEBUGASSERT(curr != NULL); + if (curr != NULL) + { + /* Clear all current entries in the interrupt table for this direction */ + + lpc54_setinttab(0, 2, offset); + + /* Remove the ED from the list.. Is this ED the first on in the list? */ + + if (prev == NULL) + { + /* Yes... set the head of the bulk list to skip over this ED */ + + head = (struct lpc54_ed_s *)ed->hw.nexted; + } + else + { + /* No.. set the forward link of the previous ED in the list + * skip over this ED. + */ + + prev->hw.nexted = ed->hw.nexted; + } + + uinfo("ed: %08x head: %08x next: %08x\n", + ed, head, head ? head->hw.nexted : 0); + + /* Calculate the new minimum interval for this list */ + + interval = MAX_PERINTERVAL; + for (curr = head; curr; curr = (struct lpc54_ed_s *)curr->hw.nexted) + { + if (curr->interval < interval) + { + interval = curr->interval; + } + } + + uinfo("min interval: %d offset: %d\n", interval, offset); + + /* Save the new minimum interval */ + + if ((ed->hw.ctrl & ED_CONTROL_D_MASK) == ED_CONTROL_D_IN) + { + priv->ininterval = interval; + } + else + { + priv->outinterval = interval; + } + + /* Set the head ED in all of the appropriate entries of the HCCA interrupt + * table (head might be NULL). + */ + + lpc54_setinttab((uint32_t)head, interval, offset); + } + + /* Re-enabled periodic list processing */ + + if (head != NULL) + { + regval = lpc54_getreg(LPC54_OHCI_CTRL); + regval |= OHCI_CTRL_PLE; + lpc54_putreg(regval, LPC54_OHCI_CTRL); + } + + return OK; +#else + return -ENOSYS; +#endif +} + +/**************************************************************************** + * Name: lpc54_addisoced + * + * Description: + * Helper functions to add an ED to the periodic table. + * + ****************************************************************************/ + +static inline int lpc54_addisoced(struct lpc54_usbhost_s *priv, + const struct usbhost_epdesc_s *epdesc, + struct lpc54_ed_s *ed) +{ +#ifndef CONFIG_OHCI_ISOC_DISABLE +# warning "Isochronous endpoints not yet supported" +#endif + return -ENOSYS; +} + +/**************************************************************************** + * Name: lpc54_remisoced + * + * Description: + * Helper functions to remove an ED from the periodic table. + * + ****************************************************************************/ + +static inline int lpc54_remisoced(struct lpc54_usbhost_s *priv, + struct lpc54_ed_s *ed) +{ +#ifndef CONFIG_OHCI_ISOC_DISABLE +# warning "Isochronous endpoints not yet supported" +#endif + return -ENOSYS; +} + +/**************************************************************************** + * Name: lpc54_enqueuetd + * + * Description: + * Enqueue a transfer descriptor. Notice that this function only supports + * queue on TD per ED. + * + ****************************************************************************/ + +static int lpc54_enqueuetd(struct lpc54_usbhost_s *priv, + struct lpc54_ed_s *ed, uint32_t dirpid, + uint32_t toggle, volatile uint8_t *buffer, size_t buflen) +{ + struct lpc54_gtd_s *td; + int ret = -ENOMEM; + + /* Allocate a TD from the free list */ + + td = lpc54_tdalloc(); + if (td != NULL) + { + /* Initialize the allocated TD and link it before the common tail TD. */ + + td->hw.ctrl = (GTD_STATUS_R | dirpid | TD_DELAY(0) | toggle | GTD_STATUS_CC_MASK); + TDTAIL->hw.ctrl = 0; + td->hw.cbp = (uint32_t)buffer; + TDTAIL->hw.cbp = 0; + td->hw.nexttd = (uint32_t)TDTAIL; + TDTAIL->hw.nexttd = 0; + td->hw.be = (uint32_t)(buffer + (buflen - 1)); + TDTAIL->hw.be = 0; + + /* Configure driver-only fields in the extended TD structure */ + + td->ed = ed; + + /* Link the td to the head of the ED's TD list */ + + ed->hw.headp = (uint32_t)td | ((ed->hw.headp) & ED_HEADP_C); + ed->hw.tailp = (uint32_t)TDTAIL; + + ret = OK; + } + + return ret; +} + +/**************************************************************************** + * Name: lpc54_wdhwait + * + * Description: + * Set the request for the Writeback Done Head 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! + * + ****************************************************************************/ + +static int lpc54_wdhwait(struct lpc54_usbhost_s *priv, struct lpc54_ed_s *ed) +{ + struct lpc54_xfrinfo_s *xfrinfo; + irqstate_t flags = enter_critical_section(); + int ret = -ENODEV; + + DEBUGASSERT(ed && ed->xfrinfo); + xfrinfo = ed->xfrinfo; + + /* Is the device still connected? */ + + if (priv->connected) + { + /* Yes.. then set wdhwait to indicate that we expect to be informed when + * either (1) the device is disconnected, or (2) the transfer completed. + */ + + xfrinfo->wdhwait = true; + ret = OK; + } + + leave_critical_section(flags); + return ret; +} + +/**************************************************************************** + * Name: lpc54_ctrltd + * + * Description: + * Process a IN or OUT request on the control endpoint. This function + * will enqueue the request and wait for it to complete. Only one transfer + * may be queued; Neither these methods nor the transfer() method can be + * called again until the control transfer functions returns. + * + * These are blocking methods; these functions will not return until the + * control transfer has completed. + * + ****************************************************************************/ + +static int lpc54_ctrltd(struct lpc54_usbhost_s *priv, struct lpc54_ed_s *ed, + uint32_t dirpid, uint8_t *buffer, size_t buflen) +{ + struct lpc54_xfrinfo_s *xfrinfo; + uint32_t toggle; + uint32_t regval; + int ret; + + /* Allocate a structure to retain the information needed when the transfer + * completes. + */ + + DEBUGASSERT(ed->xfrinfo == NULL); + + xfrinfo = lpc54_alloc_xfrinfo(); + if (xfrinfo == NULL) + { + uerr("ERROR: lpc54_alloc_xfrinfo failed\n"); + return -ENOMEM; + } + + /* Initialize the transfer structure */ + + memset(xfrinfo, 0, sizeof(struct lpc54_xfrinfo_s)); + xfrinfo->buffer = buffer; + xfrinfo->buflen = buflen; + + ed->xfrinfo = xfrinfo; + + /* Set the request for the Writeback Done Head event well BEFORE enabling the + * transfer. + */ + + ret = lpc54_wdhwait(priv, ed); + if (ret < 0) + { + uerr("ERROR: Device disconnected\n"); + goto errout_with_xfrinfo; + } + + /* Configure the toggle field in the TD */ + + if (dirpid == GTD_STATUS_DP_SETUP) + { + toggle = GTD_STATUS_T_DATA0; + } + else + { + toggle = GTD_STATUS_T_DATA1; + } + + /* Then enqueue the transfer */ + + xfrinfo->tdstatus = TD_CC_NOERROR; + ret = lpc54_enqueuetd(priv, ed, dirpid, toggle, buffer, buflen); + if (ret == OK) + { + /* Set ControlListFilled. This bit is used to indicate whether there are + * TDs on the Control list. + */ + + regval = lpc54_getreg(LPC54_OHCI_CMDST); + regval |= OHCI_CMDST_CLF; + lpc54_putreg(regval, LPC54_OHCI_CMDST); + + /* Wait for the Writeback Done Head interrupt */ + + lpc54_takesem(&ed->wdhsem); + + /* Check the TD completion status bits */ + + if (xfrinfo->tdstatus == TD_CC_NOERROR) + { + ret = OK; + } + else + { + uerr("ERROR: Bad TD completion status: %d\n", xfrinfo->tdstatus); + ret = xfrinfo->tdstatus == TD_CC_STALL ? -EPERM : -EIO; + } + } + + /* Make sure that there is no outstanding request on this endpoint */ + +errout_with_xfrinfo: + lpc54_free_xfrinfo(xfrinfo); + ed->xfrinfo = NULL; + return ret; +} + +/**************************************************************************** + * Name: lpc54_usbinterrupt + * + * Description: + * USB interrupt handler + * + ****************************************************************************/ + +static int lpc54_usbinterrupt(int irq, void *context, FAR void *arg) +{ + struct lpc54_usbhost_s *priv = &g_usbhost; + struct lpc54_ed_s *ed; + struct lpc54_xfrinfo_s *xfrinfo; + uintptr_t tmp; + uint32_t intst; + uint32_t pending; + uint32_t regval; + + /* Read Interrupt Status and mask out interrupts that are not enabled. */ + + intst = lpc54_getreg(LPC54_OHCI_INTST); + regval = lpc54_getreg(LPC54_OHCI_INTEN); + uinfo("INST: %08x INTEN: %08x\n", intst, regval); + + pending = intst & regval; + if (pending != 0) + { + /* Root hub status change interrupt */ + + if ((pending & OHCI_INT_RHSC) != 0) + { + uint32_t rhportst1 = lpc54_getreg(LPC54_OHCI_RHPORTST1); + uinfo("Root Hub Status Change, RHPORTST1: %08x\n", rhportst1); + + if ((rhportst1 & OHCI_RHPORTST_CSC) != 0) + { + uint32_t rhstatus = lpc54_getreg(LPC54_OHCI_RHSTATUS); + uinfo("Connect Status Change, RHSTATUS: %08x\n", rhstatus); + + /* If DRWE is set, Connect Status Change indicates a remote wake-up event */ + + if (rhstatus & OHCI_RHSTATUS_DRWE) + { + uinfo("DRWE: Remote wake-up\n"); + } + + /* Otherwise... Not a remote wake-up event */ + + else + { + /* Check current connect status */ + + if ((rhportst1 & OHCI_RHPORTST_CCS) != 0) + { + /* Connected ... Did we just become connected? */ + + if (!priv->connected) + { + /* Yes.. connected. */ + + uinfo("Connected\n"); + priv->connected = true; + priv->change = true; + + /* Notify any waiters */ + + if (priv->pscwait) + { + lpc54_givesem(&priv->pscsem); + priv->pscwait = false; + } + } + else + { + uwarn("WARNING: Spurious status change (connected)\n"); + } + + /* The LSDA (Low speed device attached) bit is valid + * when CCS == 1. + */ + + if ((rhportst1 & OHCI_RHPORTST_LSDA) != 0) + { + priv->rhport.hport.speed = USB_SPEED_LOW; + } + else + { + priv->rhport.hport.speed = USB_SPEED_FULL; + } + + uinfo("Speed:%d\n", priv->rhport.hport.speed); + } + + /* Check if we are now disconnected */ + + else if (priv->connected) + { + /* Yes.. disconnect the device */ + + uinfo("Disconnected\n"); + priv->connected = false; + priv->change = true; + + /* Set the port speed to the default (FULL). We cannot + * yet free the function address. That has to be done + * by the class when responds to the disconnection. + */ + + priv->rhport.hport.speed = USB_SPEED_FULL; + + /* Are we bound to a class instance? */ + + if (priv->rhport.hport.devclass) + { + /* Yes.. Disconnect the class */ + + CLASS_DISCONNECTED(priv->rhport.hport.devclass); + priv->rhport.hport.devclass = NULL; + } + + /* Notify any waiters for the Root Hub Status change event */ + + if (priv->pscwait) + { + lpc54_givesem(&priv->pscsem); + priv->pscwait = false; + } + } + else + { + uwarn("WARNING: Spurious status change (disconnected)\n"); + } + } + + /* Clear the status change interrupt */ + + lpc54_putreg(OHCI_RHPORTST_CSC, LPC54_OHCI_RHPORTST1); + } + + /* Check for port reset status change */ + + if ((rhportst1 & OHCI_RHPORTST_PRSC) != 0) + { + /* Release the RH port from reset */ + + lpc54_putreg(OHCI_RHPORTST_PRSC, LPC54_OHCI_RHPORTST1); + } + } + + /* Writeback Done Head interrupt */ + + if ((pending & OHCI_INT_WDH) != 0) + { + struct lpc54_gtd_s *td; + struct lpc54_gtd_s *next; + + /* The host controller just wrote the list of finished TDs into the HCCA + * done head. This may include multiple packets that were transferred + * in the preceding frame. + * + * Remove the TD(s) from the Writeback Done Head in the HCCA and return + * them to the free list. Note that this is safe because the hardware + * will not modify the writeback done head again until the WDH bit is + * cleared in the interrupt status register. + */ + + td = (struct lpc54_gtd_s *)(HCCA->donehead & HCCA_DONEHEAD_MASK); + HCCA->donehead = 0; + next = NULL; + + /* Process each TD in the write done list */ + + for (; td; td = next) + { + /* REVISIT: I have encountered bad TDs in the done list linked + * after at least one good TD. This is some consequence of how + * transfers are being cancelled. But for now, I have only + * this work-around. + */ + + if ((uintptr_t)td < LPC54_TDFREE_BASE || + (uintptr_t)td >= (LPC54_TDFREE_BASE + LPC54_TD_SIZE*CONFIG_LP17_OHCI_NTDS)) + { + break; + } + + /* Get the ED in which this TD was enqueued */ + + ed = td->ed; + DEBUGASSERT(ed != NULL); + + /* If there is a transfer in progress, then the xfrinfo pointer will be + * non-NULL. But it appears that a NULL pointer may be received with a + * spurious interrupt such as may occur after a transfer is cancelled. + */ + + xfrinfo = ed->xfrinfo; + if (xfrinfo) + { + /* Save the condition code from the (single) TD status/control + * word. + */ + + xfrinfo->tdstatus = (td->hw.ctrl & GTD_STATUS_CC_MASK) >> GTD_STATUS_CC_SHIFT; + +#ifdef CONFIG_DEBUG_USB + if (xfrinfo->tdstatus != TD_CC_NOERROR) + { + /* The transfer failed for some reason... dump some diagnostic info. */ + + uerr("ERROR: ED xfrtype:%d TD CTRL:%08x/CC:%d RHPORTST1:%08x\n", + ed->xfrtype, td->hw.ctrl, xfrinfo->tdstatus, + lpc54_getreg(LPC54_OHCI_RHPORTST1)); + } +#endif + + /* Determine the number of bytes actually transfer by + * subtracting the buffer start address from the CBP. A + * value of zero means that all bytes were transferred. + */ + + tmp = (uintptr_t)td->hw.cbp; + if (tmp == 0) + { + /* Set the (fake) CBP to the end of the buffer + 1 */ + + tmp = xfrinfo->buflen; + } + else + { + DEBUGASSERT(tmp >= (uintptr_t)xfrinfo->buffer); + + /* Determine the size of the transfer by subtracting + * the current buffer pointer (CBP) from the initial + * buffer pointer (on packet receipt only). + */ + + tmp -= (uintptr_t)xfrinfo->buffer; + DEBUGASSERT(tmp < UINT16_MAX); + } + + xfrinfo->xfrd = (uint16_t)tmp; + + /* Return the TD to the free list */ + + next = (struct lpc54_gtd_s *)td->hw.nexttd; + lpc54_tdfree(td); + + if (xfrinfo->wdhwait) + { + /* Wake up the thread waiting for the WDH event */ + + lpc54_givesem(&ed->wdhsem); + xfrinfo->wdhwait = false; + } + +#ifdef CONFIG_OHCI_ASYNCH + /* Perform any pending callbacks for the case of + * asynchronous transfers. + */ + + else if (xfrinfo->callback) + { + DEBUGASSERT(xfrinfo->wdhwait == false); + lpc54_asynch_completion(priv, ed); + } +#endif + } + } + } + +#ifdef CONFIG_DEBUG_USB + if ((pending & LPC54_DEBUG_INTS) != 0) + { + uerr("ERROR: Unhandled interrupts INTST:%08x\n", intst); + } +#endif + + /* Clear interrupt status register */ + + lpc54_putreg(intst, LPC54_OHCI_INTST); + } + + return OK; +} + +/**************************************************************************** + * USB Host Controller Operations + ****************************************************************************/ + +/**************************************************************************** + * Name: lpc54_wait + * + * Description: + * Wait for a device to be connected or disconnected to/from a hub port. + * + * Input Parameters: + * conn - The USB host connection instance obtained as a parameter from the call to + * the USB driver initialization logic. + * hport - The location to return the hub port descriptor that detected the + * connection related event. + * + * Returned Value: + * Zero (OK) is returned on success when a device is connected or + * disconnected. This function will not return until either (1) a device is + * connected or disconnect to/from any hub port or until (2) some failure + * occurs. On a failure, a negated errno value is returned indicating the + * nature of the failure + * + * Assumptions: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static int lpc54_wait(struct usbhost_connection_s *conn, + struct usbhost_hubport_s **hport) +{ + struct lpc54_usbhost_s *priv = (struct lpc54_usbhost_s *)&g_usbhost; + struct usbhost_hubport_s *connport; + irqstate_t flags; + + flags = enter_critical_section(); + for (; ; ) + { + /* Is there a change in the connection state of the single root hub + * port? + */ + + if (priv->change) + { + connport = &priv->rhport.hport; + priv->change = false; + + /* Yes.. check for false alarms */ + + if (priv->connected != connport->connected) + { + /* Not a false alarm.. Remember the new state */ + + connport->connected = priv->connected; + + /* And return the root hub port */ + + *hport = connport; + leave_critical_section(flags); + + uinfo("RHport Connected: %s\n", + connport->connected ? "YES" : "NO"); + + return OK; + } + } + +#ifdef CONFIG_OHCI_HUB + /* Is a device connected to an external hub? */ + + if (priv->hport) + { + /* Yes.. return the external hub port */ + + connport = (struct usbhost_hubport_s *)priv->hport; + priv->hport = NULL; + + *hport = connport; + leave_critical_section(flags); + + uinfo("Hub port Connected: %s\n", connport->connected ? "YES" : "NO"); + return OK; + } +#endif + + /* Wait for the next connection event */ + + priv->pscwait = true; + lpc54_takesem(&priv->pscsem); + } +} + +/**************************************************************************** + * Name: lpc54_enumerate + * + * Description: + * Enumerate the connected device. As part of this enumeration process, + * the driver will (1) get the device's configuration descriptor, (2) + * extract the class ID info from the configuration descriptor, (3) call + * usbhost_findclass() to find the class that supports this device, (4) + * call the create() method on the struct usbhost_registry_s interface + * to get a class instance, and finally (5) call the connect() method + * of the struct usbhost_class_s interface. After that, the class is in + * charge of the sequence of operations. + * + * Input Parameters: + * conn - The USB host connection instance obtained as a parameter from + * the call to the USB driver initialization logic. + * hport - The descriptor of the hub port that has the newly connected + * device. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static int lpc54_rh_enumerate(struct usbhost_connection_s *conn, + struct usbhost_hubport_s *hport) +{ + struct lpc54_usbhost_s *priv = (struct lpc54_usbhost_s *)&g_usbhost; + DEBUGASSERT(conn != NULL && hport != NULL && hport->port == 0); + + /* Are we connected to a device? The caller should have called the wait() + * method first to be assured that a device is connected. + */ + + while (!priv->connected) + { + /* No, return an error */ + + uwarn("WARNING: Not connected\n"); + return -ENODEV; + } + + /* USB 2.0 spec says at least 50ms delay before port reset */ + + (void)nxsig_usleep(100*1000); + + /* Put RH port 1 in reset (the LPC546x supports only a single downstream port) */ + + lpc54_putreg(OHCI_RHPORTST_PRS, LPC54_OHCI_RHPORTST1); + + /* Wait for the port reset to complete */ + + while ((lpc54_getreg(LPC54_OHCI_RHPORTST1) & OHCI_RHPORTST_PRS) != 0); + + /* Release RH port 1 from reset and wait a bit */ + + lpc54_putreg(OHCI_RHPORTST_PRSC, LPC54_OHCI_RHPORTST1); + (void)nxsig_usleep(200*1000); + return OK; +} + +static int lpc54_enumerate(FAR struct usbhost_connection_s *conn, + FAR struct usbhost_hubport_s *hport) +{ + int ret; + + DEBUGASSERT(hport); + + /* If this is a connection on the root hub, then we need to go to + * little more effort to get the device speed. If it is a connection + * on an external hub, then we already have that information. + */ + +#ifdef CONFIG_OHCI_HUB + if (ROOTHUB(hport)) +#endif + { + ret = lpc54_rh_enumerate(conn, hport); + if (ret < 0) + { + return ret; + } + } + + /* Then let the common usbhost_enumerate do the real enumeration. */ + + uinfo("Enumerate the device\n"); + ret = usbhost_enumerate(hport, &hport->devclass); + if (ret < 0) + { + uerr("ERROR: Enumeration failed: %d\n", ret); + } + + return ret; +} + +/************************************************************************************ + * Name: lpc54_ep0configure + * + * Description: + * Configure endpoint 0. This method is normally used internally by the + * enumerate() method but is made available at the interface to support + * an external implementation of the enumeration logic. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the call to + * the class create() method. + * ep0 - The (opaque) EP0 endpoint instance + * funcaddr - The USB address of the function containing the endpoint that EP0 + * controls + * speed - The speed of the port USB_SPEED_LOW, _FULL, or _HIGH + * mps (maxpacketsize) - The maximum number of bytes that can be sent to or + * received from the endpoint in a single data packet + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ************************************************************************************/ + +static int lpc54_ep0configure(struct usbhost_driver_s *drvr, usbhost_ep_t ep0, + uint8_t funcaddr, uint8_t speed, uint16_t maxpacketsize) +{ + struct lpc54_usbhost_s *priv = (struct lpc54_usbhost_s *)drvr; + struct lpc54_ed_s *ed; + uint32_t hwctrl; + + DEBUGASSERT(drvr != NULL && ep0 != NULL && funcaddr < 128 && maxpacketsize < 2048); + ed = (struct lpc54_ed_s *)ep0; + + /* We must have exclusive access to EP0 and the control list */ + + lpc54_takesem(&priv->exclsem); + + /* Set the EP0 ED control word */ + + hwctrl = (uint32_t)funcaddr << ED_CONTROL_FA_SHIFT | + (uint32_t)ED_CONTROL_D_TD1 | + (uint32_t)maxpacketsize << ED_CONTROL_MPS_SHIFT; + + if (speed == USB_SPEED_LOW) + { + hwctrl |= ED_CONTROL_S; + } + + ed->hw.ctrl = hwctrl; + + lpc54_givesem(&priv->exclsem); + + uinfo("EP0 CTRL:%08x\n", ed->hw.ctrl); + return OK; +} + +/************************************************************************************ + * Name: lpc54_epalloc + * + * Description: + * Allocate and configure one endpoint. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the call to + * the class create() method. + * epdesc - Describes the endpoint to be allocated. + * ep - A memory location provided by the caller in which to receive the + * allocated endpoint descriptor. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ************************************************************************************/ + +static int lpc54_epalloc(struct usbhost_driver_s *drvr, + const struct usbhost_epdesc_s *epdesc, usbhost_ep_t *ep) +{ + struct lpc54_usbhost_s *priv = (struct lpc54_usbhost_s *)drvr; + struct usbhost_hubport_s *hport; + struct lpc54_ed_s *ed; + int ret = -ENOMEM; + + /* Sanity check. NOTE that this method should only be called if a device is + * connected (because we need a valid low speed indication). + */ + + DEBUGASSERT(priv && epdesc && ep && priv->connected); + + /* We must have exclusive access to the ED pool, the bulk list, the periodic list + * and the interrupt table. + */ + + lpc54_takesem(&priv->exclsem); + + /* Take the next ED from the beginning of the free list */ + + ed = (struct lpc54_ed_s *)g_edfree; + if (ed) + { + /* Remove the ED from the freelist */ + + g_edfree = ((struct lpc54_list_s *)ed)->flink; + + /* Configure the endpoint descriptor. */ + + memset((void *)ed, 0, sizeof(struct lpc54_ed_s)); + + hport = epdesc->hport; + ed->hw.ctrl = (uint32_t)(hport->funcaddr) << ED_CONTROL_FA_SHIFT | + (uint32_t)(epdesc->addr) << ED_CONTROL_EN_SHIFT | + (uint32_t)(epdesc->mxpacketsize) << ED_CONTROL_MPS_SHIFT; + + /* Get the direction of the endpoint. For control endpoints, the + * direction is in the TD. + */ + + if (epdesc->xfrtype == USB_EP_ATTR_XFER_CONTROL) + { + ed->hw.ctrl |= ED_CONTROL_D_TD1; + } + else if (epdesc->in) + { + ed->hw.ctrl |= ED_CONTROL_D_IN; + } + else + { + ed->hw.ctrl |= ED_CONTROL_D_OUT; + } + + /* Check for a low-speed device */ + + if (hport->speed == USB_SPEED_LOW) + { + ed->hw.ctrl |= ED_CONTROL_S; + } + + /* Set the transfer type */ + + ed->xfrtype = epdesc->xfrtype; + + /* Special Case isochronous transfer types */ + +#if 0 /* Isochronous transfers not yet supported */ + if (ed->xfrtype == USB_EP_ATTR_XFER_ISOC) + { + ed->hw.ctrl |= ED_CONTROL_F; + } +#endif + uinfo("EP%d CTRL:%08x\n", epdesc->addr, ed->hw.ctrl); + + /* Initialize the semaphore that is used to wait for the endpoint + * WDH event. The wdhsem semaphore is used for signaling and, hence, + * should not have priority inheritance enabled. + */ + + nxsem_init(&ed->wdhsem, 0, 0); + nxsem_setprotocol(&ed->wdhsem, SEM_PRIO_NONE); + + /* Link the common tail TD to the ED's TD list */ + + ed->hw.headp = (uint32_t)TDTAIL; + ed->hw.tailp = (uint32_t)TDTAIL; + + /* Now add the endpoint descriptor to the appropriate list */ + + switch (ed->xfrtype) + { + case USB_EP_ATTR_XFER_CONTROL: + ret = lpc54_addctrled(priv, ed); + break; + + case USB_EP_ATTR_XFER_BULK: + ret = lpc54_addbulked(priv, ed); + break; + + case USB_EP_ATTR_XFER_INT: + ret = lpc54_addinted(priv, epdesc, ed); + break; + + case USB_EP_ATTR_XFER_ISOC: + ret = lpc54_addisoced(priv, epdesc, ed); + break; + + default: + ret = -EINVAL; + break; + } + + /* Was the ED successfully added? */ + + if (ret < 0) + { + /* No.. destroy it and report the error */ + + uerr("ERROR: Failed to queue ED for transfer type: %d\n", ed->xfrtype); + nxsem_destroy(&ed->wdhsem); + lpc54_edfree(ed); + } + else + { + /* Yes.. return an opaque reference to the ED */ + + *ep = (usbhost_ep_t)ed; + } + } + + lpc54_givesem(&priv->exclsem); + return ret; +} + +/************************************************************************************ + * Name: lpc54_epfree + * + * Description: + * Free and endpoint previously allocated by DRVR_EPALLOC. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the call to + * the class create() method. + * ep - The endpint to be freed. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ************************************************************************************/ + +static int lpc54_epfree(struct usbhost_driver_s *drvr, usbhost_ep_t ep) +{ + struct lpc54_usbhost_s *priv = (struct lpc54_usbhost_s *)drvr; + struct lpc54_ed_s *ed = (struct lpc54_ed_s *)ep; + int ret; + + /* There should not be any pending, real TDs linked to this ED */ + + DEBUGASSERT(ed && (ed->hw.headp & ED_HEADP_ADDR_MASK) == LPC54_TDTAIL_ADDR); + + /* We must have exclusive access to the ED pool, the bulk list, the periodic list + * and the interrupt table. + */ + + lpc54_takesem(&priv->exclsem); + + /* Remove the ED to the correct list depending on the trasfer type */ + + switch (ed->xfrtype) + { + case USB_EP_ATTR_XFER_CONTROL: + ret = lpc54_remctrled(priv, ed); + break; + + case USB_EP_ATTR_XFER_BULK: + ret = lpc54_rembulked(priv, ed); + break; + + case USB_EP_ATTR_XFER_INT: + ret = lpc54_reminted(priv, ed); + break; + + case USB_EP_ATTR_XFER_ISOC: + ret = lpc54_remisoced(priv, ed); + break; + + default: + ret = -EINVAL; + break; + } + + /* Destroy the semaphore */ + + nxsem_destroy(&ed->wdhsem); + + /* Put the ED back into the free list */ + + lpc54_edfree(ed); + lpc54_givesem(&priv->exclsem); + return ret; +} + +/**************************************************************************** + * Name: lpc54_alloc + * + * Description: + * Some hardware supports special memory in which request and descriptor data can + * be accessed more efficiently. This method provides a mechanism to allocate + * the request/descriptor memory. If the underlying hardware does not support + * such "special" memory, this functions may simply map to kmm_malloc. + * + * This interface was optimized under a particular assumption. It was assumed + * that the driver maintains a pool of small, pre-allocated buffers for descriptor + * traffic. NOTE that size is not an input, but an output: The size of the + * pre-allocated buffer is returned. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the call to + * the class create() method. + * buffer - The address of a memory location provided by the caller in which to + * return the allocated buffer memory address. + * maxlen - The address of a memory location provided by the caller in which to + * return the maximum size of the allocated buffer memory. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure + * + * Assumptions: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static int lpc54_alloc(struct usbhost_driver_s *drvr, + uint8_t **buffer, size_t *maxlen) +{ + struct lpc54_usbhost_s *priv = (struct lpc54_usbhost_s *)drvr; + DEBUGASSERT(priv && buffer && maxlen); + int ret = -ENOMEM; + + /* We must have exclusive access to the transfer buffer pool */ + + lpc54_takesem(&priv->exclsem); + + *buffer = lpc54_tballoc(); + if (*buffer) + { + *maxlen = CONFIG_LPC54_OHCI_TDBUFSIZE; + ret = OK; + } + + lpc54_givesem(&priv->exclsem); + return ret; +} + +/**************************************************************************** + * Name: lpc54_free + * + * Description: + * Some hardware supports special memory in which request and descriptor data can + * be accessed more efficiently. This method provides a mechanism to free that + * request/descriptor memory. If the underlying hardware does not support + * such "special" memory, this functions may simply map to kmm_free(). + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the call to + * the class create() method. + * buffer - The address of the allocated buffer memory to be freed. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure + * + * Assumptions: + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static int lpc54_free(struct usbhost_driver_s *drvr, uint8_t *buffer) +{ + struct lpc54_usbhost_s *priv = (struct lpc54_usbhost_s *)drvr; + DEBUGASSERT(buffer); + + /* We must have exclusive access to the transfer buffer pool */ + + lpc54_takesem(&priv->exclsem); + lpc54_tbfree(buffer); + lpc54_givesem(&priv->exclsem); + return OK; +} + +/************************************************************************************ + * Name: lpc54_ioalloc + * + * Description: + * Some hardware supports special memory in which larger IO buffers can + * be accessed more efficiently. This method provides a mechanism to allocate + * the request/descriptor memory. If the underlying hardware does not support + * such "special" memory, this functions may simply map to kmm_malloc. + * + * This interface differs from DRVR_ALLOC in that the buffers are variable-sized. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the call to + * the class create() method. + * buffer - The address of a memory location provided by the caller in which to + * return the allocated buffer memory address. + * buflen - The size of the buffer required. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ************************************************************************************/ + +static int lpc54_ioalloc(struct usbhost_driver_s *drvr, + uint8_t **buffer, size_t buflen) +{ + DEBUGASSERT(drvr && buffer); + +#if LPC54_IOBUFFERS > 0 + if (buflen <= CONFIG_LPC54_OHCI_IOBUFSIZE) + { + uint8_t *alloc = lpc54_allocio(); + if (alloc) + { + *buffer = alloc; + return OK; + } + } + + return -ENOMEM; +#else + return -ENOSYS; +#endif +} + +/************************************************************************************ + * Name: lpc54_iofree + * + * Description: + * Some hardware supports special memory in which IO data can be accessed more + * efficiently. This method provides a mechanism to free that IO buffer + * memory. If the underlying hardware does not support such "special" memory, + * this functions may simply map to kmm_free(). + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the call to + * the class create() method. + * buffer - The address of the allocated buffer memory to be freed. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ************************************************************************************/ + +static int lpc54_iofree(struct usbhost_driver_s *drvr, uint8_t *buffer) +{ + DEBUGASSERT(drvr && buffer); + +#if LPC54_IOBUFFERS > 0 + lpc54_freeio(buffer); + return OK; +#else + return -ENOSYS; +#endif +} + +/**************************************************************************** + * Name: lpc54_ctrlin and lpc54_ctrlout + * + * Description: + * Description: + * Process a IN or OUT request on the control endpoint. These methods + * will enqueue the request and wait for it to complete. Only one transfer may be + * queued; Neither these methods nor the transfer() method can be called again + * until the control transfer functions returns. + * + * These are blocking methods; these functions will not return until the + * control transfer has completed. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the call to + * the class create() method. + * ep0 - The control endpoint to send/receive the control request. + * req - Describes the request to be sent. This request must lie in memory + * created by DRVR_ALLOC. + * buffer - A buffer used for sending the request and for returning any + * responses. This buffer must be large enough to hold the length value + * in the request description. buffer must have been allocated using DRVR_ALLOC. + * + * NOTE: On an IN transaction, req and buffer may refer to the same allocated + * memory. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure + * + * Assumptions: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static int lpc54_ctrlin(struct usbhost_driver_s *drvr, usbhost_ep_t ep0, + const struct usb_ctrlreq_s *req, + uint8_t *buffer) +{ + struct lpc54_usbhost_s *priv = (struct lpc54_usbhost_s *)drvr; + struct lpc54_ed_s *ed = (struct lpc54_ed_s *)ep0; + uint16_t len; + int ret; + + DEBUGASSERT(priv != NULL && ed != NULL && req != NULL); + + uinfo("type:%02x req:%02x value:%02x%02x index:%02x%02x len:%02x%02x\n", + req->type, req->req, req->value[1], req->value[0], + req->index[1], req->index[0], req->len[1], req->len[0]); + + /* We must have exclusive access to EP0 and the control list */ + + lpc54_takesem(&priv->exclsem); + + len = lpc54_getle16(req->len); + ret = lpc54_ctrltd(priv, ed, GTD_STATUS_DP_SETUP, (uint8_t *)req, USB_SIZEOF_CTRLREQ); + if (ret == OK) + { + if (len) + { + ret = lpc54_ctrltd(priv, ed, GTD_STATUS_DP_IN, buffer, len); + } + + if (ret == OK) + { + ret = lpc54_ctrltd(priv, ed, GTD_STATUS_DP_OUT, NULL, 0); + } + } + + lpc54_givesem(&priv->exclsem); + return ret; +} + +static int lpc54_ctrlout(struct usbhost_driver_s *drvr, usbhost_ep_t ep0, + const struct usb_ctrlreq_s *req, + const uint8_t *buffer) +{ + struct lpc54_usbhost_s *priv = (struct lpc54_usbhost_s *)drvr; + struct lpc54_ed_s *ed = (struct lpc54_ed_s *)ep0; + uint16_t len; + int ret; + + DEBUGASSERT(priv != NULL && ed != NULL && req != NULL); + + uinfo("type:%02x req:%02x value:%02x%02x index:%02x%02x len:%02x%02x\n", + req->type, req->req, req->value[1], req->value[0], + req->index[1], req->index[0], req->len[1], req->len[0]); + + /* We must have exclusive access to EP0 and the control list */ + + lpc54_takesem(&priv->exclsem); + + len = lpc54_getle16(req->len); + ret = lpc54_ctrltd(priv, ed, GTD_STATUS_DP_SETUP, (uint8_t *)req, USB_SIZEOF_CTRLREQ); + if (ret == OK) + { + if (len) + { + ret = lpc54_ctrltd(priv, ed, GTD_STATUS_DP_OUT, (uint8_t *)buffer, len); + } + + if (ret == OK) + { + ret = lpc54_ctrltd(priv, ed, GTD_STATUS_DP_IN, NULL, 0); + } + } + + lpc54_givesem(&priv->exclsem); + return ret; +} + +/**************************************************************************** + * Name: lpc54_transfer_common + * + * Description: + * Initiate a request to handle a transfer descriptor. This method will + * enqueue the transfer request and return immediately + * + * Input Parameters: + * priv - Internal driver state structure. + * ed - The IN or OUT endpoint descriptor for the device endpoint on which to + * perform the transfer. + * buffer - A buffer containing the data to be sent (OUT endpoint) or received + * (IN endpoint). buffer must have been allocated using DRVR_ALLOC + * buflen - The length of the data to be sent or received. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure. + * + * + * Assumptions: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static int lpc54_transfer_common(struct lpc54_usbhost_s *priv, + struct lpc54_ed_s *ed, uint8_t *buffer, + size_t buflen) +{ + struct lpc54_xfrinfo_s *xfrinfo; + uint32_t dirpid; + uint32_t regval; + bool in; + int ret; + + xfrinfo = ed->xfrinfo; + in = (ed->hw.ctrl & ED_CONTROL_D_MASK) == ED_CONTROL_D_IN; + + uinfo("EP%u %s toggle:%u maxpacket:%u buflen:%lu\n", + (ed->hw.ctrl & ED_CONTROL_EN_MASK) >> ED_CONTROL_EN_SHIFT, + in ? "IN" : "OUT", + (ed->hw.headp & ED_HEADP_C) != 0 ? 1 : 0, + (ed->hw.ctrl & ED_CONTROL_MPS_MASK) >> ED_CONTROL_MPS_SHIFT, + (unsigned long)buflen); + + /* Get the direction of the endpoint */ + + if (in) + { + dirpid = GTD_STATUS_DP_IN; + } + else + { + dirpid = GTD_STATUS_DP_OUT; + } + + /* Then enqueue the transfer */ + + xfrinfo->tdstatus = TD_CC_NOERROR; + ret = lpc54_enqueuetd(priv, ed, dirpid, GTD_STATUS_T_TOGGLE, buffer, buflen); + if (ret == OK) + { + /* BulkListFilled. This bit is used to indicate whether there are any + * TDs on the Bulk list. + */ + + if (ed->xfrtype == USB_EP_ATTR_XFER_BULK) + { + regval = lpc54_getreg(LPC54_OHCI_CMDST); + regval |= OHCI_CMDST_BLF; + lpc54_putreg(regval, LPC54_OHCI_CMDST); + } + } + + return ret; +} + +/**************************************************************************** + * Name: lpc54_dma_alloc + * + * Description: + * Allocate DMA memory to perform a transfer, copying user data as necessary + * + * Input Parameters: + * priv - Internal driver state structure. + * ed - The IN or OUT endpoint descriptor for the device endpoint on which to + * perform the transfer. + * userbuffer - The user buffer containing the data to be sent (OUT endpoint) + * or received (IN endpoint). + * buflen - The length of the data to be sent or received. + * alloc - The location to return the allocated DMA buffer. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure. + * + * Assumptions: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +#if LPC54_IOBUFFERS > 0 +static int lpc54_dma_alloc(struct lpc54_usbhost_s *priv, + struct lpc54_ed_s *ed, uint8_t *userbuffer, + size_t buflen, uint8_t **alloc) +{ + uint8_t *newbuffer; + + if ((uintptr_t)userbuffer < LPC54_SRAM_BANK0 || + (uintptr_t)userbuffer >= (LPC54_SRAM_BANK0 + LPC54_BANK0_SIZE + LPC54_BANK1_SIZE)) + { + /* Will the transfer fit in an IO buffer? */ + + if (buflen > CONFIG_LPC54_OHCI_IOBUFSIZE) + { + uinfo("buflen (%d) > IO buffer size (%d)\n", + buflen, CONFIG_LPC54_OHCI_IOBUFSIZE); + return -ENOMEM; + } + + /* Allocate an IO buffer in AHB SRAM */ + + newbuffer = lpc54_allocio(); + if (!newbuffer) + { + uinfo("IO buffer allocation failed\n"); + return -ENOMEM; + } + + /* If this is an OUT transaction, copy the user data into the AHB + * SRAM IO buffer. Sad... so inefficient. But without exposing + * the AHB SRAM to the final, end-user client I don't know of any + * way around this copy. + */ + + if ((ed->hw.ctrl & ED_CONTROL_D_MASK) != ED_CONTROL_D_IN) + { + memcpy(newbuffer, userbuffer, buflen); + } + + /* Return the allocated buffer */ + + *alloc = newbuffer; + } + + return OK; +} + +/**************************************************************************** + * Name: lpc54_dma_free + * + * Description: + * Free allocated DMA memory. + * + * Input Parameters: + * priv - Internal driver state structure. + * ed - The IN or OUT endpoint descriptor for the device endpoint on which to + * perform the transfer. + * userbuffer - The user buffer containing the data to be sent (OUT endpoint) + * or received (IN endpoint). + * buflen - The length of the data to be sent or received. + * alloc - The allocated DMA buffer to be freed. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure. + * + * Assumptions: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static void lpc54_dma_free(struct lpc54_usbhost_s *priv, + struct lpc54_ed_s *ed, uint8_t *userbuffer, + size_t buflen, uint8_t *newbuffer) +{ + irqstate_t flags; + + /* Could be called from the interrupt level */ + + flags = enter_critical_section(); + if (userbuffer && newbuffer) + { + /* If this is an IN transaction, get the user data from the AHB + * SRAM IO buffer. Sad... so inefficient. But without exposing + * the AHB SRAM to the final, end-user client I don't know of any + * way around this copy. + */ + + if ((ed->hw.ctrl & ED_CONTROL_D_MASK) == ED_CONTROL_D_IN) + { + memcpy(userbuffer, newbuffer, buflen); + } + + /* Then free the temporary I/O buffer */ + + lpc54_freeio(newbuffer); + } + + leave_critical_section(flags); +} +#endif + +/**************************************************************************** + * Name: lpc54_transfer + * + * Description: + * Process a request to handle a transfer descriptor. This method will + * enqueue the transfer request, blocking until the transfer completes. Only + * one transfer may be queued; Neither this method nor the ctrlin or + * ctrlout methods can be called again until this function returns. + * + * This is a blocking method; this functions will not return until the + * transfer has completed. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the call to + * the class create() method. + * ep - The IN or OUT endpoint descriptor for the device endpoint on which to + * perform the transfer. + * buffer - A buffer containing the data to be sent (OUT endpoint) or received + * (IN endpoint). buffer must have been allocated using DRVR_ALLOC + * buflen - The length of the data to be sent or received. + * + * Returned Value: + * On success, a non-negative value is returned that indicates the number + * of bytes successfully transferred. On a failure, a negated errno value is + * returned that indicates the nature of the failure: + * + * EAGAIN - If devices NAKs the transfer (or NYET or other error where + * it may be appropriate to restart the entire transaction). + * EPERM - If the endpoint stalls + * EIO - On a TX or data toggle error + * EPIPE - Overrun errors + * + * Assumptions: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static ssize_t lpc54_transfer(struct usbhost_driver_s *drvr, usbhost_ep_t ep, + uint8_t *buffer, size_t buflen) +{ + struct lpc54_usbhost_s *priv = (struct lpc54_usbhost_s *)drvr; + struct lpc54_ed_s *ed = (struct lpc54_ed_s *)ep; + struct lpc54_xfrinfo_s *xfrinfo; +#if LPC54_IOBUFFERS > 0 + uint8_t *alloc = NULL; + uint8_t *userbuffer = NULL; +#endif + ssize_t nbytes; + int ret; + + DEBUGASSERT(priv && ed && buffer && buflen > 0); + + /* We must have exclusive access to the endpoint, the TD pool, the I/O buffer + * pool, the bulk and interrupt lists, and the HCCA interrupt table. + */ + + lpc54_takesem(&priv->exclsem); + + /* Allocate a structure to retain the information needed when the transfer + * completes. + */ + + DEBUGASSERT(ed->xfrinfo == NULL); + + xfrinfo = lpc54_alloc_xfrinfo(); + if (xfrinfo == NULL) + { + uerr("ERROR: lpc54_alloc_xfrinfo failed\n"); + nbytes = -ENOMEM; + goto errout_with_sem; + } + + /* Initialize the transfer structure */ + + memset(xfrinfo, 0, sizeof(struct lpc54_xfrinfo_s)); + xfrinfo->buffer = buffer; + xfrinfo->buflen = buflen; + + ed->xfrinfo = xfrinfo; + +#if LPC54_IOBUFFERS > 0 + /* Allocate an IO buffer if the user buffer does not lie in AHB SRAM */ + + ret = lpc54_dma_alloc(priv, ed, buffer, buflen, &alloc); + if (ret < 0) + { + uerr("ERROR: lpc54_dma_alloc failed: %d\n", ret); + nbytes = (ssize_t)ret; + goto errout_with_xfrinfo; + } + + /* If a buffer was allocated, then use it instead of the callers buffer */ + + if (alloc) + { + userbuffer = buffer; + buffer = alloc; + } +#endif + + /* Set the request for the Writeback Done Head event well BEFORE enabling the + * transfer. + */ + + ret = lpc54_wdhwait(priv, ed); + if (ret < 0) + { + uerr("ERROR: Device disconnected\n"); + nbytes = (ssize_t)ret; + goto errout_with_buffers; + } + + /* Set up the transfer */ + + ret = lpc54_transfer_common(priv, ed, buffer, buflen); + if (ret < 0) + { + uerr("ERROR: lpc54_transfer_common failed: %d\n", ret); + nbytes = (ssize_t)ret; + goto errout_with_wdhwait; + } + + /* Wait for the Writeback Done Head interrupt */ + + lpc54_takesem(&ed->wdhsem); + + /* Check the TD completion status bits */ + + if (xfrinfo->tdstatus == TD_CC_NOERROR) + { + /* Return the number of bytes successfully transferred */ + + nbytes = xfrinfo->xfrd; + DEBUGASSERT(nbytes >= 0 && nbytes <= buflen); + } + else + { + /* Map the bad completion status to something that a class driver + * might understand. + */ + + uerr("ERROR: Bad TD completion status: %d\n", xfrinfo->tdstatus); + + switch (xfrinfo->tdstatus) + { + case TD_CC_STALL: + nbytes = -EPERM; + break; + + case TD_CC_USER: + nbytes = -ESHUTDOWN; + break; + + default: + nbytes = -EIO; + break; + } + } + +errout_with_wdhwait: + /* Make sure that there is no outstanding request on this endpoint */ + + xfrinfo->wdhwait = false; + +errout_with_buffers: +#if LPC54_IOBUFFERS > 0 + /* Free any temporary IO buffers */ + + lpc54_dma_free(priv, ed, userbuffer, buflen, alloc); +#endif + +errout_with_xfrinfo: + /* Make sure that there is no outstanding request on this endpoint */ + + lpc54_free_xfrinfo(xfrinfo); + ed->xfrinfo = NULL; + +errout_with_sem: + lpc54_givesem(&priv->exclsem); + return nbytes; +} + +/**************************************************************************** + * Name: lpc54_asynch_completion + * + * Description: + * This function is called at the interrupt level when an asynchronous + * transfer completes. It performs the pending callback. + * + * Input Parameters: + * priv - Internal driver state structure. + * ep - 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_OHCI_ASYNCH +static void lpc54_asynch_completion(struct lpc54_usbhost_s *priv, + struct lpc54_ed_s *ed) +{ + struct lpc54_xfrinfo_s *xfrinfo; + usbhost_asynch_t callback; + void *arg; + ssize_t nbytes; + + DEBUGASSERT(ed != NULL && ed->xfrinfo != NULL); + xfrinfo = ed->xfrinfo; + + DEBUGASSERT(xfrinfo->wdhwait == false && xfrinfo->callback != NULL && + xfrinfo->buffer != NULL && xfrinfo->buflen > 0); + + /* Check the TD completion status bits */ + + if (xfrinfo->tdstatus == TD_CC_NOERROR) + { + /* Provide the number of bytes successfully transferred */ + + nbytes = xfrinfo->xfrd; + } + else + { + /* Map the bad completion status to something that a class driver + * might understand. + */ + + uerr("ERROR: Bad TD completion status: %d\n", xfrinfo->tdstatus); + + switch (xfrinfo->tdstatus) + { + case TD_CC_STALL: + nbytes = -EPERM; + break; + + case TD_CC_USER: + nbytes = -ESHUTDOWN; + break; + + default: + nbytes = -EIO; + break; + } + } + +#if LPC54_IOBUFFERS > 0 + /* Free any temporary IO buffers */ + + lpc54_dma_free(priv, ed, xfrinfo->buffer, xfrinfo->buflen, xfrinfo->alloc); +#endif + + /* Extract the callback information before freeing the buffer */ + + callback = xfrinfo->callback; + arg = xfrinfo->arg; + + /* Make sure that there is no outstanding request on this endpoint */ + + lpc54_free_xfrinfo(xfrinfo); + ed->xfrinfo = NULL; + + /* Then perform the callback */ + + callback(arg, nbytes); +} +#endif + +/**************************************************************************** + * Name: lpc54_asynch + * + * Description: + * Process a request to handle a transfer descriptor. This method will + * enqueue the transfer request and return immediately. When the transfer + * completes, the callback will be invoked with the provided transfer. + * This method is useful for receiving interrupt transfers which may come + * infrequently. + * + * Only one transfer may be queued; Neither this method nor the ctrlin or + * ctrlout methods can be called again until the transfer completes. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the call to + * the class create() method. + * ep - The IN or OUT endpoint descriptor for the device endpoint on which to + * perform the transfer. + * buffer - A buffer containing the data to be sent (OUT endpoint) or received + * (IN endpoint). buffer must have been allocated using DRVR_ALLOC + * buflen - The length of the data to be sent or received. + * callback - This function will be called when the transfer completes. + * arg - The arbitrary parameter that will be passed to the callback function + * when the transfer completes. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure + * + * Assumptions: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +#ifdef CONFIG_OHCI_ASYNCH +static int lpc54_asynch(struct usbhost_driver_s *drvr, usbhost_ep_t ep, + uint8_t *buffer, size_t buflen, + usbhost_asynch_t callback, void *arg) +{ + struct lpc54_usbhost_s *priv = (struct lpc54_usbhost_s *)drvr; + struct lpc54_ed_s *ed = (struct lpc54_ed_s *)ep; + struct lpc54_xfrinfo_s *xfrinfo; + int ret; + + DEBUGASSERT(priv && ed && ed->xfrinfo == NULL && buffer && buflen > 0 && callback); + + /* We must have exclusive access to the endpoint, the TD pool, the I/O buffer + * pool, the bulk and interrupt lists, and the HCCA interrupt table. + */ + + lpc54_takesem(&priv->exclsem); + + /* Allocate a structure to retain the information needed when the asynchronous + * transfer completes. + */ + + DEBUGASSERT(ed->xfrinfo == NULL); + + xfrinfo = lpc54_alloc_xfrinfo(); + if (xfrinfo == NULL) + { + uerr("ERROR: lpc54_alloc_xfrinfo failed\n"); + ret = -ENOMEM; + goto errout_with_sem; + } + + /* Initialize the transfer structure */ + + memset(xfrinfo, 0, sizeof(struct lpc54_xfrinfo_s)); + xfrinfo->buffer = buffer; + xfrinfo->buflen = buflen; + xfrinfo->callback = callback; + xfrinfo->arg = arg; + + ed->xfrinfo = xfrinfo; + +#if LPC54_IOBUFFERS > 0 + /* Allocate an IO buffer if the user buffer does not lie in AHB SRAM */ + + ret = lpc54_dma_alloc(priv, ed, buffer, buflen, &xfrinfo->alloc); + if (ret < 0) + { + uerr("ERROR: lpc54_dma_alloc failed: %d\n", ret); + goto errout_with_sem; + } + + /* If a buffer was allocated, then use it instead of the callers buffer */ + + if (xfrinfo->alloc) + { + buffer = xfrinfo->alloc; + } +#endif + + /* Set up the transfer */ + + ret = lpc54_transfer_common(priv, ed, buffer, buflen); + if (ret < 0) + { + uerr("ERROR: lpc54_transfer_common failed: %d\n", ret); + goto errout_with_asynch; + } + + /* And return now. The callback will be invoked when the transfer + * completes. + */ + + lpc54_givesem(&priv->exclsem); + return OK; + +errout_with_asynch: +#if LPC54_IOBUFFERS > 0 + /* Free any temporary IO buffers */ + + lpc54_dma_free(priv, ed, buffer, buflen, xfrinfo->alloc); +#endif + + /* Free the transfer structure */ + + lpc54_free_xfrinfo(xfrinfo); + ed->xfrinfo = NULL; + +errout_with_sem: + lpc54_givesem(&priv->exclsem); + return ret; +} +#endif /* CONFIG_OHCI_ASYNCH */ + +/************************************************************************************ + * Name: lpc54_cancel + * + * Description: + * Cancel a pending transfer on an endpoint. Cancelled synchronous or + * asynchronous transfer will complete normally with the error -ESHUTDOWN. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the call to + * the class create() method. + * ep - The IN or OUT endpoint descriptor for the device endpoint on which an + * asynchronous transfer should be transferred. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure. + * + ************************************************************************************/ + +static int lpc54_cancel(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep) +{ +#ifdef CONFIG_OHCI_ASYNCH + struct lpc54_usbhost_s *priv = (struct lpc54_usbhost_s *)drvr; +#endif + struct lpc54_ed_s *ed = (struct lpc54_ed_s *)ep; + struct lpc54_gtd_s *td; + struct lpc54_gtd_s *next; + struct lpc54_xfrinfo_s *xfrinfo; + uint32_t ctrl; + irqstate_t flags; + + DEBUGASSERT(drvr != NULL && ed != NULL); + + /* These first steps must be atomic as possible */ + + flags = enter_critical_section(); + + /* It is possible there there is no transfer to be in progress */ + + xfrinfo = ed->xfrinfo; + if (xfrinfo) + { + /* It might be possible for no transfer to be in progress (callback == NULL + * and wdhwait == false) + */ + +#ifdef CONFIG_OHCI_ASYNCH + if (xfrinfo->callback || xfrinfo->wdhwait) +#else + if (xfrinfo->wdhwait) +#endif + { + /* Control endpoints should not come through this path and + * isochronous endpoints are not yet implemented. So we only have + * to distinguish bulk and interrupt endpoints. + */ + + if (ed->xfrtype == USB_EP_ATTR_XFER_BULK) + { + /* Disable bulk list processing while we modify the list */ + + ctrl = lpc54_getreg(LPC54_OHCI_CTRL); + lpc54_putreg(ctrl & ~OHCI_CTRL_BLE, LPC54_OHCI_CTRL); + + /* Remove the TDs attached to the ED, keeping the ED in the list */ + + td = (struct lpc54_gtd_s *)(ed->hw.headp & ED_HEADP_ADDR_MASK); + ed->hw.headp = LPC54_TDTAIL_ADDR; + ed->xfrinfo = NULL; + + /* Re-enable bulk list processing, if it was enabled before */ + + lpc54_putreg(0, LPC54_OHCI_BULKED); + lpc54_putreg(ctrl, LPC54_OHCI_CTRL); + } + else + { + /* Remove the TDs attached to the ED, keeping the Ed in the list */ + + td = (struct lpc54_gtd_s *)(ed->hw.headp & ED_HEADP_ADDR_MASK); + ed->hw.headp = LPC54_TDTAIL_ADDR; + ed->xfrinfo = NULL; + } + + /* Free all transfer descriptors that were connected to the ED. In + * some race conditions with the hardware, this might be none. + */ + + while (td != (struct lpc54_gtd_s *)LPC54_TDTAIL_ADDR) + { + next = (struct lpc54_gtd_s *)td->hw.nexttd; + lpc54_tdfree(td); + td = next; + } + + xfrinfo->tdstatus = TD_CC_USER; + + /* If there is a thread waiting for the transfer to complete, then + * wake up the thread. + */ + + if (xfrinfo->wdhwait) + { +#ifdef CONFIG_OHCI_ASYNCH + /* Yes.. there should not also be a callback scheduled */ + + DEBUGASSERT(xfrinfo->callback == NULL); +#endif + + /* Wake up the waiting thread */ + + lpc54_givesem(&ed->wdhsem); + xfrinfo->wdhwait = false; + + /* And free the transfer structure */ + + lpc54_free_xfrinfo(xfrinfo); + ed->xfrinfo = NULL; + } +#ifdef CONFIG_OHCI_ASYNCH + else + { + /* Otherwise, perform the callback and free the transfer structure */ + + lpc54_asynch_completion(priv, ed); + } +#endif + } + else + { + /* Just free the transfer structure */ + + lpc54_free_xfrinfo(xfrinfo); + ed->xfrinfo = NULL; + } + } + + /* Determine the return value */ + + leave_critical_section(flags); + return OK; +} + +/************************************************************************************ + * Name: lpc54_connect + * + * Description: + * New connections may be detected by an attached hub. This method is the + * mechanism that is used by the hub class to introduce a new connection + * and port description to the system. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the call to + * the class create() method. + * hport - The descriptor of the hub port that detected the connection + * related event + * connected - True: device connected; false: device disconnected + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure. + * + ************************************************************************************/ + +#ifdef CONFIG_OHCI_HUB +static int lpc54_connect(FAR struct usbhost_driver_s *drvr, + FAR struct usbhost_hubport_s *hport, + bool connected) +{ + struct lpc54_usbhost_s *priv = (struct lpc54_usbhost_s *)drvr; + DEBUGASSERT(priv != NULL && hport != NULL); + irqstate_t flags; + + /* Set the connected/disconnected flag */ + + hport->connected = connected; + uinfo("Hub port %d connected: %s\n", hport->port, connected ? "YES" : "NO"); + + /* Report the connection event */ + + flags = enter_critical_section(); + priv->hport = hport; + if (priv->pscwait) + { + priv->pscwait = false; + lpc54_givesem(&priv->pscsem); + } + + leave_critical_section(flags); + return OK; +} +#endif + +/**************************************************************************** + * Name: lpc54_disconnect + * + * Description: + * Called by the class when an error occurs and driver has been disconnected. + * The USB host driver should discard the handle to the class instance (it is + * stale) and not attempt any further interaction with the class driver instance + * (until a new instance is received from the create() method). The driver + * should not called the class' disconnected() method. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the call to + * the class create() method. + * hport - The port from which the device is being disconnected. Might be a port + * on a hub. + * + * Returned Value: + * None + * + * Assumptions: + * - Only a single class bound to a single device is supported. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static void lpc54_disconnect(struct usbhost_driver_s *drvr, + struct usbhost_hubport_s *hport) +{ + DEBUGASSERT(hport != NULL); + hport->devclass = NULL; +} + +/**************************************************************************** + * Initialization + ****************************************************************************/ +/**************************************************************************** + * Name: lpc54_ep0init + * + * Description: + * Initialize ED for EP0, add it to the control ED list, and enable control + * transfers. + * + * Input Parameters: + * priv - private driver state instance. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static inline void lpc54_ep0init(struct lpc54_usbhost_s *priv) +{ + /* Initialize the common tail TD. */ + + memset(TDTAIL, 0, sizeof(struct lpc54_gtd_s)); + TDTAIL->ed = EDCTRL; + + /* Link the common tail TD to the ED's TD list */ + + memset(EDCTRL, 0, sizeof(struct lpc54_ed_s)); + EDCTRL->hw.headp = (uint32_t)TDTAIL; + EDCTRL->hw.tailp = (uint32_t)TDTAIL; + EDCTRL->xfrtype = USB_EP_ATTR_XFER_CONTROL; + + /* Set the head of the control list to the NULL (for now). */ + + lpc54_putreg(0, LPC54_OHCI_CTRLHEADED); + + /* Then add EP0 to the empty Control List */ + + lpc54_addctrled(priv, EDCTRL); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: lpc54_usbhost_initialize + * + * Description: + * Initialize USB host device controller hardware. + * + * Input Parameters: + * controller -- If the device supports more than USB host controller, then + * this identifies which controller is being initialized. Normally, this + * is just zero. + * + * Returned Value: + * And instance of the USB host interface. The controlling task should + * use this interface to (1) call the wait() method to wait for a device + * to be connected, and (2) call the enumerate() method to bind the device + * to a class driver. + * + * Assumptions: + * - This function should called in the initialization sequence in order + * to initialize the USB device functionality. + * - Class drivers should be initialized prior to calling this function. + * Otherwise, there is a race condition if the device is already connected. + * + ****************************************************************************/ + +struct usbhost_connection_s *lpc54_usbhost_initialize(int controller) +{ + struct lpc54_usbhost_s *priv = &g_usbhost; + struct usbhost_driver_s *drvr; + struct usbhost_hubport_s *hport; + struct lpc54_xfrinfo_s *xfrinfo; + uint32_t regval; + uint8_t *buffer; + irqstate_t flags; + int i; + + /* Sanity checks. NOTE: If certain OS features are enabled, it may be + * necessary to increase the size of LPC54_ED/TD_SIZE in lpc54_ohciram.h + */ + + DEBUGASSERT(controller == 0); + DEBUGASSERT(sizeof(struct lpc54_ed_s) <= LPC54_ED_SIZE); + DEBUGASSERT(sizeof(struct lpc54_gtd_s) <= LPC54_TD_SIZE); + + /* Initialize the state data structure */ + /* Initialize the device operations */ + + drvr = &priv->drvr; + drvr->ep0configure = lpc54_ep0configure; + drvr->epalloc = lpc54_epalloc; + drvr->epfree = lpc54_epfree; + drvr->alloc = lpc54_alloc; + drvr->free = lpc54_free; + drvr->ioalloc = lpc54_ioalloc; + drvr->iofree = lpc54_iofree; + drvr->ctrlin = lpc54_ctrlin; + drvr->ctrlout = lpc54_ctrlout; + drvr->transfer = lpc54_transfer; +#ifdef CONFIG_OHCI_ASYNCH + drvr->asynch = lpc54_asynch; +#endif + drvr->cancel = lpc54_cancel; +#ifdef CONFIG_OHCI_HUB + drvr->connect = lpc54_connect; +#endif + drvr->disconnect = lpc54_disconnect; + + /* Initialize the public port representation */ + + hport = &priv->rhport.hport; + hport->drvr = drvr; +#ifdef CONFIG_OHCI_HUB + hport->parent = NULL; +#endif + hport->ep0 = EDCTRL; + hport->speed = USB_SPEED_FULL; + hport->funcaddr = 0; + + /* Initialize function address generation logic */ + + usbhost_devaddr_initialize(&priv->rhport); + + /* Initialize semaphores */ + + nxsem_init(&priv->pscsem, 0, 0); + nxsem_init(&priv->exclsem, 0, 1); + + /* The pscsem semaphore is used for signaling and, hence, should not have + * priority inheritance enabled. + */ + + nxsem_setprotocol(&priv->pscsem, SEM_PRIO_NONE); + +#ifndef CONFIG_OHCI_INT_DISABLE + priv->ininterval = MAX_PERINTERVAL; + priv->outinterval = MAX_PERINTERVAL; +#endif + /* Enable the USB0 OHCI block: + * + * Power: In the PDRUNCFG0 register, set bit PDEN_USB0_PHY. On reset, the + * USB block is disabled (PDEN_USB0_PHY = 1). + * Clock: To have the full-speed USB operating, select either the System + * PLL, or USB PLL, or FRO clock output as the USB0 clock and the clock + * must be 48 MHz. The CPU clock must be configured to a minimum + * frequency of 12 MHz. In AHBCLKCTRL2, enable both the USB0 host master + * and host slave bits. + * Port control: Clear DEV_ENABLE bit in Port Mode register to ensure that + * the port is controlled by the USB0 host block. Set ID_EN to enable ID + * pin pull-up. + * Configure GPIO pins. + * Reset: The USB0 Host AHB master and slave can be reset by toggling + * USB0HMR_RST (bit 16) and USB0HSL_RST (bit17) in PRESETCTRL2. + * Wake-up: Activity on the USB bus port can wake up the microcontroller + * from deep-sleep mode. + * Interrupts: The USB0_IRQ interrupt is connected to interrupt slot #28 + * in the NVIC. The USB0_NEEDCLK signal is connected to slot #27. + */ +#warning Missing logic + + /* Set the OTG status and control register. Bits 0:1 apparently mean: + * + * 00: U1=device, U2=host + * 01: U1=host, U2=host + * 10: reserved + * 11: U1=host, U2=device + * + * We need only select U1=host (Bit 0=1, Bit 1 is not used on LPC546x); + * NOTE: The PORTSEL clock needs to be enabled when accessing OTGSTCTRL + */ + + lpc54_putreg(1, LPC54_USBOTG_STCTRL); + + /* Now we can turn off the PORTSEL clock */ + + lpc54_putreg((LPC54_CLKCTRL_ENABLES & ~USBOTG_CLK_PORTSELCLK), LPC54_USBOTG_CLKCTRL); + + /* Configure I/O pins */ + + usbhost_dumpgpio(); + lpc54_configgpio(GPIO_USB_DP); /* Positive differential data */ + lpc54_configgpio(GPIO_USB_DM); /* Negative differential data */ + lpc54_configgpio(GPIO_USB_UPLED); /* GoodLink LED control signal */ + lpc54_configgpio(GPIO_USB_PPWR); /* Port Power enable signal for USB port */ + lpc54_configgpio(GPIO_USB_PWRD); /* Power Status for USB port (host power switch) */ + lpc54_configgpio(GPIO_USB_OVRCR); /* USB port Over-Current status */ + usbhost_dumpgpio(); + + uinfo("Initializing Host Stack\n"); + + /* Show AHB SRAM memory map */ + +#if 0 /* Useful if you have doubts about the layout */ + uinfo("AHB SRAM:\n"); + uinfo(" HCCA: %08x %d\n", LPC54_HCCA_BASE, LPC54_HCCA_SIZE); + uinfo(" TDTAIL: %08x %d\n", LPC54_TDTAIL_ADDR, LPC54_TD_SIZE); + uinfo(" EDCTRL: %08x %d\n", LPC54_EDCTRL_ADDR, LPC54_ED_SIZE); + uinfo(" EDFREE: %08x %d\n", LPC54_EDFREE_BASE, LPC54_ED_SIZE); + uinfo(" TDFREE: %08x %d\n", LPC54_TDFREE_BASE, LPC54_EDFREE_SIZE); + uinfo(" TBFREE: %08x %d\n", LPC54_TBFREE_BASE, LPC54_TBFREE_SIZE); + uinfo(" IOFREE: %08x %d\n", LPC54_IOFREE_BASE, LPC54_IOBUFFERS * CONFIG_LPC54_OHCI_IOBUFSIZE); +#endif + + /* Initialize all the TDs, EDs and HCCA to 0 */ + + memset((void *)HCCA, 0, sizeof(struct ohci_hcca_s)); + memset((void *)TDTAIL, 0, sizeof(struct ohci_gtd_s)); + memset((void *)EDCTRL, 0, sizeof(struct lpc54_ed_s)); + + /* The EDCTRL wdhsem semaphore is used for signaling and, hence, should + * not have priority inheritance enabled. + */ + + nxsem_init(&EDCTRL->wdhsem, 0, 0); + nxsem_setprotocol(&EDCTRL->wdhsem, SEM_PRIO_NONE); + + /* Initialize user-configurable EDs */ + + buffer = (uint8_t *)LPC54_EDFREE_BASE; + for (i = 0; i < CONFIG_LP17_OHCI_NEDS; i++) + { + /* Put the ED in a free list */ + + lpc54_edfree((struct lpc54_ed_s *)buffer); + buffer += LPC54_ED_SIZE; + } + + /* Initialize user-configurable TDs */ + + buffer = (uint8_t *)LPC54_TDFREE_BASE; + for (i = 0; i < CONFIG_LP17_OHCI_NTDS; i++) + { + /* Put the TD in a free list */ + + lpc54_tdfree((struct lpc54_gtd_s *)buffer); + buffer += LPC54_TD_SIZE; + } + + /* Initialize user-configurable request/descriptor transfer buffers */ + + buffer = (uint8_t *)LPC54_TBFREE_BASE; + for (i = 0; i < CONFIG_LPC54_OHCI_TDBUFFERS; i++) + { + /* Put the TD buffer in a free list */ + + lpc54_tbfree(buffer); + buffer += CONFIG_LPC54_OHCI_TDBUFSIZE; + } + +#if LPC54_IOBUFFERS > 0 + /* Initialize user-configurable IO buffers */ + + buffer = (uint8_t *)LPC54_IOFREE_BASE; + for (i = 0; i < LPC54_IOBUFFERS; i++) + { + /* Put the IO buffer in a free list */ + + lpc54_freeio(buffer); + buffer += CONFIG_LPC54_OHCI_IOBUFSIZE; + } +#endif + + /* Initialize transfer structures */ + + for (i = 0, xfrinfo = g_xfrbuffers; + i < CONFIG_LPC54_OHCI_NPREALLOC; + i++, xfrinfo++) + { + /* Put the transfer structure in a free list */ + + lpc54_free_xfrinfo(xfrinfo); + } + + /* Wait 50MS then perform hardware reset */ + + up_mdelay(50); + + lpc54_putreg(0, LPC54_OHCI_CTRL); /* Hardware reset */ + lpc54_putreg(0, LPC54_OHCI_CTRLHEADED); /* Initialize control list head to Zero */ + lpc54_putreg(0, LPC54_OHCI_BULKHEADED); /* Initialize bulk list head to Zero */ + + /* Software reset */ + + lpc54_putreg(OHCI_CMDST_HCR, LPC54_OHCI_CMDST); + + /* Write Fm interval (FI), largest data packet counter (FSMPS), and + * periodic start. + */ + + lpc54_putreg(DEFAULT_FMINTERVAL, LPC54_OHCI_FMINT); + lpc54_putreg(DEFAULT_PERSTART, LPC54_OHCI_PERSTART); + + /* Put HC in operational state */ + + regval = lpc54_getreg(LPC54_OHCI_CTRL); + regval &= ~OHCI_CTRL_HCFS_MASK; + regval |= OHCI_CTRL_HCFS_OPER; + lpc54_putreg(regval, LPC54_OHCI_CTRL); + + /* Set global power in HcRhStatus */ + + lpc54_putreg(OHCI_RHSTATUS_SGP, LPC54_OHCI_RHSTATUS); + + /* Set HCCA base address */ + + lpc54_putreg((uint32_t)HCCA, LPC54_OHCI_HCCA); + + /* Set up the root hub port EP0 */ + + lpc54_ep0init(priv); + + /* Clear pending interrupts */ + + regval = lpc54_getreg(LPC54_OHCI_INTST); + lpc54_putreg(regval, LPC54_OHCI_INTST); + + /* Enable OHCI interrupts */ + + lpc54_putreg((LPC54_ALL_INTS | OHCI_INT_MIE), LPC54_OHCI_INTEN); + + /* Attach USB host controller interrupt handler */ + + if (irq_attach(LPC54_IRQ_USB, lpc54_usbinterrupt, NULL) != 0) + { + uerr("ERROR: Failed to attach IRQ\n"); + return NULL; + } + + /* Enable USB interrupts at the SYCON controller. Disable interrupts + * because this register may be shared with other drivers. + */ + + flags = enter_critical_section(); + regval = lpc54_getreg(LPC54_SYSCON_USBINTST); + regval |= SYSCON_USBINTST_ENINTS; + lpc54_putreg(regval, LPC54_SYSCON_USBINTST); + leave_critical_section(flags); + + /* If there is a USB device in the slot at power up, then we will not + * get the status change interrupt to signal us that the device is + * connected. We need to set the initial connected state accordingly. + */ + + regval = lpc54_getreg(LPC54_OHCI_RHPORTST1); + priv->connected = ((regval & OHCI_RHPORTST_CCS) != 0); + + /* Enable interrupts at the interrupt controller */ + + up_enable_irq(LPC54_IRQ_USB); /* enable USB interrupt */ + uinfo("USB host Initialized, Device connected:%s\n", + priv->connected ? "YES" : "NO"); + + return &g_usbconn; +} diff --git a/arch/arm/src/lpc54xx/lpc54_usb0_ohci.h b/arch/arm/src/lpc54xx/lpc54_usb0_ohci.h new file mode 100644 index 0000000000..5efec074f9 --- /dev/null +++ b/arch/arm/src/lpc54xx/lpc54_usb0_ohci.h @@ -0,0 +1,108 @@ +/************************************************************************************ + * arch/arm/src/lpc17xx/lpc17_usbhost.h + * + * Copyright (C) 2013 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ************************************************************************************/ + +#ifndef __ARCH_ARM_SRC_LPC17XX_LPC17_USBHOST_H +#define __ARCH_ARM_SRC_LPC17XX_LPC17_USBHOST_H + +/************************************************************************************ + * Included Files + ************************************************************************************/ + +#include + +/************************************************************************************ + * Pre-processor Definitions + ************************************************************************************/ + +/************************************************************************************ + * Public Types + ************************************************************************************/ + +#ifndef __ASSEMBLY__ + +/************************************************************************************ + * Public Data + ************************************************************************************/ + +/************************************************************************************ + * Public Functions + ************************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: lpc17_usbhost_initialize + * + * Description: + * Initialize USB host device controller hardware. + * + * Input Parameters: + * controller -- If the device supports more than USB host controller, then + * this identifies which controller is being initializeed. Normally, this + * is just zero. + * + * Returned Value: + * And instance of the USB host interface. The controlling task should + * use this interface to (1) call the wait() method to wait for a device + * to be connected, and (2) call the enumerate() method to bind the device + * to a class driver. + * + * Assumptions: + * - This function should called in the initialization sequence in order + * to initialize the USB device functionality. + * - Class drivers should be initialized prior to calling this function. + * Otherwise, there is a race condition if the device is already connected. + * + ****************************************************************************/ + +#ifdef CONFIG_USBHOST +struct usbhost_connection_s; +FAR struct usbhost_connection_s *lpc17_usbhost_initialize(int controller); +#endif + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __ASSEMBLY__ */ +#endif /* __ARCH_ARM_SRC_LPC17XX_LPC17_USBHOST_H */ diff --git a/configs/lpcxpresso-lpc54628/twm4nx/defconfig b/configs/lpcxpresso-lpc54628/twm4nx/defconfig index 807f598c78..d2286d478b 100644 --- a/configs/lpcxpresso-lpc54628/twm4nx/defconfig +++ b/configs/lpcxpresso-lpc54628/twm4nx/defconfig @@ -23,6 +23,8 @@ CONFIG_FS_PROCFS=y CONFIG_FT5X06_POLLMODE=y CONFIG_FT5X06_SINGLEPOINT=y CONFIG_FT5X06_SWAPXY=y +CONFIG_FT5X06_THRESHX=8 +CONFIG_FT5X06_THRESHY=8 CONFIG_GRAPHICS_TWM4NX=y CONFIG_HAVE_CXX=y CONFIG_HAVE_CXXINITIALIZE=y