[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <1480485910-7797-3-git-send-email-raviteja.garimella@broadcom.com>
Date: Wed, 30 Nov 2016 11:35:10 +0530
From: Raviteja Garimella <raviteja.garimella@...adcom.com>
To: Rob Herring <robh+dt@...nel.org>,
Mark Rutland <mark.rutland@....com>,
Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
Felipe Balbi <balbi@...nel.org>
Cc: devicetree@...r.kernel.org, linux-kernel@...r.kernel.org,
bcm-kernel-feedback-list@...adcom.com, linux-usb@...r.kernel.org
Subject: [PATCH 2/2] Synopsys USB 2.0 Device Controller (UDC) Driver
This is driver for Synopsys Designware Cores USB Device
Controller (UDC) Subsystem with the AMBA Advanced High-Performance
Bus (AHB). This driver works with Synopsys UDC20 products.
Signed-off-by: Raviteja Garimella <raviteja.garimella@...adcom.com>
---
drivers/usb/gadget/udc/Kconfig | 12 +
drivers/usb/gadget/udc/Makefile | 1 +
drivers/usb/gadget/udc/snps_udc.c | 1751 +++++++++++++++++++++++++++++++++++++
drivers/usb/gadget/udc/snps_udc.h | 1071 +++++++++++++++++++++++
4 files changed, 2835 insertions(+)
create mode 100644 drivers/usb/gadget/udc/snps_udc.c
create mode 100644 drivers/usb/gadget/udc/snps_udc.h
diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig
index 658b8da..28cd679 100644
--- a/drivers/usb/gadget/udc/Kconfig
+++ b/drivers/usb/gadget/udc/Kconfig
@@ -239,6 +239,18 @@ config USB_MV_U3D
MARVELL PXA2128 Processor series include a super speed USB3.0 device
controller, which support super speed USB peripheral.
+config USB_SNP_UDC
+ tristate "Synopsys USB 2.0 Device controller"
+ select USB_GADGET_DUALSPEED
+ depends on (ARM || ARM64) && USB_GADGET
+ default ARCH_BCM_IPROC
+ help
+ This adds Device support for Synopsys Designware core
+ AHB subsystem USB2.0 Device Controller(UDC) .
+
+ This driver works with Synopsys UDC20 products.
+ If unsure, say N.
+
#
# Controllers available in both integrated and discrete versions
#
diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile
index 98e74ed..2b63a2b 100644
--- a/drivers/usb/gadget/udc/Makefile
+++ b/drivers/usb/gadget/udc/Makefile
@@ -36,4 +36,5 @@ obj-$(CONFIG_USB_FOTG210_UDC) += fotg210-udc.o
obj-$(CONFIG_USB_MV_U3D) += mv_u3d_core.o
obj-$(CONFIG_USB_GR_UDC) += gr_udc.o
obj-$(CONFIG_USB_GADGET_XILINX) += udc-xilinx.o
+obj-$(CONFIG_USB_SNP_UDC) += snps_udc.o
obj-$(CONFIG_USB_BDC_UDC) += bdc/
diff --git a/drivers/usb/gadget/udc/snps_udc.c b/drivers/usb/gadget/udc/snps_udc.c
new file mode 100644
index 0000000..d8c46ce
--- /dev/null
+++ b/drivers/usb/gadget/udc/snps_udc.c
@@ -0,0 +1,1751 @@
+/*
+ * snps_udc.c - Synopsys USB 2.0 Device Controller driver
+ *
+ * Copyright (C) 2016 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/errno.h>
+#include <linux/extcon.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/phy/phy.h>
+#include <linux/proc_fs.h>
+#include <linux/types.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/version.h>
+#include "snps_udc.h"
+
+#define DRIVER_DESC "Driver for Synopsys Designware core UDC"
+
+static void ep0_setup_init(struct snps_udc_ep *ep, int status)
+{
+ struct snps_udc *udc = ep->udc;
+
+ ep->dma.virt->setup.status = DMA_STS_BUF_HOST_READY;
+ ep->dirn = USB_DIR_OUT;
+ ep->stopped = 0;
+
+ if (!status) {
+ clear_ep_nak(udc->regs, ep->num, USB_DIR_OUT);
+ clear_ep_nak(udc->regs, ep->num, USB_DIR_IN);
+ } else {
+ enable_ep_stall(udc->regs, ep->num, USB_DIR_IN);
+ enable_ep_stall(udc->regs, ep->num, USB_DIR_OUT);
+ }
+
+ enable_udc_ep_irq(udc->regs, ep->num, USB_DIR_OUT);
+ enable_ep_dma(udc->regs, ep->num, USB_DIR_OUT);
+
+ dev_dbg(udc->dev, "%s setup buffer initialized\n", ep->name);
+}
+
+static void ep_dma_init(struct snps_udc_ep *ep)
+{
+ struct snps_udc *udc = ep->udc;
+ u32 desc_cnt = (DESC_CNT - 1);
+ u32 i;
+
+ ep->dma.virt = &ep->udc->dma.virt->ep[ep->num];
+ ep->dma.phys = &ep->udc->dma.phys->ep[ep->num];
+
+ ep->dma.virt->setup.status = DMA_STS_BUF_HOST_BUSY;
+ set_setup_buf_ptr(udc->regs, ep->num, USB_DIR_OUT,
+ &ep->dma.phys->setup);
+
+ for (i = 0; i < DESC_CNT; i++) {
+ ep->dma.virt->desc[i].status = DMA_STS_BUF_HOST_BUSY;
+ ep->dma.virt->desc[i].next_desc_addr =
+ (dma_addr_t)&ep->dma.phys->desc[i + 1];
+ }
+ ep->dma.virt->desc[desc_cnt].next_desc_addr =
+ (dma_addr_t)&ep->dma.phys->desc[0];
+
+ set_data_desc_ptr(udc->regs, ep->num, USB_DIR_OUT,
+ &ep->dma.phys->desc[0]);
+ set_data_desc_ptr(udc->regs, ep->num, USB_DIR_IN,
+ &ep->dma.phys->desc[0]);
+
+ dev_dbg(udc->dev, " %s dma initialized\n", ep->name);
+}
+
+static void ep_data_dma_init(struct snps_udc_ep *ep)
+{
+ struct ep_xfer_req *ep_req;
+
+ dev_dbg(ep->udc->dev, "enter: %s\n", __func__);
+
+ ep_req = list_first_entry(&ep->queue, struct ep_xfer_req, queue);
+
+ if (ep_req->dma_aligned) {
+ ep_req->dma_addr_orig = ep_req->usb_req.dma;
+ ep_req->usb_req.dma = ep->dma.aligned_addr;
+ if (ep->dirn == USB_DIR_IN)
+ memcpy(ep->dma.aligned_buf, ep_req->usb_req.buf,
+ ep_req->usb_req.length);
+ }
+
+ ep->dma.done = 0;
+ ep->dma.len_done = 0;
+ ep->dma.len_rem = ep->dma.usb_req->length;
+ ep->dma.buf_addr = ep->dma.usb_req->dma;
+ ep->dma.status = DMA_STS_RX_SUCCESS;
+
+ if ((ep->dirn == USB_DIR_IN) &&
+ (ep->type != USB_ENDPOINT_XFER_ISOC)) {
+ if (in_bf_mode)
+ ep->dma.len_max = ep->dma.usb_req->length;
+ else
+ ep->dma.len_max = ep->usb_ep.maxpacket;
+ } else {
+ if (out_bf_mode)
+ ep->dma.len_max = ep->dma.usb_req->length;
+ else
+ ep->dma.len_max = ep->usb_ep.maxpacket;
+ }
+
+ dma_desc_chain_reset(ep);
+}
+
+static void ep_data_dma_finish(struct snps_udc_ep *ep)
+{
+ struct snps_udc *udc = ep->udc;
+ struct ep_xfer_req *ep_req;
+
+ disable_udc_ep_irq(udc->regs, ep->num, ep->dirn);
+ disable_ep_dma(udc->regs, ep->num, ep->dirn);
+
+ ep_req = list_first_entry(&ep->queue, struct ep_xfer_req, queue);
+
+ if (ep_req->dma_aligned) {
+ if (ep->dirn == USB_DIR_OUT)
+ memcpy(ep_req->usb_req.buf,
+ ep->dma.aligned_buf, ep_req->usb_req.length);
+ ep_req->usb_req.dma = ep_req->dma_addr_orig;
+ }
+ dev_dbg(udc->dev, "%s dma finished\n", ep->name);
+}
+
+static void ep_data_dma_add(struct snps_udc_ep *ep)
+{
+ struct data_desc *desc = NULL;
+ u32 status;
+ u32 len;
+
+ if (!ep->dma.len_rem)
+ ep->dma.usb_req->zero = 1;
+
+ ep->dma.last = ep->dma.usb_req->zero;
+
+ while (!dma_desc_chain_is_full(ep) &&
+ (ep->dma.len_rem || ep->dma.usb_req->zero)) {
+ desc = dma_desc_chain_alloc(ep);
+ len = (ep->dma.len_rem < ep->dma.len_max) ?
+ ep->dma.len_rem : ep->dma.len_max;
+ ep->dma.len_rem -= len;
+ status = 0;
+
+ if (len <= ep->dma.len_max ||
+ (out_bf_mode && (len <= ep->dma.len_max))) {
+ if (in_bf_mode ||
+ !((ep->dirn == USB_DIR_IN) &&
+ (ep->type == USB_ENDPOINT_XFER_BULK) &&
+ (len != 0) &&
+ (len % ep->usb_ep.maxpacket == 0)))
+ ep->dma.usb_req->zero = 0;
+ }
+
+ if ((ep->dirn == USB_DIR_IN) &&
+ (ep->type == USB_ENDPOINT_XFER_ISOC)) {
+ ep->dma.frame_num += ep->dma.frame_incr;
+ dev_dbg(ep->udc->dev, "%s: DMA started: frame_num=%d.%d\n",
+ ep->name, (ep->dma.frame_num >> 3),
+ (ep->dma.frame_num & 0x7));
+ status |= ((ep->dma.frame_num <<
+ DMA_STS_FRAME_NUM_SHIFT)
+ & DMA_STS_FRAME_NUM_MASK);
+ }
+
+ desc->buf_addr = ep->dma.buf_addr;
+ status |= (len << DMA_STS_BYTE_CNT_SHIFT);
+ desc->status = status | DMA_STS_BUF_HOST_READY;
+ /* Ensure all writes are done before going for next descriptor*/
+ wmb();
+ ep->dma.buf_addr += len;
+
+ if ((ep->dirn == USB_DIR_IN) &&
+ (ep->type == USB_ENDPOINT_XFER_ISOC))
+ break;
+ }
+
+ if (desc)
+ desc->status |= DMA_STS_LAST_DESC;
+
+ dev_dbg(ep->udc->dev, "%s dma data added\n", ep->name);
+}
+
+static void ep_data_dma_remove(struct snps_udc_ep *ep)
+{
+ struct data_desc *desc;
+ u32 status;
+ u32 len = 0;
+
+ while (!dma_desc_chain_is_empty(ep)) {
+ desc = dma_desc_chain_head(ep);
+ status = desc->status;
+ desc->status = DMA_STS_BUF_HOST_BUSY;
+ /* Ensure all writes are done before going for next descriptor*/
+ wmb();
+ len = (status & DMA_STS_NISO_BYTE_CNT_MASK) >>
+ DMA_STS_NISO_BYTE_CNT_SHIFT;
+
+ if ((ep->dirn == USB_DIR_IN) || (status &
+ DMA_STS_LAST_DESC)) {
+ ep->dma.len_done += len;
+ ep->dma.usb_req->actual += len;
+ }
+
+ if ((status & DMA_STS_RX_MASK) != DMA_STS_RX_SUCCESS) {
+ ep->dma.status = status & DMA_STS_RX_MASK;
+ ep->dma.usb_req->status = -EIO;
+ dev_warn(ep->udc->dev, "%s: DMA error\n", ep->name);
+ }
+
+ if ((ep->dirn == USB_DIR_IN) &&
+ (ep->type == USB_ENDPOINT_XFER_ISOC)) {
+ if (ep->dma.usb_req->actual ==
+ ep->dma.usb_req->length)
+ ep->dma.usb_req->status = 0;
+ dma_desc_chain_reset(ep);
+ } else {
+ dma_desc_chain_free(ep);
+ }
+ }
+
+ if ((!ep->dma.len_rem || (len < ep->usb_ep.maxpacket)) &&
+ (ep->dma.usb_req->status == -EINPROGRESS))
+ ep->dma.usb_req->status = 0;
+
+ dev_dbg(ep->udc->dev, "%s dma data removed\n", ep->name);
+}
+
+static int fifo_ram_alloc(struct snps_udc_ep *ep, u32 max_pkt_size)
+{
+ u32 rx_cnt;
+ u32 tx_cnt;
+
+ switch (EP_DIRN_TYPE(ep->dirn, ep->type)) {
+ case EP_DIRN_TYPE(USB_DIR_OUT, USB_ENDPOINT_XFER_BULK):
+ case EP_DIRN_TYPE(USB_DIR_OUT, USB_ENDPOINT_XFER_INT):
+ case EP_DIRN_TYPE(USB_DIR_OUT, USB_ENDPOINT_XFER_ISOC):
+ rx_cnt = FIFO_SZ_U8(max_pkt_size);
+ tx_cnt = 0;
+ break;
+
+ case EP_DIRN_TYPE(USB_DIR_IN, USB_ENDPOINT_XFER_BULK):
+ case EP_DIRN_TYPE(USB_DIR_IN, USB_ENDPOINT_XFER_INT):
+ rx_cnt = 0;
+ tx_cnt = FIFO_SZ_U8(max_pkt_size);
+ break;
+
+ case EP_DIRN_TYPE(USB_DIR_IN, USB_ENDPOINT_XFER_ISOC):
+ rx_cnt = 0;
+ tx_cnt = 2 * FIFO_SZ_U8(max_pkt_size);
+ break;
+
+ case EP_DIRN_TYPE(USB_DIR_IN, USB_ENDPOINT_XFER_CONTROL):
+ case EP_DIRN_TYPE(USB_DIR_OUT, USB_ENDPOINT_XFER_CONTROL):
+ rx_cnt = FIFO_SZ_U8(max_pkt_size);
+ tx_cnt = rx_cnt;
+ break;
+
+ default:
+ dev_err(ep->udc->dev, "%s: invalid EP attributes\n", ep->name);
+ return -ENODEV;
+ }
+
+ dev_dbg(ep->udc->dev, "rx req=%u free=%u: tx req=%u free=%u\n",
+ rx_cnt, ep->udc->rx_fifo_space, tx_cnt, ep->udc->tx_fifo_space);
+
+ if ((ep->udc->rx_fifo_space < rx_cnt) ||
+ (ep->udc->tx_fifo_space < tx_cnt)) {
+ dev_err(ep->udc->dev, "%s: fifo alloc failed\n", ep->name);
+ return -ENOSPC;
+ }
+
+ ep->rx_fifo_size = rx_cnt;
+ ep->tx_fifo_size = tx_cnt;
+
+ if (mrx_fifo)
+ ep->udc->rx_fifo_space -= rx_cnt;
+
+ ep->udc->tx_fifo_space -= tx_cnt;
+
+ return 0;
+}
+
+static void fifo_ram_free(struct snps_udc_ep *ep)
+{
+ if (mrx_fifo)
+ ep->udc->rx_fifo_space += ep->rx_fifo_size;
+
+ ep->udc->tx_fifo_space += ep->tx_fifo_size;
+
+ ep->rx_fifo_size = 0;
+ ep->tx_fifo_size = 0;
+}
+
+static int ep_cfg(struct snps_udc_ep *ep, u32 type,
+ u32 max_pkt_size)
+{
+ struct snps_udc *udc = ep->udc;
+
+ ep->type = type;
+ if (fifo_ram_alloc(ep, max_pkt_size) != 0)
+ return -ENOSPC;
+
+ ep->type = type;
+ ep->usb_ep.maxpacket = max_pkt_size;
+
+ if (ep->udc->conn_type)
+ init_ep_reg(udc->regs, ep->num, ep->type, ep->dirn,
+ max_pkt_size);
+ dev_dbg(udc->dev, "ep_cfg: %s: type=%u dirn=0x%x pkt=%u\n",
+ ep->usb_ep.name, type, ep->dirn, max_pkt_size);
+
+ return 0;
+}
+
+static void epreq_xfer_done(struct snps_udc_ep *ep,
+ struct ep_xfer_req *ep_req, int status)
+{
+ struct snps_udc *udc = ep->udc;
+ u32 stopped;
+
+ list_del_init(&ep_req->queue);
+
+ if (ep_req->usb_req.status == -EINPROGRESS)
+ ep_req->usb_req.status = status;
+
+ if (ep_req->dma_aligned) {
+ ep_req->dma_aligned = 0;
+ } else if (ep_req->dma_mapped) {
+ dma_unmap_single(ep->udc->gadget.dev.parent,
+ ep_req->usb_req.dma,
+ (ep_req->usb_req.length ?
+ ep_req->usb_req.length : 1),
+ (ep->dirn == USB_DIR_IN ? DMA_TO_DEVICE :
+ DMA_FROM_DEVICE));
+ ep_req->dma_mapped = 0;
+ ep_req->usb_req.dma = DMA_ADDR_INVALID;
+ }
+
+ dev_dbg(udc->dev, "%s xfer done req=0x%p buf=0x%p len=%d actual=%d\n",
+ ep->name, &ep_req->usb_req, ep_req->usb_req.buf,
+ ep_req->usb_req.length, ep_req->usb_req.actual);
+
+ stopped = ep->stopped;
+ ep->stopped = 1;
+ spin_unlock(&ep->udc->lock);
+ ep_req->usb_req.complete(&ep->usb_ep, &ep_req->usb_req);
+ spin_lock(&ep->udc->lock);
+ ep->stopped = stopped;
+}
+
+static void epreq_xfer_process(struct snps_udc_ep *ep)
+{
+ struct snps_udc *udc = ep->udc;
+ struct ep_xfer_req *ep_req;
+
+ dev_dbg(udc->dev, "%s: xfer request\n", ep->name);
+
+ if (!ep->dma.usb_req) {
+ dev_dbg(udc->dev, "%s: No dma usb request\n", ep->name);
+ return;
+ }
+
+ disable_ep_dma(udc->regs, ep->num, ep->dirn);
+ ep_data_dma_remove(ep);
+
+ if (ep->dma.usb_req->status != -EINPROGRESS) {
+ ep_data_dma_finish(ep);
+
+ if ((ep->type == USB_ENDPOINT_XFER_CONTROL) &&
+ (ep->dirn == USB_DIR_IN) &&
+ (ep->dma.usb_req->status == 0)) {
+ ep->dirn = USB_DIR_OUT;
+ ep->b_ep_addr = ep->num | ep->dirn;
+ ep->dma.usb_req->status = -EINPROGRESS;
+ ep->dma.usb_req->actual = 0;
+ ep->dma.usb_req->length = 0;
+ ep_data_dma_init(ep);
+ } else {
+ if (in_bf_mode && is_ep_in() && is_ep_bulk() &&
+ (ep->dma.usb_req->length != 0) &&
+ (ep->dma.usb_req->length %
+ ep->usb_ep.maxpacket == 0) &&
+ (ep->dma.last)) {
+ ep->dma.usb_req->status = -EINPROGRESS;
+ ep->dma.usb_req->actual = 0;
+ ep->dma.usb_req->length = 0;
+ } else if (!list_empty(&ep->queue))
+ epreq_xfer_done(ep,
+ list_first_entry(&ep->queue,
+ struct
+ ep_xfer_req,
+ queue), 0);
+
+ if (ep->type == USB_ENDPOINT_XFER_CONTROL)
+ ep0_setup_init(ep, 0);
+
+ if (is_ep_in() && is_ep_bulk() &&
+ !list_empty(&ep->queue)) {
+ ep->in_xfer_done = true;
+ clear_ep_nak(udc->regs, ep->num, ep->dirn);
+ enable_udc_ep_irq(udc->regs, ep->num, ep->dirn);
+ return;
+ }
+
+ if (list_empty(&ep->queue)) {
+ ep->dma.usb_req = NULL;
+ } else {
+ ep_req = list_first_entry(&ep->queue,
+ struct ep_xfer_req,
+ queue);
+ ep->dma.usb_req = &ep_req->usb_req;
+ ep_data_dma_init(ep);
+ }
+ }
+ }
+
+ if (ep->dma.usb_req) {
+ ep_data_dma_add(ep);
+ enable_udc_ep_irq(udc->regs, ep->num, ep->dirn);
+ clear_ep_nak(udc->regs, ep->num, ep->dirn);
+ enable_ep_dma(udc->regs, ep->num, ep->dirn);
+ }
+}
+
+static void epreq_xfer_error(struct snps_udc_ep *ep, int status)
+{
+ if (!ep->dma.usb_req) {
+ dev_err(ep->udc->dev, "%s: No DMA usb request\n", ep->name);
+ return;
+ }
+
+ ep->dma.usb_req->status = status;
+ epreq_xfer_process(ep);
+}
+
+static void epreq_xfer_add(struct snps_udc_ep *ep,
+ struct ep_xfer_req *ep_req)
+{
+ struct snps_udc *udc = ep->udc;
+
+ list_add_tail(&ep_req->queue, &ep->queue);
+ if (ep->stopped)
+ return;
+
+ if ((ep->dirn == USB_DIR_IN) &&
+ (ep->type == USB_ENDPOINT_XFER_ISOC) &&
+ (ep->dma.usb_req) &&
+ (ep->dma.frame_num == FRAME_NUM_INVALID)) {
+ ep_data_dma_finish(ep);
+ ep->dma.usb_req = NULL;
+ epreq_xfer_done(ep,
+ list_first_entry(&ep->queue,
+ struct ep_xfer_req,
+ queue),
+ -EREMOTEIO);
+ }
+
+ if (ep->dma.usb_req) {
+ dev_dbg(udc->dev, "%s: busy\n", ep->name);
+ } else if (!in_isoc_delay_disabled && (ep->dirn == USB_DIR_IN) &&
+ (ep->type == USB_ENDPOINT_XFER_ISOC) &&
+ (ep->dma.frame_num == FRAME_NUM_INVALID)) {
+ dev_dbg(udc->dev, "%s: ISOC delay xfer start\n", ep->name);
+ ep->dma.usb_req = &(list_first_entry(&ep->queue,
+ struct ep_xfer_req, queue))->usb_req;
+ ep_data_dma_init(ep);
+ clear_ep_nak(udc->regs, ep->num, ep->dirn);
+ enable_udc_ep_irq(udc->regs, ep->num, ep->dirn);
+
+ } else {
+ if (in_isoc_delay_disabled && (ep->dirn == USB_DIR_IN) &&
+ (ep->type == USB_ENDPOINT_XFER_ISOC) &&
+ (ep->dma.frame_num == FRAME_NUM_INVALID)) {
+ ep->dma.frame_num = get_last_rx_frnum(udc->regs);
+ }
+
+ if (is_ep_in() && is_ep_bulk() && !ep->dma.usb_req) {
+ ep->in_xfer_done = true;
+ clear_ep_nak(udc->regs, ep->num, ep->dirn);
+ enable_udc_ep_irq(udc->regs, ep->num, ep->dirn);
+ return;
+ }
+
+ ep_req = list_first_entry(&ep->queue,
+ struct ep_xfer_req, queue);
+ ep->dma.usb_req = &ep_req->usb_req;
+ ep_data_dma_init(ep);
+ ep_data_dma_add(ep);
+ enable_udc_ep_irq(udc->regs, ep->num, ep->dirn);
+ clear_ep_nak(udc->regs, ep->num, ep->dirn);
+ enable_ep_dma(udc->regs, ep->num, ep->dirn);
+ }
+
+ dev_dbg(udc->dev, "%s: xfer add ep request\n", ep->name);
+}
+
+static void epreq_queue_flush(struct snps_udc_ep *ep, int status)
+{
+ struct snps_udc *udc = ep->udc;
+ struct ep_xfer_req *ep_req;
+
+ ep->stopped = 1;
+
+ while (!list_empty(&ep->queue)) {
+ ep_req = list_first_entry(&ep->queue,
+ struct ep_xfer_req, queue);
+ epreq_xfer_done(ep, ep_req, status);
+ }
+
+ ep->dma.usb_req = NULL;
+ if ((is_ep_in() && is_ep_bulk()) || !ep->num) {
+ set_ep_fifo_flush(udc->regs, ep->num, ep->dirn);
+ clear_ep_fifo_flush(udc->regs, ep->num, ep->dirn);
+ }
+
+ dev_dbg(udc->dev, "%s: EP queue flushed\n", ep->usb_ep.name);
+}
+
+static void ep0_setup_rx(struct snps_udc_ep *ep,
+ struct usb_ctrlrequest *setup)
+{
+ struct snps_udc *udc = ep->udc;
+ int status;
+ u32 val;
+ u32 idx;
+ u32 len;
+
+ val = le16_to_cpu(setup->wValue);
+ idx = le16_to_cpu(setup->wIndex);
+ len = le16_to_cpu(setup->wLength);
+
+ ep->dirn = setup->bRequestType & USB_ENDPOINT_DIR_MASK;
+
+ dev_dbg(udc->dev, "%s: SETUP %02x.%02x v%04x i%04x l %04x\n",
+ ep->name, setup->bRequestType, setup->bRequest,
+ val, idx, len);
+
+ if (ep->num != 0) {
+ status = -EOPNOTSUPP;
+ } else {
+ spin_unlock(&udc->lock);
+ status = udc->gadget_driver->setup(&udc->gadget, setup);
+ spin_lock(&udc->lock);
+ }
+
+ if (status < 0)
+ ep0_setup_init(ep, status);
+ else if (len == 0)
+ ep0_setup_init(ep, 0);
+}
+
+static void irq_ep_out_setup(struct snps_udc_ep *ep)
+{
+ struct setup_desc *desc = &ep->dma.virt->setup;
+ u32 status = desc->status;
+
+ dev_dbg(ep->udc->dev, "irq set up %s desc status: 0x%x\n",
+ ep->name, status);
+
+ if ((status & DMA_STS_BUF_MASK) != DMA_STS_BUF_DMA_DONE) {
+ ep0_setup_init(ep, 0);
+ } else if ((status & DMA_STS_RX_MASK) != DMA_STS_RX_SUCCESS) {
+ ep0_setup_init(ep, 0);
+ } else {
+ desc->status = (status & ~DMA_STS_BUF_MASK)
+ | DMA_STS_BUF_HOST_BUSY;
+ ep0_setup_rx(ep, (struct usb_ctrlrequest *)&desc->data1);
+ }
+}
+
+static void irq_process_epout(struct snps_udc_ep *ep)
+{
+ struct snps_udc *udc = ep->udc;
+ u32 status;
+
+ status = get_ep_status(udc->regs, ep->num, USB_DIR_OUT);
+ clear_ep_status(udc->regs, ep->num, USB_DIR_OUT, status);
+
+ status &= EP_STS_ALL;
+
+ if (!status)
+ return;
+
+ if ((ep->dirn != USB_DIR_OUT) &&
+ (ep->type != USB_ENDPOINT_XFER_CONTROL)) {
+ dev_err(udc->dev, "%s: unexpected interrupt\n", ep->name);
+ return;
+ }
+
+ if (status & OUT_DMA_DATA_DONE) {
+ status &= ~OUT_DMA_DATA_DONE;
+ epreq_xfer_process(ep);
+ }
+
+ if (status & OUT_DMA_SETUP_DONE) {
+ status &= ~OUT_DMA_SETUP_DONE;
+ irq_ep_out_setup(ep);
+ }
+
+ if (status & DMA_BUF_NOT_AVAIL) {
+ status &= ~DMA_BUF_NOT_AVAIL;
+ dev_dbg(udc->dev, "%s: DMA BUF NOT AVAIL\n", ep->name);
+ epreq_xfer_process(ep);
+ }
+
+ if (status & DMA_ERROR) {
+ status &= ~DMA_ERROR;
+ dev_err(udc->dev, "%s: DMA ERROR\n", ep->usb_ep.name);
+ epreq_xfer_error(ep, -EIO);
+ }
+
+ if (status)
+ dev_err(udc->dev, "%s: unknown status=0x%x\n",
+ ep->name, status);
+}
+
+static void irq_process_epin(struct snps_udc_ep *ep)
+{
+ struct snps_udc *udc = ep->udc;
+ struct ep_xfer_req *ep_req;
+ u32 status;
+
+ status = get_ep_status(udc->regs, ep->num, USB_DIR_IN);
+ clear_ep_status(udc->regs, ep->num, USB_DIR_IN, status);
+
+ if (!status)
+ return;
+
+ if (ep->dirn != USB_DIR_IN) {
+ dev_err(udc->dev, "%s: unexpected OUT endpoint\n", ep->name);
+ return;
+ }
+
+ if ((ep->type == USB_ENDPOINT_XFER_ISOC) &&
+ (status & (IN_XFER_DONE | DMA_BUF_NOT_AVAIL))) {
+ dev_warn(ep->udc->dev, "%s: ISOC IN unexpected status=0x%x\n",
+ ep->name, status);
+ }
+
+ if (status & IN_TOKEN_RX) {
+ status &= ~IN_TOKEN_RX;
+ if (!ep->dma.usb_req && list_empty(&ep->queue))
+ enable_ep_nak(udc->regs, ep->num, USB_DIR_IN);
+
+ if (ep->type == USB_ENDPOINT_XFER_ISOC) {
+ ep->dma.frame_num = get_frnum_last_rx(udc->regs);
+ dev_dbg(udc->dev, "%s: ISOC IN\n", ep->name);
+ if (ep->dma.usb_req) {
+ ep->dma.usb_req->status = -EREMOTEIO;
+ epreq_xfer_process(ep);
+ }
+ }
+ }
+
+ if (is_ep_bulk() && !list_empty(&ep->queue) &&
+ ep->in_xfer_done) {
+ ep->in_xfer_done = false;
+ ep_req = list_first_entry(&ep->queue,
+ struct ep_xfer_req, queue);
+ ep->dma.usb_req = &ep_req->usb_req;
+
+ ep_data_dma_init(ep);
+ ep_data_dma_add(ep);
+ clear_ep_nak(udc->regs, ep->num, ep->dirn);
+ enable_udc_ep_irq(udc->regs, ep->num, ep->dirn);
+ enable_ep_dma(udc->regs, ep->num, ep->dirn);
+ }
+
+ if (status & IN_DMA_DONE) {
+ status &= ~IN_DMA_DONE;
+ clear_ep_nak(udc->regs, ep->num, USB_DIR_IN);
+
+ if (ep->type == USB_ENDPOINT_XFER_ISOC) {
+ dev_dbg(udc->dev, "%s: ISOC IN\n", ep->usb_ep.name);
+ epreq_xfer_process(ep);
+ } else if (ep->dma.done & IN_XFER_DONE) {
+ dev_dbg(udc->dev, "%s: late IN DMA done rec'd\n",
+ ep->name);
+ epreq_xfer_process(ep);
+ } else {
+ ep->dma.done = IN_DMA_DONE;
+ }
+ }
+
+ if (status & IN_XFER_DONE) {
+ status &= ~(IN_XFER_DONE);
+ status &= ~(IN_FIFO_EMPTY);
+
+ if (ep->dma.done & IN_DMA_DONE)
+ epreq_xfer_process(ep);
+ else
+ ep->dma.done = IN_XFER_DONE;
+ }
+
+ status &= ~(IN_FIFO_EMPTY);
+
+ if (status & DMA_BUF_NOT_AVAIL) {
+ dev_err(udc->dev, "%s: DMA BUF NOT AVAIL\n", ep->name);
+ status &= ~(DMA_BUF_NOT_AVAIL);
+ epreq_xfer_process(ep);
+ }
+
+ if (status & DMA_ERROR) {
+ status &= ~DMA_ERROR;
+ dev_err(udc->dev, "%s: DMA ERROR\n", ep->name);
+ epreq_xfer_error(ep, -EIO);
+ }
+
+ if (status)
+ dev_err(udc->dev, "%s: unknown status=0x%x\n",
+ ep->name, status);
+}
+
+static void ep_irq_process(struct snps_udc *udc, u32 irq_in, u32 irq_out)
+{
+ u32 mask = 1;
+ u32 num;
+
+ for (num = 0; num < UDC_MAX_EP; num++) {
+ if (irq_in & mask)
+ irq_process_epin(&udc->ep[num]);
+
+ if (irq_out & mask)
+ irq_process_epout(&udc->ep[num]);
+
+ mask <<= 1;
+ }
+}
+
+static void irq_process_set_intf(struct snps_udc *udc)
+{
+ struct usb_ctrlrequest setup;
+ u32 ep_num;
+ u16 intf;
+ u16 alt;
+
+ intf = (uint16_t)get_intf_num(udc->regs);
+ alt = (uint16_t)get_alt_num(udc->regs);
+
+ setup.bRequestType = USB_DIR_OUT | USB_TYPE_STANDARD
+ | USB_RECIP_INTERFACE;
+ setup.bRequest = USB_REQ_SET_INTERFACE;
+ setup.wValue = cpu_to_le16(alt);
+ setup.wIndex = cpu_to_le16(intf);
+ setup.wLength = 0;
+
+ for (ep_num = 0; ep_num < UDC_MAX_EP; ep_num++) {
+ set_ep_alt_num(udc->regs, ep_num, alt);
+ set_ep_intf_num(udc->regs, ep_num, intf);
+ }
+ dev_info(udc->dev, "SET INTF=%d ALT=%d\n", intf, alt);
+
+ ep0_setup_rx(&udc->ep[0], &setup);
+ set_setup_done(udc->regs);
+}
+
+static void irq_process_set_cfg(struct snps_udc *udc)
+{
+ struct usb_ctrlrequest setup;
+ u32 ep_num;
+ u16 cfg;
+
+ cfg = (u16)get_cfg_num(udc->regs);
+
+ setup.bRequestType = USB_DIR_OUT | USB_TYPE_STANDARD
+ | USB_RECIP_DEVICE;
+ setup.bRequest = USB_REQ_SET_CONFIGURATION;
+ setup.wValue = cpu_to_le16(cfg);
+ setup.wIndex = 0;
+ setup.wLength = 0;
+
+ for (ep_num = 0; ep_num < UDC_MAX_EP; ep_num++)
+ set_epcfg_reg(udc->regs, ep_num, cfg);
+
+ dev_info(udc->dev, "SET CFG=%d\n", cfg);
+
+ ep0_setup_rx(&udc->ep[0], &setup);
+ set_setup_done(udc->regs);
+}
+
+static void irq_process_speed_enum(struct snps_udc *udc)
+{
+ u32 speed = udc->gadget.speed;
+
+ switch (get_enum_speed(udc->regs)) {
+ case SPEED_HIGH:
+ dev_info(udc->dev, "HIGH SPEED\n");
+ udc->gadget.speed = USB_SPEED_HIGH;
+ break;
+ case SPEED_FULL:
+ dev_info(udc->dev, "FULL SPEED\n");
+ udc->gadget.speed = USB_SPEED_FULL;
+ break;
+ case SPEED_LOW:
+ dev_warn(udc->dev, "LOW SPEED not supported\n");
+ udc->gadget.speed = USB_SPEED_LOW;
+ break;
+ default:
+ dev_err(udc->dev, "Unknown SPEED = 0x%x\n",
+ get_enum_speed(udc->regs));
+ break;
+ }
+
+ if ((speed == USB_SPEED_UNKNOWN) &&
+ (udc->gadget.speed != USB_SPEED_UNKNOWN)) {
+ ep0_setup_init(&udc->ep[0], 0);
+ clear_devnak(udc->regs);
+ }
+}
+
+static void irq_process_bus_idle(struct snps_udc *udc)
+{
+ int num;
+
+ for (num = 0; num < UDC_MAX_EP; num++) {
+ set_ep_fifo_flush(udc->regs, num, EP_DIRN_IN);
+ clear_ep_fifo_flush(udc->regs, num, EP_DIRN_IN);
+ }
+}
+
+static void dev_irq_process(struct snps_udc *udc, u32 irq)
+{
+ if (irq & IRQ_BUS_RESET)
+ dev_info(udc->dev, "BUS RESET\n");
+
+ if (irq & IRQ_BUS_SUSPEND)
+ dev_dbg(udc->dev, "BUS SUSPEND\n");
+
+ if (irq & IRQ_BUS_IDLE) {
+ dev_dbg(udc->dev, "BUS IDLE\n");
+ irq_process_bus_idle(udc);
+ }
+
+ if (irq & IRQ_SPEED_ENUM_DONE) {
+ dev_dbg(udc->dev, "BUS speed enum done\n");
+ irq_process_speed_enum(udc);
+ }
+
+ if (irq & IRQ_SET_CFG) {
+ dev_dbg(udc->dev, "SET CFG\n");
+ irq_process_set_cfg(udc);
+ }
+
+ if (irq & IRQ_SET_INTF) {
+ dev_dbg(udc->dev, "SET INTF\n");
+ irq_process_set_intf(udc);
+ }
+}
+
+static irqreturn_t snps_udc_irq(int irq, void *dev)
+{
+ struct snps_udc *udc = (struct snps_udc *)dev;
+ u32 devintr, epin_intr, epout_intr;
+ unsigned long flags;
+
+ spin_lock_irqsave(&udc->lock, flags);
+
+ devintr = get_irq_active(udc->regs);
+ epin_intr = get_ep_irq_active(udc->regs, USB_DIR_IN);
+ epout_intr = get_ep_irq_active(udc->regs, USB_DIR_OUT);
+
+ clear_udc_dev_irq(udc->regs, devintr);
+ clear_udc_ep_irq_list(udc->regs, USB_DIR_IN, epin_intr);
+ clear_udc_ep_irq_list(udc->regs, USB_DIR_OUT, epout_intr);
+
+ if (!udc->gadget_driver) {
+ spin_unlock_irqrestore(&udc->lock, flags);
+ return IRQ_NONE;
+ }
+
+ /* SET_CFG and SET_INTF interrupts are handled last */
+ dev_irq_process(udc, devintr & ~(IRQ_SET_CFG | IRQ_SET_INTF));
+ ep_irq_process(udc, epin_intr, epout_intr);
+ dev_irq_process(udc, devintr & (IRQ_SET_CFG | IRQ_SET_INTF));
+
+ spin_unlock_irqrestore(&udc->lock, flags);
+ dev_dbg(udc->dev, "UDC interrupts: Dev=0x%x EpIn=0x%x EpOut=0x%x\n",
+ devintr, epin_intr, epout_intr);
+
+ return IRQ_HANDLED;
+}
+
+static int snps_ep_enable(struct usb_ep *usb_ep,
+ const struct usb_endpoint_descriptor *desc)
+{
+ struct snps_udc_ep *ep;
+ struct snps_udc *udc;
+ unsigned long flags;
+ u32 max_pkt_size;
+ u32 xfertype;
+
+ ep = container_of(usb_ep, struct snps_udc_ep, usb_ep);
+ udc = ep->udc;
+
+ if (!usb_ep || (ep->b_ep_addr != desc->bEndpointAddress)) {
+ dev_err(udc->dev, "invalid endpoint (%p)\n", usb_ep);
+ return -EINVAL;
+ }
+
+ if (!desc || (desc->bDescriptorType != USB_DT_ENDPOINT)) {
+ dev_err(udc->dev, "ep%d: invalid descriptor=%p\n",
+ ep->num, desc);
+ return -EINVAL;
+ }
+
+ if (desc == ep->desc) {
+ dev_err(udc->dev, "ep%d: already enabled\n", ep->num);
+ return -EEXIST;
+ }
+
+ if (ep->desc) {
+ dev_err(udc->dev, "ep%d:already enabled wth other descr\n",
+ ep->num);
+ return -EBUSY;
+ }
+
+ if (!udc->gadget_driver) {
+ dev_warn(udc->dev, "%s: invalid device state\n", ep->name);
+ return -ESHUTDOWN;
+ }
+
+ xfertype = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
+ max_pkt_size = le16_to_cpu(desc->wMaxPacketSize) & 0x7FF;
+
+ if (!max_pkt_size || (max_pkt_size > ep->max_pkt_size)) {
+ dev_err(udc->dev, "%s: invalid max pkt size\n", ep->name);
+ return -ERANGE;
+ }
+
+ if ((ep->dirn == USB_DIR_IN) &&
+ (xfertype == USB_ENDPOINT_XFER_ISOC)) {
+ if ((desc->bInterval < 1) || (desc->bInterval > 16)) {
+ dev_err(udc->dev, "%s: invalid binterval\n", ep->name);
+ return -ERANGE;
+ }
+ ep->dma.frame_num = FRAME_NUM_INVALID;
+ ep->dma.frame_incr = 1 << (desc->bInterval - 1);
+ }
+
+ spin_lock_irqsave(&udc->lock, flags);
+
+ if (ep_cfg(ep, xfertype, max_pkt_size) != 0) {
+ spin_unlock_irqrestore(&udc->lock, flags);
+ dev_err(udc->dev, "%s: not enough FIFO space\n", ep->name);
+ return -ENOSPC;
+ }
+
+ set_epcfg_reg(udc->regs, ep->num, get_cfg_num(udc->regs));
+
+ ep->desc = desc;
+ ep->stopped = 0;
+ ep->usb_ep.maxpacket = max_pkt_size;
+
+ spin_unlock_irqrestore(&udc->lock, flags);
+
+ dev_dbg(udc->dev, "%s: enabled: type: 0x%x, max_pkt_size: %d\n",
+ ep->name, xfertype, max_pkt_size);
+
+ return 0;
+}
+
+static int snps_ep_disable(struct usb_ep *usb_ep)
+{
+ struct snps_udc_ep *ep;
+ struct snps_udc *udc;
+ unsigned long flags;
+
+ ep = container_of(usb_ep, struct snps_udc_ep, usb_ep);
+ udc = ep->udc;
+
+ if (!usb_ep || !ep->desc) {
+ dev_err(udc->dev, "%s: invalid endpoint\n", ep->usb_ep.name);
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&udc->lock, flags);
+
+ epreq_queue_flush(ep, -ESHUTDOWN);
+ ep->desc = NULL;
+ ep->usb_ep.maxpacket = ep->max_pkt_size;
+ fifo_ram_free(ep);
+
+ spin_unlock_irqrestore(&udc->lock, flags);
+
+ return 0;
+}
+
+static struct usb_request *
+snps_ep_alloc_request(struct usb_ep *usb_ep, gfp_t gfp_flags)
+{
+ struct ep_xfer_req *ep_req;
+
+ if (!usb_ep)
+ return NULL;
+
+ ep_req = kzalloc(sizeof(*ep_req), gfp_flags);
+ if (ep_req) {
+ INIT_LIST_HEAD(&ep_req->queue);
+ ep_req->usb_req.dma = DMA_ADDR_INVALID;
+ pr_debug("%s: ep alloc req\n", usb_ep->name);
+ return &ep_req->usb_req;
+ }
+
+ return NULL;
+}
+
+static void snps_ep_free_request(struct usb_ep *usb_ep,
+ struct usb_request *usb_req)
+{
+ struct ep_xfer_req *ep_req;
+
+ ep_req = container_of(usb_req, struct ep_xfer_req, usb_req);
+
+ if (usb_req) {
+ pr_debug("%s: freed\n", usb_ep->name);
+ kfree(ep_req);
+ }
+}
+
+static int snps_ep_queue(struct usb_ep *usb_ep,
+ struct usb_request *usb_req, gfp_t gfp_flags)
+{
+ struct ep_xfer_req *ep_req;
+ struct snps_udc_ep *ep;
+ struct snps_udc *udc;
+ unsigned long flags;
+
+ ep = container_of(usb_ep, struct snps_udc_ep, usb_ep);
+ ep_req = container_of(usb_req, struct ep_xfer_req, usb_req);
+
+ dev_dbg(ep->udc->dev, "%s: %s\n", __func__, ep->usb_ep.name);
+ if (!usb_ep || !usb_req || !ep_req->usb_req.complete ||
+ !ep_req->usb_req.buf || !list_empty(&ep_req->queue)) {
+ dev_dbg(ep->udc->dev, "%s:invalid queue request\n", ep->name);
+ return -EINVAL;
+ }
+
+ if (!ep->desc && (ep->num != 0)) {
+ dev_err(ep->udc->dev, "%s: invalid EP state\n", ep->name);
+ return -EFAULT;
+ }
+
+ if ((ep->type == USB_ENDPOINT_XFER_CONTROL) &&
+ !list_empty(&ep->queue)) {
+ dev_err(ep->udc->dev, "%s: EP queue not empty\n", ep->name);
+ return -EPERM;
+ }
+
+ if (usb_req->length > 0xffff) {
+ dev_err(ep->udc->dev, "%s: request too big\n", ep->name);
+ return -E2BIG;
+ }
+
+ if ((ep->type == USB_ENDPOINT_XFER_ISOC) &&
+ (ep->dirn == USB_DIR_IN) &&
+ (usb_req->length > ep->usb_ep.maxpacket)) {
+ dev_err(ep->udc->dev, "%s: request > scheduled bandwidth, length=%u\n",
+ ep->name, usb_req->length);
+ return -EFBIG;
+ }
+
+ udc = ep->udc;
+ if (!udc->gadget_driver) {
+ dev_err(udc->dev, "%s: invalid device state\n", ep->name);
+ return -ESHUTDOWN;
+ }
+
+ if (((unsigned long)ep_req->usb_req.buf) & 0x3UL) {
+ dev_dbg(udc->dev, "%s: invalid buffer alignment: addr=0x%p\n",
+ ep->usb_ep.name, ep_req->usb_req.buf);
+
+ if ((ep->dma.aligned_buf) &&
+ (ep->dma.aligned_len < ep_req->usb_req.length)) {
+ dma_free_coherent(NULL, ep->dma.aligned_len,
+ ep->dma.aligned_buf,
+ ep->dma.aligned_addr);
+ ep->dma.aligned_buf = NULL;
+ }
+
+ if (!ep->dma.aligned_buf) {
+ ep->dma.aligned_len = ep_req->usb_req.length;
+ ep->dma.aligned_buf = dma_alloc_coherent(NULL,
+ ep->dma.aligned_len, &ep->dma.aligned_addr,
+ GFP_ATOMIC);
+ }
+
+ if (!ep->dma.aligned_buf) {
+ dev_err(udc->dev, "%s: ep dma alloc failed\n",
+ ep->name);
+ return -ENOMEM;
+ }
+
+ ep_req->dma_aligned = 1;
+ } else if ((ep_req->usb_req.dma == DMA_ADDR_INVALID) ||
+ (ep_req->usb_req.dma == 0)) {
+ ep_req->dma_mapped = 1;
+ ep_req->usb_req.dma = dma_map_single(
+ ep->udc->gadget.dev.parent,
+ ep_req->usb_req.buf,
+ (ep_req->usb_req.length ?
+ ep_req->usb_req.length : 1),
+ (ep->dirn == USB_DIR_IN ? DMA_TO_DEVICE : DMA_FROM_DEVICE));
+ if (dma_mapping_error(ep->udc->gadget.dev.parent,
+ ep_req->usb_req.dma)) {
+ dev_err(ep->udc->gadget.dev.parent,
+ "failed to map buffer\n");
+ return -EFAULT;
+ }
+ }
+
+ spin_lock_irqsave(&udc->lock, flags);
+
+ ep_req->usb_req.status = -EINPROGRESS;
+ ep_req->usb_req.actual = 0;
+
+ if ((ep->type == USB_ENDPOINT_XFER_CONTROL) &&
+ (ep->dirn == USB_DIR_OUT) &&
+ (ep_req->usb_req.length == 0)) {
+ epreq_xfer_done(ep, ep_req, 0);
+ } else {
+ if (ep_req->usb_req.length == 0)
+ ep_req->usb_req.zero = 1;
+
+ epreq_xfer_add(ep, ep_req);
+ }
+
+ spin_unlock_irqrestore(&udc->lock, flags);
+
+ return 0;
+}
+
+static int snps_ep_dequeue(struct usb_ep *usb_ep,
+ struct usb_request *usb_req)
+{
+ struct ep_xfer_req *ep_req;
+ struct snps_udc_ep *ep;
+ unsigned long flags;
+
+ ep = container_of(usb_ep, struct snps_udc_ep, usb_ep);
+ ep_req = container_of(usb_req, struct ep_xfer_req, usb_req);
+
+ if (!usb_ep || !usb_req) {
+ dev_err(ep->udc->dev, "%s: invalid dequeue request\n",
+ ep->name);
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&ep->udc->lock, flags);
+
+ list_for_each_entry(ep_req, &ep->queue, queue) {
+ if (&ep_req->usb_req == usb_req)
+ break;
+ }
+
+ if (&ep_req->usb_req != usb_req) {
+ spin_unlock_irqrestore(&ep->udc->lock, flags);
+ dev_err(ep->udc->dev, "%s: request not queued\n", ep->name);
+ return -ENOLINK;
+ }
+
+ epreq_xfer_done(ep, ep_req, -ECONNRESET);
+ spin_unlock_irqrestore(&ep->udc->lock, flags);
+
+ dev_dbg(ep->udc->dev, "%s: req=0x%p\n", ep->name, usb_req);
+ return 0;
+}
+
+static int snps_ep_set_halt(struct usb_ep *usb_ep, int halt)
+{
+ struct snps_udc_ep *ep;
+ unsigned long flags;
+ struct snps_udc *udc;
+
+ ep = container_of(usb_ep, struct snps_udc_ep, usb_ep);
+ udc = ep->udc;
+ if (!usb_ep) {
+ dev_err(udc->dev, "%s: invalid halt request\n", ep->name);
+ return -EINVAL;
+ }
+
+ if (ep->type == USB_ENDPOINT_XFER_ISOC) {
+ dev_err(udc->dev, "%s: unsupported halt req\n", ep->name);
+ return -EOPNOTSUPP;
+ }
+
+ if (halt && (ep->dirn == USB_DIR_IN) &&
+ !list_empty(&ep->queue)) {
+ dev_err(udc->dev, "%s: EP IN queue not empty\n", ep->name);
+ return -EAGAIN;
+ }
+
+ if (!halt && (ep->type == USB_ENDPOINT_XFER_CONTROL)) {
+ dev_err(udc->dev, "%s: CTRL HALT clear\n", ep->name);
+ return -EPROTO;
+ }
+
+ spin_lock_irqsave(&ep->udc->lock, flags);
+
+ if (!halt) {
+ disable_ep_stall(udc->regs, ep->num, ep->dirn);
+ } else if (ep->type != USB_ENDPOINT_XFER_CONTROL) {
+ enable_ep_stall(udc->regs, ep->num, ep->dirn);
+ } else {
+ enable_ep_stall(udc->regs, ep->num, USB_DIR_IN);
+ enable_ep_stall(udc->regs, ep->num, USB_DIR_OUT);
+ }
+
+ spin_unlock_irqrestore(&ep->udc->lock, flags);
+
+ dev_dbg(udc->dev, "%s: HALT %s done\n", ep->name,
+ halt ? "SET" : "CLR");
+
+ return 0;
+}
+
+static struct usb_ep_ops snps_ep_ops = {
+ .enable = snps_ep_enable,
+ .disable = snps_ep_disable,
+
+ .alloc_request = snps_ep_alloc_request,
+ .free_request = snps_ep_free_request,
+
+ .queue = snps_ep_queue,
+ .dequeue = snps_ep_dequeue,
+
+ .set_halt = snps_ep_set_halt,
+};
+
+static int eps_init(struct snps_udc *udc)
+{
+ struct snps_udc_ep *ep;
+ int i, ret;
+
+ /* Initialize Endpoint 0 */
+ ep = &udc->ep[0];
+ ep->udc = udc;
+ ep->num = 0;
+ ep->in_xfer_done = true;
+ ep->dirn = USB_DIR_OUT;
+ ep->b_ep_addr = ep->num | ep->dirn;
+ strncpy(ep->name, "ep0", sizeof(ep->name));
+ ep->usb_ep.name = ep->name;
+ ep->max_pkt_size = EP_CTRL_MAX_PKT_SIZE;
+ usb_ep_set_maxpacket_limit(&ep->usb_ep, EP_CTRL_MAX_PKT_SIZE);
+ ep->usb_ep.ops = &snps_ep_ops;
+ ep->stopped = 0;
+ ep->usb_ep.caps.type_control = true;
+ ep->usb_ep.caps.dir_in = true;
+ ep->usb_ep.caps.dir_out = true;
+ INIT_LIST_HEAD(&ep->queue);
+ ep->type = USB_ENDPOINT_XFER_CONTROL;
+ ep->usb_ep.maxpacket = EP_CTRL_MAX_PKT_SIZE;
+
+ if (udc->conn_type)
+ ep_dma_init(ep);
+
+ dev_dbg(udc->dev, "%s: type: 0x%x, Dir:0x%x, Max Size: %d\n",
+ ep->name, ep->type, ep->dirn, ep->max_pkt_size);
+
+ /* Initialize remaining endpoints */
+ for (i = 1; i < UDC_MAX_EP; i++) {
+ ep = &udc->ep[i];
+ ep->udc = udc;
+ ep->max_pkt_size = EP_MAX_PKT_SIZE;
+ usb_ep_set_maxpacket_limit(&ep->usb_ep, EP_MAX_PKT_SIZE);
+ ep->usb_ep.ops = &snps_ep_ops;
+ ep->in_xfer_done = true;
+ ep->num = i;
+ if (i % 2) {
+ snprintf(ep->name, sizeof(ep->name), "ep%din", i);
+ ep->dirn = EP_DIRN_IN;
+ ep->usb_ep.caps.dir_in = true;
+ } else {
+ snprintf(ep->name, sizeof(ep->name), "ep%dout", i);
+ ep->dirn = EP_DIRN_OUT;
+ ep->usb_ep.caps.dir_out = true;
+ }
+ ep->usb_ep.name = ep->name;
+ ep->b_ep_addr = ep->num | ep->dirn;
+
+ ep->usb_ep.caps.type_iso = true;
+ ep->usb_ep.caps.type_bulk = true;
+ ep->usb_ep.caps.type_int = true;
+ ep->stopped = 0;
+ ep->usb_ep.maxpacket = EP_MAX_PKT_SIZE;
+
+ INIT_LIST_HEAD(&ep->queue);
+ if (udc->conn_type)
+ ep_dma_init(ep);
+
+ dev_dbg(udc->dev, "%s: type: 0x%x, Dir: 0x%x, Max Size: %d\n",
+ ep->name, ep->type, ep->dirn, ep->max_pkt_size);
+ }
+
+ udc->rx_fifo_space = OUT_RX_FIFO_MEM_SIZE;
+ udc->tx_fifo_space = IN_TX_FIFO_MEM_SIZE;
+ ret = ep_cfg(&udc->ep[0], USB_ENDPOINT_XFER_CONTROL,
+ EP_CTRL_MAX_PKT_SIZE);
+ if (ret) {
+ dev_err(udc->dev, "Synopsys-UDC: error configuring endpoints\n");
+ return ret;
+ }
+
+ dev_dbg(udc->dev, "Synopsys UDC Endpoints initialized\n");
+ return 0;
+}
+
+static void start_udc(struct snps_udc *udc)
+{
+ int i;
+
+ init_udc_reg(udc->regs);
+
+ udc->rx_fifo_space = OUT_RX_FIFO_MEM_SIZE;
+ udc->tx_fifo_space = IN_TX_FIFO_MEM_SIZE;
+
+ eps_init(udc);
+ enable_self_pwr(udc->regs);
+
+ enable_udc_dev_irq(udc->regs, IRQ_SPEED_ENUM_DONE | IRQ_BUS_SUSPEND |
+ IRQ_BUS_IDLE | IRQ_BUS_RESET | IRQ_SET_INTF |
+ IRQ_SET_CFG);
+
+ for (i = 0; i < UDC_MAX_EP; ++i) {
+ if (udc->ep[i].usb_ep.name) {
+ enable_udc_ep_irq(udc->regs,
+ udc->ep[i].num, USB_DIR_OUT);
+ enable_udc_ep_irq(udc->regs,
+ udc->ep[i].num, USB_DIR_IN);
+ }
+ }
+
+ clear_devnak(udc->regs);
+ enable_ctrl_dma(udc->regs);
+ bus_connect(udc->regs);
+
+ dev_dbg(udc->dev, "Synopsys UDC started\n");
+}
+
+static void stop_udc(struct snps_udc *udc)
+{
+ finish_udc(udc->regs);
+
+ udc->gadget.speed = USB_SPEED_UNKNOWN;
+ epreq_queue_flush(&udc->ep[0], -ESHUTDOWN);
+ udc->ep[0].desc = NULL;
+
+ bus_disconnect(udc->regs);
+
+ if (udc->gadget_driver && udc->gadget_driver->disconnect) {
+ spin_unlock(&udc->lock);
+ udc->gadget_driver->disconnect(&udc->gadget);
+ spin_lock(&udc->lock);
+ }
+
+ dev_dbg(udc->dev, "Synopsys UDC stopped\n");
+}
+
+static int snps_gadget_pullup(struct usb_gadget *gadget, int is_on)
+{
+ struct snps_udc *udc;
+ unsigned long flags;
+
+ udc = container_of(gadget, struct snps_udc, gadget);
+
+ spin_lock_irqsave(&udc->lock, flags);
+
+ if (!udc->gadget_driver) {
+ spin_unlock_irqrestore(&udc->lock, flags);
+ return 0;
+ }
+
+ if (is_on && udc->pullup_on) {
+ start_udc(udc);
+ udc->ep[0].stopped = 0;
+ dev_info(udc->dev, "Synopsys UDC device connected\n");
+ } else if (!is_on && !udc->pullup_on) {
+ stop_udc(udc);
+ udc->ep[0].stopped = 1;
+ dev_info(udc->dev, "Synopsys UDC device Disconnected\n");
+ }
+
+ spin_unlock_irqrestore(&udc->lock, flags);
+
+ return 0;
+}
+
+static int snps_gadget_start(struct usb_gadget *gadget,
+ struct usb_gadget_driver *driver)
+{
+ struct snps_udc *udc;
+ unsigned long flags;
+
+ udc = container_of(gadget, struct snps_udc, gadget);
+
+ if (udc->gadget_driver)
+ return -EBUSY;
+
+ spin_lock_irqsave(&udc->lock, flags);
+
+ driver->driver.bus = NULL;
+ udc->gadget_driver = driver;
+ udc->gadget.dev.driver = &driver->driver;
+ udc->ep[0].stopped = 0;
+
+ spin_unlock_irqrestore(&udc->lock, flags);
+
+ /* when cable is connected at boot time */
+ if (udc->conn_type)
+ schedule_delayed_work(&udc->drd_work, USBD_WQ_DELAY_MS);
+ dev_dbg(udc->dev, "%s: Done\n", __func__);
+
+ return 0;
+}
+
+static int snps_gadget_stop(struct usb_gadget *gadget)
+{
+ struct snps_udc_ep *ep;
+ struct snps_udc *udc;
+ unsigned long flags;
+
+ udc = container_of(gadget, struct snps_udc, gadget);
+
+ spin_lock_irqsave(&udc->lock, flags);
+ stop_udc(udc);
+ udc->gadget.dev.driver = NULL;
+ udc->gadget_driver = NULL;
+
+ list_for_each_entry(ep, &udc->gadget.ep_list, usb_ep.ep_list) {
+ epreq_queue_flush(ep, -ESHUTDOWN);
+ if (ep->desc)
+ ep->desc = NULL;
+ }
+
+ spin_unlock_irqrestore(&udc->lock, flags);
+
+ dev_dbg(udc->dev, "%s: Done\n", __func__);
+
+ return 0;
+}
+
+static struct usb_gadget_ops snps_gadget_ops = {
+ .pullup = snps_gadget_pullup,
+ .udc_start = snps_gadget_start,
+ .udc_stop = snps_gadget_stop,
+};
+
+void snps_udc_drd_work(struct work_struct *work)
+{
+ struct snps_udc *udc;
+
+ udc = container_of(to_delayed_work(work),
+ struct snps_udc, drd_work);
+
+ if (udc->conn_type) {
+ dev_dbg(udc->dev, "idle -> device\n");
+ if (udc->gadget_driver) {
+ udc->pullup_on = 1;
+ snps_gadget_pullup(&udc->gadget, 1);
+ }
+ } else {
+ dev_dbg(udc->dev, "device -> idle\n");
+ udc->pullup_on = 0;
+ snps_gadget_pullup(&udc->gadget, 0);
+ }
+}
+
+static int usbd_connect_notify(struct notifier_block *self,
+ unsigned long event, void *ptr)
+{
+ struct snps_udc *udc = container_of(self, struct snps_udc, nb);
+
+ dev_dbg(udc->dev, "%s: event: %lu\n", __func__, event);
+
+ udc->conn_type = event;
+
+ schedule_delayed_work(&udc->drd_work, USBD_WQ_DELAY_MS);
+
+ return NOTIFY_OK;
+}
+
+static void free_udc_dma(struct platform_device *pdev, struct snps_udc *udc)
+{
+ u32 num;
+
+ dma_free_coherent(&pdev->dev, sizeof(struct ep_desc_array),
+ udc->dma.virt, (dma_addr_t)udc->dma.phys);
+
+ for (num = 0; num < UDC_MAX_EP; num++) {
+ if (udc->ep[num].dma.aligned_buf) {
+ dma_free_coherent(NULL, udc->ep[num].dma.aligned_len,
+ udc->ep[num].dma.aligned_buf,
+ udc->ep[num].dma.aligned_addr);
+ udc->ep[num].dma.aligned_buf = NULL;
+ }
+ }
+}
+
+static int snps_udc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ struct snps_udc *udc;
+ int i, ret;
+
+ udc = devm_kzalloc(dev, sizeof(*udc), GFP_KERNEL);
+ if (!udc)
+ return -ENOMEM;
+
+ spin_lock_init(&udc->lock);
+ udc->dev = dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ udc->regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(udc->regs))
+ return PTR_ERR(udc->regs);
+
+ udc->irq = irq_of_parse_and_map(dev->of_node, 0);
+ if (udc->irq <= 0) {
+ dev_err(dev, "Can't parse and map interrupt\n");
+ return -EINVAL;
+ }
+
+ udc->udc_phy = devm_phy_get(dev, "usb2drd");
+ if (IS_ERR(udc->udc_phy)) {
+ dev_err(dev, "Failed to obtain phy from device tree\n");
+ return PTR_ERR(udc->udc_phy);
+ }
+
+ ret = phy_init(udc->udc_phy);
+ if (ret) {
+ dev_err(dev, "UDC phy init failed");
+ return ret;
+ }
+
+ ret = phy_power_on(udc->udc_phy);
+ if (ret) {
+ dev_err(dev, "UDC phy power on failed");
+ phy_exit(udc->udc_phy);
+ return ret;
+ }
+
+ udc->edev = extcon_get_edev_by_phandle(dev, 0);
+ if (IS_ERR(udc->edev)) {
+ if (PTR_ERR(udc->edev) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ dev_err(dev, "Invalid or missing extcon\n");
+ ret = PTR_ERR(udc->edev);
+ goto exit_phy;
+ }
+
+ udc->nb.notifier_call = usbd_connect_notify;
+ ret = extcon_register_notifier(udc->edev, EXTCON_USB, &udc->nb);
+ if (ret < 0) {
+ dev_err(dev, "Can't register extcon device\n");
+ goto exit_phy;
+ }
+
+ ret = extcon_get_cable_state_(udc->edev, EXTCON_USB);
+ if (ret < 0) {
+ dev_err(dev, "Can't get cable state\n");
+ goto exit_extcon;
+ } else if (ret) {
+ udc->conn_type = ret;
+ }
+
+ udc->dma.virt = dma_alloc_coherent(&pdev->dev,
+ sizeof(struct ep_desc_array),
+ (dma_addr_t *)&udc->dma.phys,
+ GFP_KERNEL);
+ if (!udc->dma.virt) {
+ dev_err(dev, "Failed to allocate memory for ep\n");
+ ret = -ENOMEM;
+ goto exit_extcon;
+ }
+
+ INIT_DELAYED_WORK(&udc->drd_work, snps_udc_drd_work);
+
+ ret = devm_request_irq(dev, udc->irq, snps_udc_irq, IRQF_SHARED,
+ "snps-udc", udc);
+ if (ret < 0) {
+ dev_err(dev, "Request irq %d failed for UDC\n", udc->irq);
+ goto exit_dma;
+ }
+
+ /* Gagdet structure init */
+ udc->gadget.name = "snps-udc";
+ udc->gadget.speed = USB_SPEED_UNKNOWN;
+ udc->gadget.max_speed = USB_SPEED_HIGH;
+ udc->gadget.ops = &snps_gadget_ops;
+ udc->gadget.ep0 = &udc->ep[0].usb_ep;
+ INIT_LIST_HEAD(&udc->gadget.ep_list);
+
+ eps_init(udc);
+ for (i = 1; i < UDC_MAX_EP; i++) {
+ list_add_tail(&udc->ep[i].usb_ep.ep_list,
+ &udc->gadget.ep_list);
+ }
+
+ ret = usb_add_gadget_udc(&pdev->dev, &udc->gadget);
+ if (ret) {
+ dev_err(dev, "Error adding gadget udc: %d\n", ret);
+ goto exit_dma;
+ }
+
+ platform_set_drvdata(pdev, udc);
+ dev_info(dev, "Synopsys UDC driver probe successful\n");
+
+ return 0;
+exit_dma:
+ free_udc_dma(pdev, udc);
+exit_extcon:
+ extcon_unregister_notifier(udc->edev, EXTCON_USB, &udc->nb);
+exit_phy:
+ phy_power_off(udc->udc_phy);
+ phy_exit(udc->udc_phy);
+
+ return ret;
+}
+
+static int snps_udc_remove(struct platform_device *pdev)
+{
+ struct snps_udc *udc;
+
+ udc = platform_get_drvdata(pdev);
+
+ usb_del_gadget_udc(&udc->gadget);
+
+ platform_set_drvdata(pdev, NULL);
+
+ if (udc->drd_wq) {
+ flush_workqueue(udc->drd_wq);
+ destroy_workqueue(udc->drd_wq);
+ }
+
+ free_udc_dma(pdev, udc);
+ phy_power_off(udc->udc_phy);
+ phy_exit(udc->udc_phy);
+ extcon_unregister_notifier(udc->edev, EXTCON_USB, &udc->nb);
+
+ dev_info(&pdev->dev, "Synopsys UDC driver removed\n");
+
+ return 0;
+}
+
+static void snps_udc_shutdown(struct platform_device *pdev)
+{
+ struct snps_udc *udc = platform_get_drvdata(pdev);
+
+ snps_gadget_stop(&udc->gadget);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int snps_udc_suspend(struct device *dev)
+{
+ struct snps_udc *udc;
+
+ udc = dev_get_drvdata(dev);
+
+ if (extcon_get_cable_state_(udc->edev, EXTCON_USB) > 0) {
+ dev_dbg(udc->dev, "device -> idle\n");
+ snps_gadget_pullup(&udc->gadget, 0);
+ }
+ phy_power_off(udc->udc_phy);
+ phy_exit(udc->udc_phy);
+
+ return 0;
+}
+
+static int snps_udc_resume(struct device *dev)
+{
+ struct snps_udc *udc;
+ int ret;
+
+ udc = dev_get_drvdata(dev);
+
+ ret = phy_init(udc->udc_phy);
+ if (ret) {
+ dev_err(udc->dev, "UDC phy init failure");
+ return ret;
+ }
+
+ ret = phy_power_on(udc->udc_phy);
+ if (ret) {
+ dev_err(udc->dev, "UDC phy power on failure");
+ phy_exit(udc->udc_phy);
+ return ret;
+ }
+
+ if (extcon_get_cable_state_(udc->edev, EXTCON_USB) > 0) {
+ dev_dbg(udc->dev, "idle -> device\n");
+ snps_gadget_pullup(&udc->gadget, 1);
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops snps_udc_pm_ops = {
+ .suspend = snps_udc_suspend,
+ .resume = snps_udc_resume,
+};
+#endif
+
+static const struct of_device_id of_udc_match[] = {
+ { .compatible = "snps,dw-ahb-udc", },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, of_udc_match);
+
+static struct platform_driver snps_udc_driver = {
+ .probe = snps_udc_probe,
+ .remove = snps_udc_remove,
+ .shutdown = snps_udc_shutdown,
+ .driver = {
+ .name = "snps-udc",
+ .of_match_table = of_match_ptr(of_udc_match),
+#ifdef CONFIG_PM_SLEEP
+ .pm = &snps_udc_pm_ops,
+#endif
+ },
+};
+
+module_platform_driver(snps_udc_driver);
+
+MODULE_ALIAS("platform:snps-udc");
+MODULE_AUTHOR("Broadcom");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/gadget/udc/snps_udc.h b/drivers/usb/gadget/udc/snps_udc.h
new file mode 100644
index 0000000..0355d59
--- /dev/null
+++ b/drivers/usb/gadget/udc/snps_udc.h
@@ -0,0 +1,1071 @@
+/*
+ * Copyright (C) 2016 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SNPS_UDC_H
+#define __SNPS_UDC_H
+
+/* UDC speeds */
+#define SPEED_UNKNOWN (0)
+#define SPEED_LOW (1)
+#define SPEED_FULL (2)
+#define SPEED_HIGH (3)
+
+/* Endpoint directions */
+#define EP_DIRN_IN (0x80)
+#define EP_DIRN_OUT (0x00)
+#define EP_DIRN_MASK (0x80)
+
+/* Endpoint types */
+#define EP_TYPE_CTRL (0)
+#define EP_TYPE_ISOC (1)
+#define EP_TYPE_BULK (2)
+#define EP_TYPE_INTR (3)
+#define EP_TYPE_MASK (0x03)
+
+/* Max supported endpoints */
+#define UDC_MAX_EP (10)
+
+#define EP_MAX_PKT_SIZE 512
+#define EP_CTRL_MAX_PKT_SIZE 64
+#define OUT_RX_FIFO_MEM_SIZE 4096
+#define IN_TX_FIFO_MEM_SIZE 4096
+
+#define is_ep_in() ((ep->dirn) == USB_DIR_IN)
+#define is_ep_out() ((ep->dirn) == USB_DIR_OUT)
+#define is_ep_bulk() ((ep->type) == USB_ENDPOINT_XFER_BULK)
+
+#define DESC_CNT (1)
+
+#define EP_DMA_DESC_IDX_MASK (DESC_CNT - 1)
+#define EP_DMA_DESC_IDX(num) ((num) & EP_DMA_DESC_IDX_MASK)
+
+#define USB_MODE_IDLE (1)
+#define USB_MODE_DEVICE (2)
+
+#define FIFO_SZ_U32(pkt_sz) (((pkt_sz) + 3) / sizeof(u32))
+#define FIFO_SZ_U8(sz) (FIFO_SZ_U32(sz) * sizeof(u32))
+#define USBD_WQ_DELAY_MS msecs_to_jiffies(100)
+/* Register Masks and definitions */
+
+/* Endpoint Control Registers*/
+#define EP_CTRL_OUT_FLUSH_ENABLE BIT(12)
+#define EP_CTRL_OUT_CLOSE_DESC BIT(11)
+#define EP_CTRL_IN_SEND_NULL BIT(10)
+#define EP_CTRL_OUT_DMA_ENABLE BIT(9)
+#define EP_CTRL_NAK_CLEAR BIT(8)
+#define EP_CTRL_NAK_SET BIT(7)
+#define EP_CTRL_NAK_IN_PROGRESS BIT(6)
+#define EP_CTRL_TYPE_SHIFT (4)
+#define EP_CTRL_TYPE_MASK (3 << EP_CTRL_TYPE_SHIFT)
+#define EP_CTRL_IN_DMA_ENABLE BIT(3)
+#define EP_CTRL_SNOOP_ENABLE BIT(2)
+#define EP_CTRL_IN_FLUSH_ENABLE BIT(1)
+#define EP_CTRL_STALL_ENABLE BIT(0)
+
+/* Endpoint Status Registers */
+#define EP_STS_CLOSE_DESC_CLEAR BIT(28)
+#define EP_STS_IN_XFER_DONE BIT(27)
+#define EP_STS_STALL_SET_RX BIT(26)
+#define EP_STS_STALL_CLEAR_RX BIT(25)
+#define EP_STS_IN_FIFO_EMPTY BIT(24)
+#define EP_STS_IN_DMA_DONE BIT(10)
+#define EP_STS_AHB_BUS_ERROR BIT(9)
+#define EP_STS_OUT_FIFO_EMPTY BIT(8)
+#define EP_STS_DMA_BUF_NOT_AVAIL BIT(7)
+#define EP_STS_IN_TOKEN_RX BIT(6)
+#define EP_STS_OUT_DMA_SETUP_DONE BIT(5)
+#define EP_STS_OUT_DMA_DATA_DONE BIT(4)
+
+/* Buffer Regs for EP In, Receive Packet Frame Num Regs for EP Out */
+#define EP_REG2_OUT_ISOC_PID_SHIFT (16)
+#define EP_REG2_OUT_ISOC_PID_MASK (3 << EP_REG2_OUT_ISOC_PID_SHIFT)
+#define EP_REG2_IN_DEPTH_SHIFT (0)
+#define EP_REG2_IN_DEPTH_MASK (0xffff << EP_REG2_IN_DEPTH_SHIFT)
+#define EP_REG2_OUT_FRAME_NUM_SHIFT EP_REG2_IN_DEPTH_SHIFT
+#define EP_REG2_OUT_FRAME_NUM_MASK EP_REG2_IN_DEPTH_MASK
+
+/* Max Packet Size Regs for EP In, Buffer Size Regs for EP Out */
+#define EP_REG3_OUT_DEPTH_SHIFT (16)
+#define EP_REG3_OUT_DEPTH_MASK (0xffff << EP_REG3_OUT_DEPTH_SHIFT)
+#define EP_REG3_PKT_MAX_SHIFT (0)
+#define EP_REG3_PKT_MAX_MASK (0xffff << EP_REG3_PKT_MAX_SHIFT)
+
+/* Endpoint Config Registers */
+#define EP_CFG_DIRN_IN BIT(4)
+#define EP_CFG_DIRN_OUT (0)
+#define EP_CFG_PKT_MAX_SHIFT (19)
+#define EP_CFG_PKT_MAX_MASK (0x7ff << EP_CFG_PKT_MAX_SHIFT)
+#define EP_CFG_ALT_NUM_SHIFT (15)
+#define EP_CFG_ALT_NUM_MASK (0xf << EP_CFG_ALT_NUM_SHIFT)
+#define EP_CFG_INTF_NUM_SHIFT (11)
+#define EP_CFG_INTF_NUM_MASK (0xf << EP_CFG_INTF_NUM_SHIFT)
+#define EP_CFG_CFG_NUM_SHIFT (7)
+#define EP_CFG_CFG_NUM_MASK (0xf << EP_CFG_CFG_NUM_SHIFT)
+#define EP_CFG_TYPE_SHIFT (5)
+#define EP_CFG_TYPE_MASK (0x3 << EP_CFG_TYPE_SHIFT)
+#define EP_CFG_FIFO_NUM_SHIFT (0)
+#define EP_CFG_FIFO_NUM_MASK (0xf << EP_CFG_FIFO_NUM_SHIFT)
+
+/* Endpoint Interrupt Registers */
+#define EP_INTR_OUT_SHIFT (16)
+#define EP_INTR_OUT_MASK (0xffff << EP_INTR_OUT_SHIFT)
+#define EP_INTR_IN_SHIFT (0)
+#define EP_INTR_IN_MASK (0xffff << EP_INTR_IN_SHIFT)
+
+/* Device Config Register */
+#define CFG_ULPI_DDR_ENABLE BIT(19)
+#define CFG_SET_DESCRIPTOR_ENABLE BIT(18)
+#define CFG_CSR_PROGRAM_ENABLE BIT(17)
+#define CFG_HALT_STALL_ENABLE BIT(16)
+#define CFG_HS_TIMEOUT_CALIB_SHIFT (13)
+#define CFG_HS_TIMEOUT_CALIB_MASK (7 << CFG_HS_TIMEOUT_CALIB_SHIFT)
+#define CFG_FS_TIMEOUT_CALIB_SHIFT (10)
+#define CFG_FS_TIMEOUT_CALIB_MASK (7 << CFG_FS_TIMEOUT_CALIB_SHIFT)
+#define CFG_STS_1_ENABLE BIT(8)
+#define CFG_STS_ENABLE BIT(7)
+#define CFG_UTMI_BI_DIRN_ENABLE BIT(6)
+#define CFG_UTMI_8BIT_ENABLE BIT(5)
+#define CFG_SYNC_FRAME_ENABLE BIT(4)
+#define CFG_SELF_PWR_ENABLE BIT(3)
+#define CFG_REMOTE_WAKEUP_ENABLE BIT(2)
+#define CFG_SPD_SHIFT (0)
+#define CFG_SPD_MASK (3 << CFG_SPD_SHIFT)
+#define CFG_SPD_HS (0 << CFG_SPD_SHIFT)
+#define CFG_SPD_FS BIT(0)
+#define CFG_SPD_LS (2 << CFG_SPD_SHIFT)
+#define CFG_SPD_FS_48MHZ (3 << CFG_SPD_SHIFT)
+
+/* Device Control Register*/
+#define CTRL_DMA_OUT_THRESH_LEN_SHIFT (24)
+#define CTRL_DMA_OUT_THRESH_LEN_MASK (0xff << CTRL_DMA_OUT_THRESH_LEN_SHIFT)
+#define CTRL_DMA_BURST_LEN_SHIFT (16)
+#define CTRL_DMA_BURST_LEN_MASK (0xff << CTRL_DMA_BURST_LEN_SHIFT)
+#define CTRL_OUT_FIFO_FLUSH_ENABLE BIT(14)
+#define CTRL_CSR_DONE BIT(13)
+#define CTRL_OUT_ALL_NAK BIT(12)
+#define CTRL_DISCONNECT_ENABLE BIT(10)
+#define CTRL_DMA_MODE_ENABLE BIT(9)
+#define CTRL_DMA_BURST_ENABLE BIT(8)
+#define CTRL_DMA_OUT_THRESH_ENABLE BIT(7)
+#define CTRL_DMA_BUFF_FILL_MODE_ENABLE BIT(6)
+#define CTRL_ENDIAN_BIG_ENABLE BIT(5)
+#define CTRL_DMA_DESC_UPDATE_ENABLE BIT(4)
+#define CTRL_DMA_IN_ENABLE BIT(3)
+#define CTRL_DMA_OUT_ENABLE BIT(2)
+#define CTRL_RESUME_SIGNAL_ENABLE BIT(0)
+#define CTRL_LE_ENABLE (0)
+
+/* Device Status Register */
+#define STS_SOF_FRAME_NUM_SHIFT (18)
+#define STS_SOF_FRAME_NUM_MASK (0x3ffff << STS_SOF_FRAME_NUM_SHIFT)
+#define STS_REMOTE_WAKEUP_ALLOWED BIT(17)
+#define STS_PHY_ERROR BIT(16)
+#define STS_OUT_FIFO_EMPTY BIT(15)
+#define STS_SPD_SHIFT (13)
+#define STS_SPD_MASK (3 << STS_SPD_SHIFT)
+#define STS_SPD_HS (0 << STS_SPD_SHIFT)
+#define STS_SPD_FS BIT(13)
+#define STS_SPD_LS (2 << STS_SPD_SHIFT)
+#define STS_SPD_FS_48MHZ (3 << STS_SPD_SHIFT)
+#define STS_BUS_SUSPENDED BIT(12)
+#define STS_ALT_NUM_SHIFT (8)
+#define STS_ALT_NUM_MASK (0xf << STS_SPD_SHIFT)
+#define STS_INTF_NUM_SHIFT (4)
+#define STS_INTF_NUM_MASK (0xf << STS_INTF_NUM_SHIFT)
+#define STS_CFG_NUM_SHIFT (0)
+#define STS_CFG_NUM_MASK (0xf << STS_CFG_NUM_SHIFT)
+
+/* Device Interrupt Register */
+#define INTR_REMOTE_WAKEUP_DELTA BIT(7)
+#define INTR_SPD_ENUM_DONE BIT(6)
+#define INTR_SOF_RX BIT(5)
+#define INTR_BUS_SUSPEND BIT(4)
+#define INTR_BUS_RESET BIT(3)
+#define INTR_BUS_IDLE BIT(2)
+#define INTR_SET_INTF_RX BIT(1)
+#define INTR_SET_CFG_RX BIT(0)
+
+#define DMA_STS_BUF_SHIFT (30)
+#define DMA_STS_BUF_HOST_READY (0 << DMA_STS_BUF_SHIFT)
+#define DMA_STS_BUF_DMA_BUSY BIT(30)
+#define DMA_STS_BUF_DMA_DONE (2 << DMA_STS_BUF_SHIFT)
+#define DMA_STS_BUF_HOST_BUSY (3 << DMA_STS_BUF_SHIFT)
+#define DMA_STS_BUF_MASK (3 << DMA_STS_BUF_SHIFT)
+#define DMA_STS_RX_SHIFT (28)
+#define DMA_STS_RX_SUCCESS (0 << DMA_STS_RX_SHIFT)
+#define DMA_STS_RX_ERR_DESC BIT(28)
+#define DMA_STS_RX_ERR_BUF (3 << DMA_STS_RX_SHIFT)
+#define DMA_STS_RX_MASK (3 << DMA_STS_RX_SHIFT)
+#define DMA_STS_CFG_NUM_SHIFT (24)
+#define DMA_STS_CFG_NUM_MASK (0xf << DMA_STS_CFG_NUM_SHIFT)
+#define DMA_STS_INTF_NUM_SHIFT (20)
+#define DMA_STS_INTF_NUM_MASK (0xf << DMA_STS_INTF_NUM_SHIFT)
+#define DMA_STS_LAST_DESC BIT(27)
+#define DMA_STS_FRAME_NUM_SHIFT (16)
+#define DMA_STS_FRAME_NUM_MASK (0x7ff << DMA_STS_FRAME_NUM_SHIFT)
+#define DMA_STS_BYTE_CNT_SHIFT (0)
+#define DMA_STS_ISO_PID_SHIFT (14)
+#define DMA_STS_ISO_PID_MASK (0x3 << DMA_STS_ISO_PID_SHIFT)
+#define DMA_STS_ISO_BYTE_CNT_SHIFT (DMA_STS_BYTE_CNT_SHIFT)
+#define DMA_STS_ISO_BYTE_CNT_MASK (0x3fff << DMA_STS_ISO_BYTE_CNT_SHIFT)
+#define DMA_STS_NISO_BYTE_CNT_SHIFT (DMA_STS_BYTE_CNT_SHIFT)
+#define DMA_STS_NISO_BYTE_CNT_MASK (0xffff << DMA_STS_NISO_BYTE_CNT_SHIFT)
+
+/* UDC Interrupts */
+#define UDC_IRQ_ALL (IRQ_REMOTEWAKEUP_DELTA | \
+ IRQ_SPEED_ENUM_DONE | \
+ IRQ_BUS_SUSPEND | \
+ IRQ_BUS_RESET | \
+ IRQ_BUS_IDLE | \
+ IRQ_SET_INTF | \
+ IRQ_SET_CFG)
+#define IRQ_REMOTEWAKEUP_DELTA INTR_REMOTE_WAKEUP_DELTA
+#define IRQ_SPEED_ENUM_DONE INTR_SPD_ENUM_DONE
+#define IRQ_SOF_DETECTED INTR_SOF_RX
+#define IRQ_BUS_SUSPEND INTR_BUS_SUSPEND
+#define IRQ_BUS_RESET INTR_BUS_RESET
+#define IRQ_BUS_IDLE INTR_BUS_IDLE
+#define IRQ_SET_INTF INTR_SET_INTF_RX
+#define IRQ_SET_CFG INTR_SET_CFG_RX
+
+/* Endpoint status */
+#define EP_STS_ALL (DMA_ERROR | \
+ DMA_BUF_NOT_AVAIL | \
+ IN_TOKEN_RX | \
+ IN_DMA_DONE | \
+ IN_XFER_DONE | \
+ OUT_DMA_DATA_DONE | \
+ OUT_DMA_SETUP_DONE)
+
+#define DMA_ERROR EP_STS_AHB_BUS_ERROR
+#define DMA_BUF_NOT_AVAIL EP_STS_DMA_BUF_NOT_AVAIL
+#define IN_TOKEN_RX EP_STS_IN_TOKEN_RX
+#define IN_DMA_DONE EP_STS_IN_DMA_DONE
+#define IN_FIFO_EMPTY EP_STS_IN_FIFO_EMPTY
+#define IN_XFER_DONE EP_STS_IN_XFER_DONE
+#define OUT_DMA_DATA_DONE EP_STS_OUT_DMA_DATA_DONE
+#define OUT_DMA_SETUP_DONE EP_STS_OUT_DMA_SETUP_DONE
+
+#define DMA_ADDR_INVALID (~(dma_addr_t)0)
+#define DIRN_STR(dirn) ((dirn) == USB_DIR_IN ? "IN" : "OUT")
+#define EP_DIRN_TYPE(d, t) (((d) << 8) | (t))
+
+/* Used for ISOC IN transfers for frame alignment. */
+#define FRAME_NUM_INVALID (~(u32)0)
+
+/* UDC config parameters */
+
+/* If multiple RX FIFO controllers are implemented for
+ * OUT Endpoints, MRX_FIFO is enabled.
+ * Multi RX FIFO controllers are not implemented in RTL.
+ */
+#define MRX_FIFO 0
+#if MRX_FIFO
+static bool mrx_fifo = true;
+#else
+static bool mrx_fifo;
+#endif
+
+/* Buffer Fill mode is enabled for IN transfers,
+ * disabled for OUT transfers.
+ */
+#define IN_DMA_BUF_FILL_EN 1
+#if IN_DMA_BUF_FILL_EN
+static bool in_bf_mode = true;
+#else
+static bool in_bf_mode;
+#endif
+
+#define OUT_DMA_BUF_FILL_EN 0
+#if OUT_DMA_BUF_FILL_EN
+static bool out_bf_mode = true;
+#else
+static bool out_bf_mode;
+#endif
+/*
+ * If it desired that frames start being DMA'd w/o frame
+ * alignment, define ISOC_IN_XFER_DELAY_DISABLE.
+ * If frame alignment is used, this delay is not disabled.
+ */
+#define ISOC_IN_XFER_DELAY_DISABLE 0
+#if ISOC_IN_XFER_DELAY_DISABLE
+static bool in_isoc_delay_disabled = true;
+#else
+static bool in_isoc_delay_disabled;
+#endif
+
+/* Endpoint IN/OUT registers
+ * Register space is reserved for 16 endpoints, but the controller
+ * actually supports 10 endpoints only.
+ */
+#define EP_CNT (16)
+struct snps_ep_regs {
+ u32 ctrl; /* EP control */
+ u32 status; /* EP status */
+ u32 epreg2; /* Buffer for IN, Rec Pkt Frame num for OUT */
+ u32 epreg3; /* Max pkt size for IN, Buf size for OUT */
+ u32 setupbuf; /* Rsvd for IN, EP setup buffer ptr for OUT */
+ u32 datadesc; /* EP data descriptor pointer */
+ u32 rsvd[2];
+};
+
+/* UDC registers */
+struct snps_udc_regs {
+ struct snps_ep_regs ep_in[EP_CNT];
+ struct snps_ep_regs ep_out[EP_CNT];
+ u32 devcfg;
+ u32 devctrl;
+ u32 devstatus;
+ u32 devintrstat;
+ u32 devintrmask;
+ u32 epintrstat;
+ u32 epintrmask;
+ u32 testmode;
+ u32 releasenum;
+ u32 rsvd[56];
+ u32 epcfg[EP_CNT];
+ u32 rsvd1[175];
+ u32 rx_fifo[256];
+ u32 tx_fifo[256];
+ u32 strap;
+};
+
+/* Endpoint SETUP buffer */
+struct setup_desc {
+ u32 status;
+ u32 reserved;
+ u32 data1;
+ u32 data2;
+};
+
+/* Endpoint In/Out data descriptor */
+struct data_desc {
+ u32 status;
+ u32 reserved;
+ u32 buf_addr;
+ u32 next_desc_addr;
+};
+
+/* Endpoint descriptor layout. */
+struct ep_dma_desc {
+ struct setup_desc setup;
+ struct data_desc desc[DESC_CNT];
+};
+
+/* Endpoint descriptor array for Synopsys UDC */
+struct ep_desc_array {
+ struct ep_dma_desc ep[UDC_MAX_EP];
+};
+
+struct snps_udc;
+
+/* Endpoint data structure (for each endpoint) */
+struct snps_udc_ep {
+ struct usb_ep usb_ep;
+ const struct usb_endpoint_descriptor *desc;
+ struct list_head queue;
+ struct snps_udc *udc;
+ char name[14];
+ bool in_xfer_done;
+ u32 num;
+ u32 dirn;
+ u32 type; /* USB_ENDPOINT_XFER_xxx */
+ u32 b_ep_addr; /* dirn | type */
+ u32 max_pkt_size;
+ u32 rx_fifo_size; /* Rx FIFO ram allocated */
+ u32 tx_fifo_size; /* Tx FIFO ram allocated */
+ u32 stopped:1;
+ struct {
+ struct ep_dma_desc *virt;
+ struct ep_dma_desc *phys;
+ struct usb_request *usb_req;/* Current request being DMA'd */
+ u32 len_max; /* to use with a descriptor */
+ u32 len_done; /* Length of request DMA'd so far */
+ u32 len_rem; /* Length of request left to DMA */
+ u32 add_idx; /* descriptor chain index */
+ u32 remove_idx; /* descriptor chain index */
+ u32 buf_addr; /* Location in request to DMA */
+ u32 frame_num; /* Frame number for ISOC transfers */
+ u32 frame_incr; /* Frame number increment (period) */
+ u32 status;
+ u32 done; /* DMA/USB xfer completion indication */
+ void *aligned_buf; /* used if usb_req buf not aligned */
+ dma_addr_t aligned_addr;/* Aligned buffer physical address */
+ u32 aligned_len; /* Aligned buffer length */
+ u32 last;
+ } dma;
+};
+
+/* Endpoint xfer request structure */
+struct ep_xfer_req {
+ struct usb_request usb_req;
+ struct list_head queue;
+ dma_addr_t dma_addr_orig;
+ u32 dma_mapped:1;
+ u32 dma_aligned:1;
+};
+
+/* Controller data structure */
+struct snps_udc {
+ struct usb_gadget gadget;
+ struct usb_gadget_driver *gadget_driver;
+ struct device *dev;
+ void __iomem *regs;
+ int irq;
+ struct completion *dev_release;
+ spinlock_t lock; /* UDC spin lock variable */
+ u32 rx_fifo_space;
+ u32 tx_fifo_space;
+ struct snps_udc_ep ep[UDC_MAX_EP];
+ struct {
+ struct ep_desc_array *virt;
+ struct ep_desc_array *phys;
+ } dma;
+ struct gpio_desc *vbus_gpiod;
+ u32 vbus_active:1;
+ u32 pullup_on:1;
+ struct phy *udc_phy;
+ u32 mode;
+ struct extcon_dev *edev;
+ struct extcon_specific_cable_nb extcon_nb;
+ struct notifier_block nb;
+ struct delayed_work drd_work;
+ struct workqueue_struct *drd_wq;
+ u32 conn_type;
+};
+
+#define REG_WR(reg, val) writel(val, ®)
+#define REG_MOD_AND(reg, val) writel(val & readl(®), ®)
+#define REG_MOD_OR(reg, val) writel(val | readl(®), ®)
+#define REG_MOD_MASK(reg, mask, val) writel(val | (mask & readl(®)), ®)
+#define REG_RD(reg) readl(®)
+
+static inline void dump_regs(struct snps_udc_regs *regs)
+{
+ pr_debug("DEVCFG: 0x%x\n", REG_RD(regs->devcfg));
+ pr_debug("DEVCTRL: 0x%x\n", REG_RD(regs->devctrl));
+ pr_debug("DEVSTS: 0x%x\n", REG_RD(regs->devstatus));
+ pr_debug("DEVINTRMASK: 0x%x\n", REG_RD(regs->devintrmask));
+ pr_debug("DEVINTRSTS: 0x%x\n", REG_RD(regs->devintrstat));
+ pr_debug("EPINTRMASK: 0x%x\n", REG_RD(regs->epintrmask));
+ pr_debug("EPINTRSTS: 0x%x\n", REG_RD(regs->epintrstat));
+}
+
+static inline void bus_connect(struct snps_udc_regs *regs)
+{
+ REG_MOD_AND(regs->devctrl, ~CTRL_DISCONNECT_ENABLE);
+}
+
+static inline void bus_disconnect(struct snps_udc_regs *regs)
+{
+ REG_MOD_OR(regs->devctrl, CTRL_DISCONNECT_ENABLE);
+}
+
+static inline bool is_bus_suspend(struct snps_udc_regs *regs)
+{
+ return REG_RD(regs->devstatus) &
+ STS_BUS_SUSPENDED ? true : false;
+}
+
+static inline u32 get_alt_num(struct snps_udc_regs *regs)
+{
+ return (REG_RD(regs->devstatus) & STS_ALT_NUM_MASK)
+ >> STS_ALT_NUM_SHIFT;
+}
+
+static inline u32 get_cfg_num(struct snps_udc_regs *regs)
+{
+ return (REG_RD(regs->devstatus) & STS_CFG_NUM_MASK)
+ >> STS_CFG_NUM_SHIFT;
+}
+
+static inline u32 get_intf_num(struct snps_udc_regs *regs)
+{
+ return (REG_RD(regs->devstatus) & STS_INTF_NUM_MASK)
+ >> STS_INTF_NUM_SHIFT;
+}
+
+static inline void disable_ctrl_dma(struct snps_udc_regs *regs)
+{
+ REG_MOD_AND(regs->devctrl, ~(CTRL_DMA_IN_ENABLE |
+ CTRL_DMA_OUT_ENABLE));
+}
+
+static inline void enable_ctrl_dma(struct snps_udc_regs *regs)
+{
+ REG_MOD_OR(regs->devctrl, (CTRL_DMA_IN_ENABLE |
+ CTRL_DMA_OUT_ENABLE));
+}
+
+static inline bool is_ctrl_dma_enable(struct snps_udc_regs *regs)
+{
+ return REG_RD(regs->devctrl) &
+ CTRL_DMA_OUT_ENABLE ? true : false;
+}
+
+static inline void disable_epin_dma(struct snps_udc_regs *regs)
+{
+ REG_MOD_AND(regs->devctrl, ~(CTRL_DMA_IN_ENABLE));
+}
+
+static inline void enable_epin_dma(struct snps_udc_regs *regs)
+{
+ REG_MOD_OR(regs->devctrl, (CTRL_DMA_IN_ENABLE));
+}
+
+static inline bool is_epin_dma_enable(struct snps_udc_regs *regs)
+{
+ return REG_RD(regs->devctrl) &
+ CTRL_DMA_IN_ENABLE ? true : false;
+}
+
+static inline void disable_epout_dma(struct snps_udc_regs *regs)
+{
+ REG_MOD_AND(regs->devctrl, ~(CTRL_DMA_OUT_ENABLE));
+}
+
+static inline void enable_epout_dma(struct snps_udc_regs *regs)
+{
+ REG_MOD_OR(regs->devctrl, (CTRL_DMA_OUT_ENABLE));
+}
+
+static inline bool is_epout_dma_enable(struct snps_udc_regs *regs)
+{
+ return REG_RD(regs->devctrl) &
+ CTRL_DMA_OUT_ENABLE ? true : false;
+}
+
+static inline u32 get_frnum_last_rx(struct snps_udc_regs *regs)
+{
+ return (REG_RD(regs->devstatus) &
+ STS_SOF_FRAME_NUM_MASK) >> STS_SOF_FRAME_NUM_SHIFT;
+}
+
+static inline u32 get_irq_active(struct snps_udc_regs *regs)
+{
+ return REG_RD(regs->devintrstat);
+}
+
+static inline void clear_udc_dev_irq(struct snps_udc_regs *regs, u32 mask)
+{
+ REG_WR(regs->devintrstat, mask);
+}
+
+static inline void disable_udc_dev_irq(struct snps_udc_regs *regs, u32 mask)
+{
+ REG_MOD_OR(regs->devintrmask, mask);
+}
+
+static inline void enable_udc_dev_irq(struct snps_udc_regs *regs, u32 mask)
+{
+ REG_MOD_AND(regs->devintrmask, ~mask);
+}
+
+static inline u32 mask_irq(struct snps_udc_regs *regs)
+{
+ return (~REG_RD(regs->devintrmask)) & UDC_IRQ_ALL;
+}
+
+static inline void clear_devnak(struct snps_udc_regs *regs)
+{
+ REG_MOD_AND(regs->devctrl, ~CTRL_OUT_ALL_NAK);
+}
+
+static inline void set_devnak(struct snps_udc_regs *regs)
+{
+ REG_MOD_OR(regs->devctrl, CTRL_OUT_ALL_NAK);
+}
+
+static inline bool is_phy_error(struct snps_udc_regs *regs)
+{
+ return REG_RD(regs->devstatus) &
+ STS_PHY_ERROR ? true : false;
+}
+
+static inline bool is_rmtwkp(struct snps_udc_regs *regs)
+{
+ return REG_RD(regs->devstatus) &
+ STS_REMOTE_WAKEUP_ALLOWED ? true : false;
+}
+
+static inline void clear_rmtwkup(struct snps_udc_regs *regs)
+{
+ REG_MOD_AND(regs->devcfg, ~CFG_REMOTE_WAKEUP_ENABLE);
+}
+
+static inline void set_rmtwkp(struct snps_udc_regs *regs)
+{
+ REG_MOD_OR(regs->devcfg, CFG_REMOTE_WAKEUP_ENABLE);
+}
+
+static inline void start_rmtwkp(struct snps_udc_regs *regs)
+{
+ REG_MOD_OR(regs->devctrl, CTRL_RESUME_SIGNAL_ENABLE);
+}
+
+static inline void stop_rmtwkp(struct snps_udc_regs *regs)
+{
+ REG_MOD_AND(regs->devctrl, ~CTRL_RESUME_SIGNAL_ENABLE);
+}
+
+static inline void disable_self_pwr(struct snps_udc_regs *regs)
+{
+ REG_MOD_AND(regs->devcfg, ~CFG_SELF_PWR_ENABLE);
+}
+
+static inline void enable_self_pwr(struct snps_udc_regs *regs)
+{
+ REG_MOD_OR(regs->devcfg, CFG_SELF_PWR_ENABLE);
+}
+
+static inline void disable_set_desc(struct snps_udc_regs *regs)
+{
+ REG_MOD_AND(regs->devcfg, ~CFG_SET_DESCRIPTOR_ENABLE);
+}
+
+static inline void enable_set_desc(struct snps_udc_regs *regs)
+{
+ REG_MOD_OR(regs->devcfg, CFG_SET_DESCRIPTOR_ENABLE);
+}
+
+static inline void set_setup_done(struct snps_udc_regs *regs)
+{
+ REG_MOD_OR(regs->devctrl, CTRL_CSR_DONE);
+}
+
+static inline u32 get_enum_speed(struct snps_udc_regs *regs)
+{
+ switch (REG_RD(regs->devstatus) & STS_SPD_MASK) {
+ case STS_SPD_LS:
+ return SPEED_LOW;
+ case STS_SPD_HS:
+ return SPEED_HIGH;
+ case STS_SPD_FS:
+ case STS_SPD_FS_48MHZ:
+ return SPEED_FULL;
+ default:
+ return 0;
+ }
+}
+
+static inline void set_speed_requested(struct snps_udc_regs *regs, u32 speed)
+{
+ REG_MOD_AND(regs->devcfg, ~CFG_SPD_MASK);
+
+ switch (speed) {
+ case SPEED_LOW:
+ REG_MOD_OR(regs->devcfg, CFG_SPD_LS);
+ break;
+
+ case SPEED_HIGH:
+ REG_MOD_OR(regs->devcfg, CFG_SPD_HS);
+ break;
+
+ case SPEED_FULL:
+ default:
+ REG_MOD_OR(regs->devcfg, CFG_SPD_FS);
+ break;
+ }
+}
+
+static inline void init_ep_reg(struct snps_udc_regs *regs, u32 num, u32 type,
+ u32 dirn, u32 max_pkt_size)
+{
+ if ((type == EP_TYPE_CTRL) || (dirn == EP_DIRN_OUT)) {
+ REG_WR(regs->ep_out[num].ctrl,
+ (type << EP_CTRL_TYPE_SHIFT));
+ REG_WR(regs->ep_out[num].status,
+ regs->ep_out[num].status);
+ REG_WR(regs->ep_out[num].epreg2, 0);
+ REG_WR(regs->ep_out[num].epreg3,
+ ((max_pkt_size >> 2) << 16) | max_pkt_size);
+
+ if (mrx_fifo)
+ REG_MOD_OR(regs->ep_out[num].epreg3,
+ (FIFO_SZ_U32(max_pkt_size) <<
+ EP_REG3_OUT_DEPTH_SHIFT));
+ }
+ if ((type == EP_TYPE_CTRL) || (dirn == EP_DIRN_IN)) {
+ REG_WR(regs->ep_in[num].ctrl,
+ (type << EP_CTRL_TYPE_SHIFT));
+ REG_WR(regs->ep_in[num].epreg3,
+ (max_pkt_size << EP_REG3_PKT_MAX_SHIFT));
+ REG_WR(regs->ep_in[num].epreg2,
+ (max_pkt_size >> 2));
+ REG_MOD_OR(regs->ep_in[num].ctrl,
+ EP_CTRL_IN_FLUSH_ENABLE);
+ REG_MOD_AND(regs->ep_in[num].ctrl,
+ ~EP_CTRL_IN_FLUSH_ENABLE);
+ REG_MOD_AND(regs->ep_in[num].ctrl,
+ EP_CTRL_NAK_SET);
+ }
+ REG_WR(regs->epcfg[num],
+ (num << EP_CFG_FIFO_NUM_SHIFT) |
+ (type << EP_CFG_TYPE_SHIFT) |
+ (max_pkt_size << EP_CFG_PKT_MAX_SHIFT) |
+ ((dirn == EP_DIRN_OUT) ? EP_CFG_DIRN_OUT : EP_CFG_DIRN_IN));
+}
+
+static inline void set_ep_alt_num(struct snps_udc_regs *regs, u32 num, u32 alt)
+{
+ REG_MOD_MASK(regs->epcfg[num], ~EP_CFG_ALT_NUM_MASK,
+ (alt << EP_CFG_ALT_NUM_SHIFT));
+}
+
+static inline void set_epcfg_reg(struct snps_udc_regs *regs, u32 num, u32 cfg)
+{
+ REG_MOD_MASK(regs->epcfg[num], ~EP_CFG_CFG_NUM_MASK,
+ (cfg << EP_CFG_CFG_NUM_SHIFT));
+}
+
+static inline void set_ep_intf_num(struct snps_udc_regs *regs, u32 num,
+ u32 intf)
+{
+ REG_MOD_MASK(regs->epcfg[num], ~EP_CFG_INTF_NUM_MASK,
+ (intf << EP_CFG_INTF_NUM_SHIFT));
+}
+
+static inline void disable_ep_dma(struct snps_udc_regs *regs, u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT) {
+ if (mrx_fifo)
+ REG_MOD_AND(regs->ep_out[num].ctrl,
+ ~EP_CTRL_OUT_DMA_ENABLE);
+ } else {
+ REG_MOD_AND(regs->ep_in[num].ctrl,
+ ~EP_CTRL_IN_DMA_ENABLE);
+ }
+}
+
+static inline void enable_ep_dma(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT) {
+ if (mrx_fifo)
+ REG_MOD_OR(regs->ep_out[num].ctrl,
+ EP_CTRL_OUT_DMA_ENABLE);
+ else
+ REG_MOD_OR(regs->devctrl,
+ CTRL_DMA_OUT_ENABLE);
+ } else
+ REG_MOD_OR(regs->ep_in[num].ctrl,
+ EP_CTRL_IN_DMA_ENABLE);
+}
+
+static inline void set_setup_buf_ptr(struct snps_udc_regs *regs,
+ u32 num, u32 dirn, void *addr)
+{
+ if (dirn == EP_DIRN_OUT)
+ REG_WR(regs->ep_out[num].setupbuf, (dma_addr_t)addr);
+}
+
+static inline void set_data_desc_ptr(struct snps_udc_regs *regs,
+ u32 num, u32 dirn, void *addr)
+{
+ if (dirn == EP_DIRN_OUT)
+ REG_WR(regs->ep_out[num].datadesc, (dma_addr_t)addr);
+ else
+ REG_WR(regs->ep_in[num].datadesc, (dma_addr_t)addr);
+}
+
+static inline bool is_ep_fifo_empty(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT) {
+ if (mrx_fifo)
+ return REG_RD(regs->ep_out[num].status) &
+ EP_STS_OUT_FIFO_EMPTY ? true : false;
+ else
+ return REG_RD(regs->devstatus) &
+ STS_OUT_FIFO_EMPTY ? true : false;
+ }
+ return REG_RD(regs->ep_in[num].status) &
+ EP_STS_IN_FIFO_EMPTY ? true : false;
+}
+
+static inline void clear_ep_fifo_flush(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT) {
+ if (mrx_fifo)
+ REG_MOD_AND(regs->ep_out[num].ctrl,
+ ~EP_CTRL_OUT_FLUSH_ENABLE);
+ else
+ REG_MOD_AND(regs->devctrl,
+ ~CTRL_OUT_FIFO_FLUSH_ENABLE);
+ } else {
+ REG_MOD_AND(regs->ep_in[num].ctrl,
+ ~EP_CTRL_IN_FLUSH_ENABLE);
+ }
+}
+
+static inline void set_ep_fifo_flush(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT) {
+ if (mrx_fifo)
+ REG_MOD_OR(regs->ep_out[num].ctrl,
+ EP_CTRL_OUT_FLUSH_ENABLE);
+ else
+ REG_MOD_OR(regs->devctrl,
+ CTRL_OUT_FIFO_FLUSH_ENABLE);
+ } else {
+ REG_MOD_OR(regs->ep_in[num].ctrl,
+ EP_CTRL_IN_FLUSH_ENABLE);
+ }
+}
+
+static inline u32 get_ep_frnum(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT)
+ return (regs->ep_out[num].epreg2 &
+ EP_REG2_OUT_FRAME_NUM_MASK) >>
+ EP_REG2_OUT_FRAME_NUM_SHIFT;
+ return 0;
+}
+
+static inline void clear_udc_ep_irq(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT)
+ REG_WR(regs->epintrstat, (1 << num) <<
+ EP_INTR_OUT_SHIFT);
+ else
+ REG_WR(regs->epintrstat, (1 << num) <<
+ EP_INTR_IN_SHIFT);
+}
+
+static inline void disable_udc_ep_irq(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT) {
+ REG_MOD_OR(regs->epintrmask, ((1 << num) <<
+ EP_INTR_OUT_SHIFT));
+ } else {
+ REG_MOD_OR(regs->epintrmask, ((1 << num) <<
+ EP_INTR_IN_SHIFT));
+ }
+}
+
+static inline void enable_udc_ep_irq(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT) {
+ REG_MOD_AND(regs->epintrmask, ~((1 << num) <<
+ EP_INTR_OUT_SHIFT));
+ } else {
+ REG_MOD_AND(regs->epintrmask, ~((1 << num) <<
+ EP_INTR_IN_SHIFT));
+ }
+}
+
+static inline u32 get_ep_irq_active(struct snps_udc_regs *regs, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT)
+ return (REG_RD(regs->epintrstat) & EP_INTR_OUT_MASK)
+ >> EP_INTR_OUT_SHIFT;
+
+ return (REG_RD(regs->epintrstat) & EP_INTR_IN_MASK)
+ >> EP_INTR_IN_SHIFT;
+}
+
+static inline void clear_udc_ep_irq_list(struct snps_udc_regs *regs,
+ u32 dirn, u32 mask)
+{
+ if (dirn == EP_DIRN_OUT)
+ REG_WR(regs->epintrstat, (mask << EP_INTR_OUT_SHIFT));
+ else
+ REG_WR(regs->epintrstat, (mask << EP_INTR_IN_SHIFT));
+}
+
+static inline u32 get_ep_status(struct snps_udc_regs *regs, u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT)
+ return REG_RD(regs->ep_out[num].status);
+
+ return REG_RD(regs->ep_in[num].status);
+}
+
+static inline void clear_ep_status(struct snps_udc_regs *regs,
+ u32 num, u32 dirn, u32 mask)
+{
+ if (dirn == EP_DIRN_OUT)
+ REG_WR(regs->ep_out[num].status, mask);
+ else
+ REG_WR(regs->ep_in[num].status, mask);
+}
+
+static inline void clear_ep_nak(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT)
+ REG_MOD_OR(regs->ep_out[num].ctrl, EP_CTRL_NAK_CLEAR);
+ else
+ REG_MOD_OR(regs->ep_in[num].ctrl, EP_CTRL_NAK_CLEAR);
+}
+
+static inline void enable_ep_nak(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT)
+ REG_MOD_OR(regs->ep_out[num].ctrl, EP_CTRL_NAK_SET);
+ else
+ REG_MOD_OR(regs->ep_in[num].ctrl, EP_CTRL_NAK_SET);
+}
+
+static inline void disable_ep_nak(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT)
+ REG_MOD_AND(regs->ep_out[num].ctrl, ~EP_CTRL_NAK_SET);
+ else
+ REG_MOD_AND(regs->ep_in[num].ctrl, ~EP_CTRL_NAK_SET);
+}
+
+static inline bool is_ep_nak_inprog(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT)
+ return REG_RD(regs->ep_out[num].ctrl) &
+ EP_CTRL_NAK_IN_PROGRESS ? true : false;
+
+ return REG_RD(regs->ep_in[num].ctrl) &
+ EP_CTRL_NAK_IN_PROGRESS ? true : false;
+}
+
+static inline void disable_ep_stall(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT)
+ REG_MOD_AND(regs->ep_out[num].ctrl,
+ ~EP_CTRL_STALL_ENABLE);
+ else
+ REG_MOD_AND(regs->ep_in[num].ctrl,
+ ~EP_CTRL_STALL_ENABLE);
+}
+
+static inline void enable_ep_stall(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (mrx_fifo && !(REG_RD(regs->ep_out[num].status) &
+ EP_STS_OUT_FIFO_EMPTY))
+ return;
+ else if (!mrx_fifo && !(REG_RD(regs->devstatus) &
+ STS_OUT_FIFO_EMPTY))
+ return;
+
+ if (dirn == EP_DIRN_OUT)
+ REG_MOD_OR(regs->ep_out[num].ctrl,
+ EP_CTRL_STALL_ENABLE);
+ else
+ REG_MOD_OR(regs->ep_in[num].ctrl,
+ EP_CTRL_STALL_ENABLE);
+}
+
+static inline u32 get_last_rx_frnum(struct snps_udc_regs *regs)
+{
+ return (REG_RD(regs->devstatus) & STS_SOF_FRAME_NUM_MASK)
+ >> STS_SOF_FRAME_NUM_SHIFT;
+}
+
+static inline void finish_udc(struct snps_udc_regs *regs)
+{
+ u32 ep_num;
+
+ disable_ctrl_dma(regs);
+ disable_udc_dev_irq(regs, UDC_IRQ_ALL);
+ clear_udc_dev_irq(regs, UDC_IRQ_ALL);
+
+ for (ep_num = 0; ep_num < UDC_MAX_EP; ep_num++) {
+ disable_udc_ep_irq(regs, ep_num, EP_DIRN_IN);
+ clear_udc_ep_irq(regs, ep_num, EP_DIRN_IN);
+ clear_ep_status(regs, ep_num, EP_DIRN_IN,
+ get_ep_status(regs, ep_num,
+ EP_DIRN_IN));
+
+ disable_udc_ep_irq(regs, ep_num, EP_DIRN_OUT);
+ clear_udc_ep_irq(regs, ep_num, EP_DIRN_OUT);
+ clear_ep_status(regs, ep_num, EP_DIRN_OUT,
+ get_ep_status(regs, ep_num,
+ EP_DIRN_OUT));
+ }
+}
+
+static inline void init_udc_reg(struct snps_udc_regs *regs)
+{
+ finish_udc(regs);
+ REG_WR(regs->devcfg, CFG_SET_DESCRIPTOR_ENABLE
+ | CFG_UTMI_8BIT_ENABLE
+ | CFG_CSR_PROGRAM_ENABLE
+ | CFG_SPD_HS);
+ REG_WR(regs->devctrl, CTRL_LE_ENABLE
+ | CTRL_DISCONNECT_ENABLE
+ | CTRL_DMA_MODE_ENABLE
+ | CTRL_DMA_DESC_UPDATE_ENABLE
+ | CTRL_OUT_ALL_NAK
+ | CTRL_DMA_OUT_THRESH_LEN_MASK
+ | CTRL_DMA_BURST_LEN_MASK
+ | CTRL_DMA_BURST_ENABLE
+ | CTRL_OUT_FIFO_FLUSH_ENABLE
+ );
+
+ if (mrx_fifo)
+ REG_MOD_AND(regs->devctrl, ~CTRL_OUT_FIFO_FLUSH_ENABLE);
+
+ if (out_bf_mode)
+ REG_MOD_OR(regs->devctrl, CTRL_DMA_BUFF_FILL_MODE_ENABLE);
+
+ REG_WR(regs->devintrmask, IRQ_BUS_IDLE | IRQ_SOF_DETECTED);
+ REG_WR(regs->epintrmask, 0);
+}
+
+static inline struct data_desc *dma_desc_chain_alloc(struct snps_udc_ep *ep)
+{
+ u32 idx;
+
+ idx = ep->dma.add_idx++;
+
+ return &ep->dma.virt->desc[EP_DMA_DESC_IDX(idx)];
+}
+
+static inline int dma_desc_chain_is_empty(struct snps_udc_ep *ep)
+{
+ return ep->dma.add_idx == ep->dma.remove_idx;
+}
+
+static inline void dma_desc_chain_free(struct snps_udc_ep *ep)
+{
+ ep->dma.remove_idx++;
+}
+
+static inline int dma_desc_chain_is_full(struct snps_udc_ep *ep)
+{
+ return !dma_desc_chain_is_empty(ep) &&
+ (EP_DMA_DESC_IDX(ep->dma.add_idx) ==
+ EP_DMA_DESC_IDX(ep->dma.remove_idx));
+}
+
+static inline struct data_desc *dma_desc_chain_head(struct snps_udc_ep *ep)
+{
+ u32 index = EP_DMA_DESC_IDX(ep->dma.remove_idx);
+
+ return &ep->dma.virt->desc[index];
+}
+
+static inline void dma_desc_chain_reset(struct snps_udc_ep *ep)
+{
+ ep->dma.add_idx = 0;
+ ep->dma.remove_idx = 0;
+}
+#endif
--
2.1.0
Powered by blists - more mailing lists