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