From 70e94e05ef4f7aed52722ea114dc59b517ec2695 Mon Sep 17 00:00:00 2001 From: Gregory Nutt Date: Tue, 20 Aug 2013 15:46:36 -0600 Subject: Beginning of support for SAMA5 EHCI. Not much there yet --- nuttx/ChangeLog | 4 + nuttx/arch/arm/src/sama5/Kconfig | 32 +- nuttx/arch/arm/src/sama5/chip/sam_ehci.h | 4 +- nuttx/arch/arm/src/sama5/chip/sam_pmc.h | 5 +- nuttx/arch/arm/src/sama5/sam_clockconfig.c | 47 +- nuttx/arch/arm/src/sama5/sam_ehci.c | 1626 ++++++++++++++++++++++ nuttx/arch/arm/src/sama5/sam_ohci.c | 2 +- nuttx/arch/arm/src/sama5/sam_usbhost.h | 4 +- nuttx/configs/sama5d3x-ek/include/board_384mhz.h | 6 +- nuttx/configs/sama5d3x-ek/include/board_396mhz.h | 32 +- 10 files changed, 1744 insertions(+), 18 deletions(-) create mode 100755 nuttx/arch/arm/src/sama5/sam_ehci.c diff --git a/nuttx/ChangeLog b/nuttx/ChangeLog index d12359631..c980ecc48 100644 --- a/nuttx/ChangeLog +++ b/nuttx/ChangeLog @@ -5421,3 +5421,7 @@ Add logic for management of device addresses. This logic does not currently hook into into anything. It will someday be a part of the NuttX USB hub implementation (2013-8-18). + * nuttx/arch/arm/src/sama5/sam_ehci.c and other files: Create a skeleton + environment for development of an EHCI driver. Not much in place yet + (2013-8-20). + diff --git a/nuttx/arch/arm/src/sama5/Kconfig b/nuttx/arch/arm/src/sama5/Kconfig index 085d2090a..0bd3ac4d4 100644 --- a/nuttx/arch/arm/src/sama5/Kconfig +++ b/nuttx/arch/arm/src/sama5/Kconfig @@ -377,8 +377,38 @@ config SAMA5_EHCI default n ---help--- Build support for the SAMA5 USB high speed Enhanced Host Controller - Interface (OHCI). + Interface (EHCI). + +if SAMA5_EHCI + +config SAMA5_EHCI_NQHS + int "Number of Queue Head (QH) structures" + default 4 + ---help--- + Configurable number of Queue Head (QH) structures. The default is + one per Root hub port plus one for EP0. + +config SAMA5_EHCI_NQTDS + int "Number of Queue Element Transfer Descriptor (qTDs)" + default 4 + ---help--- + Configurable number of Queue Element Transfer Descriptor (qTDs). + The default is one per root hub plus three from EP0. + +config SAMA5_EHCI_BUFSIZE + int "Size of one request/descriptor buffer" + default 128 + ---help--- + The size of one request/descriptor buffer in bytes. The TD buffe + size must be an even number of 32-bit words and must be large enough + to hangle the largest transfer via a SETUP request. + +config SAMA5_EHCI_REGDEBUG + bool "Enable low-level EHCI register debug" + default n + depends on DEBUG +endif # EHCI endmenu # USB High Speed Host driver option endif # SAMA5_UHPHS diff --git a/nuttx/arch/arm/src/sama5/chip/sam_ehci.h b/nuttx/arch/arm/src/sama5/chip/sam_ehci.h index 9d1282e30..6d6c801de 100644 --- a/nuttx/arch/arm/src/sama5/chip/sam_ehci.h +++ b/nuttx/arch/arm/src/sama5/chip/sam_ehci.h @@ -69,11 +69,11 @@ /* Host Controller Capability Registers */ -#define HCCR ((struct ehci_hccr_s *)SAM_UHPEHCI_VSECTION +#define HCCR ((struct ehci_hccr_s *)SAM_UHPEHCI_VSECTION) /* Host Controller Operational Registers */ -#define HCOR ((volatile struct ehci_hcor_s *)(SAM_UHPEHCI_VSECTION + 0x10) +#define HCOR ((volatile struct ehci_hcor_s *)(SAM_UHPEHCI_VSECTION + 0x10)) /**************************************************************************** * Public Types diff --git a/nuttx/arch/arm/src/sama5/chip/sam_pmc.h b/nuttx/arch/arm/src/sama5/chip/sam_pmc.h index 92d1701a6..53ae2092c 100644 --- a/nuttx/arch/arm/src/sama5/chip/sam_pmc.h +++ b/nuttx/arch/arm/src/sama5/chip/sam_pmc.h @@ -179,9 +179,11 @@ #define PMC_CKGR_UCKR_UPLLEN (1 << 16) /* Bit 16: UTMI PLL Enable */ #define PMC_CKGR_UCKR_UPLLCOUNT_SHIFT (20) /* Bits 20-23: UTMI PLL Start-up Time */ #define PMC_CKGR_UCKR_UPLLCOUNT_MASK (15 << PMC_CKGR_UCKR_UPLLCOUNT_SHIFT) +# define PMC_CKGR_UCKR_UPLLCOUNT(n) ((n) << PMC_CKGR_UCKR_UPLLCOUNT_SHIFT) #define PMC_CKGR_UCKR_BIASEN (1 << 24) /* Bit 24: UTMI BIAS Enable */ #define PMC_CKGR_UCKR_BIASCOUNT_SHIFT (28) /* Bits 28-31: UTMI BIAS Start-up Time */ -#define PMC_CKGR_UCKR_BIASCOUNT_MASK (15 << PMC_CKGR_UCKR_BIASCOUNT_SHIFT) */ +#define PMC_CKGR_UCKR_BIASCOUNT_MASK (15 << PMC_CKGR_UCKR_BIASCOUNT_SHIFT) +# define PMC_CKGR_UCKR_BIASCOUNT(n) ((n) << PMC_CKGR_UCKR_BIASCOUNT_SHIFT) /* PMC Clock Generator Main Oscillator Register */ @@ -251,6 +253,7 @@ # define PMC_USB_USBS_UPLL PMC_USB_USBS #define PMC_USB_USBDIV_SHIFT (8) /* Bits 8-11: Divider for USB Clock */ #define PMC_USB_USBDIV_MASK (15 << PMC_USB_USBDIV_SHIFT) +# define PMC_USB_USBDIV(a) ((a) << PMC_USB_USBDIV_SHIFT) /* Soft Modem Clock Register */ diff --git a/nuttx/arch/arm/src/sama5/sam_clockconfig.c b/nuttx/arch/arm/src/sama5/sam_clockconfig.c index 0dc6884cd..42bdbe3e4 100644 --- a/nuttx/arch/arm/src/sama5/sam_clockconfig.c +++ b/nuttx/arch/arm/src/sama5/sam_clockconfig.c @@ -352,7 +352,15 @@ static inline void sam_usbclockconfig(void) #endif #ifdef CONFIG_SAMA5_EHCI - /* For High-speed operations, the user has to perform the following: + uint32_t regval; + + /* The USB Host High Speed requires a 480 MHz clock (UPLLCK) for the + * embedded High-speed transceivers. UPLLCK is the output of the 480 MHz + * UTMI PLL (UPLL). The source clock of the UTMI PLL is the Main OSC output: + * Either the 12MHz internal oscillator on a 12MHz crystal. The Main OSC + * must be 12MHz because the UPLL has a built-in 40x multiplier. + * + * For High-speed operations, the user has to perform the following: * * 1) Enable UHP peripheral clock, bit (1 << AT91C_ID_UHPHS) in * PMC_PCER register. @@ -371,19 +379,42 @@ static inline void sam_usbclockconfig(void) * driver is initialized. */ -# warning Missing logic -#endif + /* 2) Write CKGR_PLLCOUNT field in PMC_UCKR register. */ -#if 0 // #ifdef CONFIG_USBDEV - uint32_t regval; + regval = PMC_CKGR_UCKR_UPLLCOUNT(BOARD_CKGR_UCKR_UPLLCOUNT); + putreg32(regval, SAM_PMC_CKGR_UCKR); - /* Setup UTMI for USB and wait for LOCKU */ + /* 3) Enable UPLL, bit AT91C_CKGR_UPLLEN in PMC_UCKR register. */ - regval = getreg32(SAM_PMC_CKGR_UCKR); - regval |= (BOARD_CKGR_UCKR_UPLLCOUNT | PMC_CKGR_UCKR_UPLLEN); + regval |= PMC_CKGR_UCKR_UPLLEN; putreg32(regval, SAM_PMC_CKGR_UCKR); + /* 4) Wait until UTMI_PLL is locked. LOCKU bit in PMC_SR register */ + sam_pmcwait(PMC_INT_LOCKU); + + /* 5) Enable BIAS, bit AT91C_CKGR_BIASEN in PMC_UCKR register. */ + + regval |= PMC_CKGR_UCKR_BIASCOUNT(BOARD_CKGR_UCKR_BIASCOUNT); + putreg32(regval, SAM_PMC_CKGR_UCKR); + + regval |= PMC_CKGR_UCKR_BIASEN; + putreg32(regval, SAM_PMC_CKGR_UCKR); + + /* 6) Select UPLLCK as Input clock of OHCI part, USBS bit in PMC_USB + * register. + */ + + regval = PMC_USB_USBS_UPLL; + putreg32(regval, SAM_PMC_USB); + + /* 7) Program the OHCI clocks (UHP48M and UHP12M) with USBDIV field in + * PMC_USB register. USBDIV must be 9 (division by 10) if UPLLCK is + * selected. + */ + + regval |= PMC_USB_USBDIV(9); + putreg32(regval, SAM_PMC_USB); #endif } diff --git a/nuttx/arch/arm/src/sama5/sam_ehci.c b/nuttx/arch/arm/src/sama5/sam_ehci.c new file mode 100755 index 000000000..cd8657af9 --- /dev/null +++ b/nuttx/arch/arm/src/sama5/sam_ehci.c @@ -0,0 +1,1626 @@ +/******************************************************************************* + * arch/arm/src/sama5/sam_ehci.c + * + * Copyright (C) 2013 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 "up_arch.h" +#include "sam_periphclks.h" +#include "sam_usbhost.h" +#include "chip/sam_ehci.h" + +#ifdef CONFIG_SAMA5_EHCI + +/******************************************************************************* + * Pre-processor Definitions + *******************************************************************************/ +/* Configuration ***************************************************************/ + +/* Configurable number of Queue Head (QH) structures. The default is one per + * Root hub port plus one for EP0. + */ + +#ifndef CONFIG_SAMA5_EHCI_NQHS +# define CONFIG_SAMA5_EHCI_NQHS (SAM_EHCI_NRHPORT + 1) +#endif + +/* Configurable number of Queue Element Transfer Descriptor (qTDs). The default + * is one per root hub plus three from EP0. + */ + +#ifndef CONFIG_SAMA5_EHCI_NQTDS +# define CONFIG_SAMA5_EHCI_NQTDS (SAM_EHCI_NRHPORT + 3) +#endif + +/* Configurable size of a request/descriptor buffers */ + +#ifndef CONFIG_SAMA5_EHCI_BUFSIZE +# define CONFIG_SAMA5_EHCI_BUFSIZE 128 +#endif + +/* Debug options */ + +#ifndef CONFIG_DEBUG +# undef CONFIG_SAMA5_EHCI_REGDEBUG +#endif + +/******************************************************************************* + * Private Types + *******************************************************************************/ +/* Internal representation of the EHCI Queue Head (QH) */ + +struct sam_qh_s +{ + /* Fields visible to hardware */ + + struct ehci_qh_s hw; /* Hardware representation of the queue head */ + + /* Internal fields used by the EHCI driver */ + + uint32_t pad[16]; /* Padding to assure 32-byte alignment */ +}; + +/* Internal representation of the EHCI Queue Element Transfer Descriptor (qTD) */ + +struct sam_qtd_s +{ + /* Fields visible to hardware */ + + struct ehci_qtd_s hw; /* Hardware representation of the queue head */ + + /* Internal fields used by the EHCI driver */ +}; + +/* The following is used to manage lists of free QHs and qTDs */ + +struct sam_list_s +{ + struct sam_list_s *flink; /* Link to next entry in the list */ + /* Variable length entry data follows */ +}; + +/* This structure describes one endpoint. */ + +struct sam_epinfo_s +{ + uint8_t epno; /* Endpoint number */ + uint8_t devaddr; /* Device address */ + uint8_t xfrtype; /* See USB_EP_ATTR_XFER_* definitions in usb.h */ + uint8_t speed; /* See USB_*_SPEED definitions in ehci.h */ + uint8_t flags; /* See EPINFO_FLAG_* definitions above */ + volatile bool wait; /* TRUE: Thread is waiting for transfer completion */ + uint16_t maxpacket; /* Maximum packet size */ + sem_t wsem; /* Semaphore used to wait for transfer completion */ +}; + +/* This structure retains the state of one root hub port */ + +struct sam_rhport_s +{ + /* Common device fields. This must be the first thing defined in the + * structure so that it is possible to simply cast from struct usbhost_s + * to struct sam_rhport_s. + */ + + struct usbhost_driver_s drvr; + + /* Root hub port status */ + + volatile bool connected; /* Connected to device */ + volatile bool lowspeed; /* Low speed device attached */ + uint8_t rhpndx; /* Root hub port index */ + + /* The bound device class driver */ + + struct usbhost_class_s *class; +}; + +/* This structure retains the overall state of the USB host controller */ + +struct sam_ehci_s +{ + volatile bool rhwait; /* TRUE: Thread is waiting for root hub event */ + sem_t exclsem; /* Support mutually exclusive access */ + sem_t rhsem; /* Semaphore to wait for root hub events */ + + struct sam_epinfo_s ep0; /* Endpoint 0 */ + struct sam_list_s *qhfree; /* List of free Queue Head (QH) structures */ + struct sam_list_s *qtdfree; /* List of free Queue Element Transfer Descriptor (qTD) */ + + /* Root hub ports */ + + struct sam_rhport_s rhport[SAM_EHCI_NRHPORT]; +}; + +/******************************************************************************* + * Private Function Prototypes + *******************************************************************************/ + +/* Register operations ********************************************************/ + +#ifdef CONFIG_ENDIAN_BIG +static uint16_t sam_read16(volatile uint16_t *addr); +static uint32_t sam_read32(volatile uint32_t *addr); +static void sam_write16(uint16_t memval, volatile uint16_t *addr); +static void sam_write32(uint32_t memval, volatile uint32_t *addr); +#else +static inline uint16_t sam_read16(volatile uint16_t *addr); +static inline uint32_t sam_read32(volatile uint32_t *addr); +static inline void sam_write16(uint16_t memval, volatile uint16_t *addr); +static inline void sam_write32(uint32_t memval, volatile uint32_t *addr); +#endif + +#ifdef CONFIG_SAMA5_EHCI_REGDEBUG +static void sam_printreg(volatile uint32_t *regaddr, uint32_t regval, + bool iswrite); +static void sam_checkreg(volatile uint32_t *regaddr, uint32_t regval, + bool iswrite); +static uint32_t sam_getreg(volatile uint32_t *regaddr); +static void sam_putreg(uint32_t regval, volatile uint32_t *regaddr); +#else +static inline uint32_t sam_getreg(volatile uint32_t *regaddr); +static inline void sam_putreg(uint32_t regval, volatile uint32_t *regaddr); +#endif +static int ehci_wait_usbsts(uint32_t maskbits, uint32_t donebits, + unsigned int delay); + +/* Semaphores ******************************************************************/ + +static void sam_takesem(sem_t *sem); +#define sam_givesem(s) sem_post(s); + +/* Allocators ******************************************************************/ + +static struct sam_qh_s *sam_qhalloc(void); +static void sam_qhfree(struct sam_qh_s *qh); +static struct sam_qtd_s *sam_qtdalloc(void); +static void sam_qtdfree(struct sam_qtd_s *qtd); + +/* Interrupt Handling **********************************************************/ + +static int sam_ehci_interrupt(int irq, FAR void *context); + +/* USB Host Controller Operations **********************************************/ + +static int sam_wait(FAR struct usbhost_connection_s *conn, + FAR const bool *connected); +static int sam_enumerate(FAR struct usbhost_connection_s *conn, int rhpndx); + +static int sam_ep0configure(FAR struct usbhost_driver_s *drvr, uint8_t funcaddr, + uint16_t maxpacketsize); +static int sam_epalloc(FAR struct usbhost_driver_s *drvr, + const FAR struct usbhost_epdesc_s *epdesc, usbhost_ep_t *ep); +static int sam_epfree(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep); +static int sam_alloc(FAR struct usbhost_driver_s *drvr, + FAR uint8_t **buffer, FAR size_t *maxlen); +static int sam_free(FAR struct usbhost_driver_s *drvr, FAR uint8_t *buffer); +static int sam_ioalloc(FAR struct usbhost_driver_s *drvr, + FAR uint8_t **buffer, size_t buflen); +static int sam_iofree(FAR struct usbhost_driver_s *drvr, FAR uint8_t *buffer); +static int sam_ctrlin(FAR struct usbhost_driver_s *drvr, + FAR const struct usb_ctrlreq_s *req, FAR uint8_t *buffer); +static int sam_ctrlout(FAR struct usbhost_driver_s *drvr, + FAR const struct usb_ctrlreq_s *req, FAR const uint8_t *buffer); +static int sam_transfer(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep, + FAR uint8_t *buffer, size_t buflen); +static void sam_disconnect(FAR struct usbhost_driver_s *drvr); + +/* Initialization **************************************************************/ + +static int sam_reset(void); + +/******************************************************************************* + * 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 sam_ehci_s g_ehci; + +/* This is the connection/enumeration interface */ + +static struct usbhost_connection_s g_ehciconn; + +/* Pools of pre-allocated data structures. These will all be linked into the + * free lists within g_ehci. These must all be aligned to 32-byte boundaries + */ + +/* Queue Head (QH) pool */ + +static struct sam_qh_s g_ghpool[CONFIG_SAMA5_EHCI_NQHS] + __attribute__ ((aligned(32))); + +/* Queue Element Transfer Descriptor (qTD) pool */ + +static struct sam_qtd_s g_qtdpool[CONFIG_SAMA5_EHCI_NQTDS] + __attribute__ ((aligned(32))); + +/******************************************************************************* + * Private Functions + *******************************************************************************/ +/******************************************************************************* + * Register Operations + *******************************************************************************/ +/******************************************************************************* + * Name: sam_read16 + * + * Description: + * Read 16-bit little endian data + * + *******************************************************************************/ + +#ifdef CONFIG_ENDIAN_BIG +static uint16_t sam_read16(volatile uint16_t *addr) +{ + uint8_t *addr8 = (uint8_t *)addr; + + return (uint16_t)addr8[1] << 8 | (uint16_t)addr[0]; +} +#else +static inline uint16_t sam_read16(volatile uint16_t *addr) +{ + return *addr; +} +#endif + +/******************************************************************************* + * Name: sam_read32 + * + * Description: + * Read 32-bit little endian data + * + *******************************************************************************/ + +#ifdef CONFIG_ENDIAN_BIG +static uint32_t sam_read32(volatile uint32_t *addr) +{ + uint16_t *addr16 = (uint16_t *)addr; + + return (uint32_t)sam_read16(&addr16[1]) << 16 | + (uint32_t)sam_read16(&addr16[0]); +} +#else +static inline uint32_t sam_read32(volatile uint32_t *addr) +{ + return *addr; +} +#endif + +/******************************************************************************* + * Name: sam_write16 + * + * Description: + * Write 16-bit little endian data + * + *******************************************************************************/ + +#ifdef CONFIG_ENDIAN_BIG +static void sam_write16(uint16_t memval, volatile uint16_t *addr) +{ + volatile uint8_t *addr8 = (uint8_t *)addr; + + addr8[0] = memval & 0xff; + addr8[1] = memval >> 8; +} +#else +static inline void sam_write16(uint16_t memval, volatile uint16_t *addr) +{ + *addr = memval; +} +#endif + +/******************************************************************************* + * Name: sam_write32 + * + * Description: + * Write 32-bit little endian data + * + *******************************************************************************/ + +#ifdef CONFIG_ENDIAN_BIG +static void sam_write32(uint32_t memval, volatile uint32_t *addr) +{ + volatile uint16_t *addr16 = (uint16_t *)addr; + + sam_write16(memval & 0xffff, &add16[0]); + sam_write16(memval >> 16, &add16[1]); +} +#else +static inline void sam_write32(uint32_t memval, volatile uint32_t *addr) +{ + *addr = memval; +} +#endif + +/******************************************************************************* + * Name: sam_printreg + * + * Description: + * Print the contents of a SAMA5 EHCI register + * + *******************************************************************************/ + +#ifdef CONFIG_SAMA5_EHCI_REGDEBUG +static void sam_printreg(volatile uint32_t *regaddr, uint32_t regval, + bool iswrite) +{ + lldbg("%p%s%08x\n", regaddr, iswrite ? "<-" : "->", regval); +} +#endif + +/******************************************************************************* + * Name: sam_checkreg + * + * Description: + * Check if it is time to output debug information for accesses to a SAMA5 + * EHCI register + * + *******************************************************************************/ + +#ifdef CONFIG_SAMA5_EHCI_REGDEBUG +static void sam_checkreg(volatile uint32_t *regaddr, uint32_t regval, bool iswrite) +{ + static uint32_t *prevaddr = NULL; + static uint32_t preval = 0; + static uint32_t count = 0; + static bool prevwrite = false; + + /* Is this the same value that we read from/wrote to the same register last time? + * Are we polling the register? If so, suppress the output. + */ + + if (regaddr == prevaddr && regval == preval && prevwrite == iswrite) + { + /* Yes.. Just increment the count */ + + count++; + } + else + { + /* No this is a new address or value or operation. Were there any + * duplicate accesses before this one? + */ + + if (count > 0) + { + /* Yes.. Just one? */ + + if (count == 1) + { + /* Yes.. Just one */ + + sam_printreg(prevaddr, preval, prevwrite); + } + else + { + /* No.. More than one. */ + + lldbg("[repeats %d more times]\n", count); + } + } + + /* Save the new address, value, count, and operation for next time */ + + prevaddr = (uint32_t *)regaddr; + preval = regval; + count = 0; + prevwrite = iswrite; + + /* Show the new register access */ + + sam_printreg(regaddr, regval, iswrite); + } +} +#endif + +/******************************************************************************* + * Name: sam_getreg + * + * Description: + * Get the contents of an SAMA5 register + * + *******************************************************************************/ + +#ifdef CONFIG_SAMA5_EHCI_REGDEBUG +static uint32_t sam_getreg(volatile uint32_t *regaddr) +{ + /* Read the value from the register */ + + uint32_t regval = *regaddr; + + /* Check if we need to print this value */ + + sam_checkreg(regaddr, regval, false); + return regval; +} +#else +static inline uint32_t sam_getreg(volatile uint32_t *regaddr) +{ + return *regaddr; +} +#endif + +/******************************************************************************* + * Name: sam_putreg + * + * Description: + * Set the contents of an SAMA5 register to a value + * + *******************************************************************************/ + +#ifdef CONFIG_SAMA5_EHCI_REGDEBUG +static void sam_putreg(uint32_t regval, volatile uint32_t *regaddr) +{ + /* Check if we need to print this value */ + + sam_checkreg(regaddr, regval, true); + + /* Write the value */ + + *regaddr = regval; +} +#else +static inline void sam_putreg(uint32_t regval, volatile uint32_t *regaddr) +{ + *regaval = regval; +} +#endif + +/******************************************************************************* + * Name: ehci_wait_usbsts + * + * Description: + * Wait for either (1) a field in the USBSTS register to take a specific + * value, (2) for a timeout to occur, or (3) a error to occur. Return + * a value to indicate which terminated the wait. + * + *******************************************************************************/ + +static int ehci_wait_usbsts(uint32_t maskbits, uint32_t donebits, + unsigned int delay) +{ + uint32_t regval; + unsigned int timeout; + + timeout = 0; + do + { + /* Wait 5usec before trying again */ + + up_udelay(5); + timeout += 5; + + /* Read the USBSTS register and check for a system error */ + + regval = sam_getreg(&HCOR->usbsts); + if ((regval & EHCI_INT_SYSERROR) != 0) + { + udbg("ERROR: System error: 0x%08X", regval); + return -EIO; + } + + /* Mask out the bits of interest */ + + regval &= maskbits; + + /* Loop until the masked bits take the specified value or until a + * timeout occurs. + */ + } + while (regval != donebits && timeout < delay); + + /* We got here because either the waited for condition or a timeout + * occurred. Return a value to indicate which. + */ + + return (regval == donebits) ? OK : -ETIMEDOUT; +} + +/******************************************************************************* + * Semaphores + *******************************************************************************/ +/******************************************************************************* + * Name: sam_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 sam_takesem(sem_t *sem) +{ + /* Take the semaphore (perhaps waiting) */ + + while (sem_wait(sem) != 0) + { + /* The only case that an error should occr here is if the wait was + * awakened by a signal. + */ + + ASSERT(errno == EINTR); + } +} + +/******************************************************************************* + * Allocators + *******************************************************************************/ +/******************************************************************************* + * Name: sam_qhalloc + * + * Description: + * Allocate a Queue Head (QH) structure by removing it from the free list + * + *******************************************************************************/ + +static struct sam_qh_s *sam_qhalloc(void) +{ + struct sam_qh_s *qh; + + /* Remove the QH structure from the freelist */ + + qh = (struct sam_qh_s *)g_ehci.qhfree; + if (qh) + { + g_ehci.qhfree = ((struct sam_list_s *)qh)->flink; + memset(qh, 0, sizeof(struct sam_qh_s)); + } + + return qh; +} + +/******************************************************************************* + * Name: sam_qhfree + * + * Description: + * Free a Queue Head (QH) structure by returning it to the free list + * + *******************************************************************************/ + +static void sam_qhfree(struct sam_qh_s *qh) +{ + struct sam_list_s *entry = (struct sam_list_s *)qh; + + /* Put the QH structure back into the free list */ + + entry->flink = g_ehci.qhfree; + g_ehci.qhfree = entry; +} + +/******************************************************************************* + * Name: sam_qtdalloc + * + * Description: + * Allocate a Queue Element Transfer Descriptor (qTD) by removing it from the + * free list + * + *******************************************************************************/ + +static struct sam_qtd_s *sam_qtdalloc(void) +{ + struct sam_qtd_s *qtd; + + /* Remove the qTD from the freelist */ + + qtd = (struct sam_qtd_s *)g_ehci.qtdfree; + if (qtd) + { + g_ehci.qtdfree = ((struct sam_list_s *)qtd)->flink; + memset(qtd, 0, sizeof(struct sam_list_s)); + } + + return qtd; +} + +/******************************************************************************* + * Name: sam_edfree + * + * Description: + * Free a Queue Element Transfer Descriptor (qTD) by returning it to the free + * list + * + *******************************************************************************/ + +static void sam_qtdfree(struct sam_qtd_s *qtd) +{ + struct sam_list_s *entry = (struct sam_list_s *)qtd; + + /* Put the qTD back into the free list */ + + entry->flink = g_ehci.qtdfree; + g_ehci.qtdfree = entry; +} + +/******************************************************************************* + * EHCI Interrupt Handling + *******************************************************************************/ + +/******************************************************************************* + * Name: sam_ehci_interrupt + * + * Description: + * OHCI interrupt handler + * + *******************************************************************************/ + +static int sam_ehci_interrupt(int irq, FAR void *context) +{ +#warning "Missing logic" + return OK; +} + +/******************************************************************************* + * USB Host Controller Operations + *******************************************************************************/ +/******************************************************************************* + * Name: sam_wait + * + * Description: + * Wait for a device to be connected or disconnected to/from a root hub port. + * + * Input Parameters: + * conn - The USB host connection instance obtained as a parameter from the call to + * the USB driver initialization logic. + * connected - A pointer to an array of 3 boolean values corresponding to + * root hubs 1, 2, and 3. For each boolean value: TRUE: Wait for a device + * to be connected on the root hub; FALSE: wait for device to be + * disconnected from the root hub. + * + * Returned Values: + * And index [0, 1, or 2} corresponding to the root hub port number {1, 2, + * or 3} is returned when a device is connected or disconnected. This + * function will not return until either (1) a device is connected or + * disconnected to/from any root 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 sam_wait(FAR struct usbhost_connection_s *conn, + FAR const bool *connected) +{ + irqstate_t flags; + int rhpndx; + + /* Loop until a change in the connection state changes on one of the root hub + * ports or until an error occurs. + */ + + flags = irqsave(); + for (;;) + { + /* Check for a change in the connection state on any root hub port */ + + for (rhpndx = 0; rhpndx < SAM_EHCI_NRHPORT; rhpndx++) + { + /* Has the connection state changed on the RH port? */ + + if (g_ehci.rhport[rhpndx].connected != connected[rhpndx]) + { + /* Yes.. Return the RH port number */ + + irqrestore(flags); + + udbg("RHPort%d connected: %s\n", + rhpndx + 1, g_ehci.rhport[rhpndx].connected ? "YES" : "NO"); + + return rhpndx; + } + } + + /* No changes on any port. Wait for a connection/disconnection event + * and check again + */ + + g_ehci.rhwait = true; + sam_takesem(&g_ehci.rhsem); + } +} + +/******************************************************************************* + * Name: sam_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 configdesc() 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. + * rphndx - Root hub port index. 0-(n-1) corresponds to root hub port 1-n. + * + * Returned Values: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure + * + * Assumptions: + * - Only a single class bound to a single device is supported. + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + *******************************************************************************/ + +static int sam_enumerate(FAR struct usbhost_connection_s *conn, int rhpndx) +{ + struct sam_rhport_s *rhport; + + DEBUGASSERT(rhpndx >= 0 && rhpndx < SAM_EHCI_NRHPORT); + rhport = &g_ehci.rhport[rhpndx]; + + /* Are we connected to a device? The caller should have called the wait() + * method first to be assured that a device is connected. + */ + + while (!rhport->connected) + { + /* No, return an error */ + + udbg("Not connected\n"); + return -ENODEV; + } + + /* Add EP0 to the control list */ +#warning Missing logic + + /* USB 2.0 spec says at least 50ms delay before port reset */ + + up_mdelay(100); + + /* Put the root hub port in reset (the SAMA5 supports three downstream ports) */ +#warning Missing logic + + /* Wait for the port reset to complete */ +#warning Missing logic + + /* Release RH port 1 from reset and wait a bit */ +#warning Missing logic + + up_mdelay(200); + + /* Let the common usbhost_enumerate do all of the real work. Note that the + * FunctionAddress (USB address) is set to the root hub port number for now. + */ + + uvdbg("Enumerate the device\n"); + return usbhost_enumerate(&g_ehci.rhport[rhpndx].drvr, rhpndx+1, &rhport->class); +} + +/************************************************************************************ + * Name: sam_ep0configure + * + * Description: + * Configure endpoint 0. This method is normally used internally by the + * enumerate() method but is made available at the interface to support + * an external implementation of the enumeration logic. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the call to + * the class create() method. + * funcaddr - The USB address of the function containing the endpoint that EP0 + * controls. A funcaddr of zero will be received if no address is yet assigned + * to the device. + * maxpacketsize - The maximum number of bytes that can be sent to or + * received from the endpoint in a single data packet + * + * Returned Values: + * 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 sam_ep0configure(FAR struct usbhost_driver_s *drvr, uint8_t funcaddr, + uint16_t maxpacketsize) +{ + struct sam_rhport_s *rhport = (struct sam_rhport_s *)drvr; + + DEBUGASSERT(rhport && + funcaddr >= 0 && funcaddr <= SAM_EHCI_NRHPORT && + maxpacketsize < 2048); + + /* We must have exclusive access to the EHCI data structures. */ + + sam_takesem(&g_ehci.exclsem); + +#warning Missing logic + + sam_givesem(&g_ehci.exclsem); + return -ENOSYS; +} + +/************************************************************************************ + * Name: sam_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 desciptor. + * + * Returned Values: + * 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 sam_epalloc(FAR struct usbhost_driver_s *drvr, + const FAR struct usbhost_epdesc_s *epdesc, usbhost_ep_t *ep) +{ + struct sam_rhport_s *rhport = (struct sam_rhport_s *)drvr; + struct sam_epinfo_s *epinfo; + 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(rhport && epdesc && ep && rhport->connected); + + /* Allocate a container for the endpoint data */ + + epinfo = (struct sam_epinfo_s *)kzalloc(sizeof(struct sam_epinfo_s)); + if (!epinfo) + { + udbg("ERROR: Failed to allocate EP info structure\n"); + goto errout; + } + + /* Initialize the endpoint container */ + + sem_init(&epinfo->wsem, 0, 0); + + /* We must have exclusive access to the EHCI data structures. */ + + sam_takesem(&g_ehci.exclsem); + +#warning Missing logic + + /* Success.. return an opaque reference to the endpoint list container */ + + *ep = (usbhost_ep_t)epinfo; + sam_givesem(&g_ehci.exclsem); + return OK; + +errout_with_semaphore: + sam_givesem(&g_ehci.exclsem); + kfree(epinfo); +errout: + return ret; +} + +/************************************************************************************ + * Name: sam_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 Values: + * 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 sam_epfree(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep) +{ + struct sam_rhport_s *rhport = (struct sam_rhport_s *)drvr; + struct sam_epinfo_s *epinfo = (struct sam_epinfo_s *)ep; + int ret; + + DEBUGASSERT(rhport && epinfo); + + /* There should not be any pending, transfers */ +#warning Missing logic + + /* We must have exclusive access to the EHCI data structures. */ + + sam_takesem(&g_ehci.exclsem); + +#warning Missing logic + + /* And free the container */ + + sem_destroy(&epinfo->wsem); + kfree(epinfo); + sam_givesem(&g_ehci.exclsem); + return ret; +} + +/******************************************************************************* + * Name: sam_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 kmalloc. + * + * 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 Values: + * 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 sam_alloc(FAR struct usbhost_driver_s *drvr, + FAR uint8_t **buffer, FAR size_t *maxlen) +{ + int ret = -ENOMEM; + DEBUGASSERT(drvr && buffer && maxlen); + + /* There is no special requirements for transfer/descriptor buffers. */ + + *buffer = (FAR uint8_t *)kmalloc(CONFIG_SAMA5_EHCI_BUFSIZE); + if (*buffer) + { + *maxlen = CONFIG_SAMA5_EHCI_BUFSIZE; + ret = OK; + } + + return ret; +} + +/******************************************************************************* + * Name: sam_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 kfree(). + * + * 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 Values: + * 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 sam_free(FAR struct usbhost_driver_s *drvr, FAR uint8_t *buffer) +{ + DEBUGASSERT(drvr && buffer); + + /* No special action is require to free the transfer/descriptor buffer memory */ + + kfree(buffer); + return OK; +} + +/************************************************************************************ + * Name: sam_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 kumalloc. + * + * 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 Values: + * 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 sam_ioalloc(FAR struct usbhost_driver_s *drvr, FAR uint8_t **buffer, + size_t buflen) +{ + DEBUGASSERT(drvr && buffer && buflen > 0); + + /* The only special requirements for I/O buffers are they might need to be user + * accessible (depending on how the class driver implements its buffering). + */ + + *buffer = (FAR uint8_t *)kumalloc(buflen); + return *buffer ? OK : -ENOMEM; +} + +/************************************************************************************ + * Name: sam_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 kufree(). + * + * 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 Values: + * 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 sam_iofree(FAR struct usbhost_driver_s *drvr, FAR uint8_t *buffer) +{ + DEBUGASSERT(drvr && buffer); + + /* No special action is require to free the I/O buffer memory */ + + kufree(buffer); + return OK; +} + +/******************************************************************************* + * Name: sam_ctrlin and sam_ctrlout + * + * Description: + * Process a IN or OUT request on the control endpoint. These methods + * will enqueue the request and wait for it to complete. Only one transfer may + * be queued; Neither these methods nor the transfer() method can be called + * again until the control transfer functions returns. + * + * These are blocking methods; these functions will not return until the + * control transfer has completed. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the call to + * the class create() method. + * 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 Values: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure + * + * Assumptions: + * - Only a single class bound to a single device is supported. + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + *******************************************************************************/ + +static int sam_ctrlin(FAR struct usbhost_driver_s *drvr, + FAR const struct usb_ctrlreq_s *req, + FAR uint8_t *buffer) +{ + struct sam_rhport_s *rhport = (struct sam_rhport_s *)drvr; + uint16_t len; + int ret; + + DEBUGASSERT(rhport && req); + + len = sam_read16((uint16_t*)req->len); + uvdbg("RHPort%d type: %02x req: %02x value: %02x%02x index: %02x%02x len: %04x\n", + rhport->rhpndx + 1, req->type, req->req, req->value[1], req->value[0], + req->index[1], req->index[0], len); + + /* We must have exclusive access to the EHCI hardware and data structures. */ + + sam_takesem(&g_ehci.exclsem); + + /* Now perform the transfer */ +#warning Missing logic + ret = -ENOSYS; + + sam_givesem(&g_ehci.exclsem); + return ret; +} + +static int sam_ctrlout(FAR struct usbhost_driver_s *drvr, + FAR const struct usb_ctrlreq_s *req, + FAR const uint8_t *buffer) +{ + /* sam_ctrlin can handle both directions. We just need to work around the + * differences in the function signatures. + */ + + return sam_ctrlin(drvr, req, (uint8_t *)buffer); +} + +/******************************************************************************* + * Name: sam_transfer + * + * Description: + * Process a request to handle a transfer descriptor. This method will + * enqueue the transfer request and return immediately. Only one transfer may be + * queued;. + * + * 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 Values: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating 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: + * - Only a single class bound to a single device is supported. + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + *******************************************************************************/ + +static int sam_transfer(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep, + FAR uint8_t *buffer, size_t buflen) +{ + struct sam_rhport_s *rhport = (struct sam_rhport_s *)drvr; + struct sam_epinfo_s *epinfo = (struct sam_epinfo_s *)ep; + int ret; + + DEBUGASSERT(rhport && epinfo && buffer && buflen > 0); + + /* We must have exclusive access to the EHCI hardware and data structures. */ + + sam_takesem(&g_ehci.exclsem); + + /* Perform the transfer */ +#warning Missing logic + ret = -ENOSYS; + + sam_givesem(&g_ehci.exclsem); + return ret; +} + +/******************************************************************************* + * Name: sam_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. + * + * Returned Values: + * None + * + * Assumptions: + * - Only a single class bound to a single device is supported. + * - Never called from an interrupt handler. + * + *******************************************************************************/ + +static void sam_disconnect(FAR struct usbhost_driver_s *drvr) +{ + struct sam_rhport_s *rhport = (struct sam_rhport_s *)drvr; + DEBUGASSERT(rhport); + + /* Remove the disconnected port */ +#warning Missing logic + + /* Unbind the class */ + + rhport->class = NULL; +} + +/******************************************************************************* + * Initialization + *******************************************************************************/ +/******************************************************************************* + * Name: sam_reset + * + * Description: + * Set the HCRESET bit in the USBCMD register to reset the EHCI hardware. + * + * Table 2-9. USBCMD – USB Command Register Bit Definitions + * + * "Host Controller Reset (HCRESET) ... This control bit is used by software + * to reset the host controller. The effects of this on Root Hub registers + * are similar to a Chip Hardware Reset. + * + * "When software writes a one to this bit, the Host Controller resets its + * internal pipelines, timers, counters, state machines, etc. to their + * initial value. Any transaction currently in progress on USB is + * immediately terminated. A USB reset is not driven on downstream + * ports. + * + * "PCI Configuration registers are not affected by this reset. All + * operational registers, including port registers and port state machines + * are set to their initial values. Port ownership reverts to the companion + * host controller(s)... Software must reinitialize the host controller ... + * in order to return the host controller to an operational state. + * + * "This bit is set to zero by the Host Controller when the reset process is + * complete. Software cannot terminate the reset process early by writing a + * zero to this register. Software should not set this bit to a one when + * the HCHalted bit in the USBSTS register is a zero. Attempting to reset + * an actively running host controller will result in undefined behavior." + * + * Input Parameters: + * None. + * + * Returned Value: + * Zero (OK) is returned on success; A negated errno value is returned + * on failure. + * + * Assumptions: + * - Called during the initializaation of the EHCI. + * + *******************************************************************************/ + +static int sam_reset(void) +{ + uint32_t regval; + unsigned int timeout; + + /* "... Software should not set [HCRESET] to a one when the HCHalted bit in + * the USBSTS register is a zero. Attempting to reset an actively running + * host controller will result in undefined behavior." + */ + + sam_putreg(0, &HCOR->usbcmd); + timeout = 0; + do + { + /* Wait one microsecond and update the timeout counter */ + + up_udelay(1); + timeout++; + + /* Get the current valud of the USBSTS register. This loop will terminate + * when either the timeout exceeds one millisecond or when the HCHalted + * bit is no longer set in the USBSTS register. + */ + + regval = sam_getreg(&HCOR->usbsts); + } + while (((regval & EHCI_USBSTS_HALTED) == 0) && (timeout < 1000)); + + /* Is the EHCI still running? Did we timeout? */ + + if ((regval & EHCI_USBSTS_HALTED) == 0) + { + udbg("ERROR: Timed out waiting for HCHalted. USBSTS: %08X", regval); + return -ETIMEDOUT; + } + + /* Now we can set the HCReset bit in the USBCMD register to initiate the reset */ + + regval = sam_getreg(&HCOR->usbcmd); + regval |= EHCI_USBCMD_HCRESET; + sam_putreg(regval, &HCOR->usbcmd); + + /* Wait for the HCReset bit to become clear */ + + do + { + /* Wait five microsecondw and update the timeout counter */ + + up_udelay(5); + timeout += 5; + + /* Get the current valud of the USBCMD register. This loop will terminate + * when either the timeout exceeds one second or when the HCReset + * bit is no longer set in the USBSTS register. + */ + + regval = sam_getreg(&HCOR->usbcmd); + } + while (((regval & EHCI_USBCMD_HCRESET) != 0) && (timeout < 1000000)); + + /* Return either success or a timeout */ + + return (regval & EHCI_USBCMD_HCRESET) != 0 ? -ETIMEDOUT : OK; +} + +/******************************************************************************* + * Global Functions + *******************************************************************************/ +/******************************************************************************* + * Name: sam_ehci_initialize + * + * Description: + * Initialize USB EHCI host controller hardware. + * + * Input Parameters: + * controller -- If the device supports more than one EHCI interface, then + * this identifies which controller is being intialized. 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. + * + *******************************************************************************/ + +FAR struct usbhost_connection_s *sam_ehci_initialize(int controller) +{ + irqstate_t flags; + uint32_t regval; + int ret; + int i; + + /* Sanity checks */ + + DEBUGASSERT(controller == 0); + + /* SAMA5 Configuration *******************************************************/ + /* For High-speed operations, the user has to perform the following: + * + * 1) Enable UHP peripheral clock, bit (1 << AT91C_ID_UHPHS) in + * PMC_PCER register. + * 2) Write CKGR_PLLCOUNT field in PMC_UCKR register. + * 3) Enable UPLL, bit AT91C_CKGR_UPLLEN in PMC_UCKR register. + * 4) Wait until UTMI_PLL is locked. LOCKU bit in PMC_SR register + * 5) Enable BIAS, bit AT91C_CKGR_BIASEN in PMC_UCKR register. + * 6) Select UPLLCK as Input clock of OHCI part, USBS bit in PMC_USB + * register. + * 7) Program the OHCI clocks (UHP48M and UHP12M) with USBDIV field in + * PMC_USB register. USBDIV must be 9 (division by 10) if UPLLCK is + * selected. + * 8) Enable OHCI clocks, UHP bit in PMC_SCER register. + * + * Steps 1 and 8 are performed here. Steps 2 through 7 were are performed + * by sam_clockconfig() earlier in the boot sequence. + */ + + /* Enable UHP peripheral clocking */ + + flags = irqsave(); + sam_uhphs_enableclk(); + + /* Enable OHCI clocks */ + + regval = sam_getreg((volatile uint32_t *)SAM_PMC_SCER); + regval |= PMC_UHP; + sam_putreg(regval, (volatile uint32_t *)SAM_PMC_SCER); + irqrestore(flags); + + /* Note that no pin pinconfiguration is required. All USB HS pins have + * dedicated function + */ + + /* Software Configuration ****************************************************/ + + uvdbg("Initializing EHCI Stack\n"); + + /* Initialize the EHCI state data structure */ + + sem_init(&g_ehci.exclsem, 0, 1); + sem_init(&g_ehci.rhsem, 0, 0); + + /* Initialize EP0 */ + + sem_init(&g_ehci.ep0.wsem, 0, 1); + + /* Initialize the root hub port structures */ + + for (i = 0; i < SAM_EHCI_NRHPORT; i++) + { + struct sam_rhport_s *rhport = &g_ehci.rhport[i]; + rhport->rhpndx = i; + + /* Initialize the device operations */ + + rhport->drvr.ep0configure = sam_ep0configure; + rhport->drvr.epalloc = sam_epalloc; + rhport->drvr.epfree = sam_epfree; + rhport->drvr.alloc = sam_alloc; + rhport->drvr.free = sam_free; + rhport->drvr.ioalloc = sam_ioalloc; + rhport->drvr.iofree = sam_iofree; + rhport->drvr.ctrlin = sam_ctrlin; + rhport->drvr.ctrlout = sam_ctrlout; + rhport->drvr.transfer = sam_transfer; + rhport->drvr.disconnect = sam_disconnect; + } + + /* Initialize the list of free Queue Head (QH) structures */ + + for (i = 0; i < CONFIG_SAMA5_EHCI_NQHS; i++) + { + /* Put the QH structure in a free list */ + + sam_qhfree(&g_ghpool[i]); + } + + /* Initialize the list of free Queue Head (QH) structures */ + + for (i = 0; i < CONFIG_SAMA5_EHCI_NQTDS; i++) + { + /* Put the TD in a free list */ + + sam_qtdfree(&g_qtdpool[i]); + } + + /* EHCI Hardware Configuration ***********************************************/ + + /* Reset the EHCI hardware */ + + ret = sam_reset(); + if (ret < 0) + { + udbg("ERROR: sam_reset failed: %d\n", ret); + return NULL; + } + +#warning Missing logic + + /* Interrupt Configuration ***************************************************/ + + /* Clear pending interrupts */ +#warning Missing logic + + /* Enable EHCI interrupts */ +#warning Missing logic + + /* Attach USB host controller interrupt handler */ + + if (irq_attach(SAM_IRQ_UHPHS, sam_ehci_interrupt) != 0) + { + udbg("ERROR: Failed to attach IRQ\n"); + return NULL; + } + + /* Drive Vbus +5V (the smoke test). Should be done elsewhere in OTG + * mode. + */ + + sam_usbhost_vbusdrive(SAM_EHCI_IFACE, true); + up_mdelay(50); + + /* If there is a USB device in the slot at power up, then we will not + * get the status change interrupt to signal us that the device is + * connected. We need to set the initial connected state accordingly. + */ + + for (i = 0; i < SAM_EHCI_NRHPORT; i++) + { +#warning Missing logic + } + + /* Enable interrupts at the interrupt controller */ + + up_enable_irq(SAM_IRQ_UHPHS); /* enable USB interrupt */ + uvdbg("USB OHCI Initialized\n"); + + /* Initialize and return the connection interface */ + + g_ehciconn.wait = sam_wait; + g_ehciconn.enumerate = sam_enumerate; + return &g_ehciconn; +} + +#endif /* CONFIG_SAMA5_EHCI */ diff --git a/nuttx/arch/arm/src/sama5/sam_ohci.c b/nuttx/arch/arm/src/sama5/sam_ohci.c index a8a10ebfa..ee2160fd1 100644 --- a/nuttx/arch/arm/src/sama5/sam_ohci.c +++ b/nuttx/arch/arm/src/sama5/sam_ohci.c @@ -192,7 +192,7 @@ struct sam_rhport_s { /* Common device fields. This must be the first thing defined in the * structure so that it is possible to simply cast from struct usbhost_s - * to structsam_usbhost_s. + * to struct sam_rhport_s. */ struct usbhost_driver_s drvr; diff --git a/nuttx/arch/arm/src/sama5/sam_usbhost.h b/nuttx/arch/arm/src/sama5/sam_usbhost.h index deb428ce2..47bd7fe15 100644 --- a/nuttx/arch/arm/src/sama5/sam_usbhost.h +++ b/nuttx/arch/arm/src/sama5/sam_usbhost.h @@ -137,8 +137,8 @@ FAR struct usbhost_connection_s *sam_ohci_initialize(int controller); *******************************************************************************/ #ifdef CONFIG_SAMA5_EHCI -struct usbhost_driver_s; -FAR struct usbhost_driver_s *sam_ehci_initialize(int controller); +struct usbhost_connection_s; +FAR struct usbhost_connection_s *sam_ehci_initialize(int controller); #endif /*********************************************************************************** diff --git a/nuttx/configs/sama5d3x-ek/include/board_384mhz.h b/nuttx/configs/sama5d3x-ek/include/board_384mhz.h index 74494cbb2..8f94da2ed 100644 --- a/nuttx/configs/sama5d3x-ek/include/board_384mhz.h +++ b/nuttx/configs/sama5d3x-ek/include/board_384mhz.h @@ -100,6 +100,7 @@ #define BOARD_PMC_MCKR_PLLADIV PMC_MCKR_PLLADIV2 #define BOARD_PMC_MCKR_MDIV PMC_MCKR_MDIV_PCKDIV3 +#ifdef CONFIG_SAMA5_OHCI /* For OHCI Full-speed operations, the user has to perform the following: * * 1) Enable UHP peripheral clock, bit (1 << AT91C_ID_UHPHS) in PMC_PCER @@ -131,8 +132,9 @@ * frame rate. I cannot explain the factor of 2 difference. */ -#define BOARD_OHCI_INPUT PMC_USB_USBS_PLLA -#define BOARD_OHCI_DIVIDER (7) +# define BOARD_OHCI_INPUT PMC_USB_USBS_PLLA +# define BOARD_OHCI_DIVIDER (7) +#endif /* Resulting frequencies */ diff --git a/nuttx/configs/sama5d3x-ek/include/board_396mhz.h b/nuttx/configs/sama5d3x-ek/include/board_396mhz.h index f98cbac86..ac3ae724e 100644 --- a/nuttx/configs/sama5d3x-ek/include/board_396mhz.h +++ b/nuttx/configs/sama5d3x-ek/include/board_396mhz.h @@ -98,7 +98,7 @@ #define BOARD_PMC_MCKR_PRES PMC_MCKR_PRES_DIV1 #define BOARD_PMC_MCKR_PLLADIV PMC_MCKR_PLLADIV2 #define BOARD_PMC_MCKR_MDIV PMC_MCKR_MDIV_PCKDIV3 -786 + /* Resulting frequencies */ #define BOARD_MAINOSC_FREQUENCY (12000000) /* MAINOSC: 12MHz crystal on-board */ @@ -106,6 +106,36 @@ #define BOARD_PCK_FREQUENCY (396000000) /* CPU: PLLACK / 2 / 1 */ #define BOARD_MCK_FREQUENCY (132000000) /* MCK: PLLACK / 2 / 1 / 3 */ +#ifdef CONFIG_SAMA5_EHCI +/* The USB Host High Speed requires a 480 MHz clock (UPLLCK) for the embedded + * High-speed transceivers. UPLLCK is the output of the 480 MHz UTMI PLL + * (UPLL). The source clock of the UTMI PLL is the Main OSC output: Either + * the 12MHz internal RC oscillator on a an external 12MHz crystal. The + * Main OSC must be 12MHz because the UPLL has a built-in 40x multiplier. + * + * For High-speed operations, the user has to perform the following: + * + * 1) Enable UHP peripheral clock, bit (1 << AT91C_ID_UHPHS) in + * PMC_PCER register. + * 2) Write CKGR_PLLCOUNT field in PMC_UCKR register. + * 3) Enable UPLL, bit AT91C_CKGR_UPLLEN in PMC_UCKR register. + * 4) Wait until UTMI_PLL is locked. LOCKU bit in PMC_SR register + * 5) Enable BIAS, bit AT91C_CKGR_BIASEN in PMC_UCKR register. + * 6) Select UPLLCK as Input clock of OHCI part, USBS bit in PMC_USB + * register. + * 7) Program the OHCI clocks (UHP48M and UHP12M) with USBDIV field in + * PMC_USB register. USBDIV must be 9 (division by 10) if UPLLCK is + * selected. + * 8) Enable OHCI clocks, UHP bit in PMC_SCER register. + * + * Steps 2 through 7 performed here. 1 and 8 are performed in the EHCI + * driver is initialized. + */ + +# define BOARD_CKGR_UCKR_UPLLCOUNT (15) /* Maximum value */ +# define BOARD_CKGR_UCKR_BIASCOUNT (15) /* Maximum value */ +#endif + /* HSMCI clocking * * Multimedia Card Interface clock (MCCK or MCI_CK) is Master Clock (MCK) -- cgit v1.2.3