lkml.org 
[lkml]   [2022]   [Mar]   [7]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
From
Subject[PATCH v2 4/4] usb: host: add xhci-exynos module
Date
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@samsung.com>
---
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@samsung.com>
+ *
+ * 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@samsung.com>
+ *
+ * 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
\
 
 \ /
  Last update: 2022-03-07 12:01    [W:0.157 / U:4.920 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site