diff --git a/Documentation/platforms/mips/jz4780/boards/mips-creator-ci20/index.rst b/Documentation/platforms/mips/jz4780/boards/mips-creator-ci20/index.rst index 40437a42c1f9f..0d7eb826c7413 100644 --- a/Documentation/platforms/mips/jz4780/boards/mips-creator-ci20/index.rst +++ b/Documentation/platforms/mips/jz4780/boards/mips-creator-ci20/index.rst @@ -66,3 +66,10 @@ net The telnet daemon is included, so the CI20 board can be connected to through telnet. +jumbo +----- + +* Basic serial console access to the NSH shell. +* Networking support through the RJ45 connector. +* USB host support: mass storage, hub, keyboard and mouse. +* Builtin Apps: dd, hidkbd, ping, tc, fb, nsh, sh, telnetd diff --git a/arch/mips/include/jz4780/chip.h b/arch/mips/include/jz4780/chip.h index ef112aa7bff05..ec1908fba6cc3 100644 --- a/arch/mips/include/jz4780/chip.h +++ b/arch/mips/include/jz4780/chip.h @@ -67,27 +67,57 @@ #define OSTCSR (JZTMR_BASE + 0xec) #define TESR (JZTMR_BASE + 0x14) - -#define TESR_OSTEN (1 << 15) -#define TESR_TCEN(n) (1 << (n)) +# define TESR_OSTEN (1 << 15) +# define TESR_TCEN(n) (1 << (n)) #define TMSR (JZTMR_BASE + 0x34) #define TMCR (JZTMR_BASE + 0x38) -#define TMCR_OSTMCL (1 << 15) -#define TMCR_FMCL5 (1 << 5) -#define TMCR_FMCL(n) (1 << (n)) +# define TMCR_OSTMCL (1 << 15) +# define TMCR_FMCL5 (1 << 5) +# define TMCR_FMCL(n) (1 << (n)) #define TFR (JZTMR_BASE + 0x20) #define TFCR (JZTMR_BASE + 0x28) -#define TFCR_OSTFCL (1 << 15) -#define TFCR_FFCL5 (1 << 5) -#define TFCR_FFCL(n) (1 << (n)) +# define TFCR_OSTFCL (1 << 15) +# define TFCR_FFCL5 (1 << 5) +# define TFCR_FFCL(n) (1 << (n)) #define TCNT(n) (JZTMR_BASE + 0x48 + (n)*0x10) #define TCSR(n) (JZTMR_BASE + 0x4C + (n)*0x10) #define TDFR(n) (JZTMR_BASE + 0x40 + (n)*0x10) +#define CLKGR0_REG 0xb0000020 +# define CLKGR0_OTG0 (1 << 2) +# define CLKGR0_UHC (1 << 24) +# define CLKGR0_LCD (1 << 28) +# define CLKGR0_TVE (1 << 27) + +#define OPCR_REG 0xb0000024 +# define OPCR_SPENDN1 (1 << 6) + +#define CLKGR1_REG 0xb0000028 +# define CLKGR1_HDMI (1 << 9) + +#define USBPCR_REG 0xb000003c +# define USBPCR_POR (1 << 22) + +#define USBPCR1_REG 0xb0000048 +# define REFCLK_DIV_MSK 0xfcffffff /* clears bits 25:24 */ +# define REFCLK_DIV_48MHZ 0x02000000 /* bits 25:24 */ +# define WORD_IF_16BIT 0x000c0000 /* bits 19:18 */ + +#define LP1CDR_REG 0xb0000054 +# define LPCDR_VAL 0x15 +# define LPCS_VPLL 0X80000000 +# define CE_LCD (1 << 28) +# define LCD_BUSY (1 << 27) + +#define HDMICDR_REG 0xb000008c + +#define SRBC_REG 0xb00000c4 +# define SRBC_UHC_SR (1 << 14) + #define TICKS_PER_MS (48000/16) #define CHIP_JZ4780 1 diff --git a/arch/mips/src/jz4780/Kconfig b/arch/mips/src/jz4780/Kconfig index 8e71b3c649a71..a363c1f16cc2a 100644 --- a/arch/mips/src/jz4780/Kconfig +++ b/arch/mips/src/jz4780/Kconfig @@ -4,4 +4,102 @@ # if ARCH_CHIP_JZ4780 +comment "JZ4780 Configuration Options" + +menu "JZ4780 Peripheral Support" +config JZ4780_LCD + bool "LCD controller" + default n + +config JZ4780_OHCI + bool "Full/low speed OHCI support" + default n + select SCHED_HPWORK + select USBHOST + select USBHOST_HAVE_ASYNCH + select USBHOST_ASYNCH + select USBHOST_WAITER + ---help--- + Build support for the JZ4780 USB full speed Open Host Controller + Interface (OHCI). + + +if JZ4780_OHCI +config JZ4780_OHCI_NEDS + int "Number of endpoint descriptors" + default 6 + +config JZ4780_OHCI_NTDS + int "Number of transfer descriptors" + default 9 + +config JZ4780_OHCI_TDBUFFERS + int "Number of transfer descriptor buffers" + default 6 + +config JZ4780_OHCI_TDBUFSIZE + int "Size of one transfer descriptor buffer" + default 128 + ---help--- + The size of one transfer descriptor (TD) buffer in bytes. The TD + buffer size must be an even number of 32-bit words + +config JZ4780_OHCI_REGDEBUG + bool "Enable low-level OHCI register debug" + default n + depends on DEBUG_USB_INFO + +endif # JZ4780_OHCI + +config JZ4780_EHCI + bool "High speed EHCI support" + default n + select USBHOST + select USBHOST_HAVE_ASYNCH + select USBHOST_WAITER + ---help--- + Build support for the JZ4780 USB high speed Enhanced Host Controller + Interface (EHCI). If low/full speed is needed too, then you must + also enable the OHCI controller. + +if JZ4780_EHCI + +config JZ4780_EHCI_NQHS + int "Number of Queue Head (QH) structures" + default 8 + ---help--- + Configurable number of Queue Head (QH) structures. The default is + one per Root hub port plus one for EP0 (4). + +config JZ4780_EHCI_NQTDS + int "Number of Queue Element Transfer Descriptor (qTDs)" + default 10 + ---help--- + Configurable number of Queue Element Transfer Descriptor (qTDs). + The default is one per root hub plus three from EP0 (6). + +config JZ4780_EHCI_BUFSIZE + int "Size of one request/descriptor buffer" + default 1024 + ---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 JZ4780_EHCI_PREALLOCATE + bool "Preallocate descriptor pool" + default y + ---help--- + Select this option to pre-allocate EHCI queue and descriptor + structure pools in .bss. Otherwise, these pools will be + dynamically allocated using kmm_memalign(). + +config JZ4780_EHCI_REGDEBUG + bool "Enable low-level EHCI register debug" + default n + depends on DEBUG_USB_INFO + +endif # JZ4780_EHCI +endmenu # JZ4780 Peripheral Support + endif diff --git a/arch/mips/src/jz4780/Make.defs b/arch/mips/src/jz4780/Make.defs index a13a6faaa4bf5..7b434dd545e9b 100644 --- a/arch/mips/src/jz4780/Make.defs +++ b/arch/mips/src/jz4780/Make.defs @@ -51,3 +51,21 @@ endif CHIP_CSRCS = jz4780_lowinit.c jz4780_exception.c jz4780_decodeirq.c CHIP_CSRCS += jz4780_irq.c jz4780_timerisr.c jz4780_gpio.c CHIP_CSRCS += jz4780_cache.c + +ifeq ($(CONFIG_USBHOST),y) +CHIP_CSRCS += jz_usbhost.c + +ifeq ($(CONFIG_JZ4780_OHCI),y) +CHIP_CSRCS += jz4780_ohci.c +endif + +ifeq ($(CONFIG_JZ4780_EHCI),y) +CHIP_CSRCS += jz4780_ehci.c +endif +endif + +ifeq ($(CONFIG_ALLOW_BSD_COMPONENTS),y) +ifeq ($(CONFIG_JZ4780_LCD),y) +CHIP_CSRCS += jz4780_lcd.c +endif +endif diff --git a/arch/mips/src/jz4780/jz4780_ehci.c b/arch/mips/src/jz4780/jz4780_ehci.c new file mode 100644 index 0000000000000..8257de955b3cf --- /dev/null +++ b/arch/mips/src/jz4780/jz4780_ehci.c @@ -0,0 +1,5104 @@ +/**************************************************************************** + * arch/mips/src/jz4780/jz4780_ehci.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* May redefine PIO settings */ + +#include "mips_internal.h" +#include "chip.h" +#include "jz_usbhost.h" +#include "jz4780_ehci.h" + +#define jhzuinfo uinfo + +#ifdef CONFIG_JZ4780_EHCI + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Configuration ************************************************************/ + +/* Pre-requisites */ + +#if !defined(CONFIG_SCHED_WORKQUEUE) +# error Work queue support is required (CONFIG_SCHED_WORKQUEUE) +#elif !defined(CONFIG_SCHED_HPWORK) +# error Hi-priority work queue support is required (CONFIG_SCHED_HPWORK) +#endif + +/* Configurable number of Queue Head (QH) structures. The default is one per + * Root hub port plus one for EP0. + */ + +#ifndef CONFIG_JZ4780_EHCI_NQHS +# define CONFIG_JZ4780_EHCI_NQHS (JZ_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_JZ4780_EHCI_NQTDS +# define CONFIG_JZ4780_EHCI_NQTDS (JZ_EHCI_NRHPORT + 3) +#endif + +/* Buffers must be aligned to the cache line size */ + +#define DCACHE_LINEMASK (JZ4780_DCACHE_LINESIZE - 1) + +/* Configurable size of a request/descriptor buffers */ + +#ifndef CONFIG_JZ4780_EHCI_BUFSIZE +# define CONFIG_JZ4780_EHCI_BUFSIZE 128 +#endif + +#define JZ4780_EHCI_BUFSIZE \ + ((CONFIG_JZ4780_EHCI_BUFSIZE + DCACHE_LINEMASK) & ~DCACHE_LINEMASK) + +/* Debug options */ + +#ifndef CONFIG_DEBUG_USB_INFO +# undef CONFIG_JZ4780_EHCI_REGDEBUG +#endif + +/* Isochronous transfers are not currently supported */ + +#undef CONFIG_USBHOST_ISOC_DISABLE +#define CONFIG_USBHOST_ISOC_DISABLE 1 + +/* Suppress use of PORTA unless board-specific dual-role-port support + * has been included. Generally port A is used as a device-only port, + * typically for SAM-BA and the possibility of enabling host VBUS power + * for this port would be a BAD idea + */ + +#if defined(CONFIG_JZ4780_UDPHS) && !defined(CONFIG_JZ4780_USB_DRP) +# undef CONFIG_JZ4780_UHPHS_RHPORT1 +#endif + +/* Driver-private Definitions ***********************************************/ + +/* This is the set of interrupts handled by this driver */ + +#define EHCI_HANDLED_INTS (EHCI_INT_USBINT | EHCI_INT_USBERRINT | \ + EHCI_INT_PORTSC | EHCI_INT_SYSERROR | \ + EHCI_INT_AAINT) + +/* The periodic frame list is a 4K-page aligned array of Frame List Link + * pointers. The length of the frame list may be programmable. The + * programmability of the periodic frame list is exported to system software + * via the HCCPARAMS register. If non-programmable, the length is 1024 + * elements. If programmable, the length can be selected by system software + * as one of 256, 512, or 1024 elements. + */ + +#define FRAME_LIST_SIZE 1024 + +/* Port numbers */ + +#define HPNDX(hp) ((hp)->port) +#define HPORT(hp) (HPNDX(hp)+1) +#define RHPNDX(rh) ((rh)->hport.hport.port) +#define RHPORT(rh) (RHPNDX(rh)+1) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Internal representation of the EHCI Queue Head (QH) */ + +struct jz_epinfo_s; +struct jz_qh_s +{ + /* Fields visible to hardware */ + + struct ehci_qh_s hw; /* Hardware representation of the queue head */ + + /* Internal fields used by the EHCI driver */ + + struct jz_epinfo_s *epinfo; /* Endpoint used for the transfer */ + uint32_t fqp; /* First qTD in the list (physical address) */ + uint8_t pad[8]; /* Padding to assure 32-byte alignment */ +}; + +/* Internal representation of the EHCI Queue Element Transfer Descriptor + * (qTD) + */ + +struct jz_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 jz_list_s +{ + struct jz_list_s *flink; /* Link to next entry in the list */ + /* Variable length entry data follows */ +}; + +/* List traversal callout functions */ + +typedef int (*foreach_qh_t)(struct jz_qh_s *qh, uint32_t **bp, void *arg); +typedef int (*foreach_qtd_t)(struct jz_qtd_s *qtd, uint32_t **bp, + void *arg); + +/* This structure describes one endpoint. */ + +struct jz_epinfo_s +{ + uint8_t epno:7; /* Endpoint number */ + uint8_t dirin:1; /* 1:IN endpoint 0:OUT endpoint */ + uint8_t devaddr:7; /* Device address */ + uint8_t toggle:1; /* Next data toggle */ +#ifndef CONFIG_USBHOST_INT_DISABLE + uint8_t interval; /* Polling interval */ +#endif + uint8_t status; /* Retained token status bits (for debug purposes) */ + volatile bool iocwait; /* TRUE: Thread is waiting for transfer completion */ + uint16_t maxpacket:11; /* Maximum packet size */ + uint16_t xfrtype:2; /* See USB_EP_ATTR_XFER_* definitions in usb.h */ + uint16_t speed:2; /* See USB_*_SPEED definitions in ehci.h */ + int result; /* The result of the transfer */ + uint32_t xfrd; /* On completion, will hold the number of bytes transferred */ + sem_t iocsem; /* Semaphore used to wait for transfer completion */ +#ifdef CONFIG_USBHOST_ASYNCH + usbhost_asynch_t callback; /* Transfer complete callback */ + void *arg; /* Argument that accompanies the callback */ +#endif + + /* These fields are used in the split-transaction protocol. */ + + uint8_t hubaddr; /* USB device address of the high-speed hub below */ + /* which a full/low-speed device is attached. */ + uint8_t hubport; /* The port on the above high-speed hub. */ +}; + +/* This structure retains the state of one root hub port */ + +struct jz_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 jz_rhport_s. + */ + + struct usbhost_driver_s drvr; + + /* Root hub port status */ + + volatile bool connected; /* Connected to device */ + struct jz_epinfo_s ep0; /* EP0 endpoint info */ + + /* This is the hub port description understood by class drivers */ + + struct usbhost_roothubport_s hport; +}; + +/* This structure retains the overall state of the USB host controller */ + +struct jz_ehci_s +{ + volatile bool pscwait; /* TRUE: Thread is waiting for port status change event */ + + rmutex_t lock; /* Support mutually exclusive access */ + sem_t pscsem; /* Semaphore to wait for port status change events */ + + struct jz_epinfo_s ep0; /* Endpoint 0 */ + struct jz_list_s *qhfree; /* List of free Queue Head (QH) structures */ + struct jz_list_s *qtdfree; /* List of free qTDs */ + struct work_s work; /* Supports interrupt bottom half */ + +#ifdef CONFIG_USBHOST_HUB + /* Used to pass external hub port events */ + + volatile struct usbhost_hubport_s *hport; +#endif + + struct usbhost_devaddr_s devgen; /* Address generation data */ + + /* Root hub ports */ + + struct jz_rhport_s rhport[JZ_EHCI_NRHPORT]; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Register operations ******************************************************/ + +static uint16_t jz_read16(const uint8_t *addr); +static uint32_t jz_read32(const uint8_t *addr); +#if 0 /* Not used */ +static void jz_write16(uint16_t memval, uint8_t *addr); +static void jz_write32(uint32_t memval, uint8_t *addr); +#endif + +#ifdef CONFIG_ENDIAN_BIG +static uint16_t jz_swap16(uint16_t value); +static uint32_t jz_swap32(uint32_t value); +#else +# define jz_swap16(value) (value) +# define jz_swap32(value) (value) +#endif + +#ifdef CONFIG_JZ4780_EHCI_REGDEBUG +static void jz_printreg(volatile uint32_t *regaddr, uint32_t regval, + bool iswrite); +static void jz_checkreg(volatile uint32_t *regaddr, uint32_t regval, + bool iswrite); +static uint32_t jz_getreg(volatile uint32_t *regaddr); +static void jz_putreg(uint32_t regval, volatile uint32_t *regaddr); +#else +static inline uint32_t jz_getreg(volatile uint32_t *regaddr); +static inline void jz_putreg(uint32_t regval, volatile uint32_t *regaddr); +#endif +static int ehci_wait_usbsts(uint32_t maskbits, uint32_t donebits, + unsigned int delay); + +/* Allocators ***************************************************************/ + +static struct jz_qh_s *jz_qh_alloc(void); +static void jz_qh_free(struct jz_qh_s *qh); +static struct jz_qtd_s *jz_qtd_alloc(void); +static void jz_qtd_free(struct jz_qtd_s *qtd); + +/* List Management **********************************************************/ + +static int jz_qh_foreach(struct jz_qh_s *qh, uint32_t **bp, + foreach_qh_t handler, void *arg); +static int jz_qtd_foreach(struct jz_qh_s *qh, foreach_qtd_t handler, + void *arg); +static int jz_qtd_discard(struct jz_qtd_s *qtd, uint32_t **bp, void *arg); +static int jz_qh_discard(struct jz_qh_s *qh); + +/* Cache Operations *********************************************************/ + +#if 0 /* Not used */ +static int jz_qtd_invalidate(struct jz_qtd_s *qtd, uint32_t **bp, + void *arg); +static int jz_qh_invalidate(struct jz_qh_s *qh); +#endif +static int jz_qtd_flush(struct jz_qtd_s *qtd, uint32_t **bp, void *arg); +static int jz_qh_flush(struct jz_qh_s *qh); + +/* Endpoint Transfer Handling ***********************************************/ + +#ifdef CONFIG_JZ4780_EHCI_REGDEBUG +static void jz_qtd_print(struct jz_qtd_s *qtd); +static void jz_qh_print(struct jz_qh_s *qh); +static int jz_qtd_dump(struct jz_qtd_s *qtd, uint32_t **bp, void *arg); +static int jz_qh_dump(struct jz_qh_s *qh, uint32_t **bp, void *arg); +#else +# define jz_qtd_print(qtd) +# define jz_qh_print(qh) +# define jz_qtd_dump(qtd, bp, arg) +# define jz_qh_dump(qh, bp, arg) +#endif + +static inline uint8_t jz_ehci_speed(uint8_t usbspeed); +static int jz_ioc_setup(struct jz_rhport_s *rhport, + struct jz_epinfo_s *epinfo); +static int jz_ioc_wait(struct jz_epinfo_s *epinfo); +static void jz_qh_enqueue(struct jz_qh_s *qhead, struct jz_qh_s *qh); +static struct jz_qh_s *jz_qh_create(struct jz_rhport_s *rhport, + struct jz_epinfo_s *epinfo); +static int jz_qtd_addbpl(struct jz_qtd_s *qtd, const void *buffer, + size_t buflen); +static struct jz_qtd_s *jz_qtd_setupphase(struct jz_epinfo_s *epinfo, + const struct usb_ctrlreq_s *req); +static struct jz_qtd_s *jz_qtd_dataphase(struct jz_epinfo_s *epinfo, + void *buffer, int buflen, uint32_t tokenbits); +static struct jz_qtd_s *jz_qtd_statusphase(uint32_t tokenbits); +static int jz_async_setup(struct jz_rhport_s *rhport, + struct jz_epinfo_s *epinfo, const struct usb_ctrlreq_s *req, + uint8_t *buffer, size_t buflen); +#ifndef CONFIG_USBHOST_INT_DISABLE +static int jz_intr_setup(struct jz_rhport_s *rhport, + struct jz_epinfo_s *epinfo, uint8_t *buffer, size_t buflen); +#endif +static ssize_t jz_transfer_wait(struct jz_epinfo_s *epinfo); +#ifdef CONFIG_USBHOST_ASYNCH +static inline int jz_ioc_async_setup(struct jz_rhport_s *rhport, + struct jz_epinfo_s *epinfo, usbhost_asynch_t callback, + void *arg); +static void jz_asynch_completion(struct jz_epinfo_s *epinfo); +#endif + +/* Interrupt Handling *******************************************************/ + +static int jz_qtd_ioccheck(struct jz_qtd_s *qtd, uint32_t **bp, void *arg); +static int jz_qh_ioccheck(struct jz_qh_s *qh, uint32_t **bp, void *arg); +static int jz_qtd_cancel(struct jz_qtd_s *qtd, uint32_t **bp, void *arg); +static int jz_qh_cancel(struct jz_qh_s *qh, uint32_t **bp, void *arg); +static inline void jz_ioc_bottomhalf(void); +static inline void jz_portsc_bottomhalf(void); +static inline void jz_syserr_bottomhalf(void); +static inline void jz_async_advance_bottomhalf(void); +static void jz_ehci_bottomhalf(void *arg); +static int jz_ehci_tophalf(int irq, void *context, void *arg); + +/* USB Host Controller Operations *******************************************/ + +static int jz_wait(struct usbhost_connection_s *conn, + struct usbhost_hubport_s **hport); +static int jz_rh_enumerate(struct usbhost_connection_s *conn, + struct usbhost_hubport_s *hport); +static int jz_enumerate(struct usbhost_connection_s *conn, + struct usbhost_hubport_s *hport); + +static int jz_ep0configure(struct usbhost_driver_s *drvr, + usbhost_ep_t ep0, uint8_t funcaddr, uint8_t speed, + uint16_t maxpacketsize); +static int jz_epalloc(struct usbhost_driver_s *drvr, + const struct usbhost_epdesc_s *epdesc, + usbhost_ep_t *ep); +static int jz_epfree(struct usbhost_driver_s *drvr, usbhost_ep_t ep); +static int jz_alloc(struct usbhost_driver_s *drvr, + uint8_t **buffer, size_t *maxlen); +static int jz_free(struct usbhost_driver_s *drvr, uint8_t *buffer); +static int jz_ioalloc(struct usbhost_driver_s *drvr, + uint8_t **buffer, size_t buflen); +static int jz_iofree(struct usbhost_driver_s *drvr, + uint8_t *buffer); +static int jz_ctrlin(struct usbhost_driver_s *drvr, usbhost_ep_t ep0, + const struct usb_ctrlreq_s *req, uint8_t *buffer); +static int jz_ctrlout(struct usbhost_driver_s *drvr, usbhost_ep_t ep0, + const struct usb_ctrlreq_s *req, const uint8_t *buffer); +static ssize_t jz_transfer(struct usbhost_driver_s *drvr, + usbhost_ep_t ep, uint8_t *buffer, size_t buflen); +#ifdef CONFIG_USBHOST_ASYNCH +static int jz_asynch(struct usbhost_driver_s *drvr, usbhost_ep_t ep, + uint8_t *buffer, size_t buflen, usbhost_asynch_t callback, + void *arg); +#endif +static int jz_cancel(struct usbhost_driver_s *drvr, usbhost_ep_t ep); +#ifdef CONFIG_USBHOST_HUB +static int jz_connect(struct usbhost_driver_s *drvr, + struct usbhost_hubport_s *hport, bool connected); +#endif +static void jz_disconnect(struct usbhost_driver_s *drvr, + struct usbhost_hubport_s *hport); + +/* Initialization ***********************************************************/ + +static int jz_reset(void); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* In this driver implementation, support is provided for only a single + * USB device. All status information can be simply retained in a single + * global instance. + */ + +static struct jz_ehci_s g_ehci = +{ + .lock = NXRMUTEX_INITIALIZER, + .pscsem = SEM_INITIALIZER(0), + .ep0.iocsem = SEM_INITIALIZER(1), +}; + +/* This is the connection/enumeration interface */ + +static struct usbhost_connection_s g_ehciconn = +{ + .wait = jz_wait, + .enumerate = jz_enumerate, +}; + +/* Maps USB chapter 9 speed to EHCI speed */ + +static const uint8_t g_ehci_speed[4] = +{ + 0, EHCI_LOW_SPEED, EHCI_FULL_SPEED, EHCI_HIGH_SPEED +}; + +/* The head of the asynchronous queue */ + +static struct jz_qh_s g_asynchead aligned_data(32); + +#ifndef CONFIG_USBHOST_INT_DISABLE +/* The head of the periodic queue */ + +static struct jz_qh_s g_intrhead aligned_data(32); + +/* The frame list */ + +#ifdef CONFIG_JZ4780_EHCI_PREALLOCATE +static uint32_t g_framelist[FRAME_LIST_SIZE] aligned_data(4096); +#else +static uint32_t *g_framelist; +#endif +#endif /* CONFIG_USBHOST_INT_DISABLE */ + +#ifdef CONFIG_JZ4780_EHCI_PREALLOCATE +/* Pools of pre-allocated data structures. These will all be linked into the + * free lists within g_ehci. These must all be aligned to 32-byte boundaries + */ + +/* Queue Head (QH) pool */ + +static struct jz_qh_s g_qhpool[CONFIG_JZ4780_EHCI_NQHS] + aligned_data(32); + +/* Queue Element Transfer Descriptor (qTD) pool */ + +static struct jz_qtd_s g_qtdpool[CONFIG_JZ4780_EHCI_NQTDS] + aligned_data(32); + +#else +/* Pools of dynamically data structures. These will all be linked into the + * free lists within g_ehci. These must all be aligned to 32-byte boundaries + */ + +/* Queue Head (QH) pool */ + +static struct jz_qh_s *g_qhpool; + +/* Queue Element Transfer Descriptor (qTD) pool */ + +static struct jz_qtd_s *g_qtdpool; + +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: jz_read16 + * + * Description: + * Read 16-bit little endian data + * + ****************************************************************************/ + +static uint16_t jz_read16(const uint8_t *addr) +{ +#ifdef CONFIG_ENDIAN_BIG + return (uint16_t)addr[0] << 8 | (uint16_t)addr[1]; +#else + return (uint16_t)addr[1] << 8 | (uint16_t)addr[0]; +#endif +} + +/**************************************************************************** + * Name: jz_read32 + * + * Description: + * Read 32-bit little endian data + * + ****************************************************************************/ + +static inline uint32_t jz_read32(const uint8_t *addr) +{ +#ifdef CONFIG_ENDIAN_BIG + return (uint32_t)jz_read16(&addr[0]) << 16 | + (uint32_t)jz_read16(&addr[2]); +#else + return (uint32_t)jz_read16(&addr[2]) << 16 | + (uint32_t)jz_read16(&addr[0]); +#endif +} + +/**************************************************************************** + * Name: jz_write16 + * + * Description: + * Write 16-bit little endian data + * + ****************************************************************************/ + +#if 0 /* Not used */ +static void jz_write16(uint16_t memval, uint8_t *addr) +{ +#ifdef CONFIG_ENDIAN_BIG + addr[0] = memval & 0xff; + addr[1] = memval >> 8; +#else + addr[0] = memval >> 8; + addr[1] = memval & 0xff; +#endif +} +#endif + +/**************************************************************************** + * Name: jz_write32 + * + * Description: + * Write 32-bit little endian data + * + ****************************************************************************/ + +#if 0 /* Not used */ +static void jz_write32(uint32_t memval, uint8_t *addr) +{ +#ifdef CONFIG_ENDIAN_BIG + jz_write16(memval >> 16, &addr[0]); + jz_write16(memval & 0xffff, &addr[2]); +#else + jz_write16(memval & 0xffff, &addr[0]); + jz_write16(memval >> 16, &addr[2]); +#endif +} +#endif + +/**************************************************************************** + * Name: jz_swap16 + * + * Description: + * Swap bytes on a 16-bit value + * + ****************************************************************************/ + +#ifdef CONFIG_ENDIAN_BIG +static uint16_t jz_swap16(uint16_t value) +{ + return ((value >> 8) & 0xff) | ((value & 0xff) << 8); +} +#endif + +/**************************************************************************** + * Name: jz_swap32 + * + * Description: + * Swap bytes on a 32-bit value + * + ****************************************************************************/ + +#ifdef CONFIG_ENDIAN_BIG +static uint32_t jz_swap32(uint32_t value) +{ + return (uint32_t)jz_swap16((uint16_t)((value >> 16) & 0xffff)) | + (uint32_t)jz_swap16((uint16_t)(value & 0xffff)) << 16; +} +#endif + +/**************************************************************************** + * Name: jz_printreg + * + * Description: + * Print the contents of a JZ4780 EHCI register + * + ****************************************************************************/ + +#ifdef CONFIG_JZ4780_EHCI_REGDEBUG +static void jz_printreg(volatile uint32_t *regaddr, uint32_t regval, + bool iswrite) +{ + uinfo("%08x%s%08x\n", (uintptr_t)regaddr, iswrite ? "<-" : "->", regval); +} +#endif + +/**************************************************************************** + * Name: jz_checkreg + * + * Description: + * Check if it is time to output debug information for accesses to a JZ4780 + * EHCI register + * + ****************************************************************************/ + +#ifdef CONFIG_JZ4780_EHCI_REGDEBUG +static void jz_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 */ + + jz_printreg(prevaddr, preval, prevwrite); + } + else + { + /* No.. More than one. */ + + uinfo("[repeats %d more times]\n", count); + } + } + + /* Save the new address, value, count, and operation for next time */ + + prevaddr = (uint32_t *)regaddr; + preval = regval; + count = 0; + prevwrite = iswrite; + + /* Show the new register access */ + + jz_printreg(regaddr, regval, iswrite); + } +} +#endif + +/**************************************************************************** + * Name: jz_getreg + * + * Description: + * Get the contents of an JZ4780 register + * + ****************************************************************************/ + +#ifdef CONFIG_JZ4780_EHCI_REGDEBUG +static uint32_t jz_getreg(volatile uint32_t *regaddr) +{ + /* Read the value from the register */ + + uint32_t regval = *regaddr; + + /* Check if we need to print this value */ + + jz_checkreg(regaddr, regval, false); + return regval; +} +#else +static inline uint32_t jz_getreg(volatile uint32_t *regaddr) +{ + return *regaddr; +} +#endif + +/**************************************************************************** + * Name: jz_putreg + * + * Description: + * Set the contents of an JZ4780 register to a value + * + ****************************************************************************/ + +#ifdef CONFIG_JZ4780_EHCI_REGDEBUG +static void jz_putreg(uint32_t regval, volatile uint32_t *regaddr) +{ + /* Check if we need to print this value */ + + jz_checkreg(regaddr, regval, true); + + /* Write the value */ + + *regaddr = regval; +} +#else +static inline void jz_putreg(uint32_t regval, volatile uint32_t *regaddr) +{ + *regaddr = regval; +} +#endif + +/**************************************************************************** + * Name: ehci_wait_usbsts + * + * Description: + * Wait for either (1) a field in the USBSTS register to take a specific + * value, (2) for a timeout to occur, or (3) a error to occur. Return + * a value to indicate which terminated the wait. + * + ****************************************************************************/ + +static int ehci_wait_usbsts(uint32_t maskbits, uint32_t donebits, + unsigned int delay) +{ + uint32_t regval; + unsigned int timeout; + + timeout = 0; + do + { + /* Wait 5usec before trying again */ + + up_udelay(5); + timeout += 5; + + /* Read the USBSTS register and check for a system error */ + + regval = jz_getreg(&HCOR->usbsts); + if ((regval & EHCI_INT_SYSERROR) != 0) + { + usbhost_trace1(EHCI_TRACE1_SYSTEMERROR, regval); + return -EIO; + } + + /* Mask out the bits of interest */ + + regval &= maskbits; + + /* Loop until the masked bits take the specified value or until a + * timeout occurs. + */ + } + while (regval != donebits && timeout < delay); + + /* We got here because either the waited for condition or a timeout + * occurred. Return a value to indicate which. + */ + + return (regval == donebits) ? OK : -ETIMEDOUT; +} + +/**************************************************************************** + * Name: jz_qh_alloc + * + * Description: + * Allocate a Queue Head (QH) structure by removing it from the free list + * + * Assumption: Caller holds the lock + * + ****************************************************************************/ + +static struct jz_qh_s *jz_qh_alloc(void) +{ + struct jz_qh_s *qh; + + /* Remove the QH structure from the freelist */ + + qh = (struct jz_qh_s *)g_ehci.qhfree; + if (qh) + { + g_ehci.qhfree = ((struct jz_list_s *)qh)->flink; + memset(qh, 0, sizeof(struct jz_qh_s)); + } + + return qh; +} + +/**************************************************************************** + * Name: jz_qh_free + * + * Description: + * Free a Queue Head (QH) structure by returning it to the free list + * + * Assumption: Caller holds the lock + * + ****************************************************************************/ + +static void jz_qh_free(struct jz_qh_s *qh) +{ + struct jz_list_s *entry = (struct jz_list_s *)qh; + + /* Put the QH structure back into the free list */ + + entry->flink = g_ehci.qhfree; + g_ehci.qhfree = entry; +} + +/**************************************************************************** + * Name: jz_qtd_alloc + * + * Description: + * Allocate a Queue Element Transfer Descriptor (qTD) by removing it from + * the free list + * + * Assumption: Caller holds the lock + * + ****************************************************************************/ + +static struct jz_qtd_s *jz_qtd_alloc(void) +{ + struct jz_qtd_s *qtd; + + /* Remove the qTD from the freelist */ + + qtd = (struct jz_qtd_s *)g_ehci.qtdfree; + if (qtd) + { + g_ehci.qtdfree = ((struct jz_list_s *)qtd)->flink; + memset(qtd, 0, sizeof(struct jz_qtd_s)); + } + + return qtd; +} + +/**************************************************************************** + * Name: jz_qtd_free + * + * Description: + * Free a Queue Element Transfer Descriptor (qTD) by returning it to the + * free list + * + * Assumption: Caller holds the lock + * + ****************************************************************************/ + +static void jz_qtd_free(struct jz_qtd_s *qtd) +{ + struct jz_list_s *entry = (struct jz_list_s *)qtd; + + /* Put the qTD back into the free list */ + + entry->flink = g_ehci.qtdfree; + g_ehci.qtdfree = entry; +} + +/**************************************************************************** + * Name: jz_qh_foreach + * + * Description: + * Give the first entry in a list of Queue Head (QH) structures, call the + * handler for each QH structure in the list (including the one at the head + * of the list). + * + ****************************************************************************/ + +static int jz_qh_foreach(struct jz_qh_s *qh, uint32_t **bp, + foreach_qh_t handler, void *arg) +{ + struct jz_qh_s *next; + uintptr_t physaddr; + int ret; + + DEBUGASSERT(qh && handler); + while (qh) + { + /* Is this the end of the list? Check the horizontal link pointer + * (HLP) terminate (T) bit. If T==1, then the HLP address is not + * valid. + */ + + physaddr = jz_swap32(qh->hw.hlp); + if ((physaddr & QH_HLP_T) != 0) + { + /* Set the next pointer to NULL. This will terminate the loop. */ + + next = NULL; + } + + /* Is the next QH the asynchronous list head which will always be at + * the end of the asynchronous queue? + */ + + else if (jz_virtramaddr(physaddr & QH_HLP_MASK) == + (uintptr_t)&g_asynchead) + { + /* That will also terminate the loop */ + + next = NULL; + } + + /* Otherwise, there is a QH structure after this one that describes + * another transaction. + */ + + else + { + physaddr = jz_swap32(qh->hw.hlp) & QH_HLP_MASK; + next = (struct jz_qh_s *)jz_virtramaddr(physaddr); + } + + /* Perform the user action on this entry. The action might result in + * unlinking the entry! But that is okay because we already have the + * next QH pointer. + * + * Notice that we do not manage the back pointer (bp). If the call- + * out uses it, it must update it as necessary. + */ + + ret = handler(qh, bp, arg); + + /* If the handler returns any non-zero value, then terminate the + * traversal early. + */ + + if (ret != 0) + { + return ret; + } + + /* Set up to visit the next entry */ + + qh = next; + } + + return OK; +} + +/**************************************************************************** + * Name: jz_qtd_foreach + * + * Description: + * Give a Queue Head (QH) instance, call the handler for each qTD structure + * in the queue. + * + ****************************************************************************/ + +static int jz_qtd_foreach(struct jz_qh_s *qh, foreach_qtd_t handler, + void *arg) +{ + struct jz_qtd_s *qtd; + struct jz_qtd_s *next; + uintptr_t physaddr; + uint32_t *bp; + int ret; + + DEBUGASSERT(qh && handler); + + /* Handle the special case where the queue is empty */ + + bp = &qh->fqp; /* Start of qTDs in original list */ + physaddr = jz_swap32(*bp); /* Physical address of first qTD in CPU order */ + + if ((physaddr & QTD_NQP_T) != 0) + { + return 0; + } + + /* Start with the first qTD in the list */ + + qtd = (struct jz_qtd_s *)jz_virtramaddr(physaddr); + next = NULL; + + /* And loop until we encounter the end of the qTD list */ + + while (qtd) + { + /* Is this the end of the list? Check the next qTD pointer (NQP) + * terminate (T) bit. If T==1, then the NQP address is not valid. + */ + + if ((jz_swap32(qtd->hw.nqp) & QTD_NQP_T) != 0) + { + /* Set the next pointer to NULL. This will terminate the loop. */ + + next = NULL; + } + else + { + physaddr = jz_swap32(qtd->hw.nqp) & QTD_NQP_NTEP_MASK; + next = (struct jz_qtd_s *)jz_virtramaddr(physaddr); + } + + /* Perform the user action on this entry. The action might result in + * unlinking the entry! But that is okay because we already have the + * next QH pointer. + * + * Notice that we do not manage the back pointer (bp). If the call- + * out uses it, it must update it as necessary. + */ + + ret = handler(qtd, &bp, arg); + + /* If the handler returns any non-zero value, then terminate the + * traversal early. + */ + + if (ret != 0) + { + return ret; + } + + /* Set up to visit the next entry */ + + qtd = next; + } + + return OK; +} + +/**************************************************************************** + * Name: jz_qtd_discard + * + * Description: + * This is a jz_qtd_foreach callback. It simply unlinks the QTD, updates + * the back pointer, and frees the QTD structure. + * + ****************************************************************************/ + +static int jz_qtd_discard(struct jz_qtd_s *qtd, uint32_t **bp, void *arg) +{ + DEBUGASSERT(qtd && bp && *bp); + + /* Remove the qTD from the list by updating the forward pointer to skip + * around this qTD. We do not change that pointer because are repeatedly + * removing the aTD at the head of the QH list. + */ + + **bp = qtd->hw.nqp; + + /* Then free the qTD */ + + jz_qtd_free(qtd); + return OK; +} + +/**************************************************************************** + * Name: jz_qh_discard + * + * Description: + * Free the Queue Head (QH) and all qTD's attached to the QH. + * + * Assumptions: + * The QH structure itself has already been unlinked from whatever list it + * may have been in. + * + ****************************************************************************/ + +static int jz_qh_discard(struct jz_qh_s *qh) +{ + int ret; + + DEBUGASSERT(qh); + + /* Free all of the qTD's attached to the QH */ + + ret = jz_qtd_foreach(qh, jz_qtd_discard, NULL); + if (ret < 0) + { + usbhost_trace1(EHCI_TRACE1_QTDFOREACH_FAILED, -ret); + } + + /* Then free the QH itself */ + + jz_qh_free(qh); + return ret; +} + +/**************************************************************************** + * Name: jz_qtd_invalidate + * + * Description: + * This is a callback from jz_qtd_foreach. It simply invalidates D-cache + * for address range of the qTD entry. + * + ****************************************************************************/ + +#if 0 /* Not used */ +static int jz_qtd_invalidate(struct jz_qtd_s *qtd, uint32_t **bp, + void *arg) +{ + /* Invalidate the D-Cache, i.e., force reloading of the D-Cache from memory + * memory over the specified address range. + */ + + up_invalidate_dcache((uintptr_t)&qtd->hw, + (uintptr_t)&qtd->hw + sizeof(struct ehci_qtd_s)); + return OK; +} +#endif + +/**************************************************************************** + * Name: jz_qh_invalidate + * + * Description: + * Invalidate the Queue Head and all qTD entries in the queue. + * + ****************************************************************************/ + +#if 0 /* Not used */ +static int jz_qh_invalidate(struct jz_qh_s *qh) +{ + /* Invalidate the QH first so that we reload the qTD list head */ + + up_invalidate_dcache((uintptr_t)&qh->hw, + (uintptr_t)&qh->hw + sizeof(struct ehci_qh_s)); + + /* Then invalidate all of the qTD entries in the queue */ + + return jz_qtd_foreach(qh, jz_qtd_invalidate, NULL); +} +#endif + +/**************************************************************************** + * Name: jz_qtd_flush + * + * Description: + * This is a callback from jz_qtd_foreach. It simply flushes D-cache for + * address range of the qTD entry. + * + ****************************************************************************/ + +static int jz_qtd_flush(struct jz_qtd_s *qtd, uint32_t **bp, void *arg) +{ + /* Flush the D-Cache, i.e., make the contents of the memory match the + * contents of the D-Cache in the specified address range and invalidate + * the D-Cache to force re-loading of the data from memory when next + * accessed. + */ + + up_flush_dcache((uintptr_t)&qtd->hw, + (uintptr_t)&qtd->hw + sizeof(struct ehci_qtd_s)); + + return OK; +} + +/**************************************************************************** + * Name: jz_qh_flush + * + * Description: + * Invalidate the Queue Head and all qTD entries in the queue. + * + ****************************************************************************/ + +static int jz_qh_flush(struct jz_qh_s *qh) +{ + /* Flush the QH first. This will write the contents of the D-cache to RAM + * and invalidate the contents of the D-cache so that the next access will + * be reloaded from D-Cache. + */ + + up_flush_dcache((uintptr_t)&qh->hw, + (uintptr_t)&qh->hw + sizeof(struct ehci_qh_s)); + + /* Then flush all of the qTD entries in the queue */ + + return jz_qtd_foreach(qh, jz_qtd_flush, NULL); +} + +/**************************************************************************** + * Name: jz_qtd_print + * + * Description: + * Print the context of one qTD + * + ****************************************************************************/ + +#ifdef CONFIG_JZ4780_EHCI_REGDEBUG +static void jz_qtd_print(struct jz_qtd_s *qtd) +{ + uinfo(" QTD[%p]:\n", qtd); + uinfo(" hw:\n"); + uinfo(" nqp: %08x alt: %08x token: %08x\n", + qtd->hw.nqp, qtd->hw.alt, qtd->hw.token); + uinfo(" bpl: %08x %08x %08x %08x %08x\n", + qtd->hw.bpl[0], qtd->hw.bpl[1], qtd->hw.bpl[2], + qtd->hw.bpl[3], qtd->hw.bpl[4]); +} +#endif + +/**************************************************************************** + * Name: jz_qh_print + * + * Description: + * Print the context of one QH + * + ****************************************************************************/ + +#ifdef CONFIG_JZ4780_EHCI_REGDEBUG +static void jz_qh_print(struct jz_qh_s *qh) +{ + struct jz_epinfo_s *epinfo; + struct ehci_overlay_s *overlay; + + uinfo("QH[%p]:\n", qh); + uinfo(" hw:\n"); + uinfo(" hlp: %08x epchar: %08x epcaps: %08x cqp: %08x\n", + qh->hw.hlp, qh->hw.epchar, qh->hw.epcaps, qh->hw.cqp); + + overlay = &qh->hw.overlay; + uinfo(" overlay:\n"); + uinfo(" nqp: %08x alt: %08x token: %08x\n", + overlay->nqp, overlay->alt, overlay->token); + uinfo(" bpl: %08x %08x %08x %08x %08x\n", + overlay->bpl[0], overlay->bpl[1], overlay->bpl[2], + overlay->bpl[3], overlay->bpl[4]); + + uinfo(" fqp:\n", qh->fqp); + + epinfo = qh->epinfo; + uinfo(" epinfo[%p]:\n", epinfo); + if (epinfo) + { + uinfo(" EP%d DIR=%s FA=%08x TYPE=%d MaxPacket=%d\n", + epinfo->epno, epinfo->dirin ? "IN" : "OUT", epinfo->devaddr, + epinfo->xfrtype, epinfo->maxpacket); + uinfo(" Toggle=%d iocwait=%d speed=%d result=%d\n", + epinfo->toggle, epinfo->iocwait, epinfo->speed, epinfo->result); + } +} +#endif + +/**************************************************************************** + * Name: jz_qtd_dump + * + * Description: + * This is a jz_qtd_foreach callout function. It dumps the context of one + * qTD + * + ****************************************************************************/ + +#ifdef CONFIG_JZ4780_EHCI_REGDEBUG +static int jz_qtd_dump(struct jz_qtd_s *qtd, uint32_t **bp, void *arg) +{ + jz_qtd_print(qtd); + return OK; +} +#endif + +/**************************************************************************** + * Name: jz_qh_dump + * + * Description: + * This is a jz_qh_foreach callout function. It dumps a QH structure and + * all of the qTD structures linked to the QH. + * + ****************************************************************************/ + +#ifdef CONFIG_JZ4780_EHCI_REGDEBUG +static int jz_qh_dump(struct jz_qh_s *qh, uint32_t **bp, void *arg) +{ + jz_qh_print(qh); + return jz_qtd_foreach(qh, jz_qtd_dump, NULL); +} +#endif + +/**************************************************************************** + * Name: jz_ehci_speed + * + * Description: + * Map a speed enumeration value per Chapter 9 of the USB specification to + * the speed enumeration required in the EHCI queue head. + * + ****************************************************************************/ + +static inline uint8_t jz_ehci_speed(uint8_t usbspeed) +{ + DEBUGASSERT(usbspeed >= USB_SPEED_LOW && usbspeed <= USB_SPEED_HIGH); + return g_ehci_speed[usbspeed]; +} + +/**************************************************************************** + * Name: jz_ioc_setup + * + * Description: + * Set the request for the IOC event well BEFORE enabling the transfer (as + * soon as we are absolutely committed to the to avoid transfer). We do + * this to minimize race conditions. This logic would have to be expanded + * if we want to have more than one packet in flight at a time! + * + * Assumption: The caller holds tex EHCI lock + * + ****************************************************************************/ + +static int jz_ioc_setup(struct jz_rhport_s *rhport, + struct jz_epinfo_s *epinfo) +{ + irqstate_t flags; + int ret = -ENODEV; + + DEBUGASSERT(rhport && epinfo && !epinfo->iocwait); +#ifdef CONFIG_USBHOST_ASYNCH + DEBUGASSERT(epinfo->callback == NULL); +#endif + + /* Is the device still connected? */ + + flags = enter_critical_section(); + if (rhport->connected) + { + /* Then set iocwait to indicate that we expect to be informed when + * either (1) the device is disconnected, or (2) the transfer + * completed. + */ + + epinfo->iocwait = true; /* We want to be awakened by IOC interrupt */ + epinfo->status = 0; /* No status yet */ + epinfo->xfrd = 0; /* Nothing transferred yet */ + epinfo->result = -EBUSY; /* Transfer in progress */ +#ifdef CONFIG_USBHOST_ASYNCH + epinfo->callback = NULL; /* No asynchronous callback */ + epinfo->arg = NULL; +#endif + ret = OK; /* We are good to go */ + } + + leave_critical_section(flags); + return ret; +} + +/**************************************************************************** + * Name: jz_ioc_wait + * + * Description: + * Wait for the IOC event. + * + * Assumption: The caller does *NOT* hold the EHCI lock. That would + * cause a deadlock when the bottom-half, worker thread needs to take the + * semaphore. + * + ****************************************************************************/ + +static int jz_ioc_wait(struct jz_epinfo_s *epinfo) +{ + int ret = OK; + + /* Wait for the IOC event. Loop to handle any false alarm semaphore + * counts. Return an error if the task is canceled. + */ + + while (epinfo->iocwait) + { + ret = nxsem_wait_uninterruptible(&epinfo->iocsem); + if (ret < 0) + { + break; + } + } + + return ret < 0 ? ret : epinfo->result; +} + +/**************************************************************************** + * Name: jz_qh_enqueue + * + * Description: + * Add a new, ready-to-go QH w/attached qTDs to the asynchronous queue. + * + * Assumptions: The caller holds the EHCI lock + * + ****************************************************************************/ + +static void jz_qh_enqueue(struct jz_qh_s *qhead, struct jz_qh_s *qh) +{ + uintptr_t physaddr; + + /* Set the internal fqp field. When we transverse the QH list later, + * we need to know the correct place to start because the overlay may no + * longer point to the first qTD entry. + */ + + qh->fqp = qh->hw.overlay.nqp; + jz_qh_dump(qh, NULL, NULL); + + /* Add the new QH to the head of the asynchronous queue list. + * + * First, attach the old head as the new QH HLP and flush the new QH and + * its attached qTDs to RAM. + */ + + qh->hw.hlp = qhead->hw.hlp; + jz_qh_flush(qh); + + /* Then set the new QH as the first QH in the asynchronous queue and flush + * the modified head to RAM. + */ + + physaddr = (uintptr_t)jz_physramaddr((uintptr_t)qh); + qhead->hw.hlp = jz_swap32(physaddr | QH_HLP_TYP_QH); + up_clean_dcache((uintptr_t)&qhead->hw, + (uintptr_t)&qhead->hw + sizeof(struct ehci_qh_s)); +} + +/**************************************************************************** + * Name: jz_qh_create + * + * Description: + * Create a new Queue Head (QH) + * + ****************************************************************************/ + +static struct jz_qh_s *jz_qh_create(struct jz_rhport_s *rhport, + struct jz_epinfo_s *epinfo) +{ + struct jz_qh_s *qh; + uint32_t regval; + uint32_t rl = 0; + + /* Allocate a new queue head structure */ + + qh = jz_qh_alloc(); + if (qh == NULL) + { + usbhost_trace1(EHCI_TRACE1_QHALLOC_FAILED, 0); + return NULL; + } + + /* Save the endpoint information with the QH itself */ + + qh->epinfo = epinfo; + + /* Write QH endpoint characteristics: + * + * FIELD DESCRIPTION VALUE/SOURCE + * -------- ------------------------------- -------------------- + * DEVADDR Device address Endpoint structure + * I Deactivate on Next Transaction 0 + * ENDPT Endpoint number Endpoint structure + * EPS Endpoint speed Endpoint structure + * DTC Data toggle control 1 + * MAXPKT Max packet size Endpoint structure + * C Control endpoint Calculated + * RL NAK count reloaded Depends on speed and + * epinfo->xfrtype + */ + + if (epinfo->speed == USB_SPEED_HIGH && + epinfo->xfrtype != USB_EP_ATTR_XFER_INT) + { + rl = 8; + } + + regval = ((uint32_t)epinfo->devaddr << QH_EPCHAR_DEVADDR_SHIFT) | + ((uint32_t)epinfo->epno << QH_EPCHAR_ENDPT_SHIFT) | + ((uint32_t)jz_ehci_speed(epinfo->speed) << QH_EPCHAR_EPS_SHIFT) | + QH_EPCHAR_DTC | + ((uint32_t)epinfo->maxpacket << QH_EPCHAR_MAXPKT_SHIFT) | + (rl << QH_EPCHAR_RL_SHIFT); + + /* Paragraph 3.6.3: "Control Endpoint Flag (C). If the QH.EPS field + * indicates the endpoint is not a high-speed device, and the endpoint + * is an control endpoint, then software must set this bit to a one. + * Otherwise it should always set this bit to a zero." + */ + + if (epinfo->speed != USB_SPEED_HIGH && + epinfo->xfrtype == USB_EP_ATTR_XFER_CONTROL) + { + regval |= QH_EPCHAR_C; + } + + /* Save the endpoint characteristics word with the correct byte order */ + + qh->hw.epchar = jz_swap32(regval); + + /* Write QH endpoint capabilities + * + * FIELD DESCRIPTION VALUE/SOURCE + * -------- ------------------------------- -------------------- + * SSMASK Interrupt Schedule Mask Depends on epinfo->xfrtype + * SCMASK Split Completion Mask Depends on epinfo->speed + * HUBADDR Hub Address epinfo->hubaddr + * PORT Port number epinfo->hubport + * MULT High band width multiplier 1 + */ + + regval = ((uint32_t)(epinfo->hubaddr) << QH_EPCAPS_HUBADDR_SHIFT) | + ((uint32_t)(epinfo->hubport) << QH_EPCAPS_PORT_SHIFT) | + ((uint32_t)1 << QH_EPCAPS_MULT_SHIFT); + +#ifndef CONFIG_USBHOST_INT_DISABLE + if (epinfo->xfrtype == USB_EP_ATTR_XFER_INT) + { + /* Here, the S-Mask field in the queue head is set to 1, indicating + * that the transaction for the endpoint should be executed on the bus + * during micro-frame 0 of the frame. + * + * REVISIT: The polling interval should be controlled by the which + * entry is the framelist holds the QH pointer for a given micro-frame + * and the QH pointer should be replicated for different polling rates. + * This implementation currently just sets all frame_list entry to + * all the same interrupt queue. That should work but will not give + * any control over polling rates. + */ +#warning REVISIT + + regval |= ((uint32_t)(1 << 0) << QH_EPCAPS_SSMASK_SHIFT); + } +#endif + + if (epinfo->speed != USB_SPEED_HIGH) + { + /* Choose micro-frame 2. */ + + regval |= ((uint32_t)(1 << 2) << QH_EPCAPS_SCMASK_SHIFT); + } + + qh->hw.epcaps = jz_swap32(regval); + + /* Mark this as the end of this list. This will be overwritten if/when the + * next qTD is added to the queue. + */ + + qh->hw.hlp = jz_swap32(QH_HLP_T); + qh->hw.overlay.nqp = jz_swap32(QH_NQP_T); + qh->hw.overlay.alt = jz_swap32(QH_AQP_T); + return qh; +} + +/**************************************************************************** + * Name: jz_qtd_addbpl + * + * Description: + * Add a buffer pointer list to a qTD. + * + ****************************************************************************/ + +static int jz_qtd_addbpl(struct jz_qtd_s *qtd, const void *buffer, + size_t buflen) +{ + uint32_t physaddr; + uint32_t nbytes; + uint32_t next; + int ndx; + + /* Flush the contents of the data buffer to RAM so that the correct + * contents will be accessed for an OUT DMA. + */ + + /* Didn't behave as expected */ + + up_flush_dcache((uintptr_t)buffer, (uintptr_t)buffer + buflen); + + /* Loop, adding the aligned physical addresses of the buffer to the buffer + * page list. Only the first entry need not be aligned (because only the + * first entry has the offset field). The subsequent entries must begin on + * 4KB address boundaries. + */ + + physaddr = (uint32_t)jz_physramaddr((uintptr_t)buffer); + + for (ndx = 0; ndx < 5; ndx++) + { + /* Write the physical address of the buffer into the qTD buffer + * pointer list. + */ + + qtd->hw.bpl[ndx] = jz_swap32(physaddr); + + /* Get the next buffer pointer (in the case where we will have to + * transfer more then one chunk). This buffer must be aligned to a + * 4KB address boundary. + */ + + next = (physaddr + 4096) & ~4095; + + /* How many bytes were included in the last buffer? Was it the whole + * thing? + */ + + nbytes = next - physaddr; + if (nbytes >= buflen) + { + /* Yes... it was the whole thing. Break out of the loop early. */ + + break; + } + + /* Adjust the buffer length and physical address for the next time + * through the loop. + */ + + buflen -= nbytes; + physaddr = next; + } + + /* Handle the case of a huge buffer > 4*4KB = 16KB */ + + if (ndx >= 5) + { + usbhost_trace1(EHCI_TRACE1_BUFTOOBIG, buflen); + return -EFBIG; + } + + return OK; +} + +/**************************************************************************** + * Name: jz_qtd_setupphase + * + * Description: + * Create a SETUP phase request qTD. + * + ****************************************************************************/ + +static struct jz_qtd_s *jz_qtd_setupphase(struct jz_epinfo_s *epinfo, + const struct usb_ctrlreq_s *req) +{ + struct jz_qtd_s *qtd; + uint32_t regval; + int ret; + + /* Allocate a new Queue Element Transfer Descriptor (qTD) */ + + qtd = jz_qtd_alloc(); + if (qtd == NULL) + { + usbhost_trace1(EHCI_TRACE1_REQQTDALLOC_FAILED, 0); + return NULL; + } + + /* Mark this as the end of the list (this will be overwritten if another + * qTD is added after this one). + */ + + qtd->hw.nqp = jz_swap32(QTD_NQP_T); + qtd->hw.alt = jz_swap32(QTD_AQP_T); + + /* Write qTD token: + * + * FIELD DESCRIPTION VALUE/SOURCE + * -------- ------------------------------- -------------------- + * STATUS Status QTD_TOKEN_ACTIVE + * PID PID Code QTD_TOKEN_PID_SETUP + * CERR Error Counter 3 + * CPAGE Current Page 0 + * IOC Interrupt on complete 0 + * NBYTES Total Bytes to Transfer USB_SIZEOF_CTRLREQ + * TOGGLE Data Toggle 0 + */ + + regval = QTD_TOKEN_ACTIVE | QTD_TOKEN_PID_SETUP | + ((uint32_t)3 << QTD_TOKEN_CERR_SHIFT) | + ((uint32_t)USB_SIZEOF_CTRLREQ << QTD_TOKEN_NBYTES_SHIFT); + + qtd->hw.token = jz_swap32(regval); + + /* Add the buffer data */ + + ret = jz_qtd_addbpl(qtd, req, USB_SIZEOF_CTRLREQ); + if (ret < 0) + { + usbhost_trace1(EHCI_TRACE1_ADDBPL_FAILED, -ret); + jz_qtd_free(qtd); + return NULL; + } + + /* Add the data transfer size to the count in the epinfo structure */ + + epinfo->xfrd += USB_SIZEOF_CTRLREQ; + + return qtd; +} + +/**************************************************************************** + * Name: jz_qtd_dataphase + * + * Description: + * Create a data transfer or SET data phase qTD. + * + ****************************************************************************/ + +static struct jz_qtd_s *jz_qtd_dataphase(struct jz_epinfo_s *epinfo, + void *buffer, int buflen, + uint32_t tokenbits) +{ + struct jz_qtd_s *qtd; + uint32_t regval; + int ret; + + /* Allocate a new Queue Element Transfer Descriptor (qTD) */ + + qtd = jz_qtd_alloc(); + if (qtd == NULL) + { + usbhost_trace1(EHCI_TRACE1_DATAQTDALLOC_FAILED, 0); + return NULL; + } + + /* Mark this as the end of the list (this will be overwritten if another + * qTD is added after this one). + */ + + qtd->hw.nqp = jz_swap32(QTD_NQP_T); + qtd->hw.alt = jz_swap32(QTD_AQP_T); + + /* Write qTD token: + * + * FIELD DESCRIPTION VALUE/SOURCE + * -------- ------------------------------- -------------------- + * STATUS Status QTD_TOKEN_ACTIVE + * PID PID Code Contained in tokenbits + * CERR Error Counter 3 + * CPAGE Current Page 0 + * IOC Interrupt on complete Contained in tokenbits + * NBYTES Total Bytes to Transfer buflen + * TOGGLE Data Toggle Contained in tokenbits + */ + + regval = tokenbits | QTD_TOKEN_ACTIVE | + ((uint32_t)3 << QTD_TOKEN_CERR_SHIFT) | + ((uint32_t)buflen << QTD_TOKEN_NBYTES_SHIFT); + + qtd->hw.token = jz_swap32(regval); + + /* Add the buffer information to the buffer pointer list */ + + ret = jz_qtd_addbpl(qtd, buffer, buflen); + if (ret < 0) + { + usbhost_trace1(EHCI_TRACE1_ADDBPL_FAILED, -ret); + jz_qtd_free(qtd); + return NULL; + } + + /* Add the data transfer size to the count in the epinfo structure */ + + epinfo->xfrd += buflen; + + return qtd; +} + +/**************************************************************************** + * Name: jz_qtd_statusphase + * + * Description: + * Create a STATUS phase request qTD. + * + ****************************************************************************/ + +static struct jz_qtd_s *jz_qtd_statusphase(uint32_t tokenbits) +{ + struct jz_qtd_s *qtd; + uint32_t regval; + + /* Allocate a new Queue Element Transfer Descriptor (qTD) */ + + qtd = jz_qtd_alloc(); + if (qtd == NULL) + { + usbhost_trace1(EHCI_TRACE1_REQQTDALLOC_FAILED, 0); + return NULL; + } + + /* Mark this as the end of the list (this will be overwritten if another + * qTD is added after this one). + */ + + qtd->hw.nqp = jz_swap32(QTD_NQP_T); + qtd->hw.alt = jz_swap32(QTD_AQP_T); + + /* Write qTD token: + * + * FIELD DESCRIPTION VALUE/SOURCE + * -------- ------------------------------- -------------------- + * STATUS Status QTD_TOKEN_ACTIVE + * PID PID Code Contained in tokenbits + * CERR Error Counter 3 + * CPAGE Current Page 0 + * IOC Interrupt on complete QTD_TOKEN_IOC + * NBYTES Total Bytes to Transfer 0 + * TOGGLE Data Toggle Contained in tokenbits + */ + + regval = tokenbits | QTD_TOKEN_ACTIVE | QTD_TOKEN_IOC | + ((uint32_t)3 << QTD_TOKEN_CERR_SHIFT); + + qtd->hw.token = jz_swap32(regval); + return qtd; +} + +/**************************************************************************** + * Name: jz_async_setup + * + * Description: + * Process a IN or OUT request on any asynchronous endpoint (bulk or + * control). This function will enqueue the request and wait for it to + * complete. Bulk data transfers differ in that req == NULL and there are + * not SETUP or STATUS phases. + * + * This is a blocking function; it will not return until the control + * transfer has completed. + * + * Assumption: The caller holds the EHCI lock. + * + * Returned Value: + * Zero (OK) is returned on success; a negated errno value is return on + * any failure. + * + ****************************************************************************/ + +static int jz_async_setup(struct jz_rhport_s *rhport, + struct jz_epinfo_s *epinfo, + const struct usb_ctrlreq_s *req, + uint8_t *buffer, size_t buflen) +{ + struct jz_qh_s *qh; + struct jz_qtd_s *qtd; + uintptr_t physaddr; + uint32_t *flink; + uint32_t *alt; + uint32_t toggle; + bool dirin = false; + int ret; + + /* Terse output only if we are tracing */ + +#ifdef CONFIG_USBHOST_TRACE + usbhost_vtrace2(EHCI_VTRACE2_ASYNCXFR, epinfo->epno, buflen); +#else + jhzuinfo("RHport%d EP%d: buffer=%p, buflen=%d, req=%p\n", + RHPORT(rhport), epinfo->epno, buffer, buflen, req); +#endif + + DEBUGASSERT(rhport && epinfo); + + /* A buffer may or may be supplied with an EP0 SETUP transfer. A buffer + * will always be present for normal endpoint data transfers. + */ + + DEBUGASSERT(req || (buffer && buflen > 0)); + + /* Create and initialize a Queue Head (QH) structure for this transfer */ + + qh = jz_qh_create(rhport, epinfo); + if (qh == NULL) + { + usbhost_trace1(EHCI_TRACE1_QHCREATE_FAILED, 0); + return -ENOMEM; + } + + /* Initialize the QH link and get the next data toggle (not used for SETUP + * transfers) + */ + + flink = &qh->hw.overlay.nqp; + toggle = (uint32_t)epinfo->toggle << QTD_TOKEN_TOGGLE_SHIFT; + ret = -EIO; + + /* Is there an EP0 SETUP request? If so, req will be non-NULL and we will + * queue two or three qTDs: + * + * 1) One for the SETUP phase, + * 2) One for the DATA phase (if there is data), and + * 3) One for the STATUS phase. + * + * If this is not an EP0 SETUP request, then only a data transfer will be + * enqueued. + */ + + if (req != NULL) + { + /* Allocate a new Queue Element Transfer Descriptor (qTD) for the SETUP + * phase of the request sequence. + */ + + qtd = jz_qtd_setupphase(epinfo, req); + if (qtd == NULL) + { + usbhost_trace1(EHCI_TRACE1_QTDSETUP_FAILED, 0); + goto errout_with_qh; + } + + /* Link the new qTD to the QH head. */ + + physaddr = jz_physramaddr((uintptr_t)qtd); + *flink = jz_swap32(physaddr); + + /* Get the new forward link pointer and data toggle */ + + flink = &qtd->hw.nqp; + toggle = QTD_TOKEN_TOGGLE; + } + + /* A buffer may or may be supplied with an EP0 SETUP transfer. A buffer + * will always be present for normal endpoint data transfers. + */ + + alt = NULL; + if (buffer != NULL && buflen > 0) + { + uint32_t tokenbits; + + /* Extra TOKEN bits include the data toggle, the data PID, and if + * there is no request, an indication to interrupt at the end of this + * transfer. + */ + + tokenbits = toggle; + + /* Get the data token direction. + * + * If this is a SETUP request, use the direction contained in the + * request. The IOC bit is not set. + */ + + if (req) + { + if ((req->type & USB_REQ_DIR_MASK) == USB_REQ_DIR_IN) + { + tokenbits |= QTD_TOKEN_PID_IN; + dirin = true; + } + else + { + tokenbits |= QTD_TOKEN_PID_OUT; + dirin = false; + } + } + + /* Otherwise, the endpoint is uni-directional. Get the direction from + * the epinfo structure. Since this is not an EP0 SETUP request, + * nothing follows the data and we want the IOC interrupt when the + * data transfer completes. + */ + + else if (epinfo->dirin) + { + tokenbits |= (QTD_TOKEN_PID_IN | QTD_TOKEN_IOC); + dirin = true; + } + else + { + tokenbits |= (QTD_TOKEN_PID_OUT | QTD_TOKEN_IOC); + dirin = false; + } + + /* Allocate a new Queue Element Transfer Descriptor (qTD) for the data + * buffer. + */ + + qtd = jz_qtd_dataphase(epinfo, buffer, buflen, tokenbits); + if (qtd == NULL) + { + usbhost_trace1(EHCI_TRACE1_QTDDATA_FAILED, 0); + goto errout_with_qh; + } + + /* Link the new qTD to either QH head of the SETUP qTD. */ + + physaddr = jz_physramaddr((uintptr_t)qtd); + *flink = jz_swap32(physaddr); + + /* Set the forward link pointer to this new qTD */ + + flink = &qtd->hw.nqp; + + /* If this was an IN transfer, then setup a pointer alternate link. + * The EHCI hardware will use this link if a short packet is received. + */ + + if (dirin) + { + alt = &qtd->hw.alt; + } + } + + /* If this is an EP0 SETUP request, then enqueue one more qTD for the + * STATUS phase transfer. + */ + + if (req != NULL) + { + /* Extra TOKEN bits include the data toggle and the correct data PID. */ + + uint32_t tokenbits = toggle; + + /* The status phase direction is the opposite of the data phase. If + * this is an IN request, then we received the buffer and we will send + * the zero length packet handshake. + */ + + if ((req->type & USB_REQ_DIR_MASK) == USB_REQ_DIR_IN) + { + tokenbits |= QTD_TOKEN_PID_OUT; + } + + /* Otherwise, this in an OUT request. We send the buffer and we expect + * to receive the NULL packet handshake. + */ + + else + { + tokenbits |= QTD_TOKEN_PID_IN; + } + + /* Allocate a new Queue Element Transfer Descriptor (qTD) for the + * status + */ + + qtd = jz_qtd_statusphase(tokenbits); + if (qtd == NULL) + { + usbhost_trace1(EHCI_TRACE1_QTDSTATUS_FAILED, 0); + goto errout_with_qh; + } + + /* Link the new qTD to either the SETUP or data qTD. */ + + physaddr = jz_physramaddr((uintptr_t)qtd); + *flink = jz_swap32(physaddr); + + /* In an IN data qTD was also enqueued, then linked the data qTD's + * alternate pointer to this STATUS phase qTD in order to handle short + * transfers. + */ + + if (alt) + { + *alt = jz_swap32(physaddr); + } + } + + /* Add the new QH to the head of the asynchronous queue list */ + + jz_qh_enqueue(&g_asynchead, qh); + return OK; + + /* Clean-up after an error */ + +errout_with_qh: + jz_qh_discard(qh); + return ret; +} + +/**************************************************************************** + * Name: jz_intr_setup + * + * Description: + * Process a IN or OUT request on any interrupt endpoint by inserting a qTD + * into the periodic frame list. + * + * Paragraph 4.10.7 "Adding Interrupt Queue Heads to the Periodic Schedule" + * "The link path(s) from the periodic frame list to a queue head + * establishes in which frames a transaction can be executed for the + * queue head. Queue heads are linked into the periodic schedule so they + * are polled at the appropriate rate. System software sets a bit in a + * queue head's S-Mask to indicate which micro-frame with-in a 1 + * millisecond period a transaction should be executed for the queue + * head. Software must ensure that all queue heads in the periodic + * schedule have S-Mask set to a non-zero value. An S-mask with a zero + * value in the context of the periodic schedule yields undefined + * results. + * + * "If the desired poll rate is greater than one frame, system software + * can use a combination of queue head linking and S-Mask values to + * spread interrupts of equal poll rates through the schedule so that the + * periodic bandwidth is allocated and managed in the most efficient + * manner possible." + * + * Paragraph 4.6 "Periodic Schedule" + * + * "The periodic schedule is used to manage all isochronous and interrupt + * transfer streams. The base of the periodic schedule is the periodic + * frame list. Software links schedule data structures to the periodic + * frame list to produce a graph of scheduled data structures. The graph + * represents an appropriate sequence of transactions on the USB. ... + * isochronous transfers (using iTDs and siTDs) with a period of one are + * linked directly to the periodic frame list. Interrupt transfers (are + * managed with queue heads) and isochronous streams with periods other + * than one are linked following the period-one iTD/siTDs. Interrupt + * queue heads are linked into the frame list ordered by poll rate. + * Longer poll rates are linked first (e.g. closest to the periodic + * frame list), followed by shorter poll rates, with queue heads with a + * poll rate of one, on the very end." + * + * Assumption: The caller holds the EHCI lock. + * + * Returned Value: + * Zero (OK) is returned on success; a negated errno value is return on + * any failure. + * + ****************************************************************************/ + +#ifndef CONFIG_USBHOST_INT_DISABLE +static int jz_intr_setup(struct jz_rhport_s *rhport, + struct jz_epinfo_s *epinfo, + uint8_t *buffer, size_t buflen) +{ + struct jz_qh_s *qh; + struct jz_qtd_s *qtd; + uintptr_t physaddr; + uint32_t tokenbits; + uint32_t regval; + int ret; + + /* Terse output only if we are tracing */ + +#ifdef CONFIG_USBHOST_TRACE + usbhost_vtrace2(EHCI_VTRACE2_INTRXFR, epinfo->epno, buflen); +#else + jhzuinfo("RHport%d EP%d: buffer=%p, buflen=%d\n", + RHPORT(rhport), epinfo->epno, buffer, buflen); +#endif + + DEBUGASSERT(rhport && epinfo && buffer && buflen > 0); + + /* Create and initialize a Queue Head (QH) structure for this transfer */ + + qh = jz_qh_create(rhport, epinfo); + if (qh == NULL) + { + usbhost_trace1(EHCI_TRACE1_QHCREATE_FAILED, 0); + return -ENOMEM; + } + + /* Extra TOKEN bits include the data toggle, the data PID, and an + * indication to interrupt at the end of this transfer. + */ + + tokenbits = (uint32_t)epinfo->toggle << QTD_TOKEN_TOGGLE_SHIFT; + + /* Get the data token direction. */ + + if (epinfo->dirin) + { + tokenbits |= (QTD_TOKEN_PID_IN | QTD_TOKEN_IOC); + } + else + { + tokenbits |= (QTD_TOKEN_PID_OUT | QTD_TOKEN_IOC); + } + + /* Allocate a new Queue Element Transfer Descriptor (qTD) for the data + * buffer. + */ + + qtd = jz_qtd_dataphase(epinfo, buffer, buflen, tokenbits); + if (qtd == NULL) + { + usbhost_trace1(EHCI_TRACE1_QTDDATA_FAILED, 0); + ret = -ENOMEM; + goto errout_with_qh; + } + + /* Link the new qTD to the QH. */ + + physaddr = jz_physramaddr((uintptr_t)qtd); + qh->hw.overlay.nqp = jz_swap32(physaddr); + + /* Request to disable the periodic schedule */ + + regval = jz_getreg(&HCOR->usbcmd); + regval &= ~EHCI_USBCMD_PSEN; + jz_putreg(regval, &HCOR->usbcmd); + + /* Wait for Periodic Schedule Status (PSS) bit in USBSTS to clear, + * confirming DMA engine has stopped. + */ + + while ((jz_getreg(&HCOR->usbsts) & EHCI_USBSTS_PSS) != 0) + { + } + + /* Add the new QH to the head of the interrupt transfer list */ + + jz_qh_enqueue(&g_intrhead, qh); + + /* Re-enable the periodic schedule */ + + regval |= EHCI_USBCMD_PSEN; + jz_putreg(regval, &HCOR->usbcmd); + + while ((jz_getreg(&HCOR->usbsts) & EHCI_USBSTS_PSS) == 0) + { + } + + return OK; + + /* Clean-up after an error */ + +errout_with_qh: + jz_qh_discard(qh); + return ret; +} +#endif /* CONFIG_USBHOST_INT_DISABLE */ + +/**************************************************************************** + * Name: jz_transfer_wait + * + * Description: + * Wait for an IN or OUT transfer to complete. + * + * Assumption: The caller holds the EHCI lock. The caller must be aware + * that the EHCI lock will released while waiting for the transfer to + * complete, but will be re-acquired when before returning. The state of + * EHCI resources could be very different upon return. + * + * Returned Value: + * On success, this function returns the number of bytes actually + * transferred. For control transfers, this size includes the size of the + * control request plus the size of the data (which could be short); for + * bulk transfers, this will be the number of data bytes transfers (which + * could be short). + * + ****************************************************************************/ + +static ssize_t jz_transfer_wait(struct jz_epinfo_s *epinfo) +{ + int ret; + int ret2; + + /* Release the EHCI semaphore while we wait. Other threads need the + * opportunity to access the EHCI resources while we wait. + * + * REVISIT: Is this safe? NO. This is a bug and needs rethinking. + * We need to lock all of the port-resources (not EHCI common) until + * the transfer is complete. But we can't use the common EHCI lock + * or we will deadlock while waiting (because the working thread that + * wakes this thread up needs the lock). + */ +#warning REVISIT + nxrmutex_unlock(&g_ehci.lock); + + /* Wait for the IOC completion event */ + + ret = jz_ioc_wait(epinfo); + + /* Re-acquire the EHCI semaphore. The caller expects to be holding + * this upon return. + */ + + ret2 = nxrmutex_lock(&g_ehci.lock); + if (ret2 < 0) + { + ret = ret2; + } + + /* Did jz_ioc_wait() or nxrmutex_lock report an error? */ + + if (ret < 0) + { + usbhost_trace1(EHCI_TRACE1_TRANSFER_FAILED, -ret); + epinfo->iocwait = false; + return (ssize_t)ret; + } + + /* Transfer completed successfully. Return the number of bytes + * transferred. + */ + + return epinfo->xfrd; +} + +/**************************************************************************** + * Name: jz_ioc_async_setup + * + * Description: + * Setup to receive an asynchronous notification when a transfer completes. + * + * Input Parameters: + * epinfo - The IN or OUT endpoint descriptor for the device endpoint on + * which the transfer will be performed. + * callback - The function to be called when the completes + * arg - An arbitrary argument that will be provided with the callback. + * + * Returned Value: + * None + * + * Assumptions: + * - Called from the interrupt level + * + ****************************************************************************/ + +#ifdef CONFIG_USBHOST_ASYNCH +static inline int jz_ioc_async_setup(struct jz_rhport_s *rhport, + struct jz_epinfo_s *epinfo, + usbhost_asynch_t callback, void *arg) +{ + irqstate_t flags; + int ret = -ENODEV; + + DEBUGASSERT(rhport && epinfo && !epinfo->iocwait && + epinfo->callback == NULL); + + /* Is the device still connected? */ + + flags = enter_critical_section(); + if (rhport->connected) + { + /* Then save callback information to used when either (1) the + * device is disconnected, or (2) the transfer completes. + */ + + epinfo->iocwait = false; /* No synchronous wakeup */ + epinfo->status = 0; /* No status yet */ + epinfo->xfrd = 0; /* Nothing transferred yet */ + epinfo->result = -EBUSY; /* Transfer in progress */ + epinfo->callback = callback; /* Asynchronous callback */ + epinfo->arg = arg; /* Argument that accompanies the callback */ + ret = OK; /* We are good to go */ + } + + leave_critical_section(flags); + return ret; +} +#endif + +/**************************************************************************** + * Name: jz_asynch_completion + * + * Description: + * This function is called at the interrupt level when an asynchronous + * transfer completes. It performs the pending callback. + * + * Input Parameters: + * epinfo - The IN or OUT endpoint descriptor for the device endpoint on + * which the transfer was performed. + * + * Returned Value: + * None + * + * Assumptions: + * - Called from the interrupt level + * + ****************************************************************************/ + +#ifdef CONFIG_USBHOST_ASYNCH +static void jz_asynch_completion(struct jz_epinfo_s *epinfo) +{ + usbhost_asynch_t callback; + ssize_t nbytes; + void *arg; + int result; + + DEBUGASSERT(epinfo != NULL && epinfo->iocwait == false && + epinfo->callback != NULL); + + /* Extract and reset the callback info */ + + callback = epinfo->callback; + arg = epinfo->arg; + result = epinfo->result; + nbytes = epinfo->xfrd; + + epinfo->callback = NULL; + epinfo->arg = NULL; + epinfo->result = OK; + epinfo->iocwait = false; + + /* Then perform the callback. Provide the number of bytes successfully + * transferred or the negated errno value in the event of a failure. + */ + + if (result < 0) + { + nbytes = (ssize_t)result; + } + + callback(arg, nbytes); +} +#endif + +/**************************************************************************** + * Name: jz_qtd_ioccheck + * + * Description: + * This function is a jz_qtd_foreach() callback function. It services + * one qTD in the asynchronous queue. It removes all of the qTD + * structures that are no longer active. + * + ****************************************************************************/ + +static int jz_qtd_ioccheck(struct jz_qtd_s *qtd, uint32_t **bp, void *arg) +{ + struct jz_epinfo_s *epinfo = (struct jz_epinfo_s *)arg; + DEBUGASSERT(qtd && epinfo); + + /* Make sure we reload the QTD from memory */ + + up_flush_dcache((uintptr_t)&qtd->hw, + (uintptr_t)&qtd->hw + sizeof(struct ehci_qtd_s)); + jz_qtd_print(qtd); + + /* Remove the qTD from the list + * + * NOTE that we don't check if the qTD is active nor do we check if there + * are any errors reported in the qTD. If the transfer halted due to + * an error, then qTDs in the list after the error qTD will still appear + * to be active. + */ + + **bp = qtd->hw.nqp; + + /* Subtract the number of bytes left untransferred. The epinfo->xfrd + * field is initialized to the total number of bytes to be transferred + * (all qTDs in the list). We subtract out the number of untransferred + * bytes on each transfer and the final result will be the number of bytes + * actually transferred. + */ + + epinfo->xfrd -= (jz_swap32(qtd->hw.token) & QTD_TOKEN_NBYTES_MASK) >> + QTD_TOKEN_NBYTES_SHIFT; + + /* Release this QH by returning it to the free list */ + + jz_qtd_free(qtd); + return OK; +} + +/**************************************************************************** + * Name: jz_qh_ioccheck + * + * Description: + * This function is a jz_qh_foreach() callback function. It services one + * QH in the asynchronous queue. It check all attached qTD structures and + * remove all of the structures that are no longer active. if all of the + * qTD structures are removed, then QH itself will also be removed. + * + ****************************************************************************/ + +static int jz_qh_ioccheck(struct jz_qh_s *qh, uint32_t **bp, void *arg) +{ + struct jz_epinfo_s *epinfo; + uint32_t token; + int ret; + + DEBUGASSERT(qh && bp); + + /* Make sure we reload the QH from memory */ + + up_flush_dcache((uintptr_t)&qh->hw, + (uintptr_t)&qh->hw + sizeof(struct ehci_qh_s)); + jz_qh_print(qh); + + /* Get the endpoint info pointer from the extended QH data. Only the + * g_asynchead QH can have a NULL epinfo field. + */ + + epinfo = qh->epinfo; + DEBUGASSERT(epinfo); + + /* Paragraph 3.6.3: "The nine DWords in [the Transfer Overlay] area + * represent a transaction working space for the host controller. The + * general operational model is that the host controller can detect + * whether the overlay area contains a description of an active transfer. + * If it does not contain an active transfer, then it follows the Queue + * Head Horizontal Link Pointer to the next queue head. The host + * controller will never follow the Next Transfer Queue Element or + * Alternate Queue Element pointers unless it is actively attempting to + * advance the queue ..." + */ + + /* Is the qTD still active? */ + + token = jz_swap32(qh->hw.overlay.token); + usbhost_vtrace2(EHCI_VTRACE2_IOCCHECK, epinfo->epno, token); + + if ((token & QH_TOKEN_ACTIVE) != 0) + { + /* Yes... we cannot process the QH while it is still active. Return + * zero to visit the next QH in the list. + */ + + *bp = &qh->hw.hlp; + return OK; + } + + /* Remove all active, attached qTD structures from the inactive QH */ + + ret = jz_qtd_foreach(qh, jz_qtd_ioccheck, (void *)qh->epinfo); + if (ret < 0) + { + usbhost_trace1(EHCI_TRACE1_QTDFOREACH_FAILED, -ret); + } + + /* If there is no longer anything attached to the QH, then remove it from + * the asynchronous queue. + */ + + if ((jz_swap32(qh->fqp) & QTD_NQP_T) != 0) + { + /* Set the forward link of the previous QH to point to the next + * QH in the list. + */ + + **bp = qh->hw.hlp; + up_clean_dcache((uintptr_t)*bp, (uintptr_t)*bp + sizeof(uint32_t)); + + /* Check for errors, update the data toggle */ + + if ((token & QH_TOKEN_ERRORS) == 0) + { + /* No errors.. Save the last data toggle value */ + + epinfo->toggle = (token >> QTD_TOKEN_TOGGLE_SHIFT) & 1; + + /* Report success */ + + epinfo->status = 0; + epinfo->result = OK; + } + else + { + /* An error occurred */ + + epinfo->status = (token & QH_TOKEN_STATUS_MASK) >> + QH_TOKEN_STATUS_SHIFT; + + /* The HALT condition is set on a variety of conditions: babble, + * error counter countdown to zero, or a STALL. If we can rule + * out babble (babble bit not set) and if the error counter is + * non-zero, then we can assume a STALL. In this case, we return + * -PERM to inform the class driver of the stall condition. + */ + + if ((token & (QH_TOKEN_BABBLE | QH_TOKEN_HALTED)) == + QH_TOKEN_HALTED && + (token & QH_TOKEN_CERR_MASK) != 0) + { + /* It is a stall, Note that the data toggle is reset + * after the stall. + */ + + usbhost_trace2(EHCI_TRACE2_EPSTALLED, epinfo->epno, token); + epinfo->result = -EPERM; + epinfo->toggle = 0; + } + else + { + /* Otherwise, it is some kind of data transfer error */ + + usbhost_trace2(EHCI_TRACE2_EPIOERROR, epinfo->epno, token); + epinfo->result = -EIO; + } + } + + /* Is there a thread waiting for this transfer to complete? */ + + if (epinfo->iocwait) + { + /* Yes... wake it up */ + + nxsem_post(&epinfo->iocsem); + epinfo->iocwait = 0; + } + +#ifdef CONFIG_USBHOST_ASYNCH + /* No.. Is there a pending asynchronous transfer? */ + + else if (epinfo->callback != NULL) + { + /* Yes.. perform the callback */ + + jz_asynch_completion(epinfo); + } +#endif + + /* Then release this QH by returning it to the free list */ + + jz_qh_free(qh); + } + else + { + /* Otherwise, the horizontal link pointer of this QH will become the + * next back pointer. + */ + + *bp = &qh->hw.hlp; + } + + return OK; +} + +/**************************************************************************** + * Name: jz_qtd_cancel + * + * Description: + * This function is a jz_qtd_foreach() callback function. It removes each + * qTD attached to a QH. + * + ****************************************************************************/ + +static int jz_qtd_cancel(struct jz_qtd_s *qtd, uint32_t **bp, void *arg) +{ + DEBUGASSERT(qtd != NULL && bp != NULL); + + /* Make sure we reload the QH from memory */ + + up_flush_dcache((uintptr_t)&qtd->hw, + (uintptr_t)&qtd->hw + sizeof(struct ehci_qtd_s)); + jz_qtd_print(qtd); + + /* Remove the qTD from the list + * + * NOTE that we don't check if the qTD is active nor do we check if there + * are any errors reported in the qTD. If the transfer halted due to + * an error, then qTDs in the list after the error qTD will still appear + * to be active. + * + * REVISIT: There is a race condition here that needs to be resolved. + */ + + **bp = qtd->hw.nqp; + + /* Release this QH by returning it to the free list */ + + jz_qtd_free(qtd); + return OK; +} + +/**************************************************************************** + * Name: jz_qh_cancel + * + * Description: + * This function is a jz_qh_foreach() callback function. It cancels + * one QH in the asynchronous queue. It will remove all attached qTD + * structures and remove all of the structures that are no longer active. + * Then QH itself will also be removed. + * + ****************************************************************************/ + +static int jz_qh_cancel(struct jz_qh_s *qh, uint32_t **bp, void *arg) +{ + struct jz_epinfo_s *epinfo = (struct jz_epinfo_s *)arg; + uint32_t regval; + int ret; + + DEBUGASSERT(qh != NULL && bp != NULL && epinfo != NULL); + + /* Make sure we reload the QH from memory */ + + up_flush_dcache((uintptr_t)&qh->hw, + (uintptr_t)&qh->hw + sizeof(struct ehci_qh_s)); + jz_qh_print(qh); + + /* Check if this is the QH that we are looking for */ + + if (qh->epinfo != epinfo) + { + /* No... keep looking */ + + return OK; + } + + /* Disable both the asynchronous and period schedules */ + + regval = jz_getreg(&HCOR->usbcmd); + jz_putreg(regval & ~(EHCI_USBCMD_ASEN | EHCI_USBCMD_PSEN), + &HCOR->usbcmd); + + /* Remove the QH from the list + * + * NOTE that we don't check if the qTD is active nor do we check if there + * are any errors reported in the qTD. If the transfer halted due to + * an error, then qTDs in the list after the error qTD will still appear + * to be active. + * + * REVISIT: There is a race condition here that needs to be resolved. + */ + + **bp = qh->hw.hlp; + up_flush_dcache((uintptr_t)*bp, (uintptr_t)*bp + sizeof(uint32_t)); + + /* Re-enable the schedules (if they were enabled before. */ + + jz_putreg(regval, &HCOR->usbcmd); + + /* Remove all active, attached qTD structures from the removed QH */ + + ret = jz_qtd_foreach(qh, jz_qtd_cancel, NULL); + if (ret < 0) + { + usbhost_trace1(EHCI_TRACE1_QTDFOREACH_FAILED, -ret); + } + + /* Then release this QH by returning it to the free list. Return 1 + * to stop the traverse without an error. + */ + + jz_qh_free(qh); + return 1; +} + +/**************************************************************************** + * Name: jz_ioc_bottomhalf + * + * Description: + * EHCI USB Interrupt (USBINT) "Bottom Half" interrupt handler + * + * "The Host Controller sets this bit to 1 on the completion of a USB + * transaction, which results in the retirement of a Transfer Descriptor + * that had its IOC bit set. + * + * "The Host Controller also sets this bit to 1 when a short packet is + * detected (actual number of bytes received was less than the expected + * number of bytes)." + * + * Assumptions: The caller holds the EHCI lock + * + ****************************************************************************/ + +static inline void jz_ioc_bottomhalf(void) +{ + struct jz_qh_s *qh; + uint32_t *bp; + int ret; + + /* Check the Asynchronous Queue */ + + /* Make sure that the head of the asynchronous queue is invalidated */ + + up_flush_dcache((uintptr_t)&g_asynchead.hw, + (uintptr_t)&g_asynchead.hw + + sizeof(struct ehci_qh_s)); + + /* Set the back pointer to the forward QH pointer of the asynchronous + * queue head. + */ + + bp = (uint32_t *)&g_asynchead.hw.hlp; + qh = (struct jz_qh_s *)jz_virtramaddr(jz_swap32(*bp) & QH_HLP_MASK); + + /* If the asynchronous queue is empty, then the forward point in the + * asynchronous queue head will point back to the queue head. + */ + + if (qh && qh != &g_asynchead) + { + /* Then traverse and operate on every QH and qTD in the asynchronous + * queue + */ + + ret = jz_qh_foreach(qh, &bp, jz_qh_ioccheck, NULL); + if (ret < 0) + { + usbhost_trace1(EHCI_TRACE1_QHFOREACH_FAILED, -ret); + } + } + +#ifndef CONFIG_USBHOST_INT_DISABLE + /* Check the Interrupt Queue */ + + /* Make sure that the head of the interrupt queue is invalidated */ + + up_flush_dcache((uintptr_t)&g_intrhead.hw, + (uintptr_t)&g_intrhead.hw + sizeof(struct ehci_qh_s)); + + /* Set the back pointer to the forward qTD pointer of the asynchronous + * queue head. + */ + + bp = (uint32_t *)&g_intrhead.hw.hlp; + qh = (struct jz_qh_s *)jz_virtramaddr(jz_swap32(*bp) & QH_HLP_MASK); + if (qh) + { + /* Then traverse and operate on every QH and qTD in the asynchronous + * queue. + */ + + ret = jz_qh_foreach(qh, &bp, jz_qh_ioccheck, NULL); + if (ret < 0) + { + usbhost_trace1(EHCI_TRACE1_QHFOREACH_FAILED, -ret); + } + } +#endif +} + +/**************************************************************************** + * Name: jz_portsc_bottomhalf + * + * Description: + * EHCI Port Change Detect "Bottom Half" interrupt handler + * + * "The Host Controller sets this bit to a one when any port for which the + * Port Owner bit is set to zero ... has a change bit transition from a + * zero to a one or a Force Port Resume bit transition from a zero to a + * one as a result of a J-K transition detected on a suspended port. + * This bit will also be set as a result of the Connect Status Change + * being set to a one after system software has relinquished ownership of + * a connected port by writing a one to a port's Port Owner bit... + * + * "This bit is allowed to be maintained in the Auxiliary power well. + * Alternatively, it is also acceptable that on a D3 to D0 transition of + * the EHCI HC device, this bit is loaded with the OR of all of the PORTSC + * change bits (including: Force port resume, over-current change, + * enable/disable change and connect status change)." + * + ****************************************************************************/ + +static inline void jz_portsc_bottomhalf(void) +{ + struct jz_rhport_s *rhport; + struct usbhost_hubport_s *hport; + uint32_t portsc; + int rhpndx; + + /* Handle root hub status change on each root port */ + + for (rhpndx = 0; rhpndx < JZ_EHCI_NRHPORT; rhpndx++) + { + rhport = &g_ehci.rhport[rhpndx]; + portsc = jz_getreg(&HCOR->portsc[rhpndx]); + + usbhost_vtrace2(EHCI_VTRACE2_PORTSC, rhpndx + 1, portsc); + + /* Handle port connection status change (CSC) events */ + + if ((portsc & EHCI_PORTSC_CSC) != 0) + { + usbhost_vtrace1(EHCI_VTRACE1_PORTSC_CSC, portsc); + + /* Check current connect status */ + + if ((portsc & EHCI_PORTSC_CCS) != 0) + { + /* Connected ... Did we just become connected? */ + + if (!rhport->connected) + { + /* Yes.. connected. */ + + rhport->connected = true; + + usbhost_vtrace2(EHCI_VTRACE2_PORTSC_CONNECTED, + rhpndx + 1, g_ehci.pscwait); + + /* Notify any waiters */ + + if (g_ehci.pscwait) + { + nxsem_post(&g_ehci.pscsem); + g_ehci.pscwait = false; + } + } + else + { + usbhost_vtrace1(EHCI_VTRACE1_PORTSC_CONNALREADY, portsc); + } + } + else + { + /* Disconnected... Did we just become disconnected? */ + + if (rhport->connected) + { + /* Yes.. disconnect the device */ + + usbhost_vtrace2(EHCI_VTRACE2_PORTSC_DISCONND, + rhpndx + 1, g_ehci.pscwait); + + rhport->connected = false; + + /* Are we bound to a class instance? */ + + hport = &rhport->hport.hport; + if (hport->devclass) + { + /* Yes.. Disconnect the class */ + + CLASS_DISCONNECTED(hport->devclass); + hport->devclass = NULL; + } + + /* Notify any waiters for the Root Hub Status change + * event. + */ + + if (g_ehci.pscwait) + { + nxsem_post(&g_ehci.pscsem); + g_ehci.pscwait = false; + } + } + else + { + usbhost_vtrace1(EHCI_VTRACE1_PORTSC_DISCALREADY, portsc); + } + } + } + + /* Clear all pending port interrupt sources by writing a '1' to the + * corresponding bit in the PORTSC register. In addition, we need + * to preserve the values of all R/W bits (RO bits don't matter) + */ + + jz_putreg(portsc, &HCOR->portsc[rhpndx]); + } +} + +/**************************************************************************** + * Name: jz_syserr_bottomhalf + * + * Description: + * EHCI Host System Error "Bottom Half" interrupt handler + * + * "The Host Controller sets this bit to 1 when a serious error occurs + * during a host system access involving the Host Controller module. ... + * When this error occurs, the Host Controller clears the Run/Stop bit in + * the Command register to prevent further execution of the scheduled TDs." + * + ****************************************************************************/ + +static inline void jz_syserr_bottomhalf(void) +{ + usbhost_trace1(EHCI_TRACE1_SYSERR_INTR, 0); + DEBUGPANIC(); +} + +/**************************************************************************** + * Name: jz_async_advance_bottomhalf + * + * Description: + * EHCI Async Advance "Bottom Half" interrupt handler + * + * "System software can force the host controller to issue an interrupt the + * next time the host controller advances the asynchronous schedule by + * writing a one to the Interrupt on Async Advance Doorbell bit in the + * USBCMD register. This status bit indicates the assertion of that + * interrupt source." + * + ****************************************************************************/ + +static inline void jz_async_advance_bottomhalf(void) +{ + usbhost_vtrace1(EHCI_VTRACE1_AAINTR, 0); + + /* REVISIT: Could remove all tagged QH entries here */ +} + +/**************************************************************************** + * Name: jz_ehci_bottomhalf + * + * Description: + * EHCI "Bottom Half" interrupt handler + * + ****************************************************************************/ + +static void jz_ehci_bottomhalf(void *arg) +{ + uint32_t pending = (uint32_t)arg; + + /* We need to have exclusive access to the EHCI data structures. Waiting + * here is not a good thing to do on the worker thread, but there is no + * real option (other than to reschedule and delay). + */ + + nxrmutex_lock(&g_ehci.lock); + + /* Handle all unmasked interrupt sources */ + + /* USB Interrupt (USBINT) + * + * "The Host Controller sets this bit to 1 on the completion of a USB + * transaction, which results in the retirement of a Transfer Descriptor + * that had its IOC bit set. + * + * "The Host Controller also sets this bit to 1 when a short packet is + * detected (actual number of bytes received was less than the expected + * number of bytes)." + * + * USB Error Interrupt (USBERRINT) + * + * "The Host Controller sets this bit to 1 when completion of a USB + * transaction results in an error condition (e.g., error counter + * underflow). If the TD on which the error interrupt occurred also + * had its IOC bit set, both this bit and USBINT bit are set. ..." + * + * We do the same thing in either case: Traverse the asynchronous queue + * and remove all of the transfers that are no longer active. + */ + + if ((pending & (EHCI_INT_USBINT | EHCI_INT_USBERRINT)) != 0) + { + if ((pending & EHCI_INT_USBERRINT) != 0) + { + usbhost_trace1(EHCI_TRACE1_USBERR_INTR, pending); + } + else + { + usbhost_vtrace1(EHCI_VTRACE1_USBINTR, pending); + } + + jz_ioc_bottomhalf(); + } + + /* Port Change Detect + * + * "The Host Controller sets this bit to a one when any port for which + * the Port Owner bit is set to zero ... has a change bit transition + * from a zero to a one or a Force Port Resume bit transition from a zero + * to a one as a result of a J-K transition detected on a suspended port. + * This bit will also be set as a result of the Connect Status Change + * being set to a one after system software has relinquished ownership + * of a connected port by writing a one to a port's Port Owner bit... + * + * "This bit is allowed to be maintained in the Auxiliary power well. + * Alternatively, it is also acceptable that on a D3 to D0 transition + * of the EHCI HC device, this bit is loaded with the OR of all of the + * PORTSC change bits (including: Force port resume, over-current change, + * enable/disable change and connect status change)." + */ + + if ((pending & EHCI_INT_PORTSC) != 0) + { + jz_portsc_bottomhalf(); + } + + /* Frame List Rollover + * + * "The Host Controller sets this bit to a one when the Frame List Index + * ... rolls over from its maximum value to zero. The exact value at + * which the rollover occurs depends on the frame list size. For example, + * if the frame list size (as programmed in the Frame List Size field of + * the USBCMD register) is 1024, the Frame Index Register rolls over + * every time FRINDEX[13] toggles. Similarly, if the size is 512, the + * Host Controller sets this bit to a one every time FRINDEX[12] + * toggles." + */ + +#if 0 /* Not used */ + if ((pending & EHCI_INT_FLROLL) != 0) + { + jz_flroll_bottomhalf(); + } +#endif + + /* Host System Error + * + * "The Host Controller sets this bit to 1 when a serious error occurs + * during a host system access involving the Host Controller module. ... + * When this error occurs, the Host Controller clears the Run/Stop bit + * in the Command register to prevent further execution of the scheduled + * TDs." + */ + + if ((pending & EHCI_INT_SYSERROR) != 0) + { + jz_syserr_bottomhalf(); + } + + /* Interrupt on Async Advance + * + * "System software can force the host controller to issue an interrupt + * the next time the host controller advances the asynchronous schedule + * by writing a one to the Interrupt on Async Advance Doorbell bit in + * the USBCMD register. This status bit indicates the assertion of that + * interrupt source." + */ + + if ((pending & EHCI_INT_AAINT) != 0) + { + jz_async_advance_bottomhalf(); + } + + /* We are done with the EHCI structures */ + + nxrmutex_unlock(&g_ehci.lock); + + /* Re-enable relevant EHCI interrupts. Interrupts should still be enabled + * at the level of the AIC. + */ + + jz_putreg(EHCI_HANDLED_INTS, &HCOR->usbintr); +} + +/**************************************************************************** + * Name: jz_ehci_tophalf + * + * Description: + * EHCI "Top Half" interrupt handler + * + ****************************************************************************/ + +static int jz_ehci_tophalf(int irq, void *context, void *arg) +{ + uint32_t usbsts; + uint32_t pending; + uint32_t regval; + + /* Read Interrupt Status and mask out interrupts that are not enabled. */ + + usbsts = jz_getreg(&HCOR->usbsts); + regval = jz_getreg(&HCOR->usbintr); + +#ifdef CONFIG_USBHOST_TRACE + usbhost_vtrace1(EHCI_VTRACE1_TOPHALF, usbsts & regval); +#else + uinfo("USBSTS: %08" PRIx32 " USBINTR: %08" PRIx32 "\n", usbsts, regval); +#endif + + /* Handle all unmasked interrupt sources */ + + pending = usbsts & regval; + if (pending != 0) + { + /* Disable further EHCI interrupts so that we do not overrun the work + * queue. + */ + + jz_putreg(0, &HCOR->usbintr); + + /* Clear all pending status bits by writing the value of the pending + * interrupt bits back to the status register. + */ + + jz_putreg(usbsts & EHCI_INT_ALLINTS, &HCOR->usbsts); + + /* Schedule interrupt handling work for the high priority worker + * thread so that we are not pressed for time and so that we can + * interrupt with other USB threads gracefully. + * + * The worker should be available now because we implement a handshake + * by controlling the EHCI interrupts. + */ + + DEBUGASSERT(work_available(&g_ehci.work)); + DEBUGVERIFY(work_queue(HPWORK, &g_ehci.work, jz_ehci_bottomhalf, + (void *)pending, 0)); + } + + return OK; +} + +/**************************************************************************** + * Name: jz_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 jz_wait(struct usbhost_connection_s *conn, + struct usbhost_hubport_s **hport) +{ + irqstate_t flags; + int rhpndx; + int ret; + + /* Loop until the connection state changes on one of the root hub ports or + * until an error occurs. + */ + + flags = enter_critical_section(); + for (; ; ) + { + /* Check for a change in the connection state on any root hub port */ + + for (rhpndx = 0; rhpndx < JZ_EHCI_NRHPORT; rhpndx++) + { + struct jz_rhport_s *rhport; + struct usbhost_hubport_s *connport; + + /* Has the connection state changed on the RH port? */ + + rhport = &g_ehci.rhport[rhpndx]; + connport = &rhport->hport.hport; + if (rhport->connected != connport->connected) + { + /* Yes.. Return the RH port to inform the caller which + * port has the connection change. + */ + + connport->connected = rhport->connected; + *hport = connport; + leave_critical_section(flags); + + usbhost_vtrace2(EHCI_VTRACE2_MONWAKEUP, + rhpndx + 1, rhport->connected); + return OK; + } + } + +#ifdef CONFIG_USBHOST_HUB + /* Is a device connected to an external hub? */ + + if (g_ehci.hport) + { + volatile struct usbhost_hubport_s *connport; + + /* Yes.. return the external hub port */ + + connport = g_ehci.hport; + g_ehci.hport = NULL; + + *hport = (struct usbhost_hubport_s *)connport; + leave_critical_section(flags); + + usbhost_vtrace2(EHCI_VTRACE2_MONWAKEUP, + HPORT(connport), connport->connected); + return OK; + } +#endif + + /* No changes on any port. Wait for a connection/disconnection event + * and check again + */ + + g_ehci.pscwait = true; + ret = nxsem_wait_uninterruptible(&g_ehci.pscsem); + if (ret < 0) + { + return ret; + } + } +} + +/**************************************************************************** + * Name: jz_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 jz_rh_enumerate(struct usbhost_connection_s *conn, + struct usbhost_hubport_s *hport) +{ + struct jz_rhport_s *rhport; + volatile uint32_t *regaddr; + uint32_t regval; + int rhpndx; + + DEBUGASSERT(conn != NULL && hport != NULL); + rhpndx = hport->port; + + DEBUGASSERT(rhpndx >= 0 && rhpndx < JZ_EHCI_NRHPORT); + rhport = &g_ehci.rhport[rhpndx]; + + /* Are we connected to a device? The caller should have called the wait() + * method first to be assured that a device is connected. + */ + + while (!rhport->connected) + { + /* No, return an error */ + + usbhost_vtrace1(EHCI_VTRACE1_ENUM_DISCONN, 0); + return -ENODEV; + } + + /* USB 2.0 spec says at least 50ms delay before port reset. + * REVISIT: I think this is wrong. It needs to hold the port in + * reset for 50Msec, not wait 50Msec before resetting. + */ + + nxsig_usleep(100 * 1000); + + /* Paragraph 2.3.9: + * + * "Line Status ... These bits reflect the current logical levels of the + * D+ (bit 11) and D- (bit 10) signal lines. These bits are used for + * detection of low-speed USB devices prior to the port reset and enable + * sequence. This field is valid only when the port enable bit is zero + * and the current connect status bit is set to a one." + * + * Bits[11:10] USB State Interpretation + * ----------- --------- -------------- + * 00b SE0 Not Low-speed device, perform EHCI reset + * 10b J-state Not Low-speed device, perform EHCI reset + * 01b K-state Low-speed device, release ownership of port + */ + + regval = jz_getreg(&HCOR->portsc[rhpndx]); + if ((regval & EHCI_PORTSC_LSTATUS_MASK) == EHCI_PORTSC_LSTATUS_KSTATE) + { + /* Paragraph 2.3.9: + * + * "Port Owner ... This bit unconditionally goes to a 0b when the + * Configured bit in the CONFIGFLAG register makes a 0b to 1b + * transition. This bit unconditionally goes to 1b whenever the + * Configured bit is zero. + * + * "System software uses this field to release ownership of the + * port to a selected host controller (in the event that the + * attached device is not a high-speed device). Software writes + * a one to this bit when the attached device is not a high-speed + * device. A one in this bit means that a companion host + * controller owns and controls the port. .... + * + * Paragraph 4.2: + * + * "When a port is routed to a companion HC, it remains under the + * control of the companion HC until the device is disconnected + * from the root por ... When a disconnect occurs, the disconnect + * event is detected by both the companion HC port control and the + * EHCI port ownership control. On the event, the port ownership + * is returned immediately to the EHCI controller. The companion + * HC stack detects the disconnect and acknowledges as it would + * in an ordinary standalone implementation. Subsequent connects + * will be detected by the EHCI port register and the process will + * repeat." + */ + +#if defined(CONFIG_JZ4780_OHCI) + hport->speed = USB_SPEED_LOW; + regval |= EHCI_PORTSC_OWNER; + jz_putreg(regval, &HCOR->portsc[rhpndx]); +#else + uerr("EHCI Root hub does not support low speed, use a hub.\n"); +#endif + +#if 0 /* #ifdef CONFIG_JZ4780_OHCI */ + /* Give the port to the OHCI controller. Zero is the reset value for + * all ports; one makes the corresponding port available to OHCI. + */ + + regval = getreg32(JZ_SFR_OHCIICR); + regval |= SFR_OHCIICR_RES(rhpndx); + putreg32(regval, JZ_SFR_OHCIICR); +#endif + + /* And return a failure */ + + rhport->connected = false; + return -EPERM; + } + else + { + /* Assume full-speed for now */ + + hport->speed = USB_SPEED_FULL; + } + + /* Put the root hub port in reset. + * + * EHCI Paragraph 2.3.9: + * + * "The HCHalted bit in the USBSTS register should be a zero before + * software attempts to use [the Port Reset] bit. The host controller + * may hold Port Reset asserted to a one when the HCHalted bit is a one. + */ + + DEBUGASSERT((jz_getreg(&HCOR->usbsts) & EHCI_USBSTS_HALTED) == 0); + + /* EHCI Paragraph 2.3.9: + * + * "When software writes a one to [the Port Reset] bit (from a zero), the + * bus reset sequence as defined in the USB Specification Revision 2.0 + * is started. Software writes a zero to this bit to terminate the bus + * reset sequence. Software must keep this bit at a one long enough to + * ensure the reset sequence, as specified in the USB Specification + * Revision 2.0, completes. Note: when software writes this bit to a + * one, it must also write a zero to the Port Enable bit." + */ + + regaddr = &HCOR->portsc[RHPNDX(rhport)]; + regval = jz_getreg(regaddr); + regval &= ~EHCI_PORTSC_PE; + regval |= EHCI_PORTSC_RESET; + jz_putreg(regval, regaddr); + + /* USB 2.0 "Root hubs must provide an aggregate reset period of at least + * 50 ms." + */ + + nxsig_usleep(50 * 1000); + + regval = jz_getreg(regaddr); + regval &= ~EHCI_PORTSC_RESET; + jz_putreg(regval, regaddr); + + /* Wait for the port reset to complete + * + * Paragraph 2.3.9: + * + * "Note that when software writes a zero to this bit there may be a + * delay before the bit status changes to a zero. The bit status will + * not read as a zero until after the reset has completed. If the port + * is in high-speed mode after reset is complete, the host controller + * will automatically enable this port (e.g. set the Port Enable bit + * to a one). A host controller must terminate the reset and stabilize + * the state of the port within 2 milliseconds of software transitioning + * this bit from a one to a zero ..." + */ + + while ((jz_getreg(regaddr) & EHCI_PORTSC_RESET) != 0); + nxsig_usleep(200 * 1000); + + /* Paragraph 4.2.2: + * + * "... The reset process is actually complete when software reads a zero + * in the PortReset bit. The EHCI Driver checks the PortEnable bit in + * the PORTSC register. If set to a one, the connected device is a high- + * speed device and EHCI Driver (root hub emulator) issues a change + * report to the hub driver and the hub driver continues to enumerate + * the attached device." + * + * "At the time the EHCI Driver receives the port reset and enable + * request the LineStatus bits might indicate a low-speed device. + * Additionally, when the port reset process is complete, the PortEnable + * field may indicate that a full-speed device is attached. In either + * case the EHCI driver sets the PortOwner bit in the PORTSC register to + * a one to release port ownership to a companion host controller." + */ + + regval = jz_getreg(&HCOR->portsc[rhpndx]); + if ((regval & EHCI_PORTSC_PE) != 0) + { + /* High speed device */ + + hport->speed = USB_SPEED_HIGH; + } + else + { + /* Low- or Full- speed device. Set the port ownership bit. + * + * Paragraph 4.2: + * + * "When a port is routed to a companion HC, it remains under the + * control of the companion HC until the device is disconnected + * from the root por ... When a disconnect occurs, the disconnect + * event is detected by both the companion HC port control and the + * EHCI port ownership control. On the event, the port ownership + * is returned immediately to the EHCI controller. The companion + * HC stack detects the disconnect and acknowledges as it would + * in an ordinary standalone implementation. Subsequent connects + * will be detected by the EHCI port register and the process will + * repeat." + */ + +#if defined(CONFIG_JZ4780_OHCI) + regval |= EHCI_PORTSC_OWNER; + jz_putreg(regval, &HCOR->portsc[rhpndx]); +#else + uerr("EHCI Root hub does not support this device, use a hub.\n"); +#endif + +#if 0 /* #ifdef CONFIG_JZ4780_OHCI */ + /* Give the port to the OHCI controller. Zero is the reset value for + * all ports; one makes the corresponding port available to OHCI. + */ + + regval = getreg32(JZ_SFR_OHCIICR); + regval |= SFR_OHCIICR_RES(rhpndx); + putreg32(regval, JZ_SFR_OHCIICR); +#endif + + /* And return a failure */ + + rhport->connected = false; + return -EPERM; + } + + return OK; +} + +static int jz_enumerate(struct usbhost_connection_s *conn, + struct usbhost_hubport_s *hport) +{ + int ret; + + /* If this is a connection on the root hub, then we need to go to + * little more effort to get the device speed. If it is a connection + * on an external hub, then we already have that information. + */ + + DEBUGASSERT(hport); +#ifdef CONFIG_USBHOST_HUB + if (ROOTHUB(hport)) +#endif + { + ret = jz_rh_enumerate(conn, hport); + if (ret < 0) + { + return ret; + } + } + + /* Then let the common usbhost_enumerate do the real enumeration. */ + + usbhost_vtrace1(EHCI_VTRACE1_CLASSENUM, HPORT(hport)); + ret = usbhost_enumerate(hport, &hport->devclass); + if (ret < 0) + { + /* Failed to enumerate */ + + usbhost_trace2(EHCI_TRACE2_CLASSENUM_FAILED, HPORT(hport), -ret); + + /* If this is a root hub port, then marking the hub port not connected + * will cause jz_wait() to return and we will try the connection + * again. + */ + + hport->connected = false; + } + + return ret; +} + +/**************************************************************************** + * Name: jz_ep0configure + * + * Description: + * Configure endpoint 0. This method is normally used internally by the + * enumerate() method but is made available at the interface to support + * an external implementation of the enumeration logic. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * funcaddr - The USB address of the function containing the endpoint that + * EP0 controls. A funcaddr of zero will be received if no address is + * yet assigned to the device. + * speed - The speed of the port USB_SPEED_LOW, _FULL, or _HIGH + * maxpacketsize - The maximum number of bytes that can be sent to or + * received from the endpoint in a single data packet + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure. + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static int jz_ep0configure(struct usbhost_driver_s *drvr, + usbhost_ep_t ep0, uint8_t funcaddr, + uint8_t speed, uint16_t maxpacketsize) +{ + struct jz_epinfo_s *epinfo = (struct jz_epinfo_s *)ep0; + int ret; + + usbhost_vtrace2(EHCI_VTRACE2_EP0CONFIG, speed, funcaddr); + DEBUGASSERT(drvr != NULL && epinfo != NULL && maxpacketsize < 2048); + + /* We must have exclusive access to the EHCI data structures. */ + + ret = nxrmutex_lock(&g_ehci.lock); + if (ret >= 0) + { + /* Remember the new device address and max packet size */ + + epinfo->devaddr = funcaddr; + epinfo->speed = speed; + epinfo->maxpacket = maxpacketsize; + + nxrmutex_unlock(&g_ehci.lock); + } + + return ret; +} + +/**************************************************************************** + * Name: jz_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 jz_epalloc(struct usbhost_driver_s *drvr, + const struct usbhost_epdesc_s *epdesc, + usbhost_ep_t *ep) +{ + struct jz_epinfo_s *epinfo; + struct usbhost_hubport_s *hport; + + /* Sanity check. NOTE that this method should only be called if a device + * is connected (because we need a valid low speed indication). + */ + + DEBUGASSERT(drvr != 0 && epdesc != NULL && epdesc->hport != NULL && + ep != NULL); + hport = epdesc->hport; + + /* Terse output only if we are tracing */ + +#ifdef CONFIG_USBHOST_TRACE + usbhost_vtrace2(EHCI_VTRACE2_EPALLOC, epdesc->addr, epdesc->xfrtype); +#else + jhzuinfo("EP%d DIR=%s FA=%08x TYPE=%d Interval=%d MaxPacket=%d\n", + epdesc->addr, epdesc->in ? "IN" : "OUT", hport->funcaddr, + epdesc->xfrtype, epdesc->interval, epdesc->mxpacketsize); +#endif + + /* Allocate a endpoint information structure */ + + epinfo = kmm_zalloc(sizeof(struct jz_epinfo_s)); + if (!epinfo) + { + usbhost_trace1(EHCI_TRACE1_EPALLOC_FAILED, 0); + return -ENOMEM; + } + + /* Initialize the endpoint container (which is really just another form of + * 'struct usbhost_epdesc_s', packed differently and with additional + * information. A cleaner design might just embed struct usbhost_epdesc_s + * inside of struct jz_epinfo_s and just memcpy here. + */ + + epinfo->epno = epdesc->addr; + epinfo->dirin = epdesc->in; + epinfo->devaddr = hport->funcaddr; +#ifndef CONFIG_USBHOST_INT_DISABLE + epinfo->interval = epdesc->interval; +#endif + epinfo->maxpacket = epdesc->mxpacketsize; + epinfo->xfrtype = epdesc->xfrtype; + epinfo->speed = hport->speed; + nxsem_init(&epinfo->iocsem, 0, 0); + +#ifdef CONFIG_USBHOST_HUB + if (hport->speed != USB_SPEED_HIGH) + { + /* A high speed hub exists between this device and the root hub + * otherwise we would not get here. + */ + + struct usbhost_hubport_s *parent = hport->parent; + + for (; parent->speed != USB_SPEED_HIGH; parent = hport->parent) + { + hport = parent; + } + + if (parent->speed == USB_SPEED_HIGH) + { + epinfo->hubport = HPORT(hport); + epinfo->hubaddr = hport->parent->funcaddr; + } + else + { + return -EINVAL; + } + } +#endif + + /* Success.. return an opaque reference to the endpoint information + * structure instance + */ + + *ep = (usbhost_ep_t)epinfo; + return OK; +} + +/**************************************************************************** + * Name: jz_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 endpoint 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 jz_epfree(struct usbhost_driver_s *drvr, usbhost_ep_t ep) +{ + struct jz_epinfo_s *epinfo = (struct jz_epinfo_s *)ep; + + /* There should not be any pending, transfers */ + + DEBUGASSERT(drvr && epinfo && epinfo->iocwait == 0); + + /* Free the container */ + + kmm_free(epinfo); + return OK; +} + +/**************************************************************************** + * Name: jz_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 jz_alloc(struct usbhost_driver_s *drvr, + uint8_t **buffer, size_t *maxlen) +{ + int ret = -ENOMEM; + DEBUGASSERT(drvr && buffer && maxlen); + + /* The only special requirements for transfer/descriptor buffers are that + * (1) they be aligned to a cache line boundary and (2) they are a + * multiple of the cache line size in length. + */ + + *buffer = (uint8_t *) + kmm_memalign(JZ4780_DCACHE_LINESIZE, JZ4780_EHCI_BUFSIZE); + if (*buffer) + { + *maxlen = JZ4780_EHCI_BUFSIZE; + ret = OK; + } + + return ret; +} + +/**************************************************************************** + * Name: jz_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 jz_free(struct usbhost_driver_s *drvr, uint8_t *buffer) +{ + DEBUGASSERT(drvr && buffer); + + /* No special action is require to free the transfer/descriptor buffer + * memory + */ + + kmm_free(buffer); + return OK; +} + +/**************************************************************************** + * Name: jz_ioalloc + * + * Description: + * Some hardware supports special memory in which larger IO buffers can + * be accessed more efficiently. This method provides a mechanism to + * allocate the request/descriptor memory. If the underlying hardware + * does not support such "special" memory, this functions may simply map + * to kumm_malloc. + * + * This interface differs from DRVR_ALLOC in that the buffers are variable- + * sized. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * buffer - The address of a memory location provided by the caller in + * which to return the allocated buffer memory address. + * buflen - The size of the buffer required. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static int jz_ioalloc(struct usbhost_driver_s *drvr, + uint8_t **buffer, size_t buflen) +{ + DEBUGASSERT(drvr && buffer && buflen > 0); + + /* The only special requirements for I/O buffers are that (1) they be + * aligned to a cache line boundary, (2) they are a multiple of the cache + * line size in length, and (3) they might need to be user accessible + * (depending on how the class driver implements its buffering). + */ + + buflen = (buflen + DCACHE_LINEMASK) & ~DCACHE_LINEMASK; + *buffer = kumm_memalign(JZ4780_DCACHE_LINESIZE, buflen); + return *buffer ? OK : -ENOMEM; +} + +/**************************************************************************** + * Name: jz_iofree + * + * Description: + * Some hardware supports special memory in which IO data can be accessed + * more efficiently. This method provides a mechanism to free that IO + * buffer memory. If the underlying hardware does not support such + * "special" memory, this functions may simply map to kumm_free(). + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * buffer - The address of the allocated buffer memory to be freed. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static int jz_iofree(struct usbhost_driver_s *drvr, uint8_t *buffer) +{ + DEBUGASSERT(drvr && buffer); + + /* No special action is require to free the I/O buffer memory */ + + kumm_free(buffer); + return OK; +} + +/**************************************************************************** + * Name: jz_ctrlin and jz_ctrlout + * + * Description: + * Process a IN or OUT request on the control endpoint. These methods + * will enqueue the request and wait for it to complete. Only one + * transfer may be queued; Neither these methods nor the transfer() method + * can be called again until the control transfer functions returns. + * + * These are blocking methods; these functions will not return until the + * control transfer has completed. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * ep0 - The control endpoint to send/receive the control request. + * req - Describes the request to be sent. This request must lie in + * memory created by DRVR_ALLOC. + * buffer - A buffer used for sending the request and for returning any + * responses. This buffer must be large enough to hold the + * length value in the request description. buffer must have been + * allocated using DRVR_ALLOC. + * + * NOTE: On an IN transaction, req and buffer may refer to the same + * allocated memory. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static int jz_ctrlin(struct usbhost_driver_s *drvr, usbhost_ep_t ep0, + const struct usb_ctrlreq_s *req, + uint8_t *buffer) +{ + struct jz_rhport_s *rhport = (struct jz_rhport_s *)drvr; + struct jz_epinfo_s *ep0info = (struct jz_epinfo_s *)ep0; + uint16_t len; + ssize_t nbytes; + int ret; + + DEBUGASSERT(rhport != NULL && ep0info != NULL && req != NULL); + + len = jz_read16(req->len); + + /* Terse output only if we are tracing */ + +#ifdef CONFIG_USBHOST_TRACE + usbhost_vtrace2(EHCI_VTRACE2_CTRLINOUT, RHPORT(rhport), req->req); +#else + jhzuinfo("RHPort%d type: %02x req: %02x value: %02x%02x index: %02x%02x " + "len: %04x\n", + RHPORT(rhport), req->type, req->req, req->value[1], req->value[0], + req->index[1], req->index[0], len); +#endif + + /* We must have exclusive access to the EHCI hardware and data + * structures. + */ + + ret = nxrmutex_lock(&g_ehci.lock); + if (ret < 0) + { + return ret; + } + + /* Set the request for the IOC event well BEFORE initiating the transfer. */ + + ret = jz_ioc_setup(rhport, ep0info); + if (ret != OK) + { + usbhost_trace1(EHCI_TRACE1_DEVDISCONNECTED, -ret); + goto errout_with_lock; + } + + /* Now initiate the transfer */ + + ret = jz_async_setup(rhport, ep0info, req, buffer, len); + if (ret < 0) + { + uerr("ERROR: jz_async_setup failed: %d\n", ret); + goto errout_with_iocwait; + } + + /* And wait for the transfer to complete */ + + nbytes = jz_transfer_wait(ep0info); + nxrmutex_unlock(&g_ehci.lock); + return nbytes >= 0 ? OK : (int)nbytes; + +errout_with_iocwait: + ep0info->iocwait = false; +errout_with_lock: + nxrmutex_unlock(&g_ehci.lock); + return ret; +} + +static int jz_ctrlout(struct usbhost_driver_s *drvr, usbhost_ep_t ep0, + const struct usb_ctrlreq_s *req, + const uint8_t *buffer) +{ + /* jz_ctrlin can handle both directions. We just need to work around the + * differences in the function signatures. + */ + + return jz_ctrlin(drvr, ep0, req, (uint8_t *)buffer); +} + +/**************************************************************************** + * Name: jz_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 jz_transfer(struct usbhost_driver_s *drvr, + usbhost_ep_t ep, uint8_t *buffer, + size_t buflen) +{ + struct jz_rhport_s *rhport = (struct jz_rhport_s *)drvr; + struct jz_epinfo_s *epinfo = (struct jz_epinfo_s *)ep; + ssize_t nbytes; + int ret; + + DEBUGASSERT(rhport && epinfo && buffer && buflen > 0); + + /* We must have exclusive access to the EHCI hardware and data + * structures. + */ + + ret = nxrmutex_lock(&g_ehci.lock); + if (ret < 0) + { + return (ssize_t)ret; + } + + /* Set the request for the IOC event well BEFORE initiating the transfer. */ + + ret = jz_ioc_setup(rhport, epinfo); + if (ret != OK) + { + usbhost_trace1(EHCI_TRACE1_DEVDISCONNECTED, -ret); + goto errout_with_lock; + } + + /* Initiate the transfer */ + + switch (epinfo->xfrtype) + { + case USB_EP_ATTR_XFER_BULK: + ret = jz_async_setup(rhport, epinfo, NULL, buffer, buflen); + break; + +#ifndef CONFIG_USBHOST_INT_DISABLE + case USB_EP_ATTR_XFER_INT: + ret = jz_intr_setup(rhport, epinfo, buffer, buflen); + break; +#endif + +#ifndef CONFIG_USBHOST_ISOC_DISABLE + case USB_EP_ATTR_XFER_ISOC: +# warning "Isochronous endpoint support not emplemented" +#endif + case USB_EP_ATTR_XFER_CONTROL: + default: + usbhost_trace1(EHCI_TRACE1_BADXFRTYPE, epinfo->xfrtype); + ret = -ENOSYS; + break; + } + + /* Check for errors in the setup of the transfer */ + + if (ret < 0) + { + uerr("ERROR: Transfer setup failed: %d\n", ret); + goto errout_with_iocwait; + } + + /* Then wait for the transfer to complete */ + + nbytes = jz_transfer_wait(epinfo); + nxrmutex_unlock(&g_ehci.lock); + return nbytes; + +errout_with_iocwait: + epinfo->iocwait = false; +errout_with_lock: + nxrmutex_unlock(&g_ehci.lock); + return (ssize_t)ret; +} + +/**************************************************************************** + * Name: jz_asynch + * + * Description: + * Process a request to handle a transfer descriptor. This method will + * enqueue the transfer request and return immediately. When the transfer + * completes, the callback will be invoked with the provided transfer. + * This method is useful for receiving interrupt transfers which may come + * infrequently. + * + * Only one transfer may be queued; Neither this method nor the ctrlin or + * ctrlout methods can be called again until the transfer completes. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from + * the call to the class create() method. + * ep - The IN or OUT endpoint descriptor for the device endpoint on + * which to perform the transfer. + * buffer - A buffer containing the data to be sent (OUT endpoint) or + * received (IN endpoint). buffer must have been allocated + * using DRVR_ALLOC + * buflen - The length of the data to be sent or received. + * callback - This function will be called when the transfer completes. + * arg - The arbitrary parameter that will be passed to the callback + * function when the transfer completes. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +#ifdef CONFIG_USBHOST_ASYNCH +static int jz_asynch(struct usbhost_driver_s *drvr, usbhost_ep_t ep, + uint8_t *buffer, size_t buflen, + usbhost_asynch_t callback, void *arg) +{ + struct jz_rhport_s *rhport = (struct jz_rhport_s *)drvr; + struct jz_epinfo_s *epinfo = (struct jz_epinfo_s *)ep; + int ret; + + DEBUGASSERT(rhport && epinfo && buffer && buflen > 0); + + /* We must have exclusive access to the EHCI hardware and data + * structures. + */ + + ret = nxrmutex_lock(&g_ehci.lock); + if (ret < 0) + { + return ret; + } + + /* Set the request for the callback well BEFORE initiating the transfer. */ + + ret = jz_ioc_async_setup(rhport, epinfo, callback, arg); + if (ret != OK) + { + usbhost_trace1(EHCI_TRACE1_DEVDISCONNECTED, -ret); + goto errout_with_lock; + } + + /* Initiate the transfer */ + + switch (epinfo->xfrtype) + { + case USB_EP_ATTR_XFER_BULK: + ret = jz_async_setup(rhport, epinfo, NULL, buffer, buflen); + break; + +#ifndef CONFIG_USBHOST_INT_DISABLE + case USB_EP_ATTR_XFER_INT: + ret = jz_intr_setup(rhport, epinfo, buffer, buflen); + break; +#endif + +#ifndef CONFIG_USBHOST_ISOC_DISABLE + case USB_EP_ATTR_XFER_ISOC: +# warning "Isochronous endpoint support not emplemented" +#endif + case USB_EP_ATTR_XFER_CONTROL: + default: + usbhost_trace1(EHCI_TRACE1_BADXFRTYPE, epinfo->xfrtype); + ret = -ENOSYS; + break; + } + + /* Check for errors in the setup of the transfer */ + + if (ret < 0) + { + goto errout_with_callback; + } + + /* The transfer is in progress */ + + nxrmutex_unlock(&g_ehci.lock); + return OK; + +errout_with_callback: + epinfo->callback = NULL; + epinfo->arg = NULL; +errout_with_lock: + nxrmutex_unlock(&g_ehci.lock); + return ret; +} +#endif /* CONFIG_USBHOST_ASYNCH */ + +/**************************************************************************** + * Name: jz_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 jz_cancel(struct usbhost_driver_s *drvr, usbhost_ep_t ep) +{ + struct jz_epinfo_s *epinfo = (struct jz_epinfo_s *)ep; + struct jz_qh_s *qh; +#ifdef CONFIG_USBHOST_ASYNCH + usbhost_asynch_t callback; + void *arg; +#endif + uint32_t *bp; + irqstate_t flags; + bool iocwait; + int ret; + + DEBUGASSERT(epinfo); + + /* We must have exclusive access to the EHCI hardware and data structures. + * This will prevent servicing any transfer completion events while we + * perform the cancellation, but will not prevent DMA-related race + * conditions. + * + * REVISIT: This won't work. This function must be callable from the + * interrupt level. + */ + + ret = nxrmutex_lock(&g_ehci.lock); + if (ret < 0) + { + return (ssize_t)ret; + } + + /* Sample and reset all transfer termination information. This will + * prevent any callbacks from occurring while are performing the + * cancellation. The transfer may still be in progress, however, so this + * does not eliminate other DMA-related race conditions. + */ + + flags = enter_critical_section(); +#ifdef CONFIG_USBHOST_ASYNCH + callback = epinfo->callback; + arg = epinfo->arg; +#endif + iocwait = epinfo->iocwait; + +#ifdef CONFIG_USBHOST_ASYNCH + epinfo->callback = NULL; + epinfo->arg = NULL; +#endif + epinfo->iocwait = false; + leave_critical_section(flags); + + /* Bail if there is no transfer in progress for this endpoint */ + +#ifdef CONFIG_USBHOST_ASYNCH + if (callback == NULL && !iocwait) +#else + if (!iocwait) +#endif + { + ret = OK; + goto errout_with_lock; + } + + /* Handle the cancellation according to the type of the transfer */ + + switch (epinfo->xfrtype) + { + case USB_EP_ATTR_XFER_CONTROL: + case USB_EP_ATTR_XFER_BULK: + { + /* Get the horizontal pointer from the head of the asynchronous + * queue. + */ + + bp = (uint32_t *)&g_asynchead.hw.hlp; + qh = (struct jz_qh_s *) + jz_virtramaddr(jz_swap32(*bp) & QH_HLP_MASK); + + /* If the asynchronous queue is empty, then the forward point in + * the asynchronous queue head will point back to the queue + * head. + */ + + if (qh && qh == &g_asynchead) + { + /* Claim that we successfully cancelled the transfer */ + + ret = OK; + goto exit_terminate; + } + } + break; + +#ifndef CONFIG_USBHOST_INT_DISABLE + case USB_EP_ATTR_XFER_INT: + { + /* Get the horizontal pointer from the head of the interrupt + * queue. + */ + + bp = (uint32_t *)&g_intrhead.hw.hlp; + qh = (struct jz_qh_s *) + jz_virtramaddr(jz_swap32(*bp) & QH_HLP_MASK); + if (qh) + { + /* if the queue is empty, then just claim that we successfully + * cancelled the transfer. + */ + + ret = OK; + goto exit_terminate; + } + } + break; +#endif + +#ifndef CONFIG_USBHOST_ISOC_DISABLE + case USB_EP_ATTR_XFER_ISOC: +# warning "Isochronous endpoint support not emplemented" +#endif + default: + usbhost_trace1(EHCI_TRACE1_BADXFRTYPE, epinfo->xfrtype); + ret = -ENOSYS; + goto errout_with_lock; + } + + /* Find and remove the QH. There are four possibilities: + * + * 1) The transfer has already completed and the QH is no longer in the + * list. In this case, jz_hq_foreach will return zero + * 2a) The transfer is not active and still pending. It was removed from + * the list and jz_hq_foreach will return one. + * 2b) The is active but not yet complete. This is currently handled the + * same as 2a). REVISIT: This needs to be fixed. + * 3) Some bad happened and jz_hq_foreach returned an error code < 0. + */ + + ret = jz_qh_foreach(qh, &bp, jz_qh_cancel, epinfo); + if (ret < 0) + { + usbhost_trace1(EHCI_TRACE1_QTDFOREACH_FAILED, -ret); + } + + /* Was there a pending synchronous transfer? */ + +exit_terminate: + epinfo->result = -ESHUTDOWN; +#ifdef CONFIG_USBHOST_ASYNCH + if (iocwait) + { + /* Yes... wake it up */ + + DEBUGASSERT(callback == NULL); + nxsem_post(&epinfo->iocsem); + } + + /* No.. Is there a pending asynchronous transfer? */ + + else /* if (callback != NULL) */ + { + /* Yes.. perform the callback */ + + callback(arg, -ESHUTDOWN); + } + +#else + /* Wake up the waiting thread */ + + nxsem_post(&epinfo->iocsem); +#endif + +errout_with_lock: + nxrmutex_unlock(&g_ehci.lock); + return ret; +} + +/**************************************************************************** + * Name: jz_connect + * + * Description: + * New connections may be detected by an attached hub. This method is the + * mechanism that is used by the hub class to introduce a new connection + * and port description to the system. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * hport - The descriptor of the hub port that detected the connection + * related event + * connected - True: device connected; false: device disconnected + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + ****************************************************************************/ + +#ifdef CONFIG_USBHOST_HUB +static int jz_connect(struct usbhost_driver_s *drvr, + struct usbhost_hubport_s *hport, + bool connected) +{ + irqstate_t flags; + + /* Set the connected/disconnected flag */ + + hport->connected = connected; + uinfo("Hub port %d connected: %s\n", + hport->port, connected ? "YES" : "NO"); + + /* Report the connection event */ + + flags = enter_critical_section(); + DEBUGASSERT(g_ehci.hport == NULL); /* REVISIT */ + + g_ehci.hport = hport; + if (g_ehci.pscwait) + { + g_ehci.pscwait = false; + nxsem_post(&g_ehci.pscsem); + } + + leave_critical_section(flags); + return OK; +} +#endif + +/**************************************************************************** + * Name: jz_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 jz_disconnect(struct usbhost_driver_s *drvr, + struct usbhost_hubport_s *hport) +{ + DEBUGASSERT(hport != NULL); + hport->devclass = NULL; +} + +/**************************************************************************** + * Name: jz_reset + * + * Description: + * Set the HCRESET bit in the USBCMD register to reset the EHCI hardware. + * + * Table 2-9. USBCMD - USB Command Register Bit Definitions + * + * "Host Controller Reset (HCRESET) ... This control bit is used by + * software to reset the host controller. The effects of this on Root + * Hub registers are similar to a Chip Hardware Reset. + * + * "When software writes a one to this bit, the Host Controller resets its + * internal pipelines, timers, counters, state machines, etc. to their + * initial value. Any transaction currently in progress on USB is + * immediately terminated. A USB reset is not driven on downstream + * ports. + * + * "PCI Configuration registers are not affected by this reset. All + * operational registers, including port registers and port state + * machines are set to their initial values. Port ownership reverts + * to the companion host controller(s)... Software must reinitialize + * the host controller ... in order to return the host controller to + * an operational state. + * + * "This bit is set to zero by the Host Controller when the reset process + * is complete. Software cannot terminate the reset process early by + * writing a zero to this register. Software should not set this bit to + * a one when the HCHalted bit in the USBSTS register is a zero. + * Attempting to reset an actively running host controller will result + * in undefined behavior." + * + * Input Parameters: + * None. + * + * Returned Value: + * Zero (OK) is returned on success; A negated errno value is returned + * on failure. + * + * Assumptions: + * - Called during the initialization of the EHCI. + * + ****************************************************************************/ + +static int jz_reset(void) +{ + uint32_t regval; + unsigned int timeout; + + /* Make sure that the EHCI is halted: "When [the Run/Stop] bit is set to + * 0, the Host Controller completes the current transaction on the USB and + * then halts. The HC Halted bit in the status register indicates when the + * Host Controller has finished the transaction and has entered the + * stopped state..." + */ + + jz_putreg(0, &HCOR->usbcmd); + + /* "... Software should not set [HCRESET] to a one when the HCHalted bit in + * the USBSTS register is a zero. Attempting to reset an actively running + * host controller will result in undefined behavior." + */ + + timeout = 0; + do + { + /* Wait one microsecond and update the timeout counter */ + + up_udelay(1); + timeout++; + + /* Get the current value of the USBSTS register. This loop will + * terminate when either the timeout exceeds one millisecond or when + * the HCHalted bit is no longer set in the USBSTS register. + */ + + regval = jz_getreg(&HCOR->usbsts); + } + while (((regval & EHCI_USBSTS_HALTED) == 0) && (timeout < 1000)); + + /* Is the EHCI still running? Did we timeout? */ + + if ((regval & EHCI_USBSTS_HALTED) == 0) + { + usbhost_trace1(EHCI_TRACE1_HCHALTED_TIMEOUT, regval); + return -ETIMEDOUT; + } + + /* Now we can set the HCReset bit in the USBCMD register to initiate the + * reset + */ + + regval = jz_getreg(&HCOR->usbcmd); + regval |= EHCI_USBCMD_HCRESET; + jz_putreg(regval, &HCOR->usbcmd); + + /* Wait for the HCReset bit to become clear */ + + do + { + /* Wait five microsecondw and update the timeout counter */ + + up_udelay(5); + timeout += 5; + + /* Get the current value of the USBCMD register. This loop will + * terminate when either the timeout exceeds one second or when the + * HCReset bit is no longer set in the USBSTS register. + */ + + regval = jz_getreg(&HCOR->usbcmd); + } + while (((regval & EHCI_USBCMD_HCRESET) != 0) && (timeout < 1000000)); + + /* Return either success or a timeout */ + + return (regval & EHCI_USBCMD_HCRESET) != 0 ? -ETIMEDOUT : OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: jz_ehci_initialize + * + * Description: + * Initialize USB EHCI host controller hardware. + * + * Input Parameters: + * controller -- If the device supports more than one EHCI interface, then + * this identifies which controller is being initialized. Normally, this + * is just zero. + * + * Returned Value: + * And instance of the USB host interface. The controlling task should + * use this interface to (1) call the wait() method to wait for a device + * to be connected, and (2) call the enumerate() method to bind the device + * to a class driver. + * + * Assumptions: + * - This function should called in the initialization sequence in order + * to initialize the USB device functionality. + * - Class drivers should be initialized prior to calling this function. + * Otherwise, there is a race condition if the device is already connected. + * + ****************************************************************************/ + +struct usbhost_connection_s *jz_ehci_initialize(int controller) +{ + struct usbhost_hubport_s *hport; + uint32_t regval; +#if defined(CONFIG_DEBUG_USB) && defined(CONFIG_DEBUG_ASSERTIONS) + uint16_t regval16; + unsigned int nports; +#endif + uintptr_t physaddr; + int ret; + int i; + + /* Sanity checks */ + + DEBUGASSERT(controller == 0); + DEBUGASSERT(((uintptr_t)&g_asynchead & 0x1f) == 0); + DEBUGASSERT((sizeof(struct jz_qh_s) & 0x1f) == 0); + DEBUGASSERT((sizeof(struct jz_qtd_s) & 0x1f) == 0); + +#ifdef CONFIG_JZ4780_EHCI_PREALLOCATE + DEBUGASSERT(((uintptr_t)&g_qhpool & 0x1f) == 0); + DEBUGASSERT(((uintptr_t)&g_qtdpool & 0x1f) == 0); +#endif + +#ifndef CONFIG_USBHOST_INT_DISABLE + DEBUGASSERT(((uintptr_t)&g_intrhead & 0x1f) == 0); +#ifdef CONFIG_JZ4780_EHCI_PREALLOCATE + DEBUGASSERT(((uintptr_t)g_framelist & 0xfff) == 0); +#endif +#endif /* CONFIG_USBHOST_INT_DISABLE */ + + /* JZ4780 Configuration ***************************************************/ + + /* Note that no pin configuration is required. All USB HS pins have + * dedicated function + */ + + /* Software Configuration *************************************************/ + + usbhost_vtrace1(EHCI_VTRACE1_INITIALIZING, 0); + + /* Initialize function address generation logic */ + + usbhost_devaddr_initialize(&g_ehci.devgen); + + /* Initialize the root hub port structures */ + + for (i = 0; i < JZ_EHCI_NRHPORT; i++) + { + struct jz_rhport_s *rhport = &g_ehci.rhport[i]; + + /* Initialize the device operations */ + + rhport->drvr.ep0configure = jz_ep0configure; + rhport->drvr.epalloc = jz_epalloc; + rhport->drvr.epfree = jz_epfree; + rhport->drvr.alloc = jz_alloc; + rhport->drvr.free = jz_free; + rhport->drvr.ioalloc = jz_ioalloc; + rhport->drvr.iofree = jz_iofree; + rhport->drvr.ctrlin = jz_ctrlin; + rhport->drvr.ctrlout = jz_ctrlout; + rhport->drvr.transfer = jz_transfer; +#ifdef CONFIG_USBHOST_ASYNCH + rhport->drvr.asynch = jz_asynch; +#endif + rhport->drvr.cancel = jz_cancel; +#ifdef CONFIG_USBHOST_HUB + rhport->drvr.connect = jz_connect; +#endif + rhport->drvr.disconnect = jz_disconnect; + rhport->hport.pdevgen = &g_ehci.devgen; + + /* Initialize EP0 */ + + rhport->ep0.xfrtype = USB_EP_ATTR_XFER_CONTROL; + rhport->ep0.speed = USB_SPEED_FULL; + rhport->ep0.maxpacket = 8; + nxsem_init(&rhport->ep0.iocsem, 0, 0); + + /* Initialize the public port representation */ + + hport = &rhport->hport.hport; + hport->drvr = &rhport->drvr; +#ifdef CONFIG_USBHOST_HUB + hport->parent = NULL; +#endif + hport->ep0 = &rhport->ep0; + hport->port = i; + hport->speed = USB_SPEED_FULL; + } + +#ifndef CONFIG_JZ4780_EHCI_PREALLOCATE + /* Allocate a pool of free Queue Head (QH) structures */ + + g_qhpool = (struct jz_qh_s *) + kmm_memalign(32, CONFIG_JZ4780_EHCI_NQHS * sizeof(struct jz_qh_s)); + if (!g_qhpool) + { + usbhost_trace1(EHCI_TRACE1_QHPOOLALLOC_FAILED, 0); + return NULL; + } +#endif + + /* Initialize the list of free Queue Head (QH) structures */ + + for (i = 0; i < CONFIG_JZ4780_EHCI_NQHS; i++) + { + /* Put the QH structure in a free list */ + + jz_qh_free(&g_qhpool[i]); + } + +#ifndef CONFIG_JZ4780_EHCI_PREALLOCATE + /* Allocate a pool of free Transfer Descriptor (qTD) structures */ + + g_qtdpool = (struct jz_qtd_s *) + kmm_memalign(32, CONFIG_JZ4780_EHCI_NQTDS * sizeof(struct jz_qtd_s)); + if (!g_qtdpool) + { + usbhost_trace1(EHCI_TRACE1_QTDPOOLALLOC_FAILED, 0); + kmm_free(g_qhpool); + return NULL; + } +#endif + +#if !defined(CONFIG_JZ4780_EHCI_PREALLOCATE) && !defined(CONFIG_USBHOST_INT_DISABLE) + /* Allocate the periodic framelist */ + + g_framelist = (uint32_t *) + kmm_memalign(4096, FRAME_LIST_SIZE * sizeof(uint32_t)); + if (!g_framelist) + { + usbhost_trace1(EHCI_TRACE1_PERFLALLOC_FAILED, 0); + kmm_free(g_qhpool); + kmm_free(g_qtdpool); + return NULL; + } +#endif + + /* Initialize the list of free Transfer Descriptor (qTD) structures */ + + for (i = 0; i < CONFIG_JZ4780_EHCI_NQTDS; i++) + { + /* Put the TD in a free list */ + + jz_qtd_free(&g_qtdpool[i]); + } + + /* EHCI Hardware Configuration ********************************************/ + + for (i = 0; i < CONFIG_JZ4780_EHCI_NQTDS; i++) + { + /* Put the TD in a free list */ + + jz_qtd_free(&g_qtdpool[i]); + } + + /* Host Controller Initialization. Paragraph 4.1 */ + + /* Reset the EHCI hardware */ + + ret = jz_reset(); + if (ret < 0) + { + usbhost_trace1(EHCI_TRACE1_RESET_FAILED, -ret); + return NULL; + } + + modifyreg32(JZ_UHPEHCI_VSECTION + 0xb0, 0, 1 << 6); + + /* "In order to initialize the host controller, software should perform + * the following steps: + * + * - "Program the CTRLDSSEGMENT register with 4-Gigabyte segment where + * all of the interface data structures are allocated. [64-bit mode] + * - "Write the appropriate value to the USBINTR register to enable the + * appropriate interrupts. + * - "Write the base address of the Periodic Frame List to the + * PERIODICLIST BASE register. If there are no work items in the + * periodic schedule, all elements of the Periodic Frame List should + * have their T-Bits set to a one. + * - "Write the USBCMD register to set the desired interrupt threshold, + * frame list size (if applicable) and turn the host controller ON via + * setting the Run/Stop bit. + * - Write a 1 to CONFIGFLAG register to route all ports to the EHCI + * controller + * ... + * + * "At this point, the host controller is up and running and the port + * registers will begin reporting device connects, etc. System software + * can enumerate a port through the reset process (where the port is in + * the enabled state). At this point, the port is active with SOFs + * occurring down the enabled port enabled Highspeed ports, but the + * schedules have not yet been enabled. The EHCI Host controller will not + * transmit SOFs to enabled Full- or Low-speed ports." + */ + + /* Disable all interrupts */ + + jz_putreg(0, &HCOR->usbintr); + + /* Clear pending interrupts. Bits in the USBSTS register are cleared by + * writing a '1' to the corresponding bit. + */ + + jz_putreg(EHCI_INT_ALLINTS, &HCOR->usbsts); + +#if defined(CONFIG_DEBUG_USB) && defined(CONFIG_DEBUG_ASSERTIONS) + /* Show the EHCI version */ + + regval16 = jz_swap16(HCCR->hciversion); + usbhost_vtrace2(EHCI_VTRACE2_HCIVERSION, regval16 >> 8, regval16 & 0xff); + + /* Verify that the correct number of ports is reported */ + + regval = jz_getreg(&HCCR->hcsparams); + nports = (regval & EHCI_HCSPARAMS_NPORTS_MASK) >> + EHCI_HCSPARAMS_NPORTS_SHIFT; + + usbhost_vtrace2(EHCI_VTRACE2_HCSPARAMS, nports, regval); + DEBUGASSERT(nports == JZ_EHCI_NRHPORT); + + /* Show the HCCPARAMS register */ + + regval = jz_getreg(&HCCR->hccparams); + usbhost_vtrace1(EHCI_VTRACE1_HCCPARAMS, regval); +#endif + + /* Initialize the head of the asynchronous queue/reclamation list. + * + * "In order to communicate with devices via the asynchronous schedule, + * system software must write the ASYNDLISTADDR register with the address + * of a control or bulk queue head. Software must then enable the + * asynchronous schedule by writing a one to the Asynchronous Schedule + * Enable bit in the USBCMD register. In order to communicate with devices + * via the periodic schedule, system software must enable the periodic + * schedule by writing a one to the Periodic Schedule Enable bit in the + * USBCMD register. Note that the schedules can be turned on before the + * first port is reset (and enabled)." + */ + + memset(&g_asynchead, 0, sizeof(struct jz_qh_s)); + physaddr = jz_physramaddr((uintptr_t)&g_asynchead); + g_asynchead.hw.hlp = jz_swap32(physaddr | QH_HLP_TYP_QH); + g_asynchead.hw.epchar = jz_swap32(QH_EPCHAR_H | + QH_EPCHAR_EPS_FULL); + g_asynchead.hw.overlay.nqp = jz_swap32(QH_NQP_T); + g_asynchead.hw.overlay.alt = jz_swap32(QH_NQP_T); + g_asynchead.hw.overlay.token = jz_swap32(QH_TOKEN_HALTED); + g_asynchead.fqp = jz_swap32(QTD_NQP_T); + + up_clean_dcache((uintptr_t)&g_asynchead.hw, + (uintptr_t)&g_asynchead.hw + sizeof(struct ehci_qh_s)); + + /* Set the Current Asynchronous List Address. */ + + jz_putreg(jz_swap32(physaddr), &HCOR->asynclistaddr); + +#ifndef CONFIG_USBHOST_INT_DISABLE + /* Initialize the head of the periodic list. Since Isochronous + * endpoints are not not yet supported, each element of the + * frame list is initialized to point to the Interrupt Queue + * Head (g_intrhead). + */ + + memset(&g_intrhead, 0, sizeof(struct jz_qh_s)); + g_intrhead.hw.hlp = jz_swap32(QH_HLP_T); + g_intrhead.hw.overlay.nqp = jz_swap32(QH_NQP_T); + g_intrhead.hw.overlay.alt = jz_swap32(QH_NQP_T); + g_intrhead.hw.overlay.token = jz_swap32(QH_TOKEN_HALTED); + g_intrhead.hw.epcaps = jz_swap32(QH_EPCAPS_SSMASK(1)); + + /* Attach the periodic QH to Period Frame List */ + + physaddr = jz_physramaddr((uintptr_t)&g_intrhead); + + for (i = 0; i < FRAME_LIST_SIZE; i++) + { + g_framelist[i] = jz_swap32(physaddr) | PFL_TYP_QH; + } + + /* Set the Periodic Frame List Base Address. */ + + up_clean_dcache((uintptr_t)&g_intrhead.hw, + (uintptr_t)&g_intrhead.hw + + sizeof(struct ehci_qh_s)); + up_clean_dcache((uintptr_t)g_framelist, + (uintptr_t)g_framelist + + FRAME_LIST_SIZE * sizeof(uint32_t)); + + physaddr = jz_physramaddr((uintptr_t)g_framelist); + + jz_putreg(jz_swap32(physaddr), &HCOR->periodiclistbase); +#endif + + /* Enable the asynchronous schedule and, possibly enable the periodic + * schedule and set the frame list size. + */ + + regval = jz_getreg(&HCOR->usbcmd); + regval &= ~(EHCI_USBCMD_HCRESET | EHCI_USBCMD_FLSIZE_MASK | + EHCI_USBCMD_FLSIZE_MASK | EHCI_USBCMD_PSEN | + EHCI_USBCMD_IAADB | EHCI_USBCMD_LRESET); + regval |= EHCI_USBCMD_ASEN; + +#ifndef CONFIG_USBHOST_INT_DISABLE + regval |= EHCI_USBCMD_PSEN; +# if FRAME_LIST_SIZE == 1024 + regval |= EHCI_USBCMD_FLSIZE_1024; +# elif FRAME_LIST_SIZE == 512 + regval |= EHCI_USBCMD_FLSIZE_512; +# elif FRAME_LIST_SIZE == 512 + regval |= EHCI_USBCMD_FLSIZE_256; +# else +# error Unsupported frame size list size +# endif +#endif + + jz_putreg(regval, &HCOR->usbcmd); + + /* Start the host controller by setting the RUN bit in the USBCMD + * register. + */ + + regval = jz_getreg(&HCOR->usbcmd); + regval |= EHCI_USBCMD_RUN; + jz_putreg(regval, &HCOR->usbcmd); + + /* Route all ports to this host controller by setting the CONFIG flag. */ + + regval = jz_getreg(&HCOR->configflag); + regval |= EHCI_CONFIGFLAG; + jz_putreg(regval, &HCOR->configflag); + + /* Wait for the EHCI to run (i.e., no longer report halted) */ + + ret = ehci_wait_usbsts(EHCI_USBSTS_HALTED, 0, 100 * 1000); + if (ret < 0) + { + usbhost_trace1(EHCI_TRACE1_RUN_FAILED, jz_getreg(&HCOR->usbsts)); + return NULL; + } + + /* Interrupt Configuration ************************************************/ + + ret = irq_attach(JZ4780_IRQ_EHCI, jz_ehci_tophalf, NULL); + + if (ret != 0) + { + usbhost_trace1(EHCI_TRACE1_IRQATTACH_FAILED, JZ4780_IRQ_EHCI); + return NULL; + } + + /* Enable EHCI interrupts. Interrupts are still disabled at the level of + * the AIC. + */ + + jz_putreg(EHCI_HANDLED_INTS, &HCOR->usbintr); + + 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 < JZ_EHCI_NRHPORT; i++) + { + g_ehci.rhport[i].connected = + ((jz_getreg(&HCOR->portsc[i]) & EHCI_PORTSC_CCS) != 0); + } + + /* Enable interrupts at the interrupt controller */ + + up_enable_irq(JZ4780_IRQ_EHCI); /* enable USB interrupt */ + usbhost_vtrace1(EHCI_VTRACE1_INIITIALIZED, 0); + + return &g_ehciconn; +} + +#endif /* CONFIG_JZ4780_EHCI */ diff --git a/arch/mips/src/jz4780/jz4780_ehci.h b/arch/mips/src/jz4780/jz4780_ehci.h new file mode 100644 index 0000000000000..a380a34e3f7bc --- /dev/null +++ b/arch/mips/src/jz4780/jz4780_ehci.h @@ -0,0 +1,80 @@ +/**************************************************************************** + * arch/mips/src/jz4780/jz4780_ehci.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_MIPS_SRC_JZ4780_JZ_EHCI_H +#define __ARCH_MIPS_SRC_JZ4780_JZ_EHCI_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include "chip.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define JZ_UHPEHCI_VSECTION 0xb3490000 + +/* The JZ4780 supports 3 root hub ports */ + +#define JZ_EHCI_NRHPORT 3 + +/* Registers ****************************************************************/ + +/* Traditionally, NuttX specifies register locations using individual + * register offsets from a base address. That tradition is broken here and, + * instead, register blocks are represented as structures. This is done here + * because, in principle, EHCI operational register address may not be known + * at compile time; the operational registers lie at an offset specified in + * the 'caplength' byte of the Host Controller Capability Registers. + * + * However, for the case of the JZ4780 EHCI, we know apriori that the value + * of 'caplength' is 0x10. We keep this structure, however, to facilitate + * porting this driver to other environments where, perhaps, such knowledge + * is not available. + */ + +/* Host Controller Capability Registers */ + +#define HCCR ((struct ehci_hccr_s *)JZ_UHPEHCI_VSECTION) + +/* Host Controller Operational Registers */ + +#define HCOR ((volatile struct ehci_hcor_s *)(JZ_UHPEHCI_VSECTION + 0x10)) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/**************************************************************************** + * Public Functions Prototypes + ****************************************************************************/ + +#endif /* __ARCH_MIPS_SRC_JZ4780_JZ_EHCI_H */ diff --git a/arch/mips/src/jz4780/jz4780_lcd.c b/arch/mips/src/jz4780/jz4780_lcd.c new file mode 100644 index 0000000000000..4ed30e3d4efcc --- /dev/null +++ b/arch/mips/src/jz4780/jz4780_lcd.c @@ -0,0 +1,1503 @@ +/**************************************************************************** + * arch/mips/src/jz4780/jz4780_lcd.c + * + * SPDX-License-Identifier: BSD-2-Clause + *- + * Copyright (c) 2015 Oleksandr Tymoshenko + * Copyright (c) 2016 Jared McNeill + * All rights reserved. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 "mips_internal.h" +#include "chip.h" + +#include + +#ifdef CONFIG_ALLOW_BSD_COMPONENTS + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define JZ4780_LCD_VRAMBASE 0xAF800000 +#define JZ4780_LCD_HWIDTH 1360 +#define JZ4780_LCD_VHEIGHT 768 + +#define PCFG_MAGIC 0xc7ff2100 + +#define FOREGROUND_OFFSET 0x400000 + +#define VID_PHSYNC 0x0001 +#define VID_PVSYNC 0x0004 +#define VID_INTERLACE 0x0010 + +#define JZ_REG_SHIFT 2 + +#define JZ4780_COLOR_FMT FB_FMT_RGB16_565 +#define JZ4780_BPP 16 + +#define JZ4780_STRIDE ((JZ4780_LCD_HWIDTH * JZ4780_BPP + 7) / 8) +#define JZ4780_FBSIZE (JZ4780_STRIDE * JZ4780_LCD_VHEIGHT) + +/* HDMI controller registers */ + +#define HDMI_DESIGN_ID 0x0000 +#define HDMI_REVISION_ID 0x0001 +#define HDMI_PRODUCT_ID0 0x0002 +#define HDMI_PRODUCT_ID1 0x0003 + +/* Interrupt Registers */ +#define HDMI_IH_PHY_STAT0 0x0104 +# define HDMI_IH_PHY_STAT0_HPD (1 << 0) +#define HDMI_IH_I2CMPHY_STAT0 0x0108 +# define HDMI_IH_I2CMPHY_STAT0_DONE (1 << 1) +# define HDMI_IH_I2CMPHY_STAT0_ERROR (1 << 0) + +#define HDMI_IH_MUTE_FC_STAT2 0x0182 +# define HDMI_IH_MUTE_FC_STAT2_OVERFLOW_MASK (0x3) + +/* Video Sample Registers */ +#define HDMI_TX_INVID0 0x0200 +#define HDMI_TX_INVID0_INTERNAL_DE_GENERATOR_DISABLE 0x00 +#define HDMI_TX_INVID0_VIDEO_MAPPING_MASK 0x1F +#define HDMI_TX_INVID0_VIDEO_MAPPING_OFFSET 0 +#define HDMI_TX_INSTUFFING 0x0201 +#define HDMI_TX_INSTUFFING_BDBDATA_STUFFING_ENABLE 0x4 +#define HDMI_TX_INSTUFFING_RCRDATA_STUFFING_ENABLE 0x2 +#define HDMI_TX_INSTUFFING_GYDATA_STUFFING_ENABLE 0x1 +#define HDMI_TX_GYDATA0 0x0202 +#define HDMI_TX_GYDATA1 0x0203 +#define HDMI_TX_RCRDATA0 0x0204 +#define HDMI_TX_RCRDATA1 0x0205 +#define HDMI_TX_BCBDATA0 0x0206 +#define HDMI_TX_BCBDATA1 0x0207 + +/* Video Packetizer Registers */ +#define HDMI_VP_PR_CD 0x0801 +# define HDMI_VP_PR_CD_COLOR_DEPTH_MASK 0xF0 +# define HDMI_VP_PR_CD_COLOR_DEPTH_OFFSET 4 + +#define HDMI_VP_STUFF 0x0802 +# define HDMI_VP_STUFF_IDEFAULT_PHASE_MASK 0x20 +# define HDMI_VP_STUFF_IDEFAULT_PHASE_OFFSET 5 +# define HDMI_VP_STUFF_YCC422_STUFFING_MASK 0x4 +# define HDMI_VP_STUFF_YCC422_STUFFING_STUFFING_MODE 0x4 +# define HDMI_VP_STUFF_PP_STUFFING_MASK 0x2 +# define HDMI_VP_STUFF_PP_STUFFING_STUFFING_MODE 0x2 +# define HDMI_VP_STUFF_PR_STUFFING_MASK 0x1 +# define HDMI_VP_STUFF_PR_STUFFING_STUFFING_MODE 0x1 +#define HDMI_VP_REMAP 0x0803 +# define HDMI_VP_REMAP_YCC422_16BIT 0x0 +#define HDMI_VP_CONF 0x0804 +# define HDMI_VP_CONF_BYPASS_EN_MASK 0x40 +# define HDMI_VP_CONF_BYPASS_EN_ENABLE 0x40 +# define HDMI_VP_CONF_BYPASS_EN_DISABLE 0x00 +# define HDMI_VP_CONF_PP_EN_ENMASK 0x20 +# define HDMI_VP_CONF_PP_EN_ENABLE 0x20 +# define HDMI_VP_CONF_PP_EN_DISABLE 0x00 +# define HDMI_VP_CONF_PR_EN_MASK 0x10 +# define HDMI_VP_CONF_PR_EN_DISABLE 0x00 +# define HDMI_VP_CONF_YCC422_EN_MASK 0x8 +# define HDMI_VP_CONF_YCC422_EN_ENABLE 0x8 +# define HDMI_VP_CONF_YCC422_EN_DISABLE 0x0 +# define HDMI_VP_CONF_BYPASS_SELECT_MASK 0x4 +# define HDMI_VP_CONF_BYPASS_SELECT_VID_PACKETIZER 0x4 +# define HDMI_VP_CONF_OUTPUT_SELECTOR_MASK 0x3 +# define HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS 0x3 +# define HDMI_VP_CONF_OUTPUT_SELECTOR_YCC422 0x1 +# define HDMI_VP_CONF_OUTPUT_SELECTOR_PP 0x0 + +/* Frame Composer Registers */ +#define HDMI_FC_INVIDCONF 0x1000 +# define HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_HIGH 0x40 +# define HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_LOW 0x00 +# define HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_HIGH 0x20 +# define HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_LOW 0x00 +# define HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_HIGH 0x10 +# define HDMI_FC_INVIDCONF_DVI_MODEZ_DVI_MODE 0x0 +# define HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_HIGH 0x2 +# define HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_LOW 0x0 +# define HDMI_FC_INVIDCONF_IN_I_P_INTERLACED 0x1 +# define HDMI_FC_INVIDCONF_IN_I_P_PROGRESSIVE 0x0 +#define HDMI_FC_INHACTV0 0x1001 +#define HDMI_FC_INHACTV1 0x1002 +#define HDMI_FC_INHBLANK0 0x1003 +#define HDMI_FC_INHBLANK1 0x1004 +#define HDMI_FC_INVACTV0 0x1005 +#define HDMI_FC_INVACTV1 0x1006 +#define HDMI_FC_INVBLANK 0x1007 +#define HDMI_FC_HSYNCINDELAY0 0x1008 +#define HDMI_FC_HSYNCINDELAY1 0x1009 +#define HDMI_FC_HSYNCINWIDTH0 0x100A +#define HDMI_FC_HSYNCINWIDTH1 0x100B +#define HDMI_FC_VSYNCINDELAY 0x100C +#define HDMI_FC_VSYNCINWIDTH 0x100D +#define HDMI_FC_CTRLDUR 0x1011 +#define HDMI_FC_EXCTRLDUR 0x1012 +#define HDMI_FC_EXCTRLSPAC 0x1013 +#define HDMI_FC_CH0PREAM 0x1014 +#define HDMI_FC_CH1PREAM 0x1015 +#define HDMI_FC_CH2PREAM 0x1016 +#define HDMI_FC_MASK2 0x10DA +# define HDMI_FC_MASK2_LOW_PRI (1 << 1) +# define HDMI_FC_MASK2_HIGH_PRI (1 << 0) + +#define HDMI_PHY_CONF0 0x3000 +# define HDMI_PHY_CONF0_PDZ_MASK 0x80 +# define HDMI_PHY_CONF0_PDZ_OFFSET 7 +# define HDMI_PHY_CONF0_ENTMDS_MASK 0x40 +# define HDMI_PHY_CONF0_ENTMDS_OFFSET 6 +# define HDMI_PHY_CONF0_GEN2_PDDQ_MASK 0x10 +# define HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET 4 +# define HDMI_PHY_CONF0_GEN2_TXPWRON_MASK 0x8 +# define HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET 3 +# define HDMI_PHY_CONF0_SELDATAENPOL_MASK 0x2 +# define HDMI_PHY_CONF0_SELDATAENPOL_OFFSET 1 +# define HDMI_PHY_CONF0_SELDIPIF_MASK 0x1 +# define HDMI_PHY_CONF0_SELDIPIF_OFFSET 0 +#define HDMI_PHY_TST0 0x3001 +# define HDMI_PHY_TST0_TSTCLR_MASK 0x20 +# define HDMI_PHY_TST0_TSTCLR_OFFSET 5 +#define HDMI_PHY_STAT0 0x3004 +# define HDMI_PHY_TX_PHY_LOCK 0x01 +#define HDMI_PHY_POL0 0x3007 +# define HDMI_PHY_POL0_HPD 0x02 + +/* HDMI Master PHY Registers */ +#define HDMI_PHY_I2CM_SLAVE_ADDR 0x3020 +# define HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2 0x69 +#define HDMI_PHY_I2CM_ADDRESS_ADDR 0x3021 +#define HDMI_PHY_I2CM_DATAO_1_ADDR 0x3022 +#define HDMI_PHY_I2CM_DATAO_0_ADDR 0x3023 +#define HDMI_PHY_I2CM_OPERATION_ADDR 0x3026 + +/* Main Controller Registers */ +#define HDMI_MC_CLKDIS 0x4001 +# define HDMI_MC_CLKDIS_HDCPCLK_DISABLE (1 << 6) +# define HDMI_MC_CLKDIS_CECCLK_DISABLE (1 << 5) +# define HDMI_MC_CLKDIS_AUDCLK_DISABLE (1 << 3) +# define HDMI_MC_CLKDIS_TMDSCLK_DISABLE (1 << 1) +# define HDMI_MC_CLKDIS_PIXELCLK_DISABLE (1 << 0) + +#define HDMI_MC_SWRSTZ 0x4002 +# define HDMI_MC_SWRSTZ_TMDSSWRST_REQ 0x02 +#define HDMI_MC_FLOWCTRL 0x4004 +# define HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS 0x0 +#define HDMI_MC_PHYRSTZ 0x4005 +# define HDMI_MC_PHYRSTZ_ASSERT 0x0 +# define HDMI_MC_PHYRSTZ_DEASSERT 0x1 +#define HDMI_MC_HEACPHY_RST 0x4007 +# define HDMI_MC_HEACPHY_RST_ASSERT 0x1 + +/* HDCP Encryption Engine Registers */ +#define HDMI_A_HDCPCFG0 0x5000 +# define HDMI_A_HDCPCFG0_RXDETECT_MASK 0x4 +# define HDMI_A_HDCPCFG0_RXDETECT_DISABLE 0x0 +#define HDMI_A_HDCPCFG1 0x5001 +# define HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK 0x2 +# define HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_DISABLE 0x2 +#define HDMI_A_VIDPOLCFG 0x5009 +# define HDMI_A_VIDPOLCFG_DATAENPOL_MASK 0x10 +# define HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_HIGH 0x10 + +/* I2C Master Registers (E-DDC) */ +#define HDMI_I2CM_SLAVE 0x7E00 +#define HDMI_I2CMESS 0x7E01 +#define HDMI_I2CM_DATAO 0x7E02 +#define HDMI_I2CM_DATAI 0x7E03 +#define HDMI_I2CM_OPERATION 0x7E04 +# define HDMI_PHY_I2CM_OPERATION_ADDR_WRITE 0x10 +# define HDMI_PHY_I2CM_OPERATION_ADDR_READ 0x1 +#define HDMI_I2CM_INT 0x7E05 +#define HDMI_I2CM_CTLINT 0x7E06 +#define HDMI_I2CM_DIV 0x7E07 +#define HDMI_I2CM_SEGADDR 0x7E08 +#define HDMI_I2CM_SOFTRSTZ 0x7E09 +#define HDMI_I2CM_SEGPTR 0x7E0A +#define HDMI_I2CM_SS_SCL_HCNT_1_ADDR 0x7E0B +#define HDMI_I2CM_SS_SCL_HCNT_0_ADDR 0x7E0C +#define HDMI_I2CM_SS_SCL_LCNT_1_ADDR 0x7E0D +#define HDMI_I2CM_SS_SCL_LCNT_0_ADDR 0x7E0E +#define HDMI_I2CM_FS_SCL_HCNT_1_ADDR 0x7E0F +#define HDMI_I2CM_FS_SCL_HCNT_0_ADDR 0x7E10 +#define HDMI_I2CM_FS_SCL_LCNT_1_ADDR 0x7E11 +#define HDMI_I2CM_FS_SCL_LCNT_0_ADDR 0x7E12 + +/* HDMI PHY register with access through I2C */ +#define HDMI_PHY_I2C_CKCALCTRL 0x5 +# define CKCALCTRL_OVERRIDE (1 << 15) +#define HDMI_PHY_I2C_CPCE_CTRL 0x6 +# define CPCE_CTRL_45_25 ((3 << 7) | (3 << 5)) +# define CPCE_CTRL_92_50 ((2 << 7) | (2 << 5)) +# define CPCE_CTRL_185 ((1 << 7) | (1 << 5)) +# define CPCE_CTRL_370 ((0 << 7) | (0 << 5)) +#define HDMI_PHY_I2C_CKSYMTXCTRL 0x9 +# define CKSYMTXCTRL_OVERRIDE (1 << 15) +# define CKSYMTXCTRL_TX_SYMON (1 << 3) +# define CKSYMTXCTRL_TX_TRAON (1 << 2) +# define CKSYMTXCTRL_TX_TRBON (1 << 1) +# define CKSYMTXCTRL_TX_CK_SYMON (1 << 0) +#define HDMI_PHY_I2C_VLEVCTRL 0x0E +# define VLEVCTRL_TX_LVL(x) ((x) << 5) +# define VLEVCTRL_CK_LVL(x) (x) +#define HDMI_PHY_I2C_CURRCTRL 0x10 +#define HDMI_PHY_I2C_PLLPHBYCTRL 0x13 +#define HDMI_PHY_I2C_GMPCTRL 0x15 +#define GMPCTRL_45_25 0x00 +#define GMPCTRL_92_50 0x05 +#define GMPCTRL_185 0x0a +#define GMPCTRL_370 0x0f +#define HDMI_PHY_I2C_MSM_CTRL 0x17 +# define MSM_CTRL_FB_CLK (0x3 << 1) +#define HDMI_PHY_I2C_TXTERM 0x19 +#define TXTERM_133 0x5 + +/* LCD controller registers */ + +#define LCDCFG 0x0000 +# define LCDCFG_NEWDES (1 << 28) +# define LCDCFG_RECOVER (1 << 25) +# define LCDCFG_PSM (1 << 23) +# define LCDCFG_CLSM (1 << 22) +# define LCDCFG_SPLM (1 << 21) +# define LCDCFG_REVM (1 << 20) +# define LCDCFG_PCP (1 << 10) +# define LCDCFG_24 (1 << 6) +#define LCDCTRL 0x0030 +# define LCDCTRL_BST (0x7 << 28) +# define LCDCTRL_BST_64 (4 << 28) +# define LCDCTRL_OFUM (1 << 11) +# define LCDCTRL_DIS (1 << 4) +# define LCDCTRL_ENA (1 << 3) + +#define LCDSTATE 0x0034 +# define LCDSTATE_LDD (1 << 0) +#define LCDOSDS 0x0108 +#define LCDRGBC 0x0090 +# define LCDRGBC_RGBFMT (1 << 7) +#define LCDVAT 0x000c +# define LCDVAT_HT_SHIFT 16 +# define LCDVAT_VT_SHIFT 0 +#define LCDDAH 0x0010 +# define LCDDAH_HDS_SHIFT 16 +# define LCDDAH_HDE_SHIFT 0 +#define LCDDAV 0x0014 +# define LCDDAV_VDS_SHIFT 16 +# define LCDDAV_VDE_SHIFT 0 +#define LCDVSYNC 0x0004 +#define LCDHSYNC 0x0008 +#define LCDDA0 0x0040 +#define LCDDA1 0x0050 +#define LCDPCFG 0x02c0 + +/* Descriptor flags */ + +#define LCDCMD_FRM_EN (1 << 26) + +#define LCDPOS_BPP01_15_16 (4 << 27) +#define LCDPOS_BPP01_24_COMP (6 << 27) +#define LCDPOS_PREMULTI01 (1 << 26) +#define LCDPOS_COEF_SLE01 (0x3 << 24) +#define LCDPOS_COEF_BLE01_1 (1 << 24) + +#define LCDDESSIZE_ALPHA (0xff << 24) +#define LCDDESSIZE_HEIGHT_SHIFT 12 +#define LCDDESSIZE_WIDTH_SHIFT 0 + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Get information about the video controller configuration and the + * configuration of each color plane. + */ + +static int jz_getvideoinfo(struct fb_vtable_s *vtable, + struct fb_videoinfo_s *vinfo); +static int jz_getplaneinfo(struct fb_vtable_s *vtable, int planeno, + struct fb_planeinfo_s *pinfo); + +/* The following is provided only if the video hardware supports RGB color + * mapping + */ + +#ifdef CONFIG_FB_CMAP +static int jz_getcmap(struct fb_vtable_s *vtable, + struct fb_cmap_s *cmap); +static int jz_putcmap(struct fb_vtable_s *vtable, + const struct fb_cmap_s *cmap); +#endif + +/* The following is provided only if the video hardware supports a hardware + * cursor + */ + +#ifdef CONFIG_FB_HWCURSOR +static int jz_getcursor(struct fb_vtable_s *vtable, + struct fb_cursorattrib_s *attrib); +static int jz_setcursor(struct fb_vtable_s *vtable, + struct fb_setcursor_s *settings); +#endif + +static uint8_t hdmi_rd1(uint32_t off); +static void hdmi_wr1(uint32_t off, uint8_t val); +static void dwc_hdmi_init(void); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* This structure describes the video controller */ + +static const struct fb_videoinfo_s g_videoinfo = +{ + .fmt = JZ4780_COLOR_FMT, + .xres = JZ4780_LCD_HWIDTH, + .yres = JZ4780_LCD_VHEIGHT, + .nplanes = 1, +}; + +/* This structure describes the single color plane */ + +static const struct fb_planeinfo_s g_planeinfo = +{ + .fbmem = (void *)JZ4780_LCD_VRAMBASE, + .fblen = JZ4780_FBSIZE, + .stride = JZ4780_STRIDE, + .display = 0, + .bpp = JZ4780_BPP, +}; + +/* Current cursor position */ + +#ifdef CONFIG_FB_HWCURSOR +static struct fb_cursorpos_s g_cpos; + +/* Current cursor size */ + +#ifdef CONFIG_FB_HWCURSORSIZE +static struct fb_cursorsize_s g_csize; +#endif +#endif + +/* The framebuffer object -- There is no private state information in this + * framebuffer driver. + */ + +struct fb_vtable_s g_fbobject = +{ + .getvideoinfo = jz_getvideoinfo, + .getplaneinfo = jz_getplaneinfo, +#ifdef CONFIG_FB_CMAP + .getcmap = jz_getcmap, + .putcmap = jz_putcmap, +#endif +#ifdef CONFIG_FB_HWCURSOR + .getcursor = jz_getcursor, + .setcursor = jz_setcursor, +#endif +}; + +struct lcd_frame_descriptor +{ + uint32_t next; + uint32_t physaddr; + uint32_t id; + uint32_t cmd; + uint32_t offs; + uint32_t pw; + uint32_t cnum_pos; + uint32_t dessize; +} __packed; + +struct videomode_s +{ + int dot_clock; /* Dot clock frequency in kHz. */ + int hdisplay; + int hsync_start; + int hsync_end; + int htotal; + int vdisplay; + int vsync_start; + int vsync_end; + int vtotal; + int flags; /* Video mode flags; see below. */ +}; + +struct display_s +{ + uint32_t fbsize; + uint32_t paddr; + uint32_t vaddr; + + uint32_t fdesc_paddr; + struct lcd_frame_descriptor *fdesc; + + struct videomode_s sc_mode; +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static uint8_t hdmi_rd1(uint32_t off) +{ + return getreg8(0xb0180000 + (off << JZ_REG_SHIFT)); +} + +static void hdmi_wr1(uint32_t off, uint8_t val) +{ + putreg8(val, 0xb0180000 + (off << JZ_REG_SHIFT)); +} + +static uint32_t lcd_read(struct display_s *sc, uint32_t reg) +{ + return getreg32(0xb3050000 + reg); +} + +static void lcd_write(struct display_s *sc, uint32_t reg, uint32_t val) +{ + putreg32(val, 0xb3050000 + reg); +} + +static void +dwc_hdmi_phy_wait_i2c_done(int msec) +{ + uint8_t val; + + val = hdmi_rd1(HDMI_IH_I2CMPHY_STAT0) & + (HDMI_IH_I2CMPHY_STAT0_DONE | HDMI_IH_I2CMPHY_STAT0_ERROR); + while (val == 0) + { + up_mdelay(10); + msec -= 10; + if (msec <= 0) + return; + val = hdmi_rd1(HDMI_IH_I2CMPHY_STAT0) & + (HDMI_IH_I2CMPHY_STAT0_DONE | HDMI_IH_I2CMPHY_STAT0_ERROR); + } +} + +static void +dwc_hdmi_phy_i2c_write(unsigned short data, unsigned char addr) +{ + /* clear DONE and ERROR flags */ + + hdmi_wr1(HDMI_IH_I2CMPHY_STAT0, + HDMI_IH_I2CMPHY_STAT0_DONE | HDMI_IH_I2CMPHY_STAT0_ERROR); + hdmi_wr1(HDMI_PHY_I2CM_ADDRESS_ADDR, addr); + hdmi_wr1(HDMI_PHY_I2CM_DATAO_1_ADDR, ((data >> 8) & 0xff)); + hdmi_wr1(HDMI_PHY_I2CM_DATAO_0_ADDR, ((data >> 0) & 0xff)); + hdmi_wr1(HDMI_PHY_I2CM_OPERATION_ADDR, HDMI_PHY_I2CM_OPERATION_ADDR_WRITE); + dwc_hdmi_phy_wait_i2c_done(1000); +} + +static void +dwc_hdmi_disable_overflow_interrupts(void) +{ + hdmi_wr1(HDMI_IH_MUTE_FC_STAT2, HDMI_IH_MUTE_FC_STAT2_OVERFLOW_MASK); + hdmi_wr1(HDMI_FC_MASK2, HDMI_FC_MASK2_LOW_PRI | HDMI_FC_MASK2_HIGH_PRI); +} + +static void +dwc_hdmi_av_composer(struct display_s *sc) +{ + uint8_t inv_val; + int hblank; + int vblank; + int hsync_len; + int hfp; + int vfp; + + /* Set up HDMI_FC_INVIDCONF */ + + inv_val = ((sc->sc_mode.flags & VID_PVSYNC) ? + HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_HIGH : + HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_LOW); + + inv_val |= ((sc->sc_mode.flags & VID_PHSYNC) ? + HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_HIGH : + HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_LOW); + + inv_val |= HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_HIGH; + + inv_val |= ((sc->sc_mode.flags & VID_INTERLACE) ? + HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_HIGH : + HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_LOW); + + inv_val |= ((sc->sc_mode.flags & VID_INTERLACE) ? + HDMI_FC_INVIDCONF_IN_I_P_INTERLACED : + HDMI_FC_INVIDCONF_IN_I_P_PROGRESSIVE); + + /* TODO: implement HDMI part */ + + inv_val |= HDMI_FC_INVIDCONF_DVI_MODEZ_DVI_MODE; + + hdmi_wr1(HDMI_FC_INVIDCONF, inv_val); + + /* Set up horizontal active pixel region width */ + + hdmi_wr1(HDMI_FC_INHACTV1, sc->sc_mode.hdisplay >> 8); + hdmi_wr1(HDMI_FC_INHACTV0, sc->sc_mode.hdisplay); + + /* Set up vertical blanking pixel region width */ + + hdmi_wr1(HDMI_FC_INVACTV1, sc->sc_mode.vdisplay >> 8); + hdmi_wr1(HDMI_FC_INVACTV0, sc->sc_mode.vdisplay); + + /* Set up horizontal blanking pixel region width */ + + hblank = sc->sc_mode.htotal - sc->sc_mode.hdisplay; + hdmi_wr1(HDMI_FC_INHBLANK1, hblank >> 8); + hdmi_wr1(HDMI_FC_INHBLANK0, hblank); + + /* Set up vertical blanking pixel region width */ + + vblank = sc->sc_mode.vtotal - sc->sc_mode.vdisplay; + hdmi_wr1(HDMI_FC_INVBLANK, vblank); + + /* Set up HSYNC active edge delay width (in pixel clks) */ + + hfp = sc->sc_mode.hsync_start - sc->sc_mode.hdisplay; + hdmi_wr1(HDMI_FC_HSYNCINDELAY1, hfp >> 8); + hdmi_wr1(HDMI_FC_HSYNCINDELAY0, hfp); + + /* Set up VSYNC active edge delay (in pixel clks) */ + + vfp = sc->sc_mode.vsync_start - sc->sc_mode.vdisplay; + hdmi_wr1(HDMI_FC_VSYNCINDELAY, vfp); + + hsync_len = (sc->sc_mode.hsync_end - sc->sc_mode.hsync_start); + + /* Set up HSYNC active pulse width (in pixel clks) */ + + hdmi_wr1(HDMI_FC_HSYNCINWIDTH1, hsync_len >> 8); + hdmi_wr1(HDMI_FC_HSYNCINWIDTH0, hsync_len); + + /* Set up VSYNC active edge delay (in pixel clks) */ + + hdmi_wr1(HDMI_FC_VSYNCINWIDTH, + sc->sc_mode.vsync_end - sc->sc_mode.vsync_start); +} + +static void +dwc_hdmi_phy_enable_power(uint8_t enable) +{ + uint8_t reg; + + reg = hdmi_rd1(HDMI_PHY_CONF0); + reg &= ~HDMI_PHY_CONF0_PDZ_MASK; + reg |= (enable << HDMI_PHY_CONF0_PDZ_OFFSET); + hdmi_wr1(HDMI_PHY_CONF0, reg); +} + +static void +dwc_hdmi_phy_enable_tmds(uint8_t enable) +{ + uint8_t reg; + + reg = hdmi_rd1(HDMI_PHY_CONF0); + reg &= ~HDMI_PHY_CONF0_ENTMDS_MASK; + reg |= (enable << HDMI_PHY_CONF0_ENTMDS_OFFSET); + hdmi_wr1(HDMI_PHY_CONF0, reg); +} + +static void +dwc_hdmi_phy_gen2_pddq(uint8_t enable) +{ + uint8_t reg; + + reg = hdmi_rd1(HDMI_PHY_CONF0); + reg &= ~HDMI_PHY_CONF0_GEN2_PDDQ_MASK; + reg |= (enable << HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET); + hdmi_wr1(HDMI_PHY_CONF0, reg); +} + +static void +dwc_hdmi_phy_gen2_txpwron(uint8_t enable) +{ + uint8_t reg; + + reg = hdmi_rd1(HDMI_PHY_CONF0); + reg &= ~HDMI_PHY_CONF0_GEN2_TXPWRON_MASK; + reg |= (enable << HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET); + hdmi_wr1(HDMI_PHY_CONF0, reg); +} + +static void +dwc_hdmi_phy_sel_data_en_pol(uint8_t enable) +{ + uint8_t reg; + + reg = hdmi_rd1(HDMI_PHY_CONF0); + reg &= ~HDMI_PHY_CONF0_SELDATAENPOL_MASK; + reg |= (enable << HDMI_PHY_CONF0_SELDATAENPOL_OFFSET); + hdmi_wr1(HDMI_PHY_CONF0, reg); +} + +static void +dwc_hdmi_phy_sel_interface_control(uint8_t enable) +{ + uint8_t reg; + + reg = hdmi_rd1(HDMI_PHY_CONF0); + reg &= ~HDMI_PHY_CONF0_SELDIPIF_MASK; + reg |= (enable << HDMI_PHY_CONF0_SELDIPIF_OFFSET); + hdmi_wr1(HDMI_PHY_CONF0, reg); +} + +static inline void +dwc_hdmi_phy_test_clear(unsigned char bit) +{ + uint8_t val; + + val = hdmi_rd1(HDMI_PHY_TST0); + val &= ~HDMI_PHY_TST0_TSTCLR_MASK; + val |= (bit << HDMI_PHY_TST0_TSTCLR_OFFSET) & + HDMI_PHY_TST0_TSTCLR_MASK; + hdmi_wr1(HDMI_PHY_TST0, val); +} + +static void dwc_hdmi_clear_overflow(void) +{ + int count; + uint8_t val; + + /* TMDS software reset */ + + hdmi_wr1(HDMI_MC_SWRSTZ, (uint8_t)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ); + + val = hdmi_rd1(HDMI_FC_INVIDCONF); + + for (count = 0 ; count < 4 ; count++) + hdmi_wr1(HDMI_FC_INVIDCONF, val); +} + +static int +dwc_hdmi_phy_configure(int dot_clock) +{ + uint8_t val; + uint8_t msec; + + hdmi_wr1(HDMI_MC_FLOWCTRL, HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS); + + /* gen2 tx power off */ + + dwc_hdmi_phy_gen2_txpwron(0); + + /* gen2 pddq */ + + dwc_hdmi_phy_gen2_pddq(1); + + /* PHY reset */ + + hdmi_wr1(HDMI_MC_PHYRSTZ, HDMI_MC_PHYRSTZ_DEASSERT); + hdmi_wr1(HDMI_MC_PHYRSTZ, HDMI_MC_PHYRSTZ_ASSERT); + + hdmi_wr1(HDMI_MC_HEACPHY_RST, HDMI_MC_HEACPHY_RST_ASSERT); + + dwc_hdmi_phy_test_clear(1); + hdmi_wr1(HDMI_PHY_I2CM_SLAVE_ADDR, HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2); + dwc_hdmi_phy_test_clear(0); + + /* Following initialization are for 8bit per color case */ + + /* PLL/MPLL config, see section 24.7.22 in TRM + * config, see section 24.7.22 + */ + + if (dot_clock * 1000 <= 45250000) + { + dwc_hdmi_phy_i2c_write(CPCE_CTRL_45_25, HDMI_PHY_I2C_CPCE_CTRL); + dwc_hdmi_phy_i2c_write(GMPCTRL_45_25, HDMI_PHY_I2C_GMPCTRL); + } + else if (dot_clock * 1000 <= 92500000) + { + dwc_hdmi_phy_i2c_write(CPCE_CTRL_92_50, HDMI_PHY_I2C_CPCE_CTRL); + dwc_hdmi_phy_i2c_write(GMPCTRL_92_50, HDMI_PHY_I2C_GMPCTRL); + } + else if (dot_clock * 1000 <= 185000000) + { + dwc_hdmi_phy_i2c_write(CPCE_CTRL_185, HDMI_PHY_I2C_CPCE_CTRL); + dwc_hdmi_phy_i2c_write(GMPCTRL_185, HDMI_PHY_I2C_GMPCTRL); + } + else + { + dwc_hdmi_phy_i2c_write(CPCE_CTRL_370, HDMI_PHY_I2C_CPCE_CTRL); + dwc_hdmi_phy_i2c_write(GMPCTRL_370, HDMI_PHY_I2C_GMPCTRL); + } + + /* Values described in TRM section 34.9.2 PLL/MPLL Generic + * Configuration Settings. Table 34-23. + */ + + if (dot_clock * 1000 <= 54000000) + dwc_hdmi_phy_i2c_write(0x091c, HDMI_PHY_I2C_CURRCTRL); + else if (dot_clock * 1000 <= 58400000) + dwc_hdmi_phy_i2c_write(0x091c, HDMI_PHY_I2C_CURRCTRL); + else if (dot_clock * 1000 <= 72000000) + dwc_hdmi_phy_i2c_write(0x06dc, HDMI_PHY_I2C_CURRCTRL); + else if (dot_clock * 1000 <= 74250000) + dwc_hdmi_phy_i2c_write(0x06dc, HDMI_PHY_I2C_CURRCTRL); + else if (dot_clock * 1000 <= 118800000) + dwc_hdmi_phy_i2c_write(0x091c, HDMI_PHY_I2C_CURRCTRL); + else if (dot_clock * 1000 <= 216000000) + dwc_hdmi_phy_i2c_write(0x06dc, HDMI_PHY_I2C_CURRCTRL); + else + { + /* Unsupported mode */ + + return -EINVAL; + } + + dwc_hdmi_phy_i2c_write(0x0000, HDMI_PHY_I2C_PLLPHBYCTRL); + dwc_hdmi_phy_i2c_write(MSM_CTRL_FB_CLK, HDMI_PHY_I2C_MSM_CTRL); + + /* RESISTANCE TERM 133 Ohm */ + + dwc_hdmi_phy_i2c_write(TXTERM_133, HDMI_PHY_I2C_TXTERM); + + /* REMOVE CLK TERM */ + + dwc_hdmi_phy_i2c_write(CKCALCTRL_OVERRIDE, HDMI_PHY_I2C_CKCALCTRL); + + if (dot_clock * 1000 > 148500000) + { + dwc_hdmi_phy_i2c_write(CKSYMTXCTRL_OVERRIDE | CKSYMTXCTRL_TX_SYMON | + CKSYMTXCTRL_TX_TRBON | CKSYMTXCTRL_TX_CK_SYMON, + HDMI_PHY_I2C_CKSYMTXCTRL); + dwc_hdmi_phy_i2c_write(VLEVCTRL_TX_LVL(9) | VLEVCTRL_CK_LVL(9), + HDMI_PHY_I2C_VLEVCTRL); + } + else + { + dwc_hdmi_phy_i2c_write(CKSYMTXCTRL_OVERRIDE | CKSYMTXCTRL_TX_SYMON | + CKSYMTXCTRL_TX_TRAON | CKSYMTXCTRL_TX_CK_SYMON, + HDMI_PHY_I2C_CKSYMTXCTRL); + dwc_hdmi_phy_i2c_write(VLEVCTRL_TX_LVL(13) | VLEVCTRL_CK_LVL(13), + HDMI_PHY_I2C_VLEVCTRL); + } + + dwc_hdmi_phy_enable_power(1); + + /* toggle TMDS enable */ + + dwc_hdmi_phy_enable_tmds(0); + dwc_hdmi_phy_enable_tmds(1); + + /* gen2 tx power on */ + + dwc_hdmi_phy_gen2_txpwron(1); + dwc_hdmi_phy_gen2_pddq(0); + + /* Wait for PHY PLL lock */ + + msec = 4; + val = hdmi_rd1(HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK; + while (val == 0) + { + up_mdelay(1); + if (msec-- == 0) + { + lcderr("PHY PLL not locked\n"); + return -1; + } + + val = hdmi_rd1(HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK; + } + + return OK; +} + +static int +dwc_hdmi_phy_init(int dot_clock) +{ + int i; + int ret; + + /* HDMI Phy spec says to do the phy initialization sequence twice */ + + for (i = 0 ; i < 2 ; i++) + { + dwc_hdmi_phy_sel_data_en_pol(1); + dwc_hdmi_phy_sel_interface_control(0); + dwc_hdmi_phy_enable_tmds(0); + dwc_hdmi_phy_enable_power(0); + + /* Enable CSC */ + + ret = dwc_hdmi_phy_configure(dot_clock); + } + + return ret; +} + +static void dwc_hdmi_enable_video_path(void) +{ + uint8_t clkdis; + + /* Control period timing + * Values are minimal according to HDMI spec 1.4a + */ + + hdmi_wr1(HDMI_FC_CTRLDUR, 12); + hdmi_wr1(HDMI_FC_EXCTRLDUR, 32); + hdmi_wr1(HDMI_FC_EXCTRLSPAC, 1); + + /* Bits to fill data lines not used to transmit preamble + * for channels 0, 1, and 2 respectively + */ + + hdmi_wr1(HDMI_FC_CH0PREAM, 0x0b); + hdmi_wr1(HDMI_FC_CH1PREAM, 0x16); + hdmi_wr1(HDMI_FC_CH2PREAM, 0x21); + + /* Save CEC clock */ + + clkdis = hdmi_rd1(HDMI_MC_CLKDIS) & HDMI_MC_CLKDIS_CECCLK_DISABLE; + clkdis |= ~HDMI_MC_CLKDIS_CECCLK_DISABLE; + + /* Enable pixel clock and tmds data path */ + + clkdis &= ~HDMI_MC_CLKDIS_PIXELCLK_DISABLE; + hdmi_wr1(HDMI_MC_CLKDIS, clkdis); + + clkdis &= ~HDMI_MC_CLKDIS_TMDSCLK_DISABLE; + hdmi_wr1(HDMI_MC_CLKDIS, clkdis); +} + +static void +dwc_hdmi_video_packetize(void) +{ + unsigned int color_depth = 0; + unsigned int remap_size = HDMI_VP_REMAP_YCC422_16BIT; + unsigned int output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_PP; + uint8_t val; + + output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS; + color_depth = 4; + + /* set the packetizer registers */ + + val = ((color_depth << HDMI_VP_PR_CD_COLOR_DEPTH_OFFSET) & + HDMI_VP_PR_CD_COLOR_DEPTH_MASK); + hdmi_wr1(HDMI_VP_PR_CD, val); + + val = hdmi_rd1(HDMI_VP_STUFF); + val &= ~HDMI_VP_STUFF_PR_STUFFING_MASK; + val |= HDMI_VP_STUFF_PR_STUFFING_STUFFING_MODE; + hdmi_wr1(HDMI_VP_STUFF, val); + + val = hdmi_rd1(HDMI_VP_CONF); + val &= ~(HDMI_VP_CONF_PR_EN_MASK | + HDMI_VP_CONF_BYPASS_SELECT_MASK); + val |= HDMI_VP_CONF_PR_EN_DISABLE | + HDMI_VP_CONF_BYPASS_SELECT_VID_PACKETIZER; + hdmi_wr1(HDMI_VP_CONF, val); + + val = hdmi_rd1(HDMI_VP_STUFF); + val &= ~HDMI_VP_STUFF_IDEFAULT_PHASE_MASK; + val |= 1 << HDMI_VP_STUFF_IDEFAULT_PHASE_OFFSET; + hdmi_wr1(HDMI_VP_STUFF, val); + + hdmi_wr1(HDMI_VP_REMAP, remap_size); + + if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_PP) + { + val = hdmi_rd1(HDMI_VP_CONF); + val &= ~(HDMI_VP_CONF_BYPASS_EN_MASK | + HDMI_VP_CONF_PP_EN_ENMASK | + HDMI_VP_CONF_YCC422_EN_MASK); + val |= HDMI_VP_CONF_BYPASS_EN_DISABLE | + HDMI_VP_CONF_PP_EN_ENABLE | + HDMI_VP_CONF_YCC422_EN_DISABLE; + hdmi_wr1(HDMI_VP_CONF, val); + } + else if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_YCC422) + { + val = hdmi_rd1(HDMI_VP_CONF); + val &= ~(HDMI_VP_CONF_BYPASS_EN_MASK | + HDMI_VP_CONF_PP_EN_ENMASK | + HDMI_VP_CONF_YCC422_EN_MASK); + val |= HDMI_VP_CONF_BYPASS_EN_DISABLE | + HDMI_VP_CONF_PP_EN_DISABLE | + HDMI_VP_CONF_YCC422_EN_ENABLE; + hdmi_wr1(HDMI_VP_CONF, val); + } + else if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS) + { + val = hdmi_rd1(HDMI_VP_CONF); + val &= ~(HDMI_VP_CONF_BYPASS_EN_MASK | + HDMI_VP_CONF_PP_EN_ENMASK | + HDMI_VP_CONF_YCC422_EN_MASK); + val |= HDMI_VP_CONF_BYPASS_EN_ENABLE | + HDMI_VP_CONF_PP_EN_DISABLE | + HDMI_VP_CONF_YCC422_EN_DISABLE; + hdmi_wr1(HDMI_VP_CONF, val); + } + else + { + return; + } + + val = hdmi_rd1(HDMI_VP_STUFF); + val &= ~(HDMI_VP_STUFF_PP_STUFFING_MASK | + HDMI_VP_STUFF_YCC422_STUFFING_MASK); + val |= HDMI_VP_STUFF_PP_STUFFING_STUFFING_MODE | + HDMI_VP_STUFF_YCC422_STUFFING_STUFFING_MODE; + hdmi_wr1(HDMI_VP_STUFF, val); + + val = hdmi_rd1(HDMI_VP_CONF); + val &= ~HDMI_VP_CONF_OUTPUT_SELECTOR_MASK; + val |= output_select; + hdmi_wr1(HDMI_VP_CONF, val); +} + +static void dwc_hdmi_video_sample(void) +{ + int color_format; + uint8_t val; + + color_format = 0x01; + val = HDMI_TX_INVID0_INTERNAL_DE_GENERATOR_DISABLE | + ((color_format << HDMI_TX_INVID0_VIDEO_MAPPING_OFFSET) & + HDMI_TX_INVID0_VIDEO_MAPPING_MASK); + hdmi_wr1(HDMI_TX_INVID0, val); + + /* Enable TX stuffing: When DE is inactive, fix the output data to 0 */ + + val = HDMI_TX_INSTUFFING_BDBDATA_STUFFING_ENABLE | + HDMI_TX_INSTUFFING_RCRDATA_STUFFING_ENABLE | + HDMI_TX_INSTUFFING_GYDATA_STUFFING_ENABLE; + hdmi_wr1(HDMI_TX_INSTUFFING, val); + hdmi_wr1(HDMI_TX_GYDATA0, 0x0); + hdmi_wr1(HDMI_TX_GYDATA1, 0x0); + hdmi_wr1(HDMI_TX_RCRDATA0, 0x0); + hdmi_wr1(HDMI_TX_RCRDATA1, 0x0); + hdmi_wr1(HDMI_TX_BCBDATA0, 0x0); + hdmi_wr1(HDMI_TX_BCBDATA1, 0x0); +} + +static void dwc_hdmi_tx_hdcp_config(void) +{ + uint8_t de; + uint8_t val; + + de = HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_HIGH; + + /* Disable RX detect */ + + val = hdmi_rd1(HDMI_A_HDCPCFG0); + val &= ~HDMI_A_HDCPCFG0_RXDETECT_MASK; + val |= HDMI_A_HDCPCFG0_RXDETECT_DISABLE; + hdmi_wr1(HDMI_A_HDCPCFG0, val); + + /* Set polarity */ + + val = hdmi_rd1(HDMI_A_VIDPOLCFG); + val &= ~HDMI_A_VIDPOLCFG_DATAENPOL_MASK; + val |= de; + hdmi_wr1(HDMI_A_VIDPOLCFG, val); + + /* Disable encryption */ + + val = hdmi_rd1(HDMI_A_HDCPCFG1); + val &= ~HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK; + val |= HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_DISABLE; + hdmi_wr1(HDMI_A_HDCPCFG1, val); +} + +static int +dwc_hdmi_set_mode(struct display_s *sc) +{ + int ret; + + dwc_hdmi_disable_overflow_interrupts(); + dwc_hdmi_av_composer(sc); + ret = dwc_hdmi_phy_init(sc->sc_mode.dot_clock); + if (ret != OK) + { + return ret; + } + + dwc_hdmi_enable_video_path(); + + dwc_hdmi_video_packetize(); + dwc_hdmi_video_sample(); + dwc_hdmi_tx_hdcp_config(); + dwc_hdmi_clear_overflow(); + + return 0; +} + +void +dwc_hdmi_init(void) +{ + lcdinfo("HDMI controller %02x:%02x:%02x:%02x\n", + hdmi_rd1(HDMI_DESIGN_ID), hdmi_rd1(HDMI_REVISION_ID), + hdmi_rd1(HDMI_PRODUCT_ID0), hdmi_rd1(HDMI_PRODUCT_ID1)); + + hdmi_wr1(HDMI_PHY_POL0, HDMI_PHY_POL0_HPD); + hdmi_wr1(HDMI_IH_PHY_STAT0, HDMI_IH_PHY_STAT0_HPD); +} + +static void +jzlcd_start(struct display_s *sc) +{ + uint32_t ctrl; + + /* Clear status registers */ + + lcd_write(sc, LCDSTATE, 0); + lcd_write(sc, LCDOSDS, 0); + + /* Enable the controller */ + + ctrl = lcd_read(sc, LCDCTRL); + ctrl |= LCDCTRL_ENA; + ctrl &= ~LCDCTRL_DIS; + lcd_write(sc, LCDCTRL, ctrl); +} + +static void +jzlcd_stop(struct display_s *sc) +{ + uint32_t ctrl; + + ctrl = lcd_read(sc, LCDCTRL); + if ((ctrl & LCDCTRL_ENA) != 0) + { + /* Disable the controller and wait for it to stop */ + + ctrl |= LCDCTRL_DIS; + lcd_write(sc, LCDCTRL, ctrl); + while ((lcd_read(sc, LCDSTATE) & LCDSTATE_LDD) == 0) + { + up_mdelay(100); + } + } + + /* Clear all status except for disable */ + + lcd_write(sc, LCDSTATE, lcd_read(sc, LCDSTATE) & ~LCDSTATE_LDD); +} + +static void +jzlcd_setup_descriptor(struct display_s *sc, const struct videomode_s *mode, + u_int desno) +{ + uint32_t f0xpos = 0; + uint32_t f0ypos = 0; + uint32_t f1xpos = 0; + uint32_t f1ypos = 0; + uint32_t f0width = mode->hdisplay; + uint32_t f0height = mode->vdisplay; + uint32_t f1width = mode->hdisplay; + uint32_t f1height = mode->vdisplay; + + uint32_t bpp; + bpp = JZ4780_BPP == 16 ? LCDPOS_BPP01_15_16 : LCDPOS_BPP01_24_COMP; + + /* Frame size is specified in # words */ + + int line_sz = ((desno ? f1width : f0width) * JZ4780_BPP) >> 3; + line_sz = ((line_sz + 3) & ~3) / 4; + + struct lcd_frame_descriptor *fdesc = sc->fdesc + desno; + fdesc->id = desno; + fdesc->offs = 0; + fdesc->pw = 0; + + if (desno == 0) + { + fdesc->next = sc->fdesc_paddr + sizeof(struct lcd_frame_descriptor); + + fdesc->physaddr = sc->paddr + 0; + fdesc->cmd = LCDCMD_FRM_EN | (line_sz * f0height); + fdesc->cnum_pos = bpp | LCDPOS_PREMULTI01 | (f0ypos << 12) | f0xpos | + LCDPOS_COEF_BLE01_1; + fdesc->dessize = LCDDESSIZE_ALPHA | + ((f0height - 1) << LCDDESSIZE_HEIGHT_SHIFT) | + ((f0width - 1) << LCDDESSIZE_WIDTH_SHIFT); + } + else + { + fdesc->next = sc->fdesc_paddr; + + fdesc->physaddr = sc->paddr + FOREGROUND_OFFSET; + fdesc->cmd = LCDCMD_FRM_EN | (line_sz *f1height); + fdesc->cnum_pos = bpp | LCDPOS_PREMULTI01 | (f1ypos << 12) | f1xpos | + LCDPOS_COEF_SLE01; + fdesc->dessize = LCDDESSIZE_ALPHA | + ((f1height - 1) << LCDDESSIZE_HEIGHT_SHIFT) | + ((f1width - 1) << LCDDESSIZE_WIDTH_SHIFT); + } +} + +static int +jzlcd_set_videomode(struct display_s *sc, const struct videomode_s *mode) +{ + u_int hbp, hfp, hsw, vbp, vfp, vsw; + u_int hds, hde, ht, vds, vde, vt; + uint32_t ctrl; + + hbp = mode->htotal - mode->hsync_end; + hfp = mode->hsync_start - mode->hdisplay; + hsw = mode->hsync_end - mode->hsync_start; + vbp = mode->vtotal - mode->vsync_end; + vfp = mode->vsync_start - mode->vdisplay; + vsw = mode->vsync_end - mode->vsync_start; + + hds = hsw + hbp; + hde = hds + mode->hdisplay; + ht = hde + hfp; + + vds = vsw + vbp; + vde = vds + mode->vdisplay; + vt = vde + vfp; + + /* Setup timings */ + + lcd_write(sc, LCDVAT, + (ht << LCDVAT_HT_SHIFT) | (vt << LCDVAT_VT_SHIFT)); + lcd_write(sc, LCDDAH, + (hds << LCDDAH_HDS_SHIFT) | (hde << LCDDAH_HDE_SHIFT)); + lcd_write(sc, LCDDAV, + (vds << LCDDAV_VDS_SHIFT) | (vde << LCDDAV_VDE_SHIFT)); + lcd_write(sc, LCDHSYNC, hsw); + lcd_write(sc, LCDVSYNC, vsw); + + /* Set configuration */ + + lcd_write(sc, LCDCFG, LCDCFG_NEWDES | LCDCFG_RECOVER | LCDCFG_24 | + LCDCFG_PSM | LCDCFG_CLSM | LCDCFG_SPLM | LCDCFG_REVM | LCDCFG_PCP); + ctrl = lcd_read(sc, LCDCTRL); + ctrl &= ~LCDCTRL_BST; + ctrl |= LCDCTRL_BST_64 | LCDCTRL_OFUM; + lcd_write(sc, LCDCTRL, ctrl); + lcd_write(sc, LCDPCFG, PCFG_MAGIC); + lcd_write(sc, LCDRGBC, LCDRGBC_RGBFMT); + + /* Update registers */ + + lcd_write(sc, LCDSTATE, 0); + + /* Setup frame descriptors */ + + jzlcd_setup_descriptor(sc, mode, 0); + jzlcd_setup_descriptor(sc, mode, 1); + + /* Setup DMA channels */ + + lcd_write(sc, LCDDA0, sc->fdesc_paddr + + sizeof(struct lcd_frame_descriptor)); + lcd_write(sc, LCDDA1, sc->fdesc_paddr); + + /* Set display clock */ + + putreg32(LPCS_VPLL | CE_LCD | LPCDR_VAL, LP1CDR_REG); + while (getreg32(LP1CDR_REG) & LCD_BUSY) + { + } + + return 0; +} + +static int +jzlcd_configure(struct display_s *sc, const struct videomode_s *mode) +{ + uint32_t vaddr0 = (uint32_t)JZ4780_LCD_VRAMBASE; + + sc->fbsize = 0x00300000; + sc->vaddr = vaddr0; + sc->paddr = jz_physramaddr(vaddr0); + sc->fdesc = (struct lcd_frame_descriptor *)(sc->vaddr + sc->fbsize); + sc->fdesc_paddr = sc->paddr + sc->fbsize; + + /* Setup video mode */ + + int error = jzlcd_set_videomode(sc, mode); + if (error != 0) + { + return error; + } + + return 0; +} + +static void +jzlcd_hdmi_event(struct display_s *sc) +{ + /* Stop the controller */ + + jzlcd_stop(sc); + + /* Configure LCD controller */ + + int error = jzlcd_configure(sc, &sc->sc_mode); + if (error != 0) + { + lcderr("failed to configure FB: %d\n", error); + return; + } + + error = dwc_hdmi_set_mode(sc); + if (error != 0) + { + lcderr("hdmi error: %d\n", error); + return; + } + + /* Start the controller! */ + + jzlcd_start(sc); +} + +void jzlcd_clocks(void) +{ + uint32_t val; + + putreg32(0xa0000020, HDMICDR_REG); + + val = getreg32(CLKGR1_REG); + val &= ~CLKGR1_HDMI; + putreg32(val, CLKGR1_REG); +} + +int jzlcd_attach(void) +{ + uint32_t val; + static struct display_s sc_obj = { + .fbsize = 0, + .paddr = 0, + .vaddr = 0, + .fdesc_paddr = 0, + .fdesc = NULL, + + .sc_mode = { + .dot_clock = 85500, + .hdisplay = 1360, + .hsync_start = 1424, + .hsync_end = 1536, + .htotal = 1792, + .vdisplay = 768, + .vsync_start = 771, + .vsync_end = 777, + .vtotal = 795, + .flags = 5, + }, + }; + + val = getreg32(CLKGR0_REG); + val &= ~CLKGR0_LCD; + val &= ~CLKGR0_TVE; + putreg32(val, CLKGR0_REG); + + jzlcd_hdmi_event(&sc_obj); + return 1; +} + +/**************************************************************************** + * Name: jz_getvideoinfo + ****************************************************************************/ + +static int jz_getvideoinfo(struct fb_vtable_s *vtable, + struct fb_videoinfo_s *vinfo) +{ + lcdinfo("vtable=%p vinfo=%p\n", vtable, vinfo); + if (vtable && vinfo) + { + memcpy(vinfo, &g_videoinfo, sizeof(struct fb_videoinfo_s)); + return OK; + } + + lcderr("ERROR: Returning EINVAL\n"); + return -EINVAL; +} + +/**************************************************************************** + * Name: jz_getplaneinfo + ****************************************************************************/ + +static int jz_getplaneinfo(struct fb_vtable_s *vtable, int planeno, + struct fb_planeinfo_s *pinfo) +{ + lcdinfo("vtable=%p planeno=%d pinfo=%p\n", vtable, planeno, pinfo); + if (vtable && planeno == 0 && pinfo) + { + memcpy(pinfo, &g_planeinfo, sizeof(struct fb_planeinfo_s)); + return OK; + } + + lcderr("ERROR: Returning EINVAL\n"); + return -EINVAL; +} + +/**************************************************************************** + * Name: jz_getcmap + ****************************************************************************/ + +#ifdef CONFIG_FB_CMAP +static int jz_getcmap(struct fb_vtable_s *vtable, + struct fb_cmap_s *cmap) +{ + return -EINVAL; +} +#endif + +/**************************************************************************** + * Name: jz_putcmap + ****************************************************************************/ + +#ifdef CONFIG_FB_CMAP +static int jz_putcmap(struct fb_vtable_s *vtable, + const struct fb_cmap_s *cmap) +{ + return -EINVAL; +} +#endif + +/**************************************************************************** + * Name: jz_getcursor + ****************************************************************************/ + +#ifdef CONFIG_FB_HWCURSOR +static int jz_getcursor(struct fb_vtable_s *vtable, + struct fb_cursorattrib_s *attrib) +{ + return -EINVAL; +} +#endif + +/**************************************************************************** + * Name: jz_setcursor + ****************************************************************************/ + +#ifdef CONFIG_FB_HWCURSOR +static int jz_setcursor(struct fb_vtable_s *vtable, + struct fb_setcursor_s *settings) +{ + lcderr("ERROR: Returning EINVAL\n"); + return -EINVAL; +} +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: up_fbinitialize + * + * Description: + * Initialize the framebuffer video hardware associated with the display. + * + * Input Parameters: + * display - In the case of hardware with multiple displays, this + * specifies the display. Normally this is zero. + * + * Returned Value: + * Zero is returned on success; a negated errno value is returned on any + * failure. + * + ****************************************************************************/ + +int up_fbinitialize(int display) +{ + jzlcd_clocks(); + + dwc_hdmi_init(); + + jzlcd_attach(); + + return OK; +} + +/**************************************************************************** + * Name: up_fbgetvplane + * + * Description: + * Return a a reference to the framebuffer object for the specified video + * plane of the specified plane. Many OSDs support multiple planes of + * video. + * + * Input Parameters: + * display - In the case of hardware with multiple displays, this + * specifies the display. Normally this is zero. + * vplane - Identifies the plane being queried. + * + * Returned Value: + * A non-NULL pointer to the frame buffer access structure is returned on + * success; NULL is returned on any failure. + * + ****************************************************************************/ + +struct fb_vtable_s *up_fbgetvplane(int display, int vplane) +{ + lcdinfo("vplane: %d\n", vplane); + if (vplane == 0) + { + return &g_fbobject; + } + else + { + return NULL; + } +} + +/**************************************************************************** + * Name: up_fbuninitialize + * + * Description: + * Uninitialize the framebuffer support for the specified display. + * + * Input Parameters: + * display - In the case of hardware with multiple displays, this + * specifies the display. Normally this is zero. + * + * Returned Value: + * None + * + ****************************************************************************/ + +void up_fbuninitialize(int display) +{ +} + +#endif /* CONFIG_ALLOW_BSD_COMPONENTS */ diff --git a/arch/mips/src/jz4780/jz4780_lowinit.c b/arch/mips/src/jz4780/jz4780_lowinit.c index 0e066e05f7b3c..f48444d1dbcbc 100644 --- a/arch/mips/src/jz4780/jz4780_lowinit.c +++ b/arch/mips/src/jz4780/jz4780_lowinit.c @@ -37,6 +37,7 @@ #include "mips_internal.h" #include "jz4780_lowinit.h" +#include "jz4780_gpio.h" #include "jz4780_cache.h" /**************************************************************************** @@ -63,6 +64,36 @@ * Private Functions ****************************************************************************/ +static void usb_preinit(void) +{ + uint32_t reg; + jz4780_configgpio(POWER_LED); + + jz4780_gpiowrite(POWER_LED, true); + + reg = getreg32(USBPCR1_REG); + reg &= REFCLK_DIV_MSK; + reg |= REFCLK_DIV_48MHZ; + reg |= WORD_IF_16BIT; + putreg32(reg, USBPCR1_REG); + + putreg32(OPCR_SPENDN1 | getreg32(OPCR_REG), OPCR_REG); + + /* PHY power on reset. */ + + reg = getreg32(USBPCR_REG); + putreg32(reg | USBPCR_POR, USBPCR_REG); + up_mdelay(1); + putreg32(reg & ~USBPCR_POR, USBPCR_REG); + + /* UHC soft reset */ + + reg = getreg32(SRBC_REG); + putreg32(reg | SRBC_UHC_SR, SRBC_REG); + up_mdelay(30); + putreg32(reg & ~SRBC_UHC_SR, SRBC_REG); +} + /**************************************************************************** * Name: jz4780_pbclk * @@ -109,6 +140,11 @@ void jz4780_lowinit(void) #ifdef USE_EARLYSERIALINIT u16550_earlyserialinit(); #endif + uint32_t val = getreg32(CLKGR0_REG); + val &= ~CLKGR0_UHC; + val &= ~CLKGR0_OTG0; + putreg32(val, CLKGR0_REG); + usb_preinit(); /* Perform board-level initialization */ diff --git a/arch/mips/src/jz4780/jz4780_ohci.c b/arch/mips/src/jz4780/jz4780_ohci.c new file mode 100644 index 0000000000000..83804f43a2eb3 --- /dev/null +++ b/arch/mips/src/jz4780/jz4780_ohci.c @@ -0,0 +1,4177 @@ +/**************************************************************************** + * arch/mips/src/jz4780/jz4780_ohci.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include /* May redefine PIO settings */ + +#include "mips_internal.h" +#include "chip.h" +#include "jz_usbhost.h" +#include "jz4780_ohci.h" + +#ifdef CONFIG_JZ4780_OHCI + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Configuration ************************************************************/ + +/* Pre-requisites */ + +#ifndef CONFIG_SCHED_WORKQUEUE +# error Work queue support is required (CONFIG_SCHED_WORKQUEUE) +#endif + +/* Configurable number of user endpoint descriptors (EDs). This number + * excludes the control endpoint that is always allocated. + */ + +#ifndef CONFIG_JZ4780_OHCI_NEDS +# define CONFIG_JZ4780_OHCI_NEDS 2 +#endif + +/* Configurable number of user transfer descriptors (TDs). */ + +#ifndef CONFIG_JZ4780_OHCI_NTDS +# define CONFIG_JZ4780_OHCI_NTDS 3 +#endif + +#if CONFIG_JZ4780_OHCI_NTDS < 2 +# error Insufficient number of transfer descriptors (CONFIG_JZ4780_OHCI_NTDS < 2) +#endif + +/* Minimum alignment for DMA access is 16 bytes, but it is safer to align to + * the cache line size. + */ + +#define JZ4780_DMA_ALIGN JZ4780_DCACHE_LINESIZE + +/* Configurable number of request/descriptor buffers (TDBUFFER) */ + +#ifndef CONFIG_JZ4780_OHCI_TDBUFFERS +# define CONFIG_JZ4780_OHCI_TDBUFFERS 2 +#endif + +#if CONFIG_JZ4780_OHCI_TDBUFFERS < 2 +# error At least two TD buffers are required (CONFIG_JZ4780_OHCI_TDBUFFERS < 2) +#endif + +/* Configurable size of one TD buffer */ + +#if CONFIG_JZ4780_OHCI_TDBUFFERS > 0 && !defined(CONFIG_JZ4780_OHCI_TDBUFSIZE) +# define CONFIG_JZ4780_OHCI_TDBUFSIZE 128 +#endif + +#if (CONFIG_JZ4780_OHCI_TDBUFSIZE & 3) != 0 +# error "TD buffer size must be an even number of 32-bit words" +#endif + +/* Total buffer size */ + +#define JZ_BUFALLOC (CONFIG_JZ4780_OHCI_TDBUFFERS * CONFIG_JZ4780_OHCI_TDBUFSIZE) + +/* Debug */ + +#ifndef CONFIG_DEBUG_USB_INFO +# undef CONFIG_JZ4780_OHCI_REGDEBUG +#endif + +/* OHCI Setup ***************************************************************/ + +/* Frame Interval / Periodic Start. + * + * At 12Mbps, there are 12000 bit time in each 1Msec frame. + */ + +#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 JZ_CLKCTRL_ENABLES (USBOTG_CLK_HOSTCLK|USBOTG_CLK_PORTSELCLK|USBOTG_CLK_AHBCLK) + +/* Interrupt enable bits */ + +#ifdef CONFIG_DEBUG_USB +# define JZ_DEBUG_INTS (OHCI_INT_SO|OHCI_INT_RD|OHCI_INT_UE|OHCI_INT_OC) +#else +# define JZ_DEBUG_INTS 0 +#endif + +#define JZ_NORMAL_INTS (OHCI_INT_WDH|OHCI_INT_RHSC) +#define JZ_ALL_INTS (JZ_NORMAL_INTS|JZ_DEBUG_INTS) + +/* Periodic Intervals *******************************************************/ + +/* Periodic intervals 2, 4, 8, 16,and 32 supported */ + +#define MIN_PERINTERVAL 2 +#define MAX_PERINTERVAL 32 + +/* Descriptors **************************************************************/ + +/* Actual number of allocated EDs and TDs will include one for the control ED + * and one for the tail ED for each RHPort: + */ + +#define JZ4780_OHCI_NEDS (CONFIG_JZ4780_OHCI_NEDS + JZ_OHCI_NRHPORT) +#define JZ4780_OHCI_NTDS (CONFIG_JZ4780_OHCI_NTDS + JZ_OHCI_NRHPORT) + +/* TD delay interrupt value */ + +#define TD_DELAY(n) (uint32_t)((n) << GTD_STATUS_DI_SHIFT) + +/* Port numbers */ + +#define HPNDX(hp) ((hp)->port) +#define HPORT(hp) (HPNDX(hp)+1) +#define RHPNDX(rh) ((rh)->hport.hport.port) +#define RHPORT(rh) (RHPNDX(rh)+1) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* This structure contains one endpoint list. The main reason for the + * existence of this structure is to contain the sem_t value associated with + * the ED. It doesn't work well within the ED itself because then the + * semaphore counter is subject to DMA cache operations (invalidating a + * modified semaphore count is fatal!). + */ + +struct jz_eplist_s +{ + volatile bool wdhwait; /* TRUE: Thread is waiting for WDH interrupt */ + sem_t wdhsem; /* Semaphore used to wait for Writeback Done Head event */ +#ifdef CONFIG_USBHOST_ASYNCH + usbhost_asynch_t callback; /* Transfer complete callback */ + void *arg; /* Argument that accompanies the callback */ +#endif + uint8_t *buffer; /* Buffer being transferred */ + uint16_t buflen; /* Length of the buffer */ + uint16_t xfrd; /* Number of bytes completed in the last transfer */ + struct jz_ed_s *ed; /* Endpoint descriptor (ED) */ + struct jz_gtd_s *tail; /* Tail transfer descriptor (TD) */ +}; + +/* This structure retains the state of one root hub port */ + +struct jz_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 jz_rhport_s. + */ + + struct usbhost_driver_s drvr; + + /* Root hub port status */ + + volatile bool connected; /* Connected to device */ + uint8_t rhpndx; /* Root hub port index */ + bool ep0init; /* True: EP0 initialized */ + + struct jz_eplist_s ep0; /* EP0 endpoint list */ + + /* This is the hub port description understood by class drivers */ + + struct usbhost_roothubport_s hport; +}; + +/* This structure retains the overall state of the USB host controller */ + +struct jz_ohci_s +{ + volatile bool pscwait; /* TRUE: Thread is waiting for Root Hub Status change */ + +#ifndef CONFIG_USBHOST_INT_DISABLE + uint8_t ininterval; /* Minimum periodic IN EP polling interval: 2, 4, 6, 16, or 32 */ + uint8_t outinterval; /* Minimum periodic OUT EP polling interval: 2, 4, 6, 16, or 32 */ +#endif + + rmutex_t lock; /* Support mutually exclusive access */ + sem_t pscsem; /* Semaphore to wait for Root Hub Status Change event */ + struct work_s work; /* Supports interrupt bottom half */ + +#ifdef CONFIG_USBHOST_HUB + /* Used to pass external hub port events */ + + volatile struct usbhost_hubport_s *hport; +#endif + + struct usbhost_devaddr_s devgen; /* Address generation data */ + + /* Root hub ports */ + + struct jz_rhport_s rhport[JZ_OHCI_NRHPORT]; +}; + +/* The OHCI expects the size of an endpoint descriptor to be 16 bytes. + * However, the size allocated for an endpoint descriptor is 32 bytes. This + * is necessary first because the Cortex-A5 cache line size is 32 bytes and + * this is the smallest amount of memory that we can perform cache + * operations on. The 16-bytes is also used by the OHCI host driver in + * order to maintain additional endpoint-specific data. + */ + +struct jz_ed_s +{ + /* Hardware specific fields */ + + struct ohci_ed_s hw; /* 0-15 */ + + /* Software specific fields */ + + struct jz_eplist_s *eplist; /* 16-19: List structure associated with the ED */ + uint8_t xfrtype; /* 20: Transfer type. See SB_EP_ATTR_XFER_* in usb.h */ + uint8_t interval; /* 21: Periodic EP polling interval: 2, 4, 6, 16, or 32 */ + volatile uint8_t tdstatus; /* 22: TD control status bits from last Writeback Done Head event */ + uint8_t pad[9]; /* 23-31: Pad to 32-bytes */ +}; + +#define SIZEOF_JZ_ED_S 32 + +/* The OHCI expects the size of an transfer descriptor to be 16 bytes. + * However, the size allocated for an endpoint descriptor is 32 bytes in + * RAM. This extra 16-bytes is used by the OHCI host driver in order to + * maintain additional endpoint-specific data. + */ + +struct jz_gtd_s +{ + /* Hardware specific fields */ + + struct ohci_gtd_s hw; /* 0-15 */ + + /* Software specific fields */ + + struct jz_ed_s *ed; /* 16-19: Pointer to parent ED */ + uint8_t pad[12]; /* 20-31: Pad to 32 bytes */ +}; + +#define SIZEOF_JZ_TD_S 32 + +/* The following is used to manage lists of free EDs, TDs, and TD buffers */ + +struct jz_list_s +{ + struct jz_list_s *flink; /* Link to next buffer in the list */ + /* Variable length buffer data follows */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Register operations ******************************************************/ + +#ifdef CONFIG_JZ4780_OHCI_REGDEBUG +static void jz_printreg(uint32_t addr, uint32_t val, bool iswrite); +static void jz_checkreg(uint32_t addr, uint32_t val, bool iswrite); +static uint32_t jz_getreg(uint32_t addr); +static void jz_putreg(uint32_t val, uint32_t addr); +#else +# define jz_getreg(addr) getreg32(addr) +# define jz_putreg(val,addr) putreg32(val,addr) +#endif + +/* Byte stream access helper functions **************************************/ + +static inline uint16_t jz_getle16(const uint8_t *val); +#if 0 /* Not used */ +static void jz_putle16(uint8_t *dest, uint16_t val); +#endif + +/* OHCI memory pool helper functions ****************************************/ + +static struct jz_ed_s *jz_edalloc(void); +static void jz_edfree(struct jz_ed_s *ed); +static struct jz_gtd_s *jz_tdalloc(void); +static void jz_tdfree(struct jz_gtd_s *buffer); +static uint8_t *jz_tballoc(void); +static void jz_tbfree(uint8_t *buffer); + +/* ED list helper functions *************************************************/ + +static int jz_addctrled(struct jz_ed_s *ed); +static inline int jz_remctrled(struct jz_ed_s *ed); + +static inline int jz_addbulked(struct jz_ed_s *ed); +static inline int jz_rembulked(struct jz_ed_s *ed); + +#if !defined(CONFIG_USBHOST_INT_DISABLE) || !defined(CONFIG_USBHOST_ISOC_DISABLE) +static unsigned int jz_getinterval(uint8_t interval); +static void jz_setinttab(uint32_t value, unsigned int interval, + unsigned int offset); +#endif + +static inline int jz_addinted(const struct usbhost_epdesc_s *epdesc, + struct jz_ed_s *ed); +static inline int jz_reminted(struct jz_ed_s *ed); + +static inline int jz_addisoced(const struct usbhost_epdesc_s *epdesc, + struct jz_ed_s *ed); +static inline int jz_remisoced(struct jz_ed_s *ed); + +/* Descriptor helper functions **********************************************/ + +static int jz_enqueuetd(struct jz_rhport_s *rhport, + struct jz_eplist_s *eplist, struct jz_ed_s *ed, + uint32_t dirpid, uint32_t toggle, + volatile uint8_t *buffer, size_t buflen); +static int jz_ep0enqueue(struct jz_rhport_s *rhport); +static void jz_ep0dequeue(struct jz_eplist_s *ep0); +static int jz_wdhwait(struct jz_rhport_s *rhport, struct jz_ed_s *ed, + uint8_t *buffer, uint16_t buflen); +#ifdef CONFIG_USBHOST_ASYNCH +static int jz_wdhasynch(struct jz_rhport_s *rhport, struct jz_ed_s *ed, + usbhost_asynch_t callback, void *arg, + uint8_t *buffer, uint16_t buflen); +#endif +static int jz_ctrltd(struct jz_rhport_s *rhport, struct jz_eplist_s *ep0, + uint32_t dirpid, uint8_t *buffer, size_t buflen); + +/* Interrupt handling *******************************************************/ + +static void jz_rhsc_bottomhalf(void); +static void jz_wdh_bottomhalf(void); +static void jz_ohci_bottomhalf(void *arg); +static int jz_ohci_tophalf(int irq, void *context, void *arg); + +/* USB host controller operations *******************************************/ + +static int jz_wait(struct usbhost_connection_s *conn, + struct usbhost_hubport_s **hport); +static int jz_rh_enumerate(struct usbhost_connection_s *conn, + struct usbhost_hubport_s *hport); +static int jz_enumerate(struct usbhost_connection_s *conn, + struct usbhost_hubport_s *hport); + +static int jz_ep0configure(struct usbhost_driver_s *drvr, + usbhost_ep_t ep0, uint8_t funcaddr, + uint8_t speed, uint16_t maxpacketsize); +static int jz_epalloc(struct usbhost_driver_s *drvr, + const struct usbhost_epdesc_s *epdesc, + usbhost_ep_t *ep); +static int jz_epfree(struct usbhost_driver_s *drvr, usbhost_ep_t ep); +static int jz_alloc(struct usbhost_driver_s *drvr, + uint8_t **buffer, size_t *maxlen); +static int jz_free(struct usbhost_driver_s *drvr, uint8_t *buffer); +static int jz_ioalloc(struct usbhost_driver_s *drvr, + uint8_t **buffer, size_t buflen); +static int jz_iofree(struct usbhost_driver_s *drvr, uint8_t *buffer); +static int jz_ctrlin(struct usbhost_driver_s *drvr, usbhost_ep_t ep0, + const struct usb_ctrlreq_s *req, + uint8_t *buffer); +static int jz_ctrlout(struct usbhost_driver_s *drvr, usbhost_ep_t ep0, + const struct usb_ctrlreq_s *req, + const uint8_t *buffer); +static int jz_transfer_common(struct jz_rhport_s *rhport, + struct jz_eplist_s *eplist, + uint8_t *buffer, size_t buflen); +static ssize_t jz_transfer(struct usbhost_driver_s *drvr, usbhost_ep_t ep, + uint8_t *buffer, size_t buflen); +#ifdef CONFIG_USBHOST_ASYNCH +static void jz_asynch_completion(struct jz_eplist_s *eplist); +static int jz_asynch(struct usbhost_driver_s *drvr, usbhost_ep_t ep, + uint8_t *buffer, size_t buflen, + usbhost_asynch_t callback, void *arg); +#endif +static int jz_cancel(struct usbhost_driver_s *drvr, usbhost_ep_t ep); +#ifdef CONFIG_USBHOST_HUB +static int jz_connect(struct usbhost_driver_s *drvr, + struct usbhost_hubport_s *hport, + bool connected); +#endif +static void jz_disconnect(struct usbhost_driver_s *drvr, + struct usbhost_hubport_s *hport); + +/**************************************************************************** + * 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 jz_ohci_s g_ohci = +{ + .lock = NXRMUTEX_INITIALIZER, + .pscsem = SEM_INITIALIZER(0), +}; + +/* This is the connection/enumeration interface */ + +static struct usbhost_connection_s g_ohciconn = +{ + .wait = jz_wait, + .enumerate = jz_enumerate, +}; + +/* This is a free list of EDs and TD buffers */ + +static struct jz_list_s *g_edfree; /* List of unused EDs */ +static struct jz_list_s *g_tdfree; /* List of unused TDs */ +static struct jz_list_s *g_tbfree; /* List of unused transfer buffers */ + +/* Allocated descriptor memory. These must all be properly aligned + * and must be positioned in a DMA-able memory region. + */ + +/* This must be aligned to a 256-byte boundary */ + +static struct ohci_hcca_s g_hcca + aligned_data(256); + +/* Pools of free descriptors and buffers. These will all be linked + * into the free lists declared above. These must be aligned to 8-byte + * boundaries (we do 16-byte alignment). + */ + +static struct jz_ed_s g_edalloc[JZ4780_OHCI_NEDS] + aligned_data(JZ4780_DMA_ALIGN); +static struct jz_gtd_s g_tdalloc[JZ4780_OHCI_NTDS] + aligned_data(JZ4780_DMA_ALIGN); +static uint8_t g_bufalloc[JZ_BUFALLOC] + aligned_data(JZ4780_DMA_ALIGN); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: jz_printreg + * + * Description: + * Print the contents of an JZ4780 OHCI register operation + * + ****************************************************************************/ + +#ifdef CONFIG_JZ4780_OHCI_REGDEBUG +static void jz_printreg(uint32_t addr, uint32_t val, bool iswrite) +{ + uinfo("%08x%s%08x\n", addr, iswrite ? "<-" : "->", val); +} +#endif + +/**************************************************************************** + * Name: jz_checkreg + * + * Description: + * Get the contents of an JZ4780 OHCI register + * + ****************************************************************************/ + +#ifdef CONFIG_JZ4780_OHCI_REGDEBUG +static void jz_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 */ + + jz_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 register access */ + + jz_printreg(addr, val, iswrite); + } +} +#endif + +/**************************************************************************** + * Name: jz_getreg + * + * Description: + * Get the contents of an JZ4780 register + * + ****************************************************************************/ + +#ifdef CONFIG_JZ4780_OHCI_REGDEBUG +static uint32_t jz_getreg(uint32_t addr) +{ + /* Read the value from the register */ + + uint32_t val = getreg32(addr); + + /* Check if we need to print this value */ + + jz_checkreg(addr, val, false); + return val; +} +#endif + +/**************************************************************************** + * Name: jz_putreg + * + * Description: + * Set the contents of an JZ4780 register to a value + * + ****************************************************************************/ + +#ifdef CONFIG_JZ4780_OHCI_REGDEBUG +static void jz_putreg(uint32_t val, uint32_t addr) +{ + /* Check if we need to print this value */ + + jz_checkreg(addr, val, true); + + /* Write the value */ + + putreg32(val, addr); +} +#endif + +/**************************************************************************** + * Name: jz_getle16 + * + * Description: + * Get a (possibly unaligned) 16-bit little endian value. + * + ****************************************************************************/ + +static inline uint16_t jz_getle16(const uint8_t *val) +{ + return (uint16_t)val[1] << 8 | (uint16_t)val[0]; +} + +/**************************************************************************** + * Name: jz_putle16 + * + * Description: + * Put a (possibly unaligned) 16-bit little endian value. + * + ****************************************************************************/ + +#if 0 /* Not used */ +static void jz_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: jz_edalloc + * + * Description: + * Allocate an endpoint descriptor by removing it from the free list + * + ****************************************************************************/ + +static struct jz_ed_s *jz_edalloc(void) +{ + struct jz_ed_s *ed; + + /* Remove the ED from the freelist */ + + ed = (struct jz_ed_s *)g_edfree; + if (ed) + { + g_edfree = ((struct jz_list_s *)ed)->flink; + } + + return ed; +} + +/**************************************************************************** + * Name: jz_edfree + * + * Description: + * Free an endpoint descriptor by returning to the free list + * + ****************************************************************************/ + +static inline void jz_edfree(struct jz_ed_s *ed) +{ + struct jz_list_s *entry = (struct jz_list_s *)ed; + + /* Put the ED back into the free list */ + + entry->flink = g_edfree; + g_edfree = entry; +} + +/**************************************************************************** + * Name: jz_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 jz_gtd_s *jz_tdalloc(void) +{ + struct jz_gtd_s *ret; + irqstate_t flags; + + /* Disable interrupts momentarily so that jz_tdfree is not called from the + * interrupt handler. + */ + + flags = enter_critical_section(); + ret = (struct jz_gtd_s *)g_tdfree; + if (ret) + { + g_tdfree = ((struct jz_list_s *)ret)->flink; + } + + leave_critical_section(flags); + return ret; +} + +/**************************************************************************** + * Name: jz_tdfree + * + * Description: + * Free a transfer descriptor by returning it to the free list + * + * Assumptions: + * - Only called from the WDH interrupt handler (and during + * initialization). + * - Interrupts are disabled in any case. + * + ****************************************************************************/ + +static void jz_tdfree(struct jz_gtd_s *td) +{ + struct jz_list_s *tdfree = (struct jz_list_s *)td; + DEBUGASSERT(td); + + tdfree->flink = g_tdfree; + g_tdfree = tdfree; +} + +/**************************************************************************** + * Name: jz_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 *jz_tballoc(void) +{ + uint8_t *ret = (uint8_t *)g_tbfree; + if (ret) + { + g_tbfree = ((struct jz_list_s *)ret)->flink; + } + + return ret; +} + +/**************************************************************************** + * Name: jz_tbfree + * + * Description: + * Return an request/descriptor transfer buffer to the free list + * + ****************************************************************************/ + +static void jz_tbfree(uint8_t *buffer) +{ + struct jz_list_s *tbfree = (struct jz_list_s *)buffer; + + if (tbfree) + { + tbfree->flink = g_tbfree; + g_tbfree = tbfree; + } +} + +/**************************************************************************** + * Name: jz_addctrled + * + * Description: + * Helper function to add an ED to the control list. + * + ****************************************************************************/ + +static int jz_addctrled(struct jz_ed_s *ed) +{ + irqstate_t flags; + uint32_t regval; + uintptr_t physed; + + /* Disable control list processing while we modify the list */ + + flags = enter_critical_section(); + regval = jz_getreg(JZ_USBHOST_CTRL); + regval &= ~OHCI_CTRL_CLE; + jz_putreg(regval, JZ_USBHOST_CTRL); + + /* Add the new control ED to the head of the control list */ + + ed->hw.nexted = jz_getreg(JZ_USBHOST_CTRLHEADED); + up_clean_dcache((uintptr_t)ed, (uintptr_t)ed + sizeof(struct ohci_ed_s)); + + physed = jz_physramaddr((uintptr_t)ed); + jz_putreg((uint32_t)physed, JZ_USBHOST_CTRLHEADED); + + /* Re-enable control list processing. */ + + jz_putreg(0, JZ_USBHOST_CTRLED); + + regval = jz_getreg(JZ_USBHOST_CTRL); + regval |= OHCI_CTRL_CLE; + jz_putreg(regval, JZ_USBHOST_CTRL); + + leave_critical_section(flags); + return OK; +} + +/**************************************************************************** + * Name: jz_remctrled + * + * Description: + * Helper function remove an ED from the control list. + * + ****************************************************************************/ + +static inline int jz_remctrled(struct jz_ed_s *ed) +{ + struct jz_ed_s *curr; + struct jz_ed_s *prev; + irqstate_t flags; + uintptr_t physed; + uint32_t regval; + + /* Disable control list processing while we modify the list */ + + flags = enter_critical_section(); + regval = jz_getreg(JZ_USBHOST_CTRL); + regval &= ~OHCI_CTRL_CLE; + jz_putreg(regval, JZ_USBHOST_CTRL); + + /* Find the ED in the control list. */ + + physed = jz_getreg(JZ_USBHOST_CTRLHEADED); + for (curr = (struct jz_ed_s *)jz_virtramaddr(physed), prev = NULL; + curr && curr != ed; + prev = curr, curr = (struct jz_ed_s *)curr->hw.nexted); + + /* Hmmm.. 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 */ + + jz_putreg(ed->hw.nexted, JZ_USBHOST_CTRLHEADED); + } + else + { + /* No.. set the forward link of the previous ED in the list + * skip over this ED. + */ + + prev->hw.nexted = ed->hw.nexted; + up_clean_dcache((uintptr_t)prev, + (uintptr_t)prev + sizeof(struct jz_ed_s)); + } + } + + /* Re-enable control list processing if the control list is still non-empty + * after removing the ED node. + */ + + jz_putreg(0, JZ_USBHOST_CTRLED); + if (jz_getreg(JZ_USBHOST_CTRLHEADED) != 0) + { + /* If the control list is now empty, then disable it */ + + regval = jz_getreg(JZ_USBHOST_CTRL); + regval |= OHCI_CTRL_CLE; + jz_putreg(regval, JZ_USBHOST_CTRL); + } + + leave_critical_section(flags); + return OK; +} + +/**************************************************************************** + * Name: jz_addbulked + * + * Description: + * Helper function to add an ED to the bulk list. + * + ****************************************************************************/ + +static inline int jz_addbulked(struct jz_ed_s *ed) +{ +#ifndef CONFIG_USBHOST_BULK_DISABLE + irqstate_t flags; + uint32_t regval; + uintptr_t physed; + + /* Disable bulk list processing while we modify the list */ + + flags = enter_critical_section(); + regval = jz_getreg(JZ_USBHOST_CTRL); + regval &= ~OHCI_CTRL_BLE; + jz_putreg(regval, JZ_USBHOST_CTRL); + + /* Add the new bulk ED to the head of the bulk list */ + + ed->hw.nexted = jz_getreg(JZ_USBHOST_BULKHEADED); + up_clean_dcache((uintptr_t)ed, (uintptr_t)ed + sizeof(struct ohci_ed_s)); + + physed = jz_physramaddr((uintptr_t)ed); + jz_putreg((uint32_t)physed, JZ_USBHOST_BULKHEADED); + + /* Re-enable bulk list processing. */ + + jz_putreg(0, JZ_USBHOST_BULKED); + + regval = jz_getreg(JZ_USBHOST_CTRL); + regval |= OHCI_CTRL_BLE; + jz_putreg(regval, JZ_USBHOST_CTRL); + + leave_critical_section(flags); + return OK; +#else + return -ENOSYS; +#endif +} + +/**************************************************************************** + * Name: jz_rembulked + * + * Description: + * Helper function remove an ED from the bulk list. + * + ****************************************************************************/ + +static inline int jz_rembulked(struct jz_ed_s *ed) +{ +#ifndef CONFIG_USBHOST_BULK_DISABLE + struct jz_ed_s *curr; + struct jz_ed_s *prev; + irqstate_t flags; + uintptr_t physed; + uint32_t regval; + + /* Disable bulk list processing while we modify the list */ + + flags = enter_critical_section(); + regval = jz_getreg(JZ_USBHOST_CTRL); + regval &= ~OHCI_CTRL_BLE; + jz_putreg(regval, JZ_USBHOST_CTRL); + + /* Find the ED in the bulk list. */ + + physed = jz_getreg(JZ_USBHOST_BULKHEADED); + for (curr = (struct jz_ed_s *)jz_virtramaddr(physed), prev = NULL; + curr && curr != ed; + prev = curr, curr = (struct jz_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); + + /* 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 */ + + jz_putreg(ed->hw.nexted, JZ_USBHOST_BULKHEADED); + } + else + { + /* No.. set the forward link of the previous ED in the list + * skip over this ED. + */ + + prev->hw.nexted = ed->hw.nexted; + up_clean_dcache((uintptr_t)prev, + (uintptr_t)prev + sizeof(struct jz_ed_s)); + } + } + + /* Re-enable bulk list processing if the bulk list is still non-empty + * after removing the ED node. + */ + + jz_putreg(0, JZ_USBHOST_BULKED); + if (jz_getreg(JZ_USBHOST_BULKHEADED) != 0) + { + /* If the bulk list is now empty, then disable it */ + + regval = jz_getreg(JZ_USBHOST_CTRL); + regval |= OHCI_CTRL_BLE; + jz_putreg(regval, JZ_USBHOST_CTRL); + } + + leave_critical_section(flags); + return OK; +#else + return -ENOSYS; +#endif +} + +/**************************************************************************** + * Name: jz_getinterval + * + * Description: + * Convert the endpoint polling interval into a HCCA table increment + * + ****************************************************************************/ + +#if !defined(CONFIG_USBHOST_INT_DISABLE) || !defined(CONFIG_USBHOST_ISOC_DISABLE) +static unsigned int jz_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: jz_setinttab + * + * Description: + * Set the interrupt table to the selected value using the provided + * interval and offset. + * + ****************************************************************************/ + +#if !defined(CONFIG_USBHOST_INT_DISABLE) || !defined(CONFIG_USBHOST_ISOC_DISABLE) +static void jz_setinttab(uint32_t value, unsigned int interval, + unsigned int offset) +{ + uintptr_t inttbl; + unsigned int i; + + for (i = offset; i < HCCA_INTTBL_WSIZE; i += interval) + { + /* Modify the table value */ + + g_hcca.inttbl[i] = value; + } + + /* Make sure that the modified table value is flushed to RAM */ + + inttbl = (uintptr_t)g_hcca.inttbl; + up_clean_dcache(inttbl, inttbl + sizeof(uint32_t)*HCCA_INTTBL_WSIZE); +} +#endif + +/**************************************************************************** + * Name: jz_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 jz_addinted(const struct usbhost_epdesc_s *epdesc, + struct jz_ed_s *ed) +{ +#ifndef CONFIG_USBHOST_INT_DISABLE + irqstate_t flags; + unsigned int interval; + unsigned int offset; + uintptr_t physed; + uintptr_t physhead; + uint32_t regval; + + /* Disable periodic list processing. Does this take effect immediately? + * Or at the next SOF... need to check. + */ + + flags = enter_critical_section(); + regval = jz_getreg(JZ_USBHOST_CTRL); + regval &= ~OHCI_CTRL_PLE; + jz_putreg(regval, JZ_USBHOST_CTRL); + + /* Get the quanitized interval value associated with this ED and save it + * in the ED. + */ + + interval = jz_getinterval(epdesc->interval); + ed->interval = interval; + usbhost_vtrace2(OHCI_VTRACE2_INTERVAL, 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 (g_ohci.ininterval > interval) + { + g_ohci.ininterval = interval; + } + else + { + interval = g_ohci.ininterval; + } + } + else + { + offset = 1; + if (g_ohci.outinterval > interval) + { + g_ohci.outinterval = interval; + } + else + { + interval = g_ohci.outinterval; + } + } + + usbhost_vtrace2(OHCI_VTRACE2_MININTERVAL, interval, offset); + + /* Get the (physical) head of the first of the duplicated entries. The + * first offset entry is always guaranteed to contain the common ED list + * head. + */ + + physhead = g_hcca.inttbl[offset]; + + /* Clear all current entries in the interrupt table for this direction */ + + jz_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 = physhead; + up_clean_dcache((uintptr_t)ed, (uintptr_t)ed + sizeof(struct ohci_ed_s)); + + physed = jz_physramaddr((uintptr_t)ed); + jz_setinttab((uint32_t)physed, interval, offset); + + usbhost_vtrace1(OHCI_VTRACE1_PHYSED, physed); + + /* Re-enable periodic list processing */ + + regval = jz_getreg(JZ_USBHOST_CTRL); + regval |= OHCI_CTRL_PLE; + jz_putreg(regval, JZ_USBHOST_CTRL); + + leave_critical_section(flags); + return OK; +#else + return -ENOSYS; +#endif +} + +/**************************************************************************** + * Name: jz_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 jz_reminted(struct jz_ed_s *ed) +{ +#ifndef CONFIG_USBHOST_INT_DISABLE + struct jz_ed_s *head; + struct jz_ed_s *curr; + struct jz_ed_s *prev; + irqstate_t flags; + uintptr_t physhead; + 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. + */ + + flags = enter_critical_section(); + regval = jz_getreg(JZ_USBHOST_CTRL); + regval &= ~OHCI_CTRL_PLE; + jz_putreg(regval, JZ_USBHOST_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. + */ + + physhead = g_hcca.inttbl[offset]; + head = (struct jz_ed_s *)jz_virtramaddr((uintptr_t)physhead); + +#ifdef CONFIG_USBHOST_TRACE + usbhost_vtrace1(OHCI_VTRACE1_VIRTED, (uintptr_t)ed); +#else + uinfo("ed: %p head: %08" PRIxPTR " next: %08" PRIx32 " offset: %d\n", + ed, (uintptr_t)physhead, head ? head->hw.nexted : 0, offset); +#endif + + /* Find the ED to be removed in the ED list */ + + for (curr = head, prev = NULL; + curr && curr != ed; + prev = curr, curr = (struct jz_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 + */ + + jz_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 */ + + physhead = ed->hw.nexted; + head = (struct jz_ed_s *)jz_virtramaddr((uintptr_t)physhead); + } + else + { + /* No.. set the forward link of the previous ED in the list + * skip over this ED. + */ + + prev->hw.nexted = ed->hw.nexted; + up_clean_dcache((uintptr_t)prev, + (uintptr_t)prev + sizeof(struct ohci_ed_s)); + } + +#ifdef CONFIG_USBHOST_TRACE + usbhost_vtrace1(OHCI_VTRACE1_VIRTED, (uintptr_t)ed); +#else + uinfo("ed: %p head: %08" PRIxPTR " next: %08" PRIx32 "\n", + ed, physhead, head ? head->hw.nexted : 0); +#endif + + /* Calculate the new minimum interval for this list */ + + interval = MAX_PERINTERVAL; + for (curr = head; curr; curr = (struct jz_ed_s *)curr->hw.nexted) + { + if (curr->interval < interval) + { + interval = curr->interval; + } + } + + usbhost_vtrace2(OHCI_VTRACE2_MININTERVAL, interval, offset); + + /* Save the new minimum interval */ + + if ((ed->hw.ctrl & ED_CONTROL_D_MASK) == ED_CONTROL_D_IN) + { + g_ohci.ininterval = interval; + } + else + { + g_ohci.outinterval = interval; + } + + /* Set the head ED in all of the appropriate entries of the HCCA + * interrupt table (head might be NULL). + */ + + jz_setinttab((uint32_t)physhead, interval, offset); + } + + /* Re-enabled periodic list processing */ + + if (head != NULL) + { + regval = jz_getreg(JZ_USBHOST_CTRL); + regval |= OHCI_CTRL_PLE; + jz_putreg(regval, JZ_USBHOST_CTRL); + } + + leave_critical_section(flags); + return OK; +#else + return -ENOSYS; +#endif +} + +/**************************************************************************** + * Name: jz_addisoced + * + * Description: + * Helper functions to add an ED to the periodic table. + * + ****************************************************************************/ + +static inline int jz_addisoced(const struct usbhost_epdesc_s *epdesc, + struct jz_ed_s *ed) +{ +#ifndef CONFIG_USBHOST_ISOC_DISABLE +# warning "Isochronous endpoints not yet supported" +#endif + return -ENOSYS; +} + +/**************************************************************************** + * Name: jz_remisoced + * + * Description: + * Helper functions to remove an ED from the periodic table. + * + ****************************************************************************/ + +static inline int jz_remisoced(struct jz_ed_s *ed) +{ +#ifndef CONFIG_USBHOST_ISOC_DISABLE +# warning "Isochronous endpoints not yet supported" +#endif + return -ENOSYS; +} + +/**************************************************************************** + * Name: jz_enqueuetd + * + * Description: + * Enqueue a transfer descriptor. Notice that this function only supports + * queueing one TD per ED. + * + ****************************************************************************/ + +static int jz_enqueuetd(struct jz_rhport_s *rhport, + struct jz_eplist_s *eplist, struct jz_ed_s *ed, + uint32_t dirpid, uint32_t toggle, + volatile uint8_t *buffer, size_t buflen) +{ + struct jz_gtd_s *td; + struct jz_gtd_s *tdtail; + uintptr_t phytd; + uintptr_t phytail; + uintptr_t phybuf; + int ret = -ENOMEM; + + /* Allocate a TD from the free list */ + + td = jz_tdalloc(); + if (td != NULL) + { + /* Skip processing of this ED while we modify the TD list. */ + + ed->hw.ctrl |= ED_CONTROL_K; + up_clean_dcache((uintptr_t)ed, + (uintptr_t)ed + sizeof(struct ohci_ed_s)); + + /* Get the tail ED for this hub port */ + + tdtail = eplist->tail; + + /* Get physical addresses to support the DMA */ + + phytd = jz_physramaddr((uintptr_t)td); + phytail = jz_physramaddr((uintptr_t)tdtail); + phybuf = jz_physramaddr((uintptr_t)buffer); + + /* 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)phybuf; + tdtail->hw.cbp = 0; + td->hw.nexttd = (uint32_t)phytail; + tdtail->hw.nexttd = 0; + td->hw.be = (uint32_t)(phybuf + (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)phytd | ((ed->hw.headp) & ED_HEADP_C); + ed->hw.tailp = (uint32_t)phytail; + + /* Flush the buffer (if there is one), the new TD, and the modified ED + * to RAM. + */ + + if (buffer && buflen > 0) + { + up_clean_dcache((uintptr_t)buffer, + (uintptr_t)buffer + buflen); + } + + up_clean_dcache((uintptr_t)tdtail, + (uintptr_t)tdtail + sizeof(struct ohci_gtd_s)); + up_clean_dcache((uintptr_t)td, + (uintptr_t)td + sizeof(struct ohci_gtd_s)); + + /* Resume processing of this ED */ + + ed->hw.ctrl &= ~ED_CONTROL_K; + up_clean_dcache((uintptr_t)ed, + (uintptr_t)ed + sizeof(struct ohci_ed_s)); + ret = OK; + } + + return ret; +} + +/**************************************************************************** + * Name: jz_ep0enqueue + * + * Description: + * Initialize ED for a root hub EP0, add it to the control ED list, and + * enable control transfers. + * + * Input Parameters: + * rhpndx - Root hub port index. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static int jz_ep0enqueue(struct jz_rhport_s *rhport) +{ + struct jz_ed_s *edctrl; + struct jz_gtd_s *tdtail; + irqstate_t flags; + uintptr_t physaddr; + int ret; + + DEBUGASSERT(rhport && !rhport->ep0init && rhport->ep0.ed == NULL && + rhport->ep0.tail == NULL); + + /* Allocate a control ED and a tail TD */ + + flags = enter_critical_section(); + edctrl = jz_edalloc(); + if (!edctrl) + { + leave_critical_section(flags); + return -ENOMEM; + } + + tdtail = jz_tdalloc(); + if (!tdtail) + { + jz_edfree(edctrl); + leave_critical_section(flags); + return -ENOMEM; + } + + rhport->ep0.ed = edctrl; + rhport->ep0.tail = tdtail; + + /* Initialize the common tail TD for this port */ + + memset(tdtail, 0, sizeof(struct jz_gtd_s)); + tdtail->ed = edctrl; + + /* Initialize the control endpoint for this port. + * Set up some default values (like max packetsize = 8). + * NOTE that the SKIP bit is set until the first real TD is added. + */ + + memset(edctrl, 0, sizeof(struct jz_ed_s)); + jz_ep0configure(&rhport->drvr, &rhport->ep0, 0, + rhport->hport.hport.speed, 8); + + edctrl->hw.ctrl |= ED_CONTROL_K; + edctrl->eplist = &rhport->ep0; + edctrl->xfrtype = USB_EP_ATTR_XFER_CONTROL; + + /* Link the common tail TD to the ED's TD list */ + + physaddr = jz_physramaddr((uintptr_t)tdtail); + edctrl->hw.headp = (uint32_t)physaddr; + edctrl->hw.tailp = (uint32_t)physaddr; + + /* Flush the affected control ED and tail TD to RAM */ + + up_clean_dcache((uintptr_t)edctrl, + (uintptr_t)edctrl + sizeof(struct ohci_ed_s)); + up_clean_dcache((uintptr_t)tdtail, + (uintptr_t)tdtail + sizeof(struct ohci_gtd_s)); + + /* Add the ED to the control list */ + + ret = jz_addctrled(edctrl); + leave_critical_section(flags); + return ret; +} + +/**************************************************************************** + * Name: jz_ep0dequeue + * + * Description: + * Remove the ED for EP0 from the control ED list and possibly disable + * control list processing. + * + * Input Parameters: + * ep0 - The control endpoint to be released. May be the control endpoint + * for an attached hub. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void jz_ep0dequeue(struct jz_eplist_s *ep0) +{ + struct jz_ed_s *edctrl; + struct jz_ed_s *curred; + struct jz_ed_s *preved; + struct jz_gtd_s *tdtail; + struct jz_gtd_s *currtd; + irqstate_t flags; + uintptr_t physcurr; + uint32_t regval; + + DEBUGASSERT(ep0->ed != NULL && ep0->tail != NULL); + + /* ControlListEnable. This bit is cleared to disable the processing of the + * Control list. We should never modify the control list while CLE is set. + */ + + flags = enter_critical_section(); + regval = jz_getreg(JZ_USBHOST_CTRL); + regval &= ~OHCI_CTRL_CLE; + jz_putreg(regval, JZ_USBHOST_CTRL); + + /* Search the control list to find the entry to be removed (and its + * predecessor). + */ + + edctrl = ep0->ed; + physcurr = jz_getreg(JZ_USBHOST_CTRLHEADED); + + for (curred = (struct jz_ed_s *)jz_virtramaddr(physcurr), + preved = NULL; + curred && curred != edctrl; + preved = curred, + curred = (struct jz_ed_s *)jz_virtramaddr(physcurr)) + { + physcurr = curred->hw.nexted; + } + + DEBUGASSERT(curred); + + /* Remove the ED from the control list */ + + if (preved) + { + /* Unlink the ED from the previous ED in the list */ + + preved->hw.nexted = edctrl->hw.nexted; + + /* Flush the modified ED to RAM */ + + up_clean_dcache((uintptr_t)preved, + (uintptr_t)preved + sizeof(struct ohci_ed_s)); + } + else + { + /* Set the new control list head ED */ + + jz_putreg(edctrl->hw.nexted, JZ_USBHOST_CTRLHEADED); + + /* If the control list head is still non-NULL, then (re-)enable + * processing of the Control list. + */ + + if (edctrl->hw.nexted != 0) + { + regval = jz_getreg(JZ_USBHOST_CTRL); + regval |= OHCI_CTRL_CLE; + jz_putreg(regval, JZ_USBHOST_CTRL); + } + } + + leave_critical_section(flags); + + /* Release any TDs that may still be attached to the ED. */ + + tdtail = ep0->tail; + physcurr = edctrl->hw.headp; + + for (currtd = (struct jz_gtd_s *)jz_virtramaddr(physcurr); + currtd && currtd != tdtail; + currtd = (struct jz_gtd_s *)jz_virtramaddr(physcurr)) + { + physcurr = currtd->hw.nexttd; + jz_tdfree(currtd); + } + + /* Free the tail TD and the control ED allocated for this port */ + + jz_tdfree(tdtail); + jz_edfree(edctrl); + + ep0->ed = NULL; + ep0->tail = NULL; +} + +/**************************************************************************** + * Name: jz_wdhwait + * + * Description: + * Set the request for the Writeback Done Head event well BEFORE enabling + * the transfer (as soon as we are absolutely committed to perform the + * 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 jz_wdhwait(struct jz_rhport_s *rhport, struct jz_ed_s *ed, + uint8_t *buffer, uint16_t buflen) +{ + struct jz_eplist_s *eplist; + irqstate_t flags = enter_critical_section(); + int ret = -ENODEV; + + /* Is the device still connected? */ + + if (rhport->connected) + { + /* Yes.. Get the endpoint list associated with the ED */ + + eplist = ed->eplist; + DEBUGASSERT(eplist); + + /* Then set wdhwait to indicate that we expect to be informed when + * either (1) the device is disconnected, or (2) the transfer + * completed. + */ + + eplist->wdhwait = true; +#ifdef CONFIG_USBHOST_ASYNCH + eplist->callback = NULL; + eplist->arg = NULL; +#endif + eplist->buffer = buffer; + eplist->buflen = buflen; + ret = OK; + } + + leave_critical_section(flags); + return ret; +} + +/**************************************************************************** + * Name: jz_wdhasynch + * + * Description: + * Set the request for the Writeback Done Head callback well BEFORE + * enabling the transfer (as soon as we are absolutely committed to + * perform the 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! + * + ****************************************************************************/ + +#ifdef CONFIG_USBHOST_ASYNCH +static int jz_wdhasynch(struct jz_rhport_s *rhport, struct jz_ed_s *ed, + usbhost_asynch_t callback, void *arg, + uint8_t *buffer, uint16_t buflen) +{ + struct jz_eplist_s *eplist; + irqstate_t flags = enter_critical_section(); + int ret = -ENODEV; + + /* Is the device still connected? */ + + if (rhport->connected) + { + /* Yes.. Get the endpoint list associated with the ED */ + + eplist = ed->eplist; + DEBUGASSERT(eplist); + + /* Then set wdhwait to indicate that we expect to be informed when + * either (1) the device is disconnected, or (2) the transfer + * completed. + */ + + eplist->wdhwait = false; + eplist->callback = callback; + eplist->arg = arg; + eplist->buffer = buffer; + eplist->buflen = buflen; + ret = OK; + } + + leave_critical_section(flags); + return ret; +} +#endif + +/**************************************************************************** + * Name: jz_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 jz_ctrltd(struct jz_rhport_s *rhport, + struct jz_eplist_s *eplist, uint32_t dirpid, + uint8_t *buffer, size_t buflen) +{ + struct jz_ed_s *edctrl; + uint32_t toggle; + uint32_t regval; + int ret; + int ret2; + + /* Set the request for the Writeback Done Head event well BEFORE enabling + * the transfer. + */ + + edctrl = eplist->ed; + + ret = jz_wdhwait(rhport, edctrl, buffer, buflen); + if (ret != OK) + { + usbhost_trace1(OHCI_TRACE1_DEVDISCONN, RHPORT(rhport)); + return ret; + } + + /* 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 */ + + edctrl->tdstatus = TD_CC_NOERROR; + ret = jz_enqueuetd(rhport, eplist, edctrl, dirpid, toggle, buffer, + buflen); + if (ret == OK) + { + /* Set ControlListFilled. This bit is used to indicate whether there + * are TDs on the Control list. + */ + + regval = jz_getreg(JZ_USBHOST_CMDST); + regval |= OHCI_CMDST_CLF; + jz_putreg(regval, JZ_USBHOST_CMDST); + + /* Release the OHCI semaphore while we wait. Other threads need the + * opportunity to access the OHCI resources while we wait. + * + * REVISIT: Is this safe? NO. This is a bug and needs rethinking. + * We need to lock all of the port-resources (not OHCI common) until + * the transfer is complete. But we can't use the common OHCI lock + * or we will deadlock while waiting (because the working thread that + * wakes this thread up needs the lock). + */ +#warning REVISIT + nxrmutex_unlock(&g_ohci.lock); + + /* Wait for the Writeback Done Head interrupt. Loop to handle any + * false alarm semaphore counts. + */ + + while (eplist->wdhwait && ret >= 0) + { + ret = nxsem_wait_uninterruptible(&eplist->wdhsem); + } + + /* Re-acquire the OHCI semaphore. The caller expects to be holding + * this upon return. + */ + + ret2 = nxrmutex_lock(&g_ohci.lock); + if (ret2 < 0) + { + ret = ret2; + } + + if (ret < 0) + { + /* The thread was canceled */ + } + + /* Check the TD completion status bits */ + + else if (edctrl->tdstatus == TD_CC_NOERROR) + { + ret = OK; + } + else + { + usbhost_trace2(OHCI_TRACE2_BADTDSTATUS, RHPORT(rhport), + edctrl->tdstatus); + ret = edctrl->tdstatus == TD_CC_STALL ? -EPERM : -EIO; + } + } + + /* Make sure that there is no outstanding request on this endpoint */ + + eplist->wdhwait = false; + return ret; +} + +/**************************************************************************** + * Name: jz_rhsc_bottomhalf + * + * Description: + * OHCI root hub status change interrupt handler + * + ****************************************************************************/ + +static void jz_rhsc_bottomhalf(void) +{ + struct jz_rhport_s *rhport; + uint32_t regaddr; + uint32_t rhportst; + int rhpndx; + + /* Handle root hub status change on each root port */ + + for (rhpndx = 0; rhpndx < JZ_OHCI_NRHPORT; rhpndx++) + { + rhport = &g_ohci.rhport[rhpndx]; + + regaddr = JZ_USBHOST_RHPORTST(rhpndx + 1); + rhportst = jz_getreg(regaddr); + + usbhost_vtrace2(OHCI_VTRACE2_RHPORTST, rhpndx + 1, (uint16_t)rhportst); + + if ((rhportst & OHCI_RHPORTST_CSC) != 0) + { + uint32_t rhstatus = jz_getreg(JZ_USBHOST_RHSTATUS); + usbhost_vtrace1(OHCI_VTRACE1_CSC, rhstatus); + + /* If DRWE is set, Connect Status Change indicates a remote + * wake-up event + */ + + if (rhstatus & OHCI_RHSTATUS_DRWE) + { + usbhost_vtrace1(OHCI_VTRACE1_DRWE, rhstatus); + } + + /* Otherwise... Not a remote wake-up event */ + + else + { + /* Check current connect status */ + + if ((rhportst & OHCI_RHPORTST_CCS) != 0) + { + /* Connected ... Did we just become connected? */ + + if (!rhport->connected) + { + /* Yes.. connected. */ + + rhport->connected = true; + + usbhost_vtrace2(OHCI_VTRACE2_CONNECTED, + rhpndx + 1, g_ohci.pscwait); + + /* Notify any waiters */ + + if (g_ohci.pscwait) + { + nxsem_post(&g_ohci.pscsem); + g_ohci.pscwait = false; + } + } + else + { + usbhost_vtrace1(OHCI_VTRACE1_ALREADYCONN, rhportst); + } + + /* The LSDA (Low speed device attached) bit is valid + * when CCS == 1. + */ + + if ((rhportst & OHCI_RHPORTST_LSDA) != 0) + { + rhport->hport.hport.speed = USB_SPEED_LOW; + } + else + { + rhport->hport.hport.speed = USB_SPEED_FULL; + } + + usbhost_vtrace1(OHCI_VTRACE1_SPEED, + rhport->hport.hport.speed); + } + + /* Check if we are now disconnected */ + + else if (rhport->connected) + { + /* Yes.. disconnect the device */ + + usbhost_vtrace2(OHCI_VTRACE2_DISCONNECTED, + rhpndx + 1, g_ohci.pscwait); + + rhport->connected = false; + + /* 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. + */ + + rhport->hport.hport.speed = USB_SPEED_FULL; + + /* Are we bound to a class instance? */ + + if (rhport->hport.hport.devclass) + { + /* Yes.. Disconnect the class */ + + CLASS_DISCONNECTED(rhport->hport.hport.devclass); + rhport->hport.hport.devclass = NULL; + } + + /* Notify any waiters for the Root Hub Status change + * event. + */ + + if (g_ohci.pscwait) + { + nxsem_post(&g_ohci.pscsem); + g_ohci.pscwait = false; + } + } + else + { + usbhost_vtrace1(OHCI_VTRACE1_ALREADYDISCONN, rhportst); + } + } + + /* Clear the status change interrupt */ + + jz_putreg(OHCI_RHPORTST_CSC, regaddr); + } + + /* Check for port reset status change */ + + if ((rhportst & OHCI_RHPORTST_PRSC) != 0) + { + /* Release the RH port from reset */ + + jz_putreg(OHCI_RHPORTST_PRSC, regaddr); + } + } +} + +/**************************************************************************** + * Name: jz_wdh_bottomhalf + * + * Description: + * OHCI write done head interrupt handler + * + ****************************************************************************/ + +static void jz_wdh_bottomhalf(void) +{ + struct jz_eplist_s *eplist; + struct jz_gtd_s *td; + struct jz_gtd_s *next; + struct jz_ed_s *ed; + uintptr_t paddr; + uintptr_t tmp; + + /* The host controller just wrote the finished TDs into the HCCA done head. + * This may include multiple packets that were transferred in the preceding + * frame. + * + * Remove the TD from the Writeback Done Head in the HCCA and return + * it 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. + */ + + /* Invalidate D-cache to force re-reading of the Done Head */ + +#if 0 /* Apparently insufficient */ + up_invalidate_dcache((uintptr_t)&g_hcca.donehead, + (uintptr_t)&g_hcca.donehead + sizeof(uint32_t)); +#else + up_invalidate_dcache((uintptr_t)&g_hcca, + (uintptr_t)&g_hcca + sizeof(struct ohci_hcca_s)); +#endif + + /* Now read the done head. */ + + td = (struct jz_gtd_s *) + jz_virtramaddr(g_hcca.donehead & HCCA_DONEHEAD_MASK); + g_hcca.donehead = 0; + + /* Process each TD in the write done list */ + + for (; td; td = next) + { + /* Invalidate the just-finished TD from D-cache to force it to be + * reloaded from memory. + */ + + up_invalidate_dcache((uintptr_t)td, + (uintptr_t)td + sizeof(struct ohci_gtd_s)); + + /* Get the ED in which this TD was enqueued */ + + ed = td->ed; + DEBUGASSERT(ed != NULL); + + /* Get the endpoint list that contains the ED */ + + eplist = ed->eplist; + DEBUGASSERT(eplist != NULL); + + /* Do nothing if there is no transfer in progress. This situation may + * occur after cancellation of a transfer. + */ + +#ifdef CONFIG_USBHOST_ASYNCH + if (eplist->wdhwait || eplist->callback) +#else + if (eplist->wdhwait) +#endif + { + /* Invalidate the control ED so that it two will be re-read from + * memory. + */ + + up_invalidate_dcache((uintptr_t)ed, + (uintptr_t)ed + sizeof(struct ohci_ed_s)); + + /* Save the condition code from the (single) TD status/control + * word. + */ + + ed->tdstatus = (td->hw.ctrl & GTD_STATUS_CC_MASK) >> + GTD_STATUS_CC_SHIFT; + +#ifdef HAVE_USBHOST_TRACE + if (ed->tdstatus != TD_CC_NOERROR) + { + /* The transfer failed for some reason... dump some diagnostic + * info. + */ + + usbhost_trace2(OHCI_TRACE2_WHDTDSTATUS, ed->tdstatus, + ed->xfrtype); + } +#endif + + /* Determine the number of bytes actually transferred. + * A CBP value of zero means that all bytes were transferred. + */ + + tmp = (uintptr_t)td->hw.cbp; + if (tmp == 0) + { + /* All bytes have been transferred */ + + tmp = eplist->buflen; + } + else + { + paddr = jz_physramaddr((uintptr_t)eplist->buffer); + DEBUGASSERT(tmp >= paddr); + + /* Determine the size of the transfer by subtracting the + * current buffer pointer (CBP) from the initial buffer + * pointer (on packet receipt only). + */ + + tmp -= paddr; + DEBUGASSERT(tmp < UINT16_MAX); + } + + eplist->xfrd = (uint16_t)tmp; + } + + /* Return the TD to the free list */ + + next = (struct jz_gtd_s *)jz_virtramaddr(td->hw.nexttd); + jz_tdfree(td); + + /* And wake up the thread waiting for the WDH event */ + + if (eplist->wdhwait) + { + nxsem_post(&eplist->wdhsem); + eplist->wdhwait = false; + } + +#ifdef CONFIG_USBHOST_ASYNCH + /* No thread waiting. Is there a callback scheduled? */ + + else if (eplist->callback) + { + /* Yes.. perform the callback */ + + jz_asynch_completion(eplist); + } +#endif + } +} + +/**************************************************************************** + * Name: jz_ohci_bottomhalf + * + * Description: + * OHCI interrupt bottom half. This function runs on the high priority + * worker thread and was scheduled when the last interrupt occurred. The + * set of pending interrupts is provided as the argument. OHCI interrupts + * were disabled when this function is scheduled so no further interrupts + * can occur until this work re-enables OHCI interrupts + * + ****************************************************************************/ + +static void jz_ohci_bottomhalf(void *arg) +{ + uint32_t pending = (uint32_t)arg; + + /* We need to have exclusive access to the OHCI data structures. Waiting + * here is not a good thing to do on the worker thread, but there is no + * real option (other than to reschedule and delay). + */ + + nxrmutex_lock(&g_ohci.lock); + + /* Root hub status change interrupt */ + + if ((pending & OHCI_INT_RHSC) != 0) + { + /* Handle root hub status change on each root port */ + + usbhost_vtrace1(OHCI_VTRACE1_RHSC, pending); + jz_rhsc_bottomhalf(); + } + + /* Writeback Done Head interrupt */ + + if ((pending & OHCI_INT_WDH) != 0) + { + /* 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. + */ + + usbhost_vtrace1(OHCI_VTRACE1_WDHINTR, pending); + jz_wdh_bottomhalf(); + } + +#ifdef CONFIG_DEBUG_USB + if ((pending & JZ_DEBUG_INTS) != 0) + { + if ((pending & OHCI_INT_UE) != 0) + { + /* An unrecoverable error occurred. Unrecoverable errors + * are usually the consequence of bad descriptor contents + * or DMA errors. + * + * Treat this like a normal write done head interrupt. We + * just want to see if there is any status information written + * to the descriptors (and the normal write done head + * interrupt will not be occurring). + */ + + usbhost_trace1(OHCI_TRACE1_INTRUNRECOVERABLE, pending); + jz_wdh_bottomhalf(); + } + else + { + usbhost_trace1(OHCI_TRACE1_INTRUNHANDLED, pending); + } + } +#endif + + /* Now re-enable interrupts */ + + jz_putreg(OHCI_INT_MIE, JZ_USBHOST_INTEN); + nxrmutex_unlock(&g_ohci.lock); +} + +/**************************************************************************** + * Name: jz_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 jz_wait(struct usbhost_connection_s *conn, + struct usbhost_hubport_s **hport) +{ + irqstate_t flags; + int rhpndx; + int ret; + + /* Loop until a change in the connection state changes on one of the root + * hub ports or until an error occurs. + */ + + flags = enter_critical_section(); + for (; ; ) + { + /* Check for a change in the connection state on any root hub port */ + + for (rhpndx = 0; rhpndx < JZ_OHCI_NRHPORT; rhpndx++) + { + struct jz_rhport_s *rhport = &g_ohci.rhport[rhpndx]; + struct usbhost_hubport_s *connport; + + /* Has the connection state changed on the RH port? */ + + connport = &rhport->hport.hport; + if (rhport->connected != connport->connected) + { + /* Yes.. Return the RH port number to inform the caller which + * port has the connection change. + */ + + leave_critical_section(flags); + usbhost_vtrace2(OHCI_VTRACE2_WAKEUP, + rhpndx + 1, g_ohci.rhport[rhpndx].connected); + + connport->connected = rhport->connected; + *hport = connport; + return OK; + } + } + +#ifdef CONFIG_USBHOST_HUB + /* Is a device connected to an external hub? */ + + if (g_ohci.hport) + { + struct usbhost_hubport_s *connport; + + /* Yes.. return the external hub port */ + + connport = (struct usbhost_hubport_s *)g_ohci.hport; + g_ohci.hport = NULL; + + *hport = connport; + leave_critical_section(flags); + + usbhost_vtrace2(OHCI_VTRACE2_HUBWAKEUP, + HPORT(connport), connport->connected); + return OK; + } +#endif + + /* No changes on any port. Wait for a connection/disconnection event + * and check again + */ + + g_ohci.pscwait = true; + + ret = nxsem_wait_uninterruptible(&g_ohci.pscsem); + if (ret < 0) + { + return ret; + } + } +} + +/**************************************************************************** + * Name: jz_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 jz_rh_enumerate(struct usbhost_connection_s *conn, + struct usbhost_hubport_s *hport) +{ + struct jz_rhport_s *rhport; + uint32_t regaddr; + int rhpndx; + int ret; + + DEBUGASSERT(conn != NULL && hport != NULL); + rhpndx = hport->port; + + DEBUGASSERT(rhpndx >= 0 && rhpndx < JZ_OHCI_NRHPORT); + rhport = &g_ohci.rhport[rhpndx]; + + /* Are we connected to a device? The caller should have called the wait() + * method first to be assured that a device is connected. + */ + + while (!rhport->connected) + { + /* No, return an error */ + + usbhost_vtrace1(OHCI_VTRACE1_ENUMDISCONN, RHPORT(rhport)); + return -ENODEV; + } + + /* Add root hub EP0 to the control list */ + + if (!rhport->ep0init) + { + ret = jz_ep0enqueue(rhport); + if (ret < 0) + { + usbhost_trace2(OHCI_TRACE2_EP0ENQUEUE_FAILED, RHPORT(rhport), + -ret); + return ret; + } + + /* Successfully initialized */ + + rhport->ep0init = true; + } + + /* USB 2.0 spec says at least 50ms delay before port reset */ + + up_mdelay(100); + + /* Put the root hub port in reset (the JZ4780 supports three downstream + * ports) + */ + + regaddr = JZ_USBHOST_RHPORTST(rhpndx + 1); + jz_putreg(OHCI_RHPORTST_PRS, regaddr); + + /* Wait for the port reset to complete */ + + while ((jz_getreg(regaddr) & OHCI_RHPORTST_PRS) != 0); + + /* Release RH port 1 from reset and wait a bit */ + + jz_putreg(OHCI_RHPORTST_PRSC, regaddr); + up_mdelay(200); + return OK; +} + +static int jz_enumerate(struct usbhost_connection_s *conn, + 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_USBHOST_HUB + if (ROOTHUB(hport)) +#endif + { + ret = jz_rh_enumerate(conn, hport); + if (ret < 0) + { + return ret; + } + } + + /* Then let the common usbhost_enumerate do the real enumeration. */ + + usbhost_vtrace1(OHCI_VTRACE1_CLASSENUM, HPORT(hport)); + ret = usbhost_enumerate(hport, &hport->devclass); + if (ret < 0) + { + usbhost_trace2(OHCI_TRACE2_CLASSENUM_FAILED, HPORT(hport), -ret); + } + + return ret; +} + +/**************************************************************************** + * Name: jz_ep0configure + * + * Description: + * Configure endpoint 0. This method is normally used internally by the + * enumerate() method but is made available at the interface to support + * an external implementation of the enumeration logic. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * funcaddr - The USB address of the function containing the endpoint that + * EP0 controls. A funcaddr of zero will be received if no address is + * yet assigned to the device. + * speed - The speed of the port USB_SPEED_LOW, _FULL, or _HIGH + * maxpacketsize - The maximum number of bytes that can be sent to or + * received from the endpoint in a single data packet + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure. + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static int jz_ep0configure(struct usbhost_driver_s *drvr, usbhost_ep_t ep0, + uint8_t funcaddr, uint8_t speed, + uint16_t maxpacketsize) +{ + struct jz_rhport_s *rhport = (struct jz_rhport_s *)drvr; + struct jz_eplist_s *ep0list = (struct jz_eplist_s *)ep0; + struct jz_ed_s *edctrl; + uint32_t hwctrl; + int ret; + + usbhost_vtrace2(OHCI_VTRACE2_EP0CONFIG, speed, funcaddr); + DEBUGASSERT(rhport && maxpacketsize < 2048); + + /* Expect the device to be unplugged during enumeration */ + + if (!ep0list || !ep0list->ed) + { + _err("Device was probably removed\n"); + return -ENOMEM; + } + + edctrl = ep0list->ed; + + /* We must have exclusive access to EP0 and the control list */ + + ret = nxrmutex_lock(&g_ohci.lock); + if (ret < 0) + { + return ret; + } + + /* Set the EP0 ED control word (preserving only speed) */ + + 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; + } + + edctrl->hw.ctrl = hwctrl; + + /* Flush the modified control ED to RAM */ + + up_clean_dcache((uintptr_t)edctrl, + (uintptr_t)edctrl + sizeof(struct ohci_ed_s)); + nxrmutex_unlock(&g_ohci.lock); + + usbhost_vtrace2(OHCI_VTRACE2_EP0CTRLED, RHPORT(rhport), + (uint16_t)edctrl->hw.ctrl); + UNUSED(rhport); + return OK; +} + +/**************************************************************************** + * Name: jz_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 jz_epalloc(struct usbhost_driver_s *drvr, + const struct usbhost_epdesc_s *epdesc, + usbhost_ep_t *ep) +{ + struct jz_rhport_s *rhport = (struct jz_rhport_s *)drvr; + struct jz_eplist_s *eplist; + struct usbhost_hubport_s *hport; + struct jz_ed_s *ed; + struct jz_gtd_s *td; + uintptr_t physaddr; + 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 != NULL && epdesc != NULL && epdesc->hport != NULL); + DEBUGASSERT(ep != NULL && rhport->connected); + UNUSED(rhport); + + hport = epdesc->hport; + + /* Allocate a container for the endpoint data */ + + eplist = kmm_zalloc(sizeof(struct jz_eplist_s)); + if (!eplist) + { + usbhost_trace1(OHCI_TRACE1_EPLISTALLOC_FAILED, 0); + goto errout; + } + + /* Initialize the endpoint container */ + + nxsem_init(&eplist->wdhsem, 0, 0); + + /* We must have exclusive access to the ED pool, the bulk list, the + * periodic list, and the interrupt table. + */ + + ret = nxrmutex_lock(&g_ohci.lock); + if (ret < 0) + { + goto errout_with_eplist; + } + + /* Allocate an ED and a tail TD for the new endpoint */ + + ed = jz_edalloc(); + if (!ed) + { + usbhost_trace1(OHCI_TRACE1_EDALLOC_FAILED, 0); + goto errout_with_lock; + } + + td = jz_tdalloc(); + if (!td) + { + usbhost_trace1(OHCI_TRACE1_TDALLOC_FAILED, 0); + goto errout_with_ed; + } + + /* Save the descriptors in the endpoint container */ + + eplist->ed = ed; + eplist->tail = td; + + /* Configure the endpoint descriptor. */ + + memset((void *)ed, 0, sizeof(struct jz_ed_s)); + + 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 + + ed->eplist = eplist; + usbhost_vtrace2(OHCI_VTRACE2_EPALLOC, epdesc->addr, (uint16_t)ed->hw.ctrl); + + /* Configure the tail descriptor. */ + + memset(td, 0, sizeof(struct jz_gtd_s)); + td->ed = ed; + + /* Link the tail TD to the ED's TD list */ + + physaddr = (uintptr_t)jz_physramaddr((uintptr_t)td); + ed->hw.headp = physaddr; + ed->hw.tailp = physaddr; + + /* Make sure these settings are flushed to RAM */ + + up_clean_dcache((uintptr_t)ed, + (uintptr_t)ed + sizeof(struct ohci_ed_s)); + up_clean_dcache((uintptr_t)td, + (uintptr_t)td + sizeof(struct ohci_gtd_s)); + + /* Now add the endpoint descriptor to the appropriate list */ + + switch (ed->xfrtype) + { + case USB_EP_ATTR_XFER_CONTROL: + ret = jz_addctrled(ed); + break; + + case USB_EP_ATTR_XFER_BULK: + ret = jz_addbulked(ed); + break; + + case USB_EP_ATTR_XFER_INT: + ret = jz_addinted(epdesc, ed); + break; + + case USB_EP_ATTR_XFER_ISOC: + ret = jz_addisoced(epdesc, ed); + break; + + default: + ret = -EINVAL; + break; + } + + /* Was the ED successfully added? */ + + if (ret != OK) + { + /* No.. destroy it and report the error */ + + usbhost_trace2(OHCI_TRACE2_EDENQUEUE_FAILED, ed->xfrtype, -ret); + goto errout_with_td; + } + + /* Success.. return an opaque reference to the endpoint list container */ + + *ep = (usbhost_ep_t)eplist; + nxrmutex_unlock(&g_ohci.lock); + return OK; + +errout_with_td: + jz_tdfree(td); +errout_with_ed: + jz_edfree(ed); +errout_with_lock: + nxrmutex_unlock(&g_ohci.lock); +errout_with_eplist: + kmm_free(eplist); +errout: + return ret; +} + +/**************************************************************************** + * Name: jz_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 endpoint 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 jz_epfree(struct usbhost_driver_s *drvr, usbhost_ep_t ep) +{ + struct jz_rhport_s *rhport = (struct jz_rhport_s *)drvr; + struct jz_eplist_s *eplist = (struct jz_eplist_s *)ep; + struct jz_ed_s *ed; + int ret; + int ret2; + + DEBUGASSERT(rhport != NULL && eplist != NULL && + eplist->ed != NULL && eplist->tail != NULL); + + /* There should not be any pending, real TDs linked to this ED */ + + ed = eplist->ed; + DEBUGASSERT((ed->hw.headp & ED_HEADP_ADDR_MASK) == ed->hw.tailp); + + /* We must have exclusive access to the ED pool, the bulk list, the + * periodic list and the interrupt table. + */ + + ret2 = nxrmutex_lock(&g_ohci.lock); + + /* Remove the ED to the correct list depending on the transfer type */ + + switch (ed->xfrtype) + { + case USB_EP_ATTR_XFER_CONTROL: + ret = jz_remctrled(eplist->ed); + break; + + case USB_EP_ATTR_XFER_BULK: + ret = jz_rembulked(eplist->ed); + break; + + case USB_EP_ATTR_XFER_INT: + ret = jz_reminted(eplist->ed); + break; + + case USB_EP_ATTR_XFER_ISOC: + ret = jz_remisoced(eplist->ed); + break; + + default: + ret = -EINVAL; + break; + } + + /* Put the ED and tail TDs back into the free list */ + + jz_edfree(eplist->ed); + jz_tdfree(eplist->tail); + + /* And free the container */ + + nxsem_destroy(&eplist->wdhsem); + kmm_free(eplist); + nxrmutex_unlock(&g_ohci.lock); + return ret < 0 ? ret : ret2; +} + +/**************************************************************************** + * Name: jz_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 jz_alloc(struct usbhost_driver_s *drvr, + uint8_t **buffer, size_t *maxlen) +{ + int ret; + + DEBUGASSERT(drvr && buffer && maxlen); + + /* We must have exclusive access to the transfer buffer pool */ + + ret = nxrmutex_lock(&g_ohci.lock); + if (ret < 0) + { + return ret; + } + + ret = -ENOMEM; + + *buffer = jz_tballoc(); + if (*buffer) + { + *maxlen = CONFIG_JZ4780_OHCI_TDBUFSIZE; + ret = OK; + } + + nxrmutex_unlock(&g_ohci.lock); + return ret; +} + +/**************************************************************************** + * Name: jz_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 jz_free(struct usbhost_driver_s *drvr, uint8_t *buffer) +{ + int ret; + + DEBUGASSERT(drvr && buffer); + + /* We must have exclusive access to the transfer buffer pool */ + + ret = nxrmutex_lock(&g_ohci.lock); + jz_tbfree(buffer); + nxrmutex_unlock(&g_ohci.lock); + return ret; +} + +/**************************************************************************** + * Name: jz_ioalloc + * + * Description: + * Some hardware supports special memory in which larger IO buffers can + * be accessed more efficiently. This method provides a mechanism to + * allocate the request/descriptor memory. If the underlying hardware + * does not support such "special" memory, this functions may simply map + * to kumm_malloc. + * + * This interface differs from DRVR_ALLOC in that the buffers are + * variable-sized. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * buffer - The address of a memory location provided by the caller in + * which to return the allocated buffer memory address. + * buflen - The size of the buffer required. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure. + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static int jz_ioalloc(struct usbhost_driver_s *drvr, uint8_t **buffer, + size_t buflen) +{ + DEBUGASSERT(drvr && buffer); + + /* kumm_malloc() should return user accessible, DMA-able memory */ + + *buffer = kumm_malloc(buflen); + return *buffer ? OK : -ENOMEM; +} + +/**************************************************************************** + * Name: jz_iofree + * + * Description: + * Some hardware supports special memory in which IO data can be accessed + * more efficiently. This method provides a mechanism to free that IO + * buffer memory. If the underlying hardware does not support such + * "special" memory, this functions may simply map to kumm_free(). + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * buffer - The address of the allocated buffer memory to be freed. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure. + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static int jz_iofree(struct usbhost_driver_s *drvr, uint8_t *buffer) +{ + DEBUGASSERT(drvr && buffer); + + /* kumm_free is all that is required */ + + kumm_free(buffer); + return OK; +} + +/**************************************************************************** + * Name: jz_ctrlin and jz_ctrlout + * + * Description: + * Process a IN or OUT request on the control endpoint. These methods + * will enqueue the request and wait for it to complete. Only one + * transfer may be queued; Neither these methods nor the transfer() method + * can be called again until the control transfer functions returns. + * + * These are blocking methods; these functions will not return until the + * control transfer has completed. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * ep0 - The control endpoint to send/receive the control request. + * req - Describes the request to be sent. This request must lie in + * memory created by DRVR_ALLOC. + * buffer - A buffer used for sending the request and for returning any + * responses. This buffer must be large enough to hold the length value + * in the request description. buffer must have been allocated using + * DRVR_ALLOC. + * + * NOTE: On an IN transaction, req and buffer may refer to the same + * allocated memory. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure. + * + * Assumptions: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static int jz_ctrlin(struct usbhost_driver_s *drvr, usbhost_ep_t ep0, + const struct usb_ctrlreq_s *req, + uint8_t *buffer) +{ + struct jz_rhport_s *rhport = (struct jz_rhport_s *)drvr; + struct jz_eplist_s *eplist = (struct jz_eplist_s *)ep0; + uint16_t len; + int ret; + + DEBUGASSERT(rhport != NULL && eplist != NULL && req != NULL); + +#ifdef CONFIG_USBHOST_TRACE + usbhost_vtrace2(OHCI_VTRACE2_CTRLIN, RHPORT(rhport), req->req); +#else + uinfo("RHPort%d type: %02x req: %02x value: %02x%02x index: %02x%02x " + "len: %02x%02x\n", + RHPORT(rhport), req->type, req->req, req->value[1], + req->value[0], req->index[1], req->index[0], req->len[1], + req->len[0]); +#endif + + /* We must have exclusive access to EP0 and the control list */ + + ret = nxrmutex_lock(&g_ohci.lock); + if (ret < 0) + { + return ret; + } + + len = jz_getle16(req->len); + ret = jz_ctrltd(rhport, eplist, GTD_STATUS_DP_SETUP, (uint8_t *)req, + USB_SIZEOF_CTRLREQ); + if (ret == OK) + { + if (len) + { + ret = jz_ctrltd(rhport, eplist, GTD_STATUS_DP_IN, buffer, len); + } + + if (ret == OK) + { + ret = jz_ctrltd(rhport, eplist, GTD_STATUS_DP_OUT, NULL, 0); + } + } + + /* On an IN transaction, we need to invalidate the buffer contents to force + * it to be reloaded from RAM after the DMA. + */ + + nxrmutex_unlock(&g_ohci.lock); + up_invalidate_dcache((uintptr_t)buffer, (uintptr_t)buffer + len); + return ret; +} + +static int jz_ctrlout(struct usbhost_driver_s *drvr, usbhost_ep_t ep0, + const struct usb_ctrlreq_s *req, + const uint8_t *buffer) +{ + struct jz_rhport_s *rhport = (struct jz_rhport_s *)drvr; + struct jz_eplist_s *eplist = (struct jz_eplist_s *)ep0; + uint16_t len; + int ret; + + DEBUGASSERT(rhport != NULL && eplist != NULL && req != NULL); + +#ifdef CONFIG_USBHOST_TRACE + usbhost_vtrace2(OHCI_VTRACE2_CTRLOUT, RHPORT(rhport), req->req); +#else + uinfo("RHPort%d type: %02x req: %02x value: %02x%02x index: %02x%02x " + "len: %02x%02x\n", + RHPORT(rhport), req->type, req->req, req->value[1], + req->value[0], req->index[1], req->index[0], req->len[1], + req->len[0]); +#endif + + /* We must have exclusive access to EP0 and the control list */ + + ret = nxrmutex_lock(&g_ohci.lock); + if (ret < 0) + { + return ret; + } + + len = jz_getle16(req->len); + ret = jz_ctrltd(rhport, eplist, GTD_STATUS_DP_SETUP, (uint8_t *)req, + USB_SIZEOF_CTRLREQ); + if (ret == OK) + { + if (len) + { + ret = jz_ctrltd(rhport, eplist, GTD_STATUS_DP_OUT, + (uint8_t *)buffer, len); + } + + if (ret == OK) + { + ret = jz_ctrltd(rhport, eplist, GTD_STATUS_DP_IN, NULL, 0); + } + } + + nxrmutex_unlock(&g_ohci.lock); + return ret; +} + +/**************************************************************************** + * Name: jz_transfer_common + * + * Description: + * Initiate a request to handle a transfer descriptor. This method will + * enqueue the transfer request and return immediately + * + * Input Parameters: + * rhport - Internal driver root hub port state structure. + * eplist - The internal representation of 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 jz_transfer_common(struct jz_rhport_s *rhport, + struct jz_eplist_s *eplist, + uint8_t *buffer, size_t buflen) +{ + struct jz_ed_s *ed; + uint32_t dirpid; + uint32_t regval; + bool in; + int ret; + + ed = eplist->ed; + in = (ed->hw.ctrl & ED_CONTROL_D_MASK) == ED_CONTROL_D_IN; + +#ifdef CONFIG_USBHOST_TRACE + usbhost_vtrace2(OHCI_VTRACE2_TRANSFER, + (ed->hw.ctrl & ED_CONTROL_EN_MASK) >> ED_CONTROL_EN_SHIFT, + (uint16_t)buflen); +#else + uinfo("EP%" PRId32 " %s toggle: %d maxpacket: %" PRId32 " buflen: %zd\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, + buflen); +#endif + + /* Get the direction of the endpoint */ + + if (in) + { + dirpid = GTD_STATUS_DP_IN; + } + else + { + dirpid = GTD_STATUS_DP_OUT; + } + + /* Then enqueue the transfer */ + + ed->tdstatus = TD_CC_NOERROR; + ret = jz_enqueuetd(rhport, eplist, 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 = jz_getreg(JZ_USBHOST_CMDST); + regval |= OHCI_CMDST_BLF; + jz_putreg(regval, JZ_USBHOST_CMDST); + } + } + + return ret; +} + +/**************************************************************************** + * Name: jz_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, 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: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +static ssize_t jz_transfer(struct usbhost_driver_s *drvr, usbhost_ep_t ep, + uint8_t *buffer, size_t buflen) +{ + struct jz_rhport_s *rhport = (struct jz_rhport_s *)drvr; + struct jz_eplist_s *eplist = (struct jz_eplist_s *)ep; + struct jz_ed_s *ed; + ssize_t nbytes; + bool in; + int ret; + int ret2; + + DEBUGASSERT(rhport && eplist && eplist->ed && eplist->tail && + buffer && buflen > 0); + + ed = eplist->ed; + in = (ed->hw.ctrl & ED_CONTROL_D_MASK) == ED_CONTROL_D_IN; + + /* 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. + */ + + ret = nxrmutex_lock(&g_ohci.lock); + if (ret < 0) + { + return (ssize_t)ret; + } + + /* Set the request for the Writeback Done Head event well BEFORE enabling + * the transfer. + */ + + ret = jz_wdhwait(rhport, ed, buffer, buflen); + if (ret != OK) + { + usbhost_trace1(OHCI_TRACE1_DEVDISCONN, RHPORT(rhport)); + goto errout; + } + + /* Set up the transfer */ + + ret = jz_transfer_common(rhport, eplist, buffer, buflen); + if (ret < 0) + { + uerr("ERROR: jz_transfer_common failed: %d\n", ret); + goto errout; + } + + /* Release the OHCI semaphore while we wait. Other threads need the + * opportunity to access the OHCI resources while we wait. + * + * REVISIT: Is this safe? NO. This is a bug and needs rethinking. + * We need to lock all of the port-resources (not OHCI common) until + * the transfer is complete. But we can't use the common OHCI lock + * or we will deadlock while waiting (because the working thread that + * wakes this thread up needs the lock). + */ + +#warning REVISIT + nxrmutex_unlock(&g_ohci.lock); + + /* Wait for the Writeback Done Head interrupt. Loop to handle any false + * alarm semaphore counts. + */ + + while (eplist->wdhwait && ret >= 0) + { + ret = nxsem_wait_uninterruptible(&eplist->wdhsem); + } + + /* Re-acquire the OHCI semaphore. The caller expects to be holding + * this upon return. + */ + + ret2 = nxrmutex_lock(&g_ohci.lock); + if (ret2 < 0) + { + ret = ret2; + } + + if (ret < 0) + { + return ret; + } + + /* Invalidate the D cache to force the ED to be reloaded from RAM */ + + up_invalidate_dcache((uintptr_t)ed, + (uintptr_t)ed + sizeof(struct ohci_ed_s)); + + /* Check the TD completion status bits */ + + if (ed->tdstatus == TD_CC_NOERROR) + { + /* On an IN transaction, we also need to invalidate the buffer + * contents to force it to be reloaded from RAM. + */ + + if (in) + { + up_invalidate_dcache((uintptr_t)buffer, + (uintptr_t)buffer + buflen); + } + + nbytes = eplist->xfrd; + DEBUGASSERT(nbytes >= 0 && nbytes <= buflen); + + nxrmutex_unlock(&g_ohci.lock); + return nbytes; + } + + /* A transfer error occurred */ + + usbhost_trace2(OHCI_TRACE2_BADTDSTATUS, RHPORT(rhport), ed->tdstatus); + switch (ed->tdstatus) + { + case TD_CC_STALL: + ret = -EPERM; + break; + + case TD_CC_USER: + ret = -ESHUTDOWN; + break; + + default: + ret = -EIO; + break; + } + +errout: + + /* Make sure that there is no outstanding request on this endpoint */ + + eplist->wdhwait = false; + nxrmutex_unlock(&g_ohci.lock); + return (ssize_t)ret; +} + +/**************************************************************************** + * Name: jz_asynch_completion + * + * Description: + * This function is called at the interrupt level when an asynchronous + * transfer completes. It performs the pending callback. + * + * Input Parameters: + * rhport - Internal driver root hub port state structure. + * eplist - The internal representation of the device endpoint on which + * to perform the transfer. + * + * Returned Value: + * None + * + * Assumptions: + * - Called from the interrupt level + * + ****************************************************************************/ + +#ifdef CONFIG_USBHOST_ASYNCH +static void jz_asynch_completion(struct jz_eplist_s *eplist) +{ + struct jz_ed_s *ed; + usbhost_asynch_t callback; + void *arg; + ssize_t nbytes; + + DEBUGASSERT(eplist->ed && eplist->tail && eplist->callback != NULL && + eplist->buffer != NULL && eplist->buflen > 0); + ed = eplist->ed; + + /* Invalidate the D cache to force the ED to be reloaded from RAM */ + + up_invalidate_dcache((uintptr_t)ed, + (uintptr_t)ed + sizeof(struct ohci_ed_s)); + + /* Check the TD completion status bits */ + + if (ed->tdstatus == TD_CC_NOERROR) + { + /* On an IN transaction, we also need to invalidate the buffer + * contents to force it to be reloaded from RAM. + */ + + if ((ed->hw.ctrl & ED_CONTROL_D_MASK) == ED_CONTROL_D_IN) + { + uintptr_t buffaddr = (uintptr_t)eplist->buffer; + up_invalidate_dcache(buffaddr, buffaddr + eplist->buflen); + } + + nbytes = eplist->xfrd; + DEBUGASSERT(nbytes >= 0 && nbytes <= eplist->buflen); + } + else + { + /* Map the bad completion status to something that a class driver + * might understand. + */ + + usbhost_trace1(OHCI_TRACE1_BADTDSTATUS, ed->tdstatus); + + switch (ed->tdstatus) + { + case TD_CC_STALL: + nbytes = -EPERM; + break; + + case TD_CC_USER: + nbytes = -ESHUTDOWN; + break; + + default: + nbytes = -EIO; + break; + } + } + + /* Extract the callback information before freeing the buffer */ + + callback = eplist->callback; + arg = eplist->arg; + + /* Clear any pending transfer indicators */ + + eplist->wdhwait = false; + eplist->callback = NULL; + eplist->arg = NULL; + eplist->buffer = NULL; + eplist->buflen = 0; + + /* Then perform the callback */ + + callback(arg, nbytes); +} +#endif + +/**************************************************************************** + * Name: jz_asynch + * + * Description: + * Process a request to handle a transfer descriptor. This method will + * enqueue the transfer request and return immediately. When the transfer + * completes, the callback will be invoked with the provided transfer. + * This method is useful for receiving interrupt transfers which may come + * infrequently. + * + * Only one transfer may be queued; Neither this method nor the ctrlin or + * ctrlout methods can be called again until the transfer completes. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * ep - The IN or OUT endpoint descriptor for the device endpoint on + * which to perform the transfer. + * buffer - A buffer containing the data to be sent (OUT endpoint) or + * received (IN endpoint). buffer must have been allocated using + * DRVR_ALLOC + * buflen - The length of the data to be sent or received. + * callback - This function will be called when the transfer completes. + * arg - The arbitrary parameter that will be passed to the callback + * function when the transfer completes. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure. + * + * Assumptions: + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + ****************************************************************************/ + +#ifdef CONFIG_USBHOST_ASYNCH +static int jz_asynch(struct usbhost_driver_s *drvr, usbhost_ep_t ep, + uint8_t *buffer, size_t buflen, + usbhost_asynch_t callback, void *arg) +{ + struct jz_rhport_s *rhport = (struct jz_rhport_s *)drvr; + struct jz_eplist_s *eplist = (struct jz_eplist_s *)ep; + struct jz_ed_s *ed; + int ret; + + DEBUGASSERT(rhport && eplist && eplist->ed && eplist->tail && + buffer && buflen > 0 && buflen <= UINT16_MAX); + ed = eplist->ed; + + /* 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. + */ + + ret = nxrmutex_lock(&g_ohci.lock); + if (ret < 0) + { + return ret; + } + + /* Set the request for the Writeback Done Head callback well BEFORE + * enabling the transfer. + */ + + ret = jz_wdhasynch(rhport, ed, callback, arg, buffer, buflen); + if (ret != OK) + { + usbhost_trace1(OHCI_TRACE1_DEVDISCONN, RHPORT(rhport)); + goto errout; + } + + /* Set up the transfer */ + + ret = jz_transfer_common(rhport, eplist, buffer, buflen); + if (ret < 0) + { + uerr("ERROR: jz_transfer_common failed: %d\n", ret); + goto errout; + } + + /* Then just return. The callback will be performed asynchronously + * when the transfer completes. + */ + + nxrmutex_unlock(&g_ohci.lock); + return OK; + +errout: + + /* Make sure that there is no outstanding request on this endpoint */ + + eplist->callback = NULL; + eplist->arg = NULL; + nxrmutex_unlock(&g_ohci.lock); + return ret; +} +#endif /* CONFIG_USBHOST_ASYNCH */ + +/**************************************************************************** + * Name: jz_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 jz_cancel(struct usbhost_driver_s *drvr, usbhost_ep_t ep) +{ + struct jz_eplist_s *eplist = (struct jz_eplist_s *)ep; + struct jz_ed_s *ed; + struct jz_gtd_s *td; + struct jz_gtd_s *next; + irqstate_t flags; + uintptr_t paddr; + uint32_t ctrl; + + DEBUGASSERT(eplist && eplist->ed && eplist->tail); + ed = eplist->ed; + + /* These first steps must be atomic as possible */ + + flags = enter_critical_section(); + + /* It might be possible for no transfer to be in progress (callback == NULL + * and wdhwait == false) + */ + +#ifdef CONFIG_USBHOST_ASYNCH + if (eplist->callback || eplist->wdhwait) +#else + if (eplist->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 = jz_getreg(JZ_USBHOST_CTRL); + jz_putreg(ctrl & ~OHCI_CTRL_BLE, JZ_USBHOST_CTRL); + + /* Remove the TDs attached to the ED, keeping the ED in the list */ + + paddr = ed->hw.headp & ED_HEADP_ADDR_MASK; + td = (struct jz_gtd_s *)jz_virtramaddr(paddr); + + paddr = jz_physramaddr((uintptr_t)eplist->tail); + ed->hw.headp = paddr; + + up_clean_dcache((uintptr_t)ed, + (uintptr_t)ed + sizeof(struct ohci_ed_s)); + + /* Re-enable bulk list processing, if it was enabled before */ + + jz_putreg(0, JZ_USBHOST_BULKED); + jz_putreg(ctrl, JZ_USBHOST_CTRL); + } + else + { + /* Remove the TDs attached to the ED, keeping the Ed in the list */ + + paddr = ed->hw.headp & ED_HEADP_ADDR_MASK; + td = (struct jz_gtd_s *)jz_virtramaddr(paddr); + + paddr = jz_physramaddr((uintptr_t)eplist->tail); + ed->hw.headp = paddr; + + up_clean_dcache((uintptr_t)ed, + (uintptr_t)ed + sizeof(struct ohci_ed_s)); + } + + /* Free all transfer descriptors that were connected to the ED. In + * some race conditions with the hardware, this might be none. + */ + + while (td != (struct jz_gtd_s *)eplist->tail) + { + paddr = (uintptr_t)td->hw.nexttd; + next = (struct jz_gtd_s *)jz_virtramaddr(paddr); + jz_tdfree(td); + td = next; + } + + ed->tdstatus = TD_CC_USER; + + /* If there is a thread waiting for the transfer to complete, then + * wake up the thread. + */ + + if (eplist->wdhwait) + { +#ifdef CONFIG_USBHOST_ASYNCH + /* Yes.. there should not also be a callback scheduled */ + + DEBUGASSERT(eplist->callback == NULL); +#endif + + /* Wake up the waiting thread */ + + nxsem_post(&eplist->wdhsem); + eplist->wdhwait = false; + } +#ifdef CONFIG_USBHOST_ASYNCH + else + { + /* Otherwise, perform the callback */ + + jz_asynch_completion(eplist); + } +#endif + } + + /* Reset any pending activity indications */ + + eplist->wdhwait = false; +#ifdef CONFIG_USBHOST_ASYNCH + eplist->callback = NULL; + eplist->arg = NULL; +#endif + eplist->buffer = NULL; + eplist->buflen = 0; + + leave_critical_section(flags); + return OK; +} + +/**************************************************************************** + * Name: jz_connect + * + * Description: + * New connections may be detected by an attached hub. This method is the + * mechanism that is used by the hub class to introduce a new connection + * and port description to the system. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the + * call to the class create() method. + * hport - The descriptor of the hub port that detected the connection + * related event + * connected - True: device connected; false: device disconnected + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure. + * + ****************************************************************************/ + +#ifdef CONFIG_USBHOST_HUB +static int jz_connect(struct usbhost_driver_s *drvr, + struct usbhost_hubport_s *hport, + bool connected) +{ + irqstate_t flags; + + /* Set the connected/disconnected flag */ + + hport->connected = connected; + uinfo("Hub port %d connected: %s\n", + hport->port, connected ? "YES" : "NO"); + + /* Report the connection event */ + + flags = enter_critical_section(); + DEBUGASSERT(g_ohci.hport == NULL); /* REVISIT */ + + g_ohci.hport = hport; + if (g_ohci.pscwait) + { + g_ohci.pscwait = false; + nxsem_post(&g_ohci.pscsem); + } + + leave_critical_section(flags); + return OK; +} +#endif + +/**************************************************************************** + * Name: jz_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 jz_disconnect(struct usbhost_driver_s *drvr, + struct usbhost_hubport_s *hport) +{ + struct jz_rhport_s *rhport = (struct jz_rhport_s *)drvr; + struct jz_eplist_s *ep0; + + DEBUGASSERT(rhport != NULL && hport != NULL && hport->ep0); + ep0 = (struct jz_eplist_s *)hport->ep0; + + /* Did we just dequeue EP0 from a root hub port? */ + + if (ROOTHUB(hport)) + { + /* Remove the disconnected port from the control list */ + + jz_ep0dequeue(ep0); + rhport->ep0init = false; + } + + /* Unbind the class from the port */ + + hport->devclass = NULL; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: jz_ohci_initialize + * + * Description: + * Initialize USB OHCI host controller hardware. + * + * Input Parameters: + * controller -- If the device supports more than one OHCI interface, then + * this identifies which controller is being initialized. Normally, this + * is just zero. + * + * Returned Value: + * And instance of the USB host interface. The controlling task should + * use this interface to (1) call the wait() method to wait for a device + * to be connected, and (2) call the enumerate() method to bind the device + * to a class driver. + * + * Assumptions: + * - This function should called in the initialization sequence in order + * to initialize the USB device functionality. + * - Class drivers should be initialized prior to calling this function. + * Otherwise, there is a race condition if the device is already connected. + * + ****************************************************************************/ + +struct usbhost_connection_s *jz_ohci_initialize(int controller) +{ + struct usbhost_hubport_s *hport; + uintptr_t physaddr; + uint32_t regval; + uint8_t *buffer; + irqstate_t flags; + int i; + + /* One time sanity checks */ + + DEBUGASSERT(controller == 0); + DEBUGASSERT(sizeof(struct jz_ed_s) == SIZEOF_JZ_ED_S); + DEBUGASSERT(sizeof(struct jz_gtd_s) == SIZEOF_JZ_TD_S); + +#ifndef CONFIG_USBHOST_INT_DISABLE + g_ohci.ininterval = MAX_PERINTERVAL; + g_ohci.outinterval = MAX_PERINTERVAL; +#endif + + /* Enable UHP peripheral clocking */ + + flags = enter_critical_section(); + + leave_critical_section(flags); + + /* Note that no pin configuration is required. All USB HS pins have + * dedicated function + */ + + usbhost_vtrace1(OHCI_VTRACE1_INITIALIZING, 0); + + /* Initialize all the HCCA to 0 */ + + memset((void *)&g_hcca, 0, sizeof(struct ohci_hcca_s)); + + up_clean_dcache((uint32_t)&g_hcca, + (uint32_t)&g_hcca + sizeof(struct ohci_hcca_s)); + + /* Initialize user-configurable EDs */ + + for (i = 0; i < JZ4780_OHCI_NEDS; i++) + { + /* Put the ED in a free list */ + + jz_edfree(&g_edalloc[i]); + } + + /* Initialize user-configurable TDs */ + + for (i = 0; i < JZ4780_OHCI_NTDS; i++) + { + /* Put the TD in a free list */ + + jz_tdfree(&g_tdalloc[i]); + } + + /* Initialize user-configurable request/descriptor transfer buffers */ + + buffer = g_bufalloc; + for (i = 0; i < CONFIG_JZ4780_OHCI_TDBUFFERS; i++) + { + /* Put the TD buffer in a free list */ + + jz_tbfree(buffer); + buffer += CONFIG_JZ4780_OHCI_TDBUFSIZE; + } + + /* Initialize function address generation logic */ + + usbhost_devaddr_initialize(&g_ohci.devgen); + + /* Initialize the root hub port structures */ + + for (i = 0; i < JZ_OHCI_NRHPORT; i++) + { + struct jz_rhport_s *rhport = &g_ohci.rhport[i]; + + /* Initialize the device operations */ + + rhport->drvr.ep0configure = jz_ep0configure; + rhport->drvr.epalloc = jz_epalloc; + rhport->drvr.epfree = jz_epfree; + rhport->drvr.alloc = jz_alloc; + rhport->drvr.free = jz_free; + rhport->drvr.ioalloc = jz_ioalloc; + rhport->drvr.iofree = jz_iofree; + rhport->drvr.ctrlin = jz_ctrlin; + rhport->drvr.ctrlout = jz_ctrlout; + rhport->drvr.transfer = jz_transfer; +#ifdef CONFIG_USBHOST_ASYNCH + rhport->drvr.asynch = jz_asynch; +#endif + rhport->drvr.cancel = jz_cancel; +#ifdef CONFIG_USBHOST_HUB + rhport->drvr.connect = jz_connect; +#endif + rhport->drvr.disconnect = jz_disconnect; + rhport->hport.pdevgen = &g_ohci.devgen; + + /* Initialize the public port representation */ + + hport = &rhport->hport.hport; + hport->drvr = &rhport->drvr; +#ifdef CONFIG_USBHOST_HUB + hport->parent = NULL; +#endif + hport->ep0 = &rhport->ep0; + hport->port = i; + hport->speed = USB_SPEED_FULL; + hport->funcaddr = 0; + } + + /* Wait 50MS then perform hardware reset */ + + up_mdelay(50); + + jz_putreg(0, JZ_USBHOST_CTRL); /* Hardware reset */ + jz_putreg(0, JZ_USBHOST_CTRLHEADED); /* Initialize control list head to Zero */ + jz_putreg(0, JZ_USBHOST_BULKHEADED); /* Initialize bulk list head to Zero */ + + /* Software reset */ + + jz_putreg(OHCI_CMDST_HCR, JZ_USBHOST_CMDST); + + /* Write Fm interval (FI), largest data packet counter (FSMPS), and + * periodic start. + */ + + jz_putreg(DEFAULT_FMINTERVAL, JZ_USBHOST_FMINT); + jz_putreg(DEFAULT_PERSTART, JZ_USBHOST_PERSTART); + + /* Put HC in operational state */ + + regval = jz_getreg(JZ_USBHOST_CTRL); + regval &= ~OHCI_CTRL_HCFS_MASK; + regval |= OHCI_CTRL_HCFS_OPER; + jz_putreg(regval, JZ_USBHOST_CTRL); + + /* Set global power in HcRhStatus */ + + jz_putreg(OHCI_RHSTATUS_SGP, JZ_USBHOST_RHSTATUS); + + /* Set HCCA base address */ + + physaddr = jz_physramaddr((uintptr_t)&g_hcca); + jz_putreg(physaddr, JZ_USBHOST_HCCA); + + /* Clear pending interrupts */ + + regval = jz_getreg(JZ_USBHOST_INTST); + jz_putreg(regval, JZ_USBHOST_INTST); + + /* Enable OHCI interrupts */ + + jz_putreg((JZ_ALL_INTS | OHCI_INT_MIE), JZ_USBHOST_INTEN); + + /* Attach USB host controller interrupt handler. If EHCI is enabled, + * then it will manage the shared interrupt. + */ + + if (irq_attach(JZ4780_IRQ_OHCI, jz_ohci_tophalf, NULL) != 0) + { + usbhost_trace1(OHCI_TRACE1_IRQATTACH, JZ4780_IRQ_OHCI); + return NULL; + } + + /* Drive Vbus +5V (the smoke test). */ + + 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 < JZ_OHCI_NRHPORT; i++) + { + regval = jz_getreg(JZ_USBHOST_RHPORTST(i)); + g_ohci.rhport[i].connected = ((regval & OHCI_RHPORTST_CCS) != 0); + + usbhost_vtrace2(OHCI_VTRACE2_INITCONNECTED, + i + 1, g_ohci.rhport[i].connected); + } + + up_enable_irq(JZ4780_IRQ_OHCI); /* enable USB interrupt */ + + usbhost_vtrace1(OHCI_VTRACE1_INITIALIZED, 0); + + return &g_ohciconn; +} + +/**************************************************************************** + * Name: jz_ohci_tophalf + * + * Description: + * OHCI "Top Half" interrupt handler. + * + ****************************************************************************/ + +static int jz_ohci_tophalf(int irq, void *context, void *arg) +{ + uint32_t intst; + uint32_t inten; + uint32_t pending; + + /* Read Interrupt Status and mask out interrupts that are not enabled. */ + + intst = jz_getreg(JZ_USBHOST_INTST); + inten = jz_getreg(JZ_USBHOST_INTEN); + usbhost_vtrace1(OHCI_VTRACE1_INTRPENDING, intst & inten); + + { + /* Mask out the interrupts that are not enabled */ + + pending = intst & inten; + if (pending != 0) + { + /* Schedule interrupt handling work for the high priority worker + * thread so that we are not pressed for time and so that we can + * interrupt with other USB threads gracefully. + * + * The worker should be available now because we implement a + * handshake by controlling the OHCI interrupts. + */ + + DEBUGASSERT(work_available(&g_ohci.work)); + DEBUGVERIFY(work_queue(HPWORK, &g_ohci.work, jz_ohci_bottomhalf, + (void *)pending, 0)); + + /* Disable further OHCI interrupts so that we do not overrun the + * work queue. + */ + + jz_putreg(OHCI_INT_MIE, JZ_USBHOST_INTDIS); + + /* Clear all pending status bits by writing the value of the + * pending interrupt bits back to the status register. + */ + + jz_putreg(intst, JZ_USBHOST_INTST); + } + } + + return OK; +} + +#endif /* CONFIG_JZ4780_OHCI */ diff --git a/arch/mips/src/jz4780/jz4780_ohci.h b/arch/mips/src/jz4780/jz4780_ohci.h new file mode 100644 index 0000000000000..237f6378ed899 --- /dev/null +++ b/arch/mips/src/jz4780/jz4780_ohci.h @@ -0,0 +1,102 @@ +/**************************************************************************** + * arch/mips/src/jz4780/jz4780_ohci.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_MIPS_SRC_JZ4780_JZ4780_OHCI_H +#define __ARCH_MIPS_SRC_JZ4780_JZ4780_OHCI_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include "chip.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#define JZ_UHPOHCI_VSECTION 0xb34a0000 /* JZ4780 OHCI */ + +/* The JZ4780 supports 1 root hub port */ + +#define JZ_OHCI_NRHPORT 1 + +/* Register offsets *********************************************************/ + +/* See nuttx/usb/ohci.h */ + +/* Register addresses *******************************************************/ + +#define JZ_USBHOST_HCIREV (JZ_UHPOHCI_VSECTION + OHCI_HCIREV_OFFSET) +#define JZ_USBHOST_CTRL (JZ_UHPOHCI_VSECTION + OHCI_CTRL_OFFSET) +#define JZ_USBHOST_CMDST (JZ_UHPOHCI_VSECTION + OHCI_CMDST_OFFSET) +#define JZ_USBHOST_INTST (JZ_UHPOHCI_VSECTION + OHCI_INTST_OFFSET) +#define JZ_USBHOST_INTEN (JZ_UHPOHCI_VSECTION + OHCI_INTEN_OFFSET) +#define JZ_USBHOST_INTDIS (JZ_UHPOHCI_VSECTION + OHCI_INTDIS_OFFSET) + +/* Memory pointers (section 7.2) */ + +#define JZ_USBHOST_HCCA (JZ_UHPOHCI_VSECTION + OHCI_HCCA_OFFSET) +#define JZ_USBHOST_PERED (JZ_UHPOHCI_VSECTION + OHCI_PERED_OFFSET) +#define JZ_USBHOST_CTRLHEADED (JZ_UHPOHCI_VSECTION + OHCI_CTRLHEADED_OFFSET) +#define JZ_USBHOST_CTRLED (JZ_UHPOHCI_VSECTION + OHCI_CTRLED_OFFSET) +#define JZ_USBHOST_BULKHEADED (JZ_UHPOHCI_VSECTION + OHCI_BULKHEADED_OFFSET) +#define JZ_USBHOST_BULKED (JZ_UHPOHCI_VSECTION + OHCI_BULKED_OFFSET) +#define JZ_USBHOST_DONEHEAD (JZ_UHPOHCI_VSECTION + OHCI_DONEHEAD_OFFSET) + +/* Frame counters (section 7.3) */ + +#define JZ_USBHOST_FMINT (JZ_UHPOHCI_VSECTION + OHCI_FMINT_OFFSET) +#define JZ_USBHOST_FMREM (JZ_UHPOHCI_VSECTION + OHCI_FMREM_OFFSET) +#define JZ_USBHOST_FMNO (JZ_UHPOHCI_VSECTION + OHCI_FMNO_OFFSET) +#define JZ_USBHOST_PERSTART (JZ_UHPOHCI_VSECTION + OHCI_PERSTART_OFFSET) + +/* Root hub ports (section 7.4) */ + +#define JZ_USBHOST_LSTHRES (JZ_UHPOHCI_VSECTION + OHCI_LSTHRES_OFFSET) +#define JZ_USBHOST_RHDESCA (JZ_UHPOHCI_VSECTION + OHCI_RHDESCA_OFFSET) +#define JZ_USBHOST_RHDESCB (JZ_UHPOHCI_VSECTION + OHCI_RHDESCB_OFFSET) +#define JZ_USBHOST_RHSTATUS (JZ_UHPOHCI_VSECTION + OHCI_RHSTATUS_OFFSET) + +#define JZ_USBHOST_RHPORTST(n) (JZ_UHPOHCI_VSECTION + OHCI_RHPORTST_OFFSET(n)) +#define JZ_USBHOST_RHPORTST1 (JZ_UHPOHCI_VSECTION + OHCI_RHPORTST1_OFFSET) +#define JZ_USBHOST_RHPORTST2 (JZ_UHPOHCI_VSECTION + OHCI_RHPORTST2_OFFSET) +#define JZ_USBHOST_RHPORTST3 (JZ_UHPOHCI_VSECTION + OHCI_RHPORTST3_OFFSET) + +/* Register bit definitions *************************************************/ + +/* See include/nuttx/usb/ohci.h */ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/**************************************************************************** + * Public Functions Prototypes + ****************************************************************************/ + +#endif /* __ARCH_MIPS_SRC_JZ4780_JZ4780_OHCI_H */ diff --git a/arch/mips/src/jz4780/jz4780_timerisr.c b/arch/mips/src/jz4780/jz4780_timerisr.c index 9e1ee6bfedac9..f34d28ccc3f57 100644 --- a/arch/mips/src/jz4780/jz4780_timerisr.c +++ b/arch/mips/src/jz4780/jz4780_timerisr.c @@ -36,7 +36,6 @@ #include "clock/clock.h" #include "mips_internal.h" -#include "mips_internal.h" #include "jz4780_lowinit.h" /**************************************************************************** diff --git a/arch/mips/src/jz4780/jz_usbhost.c b/arch/mips/src/jz4780/jz_usbhost.c new file mode 100644 index 0000000000000..63b726f7034bc --- /dev/null +++ b/arch/mips/src/jz4780/jz_usbhost.c @@ -0,0 +1,425 @@ +/**************************************************************************** + * arch/mips/src/jz4780/jz_usbhost.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#include + +#include "jz_usbhost.h" + +#ifdef HAVE_USBHOST_TRACE + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define TR_OHCI false +#define TR_EHCI true + +#define TR_FMT1 false +#define TR_FMT2 true + +#define TRENTRY(id,ehci,fmt1,string) {string} + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct sam_usbhost_trace_s +{ +#if 0 + uint16_t id; + bool ehci; + bool fmt2; +#endif + const char *string; +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct sam_usbhost_trace_s g_trace1[TRACE1_NSTRINGS] = +{ +#ifdef CONFIG_JZ4780_OHCI + TRENTRY(OHCI_TRACE1_DEVDISCONN, + TR_OHCI, TR_FMT1, + "OHCI ERROR: RHport%d Device disconnected\n"), + TRENTRY(OHCI_TRACE1_INTRUNRECOVERABLE, + TR_OHCI, TR_FMT1, + "OHCI ERROR: Unrecoverable error. pending: %06x\n"), + TRENTRY(OHCI_TRACE1_INTRUNHANDLED, + TR_OHCI, TR_FMT1, + "OHCI ERROR: Unhandled interrupts pending: %06x\n"), + TRENTRY(OHCI_TRACE1_EPLISTALLOC_FAILED, + TR_OHCI, TR_FMT1, + "OHCI ERROR: Failed to allocate EP list\n"), + TRENTRY(OHCI_TRACE1_EDALLOC_FAILED, + TR_OHCI, TR_FMT1, + "OHCI ERROR: Failed to allocate ED\n"), + TRENTRY(OHCI_TRACE1_TDALLOC_FAILED, + TR_OHCI, TR_FMT1, + "OHCI ERROR: Failed to allocate TD\n"), + TRENTRY(OHCI_TRACE1_IRQATTACH, + TR_OHCI, TR_FMT1, + "OHCI ERROR: Failed to attach IRQ%d\n"), +#ifdef CONFIG_USBHOST_ASYNCH + TRENTRY(OHCI_TRACE1_BADTDSTATUS, + TR_OHCI, TR_FMT1, + "OHCI ERROR: Bad asynch TD completion status: %d\n"), +#endif + +#ifdef HAVE_USBHOST_TRACE_VERBOSE + TRENTRY(OHCI_VTRACE1_PHYSED, + TR_OHCI, TR_FMT1, + "OHCI physed: %06x\n"), + TRENTRY(OHCI_VTRACE1_VIRTED, + TR_OHCI, TR_FMT1, + "OHCI ed: %06x\n"), + TRENTRY(OHCI_VTRACE1_CSC, + TR_OHCI, TR_FMT1, + "OHCI Connect Status Change, RHSTATUS: %06x\n"), + TRENTRY(OHCI_VTRACE1_DRWE, + TR_OHCI, TR_FMT1, + "OHCI DRWE: Remote wake-up, RHSTATUS: %06x\n"), + TRENTRY(OHCI_VTRACE1_ALREADYCONN, + TR_OHCI, TR_FMT1, + "OHCI Already connected, RHPORTST: %06x\n"), + TRENTRY(OHCI_VTRACE1_SPEED, + TR_OHCI, TR_FMT1, + "OHCI Port speed: %d\n"), + TRENTRY(OHCI_VTRACE1_ALREADYDISCONN, + TR_OHCI, TR_FMT1, + "OHCI Already disconnected, RHPORTST: %06x\n"), + TRENTRY(OHCI_VTRACE1_RHSC, + TR_OHCI, TR_FMT1, + "OHCI Root Hub Status Change. Pending: %06x\n"), + TRENTRY(OHCI_VTRACE1_WDHINTR, + TR_OHCI, TR_FMT1, + "OHCI Writeback Done Head interrupt. Pending: %06x\n"), + TRENTRY(OHCI_VTRACE1_CLASSENUM, + TR_OHCI, TR_FMT1, + "OHCI Hub port %d: Enumerate device\n"), + TRENTRY(OHCI_VTRACE1_ENUMDISCONN, + TR_OHCI, TR_FMT1, + "OHCI RHport%dNot connected\n"), + TRENTRY(OHCI_VTRACE1_INITIALIZING, + TR_OHCI, TR_FMT1, + "OHCI Initializing Stack\n"), + TRENTRY(OHCI_VTRACE1_INITIALIZED, + TR_OHCI, TR_FMT1, + "OHCI Initialized\n"), + TRENTRY(OHCI_VTRACE1_INTRPENDING, + TR_OHCI, TR_FMT1, + "OHCI Interrupts pending: %06x\n"), +#endif +#endif + +#ifdef CONFIG_JZ4780_EHCI + TRENTRY(EHCI_TRACE1_SYSTEMERROR, + TR_EHCI, TR_FMT1, + "EHCI ERROR: System error: %06x\n"), + TRENTRY(EHCI_TRACE1_QTDFOREACH_FAILED, + TR_EHCI, TR_FMT1, + "EHCI ERROR: sam_qtd_foreach failed: %d\n"), + TRENTRY(EHCI_TRACE1_QHALLOC_FAILED, + TR_EHCI, TR_FMT1, + "EHCI ERROR: Failed to allocate a QH\n"), + TRENTRY(EHCI_TRACE1_BUFTOOBIG, + TR_EHCI, TR_FMT1, + "EHCI ERROR: Buffer too big. Remaining %d\n"), + TRENTRY(EHCI_TRACE1_REQQTDALLOC_FAILED, + TR_EHCI, TR_FMT1, + "EHCI ERROR: Failed to allocate request qTD"), + TRENTRY(EHCI_TRACE1_ADDBPL_FAILED, + TR_EHCI, TR_FMT1, + "EHCI ERROR: sam_qtd_addbpl failed: %d\n"), + TRENTRY(EHCI_TRACE1_DATAQTDALLOC_FAILED, + TR_EHCI, TR_FMT1, + "EHCI ERROR: Failed to allocate data buffer qTD, 0"), + TRENTRY(EHCI_TRACE1_DEVDISCONNECTED, + TR_EHCI, TR_FMT1, + "EHCI ERROR: Device disconnected %d\n"), + TRENTRY(EHCI_TRACE1_QHCREATE_FAILED, + TR_EHCI, TR_FMT1, + "EHCI ERROR: sam_qh_create failed\n"), + TRENTRY(EHCI_TRACE1_QTDSETUP_FAILED, + TR_EHCI, TR_FMT1, + "EHCI ERROR: sam_qtd_setupphase failed\n"), + TRENTRY(EHCI_TRACE1_QTDDATA_FAILED, + TR_EHCI, TR_FMT1, + "EHCI ERROR: sam_qtd_dataphase failed\n"), + TRENTRY(EHCI_TRACE1_QTDSTATUS_FAILED, + TR_EHCI, TR_FMT1, + "EHCI ERROR: sam_qtd_statusphase failed\n"), + TRENTRY(EHCI_TRACE1_TRANSFER_FAILED, + TR_EHCI, TR_FMT1, + "EHCI ERROR: Transfer failed %d\n"), + TRENTRY(EHCI_TRACE1_QHFOREACH_FAILED, + TR_EHCI, TR_FMT1, + "EHCI ERROR: sam_qh_foreach failed: %d\n"), + TRENTRY(EHCI_TRACE1_SYSERR_INTR, + TR_EHCI, TR_FMT1, + "EHCI: Host System Error Interrupt\n"), + TRENTRY(EHCI_TRACE1_USBERR_INTR, + TR_EHCI, TR_FMT1, + "EHCI: USB Error Interrupt (USBERRINT) Interrupt: %06x\n"), + TRENTRY(EHCI_TRACE1_EPALLOC_FAILED, + TR_EHCI, TR_FMT1, + "EHCI ERROR: Failed to allocate EP info structure\n"), + TRENTRY(EHCI_TRACE1_BADXFRTYPE, + TR_EHCI, TR_FMT1, + "EHCI ERROR: Support for transfer type %d not implemented\n"), + TRENTRY(EHCI_TRACE1_HCHALTED_TIMEOUT, + TR_EHCI, TR_FMT1, + "EHCI ERROR: Timed out waiting for HCHalted. USBSTS: %06x\n"), + TRENTRY(EHCI_TRACE1_QHPOOLALLOC_FAILED, + TR_EHCI, TR_FMT1, + "EHCI ERROR: Failed to allocate the QH pool\n"), + TRENTRY(EHCI_TRACE1_QTDPOOLALLOC_FAILED, + TR_EHCI, TR_FMT1, + "EHCI ERROR: Failed to allocate the qTD pool\n"), + TRENTRY(EHCI_TRACE1_PERFLALLOC_FAILED, + TR_EHCI, TR_FMT1, + "EHCI ERROR: Failed to allocate the periodic frame list\n"), + TRENTRY(EHCI_TRACE1_RESET_FAILED, + TR_EHCI, TR_FMT1, + "EHCI ERROR: sam_reset failed: %d\n"), + TRENTRY(EHCI_TRACE1_RUN_FAILED, + TR_EHCI, TR_FMT1, + "EHCI ERROR: EHCI Failed to run: USBSTS=%06x\n"), + TRENTRY(EHCI_TRACE1_IRQATTACH_FAILED, + TR_EHCI, TR_FMT1, + "EHCI ERROR: Failed to attach IRQ%d\n"), + +#ifdef HAVE_USBHOST_TRACE_VERBOSE + TRENTRY(EHCI_VTRACE1_PORTSC_CSC, + TR_EHCI, TR_FMT1, + "EHCI Connect Status Change: %06x\n"), + TRENTRY(EHCI_VTRACE1_PORTSC_CONNALREADY, + TR_EHCI, TR_FMT1, + "EHCI Already connected: %06x\n"), + TRENTRY(EHCI_VTRACE1_PORTSC_DISCALREADY, + TR_EHCI, TR_FMT1, + "EHCI Already disconnected: %06x\n"), + TRENTRY(EHCI_VTRACE1_TOPHALF, + TR_EHCI, TR_FMT1, + "EHCI Interrupt: %06x\n"), + TRENTRY(EHCI_VTRACE1_AAINTR, + TR_EHCI, TR_FMT1, + "EHCI Async Advance Interrupt\n"), + TRENTRY(EHCI_VTRACE1_USBINTR, + TR_EHCI, TR_FMT1, + "EHCI USB Interrupt (USBINT) Interrupt: %06x\n"), + TRENTRY(EHCI_VTRACE1_CLASSENUM, + TR_EHCI, TR_FMT1, + "EHCI Hub port %d: Enumerate device\n"), + TRENTRY(EHCI_VTRACE1_ENUM_DISCONN, + TR_EHCI, TR_FMT1, + "EHCI Enumeration not connected\n"), + TRENTRY(EHCI_VTRACE1_INITIALIZING, + TR_EHCI, TR_FMT1, + "EHCI Initializing EHCI Stack\n"), + TRENTRY(EHCI_VTRACE1_HCCPARAMS, + TR_EHCI, TR_FMT1, + "EHCI HCCPARAMS=%06x\n"), + TRENTRY(EHCI_VTRACE1_INIITIALIZED, + TR_EHCI, TR_FMT1, + "EHCI USB EHCI Initialized\n"), +#endif +#endif +}; + +static const struct sam_usbhost_trace_s g_trace2[TRACE2_NSTRINGS] = +{ +#ifdef CONFIG_JZ4780_OHCI + TRENTRY(OHCI_TRACE2_BADTDSTATUS, + TR_OHCI, TR_FMT2, + "OHCI ERROR: RHport%d Bad TD completion status: %d\n"), + TRENTRY(OHCI_TRACE2_WHDTDSTATUS, + TR_OHCI, TR_FMT2, + "OHCI ERROR: WHD Bad TD completion status: %d xfrtype: %d\n"), + TRENTRY(OHCI_TRACE2_EP0ENQUEUE_FAILED, + TR_OHCI, TR_FMT2, + "OHCI ERROR: RHport%d Failed to enqueue EP0: %d\n"), + TRENTRY(OHCI_TRACE2_EDENQUEUE_FAILED, + TR_OHCI, TR_FMT2, + "OHCI ERROR: Failed to queue ED for transfer type %d: %d\n"), + TRENTRY(OHCI_TRACE2_CLASSENUM_FAILED, + TR_OHCI, TR_FMT2, + "OHCI Hub port %d usbhost_enumerate() failed: %d\n"), + +#ifdef HAVE_USBHOST_TRACE_VERBOSE + TRENTRY(OHCI_VTRACE2_EP0CONFIG, + TR_OHCI, TR_FMT2, + "OHCI EP0 configure speed=%d funcaddr=%d\n"), + TRENTRY(OHCI_VTRACE2_INTERVAL, + TR_OHCI, TR_FMT2, + "OHCI interval: %d->%d\n"), + TRENTRY(OHCI_VTRACE2_MININTERVAL, + TR_OHCI, TR_FMT2, + "OHCI MIN interval: %d offset: %d\n"), + TRENTRY(OHCI_VTRACE2_RHPORTST, + TR_OHCI, TR_FMT2, + "OHCI RHPORTST%d: %04x\n"), + TRENTRY(OHCI_VTRACE2_CONNECTED, + TR_OHCI, TR_FMT2, + "OHCI RHPort%d connected, rhswait: %d\n"), + TRENTRY(OHCI_VTRACE2_DISCONNECTED, + TR_OHCI, TR_FMT2, + "OHCI RHPort%d disconnected, rhswait: %d\n"), + TRENTRY(OHCI_VTRACE2_WAKEUP, + TR_OHCI, TR_FMT2, + "OHCI RHPort%d connected: %d\n"), + TRENTRY(OHCI_VTRACE2_EP0CTRLED, + TR_OHCI, TR_FMT2, + "OHCI RHPort%d EP0 CTRL: %04x\n"), + TRENTRY(OHCI_VTRACE2_EPALLOC, + TR_OHCI, TR_FMT2, + "OHCI EP%d CTRL: %04x\n"), + TRENTRY(OHCI_VTRACE2_CTRLIN, + TR_OHCI, TR_FMT2, + "OHCI CTRLIN RHPort%d req: %02x\n"), + TRENTRY(OHCI_VTRACE2_CTRLOUT, + TR_OHCI, TR_FMT2, + "OHCI CTRLOUT RHPort%d req: %02x\n"), + TRENTRY(OHCI_VTRACE2_TRANSFER, + TR_OHCI, TR_FMT2, + "OHCI EP%d buflen: %d\n"), + TRENTRY(OHCI_VTRACE2_INITCONNECTED, + TR_OHCI, TR_FMT2, + "OHCI RHPort%d Device connected: %d\n"), +#ifdef CONFIG_USBHOST_HUB + TRENTRY(OHCI_VTRACE2_HUBWAKEUP, + TR_OHCI, TR_FMT2, + "OHCI Hub Port%d connected: %d\n"), +#endif +#endif +#endif + +#ifdef CONFIG_JZ4780_EHCI + TRENTRY(EHCI_TRACE2_EPSTALLED, + TR_EHCI, TR_FMT2, + "EHCI EP%d Stalled: TOKEN=%04x\n"), + TRENTRY(EHCI_TRACE2_EPIOERROR, + TR_EHCI, TR_FMT2, + "EHCI ERROR: EP%d TOKEN=%04x\n"), + TRENTRY(EHCI_TRACE2_CLASSENUM_FAILED, + TR_EHCI, TR_FMT2, + "EHCI Hub port %d usbhost_enumerate() failed: %d\n"), + +#ifdef HAVE_USBHOST_TRACE_VERBOSE + TRENTRY(EHCI_VTRACE2_EP0CONFIG, + TR_EHCI, TR_FMT2, + "EHCI EP0 configure speed=%d funcaddr=%d\n"), + TRENTRY(EHCI_VTRACE2_ASYNCXFR, + TR_EHCI, TR_FMT2, + "EHCI Async transfer EP%d buflen=%d\n"), + TRENTRY(EHCI_VTRACE2_INTRXFR, + TR_EHCI, TR_FMT2, + "EHCI Intr Transfer EP%d buflen=%d\n"), + TRENTRY(EHCI_VTRACE2_IOCCHECK, + TR_EHCI, TR_FMT2, + "EHCI IOC EP%d TOKEN=%04x\n"), + TRENTRY(EHCI_VTRACE2_PORTSC, + TR_EHCI, TR_FMT2, + "EHCI PORTSC%d: %04x\n"), + TRENTRY(EHCI_VTRACE2_PORTSC_CONNECTED, + TR_EHCI, TR_FMT2, + "EHCI RHPort%d connected, pscwait: %d\n"), + TRENTRY(EHCI_VTRACE2_PORTSC_DISCONND, + TR_EHCI, TR_FMT2, + "EHCI RHport%d disconnected, pscwait: %d\n"), + TRENTRY(EHCI_VTRACE2_MONWAKEUP, + TR_EHCI, TR_FMT2, + "EHCI Hub port%d connected: %d\n"), + TRENTRY(EHCI_VTRACE2_EPALLOC, + TR_EHCI, TR_FMT2, + "EHCI EPALLOC: EP%d TYPE=%d\n"), + TRENTRY(EHCI_VTRACE2_CTRLINOUT, + TR_EHCI, TR_FMT2, + "EHCI CTRLIN/OUT: RHPort%d req: %02x\n"), + TRENTRY(EHCI_VTRACE2_HCIVERSION, + TR_EHCI, TR_FMT2, + "EHCI HCIVERSION %x.%02x\n"), + TRENTRY(EHCI_VTRACE2_HCSPARAMS, + TR_EHCI, TR_FMT2, + "EHCI nports=%d, HCSPARAMS=%04x\n"), +#endif +#endif +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: usbhost_trformat1 and usbhost_trformat2 + * + * Description: + * This interface must be provided by platform specific logic that knows + * the HCDs encoding of USB trace data. + * + * Given an 9-bit index, return a format string suitable for use with, say, + * printf. The returned format is expected to handle two unsigned integer + * values. + * + ****************************************************************************/ + +const char *usbhost_trformat1(uint16_t id) +{ + int ndx = TRACE1_INDEX(id); + + if (ndx < TRACE1_NSTRINGS) + { + return g_trace1[ndx].string; + } + + return NULL; +} + +const char *usbhost_trformat2(uint16_t id) +{ + int ndx = TRACE2_INDEX(id); + + if (ndx < TRACE2_NSTRINGS) + { + return g_trace2[ndx].string; + } + + return NULL; +} + +#endif /* HAVE_USBHOST_TRACE */ diff --git a/arch/mips/src/jz4780/jz_usbhost.h b/arch/mips/src/jz4780/jz_usbhost.h new file mode 100644 index 0000000000000..e07dbf1dbd7ca --- /dev/null +++ b/arch/mips/src/jz4780/jz_usbhost.h @@ -0,0 +1,280 @@ +/**************************************************************************** + * arch/mips/src/jz4780/jz_usbhost.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_MIPS_SRC_JZ4780_JZ_USBHOST_H +#define __ARCH_MIPS_SRC_JZ4780_JZ_USBHOST_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#ifdef CONFIG_USBHOST + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* This is the interface argument for call outs to board-specific functions + * which need to know which USB host interface is being used. + */ + +#define JZ_EHCI_IFACE 0 +#define JZ_OHCI_IFACE 1 + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +#ifdef HAVE_USBHOST_TRACE +enum usbhost_trace1codes_e +{ + __TRACE1_BASEVALUE = 0, /* This will force the first value to be 1 */ + +#ifdef CONFIG_JZ4780_OHCI + OHCI_TRACE1_DEVDISCONN, /* OHCI ERROR: RHport Device disconnected */ + OHCI_TRACE1_INTRUNRECOVERABLE, /* OHCI ERROR: Unrecoverable error */ + OHCI_TRACE1_INTRUNHANDLED, /* OHCI ERROR: Unhandled interrupts */ + OHCI_TRACE1_EPLISTALLOC_FAILED, /* OHCI ERROR: Failed to allocate EP list */ + OHCI_TRACE1_EDALLOC_FAILED, /* OHCI ERROR: Failed to allocate ED */ + OHCI_TRACE1_TDALLOC_FAILED, /* OHCI ERROR: Failed to allocate TD */ + OHCI_TRACE1_IRQATTACH, /* OHCI ERROR: Failed to attach IRQ */ +#ifdef CONFIG_USBHOST_ASYNCH + OHCI_TRACE1_BADTDSTATUS, /* OHCI ERROR: Bad asynch TD completion status */ +#endif + +#ifdef HAVE_USBHOST_TRACE_VERBOSE + OHCI_VTRACE1_PHYSED, /* OHCI physed */ + OHCI_VTRACE1_VIRTED, /* OHCI ed */ + OHCI_VTRACE1_CSC, /* OHCI Connect Status Change */ + OHCI_VTRACE1_DRWE, /* OHCI DRWE: Remote wake-up */ + OHCI_VTRACE1_ALREADYCONN, /* OHCI Already connected */ + OHCI_VTRACE1_SPEED, /* OHCI Low speed */ + OHCI_VTRACE1_ALREADYDISCONN, /* OHCI Already disconnected */ + OHCI_VTRACE1_RHSC, /* OHCI Root Hub Status Change */ + OHCI_VTRACE1_WDHINTR, /* OHCI Writeback Done Head interrupt */ + OHCI_VTRACE1_CLASSENUM, /* OHCI Enumerate the device */ + OHCI_VTRACE1_ENUMDISCONN, /* OHCI RHport Not connected */ + OHCI_VTRACE1_INITIALIZING, /* OHCI Initializing Stack */ + OHCI_VTRACE1_INITIALIZED, /* OHCI Initialized */ + OHCI_VTRACE1_INTRPENDING, /* OHCI Interrupts pending */ +#endif +#endif + +#ifdef CONFIG_JZ4780_EHCI + EHCI_TRACE1_SYSTEMERROR, /* EHCI ERROR: System error */ + EHCI_TRACE1_QTDFOREACH_FAILED, /* EHCI ERROR: jz_qtd_foreach failed */ + EHCI_TRACE1_QHALLOC_FAILED, /* EHCI ERROR: Failed to allocate a QH */ + EHCI_TRACE1_BUFTOOBIG, /* EHCI ERROR: Buffer too big */ + EHCI_TRACE1_REQQTDALLOC_FAILED, /* EHCI ERROR: Failed to allocate request qTD */ + EHCI_TRACE1_ADDBPL_FAILED, /* EHCI ERROR: jz_qtd_addbpl failed */ + EHCI_TRACE1_DATAQTDALLOC_FAILED, /* EHCI ERROR: Failed to allocate data buffer qTD */ + EHCI_TRACE1_DEVDISCONNECTED, /* EHCI ERROR: Device disconnected */ + EHCI_TRACE1_QHCREATE_FAILED, /* EHCI ERROR: jz_qh_create failed */ + EHCI_TRACE1_QTDSETUP_FAILED, /* EHCI ERROR: jz_qtd_setupphase failed */ + EHCI_TRACE1_QTDDATA_FAILED, /* EHCI ERROR: jz_qtd_dataphase failed */ + EHCI_TRACE1_QTDSTATUS_FAILED, /* EHCI ERROR: jz_qtd_statusphase failed */ + EHCI_TRACE1_TRANSFER_FAILED, /* EHCI ERROR: Transfer failed */ + EHCI_TRACE1_QHFOREACH_FAILED, /* EHCI ERROR: jz_qh_foreach failed: */ + EHCI_TRACE1_SYSERR_INTR, /* EHCI: Host System Error Interrupt */ + EHCI_TRACE1_USBERR_INTR, /* EHCI: USB Error Interrupt (USBERRINT) Interrupt */ + EHCI_TRACE1_EPALLOC_FAILED, /* EHCI ERROR: Failed to allocate EP info structure */ + EHCI_TRACE1_BADXFRTYPE, /* EHCI ERROR: Support for transfer type not implemented */ + EHCI_TRACE1_HCHALTED_TIMEOUT, /* EHCI ERROR: Timed out waiting for HCHalted */ + EHCI_TRACE1_QHPOOLALLOC_FAILED, /* EHCI ERROR: Failed to allocate the QH pool */ + EHCI_TRACE1_QTDPOOLALLOC_FAILED, /* EHCI ERROR: Failed to allocate the qTD pool */ + EHCI_TRACE1_PERFLALLOC_FAILED, /* EHCI ERROR: Failed to allocate the periodic frame list */ + EHCI_TRACE1_RESET_FAILED, /* EHCI ERROR: jz_reset failed */ + EHCI_TRACE1_RUN_FAILED, /* EHCI ERROR: EHCI Failed to run */ + EHCI_TRACE1_IRQATTACH_FAILED, /* EHCI ERROR: Failed to attach IRQ */ + +#ifdef HAVE_USBHOST_TRACE_VERBOSE + EHCI_VTRACE1_PORTSC_CSC, /* EHCI Connect Status Change */ + EHCI_VTRACE1_PORTSC_CONNALREADY, /* EHCI Already connected */ + EHCI_VTRACE1_PORTSC_DISCALREADY, /* EHCI Already disconnected */ + EHCI_VTRACE1_TOPHALF, /* EHCI Interrupt top half */ + EHCI_VTRACE1_AAINTR, /* EHCI Async Advance Interrupt */ + EHCI_VTRACE1_USBINTR, /* EHCI USB Interrupt (USBINT) Interrupt */ + EHCI_VTRACE1_CLASSENUM, /* EHCI Enumerate the device */ + EHCI_VTRACE1_ENUM_DISCONN, /* EHCI Enumeration not connected */ + EHCI_VTRACE1_INITIALIZING, /* EHCI Initializing EHCI Stack */ + EHCI_VTRACE1_HCCPARAMS, /* EHCI HCCPARAMS */ + EHCI_VTRACE1_INIITIALIZED, /* EHCI USB EHCI Initialized */ +#endif +#endif + + __TRACE1_NSTRINGS, /* Separates the format 1 from the format 2 strings */ + +#ifdef CONFIG_JZ4780_OHCI + OHCI_TRACE2_BADTDSTATUS, /* OHCI ERROR: RHport Bad TD completion status */ + OHCI_TRACE2_WHDTDSTATUS, /* OHCI ERROR: WDH Bad TD completion status */ + OHCI_TRACE2_EP0ENQUEUE_FAILED, /* OHCI ERROR: RHport Failed to enqueue EP0 */ + OHCI_TRACE2_EDENQUEUE_FAILED, /* OHCI ERROR: Failed to queue ED for transfer type */ + OHCI_TRACE2_CLASSENUM_FAILED, /* OHCI usbhost_enumerate() failed */ + +#ifdef HAVE_USBHOST_TRACE_VERBOSE + OHCI_VTRACE2_EP0CONFIG, /* OHCI EP0 configuration */ + OHCI_VTRACE2_INTERVAL, /* OHCI interval */ + OHCI_VTRACE2_MININTERVAL, /* OHCI MIN interval/offset */ + OHCI_VTRACE2_RHPORTST, /* OHCI RHPORTST */ + OHCI_VTRACE2_CONNECTED, /* OHCI RHPort connected */ + OHCI_VTRACE2_DISCONNECTED, /* OHCI RHPort disconnected */ + OHCI_VTRACE2_WAKEUP, /* OHCI RHPort connected wakeup */ + OHCI_VTRACE2_EP0CTRLED, /* OHCI RHPort EP0 CTRL */ + OHCI_VTRACE2_EPALLOC, /* OHCI EP CTRL */ + OHCI_VTRACE2_CTRLIN, /* OHCI CTRLIN */ + OHCI_VTRACE2_CTRLOUT, /* OHCI CTRLOUT */ + OHCI_VTRACE2_TRANSFER, /* OHCI EP buflen */ + OHCI_VTRACE2_INITCONNECTED, /* OHCI RHPort Device connected */ +#ifdef CONFIG_USBHOST_HUB + OHCI_VTRACE2_HUBWAKEUP, /* EHCI Hub Port connected wakeup */ +#endif +#endif +#endif + +#ifdef CONFIG_JZ4780_EHCI + EHCI_TRACE2_EPSTALLED, /* EHCI EP Stalled */ + EHCI_TRACE2_EPIOERROR, /* EHCI ERROR: EP TOKEN */ + EHCI_TRACE2_CLASSENUM_FAILED, /* EHCI usbhost_enumerate() failed */ + +#ifdef HAVE_USBHOST_TRACE_VERBOSE + EHCI_VTRACE2_EP0CONFIG, /* EHCI EP0 configuration */ + EHCI_VTRACE2_ASYNCXFR, /* EHCI Async transfer */ + EHCI_VTRACE2_INTRXFR, /* EHCI Interrupt Transfer */ + EHCI_VTRACE2_IOCCHECK, /* EHCI IOC */ + EHCI_VTRACE2_PORTSC, /* EHCI PORTSC */ + EHCI_VTRACE2_PORTSC_CONNECTED, /* EHCI RHPort connected */ + EHCI_VTRACE2_PORTSC_DISCONND, /* EHCI RHport disconnected */ + EHCI_VTRACE2_MONWAKEUP, /* EHCI RHPort connected wakeup */ + EHCI_VTRACE2_EPALLOC, /* EHCI EPALLOC */ + EHCI_VTRACE2_CTRLINOUT, /* EHCI CTRLIN/OUT */ + EHCI_VTRACE2_HCIVERSION, /* EHCI HCIVERSION */ + EHCI_VTRACE2_HCSPARAMS, /* EHCI HCSPARAMS */ +#endif +#endif + + __TRACE2_NSTRINGS /* Total number of enumeration values */ +}; + +# define TRACE1_FIRST ((int)__TRACE1_BASEVALUE + 1) +# define TRACE1_INDEX(id) ((int)(id) - TRACE1_FIRST) +# define TRACE1_NSTRINGS TRACE1_INDEX(__TRACE1_NSTRINGS) + +# define TRACE2_FIRST ((int)__TRACE1_NSTRINGS + 1) +# define TRACE2_INDEX(id) ((int)(id) - TRACE2_FIRST) +# define TRACE2_NSTRINGS TRACE2_INDEX(__TRACE2_NSTRINGS) + +#endif + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#ifndef __ASSEMBLY__ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Public Functions Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: jz_ohci_initialize + * + * Description: + * Initialize USB OHCI host controller hardware. + * + * Input Parameters: + * controller -- If the device supports more than one USB OHCI interface, + * 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_JZ4780_OHCI +struct usbhost_connection_s; +struct usbhost_connection_s *jz_ohci_initialize(int controller); +#endif + +/**************************************************************************** + * Name: jz_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 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_JZ4780_EHCI +struct usbhost_connection_s; +struct usbhost_connection_s *jz_ehci_initialize(int controller); +#endif + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __ASSEMBLY__ */ +#endif /* CONFIG_USBHOST */ +#endif /* __ARCH_MIPS_SRC_JZ4780_JZ_USBHOST_H */ diff --git a/boards/mips/jz4780/ci20/configs/jumbo/defconfig b/boards/mips/jz4780/ci20/configs/jumbo/defconfig new file mode 100644 index 0000000000000..d1701c02b93f2 --- /dev/null +++ b/boards/mips/jz4780/ci20/configs/jumbo/defconfig @@ -0,0 +1,95 @@ +# +# This file is autogenerated: PLEASE DO NOT EDIT IT. +# +# You can use "make menuconfig" to make any modifications to the installed .config file. +# You can then do "make savedefconfig" to generate a new defconfig file that includes your +# modifications. +# +CONFIG_16550_ADDRWIDTH=32 +CONFIG_16550_REGWIDTH=32 +CONFIG_16550_SUPRESS_CONFIG=y +CONFIG_16550_UART0=y +CONFIG_16550_UART0_BASE=0xB0030000 +CONFIG_16550_UART0_CLOCK=14745600 +CONFIG_16550_UART0_IRQ=51 +CONFIG_16550_UART0_SERIAL_CONSOLE=y +CONFIG_16550_UART=y +CONFIG_ALLOW_BSD_COMPONENTS=y +CONFIG_ARCH="mips" +CONFIG_ARCH_BOARD="ci20" +CONFIG_ARCH_BOARD_CI20=y +CONFIG_ARCH_CHIP="jz4780" +CONFIG_ARCH_CHIP_JZ4780=y +CONFIG_ARCH_MIPS=y +CONFIG_ARCH_STACKDUMP=y +CONFIG_BOARD_LOOPSPERMSEC=72450 +CONFIG_BUILTIN=y +CONFIG_DEBUG_FEATURES=y +CONFIG_DEBUG_FS=y +CONFIG_DEBUG_FS_ERROR=y +CONFIG_DEBUG_FS_WARN=y +CONFIG_DEBUG_FULLOPT=y +CONFIG_DEBUG_SYMBOLS=y +CONFIG_DM9X_BASE=0xB6000000 +CONFIG_DM9X_BUSWIDTH8=y +CONFIG_DM9X_IRQ=13 +CONFIG_DRIVERS_VIDEO=y +CONFIG_EXAMPLES_FB=y +CONFIG_EXAMPLES_HIDKBD=y +CONFIG_EXAMPLES_TOUCHSCREEN=y +CONFIG_EXAMPLES_TOUCHSCREEN_MOUSE=y +CONFIG_FAT_LCNAMES=y +CONFIG_FAT_LFN=y +CONFIG_FS_FAT=y +CONFIG_FS_PROCFS=y +CONFIG_GRAN=y +CONFIG_HIDKBD_NOGETREPORT=y +CONFIG_HIDMOUSE_XMAX=1359 +CONFIG_HIDMOUSE_YMAX=767 +CONFIG_IDLETHREAD_STACKSIZE=2048 +CONFIG_INIT_ENTRYPOINT="nsh_main" +CONFIG_JZ4780_EHCI=y +CONFIG_JZ4780_LCD=y +CONFIG_JZ4780_OHCI=y +CONFIG_LIBC_EXECFUNCS=y +CONFIG_MQ_MAXMSGSIZE=64 +CONFIG_NET=y +CONFIG_NETDEV_IFINDEX=y +CONFIG_NETDEV_PHY_IOCTL=y +CONFIG_NETINIT_DRIPADDR=0xc0a80250 +CONFIG_NETINIT_IPADDR=0xc0a80203 +CONFIG_NETUTILS_TELNETD=y +CONFIG_NET_DM90x0=y +CONFIG_NET_ICMP_SOCKET=y +CONFIG_NET_TCP=y +CONFIG_NET_UDP=y +CONFIG_NSH_BUILTIN_APPS=y +CONFIG_NSH_FILEIOSIZE=512 +CONFIG_NSH_READLINE=y +CONFIG_PREALLOC_MQ_MSGS=4 +CONFIG_PREALLOC_TIMERS=4 +CONFIG_RAM_SIZE=33554432 +CONFIG_RAM_START=0x80000000 +CONFIG_READLINE_CMD_HISTORY=y +CONFIG_READLINE_TABCOMPLETION=y +CONFIG_RR_INTERVAL=200 +CONFIG_SCHED_LPWORK=y +CONFIG_SCHED_LPWORKPRIORITY=50 +CONFIG_SCHED_WAITPID=y +CONFIG_START_DAY=7 +CONFIG_START_MONTH=7 +CONFIG_START_YEAR=2026 +CONFIG_SYSTEM_NSH=y +CONFIG_SYSTEM_PING=y +CONFIG_TASK_NAME_SIZE=0 +CONFIG_UBOOT_UIMAGE=y +CONFIG_UIMAGE_ENTRY_POINT=0x800004AC +CONFIG_UIMAGE_LOAD_ADDRESS=0x80000180 +CONFIG_USBHOST_HIDKBD=y +CONFIG_USBHOST_HIDMOUSE=y +CONFIG_USBHOST_HUB=y +CONFIG_USBHOST_HUB_POLLMSEC=100 +CONFIG_USBHOST_MSC=y +CONFIG_USBHOST_NPREALLOC=8 +CONFIG_VIDEO_FB=y +CONFIG_WQUEUE_NOTIFIER=y diff --git a/boards/mips/jz4780/ci20/include/board.h b/boards/mips/jz4780/ci20/include/board.h index c17307a01e53e..e3fe631678ed0 100644 --- a/boards/mips/jz4780/ci20/include/board.h +++ b/boards/mips/jz4780/ci20/include/board.h @@ -38,6 +38,7 @@ ****************************************************************************/ #define BOARD_ETH_FLG (1 << 19) +#define POWER_LED (GPIO_MODE_OUTPUT0 | GPIO_PORTF | GPIO_PIN15) /**************************************************************************** * Public Types @@ -61,6 +62,8 @@ extern "C" * Public Function Prototypes ****************************************************************************/ +int jz_usbhost_initialize(void); + #undef EXTERN #ifdef __cplusplus } diff --git a/boards/mips/jz4780/ci20/scripts/mips-ci.ld b/boards/mips/jz4780/ci20/scripts/mips-ci.ld index efe7114bc2227..9ef9e9845bd30 100644 --- a/boards/mips/jz4780/ci20/scripts/mips-ci.ld +++ b/boards/mips/jz4780/ci20/scripts/mips-ci.ld @@ -24,7 +24,7 @@ MEMORY { - kseg0_progmem (rx) : ORIGIN = 0x80020000, LENGTH = 32M + kseg0_progmem (rx) : ORIGIN = 0x80020000, LENGTH = 0x0F7E0000 kseg0_genexcpt (rx) : ORIGIN = 0x80000180, LENGTH = 128 kseg0_ebexcpt0 (rx) : ORIGIN = 0x80000200, LENGTH = 32 diff --git a/boards/mips/jz4780/ci20/scripts/mips-debug.ld b/boards/mips/jz4780/ci20/scripts/mips-debug.ld index d99172a9be3e4..819d28fc88e00 100644 --- a/boards/mips/jz4780/ci20/scripts/mips-debug.ld +++ b/boards/mips/jz4780/ci20/scripts/mips-debug.ld @@ -24,7 +24,7 @@ MEMORY { - kseg0_progmem (rx) : ORIGIN = 0x80020000, LENGTH = 32M + kseg0_progmem (rx) : ORIGIN = 0x80020000, LENGTH = 0x0F7E0000 kseg0_genexcpt (rx) : ORIGIN = 0x80000180, LENGTH = 128 kseg0_ebexcpt0 (rx) : ORIGIN = 0x80000200, LENGTH = 32 diff --git a/boards/mips/jz4780/ci20/src/Makefile b/boards/mips/jz4780/ci20/src/Makefile index 0201344af99e5..13f820e9f1c4b 100644 --- a/boards/mips/jz4780/ci20/src/Makefile +++ b/boards/mips/jz4780/ci20/src/Makefile @@ -25,4 +25,8 @@ ASRCS = CSRCS = jz4780_boot.c jz4780_bringup.c ci20_network.c +ifeq ($(CONFIG_USBHOST),y) +CSRCS += jz4780_usb.c +endif + include $(TOPDIR)/boards/Board.mk diff --git a/boards/mips/jz4780/ci20/src/jz4780_bringup.c b/boards/mips/jz4780/ci20/src/jz4780_bringup.c index 7282d79cad475..4ad3167598d9b 100644 --- a/boards/mips/jz4780/ci20/src/jz4780_bringup.c +++ b/boards/mips/jz4780/ci20/src/jz4780_bringup.c @@ -28,15 +28,18 @@ #include #include -#include #include -#include +#include #include #include #include +#ifdef CONFIG_VIDEO_FB +# include +#endif + #include "ci20.h" /**************************************************************************** @@ -73,10 +76,30 @@ int jz4780_bringup(void) ret = nx_mount(NULL, "/proc", "procfs", 0, NULL); if (ret < 0) { - syslog(LOG_ERR, "ERROR: Failed to mount procfs at /proc: %d\n", + _err("ERROR: Failed to mount procfs at /proc: %d\n", ret); } #endif +#if defined(CONFIG_USBHOST) + + ret = jz_usbhost_initialize(); + if (ret != OK) + { + _err("ERROR: Failed to start USB host services: %d\n", ret); + return ret; + } +#endif + +#ifdef CONFIG_VIDEO_FB + /* Initialize and register the framebuffer driver */ + + ret = fb_register(0, 0); + if (ret < 0) + { + _err("ERROR: fb_register() failed: %d\n", ret); + } +#endif + return ret; } diff --git a/boards/mips/jz4780/ci20/src/jz4780_usb.c b/boards/mips/jz4780/ci20/src/jz4780_usb.c new file mode 100644 index 0000000000000..f56b380cb8e2d --- /dev/null +++ b/boards/mips/jz4780/ci20/src/jz4780_usb.c @@ -0,0 +1,202 @@ +/**************************************************************************** + * boards/mips/jz4780/ci20/src/jz4780_usb.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "mips_internal.h" +#include "chip.h" +#include "jz_usbhost.h" + +#include + +#if defined(CONFIG_USBHOST) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: jz_usbhost_bootinitialize + * + * Description: + * Called from jz_boardinitialize very early in initialization to setup + * USB host-related hardware for the board. + * + * + ****************************************************************************/ + +void weak_function jz_usbhost_bootinitialize(void) +{ +} + +/**************************************************************************** + * Name: jz_usbhost_initialize + * + * Description: + * Called at application startup time to initialize the USB host + * functionality. + * This function will start a thread that will monitor for device + * connection/disconnection events. + * + ****************************************************************************/ + +int jz_usbhost_initialize(void) +{ + int ret; + +#ifdef CONFIG_JZ4780_OHCI + struct usbhost_connection_s *ohciconn; +#endif +#ifdef CONFIG_JZ4780_EHCI + struct usbhost_connection_s *ehciconn; +#endif + + /* First, register all of the class drivers needed to support the drivers + * that we care about + */ + +#ifdef CONFIG_USBHOST_HUB + /* Initialize USB hub support */ + + ret = usbhost_hub_initialize(); + if (ret < 0) + { + uerr("ERROR: usbhost_hub_initialize failed: %d\n", ret); + } +#endif + +#ifdef CONFIG_USBHOST_MSC + /* Register theUSB host Mass Storage Class */ + + ret = usbhost_msc_initialize(); + if (ret != OK) + { + uerr("ERROR: Failed to register the mass storage class: %d\n", ret); + } +#endif + +#ifdef CONFIG_USBHOST_CDCACM + /* Register the CDC/ACM serial class */ + + ret = usbhost_cdcacm_initialize(); + if (ret != OK) + { + uerr("ERROR: Failed to register the CDC/ACM serial class\n"); + } +#endif + +#ifdef CONFIG_USBHOST_HIDKBD + /* Register the USB host HID keyboard class driver */ + + ret = usbhost_kbdinit(); + if (ret != OK) + { + uerr("ERROR: Failed to register the KBD class\n"); + } +#endif + +#ifdef CONFIG_USBHOST_HIDMOUSE + /* Initialize the HID mouse class */ + + ret = usbhost_mouse_init(); + if (ret != OK) + { + uerr("ERROR: Failed to register the HID mouse class\n"); + } +#endif + +#ifdef CONFIG_JZ4780_OHCI + /* Get an instance of the USB OHCI interface. */ + + ohciconn = jz_ohci_initialize(0); + if (!ohciconn) + { + uerr("ERROR: jz_ohci_initialize failed\n"); + return ENODEV; + } + + /* Initialize waiter */ + + ret = usbhost_waiter_initialize(ohciconn); + + if (ret < 0) + { + uerr("ERROR: Failed to create ohci_waiter task: %d\n", ret); + return -ENODEV; + } +#endif + +#ifdef CONFIG_JZ4780_EHCI + /* Then get an instance of the USB EHCI interface. */ + + ehciconn = jz_ehci_initialize(0); + if (!ehciconn) + { + uerr("ERROR: jz_ehci_initialize failed\n"); + return ENODEV; + } + + /* Initialize waiter */ + + ret = usbhost_waiter_initialize(ehciconn); + + if (ret < 0) + { + uerr("ERROR: Failed to create ehci_waiter task: %d\n", ret); + return -ENODEV; + } +#endif + + return OK; +} + +#endif /* CONFIG_USBHOST */