2016-11-30 06:05:28

by Raviteja Garimella

[permalink] [raw]
Subject: [PATCH 0/2] Support for Synopsys UDC for ARM platforms

This patchset adds support for Synposys Designware core AHB-UDC
(USB Device controller) for Arm platfoms.

New UDC driver is added to drivers/usb/gadget directory along with
updating the Kconfig and Makefile.
DT bindings documentation is also added for the same.
Device tree entry for the same in NS2 dtsi will be sent for review
once the DRD phy driver code is pushed (which is being reviewed in a
separate patch series).

This patchset is tested on Broadcom NS2 BCM958712K reference board.

Repo: https://github.com/Broadcom/arm64-linux.git
Branch: udc_v1

Raviteja Garimella (2):
Add DT bindings documentation for Synopsys UDC driver
Synopsys USB 2.0 Device Controller (UDC) Driver

.../devicetree/bindings/usb/snps,dw-ahb-udc.txt | 29 +
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 ++++++++++++
5 files changed, 2864 insertions(+)
create mode 100644 Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt
create mode 100644 drivers/usb/gadget/udc/snps_udc.c
create mode 100644 drivers/usb/gadget/udc/snps_udc.h

--
2.1.0


2016-11-30 06:05:49

by Raviteja Garimella

[permalink] [raw]
Subject: [PATCH 1/2] Add DT bindings documentation for Synopsys UDC driver

This patch adds documentation for Synopsis Designware Cores AHB
Subsystem Device Controller (UDC).

Signed-off-by: Raviteja Garimella <[email protected]>
---
.../devicetree/bindings/usb/snps,dw-ahb-udc.txt | 29 ++++++++++++++++++++++
1 file changed, 29 insertions(+)
create mode 100644 Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt

diff --git a/Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt b/Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt
new file mode 100644
index 0000000..64e1fbf
--- /dev/null
+++ b/Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt
@@ -0,0 +1,29 @@
+Synopsys USB Device controller.
+
+The device node is used for Synopsys Designware Cores AHB
+Subsystem Device Controller (UDC).
+
+Required properties:
+ - compatible: should be "snps,dw-ahbudc"
+ - reg: Offset and length of UDC register set
+ - interrupts: description of interrupt line
+ - phys: phandle to phy node.
+ - phy-names: name of phy node. Must be usb2drd.
+ - extcon: phandle to the extcon device
+
+Example:
+
+ usbdrd_phy: phy@6501c000 {
+ #phy-cells = <0>;
+ compatible = "brcm,ns2-drd-phy";
+ reg = <0x66000000 0x1000>,
+ }
+
+ udc_dwc: usb@664e0000 {
+ compatible = "snps,dw-ahb-udc";
+ reg = <0x664e0000 0x2000>;
+ interrupts = <GIC_SPI 424 IRQ_TYPE_LEVEL_HIGH>;
+ phys = <&usbdrd_phy>;
+ phy-names = "usb2drd";
+ extcon = <&usbdrd_phy>";
+ };
--
2.1.0

2016-11-30 06:06:05

by Raviteja Garimella

[permalink] [raw]
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 <[email protected]>
---
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, &reg)
+#define REG_MOD_AND(reg, val) writel(val & readl(&reg), &reg)
+#define REG_MOD_OR(reg, val) writel(val | readl(&reg), &reg)
+#define REG_MOD_MASK(reg, mask, val) writel(val | (mask & readl(&reg)), &reg)
+#define REG_RD(reg) readl(&reg)
+
+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

2016-11-30 10:41:14

by Felipe Balbi

[permalink] [raw]
Subject: Re: [PATCH 2/2] Synopsys USB 2.0 Device Controller (UDC) Driver


Hi,

Raviteja Garimella <[email protected]> writes:
> 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 <[email protected]>

use drivers/usb/dwc2 instead of duplicating it.

--
balbi


Attachments:
signature.asc (832.00 B)

2016-11-30 12:42:29

by Raviteja Garimella

[permalink] [raw]
Subject: Re: [PATCH 2/2] Synopsys USB 2.0 Device Controller (UDC) Driver

Hi Balbi,

On Wed, Nov 30, 2016 at 4:10 PM, Felipe Balbi <[email protected]> wrote:
>
> Hi,
>
> Raviteja Garimella <[email protected]> writes:
>> 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 <[email protected]>
>
> use drivers/usb/dwc2 instead of duplicating it.

The ones we have in drivers/usb/dwc2 is for Designware high speed OTG
controller IP. The one that I submitted for review is for USB Device
controller IP (UDC). The IPs are different.

Thanks,
Ravi
>
> --
> balbi

2016-11-30 12:47:27

by Felipe Balbi

[permalink] [raw]
Subject: Re: [PATCH 2/2] Synopsys USB 2.0 Device Controller (UDC) Driver


Hi,

Raviteja Garimella <[email protected]> writes:
> Hi Balbi,
>
> On Wed, Nov 30, 2016 at 4:10 PM, Felipe Balbi <[email protected]> wrote:
>>
>> Hi,
>>
>> Raviteja Garimella <[email protected]> writes:
>>> 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 <[email protected]>
>>
>> use drivers/usb/dwc2 instead of duplicating it.
>
> The ones we have in drivers/usb/dwc2 is for Designware high speed OTG
> controller IP. The one that I submitted for review is for USB Device
> controller IP (UDC). The IPs are different.

I'll wait for John's confirmation that this really isn't compatible with
dwc2. John?

--
balbi


Attachments:
signature.asc (832.00 B)

2016-12-01 00:55:11

by John Youn

[permalink] [raw]
Subject: Re: [PATCH 2/2] Synopsys USB 2.0 Device Controller (UDC) Driver

On 11/30/2016 4:47 AM, Felipe Balbi wrote:
>
> Hi,
>
> Raviteja Garimella <[email protected]> writes:
>> Hi Balbi,
>>
>> On Wed, Nov 30, 2016 at 4:10 PM, Felipe Balbi <[email protected]> wrote:
>>>
>>> Hi,
>>>
>>> Raviteja Garimella <[email protected]> writes:
>>>> 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 <[email protected]>
>>>
>>> use drivers/usb/dwc2 instead of duplicating it.
>>
>> The ones we have in drivers/usb/dwc2 is for Designware high speed OTG
>> controller IP. The one that I submitted for review is for USB Device
>> controller IP (UDC). The IPs are different.
>
> I'll wait for John's confirmation that this really isn't compatible with
> dwc2. John?
>

Hi Felipe,

This is our older UDC IP, not compatible with HSOTG.

It is also no longer supported by Synopsys and considered EOL.

Regards,
John

2016-12-01 08:56:19

by Felipe Balbi

[permalink] [raw]
Subject: Re: [PATCH 2/2] Synopsys USB 2.0 Device Controller (UDC) Driver


Hi,

John Youn <[email protected]> writes:
> On 11/30/2016 4:47 AM, Felipe Balbi wrote:
>>
>> Hi,
>>
>> Raviteja Garimella <[email protected]> writes:
>>> Hi Balbi,
>>>
>>> On Wed, Nov 30, 2016 at 4:10 PM, Felipe Balbi <[email protected]> wrote:
>>>>
>>>> Hi,
>>>>
>>>> Raviteja Garimella <[email protected]> writes:
>>>>> 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 <[email protected]>
>>>>
>>>> use drivers/usb/dwc2 instead of duplicating it.
>>>
>>> The ones we have in drivers/usb/dwc2 is for Designware high speed OTG
>>> controller IP. The one that I submitted for review is for USB Device
>>> controller IP (UDC). The IPs are different.
>>
>> I'll wait for John's confirmation that this really isn't compatible with
>> dwc2. John?
>>
>
> Hi Felipe,
>
> This is our older UDC IP, not compatible with HSOTG.
>
> It is also no longer supported by Synopsys and considered EOL.

Is it the same one used by amd5536udc.c? If it is, then it's much better
to refactor that driver so it can be used as a library of sorts by PCI
and non-PCI systems. We really don't want duplicated drivers upstream.

--
balbi


Attachments:
signature.asc (832.00 B)

2016-12-05 23:04:24

by Rob Herring

[permalink] [raw]
Subject: Re: [PATCH 1/2] Add DT bindings documentation for Synopsys UDC driver

On Wed, Nov 30, 2016 at 11:35:09AM +0530, Raviteja Garimella wrote:
> This patch adds documentation for Synopsis Designware Cores AHB
> Subsystem Device Controller (UDC).
>
> Signed-off-by: Raviteja Garimella <[email protected]>
> ---
> .../devicetree/bindings/usb/snps,dw-ahb-udc.txt | 29 ++++++++++++++++++++++
> 1 file changed, 29 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt
>
> diff --git a/Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt b/Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt
> new file mode 100644
> index 0000000..64e1fbf
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt
> @@ -0,0 +1,29 @@
> +Synopsys USB Device controller.
> +
> +The device node is used for Synopsys Designware Cores AHB
> +Subsystem Device Controller (UDC).
> +
> +Required properties:
> + - compatible: should be "snps,dw-ahbudc"

Needs an SoC specific compatible string.

> + - reg: Offset and length of UDC register set
> + - interrupts: description of interrupt line
> + - phys: phandle to phy node.
> + - phy-names: name of phy node. Must be usb2drd.

A name is pointless when there's only 1 phy. Is this a device or dual
role device(DRD)?

> + - extcon: phandle to the extcon device

I don't think extcon should be required. If this is UDC only, I'm not
sure why you'd need it.

> +
> +Example:
> +
> + usbdrd_phy: phy@6501c000 {
> + #phy-cells = <0>;
> + compatible = "brcm,ns2-drd-phy";
> + reg = <0x66000000 0x1000>,
> + }
> +
> + udc_dwc: usb@664e0000 {
> + compatible = "snps,dw-ahb-udc";

Doesn't match above.

> + reg = <0x664e0000 0x2000>;
> + interrupts = <GIC_SPI 424 IRQ_TYPE_LEVEL_HIGH>;
> + phys = <&usbdrd_phy>;
> + phy-names = "usb2drd";
> + extcon = <&usbdrd_phy>";

You are already describing the phy connection, you shouldn't need both.

> + };
> --
> 2.1.0
>

2016-12-06 10:53:16

by Raviteja Garimella

[permalink] [raw]
Subject: Re: [PATCH 1/2] Add DT bindings documentation for Synopsys UDC driver

Hi Rob,

On Tue, Dec 6, 2016 at 4:34 AM, Rob Herring <[email protected]> wrote:
> On Wed, Nov 30, 2016 at 11:35:09AM +0530, Raviteja Garimella wrote:
>> This patch adds documentation for Synopsis Designware Cores AHB
>> Subsystem Device Controller (UDC).
>>
>> Signed-off-by: Raviteja Garimella <[email protected]>
>> ---
>> .../devicetree/bindings/usb/snps,dw-ahb-udc.txt | 29 ++++++++++++++++++++++
>> 1 file changed, 29 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt
>>
>> diff --git a/Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt b/Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt
>> new file mode 100644
>> index 0000000..64e1fbf
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt
>> @@ -0,0 +1,29 @@
>> +Synopsys USB Device controller.
>> +
>> +The device node is used for Synopsys Designware Cores AHB
>> +Subsystem Device Controller (UDC).
>> +
>> +Required properties:
>> + - compatible: should be "snps,dw-ahbudc"
>
> Needs an SoC specific compatible string.

This will be changed. I am working on using amd5536udc.c driver
which's already in Kernel tree and can be used for this UDC(as per
review comments from Felipe/John).
>
>> + - reg: Offset and length of UDC register set
>> + - interrupts: description of interrupt line
>> + - phys: phandle to phy node.
>> + - phy-names: name of phy node. Must be usb2drd.
>
> A name is pointless when there's only 1 phy. Is this a device or dual
> role device(DRD)?

This is DRD phy that's is connected to a Host Controller and a Device
Controller.
>
>> + - extcon: phandle to the extcon device
>
> I don't think extcon should be required. If this is UDC only, I'm not
> sure why you'd need it.

This Phy will be initialized in Host/Device mode based on the external
connector that's plugged in. That's reason for having extcon node.
>
>> +
>> +Example:
>> +
>> + usbdrd_phy: phy@6501c000 {
>> + #phy-cells = <0>;
>> + compatible = "brcm,ns2-drd-phy";
>> + reg = <0x66000000 0x1000>,
>> + }
>> +
>> + udc_dwc: usb@664e0000 {
>> + compatible = "snps,dw-ahb-udc";
>
> Doesn't match above.

This will be changed.
>
>> + reg = <0x664e0000 0x2000>;
>> + interrupts = <GIC_SPI 424 IRQ_TYPE_LEVEL_HIGH>;
>> + phys = <&usbdrd_phy>;
>> + phy-names = "usb2drd";
>> + extcon = <&usbdrd_phy>";
>
> You are already describing the phy connection, you shouldn't need both.

"extcon_get_edev_by_phandle" requires extcon phandle. I see that's the
only way to get the extcon device, since generic Phy device doesn't
have any extcon member.
The current driver will not go as-is after the suggestion I got in UDC
driver review (to use amd5536udc driver). I will work on the changes
and submit the patches once again.

Thanks,
Ravi
>
>> + };
>> --
>> 2.1.0
>>