[PATCH v2 4/4] usb: host: add xhci-exynos module

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This is for reference to introduce usb offload as seeing how a user does.
We only care DCBAA, Device Context, Transfer Ring, Event Ring, and ERST.
They are allocated on specific address(SRAM) for Co-Processor.
Co-processor could use them directly without xhci driver after then.

Signed-off-by: Daehwan Jung <dh10.jung@xxxxxxxxxxx>
---
 drivers/usb/host/xhci-exynos.c | 2025 ++++++++++++++++++++++++++++++++
 drivers/usb/host/xhci-exynos.h |  150 +++
 2 files changed, 2175 insertions(+)
 create mode 100644 drivers/usb/host/xhci-exynos.c
 create mode 100644 drivers/usb/host/xhci-exynos.h

diff --git a/drivers/usb/host/xhci-exynos.c b/drivers/usb/host/xhci-exynos.c
new file mode 100644
index 000000000000..3913c48d4b20
--- /dev/null
+++ b/drivers/usb/host/xhci-exynos.c
@@ -0,0 +1,2025 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * xhci-exynos.c - xHCI host controller driver platform Bus Glue for Exynos.
+ *
+ * Copyright (C) 2022 Samsung Electronics Incorporated - http://www.samsung.com
+ * Author: Daehwan Jung <dh10.jung@xxxxxxxxxxx>
+ *
+ * A lot of code borrowed from the Linux xHCI driver.
+ */
+
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/usb/phy.h>
+#include <linux/slab.h>
+#include <linux/phy/phy.h>
+#include <linux/acpi.h>
+#include <linux/usb/of.h>
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+#include "../../../sound/usb/exynos_usb_audio.h"
+#include <linux/types.h>
+#include "xhci-trace.h"
+#endif
+
+#include "../core/hub.h"
+#include "../core/phy.h"
+#include "xhci.h"
+#include "xhci-plat.h"
+#include "xhci-mvebu.h"
+#include "xhci-rcar.h"
+#include "../dwc3/dwc3-exynos.h"
+#include "../dwc3/exynos-otg.h"
+#include "xhci-exynos.h"
+#include <soc/samsung/exynos-cpupm.h>
+
+static struct hc_driver __read_mostly xhci_exynos_hc_driver;
+
+static int xhci_exynos_setup(struct usb_hcd *hcd);
+static int xhci_exynos_start(struct usb_hcd *hcd);
+static int xhci_exynos_bus_suspend(struct usb_hcd *hcd);
+static int xhci_exynos_bus_resume(struct usb_hcd *hcd);
+static int xhci_exynos_wake_lock(struct xhci_hcd_exynos *xhci_exynos,
+				   int is_main_hcd, int is_lock);
+
+static int is_rewa_enabled;
+
+static struct xhci_hcd_exynos *g_xhci_exynos;
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+extern struct usb_xhci_pre_alloc xhci_pre_alloc;
+
+struct xhci_ring *xhci_ring_alloc_uram(struct xhci_hcd *xhci,
+		unsigned int num_segs, unsigned int cycle_state,
+		enum xhci_ring_type type, unsigned int max_packet, gfp_t flags,
+		u32 endpoint_type);
+
+static void *dma_pre_alloc_coherent(struct xhci_hcd *xhci, size_t size,
+			 dma_addr_t *dma_handle, gfp_t gfp)
+{
+	struct usb_xhci_pre_alloc *xhci_alloc = g_xhci_exynos->xhci_alloc;
+	u64 align = size % PAGE_SIZE;
+	u64 b_offset = xhci_alloc->offset;
+
+	if (align)
+		xhci_alloc->offset = xhci_alloc->offset + size + (PAGE_SIZE - align);
+	else
+		xhci_alloc->offset = xhci_alloc->offset + size;
+
+	*dma_handle = xhci_alloc->dma + b_offset;
+
+	return (void *)xhci_alloc->pre_dma_alloc + b_offset;
+}
+
+static void xhci_exynos_usb_offload_enable_event_ring(struct xhci_hcd *xhci)
+{
+	u32 temp;
+	u64 temp_64;
+
+	temp_64 = xhci_read_64(xhci, &g_xhci_exynos->ir_set_audio->erst_dequeue);
+	temp_64 &= ~ERST_PTR_MASK;
+	xhci_info(xhci,	"ERST2 deq = 64'h%0lx", (unsigned long) temp_64);
+
+	xhci_info(xhci, "// [USB Audio] Set the interrupt modulation register");
+	temp = readl(&g_xhci_exynos->ir_set_audio->irq_control);
+	temp &= ~ER_IRQ_INTERVAL_MASK;
+
+	temp |= (u32)160;
+	writel(temp, &g_xhci_exynos->ir_set_audio->irq_control);
+
+	temp = readl(&g_xhci_exynos->ir_set_audio->irq_pending);
+	xhci_info(xhci, "// [USB Audio] Enabling event ring interrupter %p by writing 0x%x to irq_pending",
+			g_xhci_exynos->ir_set_audio, (unsigned int) ER_IRQ_ENABLE(temp));
+	writel(ER_IRQ_ENABLE(temp), &g_xhci_exynos->ir_set_audio->irq_pending);
+}
+
+static void xhci_exynos_usb_offload_store_hw_info(struct xhci_hcd *xhci, struct usb_hcd *hcd,
+						struct usb_device *udev)
+{
+	struct xhci_virt_device *virt_dev;
+	struct xhci_erst_entry *entry = &g_xhci_exynos->erst_audio.entries[0];
+
+	virt_dev = xhci->devs[udev->slot_id];
+
+	g_hwinfo->dcbaa_dma = xhci->dcbaa->dma;
+	g_hwinfo->save_dma = g_xhci_exynos->save_dma;
+	g_hwinfo->cmd_ring = xhci->op_regs->cmd_ring;
+	g_hwinfo->slot_id = udev->slot_id;
+	g_hwinfo->in_dma = g_xhci_exynos->in_dma;
+	g_hwinfo->in_buf = g_xhci_exynos->in_addr;
+	g_hwinfo->out_dma = g_xhci_exynos->out_dma;
+	g_hwinfo->out_buf = g_xhci_exynos->out_addr;
+	g_hwinfo->in_ctx = virt_dev->in_ctx->dma;
+	g_hwinfo->out_ctx = virt_dev->out_ctx->dma;
+	g_hwinfo->erst_addr = entry->seg_addr;
+	g_hwinfo->speed = udev->speed;
+	if (xhci->quirks & XHCI_USE_URAM_FOR_EXYNOS_AUDIO)
+		g_hwinfo->use_uram = true;
+	else
+		g_hwinfo->use_uram = false;
+
+	pr_info("<<< %s\n", __func__);
+}
+static void xhci_exynos_set_hc_event_deq_audio(struct xhci_hcd *xhci)
+{
+	u64 temp;
+	dma_addr_t deq;
+
+	deq = xhci_trb_virt_to_dma(g_xhci_exynos->event_ring_audio->deq_seg,
+			g_xhci_exynos->event_ring_audio->dequeue);
+	if (deq == 0 && !in_interrupt())
+		xhci_warn(xhci, "WARN something wrong with SW event ring "
+				"dequeue ptr.\n");
+	/* Update HC event ring dequeue pointer */
+	temp = xhci_read_64(xhci, &g_xhci_exynos->ir_set_audio->erst_dequeue);
+	temp &= ERST_PTR_MASK;
+	/* Don't clear the EHB bit (which is RW1C) because
+	 * there might be more events to service.
+	 */
+	temp &= ~ERST_EHB;
+	xhci_info(xhci,	"//[%s] Write event ring dequeue pointer = 0x%llx, "
+			"preserving EHB bit", __func__,
+			((u64) deq & (u64) ~ERST_PTR_MASK) | temp);
+	xhci_write_64(xhci, ((u64) deq & (u64) ~ERST_PTR_MASK) | temp,
+			&g_xhci_exynos->ir_set_audio->erst_dequeue);
+}
+
+void xhci_exynos_parse_endpoint(struct xhci_hcd *xhci, struct usb_device *udev,
+				struct usb_endpoint_descriptor *desc,
+				struct xhci_container_ctx *ctx)
+{
+	struct usb_endpoint_descriptor *d = desc;
+	int size = 0x100;
+	unsigned int ep_index;
+	struct xhci_ep_ctx *ep_ctx;
+
+	g_hwinfo->rawdesc_length = size;
+	ep_index = xhci_get_endpoint_index(d);
+	ep_ctx = xhci_get_ep_ctx(xhci, ctx, ep_index);
+
+	pr_info("udev = 0x%8x, Ep = 0x%x, desc = 0x%8x, deq = 0x%8x\n",
+			udev, d->bEndpointAddress, d, ep_ctx->deq);
+
+	if (ep_ctx->deq == 0) {
+		pr_info("ep_ctx->deq: 0x%8x has wrong address!!\n", ep_ctx->deq);
+		return;
+	}
+
+	if ((d->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
+				USB_ENDPOINT_XFER_ISOC) {
+		if ((d->bmAttributes & USB_ENDPOINT_USAGE_MASK) ==
+					USB_ENDPOINT_USAGE_FEEDBACK) {
+			/* Only Feedback endpoint(Not implict feedback data endpoint) */
+			if (d->bEndpointAddress & USB_ENDPOINT_DIR_MASK) {
+				g_hwinfo->fb_in_ep =
+					d->bEndpointAddress;
+				pr_info("Feedback IN ISO endpoint #0%x 0x%x\n",
+					d->bEndpointAddress, d->bSynchAddress);
+
+				pr_info("[%s] set fb in deq : %#08llx\n",
+						__func__, ep_ctx->deq);
+				g_hwinfo->fb_old_in_deq = g_hwinfo->fb_in_deq;
+				g_hwinfo->fb_in_deq = ep_ctx->deq;
+			} else {
+				g_hwinfo->fb_out_ep =
+					d->bEndpointAddress;
+				pr_info("Feedback OUT ISO endpoint #0%x 0x%x\n",
+					d->bEndpointAddress, d->bSynchAddress);
+
+				pr_info("[%s] set fb out deq : %#08llx\n",
+						__func__, ep_ctx->deq);
+				g_hwinfo->fb_old_out_deq = g_hwinfo->fb_out_deq;
+				g_hwinfo->fb_out_deq = ep_ctx->deq;
+			}
+		} else {
+			/* Data Stream Endpoint only */
+			if (d->bEndpointAddress & USB_ENDPOINT_DIR_MASK) {
+				if (d->bEndpointAddress != g_hwinfo->fb_in_ep) {
+					g_hwinfo->in_ep =
+						d->bEndpointAddress;
+					pr_info(" This is IN ISO endpoint #0%x 0x%x\n",
+						d->bEndpointAddress, d->bSynchAddress);
+
+					pr_info("[%s] set in deq : %#08llx\n",
+							__func__, ep_ctx->deq);
+					g_hwinfo->old_in_deq = g_hwinfo->in_deq;
+					g_hwinfo->in_deq = ep_ctx->deq;
+				} else {
+					pr_info("IN ISO endpoint is same with FB #0%x\n",
+						d->bEndpointAddress);
+					pr_info("[%s] set fb in deq : %#08llx\n",
+							__func__, ep_ctx->deq);
+					g_hwinfo->fb_old_in_deq = g_hwinfo->fb_in_deq;
+					g_hwinfo->fb_in_deq = ep_ctx->deq;
+				}
+
+				if ((d->bLength > 7) && (d->bSynchAddress != 0x0)) {
+					g_hwinfo->fb_out_ep =
+						d->bSynchAddress;
+					pr_info("Feedback OUT ISO endpoint #0%x 0x%x\n",
+						d->bEndpointAddress, d->bSynchAddress);
+
+					pr_info("[%s] set fb out deq : %#08llx\n",
+							__func__, ep_ctx->deq);
+					g_hwinfo->fb_old_out_deq = g_hwinfo->fb_out_deq;
+					g_hwinfo->fb_out_deq = ep_ctx->deq;
+				}
+			} else {
+				g_hwinfo->out_ep =
+					d->bEndpointAddress;
+				pr_info(" This is OUT ISO endpoint #0%x 0x%x\n",
+					d->bEndpointAddress, d->bSynchAddress);
+
+				pr_info("[%s] set out deq : %#08llx\n",
+						__func__, ep_ctx->deq);
+				g_hwinfo->old_out_deq = g_hwinfo->out_deq;
+				g_hwinfo->out_deq = ep_ctx->deq;
+				if ((d->bLength > 7) && (d->bSynchAddress != 0x0)) {
+					g_hwinfo->fb_in_ep =
+						d->bSynchAddress;
+					pr_info("Feedback IN ISO endpoint #0%x 0x%x\n",
+						d->bEndpointAddress, d->bSynchAddress);
+
+					pr_info("[%s] set fb in deq : %#08llx\n",
+							__func__, ep_ctx->deq);
+					g_hwinfo->fb_old_in_deq = g_hwinfo->fb_in_deq;
+					g_hwinfo->fb_in_deq = ep_ctx->deq;
+				}
+			}
+		}
+	}
+
+
+}
+int xhci_exynos_address_device(struct usb_hcd *hcd, struct usb_device *udev)
+{
+	struct xhci_hcd *xhci;
+	int ret;
+
+	pr_debug("%s +++", __func__);
+	ret = xhci_address_device(hcd, udev);
+	xhci = hcd_to_xhci(hcd);
+
+	xhci_exynos_usb_offload_store_hw_info(xhci, hcd, udev);
+
+	pr_debug("%s ---", __func__);
+	return ret;
+}
+int xhci_exynos_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev,
+		struct usb_host_endpoint *ep)
+{
+	int ret;
+	struct xhci_hcd *xhci;
+	struct xhci_virt_device *virt_dev;
+
+	pr_debug("%s +++", __func__);
+	ret = xhci_add_endpoint(hcd, udev, ep);
+	if (!ret && udev->slot_id) {
+		xhci = hcd_to_xhci(hcd);
+		virt_dev = xhci->devs[udev->slot_id];
+		xhci_exynos_parse_endpoint(xhci, udev, &ep->desc, virt_dev->out_ctx);
+	}
+	pr_debug("%s ---", __func__);
+	return ret;
+}
+static int xhci_exynos_alloc_event_ring(struct xhci_hcd *xhci, gfp_t flags)
+{
+	dma_addr_t	dma;
+	unsigned int	val;
+	u64		val_64;
+	struct xhci_segment	*seg;
+
+	g_xhci_exynos->ir_set_audio = &xhci->run_regs->ir_set[1];
+	g_xhci_exynos->save_addr = dma_pre_alloc_coherent(xhci,
+			sizeof(PAGE_SIZE), &dma, flags);
+	g_xhci_exynos->save_dma = dma;
+	xhci_info(xhci, "// Save address = 0x%llx (DMA), %p (virt)",
+			(unsigned long long)g_xhci_exynos->save_dma, g_xhci_exynos->save_addr);
+
+	if ((xhci->quirks & XHCI_USE_URAM_FOR_EXYNOS_AUDIO)) {
+		/* for AUDIO erst */
+		g_xhci_exynos->event_ring_audio = xhci_ring_alloc_uram(xhci, ERST_NUM_SEGS,
+				1, TYPE_EVENT, 0, flags, 0);
+		if (!g_xhci_exynos->event_ring_audio)
+			goto fail;
+
+		g_xhci_exynos->erst_audio.entries = ioremap(EXYNOS_URAM_ABOX_ERST_SEG_ADDR,
+				sizeof(struct xhci_erst_entry) * ERST_NUM_SEGS);
+		if (!g_xhci_exynos->erst_audio.entries)
+			goto fail;
+
+		dma = EXYNOS_URAM_ABOX_ERST_SEG_ADDR;
+		xhci_info(xhci, "ABOX audio ERST allocated at 0x%x",
+					EXYNOS_URAM_ABOX_ERST_SEG_ADDR);
+	} else {
+		/* for AUDIO erst */
+		g_xhci_exynos->event_ring_audio = xhci_ring_alloc(xhci, ERST_NUM_SEGS, 1,
+					TYPE_EVENT, 0, flags);
+		if (!g_xhci_exynos->event_ring_audio)
+			goto fail;
+		if (xhci_check_trb_in_td_math(xhci) < 0)
+			goto fail;
+		g_xhci_exynos->erst_audio.entries = dma_pre_alloc_coherent(xhci,
+				sizeof(struct xhci_erst_entry) * ERST_NUM_SEGS, &dma,
+				flags);
+		if (!g_xhci_exynos->erst_audio.entries)
+			goto fail;
+	}
+	xhci_info(xhci,	"// Allocated event ring segment table at 0x%llx",
+			(unsigned long long)dma);
+
+	memset(g_xhci_exynos->erst_audio.entries, 0, sizeof(struct xhci_erst_entry) *
+							ERST_NUM_SEGS);
+	g_xhci_exynos->erst_audio.num_entries = ERST_NUM_SEGS;
+	g_xhci_exynos->erst_audio.erst_dma_addr = dma;
+	xhci_info(xhci,	"// Set ERST to 0; private num segs = %i, virt addr = %p, dma addr = 0x%llx",
+			xhci->erst.num_entries,
+			xhci->erst.entries,
+			(unsigned long long)xhci->erst.erst_dma_addr);
+
+	/* set ring base address and size for each segment table entry */
+	for (val = 0, seg = g_xhci_exynos->event_ring_audio->first_seg;
+					val < ERST_NUM_SEGS; val++) {
+		struct xhci_erst_entry *entry = &g_xhci_exynos->erst_audio.entries[val];
+
+		entry->seg_addr = cpu_to_le64(seg->dma);
+		entry->seg_size = cpu_to_le32(TRBS_PER_SEGMENT);
+		entry->rsvd = 0;
+		seg = seg->next;
+	}
+
+	/* set ERST count with the number of entries in the segment table */
+	val = readl(&g_xhci_exynos->ir_set_audio->erst_size);
+	val &= ERST_SIZE_MASK;
+	val |= ERST_NUM_SEGS;
+	xhci_info(xhci, "// Write ERST size = %i to ir_set 0 (some bits preserved)",
+			val);
+	writel(val, &g_xhci_exynos->ir_set_audio->erst_size);
+
+	xhci_info(xhci, "// Set ERST entries to point to event ring.");
+	/* set the segment table base address */
+	xhci_info(xhci,	"// Set ERST base address for ir_set 0 = 0x%llx",
+			(unsigned long long)g_xhci_exynos->erst_audio.erst_dma_addr);
+	val_64 = xhci_read_64(xhci, &g_xhci_exynos->ir_set_audio->erst_base);
+	val_64 &= ERST_PTR_MASK;
+	val_64 |= (g_xhci_exynos->erst_audio.erst_dma_addr & (u64) ~ERST_PTR_MASK);
+	xhci_write_64(xhci, val_64, &g_xhci_exynos->ir_set_audio->erst_base);
+
+	/* Set the event ring dequeue address */
+	xhci_exynos_set_hc_event_deq_audio(xhci);
+	xhci_info(xhci,	"// Wrote ERST address to ir_set 1.");
+
+	return 0;
+fail:
+	return -1;
+}
+
+static void xhci_exynos_free_event_ring(struct xhci_hcd *xhci)
+{
+	if (xhci->quirks & XHCI_USE_URAM_FOR_EXYNOS_AUDIO)
+		iounmap(g_xhci_exynos->erst_audio.entries);
+	else
+		g_xhci_exynos->erst_audio.entries = NULL;
+
+	xhci_info(xhci, "%s: Freed ERST for Audio offloading", __func__);
+
+	if (g_xhci_exynos->event_ring_audio)
+		xhci_exynos_ring_free(xhci, g_xhci_exynos->event_ring_audio);
+	g_xhci_exynos->event_ring_audio = NULL;
+	xhci_info(xhci, "%s: Freed event ring for Audio offloading", __func__);
+}
+
+static void xhci_exynos_free_container_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx)
+{
+	/* Ignore dma_pool_free if it is allocated from URAM */
+	if (ctx->dma != EXYNOS_URAM_DEVICE_CTX_ADDR)
+		dma_pool_free(xhci->device_pool, ctx->bytes, ctx->dma);
+}
+
+static void xhci_exynos_alloc_container_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx,
+						int type, gfp_t flags)
+{
+	if (type != XHCI_CTX_TYPE_INPUT && g_xhci_exynos->exynos_uram_ctx_alloc == 0 &&
+			xhci->quirks & XHCI_USE_URAM_FOR_EXYNOS_AUDIO) {
+		/* Only first Device Context uses URAM */
+		int i;
+
+		ctx->bytes = ioremap(EXYNOS_URAM_DEVICE_CTX_ADDR, 2112);
+		if (!ctx->bytes)
+			return;
+
+		for (i = 0; i < 2112; i++)
+			ctx->bytes[i] = 0;
+
+		ctx->dma = EXYNOS_URAM_DEVICE_CTX_ADDR;
+		g_xhci_exynos->usb_audio_ctx_addr = ctx->bytes;
+		g_xhci_exynos->exynos_uram_ctx_alloc = 1;
+		xhci_info(xhci, "First device context allocated at URAM(%x)",
+					EXYNOS_URAM_DEVICE_CTX_ADDR);
+	} else {
+		ctx->bytes = dma_pool_zalloc(xhci->device_pool, flags, &ctx->dma);
+		if (!ctx->bytes)
+			return;
+	}
+}
+
+static struct xhci_ring *xhci_exynos_alloc_transfer_ring(struct xhci_hcd *xhci, u32 endpoint_type,
+		enum xhci_ring_type ring_type, unsigned int max_packet, gfp_t mem_flags)
+{
+	if (xhci->quirks & XHCI_USE_URAM_FOR_EXYNOS_AUDIO) {
+		/* If URAM is not allocated, it try to allocate from URAM */
+		if (g_xhci_exynos->exynos_uram_isoc_out_alloc == 0 &&
+						endpoint_type == ISOC_OUT_EP) {
+			xhci_info(xhci, "First ISOC OUT ring is allocated from URAM.\n");
+			return xhci_ring_alloc_uram(xhci, 1, 1, ring_type,
+							max_packet, mem_flags,
+							endpoint_type);
+
+			g_xhci_exynos->exynos_uram_isoc_out_alloc = 1;
+		} else if (g_xhci_exynos->exynos_uram_isoc_in_alloc == 0 &&
+					endpoint_type == ISOC_IN_EP &&
+					EXYNOS_URAM_ISOC_IN_RING_ADDR != 0x0) {
+			xhci_info(xhci, "First ISOC IN ring is allocated from URAM.\n");
+			return xhci_ring_alloc_uram(xhci, 1, 1, ring_type,
+							max_packet, mem_flags,
+							endpoint_type);
+
+			g_xhci_exynos->exynos_uram_isoc_in_alloc = 1;
+		} else {
+			return xhci_ring_alloc(xhci, 2, 1, ring_type,
+							max_packet, mem_flags);
+		}
+
+	} else {
+		return xhci_ring_alloc(xhci, 2, 1, ring_type, max_packet, mem_flags);
+	}
+}
+
+void xhci_exynos_segment_free_skip(struct xhci_hcd *xhci, struct xhci_segment *seg)
+{
+	if (seg->trbs) {
+		/* Check URAM address for memory free */
+		if (seg->dma == EXYNOS_URAM_ABOX_EVT_RING_ADDR) {
+			iounmap(seg->trbs);
+		} else if (seg->dma == EXYNOS_URAM_ISOC_OUT_RING_ADDR) {
+			g_xhci_exynos->exynos_uram_isoc_out_alloc = 0;
+			if (in_interrupt())
+				g_xhci_exynos->usb_audio_isoc_out_addr = (u8 *)seg->trbs;
+			else
+				iounmap(seg->trbs);
+		} else if (seg->dma == EXYNOS_URAM_ISOC_IN_RING_ADDR) {
+			g_xhci_exynos->exynos_uram_isoc_in_alloc = 0;
+			if (in_interrupt())
+				g_xhci_exynos->usb_audio_isoc_in_addr = (u8 *)seg->trbs;
+			else
+				iounmap(seg->trbs);
+		} else
+			dma_pool_free(xhci->segment_pool, seg->trbs, seg->dma);
+
+		seg->trbs = NULL;
+	}
+	kfree(seg->bounce_buf);
+	kfree(seg);
+}
+
+void xhci_exynos_free_segments_for_ring(struct xhci_hcd *xhci,
+				struct xhci_segment *first)
+{
+	struct xhci_segment *seg;
+
+	seg = first->next;
+
+	if (!seg)
+		xhci_err(xhci, "segment is null unexpectedly\n");
+
+	while (seg != first) {
+		struct xhci_segment *next = seg->next;
+
+		xhci_exynos_segment_free_skip(xhci, seg);
+		seg = next;
+	}
+	xhci_exynos_segment_free_skip(xhci, first);
+}
+
+void xhci_exynos_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring)
+{
+	if (!ring)
+		return;
+
+	//trace_xhci_ring_free(ring);
+
+	if (ring->first_seg) {
+		if (ring->type == TYPE_STREAM)
+			xhci_remove_stream_mapping(ring);
+
+		xhci_exynos_free_segments_for_ring(xhci, ring->first_seg);
+	}
+
+	kfree(ring);
+}
+
+static void xhci_exynos_free_transfer_ring(struct xhci_hcd *xhci,
+		struct xhci_virt_device *virt_dev, unsigned int ep_index)
+{
+	xhci_exynos_ring_free(xhci, virt_dev->eps[ep_index].ring);
+}
+
+static int xhci_exynos_vendor_init(struct xhci_hcd *xhci)
+{
+	return 0;
+
+}
+static void xhci_exynos_vendor_cleanup(struct xhci_hcd *xhci)
+{
+	xhci_exynos_free_event_ring(xhci);
+}
+
+static bool xhci_exynos_is_usb_offload_enabled(struct xhci_hcd *xhci,
+		struct xhci_virt_device *virt_dev, unsigned int ep_index)
+{
+	return true;
+}
+
+static struct xhci_device_context_array *xhci_exynos_alloc_dcbaa(
+		struct xhci_hcd *xhci, gfp_t flags)
+{
+	dma_addr_t dma;
+
+	if (xhci->quirks & XHCI_USE_URAM_FOR_EXYNOS_AUDIO) {
+		int i;
+
+		xhci_info(xhci, "DCBAA is allocated at 0x%x(URAM)",
+						EXYNOS_URAM_DCBAA_ADDR);
+		/* URAM allocation for DCBAA */
+		xhci->dcbaa = ioremap(EXYNOS_URAM_DCBAA_ADDR,
+						sizeof(*xhci->dcbaa));
+		if (!xhci->dcbaa)
+			return NULL;
+		/* Clear DCBAA */
+		for (i = 0; i < MAX_HC_SLOTS; i++)
+			xhci->dcbaa->dev_context_ptrs[i] = 0x0;
+
+		xhci->dcbaa->dma = EXYNOS_URAM_DCBAA_ADDR;
+	} else {
+		xhci->dcbaa = dma_pre_alloc_coherent(xhci, sizeof(*xhci->dcbaa),
+				&dma, flags);
+		if (!xhci->dcbaa)
+			return NULL;
+		memset(xhci->dcbaa, 0, sizeof *(xhci->dcbaa));
+		xhci->dcbaa->dma = dma;
+	}
+	return xhci->dcbaa;
+}
+
+static void xhci_exynos_free_dcbaa(struct xhci_hcd *xhci)
+{
+	if (xhci->quirks & XHCI_USE_URAM_FOR_EXYNOS_AUDIO) {
+		iounmap(xhci->dcbaa);
+		if (g_xhci_exynos->usb_audio_ctx_addr != NULL) {
+			iounmap(g_xhci_exynos->usb_audio_ctx_addr);
+			g_xhci_exynos->usb_audio_ctx_addr = NULL;
+		}
+		if (g_xhci_exynos->usb_audio_isoc_out_addr != NULL) {
+			iounmap(g_xhci_exynos->usb_audio_isoc_out_addr);
+			g_xhci_exynos->usb_audio_isoc_out_addr = NULL;
+		}
+		if (g_xhci_exynos->usb_audio_isoc_in_addr != NULL) {
+			iounmap(g_xhci_exynos->usb_audio_isoc_in_addr);
+			g_xhci_exynos->usb_audio_isoc_in_addr = NULL;
+		}
+	} else
+		xhci->dcbaa = NULL;
+}
+
+/* URAM Allocation Functions */
+static struct xhci_segment *xhci_segment_alloc_uram(struct xhci_hcd *xhci,
+					       unsigned int cycle_state,
+					       unsigned int max_packet,
+					       gfp_t flags)
+{
+	struct xhci_segment *seg;
+	dma_addr_t	dma;
+	int		i;
+	struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
+
+	seg = kzalloc_node(sizeof(*seg), flags, dev_to_node(dev));
+	if (!seg)
+		return NULL;
+
+	seg->trbs = ioremap(EXYNOS_URAM_ABOX_EVT_RING_ADDR, TRB_SEGMENT_SIZE);
+	if (!seg->trbs)
+		return NULL;
+
+	dma = EXYNOS_URAM_ABOX_EVT_RING_ADDR;
+
+	if (max_packet) {
+		seg->bounce_buf = kzalloc_node(max_packet, flags,
+					dev_to_node(dev));
+		if (!seg->bounce_buf) {
+			dma_pool_free(xhci->segment_pool, seg->trbs, dma);
+			kfree(seg);
+			return NULL;
+		}
+	}
+	/* If the cycle state is 0, set the cycle bit to 1 for all the TRBs */
+	if (cycle_state == 0) {
+		for (i = 0; i < TRBS_PER_SEGMENT; i++)
+			seg->trbs[i].link.control |= cpu_to_le32(TRB_CYCLE);
+	}
+	seg->dma = dma;
+	xhci_info(xhci, "ABOX Event Ring is allocated at 0x%x",
+					EXYNOS_URAM_ABOX_EVT_RING_ADDR);
+	seg->next = NULL;
+
+	return seg;
+}
+
+static struct xhci_segment *xhci_segment_alloc_uram_ep(struct xhci_hcd *xhci,
+					       unsigned int cycle_state,
+					       unsigned int max_packet,
+					       gfp_t flags, int seg_num,
+					       u32 endpoint_type)
+{
+	struct xhci_segment *seg;
+	dma_addr_t	dma;
+	int		i;
+	struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
+
+	seg = kzalloc_node(sizeof(*seg), flags, dev_to_node(dev));
+	if (!seg)
+		return NULL;
+
+	if (seg_num != 0) {
+		/* Support just one segment */
+		xhci_err(xhci, "%s : Unexpected SEG NUMBER!\n", __func__);
+		return NULL;
+	}
+
+	if (endpoint_type == ISOC_OUT_EP) {
+		seg->trbs = ioremap(EXYNOS_URAM_ISOC_OUT_RING_ADDR,
+							TRB_SEGMENT_SIZE);
+		if (!seg->trbs)
+			return NULL;
+
+		dma = EXYNOS_URAM_ISOC_OUT_RING_ADDR;
+		xhci_info(xhci, "First ISOC-OUT Ring is allocated at 0x%x", dma);
+	} else if (endpoint_type == ISOC_IN_EP) {
+		seg->trbs = ioremap(EXYNOS_URAM_ISOC_IN_RING_ADDR,
+							TRB_SEGMENT_SIZE);
+		if (!seg->trbs)
+			return NULL;
+
+		dma = EXYNOS_URAM_ISOC_IN_RING_ADDR;
+		xhci_info(xhci, "First ISOC-IN Ring is allocated at 0x%x", dma);
+	} else {
+		xhci_err(xhci, "%s : Unexpected EP Type!\n", __func__);
+		return NULL;
+	}
+
+	for (i = 0; i < 256; i++) {
+		seg->trbs[i].link.segment_ptr = 0;
+		seg->trbs[i].link.intr_target = 0;
+		seg->trbs[i].link.control = 0;
+	}
+
+
+	if (max_packet) {
+		seg->bounce_buf = kzalloc_node(max_packet, flags,
+					dev_to_node(dev));
+		if (!seg->bounce_buf) {
+			dma_pool_free(xhci->segment_pool, seg->trbs, dma);
+			kfree(seg);
+			return NULL;
+		}
+	}
+	/* If the cycle state is 0, set the cycle bit to 1 for all the TRBs */
+	if (cycle_state == 0) {
+		for (i = 0; i < TRBS_PER_SEGMENT; i++)
+			seg->trbs[i].link.control |= cpu_to_le32(TRB_CYCLE);
+	}
+	seg->dma = dma;
+	seg->next = NULL;
+
+	return seg;
+}
+
+static int xhci_alloc_segments_for_ring_uram(struct xhci_hcd *xhci,
+		struct xhci_segment **first, struct xhci_segment **last,
+		unsigned int num_segs, unsigned int cycle_state,
+		enum xhci_ring_type type, unsigned int max_packet, gfp_t flags,
+		u32 endpoint_type)
+{
+	struct xhci_segment *prev;
+	bool chain_links;
+
+	/* Set chain bit for 0.95 hosts, and for isoc rings on AMD 0.96 host */
+	chain_links = !!(xhci_link_trb_quirk(xhci) ||
+			 (type == TYPE_ISOC &&
+			  (xhci->quirks & XHCI_AMD_0x96_HOST)));
+
+	if (type == TYPE_ISOC) {
+		prev = xhci_segment_alloc_uram_ep(xhci, cycle_state,
+							max_packet, flags, 0,
+							endpoint_type);
+	} else if (type == TYPE_EVENT) {
+		prev = xhci_segment_alloc_uram(xhci, cycle_state, max_packet, flags);
+	} else {
+		xhci_err(xhci, "Unexpected TYPE for URAM allocation!\n");
+		return -ENOMEM;
+	}
+
+	if (!prev)
+		return -ENOMEM;
+	num_segs--;
+
+	*first = prev;
+	while (num_segs > 0) {
+		struct xhci_segment	*next = NULL;
+
+		if (type == TYPE_ISOC) {
+			prev = xhci_segment_alloc_uram_ep(xhci, cycle_state,
+							max_packet, flags, 1,
+							endpoint_type);
+		} else if (type == TYPE_EVENT) {
+			next = xhci_segment_alloc_uram(xhci, cycle_state,
+							max_packet, flags);
+		} else {
+			xhci_err(xhci, "Unexpected TYPE for URAM alloc(multi)!\n");
+			return -ENOMEM;
+		}
+
+		if (!next) {
+			prev = *first;
+			while (prev) {
+				next = prev->next;
+				xhci_segment_free(xhci, prev);
+				prev = next;
+			}
+			return -ENOMEM;
+		}
+		xhci_link_segments(prev, next, type, chain_links);
+
+		prev = next;
+		num_segs--;
+	}
+	xhci_link_segments(prev, *first, type, chain_links);
+	*last = prev;
+
+	return 0;
+}
+
+struct xhci_ring *xhci_ring_alloc_uram(struct xhci_hcd *xhci,
+		unsigned int num_segs, unsigned int cycle_state,
+		enum xhci_ring_type type, unsigned int max_packet, gfp_t flags,
+		u32 endpoint_type)
+{
+	struct xhci_ring	*ring;
+	int ret;
+	struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
+
+	ring = kzalloc_node(sizeof(*ring), flags, dev_to_node(dev));
+	if (!ring)
+		return NULL;
+
+	ring->num_segs = num_segs;
+	ring->bounce_buf_len = max_packet;
+	INIT_LIST_HEAD(&ring->td_list);
+	ring->type = type;
+	if (num_segs == 0)
+		return ring;
+
+	ret = xhci_alloc_segments_for_ring_uram(xhci, &ring->first_seg,
+			&ring->last_seg, num_segs, cycle_state, type,
+			max_packet, flags, endpoint_type);
+	if (ret)
+		goto fail;
+
+	/* Only event ring does not use link TRB */
+	if (type != TYPE_EVENT) {
+		/* See section 4.9.2.1 and 6.4.4.1 */
+		ring->last_seg->trbs[TRBS_PER_SEGMENT - 1].link.control |=
+			cpu_to_le32(LINK_TOGGLE);
+	}
+	xhci_initialize_ring_info(ring, cycle_state);
+	//trace_xhci_ring_alloc(ring);
+	return ring;
+
+fail:
+	kfree(ring);
+	return NULL;
+}
+#endif
+//#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+
+extern struct usb_xhci_pre_alloc xhci_pre_alloc;
+static const struct xhci_driver_overrides xhci_exynos_overrides __initconst = {
+	.extra_priv_size = sizeof(struct xhci_exynos_priv),
+	.reset = xhci_exynos_setup,
+	.start = xhci_exynos_start,
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+	.add_endpoint = xhci_exynos_add_endpoint,
+	.address_device = xhci_exynos_address_device,
+#endif
+	.bus_suspend = xhci_exynos_bus_suspend,
+	.bus_resume = xhci_exynos_bus_resume,
+};
+
+int xhci_exynos_bus_suspend(struct usb_hcd *hcd)
+{
+	struct xhci_exynos_priv *priv = hcd_to_xhci_exynos_priv(hcd);
+	struct xhci_hcd_exynos *xhci_exynos = priv->xhci_exynos;
+	struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+	int ret, main_hcd;
+
+	pr_info("%s, priv = 0x%8x, xhci_exynos = 0x%8x\n",
+		__func__, priv, xhci_exynos);
+
+	if (hcd == xhci->main_hcd)
+		main_hcd = 1;
+	else
+		main_hcd = 0;
+
+	ret = xhci_bus_suspend(hcd);
+
+	xhci_exynos_wake_lock(xhci_exynos, main_hcd, 0);
+
+	return ret;
+}
+
+int xhci_exynos_bus_resume(struct usb_hcd *hcd)
+{
+	struct xhci_exynos_priv *priv = hcd_to_xhci_exynos_priv(hcd);
+	struct xhci_hcd_exynos *xhci_exynos = priv->xhci_exynos;
+	struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+
+	int ret, main_hcd;
+
+	if (hcd == xhci->main_hcd)
+		main_hcd = 1;
+	else
+		main_hcd = 0;
+
+	pr_info("%s, hcd = 0x%8x\n", __func__, hcd);
+
+	ret = xhci_bus_resume(hcd);
+
+	xhci_exynos_wake_lock(xhci_exynos, main_hcd, 1);
+
+	return ret;
+}
+
+static void xhci_priv_exynos_start(struct usb_hcd *hcd)
+{
+	struct xhci_exynos_priv *priv = hcd_to_xhci_exynos_priv(hcd);
+
+	if (priv->plat_start)
+		priv->plat_start(hcd);
+}
+
+void xhci_exynos_portsc_power_off(void __iomem *portsc, u32 on, u32 prt)
+{
+	struct xhci_hcd_exynos	*xhci_exynos = g_xhci_exynos;
+
+	u32 reg;
+
+	spin_lock(&xhci_exynos->xhcioff_lock);
+
+	pr_info("%s, on=%d portsc_control_priority=%d, prt=%d\n",
+		__func__, on,  xhci_exynos->portsc_control_priority, prt);
+
+	if (xhci_exynos->portsc_control_priority > prt) {
+		spin_unlock(&xhci_exynos->xhcioff_lock);
+		return;
+	}
+
+	xhci_exynos->portsc_control_priority = prt;
+
+	if (on && !xhci_exynos->port_off_done) {
+		pr_info("%s, Do not switch-on port\n", __func__);
+		spin_unlock(&xhci_exynos->xhcioff_lock);
+		return;
+	}
+
+	reg = readl(portsc);
+
+	if (on)
+		reg |= PORT_POWER;
+	else
+		reg &= ~PORT_POWER;
+
+	writel(reg, portsc);
+	reg = readl(portsc);
+
+	pr_info("power %s portsc, reg = 0x%x addr = %8x\n",
+		on ? "on" : "off", reg, (u64)portsc);
+
+	if (on)
+		xhci_exynos->port_off_done = 0;
+	else
+		xhci_exynos->port_off_done = 1;
+
+	spin_unlock(&xhci_exynos->xhcioff_lock);
+}
+
+int xhci_exynos_port_power_set(u32 on, u32 prt)
+{
+	if (!g_xhci_exynos)
+		return -EACCES;
+
+	if (g_xhci_exynos->usb3_portsc) {
+		xhci_exynos_portsc_power_off(g_xhci_exynos->usb3_portsc, on, prt);
+		return 0;
+	}
+
+	pr_info("%s, usb3_portsc is NULL\n", __func__);
+	return -EIO;
+}
+EXPORT_SYMBOL_GPL(xhci_exynos_port_power_set);
+
+static int xhci_exynos_check_port(struct usb_device *dev, bool on)
+{
+	struct usb_device *hdev;
+	struct usb_device *udev = dev;
+	struct device *ddev = &udev->dev;
+	struct xhci_hcd_exynos	*xhci_exynos = g_xhci_exynos;
+	enum usb_port_state pre_state;
+	int usb3_hub_detect = 0;
+	int usb2_detect = 0;
+	int port;
+	int bInterfaceClass = 0;
+
+	if (udev->bus->root_hub == udev) {
+		pr_info("this dev is a root hub\n");
+		goto skip;
+	}
+
+	pre_state = xhci_exynos->port_state;
+
+	/* Find root hub */
+	hdev = udev->parent;
+	if (!hdev)
+		goto skip;
+
+	hdev = dev->bus->root_hub;
+	if (!hdev)
+		goto skip;
+	pr_info("root hub maxchild = %d\n", hdev->maxchild);
+
+	/* check all ports */
+	usb_hub_for_each_child(hdev, port, udev) {
+		dev_dbg(ddev, "%s, class = %d, speed = %d\n",
+			__func__, udev->descriptor.bDeviceClass,
+						udev->speed);
+		dev_dbg(ddev, "udev = 0x%8x, state = %d\n", udev, udev->state);
+		if (udev && udev->state == USB_STATE_CONFIGURED) {
+			if (!dev->config->interface[0])
+				continue;
+
+			bInterfaceClass	= udev->config->interface[0]
+					->cur_altsetting->desc.bInterfaceClass;
+			if (on) {
+				if (bInterfaceClass == USB_CLASS_HID ||
+				    bInterfaceClass == USB_CLASS_AUDIO) {
+					udev->do_remote_wakeup =
+						(udev->config->desc.bmAttributes &
+							USB_CONFIG_ATT_WAKEUP) ? 1 : 0;
+					if (udev->do_remote_wakeup == 1) {
+						device_init_wakeup(ddev, 1);
+						usb_enable_autosuspend(dev);
+					}
+					dev_dbg(ddev, "%s, remote_wakeup = %d\n",
+						__func__, udev->do_remote_wakeup);
+				}
+			}
+			if (bInterfaceClass == USB_CLASS_HUB) {
+				xhci_exynos->port_state = PORT_HUB;
+				usb3_hub_detect = 1;
+				break;
+			} else if (bInterfaceClass == USB_CLASS_BILLBOARD) {
+				xhci_exynos->port_state = PORT_DP;
+				usb3_hub_detect = 1;
+				break;
+			}
+
+			if (udev->speed >= USB_SPEED_SUPER) {
+				xhci_exynos->port_state = PORT_USB3;
+				usb3_hub_detect = 1;
+				break;
+			} else {
+				xhci_exynos->port_state = PORT_USB2;
+				usb2_detect = 1;
+			}
+		} else {
+			pr_info("not configured, state = %d\n", udev->state);
+		}
+	}
+
+	if (!usb3_hub_detect && !usb2_detect)
+		xhci_exynos->port_state = PORT_EMPTY;
+
+	pr_info("%s %s state pre=%d now=%d\n", __func__,
+		on ? "on" : "off", pre_state, xhci_exynos->port_state);
+
+	return xhci_exynos->port_state;
+
+skip:
+	return -EINVAL;
+}
+
+static void xhci_exynos_set_port(struct usb_device *dev, bool on)
+{
+	int port;
+
+	port = xhci_exynos_check_port(dev, on);
+	if (g_xhci_exynos->dp_use == true)
+		port = PORT_DP;
+
+	switch (port) {
+	case PORT_EMPTY:
+		pr_info("Port check empty\n");
+		is_otg_only = 1;
+		if (!g_xhci_exynos->usb3_phy_control) {
+			usb_power_notify_control(0, 1);
+			g_xhci_exynos->usb3_phy_control = true;
+		}
+		xhci_exynos_port_power_set(1, 1);
+		break;
+	case PORT_USB2:
+		pr_info("Port check usb2\n");
+		is_otg_only = 0;
+		xhci_exynos_port_power_set(0, 1);
+		if (g_xhci_exynos->usb3_phy_control) {
+			usb_power_notify_control(0, 0);
+			g_xhci_exynos->usb3_phy_control = false;
+		}
+		break;
+	case PORT_USB3:
+		/* xhci_port_power_set(1, 1); */
+		is_otg_only = 0;
+		pr_info("Port check usb3\n");
+		break;
+	case PORT_HUB:
+		/*xhci_exynos_port_power_set(1, 1);*/
+		pr_info("Port check hub\n");
+		is_otg_only = 0;
+		break;
+	case PORT_DP:
+		/*xhci_exynos_port_power_set(1, 1);*/
+		pr_info("Port check DP\n");
+		is_otg_only = 0;
+		break;
+	default:
+		break;
+	}
+}
+
+int xhci_exynos_inform_dp_use(int use, int lane_cnt)
+{
+	int ret = 0;
+
+	pr_info("[%s] dp use = %d, lane_cnt = %d\n",
+			__func__, use, lane_cnt);
+
+	/*
+	 * otg_connection will be available
+	 * after usb__audio is merged.
+	 * At that time, modify this.
+	 */
+	/*if (!otg_connection || !g_xhci_exynos)*/
+	if (!g_xhci_exynos)
+		return 0;
+
+	if (use == 1) {
+		g_xhci_exynos->dp_use = true;
+#ifdef CONFIG_EXYNOS_USBDRD_PHY30
+		if (g_xhci_exynos->usb3_phy_control == false) {
+			usb_power_notify_control(1, 1);
+			g_xhci_exynos->usb3_phy_control = true;
+		}
+		if (lane_cnt == 4) {
+			exynos_usbdrd_dp_use_notice(lane_cnt);
+			ret = xhci_exynos_port_power_set(0, 2);
+		}
+#endif
+		udelay(1);
+	} else {
+#ifdef CONFIG_EXYNOS_USBDRD_PHY30
+		if (g_xhci_exynos->port_state == PORT_USB2) {
+			if (g_xhci_exynos->usb3_phy_control == true) {
+				usb_power_notify_control(1, 0);
+				g_xhci_exynos->usb3_phy_control = false;
+			}
+		}
+#endif
+		g_xhci_exynos->dp_use = false;
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL(xhci_exynos_inform_dp_use);
+
+static int xhci_exynos_power_notify(struct notifier_block *self,
+			    unsigned long action, void *dev)
+{
+	switch (action) {
+	case USB_DEVICE_ADD:
+		xhci_exynos_set_port(dev, 1);
+		break;
+	case USB_DEVICE_REMOVE:
+		xhci_exynos_set_port(dev, 0);
+		break;
+	}
+	return NOTIFY_OK;
+}
+
+static struct notifier_block dev_nb = {
+	.notifier_call = xhci_exynos_power_notify,
+};
+
+void xhci_exynos_register_notify(void)
+{
+	is_otg_only = 1;
+	g_xhci_exynos->port_state = PORT_EMPTY;
+	usb_register_notify(&dev_nb);
+}
+
+void xhci_exynos_unregister_notify(void)
+{
+	usb_unregister_notify(&dev_nb);
+}
+
+static int xhci_priv_init_quirk(struct usb_hcd *hcd)
+{
+	struct xhci_exynos_priv *priv = hcd_to_xhci_exynos_priv(hcd);
+
+	if (!priv->init_quirk)
+		return 0;
+
+	return priv->init_quirk(hcd);
+}
+
+static int xhci_priv_suspend_quirk(struct usb_hcd *hcd)
+{
+	struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd);
+
+	if (!priv->suspend_quirk)
+		return 0;
+
+	return priv->suspend_quirk(hcd);
+}
+
+static int xhci_priv_resume_quirk(struct usb_hcd *hcd)
+{
+	struct xhci_exynos_priv *priv = hcd_to_xhci_exynos_priv(hcd);
+
+	if (!priv->resume_quirk)
+		return 0;
+
+	return priv->resume_quirk(hcd);
+}
+
+static void xhci_exynos_quirks(struct device *dev, struct xhci_hcd *xhci)
+{
+	struct xhci_exynos_priv *priv = xhci_to_exynos_priv(xhci);
+
+	/*
+	 * As of now platform drivers don't provide MSI support so we ensure
+	 * here that the generic code does not try to make a pci_dev from our
+	 * dev struct in order to setup MSI
+	 */
+	xhci->quirks |= XHCI_PLAT | priv->quirks;
+}
+
+/* called during probe() after chip reset completes */
+static int xhci_exynos_setup(struct usb_hcd *hcd)
+{
+	int ret;
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+	struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+#endif
+
+	ret = xhci_priv_init_quirk(hcd);
+	if (ret)
+		return ret;
+
+	ret = xhci_gen_setup(hcd, xhci_exynos_quirks);
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+	pr_debug("%s: alloc_event_ring!\n", __func__);
+	xhci_exynos_alloc_event_ring(xhci, GFP_KERNEL);
+#endif
+
+	return ret;
+}
+
+static int xhci_exynos_start(struct usb_hcd *hcd)
+{
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+	struct xhci_hcd *xhci;
+#endif
+	int ret;
+
+	xhci_priv_exynos_start(hcd);
+	ret = xhci_run(hcd);
+
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+	xhci = hcd_to_xhci(hcd);
+	pr_debug("%s: enable_event_ring!\n", __func__);
+	xhci_exynos_usb_offload_enable_event_ring(xhci);
+#endif
+	return ret;
+}
+
+static ssize_t
+xhci_exynos_ss_compliance_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct usb_hcd *hcd = dev_get_drvdata(dev);
+	u32			reg;
+	void __iomem *reg_base;
+
+	reg_base = hcd->regs;
+	reg = readl(reg_base + PORTSC_OFFSET);
+
+	return sysfs_emit(buf, "0x%x\n", reg);
+}
+
+static ssize_t
+xhci_exynos_ss_compliance_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t n)
+{
+	struct usb_hcd *hcd = dev_get_drvdata(dev);
+	int		value;
+	u32			reg;
+	void __iomem *reg_base;
+
+	if (kstrtoint(buf, 10, &value))
+		return -EINVAL;
+
+	reg_base = hcd->regs;
+
+	if (value == 1) {
+		/* PORTSC PLS is set to 10, LWS to 1 */
+		reg = readl(reg_base + PORTSC_OFFSET);
+		reg &= ~((0xF << 5) | (1 << 16));
+		reg |= (10 << 5) | (1 << 16);
+		writel(reg, reg_base + PORTSC_OFFSET);
+		pr_info("SS host compliance enabled portsc 0x%x\n", reg);
+	} else
+		pr_info("Only 1 is allowed for input value\n");
+
+	return n;
+}
+
+static DEVICE_ATTR_RW(xhci_exynos_ss_compliance);
+
+
+static struct attribute *xhci_exynos_attrs[] = {
+	&dev_attr_xhci_exynos_ss_compliance.attr,
+	NULL
+};
+ATTRIBUTE_GROUPS(xhci_exynos);
+
+#ifdef CONFIG_OF
+static const struct of_device_id usb_xhci_of_match[] = {
+	{
+		.compatible = "generic-xhci",
+	}, {
+		.compatible = "xhci-platform",
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, usb_xhci_of_match);
+#endif
+
+static void xhci_pm_runtime_init(struct device *dev)
+{
+	dev->power.runtime_status = RPM_SUSPENDED;
+	dev->power.idle_notification = false;
+
+	dev->power.disable_depth = 1;
+	atomic_set(&dev->power.usage_count, 0);
+
+	dev->power.runtime_error = 0;
+
+	atomic_set(&dev->power.child_count, 0);
+	pm_suspend_ignore_children(dev, false);
+	dev->power.runtime_auto = true;
+
+	dev->power.request_pending = false;
+	dev->power.request = RPM_REQ_NONE;
+	dev->power.deferred_resume = false;
+	dev->power.accounting_timestamp = jiffies;
+
+	dev->power.timer_expires = 0;
+	init_waitqueue_head(&dev->power.wait_queue);
+}
+
+static struct xhci_plat_priv_overwrite xhci_plat_vendor_overwrite;
+
+int xhci_exynos_register_vendor_ops(struct xhci_vendor_ops *vendor_ops)
+{
+	if (vendor_ops == NULL)
+		return -EINVAL;
+
+	xhci_plat_vendor_overwrite.vendor_ops = vendor_ops;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(xhci_exynos_register_vendor_ops);
+
+
+static int xhci_vendor_init(struct xhci_hcd *xhci)
+{
+	struct xhci_vendor_ops *ops = NULL;
+
+	if (xhci_plat_vendor_overwrite.vendor_ops)
+		ops = xhci->vendor_ops = xhci_plat_vendor_overwrite.vendor_ops;
+
+	if (ops && ops->vendor_init)
+		return ops->vendor_init(xhci);
+	return 0;
+}
+
+static void xhci_vendor_cleanup(struct xhci_hcd *xhci)
+{
+	struct xhci_vendor_ops *ops = xhci_vendor_get_ops(xhci);
+
+	if (ops && ops->vendor_cleanup)
+		ops->vendor_cleanup(xhci);
+
+	xhci->vendor_ops = NULL;
+}
+
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+static struct xhci_vendor_ops ops = {
+	.vendor_init = xhci_exynos_vendor_init,
+	.vendor_cleanup = xhci_exynos_vendor_cleanup,
+	.is_usb_offload_enabled = xhci_exynos_is_usb_offload_enabled,
+	.alloc_dcbaa = xhci_exynos_alloc_dcbaa,
+	.free_dcbaa = xhci_exynos_free_dcbaa,
+	.alloc_transfer_ring = xhci_exynos_alloc_transfer_ring,
+	.free_transfer_ring = xhci_exynos_free_transfer_ring,
+	.alloc_container_ctx = xhci_exynos_alloc_container_ctx,
+	.free_container_ctx = xhci_exynos_free_container_ctx,
+};
+#endif
+
+int xhci_exynos_wake_lock(struct xhci_hcd_exynos *xhci_exynos,
+				   int is_main_hcd, int is_lock)
+{
+	struct usb_hcd	*hcd = xhci_exynos->hcd;
+	int idle_ip_index;
+	struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+	struct wakeup_source *main_wakelock, *shared_wakelock;
+
+	main_wakelock = xhci_exynos->main_wakelock;
+	shared_wakelock = xhci_exynos->shared_wakelock;
+
+	dev_info(xhci_exynos->dev, "<<< %s - hcd = 0x%8x 0x%8x 0x%8x\n",
+					__func__, hcd, main_wakelock, shared_wakelock);
+
+	if (xhci->xhc_state & XHCI_STATE_REMOVING) {
+		dev_info(xhci_exynos->dev, "%s - Host removing return!\n",
+				__func__);
+		return -ESHUTDOWN;
+	}
+
+	if (is_lock) {
+		if (is_main_hcd) {
+			dev_info(xhci_exynos->dev, "%s: Main HCD WAKE LOCK\n", __func__);
+			__pm_stay_awake(main_wakelock);
+		} else {
+			dev_info(xhci_exynos->dev, "%s: Shared HCD WAKE LOCK\n", __func__);
+			__pm_stay_awake(shared_wakelock);
+		}
+
+		/* Add a routine for disable IDLEIP (IP idle) */
+		dev_info(xhci_exynos->dev, "IDLEIP(SICD) disable.\n");
+		idle_ip_index = get_idle_ip_index();
+		pr_info("%s, usb idle ip = %d\n", __func__, idle_ip_index);
+		exynos_update_ip_idle_status(idle_ip_index, 0);
+	} else {
+		if (is_main_hcd) {
+			dev_info(xhci_exynos->dev, "%s: Main HCD WAKE UNLOCK\n", __func__);
+			__pm_relax(main_wakelock);
+		} else {
+			dev_info(xhci_exynos->dev, "%s: Shared HCD WAKE UNLOCK\n", __func__);
+			__pm_relax(shared_wakelock);
+		}
+
+		if (!main_wakelock->active && !shared_wakelock->active) {
+			xhci_info(xhci, "Try to IDLEIP Enable!!!\n");
+
+			/* Add a routine for enable IDLEIP (IP idle) */
+			dev_info(xhci_exynos->dev, "IDLEIP(SICD) Enable.\n");
+			idle_ip_index = get_idle_ip_index();
+			pr_info("%s, usb idle ip = %d\n", __func__, idle_ip_index);
+			exynos_update_ip_idle_status(idle_ip_index, 1);
+		}
+	}
+
+	return 0;
+}
+static int xhci_exynos_probe(struct platform_device *pdev)
+{
+	struct device		*parent = pdev->dev.parent;
+	const struct hc_driver	*driver;
+	struct device		*sysdev, *tmpdev;
+	struct xhci_hcd		*xhci;
+	struct xhci_hcd_exynos	*xhci_exynos;
+	struct xhci_exynos_priv *priv;
+	struct resource         *res;
+	struct usb_hcd		*hcd;
+	struct usb_device	*hdev;
+	struct usb_hub		*hub;
+	struct usb_port		*port_dev;
+	int			ret;
+	int			irq;
+
+	struct wakeup_source	*main_wakelock, *shared_wakelock;
+	int			value;
+
+	dev_info(&pdev->dev, "XHCI PLAT START\n");
+
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+	xhci_exynos_register_vendor_ops(&ops);
+#endif
+	main_wakelock = wakeup_source_register(&pdev->dev, dev_name(&pdev->dev));
+	__pm_stay_awake(main_wakelock);
+
+	/* Initialization shared wakelock for SS HCD */
+	shared_wakelock = wakeup_source_register(&pdev->dev, dev_name(&pdev->dev));
+	__pm_stay_awake(shared_wakelock);
+
+	is_rewa_enabled = 0;
+
+	if (usb_disabled())
+		return -ENODEV;
+
+	driver = &xhci_exynos_hc_driver;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	/*
+	 * sysdev must point to a device that is known to the system firmware
+	 * or PCI hardware. We handle these three cases here:
+	 * 1. xhci_plat comes from firmware
+	 * 2. xhci_plat is child of a device from firmware (dwc3-plat)
+	 * 3. xhci_plat is grandchild of a pci device (dwc3-pci)
+	 */
+	for (sysdev = &pdev->dev; sysdev; sysdev = sysdev->parent) {
+		if (is_of_node(sysdev->fwnode) ||
+			is_acpi_device_node(sysdev->fwnode))
+			break;
+#ifdef CONFIG_PCI
+		else if (sysdev->bus == &pci_bus_type)
+			break;
+#endif
+	}
+
+	if (!sysdev)
+		sysdev = &pdev->dev;
+
+	/* Try to set 64-bit DMA first */
+	if (WARN_ON(!sysdev->dma_mask))
+		/* Platform did not initialize dma_mask */
+		ret = dma_coerce_mask_and_coherent(sysdev,
+						   DMA_BIT_MASK(64));
+	else
+		ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(64));
+
+	/* If seting 64-bit DMA mask fails, fall back to 32-bit DMA mask */
+	if (ret) {
+		ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(32));
+		if (ret)
+			return ret;
+	}
+
+	xhci_pm_runtime_init(&pdev->dev);
+
+	pm_runtime_set_active(&pdev->dev);
+	pm_runtime_enable(&pdev->dev);
+	pm_runtime_get_noresume(&pdev->dev);
+
+	hcd = __usb_create_hcd(driver, sysdev, &pdev->dev,
+			       dev_name(&pdev->dev), NULL);
+	if (!hcd) {
+		ret = -ENOMEM;
+		goto disable_runtime;
+	}
+	hcd->skip_phy_initialization = 1;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	hcd->regs = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(hcd->regs)) {
+		ret = PTR_ERR(hcd->regs);
+		goto put_hcd;
+	}
+
+	hcd->rsrc_start = res->start;
+	hcd->rsrc_len = resource_size(res);
+
+	xhci = hcd_to_xhci(hcd);
+
+	g_xhci_exynos = devm_kzalloc(&pdev->dev, sizeof(struct xhci_hcd_exynos), GFP_KERNEL);
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+	g_hwinfo = devm_kzalloc(hcd->self.sysdev, sizeof(struct hcd_hw_info), GFP_KERNEL);
+#endif
+
+	ret = xhci_vendor_init(xhci);
+	if (ret)
+		goto put_hcd;
+
+	if (g_xhci_exynos)
+		xhci_exynos = g_xhci_exynos;
+	else
+		goto put_hcd;
+
+	xhci_exynos->dev = &pdev->dev;
+
+	xhci_exynos->hcd = (struct usb_hcd *)platform_get_drvdata(pdev);
+	platform_set_drvdata(pdev, xhci_exynos);
+
+	spin_lock_init(&xhci_exynos->xhcioff_lock);
+
+	xhci_exynos->port_off_done = 0;
+	xhci_exynos->portsc_control_priority = 0;
+	xhci_exynos->usb3_phy_control = true;
+	xhci_exynos->dp_use = 0;
+
+	xhci_exynos->usb3_portsc = hcd->regs + PORTSC_OFFSET;
+	if (xhci_exynos->port_set_delayed) {
+		pr_info("port power set delayed\n");
+		xhci_exynos_portsc_power_off(xhci_exynos->usb3_portsc, 0, 2);
+		xhci_exynos->port_set_delayed = 0;
+	}
+
+	xhci_exynos->main_wakelock = main_wakelock;
+	xhci_exynos->shared_wakelock = shared_wakelock;
+
+	xhci_exynos_register_notify();
+
+	/*
+	 * Not all platforms have clks so it is not an error if the
+	 * clock do not exist.
+	 */
+	xhci->reg_clk = devm_clk_get_optional(&pdev->dev, "reg");
+	if (IS_ERR(xhci->reg_clk)) {
+		ret = PTR_ERR(xhci->reg_clk);
+		goto put_hcd;
+	}
+
+	ret = clk_prepare_enable(xhci->reg_clk);
+	if (ret)
+		goto put_hcd;
+
+	xhci->clk = devm_clk_get_optional(&pdev->dev, NULL);
+	if (IS_ERR(xhci->clk)) {
+		ret = PTR_ERR(xhci->clk);
+		goto disable_reg_clk;
+	}
+
+	ret = clk_prepare_enable(xhci->clk);
+	if (ret)
+		goto disable_reg_clk;
+
+	priv = hcd_to_xhci_exynos_priv(hcd);
+	priv->xhci_exynos = xhci_exynos;
+
+	device_wakeup_enable(hcd->self.controller);
+
+	xhci->main_hcd = hcd;
+	xhci->shared_hcd = __usb_create_hcd(driver, sysdev, &pdev->dev,
+			dev_name(&pdev->dev), hcd);
+	if (!xhci->shared_hcd) {
+		ret = -ENOMEM;
+		goto disable_clk;
+	}
+	xhci->shared_hcd->skip_phy_initialization = 1;
+
+	xhci_exynos->shared_hcd = xhci->shared_hcd;
+
+	/* imod_interval is the interrupt moderation value in nanoseconds. */
+	xhci->imod_interval = 40000;
+
+	/* Iterate over all parent nodes for finding quirks */
+	for (tmpdev = &pdev->dev; tmpdev; tmpdev = tmpdev->parent) {
+
+		if (device_property_read_bool(tmpdev, "usb2-lpm-disable"))
+			xhci->quirks |= XHCI_HW_LPM_DISABLE;
+
+		if (device_property_read_bool(tmpdev, "usb3-lpm-capable"))
+			xhci->quirks |= XHCI_LPM_SUPPORT;
+
+		if (device_property_read_bool(tmpdev, "quirk-broken-port-ped"))
+			xhci->quirks |= XHCI_BROKEN_PORT_PED;
+
+		device_property_read_u32(tmpdev, "imod-interval-ns",
+					 &xhci->imod_interval);
+	}
+
+	hcd->usb_phy = devm_usb_get_phy_by_phandle(sysdev, "usb-phy", 0);
+	if (IS_ERR(hcd->usb_phy)) {
+		ret = PTR_ERR(hcd->usb_phy);
+		if (ret == -EPROBE_DEFER)
+			goto put_usb3_hcd;
+		hcd->usb_phy = NULL;
+	} else {
+		ret = usb_phy_init(hcd->usb_phy);
+		if (ret)
+			goto put_usb3_hcd;
+	}
+
+	/* Get USB2.0 PHY for main hcd */
+	if (parent) {
+		xhci_exynos->phy_usb2 = devm_phy_get(parent, "usb2-phy");
+		if (IS_ERR_OR_NULL(xhci_exynos->phy_usb2)) {
+			xhci_exynos->phy_usb2 = NULL;
+			dev_err(&pdev->dev,
+				"%s: failed to get phy\n", __func__);
+		}
+	}
+
+	/* Get USB3.0 PHY to tune the PHY */
+	if (parent) {
+		xhci_exynos->phy_usb3 =
+				devm_phy_get(parent, "usb3-phy");
+		if (IS_ERR_OR_NULL(xhci_exynos->phy_usb3)) {
+			xhci_exynos->phy_usb3 = NULL;
+			dev_err(&pdev->dev,
+				"%s: failed to get phy\n", __func__);
+		}
+	}
+
+	ret = of_property_read_u32(parent->of_node, "xhci_l2_support", &value);
+	if (ret == 0 && value == 1)
+		xhci->quirks |= XHCI_L2_SUPPORT;
+	else {
+		dev_err(&pdev->dev,
+			"can't get xhci l2 support, error = %d\n", ret);
+	}
+
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+	ret = of_property_read_u32(parent->of_node,
+				"xhci_use_uram_for_audio", &value);
+	if (ret == 0 && value == 1) {
+		/*
+		 * Check URAM address. At least the following address should
+		 * be defined.(Otherwise, URAM feature will be disabled.)
+		 */
+		if (EXYNOS_URAM_DCBAA_ADDR == 0x0 ||
+				EXYNOS_URAM_ABOX_ERST_SEG_ADDR == 0x0 ||
+				EXYNOS_URAM_ABOX_EVT_RING_ADDR == 0x0 ||
+				EXYNOS_URAM_DEVICE_CTX_ADDR == 0x0 ||
+				EXYNOS_URAM_ISOC_OUT_RING_ADDR == 0x0) {
+			dev_info(&pdev->dev,
+				"Some URAM addresses are not defiend!\n");
+			goto skip_uram;
+		}
+
+		dev_info(&pdev->dev, "Support URAM for USB audio.\n");
+		xhci->quirks |= XHCI_USE_URAM_FOR_EXYNOS_AUDIO;
+		/* Initialization Default Value */
+		xhci_exynos->exynos_uram_ctx_alloc = false;
+		xhci_exynos->exynos_uram_isoc_out_alloc = false;
+		xhci_exynos->exynos_uram_isoc_in_alloc = false;
+		xhci_exynos->usb_audio_ctx_addr = NULL;
+		xhci_exynos->usb_audio_isoc_out_addr = NULL;
+		xhci_exynos->usb_audio_isoc_in_addr = NULL;
+	} else {
+		dev_err(&pdev->dev, "URAM is not used.\n");
+	}
+skip_uram:
+
+	xhci_exynos->xhci_alloc = &xhci_pre_alloc;
+
+#endif
+
+	hcd->tpl_support = of_usb_host_tpl_support(sysdev->of_node);
+	xhci->shared_hcd->tpl_support = hcd->tpl_support;
+	ret = usb_add_hcd(hcd, irq, IRQF_SHARED);
+	if (ret)
+		goto disable_usb_phy;
+
+	/* Set port_dev quirks for reduce port initialize time */
+	hdev = xhci->main_hcd->self.root_hub;
+	hub = usb_get_intfdata(hdev->actconfig->interface[0]);
+	port_dev = hub->ports[0];
+	port_dev->quirks |= USB_PORT_QUIRK_FAST_ENUM;
+
+	if (HCC_MAX_PSA(xhci->hcc_params) >= 4)
+		xhci->shared_hcd->can_do_streams = 1;
+
+	ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED);
+	if (ret)
+		goto dealloc_usb2_hcd;
+
+	/* Set port_dev quirks for reduce port initialize time */
+	hdev = xhci->shared_hcd->self.root_hub;
+	hub = usb_get_intfdata(hdev->actconfig->interface[0]);
+	if (hub) {
+		port_dev = hub->ports[0];
+		port_dev->quirks |= USB_PORT_QUIRK_FAST_ENUM;
+	}
+
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+	ret = of_property_read_u32(parent->of_node,
+			"usb_audio_offloading", &value);
+	if (ret == 0 && value == 1) {
+		ret = exynos_usb_audio_init(parent, pdev);
+		if (ret) {
+			dev_err(&pdev->dev, "USB Audio INIT fail\n");
+			goto dealloc_usb2_hcd;
+		}
+		dev_info(&pdev->dev, "USB Audio offloading is supported\n");
+	} else
+		dev_err(&pdev->dev, "No usb offloading, err = %d\n", ret);
+
+	xhci_exynos->out_dma = xhci_data.out_data_dma;
+	xhci_exynos->out_addr = xhci_data.out_data_addr;
+	xhci_exynos->in_dma = xhci_data.in_data_dma;
+	xhci_exynos->in_addr = xhci_data.in_data_addr;
+#endif
+
+	device_enable_async_suspend(&pdev->dev);
+	pm_runtime_put_noidle(&pdev->dev);
+
+	device_set_wakeup_enable(&xhci->main_hcd->self.root_hub->dev, 1);
+
+#ifdef CONFIG_EXYNOS_USBDRD_PHY30
+	device_set_wakeup_enable(&xhci->shared_hcd->self.root_hub->dev, 1);
+#else
+	device_set_wakeup_enable(&xhci->shared_hcd->self.root_hub->dev, 0);
+#endif
+
+	/*
+	 * Prevent runtime pm from being on as default, users should enable
+	 * runtime pm using power/control in sysfs.
+	 */
+	pm_runtime_forbid(&pdev->dev);
+
+	return 0;
+
+
+dealloc_usb2_hcd:
+	usb_remove_hcd(hcd);
+
+disable_usb_phy:
+	usb_phy_shutdown(hcd->usb_phy);
+
+put_usb3_hcd:
+	usb_put_hcd(xhci->shared_hcd);
+
+disable_clk:
+	clk_disable_unprepare(xhci->clk);
+
+disable_reg_clk:
+	clk_disable_unprepare(xhci->reg_clk);
+
+put_hcd:
+	xhci_exynos_unregister_notify();
+	usb_put_hcd(hcd);
+
+disable_runtime:
+	pm_runtime_put_noidle(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+
+	if (main_wakelock) {
+		wakeup_source_unregister(main_wakelock);
+		pr_err("%s: unregister main_wakelock", __func__);
+	}
+	if (shared_wakelock) {
+		wakeup_source_unregister(shared_wakelock);
+		pr_err("%s: unregister shared_wakelock", __func__);
+	}
+
+	return ret;
+}
+
+static int xhci_exynos_remove(struct platform_device *dev)
+{
+	struct xhci_hcd_exynos *xhci_exynos = platform_get_drvdata(dev);
+	struct usb_hcd	*hcd = xhci_exynos->hcd;
+	struct xhci_hcd	*xhci = hcd_to_xhci(hcd);
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+	struct device	*parent = dev->dev.parent;
+#endif
+	struct clk *clk = xhci->clk;
+	struct clk *reg_clk = xhci->reg_clk;
+	struct usb_hcd *shared_hcd = xhci->shared_hcd;
+	struct usb_device *rhdev = hcd->self.root_hub;
+	struct usb_device *srhdev = shared_hcd->self.root_hub;
+	struct usb_device *udev;
+	int port, need_wait, timeout;
+
+	dev_info(&dev->dev, "XHCI PLAT REMOVE\n");
+
+	pm_runtime_get_sync(&dev->dev);
+	xhci->xhc_state |= XHCI_STATE_REMOVING;
+
+	if (xhci_exynos->port_state == PORT_USB2)
+		usb_power_notify_control(0, 1);
+
+	xhci_exynos_port_power_set(1, 3);
+
+	xhci_exynos->usb3_portsc = NULL;
+	xhci_exynos->port_set_delayed = 0;
+
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+	xhci_exynos->xhci_alloc->offset = 0;
+	dev_info(&dev->dev, "WAKE UNLOCK\n");
+#endif
+
+	__pm_relax(xhci_exynos->main_wakelock);
+
+	__pm_relax(xhci_exynos->shared_wakelock);
+
+	xhci_exynos_unregister_notify();
+
+	if (!rhdev || !srhdev)
+		goto remove_hcd;
+
+	/* check all ports */
+	for (timeout = 0; timeout < XHCI_HUB_EVENT_TIMEOUT; timeout++) {
+		need_wait = false;
+		usb_hub_for_each_child(rhdev, port, udev) {
+			if (udev && udev->devnum != -1)
+				need_wait = true;
+		}
+		if (need_wait == false) {
+			usb_hub_for_each_child(srhdev, port, udev) {
+				if (udev && udev->devnum != -1)
+					need_wait = true;
+			}
+		}
+		if (need_wait == true) {
+			usleep_range(20000, 22000);
+			timeout += 20;
+			xhci_info(xhci, "Waiting USB hub disconnect\n");
+		} else {
+			xhci_info(xhci,	"device disconnect all done\n");
+			break;
+		}
+	}
+
+remove_hcd:
+	usb_remove_hcd(shared_hcd);
+	xhci->shared_hcd = NULL;
+	usb_phy_shutdown(hcd->usb_phy);
+	/*
+	 * In usb_remove_hcd, phy_exit is called if phy is not NULL.
+	 * However, in the case that PHY was turn on or off as runtime PM,
+	 * PHY sould not exit at this time. So, to prevent the PHY exit,
+	 * PHY pointer have to be NULL.
+	 */
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+	if (parent && xhci_exynos->phy_usb2)
+		xhci_exynos->phy_usb2 = NULL;
+
+	if (parent && xhci_exynos->phy_usb3)
+		xhci_exynos->phy_usb3 = NULL;
+#endif
+	usb_remove_hcd(hcd);
+
+	xhci_vendor_cleanup(xhci);
+	wakeup_source_unregister(xhci_exynos->main_wakelock);
+	wakeup_source_unregister(xhci_exynos->shared_wakelock);
+
+	devm_iounmap(&dev->dev, hcd->regs);
+	usb_put_hcd(shared_hcd);
+
+	clk_disable_unprepare(clk);
+	clk_disable_unprepare(reg_clk);
+	usb_put_hcd(hcd);
+
+	pm_runtime_disable(&dev->dev);
+	pm_runtime_put_noidle(&dev->dev);
+	pm_runtime_set_suspended(&dev->dev);
+
+	return 0;
+}
+
+/*
+static void xhci_exynos_shutdown(struct platform_device *dev)
+{
+	struct xhci_hcd_exynos *xhci_exynos = platform_get_drvdata(dev);
+	struct usb_hcd	*hcd = xhci_exynos->hcd;
+
+	platform_set_drvdata(dev, hcd);
+	usb_hcd_platform_shutdown(dev);
+	platform_set_drvdata(dev, xhci_exynos);
+}
+*/
+extern u32 otg_is_connect(void);
+static int __maybe_unused xhci_exynos_suspend(struct device *dev)
+{
+	struct xhci_hcd_exynos *xhci_exynos = dev_get_drvdata(dev);
+	struct usb_hcd	*hcd = xhci_exynos->hcd;
+	struct xhci_hcd	*xhci = hcd_to_xhci(hcd);
+	int ret = 0;
+
+	if (otg_is_connect() == 1) { /* If it is OTG_CONNECT_ONLY */
+		ret = xhci_priv_suspend_quirk(hcd);
+		if (ret)
+			return ret;
+		/*
+		 * xhci_suspend() needs `do_wakeup` to know whether host is allowed
+		 * to do wakeup during suspend.
+		 */
+		pr_info("[%s]: xhci_suspend!\n", __func__);
+		ret = xhci_suspend(xhci, device_may_wakeup(dev));
+	} else {  /* Enable HS ReWA */
+		exynos_usbdrd_phy_vendor_set(xhci_exynos->phy_usb2, 1, 0);
+#ifdef CONFIG_EXYNOS_USBDRD_PHY30
+		/* Enable SS ReWA */
+		exynos_usbdrd_phy_vendor_set(xhci_exynos->phy_usb3, 1, 0);
+#endif
+		is_rewa_enabled = 1;
+	}
+	usb_dr_role_control(0);
+
+	return ret;
+}
+
+static int __maybe_unused xhci_exynos_resume(struct device *dev)
+{
+	struct xhci_hcd_exynos *xhci_exynos = dev_get_drvdata(dev);
+	struct usb_hcd	*hcd = xhci_exynos->hcd;
+	struct xhci_hcd	*xhci = hcd_to_xhci(hcd);
+	int ret;
+
+	ret = xhci_priv_resume_quirk(hcd);
+	if (ret)
+		return ret;
+
+	if (otg_is_connect() == 1) { /* If it is OTG_CONNECT_ONLY */
+		pr_info("[%s]: xhci_resume!\n", __func__);
+		ret = xhci_resume(xhci, 0);
+
+		if (ret)
+			return ret;
+	}
+
+	if (is_rewa_enabled == 1) {
+#ifdef CONFIG_EXYNOS_USBDRD_PHY30
+		/* Disable SS ReWA */
+		exynos_usbdrd_phy_vendor_set(xhci_exynos->phy_usb3, 1, 1);
+#endif
+		/* Disablee HS ReWA */
+		exynos_usbdrd_phy_vendor_set(xhci_exynos->phy_usb2, 1, 1);
+		exynos_usbdrd_phy_vendor_set(xhci_exynos->phy_usb2, 0, 0);
+		is_rewa_enabled = 0;
+	}
+	usb_dr_role_control(1);
+
+	return ret;
+}
+
+static const struct dev_pm_ops xhci_plat_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(xhci_exynos_suspend, xhci_exynos_resume)
+};
+
+static const struct acpi_device_id usb_xhci_acpi_match[] = {
+	/* XHCI-compliant USB Controller */
+	{ "PNP0D10", },
+	{ }
+};
+MODULE_DEVICE_TABLE(acpi, usb_xhci_acpi_match);
+
+static struct platform_driver usb_xhci_driver = {
+	.probe	= xhci_exynos_probe,
+	.remove	= xhci_exynos_remove,
+	/* Host is supposed to removed in usb_reboot_noti.
+	 * Thats's why we don't need xhci_exynos_shutdown.
+	 * .shutdown = xhci_exynos_shutdown,
+	 */
+	.driver	= {
+		.name = "xhci-hcd-exynos",
+		.pm = &xhci_plat_pm_ops,
+		.of_match_table = of_match_ptr(usb_xhci_of_match),
+		.acpi_match_table = ACPI_PTR(usb_xhci_acpi_match),
+		.dev_groups = xhci_exynos_groups,
+	},
+};
+MODULE_ALIAS("platform:xhci-hcd-exynos");
+
+static int __init xhci_exynos_init(void)
+{
+	xhci_init_driver(&xhci_exynos_hc_driver, &xhci_exynos_overrides);
+	return platform_driver_register(&usb_xhci_driver);
+}
+module_init(xhci_exynos_init);
+
+static void __exit xhci_exynos_exit(void)
+{
+	platform_driver_unregister(&usb_xhci_driver);
+}
+module_exit(xhci_exynos_exit);
+
+MODULE_DESCRIPTION("xHCI Exynos Platform Host Controller Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/host/xhci-exynos.h b/drivers/usb/host/xhci-exynos.h
new file mode 100644
index 000000000000..51a872e19fa5
--- /dev/null
+++ b/drivers/usb/host/xhci-exynos.h
@@ -0,0 +1,150 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * xhci-exynos.h - xHCI host controller driver platform Bus Glue for Exynos.
+ *
+ * Copyright (C) 2022 Samsung Electronics Incorporated - http://www.samsung.com
+ * Author: Daehwan Jung <dh10.jung@xxxxxxxxxxx>
+ *
+ * A lot of code borrowed from the Linux xHCI driver.
+ */
+
+#include "xhci.h"
+#include "../dwc3/dwc3-exynos.h"
+
+/*
+ * Sometimes deadlock occurred between hub_event and remove_hcd.
+ * In order to prevent it, waiting for completion of hub_event was added.
+ * This is a timeout (300msec) value for the waiting.
+ */
+#define XHCI_HUB_EVENT_TIMEOUT	(300)
+
+#define XHCI_USE_URAM_FOR_EXYNOS_AUDIO	BIT_ULL(62)
+#define XHCI_L2_SUPPORT			BIT_ULL(63)
+
+#define PORTSC_OFFSET		0x430
+
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+
+/* EXYNOS uram memory map */
+#if defined(CONFIG_SOC_S5E9925)
+#define EXYNOS_URAM_ABOX_EVT_RING_ADDR	0x02a00000
+#define EXYNOS_URAM_ISOC_OUT_RING_ADDR	0x02a01000
+#define EXYNOS_URAM_ISOC_IN_RING_ADDR	0x02a02000
+#define EXYNOS_URAM_DEVICE_CTX_ADDR	0x02a03000
+#define EXYNOS_URAM_DCBAA_ADDR		0x02a03880
+#define EXYNOS_URAM_ABOX_ERST_SEG_ADDR	0x02a03C80
+#elif defined(CONFIG_SOC_S5E8825)
+#define EXYNOS_URAM_ABOX_EVT_RING_ADDR	0x13300000
+#define EXYNOS_URAM_ISOC_OUT_RING_ADDR	0x13301000
+#define EXYNOS_URAM_ISOC_IN_RING_ADDR	0x13302000
+#define EXYNOS_URAM_DEVICE_CTX_ADDR	0x13303000
+#define EXYNOS_URAM_DCBAA_ADDR		0x13303880
+#define EXYNOS_URAM_ABOX_ERST_SEG_ADDR	0x13303C80
+#else
+#error
+#endif
+#endif
+
+enum usb_port_state {
+	PORT_EMPTY = 0,		/* OTG only */
+	PORT_USB2,		/* usb 2.0 device only */
+	PORT_USB3,		/* usb 3.0 device only */
+	PORT_HUB,		/* usb hub single */
+	PORT_DP			/* DP device */
+};
+
+struct xhci_hcd_exynos {
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+	struct	xhci_intr_reg __iomem *ir_set_audio;
+#endif
+
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+	struct xhci_ring	*event_ring_audio;
+	struct xhci_erst	erst_audio;
+	dma_addr_t save_dma;
+	void *save_addr;
+	dma_addr_t out_dma;
+	void *out_addr;
+	dma_addr_t in_dma;
+	void *in_addr;
+#endif
+	struct device		*dev;
+	struct usb_hcd		*hcd;
+	struct usb_hcd		*shared_hcd;
+
+	struct wakeup_source *main_wakelock; /* Wakelock for HS HCD */
+	struct wakeup_source *shared_wakelock; /* Wakelock for SS HCD */
+
+	void __iomem		*usb3_portsc;
+	spinlock_t		xhcioff_lock;
+	int			port_off_done;
+	int			port_set_delayed;
+	u32			portsc_control_priority;
+	enum usb_port_state	port_state;
+	int			dp_use;
+	int			usb3_phy_control;
+
+	struct usb_xhci_pre_alloc	*xhci_alloc;
+	struct phy		*phy_usb2;
+	struct phy		*phy_usb3;
+
+	/* This flag is used to check first allocation for URAM */
+	bool			exynos_uram_ctx_alloc;
+	bool			exynos_uram_isoc_out_alloc;
+	bool			exynos_uram_isoc_in_alloc;
+	u8			*usb_audio_ctx_addr;
+	u8			*usb_audio_isoc_out_addr;
+	u8			*usb_audio_isoc_in_addr;
+
+};
+
+struct usb_phy_roothub {
+	struct phy		*phy;
+	struct list_head	list;
+};
+
+struct xhci_exynos_priv {
+	const char *firmware_name;
+	unsigned long long quirks;
+	struct xhci_vendor_data *vendor_data;
+	int (*plat_setup)(struct usb_hcd *hcd);
+	void (*plat_start)(struct usb_hcd *hcd);
+	int (*init_quirk)(struct usb_hcd *hcd);
+	int (*suspend_quirk)(struct usb_hcd *hcd);
+	int (*resume_quirk)(struct usb_hcd *hcd);
+	struct xhci_hcd_exynos *xhci_exynos;
+};
+
+#define hcd_to_xhci_exynos_priv(h) ((struct xhci_exynos_priv *)hcd_to_xhci(h)->priv)
+#define xhci_to_exynos_priv(x) ((struct xhci_exynos_priv *)(x)->priv)
+
+extern int is_otg_only;
+extern int exynos_usbdrd_phy_vendor_set(struct phy *phy, int is_enable,
+					int is_cancel);
+extern int get_idle_ip_index(void);
+
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+int xhci_store_hw_info(struct usb_hcd *hcd, struct usb_device *udev);
+int xhci_set_deq(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx,
+		unsigned int last_ep, struct usb_device *udev);
+extern void xhci_remove_stream_mapping(struct xhci_ring *ring);
+extern struct hcd_hw_info *g_hwinfo;
+extern int xhci_check_trb_in_td_math(struct xhci_hcd *xhci);
+void xhci_exynos_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring);
+extern void xhci_segment_free(struct xhci_hcd *xhci, struct xhci_segment *seg);
+extern void xhci_link_segments(struct xhci_segment *prev, struct xhci_segment *next,
+		enum xhci_ring_type type, bool chain_links);
+extern void xhci_initialize_ring_info(struct xhci_ring *ring,
+					unsigned int cycle_state);
+#endif
+
+int xhci_hub_check_speed(struct usb_hcd *hcd);
+int xhci_check_usbl2_support(struct usb_hcd *hcd);
+int xhci_wake_lock(struct usb_hcd *hcd, int is_lock);
+void xhci_usb_parse_endpoint(struct usb_device *udev, struct usb_endpoint_descriptor *desc,
+				int size);
+
+extern int exynos_usbdrd_phy_vendor_set(struct phy *phy, int is_enable,
+		int is_cancel);
+extern int exynos_usbdrd_phy_tune(struct phy *phy, int phy_state);
+void usb_power_notify_control(int owner, int on);
-- 
2.31.1




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux