From 757f92564b8ce9982e5eea51e4047082c0733be0 Mon Sep 17 00:00:00 2001 From: Lwazi Dube Date: Tue, 21 Jul 2026 22:13:12 -0400 Subject: [PATCH] arch/mips/jz4780: Add USB host and display controller drivers Add EHCI and OHCI USB host drivers for the Ingenic JZ4780 SoC, derived from the existing sama5 implementation. Additionally, introduce the display controller driver using code ported from FreeBSD under its original license terms. Note that this driver lacks EDID support and is currently hardcoded to 1360x768. Update the linker script memory layout to utilize the full 256 MiB RAM, excluding the 8 MiB reserved at the top of memory for the framebuffer. Signed-off-by: Lwazi Dube --- .../jz4780/boards/mips-creator-ci20/index.rst | 7 + arch/mips/include/jz4780/chip.h | 48 +- arch/mips/src/jz4780/Kconfig | 98 + arch/mips/src/jz4780/Make.defs | 18 + arch/mips/src/jz4780/jz4780_ehci.c | 5104 +++++++++++++++++ arch/mips/src/jz4780/jz4780_ehci.h | 80 + arch/mips/src/jz4780/jz4780_lcd.c | 1503 +++++ arch/mips/src/jz4780/jz4780_lowinit.c | 36 + arch/mips/src/jz4780/jz4780_ohci.c | 4177 ++++++++++++++ arch/mips/src/jz4780/jz4780_ohci.h | 102 + arch/mips/src/jz4780/jz4780_timerisr.c | 1 - arch/mips/src/jz4780/jz_usbhost.c | 425 ++ arch/mips/src/jz4780/jz_usbhost.h | 280 + .../mips/jz4780/ci20/configs/jumbo/defconfig | 95 + boards/mips/jz4780/ci20/include/board.h | 3 + boards/mips/jz4780/ci20/scripts/mips-ci.ld | 2 +- boards/mips/jz4780/ci20/scripts/mips-debug.ld | 4 +- boards/mips/jz4780/ci20/src/Makefile | 4 + boards/mips/jz4780/ci20/src/jz4780_bringup.c | 29 +- boards/mips/jz4780/ci20/src/jz4780_usb.c | 202 + 20 files changed, 12202 insertions(+), 16 deletions(-) create mode 100644 arch/mips/src/jz4780/jz4780_ehci.c create mode 100644 arch/mips/src/jz4780/jz4780_ehci.h create mode 100644 arch/mips/src/jz4780/jz4780_lcd.c create mode 100644 arch/mips/src/jz4780/jz4780_ohci.c create mode 100644 arch/mips/src/jz4780/jz4780_ohci.h create mode 100644 arch/mips/src/jz4780/jz_usbhost.c create mode 100644 arch/mips/src/jz4780/jz_usbhost.h create mode 100644 boards/mips/jz4780/ci20/configs/jumbo/defconfig create mode 100644 boards/mips/jz4780/ci20/src/jz4780_usb.c 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..0bea32685dc40 100644 --- a/boards/mips/jz4780/ci20/scripts/mips-debug.ld +++ b/boards/mips/jz4780/ci20/scripts/mips-debug.ld @@ -1,5 +1,5 @@ /**************************************************************************** - * boards/mips/jz4780/ci20/scripts/mips-debug.ld + * boards/mips/jz4780/ci20/scripts/mips-ci.ld * * SPDX-License-Identifier: Apache-2.0 * @@ -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 */