mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-20 03:38:43 +00:00
83d290c56f
When U-Boot started using SPDX tags we were among the early adopters and there weren't a lot of other examples to borrow from. So we picked the area of the file that usually had a full license text and replaced it with an appropriate SPDX-License-Identifier: entry. Since then, the Linux Kernel has adopted SPDX tags and they place it as the very first line in a file (except where shebangs are used, then it's second line) and with slightly different comment styles than us. In part due to community overlap, in part due to better tag visibility and in part for other minor reasons, switch over to that style. This commit changes all instances where we have a single declared license in the tag as both the before and after are identical in tag contents. There's also a few places where I found we did not have a tag and have introduced one. Signed-off-by: Tom Rini <trini@konsulko.com>
856 lines
20 KiB
C
856 lines
20 KiB
C
// 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 <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/lin_gadget_compat.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)
|
|
|
|
/***********************************************************/
|
|
|
|
void __iomem *regs_otg;
|
|
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;
|
|
}
|
|
|
|
/*
|
|
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->speed != USB_SPEED_HIGH)
|
|
|| !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;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
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);
|
|
|
|
for (i = 1; i < DWC2_MAX_HW_ENDPOINTS; i++)
|
|
writel((rx_fifo_sz + np_tx_fifo_sz + tx_fifo_sz*(i-1)) |
|
|
tx_fifo_sz << 16, ®->dieptxf[i-1]);
|
|
|
|
/* 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 */
|
|
};
|
|
|
|
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;
|
|
|
|
/* regs_otg = (void *)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 usb_gadget_handle_interrupts(int index)
|
|
{
|
|
u32 intr_status = readl(®->gintsts);
|
|
u32 gintmsk = readl(®->gintmsk);
|
|
|
|
if (intr_status & gintmsk)
|
|
return dwc2_udc_irq(1, (void *)the_controller);
|
|
return 0;
|
|
}
|