diff options
author | Angelos Mouzakitis <a.mouzakitis@virtualopensystems.com> | 2023-10-10 14:33:42 +0000 |
---|---|---|
committer | Angelos Mouzakitis <a.mouzakitis@virtualopensystems.com> | 2023-10-10 14:33:42 +0000 |
commit | af1a266670d040d2f4083ff309d732d648afba2a (patch) | |
tree | 2fc46203448ddcc6f81546d379abfaeb323575e9 /roms/u-boot/drivers/usb/gadget | |
parent | e02cda008591317b1625707ff8e115a4841aa889 (diff) |
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'roms/u-boot/drivers/usb/gadget')
47 files changed, 30786 insertions, 0 deletions
diff --git a/roms/u-boot/drivers/usb/gadget/Kconfig b/roms/u-boot/drivers/usb/gadget/Kconfig new file mode 100644 index 000000000..4a3b22e6d --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/Kconfig @@ -0,0 +1,243 @@ +# +# USB Gadget support on a system involves +# (a) a peripheral controller, and +# (b) the gadget driver using it. +# +# NOTE: Gadget support ** DOES NOT ** depend on host-side CONFIG_USB !! +# +# - Host systems (like PCs) need CONFIG_USB (with "A" jacks). +# - Peripherals (like PDAs) need CONFIG_USB_GADGET (with "B" jacks). +# - Some systems have both kinds of controllers. +# +# With help from a special transceiver and a "Mini-AB" jack, systems with +# both kinds of controller can also support "USB On-the-Go" (CONFIG_USB_OTG). +# + +menuconfig USB_GADGET + bool "USB Gadget Support" + help + USB is a master/slave protocol, organized with one master + host (such as a PC) controlling up to 127 peripheral devices. + The USB hardware is asymmetric, which makes it easier to set up: + you can't connect a "to-the-host" connector to a peripheral. + + U-Boot can run in the host, or in the peripheral. In both cases + you need a low level bus controller driver, and some software + talking to it. Peripheral controllers are often discrete silicon, + or are integrated with the CPU in a microcontroller. The more + familiar host side controllers have names like "EHCI", "OHCI", + or "UHCI", and are usually integrated into southbridges on PC + motherboards. + + Enable this configuration option if you want to run U-Boot inside + a USB peripheral device. Configure one hardware driver for your + peripheral/device side bus controller, and a "gadget driver" for + your peripheral protocol. + +if USB_GADGET + +config USB_GADGET_MANUFACTURER + string "Vendor name of the USB device" + default "Allwinner Technology" if ARCH_SUNXI + default "Rockchip" if ARCH_ROCKCHIP + default "U-Boot" + help + Vendor name of the USB device emulated, reported to the host device. + This is usually either the manufacturer of the device or the SoC. + +config USB_GADGET_VENDOR_NUM + hex "Vendor ID of the USB device" + default 0x1f3a if ARCH_SUNXI + default 0x2207 if ARCH_ROCKCHIP + default 0x0 + help + Vendor ID of the USB device emulated, reported to the host device. + This is usually the board or SoC vendor's, unless you've registered + for one. + +config USB_GADGET_PRODUCT_NUM + hex "Product ID of the USB device" + default 0x1010 if ARCH_SUNXI + default 0x310a if ROCKCHIP_RK3036 + default 0x310c if ROCKCHIP_RK3128 + default 0x320a if ROCKCHIP_RK3229 || ROCKCHIP_RK3288 + default 0x330a if ROCKCHIP_RK3328 + default 0x330c if ROCKCHIP_RK3399 + default 0x0 + help + Product ID of the USB device emulated, reported to the host device. + +config USB_GADGET_ATMEL_USBA + bool "Atmel USBA" + select USB_GADGET_DUALSPEED + help + USBA is the integrated high-speed USB Device controller on + the AT32AP700x, some AT91SAM9 and AT91CAP9 processors from Atmel. + +config USB_GADGET_BCM_UDC_OTG_PHY + bool "Broadcom UDC OTG PHY" + help + Enable the Broadcom UDC OTG physical device interface. + +config USB_GADGET_DWC2_OTG + bool "DesignWare USB2.0 HS OTG controller (gadget mode)" + select USB_GADGET_DUALSPEED + help + The Designware USB2.0 high-speed gadget controller + integrated into many SoCs. Select this option if you want the + driver to operate in Peripheral mode. This option requires + USB_GADGET to be enabled. + +if USB_GADGET_DWC2_OTG + +config USB_GADGET_DWC2_OTG_PHY_BUS_WIDTH_8 + bool "DesignWare USB2.0 HS OTG controller 8-bit PHY bus width" + help + Set the Designware USB2.0 high-speed OTG controller + PHY interface width to 8 bits, rather than the default (16 bits). + +endif # USB_GADGET_DWC2_OTG + +config USB_GADGET_OS_DESCRIPTORS + bool "USB OS Feature Descriptors support" + help + This is a porting patch from linux kernel: 37a3a533429e + ("usb: gadget: OS Feature Descriptors support"), the original commit + log see below: + There is a custom (non-USB IF) extension to the USB standard: + http://msdn.microsoft.com/library/windows/hardware/gg463182 + +config CI_UDC + bool "ChipIdea device controller" + select USB_GADGET_DUALSPEED + help + Say Y here to enable device controller functionality of the + ChipIdea driver. + +config USB_GADGET_MAX3420 + bool "MAX3420 USB Over SPI" + depends on DM_SPI + help + MAX3420, from MAXIM, implements USB-over-SPI Full-Speed device controller. + +config USB_GADGET_VBUS_DRAW + int "Maximum VBUS Power usage (2-500 mA)" + range 2 500 + default 2 + help + Some devices need to draw power from USB when they are + configured, perhaps to operate circuitry or to recharge + batteries. This is in addition to any local power supply, + such as an AC adapter or batteries. + + Enter the maximum power your device draws through USB, in + milliAmperes. The permitted range of values is 2 - 500 mA; + 0 mA would be legal, but can make some hosts misbehave. + + This value will be used except for system-specific gadget + drivers that have more specific information. + +config SDP_LOADADDR + hex "Default load address at SDP_WRITE and SDP_JUMP" + default 0 + +# Selected by UDC drivers that support high-speed operation. +config USB_GADGET_DUALSPEED + bool + +config USB_GADGET_DOWNLOAD + bool "Enable USB download gadget" + help + Composite USB download gadget support (g_dnl) for download functions. + This code works on top of composite gadget. + +if USB_GADGET_DOWNLOAD + +config USB_FUNCTION_MASS_STORAGE + bool "Enable USB mass storage gadget" + help + Enable mass storage protocol support in U-Boot. It allows exporting + the eMMC/SD card content to HOST PC so it can be mounted. + +config USB_FUNCTION_ROCKUSB + bool "Enable USB rockusb gadget" + help + Rockusb protocol is widely used by Rockchip SoC based devices. It can + read/write info, image to/from devices. This enables the USB part of + the rockusb gadget.for more detail about Rockusb protocol, please see + doc/README.rockusb + +config USB_FUNCTION_SDP + bool "Enable USB SDP (Serial Download Protocol)" + help + Enable Serial Download Protocol (SDP) device support in U-Boot. This + allows to download images into memory and execute (jump to) them + using the same protocol as implemented by the i.MX family's boot ROM. + +config USB_FUNCTION_THOR + bool "Enable USB THOR gadget" + help + Enable Tizen's THOR download protocol support in U-Boot. It + allows downloading images into memory and flash them to target device. + +endif # USB_GADGET_DOWNLOAD + +config USB_ETHER + bool "USB Ethernet Gadget" + depends on NET + default y if ARCH_SUNXI && USB_MUSB_GADGET + help + Creates an Ethernet network device through a USB peripheral + controller. This will create a network interface on both the device + (U-Boot) and the host (remote device) that can be used just like any + other nework interface. + It will bind on the peripheral USB controller, ignoring the USB hosts + controllers in the system. + +if USB_ETHER + +choice + prompt "USB Ethernet Gadget Model" + default USB_ETH_RNDIS + help + There is several models (protocols) to implement Ethernet over USB + devices. The main ones are Microsoft's RNDIS and USB's CDC-Ethernet + (also called CDC-ECM). RNDIS is obviously compatible with Windows, + while CDC-ECM is not. Most other operating systems support both, so + if inter-operability is a concern, RNDIS is to be preferred. + +config USB_ETH_CDC + bool "CDC-ECM Protocol" + help + CDC (Communications Device Class) is the standard for Ethernet over + USB devices. While there's several alternatives, the most widely used + protocol is ECM (Ethernet Control Model). However, compatibility with + Windows is not that great. + +config USB_ETH_RNDIS + bool "RNDIS Protocol" + help + The RNDIS (Remote Network Driver Interface Specification) is a + Microsoft proprietary protocol to create an Ethernet device over USB. + Windows obviously supports it, as well as all the major operating + systems, so it's the best option for compatibility. + +endchoice + +config USBNET_DEVADDR + string "USB Gadget Ethernet device mac address" + default "de:ad:be:ef:00:01" + help + Ethernet MAC address of the device-side (ie. local board's) MAC + address of the usb_ether interface + +config USBNET_HOST_ADDR + string "USB Gadget Ethernet host mac address" + default "de:ad:be:ef:00:00" + help + Ethernet MAC address of the host-side (ie. remote device's) MAC + address of the usb_ether interface + +endif # USB_ETHER + +endif # USB_GADGET diff --git a/roms/u-boot/drivers/usb/gadget/Makefile b/roms/u-boot/drivers/usb/gadget/Makefile new file mode 100644 index 000000000..f560068b4 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/Makefile @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# (C) Copyright 2000-2007 +# Wolfgang Denk, DENX Software Engineering, wd@denx.de. + +obj-$(CONFIG_USB_GADGET) += epautoconf.o config.o usbstring.o +obj-$(CONFIG_USB_ETHER) += epautoconf.o config.o usbstring.o + +ifdef CONFIG_SPL_BUILD +obj-$(CONFIG_SPL_USB_GADGET) += g_dnl.o +obj-$(CONFIG_SPL_DFU) += f_dfu.o +obj-$(CONFIG_SPL_USB_SDP_SUPPORT) += f_sdp.o +endif + +# new USB gadget layer dependencies +ifdef CONFIG_USB_GADGET +obj-$(CONFIG_USB_GADGET_AT91) += at91_udc.o +obj-$(CONFIG_USB_GADGET_ATMEL_USBA) += atmel_usba_udc.o +obj-$(CONFIG_USB_GADGET_BCM_UDC_OTG_PHY) += bcm_udc_otg_phy.o +obj-$(CONFIG_USB_GADGET_DWC2_OTG) += dwc2_udc_otg.o +obj-$(CONFIG_USB_GADGET_DWC2_OTG_PHY) += dwc2_udc_otg_phy.o +obj-$(CONFIG_USB_GADGET_FOTG210) += fotg210.o +obj-$(CONFIG_USB_GADGET_MAX3420) += max3420_udc.o +obj-$(CONFIG_CI_UDC) += ci_udc.o +ifndef CONFIG_SPL_BUILD +obj-$(CONFIG_USB_GADGET_DOWNLOAD) += g_dnl.o +obj-$(CONFIG_USB_FUNCTION_THOR) += f_thor.o +obj-$(CONFIG_DFU_OVER_USB) += f_dfu.o +obj-$(CONFIG_USB_FUNCTION_MASS_STORAGE) += f_mass_storage.o +obj-$(CONFIG_USB_FUNCTION_FASTBOOT) += f_fastboot.o +obj-$(CONFIG_USB_FUNCTION_SDP) += f_sdp.o +obj-$(CONFIG_USB_FUNCTION_ROCKUSB) += f_rockusb.o +endif +endif +ifdef CONFIG_USB_ETHER +obj-y += ether.o +obj-$(CONFIG_USB_ETH_RNDIS) += rndis.o +obj-$(CONFIG_CI_UDC) += ci_udc.o +obj-$(CONFIG_CPU_PXA25X) += pxa25x_udc.o +else +# Devices not related to the new gadget layer depend on CONFIG_USB_DEVICE +ifdef CONFIG_USB_DEVICE +obj-y += core.o +obj-y += ep0.o +obj-$(CONFIG_DW_UDC) += designware_udc.o +obj-$(CONFIG_CPU_PXA27X) += pxa27x_udc.o +endif +endif diff --git a/roms/u-boot/drivers/usb/gadget/at91_udc.c b/roms/u-boot/drivers/usb/gadget/at91_udc.c new file mode 100644 index 000000000..1feed417d --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/at91_udc.c @@ -0,0 +1,1546 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * from linux: + * c94e289f195e: usb: gadget: remove incorrect __init/__exit annotations + * + * at91_udc -- driver for at91-series USB peripheral controller + * + * Copyright (C) 2004 by Thomas Rathbone + * Copyright (C) 2005 by HP Labs + * Copyright (C) 2005 by David Brownell + */ + +#undef VERBOSE_DEBUG +#undef PACKET_TRACE + +#include <common.h> +#include <dm/devres.h> +#include <linux/bug.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <asm/io.h> +#include <asm/gpio.h> +#include <asm/hardware.h> +#include <mach/at91_matrix.h> +#include <linux/list.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb/at91_udc.h> +#include <malloc.h> + +#include "at91_udc.h" + +/* + * This controller is simple and PIO-only. It's used in many AT91-series + * full speed USB controllers, including the at91rm9200 (arm920T, with MMU), + * at91sam926x (arm926ejs, with MMU), and several no-mmu versions. + * + * This driver expects the board has been wired with two GPIOs supporting + * a VBUS sensing IRQ, and a D+ pullup. (They may be omitted, but the + * testing hasn't covered such cases.) + * + * The pullup is most important (so it's integrated on sam926x parts). It + * provides software control over whether the host enumerates the device. + * + * The VBUS sensing helps during enumeration, and allows both USB clocks + * (and the transceiver) to stay gated off until they're necessary, saving + * power. During USB suspend, the 48 MHz clock is gated off in hardware; + * it may also be gated off by software during some Linux sleep states. + */ + +#define DRIVER_VERSION "3 May 2006" + +static const char driver_name [] = "at91_udc"; +static const char * const ep_names[] = { + "ep0", + "ep1", + "ep2", + "ep3-int", + "ep4", + "ep5", +}; +#define ep0name ep_names[0] + +#define at91_udp_read(udc, reg) \ + __raw_readl((udc)->udp_baseaddr + (reg)) +#define at91_udp_write(udc, reg, val) \ + __raw_writel((val), (udc)->udp_baseaddr + (reg)) + +static struct at91_udc *controller; + +/*-------------------------------------------------------------------------*/ + +static void done(struct at91_ep *ep, struct at91_request *req, int status) +{ + unsigned stopped = ep->stopped; + struct at91_udc *udc = ep->udc; + + list_del_init(&req->queue); + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + if (status && status != -ESHUTDOWN) + VDBG("%s done %p, status %d\n", ep->ep.name, req, status); + + ep->stopped = 1; + spin_unlock(&udc->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&udc->lock); + ep->stopped = stopped; + + /* ep0 is always ready; other endpoints need a non-empty queue */ + if (list_empty(&ep->queue) && ep->int_mask != (1 << 0)) + at91_udp_write(udc, AT91_UDP_IDR, ep->int_mask); +} + +/*-------------------------------------------------------------------------*/ + +/* bits indicating OUT fifo has data ready */ +#define RX_DATA_READY (AT91_UDP_RX_DATA_BK0 | AT91_UDP_RX_DATA_BK1) + +/* + * Endpoint FIFO CSR bits have a mix of bits, making it unsafe to just write + * back most of the value you just read (because of side effects, including + * bits that may change after reading and before writing). + * + * Except when changing a specific bit, always write values which: + * - clear SET_FX bits (setting them could change something) + * - set CLR_FX bits (clearing them could change something) + * + * There are also state bits like FORCESTALL, EPEDS, DIR, and EPTYPE + * that shouldn't normally be changed. + * + * NOTE at91sam9260 docs mention synch between UDPCK and MCK clock domains, + * implying a need to wait for one write to complete (test relevant bits) + * before starting the next write. This shouldn't be an issue given how + * infrequently we write, except maybe for write-then-read idioms. + */ +#define SET_FX (AT91_UDP_TXPKTRDY) +#define CLR_FX (RX_DATA_READY | AT91_UDP_RXSETUP \ + | AT91_UDP_STALLSENT | AT91_UDP_TXCOMP) + +/* pull OUT packet data from the endpoint's fifo */ +static int read_fifo (struct at91_ep *ep, struct at91_request *req) +{ + u32 __iomem *creg = ep->creg; + u8 __iomem *dreg = ep->creg + (AT91_UDP_FDR(0) - AT91_UDP_CSR(0)); + u32 csr; + u8 *buf; + unsigned int count, bufferspace, is_done; + + buf = req->req.buf + req->req.actual; + bufferspace = req->req.length - req->req.actual; + + /* + * there might be nothing to read if ep_queue() calls us, + * or if we already emptied both pingpong buffers + */ +rescan: + csr = __raw_readl(creg); + if ((csr & RX_DATA_READY) == 0) + return 0; + + count = (csr & AT91_UDP_RXBYTECNT) >> 16; + if (count > ep->ep.maxpacket) + count = ep->ep.maxpacket; + if (count > bufferspace) { + DBG("%s buffer overflow\n", ep->ep.name); + req->req.status = -EOVERFLOW; + count = bufferspace; + } + __raw_readsb((unsigned long)dreg, buf, count); + + /* release and swap pingpong mem bank */ + csr |= CLR_FX; + if (ep->is_pingpong) { + if (ep->fifo_bank == 0) { + csr &= ~(SET_FX | AT91_UDP_RX_DATA_BK0); + ep->fifo_bank = 1; + } else { + csr &= ~(SET_FX | AT91_UDP_RX_DATA_BK1); + ep->fifo_bank = 0; + } + } else + csr &= ~(SET_FX | AT91_UDP_RX_DATA_BK0); + __raw_writel(csr, creg); + + req->req.actual += count; + is_done = (count < ep->ep.maxpacket); + if (count == bufferspace) + is_done = 1; + + PACKET("%s %p out/%d%s\n", ep->ep.name, &req->req, count, + is_done ? " (done)" : ""); + + /* + * avoid extra trips through IRQ logic for packets already in + * the fifo ... maybe preventing an extra (expensive) OUT-NAK + */ + if (is_done) + done(ep, req, 0); + else if (ep->is_pingpong) { + /* + * One dummy read to delay the code because of a HW glitch: + * CSR returns bad RXCOUNT when read too soon after updating + * RX_DATA_BK flags. + */ + csr = __raw_readl(creg); + + bufferspace -= count; + buf += count; + goto rescan; + } + + return is_done; +} + +/* load fifo for an IN packet */ +static int write_fifo(struct at91_ep *ep, struct at91_request *req) +{ + u32 __iomem *creg = ep->creg; + u32 csr = __raw_readl(creg); + u8 __iomem *dreg = ep->creg + (AT91_UDP_FDR(0) - AT91_UDP_CSR(0)); + unsigned total, count, is_last; + u8 *buf; + + /* + * TODO: allow for writing two packets to the fifo ... that'll + * reduce the amount of IN-NAKing, but probably won't affect + * throughput much. (Unlike preventing OUT-NAKing!) + */ + + /* + * If ep_queue() calls us, the queue is empty and possibly in + * odd states like TXCOMP not yet cleared (we do it, saving at + * least one IRQ) or the fifo not yet being free. Those aren't + * issues normally (IRQ handler fast path). + */ + if (unlikely(csr & (AT91_UDP_TXCOMP | AT91_UDP_TXPKTRDY))) { + if (csr & AT91_UDP_TXCOMP) { + csr |= CLR_FX; + csr &= ~(SET_FX | AT91_UDP_TXCOMP); + __raw_writel(csr, creg); + csr = __raw_readl(creg); + } + if (csr & AT91_UDP_TXPKTRDY) + return 0; + } + + buf = req->req.buf + req->req.actual; + prefetch(buf); + total = req->req.length - req->req.actual; + if (ep->ep.maxpacket < total) { + count = ep->ep.maxpacket; + is_last = 0; + } else { + count = total; + is_last = (count < ep->ep.maxpacket) || !req->req.zero; + } + + /* + * Write the packet, maybe it's a ZLP. + * + * NOTE: incrementing req->actual before we receive the ACK means + * gadget driver IN bytecounts can be wrong in fault cases. That's + * fixable with PIO drivers like this one (save "count" here, and + * do the increment later on TX irq), but not for most DMA hardware. + * + * So all gadget drivers must accept that potential error. Some + * hardware supports precise fifo status reporting, letting them + * recover when the actual bytecount matters (e.g. for USB Test + * and Measurement Class devices). + */ + __raw_writesb((unsigned long)dreg, buf, count); + csr &= ~SET_FX; + csr |= CLR_FX | AT91_UDP_TXPKTRDY; + __raw_writel(csr, creg); + req->req.actual += count; + + PACKET("%s %p in/%d%s\n", ep->ep.name, &req->req, count, + is_last ? " (done)" : ""); + if (is_last) + done(ep, req, 0); + return is_last; +} + +static void nuke(struct at91_ep *ep, int status) +{ + struct at91_request *req; + + /* terminate any request in the queue */ + ep->stopped = 1; + if (list_empty(&ep->queue)) + return; + + VDBG("%s %s\n", __func__, ep->ep.name); + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct at91_request, queue); + done(ep, req, status); + } +} + +/*-------------------------------------------------------------------------*/ + +static int at91_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct at91_ep *ep = container_of(_ep, struct at91_ep, ep); + struct at91_udc *udc; + u16 maxpacket; + u32 tmp; + unsigned long flags; + + if (!_ep || !ep + || !desc || _ep->name == ep0name + || desc->bDescriptorType != USB_DT_ENDPOINT + || (maxpacket = usb_endpoint_maxp(desc)) == 0 + || maxpacket > ep->maxpacket) { + DBG("bad ep or descriptor\n"); + return -EINVAL; + } + + udc = ep->udc; + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) { + DBG("bogus device state\n"); + return -ESHUTDOWN; + } + + tmp = usb_endpoint_type(desc); + switch (tmp) { + case USB_ENDPOINT_XFER_CONTROL: + DBG("only one control endpoint\n"); + return -EINVAL; + case USB_ENDPOINT_XFER_INT: + if (maxpacket > 64) + goto bogus_max; + break; + case USB_ENDPOINT_XFER_BULK: + switch (maxpacket) { + case 8: + case 16: + case 32: + case 64: + goto ok; + } +bogus_max: + DBG("bogus maxpacket %d\n", maxpacket); + return -EINVAL; + case USB_ENDPOINT_XFER_ISOC: + if (!ep->is_pingpong) { + DBG("iso requires double buffering\n"); + return -EINVAL; + } + break; + } + +ok: + spin_lock_irqsave(&udc->lock, flags); + + /* initialize endpoint to match this descriptor */ + ep->is_in = usb_endpoint_dir_in(desc); + ep->is_iso = (tmp == USB_ENDPOINT_XFER_ISOC); + ep->stopped = 0; + if (ep->is_in) + tmp |= 0x04; + tmp <<= 8; + tmp |= AT91_UDP_EPEDS; + __raw_writel(tmp, ep->creg); + + ep->ep.maxpacket = maxpacket; + + /* + * reset/init endpoint fifo. NOTE: leaves fifo_bank alone, + * since endpoint resets don't reset hw pingpong state. + */ + at91_udp_write(udc, AT91_UDP_RST_EP, ep->int_mask); + at91_udp_write(udc, AT91_UDP_RST_EP, 0); + + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +static int at91_ep_disable (struct usb_ep * _ep) +{ + struct at91_ep *ep = container_of(_ep, struct at91_ep, ep); + struct at91_udc *udc = ep->udc; + unsigned long flags; + + if (ep == &ep->udc->ep[0]) + return -EINVAL; + + spin_lock_irqsave(&udc->lock, flags); + + nuke(ep, -ESHUTDOWN); + + /* restore the endpoint's pristine config */ + ep->ep.desc = NULL; + ep->ep.maxpacket = ep->maxpacket; + + /* reset fifos and endpoint */ + if (ep->udc->clocked) { + at91_udp_write(udc, AT91_UDP_RST_EP, ep->int_mask); + at91_udp_write(udc, AT91_UDP_RST_EP, 0); + __raw_writel(0, ep->creg); + } + + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +/* + * this is a PIO-only driver, so there's nothing + * interesting for request or buffer allocation. + */ + +static struct usb_request * +at91_ep_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct at91_request *req; + + req = kzalloc(sizeof (struct at91_request), gfp_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + return &req->req; +} + +static void at91_ep_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct at91_request *req; + + req = container_of(_req, struct at91_request, req); + BUG_ON(!list_empty(&req->queue)); + kfree(req); +} + +static int at91_ep_queue(struct usb_ep *_ep, + struct usb_request *_req, gfp_t gfp_flags) +{ + struct at91_request *req; + struct at91_ep *ep; + struct at91_udc *udc; + int status; + unsigned long flags; + + req = container_of(_req, struct at91_request, req); + ep = container_of(_ep, struct at91_ep, ep); + + if (!_req || !_req->complete + || !_req->buf || !list_empty(&req->queue)) { + DBG("invalid request\n"); + return -EINVAL; + } + + if (!_ep || (!ep->ep.desc && ep->ep.name != ep0name)) { + DBG("invalid ep\n"); + return -EINVAL; + } + + udc = ep->udc; + + if (!udc || !udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) { + DBG("invalid device\n"); + return -EINVAL; + } + + _req->status = -EINPROGRESS; + _req->actual = 0; + + spin_lock_irqsave(&udc->lock, flags); + + /* try to kickstart any empty and idle queue */ + if (list_empty(&ep->queue) && !ep->stopped) { + int is_ep0; + + /* + * If this control request has a non-empty DATA stage, this + * will start that stage. It works just like a non-control + * request (until the status stage starts, maybe early). + * + * If the data stage is empty, then this starts a successful + * IN/STATUS stage. (Unsuccessful ones use set_halt.) + */ + is_ep0 = (ep->ep.name == ep0name); + if (is_ep0) { + u32 tmp; + + if (!udc->req_pending) { + status = -EINVAL; + goto done; + } + + /* + * defer changing CONFG until after the gadget driver + * reconfigures the endpoints. + */ + if (udc->wait_for_config_ack) { + tmp = at91_udp_read(udc, AT91_UDP_GLB_STAT); + tmp ^= AT91_UDP_CONFG; + VDBG("toggle config\n"); + at91_udp_write(udc, AT91_UDP_GLB_STAT, tmp); + } + if (req->req.length == 0) { +ep0_in_status: + PACKET("ep0 in/status\n"); + status = 0; + tmp = __raw_readl(ep->creg); + tmp &= ~SET_FX; + tmp |= CLR_FX | AT91_UDP_TXPKTRDY; + __raw_writel(tmp, ep->creg); + udc->req_pending = 0; + goto done; + } + } + + if (ep->is_in) + status = write_fifo(ep, req); + else { + status = read_fifo(ep, req); + + /* IN/STATUS stage is otherwise triggered by irq */ + if (status && is_ep0) + goto ep0_in_status; + } + } else + status = 0; + + if (req && !status) { + list_add_tail (&req->queue, &ep->queue); + at91_udp_write(udc, AT91_UDP_IER, ep->int_mask); + } +done: + spin_unlock_irqrestore(&udc->lock, flags); + return (status < 0) ? status : 0; +} + +static int at91_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct at91_ep *ep; + struct at91_request *req; + unsigned long flags; + + ep = container_of(_ep, struct at91_ep, ep); + if (!_ep || ep->ep.name == ep0name) + return -EINVAL; + + spin_lock_irqsave(&udc->lock, flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry (req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) { + spin_unlock_irqrestore(&udc->lock, flags); + return -EINVAL; + } + + done(ep, req, -ECONNRESET); + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +static int at91_ep_set_halt(struct usb_ep *_ep, int value) +{ + struct at91_ep *ep = container_of(_ep, struct at91_ep, ep); + struct at91_udc *udc = ep->udc; + u32 __iomem *creg; + u32 csr; + unsigned long flags; + int status = 0; + + if (!_ep || ep->is_iso || !ep->udc->clocked) + return -EINVAL; + + creg = ep->creg; + spin_lock_irqsave(&udc->lock, flags); + + csr = __raw_readl(creg); + + /* + * fail with still-busy IN endpoints, ensuring correct sequencing + * of data tx then stall. note that the fifo rx bytecount isn't + * completely accurate as a tx bytecount. + */ + if (ep->is_in && (!list_empty(&ep->queue) || (csr >> 16) != 0)) + status = -EAGAIN; + else { + csr |= CLR_FX; + csr &= ~SET_FX; + if (value) { + csr |= AT91_UDP_FORCESTALL; + VDBG("halt %s\n", ep->ep.name); + } else { + at91_udp_write(udc, AT91_UDP_RST_EP, ep->int_mask); + at91_udp_write(udc, AT91_UDP_RST_EP, 0); + csr &= ~AT91_UDP_FORCESTALL; + } + __raw_writel(csr, creg); + } + + spin_unlock_irqrestore(&udc->lock, flags); + return status; +} + +static const struct usb_ep_ops at91_ep_ops = { + .enable = at91_ep_enable, + .disable = at91_ep_disable, + .alloc_request = at91_ep_alloc_request, + .free_request = at91_ep_free_request, + .queue = at91_ep_queue, + .dequeue = at91_ep_dequeue, + .set_halt = at91_ep_set_halt, + /* there's only imprecise fifo status reporting */ +}; + +/*-------------------------------------------------------------------------*/ + +static int at91_get_frame(struct usb_gadget *gadget) +{ + struct at91_udc *udc = to_udc(gadget); + + if (!to_udc(gadget)->clocked) + return -EINVAL; + return at91_udp_read(udc, AT91_UDP_FRM_NUM) & AT91_UDP_NUM; +} + +static int at91_wakeup(struct usb_gadget *gadget) +{ + struct at91_udc *udc = to_udc(gadget); + u32 glbstate; + int status = -EINVAL; + unsigned long flags; + + DBG("%s\n", __func__ ); + spin_lock_irqsave(&udc->lock, flags); + + if (!udc->clocked || !udc->suspended) + goto done; + + /* NOTE: some "early versions" handle ESR differently ... */ + + glbstate = at91_udp_read(udc, AT91_UDP_GLB_STAT); + if (!(glbstate & AT91_UDP_ESR)) + goto done; + glbstate |= AT91_UDP_ESR; + at91_udp_write(udc, AT91_UDP_GLB_STAT, glbstate); + +done: + spin_unlock_irqrestore(&udc->lock, flags); + return status; +} + +/* reinit == restore initial software state */ +static void udc_reinit(struct at91_udc *udc) +{ + u32 i; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + INIT_LIST_HEAD(&udc->gadget.ep0->ep_list); + + for (i = 0; i < NUM_ENDPOINTS; i++) { + struct at91_ep *ep = &udc->ep[i]; + + if (i != 0) + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + ep->ep.desc = NULL; + ep->stopped = 0; + ep->fifo_bank = 0; + usb_ep_set_maxpacket_limit(&ep->ep, ep->maxpacket); + ep->creg = (void __iomem *) udc->udp_baseaddr + AT91_UDP_CSR(i); + /* initialize one queue per endpoint */ + INIT_LIST_HEAD(&ep->queue); + } +} + +static void reset_gadget(struct at91_udc *udc) +{ + struct usb_gadget_driver *driver = udc->driver; + int i; + + if (udc->gadget.speed == USB_SPEED_UNKNOWN) + driver = NULL; + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->suspended = 0; + + for (i = 0; i < NUM_ENDPOINTS; i++) { + struct at91_ep *ep = &udc->ep[i]; + + ep->stopped = 1; + nuke(ep, -ESHUTDOWN); + } + if (driver) { + spin_unlock(&udc->lock); + udc->driver->disconnect(&udc->gadget); + spin_lock(&udc->lock); + } + + udc_reinit(udc); +} + +static void stop_activity(struct at91_udc *udc) +{ + struct usb_gadget_driver *driver = udc->driver; + int i; + + if (udc->gadget.speed == USB_SPEED_UNKNOWN) + driver = NULL; + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->suspended = 0; + + for (i = 0; i < NUM_ENDPOINTS; i++) { + struct at91_ep *ep = &udc->ep[i]; + ep->stopped = 1; + nuke(ep, -ESHUTDOWN); + } + if (driver) { + spin_unlock(&udc->lock); + driver->disconnect(&udc->gadget); + spin_lock(&udc->lock); + } + + udc_reinit(udc); +} + +static void clk_on(struct at91_udc *udc) +{ + if (udc->clocked) + return; + udc->clocked = 1; +} + +static void clk_off(struct at91_udc *udc) +{ + if (!udc->clocked) + return; + udc->clocked = 0; + udc->gadget.speed = USB_SPEED_UNKNOWN; +} + +/* + * activate/deactivate link with host; minimize power usage for + * inactive links by cutting clocks and transceiver power. + */ +static void pullup(struct at91_udc *udc, int is_on) +{ + if (!udc->enabled || !udc->vbus) + is_on = 0; + DBG("%sactive\n", is_on ? "" : "in"); + + if (is_on) { + clk_on(udc); + at91_udp_write(udc, AT91_UDP_ICR, AT91_UDP_RXRSM); + at91_udp_write(udc, AT91_UDP_TXVC, 0); + } else { + stop_activity(udc); + at91_udp_write(udc, AT91_UDP_IDR, AT91_UDP_RXRSM); + at91_udp_write(udc, AT91_UDP_TXVC, AT91_UDP_TXVC_TXVDIS); + clk_off(udc); + } + + if (udc->caps && udc->caps->pullup) + udc->caps->pullup(udc, is_on); +} + +/* vbus is here! turn everything on that's ready */ +static int at91_vbus_session(struct usb_gadget *gadget, int is_active) +{ + struct at91_udc *udc = to_udc(gadget); + unsigned long flags; + + /* VDBG("vbus %s\n", is_active ? "on" : "off"); */ + spin_lock_irqsave(&udc->lock, flags); + udc->vbus = (is_active != 0); + if (udc->driver) + pullup(udc, is_active); + else + pullup(udc, 0); + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +static int at91_pullup(struct usb_gadget *gadget, int is_on) +{ + struct at91_udc *udc = to_udc(gadget); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + udc->enabled = is_on = !!is_on; + pullup(udc, is_on); + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +static int at91_set_selfpowered(struct usb_gadget *gadget, int is_on) +{ + struct at91_udc *udc = to_udc(gadget); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + udc->selfpowered = (is_on != 0); + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +static int at91_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver); +static int at91_stop(struct usb_gadget *gadget); + +static const struct usb_gadget_ops at91_udc_ops = { + .get_frame = at91_get_frame, + .wakeup = at91_wakeup, + .set_selfpowered = at91_set_selfpowered, + .vbus_session = at91_vbus_session, + .pullup = at91_pullup, + .udc_start = at91_start, + .udc_stop = at91_stop, + + /* + * VBUS-powered devices may also also want to support bigger + * power budgets after an appropriate SET_CONFIGURATION. + */ + /* .vbus_power = at91_vbus_power, */ +}; + +/*-------------------------------------------------------------------------*/ + +static int handle_ep(struct at91_ep *ep) +{ + struct at91_request *req; + u32 __iomem *creg = ep->creg; + u32 csr = __raw_readl(creg); + + if (!list_empty(&ep->queue)) + req = list_entry(ep->queue.next, + struct at91_request, queue); + else + req = NULL; + + if (ep->is_in) { + if (csr & (AT91_UDP_STALLSENT | AT91_UDP_TXCOMP)) { + csr |= CLR_FX; + csr &= ~(SET_FX | AT91_UDP_STALLSENT | AT91_UDP_TXCOMP); + __raw_writel(csr, creg); + } + if (req) + return write_fifo(ep, req); + + } else { + if (csr & AT91_UDP_STALLSENT) { + /* STALLSENT bit == ISOERR */ + if (ep->is_iso && req) + req->req.status = -EILSEQ; + csr |= CLR_FX; + csr &= ~(SET_FX | AT91_UDP_STALLSENT); + __raw_writel(csr, creg); + csr = __raw_readl(creg); + } + if (req && (csr & RX_DATA_READY)) + return read_fifo(ep, req); + } + return 0; +} + +union setup { + u8 raw[8]; + struct usb_ctrlrequest r; +}; + +static void handle_setup(struct at91_udc *udc, struct at91_ep *ep, u32 csr) +{ + u32 __iomem *creg = ep->creg; + u8 __iomem *dreg = ep->creg + (AT91_UDP_FDR(0) - AT91_UDP_CSR(0)); + unsigned rxcount, i = 0; + u32 tmp; + union setup pkt; + int status = 0; + + /* read and ack SETUP; hard-fail for bogus packets */ + rxcount = (csr & AT91_UDP_RXBYTECNT) >> 16; + if (likely(rxcount == 8)) { + while (rxcount--) + pkt.raw[i++] = __raw_readb(dreg); + if (pkt.r.bRequestType & USB_DIR_IN) { + csr |= AT91_UDP_DIR; + ep->is_in = 1; + } else { + csr &= ~AT91_UDP_DIR; + ep->is_in = 0; + } + } else { + /* REVISIT this happens sometimes under load; why?? */ + ERR("SETUP len %d, csr %08x\n", rxcount, csr); + status = -EINVAL; + } + csr |= CLR_FX; + csr &= ~(SET_FX | AT91_UDP_RXSETUP); + __raw_writel(csr, creg); + udc->wait_for_addr_ack = 0; + udc->wait_for_config_ack = 0; + ep->stopped = 0; + if (unlikely(status != 0)) + goto stall; + +#define w_index le16_to_cpu(pkt.r.wIndex) +#define w_value le16_to_cpu(pkt.r.wValue) +#define w_length le16_to_cpu(pkt.r.wLength) + + VDBG("SETUP %02x.%02x v%04x i%04x l%04x\n", + pkt.r.bRequestType, pkt.r.bRequest, + w_value, w_index, w_length); + + /* + * A few standard requests get handled here, ones that touch + * hardware ... notably for device and endpoint features. + */ + udc->req_pending = 1; + csr = __raw_readl(creg); + csr |= CLR_FX; + csr &= ~SET_FX; + switch ((pkt.r.bRequestType << 8) | pkt.r.bRequest) { + + case ((USB_TYPE_STANDARD|USB_RECIP_DEVICE) << 8) + | USB_REQ_SET_ADDRESS: + __raw_writel(csr | AT91_UDP_TXPKTRDY, creg); + udc->addr = w_value; + udc->wait_for_addr_ack = 1; + udc->req_pending = 0; + /* FADDR is set later, when we ack host STATUS */ + return; + + case ((USB_TYPE_STANDARD|USB_RECIP_DEVICE) << 8) + | USB_REQ_SET_CONFIGURATION: + tmp = at91_udp_read(udc, AT91_UDP_GLB_STAT) & AT91_UDP_CONFG; + if (pkt.r.wValue) + udc->wait_for_config_ack = (tmp == 0); + else + udc->wait_for_config_ack = (tmp != 0); + if (udc->wait_for_config_ack) + VDBG("wait for config\n"); + /* CONFG is toggled later, if gadget driver succeeds */ + break; + + /* + * Hosts may set or clear remote wakeup status, and + * devices may report they're VBUS powered. + */ + case ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_DEVICE) << 8) + | USB_REQ_GET_STATUS: + tmp = (udc->selfpowered << USB_DEVICE_SELF_POWERED); + if (at91_udp_read(udc, AT91_UDP_GLB_STAT) & AT91_UDP_ESR) + tmp |= (1 << USB_DEVICE_REMOTE_WAKEUP); + PACKET("get device status\n"); + __raw_writeb(tmp, dreg); + __raw_writeb(0, dreg); + goto write_in; + /* then STATUS starts later, automatically */ + case ((USB_TYPE_STANDARD|USB_RECIP_DEVICE) << 8) + | USB_REQ_SET_FEATURE: + if (w_value != USB_DEVICE_REMOTE_WAKEUP) + goto stall; + tmp = at91_udp_read(udc, AT91_UDP_GLB_STAT); + tmp |= AT91_UDP_ESR; + at91_udp_write(udc, AT91_UDP_GLB_STAT, tmp); + goto succeed; + case ((USB_TYPE_STANDARD|USB_RECIP_DEVICE) << 8) + | USB_REQ_CLEAR_FEATURE: + if (w_value != USB_DEVICE_REMOTE_WAKEUP) + goto stall; + tmp = at91_udp_read(udc, AT91_UDP_GLB_STAT); + tmp &= ~AT91_UDP_ESR; + at91_udp_write(udc, AT91_UDP_GLB_STAT, tmp); + goto succeed; + + /* + * Interfaces have no feature settings; this is pretty useless. + * we won't even insist the interface exists... + */ + case ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_INTERFACE) << 8) + | USB_REQ_GET_STATUS: + PACKET("get interface status\n"); + __raw_writeb(0, dreg); + __raw_writeb(0, dreg); + goto write_in; + /* then STATUS starts later, automatically */ + case ((USB_TYPE_STANDARD|USB_RECIP_INTERFACE) << 8) + | USB_REQ_SET_FEATURE: + case ((USB_TYPE_STANDARD|USB_RECIP_INTERFACE) << 8) + | USB_REQ_CLEAR_FEATURE: + goto stall; + + /* + * Hosts may clear bulk/intr endpoint halt after the gadget + * driver sets it (not widely used); or set it (for testing) + */ + case ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_ENDPOINT) << 8) + | USB_REQ_GET_STATUS: + tmp = w_index & USB_ENDPOINT_NUMBER_MASK; + ep = &udc->ep[tmp]; + if (tmp >= NUM_ENDPOINTS || (tmp && !ep->ep.desc)) + goto stall; + + if (tmp) { + if ((w_index & USB_DIR_IN)) { + if (!ep->is_in) + goto stall; + } else if (ep->is_in) + goto stall; + } + PACKET("get %s status\n", ep->ep.name); + if (__raw_readl(ep->creg) & AT91_UDP_FORCESTALL) + tmp = (1 << USB_ENDPOINT_HALT); + else + tmp = 0; + __raw_writeb(tmp, dreg); + __raw_writeb(0, dreg); + goto write_in; + /* then STATUS starts later, automatically */ + case ((USB_TYPE_STANDARD|USB_RECIP_ENDPOINT) << 8) + | USB_REQ_SET_FEATURE: + tmp = w_index & USB_ENDPOINT_NUMBER_MASK; + ep = &udc->ep[tmp]; + if (w_value != USB_ENDPOINT_HALT || tmp >= NUM_ENDPOINTS) + goto stall; + if (!ep->ep.desc || ep->is_iso) + goto stall; + if ((w_index & USB_DIR_IN)) { + if (!ep->is_in) + goto stall; + } else if (ep->is_in) + goto stall; + + tmp = __raw_readl(ep->creg); + tmp &= ~SET_FX; + tmp |= CLR_FX | AT91_UDP_FORCESTALL; + __raw_writel(tmp, ep->creg); + goto succeed; + case ((USB_TYPE_STANDARD|USB_RECIP_ENDPOINT) << 8) + | USB_REQ_CLEAR_FEATURE: + tmp = w_index & USB_ENDPOINT_NUMBER_MASK; + ep = &udc->ep[tmp]; + if (w_value != USB_ENDPOINT_HALT || tmp >= NUM_ENDPOINTS) + goto stall; + if (tmp == 0) + goto succeed; + if (!ep->ep.desc || ep->is_iso) + goto stall; + if ((w_index & USB_DIR_IN)) { + if (!ep->is_in) + goto stall; + } else if (ep->is_in) + goto stall; + + at91_udp_write(udc, AT91_UDP_RST_EP, ep->int_mask); + at91_udp_write(udc, AT91_UDP_RST_EP, 0); + tmp = __raw_readl(ep->creg); + tmp |= CLR_FX; + tmp &= ~(SET_FX | AT91_UDP_FORCESTALL); + __raw_writel(tmp, ep->creg); + if (!list_empty(&ep->queue)) + handle_ep(ep); + goto succeed; + } + +#undef w_value +#undef w_index +#undef w_length + + /* pass request up to the gadget driver */ + if (udc->driver) { + spin_unlock(&udc->lock); + status = udc->driver->setup(&udc->gadget, &pkt.r); + spin_lock(&udc->lock); + } + else + status = -ENODEV; + if (status < 0) { +stall: + VDBG("req %02x.%02x protocol STALL; stat %d\n", + pkt.r.bRequestType, pkt.r.bRequest, status); + csr |= AT91_UDP_FORCESTALL; + __raw_writel(csr, creg); + udc->req_pending = 0; + } + return; + +succeed: + /* immediate successful (IN) STATUS after zero length DATA */ + PACKET("ep0 in/status\n"); +write_in: + csr |= AT91_UDP_TXPKTRDY; + __raw_writel(csr, creg); + udc->req_pending = 0; +} + +static void handle_ep0(struct at91_udc *udc) +{ + struct at91_ep *ep0 = &udc->ep[0]; + u32 __iomem *creg = ep0->creg; + u32 csr = __raw_readl(creg); + struct at91_request *req; + + if (unlikely(csr & AT91_UDP_STALLSENT)) { + nuke(ep0, -EPROTO); + udc->req_pending = 0; + csr |= CLR_FX; + csr &= ~(SET_FX | AT91_UDP_STALLSENT | AT91_UDP_FORCESTALL); + __raw_writel(csr, creg); + VDBG("ep0 stalled\n"); + csr = __raw_readl(creg); + } + if (csr & AT91_UDP_RXSETUP) { + nuke(ep0, 0); + udc->req_pending = 0; + handle_setup(udc, ep0, csr); + return; + } + + if (list_empty(&ep0->queue)) + req = NULL; + else + req = list_entry(ep0->queue.next, struct at91_request, queue); + + /* host ACKed an IN packet that we sent */ + if (csr & AT91_UDP_TXCOMP) { + csr |= CLR_FX; + csr &= ~(SET_FX | AT91_UDP_TXCOMP); + + /* write more IN DATA? */ + if (req && ep0->is_in) { + if (handle_ep(ep0)) + udc->req_pending = 0; + + /* + * Ack after: + * - last IN DATA packet (including GET_STATUS) + * - IN/STATUS for OUT DATA + * - IN/STATUS for any zero-length DATA stage + * except for the IN DATA case, the host should send + * an OUT status later, which we'll ack. + */ + } else { + udc->req_pending = 0; + __raw_writel(csr, creg); + + /* + * SET_ADDRESS takes effect only after the STATUS + * (to the original address) gets acked. + */ + if (udc->wait_for_addr_ack) { + u32 tmp; + + at91_udp_write(udc, AT91_UDP_FADDR, + AT91_UDP_FEN | udc->addr); + tmp = at91_udp_read(udc, AT91_UDP_GLB_STAT); + tmp &= ~AT91_UDP_FADDEN; + if (udc->addr) + tmp |= AT91_UDP_FADDEN; + at91_udp_write(udc, AT91_UDP_GLB_STAT, tmp); + + udc->wait_for_addr_ack = 0; + VDBG("address %d\n", udc->addr); + } + } + } + + /* OUT packet arrived ... */ + else if (csr & AT91_UDP_RX_DATA_BK0) { + csr |= CLR_FX; + csr &= ~(SET_FX | AT91_UDP_RX_DATA_BK0); + + /* OUT DATA stage */ + if (!ep0->is_in) { + if (req) { + if (handle_ep(ep0)) { + /* send IN/STATUS */ + PACKET("ep0 in/status\n"); + csr = __raw_readl(creg); + csr &= ~SET_FX; + csr |= CLR_FX | AT91_UDP_TXPKTRDY; + __raw_writel(csr, creg); + udc->req_pending = 0; + } + } else if (udc->req_pending) { + /* + * AT91 hardware has a hard time with this + * "deferred response" mode for control-OUT + * transfers. (For control-IN it's fine.) + * + * The normal solution leaves OUT data in the + * fifo until the gadget driver is ready. + * We couldn't do that here without disabling + * the IRQ that tells about SETUP packets, + * e.g. when the host gets impatient... + * + * Working around it by copying into a buffer + * would almost be a non-deferred response, + * except that it wouldn't permit reliable + * stalling of the request. Instead, demand + * that gadget drivers not use this mode. + */ + DBG("no control-OUT deferred responses!\n"); + __raw_writel(csr | AT91_UDP_FORCESTALL, creg); + udc->req_pending = 0; + } + + /* STATUS stage for control-IN; ack. */ + } else { + PACKET("ep0 out/status ACK\n"); + __raw_writel(csr, creg); + + /* "early" status stage */ + if (req) + done(ep0, req, 0); + } + } +} + +static irqreturn_t at91_udc_irq(struct at91_udc *udc) +{ + u32 rescans = 5; + int disable_clock = 0; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + if (!udc->clocked) { + clk_on(udc); + disable_clock = 1; + } + + while (rescans--) { + u32 status; + + status = at91_udp_read(udc, AT91_UDP_ISR) + & at91_udp_read(udc, AT91_UDP_IMR); + if (!status) + break; + + /* USB reset irq: not maskable */ + if (status & AT91_UDP_ENDBUSRES) { + at91_udp_write(udc, AT91_UDP_IDR, ~MINIMUS_INTERRUPTUS); + at91_udp_write(udc, AT91_UDP_IER, MINIMUS_INTERRUPTUS); + /* Atmel code clears this irq twice */ + at91_udp_write(udc, AT91_UDP_ICR, AT91_UDP_ENDBUSRES); + at91_udp_write(udc, AT91_UDP_ICR, AT91_UDP_ENDBUSRES); + VDBG("end bus reset\n"); + udc->addr = 0; + reset_gadget(udc); + + /* enable ep0 */ + at91_udp_write(udc, AT91_UDP_CSR(0), + AT91_UDP_EPEDS | AT91_UDP_EPTYPE_CTRL); + udc->gadget.speed = USB_SPEED_FULL; + udc->suspended = 0; + at91_udp_write(udc, AT91_UDP_IER, AT91_UDP_EP(0)); + + /* + * NOTE: this driver keeps clocks off unless the + * USB host is present. That saves power, but for + * boards that don't support VBUS detection, both + * clocks need to be active most of the time. + */ + + /* host initiated suspend (3+ms bus idle) */ + } else if (status & AT91_UDP_RXSUSP) { + at91_udp_write(udc, AT91_UDP_IDR, AT91_UDP_RXSUSP); + at91_udp_write(udc, AT91_UDP_IER, AT91_UDP_RXRSM); + at91_udp_write(udc, AT91_UDP_ICR, AT91_UDP_RXSUSP); + /* VDBG("bus suspend\n"); */ + if (udc->suspended) + continue; + udc->suspended = 1; + + /* + * NOTE: when suspending a VBUS-powered device, the + * gadget driver should switch into slow clock mode + * and then into standby to avoid drawing more than + * 500uA power (2500uA for some high-power configs). + */ + if (udc->driver && udc->driver->suspend) { + spin_unlock(&udc->lock); + udc->driver->suspend(&udc->gadget); + spin_lock(&udc->lock); + } + + /* host initiated resume */ + } else if (status & AT91_UDP_RXRSM) { + at91_udp_write(udc, AT91_UDP_IDR, AT91_UDP_RXRSM); + at91_udp_write(udc, AT91_UDP_IER, AT91_UDP_RXSUSP); + at91_udp_write(udc, AT91_UDP_ICR, AT91_UDP_RXRSM); + /* VDBG("bus resume\n"); */ + if (!udc->suspended) + continue; + udc->suspended = 0; + + /* + * NOTE: for a VBUS-powered device, the gadget driver + * would normally want to switch out of slow clock + * mode into normal mode. + */ + if (udc->driver && udc->driver->resume) { + spin_unlock(&udc->lock); + udc->driver->resume(&udc->gadget); + spin_lock(&udc->lock); + } + + /* endpoint IRQs are cleared by handling them */ + } else { + int i; + unsigned mask = 1; + struct at91_ep *ep = &udc->ep[1]; + + if (status & mask) + handle_ep0(udc); + for (i = 1; i < NUM_ENDPOINTS; i++) { + mask <<= 1; + if (status & mask) + handle_ep(ep); + ep++; + } + } + } + + if (disable_clock) + clk_off(udc); + + spin_unlock_irqrestore(&udc->lock, flags); + + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + +static int at91_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct at91_udc *udc = controller; + + udc->driver = driver; + udc->enabled = 1; + udc->selfpowered = 1; + + return 0; +} + +static int at91_stop(struct usb_gadget *gadget) +{ + struct at91_udc *udc = controller; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + udc->enabled = 0; + at91_udp_write(udc, AT91_UDP_IDR, ~0); + spin_unlock_irqrestore(&udc->lock, flags); + + udc->driver = NULL; + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +#if defined(CONFIG_AT91SAM9260) || defined(CONFIG_AT91SAM9G20) +static int at91sam9260_udc_init(struct at91_udc *udc) +{ + struct at91_ep *ep; + int i; + + for (i = 0; i < NUM_ENDPOINTS; i++) { + ep = &udc->ep[i]; + + switch (i) { + case 0 ... 3: + ep->maxpacket = 64; + break; + case 4 ... 5: + ep->maxpacket = 512; + break; + } + } + + return 0; +} + +static void at91sam9260_udc_pullup(struct at91_udc *udc, int is_on) +{ + u32 txvc = at91_udp_read(udc, AT91_UDP_TXVC); + + if (is_on) + txvc |= AT91_UDP_TXVC_PUON; + else + txvc &= ~AT91_UDP_TXVC_PUON; + + at91_udp_write(udc, AT91_UDP_TXVC, txvc); +} + +static const struct at91_udc_caps at91sam9260_udc_caps = { + .init = at91sam9260_udc_init, + .pullup = at91sam9260_udc_pullup, +}; +#endif + +#if defined(CONFIG_AT91SAM9261) +static int at91sam9261_udc_init(struct at91_udc *udc) +{ + struct at91_ep *ep; + int i; + + for (i = 0; i < NUM_ENDPOINTS; i++) { + ep = &udc->ep[i]; + + switch (i) { + case 0: + ep->maxpacket = 8; + break; + case 1 ... 3: + ep->maxpacket = 64; + break; + case 4 ... 5: + ep->maxpacket = 256; + break; + } + } + + udc->matrix = (struct at91_matrix *)ATMEL_BASE_MATRIX; + + if (IS_ERR(udc->matrix)) + return PTR_ERR(udc->matrix); + + return 0; +} + +static void at91sam9261_udc_pullup(struct at91_udc *udc, int is_on) +{ + u32 usbpucr = 0; + + usbpucr = readl(&udc->matrix->pucr); + if (is_on) + usbpucr |= AT91_MATRIX_USBPUCR_PUON; + + writel(usbpucr, &udc->matrix->pucr); +} + +static const struct at91_udc_caps at91sam9261_udc_caps = { + .init = at91sam9261_udc_init, + .pullup = at91sam9261_udc_pullup, +}; +#endif + +int usb_gadget_handle_interrupts(int index) +{ + struct at91_udc *udc = controller; + + return at91_udc_irq(udc); +} + +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + struct at91_udc *udc = controller; + int ret; + + if (!driver || !driver->bind || !driver->setup) { + printf("bad paramter\n"); + return -EINVAL; + } + + if (udc->driver) { + printf("UDC already has a gadget driver\n"); + return -EBUSY; + } + + at91_start(&udc->gadget, driver); + + udc->driver = driver; + + ret = driver->bind(&udc->gadget); + if (ret) { + pr_err("driver->bind() returned %d\n", ret); + udc->driver = NULL; + } + + return ret; +} + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct at91_udc *udc = controller; + + if (!driver || !driver->unbind || !driver->disconnect) { + pr_err("bad paramter\n"); + return -EINVAL; + } + + driver->disconnect(&udc->gadget); + driver->unbind(&udc->gadget); + udc->driver = NULL; + + at91_stop(&udc->gadget); + + return 0; +} + +int at91_udc_probe(struct at91_udc_data *pdata) +{ + struct at91_udc *udc; + int retval; + struct at91_ep *ep; + int i; + + udc = kzalloc(sizeof(*udc), GFP_KERNEL); + if (!udc) + return -ENOMEM; + + controller = udc; + memcpy(&udc->board, pdata, sizeof(struct at91_udc_data)); + if (udc->board.vbus_pin) { + printf("%s: gpio vbus pin not supported yet.\n", __func__); + return -ENXIO; + } else { + DBG("no VBUS detection, assuming always-on\n"); + udc->vbus = 1; + } + +#if defined(CONFIG_AT91SAM9260) || defined(CONFIG_AT91SAM9G20) + udc->caps = &at91sam9260_udc_caps; +#endif + + udc->enabled = 0; + spin_lock_init(&udc->lock); + + udc->gadget.ops = &at91_udc_ops; + udc->gadget.ep0 = &udc->ep[0].ep; + udc->gadget.name = driver_name; + + for (i = 0; i < NUM_ENDPOINTS; i++) { + ep = &udc->ep[i]; + ep->ep.name = ep_names[i]; + ep->ep.ops = &at91_ep_ops; + ep->udc = udc; + ep->int_mask = (1 << i); + if (i != 0 && i != 3) + ep->is_pingpong = 1; + } + + udc->udp_baseaddr = (void *)udc->board.baseaddr; + if (IS_ERR(udc->udp_baseaddr)) + return PTR_ERR(udc->udp_baseaddr); + + if (udc->caps && udc->caps->init) { + retval = udc->caps->init(udc); + if (retval) + return retval; + } + + udc_reinit(udc); + + at91_udp_write(udc, AT91_UDP_TXVC, AT91_UDP_TXVC_TXVDIS); + at91_udp_write(udc, AT91_UDP_IDR, 0xffffffff); + /* Clear all pending interrupts - UDP may be used by bootloader. */ + at91_udp_write(udc, AT91_UDP_ICR, 0xffffffff); + + INFO("%s version %s\n", driver_name, DRIVER_VERSION); + return 0; +} diff --git a/roms/u-boot/drivers/usb/gadget/at91_udc.h b/roms/u-boot/drivers/usb/gadget/at91_udc.h new file mode 100644 index 000000000..668b8c4a7 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/at91_udc.h @@ -0,0 +1,167 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2004 by Thomas Rathbone, HP Labs + * Copyright (C) 2005 by Ivan Kokshaysky + * Copyright (C) 2006 by SAN People + */ + +#ifndef AT91_UDC_H +#define AT91_UDC_H + +/* + * USB Device Port (UDP) registers. + * Based on AT91RM9200 datasheet revision E. + */ + +#define AT91_UDP_FRM_NUM 0x00 /* Frame Number Register */ +#define AT91_UDP_NUM (0x7ff << 0) /* Frame Number */ +#define AT91_UDP_FRM_ERR (1 << 16) /* Frame Error */ +#define AT91_UDP_FRM_OK (1 << 17) /* Frame OK */ + +#define AT91_UDP_GLB_STAT 0x04 /* Global State Register */ +#define AT91_UDP_FADDEN (1 << 0) /* Function Address Enable */ +#define AT91_UDP_CONFG (1 << 1) /* Configured */ +#define AT91_UDP_ESR (1 << 2) /* Enable Send Resume */ +#define AT91_UDP_RSMINPR (1 << 3) /* Resume has been sent */ +#define AT91_UDP_RMWUPE (1 << 4) /* Remote Wake Up Enable */ + +#define AT91_UDP_FADDR 0x08 /* Function Address Register */ +#define AT91_UDP_FADD (0x7f << 0) /* Function Address Value */ +#define AT91_UDP_FEN (1 << 8) /* Function Enable */ + +#define AT91_UDP_IER 0x10 /* Interrupt Enable Register */ +#define AT91_UDP_IDR 0x14 /* Interrupt Disable Register */ +#define AT91_UDP_IMR 0x18 /* Interrupt Mask Register */ + +#define AT91_UDP_ISR 0x1c /* Interrupt Status Register */ +#define AT91_UDP_EP(n) (1 << (n)) /* Endpoint Interrupt Status */ +#define AT91_UDP_RXSUSP (1 << 8) /* USB Suspend Interrupt Status */ +#define AT91_UDP_RXRSM (1 << 9) /* USB Resume Interrupt Status */ +#define AT91_UDP_EXTRSM (1 << 10) /* External Resume Interrupt Status [AT91RM9200 only] */ +#define AT91_UDP_SOFINT (1 << 11) /* Start of Frame Interrupt Status */ +#define AT91_UDP_ENDBUSRES (1 << 12) /* End of Bus Reset Interrupt Status */ +#define AT91_UDP_WAKEUP (1 << 13) /* USB Wakeup Interrupt Status [AT91RM9200 only] */ + +#define AT91_UDP_ICR 0x20 /* Interrupt Clear Register */ +#define AT91_UDP_RST_EP 0x28 /* Reset Endpoint Register */ + +#define AT91_UDP_CSR(n) (0x30+((n)*4)) /* Endpoint Control/Status Registers 0-7 */ +#define AT91_UDP_TXCOMP (1 << 0) /* Generates IN packet with data previously written in DPR */ +#define AT91_UDP_RX_DATA_BK0 (1 << 1) /* Receive Data Bank 0 */ +#define AT91_UDP_RXSETUP (1 << 2) /* Send STALL to the host */ +#define AT91_UDP_STALLSENT (1 << 3) /* Stall Sent / Isochronous error (Isochronous endpoints) */ +#define AT91_UDP_TXPKTRDY (1 << 4) /* Transmit Packet Ready */ +#define AT91_UDP_FORCESTALL (1 << 5) /* Force Stall */ +#define AT91_UDP_RX_DATA_BK1 (1 << 6) /* Receive Data Bank 1 */ +#define AT91_UDP_DIR (1 << 7) /* Transfer Direction */ +#define AT91_UDP_EPTYPE (7 << 8) /* Endpoint Type */ +#define AT91_UDP_EPTYPE_CTRL (0 << 8) +#define AT91_UDP_EPTYPE_ISO_OUT (1 << 8) +#define AT91_UDP_EPTYPE_BULK_OUT (2 << 8) +#define AT91_UDP_EPTYPE_INT_OUT (3 << 8) +#define AT91_UDP_EPTYPE_ISO_IN (5 << 8) +#define AT91_UDP_EPTYPE_BULK_IN (6 << 8) +#define AT91_UDP_EPTYPE_INT_IN (7 << 8) +#define AT91_UDP_DTGLE (1 << 11) /* Data Toggle */ +#define AT91_UDP_EPEDS (1 << 15) /* Endpoint Enable/Disable */ +#define AT91_UDP_RXBYTECNT (0x7ff << 16) /* Number of bytes in FIFO */ + +#define AT91_UDP_FDR(n) (0x50+((n)*4)) /* Endpoint FIFO Data Registers 0-7 */ + +#define AT91_UDP_TXVC 0x74 /* Transceiver Control Register */ +#define AT91_UDP_TXVC_TXVDIS (1 << 8) /* Transceiver Disable */ +#define AT91_UDP_TXVC_PUON (1 << 9) /* PullUp On [AT91SAM9260 only] */ + +/*-------------------------------------------------------------------------*/ + +/* + * controller driver data structures + */ + +#define NUM_ENDPOINTS 6 + +/* + * hardware won't disable bus reset, or resume while the controller + * is suspended ... watching suspend helps keep the logic symmetric. + */ +#define MINIMUS_INTERRUPTUS \ + (AT91_UDP_ENDBUSRES | AT91_UDP_RXRSM | AT91_UDP_RXSUSP) + +struct at91_ep { + struct usb_ep ep; + struct list_head queue; + struct at91_udc *udc; + void __iomem *creg; + + unsigned maxpacket:16; + u8 int_mask; + unsigned is_pingpong:1; + + unsigned stopped:1; + unsigned is_in:1; + unsigned is_iso:1; + unsigned fifo_bank:1; +}; + +struct at91_udc_caps { + int (*init)(struct at91_udc *udc); + void (*pullup)(struct at91_udc *udc, int is_on); +}; + +/* + * driver is non-SMP, and just blocks IRQs whenever it needs + * access protection for chip registers or driver state + */ +struct at91_udc { + struct usb_gadget gadget; + struct at91_ep ep[NUM_ENDPOINTS]; + struct usb_gadget_driver *driver; + const struct at91_udc_caps *caps; + unsigned vbus:1; + unsigned enabled:1; + unsigned clocked:1; + unsigned suspended:1; + unsigned req_pending:1; + unsigned wait_for_addr_ack:1; + unsigned wait_for_config_ack:1; + unsigned selfpowered:1; + unsigned active_suspend:1; + u8 addr; + struct at91_udc_data board; + void __iomem *udp_baseaddr; + int udp_irq; + spinlock_t lock; + struct at91_matrix *matrix; +}; + +static inline struct at91_udc *to_udc(struct usb_gadget *g) +{ + return container_of(g, struct at91_udc, gadget); +} + +struct at91_request { + struct usb_request req; + struct list_head queue; +}; + +/*-------------------------------------------------------------------------*/ + +#ifdef VERBOSE_DEBUG +# define VDBG DBG +#else +# define VDBG(stuff...) do{}while(0) +#endif + +#ifdef PACKET_TRACE +# define PACKET VDBG +#else +# define PACKET(stuff...) do{}while(0) +#endif + +#define ERR(stuff...) debug("udc: " stuff) +#define WARNING(stuff...) debug("udc: " stuff) +#define INFO(stuff...) debug("udc: " stuff) +#define DBG(stuff...) debug("udc: " stuff) + +#endif + diff --git a/roms/u-boot/drivers/usb/gadget/atmel_usba_udc.c b/roms/u-boot/drivers/usb/gadget/atmel_usba_udc.c new file mode 100644 index 000000000..7d5182149 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/atmel_usba_udc.c @@ -0,0 +1,1304 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for the Atmel USBA high speed USB device controller + * [Original from Linux kernel: drivers/usb/gadget/atmel_usba_udc.c] + * + * Copyright (C) 2005-2013 Atmel Corporation + * Bo Shen <voice.shen@atmel.com> + */ + +#include <common.h> +#include <linux/bitops.h> +#include <linux/errno.h> +#include <asm/gpio.h> +#include <asm/hardware.h> +#include <linux/list.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb/atmel_usba_udc.h> +#include <malloc.h> + +#include "atmel_usba_udc.h" + +static int vbus_is_present(struct usba_udc *udc) +{ + /* No Vbus detection: Assume always present */ + return 1; +} + +static void next_fifo_transaction(struct usba_ep *ep, struct usba_request *req) +{ + unsigned int transaction_len; + + transaction_len = req->req.length - req->req.actual; + req->last_transaction = 1; + if (transaction_len > ep->ep.maxpacket) { + transaction_len = ep->ep.maxpacket; + req->last_transaction = 0; + } else if (transaction_len == ep->ep.maxpacket && req->req.zero) { + req->last_transaction = 0; + } + + DBG(DBG_QUEUE, "%s: submit_transaction, req %p (length %d)%s\n", + ep->ep.name, req, transaction_len, + req->last_transaction ? ", done" : ""); + + memcpy(ep->fifo, req->req.buf + req->req.actual, transaction_len); + usba_ep_writel(ep, SET_STA, USBA_TX_PK_RDY); + req->req.actual += transaction_len; +} + +static void submit_request(struct usba_ep *ep, struct usba_request *req) +{ + DBG(DBG_QUEUE, "%s: submit_request: req %p (length %d), dma: %d\n", + ep->ep.name, req, req->req.length, req->using_dma); + + req->req.actual = 0; + req->submitted = 1; + + next_fifo_transaction(ep, req); + if (req->last_transaction) { + usba_ep_writel(ep, CTL_DIS, USBA_TX_PK_RDY); + usba_ep_writel(ep, CTL_ENB, USBA_TX_COMPLETE); + } else { + usba_ep_writel(ep, CTL_DIS, USBA_TX_COMPLETE); + usba_ep_writel(ep, CTL_ENB, USBA_TX_PK_RDY); + } +} + +static void submit_next_request(struct usba_ep *ep) +{ + struct usba_request *req; + + if (list_empty(&ep->queue)) { + usba_ep_writel(ep, CTL_DIS, USBA_TX_PK_RDY | USBA_RX_BK_RDY); + return; + } + + req = list_entry(ep->queue.next, struct usba_request, queue); + if (!req->submitted) + submit_request(ep, req); +} + +static void send_status(struct usba_udc *udc, struct usba_ep *ep) +{ + ep->state = STATUS_STAGE_IN; + usba_ep_writel(ep, SET_STA, USBA_TX_PK_RDY); + usba_ep_writel(ep, CTL_ENB, USBA_TX_COMPLETE); +} + +static void receive_data(struct usba_ep *ep) +{ + struct usba_udc *udc = ep->udc; + struct usba_request *req; + unsigned long status; + unsigned int bytecount, nr_busy; + int is_complete = 0; + + status = usba_ep_readl(ep, STA); + nr_busy = USBA_BFEXT(BUSY_BANKS, status); + + DBG(DBG_QUEUE, "receive data: nr_busy=%u\n", nr_busy); + + while (nr_busy > 0) { + if (list_empty(&ep->queue)) { + usba_ep_writel(ep, CTL_DIS, USBA_RX_BK_RDY); + break; + } + req = list_entry(ep->queue.next, + struct usba_request, queue); + + bytecount = USBA_BFEXT(BYTE_COUNT, status); + + if (status & USBA_SHORT_PACKET) + is_complete = 1; + if (req->req.actual + bytecount >= req->req.length) { + is_complete = 1; + bytecount = req->req.length - req->req.actual; + } + + memcpy(req->req.buf + req->req.actual, ep->fifo, bytecount); + req->req.actual += bytecount; + + usba_ep_writel(ep, CLR_STA, USBA_RX_BK_RDY); + + if (is_complete) { + DBG(DBG_QUEUE, "%s: request done\n", ep->ep.name); + req->req.status = 0; + list_del_init(&req->queue); + usba_ep_writel(ep, CTL_DIS, USBA_RX_BK_RDY); + spin_lock(&udc->lock); + req->req.complete(&ep->ep, &req->req); + spin_unlock(&udc->lock); + } + + status = usba_ep_readl(ep, STA); + nr_busy = USBA_BFEXT(BUSY_BANKS, status); + + if (is_complete && ep_is_control(ep)) { + send_status(udc, ep); + break; + } + } +} + +static void +request_complete(struct usba_ep *ep, struct usba_request *req, int status) +{ + if (req->req.status == -EINPROGRESS) + req->req.status = status; + + DBG(DBG_GADGET | DBG_REQ, "%s: req %p complete: status %d, actual %u\n", + ep->ep.name, req, req->req.status, req->req.actual); + + req->req.complete(&ep->ep, &req->req); +} + +static void +request_complete_list(struct usba_ep *ep, struct list_head *list, int status) +{ + struct usba_request *req, *tmp_req; + + list_for_each_entry_safe(req, tmp_req, list, queue) { + list_del_init(&req->queue); + request_complete(ep, req, status); + } +} + +static int +usba_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) +{ + struct usba_ep *ep = to_usba_ep(_ep); + struct usba_udc *udc = ep->udc; + unsigned long flags = 0, ept_cfg, maxpacket; + unsigned int nr_trans; + + DBG(DBG_GADGET, "%s: ep_enable: desc=%p\n", ep->ep.name, desc); + + maxpacket = usb_endpoint_maxp(desc) & 0x7ff; + + if (((desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK) + != ep->index) || + ep->index == 0 || + desc->bDescriptorType != USB_DT_ENDPOINT || + maxpacket == 0 || + maxpacket > ep->fifo_size) { + DBG(DBG_ERR, "ep_enable: Invalid argument"); + return -EINVAL; + } + + ep->is_isoc = 0; + ep->is_in = 0; + + if (maxpacket <= 8) + ept_cfg = USBA_BF(EPT_SIZE, USBA_EPT_SIZE_8); + else + /* LSB is bit 1, not 0 */ + ept_cfg = USBA_BF(EPT_SIZE, fls(maxpacket - 1) - 3); + + DBG(DBG_HW, "%s: EPT_SIZE = %lu (maxpacket = %lu)\n", + ep->ep.name, ept_cfg, maxpacket); + + if (usb_endpoint_dir_in(desc)) { + ep->is_in = 1; + ept_cfg |= USBA_EPT_DIR_IN; + } + + switch (usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_CONTROL: + ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_CONTROL); + ept_cfg |= USBA_BF(BK_NUMBER, USBA_BK_NUMBER_ONE); + break; + case USB_ENDPOINT_XFER_ISOC: + if (!ep->can_isoc) { + DBG(DBG_ERR, "ep_enable: %s is not isoc capable\n", + ep->ep.name); + return -EINVAL; + } + + /* + * Bits 11:12 specify number of _additional_ + * transactions per microframe. + */ + nr_trans = ((usb_endpoint_maxp(desc) >> 11) & 3) + 1; + if (nr_trans > 3) + return -EINVAL; + + ep->is_isoc = 1; + ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_ISO); + + /* + * Do triple-buffering on high-bandwidth iso endpoints. + */ + if (nr_trans > 1 && ep->nr_banks == 3) + ept_cfg |= USBA_BF(BK_NUMBER, USBA_BK_NUMBER_TRIPLE); + else + ept_cfg |= USBA_BF(BK_NUMBER, USBA_BK_NUMBER_DOUBLE); + ept_cfg |= USBA_BF(NB_TRANS, nr_trans); + break; + case USB_ENDPOINT_XFER_BULK: + ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_BULK); + ept_cfg |= USBA_BF(BK_NUMBER, USBA_BK_NUMBER_ONE); + break; + case USB_ENDPOINT_XFER_INT: + ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_INT); + ept_cfg |= USBA_BF(BK_NUMBER, USBA_BK_NUMBER_ONE); + break; + } + + spin_lock_irqsave(&ep->udc->lock, flags); + + ep->desc = desc; + ep->ep.maxpacket = maxpacket; + + usba_ep_writel(ep, CFG, ept_cfg); + usba_ep_writel(ep, CTL_ENB, USBA_EPT_ENABLE); + + usba_writel(udc, INT_ENB, + (usba_readl(udc, INT_ENB) + | USBA_BF(EPT_INT, 1 << ep->index))); + + spin_unlock_irqrestore(&udc->lock, flags); + + DBG(DBG_HW, "EPT_CFG%d after init: %#08lx\n", ep->index, + (unsigned long)usba_ep_readl(ep, CFG)); + DBG(DBG_HW, "INT_ENB after init: %#08lx\n", + (unsigned long)usba_readl(udc, INT_ENB)); + + return 0; +} + +static int usba_ep_disable(struct usb_ep *_ep) +{ + struct usba_ep *ep = to_usba_ep(_ep); + struct usba_udc *udc = ep->udc; + LIST_HEAD(req_list); + unsigned long flags = 0; + + DBG(DBG_GADGET, "ep_disable: %s\n", ep->ep.name); + + spin_lock_irqsave(&udc->lock, flags); + + if (!ep->desc) { + spin_unlock_irqrestore(&udc->lock, flags); + /* REVISIT because this driver disables endpoints in + * reset_all_endpoints() before calling disconnect(), + * most gadget drivers would trigger this non-error ... + */ + if (udc->gadget.speed != USB_SPEED_UNKNOWN) + DBG(DBG_ERR, "ep_disable: %s not enabled\n", + ep->ep.name); + return -EINVAL; + } + ep->desc = NULL; + + list_splice_init(&ep->queue, &req_list); + usba_ep_writel(ep, CFG, 0); + usba_ep_writel(ep, CTL_DIS, USBA_EPT_ENABLE); + usba_writel(udc, INT_ENB, + usba_readl(udc, INT_ENB) & + ~USBA_BF(EPT_INT, 1 << ep->index)); + + request_complete_list(ep, &req_list, -ESHUTDOWN); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static struct usb_request * +usba_ep_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct usba_request *req; + + DBG(DBG_GADGET, "ep_alloc_request: %p, 0x%x\n", _ep, gfp_flags); + + req = calloc(1, sizeof(struct usba_request)); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void +usba_ep_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct usba_request *req = to_usba_req(_req); + + DBG(DBG_GADGET, "ep_free_request: %p, %p\n", _ep, _req); + + free(req); +} + +static int +usba_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) +{ + struct usba_request *req = to_usba_req(_req); + struct usba_ep *ep = to_usba_ep(_ep); + struct usba_udc *udc = ep->udc; + unsigned long flags = 0; + int ret; + + DBG(DBG_GADGET | DBG_QUEUE | DBG_REQ, "%s: queue req %p, len %u\n", + ep->ep.name, req, _req->length); + + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN || + !ep->desc) + return -ESHUTDOWN; + + req->submitted = 0; + req->using_dma = 0; + req->last_transaction = 0; + + _req->status = -EINPROGRESS; + _req->actual = 0; + + /* May have received a reset since last time we checked */ + ret = -ESHUTDOWN; + spin_lock_irqsave(&udc->lock, flags); + if (ep->desc) { + list_add_tail(&req->queue, &ep->queue); + + if ((!ep_is_control(ep) && ep->is_in) || + (ep_is_control(ep) && (ep->state == DATA_STAGE_IN || + ep->state == STATUS_STAGE_IN))) + usba_ep_writel(ep, CTL_ENB, USBA_TX_PK_RDY); + else + usba_ep_writel(ep, CTL_ENB, USBA_RX_BK_RDY); + + ret = 0; + } + spin_unlock_irqrestore(&udc->lock, flags); + + return ret; +} + +static int usba_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct usba_ep *ep = to_usba_ep(_ep); + struct usba_request *req = to_usba_req(_req); + + DBG(DBG_GADGET | DBG_QUEUE, "ep_dequeue: %s, req %p\n", + ep->ep.name, req); + + /* + * Errors should stop the queue from advancing until the + * completion function returns. + */ + list_del_init(&req->queue); + + request_complete(ep, req, -ECONNRESET); + + /* Process the next request if any */ + submit_next_request(ep); + + return 0; +} + +static int usba_ep_set_halt(struct usb_ep *_ep, int value) +{ + struct usba_ep *ep = to_usba_ep(_ep); + unsigned long flags = 0; + int ret = 0; + + DBG(DBG_GADGET, "endpoint %s: %s HALT\n", ep->ep.name, + value ? "set" : "clear"); + + if (!ep->desc) { + DBG(DBG_ERR, "Attempted to halt uninitialized ep %s\n", + ep->ep.name); + return -ENODEV; + } + + if (ep->is_isoc) { + DBG(DBG_ERR, "Attempted to halt isochronous ep %s\n", + ep->ep.name); + return -ENOTTY; + } + + spin_lock_irqsave(&udc->lock, flags); + + /* + * We can't halt IN endpoints while there are still data to be + * transferred + */ + if (!list_empty(&ep->queue) || + ((value && ep->is_in && (usba_ep_readl(ep, STA) & + USBA_BF(BUSY_BANKS, -1L))))) { + ret = -EAGAIN; + } else { + if (value) + usba_ep_writel(ep, SET_STA, USBA_FORCE_STALL); + else + usba_ep_writel(ep, CLR_STA, + USBA_FORCE_STALL | USBA_TOGGLE_CLR); + usba_ep_readl(ep, STA); + } + + spin_unlock_irqrestore(&udc->lock, flags); + + return ret; +} + +static int usba_ep_fifo_status(struct usb_ep *_ep) +{ + struct usba_ep *ep = to_usba_ep(_ep); + + return USBA_BFEXT(BYTE_COUNT, usba_ep_readl(ep, STA)); +} + +static void usba_ep_fifo_flush(struct usb_ep *_ep) +{ + struct usba_ep *ep = to_usba_ep(_ep); + struct usba_udc *udc = ep->udc; + + usba_writel(udc, EPT_RST, 1 << ep->index); +} + +static const struct usb_ep_ops usba_ep_ops = { + .enable = usba_ep_enable, + .disable = usba_ep_disable, + .alloc_request = usba_ep_alloc_request, + .free_request = usba_ep_free_request, + .queue = usba_ep_queue, + .dequeue = usba_ep_dequeue, + .set_halt = usba_ep_set_halt, + .fifo_status = usba_ep_fifo_status, + .fifo_flush = usba_ep_fifo_flush, +}; + +static int usba_udc_get_frame(struct usb_gadget *gadget) +{ + struct usba_udc *udc = to_usba_udc(gadget); + + return USBA_BFEXT(FRAME_NUMBER, usba_readl(udc, FNUM)); +} + +static int usba_udc_wakeup(struct usb_gadget *gadget) +{ + struct usba_udc *udc = to_usba_udc(gadget); + unsigned long flags = 0; + u32 ctrl; + int ret = -EINVAL; + + spin_lock_irqsave(&udc->lock, flags); + if (udc->devstatus & (1 << USB_DEVICE_REMOTE_WAKEUP)) { + ctrl = usba_readl(udc, CTRL); + usba_writel(udc, CTRL, ctrl | USBA_REMOTE_WAKE_UP); + ret = 0; + } + spin_unlock_irqrestore(&udc->lock, flags); + + return ret; +} + +static int +usba_udc_set_selfpowered(struct usb_gadget *gadget, int is_selfpowered) +{ + struct usba_udc *udc = to_usba_udc(gadget); + unsigned long flags = 0; + + spin_lock_irqsave(&udc->lock, flags); + if (is_selfpowered) + udc->devstatus |= 1 << USB_DEVICE_SELF_POWERED; + else + udc->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static const struct usb_gadget_ops usba_udc_ops = { + .get_frame = usba_udc_get_frame, + .wakeup = usba_udc_wakeup, + .set_selfpowered = usba_udc_set_selfpowered, +}; + +static struct usb_endpoint_descriptor usba_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = cpu_to_le16(64), + /* FIXME: I have no idea what to put here */ + .bInterval = 1, +}; + +/* + * Called with interrupts disabled and udc->lock held. + */ +static void reset_all_endpoints(struct usba_udc *udc) +{ + struct usba_ep *ep; + struct usba_request *req, *tmp_req; + + usba_writel(udc, EPT_RST, ~0UL); + + ep = to_usba_ep(udc->gadget.ep0); + list_for_each_entry_safe(req, tmp_req, &ep->queue, queue) { + list_del_init(&req->queue); + request_complete(ep, req, -ECONNRESET); + } + + /* NOTE: normally, the next call to the gadget driver is in + * charge of disabling endpoints... usually disconnect(). + * The exception would be entering a high speed test mode. + * + * FIXME remove this code ... and retest thoroughly. + */ + list_for_each_entry(ep, &udc->gadget.ep_list, ep.ep_list) { + if (ep->desc) { + spin_unlock(&udc->lock); + usba_ep_disable(&ep->ep); + spin_lock(&udc->lock); + } + } +} + +static struct usba_ep *get_ep_by_addr(struct usba_udc *udc, u16 wIndex) +{ + struct usba_ep *ep; + + if ((wIndex & USB_ENDPOINT_NUMBER_MASK) == 0) + return to_usba_ep(udc->gadget.ep0); + + list_for_each_entry(ep, &udc->gadget.ep_list, ep.ep_list) { + u8 bEndpointAddress; + + if (!ep->desc) + continue; + bEndpointAddress = ep->desc->bEndpointAddress; + if ((wIndex ^ bEndpointAddress) & USB_DIR_IN) + continue; + if ((bEndpointAddress & USB_ENDPOINT_NUMBER_MASK) + == (wIndex & USB_ENDPOINT_NUMBER_MASK)) + return ep; + } + + return NULL; +} + +/* Called with interrupts disabled and udc->lock held */ +static inline void set_protocol_stall(struct usba_udc *udc, struct usba_ep *ep) +{ + usba_ep_writel(ep, SET_STA, USBA_FORCE_STALL); + ep->state = WAIT_FOR_SETUP; +} + +static inline int is_stalled(struct usba_udc *udc, struct usba_ep *ep) +{ + if (usba_ep_readl(ep, STA) & USBA_FORCE_STALL) + return 1; + return 0; +} + +static inline void set_address(struct usba_udc *udc, unsigned int addr) +{ + u32 regval; + + DBG(DBG_BUS, "setting address %u...\n", addr); + regval = usba_readl(udc, CTRL); + regval = USBA_BFINS(DEV_ADDR, addr, regval); + usba_writel(udc, CTRL, regval); +} + +static int do_test_mode(struct usba_udc *udc) +{ + static const char test_packet_buffer[] = { + /* JKJKJKJK * 9 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* JJKKJJKK * 8 */ + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + /* JJKKJJKK * 8 */ + 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, + /* JJJJJJJKKKKKKK * 8 */ + 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* JJJJJJJK * 8 */ + 0x7F, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, + /* {JKKKKKKK * 10}, JK */ + 0xFC, 0x7E, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, 0x7E + }; + struct usba_ep *ep; + int test_mode; + + test_mode = udc->test_mode; + + /* Start from a clean slate */ + reset_all_endpoints(udc); + + switch (test_mode) { + case 0x0100: + /* Test_J */ + usba_writel(udc, TST, USBA_TST_J_MODE); + DBG(DBG_ALL, "Entering Test_J mode...\n"); + break; + case 0x0200: + /* Test_K */ + usba_writel(udc, TST, USBA_TST_K_MODE); + DBG(DBG_ALL, "Entering Test_K mode...\n"); + break; + case 0x0300: + /* + * Test_SE0_NAK: Force high-speed mode and set up ep0 + * for Bulk IN transfers + */ + ep = &udc->usba_ep[0]; + usba_writel(udc, TST, + USBA_BF(SPEED_CFG, USBA_SPEED_CFG_FORCE_HIGH)); + usba_ep_writel(ep, CFG, + USBA_BF(EPT_SIZE, USBA_EPT_SIZE_64) + | USBA_EPT_DIR_IN + | USBA_BF(EPT_TYPE, USBA_EPT_TYPE_BULK) + | USBA_BF(BK_NUMBER, 1)); + if (!(usba_ep_readl(ep, CFG) & USBA_EPT_MAPPED)) { + set_protocol_stall(udc, ep); + DBG(DBG_ALL, "Test_SE0_NAK: ep0 not mapped\n"); + } else { + usba_ep_writel(ep, CTL_ENB, USBA_EPT_ENABLE); + DBG(DBG_ALL, "Entering Test_SE0_NAK mode...\n"); + } + break; + case 0x0400: + /* Test_Packet */ + ep = &udc->usba_ep[0]; + usba_ep_writel(ep, CFG, + USBA_BF(EPT_SIZE, USBA_EPT_SIZE_64) + | USBA_EPT_DIR_IN + | USBA_BF(EPT_TYPE, USBA_EPT_TYPE_BULK) + | USBA_BF(BK_NUMBER, 1)); + if (!(usba_ep_readl(ep, CFG) & USBA_EPT_MAPPED)) { + set_protocol_stall(udc, ep); + DBG(DBG_ALL, "Test_Packet: ep0 not mapped\n"); + } else { + usba_ep_writel(ep, CTL_ENB, USBA_EPT_ENABLE); + usba_writel(udc, TST, USBA_TST_PKT_MODE); + memcpy(ep->fifo, test_packet_buffer, + sizeof(test_packet_buffer)); + usba_ep_writel(ep, SET_STA, USBA_TX_PK_RDY); + DBG(DBG_ALL, "Entering Test_Packet mode...\n"); + } + break; + default: + DBG(DBG_ERR, "Invalid test mode: 0x%04x\n", test_mode); + return -EINVAL; + } + + return 0; +} + +/* Avoid overly long expressions */ +static inline bool feature_is_dev_remote_wakeup(struct usb_ctrlrequest *crq) +{ + if (crq->wValue == cpu_to_le16(USB_DEVICE_REMOTE_WAKEUP)) + return true; + return false; +} + +static inline bool feature_is_dev_test_mode(struct usb_ctrlrequest *crq) +{ + if (crq->wValue == cpu_to_le16(USB_DEVICE_TEST_MODE)) + return true; + return false; +} + +static inline bool feature_is_ep_halt(struct usb_ctrlrequest *crq) +{ + if (crq->wValue == cpu_to_le16(USB_ENDPOINT_HALT)) + return true; + return false; +} + +static int handle_ep0_setup(struct usba_udc *udc, struct usba_ep *ep, + struct usb_ctrlrequest *crq) +{ + int retval = 0; + + switch (crq->bRequest) { + case USB_REQ_GET_STATUS: { + u16 status; + + if (crq->bRequestType == (USB_DIR_IN | USB_RECIP_DEVICE)) { + status = cpu_to_le16(udc->devstatus); + } else if (crq->bRequestType + == (USB_DIR_IN | USB_RECIP_INTERFACE)) { + status = cpu_to_le16(0); + } else if (crq->bRequestType + == (USB_DIR_IN | USB_RECIP_ENDPOINT)) { + struct usba_ep *target; + + target = get_ep_by_addr(udc, le16_to_cpu(crq->wIndex)); + if (!target) + goto stall; + + status = 0; + if (is_stalled(udc, target)) + status |= cpu_to_le16(1); + } else { + goto delegate; + } + + /* Write directly to the FIFO. No queueing is done. */ + if (crq->wLength != cpu_to_le16(sizeof(status))) + goto stall; + ep->state = DATA_STAGE_IN; + __raw_writew(status, ep->fifo); + usba_ep_writel(ep, SET_STA, USBA_TX_PK_RDY); + break; + } + + case USB_REQ_CLEAR_FEATURE: { + if (crq->bRequestType == USB_RECIP_DEVICE) { + if (feature_is_dev_remote_wakeup(crq)) + udc->devstatus + &= ~(1 << USB_DEVICE_REMOTE_WAKEUP); + else + /* Can't CLEAR_FEATURE TEST_MODE */ + goto stall; + } else if (crq->bRequestType == USB_RECIP_ENDPOINT) { + struct usba_ep *target; + + if (crq->wLength != cpu_to_le16(0) || + !feature_is_ep_halt(crq)) + goto stall; + target = get_ep_by_addr(udc, le16_to_cpu(crq->wIndex)); + if (!target) + goto stall; + + usba_ep_writel(target, CLR_STA, USBA_FORCE_STALL); + if (target->index != 0) + usba_ep_writel(target, CLR_STA, + USBA_TOGGLE_CLR); + } else { + goto delegate; + } + + send_status(udc, ep); + break; + } + + case USB_REQ_SET_FEATURE: { + if (crq->bRequestType == USB_RECIP_DEVICE) { + if (feature_is_dev_test_mode(crq)) { + send_status(udc, ep); + ep->state = STATUS_STAGE_TEST; + udc->test_mode = le16_to_cpu(crq->wIndex); + return 0; + } else if (feature_is_dev_remote_wakeup(crq)) { + udc->devstatus |= 1 << USB_DEVICE_REMOTE_WAKEUP; + } else { + goto stall; + } + } else if (crq->bRequestType == USB_RECIP_ENDPOINT) { + struct usba_ep *target; + + if (crq->wLength != cpu_to_le16(0) || + !feature_is_ep_halt(crq)) + goto stall; + + target = get_ep_by_addr(udc, le16_to_cpu(crq->wIndex)); + if (!target) + goto stall; + + usba_ep_writel(target, SET_STA, USBA_FORCE_STALL); + } else { + goto delegate; + } + + send_status(udc, ep); + break; + } + + case USB_REQ_SET_ADDRESS: + if (crq->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE)) + goto delegate; + + set_address(udc, le16_to_cpu(crq->wValue)); + send_status(udc, ep); + ep->state = STATUS_STAGE_ADDR; + break; + + default: +delegate: + spin_unlock(&udc->lock); + retval = udc->driver->setup(&udc->gadget, crq); + spin_lock(&udc->lock); + } + + return retval; + +stall: + DBG(DBG_ALL, "%s: Invalid setup request: %02x.%02x v%04x i%04x l%d\n", + ep->ep.name, crq->bRequestType, crq->bRequest, + le16_to_cpu(crq->wValue), le16_to_cpu(crq->wIndex), + le16_to_cpu(crq->wLength)); + set_protocol_stall(udc, ep); + + return -1; +} + +static void usba_control_irq(struct usba_udc *udc, struct usba_ep *ep) +{ + struct usba_request *req; + u32 epstatus; + u32 epctrl; + +restart: + epstatus = usba_ep_readl(ep, STA); + epctrl = usba_ep_readl(ep, CTL); + + DBG(DBG_INT, "%s [%d]: s/%08x c/%08x\n", + ep->ep.name, ep->state, epstatus, epctrl); + + req = NULL; + if (!list_empty(&ep->queue)) + req = list_entry(ep->queue.next, + struct usba_request, queue); + + if ((epctrl & USBA_TX_PK_RDY) && !(epstatus & USBA_TX_PK_RDY)) { + if (req->submitted) + next_fifo_transaction(ep, req); + else + submit_request(ep, req); + + if (req->last_transaction) { + usba_ep_writel(ep, CTL_DIS, USBA_TX_PK_RDY); + usba_ep_writel(ep, CTL_ENB, USBA_TX_COMPLETE); + } + goto restart; + } + if ((epstatus & epctrl) & USBA_TX_COMPLETE) { + usba_ep_writel(ep, CLR_STA, USBA_TX_COMPLETE); + + switch (ep->state) { + case DATA_STAGE_IN: + usba_ep_writel(ep, CTL_ENB, USBA_RX_BK_RDY); + usba_ep_writel(ep, CTL_DIS, USBA_TX_COMPLETE); + ep->state = STATUS_STAGE_OUT; + break; + case STATUS_STAGE_ADDR: + /* Activate our new address */ + usba_writel(udc, CTRL, (usba_readl(udc, CTRL) + | USBA_FADDR_EN)); + usba_ep_writel(ep, CTL_DIS, USBA_TX_COMPLETE); + ep->state = WAIT_FOR_SETUP; + break; + case STATUS_STAGE_IN: + if (req) { + list_del_init(&req->queue); + request_complete(ep, req, 0); + submit_next_request(ep); + } + usba_ep_writel(ep, CTL_DIS, USBA_TX_COMPLETE); + ep->state = WAIT_FOR_SETUP; + break; + case STATUS_STAGE_TEST: + usba_ep_writel(ep, CTL_DIS, USBA_TX_COMPLETE); + ep->state = WAIT_FOR_SETUP; + if (do_test_mode(udc)) + set_protocol_stall(udc, ep); + break; + default: + DBG(DBG_ALL, "%s: TXCOMP: Invalid endpoint state %d\n", + ep->ep.name, ep->state); + set_protocol_stall(udc, ep); + break; + } + + goto restart; + } + if ((epstatus & epctrl) & USBA_RX_BK_RDY) { + switch (ep->state) { + case STATUS_STAGE_OUT: + usba_ep_writel(ep, CLR_STA, USBA_RX_BK_RDY); + usba_ep_writel(ep, CTL_DIS, USBA_RX_BK_RDY); + + if (req) { + list_del_init(&req->queue); + request_complete(ep, req, 0); + } + ep->state = WAIT_FOR_SETUP; + break; + + case DATA_STAGE_OUT: + receive_data(ep); + break; + + default: + usba_ep_writel(ep, CLR_STA, USBA_RX_BK_RDY); + usba_ep_writel(ep, CTL_DIS, USBA_RX_BK_RDY); + DBG(DBG_ALL, "%s: RXRDY: Invalid endpoint state %d\n", + ep->ep.name, ep->state); + set_protocol_stall(udc, ep); + break; + } + + goto restart; + } + if (epstatus & USBA_RX_SETUP) { + union { + struct usb_ctrlrequest crq; + unsigned long data[2]; + } crq; + unsigned int pkt_len; + int ret; + + if (ep->state != WAIT_FOR_SETUP) { + /* + * Didn't expect a SETUP packet at this + * point. Clean up any pending requests (which + * may be successful). + */ + int status = -EPROTO; + + /* + * RXRDY and TXCOMP are dropped when SETUP + * packets arrive. Just pretend we received + * the status packet. + */ + if (ep->state == STATUS_STAGE_OUT || + ep->state == STATUS_STAGE_IN) { + usba_ep_writel(ep, CTL_DIS, USBA_RX_BK_RDY); + status = 0; + } + + if (req) { + list_del_init(&req->queue); + request_complete(ep, req, status); + } + } + + pkt_len = USBA_BFEXT(BYTE_COUNT, usba_ep_readl(ep, STA)); + DBG(DBG_HW, "Packet length: %u\n", pkt_len); + if (pkt_len != sizeof(crq)) { + DBG(DBG_ALL, "udc: Invalid length %u (expected %zu)\n", + pkt_len, sizeof(crq)); + set_protocol_stall(udc, ep); + return; + } + + DBG(DBG_FIFO, "Copying ctrl request from 0x%p:\n", ep->fifo); + memcpy(crq.data, ep->fifo, sizeof(crq)); + + /* Free up one bank in the FIFO so that we can + * generate or receive a reply right away. */ + usba_ep_writel(ep, CLR_STA, USBA_RX_SETUP); + + if (crq.crq.bRequestType & USB_DIR_IN) { + /* + * The USB 2.0 spec states that "if wLength is + * zero, there is no data transfer phase." + * However, testusb #14 seems to actually + * expect a data phase even if wLength = 0... + */ + ep->state = DATA_STAGE_IN; + } else { + if (crq.crq.wLength != cpu_to_le16(0)) + ep->state = DATA_STAGE_OUT; + else + ep->state = STATUS_STAGE_IN; + } + + ret = -1; + if (ep->index == 0) { + ret = handle_ep0_setup(udc, ep, &crq.crq); + } else { + spin_unlock(&udc->lock); + ret = udc->driver->setup(&udc->gadget, &crq.crq); + spin_lock(&udc->lock); + } + + DBG(DBG_BUS, "req %02x.%02x, length %d, state %d, ret %d\n", + crq.crq.bRequestType, crq.crq.bRequest, + le16_to_cpu(crq.crq.wLength), ep->state, ret); + + if (ret < 0) { + /* Let the host know that we failed */ + set_protocol_stall(udc, ep); + } + } +} + +static void usba_ep_irq(struct usba_udc *udc, struct usba_ep *ep) +{ + struct usba_request *req; + u32 epstatus; + u32 epctrl; + + epstatus = usba_ep_readl(ep, STA); + epctrl = usba_ep_readl(ep, CTL); + + DBG(DBG_INT, "%s: interrupt, status: 0x%08x\n", ep->ep.name, epstatus); + + while ((epctrl & USBA_TX_PK_RDY) && !(epstatus & USBA_TX_PK_RDY)) { + DBG(DBG_BUS, "%s: TX PK ready\n", ep->ep.name); + + if (list_empty(&ep->queue)) { + DBG(DBG_INT, "ep_irq: queue empty\n"); + usba_ep_writel(ep, CTL_DIS, USBA_TX_PK_RDY); + return; + } + + req = list_entry(ep->queue.next, struct usba_request, queue); + + if (req->submitted) + next_fifo_transaction(ep, req); + else + submit_request(ep, req); + + if (req->last_transaction) { + list_del_init(&req->queue); + submit_next_request(ep); + request_complete(ep, req, 0); + } + + epstatus = usba_ep_readl(ep, STA); + epctrl = usba_ep_readl(ep, CTL); + } + + if ((epstatus & epctrl) & USBA_RX_BK_RDY) { + DBG(DBG_BUS, "%s: RX data ready\n", ep->ep.name); + receive_data(ep); + } +} + +static int usba_udc_irq(struct usba_udc *udc) +{ + u32 status, ep_status; + + spin_lock(&udc->lock); + + status = usba_readl(udc, INT_STA); + DBG(DBG_INT, "irq, status=%#08x\n", status); + + if (status & USBA_DET_SUSPEND) { + usba_writel(udc, INT_CLR, USBA_DET_SUSPEND); + DBG(DBG_BUS, "Suspend detected\n"); + if (udc->gadget.speed != USB_SPEED_UNKNOWN && + udc->driver && udc->driver->suspend) { + spin_unlock(&udc->lock); + udc->driver->suspend(&udc->gadget); + spin_lock(&udc->lock); + } + } + + if (status & USBA_WAKE_UP) { + usba_writel(udc, INT_CLR, USBA_WAKE_UP); + DBG(DBG_BUS, "Wake Up CPU detected\n"); + } + + if (status & USBA_END_OF_RESUME) { + usba_writel(udc, INT_CLR, USBA_END_OF_RESUME); + DBG(DBG_BUS, "Resume detected\n"); + if (udc->gadget.speed != USB_SPEED_UNKNOWN && + udc->driver && udc->driver->resume) { + spin_unlock(&udc->lock); + udc->driver->resume(&udc->gadget); + spin_lock(&udc->lock); + } + } + + ep_status = USBA_BFEXT(EPT_INT, status); + if (ep_status) { + int i; + + for (i = 0; i < USBA_NR_ENDPOINTS; i++) + if (ep_status & (1 << i)) { + if (ep_is_control(&udc->usba_ep[i])) + usba_control_irq(udc, &udc->usba_ep[i]); + else + usba_ep_irq(udc, &udc->usba_ep[i]); + } + } + + if (status & USBA_END_OF_RESET) { + struct usba_ep *ep0; + + usba_writel(udc, INT_CLR, USBA_END_OF_RESET); + reset_all_endpoints(udc); + + if (udc->gadget.speed != USB_SPEED_UNKNOWN && + udc->driver->disconnect) { + udc->gadget.speed = USB_SPEED_UNKNOWN; + spin_unlock(&udc->lock); + udc->driver->disconnect(&udc->gadget); + spin_lock(&udc->lock); + } + + if (status & USBA_HIGH_SPEED) + udc->gadget.speed = USB_SPEED_HIGH; + else + udc->gadget.speed = USB_SPEED_FULL; + + ep0 = &udc->usba_ep[0]; + ep0->desc = &usba_ep0_desc; + ep0->state = WAIT_FOR_SETUP; + usba_ep_writel(ep0, CFG, + (USBA_BF(EPT_SIZE, EP0_EPT_SIZE) + | USBA_BF(EPT_TYPE, USBA_EPT_TYPE_CONTROL) + | USBA_BF(BK_NUMBER, USBA_BK_NUMBER_ONE))); + usba_ep_writel(ep0, CTL_ENB, + USBA_EPT_ENABLE | USBA_RX_SETUP); + usba_writel(udc, INT_ENB, + (usba_readl(udc, INT_ENB) + | USBA_BF(EPT_INT, 1) + | USBA_DET_SUSPEND + | USBA_END_OF_RESUME)); + + /* + * Unclear why we hit this irregularly, e.g. in usbtest, + * but it's clearly harmless... + */ + if (!(usba_ep_readl(ep0, CFG) & USBA_EPT_MAPPED)) + DBG(DBG_ALL, "ODD: EP0 configuration is invalid!\n"); + } + + spin_unlock(&udc->lock); + + return 0; +} + +static int atmel_usba_start(struct usba_udc *udc) +{ + udc->devstatus = 1 << USB_DEVICE_SELF_POWERED; + + udc->vbus_prev = 0; + + /* If Vbus is present, enable the controller and wait for reset */ + if (vbus_is_present(udc) && udc->vbus_prev == 0) { + usba_writel(udc, CTRL, USBA_ENABLE_MASK); + usba_writel(udc, INT_ENB, USBA_END_OF_RESET); + } + + return 0; +} + +static int atmel_usba_stop(struct usba_udc *udc) +{ + udc->gadget.speed = USB_SPEED_UNKNOWN; + reset_all_endpoints(udc); + + /* This will also disable the DP pullup */ + usba_writel(udc, CTRL, USBA_DISABLE_MASK); + + return 0; +} + +static struct usba_udc controller = { + .regs = (unsigned *)ATMEL_BASE_UDPHS, + .fifo = (unsigned *)ATMEL_BASE_UDPHS_FIFO, + .gadget = { + .ops = &usba_udc_ops, + .ep_list = LIST_HEAD_INIT(controller.gadget.ep_list), + .speed = USB_SPEED_HIGH, + .is_dualspeed = 1, + .name = "atmel_usba_udc", + }, +}; + +int usb_gadget_handle_interrupts(int index) +{ + struct usba_udc *udc = &controller; + + return usba_udc_irq(udc); +} + + +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + struct usba_udc *udc = &controller; + int ret; + + if (!driver || !driver->bind || !driver->setup) { + printf("bad paramter\n"); + return -EINVAL; + } + + if (udc->driver) { + printf("UDC already has a gadget driver\n"); + return -EBUSY; + } + + atmel_usba_start(udc); + + udc->driver = driver; + + ret = driver->bind(&udc->gadget); + if (ret) { + pr_err("driver->bind() returned %d\n", ret); + udc->driver = NULL; + } + + return ret; +} + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct usba_udc *udc = &controller; + + if (!driver || !driver->unbind || !driver->disconnect) { + pr_err("bad paramter\n"); + return -EINVAL; + } + + driver->disconnect(&udc->gadget); + driver->unbind(&udc->gadget); + udc->driver = NULL; + + atmel_usba_stop(udc); + + return 0; +} + +static struct usba_ep *usba_udc_pdata(struct usba_platform_data *pdata, + struct usba_udc *udc) +{ + struct usba_ep *eps; + int i; + + eps = malloc(sizeof(struct usba_ep) * pdata->num_ep); + if (!eps) { + pr_err("failed to alloc eps\n"); + return NULL; + } + + udc->gadget.ep0 = &eps[0].ep; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + INIT_LIST_HEAD(&eps[0].ep.ep_list); + + for (i = 0; i < pdata->num_ep; i++) { + struct usba_ep *ep = &eps[i]; + + ep->ep_regs = udc->regs + USBA_EPT_BASE(i); + ep->dma_regs = udc->regs + USBA_DMA_BASE(i); + ep->fifo = udc->fifo + USBA_FIFO_BASE(i); + ep->ep.ops = &usba_ep_ops; + ep->ep.name = pdata->ep[i].name; + ep->ep.maxpacket = pdata->ep[i].fifo_size; + ep->fifo_size = ep->ep.maxpacket; + ep->udc = udc; + INIT_LIST_HEAD(&ep->queue); + ep->nr_banks = pdata->ep[i].nr_banks; + ep->index = pdata->ep[i].index; + ep->can_dma = pdata->ep[i].can_dma; + ep->can_isoc = pdata->ep[i].can_isoc; + if (i) + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + }; + + return eps; +} + +int usba_udc_probe(struct usba_platform_data *pdata) +{ + struct usba_udc *udc; + + udc = &controller; + + udc->usba_ep = usba_udc_pdata(pdata, udc); + + return 0; +} diff --git a/roms/u-boot/drivers/usb/gadget/atmel_usba_udc.h b/roms/u-boot/drivers/usb/gadget/atmel_usba_udc.h new file mode 100644 index 000000000..f6cb48c1c --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/atmel_usba_udc.h @@ -0,0 +1,320 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Register definition for Atmel USBA high speed USB device controller + * [Original from Linux kernel: drivers/usb/gadget/atmel_usba_udc.h] + * + * Copyright (C) 2005-2013 Atmel Corporation + * Bo Shen <voice.shen@atmel.com> + */ + +#ifndef __LINUX_USB_GADGET_USBA_UDC_H__ +#define __LINUX_USB_GADGET_USBA_UDC_H__ + +/* USB register offsets */ +#define USBA_CTRL 0x0000 +#define USBA_FNUM 0x0004 +#define USBA_INT_ENB 0x0010 +#define USBA_INT_STA 0x0014 +#define USBA_INT_CLR 0x0018 +#define USBA_EPT_RST 0x001c +#define USBA_TST 0x00e0 + +/* USB endpoint register offsets */ +#define USBA_EPT_CFG 0x0000 +#define USBA_EPT_CTL_ENB 0x0004 +#define USBA_EPT_CTL_DIS 0x0008 +#define USBA_EPT_CTL 0x000c +#define USBA_EPT_SET_STA 0x0014 +#define USBA_EPT_CLR_STA 0x0018 +#define USBA_EPT_STA 0x001c + +/* USB DMA register offsets */ +#define USBA_DMA_NXT_DSC 0x0000 +#define USBA_DMA_ADDRESS 0x0004 +#define USBA_DMA_CONTROL 0x0008 +#define USBA_DMA_STATUS 0x000c + +/* Bitfields in CTRL */ +#define USBA_DEV_ADDR_OFFSET 0 +#define USBA_DEV_ADDR_SIZE 7 +#define USBA_FADDR_EN (1 << 7) +#define USBA_EN_USBA (1 << 8) +#define USBA_DETACH (1 << 9) +#define USBA_REMOTE_WAKE_UP (1 << 10) +#define USBA_PULLD_DIS (1 << 11) + +#define USBA_ENABLE_MASK (USBA_EN_USBA | USBA_PULLD_DIS) +#define USBA_DISABLE_MASK USBA_DETACH + +/* Bitfields in FNUM */ +#define USBA_MICRO_FRAME_NUM_OFFSET 0 +#define USBA_MICRO_FRAME_NUM_SIZE 3 +#define USBA_FRAME_NUMBER_OFFSET 3 +#define USBA_FRAME_NUMBER_SIZE 11 +#define USBA_FRAME_NUM_ERROR (1 << 31) + +/* Bitfields in INT_ENB/INT_STA/INT_CLR */ +#define USBA_HIGH_SPEED (1 << 0) +#define USBA_DET_SUSPEND (1 << 1) +#define USBA_MICRO_SOF (1 << 2) +#define USBA_SOF (1 << 3) +#define USBA_END_OF_RESET (1 << 4) +#define USBA_WAKE_UP (1 << 5) +#define USBA_END_OF_RESUME (1 << 6) +#define USBA_UPSTREAM_RESUME (1 << 7) +#define USBA_EPT_INT_OFFSET 8 +#define USBA_EPT_INT_SIZE 16 +#define USBA_DMA_INT_OFFSET 24 +#define USBA_DMA_INT_SIZE 8 + +/* Bitfields in EPT_RST */ +#define USBA_RST_OFFSET 0 +#define USBA_RST_SIZE 16 + +/* Bitfields in USBA_TST */ +#define USBA_SPEED_CFG_OFFSET 0 +#define USBA_SPEED_CFG_SIZE 2 +#define USBA_TST_J_MODE (1 << 2) +#define USBA_TST_K_MODE (1 << 3) +#define USBA_TST_PKT_MODE (1 << 4) +#define USBA_OPMODE2 (1 << 5) + +/* Bitfields in EPT_CFG */ +#define USBA_EPT_SIZE_OFFSET 0 +#define USBA_EPT_SIZE_SIZE 3 +#define USBA_EPT_DIR_IN (1 << 3) +#define USBA_EPT_TYPE_OFFSET 4 +#define USBA_EPT_TYPE_SIZE 2 +#define USBA_BK_NUMBER_OFFSET 6 +#define USBA_BK_NUMBER_SIZE 2 +#define USBA_NB_TRANS_OFFSET 8 +#define USBA_NB_TRANS_SIZE 2 +#define USBA_EPT_MAPPED (1 << 31) + +/* Bitfields in EPT_CTL/EPT_CTL_ENB/EPT_CTL_DIS */ +#define USBA_EPT_ENABLE (1 << 0) +#define USBA_AUTO_VALID (1 << 1) +#define USBA_INTDIS_DMA (1 << 3) +#define USBA_NYET_DIS (1 << 4) +#define USBA_DATAX_RX (1 << 6) +#define USBA_MDATA_RX (1 << 7) +/* Bits 8-15 and 31 enable interrupts for respective bits in EPT_STA */ +#define USBA_BUSY_BANK_IE (1 << 18) + +/* Bitfields in EPT_SET_STA/EPT_CLR_STA/EPT_STA */ +#define USBA_FORCE_STALL (1 << 5) +#define USBA_TOGGLE_CLR (1 << 6) +#define USBA_TOGGLE_SEQ_OFFSET 6 +#define USBA_TOGGLE_SEQ_SIZE 2 +#define USBA_ERR_OVFLW (1 << 8) +#define USBA_RX_BK_RDY (1 << 9) +#define USBA_KILL_BANK (1 << 9) +#define USBA_TX_COMPLETE (1 << 10) +#define USBA_TX_PK_RDY (1 << 11) +#define USBA_ISO_ERR_TRANS (1 << 11) +#define USBA_RX_SETUP (1 << 12) +#define USBA_ISO_ERR_FLOW (1 << 12) +#define USBA_STALL_SENT (1 << 13) +#define USBA_ISO_ERR_CRC (1 << 13) +#define USBA_ISO_ERR_NBTRANS (1 << 13) +#define USBA_NAK_IN (1 << 14) +#define USBA_ISO_ERR_FLUSH (1 << 14) +#define USBA_NAK_OUT (1 << 15) +#define USBA_CURRENT_BANK_OFFSET 16 +#define USBA_CURRENT_BANK_SIZE 2 +#define USBA_BUSY_BANKS_OFFSET 18 +#define USBA_BUSY_BANKS_SIZE 2 +#define USBA_BYTE_COUNT_OFFSET 20 +#define USBA_BYTE_COUNT_SIZE 11 +#define USBA_SHORT_PACKET (1 << 31) + +/* Bitfields in DMA_CONTROL */ +#define USBA_DMA_CH_EN (1 << 0) +#define USBA_DMA_LINK (1 << 1) +#define USBA_DMA_END_TR_EN (1 << 2) +#define USBA_DMA_END_BUF_EN (1 << 3) +#define USBA_DMA_END_TR_IE (1 << 4) +#define USBA_DMA_END_BUF_IE (1 << 5) +#define USBA_DMA_DESC_LOAD_IE (1 << 6) +#define USBA_DMA_BURST_LOCK (1 << 7) +#define USBA_DMA_BUF_LEN_OFFSET 16 +#define USBA_DMA_BUF_LEN_SIZE 16 + +/* Bitfields in DMA_STATUS */ +#define USBA_DMA_CH_ACTIVE (1 << 1) +#define USBA_DMA_END_TR_ST (1 << 4) +#define USBA_DMA_END_BUF_ST (1 << 5) +#define USBA_DMA_DESC_LOAD_ST (1 << 6) + +/* Constants for SPEED_CFG */ +#define USBA_SPEED_CFG_NORMAL 0 +#define USBA_SPEED_CFG_FORCE_HIGH 2 +#define USBA_SPEED_CFG_FORCE_FULL 3 + +/* Constants for EPT_SIZE */ +#define USBA_EPT_SIZE_8 0 +#define USBA_EPT_SIZE_16 1 +#define USBA_EPT_SIZE_32 2 +#define USBA_EPT_SIZE_64 3 +#define USBA_EPT_SIZE_128 4 +#define USBA_EPT_SIZE_256 5 +#define USBA_EPT_SIZE_512 6 +#define USBA_EPT_SIZE_1024 7 + +/* Constants for EPT_TYPE */ +#define USBA_EPT_TYPE_CONTROL 0 +#define USBA_EPT_TYPE_ISO 1 +#define USBA_EPT_TYPE_BULK 2 +#define USBA_EPT_TYPE_INT 3 + +/* Constants for BK_NUMBER */ +#define USBA_BK_NUMBER_ZERO 0 +#define USBA_BK_NUMBER_ONE 1 +#define USBA_BK_NUMBER_DOUBLE 2 +#define USBA_BK_NUMBER_TRIPLE 3 + +/* Bit manipulation macros */ +#define USBA_BF(name, value) \ + (((value) & ((1 << USBA_##name##_SIZE) - 1)) \ + << USBA_##name##_OFFSET) +#define USBA_BFEXT(name, value) \ + (((value) >> USBA_##name##_OFFSET) \ + & ((1 << USBA_##name##_SIZE) - 1)) +#define USBA_BFINS(name, value, old) \ + (((old) & ~(((1 << USBA_##name##_SIZE) - 1) \ + << USBA_##name##_OFFSET)) \ + | USBA_BF(name, value)) + +/* Register access macros */ +#define usba_readl(udc, reg) \ + __raw_readl((udc)->regs + USBA_##reg) +#define usba_writel(udc, reg, value) \ + __raw_writel((value), (udc)->regs + USBA_##reg) +#define usba_ep_readl(ep, reg) \ + __raw_readl((ep)->ep_regs + USBA_EPT_##reg) +#define usba_ep_writel(ep, reg, value) \ + __raw_writel((value), (ep)->ep_regs + USBA_EPT_##reg) +#define usba_dma_readl(ep, reg) \ + __raw_readl((ep)->dma_regs + USBA_DMA_##reg) +#define usba_dma_writel(ep, reg, value) \ + __raw_writel((value), (ep)->dma_regs + USBA_DMA_##reg) + +/* Calculate base address for a given endpoint or DMA controller */ +#define USBA_EPT_BASE(x) (0x100 + (x) * 0x20) +#define USBA_DMA_BASE(x) (0x300 + (x) * 0x10) +#define USBA_FIFO_BASE(x) ((x) << 16) + +/* Synth parameters */ +#define USBA_NR_ENDPOINTS 7 + +#define EP0_FIFO_SIZE 64 +#define EP0_EPT_SIZE USBA_EPT_SIZE_64 +#define EP0_NR_BANKS 1 + +#define DBG_ERR 0x0001 /* report all error returns */ +#define DBG_HW 0x0002 /* debug hardware initialization */ +#define DBG_GADGET 0x0004 /* calls to/from gadget driver */ +#define DBG_INT 0x0008 /* interrupts */ +#define DBG_BUS 0x0010 /* report changes in bus state */ +#define DBG_QUEUE 0x0020 /* debug request queue processing */ +#define DBG_FIFO 0x0040 /* debug FIFO contents */ +#define DBG_DMA 0x0080 /* debug DMA handling */ +#define DBG_REQ 0x0100 /* print out queued request length */ +#define DBG_ALL 0xffff +#define DBG_NONE 0x0000 + +#define DEBUG_LEVEL (DBG_ERR) + +#define DBG(level, fmt, ...) \ + do { \ + if ((level) & DEBUG_LEVEL) \ + debug("udc: " fmt, ## __VA_ARGS__); \ + } while (0) + +enum usba_ctrl_state { + WAIT_FOR_SETUP, + DATA_STAGE_IN, + DATA_STAGE_OUT, + STATUS_STAGE_IN, + STATUS_STAGE_OUT, + STATUS_STAGE_ADDR, + STATUS_STAGE_TEST, +}; + +struct usba_dma_desc { + dma_addr_t next; + dma_addr_t addr; + u32 ctrl; +}; + +struct usba_ep { + int state; + void *ep_regs; + void *dma_regs; + void *fifo; + struct usb_ep ep; + struct usba_udc *udc; + + struct list_head queue; + + u16 fifo_size; + u8 nr_banks; + u8 index; + unsigned int can_dma:1; + unsigned int can_isoc:1; + unsigned int is_isoc:1; + unsigned int is_in:1; + + const struct usb_endpoint_descriptor *desc; +}; + +struct usba_request { + struct usb_request req; + struct list_head queue; + + u32 ctrl; + + unsigned int submitted:1; + unsigned int last_transaction:1; + unsigned int using_dma:1; + unsigned int mapped:1; +}; + +struct usba_udc { + void *regs; + void *fifo; + + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct platform_device *pdev; + int irq; + int vbus_pin; + int vbus_pin_inverted; + int num_ep; + struct usba_ep *usba_ep; + + u16 devstatus; + + u16 test_mode; + int vbus_prev; +}; + +static inline struct usba_ep *to_usba_ep(struct usb_ep *ep) +{ + return container_of(ep, struct usba_ep, ep); +} + +static inline struct usba_request *to_usba_req(struct usb_request *req) +{ + return container_of(req, struct usba_request, req); +} + +static inline struct usba_udc *to_usba_udc(struct usb_gadget *gadget) +{ + return container_of(gadget, struct usba_udc, gadget); +} + +#define ep_is_control(ep) ((ep)->index == 0) +#define ep_is_idle(ep) ((ep)->state == EP_STATE_IDLE) + +#endif /* __LINUX_USB_GADGET_USBA_UDC_H */ diff --git a/roms/u-boot/drivers/usb/gadget/bcm_udc_otg.h b/roms/u-boot/drivers/usb/gadget/bcm_udc_otg.h new file mode 100644 index 000000000..24cc936c6 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/bcm_udc_otg.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2015 Broadcom Corporation. + */ + +#ifndef __BCM_UDC_OTG_H +#define __BCM_UDC_OTG_H + +#include <common.h> + +static inline void wfld_set(uintptr_t addr, uint32_t fld_val, uint32_t fld_mask) +{ + writel(((readl(addr) & ~(fld_mask)) | (fld_val)), (addr)); +} + +static inline void wfld_clear(uintptr_t addr, uint32_t fld_mask) +{ + writel((readl(addr) & ~(fld_mask)), (addr)); +} + +#endif diff --git a/roms/u-boot/drivers/usb/gadget/bcm_udc_otg_phy.c b/roms/u-boot/drivers/usb/gadget/bcm_udc_otg_phy.c new file mode 100644 index 000000000..c89cd57c2 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/bcm_udc_otg_phy.c @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2015 Broadcom Corporation. + */ + +#include <config.h> +#include <common.h> +#include <asm/io.h> +#include <asm/arch/sysmap.h> +#include <asm/kona-common/clk.h> +#include <linux/delay.h> + +#include "dwc2_udc_otg_priv.h" +#include "bcm_udc_otg.h" + +void otg_phy_init(struct dwc2_udc *dev) +{ + /* turn on the USB OTG clocks */ + clk_usb_otg_enable((void *)HSOTG_BASE_ADDR); + + /* set Phy to driving mode */ + wfld_clear(HSOTG_CTRL_BASE_ADDR + HSOTG_CTRL_PHY_P1CTL_OFFSET, + HSOTG_CTRL_PHY_P1CTL_NON_DRIVING_MASK); + + udelay(100); + + /* clear Soft Disconnect */ + wfld_clear(HSOTG_BASE_ADDR + HSOTG_DCTL_OFFSET, + HSOTG_DCTL_SFTDISCON_MASK); + + /* invoke Reset (active low) */ + wfld_clear(HSOTG_CTRL_BASE_ADDR + HSOTG_CTRL_PHY_P1CTL_OFFSET, + HSOTG_CTRL_PHY_P1CTL_SOFT_RESET_MASK); + + /* Reset needs to be asserted for 2ms */ + udelay(2000); + + /* release Reset */ + wfld_set(HSOTG_CTRL_BASE_ADDR + HSOTG_CTRL_PHY_P1CTL_OFFSET, + HSOTG_CTRL_PHY_P1CTL_SOFT_RESET_MASK, + HSOTG_CTRL_PHY_P1CTL_SOFT_RESET_MASK); +} + +void otg_phy_off(struct dwc2_udc *dev) +{ + /* Soft Disconnect */ + wfld_set(HSOTG_BASE_ADDR + HSOTG_DCTL_OFFSET, + HSOTG_DCTL_SFTDISCON_MASK, + HSOTG_DCTL_SFTDISCON_MASK); + + /* set Phy to non-driving (reset) mode */ + wfld_set(HSOTG_CTRL_BASE_ADDR + HSOTG_CTRL_PHY_P1CTL_OFFSET, + HSOTG_CTRL_PHY_P1CTL_NON_DRIVING_MASK, + HSOTG_CTRL_PHY_P1CTL_NON_DRIVING_MASK); +} diff --git a/roms/u-boot/drivers/usb/gadget/ci_udc.c b/roms/u-boot/drivers/usb/gadget/ci_udc.c new file mode 100644 index 000000000..226a9e6d6 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/ci_udc.c @@ -0,0 +1,1072 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2011, Marvell Semiconductor Inc. + * Lei Wen <leiwen@marvell.com> + * + * Back ported to the 8xx platform (from the 8260 platform) by + * Murray.Jensen@cmst.csiro.au, 27-Jan-01. + */ + +#include <common.h> +#include <command.h> +#include <config.h> +#include <cpu_func.h> +#include <net.h> +#include <malloc.h> +#include <asm/byteorder.h> +#include <asm/cache.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <asm/io.h> +#include <asm/unaligned.h> +#include <linux/types.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <usb/ci_udc.h> +#include "../host/ehci.h" +#include "ci_udc.h" + +/* + * Check if the system has too long cachelines. If the cachelines are + * longer then 128b, the driver will not be able flush/invalidate data + * cache over separate QH entries. We use 128b because one QH entry is + * 64b long and there are always two QH list entries for each endpoint. + */ +#if ARCH_DMA_MINALIGN > 128 +#error This driver can not work on systems with caches longer than 128b +#endif + +/* + * Every QTD must be individually aligned, since we can program any + * QTD's address into HW. Cache flushing requires ARCH_DMA_MINALIGN, + * and the USB HW requires 32-byte alignment. Align to both: + */ +#define ILIST_ALIGN roundup(ARCH_DMA_MINALIGN, 32) +/* Each QTD is this size */ +#define ILIST_ENT_RAW_SZ sizeof(struct ept_queue_item) +/* + * Align the size of the QTD too, so we can add this value to each + * QTD's address to get another aligned address. + */ +#define ILIST_ENT_SZ roundup(ILIST_ENT_RAW_SZ, ILIST_ALIGN) +/* For each endpoint, we need 2 QTDs, one for each of IN and OUT */ +#define ILIST_SZ (NUM_ENDPOINTS * 2 * ILIST_ENT_SZ) + +#define EP_MAX_LENGTH_TRANSFER 0x4000 + +#ifndef DEBUG +#define DBG(x...) do {} while (0) +#else +#define DBG(x...) printf(x) +static const char *reqname(unsigned r) +{ + switch (r) { + case USB_REQ_GET_STATUS: return "GET_STATUS"; + case USB_REQ_CLEAR_FEATURE: return "CLEAR_FEATURE"; + case USB_REQ_SET_FEATURE: return "SET_FEATURE"; + case USB_REQ_SET_ADDRESS: return "SET_ADDRESS"; + case USB_REQ_GET_DESCRIPTOR: return "GET_DESCRIPTOR"; + case USB_REQ_SET_DESCRIPTOR: return "SET_DESCRIPTOR"; + case USB_REQ_GET_CONFIGURATION: return "GET_CONFIGURATION"; + case USB_REQ_SET_CONFIGURATION: return "SET_CONFIGURATION"; + case USB_REQ_GET_INTERFACE: return "GET_INTERFACE"; + case USB_REQ_SET_INTERFACE: return "SET_INTERFACE"; + default: return "*UNKNOWN*"; + } +} +#endif + +static struct usb_endpoint_descriptor ep0_desc = { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, +}; + +static int ci_pullup(struct usb_gadget *gadget, int is_on); +static int ci_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc); +static int ci_ep_disable(struct usb_ep *ep); +static int ci_ep_queue(struct usb_ep *ep, + struct usb_request *req, gfp_t gfp_flags); +static int ci_ep_dequeue(struct usb_ep *ep, struct usb_request *req); +static struct usb_request * +ci_ep_alloc_request(struct usb_ep *ep, unsigned int gfp_flags); +static void ci_ep_free_request(struct usb_ep *ep, struct usb_request *_req); + +static struct usb_gadget_ops ci_udc_ops = { + .pullup = ci_pullup, +}; + +static struct usb_ep_ops ci_ep_ops = { + .enable = ci_ep_enable, + .disable = ci_ep_disable, + .queue = ci_ep_queue, + .dequeue = ci_ep_dequeue, + .alloc_request = ci_ep_alloc_request, + .free_request = ci_ep_free_request, +}; + +__weak void ci_init_after_reset(struct ehci_ctrl *ctrl) +{ +} + +/* Init values for USB endpoints. */ +static const struct usb_ep ci_ep_init[5] = { + [0] = { /* EP 0 */ + .maxpacket = 64, + .name = "ep0", + .ops = &ci_ep_ops, + }, + [1] = { + .maxpacket = 512, + .name = "ep1in-bulk", + .ops = &ci_ep_ops, + }, + [2] = { + .maxpacket = 512, + .name = "ep2out-bulk", + .ops = &ci_ep_ops, + }, + [3] = { + .maxpacket = 512, + .name = "ep3in-int", + .ops = &ci_ep_ops, + }, + [4] = { + .maxpacket = 512, + .name = "ep-", + .ops = &ci_ep_ops, + }, +}; + +static struct ci_drv controller = { + .gadget = { + .name = "ci_udc", + .ops = &ci_udc_ops, + .is_dualspeed = 1, + .max_speed = USB_SPEED_HIGH, + }, +}; + +/** + * ci_get_qh() - return queue head for endpoint + * @ep_num: Endpoint number + * @dir_in: Direction of the endpoint (IN = 1, OUT = 0) + * + * This function returns the QH associated with particular endpoint + * and it's direction. + */ +static struct ept_queue_head *ci_get_qh(int ep_num, int dir_in) +{ + return &controller.epts[(ep_num * 2) + dir_in]; +} + +/** + * ci_get_qtd() - return queue item for endpoint + * @ep_num: Endpoint number + * @dir_in: Direction of the endpoint (IN = 1, OUT = 0) + * + * This function returns the QH associated with particular endpoint + * and it's direction. + */ +static struct ept_queue_item *ci_get_qtd(int ep_num, int dir_in) +{ + int index = (ep_num * 2) + dir_in; + uint8_t *imem = controller.items_mem + (index * ILIST_ENT_SZ); + return (struct ept_queue_item *)imem; +} + +/** + * ci_flush_qh - flush cache over queue head + * @ep_num: Endpoint number + * + * This function flushes cache over QH for particular endpoint. + */ +static void ci_flush_qh(int ep_num) +{ + struct ept_queue_head *head = ci_get_qh(ep_num, 0); + const unsigned long start = (unsigned long)head; + const unsigned long end = start + 2 * sizeof(*head); + + flush_dcache_range(start, end); +} + +/** + * ci_invalidate_qh - invalidate cache over queue head + * @ep_num: Endpoint number + * + * This function invalidates cache over QH for particular endpoint. + */ +static void ci_invalidate_qh(int ep_num) +{ + struct ept_queue_head *head = ci_get_qh(ep_num, 0); + unsigned long start = (unsigned long)head; + unsigned long end = start + 2 * sizeof(*head); + + invalidate_dcache_range(start, end); +} + +/** + * ci_flush_qtd - flush cache over queue item + * @ep_num: Endpoint number + * + * This function flushes cache over qTD pair for particular endpoint. + */ +static void ci_flush_qtd(int ep_num) +{ + struct ept_queue_item *item = ci_get_qtd(ep_num, 0); + const unsigned long start = (unsigned long)item; + const unsigned long end = start + 2 * ILIST_ENT_SZ; + + flush_dcache_range(start, end); +} + +/** + * ci_flush_td - flush cache over queue item + * @td: td pointer + * + * This function flushes cache for particular transfer descriptor. + */ +static void ci_flush_td(struct ept_queue_item *td) +{ + const unsigned long start = (unsigned long)td; + const unsigned long end = (unsigned long)td + ILIST_ENT_SZ; + flush_dcache_range(start, end); +} + +/** + * ci_invalidate_qtd - invalidate cache over queue item + * @ep_num: Endpoint number + * + * This function invalidates cache over qTD pair for particular endpoint. + */ +static void ci_invalidate_qtd(int ep_num) +{ + struct ept_queue_item *item = ci_get_qtd(ep_num, 0); + const unsigned long start = (unsigned long)item; + const unsigned long end = start + 2 * ILIST_ENT_SZ; + + invalidate_dcache_range(start, end); +} + +/** + * ci_invalidate_td - invalidate cache over queue item + * @td: td pointer + * + * This function invalidates cache for particular transfer descriptor. + */ +static void ci_invalidate_td(struct ept_queue_item *td) +{ + const unsigned long start = (unsigned long)td; + const unsigned long end = start + ILIST_ENT_SZ; + invalidate_dcache_range(start, end); +} + +static struct usb_request * +ci_ep_alloc_request(struct usb_ep *ep, unsigned int gfp_flags) +{ + struct ci_ep *ci_ep = container_of(ep, struct ci_ep, ep); + int num = -1; + struct ci_req *ci_req; + + if (ci_ep->desc) + num = ci_ep->desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + + if (num == 0 && controller.ep0_req) + return &controller.ep0_req->req; + + ci_req = calloc(1, sizeof(*ci_req)); + if (!ci_req) + return NULL; + + INIT_LIST_HEAD(&ci_req->queue); + + if (num == 0) + controller.ep0_req = ci_req; + + return &ci_req->req; +} + +static void ci_ep_free_request(struct usb_ep *ep, struct usb_request *req) +{ + struct ci_ep *ci_ep = container_of(ep, struct ci_ep, ep); + struct ci_req *ci_req = container_of(req, struct ci_req, req); + int num = -1; + + if (ci_ep->desc) + num = ci_ep->desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + + if (num == 0) { + if (!controller.ep0_req) + return; + controller.ep0_req = 0; + } + + if (ci_req->b_buf) + free(ci_req->b_buf); + free(ci_req); +} + +static void ep_enable(int num, int in, int maxpacket) +{ + struct ci_udc *udc = (struct ci_udc *)controller.ctrl->hcor; + unsigned n; + + n = readl(&udc->epctrl[num]); + if (in) + n |= (CTRL_TXE | CTRL_TXR | CTRL_TXT_BULK); + else + n |= (CTRL_RXE | CTRL_RXR | CTRL_RXT_BULK); + + if (num != 0) { + struct ept_queue_head *head = ci_get_qh(num, in); + + head->config = CONFIG_MAX_PKT(maxpacket) | CONFIG_ZLT; + ci_flush_qh(num); + } + writel(n, &udc->epctrl[num]); +} + +static int ci_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct ci_ep *ci_ep = container_of(ep, struct ci_ep, ep); + int num, in; + num = desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + in = (desc->bEndpointAddress & USB_DIR_IN) != 0; + ci_ep->desc = desc; + ep->desc = desc; + + if (num) { + int max = get_unaligned_le16(&desc->wMaxPacketSize); + + if ((max > 64) && (controller.gadget.speed == USB_SPEED_FULL)) + max = 64; + if (ep->maxpacket != max) { + DBG("%s: from %d to %d\n", __func__, + ep->maxpacket, max); + ep->maxpacket = max; + } + } + ep_enable(num, in, ep->maxpacket); + DBG("%s: num=%d maxpacket=%d\n", __func__, num, ep->maxpacket); + return 0; +} + +static int ci_ep_disable(struct usb_ep *ep) +{ + struct ci_ep *ci_ep = container_of(ep, struct ci_ep, ep); + + ci_ep->desc = NULL; + ep->desc = NULL; + return 0; +} + +static int ci_bounce(struct ci_req *ci_req, int in) +{ + struct usb_request *req = &ci_req->req; + unsigned long addr = (unsigned long)req->buf; + unsigned long hwaddr; + uint32_t aligned_used_len; + + /* Input buffer address is not aligned. */ + if (addr & (ARCH_DMA_MINALIGN - 1)) + goto align; + + /* Input buffer length is not aligned. */ + if (req->length & (ARCH_DMA_MINALIGN - 1)) + goto align; + + /* The buffer is well aligned, only flush cache. */ + ci_req->hw_len = req->length; + ci_req->hw_buf = req->buf; + goto flush; + +align: + if (ci_req->b_buf && req->length > ci_req->b_len) { + free(ci_req->b_buf); + ci_req->b_buf = 0; + } + if (!ci_req->b_buf) { + ci_req->b_len = roundup(req->length, ARCH_DMA_MINALIGN); + ci_req->b_buf = memalign(ARCH_DMA_MINALIGN, ci_req->b_len); + if (!ci_req->b_buf) + return -ENOMEM; + } + ci_req->hw_len = ci_req->b_len; + ci_req->hw_buf = ci_req->b_buf; + + if (in) + memcpy(ci_req->hw_buf, req->buf, req->length); + +flush: + hwaddr = (unsigned long)ci_req->hw_buf; + aligned_used_len = roundup(req->length, ARCH_DMA_MINALIGN); + flush_dcache_range(hwaddr, hwaddr + aligned_used_len); + + return 0; +} + +static void ci_debounce(struct ci_req *ci_req, int in) +{ + struct usb_request *req = &ci_req->req; + unsigned long addr = (unsigned long)req->buf; + unsigned long hwaddr = (unsigned long)ci_req->hw_buf; + uint32_t aligned_used_len; + + if (in) + return; + + aligned_used_len = roundup(req->actual, ARCH_DMA_MINALIGN); + invalidate_dcache_range(hwaddr, hwaddr + aligned_used_len); + + if (addr == hwaddr) + return; /* not a bounce */ + + memcpy(req->buf, ci_req->hw_buf, req->actual); +} + +static void ci_ep_submit_next_request(struct ci_ep *ci_ep) +{ + struct ci_udc *udc = (struct ci_udc *)controller.ctrl->hcor; + struct ept_queue_item *item; + struct ept_queue_head *head; + int bit, num, len, in; + struct ci_req *ci_req; + u8 *buf; + uint32_t len_left, len_this_dtd; + struct ept_queue_item *dtd, *qtd; + + ci_ep->req_primed = true; + + num = ci_ep->desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + in = (ci_ep->desc->bEndpointAddress & USB_DIR_IN) != 0; + item = ci_get_qtd(num, in); + head = ci_get_qh(num, in); + + ci_req = list_first_entry(&ci_ep->queue, struct ci_req, queue); + len = ci_req->req.length; + + head->next = (unsigned long)item; + head->info = 0; + + ci_req->dtd_count = 0; + buf = ci_req->hw_buf; + len_left = len; + dtd = item; + + do { + len_this_dtd = min(len_left, (unsigned)EP_MAX_LENGTH_TRANSFER); + + dtd->info = INFO_BYTES(len_this_dtd) | INFO_ACTIVE; + dtd->page0 = (unsigned long)buf; + dtd->page1 = ((unsigned long)buf & 0xfffff000) + 0x1000; + dtd->page2 = ((unsigned long)buf & 0xfffff000) + 0x2000; + dtd->page3 = ((unsigned long)buf & 0xfffff000) + 0x3000; + dtd->page4 = ((unsigned long)buf & 0xfffff000) + 0x4000; + + len_left -= len_this_dtd; + buf += len_this_dtd; + + if (len_left) { + qtd = (struct ept_queue_item *) + memalign(ILIST_ALIGN, ILIST_ENT_SZ); + dtd->next = (unsigned long)qtd; + dtd = qtd; + memset(dtd, 0, ILIST_ENT_SZ); + } + + ci_req->dtd_count++; + } while (len_left); + + item = dtd; + /* + * When sending the data for an IN transaction, the attached host + * knows that all data for the IN is sent when one of the following + * occurs: + * a) A zero-length packet is transmitted. + * b) A packet with length that isn't an exact multiple of the ep's + * maxpacket is transmitted. + * c) Enough data is sent to exactly fill the host's maximum expected + * IN transaction size. + * + * One of these conditions MUST apply at the end of an IN transaction, + * or the transaction will not be considered complete by the host. If + * none of (a)..(c) already applies, then we must force (a) to apply + * by explicitly sending an extra zero-length packet. + */ + /* IN !a !b !c */ + if (in && len && !(len % ci_ep->ep.maxpacket) && ci_req->req.zero) { + /* + * Each endpoint has 2 items allocated, even though typically + * only 1 is used at a time since either an IN or an OUT but + * not both is queued. For an IN transaction, item currently + * points at the second of these items, so we know that we + * can use the other to transmit the extra zero-length packet. + */ + struct ept_queue_item *other_item = ci_get_qtd(num, 0); + item->next = (unsigned long)other_item; + item = other_item; + item->info = INFO_ACTIVE; + } + + item->next = TERMINATE; + item->info |= INFO_IOC; + + ci_flush_qtd(num); + + item = (struct ept_queue_item *)(unsigned long)head->next; + while (item->next != TERMINATE) { + ci_flush_td((struct ept_queue_item *)(unsigned long)item->next); + item = (struct ept_queue_item *)(unsigned long)item->next; + } + + DBG("ept%d %s queue len %x, req %p, buffer %p\n", + num, in ? "in" : "out", len, ci_req, ci_req->hw_buf); + ci_flush_qh(num); + + if (in) + bit = EPT_TX(num); + else + bit = EPT_RX(num); + + writel(bit, &udc->epprime); +} + +static int ci_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct ci_ep *ci_ep = container_of(_ep, struct ci_ep, ep); + struct ci_req *ci_req; + + list_for_each_entry(ci_req, &ci_ep->queue, queue) { + if (&ci_req->req == _req) + break; + } + + if (&ci_req->req != _req) + return -EINVAL; + + list_del_init(&ci_req->queue); + + if (ci_req->req.status == -EINPROGRESS) { + ci_req->req.status = -ECONNRESET; + if (ci_req->req.complete) + ci_req->req.complete(_ep, _req); + } + + return 0; +} + +static int ci_ep_queue(struct usb_ep *ep, + struct usb_request *req, gfp_t gfp_flags) +{ + struct ci_ep *ci_ep = container_of(ep, struct ci_ep, ep); + struct ci_req *ci_req = container_of(req, struct ci_req, req); + int in, ret; + int __maybe_unused num; + + num = ci_ep->desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + in = (ci_ep->desc->bEndpointAddress & USB_DIR_IN) != 0; + + if (!num && ci_ep->req_primed) { + /* + * The flipping of ep0 between IN and OUT relies on + * ci_ep_queue consuming the current IN/OUT setting + * immediately. If this is deferred to a later point when the + * req is pulled out of ci_req->queue, then the IN/OUT setting + * may have been changed since the req was queued, and state + * will get out of sync. This condition doesn't occur today, + * but could if bugs were introduced later, and this error + * check will save a lot of debugging time. + */ + printf("%s: ep0 transaction already in progress\n", __func__); + return -EPROTO; + } + + ret = ci_bounce(ci_req, in); + if (ret) + return ret; + + DBG("ept%d %s pre-queue req %p, buffer %p\n", + num, in ? "in" : "out", ci_req, ci_req->hw_buf); + list_add_tail(&ci_req->queue, &ci_ep->queue); + + if (!ci_ep->req_primed) + ci_ep_submit_next_request(ci_ep); + + return 0; +} + +static void flip_ep0_direction(void) +{ + if (ep0_desc.bEndpointAddress == USB_DIR_IN) { + DBG("%s: Flipping ep0 to OUT\n", __func__); + ep0_desc.bEndpointAddress = 0; + } else { + DBG("%s: Flipping ep0 to IN\n", __func__); + ep0_desc.bEndpointAddress = USB_DIR_IN; + } +} + +static void handle_ep_complete(struct ci_ep *ci_ep) +{ + struct ept_queue_item *item, *next_td; + int num, in, len, j; + struct ci_req *ci_req; + + num = ci_ep->desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + in = (ci_ep->desc->bEndpointAddress & USB_DIR_IN) != 0; + item = ci_get_qtd(num, in); + ci_invalidate_qtd(num); + ci_req = list_first_entry(&ci_ep->queue, struct ci_req, queue); + + next_td = item; + len = 0; + for (j = 0; j < ci_req->dtd_count; j++) { + ci_invalidate_td(next_td); + item = next_td; + len += (item->info >> 16) & 0x7fff; + if (item->info & 0xff) + printf("EP%d/%s FAIL info=%x pg0=%x\n", + num, in ? "in" : "out", item->info, item->page0); + if (j != ci_req->dtd_count - 1) + next_td = (struct ept_queue_item *)(unsigned long) + item->next; + if (j != 0) + free(item); + } + + list_del_init(&ci_req->queue); + ci_ep->req_primed = false; + + if (!list_empty(&ci_ep->queue)) + ci_ep_submit_next_request(ci_ep); + + ci_req->req.actual = ci_req->req.length - len; + ci_debounce(ci_req, in); + + DBG("ept%d %s req %p, complete %x\n", + num, in ? "in" : "out", ci_req, len); + if (num != 0 || controller.ep0_data_phase) + ci_req->req.complete(&ci_ep->ep, &ci_req->req); + if (num == 0 && controller.ep0_data_phase) { + /* + * Data Stage is complete, so flip ep0 dir for Status Stage, + * which always transfers a packet in the opposite direction. + */ + DBG("%s: flip ep0 dir for Status Stage\n", __func__); + flip_ep0_direction(); + controller.ep0_data_phase = false; + ci_req->req.length = 0; + usb_ep_queue(&ci_ep->ep, &ci_req->req, 0); + } +} + +#define SETUP(type, request) (((type) << 8) | (request)) + +static void handle_setup(void) +{ + struct ci_ep *ci_ep = &controller.ep[0]; + struct ci_req *ci_req; + struct usb_request *req; + struct ci_udc *udc = (struct ci_udc *)controller.ctrl->hcor; + struct ept_queue_head *head; + struct usb_ctrlrequest r; + int status = 0; + int num, in, _num, _in, i; + char *buf; + + ci_req = controller.ep0_req; + req = &ci_req->req; + head = ci_get_qh(0, 0); /* EP0 OUT */ + + ci_invalidate_qh(0); + memcpy(&r, head->setup_data, sizeof(struct usb_ctrlrequest)); +#ifdef CONFIG_CI_UDC_HAS_HOSTPC + writel(EPT_RX(0), &udc->epsetupstat); +#else + writel(EPT_RX(0), &udc->epstat); +#endif + DBG("handle setup %s, %x, %x index %x value %x length %x\n", + reqname(r.bRequest), r.bRequestType, r.bRequest, r.wIndex, + r.wValue, r.wLength); + + /* Set EP0 dir for Data Stage based on Setup Stage data */ + if (r.bRequestType & USB_DIR_IN) { + DBG("%s: Set ep0 to IN for Data Stage\n", __func__); + ep0_desc.bEndpointAddress = USB_DIR_IN; + } else { + DBG("%s: Set ep0 to OUT for Data Stage\n", __func__); + ep0_desc.bEndpointAddress = 0; + } + if (r.wLength) { + controller.ep0_data_phase = true; + } else { + /* 0 length -> no Data Stage. Flip dir for Status Stage */ + DBG("%s: 0 length: flip ep0 dir for Status Stage\n", __func__); + flip_ep0_direction(); + controller.ep0_data_phase = false; + } + + list_del_init(&ci_req->queue); + ci_ep->req_primed = false; + + switch (SETUP(r.bRequestType, r.bRequest)) { + case SETUP(USB_RECIP_ENDPOINT, USB_REQ_CLEAR_FEATURE): + _num = r.wIndex & 15; + _in = !!(r.wIndex & 0x80); + + if ((r.wValue == 0) && (r.wLength == 0)) { + req->length = 0; + for (i = 0; i < NUM_ENDPOINTS; i++) { + struct ci_ep *ep = &controller.ep[i]; + + if (!ep->desc) + continue; + num = ep->desc->bEndpointAddress + & USB_ENDPOINT_NUMBER_MASK; + in = (ep->desc->bEndpointAddress + & USB_DIR_IN) != 0; + if ((num == _num) && (in == _in)) { + ep_enable(num, in, ep->ep.maxpacket); + usb_ep_queue(controller.gadget.ep0, + req, 0); + break; + } + } + } + return; + + case SETUP(USB_RECIP_DEVICE, USB_REQ_SET_ADDRESS): + /* + * write address delayed (will take effect + * after the next IN txn) + */ + writel((r.wValue << 25) | (1 << 24), &udc->devaddr); + req->length = 0; + usb_ep_queue(controller.gadget.ep0, req, 0); + return; + + case SETUP(USB_DIR_IN | USB_RECIP_DEVICE, USB_REQ_GET_STATUS): + req->length = 2; + buf = (char *)req->buf; + buf[0] = 1 << USB_DEVICE_SELF_POWERED; + buf[1] = 0; + usb_ep_queue(controller.gadget.ep0, req, 0); + return; + } + /* pass request up to the gadget driver */ + if (controller.driver) + status = controller.driver->setup(&controller.gadget, &r); + else + status = -ENODEV; + + if (!status) + return; + DBG("STALL reqname %s type %x value %x, index %x\n", + reqname(r.bRequest), r.bRequestType, r.wValue, r.wIndex); + writel((1<<16) | (1 << 0), &udc->epctrl[0]); +} + +static void stop_activity(void) +{ + int i, num, in; + struct ept_queue_head *head; + struct ci_udc *udc = (struct ci_udc *)controller.ctrl->hcor; + writel(readl(&udc->epcomp), &udc->epcomp); +#ifdef CONFIG_CI_UDC_HAS_HOSTPC + writel(readl(&udc->epsetupstat), &udc->epsetupstat); +#endif + writel(readl(&udc->epstat), &udc->epstat); + writel(0xffffffff, &udc->epflush); + + /* error out any pending reqs */ + for (i = 0; i < NUM_ENDPOINTS; i++) { + if (i != 0) + writel(0, &udc->epctrl[i]); + if (controller.ep[i].desc) { + num = controller.ep[i].desc->bEndpointAddress + & USB_ENDPOINT_NUMBER_MASK; + in = (controller.ep[i].desc->bEndpointAddress + & USB_DIR_IN) != 0; + head = ci_get_qh(num, in); + head->info = INFO_ACTIVE; + ci_flush_qh(num); + } + } +} + +void udc_irq(void) +{ + struct ci_udc *udc = (struct ci_udc *)controller.ctrl->hcor; + unsigned n = readl(&udc->usbsts); + writel(n, &udc->usbsts); + int bit, i, num, in; + + n &= (STS_SLI | STS_URI | STS_PCI | STS_UI | STS_UEI); + if (n == 0) + return; + + if (n & STS_URI) { + DBG("-- reset --\n"); + stop_activity(); + } + if (n & STS_SLI) + DBG("-- suspend --\n"); + + if (n & STS_PCI) { + int max = 64; + int speed = USB_SPEED_FULL; + +#ifdef CONFIG_CI_UDC_HAS_HOSTPC + bit = (readl(&udc->hostpc1_devlc) >> 25) & 3; +#else + bit = (readl(&udc->portsc) >> 26) & 3; +#endif + DBG("-- portchange %x %s\n", bit, (bit == 2) ? "High" : "Full"); + if (bit == 2) { + speed = USB_SPEED_HIGH; + max = 512; + } + controller.gadget.speed = speed; + for (i = 1; i < NUM_ENDPOINTS; i++) { + if (controller.ep[i].ep.maxpacket > max) + controller.ep[i].ep.maxpacket = max; + } + } + + if (n & STS_UEI) + printf("<UEI %x>\n", readl(&udc->epcomp)); + + if ((n & STS_UI) || (n & STS_UEI)) { +#ifdef CONFIG_CI_UDC_HAS_HOSTPC + n = readl(&udc->epsetupstat); +#else + n = readl(&udc->epstat); +#endif + if (n & EPT_RX(0)) + handle_setup(); + + n = readl(&udc->epcomp); + if (n != 0) + writel(n, &udc->epcomp); + + for (i = 0; i < NUM_ENDPOINTS && n; i++) { + if (controller.ep[i].desc) { + num = controller.ep[i].desc->bEndpointAddress + & USB_ENDPOINT_NUMBER_MASK; + in = (controller.ep[i].desc->bEndpointAddress + & USB_DIR_IN) != 0; + bit = (in) ? EPT_TX(num) : EPT_RX(num); + if (n & bit) + handle_ep_complete(&controller.ep[i]); + } + } + } +} + +int usb_gadget_handle_interrupts(int index) +{ + u32 value; + struct ci_udc *udc = (struct ci_udc *)controller.ctrl->hcor; + + value = readl(&udc->usbsts); + if (value) + udc_irq(); + + return value; +} + +void udc_disconnect(void) +{ + struct ci_udc *udc = (struct ci_udc *)controller.ctrl->hcor; + /* disable pullup */ + stop_activity(); + writel(USBCMD_FS2, &udc->usbcmd); + udelay(800); + if (controller.driver) + controller.driver->disconnect(&controller.gadget); +} + +static int ci_pullup(struct usb_gadget *gadget, int is_on) +{ + struct ci_udc *udc = (struct ci_udc *)controller.ctrl->hcor; + if (is_on) { + /* RESET */ + writel(USBCMD_ITC(MICRO_8FRAME) | USBCMD_RST, &udc->usbcmd); + udelay(200); + + ci_init_after_reset(controller.ctrl); + + writel((unsigned long)controller.epts, &udc->epinitaddr); + + /* select DEVICE mode */ + writel(USBMODE_DEVICE, &udc->usbmode); + +#if !defined(CONFIG_USB_GADGET_DUALSPEED) + /* Port force Full-Speed Connect */ + setbits_le32(&udc->portsc, PFSC); +#endif + + writel(0xffffffff, &udc->epflush); + + /* Turn on the USB connection by enabling the pullup resistor */ + setbits_le32(&udc->usbcmd, USBCMD_ITC(MICRO_8FRAME) | + USBCMD_RUN); + } else { + udc_disconnect(); + } + + return 0; +} + +static int ci_udc_probe(void) +{ + struct ept_queue_head *head; + int i; + + const int num = 2 * NUM_ENDPOINTS; + + const int eplist_min_align = 4096; + const int eplist_align = roundup(eplist_min_align, ARCH_DMA_MINALIGN); + const int eplist_raw_sz = num * sizeof(struct ept_queue_head); + const int eplist_sz = roundup(eplist_raw_sz, ARCH_DMA_MINALIGN); + + /* The QH list must be aligned to 4096 bytes. */ + controller.epts = memalign(eplist_align, eplist_sz); + if (!controller.epts) + return -ENOMEM; + memset(controller.epts, 0, eplist_sz); + + controller.items_mem = memalign(ILIST_ALIGN, ILIST_SZ); + if (!controller.items_mem) { + free(controller.epts); + return -ENOMEM; + } + memset(controller.items_mem, 0, ILIST_SZ); + + for (i = 0; i < 2 * NUM_ENDPOINTS; i++) { + /* + * Configure QH for each endpoint. The structure of the QH list + * is such that each two subsequent fields, N and N+1 where N is + * even, in the QH list represent QH for one endpoint. The Nth + * entry represents OUT configuration and the N+1th entry does + * represent IN configuration of the endpoint. + */ + head = controller.epts + i; + if (i < 2) + head->config = CONFIG_MAX_PKT(EP0_MAX_PACKET_SIZE) + | CONFIG_ZLT | CONFIG_IOS; + else + head->config = CONFIG_MAX_PKT(EP_MAX_PACKET_SIZE) + | CONFIG_ZLT; + head->next = TERMINATE; + head->info = 0; + + if (i & 1) { + ci_flush_qh(i / 2); + ci_flush_qtd(i / 2); + } + } + + INIT_LIST_HEAD(&controller.gadget.ep_list); + + /* Init EP 0 */ + memcpy(&controller.ep[0].ep, &ci_ep_init[0], sizeof(*ci_ep_init)); + controller.ep[0].desc = &ep0_desc; + INIT_LIST_HEAD(&controller.ep[0].queue); + controller.ep[0].req_primed = false; + controller.gadget.ep0 = &controller.ep[0].ep; + INIT_LIST_HEAD(&controller.gadget.ep0->ep_list); + + /* Init EP 1..3 */ + for (i = 1; i < 4; i++) { + memcpy(&controller.ep[i].ep, &ci_ep_init[i], + sizeof(*ci_ep_init)); + INIT_LIST_HEAD(&controller.ep[i].queue); + controller.ep[i].req_primed = false; + list_add_tail(&controller.ep[i].ep.ep_list, + &controller.gadget.ep_list); + } + + /* Init EP 4..n */ + for (i = 4; i < NUM_ENDPOINTS; i++) { + memcpy(&controller.ep[i].ep, &ci_ep_init[4], + sizeof(*ci_ep_init)); + INIT_LIST_HEAD(&controller.ep[i].queue); + controller.ep[i].req_primed = false; + list_add_tail(&controller.ep[i].ep.ep_list, + &controller.gadget.ep_list); + } + + ci_ep_alloc_request(&controller.ep[0].ep, 0); + if (!controller.ep0_req) { + free(controller.items_mem); + free(controller.epts); + return -ENOMEM; + } + + return 0; +} + +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + int ret; + + if (!driver) + return -EINVAL; + if (!driver->bind || !driver->setup || !driver->disconnect) + return -EINVAL; + +#if CONFIG_IS_ENABLED(DM_USB) + ret = usb_setup_ehci_gadget(&controller.ctrl); +#else + ret = usb_lowlevel_init(0, USB_INIT_DEVICE, (void **)&controller.ctrl); +#endif + if (ret) + return ret; + + ret = ci_udc_probe(); + if (ret) { + DBG("udc probe failed, returned %d\n", ret); + return ret; + } + + ret = driver->bind(&controller.gadget); + if (ret) { + DBG("driver->bind() returned %d\n", ret); + return ret; + } + controller.driver = driver; + + return 0; +} + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + udc_disconnect(); + + driver->unbind(&controller.gadget); + controller.driver = NULL; + + ci_ep_free_request(&controller.ep[0].ep, &controller.ep0_req->req); + free(controller.items_mem); + free(controller.epts); + +#if CONFIG_IS_ENABLED(DM_USB) + usb_remove_ehci_gadget(&controller.ctrl); +#else + usb_lowlevel_stop(0); + controller.ctrl = NULL; +#endif + + return 0; +} + +bool dfu_usb_get_reset(void) +{ + struct ci_udc *udc = (struct ci_udc *)controller.ctrl->hcor; + + return !!(readl(&udc->usbsts) & STS_URI); +} diff --git a/roms/u-boot/drivers/usb/gadget/ci_udc.h b/roms/u-boot/drivers/usb/gadget/ci_udc.h new file mode 100644 index 000000000..95cc07992 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/ci_udc.h @@ -0,0 +1,153 @@ +/* + * Copyright 2011, Marvell Semiconductor Inc. + * + * Licensed under the GPL-2 or later. + */ +#ifndef __GADGET__CI_UDC_H__ +#define __GADGET__CI_UDC_H__ + +#define NUM_ENDPOINTS 6 + +#ifdef CONFIG_CI_UDC_HAS_HOSTPC +struct ci_udc { + u32 usbcmd; /* 0x130 */ + u32 usbsts; /* 0x134 */ + u32 pad1[3]; + u32 devaddr; /* 0x144 */ + u32 epinitaddr; /* 0x148 */ + u32 pad2[10]; + u32 portsc; /* 0x174 */ + u32 pad178[(0x1b4 - (0x174 + 4)) / 4]; + u32 hostpc1_devlc; /* 0x1b4 */ + u32 pad1b8[(0x1f8 - (0x1b4 + 4)) / 4]; + u32 usbmode; /* 0x1f8 */ + u32 pad1fc[(0x208 - (0x1f8 + 4)) / 4]; + u32 epsetupstat; /* 0x208 */ + u32 epprime; /* 0x20c */ + u32 epflush; /* 0x210 */ + u32 epstat; /* 0x214 */ + u32 epcomp; /* 0x218 */ + u32 epctrl[16]; /* 0x21c */ +}; +#else +struct ci_udc { + u32 usbcmd; /* 0x140 */ + u32 usbsts; /* 0x144 */ + u32 pad1[3]; + u32 devaddr; /* 0x154 */ + u32 epinitaddr; /* 0x158 */ + u32 pad2[10]; + u32 portsc; /* 0x184 */ + u32 pad3[8]; + u32 usbmode; /* 0x1a8 */ + u32 epstat; /* 0x1ac */ + u32 epprime; /* 0x1b0 */ + u32 epflush; /* 0x1b4 */ + u32 pad4; + u32 epcomp; /* 0x1bc */ + u32 epctrl[16]; /* 0x1c0 */ +}; + +#define PTS_ENABLE 2 +#define PTS(x) (((x) & 0x3) << 30) +#define PFSC (1 << 24) +#endif + +#define MICRO_8FRAME 0x8 +#define USBCMD_ITC(x) ((((x) > 0xff) ? 0xff : x) << 16) +#define USBCMD_FS2 (1 << 15) +#define USBCMD_RST (1 << 1) +#define USBCMD_RUN (1) + +#define STS_SLI (1 << 8) +#define STS_URI (1 << 6) +#define STS_PCI (1 << 2) +#define STS_UEI (1 << 1) +#define STS_UI (1 << 0) + +#define USBMODE_DEVICE 2 + +#define EPT_TX(x) (1 << (((x) & 0xffff) + 16)) +#define EPT_RX(x) (1 << ((x) & 0xffff)) + +#define CTRL_TXE (1 << 23) +#define CTRL_TXR (1 << 22) +#define CTRL_RXE (1 << 7) +#define CTRL_RXR (1 << 6) +#define CTRL_TXT_BULK (2 << 18) +#define CTRL_RXT_BULK (2 << 2) + +struct ci_req { + struct usb_request req; + struct list_head queue; + /* Bounce buffer allocated if needed to align the transfer */ + uint8_t *b_buf; + uint32_t b_len; + /* Buffer for the current transfer. Either req.buf/len or b_buf/len */ + uint8_t *hw_buf; + uint32_t hw_len; + uint32_t dtd_count; +}; + +struct ci_ep { + struct usb_ep ep; + struct list_head queue; + bool req_primed; + const struct usb_endpoint_descriptor *desc; +}; + +struct ci_drv { + struct usb_gadget gadget; + struct ci_req *ep0_req; + bool ep0_data_phase; + struct usb_gadget_driver *driver; + struct ehci_ctrl *ctrl; + struct ept_queue_head *epts; + uint8_t *items_mem; + struct ci_ep ep[NUM_ENDPOINTS]; +}; + +struct ept_queue_head { + unsigned config; + unsigned current; /* read-only */ + + unsigned next; + unsigned info; + unsigned page0; + unsigned page1; + unsigned page2; + unsigned page3; + unsigned page4; + unsigned reserved_0; + + unsigned char setup_data[8]; + + unsigned reserved_1; + unsigned reserved_2; + unsigned reserved_3; + unsigned reserved_4; +}; + +#define CONFIG_MAX_PKT(n) ((n) << 16) +#define CONFIG_ZLT (1 << 29) /* stop on zero-len xfer */ +#define CONFIG_IOS (1 << 15) /* IRQ on setup */ + +struct ept_queue_item { + unsigned next; + unsigned info; + unsigned page0; + unsigned page1; + unsigned page2; + unsigned page3; + unsigned page4; + unsigned reserved; +}; + +#define TERMINATE 1 +#define INFO_BYTES(n) ((n) << 16) +#define INFO_IOC (1 << 15) +#define INFO_ACTIVE (1 << 7) +#define INFO_HALTED (1 << 6) +#define INFO_BUFFER_ERROR (1 << 5) +#define INFO_TX_ERROR (1 << 3) +#endif diff --git a/roms/u-boot/drivers/usb/gadget/composite.c b/roms/u-boot/drivers/usb/gadget/composite.c new file mode 100644 index 000000000..2a309e624 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/composite.c @@ -0,0 +1,1531 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * composite.c - infrastructure for Composite USB Gadgets + * + * Copyright (C) 2006-2008 David Brownell + * U-Boot porting: Lukasz Majewski <l.majewski@samsung.com> + */ +#undef DEBUG + +#include <log.h> +#include <dm/devres.h> +#include <linux/bitops.h> +#include <linux/bug.h> +#include <linux/usb/composite.h> +#include "u_os_desc.h" + +#define USB_BUFSIZ 4096 + +/* Helper type for accessing packed u16 pointers */ +typedef struct { __le16 val; } __packed __le16_packed; + +static struct usb_composite_driver *composite; +static struct usb_configuration *os_desc_config; + +/* Microsoft OS String Descriptor */ +static char qw_sign_buf[OS_STRING_QW_SIGN_LEN / 2] = {'M', 'S', 'F', 'T', '1', '0', '0'}; + +static inline void le16_add_cpu_packed(__le16_packed *var, u16 val) +{ + var->val = cpu_to_le16(le16_to_cpu(var->val) + val); +} + +/** + * struct usb_os_string - represents OS String to be reported by a gadget + * @bLength: total length of the entire descritor, always 0x12 + * @bDescriptorType: USB_DT_STRING + * @qwSignature: the OS String proper + * @bMS_VendorCode: code used by the host for subsequent requests + * @bPad: not used, must be zero + */ +struct usb_os_string { + __u8 bLength; + __u8 bDescriptorType; + __u8 qwSignature[OS_STRING_QW_SIGN_LEN]; + __u8 bMS_VendorCode; + __u8 bPad; +} __packed; + +/** + * usb_add_function() - add a function to a configuration + * @config: the configuration + * @function: the function being added + * Context: single threaded during gadget setup + * + * After initialization, each configuration must have one or more + * functions added to it. Adding a function involves calling its @bind() + * method to allocate resources such as interface and string identifiers + * and endpoints. + * + * This function returns the value of the function's bind(), which is + * zero for success else a negative errno value. + */ +int usb_add_function(struct usb_configuration *config, + struct usb_function *function) +{ + int value = -EINVAL; + + debug("adding '%s'/%p to config '%s'/%p\n", + function->name, function, + config->label, config); + + if (!function->set_alt || !function->disable) + goto done; + + function->config = config; + list_add_tail(&function->list, &config->functions); + + if (function->bind) { + value = function->bind(config, function); + if (value < 0) { + list_del(&function->list); + function->config = NULL; + } + } else + value = 0; + + if (!config->fullspeed && function->descriptors) + config->fullspeed = 1; + if (!config->highspeed && function->hs_descriptors) + config->highspeed = 1; + if (!config->superspeed && function->ss_descriptors) + config->superspeed = 1; + +done: + if (value) + debug("adding '%s'/%p --> %d\n", + function->name, function, value); + return value; +} + +/** + * usb_function_deactivate - prevent function and gadget enumeration + * @function: the function that isn't yet ready to respond + * + * Blocks response of the gadget driver to host enumeration by + * preventing the data line pullup from being activated. This is + * normally called during @bind() processing to change from the + * initial "ready to respond" state, or when a required resource + * becomes available. + * + * For example, drivers that serve as a passthrough to a userspace + * daemon can block enumeration unless that daemon (such as an OBEX, + * MTP, or print server) is ready to handle host requests. + * + * Not all systems support software control of their USB peripheral + * data pullups. + * + * Returns zero on success, else negative errno. + */ +int usb_function_deactivate(struct usb_function *function) +{ + struct usb_composite_dev *cdev = function->config->cdev; + int status = 0; + + if (cdev->deactivations == 0) + status = usb_gadget_disconnect(cdev->gadget); + if (status == 0) + cdev->deactivations++; + + return status; +} + +/** + * usb_function_activate - allow function and gadget enumeration + * @function: function on which usb_function_activate() was called + * + * Reverses effect of usb_function_deactivate(). If no more functions + * are delaying their activation, the gadget driver will respond to + * host enumeration procedures. + * + * Returns zero on success, else negative errno. + */ +int usb_function_activate(struct usb_function *function) +{ + struct usb_composite_dev *cdev = function->config->cdev; + int status = 0; + + if (cdev->deactivations == 0) + status = -EINVAL; + else { + cdev->deactivations--; + if (cdev->deactivations == 0) + status = usb_gadget_connect(cdev->gadget); + } + + return status; +} + +/** + * usb_interface_id() - allocate an unused interface ID + * @config: configuration associated with the interface + * @function: function handling the interface + * Context: single threaded during gadget setup + * + * usb_interface_id() is called from usb_function.bind() callbacks to + * allocate new interface IDs. The function driver will then store that + * ID in interface, association, CDC union, and other descriptors. It + * will also handle any control requests targetted at that interface, + * particularly changing its altsetting via set_alt(). There may + * also be class-specific or vendor-specific requests to handle. + * + * All interface identifier should be allocated using this routine, to + * ensure that for example different functions don't wrongly assign + * different meanings to the same identifier. Note that since interface + * identifers are configuration-specific, functions used in more than + * one configuration (or more than once in a given configuration) need + * multiple versions of the relevant descriptors. + * + * Returns the interface ID which was allocated; or -ENODEV if no + * more interface IDs can be allocated. + */ +int usb_interface_id(struct usb_configuration *config, + struct usb_function *function) +{ + unsigned char id = config->next_interface_id; + + if (id < MAX_CONFIG_INTERFACES) { + config->interface[id] = function; + config->next_interface_id = id + 1; + return id; + } + return -ENODEV; +} + +static int config_buf(struct usb_configuration *config, + enum usb_device_speed speed, void *buf, u8 type) +{ + int len = USB_BUFSIZ - USB_DT_CONFIG_SIZE; + void *next = buf + USB_DT_CONFIG_SIZE; + struct usb_descriptor_header **descriptors; + struct usb_config_descriptor *c; + int status; + struct usb_function *f; + + /* write the config descriptor */ + c = buf; + c->bLength = USB_DT_CONFIG_SIZE; + c->bDescriptorType = type; + + c->bNumInterfaces = config->next_interface_id; + c->bConfigurationValue = config->bConfigurationValue; + c->iConfiguration = config->iConfiguration; + c->bmAttributes = USB_CONFIG_ATT_ONE | config->bmAttributes; + c->bMaxPower = config->bMaxPower ? : (CONFIG_USB_GADGET_VBUS_DRAW / 2); + + /* There may be e.g. OTG descriptors */ + if (config->descriptors) { + status = usb_descriptor_fillbuf(next, len, + config->descriptors); + if (status < 0) + return status; + len -= status; + next += status; + } + + /* add each function's descriptors */ + list_for_each_entry(f, &config->functions, list) { + if (speed == USB_SPEED_SUPER) + descriptors = f->ss_descriptors; + else if (speed == USB_SPEED_HIGH) + descriptors = f->hs_descriptors; + else + descriptors = f->descriptors; + if (!descriptors) + continue; + status = usb_descriptor_fillbuf(next, len, + (const struct usb_descriptor_header **) descriptors); + if (status < 0) + return status; + len -= status; + next += status; + } + + len = next - buf; + c->wTotalLength = cpu_to_le16(len); + return len; +} + +static int config_desc(struct usb_composite_dev *cdev, unsigned w_value) +{ + enum usb_device_speed speed = USB_SPEED_UNKNOWN; + struct usb_gadget *gadget = cdev->gadget; + u8 type = w_value >> 8; + int hs = 0; + struct usb_configuration *c; + struct list_head *pos; + + if (gadget_is_superspeed(gadget)) { + speed = gadget->speed; + } else if (gadget_is_dualspeed(gadget)) { + if (gadget->speed == USB_SPEED_HIGH) + hs = 1; + if (type == USB_DT_OTHER_SPEED_CONFIG) + hs = !hs; + if (hs) + speed = USB_SPEED_HIGH; + } + + w_value &= 0xff; + + pos = &cdev->configs; + c = cdev->os_desc_config; + if (c) + goto check_config; + + while ((pos = pos->next) != &cdev->configs) { + c = list_entry(pos, typeof(*c), list); + + /* skip OS Descriptors config which is handled separately */ + if (c == cdev->os_desc_config) + continue; + +check_config: + if (speed == USB_SPEED_SUPER) { + if (!c->superspeed) + continue; + } else if (speed == USB_SPEED_HIGH) { + if (!c->highspeed) + continue; + } else { + if (!c->fullspeed) + continue; + } + if (w_value == 0) + return config_buf(c, speed, cdev->req->buf, type); + w_value--; + } + return -EINVAL; +} + +static int count_configs(struct usb_composite_dev *cdev, unsigned type) +{ + struct usb_gadget *gadget = cdev->gadget; + unsigned count = 0; + int hs = 0; + int ss = 0; + struct usb_configuration *c; + + if (gadget->speed == USB_SPEED_SUPER) + ss = 1; + + if (gadget_is_dualspeed(gadget)) { + if (gadget->speed == USB_SPEED_HIGH) + hs = 1; + if (type == USB_DT_DEVICE_QUALIFIER) + hs = !hs; + } + list_for_each_entry(c, &cdev->configs, list) { + /* ignore configs that won't work at this speed */ + if (ss) { + if (!c->superspeed) + continue; + } else if (hs) { + if (!c->highspeed) + continue; + } else { + if (!c->fullspeed) + continue; + } + count++; + } + return count; +} + +static void device_qual(struct usb_composite_dev *cdev) +{ + struct usb_qualifier_descriptor *qual = cdev->req->buf; + + qual->bLength = sizeof(*qual); + qual->bDescriptorType = USB_DT_DEVICE_QUALIFIER; + /* POLICY: same bcdUSB and device type info at both speeds */ + qual->bcdUSB = cdev->desc.bcdUSB; + qual->bDeviceClass = cdev->desc.bDeviceClass; + qual->bDeviceSubClass = cdev->desc.bDeviceSubClass; + qual->bDeviceProtocol = cdev->desc.bDeviceProtocol; + /* ASSUME same EP0 fifo size at both speeds */ + qual->bMaxPacketSize0 = cdev->gadget->ep0->maxpacket; + qual->bNumConfigurations = count_configs(cdev, USB_DT_DEVICE_QUALIFIER); + qual->bRESERVED = 0; +} + +static void reset_config(struct usb_composite_dev *cdev) +{ + struct usb_function *f; + + debug("%s:\n", __func__); + + list_for_each_entry(f, &cdev->config->functions, list) { + if (f->disable) + f->disable(f); + + bitmap_zero(f->endpoints, 32); + } + cdev->config = NULL; +} + +static int set_config(struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *ctrl, unsigned number) +{ + struct usb_gadget *gadget = cdev->gadget; + unsigned power = gadget_is_otg(gadget) ? 8 : 100; + struct usb_descriptor_header **descriptors; + int result = -EINVAL; + struct usb_endpoint_descriptor *ep; + struct usb_configuration *c = NULL; + int addr; + int tmp; + struct usb_function *f; + + if (cdev->config) + reset_config(cdev); + + if (number) { + list_for_each_entry(c, &cdev->configs, list) { + if (c->bConfigurationValue == number) { + result = 0; + break; + } + } + if (result < 0) + goto done; + } else + result = 0; + + debug("%s: %s speed config #%d: %s\n", __func__, + ({ char *speed; + switch (gadget->speed) { + case USB_SPEED_LOW: + speed = "low"; + break; + case USB_SPEED_FULL: + speed = "full"; + break; + case USB_SPEED_HIGH: + speed = "high"; + break; + case USB_SPEED_SUPER: + speed = "super"; + break; + default: + speed = "?"; + break; + }; + speed; + }), number, c ? c->label : "unconfigured"); + + if (!c) + goto done; + + cdev->config = c; + + /* Initialize all interfaces by setting them to altsetting zero. */ + for (tmp = 0; tmp < MAX_CONFIG_INTERFACES; tmp++) { + f = c->interface[tmp]; + if (!f) + break; + + /* + * Record which endpoints are used by the function. This is used + * to dispatch control requests targeted at that endpoint to the + * function's setup callback instead of the current + * configuration's setup callback. + */ + if (gadget->speed == USB_SPEED_SUPER) + descriptors = f->ss_descriptors; + else if (gadget->speed == USB_SPEED_HIGH) + descriptors = f->hs_descriptors; + else + descriptors = f->descriptors; + + for (; *descriptors; ++descriptors) { + if ((*descriptors)->bDescriptorType != USB_DT_ENDPOINT) + continue; + + ep = (struct usb_endpoint_descriptor *)*descriptors; + addr = ((ep->bEndpointAddress & 0x80) >> 3) + | (ep->bEndpointAddress & 0x0f); + generic_set_bit(addr, f->endpoints); + } + + result = f->set_alt(f, tmp, 0); + if (result < 0) { + debug("interface %d (%s/%p) alt 0 --> %d\n", + tmp, f->name, f, result); + + reset_config(cdev); + goto done; + } + } + + /* when we return, be sure our power usage is valid */ + power = c->bMaxPower ? (2 * c->bMaxPower) : CONFIG_USB_GADGET_VBUS_DRAW; +done: + usb_gadget_vbus_draw(gadget, power); + return result; +} + +/** + * usb_add_config() - add a configuration to a device. + * @cdev: wraps the USB gadget + * @config: the configuration, with bConfigurationValue assigned + * Context: single threaded during gadget setup + * + * One of the main tasks of a composite driver's bind() routine is to + * add each of the configurations it supports, using this routine. + * + * This function returns the value of the configuration's bind(), which + * is zero for success else a negative errno value. Binding configurations + * assigns global resources including string IDs, and per-configuration + * resources such as interface IDs and endpoints. + */ +int usb_add_config(struct usb_composite_dev *cdev, + struct usb_configuration *config) +{ + int status = -EINVAL; + struct usb_configuration *c; + struct usb_function *f; + unsigned int i; + + debug("%s: adding config #%u '%s'/%p\n", __func__, + config->bConfigurationValue, + config->label, config); + + if (!config->bConfigurationValue || !config->bind) + goto done; + + /* Prevent duplicate configuration identifiers */ + list_for_each_entry(c, &cdev->configs, list) { + if (c->bConfigurationValue == config->bConfigurationValue) { + status = -EBUSY; + goto done; + } + } + + config->cdev = cdev; + list_add_tail(&config->list, &cdev->configs); + + INIT_LIST_HEAD(&config->functions); + config->next_interface_id = 0; + + status = config->bind(config); + if (status < 0) { + list_del(&config->list); + config->cdev = NULL; + } else { + debug("cfg %d/%p speeds:%s%s%s\n", + config->bConfigurationValue, config, + config->superspeed ? " super" : "", + config->highspeed ? " high" : "", + config->fullspeed + ? (gadget_is_dualspeed(cdev->gadget) + ? " full" + : " full/low") + : ""); + + for (i = 0; i < MAX_CONFIG_INTERFACES; i++) { + f = config->interface[i]; + if (!f) + continue; + debug("%s: interface %d = %s/%p\n", + __func__, i, f->name, f); + } + } + + /* + * If one function of config is not super speed capable, + * force the gadget to be high speed so controller driver + * can init HW to be USB 2.0 + */ + if (gadget_is_superspeed(cdev->gadget)) { + list_for_each_entry(f, &config->functions, list) { + if (!f->ss_descriptors) + cdev->gadget->max_speed = + USB_SPEED_HIGH; + } + } + + usb_ep_autoconfig_reset(cdev->gadget); + + os_desc_config = config; + cdev->os_desc_config = os_desc_config; + +done: + if (status) + debug("added config '%s'/%u --> %d\n", config->label, + config->bConfigurationValue, status); + return status; +} + +/* + * We support strings in multiple languages ... string descriptor zero + * says which languages are supported. The typical case will be that + * only one language (probably English) is used, with I18N handled on + * the host side. + */ + +static void collect_langs(struct usb_gadget_strings **sp, void *buf) +{ + const struct usb_gadget_strings *s; + u16 language; + __le16_packed *tmp; + __le16_packed *end = (buf + 252); + + while (*sp) { + s = *sp; + language = cpu_to_le16(s->language); + for (tmp = buf; tmp->val && tmp < end; tmp++) { + if (tmp->val == language) + goto repeat; + } + tmp->val = language; +repeat: + sp++; + } +} + +static int lookup_string( + struct usb_gadget_strings **sp, + void *buf, + u16 language, + int id +) +{ + int value; + struct usb_gadget_strings *s; + + while (*sp) { + s = *sp++; + if (s->language != language) + continue; + value = usb_gadget_get_string(s, id, buf); + if (value > 0) + return value; + } + return -EINVAL; +} + +static int get_string(struct usb_composite_dev *cdev, + void *buf, u16 language, int id) +{ + struct usb_string_descriptor *s = buf; + struct usb_gadget_strings **sp; + int len; + struct usb_configuration *c; + struct usb_function *f; + + /* + * Yes, not only is USB's I18N support probably more than most + * folk will ever care about ... also, it's all supported here. + * (Except for UTF8 support for Unicode's "Astral Planes".) + */ + + /* 0 == report all available language codes */ + if (id == 0) { + memset(s, 0, 256); + s->bDescriptorType = USB_DT_STRING; + + sp = composite->strings; + if (sp) + collect_langs(sp, s->wData); + + list_for_each_entry(c, &cdev->configs, list) { + sp = c->strings; + if (sp) + collect_langs(sp, s->wData); + + list_for_each_entry(f, &c->functions, list) { + sp = f->strings; + if (sp) + collect_langs(sp, s->wData); + } + } + + for (len = 0; len <= 126 && s->wData[len]; len++) + continue; + if (!len) + return -EINVAL; + + s->bLength = 2 * (len + 1); + return s->bLength; + } + + if (cdev->use_os_string && language == 0 && id == OS_STRING_IDX) { + struct usb_os_string *b = buf; + b->bLength = sizeof(*b); + b->bDescriptorType = USB_DT_STRING; + memcpy(&b->qwSignature, cdev->qw_sign, sizeof(b->qwSignature)); + b->bMS_VendorCode = cdev->b_vendor_code; + b->bPad = 0; + return sizeof(*b); + } + + /* + * Otherwise, look up and return a specified string. String IDs + * are device-scoped, so we look up each string table we're told + * about. These lookups are infrequent; simpler-is-better here. + */ + if (composite->strings) { + len = lookup_string(composite->strings, buf, language, id); + if (len > 0) + return len; + } + list_for_each_entry(c, &cdev->configs, list) { + if (c->strings) { + len = lookup_string(c->strings, buf, language, id); + if (len > 0) + return len; + } + list_for_each_entry(f, &c->functions, list) { + if (!f->strings) + continue; + len = lookup_string(f->strings, buf, language, id); + if (len > 0) + return len; + } + } + return -EINVAL; +} + +/** + * usb_string_id() - allocate an unused string ID + * @cdev: the device whose string descriptor IDs are being allocated + * Context: single threaded during gadget setup + * + * @usb_string_id() is called from bind() callbacks to allocate + * string IDs. Drivers for functions, configurations, or gadgets will + * then store that ID in the appropriate descriptors and string table. + * + * All string identifier should be allocated using this, + * @usb_string_ids_tab() or @usb_string_ids_n() routine, to ensure + * that for example different functions don't wrongly assign different + * meanings to the same identifier. + */ +int usb_string_id(struct usb_composite_dev *cdev) +{ + if (cdev->next_string_id < 254) { + /* + * string id 0 is reserved by USB spec for list of + * supported languages + * 255 reserved as well? -- mina86 + */ + cdev->next_string_id++; + return cdev->next_string_id; + } + return -ENODEV; +} + +/** + * usb_string_ids() - allocate unused string IDs in batch + * @cdev: the device whose string descriptor IDs are being allocated + * @str: an array of usb_string objects to assign numbers to + * Context: single threaded during gadget setup + * + * @usb_string_ids() is called from bind() callbacks to allocate + * string IDs. Drivers for functions, configurations, or gadgets will + * then copy IDs from the string table to the appropriate descriptors + * and string table for other languages. + * + * All string identifier should be allocated using this, + * @usb_string_id() or @usb_string_ids_n() routine, to ensure that for + * example different functions don't wrongly assign different meanings + * to the same identifier. + */ +int usb_string_ids_tab(struct usb_composite_dev *cdev, struct usb_string *str) +{ + u8 next = cdev->next_string_id; + + for (; str->s; ++str) { + if (next >= 254) + return -ENODEV; + str->id = ++next; + } + + cdev->next_string_id = next; + + return 0; +} + +/** + * usb_string_ids_n() - allocate unused string IDs in batch + * @c: the device whose string descriptor IDs are being allocated + * @n: number of string IDs to allocate + * Context: single threaded during gadget setup + * + * Returns the first requested ID. This ID and next @n-1 IDs are now + * valid IDs. At least provided that @n is non-zero because if it + * is, returns last requested ID which is now very useful information. + * + * @usb_string_ids_n() is called from bind() callbacks to allocate + * string IDs. Drivers for functions, configurations, or gadgets will + * then store that ID in the appropriate descriptors and string table. + * + * All string identifier should be allocated using this, + * @usb_string_id() or @usb_string_ids_n() routine, to ensure that for + * example different functions don't wrongly assign different meanings + * to the same identifier. + */ +int usb_string_ids_n(struct usb_composite_dev *c, unsigned n) +{ + u8 next = c->next_string_id; + + if (n > 254 || next + n > 254) + return -ENODEV; + + c->next_string_id += n; + return next + 1; +} + +static void composite_setup_complete(struct usb_ep *ep, struct usb_request *req) +{ + if (req->status || req->actual != req->length) + debug("%s: setup complete --> %d, %d/%d\n", __func__, + req->status, req->actual, req->length); +} + +static int bos_desc(struct usb_composite_dev *cdev) +{ + struct usb_ext_cap_descriptor *usb_ext; + struct usb_dcd_config_params dcd_config_params; + struct usb_bos_descriptor *bos = cdev->req->buf; + + bos->bLength = USB_DT_BOS_SIZE; + bos->bDescriptorType = USB_DT_BOS; + + bos->wTotalLength = cpu_to_le16(USB_DT_BOS_SIZE); + bos->bNumDeviceCaps = 0; + + /* + * A SuperSpeed device shall include the USB2.0 extension descriptor + * and shall support LPM when operating in USB2.0 HS mode. + */ + usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength); + bos->bNumDeviceCaps++; + le16_add_cpu_packed((__le16_packed *)&bos->wTotalLength, + USB_DT_USB_EXT_CAP_SIZE); + usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE; + usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY; + usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT; + usb_ext->bmAttributes = + cpu_to_le32(USB_LPM_SUPPORT | USB_BESL_SUPPORT); + + /* + * The Superspeed USB Capability descriptor shall be implemented + * by all SuperSpeed devices. + */ + if (gadget_is_superspeed(cdev->gadget)) { + struct usb_ss_cap_descriptor *ss_cap; + + ss_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength); + bos->bNumDeviceCaps++; + le16_add_cpu_packed((__le16_packed *)&bos->wTotalLength, + USB_DT_USB_SS_CAP_SIZE); + ss_cap->bLength = USB_DT_USB_SS_CAP_SIZE; + ss_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY; + ss_cap->bDevCapabilityType = USB_SS_CAP_TYPE; + ss_cap->bmAttributes = 0; /* LTM is not supported yet */ + ss_cap->wSpeedSupported = + cpu_to_le16(USB_LOW_SPEED_OPERATION | + USB_FULL_SPEED_OPERATION | + USB_HIGH_SPEED_OPERATION | + USB_5GBPS_OPERATION); + ss_cap->bFunctionalitySupport = USB_LOW_SPEED_OPERATION; + + /* Get Controller configuration */ + if (cdev->gadget->ops->get_config_params) { + cdev->gadget->ops->get_config_params( + &dcd_config_params); + } else { + dcd_config_params.bU1devExitLat = + USB_DEFAULT_U1_DEV_EXIT_LAT; + dcd_config_params.bU2DevExitLat = + cpu_to_le16(USB_DEFAULT_U2_DEV_EXIT_LAT); + } + ss_cap->bU1devExitLat = dcd_config_params.bU1devExitLat; + ss_cap->bU2DevExitLat = dcd_config_params.bU2DevExitLat; + } + return le16_to_cpu(bos->wTotalLength); +} + +static int count_ext_compat(struct usb_configuration *c) +{ + int i, res; + + res = 0; + for (i = 0; i < c->next_interface_id; ++i) { + struct usb_function *f; + int j; + + f = c->interface[i]; + for (j = 0; j < f->os_desc_n; ++j) { + struct usb_os_desc *d; + + if (i != f->os_desc_table[j].if_id) + continue; + d = f->os_desc_table[j].os_desc; + if (d && d->ext_compat_id) + ++res; + } + } + BUG_ON(res > 255); + return res; +} + +static void fill_ext_compat(struct usb_configuration *c, u8 *buf) +{ + int i, count; + + count = 16; + for (i = 0; i < c->next_interface_id; ++i) { + struct usb_function *f; + int j; + + f = c->interface[i]; + for (j = 0; j < f->os_desc_n; ++j) { + struct usb_os_desc *d; + + if (i != f->os_desc_table[j].if_id) + continue; + d = f->os_desc_table[j].os_desc; + if (d && d->ext_compat_id) { + *buf++ = i; + *buf++ = 0x01; + memcpy(buf, d->ext_compat_id, 16); + buf += 22; + } else { + ++buf; + *buf = 0x01; + buf += 23; + } + count += 24; + if (count >= 4096) + return; + } + } +} + +static int count_ext_prop(struct usb_configuration *c, int interface) +{ + struct usb_function *f; + int j; + + f = c->interface[interface]; + for (j = 0; j < f->os_desc_n; ++j) { + struct usb_os_desc *d; + + if (interface != f->os_desc_table[j].if_id) + continue; + d = f->os_desc_table[j].os_desc; + if (d && d->ext_compat_id) + return d->ext_prop_count; + } + return 0; +} + +static int len_ext_prop(struct usb_configuration *c, int interface) +{ + struct usb_function *f; + struct usb_os_desc *d; + int j, res; + + res = 10; /* header length */ + f = c->interface[interface]; + for (j = 0; j < f->os_desc_n; ++j) { + if (interface != f->os_desc_table[j].if_id) + continue; + d = f->os_desc_table[j].os_desc; + if (d) + return min(res + d->ext_prop_len, 4096); + } + return res; +} + +static int fill_ext_prop(struct usb_configuration *c, int interface, u8 *buf) +{ + struct usb_function *f; + struct usb_os_desc *d; + struct usb_os_desc_ext_prop *ext_prop; + int j, count, n, ret; + u8 *start = buf; + + f = c->interface[interface]; + for (j = 0; j < f->os_desc_n; ++j) { + if (interface != f->os_desc_table[j].if_id) + continue; + d = f->os_desc_table[j].os_desc; + if (d) + list_for_each_entry(ext_prop, &d->ext_prop, entry) { + /* 4kB minus header length */ + n = buf - start; + if (n >= 4086) + return 0; + + count = ext_prop->data_len + + ext_prop->name_len + 14; + if (count > 4086 - n) + return -EINVAL; + usb_ext_prop_put_size(buf, count); + usb_ext_prop_put_type(buf, ext_prop->type); + ret = usb_ext_prop_put_name(buf, ext_prop->name, + ext_prop->name_len); + if (ret < 0) + return ret; + switch (ext_prop->type) { + case USB_EXT_PROP_UNICODE: + case USB_EXT_PROP_UNICODE_ENV: + case USB_EXT_PROP_UNICODE_LINK: + usb_ext_prop_put_unicode(buf, ret, + ext_prop->data, + ext_prop->data_len); + break; + case USB_EXT_PROP_BINARY: + usb_ext_prop_put_binary(buf, ret, + ext_prop->data, + ext_prop->data_len); + break; + case USB_EXT_PROP_LE32: + /* not implemented */ + case USB_EXT_PROP_BE32: + /* not implemented */ + default: + return -EINVAL; + } + buf += count; + } + } + + return 0; +} + +/* + * The setup() callback implements all the ep0 functionality that's + * not handled lower down, in hardware or the hardware driver(like + * device and endpoint feature flags, and their status). It's all + * housekeeping for the gadget function we're implementing. Most of + * the work is in config and function specific setup. + */ +static int +composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) +{ + u16 w_length = le16_to_cpu(ctrl->wLength); + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + struct usb_composite_dev *cdev = get_gadget_data(gadget); + u8 intf = w_index & 0xFF; + int value = -EOPNOTSUPP; + struct usb_request *req = cdev->req; + struct usb_function *f = NULL; + int standard; + u8 endp; + struct usb_configuration *c; + + /* + * partial re-init of the response message; the function or the + * gadget might need to intercept e.g. a control-OUT completion + * when we delegate to it. + */ + req->zero = 0; + req->complete = composite_setup_complete; + req->length = USB_BUFSIZ; + gadget->ep0->driver_data = cdev; + standard = (ctrl->bRequestType & USB_TYPE_MASK) + == USB_TYPE_STANDARD; + if (!standard) + goto unknown; + + switch (ctrl->bRequest) { + + /* we handle all standard USB descriptors */ + case USB_REQ_GET_DESCRIPTOR: + if (ctrl->bRequestType != USB_DIR_IN) + goto unknown; + switch (w_value >> 8) { + + case USB_DT_DEVICE: + cdev->desc.bNumConfigurations = + count_configs(cdev, USB_DT_DEVICE); + + cdev->desc.bMaxPacketSize0 = + cdev->gadget->ep0->maxpacket; + if (gadget->speed >= USB_SPEED_SUPER) { + cdev->desc.bcdUSB = cpu_to_le16(0x0310); + cdev->desc.bMaxPacketSize0 = 9; + } else { + cdev->desc.bcdUSB = cpu_to_le16(0x0200); + } + value = min(w_length, (u16) sizeof cdev->desc); + memcpy(req->buf, &cdev->desc, value); + break; + case USB_DT_DEVICE_QUALIFIER: + if (!gadget_is_dualspeed(gadget) || + gadget->speed >= USB_SPEED_SUPER) + break; + device_qual(cdev); + value = min_t(int, w_length, + sizeof(struct usb_qualifier_descriptor)); + break; + case USB_DT_OTHER_SPEED_CONFIG: + if (!gadget_is_dualspeed(gadget) || + gadget->speed >= USB_SPEED_SUPER) + break; + + case USB_DT_CONFIG: + value = config_desc(cdev, w_value); + if (value >= 0) + value = min(w_length, (u16) value); + break; + case USB_DT_STRING: + value = get_string(cdev, req->buf, + w_index, w_value & 0xff); + if (value >= 0) + value = min(w_length, (u16) value); + break; + case USB_DT_BOS: + /* + * Super speed connection should support BOS, and + * USB compliance test (USB 2.0 Command Verifier) + * also issues this request, return for now for + * USB 2.0 connection. + */ + if (gadget->speed >= USB_SPEED_SUPER) { + value = bos_desc(cdev); + value = min(w_length, (u16)value); + } + break; + default: + goto unknown; + } + break; + + /* any number of configs can work */ + case USB_REQ_SET_CONFIGURATION: + if (ctrl->bRequestType != 0) + goto unknown; + if (gadget_is_otg(gadget)) { + if (gadget->a_hnp_support) + debug("HNP available\n"); + else if (gadget->a_alt_hnp_support) + debug("HNP on another port\n"); + else + debug("HNP inactive\n"); + } + + value = set_config(cdev, ctrl, w_value); + break; + case USB_REQ_GET_CONFIGURATION: + if (ctrl->bRequestType != USB_DIR_IN) + goto unknown; + if (cdev->config) + *(u8 *)req->buf = cdev->config->bConfigurationValue; + else + *(u8 *)req->buf = 0; + value = min(w_length, (u16) 1); + break; + + /* + * function drivers must handle get/set altsetting; if there's + * no get() method, we know only altsetting zero works. + */ + case USB_REQ_SET_INTERFACE: + if (ctrl->bRequestType != USB_RECIP_INTERFACE) + goto unknown; + if (!cdev->config || w_index >= MAX_CONFIG_INTERFACES) + break; + f = cdev->config->interface[intf]; + if (!f) + break; + if (w_value && !f->set_alt) + break; + value = f->set_alt(f, w_index, w_value); + break; + case USB_REQ_GET_INTERFACE: + if (ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE)) + goto unknown; + if (!cdev->config || w_index >= MAX_CONFIG_INTERFACES) + break; + f = cdev->config->interface[intf]; + if (!f) + break; + /* lots of interfaces only need altsetting zero... */ + value = f->get_alt ? f->get_alt(f, w_index) : 0; + if (value < 0) + break; + *((u8 *)req->buf) = value; + value = min(w_length, (u16) 1); + break; + default: +unknown: + /* + * OS descriptors handling + */ + if (CONFIG_IS_ENABLED(USB_GADGET_OS_DESCRIPTORS) && cdev->use_os_string && + cdev->os_desc_config && (ctrl->bRequestType & USB_TYPE_VENDOR) && + ctrl->bRequest == cdev->b_vendor_code) { + struct usb_configuration *os_desc_cfg; + u8 *buf; + int interface; + int count = 0; + + buf = req->buf; + os_desc_cfg = cdev->os_desc_config; + memset(buf, 0, w_length); + buf[5] = 0x01; + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + if (w_index != 0x4 || (w_value >> 8)) + break; + buf[6] = w_index; + if (w_length == 0x10) { + /* Number of ext compat interfaces */ + count = count_ext_compat(os_desc_cfg); + buf[8] = count; + count *= 24; /* 24 B/ext compat desc */ + count += 16; /* header */ + put_unaligned_le32(count, buf); + value = w_length; + } else { + /* "extended compatibility ID"s */ + count = count_ext_compat(os_desc_cfg); + buf[8] = count; + count *= 24; /* 24 B/ext compat desc */ + count += 16; /* header */ + put_unaligned_le32(count, buf); + buf += 16; + fill_ext_compat(os_desc_cfg, buf); + value = w_length; + } + break; + case USB_RECIP_INTERFACE: + if (w_index != 0x5 || (w_value >> 8)) + break; + interface = w_value & 0xFF; + buf[6] = w_index; + if (w_length == 0x0A) { + count = count_ext_prop(os_desc_cfg, + interface); + put_unaligned_le16(count, buf + 8); + count = len_ext_prop(os_desc_cfg, + interface); + put_unaligned_le32(count, buf); + + value = w_length; + } else { + count = count_ext_prop(os_desc_cfg, + interface); + put_unaligned_le16(count, buf + 8); + count = len_ext_prop(os_desc_cfg, + interface); + put_unaligned_le32(count, buf); + buf += 10; + value = fill_ext_prop(os_desc_cfg, + interface, buf); + if (value < 0) + return value; + + value = w_length; + } + break; + } + + if (value >= 0) { + req->length = value; + req->zero = value < w_length; + value = usb_ep_queue(gadget->ep0, req, GFP_KERNEL); + if (value < 0) { + debug("ep_queue --> %d\n", value); + req->status = 0; + composite_setup_complete(gadget->ep0, req); + } + } + return value; + } + + debug("non-core control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + + if (!cdev->config) + goto done; + + /* + * functions always handle their interfaces and endpoints... + * punt other recipients (other, WUSB, ...) to the current + * configuration code. + */ + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_INTERFACE: + f = cdev->config->interface[intf]; + break; + + case USB_RECIP_ENDPOINT: + endp = ((w_index & 0x80) >> 3) | (w_index & 0x0f); + list_for_each_entry(f, &cdev->config->functions, list) { + if (test_bit(endp, f->endpoints)) + break; + } + if (&f->list == &cdev->config->functions) + f = NULL; + break; + /* + * dfu-util (version 0.5) sets bmRequestType.Receipent = Device + * for non-standard request (w_value = 0x21, + * bRequest = GET_DESCRIPTOR in this case). + * When only one interface is registered (as it is done now), + * then this request shall be handled as it was requested for + * interface. + * + * In the below code it is checked if only one interface is + * present and proper function for it is extracted. Due to that + * function's setup (f->setup) is called to handle this + * special non-standard request. + */ + case USB_RECIP_DEVICE: + debug("cdev->config->next_interface_id: %d intf: %d\n", + cdev->config->next_interface_id, intf); + if (cdev->config->next_interface_id == 1) + f = cdev->config->interface[intf]; + break; + } + + if (f && f->setup) + value = f->setup(f, ctrl); + else { + c = cdev->config; + if (c->setup) + value = c->setup(c, ctrl); + } + + goto done; + } + + /* respond with data transfer before status phase? */ + if (value >= 0) { + req->length = value; + req->zero = value < w_length; + value = usb_ep_queue(gadget->ep0, req, GFP_KERNEL); + if (value < 0) { + debug("ep_queue --> %d\n", value); + req->status = 0; + composite_setup_complete(gadget->ep0, req); + } + } + +done: + /* device either stalls (value < 0) or reports success */ + return value; +} + +static void composite_disconnect(struct usb_gadget *gadget) +{ + struct usb_composite_dev *cdev = get_gadget_data(gadget); + + if (cdev->config) + reset_config(cdev); + if (composite->disconnect) + composite->disconnect(cdev); +} + +static void composite_unbind(struct usb_gadget *gadget) +{ + struct usb_composite_dev *cdev = get_gadget_data(gadget); + struct usb_configuration *c; + struct usb_function *f; + + /* + * composite_disconnect() must already have been called + * by the underlying peripheral controller driver! + * so there's no i/o concurrency that could affect the + * state protected by cdev->lock. + */ +#ifdef __UBOOT__ + assert_noisy(!cdev->config); +#else + BUG_ON(cdev->config); +#endif + + while (!list_empty(&cdev->configs)) { + c = list_first_entry(&cdev->configs, + struct usb_configuration, list); + while (!list_empty(&c->functions)) { + f = list_first_entry(&c->functions, + struct usb_function, list); + list_del(&f->list); + if (f->unbind) { + debug("unbind function '%s'/%p\n", + f->name, f); + f->unbind(c, f); + } + } + list_del(&c->list); + if (c->unbind) { + debug("unbind config '%s'/%p\n", c->label, c); + c->unbind(c); + } + free(c); + } + if (composite->unbind) + composite->unbind(cdev); + + if (cdev->req) { + kfree(cdev->req->buf); + usb_ep_free_request(gadget->ep0, cdev->req); + } + kfree(cdev); + set_gadget_data(gadget, NULL); + + composite = NULL; +} + +static int composite_bind(struct usb_gadget *gadget) +{ + int status = -ENOMEM; + struct usb_composite_dev *cdev; + + cdev = calloc(sizeof *cdev, 1); + if (!cdev) + return status; + + cdev->gadget = gadget; + set_gadget_data(gadget, cdev); + INIT_LIST_HEAD(&cdev->configs); + + /* preallocate control response and buffer */ + cdev->req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL); + if (!cdev->req) + goto fail; + cdev->req->buf = memalign(CONFIG_SYS_CACHELINE_SIZE, USB_BUFSIZ); + if (!cdev->req->buf) + goto fail; + cdev->req->complete = composite_setup_complete; + gadget->ep0->driver_data = cdev; + + cdev->bufsiz = USB_BUFSIZ; + cdev->driver = composite; + + usb_gadget_set_selfpowered(gadget); + usb_ep_autoconfig_reset(cdev->gadget); + + status = composite->bind(cdev); + if (status < 0) + goto fail; + + memcpy(&cdev->desc, composite->dev, + sizeof(struct usb_device_descriptor)); + cdev->desc.bMaxPacketSize0 = gadget->ep0->maxpacket; + + if (cdev->use_os_string) { + /* TODO: Do we want to pass this via platform? */ + cdev->b_vendor_code = 0x40; + + /* Microsoft OS String Descriptor */ + utf8_to_utf16le(qw_sign_buf, (__le16 *)cdev->qw_sign, + OS_STRING_QW_SIGN_LEN / 2); + } + + debug("%s: ready\n", composite->name); + return 0; + +fail: + composite_unbind(gadget); + return status; +} + +static void +composite_suspend(struct usb_gadget *gadget) +{ + struct usb_composite_dev *cdev = get_gadget_data(gadget); + struct usb_function *f; + + debug("%s: suspend\n", __func__); + if (cdev->config) { + list_for_each_entry(f, &cdev->config->functions, list) { + if (f->suspend) + f->suspend(f); + } + } + if (composite->suspend) + composite->suspend(cdev); + + cdev->suspended = 1; +} + +static void +composite_resume(struct usb_gadget *gadget) +{ + struct usb_composite_dev *cdev = get_gadget_data(gadget); + struct usb_function *f; + + debug("%s: resume\n", __func__); + if (composite->resume) + composite->resume(cdev); + if (cdev->config) { + list_for_each_entry(f, &cdev->config->functions, list) { + if (f->resume) + f->resume(f); + } + } + + cdev->suspended = 0; +} + +static struct usb_gadget_driver composite_driver = { + .speed = USB_SPEED_SUPER, + + .bind = composite_bind, + .unbind = composite_unbind, + + .setup = composite_setup, + .reset = composite_disconnect, + .disconnect = composite_disconnect, + + .suspend = composite_suspend, + .resume = composite_resume, +}; + +/** + * usb_composite_register() - register a composite driver + * @driver: the driver to register + * Context: single threaded during gadget setup + * + * This function is used to register drivers using the composite driver + * framework. The return value is zero, or a negative errno value. + * Those values normally come from the driver's @bind method, which does + * all the work of setting up the driver to match the hardware. + * + * On successful return, the gadget is ready to respond to requests from + * the host, unless one of its components invokes usb_gadget_disconnect() + * while it was binding. That would usually be done in order to wait for + * some userspace participation. + */ +int usb_composite_register(struct usb_composite_driver *driver) +{ + int res; + + if (!driver || !driver->dev || !driver->bind || composite) + return -EINVAL; + + if (!driver->name) + driver->name = "composite"; + composite = driver; + + res = usb_gadget_register_driver(&composite_driver); + if (res != 0) + composite = NULL; + + return res; +} + +/** + * usb_composite_unregister() - unregister a composite driver + * @driver: the driver to unregister + * + * This function is used to unregister drivers using the composite + * driver framework. + */ +void usb_composite_unregister(struct usb_composite_driver *driver) +{ + if (composite != driver) + return; + usb_gadget_unregister_driver(&composite_driver); + composite = NULL; +} diff --git a/roms/u-boot/drivers/usb/gadget/config.c b/roms/u-boot/drivers/usb/gadget/config.c new file mode 100644 index 000000000..e96782644 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/config.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * usb/gadget/config.c -- simplify building config descriptors + * + * Copyright (C) 2003 David Brownell + * + * Ported to U-Boot by: Thomas Smits <ts.smits@gmail.com> and + * Remy Bohmer <linux@bohmer.net> + */ + +#include <common.h> +#include <asm/unaligned.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/string.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + + +/** + * usb_descriptor_fillbuf - fill buffer with descriptors + * @buf: Buffer to be filled + * @buflen: Size of buf + * @src: Array of descriptor pointers, terminated by null pointer. + * + * Copies descriptors into the buffer, returning the length or a + * negative error code if they can't all be copied. Useful when + * assembling descriptors for an associated set of interfaces used + * as part of configuring a composite device; or in other cases where + * sets of descriptors need to be marshaled. + */ +int +usb_descriptor_fillbuf(void *buf, unsigned buflen, + const struct usb_descriptor_header **src) +{ + u8 *dest = buf; + + if (!src) + return -EINVAL; + + /* fill buffer from src[] until null descriptor ptr */ + for (; NULL != *src; src++) { + unsigned len = (*src)->bLength; + + if (len > buflen) + return -EINVAL; + memcpy(dest, *src, len); + buflen -= len; + dest += len; + } + return dest - (u8 *)buf; +} + + +/** + * usb_gadget_config_buf - builts a complete configuration descriptor + * @config: Header for the descriptor, including characteristics such + * as power requirements and number of interfaces. + * @desc: Null-terminated vector of pointers to the descriptors (interface, + * endpoint, etc) defining all functions in this device configuration. + * @buf: Buffer for the resulting configuration descriptor. + * @length: Length of buffer. If this is not big enough to hold the + * entire configuration descriptor, an error code will be returned. + * + * This copies descriptors into the response buffer, building a descriptor + * for that configuration. It returns the buffer length or a negative + * status code. The config.wTotalLength field is set to match the length + * of the result, but other descriptor fields (including power usage and + * interface count) must be set by the caller. + * + * Gadget drivers could use this when constructing a config descriptor + * in response to USB_REQ_GET_DESCRIPTOR. They will need to patch the + * resulting bDescriptorType value if USB_DT_OTHER_SPEED_CONFIG is needed. + */ +int usb_gadget_config_buf( + const struct usb_config_descriptor *config, + void *buf, + unsigned length, + const struct usb_descriptor_header **desc +) +{ + struct usb_config_descriptor *cp = buf; + int len; + + /* config descriptor first */ + if (length < USB_DT_CONFIG_SIZE || !desc) + return -EINVAL; + /* config need not be aligned */ + memcpy(cp, config, sizeof(*cp)); + + /* then interface/endpoint/class/vendor/... */ + len = usb_descriptor_fillbuf(USB_DT_CONFIG_SIZE + (u8 *)buf, + length - USB_DT_CONFIG_SIZE, desc); + if (len < 0) + return len; + len += USB_DT_CONFIG_SIZE; + if (len > 0xffff) + return -EINVAL; + + /* patch up the config descriptor */ + cp->bLength = USB_DT_CONFIG_SIZE; + cp->bDescriptorType = USB_DT_CONFIG; + put_unaligned_le16(len, &cp->wTotalLength); + cp->bmAttributes |= USB_CONFIG_ATT_ONE; + return len; +} diff --git a/roms/u-boot/drivers/usb/gadget/core.c b/roms/u-boot/drivers/usb/gadget/core.c new file mode 100644 index 000000000..888f0cfea --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/core.c @@ -0,0 +1,633 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2003 + * Gerry Hamel, geh@ti.com, Texas Instruments + * + * Based on + * linux/drivers/usbd/usbd.c.c - USB Device Core Layer + * + * Copyright (c) 2000, 2001, 2002 Lineo + * Copyright (c) 2001 Hewlett Packard + * + * By: + * Stuart Lynne <sl@lineo.com>, + * Tom Rushworth <tbr@lineo.com>, + * Bruce Balden <balden@lineo.com> + */ + +#include <log.h> +#include <malloc.h> +#include <serial.h> +#include <usbdevice.h> + +#define MAX_INTERFACES 2 + + +int maxstrings = 20; + +/* Global variables ************************************************************************** */ + +struct usb_string_descriptor **usb_strings; + +int usb_devices; + +extern struct usb_function_driver ep0_driver; + +int registered_functions; +int registered_devices; + +__maybe_unused static char *usbd_device_events[] = { + "DEVICE_UNKNOWN", + "DEVICE_INIT", + "DEVICE_CREATE", + "DEVICE_HUB_CONFIGURED", + "DEVICE_RESET", + "DEVICE_ADDRESS_ASSIGNED", + "DEVICE_CONFIGURED", + "DEVICE_SET_INTERFACE", + "DEVICE_SET_FEATURE", + "DEVICE_CLEAR_FEATURE", + "DEVICE_DE_CONFIGURED", + "DEVICE_BUS_INACTIVE", + "DEVICE_BUS_ACTIVITY", + "DEVICE_POWER_INTERRUPTION", + "DEVICE_HUB_RESET", + "DEVICE_DESTROY", + "DEVICE_FUNCTION_PRIVATE", +}; + +__maybe_unused static char *usbd_device_status[] = { + "USBD_OPENING", + "USBD_OK", + "USBD_SUSPENDED", + "USBD_CLOSING", +}; + +#define USBD_DEVICE_STATUS(x) (((unsigned int)x <= USBD_CLOSING) ? usbd_device_status[x] : "UNKNOWN") + + +/* Descriptor support functions ************************************************************** */ + + +/** + * usbd_get_string - find and return a string descriptor + * @index: string index to return + * + * Find an indexed string and return a pointer to a it. + */ +struct usb_string_descriptor *usbd_get_string (__u8 index) +{ + if (index >= maxstrings) { + return NULL; + } + return usb_strings[index]; +} + + +/* Access to device descriptor functions ***************************************************** */ + + +/* * + * usbd_device_configuration_instance - find a configuration instance for this device + * @device: + * @configuration: index to configuration, 0 - N-1 + * + * Get specifed device configuration. Index should be bConfigurationValue-1. + */ +static struct usb_configuration_instance *usbd_device_configuration_instance (struct usb_device_instance *device, + unsigned int port, unsigned int configuration) +{ + if (configuration >= device->configurations) + return NULL; + + return device->configuration_instance_array + configuration; +} + + +/* * + * usbd_device_interface_instance + * @device: + * @configuration: index to configuration, 0 - N-1 + * @interface: index to interface + * + * Return the specified interface descriptor for the specified device. + */ +struct usb_interface_instance *usbd_device_interface_instance (struct usb_device_instance *device, int port, int configuration, int interface) +{ + struct usb_configuration_instance *configuration_instance; + + if ((configuration_instance = usbd_device_configuration_instance (device, port, configuration)) == NULL) { + return NULL; + } + if (interface >= configuration_instance->interfaces) { + return NULL; + } + return configuration_instance->interface_instance_array + interface; +} + +/* * + * usbd_device_alternate_descriptor_list + * @device: + * @configuration: index to configuration, 0 - N-1 + * @interface: index to interface + * @alternate: alternate setting + * + * Return the specified alternate descriptor for the specified device. + */ +struct usb_alternate_instance *usbd_device_alternate_instance (struct usb_device_instance *device, int port, int configuration, int interface, int alternate) +{ + struct usb_interface_instance *interface_instance; + + if ((interface_instance = usbd_device_interface_instance (device, port, configuration, interface)) == NULL) { + return NULL; + } + + if (alternate >= interface_instance->alternates) { + return NULL; + } + + return interface_instance->alternates_instance_array + alternate; +} + + +/* * + * usbd_device_device_descriptor + * @device: which device + * @configuration: index to configuration, 0 - N-1 + * @port: which port + * + * Return the specified configuration descriptor for the specified device. + */ +struct usb_device_descriptor *usbd_device_device_descriptor (struct usb_device_instance *device, int port) +{ + return (device->device_descriptor); +} + +/** + * usbd_device_configuration_descriptor + * @device: which device + * @port: which port + * @configuration: index to configuration, 0 - N-1 + * + * Return the specified configuration descriptor for the specified device. + */ +struct usb_configuration_descriptor *usbd_device_configuration_descriptor (struct + usb_device_instance + *device, int port, int configuration) +{ + struct usb_configuration_instance *configuration_instance; + if (!(configuration_instance = usbd_device_configuration_instance (device, port, configuration))) { + return NULL; + } + return (configuration_instance->configuration_descriptor); +} + + +/** + * usbd_device_interface_descriptor + * @device: which device + * @port: which port + * @configuration: index to configuration, 0 - N-1 + * @interface: index to interface + * @alternate: alternate setting + * + * Return the specified interface descriptor for the specified device. + */ +struct usb_interface_descriptor *usbd_device_interface_descriptor (struct usb_device_instance + *device, int port, int configuration, int interface, int alternate) +{ + struct usb_interface_instance *interface_instance; + if (!(interface_instance = usbd_device_interface_instance (device, port, configuration, interface))) { + return NULL; + } + if ((alternate < 0) || (alternate >= interface_instance->alternates)) { + return NULL; + } + return (interface_instance->alternates_instance_array[alternate].interface_descriptor); +} + +/** + * usbd_device_endpoint_descriptor_index + * @device: which device + * @port: which port + * @configuration: index to configuration, 0 - N-1 + * @interface: index to interface + * @alternate: index setting + * @index: which index + * + * Return the specified endpoint descriptor for the specified device. + */ +struct usb_endpoint_descriptor *usbd_device_endpoint_descriptor_index (struct usb_device_instance + *device, int port, int configuration, int interface, int alternate, int index) +{ + struct usb_alternate_instance *alternate_instance; + + if (!(alternate_instance = usbd_device_alternate_instance (device, port, configuration, interface, alternate))) { + return NULL; + } + if (index >= alternate_instance->endpoints) { + return NULL; + } + return *(alternate_instance->endpoints_descriptor_array + index); +} + + +/** + * usbd_device_endpoint_transfersize + * @device: which device + * @port: which port + * @configuration: index to configuration, 0 - N-1 + * @interface: index to interface + * @index: which index + * + * Return the specified endpoint transfer size; + */ +int usbd_device_endpoint_transfersize (struct usb_device_instance *device, int port, int configuration, int interface, int alternate, int index) +{ + struct usb_alternate_instance *alternate_instance; + + if (!(alternate_instance = usbd_device_alternate_instance (device, port, configuration, interface, alternate))) { + return 0; + } + if (index >= alternate_instance->endpoints) { + return 0; + } + return *(alternate_instance->endpoint_transfersize_array + index); +} + + +/** + * usbd_device_endpoint_descriptor + * @device: which device + * @port: which port + * @configuration: index to configuration, 0 - N-1 + * @interface: index to interface + * @alternate: alternate setting + * @endpoint: which endpoint + * + * Return the specified endpoint descriptor for the specified device. + */ +struct usb_endpoint_descriptor *usbd_device_endpoint_descriptor (struct usb_device_instance *device, int port, int configuration, int interface, int alternate, int endpoint) +{ + struct usb_endpoint_descriptor *endpoint_descriptor; + int i; + + for (i = 0; !(endpoint_descriptor = usbd_device_endpoint_descriptor_index (device, port, configuration, interface, alternate, i)); i++) { + if (endpoint_descriptor->bEndpointAddress == endpoint) { + return endpoint_descriptor; + } + } + return NULL; +} + +/** + * usbd_endpoint_halted + * @device: point to struct usb_device_instance + * @endpoint: endpoint to check + * + * Return non-zero if endpoint is halted. + */ +int usbd_endpoint_halted (struct usb_device_instance *device, int endpoint) +{ + return (device->status == USB_STATUS_HALT); +} + + +/** + * usbd_rcv_complete - complete a receive + * @endpoint: + * @len: + * @urb_bad: + * + * Called from rcv interrupt to complete. + */ +void usbd_rcv_complete(struct usb_endpoint_instance *endpoint, int len, int urb_bad) +{ + if (endpoint) { + struct urb *rcv_urb; + + /*usbdbg("len: %d urb: %p\n", len, endpoint->rcv_urb); */ + + /* if we had an urb then update actual_length, dispatch if neccessary */ + if ((rcv_urb = endpoint->rcv_urb)) { + + /*usbdbg("actual: %d buffer: %d\n", */ + /*rcv_urb->actual_length, rcv_urb->buffer_length); */ + + /* check the urb is ok, are we adding data less than the packetsize */ + if (!urb_bad && (len <= endpoint->rcv_packetSize)) { + /*usbdbg("updating actual_length by %d\n",len); */ + + /* increment the received data size */ + rcv_urb->actual_length += len; + + } else { + usberr(" RECV_ERROR actual: %d buffer: %d urb_bad: %d\n", + rcv_urb->actual_length, rcv_urb->buffer_length, urb_bad); + + rcv_urb->actual_length = 0; + rcv_urb->status = RECV_ERROR; + } + } else { + usberr("no rcv_urb!"); + } + } else { + usberr("no endpoint!"); + } + +} + +/** + * usbd_tx_complete - complete a transmit + * @endpoint: + * @resetart: + * + * Called from tx interrupt to complete. + */ +void usbd_tx_complete (struct usb_endpoint_instance *endpoint) +{ + if (endpoint) { + struct urb *tx_urb; + + /* if we have a tx_urb advance or reset, finish if complete */ + if ((tx_urb = endpoint->tx_urb)) { + int sent = endpoint->last; + endpoint->sent += sent; + endpoint->last -= sent; + + if( (endpoint->tx_urb->actual_length - endpoint->sent) <= 0 ) { + tx_urb->actual_length = 0; + endpoint->sent = 0; + endpoint->last = 0; + + /* Remove from active, save for re-use */ + urb_detach(tx_urb); + urb_append(&endpoint->done, tx_urb); + /*usbdbg("done->next %p, tx_urb %p, done %p", */ + /* endpoint->done.next, tx_urb, &endpoint->done); */ + + endpoint->tx_urb = first_urb_detached(&endpoint->tx); + if( endpoint->tx_urb ) { + endpoint->tx_queue--; + usbdbg("got urb from tx list"); + } + if( !endpoint->tx_urb ) { + /*usbdbg("taking urb from done list"); */ + endpoint->tx_urb = first_urb_detached(&endpoint->done); + } + if( !endpoint->tx_urb ) { + usbdbg("allocating new urb for tx_urb"); + endpoint->tx_urb = usbd_alloc_urb(tx_urb->device, endpoint); + } + } + } + } +} + +/* URB linked list functions ***************************************************** */ + +/* + * Initialize an urb_link to be a single element list. + * If the urb_link is being used as a distinguished list head + * the list is empty when the head is the only link in the list. + */ +void urb_link_init (urb_link * ul) +{ + if (ul) { + ul->prev = ul->next = ul; + } +} + +/* + * Detach an urb_link from a list, and set it + * up as a single element list, so no dangling + * pointers can be followed, and so it can be + * joined to another list if so desired. + */ +void urb_detach (struct urb *urb) +{ + if (urb) { + urb_link *ul = &urb->link; + ul->next->prev = ul->prev; + ul->prev->next = ul->next; + urb_link_init (ul); + } +} + +/* + * Return the first urb_link in a list with a distinguished + * head "hd", or NULL if the list is empty. This will also + * work as a predicate, returning NULL if empty, and non-NULL + * otherwise. + */ +urb_link *first_urb_link (urb_link * hd) +{ + urb_link *nx; + if (NULL != hd && NULL != (nx = hd->next) && nx != hd) { + /* There is at least one element in the list */ + /* (besides the distinguished head). */ + return (nx); + } + /* The list is empty */ + return (NULL); +} + +/* + * Return the first urb in a list with a distinguished + * head "hd", or NULL if the list is empty. + */ +struct urb *first_urb (urb_link * hd) +{ + urb_link *nx; + if (NULL == (nx = first_urb_link (hd))) { + /* The list is empty */ + return (NULL); + } + return (p2surround (struct urb, link, nx)); +} + +/* + * Detach and return the first urb in a list with a distinguished + * head "hd", or NULL if the list is empty. + * + */ +struct urb *first_urb_detached (urb_link * hd) +{ + struct urb *urb; + if ((urb = first_urb (hd))) { + urb_detach (urb); + } + return urb; +} + + +/* + * Append an urb_link (or a whole list of + * urb_links) to the tail of another list + * of urb_links. + */ +void urb_append (urb_link * hd, struct urb *urb) +{ + if (hd && urb) { + urb_link *new = &urb->link; + + /* This allows the new urb to be a list of urbs, */ + /* with new pointing at the first, but the link */ + /* must be initialized. */ + /* Order is important here... */ + urb_link *pul = hd->prev; + new->prev->next = hd; + hd->prev = new->prev; + new->prev = pul; + pul->next = new; + } +} + +/* URB create/destroy functions ***************************************************** */ + +/** + * usbd_alloc_urb - allocate an URB appropriate for specified endpoint + * @device: device instance + * @endpoint: endpoint + * + * Allocate an urb structure. The usb device urb structure is used to + * contain all data associated with a transfer, including a setup packet for + * control transfers. + * + * NOTE: endpoint_address MUST contain a direction flag. + */ +struct urb *usbd_alloc_urb (struct usb_device_instance *device, + struct usb_endpoint_instance *endpoint) +{ + struct urb *urb; + + if (!(urb = (struct urb *) malloc (sizeof (struct urb)))) { + usberr (" F A T A L: malloc(%zu) FAILED!!!!", + sizeof (struct urb)); + return NULL; + } + + /* Fill in known fields */ + memset (urb, 0, sizeof (struct urb)); + urb->endpoint = endpoint; + urb->device = device; + urb->buffer = (u8 *) urb->buffer_data; + urb->buffer_length = sizeof (urb->buffer_data); + + urb_link_init (&urb->link); + + return urb; +} + +/** + * usbd_dealloc_urb - deallocate an URB and associated buffer + * @urb: pointer to an urb structure + * + * Deallocate an urb structure and associated data. + */ +void usbd_dealloc_urb (struct urb *urb) +{ + if (urb) { + free (urb); + } +} + +/* Event signaling functions ***************************************************** */ + +/** + * usbd_device_event - called to respond to various usb events + * @device: pointer to struct device + * @event: event to respond to + * + * Used by a Bus driver to indicate an event. + */ +void usbd_device_event_irq (struct usb_device_instance *device, usb_device_event_t event, int data) +{ + usb_device_state_t state; + + if (!device || !device->bus) { + usberr("(%p,%d) NULL device or device->bus", device, event); + return; + } + + state = device->device_state; + + usbinfo("%s", usbd_device_events[event]); + + switch (event) { + case DEVICE_UNKNOWN: + break; + case DEVICE_INIT: + device->device_state = STATE_INIT; + break; + + case DEVICE_CREATE: + device->device_state = STATE_ATTACHED; + break; + + case DEVICE_HUB_CONFIGURED: + device->device_state = STATE_POWERED; + break; + + case DEVICE_RESET: + device->device_state = STATE_DEFAULT; + device->address = 0; + break; + + case DEVICE_ADDRESS_ASSIGNED: + device->device_state = STATE_ADDRESSED; + break; + + case DEVICE_CONFIGURED: + device->device_state = STATE_CONFIGURED; + break; + + case DEVICE_DE_CONFIGURED: + device->device_state = STATE_ADDRESSED; + break; + + case DEVICE_BUS_INACTIVE: + if (device->status != USBD_CLOSING) { + device->status = USBD_SUSPENDED; + } + break; + case DEVICE_BUS_ACTIVITY: + if (device->status != USBD_CLOSING) { + device->status = USBD_OK; + } + break; + + case DEVICE_SET_INTERFACE: + break; + case DEVICE_SET_FEATURE: + break; + case DEVICE_CLEAR_FEATURE: + break; + + case DEVICE_POWER_INTERRUPTION: + device->device_state = STATE_POWERED; + break; + case DEVICE_HUB_RESET: + device->device_state = STATE_ATTACHED; + break; + case DEVICE_DESTROY: + device->device_state = STATE_UNKNOWN; + break; + + case DEVICE_FUNCTION_PRIVATE: + break; + + default: + usbdbg("event %d - not handled",event); + break; + } + debug("%s event: %d oldstate: %d newstate: %d status: %d address: %d", + device->name, event, state, + device->device_state, device->status, device->address); + + /* tell the bus interface driver */ + if( device->event ) { + /* usbdbg("calling device->event"); */ + device->event(device, event, data); + } +} diff --git a/roms/u-boot/drivers/usb/gadget/designware_udc.c b/roms/u-boot/drivers/usb/gadget/designware_udc.c new file mode 100644 index 000000000..7fc5d27d4 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/designware_udc.c @@ -0,0 +1,1021 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Based on drivers/usb/gadget/omap1510_udc.c + * TI OMAP1510 USB bus interface driver + * + * (C) Copyright 2009 + * Vipin Kumar, ST Micoelectronics, vipin.kumar@st.com. + */ + +#include <common.h> +#include <serial.h> +#include <asm/io.h> +#include <linux/delay.h> + +#include <env.h> +#include <usbdevice.h> +#include "ep0.h" +#include <usb/designware_udc.h> +#include <usb/udc.h> +#include <asm/arch/hardware.h> + +#define UDC_INIT_MDELAY 80 /* Device settle delay */ + +/* Some kind of debugging output... */ +#ifndef DEBUG_DWUSBTTY +#define UDCDBG(str) +#define UDCDBGA(fmt, args...) +#else +#define UDCDBG(str) serial_printf(str "\n") +#define UDCDBGA(fmt, args...) serial_printf(fmt "\n", ##args) +#endif + +static struct urb *ep0_urb; +static struct usb_device_instance *udc_device; + +static struct plug_regs *const plug_regs_p = + (struct plug_regs * const)CONFIG_SYS_PLUG_BASE; +static struct udc_regs *const udc_regs_p = + (struct udc_regs * const)CONFIG_SYS_USBD_BASE; +static struct udc_endp_regs *const outep_regs_p = + &((struct udc_regs * const)CONFIG_SYS_USBD_BASE)->out_regs[0]; +static struct udc_endp_regs *const inep_regs_p = + &((struct udc_regs * const)CONFIG_SYS_USBD_BASE)->in_regs[0]; + +/* + * udc_state_transition - Write the next packet to TxFIFO. + * @initial: Initial state. + * @final: Final state. + * + * Helper function to implement device state changes. The device states and + * the events that transition between them are: + * + * STATE_ATTACHED + * || /\ + * \/ || + * DEVICE_HUB_CONFIGURED DEVICE_HUB_RESET + * || /\ + * \/ || + * STATE_POWERED + * || /\ + * \/ || + * DEVICE_RESET DEVICE_POWER_INTERRUPTION + * || /\ + * \/ || + * STATE_DEFAULT + * || /\ + * \/ || + * DEVICE_ADDRESS_ASSIGNED DEVICE_RESET + * || /\ + * \/ || + * STATE_ADDRESSED + * || /\ + * \/ || + * DEVICE_CONFIGURED DEVICE_DE_CONFIGURED + * || /\ + * \/ || + * STATE_CONFIGURED + * + * udc_state_transition transitions up (in the direction from STATE_ATTACHED + * to STATE_CONFIGURED) from the specified initial state to the specified final + * state, passing through each intermediate state on the way. If the initial + * state is at or above (i.e. nearer to STATE_CONFIGURED) the final state, then + * no state transitions will take place. + * + * udc_state_transition also transitions down (in the direction from + * STATE_CONFIGURED to STATE_ATTACHED) from the specified initial state to the + * specified final state, passing through each intermediate state on the way. + * If the initial state is at or below (i.e. nearer to STATE_ATTACHED) the final + * state, then no state transitions will take place. + * + * This function must only be called with interrupts disabled. + */ +static void udc_state_transition(usb_device_state_t initial, + usb_device_state_t final) +{ + if (initial < final) { + switch (initial) { + case STATE_ATTACHED: + usbd_device_event_irq(udc_device, + DEVICE_HUB_CONFIGURED, 0); + if (final == STATE_POWERED) + break; + case STATE_POWERED: + usbd_device_event_irq(udc_device, DEVICE_RESET, 0); + if (final == STATE_DEFAULT) + break; + case STATE_DEFAULT: + usbd_device_event_irq(udc_device, + DEVICE_ADDRESS_ASSIGNED, 0); + if (final == STATE_ADDRESSED) + break; + case STATE_ADDRESSED: + usbd_device_event_irq(udc_device, DEVICE_CONFIGURED, 0); + case STATE_CONFIGURED: + break; + default: + break; + } + } else if (initial > final) { + switch (initial) { + case STATE_CONFIGURED: + usbd_device_event_irq(udc_device, + DEVICE_DE_CONFIGURED, 0); + if (final == STATE_ADDRESSED) + break; + case STATE_ADDRESSED: + usbd_device_event_irq(udc_device, DEVICE_RESET, 0); + if (final == STATE_DEFAULT) + break; + case STATE_DEFAULT: + usbd_device_event_irq(udc_device, + DEVICE_POWER_INTERRUPTION, 0); + if (final == STATE_POWERED) + break; + case STATE_POWERED: + usbd_device_event_irq(udc_device, DEVICE_HUB_RESET, 0); + case STATE_ATTACHED: + break; + default: + break; + } + } +} + +/* Stall endpoint */ +static void udc_stall_ep(u32 ep_num) +{ + writel(readl(&inep_regs_p[ep_num].endp_cntl) | ENDP_CNTL_STALL, + &inep_regs_p[ep_num].endp_cntl); + + writel(readl(&outep_regs_p[ep_num].endp_cntl) | ENDP_CNTL_STALL, + &outep_regs_p[ep_num].endp_cntl); +} + +static void *get_fifo(int ep_num, int in) +{ + u32 *fifo_ptr = (u32 *)CONFIG_SYS_FIFO_BASE; + + switch (ep_num) { + case UDC_EP3: + fifo_ptr += readl(&inep_regs_p[1].endp_bsorfn); + /* break intentionally left out */ + + case UDC_EP1: + fifo_ptr += readl(&inep_regs_p[0].endp_bsorfn); + /* break intentionally left out */ + + case UDC_EP0: + default: + if (in) { + fifo_ptr += + readl(&outep_regs_p[2].endp_maxpacksize) >> 16; + /* break intentionally left out */ + } else { + break; + } + + case UDC_EP2: + fifo_ptr += readl(&outep_regs_p[0].endp_maxpacksize) >> 16; + /* break intentionally left out */ + } + + return (void *)fifo_ptr; +} + +static int usbgetpckfromfifo(int epNum, u8 *bufp, u32 len) +{ + u8 *fifo_ptr = (u8 *)get_fifo(epNum, 0); + u32 i, nw, nb; + u32 *wrdp; + u8 *bytp; + u32 tmp[128]; + + if (readl(&udc_regs_p->dev_stat) & DEV_STAT_RXFIFO_EMPTY) + return -1; + + nw = len / sizeof(u32); + nb = len % sizeof(u32); + + /* use tmp buf if bufp is not word aligned */ + if ((int)bufp & 0x3) + wrdp = (u32 *)&tmp[0]; + else + wrdp = (u32 *)bufp; + + for (i = 0; i < nw; i++) { + writel(readl(fifo_ptr), wrdp); + wrdp++; + } + + bytp = (u8 *)wrdp; + for (i = 0; i < nb; i++) { + writeb(readb(fifo_ptr), bytp); + fifo_ptr++; + bytp++; + } + readl(&outep_regs_p[epNum].write_done); + + /* copy back tmp buffer to bufp if bufp is not word aligned */ + if ((int)bufp & 0x3) + memcpy(bufp, tmp, len); + + return 0; +} + +static void usbputpcktofifo(int epNum, u8 *bufp, u32 len) +{ + u32 i, nw, nb; + u32 *wrdp; + u8 *bytp; + u8 *fifo_ptr = get_fifo(epNum, 1); + + nw = len / sizeof(int); + nb = len % sizeof(int); + wrdp = (u32 *)bufp; + for (i = 0; i < nw; i++) { + writel(*wrdp, fifo_ptr); + wrdp++; + } + + bytp = (u8 *)wrdp; + for (i = 0; i < nb; i++) { + writeb(*bytp, fifo_ptr); + fifo_ptr++; + bytp++; + } +} + +/* + * dw_write_noniso_tx_fifo - Write the next packet to TxFIFO. + * @endpoint: Endpoint pointer. + * + * If the endpoint has an active tx_urb, then the next packet of data from the + * URB is written to the tx FIFO. The total amount of data in the urb is given + * by urb->actual_length. The maximum amount of data that can be sent in any + * one packet is given by endpoint->tx_packetSize. The number of data bytes + * from this URB that have already been transmitted is given by endpoint->sent. + * endpoint->last is updated by this routine with the number of data bytes + * transmitted in this packet. + * + */ +static void dw_write_noniso_tx_fifo(struct usb_endpoint_instance + *endpoint) +{ + struct urb *urb = endpoint->tx_urb; + int align; + + if (urb) { + u32 last; + + UDCDBGA("urb->buffer %p, buffer_length %d, actual_length %d", + urb->buffer, urb->buffer_length, urb->actual_length); + + last = min_t(u32, urb->actual_length - endpoint->sent, + endpoint->tx_packetSize); + + if (last) { + u8 *cp = urb->buffer + endpoint->sent; + + /* + * This ensures that USBD packet fifo is accessed + * - through word aligned pointer or + * - through non word aligned pointer but only + * with a max length to make the next packet + * word aligned + */ + + align = ((ulong)cp % sizeof(int)); + if (align) + last = min(last, sizeof(int) - align); + + UDCDBGA("endpoint->sent %d, tx_packetSize %d, last %d", + endpoint->sent, endpoint->tx_packetSize, last); + + usbputpcktofifo(endpoint->endpoint_address & + USB_ENDPOINT_NUMBER_MASK, cp, last); + } + endpoint->last = last; + } +} + +/* + * Handle SETUP USB interrupt. + * This function implements TRM Figure 14-14. + */ +static void dw_udc_setup(struct usb_endpoint_instance *endpoint) +{ + u8 *datap = (u8 *)&ep0_urb->device_request; + int ep_addr = endpoint->endpoint_address; + + UDCDBG("-> Entering device setup"); + usbgetpckfromfifo(ep_addr, datap, 8); + + /* Try to process setup packet */ + if (ep0_recv_setup(ep0_urb)) { + /* Not a setup packet, stall next EP0 transaction */ + udc_stall_ep(0); + UDCDBG("can't parse setup packet, still waiting for setup"); + return; + } + + /* Check direction */ + if ((ep0_urb->device_request.bmRequestType & USB_REQ_DIRECTION_MASK) + == USB_REQ_HOST2DEVICE) { + UDCDBG("control write on EP0"); + if (le16_to_cpu(ep0_urb->device_request.wLength)) { + /* Stall this request */ + UDCDBG("Stalling unsupported EP0 control write data " + "stage."); + udc_stall_ep(0); + } + } else { + + UDCDBG("control read on EP0"); + /* + * The ep0_recv_setup function has already placed our response + * packet data in ep0_urb->buffer and the packet length in + * ep0_urb->actual_length. + */ + endpoint->tx_urb = ep0_urb; + endpoint->sent = 0; + /* + * Write packet data to the FIFO. dw_write_noniso_tx_fifo + * will update endpoint->last with the number of bytes written + * to the FIFO. + */ + dw_write_noniso_tx_fifo(endpoint); + + writel(0x0, &inep_regs_p[ep_addr].write_done); + } + + udc_unset_nak(endpoint->endpoint_address); + + UDCDBG("<- Leaving device setup"); +} + +/* + * Handle endpoint 0 RX interrupt + */ +static void dw_udc_ep0_rx(struct usb_endpoint_instance *endpoint) +{ + u8 dummy[64]; + + UDCDBG("RX on EP0"); + + /* Check direction */ + if ((ep0_urb->device_request.bmRequestType + & USB_REQ_DIRECTION_MASK) == USB_REQ_HOST2DEVICE) { + /* + * This rx interrupt must be for a control write data + * stage packet. + * + * We don't support control write data stages. + * We should never end up here. + */ + + UDCDBG("Stalling unexpected EP0 control write " + "data stage packet"); + udc_stall_ep(0); + } else { + /* + * This rx interrupt must be for a control read status + * stage packet. + */ + UDCDBG("ACK on EP0 control read status stage packet"); + u32 len = (readl(&outep_regs_p[0].endp_status) >> 11) & 0xfff; + usbgetpckfromfifo(0, dummy, len); + } +} + +/* + * Handle endpoint 0 TX interrupt + */ +static void dw_udc_ep0_tx(struct usb_endpoint_instance *endpoint) +{ + struct usb_device_request *request = &ep0_urb->device_request; + int ep_addr; + + UDCDBG("TX on EP0"); + + /* Check direction */ + if ((request->bmRequestType & USB_REQ_DIRECTION_MASK) == + USB_REQ_HOST2DEVICE) { + /* + * This tx interrupt must be for a control write status + * stage packet. + */ + UDCDBG("ACK on EP0 control write status stage packet"); + } else { + /* + * This tx interrupt must be for a control read data + * stage packet. + */ + int wLength = le16_to_cpu(request->wLength); + + /* + * Update our count of bytes sent so far in this + * transfer. + */ + endpoint->sent += endpoint->last; + + /* + * We are finished with this transfer if we have sent + * all of the bytes in our tx urb (urb->actual_length) + * unless we need a zero-length terminating packet. We + * need a zero-length terminating packet if we returned + * fewer bytes than were requested (wLength) by the host, + * and the number of bytes we returned is an exact + * multiple of the packet size endpoint->tx_packetSize. + */ + if ((endpoint->sent == ep0_urb->actual_length) && + ((ep0_urb->actual_length == wLength) || + (endpoint->last != endpoint->tx_packetSize))) { + /* Done with control read data stage. */ + UDCDBG("control read data stage complete"); + } else { + /* + * We still have another packet of data to send + * in this control read data stage or else we + * need a zero-length terminating packet. + */ + UDCDBG("ACK control read data stage packet"); + dw_write_noniso_tx_fifo(endpoint); + + ep_addr = endpoint->endpoint_address; + writel(0x0, &inep_regs_p[ep_addr].write_done); + } + } +} + +static struct usb_endpoint_instance *dw_find_ep(int ep) +{ + int i; + + for (i = 0; i < udc_device->bus->max_endpoints; i++) { + if ((udc_device->bus->endpoint_array[i].endpoint_address & + USB_ENDPOINT_NUMBER_MASK) == ep) + return &udc_device->bus->endpoint_array[i]; + } + return NULL; +} + +/* + * Handle RX transaction on non-ISO endpoint. + * The ep argument is a physical endpoint number for a non-ISO IN endpoint + * in the range 1 to 15. + */ +static void dw_udc_epn_rx(int ep) +{ + int nbytes = 0; + struct urb *urb; + struct usb_endpoint_instance *endpoint = dw_find_ep(ep); + + if (endpoint) { + urb = endpoint->rcv_urb; + + if (urb) { + u8 *cp = urb->buffer + urb->actual_length; + + nbytes = (readl(&outep_regs_p[ep].endp_status) >> 11) & + 0xfff; + usbgetpckfromfifo(ep, cp, nbytes); + usbd_rcv_complete(endpoint, nbytes, 0); + } + } +} + +/* + * Handle TX transaction on non-ISO endpoint. + * The ep argument is a physical endpoint number for a non-ISO IN endpoint + * in the range 16 to 30. + */ +static void dw_udc_epn_tx(int ep) +{ + struct usb_endpoint_instance *endpoint = dw_find_ep(ep); + + if (!endpoint) + return; + + /* + * We need to transmit a terminating zero-length packet now if + * we have sent all of the data in this URB and the transfer + * size was an exact multiple of the packet size. + */ + if (endpoint->tx_urb && + (endpoint->last == endpoint->tx_packetSize) && + (endpoint->tx_urb->actual_length - endpoint->sent - + endpoint->last == 0)) { + /* handle zero length packet here */ + writel(0x0, &inep_regs_p[ep].write_done); + + } + + if (endpoint->tx_urb && endpoint->tx_urb->actual_length) { + /* retire the data that was just sent */ + usbd_tx_complete(endpoint); + /* + * Check to see if we have more data ready to transmit + * now. + */ + if (endpoint->tx_urb && endpoint->tx_urb->actual_length) { + /* write data to FIFO */ + dw_write_noniso_tx_fifo(endpoint); + writel(0x0, &inep_regs_p[ep].write_done); + + } else if (endpoint->tx_urb + && (endpoint->tx_urb->actual_length == 0)) { + /* udc_set_nak(ep); */ + } + } +} + +/* + * Start of public functions. + */ + +/* Called to start packet transmission. */ +int udc_endpoint_write(struct usb_endpoint_instance *endpoint) +{ + udc_unset_nak(endpoint->endpoint_address & USB_ENDPOINT_NUMBER_MASK); + return 0; +} + +/* Start to initialize h/w stuff */ +int udc_init(void) +{ + int i; + u32 plug_st; + + udc_device = NULL; + + UDCDBG("starting"); + + readl(&plug_regs_p->plug_pending); + + for (i = 0; i < UDC_INIT_MDELAY; i++) + udelay(1000); + + plug_st = readl(&plug_regs_p->plug_state); + writel(plug_st | PLUG_STATUS_EN, &plug_regs_p->plug_state); + + writel(~0x0, &udc_regs_p->endp_int); + writel(~0x0, &udc_regs_p->dev_int_mask); + writel(~0x0, &udc_regs_p->endp_int_mask); + +#ifndef CONFIG_USBD_HS + writel(DEV_CONF_FS_SPEED | DEV_CONF_REMWAKEUP | DEV_CONF_SELFPOW | + DEV_CONF_PHYINT_16, &udc_regs_p->dev_conf); +#else + writel(DEV_CONF_HS_SPEED | DEV_CONF_REMWAKEUP | DEV_CONF_SELFPOW | + DEV_CONF_PHYINT_16, &udc_regs_p->dev_conf); +#endif + + writel(DEV_CNTL_SOFTDISCONNECT, &udc_regs_p->dev_cntl); + + /* Clear all interrupts pending */ + writel(DEV_INT_MSK, &udc_regs_p->dev_int); + + return 0; +} + +int is_usbd_high_speed(void) +{ + return (readl(&udc_regs_p->dev_stat) & DEV_STAT_ENUM) ? 0 : 1; +} + +/* + * udc_setup_ep - setup endpoint + * Associate a physical endpoint with endpoint_instance + */ +void udc_setup_ep(struct usb_device_instance *device, + u32 ep, struct usb_endpoint_instance *endpoint) +{ + UDCDBGA("setting up endpoint addr %x", endpoint->endpoint_address); + int ep_addr; + int ep_num, ep_type; + int packet_size; + int buffer_size; + int attributes; + char *tt; + u32 endp_intmask; + + if ((ep != 0) && (udc_device->device_state < STATE_ADDRESSED)) + return; + + tt = env_get("usbtty"); + if (!tt) + tt = "generic"; + + ep_addr = endpoint->endpoint_address; + ep_num = ep_addr & USB_ENDPOINT_NUMBER_MASK; + + if ((ep_addr & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) { + /* IN endpoint */ + packet_size = endpoint->tx_packetSize; + buffer_size = packet_size * 2; + attributes = endpoint->tx_attributes; + } else { + /* OUT endpoint */ + packet_size = endpoint->rcv_packetSize; + buffer_size = packet_size * 2; + attributes = endpoint->rcv_attributes; + } + + switch (attributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_CONTROL: + ep_type = ENDP_EPTYPE_CNTL; + break; + case USB_ENDPOINT_XFER_BULK: + default: + ep_type = ENDP_EPTYPE_BULK; + break; + case USB_ENDPOINT_XFER_INT: + ep_type = ENDP_EPTYPE_INT; + break; + case USB_ENDPOINT_XFER_ISOC: + ep_type = ENDP_EPTYPE_ISO; + break; + } + + struct udc_endp_regs *out_p = &outep_regs_p[ep_num]; + struct udc_endp_regs *in_p = &inep_regs_p[ep_num]; + + if (!ep_addr) { + /* Setup endpoint 0 */ + buffer_size = packet_size; + + writel(readl(&in_p->endp_cntl) | ENDP_CNTL_CNAK, + &in_p->endp_cntl); + + writel(readl(&out_p->endp_cntl) | ENDP_CNTL_CNAK, + &out_p->endp_cntl); + + writel(ENDP_CNTL_CONTROL | ENDP_CNTL_FLUSH, &in_p->endp_cntl); + + writel(buffer_size / sizeof(int), &in_p->endp_bsorfn); + + writel(packet_size, &in_p->endp_maxpacksize); + + writel(ENDP_CNTL_CONTROL | ENDP_CNTL_RRDY, &out_p->endp_cntl); + + writel(packet_size | ((buffer_size / sizeof(int)) << 16), + &out_p->endp_maxpacksize); + + } else if ((ep_addr & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) { + /* Setup the IN endpoint */ + writel(0x0, &in_p->endp_status); + writel((ep_type << 4) | ENDP_CNTL_RRDY, &in_p->endp_cntl); + writel(buffer_size / sizeof(int), &in_p->endp_bsorfn); + writel(packet_size, &in_p->endp_maxpacksize); + + if (!strcmp(tt, "cdc_acm")) { + if (ep_type == ENDP_EPTYPE_INT) { + /* Conf no. 1 Interface no. 0 */ + writel((packet_size << 19) | + ENDP_EPDIR_IN | (1 << 7) | + (0 << 11) | (ep_type << 5) | ep_num, + &udc_regs_p->udc_endp_reg[ep_num]); + } else { + /* Conf no. 1 Interface no. 1 */ + writel((packet_size << 19) | + ENDP_EPDIR_IN | (1 << 7) | + (1 << 11) | (ep_type << 5) | ep_num, + &udc_regs_p->udc_endp_reg[ep_num]); + } + } else { + /* Conf no. 1 Interface no. 0 */ + writel((packet_size << 19) | + ENDP_EPDIR_IN | (1 << 7) | + (0 << 11) | (ep_type << 5) | ep_num, + &udc_regs_p->udc_endp_reg[ep_num]); + } + + } else { + /* Setup the OUT endpoint */ + writel(0x0, &out_p->endp_status); + writel((ep_type << 4) | ENDP_CNTL_RRDY, &out_p->endp_cntl); + writel(packet_size | ((buffer_size / sizeof(int)) << 16), + &out_p->endp_maxpacksize); + + if (!strcmp(tt, "cdc_acm")) { + writel((packet_size << 19) | + ENDP_EPDIR_OUT | (1 << 7) | + (1 << 11) | (ep_type << 5) | ep_num, + &udc_regs_p->udc_endp_reg[ep_num]); + } else { + writel((packet_size << 19) | + ENDP_EPDIR_OUT | (1 << 7) | + (0 << 11) | (ep_type << 5) | ep_num, + &udc_regs_p->udc_endp_reg[ep_num]); + } + + } + + endp_intmask = readl(&udc_regs_p->endp_int_mask); + endp_intmask &= ~((1 << ep_num) | 0x10000 << ep_num); + writel(endp_intmask, &udc_regs_p->endp_int_mask); +} + +/* Turn on the USB connection by enabling the pullup resistor */ +void udc_connect(void) +{ + u32 plug_st, dev_cntl; + + dev_cntl = readl(&udc_regs_p->dev_cntl); + dev_cntl |= DEV_CNTL_SOFTDISCONNECT; + writel(dev_cntl, &udc_regs_p->dev_cntl); + + udelay(1000); + + dev_cntl = readl(&udc_regs_p->dev_cntl); + dev_cntl &= ~DEV_CNTL_SOFTDISCONNECT; + writel(dev_cntl, &udc_regs_p->dev_cntl); + + plug_st = readl(&plug_regs_p->plug_state); + plug_st &= ~(PLUG_STATUS_PHY_RESET | PLUG_STATUS_PHY_MODE); + writel(plug_st, &plug_regs_p->plug_state); +} + +/* Turn off the USB connection by disabling the pullup resistor */ +void udc_disconnect(void) +{ + u32 plug_st; + + writel(DEV_CNTL_SOFTDISCONNECT, &udc_regs_p->dev_cntl); + + plug_st = readl(&plug_regs_p->plug_state); + plug_st |= (PLUG_STATUS_PHY_RESET | PLUG_STATUS_PHY_MODE); + writel(plug_st, &plug_regs_p->plug_state); +} + +/* Switch on the UDC */ +void udc_enable(struct usb_device_instance *device) +{ + UDCDBGA("enable device %p, status %d", device, device->status); + + /* Save the device structure pointer */ + udc_device = device; + + /* Setup ep0 urb */ + if (!ep0_urb) { + ep0_urb = + usbd_alloc_urb(udc_device, udc_device->bus->endpoint_array); + } else { + serial_printf("udc_enable: ep0_urb already allocated %p\n", + ep0_urb); + } + + writel(DEV_INT_SOF, &udc_regs_p->dev_int_mask); +} + +/** + * udc_startup - allow udc code to do any additional startup + */ +void udc_startup_events(struct usb_device_instance *device) +{ + /* The DEVICE_INIT event puts the USB device in the state STATE_INIT. */ + usbd_device_event_irq(device, DEVICE_INIT, 0); + + /* + * The DEVICE_CREATE event puts the USB device in the state + * STATE_ATTACHED. + */ + usbd_device_event_irq(device, DEVICE_CREATE, 0); + + /* + * Some USB controller driver implementations signal + * DEVICE_HUB_CONFIGURED and DEVICE_RESET events here. + * DEVICE_HUB_CONFIGURED causes a transition to the state STATE_POWERED, + * and DEVICE_RESET causes a transition to the state STATE_DEFAULT. + * The DW USB client controller has the capability to detect when the + * USB cable is connected to a powered USB bus, so we will defer the + * DEVICE_HUB_CONFIGURED and DEVICE_RESET events until later. + */ + + udc_enable(device); +} + +/* + * Plug detection interrupt handling + */ +static void dw_udc_plug_irq(void) +{ + if (readl(&plug_regs_p->plug_state) & PLUG_STATUS_ATTACHED) { + /* + * USB cable attached + * Turn off PHY reset bit (PLUG detect). + * Switch PHY opmode to normal operation (PLUG detect). + */ + udc_connect(); + writel(DEV_INT_SOF, &udc_regs_p->dev_int_mask); + + UDCDBG("device attached and powered"); + udc_state_transition(udc_device->device_state, STATE_POWERED); + } else { + writel(~0x0, &udc_regs_p->dev_int_mask); + + UDCDBG("device detached or unpowered"); + udc_state_transition(udc_device->device_state, STATE_ATTACHED); + } +} + +/* + * Device interrupt handling + */ +static void dw_udc_dev_irq(void) +{ + if (readl(&udc_regs_p->dev_int) & DEV_INT_USBRESET) { + writel(~0x0, &udc_regs_p->endp_int_mask); + + writel(readl(&inep_regs_p[0].endp_cntl) | ENDP_CNTL_FLUSH, + &inep_regs_p[0].endp_cntl); + + writel(DEV_INT_USBRESET, &udc_regs_p->dev_int); + + /* + * This endpoint0 specific register can be programmed only + * after the phy clock is initialized + */ + writel((EP0_MAX_PACKET_SIZE << 19) | ENDP_EPTYPE_CNTL, + &udc_regs_p->udc_endp_reg[0]); + + UDCDBG("device reset in progess"); + udc_state_transition(udc_device->device_state, STATE_DEFAULT); + } + + /* Device Enumeration completed */ + if (readl(&udc_regs_p->dev_int) & DEV_INT_ENUM) { + writel(DEV_INT_ENUM, &udc_regs_p->dev_int); + + /* Endpoint interrupt enabled for Ctrl IN & Ctrl OUT */ + writel(readl(&udc_regs_p->endp_int_mask) & ~0x10001, + &udc_regs_p->endp_int_mask); + + UDCDBG("default -> addressed"); + udc_state_transition(udc_device->device_state, STATE_ADDRESSED); + } + + /* The USB will be in SUSPEND in 3 ms */ + if (readl(&udc_regs_p->dev_int) & DEV_INT_INACTIVE) { + writel(DEV_INT_INACTIVE, &udc_regs_p->dev_int); + + UDCDBG("entering inactive state"); + /* usbd_device_event_irq(udc_device, DEVICE_BUS_INACTIVE, 0); */ + } + + /* SetConfiguration command received */ + if (readl(&udc_regs_p->dev_int) & DEV_INT_SETCFG) { + writel(DEV_INT_SETCFG, &udc_regs_p->dev_int); + + UDCDBG("entering configured state"); + udc_state_transition(udc_device->device_state, + STATE_CONFIGURED); + } + + /* SetInterface command received */ + if (readl(&udc_regs_p->dev_int) & DEV_INT_SETINTF) + writel(DEV_INT_SETINTF, &udc_regs_p->dev_int); + + /* USB Suspend detected on cable */ + if (readl(&udc_regs_p->dev_int) & DEV_INT_SUSPUSB) { + writel(DEV_INT_SUSPUSB, &udc_regs_p->dev_int); + + UDCDBG("entering suspended state"); + usbd_device_event_irq(udc_device, DEVICE_BUS_INACTIVE, 0); + } + + /* USB Start-Of-Frame detected on cable */ + if (readl(&udc_regs_p->dev_int) & DEV_INT_SOF) + writel(DEV_INT_SOF, &udc_regs_p->dev_int); +} + +/* + * Endpoint interrupt handling + */ +static void dw_udc_endpoint_irq(void) +{ + while (readl(&udc_regs_p->endp_int) & ENDP0_INT_CTRLOUT) { + + writel(ENDP0_INT_CTRLOUT, &udc_regs_p->endp_int); + + if ((readl(&outep_regs_p[0].endp_status) & ENDP_STATUS_OUTMSK) + == ENDP_STATUS_OUT_SETUP) { + dw_udc_setup(udc_device->bus->endpoint_array + 0); + writel(ENDP_STATUS_OUT_SETUP, + &outep_regs_p[0].endp_status); + + } else if ((readl(&outep_regs_p[0].endp_status) & + ENDP_STATUS_OUTMSK) == ENDP_STATUS_OUT_DATA) { + dw_udc_ep0_rx(udc_device->bus->endpoint_array + 0); + writel(ENDP_STATUS_OUT_DATA, + &outep_regs_p[0].endp_status); + + } else if ((readl(&outep_regs_p[0].endp_status) & + ENDP_STATUS_OUTMSK) == ENDP_STATUS_OUT_NONE) { + /* NONE received */ + } + + writel(0x0, &outep_regs_p[0].endp_status); + } + + if (readl(&udc_regs_p->endp_int) & ENDP0_INT_CTRLIN) { + dw_udc_ep0_tx(udc_device->bus->endpoint_array + 0); + + writel(ENDP_STATUS_IN, &inep_regs_p[0].endp_status); + writel(ENDP0_INT_CTRLIN, &udc_regs_p->endp_int); + } + + if (readl(&udc_regs_p->endp_int) & ENDP_INT_NONISOOUT_MSK) { + u32 epnum = 0; + u32 ep_int = readl(&udc_regs_p->endp_int) & + ENDP_INT_NONISOOUT_MSK; + + ep_int >>= 16; + while (0x0 == (ep_int & 0x1)) { + ep_int >>= 1; + epnum++; + } + + writel((1 << 16) << epnum, &udc_regs_p->endp_int); + + if ((readl(&outep_regs_p[epnum].endp_status) & + ENDP_STATUS_OUTMSK) == ENDP_STATUS_OUT_DATA) { + + dw_udc_epn_rx(epnum); + writel(ENDP_STATUS_OUT_DATA, + &outep_regs_p[epnum].endp_status); + } else if ((readl(&outep_regs_p[epnum].endp_status) & + ENDP_STATUS_OUTMSK) == ENDP_STATUS_OUT_NONE) { + writel(0x0, &outep_regs_p[epnum].endp_status); + } + } + + if (readl(&udc_regs_p->endp_int) & ENDP_INT_NONISOIN_MSK) { + u32 epnum = 0; + u32 ep_int = readl(&udc_regs_p->endp_int) & + ENDP_INT_NONISOIN_MSK; + + while (0x0 == (ep_int & 0x1)) { + ep_int >>= 1; + epnum++; + } + + if (readl(&inep_regs_p[epnum].endp_status) & ENDP_STATUS_IN) { + writel(ENDP_STATUS_IN, + &outep_regs_p[epnum].endp_status); + dw_udc_epn_tx(epnum); + + writel(ENDP_STATUS_IN, + &outep_regs_p[epnum].endp_status); + } + + writel((1 << epnum), &udc_regs_p->endp_int); + } +} + +/* + * UDC interrupts + */ +void udc_irq(void) +{ + /* + * Loop while we have interrupts. + * If we don't do this, the input chain + * polling delay is likely to miss + * host requests. + */ + while (readl(&plug_regs_p->plug_pending)) + dw_udc_plug_irq(); + + while (readl(&udc_regs_p->dev_int)) + dw_udc_dev_irq(); + + if (readl(&udc_regs_p->endp_int)) + dw_udc_endpoint_irq(); +} + +/* Flow control */ +void udc_set_nak(int epid) +{ + writel(readl(&inep_regs_p[epid].endp_cntl) | ENDP_CNTL_SNAK, + &inep_regs_p[epid].endp_cntl); + + writel(readl(&outep_regs_p[epid].endp_cntl) | ENDP_CNTL_SNAK, + &outep_regs_p[epid].endp_cntl); +} + +void udc_unset_nak(int epid) +{ + u32 val; + + val = readl(&inep_regs_p[epid].endp_cntl); + val &= ~ENDP_CNTL_SNAK; + val |= ENDP_CNTL_CNAK; + writel(val, &inep_regs_p[epid].endp_cntl); + + val = readl(&outep_regs_p[epid].endp_cntl); + val &= ~ENDP_CNTL_SNAK; + val |= ENDP_CNTL_CNAK; + writel(val, &outep_regs_p[epid].endp_cntl); +} diff --git a/roms/u-boot/drivers/usb/gadget/dwc2_udc_otg.c b/roms/u-boot/drivers/usb/gadget/dwc2_udc_otg.c new file mode 100644 index 000000000..2f3181444 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/dwc2_udc_otg.c @@ -0,0 +1,1199 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * drivers/usb/gadget/dwc2_udc_otg.c + * Designware DWC2 on-chip full/high speed USB OTG 2.0 device controllers + * + * Copyright (C) 2008 for Samsung Electronics + * + * BSP Support for Samsung's UDC driver + * available at: + * git://git.kernel.org/pub/scm/linux/kernel/git/kki_ap/linux-2.6-samsung.git + * + * State machine bugfixes: + * Marek Szyprowski <m.szyprowski@samsung.com> + * + * Ported to u-boot: + * Marek Szyprowski <m.szyprowski@samsung.com> + * Lukasz Majewski <l.majewski@samsumg.com> + */ +#undef DEBUG +#include <common.h> +#include <clk.h> +#include <dm.h> +#include <generic-phy.h> +#include <log.h> +#include <malloc.h> +#include <reset.h> +#include <dm/device_compat.h> +#include <dm/devres.h> +#include <linux/bug.h> +#include <linux/delay.h> + +#include <linux/errno.h> +#include <linux/list.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/otg.h> +#include <linux/usb/gadget.h> + +#include <phys2bus.h> +#include <asm/byteorder.h> +#include <asm/unaligned.h> +#include <asm/io.h> + +#include <asm/mach-types.h> + +#include <power/regulator.h> + +#include "dwc2_udc_otg_regs.h" +#include "dwc2_udc_otg_priv.h" + +/***********************************************************/ + +#define OTG_DMA_MODE 1 + +#define DEBUG_SETUP 0 +#define DEBUG_EP0 0 +#define DEBUG_ISR 0 +#define DEBUG_OUT_EP 0 +#define DEBUG_IN_EP 0 + +#include <usb/dwc2_udc.h> + +#define EP0_CON 0 +#define EP_MASK 0xF + +static char *state_names[] = { + "WAIT_FOR_SETUP", + "DATA_STATE_XMIT", + "DATA_STATE_NEED_ZLP", + "WAIT_FOR_OUT_STATUS", + "DATA_STATE_RECV", + "WAIT_FOR_COMPLETE", + "WAIT_FOR_OUT_COMPLETE", + "WAIT_FOR_IN_COMPLETE", + "WAIT_FOR_NULL_COMPLETE", +}; + +#define DRIVER_VERSION "15 March 2009" + +struct dwc2_udc *the_controller; + +static const char driver_name[] = "dwc2-udc"; +static const char ep0name[] = "ep0-control"; + +/* Max packet size*/ +static unsigned int ep0_fifo_size = 64; +static unsigned int ep_fifo_size = 512; +static unsigned int ep_fifo_size2 = 1024; +static int reset_available = 1; + +static struct usb_ctrlrequest *usb_ctrl; +static dma_addr_t usb_ctrl_dma_addr; + +/* + Local declarations. +*/ +static int dwc2_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *); +static int dwc2_ep_disable(struct usb_ep *ep); +static struct usb_request *dwc2_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags); +static void dwc2_free_request(struct usb_ep *ep, struct usb_request *); + +static int dwc2_queue(struct usb_ep *ep, struct usb_request *, gfp_t gfp_flags); +static int dwc2_dequeue(struct usb_ep *ep, struct usb_request *); +static int dwc2_fifo_status(struct usb_ep *ep); +static void dwc2_fifo_flush(struct usb_ep *ep); +static void dwc2_ep0_read(struct dwc2_udc *dev); +static void dwc2_ep0_kick(struct dwc2_udc *dev, struct dwc2_ep *ep); +static void dwc2_handle_ep0(struct dwc2_udc *dev); +static int dwc2_ep0_write(struct dwc2_udc *dev); +static int write_fifo_ep0(struct dwc2_ep *ep, struct dwc2_request *req); +static void done(struct dwc2_ep *ep, struct dwc2_request *req, int status); +static void stop_activity(struct dwc2_udc *dev, + struct usb_gadget_driver *driver); +static int udc_enable(struct dwc2_udc *dev); +static void udc_set_address(struct dwc2_udc *dev, unsigned char address); +static void reconfig_usbd(struct dwc2_udc *dev); +static void set_max_pktsize(struct dwc2_udc *dev, enum usb_device_speed speed); +static void nuke(struct dwc2_ep *ep, int status); +static int dwc2_udc_set_halt(struct usb_ep *_ep, int value); +static void dwc2_udc_set_nak(struct dwc2_ep *ep); + +void set_udc_gadget_private_data(void *p) +{ + debug_cond(DEBUG_SETUP != 0, + "%s: the_controller: 0x%p, p: 0x%p\n", __func__, + the_controller, p); + the_controller->gadget.dev.device_data = p; +} + +void *get_udc_gadget_private_data(struct usb_gadget *gadget) +{ + return gadget->dev.device_data; +} + +static struct usb_ep_ops dwc2_ep_ops = { + .enable = dwc2_ep_enable, + .disable = dwc2_ep_disable, + + .alloc_request = dwc2_alloc_request, + .free_request = dwc2_free_request, + + .queue = dwc2_queue, + .dequeue = dwc2_dequeue, + + .set_halt = dwc2_udc_set_halt, + .fifo_status = dwc2_fifo_status, + .fifo_flush = dwc2_fifo_flush, +}; + +#define create_proc_files() do {} while (0) +#define remove_proc_files() do {} while (0) + +/***********************************************************/ + +struct dwc2_usbotg_reg *reg; + +bool dfu_usb_get_reset(void) +{ + return !!(readl(®->gintsts) & INT_RESET); +} + +__weak void otg_phy_init(struct dwc2_udc *dev) {} +__weak void otg_phy_off(struct dwc2_udc *dev) {} + +/***********************************************************/ + +#include "dwc2_udc_otg_xfer_dma.c" + +/* + * udc_disable - disable USB device controller + */ +static void udc_disable(struct dwc2_udc *dev) +{ + debug_cond(DEBUG_SETUP != 0, "%s: %p\n", __func__, dev); + + udc_set_address(dev, 0); + + dev->ep0state = WAIT_FOR_SETUP; + dev->gadget.speed = USB_SPEED_UNKNOWN; + dev->usb_address = 0; + + otg_phy_off(dev); +} + +/* + * udc_reinit - initialize software state + */ +static void udc_reinit(struct dwc2_udc *dev) +{ + unsigned int i; + + debug_cond(DEBUG_SETUP != 0, "%s: %p\n", __func__, dev); + + /* device/ep0 records init */ + INIT_LIST_HEAD(&dev->gadget.ep_list); + INIT_LIST_HEAD(&dev->gadget.ep0->ep_list); + dev->ep0state = WAIT_FOR_SETUP; + + /* basic endpoint records init */ + for (i = 0; i < DWC2_MAX_ENDPOINTS; i++) { + struct dwc2_ep *ep = &dev->ep[i]; + + if (i != 0) + list_add_tail(&ep->ep.ep_list, &dev->gadget.ep_list); + + ep->desc = 0; + ep->stopped = 0; + INIT_LIST_HEAD(&ep->queue); + ep->pio_irqs = 0; + } + + /* the rest was statically initialized, and is read-only */ +} + +#define BYTES2MAXP(x) (x / 8) +#define MAXP2BYTES(x) (x * 8) + +/* until it's enabled, this UDC should be completely invisible + * to any USB host. + */ +static int udc_enable(struct dwc2_udc *dev) +{ + debug_cond(DEBUG_SETUP != 0, "%s: %p\n", __func__, dev); + + otg_phy_init(dev); + reconfig_usbd(dev); + + debug_cond(DEBUG_SETUP != 0, + "DWC2 USB 2.0 OTG Controller Core Initialized : 0x%x\n", + readl(®->gintmsk)); + + dev->gadget.speed = USB_SPEED_UNKNOWN; + + return 0; +} + +#if !CONFIG_IS_ENABLED(DM_USB_GADGET) +/* + Register entry point for the peripheral controller driver. +*/ +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + struct dwc2_udc *dev = the_controller; + int retval = 0; + unsigned long flags = 0; + + debug_cond(DEBUG_SETUP != 0, "%s: %s\n", __func__, "no name"); + + if (!driver || driver->speed < USB_SPEED_FULL + || !driver->bind || !driver->disconnect || !driver->setup) + return -EINVAL; + if (!dev) + return -ENODEV; + if (dev->driver) + return -EBUSY; + + spin_lock_irqsave(&dev->lock, flags); + /* first hook up the driver ... */ + dev->driver = driver; + spin_unlock_irqrestore(&dev->lock, flags); + + if (retval) { /* TODO */ + printf("target device_add failed, error %d\n", retval); + return retval; + } + + retval = driver->bind(&dev->gadget); + if (retval) { + debug_cond(DEBUG_SETUP != 0, + "%s: bind to driver --> error %d\n", + dev->gadget.name, retval); + dev->driver = 0; + return retval; + } + + enable_irq(IRQ_OTG); + + debug_cond(DEBUG_SETUP != 0, + "Registered gadget driver %s\n", dev->gadget.name); + udc_enable(dev); + + return 0; +} + +/* + * Unregister entry point for the peripheral controller driver. + */ +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct dwc2_udc *dev = the_controller; + unsigned long flags = 0; + + if (!dev) + return -ENODEV; + if (!driver || driver != dev->driver) + return -EINVAL; + + spin_lock_irqsave(&dev->lock, flags); + dev->driver = 0; + stop_activity(dev, driver); + spin_unlock_irqrestore(&dev->lock, flags); + + driver->unbind(&dev->gadget); + + disable_irq(IRQ_OTG); + + udc_disable(dev); + return 0; +} +#else /* !CONFIG_IS_ENABLED(DM_USB_GADGET) */ + +static int dwc2_gadget_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct dwc2_udc *dev = the_controller; + + debug_cond(DEBUG_SETUP != 0, "%s: %s\n", __func__, "no name"); + + if (!driver || driver->speed < USB_SPEED_FULL || + !driver->bind || !driver->disconnect || !driver->setup) + return -EINVAL; + + if (!dev) + return -ENODEV; + + if (dev->driver) + return -EBUSY; + + /* first hook up the driver ... */ + dev->driver = driver; + + debug_cond(DEBUG_SETUP != 0, + "Registered gadget driver %s\n", dev->gadget.name); + return udc_enable(dev); +} + +static int dwc2_gadget_stop(struct usb_gadget *g) +{ + struct dwc2_udc *dev = the_controller; + + if (!dev) + return -ENODEV; + + if (!dev->driver) + return -EINVAL; + + dev->driver = 0; + stop_activity(dev, dev->driver); + + udc_disable(dev); + + return 0; +} + +#endif /* !CONFIG_IS_ENABLED(DM_USB_GADGET) */ + +/* + * done - retire a request; caller blocked irqs + */ +static void done(struct dwc2_ep *ep, struct dwc2_request *req, int status) +{ + unsigned int stopped = ep->stopped; + + debug("%s: %s %p, req = %p, stopped = %d\n", + __func__, ep->ep.name, ep, &req->req, stopped); + + list_del_init(&req->queue); + + if (likely(req->req.status == -EINPROGRESS)) + req->req.status = status; + else + status = req->req.status; + + if (status && status != -ESHUTDOWN) { + debug("complete %s req %p stat %d len %u/%u\n", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + } + + /* don't modify queue heads during completion callback */ + ep->stopped = 1; + +#ifdef DEBUG + printf("calling complete callback\n"); + { + int i, len = req->req.length; + + printf("pkt[%d] = ", req->req.length); + if (len > 64) + len = 64; + for (i = 0; i < len; i++) { + printf("%02x", ((u8 *)req->req.buf)[i]); + if ((i & 7) == 7) + printf(" "); + } + printf("\n"); + } +#endif + spin_unlock(&ep->dev->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&ep->dev->lock); + + debug("callback completed\n"); + + ep->stopped = stopped; +} + +/* + * nuke - dequeue ALL requests + */ +static void nuke(struct dwc2_ep *ep, int status) +{ + struct dwc2_request *req; + + debug("%s: %s %p\n", __func__, ep->ep.name, ep); + + /* called with irqs blocked */ + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct dwc2_request, queue); + done(ep, req, status); + } +} + +static void stop_activity(struct dwc2_udc *dev, + struct usb_gadget_driver *driver) +{ + int i; + + /* don't disconnect drivers more than once */ + if (dev->gadget.speed == USB_SPEED_UNKNOWN) + driver = 0; + dev->gadget.speed = USB_SPEED_UNKNOWN; + + /* prevent new request submissions, kill any outstanding requests */ + for (i = 0; i < DWC2_MAX_ENDPOINTS; i++) { + struct dwc2_ep *ep = &dev->ep[i]; + ep->stopped = 1; + nuke(ep, -ESHUTDOWN); + } + + /* report disconnect; the driver is already quiesced */ + if (driver) { + spin_unlock(&dev->lock); + driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + } + + /* re-init driver-visible data structures */ + udc_reinit(dev); +} + +static void reconfig_usbd(struct dwc2_udc *dev) +{ + /* 2. Soft-reset OTG Core and then unreset again. */ + int i; + unsigned int uTemp = writel(CORE_SOFT_RESET, ®->grstctl); + uint32_t dflt_gusbcfg; + uint32_t rx_fifo_sz, tx_fifo_sz, np_tx_fifo_sz; + u32 max_hw_ep; + int pdata_hw_ep; + + debug("Reseting OTG controller\n"); + + dflt_gusbcfg = + 0<<15 /* PHY Low Power Clock sel*/ + |1<<14 /* Non-Periodic TxFIFO Rewind Enable*/ + |0x5<<10 /* Turnaround time*/ + |0<<9 | 0<<8 /* [0:HNP disable,1:HNP enable][ 0:SRP disable*/ + /* 1:SRP enable] H1= 1,1*/ + |0<<7 /* Ulpi DDR sel*/ + |0<<6 /* 0: high speed utmi+, 1: full speed serial*/ + |0<<4 /* 0: utmi+, 1:ulpi*/ +#ifdef CONFIG_USB_GADGET_DWC2_OTG_PHY_BUS_WIDTH_8 + |0<<3 /* phy i/f 0:8bit, 1:16bit*/ +#else + |1<<3 /* phy i/f 0:8bit, 1:16bit*/ +#endif + |0x7<<0; /* HS/FS Timeout**/ + + if (dev->pdata->usb_gusbcfg) + dflt_gusbcfg = dev->pdata->usb_gusbcfg; + + writel(dflt_gusbcfg, ®->gusbcfg); + + /* 3. Put the OTG device core in the disconnected state.*/ + uTemp = readl(®->dctl); + uTemp |= SOFT_DISCONNECT; + writel(uTemp, ®->dctl); + + udelay(20); + + /* 4. Make the OTG device core exit from the disconnected state.*/ + uTemp = readl(®->dctl); + uTemp = uTemp & ~SOFT_DISCONNECT; + writel(uTemp, ®->dctl); + + /* 5. Configure OTG Core to initial settings of device mode.*/ + /* [][1: full speed(30Mhz) 0:high speed]*/ + writel(EP_MISS_CNT(1) | DEV_SPEED_HIGH_SPEED_20, ®->dcfg); + + mdelay(1); + + /* 6. Unmask the core interrupts*/ + writel(GINTMSK_INIT, ®->gintmsk); + + /* 7. Set NAK bit of EP0, EP1, EP2*/ + writel(DEPCTL_EPDIS|DEPCTL_SNAK, ®->out_endp[EP0_CON].doepctl); + writel(DEPCTL_EPDIS|DEPCTL_SNAK, ®->in_endp[EP0_CON].diepctl); + + for (i = 1; i < DWC2_MAX_ENDPOINTS; i++) { + writel(DEPCTL_EPDIS|DEPCTL_SNAK, ®->out_endp[i].doepctl); + writel(DEPCTL_EPDIS|DEPCTL_SNAK, ®->in_endp[i].diepctl); + } + + /* 8. Unmask EPO interrupts*/ + writel(((1 << EP0_CON) << DAINT_OUT_BIT) + | (1 << EP0_CON), ®->daintmsk); + + /* 9. Unmask device OUT EP common interrupts*/ + writel(DOEPMSK_INIT, ®->doepmsk); + + /* 10. Unmask device IN EP common interrupts*/ + writel(DIEPMSK_INIT, ®->diepmsk); + + rx_fifo_sz = RX_FIFO_SIZE; + np_tx_fifo_sz = NPTX_FIFO_SIZE; + tx_fifo_sz = PTX_FIFO_SIZE; + + if (dev->pdata->rx_fifo_sz) + rx_fifo_sz = dev->pdata->rx_fifo_sz; + if (dev->pdata->np_tx_fifo_sz) + np_tx_fifo_sz = dev->pdata->np_tx_fifo_sz; + if (dev->pdata->tx_fifo_sz) + tx_fifo_sz = dev->pdata->tx_fifo_sz; + + /* 11. Set Rx FIFO Size (in 32-bit words) */ + writel(rx_fifo_sz, ®->grxfsiz); + + /* 12. Set Non Periodic Tx FIFO Size */ + writel((np_tx_fifo_sz << 16) | rx_fifo_sz, + ®->gnptxfsiz); + + /* retrieve the number of IN Endpoints (excluding ep0) */ + max_hw_ep = (readl(®->ghwcfg4) & GHWCFG4_NUM_IN_EPS_MASK) >> + GHWCFG4_NUM_IN_EPS_SHIFT; + pdata_hw_ep = dev->pdata->tx_fifo_sz_nb; + + /* tx_fifo_sz_nb should equal to number of IN Endpoint */ + if (pdata_hw_ep && max_hw_ep != pdata_hw_ep) + pr_warn("Got %d hw endpoint but %d tx-fifo-size in array !!\n", + max_hw_ep, pdata_hw_ep); + + for (i = 0; i < max_hw_ep; i++) { + if (pdata_hw_ep) + tx_fifo_sz = dev->pdata->tx_fifo_sz_array[i]; + + writel((rx_fifo_sz + np_tx_fifo_sz + (tx_fifo_sz * i)) | + tx_fifo_sz << 16, ®->dieptxf[i]); + } + /* Flush the RX FIFO */ + writel(RX_FIFO_FLUSH, ®->grstctl); + while (readl(®->grstctl) & RX_FIFO_FLUSH) + debug("%s: waiting for DWC2_UDC_OTG_GRSTCTL\n", __func__); + + /* Flush all the Tx FIFO's */ + writel(TX_FIFO_FLUSH_ALL, ®->grstctl); + writel(TX_FIFO_FLUSH_ALL | TX_FIFO_FLUSH, ®->grstctl); + while (readl(®->grstctl) & TX_FIFO_FLUSH) + debug("%s: waiting for DWC2_UDC_OTG_GRSTCTL\n", __func__); + + /* 13. Clear NAK bit of EP0, EP1, EP2*/ + /* For Slave mode*/ + /* EP0: Control OUT */ + writel(DEPCTL_EPDIS | DEPCTL_CNAK, + ®->out_endp[EP0_CON].doepctl); + + /* 14. Initialize OTG Link Core.*/ + writel(GAHBCFG_INIT, ®->gahbcfg); +} + +static void set_max_pktsize(struct dwc2_udc *dev, enum usb_device_speed speed) +{ + unsigned int ep_ctrl; + int i; + + if (speed == USB_SPEED_HIGH) { + ep0_fifo_size = 64; + ep_fifo_size = 512; + ep_fifo_size2 = 1024; + dev->gadget.speed = USB_SPEED_HIGH; + } else { + ep0_fifo_size = 64; + ep_fifo_size = 64; + ep_fifo_size2 = 64; + dev->gadget.speed = USB_SPEED_FULL; + } + + dev->ep[0].ep.maxpacket = ep0_fifo_size; + for (i = 1; i < DWC2_MAX_ENDPOINTS; i++) + dev->ep[i].ep.maxpacket = ep_fifo_size; + + /* EP0 - Control IN (64 bytes)*/ + ep_ctrl = readl(®->in_endp[EP0_CON].diepctl); + writel(ep_ctrl|(0<<0), ®->in_endp[EP0_CON].diepctl); + + /* EP0 - Control OUT (64 bytes)*/ + ep_ctrl = readl(®->out_endp[EP0_CON].doepctl); + writel(ep_ctrl|(0<<0), ®->out_endp[EP0_CON].doepctl); +} + +static int dwc2_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct dwc2_ep *ep; + struct dwc2_udc *dev; + unsigned long flags = 0; + + debug("%s: %p\n", __func__, _ep); + + ep = container_of(_ep, struct dwc2_ep, ep); + if (!_ep || !desc || ep->desc || _ep->name == ep0name + || desc->bDescriptorType != USB_DT_ENDPOINT + || ep->bEndpointAddress != desc->bEndpointAddress + || ep_maxpacket(ep) < + le16_to_cpu(get_unaligned(&desc->wMaxPacketSize))) { + + debug("%s: bad ep or descriptor\n", __func__); + return -EINVAL; + } + + /* xfer types must match, except that interrupt ~= bulk */ + if (ep->bmAttributes != desc->bmAttributes + && ep->bmAttributes != USB_ENDPOINT_XFER_BULK + && desc->bmAttributes != USB_ENDPOINT_XFER_INT) { + + debug("%s: %s type mismatch\n", __func__, _ep->name); + return -EINVAL; + } + + /* hardware _could_ do smaller, but driver doesn't */ + if ((desc->bmAttributes == USB_ENDPOINT_XFER_BULK && + le16_to_cpu(get_unaligned(&desc->wMaxPacketSize)) > + ep_maxpacket(ep)) || !get_unaligned(&desc->wMaxPacketSize)) { + + debug("%s: bad %s maxpacket\n", __func__, _ep->name); + return -ERANGE; + } + + dev = ep->dev; + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) { + + debug("%s: bogus device state\n", __func__); + return -ESHUTDOWN; + } + + ep->stopped = 0; + ep->desc = desc; + ep->pio_irqs = 0; + ep->ep.maxpacket = le16_to_cpu(get_unaligned(&desc->wMaxPacketSize)); + + /* Reset halt state */ + dwc2_udc_set_nak(ep); + dwc2_udc_set_halt(_ep, 0); + + spin_lock_irqsave(&ep->dev->lock, flags); + dwc2_udc_ep_activate(ep); + spin_unlock_irqrestore(&ep->dev->lock, flags); + + debug("%s: enabled %s, stopped = %d, maxpacket = %d\n", + __func__, _ep->name, ep->stopped, ep->ep.maxpacket); + return 0; +} + +/* + * Disable EP + */ +static int dwc2_ep_disable(struct usb_ep *_ep) +{ + struct dwc2_ep *ep; + unsigned long flags = 0; + + debug("%s: %p\n", __func__, _ep); + + ep = container_of(_ep, struct dwc2_ep, ep); + if (!_ep || !ep->desc) { + debug("%s: %s not enabled\n", __func__, + _ep ? ep->ep.name : NULL); + return -EINVAL; + } + + spin_lock_irqsave(&ep->dev->lock, flags); + + /* Nuke all pending requests */ + nuke(ep, -ESHUTDOWN); + + ep->desc = 0; + ep->stopped = 1; + + spin_unlock_irqrestore(&ep->dev->lock, flags); + + debug("%s: disabled %s\n", __func__, _ep->name); + return 0; +} + +static struct usb_request *dwc2_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct dwc2_request *req; + + debug("%s: %s %p\n", __func__, ep->name, ep); + + req = memalign(CONFIG_SYS_CACHELINE_SIZE, sizeof(*req)); + if (!req) + return 0; + + memset(req, 0, sizeof *req); + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void dwc2_free_request(struct usb_ep *ep, struct usb_request *_req) +{ + struct dwc2_request *req; + + debug("%s: %p\n", __func__, ep); + + req = container_of(_req, struct dwc2_request, req); + WARN_ON(!list_empty(&req->queue)); + kfree(req); +} + +/* dequeue JUST ONE request */ +static int dwc2_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct dwc2_ep *ep; + struct dwc2_request *req; + unsigned long flags = 0; + + debug("%s: %p\n", __func__, _ep); + + ep = container_of(_ep, struct dwc2_ep, ep); + if (!_ep || ep->ep.name == ep0name) + return -EINVAL; + + spin_lock_irqsave(&ep->dev->lock, flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) { + spin_unlock_irqrestore(&ep->dev->lock, flags); + return -EINVAL; + } + + done(ep, req, -ECONNRESET); + + spin_unlock_irqrestore(&ep->dev->lock, flags); + return 0; +} + +/* + * Return bytes in EP FIFO + */ +static int dwc2_fifo_status(struct usb_ep *_ep) +{ + int count = 0; + struct dwc2_ep *ep; + + ep = container_of(_ep, struct dwc2_ep, ep); + if (!_ep) { + debug("%s: bad ep\n", __func__); + return -ENODEV; + } + + debug("%s: %d\n", __func__, ep_index(ep)); + + /* LPD can't report unclaimed bytes from IN fifos */ + if (ep_is_in(ep)) + return -EOPNOTSUPP; + + return count; +} + +/* + * Flush EP FIFO + */ +static void dwc2_fifo_flush(struct usb_ep *_ep) +{ + struct dwc2_ep *ep; + + ep = container_of(_ep, struct dwc2_ep, ep); + if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) { + debug("%s: bad ep\n", __func__); + return; + } + + debug("%s: %d\n", __func__, ep_index(ep)); +} + +static const struct usb_gadget_ops dwc2_udc_ops = { + /* current versions must always be self-powered */ +#if CONFIG_IS_ENABLED(DM_USB_GADGET) + .udc_start = dwc2_gadget_start, + .udc_stop = dwc2_gadget_stop, +#endif +}; + +static struct dwc2_udc memory = { + .usb_address = 0, + .gadget = { + .ops = &dwc2_udc_ops, + .ep0 = &memory.ep[0].ep, + .name = driver_name, + }, + + /* control endpoint */ + .ep[0] = { + .ep = { + .name = ep0name, + .ops = &dwc2_ep_ops, + .maxpacket = EP0_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = 0, + .bmAttributes = 0, + + .ep_type = ep_control, + }, + + /* first group of endpoints */ + .ep[1] = { + .ep = { + .name = "ep1in-bulk", + .ops = &dwc2_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 1, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_out, + .fifo_num = 1, + }, + + .ep[2] = { + .ep = { + .name = "ep2out-bulk", + .ops = &dwc2_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_OUT | 2, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_in, + .fifo_num = 2, + }, + + .ep[3] = { + .ep = { + .name = "ep3in-int", + .ops = &dwc2_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 3, + .bmAttributes = USB_ENDPOINT_XFER_INT, + + .ep_type = ep_interrupt, + .fifo_num = 3, + }, +}; + +/* + * probe - binds to the platform device + */ + +int dwc2_udc_probe(struct dwc2_plat_otg_data *pdata) +{ + struct dwc2_udc *dev = &memory; + int retval = 0; + + debug("%s: %p\n", __func__, pdata); + + dev->pdata = pdata; + + reg = (struct dwc2_usbotg_reg *)pdata->regs_otg; + + dev->gadget.is_dualspeed = 1; /* Hack only*/ + dev->gadget.is_otg = 0; + dev->gadget.is_a_peripheral = 0; + dev->gadget.b_hnp_enable = 0; + dev->gadget.a_hnp_support = 0; + dev->gadget.a_alt_hnp_support = 0; + + the_controller = dev; + + usb_ctrl = memalign(CONFIG_SYS_CACHELINE_SIZE, + ROUND(sizeof(struct usb_ctrlrequest), + CONFIG_SYS_CACHELINE_SIZE)); + if (!usb_ctrl) { + pr_err("No memory available for UDC!\n"); + return -ENOMEM; + } + + usb_ctrl_dma_addr = (dma_addr_t) usb_ctrl; + + udc_reinit(dev); + + return retval; +} + +int dwc2_udc_handle_interrupt(void) +{ + u32 intr_status = readl(®->gintsts); + u32 gintmsk = readl(®->gintmsk); + + if (intr_status & gintmsk) + return dwc2_udc_irq(1, (void *)the_controller); + + return 0; +} + +#if !CONFIG_IS_ENABLED(DM_USB_GADGET) + +int usb_gadget_handle_interrupts(int index) +{ + return dwc2_udc_handle_interrupt(); +} + +#else /* CONFIG_IS_ENABLED(DM_USB_GADGET) */ + +struct dwc2_priv_data { + struct clk_bulk clks; + struct reset_ctl_bulk resets; + struct phy_bulk phys; + struct udevice *usb33d_supply; +}; + +int dm_usb_gadget_handle_interrupts(struct udevice *dev) +{ + return dwc2_udc_handle_interrupt(); +} + +static int dwc2_phy_setup(struct udevice *dev, struct phy_bulk *phys) +{ + int ret; + + ret = generic_phy_get_bulk(dev, phys); + if (ret) + return ret; + + ret = generic_phy_init_bulk(phys); + if (ret) + return ret; + + ret = generic_phy_power_on_bulk(phys); + if (ret) + generic_phy_exit_bulk(phys); + + return ret; +} + +static void dwc2_phy_shutdown(struct udevice *dev, struct phy_bulk *phys) +{ + generic_phy_power_off_bulk(phys); + generic_phy_exit_bulk(phys); +} + +static int dwc2_udc_otg_of_to_plat(struct udevice *dev) +{ + struct dwc2_plat_otg_data *plat = dev_get_plat(dev); + ulong drvdata; + void (*set_params)(struct dwc2_plat_otg_data *data); + int ret; + + if (usb_get_dr_mode(dev_ofnode(dev)) != USB_DR_MODE_PERIPHERAL && + usb_get_dr_mode(dev_ofnode(dev)) != USB_DR_MODE_OTG) { + dev_dbg(dev, "Invalid mode\n"); + return -ENODEV; + } + + plat->regs_otg = dev_read_addr(dev); + + plat->rx_fifo_sz = dev_read_u32_default(dev, "g-rx-fifo-size", 0); + plat->np_tx_fifo_sz = dev_read_u32_default(dev, "g-np-tx-fifo-size", 0); + + plat->tx_fifo_sz_nb = + dev_read_size(dev, "g-tx-fifo-size") / sizeof(u32); + if (plat->tx_fifo_sz_nb > DWC2_MAX_HW_ENDPOINTS) + plat->tx_fifo_sz_nb = DWC2_MAX_HW_ENDPOINTS; + if (plat->tx_fifo_sz_nb) { + ret = dev_read_u32_array(dev, "g-tx-fifo-size", + plat->tx_fifo_sz_array, + plat->tx_fifo_sz_nb); + if (ret) + return ret; + } + + plat->force_b_session_valid = + dev_read_bool(dev, "u-boot,force-b-session-valid"); + + plat->force_vbus_detection = + dev_read_bool(dev, "u-boot,force-vbus-detection"); + + /* force plat according compatible */ + drvdata = dev_get_driver_data(dev); + if (drvdata) { + set_params = (void *)drvdata; + set_params(plat); + } + + return 0; +} + +static void dwc2_set_stm32mp1_hsotg_params(struct dwc2_plat_otg_data *p) +{ + p->activate_stm_id_vb_detection = true; + p->usb_gusbcfg = + 0 << 15 /* PHY Low Power Clock sel*/ + | 0x9 << 10 /* USB Turnaround time (0x9 for HS phy) */ + | 0 << 9 /* [0:HNP disable,1:HNP enable]*/ + | 0 << 8 /* [0:SRP disable 1:SRP enable]*/ + | 0 << 6 /* 0: high speed utmi+, 1: full speed serial*/ + | 0x7 << 0; /* FS timeout calibration**/ + + if (p->force_b_session_valid) + p->usb_gusbcfg |= 1 << 30; /* FDMOD: Force device mode */ +} + +static int dwc2_udc_otg_reset_init(struct udevice *dev, + struct reset_ctl_bulk *resets) +{ + int ret; + + ret = reset_get_bulk(dev, resets); + if (ret == -ENOTSUPP || ret == -ENOENT) + return 0; + + if (ret) + return ret; + + ret = reset_assert_bulk(resets); + + if (!ret) { + udelay(2); + ret = reset_deassert_bulk(resets); + } + if (ret) { + reset_release_bulk(resets); + return ret; + } + + return 0; +} + +static int dwc2_udc_otg_clk_init(struct udevice *dev, + struct clk_bulk *clks) +{ + int ret; + + ret = clk_get_bulk(dev, clks); + if (ret == -ENOSYS) + return 0; + + if (ret) + return ret; + + ret = clk_enable_bulk(clks); + if (ret) { + clk_release_bulk(clks); + return ret; + } + + return 0; +} + +static int dwc2_udc_otg_probe(struct udevice *dev) +{ + struct dwc2_plat_otg_data *plat = dev_get_plat(dev); + struct dwc2_priv_data *priv = dev_get_priv(dev); + struct dwc2_usbotg_reg *usbotg_reg = + (struct dwc2_usbotg_reg *)plat->regs_otg; + int ret; + + ret = dwc2_udc_otg_clk_init(dev, &priv->clks); + if (ret) + return ret; + + ret = dwc2_udc_otg_reset_init(dev, &priv->resets); + if (ret) + return ret; + + ret = dwc2_phy_setup(dev, &priv->phys); + if (ret) + return ret; + + if (plat->activate_stm_id_vb_detection) { + if (CONFIG_IS_ENABLED(DM_REGULATOR) && + (!plat->force_b_session_valid || + plat->force_vbus_detection)) { + ret = device_get_supply_regulator(dev, "usb33d-supply", + &priv->usb33d_supply); + if (ret) { + dev_err(dev, "can't get voltage level detector supply\n"); + return ret; + } + ret = regulator_set_enable(priv->usb33d_supply, true); + if (ret) { + dev_err(dev, "can't enable voltage level detector supply\n"); + return ret; + } + } + + if (plat->force_b_session_valid && + !plat->force_vbus_detection) { + /* Override VBUS detection: enable then value*/ + setbits_le32(&usbotg_reg->gotgctl, VB_VALOEN); + setbits_le32(&usbotg_reg->gotgctl, VB_VALOVAL); + } else { + /* Enable VBUS sensing */ + setbits_le32(&usbotg_reg->ggpio, + GGPIO_STM32_OTG_GCCFG_VBDEN); + } + if (plat->force_b_session_valid) { + /* Override B session bits: enable then value */ + setbits_le32(&usbotg_reg->gotgctl, A_VALOEN | B_VALOEN); + setbits_le32(&usbotg_reg->gotgctl, + A_VALOVAL | B_VALOVAL); + } else { + /* Enable ID detection */ + setbits_le32(&usbotg_reg->ggpio, + GGPIO_STM32_OTG_GCCFG_IDEN); + } + } + + ret = dwc2_udc_probe(plat); + if (ret) + return ret; + + the_controller->driver = 0; + + ret = usb_add_gadget_udc((struct device *)dev, &the_controller->gadget); + + return ret; +} + +static int dwc2_udc_otg_remove(struct udevice *dev) +{ + struct dwc2_priv_data *priv = dev_get_priv(dev); + + usb_del_gadget_udc(&the_controller->gadget); + + reset_release_bulk(&priv->resets); + + clk_release_bulk(&priv->clks); + + dwc2_phy_shutdown(dev, &priv->phys); + + return dm_scan_fdt_dev(dev); +} + +static const struct udevice_id dwc2_udc_otg_ids[] = { + { .compatible = "snps,dwc2" }, + { .compatible = "brcm,bcm2835-usb" }, + { .compatible = "st,stm32mp15-hsotg", + .data = (ulong)dwc2_set_stm32mp1_hsotg_params }, + {}, +}; + +U_BOOT_DRIVER(dwc2_udc_otg) = { + .name = "dwc2-udc-otg", + .id = UCLASS_USB_GADGET_GENERIC, + .of_match = dwc2_udc_otg_ids, + .of_to_plat = dwc2_udc_otg_of_to_plat, + .probe = dwc2_udc_otg_probe, + .remove = dwc2_udc_otg_remove, + .plat_auto = sizeof(struct dwc2_plat_otg_data), + .priv_auto = sizeof(struct dwc2_priv_data), +}; + +int dwc2_udc_B_session_valid(struct udevice *dev) +{ + struct dwc2_plat_otg_data *plat = dev_get_plat(dev); + struct dwc2_usbotg_reg *usbotg_reg = + (struct dwc2_usbotg_reg *)plat->regs_otg; + + return readl(&usbotg_reg->gotgctl) & B_SESSION_VALID; +} +#endif /* CONFIG_IS_ENABLED(DM_USB_GADGET) */ diff --git a/roms/u-boot/drivers/usb/gadget/dwc2_udc_otg_phy.c b/roms/u-boot/drivers/usb/gadget/dwc2_udc_otg_phy.c new file mode 100644 index 000000000..7f8e9564b --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/dwc2_udc_otg_phy.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * drivers/usb/gadget/dwc2_udc_otg.c + * Designware DWC2 on-chip full/high speed USB OTG 2.0 device controllers + * + * Copyright (C) 2008 for Samsung Electronics + * + * BSP Support for Samsung's UDC driver + * available at: + * git://git.kernel.org/pub/scm/linux/kernel/git/kki_ap/linux-2.6-samsung.git + * + * State machine bugfixes: + * Marek Szyprowski <m.szyprowski@samsung.com> + * + * Ported to u-boot: + * Marek Szyprowski <m.szyprowski@samsung.com> + * Lukasz Majewski <l.majewski@samsumg.com> + */ + +#include <common.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <malloc.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + +#include <asm/byteorder.h> +#include <asm/unaligned.h> +#include <asm/io.h> + +#include <asm/mach-types.h> + +#include "dwc2_udc_otg_regs.h" +#include "dwc2_udc_otg_priv.h" + +#include <usb/dwc2_udc.h> + +void otg_phy_init(struct dwc2_udc *dev) +{ + unsigned int usb_phy_ctrl = dev->pdata->usb_phy_ctrl; + struct dwc2_usbotg_phy *phy = + (struct dwc2_usbotg_phy *)dev->pdata->regs_phy; + + dev->pdata->phy_control(1); + + /* USB PHY0 Enable */ + printf("USB PHY0 Enable\n"); + + /* Enable PHY */ + writel(readl(usb_phy_ctrl) | USB_PHY_CTRL_EN0, usb_phy_ctrl); + + if (dev->pdata->usb_flags == PHY0_SLEEP) /* C210 Universal */ + writel((readl(&phy->phypwr) + &~(PHY_0_SLEEP | OTG_DISABLE_0 | ANALOG_PWRDOWN) + &~FORCE_SUSPEND_0), &phy->phypwr); + else /* C110 GONI */ + writel((readl(&phy->phypwr) &~(OTG_DISABLE_0 | ANALOG_PWRDOWN) + &~FORCE_SUSPEND_0), &phy->phypwr); + + if (s5p_cpu_id == 0x4412) + writel((readl(&phy->phyclk) & ~(EXYNOS4X12_ID_PULLUP0 | + EXYNOS4X12_COMMON_ON_N0)) | EXYNOS4X12_CLK_SEL_24MHZ, + &phy->phyclk); /* PLL 24Mhz */ + else + writel((readl(&phy->phyclk) & ~(ID_PULLUP0 | COMMON_ON_N0)) | + CLK_SEL_24MHZ, &phy->phyclk); /* PLL 24Mhz */ + + writel((readl(&phy->rstcon) &~(LINK_SW_RST | PHYLNK_SW_RST)) + | PHY_SW_RST0, &phy->rstcon); + udelay(10); + writel(readl(&phy->rstcon) + &~(PHY_SW_RST0 | LINK_SW_RST | PHYLNK_SW_RST), &phy->rstcon); + udelay(10); +} + +void otg_phy_off(struct dwc2_udc *dev) +{ + unsigned int usb_phy_ctrl = dev->pdata->usb_phy_ctrl; + struct dwc2_usbotg_phy *phy = + (struct dwc2_usbotg_phy *)dev->pdata->regs_phy; + + /* reset controller just in case */ + writel(PHY_SW_RST0, &phy->rstcon); + udelay(20); + writel(readl(&phy->phypwr) &~PHY_SW_RST0, &phy->rstcon); + udelay(20); + + writel(readl(&phy->phypwr) | OTG_DISABLE_0 | ANALOG_PWRDOWN + | FORCE_SUSPEND_0, &phy->phypwr); + + writel(readl(usb_phy_ctrl) &~USB_PHY_CTRL_EN0, usb_phy_ctrl); + + writel((readl(&phy->phyclk) & ~(ID_PULLUP0 | COMMON_ON_N0)), + &phy->phyclk); + + udelay(10000); + + dev->pdata->phy_control(0); +} diff --git a/roms/u-boot/drivers/usb/gadget/dwc2_udc_otg_priv.h b/roms/u-boot/drivers/usb/gadget/dwc2_udc_otg_priv.h new file mode 100644 index 000000000..e72b22ac6 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/dwc2_udc_otg_priv.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Designware DWC2 on-chip full/high speed USB device controllers + * Copyright (C) 2005 for Samsung Electronics + */ + +#ifndef __DWC2_UDC_OTG_PRIV__ +#define __DWC2_UDC_OTG_PRIV__ + +#include <linux/errno.h> +#include <linux/sizes.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/list.h> +#include <usb/dwc2_udc.h> + +/*-------------------------------------------------------------------------*/ +/* DMA bounce buffer size, 16K is enough even for mass storage */ +#define DMA_BUFFER_SIZE (16*SZ_1K) + +#define EP0_FIFO_SIZE 64 +#define EP_FIFO_SIZE 512 +#define EP_FIFO_SIZE2 1024 +/* ep0-control, ep1in-bulk, ep2out-bulk, ep3in-int */ +#define DWC2_MAX_ENDPOINTS 4 + +#define WAIT_FOR_SETUP 0 +#define DATA_STATE_XMIT 1 +#define DATA_STATE_NEED_ZLP 2 +#define WAIT_FOR_OUT_STATUS 3 +#define DATA_STATE_RECV 4 +#define WAIT_FOR_COMPLETE 5 +#define WAIT_FOR_OUT_COMPLETE 6 +#define WAIT_FOR_IN_COMPLETE 7 +#define WAIT_FOR_NULL_COMPLETE 8 + +#define TEST_J_SEL 0x1 +#define TEST_K_SEL 0x2 +#define TEST_SE0_NAK_SEL 0x3 +#define TEST_PACKET_SEL 0x4 +#define TEST_FORCE_ENABLE_SEL 0x5 + +/* ************************************************************************* */ +/* IO + */ + +enum ep_type { + ep_control, ep_bulk_in, ep_bulk_out, ep_interrupt +}; + +struct dwc2_ep { + struct usb_ep ep; + struct dwc2_udc *dev; + + const struct usb_endpoint_descriptor *desc; + struct list_head queue; + unsigned long pio_irqs; + int len; + void *dma_buf; + + u8 stopped; + u8 bEndpointAddress; + u8 bmAttributes; + + enum ep_type ep_type; + int fifo_num; +}; + +struct dwc2_request { + struct usb_request req; + struct list_head queue; +}; + +struct dwc2_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + + struct dwc2_plat_otg_data *pdata; + + int ep0state; + struct dwc2_ep ep[DWC2_MAX_ENDPOINTS]; + + unsigned char usb_address; + + unsigned req_pending:1, req_std:1; +}; + +#define ep_is_in(EP) (((EP)->bEndpointAddress&USB_DIR_IN) == USB_DIR_IN) +#define ep_index(EP) ((EP)->bEndpointAddress&0xF) +#define ep_maxpacket(EP) ((EP)->ep.maxpacket) + +void otg_phy_init(struct dwc2_udc *dev); +void otg_phy_off(struct dwc2_udc *dev); + +#endif /* __DWC2_UDC_OTG_PRIV__ */ diff --git a/roms/u-boot/drivers/usb/gadget/dwc2_udc_otg_regs.h b/roms/u-boot/drivers/usb/gadget/dwc2_udc_otg_regs.h new file mode 100644 index 000000000..9ca6f4237 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/dwc2_udc_otg_regs.h @@ -0,0 +1,296 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* linux/arch/arm/plat-s3c/include/plat/regs-otg.h + * + * Copyright (C) 2004 Herbert Poetzl <herbert@13thfloor.at> + * + * Registers remapping: + * Lukasz Majewski <l.majewski@samsumg.com> + */ + +#ifndef __ASM_ARCH_REGS_USB_OTG_HS_H +#define __ASM_ARCH_REGS_USB_OTG_HS_H + +/* USB2.0 OTG Controller register */ +#include <linux/bitops.h> +struct dwc2_usbotg_phy { + u32 phypwr; + u32 phyclk; + u32 rstcon; +}; + +/* Device Logical IN Endpoint-Specific Registers */ +struct dwc2_dev_in_endp { + u32 diepctl; + u8 res1[4]; + u32 diepint; + u8 res2[4]; + u32 dieptsiz; + u32 diepdma; + u8 res3[4]; + u32 diepdmab; +}; + +/* Device Logical OUT Endpoint-Specific Registers */ +struct dwc2_dev_out_endp { + u32 doepctl; + u8 res1[4]; + u32 doepint; + u8 res2[4]; + u32 doeptsiz; + u32 doepdma; + u8 res3[4]; + u32 doepdmab; +}; + +struct ep_fifo { + u32 fifo; + u8 res[4092]; +}; + +/* USB2.0 OTG Controller register */ +struct dwc2_usbotg_reg { + /* Core Global Registers */ + u32 gotgctl; /* OTG Control & Status */ + u32 gotgint; /* OTG Interrupt */ + u32 gahbcfg; /* Core AHB Configuration */ + u32 gusbcfg; /* Core USB Configuration */ + u32 grstctl; /* Core Reset */ + u32 gintsts; /* Core Interrupt */ + u32 gintmsk; /* Core Interrupt Mask */ + u32 grxstsr; /* Receive Status Debug Read/Status Read */ + u32 grxstsp; /* Receive Status Debug Pop/Status Pop */ + u32 grxfsiz; /* Receive FIFO Size */ + u32 gnptxfsiz; /* Non-Periodic Transmit FIFO Size */ + u8 res0[12]; + u32 ggpio; /* 0x038 */ + u8 res1[20]; + u32 ghwcfg4; /* User HW Config4 */ + u8 res2[176]; + u32 dieptxf[15]; /* Device Periodic Transmit FIFO size register */ + u8 res3[1728]; + /* Device Configuration */ + u32 dcfg; /* Device Configuration Register */ + u32 dctl; /* Device Control */ + u32 dsts; /* Device Status */ + u8 res4[4]; + u32 diepmsk; /* Device IN Endpoint Common Interrupt Mask */ + u32 doepmsk; /* Device OUT Endpoint Common Interrupt Mask */ + u32 daint; /* Device All Endpoints Interrupt */ + u32 daintmsk; /* Device All Endpoints Interrupt Mask */ + u8 res5[224]; + struct dwc2_dev_in_endp in_endp[16]; + struct dwc2_dev_out_endp out_endp[16]; + u8 res6[768]; + struct ep_fifo ep[16]; +}; + +/*===================================================================== */ +/*definitions related to CSR setting */ + +/* DWC2_UDC_OTG_GOTGCTL */ +#define B_SESSION_VALID BIT(19) +#define A_SESSION_VALID BIT(18) +#define B_VALOVAL BIT(7) +#define B_VALOEN BIT(6) +#define A_VALOVAL BIT(5) +#define A_VALOEN BIT(4) +#define VB_VALOVAL BIT(3) +#define VB_VALOEN BIT(2) + +/* DWC2_UDC_OTG_GOTINT */ +#define GOTGINT_SES_END_DET (1<<2) + +/* DWC2_UDC_OTG_GAHBCFG */ +#define PTXFE_HALF (0<<8) +#define PTXFE_ZERO (1<<8) +#define NPTXFE_HALF (0<<7) +#define NPTXFE_ZERO (1<<7) +#define MODE_SLAVE (0<<5) +#define MODE_DMA (1<<5) +#define BURST_SINGLE (0<<1) +#define BURST_INCR (1<<1) +#define BURST_INCR4 (3<<1) +#define BURST_INCR8 (5<<1) +#define BURST_INCR16 (7<<1) +#define GBL_INT_UNMASK (1<<0) +#define GBL_INT_MASK (0<<0) + +/* DWC2_UDC_OTG_GRSTCTL */ +#define AHB_MASTER_IDLE (1u<<31) +#define CORE_SOFT_RESET (0x1<<0) + +/* DWC2_UDC_OTG_GINTSTS/DWC2_UDC_OTG_GINTMSK core interrupt register */ +#define INT_RESUME (1u<<31) +#define INT_DISCONN (0x1<<29) +#define INT_CONN_ID_STS_CNG (0x1<<28) +#define INT_OUT_EP (0x1<<19) +#define INT_IN_EP (0x1<<18) +#define INT_ENUMDONE (0x1<<13) +#define INT_RESET (0x1<<12) +#define INT_SUSPEND (0x1<<11) +#define INT_EARLY_SUSPEND (0x1<<10) +#define INT_NP_TX_FIFO_EMPTY (0x1<<5) +#define INT_RX_FIFO_NOT_EMPTY (0x1<<4) +#define INT_SOF (0x1<<3) +#define INT_OTG (0x1<<2) +#define INT_DEV_MODE (0x0<<0) +#define INT_HOST_MODE (0x1<<1) +#define INT_GOUTNakEff (0x01<<7) +#define INT_GINNakEff (0x01<<6) + +#define FULL_SPEED_CONTROL_PKT_SIZE 8 +#define FULL_SPEED_BULK_PKT_SIZE 64 + +#define HIGH_SPEED_CONTROL_PKT_SIZE 64 +#define HIGH_SPEED_BULK_PKT_SIZE 512 + +#define RX_FIFO_SIZE (1024) +#define NPTX_FIFO_SIZE (1024) +#define PTX_FIFO_SIZE (384) + +#define DEPCTL_TXFNUM_0 (0x0<<22) +#define DEPCTL_TXFNUM_1 (0x1<<22) +#define DEPCTL_TXFNUM_2 (0x2<<22) +#define DEPCTL_TXFNUM_3 (0x3<<22) +#define DEPCTL_TXFNUM_4 (0x4<<22) + +/* Enumeration speed */ +#define USB_HIGH_30_60MHZ (0x0<<1) +#define USB_FULL_30_60MHZ (0x1<<1) +#define USB_LOW_6MHZ (0x2<<1) +#define USB_FULL_48MHZ (0x3<<1) + +/* DWC2_UDC_OTG_GRXSTSP STATUS */ +#define OUT_PKT_RECEIVED (0x2<<17) +#define OUT_TRANSFER_COMPLELTED (0x3<<17) +#define SETUP_TRANSACTION_COMPLETED (0x4<<17) +#define SETUP_PKT_RECEIVED (0x6<<17) +#define GLOBAL_OUT_NAK (0x1<<17) + +/* DWC2_UDC_OTG_DCTL device control register */ +#define NORMAL_OPERATION (0x1<<0) +#define SOFT_DISCONNECT (0x1<<1) + +/* DWC2_UDC_OTG_DAINT device all endpoint interrupt register */ +#define DAINT_OUT_BIT (16) +#define DAINT_MASK (0xFFFF) + +/* DWC2_UDC_OTG_DIEPCTL0/DOEPCTL0 device + control IN/OUT endpoint 0 control register */ +#define DEPCTL_EPENA (0x1<<31) +#define DEPCTL_EPDIS (0x1<<30) +#define DEPCTL_SETD1PID (0x1<<29) +#define DEPCTL_SETD0PID (0x1<<28) +#define DEPCTL_SNAK (0x1<<27) +#define DEPCTL_CNAK (0x1<<26) +#define DEPCTL_STALL (0x1<<21) +#define DEPCTL_TYPE_BIT (18) +#define DEPCTL_TYPE_MASK (0x3<<18) +#define DEPCTL_CTRL_TYPE (0x0<<18) +#define DEPCTL_ISO_TYPE (0x1<<18) +#define DEPCTL_BULK_TYPE (0x2<<18) +#define DEPCTL_INTR_TYPE (0x3<<18) +#define DEPCTL_USBACTEP (0x1<<15) +#define DEPCTL_NEXT_EP_BIT (11) +#define DEPCTL_MPS_BIT (0) +#define DEPCTL_MPS_MASK (0x7FF) + +#define DEPCTL0_MPS_64 (0x0<<0) +#define DEPCTL0_MPS_32 (0x1<<0) +#define DEPCTL0_MPS_16 (0x2<<0) +#define DEPCTL0_MPS_8 (0x3<<0) +#define DEPCTL_MPS_BULK_512 (512<<0) +#define DEPCTL_MPS_INT_MPS_16 (16<<0) + +#define DIEPCTL0_NEXT_EP_BIT (11) + + +/* DWC2_UDC_OTG_DIEPMSK/DOEPMSK device IN/OUT endpoint + common interrupt mask register */ +/* DWC2_UDC_OTG_DIEPINTn/DOEPINTn device IN/OUT endpoint interrupt register */ +#define BACK2BACK_SETUP_RECEIVED (0x1<<6) +#define INTKNEPMIS (0x1<<5) +#define INTKN_TXFEMP (0x1<<4) +#define NON_ISO_IN_EP_TIMEOUT (0x1<<3) +#define CTRL_OUT_EP_SETUP_PHASE_DONE (0x1<<3) +#define AHB_ERROR (0x1<<2) +#define EPDISBLD (0x1<<1) +#define TRANSFER_DONE (0x1<<0) + +#define USB_PHY_CTRL_EN0 (0x1 << 0) + +/* OPHYPWR */ +#define PHY_0_SLEEP (0x1 << 5) +#define OTG_DISABLE_0 (0x1 << 4) +#define ANALOG_PWRDOWN (0x1 << 3) +#define FORCE_SUSPEND_0 (0x1 << 0) + +/* URSTCON */ +#define HOST_SW_RST (0x1 << 4) +#define PHY_SW_RST1 (0x1 << 3) +#define PHYLNK_SW_RST (0x1 << 2) +#define LINK_SW_RST (0x1 << 1) +#define PHY_SW_RST0 (0x1 << 0) + +/* OPHYCLK */ +#define COMMON_ON_N1 (0x1 << 7) +#define COMMON_ON_N0 (0x1 << 4) +#define ID_PULLUP0 (0x1 << 2) +#define CLK_SEL_24MHZ (0x3 << 0) +#define CLK_SEL_12MHZ (0x2 << 0) +#define CLK_SEL_48MHZ (0x0 << 0) + +#define EXYNOS4X12_ID_PULLUP0 (0x01 << 3) +#define EXYNOS4X12_COMMON_ON_N0 (0x01 << 4) +#define EXYNOS4X12_CLK_SEL_12MHZ (0x02 << 0) +#define EXYNOS4X12_CLK_SEL_24MHZ (0x05 << 0) + +/* Device Configuration Register DCFG */ +#define DEV_SPEED_HIGH_SPEED_20 (0x0 << 0) +#define DEV_SPEED_FULL_SPEED_20 (0x1 << 0) +#define DEV_SPEED_LOW_SPEED_11 (0x2 << 0) +#define DEV_SPEED_FULL_SPEED_11 (0x3 << 0) +#define EP_MISS_CNT(x) (x << 18) +#define DEVICE_ADDRESS(x) (x << 4) + +/* Core Reset Register (GRSTCTL) */ +#define TX_FIFO_FLUSH (0x1 << 5) +#define RX_FIFO_FLUSH (0x1 << 4) +#define TX_FIFO_NUMBER(x) (x << 6) +#define TX_FIFO_FLUSH_ALL TX_FIFO_NUMBER(0x10) + +/* Masks definitions */ +#define GINTMSK_INIT (INT_OUT_EP | INT_IN_EP | INT_RESUME | INT_ENUMDONE\ + | INT_RESET | INT_SUSPEND | INT_OTG) +#define DOEPMSK_INIT (CTRL_OUT_EP_SETUP_PHASE_DONE | AHB_ERROR|TRANSFER_DONE) +#define DIEPMSK_INIT (NON_ISO_IN_EP_TIMEOUT|AHB_ERROR|TRANSFER_DONE) +#define GAHBCFG_INIT (PTXFE_HALF | NPTXFE_HALF | MODE_DMA | BURST_INCR4\ + | GBL_INT_UNMASK) + +/* Device Endpoint X Transfer Size Register (DIEPTSIZX) */ +#define DIEPT_SIZ_PKT_CNT(x) (x << 19) +#define DIEPT_SIZ_XFER_SIZE(x) (x << 0) + +/* Device OUT Endpoint X Transfer Size Register (DOEPTSIZX) */ +#define DOEPT_SIZ_PKT_CNT(x) (x << 19) +#define DOEPT_SIZ_XFER_SIZE(x) (x << 0) +#define DOEPT_SIZ_XFER_SIZE_MAX_EP0 (0x7F << 0) +#define DOEPT_SIZ_XFER_SIZE_MAX_EP (0x7FFF << 0) + +/* Device Endpoint-N Control Register (DIEPCTLn/DOEPCTLn) */ +#define DIEPCTL_TX_FIFO_NUM(x) (x << 22) +#define DIEPCTL_TX_FIFO_NUM_MASK (~DIEPCTL_TX_FIFO_NUM(0xF)) + +/* Device ALL Endpoints Interrupt Register (DAINT) */ +#define DAINT_IN_EP_INT(x) (x << 0) +#define DAINT_OUT_EP_INT(x) (x << 16) + +/* User HW Config4 */ +#define GHWCFG4_NUM_IN_EPS_MASK (0xf << 26) +#define GHWCFG4_NUM_IN_EPS_SHIFT 26 + +/* OTG general core configuration register (OTG_GCCFG:0x38) for STM32MP1 */ +#define GGPIO_STM32_OTG_GCCFG_VBDEN BIT(21) +#define GGPIO_STM32_OTG_GCCFG_IDEN BIT(22) + +#endif diff --git a/roms/u-boot/drivers/usb/gadget/dwc2_udc_otg_xfer_dma.c b/roms/u-boot/drivers/usb/gadget/dwc2_udc_otg_xfer_dma.c new file mode 100644 index 000000000..f17009a29 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/dwc2_udc_otg_xfer_dma.c @@ -0,0 +1,1508 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * drivers/usb/gadget/dwc2_udc_otg_xfer_dma.c + * Designware DWC2 on-chip full/high speed USB OTG 2.0 device controllers + * + * Copyright (C) 2009 for Samsung Electronics + * + * BSP Support for Samsung's UDC driver + * available at: + * git://git.kernel.org/pub/scm/linux/kernel/git/kki_ap/linux-2.6-samsung.git + * + * State machine bugfixes: + * Marek Szyprowski <m.szyprowski@samsung.com> + * + * Ported to u-boot: + * Marek Szyprowski <m.szyprowski@samsung.com> + * Lukasz Majewski <l.majewski@samsumg.com> + */ + +#include <common.h> +#include <cpu_func.h> +#include <log.h> +#include <linux/bug.h> + +static u8 clear_feature_num; +int clear_feature_flag; + +/* Bulk-Only Mass Storage Reset (class-specific request) */ +#define GET_MAX_LUN_REQUEST 0xFE +#define BOT_RESET_REQUEST 0xFF + +static inline void dwc2_udc_ep0_zlp(struct dwc2_udc *dev) +{ + u32 ep_ctrl; + + writel(phys_to_bus((unsigned long)usb_ctrl_dma_addr), ®->in_endp[EP0_CON].diepdma); + writel(DIEPT_SIZ_PKT_CNT(1), ®->in_endp[EP0_CON].dieptsiz); + + ep_ctrl = readl(®->in_endp[EP0_CON].diepctl); + writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK, + ®->in_endp[EP0_CON].diepctl); + + debug_cond(DEBUG_EP0 != 0, "%s:EP0 ZLP DIEPCTL0 = 0x%x\n", + __func__, readl(®->in_endp[EP0_CON].diepctl)); + dev->ep0state = WAIT_FOR_IN_COMPLETE; +} + +static void dwc2_udc_pre_setup(void) +{ + u32 ep_ctrl; + + debug_cond(DEBUG_IN_EP, + "%s : Prepare Setup packets.\n", __func__); + + writel(DOEPT_SIZ_PKT_CNT(1) | sizeof(struct usb_ctrlrequest), + ®->out_endp[EP0_CON].doeptsiz); + writel(phys_to_bus((unsigned long)usb_ctrl_dma_addr), ®->out_endp[EP0_CON].doepdma); + + ep_ctrl = readl(®->out_endp[EP0_CON].doepctl); + writel(ep_ctrl|DEPCTL_EPENA, ®->out_endp[EP0_CON].doepctl); + + debug_cond(DEBUG_EP0 != 0, "%s:EP0 ZLP DIEPCTL0 = 0x%x\n", + __func__, readl(®->in_endp[EP0_CON].diepctl)); + debug_cond(DEBUG_EP0 != 0, "%s:EP0 ZLP DOEPCTL0 = 0x%x\n", + __func__, readl(®->out_endp[EP0_CON].doepctl)); + +} + +static inline void dwc2_ep0_complete_out(void) +{ + u32 ep_ctrl; + + debug_cond(DEBUG_EP0 != 0, "%s:EP0 ZLP DIEPCTL0 = 0x%x\n", + __func__, readl(®->in_endp[EP0_CON].diepctl)); + debug_cond(DEBUG_EP0 != 0, "%s:EP0 ZLP DOEPCTL0 = 0x%x\n", + __func__, readl(®->out_endp[EP0_CON].doepctl)); + + debug_cond(DEBUG_IN_EP, + "%s : Prepare Complete Out packet.\n", __func__); + + writel(DOEPT_SIZ_PKT_CNT(1) | sizeof(struct usb_ctrlrequest), + ®->out_endp[EP0_CON].doeptsiz); + writel(phys_to_bus((unsigned long)usb_ctrl_dma_addr), ®->out_endp[EP0_CON].doepdma); + + ep_ctrl = readl(®->out_endp[EP0_CON].doepctl); + writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK, + ®->out_endp[EP0_CON].doepctl); + + debug_cond(DEBUG_EP0 != 0, "%s:EP0 ZLP DIEPCTL0 = 0x%x\n", + __func__, readl(®->in_endp[EP0_CON].diepctl)); + debug_cond(DEBUG_EP0 != 0, "%s:EP0 ZLP DOEPCTL0 = 0x%x\n", + __func__, readl(®->out_endp[EP0_CON].doepctl)); + +} + + +static int setdma_rx(struct dwc2_ep *ep, struct dwc2_request *req) +{ + u32 *buf, ctrl; + u32 length, pktcnt; + u32 ep_num = ep_index(ep); + + buf = req->req.buf + req->req.actual; + length = min_t(u32, req->req.length - req->req.actual, + ep_num ? DMA_BUFFER_SIZE : ep->ep.maxpacket); + + ep->len = length; + ep->dma_buf = buf; + + if (ep_num == EP0_CON || length == 0) + pktcnt = 1; + else + pktcnt = (length - 1)/(ep->ep.maxpacket) + 1; + + ctrl = readl(®->out_endp[ep_num].doepctl); + + invalidate_dcache_range((unsigned long) ep->dma_buf, + (unsigned long) ep->dma_buf + + ROUND(ep->len, CONFIG_SYS_CACHELINE_SIZE)); + + writel(phys_to_bus((unsigned long)ep->dma_buf), ®->out_endp[ep_num].doepdma); + writel(DOEPT_SIZ_PKT_CNT(pktcnt) | DOEPT_SIZ_XFER_SIZE(length), + ®->out_endp[ep_num].doeptsiz); + writel(DEPCTL_EPENA|DEPCTL_CNAK|ctrl, ®->out_endp[ep_num].doepctl); + + debug_cond(DEBUG_OUT_EP != 0, + "%s: EP%d RX DMA start : DOEPDMA = 0x%x," + "DOEPTSIZ = 0x%x, DOEPCTL = 0x%x\n" + "\tbuf = 0x%p, pktcnt = %d, xfersize = %d\n", + __func__, ep_num, + readl(®->out_endp[ep_num].doepdma), + readl(®->out_endp[ep_num].doeptsiz), + readl(®->out_endp[ep_num].doepctl), + buf, pktcnt, length); + return 0; + +} + +static int setdma_tx(struct dwc2_ep *ep, struct dwc2_request *req) +{ + u32 *buf, ctrl = 0; + u32 length, pktcnt; + u32 ep_num = ep_index(ep); + + buf = req->req.buf + req->req.actual; + length = req->req.length - req->req.actual; + + if (ep_num == EP0_CON) + length = min(length, (u32)ep_maxpacket(ep)); + + ep->len = length; + ep->dma_buf = buf; + + flush_dcache_range((unsigned long) ep->dma_buf, + (unsigned long) ep->dma_buf + + ROUND(ep->len, CONFIG_SYS_CACHELINE_SIZE)); + + if (length == 0) + pktcnt = 1; + else + pktcnt = (length - 1)/(ep->ep.maxpacket) + 1; + + /* Flush the endpoint's Tx FIFO */ + writel(TX_FIFO_NUMBER(ep->fifo_num), ®->grstctl); + writel(TX_FIFO_NUMBER(ep->fifo_num) | TX_FIFO_FLUSH, ®->grstctl); + while (readl(®->grstctl) & TX_FIFO_FLUSH) + ; + + writel(phys_to_bus((unsigned long)ep->dma_buf), ®->in_endp[ep_num].diepdma); + writel(DIEPT_SIZ_PKT_CNT(pktcnt) | DIEPT_SIZ_XFER_SIZE(length), + ®->in_endp[ep_num].dieptsiz); + + ctrl = readl(®->in_endp[ep_num].diepctl); + + /* Write the FIFO number to be used for this endpoint */ + ctrl &= DIEPCTL_TX_FIFO_NUM_MASK; + ctrl |= DIEPCTL_TX_FIFO_NUM(ep->fifo_num); + + /* Clear reserved (Next EP) bits */ + ctrl = (ctrl&~(EP_MASK<<DEPCTL_NEXT_EP_BIT)); + + writel(DEPCTL_EPENA|DEPCTL_CNAK|ctrl, ®->in_endp[ep_num].diepctl); + + debug_cond(DEBUG_IN_EP, + "%s:EP%d TX DMA start : DIEPDMA0 = 0x%x," + "DIEPTSIZ0 = 0x%x, DIEPCTL0 = 0x%x\n" + "\tbuf = 0x%p, pktcnt = %d, xfersize = %d\n", + __func__, ep_num, + readl(®->in_endp[ep_num].diepdma), + readl(®->in_endp[ep_num].dieptsiz), + readl(®->in_endp[ep_num].diepctl), + buf, pktcnt, length); + + return length; +} + +static void complete_rx(struct dwc2_udc *dev, u8 ep_num) +{ + struct dwc2_ep *ep = &dev->ep[ep_num]; + struct dwc2_request *req = NULL; + u32 ep_tsr = 0, xfer_size = 0, is_short = 0; + + if (list_empty(&ep->queue)) { + debug_cond(DEBUG_OUT_EP != 0, + "%s: RX DMA done : NULL REQ on OUT EP-%d\n", + __func__, ep_num); + return; + + } + + req = list_entry(ep->queue.next, struct dwc2_request, queue); + ep_tsr = readl(®->out_endp[ep_num].doeptsiz); + + if (ep_num == EP0_CON) + xfer_size = (ep_tsr & DOEPT_SIZ_XFER_SIZE_MAX_EP0); + else + xfer_size = (ep_tsr & DOEPT_SIZ_XFER_SIZE_MAX_EP); + + xfer_size = ep->len - xfer_size; + + /* + * NOTE: + * + * Please be careful with proper buffer allocation for USB request, + * which needs to be aligned to CONFIG_SYS_CACHELINE_SIZE, not only + * with starting address, but also its size shall be a cache line + * multiplication. + * + * This will prevent from corruption of data allocated immediatelly + * before or after the buffer. + * + * For armv7, the cache_v7.c provides proper code to emit "ERROR" + * message to warn users. + */ + invalidate_dcache_range((unsigned long) ep->dma_buf, + (unsigned long) ep->dma_buf + + ROUND(xfer_size, CONFIG_SYS_CACHELINE_SIZE)); + + req->req.actual += min(xfer_size, req->req.length - req->req.actual); + is_short = !!(xfer_size % ep->ep.maxpacket); + + debug_cond(DEBUG_OUT_EP != 0, + "%s: RX DMA done : ep = %d, rx bytes = %d/%d, " + "is_short = %d, DOEPTSIZ = 0x%x, remained bytes = %d\n", + __func__, ep_num, req->req.actual, req->req.length, + is_short, ep_tsr, req->req.length - req->req.actual); + + if (is_short || req->req.actual == req->req.length) { + if (ep_num == EP0_CON && dev->ep0state == DATA_STATE_RECV) { + debug_cond(DEBUG_OUT_EP != 0, " => Send ZLP\n"); + dwc2_udc_ep0_zlp(dev); + /* packet will be completed in complete_tx() */ + dev->ep0state = WAIT_FOR_IN_COMPLETE; + } else { + done(ep, req, 0); + + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, + struct dwc2_request, queue); + debug_cond(DEBUG_OUT_EP != 0, + "%s: Next Rx request start...\n", + __func__); + setdma_rx(ep, req); + } + } + } else + setdma_rx(ep, req); +} + +static void complete_tx(struct dwc2_udc *dev, u8 ep_num) +{ + struct dwc2_ep *ep = &dev->ep[ep_num]; + struct dwc2_request *req; + u32 ep_tsr = 0, xfer_size = 0, is_short = 0; + u32 last; + + if (dev->ep0state == WAIT_FOR_NULL_COMPLETE) { + dev->ep0state = WAIT_FOR_OUT_COMPLETE; + dwc2_ep0_complete_out(); + return; + } + + if (list_empty(&ep->queue)) { + debug_cond(DEBUG_IN_EP, + "%s: TX DMA done : NULL REQ on IN EP-%d\n", + __func__, ep_num); + return; + + } + + req = list_entry(ep->queue.next, struct dwc2_request, queue); + + ep_tsr = readl(®->in_endp[ep_num].dieptsiz); + + xfer_size = ep->len; + is_short = (xfer_size < ep->ep.maxpacket); + req->req.actual += min(xfer_size, req->req.length - req->req.actual); + + debug_cond(DEBUG_IN_EP, + "%s: TX DMA done : ep = %d, tx bytes = %d/%d, " + "is_short = %d, DIEPTSIZ = 0x%x, remained bytes = %d\n", + __func__, ep_num, req->req.actual, req->req.length, + is_short, ep_tsr, req->req.length - req->req.actual); + + if (ep_num == 0) { + if (dev->ep0state == DATA_STATE_XMIT) { + debug_cond(DEBUG_IN_EP, + "%s: ep_num = %d, ep0stat ==" + "DATA_STATE_XMIT\n", + __func__, ep_num); + last = write_fifo_ep0(ep, req); + if (last) + dev->ep0state = WAIT_FOR_COMPLETE; + } else if (dev->ep0state == WAIT_FOR_IN_COMPLETE) { + debug_cond(DEBUG_IN_EP, + "%s: ep_num = %d, completing request\n", + __func__, ep_num); + done(ep, req, 0); + dev->ep0state = WAIT_FOR_SETUP; + } else if (dev->ep0state == WAIT_FOR_COMPLETE) { + debug_cond(DEBUG_IN_EP, + "%s: ep_num = %d, completing request\n", + __func__, ep_num); + done(ep, req, 0); + dev->ep0state = WAIT_FOR_OUT_COMPLETE; + dwc2_ep0_complete_out(); + } else { + debug_cond(DEBUG_IN_EP, + "%s: ep_num = %d, invalid ep state\n", + __func__, ep_num); + } + return; + } + + if (req->req.actual == req->req.length) + done(ep, req, 0); + + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct dwc2_request, queue); + debug_cond(DEBUG_IN_EP, + "%s: Next Tx request start...\n", __func__); + setdma_tx(ep, req); + } +} + +static inline void dwc2_udc_check_tx_queue(struct dwc2_udc *dev, u8 ep_num) +{ + struct dwc2_ep *ep = &dev->ep[ep_num]; + struct dwc2_request *req; + + debug_cond(DEBUG_IN_EP, + "%s: Check queue, ep_num = %d\n", __func__, ep_num); + + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct dwc2_request, queue); + debug_cond(DEBUG_IN_EP, + "%s: Next Tx request(0x%p) start...\n", + __func__, req); + + if (ep_is_in(ep)) + setdma_tx(ep, req); + else + setdma_rx(ep, req); + } else { + debug_cond(DEBUG_IN_EP, + "%s: NULL REQ on IN EP-%d\n", __func__, ep_num); + + return; + } + +} + +static void process_ep_in_intr(struct dwc2_udc *dev) +{ + u32 ep_intr, ep_intr_status; + u8 ep_num = 0; + + ep_intr = readl(®->daint); + debug_cond(DEBUG_IN_EP, + "*** %s: EP In interrupt : DAINT = 0x%x\n", __func__, ep_intr); + + ep_intr &= DAINT_MASK; + + while (ep_intr) { + if (ep_intr & DAINT_IN_EP_INT(1)) { + ep_intr_status = readl(®->in_endp[ep_num].diepint); + debug_cond(DEBUG_IN_EP, + "\tEP%d-IN : DIEPINT = 0x%x\n", + ep_num, ep_intr_status); + + /* Interrupt Clear */ + writel(ep_intr_status, ®->in_endp[ep_num].diepint); + + if (ep_intr_status & TRANSFER_DONE) { + complete_tx(dev, ep_num); + + if (ep_num == 0) { + if (dev->ep0state == + WAIT_FOR_IN_COMPLETE) + dev->ep0state = WAIT_FOR_SETUP; + + if (dev->ep0state == WAIT_FOR_SETUP) + dwc2_udc_pre_setup(); + + /* continue transfer after + set_clear_halt for DMA mode */ + if (clear_feature_flag == 1) { + dwc2_udc_check_tx_queue(dev, + clear_feature_num); + clear_feature_flag = 0; + } + } + } + } + ep_num++; + ep_intr >>= 1; + } +} + +static void process_ep_out_intr(struct dwc2_udc *dev) +{ + u32 ep_intr, ep_intr_status; + u8 ep_num = 0; + u32 ep_tsr = 0, xfer_size = 0; + u32 epsiz_reg = reg->out_endp[ep_num].doeptsiz; + u32 req_size = sizeof(struct usb_ctrlrequest); + + ep_intr = readl(®->daint); + debug_cond(DEBUG_OUT_EP != 0, + "*** %s: EP OUT interrupt : DAINT = 0x%x\n", + __func__, ep_intr); + + ep_intr = (ep_intr >> DAINT_OUT_BIT) & DAINT_MASK; + + while (ep_intr) { + if (ep_intr & 0x1) { + ep_intr_status = readl(®->out_endp[ep_num].doepint); + debug_cond(DEBUG_OUT_EP != 0, + "\tEP%d-OUT : DOEPINT = 0x%x\n", + ep_num, ep_intr_status); + + /* Interrupt Clear */ + writel(ep_intr_status, ®->out_endp[ep_num].doepint); + + if (ep_num == 0) { + if (ep_intr_status & TRANSFER_DONE) { + ep_tsr = readl(&epsiz_reg); + xfer_size = ep_tsr & + DOEPT_SIZ_XFER_SIZE_MAX_EP0; + + if (xfer_size == req_size && + dev->ep0state == WAIT_FOR_SETUP) { + dwc2_udc_pre_setup(); + } else if (dev->ep0state != + WAIT_FOR_OUT_COMPLETE) { + complete_rx(dev, ep_num); + } else { + dev->ep0state = WAIT_FOR_SETUP; + dwc2_udc_pre_setup(); + } + } + + if (ep_intr_status & + CTRL_OUT_EP_SETUP_PHASE_DONE) { + debug_cond(DEBUG_OUT_EP != 0, + "SETUP packet arrived\n"); + dwc2_handle_ep0(dev); + } + } else { + if (ep_intr_status & TRANSFER_DONE) + complete_rx(dev, ep_num); + } + } + ep_num++; + ep_intr >>= 1; + } +} + +/* + * usb client interrupt handler. + */ +static int dwc2_udc_irq(int irq, void *_dev) +{ + struct dwc2_udc *dev = _dev; + u32 intr_status, gotgint; + u32 usb_status, gintmsk; + unsigned long flags = 0; + + spin_lock_irqsave(&dev->lock, flags); + + intr_status = readl(®->gintsts); + gintmsk = readl(®->gintmsk); + + debug_cond(DEBUG_ISR, + "\n*** %s : GINTSTS=0x%x(on state %s), GINTMSK : 0x%x," + "DAINT : 0x%x, DAINTMSK : 0x%x\n", + __func__, intr_status, state_names[dev->ep0state], gintmsk, + readl(®->daint), readl(®->daintmsk)); + + if (!intr_status) { + spin_unlock_irqrestore(&dev->lock, flags); + return IRQ_HANDLED; + } + + if (intr_status & INT_ENUMDONE) { + debug_cond(DEBUG_ISR, "\tSpeed Detection interrupt\n"); + + writel(INT_ENUMDONE, ®->gintsts); + usb_status = (readl(®->dsts) & 0x6); + + if (usb_status & (USB_FULL_30_60MHZ | USB_FULL_48MHZ)) { + debug_cond(DEBUG_ISR, + "\t\tFull Speed Detection\n"); + set_max_pktsize(dev, USB_SPEED_FULL); + + } else { + debug_cond(DEBUG_ISR, + "\t\tHigh Speed Detection : 0x%x\n", + usb_status); + set_max_pktsize(dev, USB_SPEED_HIGH); + } + } + + if (intr_status & INT_EARLY_SUSPEND) { + debug_cond(DEBUG_ISR, "\tEarly suspend interrupt\n"); + writel(INT_EARLY_SUSPEND, ®->gintsts); + } + + if (intr_status & INT_SUSPEND) { + usb_status = readl(®->dsts); + debug_cond(DEBUG_ISR, + "\tSuspend interrupt :(DSTS):0x%x\n", usb_status); + writel(INT_SUSPEND, ®->gintsts); + + if (dev->gadget.speed != USB_SPEED_UNKNOWN + && dev->driver) { + if (dev->driver->suspend) + dev->driver->suspend(&dev->gadget); + } + } + + if (intr_status & INT_OTG) { + gotgint = readl(®->gotgint); + debug_cond(DEBUG_ISR, + "\tOTG interrupt: (GOTGINT):0x%x\n", gotgint); + + if (gotgint & GOTGINT_SES_END_DET) { + debug_cond(DEBUG_ISR, "\t\tSession End Detected\n"); + /* Let gadget detect disconnected state */ + if (dev->driver->disconnect) { + spin_unlock_irqrestore(&dev->lock, flags); + dev->driver->disconnect(&dev->gadget); + spin_lock_irqsave(&dev->lock, flags); + } + } + writel(gotgint, ®->gotgint); + } + + if (intr_status & INT_RESUME) { + debug_cond(DEBUG_ISR, "\tResume interrupt\n"); + writel(INT_RESUME, ®->gintsts); + + if (dev->gadget.speed != USB_SPEED_UNKNOWN + && dev->driver + && dev->driver->resume) { + + dev->driver->resume(&dev->gadget); + } + } + + if (intr_status & INT_RESET) { + usb_status = readl(®->gotgctl); + debug_cond(DEBUG_ISR, + "\tReset interrupt - (GOTGCTL):0x%x\n", usb_status); + writel(INT_RESET, ®->gintsts); + + if ((usb_status & 0xc0000) == (0x3 << 18)) { + if (reset_available) { + debug_cond(DEBUG_ISR, + "\t\tOTG core got reset (%d)!!\n", + reset_available); + reconfig_usbd(dev); + dev->ep0state = WAIT_FOR_SETUP; + reset_available = 0; + dwc2_udc_pre_setup(); + } else + reset_available = 1; + + } else { + reset_available = 1; + debug_cond(DEBUG_ISR, + "\t\tRESET handling skipped\n"); + } + } + + if (intr_status & INT_IN_EP) + process_ep_in_intr(dev); + + if (intr_status & INT_OUT_EP) + process_ep_out_intr(dev); + + spin_unlock_irqrestore(&dev->lock, flags); + + return IRQ_HANDLED; +} + +/** Queue one request + * Kickstart transfer if needed + */ +static int dwc2_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct dwc2_request *req; + struct dwc2_ep *ep; + struct dwc2_udc *dev; + unsigned long flags = 0; + u32 ep_num, gintsts; + + req = container_of(_req, struct dwc2_request, req); + if (unlikely(!_req || !_req->complete || !_req->buf + || !list_empty(&req->queue))) { + + debug("%s: bad params\n", __func__); + return -EINVAL; + } + + ep = container_of(_ep, struct dwc2_ep, ep); + + if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) { + + debug("%s: bad ep: %s, %d, %p\n", __func__, + ep->ep.name, !ep->desc, _ep); + return -EINVAL; + } + + ep_num = ep_index(ep); + dev = ep->dev; + if (unlikely(!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)) { + + debug("%s: bogus device state %p\n", __func__, dev->driver); + return -ESHUTDOWN; + } + + spin_lock_irqsave(&dev->lock, flags); + + _req->status = -EINPROGRESS; + _req->actual = 0; + + /* kickstart this i/o queue? */ + debug("\n*** %s: %s-%s req = %p, len = %d, buf = %p" + "Q empty = %d, stopped = %d\n", + __func__, _ep->name, ep_is_in(ep) ? "in" : "out", + _req, _req->length, _req->buf, + list_empty(&ep->queue), ep->stopped); + +#ifdef DEBUG + { + int i, len = _req->length; + + printf("pkt = "); + if (len > 64) + len = 64; + for (i = 0; i < len; i++) { + printf("%02x", ((u8 *)_req->buf)[i]); + if ((i & 7) == 7) + printf(" "); + } + printf("\n"); + } +#endif + + if (list_empty(&ep->queue) && !ep->stopped) { + + if (ep_num == 0) { + /* EP0 */ + list_add_tail(&req->queue, &ep->queue); + dwc2_ep0_kick(dev, ep); + req = 0; + + } else if (ep_is_in(ep)) { + gintsts = readl(®->gintsts); + debug_cond(DEBUG_IN_EP, + "%s: ep_is_in, DWC2_UDC_OTG_GINTSTS=0x%x\n", + __func__, gintsts); + + setdma_tx(ep, req); + } else { + gintsts = readl(®->gintsts); + debug_cond(DEBUG_OUT_EP != 0, + "%s:ep_is_out, DWC2_UDC_OTG_GINTSTS=0x%x\n", + __func__, gintsts); + + setdma_rx(ep, req); + } + } + + /* pio or dma irq handler advances the queue. */ + if (likely(req != 0)) + list_add_tail(&req->queue, &ep->queue); + + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +/****************************************************************/ +/* End Point 0 related functions */ +/****************************************************************/ + +/* return: 0 = still running, 1 = completed, negative = errno */ +static int write_fifo_ep0(struct dwc2_ep *ep, struct dwc2_request *req) +{ + u32 max; + unsigned count; + int is_last; + + max = ep_maxpacket(ep); + + debug_cond(DEBUG_EP0 != 0, "%s: max = %d\n", __func__, max); + + count = setdma_tx(ep, req); + + /* last packet is usually short (or a zlp) */ + if (likely(count != max)) + is_last = 1; + else { + if (likely(req->req.length != req->req.actual + count) + || req->req.zero) + is_last = 0; + else + is_last = 1; + } + + debug_cond(DEBUG_EP0 != 0, + "%s: wrote %s %d bytes%s %d left %p\n", __func__, + ep->ep.name, count, + is_last ? "/L" : "", + req->req.length - req->req.actual - count, req); + + /* requests complete when all IN data is in the FIFO */ + if (is_last) { + ep->dev->ep0state = WAIT_FOR_SETUP; + return 1; + } + + return 0; +} + +static int dwc2_fifo_read(struct dwc2_ep *ep, void *cp, int max) +{ + invalidate_dcache_range((unsigned long)cp, (unsigned long)cp + + ROUND(max, CONFIG_SYS_CACHELINE_SIZE)); + + debug_cond(DEBUG_EP0 != 0, + "%s: bytes=%d, ep_index=%d 0x%p\n", __func__, + max, ep_index(ep), cp); + + return max; +} + +/** + * udc_set_address - set the USB address for this device + * @address: + * + * Called from control endpoint function + * after it decodes a set address setup packet. + */ +static void udc_set_address(struct dwc2_udc *dev, unsigned char address) +{ + u32 ctrl = readl(®->dcfg); + writel(DEVICE_ADDRESS(address) | ctrl, ®->dcfg); + + dwc2_udc_ep0_zlp(dev); + + debug_cond(DEBUG_EP0 != 0, + "%s: USB OTG 2.0 Device address=%d, DCFG=0x%x\n", + __func__, address, readl(®->dcfg)); + + dev->usb_address = address; +} + +static inline void dwc2_udc_ep0_set_stall(struct dwc2_ep *ep) +{ + struct dwc2_udc *dev; + u32 ep_ctrl = 0; + + dev = ep->dev; + ep_ctrl = readl(®->in_endp[EP0_CON].diepctl); + + /* set the disable and stall bits */ + if (ep_ctrl & DEPCTL_EPENA) + ep_ctrl |= DEPCTL_EPDIS; + + ep_ctrl |= DEPCTL_STALL; + + writel(ep_ctrl, ®->in_endp[EP0_CON].diepctl); + + debug_cond(DEBUG_EP0 != 0, + "%s: set ep%d stall, DIEPCTL0 = 0x%p\n", + __func__, ep_index(ep), ®->in_endp[EP0_CON].diepctl); + /* + * The application can only set this bit, and the core clears it, + * when a SETUP token is received for this endpoint + */ + dev->ep0state = WAIT_FOR_SETUP; + + dwc2_udc_pre_setup(); +} + +static void dwc2_ep0_read(struct dwc2_udc *dev) +{ + struct dwc2_request *req; + struct dwc2_ep *ep = &dev->ep[0]; + + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct dwc2_request, queue); + + } else { + debug("%s: ---> BUG\n", __func__); + BUG(); + return; + } + + debug_cond(DEBUG_EP0 != 0, + "%s: req = %p, req.length = 0x%x, req.actual = 0x%x\n", + __func__, req, req->req.length, req->req.actual); + + if (req->req.length == 0) { + /* zlp for Set_configuration, Set_interface, + * or Bulk-Only mass storge reset */ + + ep->len = 0; + dwc2_udc_ep0_zlp(dev); + + debug_cond(DEBUG_EP0 != 0, + "%s: req.length = 0, bRequest = %d\n", + __func__, usb_ctrl->bRequest); + return; + } + + setdma_rx(ep, req); +} + +/* + * DATA_STATE_XMIT + */ +static int dwc2_ep0_write(struct dwc2_udc *dev) +{ + struct dwc2_request *req; + struct dwc2_ep *ep = &dev->ep[0]; + int ret, need_zlp = 0; + + if (list_empty(&ep->queue)) + req = 0; + else + req = list_entry(ep->queue.next, struct dwc2_request, queue); + + if (!req) { + debug_cond(DEBUG_EP0 != 0, "%s: NULL REQ\n", __func__); + return 0; + } + + debug_cond(DEBUG_EP0 != 0, + "%s: req = %p, req.length = 0x%x, req.actual = 0x%x\n", + __func__, req, req->req.length, req->req.actual); + + if (req->req.length - req->req.actual == ep0_fifo_size) { + /* Next write will end with the packet size, */ + /* so we need Zero-length-packet */ + need_zlp = 1; + } + + ret = write_fifo_ep0(ep, req); + + if ((ret == 1) && !need_zlp) { + /* Last packet */ + dev->ep0state = WAIT_FOR_COMPLETE; + debug_cond(DEBUG_EP0 != 0, + "%s: finished, waiting for status\n", __func__); + + } else { + dev->ep0state = DATA_STATE_XMIT; + debug_cond(DEBUG_EP0 != 0, + "%s: not finished\n", __func__); + } + + return 1; +} + +static int dwc2_udc_get_status(struct dwc2_udc *dev, + struct usb_ctrlrequest *crq) +{ + u8 ep_num = crq->wIndex & 0x7F; + u16 g_status = 0; + u32 ep_ctrl; + + debug_cond(DEBUG_SETUP != 0, + "%s: *** USB_REQ_GET_STATUS\n", __func__); + printf("crq->brequest:0x%x\n", crq->bRequestType & USB_RECIP_MASK); + switch (crq->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_INTERFACE: + g_status = 0; + debug_cond(DEBUG_SETUP != 0, + "\tGET_STATUS:USB_RECIP_INTERFACE, g_stauts = %d\n", + g_status); + break; + + case USB_RECIP_DEVICE: + g_status = 0x1; /* Self powered */ + debug_cond(DEBUG_SETUP != 0, + "\tGET_STATUS: USB_RECIP_DEVICE, g_stauts = %d\n", + g_status); + break; + + case USB_RECIP_ENDPOINT: + if (crq->wLength > 2) { + debug_cond(DEBUG_SETUP != 0, + "\tGET_STATUS:Not support EP or wLength\n"); + return 1; + } + + g_status = dev->ep[ep_num].stopped; + debug_cond(DEBUG_SETUP != 0, + "\tGET_STATUS: USB_RECIP_ENDPOINT, g_stauts = %d\n", + g_status); + + break; + + default: + return 1; + } + + memcpy(usb_ctrl, &g_status, sizeof(g_status)); + + flush_dcache_range((unsigned long) usb_ctrl, + (unsigned long) usb_ctrl + + ROUND(sizeof(g_status), CONFIG_SYS_CACHELINE_SIZE)); + + writel(phys_to_bus(usb_ctrl_dma_addr), ®->in_endp[EP0_CON].diepdma); + writel(DIEPT_SIZ_PKT_CNT(1) | DIEPT_SIZ_XFER_SIZE(2), + ®->in_endp[EP0_CON].dieptsiz); + + ep_ctrl = readl(®->in_endp[EP0_CON].diepctl); + writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK, + ®->in_endp[EP0_CON].diepctl); + dev->ep0state = WAIT_FOR_NULL_COMPLETE; + + return 0; +} + +static void dwc2_udc_set_nak(struct dwc2_ep *ep) +{ + u8 ep_num; + u32 ep_ctrl = 0; + + ep_num = ep_index(ep); + debug("%s: ep_num = %d, ep_type = %d\n", __func__, ep_num, ep->ep_type); + + if (ep_is_in(ep)) { + ep_ctrl = readl(®->in_endp[ep_num].diepctl); + ep_ctrl |= DEPCTL_SNAK; + writel(ep_ctrl, ®->in_endp[ep_num].diepctl); + debug("%s: set NAK, DIEPCTL%d = 0x%x\n", + __func__, ep_num, readl(®->in_endp[ep_num].diepctl)); + } else { + ep_ctrl = readl(®->out_endp[ep_num].doepctl); + ep_ctrl |= DEPCTL_SNAK; + writel(ep_ctrl, ®->out_endp[ep_num].doepctl); + debug("%s: set NAK, DOEPCTL%d = 0x%x\n", + __func__, ep_num, readl(®->out_endp[ep_num].doepctl)); + } + + return; +} + + +static void dwc2_udc_ep_set_stall(struct dwc2_ep *ep) +{ + u8 ep_num; + u32 ep_ctrl = 0; + + ep_num = ep_index(ep); + debug("%s: ep_num = %d, ep_type = %d\n", __func__, ep_num, ep->ep_type); + + if (ep_is_in(ep)) { + ep_ctrl = readl(®->in_endp[ep_num].diepctl); + + /* set the disable and stall bits */ + if (ep_ctrl & DEPCTL_EPENA) + ep_ctrl |= DEPCTL_EPDIS; + + ep_ctrl |= DEPCTL_STALL; + + writel(ep_ctrl, ®->in_endp[ep_num].diepctl); + debug("%s: set stall, DIEPCTL%d = 0x%x\n", + __func__, ep_num, readl(®->in_endp[ep_num].diepctl)); + + } else { + ep_ctrl = readl(®->out_endp[ep_num].doepctl); + + /* set the stall bit */ + ep_ctrl |= DEPCTL_STALL; + + writel(ep_ctrl, ®->out_endp[ep_num].doepctl); + debug("%s: set stall, DOEPCTL%d = 0x%x\n", + __func__, ep_num, readl(®->out_endp[ep_num].doepctl)); + } + + return; +} + +static void dwc2_udc_ep_clear_stall(struct dwc2_ep *ep) +{ + u8 ep_num; + u32 ep_ctrl = 0; + + ep_num = ep_index(ep); + debug("%s: ep_num = %d, ep_type = %d\n", __func__, ep_num, ep->ep_type); + + if (ep_is_in(ep)) { + ep_ctrl = readl(®->in_endp[ep_num].diepctl); + + /* clear stall bit */ + ep_ctrl &= ~DEPCTL_STALL; + + /* + * USB Spec 9.4.5: For endpoints using data toggle, regardless + * of whether an endpoint has the Halt feature set, a + * ClearFeature(ENDPOINT_HALT) request always results in the + * data toggle being reinitialized to DATA0. + */ + if (ep->bmAttributes == USB_ENDPOINT_XFER_INT + || ep->bmAttributes == USB_ENDPOINT_XFER_BULK) { + ep_ctrl |= DEPCTL_SETD0PID; /* DATA0 */ + } + + writel(ep_ctrl, ®->in_endp[ep_num].diepctl); + debug("%s: cleared stall, DIEPCTL%d = 0x%x\n", + __func__, ep_num, readl(®->in_endp[ep_num].diepctl)); + + } else { + ep_ctrl = readl(®->out_endp[ep_num].doepctl); + + /* clear stall bit */ + ep_ctrl &= ~DEPCTL_STALL; + + if (ep->bmAttributes == USB_ENDPOINT_XFER_INT + || ep->bmAttributes == USB_ENDPOINT_XFER_BULK) { + ep_ctrl |= DEPCTL_SETD0PID; /* DATA0 */ + } + + writel(ep_ctrl, ®->out_endp[ep_num].doepctl); + debug("%s: cleared stall, DOEPCTL%d = 0x%x\n", + __func__, ep_num, readl(®->out_endp[ep_num].doepctl)); + } + + return; +} + +static int dwc2_udc_set_halt(struct usb_ep *_ep, int value) +{ + struct dwc2_ep *ep; + struct dwc2_udc *dev; + unsigned long flags = 0; + u8 ep_num; + + ep = container_of(_ep, struct dwc2_ep, ep); + ep_num = ep_index(ep); + + if (unlikely(!_ep || !ep->desc || ep_num == EP0_CON || + ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC)) { + debug("%s: %s bad ep or descriptor\n", __func__, ep->ep.name); + return -EINVAL; + } + + /* Attempt to halt IN ep will fail if any transfer requests + * are still queue */ + if (value && ep_is_in(ep) && !list_empty(&ep->queue)) { + debug("%s: %s queue not empty, req = %p\n", + __func__, ep->ep.name, + list_entry(ep->queue.next, struct dwc2_request, queue)); + + return -EAGAIN; + } + + dev = ep->dev; + debug("%s: ep_num = %d, value = %d\n", __func__, ep_num, value); + + spin_lock_irqsave(&dev->lock, flags); + + if (value == 0) { + ep->stopped = 0; + dwc2_udc_ep_clear_stall(ep); + } else { + if (ep_num == 0) + dev->ep0state = WAIT_FOR_SETUP; + + ep->stopped = 1; + dwc2_udc_ep_set_stall(ep); + } + + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +static void dwc2_udc_ep_activate(struct dwc2_ep *ep) +{ + u8 ep_num; + u32 ep_ctrl = 0, daintmsk = 0; + + ep_num = ep_index(ep); + + /* Read DEPCTLn register */ + if (ep_is_in(ep)) { + ep_ctrl = readl(®->in_endp[ep_num].diepctl); + daintmsk = 1 << ep_num; + } else { + ep_ctrl = readl(®->out_endp[ep_num].doepctl); + daintmsk = (1 << ep_num) << DAINT_OUT_BIT; + } + + debug("%s: EPCTRL%d = 0x%x, ep_is_in = %d\n", + __func__, ep_num, ep_ctrl, ep_is_in(ep)); + + /* If the EP is already active don't change the EP Control + * register. */ + if (!(ep_ctrl & DEPCTL_USBACTEP)) { + ep_ctrl = (ep_ctrl & ~DEPCTL_TYPE_MASK) | + (ep->bmAttributes << DEPCTL_TYPE_BIT); + ep_ctrl = (ep_ctrl & ~DEPCTL_MPS_MASK) | + (ep->ep.maxpacket << DEPCTL_MPS_BIT); + ep_ctrl |= (DEPCTL_SETD0PID | DEPCTL_USBACTEP | DEPCTL_SNAK); + + if (ep_is_in(ep)) { + writel(ep_ctrl, ®->in_endp[ep_num].diepctl); + debug("%s: USB Ative EP%d, DIEPCTRL%d = 0x%x\n", + __func__, ep_num, ep_num, + readl(®->in_endp[ep_num].diepctl)); + } else { + writel(ep_ctrl, ®->out_endp[ep_num].doepctl); + debug("%s: USB Ative EP%d, DOEPCTRL%d = 0x%x\n", + __func__, ep_num, ep_num, + readl(®->out_endp[ep_num].doepctl)); + } + } + + /* Unmask EP Interrtupt */ + writel(readl(®->daintmsk)|daintmsk, ®->daintmsk); + debug("%s: DAINTMSK = 0x%x\n", __func__, readl(®->daintmsk)); + +} + +static int dwc2_udc_clear_feature(struct usb_ep *_ep) +{ + struct dwc2_udc *dev; + struct dwc2_ep *ep; + u8 ep_num; + + ep = container_of(_ep, struct dwc2_ep, ep); + ep_num = ep_index(ep); + + dev = ep->dev; + debug_cond(DEBUG_SETUP != 0, + "%s: ep_num = %d, is_in = %d, clear_feature_flag = %d\n", + __func__, ep_num, ep_is_in(ep), clear_feature_flag); + + if (usb_ctrl->wLength != 0) { + debug_cond(DEBUG_SETUP != 0, + "\tCLEAR_FEATURE: wLength is not zero.....\n"); + return 1; + } + + switch (usb_ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + switch (usb_ctrl->wValue) { + case USB_DEVICE_REMOTE_WAKEUP: + debug_cond(DEBUG_SETUP != 0, + "\tOFF:USB_DEVICE_REMOTE_WAKEUP\n"); + break; + + case USB_DEVICE_TEST_MODE: + debug_cond(DEBUG_SETUP != 0, + "\tCLEAR_FEATURE: USB_DEVICE_TEST_MODE\n"); + /** @todo Add CLEAR_FEATURE for TEST modes. */ + break; + } + + dwc2_udc_ep0_zlp(dev); + break; + + case USB_RECIP_ENDPOINT: + debug_cond(DEBUG_SETUP != 0, + "\tCLEAR_FEATURE:USB_RECIP_ENDPOINT, wValue = %d\n", + usb_ctrl->wValue); + + if (usb_ctrl->wValue == USB_ENDPOINT_HALT) { + if (ep_num == 0) { + dwc2_udc_ep0_set_stall(ep); + return 0; + } + + dwc2_udc_ep0_zlp(dev); + + dwc2_udc_ep_clear_stall(ep); + dwc2_udc_ep_activate(ep); + ep->stopped = 0; + + clear_feature_num = ep_num; + clear_feature_flag = 1; + } + break; + } + + return 0; +} + +static int dwc2_udc_set_feature(struct usb_ep *_ep) +{ + struct dwc2_udc *dev; + struct dwc2_ep *ep; + u8 ep_num; + + ep = container_of(_ep, struct dwc2_ep, ep); + ep_num = ep_index(ep); + dev = ep->dev; + + debug_cond(DEBUG_SETUP != 0, + "%s: *** USB_REQ_SET_FEATURE , ep_num = %d\n", + __func__, ep_num); + + if (usb_ctrl->wLength != 0) { + debug_cond(DEBUG_SETUP != 0, + "\tSET_FEATURE: wLength is not zero.....\n"); + return 1; + } + + switch (usb_ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + switch (usb_ctrl->wValue) { + case USB_DEVICE_REMOTE_WAKEUP: + debug_cond(DEBUG_SETUP != 0, + "\tSET_FEATURE:USB_DEVICE_REMOTE_WAKEUP\n"); + break; + case USB_DEVICE_B_HNP_ENABLE: + debug_cond(DEBUG_SETUP != 0, + "\tSET_FEATURE: USB_DEVICE_B_HNP_ENABLE\n"); + break; + + case USB_DEVICE_A_HNP_SUPPORT: + /* RH port supports HNP */ + debug_cond(DEBUG_SETUP != 0, + "\tSET_FEATURE:USB_DEVICE_A_HNP_SUPPORT\n"); + break; + + case USB_DEVICE_A_ALT_HNP_SUPPORT: + /* other RH port does */ + debug_cond(DEBUG_SETUP != 0, + "\tSET: USB_DEVICE_A_ALT_HNP_SUPPORT\n"); + break; + } + + dwc2_udc_ep0_zlp(dev); + return 0; + + case USB_RECIP_INTERFACE: + debug_cond(DEBUG_SETUP != 0, + "\tSET_FEATURE: USB_RECIP_INTERFACE\n"); + break; + + case USB_RECIP_ENDPOINT: + debug_cond(DEBUG_SETUP != 0, + "\tSET_FEATURE: USB_RECIP_ENDPOINT\n"); + if (usb_ctrl->wValue == USB_ENDPOINT_HALT) { + if (ep_num == 0) { + dwc2_udc_ep0_set_stall(ep); + return 0; + } + ep->stopped = 1; + dwc2_udc_ep_set_stall(ep); + } + + dwc2_udc_ep0_zlp(dev); + return 0; + } + + return 1; +} + +/* + * WAIT_FOR_SETUP (OUT_PKT_RDY) + */ +static void dwc2_ep0_setup(struct dwc2_udc *dev) +{ + struct dwc2_ep *ep = &dev->ep[0]; + int i; + u8 ep_num; + + /* Nuke all previous transfers */ + nuke(ep, -EPROTO); + + /* read control req from fifo (8 bytes) */ + dwc2_fifo_read(ep, usb_ctrl, 8); + + debug_cond(DEBUG_SETUP != 0, + "%s: bRequestType = 0x%x(%s), bRequest = 0x%x" + "\twLength = 0x%x, wValue = 0x%x, wIndex= 0x%x\n", + __func__, usb_ctrl->bRequestType, + (usb_ctrl->bRequestType & USB_DIR_IN) ? "IN" : "OUT", + usb_ctrl->bRequest, + usb_ctrl->wLength, usb_ctrl->wValue, usb_ctrl->wIndex); + +#ifdef DEBUG + { + int i, len = sizeof(*usb_ctrl); + char *p = (char *)usb_ctrl; + + printf("pkt = "); + for (i = 0; i < len; i++) { + printf("%02x", ((u8 *)p)[i]); + if ((i & 7) == 7) + printf(" "); + } + printf("\n"); + } +#endif + + if (usb_ctrl->bRequest == GET_MAX_LUN_REQUEST && + usb_ctrl->wLength != 1) { + debug_cond(DEBUG_SETUP != 0, + "\t%s:GET_MAX_LUN_REQUEST:invalid", + __func__); + debug_cond(DEBUG_SETUP != 0, + "wLength = %d, setup returned\n", + usb_ctrl->wLength); + + dwc2_udc_ep0_set_stall(ep); + dev->ep0state = WAIT_FOR_SETUP; + + return; + } else if (usb_ctrl->bRequest == BOT_RESET_REQUEST && + usb_ctrl->wLength != 0) { + /* Bulk-Only *mass storge reset of class-specific request */ + debug_cond(DEBUG_SETUP != 0, + "%s:BOT Rest:invalid wLength =%d, setup returned\n", + __func__, usb_ctrl->wLength); + + dwc2_udc_ep0_set_stall(ep); + dev->ep0state = WAIT_FOR_SETUP; + + return; + } + + /* Set direction of EP0 */ + if (likely(usb_ctrl->bRequestType & USB_DIR_IN)) { + ep->bEndpointAddress |= USB_DIR_IN; + } else { + ep->bEndpointAddress &= ~USB_DIR_IN; + } + /* cope with automagic for some standard requests. */ + dev->req_std = (usb_ctrl->bRequestType & USB_TYPE_MASK) + == USB_TYPE_STANDARD; + + dev->req_pending = 1; + + /* Handle some SETUP packets ourselves */ + if (dev->req_std) { + switch (usb_ctrl->bRequest) { + case USB_REQ_SET_ADDRESS: + debug_cond(DEBUG_SETUP != 0, + "%s: *** USB_REQ_SET_ADDRESS (%d)\n", + __func__, usb_ctrl->wValue); + if (usb_ctrl->bRequestType + != (USB_TYPE_STANDARD | USB_RECIP_DEVICE)) + break; + + udc_set_address(dev, usb_ctrl->wValue); + return; + + case USB_REQ_SET_CONFIGURATION: + debug_cond(DEBUG_SETUP != 0, + "=====================================\n"); + debug_cond(DEBUG_SETUP != 0, + "%s: USB_REQ_SET_CONFIGURATION (%d)\n", + __func__, usb_ctrl->wValue); + + if (usb_ctrl->bRequestType == USB_RECIP_DEVICE) + reset_available = 1; + + break; + + case USB_REQ_GET_DESCRIPTOR: + debug_cond(DEBUG_SETUP != 0, + "%s: *** USB_REQ_GET_DESCRIPTOR\n", + __func__); + break; + + case USB_REQ_SET_INTERFACE: + debug_cond(DEBUG_SETUP != 0, + "%s: *** USB_REQ_SET_INTERFACE (%d)\n", + __func__, usb_ctrl->wValue); + + if (usb_ctrl->bRequestType == USB_RECIP_INTERFACE) + reset_available = 1; + + break; + + case USB_REQ_GET_CONFIGURATION: + debug_cond(DEBUG_SETUP != 0, + "%s: *** USB_REQ_GET_CONFIGURATION\n", + __func__); + break; + + case USB_REQ_GET_STATUS: + if (!dwc2_udc_get_status(dev, usb_ctrl)) + return; + + break; + + case USB_REQ_CLEAR_FEATURE: + ep_num = usb_ctrl->wIndex & 0x7f; + + if (!dwc2_udc_clear_feature(&dev->ep[ep_num].ep)) + return; + + break; + + case USB_REQ_SET_FEATURE: + ep_num = usb_ctrl->wIndex & 0x7f; + + if (!dwc2_udc_set_feature(&dev->ep[ep_num].ep)) + return; + + break; + + default: + debug_cond(DEBUG_SETUP != 0, + "%s: *** Default of usb_ctrl->bRequest=0x%x" + "happened.\n", __func__, usb_ctrl->bRequest); + break; + } + } + + + if (likely(dev->driver)) { + /* device-2-host (IN) or no data setup command, + * process immediately */ + debug_cond(DEBUG_SETUP != 0, + "%s:usb_ctrlreq will be passed to fsg_setup()\n", + __func__); + + spin_unlock(&dev->lock); + i = dev->driver->setup(&dev->gadget, usb_ctrl); + spin_lock(&dev->lock); + + if (i < 0) { + /* setup processing failed, force stall */ + dwc2_udc_ep0_set_stall(ep); + dev->ep0state = WAIT_FOR_SETUP; + + debug_cond(DEBUG_SETUP != 0, + "\tdev->driver->setup failed (%d)," + " bRequest = %d\n", + i, usb_ctrl->bRequest); + + + } else if (dev->req_pending) { + dev->req_pending = 0; + debug_cond(DEBUG_SETUP != 0, + "\tdev->req_pending...\n"); + } + + debug_cond(DEBUG_SETUP != 0, + "\tep0state = %s\n", state_names[dev->ep0state]); + + } +} + +/* + * handle ep0 interrupt + */ +static void dwc2_handle_ep0(struct dwc2_udc *dev) +{ + if (dev->ep0state == WAIT_FOR_SETUP) { + debug_cond(DEBUG_OUT_EP != 0, + "%s: WAIT_FOR_SETUP\n", __func__); + dwc2_ep0_setup(dev); + + } else { + debug_cond(DEBUG_OUT_EP != 0, + "%s: strange state!!(state = %s)\n", + __func__, state_names[dev->ep0state]); + } +} + +static void dwc2_ep0_kick(struct dwc2_udc *dev, struct dwc2_ep *ep) +{ + debug_cond(DEBUG_EP0 != 0, + "%s: ep_is_in = %d\n", __func__, ep_is_in(ep)); + if (ep_is_in(ep)) { + dev->ep0state = DATA_STATE_XMIT; + dwc2_ep0_write(dev); + + } else { + dev->ep0state = DATA_STATE_RECV; + dwc2_ep0_read(dev); + } +} diff --git a/roms/u-boot/drivers/usb/gadget/ep0.c b/roms/u-boot/drivers/usb/gadget/ep0.c new file mode 100644 index 000000000..6624f61b7 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/ep0.c @@ -0,0 +1,643 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2003 + * Gerry Hamel, geh@ti.com, Texas Instruments + * + * (C) Copyright 2006 + * Bryan O'Donoghue, deckard@CodeHermit.ie + * + * Based on + * linux/drivers/usbd/ep0.c + * + * Copyright (c) 2000, 2001, 2002 Lineo + * Copyright (c) 2001 Hewlett Packard + * + * By: + * Stuart Lynne <sl@lineo.com>, + * Tom Rushworth <tbr@lineo.com>, + * Bruce Balden <balden@lineo.com> + */ + +/* + * This is the builtin ep0 control function. It implements all required functionality + * for responding to control requests (SETUP packets). + * + * XXX + * + * Currently we do not pass any SETUP packets (or other) to the configured + * function driver. This may need to change. + * + * XXX + * + * As alluded to above, a simple callback cdc_recv_setup has been implemented + * in the usb_device data structure to facilicate passing + * Common Device Class packets to a function driver. + * + * XXX + */ + +#include <common.h> +#include <serial.h> +#include <usbdevice.h> + +#if 0 +#define dbg_ep0(lvl,fmt,args...) serial_printf("[%s] %s:%d: "fmt"\n",__FILE__,__FUNCTION__,__LINE__,##args) +#else +#define dbg_ep0(lvl,fmt,args...) +#endif + +__maybe_unused static char *usbd_device_descriptors[] = { + "UNKNOWN", /* 0 */ + "DEVICE", /* 1 */ + "CONFIG", /* 2 */ + "STRING", /* 3 */ + "INTERFACE", /* 4 */ + "ENDPOINT", /* 5 */ + "DEVICE QUALIFIER", /* 6 */ + "OTHER SPEED", /* 7 */ + "INTERFACE POWER", /* 8 */ +}; + +#define USBD_DEVICE_DESCRIPTORS(x) (((unsigned int)x <= USB_DESCRIPTOR_TYPE_INTERFACE_POWER) ? \ + usbd_device_descriptors[x] : "UNKNOWN") + +__maybe_unused static char *usbd_device_states[] = { + "STATE_INIT", + "STATE_CREATED", + "STATE_ATTACHED", + "STATE_POWERED", + "STATE_DEFAULT", + "STATE_ADDRESSED", + "STATE_CONFIGURED", + "STATE_UNKNOWN", +}; + +#define USBD_DEVICE_STATE(x) (((unsigned int)x <= STATE_UNKNOWN) ? usbd_device_states[x] : "UNKNOWN") + +__maybe_unused static char *usbd_device_requests[] = { + "GET STATUS", /* 0 */ + "CLEAR FEATURE", /* 1 */ + "RESERVED", /* 2 */ + "SET FEATURE", /* 3 */ + "RESERVED", /* 4 */ + "SET ADDRESS", /* 5 */ + "GET DESCRIPTOR", /* 6 */ + "SET DESCRIPTOR", /* 7 */ + "GET CONFIGURATION", /* 8 */ + "SET CONFIGURATION", /* 9 */ + "GET INTERFACE", /* 10 */ + "SET INTERFACE", /* 11 */ + "SYNC FRAME", /* 12 */ +}; + +#define USBD_DEVICE_REQUESTS(x) (((unsigned int)x <= USB_REQ_SYNCH_FRAME) ? usbd_device_requests[x] : "UNKNOWN") + +/* EP0 Configuration Set ********************************************************************* */ + + +/** + * ep0_get_status - fill in URB data with appropriate status + * @device: + * @urb: + * @index: + * @requesttype: + * + */ +static int ep0_get_status (struct usb_device_instance *device, + struct urb *urb, int index, int requesttype) +{ + char *cp; + + urb->actual_length = 2; + cp = (char*)urb->buffer; + cp[0] = cp[1] = 0; + + switch (requesttype) { + case USB_REQ_RECIPIENT_DEVICE: + cp[0] = USB_STATUS_SELFPOWERED; + break; + case USB_REQ_RECIPIENT_INTERFACE: + break; + case USB_REQ_RECIPIENT_ENDPOINT: + cp[0] = usbd_endpoint_halted (device, index); + break; + case USB_REQ_RECIPIENT_OTHER: + urb->actual_length = 0; + default: + break; + } + dbg_ep0 (2, "%02x %02x", cp[0], cp[1]); + return 0; +} + +/** + * ep0_get_one + * @device: + * @urb: + * @result: + * + * Set a single byte value in the urb send buffer. Return non-zero to signal + * a request error. + */ +static int ep0_get_one (struct usb_device_instance *device, struct urb *urb, + __u8 result) +{ + urb->actual_length = 1; /* XXX 2? */ + ((char *) urb->buffer)[0] = result; + return 0; +} + +/** + * copy_config + * @urb: pointer to urb + * @data: pointer to configuration data + * @length: length of data + * + * Copy configuration data to urb transfer buffer if there is room for it. + */ +void copy_config (struct urb *urb, void *data, int max_length, + int max_buf) +{ + int available; + int length; + + /*dbg_ep0(3, "-> actual: %d buf: %d max_buf: %d max_length: %d data: %p", */ + /* urb->actual_length, urb->buffer_length, max_buf, max_length, data); */ + + if (!data) { + dbg_ep0 (1, "data is NULL"); + return; + } + length = max_length; + + if (length > max_length) { + dbg_ep0 (1, "length: %d >= max_length: %d", length, + max_length); + return; + } + /*dbg_ep0(1, " actual: %d buf: %d max_buf: %d max_length: %d length: %d", */ + /* urb->actual_length, urb->buffer_length, max_buf, max_length, length); */ + + if ((available = + /*urb->buffer_length */ max_buf - urb->actual_length) <= 0) { + return; + } + /*dbg_ep0(1, "actual: %d buf: %d max_buf: %d length: %d available: %d", */ + /* urb->actual_length, urb->buffer_length, max_buf, length, available); */ + + if (length > available) { + length = available; + } + /*dbg_ep0(1, "actual: %d buf: %d max_buf: %d length: %d available: %d", */ + /* urb->actual_length, urb->buffer_length, max_buf, length, available); */ + + memcpy (urb->buffer + urb->actual_length, data, length); + urb->actual_length += length; + + dbg_ep0 (3, + "copy_config: <- actual: %d buf: %d max_buf: %d max_length: %d available: %d", + urb->actual_length, urb->buffer_length, max_buf, max_length, + available); +} + +/** + * ep0_get_descriptor + * @device: + * @urb: + * @max: + * @descriptor_type: + * @index: + * + * Called by ep0_rx_process for a get descriptor device command. Determine what + * descriptor is being requested, copy to send buffer. Return zero if ok to send, + * return non-zero to signal a request error. + */ +static int ep0_get_descriptor (struct usb_device_instance *device, + struct urb *urb, int max, int descriptor_type, + int index) +{ + int port = 0; /* XXX compound device */ + + /*dbg_ep0(3, "max: %x type: %x index: %x", max, descriptor_type, index); */ + + if (!urb || !urb->buffer || !urb->buffer_length + || (urb->buffer_length < 255)) { + dbg_ep0 (2, "invalid urb %p", urb); + return -1L; + } + + /* setup tx urb */ + urb->actual_length = 0; + + dbg_ep0 (2, "%s", USBD_DEVICE_DESCRIPTORS (descriptor_type)); + + switch (descriptor_type) { + case USB_DESCRIPTOR_TYPE_DEVICE: + { + struct usb_device_descriptor *device_descriptor; + if (! + (device_descriptor = + usbd_device_device_descriptor (device, port))) { + return -1; + } + /* copy descriptor for this device */ + copy_config (urb, device_descriptor, + sizeof (struct usb_device_descriptor), + max); + + /* correct the correct control endpoint 0 max packet size into the descriptor */ + device_descriptor = + (struct usb_device_descriptor *) urb->buffer; + + } + dbg_ep0(3, "copied device configuration, actual_length: 0x%x", urb->actual_length); + break; + + case USB_DESCRIPTOR_TYPE_CONFIGURATION: + { + struct usb_configuration_descriptor + *configuration_descriptor; + struct usb_device_descriptor *device_descriptor; + if (! + (device_descriptor = + usbd_device_device_descriptor (device, port))) { + return -1; + } + /*dbg_ep0(2, "%d %d", index, device_descriptor->bNumConfigurations); */ + if (index >= device_descriptor->bNumConfigurations) { + dbg_ep0 (0, "index too large: %d >= %d", index, + device_descriptor-> + bNumConfigurations); + return -1; + } + + if (! + (configuration_descriptor = + usbd_device_configuration_descriptor (device, + port, + index))) { + dbg_ep0 (0, + "usbd_device_configuration_descriptor failed: %d", + index); + return -1; + } + dbg_ep0(0, "attempt to copy %d bytes to urb\n",cpu_to_le16(configuration_descriptor->wTotalLength)); + copy_config (urb, configuration_descriptor, + + cpu_to_le16(configuration_descriptor->wTotalLength), + max); + } + + break; + + case USB_DESCRIPTOR_TYPE_STRING: + { + struct usb_string_descriptor *string_descriptor; + if (!(string_descriptor = usbd_get_string (index))) { + dbg_ep0(0, "Invalid string index %d\n", index); + return -1; + } + dbg_ep0(3, "string_descriptor: %p length %d", string_descriptor, string_descriptor->bLength); + copy_config (urb, string_descriptor, string_descriptor->bLength, max); + } + break; + case USB_DESCRIPTOR_TYPE_INTERFACE: + dbg_ep0(2, "USB_DESCRIPTOR_TYPE_INTERFACE - error not implemented\n"); + return -1; + case USB_DESCRIPTOR_TYPE_ENDPOINT: + dbg_ep0(2, "USB_DESCRIPTOR_TYPE_ENDPOINT - error not implemented\n"); + return -1; + case USB_DESCRIPTOR_TYPE_HID: + { + dbg_ep0(2, "USB_DESCRIPTOR_TYPE_HID - error not implemented\n"); + return -1; /* unsupported at this time */ +#if 0 + int bNumInterface = + le16_to_cpu (urb->device_request.wIndex); + int bAlternateSetting = 0; + int class = 0; + struct usb_class_descriptor *class_descriptor; + + if (!(class_descriptor = + usbd_device_class_descriptor_index (device, + port, 0, + bNumInterface, + bAlternateSetting, + class)) + || class_descriptor->descriptor.hid.bDescriptorType != USB_DT_HID) { + dbg_ep0 (3, "[%d] interface is not HID", + bNumInterface); + return -1; + } + /* copy descriptor for this class */ + copy_config (urb, class_descriptor, + class_descriptor->descriptor.hid.bLength, + max); +#endif + } + break; + case USB_DESCRIPTOR_TYPE_REPORT: + { + dbg_ep0(2, "USB_DESCRIPTOR_TYPE_REPORT - error not implemented\n"); + return -1; /* unsupported at this time */ +#if 0 + int bNumInterface = + le16_to_cpu (urb->device_request.wIndex); + int bAlternateSetting = 0; + int class = 0; + struct usb_class_report_descriptor *report_descriptor; + + if (!(report_descriptor = + usbd_device_class_report_descriptor_index + (device, port, 0, bNumInterface, + bAlternateSetting, class)) + || report_descriptor->bDescriptorType != + USB_DT_REPORT) { + dbg_ep0 (3, "[%d] descriptor is not REPORT", + bNumInterface); + return -1; + } + /* copy report descriptor for this class */ + /*copy_config(urb, &report_descriptor->bData[0], report_descriptor->wLength, max); */ + if (max - urb->actual_length > 0) { + int length = + min(report_descriptor->wLength, + max - urb->actual_length); + memcpy (urb->buffer + urb->actual_length, + &report_descriptor->bData[0], length); + urb->actual_length += length; + } +#endif + } + break; + case USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER: +#if defined(CONFIG_USBD_HS) + { + struct usb_qualifier_descriptor *qualifier_descriptor = + device->qualifier_descriptor; + + if (!qualifier_descriptor) + return -1; + + /* copy descriptor for this device */ + copy_config(urb, qualifier_descriptor, + sizeof(struct usb_qualifier_descriptor), + max); + + } + dbg_ep0(3, "copied qualifier descriptor, actual_length: 0x%x", + urb->actual_length); +#else + return -1; +#endif + break; + + default: + return -1; + } + + + dbg_ep0 (1, "urb: buffer: %p buffer_length: %2d actual_length: %2d tx_packetSize: %2d", + urb->buffer, urb->buffer_length, urb->actual_length, + device->bus->endpoint_array[0].tx_packetSize); +/* + if ((urb->actual_length < max) && !(urb->actual_length % device->bus->endpoint_array[0].tx_packetSize)) { + dbg_ep0(0, "adding null byte"); + urb->buffer[urb->actual_length++] = 0; + dbg_ep0(0, "urb: buffer_length: %2d actual_length: %2d packet size: %2d", + urb->buffer_length, urb->actual_length device->bus->endpoint_array[0].tx_packetSize); + } +*/ + return 0; + +} + +/** + * ep0_recv_setup - called to indicate URB has been received + * @urb: pointer to struct urb + * + * Check if this is a setup packet, process the device request, put results + * back into the urb and return zero or non-zero to indicate success (DATA) + * or failure (STALL). + * + */ +int ep0_recv_setup (struct urb *urb) +{ + /*struct usb_device_request *request = urb->buffer; */ + /*struct usb_device_instance *device = urb->device; */ + + struct usb_device_request *request; + struct usb_device_instance *device; + int address; + + dbg_ep0 (0, "entering ep0_recv_setup()"); + if (!urb || !urb->device) { + dbg_ep0 (3, "invalid URB %p", urb); + return -1; + } + + request = &urb->device_request; + device = urb->device; + + dbg_ep0 (3, "urb: %p device: %p", urb, urb->device); + + + /*dbg_ep0(2, "- - - - - - - - - -"); */ + + dbg_ep0 (2, + "bmRequestType:%02x bRequest:%02x wValue:%04x wIndex:%04x wLength:%04x %s", + request->bmRequestType, request->bRequest, + le16_to_cpu (request->wValue), le16_to_cpu (request->wIndex), + le16_to_cpu (request->wLength), + USBD_DEVICE_REQUESTS (request->bRequest)); + + /* handle USB Standard Request (c.f. USB Spec table 9-2) */ + if ((request->bmRequestType & USB_REQ_TYPE_MASK) != 0) { + if(device->device_state <= STATE_CONFIGURED){ + /* Attempt to handle a CDC specific request if we are + * in the configured state. + */ + return device->cdc_recv_setup(request,urb); + } + dbg_ep0 (1, "non standard request: %x", + request->bmRequestType & USB_REQ_TYPE_MASK); + return -1; /* Stall here */ + } + + switch (device->device_state) { + case STATE_CREATED: + case STATE_ATTACHED: + case STATE_POWERED: + /* It actually is important to allow requests in these states, + * Windows will request descriptors before assigning an + * address to the client. + */ + + /*dbg_ep0 (1, "request %s not allowed in this state: %s", */ + /* USBD_DEVICE_REQUESTS(request->bRequest), */ + /* usbd_device_states[device->device_state]); */ + /*return -1; */ + break; + + case STATE_INIT: + case STATE_DEFAULT: + switch (request->bRequest) { + case USB_REQ_GET_STATUS: + case USB_REQ_GET_INTERFACE: + case USB_REQ_SYNCH_FRAME: /* XXX should never see this (?) */ + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + case USB_REQ_SET_DESCRIPTOR: + /* case USB_REQ_SET_CONFIGURATION: */ + case USB_REQ_SET_INTERFACE: + dbg_ep0 (1, + "request %s not allowed in DEFAULT state: %s", + USBD_DEVICE_REQUESTS (request->bRequest), + usbd_device_states[device->device_state]); + return -1; + + case USB_REQ_SET_CONFIGURATION: + case USB_REQ_SET_ADDRESS: + case USB_REQ_GET_DESCRIPTOR: + case USB_REQ_GET_CONFIGURATION: + break; + } + case STATE_ADDRESSED: + case STATE_CONFIGURED: + break; + case STATE_UNKNOWN: + dbg_ep0 (1, "request %s not allowed in UNKNOWN state: %s", + USBD_DEVICE_REQUESTS (request->bRequest), + usbd_device_states[device->device_state]); + return -1; + } + + /* handle all requests that return data (direction bit set on bm RequestType) */ + if ((request->bmRequestType & USB_REQ_DIRECTION_MASK)) { + + dbg_ep0 (3, "Device-to-Host"); + + switch (request->bRequest) { + + case USB_REQ_GET_STATUS: + return ep0_get_status (device, urb, request->wIndex, + request->bmRequestType & + USB_REQ_RECIPIENT_MASK); + + case USB_REQ_GET_DESCRIPTOR: + return ep0_get_descriptor (device, urb, + le16_to_cpu (request->wLength), + le16_to_cpu (request->wValue) >> 8, + le16_to_cpu (request->wValue) & 0xff); + + case USB_REQ_GET_CONFIGURATION: + dbg_ep0(2, "get config %d\n", device->configuration); + return ep0_get_one (device, urb, + device->configuration); + + case USB_REQ_GET_INTERFACE: + return ep0_get_one (device, urb, device->alternate); + + case USB_REQ_SYNCH_FRAME: /* XXX should never see this (?) */ + return -1; + + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + case USB_REQ_SET_ADDRESS: + case USB_REQ_SET_DESCRIPTOR: + case USB_REQ_SET_CONFIGURATION: + case USB_REQ_SET_INTERFACE: + return -1; + } + } + /* handle the requests that do not return data */ + else { + + + /*dbg_ep0(3, "Host-to-Device"); */ + switch (request->bRequest) { + + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + dbg_ep0 (0, "Host-to-Device"); + switch (request-> + bmRequestType & USB_REQ_RECIPIENT_MASK) { + case USB_REQ_RECIPIENT_DEVICE: + /* XXX DEVICE_REMOTE_WAKEUP or TEST_MODE would be added here */ + /* XXX fall through for now as we do not support either */ + case USB_REQ_RECIPIENT_INTERFACE: + case USB_REQ_RECIPIENT_OTHER: + dbg_ep0 (0, "request %s not", + USBD_DEVICE_REQUESTS (request->bRequest)); + default: + return -1; + + case USB_REQ_RECIPIENT_ENDPOINT: + dbg_ep0 (0, "ENDPOINT: %x", le16_to_cpu (request->wValue)); + if (le16_to_cpu (request->wValue) == USB_ENDPOINT_HALT) { + /*return usbd_device_feature (device, le16_to_cpu (request->wIndex), */ + /* request->bRequest == USB_REQ_SET_FEATURE); */ + /* NEED TO IMPLEMENT THIS!!! */ + return -1; + } else { + dbg_ep0 (1, "request %s bad wValue: %04x", + USBD_DEVICE_REQUESTS + (request->bRequest), + le16_to_cpu (request->wValue)); + return -1; + } + } + + case USB_REQ_SET_ADDRESS: + /* check if this is a re-address, reset first if it is (this shouldn't be possible) */ + if (device->device_state != STATE_DEFAULT) { + dbg_ep0 (1, "set_address: %02x state: %s", + le16_to_cpu (request->wValue), + usbd_device_states[device->device_state]); + return -1; + } + address = le16_to_cpu (request->wValue); + if ((address & 0x7f) != address) { + dbg_ep0 (1, "invalid address %04x %04x", + address, address & 0x7f); + return -1; + } + device->address = address; + + /*dbg_ep0(2, "address: %d %d %d", */ + /* request->wValue, le16_to_cpu(request->wValue), device->address); */ + + return 0; + + case USB_REQ_SET_DESCRIPTOR: /* XXX should we support this? */ + dbg_ep0 (0, "set descriptor: NOT SUPPORTED"); + return -1; + + case USB_REQ_SET_CONFIGURATION: + /* c.f. 9.4.7 - the top half of wValue is reserved */ + device->configuration = le16_to_cpu(request->wValue) & 0xff; + + /* reset interface and alternate settings */ + device->interface = device->alternate = 0; + + /*dbg_ep0(2, "set configuration: %d", device->configuration); */ + /*dbg_ep0(2, "DEVICE_CONFIGURED.. event?\n"); */ + return 0; + + case USB_REQ_SET_INTERFACE: + device->interface = le16_to_cpu (request->wIndex); + device->alternate = le16_to_cpu (request->wValue); + /*dbg_ep0(2, "set interface: %d alternate: %d", device->interface, device->alternate); */ + dbg_ep0(2, "DEVICE_SET_INTERFACE.. event?\n"); + return 0; + + case USB_REQ_GET_STATUS: + case USB_REQ_GET_DESCRIPTOR: + case USB_REQ_GET_CONFIGURATION: + case USB_REQ_GET_INTERFACE: + case USB_REQ_SYNCH_FRAME: /* XXX should never see this (?) */ + return -1; + } + } + return -1; +} diff --git a/roms/u-boot/drivers/usb/gadget/ep0.h b/roms/u-boot/drivers/usb/gadget/ep0.h new file mode 100644 index 000000000..976825ab9 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/ep0.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * (C) Copyright 2003 + * Gerry Hamel, geh@ti.com, Texas Instruments + * + * Based on + * linux/drivers/usbd/ep0.c + * + * Copyright (c) 2000, 2001, 2002 Lineo + * Copyright (c) 2001 Hewlett Packard + * + * By: + * Stuart Lynne <sl@lineo.com>, + * Tom Rushworth <tbr@lineo.com>, + * Bruce Balden <balden@lineo.com> + */ + +#ifndef __USBDCORE_EP0_H__ +#define __USBDCORE_EP0_H__ + + +int ep0_recv_setup (struct urb *urb); + + +#endif diff --git a/roms/u-boot/drivers/usb/gadget/epautoconf.c b/roms/u-boot/drivers/usb/gadget/epautoconf.c new file mode 100644 index 000000000..7da334f5d --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/epautoconf.c @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * epautoconf.c -- endpoint autoconfiguration for usb gadget drivers + * + * Copyright (C) 2004 David Brownell + * + * Ported to U-Boot by: Thomas Smits <ts.smits@gmail.com> and + * Remy Bohmer <linux@bohmer.net> + */ + +#include <common.h> +#include <linux/usb/ch9.h> +#include <linux/errno.h> +#include <linux/usb/gadget.h> +#include <asm/unaligned.h> +#include "gadget_chips.h" + +#define isdigit(c) ('0' <= (c) && (c) <= '9') + +/* we must assign addresses for configurable endpoints (like net2280) */ +static unsigned epnum; + +/* #define MANY_ENDPOINTS */ +#ifdef MANY_ENDPOINTS +/* more than 15 configurable endpoints */ +static unsigned in_epnum; +#endif + + +/* + * This should work with endpoints from controller drivers sharing the + * same endpoint naming convention. By example: + * + * - ep1, ep2, ... address is fixed, not direction or type + * - ep1in, ep2out, ... address and direction are fixed, not type + * - ep1-bulk, ep2-bulk, ... address and type are fixed, not direction + * - ep1in-bulk, ep2out-iso, ... all three are fixed + * - ep-* ... no functionality restrictions + * + * Type suffixes are "-bulk", "-iso", or "-int". Numbers are decimal. + * Less common restrictions are implied by gadget_is_*(). + * + * NOTE: each endpoint is unidirectional, as specified by its USB + * descriptor; and isn't specific to a configuration or altsetting. + */ +static int ep_matches( + struct usb_gadget *gadget, + struct usb_ep *ep, + struct usb_endpoint_descriptor *desc +) +{ + u8 type; + const char *tmp; + u16 max; + + /* endpoint already claimed? */ + if (NULL != ep->driver_data) + return 0; + + /* only support ep0 for portable CONTROL traffic */ + type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + if (USB_ENDPOINT_XFER_CONTROL == type) + return 0; + + /* some other naming convention */ + if ('e' != ep->name[0]) + return 0; + + /* type-restriction: "-iso", "-bulk", or "-int". + * direction-restriction: "in", "out". + */ + if ('-' != ep->name[2]) { + tmp = strrchr(ep->name, '-'); + if (tmp) { + switch (type) { + case USB_ENDPOINT_XFER_INT: + /* bulk endpoints handle interrupt transfers, + * except the toggle-quirky iso-synch kind + */ + if ('s' == tmp[2]) /* == "-iso" */ + return 0; + /* for now, avoid PXA "interrupt-in"; + * it's documented as never using DATA1. + */ + if (gadget_is_pxa(gadget) + && 'i' == tmp[1]) + return 0; + break; + case USB_ENDPOINT_XFER_BULK: + if ('b' != tmp[1]) /* != "-bulk" */ + return 0; + break; + case USB_ENDPOINT_XFER_ISOC: + if ('s' != tmp[2]) /* != "-iso" */ + return 0; + } + } else { + tmp = ep->name + strlen(ep->name); + } + + /* direction-restriction: "..in-..", "out-.." */ + tmp--; + if (!isdigit(*tmp)) { + if (desc->bEndpointAddress & USB_DIR_IN) { + if ('n' != *tmp) + return 0; + } else { + if ('t' != *tmp) + return 0; + } + } + } + + /* endpoint maxpacket size is an input parameter, except for bulk + * where it's an output parameter representing the full speed limit. + * the usb spec fixes high speed bulk maxpacket at 512 bytes. + */ + max = 0x7ff & le16_to_cpu(get_unaligned(&desc->wMaxPacketSize)); + switch (type) { + case USB_ENDPOINT_XFER_INT: + /* INT: limit 64 bytes full speed, 1024 high speed */ + if (!gadget->is_dualspeed && max > 64) + return 0; + /* FALLTHROUGH */ + + case USB_ENDPOINT_XFER_ISOC: + /* ISO: limit 1023 bytes full speed, 1024 high speed */ + if (ep->maxpacket < max) + return 0; + if (!gadget->is_dualspeed && max > 1023) + return 0; + + /* BOTH: "high bandwidth" works only at high speed */ + if ((get_unaligned(&desc->wMaxPacketSize) & + __constant_cpu_to_le16(3<<11))) { + if (!gadget->is_dualspeed) + return 0; + /* configure your hardware with enough buffering!! */ + } + break; + } + + /* MATCH!! */ + + /* report address */ + if (isdigit(ep->name[2])) { + u8 num = simple_strtoul(&ep->name[2], NULL, 10); + desc->bEndpointAddress |= num; +#ifdef MANY_ENDPOINTS + } else if (desc->bEndpointAddress & USB_DIR_IN) { + if (++in_epnum > 15) + return 0; + desc->bEndpointAddress = USB_DIR_IN | in_epnum; +#endif + } else { + if (++epnum > 15) + return 0; + desc->bEndpointAddress |= epnum; + } + + /* report (variable) full speed bulk maxpacket */ + if (USB_ENDPOINT_XFER_BULK == type) { + int size = ep->maxpacket; + + /* min() doesn't work on bitfields with gcc-3.5 */ + if (size > 64) + size = 64; + put_unaligned(cpu_to_le16(size), &desc->wMaxPacketSize); + } + + if (gadget->ops->ep_conf) + return gadget->ops->ep_conf(gadget, ep, desc); + + return 1; +} + +static struct usb_ep * +find_ep(struct usb_gadget *gadget, const char *name) +{ + struct usb_ep *ep; + + list_for_each_entry(ep, &gadget->ep_list, ep_list) { + if (0 == strcmp(ep->name, name)) + return ep; + } + return NULL; +} + +/** + * usb_ep_autoconfig - choose an endpoint matching the descriptor + * @gadget: The device to which the endpoint must belong. + * @desc: Endpoint descriptor, with endpoint direction and transfer mode + * initialized. For periodic transfers, the maximum packet + * size must also be initialized. This is modified on success. + * + * By choosing an endpoint to use with the specified descriptor, this + * routine simplifies writing gadget drivers that work with multiple + * USB device controllers. The endpoint would be passed later to + * usb_ep_enable(), along with some descriptor. + * + * That second descriptor won't always be the same as the first one. + * For example, isochronous endpoints can be autoconfigured for high + * bandwidth, and then used in several lower bandwidth altsettings. + * Also, high and full speed descriptors will be different. + * + * Be sure to examine and test the results of autoconfiguration on your + * hardware. This code may not make the best choices about how to use the + * USB controller, and it can't know all the restrictions that may apply. + * Some combinations of driver and hardware won't be able to autoconfigure. + * + * On success, this returns an un-claimed usb_ep, and modifies the endpoint + * descriptor bEndpointAddress. For bulk endpoints, the wMaxPacket value + * is initialized as if the endpoint were used at full speed. To prevent + * the endpoint from being returned by a later autoconfig call, claim it + * by assigning ep->driver_data to some non-null value. + * + * On failure, this returns a null endpoint descriptor. + */ +struct usb_ep *usb_ep_autoconfig( + struct usb_gadget *gadget, + struct usb_endpoint_descriptor *desc +) +{ + struct usb_ep *ep = NULL; + u8 type; + + type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + + /* First, apply chip-specific "best usage" knowledge. + * This might make a good usb_gadget_ops hook ... + */ + if (gadget_is_net2280(gadget) && type == USB_ENDPOINT_XFER_INT) { + /* ep-e, ep-f are PIO with only 64 byte fifos */ + ep = find_ep(gadget, "ep-e"); + if (ep && ep_matches(gadget, ep, desc)) + return ep; + ep = find_ep(gadget, "ep-f"); + if (ep && ep_matches(gadget, ep, desc)) + return ep; + + } else if (gadget_is_goku(gadget)) { + if (USB_ENDPOINT_XFER_INT == type) { + /* single buffering is enough */ + ep = find_ep(gadget, "ep3-bulk"); + if (ep && ep_matches(gadget, ep, desc)) + return ep; + } else if (USB_ENDPOINT_XFER_BULK == type + && (USB_DIR_IN & desc->bEndpointAddress)) { + /* DMA may be available */ + ep = find_ep(gadget, "ep2-bulk"); + if (ep && ep_matches(gadget, ep, desc)) + return ep; + } + + } else if (gadget_is_sh(gadget) && USB_ENDPOINT_XFER_INT == type) { + /* single buffering is enough; maybe 8 byte fifo is too */ + ep = find_ep(gadget, "ep3in-bulk"); + if (ep && ep_matches(gadget, ep, desc)) + return ep; + + } else if (gadget_is_mq11xx(gadget) && USB_ENDPOINT_XFER_INT == type) { + ep = find_ep(gadget, "ep1-bulk"); + if (ep && ep_matches(gadget, ep, desc)) + return ep; +#ifndef CONFIG_SPL_BUILD + } else if (gadget_is_dwc3(gadget)) { + const char *name = NULL; + /* + * First try standard, common configuration: ep1in-bulk, + * ep2out-bulk, ep3in-int to match other udc drivers to avoid + * confusion in already deployed software (endpoint numbers + * hardcoded in userspace software/drivers) + */ + if ((desc->bEndpointAddress & USB_DIR_IN) && + type == USB_ENDPOINT_XFER_BULK) + name = "ep1in"; + else if ((desc->bEndpointAddress & USB_DIR_IN) == 0 && + type == USB_ENDPOINT_XFER_BULK) + name = "ep2out"; + else if ((desc->bEndpointAddress & USB_DIR_IN) && + type == USB_ENDPOINT_XFER_INT) + name = "ep3in"; + + if (name) + ep = find_ep(gadget, name); + if (ep && ep_matches(gadget, ep, desc)) + return ep; +#endif + } + + if (gadget->ops->match_ep) + ep = gadget->ops->match_ep(gadget, desc, NULL); + + /* Second, look at endpoints until an unclaimed one looks usable */ + list_for_each_entry(ep, &gadget->ep_list, ep_list) { + if (ep_matches(gadget, ep, desc)) + return ep; + } + + /* Fail */ + return NULL; +} + +/** + * usb_ep_autoconfig_reset - reset endpoint autoconfig state + * @gadget: device for which autoconfig state will be reset + * + * Use this for devices where one configuration may need to assign + * endpoint resources very differently from the next one. It clears + * state such as ep->driver_data and the record of assigned endpoints + * used by usb_ep_autoconfig(). + */ +void usb_ep_autoconfig_reset(struct usb_gadget *gadget) +{ + struct usb_ep *ep; + + list_for_each_entry(ep, &gadget->ep_list, ep_list) { + ep->driver_data = NULL; + } +#ifdef MANY_ENDPOINTS + in_epnum = 0; +#endif + epnum = 0; +} diff --git a/roms/u-boot/drivers/usb/gadget/ether.c b/roms/u-boot/drivers/usb/gadget/ether.c new file mode 100644 index 000000000..16922ff15 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/ether.c @@ -0,0 +1,2706 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ether.c -- Ethernet gadget driver, with CDC and non-CDC options + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger + * Copyright (C) 2008 Nokia Corporation + */ + +#include <common.h> +#include <console.h> +#include <env.h> +#include <log.h> +#include <part.h> +#include <linux/errno.h> +#include <linux/netdevice.h> +#include <linux/usb/ch9.h> +#include <linux/usb/cdc.h> +#include <linux/usb/gadget.h> +#include <net.h> +#include <usb.h> +#include <malloc.h> +#include <memalign.h> +#include <linux/ctype.h> + +#include "gadget_chips.h" +#include "rndis.h" + +#include <dm.h> +#include <dm/lists.h> +#include <dm/uclass-internal.h> +#include <dm/device-internal.h> + +#define USB_NET_NAME "usb_ether" + +extern struct platform_data brd; + + +unsigned packet_received, packet_sent; + +/* + * Ethernet gadget driver -- with CDC and non-CDC options + * Builds on hardware support for a full duplex link. + * + * CDC Ethernet is the standard USB solution for sending Ethernet frames + * using USB. Real hardware tends to use the same framing protocol but look + * different for control features. This driver strongly prefers to use + * this USB-IF standard as its open-systems interoperability solution; + * most host side USB stacks (except from Microsoft) support it. + * + * This is sometimes called "CDC ECM" (Ethernet Control Model) to support + * TLA-soup. "CDC ACM" (Abstract Control Model) is for modems, and a new + * "CDC EEM" (Ethernet Emulation Model) is starting to spread. + * + * There's some hardware that can't talk CDC ECM. We make that hardware + * implement a "minimalist" vendor-agnostic CDC core: same framing, but + * link-level setup only requires activating the configuration. Only the + * endpoint descriptors, and product/vendor IDs, are relevant; no control + * operations are available. Linux supports it, but other host operating + * systems may not. (This is a subset of CDC Ethernet.) + * + * It turns out that if you add a few descriptors to that "CDC Subset", + * (Windows) host side drivers from MCCI can treat it as one submode of + * a proprietary scheme called "SAFE" ... without needing to know about + * specific product/vendor IDs. So we do that, making it easier to use + * those MS-Windows drivers. Those added descriptors make it resemble a + * CDC MDLM device, but they don't change device behavior at all. (See + * MCCI Engineering report 950198 "SAFE Networking Functions".) + * + * A third option is also in use. Rather than CDC Ethernet, or something + * simpler, Microsoft pushes their own approach: RNDIS. The published + * RNDIS specs are ambiguous and appear to be incomplete, and are also + * needlessly complex. They borrow more from CDC ACM than CDC ECM. + */ + +#define DRIVER_DESC "Ethernet Gadget" +/* Based on linux 2.6.27 version */ +#define DRIVER_VERSION "May Day 2005" + +static const char driver_desc[] = DRIVER_DESC; + +#define RX_EXTRA 20 /* guard against rx overflows */ + +#ifndef CONFIG_USB_ETH_RNDIS +#define rndis_uninit(x) do {} while (0) +#define rndis_deregister(c) do {} while (0) +#define rndis_exit() do {} while (0) +#endif + +/* CDC and RNDIS support the same host-chosen outgoing packet filters. */ +#define DEFAULT_FILTER (USB_CDC_PACKET_TYPE_BROADCAST \ + |USB_CDC_PACKET_TYPE_ALL_MULTICAST \ + |USB_CDC_PACKET_TYPE_PROMISCUOUS \ + |USB_CDC_PACKET_TYPE_DIRECTED) + +#define USB_CONNECT_TIMEOUT (3 * CONFIG_SYS_HZ) + +/*-------------------------------------------------------------------------*/ + +struct eth_dev { + struct usb_gadget *gadget; + struct usb_request *req; /* for control responses */ + struct usb_request *stat_req; /* for cdc & rndis status */ + + u8 config; + struct usb_ep *in_ep, *out_ep, *status_ep; + const struct usb_endpoint_descriptor + *in, *out, *status; + + struct usb_request *tx_req, *rx_req; + +#ifndef CONFIG_DM_ETH + struct eth_device *net; +#else + struct udevice *net; +#endif + struct net_device_stats stats; + unsigned int tx_qlen; + + unsigned zlp:1; + unsigned cdc:1; + unsigned rndis:1; + unsigned suspended:1; + unsigned network_started:1; + u16 cdc_filter; + unsigned long todo; + int mtu; +#define WORK_RX_MEMORY 0 + int rndis_config; + u8 host_mac[ETH_ALEN]; +}; + +/* + * This version autoconfigures as much as possible at run-time. + * + * It also ASSUMES a self-powered device, without remote wakeup, + * although remote wakeup support would make sense. + */ + +/*-------------------------------------------------------------------------*/ +struct ether_priv { + struct eth_dev ethdev; +#ifndef CONFIG_DM_ETH + struct eth_device netdev; +#else + struct udevice *netdev; +#endif + struct usb_gadget_driver eth_driver; +}; + +struct ether_priv eth_priv; +struct ether_priv *l_priv = ð_priv; + +/*-------------------------------------------------------------------------*/ + +/* "main" config is either CDC, or its simple subset */ +static inline int is_cdc(struct eth_dev *dev) +{ +#if !defined(CONFIG_USB_ETH_SUBSET) + return 1; /* only cdc possible */ +#elif !defined(CONFIG_USB_ETH_CDC) + return 0; /* only subset possible */ +#else + return dev->cdc; /* depends on what hardware we found */ +#endif +} + +/* "secondary" RNDIS config may sometimes be activated */ +static inline int rndis_active(struct eth_dev *dev) +{ +#ifdef CONFIG_USB_ETH_RNDIS + return dev->rndis; +#else + return 0; +#endif +} + +#define subset_active(dev) (!is_cdc(dev) && !rndis_active(dev)) +#define cdc_active(dev) (is_cdc(dev) && !rndis_active(dev)) + +#define DEFAULT_QLEN 2 /* double buffering by default */ + +/* peak bulk transfer bits-per-second */ +#define HS_BPS (13 * 512 * 8 * 1000 * 8) +#define FS_BPS (19 * 64 * 1 * 1000 * 8) + +#ifdef CONFIG_USB_GADGET_DUALSPEED +#define DEVSPEED USB_SPEED_HIGH + +#ifdef CONFIG_USB_ETH_QMULT +#define qmult CONFIG_USB_ETH_QMULT +#else +#define qmult 5 +#endif + +/* for dual-speed hardware, use deeper queues at highspeed */ +#define qlen(gadget) \ + (DEFAULT_QLEN*((gadget->speed == USB_SPEED_HIGH) ? qmult : 1)) + +static inline int BITRATE(struct usb_gadget *g) +{ + return (g->speed == USB_SPEED_HIGH) ? HS_BPS : FS_BPS; +} + +#else /* full speed (low speed doesn't do bulk) */ + +#define qmult 1 + +#define DEVSPEED USB_SPEED_FULL + +#define qlen(gadget) DEFAULT_QLEN + +static inline int BITRATE(struct usb_gadget *g) +{ + return FS_BPS; +} +#endif + +/*-------------------------------------------------------------------------*/ + +/* + * DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! + * Instead: allocate your own, using normal USB-IF procedures. + */ + +/* + * Thanks to NetChip Technologies for donating this product ID. + * It's for devices with only CDC Ethernet configurations. + */ +#define CDC_VENDOR_NUM 0x0525 /* NetChip */ +#define CDC_PRODUCT_NUM 0xa4a1 /* Linux-USB Ethernet Gadget */ + +/* + * For hardware that can't talk CDC, we use the same vendor ID that + * ARM Linux has used for ethernet-over-usb, both with sa1100 and + * with pxa250. We're protocol-compatible, if the host-side drivers + * use the endpoint descriptors. bcdDevice (version) is nonzero, so + * drivers that need to hard-wire endpoint numbers have a hook. + * + * The protocol is a minimal subset of CDC Ether, which works on any bulk + * hardware that's not deeply broken ... even on hardware that can't talk + * RNDIS (like SA-1100, with no interrupt endpoint, or anything that + * doesn't handle control-OUT). + */ +#define SIMPLE_VENDOR_NUM 0x049f /* Compaq Computer Corp. */ +#define SIMPLE_PRODUCT_NUM 0x505a /* Linux-USB "CDC Subset" Device */ + +/* + * For hardware that can talk RNDIS and either of the above protocols, + * use this ID ... the windows INF files will know it. Unless it's + * used with CDC Ethernet, Linux 2.4 hosts will need updates to choose + * the non-RNDIS configuration. + */ +#define RNDIS_VENDOR_NUM 0x0525 /* NetChip */ +#define RNDIS_PRODUCT_NUM 0xa4a2 /* Ethernet/RNDIS Gadget */ + +/* + * Some systems will want different product identifers published in the + * device descriptor, either numbers or strings or both. These string + * parameters are in UTF-8 (superset of ASCII's 7 bit characters). + */ + +/* + * Emulating them in eth_bind: + * static ushort idVendor; + * static ushort idProduct; + */ + +#if defined(CONFIG_USB_GADGET_MANUFACTURER) +static char *iManufacturer = CONFIG_USB_GADGET_MANUFACTURER; +#else +static char *iManufacturer = "U-Boot"; +#endif + +/* These probably need to be configurable. */ +static ushort bcdDevice; +static char *iProduct; +static char *iSerialNumber; + +static char dev_addr[18]; + +static char host_addr[18]; + + +/*-------------------------------------------------------------------------*/ + +/* + * USB DRIVER HOOKUP (to the hardware driver, below us), mostly + * ep0 implementation: descriptors, config management, setup(). + * also optional class-specific notification interrupt transfer. + */ + +/* + * DESCRIPTORS ... most are static, but strings and (full) configuration + * descriptors are built on demand. For now we do either full CDC, or + * our simple subset, with RNDIS as an optional second configuration. + * + * RNDIS includes some CDC ACM descriptors ... like CDC Ethernet. But + * the class descriptors match a modem (they're ignored; it's really just + * Ethernet functionality), they don't need the NOP altsetting, and the + * status transfer endpoint isn't optional. + */ + +#define STRING_MANUFACTURER 1 +#define STRING_PRODUCT 2 +#define STRING_ETHADDR 3 +#define STRING_DATA 4 +#define STRING_CONTROL 5 +#define STRING_RNDIS_CONTROL 6 +#define STRING_CDC 7 +#define STRING_SUBSET 8 +#define STRING_RNDIS 9 +#define STRING_SERIALNUMBER 10 + +/* holds our biggest descriptor (or RNDIS response) */ +#define USB_BUFSIZ 256 + +/* + * This device advertises one configuration, eth_config, unless RNDIS + * is enabled (rndis_config) on hardware supporting at least two configs. + * + * NOTE: Controllers like superh_udc should probably be able to use + * an RNDIS-only configuration. + * + * FIXME define some higher-powered configurations to make it easier + * to recharge batteries ... + */ + +#define DEV_CONFIG_VALUE 1 /* cdc or subset */ +#define DEV_RNDIS_CONFIG_VALUE 2 /* rndis; optional */ + +static struct usb_device_descriptor +device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = __constant_cpu_to_le16(0x0200), + + .bDeviceClass = USB_CLASS_COMM, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + + .idVendor = __constant_cpu_to_le16(CDC_VENDOR_NUM), + .idProduct = __constant_cpu_to_le16(CDC_PRODUCT_NUM), + .iManufacturer = STRING_MANUFACTURER, + .iProduct = STRING_PRODUCT, + .bNumConfigurations = 1, +}; + +static struct usb_otg_descriptor +otg_descriptor = { + .bLength = sizeof otg_descriptor, + .bDescriptorType = USB_DT_OTG, + + .bmAttributes = USB_OTG_SRP, +}; + +static struct usb_config_descriptor +eth_config = { + .bLength = sizeof eth_config, + .bDescriptorType = USB_DT_CONFIG, + + /* compute wTotalLength on the fly */ + .bNumInterfaces = 2, + .bConfigurationValue = DEV_CONFIG_VALUE, + .iConfiguration = STRING_CDC, + .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, + .bMaxPower = 1, +}; + +#ifdef CONFIG_USB_ETH_RNDIS +static struct usb_config_descriptor +rndis_config = { + .bLength = sizeof rndis_config, + .bDescriptorType = USB_DT_CONFIG, + + /* compute wTotalLength on the fly */ + .bNumInterfaces = 2, + .bConfigurationValue = DEV_RNDIS_CONFIG_VALUE, + .iConfiguration = STRING_RNDIS, + .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, + .bMaxPower = 1, +}; +#endif + +/* + * Compared to the simple CDC subset, the full CDC Ethernet model adds + * three class descriptors, two interface descriptors, optional status + * endpoint. Both have a "data" interface and two bulk endpoints. + * There are also differences in how control requests are handled. + * + * RNDIS shares a lot with CDC-Ethernet, since it's a variant of the + * CDC-ACM (modem) spec. Unfortunately MSFT's RNDIS driver is buggy; it + * may hang or oops. Since bugfixes (or accurate specs, letting Linux + * work around those bugs) are unlikely to ever come from MSFT, you may + * wish to avoid using RNDIS. + * + * MCCI offers an alternative to RNDIS if you need to connect to Windows + * but have hardware that can't support CDC Ethernet. We add descriptors + * to present the CDC Subset as a (nonconformant) CDC MDLM variant called + * "SAFE". That borrows from both CDC Ethernet and CDC MDLM. You can + * get those drivers from MCCI, or bundled with various products. + */ + +#ifdef CONFIG_USB_ETH_CDC +static struct usb_interface_descriptor +control_intf = { + .bLength = sizeof control_intf, + .bDescriptorType = USB_DT_INTERFACE, + + .bInterfaceNumber = 0, + /* status endpoint is optional; this may be patched later */ + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET, + .bInterfaceProtocol = USB_CDC_PROTO_NONE, + .iInterface = STRING_CONTROL, +}; +#endif + +#ifdef CONFIG_USB_ETH_RNDIS +static const struct usb_interface_descriptor +rndis_control_intf = { + .bLength = sizeof rndis_control_intf, + .bDescriptorType = USB_DT_INTERFACE, + + .bInterfaceNumber = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, + .bInterfaceProtocol = USB_CDC_ACM_PROTO_VENDOR, + .iInterface = STRING_RNDIS_CONTROL, +}; +#endif + +static const struct usb_cdc_header_desc header_desc = { + .bLength = sizeof header_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + + .bcdCDC = __constant_cpu_to_le16(0x0110), +}; + +#if defined(CONFIG_USB_ETH_CDC) || defined(CONFIG_USB_ETH_RNDIS) + +static const struct usb_cdc_union_desc union_desc = { + .bLength = sizeof union_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + + .bMasterInterface0 = 0, /* index of control interface */ + .bSlaveInterface0 = 1, /* index of DATA interface */ +}; + +#endif /* CDC || RNDIS */ + +#ifdef CONFIG_USB_ETH_RNDIS + +static const struct usb_cdc_call_mgmt_descriptor call_mgmt_descriptor = { + .bLength = sizeof call_mgmt_descriptor, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE, + + .bmCapabilities = 0x00, + .bDataInterface = 0x01, +}; + +static const struct usb_cdc_acm_descriptor acm_descriptor = { + .bLength = sizeof acm_descriptor, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ACM_TYPE, + + .bmCapabilities = 0x00, +}; + +#endif + +#ifndef CONFIG_USB_ETH_CDC + +/* + * "SAFE" loosely follows CDC WMC MDLM, violating the spec in various + * ways: data endpoints live in the control interface, there's no data + * interface, and it's not used to talk to a cell phone radio. + */ + +static const struct usb_cdc_mdlm_desc mdlm_desc = { + .bLength = sizeof mdlm_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_MDLM_TYPE, + + .bcdVersion = __constant_cpu_to_le16(0x0100), + .bGUID = { + 0x5d, 0x34, 0xcf, 0x66, 0x11, 0x18, 0x11, 0xd6, + 0xa2, 0x1a, 0x00, 0x01, 0x02, 0xca, 0x9a, 0x7f, + }, +}; + +/* + * since "usb_cdc_mdlm_detail_desc" is a variable length structure, we + * can't really use its struct. All we do here is say that we're using + * the submode of "SAFE" which directly matches the CDC Subset. + */ +#ifdef CONFIG_USB_ETH_SUBSET +static const u8 mdlm_detail_desc[] = { + 6, + USB_DT_CS_INTERFACE, + USB_CDC_MDLM_DETAIL_TYPE, + + 0, /* "SAFE" */ + 0, /* network control capabilities (none) */ + 0, /* network data capabilities ("raw" encapsulation) */ +}; +#endif + +#endif + +static const struct usb_cdc_ether_desc ether_desc = { + .bLength = sizeof(ether_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ETHERNET_TYPE, + + /* this descriptor actually adds value, surprise! */ + .iMACAddress = STRING_ETHADDR, + .bmEthernetStatistics = __constant_cpu_to_le32(0), /* no statistics */ + .wMaxSegmentSize = __constant_cpu_to_le16(PKTSIZE_ALIGN), + .wNumberMCFilters = __constant_cpu_to_le16(0), + .bNumberPowerFilters = 0, +}; + +#if defined(CONFIG_USB_ETH_CDC) || defined(CONFIG_USB_ETH_RNDIS) + +/* + * include the status endpoint if we can, even where it's optional. + * use wMaxPacketSize big enough to fit CDC_NOTIFY_SPEED_CHANGE in one + * packet, to simplify cancellation; and a big transfer interval, to + * waste less bandwidth. + * + * some drivers (like Linux 2.4 cdc-ether!) "need" it to exist even + * if they ignore the connect/disconnect notifications that real aether + * can provide. more advanced cdc configurations might want to support + * encapsulated commands (vendor-specific, using control-OUT). + * + * RNDIS requires the status endpoint, since it uses that encapsulation + * mechanism for its funky RPC scheme. + */ + +#define LOG2_STATUS_INTERVAL_MSEC 5 /* 1 << 5 == 32 msec */ +#define STATUS_BYTECOUNT 16 /* 8 byte header + data */ + +static struct usb_endpoint_descriptor +fs_status_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(STATUS_BYTECOUNT), + .bInterval = 1 << LOG2_STATUS_INTERVAL_MSEC, +}; +#endif + +#ifdef CONFIG_USB_ETH_CDC + +/* the default data interface has no endpoints ... */ + +static const struct usb_interface_descriptor +data_nop_intf = { + .bLength = sizeof data_nop_intf, + .bDescriptorType = USB_DT_INTERFACE, + + .bInterfaceNumber = 1, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, +}; + +/* ... but the "real" data interface has two bulk endpoints */ + +static const struct usb_interface_descriptor +data_intf = { + .bLength = sizeof data_intf, + .bDescriptorType = USB_DT_INTERFACE, + + .bInterfaceNumber = 1, + .bAlternateSetting = 1, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .iInterface = STRING_DATA, +}; + +#endif + +#ifdef CONFIG_USB_ETH_RNDIS + +/* RNDIS doesn't activate by changing to the "real" altsetting */ + +static const struct usb_interface_descriptor +rndis_data_intf = { + .bLength = sizeof rndis_data_intf, + .bDescriptorType = USB_DT_INTERFACE, + + .bInterfaceNumber = 1, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .iInterface = STRING_DATA, +}; + +#endif + +#ifdef CONFIG_USB_ETH_SUBSET + +/* + * "Simple" CDC-subset option is a simple vendor-neutral model that most + * full speed controllers can handle: one interface, two bulk endpoints. + * + * To assist host side drivers, we fancy it up a bit, and add descriptors + * so some host side drivers will understand it as a "SAFE" variant. + */ + +static const struct usb_interface_descriptor +subset_data_intf = { + .bLength = sizeof subset_data_intf, + .bDescriptorType = USB_DT_INTERFACE, + + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_MDLM, + .bInterfaceProtocol = 0, + .iInterface = STRING_DATA, +}; + +#endif /* SUBSET */ + +static struct usb_endpoint_descriptor +fs_source_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), +}; + +static struct usb_endpoint_descriptor +fs_sink_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), +}; + +static const struct usb_descriptor_header *fs_eth_function[11] = { + (struct usb_descriptor_header *) &otg_descriptor, +#ifdef CONFIG_USB_ETH_CDC + /* "cdc" mode descriptors */ + (struct usb_descriptor_header *) &control_intf, + (struct usb_descriptor_header *) &header_desc, + (struct usb_descriptor_header *) &union_desc, + (struct usb_descriptor_header *) ðer_desc, + /* NOTE: status endpoint may need to be removed */ + (struct usb_descriptor_header *) &fs_status_desc, + /* data interface, with altsetting */ + (struct usb_descriptor_header *) &data_nop_intf, + (struct usb_descriptor_header *) &data_intf, + (struct usb_descriptor_header *) &fs_source_desc, + (struct usb_descriptor_header *) &fs_sink_desc, + NULL, +#endif /* CONFIG_USB_ETH_CDC */ +}; + +static inline void fs_subset_descriptors(void) +{ +#ifdef CONFIG_USB_ETH_SUBSET + /* behavior is "CDC Subset"; extra descriptors say "SAFE" */ + fs_eth_function[1] = (struct usb_descriptor_header *) &subset_data_intf; + fs_eth_function[2] = (struct usb_descriptor_header *) &header_desc; + fs_eth_function[3] = (struct usb_descriptor_header *) &mdlm_desc; + fs_eth_function[4] = (struct usb_descriptor_header *) &mdlm_detail_desc; + fs_eth_function[5] = (struct usb_descriptor_header *) ðer_desc; + fs_eth_function[6] = (struct usb_descriptor_header *) &fs_source_desc; + fs_eth_function[7] = (struct usb_descriptor_header *) &fs_sink_desc; + fs_eth_function[8] = NULL; +#else + fs_eth_function[1] = NULL; +#endif +} + +#ifdef CONFIG_USB_ETH_RNDIS +static const struct usb_descriptor_header *fs_rndis_function[] = { + (struct usb_descriptor_header *) &otg_descriptor, + /* control interface matches ACM, not Ethernet */ + (struct usb_descriptor_header *) &rndis_control_intf, + (struct usb_descriptor_header *) &header_desc, + (struct usb_descriptor_header *) &call_mgmt_descriptor, + (struct usb_descriptor_header *) &acm_descriptor, + (struct usb_descriptor_header *) &union_desc, + (struct usb_descriptor_header *) &fs_status_desc, + /* data interface has no altsetting */ + (struct usb_descriptor_header *) &rndis_data_intf, + (struct usb_descriptor_header *) &fs_source_desc, + (struct usb_descriptor_header *) &fs_sink_desc, + NULL, +}; +#endif + +/* + * usb 2.0 devices need to expose both high speed and full speed + * descriptors, unless they only run at full speed. + */ + +#if defined(CONFIG_USB_ETH_CDC) || defined(CONFIG_USB_ETH_RNDIS) +static struct usb_endpoint_descriptor +hs_status_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(STATUS_BYTECOUNT), + .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4, +}; +#endif /* CONFIG_USB_ETH_CDC */ + +static struct usb_endpoint_descriptor +hs_source_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor +hs_sink_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_qualifier_descriptor +dev_qualifier = { + .bLength = sizeof dev_qualifier, + .bDescriptorType = USB_DT_DEVICE_QUALIFIER, + + .bcdUSB = __constant_cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_COMM, + + .bNumConfigurations = 1, +}; + +static const struct usb_descriptor_header *hs_eth_function[11] = { + (struct usb_descriptor_header *) &otg_descriptor, +#ifdef CONFIG_USB_ETH_CDC + /* "cdc" mode descriptors */ + (struct usb_descriptor_header *) &control_intf, + (struct usb_descriptor_header *) &header_desc, + (struct usb_descriptor_header *) &union_desc, + (struct usb_descriptor_header *) ðer_desc, + /* NOTE: status endpoint may need to be removed */ + (struct usb_descriptor_header *) &hs_status_desc, + /* data interface, with altsetting */ + (struct usb_descriptor_header *) &data_nop_intf, + (struct usb_descriptor_header *) &data_intf, + (struct usb_descriptor_header *) &hs_source_desc, + (struct usb_descriptor_header *) &hs_sink_desc, + NULL, +#endif /* CONFIG_USB_ETH_CDC */ +}; + +static inline void hs_subset_descriptors(void) +{ +#ifdef CONFIG_USB_ETH_SUBSET + /* behavior is "CDC Subset"; extra descriptors say "SAFE" */ + hs_eth_function[1] = (struct usb_descriptor_header *) &subset_data_intf; + hs_eth_function[2] = (struct usb_descriptor_header *) &header_desc; + hs_eth_function[3] = (struct usb_descriptor_header *) &mdlm_desc; + hs_eth_function[4] = (struct usb_descriptor_header *) &mdlm_detail_desc; + hs_eth_function[5] = (struct usb_descriptor_header *) ðer_desc; + hs_eth_function[6] = (struct usb_descriptor_header *) &hs_source_desc; + hs_eth_function[7] = (struct usb_descriptor_header *) &hs_sink_desc; + hs_eth_function[8] = NULL; +#else + hs_eth_function[1] = NULL; +#endif +} + +#ifdef CONFIG_USB_ETH_RNDIS +static const struct usb_descriptor_header *hs_rndis_function[] = { + (struct usb_descriptor_header *) &otg_descriptor, + /* control interface matches ACM, not Ethernet */ + (struct usb_descriptor_header *) &rndis_control_intf, + (struct usb_descriptor_header *) &header_desc, + (struct usb_descriptor_header *) &call_mgmt_descriptor, + (struct usb_descriptor_header *) &acm_descriptor, + (struct usb_descriptor_header *) &union_desc, + (struct usb_descriptor_header *) &hs_status_desc, + /* data interface has no altsetting */ + (struct usb_descriptor_header *) &rndis_data_intf, + (struct usb_descriptor_header *) &hs_source_desc, + (struct usb_descriptor_header *) &hs_sink_desc, + NULL, +}; +#endif + + +/* maxpacket and other transfer characteristics vary by speed. */ +static inline struct usb_endpoint_descriptor * +ep_desc(struct usb_gadget *g, struct usb_endpoint_descriptor *hs, + struct usb_endpoint_descriptor *fs) +{ + if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) + return hs; + return fs; +} + +/*-------------------------------------------------------------------------*/ + +/* descriptors that are built on-demand */ + +static char manufacturer[50]; +static char product_desc[40] = DRIVER_DESC; +static char serial_number[20]; + +/* address that the host will use ... usually assigned at random */ +static char ethaddr[2 * ETH_ALEN + 1]; + +/* static strings, in UTF-8 */ +static struct usb_string strings[] = { + { STRING_MANUFACTURER, manufacturer, }, + { STRING_PRODUCT, product_desc, }, + { STRING_SERIALNUMBER, serial_number, }, + { STRING_DATA, "Ethernet Data", }, + { STRING_ETHADDR, ethaddr, }, +#ifdef CONFIG_USB_ETH_CDC + { STRING_CDC, "CDC Ethernet", }, + { STRING_CONTROL, "CDC Communications Control", }, +#endif +#ifdef CONFIG_USB_ETH_SUBSET + { STRING_SUBSET, "CDC Ethernet Subset", }, +#endif +#ifdef CONFIG_USB_ETH_RNDIS + { STRING_RNDIS, "RNDIS", }, + { STRING_RNDIS_CONTROL, "RNDIS Communications Control", }, +#endif + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab = { + .language = 0x0409, /* en-us */ + .strings = strings, +}; + +/*============================================================================*/ +DEFINE_CACHE_ALIGN_BUFFER(u8, control_req, USB_BUFSIZ); + +#if defined(CONFIG_USB_ETH_CDC) || defined(CONFIG_USB_ETH_RNDIS) +DEFINE_CACHE_ALIGN_BUFFER(u8, status_req, STATUS_BYTECOUNT); +#endif + +/*============================================================================*/ + +/* + * one config, two interfaces: control, data. + * complications: class descriptors, and an altsetting. + */ +static int +config_buf(struct usb_gadget *g, u8 *buf, u8 type, unsigned index, int is_otg) +{ + int len; + const struct usb_config_descriptor *config; + const struct usb_descriptor_header **function; + int hs = 0; + + if (gadget_is_dualspeed(g)) { + hs = (g->speed == USB_SPEED_HIGH); + if (type == USB_DT_OTHER_SPEED_CONFIG) + hs = !hs; + } +#define which_fn(t) (hs ? hs_ ## t ## _function : fs_ ## t ## _function) + + if (index >= device_desc.bNumConfigurations) + return -EINVAL; + +#ifdef CONFIG_USB_ETH_RNDIS + /* + * list the RNDIS config first, to make Microsoft's drivers + * happy. DOCSIS 1.0 needs this too. + */ + if (device_desc.bNumConfigurations == 2 && index == 0) { + config = &rndis_config; + function = which_fn(rndis); + } else +#endif + { + config = ð_config; + function = which_fn(eth); + } + + /* for now, don't advertise srp-only devices */ + if (!is_otg) + function++; + + len = usb_gadget_config_buf(config, buf, USB_BUFSIZ, function); + if (len < 0) + return len; + ((struct usb_config_descriptor *) buf)->bDescriptorType = type; + return len; +} + +/*-------------------------------------------------------------------------*/ + +static void eth_start(struct eth_dev *dev, gfp_t gfp_flags); +static int alloc_requests(struct eth_dev *dev, unsigned n, gfp_t gfp_flags); + +static int +set_ether_config(struct eth_dev *dev, gfp_t gfp_flags) +{ + int result = 0; + struct usb_gadget *gadget = dev->gadget; + +#if defined(CONFIG_USB_ETH_CDC) || defined(CONFIG_USB_ETH_RNDIS) + /* status endpoint used for RNDIS and (optionally) CDC */ + if (!subset_active(dev) && dev->status_ep) { + dev->status = ep_desc(gadget, &hs_status_desc, + &fs_status_desc); + dev->status_ep->driver_data = dev; + + result = usb_ep_enable(dev->status_ep, dev->status); + if (result != 0) { + debug("enable %s --> %d\n", + dev->status_ep->name, result); + goto done; + } + } +#endif + + dev->in = ep_desc(gadget, &hs_source_desc, &fs_source_desc); + dev->in_ep->driver_data = dev; + + dev->out = ep_desc(gadget, &hs_sink_desc, &fs_sink_desc); + dev->out_ep->driver_data = dev; + + /* + * With CDC, the host isn't allowed to use these two data + * endpoints in the default altsetting for the interface. + * so we don't activate them yet. Reset from SET_INTERFACE. + * + * Strictly speaking RNDIS should work the same: activation is + * a side effect of setting a packet filter. Deactivation is + * from REMOTE_NDIS_HALT_MSG, reset from REMOTE_NDIS_RESET_MSG. + */ + if (!cdc_active(dev)) { + result = usb_ep_enable(dev->in_ep, dev->in); + if (result != 0) { + debug("enable %s --> %d\n", + dev->in_ep->name, result); + goto done; + } + + result = usb_ep_enable(dev->out_ep, dev->out); + if (result != 0) { + debug("enable %s --> %d\n", + dev->out_ep->name, result); + goto done; + } + } + +done: + if (result == 0) + result = alloc_requests(dev, qlen(gadget), gfp_flags); + + /* on error, disable any endpoints */ + if (result < 0) { + if (!subset_active(dev) && dev->status_ep) + (void) usb_ep_disable(dev->status_ep); + dev->status = NULL; + (void) usb_ep_disable(dev->in_ep); + (void) usb_ep_disable(dev->out_ep); + dev->in = NULL; + dev->out = NULL; + } else if (!cdc_active(dev)) { + /* + * activate non-CDC configs right away + * this isn't strictly according to the RNDIS spec + */ + eth_start(dev, GFP_ATOMIC); + } + + /* caller is responsible for cleanup on error */ + return result; +} + +static void eth_reset_config(struct eth_dev *dev) +{ + if (dev->config == 0) + return; + + debug("%s\n", __func__); + + rndis_uninit(dev->rndis_config); + + /* + * disable endpoints, forcing (synchronous) completion of + * pending i/o. then free the requests. + */ + + if (dev->in) { + usb_ep_disable(dev->in_ep); + if (dev->tx_req) { + usb_ep_free_request(dev->in_ep, dev->tx_req); + dev->tx_req = NULL; + } + } + if (dev->out) { + usb_ep_disable(dev->out_ep); + if (dev->rx_req) { + usb_ep_free_request(dev->out_ep, dev->rx_req); + dev->rx_req = NULL; + } + } + if (dev->status) + usb_ep_disable(dev->status_ep); + + dev->rndis = 0; + dev->cdc_filter = 0; + dev->config = 0; +} + +/* + * change our operational config. must agree with the code + * that returns config descriptors, and altsetting code. + */ +static int eth_set_config(struct eth_dev *dev, unsigned number, + gfp_t gfp_flags) +{ + int result = 0; + struct usb_gadget *gadget = dev->gadget; + + if (gadget_is_sa1100(gadget) + && dev->config + && dev->tx_qlen != 0) { + /* tx fifo is full, but we can't clear it...*/ + pr_err("can't change configurations"); + return -ESPIPE; + } + eth_reset_config(dev); + + switch (number) { + case DEV_CONFIG_VALUE: + result = set_ether_config(dev, gfp_flags); + break; +#ifdef CONFIG_USB_ETH_RNDIS + case DEV_RNDIS_CONFIG_VALUE: + dev->rndis = 1; + result = set_ether_config(dev, gfp_flags); + break; +#endif + default: + result = -EINVAL; + /* FALL THROUGH */ + case 0: + break; + } + + if (result) { + if (number) + eth_reset_config(dev); + usb_gadget_vbus_draw(dev->gadget, + gadget_is_otg(dev->gadget) ? 8 : 100); + } else { + char *speed; + unsigned power; + + power = 2 * eth_config.bMaxPower; + usb_gadget_vbus_draw(dev->gadget, power); + + switch (gadget->speed) { + case USB_SPEED_FULL: + speed = "full"; break; +#ifdef CONFIG_USB_GADGET_DUALSPEED + case USB_SPEED_HIGH: + speed = "high"; break; +#endif + default: + speed = "?"; break; + } + + dev->config = number; + printf("%s speed config #%d: %d mA, %s, using %s\n", + speed, number, power, driver_desc, + rndis_active(dev) + ? "RNDIS" + : (cdc_active(dev) + ? "CDC Ethernet" + : "CDC Ethernet Subset")); + } + return result; +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_USB_ETH_CDC + +/* + * The interrupt endpoint is used in CDC networking models (Ethernet, ATM) + * only to notify the host about link status changes (which we support) or + * report completion of some encapsulated command (as used in RNDIS). Since + * we want this CDC Ethernet code to be vendor-neutral, we don't use that + * command mechanism; and only one status request is ever queued. + */ +static void eth_status_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct usb_cdc_notification *event = req->buf; + int value = req->status; + struct eth_dev *dev = ep->driver_data; + + /* issue the second notification if host reads the first */ + if (event->bNotificationType == USB_CDC_NOTIFY_NETWORK_CONNECTION + && value == 0) { + __le32 *data = req->buf + sizeof *event; + + event->bmRequestType = 0xA1; + event->bNotificationType = USB_CDC_NOTIFY_SPEED_CHANGE; + event->wValue = __constant_cpu_to_le16(0); + event->wIndex = __constant_cpu_to_le16(1); + event->wLength = __constant_cpu_to_le16(8); + + /* SPEED_CHANGE data is up/down speeds in bits/sec */ + data[0] = data[1] = cpu_to_le32(BITRATE(dev->gadget)); + + req->length = STATUS_BYTECOUNT; + value = usb_ep_queue(ep, req, GFP_ATOMIC); + debug("send SPEED_CHANGE --> %d\n", value); + if (value == 0) + return; + } else if (value != -ECONNRESET) { + debug("event %02x --> %d\n", + event->bNotificationType, value); + if (event->bNotificationType == + USB_CDC_NOTIFY_SPEED_CHANGE) { + dev->network_started = 1; + printf("USB network up!\n"); + } + } + req->context = NULL; +} + +static void issue_start_status(struct eth_dev *dev) +{ + struct usb_request *req = dev->stat_req; + struct usb_cdc_notification *event; + int value; + + /* + * flush old status + * + * FIXME ugly idiom, maybe we'd be better with just + * a "cancel the whole queue" primitive since any + * unlink-one primitive has way too many error modes. + * here, we "know" toggle is already clear... + * + * FIXME iff req->context != null just dequeue it + */ + usb_ep_disable(dev->status_ep); + usb_ep_enable(dev->status_ep, dev->status); + + /* + * 3.8.1 says to issue first NETWORK_CONNECTION, then + * a SPEED_CHANGE. could be useful in some configs. + */ + event = req->buf; + event->bmRequestType = 0xA1; + event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION; + event->wValue = __constant_cpu_to_le16(1); /* connected */ + event->wIndex = __constant_cpu_to_le16(1); + event->wLength = 0; + + req->length = sizeof *event; + req->complete = eth_status_complete; + req->context = dev; + + value = usb_ep_queue(dev->status_ep, req, GFP_ATOMIC); + if (value < 0) + debug("status buf queue --> %d\n", value); +} + +#endif + +/*-------------------------------------------------------------------------*/ + +static void eth_setup_complete(struct usb_ep *ep, struct usb_request *req) +{ + if (req->status || req->actual != req->length) + debug("setup complete --> %d, %d/%d\n", + req->status, req->actual, req->length); +} + +#ifdef CONFIG_USB_ETH_RNDIS + +static void rndis_response_complete(struct usb_ep *ep, struct usb_request *req) +{ + if (req->status || req->actual != req->length) + debug("rndis response complete --> %d, %d/%d\n", + req->status, req->actual, req->length); + + /* done sending after USB_CDC_GET_ENCAPSULATED_RESPONSE */ +} + +static void rndis_command_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct eth_dev *dev = ep->driver_data; + int status; + + /* received RNDIS command from USB_CDC_SEND_ENCAPSULATED_COMMAND */ + status = rndis_msg_parser(dev->rndis_config, (u8 *) req->buf); + if (status < 0) + pr_err("%s: rndis parse error %d", __func__, status); +} + +#endif /* RNDIS */ + +/* + * The setup() callback implements all the ep0 functionality that's not + * handled lower down. CDC has a number of less-common features: + * + * - two interfaces: control, and ethernet data + * - Ethernet data interface has two altsettings: default, and active + * - class-specific descriptors for the control interface + * - class-specific control requests + */ +static int +eth_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) +{ + struct eth_dev *dev = get_gadget_data(gadget); + struct usb_request *req = dev->req; + int value = -EOPNOTSUPP; + u16 wIndex = le16_to_cpu(ctrl->wIndex); + u16 wValue = le16_to_cpu(ctrl->wValue); + u16 wLength = le16_to_cpu(ctrl->wLength); + + /* + * descriptors just go into the pre-allocated ep0 buffer, + * while config change events may enable network traffic. + */ + + debug("%s\n", __func__); + + req->complete = eth_setup_complete; + switch (ctrl->bRequest) { + + case USB_REQ_GET_DESCRIPTOR: + if (ctrl->bRequestType != USB_DIR_IN) + break; + switch (wValue >> 8) { + + case USB_DT_DEVICE: + device_desc.bMaxPacketSize0 = gadget->ep0->maxpacket; + value = min(wLength, (u16) sizeof device_desc); + memcpy(req->buf, &device_desc, value); + break; + case USB_DT_DEVICE_QUALIFIER: + if (!gadget_is_dualspeed(gadget)) + break; + value = min(wLength, (u16) sizeof dev_qualifier); + memcpy(req->buf, &dev_qualifier, value); + break; + + case USB_DT_OTHER_SPEED_CONFIG: + if (!gadget_is_dualspeed(gadget)) + break; + /* FALLTHROUGH */ + case USB_DT_CONFIG: + value = config_buf(gadget, req->buf, + wValue >> 8, + wValue & 0xff, + gadget_is_otg(gadget)); + if (value >= 0) + value = min(wLength, (u16) value); + break; + + case USB_DT_STRING: + value = usb_gadget_get_string(&stringtab, + wValue & 0xff, req->buf); + + if (value >= 0) + value = min(wLength, (u16) value); + + break; + } + break; + + case USB_REQ_SET_CONFIGURATION: + if (ctrl->bRequestType != 0) + break; + if (gadget->a_hnp_support) + debug("HNP available\n"); + else if (gadget->a_alt_hnp_support) + debug("HNP needs a different root port\n"); + value = eth_set_config(dev, wValue, GFP_ATOMIC); + break; + case USB_REQ_GET_CONFIGURATION: + if (ctrl->bRequestType != USB_DIR_IN) + break; + *(u8 *)req->buf = dev->config; + value = min(wLength, (u16) 1); + break; + + case USB_REQ_SET_INTERFACE: + if (ctrl->bRequestType != USB_RECIP_INTERFACE + || !dev->config + || wIndex > 1) + break; + if (!cdc_active(dev) && wIndex != 0) + break; + + /* + * PXA hardware partially handles SET_INTERFACE; + * we need to kluge around that interference. + */ + if (gadget_is_pxa(gadget)) { + value = eth_set_config(dev, DEV_CONFIG_VALUE, + GFP_ATOMIC); + /* + * PXA25x driver use non-CDC ethernet gadget. + * But only _CDC and _RNDIS code can signalize + * that network is working. So we signalize it + * here. + */ + dev->network_started = 1; + debug("USB network up!\n"); + goto done_set_intf; + } + +#ifdef CONFIG_USB_ETH_CDC + switch (wIndex) { + case 0: /* control/master intf */ + if (wValue != 0) + break; + if (dev->status) { + usb_ep_disable(dev->status_ep); + usb_ep_enable(dev->status_ep, dev->status); + } + + value = 0; + break; + case 1: /* data intf */ + if (wValue > 1) + break; + usb_ep_disable(dev->in_ep); + usb_ep_disable(dev->out_ep); + + /* + * CDC requires the data transfers not be done from + * the default interface setting ... also, setting + * the non-default interface resets filters etc. + */ + if (wValue == 1) { + if (!cdc_active(dev)) + break; + usb_ep_enable(dev->in_ep, dev->in); + usb_ep_enable(dev->out_ep, dev->out); + dev->cdc_filter = DEFAULT_FILTER; + if (dev->status) + issue_start_status(dev); + eth_start(dev, GFP_ATOMIC); + } + value = 0; + break; + } +#else + /* + * FIXME this is wrong, as is the assumption that + * all non-PXA hardware talks real CDC ... + */ + debug("set_interface ignored!\n"); +#endif /* CONFIG_USB_ETH_CDC */ + +done_set_intf: + break; + case USB_REQ_GET_INTERFACE: + if (ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE) + || !dev->config + || wIndex > 1) + break; + if (!(cdc_active(dev) || rndis_active(dev)) && wIndex != 0) + break; + + /* for CDC, iff carrier is on, data interface is active. */ + if (rndis_active(dev) || wIndex != 1) + *(u8 *)req->buf = 0; + else { + /* *(u8 *)req->buf = netif_carrier_ok (dev->net) ? 1 : 0; */ + /* carrier always ok ...*/ + *(u8 *)req->buf = 1 ; + } + value = min(wLength, (u16) 1); + break; + +#ifdef CONFIG_USB_ETH_CDC + case USB_CDC_SET_ETHERNET_PACKET_FILTER: + /* + * see 6.2.30: no data, wIndex = interface, + * wValue = packet filter bitmap + */ + if (ctrl->bRequestType != (USB_TYPE_CLASS|USB_RECIP_INTERFACE) + || !cdc_active(dev) + || wLength != 0 + || wIndex > 1) + break; + debug("packet filter %02x\n", wValue); + dev->cdc_filter = wValue; + value = 0; + break; + + /* + * and potentially: + * case USB_CDC_SET_ETHERNET_MULTICAST_FILTERS: + * case USB_CDC_SET_ETHERNET_PM_PATTERN_FILTER: + * case USB_CDC_GET_ETHERNET_PM_PATTERN_FILTER: + * case USB_CDC_GET_ETHERNET_STATISTIC: + */ + +#endif /* CONFIG_USB_ETH_CDC */ + +#ifdef CONFIG_USB_ETH_RNDIS + /* + * RNDIS uses the CDC command encapsulation mechanism to implement + * an RPC scheme, with much getting/setting of attributes by OID. + */ + case USB_CDC_SEND_ENCAPSULATED_COMMAND: + if (ctrl->bRequestType != (USB_TYPE_CLASS|USB_RECIP_INTERFACE) + || !rndis_active(dev) + || wLength > USB_BUFSIZ + || wValue + || rndis_control_intf.bInterfaceNumber + != wIndex) + break; + /* read the request, then process it */ + value = wLength; + req->complete = rndis_command_complete; + /* later, rndis_control_ack () sends a notification */ + break; + + case USB_CDC_GET_ENCAPSULATED_RESPONSE: + if ((USB_DIR_IN|USB_TYPE_CLASS|USB_RECIP_INTERFACE) + == ctrl->bRequestType + && rndis_active(dev) + /* && wLength >= 0x0400 */ + && !wValue + && rndis_control_intf.bInterfaceNumber + == wIndex) { + u8 *buf; + u32 n; + + /* return the result */ + buf = rndis_get_next_response(dev->rndis_config, &n); + if (buf) { + memcpy(req->buf, buf, n); + req->complete = rndis_response_complete; + rndis_free_response(dev->rndis_config, buf); + value = n; + } + /* else stalls ... spec says to avoid that */ + } + break; +#endif /* RNDIS */ + + default: + debug("unknown control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + wValue, wIndex, wLength); + } + + /* respond with data transfer before status phase? */ + if (value >= 0) { + debug("respond with data transfer before status phase\n"); + req->length = value; + req->zero = value < wLength + && (value % gadget->ep0->maxpacket) == 0; + value = usb_ep_queue(gadget->ep0, req, GFP_ATOMIC); + if (value < 0) { + debug("ep_queue --> %d\n", value); + req->status = 0; + eth_setup_complete(gadget->ep0, req); + } + } + + /* host either stalls (value < 0) or reports success */ + return value; +} + +/*-------------------------------------------------------------------------*/ + +static void rx_complete(struct usb_ep *ep, struct usb_request *req); + +static int rx_submit(struct eth_dev *dev, struct usb_request *req, + gfp_t gfp_flags) +{ + int retval = -ENOMEM; + size_t size; + + /* + * Padding up to RX_EXTRA handles minor disagreements with host. + * Normally we use the USB "terminate on short read" convention; + * so allow up to (N*maxpacket), since that memory is normally + * already allocated. Some hardware doesn't deal well with short + * reads (e.g. DMA must be N*maxpacket), so for now don't trim a + * byte off the end (to force hardware errors on overflow). + * + * RNDIS uses internal framing, and explicitly allows senders to + * pad to end-of-packet. That's potentially nice for speed, + * but means receivers can't recover synch on their own. + */ + + debug("%s\n", __func__); + if (!req) + return -EINVAL; + + size = (ETHER_HDR_SIZE + dev->mtu + RX_EXTRA); + size += dev->out_ep->maxpacket - 1; + if (rndis_active(dev)) + size += sizeof(struct rndis_packet_msg_type); + size -= size % dev->out_ep->maxpacket; + + /* + * Some platforms perform better when IP packets are aligned, + * but on at least one, checksumming fails otherwise. Note: + * RNDIS headers involve variable numbers of LE32 values. + */ + + req->buf = (u8 *)net_rx_packets[0]; + req->length = size; + req->complete = rx_complete; + + retval = usb_ep_queue(dev->out_ep, req, gfp_flags); + + if (retval) + pr_err("rx submit --> %d", retval); + + return retval; +} + +static void rx_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct eth_dev *dev = ep->driver_data; + + debug("%s: status %d\n", __func__, req->status); + switch (req->status) { + /* normal completion */ + case 0: + if (rndis_active(dev)) { + /* we know MaxPacketsPerTransfer == 1 here */ + int length = rndis_rm_hdr(req->buf, req->actual); + if (length < 0) + goto length_err; + req->length -= length; + req->actual -= length; + } + if (req->actual < ETH_HLEN || PKTSIZE_ALIGN < req->actual) { +length_err: + dev->stats.rx_errors++; + dev->stats.rx_length_errors++; + debug("rx length %d\n", req->length); + break; + } + + dev->stats.rx_packets++; + dev->stats.rx_bytes += req->length; + break; + + /* software-driven interface shutdown */ + case -ECONNRESET: /* unlink */ + case -ESHUTDOWN: /* disconnect etc */ + /* for hardware automagic (such as pxa) */ + case -ECONNABORTED: /* endpoint reset */ + break; + + /* data overrun */ + case -EOVERFLOW: + dev->stats.rx_over_errors++; + /* FALLTHROUGH */ + default: + dev->stats.rx_errors++; + break; + } + + packet_received = 1; +} + +static int alloc_requests(struct eth_dev *dev, unsigned n, gfp_t gfp_flags) +{ + + dev->tx_req = usb_ep_alloc_request(dev->in_ep, 0); + + if (!dev->tx_req) + goto fail1; + + dev->rx_req = usb_ep_alloc_request(dev->out_ep, 0); + + if (!dev->rx_req) + goto fail2; + + return 0; + +fail2: + usb_ep_free_request(dev->in_ep, dev->tx_req); +fail1: + pr_err("can't alloc requests"); + return -1; +} + +static void tx_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct eth_dev *dev = ep->driver_data; + + debug("%s: status %s\n", __func__, (req->status) ? "failed" : "ok"); + switch (req->status) { + default: + dev->stats.tx_errors++; + debug("tx err %d\n", req->status); + /* FALLTHROUGH */ + case -ECONNRESET: /* unlink */ + case -ESHUTDOWN: /* disconnect etc */ + break; + case 0: + dev->stats.tx_bytes += req->length; + } + dev->stats.tx_packets++; + + packet_sent = 1; +} + +static inline int eth_is_promisc(struct eth_dev *dev) +{ + /* no filters for the CDC subset; always promisc */ + if (subset_active(dev)) + return 1; + return dev->cdc_filter & USB_CDC_PACKET_TYPE_PROMISCUOUS; +} + +#if 0 +static int eth_start_xmit (struct sk_buff *skb, struct net_device *net) +{ + struct eth_dev *dev = netdev_priv(net); + int length = skb->len; + int retval; + struct usb_request *req = NULL; + unsigned long flags; + + /* apply outgoing CDC or RNDIS filters */ + if (!eth_is_promisc (dev)) { + u8 *dest = skb->data; + + if (is_multicast_ethaddr(dest)) { + u16 type; + + /* ignores USB_CDC_PACKET_TYPE_MULTICAST and host + * SET_ETHERNET_MULTICAST_FILTERS requests + */ + if (is_broadcast_ethaddr(dest)) + type = USB_CDC_PACKET_TYPE_BROADCAST; + else + type = USB_CDC_PACKET_TYPE_ALL_MULTICAST; + if (!(dev->cdc_filter & type)) { + dev_kfree_skb_any (skb); + return 0; + } + } + /* ignores USB_CDC_PACKET_TYPE_DIRECTED */ + } + + spin_lock_irqsave(&dev->req_lock, flags); + /* + * this freelist can be empty if an interrupt triggered disconnect() + * and reconfigured the gadget (shutting down this queue) after the + * network stack decided to xmit but before we got the spinlock. + */ + if (list_empty(&dev->tx_reqs)) { + spin_unlock_irqrestore(&dev->req_lock, flags); + return 1; + } + + req = container_of (dev->tx_reqs.next, struct usb_request, list); + list_del (&req->list); + + /* temporarily stop TX queue when the freelist empties */ + if (list_empty (&dev->tx_reqs)) + netif_stop_queue (net); + spin_unlock_irqrestore(&dev->req_lock, flags); + + /* no buffer copies needed, unless the network stack did it + * or the hardware can't use skb buffers. + * or there's not enough space for any RNDIS headers we need + */ + if (rndis_active(dev)) { + struct sk_buff *skb_rndis; + + skb_rndis = skb_realloc_headroom (skb, + sizeof (struct rndis_packet_msg_type)); + if (!skb_rndis) + goto drop; + + dev_kfree_skb_any (skb); + skb = skb_rndis; + rndis_add_hdr (skb); + length = skb->len; + } + req->buf = skb->data; + req->context = skb; + req->complete = tx_complete; + + /* use zlp framing on tx for strict CDC-Ether conformance, + * though any robust network rx path ignores extra padding. + * and some hardware doesn't like to write zlps. + */ + req->zero = 1; + if (!dev->zlp && (length % dev->in_ep->maxpacket) == 0) + length++; + + req->length = length; + + /* throttle highspeed IRQ rate back slightly */ + if (gadget_is_dualspeed(dev->gadget)) + req->no_interrupt = (dev->gadget->speed == USB_SPEED_HIGH) + ? ((atomic_read(&dev->tx_qlen) % qmult) != 0) + : 0; + + retval = usb_ep_queue (dev->in_ep, req, GFP_ATOMIC); + switch (retval) { + default: + DEBUG (dev, "tx queue err %d\n", retval); + break; + case 0: + net->trans_start = jiffies; + atomic_inc (&dev->tx_qlen); + } + + if (retval) { +drop: + dev->stats.tx_dropped++; + dev_kfree_skb_any (skb); + spin_lock_irqsave(&dev->req_lock, flags); + if (list_empty (&dev->tx_reqs)) + netif_start_queue (net); + list_add (&req->list, &dev->tx_reqs); + spin_unlock_irqrestore(&dev->req_lock, flags); + } + return 0; +} + +/*-------------------------------------------------------------------------*/ +#endif + +static void eth_unbind(struct usb_gadget *gadget) +{ + struct eth_dev *dev = get_gadget_data(gadget); + + debug("%s...\n", __func__); + rndis_deregister(dev->rndis_config); + rndis_exit(); + + /* we've already been disconnected ... no i/o is active */ + if (dev->req) { + usb_ep_free_request(gadget->ep0, dev->req); + dev->req = NULL; + } + if (dev->stat_req) { + usb_ep_free_request(dev->status_ep, dev->stat_req); + dev->stat_req = NULL; + } + + if (dev->tx_req) { + usb_ep_free_request(dev->in_ep, dev->tx_req); + dev->tx_req = NULL; + } + + if (dev->rx_req) { + usb_ep_free_request(dev->out_ep, dev->rx_req); + dev->rx_req = NULL; + } + +/* unregister_netdev (dev->net);*/ +/* free_netdev(dev->net);*/ + + dev->gadget = NULL; + set_gadget_data(gadget, NULL); +} + +static void eth_disconnect(struct usb_gadget *gadget) +{ + eth_reset_config(get_gadget_data(gadget)); + /* FIXME RNDIS should enter RNDIS_UNINITIALIZED */ +} + +static void eth_suspend(struct usb_gadget *gadget) +{ + /* Not used */ +} + +static void eth_resume(struct usb_gadget *gadget) +{ + /* Not used */ +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_USB_ETH_RNDIS + +/* + * The interrupt endpoint is used in RNDIS to notify the host when messages + * other than data packets are available ... notably the REMOTE_NDIS_*_CMPLT + * messages, but also REMOTE_NDIS_INDICATE_STATUS_MSG and potentially even + * REMOTE_NDIS_KEEPALIVE_MSG. + * + * The RNDIS control queue is processed by GET_ENCAPSULATED_RESPONSE, and + * normally just one notification will be queued. + */ + +static void rndis_control_ack_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct eth_dev *dev = ep->driver_data; + + debug("%s...\n", __func__); + if (req->status || req->actual != req->length) + debug("rndis control ack complete --> %d, %d/%d\n", + req->status, req->actual, req->length); + + if (!dev->network_started) { + if (rndis_get_state(dev->rndis_config) + == RNDIS_DATA_INITIALIZED) { + dev->network_started = 1; + printf("USB RNDIS network up!\n"); + } + } + + req->context = NULL; + + if (req != dev->stat_req) + usb_ep_free_request(ep, req); +} + +static char rndis_resp_buf[8] __attribute__((aligned(sizeof(__le32)))); + +#ifndef CONFIG_DM_ETH +static int rndis_control_ack(struct eth_device *net) +#else +static int rndis_control_ack(struct udevice *net) +#endif +{ + struct ether_priv *priv; + struct eth_dev *dev; + int length; + struct usb_request *resp; + +#ifndef CONFIG_DM_ETH + priv = (struct ether_priv *)net->priv; +#else + priv = dev_get_priv(net); +#endif + dev = &priv->ethdev; + resp = dev->stat_req; + + /* in case RNDIS calls this after disconnect */ + if (!dev->status) { + debug("status ENODEV\n"); + return -ENODEV; + } + + /* in case queue length > 1 */ + if (resp->context) { + resp = usb_ep_alloc_request(dev->status_ep, GFP_ATOMIC); + if (!resp) + return -ENOMEM; + resp->buf = rndis_resp_buf; + } + + /* + * Send RNDIS RESPONSE_AVAILABLE notification; + * USB_CDC_NOTIFY_RESPONSE_AVAILABLE should work too + */ + resp->length = 8; + resp->complete = rndis_control_ack_complete; + resp->context = dev; + + *((__le32 *) resp->buf) = __constant_cpu_to_le32(1); + *((__le32 *) (resp->buf + 4)) = __constant_cpu_to_le32(0); + + length = usb_ep_queue(dev->status_ep, resp, GFP_ATOMIC); + if (length < 0) { + resp->status = 0; + rndis_control_ack_complete(dev->status_ep, resp); + } + + return 0; +} + +#else + +#define rndis_control_ack NULL + +#endif /* RNDIS */ + +static void eth_start(struct eth_dev *dev, gfp_t gfp_flags) +{ + if (rndis_active(dev)) { + rndis_set_param_medium(dev->rndis_config, + NDIS_MEDIUM_802_3, + BITRATE(dev->gadget)/100); + rndis_signal_connect(dev->rndis_config); + } +} + +static int eth_stop(struct eth_dev *dev) +{ +#ifdef RNDIS_COMPLETE_SIGNAL_DISCONNECT + unsigned long ts; + unsigned long timeout = CONFIG_SYS_HZ; /* 1 sec to stop RNDIS */ +#endif + + if (rndis_active(dev)) { + rndis_set_param_medium(dev->rndis_config, NDIS_MEDIUM_802_3, 0); + rndis_signal_disconnect(dev->rndis_config); + +#ifdef RNDIS_COMPLETE_SIGNAL_DISCONNECT + /* Wait until host receives OID_GEN_MEDIA_CONNECT_STATUS */ + ts = get_timer(0); + while (get_timer(ts) < timeout) + usb_gadget_handle_interrupts(0); +#endif + + rndis_uninit(dev->rndis_config); + dev->rndis = 0; + } + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static int is_eth_addr_valid(char *str) +{ + if (strlen(str) == 17) { + int i; + char *p, *q; + uchar ea[6]; + + /* see if it looks like an ethernet address */ + + p = str; + + for (i = 0; i < 6; i++) { + char term = (i == 5 ? '\0' : ':'); + + ea[i] = simple_strtol(p, &q, 16); + + if ((q - p) != 2 || *q++ != term) + break; + + p = q; + } + + /* Now check the contents. */ + return is_valid_ethaddr(ea); + } + return 0; +} + +static u8 nibble(unsigned char c) +{ + if (likely(isdigit(c))) + return c - '0'; + c = toupper(c); + if (likely(isxdigit(c))) + return 10 + c - 'A'; + return 0; +} + +static int get_ether_addr(const char *str, u8 *dev_addr) +{ + if (str) { + unsigned i; + + for (i = 0; i < 6; i++) { + unsigned char num; + + if ((*str == '.') || (*str == ':')) + str++; + num = nibble(*str++) << 4; + num |= (nibble(*str++)); + dev_addr[i] = num; + } + if (is_valid_ethaddr(dev_addr)) + return 0; + } + return 1; +} + +static int eth_bind(struct usb_gadget *gadget) +{ + struct eth_dev *dev = &l_priv->ethdev; + u8 cdc = 1, zlp = 1, rndis = 1; + struct usb_ep *in_ep, *out_ep, *status_ep = NULL; + int status = -ENOMEM; + int gcnum; + u8 tmp[7]; +#ifdef CONFIG_DM_ETH + struct eth_pdata *pdata = dev_get_plat(l_priv->netdev); +#endif + + /* these flags are only ever cleared; compiler take note */ +#ifndef CONFIG_USB_ETH_CDC + cdc = 0; +#endif +#ifndef CONFIG_USB_ETH_RNDIS + rndis = 0; +#endif + /* + * Because most host side USB stacks handle CDC Ethernet, that + * standard protocol is _strongly_ preferred for interop purposes. + * (By everyone except Microsoft.) + */ + if (gadget_is_pxa(gadget)) { + /* pxa doesn't support altsettings */ + cdc = 0; + } else if (gadget_is_musbhdrc(gadget)) { + /* reduce tx dma overhead by avoiding special cases */ + zlp = 0; + } else if (gadget_is_sh(gadget)) { + /* sh doesn't support multiple interfaces or configs */ + cdc = 0; + rndis = 0; + } else if (gadget_is_sa1100(gadget)) { + /* hardware can't write zlps */ + zlp = 0; + /* + * sa1100 CAN do CDC, without status endpoint ... we use + * non-CDC to be compatible with ARM Linux-2.4 "usb-eth". + */ + cdc = 0; + } + + gcnum = usb_gadget_controller_number(gadget); + if (gcnum >= 0) + device_desc.bcdDevice = cpu_to_le16(0x0300 + gcnum); + else { + /* + * can't assume CDC works. don't want to default to + * anything less functional on CDC-capable hardware, + * so we fail in this case. + */ + pr_err("controller '%s' not recognized", + gadget->name); + return -ENODEV; + } + + /* + * If there's an RNDIS configuration, that's what Windows wants to + * be using ... so use these product IDs here and in the "linux.inf" + * needed to install MSFT drivers. Current Linux kernels will use + * the second configuration if it's CDC Ethernet, and need some help + * to choose the right configuration otherwise. + */ + if (rndis) { +#if defined(CONFIG_USB_GADGET_VENDOR_NUM) && defined(CONFIG_USB_GADGET_PRODUCT_NUM) + device_desc.idVendor = + __constant_cpu_to_le16(CONFIG_USB_GADGET_VENDOR_NUM); + device_desc.idProduct = + __constant_cpu_to_le16(CONFIG_USB_GADGET_PRODUCT_NUM); +#else + device_desc.idVendor = + __constant_cpu_to_le16(RNDIS_VENDOR_NUM); + device_desc.idProduct = + __constant_cpu_to_le16(RNDIS_PRODUCT_NUM); +#endif + sprintf(product_desc, "RNDIS/%s", driver_desc); + + /* + * CDC subset ... recognized by Linux since 2.4.10, but Windows + * drivers aren't widely available. (That may be improved by + * supporting one submode of the "SAFE" variant of MDLM.) + */ + } else { +#if defined(CONFIG_USB_GADGET_VENDOR_NUM) && defined(CONFIG_USB_GADGET_PRODUCT_NUM) + device_desc.idVendor = cpu_to_le16(CONFIG_USB_GADGET_VENDOR_NUM); + device_desc.idProduct = cpu_to_le16(CONFIG_USB_GADGET_PRODUCT_NUM); +#else + if (!cdc) { + device_desc.idVendor = + __constant_cpu_to_le16(SIMPLE_VENDOR_NUM); + device_desc.idProduct = + __constant_cpu_to_le16(SIMPLE_PRODUCT_NUM); + } +#endif + } + /* support optional vendor/distro customization */ + if (bcdDevice) + device_desc.bcdDevice = cpu_to_le16(bcdDevice); + if (iManufacturer) + strlcpy(manufacturer, iManufacturer, sizeof manufacturer); + if (iProduct) + strlcpy(product_desc, iProduct, sizeof product_desc); + if (iSerialNumber) { + device_desc.iSerialNumber = STRING_SERIALNUMBER, + strlcpy(serial_number, iSerialNumber, sizeof serial_number); + } + + /* all we really need is bulk IN/OUT */ + usb_ep_autoconfig_reset(gadget); + in_ep = usb_ep_autoconfig(gadget, &fs_source_desc); + if (!in_ep) { +autoconf_fail: + pr_err("can't autoconfigure on %s\n", + gadget->name); + return -ENODEV; + } + in_ep->driver_data = in_ep; /* claim */ + + out_ep = usb_ep_autoconfig(gadget, &fs_sink_desc); + if (!out_ep) + goto autoconf_fail; + out_ep->driver_data = out_ep; /* claim */ + +#if defined(CONFIG_USB_ETH_CDC) || defined(CONFIG_USB_ETH_RNDIS) + /* + * CDC Ethernet control interface doesn't require a status endpoint. + * Since some hosts expect one, try to allocate one anyway. + */ + if (cdc || rndis) { + status_ep = usb_ep_autoconfig(gadget, &fs_status_desc); + if (status_ep) { + status_ep->driver_data = status_ep; /* claim */ + } else if (rndis) { + pr_err("can't run RNDIS on %s", gadget->name); + return -ENODEV; +#ifdef CONFIG_USB_ETH_CDC + } else if (cdc) { + control_intf.bNumEndpoints = 0; + /* FIXME remove endpoint from descriptor list */ +#endif + } + } +#endif + + /* one config: cdc, else minimal subset */ + if (!cdc) { + eth_config.bNumInterfaces = 1; + eth_config.iConfiguration = STRING_SUBSET; + + /* + * use functions to set these up, in case we're built to work + * with multiple controllers and must override CDC Ethernet. + */ + fs_subset_descriptors(); + hs_subset_descriptors(); + } + + usb_gadget_set_selfpowered(gadget); + + /* For now RNDIS is always a second config */ + if (rndis) + device_desc.bNumConfigurations = 2; + + if (gadget_is_dualspeed(gadget)) { + if (rndis) + dev_qualifier.bNumConfigurations = 2; + else if (!cdc) + dev_qualifier.bDeviceClass = USB_CLASS_VENDOR_SPEC; + + /* assumes ep0 uses the same value for both speeds ... */ + dev_qualifier.bMaxPacketSize0 = device_desc.bMaxPacketSize0; + + /* and that all endpoints are dual-speed */ + hs_source_desc.bEndpointAddress = + fs_source_desc.bEndpointAddress; + hs_sink_desc.bEndpointAddress = + fs_sink_desc.bEndpointAddress; +#if defined(CONFIG_USB_ETH_CDC) || defined(CONFIG_USB_ETH_RNDIS) + if (status_ep) + hs_status_desc.bEndpointAddress = + fs_status_desc.bEndpointAddress; +#endif + } + + if (gadget_is_otg(gadget)) { + otg_descriptor.bmAttributes |= USB_OTG_HNP, + eth_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP; + eth_config.bMaxPower = 4; +#ifdef CONFIG_USB_ETH_RNDIS + rndis_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP; + rndis_config.bMaxPower = 4; +#endif + } + + + /* network device setup */ +#ifndef CONFIG_DM_ETH + dev->net = &l_priv->netdev; +#else + dev->net = l_priv->netdev; +#endif + + dev->cdc = cdc; + dev->zlp = zlp; + + dev->in_ep = in_ep; + dev->out_ep = out_ep; + dev->status_ep = status_ep; + + memset(tmp, 0, sizeof(tmp)); + /* + * Module params for these addresses should come from ID proms. + * The host side address is used with CDC and RNDIS, and commonly + * ends up in a persistent config database. It's not clear if + * host side code for the SAFE thing cares -- its original BLAN + * thing didn't, Sharp never assigned those addresses on Zaurii. + */ +#ifndef CONFIG_DM_ETH + get_ether_addr(dev_addr, dev->net->enetaddr); + memcpy(tmp, dev->net->enetaddr, sizeof(dev->net->enetaddr)); +#else + get_ether_addr(dev_addr, pdata->enetaddr); + memcpy(tmp, pdata->enetaddr, sizeof(pdata->enetaddr)); +#endif + + get_ether_addr(host_addr, dev->host_mac); + + sprintf(ethaddr, "%02X%02X%02X%02X%02X%02X", + dev->host_mac[0], dev->host_mac[1], + dev->host_mac[2], dev->host_mac[3], + dev->host_mac[4], dev->host_mac[5]); + + if (rndis) { + status = rndis_init(); + if (status < 0) { + pr_err("can't init RNDIS, %d", status); + goto fail; + } + } + + /* + * use PKTSIZE (or aligned... from u-boot) and set + * wMaxSegmentSize accordingly + */ + dev->mtu = PKTSIZE_ALIGN; /* RNDIS does not like this, only 1514, TODO*/ + + /* preallocate control message data and buffer */ + dev->req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL); + if (!dev->req) + goto fail; + dev->req->buf = control_req; + dev->req->complete = eth_setup_complete; + + /* ... and maybe likewise for status transfer */ +#if defined(CONFIG_USB_ETH_CDC) || defined(CONFIG_USB_ETH_RNDIS) + if (dev->status_ep) { + dev->stat_req = usb_ep_alloc_request(dev->status_ep, + GFP_KERNEL); + if (!dev->stat_req) { + usb_ep_free_request(dev->status_ep, dev->req); + + goto fail; + } + dev->stat_req->buf = status_req; + dev->stat_req->context = NULL; + } +#endif + + /* finish hookup to lower layer ... */ + dev->gadget = gadget; + set_gadget_data(gadget, dev); + gadget->ep0->driver_data = dev; + + /* + * two kinds of host-initiated state changes: + * - iff DATA transfer is active, carrier is "on" + * - tx queueing enabled if open *and* carrier is "on" + */ + + printf("using %s, OUT %s IN %s%s%s\n", gadget->name, + out_ep->name, in_ep->name, + status_ep ? " STATUS " : "", + status_ep ? status_ep->name : "" + ); +#ifndef CONFIG_DM_ETH + printf("MAC %pM\n", dev->net->enetaddr); +#else + printf("MAC %pM\n", pdata->enetaddr); +#endif + + if (cdc || rndis) + printf("HOST MAC %02x:%02x:%02x:%02x:%02x:%02x\n", + dev->host_mac[0], dev->host_mac[1], + dev->host_mac[2], dev->host_mac[3], + dev->host_mac[4], dev->host_mac[5]); + + if (rndis) { + u32 vendorID = 0; + + /* FIXME RNDIS vendor id == "vendor NIC code" == ? */ + + dev->rndis_config = rndis_register(rndis_control_ack); + if (dev->rndis_config < 0) { +fail0: + eth_unbind(gadget); + debug("RNDIS setup failed\n"); + status = -ENODEV; + goto fail; + } + + /* these set up a lot of the OIDs that RNDIS needs */ + rndis_set_host_mac(dev->rndis_config, dev->host_mac); + if (rndis_set_param_dev(dev->rndis_config, dev->net, dev->mtu, + &dev->stats, &dev->cdc_filter)) + goto fail0; + if (rndis_set_param_vendor(dev->rndis_config, vendorID, + manufacturer)) + goto fail0; + if (rndis_set_param_medium(dev->rndis_config, + NDIS_MEDIUM_802_3, 0)) + goto fail0; + printf("RNDIS ready\n"); + } + return 0; + +fail: + pr_err("%s failed, status = %d", __func__, status); + eth_unbind(gadget); + return status; +} + +/*-------------------------------------------------------------------------*/ +static void _usb_eth_halt(struct ether_priv *priv); + +static int _usb_eth_init(struct ether_priv *priv) +{ + struct eth_dev *dev = &priv->ethdev; + struct usb_gadget *gadget; + unsigned long ts; + int ret; + unsigned long timeout = USB_CONNECT_TIMEOUT; + + ret = usb_gadget_initialize(0); + if (ret) + return ret; + + /* Configure default mac-addresses for the USB ethernet device */ +#ifdef CONFIG_USBNET_DEV_ADDR + strlcpy(dev_addr, CONFIG_USBNET_DEV_ADDR, sizeof(dev_addr)); +#endif +#ifdef CONFIG_USBNET_HOST_ADDR + strlcpy(host_addr, CONFIG_USBNET_HOST_ADDR, sizeof(host_addr)); +#endif + /* Check if the user overruled the MAC addresses */ + if (env_get("usbnet_devaddr")) + strlcpy(dev_addr, env_get("usbnet_devaddr"), + sizeof(dev_addr)); + + if (env_get("usbnet_hostaddr")) + strlcpy(host_addr, env_get("usbnet_hostaddr"), + sizeof(host_addr)); + + if (!is_eth_addr_valid(dev_addr)) { + pr_err("Need valid 'usbnet_devaddr' to be set"); + goto fail; + } + if (!is_eth_addr_valid(host_addr)) { + pr_err("Need valid 'usbnet_hostaddr' to be set"); + goto fail; + } + + priv->eth_driver.speed = DEVSPEED; + priv->eth_driver.bind = eth_bind; + priv->eth_driver.unbind = eth_unbind; + priv->eth_driver.setup = eth_setup; + priv->eth_driver.reset = eth_disconnect; + priv->eth_driver.disconnect = eth_disconnect; + priv->eth_driver.suspend = eth_suspend; + priv->eth_driver.resume = eth_resume; + if (usb_gadget_register_driver(&priv->eth_driver) < 0) + goto fail; + + dev->network_started = 0; + + packet_received = 0; + packet_sent = 0; + + gadget = dev->gadget; + usb_gadget_connect(gadget); + + if (env_get("cdc_connect_timeout")) + timeout = simple_strtoul(env_get("cdc_connect_timeout"), + NULL, 10) * CONFIG_SYS_HZ; + ts = get_timer(0); + while (!dev->network_started) { + /* Handle control-c and timeouts */ + if (ctrlc() || (get_timer(ts) > timeout)) { + pr_err("The remote end did not respond in time."); + goto fail; + } + usb_gadget_handle_interrupts(0); + } + + packet_received = 0; + rx_submit(dev, dev->rx_req, 0); + return 0; +fail: + _usb_eth_halt(priv); + return -1; +} + +static int _usb_eth_send(struct ether_priv *priv, void *packet, int length) +{ + int retval; + void *rndis_pkt = NULL; + struct eth_dev *dev = &priv->ethdev; + struct usb_request *req = dev->tx_req; + unsigned long ts; + unsigned long timeout = USB_CONNECT_TIMEOUT; + + debug("%s:...\n", __func__); + + /* new buffer is needed to include RNDIS header */ + if (rndis_active(dev)) { + rndis_pkt = malloc(length + + sizeof(struct rndis_packet_msg_type)); + if (!rndis_pkt) { + pr_err("No memory to alloc RNDIS packet"); + goto drop; + } + rndis_add_hdr(rndis_pkt, length); + memcpy(rndis_pkt + sizeof(struct rndis_packet_msg_type), + packet, length); + packet = rndis_pkt; + length += sizeof(struct rndis_packet_msg_type); + } + req->buf = packet; + req->context = NULL; + req->complete = tx_complete; + + /* + * use zlp framing on tx for strict CDC-Ether conformance, + * though any robust network rx path ignores extra padding. + * and some hardware doesn't like to write zlps. + */ + req->zero = 1; + if (!dev->zlp && (length % dev->in_ep->maxpacket) == 0) + length++; + + req->length = length; +#if 0 + /* throttle highspeed IRQ rate back slightly */ + if (gadget_is_dualspeed(dev->gadget)) + req->no_interrupt = (dev->gadget->speed == USB_SPEED_HIGH) + ? ((dev->tx_qlen % qmult) != 0) : 0; +#endif + dev->tx_qlen = 1; + ts = get_timer(0); + packet_sent = 0; + + retval = usb_ep_queue(dev->in_ep, req, GFP_ATOMIC); + + if (!retval) + debug("%s: packet queued\n", __func__); + while (!packet_sent) { + if (get_timer(ts) > timeout) { + printf("timeout sending packets to usb ethernet\n"); + return -1; + } + usb_gadget_handle_interrupts(0); + } + free(rndis_pkt); + + return 0; +drop: + dev->stats.tx_dropped++; + return -ENOMEM; +} + +static int _usb_eth_recv(struct ether_priv *priv) +{ + usb_gadget_handle_interrupts(0); + + return 0; +} + +static void _usb_eth_halt(struct ether_priv *priv) +{ + struct eth_dev *dev = &priv->ethdev; + + /* If the gadget not registered, simple return */ + if (!dev->gadget) + return; + + /* + * Some USB controllers may need additional deinitialization here + * before dropping pull-up (also due to hardware issues). + * For example: unhandled interrupt with status stage started may + * bring the controller to fully broken state (until board reset). + * There are some variants to debug and fix such cases: + * 1) In the case of RNDIS connection eth_stop can perform additional + * interrupt handling. See RNDIS_COMPLETE_SIGNAL_DISCONNECT definition. + * 2) 'pullup' callback in your UDC driver can be improved to perform + * this deinitialization. + */ + eth_stop(dev); + + usb_gadget_disconnect(dev->gadget); + + /* Clear pending interrupt */ + if (dev->network_started) { + usb_gadget_handle_interrupts(0); + dev->network_started = 0; + } + + usb_gadget_unregister_driver(&priv->eth_driver); + usb_gadget_release(0); +} + +#ifndef CONFIG_DM_ETH +static int usb_eth_init(struct eth_device *netdev, struct bd_info *bd) +{ + struct ether_priv *priv = (struct ether_priv *)netdev->priv; + + return _usb_eth_init(priv); +} + +static int usb_eth_send(struct eth_device *netdev, void *packet, int length) +{ + struct ether_priv *priv = (struct ether_priv *)netdev->priv; + + return _usb_eth_send(priv, packet, length); +} + +static int usb_eth_recv(struct eth_device *netdev) +{ + struct ether_priv *priv = (struct ether_priv *)netdev->priv; + struct eth_dev *dev = &priv->ethdev; + int ret; + + ret = _usb_eth_recv(priv); + if (ret) { + pr_err("error packet receive\n"); + return ret; + } + + if (!packet_received) + return 0; + + if (dev->rx_req) { + net_process_received_packet(net_rx_packets[0], + dev->rx_req->length); + } else { + pr_err("dev->rx_req invalid"); + } + packet_received = 0; + rx_submit(dev, dev->rx_req, 0); + + return 0; +} + +void usb_eth_halt(struct eth_device *netdev) +{ + struct ether_priv *priv = (struct ether_priv *)netdev->priv; + + _usb_eth_halt(priv); +} + +int usb_eth_initialize(struct bd_info *bi) +{ + struct eth_device *netdev = &l_priv->netdev; + + strlcpy(netdev->name, USB_NET_NAME, sizeof(netdev->name)); + + netdev->init = usb_eth_init; + netdev->send = usb_eth_send; + netdev->recv = usb_eth_recv; + netdev->halt = usb_eth_halt; + netdev->priv = l_priv; + + eth_register(netdev); + return 0; +} +#else +static int usb_eth_start(struct udevice *dev) +{ + struct ether_priv *priv = dev_get_priv(dev); + + return _usb_eth_init(priv); +} + +static int usb_eth_send(struct udevice *dev, void *packet, int length) +{ + struct ether_priv *priv = dev_get_priv(dev); + + return _usb_eth_send(priv, packet, length); +} + +static int usb_eth_recv(struct udevice *dev, int flags, uchar **packetp) +{ + struct ether_priv *priv = dev_get_priv(dev); + struct eth_dev *ethdev = &priv->ethdev; + int ret; + + ret = _usb_eth_recv(priv); + if (ret) { + pr_err("error packet receive\n"); + return ret; + } + + if (packet_received) { + if (ethdev->rx_req) { + *packetp = (uchar *)net_rx_packets[0]; + return ethdev->rx_req->length; + } else { + pr_err("dev->rx_req invalid"); + return -EFAULT; + } + } + + return -EAGAIN; +} + +static int usb_eth_free_pkt(struct udevice *dev, uchar *packet, + int length) +{ + struct ether_priv *priv = dev_get_priv(dev); + struct eth_dev *ethdev = &priv->ethdev; + + packet_received = 0; + + return rx_submit(ethdev, ethdev->rx_req, 0); +} + +static void usb_eth_stop(struct udevice *dev) +{ + struct ether_priv *priv = dev_get_priv(dev); + + _usb_eth_halt(priv); +} + +static int usb_eth_probe(struct udevice *dev) +{ + struct ether_priv *priv = dev_get_priv(dev); + struct eth_pdata *pdata = dev_get_plat(dev); + + priv->netdev = dev; + l_priv = priv; + + get_ether_addr(CONFIG_USBNET_DEVADDR, pdata->enetaddr); + eth_env_set_enetaddr("usbnet_devaddr", pdata->enetaddr); + + return 0; +} + +static const struct eth_ops usb_eth_ops = { + .start = usb_eth_start, + .send = usb_eth_send, + .recv = usb_eth_recv, + .free_pkt = usb_eth_free_pkt, + .stop = usb_eth_stop, +}; + +int usb_ether_init(void) +{ + struct udevice *dev; + struct udevice *usb_dev; + int ret; + + ret = uclass_first_device(UCLASS_USB_GADGET_GENERIC, &usb_dev); + if (!usb_dev || ret) { + pr_err("No USB device found\n"); + return ret; + } + + ret = device_bind_driver(usb_dev, "usb_ether", "usb_ether", &dev); + if (!dev || ret) { + pr_err("usb - not able to bind usb_ether device\n"); + return ret; + } + + return 0; +} + +U_BOOT_DRIVER(eth_usb) = { + .name = "usb_ether", + .id = UCLASS_ETH, + .probe = usb_eth_probe, + .ops = &usb_eth_ops, + .priv_auto = sizeof(struct ether_priv), + .plat_auto = sizeof(struct eth_pdata), + .flags = DM_FLAG_ALLOC_PRIV_DMA, +}; +#endif /* CONFIG_DM_ETH */ diff --git a/roms/u-boot/drivers/usb/gadget/f_dfu.c b/roms/u-boot/drivers/usb/gadget/f_dfu.c new file mode 100644 index 000000000..4bedc7d3a --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/f_dfu.c @@ -0,0 +1,844 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * f_dfu.c -- Device Firmware Update USB function + * + * Copyright (C) 2012 Samsung Electronics + * authors: Andrzej Pietrasiewicz <andrzej.p@samsung.com> + * Lukasz Majewski <l.majewski@samsung.com> + * + * Based on OpenMoko u-boot: drivers/usb/usbdfu.c + * (C) 2007 by OpenMoko, Inc. + * Author: Harald Welte <laforge@openmoko.org> + * + * based on existing SAM7DFU code from OpenPCD: + * (C) Copyright 2006 by Harald Welte <hwelte at hmw-consulting.de> + */ + +#include <env.h> +#include <errno.h> +#include <common.h> +#include <log.h> +#include <malloc.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb/composite.h> + +#include <dfu.h> +#include <g_dnl.h> +#include "f_dfu.h" + +struct f_dfu { + struct usb_function usb_function; + + struct usb_descriptor_header **function; + struct usb_string *strings; + + /* when configured, we have one config */ + u8 config; + u8 altsetting; + enum dfu_state dfu_state; + unsigned int dfu_status; + + /* Send/received block number is handy for data integrity check */ + int blk_seq_num; + unsigned int poll_timeout; +}; + +struct dfu_entity *dfu_defer_flush; + +typedef int (*dfu_state_fn) (struct f_dfu *, + const struct usb_ctrlrequest *, + struct usb_gadget *, + struct usb_request *); + +static inline struct f_dfu *func_to_dfu(struct usb_function *f) +{ + return container_of(f, struct f_dfu, usb_function); +} + +static const struct dfu_function_descriptor dfu_func = { + .bLength = sizeof dfu_func, + .bDescriptorType = DFU_DT_FUNC, + .bmAttributes = DFU_BIT_WILL_DETACH | + DFU_BIT_MANIFESTATION_TOLERANT | + DFU_BIT_CAN_UPLOAD | + DFU_BIT_CAN_DNLOAD, + .wDetachTimeOut = 0, + .wTransferSize = DFU_USB_BUFSIZ, + .bcdDFUVersion = __constant_cpu_to_le16(0x0110), +}; + +static struct usb_interface_descriptor dfu_intf_runtime = { + .bLength = sizeof dfu_intf_runtime, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_APP_SPEC, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 1, + /* .iInterface = DYNAMIC */ +}; + +static struct usb_descriptor_header *dfu_runtime_descs[] = { + (struct usb_descriptor_header *) &dfu_intf_runtime, + NULL, +}; + +static const char dfu_name[] = "Device Firmware Upgrade"; + +/* + * static strings, in UTF-8 + * + * dfu_generic configuration + */ +static struct usb_string strings_dfu_generic[] = { + [0].s = dfu_name, + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dfu_generic = { + .language = 0x0409, /* en-us */ + .strings = strings_dfu_generic, +}; + +static struct usb_gadget_strings *dfu_generic_strings[] = { + &stringtab_dfu_generic, + NULL, +}; + +/* + * usb_function specific + */ +static struct usb_gadget_strings stringtab_dfu = { + .language = 0x0409, /* en-us */ + /* + * .strings + * + * assigned during initialization, + * depends on number of flash entities + * + */ +}; + +static struct usb_gadget_strings *dfu_strings[] = { + &stringtab_dfu, + NULL, +}; + +static void dfu_set_poll_timeout(struct dfu_status *dstat, unsigned int ms) +{ + /* + * The bwPollTimeout DFU_GETSTATUS request payload provides information + * about minimum time, in milliseconds, that the host should wait before + * sending a subsequent DFU_GETSTATUS request + * + * This permits the device to vary the delay depending on its need to + * erase or program the memory + * + */ + + unsigned char *p = (unsigned char *)&ms; + + if (!ms || (ms & ~DFU_POLL_TIMEOUT_MASK)) { + dstat->bwPollTimeout[0] = 0; + dstat->bwPollTimeout[1] = 0; + dstat->bwPollTimeout[2] = 0; + + return; + } + + dstat->bwPollTimeout[0] = *p++; + dstat->bwPollTimeout[1] = *p++; + dstat->bwPollTimeout[2] = *p; +} + +/*-------------------------------------------------------------------------*/ + +static void dnload_request_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_dfu *f_dfu = req->context; + int ret; + + ret = dfu_write(dfu_get_entity(f_dfu->altsetting), req->buf, + req->actual, f_dfu->blk_seq_num); + if (ret) { + f_dfu->dfu_status = DFU_STATUS_errUNKNOWN; + f_dfu->dfu_state = DFU_STATE_dfuERROR; + } +} + +static void dnload_request_flush(struct usb_ep *ep, struct usb_request *req) +{ + struct f_dfu *f_dfu = req->context; + dfu_set_defer_flush(dfu_get_entity(f_dfu->altsetting)); +} + +static inline int dfu_get_manifest_timeout(struct dfu_entity *dfu) +{ + return dfu->poll_timeout ? dfu->poll_timeout(dfu) : + DFU_MANIFEST_POLL_TIMEOUT; +} + +static int handle_getstatus(struct usb_request *req) +{ + struct dfu_status *dstat = (struct dfu_status *)req->buf; + struct f_dfu *f_dfu = req->context; + struct dfu_entity *dfu = dfu_get_entity(f_dfu->altsetting); + + dfu_set_poll_timeout(dstat, 0); + + switch (f_dfu->dfu_state) { + case DFU_STATE_dfuDNLOAD_SYNC: + case DFU_STATE_dfuDNBUSY: + f_dfu->dfu_state = DFU_STATE_dfuDNLOAD_IDLE; + break; + case DFU_STATE_dfuMANIFEST_SYNC: + f_dfu->dfu_state = DFU_STATE_dfuMANIFEST; + break; + case DFU_STATE_dfuMANIFEST: + dfu_set_poll_timeout(dstat, dfu_get_manifest_timeout(dfu)); + break; + default: + break; + } + + if (f_dfu->poll_timeout) + if (!(f_dfu->blk_seq_num % + (dfu_get_buf_size() / DFU_USB_BUFSIZ))) + dfu_set_poll_timeout(dstat, f_dfu->poll_timeout); + + /* send status response */ + dstat->bStatus = f_dfu->dfu_status; + dstat->bState = f_dfu->dfu_state; + dstat->iString = 0; + + return sizeof(struct dfu_status); +} + +static int handle_getstate(struct usb_request *req) +{ + struct f_dfu *f_dfu = req->context; + + ((u8 *)req->buf)[0] = f_dfu->dfu_state; + return sizeof(u8); +} + +static inline void to_dfu_mode(struct f_dfu *f_dfu) +{ + f_dfu->usb_function.strings = dfu_strings; + f_dfu->usb_function.hs_descriptors = f_dfu->function; + f_dfu->usb_function.descriptors = f_dfu->function; + f_dfu->dfu_state = DFU_STATE_dfuIDLE; +} + +static inline void to_runtime_mode(struct f_dfu *f_dfu) +{ + f_dfu->usb_function.strings = NULL; + f_dfu->usb_function.hs_descriptors = dfu_runtime_descs; + f_dfu->usb_function.descriptors = dfu_runtime_descs; +} + +static int handle_upload(struct usb_request *req, u16 len) +{ + struct f_dfu *f_dfu = req->context; + + return dfu_read(dfu_get_entity(f_dfu->altsetting), req->buf, + req->length, f_dfu->blk_seq_num); +} + +static int handle_dnload(struct usb_gadget *gadget, u16 len) +{ + struct usb_composite_dev *cdev = get_gadget_data(gadget); + struct usb_request *req = cdev->req; + struct f_dfu *f_dfu = req->context; + + if (len == 0) + f_dfu->dfu_state = DFU_STATE_dfuMANIFEST_SYNC; + + req->complete = dnload_request_complete; + + return len; +} + +/*-------------------------------------------------------------------------*/ +/* DFU state machine */ +static int state_app_idle(struct f_dfu *f_dfu, + const struct usb_ctrlrequest *ctrl, + struct usb_gadget *gadget, + struct usb_request *req) +{ + int value = 0; + + switch (ctrl->bRequest) { + case USB_REQ_DFU_GETSTATUS: + value = handle_getstatus(req); + break; + case USB_REQ_DFU_GETSTATE: + value = handle_getstate(req); + break; + case USB_REQ_DFU_DETACH: + f_dfu->dfu_state = DFU_STATE_appDETACH; + to_dfu_mode(f_dfu); + value = RET_ZLP; + break; + default: + value = RET_STALL; + break; + } + + return value; +} + +static int state_app_detach(struct f_dfu *f_dfu, + const struct usb_ctrlrequest *ctrl, + struct usb_gadget *gadget, + struct usb_request *req) +{ + int value = 0; + + switch (ctrl->bRequest) { + case USB_REQ_DFU_GETSTATUS: + value = handle_getstatus(req); + break; + case USB_REQ_DFU_GETSTATE: + value = handle_getstate(req); + break; + default: + f_dfu->dfu_state = DFU_STATE_appIDLE; + value = RET_STALL; + break; + } + + return value; +} + +static int state_dfu_idle(struct f_dfu *f_dfu, + const struct usb_ctrlrequest *ctrl, + struct usb_gadget *gadget, + struct usb_request *req) +{ + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 len = le16_to_cpu(ctrl->wLength); + int value = 0; + + switch (ctrl->bRequest) { + case USB_REQ_DFU_DNLOAD: + if (len == 0) { + f_dfu->dfu_state = DFU_STATE_dfuERROR; + value = RET_STALL; + break; + } + f_dfu->dfu_state = DFU_STATE_dfuDNLOAD_SYNC; + f_dfu->blk_seq_num = w_value; + value = handle_dnload(gadget, len); + break; + case USB_REQ_DFU_UPLOAD: + f_dfu->dfu_state = DFU_STATE_dfuUPLOAD_IDLE; + f_dfu->blk_seq_num = 0; + value = handle_upload(req, len); + break; + case USB_REQ_DFU_ABORT: + /* no zlp? */ + value = RET_ZLP; + break; + case USB_REQ_DFU_GETSTATUS: + value = handle_getstatus(req); + break; + case USB_REQ_DFU_GETSTATE: + value = handle_getstate(req); + break; + case USB_REQ_DFU_DETACH: + /* + * Proprietary extension: 'detach' from idle mode and + * get back to runtime mode in case of USB Reset. As + * much as I dislike this, we just can't use every USB + * bus reset to switch back to runtime mode, since at + * least the Linux USB stack likes to send a number of + * resets in a row :( + */ + f_dfu->dfu_state = + DFU_STATE_dfuMANIFEST_WAIT_RST; + to_runtime_mode(f_dfu); + f_dfu->dfu_state = DFU_STATE_appIDLE; + + g_dnl_trigger_detach(); + break; + default: + f_dfu->dfu_state = DFU_STATE_dfuERROR; + value = RET_STALL; + break; + } + + return value; +} + +static int state_dfu_dnload_sync(struct f_dfu *f_dfu, + const struct usb_ctrlrequest *ctrl, + struct usb_gadget *gadget, + struct usb_request *req) +{ + int value = 0; + + switch (ctrl->bRequest) { + case USB_REQ_DFU_GETSTATUS: + value = handle_getstatus(req); + break; + case USB_REQ_DFU_GETSTATE: + value = handle_getstate(req); + break; + default: + f_dfu->dfu_state = DFU_STATE_dfuERROR; + value = RET_STALL; + break; + } + + return value; +} + +static int state_dfu_dnbusy(struct f_dfu *f_dfu, + const struct usb_ctrlrequest *ctrl, + struct usb_gadget *gadget, + struct usb_request *req) +{ + int value = 0; + + switch (ctrl->bRequest) { + case USB_REQ_DFU_GETSTATUS: + value = handle_getstatus(req); + break; + default: + f_dfu->dfu_state = DFU_STATE_dfuERROR; + value = RET_STALL; + break; + } + + return value; +} + +static int state_dfu_dnload_idle(struct f_dfu *f_dfu, + const struct usb_ctrlrequest *ctrl, + struct usb_gadget *gadget, + struct usb_request *req) +{ + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 len = le16_to_cpu(ctrl->wLength); + int value = 0; + + switch (ctrl->bRequest) { + case USB_REQ_DFU_DNLOAD: + f_dfu->dfu_state = DFU_STATE_dfuDNLOAD_SYNC; + f_dfu->blk_seq_num = w_value; + value = handle_dnload(gadget, len); + break; + case USB_REQ_DFU_ABORT: + f_dfu->dfu_state = DFU_STATE_dfuIDLE; + value = RET_ZLP; + break; + case USB_REQ_DFU_GETSTATUS: + value = handle_getstatus(req); + break; + case USB_REQ_DFU_GETSTATE: + value = handle_getstate(req); + break; + default: + f_dfu->dfu_state = DFU_STATE_dfuERROR; + value = RET_STALL; + break; + } + + return value; +} + +static int state_dfu_manifest_sync(struct f_dfu *f_dfu, + const struct usb_ctrlrequest *ctrl, + struct usb_gadget *gadget, + struct usb_request *req) +{ + int value = 0; + + switch (ctrl->bRequest) { + case USB_REQ_DFU_GETSTATUS: + /* We're MainfestationTolerant */ + f_dfu->dfu_state = DFU_STATE_dfuMANIFEST; + value = handle_getstatus(req); + f_dfu->blk_seq_num = 0; + req->complete = dnload_request_flush; + break; + case USB_REQ_DFU_GETSTATE: + value = handle_getstate(req); + break; + default: + f_dfu->dfu_state = DFU_STATE_dfuERROR; + value = RET_STALL; + break; + } + + return value; +} + +static int state_dfu_manifest(struct f_dfu *f_dfu, + const struct usb_ctrlrequest *ctrl, + struct usb_gadget *gadget, + struct usb_request *req) +{ + int value = 0; + + switch (ctrl->bRequest) { + case USB_REQ_DFU_GETSTATUS: + /* We're MainfestationTolerant */ + f_dfu->dfu_state = DFU_STATE_dfuIDLE; + value = handle_getstatus(req); + f_dfu->blk_seq_num = 0; + puts("DOWNLOAD ... OK\nCtrl+C to exit ...\n"); + break; + case USB_REQ_DFU_GETSTATE: + value = handle_getstate(req); + break; + default: + f_dfu->dfu_state = DFU_STATE_dfuERROR; + value = RET_STALL; + break; + } + return value; +} + +static int state_dfu_upload_idle(struct f_dfu *f_dfu, + const struct usb_ctrlrequest *ctrl, + struct usb_gadget *gadget, + struct usb_request *req) +{ + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 len = le16_to_cpu(ctrl->wLength); + int value = 0; + + switch (ctrl->bRequest) { + case USB_REQ_DFU_UPLOAD: + /* state transition if less data then requested */ + f_dfu->blk_seq_num = w_value; + value = handle_upload(req, len); + if (value >= 0 && value < len) + f_dfu->dfu_state = DFU_STATE_dfuIDLE; + break; + case USB_REQ_DFU_ABORT: + f_dfu->dfu_state = DFU_STATE_dfuIDLE; + /* no zlp? */ + value = RET_ZLP; + break; + case USB_REQ_DFU_GETSTATUS: + value = handle_getstatus(req); + break; + case USB_REQ_DFU_GETSTATE: + value = handle_getstate(req); + break; + default: + f_dfu->dfu_state = DFU_STATE_dfuERROR; + value = RET_STALL; + break; + } + + return value; +} + +static int state_dfu_error(struct f_dfu *f_dfu, + const struct usb_ctrlrequest *ctrl, + struct usb_gadget *gadget, + struct usb_request *req) +{ + int value = 0; + + switch (ctrl->bRequest) { + case USB_REQ_DFU_GETSTATUS: + value = handle_getstatus(req); + break; + case USB_REQ_DFU_GETSTATE: + value = handle_getstate(req); + break; + case USB_REQ_DFU_CLRSTATUS: + f_dfu->dfu_state = DFU_STATE_dfuIDLE; + f_dfu->dfu_status = DFU_STATUS_OK; + /* no zlp? */ + value = RET_ZLP; + break; + default: + f_dfu->dfu_state = DFU_STATE_dfuERROR; + value = RET_STALL; + break; + } + + return value; +} + +static dfu_state_fn dfu_state[] = { + state_app_idle, /* DFU_STATE_appIDLE */ + state_app_detach, /* DFU_STATE_appDETACH */ + state_dfu_idle, /* DFU_STATE_dfuIDLE */ + state_dfu_dnload_sync, /* DFU_STATE_dfuDNLOAD_SYNC */ + state_dfu_dnbusy, /* DFU_STATE_dfuDNBUSY */ + state_dfu_dnload_idle, /* DFU_STATE_dfuDNLOAD_IDLE */ + state_dfu_manifest_sync, /* DFU_STATE_dfuMANIFEST_SYNC */ + state_dfu_manifest, /* DFU_STATE_dfuMANIFEST */ + NULL, /* DFU_STATE_dfuMANIFEST_WAIT_RST */ + state_dfu_upload_idle, /* DFU_STATE_dfuUPLOAD_IDLE */ + state_dfu_error /* DFU_STATE_dfuERROR */ +}; + +static int +dfu_handle(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct usb_gadget *gadget = f->config->cdev->gadget; + struct usb_request *req = f->config->cdev->req; + struct f_dfu *f_dfu = f->config->cdev->req->context; + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + int value = 0; + u8 req_type = ctrl->bRequestType & USB_TYPE_MASK; + + debug("w_value: 0x%x len: 0x%x\n", w_value, len); + debug("req_type: 0x%x ctrl->bRequest: 0x%x f_dfu->dfu_state: 0x%x\n", + req_type, ctrl->bRequest, f_dfu->dfu_state); + +#ifdef CONFIG_DFU_TIMEOUT + /* Forbid aborting by timeout. Next dfu command may update this */ + dfu_set_timeout(0); +#endif + + if (req_type == USB_TYPE_STANDARD) { + if (ctrl->bRequest == USB_REQ_GET_DESCRIPTOR && + (w_value >> 8) == DFU_DT_FUNC) { + value = min(len, (u16) sizeof(dfu_func)); + memcpy(req->buf, &dfu_func, value); + } + } else /* DFU specific request */ + value = dfu_state[f_dfu->dfu_state] (f_dfu, ctrl, gadget, req); + + if (value >= 0) { + req->length = value; + req->zero = value < len; + value = usb_ep_queue(gadget->ep0, req, 0); + if (value < 0) { + debug("ep_queue --> %d\n", value); + req->status = 0; + } + } + + return value; +} + +/*-------------------------------------------------------------------------*/ + +static int +dfu_prepare_strings(struct f_dfu *f_dfu, int n) +{ + struct dfu_entity *de = NULL; + int i = 0; + + f_dfu->strings = calloc(sizeof(struct usb_string), n + 1); + if (!f_dfu->strings) + return -ENOMEM; + + for (i = 0; i < n; ++i) { + de = dfu_get_entity(i); + f_dfu->strings[i].s = de->name; + } + + f_dfu->strings[i].id = 0; + f_dfu->strings[i].s = NULL; + + return 0; +} + +static int dfu_prepare_function(struct f_dfu *f_dfu, int n) +{ + struct usb_interface_descriptor *d; + int i = 0; + + f_dfu->function = calloc(sizeof(struct usb_descriptor_header *), n + 2); + if (!f_dfu->function) + goto enomem; + + for (i = 0; i < n; ++i) { + d = calloc(sizeof(*d), 1); + if (!d) + goto enomem; + + d->bLength = sizeof(*d); + d->bDescriptorType = USB_DT_INTERFACE; + d->bAlternateSetting = i; + d->bNumEndpoints = 0; + d->bInterfaceClass = USB_CLASS_APP_SPEC; + d->bInterfaceSubClass = 1; + d->bInterfaceProtocol = 2; + + f_dfu->function[i] = (struct usb_descriptor_header *)d; + } + + /* add DFU Functional Descriptor */ + f_dfu->function[i] = calloc(sizeof(dfu_func), 1); + if (!f_dfu->function[i]) + goto enomem; + memcpy(f_dfu->function[i], &dfu_func, sizeof(dfu_func)); + + i++; + f_dfu->function[i] = NULL; + + return 0; + +enomem: + while (i) { + free(f_dfu->function[--i]); + f_dfu->function[i] = NULL; + } + free(f_dfu->function); + + return -ENOMEM; +} + +static int dfu_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_dfu *f_dfu = func_to_dfu(f); + const char *s; + int alt_num = dfu_get_alt_number(); + int rv, id, i; + + id = usb_interface_id(c, f); + if (id < 0) + return id; + dfu_intf_runtime.bInterfaceNumber = id; + + f_dfu->dfu_state = DFU_STATE_appIDLE; + f_dfu->dfu_status = DFU_STATUS_OK; + + rv = dfu_prepare_function(f_dfu, alt_num); + if (rv) + goto error; + + rv = dfu_prepare_strings(f_dfu, alt_num); + if (rv) + goto error; + for (i = 0; i < alt_num; i++) { + id = usb_string_id(cdev); + if (id < 0) + return id; + f_dfu->strings[i].id = id; + ((struct usb_interface_descriptor *)f_dfu->function[i]) + ->iInterface = id; + } + + to_dfu_mode(f_dfu); + + stringtab_dfu.strings = f_dfu->strings; + + cdev->req->context = f_dfu; + + s = env_get("serial#"); + if (s) + g_dnl_set_serialnumber((char *)s); + +error: + return rv; +} + +static void dfu_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_dfu *f_dfu = func_to_dfu(f); + int alt_num = dfu_get_alt_number(); + int i; + + if (f_dfu->strings) { + i = alt_num; + while (i) + f_dfu->strings[--i].s = NULL; + + free(f_dfu->strings); + } + + if (f_dfu->function) { + i = alt_num; + i++; /* free DFU Functional Descriptor */ + while (i) { + free(f_dfu->function[--i]); + f_dfu->function[i] = NULL; + } + free(f_dfu->function); + } + + free(f_dfu); +} + +static int dfu_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_dfu *f_dfu = func_to_dfu(f); + + debug("%s: intf:%d alt:%d\n", __func__, intf, alt); + + f_dfu->altsetting = alt; + f_dfu->dfu_state = DFU_STATE_dfuIDLE; + f_dfu->dfu_status = DFU_STATUS_OK; + + return 0; +} + +static int __dfu_get_alt(struct usb_function *f, unsigned intf) +{ + struct f_dfu *f_dfu = func_to_dfu(f); + + return f_dfu->altsetting; +} + +/* TODO: is this really what we need here? */ +static void dfu_disable(struct usb_function *f) +{ + struct f_dfu *f_dfu = func_to_dfu(f); + if (f_dfu->config == 0) + return; + + debug("%s: reset config\n", __func__); + + f_dfu->config = 0; +} + +static int dfu_bind_config(struct usb_configuration *c) +{ + struct f_dfu *f_dfu; + int status; + + f_dfu = calloc(sizeof(*f_dfu), 1); + if (!f_dfu) + return -ENOMEM; + f_dfu->usb_function.name = "dfu"; + f_dfu->usb_function.hs_descriptors = dfu_runtime_descs; + f_dfu->usb_function.descriptors = dfu_runtime_descs; + f_dfu->usb_function.bind = dfu_bind; + f_dfu->usb_function.unbind = dfu_unbind; + f_dfu->usb_function.set_alt = dfu_set_alt; + f_dfu->usb_function.get_alt = __dfu_get_alt; + f_dfu->usb_function.disable = dfu_disable; + f_dfu->usb_function.strings = dfu_generic_strings; + f_dfu->usb_function.setup = dfu_handle; + f_dfu->poll_timeout = DFU_DEFAULT_POLL_TIMEOUT; + + status = usb_add_function(c, &f_dfu->usb_function); + if (status) + free(f_dfu); + + return status; +} + +int dfu_add(struct usb_configuration *c) +{ + int id; + + id = usb_string_id(c->cdev); + if (id < 0) + return id; + strings_dfu_generic[0].id = id; + dfu_intf_runtime.iInterface = id; + + debug("%s: cdev: 0x%p gadget:0x%p gadget->ep0: 0x%p\n", __func__, + c->cdev, c->cdev->gadget, c->cdev->gadget->ep0); + + return dfu_bind_config(c); +} + +DECLARE_GADGET_BIND_CALLBACK(usb_dnl_dfu, dfu_add); diff --git a/roms/u-boot/drivers/usb/gadget/f_dfu.h b/roms/u-boot/drivers/usb/gadget/f_dfu.h new file mode 100644 index 000000000..a175e3f13 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/f_dfu.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * f_dfu.h -- Device Firmware Update gadget + * + * Copyright (C) 2011-2012 Samsung Electronics + * author: Andrzej Pietrasiewicz <andrzej.p@samsung.com> + */ + +#ifndef __F_DFU_H_ +#define __F_DFU_H_ + +#include <linux/compiler.h> +#include <linux/usb/composite.h> + +#define DFU_CONFIG_VAL 1 +#define DFU_DT_FUNC 0x21 + +#define DFU_BIT_WILL_DETACH (0x1 << 3) +#define DFU_BIT_MANIFESTATION_TOLERANT (0x1 << 2) +#define DFU_BIT_CAN_UPLOAD (0x1 << 1) +#define DFU_BIT_CAN_DNLOAD 0x1 + +/* big enough to hold our biggest descriptor */ +#define DFU_USB_BUFSIZ 4096 + +#define USB_REQ_DFU_DETACH 0x00 +#define USB_REQ_DFU_DNLOAD 0x01 +#define USB_REQ_DFU_UPLOAD 0x02 +#define USB_REQ_DFU_GETSTATUS 0x03 +#define USB_REQ_DFU_CLRSTATUS 0x04 +#define USB_REQ_DFU_GETSTATE 0x05 +#define USB_REQ_DFU_ABORT 0x06 + +#define DFU_STATUS_OK 0x00 +#define DFU_STATUS_errTARGET 0x01 +#define DFU_STATUS_errFILE 0x02 +#define DFU_STATUS_errWRITE 0x03 +#define DFU_STATUS_errERASE 0x04 +#define DFU_STATUS_errCHECK_ERASED 0x05 +#define DFU_STATUS_errPROG 0x06 +#define DFU_STATUS_errVERIFY 0x07 +#define DFU_STATUS_errADDRESS 0x08 +#define DFU_STATUS_errNOTDONE 0x09 +#define DFU_STATUS_errFIRMWARE 0x0a +#define DFU_STATUS_errVENDOR 0x0b +#define DFU_STATUS_errUSBR 0x0c +#define DFU_STATUS_errPOR 0x0d +#define DFU_STATUS_errUNKNOWN 0x0e +#define DFU_STATUS_errSTALLEDPKT 0x0f + +#define RET_STALL -1 +#define RET_ZLP 0 + +enum dfu_state { + DFU_STATE_appIDLE = 0, + DFU_STATE_appDETACH = 1, + DFU_STATE_dfuIDLE = 2, + DFU_STATE_dfuDNLOAD_SYNC = 3, + DFU_STATE_dfuDNBUSY = 4, + DFU_STATE_dfuDNLOAD_IDLE = 5, + DFU_STATE_dfuMANIFEST_SYNC = 6, + DFU_STATE_dfuMANIFEST = 7, + DFU_STATE_dfuMANIFEST_WAIT_RST = 8, + DFU_STATE_dfuUPLOAD_IDLE = 9, + DFU_STATE_dfuERROR = 10, +}; + +struct dfu_status { + __u8 bStatus; + __u8 bwPollTimeout[3]; + __u8 bState; + __u8 iString; +} __packed; + +struct dfu_function_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bmAttributes; + __le16 wDetachTimeOut; + __le16 wTransferSize; + __le16 bcdDFUVersion; +} __packed; + +#define DFU_POLL_TIMEOUT_MASK (0xFFFFFFUL) +#endif /* __F_DFU_H_ */ diff --git a/roms/u-boot/drivers/usb/gadget/f_fastboot.c b/roms/u-boot/drivers/usb/gadget/f_fastboot.c new file mode 100644 index 000000000..8ba55aab9 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/f_fastboot.c @@ -0,0 +1,560 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2008 - 2009 + * Windriver, <www.windriver.com> + * Tom Rix <Tom.Rix@windriver.com> + * + * Copyright 2011 Sebastian Andrzej Siewior <bigeasy@linutronix.de> + * + * Copyright 2014 Linaro, Ltd. + * Rob Herring <robh@kernel.org> + */ +#include <command.h> +#include <config.h> +#include <common.h> +#include <env.h> +#include <errno.h> +#include <fastboot.h> +#include <log.h> +#include <malloc.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb/composite.h> +#include <linux/compiler.h> +#include <g_dnl.h> + +#define FASTBOOT_INTERFACE_CLASS 0xff +#define FASTBOOT_INTERFACE_SUB_CLASS 0x42 +#define FASTBOOT_INTERFACE_PROTOCOL 0x03 + +#define RX_ENDPOINT_MAXIMUM_PACKET_SIZE_2_0 (0x0200) +#define RX_ENDPOINT_MAXIMUM_PACKET_SIZE_1_1 (0x0040) +#define TX_ENDPOINT_MAXIMUM_PACKET_SIZE (0x0040) + +#define EP_BUFFER_SIZE 4096 +/* + * EP_BUFFER_SIZE must always be an integral multiple of maxpacket size + * (64 or 512 or 1024), else we break on certain controllers like DWC3 + * that expect bulk OUT requests to be divisible by maxpacket size. + */ + +struct f_fastboot { + struct usb_function usb_function; + + /* IN/OUT EP's and corresponding requests */ + struct usb_ep *in_ep, *out_ep; + struct usb_request *in_req, *out_req; +}; + +static char fb_ext_prop_name[] = "DeviceInterfaceGUID"; +static char fb_ext_prop_data[] = "{4866319A-F4D6-4374-93B9-DC2DEB361BA9}"; + +static struct usb_os_desc_ext_prop fb_ext_prop = { + .type = 1, /* NUL-terminated Unicode String (REG_SZ) */ + .name = fb_ext_prop_name, + .data = fb_ext_prop_data, +}; + +/* 16 bytes of "Compatible ID" and "Subcompatible ID" */ +static char fb_cid[16] = {'W', 'I', 'N', 'U', 'S', 'B'}; +static struct usb_os_desc fb_os_desc = { + .ext_compat_id = fb_cid, +}; + +static struct usb_os_desc_table fb_os_desc_table = { + .os_desc = &fb_os_desc, +}; + +static inline struct f_fastboot *func_to_fastboot(struct usb_function *f) +{ + return container_of(f, struct f_fastboot, usb_function); +} + +static struct f_fastboot *fastboot_func; + +static struct usb_endpoint_descriptor fs_ep_in = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(64), +}; + +static struct usb_endpoint_descriptor fs_ep_out = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(64), +}; + +static struct usb_endpoint_descriptor hs_ep_in = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor hs_ep_out = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_interface_descriptor interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0x00, + .bAlternateSetting = 0x00, + .bNumEndpoints = 0x02, + .bInterfaceClass = FASTBOOT_INTERFACE_CLASS, + .bInterfaceSubClass = FASTBOOT_INTERFACE_SUB_CLASS, + .bInterfaceProtocol = FASTBOOT_INTERFACE_PROTOCOL, +}; + +static struct usb_descriptor_header *fb_fs_function[] = { + (struct usb_descriptor_header *)&interface_desc, + (struct usb_descriptor_header *)&fs_ep_in, + (struct usb_descriptor_header *)&fs_ep_out, +}; + +static struct usb_descriptor_header *fb_hs_function[] = { + (struct usb_descriptor_header *)&interface_desc, + (struct usb_descriptor_header *)&hs_ep_in, + (struct usb_descriptor_header *)&hs_ep_out, + NULL, +}; + +/* Super speed */ +static struct usb_endpoint_descriptor ss_ep_in = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor ss_ep_out = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor fb_ss_bulk_comp_desc = { + .bLength = sizeof(fb_ss_bulk_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_descriptor_header *fb_ss_function[] = { + (struct usb_descriptor_header *)&interface_desc, + (struct usb_descriptor_header *)&ss_ep_in, + (struct usb_descriptor_header *)&fb_ss_bulk_comp_desc, + (struct usb_descriptor_header *)&ss_ep_out, + (struct usb_descriptor_header *)&fb_ss_bulk_comp_desc, + NULL, +}; + +static struct usb_endpoint_descriptor * +fb_ep_desc(struct usb_gadget *g, struct usb_endpoint_descriptor *fs, + struct usb_endpoint_descriptor *hs, + struct usb_endpoint_descriptor *ss) +{ + if (gadget_is_superspeed(g) && g->speed >= USB_SPEED_SUPER) + return ss; + + if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) + return hs; + return fs; +} + +/* + * static strings, in UTF-8 + */ +static const char fastboot_name[] = "Android Fastboot"; + +static struct usb_string fastboot_string_defs[] = { + [0].s = fastboot_name, + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_fastboot = { + .language = 0x0409, /* en-us */ + .strings = fastboot_string_defs, +}; + +static struct usb_gadget_strings *fastboot_strings[] = { + &stringtab_fastboot, + NULL, +}; + +static void rx_handler_command(struct usb_ep *ep, struct usb_request *req); + +static void fastboot_complete(struct usb_ep *ep, struct usb_request *req) +{ + int status = req->status; + if (!status) + return; + printf("status: %d ep '%s' trans: %d\n", status, ep->name, req->actual); +} + +static int fastboot_bind(struct usb_configuration *c, struct usb_function *f) +{ + int id; + struct usb_gadget *gadget = c->cdev->gadget; + struct f_fastboot *f_fb = func_to_fastboot(f); + const char *s; + + /* DYNAMIC interface numbers assignments */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + interface_desc.bInterfaceNumber = id; + + /* Enable OS and Extended Properties Feature Descriptor */ + c->cdev->use_os_string = 1; + f->os_desc_table = &fb_os_desc_table; + f->os_desc_n = 1; + f->os_desc_table->if_id = id; + INIT_LIST_HEAD(&fb_os_desc.ext_prop); + fb_ext_prop.name_len = strlen(fb_ext_prop.name) * 2 + 2; + fb_os_desc.ext_prop_len = 10 + fb_ext_prop.name_len; + fb_os_desc.ext_prop_count = 1; + fb_ext_prop.data_len = strlen(fb_ext_prop.data) * 2 + 2; + fb_os_desc.ext_prop_len += fb_ext_prop.data_len + 4; + list_add_tail(&fb_ext_prop.entry, &fb_os_desc.ext_prop); + + id = usb_string_id(c->cdev); + if (id < 0) + return id; + fastboot_string_defs[0].id = id; + interface_desc.iInterface = id; + + f_fb->in_ep = usb_ep_autoconfig(gadget, &fs_ep_in); + if (!f_fb->in_ep) + return -ENODEV; + f_fb->in_ep->driver_data = c->cdev; + + f_fb->out_ep = usb_ep_autoconfig(gadget, &fs_ep_out); + if (!f_fb->out_ep) + return -ENODEV; + f_fb->out_ep->driver_data = c->cdev; + + f->descriptors = fb_fs_function; + + if (gadget_is_dualspeed(gadget)) { + /* Assume endpoint addresses are the same for both speeds */ + hs_ep_in.bEndpointAddress = fs_ep_in.bEndpointAddress; + hs_ep_out.bEndpointAddress = fs_ep_out.bEndpointAddress; + /* copy HS descriptors */ + f->hs_descriptors = fb_hs_function; + } + + if (gadget_is_superspeed(gadget)) { + ss_ep_in.bEndpointAddress = fs_ep_in.bEndpointAddress; + ss_ep_out.bEndpointAddress = fs_ep_out.bEndpointAddress; + f->ss_descriptors = fb_ss_function; + } + + s = env_get("serial#"); + if (s) + g_dnl_set_serialnumber((char *)s); + + return 0; +} + +static void fastboot_unbind(struct usb_configuration *c, struct usb_function *f) +{ + f->os_desc_table = NULL; + list_del(&fb_os_desc.ext_prop); + memset(fastboot_func, 0, sizeof(*fastboot_func)); +} + +static void fastboot_disable(struct usb_function *f) +{ + struct f_fastboot *f_fb = func_to_fastboot(f); + + usb_ep_disable(f_fb->out_ep); + usb_ep_disable(f_fb->in_ep); + + if (f_fb->out_req) { + free(f_fb->out_req->buf); + usb_ep_free_request(f_fb->out_ep, f_fb->out_req); + f_fb->out_req = NULL; + } + if (f_fb->in_req) { + free(f_fb->in_req->buf); + usb_ep_free_request(f_fb->in_ep, f_fb->in_req); + f_fb->in_req = NULL; + } +} + +static struct usb_request *fastboot_start_ep(struct usb_ep *ep) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, 0); + if (!req) + return NULL; + + req->length = EP_BUFFER_SIZE; + req->buf = memalign(CONFIG_SYS_CACHELINE_SIZE, EP_BUFFER_SIZE); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + memset(req->buf, 0, req->length); + return req; +} + +static int fastboot_set_alt(struct usb_function *f, + unsigned interface, unsigned alt) +{ + int ret; + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_gadget *gadget = cdev->gadget; + struct f_fastboot *f_fb = func_to_fastboot(f); + const struct usb_endpoint_descriptor *d; + + debug("%s: func: %s intf: %d alt: %d\n", + __func__, f->name, interface, alt); + + d = fb_ep_desc(gadget, &fs_ep_out, &hs_ep_out, &ss_ep_out); + ret = usb_ep_enable(f_fb->out_ep, d); + if (ret) { + puts("failed to enable out ep\n"); + return ret; + } + + f_fb->out_req = fastboot_start_ep(f_fb->out_ep); + if (!f_fb->out_req) { + puts("failed to alloc out req\n"); + ret = -EINVAL; + goto err; + } + f_fb->out_req->complete = rx_handler_command; + + d = fb_ep_desc(gadget, &fs_ep_in, &hs_ep_in, &ss_ep_in); + ret = usb_ep_enable(f_fb->in_ep, d); + if (ret) { + puts("failed to enable in ep\n"); + goto err; + } + + f_fb->in_req = fastboot_start_ep(f_fb->in_ep); + if (!f_fb->in_req) { + puts("failed alloc req in\n"); + ret = -EINVAL; + goto err; + } + f_fb->in_req->complete = fastboot_complete; + + ret = usb_ep_queue(f_fb->out_ep, f_fb->out_req, 0); + if (ret) + goto err; + + return 0; +err: + fastboot_disable(f); + return ret; +} + +static int fastboot_add(struct usb_configuration *c) +{ + struct f_fastboot *f_fb = fastboot_func; + int status; + + debug("%s: cdev: 0x%p\n", __func__, c->cdev); + + if (!f_fb) { + f_fb = memalign(CONFIG_SYS_CACHELINE_SIZE, sizeof(*f_fb)); + if (!f_fb) + return -ENOMEM; + + fastboot_func = f_fb; + memset(f_fb, 0, sizeof(*f_fb)); + } + + f_fb->usb_function.name = "f_fastboot"; + f_fb->usb_function.bind = fastboot_bind; + f_fb->usb_function.unbind = fastboot_unbind; + f_fb->usb_function.set_alt = fastboot_set_alt; + f_fb->usb_function.disable = fastboot_disable; + f_fb->usb_function.strings = fastboot_strings; + + status = usb_add_function(c, &f_fb->usb_function); + if (status) { + free(f_fb); + fastboot_func = NULL; + } + + return status; +} +DECLARE_GADGET_BIND_CALLBACK(usb_dnl_fastboot, fastboot_add); + +static int fastboot_tx_write(const char *buffer, unsigned int buffer_size) +{ + struct usb_request *in_req = fastboot_func->in_req; + int ret; + + memcpy(in_req->buf, buffer, buffer_size); + in_req->length = buffer_size; + + usb_ep_dequeue(fastboot_func->in_ep, in_req); + + ret = usb_ep_queue(fastboot_func->in_ep, in_req, 0); + if (ret) + printf("Error %d on queue\n", ret); + return 0; +} + +static int fastboot_tx_write_str(const char *buffer) +{ + return fastboot_tx_write(buffer, strlen(buffer)); +} + +static void compl_do_reset(struct usb_ep *ep, struct usb_request *req) +{ + do_reset(NULL, 0, 0, NULL); +} + +static unsigned int rx_bytes_expected(struct usb_ep *ep) +{ + int rx_remain = fastboot_data_remaining(); + unsigned int rem; + unsigned int maxpacket = usb_endpoint_maxp(ep->desc); + + if (rx_remain <= 0) + return 0; + else if (rx_remain > EP_BUFFER_SIZE) + return EP_BUFFER_SIZE; + + /* + * Some controllers e.g. DWC3 don't like OUT transfers to be + * not ending in maxpacket boundary. So just make them happy by + * always requesting for integral multiple of maxpackets. + * This shouldn't bother controllers that don't care about it. + */ + rem = rx_remain % maxpacket; + if (rem > 0) + rx_remain = rx_remain + (maxpacket - rem); + + return rx_remain; +} + +static void rx_handler_dl_image(struct usb_ep *ep, struct usb_request *req) +{ + char response[FASTBOOT_RESPONSE_LEN] = {0}; + unsigned int transfer_size = fastboot_data_remaining(); + const unsigned char *buffer = req->buf; + unsigned int buffer_size = req->actual; + + if (req->status != 0) { + printf("Bad status: %d\n", req->status); + return; + } + + if (buffer_size < transfer_size) + transfer_size = buffer_size; + + fastboot_data_download(buffer, transfer_size, response); + if (response[0]) { + fastboot_tx_write_str(response); + } else if (!fastboot_data_remaining()) { + fastboot_data_complete(response); + + /* + * Reset global transfer variable + */ + req->complete = rx_handler_command; + req->length = EP_BUFFER_SIZE; + + fastboot_tx_write_str(response); + } else { + req->length = rx_bytes_expected(ep); + } + + req->actual = 0; + usb_ep_queue(ep, req, 0); +} + +static void do_exit_on_complete(struct usb_ep *ep, struct usb_request *req) +{ + g_dnl_trigger_detach(); +} + +static void do_bootm_on_complete(struct usb_ep *ep, struct usb_request *req) +{ + fastboot_boot(); + do_exit_on_complete(ep, req); +} + +#if CONFIG_IS_ENABLED(FASTBOOT_UUU_SUPPORT) +static void do_acmd_complete(struct usb_ep *ep, struct usb_request *req) +{ + /* When usb dequeue complete will be called + * Need status value before call run_command. + * otherwise, host can't get last message. + */ + if (req->status == 0) + fastboot_acmd_complete(); +} +#endif + +static void rx_handler_command(struct usb_ep *ep, struct usb_request *req) +{ + char *cmdbuf = req->buf; + char response[FASTBOOT_RESPONSE_LEN] = {0}; + int cmd = -1; + + if (req->status != 0 || req->length == 0) + return; + + if (req->actual < req->length) { + cmdbuf[req->actual] = '\0'; + cmd = fastboot_handle_command(cmdbuf, response); + } else { + pr_err("buffer overflow"); + fastboot_fail("buffer overflow", response); + } + + if (!strncmp("DATA", response, 4)) { + req->complete = rx_handler_dl_image; + req->length = rx_bytes_expected(ep); + } + + if (!strncmp("OKAY", response, 4)) { + switch (cmd) { + case FASTBOOT_COMMAND_BOOT: + fastboot_func->in_req->complete = do_bootm_on_complete; + break; + + case FASTBOOT_COMMAND_CONTINUE: + fastboot_func->in_req->complete = do_exit_on_complete; + break; + + case FASTBOOT_COMMAND_REBOOT: + case FASTBOOT_COMMAND_REBOOT_BOOTLOADER: + case FASTBOOT_COMMAND_REBOOT_FASTBOOTD: + case FASTBOOT_COMMAND_REBOOT_RECOVERY: + fastboot_func->in_req->complete = compl_do_reset; + break; +#if CONFIG_IS_ENABLED(FASTBOOT_UUU_SUPPORT) + case FASTBOOT_COMMAND_ACMD: + fastboot_func->in_req->complete = do_acmd_complete; + break; +#endif + } + } + + fastboot_tx_write_str(response); + + *cmdbuf = '\0'; + req->actual = 0; + usb_ep_queue(ep, req, 0); +} diff --git a/roms/u-boot/drivers/usb/gadget/f_mass_storage.c b/roms/u-boot/drivers/usb/gadget/f_mass_storage.c new file mode 100644 index 000000000..45f0504b6 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/f_mass_storage.c @@ -0,0 +1,2777 @@ +// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause +/* + * f_mass_storage.c -- Mass Storage USB Composite Function + * + * Copyright (C) 2003-2008 Alan Stern + * Copyright (C) 2009 Samsung Electronics + * Author: Michal Nazarewicz <m.nazarewicz@samsung.com> + * All rights reserved. + */ + +/* + * The Mass Storage Function acts as a USB Mass Storage device, + * appearing to the host as a disk drive or as a CD-ROM drive. In + * addition to providing an example of a genuinely useful composite + * function for a USB device, it also illustrates a technique of + * double-buffering for increased throughput. + * + * Function supports multiple logical units (LUNs). Backing storage + * for each LUN is provided by a regular file or a block device. + * Access for each LUN can be limited to read-only. Moreover, the + * function can indicate that LUN is removable and/or CD-ROM. (The + * later implies read-only access.) + * + * MSF is configured by specifying a fsg_config structure. It has the + * following fields: + * + * nluns Number of LUNs function have (anywhere from 1 + * to FSG_MAX_LUNS which is 8). + * luns An array of LUN configuration values. This + * should be filled for each LUN that + * function will include (ie. for "nluns" + * LUNs). Each element of the array has + * the following fields: + * ->filename The path to the backing file for the LUN. + * Required if LUN is not marked as + * removable. + * ->ro Flag specifying access to the LUN shall be + * read-only. This is implied if CD-ROM + * emulation is enabled as well as when + * it was impossible to open "filename" + * in R/W mode. + * ->removable Flag specifying that LUN shall be indicated as + * being removable. + * ->cdrom Flag specifying that LUN shall be reported as + * being a CD-ROM. + * + * lun_name_format A printf-like format for names of the LUN + * devices. This determines how the + * directory in sysfs will be named. + * Unless you are using several MSFs in + * a single gadget (as opposed to single + * MSF in many configurations) you may + * leave it as NULL (in which case + * "lun%d" will be used). In the format + * you can use "%d" to index LUNs for + * MSF's with more than one LUN. (Beware + * that there is only one integer given + * as an argument for the format and + * specifying invalid format may cause + * unspecified behaviour.) + * thread_name Name of the kernel thread process used by the + * MSF. You can safely set it to NULL + * (in which case default "file-storage" + * will be used). + * + * vendor_name + * product_name + * release Information used as a reply to INQUIRY + * request. To use default set to NULL, + * NULL, 0xffff respectively. The first + * field should be 8 and the second 16 + * characters or less. + * + * can_stall Set to permit function to halt bulk endpoints. + * Disabled on some USB devices known not + * to work correctly. You should set it + * to true. + * + * If "removable" is not set for a LUN then a backing file must be + * specified. If it is set, then NULL filename means the LUN's medium + * is not loaded (an empty string as "filename" in the fsg_config + * structure causes error). The CD-ROM emulation includes a single + * data track and no audio tracks; hence there need be only one + * backing file per LUN. Note also that the CD-ROM block length is + * set to 512 rather than the more common value 2048. + * + * + * MSF includes support for module parameters. If gadget using it + * decides to use it, the following module parameters will be + * available: + * + * file=filename[,filename...] + * Names of the files or block devices used for + * backing storage. + * ro=b[,b...] Default false, boolean for read-only access. + * removable=b[,b...] + * Default true, boolean for removable media. + * cdrom=b[,b...] Default false, boolean for whether to emulate + * a CD-ROM drive. + * luns=N Default N = number of filenames, number of + * LUNs to support. + * stall Default determined according to the type of + * USB device controller (usually true), + * boolean to permit the driver to halt + * bulk endpoints. + * + * The module parameters may be prefixed with some string. You need + * to consult gadget's documentation or source to verify whether it is + * using those module parameters and if it does what are the prefixes + * (look for FSG_MODULE_PARAMETERS() macro usage, what's inside it is + * the prefix). + * + * + * Requirements are modest; only a bulk-in and a bulk-out endpoint are + * needed. The memory requirement amounts to two 16K buffers, size + * configurable by a parameter. Support is included for both + * full-speed and high-speed operation. + * + * Note that the driver is slightly non-portable in that it assumes a + * single memory/DMA buffer will be useable for bulk-in, bulk-out, and + * interrupt-in endpoints. With most device controllers this isn't an + * issue, but there may be some with hardware restrictions that prevent + * a buffer from being used by more than one endpoint. + * + * + * The pathnames of the backing files and the ro settings are + * available in the attribute files "file" and "ro" in the lun<n> (or + * to be more precise in a directory which name comes from + * "lun_name_format" option!) subdirectory of the gadget's sysfs + * directory. If the "removable" option is set, writing to these + * files will simulate ejecting/loading the medium (writing an empty + * line means eject) and adjusting a write-enable tab. Changes to the + * ro setting are not allowed when the medium is loaded or if CD-ROM + * emulation is being used. + * + * When a LUN receive an "eject" SCSI request (Start/Stop Unit), + * if the LUN is removable, the backing file is released to simulate + * ejection. + * + * + * This function is heavily based on "File-backed Storage Gadget" by + * Alan Stern which in turn is heavily based on "Gadget Zero" by David + * Brownell. The driver's SCSI command interface was based on the + * "Information technology - Small Computer System Interface - 2" + * document from X3T9.2 Project 375D, Revision 10L, 7-SEP-93, + * available at <http://www.t10.org/ftp/t10/drafts/s2/s2-r10l.pdf>. + * The single exception is opcode 0x23 (READ FORMAT CAPACITIES), which + * was based on the "Universal Serial Bus Mass Storage Class UFI + * Command Specification" document, Revision 1.0, December 14, 1998, + * available at + * <http://www.usb.org/developers/devclass_docs/usbmass-ufi10.pdf>. + */ + +/* + * Driver Design + * + * The MSF is fairly straightforward. There is a main kernel + * thread that handles most of the work. Interrupt routines field + * callbacks from the controller driver: bulk- and interrupt-request + * completion notifications, endpoint-0 events, and disconnect events. + * Completion events are passed to the main thread by wakeup calls. Many + * ep0 requests are handled at interrupt time, but SetInterface, + * SetConfiguration, and device reset requests are forwarded to the + * thread in the form of "exceptions" using SIGUSR1 signals (since they + * should interrupt any ongoing file I/O operations). + * + * The thread's main routine implements the standard command/data/status + * parts of a SCSI interaction. It and its subroutines are full of tests + * for pending signals/exceptions -- all this polling is necessary since + * the kernel has no setjmp/longjmp equivalents. (Maybe this is an + * indication that the driver really wants to be running in userspace.) + * An important point is that so long as the thread is alive it keeps an + * open reference to the backing file. This will prevent unmounting + * the backing file's underlying filesystem and could cause problems + * during system shutdown, for example. To prevent such problems, the + * thread catches INT, TERM, and KILL signals and converts them into + * an EXIT exception. + * + * In normal operation the main thread is started during the gadget's + * fsg_bind() callback and stopped during fsg_unbind(). But it can + * also exit when it receives a signal, and there's no point leaving + * the gadget running when the thread is dead. At of this moment, MSF + * provides no way to deregister the gadget when thread dies -- maybe + * a callback functions is needed. + * + * To provide maximum throughput, the driver uses a circular pipeline of + * buffer heads (struct fsg_buffhd). In principle the pipeline can be + * arbitrarily long; in practice the benefits don't justify having more + * than 2 stages (i.e., double buffering). But it helps to think of the + * pipeline as being a long one. Each buffer head contains a bulk-in and + * a bulk-out request pointer (since the buffer can be used for both + * output and input -- directions always are given from the host's + * point of view) as well as a pointer to the buffer and various state + * variables. + * + * Use of the pipeline follows a simple protocol. There is a variable + * (fsg->next_buffhd_to_fill) that points to the next buffer head to use. + * At any time that buffer head may still be in use from an earlier + * request, so each buffer head has a state variable indicating whether + * it is EMPTY, FULL, or BUSY. Typical use involves waiting for the + * buffer head to be EMPTY, filling the buffer either by file I/O or by + * USB I/O (during which the buffer head is BUSY), and marking the buffer + * head FULL when the I/O is complete. Then the buffer will be emptied + * (again possibly by USB I/O, during which it is marked BUSY) and + * finally marked EMPTY again (possibly by a completion routine). + * + * A module parameter tells the driver to avoid stalling the bulk + * endpoints wherever the transport specification allows. This is + * necessary for some UDCs like the SuperH, which cannot reliably clear a + * halt on a bulk endpoint. However, under certain circumstances the + * Bulk-only specification requires a stall. In such cases the driver + * will halt the endpoint and set a flag indicating that it should clear + * the halt in software during the next device reset. Hopefully this + * will permit everything to work correctly. Furthermore, although the + * specification allows the bulk-out endpoint to halt when the host sends + * too much data, implementing this would cause an unavoidable race. + * The driver will always use the "no-stall" approach for OUT transfers. + * + * One subtle point concerns sending status-stage responses for ep0 + * requests. Some of these requests, such as device reset, can involve + * interrupting an ongoing file I/O operation, which might take an + * arbitrarily long time. During that delay the host might give up on + * the original ep0 request and issue a new one. When that happens the + * driver should not notify the host about completion of the original + * request, as the host will no longer be waiting for it. So the driver + * assigns to each ep0 request a unique tag, and it keeps track of the + * tag value of the request associated with a long-running exception + * (device-reset, interface-change, or configuration-change). When the + * exception handler is finished, the status-stage response is submitted + * only if the current ep0 request tag is equal to the exception request + * tag. Thus only the most recently received ep0 request will get a + * status-stage response. + * + * Warning: This driver source file is too long. It ought to be split up + * into a header file plus about 3 separate .c files, to handle the details + * of the Gadget, USB Mass Storage, and SCSI protocols. + */ + +/* #define VERBOSE_DEBUG */ +/* #define DUMP_MSGS */ + +#include <config.h> +#include <hexdump.h> +#include <log.h> +#include <malloc.h> +#include <common.h> +#include <console.h> +#include <g_dnl.h> +#include <dm/devres.h> +#include <linux/bug.h> + +#include <linux/err.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <usb_mass_storage.h> + +#include <asm/unaligned.h> +#include <linux/bitops.h> +#include <linux/usb/gadget.h> +#include <linux/usb/gadget.h> +#include <linux/usb/composite.h> +#include <linux/bitmap.h> +#include <g_dnl.h> + +/*------------------------------------------------------------------------*/ + +#define FSG_DRIVER_DESC "Mass Storage Function" +#define FSG_DRIVER_VERSION "2012/06/5" + +static const char fsg_string_interface[] = "Mass Storage"; + +#define FSG_NO_INTR_EP 1 +#define FSG_NO_DEVICE_STRINGS 1 +#define FSG_NO_OTG 1 +#define FSG_NO_INTR_EP 1 + +#include "storage_common.c" + +/*-------------------------------------------------------------------------*/ + +#define GFP_ATOMIC ((gfp_t) 0) +#define PAGE_CACHE_SHIFT 12 +#define PAGE_CACHE_SIZE (1 << PAGE_CACHE_SHIFT) +#define kthread_create(...) __builtin_return_address(0) +#define wait_for_completion(...) do {} while (0) + +struct kref {int x; }; +struct completion {int x; }; + +struct fsg_dev; +struct fsg_common; + +/* Data shared by all the FSG instances. */ +struct fsg_common { + struct usb_gadget *gadget; + struct fsg_dev *fsg, *new_fsg; + + struct usb_ep *ep0; /* Copy of gadget->ep0 */ + struct usb_request *ep0req; /* Copy of cdev->req */ + unsigned int ep0_req_tag; + + struct fsg_buffhd *next_buffhd_to_fill; + struct fsg_buffhd *next_buffhd_to_drain; + struct fsg_buffhd buffhds[FSG_NUM_BUFFERS]; + + int cmnd_size; + u8 cmnd[MAX_COMMAND_SIZE]; + + unsigned int nluns; + unsigned int lun; + struct fsg_lun luns[FSG_MAX_LUNS]; + + unsigned int bulk_out_maxpacket; + enum fsg_state state; /* For exception handling */ + unsigned int exception_req_tag; + + enum data_direction data_dir; + u32 data_size; + u32 data_size_from_cmnd; + u32 tag; + u32 residue; + u32 usb_amount_left; + + unsigned int can_stall:1; + unsigned int free_storage_on_release:1; + unsigned int phase_error:1; + unsigned int short_packet_received:1; + unsigned int bad_lun_okay:1; + unsigned int running:1; + + int thread_wakeup_needed; + struct completion thread_notifier; + struct task_struct *thread_task; + + /* Callback functions. */ + const struct fsg_operations *ops; + /* Gadget's private data. */ + void *private_data; + + const char *vendor_name; /* 8 characters or less */ + const char *product_name; /* 16 characters or less */ + u16 release; + + /* Vendor (8 chars), product (16 chars), release (4 + * hexadecimal digits) and NUL byte */ + char inquiry_string[8 + 16 + 4 + 1]; + + struct kref ref; +}; + +struct fsg_config { + unsigned nluns; + struct fsg_lun_config { + const char *filename; + char ro; + char removable; + char cdrom; + char nofua; + } luns[FSG_MAX_LUNS]; + + /* Callback functions. */ + const struct fsg_operations *ops; + /* Gadget's private data. */ + void *private_data; + + const char *vendor_name; /* 8 characters or less */ + const char *product_name; /* 16 characters or less */ + + char can_stall; +}; + +struct fsg_dev { + struct usb_function function; + struct usb_gadget *gadget; /* Copy of cdev->gadget */ + struct fsg_common *common; + + u16 interface_number; + + unsigned int bulk_in_enabled:1; + unsigned int bulk_out_enabled:1; + + unsigned long atomic_bitflags; +#define IGNORE_BULK_OUT 0 + + struct usb_ep *bulk_in; + struct usb_ep *bulk_out; +}; + + +static inline int __fsg_is_set(struct fsg_common *common, + const char *func, unsigned line) +{ + if (common->fsg) + return 1; + ERROR(common, "common->fsg is NULL in %s at %u\n", func, line); +#ifdef __UBOOT__ + assert_noisy(false); +#else + WARN_ON(1); +#endif + return 0; +} + +#define fsg_is_set(common) likely(__fsg_is_set(common, __func__, __LINE__)) + + +static inline struct fsg_dev *fsg_from_func(struct usb_function *f) +{ + return container_of(f, struct fsg_dev, function); +} + + +typedef void (*fsg_routine_t)(struct fsg_dev *); + +static int exception_in_progress(struct fsg_common *common) +{ + return common->state > FSG_STATE_IDLE; +} + +/* Make bulk-out requests be divisible by the maxpacket size */ +static void set_bulk_out_req_length(struct fsg_common *common, + struct fsg_buffhd *bh, unsigned int length) +{ + unsigned int rem; + + bh->bulk_out_intended_length = length; + rem = length % common->bulk_out_maxpacket; + if (rem > 0) + length += common->bulk_out_maxpacket - rem; + bh->outreq->length = length; +} + +/*-------------------------------------------------------------------------*/ + +static struct ums *ums; +static int ums_count; +static struct fsg_common *the_fsg_common; +static unsigned int controller_index; + +static int fsg_set_halt(struct fsg_dev *fsg, struct usb_ep *ep) +{ + const char *name; + + if (ep == fsg->bulk_in) + name = "bulk-in"; + else if (ep == fsg->bulk_out) + name = "bulk-out"; + else + name = ep->name; + DBG(fsg, "%s set halt\n", name); + return usb_ep_set_halt(ep); +} + +/*-------------------------------------------------------------------------*/ + +/* These routines may be called in process context or in_irq */ + +/* Caller must hold fsg->lock */ +static void wakeup_thread(struct fsg_common *common) +{ + common->thread_wakeup_needed = 1; +} + +static void raise_exception(struct fsg_common *common, enum fsg_state new_state) +{ + /* Do nothing if a higher-priority exception is already in progress. + * If a lower-or-equal priority exception is in progress, preempt it + * and notify the main thread by sending it a signal. */ + if (common->state <= new_state) { + common->exception_req_tag = common->ep0_req_tag; + common->state = new_state; + common->thread_wakeup_needed = 1; + } +} + +/*-------------------------------------------------------------------------*/ + +static int ep0_queue(struct fsg_common *common) +{ + int rc; + + rc = usb_ep_queue(common->ep0, common->ep0req, GFP_ATOMIC); + common->ep0->driver_data = common; + if (rc != 0 && rc != -ESHUTDOWN) { + /* We can't do much more than wait for a reset */ + WARNING(common, "error in submission: %s --> %d\n", + common->ep0->name, rc); + } + return rc; +} + +/*-------------------------------------------------------------------------*/ + +/* Bulk and interrupt endpoint completion handlers. + * These always run in_irq. */ + +static void bulk_in_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct fsg_common *common = ep->driver_data; + struct fsg_buffhd *bh = req->context; + + if (req->status || req->actual != req->length) + DBG(common, "%s --> %d, %u/%u\n", __func__, + req->status, req->actual, req->length); + if (req->status == -ECONNRESET) /* Request was cancelled */ + usb_ep_fifo_flush(ep); + + /* Hold the lock while we update the request and buffer states */ + bh->inreq_busy = 0; + bh->state = BUF_STATE_EMPTY; + wakeup_thread(common); +} + +static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct fsg_common *common = ep->driver_data; + struct fsg_buffhd *bh = req->context; + + dump_msg(common, "bulk-out", req->buf, req->actual); + if (req->status || req->actual != bh->bulk_out_intended_length) + DBG(common, "%s --> %d, %u/%u\n", __func__, + req->status, req->actual, + bh->bulk_out_intended_length); + if (req->status == -ECONNRESET) /* Request was cancelled */ + usb_ep_fifo_flush(ep); + + /* Hold the lock while we update the request and buffer states */ + bh->outreq_busy = 0; + bh->state = BUF_STATE_FULL; + wakeup_thread(common); +} + +/*-------------------------------------------------------------------------*/ + +/* Ep0 class-specific handlers. These always run in_irq. */ + +static int fsg_setup(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct fsg_dev *fsg = fsg_from_func(f); + struct usb_request *req = fsg->common->ep0req; + u16 w_index = get_unaligned_le16(&ctrl->wIndex); + u16 w_value = get_unaligned_le16(&ctrl->wValue); + u16 w_length = get_unaligned_le16(&ctrl->wLength); + + if (!fsg_is_set(fsg->common)) + return -EOPNOTSUPP; + + switch (ctrl->bRequest) { + + case USB_BULK_RESET_REQUEST: + if (ctrl->bRequestType != + (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE)) + break; + if (w_index != fsg->interface_number || w_value != 0) + return -EDOM; + + /* Raise an exception to stop the current operation + * and reinitialize our state. */ + DBG(fsg, "bulk reset request\n"); + raise_exception(fsg->common, FSG_STATE_RESET); + return DELAYED_STATUS; + + case USB_BULK_GET_MAX_LUN_REQUEST: + if (ctrl->bRequestType != + (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE)) + break; + if (w_index != fsg->interface_number || w_value != 0) + return -EDOM; + VDBG(fsg, "get max LUN\n"); + *(u8 *) req->buf = fsg->common->nluns - 1; + + /* Respond with data/status */ + req->length = min((u16)1, w_length); + return ep0_queue(fsg->common); + } + + VDBG(fsg, + "unknown class-specific control req " + "%02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + get_unaligned_le16(&ctrl->wValue), w_index, w_length); + return -EOPNOTSUPP; +} + +/*-------------------------------------------------------------------------*/ + +/* All the following routines run in process context */ + +/* Use this for bulk or interrupt transfers, not ep0 */ +static void start_transfer(struct fsg_dev *fsg, struct usb_ep *ep, + struct usb_request *req, int *pbusy, + enum fsg_buffer_state *state) +{ + int rc; + + if (ep == fsg->bulk_in) + dump_msg(fsg, "bulk-in", req->buf, req->length); + + *pbusy = 1; + *state = BUF_STATE_BUSY; + rc = usb_ep_queue(ep, req, GFP_KERNEL); + if (rc != 0) { + *pbusy = 0; + *state = BUF_STATE_EMPTY; + + /* We can't do much more than wait for a reset */ + + /* Note: currently the net2280 driver fails zero-length + * submissions if DMA is enabled. */ + if (rc != -ESHUTDOWN && !(rc == -EOPNOTSUPP && + req->length == 0)) + WARNING(fsg, "error in submission: %s --> %d\n", + ep->name, rc); + } +} + +#define START_TRANSFER_OR(common, ep_name, req, pbusy, state) \ + if (fsg_is_set(common)) \ + start_transfer((common)->fsg, (common)->fsg->ep_name, \ + req, pbusy, state); \ + else + +#define START_TRANSFER(common, ep_name, req, pbusy, state) \ + START_TRANSFER_OR(common, ep_name, req, pbusy, state) (void)0 + +static void busy_indicator(void) +{ + static int state; + + switch (state) { + case 0: + puts("\r|"); break; + case 1: + puts("\r/"); break; + case 2: + puts("\r-"); break; + case 3: + puts("\r\\"); break; + case 4: + puts("\r|"); break; + case 5: + puts("\r/"); break; + case 6: + puts("\r-"); break; + case 7: + puts("\r\\"); break; + default: + state = 0; + } + if (state++ == 8) + state = 0; +} + +static int sleep_thread(struct fsg_common *common) +{ + int rc = 0; + int i = 0, k = 0; + + /* Wait until a signal arrives or we are woken up */ + for (;;) { + if (common->thread_wakeup_needed) + break; + + if (++i == 20000) { + busy_indicator(); + i = 0; + k++; + } + + if (k == 10) { + /* Handle CTRL+C */ + if (ctrlc()) + return -EPIPE; + + /* Check cable connection */ + if (!g_dnl_board_usb_cable_connected()) + return -EIO; + + k = 0; + } + + usb_gadget_handle_interrupts(controller_index); + } + common->thread_wakeup_needed = 0; + return rc; +} + +/*-------------------------------------------------------------------------*/ + +static int do_read(struct fsg_common *common) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + u32 lba; + struct fsg_buffhd *bh; + int rc; + u32 amount_left; + loff_t file_offset; + unsigned int amount; + unsigned int partial_page; + ssize_t nread; + + /* Get the starting Logical Block Address and check that it's + * not too big */ + if (common->cmnd[0] == SC_READ_6) + lba = get_unaligned_be24(&common->cmnd[1]); + else { + lba = get_unaligned_be32(&common->cmnd[2]); + + /* We allow DPO (Disable Page Out = don't save data in the + * cache) and FUA (Force Unit Access = don't read from the + * cache), but we don't implement them. */ + if ((common->cmnd[1] & ~0x18) != 0) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + } + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + file_offset = ((loff_t) lba) << 9; + + /* Carry out the file reads */ + amount_left = common->data_size_from_cmnd; + if (unlikely(amount_left == 0)) + return -EIO; /* No default reply */ + + for (;;) { + + /* Figure out how much we need to read: + * Try to read the remaining amount. + * But don't read more than the buffer size. + * And don't try to read past the end of the file. + * Finally, if we're not at a page boundary, don't read past + * the next page. + * If this means reading 0 then we were asked to read past + * the end of file. */ + amount = min(amount_left, FSG_BUFLEN); + partial_page = file_offset & (PAGE_CACHE_SIZE - 1); + if (partial_page > 0) + amount = min(amount, (unsigned int) PAGE_CACHE_SIZE - + partial_page); + + /* Wait for the next buffer to become available */ + bh = common->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(common); + if (rc) + return rc; + } + + /* If we were asked to read past the end of file, + * end with an empty buffer. */ + if (amount == 0) { + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->info_valid = 1; + bh->inreq->length = 0; + bh->state = BUF_STATE_FULL; + break; + } + + /* Perform the read */ + rc = ums[common->lun].read_sector(&ums[common->lun], + file_offset / SECTOR_SIZE, + amount / SECTOR_SIZE, + (char __user *)bh->buf); + if (!rc) + return -EIO; + + nread = rc * SECTOR_SIZE; + + VLDBG(curlun, "file read %u @ %llu -> %d\n", amount, + (unsigned long long) file_offset, + (int) nread); + + if (nread < 0) { + LDBG(curlun, "error in file read: %d\n", + (int) nread); + nread = 0; + } else if (nread < amount) { + LDBG(curlun, "partial file read: %d/%u\n", + (int) nread, amount); + nread -= (nread & 511); /* Round down to a block */ + } + file_offset += nread; + amount_left -= nread; + common->residue -= nread; + bh->inreq->length = nread; + bh->state = BUF_STATE_FULL; + + /* If an error occurred, report it and its position */ + if (nread < amount) { + curlun->sense_data = SS_UNRECOVERED_READ_ERROR; + curlun->info_valid = 1; + break; + } + + if (amount_left == 0) + break; /* No more left to read */ + + /* Send this buffer and go read some more */ + bh->inreq->zero = 0; + START_TRANSFER_OR(common, bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state) + /* Don't know what to do if + * common->fsg is NULL */ + return -EIO; + common->next_buffhd_to_fill = bh->next; + } + + return -EIO; /* No default reply */ +} + +/*-------------------------------------------------------------------------*/ + +static int do_write(struct fsg_common *common) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + u32 lba; + struct fsg_buffhd *bh; + int get_some_more; + u32 amount_left_to_req, amount_left_to_write; + loff_t usb_offset, file_offset; + unsigned int amount; + unsigned int partial_page; + ssize_t nwritten; + int rc; + + if (curlun->ro) { + curlun->sense_data = SS_WRITE_PROTECTED; + return -EINVAL; + } + + /* Get the starting Logical Block Address and check that it's + * not too big */ + if (common->cmnd[0] == SC_WRITE_6) + lba = get_unaligned_be24(&common->cmnd[1]); + else { + lba = get_unaligned_be32(&common->cmnd[2]); + + /* We allow DPO (Disable Page Out = don't save data in the + * cache) and FUA (Force Unit Access = write directly to the + * medium). We don't implement DPO; we implement FUA by + * performing synchronous output. */ + if (common->cmnd[1] & ~0x18) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + } + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + /* Carry out the file writes */ + get_some_more = 1; + file_offset = usb_offset = ((loff_t) lba) << 9; + amount_left_to_req = common->data_size_from_cmnd; + amount_left_to_write = common->data_size_from_cmnd; + + while (amount_left_to_write > 0) { + + /* Queue a request for more data from the host */ + bh = common->next_buffhd_to_fill; + if (bh->state == BUF_STATE_EMPTY && get_some_more) { + + /* Figure out how much we want to get: + * Try to get the remaining amount. + * But don't get more than the buffer size. + * And don't try to go past the end of the file. + * If we're not at a page boundary, + * don't go past the next page. + * If this means getting 0, then we were asked + * to write past the end of file. + * Finally, round down to a block boundary. */ + amount = min(amount_left_to_req, FSG_BUFLEN); + partial_page = usb_offset & (PAGE_CACHE_SIZE - 1); + if (partial_page > 0) + amount = min(amount, + (unsigned int) PAGE_CACHE_SIZE - partial_page); + + if (amount == 0) { + get_some_more = 0; + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->info_valid = 1; + continue; + } + amount -= (amount & 511); + if (amount == 0) { + + /* Why were we were asked to transfer a + * partial block? */ + get_some_more = 0; + continue; + } + + /* Get the next buffer */ + usb_offset += amount; + common->usb_amount_left -= amount; + amount_left_to_req -= amount; + if (amount_left_to_req == 0) + get_some_more = 0; + + /* amount is always divisible by 512, hence by + * the bulk-out maxpacket size */ + bh->outreq->length = amount; + bh->bulk_out_intended_length = amount; + bh->outreq->short_not_ok = 1; + START_TRANSFER_OR(common, bulk_out, bh->outreq, + &bh->outreq_busy, &bh->state) + /* Don't know what to do if + * common->fsg is NULL */ + return -EIO; + common->next_buffhd_to_fill = bh->next; + continue; + } + + /* Write the received data to the backing file */ + bh = common->next_buffhd_to_drain; + if (bh->state == BUF_STATE_EMPTY && !get_some_more) + break; /* We stopped early */ + if (bh->state == BUF_STATE_FULL) { + common->next_buffhd_to_drain = bh->next; + bh->state = BUF_STATE_EMPTY; + + /* Did something go wrong with the transfer? */ + if (bh->outreq->status != 0) { + curlun->sense_data = SS_COMMUNICATION_FAILURE; + curlun->info_valid = 1; + break; + } + + amount = bh->outreq->actual; + + /* Perform the write */ + rc = ums[common->lun].write_sector(&ums[common->lun], + file_offset / SECTOR_SIZE, + amount / SECTOR_SIZE, + (char __user *)bh->buf); + if (!rc) + return -EIO; + nwritten = rc * SECTOR_SIZE; + + VLDBG(curlun, "file write %u @ %llu -> %d\n", amount, + (unsigned long long) file_offset, + (int) nwritten); + + if (nwritten < 0) { + LDBG(curlun, "error in file write: %d\n", + (int) nwritten); + nwritten = 0; + } else if (nwritten < amount) { + LDBG(curlun, "partial file write: %d/%u\n", + (int) nwritten, amount); + nwritten -= (nwritten & 511); + /* Round down to a block */ + } + file_offset += nwritten; + amount_left_to_write -= nwritten; + common->residue -= nwritten; + + /* If an error occurred, report it and its position */ + if (nwritten < amount) { + printf("nwritten:%zd amount:%u\n", nwritten, + amount); + curlun->sense_data = SS_WRITE_ERROR; + curlun->info_valid = 1; + break; + } + + /* Did the host decide to stop early? */ + if (bh->outreq->actual != bh->outreq->length) { + common->short_packet_received = 1; + break; + } + continue; + } + + /* Wait for something to happen */ + rc = sleep_thread(common); + if (rc) + return rc; + } + + return -EIO; /* No default reply */ +} + +/*-------------------------------------------------------------------------*/ + +static int do_synchronize_cache(struct fsg_common *common) +{ + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static int do_verify(struct fsg_common *common) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + u32 lba; + u32 verification_length; + struct fsg_buffhd *bh = common->next_buffhd_to_fill; + loff_t file_offset; + u32 amount_left; + unsigned int amount; + ssize_t nread; + int rc; + + /* Get the starting Logical Block Address and check that it's + * not too big */ + lba = get_unaligned_be32(&common->cmnd[2]); + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + /* We allow DPO (Disable Page Out = don't save data in the + * cache) but we don't implement it. */ + if (common->cmnd[1] & ~0x10) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + verification_length = get_unaligned_be16(&common->cmnd[7]); + if (unlikely(verification_length == 0)) + return -EIO; /* No default reply */ + + /* Prepare to carry out the file verify */ + amount_left = verification_length << 9; + file_offset = ((loff_t) lba) << 9; + + /* Write out all the dirty buffers before invalidating them */ + + /* Just try to read the requested blocks */ + while (amount_left > 0) { + + /* Figure out how much we need to read: + * Try to read the remaining amount, but not more than + * the buffer size. + * And don't try to read past the end of the file. + * If this means reading 0 then we were asked to read + * past the end of file. */ + amount = min(amount_left, FSG_BUFLEN); + if (amount == 0) { + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->info_valid = 1; + break; + } + + /* Perform the read */ + rc = ums[common->lun].read_sector(&ums[common->lun], + file_offset / SECTOR_SIZE, + amount / SECTOR_SIZE, + (char __user *)bh->buf); + if (!rc) + return -EIO; + nread = rc * SECTOR_SIZE; + + VLDBG(curlun, "file read %u @ %llu -> %d\n", amount, + (unsigned long long) file_offset, + (int) nread); + if (nread < 0) { + LDBG(curlun, "error in file verify: %d\n", + (int) nread); + nread = 0; + } else if (nread < amount) { + LDBG(curlun, "partial file verify: %d/%u\n", + (int) nread, amount); + nread -= (nread & 511); /* Round down to a sector */ + } + if (nread == 0) { + curlun->sense_data = SS_UNRECOVERED_READ_ERROR; + curlun->info_valid = 1; + break; + } + file_offset += nread; + amount_left -= nread; + } + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static int do_inquiry(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + static const char vendor_id[] = "Linux "; + u8 *buf = (u8 *) bh->buf; + + if (!curlun) { /* Unsupported LUNs are okay */ + common->bad_lun_okay = 1; + memset(buf, 0, 36); + buf[0] = 0x7f; /* Unsupported, no device-type */ + buf[4] = 31; /* Additional length */ + return 36; + } + + memset(buf, 0, 8); + buf[0] = TYPE_DISK; + buf[1] = curlun->removable ? 0x80 : 0; + buf[2] = 2; /* ANSI SCSI level 2 */ + buf[3] = 2; /* SCSI-2 INQUIRY data format */ + buf[4] = 31; /* Additional length */ + /* No special options */ + sprintf((char *) (buf + 8), "%-8s%-16s%04x", (char*) vendor_id , + ums[common->lun].name, (u16) 0xffff); + + return 36; +} + + +static int do_request_sense(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + u8 *buf = (u8 *) bh->buf; + u32 sd, sdinfo; + int valid; + + /* + * From the SCSI-2 spec., section 7.9 (Unit attention condition): + * + * If a REQUEST SENSE command is received from an initiator + * with a pending unit attention condition (before the target + * generates the contingent allegiance condition), then the + * target shall either: + * a) report any pending sense data and preserve the unit + * attention condition on the logical unit, or, + * b) report the unit attention condition, may discard any + * pending sense data, and clear the unit attention + * condition on the logical unit for that initiator. + * + * FSG normally uses option a); enable this code to use option b). + */ +#if 0 + if (curlun && curlun->unit_attention_data != SS_NO_SENSE) { + curlun->sense_data = curlun->unit_attention_data; + curlun->unit_attention_data = SS_NO_SENSE; + } +#endif + + if (!curlun) { /* Unsupported LUNs are okay */ + common->bad_lun_okay = 1; + sd = SS_LOGICAL_UNIT_NOT_SUPPORTED; + sdinfo = 0; + valid = 0; + } else { + sd = curlun->sense_data; + valid = curlun->info_valid << 7; + curlun->sense_data = SS_NO_SENSE; + curlun->info_valid = 0; + } + + memset(buf, 0, 18); + buf[0] = valid | 0x70; /* Valid, current error */ + buf[2] = SK(sd); + put_unaligned_be32(sdinfo, &buf[3]); /* Sense information */ + buf[7] = 18 - 8; /* Additional sense length */ + buf[12] = ASC(sd); + buf[13] = ASCQ(sd); + return 18; +} + +static int do_read_capacity(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + u32 lba = get_unaligned_be32(&common->cmnd[2]); + int pmi = common->cmnd[8]; + u8 *buf = (u8 *) bh->buf; + + /* Check the PMI and LBA fields */ + if (pmi > 1 || (pmi == 0 && lba != 0)) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + put_unaligned_be32(curlun->num_sectors - 1, &buf[0]); + /* Max logical block */ + put_unaligned_be32(512, &buf[4]); /* Block length */ + return 8; +} + +static int do_read_header(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + int msf = common->cmnd[1] & 0x02; + u32 lba = get_unaligned_be32(&common->cmnd[2]); + u8 *buf = (u8 *) bh->buf; + + if (common->cmnd[1] & ~0x02) { /* Mask away MSF */ + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + memset(buf, 0, 8); + buf[0] = 0x01; /* 2048 bytes of user data, rest is EC */ + store_cdrom_address(&buf[4], msf, lba); + return 8; +} + + +static int do_read_toc(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + int msf = common->cmnd[1] & 0x02; + int start_track = common->cmnd[6]; + u8 *buf = (u8 *) bh->buf; + + if ((common->cmnd[1] & ~0x02) != 0 || /* Mask away MSF */ + start_track > 1) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + memset(buf, 0, 20); + buf[1] = (20-2); /* TOC data length */ + buf[2] = 1; /* First track number */ + buf[3] = 1; /* Last track number */ + buf[5] = 0x16; /* Data track, copying allowed */ + buf[6] = 0x01; /* Only track is number 1 */ + store_cdrom_address(&buf[8], msf, 0); + + buf[13] = 0x16; /* Lead-out track is data */ + buf[14] = 0xAA; /* Lead-out track number */ + store_cdrom_address(&buf[16], msf, curlun->num_sectors); + + return 20; +} + +static int do_mode_sense(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + int mscmnd = common->cmnd[0]; + u8 *buf = (u8 *) bh->buf; + u8 *buf0 = buf; + int pc, page_code; + int changeable_values, all_pages; + int valid_page = 0; + int len, limit; + + if ((common->cmnd[1] & ~0x08) != 0) { /* Mask away DBD */ + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + pc = common->cmnd[2] >> 6; + page_code = common->cmnd[2] & 0x3f; + if (pc == 3) { + curlun->sense_data = SS_SAVING_PARAMETERS_NOT_SUPPORTED; + return -EINVAL; + } + changeable_values = (pc == 1); + all_pages = (page_code == 0x3f); + + /* Write the mode parameter header. Fixed values are: default + * medium type, no cache control (DPOFUA), and no block descriptors. + * The only variable value is the WriteProtect bit. We will fill in + * the mode data length later. */ + memset(buf, 0, 8); + if (mscmnd == SC_MODE_SENSE_6) { + buf[2] = (curlun->ro ? 0x80 : 0x00); /* WP, DPOFUA */ + buf += 4; + limit = 255; + } else { /* SC_MODE_SENSE_10 */ + buf[3] = (curlun->ro ? 0x80 : 0x00); /* WP, DPOFUA */ + buf += 8; + limit = 65535; /* Should really be FSG_BUFLEN */ + } + + /* No block descriptors */ + + /* The mode pages, in numerical order. The only page we support + * is the Caching page. */ + if (page_code == 0x08 || all_pages) { + valid_page = 1; + buf[0] = 0x08; /* Page code */ + buf[1] = 10; /* Page length */ + memset(buf+2, 0, 10); /* None of the fields are changeable */ + + if (!changeable_values) { + buf[2] = 0x04; /* Write cache enable, */ + /* Read cache not disabled */ + /* No cache retention priorities */ + put_unaligned_be16(0xffff, &buf[4]); + /* Don't disable prefetch */ + /* Minimum prefetch = 0 */ + put_unaligned_be16(0xffff, &buf[8]); + /* Maximum prefetch */ + put_unaligned_be16(0xffff, &buf[10]); + /* Maximum prefetch ceiling */ + } + buf += 12; + } + + /* Check that a valid page was requested and the mode data length + * isn't too long. */ + len = buf - buf0; + if (!valid_page || len > limit) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + /* Store the mode data length */ + if (mscmnd == SC_MODE_SENSE_6) + buf0[0] = len - 1; + else + put_unaligned_be16(len - 2, buf0); + return len; +} + + +static int do_start_stop(struct fsg_common *common) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + + if (!curlun) { + return -EINVAL; + } else if (!curlun->removable) { + curlun->sense_data = SS_INVALID_COMMAND; + return -EINVAL; + } + + return 0; +} + +static int do_prevent_allow(struct fsg_common *common) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + int prevent; + + if (!curlun->removable) { + curlun->sense_data = SS_INVALID_COMMAND; + return -EINVAL; + } + + prevent = common->cmnd[4] & 0x01; + if ((common->cmnd[4] & ~0x01) != 0) { /* Mask away Prevent */ + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + if (curlun->prevent_medium_removal && !prevent) + fsg_lun_fsync_sub(curlun); + curlun->prevent_medium_removal = prevent; + return 0; +} + + +static int do_read_format_capacities(struct fsg_common *common, + struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + u8 *buf = (u8 *) bh->buf; + + buf[0] = buf[1] = buf[2] = 0; + buf[3] = 8; /* Only the Current/Maximum Capacity Descriptor */ + buf += 4; + + put_unaligned_be32(curlun->num_sectors, &buf[0]); + /* Number of blocks */ + put_unaligned_be32(512, &buf[4]); /* Block length */ + buf[4] = 0x02; /* Current capacity */ + return 12; +} + + +static int do_mode_select(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + + /* We don't support MODE SELECT */ + if (curlun) + curlun->sense_data = SS_INVALID_COMMAND; + return -EINVAL; +} + + +/*-------------------------------------------------------------------------*/ + +static int halt_bulk_in_endpoint(struct fsg_dev *fsg) +{ + int rc; + + rc = fsg_set_halt(fsg, fsg->bulk_in); + if (rc == -EAGAIN) + VDBG(fsg, "delayed bulk-in endpoint halt\n"); + while (rc != 0) { + if (rc != -EAGAIN) { + WARNING(fsg, "usb_ep_set_halt -> %d\n", rc); + rc = 0; + break; + } + + rc = usb_ep_set_halt(fsg->bulk_in); + } + return rc; +} + +static int wedge_bulk_in_endpoint(struct fsg_dev *fsg) +{ + int rc; + + DBG(fsg, "bulk-in set wedge\n"); + rc = 0; /* usb_ep_set_wedge(fsg->bulk_in); */ + if (rc == -EAGAIN) + VDBG(fsg, "delayed bulk-in endpoint wedge\n"); + while (rc != 0) { + if (rc != -EAGAIN) { + WARNING(fsg, "usb_ep_set_wedge -> %d\n", rc); + rc = 0; + break; + } + } + return rc; +} + +static int pad_with_zeros(struct fsg_dev *fsg) +{ + struct fsg_buffhd *bh = fsg->common->next_buffhd_to_fill; + u32 nkeep = bh->inreq->length; + u32 nsend; + int rc; + + bh->state = BUF_STATE_EMPTY; /* For the first iteration */ + fsg->common->usb_amount_left = nkeep + fsg->common->residue; + while (fsg->common->usb_amount_left > 0) { + + /* Wait for the next buffer to be free */ + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(fsg->common); + if (rc) + return rc; + } + + nsend = min(fsg->common->usb_amount_left, FSG_BUFLEN); + memset(bh->buf + nkeep, 0, nsend - nkeep); + bh->inreq->length = nsend; + bh->inreq->zero = 0; + start_transfer(fsg, fsg->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + bh = fsg->common->next_buffhd_to_fill = bh->next; + fsg->common->usb_amount_left -= nsend; + nkeep = 0; + } + return 0; +} + +static int throw_away_data(struct fsg_common *common) +{ + struct fsg_buffhd *bh; + u32 amount; + int rc; + + for (bh = common->next_buffhd_to_drain; + bh->state != BUF_STATE_EMPTY || common->usb_amount_left > 0; + bh = common->next_buffhd_to_drain) { + + /* Throw away the data in a filled buffer */ + if (bh->state == BUF_STATE_FULL) { + bh->state = BUF_STATE_EMPTY; + common->next_buffhd_to_drain = bh->next; + + /* A short packet or an error ends everything */ + if (bh->outreq->actual != bh->outreq->length || + bh->outreq->status != 0) { + raise_exception(common, + FSG_STATE_ABORT_BULK_OUT); + return -EINTR; + } + continue; + } + + /* Try to submit another request if we need one */ + bh = common->next_buffhd_to_fill; + if (bh->state == BUF_STATE_EMPTY + && common->usb_amount_left > 0) { + amount = min(common->usb_amount_left, FSG_BUFLEN); + + /* amount is always divisible by 512, hence by + * the bulk-out maxpacket size */ + bh->outreq->length = amount; + bh->bulk_out_intended_length = amount; + bh->outreq->short_not_ok = 1; + START_TRANSFER_OR(common, bulk_out, bh->outreq, + &bh->outreq_busy, &bh->state) + /* Don't know what to do if + * common->fsg is NULL */ + return -EIO; + common->next_buffhd_to_fill = bh->next; + common->usb_amount_left -= amount; + continue; + } + + /* Otherwise wait for something to happen */ + rc = sleep_thread(common); + if (rc) + return rc; + } + return 0; +} + + +static int finish_reply(struct fsg_common *common) +{ + struct fsg_buffhd *bh = common->next_buffhd_to_fill; + int rc = 0; + + switch (common->data_dir) { + case DATA_DIR_NONE: + break; /* Nothing to send */ + + /* If we don't know whether the host wants to read or write, + * this must be CB or CBI with an unknown command. We mustn't + * try to send or receive any data. So stall both bulk pipes + * if we can and wait for a reset. */ + case DATA_DIR_UNKNOWN: + if (!common->can_stall) { + /* Nothing */ + } else if (fsg_is_set(common)) { + fsg_set_halt(common->fsg, common->fsg->bulk_out); + rc = halt_bulk_in_endpoint(common->fsg); + } else { + /* Don't know what to do if common->fsg is NULL */ + rc = -EIO; + } + break; + + /* All but the last buffer of data must have already been sent */ + case DATA_DIR_TO_HOST: + if (common->data_size == 0) { + /* Nothing to send */ + + /* If there's no residue, simply send the last buffer */ + } else if (common->residue == 0) { + bh->inreq->zero = 0; + START_TRANSFER_OR(common, bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state) + return -EIO; + common->next_buffhd_to_fill = bh->next; + + /* For Bulk-only, if we're allowed to stall then send the + * short packet and halt the bulk-in endpoint. If we can't + * stall, pad out the remaining data with 0's. */ + } else if (common->can_stall) { + bh->inreq->zero = 1; + START_TRANSFER_OR(common, bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state) + /* Don't know what to do if + * common->fsg is NULL */ + rc = -EIO; + common->next_buffhd_to_fill = bh->next; + if (common->fsg) + rc = halt_bulk_in_endpoint(common->fsg); + } else if (fsg_is_set(common)) { + rc = pad_with_zeros(common->fsg); + } else { + /* Don't know what to do if common->fsg is NULL */ + rc = -EIO; + } + break; + + /* We have processed all we want from the data the host has sent. + * There may still be outstanding bulk-out requests. */ + case DATA_DIR_FROM_HOST: + if (common->residue == 0) { + /* Nothing to receive */ + + /* Did the host stop sending unexpectedly early? */ + } else if (common->short_packet_received) { + raise_exception(common, FSG_STATE_ABORT_BULK_OUT); + rc = -EINTR; + + /* We haven't processed all the incoming data. Even though + * we may be allowed to stall, doing so would cause a race. + * The controller may already have ACK'ed all the remaining + * bulk-out packets, in which case the host wouldn't see a + * STALL. Not realizing the endpoint was halted, it wouldn't + * clear the halt -- leading to problems later on. */ +#if 0 + } else if (common->can_stall) { + if (fsg_is_set(common)) + fsg_set_halt(common->fsg, + common->fsg->bulk_out); + raise_exception(common, FSG_STATE_ABORT_BULK_OUT); + rc = -EINTR; +#endif + + /* We can't stall. Read in the excess data and throw it + * all away. */ + } else { + rc = throw_away_data(common); + } + break; + } + return rc; +} + + +static int send_status(struct fsg_common *common) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + struct fsg_buffhd *bh; + struct bulk_cs_wrap *csw; + int rc; + u8 status = USB_STATUS_PASS; + u32 sd, sdinfo = 0; + + /* Wait for the next buffer to become available */ + bh = common->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(common); + if (rc) + return rc; + } + + if (curlun) + sd = curlun->sense_data; + else if (common->bad_lun_okay) + sd = SS_NO_SENSE; + else + sd = SS_LOGICAL_UNIT_NOT_SUPPORTED; + + if (common->phase_error) { + DBG(common, "sending phase-error status\n"); + status = USB_STATUS_PHASE_ERROR; + sd = SS_INVALID_COMMAND; + } else if (sd != SS_NO_SENSE) { + DBG(common, "sending command-failure status\n"); + status = USB_STATUS_FAIL; + VDBG(common, " sense data: SK x%02x, ASC x%02x, ASCQ x%02x;" + " info x%x\n", + SK(sd), ASC(sd), ASCQ(sd), sdinfo); + } + + /* Store and send the Bulk-only CSW */ + csw = (void *)bh->buf; + + csw->Signature = cpu_to_le32(USB_BULK_CS_SIG); + csw->Tag = common->tag; + csw->Residue = cpu_to_le32(common->residue); + csw->Status = status; + + bh->inreq->length = USB_BULK_CS_WRAP_LEN; + bh->inreq->zero = 0; + START_TRANSFER_OR(common, bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state) + /* Don't know what to do if common->fsg is NULL */ + return -EIO; + + common->next_buffhd_to_fill = bh->next; + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +/* Check whether the command is properly formed and whether its data size + * and direction agree with the values we already have. */ +static int check_command(struct fsg_common *common, int cmnd_size, + enum data_direction data_dir, unsigned int mask, + int needs_medium, const char *name) +{ + int i; + int lun = common->cmnd[1] >> 5; + static const char dirletter[4] = {'u', 'o', 'i', 'n'}; + char hdlen[20]; + struct fsg_lun *curlun; + + hdlen[0] = 0; + if (common->data_dir != DATA_DIR_UNKNOWN) + sprintf(hdlen, ", H%c=%u", dirletter[(int) common->data_dir], + common->data_size); + VDBG(common, "SCSI command: %s; Dc=%d, D%c=%u; Hc=%d%s\n", + name, cmnd_size, dirletter[(int) data_dir], + common->data_size_from_cmnd, common->cmnd_size, hdlen); + + /* We can't reply at all until we know the correct data direction + * and size. */ + if (common->data_size_from_cmnd == 0) + data_dir = DATA_DIR_NONE; + if (common->data_size < common->data_size_from_cmnd) { + /* Host data size < Device data size is a phase error. + * Carry out the command, but only transfer as much as + * we are allowed. */ + common->data_size_from_cmnd = common->data_size; + common->phase_error = 1; + } + common->residue = common->data_size; + common->usb_amount_left = common->data_size; + + /* Conflicting data directions is a phase error */ + if (common->data_dir != data_dir + && common->data_size_from_cmnd > 0) { + common->phase_error = 1; + return -EINVAL; + } + + /* Verify the length of the command itself */ + if (cmnd_size != common->cmnd_size) { + + /* Special case workaround: There are plenty of buggy SCSI + * implementations. Many have issues with cbw->Length + * field passing a wrong command size. For those cases we + * always try to work around the problem by using the length + * sent by the host side provided it is at least as large + * as the correct command length. + * Examples of such cases would be MS-Windows, which issues + * REQUEST SENSE with cbw->Length == 12 where it should + * be 6, and xbox360 issuing INQUIRY, TEST UNIT READY and + * REQUEST SENSE with cbw->Length == 10 where it should + * be 6 as well. + */ + if (cmnd_size <= common->cmnd_size) { + DBG(common, "%s is buggy! Expected length %d " + "but we got %d\n", name, + cmnd_size, common->cmnd_size); + cmnd_size = common->cmnd_size; + } else { + common->phase_error = 1; + return -EINVAL; + } + } + + /* Check that the LUN values are consistent */ + if (common->lun != lun) + DBG(common, "using LUN %d from CBW, not LUN %d from CDB\n", + common->lun, lun); + + /* Check the LUN */ + if (common->lun < common->nluns) { + curlun = &common->luns[common->lun]; + if (common->cmnd[0] != SC_REQUEST_SENSE) { + curlun->sense_data = SS_NO_SENSE; + curlun->info_valid = 0; + } + } else { + curlun = NULL; + common->bad_lun_okay = 0; + + /* INQUIRY and REQUEST SENSE commands are explicitly allowed + * to use unsupported LUNs; all others may not. */ + if (common->cmnd[0] != SC_INQUIRY && + common->cmnd[0] != SC_REQUEST_SENSE) { + DBG(common, "unsupported LUN %d\n", common->lun); + return -EINVAL; + } + } +#if 0 + /* If a unit attention condition exists, only INQUIRY and + * REQUEST SENSE commands are allowed; anything else must fail. */ + if (curlun && curlun->unit_attention_data != SS_NO_SENSE && + common->cmnd[0] != SC_INQUIRY && + common->cmnd[0] != SC_REQUEST_SENSE) { + curlun->sense_data = curlun->unit_attention_data; + curlun->unit_attention_data = SS_NO_SENSE; + return -EINVAL; + } +#endif + /* Check that only command bytes listed in the mask are non-zero */ + common->cmnd[1] &= 0x1f; /* Mask away the LUN */ + for (i = 1; i < cmnd_size; ++i) { + if (common->cmnd[i] && !(mask & (1 << i))) { + if (curlun) + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + } + + return 0; +} + + +static int do_scsi_command(struct fsg_common *common) +{ + struct fsg_buffhd *bh; + int rc; + int reply = -EINVAL; + int i; + static char unknown[16]; + struct fsg_lun *curlun = &common->luns[common->lun]; + + dump_cdb(common); + + /* Wait for the next buffer to become available for data or status */ + bh = common->next_buffhd_to_fill; + common->next_buffhd_to_drain = bh; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(common); + if (rc) + return rc; + } + common->phase_error = 0; + common->short_packet_received = 0; + + down_read(&common->filesem); /* We're using the backing file */ + switch (common->cmnd[0]) { + + case SC_INQUIRY: + common->data_size_from_cmnd = common->cmnd[4]; + reply = check_command(common, 6, DATA_DIR_TO_HOST, + (1<<4), 0, + "INQUIRY"); + if (reply == 0) + reply = do_inquiry(common, bh); + break; + + case SC_MODE_SELECT_6: + common->data_size_from_cmnd = common->cmnd[4]; + reply = check_command(common, 6, DATA_DIR_FROM_HOST, + (1<<1) | (1<<4), 0, + "MODE SELECT(6)"); + if (reply == 0) + reply = do_mode_select(common, bh); + break; + + case SC_MODE_SELECT_10: + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]); + reply = check_command(common, 10, DATA_DIR_FROM_HOST, + (1<<1) | (3<<7), 0, + "MODE SELECT(10)"); + if (reply == 0) + reply = do_mode_select(common, bh); + break; + + case SC_MODE_SENSE_6: + common->data_size_from_cmnd = common->cmnd[4]; + reply = check_command(common, 6, DATA_DIR_TO_HOST, + (1<<1) | (1<<2) | (1<<4), 0, + "MODE SENSE(6)"); + if (reply == 0) + reply = do_mode_sense(common, bh); + break; + + case SC_MODE_SENSE_10: + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]); + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (1<<1) | (1<<2) | (3<<7), 0, + "MODE SENSE(10)"); + if (reply == 0) + reply = do_mode_sense(common, bh); + break; + + case SC_PREVENT_ALLOW_MEDIUM_REMOVAL: + common->data_size_from_cmnd = 0; + reply = check_command(common, 6, DATA_DIR_NONE, + (1<<4), 0, + "PREVENT-ALLOW MEDIUM REMOVAL"); + if (reply == 0) + reply = do_prevent_allow(common); + break; + + case SC_READ_6: + i = common->cmnd[4]; + common->data_size_from_cmnd = (i == 0 ? 256 : i) << 9; + reply = check_command(common, 6, DATA_DIR_TO_HOST, + (7<<1) | (1<<4), 1, + "READ(6)"); + if (reply == 0) + reply = do_read(common); + break; + + case SC_READ_10: + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]) << 9; + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (1<<1) | (0xf<<2) | (3<<7), 1, + "READ(10)"); + if (reply == 0) + reply = do_read(common); + break; + + case SC_READ_12: + common->data_size_from_cmnd = + get_unaligned_be32(&common->cmnd[6]) << 9; + reply = check_command(common, 12, DATA_DIR_TO_HOST, + (1<<1) | (0xf<<2) | (0xf<<6), 1, + "READ(12)"); + if (reply == 0) + reply = do_read(common); + break; + + case SC_READ_CAPACITY: + common->data_size_from_cmnd = 8; + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (0xf<<2) | (1<<8), 1, + "READ CAPACITY"); + if (reply == 0) + reply = do_read_capacity(common, bh); + break; + + case SC_READ_HEADER: + if (!common->luns[common->lun].cdrom) + goto unknown_cmnd; + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]); + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (3<<7) | (0x1f<<1), 1, + "READ HEADER"); + if (reply == 0) + reply = do_read_header(common, bh); + break; + + case SC_READ_TOC: + if (!common->luns[common->lun].cdrom) + goto unknown_cmnd; + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]); + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (7<<6) | (1<<1), 1, + "READ TOC"); + if (reply == 0) + reply = do_read_toc(common, bh); + break; + + case SC_READ_FORMAT_CAPACITIES: + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]); + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (3<<7), 1, + "READ FORMAT CAPACITIES"); + if (reply == 0) + reply = do_read_format_capacities(common, bh); + break; + + case SC_REQUEST_SENSE: + common->data_size_from_cmnd = common->cmnd[4]; + reply = check_command(common, 6, DATA_DIR_TO_HOST, + (1<<4), 0, + "REQUEST SENSE"); + if (reply == 0) + reply = do_request_sense(common, bh); + break; + + case SC_START_STOP_UNIT: + common->data_size_from_cmnd = 0; + reply = check_command(common, 6, DATA_DIR_NONE, + (1<<1) | (1<<4), 0, + "START-STOP UNIT"); + if (reply == 0) + reply = do_start_stop(common); + break; + + case SC_SYNCHRONIZE_CACHE: + common->data_size_from_cmnd = 0; + reply = check_command(common, 10, DATA_DIR_NONE, + (0xf<<2) | (3<<7), 1, + "SYNCHRONIZE CACHE"); + if (reply == 0) + reply = do_synchronize_cache(common); + break; + + case SC_TEST_UNIT_READY: + common->data_size_from_cmnd = 0; + reply = check_command(common, 6, DATA_DIR_NONE, + 0, 1, + "TEST UNIT READY"); + break; + + /* Although optional, this command is used by MS-Windows. We + * support a minimal version: BytChk must be 0. */ + case SC_VERIFY: + common->data_size_from_cmnd = 0; + reply = check_command(common, 10, DATA_DIR_NONE, + (1<<1) | (0xf<<2) | (3<<7), 1, + "VERIFY"); + if (reply == 0) + reply = do_verify(common); + break; + + case SC_WRITE_6: + i = common->cmnd[4]; + common->data_size_from_cmnd = (i == 0 ? 256 : i) << 9; + reply = check_command(common, 6, DATA_DIR_FROM_HOST, + (7<<1) | (1<<4), 1, + "WRITE(6)"); + if (reply == 0) + reply = do_write(common); + break; + + case SC_WRITE_10: + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]) << 9; + reply = check_command(common, 10, DATA_DIR_FROM_HOST, + (1<<1) | (0xf<<2) | (3<<7), 1, + "WRITE(10)"); + if (reply == 0) + reply = do_write(common); + break; + + case SC_WRITE_12: + common->data_size_from_cmnd = + get_unaligned_be32(&common->cmnd[6]) << 9; + reply = check_command(common, 12, DATA_DIR_FROM_HOST, + (1<<1) | (0xf<<2) | (0xf<<6), 1, + "WRITE(12)"); + if (reply == 0) + reply = do_write(common); + break; + + /* Some mandatory commands that we recognize but don't implement. + * They don't mean much in this setting. It's left as an exercise + * for anyone interested to implement RESERVE and RELEASE in terms + * of Posix locks. */ + case SC_FORMAT_UNIT: + case SC_RELEASE: + case SC_RESERVE: + case SC_SEND_DIAGNOSTIC: + /* Fall through */ + + default: +unknown_cmnd: + common->data_size_from_cmnd = 0; + sprintf(unknown, "Unknown x%02x", common->cmnd[0]); + reply = check_command(common, common->cmnd_size, + DATA_DIR_UNKNOWN, 0xff, 0, unknown); + if (reply == 0) { + curlun->sense_data = SS_INVALID_COMMAND; + reply = -EINVAL; + } + break; + } + up_read(&common->filesem); + + if (reply == -EINTR) + return -EINTR; + + /* Set up the single reply buffer for finish_reply() */ + if (reply == -EINVAL) + reply = 0; /* Error reply length */ + if (reply >= 0 && common->data_dir == DATA_DIR_TO_HOST) { + reply = min((u32) reply, common->data_size_from_cmnd); + bh->inreq->length = reply; + bh->state = BUF_STATE_FULL; + common->residue -= reply; + } /* Otherwise it's already set */ + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static int received_cbw(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct usb_request *req = bh->outreq; + struct fsg_bulk_cb_wrap *cbw = req->buf; + struct fsg_common *common = fsg->common; + + /* Was this a real packet? Should it be ignored? */ + if (req->status || test_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags)) + return -EINVAL; + + /* Is the CBW valid? */ + if (req->actual != USB_BULK_CB_WRAP_LEN || + cbw->Signature != cpu_to_le32( + USB_BULK_CB_SIG)) { + DBG(fsg, "invalid CBW: len %u sig 0x%x\n", + req->actual, + le32_to_cpu(cbw->Signature)); + + /* The Bulk-only spec says we MUST stall the IN endpoint + * (6.6.1), so it's unavoidable. It also says we must + * retain this state until the next reset, but there's + * no way to tell the controller driver it should ignore + * Clear-Feature(HALT) requests. + * + * We aren't required to halt the OUT endpoint; instead + * we can simply accept and discard any data received + * until the next reset. */ + wedge_bulk_in_endpoint(fsg); + generic_set_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags); + return -EINVAL; + } + + /* Is the CBW meaningful? */ + if (cbw->Lun >= FSG_MAX_LUNS || cbw->Flags & ~USB_BULK_IN_FLAG || + cbw->Length <= 0 || cbw->Length > MAX_COMMAND_SIZE) { + DBG(fsg, "non-meaningful CBW: lun = %u, flags = 0x%x, " + "cmdlen %u\n", + cbw->Lun, cbw->Flags, cbw->Length); + + /* We can do anything we want here, so let's stall the + * bulk pipes if we are allowed to. */ + if (common->can_stall) { + fsg_set_halt(fsg, fsg->bulk_out); + halt_bulk_in_endpoint(fsg); + } + return -EINVAL; + } + + /* Save the command for later */ + common->cmnd_size = cbw->Length; + memcpy(common->cmnd, cbw->CDB, common->cmnd_size); + if (cbw->Flags & USB_BULK_IN_FLAG) + common->data_dir = DATA_DIR_TO_HOST; + else + common->data_dir = DATA_DIR_FROM_HOST; + common->data_size = le32_to_cpu(cbw->DataTransferLength); + if (common->data_size == 0) + common->data_dir = DATA_DIR_NONE; + common->lun = cbw->Lun; + common->tag = cbw->Tag; + return 0; +} + + +static int get_next_command(struct fsg_common *common) +{ + struct fsg_buffhd *bh; + int rc = 0; + + /* Wait for the next buffer to become available */ + bh = common->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(common); + if (rc) + return rc; + } + + /* Queue a request to read a Bulk-only CBW */ + set_bulk_out_req_length(common, bh, USB_BULK_CB_WRAP_LEN); + bh->outreq->short_not_ok = 1; + START_TRANSFER_OR(common, bulk_out, bh->outreq, + &bh->outreq_busy, &bh->state) + /* Don't know what to do if common->fsg is NULL */ + return -EIO; + + /* We will drain the buffer in software, which means we + * can reuse it for the next filling. No need to advance + * next_buffhd_to_fill. */ + + /* Wait for the CBW to arrive */ + while (bh->state != BUF_STATE_FULL) { + rc = sleep_thread(common); + if (rc) + return rc; + } + + rc = fsg_is_set(common) ? received_cbw(common->fsg, bh) : -EIO; + bh->state = BUF_STATE_EMPTY; + + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +static int enable_endpoint(struct fsg_common *common, struct usb_ep *ep, + const struct usb_endpoint_descriptor *d) +{ + int rc; + + ep->driver_data = common; + rc = usb_ep_enable(ep, d); + if (rc) + ERROR(common, "can't enable %s, result %d\n", ep->name, rc); + return rc; +} + +static int alloc_request(struct fsg_common *common, struct usb_ep *ep, + struct usb_request **preq) +{ + *preq = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (*preq) + return 0; + ERROR(common, "can't allocate request for %s\n", ep->name); + return -ENOMEM; +} + +/* Reset interface setting and re-init endpoint state (toggle etc). */ +static int do_set_interface(struct fsg_common *common, struct fsg_dev *new_fsg) +{ + const struct usb_endpoint_descriptor *d; + struct fsg_dev *fsg; + int i, rc = 0; + + if (common->running) + DBG(common, "reset interface\n"); + +reset: + /* Deallocate the requests */ + if (common->fsg) { + fsg = common->fsg; + + for (i = 0; i < FSG_NUM_BUFFERS; ++i) { + struct fsg_buffhd *bh = &common->buffhds[i]; + + if (bh->inreq) { + usb_ep_free_request(fsg->bulk_in, bh->inreq); + bh->inreq = NULL; + } + if (bh->outreq) { + usb_ep_free_request(fsg->bulk_out, bh->outreq); + bh->outreq = NULL; + } + } + + /* Disable the endpoints */ + if (fsg->bulk_in_enabled) { + usb_ep_disable(fsg->bulk_in); + fsg->bulk_in_enabled = 0; + } + if (fsg->bulk_out_enabled) { + usb_ep_disable(fsg->bulk_out); + fsg->bulk_out_enabled = 0; + } + + common->fsg = NULL; + /* wake_up(&common->fsg_wait); */ + } + + common->running = 0; + if (!new_fsg || rc) + return rc; + + common->fsg = new_fsg; + fsg = common->fsg; + + /* Enable the endpoints */ + d = fsg_ep_desc(common->gadget, + &fsg_fs_bulk_in_desc, &fsg_hs_bulk_in_desc); + rc = enable_endpoint(common, fsg->bulk_in, d); + if (rc) + goto reset; + fsg->bulk_in_enabled = 1; + + d = fsg_ep_desc(common->gadget, + &fsg_fs_bulk_out_desc, &fsg_hs_bulk_out_desc); + rc = enable_endpoint(common, fsg->bulk_out, d); + if (rc) + goto reset; + fsg->bulk_out_enabled = 1; + common->bulk_out_maxpacket = + le16_to_cpu(get_unaligned(&d->wMaxPacketSize)); + generic_clear_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags); + + /* Allocate the requests */ + for (i = 0; i < FSG_NUM_BUFFERS; ++i) { + struct fsg_buffhd *bh = &common->buffhds[i]; + + rc = alloc_request(common, fsg->bulk_in, &bh->inreq); + if (rc) + goto reset; + rc = alloc_request(common, fsg->bulk_out, &bh->outreq); + if (rc) + goto reset; + bh->inreq->buf = bh->outreq->buf = bh->buf; + bh->inreq->context = bh->outreq->context = bh; + bh->inreq->complete = bulk_in_complete; + bh->outreq->complete = bulk_out_complete; + } + + common->running = 1; + + return rc; +} + + +/****************************** ALT CONFIGS ******************************/ + + +static int fsg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct fsg_dev *fsg = fsg_from_func(f); + fsg->common->new_fsg = fsg; + raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE); + return 0; +} + +static void fsg_disable(struct usb_function *f) +{ + struct fsg_dev *fsg = fsg_from_func(f); + fsg->common->new_fsg = NULL; + raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE); +} + +/*-------------------------------------------------------------------------*/ + +static void handle_exception(struct fsg_common *common) +{ + int i; + struct fsg_buffhd *bh; + enum fsg_state old_state; + struct fsg_lun *curlun; + unsigned int exception_req_tag; + + /* Cancel all the pending transfers */ + if (common->fsg) { + for (i = 0; i < FSG_NUM_BUFFERS; ++i) { + bh = &common->buffhds[i]; + if (bh->inreq_busy) + usb_ep_dequeue(common->fsg->bulk_in, bh->inreq); + if (bh->outreq_busy) + usb_ep_dequeue(common->fsg->bulk_out, + bh->outreq); + } + + /* Wait until everything is idle */ + for (;;) { + int num_active = 0; + for (i = 0; i < FSG_NUM_BUFFERS; ++i) { + bh = &common->buffhds[i]; + num_active += bh->inreq_busy + bh->outreq_busy; + } + if (num_active == 0) + break; + if (sleep_thread(common)) + return; + } + + /* Clear out the controller's fifos */ + if (common->fsg->bulk_in_enabled) + usb_ep_fifo_flush(common->fsg->bulk_in); + if (common->fsg->bulk_out_enabled) + usb_ep_fifo_flush(common->fsg->bulk_out); + } + + /* Reset the I/O buffer states and pointers, the SCSI + * state, and the exception. Then invoke the handler. */ + + for (i = 0; i < FSG_NUM_BUFFERS; ++i) { + bh = &common->buffhds[i]; + bh->state = BUF_STATE_EMPTY; + } + common->next_buffhd_to_fill = &common->buffhds[0]; + common->next_buffhd_to_drain = &common->buffhds[0]; + exception_req_tag = common->exception_req_tag; + old_state = common->state; + + if (old_state == FSG_STATE_ABORT_BULK_OUT) + common->state = FSG_STATE_STATUS_PHASE; + else { + for (i = 0; i < common->nluns; ++i) { + curlun = &common->luns[i]; + curlun->sense_data = SS_NO_SENSE; + curlun->info_valid = 0; + } + common->state = FSG_STATE_IDLE; + } + + /* Carry out any extra actions required for the exception */ + switch (old_state) { + case FSG_STATE_ABORT_BULK_OUT: + send_status(common); + + if (common->state == FSG_STATE_STATUS_PHASE) + common->state = FSG_STATE_IDLE; + break; + + case FSG_STATE_RESET: + /* In case we were forced against our will to halt a + * bulk endpoint, clear the halt now. (The SuperH UDC + * requires this.) */ + if (!fsg_is_set(common)) + break; + if (test_and_clear_bit(IGNORE_BULK_OUT, + &common->fsg->atomic_bitflags)) + usb_ep_clear_halt(common->fsg->bulk_in); + + if (common->ep0_req_tag == exception_req_tag) + ep0_queue(common); /* Complete the status stage */ + + break; + + case FSG_STATE_CONFIG_CHANGE: + do_set_interface(common, common->new_fsg); + break; + + case FSG_STATE_EXIT: + case FSG_STATE_TERMINATED: + do_set_interface(common, NULL); /* Free resources */ + common->state = FSG_STATE_TERMINATED; /* Stop the thread */ + break; + + case FSG_STATE_INTERFACE_CHANGE: + case FSG_STATE_DISCONNECT: + case FSG_STATE_COMMAND_PHASE: + case FSG_STATE_DATA_PHASE: + case FSG_STATE_STATUS_PHASE: + case FSG_STATE_IDLE: + break; + } +} + +/*-------------------------------------------------------------------------*/ + +int fsg_main_thread(void *common_) +{ + int ret; + struct fsg_common *common = the_fsg_common; + /* The main loop */ + do { + if (exception_in_progress(common)) { + handle_exception(common); + continue; + } + + if (!common->running) { + ret = sleep_thread(common); + if (ret) + return ret; + + continue; + } + + ret = get_next_command(common); + if (ret) + return ret; + + if (!exception_in_progress(common)) + common->state = FSG_STATE_DATA_PHASE; + + if (do_scsi_command(common) || finish_reply(common)) + continue; + + if (!exception_in_progress(common)) + common->state = FSG_STATE_STATUS_PHASE; + + if (send_status(common)) + continue; + + if (!exception_in_progress(common)) + common->state = FSG_STATE_IDLE; + } while (0); + + common->thread_task = NULL; + + return 0; +} + +static void fsg_common_release(struct kref *ref); + +static struct fsg_common *fsg_common_init(struct fsg_common *common, + struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + struct fsg_buffhd *bh; + struct fsg_lun *curlun; + int nluns, i, rc; + + /* Find out how many LUNs there should be */ + nluns = ums_count; + if (nluns < 1 || nluns > FSG_MAX_LUNS) { + printf("invalid number of LUNs: %u\n", nluns); + return ERR_PTR(-EINVAL); + } + + /* Allocate? */ + if (!common) { + common = calloc(sizeof(*common), 1); + if (!common) + return ERR_PTR(-ENOMEM); + common->free_storage_on_release = 1; + } else { + memset(common, 0, sizeof(*common)); + common->free_storage_on_release = 0; + } + + common->ops = NULL; + common->private_data = NULL; + + common->gadget = gadget; + common->ep0 = gadget->ep0; + common->ep0req = cdev->req; + + /* Maybe allocate device-global string IDs, and patch descriptors */ + if (fsg_strings[FSG_STRING_INTERFACE].id == 0) { + rc = usb_string_id(cdev); + if (unlikely(rc < 0)) + goto error_release; + fsg_strings[FSG_STRING_INTERFACE].id = rc; + fsg_intf_desc.iInterface = rc; + } + + /* Create the LUNs, open their backing files, and register the + * LUN devices in sysfs. */ + curlun = calloc(nluns, sizeof *curlun); + if (!curlun) { + rc = -ENOMEM; + goto error_release; + } + common->nluns = nluns; + + for (i = 0; i < nluns; i++) { + common->luns[i].removable = 1; + + rc = fsg_lun_open(&common->luns[i], ums[i].num_sectors, ""); + if (rc) + goto error_luns; + } + common->lun = 0; + + /* Data buffers cyclic list */ + bh = common->buffhds; + + i = FSG_NUM_BUFFERS; + goto buffhds_first_it; + do { + bh->next = bh + 1; + ++bh; +buffhds_first_it: + bh->inreq_busy = 0; + bh->outreq_busy = 0; + bh->buf = memalign(CONFIG_SYS_CACHELINE_SIZE, FSG_BUFLEN); + if (unlikely(!bh->buf)) { + rc = -ENOMEM; + goto error_release; + } + } while (--i); + bh->next = common->buffhds; + + snprintf(common->inquiry_string, sizeof common->inquiry_string, + "%-8s%-16s%04x", + "Linux ", + "File-Store Gadget", + 0xffff); + + /* Some peripheral controllers are known not to be able to + * halt bulk endpoints correctly. If one of them is present, + * disable stalls. + */ + + /* Tell the thread to start working */ + common->thread_task = + kthread_create(fsg_main_thread, common, + OR(cfg->thread_name, "file-storage")); + if (IS_ERR(common->thread_task)) { + rc = PTR_ERR(common->thread_task); + goto error_release; + } + +#undef OR + /* Information */ + INFO(common, FSG_DRIVER_DESC ", version: " FSG_DRIVER_VERSION "\n"); + INFO(common, "Number of LUNs=%d\n", common->nluns); + + return common; + +error_luns: + common->nluns = i + 1; +error_release: + common->state = FSG_STATE_TERMINATED; /* The thread is dead */ + /* Call fsg_common_release() directly, ref might be not + * initialised */ + fsg_common_release(&common->ref); + return ERR_PTR(rc); +} + +static void fsg_common_release(struct kref *ref) +{ + struct fsg_common *common = container_of(ref, struct fsg_common, ref); + + /* If the thread isn't already dead, tell it to exit now */ + if (common->state != FSG_STATE_TERMINATED) { + raise_exception(common, FSG_STATE_EXIT); + wait_for_completion(&common->thread_notifier); + } + + if (likely(common->luns)) { + struct fsg_lun *lun = common->luns; + unsigned i = common->nluns; + + /* In error recovery common->nluns may be zero. */ + for (; i; --i, ++lun) + fsg_lun_close(lun); + + kfree(common->luns); + } + + { + struct fsg_buffhd *bh = common->buffhds; + unsigned i = FSG_NUM_BUFFERS; + do { + kfree(bh->buf); + } while (++bh, --i); + } + + if (common->free_storage_on_release) + kfree(common); +} + + +/*-------------------------------------------------------------------------*/ + +/** + * usb_copy_descriptors - copy a vector of USB descriptors + * @src: null-terminated vector to copy + * Context: initialization code, which may sleep + * + * This makes a copy of a vector of USB descriptors. Its primary use + * is to support usb_function objects which can have multiple copies, + * each needing different descriptors. Functions may have static + * tables of descriptors, which are used as templates and customized + * with identifiers (for interfaces, strings, endpoints, and more) + * as needed by a given function instance. + */ +struct usb_descriptor_header ** +usb_copy_descriptors(struct usb_descriptor_header **src) +{ + struct usb_descriptor_header **tmp; + unsigned bytes; + unsigned n_desc; + void *mem; + struct usb_descriptor_header **ret; + + /* count descriptors and their sizes; then add vector size */ + for (bytes = 0, n_desc = 0, tmp = src; *tmp; tmp++, n_desc++) + bytes += (*tmp)->bLength; + bytes += (n_desc + 1) * sizeof(*tmp); + + mem = memalign(CONFIG_SYS_CACHELINE_SIZE, bytes); + if (!mem) + return NULL; + + /* fill in pointers starting at "tmp", + * to descriptors copied starting at "mem"; + * and return "ret" + */ + tmp = mem; + ret = mem; + mem += (n_desc + 1) * sizeof(*tmp); + while (*src) { + memcpy(mem, *src, (*src)->bLength); + *tmp = mem; + tmp++; + mem += (*src)->bLength; + src++; + } + *tmp = NULL; + + return ret; +} + +static void fsg_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct fsg_dev *fsg = fsg_from_func(f); + + DBG(fsg, "unbind\n"); + if (fsg->common->fsg == fsg) { + fsg->common->new_fsg = NULL; + raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE); + } + + free(fsg->function.descriptors); + free(fsg->function.hs_descriptors); + kfree(fsg); +} + +static int fsg_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct fsg_dev *fsg = fsg_from_func(f); + struct usb_gadget *gadget = c->cdev->gadget; + int i; + struct usb_ep *ep; + fsg->gadget = gadget; + + /* New interface */ + i = usb_interface_id(c, f); + if (i < 0) + return i; + fsg_intf_desc.bInterfaceNumber = i; + fsg->interface_number = i; + + /* Find all the endpoints we will use */ + ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_in_desc); + if (!ep) + goto autoconf_fail; + ep->driver_data = fsg->common; /* claim the endpoint */ + fsg->bulk_in = ep; + + ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_out_desc); + if (!ep) + goto autoconf_fail; + ep->driver_data = fsg->common; /* claim the endpoint */ + fsg->bulk_out = ep; + + /* Copy descriptors */ + f->descriptors = usb_copy_descriptors(fsg_fs_function); + if (unlikely(!f->descriptors)) + return -ENOMEM; + + if (gadget_is_dualspeed(gadget)) { + /* Assume endpoint addresses are the same for both speeds */ + fsg_hs_bulk_in_desc.bEndpointAddress = + fsg_fs_bulk_in_desc.bEndpointAddress; + fsg_hs_bulk_out_desc.bEndpointAddress = + fsg_fs_bulk_out_desc.bEndpointAddress; + f->hs_descriptors = usb_copy_descriptors(fsg_hs_function); + if (unlikely(!f->hs_descriptors)) { + free(f->descriptors); + return -ENOMEM; + } + } + return 0; + +autoconf_fail: + ERROR(fsg, "unable to autoconfigure all endpoints\n"); + return -ENOTSUPP; +} + + +/****************************** ADD FUNCTION ******************************/ + +static struct usb_gadget_strings *fsg_strings_array[] = { + &fsg_stringtab, + NULL, +}; + +static int fsg_bind_config(struct usb_composite_dev *cdev, + struct usb_configuration *c, + struct fsg_common *common) +{ + struct fsg_dev *fsg; + int rc; + + fsg = calloc(1, sizeof *fsg); + if (!fsg) + return -ENOMEM; + fsg->function.name = FSG_DRIVER_DESC; + fsg->function.strings = fsg_strings_array; + fsg->function.bind = fsg_bind; + fsg->function.unbind = fsg_unbind; + fsg->function.setup = fsg_setup; + fsg->function.set_alt = fsg_set_alt; + fsg->function.disable = fsg_disable; + + fsg->common = common; + common->fsg = fsg; + /* Our caller holds a reference to common structure so we + * don't have to be worry about it being freed until we return + * from this function. So instead of incrementing counter now + * and decrement in error recovery we increment it only when + * call to usb_add_function() was successful. */ + + rc = usb_add_function(c, &fsg->function); + + if (rc) + kfree(fsg); + + return rc; +} + +int fsg_add(struct usb_configuration *c) +{ + struct fsg_common *fsg_common; + + fsg_common = fsg_common_init(NULL, c->cdev); + + fsg_common->vendor_name = 0; + fsg_common->product_name = 0; + fsg_common->release = 0xffff; + + fsg_common->ops = NULL; + fsg_common->private_data = NULL; + + the_fsg_common = fsg_common; + + return fsg_bind_config(c->cdev, c, fsg_common); +} + +int fsg_init(struct ums *ums_devs, int count, unsigned int controller_idx) +{ + ums = ums_devs; + ums_count = count; + controller_index = controller_idx; + + return 0; +} + +DECLARE_GADGET_BIND_CALLBACK(usb_dnl_ums, fsg_add); diff --git a/roms/u-boot/drivers/usb/gadget/f_rockusb.c b/roms/u-boot/drivers/usb/gadget/f_rockusb.c new file mode 100644 index 000000000..bd846ce9a --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/f_rockusb.c @@ -0,0 +1,934 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2017 + * + * Eddie Cai <eddie.cai.linux@gmail.com> + */ +#include <command.h> +#include <config.h> +#include <common.h> +#include <env.h> +#include <errno.h> +#include <log.h> +#include <malloc.h> +#include <memalign.h> +#include <part.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb/composite.h> +#include <linux/compiler.h> +#include <version.h> +#include <g_dnl.h> +#include <asm/arch-rockchip/f_rockusb.h> + +static inline struct f_rockusb *func_to_rockusb(struct usb_function *f) +{ + return container_of(f, struct f_rockusb, usb_function); +} + +static struct usb_endpoint_descriptor fs_ep_in = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(64), +}; + +static struct usb_endpoint_descriptor fs_ep_out = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(64), +}; + +static struct usb_endpoint_descriptor hs_ep_in = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor hs_ep_out = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_interface_descriptor interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0x00, + .bAlternateSetting = 0x00, + .bNumEndpoints = 0x02, + .bInterfaceClass = ROCKUSB_INTERFACE_CLASS, + .bInterfaceSubClass = ROCKUSB_INTERFACE_SUB_CLASS, + .bInterfaceProtocol = ROCKUSB_INTERFACE_PROTOCOL, +}; + +static struct usb_descriptor_header *rkusb_fs_function[] = { + (struct usb_descriptor_header *)&interface_desc, + (struct usb_descriptor_header *)&fs_ep_in, + (struct usb_descriptor_header *)&fs_ep_out, +}; + +static struct usb_descriptor_header *rkusb_hs_function[] = { + (struct usb_descriptor_header *)&interface_desc, + (struct usb_descriptor_header *)&hs_ep_in, + (struct usb_descriptor_header *)&hs_ep_out, + NULL, +}; + +static const char rkusb_name[] = "Rockchip Rockusb"; + +static struct usb_string rkusb_string_defs[] = { + [0].s = rkusb_name, + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_rkusb = { + .language = 0x0409, /* en-us */ + .strings = rkusb_string_defs, +}; + +static struct usb_gadget_strings *rkusb_strings[] = { + &stringtab_rkusb, + NULL, +}; + +static struct f_rockusb *rockusb_func; +static void rx_handler_command(struct usb_ep *ep, struct usb_request *req); +static int rockusb_tx_write_csw(u32 tag, int residue, u8 status, int size); + +struct f_rockusb *get_rkusb(void) +{ + struct f_rockusb *f_rkusb = rockusb_func; + + if (!f_rkusb) { + f_rkusb = memalign(CONFIG_SYS_CACHELINE_SIZE, sizeof(*f_rkusb)); + if (!f_rkusb) + return NULL; + + rockusb_func = f_rkusb; + memset(f_rkusb, 0, sizeof(*f_rkusb)); + } + + if (!f_rkusb->buf_head) { + f_rkusb->buf_head = memalign(CONFIG_SYS_CACHELINE_SIZE, + RKUSB_BUF_SIZE); + if (!f_rkusb->buf_head) + return NULL; + + f_rkusb->buf = f_rkusb->buf_head; + memset(f_rkusb->buf_head, 0, RKUSB_BUF_SIZE); + } + return f_rkusb; +} + +static struct usb_endpoint_descriptor *rkusb_ep_desc( +struct usb_gadget *g, +struct usb_endpoint_descriptor *fs, +struct usb_endpoint_descriptor *hs) +{ + if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) + return hs; + return fs; +} + +static void rockusb_complete(struct usb_ep *ep, struct usb_request *req) +{ + int status = req->status; + + if (!status) + return; + debug("status: %d ep '%s' trans: %d\n", status, ep->name, req->actual); +} + +/* config the rockusb device*/ +static int rockusb_bind(struct usb_configuration *c, struct usb_function *f) +{ + int id; + struct usb_gadget *gadget = c->cdev->gadget; + struct f_rockusb *f_rkusb = func_to_rockusb(f); + const char *s; + + id = usb_interface_id(c, f); + if (id < 0) + return id; + interface_desc.bInterfaceNumber = id; + + id = usb_string_id(c->cdev); + if (id < 0) + return id; + + rkusb_string_defs[0].id = id; + interface_desc.iInterface = id; + + f_rkusb->in_ep = usb_ep_autoconfig(gadget, &fs_ep_in); + if (!f_rkusb->in_ep) + return -ENODEV; + f_rkusb->in_ep->driver_data = c->cdev; + + f_rkusb->out_ep = usb_ep_autoconfig(gadget, &fs_ep_out); + if (!f_rkusb->out_ep) + return -ENODEV; + f_rkusb->out_ep->driver_data = c->cdev; + + f->descriptors = rkusb_fs_function; + + if (gadget_is_dualspeed(gadget)) { + hs_ep_in.bEndpointAddress = fs_ep_in.bEndpointAddress; + hs_ep_out.bEndpointAddress = fs_ep_out.bEndpointAddress; + f->hs_descriptors = rkusb_hs_function; + } + + s = env_get("serial#"); + if (s) + g_dnl_set_serialnumber((char *)s); + + return 0; +} + +static void rockusb_unbind(struct usb_configuration *c, struct usb_function *f) +{ + /* clear the configuration*/ + memset(rockusb_func, 0, sizeof(*rockusb_func)); +} + +static void rockusb_disable(struct usb_function *f) +{ + struct f_rockusb *f_rkusb = func_to_rockusb(f); + + usb_ep_disable(f_rkusb->out_ep); + usb_ep_disable(f_rkusb->in_ep); + + if (f_rkusb->out_req) { + free(f_rkusb->out_req->buf); + usb_ep_free_request(f_rkusb->out_ep, f_rkusb->out_req); + f_rkusb->out_req = NULL; + } + if (f_rkusb->in_req) { + free(f_rkusb->in_req->buf); + usb_ep_free_request(f_rkusb->in_ep, f_rkusb->in_req); + f_rkusb->in_req = NULL; + } + if (f_rkusb->buf_head) { + free(f_rkusb->buf_head); + f_rkusb->buf_head = NULL; + f_rkusb->buf = NULL; + } +} + +static struct usb_request *rockusb_start_ep(struct usb_ep *ep) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, 0); + if (!req) + return NULL; + + req->length = EP_BUFFER_SIZE; + req->buf = memalign(CONFIG_SYS_CACHELINE_SIZE, EP_BUFFER_SIZE); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + memset(req->buf, 0, req->length); + + return req; +} + +static int rockusb_set_alt(struct usb_function *f, unsigned int interface, + unsigned int alt) +{ + int ret; + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_gadget *gadget = cdev->gadget; + struct f_rockusb *f_rkusb = func_to_rockusb(f); + const struct usb_endpoint_descriptor *d; + + debug("%s: func: %s intf: %d alt: %d\n", + __func__, f->name, interface, alt); + + d = rkusb_ep_desc(gadget, &fs_ep_out, &hs_ep_out); + ret = usb_ep_enable(f_rkusb->out_ep, d); + if (ret) { + printf("failed to enable out ep\n"); + return ret; + } + + f_rkusb->out_req = rockusb_start_ep(f_rkusb->out_ep); + if (!f_rkusb->out_req) { + printf("failed to alloc out req\n"); + ret = -EINVAL; + goto err; + } + f_rkusb->out_req->complete = rx_handler_command; + + d = rkusb_ep_desc(gadget, &fs_ep_in, &hs_ep_in); + ret = usb_ep_enable(f_rkusb->in_ep, d); + if (ret) { + printf("failed to enable in ep\n"); + goto err; + } + + f_rkusb->in_req = rockusb_start_ep(f_rkusb->in_ep); + if (!f_rkusb->in_req) { + printf("failed alloc req in\n"); + ret = -EINVAL; + goto err; + } + f_rkusb->in_req->complete = rockusb_complete; + + ret = usb_ep_queue(f_rkusb->out_ep, f_rkusb->out_req, 0); + if (ret) + goto err; + + return 0; +err: + rockusb_disable(f); + return ret; +} + +static int rockusb_add(struct usb_configuration *c) +{ + struct f_rockusb *f_rkusb = get_rkusb(); + int status; + + debug("%s: cdev: 0x%p\n", __func__, c->cdev); + + f_rkusb->usb_function.name = "f_rockusb"; + f_rkusb->usb_function.bind = rockusb_bind; + f_rkusb->usb_function.unbind = rockusb_unbind; + f_rkusb->usb_function.set_alt = rockusb_set_alt; + f_rkusb->usb_function.disable = rockusb_disable; + f_rkusb->usb_function.strings = rkusb_strings; + + status = usb_add_function(c, &f_rkusb->usb_function); + if (status) { + free(f_rkusb->buf_head); + free(f_rkusb); + rockusb_func = NULL; + } + return status; +} + +void rockusb_dev_init(char *dev_type, int dev_index) +{ + struct f_rockusb *f_rkusb = get_rkusb(); + + f_rkusb->dev_type = dev_type; + f_rkusb->dev_index = dev_index; +} + +DECLARE_GADGET_BIND_CALLBACK(usb_dnl_rockusb, rockusb_add); + +static int rockusb_tx_write(const char *buffer, unsigned int buffer_size) +{ + struct usb_request *in_req = rockusb_func->in_req; + int ret; + + memcpy(in_req->buf, buffer, buffer_size); + in_req->length = buffer_size; + debug("Transferring 0x%x bytes\n", buffer_size); + usb_ep_dequeue(rockusb_func->in_ep, in_req); + ret = usb_ep_queue(rockusb_func->in_ep, in_req, 0); + if (ret) + printf("Error %d on queue\n", ret); + return 0; +} + +static int rockusb_tx_write_str(const char *buffer) +{ + return rockusb_tx_write(buffer, strlen(buffer)); +} + +#ifdef DEBUG +static void printcbw(char *buf) +{ + ALLOC_CACHE_ALIGN_BUFFER(struct fsg_bulk_cb_wrap, cbw, + sizeof(struct fsg_bulk_cb_wrap)); + + memcpy((char *)cbw, buf, USB_BULK_CB_WRAP_LEN); + + debug("cbw: signature:%x\n", cbw->signature); + debug("cbw: tag=%x\n", cbw->tag); + debug("cbw: data_transfer_length=%d\n", cbw->data_transfer_length); + debug("cbw: flags=%x\n", cbw->flags); + debug("cbw: lun=%d\n", cbw->lun); + debug("cbw: length=%d\n", cbw->length); + debug("cbw: ucOperCode=%x\n", cbw->CDB[0]); + debug("cbw: ucReserved=%x\n", cbw->CDB[1]); + debug("cbw: dwAddress:%x %x %x %x\n", cbw->CDB[5], cbw->CDB[4], + cbw->CDB[3], cbw->CDB[2]); + debug("cbw: ucReserved2=%x\n", cbw->CDB[6]); + debug("cbw: uslength:%x %x\n", cbw->CDB[8], cbw->CDB[7]); +} + +static void printcsw(char *buf) +{ + ALLOC_CACHE_ALIGN_BUFFER(struct bulk_cs_wrap, csw, + sizeof(struct bulk_cs_wrap)); + memcpy((char *)csw, buf, USB_BULK_CS_WRAP_LEN); + debug("csw: signature:%x\n", csw->signature); + debug("csw: tag:%x\n", csw->tag); + debug("csw: residue:%x\n", csw->residue); + debug("csw: status:%x\n", csw->status); +} +#endif + +static int rockusb_tx_write_csw(u32 tag, int residue, u8 status, int size) +{ + ALLOC_CACHE_ALIGN_BUFFER(struct bulk_cs_wrap, csw, + sizeof(struct bulk_cs_wrap)); + csw->signature = cpu_to_le32(USB_BULK_CS_SIG); + csw->tag = tag; + csw->residue = cpu_to_be32(residue); + csw->status = status; +#ifdef DEBUG + printcsw((char *)csw); +#endif + return rockusb_tx_write((char *)csw, size); +} + +static void tx_handler_send_csw(struct usb_ep *ep, struct usb_request *req) +{ + struct f_rockusb *f_rkusb = get_rkusb(); + int status = req->status; + + if (status) + debug("status: %d ep '%s' trans: %d\n", + status, ep->name, req->actual); + + /* Return back to default in_req complete function after sending CSW */ + req->complete = rockusb_complete; + rockusb_tx_write_csw(f_rkusb->tag, 0, CSW_GOOD, USB_BULK_CS_WRAP_LEN); +} + +static unsigned int rx_bytes_expected(struct usb_ep *ep) +{ + struct f_rockusb *f_rkusb = get_rkusb(); + int rx_remain = f_rkusb->dl_size - f_rkusb->dl_bytes; + unsigned int rem; + unsigned int maxpacket = ep->maxpacket; + + if (rx_remain <= 0) + return 0; + else if (rx_remain > EP_BUFFER_SIZE) + return EP_BUFFER_SIZE; + + rem = rx_remain % maxpacket; + if (rem > 0) + rx_remain = rx_remain + (maxpacket - rem); + + return rx_remain; +} + +/* usb_request complete call back to handle upload image */ +static void tx_handler_ul_image(struct usb_ep *ep, struct usb_request *req) +{ + ALLOC_CACHE_ALIGN_BUFFER(char, rbuffer, RKBLOCK_BUF_SIZE); + struct f_rockusb *f_rkusb = get_rkusb(); + struct usb_request *in_req = rockusb_func->in_req; + int ret; + + /* Print error status of previous transfer */ + if (req->status) + debug("status: %d ep '%s' trans: %d len %d\n", req->status, + ep->name, req->actual, req->length); + + /* On transfer complete reset in_req and feedback host with CSW_GOOD */ + if (f_rkusb->ul_bytes >= f_rkusb->ul_size) { + in_req->length = 0; + in_req->complete = rockusb_complete; + + rockusb_tx_write_csw(f_rkusb->tag, 0, CSW_GOOD, + USB_BULK_CS_WRAP_LEN); + return; + } + + /* Proceed with current chunk */ + unsigned int transfer_size = f_rkusb->ul_size - f_rkusb->ul_bytes; + + if (transfer_size > RKBLOCK_BUF_SIZE) + transfer_size = RKBLOCK_BUF_SIZE; + /* Read at least one block */ + unsigned int blkcount = (transfer_size + f_rkusb->desc->blksz - 1) / + f_rkusb->desc->blksz; + + debug("ul %x bytes, %x blks, read lba %x, ul_size:%x, ul_bytes:%x, ", + transfer_size, blkcount, f_rkusb->lba, + f_rkusb->ul_size, f_rkusb->ul_bytes); + + int blks = blk_dread(f_rkusb->desc, f_rkusb->lba, blkcount, rbuffer); + + if (blks != blkcount) { + printf("failed reading from device %s: %d\n", + f_rkusb->dev_type, f_rkusb->dev_index); + rockusb_tx_write_csw(f_rkusb->tag, 0, CSW_FAIL, + USB_BULK_CS_WRAP_LEN); + return; + } + f_rkusb->lba += blkcount; + f_rkusb->ul_bytes += transfer_size; + + /* Proceed with USB request */ + memcpy(in_req->buf, rbuffer, transfer_size); + in_req->length = transfer_size; + in_req->complete = tx_handler_ul_image; + debug("Uploading 0x%x bytes\n", transfer_size); + usb_ep_dequeue(rockusb_func->in_ep, in_req); + ret = usb_ep_queue(rockusb_func->in_ep, in_req, 0); + if (ret) + printf("Error %d on queue\n", ret); +} + +/* usb_request complete call back to handle down load image */ +static void rx_handler_dl_image(struct usb_ep *ep, struct usb_request *req) +{ + struct f_rockusb *f_rkusb = get_rkusb(); + unsigned int transfer_size = 0; + const unsigned char *buffer = req->buf; + unsigned int buffer_size = req->actual; + + transfer_size = f_rkusb->dl_size - f_rkusb->dl_bytes; + + if (req->status != 0) { + printf("Bad status: %d\n", req->status); + rockusb_tx_write_csw(f_rkusb->tag, 0, CSW_FAIL, + USB_BULK_CS_WRAP_LEN); + return; + } + + if (buffer_size < transfer_size) + transfer_size = buffer_size; + + memcpy((void *)f_rkusb->buf, buffer, transfer_size); + f_rkusb->dl_bytes += transfer_size; + int blks = 0, blkcnt = transfer_size / f_rkusb->desc->blksz; + + debug("dl %x bytes, %x blks, write lba %x, dl_size:%x, dl_bytes:%x, ", + transfer_size, blkcnt, f_rkusb->lba, f_rkusb->dl_size, + f_rkusb->dl_bytes); + blks = blk_dwrite(f_rkusb->desc, f_rkusb->lba, blkcnt, f_rkusb->buf); + if (blks != blkcnt) { + printf("failed writing to device %s: %d\n", f_rkusb->dev_type, + f_rkusb->dev_index); + rockusb_tx_write_csw(f_rkusb->tag, 0, CSW_FAIL, + USB_BULK_CS_WRAP_LEN); + return; + } + f_rkusb->lba += blkcnt; + + /* Check if transfer is done */ + if (f_rkusb->dl_bytes >= f_rkusb->dl_size) { + req->complete = rx_handler_command; + req->length = EP_BUFFER_SIZE; + f_rkusb->buf = f_rkusb->buf_head; + debug("transfer 0x%x bytes done\n", f_rkusb->dl_size); + f_rkusb->dl_size = 0; + rockusb_tx_write_csw(f_rkusb->tag, 0, CSW_GOOD, + USB_BULK_CS_WRAP_LEN); + } else { + req->length = rx_bytes_expected(ep); + if (f_rkusb->buf == f_rkusb->buf_head) + f_rkusb->buf = f_rkusb->buf_head + EP_BUFFER_SIZE; + else + f_rkusb->buf = f_rkusb->buf_head; + + debug("remain %x bytes, %lx sectors\n", req->length, + req->length / f_rkusb->desc->blksz); + } + + req->actual = 0; + usb_ep_queue(ep, req, 0); +} + +static void cb_test_unit_ready(struct usb_ep *ep, struct usb_request *req) +{ + ALLOC_CACHE_ALIGN_BUFFER(struct fsg_bulk_cb_wrap, cbw, + sizeof(struct fsg_bulk_cb_wrap)); + + memcpy((char *)cbw, req->buf, USB_BULK_CB_WRAP_LEN); + + rockusb_tx_write_csw(cbw->tag, cbw->data_transfer_length, + CSW_GOOD, USB_BULK_CS_WRAP_LEN); +} + +static void cb_read_storage_id(struct usb_ep *ep, struct usb_request *req) +{ + ALLOC_CACHE_ALIGN_BUFFER(struct fsg_bulk_cb_wrap, cbw, + sizeof(struct fsg_bulk_cb_wrap)); + struct f_rockusb *f_rkusb = get_rkusb(); + char emmc_id[] = "EMMC "; + + printf("read storage id\n"); + memcpy((char *)cbw, req->buf, USB_BULK_CB_WRAP_LEN); + + /* Prepare for sending subsequent CSW_GOOD */ + f_rkusb->tag = cbw->tag; + f_rkusb->in_req->complete = tx_handler_send_csw; + + rockusb_tx_write_str(emmc_id); +} + +int __weak rk_get_bootrom_chip_version(unsigned int *chip_info, int size) +{ + return 0; +} + +static void cb_get_chip_version(struct usb_ep *ep, struct usb_request *req) +{ + ALLOC_CACHE_ALIGN_BUFFER(struct fsg_bulk_cb_wrap, cbw, + sizeof(struct fsg_bulk_cb_wrap)); + struct f_rockusb *f_rkusb = get_rkusb(); + unsigned int chip_info[4], i; + + memset(chip_info, 0, sizeof(chip_info)); + rk_get_bootrom_chip_version(chip_info, 4); + + /* + * Chip Version is a string saved in BOOTROM address space Little Endian + * + * Ex for rk3288: 0x33323041 0x32303134 0x30383133 0x56323030 + * which brings: 320A20140813V200 + * + * Note that memory version do invert MSB/LSB so printing the char + * buffer will show: A02341023180002V + */ + printf("read chip version: "); + for (i = 0; i < 4; i++) { + printf("%c%c%c%c", + (chip_info[i] >> 24) & 0xFF, + (chip_info[i] >> 16) & 0xFF, + (chip_info[i] >> 8) & 0xFF, + (chip_info[i] >> 0) & 0xFF); + } + printf("\n"); + memcpy((char *)cbw, req->buf, USB_BULK_CB_WRAP_LEN); + + /* Prepare for sending subsequent CSW_GOOD */ + f_rkusb->tag = cbw->tag; + f_rkusb->in_req->complete = tx_handler_send_csw; + + rockusb_tx_write((char *)chip_info, sizeof(chip_info)); +} + +static void cb_read_lba(struct usb_ep *ep, struct usb_request *req) +{ + ALLOC_CACHE_ALIGN_BUFFER(struct fsg_bulk_cb_wrap, cbw, + sizeof(struct fsg_bulk_cb_wrap)); + struct f_rockusb *f_rkusb = get_rkusb(); + int sector_count; + + memcpy((char *)cbw, req->buf, USB_BULK_CB_WRAP_LEN); + sector_count = (int)get_unaligned_be16(&cbw->CDB[7]); + f_rkusb->tag = cbw->tag; + + if (!f_rkusb->desc) { + char *type = f_rkusb->dev_type; + int index = f_rkusb->dev_index; + + f_rkusb->desc = blk_get_dev(type, index); + if (!f_rkusb->desc || + f_rkusb->desc->type == DEV_TYPE_UNKNOWN) { + printf("invalid device \"%s\", %d\n", type, index); + rockusb_tx_write_csw(f_rkusb->tag, 0, CSW_FAIL, + USB_BULK_CS_WRAP_LEN); + return; + } + } + + f_rkusb->lba = get_unaligned_be32(&cbw->CDB[2]); + f_rkusb->ul_size = sector_count * f_rkusb->desc->blksz; + f_rkusb->ul_bytes = 0; + + debug("require read %x bytes, %x sectors from lba %x\n", + f_rkusb->ul_size, sector_count, f_rkusb->lba); + + if (f_rkusb->ul_size == 0) { + rockusb_tx_write_csw(cbw->tag, cbw->data_transfer_length, + CSW_FAIL, USB_BULK_CS_WRAP_LEN); + return; + } + + /* Start right now sending first chunk */ + tx_handler_ul_image(ep, req); +} + +static void cb_write_lba(struct usb_ep *ep, struct usb_request *req) +{ + ALLOC_CACHE_ALIGN_BUFFER(struct fsg_bulk_cb_wrap, cbw, + sizeof(struct fsg_bulk_cb_wrap)); + struct f_rockusb *f_rkusb = get_rkusb(); + int sector_count; + + memcpy((char *)cbw, req->buf, USB_BULK_CB_WRAP_LEN); + sector_count = (int)get_unaligned_be16(&cbw->CDB[7]); + f_rkusb->tag = cbw->tag; + + if (!f_rkusb->desc) { + char *type = f_rkusb->dev_type; + int index = f_rkusb->dev_index; + + f_rkusb->desc = blk_get_dev(type, index); + if (!f_rkusb->desc || + f_rkusb->desc->type == DEV_TYPE_UNKNOWN) { + printf("invalid device \"%s\", %d\n", type, index); + rockusb_tx_write_csw(f_rkusb->tag, 0, CSW_FAIL, + USB_BULK_CS_WRAP_LEN); + return; + } + } + + f_rkusb->lba = get_unaligned_be32(&cbw->CDB[2]); + f_rkusb->dl_size = sector_count * f_rkusb->desc->blksz; + f_rkusb->dl_bytes = 0; + + debug("require write %x bytes, %x sectors to lba %x\n", + f_rkusb->dl_size, sector_count, f_rkusb->lba); + + if (f_rkusb->dl_size == 0) { + rockusb_tx_write_csw(cbw->tag, cbw->data_transfer_length, + CSW_FAIL, USB_BULK_CS_WRAP_LEN); + } else { + req->complete = rx_handler_dl_image; + req->length = rx_bytes_expected(ep); + } +} + +static void cb_erase_lba(struct usb_ep *ep, struct usb_request *req) +{ + ALLOC_CACHE_ALIGN_BUFFER(struct fsg_bulk_cb_wrap, cbw, + sizeof(struct fsg_bulk_cb_wrap)); + struct f_rockusb *f_rkusb = get_rkusb(); + int sector_count, lba, blks; + + memcpy((char *)cbw, req->buf, USB_BULK_CB_WRAP_LEN); + sector_count = (int)get_unaligned_be16(&cbw->CDB[7]); + f_rkusb->tag = cbw->tag; + + if (!f_rkusb->desc) { + char *type = f_rkusb->dev_type; + int index = f_rkusb->dev_index; + + f_rkusb->desc = blk_get_dev(type, index); + if (!f_rkusb->desc || + f_rkusb->desc->type == DEV_TYPE_UNKNOWN) { + printf("invalid device \"%s\", %d\n", type, index); + rockusb_tx_write_csw(f_rkusb->tag, 0, CSW_FAIL, + USB_BULK_CS_WRAP_LEN); + return; + } + } + + lba = get_unaligned_be32(&cbw->CDB[2]); + + debug("require erase %x sectors from lba %x\n", + sector_count, lba); + + blks = blk_derase(f_rkusb->desc, lba, sector_count); + if (blks != sector_count) { + printf("failed erasing device %s: %d\n", f_rkusb->dev_type, + f_rkusb->dev_index); + rockusb_tx_write_csw(f_rkusb->tag, + cbw->data_transfer_length, CSW_FAIL, + USB_BULK_CS_WRAP_LEN); + return; + } + + rockusb_tx_write_csw(cbw->tag, cbw->data_transfer_length, CSW_GOOD, + USB_BULK_CS_WRAP_LEN); +} + +void __weak rkusb_set_reboot_flag(int flag) +{ + struct f_rockusb *f_rkusb = get_rkusb(); + + printf("rockkusb set reboot flag: %d\n", f_rkusb->reboot_flag); +} + +static void compl_do_reset(struct usb_ep *ep, struct usb_request *req) +{ + struct f_rockusb *f_rkusb = get_rkusb(); + + rkusb_set_reboot_flag(f_rkusb->reboot_flag); + do_reset(NULL, 0, 0, NULL); +} + +static void cb_reboot(struct usb_ep *ep, struct usb_request *req) +{ + ALLOC_CACHE_ALIGN_BUFFER(struct fsg_bulk_cb_wrap, cbw, + sizeof(struct fsg_bulk_cb_wrap)); + struct f_rockusb *f_rkusb = get_rkusb(); + + memcpy((char *)cbw, req->buf, USB_BULK_CB_WRAP_LEN); + f_rkusb->reboot_flag = cbw->CDB[1]; + rockusb_func->in_req->complete = compl_do_reset; + rockusb_tx_write_csw(cbw->tag, cbw->data_transfer_length, CSW_GOOD, + USB_BULK_CS_WRAP_LEN); +} + +static void cb_not_support(struct usb_ep *ep, struct usb_request *req) +{ + ALLOC_CACHE_ALIGN_BUFFER(struct fsg_bulk_cb_wrap, cbw, + sizeof(struct fsg_bulk_cb_wrap)); + + memcpy((char *)cbw, req->buf, USB_BULK_CB_WRAP_LEN); + printf("Rockusb command %x not support yet\n", cbw->CDB[0]); + rockusb_tx_write_csw(cbw->tag, 0, CSW_FAIL, USB_BULK_CS_WRAP_LEN); +} + +static const struct cmd_dispatch_info cmd_dispatch_info[] = { + { + .cmd = K_FW_TEST_UNIT_READY, + .cb = cb_test_unit_ready, + }, + { + .cmd = K_FW_READ_FLASH_ID, + .cb = cb_read_storage_id, + }, + { + .cmd = K_FW_SET_DEVICE_ID, + .cb = cb_not_support, + }, + { + .cmd = K_FW_TEST_BAD_BLOCK, + .cb = cb_not_support, + }, + { + .cmd = K_FW_READ_10, + .cb = cb_not_support, + }, + { + .cmd = K_FW_WRITE_10, + .cb = cb_not_support, + }, + { + .cmd = K_FW_ERASE_10, + .cb = cb_not_support, + }, + { + .cmd = K_FW_WRITE_SPARE, + .cb = cb_not_support, + }, + { + .cmd = K_FW_READ_SPARE, + .cb = cb_not_support, + }, + { + .cmd = K_FW_ERASE_10_FORCE, + .cb = cb_not_support, + }, + { + .cmd = K_FW_GET_VERSION, + .cb = cb_not_support, + }, + { + .cmd = K_FW_LBA_READ_10, + .cb = cb_read_lba, + }, + { + .cmd = K_FW_LBA_WRITE_10, + .cb = cb_write_lba, + }, + { + .cmd = K_FW_ERASE_SYS_DISK, + .cb = cb_not_support, + }, + { + .cmd = K_FW_SDRAM_READ_10, + .cb = cb_not_support, + }, + { + .cmd = K_FW_SDRAM_WRITE_10, + .cb = cb_not_support, + }, + { + .cmd = K_FW_SDRAM_EXECUTE, + .cb = cb_not_support, + }, + { + .cmd = K_FW_READ_FLASH_INFO, + .cb = cb_not_support, + }, + { + .cmd = K_FW_GET_CHIP_VER, + .cb = cb_get_chip_version, + }, + { + .cmd = K_FW_LOW_FORMAT, + .cb = cb_not_support, + }, + { + .cmd = K_FW_SET_RESET_FLAG, + .cb = cb_not_support, + }, + { + .cmd = K_FW_SPI_READ_10, + .cb = cb_not_support, + }, + { + .cmd = K_FW_SPI_WRITE_10, + .cb = cb_not_support, + }, + { + .cmd = K_FW_LBA_ERASE_10, + .cb = cb_erase_lba, + }, + { + .cmd = K_FW_SESSION, + .cb = cb_not_support, + }, + { + .cmd = K_FW_RESET, + .cb = cb_reboot, + }, +}; + +static void rx_handler_command(struct usb_ep *ep, struct usb_request *req) +{ + void (*func_cb)(struct usb_ep *ep, struct usb_request *req) = NULL; + + ALLOC_CACHE_ALIGN_BUFFER(struct fsg_bulk_cb_wrap, cbw, + sizeof(struct fsg_bulk_cb_wrap)); + char *cmdbuf = req->buf; + int i; + + if (req->status || req->length == 0) + return; + + memcpy((char *)cbw, req->buf, USB_BULK_CB_WRAP_LEN); +#ifdef DEBUG + printcbw(req->buf); +#endif + + for (i = 0; i < ARRAY_SIZE(cmd_dispatch_info); i++) { + if (cmd_dispatch_info[i].cmd == cbw->CDB[0]) { + func_cb = cmd_dispatch_info[i].cb; + break; + } + } + + if (!func_cb) { + printf("unknown command: %s\n", (char *)req->buf); + rockusb_tx_write_str("FAILunknown command"); + } else { + if (req->actual < req->length) { + u8 *buf = (u8 *)req->buf; + + buf[req->actual] = 0; + func_cb(ep, req); + } else { + puts("buffer overflow\n"); + rockusb_tx_write_str("FAILbuffer overflow"); + } + } + + *cmdbuf = '\0'; + req->actual = 0; + usb_ep_queue(ep, req, 0); +} diff --git a/roms/u-boot/drivers/usb/gadget/f_sdp.c b/roms/u-boot/drivers/usb/gadget/f_sdp.c new file mode 100644 index 000000000..e48aa2f90 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/f_sdp.c @@ -0,0 +1,956 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * f_sdp.c -- USB HID Serial Download Protocol + * + * Copyright (C) 2017 Toradex + * Author: Stefan Agner <stefan.agner@toradex.com> + * + * This file implements the Serial Download Protocol (SDP) as specified in + * the i.MX 6 Reference Manual. The SDP is a USB HID based protocol and + * allows to download images directly to memory. The implementation + * works with the imx_loader (imx_usb) USB client software on host side. + * + * Not all commands are implemented, e.g. WRITE_REGISTER, DCD_WRITE and + * SKIP_DCD_HEADER are only stubs. + * + * Parts of the implementation are based on f_dfu and f_thor. + */ + +#include <errno.h> +#include <common.h> +#include <console.h> +#include <env.h> +#include <log.h> +#include <malloc.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb/composite.h> + +#include <asm/io.h> +#include <g_dnl.h> +#include <sdp.h> +#include <spl.h> +#include <image.h> +#include <imximage.h> +#include <watchdog.h> + +#define HID_REPORT_ID_MASK 0x000000ff + +/* + * HID class requests + */ +#define HID_REQ_GET_REPORT 0x01 +#define HID_REQ_GET_IDLE 0x02 +#define HID_REQ_GET_PROTOCOL 0x03 +#define HID_REQ_SET_REPORT 0x09 +#define HID_REQ_SET_IDLE 0x0A +#define HID_REQ_SET_PROTOCOL 0x0B + +#define HID_USAGE_PAGE_LEN 76 + +struct hid_report { + u8 usage_page[HID_USAGE_PAGE_LEN]; +} __packed; + +#define SDP_READ_REGISTER 0x0101 +#define SDP_WRITE_REGISTER 0x0202 +#define SDP_WRITE_FILE 0x0404 +#define SDP_ERROR_STATUS 0x0505 +#define SDP_DCD_WRITE 0x0a0a +#define SDP_JUMP_ADDRESS 0x0b0b +#define SDP_SKIP_DCD_HEADER 0x0c0c + +#define SDP_SECURITY_CLOSED 0x12343412 +#define SDP_SECURITY_OPEN 0x56787856 + +#define SDP_WRITE_FILE_COMPLETE 0x88888888 +#define SDP_WRITE_REGISTER_COMPLETE 0x128A8A12 +#define SDP_SKIP_DCD_HEADER_COMPLETE 0x900DD009 +#define SDP_ERROR_IMXHEADER 0x000a0533 + +#define SDP_COMMAND_LEN 16 + +#define SDP_HID_PACKET_SIZE_EP1 1024 + +#define SDP_EXIT 1 + +struct sdp_command { + u16 cmd; + u32 addr; + u8 format; + u32 cnt; + u32 data; + u8 rsvd; +} __packed; + +enum sdp_state { + SDP_STATE_IDLE, + SDP_STATE_RX_CMD, + SDP_STATE_RX_DCD_DATA, + SDP_STATE_RX_FILE_DATA, + SDP_STATE_RX_FILE_DATA_BUSY, + SDP_STATE_TX_SEC_CONF, + SDP_STATE_TX_SEC_CONF_BUSY, + SDP_STATE_TX_REGISTER, + SDP_STATE_TX_REGISTER_BUSY, + SDP_STATE_TX_STATUS, + SDP_STATE_TX_STATUS_BUSY, + SDP_STATE_JUMP, +}; + +struct f_sdp { + struct usb_function usb_function; + + struct usb_descriptor_header **function; + + u8 altsetting; + enum sdp_state state; + enum sdp_state next_state; + u32 dnl_address; + u32 dnl_bytes; + u32 dnl_bytes_remaining; + u32 jmp_address; + bool always_send_status; + u32 error_status; + + /* EP0 request */ + struct usb_request *req; + + /* EP1 IN */ + struct usb_ep *in_ep; + struct usb_request *in_req; + /* EP1 OUT */ + struct usb_ep *out_ep; + struct usb_request *out_req; + + bool configuration_done; + bool ep_int_enable; +}; + +static struct f_sdp *sdp_func; + +static inline struct f_sdp *func_to_sdp(struct usb_function *f) +{ + return container_of(f, struct f_sdp, usb_function); +} + +static struct usb_interface_descriptor sdp_intf_runtime = { + .bLength = sizeof(sdp_intf_runtime), + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + /* .iInterface = DYNAMIC */ +}; + +/* HID configuration */ +static struct usb_class_hid_descriptor sdp_hid_desc = { + .bLength = sizeof(sdp_hid_desc), + .bDescriptorType = USB_DT_CS_DEVICE, + + .bcdCDC = __constant_cpu_to_le16(0x0110), + .bCountryCode = 0, + .bNumDescriptors = 1, + + .bDescriptorType0 = USB_DT_HID_REPORT, + .wDescriptorLength0 = HID_USAGE_PAGE_LEN, +}; + +static struct usb_endpoint_descriptor in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, /*USB_DT_CS_ENDPOINT*/ + + .bEndpointAddress = 1 | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = 64, + .bInterval = 1, +}; + +static struct usb_endpoint_descriptor out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, /*USB_DT_CS_ENDPOINT*/ + + .bEndpointAddress = 1 | USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = 64, + .bInterval = 1, +}; + +static struct usb_endpoint_descriptor in_hs_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, /*USB_DT_CS_ENDPOINT*/ + + .bEndpointAddress = 1 | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = 512, + .bInterval = 3, +}; + +static struct usb_endpoint_descriptor out_hs_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, /*USB_DT_CS_ENDPOINT*/ + + .bEndpointAddress = 1 | USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = SDP_HID_PACKET_SIZE_EP1, + .bInterval = 3, +}; + +static struct usb_descriptor_header *sdp_runtime_descs[] = { + (struct usb_descriptor_header *)&sdp_intf_runtime, + (struct usb_descriptor_header *)&sdp_hid_desc, + (struct usb_descriptor_header *)&in_desc, + (struct usb_descriptor_header *)&out_desc, + NULL, +}; + +static struct usb_descriptor_header *sdp_runtime_hs_descs[] = { + (struct usb_descriptor_header *)&sdp_intf_runtime, + (struct usb_descriptor_header *)&sdp_hid_desc, + (struct usb_descriptor_header *)&in_hs_desc, + (struct usb_descriptor_header *)&out_hs_desc, + NULL, +}; + +/* This is synchronized with what the SoC implementation reports */ +static struct hid_report sdp_hid_report = { + .usage_page = { + 0x06, 0x00, 0xff, /* Usage Page */ + 0x09, 0x01, /* Usage (Pointer?) */ + 0xa1, 0x01, /* Collection */ + + 0x85, 0x01, /* Report ID */ + 0x19, 0x01, /* Usage Minimum */ + 0x29, 0x01, /* Usage Maximum */ + 0x15, 0x00, /* Local Minimum */ + 0x26, 0xFF, 0x00, /* Local Maximum? */ + 0x75, 0x08, /* Report Size */ + 0x95, 0x10, /* Report Count */ + 0x91, 0x02, /* Output Data */ + + 0x85, 0x02, /* Report ID */ + 0x19, 0x01, /* Usage Minimum */ + 0x29, 0x01, /* Usage Maximum */ + 0x15, 0x00, /* Local Minimum */ + 0x26, 0xFF, 0x00, /* Local Maximum? */ + 0x75, 0x80, /* Report Size 128 */ + 0x95, 0x40, /* Report Count */ + 0x91, 0x02, /* Output Data */ + + 0x85, 0x03, /* Report ID */ + 0x19, 0x01, /* Usage Minimum */ + 0x29, 0x01, /* Usage Maximum */ + 0x15, 0x00, /* Local Minimum */ + 0x26, 0xFF, 0x00, /* Local Maximum? */ + 0x75, 0x08, /* Report Size 8 */ + 0x95, 0x04, /* Report Count */ + 0x81, 0x02, /* Input Data */ + + 0x85, 0x04, /* Report ID */ + 0x19, 0x01, /* Usage Minimum */ + 0x29, 0x01, /* Usage Maximum */ + 0x15, 0x00, /* Local Minimum */ + 0x26, 0xFF, 0x00, /* Local Maximum? */ + 0x75, 0x08, /* Report Size 8 */ + 0x95, 0x40, /* Report Count */ + 0x81, 0x02, /* Input Data */ + 0xc0 + }, +}; + +static const char sdp_name[] = "Serial Downloader Protocol"; + +/* + * static strings, in UTF-8 + */ +static struct usb_string strings_sdp_generic[] = { + [0].s = sdp_name, + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_sdp_generic = { + .language = 0x0409, /* en-us */ + .strings = strings_sdp_generic, +}; + +static struct usb_gadget_strings *sdp_generic_strings[] = { + &stringtab_sdp_generic, + NULL, +}; + +static inline void *sdp_ptr(u32 val) +{ + return (void *)(uintptr_t)val; +} + +static void sdp_rx_command_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_sdp *sdp = req->context; + int status = req->status; + u8 *data = req->buf; + u8 report = data[0]; + + if (status != 0) { + pr_err("Status: %d\n", status); + return; + } + + if (report != 1) { + pr_err("Unexpected report %d\n", report); + return; + } + + struct sdp_command *cmd = req->buf + 1; + + debug("%s: command: %04x, addr: %08x, cnt: %u\n", + __func__, be16_to_cpu(cmd->cmd), + be32_to_cpu(cmd->addr), be32_to_cpu(cmd->cnt)); + + switch (be16_to_cpu(cmd->cmd)) { + case SDP_READ_REGISTER: + sdp->always_send_status = false; + sdp->error_status = 0x0; + + sdp->state = SDP_STATE_TX_SEC_CONF; + sdp->dnl_address = be32_to_cpu(cmd->addr); + sdp->dnl_bytes_remaining = be32_to_cpu(cmd->cnt); + sdp->next_state = SDP_STATE_TX_REGISTER; + printf("Reading %d registers at 0x%08x... ", + sdp->dnl_bytes_remaining, sdp->dnl_address); + break; + case SDP_WRITE_FILE: + sdp->always_send_status = true; + sdp->error_status = SDP_WRITE_FILE_COMPLETE; + + sdp->state = SDP_STATE_RX_FILE_DATA; + sdp->dnl_address = cmd->addr ? be32_to_cpu(cmd->addr) : CONFIG_SDP_LOADADDR; + sdp->dnl_bytes_remaining = be32_to_cpu(cmd->cnt); + sdp->dnl_bytes = sdp->dnl_bytes_remaining; + sdp->next_state = SDP_STATE_IDLE; + + printf("Downloading file of size %d to 0x%08x... ", + sdp->dnl_bytes_remaining, sdp->dnl_address); + + break; + case SDP_ERROR_STATUS: + sdp->always_send_status = true; + sdp->error_status = 0; + + sdp->state = SDP_STATE_TX_SEC_CONF; + sdp->next_state = SDP_STATE_IDLE; + break; + case SDP_DCD_WRITE: + sdp->always_send_status = true; + sdp->error_status = SDP_WRITE_REGISTER_COMPLETE; + + sdp->state = SDP_STATE_RX_DCD_DATA; + sdp->dnl_bytes_remaining = be32_to_cpu(cmd->cnt); + sdp->next_state = SDP_STATE_IDLE; + break; + case SDP_JUMP_ADDRESS: + sdp->always_send_status = false; + sdp->error_status = 0; + + sdp->jmp_address = cmd->addr ? be32_to_cpu(cmd->addr) : CONFIG_SDP_LOADADDR; + sdp->state = SDP_STATE_TX_SEC_CONF; + sdp->next_state = SDP_STATE_JUMP; + break; + case SDP_SKIP_DCD_HEADER: + sdp->always_send_status = true; + sdp->error_status = SDP_SKIP_DCD_HEADER_COMPLETE; + + /* Ignore command, DCD not supported anyway */ + sdp->state = SDP_STATE_TX_SEC_CONF; + sdp->next_state = SDP_STATE_IDLE; + break; + default: + pr_err("Unknown command: %04x\n", be16_to_cpu(cmd->cmd)); + } +} + +static void sdp_rx_data_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_sdp *sdp = req->context; + int status = req->status; + u8 *data = req->buf; + u8 report = data[0]; + int datalen = req->actual - 1; + + if (status != 0) { + pr_err("Status: %d\n", status); + return; + } + + if (report != 2) { + pr_err("Unexpected report %d\n", report); + return; + } + + if (sdp->dnl_bytes_remaining < datalen) { + /* + * Some USB stacks require to send a complete buffer as + * specified in the HID descriptor. This leads to longer + * transfers than the file length, no problem for us. + */ + sdp->dnl_bytes_remaining = 0; + } else { + sdp->dnl_bytes_remaining -= datalen; + } + + if (sdp->state == SDP_STATE_RX_FILE_DATA_BUSY) { + memcpy(sdp_ptr(sdp->dnl_address), req->buf + 1, datalen); + sdp->dnl_address += datalen; + } + + if (sdp->dnl_bytes_remaining) { + sdp->state = SDP_STATE_RX_FILE_DATA; + return; + } + +#ifndef CONFIG_SPL_BUILD + env_set_hex("filesize", sdp->dnl_bytes); +#endif + printf("done\n"); + + switch (sdp->state) { + case SDP_STATE_RX_FILE_DATA_BUSY: + sdp->state = SDP_STATE_TX_SEC_CONF; + break; + case SDP_STATE_RX_DCD_DATA: + sdp->state = SDP_STATE_TX_SEC_CONF; + break; + default: + pr_err("Invalid state: %d\n", sdp->state); + } +} + +static void sdp_tx_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_sdp *sdp = req->context; + int status = req->status; + + if (status != 0) { + pr_err("Status: %d\n", status); + return; + } + + switch (sdp->state) { + case SDP_STATE_TX_SEC_CONF_BUSY: + /* Not all commands require status report */ + if (sdp->always_send_status || sdp->error_status) + sdp->state = SDP_STATE_TX_STATUS; + else + sdp->state = sdp->next_state; + + break; + case SDP_STATE_TX_STATUS_BUSY: + sdp->state = sdp->next_state; + break; + case SDP_STATE_TX_REGISTER_BUSY: + if (sdp->dnl_bytes_remaining) + sdp->state = SDP_STATE_TX_REGISTER; + else + sdp->state = SDP_STATE_IDLE; + break; + default: + pr_err("Wrong State: %d\n", sdp->state); + sdp->state = SDP_STATE_IDLE; + break; + } + debug("%s complete --> %d, %d/%d\n", ep->name, + status, req->actual, req->length); +} + +static int sdp_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct usb_gadget *gadget = f->config->cdev->gadget; + struct usb_request *req = f->config->cdev->req; + struct f_sdp *sdp = f->config->cdev->req->context; + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + int value = 0; + u8 req_type = ctrl->bRequestType & USB_TYPE_MASK; + + debug("w_value: 0x%04x len: 0x%04x\n", w_value, len); + debug("req_type: 0x%02x ctrl->bRequest: 0x%02x sdp->state: %d\n", + req_type, ctrl->bRequest, sdp->state); + + if (req_type == USB_TYPE_STANDARD) { + if (ctrl->bRequest == USB_REQ_GET_DESCRIPTOR) { + /* Send HID report descriptor */ + value = min(len, (u16) sizeof(sdp_hid_report)); + memcpy(req->buf, &sdp_hid_report, value); + sdp->configuration_done = true; + } + } + + if (req_type == USB_TYPE_CLASS) { + int report = w_value & HID_REPORT_ID_MASK; + + /* HID (SDP) request */ + switch (ctrl->bRequest) { + case HID_REQ_SET_REPORT: + switch (report) { + case 1: + value = SDP_COMMAND_LEN + 1; + req->complete = sdp_rx_command_complete; + sdp_func->ep_int_enable = false; + break; + case 2: + value = len; + req->complete = sdp_rx_data_complete; + sdp_func->state = SDP_STATE_RX_FILE_DATA_BUSY; + break; + } + } + } + + if (value >= 0) { + req->length = value; + req->zero = value < len; + value = usb_ep_queue(gadget->ep0, req, 0); + if (value < 0) { + debug("ep_queue --> %d\n", value); + req->status = 0; + } + } + + return value; +} + +static int sdp_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_gadget *gadget = c->cdev->gadget; + struct usb_composite_dev *cdev = c->cdev; + struct f_sdp *sdp = func_to_sdp(f); + int rv = 0, id; + + id = usb_interface_id(c, f); + if (id < 0) + return id; + sdp_intf_runtime.bInterfaceNumber = id; + + struct usb_ep *ep_in, *ep_out; + + /* allocate instance-specific endpoints */ + ep_in = usb_ep_autoconfig(gadget, &in_desc); + if (!ep_in) { + rv = -ENODEV; + goto error; + } + + ep_out = usb_ep_autoconfig(gadget, &out_desc); + if (!ep_out) { + rv = -ENODEV; + goto error; + } + + if (gadget_is_dualspeed(gadget)) { + /* Assume endpoint addresses are the same for both speeds */ + in_hs_desc.bEndpointAddress = in_desc.bEndpointAddress; + out_hs_desc.bEndpointAddress = out_desc.bEndpointAddress; + } + + sdp->in_ep = ep_in; /* Store IN EP for enabling @ setup */ + sdp->out_ep = ep_out; + + cdev->req->context = sdp; + +error: + return rv; +} + +static void sdp_unbind(struct usb_configuration *c, struct usb_function *f) +{ + free(sdp_func); + sdp_func = NULL; +} + +static struct usb_request *alloc_ep_req(struct usb_ep *ep, unsigned length) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, 0); + if (!req) + return req; + + req->length = length; + req->buf = memalign(CONFIG_SYS_CACHELINE_SIZE, length); + if (!req->buf) { + usb_ep_free_request(ep, req); + req = NULL; + } + + return req; +} + + +static struct usb_request *sdp_start_ep(struct usb_ep *ep, bool in) +{ + struct usb_request *req; + + if (in) + req = alloc_ep_req(ep, 65); + else + req = alloc_ep_req(ep, 2048); +/* + * OUT endpoint request length should be an integral multiple of + * maxpacket size 1024, else we break on certain controllers like + * DWC3 that expect bulk OUT requests to be divisible by maxpacket size. + */ + debug("%s: ep:%p req:%p\n", __func__, ep, req); + + if (!req) + return NULL; + + memset(req->buf, 0, req->length); + if (in) + req->complete = sdp_tx_complete; + else + req->complete = sdp_rx_command_complete; + + return req; +} +static int sdp_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_sdp *sdp = func_to_sdp(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_gadget *gadget = cdev->gadget; + int result; + + debug("%s: intf: %d alt: %d\n", __func__, intf, alt); + + if (gadget_is_dualspeed(gadget) && gadget->speed == USB_SPEED_HIGH) { + result = usb_ep_enable(sdp->in_ep, &in_hs_desc); + result |= usb_ep_enable(sdp->out_ep, &out_hs_desc); + } else { + result = usb_ep_enable(sdp->in_ep, &in_desc); + result |= usb_ep_enable(sdp->out_ep, &out_desc); + } + if (result) + return result; + + sdp->in_req = sdp_start_ep(sdp->in_ep, true); + sdp->in_req->context = sdp; + sdp->out_req = sdp_start_ep(sdp->out_ep, false); + sdp->out_req->context = sdp; + + sdp->in_ep->driver_data = cdev; /* claim */ + sdp->out_ep->driver_data = cdev; /* claim */ + + sdp->altsetting = alt; + sdp->state = SDP_STATE_IDLE; + sdp->ep_int_enable = true; + + return 0; +} + +static int sdp_get_alt(struct usb_function *f, unsigned intf) +{ + struct f_sdp *sdp = func_to_sdp(f); + + return sdp->altsetting; +} + +static void sdp_disable(struct usb_function *f) +{ + struct f_sdp *sdp = func_to_sdp(f); + + usb_ep_disable(sdp->in_ep); + usb_ep_disable(sdp->out_ep); + + if (sdp->in_req) { + free(sdp->in_req->buf); + usb_ep_free_request(sdp->in_ep, sdp->in_req); + sdp->in_req = NULL; + } + if (sdp->out_req) { + free(sdp->out_req->buf); + usb_ep_free_request(sdp->out_ep, sdp->out_req); + sdp->out_req = NULL; + } +} + +static int sdp_bind_config(struct usb_configuration *c) +{ + int status; + + if (!sdp_func) { + sdp_func = memalign(CONFIG_SYS_CACHELINE_SIZE, sizeof(*sdp_func)); + if (!sdp_func) + return -ENOMEM; + } + + memset(sdp_func, 0, sizeof(*sdp_func)); + + sdp_func->usb_function.name = "sdp"; + sdp_func->usb_function.hs_descriptors = sdp_runtime_hs_descs; + sdp_func->usb_function.descriptors = sdp_runtime_descs; + sdp_func->usb_function.bind = sdp_bind; + sdp_func->usb_function.unbind = sdp_unbind; + sdp_func->usb_function.set_alt = sdp_set_alt; + sdp_func->usb_function.get_alt = sdp_get_alt; + sdp_func->usb_function.disable = sdp_disable; + sdp_func->usb_function.strings = sdp_generic_strings; + sdp_func->usb_function.setup = sdp_setup; + + status = usb_add_function(c, &sdp_func->usb_function); + + return status; +} + +int sdp_init(int controller_index) +{ + printf("SDP: initialize...\n"); + while (!sdp_func->configuration_done) { + if (ctrlc()) { + puts("\rCTRL+C - Operation aborted.\n"); + return 1; + } + + WATCHDOG_RESET(); + usb_gadget_handle_interrupts(controller_index); + } + + return 0; +} + +static u32 sdp_jump_imxheader(void *address) +{ + flash_header_v2_t *headerv2 = address; + ulong (*entry)(void); + + if (headerv2->header.tag != IVT_HEADER_TAG) { + printf("Header Tag is not an IMX image\n"); + return SDP_ERROR_IMXHEADER; + } + + printf("Jumping to 0x%08x\n", headerv2->entry); + entry = sdp_ptr(headerv2->entry); + entry(); + + /* The image probably never returns hence we won't reach that point */ + return 0; +} + +#ifdef CONFIG_SPL_BUILD +static ulong sdp_load_read(struct spl_load_info *load, ulong sector, + ulong count, void *buf) +{ + debug("%s: sector %lx, count %lx, buf %lx\n", + __func__, sector, count, (ulong)buf); + memcpy(buf, (void *)(load->dev + sector), count); + return count; +} + +static ulong search_fit_header(ulong p, int size) +{ + int i; + + for (i = 0; i < size; i += 4) { + if (genimg_get_format((const void *)(p + i)) == IMAGE_FORMAT_FIT) + return p + i; + } + + return 0; +} + +static ulong search_container_header(ulong p, int size) +{ + int i; + u8 *hdr; + + for (i = 0; i < size; i += 4) { + hdr = (u8 *)(p + i); + if (*(hdr + 3) == 0x87 && *hdr == 0) + if (*(hdr + 1) != 0 || *(hdr + 2) != 0) + return p + i; + } + return 0; +} +#endif + +static int sdp_handle_in_ep(struct spl_image_info *spl_image) +{ + u8 *data = sdp_func->in_req->buf; + u32 status; + int datalen; + + switch (sdp_func->state) { + case SDP_STATE_TX_SEC_CONF: + debug("Report 3: HAB security\n"); + data[0] = 3; + + status = SDP_SECURITY_OPEN; + memcpy(&data[1], &status, 4); + sdp_func->in_req->length = 5; + usb_ep_queue(sdp_func->in_ep, sdp_func->in_req, 0); + sdp_func->state = SDP_STATE_TX_SEC_CONF_BUSY; + break; + + case SDP_STATE_TX_STATUS: + debug("Report 4: Status\n"); + data[0] = 4; + + memcpy(&data[1], &sdp_func->error_status, 4); + sdp_func->in_req->length = 65; + usb_ep_queue(sdp_func->in_ep, sdp_func->in_req, 0); + sdp_func->state = SDP_STATE_TX_STATUS_BUSY; + break; + case SDP_STATE_TX_REGISTER: + debug("Report 4: Register Values\n"); + data[0] = 4; + + datalen = sdp_func->dnl_bytes_remaining; + + if (datalen > 64) + datalen = 64; + + memcpy(&data[1], sdp_ptr(sdp_func->dnl_address), datalen); + sdp_func->in_req->length = 65; + + sdp_func->dnl_bytes_remaining -= datalen; + sdp_func->dnl_address += datalen; + + usb_ep_queue(sdp_func->in_ep, sdp_func->in_req, 0); + sdp_func->state = SDP_STATE_TX_REGISTER_BUSY; + break; + case SDP_STATE_JUMP: + printf("Jumping to header at 0x%08x\n", sdp_func->jmp_address); + status = sdp_jump_imxheader(sdp_ptr(sdp_func->jmp_address)); + + /* If imx header fails, try some U-Boot specific headers */ + if (status) { +#ifdef CONFIG_SPL_BUILD + if (IS_ENABLED(CONFIG_SPL_LOAD_IMX_CONTAINER)) + sdp_func->jmp_address = (u32)search_container_header((ulong)sdp_func->jmp_address, sdp_func->dnl_bytes); + else if (IS_ENABLED(CONFIG_SPL_LOAD_FIT)) + sdp_func->jmp_address = (u32)search_fit_header((ulong)sdp_func->jmp_address, sdp_func->dnl_bytes); + if (sdp_func->jmp_address == 0) + panic("Error in search header, failed to jump\n"); + + printf("Found header at 0x%08x\n", sdp_func->jmp_address); + + image_header_t *header = + sdp_ptr(sdp_func->jmp_address); +#ifdef CONFIG_SPL_LOAD_FIT + if (image_get_magic(header) == FDT_MAGIC) { + struct spl_load_info load; + + debug("Found FIT\n"); + load.dev = header; + load.bl_len = 1; + load.read = sdp_load_read; + spl_load_simple_fit(spl_image, &load, 0, + header); + + return SDP_EXIT; + } +#endif + if (IS_ENABLED(CONFIG_SPL_LOAD_IMX_CONTAINER)) { + struct spl_load_info load; + + load.dev = header; + load.bl_len = 1; + load.read = sdp_load_read; + spl_load_imx_container(spl_image, &load, 0); + return SDP_EXIT; + } + + /* In SPL, allow jumps to U-Boot images */ + struct spl_image_info spl_image = {}; + spl_parse_image_header(&spl_image, header); + jump_to_image_no_args(&spl_image); +#else + /* In U-Boot, allow jumps to scripts */ + image_source_script(sdp_func->jmp_address, "script@1"); +#endif + } + + sdp_func->next_state = SDP_STATE_IDLE; + sdp_func->error_status = status; + + /* Only send Report 4 if there was an error */ + if (status) + sdp_func->state = SDP_STATE_TX_STATUS; + else + sdp_func->state = SDP_STATE_IDLE; + break; + default: + break; + }; + + return 0; +} + +static void sdp_handle_out_ep(void) +{ + int rc; + + if (sdp_func->state == SDP_STATE_IDLE) { + sdp_func->out_req->complete = sdp_rx_command_complete; + rc = usb_ep_queue(sdp_func->out_ep, sdp_func->out_req, 0); + if (rc) + printf("error in submission: %s\n", + sdp_func->out_ep->name); + sdp_func->state = SDP_STATE_RX_CMD; + } else if (sdp_func->state == SDP_STATE_RX_FILE_DATA) { + sdp_func->out_req->complete = sdp_rx_data_complete; + rc = usb_ep_queue(sdp_func->out_ep, sdp_func->out_req, 0); + if (rc) + printf("error in submission: %s\n", + sdp_func->out_ep->name); + sdp_func->state = SDP_STATE_RX_FILE_DATA_BUSY; + } +} + +#ifndef CONFIG_SPL_BUILD +int sdp_handle(int controller_index) +#else +int spl_sdp_handle(int controller_index, struct spl_image_info *spl_image) +#endif +{ + int flag = 0; + printf("SDP: handle requests...\n"); + while (1) { + if (ctrlc()) { + puts("\rCTRL+C - Operation aborted.\n"); + return -EINVAL; + } + + if (flag == SDP_EXIT) + return 0; + + WATCHDOG_RESET(); + usb_gadget_handle_interrupts(controller_index); + +#ifdef CONFIG_SPL_BUILD + flag = sdp_handle_in_ep(spl_image); +#else + flag = sdp_handle_in_ep(NULL); +#endif + if (sdp_func->ep_int_enable) + sdp_handle_out_ep(); + } +} + +int sdp_add(struct usb_configuration *c) +{ + int id; + + id = usb_string_id(c->cdev); + if (id < 0) + return id; + strings_sdp_generic[0].id = id; + sdp_intf_runtime.iInterface = id; + + debug("%s: cdev: %p gadget: %p gadget->ep0: %p\n", __func__, + c->cdev, c->cdev->gadget, c->cdev->gadget->ep0); + + return sdp_bind_config(c); +} + +DECLARE_GADGET_BIND_CALLBACK(usb_dnl_sdp, sdp_add); diff --git a/roms/u-boot/drivers/usb/gadget/f_thor.c b/roms/u-boot/drivers/usb/gadget/f_thor.c new file mode 100644 index 000000000..47ef55b2f --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/f_thor.c @@ -0,0 +1,1034 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * f_thor.c -- USB TIZEN THOR Downloader gadget function + * + * Copyright (C) 2013 Samsung Electronics + * Lukasz Majewski <l.majewski@samsung.com> + * + * Based on code from: + * git://review.tizen.org/kernel/u-boot + * + * Developed by: + * Copyright (C) 2009 Samsung Electronics + * Minkyu Kang <mk7.kang@samsung.com> + * Sanghee Kim <sh0130.kim@samsung.com> + */ + +#include <command.h> +#include <errno.h> +#include <common.h> +#include <console.h> +#include <init.h> +#include <log.h> +#include <malloc.h> +#include <memalign.h> +#include <version.h> +#include <linux/delay.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb/composite.h> +#include <linux/usb/cdc.h> +#include <g_dnl.h> +#include <dfu.h> +#include <thor.h> + +#include "f_thor.h" + +static void thor_tx_data(unsigned char *data, int len); +static void thor_set_dma(void *addr, int len); +static int thor_rx_data(void); + +static struct f_thor *thor_func; +static inline struct f_thor *func_to_thor(struct usb_function *f) +{ + return container_of(f, struct f_thor, usb_function); +} + +DEFINE_CACHE_ALIGN_BUFFER(unsigned char, thor_tx_data_buf, + sizeof(struct rsp_box)); +DEFINE_CACHE_ALIGN_BUFFER(unsigned char, thor_rx_data_buf, + sizeof(struct rqt_box)); + +/* ********************************************************** */ +/* THOR protocol - transmission handling */ +/* ********************************************************** */ +DEFINE_CACHE_ALIGN_BUFFER(char, f_name, F_NAME_BUF_SIZE + 1); +static unsigned long long int thor_file_size; +static int alt_setting_num; + +static void send_rsp(const struct rsp_box *rsp) +{ + memcpy(thor_tx_data_buf, rsp, sizeof(struct rsp_box)); + thor_tx_data(thor_tx_data_buf, sizeof(struct rsp_box)); + + debug("-RSP: %d, %d\n", rsp->rsp, rsp->rsp_data); +} + +static void send_data_rsp(s32 ack, s32 count) +{ + ALLOC_CACHE_ALIGN_BUFFER(struct data_rsp_box, rsp, + sizeof(struct data_rsp_box)); + + rsp->ack = ack; + rsp->count = count; + + memcpy(thor_tx_data_buf, rsp, sizeof(struct data_rsp_box)); + thor_tx_data(thor_tx_data_buf, sizeof(struct data_rsp_box)); + + debug("-DATA RSP: %d, %d\n", ack, count); +} + +static int process_rqt_info(const struct rqt_box *rqt) +{ + ALLOC_CACHE_ALIGN_BUFFER(struct rsp_box, rsp, sizeof(struct rsp_box)); + memset(rsp, 0, sizeof(struct rsp_box)); + + rsp->rsp = rqt->rqt; + rsp->rsp_data = rqt->rqt_data; + + switch (rqt->rqt_data) { + case RQT_INFO_VER_PROTOCOL: + rsp->int_data[0] = VER_PROTOCOL_MAJOR; + rsp->int_data[1] = VER_PROTOCOL_MINOR; + break; + case RQT_INIT_VER_HW: + snprintf(rsp->str_data[0], sizeof(rsp->str_data[0]), + "%x", checkboard()); + break; + case RQT_INIT_VER_BOOT: + sprintf(rsp->str_data[0], "%s", U_BOOT_VERSION); + break; + case RQT_INIT_VER_KERNEL: + sprintf(rsp->str_data[0], "%s", "k unknown"); + break; + case RQT_INIT_VER_PLATFORM: + sprintf(rsp->str_data[0], "%s", "p unknown"); + break; + case RQT_INIT_VER_CSC: + sprintf(rsp->str_data[0], "%s", "c unknown"); + break; + default: + return -EINVAL; + } + + send_rsp(rsp); + return true; +} + +static int process_rqt_cmd(const struct rqt_box *rqt) +{ + ALLOC_CACHE_ALIGN_BUFFER(struct rsp_box, rsp, sizeof(struct rsp_box)); + memset(rsp, 0, sizeof(struct rsp_box)); + + rsp->rsp = rqt->rqt; + rsp->rsp_data = rqt->rqt_data; + + switch (rqt->rqt_data) { + case RQT_CMD_REBOOT: + debug("TARGET RESET\n"); + send_rsp(rsp); + g_dnl_unregister(); + dfu_free_entities(); +#ifdef CONFIG_THOR_RESET_OFF + return RESET_DONE; +#endif + run_command("reset", 0); + break; + case RQT_CMD_POWEROFF: + case RQT_CMD_EFSCLEAR: + send_rsp(rsp); + default: + printf("Command not supported -> cmd: %d\n", rqt->rqt_data); + return -EINVAL; + } + + return true; +} + +static long long int download_head(unsigned long long total, + unsigned int packet_size, + long long int *left, + int *cnt) +{ + long long int rcv_cnt = 0, left_to_rcv, ret_rcv; + struct dfu_entity *dfu_entity = dfu_get_entity(alt_setting_num); + void *transfer_buffer = dfu_get_buf(dfu_entity); + void *buf = transfer_buffer; + int usb_pkt_cnt = 0, ret; + + /* + * Files smaller than THOR_STORE_UNIT_SIZE (now 32 MiB) are stored on + * the medium. + * The packet response is sent on the purpose after successful data + * chunk write. There is a room for improvement when asynchronous write + * is performed. + */ + while (total - rcv_cnt >= packet_size) { + thor_set_dma(buf, packet_size); + buf += packet_size; + ret_rcv = thor_rx_data(); + if (ret_rcv < 0) + return ret_rcv; + rcv_cnt += ret_rcv; + debug("%d: RCV data count: %llu cnt: %d\n", usb_pkt_cnt, + rcv_cnt, *cnt); + + if ((rcv_cnt % THOR_STORE_UNIT_SIZE) == 0) { + ret = dfu_write(dfu_get_entity(alt_setting_num), + transfer_buffer, THOR_STORE_UNIT_SIZE, + (*cnt)++); + if (ret) { + pr_err("DFU write failed [%d] cnt: %d\n", + ret, *cnt); + return ret; + } + buf = transfer_buffer; + } + send_data_rsp(0, ++usb_pkt_cnt); + } + + /* Calculate the amount of data to arrive from PC (in bytes) */ + left_to_rcv = total - rcv_cnt; + + /* + * Calculate number of data already received. but not yet stored + * on the medium (they are smaller than THOR_STORE_UNIT_SIZE) + */ + *left = left_to_rcv + buf - transfer_buffer; + debug("%s: left: %llu left_to_rcv: %llu buf: 0x%p\n", __func__, + *left, left_to_rcv, buf); + + if (left_to_rcv) { + thor_set_dma(buf, packet_size); + ret_rcv = thor_rx_data(); + if (ret_rcv < 0) + return ret_rcv; + rcv_cnt += ret_rcv; + send_data_rsp(0, ++usb_pkt_cnt); + } + + debug("%s: %llu total: %llu cnt: %d\n", __func__, rcv_cnt, total, *cnt); + + return rcv_cnt; +} + +static int download_tail(long long int left, int cnt) +{ + struct dfu_entity *dfu_entity; + void *transfer_buffer; + int ret; + + debug("%s: left: %llu cnt: %d\n", __func__, left, cnt); + + dfu_entity = dfu_get_entity(alt_setting_num); + if (!dfu_entity) { + pr_err("Alt setting: %d entity not found!\n", alt_setting_num); + return -ENOENT; + } + + transfer_buffer = dfu_get_buf(dfu_entity); + if (!transfer_buffer) { + pr_err("Transfer buffer not allocated!\n"); + return -ENXIO; + } + + if (left) { + ret = dfu_write(dfu_entity, transfer_buffer, left, cnt++); + if (ret) { + pr_err("DFU write failed[%d]: left: %llu\n", ret, left); + return ret; + } + } + + /* + * To store last "packet" or write file from buffer to filesystem + * DFU storage backend requires dfu_flush + * + * This also frees memory malloc'ed by dfu_get_buf(), so no explicit + * need fo call dfu_free_buf() is needed. + */ + ret = dfu_flush(dfu_entity, transfer_buffer, 0, cnt); + if (ret) + pr_err("DFU flush failed!\n"); + + return ret; +} + +static long long int process_rqt_download(const struct rqt_box *rqt) +{ + ALLOC_CACHE_ALIGN_BUFFER(struct rsp_box, rsp, sizeof(struct rsp_box)); + static long long int left, ret_head; + int file_type, ret = 0; + static int cnt; + + memset(rsp, 0, sizeof(struct rsp_box)); + rsp->rsp = rqt->rqt; + rsp->rsp_data = rqt->rqt_data; + + switch (rqt->rqt_data) { + case RQT_DL_INIT: + thor_file_size = (uint64_t)(uint32_t)rqt->int_data[0] + + (((uint64_t)(uint32_t)rqt->int_data[1]) + << 32); + debug("INIT: total %llu bytes\n", thor_file_size); + break; + case RQT_DL_FILE_INFO: + file_type = rqt->int_data[0]; + if (file_type == FILE_TYPE_PIT) { + puts("PIT table file - not supported\n"); + rsp->ack = -ENOTSUPP; + ret = rsp->ack; + break; + } + + thor_file_size = (uint64_t)(uint32_t)rqt->int_data[1] + + (((uint64_t)(uint32_t)rqt->int_data[2]) + << 32); + memcpy(f_name, rqt->str_data[0], F_NAME_BUF_SIZE); + f_name[F_NAME_BUF_SIZE] = '\0'; + + debug("INFO: name(%s, %d), size(%llu), type(%d)\n", + f_name, 0, thor_file_size, file_type); + + rsp->int_data[0] = THOR_PACKET_SIZE; + + alt_setting_num = dfu_get_alt(f_name); + if (alt_setting_num < 0) { + pr_err("Alt setting [%d] to write not found!\n", + alt_setting_num); + rsp->ack = -ENODEV; + ret = rsp->ack; + } + break; + case RQT_DL_FILE_START: + send_rsp(rsp); + ret_head = download_head(thor_file_size, THOR_PACKET_SIZE, + &left, &cnt); + if (ret_head < 0) { + left = 0; + cnt = 0; + } + return ret_head; + case RQT_DL_FILE_END: + debug("DL FILE_END\n"); + rsp->ack = download_tail(left, cnt); + ret = rsp->ack; + left = 0; + cnt = 0; + break; + case RQT_DL_EXIT: + debug("DL EXIT\n"); + break; + default: + pr_err("Operation not supported: %d\n", rqt->rqt_data); + ret = -ENOTSUPP; + } + + send_rsp(rsp); + return ret; +} + +static int process_data(void) +{ + ALLOC_CACHE_ALIGN_BUFFER(struct rqt_box, rqt, sizeof(struct rqt_box)); + int ret = -EINVAL; + + memcpy(rqt, thor_rx_data_buf, sizeof(struct rqt_box)); + + debug("+RQT: %d, %d\n", rqt->rqt, rqt->rqt_data); + + switch (rqt->rqt) { + case RQT_INFO: + ret = process_rqt_info(rqt); + break; + case RQT_CMD: + ret = process_rqt_cmd(rqt); + break; + case RQT_DL: + ret = (int) process_rqt_download(rqt); + break; + case RQT_UL: + puts("RQT: UPLOAD not supported!\n"); + break; + default: + pr_err("unknown request (%d)\n", rqt->rqt); + } + + return ret; +} + +/* ********************************************************** */ +/* THOR USB Function */ +/* ********************************************************** */ + +static inline struct usb_endpoint_descriptor * +ep_desc(struct usb_gadget *g, struct usb_endpoint_descriptor *hs, + struct usb_endpoint_descriptor *fs) +{ + if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) + return hs; + return fs; +} + +static struct usb_interface_descriptor thor_downloader_intf_data = { + .bLength = sizeof(thor_downloader_intf_data), + .bDescriptorType = USB_DT_INTERFACE, + + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_CDC_DATA, +}; + +static struct usb_endpoint_descriptor fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +/* CDC configuration */ +static struct usb_interface_descriptor thor_downloader_intf_int = { + .bLength = sizeof(thor_downloader_intf_int), + .bDescriptorType = USB_DT_INTERFACE, + + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_COMM, + /* 0x02 Abstract Line Control Model */ + .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, + /* 0x01 Common AT commands */ + .bInterfaceProtocol = USB_CDC_ACM_PROTO_AT_V25TER, +}; + +static struct usb_cdc_header_desc thor_downloader_cdc_header = { + .bLength = sizeof(thor_downloader_cdc_header), + .bDescriptorType = 0x24, /* CS_INTERFACE */ + .bDescriptorSubType = 0x00, + .bcdCDC = 0x0110, +}; + +static struct usb_cdc_call_mgmt_descriptor thor_downloader_cdc_call = { + .bLength = sizeof(thor_downloader_cdc_call), + .bDescriptorType = 0x24, /* CS_INTERFACE */ + .bDescriptorSubType = 0x01, + .bmCapabilities = 0x00, + .bDataInterface = 0x01, +}; + +static struct usb_cdc_acm_descriptor thor_downloader_cdc_abstract = { + .bLength = sizeof(thor_downloader_cdc_abstract), + .bDescriptorType = 0x24, /* CS_INTERFACE */ + .bDescriptorSubType = 0x02, + .bmCapabilities = 0x00, +}; + +static struct usb_cdc_union_desc thor_downloader_cdc_union = { + .bLength = sizeof(thor_downloader_cdc_union), + .bDescriptorType = 0x24, /* CS_INTERFACE */ + .bDescriptorSubType = USB_CDC_UNION_TYPE, +}; + +static struct usb_endpoint_descriptor fs_int_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = 3 | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(16), + + .bInterval = 0x9, +}; + +static struct usb_interface_assoc_descriptor +thor_iad_descriptor = { + .bLength = sizeof(thor_iad_descriptor), + .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, + + .bFirstInterface = 0, + .bInterfaceCount = 2, /* control + data */ + .bFunctionClass = USB_CLASS_COMM, + .bFunctionSubClass = USB_CDC_SUBCLASS_ACM, + .bFunctionProtocol = USB_CDC_PROTO_NONE, +}; + +static struct usb_endpoint_descriptor hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor hs_int_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(16), + + .bInterval = 0x9, +}; + +/* + * This attribute vendor descriptor is necessary for correct operation with + * Windows version of THOR download program + * + * It prevents windows driver from sending zero lenght packet (ZLP) after + * each THOR_PACKET_SIZE. This assures consistent behaviour with libusb + */ +static struct usb_cdc_attribute_vendor_descriptor thor_downloader_cdc_av = { + .bLength = sizeof(thor_downloader_cdc_av), + .bDescriptorType = 0x24, + .bDescriptorSubType = 0x80, + .DAUType = 0x0002, + .DAULength = 0x0001, + .DAUValue = 0x00, +}; + +static const struct usb_descriptor_header *hs_thor_downloader_function[] = { + (struct usb_descriptor_header *)&thor_iad_descriptor, + + (struct usb_descriptor_header *)&thor_downloader_intf_int, + (struct usb_descriptor_header *)&thor_downloader_cdc_header, + (struct usb_descriptor_header *)&thor_downloader_cdc_call, + (struct usb_descriptor_header *)&thor_downloader_cdc_abstract, + (struct usb_descriptor_header *)&thor_downloader_cdc_union, + (struct usb_descriptor_header *)&hs_int_desc, + + (struct usb_descriptor_header *)&thor_downloader_intf_data, + (struct usb_descriptor_header *)&thor_downloader_cdc_av, + (struct usb_descriptor_header *)&hs_in_desc, + (struct usb_descriptor_header *)&hs_out_desc, + NULL, +}; + +/*-------------------------------------------------------------------------*/ +static struct usb_request *alloc_ep_req(struct usb_ep *ep, unsigned length) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, 0); + if (!req) + return req; + + req->length = length; + req->buf = memalign(CONFIG_SYS_CACHELINE_SIZE, length); + if (!req->buf) { + usb_ep_free_request(ep, req); + req = NULL; + } + + return req; +} + +static int thor_rx_data(void) +{ + struct thor_dev *dev = thor_func->dev; + int data_to_rx, tmp, status; + + data_to_rx = dev->out_req->length; + tmp = data_to_rx; + do { + dev->out_req->length = data_to_rx; + debug("dev->out_req->length:%d dev->rxdata:%d\n", + dev->out_req->length, dev->rxdata); + + status = usb_ep_queue(dev->out_ep, dev->out_req, 0); + if (status) { + pr_err("kill %s: resubmit %d bytes --> %d\n", + dev->out_ep->name, dev->out_req->length, status); + usb_ep_set_halt(dev->out_ep); + return -EAGAIN; + } + + while (!dev->rxdata) { + usb_gadget_handle_interrupts(0); + if (ctrlc()) + return -1; + } + dev->rxdata = 0; + data_to_rx -= dev->out_req->actual; + } while (data_to_rx); + + return tmp; +} + +static void thor_tx_data(unsigned char *data, int len) +{ + struct thor_dev *dev = thor_func->dev; + unsigned char *ptr = dev->in_req->buf; + int status; + + memset(ptr, 0, len); + memcpy(ptr, data, len); + + dev->in_req->length = len; + + debug("%s: dev->in_req->length:%d to_cpy:%zd\n", __func__, + dev->in_req->length, sizeof(data)); + + status = usb_ep_queue(dev->in_ep, dev->in_req, 0); + if (status) { + pr_err("kill %s: resubmit %d bytes --> %d\n", + dev->in_ep->name, dev->in_req->length, status); + usb_ep_set_halt(dev->in_ep); + } + + /* Wait until tx interrupt received */ + while (!dev->txdata) + usb_gadget_handle_interrupts(0); + + dev->txdata = 0; +} + +static void thor_rx_tx_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct thor_dev *dev = thor_func->dev; + int status = req->status; + + debug("%s: ep_ptr:%p, req_ptr:%p\n", __func__, ep, req); + switch (status) { + case 0: + if (ep == dev->out_ep) + dev->rxdata = 1; + else + dev->txdata = 1; + + break; + + /* this endpoint is normally active while we're configured */ + case -ECONNABORTED: /* hardware forced ep reset */ + case -ECONNRESET: /* request dequeued */ + case -ESHUTDOWN: /* disconnect from host */ + case -EREMOTEIO: /* short read */ + case -EOVERFLOW: + pr_err("ERROR:%d\n", status); + break; + } + + debug("%s complete --> %d, %d/%d\n", ep->name, + status, req->actual, req->length); +} + +static void thor_setup_complete(struct usb_ep *ep, struct usb_request *req) +{ + if (req->status || req->actual != req->length) + debug("setup complete --> %d, %d/%d\n", + req->status, req->actual, req->length); +} + +static int +thor_func_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct thor_dev *dev = thor_func->dev; + struct usb_request *req = dev->req; + struct usb_gadget *gadget = dev->gadget; + int value = 0; + + u16 len = le16_to_cpu(ctrl->wLength); + + debug("Req_Type: 0x%x Req: 0x%x wValue: 0x%x wIndex: 0x%x wLen: 0x%x\n", + ctrl->bRequestType, ctrl->bRequest, ctrl->wValue, ctrl->wIndex, + ctrl->wLength); + + switch (ctrl->bRequest) { + case USB_CDC_REQ_SET_CONTROL_LINE_STATE: + value = 0; + break; + case USB_CDC_REQ_SET_LINE_CODING: + value = len; + /* Line Coding set done = configuration done */ + thor_func->dev->configuration_done = 1; + break; + + default: + pr_err("thor_setup: unknown request: %d\n", ctrl->bRequest); + } + + if (value >= 0) { + req->length = value; + req->zero = value < len; + value = usb_ep_queue(gadget->ep0, req, 0); + if (value < 0) { + debug("%s: ep_queue: %d\n", __func__, value); + req->status = 0; + } + } + + return value; +} + +/* Specific to the THOR protocol */ +static void thor_set_dma(void *addr, int len) +{ + struct thor_dev *dev = thor_func->dev; + + debug("in_req:%p, out_req:%p\n", dev->in_req, dev->out_req); + debug("addr:%p, len:%d\n", addr, len); + + dev->out_req->buf = addr; + dev->out_req->length = len; +} + +int thor_init(void) +{ + struct thor_dev *dev = thor_func->dev; + + /* Wait for a device enumeration and configuration settings */ + debug("THOR enumeration/configuration setting....\n"); + while (!dev->configuration_done) + usb_gadget_handle_interrupts(0); + + thor_set_dma(thor_rx_data_buf, strlen("THOR")); + /* detect the download request from Host PC */ + if (thor_rx_data() < 0) { + printf("%s: Data not received!\n", __func__); + return -1; + } + + if (!strncmp((char *)thor_rx_data_buf, "THOR", strlen("THOR"))) { + puts("Download request from the Host PC\n"); + udelay(30 * 1000); /* 30 ms */ + + strcpy((char *)thor_tx_data_buf, "ROHT"); + thor_tx_data(thor_tx_data_buf, strlen("ROHT")); + } else { + puts("Wrong reply information\n"); + return -1; + } + + return 0; +} + +int thor_handle(void) +{ + int ret; + + /* receive the data from Host PC */ + while (1) { + thor_set_dma(thor_rx_data_buf, sizeof(struct rqt_box)); + ret = thor_rx_data(); + + if (ret > 0) { + ret = process_data(); +#ifdef CONFIG_THOR_RESET_OFF + if (ret == RESET_DONE) + break; +#endif + if (ret < 0) + return ret; + } else { + printf("%s: No data received!\n", __func__); + break; + } + if (dfu_reinit_needed) + return THOR_DFU_REINIT_NEEDED; + } + + return 0; +} + +static void free_ep_req(struct usb_ep *ep, struct usb_request *req) +{ + if (req->buf) + free(req->buf); + usb_ep_free_request(ep, req); +} + +static int thor_func_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_gadget *gadget = c->cdev->gadget; + struct f_thor *f_thor = func_to_thor(f); + struct thor_dev *dev; + struct usb_ep *ep; + int status; + + thor_func = f_thor; + dev = memalign(CONFIG_SYS_CACHELINE_SIZE, sizeof(*dev)); + if (!dev) + return -ENOMEM; + + memset(dev, 0, sizeof(*dev)); + dev->gadget = gadget; + f_thor->dev = dev; + + debug("%s: usb_configuration: 0x%p usb_function: 0x%p\n", + __func__, c, f); + debug("f_thor: 0x%p thor: 0x%p\n", f_thor, dev); + + /* EP0 */ + /* preallocate control response and buffer */ + dev->req = usb_ep_alloc_request(gadget->ep0, 0); + if (!dev->req) { + status = -ENOMEM; + goto fail; + } + dev->req->buf = memalign(CONFIG_SYS_CACHELINE_SIZE, + THOR_PACKET_SIZE); + if (!dev->req->buf) { + status = -ENOMEM; + goto fail; + } + + dev->req->complete = thor_setup_complete; + + /* DYNAMIC interface numbers assignments */ + status = usb_interface_id(c, f); + + if (status < 0) + goto fail; + + thor_downloader_intf_int.bInterfaceNumber = status; + thor_downloader_cdc_union.bMasterInterface0 = status; + + status = usb_interface_id(c, f); + + if (status < 0) + goto fail; + + thor_downloader_intf_data.bInterfaceNumber = status; + thor_downloader_cdc_union.bSlaveInterface0 = status; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(gadget, &fs_in_desc); + if (!ep) { + status = -ENODEV; + goto fail; + } + + if (gadget_is_dualspeed(gadget)) { + hs_in_desc.bEndpointAddress = + fs_in_desc.bEndpointAddress; + } + + dev->in_ep = ep; /* Store IN EP for enabling @ setup */ + ep->driver_data = dev; + + ep = usb_ep_autoconfig(gadget, &fs_out_desc); + if (!ep) { + status = -ENODEV; + goto fail; + } + + if (gadget_is_dualspeed(gadget)) + hs_out_desc.bEndpointAddress = + fs_out_desc.bEndpointAddress; + + dev->out_ep = ep; /* Store OUT EP for enabling @ setup */ + ep->driver_data = dev; + + ep = usb_ep_autoconfig(gadget, &fs_int_desc); + if (!ep) { + status = -ENODEV; + goto fail; + } + + dev->int_ep = ep; + ep->driver_data = dev; + + if (gadget_is_dualspeed(gadget)) { + hs_int_desc.bEndpointAddress = + fs_int_desc.bEndpointAddress; + + f->hs_descriptors = (struct usb_descriptor_header **) + &hs_thor_downloader_function; + + if (!f->hs_descriptors) + goto fail; + } + + debug("%s: out_ep:%p out_req:%p\n", __func__, + dev->out_ep, dev->out_req); + + return 0; + + fail: + if (dev->req) + free_ep_req(gadget->ep0, dev->req); + free(dev); + return status; +} + +static void thor_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_thor *f_thor = func_to_thor(f); + struct thor_dev *dev = f_thor->dev; + + free_ep_req(dev->gadget->ep0, dev->req); + free(dev); + memset(thor_func, 0, sizeof(*thor_func)); + thor_func = NULL; +} + +static void thor_func_disable(struct usb_function *f) +{ + struct f_thor *f_thor = func_to_thor(f); + struct thor_dev *dev = f_thor->dev; + + debug("%s:\n", __func__); + + /* Avoid freeing memory when ep is still claimed */ + if (dev->in_ep->driver_data) { + usb_ep_disable(dev->in_ep); + free_ep_req(dev->in_ep, dev->in_req); + dev->in_ep->driver_data = NULL; + } + + if (dev->out_ep->driver_data) { + usb_ep_disable(dev->out_ep); + usb_ep_free_request(dev->out_ep, dev->out_req); + dev->out_ep->driver_data = NULL; + } + + if (dev->int_ep->driver_data) { + usb_ep_disable(dev->int_ep); + dev->int_ep->driver_data = NULL; + } +} + +static int thor_eps_setup(struct usb_function *f) +{ + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_gadget *gadget = cdev->gadget; + struct thor_dev *dev = thor_func->dev; + struct usb_endpoint_descriptor *d; + struct usb_request *req; + struct usb_ep *ep; + int result; + + ep = dev->in_ep; + d = ep_desc(gadget, &hs_in_desc, &fs_in_desc); + debug("(d)bEndpointAddress: 0x%x\n", d->bEndpointAddress); + + result = usb_ep_enable(ep, d); + if (result) + goto err; + + ep->driver_data = cdev; /* claim */ + req = alloc_ep_req(ep, THOR_PACKET_SIZE); + if (!req) { + result = -EIO; + goto err_disable_in_ep; + } + + memset(req->buf, 0, req->length); + req->complete = thor_rx_tx_complete; + dev->in_req = req; + ep = dev->out_ep; + d = ep_desc(gadget, &hs_out_desc, &fs_out_desc); + debug("(d)bEndpointAddress: 0x%x\n", d->bEndpointAddress); + + result = usb_ep_enable(ep, d); + if (result) + goto err_free_in_req; + + ep->driver_data = cdev; /* claim */ + req = usb_ep_alloc_request(ep, 0); + if (!req) { + result = -EIO; + goto err_disable_out_ep; + } + + req->complete = thor_rx_tx_complete; + dev->out_req = req; + /* ACM control EP */ + ep = dev->int_ep; + d = ep_desc(gadget, &hs_int_desc, &fs_int_desc); + debug("(d)bEndpointAddress: 0x%x\n", d->bEndpointAddress); + + result = usb_ep_enable(ep, d); + if (result) + goto err; + + ep->driver_data = cdev; /* claim */ + + return 0; + + err_disable_out_ep: + usb_ep_disable(dev->out_ep); + + err_free_in_req: + free_ep_req(dev->in_ep, dev->in_req); + dev->in_req = NULL; + + err_disable_in_ep: + usb_ep_disable(dev->in_ep); + + err: + return result; +} + +static int thor_func_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct thor_dev *dev = thor_func->dev; + int result; + + debug("%s: func: %s intf: %d alt: %d\n", + __func__, f->name, intf, alt); + + switch (intf) { + case 0: + debug("ACM INTR interface\n"); + break; + case 1: + debug("Communication Data interface\n"); + result = thor_eps_setup(f); + if (result) + pr_err("%s: EPs setup failed!\n", __func__); + dev->configuration_done = 1; + break; + } + + return 0; +} + +static int thor_func_init(struct usb_configuration *c) +{ + struct f_thor *f_thor; + int status; + + debug("%s: cdev: 0x%p\n", __func__, c->cdev); + + f_thor = memalign(CONFIG_SYS_CACHELINE_SIZE, sizeof(*f_thor)); + if (!f_thor) + return -ENOMEM; + + memset(f_thor, 0, sizeof(*f_thor)); + + f_thor->usb_function.name = "f_thor"; + f_thor->usb_function.bind = thor_func_bind; + f_thor->usb_function.unbind = thor_unbind; + f_thor->usb_function.setup = thor_func_setup; + f_thor->usb_function.set_alt = thor_func_set_alt; + f_thor->usb_function.disable = thor_func_disable; + + status = usb_add_function(c, &f_thor->usb_function); + if (status) + free(f_thor); + + return status; +} + +int thor_add(struct usb_configuration *c) +{ + debug("%s:\n", __func__); + return thor_func_init(c); +} + +DECLARE_GADGET_BIND_CALLBACK(usb_dnl_thor, thor_add); diff --git a/roms/u-boot/drivers/usb/gadget/f_thor.h b/roms/u-boot/drivers/usb/gadget/f_thor.h new file mode 100644 index 000000000..8ba3fa21b --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/f_thor.h @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * f_thor.h - USB TIZEN THOR - internal gadget definitions + * + * Copyright (C) 2013 Samsung Electronics + * Lukasz Majewski <l.majewski@samsung.com> + */ + +#ifndef _USB_THOR_H_ +#define _USB_THOR_H_ + +#include <linux/compiler.h> +#include <linux/sizes.h> + +/* THOR Composite Gadget */ +#define STRING_MANUFACTURER_IDX 0 +#define STRING_PRODUCT_IDX 1 +#define STRING_SERIAL_IDX 2 + +/* ********************************************************** */ +/* THOR protocol definitions */ +/* ********************************************************** */ + +/* + * Attribute Vendor descriptor - necessary to prevent ZLP transmission + * from Windows XP HOST PC + */ +struct usb_cdc_attribute_vendor_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubType; + __u16 DAUType; + __u16 DAULength; + __u8 DAUValue; +} __packed; + +#define VER_PROTOCOL_MAJOR 5 +#define VER_PROTOCOL_MINOR 0 + +enum rqt { + RQT_INFO = 200, + RQT_CMD, + RQT_DL, + RQT_UL, +}; + +enum rqt_data { + /* RQT_INFO */ + RQT_INFO_VER_PROTOCOL = 1, + RQT_INIT_VER_HW, + RQT_INIT_VER_BOOT, + RQT_INIT_VER_KERNEL, + RQT_INIT_VER_PLATFORM, + RQT_INIT_VER_CSC, + + /* RQT_CMD */ + RQT_CMD_REBOOT = 1, + RQT_CMD_POWEROFF, + RQT_CMD_EFSCLEAR, + + /* RQT_DL */ + RQT_DL_INIT = 1, + RQT_DL_FILE_INFO, + RQT_DL_FILE_START, + RQT_DL_FILE_END, + RQT_DL_EXIT, + + /* RQT_UL */ + RQT_UL_INIT = 1, + RQT_UL_START, + RQT_UL_END, + RQT_UL_EXIT, +}; + +struct rqt_box { /* total: 256B */ + s32 rqt; /* request id */ + s32 rqt_data; /* request data id */ + s32 int_data[14]; /* int data */ + char str_data[5][32]; /* string data */ + char md5[32]; /* md5 checksum */ +} __packed; + +struct rsp_box { /* total: 128B */ + s32 rsp; /* response id (= request id) */ + s32 rsp_data; /* response data id */ + s32 ack; /* ack */ + s32 int_data[5]; /* int data */ + char str_data[3][32]; /* string data */ +} __packed; + +struct data_rsp_box { /* total: 8B */ + s32 ack; /* response id (= request id) */ + s32 count; /* response data id */ +} __packed; + +enum { + FILE_TYPE_NORMAL, + FILE_TYPE_PIT, +}; + +struct thor_dev { + struct usb_gadget *gadget; + struct usb_request *req; /* EP0 -> control responses */ + + /* IN/OUT EP's and correspoinding requests */ + struct usb_ep *in_ep, *out_ep, *int_ep; + struct usb_request *in_req, *out_req; + + /* Control flow variables */ + unsigned char configuration_done; + unsigned char rxdata; + unsigned char txdata; +}; + +struct f_thor { + struct usb_function usb_function; + struct thor_dev *dev; +}; + +#define F_NAME_BUF_SIZE 32 +#define THOR_PACKET_SIZE SZ_1M /* 1 MiB */ +#define THOR_STORE_UNIT_SIZE SZ_32M /* 32 MiB */ +#ifdef CONFIG_THOR_RESET_OFF +#define RESET_DONE 0xFFFFFFFF +#endif +#endif /* _USB_THOR_H_ */ diff --git a/roms/u-boot/drivers/usb/gadget/fotg210.c b/roms/u-boot/drivers/usb/gadget/fotg210.c new file mode 100644 index 000000000..af43433d8 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/fotg210.c @@ -0,0 +1,964 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Faraday USB 2.0 OTG Controller + * + * (C) Copyright 2010 Faraday Technology + * Dante Su <dantesu@faraday-tech.com> + */ + +#include <common.h> +#include <command.h> +#include <config.h> +#include <cpu_func.h> +#include <log.h> +#include <net.h> +#include <malloc.h> +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + +#include <usb/fotg210.h> + +#define CFG_NUM_ENDPOINTS 4 +#define CFG_EP0_MAX_PACKET_SIZE 64 +#define CFG_EPX_MAX_PACKET_SIZE 512 + +#define CFG_CMD_TIMEOUT (CONFIG_SYS_HZ >> 2) /* 250 ms */ + +struct fotg210_chip; + +struct fotg210_ep { + struct usb_ep ep; + + uint maxpacket; + uint id; + uint stopped; + + struct list_head queue; + struct fotg210_chip *chip; + const struct usb_endpoint_descriptor *desc; +}; + +struct fotg210_request { + struct usb_request req; + struct list_head queue; + struct fotg210_ep *ep; +}; + +struct fotg210_chip { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct fotg210_regs *regs; + uint8_t irq; + uint16_t addr; + int pullup; + enum usb_device_state state; + struct fotg210_ep ep[1 + CFG_NUM_ENDPOINTS]; +}; + +static struct usb_endpoint_descriptor ep0_desc = { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, +}; + +static inline int fifo_to_ep(struct fotg210_chip *chip, int id, int in) +{ + return (id < 0) ? 0 : ((id & 0x03) + 1); +} + +static inline int ep_to_fifo(struct fotg210_chip *chip, int id) +{ + return (id <= 0) ? -1 : ((id - 1) & 0x03); +} + +static inline int ep_reset(struct fotg210_chip *chip, uint8_t ep_addr) +{ + int ep = ep_addr & USB_ENDPOINT_NUMBER_MASK; + struct fotg210_regs *regs = chip->regs; + + if (ep_addr & USB_DIR_IN) { + /* reset endpoint */ + setbits_le32(®s->iep[ep - 1], IEP_RESET); + mdelay(1); + clrbits_le32(®s->iep[ep - 1], IEP_RESET); + /* clear endpoint stall */ + clrbits_le32(®s->iep[ep - 1], IEP_STALL); + } else { + /* reset endpoint */ + setbits_le32(®s->oep[ep - 1], OEP_RESET); + mdelay(1); + clrbits_le32(®s->oep[ep - 1], OEP_RESET); + /* clear endpoint stall */ + clrbits_le32(®s->oep[ep - 1], OEP_STALL); + } + + return 0; +} + +static int fotg210_reset(struct fotg210_chip *chip) +{ + struct fotg210_regs *regs = chip->regs; + uint32_t i; + + chip->state = USB_STATE_POWERED; + + /* chip enable */ + writel(DEVCTRL_EN, ®s->dev_ctrl); + + /* device address reset */ + chip->addr = 0; + writel(0, ®s->dev_addr); + + /* set idle counter to 7ms */ + writel(7, ®s->idle); + + /* disable all interrupts */ + writel(IMR_MASK, ®s->imr); + writel(GIMR_MASK, ®s->gimr); + writel(GIMR0_MASK, ®s->gimr0); + writel(GIMR1_MASK, ®s->gimr1); + writel(GIMR2_MASK, ®s->gimr2); + + /* clear interrupts */ + writel(ISR_MASK, ®s->isr); + writel(0, ®s->gisr); + writel(0, ®s->gisr0); + writel(0, ®s->gisr1); + writel(0, ®s->gisr2); + + /* chip reset */ + setbits_le32(®s->dev_ctrl, DEVCTRL_RESET); + mdelay(10); + if (readl(®s->dev_ctrl) & DEVCTRL_RESET) { + printf("fotg210: chip reset failed\n"); + return -1; + } + + /* CX FIFO reset */ + setbits_le32(®s->cxfifo, CXFIFO_CXFIFOCLR); + mdelay(10); + if (readl(®s->cxfifo) & CXFIFO_CXFIFOCLR) { + printf("fotg210: ep0 fifo reset failed\n"); + return -1; + } + + /* create static ep-fifo map (EP1 <-> FIFO0, EP2 <-> FIFO1 ...) */ + writel(EPMAP14_DEFAULT, ®s->epmap14); + writel(EPMAP58_DEFAULT, ®s->epmap58); + writel(FIFOMAP_DEFAULT, ®s->fifomap); + writel(0, ®s->fifocfg); + for (i = 0; i < 8; ++i) { + writel(CFG_EPX_MAX_PACKET_SIZE, ®s->iep[i]); + writel(CFG_EPX_MAX_PACKET_SIZE, ®s->oep[i]); + } + + /* FIFO reset */ + for (i = 0; i < 4; ++i) { + writel(FIFOCSR_RESET, ®s->fifocsr[i]); + mdelay(10); + if (readl(®s->fifocsr[i]) & FIFOCSR_RESET) { + printf("fotg210: fifo%d reset failed\n", i); + return -1; + } + } + + /* enable only device interrupt and triggered at level-high */ + writel(IMR_IRQLH | IMR_HOST | IMR_OTG, ®s->imr); + writel(ISR_MASK, ®s->isr); + /* disable EP0 IN/OUT interrupt */ + writel(GIMR0_CXOUT | GIMR0_CXIN, ®s->gimr0); + /* disable EPX IN+SPK+OUT interrupts */ + writel(GIMR1_MASK, ®s->gimr1); + /* disable wakeup+idle+dma+zlp interrupts */ + writel(GIMR2_WAKEUP | GIMR2_IDLE | GIMR2_DMAERR | GIMR2_DMAFIN + | GIMR2_ZLPRX | GIMR2_ZLPTX, ®s->gimr2); + /* enable all group interrupt */ + writel(0, ®s->gimr); + + /* suspend delay = 3 ms */ + writel(3, ®s->idle); + + /* turn-on device interrupts */ + setbits_le32(®s->dev_ctrl, DEVCTRL_GIRQ_EN); + + return 0; +} + +static inline int fotg210_cxwait(struct fotg210_chip *chip, uint32_t mask) +{ + struct fotg210_regs *regs = chip->regs; + int ret = -1; + ulong ts; + + for (ts = get_timer(0); get_timer(ts) < CFG_CMD_TIMEOUT; ) { + if ((readl(®s->cxfifo) & mask) != mask) + continue; + ret = 0; + break; + } + + if (ret) + printf("fotg210: cx/ep0 timeout\n"); + + return ret; +} + +static int fotg210_dma(struct fotg210_ep *ep, struct fotg210_request *req) +{ + struct fotg210_chip *chip = ep->chip; + struct fotg210_regs *regs = chip->regs; + uint32_t tmp, ts; + uint8_t *buf = req->req.buf + req->req.actual; + uint32_t len = req->req.length - req->req.actual; + int fifo = ep_to_fifo(chip, ep->id); + int ret = -EBUSY; + + /* 1. init dma buffer */ + if (len > ep->maxpacket) + len = ep->maxpacket; + + /* 2. wait for dma ready (hardware) */ + for (ts = get_timer(0); get_timer(ts) < CFG_CMD_TIMEOUT; ) { + if (!(readl(®s->dma_ctrl) & DMACTRL_START)) { + ret = 0; + break; + } + } + if (ret) { + printf("fotg210: dma busy\n"); + req->req.status = ret; + return ret; + } + + /* 3. DMA target setup */ + if (ep->desc->bEndpointAddress & USB_DIR_IN) + flush_dcache_range((ulong)buf, (ulong)buf + len); + else + invalidate_dcache_range((ulong)buf, (ulong)buf + len); + + writel(virt_to_phys(buf), ®s->dma_addr); + + if (ep->desc->bEndpointAddress & USB_DIR_IN) { + if (ep->id == 0) { + /* Wait until cx/ep0 fifo empty */ + fotg210_cxwait(chip, CXFIFO_CXFIFOE); + udelay(1); + writel(DMAFIFO_CX, ®s->dma_fifo); + } else { + /* Wait until epx fifo empty */ + fotg210_cxwait(chip, CXFIFO_FIFOE(fifo)); + writel(DMAFIFO_FIFO(fifo), ®s->dma_fifo); + } + writel(DMACTRL_LEN(len) | DMACTRL_MEM2FIFO, ®s->dma_ctrl); + } else { + uint32_t blen; + + if (ep->id == 0) { + writel(DMAFIFO_CX, ®s->dma_fifo); + do { + blen = CXFIFO_BYTES(readl(®s->cxfifo)); + } while (blen < len); + } else { + writel(DMAFIFO_FIFO(fifo), ®s->dma_fifo); + blen = FIFOCSR_BYTES(readl(®s->fifocsr[fifo])); + } + len = (len < blen) ? len : blen; + writel(DMACTRL_LEN(len) | DMACTRL_FIFO2MEM, ®s->dma_ctrl); + } + + /* 4. DMA start */ + setbits_le32(®s->dma_ctrl, DMACTRL_START); + + /* 5. DMA wait */ + ret = -EBUSY; + for (ts = get_timer(0); get_timer(ts) < CFG_CMD_TIMEOUT; ) { + tmp = readl(®s->gisr2); + /* DMA complete */ + if (tmp & GISR2_DMAFIN) { + ret = 0; + break; + } + /* DMA error */ + if (tmp & GISR2_DMAERR) { + printf("fotg210: dma error\n"); + break; + } + /* resume, suspend, reset */ + if (tmp & (GISR2_RESUME | GISR2_SUSPEND | GISR2_RESET)) { + printf("fotg210: dma reset by host\n"); + break; + } + } + + /* 7. DMA target reset */ + if (ret) + writel(DMACTRL_ABORT | DMACTRL_CLRFF, ®s->dma_ctrl); + + writel(0, ®s->gisr2); + writel(0, ®s->dma_fifo); + + req->req.status = ret; + if (!ret) + req->req.actual += len; + else + printf("fotg210: ep%d dma error(code=%d)\n", ep->id, ret); + + return len; +} + +/* + * result of setup packet + */ +#define CX_IDLE 0 +#define CX_FINISH 1 +#define CX_STALL 2 + +static void fotg210_setup(struct fotg210_chip *chip) +{ + int id, ret = CX_IDLE; + uint32_t tmp[2]; + struct usb_ctrlrequest *req = (struct usb_ctrlrequest *)tmp; + struct fotg210_regs *regs = chip->regs; + + /* + * If this is the first Cx 8 byte command, + * we can now query USB mode (high/full speed; USB 2.0/USB 1.0) + */ + if (chip->state == USB_STATE_POWERED) { + chip->state = USB_STATE_DEFAULT; + if (readl(®s->otgcsr) & OTGCSR_DEV_B) { + /* Mini-B */ + if (readl(®s->dev_ctrl) & DEVCTRL_HS) { + puts("fotg210: HS\n"); + chip->gadget.speed = USB_SPEED_HIGH; + /* SOF mask timer = 1100 ticks */ + writel(SOFMTR_TMR(1100), ®s->sof_mtr); + } else { + puts("fotg210: FS\n"); + chip->gadget.speed = USB_SPEED_FULL; + /* SOF mask timer = 10000 ticks */ + writel(SOFMTR_TMR(10000), ®s->sof_mtr); + } + } else { + printf("fotg210: mini-A?\n"); + } + } + + /* switch data port to ep0 */ + writel(DMAFIFO_CX, ®s->dma_fifo); + /* fetch 8 bytes setup packet */ + tmp[0] = readl(®s->ep0_data); + tmp[1] = readl(®s->ep0_data); + /* release data port */ + writel(0, ®s->dma_fifo); + + if (req->bRequestType & USB_DIR_IN) + ep0_desc.bEndpointAddress = USB_DIR_IN; + else + ep0_desc.bEndpointAddress = USB_DIR_OUT; + + ret = CX_IDLE; + + if ((req->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (req->bRequest) { + case USB_REQ_SET_CONFIGURATION: + debug("fotg210: set_cfg(%d)\n", req->wValue & 0x00FF); + if (!(req->wValue & 0x00FF)) { + chip->state = USB_STATE_ADDRESS; + writel(chip->addr, ®s->dev_addr); + } else { + chip->state = USB_STATE_CONFIGURED; + writel(chip->addr | DEVADDR_CONF, + ®s->dev_addr); + } + ret = CX_IDLE; + break; + + case USB_REQ_SET_ADDRESS: + debug("fotg210: set_addr(0x%04X)\n", req->wValue); + chip->state = USB_STATE_ADDRESS; + chip->addr = req->wValue & DEVADDR_ADDR_MASK; + ret = CX_FINISH; + writel(chip->addr, ®s->dev_addr); + break; + + case USB_REQ_CLEAR_FEATURE: + debug("fotg210: clr_feature(%d, %d)\n", + req->bRequestType & 0x03, req->wValue); + switch (req->wValue) { + case 0: /* [Endpoint] halt */ + ep_reset(chip, req->wIndex); + ret = CX_FINISH; + break; + case 1: /* [Device] remote wake-up */ + case 2: /* [Device] test mode */ + default: + ret = CX_STALL; + break; + } + break; + + case USB_REQ_SET_FEATURE: + debug("fotg210: set_feature(%d, %d)\n", + req->wValue, req->wIndex & 0xf); + switch (req->wValue) { + case 0: /* Endpoint Halt */ + id = req->wIndex & 0xf; + setbits_le32(®s->iep[id - 1], IEP_STALL); + setbits_le32(®s->oep[id - 1], OEP_STALL); + ret = CX_FINISH; + break; + case 1: /* Remote Wakeup */ + case 2: /* Test Mode */ + default: + ret = CX_STALL; + break; + } + break; + + case USB_REQ_GET_STATUS: + debug("fotg210: get_status\n"); + ret = CX_STALL; + break; + + case USB_REQ_SET_DESCRIPTOR: + debug("fotg210: set_descriptor\n"); + ret = CX_STALL; + break; + + case USB_REQ_SYNCH_FRAME: + debug("fotg210: sync frame\n"); + ret = CX_STALL; + break; + } + } /* if ((req->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) */ + + if (ret == CX_IDLE && chip->driver->setup) { + if (chip->driver->setup(&chip->gadget, req) < 0) + ret = CX_STALL; + else + ret = CX_FINISH; + } + + switch (ret) { + case CX_FINISH: + setbits_le32(®s->cxfifo, CXFIFO_CXFIN); + break; + + case CX_STALL: + setbits_le32(®s->cxfifo, CXFIFO_CXSTALL | CXFIFO_CXFIN); + printf("fotg210: cx_stall!\n"); + break; + + case CX_IDLE: + debug("fotg210: cx_idle?\n"); + default: + break; + } +} + +/* + * fifo - FIFO id + * zlp - zero length packet + */ +static void fotg210_recv(struct fotg210_chip *chip, int ep_id) +{ + struct fotg210_regs *regs = chip->regs; + struct fotg210_ep *ep = chip->ep + ep_id; + struct fotg210_request *req; + int len; + + if (ep->stopped || (ep->desc->bEndpointAddress & USB_DIR_IN)) { + printf("fotg210: ep%d recv, invalid!\n", ep->id); + return; + } + + if (list_empty(&ep->queue)) { + printf("fotg210: ep%d recv, drop!\n", ep->id); + return; + } + + req = list_first_entry(&ep->queue, struct fotg210_request, queue); + len = fotg210_dma(ep, req); + if (len < ep->ep.maxpacket || req->req.length <= req->req.actual) { + list_del_init(&req->queue); + if (req->req.complete) + req->req.complete(&ep->ep, &req->req); + } + + if (ep->id > 0 && list_empty(&ep->queue)) { + setbits_le32(®s->gimr1, + GIMR1_FIFO_RX(ep_to_fifo(chip, ep->id))); + } +} + +/* + * USB Gadget Layer + */ +static int fotg210_ep_enable( + struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) +{ + struct fotg210_ep *ep = container_of(_ep, struct fotg210_ep, ep); + struct fotg210_chip *chip = ep->chip; + struct fotg210_regs *regs = chip->regs; + int id = ep_to_fifo(chip, ep->id); + int in = (desc->bEndpointAddress & USB_DIR_IN) ? 1 : 0; + + if (!_ep || !desc + || desc->bDescriptorType != USB_DT_ENDPOINT + || le16_to_cpu(desc->wMaxPacketSize) == 0) { + printf("fotg210: bad ep or descriptor\n"); + return -EINVAL; + } + + ep->desc = desc; + ep->stopped = 0; + + if (in) + setbits_le32(®s->fifomap, FIFOMAP(id, FIFOMAP_IN)); + + switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_CONTROL: + return -EINVAL; + + case USB_ENDPOINT_XFER_ISOC: + setbits_le32(®s->fifocfg, + FIFOCFG(id, FIFOCFG_EN | FIFOCFG_ISOC)); + break; + + case USB_ENDPOINT_XFER_BULK: + setbits_le32(®s->fifocfg, + FIFOCFG(id, FIFOCFG_EN | FIFOCFG_BULK)); + break; + + case USB_ENDPOINT_XFER_INT: + setbits_le32(®s->fifocfg, + FIFOCFG(id, FIFOCFG_EN | FIFOCFG_INTR)); + break; + } + + return 0; +} + +static int fotg210_ep_disable(struct usb_ep *_ep) +{ + struct fotg210_ep *ep = container_of(_ep, struct fotg210_ep, ep); + struct fotg210_chip *chip = ep->chip; + struct fotg210_regs *regs = chip->regs; + int id = ep_to_fifo(chip, ep->id); + + ep->desc = NULL; + ep->stopped = 1; + + clrbits_le32(®s->fifocfg, FIFOCFG(id, FIFOCFG_CFG_MASK)); + clrbits_le32(®s->fifomap, FIFOMAP(id, FIFOMAP_DIR_MASK)); + + return 0; +} + +static struct usb_request *fotg210_ep_alloc_request( + struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct fotg210_request *req = malloc(sizeof(*req)); + + if (req) { + memset(req, 0, sizeof(*req)); + INIT_LIST_HEAD(&req->queue); + } + return &req->req; +} + +static void fotg210_ep_free_request( + struct usb_ep *_ep, struct usb_request *_req) +{ + struct fotg210_request *req; + + req = container_of(_req, struct fotg210_request, req); + free(req); +} + +static int fotg210_ep_queue( + struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) +{ + struct fotg210_ep *ep = container_of(_ep, struct fotg210_ep, ep); + struct fotg210_chip *chip = ep->chip; + struct fotg210_regs *regs = chip->regs; + struct fotg210_request *req; + + req = container_of(_req, struct fotg210_request, req); + if (!_req || !_req->complete || !_req->buf + || !list_empty(&req->queue)) { + printf("fotg210: invalid request to ep%d\n", ep->id); + return -EINVAL; + } + + if (!chip || chip->state == USB_STATE_SUSPENDED) { + printf("fotg210: request while chip suspended\n"); + return -EINVAL; + } + + req->req.actual = 0; + req->req.status = -EINPROGRESS; + + if (req->req.length == 0) { + req->req.status = 0; + if (req->req.complete) + req->req.complete(&ep->ep, &req->req); + return 0; + } + + if (ep->id == 0) { + do { + int len = fotg210_dma(ep, req); + if (len < ep->ep.maxpacket) + break; + if (ep->desc->bEndpointAddress & USB_DIR_IN) + udelay(100); + } while (req->req.length > req->req.actual); + } else { + if (ep->desc->bEndpointAddress & USB_DIR_IN) { + do { + int len = fotg210_dma(ep, req); + if (len < ep->ep.maxpacket) + break; + } while (req->req.length > req->req.actual); + } else { + list_add_tail(&req->queue, &ep->queue); + clrbits_le32(®s->gimr1, + GIMR1_FIFO_RX(ep_to_fifo(chip, ep->id))); + } + } + + if (ep->id == 0 || (ep->desc->bEndpointAddress & USB_DIR_IN)) { + if (req->req.complete) + req->req.complete(&ep->ep, &req->req); + } + + return 0; +} + +static int fotg210_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct fotg210_ep *ep = container_of(_ep, struct fotg210_ep, ep); + struct fotg210_request *req; + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) + return -EINVAL; + + /* remove the request */ + list_del_init(&req->queue); + + /* update status & invoke complete callback */ + if (req->req.status == -EINPROGRESS) { + req->req.status = -ECONNRESET; + if (req->req.complete) + req->req.complete(_ep, &req->req); + } + + return 0; +} + +static int fotg210_ep_halt(struct usb_ep *_ep, int halt) +{ + struct fotg210_ep *ep = container_of(_ep, struct fotg210_ep, ep); + struct fotg210_chip *chip = ep->chip; + struct fotg210_regs *regs = chip->regs; + int ret = -1; + + debug("fotg210: ep%d halt=%d\n", ep->id, halt); + + /* Endpoint STALL */ + if (ep->id > 0 && ep->id <= CFG_NUM_ENDPOINTS) { + if (halt) { + /* wait until all ep fifo empty */ + fotg210_cxwait(chip, 0xf00); + /* stall */ + if (ep->desc->bEndpointAddress & USB_DIR_IN) { + setbits_le32(®s->iep[ep->id - 1], + IEP_STALL); + } else { + setbits_le32(®s->oep[ep->id - 1], + OEP_STALL); + } + } else { + if (ep->desc->bEndpointAddress & USB_DIR_IN) { + clrbits_le32(®s->iep[ep->id - 1], + IEP_STALL); + } else { + clrbits_le32(®s->oep[ep->id - 1], + OEP_STALL); + } + } + ret = 0; + } + + return ret; +} + +/* + * activate/deactivate link with host. + */ +static void pullup(struct fotg210_chip *chip, int is_on) +{ + struct fotg210_regs *regs = chip->regs; + + if (is_on) { + if (!chip->pullup) { + chip->state = USB_STATE_POWERED; + chip->pullup = 1; + /* enable the chip */ + setbits_le32(®s->dev_ctrl, DEVCTRL_EN); + /* clear unplug bit (BIT0) */ + clrbits_le32(®s->phy_tmsr, PHYTMSR_UNPLUG); + } + } else { + chip->state = USB_STATE_NOTATTACHED; + chip->pullup = 0; + chip->addr = 0; + writel(chip->addr, ®s->dev_addr); + /* set unplug bit (BIT0) */ + setbits_le32(®s->phy_tmsr, PHYTMSR_UNPLUG); + /* disable the chip */ + clrbits_le32(®s->dev_ctrl, DEVCTRL_EN); + } +} + +static int fotg210_pullup(struct usb_gadget *_gadget, int is_on) +{ + struct fotg210_chip *chip; + + chip = container_of(_gadget, struct fotg210_chip, gadget); + + debug("fotg210: pullup=%d\n", is_on); + + pullup(chip, is_on); + + return 0; +} + +static int fotg210_get_frame(struct usb_gadget *_gadget) +{ + struct fotg210_chip *chip; + struct fotg210_regs *regs; + + chip = container_of(_gadget, struct fotg210_chip, gadget); + regs = chip->regs; + + return SOFFNR_FNR(readl(®s->sof_fnr)); +} + +static struct usb_gadget_ops fotg210_gadget_ops = { + .get_frame = fotg210_get_frame, + .pullup = fotg210_pullup, +}; + +static struct usb_ep_ops fotg210_ep_ops = { + .enable = fotg210_ep_enable, + .disable = fotg210_ep_disable, + .queue = fotg210_ep_queue, + .dequeue = fotg210_ep_dequeue, + .set_halt = fotg210_ep_halt, + .alloc_request = fotg210_ep_alloc_request, + .free_request = fotg210_ep_free_request, +}; + +static struct fotg210_chip controller = { + .regs = (void __iomem *)CONFIG_FOTG210_BASE, + .gadget = { + .name = "fotg210_udc", + .ops = &fotg210_gadget_ops, + .ep0 = &controller.ep[0].ep, + .speed = USB_SPEED_UNKNOWN, + .is_dualspeed = 1, + .is_otg = 0, + .is_a_peripheral = 0, + .b_hnp_enable = 0, + .a_hnp_support = 0, + .a_alt_hnp_support = 0, + }, + .ep[0] = { + .id = 0, + .ep = { + .name = "ep0", + .ops = &fotg210_ep_ops, + }, + .desc = &ep0_desc, + .chip = &controller, + .maxpacket = CFG_EP0_MAX_PACKET_SIZE, + }, + .ep[1] = { + .id = 1, + .ep = { + .name = "ep1", + .ops = &fotg210_ep_ops, + }, + .chip = &controller, + .maxpacket = CFG_EPX_MAX_PACKET_SIZE, + }, + .ep[2] = { + .id = 2, + .ep = { + .name = "ep2", + .ops = &fotg210_ep_ops, + }, + .chip = &controller, + .maxpacket = CFG_EPX_MAX_PACKET_SIZE, + }, + .ep[3] = { + .id = 3, + .ep = { + .name = "ep3", + .ops = &fotg210_ep_ops, + }, + .chip = &controller, + .maxpacket = CFG_EPX_MAX_PACKET_SIZE, + }, + .ep[4] = { + .id = 4, + .ep = { + .name = "ep4", + .ops = &fotg210_ep_ops, + }, + .chip = &controller, + .maxpacket = CFG_EPX_MAX_PACKET_SIZE, + }, +}; + +int usb_gadget_handle_interrupts(int index) +{ + struct fotg210_chip *chip = &controller; + struct fotg210_regs *regs = chip->regs; + uint32_t id, st, isr, gisr; + + isr = readl(®s->isr) & (~readl(®s->imr)); + gisr = readl(®s->gisr) & (~readl(®s->gimr)); + if (!(isr & ISR_DEV) || !gisr) + return 0; + + writel(ISR_DEV, ®s->isr); + + /* CX interrupts */ + if (gisr & GISR_GRP0) { + st = readl(®s->gisr0); + /* + * Write 1 and then 0 works for both W1C & RW. + * + * HW v1.11.0+: It's a W1C register (write 1 clear) + * HW v1.10.0-: It's a R/W register (write 0 clear) + */ + writel(st & GISR0_CXABORT, ®s->gisr0); + writel(0, ®s->gisr0); + + if (st & GISR0_CXERR) + printf("fotg210: cmd error\n"); + + if (st & GISR0_CXABORT) + printf("fotg210: cmd abort\n"); + + if (st & GISR0_CXSETUP) /* setup */ + fotg210_setup(chip); + else if (st & GISR0_CXEND) /* command finish */ + setbits_le32(®s->cxfifo, CXFIFO_CXFIN); + } + + /* FIFO interrupts */ + if (gisr & GISR_GRP1) { + st = readl(®s->gisr1); + for (id = 0; id < 4; ++id) { + if (st & GISR1_RX_FIFO(id)) + fotg210_recv(chip, fifo_to_ep(chip, id, 0)); + } + } + + /* Device Status Interrupts */ + if (gisr & GISR_GRP2) { + st = readl(®s->gisr2); + /* + * Write 1 and then 0 works for both W1C & RW. + * + * HW v1.11.0+: It's a W1C register (write 1 clear) + * HW v1.10.0-: It's a R/W register (write 0 clear) + */ + writel(st, ®s->gisr2); + writel(0, ®s->gisr2); + + if (st & GISR2_RESET) + printf("fotg210: reset by host\n"); + else if (st & GISR2_SUSPEND) + printf("fotg210: suspend/removed\n"); + else if (st & GISR2_RESUME) + printf("fotg210: resume\n"); + + /* Errors */ + if (st & GISR2_ISOCERR) + printf("fotg210: iso error\n"); + if (st & GISR2_ISOCABT) + printf("fotg210: iso abort\n"); + if (st & GISR2_DMAERR) + printf("fotg210: dma error\n"); + } + + return 0; +} + +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + int i, ret = 0; + struct fotg210_chip *chip = &controller; + + if (!driver || !driver->bind || !driver->setup) { + puts("fotg210: bad parameter.\n"); + return -EINVAL; + } + + INIT_LIST_HEAD(&chip->gadget.ep_list); + for (i = 0; i < CFG_NUM_ENDPOINTS + 1; ++i) { + struct fotg210_ep *ep = chip->ep + i; + + ep->ep.maxpacket = ep->maxpacket; + INIT_LIST_HEAD(&ep->queue); + + if (ep->id == 0) { + ep->stopped = 0; + } else { + ep->stopped = 1; + list_add_tail(&ep->ep.ep_list, &chip->gadget.ep_list); + } + } + + if (fotg210_reset(chip)) { + puts("fotg210: reset failed.\n"); + return -EINVAL; + } + + ret = driver->bind(&chip->gadget); + if (ret) { + debug("fotg210: driver->bind() returned %d\n", ret); + return ret; + } + chip->driver = driver; + + return ret; +} + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct fotg210_chip *chip = &controller; + + driver->unbind(&chip->gadget); + chip->driver = NULL; + + pullup(chip, 0); + + return 0; +} diff --git a/roms/u-boot/drivers/usb/gadget/g_dnl.c b/roms/u-boot/drivers/usb/gadget/g_dnl.c new file mode 100644 index 000000000..afb7b74f3 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/g_dnl.c @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * g_dnl.c -- USB Downloader Gadget + * + * Copyright (C) 2012 Samsung Electronics + * Lukasz Majewski <l.majewski@samsung.com> + */ + +#include <common.h> +#include <log.h> +#include <malloc.h> + +#include <mmc.h> +#include <part.h> +#include <usb.h> + +#include <g_dnl.h> +#include <usb_mass_storage.h> +#include <dfu.h> +#include <thor.h> + +#include <env_callback.h> + +#include "gadget_chips.h" +#include "composite.c" + +/* + * One needs to define the following: + * CONFIG_USB_GADGET_VENDOR_NUM + * CONFIG_USB_GADGET_PRODUCT_NUM + * CONFIG_USB_GADGET_MANUFACTURER + * at e.g. ./configs/<board>_defconfig + */ + +#define STRING_MANUFACTURER 25 +#define STRING_PRODUCT 2 +/* Index of String Descriptor describing this configuration */ +#define STRING_USBDOWN 2 +/* Index of String serial */ +#define STRING_SERIAL 3 +#define MAX_STRING_SERIAL 256 +/* Number of supported configurations */ +#define CONFIGURATION_NUMBER 1 + +#define DRIVER_VERSION "usb_dnl 2.0" + +static const char product[] = "USB download gadget"; +static char g_dnl_serial[MAX_STRING_SERIAL]; +static const char manufacturer[] = CONFIG_USB_GADGET_MANUFACTURER; + +void g_dnl_set_serialnumber(char *s) +{ + memset(g_dnl_serial, 0, MAX_STRING_SERIAL); + strncpy(g_dnl_serial, s, MAX_STRING_SERIAL - 1); +} + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = __constant_cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .bDeviceSubClass = 0, /*0x02:CDC-modem , 0x00:CDC-serial*/ + + .idVendor = __constant_cpu_to_le16(CONFIG_USB_GADGET_VENDOR_NUM), + .idProduct = __constant_cpu_to_le16(CONFIG_USB_GADGET_PRODUCT_NUM), + /* .iProduct = DYNAMIC */ + /* .iSerialNumber = DYNAMIC */ + .bNumConfigurations = 1, +}; + +/* + * static strings, in UTF-8 + * IDs for those strings are assigned dynamically at g_dnl_bind() + */ +static struct usb_string g_dnl_string_defs[] = { + {.s = manufacturer}, + {.s = product}, + {.s = g_dnl_serial}, + { } /* end of list */ +}; + +static struct usb_gadget_strings g_dnl_string_tab = { + .language = 0x0409, /* en-us */ + .strings = g_dnl_string_defs, +}; + +static struct usb_gadget_strings *g_dnl_composite_strings[] = { + &g_dnl_string_tab, + NULL, +}; + +void g_dnl_set_product(const char *s) +{ + if (s) + g_dnl_string_defs[1].s = s; + else + g_dnl_string_defs[1].s = product; +} + +static int g_dnl_unbind(struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + + debug("%s: calling usb_gadget_disconnect for " + "controller '%s'\n", __func__, gadget->name); + usb_gadget_disconnect(gadget); + + return 0; +} + +static inline struct g_dnl_bind_callback *g_dnl_bind_callback_first(void) +{ + return ll_entry_start(struct g_dnl_bind_callback, + g_dnl_bind_callbacks); +} + +static inline struct g_dnl_bind_callback *g_dnl_bind_callback_end(void) +{ + return ll_entry_end(struct g_dnl_bind_callback, + g_dnl_bind_callbacks); +} + +static int g_dnl_do_config(struct usb_configuration *c) +{ + const char *s = c->cdev->driver->name; + struct g_dnl_bind_callback *callback = g_dnl_bind_callback_first(); + + debug("%s: configuration: 0x%p composite dev: 0x%p\n", + __func__, c, c->cdev); + + for (; callback != g_dnl_bind_callback_end(); callback++) + if (!strcmp(s, callback->usb_function_name)) + return callback->fptr(c); + return -ENODEV; +} + +static int g_dnl_config_register(struct usb_composite_dev *cdev) +{ + struct usb_configuration *config; + const char *name = "usb_dnload"; + + config = memalign(CONFIG_SYS_CACHELINE_SIZE, sizeof(*config)); + if (!config) + return -ENOMEM; + + memset(config, 0, sizeof(*config)); + + config->label = name; + config->bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER; + config->bConfigurationValue = CONFIGURATION_NUMBER; + config->iConfiguration = STRING_USBDOWN; + config->bind = g_dnl_do_config; + + return usb_add_config(cdev, config); +} + +__weak +int board_usb_init(int index, enum usb_init_type init) +{ + return 0; +} + +__weak +int board_usb_cleanup(int index, enum usb_init_type init) +{ + return 0; +} + +__weak +int g_dnl_bind_fixup(struct usb_device_descriptor *dev, const char *name) +{ + return 0; +} + +__weak int g_dnl_get_board_bcd_device_number(int gcnum) +{ + return gcnum; +} + +__weak int g_dnl_board_usb_cable_connected(void) +{ + return -EOPNOTSUPP; +} + +static bool g_dnl_detach_request; + +bool g_dnl_detach(void) +{ + return g_dnl_detach_request; +} + +void g_dnl_trigger_detach(void) +{ + g_dnl_detach_request = true; +} + +void g_dnl_clear_detach(void) +{ + g_dnl_detach_request = false; +} + +static int g_dnl_get_bcd_device_number(struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + int gcnum; + + gcnum = usb_gadget_controller_number(gadget); + if (gcnum > 0) + gcnum += 0x200; + + return g_dnl_get_board_bcd_device_number(gcnum); +} + +/** + * Update internal serial number variable when the "serial#" env var changes. + * + * Handle all cases, even when flags == H_PROGRAMMATIC or op == env_op_delete. + */ +static int on_serialno(const char *name, const char *value, enum env_op op, + int flags) +{ + g_dnl_set_serialnumber((char *)value); + return 0; +} +U_BOOT_ENV_CALLBACK(serialno, on_serialno); + +static int g_dnl_bind(struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + int id, ret; + int gcnum; + + debug("%s: gadget: 0x%p cdev: 0x%p\n", __func__, gadget, cdev); + + id = usb_string_id(cdev); + + if (id < 0) + return id; + g_dnl_string_defs[0].id = id; + device_desc.iManufacturer = id; + + id = usb_string_id(cdev); + if (id < 0) + return id; + + g_dnl_string_defs[1].id = id; + device_desc.iProduct = id; + + g_dnl_bind_fixup(&device_desc, cdev->driver->name); + + if (strlen(g_dnl_serial)) { + id = usb_string_id(cdev); + if (id < 0) + return id; + + g_dnl_string_defs[2].id = id; + device_desc.iSerialNumber = id; + } + + ret = g_dnl_config_register(cdev); + if (ret) + goto error; + + gcnum = g_dnl_get_bcd_device_number(cdev); + if (gcnum >= 0) + device_desc.bcdDevice = cpu_to_le16(gcnum); + else { + debug("%s: controller '%s' not recognized\n", + __func__, gadget->name); + device_desc.bcdDevice = __constant_cpu_to_le16(0x9999); + } + + debug("%s: calling usb_gadget_connect for " + "controller '%s'\n", __func__, gadget->name); + usb_gadget_connect(gadget); + + return 0; + + error: + g_dnl_unbind(cdev); + return -ENOMEM; +} + +static struct usb_composite_driver g_dnl_driver = { + .name = NULL, + .dev = &device_desc, + .strings = g_dnl_composite_strings, + .max_speed = USB_SPEED_SUPER, + + .bind = g_dnl_bind, + .unbind = g_dnl_unbind, +}; + +/* + * NOTICE: + * Registering via USB function name won't be necessary after rewriting + * g_dnl to support multiple USB functions. + */ +int g_dnl_register(const char *name) +{ + int ret; + + debug("%s: g_dnl_driver.name = %s\n", __func__, name); + g_dnl_driver.name = name; + + ret = usb_composite_register(&g_dnl_driver); + if (ret) { + printf("%s: failed!, error: %d\n", __func__, ret); + return ret; + } + return 0; +} + +void g_dnl_unregister(void) +{ + usb_composite_unregister(&g_dnl_driver); +} diff --git a/roms/u-boot/drivers/usb/gadget/gadget_chips.h b/roms/u-boot/drivers/usb/gadget/gadget_chips.h new file mode 100644 index 000000000..0cdf47c2d --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/gadget_chips.h @@ -0,0 +1,236 @@ +/* + * USB device controllers have lots of quirks. Use these macros in + * gadget drivers or other code that needs to deal with them, and which + * autoconfigures instead of using early binding to the hardware. + * + * This SHOULD eventually work like the ARM mach_is_*() stuff, driven by + * some config file that gets updated as new hardware is supported. + * (And avoiding all runtime comparisons in typical one-choice configs!) + * + * NOTE: some of these controller drivers may not be available yet. + * Some are available on 2.4 kernels; several are available, but not + * yet pushed in the 2.6 mainline tree. + * + * Ported to U-Boot by: Thomas Smits <ts.smits@gmail.com> and + * Remy Bohmer <linux@bohmer.net> + */ +#ifdef CONFIG_USB_GADGET_NET2280 +#define gadget_is_net2280(g) (!strcmp("net2280", (g)->name)) +#else +#define gadget_is_net2280(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_AMD5536UDC +#define gadget_is_amd5536udc(g) (!strcmp("amd5536udc", (g)->name)) +#else +#define gadget_is_amd5536udc(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_DUMMY_HCD +#define gadget_is_dummy(g) (!strcmp("dummy_udc", (g)->name)) +#else +#define gadget_is_dummy(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_PXA2XX +#define gadget_is_pxa(g) (!strcmp("pxa2xx_udc", (g)->name)) +#else +#define gadget_is_pxa(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_GOKU +#define gadget_is_goku(g) (!strcmp("goku_udc", (g)->name)) +#else +#define gadget_is_goku(g) 0 +#endif + +/* SH3 UDC -- not yet ported 2.4 --> 2.6 */ +#ifdef CONFIG_USB_GADGET_SUPERH +#define gadget_is_sh(g) (!strcmp("sh_udc", (g)->name)) +#else +#define gadget_is_sh(g) 0 +#endif + +/* not yet stable on 2.6 (would help "original Zaurus") */ +#ifdef CONFIG_USB_GADGET_SA1100 +#define gadget_is_sa1100(g) (!strcmp("sa1100_udc", (g)->name)) +#else +#define gadget_is_sa1100(g) 0 +#endif + +/* handhelds.org tree (?) */ +#ifdef CONFIG_USB_GADGET_MQ11XX +#define gadget_is_mq11xx(g) (!strcmp("mq11xx_udc", (g)->name)) +#else +#define gadget_is_mq11xx(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_OMAP +#define gadget_is_omap(g) (!strcmp("omap_udc", (g)->name)) +#else +#define gadget_is_omap(g) 0 +#endif + +/* not yet ported 2.4 --> 2.6 */ +#ifdef CONFIG_USB_GADGET_N9604 +#define gadget_is_n9604(g) (!strcmp("n9604_udc", (g)->name)) +#else +#define gadget_is_n9604(g) 0 +#endif + +/* various unstable versions available */ +#ifdef CONFIG_USB_GADGET_PXA27X +#define gadget_is_pxa27x(g) (!strcmp("pxa27x_udc", (g)->name)) +#else +#define gadget_is_pxa27x(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_ATMEL_USBA +#define gadget_is_atmel_usba(g) (!strcmp("atmel_usba_udc", (g)->name)) +#else +#define gadget_is_atmel_usba(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_AT91 +#define gadget_is_at91(g) (!strcmp("at91_udc", (g)->name)) +#else +#define gadget_is_at91(g) 0 +#endif + +/* status unclear */ +#ifdef CONFIG_USB_GADGET_IMX +#define gadget_is_imx(g) (!strcmp("imx_udc", (g)->name)) +#else +#define gadget_is_imx(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_FSL_USB2 +#define gadget_is_fsl_usb2(g) (!strcmp("fsl-usb2-udc", (g)->name)) +#else +#define gadget_is_fsl_usb2(g) 0 +#endif + +/* Mentor high speed function controller */ +/* from Montavista kernel (?) */ +#ifdef CONFIG_USB_GADGET_MUSBHSFC +#define gadget_is_musbhsfc(g) (!strcmp("musbhsfc_udc", (g)->name)) +#else +#define gadget_is_musbhsfc(g) 0 +#endif + +/* Mentor high speed "dual role" controller, in peripheral role */ +#ifdef CONFIG_USB_MUSB_GADGET +#define gadget_is_musbhdrc(g) (!strcmp("musb-hdrc", (g)->name)) +#else +#define gadget_is_musbhdrc(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_M66592 +#define gadget_is_m66592(g) (!strcmp("m66592_udc", (g)->name)) +#else +#define gadget_is_m66592(g) 0 +#endif + +#ifdef CONFIG_CI_UDC +#define gadget_is_ci(g) (!strcmp("ci_udc", (g)->name)) +#else +#define gadget_is_ci(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_FOTG210 +#define gadget_is_fotg210(g) (!strcmp("fotg210_udc", (g)->name)) +#else +#define gadget_is_fotg210(g) 0 +#endif + +#ifdef CONFIG_USB_DWC3_GADGET +#define gadget_is_dwc3(g) (!strcmp("dwc3-gadget", (g)->name)) +#else +#define gadget_is_dwc3(g) 0 +#endif + +#ifdef CONFIG_USB_CDNS3_GADGET +#define gadget_is_cdns3(g) (!strcmp("cdns3-gadget", (g)->name)) +#else +#define gadget_is_cdns3(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_MAX3420 +#define gadget_is_max3420(g) (!strcmp("max3420-udc", (g)->name)) +#else +#define gadget_is_max3420(g) 0 +#endif + +#ifdef CONFIG_USB_MTU3_GADGET +#define gadget_is_mtu3(g) (!strcmp("mtu3-gadget", (g)->name)) +#else +#define gadget_is_mtu3(g) 0 +#endif + +/** + * usb_gadget_controller_number - support bcdDevice id convention + * @gadget: the controller being driven + * + * Return a 2-digit BCD value associated with the peripheral controller, + * suitable for use as part of a bcdDevice value, or a negative error code. + * + * NOTE: this convention is purely optional, and has no meaning in terms of + * any USB specification. If you want to use a different convention in your + * gadget driver firmware -- maybe a more formal revision ID -- feel free. + * + * Hosts see these bcdDevice numbers, and are allowed (but not encouraged!) + * to change their behavior accordingly. For example it might help avoiding + * some chip bug. + */ +static inline int usb_gadget_controller_number(struct usb_gadget *gadget) +{ + if (gadget_is_net2280(gadget)) + return 0x01; + else if (gadget_is_dummy(gadget)) + return 0x02; + else if (gadget_is_pxa(gadget)) + return 0x03; + else if (gadget_is_sh(gadget)) + return 0x04; + else if (gadget_is_sa1100(gadget)) + return 0x05; + else if (gadget_is_goku(gadget)) + return 0x06; + else if (gadget_is_mq11xx(gadget)) + return 0x07; + else if (gadget_is_omap(gadget)) + return 0x08; + else if (gadget_is_n9604(gadget)) + return 0x09; + else if (gadget_is_pxa27x(gadget)) + return 0x10; + else if (gadget_is_at91(gadget)) + return 0x12; + else if (gadget_is_imx(gadget)) + return 0x13; + else if (gadget_is_musbhsfc(gadget)) + return 0x14; + else if (gadget_is_musbhdrc(gadget)) + return 0x15; + else if (gadget_is_atmel_usba(gadget)) + return 0x17; + else if (gadget_is_fsl_usb2(gadget)) + return 0x18; + else if (gadget_is_amd5536udc(gadget)) + return 0x19; + else if (gadget_is_m66592(gadget)) + return 0x20; + else if (gadget_is_ci(gadget)) + return 0x21; + else if (gadget_is_fotg210(gadget)) + return 0x22; + else if (gadget_is_dwc3(gadget)) + return 0x23; + else if (gadget_is_cdns3(gadget)) + return 0x24; + else if (gadget_is_max3420(gadget)) + return 0x25; + else if (gadget_is_mtu3(gadget)) + return 0x26; + return -ENOENT; +} diff --git a/roms/u-boot/drivers/usb/gadget/max3420_udc.c b/roms/u-boot/drivers/usb/gadget/max3420_udc.c new file mode 100644 index 000000000..a16095f89 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/max3420_udc.c @@ -0,0 +1,875 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <common.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <asm/gpio.h> +#include <linux/list.h> +#include <linux/bitfield.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <malloc.h> +#include <spi.h> +#include <dm.h> +#include <g_dnl.h> + +#define MAX3420_MAX_EPS 4 +#define EP_MAX_PACKET 64 /* Same for all Endpoints */ +#define EPNAME_SIZE 16 /* Buffer size for endpoint name */ + +#define MAX3420_SPI_DIR_RD 0 /* read register from MAX3420 */ +#define MAX3420_SPI_DIR_WR 1 /* write register to MAX3420 */ + +/* SPI commands: */ +#define MAX3420_SPI_ACK_MASK BIT(0) +#define MAX3420_SPI_DIR_MASK BIT(1) +#define MAX3420_SPI_REG_MASK GENMASK(7, 3) + +#define MAX3420_REG_EP0FIFO 0 +#define MAX3420_REG_EP1FIFO 1 +#define MAX3420_REG_EP2FIFO 2 +#define MAX3420_REG_EP3FIFO 3 +#define MAX3420_REG_SUDFIFO 4 +#define MAX3420_REG_EP0BC 5 +#define MAX3420_REG_EP1BC 6 +#define MAX3420_REG_EP2BC 7 +#define MAX3420_REG_EP3BC 8 + +#define MAX3420_REG_EPSTALLS 9 + #define bACKSTAT BIT(6) + #define bSTLSTAT BIT(5) + #define bSTLEP3IN BIT(4) + #define bSTLEP2IN BIT(3) + #define bSTLEP1OUT BIT(2) + #define bSTLEP0OUT BIT(1) + #define bSTLEP0IN BIT(0) + +#define MAX3420_REG_CLRTOGS 10 + #define bEP3DISAB BIT(7) + #define bEP2DISAB BIT(6) + #define bEP1DISAB BIT(5) + #define bCTGEP3IN BIT(4) + #define bCTGEP2IN BIT(3) + #define bCTGEP1OUT BIT(2) + +#define MAX3420_REG_EPIRQ 11 +#define MAX3420_REG_EPIEN 12 + #define bSUDAVIRQ BIT(5) + #define bIN3BAVIRQ BIT(4) + #define bIN2BAVIRQ BIT(3) + #define bOUT1DAVIRQ BIT(2) + #define bOUT0DAVIRQ BIT(1) + #define bIN0BAVIRQ BIT(0) + +#define MAX3420_REG_USBIRQ 13 +#define MAX3420_REG_USBIEN 14 + #define bOSCOKIRQ BIT(0) + #define bRWUDNIRQ BIT(1) + #define bBUSACTIRQ BIT(2) + #define bURESIRQ BIT(3) + #define bSUSPIRQ BIT(4) + #define bNOVBUSIRQ BIT(5) + #define bVBUSIRQ BIT(6) + #define bURESDNIRQ BIT(7) + +#define MAX3420_REG_USBCTL 15 + #define bHOSCSTEN BIT(7) + #define bVBGATE BIT(6) + #define bCHIPRES BIT(5) + #define bPWRDOWN BIT(4) + #define bCONNECT BIT(3) + #define bSIGRWU BIT(2) + +#define MAX3420_REG_CPUCTL 16 + #define bIE BIT(0) + +#define MAX3420_REG_PINCTL 17 + #define bEP3INAK BIT(7) + #define bEP2INAK BIT(6) + #define bEP0INAK BIT(5) + #define bFDUPSPI BIT(4) + #define bINTLEVEL BIT(3) + #define bPOSINT BIT(2) + #define bGPXB BIT(1) + #define bGPXA BIT(0) + +#define MAX3420_REG_REVISION 18 + +#define MAX3420_REG_FNADDR 19 + #define FNADDR_MASK 0x7f + +#define MAX3420_REG_IOPINS 20 +#define MAX3420_REG_IOPINS2 21 +#define MAX3420_REG_GPINIRQ 22 +#define MAX3420_REG_GPINIEN 23 +#define MAX3420_REG_GPINPOL 24 +#define MAX3420_REG_HIRQ 25 +#define MAX3420_REG_HIEN 26 +#define MAX3420_REG_MODE 27 +#define MAX3420_REG_PERADDR 28 +#define MAX3420_REG_HCTL 29 +#define MAX3420_REG_HXFR 30 +#define MAX3420_REG_HRSL 31 + +struct max3420_req { + struct usb_request usb_req; + struct list_head queue; + struct max3420_ep *ep; +}; + +struct max3420_ep { + struct max3420_udc *udc; + struct list_head queue; + char name[EPNAME_SIZE]; + unsigned int maxpacket; + struct usb_ep ep_usb; + int halted; + int id; +}; + +struct max3420_udc { + struct max3420_ep ep[MAX3420_MAX_EPS]; + struct usb_gadget_driver *driver; + bool softconnect; + struct usb_ctrlrequest setup; + struct max3420_req ep0req; + struct usb_gadget gadget; + struct spi_slave *slave; + struct udevice *dev; + u8 ep0buf[64]; + int remote_wkp; + bool suspended; +}; + +#define to_max3420_req(r) container_of((r), struct max3420_req, usb_req) +#define to_max3420_ep(e) container_of((e), struct max3420_ep, ep_usb) +#define to_udc(g) container_of((g), struct max3420_udc, gadget) + +static void spi_ack_ctrl(struct max3420_udc *udc) +{ + struct spi_slave *slave = udc->slave; + u8 txdata[1]; + + txdata[0] = FIELD_PREP(MAX3420_SPI_ACK_MASK, 1); + spi_xfer(slave, sizeof(txdata), txdata, NULL, SPI_XFER_ONCE); +} + +static u8 spi_rd8_ack(struct max3420_udc *udc, u8 reg, int ackstat) +{ + struct spi_slave *slave = udc->slave; + u8 txdata[2], rxdata[2]; + + txdata[0] = FIELD_PREP(MAX3420_SPI_REG_MASK, reg) | + FIELD_PREP(MAX3420_SPI_DIR_MASK, MAX3420_SPI_DIR_RD) | + FIELD_PREP(MAX3420_SPI_ACK_MASK, ackstat ? 1 : 0); + + rxdata[0] = 0; + rxdata[1] = 0; + spi_xfer(slave, sizeof(txdata), txdata, rxdata, SPI_XFER_ONCE); + + return rxdata[1]; +} + +static u8 spi_rd8(struct max3420_udc *udc, u8 reg) +{ + return spi_rd8_ack(udc, reg, 0); +} + +static void spi_wr8_ack(struct max3420_udc *udc, u8 reg, u8 val, int ackstat) +{ + struct spi_slave *slave = udc->slave; + u8 txdata[2]; + + txdata[0] = FIELD_PREP(MAX3420_SPI_REG_MASK, reg) | + FIELD_PREP(MAX3420_SPI_DIR_MASK, MAX3420_SPI_DIR_WR) | + FIELD_PREP(MAX3420_SPI_ACK_MASK, ackstat ? 1 : 0); + txdata[1] = val; + + spi_xfer(slave, sizeof(txdata), txdata, NULL, SPI_XFER_ONCE); +} + +static void spi_wr8(struct max3420_udc *udc, u8 reg, u8 val) +{ + spi_wr8_ack(udc, reg, val, 0); +} + +static void spi_rd_buf(struct max3420_udc *udc, u8 reg, void *buf, u8 len) +{ + struct spi_slave *slave = udc->slave; + u8 txdata[1]; + + txdata[0] = FIELD_PREP(MAX3420_SPI_REG_MASK, reg) | + FIELD_PREP(MAX3420_SPI_DIR_MASK, MAX3420_SPI_DIR_RD); + + spi_xfer(slave, sizeof(txdata), txdata, NULL, SPI_XFER_BEGIN); + spi_xfer(slave, len * 8, NULL, buf, SPI_XFER_END); +} + +static void spi_wr_buf(struct max3420_udc *udc, u8 reg, void *buf, u8 len) +{ + struct spi_slave *slave = udc->slave; + u8 txdata[1]; + + txdata[0] = FIELD_PREP(MAX3420_SPI_REG_MASK, reg) | + FIELD_PREP(MAX3420_SPI_DIR_MASK, MAX3420_SPI_DIR_WR); + + spi_xfer(slave, sizeof(txdata), txdata, NULL, SPI_XFER_BEGIN); + spi_xfer(slave, len * 8, buf, NULL, SPI_XFER_END); +} + +/* 0 if not-connected */ +int g_dnl_board_usb_cable_connected(void) +{ + return 1; +} + +static void spi_max3420_enable(struct max3420_ep *ep, int enable) +{ + struct max3420_udc *udc = ep->udc; + u8 epdis, epien; + + if (ep->id == 0) + return; + + epien = spi_rd8(udc, MAX3420_REG_EPIEN); + epdis = spi_rd8(udc, MAX3420_REG_CLRTOGS); + + if (enable) { + epdis &= ~BIT(ep->id + 4); + epien |= BIT(ep->id + 1); + } else { + epdis |= BIT(ep->id + 4); + epien &= ~BIT(ep->id + 1); + } + + spi_wr8(udc, MAX3420_REG_CLRTOGS, epdis); + spi_wr8(udc, MAX3420_REG_EPIEN, epien); +} + +static int +max3420_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct max3420_ep *ep = to_max3420_ep(_ep); + + _ep->desc = desc; + _ep->maxpacket = usb_endpoint_maxp(desc) & 0x7ff; + + spi_max3420_enable(ep, 1); + + return 0; +} + +static void max3420_req_done(struct max3420_req *req, int status) +{ + struct max3420_ep *ep = req->ep; + + if (req->usb_req.status == -EINPROGRESS) + req->usb_req.status = status; + else + status = req->usb_req.status; + + if (status && status != -ESHUTDOWN) + dev_err(ep->udc->dev, "%s done %p, status %d\n", + ep->ep_usb.name, req, status); + + if (req->usb_req.complete) + req->usb_req.complete(&ep->ep_usb, &req->usb_req); +} + +static void max3420_ep_nuke(struct max3420_ep *ep, int status) +{ + struct max3420_req *req, *r; + + list_for_each_entry_safe(req, r, &ep->queue, queue) { + list_del_init(&req->queue); + max3420_req_done(req, status); + } +} + +static int max3420_ep_disable(struct usb_ep *_ep) +{ + struct max3420_ep *ep = to_max3420_ep(_ep); + + _ep->desc = NULL; + max3420_ep_nuke(ep, -ESHUTDOWN); + spi_max3420_enable(ep, 0); + + return 0; +} + +static struct usb_request * +max3420_ep_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct max3420_ep *ep = to_max3420_ep(_ep); + struct max3420_req *req = kzalloc(sizeof(*req), gfp_flags); + + if (!req) + return NULL; + + req->ep = ep; + INIT_LIST_HEAD(&req->queue); + + return &req->usb_req; +} + +static void +max3420_ep_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + kfree(to_max3420_req(_req)); +} + +static int +max3420_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) +{ + struct max3420_req *req = to_max3420_req(_req); + struct max3420_ep *ep = to_max3420_ep(_ep); + + _req->status = -EINPROGRESS; + _req->actual = 0; + list_add_tail(&req->queue, &ep->queue); + + return 0; +} + +static int max3420_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct max3420_req *req = to_max3420_req(_req); + + list_del_init(&req->queue); + max3420_req_done(req, -ECONNRESET); + + return 0; +} + +static int max3420_ep_set_halt(struct usb_ep *_ep, int halt) +{ + struct max3420_ep *ep = to_max3420_ep(_ep); + struct max3420_udc *udc = ep->udc; + u8 epstalls; + + if (ep->id == 0) /* can't stall EP0 */ + return 0; + + epstalls = spi_rd8(udc, MAX3420_REG_EPSTALLS); + if (halt) { + ep->halted = 1; + epstalls |= BIT(ep->id + 1); + } else { + u8 clrtogs; + + ep->halted = 0; + epstalls &= ~BIT(ep->id + 1); + clrtogs = spi_rd8(udc, MAX3420_REG_CLRTOGS); + clrtogs |= BIT(ep->id + 1); + spi_wr8(udc, MAX3420_REG_CLRTOGS, clrtogs); + } + spi_wr8(udc, MAX3420_REG_EPSTALLS, epstalls | bACKSTAT); + + return 0; +} + +static const struct usb_ep_ops max3420_ep_ops = { + .enable = max3420_ep_enable, + .disable = max3420_ep_disable, + .alloc_request = max3420_ep_alloc_request, + .free_request = max3420_ep_free_request, + .queue = max3420_ep_queue, + .dequeue = max3420_ep_dequeue, + .set_halt = max3420_ep_set_halt, +}; + +static void __max3420_stop(struct max3420_udc *udc) +{ + u8 val; + + /* Disable IRQ to CPU */ + spi_wr8(udc, MAX3420_REG_CPUCTL, 0); + + val = spi_rd8(udc, MAX3420_REG_USBCTL); + val |= bPWRDOWN; + val |= bHOSCSTEN; + spi_wr8(udc, MAX3420_REG_USBCTL, val); +} + +static void __max3420_start(struct max3420_udc *udc) +{ + u8 val; + + /* configure SPI */ + spi_wr8(udc, MAX3420_REG_PINCTL, bFDUPSPI); + + /* Chip Reset */ + spi_wr8(udc, MAX3420_REG_USBCTL, bCHIPRES); + mdelay(5); + spi_wr8(udc, MAX3420_REG_USBCTL, 0); + + /* Poll for OSC to stabilize */ + while (1) { + val = spi_rd8(udc, MAX3420_REG_USBIRQ); + if (val & bOSCOKIRQ) + break; + cond_resched(); + } + + /* Enable PULL-UP only when Vbus detected */ + val = spi_rd8(udc, MAX3420_REG_USBCTL); + val |= bVBGATE | bCONNECT; + spi_wr8(udc, MAX3420_REG_USBCTL, val); + + val = bURESDNIRQ | bURESIRQ; + spi_wr8(udc, MAX3420_REG_USBIEN, val); + + /* Enable only EP0 interrupts */ + val = bIN0BAVIRQ | bOUT0DAVIRQ | bSUDAVIRQ; + spi_wr8(udc, MAX3420_REG_EPIEN, val); + + /* Enable IRQ to CPU */ + spi_wr8(udc, MAX3420_REG_CPUCTL, bIE); +} + +static int max3420_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct max3420_udc *udc = to_udc(gadget); + + udc->driver = driver; + udc->remote_wkp = 0; + udc->softconnect = true; + + __max3420_start(udc); + + return 0; +} + +static int max3420_udc_stop(struct usb_gadget *gadget) +{ + struct max3420_udc *udc = to_udc(gadget); + + udc->driver = NULL; + udc->softconnect = false; + + __max3420_stop(udc); + + return 0; +} + +static int max3420_wakeup(struct usb_gadget *gadget) +{ + struct max3420_udc *udc = to_udc(gadget); + u8 usbctl; + + /* Only if wakeup allowed by host */ + if (!udc->remote_wkp || !udc->suspended) + return 0; + + /* Set Remote-Wakeup Signal*/ + usbctl = spi_rd8(udc, MAX3420_REG_USBCTL); + usbctl |= bSIGRWU; + spi_wr8(udc, MAX3420_REG_USBCTL, usbctl); + + mdelay(5); + + /* Clear Remote-WkUp Signal*/ + usbctl = spi_rd8(udc, MAX3420_REG_USBCTL); + usbctl &= ~bSIGRWU; + spi_wr8(udc, MAX3420_REG_USBCTL, usbctl); + + udc->suspended = false; + + return 0; +} + +static const struct usb_gadget_ops max3420_udc_ops = { + .udc_start = max3420_udc_start, + .udc_stop = max3420_udc_stop, + .wakeup = max3420_wakeup, +}; + +static struct usb_endpoint_descriptor ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = cpu_to_le16(EP_MAX_PACKET), +}; + +static void max3420_getstatus(struct max3420_udc *udc) +{ + struct max3420_ep *ep; + u16 status = 0; + + switch (udc->setup.bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + /* Get device status */ + status = 0 << USB_DEVICE_SELF_POWERED; + status |= (udc->remote_wkp << USB_DEVICE_REMOTE_WAKEUP); + break; + case USB_RECIP_INTERFACE: + if (udc->driver->setup(&udc->gadget, &udc->setup) < 0) + goto stall; + break; + case USB_RECIP_ENDPOINT: + ep = &udc->ep[udc->setup.wIndex & USB_ENDPOINT_NUMBER_MASK]; + if (ep->halted) + status = 1 << USB_ENDPOINT_HALT; + break; + default: + goto stall; + } + + status = cpu_to_le16(status); + spi_wr_buf(udc, MAX3420_REG_EP0FIFO, &status, 2); + spi_wr8_ack(udc, MAX3420_REG_EP0BC, 2, 1); + return; +stall: + dev_err(udc->dev, "Can't respond to getstatus request\n"); + spi_wr8(udc, MAX3420_REG_EPSTALLS, bSTLEP0IN | bSTLEP0OUT | bSTLSTAT); +} + +static void max3420_set_clear_feature(struct max3420_udc *udc) +{ + int set = udc->setup.bRequest == USB_REQ_SET_FEATURE; + struct max3420_ep *ep; + int id; + + switch (udc->setup.bRequestType) { + case USB_RECIP_DEVICE: + if (udc->setup.wValue != USB_DEVICE_REMOTE_WAKEUP) + break; + + if (udc->setup.bRequest == USB_REQ_SET_FEATURE) + udc->remote_wkp = 1; + else + udc->remote_wkp = 0; + + return spi_ack_ctrl(udc); + + case USB_RECIP_ENDPOINT: + if (udc->setup.wValue != USB_ENDPOINT_HALT) + break; + + id = udc->setup.wIndex & USB_ENDPOINT_NUMBER_MASK; + ep = &udc->ep[id]; + + max3420_ep_set_halt(&ep->ep_usb, set); + return; + default: + break; + } + + dev_err(udc->dev, "Can't respond to SET/CLEAR FEATURE\n"); + spi_wr8(udc, MAX3420_REG_EPSTALLS, bSTLEP0IN | bSTLEP0OUT | bSTLSTAT); +} + +static void max3420_handle_setup(struct max3420_udc *udc) +{ + struct usb_ctrlrequest setup; + u8 addr; + + spi_rd_buf(udc, MAX3420_REG_SUDFIFO, (void *)&setup, 8); + + udc->setup = setup; + udc->setup.wValue = cpu_to_le16(setup.wValue); + udc->setup.wIndex = cpu_to_le16(setup.wIndex); + udc->setup.wLength = cpu_to_le16(setup.wLength); + + switch (udc->setup.bRequest) { + case USB_REQ_GET_STATUS: + /* Data+Status phase form udc */ + if ((udc->setup.bRequestType & + (USB_DIR_IN | USB_TYPE_MASK)) != + (USB_DIR_IN | USB_TYPE_STANDARD)) { + break; + } + return max3420_getstatus(udc); + case USB_REQ_SET_ADDRESS: + /* Status phase from udc */ + if (udc->setup.bRequestType != (USB_DIR_OUT | + USB_TYPE_STANDARD | USB_RECIP_DEVICE)) + break; + addr = spi_rd8_ack(udc, MAX3420_REG_FNADDR, 1); + dev_dbg(udc->dev, "Assigned Address=%d/%d\n", + udc->setup.wValue, addr); + return; + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + /* Requests with no data phase, status phase from udc */ + if ((udc->setup.bRequestType & USB_TYPE_MASK) + != USB_TYPE_STANDARD) + break; + return max3420_set_clear_feature(udc); + default: + break; + } + + if (udc->driver->setup(&udc->gadget, &setup) < 0) { + /* Stall EP0 */ + spi_wr8(udc, MAX3420_REG_EPSTALLS, + bSTLEP0IN | bSTLEP0OUT | bSTLSTAT); + } +} + +static int do_data(struct max3420_udc *udc, int ep_id, int in) +{ + struct max3420_ep *ep = &udc->ep[ep_id]; + struct max3420_req *req; + int done, length, psz; + void *buf; + + if (list_empty(&ep->queue)) + return 0; + + req = list_first_entry(&ep->queue, struct max3420_req, queue); + buf = req->usb_req.buf + req->usb_req.actual; + + psz = ep->ep_usb.maxpacket; + length = req->usb_req.length - req->usb_req.actual; + length = min(length, psz); + + if (length == 0) { + done = 1; + goto xfer_done; + } + + done = 0; + if (in) { + spi_wr_buf(udc, MAX3420_REG_EP0FIFO + ep_id, buf, length); + spi_wr8(udc, MAX3420_REG_EP0BC + ep_id, length); + if (length < psz) + done = 1; + } else { + psz = spi_rd8(udc, MAX3420_REG_EP0BC + ep_id); + length = min(length, psz); + spi_rd_buf(udc, MAX3420_REG_EP0FIFO + ep_id, buf, length); + if (length < ep->ep_usb.maxpacket) + done = 1; + } + + req->usb_req.actual += length; + + if (req->usb_req.actual == req->usb_req.length) + done = 1; + +xfer_done: + if (done) { + list_del_init(&req->queue); + + if (ep_id == 0) + spi_ack_ctrl(udc); + + max3420_req_done(req, 0); + } + + return 1; +} + +static int max3420_handle_irqs(struct max3420_udc *udc) +{ + u8 epien, epirq, usbirq, usbien, reg[4]; + int ret = 0; + + spi_rd_buf(udc, MAX3420_REG_EPIRQ, reg, 4); + epirq = reg[0]; + epien = reg[1]; + usbirq = reg[2]; + usbien = reg[3]; + + usbirq &= usbien; + epirq &= epien; + + if (epirq & bSUDAVIRQ) { + spi_wr8(udc, MAX3420_REG_EPIRQ, bSUDAVIRQ); + max3420_handle_setup(udc); + return 1; + } + + if (usbirq & bVBUSIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, bVBUSIRQ); + dev_dbg(udc->dev, "Cable plugged in\n"); + g_dnl_clear_detach(); + return 1; + } + + if (usbirq & bNOVBUSIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, bNOVBUSIRQ); + dev_dbg(udc->dev, "Cable pulled out\n"); + g_dnl_trigger_detach(); + return 1; + } + + if (usbirq & bURESIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, bURESIRQ); + return 1; + } + + if (usbirq & bURESDNIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, bURESDNIRQ); + spi_wr8(udc, MAX3420_REG_USBIEN, bURESDNIRQ | bURESIRQ); + spi_wr8(udc, MAX3420_REG_EPIEN, bSUDAVIRQ + | bIN0BAVIRQ | bOUT0DAVIRQ); + return 1; + } + + if (usbirq & bSUSPIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, bSUSPIRQ); + dev_dbg(udc->dev, "USB Suspend - Enter\n"); + udc->suspended = true; + return 1; + } + + if (usbirq & bBUSACTIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, bBUSACTIRQ); + dev_dbg(udc->dev, "USB Suspend - Exit\n"); + udc->suspended = false; + return 1; + } + + if (usbirq & bRWUDNIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, bRWUDNIRQ); + dev_dbg(udc->dev, "Asked Host to wakeup\n"); + return 1; + } + + if (usbirq & bOSCOKIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, bOSCOKIRQ); + dev_dbg(udc->dev, "Osc stabilized, start work\n"); + return 1; + } + + if (epirq & bOUT0DAVIRQ && do_data(udc, 0, 0)) { + spi_wr8_ack(udc, MAX3420_REG_EPIRQ, bOUT0DAVIRQ, 1); + ret = 1; + } + + if (epirq & bIN0BAVIRQ && do_data(udc, 0, 1)) + ret = 1; + + if (epirq & bOUT1DAVIRQ && do_data(udc, 1, 0)) { + spi_wr8_ack(udc, MAX3420_REG_EPIRQ, bOUT1DAVIRQ, 1); + ret = 1; + } + + if (epirq & bIN2BAVIRQ && do_data(udc, 2, 1)) + ret = 1; + + if (epirq & bIN3BAVIRQ && do_data(udc, 3, 1)) + ret = 1; + + return ret; +} + +static int max3420_irq(struct max3420_udc *udc) +{ + do_data(udc, 0, 1); /* get done with the EP0 ZLP */ + + return max3420_handle_irqs(udc); +} + +static void max3420_setup_eps(struct max3420_udc *udc) +{ + int i; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + INIT_LIST_HEAD(&udc->ep[0].ep_usb.ep_list); + + for (i = 0; i < MAX3420_MAX_EPS; i++) { + struct max3420_ep *ep = &udc->ep[i]; + + INIT_LIST_HEAD(&ep->queue); + + ep->id = i; + ep->udc = udc; + ep->ep_usb.ops = &max3420_ep_ops; + ep->ep_usb.name = ep->name; + ep->ep_usb.maxpacket = EP_MAX_PACKET; + + if (i == 0) { + ep->ep_usb.desc = &ep0_desc; + snprintf(ep->name, EPNAME_SIZE, "ep0"); + continue; + } + + list_add_tail(&ep->ep_usb.ep_list, &udc->gadget.ep_list); + + if (i == 1) + snprintf(ep->name, EPNAME_SIZE, "ep1out-bulk"); + else + snprintf(ep->name, EPNAME_SIZE, "ep%din-bulk", i); + }; +} + +static void max3420_setup_spi(struct max3420_udc *udc) +{ + u8 reg[8]; + + spi_claim_bus(udc->slave); + spi_rd_buf(udc, MAX3420_REG_EPIRQ, reg, 8); + /* configure SPI */ + spi_wr8(udc, MAX3420_REG_PINCTL, bFDUPSPI); +} + +int dm_usb_gadget_handle_interrupts(struct udevice *dev) +{ + struct max3420_udc *udc = dev_get_priv(dev); + + return max3420_irq(udc); +} + +static int max3420_udc_probe(struct udevice *dev) +{ + struct max3420_udc *udc = dev_get_priv(dev); + struct dm_spi_slave_plat *slave_pdata; + struct udevice *bus = dev->parent; + int busnum = dev_seq(bus); + unsigned int cs; + uint speed, mode; + struct udevice *spid; + + slave_pdata = dev_get_parent_plat(dev); + cs = slave_pdata->cs; + speed = slave_pdata->max_hz; + mode = slave_pdata->mode; + spi_get_bus_and_cs(busnum, cs, speed, mode, "spi_generic_drv", + NULL, &spid, &udc->slave); + + udc->dev = dev; + udc->gadget.ep0 = &udc->ep[0].ep_usb; + udc->gadget.max_speed = USB_SPEED_FULL; + udc->gadget.speed = USB_SPEED_FULL; + udc->gadget.is_dualspeed = 0; + udc->gadget.ops = &max3420_udc_ops; + udc->gadget.name = "max3420-udc"; + + max3420_setup_eps(udc); + max3420_setup_spi(udc); + + usb_add_gadget_udc((struct device *)dev, &udc->gadget); + + return 0; +} + +static int max3420_udc_remove(struct udevice *dev) +{ + struct max3420_udc *udc = dev_get_priv(dev); + + usb_del_gadget_udc(&udc->gadget); + + spi_release_bus(udc->slave); + + return 0; +} + +static const struct udevice_id max3420_ids[] = { + { .compatible = "maxim,max3421-udc" }, + { } +}; + +U_BOOT_DRIVER(max3420_generic_udc) = { + .name = "max3420-udc", + .id = UCLASS_USB_GADGET_GENERIC, + .of_match = max3420_ids, + .probe = max3420_udc_probe, + .remove = max3420_udc_remove, + .priv_auto = sizeof(struct max3420_udc), +}; diff --git a/roms/u-boot/drivers/usb/gadget/ndis.h b/roms/u-boot/drivers/usb/gadget/ndis.h new file mode 100644 index 000000000..753838f79 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/ndis.h @@ -0,0 +1,217 @@ +/* + * ndis.h + * + * ntddndis.h modified by Benedikt Spranger <b.spranger@pengutronix.de> + * + * Thanks to the cygwin development team, + * espacially to Casper S. Hornstrup <chorns@users.sourceforge.net> + * + * THIS SOFTWARE IS NOT COPYRIGHTED + * + * This source code is offered for use in the public domain. You may + * use, modify or distribute it freely. + * + * This code is distributed in the hope that it will be useful but + * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY + * DISCLAIMED. This includes but is not limited to warranties of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +#ifndef _USBGADGET_NDIS_H +#define _USBGADGET_NDIS_H + + +#define NDIS_STATUS_MULTICAST_FULL 0xC0010009 +#define NDIS_STATUS_MULTICAST_EXISTS 0xC001000A +#define NDIS_STATUS_MULTICAST_NOT_FOUND 0xC001000B + +enum NDIS_DEVICE_POWER_STATE { + NdisDeviceStateUnspecified = 0, + NdisDeviceStateD0, + NdisDeviceStateD1, + NdisDeviceStateD2, + NdisDeviceStateD3, + NdisDeviceStateMaximum +}; + +struct NDIS_PM_WAKE_UP_CAPABILITIES { + enum NDIS_DEVICE_POWER_STATE MinMagicPacketWakeUp; + enum NDIS_DEVICE_POWER_STATE MinPatternWakeUp; + enum NDIS_DEVICE_POWER_STATE MinLinkChangeWakeUp; +}; + +/* NDIS_PNP_CAPABILITIES.Flags constants */ +#define NDIS_DEVICE_WAKE_UP_ENABLE 0x00000001 +#define NDIS_DEVICE_WAKE_ON_PATTERN_MATCH_ENABLE 0x00000002 +#define NDIS_DEVICE_WAKE_ON_MAGIC_PACKET_ENABLE 0x00000004 + +struct NDIS_PNP_CAPABILITIES { + __le32 Flags; + struct NDIS_PM_WAKE_UP_CAPABILITIES WakeUpCapabilities; +}; + +struct NDIS_PM_PACKET_PATTERN { + __le32 Priority; + __le32 Reserved; + __le32 MaskSize; + __le32 PatternOffset; + __le32 PatternSize; + __le32 PatternFlags; +}; + + +/* Required Object IDs (OIDs) */ +#define OID_GEN_SUPPORTED_LIST 0x00010101 +#define OID_GEN_HARDWARE_STATUS 0x00010102 +#define OID_GEN_MEDIA_SUPPORTED 0x00010103 +#define OID_GEN_MEDIA_IN_USE 0x00010104 +#define OID_GEN_MAXIMUM_LOOKAHEAD 0x00010105 +#define OID_GEN_MAXIMUM_FRAME_SIZE 0x00010106 +#define OID_GEN_LINK_SPEED 0x00010107 +#define OID_GEN_TRANSMIT_BUFFER_SPACE 0x00010108 +#define OID_GEN_RECEIVE_BUFFER_SPACE 0x00010109 +#define OID_GEN_TRANSMIT_BLOCK_SIZE 0x0001010A +#define OID_GEN_RECEIVE_BLOCK_SIZE 0x0001010B +#define OID_GEN_VENDOR_ID 0x0001010C +#define OID_GEN_VENDOR_DESCRIPTION 0x0001010D +#define OID_GEN_CURRENT_PACKET_FILTER 0x0001010E +#define OID_GEN_CURRENT_LOOKAHEAD 0x0001010F +#define OID_GEN_DRIVER_VERSION 0x00010110 +#define OID_GEN_MAXIMUM_TOTAL_SIZE 0x00010111 +#define OID_GEN_PROTOCOL_OPTIONS 0x00010112 +#define OID_GEN_MAC_OPTIONS 0x00010113 +#define OID_GEN_MEDIA_CONNECT_STATUS 0x00010114 +#define OID_GEN_MAXIMUM_SEND_PACKETS 0x00010115 +#define OID_GEN_VENDOR_DRIVER_VERSION 0x00010116 +#define OID_GEN_SUPPORTED_GUIDS 0x00010117 +#define OID_GEN_NETWORK_LAYER_ADDRESSES 0x00010118 +#define OID_GEN_TRANSPORT_HEADER_OFFSET 0x00010119 +#define OID_GEN_MACHINE_NAME 0x0001021A +#define OID_GEN_RNDIS_CONFIG_PARAMETER 0x0001021B +#define OID_GEN_VLAN_ID 0x0001021C + +/* Optional OIDs */ +#define OID_GEN_MEDIA_CAPABILITIES 0x00010201 +#define OID_GEN_PHYSICAL_MEDIUM 0x00010202 + +/* Required statistics OIDs */ +#define OID_GEN_XMIT_OK 0x00020101 +#define OID_GEN_RCV_OK 0x00020102 +#define OID_GEN_XMIT_ERROR 0x00020103 +#define OID_GEN_RCV_ERROR 0x00020104 +#define OID_GEN_RCV_NO_BUFFER 0x00020105 + +/* Optional statistics OIDs */ +#define OID_GEN_DIRECTED_BYTES_XMIT 0x00020201 +#define OID_GEN_DIRECTED_FRAMES_XMIT 0x00020202 +#define OID_GEN_MULTICAST_BYTES_XMIT 0x00020203 +#define OID_GEN_MULTICAST_FRAMES_XMIT 0x00020204 +#define OID_GEN_BROADCAST_BYTES_XMIT 0x00020205 +#define OID_GEN_BROADCAST_FRAMES_XMIT 0x00020206 +#define OID_GEN_DIRECTED_BYTES_RCV 0x00020207 +#define OID_GEN_DIRECTED_FRAMES_RCV 0x00020208 +#define OID_GEN_MULTICAST_BYTES_RCV 0x00020209 +#define OID_GEN_MULTICAST_FRAMES_RCV 0x0002020A +#define OID_GEN_BROADCAST_BYTES_RCV 0x0002020B +#define OID_GEN_BROADCAST_FRAMES_RCV 0x0002020C +#define OID_GEN_RCV_CRC_ERROR 0x0002020D +#define OID_GEN_TRANSMIT_QUEUE_LENGTH 0x0002020E +#define OID_GEN_GET_TIME_CAPS 0x0002020F +#define OID_GEN_GET_NETCARD_TIME 0x00020210 +#define OID_GEN_NETCARD_LOAD 0x00020211 +#define OID_GEN_DEVICE_PROFILE 0x00020212 +#define OID_GEN_INIT_TIME_MS 0x00020213 +#define OID_GEN_RESET_COUNTS 0x00020214 +#define OID_GEN_MEDIA_SENSE_COUNTS 0x00020215 +#define OID_GEN_FRIENDLY_NAME 0x00020216 +#define OID_GEN_MINIPORT_INFO 0x00020217 +#define OID_GEN_RESET_VERIFY_PARAMETERS 0x00020218 + +/* IEEE 802.3 (Ethernet) OIDs */ +#define NDIS_802_3_MAC_OPTION_PRIORITY 0x00000001 + +#define OID_802_3_PERMANENT_ADDRESS 0x01010101 +#define OID_802_3_CURRENT_ADDRESS 0x01010102 +#define OID_802_3_MULTICAST_LIST 0x01010103 +#define OID_802_3_MAXIMUM_LIST_SIZE 0x01010104 +#define OID_802_3_MAC_OPTIONS 0x01010105 +#define OID_802_3_RCV_ERROR_ALIGNMENT 0x01020101 +#define OID_802_3_XMIT_ONE_COLLISION 0x01020102 +#define OID_802_3_XMIT_MORE_COLLISIONS 0x01020103 +#define OID_802_3_XMIT_DEFERRED 0x01020201 +#define OID_802_3_XMIT_MAX_COLLISIONS 0x01020202 +#define OID_802_3_RCV_OVERRUN 0x01020203 +#define OID_802_3_XMIT_UNDERRUN 0x01020204 +#define OID_802_3_XMIT_HEARTBEAT_FAILURE 0x01020205 +#define OID_802_3_XMIT_TIMES_CRS_LOST 0x01020206 +#define OID_802_3_XMIT_LATE_COLLISIONS 0x01020207 + +/* OID_GEN_MINIPORT_INFO constants */ +#define NDIS_MINIPORT_BUS_MASTER 0x00000001 +#define NDIS_MINIPORT_WDM_DRIVER 0x00000002 +#define NDIS_MINIPORT_SG_LIST 0x00000004 +#define NDIS_MINIPORT_SUPPORTS_MEDIA_QUERY 0x00000008 +#define NDIS_MINIPORT_INDICATES_PACKETS 0x00000010 +#define NDIS_MINIPORT_IGNORE_PACKET_QUEUE 0x00000020 +#define NDIS_MINIPORT_IGNORE_REQUEST_QUEUE 0x00000040 +#define NDIS_MINIPORT_IGNORE_TOKEN_RING_ERRORS 0x00000080 +#define NDIS_MINIPORT_INTERMEDIATE_DRIVER 0x00000100 +#define NDIS_MINIPORT_IS_NDIS_5 0x00000200 +#define NDIS_MINIPORT_IS_CO 0x00000400 +#define NDIS_MINIPORT_DESERIALIZE 0x00000800 +#define NDIS_MINIPORT_REQUIRES_MEDIA_POLLING 0x00001000 +#define NDIS_MINIPORT_SUPPORTS_MEDIA_SENSE 0x00002000 +#define NDIS_MINIPORT_NETBOOT_CARD 0x00004000 +#define NDIS_MINIPORT_PM_SUPPORTED 0x00008000 +#define NDIS_MINIPORT_SUPPORTS_MAC_ADDRESS_OVERWRITE 0x00010000 +#define NDIS_MINIPORT_USES_SAFE_BUFFER_APIS 0x00020000 +#define NDIS_MINIPORT_HIDDEN 0x00040000 +#define NDIS_MINIPORT_SWENUM 0x00080000 +#define NDIS_MINIPORT_SURPRISE_REMOVE_OK 0x00100000 +#define NDIS_MINIPORT_NO_HALT_ON_SUSPEND 0x00200000 +#define NDIS_MINIPORT_HARDWARE_DEVICE 0x00400000 +#define NDIS_MINIPORT_SUPPORTS_CANCEL_SEND_PACKETS 0x00800000 +#define NDIS_MINIPORT_64BITS_DMA 0x01000000 + +#define NDIS_MEDIUM_802_3 0x00000000 +#define NDIS_MEDIUM_802_5 0x00000001 +#define NDIS_MEDIUM_FDDI 0x00000002 +#define NDIS_MEDIUM_WAN 0x00000003 +#define NDIS_MEDIUM_LOCAL_TALK 0x00000004 +#define NDIS_MEDIUM_DIX 0x00000005 +#define NDIS_MEDIUM_ARCENT_RAW 0x00000006 +#define NDIS_MEDIUM_ARCENT_878_2 0x00000007 +#define NDIS_MEDIUM_ATM 0x00000008 +#define NDIS_MEDIUM_WIRELESS_LAN 0x00000009 +#define NDIS_MEDIUM_IRDA 0x0000000A +#define NDIS_MEDIUM_BPC 0x0000000B +#define NDIS_MEDIUM_CO_WAN 0x0000000C +#define NDIS_MEDIUM_1394 0x0000000D + +#define NDIS_PACKET_TYPE_DIRECTED 0x00000001 +#define NDIS_PACKET_TYPE_MULTICAST 0x00000002 +#define NDIS_PACKET_TYPE_ALL_MULTICAST 0x00000004 +#define NDIS_PACKET_TYPE_BROADCAST 0x00000008 +#define NDIS_PACKET_TYPE_SOURCE_ROUTING 0x00000010 +#define NDIS_PACKET_TYPE_PROMISCUOUS 0x00000020 +#define NDIS_PACKET_TYPE_SMT 0x00000040 +#define NDIS_PACKET_TYPE_ALL_LOCAL 0x00000080 +#define NDIS_PACKET_TYPE_GROUP 0x00000100 +#define NDIS_PACKET_TYPE_ALL_FUNCTIONAL 0x00000200 +#define NDIS_PACKET_TYPE_FUNCTIONAL 0x00000400 +#define NDIS_PACKET_TYPE_MAC_FRAME 0x00000800 + +#define NDIS_MEDIA_STATE_CONNECTED 0x00000000 +#define NDIS_MEDIA_STATE_DISCONNECTED 0x00000001 + +#define NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA 0x00000001 +#define NDIS_MAC_OPTION_RECEIVE_SERIALIZED 0x00000002 +#define NDIS_MAC_OPTION_TRANSFERS_NOT_PEND 0x00000004 +#define NDIS_MAC_OPTION_NO_LOOPBACK 0x00000008 +#define NDIS_MAC_OPTION_FULL_DUPLEX 0x00000010 +#define NDIS_MAC_OPTION_EOTX_INDICATION 0x00000020 +#define NDIS_MAC_OPTION_8021P_PRIORITY 0x00000040 +#define NDIS_MAC_OPTION_RESERVED 0x80000000 + +#endif /* _USBGADGET_NDIS_H */ diff --git a/roms/u-boot/drivers/usb/gadget/pxa25x_udc.c b/roms/u-boot/drivers/usb/gadget/pxa25x_udc.c new file mode 100644 index 000000000..d19ac1d03 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/pxa25x_udc.c @@ -0,0 +1,2049 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Intel PXA25x and IXP4xx on-chip full speed USB device controllers + * + * Copyright (C) 2002 Intrinsyc, Inc. (Frank Becker) + * Copyright (C) 2003 Robert Schwebel, Pengutronix + * Copyright (C) 2003 Benedikt Spranger, Pengutronix + * Copyright (C) 2003 David Brownell + * Copyright (C) 2003 Joshua Wise + * Copyright (C) 2012 Lukasz Dalek <luk0104@gmail.com> + * + * MODULE_AUTHOR("Frank Becker, Robert Schwebel, David Brownell"); + */ + +#define CONFIG_USB_PXA25X_SMALL +#define DRIVER_NAME "pxa25x_udc_linux" +#define ARCH_HAS_PREFETCH + +#include <common.h> +#include <errno.h> +#include <log.h> +#include <asm/byteorder.h> +#include <asm/system.h> +#include <asm/mach-types.h> +#include <asm/unaligned.h> +#include <dm/devres.h> +#include <linux/bug.h> +#include <linux/compat.h> +#include <malloc.h> +#include <asm/io.h> +#include <asm/arch/pxa.h> +#include <linux/delay.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <asm/arch/pxa-regs.h> + +#include "pxa25x_udc.h" + +/* + * This driver handles the USB Device Controller (UDC) in Intel's PXA 25x + * series processors. The UDC for the IXP 4xx series is very similar. + * There are fifteen endpoints, in addition to ep0. + * + * Such controller drivers work with a gadget driver. The gadget driver + * returns descriptors, implements configuration and data protocols used + * by the host to interact with this device, and allocates endpoints to + * the different protocol interfaces. The controller driver virtualizes + * usb hardware so that the gadget drivers will be more portable. + * + * This UDC hardware wants to implement a bit too much USB protocol, so + * it constrains the sorts of USB configuration change events that work. + * The errata for these chips are misleading; some "fixed" bugs from + * pxa250 a0/a1 b0/b1/b2 sure act like they're still there. + * + * Note that the UDC hardware supports DMA (except on IXP) but that's + * not used here. IN-DMA (to host) is simple enough, when the data is + * suitably aligned (16 bytes) ... the network stack doesn't do that, + * other software can. OUT-DMA is buggy in most chip versions, as well + * as poorly designed (data toggle not automatic). So this driver won't + * bother using DMA. (Mostly-working IN-DMA support was available in + * kernels before 2.6.23, but was never enabled or well tested.) + */ + +#define DRIVER_VERSION "18-August-2012" +#define DRIVER_DESC "PXA 25x USB Device Controller driver" + +static const char driver_name[] = "pxa25x_udc"; +static const char ep0name[] = "ep0"; + +/* Watchdog */ +static inline void start_watchdog(struct pxa25x_udc *udc) +{ + debug("Started watchdog\n"); + udc->watchdog.base = get_timer(0); + udc->watchdog.running = 1; +} + +static inline void stop_watchdog(struct pxa25x_udc *udc) +{ + udc->watchdog.running = 0; + debug("Stopped watchdog\n"); +} + +static inline void test_watchdog(struct pxa25x_udc *udc) +{ + if (!udc->watchdog.running) + return; + + debug("watchdog %ld %ld\n", get_timer(udc->watchdog.base), + udc->watchdog.period); + + if (get_timer(udc->watchdog.base) >= udc->watchdog.period) { + stop_watchdog(udc); + udc->watchdog.function(udc); + } +} + +static void udc_watchdog(struct pxa25x_udc *dev) +{ + uint32_t udccs0 = readl(&dev->regs->udccs[0]); + + debug("Fired up udc_watchdog\n"); + + local_irq_disable(); + if (dev->ep0state == EP0_STALL + && (udccs0 & UDCCS0_FST) == 0 + && (udccs0 & UDCCS0_SST) == 0) { + writel(UDCCS0_FST|UDCCS0_FTF, &dev->regs->udccs[0]); + debug("ep0 re-stall\n"); + start_watchdog(dev); + } + local_irq_enable(); +} + +#ifdef DEBUG + +static const char * const state_name[] = { + "EP0_IDLE", + "EP0_IN_DATA_PHASE", "EP0_OUT_DATA_PHASE", + "EP0_END_XFER", "EP0_STALL" +}; + +static void +dump_udccr(const char *label) +{ + u32 udccr = readl(&UDC_REGS->udccr); + debug("%s %02X =%s%s%s%s%s%s%s%s\n", + label, udccr, + (udccr & UDCCR_REM) ? " rem" : "", + (udccr & UDCCR_RSTIR) ? " rstir" : "", + (udccr & UDCCR_SRM) ? " srm" : "", + (udccr & UDCCR_SUSIR) ? " susir" : "", + (udccr & UDCCR_RESIR) ? " resir" : "", + (udccr & UDCCR_RSM) ? " rsm" : "", + (udccr & UDCCR_UDA) ? " uda" : "", + (udccr & UDCCR_UDE) ? " ude" : ""); +} + +static void +dump_udccs0(const char *label) +{ + u32 udccs0 = readl(&UDC_REGS->udccs[0]); + + debug("%s %s %02X =%s%s%s%s%s%s%s%s\n", + label, state_name[the_controller->ep0state], udccs0, + (udccs0 & UDCCS0_SA) ? " sa" : "", + (udccs0 & UDCCS0_RNE) ? " rne" : "", + (udccs0 & UDCCS0_FST) ? " fst" : "", + (udccs0 & UDCCS0_SST) ? " sst" : "", + (udccs0 & UDCCS0_DRWF) ? " dwrf" : "", + (udccs0 & UDCCS0_FTF) ? " ftf" : "", + (udccs0 & UDCCS0_IPR) ? " ipr" : "", + (udccs0 & UDCCS0_OPR) ? " opr" : ""); +} + +static void +dump_state(struct pxa25x_udc *dev) +{ + u32 tmp; + unsigned i; + + debug("%s, uicr %02X.%02X, usir %02X.%02x, ufnr %02X.%02X\n", + state_name[dev->ep0state], + readl(&UDC_REGS->uicr1), readl(&UDC_REGS->uicr0), + readl(&UDC_REGS->usir1), readl(&UDC_REGS->usir0), + readl(&UDC_REGS->ufnrh), readl(&UDC_REGS->ufnrl)); + dump_udccr("udccr"); + if (dev->has_cfr) { + tmp = readl(&UDC_REGS->udccfr); + debug("udccfr %02X =%s%s\n", tmp, + (tmp & UDCCFR_AREN) ? " aren" : "", + (tmp & UDCCFR_ACM) ? " acm" : ""); + } + + if (!dev->driver) { + debug("no gadget driver bound\n"); + return; + } else + debug("ep0 driver '%s'\n", "ether"); + + dump_udccs0("udccs0"); + debug("ep0 IN %lu/%lu, OUT %lu/%lu\n", + dev->stats.write.bytes, dev->stats.write.ops, + dev->stats.read.bytes, dev->stats.read.ops); + + for (i = 1; i < PXA_UDC_NUM_ENDPOINTS; i++) { + if (dev->ep[i].desc == NULL) + continue; + debug("udccs%d = %02x\n", i, *dev->ep->reg_udccs); + } +} + +#else /* DEBUG */ + +static inline void dump_udccr(const char *label) { } +static inline void dump_udccs0(const char *label) { } +static inline void dump_state(struct pxa25x_udc *dev) { } + +#endif /* DEBUG */ + +/* + * --------------------------------------------------------------------------- + * endpoint related parts of the api to the usb controller hardware, + * used by gadget driver; and the inner talker-to-hardware core. + * --------------------------------------------------------------------------- + */ + +static void pxa25x_ep_fifo_flush(struct usb_ep *ep); +static void nuke(struct pxa25x_ep *, int status); + +/* one GPIO should control a D+ pullup, so host sees this device (or not) */ +static void pullup_off(void) +{ + struct pxa2xx_udc_mach_info *mach = the_controller->mach; + + if (mach->udc_command) + mach->udc_command(PXA2XX_UDC_CMD_DISCONNECT); +} + +static void pullup_on(void) +{ + struct pxa2xx_udc_mach_info *mach = the_controller->mach; + + if (mach->udc_command) + mach->udc_command(PXA2XX_UDC_CMD_CONNECT); +} + +static void pio_irq_enable(int bEndpointAddress) +{ + bEndpointAddress &= 0xf; + if (bEndpointAddress < 8) { + clrbits_le32(&the_controller->regs->uicr0, + 1 << bEndpointAddress); + } else { + bEndpointAddress -= 8; + clrbits_le32(&the_controller->regs->uicr1, + 1 << bEndpointAddress); + } +} + +static void pio_irq_disable(int bEndpointAddress) +{ + bEndpointAddress &= 0xf; + if (bEndpointAddress < 8) { + setbits_le32(&the_controller->regs->uicr0, + 1 << bEndpointAddress); + } else { + bEndpointAddress -= 8; + setbits_le32(&the_controller->regs->uicr1, + 1 << bEndpointAddress); + } +} + +static inline void udc_set_mask_UDCCR(int mask) +{ + /* + * The UDCCR reg contains mask and interrupt status bits, + * so using '|=' isn't safe as it may ack an interrupt. + */ + const uint32_t mask_bits = UDCCR_REM | UDCCR_SRM | UDCCR_UDE; + + mask &= mask_bits; + clrsetbits_le32(&the_controller->regs->udccr, ~mask_bits, mask); +} + +static inline void udc_clear_mask_UDCCR(int mask) +{ + const uint32_t mask_bits = UDCCR_REM | UDCCR_SRM | UDCCR_UDE; + + mask = ~mask & mask_bits; + clrbits_le32(&the_controller->regs->udccr, ~mask); +} + +static inline void udc_ack_int_UDCCR(int mask) +{ + const uint32_t mask_bits = UDCCR_REM | UDCCR_SRM | UDCCR_UDE; + + mask &= ~mask_bits; + clrsetbits_le32(&the_controller->regs->udccr, ~mask_bits, mask); +} + +/* + * endpoint enable/disable + * + * we need to verify the descriptors used to enable endpoints. since pxa25x + * endpoint configurations are fixed, and are pretty much always enabled, + * there's not a lot to manage here. + * + * because pxa25x can't selectively initialize bulk (or interrupt) endpoints, + * (resetting endpoint halt and toggle), SET_INTERFACE is unusable except + * for a single interface (with only the default altsetting) and for gadget + * drivers that don't halt endpoints (not reset by set_interface). that also + * means that if you use ISO, you must violate the USB spec rule that all + * iso endpoints must be in non-default altsettings. + */ +static int pxa25x_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct pxa25x_ep *ep; + struct pxa25x_udc *dev; + + ep = container_of(_ep, struct pxa25x_ep, ep); + if (!_ep || !desc || ep->desc || _ep->name == ep0name + || desc->bDescriptorType != USB_DT_ENDPOINT + || ep->bEndpointAddress != desc->bEndpointAddress + || ep->fifo_size < + le16_to_cpu(get_unaligned(&desc->wMaxPacketSize))) { + printf("%s, bad ep or descriptor\n", __func__); + return -EINVAL; + } + + /* xfer types must match, except that interrupt ~= bulk */ + if (ep->bmAttributes != desc->bmAttributes + && ep->bmAttributes != USB_ENDPOINT_XFER_BULK + && desc->bmAttributes != USB_ENDPOINT_XFER_INT) { + printf("%s, %s type mismatch\n", __func__, _ep->name); + return -EINVAL; + } + + /* hardware _could_ do smaller, but driver doesn't */ + if ((desc->bmAttributes == USB_ENDPOINT_XFER_BULK + && le16_to_cpu(get_unaligned(&desc->wMaxPacketSize)) + != BULK_FIFO_SIZE) + || !get_unaligned(&desc->wMaxPacketSize)) { + printf("%s, bad %s maxpacket\n", __func__, _ep->name); + return -ERANGE; + } + + dev = ep->dev; + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) { + printf("%s, bogus device state\n", __func__); + return -ESHUTDOWN; + } + + ep->desc = desc; + ep->stopped = 0; + ep->pio_irqs = 0; + ep->ep.maxpacket = le16_to_cpu(get_unaligned(&desc->wMaxPacketSize)); + + /* flush fifo (mostly for OUT buffers) */ + pxa25x_ep_fifo_flush(_ep); + + /* ... reset halt state too, if we could ... */ + + debug("enabled %s\n", _ep->name); + return 0; +} + +static int pxa25x_ep_disable(struct usb_ep *_ep) +{ + struct pxa25x_ep *ep; + unsigned long flags; + + ep = container_of(_ep, struct pxa25x_ep, ep); + if (!_ep || !ep->desc) { + printf("%s, %s not enabled\n", __func__, + _ep ? ep->ep.name : NULL); + return -EINVAL; + } + local_irq_save(flags); + + nuke(ep, -ESHUTDOWN); + + /* flush fifo (mostly for IN buffers) */ + pxa25x_ep_fifo_flush(_ep); + + ep->desc = NULL; + ep->stopped = 1; + + local_irq_restore(flags); + debug("%s disabled\n", _ep->name); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* + * for the pxa25x, these can just wrap kmalloc/kfree. gadget drivers + * must still pass correctly initialized endpoints, since other controller + * drivers may care about how it's currently set up (dma issues etc). + */ + +/* + * pxa25x_ep_alloc_request - allocate a request data structure + */ +static struct usb_request * +pxa25x_ep_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct pxa25x_request *req; + + req = kzalloc(sizeof(*req), gfp_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + return &req->req; +} + + +/* + * pxa25x_ep_free_request - deallocate a request data structure + */ +static void +pxa25x_ep_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct pxa25x_request *req; + + req = container_of(_req, struct pxa25x_request, req); + WARN_ON(!list_empty(&req->queue)); + kfree(req); +} + +/*-------------------------------------------------------------------------*/ + +/* + * done - retire a request; caller blocked irqs + */ +static void done(struct pxa25x_ep *ep, struct pxa25x_request *req, int status) +{ + unsigned stopped = ep->stopped; + + list_del_init(&req->queue); + + if (likely(req->req.status == -EINPROGRESS)) + req->req.status = status; + else + status = req->req.status; + + if (status && status != -ESHUTDOWN) + debug("complete %s req %p stat %d len %u/%u\n", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + + /* don't modify queue heads during completion callback */ + ep->stopped = 1; + req->req.complete(&ep->ep, &req->req); + ep->stopped = stopped; +} + + +static inline void ep0_idle(struct pxa25x_udc *dev) +{ + dev->ep0state = EP0_IDLE; +} + +static int +write_packet(u32 *uddr, struct pxa25x_request *req, unsigned max) +{ + u8 *buf; + unsigned length, count; + + debug("%s(): uddr %p\n", __func__, uddr); + + buf = req->req.buf + req->req.actual; + prefetch(buf); + + /* how big will this packet be? */ + length = min(req->req.length - req->req.actual, max); + req->req.actual += length; + + count = length; + while (likely(count--)) + writeb(*buf++, uddr); + + return length; +} + +/* + * write to an IN endpoint fifo, as many packets as possible. + * irqs will use this to write the rest later. + * caller guarantees at least one packet buffer is ready (or a zlp). + */ +static int +write_fifo(struct pxa25x_ep *ep, struct pxa25x_request *req) +{ + unsigned max; + + max = le16_to_cpu(get_unaligned(&ep->desc->wMaxPacketSize)); + do { + unsigned count; + int is_last, is_short; + + count = write_packet(ep->reg_uddr, req, max); + + /* last packet is usually short (or a zlp) */ + if (unlikely(count != max)) + is_last = is_short = 1; + else { + if (likely(req->req.length != req->req.actual) + || req->req.zero) + is_last = 0; + else + is_last = 1; + /* interrupt/iso maxpacket may not fill the fifo */ + is_short = unlikely(max < ep->fifo_size); + } + + debug_cond(NOISY, "wrote %s %d bytes%s%s %d left %p\n", + ep->ep.name, count, + is_last ? "/L" : "", is_short ? "/S" : "", + req->req.length - req->req.actual, req); + + /* + * let loose that packet. maybe try writing another one, + * double buffering might work. TSP, TPC, and TFS + * bit values are the same for all normal IN endpoints. + */ + writel(UDCCS_BI_TPC, ep->reg_udccs); + if (is_short) + writel(UDCCS_BI_TSP, ep->reg_udccs); + + /* requests complete when all IN data is in the FIFO */ + if (is_last) { + done(ep, req, 0); + if (list_empty(&ep->queue)) + pio_irq_disable(ep->bEndpointAddress); + return 1; + } + + /* + * TODO experiment: how robust can fifo mode tweaking be? + * double buffering is off in the default fifo mode, which + * prevents TFS from being set here. + */ + + } while (readl(ep->reg_udccs) & UDCCS_BI_TFS); + return 0; +} + +/* + * caller asserts req->pending (ep0 irq status nyet cleared); starts + * ep0 data stage. these chips want very simple state transitions. + */ +static inline +void ep0start(struct pxa25x_udc *dev, u32 flags, const char *tag) +{ + writel(flags|UDCCS0_SA|UDCCS0_OPR, &dev->regs->udccs[0]); + writel(USIR0_IR0, &dev->regs->usir0); + dev->req_pending = 0; + debug_cond(NOISY, "%s() %s, udccs0: %02x/%02x usir: %X.%X\n", + __func__, tag, readl(&dev->regs->udccs[0]), flags, + readl(&dev->regs->usir1), readl(&dev->regs->usir0)); +} + +static int +write_ep0_fifo(struct pxa25x_ep *ep, struct pxa25x_request *req) +{ + unsigned count; + int is_short; + + count = write_packet(&ep->dev->regs->uddr0, req, EP0_FIFO_SIZE); + ep->dev->stats.write.bytes += count; + + /* last packet "must be" short (or a zlp) */ + is_short = (count != EP0_FIFO_SIZE); + + debug_cond(NOISY, "ep0in %d bytes %d left %p\n", count, + req->req.length - req->req.actual, req); + + if (unlikely(is_short)) { + if (ep->dev->req_pending) + ep0start(ep->dev, UDCCS0_IPR, "short IN"); + else + writel(UDCCS0_IPR, &ep->dev->regs->udccs[0]); + + count = req->req.length; + done(ep, req, 0); + ep0_idle(ep->dev); + + /* + * This seems to get rid of lost status irqs in some cases: + * host responds quickly, or next request involves config + * change automagic, or should have been hidden, or ... + * + * FIXME get rid of all udelays possible... + */ + if (count >= EP0_FIFO_SIZE) { + count = 100; + do { + if ((readl(&ep->dev->regs->udccs[0]) & + UDCCS0_OPR) != 0) { + /* clear OPR, generate ack */ + writel(UDCCS0_OPR, + &ep->dev->regs->udccs[0]); + break; + } + count--; + udelay(1); + } while (count); + } + } else if (ep->dev->req_pending) + ep0start(ep->dev, 0, "IN"); + + return is_short; +} + + +/* + * read_fifo - unload packet(s) from the fifo we use for usb OUT + * transfers and put them into the request. caller should have made + * sure there's at least one packet ready. + * + * returns true if the request completed because of short packet or the + * request buffer having filled (and maybe overran till end-of-packet). + */ +static int +read_fifo(struct pxa25x_ep *ep, struct pxa25x_request *req) +{ + u32 udccs; + u8 *buf; + unsigned bufferspace, count, is_short; + + for (;;) { + /* + * make sure there's a packet in the FIFO. + * UDCCS_{BO,IO}_RPC are all the same bit value. + * UDCCS_{BO,IO}_RNE are all the same bit value. + */ + udccs = readl(ep->reg_udccs); + if (unlikely((udccs & UDCCS_BO_RPC) == 0)) + break; + buf = req->req.buf + req->req.actual; + prefetchw(buf); + bufferspace = req->req.length - req->req.actual; + + /* read all bytes from this packet */ + if (likely(udccs & UDCCS_BO_RNE)) { + count = 1 + (0x0ff & readl(ep->reg_ubcr)); + req->req.actual += min(count, bufferspace); + } else /* zlp */ + count = 0; + is_short = (count < ep->ep.maxpacket); + debug_cond(NOISY, "read %s %02x, %d bytes%s req %p %d/%d\n", + ep->ep.name, udccs, count, + is_short ? "/S" : "", + req, req->req.actual, req->req.length); + while (likely(count-- != 0)) { + u8 byte = readb(ep->reg_uddr); + + if (unlikely(bufferspace == 0)) { + /* + * this happens when the driver's buffer + * is smaller than what the host sent. + * discard the extra data. + */ + if (req->req.status != -EOVERFLOW) + printf("%s overflow %d\n", + ep->ep.name, count); + req->req.status = -EOVERFLOW; + } else { + *buf++ = byte; + bufferspace--; + } + } + writel(UDCCS_BO_RPC, ep->reg_udccs); + /* RPC/RSP/RNE could now reflect the other packet buffer */ + + /* iso is one request per packet */ + if (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + if (udccs & UDCCS_IO_ROF) + req->req.status = -EHOSTUNREACH; + /* more like "is_done" */ + is_short = 1; + } + + /* completion */ + if (is_short || req->req.actual == req->req.length) { + done(ep, req, 0); + if (list_empty(&ep->queue)) + pio_irq_disable(ep->bEndpointAddress); + return 1; + } + + /* finished that packet. the next one may be waiting... */ + } + return 0; +} + +/* + * special ep0 version of the above. no UBCR0 or double buffering; status + * handshaking is magic. most device protocols don't need control-OUT. + * CDC vendor commands (and RNDIS), mass storage CB/CBI, and some other + * protocols do use them. + */ +static int +read_ep0_fifo(struct pxa25x_ep *ep, struct pxa25x_request *req) +{ + u8 *buf, byte; + unsigned bufferspace; + + buf = req->req.buf + req->req.actual; + bufferspace = req->req.length - req->req.actual; + + while (readl(&ep->dev->regs->udccs[0]) & UDCCS0_RNE) { + byte = (u8)readb(&ep->dev->regs->uddr0); + + if (unlikely(bufferspace == 0)) { + /* + * this happens when the driver's buffer + * is smaller than what the host sent. + * discard the extra data. + */ + if (req->req.status != -EOVERFLOW) + printf("%s overflow\n", ep->ep.name); + req->req.status = -EOVERFLOW; + } else { + *buf++ = byte; + req->req.actual++; + bufferspace--; + } + } + + writel(UDCCS0_OPR | UDCCS0_IPR, &ep->dev->regs->udccs[0]); + + /* completion */ + if (req->req.actual >= req->req.length) + return 1; + + /* finished that packet. the next one may be waiting... */ + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static int +pxa25x_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) +{ + struct pxa25x_request *req; + struct pxa25x_ep *ep; + struct pxa25x_udc *dev; + unsigned long flags; + + req = container_of(_req, struct pxa25x_request, req); + if (unlikely(!_req || !_req->complete || !_req->buf + || !list_empty(&req->queue))) { + printf("%s, bad params\n", __func__); + return -EINVAL; + } + + ep = container_of(_ep, struct pxa25x_ep, ep); + if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) { + printf("%s, bad ep\n", __func__); + return -EINVAL; + } + + dev = ep->dev; + if (unlikely(!dev->driver + || dev->gadget.speed == USB_SPEED_UNKNOWN)) { + printf("%s, bogus device state\n", __func__); + return -ESHUTDOWN; + } + + /* + * iso is always one packet per request, that's the only way + * we can report per-packet status. that also helps with dma. + */ + if (unlikely(ep->bmAttributes == USB_ENDPOINT_XFER_ISOC + && req->req.length > + le16_to_cpu(get_unaligned(&ep->desc->wMaxPacketSize)))) + return -EMSGSIZE; + + debug_cond(NOISY, "%s queue req %p, len %d buf %p\n", + _ep->name, _req, _req->length, _req->buf); + + local_irq_save(flags); + + _req->status = -EINPROGRESS; + _req->actual = 0; + + /* kickstart this i/o queue? */ + if (list_empty(&ep->queue) && !ep->stopped) { + if (ep->desc == NULL/* ep0 */) { + unsigned length = _req->length; + + switch (dev->ep0state) { + case EP0_IN_DATA_PHASE: + dev->stats.write.ops++; + if (write_ep0_fifo(ep, req)) + req = NULL; + break; + + case EP0_OUT_DATA_PHASE: + dev->stats.read.ops++; + /* messy ... */ + if (dev->req_config) { + debug("ep0 config ack%s\n", + dev->has_cfr ? "" : " raced"); + if (dev->has_cfr) + writel(UDCCFR_AREN|UDCCFR_ACM + |UDCCFR_MB1, + &ep->dev->regs->udccfr); + done(ep, req, 0); + dev->ep0state = EP0_END_XFER; + local_irq_restore(flags); + return 0; + } + if (dev->req_pending) + ep0start(dev, UDCCS0_IPR, "OUT"); + if (length == 0 || + ((readl( + &ep->dev->regs->udccs[0]) + & UDCCS0_RNE) != 0 + && read_ep0_fifo(ep, req))) { + ep0_idle(dev); + done(ep, req, 0); + req = NULL; + } + break; + + default: + printf("ep0 i/o, odd state %d\n", + dev->ep0state); + local_irq_restore(flags); + return -EL2HLT; + } + /* can the FIFO can satisfy the request immediately? */ + } else if ((ep->bEndpointAddress & USB_DIR_IN) != 0) { + if ((readl(ep->reg_udccs) & UDCCS_BI_TFS) != 0 + && write_fifo(ep, req)) + req = NULL; + } else if ((readl(ep->reg_udccs) & UDCCS_BO_RFS) != 0 + && read_fifo(ep, req)) { + req = NULL; + } + + if (likely(req && ep->desc)) + pio_irq_enable(ep->bEndpointAddress); + } + + /* pio or dma irq handler advances the queue. */ + if (likely(req != NULL)) + list_add_tail(&req->queue, &ep->queue); + local_irq_restore(flags); + + return 0; +} + + +/* + * nuke - dequeue ALL requests + */ +static void nuke(struct pxa25x_ep *ep, int status) +{ + struct pxa25x_request *req; + + /* called with irqs blocked */ + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, + struct pxa25x_request, + queue); + done(ep, req, status); + } + if (ep->desc) + pio_irq_disable(ep->bEndpointAddress); +} + + +/* dequeue JUST ONE request */ +static int pxa25x_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct pxa25x_ep *ep; + struct pxa25x_request *req; + unsigned long flags; + + ep = container_of(_ep, struct pxa25x_ep, ep); + if (!_ep || ep->ep.name == ep0name) + return -EINVAL; + + local_irq_save(flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) { + local_irq_restore(flags); + return -EINVAL; + } + + done(ep, req, -ECONNRESET); + + local_irq_restore(flags); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static int pxa25x_ep_set_halt(struct usb_ep *_ep, int value) +{ + struct pxa25x_ep *ep; + unsigned long flags; + + ep = container_of(_ep, struct pxa25x_ep, ep); + if (unlikely(!_ep + || (!ep->desc && ep->ep.name != ep0name)) + || ep->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + printf("%s, bad ep\n", __func__); + return -EINVAL; + } + if (value == 0) { + /* + * this path (reset toggle+halt) is needed to implement + * SET_INTERFACE on normal hardware. but it can't be + * done from software on the PXA UDC, and the hardware + * forgets to do it as part of SET_INTERFACE automagic. + */ + printf("only host can clear %s halt\n", _ep->name); + return -EROFS; + } + + local_irq_save(flags); + + if ((ep->bEndpointAddress & USB_DIR_IN) != 0 + && ((readl(ep->reg_udccs) & UDCCS_BI_TFS) == 0 + || !list_empty(&ep->queue))) { + local_irq_restore(flags); + return -EAGAIN; + } + + /* FST bit is the same for control, bulk in, bulk out, interrupt in */ + writel(UDCCS_BI_FST|UDCCS_BI_FTF, ep->reg_udccs); + + /* ep0 needs special care */ + if (!ep->desc) { + start_watchdog(ep->dev); + ep->dev->req_pending = 0; + ep->dev->ep0state = EP0_STALL; + + /* and bulk/intr endpoints like dropping stalls too */ + } else { + unsigned i; + for (i = 0; i < 1000; i += 20) { + if (readl(ep->reg_udccs) & UDCCS_BI_SST) + break; + udelay(20); + } + } + local_irq_restore(flags); + + debug("%s halt\n", _ep->name); + return 0; +} + +static int pxa25x_ep_fifo_status(struct usb_ep *_ep) +{ + struct pxa25x_ep *ep; + + ep = container_of(_ep, struct pxa25x_ep, ep); + if (!_ep) { + printf("%s, bad ep\n", __func__); + return -ENODEV; + } + /* pxa can't report unclaimed bytes from IN fifos */ + if ((ep->bEndpointAddress & USB_DIR_IN) != 0) + return -EOPNOTSUPP; + if (ep->dev->gadget.speed == USB_SPEED_UNKNOWN + || (readl(ep->reg_udccs) & UDCCS_BO_RFS) == 0) + return 0; + else + return (readl(ep->reg_ubcr) & 0xfff) + 1; +} + +static void pxa25x_ep_fifo_flush(struct usb_ep *_ep) +{ + struct pxa25x_ep *ep; + + ep = container_of(_ep, struct pxa25x_ep, ep); + if (!_ep || ep->ep.name == ep0name || !list_empty(&ep->queue)) { + printf("%s, bad ep\n", __func__); + return; + } + + /* toggle and halt bits stay unchanged */ + + /* for OUT, just read and discard the FIFO contents. */ + if ((ep->bEndpointAddress & USB_DIR_IN) == 0) { + while (((readl(ep->reg_udccs)) & UDCCS_BO_RNE) != 0) + (void)readb(ep->reg_uddr); + return; + } + + /* most IN status is the same, but ISO can't stall */ + writel(UDCCS_BI_TPC|UDCCS_BI_FTF|UDCCS_BI_TUR + | (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC + ? 0 : UDCCS_BI_SST), ep->reg_udccs); +} + + +static struct usb_ep_ops pxa25x_ep_ops = { + .enable = pxa25x_ep_enable, + .disable = pxa25x_ep_disable, + + .alloc_request = pxa25x_ep_alloc_request, + .free_request = pxa25x_ep_free_request, + + .queue = pxa25x_ep_queue, + .dequeue = pxa25x_ep_dequeue, + + .set_halt = pxa25x_ep_set_halt, + .fifo_status = pxa25x_ep_fifo_status, + .fifo_flush = pxa25x_ep_fifo_flush, +}; + + +/* --------------------------------------------------------------------------- + * device-scoped parts of the api to the usb controller hardware + * --------------------------------------------------------------------------- + */ + +static int pxa25x_udc_get_frame(struct usb_gadget *_gadget) +{ + return ((readl(&the_controller->regs->ufnrh) & 0x07) << 8) | + (readl(&the_controller->regs->ufnrl) & 0xff); +} + +static int pxa25x_udc_wakeup(struct usb_gadget *_gadget) +{ + /* host may not have enabled remote wakeup */ + if ((readl(&the_controller->regs->udccs[0]) & UDCCS0_DRWF) == 0) + return -EHOSTUNREACH; + udc_set_mask_UDCCR(UDCCR_RSM); + return 0; +} + +static void stop_activity(struct pxa25x_udc *, struct usb_gadget_driver *); +static void udc_enable(struct pxa25x_udc *); +static void udc_disable(struct pxa25x_udc *); + +/* + * We disable the UDC -- and its 48 MHz clock -- whenever it's not + * in active use. + */ +static int pullup(struct pxa25x_udc *udc) +{ + if (udc->pullup) + pullup_on(); + else + pullup_off(); + + + int is_active = udc->pullup; + if (is_active) { + if (!udc->active) { + udc->active = 1; + udc_enable(udc); + } + } else { + if (udc->active) { + if (udc->gadget.speed != USB_SPEED_UNKNOWN) + stop_activity(udc, udc->driver); + udc_disable(udc); + udc->active = 0; + } + + } + return 0; +} + +/* VBUS reporting logically comes from a transceiver */ +static int pxa25x_udc_vbus_session(struct usb_gadget *_gadget, int is_active) +{ + struct pxa25x_udc *udc; + + udc = container_of(_gadget, struct pxa25x_udc, gadget); + printf("vbus %s\n", is_active ? "supplied" : "inactive"); + pullup(udc); + return 0; +} + +/* drivers may have software control over D+ pullup */ +static int pxa25x_udc_pullup(struct usb_gadget *_gadget, int is_active) +{ + struct pxa25x_udc *udc; + + udc = container_of(_gadget, struct pxa25x_udc, gadget); + + /* not all boards support pullup control */ + if (!udc->mach->udc_command) + return -EOPNOTSUPP; + + udc->pullup = (is_active != 0); + pullup(udc); + return 0; +} + +/* + * boards may consume current from VBUS, up to 100-500mA based on config. + * the 500uA suspend ceiling means that exclusively vbus-powered PXA designs + * violate USB specs. + */ +static int pxa25x_udc_vbus_draw(struct usb_gadget *_gadget, unsigned mA) +{ + return -EOPNOTSUPP; +} + +static const struct usb_gadget_ops pxa25x_udc_ops = { + .get_frame = pxa25x_udc_get_frame, + .wakeup = pxa25x_udc_wakeup, + .vbus_session = pxa25x_udc_vbus_session, + .pullup = pxa25x_udc_pullup, + .vbus_draw = pxa25x_udc_vbus_draw, +}; + +/*-------------------------------------------------------------------------*/ + +/* + * udc_disable - disable USB device controller + */ +static void udc_disable(struct pxa25x_udc *dev) +{ + /* block all irqs */ + udc_set_mask_UDCCR(UDCCR_SRM|UDCCR_REM); + writel(0xff, &dev->regs->uicr0); + writel(0xff, &dev->regs->uicr1); + writel(UFNRH_SIM, &dev->regs->ufnrh); + + /* if hardware supports it, disconnect from usb */ + pullup_off(); + + udc_clear_mask_UDCCR(UDCCR_UDE); + + ep0_idle(dev); + dev->gadget.speed = USB_SPEED_UNKNOWN; +} + +/* + * udc_reinit - initialize software state + */ +static void udc_reinit(struct pxa25x_udc *dev) +{ + u32 i; + + /* device/ep0 records init */ + INIT_LIST_HEAD(&dev->gadget.ep_list); + INIT_LIST_HEAD(&dev->gadget.ep0->ep_list); + dev->ep0state = EP0_IDLE; + + /* basic endpoint records init */ + for (i = 0; i < PXA_UDC_NUM_ENDPOINTS; i++) { + struct pxa25x_ep *ep = &dev->ep[i]; + + if (i != 0) + list_add_tail(&ep->ep.ep_list, &dev->gadget.ep_list); + + ep->desc = NULL; + ep->stopped = 0; + INIT_LIST_HEAD(&ep->queue); + ep->pio_irqs = 0; + } + + /* the rest was statically initialized, and is read-only */ +} + +/* + * until it's enabled, this UDC should be completely invisible + * to any USB host. + */ +static void udc_enable(struct pxa25x_udc *dev) +{ + debug("udc: enabling udc\n"); + + udc_clear_mask_UDCCR(UDCCR_UDE); + + /* + * Try to clear these bits before we enable the udc. + * Do not touch reset ack bit, we would take care of it in + * interrupt handle routine + */ + udc_ack_int_UDCCR(UDCCR_SUSIR|UDCCR_RESIR); + + ep0_idle(dev); + dev->gadget.speed = USB_SPEED_UNKNOWN; + dev->stats.irqs = 0; + + /* + * sequence taken from chapter 12.5.10, PXA250 AppProcDevManual: + * - enable UDC + * - if RESET is already in progress, ack interrupt + * - unmask reset interrupt + */ + udc_set_mask_UDCCR(UDCCR_UDE); + if (!(readl(&dev->regs->udccr) & UDCCR_UDA)) + udc_ack_int_UDCCR(UDCCR_RSTIR); + + if (dev->has_cfr /* UDC_RES2 is defined */) { + /* + * pxa255 (a0+) can avoid a set_config race that could + * prevent gadget drivers from configuring correctly + */ + writel(UDCCFR_ACM | UDCCFR_MB1, &dev->regs->udccfr); + } + + /* enable suspend/resume and reset irqs */ + udc_clear_mask_UDCCR(UDCCR_SRM | UDCCR_REM); + + /* enable ep0 irqs */ + clrbits_le32(&dev->regs->uicr0, UICR0_IM0); + + /* if hardware supports it, pullup D+ and wait for reset */ + pullup_on(); +} + +static inline void clear_ep_state(struct pxa25x_udc *dev) +{ + unsigned i; + + /* + * hardware SET_{CONFIGURATION,INTERFACE} automagic resets endpoint + * fifos, and pending transactions mustn't be continued in any case. + */ + for (i = 1; i < PXA_UDC_NUM_ENDPOINTS; i++) + nuke(&dev->ep[i], -ECONNABORTED); +} + +static void handle_ep0(struct pxa25x_udc *dev) +{ + u32 udccs0 = readl(&dev->regs->udccs[0]); + struct pxa25x_ep *ep = &dev->ep[0]; + struct pxa25x_request *req; + union { + struct usb_ctrlrequest r; + u8 raw[8]; + u32 word[2]; + } u; + + if (list_empty(&ep->queue)) + req = NULL; + else + req = list_entry(ep->queue.next, struct pxa25x_request, queue); + + /* clear stall status */ + if (udccs0 & UDCCS0_SST) { + nuke(ep, -EPIPE); + writel(UDCCS0_SST, &dev->regs->udccs[0]); + stop_watchdog(dev); + ep0_idle(dev); + } + + /* previous request unfinished? non-error iff back-to-back ... */ + if ((udccs0 & UDCCS0_SA) != 0 && dev->ep0state != EP0_IDLE) { + nuke(ep, 0); + stop_watchdog(dev); + ep0_idle(dev); + } + + switch (dev->ep0state) { + case EP0_IDLE: + /* late-breaking status? */ + udccs0 = readl(&dev->regs->udccs[0]); + + /* start control request? */ + if (likely((udccs0 & (UDCCS0_OPR|UDCCS0_SA|UDCCS0_RNE)) + == (UDCCS0_OPR|UDCCS0_SA|UDCCS0_RNE))) { + int i; + + nuke(ep, -EPROTO); + + /* read SETUP packet */ + for (i = 0; i < 8; i++) { + if (unlikely(!(readl(&dev->regs->udccs[0]) & + UDCCS0_RNE))) { +bad_setup: + debug("SETUP %d!\n", i); + goto stall; + } + u.raw[i] = (u8)readb(&dev->regs->uddr0); + } + if (unlikely((readl(&dev->regs->udccs[0]) & + UDCCS0_RNE) != 0)) + goto bad_setup; + +got_setup: + debug("SETUP %02x.%02x v%04x i%04x l%04x\n", + u.r.bRequestType, u.r.bRequest, + le16_to_cpu(u.r.wValue), + le16_to_cpu(u.r.wIndex), + le16_to_cpu(u.r.wLength)); + + /* cope with automagic for some standard requests. */ + dev->req_std = (u.r.bRequestType & USB_TYPE_MASK) + == USB_TYPE_STANDARD; + dev->req_config = 0; + dev->req_pending = 1; + switch (u.r.bRequest) { + /* hardware restricts gadget drivers here! */ + case USB_REQ_SET_CONFIGURATION: + debug("GOT SET_CONFIGURATION\n"); + if (u.r.bRequestType == USB_RECIP_DEVICE) { + /* + * reflect hardware's automagic + * up to the gadget driver. + */ +config_change: + dev->req_config = 1; + clear_ep_state(dev); + /* + * if !has_cfr, there's no synch + * else use AREN (later) not SA|OPR + * USIR0_IR0 acts edge sensitive + */ + } + break; + /* ... and here, even more ... */ + case USB_REQ_SET_INTERFACE: + if (u.r.bRequestType == USB_RECIP_INTERFACE) { + /* + * udc hardware is broken by design: + * - altsetting may only be zero; + * - hw resets all interfaces' eps; + * - ep reset doesn't include halt(?). + */ + printf("broken set_interface (%d/%d)\n", + le16_to_cpu(u.r.wIndex), + le16_to_cpu(u.r.wValue)); + goto config_change; + } + break; + /* hardware was supposed to hide this */ + case USB_REQ_SET_ADDRESS: + debug("GOT SET ADDRESS\n"); + if (u.r.bRequestType == USB_RECIP_DEVICE) { + ep0start(dev, 0, "address"); + return; + } + break; + } + + if (u.r.bRequestType & USB_DIR_IN) + dev->ep0state = EP0_IN_DATA_PHASE; + else + dev->ep0state = EP0_OUT_DATA_PHASE; + + i = dev->driver->setup(&dev->gadget, &u.r); + if (i < 0) { + /* hardware automagic preventing STALL... */ + if (dev->req_config) { + /* + * hardware sometimes neglects to tell + * tell us about config change events, + * so later ones may fail... + */ + printf("config change %02x fail %d?\n", + u.r.bRequest, i); + return; + /* + * TODO experiment: if has_cfr, + * hardware didn't ACK; maybe we + * could actually STALL! + */ + } + if (0) { +stall: + /* uninitialized when goto stall */ + i = 0; + } + debug("protocol STALL, " + "%02x err %d\n", + readl(&dev->regs->udccs[0]), i); + + /* + * the watchdog timer helps deal with cases + * where udc seems to clear FST wrongly, and + * then NAKs instead of STALLing. + */ + ep0start(dev, UDCCS0_FST|UDCCS0_FTF, "stall"); + start_watchdog(dev); + dev->ep0state = EP0_STALL; + + /* deferred i/o == no response yet */ + } else if (dev->req_pending) { + if (likely(dev->ep0state == EP0_IN_DATA_PHASE + || dev->req_std || u.r.wLength)) + ep0start(dev, 0, "defer"); + else + ep0start(dev, UDCCS0_IPR, "defer/IPR"); + } + + /* expect at least one data or status stage irq */ + return; + + } else if (likely((udccs0 & (UDCCS0_OPR|UDCCS0_SA)) + == (UDCCS0_OPR|UDCCS0_SA))) { + unsigned i; + + /* + * pxa210/250 erratum 131 for B0/B1 says RNE lies. + * still observed on a pxa255 a0. + */ + debug("e131\n"); + nuke(ep, -EPROTO); + + /* read SETUP data, but don't trust it too much */ + for (i = 0; i < 8; i++) + u.raw[i] = (u8)readb(&dev->regs->uddr0); + if ((u.r.bRequestType & USB_RECIP_MASK) + > USB_RECIP_OTHER) + goto stall; + if (u.word[0] == 0 && u.word[1] == 0) + goto stall; + goto got_setup; + } else { + /* + * some random early IRQ: + * - we acked FST + * - IPR cleared + * - OPR got set, without SA (likely status stage) + */ + debug("random IRQ %X %X\n", udccs0, + readl(&dev->regs->udccs[0])); + writel(udccs0 & (UDCCS0_SA|UDCCS0_OPR), + &dev->regs->udccs[0]); + } + break; + case EP0_IN_DATA_PHASE: /* GET_DESCRIPTOR etc */ + if (udccs0 & UDCCS0_OPR) { + debug("ep0in premature status\n"); + if (req) + done(ep, req, 0); + ep0_idle(dev); + } else /* irq was IPR clearing */ { + if (req) { + debug("next ep0 in packet\n"); + /* this IN packet might finish the request */ + (void) write_ep0_fifo(ep, req); + } /* else IN token before response was written */ + } + break; + case EP0_OUT_DATA_PHASE: /* SET_DESCRIPTOR etc */ + if (udccs0 & UDCCS0_OPR) { + if (req) { + /* this OUT packet might finish the request */ + if (read_ep0_fifo(ep, req)) + done(ep, req, 0); + /* else more OUT packets expected */ + } /* else OUT token before read was issued */ + } else /* irq was IPR clearing */ { + debug("ep0out premature status\n"); + if (req) + done(ep, req, 0); + ep0_idle(dev); + } + break; + case EP0_END_XFER: + if (req) + done(ep, req, 0); + /* + * ack control-IN status (maybe in-zlp was skipped) + * also appears after some config change events. + */ + if (udccs0 & UDCCS0_OPR) + writel(UDCCS0_OPR, &dev->regs->udccs[0]); + ep0_idle(dev); + break; + case EP0_STALL: + writel(UDCCS0_FST, &dev->regs->udccs[0]); + break; + } + + writel(USIR0_IR0, &dev->regs->usir0); +} + +static void handle_ep(struct pxa25x_ep *ep) +{ + struct pxa25x_request *req; + int is_in = ep->bEndpointAddress & USB_DIR_IN; + int completed; + u32 udccs, tmp; + + do { + completed = 0; + if (likely(!list_empty(&ep->queue))) + req = list_entry(ep->queue.next, + struct pxa25x_request, queue); + else + req = NULL; + + /* TODO check FST handling */ + + udccs = readl(ep->reg_udccs); + if (unlikely(is_in)) { /* irq from TPC, SST, or (ISO) TUR */ + tmp = UDCCS_BI_TUR; + if (likely(ep->bmAttributes == USB_ENDPOINT_XFER_BULK)) + tmp |= UDCCS_BI_SST; + tmp &= udccs; + if (likely(tmp)) + writel(tmp, ep->reg_udccs); + if (req && likely((udccs & UDCCS_BI_TFS) != 0)) + completed = write_fifo(ep, req); + + } else { /* irq from RPC (or for ISO, ROF) */ + if (likely(ep->bmAttributes == USB_ENDPOINT_XFER_BULK)) + tmp = UDCCS_BO_SST | UDCCS_BO_DME; + else + tmp = UDCCS_IO_ROF | UDCCS_IO_DME; + tmp &= udccs; + if (likely(tmp)) + writel(tmp, ep->reg_udccs); + + /* fifos can hold packets, ready for reading... */ + if (likely(req)) + completed = read_fifo(ep, req); + else + pio_irq_disable(ep->bEndpointAddress); + } + ep->pio_irqs++; + } while (completed); +} + +/* + * pxa25x_udc_irq - interrupt handler + * + * avoid delays in ep0 processing. the control handshaking isn't always + * under software control (pxa250c0 and the pxa255 are better), and delays + * could cause usb protocol errors. + */ +static struct pxa25x_udc memory; +static int +pxa25x_udc_irq(void) +{ + struct pxa25x_udc *dev = &memory; + int handled; + + test_watchdog(dev); + + dev->stats.irqs++; + do { + u32 udccr = readl(&dev->regs->udccr); + + handled = 0; + + /* SUSpend Interrupt Request */ + if (unlikely(udccr & UDCCR_SUSIR)) { + udc_ack_int_UDCCR(UDCCR_SUSIR); + handled = 1; + debug("USB suspend\n"); + + if (dev->gadget.speed != USB_SPEED_UNKNOWN + && dev->driver + && dev->driver->suspend) + dev->driver->suspend(&dev->gadget); + ep0_idle(dev); + } + + /* RESume Interrupt Request */ + if (unlikely(udccr & UDCCR_RESIR)) { + udc_ack_int_UDCCR(UDCCR_RESIR); + handled = 1; + debug("USB resume\n"); + + if (dev->gadget.speed != USB_SPEED_UNKNOWN + && dev->driver + && dev->driver->resume) + dev->driver->resume(&dev->gadget); + } + + /* ReSeT Interrupt Request - USB reset */ + if (unlikely(udccr & UDCCR_RSTIR)) { + udc_ack_int_UDCCR(UDCCR_RSTIR); + handled = 1; + + if ((readl(&dev->regs->udccr) & UDCCR_UDA) == 0) { + debug("USB reset start\n"); + + /* + * reset driver and endpoints, + * in case that's not yet done + */ + stop_activity(dev, dev->driver); + + } else { + debug("USB reset end\n"); + dev->gadget.speed = USB_SPEED_FULL; + memset(&dev->stats, 0, sizeof dev->stats); + /* driver and endpoints are still reset */ + } + + } else { + u32 uicr0 = readl(&dev->regs->uicr0); + u32 uicr1 = readl(&dev->regs->uicr1); + u32 usir0 = readl(&dev->regs->usir0); + u32 usir1 = readl(&dev->regs->usir1); + + usir0 = usir0 & ~uicr0; + usir1 = usir1 & ~uicr1; + int i; + + if (unlikely(!usir0 && !usir1)) + continue; + + debug_cond(NOISY, "irq %02x.%02x\n", usir1, usir0); + + /* control traffic */ + if (usir0 & USIR0_IR0) { + dev->ep[0].pio_irqs++; + handle_ep0(dev); + handled = 1; + } + + /* endpoint data transfers */ + for (i = 0; i < 8; i++) { + u32 tmp = 1 << i; + + if (i && (usir0 & tmp)) { + handle_ep(&dev->ep[i]); + setbits_le32(&dev->regs->usir0, tmp); + handled = 1; + } +#ifndef CONFIG_USB_PXA25X_SMALL + if (usir1 & tmp) { + handle_ep(&dev->ep[i+8]); + setbits_le32(&dev->regs->usir1, tmp); + handled = 1; + } +#endif + } + } + + /* we could also ask for 1 msec SOF (SIR) interrupts */ + + } while (handled); + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + +/* + * this uses load-time allocation and initialization (instead of + * doing it at run-time) to save code, eliminate fault paths, and + * be more obviously correct. + */ +static struct pxa25x_udc memory = { + .regs = UDC_REGS, + + .gadget = { + .ops = &pxa25x_udc_ops, + .ep0 = &memory.ep[0].ep, + .name = driver_name, + }, + + /* control endpoint */ + .ep[0] = { + .ep = { + .name = ep0name, + .ops = &pxa25x_ep_ops, + .maxpacket = EP0_FIFO_SIZE, + }, + .dev = &memory, + .reg_udccs = &UDC_REGS->udccs[0], + .reg_uddr = &UDC_REGS->uddr0, + }, + + /* first group of endpoints */ + .ep[1] = { + .ep = { + .name = "ep1in-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 1, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .reg_udccs = &UDC_REGS->udccs[1], + .reg_uddr = &UDC_REGS->uddr1, + }, + .ep[2] = { + .ep = { + .name = "ep2out-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = 2, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .reg_udccs = &UDC_REGS->udccs[2], + .reg_ubcr = &UDC_REGS->ubcr2, + .reg_uddr = &UDC_REGS->uddr2, + }, +#ifndef CONFIG_USB_PXA25X_SMALL + .ep[3] = { + .ep = { + .name = "ep3in-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 3, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .reg_udccs = &UDC_REGS->udccs[3], + .reg_uddr = &UDC_REGS->uddr3, + }, + .ep[4] = { + .ep = { + .name = "ep4out-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = 4, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .reg_udccs = &UDC_REGS->udccs[4], + .reg_ubcr = &UDC_REGS->ubcr4, + .reg_uddr = &UDC_REGS->uddr4, + }, + .ep[5] = { + .ep = { + .name = "ep5in-int", + .ops = &pxa25x_ep_ops, + .maxpacket = INT_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = INT_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 5, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .reg_udccs = &UDC_REGS->udccs[5], + .reg_uddr = &UDC_REGS->uddr5, + }, + + /* second group of endpoints */ + .ep[6] = { + .ep = { + .name = "ep6in-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 6, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .reg_udccs = &UDC_REGS->udccs[6], + .reg_uddr = &UDC_REGS->uddr6, + }, + .ep[7] = { + .ep = { + .name = "ep7out-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = 7, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .reg_udccs = &UDC_REGS->udccs[7], + .reg_ubcr = &UDC_REGS->ubcr7, + .reg_uddr = &UDC_REGS->uddr7, + }, + .ep[8] = { + .ep = { + .name = "ep8in-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 8, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .reg_udccs = &UDC_REGS->udccs[8], + .reg_uddr = &UDC_REGS->uddr8, + }, + .ep[9] = { + .ep = { + .name = "ep9out-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = 9, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .reg_udccs = &UDC_REGS->udccs[9], + .reg_ubcr = &UDC_REGS->ubcr9, + .reg_uddr = &UDC_REGS->uddr9, + }, + .ep[10] = { + .ep = { + .name = "ep10in-int", + .ops = &pxa25x_ep_ops, + .maxpacket = INT_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = INT_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 10, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .reg_udccs = &UDC_REGS->udccs[10], + .reg_uddr = &UDC_REGS->uddr10, + }, + + /* third group of endpoints */ + .ep[11] = { + .ep = { + .name = "ep11in-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 11, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .reg_udccs = &UDC_REGS->udccs[11], + .reg_uddr = &UDC_REGS->uddr11, + }, + .ep[12] = { + .ep = { + .name = "ep12out-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = 12, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .reg_udccs = &UDC_REGS->udccs[12], + .reg_ubcr = &UDC_REGS->ubcr12, + .reg_uddr = &UDC_REGS->uddr12, + }, + .ep[13] = { + .ep = { + .name = "ep13in-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 13, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .reg_udccs = &UDC_REGS->udccs[13], + .reg_uddr = &UDC_REGS->uddr13, + }, + .ep[14] = { + .ep = { + .name = "ep14out-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = 14, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .reg_udccs = &UDC_REGS->udccs[14], + .reg_ubcr = &UDC_REGS->ubcr14, + .reg_uddr = &UDC_REGS->uddr14, + }, + .ep[15] = { + .ep = { + .name = "ep15in-int", + .ops = &pxa25x_ep_ops, + .maxpacket = INT_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = INT_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 15, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .reg_udccs = &UDC_REGS->udccs[15], + .reg_uddr = &UDC_REGS->uddr15, + }, +#endif /* !CONFIG_USB_PXA25X_SMALL */ +}; + +static void udc_command(int cmd) +{ + switch (cmd) { + case PXA2XX_UDC_CMD_CONNECT: + setbits_le32(GPDR(CONFIG_USB_DEV_PULLUP_GPIO), + GPIO_bit(CONFIG_USB_DEV_PULLUP_GPIO)); + + /* enable pullup */ + writel(GPIO_bit(CONFIG_USB_DEV_PULLUP_GPIO), + GPCR(CONFIG_USB_DEV_PULLUP_GPIO)); + + debug("Connected to USB\n"); + break; + + case PXA2XX_UDC_CMD_DISCONNECT: + /* disable pullup resistor */ + writel(GPIO_bit(CONFIG_USB_DEV_PULLUP_GPIO), + GPSR(CONFIG_USB_DEV_PULLUP_GPIO)); + + /* setup pin as input, line will float */ + clrbits_le32(GPDR(CONFIG_USB_DEV_PULLUP_GPIO), + GPIO_bit(CONFIG_USB_DEV_PULLUP_GPIO)); + + debug("Disconnected from USB\n"); + break; + } +} + +static struct pxa2xx_udc_mach_info mach_info = { + .udc_command = udc_command, +}; + +/* + * when a driver is successfully registered, it will receive + * control requests including set_configuration(), which enables + * non-control requests. then usb traffic follows until a + * disconnect is reported. then a host may connect again, or + * the driver might get unbound. + */ +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + struct pxa25x_udc *dev = &memory; + int retval; + uint32_t chiprev; + + if (!driver + || driver->speed < USB_SPEED_FULL + || !driver->disconnect + || !driver->setup) + return -EINVAL; + if (!dev) + return -ENODEV; + if (dev->driver) + return -EBUSY; + + /* Enable clock for usb controller */ + setbits_le32(CKEN, CKEN11_USB); + + /* first hook up the driver ... */ + dev->driver = driver; + dev->pullup = 1; + + /* trigger chiprev-specific logic */ + switch ((chiprev = pxa_get_cpu_revision())) { + case PXA255_A0: + dev->has_cfr = 1; + break; + case PXA250_A0: + case PXA250_A1: + /* A0/A1 "not released"; ep 13, 15 unusable */ + /* fall through */ + case PXA250_B2: case PXA210_B2: + case PXA250_B1: case PXA210_B1: + case PXA250_B0: case PXA210_B0: + /* OUT-DMA is broken ... */ + /* fall through */ + case PXA250_C0: case PXA210_C0: + break; + default: + printf("%s: unrecognized processor: %08x\n", + DRIVER_NAME, chiprev); + return -ENODEV; + } + + the_controller = dev; + + /* prepare watchdog timer */ + dev->watchdog.running = 0; + dev->watchdog.period = 5000 * CONFIG_SYS_HZ / 1000000; /* 5 ms */ + dev->watchdog.function = udc_watchdog; + + dev->mach = &mach_info; + + udc_disable(dev); + udc_reinit(dev); + + dev->gadget.name = "pxa2xx_udc"; + retval = driver->bind(&dev->gadget); + if (retval) { + printf("bind to driver %s --> error %d\n", + DRIVER_NAME, retval); + dev->driver = NULL; + return retval; + } + + /* + * ... then enable host detection and ep0; and we're ready + * for set_configuration as well as eventual disconnect. + */ + printf("registered gadget driver '%s'\n", DRIVER_NAME); + + pullup(dev); + dump_state(dev); + return 0; +} + +static void +stop_activity(struct pxa25x_udc *dev, struct usb_gadget_driver *driver) +{ + int i; + + /* don't disconnect drivers more than once */ + if (dev->gadget.speed == USB_SPEED_UNKNOWN) + driver = NULL; + dev->gadget.speed = USB_SPEED_UNKNOWN; + + /* prevent new request submissions, kill any outstanding requests */ + for (i = 0; i < PXA_UDC_NUM_ENDPOINTS; i++) { + struct pxa25x_ep *ep = &dev->ep[i]; + + ep->stopped = 1; + nuke(ep, -ESHUTDOWN); + } + stop_watchdog(dev); + + /* report disconnect; the driver is already quiesced */ + if (driver) + driver->disconnect(&dev->gadget); + + /* re-init driver-visible data structures */ + udc_reinit(dev); +} + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct pxa25x_udc *dev = the_controller; + + if (!dev) + return -ENODEV; + if (!driver || driver != dev->driver || !driver->unbind) + return -EINVAL; + + local_irq_disable(); + dev->pullup = 0; + pullup(dev); + stop_activity(dev, driver); + local_irq_enable(); + + driver->unbind(&dev->gadget); + dev->driver = NULL; + + printf("unregistered gadget driver '%s'\n", DRIVER_NAME); + dump_state(dev); + + the_controller = NULL; + + clrbits_le32(CKEN, CKEN11_USB); + + return 0; +} + +extern void udc_disconnect(void) +{ + setbits_le32(CKEN, CKEN11_USB); + udc_clear_mask_UDCCR(UDCCR_UDE); + udc_command(PXA2XX_UDC_CMD_DISCONNECT); + clrbits_le32(CKEN, CKEN11_USB); +} + +/*-------------------------------------------------------------------------*/ + +extern int +usb_gadget_handle_interrupts(int index) +{ + return pxa25x_udc_irq(); +} diff --git a/roms/u-boot/drivers/usb/gadget/pxa25x_udc.h b/roms/u-boot/drivers/usb/gadget/pxa25x_udc.h new file mode 100644 index 000000000..7c3882aa1 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/pxa25x_udc.h @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Intel PXA25x on-chip full speed USB device controller + * + * Copyright (C) 2003 Robert Schwebel <r.schwebel@pengutronix.de>, Pengutronix + * Copyright (C) 2003 David Brownell + * Copyright (C) 2012 Lukasz Dalek <luk0104@gmail.com> + */ + +#ifndef __LINUX_USB_GADGET_PXA25X_H +#define __LINUX_USB_GADGET_PXA25X_H + +#include <linux/types.h> +#include <asm/arch/regs-usb.h> + +/* + * Prefetching support - only ARMv5. + */ + +#ifdef ARCH_HAS_PREFETCH +static inline void prefetch(const void *ptr) +{ + __asm__ __volatile__( + "pld\t%a0" + : + : "p" (ptr) + : "cc"); +} + +#define prefetchw(ptr) prefetch(ptr) +#endif /* ARCH_HAS_PREFETCH */ + +/*-------------------------------------------------------------------------*/ + +#define UDC_REGS ((struct pxa25x_udc_regs *)PXA25X_UDC_BASE) + +/*-------------------------------------------------------------------------*/ + +struct pxa2xx_udc_mach_info { + int (*udc_is_connected)(void); /* do we see host? */ + void (*udc_command)(int cmd); +#define PXA2XX_UDC_CMD_CONNECT 0 /* let host see us */ +#define PXA2XX_UDC_CMD_DISCONNECT 1 /* so host won't see us */ +}; + +struct pxa25x_udc; + +struct pxa25x_ep { + struct usb_ep ep; + struct pxa25x_udc *dev; + + const struct usb_endpoint_descriptor *desc; + struct list_head queue; + unsigned long pio_irqs; + + unsigned short fifo_size; + u8 bEndpointAddress; + u8 bmAttributes; + + unsigned stopped:1; + + /* UDCCS = UDC Control/Status for this EP + * UBCR = UDC Byte Count Remaining (contents of OUT fifo) + * UDDR = UDC Endpoint Data Register (the fifo) + * DRCM = DMA Request Channel Map + */ + u32 *reg_udccs; + u32 *reg_ubcr; + u32 *reg_uddr; +}; + +struct pxa25x_request { + struct usb_request req; + struct list_head queue; +}; + +enum ep0_state { + EP0_IDLE, + EP0_IN_DATA_PHASE, + EP0_OUT_DATA_PHASE, + EP0_END_XFER, + EP0_STALL, +}; + +#define EP0_FIFO_SIZE 16U +#define BULK_FIFO_SIZE 64U +#define ISO_FIFO_SIZE 256U +#define INT_FIFO_SIZE 8U + +struct udc_stats { + struct ep0stats { + unsigned long ops; + unsigned long bytes; + } read, write; + unsigned long irqs; +}; + +#ifdef CONFIG_USB_PXA25X_SMALL +/* when memory's tight, SMALL config saves code+data. */ +#define PXA_UDC_NUM_ENDPOINTS 3 +#endif + +#ifndef PXA_UDC_NUM_ENDPOINTS +#define PXA_UDC_NUM_ENDPOINTS 16 +#endif + +struct pxa25x_watchdog { + unsigned running:1; + ulong period; + ulong base; + struct pxa25x_udc *udc; + + void (*function)(struct pxa25x_udc *udc); +}; + +struct pxa25x_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct pxa25x_udc_regs *regs; + + enum ep0_state ep0state; + struct udc_stats stats; + unsigned got_irq:1, + pullup:1, + has_cfr:1, + req_pending:1, + req_std:1, + req_config:1, + active:1; + + struct clk *clk; + struct pxa2xx_udc_mach_info *mach; + u64 dma_mask; + struct pxa25x_ep ep[PXA_UDC_NUM_ENDPOINTS]; + + struct pxa25x_watchdog watchdog; +}; + +/*-------------------------------------------------------------------------*/ + +static struct pxa25x_udc *the_controller; + +/*-------------------------------------------------------------------------*/ + +#ifndef DEBUG +# define NOISY 0 +#endif + +#endif /* __LINUX_USB_GADGET_PXA25X_H */ diff --git a/roms/u-boot/drivers/usb/gadget/pxa27x_udc.c b/roms/u-boot/drivers/usb/gadget/pxa27x_udc.c new file mode 100644 index 000000000..ba362b8f2 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/pxa27x_udc.c @@ -0,0 +1,703 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * PXA27x USB device driver for u-boot. + * + * Copyright (C) 2007 Rodolfo Giometti <giometti@linux.it> + * Copyright (C) 2007 Eurotech S.p.A. <info@eurotech.it> + * Copyright (C) 2008 Vivek Kutal <vivek.kutal@azingo.com> + */ + + +#include <common.h> +#include <asm/arch/hardware.h> +#include <asm/byteorder.h> +#include <asm/io.h> +#include <usbdevice.h> +#include <linux/delay.h> +#include <usb/pxa27x_udc.h> +#include <usb/udc.h> + +#include "ep0.h" + +/* number of endpoints on this UDC */ +#define UDC_MAX_ENDPOINTS 24 + +static struct urb *ep0_urb; +static struct usb_device_instance *udc_device; +static int ep0state = EP0_IDLE; + +#ifdef USBDDBG +static void udc_dump_buffer(char *name, u8 *buf, int len) +{ + usbdbg("%s - buf %p, len %d", name, buf, len); + print_buffer(0, buf, 1, len, 0); +} +#else +#define udc_dump_buffer(name, buf, len) /* void */ +#endif + +static inline void udc_ack_int_UDCCR(int mask) +{ + writel(readl(USIR1) | mask, USIR1); +} + +/* + * If the endpoint has an active tx_urb, then the next packet of data from the + * URB is written to the tx FIFO. + * The total amount of data in the urb is given by urb->actual_length. + * The maximum amount of data that can be sent in any one packet is given by + * endpoint->tx_packetSize. + * The number of data bytes from this URB that have already been transmitted + * is given by endpoint->sent. + * endpoint->last is updated by this routine with the number of data bytes + * transmitted in this packet. + */ +static int udc_write_urb(struct usb_endpoint_instance *endpoint) +{ + struct urb *urb = endpoint->tx_urb; + int ep_num = endpoint->endpoint_address & USB_ENDPOINT_NUMBER_MASK; + u32 *data32 = (u32 *) urb->buffer; + u8 *data8 = (u8 *) urb->buffer; + unsigned int i, n, w, b, is_short; + int timeout = 2000; /* 2ms */ + + if (!urb || !urb->actual_length) + return -1; + + n = min_t(unsigned int, urb->actual_length - endpoint->sent, + endpoint->tx_packetSize); + if (n <= 0) + return -1; + + usbdbg("write urb on ep %d", ep_num); +#if defined(USBDDBG) && defined(USBDPARANOIA) + usbdbg("urb: buf %p, buf_len %d, actual_len %d", + urb->buffer, urb->buffer_length, urb->actual_length); + usbdbg("endpoint: sent %d, tx_packetSize %d, last %d", + endpoint->sent, endpoint->tx_packetSize, endpoint->last); +#endif + + is_short = n != endpoint->tx_packetSize; + w = n / 4; + b = n % 4; + usbdbg("n %d%s w %d b %d", n, is_short ? "-s" : "", w, b); + udc_dump_buffer("urb write", data8 + endpoint->sent, n); + + /* Prepare for data send */ + if (ep_num) + writel(UDCCSR_PC ,UDCCSN(ep_num)); + + for (i = 0; i < w; i++) + writel(data32[endpoint->sent / 4 + i], UDCDN(ep_num)); + + for (i = 0; i < b; i++) + writeb(data8[endpoint->sent + w * 4 + i], UDCDN(ep_num)); + + /* Set "Packet Complete" if less data then tx_packetSize */ + if (is_short) + writel(ep_num ? UDCCSR_SP : UDCCSR0_IPR, UDCCSN(ep_num)); + + /* Wait for data sent */ + if (ep_num) { + while (!(readl(UDCCSN(ep_num)) & UDCCSR_PC)) { + if (timeout-- == 0) + return -1; + else + udelay(1); + } + } + + endpoint->last = n; + + if (ep_num) { + usbd_tx_complete(endpoint); + } else { + endpoint->sent += n; + endpoint->last -= n; + } + + if (endpoint->sent >= urb->actual_length) { + urb->actual_length = 0; + endpoint->sent = 0; + endpoint->last = 0; + } + + if ((endpoint->sent >= urb->actual_length) && (!ep_num)) { + usbdbg("ep0 IN stage done"); + if (is_short) + ep0state = EP0_IDLE; + else + ep0state = EP0_XFER_COMPLETE; + } + + return 0; +} + +static int udc_read_urb(struct usb_endpoint_instance *endpoint) +{ + struct urb *urb = endpoint->rcv_urb; + int ep_num = endpoint->endpoint_address & USB_ENDPOINT_NUMBER_MASK; + u32 *data32 = (u32 *) urb->buffer; + unsigned int i, n; + + usbdbg("read urb on ep %d", ep_num); +#if defined(USBDDBG) && defined(USBDPARANOIA) + usbdbg("urb: buf %p, buf_len %d, actual_len %d", + urb->buffer, urb->buffer_length, urb->actual_length); + usbdbg("endpoint: rcv_packetSize %d", + endpoint->rcv_packetSize); +#endif + + if (readl(UDCCSN(ep_num)) & UDCCSR_BNE) + n = readl(UDCBCN(ep_num)) & 0x3ff; + else /* zlp */ + n = 0; + + usbdbg("n %d%s", n, n != endpoint->rcv_packetSize ? "-s" : ""); + for (i = 0; i < n; i += 4) + data32[urb->actual_length / 4 + i / 4] = readl(UDCDN(ep_num)); + + udc_dump_buffer("urb read", (u8 *) data32, urb->actual_length + n); + usbd_rcv_complete(endpoint, n, 0); + + return 0; +} + +static int udc_read_urb_ep0(void) +{ + u32 *data32 = (u32 *) ep0_urb->buffer; + u8 *data8 = (u8 *) ep0_urb->buffer; + unsigned int i, n, w, b; + + usbdbg("read urb on ep 0"); +#if defined(USBDDBG) && defined(USBDPARANOIA) + usbdbg("urb: buf %p, buf_len %d, actual_len %d", + ep0_urb->buffer, ep0_urb->buffer_length, ep0_urb->actual_length); +#endif + + n = readl(UDCBCR0); + w = n / 4; + b = n % 4; + + for (i = 0; i < w; i++) { + data32[ep0_urb->actual_length / 4 + i] = readl(UDCDN(0)); + /* ep0_urb->actual_length += 4; */ + } + + for (i = 0; i < b; i++) { + data8[ep0_urb->actual_length + w * 4 + i] = readb(UDCDN(0)); + /* ep0_urb->actual_length++; */ + } + + ep0_urb->actual_length += n; + + udc_dump_buffer("urb read", (u8 *) data32, ep0_urb->actual_length); + + writel(UDCCSR0_OPC | UDCCSR0_IPR, UDCCSR0); + if (ep0_urb->actual_length == ep0_urb->device_request.wLength) + return 1; + + return 0; +} + +static void udc_handle_ep0(struct usb_endpoint_instance *endpoint) +{ + u32 udccsr0 = readl(UDCCSR0); + u32 *data = (u32 *) &ep0_urb->device_request; + int i; + + usbdbg("udccsr0 %x", udccsr0); + + /* Clear stall status */ + if (udccsr0 & UDCCSR0_SST) { + usberr("clear stall status"); + writel(UDCCSR0_SST, UDCCSR0); + ep0state = EP0_IDLE; + } + + /* previous request unfinished? non-error iff back-to-back ... */ + if ((udccsr0 & UDCCSR0_SA) != 0 && ep0state != EP0_IDLE) + ep0state = EP0_IDLE; + + switch (ep0state) { + + case EP0_IDLE: + udccsr0 = readl(UDCCSR0); + /* Start control request? */ + if ((udccsr0 & (UDCCSR0_OPC | UDCCSR0_SA | UDCCSR0_RNE)) + == (UDCCSR0_OPC | UDCCSR0_SA | UDCCSR0_RNE)) { + + /* Read SETUP packet. + * SETUP packet size is 8 bytes (aka 2 words) + */ + usbdbg("try reading SETUP packet"); + for (i = 0; i < 2; i++) { + if ((readl(UDCCSR0) & UDCCSR0_RNE) == 0) { + usberr("setup packet too short:%d", i); + goto stall; + } + data[i] = readl(UDCDR0); + } + + writel(readl(UDCCSR0) | UDCCSR0_OPC | UDCCSR0_SA, UDCCSR0); + if ((readl(UDCCSR0) & UDCCSR0_RNE) != 0) { + usberr("setup packet too long"); + goto stall; + } + + udc_dump_buffer("ep0 setup read", (u8 *) data, 8); + + if (ep0_urb->device_request.wLength == 0) { + usbdbg("Zero Data control Packet\n"); + if (ep0_recv_setup(ep0_urb)) { + usberr("Invalid Setup Packet\n"); + udc_dump_buffer("ep0 setup read", + (u8 *)data, 8); + goto stall; + } + writel(UDCCSR0_IPR, UDCCSR0); + ep0state = EP0_IDLE; + } else { + /* Check direction */ + if ((ep0_urb->device_request.bmRequestType & + USB_REQ_DIRECTION_MASK) + == USB_REQ_HOST2DEVICE) { + ep0state = EP0_OUT_DATA; + ep0_urb->buffer = + (u8 *)ep0_urb->buffer_data; + ep0_urb->buffer_length = + sizeof(ep0_urb->buffer_data); + ep0_urb->actual_length = 0; + writel(UDCCSR0_IPR, UDCCSR0); + } else { + /* The ep0_recv_setup function has + * already placed our response packet + * data in ep0_urb->buffer and the + * packet length in + * ep0_urb->actual_length. + */ + if (ep0_recv_setup(ep0_urb)) { +stall: + usberr("Invalid setup packet"); + udc_dump_buffer("ep0 setup read" + , (u8 *) data, 8); + ep0state = EP0_IDLE; + + writel(UDCCSR0_SA | + UDCCSR0_OPC | UDCCSR0_FST | + UDCCS0_FTF, UDCCSR0); + + return; + } + + endpoint->tx_urb = ep0_urb; + endpoint->sent = 0; + usbdbg("EP0_IN_DATA"); + ep0state = EP0_IN_DATA; + if (udc_write_urb(endpoint) < 0) + goto stall; + + } + } + return; + } else if ((udccsr0 & (UDCCSR0_OPC | UDCCSR0_SA)) + == (UDCCSR0_OPC|UDCCSR0_SA)) { + usberr("Setup Active but no data. Stalling ....\n"); + goto stall; + } else { + usbdbg("random early IRQs"); + /* Some random early IRQs: + * - we acked FST + * - IPR cleared + * - OPC got set, without SA (likely status stage) + */ + writel(udccsr0 & (UDCCSR0_SA | UDCCSR0_OPC), UDCCSR0); + } + break; + + case EP0_OUT_DATA: + + if ((udccsr0 & UDCCSR0_OPC) && !(udccsr0 & UDCCSR0_SA)) { + if (udc_read_urb_ep0()) { +read_complete: + ep0state = EP0_IDLE; + if (ep0_recv_setup(ep0_urb)) { + /* Not a setup packet, stall next + * EP0 transaction + */ + udc_dump_buffer("ep0 setup read", + (u8 *) data, 8); + usberr("can't parse setup packet\n"); + goto stall; + } + } + } else if (!(udccsr0 & UDCCSR0_OPC) && + !(udccsr0 & UDCCSR0_IPR)) { + if (ep0_urb->device_request.wLength == + ep0_urb->actual_length) + goto read_complete; + + usberr("Premature Status\n"); + ep0state = EP0_IDLE; + } + break; + + case EP0_IN_DATA: + /* GET_DESCRIPTOR etc */ + if (udccsr0 & UDCCSR0_OPC) { + writel(UDCCSR0_OPC | UDCCSR0_FTF, UDCCSR0); + usberr("ep0in premature status"); + ep0state = EP0_IDLE; + } else { + /* irq was IPR clearing */ + if (udc_write_urb(endpoint) < 0) { + usberr("ep0_write_error\n"); + goto stall; + } + } + break; + + case EP0_XFER_COMPLETE: + writel(UDCCSR0_IPR, UDCCSR0); + ep0state = EP0_IDLE; + break; + + default: + usbdbg("Default\n"); + } + writel(USIR0_IR0, USIR0); +} + +static void udc_handle_ep(struct usb_endpoint_instance *endpoint) +{ + int ep_addr = endpoint->endpoint_address; + int ep_num = ep_addr & USB_ENDPOINT_NUMBER_MASK; + int ep_isout = (ep_addr & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT; + + u32 flags = readl(UDCCSN(ep_num)) & (UDCCSR_SST | UDCCSR_TRN); + if (flags) + writel(flags, UDCCSN(ep_num)); + + if (ep_isout) + udc_read_urb(endpoint); + else + udc_write_urb(endpoint); + + writel(UDCCSR_PC, UDCCSN(ep_num)); +} + +static void udc_state_changed(void) +{ + + writel(readl(UDCCR) | UDCCR_SMAC, UDCCR); + + usbdbg("New UDC settings are: conf %d - inter %d - alter %d", + (readl(UDCCR) & UDCCR_ACN) >> UDCCR_ACN_S, + (readl(UDCCR) & UDCCR_AIN) >> UDCCR_AIN_S, + (readl(UDCCR) & UDCCR_AAISN) >> UDCCR_AAISN_S); + + usbd_device_event_irq(udc_device, DEVICE_CONFIGURED, 0); + writel(UDCISR1_IRCC, UDCISR1); +} + +void udc_irq(void) +{ + int handled; + struct usb_endpoint_instance *endpoint; + int ep_num, i; + u32 udcisr0; + + do { + handled = 0; + /* Suspend Interrupt Request */ + if (readl(USIR1) & UDCCR_SUSIR) { + usbdbg("Suspend\n"); + udc_ack_int_UDCCR(UDCCR_SUSIR); + handled = 1; + ep0state = EP0_IDLE; + } + + /* Resume Interrupt Request */ + if (readl(USIR1) & UDCCR_RESIR) { + udc_ack_int_UDCCR(UDCCR_RESIR); + handled = 1; + usbdbg("USB resume\n"); + } + + if (readl(USIR1) & (1<<31)) { + handled = 1; + udc_state_changed(); + } + + /* Reset Interrupt Request */ + if (readl(USIR1) & UDCCR_RSTIR) { + udc_ack_int_UDCCR(UDCCR_RSTIR); + handled = 1; + usbdbg("Reset\n"); + usbd_device_event_irq(udc_device, DEVICE_RESET, 0); + } else { + if (readl(USIR0)) + usbdbg("UISR0: %x \n", readl(USIR0)); + + if (readl(USIR0) & 0x2) + writel(0x2, USIR0); + + /* Control traffic */ + if (readl(USIR0) & USIR0_IR0) { + handled = 1; + writel(USIR0_IR0, USIR0); + udc_handle_ep0(udc_device->bus->endpoint_array); + } + + endpoint = udc_device->bus->endpoint_array; + for (i = 0; i < udc_device->bus->max_endpoints; i++) { + ep_num = (endpoint[i].endpoint_address) & + USB_ENDPOINT_NUMBER_MASK; + if (!ep_num) + continue; + udcisr0 = readl(UDCISR0); + if (udcisr0 & + UDCISR_INT(ep_num, UDC_INT_PACKETCMP)) { + writel(UDCISR_INT(ep_num, UDC_INT_PACKETCMP), + UDCISR0); + udc_handle_ep(&endpoint[i]); + } + } + } + + } while (handled); +} + +/* The UDCCR reg contains mask and interrupt status bits, + * so using '|=' isn't safe as it may ack an interrupt. + */ +#define UDCCR_OEN (1 << 31) /* On-the-Go Enable */ +#define UDCCR_MASK_BITS (UDCCR_OEN | UDCCR_UDE) + +static inline void udc_set_mask_UDCCR(int mask) +{ + writel((readl(UDCCR) & UDCCR_MASK_BITS) | (mask & UDCCR_MASK_BITS), UDCCR); +} + +static inline void udc_clear_mask_UDCCR(int mask) +{ + writel((readl(UDCCR) & UDCCR_MASK_BITS) & ~(mask & UDCCR_MASK_BITS), UDCCR); +} + +static void pio_irq_enable(int ep_num) +{ + if (ep_num < 16) + writel(readl(UDCICR0) | 3 << (ep_num * 2), UDCICR0); + else { + ep_num -= 16; + writel(readl(UDCICR1) | 3 << (ep_num * 2), UDCICR1); + } +} + +/* + * udc_set_nak + * + * Allow upper layers to signal lower layers should not accept more RX data + */ +void udc_set_nak(int ep_num) +{ + /* TODO */ +} + +/* + * udc_unset_nak + * + * Suspend sending of NAK tokens for DATA OUT tokens on a given endpoint. + * Switch off NAKing on this endpoint to accept more data output from host. + */ +void udc_unset_nak(int ep_num) +{ + /* TODO */ +} + +int udc_endpoint_write(struct usb_endpoint_instance *endpoint) +{ + return udc_write_urb(endpoint); +} + +/* Associate a physical endpoint with endpoint instance */ +void udc_setup_ep(struct usb_device_instance *device, unsigned int id, + struct usb_endpoint_instance *endpoint) +{ + int ep_num, ep_addr, ep_isout, ep_type, ep_size; + int config, interface, alternate; + u32 tmp; + + usbdbg("setting up endpoint id %d", id); + + if (!endpoint) { + usberr("endpoint void!"); + return; + } + + ep_num = endpoint->endpoint_address & USB_ENDPOINT_NUMBER_MASK; + if (ep_num >= UDC_MAX_ENDPOINTS) { + usberr("unable to setup ep %d!", ep_num); + return; + } + + pio_irq_enable(ep_num); + if (ep_num == 0) { + /* Done for ep0 */ + return; + } + + config = 1; + interface = 0; + alternate = 0; + + usbdbg("config %d - interface %d - alternate %d", + config, interface, alternate); + + ep_addr = endpoint->endpoint_address; + ep_num = ep_addr & USB_ENDPOINT_NUMBER_MASK; + ep_isout = (ep_addr & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT; + ep_type = ep_isout ? endpoint->rcv_attributes : endpoint->tx_attributes; + ep_size = ep_isout ? endpoint->rcv_packetSize : endpoint->tx_packetSize; + + usbdbg("addr %x, num %d, dir %s, type %s, packet size %d", + ep_addr, ep_num, + ep_isout ? "out" : "in", + ep_type == USB_ENDPOINT_XFER_ISOC ? "isoc" : + ep_type == USB_ENDPOINT_XFER_BULK ? "bulk" : + ep_type == USB_ENDPOINT_XFER_INT ? "int" : "???", + ep_size + ); + + /* Configure UDCCRx */ + tmp = 0; + tmp |= (config << UDCCONR_CN_S) & UDCCONR_CN; + tmp |= (interface << UDCCONR_IN_S) & UDCCONR_IN; + tmp |= (alternate << UDCCONR_AISN_S) & UDCCONR_AISN; + tmp |= (ep_num << UDCCONR_EN_S) & UDCCONR_EN; + tmp |= (ep_type << UDCCONR_ET_S) & UDCCONR_ET; + tmp |= ep_isout ? 0 : UDCCONR_ED; + tmp |= (ep_size << UDCCONR_MPS_S) & UDCCONR_MPS; + tmp |= UDCCONR_EE; + + writel(tmp, UDCCN(ep_num)); + + usbdbg("UDCCR%c = %x", 'A' + ep_num-1, readl(UDCCN(ep_num))); + usbdbg("UDCCSR%c = %x", 'A' + ep_num-1, readl(UDCCSN(ep_num))); +} + +/* Connect the USB device to the bus */ +void udc_connect(void) +{ + usbdbg("UDC connect"); + +#ifdef CONFIG_USB_DEV_PULLUP_GPIO + /* Turn on the USB connection by enabling the pullup resistor */ + writel(readl(GPDR(CONFIG_USB_DEV_PULLUP_GPIO)) + | GPIO_bit(CONFIG_USB_DEV_PULLUP_GPIO), + GPDR(CONFIG_USB_DEV_PULLUP_GPIO)); + writel(GPIO_bit(CONFIG_USB_DEV_PULLUP_GPIO), GPSR(CONFIG_USB_DEV_PULLUP_GPIO)); +#else + /* Host port 2 transceiver D+ pull up enable */ + writel(readl(UP2OCR) | UP2OCR_DPPUE, UP2OCR); +#endif +} + +/* Disconnect the USB device to the bus */ +void udc_disconnect(void) +{ + usbdbg("UDC disconnect"); + +#ifdef CONFIG_USB_DEV_PULLUP_GPIO + /* Turn off the USB connection by disabling the pullup resistor */ + writel(GPIO_bit(CONFIG_USB_DEV_PULLUP_GPIO), GPCR(CONFIG_USB_DEV_PULLUP_GPIO)); +#else + /* Host port 2 transceiver D+ pull up disable */ + writel(readl(UP2OCR) & ~UP2OCR_DPPUE, UP2OCR); +#endif +} + +/* Switch on the UDC */ +void udc_enable(struct usb_device_instance *device) +{ + + ep0state = EP0_IDLE; + + /* enable endpoint 0, A, B's Packet Complete Interrupt. */ + writel(0xffffffff, UDCICR0); + writel(0xa8000000, UDCICR1); + + /* clear the interrupt status/control registers */ + writel(0xffffffff, UDCISR0); + writel(0xffffffff, UDCISR1); + + /* set UDC-enable */ + udc_set_mask_UDCCR(UDCCR_UDE); + + udc_device = device; + if (!ep0_urb) + ep0_urb = usbd_alloc_urb(udc_device, + udc_device->bus->endpoint_array); + else + usbinfo("ep0_urb %p already allocated", ep0_urb); + + usbdbg("UDC Enabled\n"); +} + +/* Need to check this again */ +void udc_disable(void) +{ + usbdbg("disable UDC"); + + udc_clear_mask_UDCCR(UDCCR_UDE); + + /* Disable clock for USB device */ + writel(readl(CKEN) & ~CKEN11_USB, CKEN); + + /* Free ep0 URB */ + if (ep0_urb) { + usbd_dealloc_urb(ep0_urb); + ep0_urb = NULL; + } + + /* Reset device pointer */ + udc_device = NULL; +} + +/* Allow udc code to do any additional startup */ +void udc_startup_events(struct usb_device_instance *device) +{ + /* The DEVICE_INIT event puts the USB device in the state STATE_INIT */ + usbd_device_event_irq(device, DEVICE_INIT, 0); + + /* The DEVICE_CREATE event puts the USB device in the state + * STATE_ATTACHED */ + usbd_device_event_irq(device, DEVICE_CREATE, 0); + + /* Some USB controller driver implementations signal + * DEVICE_HUB_CONFIGURED and DEVICE_RESET events here. + * DEVICE_HUB_CONFIGURED causes a transition to the state + * STATE_POWERED, and DEVICE_RESET causes a transition to + * the state STATE_DEFAULT. + */ + udc_enable(device); +} + +/* Initialize h/w stuff */ +int udc_init(void) +{ + udc_device = NULL; + usbdbg("PXA27x usbd start"); + + /* Enable clock for USB device */ + writel(readl(CKEN) | CKEN11_USB, CKEN); + + /* Disable the UDC */ + udc_clear_mask_UDCCR(UDCCR_UDE); + + /* Disable IRQs: we don't use them */ + writel(0, UDCICR0); + writel(0, UDCICR1); + + return 0; +} diff --git a/roms/u-boot/drivers/usb/gadget/rndis.c b/roms/u-boot/drivers/usb/gadget/rndis.c new file mode 100644 index 000000000..13c327ea3 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/rndis.c @@ -0,0 +1,1314 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * RNDIS MSG parser + * + * Authors: Benedikt Spranger, Pengutronix + * Robert Schwebel, Pengutronix + * + * This software was originally developed in conformance with + * Microsoft's Remote NDIS Specification License Agreement. + * + * 03/12/2004 Kai-Uwe Bloem <linux-development@auerswald.de> + * Fixed message length bug in init_response + * + * 03/25/2004 Kai-Uwe Bloem <linux-development@auerswald.de> + * Fixed rndis_rm_hdr length bug. + * + * Copyright (C) 2004 by David Brownell + * updates to merge with Linux 2.6, better match RNDIS spec + */ + +#include <common.h> +#include <log.h> +#include <net.h> +#include <malloc.h> +#include <linux/types.h> +#include <linux/list.h> +#include <linux/netdevice.h> + +#include <asm/byteorder.h> +#include <asm/unaligned.h> +#include <linux/errno.h> + +#undef RNDIS_PM +#undef RNDIS_WAKEUP +#undef VERBOSE + +#include "rndis.h" + +/* + * The driver for your USB chip needs to support ep0 OUT to work with + * RNDIS, plus all three CDC Ethernet endpoints (interrupt not optional). + * + * Windows hosts need an INF file like Documentation/usb/linux.inf + * and will be happier if you provide the host_addr module parameter. + */ + +#define RNDIS_MAX_CONFIGS 1 + +static rndis_params rndis_per_dev_params[RNDIS_MAX_CONFIGS]; + +/* Driver Version */ +static const __le32 rndis_driver_version = __constant_cpu_to_le32(1); + +/* Function Prototypes */ +static rndis_resp_t *rndis_add_response(int configNr, u32 length); + + +/* supported OIDs */ +static const u32 oid_supported_list[] = { + /* the general stuff */ + OID_GEN_SUPPORTED_LIST, + OID_GEN_HARDWARE_STATUS, + OID_GEN_MEDIA_SUPPORTED, + OID_GEN_MEDIA_IN_USE, + OID_GEN_MAXIMUM_FRAME_SIZE, + OID_GEN_LINK_SPEED, + OID_GEN_TRANSMIT_BLOCK_SIZE, + OID_GEN_RECEIVE_BLOCK_SIZE, + OID_GEN_VENDOR_ID, + OID_GEN_VENDOR_DESCRIPTION, + OID_GEN_VENDOR_DRIVER_VERSION, + OID_GEN_CURRENT_PACKET_FILTER, + OID_GEN_MAXIMUM_TOTAL_SIZE, + OID_GEN_MEDIA_CONNECT_STATUS, + OID_GEN_PHYSICAL_MEDIUM, +#if 0 + OID_GEN_RNDIS_CONFIG_PARAMETER, +#endif + + /* the statistical stuff */ + OID_GEN_XMIT_OK, + OID_GEN_RCV_OK, + OID_GEN_XMIT_ERROR, + OID_GEN_RCV_ERROR, + OID_GEN_RCV_NO_BUFFER, +#ifdef RNDIS_OPTIONAL_STATS + OID_GEN_DIRECTED_BYTES_XMIT, + OID_GEN_DIRECTED_FRAMES_XMIT, + OID_GEN_MULTICAST_BYTES_XMIT, + OID_GEN_MULTICAST_FRAMES_XMIT, + OID_GEN_BROADCAST_BYTES_XMIT, + OID_GEN_BROADCAST_FRAMES_XMIT, + OID_GEN_DIRECTED_BYTES_RCV, + OID_GEN_DIRECTED_FRAMES_RCV, + OID_GEN_MULTICAST_BYTES_RCV, + OID_GEN_MULTICAST_FRAMES_RCV, + OID_GEN_BROADCAST_BYTES_RCV, + OID_GEN_BROADCAST_FRAMES_RCV, + OID_GEN_RCV_CRC_ERROR, + OID_GEN_TRANSMIT_QUEUE_LENGTH, +#endif /* RNDIS_OPTIONAL_STATS */ + + /* mandatory 802.3 */ + /* the general stuff */ + OID_802_3_PERMANENT_ADDRESS, + OID_802_3_CURRENT_ADDRESS, + OID_802_3_MULTICAST_LIST, + OID_802_3_MAC_OPTIONS, + OID_802_3_MAXIMUM_LIST_SIZE, + + /* the statistical stuff */ + OID_802_3_RCV_ERROR_ALIGNMENT, + OID_802_3_XMIT_ONE_COLLISION, + OID_802_3_XMIT_MORE_COLLISIONS, +#ifdef RNDIS_OPTIONAL_STATS + OID_802_3_XMIT_DEFERRED, + OID_802_3_XMIT_MAX_COLLISIONS, + OID_802_3_RCV_OVERRUN, + OID_802_3_XMIT_UNDERRUN, + OID_802_3_XMIT_HEARTBEAT_FAILURE, + OID_802_3_XMIT_TIMES_CRS_LOST, + OID_802_3_XMIT_LATE_COLLISIONS, +#endif /* RNDIS_OPTIONAL_STATS */ + +#ifdef RNDIS_PM + /* PM and wakeup are mandatory for USB: */ + + /* power management */ + OID_PNP_CAPABILITIES, + OID_PNP_QUERY_POWER, + OID_PNP_SET_POWER, + +#ifdef RNDIS_WAKEUP + /* wake up host */ + OID_PNP_ENABLE_WAKE_UP, + OID_PNP_ADD_WAKE_UP_PATTERN, + OID_PNP_REMOVE_WAKE_UP_PATTERN, +#endif /* RNDIS_WAKEUP */ +#endif /* RNDIS_PM */ +}; + + +/* NDIS Functions */ +static int gen_ndis_query_resp(int configNr, u32 OID, u8 *buf, + unsigned buf_len, rndis_resp_t *r) +{ + int retval = -ENOTSUPP; + u32 length = 4; /* usually */ + __le32 *outbuf; + int i, count; + rndis_query_cmplt_type *resp; + rndis_params *params; + + if (!r) + return -ENOMEM; + resp = (rndis_query_cmplt_type *) r->buf; + + if (!resp) + return -ENOMEM; + +#if defined(DEBUG) && defined(DEBUG_VERBOSE) + if (buf_len) { + debug("query OID %08x value, len %d:\n", OID, buf_len); + for (i = 0; i < buf_len; i += 16) { + debug("%03d: %08x %08x %08x %08x\n", i, + get_unaligned_le32(&buf[i]), + get_unaligned_le32(&buf[i + 4]), + get_unaligned_le32(&buf[i + 8]), + get_unaligned_le32(&buf[i + 12])); + } + } +#endif + + /* response goes here, right after the header */ + outbuf = (__le32 *) &resp[1]; + resp->InformationBufferOffset = __constant_cpu_to_le32(16); + + params = &rndis_per_dev_params[configNr]; + switch (OID) { + + /* general oids (table 4-1) */ + + /* mandatory */ + case OID_GEN_SUPPORTED_LIST: + debug("%s: OID_GEN_SUPPORTED_LIST\n", __func__); + length = sizeof(oid_supported_list); + count = length / sizeof(u32); + for (i = 0; i < count; i++) + outbuf[i] = cpu_to_le32(oid_supported_list[i]); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_HARDWARE_STATUS: + debug("%s: OID_GEN_HARDWARE_STATUS\n", __func__); + /* + * Bogus question! + * Hardware must be ready to receive high level protocols. + * BTW: + * reddite ergo quae sunt Caesaris Caesari + * et quae sunt Dei Deo! + */ + *outbuf = __constant_cpu_to_le32(0); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_MEDIA_SUPPORTED: + debug("%s: OID_GEN_MEDIA_SUPPORTED\n", __func__); + *outbuf = cpu_to_le32(params->medium); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_MEDIA_IN_USE: + debug("%s: OID_GEN_MEDIA_IN_USE\n", __func__); + /* one medium, one transport... (maybe you do it better) */ + *outbuf = cpu_to_le32(params->medium); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_MAXIMUM_FRAME_SIZE: + debug("%s: OID_GEN_MAXIMUM_FRAME_SIZE\n", __func__); + if (params->dev) { + *outbuf = cpu_to_le32(params->mtu); + retval = 0; + } + break; + + /* mandatory */ + case OID_GEN_LINK_SPEED: +#if defined(DEBUG) && defined(DEBUG_VERBOSE) + debug("%s: OID_GEN_LINK_SPEED\n", __func__); +#endif + if (params->media_state == NDIS_MEDIA_STATE_DISCONNECTED) + *outbuf = __constant_cpu_to_le32(0); + else + *outbuf = cpu_to_le32(params->speed); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_TRANSMIT_BLOCK_SIZE: + debug("%s: OID_GEN_TRANSMIT_BLOCK_SIZE\n", __func__); + if (params->dev) { + *outbuf = cpu_to_le32(params->mtu); + retval = 0; + } + break; + + /* mandatory */ + case OID_GEN_RECEIVE_BLOCK_SIZE: + debug("%s: OID_GEN_RECEIVE_BLOCK_SIZE\n", __func__); + if (params->dev) { + *outbuf = cpu_to_le32(params->mtu); + retval = 0; + } + break; + + /* mandatory */ + case OID_GEN_VENDOR_ID: + debug("%s: OID_GEN_VENDOR_ID\n", __func__); + *outbuf = cpu_to_le32(params->vendorID); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_VENDOR_DESCRIPTION: + debug("%s: OID_GEN_VENDOR_DESCRIPTION\n", __func__); + length = strlen(params->vendorDescr); + memcpy(outbuf, params->vendorDescr, length); + retval = 0; + break; + + case OID_GEN_VENDOR_DRIVER_VERSION: + debug("%s: OID_GEN_VENDOR_DRIVER_VERSION\n", __func__); + /* Created as LE */ + *outbuf = rndis_driver_version; + retval = 0; + break; + + /* mandatory */ + case OID_GEN_CURRENT_PACKET_FILTER: + debug("%s: OID_GEN_CURRENT_PACKET_FILTER\n", __func__); + *outbuf = cpu_to_le32(*params->filter); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_MAXIMUM_TOTAL_SIZE: + debug("%s: OID_GEN_MAXIMUM_TOTAL_SIZE\n", __func__); + *outbuf = __constant_cpu_to_le32(RNDIS_MAX_TOTAL_SIZE); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_MEDIA_CONNECT_STATUS: +#if defined(DEBUG) && defined(DEBUG_VERBOSE) + debug("%s: OID_GEN_MEDIA_CONNECT_STATUS\n", __func__); +#endif + *outbuf = cpu_to_le32(params->media_state); + retval = 0; + break; + + case OID_GEN_PHYSICAL_MEDIUM: + debug("%s: OID_GEN_PHYSICAL_MEDIUM\n", __func__); + *outbuf = __constant_cpu_to_le32(0); + retval = 0; + break; + + /* + * The RNDIS specification is incomplete/wrong. Some versions + * of MS-Windows expect OIDs that aren't specified there. Other + * versions emit undefined RNDIS messages. DOCUMENT ALL THESE! + */ + case OID_GEN_MAC_OPTIONS: /* from WinME */ + debug("%s: OID_GEN_MAC_OPTIONS\n", __func__); + *outbuf = __constant_cpu_to_le32( + NDIS_MAC_OPTION_RECEIVE_SERIALIZED + | NDIS_MAC_OPTION_FULL_DUPLEX); + retval = 0; + break; + + /* statistics OIDs (table 4-2) */ + + /* mandatory */ + case OID_GEN_XMIT_OK: +#if defined(DEBUG) && defined(DEBUG_VERBOSE) + debug("%s: OID_GEN_XMIT_OK\n", __func__); +#endif + if (params->stats) { + *outbuf = cpu_to_le32( + params->stats->tx_packets - + params->stats->tx_errors - + params->stats->tx_dropped); + retval = 0; + } + break; + + /* mandatory */ + case OID_GEN_RCV_OK: +#if defined(DEBUG) && defined(DEBUG_VERBOSE) + debug("%s: OID_GEN_RCV_OK\n", __func__); +#endif + if (params->stats) { + *outbuf = cpu_to_le32( + params->stats->rx_packets - + params->stats->rx_errors - + params->stats->rx_dropped); + retval = 0; + } + break; + + /* mandatory */ + case OID_GEN_XMIT_ERROR: +#if defined(DEBUG) && defined(DEBUG_VERBOSE) + debug("%s: OID_GEN_XMIT_ERROR\n", __func__); +#endif + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->tx_errors); + retval = 0; + } + break; + + /* mandatory */ + case OID_GEN_RCV_ERROR: +#if defined(DEBUG) && defined(DEBUG_VERBOSE) + debug("%s: OID_GEN_RCV_ERROR\n", __func__); +#endif + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->rx_errors); + retval = 0; + } + break; + + /* mandatory */ + case OID_GEN_RCV_NO_BUFFER: + debug("%s: OID_GEN_RCV_NO_BUFFER\n", __func__); + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->rx_dropped); + retval = 0; + } + break; + +#ifdef RNDIS_OPTIONAL_STATS + case OID_GEN_DIRECTED_BYTES_XMIT: + debug("%s: OID_GEN_DIRECTED_BYTES_XMIT\n", __func__); + /* + * Aunt Tilly's size of shoes + * minus antarctica count of penguins + * divided by weight of Alpha Centauri + */ + if (params->stats) { + *outbuf = cpu_to_le32( + (params->stats->tx_packets - + params->stats->tx_errors - + params->stats->tx_dropped) + * 123); + retval = 0; + } + break; + + case OID_GEN_DIRECTED_FRAMES_XMIT: + debug("%s: OID_GEN_DIRECTED_FRAMES_XMIT\n", __func__); + /* dito */ + if (params->stats) { + *outbuf = cpu_to_le32( + (params->stats->tx_packets - + params->stats->tx_errors - + params->stats->tx_dropped) + / 123); + retval = 0; + } + break; + + case OID_GEN_MULTICAST_BYTES_XMIT: + debug("%s: OID_GEN_MULTICAST_BYTES_XMIT\n", __func__); + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->multicast * 1234); + retval = 0; + } + break; + + case OID_GEN_MULTICAST_FRAMES_XMIT: + debug("%s: OID_GEN_MULTICAST_FRAMES_XMIT\n", __func__); + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->multicast); + retval = 0; + } + break; + + case OID_GEN_BROADCAST_BYTES_XMIT: + debug("%s: OID_GEN_BROADCAST_BYTES_XMIT\n", __func__); + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->tx_packets/42*255); + retval = 0; + } + break; + + case OID_GEN_BROADCAST_FRAMES_XMIT: + debug("%s: OID_GEN_BROADCAST_FRAMES_XMIT\n", __func__); + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->tx_packets / 42); + retval = 0; + } + break; + + case OID_GEN_DIRECTED_BYTES_RCV: + debug("%s: OID_GEN_DIRECTED_BYTES_RCV\n", __func__); + *outbuf = __constant_cpu_to_le32(0); + retval = 0; + break; + + case OID_GEN_DIRECTED_FRAMES_RCV: + debug("%s: OID_GEN_DIRECTED_FRAMES_RCV\n", __func__); + *outbuf = __constant_cpu_to_le32(0); + retval = 0; + break; + + case OID_GEN_MULTICAST_BYTES_RCV: + debug("%s: OID_GEN_MULTICAST_BYTES_RCV\n", __func__); + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->multicast * 1111); + retval = 0; + } + break; + + case OID_GEN_MULTICAST_FRAMES_RCV: + debug("%s: OID_GEN_MULTICAST_FRAMES_RCV\n", __func__); + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->multicast); + retval = 0; + } + break; + + case OID_GEN_BROADCAST_BYTES_RCV: + debug("%s: OID_GEN_BROADCAST_BYTES_RCV\n", __func__); + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->rx_packets/42*255); + retval = 0; + } + break; + + case OID_GEN_BROADCAST_FRAMES_RCV: + debug("%s: OID_GEN_BROADCAST_FRAMES_RCV\n", __func__); + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->rx_packets / 42); + retval = 0; + } + break; + + case OID_GEN_RCV_CRC_ERROR: + debug("%s: OID_GEN_RCV_CRC_ERROR\n", __func__); + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->rx_crc_errors); + retval = 0; + } + break; + + case OID_GEN_TRANSMIT_QUEUE_LENGTH: + debug("%s: OID_GEN_TRANSMIT_QUEUE_LENGTH\n", __func__); + *outbuf = __constant_cpu_to_le32(0); + retval = 0; + break; +#endif /* RNDIS_OPTIONAL_STATS */ + + /* ieee802.3 OIDs (table 4-3) */ + + /* mandatory */ + case OID_802_3_PERMANENT_ADDRESS: + debug("%s: OID_802_3_PERMANENT_ADDRESS\n", __func__); + if (params->dev) { + length = ETH_ALEN; + memcpy(outbuf, params->host_mac, length); + retval = 0; + } + break; + + /* mandatory */ + case OID_802_3_CURRENT_ADDRESS: + debug("%s: OID_802_3_CURRENT_ADDRESS\n", __func__); + if (params->dev) { + length = ETH_ALEN; + memcpy(outbuf, params->host_mac, length); + retval = 0; + } + break; + + /* mandatory */ + case OID_802_3_MULTICAST_LIST: + debug("%s: OID_802_3_MULTICAST_LIST\n", __func__); + /* Multicast base address only */ + *outbuf = __constant_cpu_to_le32(0xE0000000); + retval = 0; + break; + + /* mandatory */ + case OID_802_3_MAXIMUM_LIST_SIZE: + debug("%s: OID_802_3_MAXIMUM_LIST_SIZE\n", __func__); + /* Multicast base address only */ + *outbuf = __constant_cpu_to_le32(1); + retval = 0; + break; + + case OID_802_3_MAC_OPTIONS: + debug("%s: OID_802_3_MAC_OPTIONS\n", __func__); + break; + + /* ieee802.3 statistics OIDs (table 4-4) */ + + /* mandatory */ + case OID_802_3_RCV_ERROR_ALIGNMENT: + debug("%s: OID_802_3_RCV_ERROR_ALIGNMENT\n", __func__); + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->rx_frame_errors); + retval = 0; + } + break; + + /* mandatory */ + case OID_802_3_XMIT_ONE_COLLISION: + debug("%s: OID_802_3_XMIT_ONE_COLLISION\n", __func__); + *outbuf = __constant_cpu_to_le32(0); + retval = 0; + break; + + /* mandatory */ + case OID_802_3_XMIT_MORE_COLLISIONS: + debug("%s: OID_802_3_XMIT_MORE_COLLISIONS\n", __func__); + *outbuf = __constant_cpu_to_le32(0); + retval = 0; + break; + +#ifdef RNDIS_OPTIONAL_STATS + case OID_802_3_XMIT_DEFERRED: + debug("%s: OID_802_3_XMIT_DEFERRED\n", __func__); + /* TODO */ + break; + + case OID_802_3_XMIT_MAX_COLLISIONS: + debug("%s: OID_802_3_XMIT_MAX_COLLISIONS\n", __func__); + /* TODO */ + break; + + case OID_802_3_RCV_OVERRUN: + debug("%s: OID_802_3_RCV_OVERRUN\n", __func__); + /* TODO */ + break; + + case OID_802_3_XMIT_UNDERRUN: + debug("%s: OID_802_3_XMIT_UNDERRUN\n", __func__); + /* TODO */ + break; + + case OID_802_3_XMIT_HEARTBEAT_FAILURE: + debug("%s: OID_802_3_XMIT_HEARTBEAT_FAILURE\n", __func__); + /* TODO */ + break; + + case OID_802_3_XMIT_TIMES_CRS_LOST: + debug("%s: OID_802_3_XMIT_TIMES_CRS_LOST\n", __func__); + /* TODO */ + break; + + case OID_802_3_XMIT_LATE_COLLISIONS: + debug("%s: OID_802_3_XMIT_LATE_COLLISIONS\n", __func__); + /* TODO */ + break; +#endif /* RNDIS_OPTIONAL_STATS */ + +#ifdef RNDIS_PM + /* power management OIDs (table 4-5) */ + case OID_PNP_CAPABILITIES: + debug("%s: OID_PNP_CAPABILITIES\n", __func__); + + /* for now, no wakeup capabilities */ + length = sizeof(struct NDIS_PNP_CAPABILITIES); + memset(outbuf, 0, length); + retval = 0; + break; + case OID_PNP_QUERY_POWER: + debug("%s: OID_PNP_QUERY_POWER D%d\n", __func__, + get_unaligned_le32(buf) - 1); + /* + * only suspend is a real power state, and + * it can't be entered by OID_PNP_SET_POWER... + */ + length = 0; + retval = 0; + break; +#endif + + default: + debug("%s: query unknown OID 0x%08X\n", __func__, OID); + } + if (retval < 0) + length = 0; + + resp->InformationBufferLength = cpu_to_le32(length); + r->length = length + sizeof *resp; + resp->MessageLength = cpu_to_le32(r->length); + return retval; +} + +static int gen_ndis_set_resp(u8 configNr, u32 OID, u8 *buf, u32 buf_len, + rndis_resp_t *r) +{ + rndis_set_cmplt_type *resp; + int retval = -ENOTSUPP; + struct rndis_params *params; +#if (defined(DEBUG) && defined(DEBUG_VERBOSE)) || defined(RNDIS_PM) + int i; +#endif + + if (!r) + return -ENOMEM; + resp = (rndis_set_cmplt_type *) r->buf; + if (!resp) + return -ENOMEM; + +#if defined(DEBUG) && defined(DEBUG_VERBOSE) + if (buf_len) { + debug("set OID %08x value, len %d:\n", OID, buf_len); + for (i = 0; i < buf_len; i += 16) { + debug("%03d: %08x %08x %08x %08x\n", i, + get_unaligned_le32(&buf[i]), + get_unaligned_le32(&buf[i + 4]), + get_unaligned_le32(&buf[i + 8]), + get_unaligned_le32(&buf[i + 12])); + } + } +#endif + + params = &rndis_per_dev_params[configNr]; + switch (OID) { + case OID_GEN_CURRENT_PACKET_FILTER: + + /* + * these NDIS_PACKET_TYPE_* bitflags are shared with + * cdc_filter; it's not RNDIS-specific + * NDIS_PACKET_TYPE_x == USB_CDC_PACKET_TYPE_x for x in: + * PROMISCUOUS, DIRECTED, + * MULTICAST, ALL_MULTICAST, BROADCAST + */ + *params->filter = (u16) get_unaligned_le32(buf); + debug("%s: OID_GEN_CURRENT_PACKET_FILTER %08x\n", + __func__, *params->filter); + + /* + * this call has a significant side effect: it's + * what makes the packet flow start and stop, like + * activating the CDC Ethernet altsetting. + */ +#ifdef RNDIS_PM +update_linkstate: +#endif + retval = 0; + if (*params->filter) + params->state = RNDIS_DATA_INITIALIZED; + else + params->state = RNDIS_INITIALIZED; + break; + + case OID_802_3_MULTICAST_LIST: + /* I think we can ignore this */ + debug("%s: OID_802_3_MULTICAST_LIST\n", __func__); + retval = 0; + break; +#if 0 + case OID_GEN_RNDIS_CONFIG_PARAMETER: + { + struct rndis_config_parameter *param; + param = (struct rndis_config_parameter *) buf; + debug("%s: OID_GEN_RNDIS_CONFIG_PARAMETER '%*s'\n", + __func__, + min(cpu_to_le32(param->ParameterNameLength), 80), + buf + param->ParameterNameOffset); + retval = 0; + } + break; +#endif + +#ifdef RNDIS_PM + case OID_PNP_SET_POWER: + /* + * The only real power state is USB suspend, and RNDIS requests + * can't enter it; this one isn't really about power. After + * resuming, Windows forces a reset, and then SET_POWER D0. + * FIXME ... then things go batty; Windows wedges itself. + */ + i = get_unaligned_le32(buf); + debug("%s: OID_PNP_SET_POWER D%d\n", __func__, i - 1); + switch (i) { + case NdisDeviceStateD0: + *params->filter = params->saved_filter; + goto update_linkstate; + case NdisDeviceStateD3: + case NdisDeviceStateD2: + case NdisDeviceStateD1: + params->saved_filter = *params->filter; + retval = 0; + break; + } + break; + +#ifdef RNDIS_WAKEUP + /* + * no wakeup support advertised, so wakeup OIDs always fail: + * - OID_PNP_ENABLE_WAKE_UP + * - OID_PNP_{ADD,REMOVE}_WAKE_UP_PATTERN + */ +#endif + +#endif /* RNDIS_PM */ + + default: + debug("%s: set unknown OID 0x%08X, size %d\n", + __func__, OID, buf_len); + } + + return retval; +} + +/* + * Response Functions + */ + +static int rndis_init_response(int configNr, rndis_init_msg_type *buf) +{ + rndis_init_cmplt_type *resp; + rndis_resp_t *r; + + if (!rndis_per_dev_params[configNr].dev) + return -ENOTSUPP; + + r = rndis_add_response(configNr, sizeof(rndis_init_cmplt_type)); + if (!r) + return -ENOMEM; + resp = (rndis_init_cmplt_type *) r->buf; + + resp->MessageType = __constant_cpu_to_le32( + REMOTE_NDIS_INITIALIZE_CMPLT); + resp->MessageLength = __constant_cpu_to_le32(52); + resp->RequestID = get_unaligned(&buf->RequestID); /* Still LE in msg buffer */ + resp->Status = __constant_cpu_to_le32(RNDIS_STATUS_SUCCESS); + resp->MajorVersion = __constant_cpu_to_le32(RNDIS_MAJOR_VERSION); + resp->MinorVersion = __constant_cpu_to_le32(RNDIS_MINOR_VERSION); + resp->DeviceFlags = __constant_cpu_to_le32(RNDIS_DF_CONNECTIONLESS); + resp->Medium = __constant_cpu_to_le32(RNDIS_MEDIUM_802_3); + resp->MaxPacketsPerTransfer = __constant_cpu_to_le32(1); + resp->MaxTransferSize = cpu_to_le32( + rndis_per_dev_params[configNr].mtu + + ETHER_HDR_SIZE + + sizeof(struct rndis_packet_msg_type) + + 22); + resp->PacketAlignmentFactor = __constant_cpu_to_le32(0); + resp->AFListOffset = __constant_cpu_to_le32(0); + resp->AFListSize = __constant_cpu_to_le32(0); + + if (rndis_per_dev_params[configNr].ack) + rndis_per_dev_params[configNr].ack( + rndis_per_dev_params[configNr].dev); + + return 0; +} + +static int rndis_query_response(int configNr, rndis_query_msg_type *buf) +{ + rndis_query_cmplt_type *resp; + rndis_resp_t *r; + + debug("%s: OID = %08X\n", __func__, get_unaligned_le32(&buf->OID)); + if (!rndis_per_dev_params[configNr].dev) + return -ENOTSUPP; + + /* + * we need more memory: + * gen_ndis_query_resp expects enough space for + * rndis_query_cmplt_type followed by data. + * oid_supported_list is the largest data reply + */ + r = rndis_add_response(configNr, + sizeof(oid_supported_list) + sizeof(rndis_query_cmplt_type)); + if (!r) + return -ENOMEM; + resp = (rndis_query_cmplt_type *) r->buf; + + resp->MessageType = __constant_cpu_to_le32(REMOTE_NDIS_QUERY_CMPLT); + resp->RequestID = get_unaligned(&buf->RequestID); /* Still LE in msg buffer */ + + if (gen_ndis_query_resp(configNr, get_unaligned_le32(&buf->OID), + get_unaligned_le32(&buf->InformationBufferOffset) + + 8 + (u8 *) buf, + get_unaligned_le32(&buf->InformationBufferLength), + r)) { + /* OID not supported */ + resp->Status = __constant_cpu_to_le32( + RNDIS_STATUS_NOT_SUPPORTED); + resp->MessageLength = __constant_cpu_to_le32(sizeof *resp); + resp->InformationBufferLength = __constant_cpu_to_le32(0); + resp->InformationBufferOffset = __constant_cpu_to_le32(0); + } else + resp->Status = __constant_cpu_to_le32(RNDIS_STATUS_SUCCESS); + + if (rndis_per_dev_params[configNr].ack) + rndis_per_dev_params[configNr].ack( + rndis_per_dev_params[configNr].dev); + return 0; +} + +static int rndis_set_response(int configNr, rndis_set_msg_type *buf) +{ + u32 BufLength, BufOffset; + rndis_set_cmplt_type *resp; + rndis_resp_t *r; + + r = rndis_add_response(configNr, sizeof(rndis_set_cmplt_type)); + if (!r) + return -ENOMEM; + resp = (rndis_set_cmplt_type *) r->buf; + + BufLength = get_unaligned_le32(&buf->InformationBufferLength); + BufOffset = get_unaligned_le32(&buf->InformationBufferOffset); + +#ifdef VERBOSE + debug("%s: Length: %d\n", __func__, BufLength); + debug("%s: Offset: %d\n", __func__, BufOffset); + debug("%s: InfoBuffer: ", __func__); + + for (i = 0; i < BufLength; i++) + debug("%02x ", *(((u8 *) buf) + i + 8 + BufOffset)); + + debug("\n"); +#endif + + resp->MessageType = __constant_cpu_to_le32(REMOTE_NDIS_SET_CMPLT); + resp->MessageLength = __constant_cpu_to_le32(16); + resp->RequestID = get_unaligned(&buf->RequestID); /* Still LE in msg buffer */ + if (gen_ndis_set_resp(configNr, get_unaligned_le32(&buf->OID), + ((u8 *) buf) + 8 + BufOffset, BufLength, r)) + resp->Status = __constant_cpu_to_le32( + RNDIS_STATUS_NOT_SUPPORTED); + else + resp->Status = __constant_cpu_to_le32(RNDIS_STATUS_SUCCESS); + + if (rndis_per_dev_params[configNr].ack) + rndis_per_dev_params[configNr].ack( + rndis_per_dev_params[configNr].dev); + + return 0; +} + +static int rndis_reset_response(int configNr, rndis_reset_msg_type *buf) +{ + rndis_reset_cmplt_type *resp; + rndis_resp_t *r; + + r = rndis_add_response(configNr, sizeof(rndis_reset_cmplt_type)); + if (!r) + return -ENOMEM; + resp = (rndis_reset_cmplt_type *) r->buf; + + resp->MessageType = __constant_cpu_to_le32(REMOTE_NDIS_RESET_CMPLT); + resp->MessageLength = __constant_cpu_to_le32(16); + resp->Status = __constant_cpu_to_le32(RNDIS_STATUS_SUCCESS); + /* resent information */ + resp->AddressingReset = __constant_cpu_to_le32(1); + + if (rndis_per_dev_params[configNr].ack) + rndis_per_dev_params[configNr].ack( + rndis_per_dev_params[configNr].dev); + + return 0; +} + +static int rndis_keepalive_response(int configNr, + rndis_keepalive_msg_type *buf) +{ + rndis_keepalive_cmplt_type *resp; + rndis_resp_t *r; + + /* host "should" check only in RNDIS_DATA_INITIALIZED state */ + + r = rndis_add_response(configNr, sizeof(rndis_keepalive_cmplt_type)); + if (!r) + return -ENOMEM; + resp = (rndis_keepalive_cmplt_type *) r->buf; + + resp->MessageType = __constant_cpu_to_le32( + REMOTE_NDIS_KEEPALIVE_CMPLT); + resp->MessageLength = __constant_cpu_to_le32(16); + resp->RequestID = get_unaligned(&buf->RequestID); /* Still LE in msg buffer */ + resp->Status = __constant_cpu_to_le32(RNDIS_STATUS_SUCCESS); + + if (rndis_per_dev_params[configNr].ack) + rndis_per_dev_params[configNr].ack( + rndis_per_dev_params[configNr].dev); + + return 0; +} + + +/* + * Device to Host Comunication + */ +static int rndis_indicate_status_msg(int configNr, u32 status) +{ + rndis_indicate_status_msg_type *resp; + rndis_resp_t *r; + + if (rndis_per_dev_params[configNr].state == RNDIS_UNINITIALIZED) + return -ENOTSUPP; + + r = rndis_add_response(configNr, + sizeof(rndis_indicate_status_msg_type)); + if (!r) + return -ENOMEM; + resp = (rndis_indicate_status_msg_type *) r->buf; + + resp->MessageType = __constant_cpu_to_le32( + REMOTE_NDIS_INDICATE_STATUS_MSG); + resp->MessageLength = __constant_cpu_to_le32(20); + resp->Status = cpu_to_le32(status); + resp->StatusBufferLength = __constant_cpu_to_le32(0); + resp->StatusBufferOffset = __constant_cpu_to_le32(0); + + if (rndis_per_dev_params[configNr].ack) + rndis_per_dev_params[configNr].ack( + rndis_per_dev_params[configNr].dev); + return 0; +} + +int rndis_signal_connect(int configNr) +{ + rndis_per_dev_params[configNr].media_state + = NDIS_MEDIA_STATE_CONNECTED; + return rndis_indicate_status_msg(configNr, + RNDIS_STATUS_MEDIA_CONNECT); +} + +int rndis_signal_disconnect(int configNr) +{ + rndis_per_dev_params[configNr].media_state + = NDIS_MEDIA_STATE_DISCONNECTED; + +#ifdef RNDIS_COMPLETE_SIGNAL_DISCONNECT + return rndis_indicate_status_msg(configNr, + RNDIS_STATUS_MEDIA_DISCONNECT); +#else + return 0; +#endif +} + +void rndis_uninit(int configNr) +{ + u8 *buf; + u32 length; + + if (configNr >= RNDIS_MAX_CONFIGS) + return; + rndis_per_dev_params[configNr].used = 0; + rndis_per_dev_params[configNr].state = RNDIS_UNINITIALIZED; + + /* drain the response queue */ + while ((buf = rndis_get_next_response(configNr, &length))) + rndis_free_response(configNr, buf); +} + +void rndis_set_host_mac(int configNr, const u8 *addr) +{ + rndis_per_dev_params[configNr].host_mac = addr; +} + +enum rndis_state rndis_get_state(int configNr) +{ + if (configNr >= RNDIS_MAX_CONFIGS || configNr < 0) + return -ENOTSUPP; + return rndis_per_dev_params[configNr].state; +} + +/* + * Message Parser + */ +int rndis_msg_parser(u8 configNr, u8 *buf) +{ + u32 MsgType, MsgLength; + __le32 *tmp; + struct rndis_params *params; + + debug("%s: configNr = %d, %p\n", __func__, configNr, buf); + + if (!buf) + return -ENOMEM; + + tmp = (__le32 *) buf; + MsgType = get_unaligned_le32(tmp++); + MsgLength = get_unaligned_le32(tmp++); + + if (configNr >= RNDIS_MAX_CONFIGS) + return -ENOTSUPP; + params = &rndis_per_dev_params[configNr]; + + /* + * NOTE: RNDIS is *EXTREMELY* chatty ... Windows constantly polls for + * rx/tx statistics and link status, in addition to KEEPALIVE traffic + * and normal HC level polling to see if there's any IN traffic. + */ + + /* For USB: responses may take up to 10 seconds */ + switch (MsgType) { + case REMOTE_NDIS_INITIALIZE_MSG: + debug("%s: REMOTE_NDIS_INITIALIZE_MSG\n", __func__); + params->state = RNDIS_INITIALIZED; + return rndis_init_response(configNr, + (rndis_init_msg_type *) buf); + + case REMOTE_NDIS_HALT_MSG: + debug("%s: REMOTE_NDIS_HALT_MSG\n", __func__); + params->state = RNDIS_UNINITIALIZED; + return 0; + + case REMOTE_NDIS_QUERY_MSG: + return rndis_query_response(configNr, + (rndis_query_msg_type *) buf); + + case REMOTE_NDIS_SET_MSG: + return rndis_set_response(configNr, + (rndis_set_msg_type *) buf); + + case REMOTE_NDIS_RESET_MSG: + debug("%s: REMOTE_NDIS_RESET_MSG\n", __func__); + return rndis_reset_response(configNr, + (rndis_reset_msg_type *) buf); + + case REMOTE_NDIS_KEEPALIVE_MSG: + /* For USB: host does this every 5 seconds */ +#if defined(DEBUG) && defined(DEBUG_VERBOSE) + debug("%s: REMOTE_NDIS_KEEPALIVE_MSG\n", __func__); +#endif + return rndis_keepalive_response(configNr, + (rndis_keepalive_msg_type *) buf); + + default: + /* + * At least Windows XP emits some undefined RNDIS messages. + * In one case those messages seemed to relate to the host + * suspending itself. + */ + debug("%s: unknown RNDIS message 0x%08X len %d\n", + __func__ , MsgType, MsgLength); + { + unsigned i; + for (i = 0; i < MsgLength; i += 16) { + debug("%03d: " + " %02x %02x %02x %02x" + " %02x %02x %02x %02x" + " %02x %02x %02x %02x" + " %02x %02x %02x %02x" + "\n", + i, + buf[i], buf[i+1], + buf[i+2], buf[i+3], + buf[i+4], buf[i+5], + buf[i+6], buf[i+7], + buf[i+8], buf[i+9], + buf[i+10], buf[i+11], + buf[i+12], buf[i+13], + buf[i+14], buf[i+15]); + } + } + break; + } + + return -ENOTSUPP; +} + +#ifndef CONFIG_DM_ETH +int rndis_register(int (*rndis_control_ack)(struct eth_device *)) +#else +int rndis_register(int (*rndis_control_ack)(struct udevice *)) +#endif +{ + u8 i; + + for (i = 0; i < RNDIS_MAX_CONFIGS; i++) { + if (!rndis_per_dev_params[i].used) { + rndis_per_dev_params[i].used = 1; + rndis_per_dev_params[i].ack = rndis_control_ack; + debug("%s: configNr = %d\n", __func__, i); + return i; + } + } + debug("%s failed\n", __func__); + + return -1; +} + +void rndis_deregister(int configNr) +{ + debug("%s: configNr = %d\n", __func__, configNr); + + if (configNr >= RNDIS_MAX_CONFIGS) + return; + rndis_per_dev_params[configNr].used = 0; + + return; +} + +#ifndef CONFIG_DM_ETH +int rndis_set_param_dev(u8 configNr, struct eth_device *dev, int mtu, + struct net_device_stats *stats, u16 *cdc_filter) +#else +int rndis_set_param_dev(u8 configNr, struct udevice *dev, int mtu, + struct net_device_stats *stats, u16 *cdc_filter) +#endif +{ + debug("%s: configNr = %d\n", __func__, configNr); + if (!dev || !stats) + return -1; + if (configNr >= RNDIS_MAX_CONFIGS) + return -1; + + rndis_per_dev_params[configNr].dev = dev; + rndis_per_dev_params[configNr].stats = stats; + rndis_per_dev_params[configNr].mtu = mtu; + rndis_per_dev_params[configNr].filter = cdc_filter; + + return 0; +} + +int rndis_set_param_vendor(u8 configNr, u32 vendorID, const char *vendorDescr) +{ + debug("%s: configNr = %d\n", __func__, configNr); + if (!vendorDescr) + return -1; + if (configNr >= RNDIS_MAX_CONFIGS) + return -1; + + rndis_per_dev_params[configNr].vendorID = vendorID; + rndis_per_dev_params[configNr].vendorDescr = vendorDescr; + + return 0; +} + +int rndis_set_param_medium(u8 configNr, u32 medium, u32 speed) +{ + debug("%s: configNr = %d, %u %u\n", __func__, configNr, medium, speed); + if (configNr >= RNDIS_MAX_CONFIGS) + return -1; + + rndis_per_dev_params[configNr].medium = medium; + rndis_per_dev_params[configNr].speed = speed; + + return 0; +} + +void rndis_add_hdr(void *buf, int length) +{ + struct rndis_packet_msg_type *header; + + header = buf; + memset(header, 0, sizeof *header); + header->MessageType = __constant_cpu_to_le32(REMOTE_NDIS_PACKET_MSG); + header->MessageLength = cpu_to_le32(length + sizeof *header); + header->DataOffset = __constant_cpu_to_le32(36); + header->DataLength = cpu_to_le32(length); +} + +void rndis_free_response(int configNr, u8 *buf) +{ + rndis_resp_t *r; + struct list_head *act, *tmp; + + list_for_each_safe(act, tmp, + &(rndis_per_dev_params[configNr].resp_queue)) + { + r = list_entry(act, rndis_resp_t, list); + if (r && r->buf == buf) { + list_del(&r->list); + free(r); + } + } +} + +u8 *rndis_get_next_response(int configNr, u32 *length) +{ + rndis_resp_t *r; + struct list_head *act, *tmp; + + if (!length) + return NULL; + + list_for_each_safe(act, tmp, + &(rndis_per_dev_params[configNr].resp_queue)) + { + r = list_entry(act, rndis_resp_t, list); + if (!r->send) { + r->send = 1; + *length = r->length; + return r->buf; + } + } + + return NULL; +} + +static rndis_resp_t *rndis_add_response(int configNr, u32 length) +{ + rndis_resp_t *r; + + /* NOTE: this gets copied into ether.c USB_BUFSIZ bytes ... */ + r = malloc(sizeof(rndis_resp_t) + length); + if (!r) + return NULL; + + r->buf = (u8 *) (r + 1); + r->length = length; + r->send = 0; + + list_add_tail(&r->list, + &(rndis_per_dev_params[configNr].resp_queue)); + return r; +} + +int rndis_rm_hdr(void *buf, int length) +{ + /* tmp points to a struct rndis_packet_msg_type */ + __le32 *tmp = buf; + int offs, len; + + /* MessageType, MessageLength */ + if (__constant_cpu_to_le32(REMOTE_NDIS_PACKET_MSG) + != get_unaligned(tmp++)) + return -EINVAL; + tmp++; + + /* DataOffset, DataLength */ + offs = get_unaligned_le32(tmp++) + 8 /* offset of DataOffset */; + if (offs != sizeof(struct rndis_packet_msg_type)) + debug("%s: unexpected DataOffset: %d\n", __func__, offs); + if (offs >= length) + return -EOVERFLOW; + + len = get_unaligned_le32(tmp++); + if (len + sizeof(struct rndis_packet_msg_type) != length) + debug("%s: unexpected DataLength: %d, packet length=%d\n", + __func__, len, length); + + memmove(buf, buf + offs, len); + + return offs; +} + +int rndis_init(void) +{ + u8 i; + + for (i = 0; i < RNDIS_MAX_CONFIGS; i++) { + rndis_per_dev_params[i].confignr = i; + rndis_per_dev_params[i].used = 0; + rndis_per_dev_params[i].state = RNDIS_UNINITIALIZED; + rndis_per_dev_params[i].media_state + = NDIS_MEDIA_STATE_DISCONNECTED; + INIT_LIST_HEAD(&(rndis_per_dev_params[i].resp_queue)); + } + + return 0; +} + +void rndis_exit(void) +{ + /* Nothing to do */ +} diff --git a/roms/u-boot/drivers/usb/gadget/rndis.h b/roms/u-boot/drivers/usb/gadget/rndis.h new file mode 100644 index 000000000..e827af0be --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/rndis.h @@ -0,0 +1,268 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * RNDIS Definitions for Remote NDIS + * + * Authors: Benedikt Spranger, Pengutronix + * Robert Schwebel, Pengutronix + * + * This software was originally developed in conformance with + * Microsoft's Remote NDIS Specification License Agreement. + */ + +#ifndef _USBGADGET_RNDIS_H +#define _USBGADGET_RNDIS_H + +#include "ndis.h" + +/* + * By default rndis_signal_disconnect does not send status message about + * RNDIS disconnection to USB host (indicated as cable disconnected). + * Define RNDIS_COMPLETE_SIGNAL_DISCONNECT to send it. + * However, this will cause 1 sec delay on Ethernet device halt. + * Usually you do not need to define it. Mostly usable for debugging. + */ + +#define RNDIS_MAXIMUM_FRAME_SIZE 1518 +#define RNDIS_MAX_TOTAL_SIZE 1558 + +/* Remote NDIS Versions */ +#define RNDIS_MAJOR_VERSION 1 +#define RNDIS_MINOR_VERSION 0 + +/* Status Values */ +#define RNDIS_STATUS_SUCCESS 0x00000000U /* Success */ +#define RNDIS_STATUS_FAILURE 0xC0000001U /* Unspecified error */ +#define RNDIS_STATUS_INVALID_DATA 0xC0010015U /* Invalid data */ +#define RNDIS_STATUS_NOT_SUPPORTED 0xC00000BBU /* Unsupported request */ +#define RNDIS_STATUS_MEDIA_CONNECT 0x4001000BU /* Device connected */ +#define RNDIS_STATUS_MEDIA_DISCONNECT 0x4001000CU /* Device disconnected */ +/* + * For all not specified status messages: + * RNDIS_STATUS_Xxx -> NDIS_STATUS_Xxx + */ + +/* Message Set for Connectionless (802.3) Devices */ +#define REMOTE_NDIS_PACKET_MSG 0x00000001U +#define REMOTE_NDIS_INITIALIZE_MSG 0x00000002U /* Initialize device */ +#define REMOTE_NDIS_HALT_MSG 0x00000003U +#define REMOTE_NDIS_QUERY_MSG 0x00000004U +#define REMOTE_NDIS_SET_MSG 0x00000005U +#define REMOTE_NDIS_RESET_MSG 0x00000006U +#define REMOTE_NDIS_INDICATE_STATUS_MSG 0x00000007U +#define REMOTE_NDIS_KEEPALIVE_MSG 0x00000008U + +/* Message completion */ +#define REMOTE_NDIS_INITIALIZE_CMPLT 0x80000002U +#define REMOTE_NDIS_QUERY_CMPLT 0x80000004U +#define REMOTE_NDIS_SET_CMPLT 0x80000005U +#define REMOTE_NDIS_RESET_CMPLT 0x80000006U +#define REMOTE_NDIS_KEEPALIVE_CMPLT 0x80000008U + +/* Device Flags */ +#define RNDIS_DF_CONNECTIONLESS 0x00000001U +#define RNDIS_DF_CONNECTION_ORIENTED 0x00000002U + +#define RNDIS_MEDIUM_802_3 0x00000000U + +/* from drivers/net/sk98lin/h/skgepnmi.h */ +#define OID_PNP_CAPABILITIES 0xFD010100 +#define OID_PNP_SET_POWER 0xFD010101 +#define OID_PNP_QUERY_POWER 0xFD010102 +#define OID_PNP_ADD_WAKE_UP_PATTERN 0xFD010103 +#define OID_PNP_REMOVE_WAKE_UP_PATTERN 0xFD010104 +#define OID_PNP_ENABLE_WAKE_UP 0xFD010106 + + +typedef struct rndis_init_msg_type { + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 MajorVersion; + __le32 MinorVersion; + __le32 MaxTransferSize; +} rndis_init_msg_type; + +typedef struct rndis_init_cmplt_type { + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 Status; + __le32 MajorVersion; + __le32 MinorVersion; + __le32 DeviceFlags; + __le32 Medium; + __le32 MaxPacketsPerTransfer; + __le32 MaxTransferSize; + __le32 PacketAlignmentFactor; + __le32 AFListOffset; + __le32 AFListSize; +} rndis_init_cmplt_type; + +typedef struct rndis_halt_msg_type { + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; +} rndis_halt_msg_type; + +typedef struct rndis_query_msg_type { + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 OID; + __le32 InformationBufferLength; + __le32 InformationBufferOffset; + __le32 DeviceVcHandle; +} rndis_query_msg_type; + +typedef struct rndis_query_cmplt_type { + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 Status; + __le32 InformationBufferLength; + __le32 InformationBufferOffset; +} rndis_query_cmplt_type; + +typedef struct rndis_set_msg_type { + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 OID; + __le32 InformationBufferLength; + __le32 InformationBufferOffset; + __le32 DeviceVcHandle; +} rndis_set_msg_type; + +typedef struct rndis_set_cmplt_type { + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 Status; +} rndis_set_cmplt_type; + +typedef struct rndis_reset_msg_type { + __le32 MessageType; + __le32 MessageLength; + __le32 Reserved; +} rndis_reset_msg_type; + +typedef struct rndis_reset_cmplt_type { + __le32 MessageType; + __le32 MessageLength; + __le32 Status; + __le32 AddressingReset; +} rndis_reset_cmplt_type; + +typedef struct rndis_indicate_status_msg_type { + __le32 MessageType; + __le32 MessageLength; + __le32 Status; + __le32 StatusBufferLength; + __le32 StatusBufferOffset; +} rndis_indicate_status_msg_type; + +typedef struct rndis_keepalive_msg_type { + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; +} rndis_keepalive_msg_type; + +typedef struct rndis_keepalive_cmplt_type { + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 Status; +} rndis_keepalive_cmplt_type; + +struct rndis_packet_msg_type { + __le32 MessageType; + __le32 MessageLength; + __le32 DataOffset; + __le32 DataLength; + __le32 OOBDataOffset; + __le32 OOBDataLength; + __le32 NumOOBDataElements; + __le32 PerPacketInfoOffset; + __le32 PerPacketInfoLength; + __le32 VcHandle; + __le32 Reserved; +} __attribute__ ((packed)); + +struct rndis_config_parameter { + __le32 ParameterNameOffset; + __le32 ParameterNameLength; + __le32 ParameterType; + __le32 ParameterValueOffset; + __le32 ParameterValueLength; +}; + +/* implementation specific */ +enum rndis_state { + RNDIS_UNINITIALIZED, + RNDIS_INITIALIZED, + RNDIS_DATA_INITIALIZED, +}; + +typedef struct rndis_resp_t { + struct list_head list; + u8 *buf; + u32 length; + int send; +} rndis_resp_t; + +typedef struct rndis_params { + u8 confignr; + u8 used; + u16 saved_filter; + enum rndis_state state; + u32 medium; + u32 speed; + u32 media_state; + + const u8 *host_mac; + u16 *filter; + struct net_device_stats *stats; + int mtu; + + u32 vendorID; + const char *vendorDescr; +#ifndef CONFIG_DM_ETH + struct eth_device *dev; + int (*ack)(struct eth_device *); +#else + struct udevice *dev; + int (*ack)(struct udevice *); +#endif + struct list_head resp_queue; +} rndis_params; + +/* RNDIS Message parser and other useless functions */ +int rndis_msg_parser(u8 configNr, u8 *buf); +enum rndis_state rndis_get_state(int configNr); +void rndis_deregister(int configNr); +#ifndef CONFIG_DM_ETH +int rndis_register(int (*rndis_control_ack)(struct eth_device *)); +int rndis_set_param_dev(u8 configNr, struct eth_device *dev, int mtu, + struct net_device_stats *stats, u16 *cdc_filter); +#else +int rndis_register(int (*rndis_control_ack)(struct udevice *)); +int rndis_set_param_dev(u8 configNr, struct udevice *dev, int mtu, + struct net_device_stats *stats, u16 *cdc_filter); +#endif +int rndis_set_param_vendor(u8 configNr, u32 vendorID, + const char *vendorDescr); +int rndis_set_param_medium(u8 configNr, u32 medium, u32 speed); +void rndis_add_hdr(void *bug, int length); +int rndis_rm_hdr(void *bug, int length); +u8 *rndis_get_next_response(int configNr, u32 *length); +void rndis_free_response(int configNr, u8 *buf); + +void rndis_uninit(int configNr); +int rndis_signal_connect(int configNr); +int rndis_signal_disconnect(int configNr); +extern void rndis_set_host_mac(int configNr, const u8 *addr); + +int rndis_init(void); +void rndis_exit(void); + +#endif /* _USBGADGET_RNDIS_H */ diff --git a/roms/u-boot/drivers/usb/gadget/storage_common.c b/roms/u-boot/drivers/usb/gadget/storage_common.c new file mode 100644 index 000000000..5674e8fe4 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/storage_common.c @@ -0,0 +1,617 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * storage_common.c -- Common definitions for mass storage functionality + * + * Copyright (C) 2003-2008 Alan Stern + * Copyeight (C) 2009 Samsung Electronics + * Author: Michal Nazarewicz (m.nazarewicz@samsung.com) + * + * Ported to u-boot: + * Andrzej Pietrasiewicz <andrzej.p@samsung.com> + * + * Code refactoring & cleanup: + * Łukasz Majewski <l.majewski@samsung.com> + */ + + +/* + * This file requires the following identifiers used in USB strings to + * be defined (each of type pointer to char): + * - fsg_string_manufacturer -- name of the manufacturer + * - fsg_string_product -- name of the product + * - fsg_string_serial -- product's serial + * - fsg_string_config -- name of the configuration + * - fsg_string_interface -- name of the interface + * The first four are only needed when FSG_DESCRIPTORS_DEVICE_STRINGS + * macro is defined prior to including this file. + */ + +/* + * When FSG_NO_INTR_EP is defined fsg_fs_intr_in_desc and + * fsg_hs_intr_in_desc objects as well as + * FSG_FS_FUNCTION_PRE_EP_ENTRIES and FSG_HS_FUNCTION_PRE_EP_ENTRIES + * macros are not defined. + * + * When FSG_NO_DEVICE_STRINGS is defined FSG_STRING_MANUFACTURER, + * FSG_STRING_PRODUCT, FSG_STRING_SERIAL and FSG_STRING_CONFIG are not + * defined (as well as corresponding entries in string tables are + * missing) and FSG_STRING_INTERFACE has value of zero. + * + * When FSG_NO_OTG is defined fsg_otg_desc won't be defined. + */ + +/* + * When FSG_BUFFHD_STATIC_BUFFER is defined when this file is included + * the fsg_buffhd structure's buf field will be an array of FSG_BUFLEN + * characters rather then a pointer to void. + */ + + +/* #include <asm/unaligned.h> */ + + +/* + * Thanks to NetChip Technologies for donating this product ID. + * + * DO NOT REUSE THESE IDs with any other driver!! Ever!! + * Instead: allocate your own, using normal USB-IF procedures. + */ +#define FSG_VENDOR_ID 0x0525 /* NetChip */ +#define FSG_PRODUCT_ID 0xa4a5 /* Linux-USB File-backed Storage Gadget */ + +/*-------------------------------------------------------------------------*/ + +#ifndef DEBUG +#undef VERBOSE_DEBUG +#undef DUMP_MSGS +#endif /* !DEBUG */ + +#ifdef VERBOSE_DEBUG +#define VLDBG LDBG +#else +#define VLDBG(lun, fmt, args...) do { } while (0) +#endif /* VERBOSE_DEBUG */ + +/* +#define LDBG(lun, fmt, args...) dev_dbg (&(lun)->dev, fmt, ## args) +#define LERROR(lun, fmt, args...) dev_err (&(lun)->dev, fmt, ## args) +#define LWARN(lun, fmt, args...) dev_warn(&(lun)->dev, fmt, ## args) +#define LINFO(lun, fmt, args...) dev_info(&(lun)->dev, fmt, ## args) +*/ + +#define LDBG(lun, fmt, args...) do { } while (0) +#define LERROR(lun, fmt, args...) do { } while (0) +#define LWARN(lun, fmt, args...) do { } while (0) +#define LINFO(lun, fmt, args...) do { } while (0) + +/* + * Keep those macros in sync with those in + * include/linux/usb/composite.h or else GCC will complain. If they + * are identical (the same names of arguments, white spaces in the + * same places) GCC will allow redefinition otherwise (even if some + * white space is removed or added) warning will be issued. + * + * Those macros are needed here because File Storage Gadget does not + * include the composite.h header. For composite gadgets those macros + * are redundant since composite.h is included any way. + * + * One could check whether those macros are already defined (which + * would indicate composite.h had been included) or not (which would + * indicate we were in FSG) but this is not done because a warning is + * desired if definitions here differ from the ones in composite.h. + * + * We want the definitions to match and be the same in File Storage + * Gadget as well as Mass Storage Function (and so composite gadgets + * using MSF). If someone changes them in composite.h it will produce + * a warning in this file when building MSF. + */ + +#define DBG(d, fmt, args...) debug(fmt , ## args) +#define VDBG(d, fmt, args...) debug(fmt , ## args) +/* #define ERROR(d, fmt, args...) printf(fmt , ## args) */ +/* #define WARNING(d, fmt, args...) printf(fmt , ## args) */ +/* #define INFO(d, fmt, args...) printf(fmt , ## args) */ + +/* #define DBG(d, fmt, args...) do { } while (0) */ +/* #define VDBG(d, fmt, args...) do { } while (0) */ +#define ERROR(d, fmt, args...) do { } while (0) +#define WARNING(d, fmt, args...) do { } while (0) +#define INFO(d, fmt, args...) do { } while (0) + +#ifdef DUMP_MSGS + +/* dump_msg(fsg, const char * label, const u8 * buf, unsigned length); */ +# define dump_msg(fsg, label, buf, length) do { \ + if (length < 512) { \ + DBG(fsg, "%s, length %u:\n", label, length); \ + print_hex_dump("", DUMP_PREFIX_OFFSET, \ + 16, 1, buf, length, 0); \ + } \ +} while (0) + +# define dump_cdb(fsg) do { } while (0) + +#else + +# define dump_msg(fsg, /* const char * */ label, \ + /* const u8 * */ buf, /* unsigned */ length) do { } while (0) + +# ifdef VERBOSE_DEBUG + +# define dump_cdb(fsg) \ + print_hex_dump("SCSI CDB: ", DUMP_PREFIX_NONE, \ + 16, 1, (fsg)->cmnd, (fsg)->cmnd_size, 0) \ + +# else + +# define dump_cdb(fsg) do { } while (0) + +# endif /* VERBOSE_DEBUG */ + +#endif /* DUMP_MSGS */ + +/*-------------------------------------------------------------------------*/ + +/* SCSI device types */ +#define TYPE_DISK 0x00 +#define TYPE_CDROM 0x05 + +/* USB protocol value = the transport method */ +#define USB_PR_CBI 0x00 /* Control/Bulk/Interrupt */ +#define USB_PR_CB 0x01 /* Control/Bulk w/o interrupt */ +#define USB_PR_BULK 0x50 /* Bulk-only */ + +/* USB subclass value = the protocol encapsulation */ +#define USB_SC_RBC 0x01 /* Reduced Block Commands (flash) */ +#define USB_SC_8020 0x02 /* SFF-8020i, MMC-2, ATAPI (CD-ROM) */ +#define USB_SC_QIC 0x03 /* QIC-157 (tape) */ +#define USB_SC_UFI 0x04 /* UFI (floppy) */ +#define USB_SC_8070 0x05 /* SFF-8070i (removable) */ +#define USB_SC_SCSI 0x06 /* Transparent SCSI */ + +/* Bulk-only data structures */ + +/* Command Block Wrapper */ +struct fsg_bulk_cb_wrap { + __le32 Signature; /* Contains 'USBC' */ + u32 Tag; /* Unique per command id */ + __le32 DataTransferLength; /* Size of the data */ + u8 Flags; /* Direction in bit 7 */ + u8 Lun; /* LUN (normally 0) */ + u8 Length; /* Of the CDB, <= MAX_COMMAND_SIZE */ + u8 CDB[16]; /* Command Data Block */ +}; + +#define USB_BULK_CB_WRAP_LEN 31 +#define USB_BULK_CB_SIG 0x43425355 /* Spells out USBC */ +#define USB_BULK_IN_FLAG 0x80 + +/* Command Status Wrapper */ +struct bulk_cs_wrap { + __le32 Signature; /* Should = 'USBS' */ + u32 Tag; /* Same as original command */ + __le32 Residue; /* Amount not transferred */ + u8 Status; /* See below */ +}; + +#define USB_BULK_CS_WRAP_LEN 13 +#define USB_BULK_CS_SIG 0x53425355 /* Spells out 'USBS' */ +#define USB_STATUS_PASS 0 +#define USB_STATUS_FAIL 1 +#define USB_STATUS_PHASE_ERROR 2 + +/* Bulk-only class specific requests */ +#define USB_BULK_RESET_REQUEST 0xff +#define USB_BULK_GET_MAX_LUN_REQUEST 0xfe + +/* CBI Interrupt data structure */ +struct interrupt_data { + u8 bType; + u8 bValue; +}; + +#define CBI_INTERRUPT_DATA_LEN 2 + +/* CBI Accept Device-Specific Command request */ +#define USB_CBI_ADSC_REQUEST 0x00 + +/* Length of a SCSI Command Data Block */ +#define MAX_COMMAND_SIZE 16 + +/* SCSI commands that we recognize */ +#define SC_FORMAT_UNIT 0x04 +#define SC_INQUIRY 0x12 +#define SC_MODE_SELECT_6 0x15 +#define SC_MODE_SELECT_10 0x55 +#define SC_MODE_SENSE_6 0x1a +#define SC_MODE_SENSE_10 0x5a +#define SC_PREVENT_ALLOW_MEDIUM_REMOVAL 0x1e +#define SC_READ_6 0x08 +#define SC_READ_10 0x28 +#define SC_READ_12 0xa8 +#define SC_READ_CAPACITY 0x25 +#define SC_READ_FORMAT_CAPACITIES 0x23 +#define SC_READ_HEADER 0x44 +#define SC_READ_TOC 0x43 +#define SC_RELEASE 0x17 +#define SC_REQUEST_SENSE 0x03 +#define SC_RESERVE 0x16 +#define SC_SEND_DIAGNOSTIC 0x1d +#define SC_START_STOP_UNIT 0x1b +#define SC_SYNCHRONIZE_CACHE 0x35 +#define SC_TEST_UNIT_READY 0x00 +#define SC_VERIFY 0x2f +#define SC_WRITE_6 0x0a +#define SC_WRITE_10 0x2a +#define SC_WRITE_12 0xaa + +/* SCSI Sense Key/Additional Sense Code/ASC Qualifier values */ +#define SS_NO_SENSE 0 +#define SS_COMMUNICATION_FAILURE 0x040800 +#define SS_INVALID_COMMAND 0x052000 +#define SS_INVALID_FIELD_IN_CDB 0x052400 +#define SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE 0x052100 +#define SS_LOGICAL_UNIT_NOT_SUPPORTED 0x052500 +#define SS_MEDIUM_NOT_PRESENT 0x023a00 +#define SS_MEDIUM_REMOVAL_PREVENTED 0x055302 +#define SS_NOT_READY_TO_READY_TRANSITION 0x062800 +#define SS_RESET_OCCURRED 0x062900 +#define SS_SAVING_PARAMETERS_NOT_SUPPORTED 0x053900 +#define SS_UNRECOVERED_READ_ERROR 0x031100 +#define SS_WRITE_ERROR 0x030c02 +#define SS_WRITE_PROTECTED 0x072700 + +#define SK(x) ((u8) ((x) >> 16)) /* Sense Key byte, etc. */ +#define ASC(x) ((u8) ((x) >> 8)) +#define ASCQ(x) ((u8) (x)) + +struct device_attribute { int i; }; +#define ETOOSMALL 525 + +#include <log.h> +#include <usb_mass_storage.h> +#include <dm/device_compat.h> + +/*-------------------------------------------------------------------------*/ + +struct fsg_lun { + loff_t file_length; + loff_t num_sectors; + + unsigned int initially_ro:1; + unsigned int ro:1; + unsigned int removable:1; + unsigned int cdrom:1; + unsigned int prevent_medium_removal:1; + unsigned int registered:1; + unsigned int info_valid:1; + unsigned int nofua:1; + + u32 sense_data; + u32 sense_data_info; + u32 unit_attention_data; + + struct device dev; +}; + +#define fsg_lun_is_open(curlun) ((curlun)->filp != NULL) +#if 0 +static struct fsg_lun *fsg_lun_from_dev(struct device *dev) +{ + return container_of(dev, struct fsg_lun, dev); +} +#endif + +/* Big enough to hold our biggest descriptor */ +#define EP0_BUFSIZE 256 +#define DELAYED_STATUS (EP0_BUFSIZE + 999) /* An impossibly large value */ + +/* Number of buffers we will use. 2 is enough for double-buffering */ +#define FSG_NUM_BUFFERS 2 + +/* Default size of buffer length. */ +#define FSG_BUFLEN ((u32)131072) + +/* Maximal number of LUNs supported in mass storage function */ +#define FSG_MAX_LUNS 8 + +enum fsg_buffer_state { + BUF_STATE_EMPTY = 0, + BUF_STATE_FULL, + BUF_STATE_BUSY +}; + +struct fsg_buffhd { +#ifdef FSG_BUFFHD_STATIC_BUFFER + char buf[FSG_BUFLEN]; +#else + void *buf; +#endif + enum fsg_buffer_state state; + struct fsg_buffhd *next; + + /* + * The NetChip 2280 is faster, and handles some protocol faults + * better, if we don't submit any short bulk-out read requests. + * So we will record the intended request length here. + */ + unsigned int bulk_out_intended_length; + + struct usb_request *inreq; + int inreq_busy; + struct usb_request *outreq; + int outreq_busy; +}; + +enum fsg_state { + /* This one isn't used anywhere */ + FSG_STATE_COMMAND_PHASE = -10, + FSG_STATE_DATA_PHASE, + FSG_STATE_STATUS_PHASE, + + FSG_STATE_IDLE = 0, + FSG_STATE_ABORT_BULK_OUT, + FSG_STATE_RESET, + FSG_STATE_INTERFACE_CHANGE, + FSG_STATE_CONFIG_CHANGE, + FSG_STATE_DISCONNECT, + FSG_STATE_EXIT, + FSG_STATE_TERMINATED +}; + +enum data_direction { + DATA_DIR_UNKNOWN = 0, + DATA_DIR_FROM_HOST, + DATA_DIR_TO_HOST, + DATA_DIR_NONE +}; + +/*-------------------------------------------------------------------------*/ + +static inline u32 get_unaligned_be24(u8 *buf) +{ + return 0xffffff & (u32) get_unaligned_be32(buf - 1); +} + +/*-------------------------------------------------------------------------*/ + +enum { +#ifndef FSG_NO_DEVICE_STRINGS + FSG_STRING_MANUFACTURER = 1, + FSG_STRING_PRODUCT, + FSG_STRING_SERIAL, + FSG_STRING_CONFIG, +#endif + FSG_STRING_INTERFACE +}; + +#ifndef FSG_NO_OTG +static struct usb_otg_descriptor +fsg_otg_desc = { + .bLength = sizeof fsg_otg_desc, + .bDescriptorType = USB_DT_OTG, + + .bmAttributes = USB_OTG_SRP, +}; +#endif + +/* There is only one interface. */ + +static struct usb_interface_descriptor +fsg_intf_desc = { + .bLength = sizeof fsg_intf_desc, + .bDescriptorType = USB_DT_INTERFACE, + + .bNumEndpoints = 2, /* Adjusted during fsg_bind() */ + .bInterfaceClass = USB_CLASS_MASS_STORAGE, + .bInterfaceSubClass = USB_SC_SCSI, /* Adjusted during fsg_bind() */ + .bInterfaceProtocol = USB_PR_BULK, /* Adjusted during fsg_bind() */ + .iInterface = FSG_STRING_INTERFACE, +}; + +/* + * Three full-speed endpoint descriptors: bulk-in, bulk-out, and + * interrupt-in. + */ + +static struct usb_endpoint_descriptor +fsg_fs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + /* wMaxPacketSize set by autoconfiguration */ +}; + +static struct usb_endpoint_descriptor +fsg_fs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + /* wMaxPacketSize set by autoconfiguration */ +}; + +#ifndef FSG_NO_INTR_EP + +static struct usb_endpoint_descriptor +fsg_fs_intr_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(2), + .bInterval = 32, /* frames -> 32 ms */ +}; + +#ifndef FSG_NO_OTG +# define FSG_FS_FUNCTION_PRE_EP_ENTRIES 2 +#else +# define FSG_FS_FUNCTION_PRE_EP_ENTRIES 1 +#endif + +#endif + +static struct usb_descriptor_header *fsg_fs_function[] = { +#ifndef FSG_NO_OTG + (struct usb_descriptor_header *) &fsg_otg_desc, +#endif + (struct usb_descriptor_header *) &fsg_intf_desc, + (struct usb_descriptor_header *) &fsg_fs_bulk_in_desc, + (struct usb_descriptor_header *) &fsg_fs_bulk_out_desc, +#ifndef FSG_NO_INTR_EP + (struct usb_descriptor_header *) &fsg_fs_intr_in_desc, +#endif + NULL, +}; + +/* + * USB 2.0 devices need to expose both high speed and full speed + * descriptors, unless they only run at full speed. + * + * That means alternate endpoint descriptors (bigger packets) + * and a "device qualifier" ... plus more construction options + * for the configuration descriptor. + */ +static struct usb_endpoint_descriptor +fsg_hs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + /* bEndpointAddress copied from fs_bulk_in_desc during fsg_bind() */ + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor +fsg_hs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + /* bEndpointAddress copied from fs_bulk_out_desc during fsg_bind() */ + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), + .bInterval = 1, /* NAK every 1 uframe */ +}; + +#ifndef FSG_NO_INTR_EP + +static struct usb_endpoint_descriptor +fsg_hs_intr_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + /* bEndpointAddress copied from fs_intr_in_desc during fsg_bind() */ + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(2), + .bInterval = 9, /* 2**(9-1) = 256 uframes -> 32 ms */ +}; + +#ifndef FSG_NO_OTG +# define FSG_HS_FUNCTION_PRE_EP_ENTRIES 2 +#else +# define FSG_HS_FUNCTION_PRE_EP_ENTRIES 1 +#endif + +#endif + +static struct usb_descriptor_header *fsg_hs_function[] = { +#ifndef FSG_NO_OTG + (struct usb_descriptor_header *) &fsg_otg_desc, +#endif + (struct usb_descriptor_header *) &fsg_intf_desc, + (struct usb_descriptor_header *) &fsg_hs_bulk_in_desc, + (struct usb_descriptor_header *) &fsg_hs_bulk_out_desc, +#ifndef FSG_NO_INTR_EP + (struct usb_descriptor_header *) &fsg_hs_intr_in_desc, +#endif + NULL, +}; + +/* Maxpacket and other transfer characteristics vary by speed. */ +static struct usb_endpoint_descriptor * +fsg_ep_desc(struct usb_gadget *g, struct usb_endpoint_descriptor *fs, + struct usb_endpoint_descriptor *hs) +{ + if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) + return hs; + return fs; +} + +/* Static strings, in UTF-8 (for simplicity we use only ASCII characters) */ +static struct usb_string fsg_strings[] = { +#ifndef FSG_NO_DEVICE_STRINGS + {FSG_STRING_MANUFACTURER, fsg_string_manufacturer}, + {FSG_STRING_PRODUCT, fsg_string_product}, + {FSG_STRING_SERIAL, fsg_string_serial}, + {FSG_STRING_CONFIG, fsg_string_config}, +#endif + {FSG_STRING_INTERFACE, fsg_string_interface}, + {} +}; + +static struct usb_gadget_strings fsg_stringtab = { + .language = 0x0409, /* en-us */ + .strings = fsg_strings, +}; + +/*-------------------------------------------------------------------------*/ + +/* + * If the next two routines are called while the gadget is registered, + * the caller must own fsg->filesem for writing. + */ + +static int fsg_lun_open(struct fsg_lun *curlun, unsigned int num_sectors, + const char *filename) +{ + int ro; + + /* R/W if we can, R/O if we must */ + ro = curlun->initially_ro; + + curlun->ro = ro; + curlun->file_length = num_sectors << 9; + curlun->num_sectors = num_sectors; + debug("open backing file: %s\n", filename); + + return 0; +} + +static void fsg_lun_close(struct fsg_lun *curlun) +{ +} + +/*-------------------------------------------------------------------------*/ + +/* + * Sync the file data, don't bother with the metadata. + * This code was copied from fs/buffer.c:sys_fdatasync(). + */ +static int fsg_lun_fsync_sub(struct fsg_lun *curlun) +{ + return 0; +} + +static void store_cdrom_address(u8 *dest, int msf, u32 addr) +{ + if (msf) { + /* Convert to Minutes-Seconds-Frames */ + addr >>= 2; /* Convert to 2048-byte frames */ + addr += 2*75; /* Lead-in occupies 2 seconds */ + dest[3] = addr % 75; /* Frames */ + addr /= 75; + dest[2] = addr % 60; /* Seconds */ + addr /= 60; + dest[1] = addr; /* Minutes */ + dest[0] = 0; /* Reserved */ + } else { + /* Absolute sector */ + put_unaligned_be32(addr, dest); + } +} + +/*-------------------------------------------------------------------------*/ diff --git a/roms/u-boot/drivers/usb/gadget/u_os_desc.h b/roms/u-boot/drivers/usb/gadget/u_os_desc.h new file mode 100644 index 000000000..4dab4814a --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/u_os_desc.h @@ -0,0 +1,123 @@ +/* + * u_os_desc.h + * + * Utility definitions for "OS Descriptors" support + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __U_OS_DESC_H__ +#define __U_OS_DESC_H__ + +#include <linux/utf.h> + +#define USB_EXT_PROP_DW_SIZE 0 +#define USB_EXT_PROP_DW_PROPERTY_DATA_TYPE 4 +#define USB_EXT_PROP_W_PROPERTY_NAME_LENGTH 8 +#define USB_EXT_PROP_B_PROPERTY_NAME 10 +#define USB_EXT_PROP_DW_PROPERTY_DATA_LENGTH 10 +#define USB_EXT_PROP_B_PROPERTY_DATA 14 + +#define USB_EXT_PROP_RESERVED 0 +#define USB_EXT_PROP_UNICODE 1 +#define USB_EXT_PROP_UNICODE_ENV 2 +#define USB_EXT_PROP_BINARY 3 +#define USB_EXT_PROP_LE32 4 +#define USB_EXT_PROP_BE32 5 +#define USB_EXT_PROP_UNICODE_LINK 6 +#define USB_EXT_PROP_UNICODE_MULTI 7 + +static inline u8 *__usb_ext_prop_ptr(u8 *buf, size_t offset) +{ + return buf + offset; +} + +static inline u8 *usb_ext_prop_size_ptr(u8 *buf) +{ + return __usb_ext_prop_ptr(buf, USB_EXT_PROP_DW_SIZE); +} + +static inline u8 *usb_ext_prop_type_ptr(u8 *buf) +{ + return __usb_ext_prop_ptr(buf, USB_EXT_PROP_DW_PROPERTY_DATA_TYPE); +} + +static inline u8 *usb_ext_prop_name_len_ptr(u8 *buf) +{ + return __usb_ext_prop_ptr(buf, USB_EXT_PROP_W_PROPERTY_NAME_LENGTH); +} + +static inline u8 *usb_ext_prop_name_ptr(u8 *buf) +{ + return __usb_ext_prop_ptr(buf, USB_EXT_PROP_B_PROPERTY_NAME); +} + +static inline u8 *usb_ext_prop_data_len_ptr(u8 *buf, size_t off) +{ + return __usb_ext_prop_ptr(buf, + USB_EXT_PROP_DW_PROPERTY_DATA_LENGTH + off); +} + +static inline u8 *usb_ext_prop_data_ptr(u8 *buf, size_t off) +{ + return __usb_ext_prop_ptr(buf, USB_EXT_PROP_B_PROPERTY_DATA + off); +} + +static inline void usb_ext_prop_put_size(u8 *buf, int dw_size) +{ + put_unaligned_le32(dw_size, usb_ext_prop_size_ptr(buf)); +} + +static inline void usb_ext_prop_put_type(u8 *buf, int type) +{ + put_unaligned_le32(type, usb_ext_prop_type_ptr(buf)); +} + +static inline int usb_ext_prop_put_name(u8 *buf, const char *name, int pnl) +{ + int result; + + put_unaligned_le16(pnl, usb_ext_prop_name_len_ptr(buf)); + memset(usb_ext_prop_name_ptr(buf), 0, 2 * strlen(name)); + result = utf8_to_utf16le(name, (__le16 *)usb_ext_prop_name_ptr(buf), + strlen(name)); + if (result < 0) + return result; + + put_unaligned_le16(0, &buf[USB_EXT_PROP_B_PROPERTY_NAME + pnl - 2]); + + return pnl; +} + +static inline void usb_ext_prop_put_binary(u8 *buf, int pnl, const char *data, + int data_len) +{ + put_unaligned_le32(data_len, usb_ext_prop_data_len_ptr(buf, pnl)); + memcpy(usb_ext_prop_data_ptr(buf, pnl), data, data_len); +} + +static inline int usb_ext_prop_put_unicode(u8 *buf, int pnl, const char *string, + int data_len) +{ + int result; + put_unaligned_le32(data_len, usb_ext_prop_data_len_ptr(buf, pnl)); + memset(usb_ext_prop_data_ptr(buf, pnl), 0, 2 * (data_len >> 1)); + result = utf8_to_utf16le(string, (__le16 *) usb_ext_prop_data_ptr(buf, pnl), + data_len >> 1); + if (result < 0) + return result; + + put_unaligned_le16(0, + &buf[USB_EXT_PROP_B_PROPERTY_DATA + pnl + data_len - 2]); + + return data_len; +} + +#endif /* __U_OS_DESC_H__ */ diff --git a/roms/u-boot/drivers/usb/gadget/udc/Makefile b/roms/u-boot/drivers/usb/gadget/udc/Makefile new file mode 100644 index 000000000..95dbf0c82 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/udc/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# USB peripheral controller drivers + +ifndef CONFIG_$(SPL_)DM_USB_GADGET +obj-$(CONFIG_USB_DWC3_GADGET) += udc-core.o +endif + +obj-$(CONFIG_$(SPL_)DM_USB_GADGET) += udc-core.o +obj-$(CONFIG_$(SPL_)DM) += udc-uclass.o diff --git a/roms/u-boot/drivers/usb/gadget/udc/udc-core.c b/roms/u-boot/drivers/usb/gadget/udc/udc-core.c new file mode 100644 index 000000000..7f73926cb --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/udc/udc-core.c @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * udc-core.c - Core UDC Framework + * + * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Felipe Balbi <balbi@ti.com> + * + * Taken from Linux Kernel v3.19-rc1 (drivers/usb/gadget/udc-core.c) and ported + * to uboot. + * + * commit 02e8c96627 : usb: gadget: udc: core: prepend udc_attach_driver with + * usb_ + */ + +#include <dm/device_compat.h> +#include <dm/devres.h> +#include <linux/compat.h> +#include <malloc.h> +#include <asm/cache.h> +#include <linux/dma-mapping.h> +#include <common.h> +#include <dm.h> +#include <dm/device-internal.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + +/** + * struct usb_udc - describes one usb device controller + * @driver - the gadget driver pointer. For use by the class code + * @dev - the child device to the actual controller + * @gadget - the gadget. For use by the class code + * @list - for use by the udc class driver + * + * This represents the internal data structure which is used by the UDC-class + * to hold information about udc driver and gadget together. + */ +struct usb_udc { + struct usb_gadget_driver *driver; + struct usb_gadget *gadget; + struct device dev; + struct list_head list; +}; + +static struct class *udc_class; +static LIST_HEAD(udc_list); +DEFINE_MUTEX(udc_lock); + +/* ------------------------------------------------------------------------- */ + +int usb_gadget_map_request(struct usb_gadget *gadget, + struct usb_request *req, int is_in) +{ + if (req->length == 0) + return 0; + + req->dma = dma_map_single(req->buf, req->length, + is_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + + return 0; +} +EXPORT_SYMBOL_GPL(usb_gadget_map_request); + +void usb_gadget_unmap_request(struct usb_gadget *gadget, + struct usb_request *req, int is_in) +{ + if (req->length == 0) + return; + + dma_unmap_single(req->dma, req->length, + is_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE); +} +EXPORT_SYMBOL_GPL(usb_gadget_unmap_request); + +/* ------------------------------------------------------------------------- */ + +/** + * usb_gadget_giveback_request - give the request back to the gadget layer + * Context: in_interrupt() + * + * This is called by device controller drivers in order to return the + * completed request back to the gadget layer. + */ +void usb_gadget_giveback_request(struct usb_ep *ep, + struct usb_request *req) +{ + req->complete(ep, req); +} +EXPORT_SYMBOL_GPL(usb_gadget_giveback_request); + +/* ------------------------------------------------------------------------- */ + +void usb_gadget_set_state(struct usb_gadget *gadget, + enum usb_device_state state) +{ + gadget->state = state; +} +EXPORT_SYMBOL_GPL(usb_gadget_set_state); + +/* ------------------------------------------------------------------------- */ + +/** + * usb_gadget_udc_reset - notifies the udc core that bus reset occurs + * @gadget: The gadget which bus reset occurs + * @driver: The gadget driver we want to notify + * + * If the udc driver has bus reset handler, it needs to call this when the bus + * reset occurs, it notifies the gadget driver that the bus reset occurs as + * well as updates gadget state. + */ +void usb_gadget_udc_reset(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + driver->reset(gadget); + usb_gadget_set_state(gadget, USB_STATE_DEFAULT); +} +EXPORT_SYMBOL_GPL(usb_gadget_udc_reset); + +/** + * usb_gadget_udc_start - tells usb device controller to start up + * @udc: The UDC to be started + * + * This call is issued by the UDC Class driver when it's about + * to register a gadget driver to the device controller, before + * calling gadget driver's bind() method. + * + * It allows the controller to be powered off until strictly + * necessary to have it powered on. + * + * Returns zero on success, else negative errno. + */ +static inline int usb_gadget_udc_start(struct usb_udc *udc) +{ + return udc->gadget->ops->udc_start(udc->gadget, udc->driver); +} + +/** + * usb_gadget_udc_stop - tells usb device controller we don't need it anymore + * @gadget: The device we want to stop activity + * @driver: The driver to unbind from @gadget + * + * This call is issued by the UDC Class driver after calling + * gadget driver's unbind() method. + * + * The details are implementation specific, but it can go as + * far as powering off UDC completely and disable its data + * line pullups. + */ +static inline void usb_gadget_udc_stop(struct usb_udc *udc) +{ + udc->gadget->ops->udc_stop(udc->gadget); +} + +/** + * usb_udc_release - release the usb_udc struct + * @dev: the dev member within usb_udc + * + * This is called by driver's core in order to free memory once the last + * reference is released. + */ +static void usb_udc_release(struct device *dev) +{ + struct usb_udc *udc; + + udc = container_of(dev, struct usb_udc, dev); + kfree(udc); +} + +/** + * usb_add_gadget_udc_release - adds a new gadget to the udc class driver list + * @parent: the parent device to this udc. Usually the controller driver's + * device. + * @gadget: the gadget to be added to the list. + * @release: a gadget release function. + * + * Returns zero on success, negative errno otherwise. + */ +int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget, + void (*release)(struct device *dev)) +{ + struct usb_udc *udc; + int ret = -ENOMEM; + + udc = kzalloc(sizeof(*udc), GFP_KERNEL); + if (!udc) + goto err1; + + dev_set_name(&gadget->dev, "gadget"); + gadget->dev.parent = parent; + + udc->dev.release = usb_udc_release; + udc->dev.class = udc_class; + udc->dev.parent = parent; + + udc->gadget = gadget; + + mutex_lock(&udc_lock); + list_add_tail(&udc->list, &udc_list); + + usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED); + + mutex_unlock(&udc_lock); + + return 0; + +err1: + return ret; +} +EXPORT_SYMBOL_GPL(usb_add_gadget_udc_release); + +/** + * usb_add_gadget_udc - adds a new gadget to the udc class driver list + * @parent: the parent device to this udc. Usually the controller + * driver's device. + * @gadget: the gadget to be added to the list + * + * Returns zero on success, negative errno otherwise. + */ +int usb_add_gadget_udc(struct device *parent, struct usb_gadget *gadget) +{ + return usb_add_gadget_udc_release(parent, gadget, NULL); +} +EXPORT_SYMBOL_GPL(usb_add_gadget_udc); + +static void usb_gadget_remove_driver(struct usb_udc *udc) +{ + dev_dbg(&udc->dev, "unregistering UDC driver [%s]\n", + udc->driver->function); + + usb_gadget_disconnect(udc->gadget); + udc->driver->disconnect(udc->gadget); + udc->driver->unbind(udc->gadget); + usb_gadget_udc_stop(udc); + + udc->driver = NULL; +} + +/** + * usb_del_gadget_udc - deletes @udc from udc_list + * @gadget: the gadget to be removed. + * + * This, will call usb_gadget_unregister_driver() if + * the @udc is still busy. + */ +void usb_del_gadget_udc(struct usb_gadget *gadget) +{ + struct usb_udc *udc = NULL; + + mutex_lock(&udc_lock); + list_for_each_entry(udc, &udc_list, list) + if (udc->gadget == gadget) + goto found; + + dev_err(gadget->dev.parent, "gadget not registered.\n"); + mutex_unlock(&udc_lock); + + return; + +found: + dev_vdbg(gadget->dev.parent, "unregistering gadget\n"); + + list_del(&udc->list); + mutex_unlock(&udc_lock); + + if (udc->driver) + usb_gadget_remove_driver(udc); +} +EXPORT_SYMBOL_GPL(usb_del_gadget_udc); + +/* ------------------------------------------------------------------------- */ + +/** + * usb_gadget_udc_set_speed - tells usb device controller speed supported by + * current driver + * @udc: The device we want to set maximum speed + * @speed: The maximum speed to allowed to run + * + * This call is issued by the UDC Class driver before calling + * usb_gadget_udc_start() in order to make sure that we don't try to + * connect on speeds the gadget driver doesn't support. + */ +static inline void usb_gadget_udc_set_speed(struct usb_udc *udc, + enum usb_device_speed speed) +{ + if (udc->gadget->ops->udc_set_speed) { + enum usb_device_speed s; + + s = min(speed, udc->gadget->max_speed); + udc->gadget->ops->udc_set_speed(udc->gadget, s); + } +} + +static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *driver) +{ + int ret; + + dev_dbg(&udc->dev, "registering UDC driver [%s]\n", + driver->function); + + udc->driver = driver; + + usb_gadget_udc_set_speed(udc, driver->speed); + + ret = driver->bind(udc->gadget); + if (ret) + goto err1; + ret = usb_gadget_udc_start(udc); + if (ret) { + driver->unbind(udc->gadget); + goto err1; + } + usb_gadget_connect(udc->gadget); + + return 0; +err1: + if (ret != -EISNAM) + dev_err(&udc->dev, "failed to start %s: %d\n", + udc->driver->function, ret); + udc->driver = NULL; + return ret; +} + +int usb_gadget_probe_driver(struct usb_gadget_driver *driver) +{ + struct usb_udc *udc = NULL; + int ret; + + if (!driver || !driver->bind || !driver->setup) + return -EINVAL; + + mutex_lock(&udc_lock); + list_for_each_entry(udc, &udc_list, list) { + /* For now we take the first one */ + if (!udc->driver) + goto found; + } + + printf("couldn't find an available UDC\n"); + mutex_unlock(&udc_lock); + return -ENODEV; +found: + ret = udc_bind_to_driver(udc, driver); + mutex_unlock(&udc_lock); + return ret; +} +EXPORT_SYMBOL_GPL(usb_gadget_probe_driver); + +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + return usb_gadget_probe_driver(driver); +} +EXPORT_SYMBOL_GPL(usb_gadget_register_driver); + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct usb_udc *udc = NULL; + int ret = -ENODEV; + + if (!driver || !driver->unbind) + return -EINVAL; + + mutex_lock(&udc_lock); + list_for_each_entry(udc, &udc_list, list) + if (udc->driver == driver) { + usb_gadget_remove_driver(udc); + usb_gadget_set_state(udc->gadget, + USB_STATE_NOTATTACHED); + ret = 0; + break; + } + + mutex_unlock(&udc_lock); + return ret; +} +EXPORT_SYMBOL_GPL(usb_gadget_unregister_driver); + +MODULE_DESCRIPTION("UDC Framework"); +MODULE_AUTHOR("Felipe Balbi <balbi@ti.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/roms/u-boot/drivers/usb/gadget/udc/udc-uclass.c b/roms/u-boot/drivers/usb/gadget/udc/udc-uclass.c new file mode 100644 index 000000000..dbc354e84 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/udc/udc-uclass.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com + * Written by Jean-Jacques Hiblot <jjhiblot@ti.com> + */ + +#include <common.h> +#include <dm.h> +#include <dm/device-internal.h> +#include <linux/usb/gadget.h> + +#if CONFIG_IS_ENABLED(DM_USB_GADGET) +#define MAX_UDC_DEVICES 4 +static struct udevice *dev_array[MAX_UDC_DEVICES]; +int usb_gadget_initialize(int index) +{ + int ret; + struct udevice *dev = NULL; + + if (index < 0 || index >= ARRAY_SIZE(dev_array)) + return -EINVAL; + if (dev_array[index]) + return 0; + ret = uclass_get_device_by_seq(UCLASS_USB_GADGET_GENERIC, index, &dev); + if (!dev || ret) { + ret = uclass_get_device(UCLASS_USB_GADGET_GENERIC, index, &dev); + if (!dev || ret) { + pr_err("No USB device found\n"); + return -ENODEV; + } + } + dev_array[index] = dev; + return 0; +} + +int usb_gadget_release(int index) +{ +#if CONFIG_IS_ENABLED(DM_DEVICE_REMOVE) + int ret; + if (index < 0 || index >= ARRAY_SIZE(dev_array)) + return -EINVAL; + + ret = device_remove(dev_array[index], DM_REMOVE_NORMAL); + if (!ret) + dev_array[index] = NULL; + return ret; +#else + return -ENOSYS; +#endif +} + +int usb_gadget_handle_interrupts(int index) +{ + if (index < 0 || index >= ARRAY_SIZE(dev_array)) + return -EINVAL; + return dm_usb_gadget_handle_interrupts(dev_array[index]); +} +#endif + +UCLASS_DRIVER(usb_gadget_generic) = { + .id = UCLASS_USB_GADGET_GENERIC, + .name = "usb", + .flags = DM_UC_FLAG_SEQ_ALIAS, +}; diff --git a/roms/u-boot/drivers/usb/gadget/usbstring.c b/roms/u-boot/drivers/usb/gadget/usbstring.c new file mode 100644 index 000000000..e2464ad92 --- /dev/null +++ b/roms/u-boot/drivers/usb/gadget/usbstring.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: LGPL-2.1+ +/* + * Copyright (C) 2003 David Brownell + * + * Ported to U-Boot by: Thomas Smits <ts.smits@gmail.com> and + * Remy Bohmer <linux@bohmer.net> + */ + +#include <common.h> +#include <linux/errno.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/utf.h> + +/** + * usb_gadget_get_string - fill out a string descriptor + * @table: of c strings encoded using UTF-8 + * @id: string id, from low byte of wValue in get string descriptor + * @buf: at least 256 bytes + * + * Finds the UTF-8 string matching the ID, and converts it into a + * string descriptor in utf16-le. + * Returns length of descriptor (always even) or negative errno + * + * If your driver needs stings in multiple languages, you'll probably + * "switch (wIndex) { ... }" in your ep0 string descriptor logic, + * using this routine after choosing which set of UTF-8 strings to use. + * Note that US-ASCII is a strict subset of UTF-8; any string bytes with + * the eighth bit set will be multibyte UTF-8 characters, not ISO-8859/1 + * characters (which are also widely used in C strings). + */ +int +usb_gadget_get_string(struct usb_gadget_strings *table, int id, u8 *buf) +{ + struct usb_string *s; + int len; + + if (!table) + return -EINVAL; + + /* descriptor 0 has the language id */ + if (id == 0) { + buf[0] = 4; + buf[1] = USB_DT_STRING; + buf[2] = (u8) table->language; + buf[3] = (u8) (table->language >> 8); + return 4; + } + for (s = table->strings; s && s->s; s++) + if (s->id == id) + break; + + /* unrecognized: stall. */ + if (!s || !s->s) + return -EINVAL; + + /* string descriptors have length, tag, then UTF16-LE text */ + len = min((size_t) 126, strlen(s->s)); + memset(buf + 2, 0, 2 * len); /* zero all the bytes */ + len = utf8_to_utf16le(s->s, (__le16 *)&buf[2], len); + if (len < 0) + return -EINVAL; + buf[0] = (len + 1) * 2; + buf[1] = USB_DT_STRING; + return buf[0]; +} |