From de66754e9f8029f8ae955a588959b99cab56b506 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Wed, 9 Apr 2025 12:47:34 -0700 Subject: xhci: sideband: add initial api to register a secondary interrupter entity Introduce XHCI sideband, which manages the USB endpoints being requested by a client driver. This is used for when client drivers are attempting to offload USB endpoints to another entity for handling USB transfers. XHCI sec intr will allow for drivers to fetch the required information about the transfer ring, so the user can submit transfers independently. Expose the required APIs for drivers to register and request for a USB endpoint and to manage XHCI secondary interrupters. Driver renaming, multiple ring segment page linking, proper endpoint clean up, and allowing module compilation added by Wesley Cheng to complete original concept code by Mathias Nyman. Tested-by: Puma Hsu Tested-by: Daehwan Jung Signed-off-by: Mathias Nyman Co-developed-by: Wesley Cheng Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-2-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/Kconfig | 9 + drivers/usb/host/Makefile | 4 + drivers/usb/host/xhci-sideband.c | 429 ++++++++++++++++++++++++++++++++++++++ drivers/usb/host/xhci.h | 4 + include/linux/usb/xhci-sideband.h | 74 +++++++ 5 files changed, 520 insertions(+) create mode 100644 drivers/usb/host/xhci-sideband.c create mode 100644 include/linux/usb/xhci-sideband.h diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index d011d6c753ed..033a9a4b51fe 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -104,6 +104,15 @@ config USB_XHCI_RZV2M Say 'Y' to enable the support for the xHCI host controller found in Renesas RZ/V2M SoC. +config USB_XHCI_SIDEBAND + bool "xHCI support for sideband" + help + Say 'Y' to enable the support for the xHCI sideband capability. + Provide a mechanism for a sideband datapath for payload associated + with audio class endpoints. This allows for an audio DSP to use + xHCI USB endpoints directly, allowing CPU to sleep while playing + audio. + config USB_XHCI_TEGRA tristate "xHCI support for NVIDIA Tegra SoCs" depends on PHY_TEGRA_XUSB diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index be4e5245c52f..4df946c05ba0 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -32,6 +32,10 @@ endif xhci-rcar-hcd-y += xhci-rcar.o xhci-rcar-hcd-$(CONFIG_USB_XHCI_RZV2M) += xhci-rzv2m.o +ifneq ($(CONFIG_USB_XHCI_SIDEBAND),) + xhci-hcd-y += xhci-sideband.o +endif + obj-$(CONFIG_USB_PCI) += pci-quirks.o obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c new file mode 100644 index 000000000000..19c58ae60414 --- /dev/null +++ b/drivers/usb/host/xhci-sideband.c @@ -0,0 +1,429 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * xHCI host controller sideband support + * + * Copyright (c) 2023-2025, Intel Corporation. + * + * Author: Mathias Nyman + */ + +#include +#include + +#include "xhci.h" + +/* sideband internal helpers */ +static struct sg_table * +xhci_ring_to_sgtable(struct xhci_sideband *sb, struct xhci_ring *ring) +{ + struct xhci_segment *seg; + struct sg_table *sgt; + unsigned int n_pages; + struct page **pages; + struct device *dev; + size_t sz; + int i; + + dev = xhci_to_hcd(sb->xhci)->self.sysdev; + sz = ring->num_segs * TRB_SEGMENT_SIZE; + n_pages = PAGE_ALIGN(sz) >> PAGE_SHIFT; + pages = kvmalloc_array(n_pages, sizeof(struct page *), GFP_KERNEL); + if (!pages) + return NULL; + + sgt = kzalloc(sizeof(*sgt), GFP_KERNEL); + if (!sgt) { + kvfree(pages); + return NULL; + } + + seg = ring->first_seg; + if (!seg) + goto err; + /* + * Rings can potentially have multiple segments, create an array that + * carries page references to allocated segments. Utilize the + * sg_alloc_table_from_pages() to create the sg table, and to ensure + * that page links are created. + */ + for (i = 0; i < ring->num_segs; i++) { + dma_get_sgtable(dev, sgt, seg->trbs, seg->dma, + TRB_SEGMENT_SIZE); + pages[i] = sg_page(sgt->sgl); + sg_free_table(sgt); + seg = seg->next; + } + + if (sg_alloc_table_from_pages(sgt, pages, n_pages, 0, sz, GFP_KERNEL)) + goto err; + + /* + * Save first segment dma address to sg dma_address field for the sideband + * client to have access to the IOVA of the ring. + */ + sg_dma_address(sgt->sgl) = ring->first_seg->dma; + + return sgt; + +err: + kvfree(pages); + kfree(sgt); + + return NULL; +} + +static void +__xhci_sideband_remove_endpoint(struct xhci_sideband *sb, struct xhci_virt_ep *ep) +{ + /* + * Issue a stop endpoint command when an endpoint is removed. + * The stop ep cmd handler will handle the ring cleanup. + */ + xhci_stop_endpoint_sync(sb->xhci, ep, 0, GFP_KERNEL); + + ep->sideband = NULL; + sb->eps[ep->ep_index] = NULL; +} + +/* sideband api functions */ + +/** + * xhci_sideband_add_endpoint - add endpoint to sideband access list + * @sb: sideband instance for this usb device + * @host_ep: usb host endpoint + * + * Adds an endpoint to the list of sideband accessed endpoints for this usb + * device. + * After an endpoint is added the sideband client can get the endpoint transfer + * ring buffer by calling xhci_sideband_endpoint_buffer() + * + * Return: 0 on success, negative error otherwise. + */ +int +xhci_sideband_add_endpoint(struct xhci_sideband *sb, + struct usb_host_endpoint *host_ep) +{ + struct xhci_virt_ep *ep; + unsigned int ep_index; + + mutex_lock(&sb->mutex); + ep_index = xhci_get_endpoint_index(&host_ep->desc); + ep = &sb->vdev->eps[ep_index]; + + if (ep->ep_state & EP_HAS_STREAMS) { + mutex_unlock(&sb->mutex); + return -EINVAL; + } + + /* + * Note, we don't know the DMA mask of the audio DSP device, if its + * smaller than for xhci it won't be able to access the endpoint ring + * buffer. This could be solved by not allowing the audio class driver + * to add the endpoint the normal way, but instead offload it immediately, + * and let this function add the endpoint and allocate the ring buffer + * with the smallest common DMA mask + */ + if (sb->eps[ep_index] || ep->sideband) { + mutex_unlock(&sb->mutex); + return -EBUSY; + } + + ep->sideband = sb; + sb->eps[ep_index] = ep; + mutex_unlock(&sb->mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(xhci_sideband_add_endpoint); + +/** + * xhci_sideband_remove_endpoint - remove endpoint from sideband access list + * @sb: sideband instance for this usb device + * @host_ep: usb host endpoint + * + * Removes an endpoint from the list of sideband accessed endpoints for this usb + * device. + * sideband client should no longer touch the endpoint transfer buffer after + * calling this. + * + * Return: 0 on success, negative error otherwise. + */ +int +xhci_sideband_remove_endpoint(struct xhci_sideband *sb, + struct usb_host_endpoint *host_ep) +{ + struct xhci_virt_ep *ep; + unsigned int ep_index; + + mutex_lock(&sb->mutex); + ep_index = xhci_get_endpoint_index(&host_ep->desc); + ep = sb->eps[ep_index]; + + if (!ep || !ep->sideband || ep->sideband != sb) { + mutex_unlock(&sb->mutex); + return -ENODEV; + } + + __xhci_sideband_remove_endpoint(sb, ep); + xhci_initialize_ring_info(ep->ring); + mutex_unlock(&sb->mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(xhci_sideband_remove_endpoint); + +int +xhci_sideband_stop_endpoint(struct xhci_sideband *sb, + struct usb_host_endpoint *host_ep) +{ + struct xhci_virt_ep *ep; + unsigned int ep_index; + + ep_index = xhci_get_endpoint_index(&host_ep->desc); + ep = sb->eps[ep_index]; + + if (!ep || !ep->sideband || ep->sideband != sb) + return -EINVAL; + + return xhci_stop_endpoint_sync(sb->xhci, ep, 0, GFP_KERNEL); +} +EXPORT_SYMBOL_GPL(xhci_sideband_stop_endpoint); + +/** + * xhci_sideband_get_endpoint_buffer - gets the endpoint transfer buffer address + * @sb: sideband instance for this usb device + * @host_ep: usb host endpoint + * + * Returns the address of the endpoint buffer where xHC controller reads queued + * transfer TRBs from. This is the starting address of the ringbuffer where the + * sideband client should write TRBs to. + * + * Caller needs to free the returned sg_table + * + * Return: struct sg_table * if successful. NULL otherwise. + */ +struct sg_table * +xhci_sideband_get_endpoint_buffer(struct xhci_sideband *sb, + struct usb_host_endpoint *host_ep) +{ + struct xhci_virt_ep *ep; + unsigned int ep_index; + + ep_index = xhci_get_endpoint_index(&host_ep->desc); + ep = sb->eps[ep_index]; + + if (!ep || !ep->ring || !ep->sideband || ep->sideband != sb) + return NULL; + + return xhci_ring_to_sgtable(sb, ep->ring); +} +EXPORT_SYMBOL_GPL(xhci_sideband_get_endpoint_buffer); + +/** + * xhci_sideband_get_event_buffer - return the event buffer for this device + * @sb: sideband instance for this usb device + * + * If a secondary xhci interupter is set up for this usb device then this + * function returns the address of the event buffer where xHC writes + * the transfer completion events. + * + * Caller needs to free the returned sg_table + * + * Return: struct sg_table * if successful. NULL otherwise. + */ +struct sg_table * +xhci_sideband_get_event_buffer(struct xhci_sideband *sb) +{ + if (!sb || !sb->ir) + return NULL; + + return xhci_ring_to_sgtable(sb, sb->ir->event_ring); +} +EXPORT_SYMBOL_GPL(xhci_sideband_get_event_buffer); + +/** + * xhci_sideband_create_interrupter - creates a new interrupter for this sideband + * @sb: sideband instance for this usb device + * @num_seg: number of event ring segments to allocate + * @ip_autoclear: IP autoclearing support such as MSI implemented + * + * Sets up a xhci interrupter that can be used for this sideband accessed usb + * device. Transfer events for this device can be routed to this interrupters + * event ring by setting the 'Interrupter Target' field correctly when queueing + * the transfer TRBs. + * Once this interrupter is created the interrupter target ID can be obtained + * by calling xhci_sideband_interrupter_id() + * + * Returns 0 on success, negative error otherwise + */ +int +xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg, + bool ip_autoclear, u32 imod_interval) +{ + int ret = 0; + + if (!sb || !sb->xhci) + return -ENODEV; + + mutex_lock(&sb->mutex); + if (sb->ir) { + ret = -EBUSY; + goto out; + } + + sb->ir = xhci_create_secondary_interrupter(xhci_to_hcd(sb->xhci), + num_seg, imod_interval); + if (!sb->ir) { + ret = -ENOMEM; + goto out; + } + + sb->ir->ip_autoclear = ip_autoclear; + +out: + mutex_unlock(&sb->mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(xhci_sideband_create_interrupter); + +/** + * xhci_sideband_remove_interrupter - remove the interrupter from a sideband + * @sb: sideband instance for this usb device + * + * Removes a registered interrupt for a sideband. This would allow for other + * sideband users to utilize this interrupter. + */ +void +xhci_sideband_remove_interrupter(struct xhci_sideband *sb) +{ + if (!sb || !sb->ir) + return; + + mutex_lock(&sb->mutex); + xhci_remove_secondary_interrupter(xhci_to_hcd(sb->xhci), sb->ir); + + sb->ir = NULL; + mutex_unlock(&sb->mutex); +} +EXPORT_SYMBOL_GPL(xhci_sideband_remove_interrupter); + +/** + * xhci_sideband_interrupter_id - return the interrupter target id + * @sb: sideband instance for this usb device + * + * If a secondary xhci interrupter is set up for this usb device then this + * function returns the ID used by the interrupter. The sideband client + * needs to write this ID to the 'Interrupter Target' field of the transfer TRBs + * it queues on the endpoints transfer ring to ensure transfer completion event + * are written by xHC to the correct interrupter event ring. + * + * Returns interrupter id on success, negative error othgerwise + */ +int +xhci_sideband_interrupter_id(struct xhci_sideband *sb) +{ + if (!sb || !sb->ir) + return -ENODEV; + + return sb->ir->intr_num; +} +EXPORT_SYMBOL_GPL(xhci_sideband_interrupter_id); + +/** + * xhci_sideband_register - register a sideband for a usb device + * @intf: usb interface associated with the sideband device + * + * Allows for clients to utilize XHCI interrupters and fetch transfer and event + * ring parameters for executing data transfers. + * + * Return: pointer to a new xhci_sideband instance if successful. NULL otherwise. + */ +struct xhci_sideband * +xhci_sideband_register(struct usb_interface *intf, enum xhci_sideband_type type) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_virt_device *vdev; + struct xhci_sideband *sb; + + /* + * Make sure the usb device is connected to a xhci controller. Fail + * registration if the type is anything other than XHCI_SIDEBAND_VENDOR, + * as this is the only type that is currently supported by xhci-sideband. + */ + if (!udev->slot_id || type != XHCI_SIDEBAND_VENDOR) + return NULL; + + sb = kzalloc_node(sizeof(*sb), GFP_KERNEL, dev_to_node(hcd->self.sysdev)); + if (!sb) + return NULL; + + mutex_init(&sb->mutex); + + /* check this device isn't already controlled via sideband */ + spin_lock_irq(&xhci->lock); + + vdev = xhci->devs[udev->slot_id]; + + if (!vdev || vdev->sideband) { + xhci_warn(xhci, "XHCI sideband for slot %d already in use\n", + udev->slot_id); + spin_unlock_irq(&xhci->lock); + kfree(sb); + return NULL; + } + + sb->xhci = xhci; + sb->vdev = vdev; + sb->intf = intf; + sb->type = type; + vdev->sideband = sb; + + spin_unlock_irq(&xhci->lock); + + return sb; +} +EXPORT_SYMBOL_GPL(xhci_sideband_register); + +/** + * xhci_sideband_unregister - unregister sideband access to a usb device + * @sb: sideband instance to be unregistered + * + * Unregisters sideband access to a usb device and frees the sideband + * instance. + * After this the endpoint and interrupter event buffers should no longer + * be accessed via sideband. The xhci driver can now take over handling + * the buffers. + */ +void +xhci_sideband_unregister(struct xhci_sideband *sb) +{ + struct xhci_hcd *xhci; + int i; + + if (!sb) + return; + + xhci = sb->xhci; + + mutex_lock(&sb->mutex); + for (i = 0; i < EP_CTX_PER_DEV; i++) + if (sb->eps[i]) + __xhci_sideband_remove_endpoint(sb, sb->eps[i]); + mutex_unlock(&sb->mutex); + + xhci_sideband_remove_interrupter(sb); + + spin_lock_irq(&xhci->lock); + sb->xhci = NULL; + sb->vdev->sideband = NULL; + spin_unlock_irq(&xhci->lock); + + kfree(sb); +} +EXPORT_SYMBOL_GPL(xhci_sideband_unregister); +MODULE_DESCRIPTION("xHCI sideband driver for secondary interrupter management"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 37860f1e3aba..39db228f0b84 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -701,6 +701,8 @@ struct xhci_virt_ep { int next_frame_id; /* Use new Isoch TRB layout needed for extended TBC support */ bool use_extended_tbc; + /* set if this endpoint is controlled via sideband access*/ + struct xhci_sideband *sideband; }; enum xhci_overhead_type { @@ -763,6 +765,8 @@ struct xhci_virt_device { u16 current_mel; /* Used for the debugfs interfaces. */ void *debugfs_private; + /* set if this endpoint is controlled via sideband access*/ + struct xhci_sideband *sideband; }; /* diff --git a/include/linux/usb/xhci-sideband.h b/include/linux/usb/xhci-sideband.h new file mode 100644 index 000000000000..4b382af892fa --- /dev/null +++ b/include/linux/usb/xhci-sideband.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * xHCI host controller sideband support + * + * Copyright (c) 2023-2025, Intel Corporation. + * + * Author: Mathias Nyman + */ +#ifndef __LINUX_XHCI_SIDEBAND_H +#define __LINUX_XHCI_SIDEBAND_H + +#include +#include + +#define EP_CTX_PER_DEV 31 /* FIXME defined twice, from xhci.h */ + +struct xhci_sideband; + +enum xhci_sideband_type { + XHCI_SIDEBAND_AUDIO, + XHCI_SIDEBAND_VENDOR, +}; + +/** + * struct xhci_sideband - representation of a sideband accessed usb device. + * @xhci: The xhci host controller the usb device is connected to + * @vdev: the usb device accessed via sideband + * @eps: array of endpoints controlled via sideband + * @ir: event handling and buffer for sideband accessed device + * @type: xHCI sideband type + * @mutex: mutex for sideband operations + * @intf: USB sideband client interface + * + * FIXME usb device accessed via sideband Keeping track of sideband accessed usb devices. + */ +struct xhci_sideband { + struct xhci_hcd *xhci; + struct xhci_virt_device *vdev; + struct xhci_virt_ep *eps[EP_CTX_PER_DEV]; + struct xhci_interrupter *ir; + enum xhci_sideband_type type; + + /* Synchronizing xHCI sideband operations with client drivers operations */ + struct mutex mutex; + + struct usb_interface *intf; +}; + +struct xhci_sideband * +xhci_sideband_register(struct usb_interface *intf, enum xhci_sideband_type type); +void +xhci_sideband_unregister(struct xhci_sideband *sb); +int +xhci_sideband_add_endpoint(struct xhci_sideband *sb, + struct usb_host_endpoint *host_ep); +int +xhci_sideband_remove_endpoint(struct xhci_sideband *sb, + struct usb_host_endpoint *host_ep); +int +xhci_sideband_stop_endpoint(struct xhci_sideband *sb, + struct usb_host_endpoint *host_ep); +struct sg_table * +xhci_sideband_get_endpoint_buffer(struct xhci_sideband *sb, + struct usb_host_endpoint *host_ep); +struct sg_table * +xhci_sideband_get_event_buffer(struct xhci_sideband *sb); +int +xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg, + bool ip_autoclear, u32 imod_interval); +void +xhci_sideband_remove_interrupter(struct xhci_sideband *sb); +int +xhci_sideband_interrupter_id(struct xhci_sideband *sb); +#endif /* __LINUX_XHCI_SIDEBAND_H */ -- cgit v1.2.3-59-g8ed1b From 5beb4a53a1dd1868aa0ba0d48b1bbc557126caaa Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:35 -0700 Subject: usb: host: xhci-mem: Cleanup pending secondary event ring events As part of xHCI bus suspend, the xHCI is halted. However, if there are pending events in the secondary event ring, it is observed that the xHCI controller stops responding to further commands upon host or device initiated bus resume. Iterate through all pending events and update the dequeue pointer to the beginning of the event ring. Tested-by: Puma Hsu Tested-by: Daehwan Jung Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-3-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 7 ++++++- drivers/usb/host/xhci-ring.c | 47 +++++++++++++++++++++++++++++++++++++------- drivers/usb/host/xhci.c | 2 +- drivers/usb/host/xhci.h | 7 +++++++ 4 files changed, 54 insertions(+), 9 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index d698095fc88d..daea0f76e844 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1805,7 +1805,7 @@ xhci_remove_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir) tmp &= ERST_SIZE_MASK; writel(tmp, &ir->ir_set->erst_size); - xhci_write_64(xhci, ERST_EHB, &ir->ir_set->erst_dequeue); + xhci_update_erst_dequeue(xhci, ir, true); } } @@ -1848,6 +1848,11 @@ void xhci_remove_secondary_interrupter(struct usb_hcd *hcd, struct xhci_interrup return; } + /* + * Cleanup secondary interrupter to ensure there are no pending events. + * This also updates event ring dequeue pointer back to the start. + */ + xhci_skip_sec_intr_events(xhci, ir->event_ring, ir); intr_num = ir->intr_num; xhci_remove_interrupter(xhci, ir); diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 5d64c297721c..bfef765dd78c 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -3054,9 +3054,9 @@ static int xhci_handle_event_trb(struct xhci_hcd *xhci, struct xhci_interrupter * - When all events have finished * - To avoid "Event Ring Full Error" condition */ -static void xhci_update_erst_dequeue(struct xhci_hcd *xhci, - struct xhci_interrupter *ir, - bool clear_ehb) +void xhci_update_erst_dequeue(struct xhci_hcd *xhci, + struct xhci_interrupter *ir, + bool clear_ehb) { u64 temp_64; dma_addr_t deq; @@ -3099,10 +3099,11 @@ static void xhci_clear_interrupt_pending(struct xhci_interrupter *ir) * Handle all OS-owned events on an interrupter event ring. It may drop * and reaquire xhci->lock between event processing. */ -static int xhci_handle_events(struct xhci_hcd *xhci, struct xhci_interrupter *ir) +static int xhci_handle_events(struct xhci_hcd *xhci, struct xhci_interrupter *ir, + bool skip_events) { int event_loop = 0; - int err; + int err = 0; u64 temp; xhci_clear_interrupt_pending(ir); @@ -3125,7 +3126,8 @@ static int xhci_handle_events(struct xhci_hcd *xhci, struct xhci_interrupter *ir /* Process all OS owned event TRBs on this event ring */ while (unhandled_event_trb(ir->event_ring)) { - err = xhci_handle_event_trb(xhci, ir, ir->event_ring->dequeue); + if (!skip_events) + err = xhci_handle_event_trb(xhci, ir, ir->event_ring->dequeue); /* * If half a segment of events have been handled in one go then @@ -3152,6 +3154,37 @@ static int xhci_handle_events(struct xhci_hcd *xhci, struct xhci_interrupter *ir return 0; } +/* + * Move the event ring dequeue pointer to skip events kept in the secondary + * event ring. This is used to ensure that pending events in the ring are + * acknowledged, so the xHCI HCD can properly enter suspend/resume. The + * secondary ring is typically maintained by an external component. + */ +void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, + struct xhci_ring *ring, struct xhci_interrupter *ir) +{ + union xhci_trb *current_trb; + u64 erdp_reg; + dma_addr_t deq; + + /* disable irq, ack pending interrupt and ack all pending events */ + xhci_disable_interrupter(ir); + + /* last acked event trb is in erdp reg */ + erdp_reg = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); + deq = (dma_addr_t)(erdp_reg & ERST_PTR_MASK); + if (!deq) { + xhci_err(xhci, "event ring handling not required\n"); + return; + } + + current_trb = ir->event_ring->dequeue; + /* read cycle state of the last acked trb to find out CCS */ + ring->cycle_state = le32_to_cpu(current_trb->event_cmd.flags) & TRB_CYCLE; + + xhci_handle_events(xhci, ir, true); +} + /* * xHCI spec says we can get an interrupt, and if the HC has an error condition, * we might get bad data out of the event ring. Section 4.10.2.7 has a list of @@ -3196,7 +3229,7 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd) writel(status, &xhci->op_regs->status); /* This is the handler of the primary interrupter */ - xhci_handle_events(xhci, xhci->interrupters[0]); + xhci_handle_events(xhci, xhci->interrupters[0], false); out: spin_unlock(&xhci->lock); diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 0452b8d65832..b361312e05ed 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -335,7 +335,7 @@ static int xhci_enable_interrupter(struct xhci_interrupter *ir) return 0; } -static int xhci_disable_interrupter(struct xhci_interrupter *ir) +int xhci_disable_interrupter(struct xhci_interrupter *ir) { u32 iman; diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 39db228f0b84..3fa8669e3b2d 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1856,6 +1856,9 @@ xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs, u32 imod_interval); void xhci_remove_secondary_interrupter(struct usb_hcd *hcd, struct xhci_interrupter *ir); +void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, + struct xhci_ring *ring, + struct xhci_interrupter *ir); /* xHCI host controller glue */ typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *); @@ -1895,6 +1898,7 @@ int xhci_alloc_tt_info(struct xhci_hcd *xhci, struct usb_tt *tt, gfp_t mem_flags); int xhci_set_interrupter_moderation(struct xhci_interrupter *ir, u32 imod_interval); +int xhci_disable_interrupter(struct xhci_interrupter *ir); /* xHCI ring, segment, TRB, and TD functions */ dma_addr_t xhci_trb_virt_to_dma(struct xhci_segment *seg, union xhci_trb *trb); @@ -1939,6 +1943,9 @@ unsigned int count_trbs(u64 addr, u64 len); int xhci_stop_endpoint_sync(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, int suspend, gfp_t gfp_flags); void xhci_process_cancelled_tds(struct xhci_virt_ep *ep); +void xhci_update_erst_dequeue(struct xhci_hcd *xhci, + struct xhci_interrupter *ir, + bool clear_ehb); /* xHCI roothub code */ void xhci_set_link_state(struct xhci_hcd *xhci, struct xhci_port *port, -- cgit v1.2.3-59-g8ed1b From fce57295497df70711d50ed01bf6d914de0ca647 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:36 -0700 Subject: usb: host: xhci-mem: Allow for interrupter clients to choose specific index Some clients may operate only on a specific XHCI interrupter instance. Allow for the associated class driver to request for the interrupter that it requires. Tested-by: Puma Hsu Tested-by: Daehwan Jung Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-4-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 24 ++++++++++++++---------- drivers/usb/host/xhci-sideband.c | 5 +++-- drivers/usb/host/xhci.h | 2 +- include/linux/usb/xhci-sideband.h | 2 +- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index daea0f76e844..ed36df46b140 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2331,14 +2331,15 @@ xhci_add_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir, struct xhci_interrupter * xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs, - u32 imod_interval) + u32 imod_interval, unsigned int intr_num) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); struct xhci_interrupter *ir; unsigned int i; int err = -ENOSPC; - if (!xhci->interrupters || xhci->max_interrupters <= 1) + if (!xhci->interrupters || xhci->max_interrupters <= 1 || + intr_num >= xhci->max_interrupters) return NULL; ir = xhci_alloc_interrupter(xhci, segs, GFP_KERNEL); @@ -2346,15 +2347,18 @@ xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs, return NULL; spin_lock_irq(&xhci->lock); - - /* Find available secondary interrupter, interrupter 0 is reserved for primary */ - for (i = 1; i < xhci->max_interrupters; i++) { - if (xhci->interrupters[i] == NULL) { - err = xhci_add_interrupter(xhci, ir, i); - break; + if (!intr_num) { + /* Find available secondary interrupter, interrupter 0 is reserved for primary */ + for (i = 1; i < xhci->max_interrupters; i++) { + if (!xhci->interrupters[i]) { + err = xhci_add_interrupter(xhci, ir, i); + break; + } } + } else { + if (!xhci->interrupters[intr_num]) + err = xhci_add_interrupter(xhci, ir, intr_num); } - spin_unlock_irq(&xhci->lock); if (err) { @@ -2370,7 +2374,7 @@ xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs, i, imod_interval); xhci_dbg(xhci, "Add secondary interrupter %d, max interrupters %d\n", - i, xhci->max_interrupters); + ir->intr_num, xhci->max_interrupters); return ir; } diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c index 19c58ae60414..742bbc6c2d9b 100644 --- a/drivers/usb/host/xhci-sideband.c +++ b/drivers/usb/host/xhci-sideband.c @@ -259,7 +259,7 @@ EXPORT_SYMBOL_GPL(xhci_sideband_get_event_buffer); */ int xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg, - bool ip_autoclear, u32 imod_interval) + bool ip_autoclear, u32 imod_interval, int intr_num) { int ret = 0; @@ -273,7 +273,8 @@ xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg, } sb->ir = xhci_create_secondary_interrupter(xhci_to_hcd(sb->xhci), - num_seg, imod_interval); + num_seg, imod_interval, + intr_num); if (!sb->ir) { ret = -ENOMEM; goto out; diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 3fa8669e3b2d..7eaabe4f6c87 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1853,7 +1853,7 @@ void xhci_free_container_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx); struct xhci_interrupter * xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs, - u32 imod_interval); + u32 imod_interval, unsigned int intr_num); void xhci_remove_secondary_interrupter(struct usb_hcd *hcd, struct xhci_interrupter *ir); void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, diff --git a/include/linux/usb/xhci-sideband.h b/include/linux/usb/xhci-sideband.h index 4b382af892fa..f8722afb8a2d 100644 --- a/include/linux/usb/xhci-sideband.h +++ b/include/linux/usb/xhci-sideband.h @@ -66,7 +66,7 @@ struct sg_table * xhci_sideband_get_event_buffer(struct xhci_sideband *sb); int xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg, - bool ip_autoclear, u32 imod_interval); + bool ip_autoclear, u32 imod_interval, int intr_num); void xhci_sideband_remove_interrupter(struct xhci_sideband *sb); int -- cgit v1.2.3-59-g8ed1b From 000ab7dab5b8ed4f788ea7714e2df82275db5f7f Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:37 -0700 Subject: usb: host: xhci-plat: Set XHCI max interrupters if property is present Some platforms may want to limit the number of XHCI interrupters allocated. This is passed to xhci-plat as a device property. Ensure that this is read and the max_interrupters field is set. Tested-by: Puma Hsu Tested-by: Daehwan Jung Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-5-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-plat.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 3155e3a842da..6dab142e7278 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -267,6 +267,8 @@ int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, const s device_property_read_u32(tmpdev, "imod-interval-ns", &xhci->imod_interval); + device_property_read_u16(tmpdev, "num-hc-interrupters", + &xhci->max_interrupters); } /* -- cgit v1.2.3-59-g8ed1b From b85a2ebda1034881d09e6127726dc78950669474 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:38 -0700 Subject: usb: host: xhci: Notify xHCI sideband on transfer ring free In the case of handling a USB bus reset, the xhci_discover_or_reset_device can run without first notifying the xHCI sideband client driver to stop or prevent the use of the transfer ring. It was seen that when a bus reset situation happened, the USB offload driver was attempting to fetch the xHCI transfer ring information, which was already freed. Tested-by: Puma Hsu Tested-by: Daehwan Jung Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-6-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-sideband.c | 29 ++++++++++++++++++++++++++++- drivers/usb/host/xhci.c | 3 +++ include/linux/usb/xhci-sideband.h | 30 +++++++++++++++++++++++++++++- 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c index 742bbc6c2d9b..d49f9886dd84 100644 --- a/drivers/usb/host/xhci-sideband.c +++ b/drivers/usb/host/xhci-sideband.c @@ -88,6 +88,30 @@ __xhci_sideband_remove_endpoint(struct xhci_sideband *sb, struct xhci_virt_ep *e /* sideband api functions */ +/** + * xhci_sideband_notify_ep_ring_free - notify client of xfer ring free + * @sb: sideband instance for this usb device + * @ep_index: usb endpoint index + * + * Notifies the xHCI sideband client driver of a xHCI transfer ring free + * routine. This will allow for the client to ensure that all transfers + * are completed. + * + * The callback should be synchronous, as the ring free happens after. + */ +void xhci_sideband_notify_ep_ring_free(struct xhci_sideband *sb, + unsigned int ep_index) +{ + struct xhci_sideband_event evt; + + evt.type = XHCI_SIDEBAND_XFER_RING_FREE; + evt.evt_data = &ep_index; + + if (sb->notify_client) + sb->notify_client(sb->intf, &evt); +} +EXPORT_SYMBOL_GPL(xhci_sideband_notify_ep_ring_free); + /** * xhci_sideband_add_endpoint - add endpoint to sideband access list * @sb: sideband instance for this usb device @@ -342,7 +366,9 @@ EXPORT_SYMBOL_GPL(xhci_sideband_interrupter_id); * Return: pointer to a new xhci_sideband instance if successful. NULL otherwise. */ struct xhci_sideband * -xhci_sideband_register(struct usb_interface *intf, enum xhci_sideband_type type) +xhci_sideband_register(struct usb_interface *intf, enum xhci_sideband_type type, + int (*notify_client)(struct usb_interface *intf, + struct xhci_sideband_event *evt)) { struct usb_device *udev = interface_to_usbdev(intf); struct usb_hcd *hcd = bus_to_hcd(udev->bus); @@ -381,6 +407,7 @@ xhci_sideband_register(struct usb_interface *intf, enum xhci_sideband_type type) sb->vdev = vdev; sb->intf = intf; sb->type = type; + sb->notify_client = notify_client; vdev->sideband = sb; spin_unlock_irq(&xhci->lock); diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index b361312e05ed..cfda35004754 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -20,6 +20,7 @@ #include #include #include +#include #include "xhci.h" #include "xhci-trace.h" @@ -3919,6 +3920,8 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd, } if (ep->ring) { + if (ep->sideband) + xhci_sideband_notify_ep_ring_free(ep->sideband, i); xhci_debugfs_remove_endpoint(xhci, virt_dev, i); xhci_free_endpoint_ring(xhci, virt_dev, i); } diff --git a/include/linux/usb/xhci-sideband.h b/include/linux/usb/xhci-sideband.h index f8722afb8a2d..45288c392f6e 100644 --- a/include/linux/usb/xhci-sideband.h +++ b/include/linux/usb/xhci-sideband.h @@ -21,6 +21,20 @@ enum xhci_sideband_type { XHCI_SIDEBAND_VENDOR, }; +enum xhci_sideband_notify_type { + XHCI_SIDEBAND_XFER_RING_FREE, +}; + +/** + * struct xhci_sideband_event - sideband event + * @type: notifier type + * @evt_data: event data + */ +struct xhci_sideband_event { + enum xhci_sideband_notify_type type; + void *evt_data; +}; + /** * struct xhci_sideband - representation of a sideband accessed usb device. * @xhci: The xhci host controller the usb device is connected to @@ -30,6 +44,7 @@ enum xhci_sideband_type { * @type: xHCI sideband type * @mutex: mutex for sideband operations * @intf: USB sideband client interface + * @notify_client: callback for xHCI sideband sequences * * FIXME usb device accessed via sideband Keeping track of sideband accessed usb devices. */ @@ -44,10 +59,14 @@ struct xhci_sideband { struct mutex mutex; struct usb_interface *intf; + int (*notify_client)(struct usb_interface *intf, + struct xhci_sideband_event *evt); }; struct xhci_sideband * -xhci_sideband_register(struct usb_interface *intf, enum xhci_sideband_type type); +xhci_sideband_register(struct usb_interface *intf, enum xhci_sideband_type type, + int (*notify_client)(struct usb_interface *intf, + struct xhci_sideband_event *evt)); void xhci_sideband_unregister(struct xhci_sideband *sb); int @@ -71,4 +90,13 @@ void xhci_sideband_remove_interrupter(struct xhci_sideband *sb); int xhci_sideband_interrupter_id(struct xhci_sideband *sb); + +#if IS_ENABLED(CONFIG_USB_XHCI_SIDEBAND) +void xhci_sideband_notify_ep_ring_free(struct xhci_sideband *sb, + unsigned int ep_index); +#else +static inline void xhci_sideband_notify_ep_ring_free(struct xhci_sideband *sb, + unsigned int ep_index) +{ } +#endif /* IS_ENABLED(CONFIG_USB_XHCI_SIDEBAND) */ #endif /* __LINUX_XHCI_SIDEBAND_H */ -- cgit v1.2.3-59-g8ed1b From 8da7644493b4cad0db294b9bd213c7b1d3523cff Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:39 -0700 Subject: usb: dwc3: Specify maximum number of XHCI interrupters Allow for the DWC3 host driver to pass along XHCI property that defines how many interrupters to allocate. This is in relation for the number of event rings that can be potentially used by other processors within the system. Acked-by: Thinh Nguyen Tested-by: Puma Hsu Tested-by: Daehwan Jung Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-7-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 12 ++++++++++++ drivers/usb/dwc3/core.h | 2 ++ drivers/usb/dwc3/host.c | 3 +++ 3 files changed, 17 insertions(+) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 66a08b527165..17ae5c13fe36 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1699,6 +1699,7 @@ static void dwc3_get_properties(struct dwc3 *dwc) u8 tx_thr_num_pkt_prd = 0; u8 tx_max_burst_prd = 0; u8 tx_fifo_resize_max_num; + u16 num_hc_interrupters; /* default to highest possible threshold */ lpm_nyet_threshold = 0xf; @@ -1719,6 +1720,9 @@ static void dwc3_get_properties(struct dwc3 *dwc) */ tx_fifo_resize_max_num = 6; + /* default to a single XHCI interrupter */ + num_hc_interrupters = 1; + dwc->maximum_speed = usb_get_maximum_speed(dev); dwc->max_ssp_rate = usb_get_maximum_ssp_rate(dev); dwc->dr_mode = usb_get_dr_mode(dev); @@ -1765,6 +1769,12 @@ static void dwc3_get_properties(struct dwc3 *dwc) &tx_thr_num_pkt_prd); device_property_read_u8(dev, "snps,tx-max-burst-prd", &tx_max_burst_prd); + device_property_read_u16(dev, "num-hc-interrupters", + &num_hc_interrupters); + /* DWC3 core allowed to have a max of 8 interrupters */ + if (num_hc_interrupters > 8) + num_hc_interrupters = 8; + dwc->do_fifo_resize = device_property_read_bool(dev, "tx-fifo-resize"); if (dwc->do_fifo_resize) @@ -1851,6 +1861,8 @@ static void dwc3_get_properties(struct dwc3 *dwc) dwc->tx_max_burst_prd = tx_max_burst_prd; dwc->tx_fifo_resize_max_num = tx_fifo_resize_max_num; + + dwc->num_hc_interrupters = num_hc_interrupters; } /* check whether the core supports IMOD */ diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index aaa39e663f60..fbe83914d9f9 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -1083,6 +1083,7 @@ struct dwc3_scratchpad_array { * @tx_max_burst_prd: max periodic ESS transmit burst size * @tx_fifo_resize_max_num: max number of fifos allocated during txfifo resize * @clear_stall_protocol: endpoint number that requires a delayed status phase + * @num_hc_interrupters: number of host controller interrupters * @hsphy_interface: "utmi" or "ulpi" * @connected: true when we're connected to a host, false otherwise * @softconnect: true when gadget connect is called, false when disconnect runs @@ -1330,6 +1331,7 @@ struct dwc3 { u8 tx_max_burst_prd; u8 tx_fifo_resize_max_num; u8 clear_stall_protocol; + u16 num_hc_interrupters; const char *hsphy_interface; diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c index b48e108fc8fe..1c513bf8002e 100644 --- a/drivers/usb/dwc3/host.c +++ b/drivers/usb/dwc3/host.c @@ -182,6 +182,9 @@ int dwc3_host_init(struct dwc3 *dwc) if (DWC3_VER_IS_WITHIN(DWC3, ANY, 300A)) props[prop_idx++] = PROPERTY_ENTRY_BOOL("quirk-broken-port-ped"); + props[prop_idx++] = PROPERTY_ENTRY_U16("num-hc-interrupters", + dwc->num_hc_interrupters); + if (prop_idx) { ret = device_create_managed_software_node(&xhci->dev, props, NULL); if (ret) { -- cgit v1.2.3-59-g8ed1b From 67890d579402804b1d32b3280d9860073542528e Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:40 -0700 Subject: ALSA: Add USB audio device jack type Add an USB jack type, in order to support notifying of a valid USB audio device. Since USB audio devices can have a slew of different configurations that reach beyond the basic headset and headphone use cases, classify these devices differently. Reviewed-by: Pierre-Louis Bossart Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-8-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- include/linux/mod_devicetable.h | 2 +- include/sound/jack.h | 4 +++- include/uapi/linux/input-event-codes.h | 3 ++- sound/core/jack.c | 6 ++++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index bd7e60c0b72f..dde836875deb 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -340,7 +340,7 @@ struct pcmcia_device_id { #define INPUT_DEVICE_ID_LED_MAX 0x0f #define INPUT_DEVICE_ID_SND_MAX 0x07 #define INPUT_DEVICE_ID_FF_MAX 0x7f -#define INPUT_DEVICE_ID_SW_MAX 0x10 +#define INPUT_DEVICE_ID_SW_MAX 0x11 #define INPUT_DEVICE_ID_PROP_MAX 0x1f #define INPUT_DEVICE_ID_MATCH_BUS 1 diff --git a/include/sound/jack.h b/include/sound/jack.h index 1ed90e2109e9..bd3f62281c97 100644 --- a/include/sound/jack.h +++ b/include/sound/jack.h @@ -22,6 +22,7 @@ struct input_dev; * @SND_JACK_VIDEOOUT: Video out * @SND_JACK_AVOUT: AV (Audio Video) out * @SND_JACK_LINEIN: Line in + * @SND_JACK_USB: USB audio device * @SND_JACK_BTN_0: Button 0 * @SND_JACK_BTN_1: Button 1 * @SND_JACK_BTN_2: Button 2 @@ -43,6 +44,7 @@ enum snd_jack_types { SND_JACK_VIDEOOUT = 0x0010, SND_JACK_AVOUT = SND_JACK_LINEOUT | SND_JACK_VIDEOOUT, SND_JACK_LINEIN = 0x0020, + SND_JACK_USB = 0x0040, /* Kept separate from switches to facilitate implementation */ SND_JACK_BTN_0 = 0x4000, @@ -54,7 +56,7 @@ enum snd_jack_types { }; /* Keep in sync with definitions above */ -#define SND_JACK_SWITCH_TYPES 6 +#define SND_JACK_SWITCH_TYPES 7 struct snd_jack { struct list_head kctl_list; diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h index 5a199f3d4a26..3b2524e4b667 100644 --- a/include/uapi/linux/input-event-codes.h +++ b/include/uapi/linux/input-event-codes.h @@ -925,7 +925,8 @@ #define SW_MUTE_DEVICE 0x0e /* set = device disabled */ #define SW_PEN_INSERTED 0x0f /* set = pen inserted */ #define SW_MACHINE_COVER 0x10 /* set = cover closed */ -#define SW_MAX 0x10 +#define SW_USB_INSERT 0x11 /* set = USB audio device connected */ +#define SW_MAX 0x11 #define SW_CNT (SW_MAX+1) /* diff --git a/sound/core/jack.c b/sound/core/jack.c index e4bcecdf89b7..de7c603e92b7 100644 --- a/sound/core/jack.c +++ b/sound/core/jack.c @@ -34,6 +34,7 @@ static const int jack_switch_types[SND_JACK_SWITCH_TYPES] = { SW_JACK_PHYSICAL_INSERT, SW_VIDEOOUT_INSERT, SW_LINEIN_INSERT, + SW_USB_INSERT, }; #endif /* CONFIG_SND_JACK_INPUT_DEV */ @@ -241,8 +242,9 @@ static ssize_t jack_kctl_id_read(struct file *file, static const char * const jack_events_name[] = { "HEADPHONE(0x0001)", "MICROPHONE(0x0002)", "LINEOUT(0x0004)", "MECHANICAL(0x0008)", "VIDEOOUT(0x0010)", "LINEIN(0x0020)", - "", "", "", "BTN_5(0x0200)", "BTN_4(0x0400)", "BTN_3(0x0800)", - "BTN_2(0x1000)", "BTN_1(0x2000)", "BTN_0(0x4000)", "", + "USB(0x0040)", "", "", "BTN_5(0x0200)", "BTN_4(0x0400)", + "BTN_3(0x0800)", "BTN_2(0x1000)", "BTN_1(0x2000)", "BTN_0(0x4000)", + "", }; /* the recommended buffer size is 256 */ -- cgit v1.2.3-59-g8ed1b From 5a49a6ba2214a438edcb05f9a1f5259a70a61e47 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:41 -0700 Subject: ALSA: usb-audio: Export USB SND APIs for modules Some vendor modules will utilize useful parsing and endpoint management APIs to start audio playback/capture. Reviewed-by: Pierre-Louis Bossart Tested-by: Puma Hsu Tested-by: Daehwan Jung Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-9-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- sound/usb/card.c | 4 +++ sound/usb/endpoint.c | 1 + sound/usb/helper.c | 1 + sound/usb/pcm.c | 75 +++++++++++++++++++++++++++++++++++++--------------- sound/usb/pcm.h | 11 ++++++++ 5 files changed, 71 insertions(+), 21 deletions(-) diff --git a/sound/usb/card.c b/sound/usb/card.c index 9c411b82a218..a0087bde684c 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -1030,6 +1030,7 @@ int snd_usb_lock_shutdown(struct snd_usb_audio *chip) wake_up(&chip->shutdown_wait); return err; } +EXPORT_SYMBOL_GPL(snd_usb_lock_shutdown); /* autosuspend and unlock the shutdown */ void snd_usb_unlock_shutdown(struct snd_usb_audio *chip) @@ -1038,6 +1039,7 @@ void snd_usb_unlock_shutdown(struct snd_usb_audio *chip) if (atomic_dec_and_test(&chip->usage_count)) wake_up(&chip->shutdown_wait); } +EXPORT_SYMBOL_GPL(snd_usb_unlock_shutdown); int snd_usb_autoresume(struct snd_usb_audio *chip) { @@ -1060,6 +1062,7 @@ int snd_usb_autoresume(struct snd_usb_audio *chip) } return 0; } +EXPORT_SYMBOL_GPL(snd_usb_autoresume); void snd_usb_autosuspend(struct snd_usb_audio *chip) { @@ -1073,6 +1076,7 @@ void snd_usb_autosuspend(struct snd_usb_audio *chip) for (i = 0; i < chip->num_interfaces; i++) usb_autopm_put_interface(chip->intf[i]); } +EXPORT_SYMBOL_GPL(snd_usb_autosuspend); static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message) { diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index a29f28eb7d0c..1fed039b10ed 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -1524,6 +1524,7 @@ unlock: mutex_unlock(&chip->mutex); return err; } +EXPORT_SYMBOL_GPL(snd_usb_endpoint_prepare); /* get the current rate set to the given clock by any endpoint */ int snd_usb_endpoint_get_clock_rate(struct snd_usb_audio *chip, int clock) diff --git a/sound/usb/helper.c b/sound/usb/helper.c index 72b671fb2c84..497d2b27fb59 100644 --- a/sound/usb/helper.c +++ b/sound/usb/helper.c @@ -62,6 +62,7 @@ void *snd_usb_find_csint_desc(void *buffer, int buflen, void *after, u8 dsubtype } return NULL; } +EXPORT_SYMBOL_GPL(snd_usb_find_csint_desc); /* * Wrapper for usb_control_msg(). diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index 08bf535ed163..18467da6fd9e 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -148,6 +148,16 @@ find_format(struct list_head *fmt_list_head, snd_pcm_format_t format, return found; } +const struct audioformat * +snd_usb_find_format(struct list_head *fmt_list_head, snd_pcm_format_t format, + unsigned int rate, unsigned int channels, bool strict_match, + struct snd_usb_substream *subs) +{ + return find_format(fmt_list_head, format, rate, channels, strict_match, + subs); +} +EXPORT_SYMBOL_GPL(snd_usb_find_format); + static const struct audioformat * find_substream_format(struct snd_usb_substream *subs, const struct snd_pcm_hw_params *params) @@ -157,6 +167,14 @@ find_substream_format(struct snd_usb_substream *subs, true, subs); } +const struct audioformat * +snd_usb_find_substream_format(struct snd_usb_substream *subs, + const struct snd_pcm_hw_params *params) +{ + return find_substream_format(subs, params); +} +EXPORT_SYMBOL_GPL(snd_usb_find_substream_format); + bool snd_usb_pcm_has_fixed_rate(struct snd_usb_substream *subs) { const struct audioformat *fp; @@ -461,20 +479,9 @@ static void close_endpoints(struct snd_usb_audio *chip, } } -/* - * hw_params callback - * - * allocate a buffer and set the given audio format. - * - * so far we use a physically linear buffer although packetize transfer - * doesn't need a continuous area. - * if sg buffer is supported on the later version of alsa, we'll follow - * that. - */ -static int snd_usb_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *hw_params) +int snd_usb_hw_params(struct snd_usb_substream *subs, + struct snd_pcm_hw_params *hw_params) { - struct snd_usb_substream *subs = substream->runtime->private_data; struct snd_usb_audio *chip = subs->stream->chip; const struct audioformat *fmt; const struct audioformat *sync_fmt; @@ -499,7 +506,7 @@ static int snd_usb_hw_params(struct snd_pcm_substream *substream, if (fmt->implicit_fb) { sync_fmt = snd_usb_find_implicit_fb_sync_format(chip, fmt, hw_params, - !substream->stream, + !subs->direction, &sync_fixed_rate); if (!sync_fmt) { usb_audio_dbg(chip, @@ -579,15 +586,28 @@ static int snd_usb_hw_params(struct snd_pcm_substream *substream, return ret; } +EXPORT_SYMBOL_GPL(snd_usb_hw_params); /* - * hw_free callback + * hw_params callback * - * reset the audio format and release the buffer + * allocate a buffer and set the given audio format. + * + * so far we use a physically linear buffer although packetize transfer + * doesn't need a continuous area. + * if sg buffer is supported on the later version of alsa, we'll follow + * that. */ -static int snd_usb_hw_free(struct snd_pcm_substream *substream) +static int snd_usb_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) { struct snd_usb_substream *subs = substream->runtime->private_data; + + return snd_usb_hw_params(subs, hw_params); +} + +int snd_usb_hw_free(struct snd_usb_substream *subs) +{ struct snd_usb_audio *chip = subs->stream->chip; snd_media_stop_pipeline(subs); @@ -603,6 +623,19 @@ static int snd_usb_hw_free(struct snd_pcm_substream *substream) return 0; } +EXPORT_SYMBOL_GPL(snd_usb_hw_free); + +/* + * hw_free callback + * + * reset the audio format and release the buffer + */ +static int snd_usb_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_usb_substream *subs = substream->runtime->private_data; + + return snd_usb_hw_free(subs); +} /* free-wheeling mode? (e.g. dmix) */ static int in_free_wheeling_mode(struct snd_pcm_runtime *runtime) @@ -1746,8 +1779,8 @@ static int snd_usb_substream_capture_trigger(struct snd_pcm_substream *substream static const struct snd_pcm_ops snd_usb_playback_ops = { .open = snd_usb_pcm_open, .close = snd_usb_pcm_close, - .hw_params = snd_usb_hw_params, - .hw_free = snd_usb_hw_free, + .hw_params = snd_usb_pcm_hw_params, + .hw_free = snd_usb_pcm_hw_free, .prepare = snd_usb_pcm_prepare, .trigger = snd_usb_substream_playback_trigger, .sync_stop = snd_usb_pcm_sync_stop, @@ -1758,8 +1791,8 @@ static const struct snd_pcm_ops snd_usb_playback_ops = { static const struct snd_pcm_ops snd_usb_capture_ops = { .open = snd_usb_pcm_open, .close = snd_usb_pcm_close, - .hw_params = snd_usb_hw_params, - .hw_free = snd_usb_hw_free, + .hw_params = snd_usb_pcm_hw_params, + .hw_free = snd_usb_pcm_hw_free, .prepare = snd_usb_pcm_prepare, .trigger = snd_usb_substream_capture_trigger, .sync_stop = snd_usb_pcm_sync_stop, diff --git a/sound/usb/pcm.h b/sound/usb/pcm.h index 388fe2ba346d..c096021adb2b 100644 --- a/sound/usb/pcm.h +++ b/sound/usb/pcm.h @@ -15,4 +15,15 @@ void snd_usb_preallocate_buffer(struct snd_usb_substream *subs); int snd_usb_audioformat_set_sync_ep(struct snd_usb_audio *chip, struct audioformat *fmt); +const struct audioformat * +snd_usb_find_format(struct list_head *fmt_list_head, snd_pcm_format_t format, + unsigned int rate, unsigned int channels, bool strict_match, + struct snd_usb_substream *subs); +const struct audioformat * +snd_usb_find_substream_format(struct snd_usb_substream *subs, + const struct snd_pcm_hw_params *params); + +int snd_usb_hw_params(struct snd_usb_substream *subs, + struct snd_pcm_hw_params *hw_params); +int snd_usb_hw_free(struct snd_usb_substream *subs); #endif /* __USBAUDIO_PCM_H */ -- cgit v1.2.3-59-g8ed1b From 2bde439265e24ee2086ba6573458c6bdc43d6364 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:42 -0700 Subject: ALSA: usb-audio: Check for support for requested audio format Allow for checks on a specific USB audio device to see if a requested PCM format is supported. This is needed for support when playback is initiated by the ASoC USB backend path. Reviewed-by: Pierre-Louis Bossart Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-10-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- sound/usb/card.c | 32 ++++++++++++++++++++++++++++++++ sound/usb/card.h | 3 +++ 2 files changed, 35 insertions(+) diff --git a/sound/usb/card.c b/sound/usb/card.c index a0087bde684c..6c5b0e02e57b 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -119,6 +119,38 @@ static DEFINE_MUTEX(register_mutex); static struct snd_usb_audio *usb_chip[SNDRV_CARDS]; static struct usb_driver usb_audio_driver; +/* + * Checks to see if requested audio profile, i.e sample rate, # of + * channels, etc... is supported by the substream associated to the + * USB audio device. + */ +struct snd_usb_stream * +snd_usb_find_suppported_substream(int card_idx, struct snd_pcm_hw_params *params, + int direction) +{ + struct snd_usb_audio *chip; + struct snd_usb_substream *subs; + struct snd_usb_stream *as; + + /* + * Register mutex is held when populating and clearing usb_chip + * array. + */ + guard(mutex)(®ister_mutex); + chip = usb_chip[card_idx]; + + if (chip && enable[card_idx]) { + list_for_each_entry(as, &chip->pcm_list, list) { + subs = &as->substream[direction]; + if (snd_usb_find_substream_format(subs, params)) + return as; + } + } + + return NULL; +} +EXPORT_SYMBOL_GPL(snd_usb_find_suppported_substream); + /* * disconnect streams * called from usb_audio_disconnect() diff --git a/sound/usb/card.h b/sound/usb/card.h index 6ec95b2edf86..4f4f3f39b7fa 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -207,4 +207,7 @@ struct snd_usb_stream { struct list_head list; }; +struct snd_usb_stream * +snd_usb_find_suppported_substream(int card_idx, struct snd_pcm_hw_params *params, + int direction); #endif /* __USBAUDIO_CARD_H */ -- cgit v1.2.3-59-g8ed1b From d893d5eaabfa948e983cc447bacf80a8306358da Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:43 -0700 Subject: ALSA: usb-audio: Save UAC sample size information Within the UAC descriptor, there is information describing the size of a sample (bSubframeSize/bSubslotSize) and the number of relevant bits (bBitResolution). Currently, fmt_bits carries only the bit resolution, however, some offloading entities may also require the overall size of the sample. Save this information in a separate parameter, as depending on the UAC format type, the sample size can not easily be decoded from other existing parameters. Reviewed-by: Pierre-Louis Bossart Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-11-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- sound/usb/card.h | 1 + sound/usb/format.c | 1 + 2 files changed, 2 insertions(+) diff --git a/sound/usb/card.h b/sound/usb/card.h index 4f4f3f39b7fa..b65163c60176 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -15,6 +15,7 @@ struct audioformat { unsigned int channels; /* # channels */ unsigned int fmt_type; /* USB audio format type (1-3) */ unsigned int fmt_bits; /* number of significant bits */ + unsigned int fmt_sz; /* overall audio sub frame/slot size */ unsigned int frame_size; /* samples per frame for non-audio */ unsigned char iface; /* interface number */ unsigned char altsetting; /* corresponding alternate setting */ diff --git a/sound/usb/format.c b/sound/usb/format.c index 9d32b21a5fbb..8490d80ade36 100644 --- a/sound/usb/format.c +++ b/sound/usb/format.c @@ -85,6 +85,7 @@ static u64 parse_audio_format_i_type(struct snd_usb_audio *chip, } fp->fmt_bits = sample_width; + fp->fmt_sz = sample_bytes; if ((pcm_formats == 0) && (format == 0 || format == BIT(UAC_FORMAT_TYPE_I_UNDEFINED))) { -- cgit v1.2.3-59-g8ed1b From f15d1e557b01b19f4b20aae8cf1a1f2b1f565571 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:44 -0700 Subject: ALSA: usb-audio: Prevent starting of audio stream if in use With USB audio offloading, an audio session is started from the ASoC platform sound card and PCM devices. Likewise, the USB SND path is still readily available for use, in case the non-offload path is desired. In order to prevent the two entities from attempting to use the USB bus, introduce a flag that determines when either paths are in use. If a PCM device is already in use, the check will return an error to userspace notifying that the stream is currently busy. This ensures that only one path is using the USB substream. Reviewed-by: Pierre-Louis Bossart Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-12-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- sound/usb/card.h | 1 + sound/usb/pcm.c | 29 ++++++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/sound/usb/card.h b/sound/usb/card.h index b65163c60176..cdafc9e9cecd 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -165,6 +165,7 @@ struct snd_usb_substream { unsigned int pkt_offset_adj; /* Bytes to drop from beginning of packets (for non-compliant devices) */ unsigned int stream_offset_adj; /* Bytes to drop from beginning of stream (for non-compliant devices) */ + unsigned int opened:1; /* pcm device opened */ unsigned int running: 1; /* running status */ unsigned int period_elapsed_pending; /* delay period handling */ diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index 18467da6fd9e..b24ee38fad72 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -1241,8 +1241,17 @@ static int snd_usb_pcm_open(struct snd_pcm_substream *substream) struct snd_usb_stream *as = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; struct snd_usb_substream *subs = &as->substream[direction]; + struct snd_usb_audio *chip = subs->stream->chip; int ret; + mutex_lock(&chip->mutex); + if (subs->opened) { + mutex_unlock(&chip->mutex); + return -EBUSY; + } + subs->opened = 1; + mutex_unlock(&chip->mutex); + runtime->hw = snd_usb_hardware; /* need an explicit sync to catch applptr update in low-latency mode */ if (direction == SNDRV_PCM_STREAM_PLAYBACK && @@ -1259,13 +1268,23 @@ static int snd_usb_pcm_open(struct snd_pcm_substream *substream) ret = setup_hw_info(runtime, subs); if (ret < 0) - return ret; + goto err_open; ret = snd_usb_autoresume(subs->stream->chip); if (ret < 0) - return ret; + goto err_open; ret = snd_media_stream_init(subs, as->pcm, direction); if (ret < 0) - snd_usb_autosuspend(subs->stream->chip); + goto err_resume; + + return 0; + +err_resume: + snd_usb_autosuspend(subs->stream->chip); +err_open: + mutex_lock(&chip->mutex); + subs->opened = 0; + mutex_unlock(&chip->mutex); + return ret; } @@ -1274,6 +1293,7 @@ static int snd_usb_pcm_close(struct snd_pcm_substream *substream) int direction = substream->stream; struct snd_usb_stream *as = snd_pcm_substream_chip(substream); struct snd_usb_substream *subs = &as->substream[direction]; + struct snd_usb_audio *chip = subs->stream->chip; int ret; snd_media_stop_pipeline(subs); @@ -1287,6 +1307,9 @@ static int snd_usb_pcm_close(struct snd_pcm_substream *substream) subs->pcm_substream = NULL; snd_usb_autosuspend(subs->stream->chip); + mutex_lock(&chip->mutex); + subs->opened = 0; + mutex_unlock(&chip->mutex); return 0; } -- cgit v1.2.3-59-g8ed1b From 74914dc1ea268a58be7112f14d8b3568866e40e8 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:45 -0700 Subject: ALSA: usb-audio: Introduce USB SND platform op callbacks Allow for different platforms to be notified on USB SND connect/disconnect sequences. This allows for platform USB SND modules to properly initialize and populate internal structures with references to the USB SND chip device. Tested-by: Puma Hsu Tested-by: Daehwan Jung Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-13-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- sound/usb/card.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ sound/usb/card.h | 10 ++++++++++ 2 files changed, 59 insertions(+) diff --git a/sound/usb/card.c b/sound/usb/card.c index 6c5b0e02e57b..4ac7f2b8309a 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -118,6 +118,42 @@ MODULE_PARM_DESC(skip_validation, "Skip unit descriptor validation (default: no) static DEFINE_MUTEX(register_mutex); static struct snd_usb_audio *usb_chip[SNDRV_CARDS]; static struct usb_driver usb_audio_driver; +static struct snd_usb_platform_ops *platform_ops; + +/* + * Register platform specific operations that will be notified on events + * which occur in USB SND. The platform driver can utilize this path to + * enable features, such as USB audio offloading, which allows for audio data + * to be queued by an audio DSP. + * + * Only one set of platform operations can be registered to USB SND. The + * platform register operation is protected by the register_mutex. + */ +int snd_usb_register_platform_ops(struct snd_usb_platform_ops *ops) +{ + guard(mutex)(®ister_mutex); + if (platform_ops) + return -EEXIST; + + platform_ops = ops; + return 0; +} +EXPORT_SYMBOL_GPL(snd_usb_register_platform_ops); + +/* + * Unregisters the current set of platform operations. This allows for + * a new set to be registered if required. + * + * The platform unregister operation is protected by the register_mutex. + */ +int snd_usb_unregister_platform_ops(void) +{ + guard(mutex)(®ister_mutex); + platform_ops = NULL; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_usb_unregister_platform_ops); /* * Checks to see if requested audio profile, i.e sample rate, # of @@ -954,7 +990,11 @@ static int usb_audio_probe(struct usb_interface *intf, chip->num_interfaces++; usb_set_intfdata(intf, chip); atomic_dec(&chip->active); + + if (platform_ops && platform_ops->connect_cb) + platform_ops->connect_cb(chip); mutex_unlock(®ister_mutex); + return 0; __error: @@ -991,6 +1031,9 @@ static void usb_audio_disconnect(struct usb_interface *intf) card = chip->card; mutex_lock(®ister_mutex); + if (platform_ops && platform_ops->disconnect_cb) + platform_ops->disconnect_cb(chip); + if (atomic_inc_return(&chip->shutdown) == 1) { struct snd_usb_stream *as; struct snd_usb_endpoint *ep; @@ -1138,6 +1181,9 @@ static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message) chip->system_suspend = chip->num_suspended_intf; } + if (platform_ops && platform_ops->suspend_cb) + platform_ops->suspend_cb(intf, message); + return 0; } @@ -1178,6 +1224,9 @@ static int usb_audio_resume(struct usb_interface *intf) snd_usb_midi_v2_resume_all(chip); + if (platform_ops && platform_ops->resume_cb) + platform_ops->resume_cb(intf); + out: if (chip->num_suspended_intf == chip->system_suspend) { snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0); diff --git a/sound/usb/card.h b/sound/usb/card.h index cdafc9e9cecd..d8b8522e1613 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -209,7 +209,17 @@ struct snd_usb_stream { struct list_head list; }; +struct snd_usb_platform_ops { + void (*connect_cb)(struct snd_usb_audio *chip); + void (*disconnect_cb)(struct snd_usb_audio *chip); + void (*suspend_cb)(struct usb_interface *intf, pm_message_t message); + void (*resume_cb)(struct usb_interface *intf); +}; + struct snd_usb_stream * snd_usb_find_suppported_substream(int card_idx, struct snd_pcm_hw_params *params, int direction); + +int snd_usb_register_platform_ops(struct snd_usb_platform_ops *ops); +int snd_usb_unregister_platform_ops(void); #endif /* __USBAUDIO_CARD_H */ -- cgit v1.2.3-59-g8ed1b From 722f79117ee886c4c1ef08f443a1fdfd5433747f Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:46 -0700 Subject: ALSA: usb-audio: Allow for rediscovery of connected USB SND devices In case of notifying SND platform drivers of connection events, some of these use cases, such as offloading, require an ASoC USB backend device to be initialized before the events can be handled. If the USB backend device has not yet been probed, this leads to missing initial USB audio device connection events. Expose an API that traverses the usb_chip array for connected devices, and to call the respective connection callback registered to the SND platform driver. Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-14-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- sound/usb/card.c | 21 +++++++++++++++++++++ sound/usb/card.h | 2 ++ 2 files changed, 23 insertions(+) diff --git a/sound/usb/card.c b/sound/usb/card.c index 4ac7f2b8309a..9fb8726a6c93 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -155,6 +155,27 @@ int snd_usb_unregister_platform_ops(void) } EXPORT_SYMBOL_GPL(snd_usb_unregister_platform_ops); +/* + * in case the platform driver was not ready at the time of USB SND + * device connect, expose an API to discover all connected USB devices + * so it can populate any dependent resources/structures. + */ +void snd_usb_rediscover_devices(void) +{ + int i; + + guard(mutex)(®ister_mutex); + + if (!platform_ops || !platform_ops->connect_cb) + return; + + for (i = 0; i < SNDRV_CARDS; i++) { + if (usb_chip[i]) + platform_ops->connect_cb(usb_chip[i]); + } +} +EXPORT_SYMBOL_GPL(snd_usb_rediscover_devices); + /* * Checks to see if requested audio profile, i.e sample rate, # of * channels, etc... is supported by the substream associated to the diff --git a/sound/usb/card.h b/sound/usb/card.h index d8b8522e1613..94404c24d240 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -222,4 +222,6 @@ snd_usb_find_suppported_substream(int card_idx, struct snd_pcm_hw_params *params int snd_usb_register_platform_ops(struct snd_usb_platform_ops *ops); int snd_usb_unregister_platform_ops(void); + +void snd_usb_rediscover_devices(void); #endif /* __USBAUDIO_CARD_H */ -- cgit v1.2.3-59-g8ed1b From dba7759af789b75240379e94017e655221c83225 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:47 -0700 Subject: ASoC: Add SoC USB APIs for adding an USB backend Some platforms may have support for offloading USB audio devices to a dedicated audio DSP. Introduce a set of APIs that allow for management of USB sound card and PCM devices enumerated by the USB SND class driver. This allows for the ASoC components to be aware of what USB devices are available for offloading. Reviewed-by: Pierre-Louis Bossart Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-15-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- include/sound/soc-usb.h | 93 ++++++++++++++++++++ sound/soc/Kconfig | 10 +++ sound/soc/Makefile | 2 + sound/soc/soc-usb.c | 219 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 324 insertions(+) create mode 100644 include/sound/soc-usb.h create mode 100644 sound/soc/soc-usb.c diff --git a/include/sound/soc-usb.h b/include/sound/soc-usb.h new file mode 100644 index 000000000000..a7be5d05218f --- /dev/null +++ b/include/sound/soc-usb.h @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __LINUX_SND_SOC_USB_H +#define __LINUX_SND_SOC_USB_H + +#include + +/** + * struct snd_soc_usb_device - SoC USB representation of a USB sound device + * @card_idx: sound card index associated with USB device + * @chip_idx: USB sound chip array index + * @cpcm_idx: capture PCM index array associated with USB device + * @ppcm_idx: playback PCM index array associated with USB device + * @num_capture: number of capture streams + * @num_playback: number of playback streams + * @list: list head for SoC USB devices + **/ +struct snd_soc_usb_device { + int card_idx; + int chip_idx; + + /* PCM index arrays */ + unsigned int *cpcm_idx; /* TODO: capture path is not tested yet */ + unsigned int *ppcm_idx; + int num_capture; /* TODO: capture path is not tested yet */ + int num_playback; + + struct list_head list; +}; + +/** + * struct snd_soc_usb - representation of a SoC USB backend entity + * @list: list head for SND SOC struct list + * @component: reference to ASoC component + * @connection_status_cb: callback to notify connection events + * @priv_data: driver data + **/ +struct snd_soc_usb { + struct list_head list; + struct snd_soc_component *component; + int (*connection_status_cb)(struct snd_soc_usb *usb, + struct snd_soc_usb_device *sdev, + bool connected); + void *priv_data; +}; + +#if IS_ENABLED(CONFIG_SND_SOC_USB) +int snd_soc_usb_connect(struct device *usbdev, struct snd_soc_usb_device *sdev); +int snd_soc_usb_disconnect(struct device *usbdev, struct snd_soc_usb_device *sdev); +void *snd_soc_usb_find_priv_data(struct device *usbdev); + +struct snd_soc_usb *snd_soc_usb_allocate_port(struct snd_soc_component *component, + void *data); +void snd_soc_usb_free_port(struct snd_soc_usb *usb); +void snd_soc_usb_add_port(struct snd_soc_usb *usb); +void snd_soc_usb_remove_port(struct snd_soc_usb *usb); +#else +static inline int snd_soc_usb_connect(struct device *usbdev, + struct snd_soc_usb_device *sdev) +{ + return -ENODEV; +} + +static inline int snd_soc_usb_disconnect(struct device *usbdev, + struct snd_soc_usb_device *sdev) +{ + return -EINVAL; +} + +static inline void *snd_soc_usb_find_priv_data(struct device *usbdev) +{ + return NULL; +} + +static inline struct snd_soc_usb * +snd_soc_usb_allocate_port(struct snd_soc_component *component, void *data) +{ + return ERR_PTR(-ENOMEM); +} + +static inline void snd_soc_usb_free_port(struct snd_soc_usb *usb) +{ } + +static inline void snd_soc_usb_add_port(struct snd_soc_usb *usb) +{ } + +static inline void snd_soc_usb_remove_port(struct snd_soc_usb *usb) +{ } +#endif /* IS_ENABLED(CONFIG_SND_SOC_USB) */ +#endif /*__LINUX_SND_SOC_USB_H */ diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 8b7d51266f81..1b983c7006f1 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -91,6 +91,16 @@ config SND_SOC_OPS_KUNIT_TEST config SND_SOC_ACPI tristate +config SND_SOC_USB + tristate "SoC based USB audio offloading" + depends on SND_USB_AUDIO + help + Enable this option if an ASoC platform card has support to handle + USB audio offloading. This enables the SoC USB layer, which will + notify the ASoC USB DPCM backend DAI link about available USB audio + devices. Based on the notifications, sequences to enable the audio + stream can be taken based on the design. + # All the supported SoCs source "sound/soc/adi/Kconfig" source "sound/soc/amd/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 358e227c5ab6..462322c38aa4 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -39,6 +39,8 @@ endif obj-$(CONFIG_SND_SOC_ACPI) += snd-soc-acpi.o +obj-$(CONFIG_SND_SOC_USB) += soc-usb.o + obj-$(CONFIG_SND_SOC) += snd-soc-core.o obj-$(CONFIG_SND_SOC) += codecs/ obj-$(CONFIG_SND_SOC) += generic/ diff --git a/sound/soc/soc-usb.c b/sound/soc/soc-usb.c new file mode 100644 index 000000000000..edfc52c88b58 --- /dev/null +++ b/sound/soc/soc-usb.c @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ +#include +#include +#include +#include "../usb/card.h" + +static DEFINE_MUTEX(ctx_mutex); +static LIST_HEAD(usb_ctx_list); + +static struct device_node *snd_soc_find_phandle(struct device *dev) +{ + struct device_node *node; + + node = of_parse_phandle(dev->of_node, "usb-soc-be", 0); + if (!node) + return ERR_PTR(-ENODEV); + + return node; +} + +static struct snd_soc_usb *snd_soc_usb_ctx_lookup(struct device_node *node) +{ + struct snd_soc_usb *ctx; + + if (!node) + return NULL; + + list_for_each_entry(ctx, &usb_ctx_list, list) { + if (ctx->component->dev->of_node == node) + return ctx; + } + + return NULL; +} + +static struct snd_soc_usb *snd_soc_find_usb_ctx(struct device *dev) +{ + struct snd_soc_usb *ctx; + struct device_node *node; + + node = snd_soc_find_phandle(dev); + if (!IS_ERR(node)) { + ctx = snd_soc_usb_ctx_lookup(node); + of_node_put(node); + } else { + ctx = snd_soc_usb_ctx_lookup(dev->of_node); + } + + return ctx ? ctx : NULL; +} + +/** + * snd_soc_usb_find_priv_data() - Retrieve private data stored + * @usbdev: device reference + * + * Fetch the private data stored in the USB SND SoC structure. + * + */ +void *snd_soc_usb_find_priv_data(struct device *usbdev) +{ + struct snd_soc_usb *ctx; + + mutex_lock(&ctx_mutex); + ctx = snd_soc_find_usb_ctx(usbdev); + mutex_unlock(&ctx_mutex); + + return ctx ? ctx->priv_data : NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_usb_find_priv_data); + +/** + * snd_soc_usb_allocate_port() - allocate a SoC USB port for offloading support + * @component: USB DPCM backend DAI component + * @data: private data + * + * Allocate and initialize a SoC USB port. The SoC USB port is used to communicate + * different USB audio devices attached, in order to start audio offloading handled + * by an ASoC entity. USB device plug in/out events are signaled with a + * notification, but don't directly impact the memory allocated for the SoC USB + * port. + * + */ +struct snd_soc_usb *snd_soc_usb_allocate_port(struct snd_soc_component *component, + void *data) +{ + struct snd_soc_usb *usb; + + usb = kzalloc(sizeof(*usb), GFP_KERNEL); + if (!usb) + return ERR_PTR(-ENOMEM); + + usb->component = component; + usb->priv_data = data; + + return usb; +} +EXPORT_SYMBOL_GPL(snd_soc_usb_allocate_port); + +/** + * snd_soc_usb_free_port() - free a SoC USB port used for offloading support + * @usb: allocated SoC USB port + * + * Free and remove the SoC USB port from the available list of ports. This will + * ensure that the communication between USB SND and ASoC is halted. + * + */ +void snd_soc_usb_free_port(struct snd_soc_usb *usb) +{ + snd_soc_usb_remove_port(usb); + kfree(usb); +} +EXPORT_SYMBOL_GPL(snd_soc_usb_free_port); + +/** + * snd_soc_usb_add_port() - Add a USB backend port + * @usb: soc usb port to add + * + * Register a USB backend DAI link to the USB SoC framework. Memory is allocated + * as part of the USB backend DAI link. + * + */ +void snd_soc_usb_add_port(struct snd_soc_usb *usb) +{ + mutex_lock(&ctx_mutex); + list_add_tail(&usb->list, &usb_ctx_list); + mutex_unlock(&ctx_mutex); +} +EXPORT_SYMBOL_GPL(snd_soc_usb_add_port); + +/** + * snd_soc_usb_remove_port() - Remove a USB backend port + * @usb: soc usb port to remove + * + * Remove a USB backend DAI link from USB SoC. Memory is freed when USB backend + * DAI is removed, or when snd_soc_usb_free_port() is called. + * + */ +void snd_soc_usb_remove_port(struct snd_soc_usb *usb) +{ + struct snd_soc_usb *ctx, *tmp; + + mutex_lock(&ctx_mutex); + list_for_each_entry_safe(ctx, tmp, &usb_ctx_list, list) { + if (ctx == usb) { + list_del(&ctx->list); + break; + } + } + mutex_unlock(&ctx_mutex); +} +EXPORT_SYMBOL_GPL(snd_soc_usb_remove_port); + +/** + * snd_soc_usb_connect() - Notification of USB device connection + * @usbdev: USB bus device + * @sdev: USB SND device to add + * + * Notify of a new USB SND device connection. The sdev->card_idx can be used to + * handle how the DPCM backend selects, which device to enable USB offloading + * on. + * + */ +int snd_soc_usb_connect(struct device *usbdev, struct snd_soc_usb_device *sdev) +{ + struct snd_soc_usb *ctx; + + if (!usbdev) + return -ENODEV; + + mutex_lock(&ctx_mutex); + ctx = snd_soc_find_usb_ctx(usbdev); + if (!ctx) + goto exit; + + if (ctx->connection_status_cb) + ctx->connection_status_cb(ctx, sdev, true); + +exit: + mutex_unlock(&ctx_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_usb_connect); + +/** + * snd_soc_usb_disconnect() - Notification of USB device disconnection + * @usbdev: USB bus device + * @sdev: USB SND device to remove + * + * Notify of a new USB SND device disconnection to the USB backend. + * + */ +int snd_soc_usb_disconnect(struct device *usbdev, struct snd_soc_usb_device *sdev) +{ + struct snd_soc_usb *ctx; + + if (!usbdev) + return -ENODEV; + + mutex_lock(&ctx_mutex); + ctx = snd_soc_find_usb_ctx(usbdev); + if (!ctx) + goto exit; + + if (ctx->connection_status_cb) + ctx->connection_status_cb(ctx, sdev, false); + +exit: + mutex_unlock(&ctx_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_usb_disconnect); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("SoC USB driver for offloading"); -- cgit v1.2.3-59-g8ed1b From 00f5d6bfba3ac7b28dd9bbf00eab5db35e0347b7 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:48 -0700 Subject: ASoC: usb: Add PCM format check API for USB backend Introduce a helper to check if a particular PCM format is supported by the USB audio device connected. If the USB audio device does not have an audio profile which can support the requested format, then notify the USB backend. Reviewed-by: Pierre-Louis Bossart Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-16-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- include/sound/soc-usb.h | 11 +++++++++++ sound/soc/soc-usb.c | 26 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/include/sound/soc-usb.h b/include/sound/soc-usb.h index a7be5d05218f..188b8df52932 100644 --- a/include/sound/soc-usb.h +++ b/include/sound/soc-usb.h @@ -48,6 +48,10 @@ struct snd_soc_usb { }; #if IS_ENABLED(CONFIG_SND_SOC_USB) +int snd_soc_usb_find_supported_format(int card_idx, + struct snd_pcm_hw_params *params, + int direction); + int snd_soc_usb_connect(struct device *usbdev, struct snd_soc_usb_device *sdev); int snd_soc_usb_disconnect(struct device *usbdev, struct snd_soc_usb_device *sdev); void *snd_soc_usb_find_priv_data(struct device *usbdev); @@ -58,6 +62,13 @@ void snd_soc_usb_free_port(struct snd_soc_usb *usb); void snd_soc_usb_add_port(struct snd_soc_usb *usb); void snd_soc_usb_remove_port(struct snd_soc_usb *usb); #else +static inline int +snd_soc_usb_find_supported_format(int card_idx, struct snd_pcm_hw_params *params, + int direction) +{ + return -EINVAL; +} + static inline int snd_soc_usb_connect(struct device *usbdev, struct snd_soc_usb_device *sdev) { diff --git a/sound/soc/soc-usb.c b/sound/soc/soc-usb.c index edfc52c88b58..cb2025d7b58f 100644 --- a/sound/soc/soc-usb.c +++ b/sound/soc/soc-usb.c @@ -71,6 +71,32 @@ void *snd_soc_usb_find_priv_data(struct device *usbdev) } EXPORT_SYMBOL_GPL(snd_soc_usb_find_priv_data); +/** + * snd_soc_usb_find_supported_format() - Check if audio format is supported + * @card_idx: USB sound chip array index + * @params: PCM parameters + * @direction: capture or playback + * + * Ensure that a requested audio profile from the ASoC side is able to be + * supported by the USB device. + * + * Return 0 on success, negative on error. + * + */ +int snd_soc_usb_find_supported_format(int card_idx, + struct snd_pcm_hw_params *params, + int direction) +{ + struct snd_usb_stream *as; + + as = snd_usb_find_suppported_substream(card_idx, params, direction); + if (!as) + return -EOPNOTSUPP; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_usb_find_supported_format); + /** * snd_soc_usb_allocate_port() - allocate a SoC USB port for offloading support * @component: USB DPCM backend DAI component -- cgit v1.2.3-59-g8ed1b From 0bb5f3614b2588f758e6db21397776d7164f3b66 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:49 -0700 Subject: ASoC: usb: Create SOC USB SND jack kcontrol Expose API for creation of a jack control for notifying of available devices that are plugged in/discovered, and that support offloading. This allows for control names to be standardized across implementations of USB audio offloading. Reviewed-by: Pierre-Louis Bossart Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-17-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- include/sound/soc-usb.h | 9 +++++++++ sound/soc/soc-usb.c | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/include/sound/soc-usb.h b/include/sound/soc-usb.h index 188b8df52932..e9eb8dbb2e0e 100644 --- a/include/sound/soc-usb.h +++ b/include/sound/soc-usb.h @@ -56,6 +56,9 @@ int snd_soc_usb_connect(struct device *usbdev, struct snd_soc_usb_device *sdev); int snd_soc_usb_disconnect(struct device *usbdev, struct snd_soc_usb_device *sdev); void *snd_soc_usb_find_priv_data(struct device *usbdev); +int snd_soc_usb_setup_offload_jack(struct snd_soc_component *component, + struct snd_soc_jack *jack); + struct snd_soc_usb *snd_soc_usb_allocate_port(struct snd_soc_component *component, void *data); void snd_soc_usb_free_port(struct snd_soc_usb *usb); @@ -86,6 +89,12 @@ static inline void *snd_soc_usb_find_priv_data(struct device *usbdev) return NULL; } +static inline int snd_soc_usb_setup_offload_jack(struct snd_soc_component *component, + struct snd_soc_jack *jack) +{ + return 0; +} + static inline struct snd_soc_usb * snd_soc_usb_allocate_port(struct snd_soc_component *component, void *data) { diff --git a/sound/soc/soc-usb.c b/sound/soc/soc-usb.c index cb2025d7b58f..ec894182f816 100644 --- a/sound/soc/soc-usb.c +++ b/sound/soc/soc-usb.c @@ -4,7 +4,10 @@ */ #include #include + +#include #include + #include "../usb/card.h" static DEFINE_MUTEX(ctx_mutex); @@ -52,6 +55,41 @@ static struct snd_soc_usb *snd_soc_find_usb_ctx(struct device *dev) return ctx ? ctx : NULL; } +/* SOC USB sound kcontrols */ +/** + * snd_soc_usb_setup_offload_jack() - Create USB offloading jack + * @component: USB DPCM backend DAI component + * @jack: jack structure to create + * + * Creates a jack device for notifying userspace of the availability + * of an offload capable device. + * + * Returns 0 on success, negative on error. + * + */ +int snd_soc_usb_setup_offload_jack(struct snd_soc_component *component, + struct snd_soc_jack *jack) +{ + int ret; + + ret = snd_soc_card_jack_new(component->card, "USB Offload Jack", + SND_JACK_USB, jack); + if (ret < 0) { + dev_err(component->card->dev, "Unable to add USB offload jack: %d\n", + ret); + return ret; + } + + ret = snd_soc_component_set_jack(component, jack, NULL); + if (ret) { + dev_err(component->card->dev, "Failed to set jack: %d\n", ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_usb_setup_offload_jack); + /** * snd_soc_usb_find_priv_data() - Retrieve private data stored * @usbdev: device reference -- cgit v1.2.3-59-g8ed1b From 234ed325920c4441090e4dd5441d4e424c1804c9 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:50 -0700 Subject: ASoC: usb: Fetch ASoC card and pcm device information USB SND needs to know how the USB offload path is being routed. This would allow for applications to open the corresponding sound card and pcm device when it wants to take the audio offload path. This callback should return the mapped indexes based on the USB SND device information. Reviewed-by: Pierre-Louis Bossart Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-18-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- include/sound/soc-usb.h | 25 +++++++++++++++++++++++++ sound/soc/soc-usb.c | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/include/sound/soc-usb.h b/include/sound/soc-usb.h index e9eb8dbb2e0e..124acb1939e5 100644 --- a/include/sound/soc-usb.h +++ b/include/sound/soc-usb.h @@ -8,6 +8,11 @@ #include +enum snd_soc_usb_kctl { + SND_SOC_USB_KCTL_CARD_ROUTE, + SND_SOC_USB_KCTL_PCM_ROUTE, +}; + /** * struct snd_soc_usb_device - SoC USB representation of a USB sound device * @card_idx: sound card index associated with USB device @@ -36,6 +41,12 @@ struct snd_soc_usb_device { * @list: list head for SND SOC struct list * @component: reference to ASoC component * @connection_status_cb: callback to notify connection events + * @update_offload_route_info: callback to fetch mapped ASoC card and pcm + * device pair. This is unrelated to the concept + * of DAPM route. The "route" argument carries + * an array used for a kcontrol output for either + * the card or pcm index. "path" determines the + * which entry to look for. (ie mapped card or pcm) * @priv_data: driver data **/ struct snd_soc_usb { @@ -44,6 +55,10 @@ struct snd_soc_usb { int (*connection_status_cb)(struct snd_soc_usb *usb, struct snd_soc_usb_device *sdev, bool connected); + int (*update_offload_route_info)(struct snd_soc_component *component, + int card, int pcm, int direction, + enum snd_soc_usb_kctl path, + long *route); void *priv_data; }; @@ -58,6 +73,9 @@ void *snd_soc_usb_find_priv_data(struct device *usbdev); int snd_soc_usb_setup_offload_jack(struct snd_soc_component *component, struct snd_soc_jack *jack); +int snd_soc_usb_update_offload_route(struct device *dev, int card, int pcm, + int direction, enum snd_soc_usb_kctl path, + long *route); struct snd_soc_usb *snd_soc_usb_allocate_port(struct snd_soc_component *component, void *data); @@ -95,6 +113,13 @@ static inline int snd_soc_usb_setup_offload_jack(struct snd_soc_component *compo return 0; } +static int snd_soc_usb_update_offload_route(struct device *dev, int card, int pcm, + int direction, enum snd_soc_usb_kctl path, + long *route) +{ + return -ENODEV; +} + static inline struct snd_soc_usb * snd_soc_usb_allocate_port(struct snd_soc_component *component, void *data) { diff --git a/sound/soc/soc-usb.c b/sound/soc/soc-usb.c index ec894182f816..406bc4a1015d 100644 --- a/sound/soc/soc-usb.c +++ b/sound/soc/soc-usb.c @@ -90,6 +90,43 @@ int snd_soc_usb_setup_offload_jack(struct snd_soc_component *component, } EXPORT_SYMBOL_GPL(snd_soc_usb_setup_offload_jack); +/** + * snd_soc_usb_update_offload_route - Find active USB offload path + * @dev: USB device to get offload status + * @card: USB card index + * @pcm: USB PCM device index + * @direction: playback or capture direction + * @path: pcm or card index + * @route: pointer to route output array + * + * Fetch the current status for the USB SND card and PCM device indexes + * specified. The "route" argument should be an array of integers being + * used for a kcontrol output. The first element should have the selected + * card index, and the second element should have the selected pcm device + * index. + */ +int snd_soc_usb_update_offload_route(struct device *dev, int card, int pcm, + int direction, enum snd_soc_usb_kctl path, + long *route) +{ + struct snd_soc_usb *ctx; + int ret = -ENODEV; + + mutex_lock(&ctx_mutex); + ctx = snd_soc_find_usb_ctx(dev); + if (!ctx) + goto exit; + + if (ctx->update_offload_route_info) + ret = ctx->update_offload_route_info(ctx->component, card, pcm, + direction, path, route); +exit: + mutex_unlock(&ctx_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_usb_update_offload_route); + /** * snd_soc_usb_find_priv_data() - Retrieve private data stored * @usbdev: device reference -- cgit v1.2.3-59-g8ed1b From f98cd6ecda1d32d4c7f8a238a49e9ce71db89a2d Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:51 -0700 Subject: ASoC: usb: Rediscover USB SND devices on USB port add In case the USB backend device has not been initialized/probed, USB SND device connections can still occur. When the USB backend is eventually made available, previous USB SND device connections are not communicated to the USB backend. Call snd_usb_rediscover_devices() to generate the connect callbacks for all USB SND devices connected. This will allow for the USB backend to be updated with the current set of devices available. The chip array entries are all populated and removed while under the register_mutex, so going over potential race conditions: Thread#1: q6usb_component_probe() --> snd_soc_usb_add_port() --> snd_usb_rediscover_devices() --> mutex_lock(register_mutex) Thread#2 --> usb_audio_disconnect() --> mutex_lock(register_mutex) So either thread#1 or thread#2 will complete first. If Thread#1 completes before thread#2: SOC USB will notify DPCM backend of the device connection. Shortly after, once thread#2 runs, we will get a disconnect event for the connected device. Thread#2 completes before thread#1: Then during snd_usb_rediscover_devices() it won't notify of any connection for that particular chip index. Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-19-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- sound/soc/soc-usb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/soc-usb.c b/sound/soc/soc-usb.c index 406bc4a1015d..26baa66d29a8 100644 --- a/sound/soc/soc-usb.c +++ b/sound/soc/soc-usb.c @@ -228,6 +228,8 @@ void snd_soc_usb_add_port(struct snd_soc_usb *usb) mutex_lock(&ctx_mutex); list_add_tail(&usb->list, &usb_ctx_list); mutex_unlock(&ctx_mutex); + + snd_usb_rediscover_devices(); } EXPORT_SYMBOL_GPL(snd_soc_usb_add_port); -- cgit v1.2.3-59-g8ed1b From 6640c9bc5c974efaa56347215696cbd03aa36ced Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:52 -0700 Subject: ASoC: doc: Add documentation for SOC USB With the introduction of the soc-usb driver, add documentation highlighting details on how to utilize the new driver and how it interacts with different components in USB SND and ASoC. It provides examples on how to implement the drivers that will need to be introduced in order to enable USB audio offloading. Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-20-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- Documentation/sound/soc/index.rst | 1 + Documentation/sound/soc/usb.rst | 482 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 483 insertions(+) create mode 100644 Documentation/sound/soc/usb.rst diff --git a/Documentation/sound/soc/index.rst b/Documentation/sound/soc/index.rst index e57df2dab2fd..8bed8f8f48da 100644 --- a/Documentation/sound/soc/index.rst +++ b/Documentation/sound/soc/index.rst @@ -18,3 +18,4 @@ The documentation is spilt into the following sections:- jack dpcm codec-to-codec + usb diff --git a/Documentation/sound/soc/usb.rst b/Documentation/sound/soc/usb.rst new file mode 100644 index 000000000000..94c12f9d9dd1 --- /dev/null +++ b/Documentation/sound/soc/usb.rst @@ -0,0 +1,482 @@ +================ +ASoC USB support +================ + +Overview +======== +In order to leverage the existing USB sound device support in ALSA, the +ASoC USB APIs are introduced to allow the subsystems to exchange +configuration information. + +One potential use case would be to support USB audio offloading, which is +an implementation that allows for an alternate power-optimized path in the audio +subsystem to handle the transfer of audio data over the USB bus. This would +let the main processor to stay in lower power modes for longer duration. The +following is an example design of how the ASoC and ALSA pieces can be connected +together to achieve this: + +:: + + USB | ASoC + | _________________________ + | | ASoC Platform card | + | |_________________________| + | | | + | ___V____ ____V____ + | |ASoC BE | |ASoC FE | + | |DAI LNK | |DAI LNK | + | |________| |_________| + | ^ ^ ^ + | | |________| + | ___V____ | + | |SoC-USB | | + ________ ________ | | | + |USB SND |<--->|USBSND |<------------>|________| | + |(card.c)| |offld |<---------- | + |________| |________|___ | | | + ^ ^ | | | ____________V_________ + | | | | | |IPC | + __ V_______________V_____ | | | |______________________| + |USB SND (endpoint.c) | | | | ^ + |_________________________| | | | | + ^ | | | ___________V___________ + | | | |->|audio DSP | + ___________V_____________ | | |_______________________| + |XHCI HCD |<- | + |_________________________| | + + +SoC USB driver +============== +Structures +---------- +``struct snd_soc_usb`` + + - ``list``: list head for SND SoC struct list + - ``component``: reference to ASoC component + - ``connection_status_cb``: callback to notify connection events + - ``update_offload_route_info``: callback to fetch selected USB sound card/PCM + device + - ``priv_data``: driver data + +The snd_soc_usb structure can be referenced using the ASoC platform card +device, or a USB device (udev->dev). This is created by the ASoC BE DAI +link, and the USB sound entity will be able to pass information to the +ASoC BE DAI link using this structure. + +``struct snd_soc_usb_device`` + + - ``card_idx``: sound card index associated with USB sound device + - ``chip_idx``: USB sound chip array index + - ``cpcm_idx``: capture pcm device indexes associated with the USB sound device + - ``ppcm_idx``: playback pcm device indexes associated with the USB sound device + - ``num_playback``: number of playback streams + - ``num_capture``: number of capture streams + - ``list``: list head for the USB sound device list + +The struct snd_soc_usb_device is created by the USB sound offload driver. +This will carry basic parameters/limitations that will be used to +determine the possible offloading paths for this USB audio device. + +Functions +--------- +.. code-block:: rst + + int snd_soc_usb_find_supported_format(int card_idx, + struct snd_pcm_hw_params *params, int direction) +.. + + - ``card_idx``: the index into the USB sound chip array. + - ``params``: Requested PCM parameters from the USB DPCM BE DAI link + - ``direction``: capture or playback + +**snd_soc_usb_find_supported_format()** ensures that the requested audio profile +being requested by the external DSP is supported by the USB device. + +Returns 0 on success, and -EOPNOTSUPP on failure. + +.. code-block:: rst + + int snd_soc_usb_connect(struct device *usbdev, struct snd_soc_usb_device *sdev) +.. + + - ``usbdev``: the usb device that was discovered + - ``sdev``: capabilities of the device + +**snd_soc_usb_connect()** notifies the ASoC USB DCPM BE DAI link of a USB +audio device detection. This can be utilized in the BE DAI +driver to keep track of available USB audio devices. This is intended +to be called by the USB offload driver residing in USB SND. + +Returns 0 on success, negative error code on failure. + +.. code-block:: rst + + int snd_soc_usb_disconnect(struct device *usbdev, struct snd_soc_usb_device *sdev) +.. + + - ``usbdev``: the usb device that was removed + - ``sdev``: capabilities to free + +**snd_soc_usb_disconnect()** notifies the ASoC USB DCPM BE DAI link of a USB +audio device removal. This is intended to be called by the USB offload +driver that resides in USB SND. + +.. code-block:: rst + + void *snd_soc_usb_find_priv_data(struct device *usbdev) +.. + + - ``usbdev``: the usb device to reference to find private data + +**snd_soc_usb_find_priv_data()** fetches the private data saved to the SoC USB +device. + +Returns pointer to priv_data on success, NULL on failure. + +.. code-block:: rst + + int snd_soc_usb_setup_offload_jack(struct snd_soc_component *component, + struct snd_soc_jack *jack) +.. + + - ``component``: ASoC component to add the jack + - ``jack``: jack component to populate + +**snd_soc_usb_setup_offload_jack()** is a helper to add a sound jack control to +the platform sound card. This will allow for consistent naming to be used on +designs that support USB audio offloading. Additionally, this will enable the +jack to notify of changes. + +Returns 0 on success, negative otherwise. + +.. code-block:: rst + + int snd_soc_usb_update_offload_route(struct device *dev, int card, int pcm, + int direction, enum snd_soc_usb_kctl path, + long *route) +.. + + - ``dev``: USB device to look up offload path mapping + - ``card``: USB sound card index + - ``pcm``: USB sound PCM device index + - ``direction``: direction to fetch offload routing information + - ``path``: kcontrol selector - pcm device or card index + - ``route``: mapping of sound card and pcm indexes for the offload path. This is + an array of two integers that will carry the card and pcm device indexes + in that specific order. This can be used as the array for the kcontrol + output. + +**snd_soc_usb_update_offload_route()** calls a registered callback to the USB BE DAI +link to fetch the information about the mapped ASoC devices for executing USB audio +offload for the device. ``route`` may be a pointer to a kcontrol value output array, +which carries values when the kcontrol is read. + +Returns 0 on success, negative otherwise. + +.. code-block:: rst + + struct snd_soc_usb *snd_soc_usb_allocate_port(struct snd_soc_component *component, + void *data); +.. + + - ``component``: DPCM BE DAI link component + - ``data``: private data + +**snd_soc_usb_allocate_port()** allocates a SoC USB device and populates standard +parameters that is used for further operations. + +Returns a pointer to struct soc_usb on success, negative on error. + +.. code-block:: rst + + void snd_soc_usb_free_port(struct snd_soc_usb *usb); +.. + + - ``usb``: SoC USB device to free + +**snd_soc_usb_free_port()** frees a SoC USB device. + +.. code-block:: rst + + void snd_soc_usb_add_port(struct snd_soc_usb *usb); +.. + + - ``usb``: SoC USB device to add + +**snd_soc_usb_add_port()** add an allocated SoC USB device to the SOC USB framework. +Once added, this device can be referenced by further operations. + +.. code-block:: rst + + void snd_soc_usb_remove_port(struct snd_soc_usb *usb); +.. + + - ``usb``: SoC USB device to remove + +**snd_soc_usb_remove_port()** removes a SoC USB device from the SoC USB framework. +After removing a device, any SOC USB operations would not be able to reference the +device removed. + +How to Register to SoC USB +-------------------------- +The ASoC DPCM USB BE DAI link is the entity responsible for allocating and +registering the SoC USB device on the component bind. Likewise, it will +also be responsible for freeing the allocated resources. An example can +be shown below: + +.. code-block:: rst + + static int q6usb_component_probe(struct snd_soc_component *component) + { + ... + data->usb = snd_soc_usb_allocate_port(component, 1, &data->priv); + if (!data->usb) + return -ENOMEM; + + usb->connection_status_cb = q6usb_alsa_connection_cb; + + ret = snd_soc_usb_add_port(usb); + if (ret < 0) { + dev_err(component->dev, "failed to add usb port\n"); + goto free_usb; + } + ... + } + + static void q6usb_component_remove(struct snd_soc_component *component) + { + ... + snd_soc_usb_remove_port(data->usb); + snd_soc_usb_free_port(data->usb); + } + + static const struct snd_soc_component_driver q6usb_dai_component = { + .probe = q6usb_component_probe, + .remove = q6usb_component_remove, + .name = "q6usb-dai-component", + ... + }; +.. + +BE DAI links can pass along vendor specific information as part of the +call to allocate the SoC USB device. This will allow any BE DAI link +parameters or settings to be accessed by the USB offload driver that +resides in USB SND. + +USB Audio Device Connection Flow +-------------------------------- +USB devices can be hotplugged into the USB ports at any point in time. +The BE DAI link should be aware of the current state of the physical USB +port, i.e. if there are any USB devices with audio interface(s) connected. +connection_status_cb() can be used to notify the BE DAI link of any change. + +This is called whenever there is a USB SND interface bind or remove event, +using snd_soc_usb_connect() or snd_soc_usb_disconnect(): + +.. code-block:: rst + + static void qc_usb_audio_offload_probe(struct snd_usb_audio *chip) + { + ... + snd_soc_usb_connect(usb_get_usb_backend(udev), sdev); + ... + } + + static void qc_usb_audio_offload_disconnect(struct snd_usb_audio *chip) + { + ... + snd_soc_usb_disconnect(usb_get_usb_backend(chip->dev), dev->sdev); + ... + } +.. + +In order to account for conditions where driver or device existence is +not guaranteed, USB SND exposes snd_usb_rediscover_devices() to resend the +connect events for any identified USB audio interfaces. Consider the +the following situation: + + **usb_audio_probe()** + | --> USB audio streams allocated and saved to usb_chip[] + | --> Propagate connect event to USB offload driver in USB SND + | --> **snd_soc_usb_connect()** exits as USB BE DAI link is not ready + + BE DAI link component probe + | --> DAI link is probed and SoC USB port is allocated + | --> The USB audio device connect event is missed + +To ensure connection events are not missed, **snd_usb_rediscover_devices()** +is executed when the SoC USB device is registered. Now, when the BE DAI +link component probe occurs, the following highlights the sequence: + + BE DAI link component probe + | --> DAI link is probed and SoC USB port is allocated + | --> SoC USB device added, and **snd_usb_rediscover_devices()** runs + + **snd_usb_rediscover_devices()** + | --> Traverses through usb_chip[] and for non-NULL entries issue + | **connection_status_cb()** + +In the case where the USB offload driver is unbound, while USB SND is ready, +the **snd_usb_rediscover_devices()** is called during module init. This allows +for the offloading path to also be enabled with the following flow: + + **usb_audio_probe()** + | --> USB audio streams allocated and saved to usb_chip[] + | --> Propagate connect event to USB offload driver in USB SND + | --> USB offload driver **NOT** ready! + + BE DAI link component probe + | --> DAI link is probed and SoC USB port is allocated + | --> No USB connect event due to missing USB offload driver + + USB offload driver probe + | --> **qc_usb_audio_offload_init()** + | --> Calls **snd_usb_rediscover_devices()** to notify of devices + +USB Offload Related Kcontrols +============================= +Details +------- +A set of kcontrols can be utilized by applications to help select the proper sound +devices to enable USB audio offloading. SoC USB exposes the get_offload_dev() +callback that designs can use to ensure that the proper indices are returned to the +application. + +Implementation +-------------- + +**Example:** + + **Sound Cards**: + + :: + + 0 [SM8250MTPWCD938]: sm8250 - SM8250-MTP-WCD9380-WSA8810-VA-D + SM8250-MTP-WCD9380-WSA8810-VA-DMIC + 1 [Seri ]: USB-Audio - Plantronics Blackwire 3225 Seri + Plantronics Plantronics Blackwire + 3225 Seri at usb-xhci-hcd.1.auto-1.1, + full sp + 2 [C320M ]: USB-Audio - Plantronics C320-M + Plantronics Plantronics C320-M at usb-xhci-hcd.1.auto-1.2, full speed + + **PCM Devices**: + + :: + + card 0: SM8250MTPWCD938 [SM8250-MTP-WCD9380-WSA8810-VA-D], device 0: MultiMedia1 (*) [] + Subdevices: 1/1 + Subdevice #0: subdevice #0 + card 0: SM8250MTPWCD938 [SM8250-MTP-WCD9380-WSA8810-VA-D], device 1: MultiMedia2 (*) [] + Subdevices: 1/1 + Subdevice #0: subdevice #0 + card 1: Seri [Plantronics Blackwire 3225 Seri], device 0: USB Audio [USB Audio] + Subdevices: 1/1 + Subdevice #0: subdevice #0 + card 2: C320M [Plantronics C320-M], device 0: USB Audio [USB Audio] + Subdevices: 1/1 + Subdevice #0: subdevice #0 + + **USB Sound Card** - card#1: + + :: + + USB Offload Playback Card Route PCM#0 -1 (range -1->32) + USB Offload Playback PCM Route PCM#0 -1 (range -1->255) + + **USB Sound Card** - card#2: + + :: + + USB Offload Playback Card Route PCM#0 0 (range -1->32) + USB Offload Playback PCM Route PCM#0 1 (range -1->255) + +The above example shows a scenario where the system has one ASoC platform card +(card#0) and two USB sound devices connected (card#1 and card#2). When reading +the available kcontrols for each USB audio device, the following kcontrols lists +the mapped offload card and pcm device indexes for the specific USB device: + + ``USB Offload Playback Card Route PCM#*`` + + ``USB Offload Playback PCM Route PCM#*`` + +The kcontrol is indexed, because a USB audio device could potentially have +several PCM devices. The above kcontrols are defined as: + + - ``USB Offload Playback Card Route PCM#`` **(R)**: Returns the ASoC platform sound + card index for a mapped offload path. The output **"0"** (card index) signifies + that there is an available offload path for the USB SND device through card#0. + If **"-1"** is seen, then no offload path is available for the USB SND device. + This kcontrol exists for each USB audio device that exists in the system, and + its expected to derive the current status of offload based on the output value + for the kcontrol along with the PCM route kcontrol. + + - ``USB Offload Playback PCM Route PCM#`` **(R)**: Returns the ASoC platform sound + PCM device index for a mapped offload path. The output **"1"** (PCM device index) + signifies that there is an available offload path for the USB SND device through + PCM device#0. If **"-1"** is seen, then no offload path is available for the USB\ + SND device. This kcontrol exists for each USB audio device that exists in the + system, and its expected to derive the current status of offload based on the + output value for this kcontrol, in addition to the card route kcontrol. + +USB Offload Playback Route Kcontrol +----------------------------------- +In order to allow for vendor specific implementations on audio offloading device +selection, the SoC USB layer exposes the following: + +.. code-block:: rst + + int (*update_offload_route_info)(struct snd_soc_component *component, + int card, int pcm, int direction, + enum snd_soc_usb_kctl path, + long *route) +.. + +These are specific for the **USB Offload Playback Card Route PCM#** and **USB +Offload PCM Route PCM#** kcontrols. + +When users issue get calls to the kcontrol, the registered SoC USB callbacks will +execute the registered function calls to the DPCM BE DAI link. + +**Callback Registration:** + +.. code-block:: rst + + static int q6usb_component_probe(struct snd_soc_component *component) + { + ... + usb = snd_soc_usb_allocate_port(component, 1, &data->priv); + if (IS_ERR(usb)) + return -ENOMEM; + + usb->connection_status_cb = q6usb_alsa_connection_cb; + usb->update_offload_route_info = q6usb_get_offload_dev; + + ret = snd_soc_usb_add_port(usb); +.. + +Existing USB Sound Kcontrol +--------------------------- +With the introduction of USB offload support, the above USB offload kcontrol +will be added to the pre existing list of kcontrols identified by the USB sound +framework. These kcontrols are still the main controls that are used to +modify characteristics pertaining to the USB audio device. + + :: + + Number of controls: 9 + ctl type num name value + 0 INT 2 Capture Channel Map 0, 0 (range 0->36) + 1 INT 2 Playback Channel Map 0, 0 (range 0->36) + 2 BOOL 1 Headset Capture Switch On + 3 INT 1 Headset Capture Volume 10 (range 0->13) + 4 BOOL 1 Sidetone Playback Switch On + 5 INT 1 Sidetone Playback Volume 4096 (range 0->8192) + 6 BOOL 1 Headset Playback Switch On + 7 INT 2 Headset Playback Volume 20, 20 (range 0->24) + 8 INT 1 USB Offload Playback Card Route PCM#0 0 (range -1->32) + 9 INT 1 USB Offload Playback PCM Route PCM#0 1 (range -1->255) + +Since USB audio device controls are handled over the USB control endpoint, use the +existing mechanisms present in the USB mixer to set parameters, such as volume. -- cgit v1.2.3-59-g8ed1b From 55b5fb369c02177b2f7ea790cec839fc5ec57173 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:53 -0700 Subject: ASoC: dt-bindings: qcom,q6dsp-lpass-ports: Add USB_RX port Q6DSP supports handling of USB playback audio data if USB audio offloading is enabled. Add a new definition for the USB_RX AFE port, which is referenced when the AFE port is started. Acked-by: Krzysztof Kozlowski Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-21-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h index 39f203256c4f..6d1ce7f5da51 100644 --- a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +++ b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h @@ -139,6 +139,7 @@ #define DISPLAY_PORT_RX_5 133 #define DISPLAY_PORT_RX_6 134 #define DISPLAY_PORT_RX_7 135 +#define USB_RX 136 #define LPASS_CLK_ID_PRI_MI2S_IBIT 1 #define LPASS_CLK_ID_PRI_MI2S_EBIT 2 -- cgit v1.2.3-59-g8ed1b From 305da591bd056ae1cdf9fcf32b4abb3c38ba82c2 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:54 -0700 Subject: ASoC: dt-bindings: Update example for enabling USB offload on SM8250 Add an example on enabling of USB offload for the Q6DSP. The routing can be done by the mixer, which can pass the multimedia stream to the USB backend. Acked-by: Rob Herring Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-22-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/sound/qcom,sm8250.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml index b9e33a7429b0..4e208cb7f6c6 100644 --- a/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml @@ -190,4 +190,19 @@ examples: sound-dai = <&vamacro 0>; }; }; + + usb-dai-link { + link-name = "USB Playback"; + cpu { + sound-dai = <&q6afedai USB_RX>; + }; + + codec { + sound-dai = <&usbdai USB_RX>; + }; + + platform { + sound-dai = <&q6routing>; + }; + }; }; -- cgit v1.2.3-59-g8ed1b From 450d63471d1c953d8139ca1d67c05d51310bec2a Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:55 -0700 Subject: ASoC: qcom: qdsp6: Introduce USB AFE port to q6dsp The QC ADSP is able to support USB playback endpoints, so that the main application processor can be placed into lower CPU power modes. This adds the required AFE port configurations and port start command to start an audio session. Specifically, the QC ADSP can support all potential endpoints that are exposed by the audio data interface. This includes isochronous data endpoints, in either synchronous mode or asynchronous mode. In the latter case both implicit or explicit feedback endpoints are supported. The size of audio samples sent per USB frame (microframe) will be adjusted based on information received on the feedback endpoint. Some pre-requisites are needed before issuing the AFE port start command, such as setting the USB AFE dev_token. This carries information about the available USB SND cards and PCM devices that have been discovered on the USB bus. The dev_token field is used by the audio DSP to notify the USB offload driver of which card and PCM index to enable playback on. Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-23-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- sound/soc/qcom/qdsp6/q6afe-dai.c | 60 ++++++++++ sound/soc/qcom/qdsp6/q6afe.c | 190 ++++++++++++++++++++++++++++++- sound/soc/qcom/qdsp6/q6afe.h | 36 +++++- sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c | 23 ++++ sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h | 1 + sound/soc/qcom/qdsp6/q6routing.c | 9 +- 6 files changed, 316 insertions(+), 3 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c index 7d9628cda875..0f47aadaabe1 100644 --- a/sound/soc/qcom/qdsp6/q6afe-dai.c +++ b/sound/soc/qcom/qdsp6/q6afe-dai.c @@ -92,6 +92,39 @@ static int q6hdmi_hw_params(struct snd_pcm_substream *substream, return 0; } +static int q6afe_usb_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + int channels = params_channels(params); + int rate = params_rate(params); + struct q6afe_usb_cfg *usb = &dai_data->port_config[dai->id].usb_audio; + + usb->sample_rate = rate; + usb->num_channels = channels; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_U16_LE: + case SNDRV_PCM_FORMAT_S16_LE: + usb->bit_width = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S24_3LE: + usb->bit_width = 24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + usb->bit_width = 32; + break; + default: + dev_err(dai->dev, "%s: invalid format %d\n", + __func__, params_format(params)); + return -EINVAL; + } + + return 0; +} + static int q6i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -394,6 +427,10 @@ static int q6afe_dai_prepare(struct snd_pcm_substream *substream, q6afe_cdc_dma_port_prepare(dai_data->port[dai->id], &dai_data->port_config[dai->id].dma_cfg); break; + case USB_RX: + q6afe_usb_port_prepare(dai_data->port[dai->id], + &dai_data->port_config[dai->id].usb_audio); + break; default: return -EINVAL; } @@ -622,6 +659,9 @@ static const struct snd_soc_dapm_route q6afe_dapm_routes[] = { {"TX_CODEC_DMA_TX_5", NULL, "TX_CODEC_DMA_TX_5 Capture"}, {"RX_CODEC_DMA_RX_6 Playback", NULL, "RX_CODEC_DMA_RX_6"}, {"RX_CODEC_DMA_RX_7 Playback", NULL, "RX_CODEC_DMA_RX_7"}, + + /* USB playback AFE port receives data for playback, hence use the RX port */ + {"USB Playback", NULL, "USB_RX"}, }; static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai) @@ -649,6 +689,23 @@ static int msm_dai_q6_dai_remove(struct snd_soc_dai *dai) return 0; } +static const struct snd_soc_dai_ops q6afe_usb_ops = { + .probe = msm_dai_q6_dai_probe, + .prepare = q6afe_dai_prepare, + .hw_params = q6afe_usb_hw_params, + /* + * Shutdown callback required to stop the USB AFE port, which is enabled + * by the prepare() stage. This stops the audio traffic on the USB AFE + * port on the Q6DSP. + */ + .shutdown = q6afe_dai_shutdown, + /* + * Startup callback not needed, as AFE port start command passes the PCM + * parameters within the AFE command, which is provided by the PCM core + * during the prepare() stage. + */ +}; + static const struct snd_soc_dai_ops q6hdmi_ops = { .probe = msm_dai_q6_dai_probe, .remove = msm_dai_q6_dai_remove, @@ -947,6 +1004,8 @@ static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = { 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_IN("RX_CODEC_DMA_RX_7", "NULL", 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_IN("USB_RX", NULL, 0, SND_SOC_NOPM, 0, 0), }; static const struct snd_soc_component_driver q6afe_dai_component = { @@ -1061,6 +1120,7 @@ static int q6afe_dai_dev_probe(struct platform_device *pdev) cfg.q6i2s_ops = &q6i2s_ops; cfg.q6tdm_ops = &q6tdm_ops; cfg.q6dma_ops = &q6dma_ops; + cfg.q6usb_ops = &q6afe_usb_ops; dais = q6dsp_audio_ports_set_config(dev, &cfg, &num_dais); return devm_snd_soc_register_component(dev, &q6afe_dai_component, dais, num_dais); diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c index ef7557be5d66..fef0135588f3 100644 --- a/sound/soc/qcom/qdsp6/q6afe.c +++ b/sound/soc/qcom/qdsp6/q6afe.c @@ -35,6 +35,8 @@ #define AFE_MODULE_TDM 0x0001028A #define AFE_PARAM_ID_CDC_SLIMBUS_SLAVE_CFG 0x00010235 +#define AFE_PARAM_ID_USB_AUDIO_DEV_PARAMS 0x000102A5 +#define AFE_PARAM_ID_USB_AUDIO_DEV_LPCM_FMT 0x000102AA #define AFE_PARAM_ID_LPAIF_CLK_CONFIG 0x00010238 #define AFE_PARAM_ID_INT_DIGITAL_CDC_CLK_CONFIG 0x00010239 @@ -44,6 +46,7 @@ #define AFE_PARAM_ID_TDM_CONFIG 0x0001029D #define AFE_PARAM_ID_PORT_SLOT_MAPPING_CONFIG 0x00010297 #define AFE_PARAM_ID_CODEC_DMA_CONFIG 0x000102B8 +#define AFE_PARAM_ID_USB_AUDIO_CONFIG 0x000102A4 #define AFE_CMD_REMOTE_LPASS_CORE_HW_VOTE_REQUEST 0x000100f4 #define AFE_CMD_RSP_REMOTE_LPASS_CORE_HW_VOTE_REQUEST 0x000100f5 #define AFE_CMD_REMOTE_LPASS_CORE_HW_DEVOTE_REQUEST 0x000100f6 @@ -72,12 +75,16 @@ #define AFE_PORT_CONFIG_I2S_WS_SRC_INTERNAL 0x1 #define AFE_LINEAR_PCM_DATA 0x0 +#define AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG 0x1 /* Port IDs */ #define AFE_API_VERSION_HDMI_CONFIG 0x1 #define AFE_PORT_ID_MULTICHAN_HDMI_RX 0x100E #define AFE_PORT_ID_HDMI_OVER_DP_RX 0x6020 +/* USB AFE port */ +#define AFE_PORT_ID_USB_RX 0x7000 + #define AFE_API_VERSION_SLIMBUS_CONFIG 0x1 /* Clock set API version */ #define AFE_API_VERSION_CLOCK_SET 1 @@ -513,12 +520,96 @@ struct afe_param_id_cdc_dma_cfg { u16 active_channels_mask; } __packed; +struct afe_param_id_usb_cfg { +/* Minor version used for tracking USB audio device configuration. + * Supported values: AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG + */ + u32 cfg_minor_version; +/* Sampling rate of the port. + * Supported values: + * - AFE_PORT_SAMPLE_RATE_8K + * - AFE_PORT_SAMPLE_RATE_11025 + * - AFE_PORT_SAMPLE_RATE_12K + * - AFE_PORT_SAMPLE_RATE_16K + * - AFE_PORT_SAMPLE_RATE_22050 + * - AFE_PORT_SAMPLE_RATE_24K + * - AFE_PORT_SAMPLE_RATE_32K + * - AFE_PORT_SAMPLE_RATE_44P1K + * - AFE_PORT_SAMPLE_RATE_48K + * - AFE_PORT_SAMPLE_RATE_96K + * - AFE_PORT_SAMPLE_RATE_192K + */ + u32 sample_rate; +/* Bit width of the sample. + * Supported values: 16, 24 + */ + u16 bit_width; +/* Number of channels. + * Supported values: 1 and 2 + */ + u16 num_channels; +/* Data format supported by the USB. The supported value is + * 0 (#AFE_USB_AUDIO_DATA_FORMAT_LINEAR_PCM). + */ + u16 data_format; +/* this field must be 0 */ + u16 reserved; +/* device token of actual end USB audio device */ + u32 dev_token; +/* endianness of this interface */ + u32 endian; +/* service interval */ + u32 service_interval; +} __packed; + +/** + * struct afe_param_id_usb_audio_dev_params + * @cfg_minor_version: Minor version used for tracking USB audio device + * configuration. + * Supported values: + * AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG + * @dev_token: device token of actual end USB audio device + **/ +struct afe_param_id_usb_audio_dev_params { + u32 cfg_minor_version; + u32 dev_token; +} __packed; + +/** + * struct afe_param_id_usb_audio_dev_lpcm_fmt + * @cfg_minor_version: Minor version used for tracking USB audio device + * configuration. + * Supported values: + * AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG + * @endian: endianness of this interface + **/ +struct afe_param_id_usb_audio_dev_lpcm_fmt { + u32 cfg_minor_version; + u32 endian; +} __packed; + +#define AFE_PARAM_ID_USB_AUDIO_SVC_INTERVAL 0x000102B7 + +/** + * struct afe_param_id_usb_audio_svc_interval + * @cfg_minor_version: Minor version used for tracking USB audio device + * configuration. + * Supported values: + * AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG + * @svc_interval: service interval + **/ +struct afe_param_id_usb_audio_svc_interval { + u32 cfg_minor_version; + u32 svc_interval; +} __packed; + union afe_port_config { struct afe_param_id_hdmi_multi_chan_audio_cfg hdmi_multi_ch; struct afe_param_id_slimbus_cfg slim_cfg; struct afe_param_id_i2s_cfg i2s_cfg; struct afe_param_id_tdm_cfg tdm_cfg; struct afe_param_id_cdc_dma_cfg dma_cfg; + struct afe_param_id_usb_cfg usb_cfg; } __packed; @@ -833,6 +924,7 @@ static struct afe_port_map port_maps[AFE_PORT_MAX] = { RX_CODEC_DMA_RX_6, 1, 1}, [RX_CODEC_DMA_RX_7] = { AFE_PORT_ID_RX_CODEC_DMA_RX_7, RX_CODEC_DMA_RX_7, 1, 1}, + [USB_RX] = { AFE_PORT_ID_USB_RX, USB_RX, 1, 1}, }; static void q6afe_port_free(struct kref *ref) @@ -1290,6 +1382,99 @@ void q6afe_tdm_port_prepare(struct q6afe_port *port, } EXPORT_SYMBOL_GPL(q6afe_tdm_port_prepare); +/** + * afe_port_send_usb_dev_param() - Send USB dev token + * + * @port: Instance of afe port + * @cardidx: USB SND card index to reference + * @pcmidx: USB SND PCM device index to reference + * + * The USB dev token carries information about which USB SND card instance and + * PCM device to execute the offload on. This information is carried through + * to the stream enable QMI request, which is handled by the offload class + * driver. The information is parsed to determine which USB device to query + * the required resources for. + */ +int afe_port_send_usb_dev_param(struct q6afe_port *port, int cardidx, int pcmidx) +{ + struct afe_param_id_usb_audio_dev_params usb_dev; + int ret; + + memset(&usb_dev, 0, sizeof(usb_dev)); + + usb_dev.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG; + usb_dev.dev_token = (cardidx << 16) | (pcmidx << 8); + ret = q6afe_port_set_param_v2(port, &usb_dev, + AFE_PARAM_ID_USB_AUDIO_DEV_PARAMS, + AFE_MODULE_AUDIO_DEV_INTERFACE, + sizeof(usb_dev)); + if (ret) + dev_err(port->afe->dev, "%s: AFE device param cmd failed %d\n", + __func__, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(afe_port_send_usb_dev_param); + +static int afe_port_send_usb_params(struct q6afe_port *port, struct q6afe_usb_cfg *cfg) +{ + union afe_port_config *pcfg = &port->port_cfg; + struct afe_param_id_usb_audio_dev_lpcm_fmt lpcm_fmt; + struct afe_param_id_usb_audio_svc_interval svc_int; + int ret; + + if (!pcfg) { + dev_err(port->afe->dev, "%s: Error, no configuration data\n", __func__); + return -EINVAL; + } + + memset(&lpcm_fmt, 0, sizeof(lpcm_fmt)); + memset(&svc_int, 0, sizeof(svc_int)); + + lpcm_fmt.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG; + lpcm_fmt.endian = pcfg->usb_cfg.endian; + ret = q6afe_port_set_param_v2(port, &lpcm_fmt, + AFE_PARAM_ID_USB_AUDIO_DEV_LPCM_FMT, + AFE_MODULE_AUDIO_DEV_INTERFACE, sizeof(lpcm_fmt)); + if (ret) { + dev_err(port->afe->dev, "%s: AFE device param cmd LPCM_FMT failed %d\n", + __func__, ret); + return ret; + } + + svc_int.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG; + svc_int.svc_interval = pcfg->usb_cfg.service_interval; + ret = q6afe_port_set_param_v2(port, &svc_int, + AFE_PARAM_ID_USB_AUDIO_SVC_INTERVAL, + AFE_MODULE_AUDIO_DEV_INTERFACE, sizeof(svc_int)); + if (ret) + dev_err(port->afe->dev, "%s: AFE device param cmd svc_interval failed %d\n", + __func__, ret); + + return ret; +} + +/** + * q6afe_usb_port_prepare() - Prepare usb afe port. + * + * @port: Instance of afe port + * @cfg: USB configuration for the afe port + * + */ +void q6afe_usb_port_prepare(struct q6afe_port *port, + struct q6afe_usb_cfg *cfg) +{ + union afe_port_config *pcfg = &port->port_cfg; + + pcfg->usb_cfg.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG; + pcfg->usb_cfg.sample_rate = cfg->sample_rate; + pcfg->usb_cfg.num_channels = cfg->num_channels; + pcfg->usb_cfg.bit_width = cfg->bit_width; + + afe_port_send_usb_params(port, cfg); +} +EXPORT_SYMBOL_GPL(q6afe_usb_port_prepare); + /** * q6afe_hdmi_port_prepare() - Prepare hdmi afe port. * @@ -1612,7 +1797,10 @@ struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id) break; case AFE_PORT_ID_WSA_CODEC_DMA_RX_0 ... AFE_PORT_ID_RX_CODEC_DMA_RX_7: cfg_type = AFE_PARAM_ID_CODEC_DMA_CONFIG; - break; + break; + case AFE_PORT_ID_USB_RX: + cfg_type = AFE_PARAM_ID_USB_AUDIO_CONFIG; + break; default: dev_err(dev, "Invalid port id 0x%x\n", port_id); return ERR_PTR(-EINVAL); diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h index 65d0676075e1..a29abe4ce436 100644 --- a/sound/soc/qcom/qdsp6/q6afe.h +++ b/sound/soc/qcom/qdsp6/q6afe.h @@ -3,7 +3,7 @@ #ifndef __Q6AFE_H__ #define __Q6AFE_H__ -#define AFE_PORT_MAX 129 +#define AFE_PORT_MAX 137 #define MSM_AFE_PORT_TYPE_RX 0 #define MSM_AFE_PORT_TYPE_TX 1 @@ -203,6 +203,36 @@ struct q6afe_cdc_dma_cfg { u16 active_channels_mask; }; +/** + * struct q6afe_usb_cfg + * @cfg_minor_version: Minor version used for tracking USB audio device + * configuration. + * Supported values: + * AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG + * @sample_rate: Sampling rate of the port + * Supported values: + * AFE_PORT_SAMPLE_RATE_8K + * AFE_PORT_SAMPLE_RATE_11025 + * AFE_PORT_SAMPLE_RATE_12K + * AFE_PORT_SAMPLE_RATE_16K + * AFE_PORT_SAMPLE_RATE_22050 + * AFE_PORT_SAMPLE_RATE_24K + * AFE_PORT_SAMPLE_RATE_32K + * AFE_PORT_SAMPLE_RATE_44P1K + * AFE_PORT_SAMPLE_RATE_48K + * AFE_PORT_SAMPLE_RATE_96K + * AFE_PORT_SAMPLE_RATE_192K + * @bit_width: Bit width of the sample. + * Supported values: 16, 24 + * @num_channels: Number of channels + * Supported values: 1, 2 + **/ +struct q6afe_usb_cfg { + u32 cfg_minor_version; + u32 sample_rate; + u16 bit_width; + u16 num_channels; +}; struct q6afe_port_config { struct q6afe_hdmi_cfg hdmi; @@ -210,6 +240,7 @@ struct q6afe_port_config { struct q6afe_i2s_cfg i2s_cfg; struct q6afe_tdm_cfg tdm; struct q6afe_cdc_dma_cfg dma_cfg; + struct q6afe_usb_cfg usb_audio; }; struct q6afe_port; @@ -219,6 +250,8 @@ int q6afe_port_start(struct q6afe_port *port); int q6afe_port_stop(struct q6afe_port *port); void q6afe_port_put(struct q6afe_port *port); int q6afe_get_port_id(int index); +void q6afe_usb_port_prepare(struct q6afe_port *port, + struct q6afe_usb_cfg *cfg); void q6afe_hdmi_port_prepare(struct q6afe_port *port, struct q6afe_hdmi_cfg *cfg); void q6afe_slim_port_prepare(struct q6afe_port *port, @@ -228,6 +261,7 @@ void q6afe_tdm_port_prepare(struct q6afe_port *port, struct q6afe_tdm_cfg *cfg); void q6afe_cdc_dma_port_prepare(struct q6afe_port *port, struct q6afe_cdc_dma_cfg *cfg); +int afe_port_send_usb_dev_param(struct q6afe_port *port, int cardidx, int pcmidx); int q6afe_port_set_sysclk(struct q6afe_port *port, int clk_id, int clk_src, int clk_root, unsigned int freq, int dir); diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c index 4919001de08b..4eed54b071a5 100644 --- a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c +++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c @@ -97,6 +97,26 @@ } static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { + { + .playback = { + .stream_name = "USB Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE | + SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .id = USB_RX, + .name = "USB_RX", + }, { .playback = { .stream_name = "HDMI Playback", @@ -624,6 +644,9 @@ struct snd_soc_dai_driver *q6dsp_audio_ports_set_config(struct device *dev, case WSA_CODEC_DMA_RX_0 ... RX_CODEC_DMA_RX_7: q6dsp_audio_fe_dais[i].ops = cfg->q6dma_ops; break; + case USB_RX: + q6dsp_audio_fe_dais[i].ops = cfg->q6usb_ops; + break; default: break; } diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h index 7f052c8a1257..d8dde6dd0aca 100644 --- a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h +++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h @@ -11,6 +11,7 @@ struct q6dsp_audio_port_dai_driver_config { const struct snd_soc_dai_ops *q6i2s_ops; const struct snd_soc_dai_ops *q6tdm_ops; const struct snd_soc_dai_ops *q6dma_ops; + const struct snd_soc_dai_ops *q6usb_ops; }; struct snd_soc_dai_driver *q6dsp_audio_ports_set_config(struct device *dev, diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c index 90228699ba7d..f49243daa517 100644 --- a/sound/soc/qcom/qdsp6/q6routing.c +++ b/sound/soc/qcom/qdsp6/q6routing.c @@ -435,6 +435,7 @@ static struct session_data *get_session_from_id(struct msm_routing_data *data, return NULL; } + /** * q6routing_stream_close() - Deregister a stream * @@ -515,6 +516,9 @@ static int msm_routing_put_audio_mixer(struct snd_kcontrol *kcontrol, return 1; } +static const struct snd_kcontrol_new usb_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(USB_RX) }; + static const struct snd_kcontrol_new hdmi_mixer_controls[] = { Q6ROUTING_RX_MIXERS(HDMI_RX) }; @@ -933,6 +937,9 @@ static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { SND_SOC_DAPM_MIXER("RX_CODEC_DMA_RX_7 Audio Mixer", SND_SOC_NOPM, 0, 0, rx_codec_dma_rx_7_mixer_controls, ARRAY_SIZE(rx_codec_dma_rx_7_mixer_controls)), + SND_SOC_DAPM_MIXER("USB_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + usb_rx_mixer_controls, + ARRAY_SIZE(usb_rx_mixer_controls)), SND_SOC_DAPM_MIXER("MultiMedia1 Mixer", SND_SOC_NOPM, 0, 0, mmul1_mixer_controls, ARRAY_SIZE(mmul1_mixer_controls)), SND_SOC_DAPM_MIXER("MultiMedia2 Mixer", SND_SOC_NOPM, 0, 0, @@ -949,7 +956,6 @@ static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { mmul7_mixer_controls, ARRAY_SIZE(mmul7_mixer_controls)), SND_SOC_DAPM_MIXER("MultiMedia8 Mixer", SND_SOC_NOPM, 0, 0, mmul8_mixer_controls, ARRAY_SIZE(mmul8_mixer_controls)), - }; static const struct snd_soc_dapm_route intercon[] = { @@ -1026,6 +1032,7 @@ static const struct snd_soc_dapm_route intercon[] = { Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_5 Audio Mixer", "RX_CODEC_DMA_RX_5"), Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_6 Audio Mixer", "RX_CODEC_DMA_RX_6"), Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_7 Audio Mixer", "RX_CODEC_DMA_RX_7"), + Q6ROUTING_RX_DAPM_ROUTE("USB_RX Audio Mixer", "USB_RX"), Q6ROUTING_TX_DAPM_ROUTE("MultiMedia1 Mixer"), Q6ROUTING_TX_DAPM_ROUTE("MultiMedia2 Mixer"), Q6ROUTING_TX_DAPM_ROUTE("MultiMedia3 Mixer"), -- cgit v1.2.3-59-g8ed1b From 3aafa53515b052ce353a0fc931f7fb188b229ceb Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:56 -0700 Subject: ASoC: qcom: qdsp6: q6afe: Increase APR timeout For USB offloading situations, the AFE port start command will result in a QMI handshake between the Q6DSP and the main processor. Depending on if the USB bus is suspended, this routine would require more time to complete, as resuming the USB bus has some overhead associated with it. Increase the timeout to 3s to allow for sufficient time for the USB QMI stream enable handshake to complete. Reviewed-by: Pierre-Louis Bossart Reviewed-by: Srinivas Kandagatla Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-24-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- sound/soc/qcom/qdsp6/q6afe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c index fef0135588f3..7b59d514b432 100644 --- a/sound/soc/qcom/qdsp6/q6afe.c +++ b/sound/soc/qcom/qdsp6/q6afe.c @@ -366,7 +366,7 @@ #define AFE_API_VERSION_SLOT_MAPPING_CONFIG 1 #define AFE_API_VERSION_CODEC_DMA_CONFIG 1 -#define TIMEOUT_MS 1000 +#define TIMEOUT_MS 3000 #define AFE_CMD_RESP_AVAIL 0 #define AFE_CMD_RESP_NONE 1 #define AFE_CLK_TOKEN 1024 -- cgit v1.2.3-59-g8ed1b From 72b0b8b2998043c50b191d9668c7a828ca0d5c70 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:57 -0700 Subject: ASoC: qcom: qdsp6: Add USB backend ASoC driver for Q6 Create a USB BE component that will register a new USB port to the ASoC USB framework. This will handle determination on if the requested audio profile is supported by the USB device currently selected. Check for if the PCM format is supported during the hw_params callback. If the profile is not supported then the userspace ALSA entity will receive an error, and can take further action. Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-25-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- include/sound/q6usboffload.h | 20 +++ sound/soc/qcom/Kconfig | 12 ++ sound/soc/qcom/qdsp6/Makefile | 1 + sound/soc/qcom/qdsp6/q6usb.c | 280 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 313 insertions(+) create mode 100644 include/sound/q6usboffload.h create mode 100644 sound/soc/qcom/qdsp6/q6usb.c diff --git a/include/sound/q6usboffload.h b/include/sound/q6usboffload.h new file mode 100644 index 000000000000..f7e2449fe1b3 --- /dev/null +++ b/include/sound/q6usboffload.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * sound/q6usboffload.h -- QDSP6 USB offload + * + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +/** + * struct q6usb_offload - USB backend DAI link offload parameters + * @dev: dev handle to usb be + * @domain: allocated iommu domain + * @intr_num: usb interrupter number + * @sid: streamID for iommu + **/ +struct q6usb_offload { + struct device *dev; + struct iommu_domain *domain; + u16 intr_num; + u8 sid; +}; diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index ca7a30ebd26a..1b4f3faadbc7 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -118,6 +118,18 @@ config SND_SOC_QDSP6_PRM tristate select SND_SOC_QDSP6_PRM_LPASS_CLOCKS +config SND_SOC_QDSP6_USB + tristate "SoC ALSA USB offloading backing for QDSP6" + depends on SND_SOC_USB + select AUXILIARY_BUS + + help + Adds support for USB offloading for QDSP6 ASoC + based platform sound cards. This will enable the + Q6USB DPCM backend DAI link, which will interact + with the SoC USB framework to initialize a session + with active USB SND devices. + config SND_SOC_QDSP6 tristate "SoC ALSA audio driver for QDSP6" depends on QCOM_APR diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index 26b7c55c9c11..67267304e7e9 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_SND_SOC_QDSP6_APM_DAI) += q6apm-dai.o obj-$(CONFIG_SND_SOC_QDSP6_APM_LPASS_DAI) += q6apm-lpass-dais.o obj-$(CONFIG_SND_SOC_QDSP6_PRM) += q6prm.o obj-$(CONFIG_SND_SOC_QDSP6_PRM_LPASS_CLOCKS) += q6prm-clocks.o +obj-$(CONFIG_SND_SOC_QDSP6_USB) += q6usb.o diff --git a/sound/soc/qcom/qdsp6/q6usb.c b/sound/soc/qcom/qdsp6/q6usb.c new file mode 100644 index 000000000000..99848a320784 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6usb.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "q6afe.h" +#include "q6dsp-lpass-ports.h" + +#define Q6_USB_SID_MASK 0xF + +struct q6usb_port_data { + struct auxiliary_device uauxdev; + struct q6afe_usb_cfg usb_cfg; + struct snd_soc_usb *usb; + struct q6usb_offload priv; + + /* Protects against operations between SOC USB and ASoC */ + struct mutex mutex; + struct list_head devices; +}; + +static const struct snd_soc_dapm_widget q6usb_dai_widgets[] = { + SND_SOC_DAPM_HP("USB_RX_BE", NULL), +}; + +static const struct snd_soc_dapm_route q6usb_dapm_routes[] = { + {"USB Playback", NULL, "USB_RX_BE"}, +}; + +static int q6usb_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct q6usb_port_data *data = dev_get_drvdata(dai->dev); + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + int direction = substream->stream; + struct q6afe_port *q6usb_afe; + struct snd_soc_usb_device *sdev; + int ret = -EINVAL; + + mutex_lock(&data->mutex); + + /* No active chip index */ + if (list_empty(&data->devices)) + goto out; + + sdev = list_last_entry(&data->devices, struct snd_soc_usb_device, list); + + ret = snd_soc_usb_find_supported_format(sdev->chip_idx, params, direction); + if (ret < 0) + goto out; + + q6usb_afe = q6afe_port_get_from_id(cpu_dai->dev, USB_RX); + if (IS_ERR(q6usb_afe)) + goto out; + + /* Notify audio DSP about the devices being offloaded */ + ret = afe_port_send_usb_dev_param(q6usb_afe, sdev->card_idx, + sdev->ppcm_idx[sdev->num_playback - 1]); + +out: + mutex_unlock(&data->mutex); + + return ret; +} + +static const struct snd_soc_dai_ops q6usb_ops = { + .hw_params = q6usb_hw_params, +}; + +static struct snd_soc_dai_driver q6usb_be_dais[] = { + { + .playback = { + .stream_name = "USB BE RX", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE | + SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE, + .channels_min = 1, + .channels_max = 2, + .rate_max = 192000, + .rate_min = 8000, + }, + .id = USB_RX, + .name = "USB_RX_BE", + .ops = &q6usb_ops, + }, +}; + +static int q6usb_audio_ports_of_xlate_dai_name(struct snd_soc_component *component, + const struct of_phandle_args *args, + const char **dai_name) +{ + int id = args->args[0]; + int ret = -EINVAL; + int i; + + for (i = 0; i < ARRAY_SIZE(q6usb_be_dais); i++) { + if (q6usb_be_dais[i].id == id) { + *dai_name = q6usb_be_dais[i].name; + ret = 0; + break; + } + } + + return ret; +} + +static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb, + struct snd_soc_usb_device *sdev, bool connected) +{ + struct q6usb_port_data *data; + + if (!usb->component) + return -ENODEV; + + data = dev_get_drvdata(usb->component->dev); + + mutex_lock(&data->mutex); + if (connected) { + /* Selects the latest USB headset plugged in for offloading */ + list_add_tail(&sdev->list, &data->devices); + } else { + list_del(&sdev->list); + } + mutex_unlock(&data->mutex); + + return 0; +} + +static void q6usb_dai_aux_release(struct device *dev) {} + +static int q6usb_dai_add_aux_device(struct q6usb_port_data *data, + struct auxiliary_device *auxdev) +{ + int ret; + + auxdev->dev.parent = data->priv.dev; + auxdev->dev.release = q6usb_dai_aux_release; + auxdev->name = "qc-usb-audio-offload"; + + ret = auxiliary_device_init(auxdev); + if (ret) + return ret; + + ret = auxiliary_device_add(auxdev); + if (ret) + auxiliary_device_uninit(auxdev); + + return ret; +} + +static int q6usb_component_probe(struct snd_soc_component *component) +{ + struct q6usb_port_data *data = dev_get_drvdata(component->dev); + struct snd_soc_usb *usb; + int ret; + + /* Add the QC USB SND aux device */ + ret = q6usb_dai_add_aux_device(data, &data->uauxdev); + if (ret < 0) + return ret; + + usb = snd_soc_usb_allocate_port(component, &data->priv); + if (IS_ERR(usb)) + return -ENOMEM; + + usb->connection_status_cb = q6usb_alsa_connection_cb; + + snd_soc_usb_add_port(usb); + data->usb = usb; + + return 0; +} + +static void q6usb_component_remove(struct snd_soc_component *component) +{ + struct q6usb_port_data *data = dev_get_drvdata(component->dev); + + snd_soc_usb_remove_port(data->usb); + auxiliary_device_delete(&data->uauxdev); + auxiliary_device_uninit(&data->uauxdev); + snd_soc_usb_free_port(data->usb); +} + +static const struct snd_soc_component_driver q6usb_dai_component = { + .probe = q6usb_component_probe, + .remove = q6usb_component_remove, + .name = "q6usb-dai-component", + .dapm_widgets = q6usb_dai_widgets, + .num_dapm_widgets = ARRAY_SIZE(q6usb_dai_widgets), + .dapm_routes = q6usb_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(q6usb_dapm_routes), + .of_xlate_dai_name = q6usb_audio_ports_of_xlate_dai_name, +}; + +static int q6usb_dai_dev_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct q6usb_port_data *data; + struct device *dev = &pdev->dev; + struct of_phandle_args args; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + ret = of_property_read_u16(node, "qcom,usb-audio-intr-idx", + &data->priv.intr_num); + if (ret) { + dev_err(&pdev->dev, "failed to read intr idx.\n"); + return ret; + } + + ret = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args); + if (!ret) + data->priv.sid = args.args[0] & Q6_USB_SID_MASK; + + ret = devm_mutex_init(dev, &data->mutex); + if (ret < 0) + return ret; + + data->priv.domain = iommu_get_domain_for_dev(&pdev->dev); + + data->priv.dev = dev; + INIT_LIST_HEAD(&data->devices); + dev_set_drvdata(dev, data); + + return devm_snd_soc_register_component(dev, &q6usb_dai_component, + q6usb_be_dais, ARRAY_SIZE(q6usb_be_dais)); +} + +static const struct of_device_id q6usb_dai_device_id[] = { + { .compatible = "qcom,q6usb" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6usb_dai_device_id); + +static struct platform_driver q6usb_dai_platform_driver = { + .driver = { + .name = "q6usb-dai", + .of_match_table = of_match_ptr(q6usb_dai_device_id), + }, + .probe = q6usb_dai_dev_probe, + /* + * Remove not required as resources are cleaned up as part of + * component removal. Others are device managed resources. + */ +}; +module_platform_driver(q6usb_dai_platform_driver); + +MODULE_DESCRIPTION("Q6 USB backend dai driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-59-g8ed1b From 1b8d0d87b934f002a06a8ec2abfe907bd12e8cd4 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:58 -0700 Subject: ASoC: qcom: qdsp6: Add headphone jack for offload connection status The headphone jack framework has a well defined infrastructure for notifying userspace entities through input devices. Expose a jack device that carries information about if an offload capable device is connected. Applications can further identify specific offloading information through other SND kcontrols. Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-26-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- sound/soc/qcom/Kconfig | 4 +++ sound/soc/qcom/Makefile | 2 ++ sound/soc/qcom/qdsp6/q6usb.c | 41 ++++++++++++++++++++++++++++ sound/soc/qcom/sm8250.c | 24 +++++++++++++++- sound/soc/qcom/usb_offload_utils.c | 56 ++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/usb_offload_utils.h | 30 ++++++++++++++++++++ 6 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 sound/soc/qcom/usb_offload_utils.c create mode 100644 sound/soc/qcom/usb_offload_utils.h diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index 1b4f3faadbc7..e86b4a03dd61 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -118,10 +118,14 @@ config SND_SOC_QDSP6_PRM tristate select SND_SOC_QDSP6_PRM_LPASS_CLOCKS +config SND_SOC_QCOM_OFFLOAD_UTILS + tristate + config SND_SOC_QDSP6_USB tristate "SoC ALSA USB offloading backing for QDSP6" depends on SND_SOC_USB select AUXILIARY_BUS + select SND_SOC_QCOM_OFFLOAD_UTILS help Adds support for USB offloading for QDSP6 ASoC diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile index 16db7b53ddac..985ce2ae286b 100644 --- a/sound/soc/qcom/Makefile +++ b/sound/soc/qcom/Makefile @@ -30,6 +30,7 @@ snd-soc-sc8280xp-y := sc8280xp.o snd-soc-qcom-common-y := common.o snd-soc-qcom-sdw-y := sdw.o snd-soc-x1e80100-y := x1e80100.o +snd-soc-qcom-offload-utils-objs := usb_offload_utils.o obj-$(CONFIG_SND_SOC_STORM) += snd-soc-storm.o obj-$(CONFIG_SND_SOC_APQ8016_SBC) += snd-soc-apq8016-sbc.o @@ -42,6 +43,7 @@ obj-$(CONFIG_SND_SOC_SM8250) += snd-soc-sm8250.o obj-$(CONFIG_SND_SOC_QCOM_COMMON) += snd-soc-qcom-common.o obj-$(CONFIG_SND_SOC_QCOM_SDW) += snd-soc-qcom-sdw.o obj-$(CONFIG_SND_SOC_X1E80100) += snd-soc-x1e80100.o +obj-$(CONFIG_SND_SOC_QCOM_OFFLOAD_UTILS) += snd-soc-qcom-offload-utils.o #DSP lib obj-$(CONFIG_SND_SOC_QDSP6) += qdsp6/ diff --git a/sound/soc/qcom/qdsp6/q6usb.c b/sound/soc/qcom/qdsp6/q6usb.c index 99848a320784..6634e132787e 100644 --- a/sound/soc/qcom/qdsp6/q6usb.c +++ b/sound/soc/qcom/qdsp6/q6usb.c @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -32,6 +33,7 @@ struct q6usb_port_data { struct auxiliary_device uauxdev; struct q6afe_usb_cfg usb_cfg; struct snd_soc_usb *usb; + struct snd_soc_jack *hs_jack; struct q6usb_offload priv; /* Protects against operations between SOC USB and ASoC */ @@ -144,16 +146,54 @@ static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb, mutex_lock(&data->mutex); if (connected) { + if (data->hs_jack) + snd_jack_report(data->hs_jack->jack, SND_JACK_USB); + /* Selects the latest USB headset plugged in for offloading */ list_add_tail(&sdev->list, &data->devices); } else { list_del(&sdev->list); + + if (data->hs_jack) + snd_jack_report(data->hs_jack->jack, 0); } mutex_unlock(&data->mutex); return 0; } +static void q6usb_component_disable_jack(struct q6usb_port_data *data) +{ + /* Offload jack has already been disabled */ + if (!data->hs_jack) + return; + + snd_jack_report(data->hs_jack->jack, 0); + data->hs_jack = NULL; +} + +static void q6usb_component_enable_jack(struct q6usb_port_data *data, + struct snd_soc_jack *jack) +{ + snd_jack_report(jack->jack, !list_empty(&data->devices) ? SND_JACK_USB : 0); + data->hs_jack = jack; +} + +static int q6usb_component_set_jack(struct snd_soc_component *component, + struct snd_soc_jack *jack, void *priv) +{ + struct q6usb_port_data *data = dev_get_drvdata(component->dev); + + mutex_lock(&data->mutex); + if (jack) + q6usb_component_enable_jack(data, jack); + else + q6usb_component_disable_jack(data); + mutex_unlock(&data->mutex); + + return 0; +} + static void q6usb_dai_aux_release(struct device *dev) {} static int q6usb_dai_add_aux_device(struct q6usb_port_data *data, @@ -211,6 +251,7 @@ static void q6usb_component_remove(struct snd_soc_component *component) static const struct snd_soc_component_driver q6usb_dai_component = { .probe = q6usb_component_probe, + .set_jack = q6usb_component_set_jack, .remove = q6usb_component_remove, .name = "q6usb-dai-component", .dapm_widgets = q6usb_dai_widgets, diff --git a/sound/soc/qcom/sm8250.c b/sound/soc/qcom/sm8250.c index 9039107972e2..b70b2a5031df 100644 --- a/sound/soc/qcom/sm8250.c +++ b/sound/soc/qcom/sm8250.c @@ -13,6 +13,7 @@ #include #include "qdsp6/q6afe.h" #include "common.h" +#include "usb_offload_utils.h" #include "sdw.h" #define DRIVER_NAME "sm8250" @@ -23,14 +24,34 @@ struct sm8250_snd_data { struct snd_soc_card *card; struct sdw_stream_runtime *sruntime[AFE_PORT_MAX]; struct snd_soc_jack jack; + struct snd_soc_jack usb_offload_jack; + bool usb_offload_jack_setup; bool jack_setup; }; static int sm8250_snd_init(struct snd_soc_pcm_runtime *rtd) { struct sm8250_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + int ret; + + if (cpu_dai->id == USB_RX) + ret = qcom_snd_usb_offload_jack_setup(rtd, &data->usb_offload_jack, + &data->usb_offload_jack_setup); + else + ret = qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); + return ret; +} + +static void sm8250_snd_exit(struct snd_soc_pcm_runtime *rtd) +{ + struct sm8250_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + + if (cpu_dai->id == USB_RX) + qcom_snd_usb_offload_jack_remove(rtd, + &data->usb_offload_jack_setup); - return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); } static int sm8250_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, @@ -148,6 +169,7 @@ static void sm8250_add_be_ops(struct snd_soc_card *card) for_each_card_prelinks(card, i, link) { if (link->no_pcm == 1) { link->init = sm8250_snd_init; + link->exit = sm8250_snd_exit; link->be_hw_params_fixup = sm8250_be_hw_params_fixup; link->ops = &sm8250_be_ops; } diff --git a/sound/soc/qcom/usb_offload_utils.c b/sound/soc/qcom/usb_offload_utils.c new file mode 100644 index 000000000000..0a24b278fcdf --- /dev/null +++ b/sound/soc/qcom/usb_offload_utils.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ +#include +#include +#include +#include + +#include "usb_offload_utils.h" + +int qcom_snd_usb_offload_jack_setup(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_jack *jack, bool *jack_setup) +{ + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + int ret = 0; + + if (cpu_dai->id != USB_RX) + return -EINVAL; + + if (!*jack_setup) { + ret = snd_soc_usb_setup_offload_jack(codec_dai->component, jack); + if (ret) + return ret; + } + + *jack_setup = true; + + return 0; +} +EXPORT_SYMBOL_GPL(qcom_snd_usb_offload_jack_setup); + +int qcom_snd_usb_offload_jack_remove(struct snd_soc_pcm_runtime *rtd, + bool *jack_setup) +{ + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + int ret = 0; + + if (cpu_dai->id != USB_RX) + return -EINVAL; + + if (*jack_setup) { + ret = snd_soc_component_set_jack(codec_dai->component, NULL, NULL); + if (ret) + return ret; + } + + *jack_setup = false; + + return 0; +} +EXPORT_SYMBOL_GPL(qcom_snd_usb_offload_jack_remove); +MODULE_DESCRIPTION("ASoC Q6 USB offload controls"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/qcom/usb_offload_utils.h b/sound/soc/qcom/usb_offload_utils.h new file mode 100644 index 000000000000..c3f411f565b0 --- /dev/null +++ b/sound/soc/qcom/usb_offload_utils.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ +#ifndef __QCOM_SND_USB_OFFLOAD_UTILS_H__ +#define __QCOM_SND_USB_OFFLOAD_UTILS_H__ + +#include + +#if IS_ENABLED(CONFIG_SND_SOC_QCOM_OFFLOAD_UTILS) +int qcom_snd_usb_offload_jack_setup(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_jack *jack, bool *jack_setup); + +int qcom_snd_usb_offload_jack_remove(struct snd_soc_pcm_runtime *rtd, + bool *jack_setup); +#else +static inline int qcom_snd_usb_offload_jack_setup(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_jack *jack, + bool *jack_setup) +{ + return -ENODEV; +} + +static inline int qcom_snd_usb_offload_jack_remove(struct snd_soc_pcm_runtime *rtd, + bool *jack_setup) +{ + return -ENODEV; +} +#endif /* IS_ENABLED(CONFIG_SND_SOC_QCOM_OFFLOAD_UTILS) */ +#endif /* __QCOM_SND_USB_OFFLOAD_UTILS_H__ */ -- cgit v1.2.3-59-g8ed1b From e0dd9240f13a8ea3e9fa77b547826c22e1337e7f Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:47:59 -0700 Subject: ASoC: qcom: qdsp6: Fetch USB offload mapped card and PCM device The USB SND path may need to know how the USB offload path is routed, so that applications can open the proper sound card and PCM device. The implementation for the QC ASoC design has a "USB Mixer" kcontrol for each possible FE (Q6ASM) DAI, which can be utilized to know which front end link is enabled. When an application/userspace queries for the mapped offload devices, the logic will lookup the USB mixer status though the following path: MultiMedia* <-> MM_DL* <-> USB Mixer* The "USB Mixer" is a DAPM widget, and the q6routing entity will set the DAPM connect status accordingly if the USB mixer is enabled. If enabled, the Q6USB backend link can fetch the PCM device number from the FE DAI link (Multimedia*). With respects to the card number, that is straightforward, as the ASoC components have direct references to the ASoC platform sound card. An example output can be shown below: Number of controls: 9 name value Capture Channel Map 0, 0 (range 0->36) Playback Channel Map 0, 0 (range 0->36) Headset Capture Switch On Headset Capture Volume 1 (range 0->4) Sidetone Playback Switch On Sidetone Playback Volume 4096 (range 0->8192) Headset Playback Switch On Headset Playback Volume 20, 20 (range 0->24) USB Offload Playback Route PCM#0 0, 1 (range -1->255) The "USB Offload Playback Route PCM#*" kcontrol will signify the corresponding card and pcm device it is offload to. (card#0 pcm - device#1) If the USB SND device supports multiple audio interfaces, then it will contain several PCM streams, hence in those situations, it is expected that there will be multiple playback route kcontrols created. Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-27-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- sound/soc/qcom/qdsp6/q6usb.c | 98 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/sound/soc/qcom/qdsp6/q6usb.c b/sound/soc/qcom/qdsp6/q6usb.c index 6634e132787e..274c251e84dd 100644 --- a/sound/soc/qcom/qdsp6/q6usb.c +++ b/sound/soc/qcom/qdsp6/q6usb.c @@ -134,6 +134,103 @@ static int q6usb_audio_ports_of_xlate_dai_name(struct snd_soc_component *compone return ret; } +static int q6usb_get_pcm_id_from_widget(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *dai; + + for_each_card_rtds(w->dapm->card, rtd) { + dai = snd_soc_rtd_to_cpu(rtd, 0); + /* + * Only look for playback widget. RTD number carries the assigned + * PCM index. + */ + if (dai->stream[0].widget == w) + return rtd->id; + } + + return -1; +} + +static int q6usb_usb_mixer_enabled(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dapm_path *p; + + /* Checks to ensure USB path is enabled/connected */ + snd_soc_dapm_widget_for_each_sink_path(w, p) + if (!strcmp(p->sink->name, "USB Mixer") && p->connect) + return 1; + + return 0; +} + +static int q6usb_get_pcm_id(struct snd_soc_component *component) +{ + struct snd_soc_dapm_widget *w; + struct snd_soc_dapm_path *p; + int pidx; + + /* + * Traverse widgets to find corresponding FE widget. The DAI links are + * built like the following: + * MultiMedia* <-> MM_DL* <-> USB Mixer* + */ + for_each_card_widgets(component->card, w) { + if (!strncmp(w->name, "MultiMedia", 10)) { + /* + * Look up all paths associated with the FE widget to see if + * the USB BE is enabled. The sink widget is responsible to + * link with the USB mixers. + */ + snd_soc_dapm_widget_for_each_sink_path(w, p) { + if (q6usb_usb_mixer_enabled(p->sink)) { + pidx = q6usb_get_pcm_id_from_widget(w); + return pidx; + } + } + } + } + + return -1; +} + +static int q6usb_update_offload_route(struct snd_soc_component *component, int card, + int pcm, int direction, enum snd_soc_usb_kctl path, + long *route) +{ + struct q6usb_port_data *data = dev_get_drvdata(component->dev); + struct snd_soc_usb_device *sdev; + int ret = 0; + int idx = -1; + + mutex_lock(&data->mutex); + + if (list_empty(&data->devices) || + direction == SNDRV_PCM_STREAM_CAPTURE) { + ret = -ENODEV; + goto out; + } + + sdev = list_last_entry(&data->devices, struct snd_soc_usb_device, list); + + /* + * Will always look for last PCM device discovered/probed as the + * active offload index. + */ + if (card == sdev->card_idx && + pcm == sdev->ppcm_idx[sdev->num_playback - 1]) { + idx = path == SND_SOC_USB_KCTL_CARD_ROUTE ? + component->card->snd_card->number : + q6usb_get_pcm_id(component); + } + +out: + route[0] = idx; + mutex_unlock(&data->mutex); + + return ret; +} + static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb, struct snd_soc_usb_device *sdev, bool connected) { @@ -232,6 +329,7 @@ static int q6usb_component_probe(struct snd_soc_component *component) return -ENOMEM; usb->connection_status_cb = q6usb_alsa_connection_cb; + usb->update_offload_route_info = q6usb_update_offload_route; snd_soc_usb_add_port(usb); data->usb = usb; -- cgit v1.2.3-59-g8ed1b From bd1979b9d3fcbba6632550e9d86d9acac481884f Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:48:00 -0700 Subject: ALSA: usb-audio: qcom: Add USB QMI definitions The Qualcomm USB audio offload driver utilizes the QMI protocol to communicate with the audio DSP. Add the necessary QMI header and field definitions, so the QMI interface driver is able to route the QMI packet received to the USB audio offload driver. Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-28-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- sound/usb/qcom/usb_audio_qmi_v01.c | 863 +++++++++++++++++++++++++++++++++++++ sound/usb/qcom/usb_audio_qmi_v01.h | 164 +++++++ 2 files changed, 1027 insertions(+) create mode 100644 sound/usb/qcom/usb_audio_qmi_v01.c create mode 100644 sound/usb/qcom/usb_audio_qmi_v01.h diff --git a/sound/usb/qcom/usb_audio_qmi_v01.c b/sound/usb/qcom/usb_audio_qmi_v01.c new file mode 100644 index 000000000000..151ae7b591de --- /dev/null +++ b/sound/usb/qcom/usb_audio_qmi_v01.c @@ -0,0 +1,863 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include + +#include "usb_audio_qmi_v01.h" + +static const struct qmi_elem_info mem_info_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(u64), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct mem_info_v01, va), + }, + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(u64), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct mem_info_v01, pa), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct mem_info_v01, size), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static const struct qmi_elem_info apps_mem_info_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct mem_info_v01), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct apps_mem_info_v01, evt_ring), + .ei_array = mem_info_v01_ei, + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct mem_info_v01), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct apps_mem_info_v01, tr_data), + .ei_array = mem_info_v01_ei, + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct mem_info_v01), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct apps_mem_info_v01, tr_sync), + .ei_array = mem_info_v01_ei, + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct mem_info_v01), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct apps_mem_info_v01, xfer_buff), + .ei_array = mem_info_v01_ei, + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct mem_info_v01), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct apps_mem_info_v01, dcba), + .ei_array = mem_info_v01_ei, + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static const struct qmi_elem_info usb_endpoint_descriptor_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_endpoint_descriptor_v01, + bLength), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_endpoint_descriptor_v01, + bDescriptorType), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_endpoint_descriptor_v01, + bEndpointAddress), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_endpoint_descriptor_v01, + bmAttributes), + }, + { + .data_type = QMI_UNSIGNED_2_BYTE, + .elem_len = 1, + .elem_size = sizeof(u16), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_endpoint_descriptor_v01, + wMaxPacketSize), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_endpoint_descriptor_v01, + bInterval), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_endpoint_descriptor_v01, + bRefresh), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_endpoint_descriptor_v01, + bSynchAddress), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static const struct qmi_elem_info usb_interface_descriptor_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_interface_descriptor_v01, + bLength), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_interface_descriptor_v01, + bDescriptorType), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_interface_descriptor_v01, + bInterfaceNumber), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_interface_descriptor_v01, + bAlternateSetting), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_interface_descriptor_v01, + bNumEndpoints), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_interface_descriptor_v01, + bInterfaceClass), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_interface_descriptor_v01, + bInterfaceSubClass), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_interface_descriptor_v01, + bInterfaceProtocol), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct usb_interface_descriptor_v01, + iInterface), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +const struct qmi_elem_info qmi_uaudio_stream_req_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + enable), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + usb_token), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + audio_format_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + audio_format), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + number_of_ch_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + number_of_ch), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + bit_rate_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + bit_rate), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + xfer_buff_size_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + xfer_buff_size), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + service_interval_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct qmi_uaudio_stream_req_msg_v01, + service_interval), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +const struct qmi_elem_info qmi_uaudio_stream_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + status_valid), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum usb_qmi_audio_stream_status_enum_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + status), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + internal_status_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + internal_status), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + slot_id_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + slot_id), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + usb_token_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + usb_token), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + std_as_opr_intf_desc_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct usb_interface_descriptor_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + std_as_opr_intf_desc), + .ei_array = usb_interface_descriptor_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x15, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + std_as_data_ep_desc_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct usb_endpoint_descriptor_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x15, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + std_as_data_ep_desc), + .ei_array = usb_endpoint_descriptor_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x16, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + std_as_sync_ep_desc_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct usb_endpoint_descriptor_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x16, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + std_as_sync_ep_desc), + .ei_array = usb_endpoint_descriptor_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x17, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + usb_audio_spec_revision_valid), + }, + { + .data_type = QMI_UNSIGNED_2_BYTE, + .elem_len = 1, + .elem_size = sizeof(u16), + .array_type = NO_ARRAY, + .tlv_type = 0x17, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + usb_audio_spec_revision), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x18, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + data_path_delay_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x18, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + data_path_delay), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x19, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + usb_audio_subslot_size_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x19, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + usb_audio_subslot_size), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x1A, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + xhci_mem_info_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct apps_mem_info_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x1A, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + xhci_mem_info), + .ei_array = apps_mem_info_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x1B, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + interrupter_num_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x1B, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + interrupter_num), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x1C, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + speed_info_valid), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum usb_qmi_audio_device_speed_enum_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x1C, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + speed_info), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x1D, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + controller_num_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x1D, + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, + controller_num), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +const struct qmi_elem_info qmi_uaudio_stream_ind_msg_v01_ei[] = { + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof( + enum usb_qmi_audio_device_indication_enum_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + dev_event), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + slot_id), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + usb_token_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + usb_token), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + std_as_opr_intf_desc_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct usb_interface_descriptor_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + std_as_opr_intf_desc), + .ei_array = usb_interface_descriptor_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + std_as_data_ep_desc_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct usb_endpoint_descriptor_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + std_as_data_ep_desc), + .ei_array = usb_endpoint_descriptor_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + std_as_sync_ep_desc_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct usb_endpoint_descriptor_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + std_as_sync_ep_desc), + .ei_array = usb_endpoint_descriptor_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + usb_audio_spec_revision_valid), + }, + { + .data_type = QMI_UNSIGNED_2_BYTE, + .elem_len = 1, + .elem_size = sizeof(u16), + .array_type = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + usb_audio_spec_revision), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x15, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + data_path_delay_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x15, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + data_path_delay), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x16, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + usb_audio_subslot_size_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x16, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + usb_audio_subslot_size), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x17, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + xhci_mem_info_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct apps_mem_info_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x17, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + xhci_mem_info), + .ei_array = apps_mem_info_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x18, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + interrupter_num_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x18, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + interrupter_num), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x19, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + controller_num_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x19, + .offset = offsetof(struct qmi_uaudio_stream_ind_msg_v01, + controller_num), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; diff --git a/sound/usb/qcom/usb_audio_qmi_v01.h b/sound/usb/qcom/usb_audio_qmi_v01.h new file mode 100644 index 000000000000..f2563537ed40 --- /dev/null +++ b/sound/usb/qcom/usb_audio_qmi_v01.h @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef USB_QMI_V01_H +#define USB_QMI_V01_H + +#define UAUDIO_STREAM_SERVICE_ID_V01 0x41D +#define UAUDIO_STREAM_SERVICE_VERS_V01 0x01 + +#define QMI_UAUDIO_STREAM_RESP_V01 0x0001 +#define QMI_UAUDIO_STREAM_REQ_V01 0x0001 +#define QMI_UAUDIO_STREAM_IND_V01 0x0001 + +struct mem_info_v01 { + u64 va; + u64 pa; + u32 size; +}; + +struct apps_mem_info_v01 { + struct mem_info_v01 evt_ring; + struct mem_info_v01 tr_data; + struct mem_info_v01 tr_sync; + struct mem_info_v01 xfer_buff; + struct mem_info_v01 dcba; +}; + +struct usb_endpoint_descriptor_v01 { + u8 bLength; + u8 bDescriptorType; + u8 bEndpointAddress; + u8 bmAttributes; + u16 wMaxPacketSize; + u8 bInterval; + u8 bRefresh; + u8 bSynchAddress; +}; + +struct usb_interface_descriptor_v01 { + u8 bLength; + u8 bDescriptorType; + u8 bInterfaceNumber; + u8 bAlternateSetting; + u8 bNumEndpoints; + u8 bInterfaceClass; + u8 bInterfaceSubClass; + u8 bInterfaceProtocol; + u8 iInterface; +}; + +enum usb_qmi_audio_stream_status_enum_v01 { + USB_QMI_STREAM_STATUS_ENUM_MIN_VAL_V01 = INT_MIN, + USB_QMI_STREAM_REQ_SUCCESS_V01 = 0, + USB_QMI_STREAM_REQ_FAILURE_V01 = 1, + USB_QMI_STREAM_REQ_FAILURE_NOT_FOUND_V01 = 2, + USB_QMI_STREAM_REQ_FAILURE_INVALID_PARAM_V01 = 3, + USB_QMI_STREAM_REQ_FAILURE_MEMALLOC_V01 = 4, + USB_QMI_STREAM_STATUS_ENUM_MAX_VAL_V01 = INT_MAX, +}; + +enum usb_qmi_audio_device_indication_enum_v01 { + USB_QMI_DEVICE_INDICATION_ENUM_MIN_VAL_V01 = INT_MIN, + USB_QMI_DEV_CONNECT_V01 = 0, + USB_QMI_DEV_DISCONNECT_V01 = 1, + USB_QMI_DEV_SUSPEND_V01 = 2, + USB_QMI_DEV_RESUME_V01 = 3, + USB_QMI_DEVICE_INDICATION_ENUM_MAX_VAL_V01 = INT_MAX, +}; + +enum usb_qmi_audio_device_speed_enum_v01 { + USB_QMI_DEVICE_SPEED_ENUM_MIN_VAL_V01 = INT_MIN, + USB_QMI_DEVICE_SPEED_INVALID_V01 = 0, + USB_QMI_DEVICE_SPEED_LOW_V01 = 1, + USB_QMI_DEVICE_SPEED_FULL_V01 = 2, + USB_QMI_DEVICE_SPEED_HIGH_V01 = 3, + USB_QMI_DEVICE_SPEED_SUPER_V01 = 4, + USB_QMI_DEVICE_SPEED_SUPER_PLUS_V01 = 5, + USB_QMI_DEVICE_SPEED_ENUM_MAX_VAL_V01 = INT_MAX, +}; + +struct qmi_uaudio_stream_req_msg_v01 { + u8 enable; + u32 usb_token; + u8 audio_format_valid; + u32 audio_format; + u8 number_of_ch_valid; + u32 number_of_ch; + u8 bit_rate_valid; + u32 bit_rate; + u8 xfer_buff_size_valid; + u32 xfer_buff_size; + u8 service_interval_valid; + u32 service_interval; +}; + +#define QMI_UAUDIO_STREAM_REQ_MSG_V01_MAX_MSG_LEN 46 +extern const struct qmi_elem_info qmi_uaudio_stream_req_msg_v01_ei[]; + +struct qmi_uaudio_stream_resp_msg_v01 { + struct qmi_response_type_v01 resp; + u8 status_valid; + enum usb_qmi_audio_stream_status_enum_v01 status; + u8 internal_status_valid; + u32 internal_status; + u8 slot_id_valid; + u32 slot_id; + u8 usb_token_valid; + u32 usb_token; + u8 std_as_opr_intf_desc_valid; + struct usb_interface_descriptor_v01 std_as_opr_intf_desc; + u8 std_as_data_ep_desc_valid; + struct usb_endpoint_descriptor_v01 std_as_data_ep_desc; + u8 std_as_sync_ep_desc_valid; + struct usb_endpoint_descriptor_v01 std_as_sync_ep_desc; + u8 usb_audio_spec_revision_valid; + u16 usb_audio_spec_revision; + u8 data_path_delay_valid; + u8 data_path_delay; + u8 usb_audio_subslot_size_valid; + u8 usb_audio_subslot_size; + u8 xhci_mem_info_valid; + struct apps_mem_info_v01 xhci_mem_info; + u8 interrupter_num_valid; + u8 interrupter_num; + u8 speed_info_valid; + enum usb_qmi_audio_device_speed_enum_v01 speed_info; + u8 controller_num_valid; + u8 controller_num; +}; + +#define QMI_UAUDIO_STREAM_RESP_MSG_V01_MAX_MSG_LEN 202 +extern const struct qmi_elem_info qmi_uaudio_stream_resp_msg_v01_ei[]; + +struct qmi_uaudio_stream_ind_msg_v01 { + enum usb_qmi_audio_device_indication_enum_v01 dev_event; + u32 slot_id; + u8 usb_token_valid; + u32 usb_token; + u8 std_as_opr_intf_desc_valid; + struct usb_interface_descriptor_v01 std_as_opr_intf_desc; + u8 std_as_data_ep_desc_valid; + struct usb_endpoint_descriptor_v01 std_as_data_ep_desc; + u8 std_as_sync_ep_desc_valid; + struct usb_endpoint_descriptor_v01 std_as_sync_ep_desc; + u8 usb_audio_spec_revision_valid; + u16 usb_audio_spec_revision; + u8 data_path_delay_valid; + u8 data_path_delay; + u8 usb_audio_subslot_size_valid; + u8 usb_audio_subslot_size; + u8 xhci_mem_info_valid; + struct apps_mem_info_v01 xhci_mem_info; + u8 interrupter_num_valid; + u8 interrupter_num; + u8 controller_num_valid; + u8 controller_num; +}; + +#define QMI_UAUDIO_STREAM_IND_MSG_V01_MAX_MSG_LEN 181 +extern const struct qmi_elem_info qmi_uaudio_stream_ind_msg_v01_ei[]; + +#endif -- cgit v1.2.3-59-g8ed1b From 326bbc348298ab0946c5560defe024a5f6ef28bb Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:48:01 -0700 Subject: ALSA: usb-audio: qcom: Introduce QC USB SND offloading support Several Qualcomm SoCs have a dedicated audio DSP, which has the ability to support USB sound devices. This vendor driver will implement the required handshaking with the DSP, in order to pass along required resources that will be utilized by the DSP's USB SW. The communication channel used for this handshaking will be using the QMI protocol. Required resources include: - Allocated secondary event ring address - EP transfer ring address - Interrupter number The above information will allow for the audio DSP to execute USB transfers over the USB bus. It will also be able to support devices that have an implicit feedback and sync endpoint as well. Offloading these data transfers will allow the main/applications processor to enter lower CPU power modes, and sustain a longer duration in those modes. Audio offloading is initiated with the following sequence: 1. Userspace configures to route audio playback to USB backend and starts playback on the platform soundcard. 2. The Q6DSP AFE will communicate to the audio DSP to start the USB AFE port. 3. This results in a QMI packet with a STREAM enable command. 4. The QC audio offload driver will fetch the required resources, and pass this information as part of the QMI response to the STREAM enable command. 5. Once the QMI response is received the audio DSP will start queuing data on the USB bus. As part of step#2, the audio DSP is aware of the USB SND card and pcm device index that is being selected, and is communicated as part of the QMI request received by QC audio offload. These indices will be used to handle the stream enable QMI request. Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-29-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- sound/usb/Kconfig | 14 + sound/usb/Makefile | 2 +- sound/usb/qcom/Makefile | 2 + sound/usb/qcom/qc_audio_offload.c | 1987 +++++++++++++++++++++++++++++++++++++ 4 files changed, 2004 insertions(+), 1 deletion(-) create mode 100644 sound/usb/qcom/Makefile create mode 100644 sound/usb/qcom/qc_audio_offload.c diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig index 4a9569a3a39a..6daa551738da 100644 --- a/sound/usb/Kconfig +++ b/sound/usb/Kconfig @@ -176,6 +176,20 @@ config SND_BCD2000 To compile this driver as a module, choose M here: the module will be called snd-bcd2000. +config SND_USB_AUDIO_QMI + tristate "Qualcomm Audio Offload driver" + depends on QCOM_QMI_HELPERS && SND_USB_AUDIO && USB_XHCI_SIDEBAND && SND_SOC_USB + help + Say Y here to enable the Qualcomm USB audio offloading feature. + + This module sets up the required QMI stream enable/disable + responses to requests generated by the audio DSP. It passes the + USB transfer resource references, so that the audio DSP can issue + USB transfers to the host controller. + + To compile this driver as a module, choose M here: the module + will be called snd-usb-audio-qmi. + source "sound/usb/line6/Kconfig" endif # SND_USB diff --git a/sound/usb/Makefile b/sound/usb/Makefile index 30bd5348477b..e62794a87e73 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -35,5 +35,5 @@ obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o -obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/ bcd2000/ +obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/ bcd2000/ qcom/ obj-$(CONFIG_SND_USB_LINE6) += line6/ diff --git a/sound/usb/qcom/Makefile b/sound/usb/qcom/Makefile new file mode 100644 index 000000000000..1eb51160e2e5 --- /dev/null +++ b/sound/usb/qcom/Makefile @@ -0,0 +1,2 @@ +snd-usb-audio-qmi-y := usb_audio_qmi_v01.o qc_audio_offload.o +obj-$(CONFIG_SND_USB_AUDIO_QMI) += snd-usb-audio-qmi.o diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c new file mode 100644 index 000000000000..87e74c949370 --- /dev/null +++ b/sound/usb/qcom/qc_audio_offload.c @@ -0,0 +1,1987 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../usbaudio.h" +#include "../card.h" +#include "../endpoint.h" +#include "../format.h" +#include "../helper.h" +#include "../pcm.h" +#include "../power.h" + +#include "usb_audio_qmi_v01.h" + +/* Stream disable request timeout during USB device disconnect */ +#define DEV_RELEASE_WAIT_TIMEOUT 10000 /* in ms */ + +/* Data interval calculation parameters */ +#define BUS_INTERVAL_FULL_SPEED 1000 /* in us */ +#define BUS_INTERVAL_HIGHSPEED_AND_ABOVE 125 /* in us */ +#define MAX_BINTERVAL_ISOC_EP 16 + +#define QMI_STREAM_REQ_CARD_NUM_MASK 0xffff0000 +#define QMI_STREAM_REQ_DEV_NUM_MASK 0xff00 +#define QMI_STREAM_REQ_DIRECTION 0xff + +/* iommu resource parameters and management */ +#define PREPEND_SID_TO_IOVA(iova, sid) ((u64)(((u64)(iova)) | \ + (((u64)sid) << 32))) +#define IOVA_MASK(iova) (((u64)(iova)) & 0xFFFFFFFF) +#define IOVA_BASE 0x1000 +#define IOVA_XFER_RING_BASE (IOVA_BASE + PAGE_SIZE * (SNDRV_CARDS + 1)) +#define IOVA_XFER_BUF_BASE (IOVA_XFER_RING_BASE + PAGE_SIZE * SNDRV_CARDS * 32) +#define IOVA_XFER_RING_MAX (IOVA_XFER_BUF_BASE - PAGE_SIZE) +#define IOVA_XFER_BUF_MAX (0xfffff000 - PAGE_SIZE) + +#define MAX_XFER_BUFF_LEN (24 * PAGE_SIZE) + +struct iova_info { + struct list_head list; + unsigned long start_iova; + size_t size; + bool in_use; +}; + +struct intf_info { + /* IOMMU ring/buffer mapping information */ + unsigned long data_xfer_ring_va; + size_t data_xfer_ring_size; + unsigned long sync_xfer_ring_va; + size_t sync_xfer_ring_size; + unsigned long xfer_buf_va; + size_t xfer_buf_size; + phys_addr_t xfer_buf_pa; + u8 *xfer_buf; + + /* USB endpoint information */ + unsigned int data_ep_pipe; + unsigned int sync_ep_pipe; + unsigned int data_ep_idx; + unsigned int sync_ep_idx; + + u8 intf_num; + u8 pcm_card_num; + u8 pcm_dev_num; + u8 direction; + bool in_use; +}; + +struct uaudio_qmi_dev { + struct device *dev; + struct q6usb_offload *data; + struct auxiliary_device *auxdev; + + /* list to keep track of available iova */ + struct list_head xfer_ring_list; + size_t xfer_ring_iova_size; + unsigned long curr_xfer_ring_iova; + struct list_head xfer_buf_list; + size_t xfer_buf_iova_size; + unsigned long curr_xfer_buf_iova; + + /* bit fields representing pcm card enabled */ + unsigned long card_slot; + /* indicate event ring mapped or not */ + bool er_mapped; +}; + +struct uaudio_dev { + struct usb_device *udev; + /* audio control interface */ + struct usb_host_interface *ctrl_intf; + unsigned int usb_core_id; + atomic_t in_use; + struct kref kref; + wait_queue_head_t disconnect_wq; + + /* interface specific */ + int num_intf; + struct intf_info *info; + struct snd_usb_audio *chip; + + /* xhci sideband */ + struct xhci_sideband *sb; + + /* SoC USB device */ + struct snd_soc_usb_device *sdev; +}; + +static struct uaudio_dev uadev[SNDRV_CARDS]; +static struct uaudio_qmi_dev *uaudio_qdev; +static struct uaudio_qmi_svc *uaudio_svc; +static DEFINE_MUTEX(qdev_mutex); + +struct uaudio_qmi_svc { + struct qmi_handle *uaudio_svc_hdl; + struct sockaddr_qrtr client_sq; + bool client_connected; +}; + +enum mem_type { + MEM_EVENT_RING, + MEM_XFER_RING, + MEM_XFER_BUF, +}; + +/* Supported audio formats */ +enum usb_qmi_audio_format { + USB_QMI_PCM_FORMAT_S8 = 0, + USB_QMI_PCM_FORMAT_U8, + USB_QMI_PCM_FORMAT_S16_LE, + USB_QMI_PCM_FORMAT_S16_BE, + USB_QMI_PCM_FORMAT_U16_LE, + USB_QMI_PCM_FORMAT_U16_BE, + USB_QMI_PCM_FORMAT_S24_LE, + USB_QMI_PCM_FORMAT_S24_BE, + USB_QMI_PCM_FORMAT_U24_LE, + USB_QMI_PCM_FORMAT_U24_BE, + USB_QMI_PCM_FORMAT_S24_3LE, + USB_QMI_PCM_FORMAT_S24_3BE, + USB_QMI_PCM_FORMAT_U24_3LE, + USB_QMI_PCM_FORMAT_U24_3BE, + USB_QMI_PCM_FORMAT_S32_LE, + USB_QMI_PCM_FORMAT_S32_BE, + USB_QMI_PCM_FORMAT_U32_LE, + USB_QMI_PCM_FORMAT_U32_BE, +}; + +static int usb_qmi_get_pcm_num(struct snd_usb_audio *chip, int direction) +{ + struct snd_usb_substream *subs = NULL; + struct snd_usb_stream *as; + int count = 0; + + list_for_each_entry(as, &chip->pcm_list, list) { + subs = &as->substream[direction]; + if (subs->ep_num) + count++; + } + + return count; +} + +static enum usb_qmi_audio_device_speed_enum_v01 +get_speed_info(enum usb_device_speed udev_speed) +{ + switch (udev_speed) { + case USB_SPEED_LOW: + return USB_QMI_DEVICE_SPEED_LOW_V01; + case USB_SPEED_FULL: + return USB_QMI_DEVICE_SPEED_FULL_V01; + case USB_SPEED_HIGH: + return USB_QMI_DEVICE_SPEED_HIGH_V01; + case USB_SPEED_SUPER: + return USB_QMI_DEVICE_SPEED_SUPER_V01; + case USB_SPEED_SUPER_PLUS: + return USB_QMI_DEVICE_SPEED_SUPER_PLUS_V01; + default: + return USB_QMI_DEVICE_SPEED_INVALID_V01; + } +} + +static struct snd_usb_substream *find_substream(unsigned int card_num, + unsigned int pcm_idx, + unsigned int direction) +{ + struct snd_usb_substream *subs = NULL; + struct snd_usb_audio *chip; + struct snd_usb_stream *as; + + chip = uadev[card_num].chip; + if (!chip || atomic_read(&chip->shutdown)) + goto done; + + if (pcm_idx >= chip->pcm_devs) + goto done; + + if (direction > SNDRV_PCM_STREAM_CAPTURE) + goto done; + + list_for_each_entry(as, &chip->pcm_list, list) { + if (as->pcm_index == pcm_idx) { + subs = &as->substream[direction]; + goto done; + } + } + +done: + return subs; +} + +static int info_idx_from_ifnum(int card_num, int intf_num, bool enable) +{ + int i; + + /* + * default index 0 is used when info is allocated upon + * first enable audio stream req for a pcm device + */ + if (enable && !uadev[card_num].info) + return 0; + + for (i = 0; i < uadev[card_num].num_intf; i++) { + if (enable && !uadev[card_num].info[i].in_use) + return i; + else if (!enable && + uadev[card_num].info[i].intf_num == intf_num) + return i; + } + + return -EINVAL; +} + +static int get_data_interval_from_si(struct snd_usb_substream *subs, + u32 service_interval) +{ + unsigned int bus_intval_mult; + unsigned int bus_intval; + unsigned int binterval; + + if (subs->dev->speed >= USB_SPEED_HIGH) + bus_intval = BUS_INTERVAL_HIGHSPEED_AND_ABOVE; + else + bus_intval = BUS_INTERVAL_FULL_SPEED; + + if (service_interval % bus_intval) + return -EINVAL; + + bus_intval_mult = service_interval / bus_intval; + binterval = ffs(bus_intval_mult); + if (!binterval || binterval > MAX_BINTERVAL_ISOC_EP) + return -EINVAL; + + /* check if another bit is set then bail out */ + bus_intval_mult = bus_intval_mult >> binterval; + if (bus_intval_mult) + return -EINVAL; + + return (binterval - 1); +} + +/* maps audio format received over QMI to asound.h based pcm format */ +static snd_pcm_format_t map_pcm_format(enum usb_qmi_audio_format fmt_received) +{ + switch (fmt_received) { + case USB_QMI_PCM_FORMAT_S8: + return SNDRV_PCM_FORMAT_S8; + case USB_QMI_PCM_FORMAT_U8: + return SNDRV_PCM_FORMAT_U8; + case USB_QMI_PCM_FORMAT_S16_LE: + return SNDRV_PCM_FORMAT_S16_LE; + case USB_QMI_PCM_FORMAT_S16_BE: + return SNDRV_PCM_FORMAT_S16_BE; + case USB_QMI_PCM_FORMAT_U16_LE: + return SNDRV_PCM_FORMAT_U16_LE; + case USB_QMI_PCM_FORMAT_U16_BE: + return SNDRV_PCM_FORMAT_U16_BE; + case USB_QMI_PCM_FORMAT_S24_LE: + return SNDRV_PCM_FORMAT_S24_LE; + case USB_QMI_PCM_FORMAT_S24_BE: + return SNDRV_PCM_FORMAT_S24_BE; + case USB_QMI_PCM_FORMAT_U24_LE: + return SNDRV_PCM_FORMAT_U24_LE; + case USB_QMI_PCM_FORMAT_U24_BE: + return SNDRV_PCM_FORMAT_U24_BE; + case USB_QMI_PCM_FORMAT_S24_3LE: + return SNDRV_PCM_FORMAT_S24_3LE; + case USB_QMI_PCM_FORMAT_S24_3BE: + return SNDRV_PCM_FORMAT_S24_3BE; + case USB_QMI_PCM_FORMAT_U24_3LE: + return SNDRV_PCM_FORMAT_U24_3LE; + case USB_QMI_PCM_FORMAT_U24_3BE: + return SNDRV_PCM_FORMAT_U24_3BE; + case USB_QMI_PCM_FORMAT_S32_LE: + return SNDRV_PCM_FORMAT_S32_LE; + case USB_QMI_PCM_FORMAT_S32_BE: + return SNDRV_PCM_FORMAT_S32_BE; + case USB_QMI_PCM_FORMAT_U32_LE: + return SNDRV_PCM_FORMAT_U32_LE; + case USB_QMI_PCM_FORMAT_U32_BE: + return SNDRV_PCM_FORMAT_U32_BE; + default: + /* + * We expect the caller to do input validation so we should + * never hit this. But we do have to return a proper + * snd_pcm_format_t value due to the __bitwise attribute; so + * just return the equivalent of 0 in case of bad input. + */ + return SNDRV_PCM_FORMAT_S8; + } +} + +/* + * Sends QMI disconnect indication message, assumes chip->mutex and qdev_mutex + * lock held by caller. + */ +static int uaudio_send_disconnect_ind(struct snd_usb_audio *chip) +{ + struct qmi_uaudio_stream_ind_msg_v01 disconnect_ind = {0}; + struct uaudio_qmi_svc *svc = uaudio_svc; + struct uaudio_dev *dev; + int ret = 0; + + dev = &uadev[chip->card->number]; + + if (atomic_read(&dev->in_use)) { + mutex_unlock(&chip->mutex); + mutex_unlock(&qdev_mutex); + dev_dbg(uaudio_qdev->data->dev, "sending qmi indication suspend\n"); + disconnect_ind.dev_event = USB_QMI_DEV_DISCONNECT_V01; + disconnect_ind.slot_id = dev->udev->slot_id; + disconnect_ind.controller_num = dev->usb_core_id; + disconnect_ind.controller_num_valid = 1; + ret = qmi_send_indication(svc->uaudio_svc_hdl, &svc->client_sq, + QMI_UAUDIO_STREAM_IND_V01, + QMI_UAUDIO_STREAM_IND_MSG_V01_MAX_MSG_LEN, + qmi_uaudio_stream_ind_msg_v01_ei, + &disconnect_ind); + if (ret < 0) + dev_err(uaudio_qdev->data->dev, + "qmi send failed with err: %d\n", ret); + + ret = wait_event_interruptible_timeout(dev->disconnect_wq, + !atomic_read(&dev->in_use), + msecs_to_jiffies(DEV_RELEASE_WAIT_TIMEOUT)); + if (!ret) { + dev_err(uaudio_qdev->data->dev, + "timeout while waiting for dev_release\n"); + atomic_set(&dev->in_use, 0); + } else if (ret < 0) { + dev_err(uaudio_qdev->data->dev, + "failed with ret %d\n", ret); + atomic_set(&dev->in_use, 0); + } + mutex_lock(&qdev_mutex); + mutex_lock(&chip->mutex); + } + + return ret; +} + +/* Offloading IOMMU management */ +static unsigned long uaudio_get_iova(unsigned long *curr_iova, + size_t *curr_iova_size, + struct list_head *head, size_t size) +{ + struct iova_info *info, *new_info = NULL; + struct list_head *curr_head; + size_t tmp_size = size; + unsigned long va = 0; + + if (size % PAGE_SIZE) + goto done; + + if (size > *curr_iova_size) + goto done; + + if (*curr_iova_size == 0) + goto done; + + list_for_each_entry(info, head, list) { + /* exact size iova_info */ + if (!info->in_use && info->size == size) { + info->in_use = true; + va = info->start_iova; + *curr_iova_size -= size; + goto done; + } else if (!info->in_use && tmp_size >= info->size) { + if (!new_info) + new_info = info; + tmp_size -= info->size; + if (tmp_size) + continue; + + va = new_info->start_iova; + for (curr_head = &new_info->list; curr_head != + &info->list; curr_head = curr_head->next) { + new_info = list_entry(curr_head, struct + iova_info, list); + new_info->in_use = true; + } + info->in_use = true; + *curr_iova_size -= size; + goto done; + } else { + /* iova region in use */ + new_info = NULL; + tmp_size = size; + } + } + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + va = 0; + goto done; + } + + va = *curr_iova; + info->start_iova = *curr_iova; + info->size = size; + info->in_use = true; + *curr_iova += size; + *curr_iova_size -= size; + list_add_tail(&info->list, head); + +done: + return va; +} + +static void uaudio_put_iova(unsigned long va, size_t size, struct list_head + *head, size_t *curr_iova_size) +{ + struct iova_info *info; + size_t tmp_size = size; + bool found = false; + + list_for_each_entry(info, head, list) { + if (info->start_iova == va) { + if (!info->in_use) + return; + + found = true; + info->in_use = false; + if (info->size == size) + goto done; + } + + if (found && tmp_size >= info->size) { + info->in_use = false; + tmp_size -= info->size; + if (!tmp_size) + goto done; + } + } + + if (!found) + return; + +done: + *curr_iova_size += size; +} + +/** + * uaudio_iommu_unmap() - unmaps iommu memory for adsp + * @mtype: ring type + * @va: virtual address to unmap + * @iova_size: region size + * @mapped_iova_size: mapped region size + * + * Unmaps the memory region that was previously assigned to the adsp. + * + */ +static void uaudio_iommu_unmap(enum mem_type mtype, unsigned long va, + size_t iova_size, size_t mapped_iova_size) +{ + size_t umap_size; + bool unmap = true; + + if (!va || !iova_size) + return; + + switch (mtype) { + case MEM_EVENT_RING: + if (uaudio_qdev->er_mapped) + uaudio_qdev->er_mapped = false; + else + unmap = false; + break; + + case MEM_XFER_RING: + uaudio_put_iova(va, iova_size, &uaudio_qdev->xfer_ring_list, + &uaudio_qdev->xfer_ring_iova_size); + break; + case MEM_XFER_BUF: + uaudio_put_iova(va, iova_size, &uaudio_qdev->xfer_buf_list, + &uaudio_qdev->xfer_buf_iova_size); + break; + default: + unmap = false; + } + + if (!unmap || !mapped_iova_size) + return; + + umap_size = iommu_unmap(uaudio_qdev->data->domain, va, mapped_iova_size); + if (umap_size != mapped_iova_size) + dev_err(uaudio_qdev->data->dev, + "unmapped size %zu for iova 0x%08lx of mapped size %zu\n", + umap_size, va, mapped_iova_size); +} + +/** + * uaudio_iommu_map() - maps iommu memory for adsp + * @mtype: ring type + * @dma_coherent: dma coherent + * @pa: physical address for ring/buffer + * @size: size of memory region + * @sgt: sg table for memory region + * + * Maps the XHCI related resources to a memory region that is assigned to be + * used by the adsp. This will be mapped to the domain, which is created by + * the ASoC USB backend driver. + * + */ +static unsigned long uaudio_iommu_map(enum mem_type mtype, bool dma_coherent, + phys_addr_t pa, size_t size, + struct sg_table *sgt) +{ + struct scatterlist *sg; + unsigned long va = 0; + size_t total_len = 0; + unsigned long va_sg; + phys_addr_t pa_sg; + bool map = true; + size_t sg_len; + int prot; + int ret; + int i; + + prot = IOMMU_READ | IOMMU_WRITE; + + if (dma_coherent) + prot |= IOMMU_CACHE; + + switch (mtype) { + case MEM_EVENT_RING: + va = IOVA_BASE; + /* er already mapped */ + if (uaudio_qdev->er_mapped) + map = false; + break; + case MEM_XFER_RING: + va = uaudio_get_iova(&uaudio_qdev->curr_xfer_ring_iova, + &uaudio_qdev->xfer_ring_iova_size, + &uaudio_qdev->xfer_ring_list, size); + break; + case MEM_XFER_BUF: + va = uaudio_get_iova(&uaudio_qdev->curr_xfer_buf_iova, + &uaudio_qdev->xfer_buf_iova_size, + &uaudio_qdev->xfer_buf_list, size); + break; + default: + dev_err(uaudio_qdev->data->dev, "unknown mem type %d\n", mtype); + } + + if (!va || !map) + goto done; + + if (!sgt) + goto skip_sgt_map; + + va_sg = va; + for_each_sg(sgt->sgl, sg, sgt->nents, i) { + sg_len = PAGE_ALIGN(sg->offset + sg->length); + pa_sg = page_to_phys(sg_page(sg)); + ret = iommu_map(uaudio_qdev->data->domain, va_sg, pa_sg, sg_len, + prot, GFP_KERNEL); + if (ret) { + uaudio_iommu_unmap(MEM_XFER_BUF, va, size, total_len); + va = 0; + goto done; + } + + va_sg += sg_len; + total_len += sg_len; + } + + if (size != total_len) { + uaudio_iommu_unmap(MEM_XFER_BUF, va, size, total_len); + va = 0; + } + return va; + +skip_sgt_map: + iommu_map(uaudio_qdev->data->domain, va, pa, size, prot, GFP_KERNEL); + +done: + return va; +} + +/* looks up alias, if any, for controller DT node and returns the index */ +static int usb_get_controller_id(struct usb_device *udev) +{ + if (udev->bus->sysdev && udev->bus->sysdev->of_node) + return of_alias_get_id(udev->bus->sysdev->of_node, "usb"); + + return -ENODEV; +} + +/** + * uaudio_dev_intf_cleanup() - cleanup transfer resources + * @udev: usb device + * @info: usb offloading interface + * + * Cleans up the transfer ring related resources which are assigned per + * endpoint from XHCI. This is invoked when the USB endpoints are no + * longer in use by the adsp. + * + */ +static void uaudio_dev_intf_cleanup(struct usb_device *udev, struct intf_info *info) +{ + uaudio_iommu_unmap(MEM_XFER_RING, info->data_xfer_ring_va, + info->data_xfer_ring_size, info->data_xfer_ring_size); + info->data_xfer_ring_va = 0; + info->data_xfer_ring_size = 0; + + uaudio_iommu_unmap(MEM_XFER_RING, info->sync_xfer_ring_va, + info->sync_xfer_ring_size, info->sync_xfer_ring_size); + info->sync_xfer_ring_va = 0; + info->sync_xfer_ring_size = 0; + + uaudio_iommu_unmap(MEM_XFER_BUF, info->xfer_buf_va, info->xfer_buf_size, + info->xfer_buf_size); + info->xfer_buf_va = 0; + + usb_free_coherent(udev, info->xfer_buf_size, info->xfer_buf, + info->xfer_buf_pa); + info->xfer_buf_size = 0; + info->xfer_buf = NULL; + info->xfer_buf_pa = 0; + + info->in_use = false; +} + +/** + * uaudio_event_ring_cleanup_free() - cleanup secondary event ring + * @dev: usb offload device + * + * Cleans up the secondary event ring that was requested. This will + * occur when the adsp is no longer transferring data on the USB bus + * across all endpoints. + * + */ +static void uaudio_event_ring_cleanup_free(struct uaudio_dev *dev) +{ + clear_bit(dev->chip->card->number, &uaudio_qdev->card_slot); + /* all audio devices are disconnected */ + if (!uaudio_qdev->card_slot) { + uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, PAGE_SIZE, + PAGE_SIZE); + xhci_sideband_remove_interrupter(uadev[dev->chip->card->number].sb); + } +} + +static void uaudio_dev_cleanup(struct uaudio_dev *dev) +{ + int if_idx; + + if (!dev->udev) + return; + + /* free xfer buffer and unmap xfer ring and buf per interface */ + for (if_idx = 0; if_idx < dev->num_intf; if_idx++) { + if (!dev->info[if_idx].in_use) + continue; + uaudio_dev_intf_cleanup(dev->udev, &dev->info[if_idx]); + dev_dbg(uaudio_qdev->data->dev, + "release resources: intf# %d card# %d\n", + dev->info[if_idx].intf_num, dev->chip->card->number); + } + + dev->num_intf = 0; + + /* free interface info */ + kfree(dev->info); + dev->info = NULL; + uaudio_event_ring_cleanup_free(dev); + dev->udev = NULL; +} + +/** + * disable_audio_stream() - disable usb snd endpoints + * @subs: usb substream + * + * Closes the USB SND endpoints associated with the current audio stream + * used. This will decrement the USB SND endpoint opened reference count. + * + */ +static void disable_audio_stream(struct snd_usb_substream *subs) +{ + struct snd_usb_audio *chip = subs->stream->chip; + + snd_usb_hw_free(subs); + snd_usb_autosuspend(chip); +} + +/* QMI service disconnect handlers */ +static void qmi_stop_session(void) +{ + struct snd_usb_substream *subs; + struct usb_host_endpoint *ep; + struct snd_usb_audio *chip; + struct intf_info *info; + int pcm_card_num; + int if_idx; + int idx; + + mutex_lock(&qdev_mutex); + /* find all active intf for set alt 0 and cleanup usb audio dev */ + for (idx = 0; idx < SNDRV_CARDS; idx++) { + if (!atomic_read(&uadev[idx].in_use)) + continue; + + chip = uadev[idx].chip; + for (if_idx = 0; if_idx < uadev[idx].num_intf; if_idx++) { + if (!uadev[idx].info || !uadev[idx].info[if_idx].in_use) + continue; + info = &uadev[idx].info[if_idx]; + pcm_card_num = info->pcm_card_num; + subs = find_substream(pcm_card_num, info->pcm_dev_num, + info->direction); + if (!subs || !chip || atomic_read(&chip->shutdown)) { + dev_err(&subs->dev->dev, + "no sub for c#%u dev#%u dir%u\n", + info->pcm_card_num, + info->pcm_dev_num, + info->direction); + continue; + } + /* Release XHCI endpoints */ + if (info->data_ep_pipe) + ep = usb_pipe_endpoint(uadev[pcm_card_num].udev, + info->data_ep_pipe); + xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb, ep); + + if (info->sync_ep_pipe) + ep = usb_pipe_endpoint(uadev[pcm_card_num].udev, + info->sync_ep_pipe); + xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb, ep); + + disable_audio_stream(subs); + } + atomic_set(&uadev[idx].in_use, 0); + mutex_lock(&chip->mutex); + uaudio_dev_cleanup(&uadev[idx]); + mutex_unlock(&chip->mutex); + } + mutex_unlock(&qdev_mutex); +} + +/** + * uaudio_sideband_notifier() - xHCI sideband event handler + * @intf: USB interface handle + * @evt: xHCI sideband event type + * + * This callback is executed when the xHCI sideband encounters a sequence + * that requires the sideband clients to take action. An example, is when + * xHCI frees the transfer ring, so the client has to ensure that the + * offload path is halted. + * + */ +static int uaudio_sideband_notifier(struct usb_interface *intf, + struct xhci_sideband_event *evt) +{ + struct snd_usb_audio *chip; + struct uaudio_dev *dev; + int if_idx; + + if (!intf || !evt) + return 0; + + chip = usb_get_intfdata(intf); + + mutex_lock(&qdev_mutex); + mutex_lock(&chip->mutex); + + dev = &uadev[chip->card->number]; + + if (evt->type == XHCI_SIDEBAND_XFER_RING_FREE) { + unsigned int *ep = (unsigned int *) evt->evt_data; + + for (if_idx = 0; if_idx < dev->num_intf; if_idx++) { + if (dev->info[if_idx].data_ep_idx == *ep || + dev->info[if_idx].sync_ep_idx == *ep) + uaudio_send_disconnect_ind(chip); + } + } + + mutex_unlock(&qdev_mutex); + mutex_unlock(&chip->mutex); + + return 0; +} + +/** + * qmi_bye_cb() - qmi bye message callback + * @handle: QMI handle + * @node: id of the dying node + * + * This callback is invoked when the QMI bye control message is received + * from the QMI client. Handle the message accordingly by ensuring that + * the USB offload path is disabled and cleaned up. At this point, ADSP + * is not utilizing the USB bus. + * + */ +static void qmi_bye_cb(struct qmi_handle *handle, unsigned int node) +{ + struct uaudio_qmi_svc *svc = uaudio_svc; + + if (svc->uaudio_svc_hdl != handle) + return; + + if (svc->client_connected && svc->client_sq.sq_node == node) { + qmi_stop_session(); + + /* clear QMI client parameters to block further QMI messages */ + svc->client_sq.sq_node = 0; + svc->client_sq.sq_port = 0; + svc->client_sq.sq_family = 0; + svc->client_connected = false; + } +} + +/** + * qmi_svc_disconnect_cb() - qmi client disconnected + * @handle: QMI handle + * @node: id of the dying node + * @port: port of the dying client + * + * Invoked when the remote QMI client is disconnected. Handle this event + * the same way as when the QMI bye message is received. This will ensure + * the USB offloading path is disabled and cleaned up. + * + */ +static void qmi_svc_disconnect_cb(struct qmi_handle *handle, + unsigned int node, unsigned int port) +{ + struct uaudio_qmi_svc *svc; + + if (!uaudio_svc) + return; + + svc = uaudio_svc; + if (svc->uaudio_svc_hdl != handle) + return; + + if (svc->client_connected && svc->client_sq.sq_node == node && + svc->client_sq.sq_port == port) { + qmi_stop_session(); + + /* clear QMI client parameters to block further QMI messages */ + svc->client_sq.sq_node = 0; + svc->client_sq.sq_port = 0; + svc->client_sq.sq_family = 0; + svc->client_connected = false; + } +} + +/* QMI client callback handlers from QMI interface */ +static struct qmi_ops uaudio_svc_ops_options = { + .bye = qmi_bye_cb, + .del_client = qmi_svc_disconnect_cb, +}; + +/* kref release callback when all streams are disabled */ +static void uaudio_dev_release(struct kref *kref) +{ + struct uaudio_dev *dev = container_of(kref, struct uaudio_dev, kref); + + uaudio_event_ring_cleanup_free(dev); + atomic_set(&dev->in_use, 0); + wake_up(&dev->disconnect_wq); +} + +/** + * enable_audio_stream() - enable usb snd endpoints + * @subs: usb substream + * @pcm_format: pcm format requested + * @channels: number of channels + * @cur_rate: sample rate + * @datainterval: interval + * + * Opens all USB SND endpoints used for the data interface. This will increment + * the USB SND endpoint's opened count. Requests to keep the interface resumed + * until the audio stream is stopped. Will issue the USB set interface control + * message to enable the data interface. + * + */ +static int enable_audio_stream(struct snd_usb_substream *subs, + snd_pcm_format_t pcm_format, + unsigned int channels, unsigned int cur_rate, + int datainterval) +{ + struct snd_pcm_hw_params params; + struct snd_usb_audio *chip; + struct snd_interval *i; + struct snd_mask *m; + int ret; + + chip = subs->stream->chip; + + _snd_pcm_hw_params_any(¶ms); + + m = hw_param_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_leave(m, pcm_format); + + i = hw_param_interval(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS); + snd_interval_setinteger(i); + i->min = channels; + i->max = channels; + + i = hw_param_interval(¶ms, SNDRV_PCM_HW_PARAM_RATE); + snd_interval_setinteger(i); + i->min = cur_rate; + i->max = cur_rate; + + pm_runtime_barrier(&chip->intf[0]->dev); + snd_usb_autoresume(chip); + + ret = snd_usb_hw_params(subs, ¶ms); + if (ret < 0) + goto put_suspend; + + if (!atomic_read(&chip->shutdown)) { + ret = snd_usb_lock_shutdown(chip); + if (ret < 0) + goto detach_ep; + + if (subs->sync_endpoint) { + ret = snd_usb_endpoint_prepare(chip, subs->sync_endpoint); + if (ret < 0) + goto unlock; + } + + ret = snd_usb_endpoint_prepare(chip, subs->data_endpoint); + if (ret < 0) + goto unlock; + + snd_usb_unlock_shutdown(chip); + + dev_dbg(uaudio_qdev->data->dev, + "selected %s iface:%d altsetting:%d datainterval:%dus\n", + subs->direction ? "capture" : "playback", + subs->cur_audiofmt->iface, subs->cur_audiofmt->altsetting, + (1 << subs->cur_audiofmt->datainterval) * + (subs->dev->speed >= USB_SPEED_HIGH ? + BUS_INTERVAL_HIGHSPEED_AND_ABOVE : + BUS_INTERVAL_FULL_SPEED)); + } + + return 0; + +unlock: + snd_usb_unlock_shutdown(chip); + +detach_ep: + snd_usb_hw_free(subs); + +put_suspend: + snd_usb_autosuspend(chip); + + return ret; +} + +/** + * uaudio_transfer_buffer_setup() - fetch and populate xfer buffer params + * @subs: usb substream + * @xfer_buf: xfer buf to be allocated + * @xfer_buf_len: size of allocation + * @mem_info: QMI response info + * + * Allocates and maps the transfer buffers that will be utilized by the + * audio DSP. Will populate the information in the QMI response that is + * sent back to the stream enable request. + * + */ +static int uaudio_transfer_buffer_setup(struct snd_usb_substream *subs, + u8 *xfer_buf, u32 xfer_buf_len, + struct mem_info_v01 *mem_info) +{ + struct sg_table xfer_buf_sgt; + phys_addr_t xfer_buf_pa; + u32 len = xfer_buf_len; + bool dma_coherent; + unsigned long va; + u32 remainder; + u32 mult; + int ret; + + dma_coherent = dev_is_dma_coherent(subs->dev->bus->sysdev); + + /* xfer buffer, multiple of 4K only */ + if (!len) + len = PAGE_SIZE; + + mult = len / PAGE_SIZE; + remainder = len % PAGE_SIZE; + len = mult * PAGE_SIZE; + len += remainder ? PAGE_SIZE : 0; + + if (len > MAX_XFER_BUFF_LEN) { + dev_err(uaudio_qdev->data->dev, + "req buf len %d > max buf len %lu, setting %lu\n", + len, MAX_XFER_BUFF_LEN, MAX_XFER_BUFF_LEN); + len = MAX_XFER_BUFF_LEN; + } + + xfer_buf = usb_alloc_coherent(subs->dev, len, GFP_KERNEL, &xfer_buf_pa); + if (!xfer_buf) + return -ENOMEM; + + dma_get_sgtable(subs->dev->bus->sysdev, &xfer_buf_sgt, xfer_buf, + xfer_buf_pa, len); + va = uaudio_iommu_map(MEM_XFER_BUF, dma_coherent, xfer_buf_pa, len, + &xfer_buf_sgt); + if (!va) { + ret = -ENOMEM; + goto unmap_sync; + } + + mem_info->pa = xfer_buf_pa; + mem_info->size = len; + mem_info->va = PREPEND_SID_TO_IOVA(va, uaudio_qdev->data->sid); + sg_free_table(&xfer_buf_sgt); + + return 0; + +unmap_sync: + usb_free_coherent(subs->dev, len, xfer_buf, xfer_buf_pa); + + return ret; +} + +/** + * uaudio_endpoint_setup() - fetch and populate endpoint params + * @subs: usb substream + * @endpoint: usb endpoint to add + * @card_num: uadev index + * @mem_info: QMI response info + * @ep_desc: QMI ep desc response field + * + * Initialize the USB endpoint being used for a particular USB + * stream. Will request XHCI sec intr to reserve the EP for + * offloading as well as populating the QMI response with the + * transfer ring parameters. + * + */ +static phys_addr_t +uaudio_endpoint_setup(struct snd_usb_substream *subs, + struct snd_usb_endpoint *endpoint, int card_num, + struct mem_info_v01 *mem_info, + struct usb_endpoint_descriptor_v01 *ep_desc) +{ + struct usb_host_endpoint *ep; + phys_addr_t tr_pa = 0; + struct sg_table *sgt; + bool dma_coherent; + unsigned long va; + struct page *pg; + int ret = -ENODEV; + + dma_coherent = dev_is_dma_coherent(subs->dev->bus->sysdev); + + ep = usb_pipe_endpoint(subs->dev, endpoint->pipe); + if (!ep) { + dev_err(uaudio_qdev->data->dev, "data ep # %d context is null\n", + subs->data_endpoint->ep_num); + goto exit; + } + + memcpy(ep_desc, &ep->desc, sizeof(ep->desc)); + + ret = xhci_sideband_add_endpoint(uadev[card_num].sb, ep); + if (ret < 0) { + dev_err(&subs->dev->dev, + "failed to add data ep to sec intr\n"); + ret = -ENODEV; + goto exit; + } + + sgt = xhci_sideband_get_endpoint_buffer(uadev[card_num].sb, ep); + if (!sgt) { + dev_err(&subs->dev->dev, + "failed to get data ep ring address\n"); + ret = -ENODEV; + goto remove_ep; + } + + pg = sg_page(sgt->sgl); + tr_pa = page_to_phys(pg); + mem_info->pa = sg_dma_address(sgt->sgl); + sg_free_table(sgt); + + /* data transfer ring */ + va = uaudio_iommu_map(MEM_XFER_RING, dma_coherent, tr_pa, + PAGE_SIZE, NULL); + if (!va) { + ret = -ENOMEM; + goto clear_pa; + } + + mem_info->va = PREPEND_SID_TO_IOVA(va, uaudio_qdev->data->sid); + mem_info->size = PAGE_SIZE; + + return 0; + +clear_pa: + mem_info->pa = 0; +remove_ep: + xhci_sideband_remove_endpoint(uadev[card_num].sb, ep); +exit: + return ret; +} + +/** + * uaudio_event_ring_setup() - fetch and populate event ring params + * @subs: usb substream + * @card_num: uadev index + * @mem_info: QMI response info + * + * Register secondary interrupter to XHCI and fetch the event buffer info + * and populate the information into the QMI response. + * + */ +static int uaudio_event_ring_setup(struct snd_usb_substream *subs, + int card_num, struct mem_info_v01 *mem_info) +{ + struct sg_table *sgt; + phys_addr_t er_pa; + bool dma_coherent; + unsigned long va; + struct page *pg; + int ret; + + dma_coherent = dev_is_dma_coherent(subs->dev->bus->sysdev); + er_pa = 0; + + /* event ring */ + ret = xhci_sideband_create_interrupter(uadev[card_num].sb, 1, false, + 0, uaudio_qdev->data->intr_num); + if (ret < 0) { + dev_err(&subs->dev->dev, "failed to fetch interrupter\n"); + goto exit; + } + + sgt = xhci_sideband_get_event_buffer(uadev[card_num].sb); + if (!sgt) { + dev_err(&subs->dev->dev, + "failed to get event ring address\n"); + ret = -ENODEV; + goto remove_interrupter; + } + + pg = sg_page(sgt->sgl); + er_pa = page_to_phys(pg); + mem_info->pa = sg_dma_address(sgt->sgl); + sg_free_table(sgt); + + va = uaudio_iommu_map(MEM_EVENT_RING, dma_coherent, er_pa, + PAGE_SIZE, NULL); + if (!va) { + ret = -ENOMEM; + goto clear_pa; + } + + mem_info->va = PREPEND_SID_TO_IOVA(va, uaudio_qdev->data->sid); + mem_info->size = PAGE_SIZE; + + return 0; + +clear_pa: + mem_info->pa = 0; +remove_interrupter: + xhci_sideband_remove_interrupter(uadev[card_num].sb); +exit: + return ret; +} + +/** + * uaudio_populate_uac_desc() - parse UAC parameters and populate QMI resp + * @subs: usb substream + * @resp: QMI response buffer + * + * Parses information specified within UAC descriptors which explain the + * sample parameters that the device expects. This information is populated + * to the QMI response sent back to the audio DSP. + * + */ +static int uaudio_populate_uac_desc(struct snd_usb_substream *subs, + struct qmi_uaudio_stream_resp_msg_v01 *resp) +{ + struct usb_interface_descriptor *altsd; + struct usb_host_interface *alts; + struct usb_interface *iface; + int protocol; + + iface = usb_ifnum_to_if(subs->dev, subs->cur_audiofmt->iface); + if (!iface) { + dev_err(&subs->dev->dev, "interface # %d does not exist\n", + subs->cur_audiofmt->iface); + return -ENODEV; + } + + alts = &iface->altsetting[subs->cur_audiofmt->altset_idx]; + altsd = get_iface_desc(alts); + protocol = altsd->bInterfaceProtocol; + + if (protocol == UAC_VERSION_1) { + struct uac1_as_header_descriptor *as; + + as = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, + UAC_AS_GENERAL); + if (!as) { + dev_err(&subs->dev->dev, + "%u:%d : no UAC_AS_GENERAL desc\n", + subs->cur_audiofmt->iface, + subs->cur_audiofmt->altset_idx); + return -ENODEV; + } + + resp->data_path_delay = as->bDelay; + resp->data_path_delay_valid = 1; + + resp->usb_audio_subslot_size = subs->cur_audiofmt->fmt_sz; + resp->usb_audio_subslot_size_valid = 1; + + resp->usb_audio_spec_revision = le16_to_cpu((__force __le16)0x0100); + resp->usb_audio_spec_revision_valid = 1; + } else if (protocol == UAC_VERSION_2) { + resp->usb_audio_subslot_size = subs->cur_audiofmt->fmt_sz; + resp->usb_audio_subslot_size_valid = 1; + + resp->usb_audio_spec_revision = le16_to_cpu((__force __le16)0x0200); + resp->usb_audio_spec_revision_valid = 1; + } else if (protocol == UAC_VERSION_3) { + if (iface->intf_assoc->bFunctionSubClass == + UAC3_FUNCTION_SUBCLASS_FULL_ADC_3_0) { + dev_err(&subs->dev->dev, + "full adc is not supported\n"); + return -EINVAL; + } + + switch (le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize)) { + case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_16: + case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_16: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_16: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_16: { + resp->usb_audio_subslot_size = 0x2; + break; + } + + case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_24: + case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_24: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_24: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_24: { + resp->usb_audio_subslot_size = 0x3; + break; + } + + default: + dev_err(&subs->dev->dev, + "%d: %u: Invalid wMaxPacketSize\n", + subs->cur_audiofmt->iface, + subs->cur_audiofmt->altset_idx); + return -EINVAL; + } + resp->usb_audio_subslot_size_valid = 1; + } else { + dev_err(&subs->dev->dev, "unknown protocol version %x\n", + protocol); + return -ENODEV; + } + + memcpy(&resp->std_as_opr_intf_desc, &alts->desc, sizeof(alts->desc)); + + return 0; +} + +/** + * prepare_qmi_response() - prepare stream enable response + * @subs: usb substream + * @req_msg: QMI request message + * @resp: QMI response buffer + * @info_idx: usb interface array index + * + * Prepares the QMI response for a USB QMI stream enable request. Will parse + * out the parameters within the stream enable request, in order to match + * requested audio profile to the ones exposed by the USB device connected. + * + * In addition, will fetch the XHCI transfer resources needed for the handoff to + * happen. This includes, transfer ring and buffer addresses and secondary event + * ring address. These parameters will be communicated as part of the USB QMI + * stream enable response. + * + */ +static int prepare_qmi_response(struct snd_usb_substream *subs, + struct qmi_uaudio_stream_req_msg_v01 *req_msg, + struct qmi_uaudio_stream_resp_msg_v01 *resp, + int info_idx) +{ + struct q6usb_offload *data; + int pcm_dev_num; + int card_num; + u8 *xfer_buf = NULL; + int ret; + + pcm_dev_num = (req_msg->usb_token & QMI_STREAM_REQ_DEV_NUM_MASK) >> 8; + card_num = (req_msg->usb_token & QMI_STREAM_REQ_CARD_NUM_MASK) >> 16; + + if (!uadev[card_num].ctrl_intf) { + dev_err(&subs->dev->dev, "audio ctrl intf info not cached\n"); + ret = -ENODEV; + goto err; + } + + ret = uaudio_populate_uac_desc(subs, resp); + if (ret < 0) + goto err; + + resp->slot_id = subs->dev->slot_id; + resp->slot_id_valid = 1; + + data = snd_soc_usb_find_priv_data(uaudio_qdev->auxdev->dev.parent); + if (!data) + goto err; + + uaudio_qdev->data = data; + + resp->std_as_opr_intf_desc_valid = 1; + ret = uaudio_endpoint_setup(subs, subs->data_endpoint, card_num, + &resp->xhci_mem_info.tr_data, + &resp->std_as_data_ep_desc); + if (ret < 0) + goto err; + + resp->std_as_data_ep_desc_valid = 1; + + if (subs->sync_endpoint) { + ret = uaudio_endpoint_setup(subs, subs->sync_endpoint, card_num, + &resp->xhci_mem_info.tr_sync, + &resp->std_as_sync_ep_desc); + if (ret < 0) + goto drop_data_ep; + + resp->std_as_sync_ep_desc_valid = 1; + } + + resp->interrupter_num_valid = 1; + resp->controller_num_valid = 0; + ret = usb_get_controller_id(subs->dev); + if (ret >= 0) { + resp->controller_num = ret; + resp->controller_num_valid = 1; + } + + /* event ring */ + ret = uaudio_event_ring_setup(subs, card_num, + &resp->xhci_mem_info.evt_ring); + if (ret < 0) + goto drop_sync_ep; + + uaudio_qdev->er_mapped = true; + resp->interrupter_num = xhci_sideband_interrupter_id(uadev[card_num].sb); + + resp->speed_info = get_speed_info(subs->dev->speed); + if (resp->speed_info == USB_QMI_DEVICE_SPEED_INVALID_V01) { + ret = -ENODEV; + goto free_sec_ring; + } + + resp->speed_info_valid = 1; + + ret = uaudio_transfer_buffer_setup(subs, xfer_buf, req_msg->xfer_buff_size, + &resp->xhci_mem_info.xfer_buff); + if (ret < 0) { + ret = -ENOMEM; + goto free_sec_ring; + } + + resp->xhci_mem_info_valid = 1; + + if (!atomic_read(&uadev[card_num].in_use)) { + kref_init(&uadev[card_num].kref); + init_waitqueue_head(&uadev[card_num].disconnect_wq); + uadev[card_num].num_intf = + subs->dev->config->desc.bNumInterfaces; + uadev[card_num].info = kcalloc(uadev[card_num].num_intf, + sizeof(struct intf_info), + GFP_KERNEL); + if (!uadev[card_num].info) { + ret = -ENOMEM; + goto unmap_er; + } + uadev[card_num].udev = subs->dev; + atomic_set(&uadev[card_num].in_use, 1); + } else { + kref_get(&uadev[card_num].kref); + } + + uadev[card_num].usb_core_id = resp->controller_num; + + /* cache intf specific info to use it for unmap and free xfer buf */ + uadev[card_num].info[info_idx].data_xfer_ring_va = + IOVA_MASK(resp->xhci_mem_info.tr_data.va); + uadev[card_num].info[info_idx].data_xfer_ring_size = PAGE_SIZE; + uadev[card_num].info[info_idx].sync_xfer_ring_va = + IOVA_MASK(resp->xhci_mem_info.tr_sync.va); + uadev[card_num].info[info_idx].sync_xfer_ring_size = PAGE_SIZE; + uadev[card_num].info[info_idx].xfer_buf_va = + IOVA_MASK(resp->xhci_mem_info.xfer_buff.va); + uadev[card_num].info[info_idx].xfer_buf_pa = + resp->xhci_mem_info.xfer_buff.pa; + uadev[card_num].info[info_idx].xfer_buf_size = + resp->xhci_mem_info.xfer_buff.size; + uadev[card_num].info[info_idx].data_ep_pipe = subs->data_endpoint ? + subs->data_endpoint->pipe : 0; + uadev[card_num].info[info_idx].sync_ep_pipe = subs->sync_endpoint ? + subs->sync_endpoint->pipe : 0; + uadev[card_num].info[info_idx].data_ep_idx = subs->data_endpoint ? + subs->data_endpoint->ep_num : 0; + uadev[card_num].info[info_idx].sync_ep_idx = subs->sync_endpoint ? + subs->sync_endpoint->ep_num : 0; + uadev[card_num].info[info_idx].xfer_buf = xfer_buf; + uadev[card_num].info[info_idx].pcm_card_num = card_num; + uadev[card_num].info[info_idx].pcm_dev_num = pcm_dev_num; + uadev[card_num].info[info_idx].direction = subs->direction; + uadev[card_num].info[info_idx].intf_num = subs->cur_audiofmt->iface; + uadev[card_num].info[info_idx].in_use = true; + + set_bit(card_num, &uaudio_qdev->card_slot); + + return 0; + +unmap_er: + uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, PAGE_SIZE, PAGE_SIZE); +free_sec_ring: + xhci_sideband_remove_interrupter(uadev[card_num].sb); +drop_sync_ep: + if (subs->sync_endpoint) { + uaudio_iommu_unmap(MEM_XFER_RING, + IOVA_MASK(resp->xhci_mem_info.tr_sync.va), + PAGE_SIZE, PAGE_SIZE); + xhci_sideband_remove_endpoint(uadev[card_num].sb, + usb_pipe_endpoint(subs->dev, subs->sync_endpoint->pipe)); + } +drop_data_ep: + uaudio_iommu_unmap(MEM_XFER_RING, IOVA_MASK(resp->xhci_mem_info.tr_data.va), + PAGE_SIZE, PAGE_SIZE); + xhci_sideband_remove_endpoint(uadev[card_num].sb, + usb_pipe_endpoint(subs->dev, subs->data_endpoint->pipe)); + +err: + return ret; +} + +/** + * handle_uaudio_stream_req() - handle stream enable/disable request + * @handle: QMI client handle + * @sq: qrtr socket + * @txn: QMI transaction context + * @decoded_msg: decoded QMI message + * + * Main handler for the QMI stream enable/disable requests. This executes the + * corresponding enable/disable stream apis, respectively. + * + */ +static void handle_uaudio_stream_req(struct qmi_handle *handle, + struct sockaddr_qrtr *sq, + struct qmi_txn *txn, + const void *decoded_msg) +{ + struct qmi_uaudio_stream_req_msg_v01 *req_msg; + struct qmi_uaudio_stream_resp_msg_v01 resp = {{0}, 0}; + struct uaudio_qmi_svc *svc = uaudio_svc; + struct snd_usb_audio *chip = NULL; + struct snd_usb_substream *subs; + struct usb_host_endpoint *ep; + int datainterval = -EINVAL; + int info_idx = -EINVAL; + struct intf_info *info; + u8 pcm_card_num; + u8 pcm_dev_num; + u8 direction; + int ret = 0; + + if (!svc->client_connected) { + svc->client_sq = *sq; + svc->client_connected = true; + } + + mutex_lock(&qdev_mutex); + req_msg = (struct qmi_uaudio_stream_req_msg_v01 *)decoded_msg; + if (!req_msg->audio_format_valid || !req_msg->bit_rate_valid || + !req_msg->number_of_ch_valid || !req_msg->xfer_buff_size_valid) { + ret = -EINVAL; + goto response; + } + + if (!uaudio_qdev) { + ret = -EINVAL; + goto response; + } + + direction = (req_msg->usb_token & QMI_STREAM_REQ_DIRECTION); + pcm_dev_num = (req_msg->usb_token & QMI_STREAM_REQ_DEV_NUM_MASK) >> 8; + pcm_card_num = (req_msg->usb_token & QMI_STREAM_REQ_CARD_NUM_MASK) >> 16; + if (pcm_card_num >= SNDRV_CARDS) { + ret = -EINVAL; + goto response; + } + + if (req_msg->audio_format > USB_QMI_PCM_FORMAT_U32_BE) { + ret = -EINVAL; + goto response; + } + + subs = find_substream(pcm_card_num, pcm_dev_num, direction); + chip = uadev[pcm_card_num].chip; + if (!subs || !chip || atomic_read(&chip->shutdown)) { + ret = -ENODEV; + goto response; + } + + info_idx = info_idx_from_ifnum(pcm_card_num, subs->cur_audiofmt ? + subs->cur_audiofmt->iface : -1, req_msg->enable); + if (atomic_read(&chip->shutdown) || !subs->stream || !subs->stream->pcm || + !subs->stream->chip) { + ret = -ENODEV; + goto response; + } + + if (req_msg->enable) { + if (info_idx < 0 || chip->system_suspend) { + ret = -EBUSY; + goto response; + } + } + + if (req_msg->service_interval_valid) { + ret = get_data_interval_from_si(subs, + req_msg->service_interval); + if (ret == -EINVAL) + goto response; + + datainterval = ret; + } + + uadev[pcm_card_num].ctrl_intf = chip->ctrl_intf; + + if (req_msg->enable) { + ret = enable_audio_stream(subs, + map_pcm_format(req_msg->audio_format), + req_msg->number_of_ch, req_msg->bit_rate, + datainterval); + + if (!ret) + ret = prepare_qmi_response(subs, req_msg, &resp, + info_idx); + } else { + info = &uadev[pcm_card_num].info[info_idx]; + if (info->data_ep_pipe) { + ep = usb_pipe_endpoint(uadev[pcm_card_num].udev, + info->data_ep_pipe); + if (ep) { + xhci_sideband_stop_endpoint(uadev[pcm_card_num].sb, + ep); + xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb, + ep); + } + + info->data_ep_pipe = 0; + } + + if (info->sync_ep_pipe) { + ep = usb_pipe_endpoint(uadev[pcm_card_num].udev, + info->sync_ep_pipe); + if (ep) { + xhci_sideband_stop_endpoint(uadev[pcm_card_num].sb, + ep); + xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb, + ep); + } + + info->sync_ep_pipe = 0; + } + + disable_audio_stream(subs); + } + +response: + if (!req_msg->enable && ret != -EINVAL && ret != -ENODEV) { + mutex_lock(&chip->mutex); + if (info_idx >= 0) { + info = &uadev[pcm_card_num].info[info_idx]; + uaudio_dev_intf_cleanup(uadev[pcm_card_num].udev, + info); + } + if (atomic_read(&uadev[pcm_card_num].in_use)) + kref_put(&uadev[pcm_card_num].kref, + uaudio_dev_release); + mutex_unlock(&chip->mutex); + } + mutex_unlock(&qdev_mutex); + + resp.usb_token = req_msg->usb_token; + resp.usb_token_valid = 1; + resp.internal_status = ret; + resp.internal_status_valid = 1; + resp.status = ret ? USB_QMI_STREAM_REQ_FAILURE_V01 : ret; + resp.status_valid = 1; + ret = qmi_send_response(svc->uaudio_svc_hdl, sq, txn, + QMI_UAUDIO_STREAM_RESP_V01, + QMI_UAUDIO_STREAM_RESP_MSG_V01_MAX_MSG_LEN, + qmi_uaudio_stream_resp_msg_v01_ei, &resp); +} + +static struct qmi_msg_handler uaudio_stream_req_handlers = { + .type = QMI_REQUEST, + .msg_id = QMI_UAUDIO_STREAM_REQ_V01, + .ei = qmi_uaudio_stream_req_msg_v01_ei, + .decoded_size = QMI_UAUDIO_STREAM_REQ_MSG_V01_MAX_MSG_LEN, + .fn = handle_uaudio_stream_req, +}; + +/** + * qc_usb_audio_offload_init_qmi_dev() - initializes qmi dev + * + * Initializes the USB qdev, which is used to carry information pertaining to + * the offloading resources. This device is freed only when there are no longer + * any offloading candidates. (i.e, when all audio devices are disconnected) + * + */ +static int qc_usb_audio_offload_init_qmi_dev(void) +{ + uaudio_qdev = kzalloc(sizeof(*uaudio_qdev), GFP_KERNEL); + if (!uaudio_qdev) + return -ENOMEM; + + /* initialize xfer ring and xfer buf iova list */ + INIT_LIST_HEAD(&uaudio_qdev->xfer_ring_list); + uaudio_qdev->curr_xfer_ring_iova = IOVA_XFER_RING_BASE; + uaudio_qdev->xfer_ring_iova_size = + IOVA_XFER_RING_MAX - IOVA_XFER_RING_BASE; + + INIT_LIST_HEAD(&uaudio_qdev->xfer_buf_list); + uaudio_qdev->curr_xfer_buf_iova = IOVA_XFER_BUF_BASE; + uaudio_qdev->xfer_buf_iova_size = + IOVA_XFER_BUF_MAX - IOVA_XFER_BUF_BASE; + + return 0; +} + +/* Populates ppcm_idx array with supported PCM indexes */ +static int qc_usb_audio_offload_fill_avail_pcms(struct snd_usb_audio *chip, + struct snd_soc_usb_device *sdev) +{ + struct snd_usb_stream *as; + struct snd_usb_substream *subs; + int idx = 0; + + list_for_each_entry(as, &chip->pcm_list, list) { + subs = &as->substream[SNDRV_PCM_STREAM_PLAYBACK]; + if (subs->ep_num) { + sdev->ppcm_idx[idx] = as->pcm->device; + idx++; + } + /* + * Break if the current index exceeds the number of possible + * playback streams counted from the UAC descriptors. + */ + if (idx >= sdev->num_playback) + break; + } + + return -1; +} + +/** + * qc_usb_audio_offload_probe() - platform op connect handler + * @chip: USB SND device + * + * Platform connect handler when a USB SND device is detected. Will + * notify SOC USB about the connection to enable the USB ASoC backend + * and populate internal USB chip array. + * + */ +static void qc_usb_audio_offload_probe(struct snd_usb_audio *chip) +{ + struct usb_interface *intf = chip->intf[chip->num_interfaces - 1]; + struct usb_interface_descriptor *altsd; + struct usb_host_interface *alts; + struct snd_soc_usb_device *sdev; + struct xhci_sideband *sb; + + /* + * If there is no priv_data, or no playback paths, the connected + * device doesn't support offloading. Avoid populating entries for + * this device. + */ + if (!snd_soc_usb_find_priv_data(uaudio_qdev->auxdev->dev.parent) || + !usb_qmi_get_pcm_num(chip, 0)) + return; + + mutex_lock(&qdev_mutex); + mutex_lock(&chip->mutex); + if (!uadev[chip->card->number].chip) { + sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); + if (!sdev) + goto exit; + + sb = xhci_sideband_register(intf, XHCI_SIDEBAND_VENDOR, + uaudio_sideband_notifier); + if (!sb) + goto free_sdev; + } else { + sb = uadev[chip->card->number].sb; + sdev = uadev[chip->card->number].sdev; + } + + uadev[chip->card->number].sb = sb; + uadev[chip->card->number].chip = chip; + uadev[chip->card->number].sdev = sdev; + + alts = &intf->altsetting[0]; + altsd = get_iface_desc(alts); + + /* Wait until all PCM devices are populated before notifying soc-usb */ + if (altsd->bInterfaceNumber == chip->last_iface) { + sdev->num_playback = usb_qmi_get_pcm_num(chip, 0); + + /* + * Allocate playback pcm index array based on number of possible + * playback paths within the UAC descriptors. + */ + sdev->ppcm_idx = kcalloc(sdev->num_playback, sizeof(unsigned int), + GFP_KERNEL); + if (!sdev->ppcm_idx) + goto unreg_xhci; + + qc_usb_audio_offload_fill_avail_pcms(chip, sdev); + sdev->card_idx = chip->card->number; + sdev->chip_idx = chip->index; + + snd_soc_usb_connect(uaudio_qdev->auxdev->dev.parent, sdev); + } + + mutex_unlock(&chip->mutex); + mutex_unlock(&qdev_mutex); + + return; + +unreg_xhci: + xhci_sideband_unregister(sb); + uadev[chip->card->number].sb = NULL; +free_sdev: + kfree(sdev); + uadev[chip->card->number].sdev = NULL; + uadev[chip->card->number].chip = NULL; +exit: + mutex_unlock(&chip->mutex); + mutex_unlock(&qdev_mutex); +} + +/** + * qc_usb_audio_cleanup_qmi_dev() - release qmi device + * + * Frees the USB qdev. Only occurs when there are no longer any potential + * devices that can utilize USB audio offloading. + * + */ +static void qc_usb_audio_cleanup_qmi_dev(void) +{ + kfree(uaudio_qdev); + uaudio_qdev = NULL; +} + +/** + * qc_usb_audio_offload_disconnect() - platform op disconnect handler + * @chip: USB SND device + * + * Platform disconnect handler. Will ensure that any pending stream is + * halted by issuing a QMI disconnect indication packet to the adsp. + * + */ +static void qc_usb_audio_offload_disconnect(struct snd_usb_audio *chip) +{ + struct uaudio_dev *dev; + int card_num; + + if (!chip) + return; + + card_num = chip->card->number; + if (card_num >= SNDRV_CARDS) + return; + + mutex_lock(&qdev_mutex); + mutex_lock(&chip->mutex); + dev = &uadev[card_num]; + + /* Device has already been cleaned up, or never populated */ + if (!dev->chip) { + mutex_unlock(&qdev_mutex); + mutex_unlock(&chip->mutex); + return; + } + + /* cleaned up already */ + if (!dev->udev) + goto done; + + uaudio_send_disconnect_ind(chip); + uaudio_dev_cleanup(dev); +done: + /* + * If num_interfaces == 1, the last USB SND interface is being removed. + * This is to accommodate for devices w/ multiple UAC functions. + */ + if (chip->num_interfaces == 1) { + snd_soc_usb_disconnect(uaudio_qdev->auxdev->dev.parent, dev->sdev); + xhci_sideband_unregister(dev->sb); + dev->chip = NULL; + kfree(dev->sdev->ppcm_idx); + kfree(dev->sdev); + dev->sdev = NULL; + } + mutex_unlock(&chip->mutex); + + mutex_unlock(&qdev_mutex); +} + +/** + * qc_usb_audio_offload_suspend() - USB offload PM suspend handler + * @intf: USB interface + * @message: suspend type + * + * PM suspend handler to ensure that the USB offloading driver is able to stop + * any pending traffic, so that the bus can be suspended. + * + */ +static void qc_usb_audio_offload_suspend(struct usb_interface *intf, + pm_message_t message) +{ + struct snd_usb_audio *chip = usb_get_intfdata(intf); + int card_num; + + if (!chip) + return; + + card_num = chip->card->number; + if (card_num >= SNDRV_CARDS) + return; + + mutex_lock(&qdev_mutex); + mutex_lock(&chip->mutex); + + uaudio_send_disconnect_ind(chip); + + mutex_unlock(&qdev_mutex); + mutex_unlock(&chip->mutex); +} + +static struct snd_usb_platform_ops offload_ops = { + .connect_cb = qc_usb_audio_offload_probe, + .disconnect_cb = qc_usb_audio_offload_disconnect, + .suspend_cb = qc_usb_audio_offload_suspend, +}; + +static int qc_usb_audio_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) + +{ + struct uaudio_qmi_svc *svc; + int ret; + + svc = kzalloc(sizeof(*svc), GFP_KERNEL); + if (!svc) + return -ENOMEM; + + svc->uaudio_svc_hdl = kzalloc(sizeof(*svc->uaudio_svc_hdl), GFP_KERNEL); + if (!svc->uaudio_svc_hdl) { + ret = -ENOMEM; + goto free_svc; + } + + ret = qmi_handle_init(svc->uaudio_svc_hdl, + QMI_UAUDIO_STREAM_REQ_MSG_V01_MAX_MSG_LEN, + &uaudio_svc_ops_options, + &uaudio_stream_req_handlers); + ret = qmi_add_server(svc->uaudio_svc_hdl, UAUDIO_STREAM_SERVICE_ID_V01, + UAUDIO_STREAM_SERVICE_VERS_V01, 0); + + uaudio_svc = svc; + + qc_usb_audio_offload_init_qmi_dev(); + uaudio_qdev->auxdev = auxdev; + + ret = snd_usb_register_platform_ops(&offload_ops); + if (ret < 0) + goto release_qmi; + + return 0; + +release_qmi: + qmi_handle_release(svc->uaudio_svc_hdl); +free_svc: + kfree(svc); + + return ret; +} + +static void qc_usb_audio_remove(struct auxiliary_device *auxdev) +{ + struct uaudio_qmi_svc *svc = uaudio_svc; + int idx; + + /* + * Remove all connected devices after unregistering ops, to ensure + * that no further connect events will occur. The disconnect routine + * will issue the QMI disconnect indication, which results in the + * external DSP to stop issuing transfers. + */ + snd_usb_unregister_platform_ops(); + for (idx = 0; idx < SNDRV_CARDS; idx++) + qc_usb_audio_offload_disconnect(uadev[idx].chip); + + qc_usb_audio_cleanup_qmi_dev(); + + qmi_handle_release(svc->uaudio_svc_hdl); + kfree(svc); + uaudio_svc = NULL; +} + +static const struct auxiliary_device_id qc_usb_audio_table[] = { + { .name = "q6usb.qc-usb-audio-offload" }, + {}, +}; +MODULE_DEVICE_TABLE(auxiliary, qc_usb_audio_table); + +static struct auxiliary_driver qc_usb_audio_offload_drv = { + .name = "qc-usb-audio-offload", + .id_table = qc_usb_audio_table, + .probe = qc_usb_audio_probe, + .remove = qc_usb_audio_remove, +}; +module_auxiliary_driver(qc_usb_audio_offload_drv); + +MODULE_DESCRIPTION("QC USB Audio Offloading"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-59-g8ed1b From 6a348e9236c336f363375a526b7b868667011121 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:48:02 -0700 Subject: ALSA: usb-audio: qcom: Don't allow USB offload path if PCM device is in use Add proper checks and updates to the USB substream once receiving a USB QMI stream enable request. If the substream is already in use from the non offload path, reject the stream enable request. In addition, update the USB substream opened parameter when enabling the offload path, so the non offload path can be blocked. Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-30-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- sound/usb/qcom/qc_audio_offload.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c index 87e74c949370..464c5106b420 100644 --- a/sound/usb/qcom/qc_audio_offload.c +++ b/sound/usb/qcom/qc_audio_offload.c @@ -1567,12 +1567,17 @@ static void handle_uaudio_stream_req(struct qmi_handle *handle, goto response; } + mutex_lock(&chip->mutex); if (req_msg->enable) { - if (info_idx < 0 || chip->system_suspend) { + if (info_idx < 0 || chip->system_suspend || subs->opened) { ret = -EBUSY; + mutex_unlock(&chip->mutex); + goto response; } + subs->opened = 1; } + mutex_unlock(&chip->mutex); if (req_msg->service_interval_valid) { ret = get_data_interval_from_si(subs, @@ -1594,6 +1599,11 @@ static void handle_uaudio_stream_req(struct qmi_handle *handle, if (!ret) ret = prepare_qmi_response(subs, req_msg, &resp, info_idx); + if (ret < 0) { + mutex_lock(&chip->mutex); + subs->opened = 0; + mutex_unlock(&chip->mutex); + } } else { info = &uadev[pcm_card_num].info[info_idx]; if (info->data_ep_pipe) { @@ -1623,6 +1633,9 @@ static void handle_uaudio_stream_req(struct qmi_handle *handle, } disable_audio_stream(subs); + mutex_lock(&chip->mutex); + subs->opened = 0; + mutex_unlock(&chip->mutex); } response: -- cgit v1.2.3-59-g8ed1b From a67656f011d18c58a1e5c6744ba7d6061b5bfd3b Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:48:03 -0700 Subject: ALSA: usb-audio: qcom: Add USB offload route kcontrol In order to allow userspace/applications know about USB offloading status, expose a sound kcontrol that fetches information about which sound card and PCM index the USB device is mapped to for supporting offloading. In the USB audio offloading framework, the ASoC BE DAI link is the entity responsible for registering to the SOC USB layer. It is expected for the USB SND offloading driver to add the kcontrol to the sound card associated with the USB audio device. An example output would look like: tinymix -D 1 get 'USB Offload Playback Route PCM#0' -1, -1 (range -1->255) This example signifies that there is no mapped ASoC path available for the USB SND device. tinymix -D 1 get 'USB Offload Playback Route PCM#0' 0, 0 (range -1->255) This example signifies that the offload path is available over ASoC sound card index#0 and PCM device#0. The USB offload kcontrol will be added in addition to the existing kcontrols identified by the USB SND mixer. The kcontrols used to modify the USB audio device specific parameters are still valid and expected to be used. These parameters are not mirrored to the ASoC subsystem. Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-31-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- sound/usb/qcom/Makefile | 2 + sound/usb/qcom/mixer_usb_offload.c | 155 +++++++++++++++++++++++++++++++++++++ sound/usb/qcom/mixer_usb_offload.h | 11 +++ sound/usb/qcom/qc_audio_offload.c | 2 + 4 files changed, 170 insertions(+) create mode 100644 sound/usb/qcom/mixer_usb_offload.c create mode 100644 sound/usb/qcom/mixer_usb_offload.h diff --git a/sound/usb/qcom/Makefile b/sound/usb/qcom/Makefile index 1eb51160e2e5..6567727b66f0 100644 --- a/sound/usb/qcom/Makefile +++ b/sound/usb/qcom/Makefile @@ -1,2 +1,4 @@ snd-usb-audio-qmi-y := usb_audio_qmi_v01.o qc_audio_offload.o +snd-usb-audio-qmi-y += mixer_usb_offload.o obj-$(CONFIG_SND_USB_AUDIO_QMI) += snd-usb-audio-qmi.o + diff --git a/sound/usb/qcom/mixer_usb_offload.c b/sound/usb/qcom/mixer_usb_offload.c new file mode 100644 index 000000000000..2adeb64f4d33 --- /dev/null +++ b/sound/usb/qcom/mixer_usb_offload.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include + +#include +#include +#include + +#include "../usbaudio.h" +#include "../card.h" +#include "../helper.h" +#include "../mixer.h" + +#include "mixer_usb_offload.h" + +#define PCM_IDX(n) ((n) & 0xffff) +#define CARD_IDX(n) ((n) >> 16) + +static int +snd_usb_offload_card_route_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct device *sysdev = snd_kcontrol_chip(kcontrol); + int ret; + + ret = snd_soc_usb_update_offload_route(sysdev, + CARD_IDX(kcontrol->private_value), + PCM_IDX(kcontrol->private_value), + SNDRV_PCM_STREAM_PLAYBACK, + SND_SOC_USB_KCTL_CARD_ROUTE, + ucontrol->value.integer.value); + if (ret < 0) { + ucontrol->value.integer.value[0] = -1; + ucontrol->value.integer.value[1] = -1; + } + + return 0; +} + +static int snd_usb_offload_card_route_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = -1; + uinfo->value.integer.max = SNDRV_CARDS; + + return 0; +} + +static struct snd_kcontrol_new snd_usb_offload_mapped_card_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_usb_offload_card_route_info, + .get = snd_usb_offload_card_route_get, +}; + +static int +snd_usb_offload_pcm_route_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct device *sysdev = snd_kcontrol_chip(kcontrol); + int ret; + + ret = snd_soc_usb_update_offload_route(sysdev, + CARD_IDX(kcontrol->private_value), + PCM_IDX(kcontrol->private_value), + SNDRV_PCM_STREAM_PLAYBACK, + SND_SOC_USB_KCTL_PCM_ROUTE, + ucontrol->value.integer.value); + if (ret < 0) { + ucontrol->value.integer.value[0] = -1; + ucontrol->value.integer.value[1] = -1; + } + + return 0; +} + +static int snd_usb_offload_pcm_route_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = -1; + /* Arbitrary max value, as there is no 'limit' on number of PCM devices */ + uinfo->value.integer.max = 0xff; + + return 0; +} + +static struct snd_kcontrol_new snd_usb_offload_mapped_pcm_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_usb_offload_pcm_route_info, + .get = snd_usb_offload_pcm_route_get, +}; + +/** + * snd_usb_offload_create_ctl() - Add USB offload bounded mixer + * @chip: USB SND chip device + * @bedev: Reference to USB backend DAI device + * + * Creates a sound control for a USB audio device, so that applications can + * query for if there is an available USB audio offload path, and which + * card is managing it. + */ +int snd_usb_offload_create_ctl(struct snd_usb_audio *chip, struct device *bedev) +{ + struct snd_kcontrol_new *chip_kctl; + struct snd_usb_substream *subs; + struct snd_usb_stream *as; + char ctl_name[48]; + int ret; + + list_for_each_entry(as, &chip->pcm_list, list) { + subs = &as->substream[SNDRV_PCM_STREAM_PLAYBACK]; + if (!subs->ep_num || as->pcm_index > 0xff) + continue; + + chip_kctl = &snd_usb_offload_mapped_card_ctl; + chip_kctl->count = 1; + /* + * Store the associated USB SND card number and PCM index for + * the kctl. + */ + chip_kctl->private_value = as->pcm_index | + chip->card->number << 16; + sprintf(ctl_name, "USB Offload Playback Card Route PCM#%d", + as->pcm_index); + chip_kctl->name = ctl_name; + ret = snd_ctl_add(chip->card, snd_ctl_new1(chip_kctl, bedev)); + if (ret < 0) + break; + + chip_kctl = &snd_usb_offload_mapped_pcm_ctl; + chip_kctl->count = 1; + /* + * Store the associated USB SND card number and PCM index for + * the kctl. + */ + chip_kctl->private_value = as->pcm_index | + chip->card->number << 16; + sprintf(ctl_name, "USB Offload Playback PCM Route PCM#%d", + as->pcm_index); + chip_kctl->name = ctl_name; + ret = snd_ctl_add(chip->card, snd_ctl_new1(chip_kctl, bedev)); + if (ret < 0) + break; + } + + return ret; +} diff --git a/sound/usb/qcom/mixer_usb_offload.h b/sound/usb/qcom/mixer_usb_offload.h new file mode 100644 index 000000000000..cf20673f4dc9 --- /dev/null +++ b/sound/usb/qcom/mixer_usb_offload.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __USB_OFFLOAD_MIXER_H +#define __USB_OFFLOAD_MIXER_H + +int snd_usb_offload_create_ctl(struct snd_usb_audio *chip, struct device *bedev); + +#endif /* __USB_OFFLOAD_MIXER_H */ diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c index 464c5106b420..378249a264a3 100644 --- a/sound/usb/qcom/qc_audio_offload.c +++ b/sound/usb/qcom/qc_audio_offload.c @@ -38,6 +38,7 @@ #include "../pcm.h" #include "../power.h" +#include "mixer_usb_offload.h" #include "usb_audio_qmi_v01.h" /* Stream disable request timeout during USB device disconnect */ @@ -1792,6 +1793,7 @@ static void qc_usb_audio_offload_probe(struct snd_usb_audio *chip) sdev->card_idx = chip->card->number; sdev->chip_idx = chip->index; + snd_usb_offload_create_ctl(chip, uaudio_qdev->auxdev->dev.parent); snd_soc_usb_connect(uaudio_qdev->auxdev->dev.parent, sdev); } -- cgit v1.2.3-59-g8ed1b From 9bf4294d0c1e5268332964604ece43eaf7f33cc3 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 12:48:04 -0700 Subject: ALSA: usb-audio: qcom: Notify USB audio devices on USB offload probing If the vendor USB offload class driver is not ready/initialized before USB SND discovers attached devices, utilize snd_usb_rediscover_devices() to find all currently attached devices, so that the ASoC entities are notified on available USB audio devices. Signed-off-by: Wesley Cheng Acked-by: Mark Brown Link: https://lore.kernel.org/r/20250409194804.3773260-32-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- sound/usb/qcom/qc_audio_offload.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c index 378249a264a3..5874eb5ba827 100644 --- a/sound/usb/qcom/qc_audio_offload.c +++ b/sound/usb/qcom/qc_audio_offload.c @@ -1952,6 +1952,8 @@ static int qc_usb_audio_probe(struct auxiliary_device *auxdev, if (ret < 0) goto release_qmi; + snd_usb_rediscover_devices(); + return 0; release_qmi: -- cgit v1.2.3-59-g8ed1b From 4bfeea6ec1c0241a6c856ed1694a72a8cbaa8494 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 9 Apr 2025 23:36:57 -0500 Subject: thunderbolt: Use wake on connect and disconnect over suspend Wake on connect is useful for being able to wake up a suspended laptop without opening the lid by plugging into a dock. Add the default policy to the USB4 router when wakeup is enabled for the router. Behavior for individual ports can be controlled by port wakeup settings. Cc: Opal Voravootivat Cc: Raul Rangel Cc: Utkarsh Patel Cc: Richard Gong Cc: Sanath S Link: https://lore.kernel.org/linux-usb/20250410042723.GU3152277@black.fi.intel.com/T/#m0249e8c0e1c77ec92a44a3d6c8b4a8e5a9b7114e Signed-off-by: Mario Limonciello Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 6a2116cbb06f..28febb95f8fa 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -3599,6 +3599,7 @@ void tb_switch_suspend(struct tb_switch *sw, bool runtime) flags |= TB_WAKE_ON_USB4; flags |= TB_WAKE_ON_USB3 | TB_WAKE_ON_PCIE | TB_WAKE_ON_DP; } else if (device_may_wakeup(&sw->dev)) { + flags |= TB_WAKE_ON_CONNECT | TB_WAKE_ON_DISCONNECT; flags |= TB_WAKE_ON_USB4 | TB_WAKE_ON_USB3 | TB_WAKE_ON_PCIE; } -- cgit v1.2.3-59-g8ed1b From 00327d7f2c8c512c9b168daae02c8b989f79ec71 Mon Sep 17 00:00:00 2001 From: Pengyu Luo Date: Sun, 16 Mar 2025 17:43:55 +0800 Subject: usb: typec: ucsi: add Huawei Matebook E Go ucsi driver The Huawei Matebook E Go tablet implements the UCSI interface in the onboard EC. Add the glue driver to interface with the platform's UCSI implementation. This driver is inspired by the following drivers: drivers/usb/typec/ucsi/ucsi_yoga_c630.c drivers/usb/typec/ucsi/ucsi_glink.c drivers/soc/qcom/pmic_glink_altmode.c Signed-off-by: Pengyu Luo Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20250316094357.462022-1-mitltlatltl@gmail.com Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 1 + drivers/usb/typec/ucsi/Kconfig | 11 + drivers/usb/typec/ucsi/Makefile | 1 + drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c | 522 ++++++++++++++++++++++++++++ 4 files changed, 535 insertions(+) create mode 100644 drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c diff --git a/MAINTAINERS b/MAINTAINERS index 96b827049501..03da81994d21 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10950,6 +10950,7 @@ M: Pengyu Luo S: Maintained F: Documentation/devicetree/bindings/platform/huawei,gaokun-ec.yaml F: drivers/platform/arm64/huawei-gaokun-ec.c +F: drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c F: include/linux/platform_data/huawei-gaokun-ec.h HUGETLB SUBSYSTEM diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig index 75559601fe8f..e94956d27325 100644 --- a/drivers/usb/typec/ucsi/Kconfig +++ b/drivers/usb/typec/ucsi/Kconfig @@ -91,4 +91,15 @@ config UCSI_LENOVO_YOGA_C630 To compile the driver as a module, choose M here: the module will be called ucsi_yoga_c630. +config UCSI_HUAWEI_GAOKUN + tristate "UCSI Interface Driver for Huawei Matebook E Go" + depends on EC_HUAWEI_GAOKUN + select DRM_AUX_HPD_BRIDGE + help + This driver enables UCSI support on the Huawei Matebook E Go tablet, + which is a sc8280xp-based 2-in-1 tablet. + + To compile the driver as a module, choose M here: the module will be + called ucsi_huawei_gaokun. + endif diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile index be98a879104d..dbc571763eff 100644 --- a/drivers/usb/typec/ucsi/Makefile +++ b/drivers/usb/typec/ucsi/Makefile @@ -23,3 +23,4 @@ obj-$(CONFIG_UCSI_STM32G0) += ucsi_stm32g0.o obj-$(CONFIG_UCSI_PMIC_GLINK) += ucsi_glink.o obj-$(CONFIG_CROS_EC_UCSI) += cros_ec_ucsi.o obj-$(CONFIG_UCSI_LENOVO_YOGA_C630) += ucsi_yoga_c630.o +obj-$(CONFIG_UCSI_HUAWEI_GAOKUN) += ucsi_huawei_gaokun.o diff --git a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c new file mode 100644 index 000000000000..344aa7aeaf02 --- /dev/null +++ b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c @@ -0,0 +1,522 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ucsi-huawei-gaokun - A UCSI driver for HUAWEI Matebook E Go + * + * Copyright (C) 2024-2025 Pengyu Luo + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ucsi.h" + +#define EC_EVENT_UCSI 0x21 +#define EC_EVENT_USB 0x22 + +#define GAOKUN_CCX_MASK GENMASK(1, 0) +#define GAOKUN_MUX_MASK GENMASK(3, 2) + +#define GAOKUN_DPAM_MASK GENMASK(3, 0) +#define GAOKUN_HPD_STATE_MASK BIT(4) +#define GAOKUN_HPD_IRQ_MASK BIT(5) + +#define GET_IDX(updt) (ffs(updt) - 1) + +#define CCX_TO_ORI(ccx) (++(ccx) % 3) /* convert ccx to enum typec_orientation */ + +/* Configuration Channel Extension */ +enum gaokun_ucsi_ccx { + USBC_CCX_NORMAL, + USBC_CCX_REVERSE, + USBC_CCX_NONE, +}; + +enum gaokun_ucsi_mux { + USBC_MUX_NONE, + USBC_MUX_USB_2L, + USBC_MUX_DP_4L, + USBC_MUX_USB_DP, +}; + +/* based on pmic_glink_altmode_pin_assignment */ +enum gaokun_ucsi_dpam_pan { /* DP Alt Mode Pin Assignments */ + USBC_DPAM_PAN_NONE, + USBC_DPAM_PAN_A, /* Not supported after USB Type-C Standard v1.0b */ + USBC_DPAM_PAN_B, /* Not supported after USB Type-C Standard v1.0b */ + USBC_DPAM_PAN_C, /* USBC_DPAM_PAN_C_REVERSE - 6 */ + USBC_DPAM_PAN_D, + USBC_DPAM_PAN_E, + USBC_DPAM_PAN_F, /* Not supported after USB Type-C Standard v1.0b */ + USBC_DPAM_PAN_A_REVERSE,/* Not supported after USB Type-C Standard v1.0b */ + USBC_DPAM_PAN_B_REVERSE,/* Not supported after USB Type-C Standard v1.0b */ + USBC_DPAM_PAN_C_REVERSE, + USBC_DPAM_PAN_D_REVERSE, + USBC_DPAM_PAN_E_REVERSE, + USBC_DPAM_PAN_F_REVERSE,/* Not supported after USB Type-C Standard v1.0b */ +}; + +struct gaokun_ucsi_reg { + u8 num_ports; + u8 port_updt; + u8 port_data[4]; + u8 checksum; + u8 reserved; +} __packed; + +struct gaokun_ucsi_port { + struct completion usb_ack; + spinlock_t lock; /* serializing port resource access */ + + struct gaokun_ucsi *ucsi; + struct auxiliary_device *bridge; + + int idx; + enum gaokun_ucsi_ccx ccx; + enum gaokun_ucsi_mux mux; + u8 mode; + u16 svid; + u8 hpd_state; + u8 hpd_irq; +}; + +struct gaokun_ucsi { + struct gaokun_ec *ec; + struct ucsi *ucsi; + struct gaokun_ucsi_port *ports; + struct device *dev; + struct delayed_work work; + struct notifier_block nb; + u16 version; + u8 num_ports; +}; + +/* -------------------------------------------------------------------------- */ +/* For UCSI */ + +static int gaokun_ucsi_read_version(struct ucsi *ucsi, u16 *version) +{ + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi); + + *version = uec->version; + + return 0; +} + +static int gaokun_ucsi_read_cci(struct ucsi *ucsi, u32 *cci) +{ + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi); + u8 buf[GAOKUN_UCSI_READ_SIZE]; + int ret; + + ret = gaokun_ec_ucsi_read(uec->ec, buf); + if (ret) + return ret; + + memcpy(cci, buf, sizeof(*cci)); + + return 0; +} + +static int gaokun_ucsi_read_message_in(struct ucsi *ucsi, + void *val, size_t val_len) +{ + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi); + u8 buf[GAOKUN_UCSI_READ_SIZE]; + int ret; + + ret = gaokun_ec_ucsi_read(uec->ec, buf); + if (ret) + return ret; + + memcpy(val, buf + GAOKUN_UCSI_CCI_SIZE, + min(val_len, GAOKUN_UCSI_MSGI_SIZE)); + + return 0; +} + +static int gaokun_ucsi_async_control(struct ucsi *ucsi, u64 command) +{ + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi); + u8 buf[GAOKUN_UCSI_WRITE_SIZE] = {}; + + memcpy(buf, &command, sizeof(command)); + + return gaokun_ec_ucsi_write(uec->ec, buf); +} + +static void gaokun_ucsi_update_connector(struct ucsi_connector *con) +{ + struct gaokun_ucsi *uec = ucsi_get_drvdata(con->ucsi); + + if (con->num > uec->num_ports) + return; + + con->typec_cap.orientation_aware = true; +} + +static void gaokun_set_orientation(struct ucsi_connector *con, + struct gaokun_ucsi_port *port) +{ + enum gaokun_ucsi_ccx ccx; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + ccx = port->ccx; + spin_unlock_irqrestore(&port->lock, flags); + + typec_set_orientation(con->port, CCX_TO_ORI(ccx)); +} + +static void gaokun_ucsi_connector_status(struct ucsi_connector *con) +{ + struct gaokun_ucsi *uec = ucsi_get_drvdata(con->ucsi); + int idx; + + idx = con->num - 1; + if (con->num > uec->num_ports) { + dev_warn(uec->dev, "set orientation out of range: con%d\n", idx); + return; + } + + gaokun_set_orientation(con, &uec->ports[idx]); +} + +const struct ucsi_operations gaokun_ucsi_ops = { + .read_version = gaokun_ucsi_read_version, + .read_cci = gaokun_ucsi_read_cci, + .read_message_in = gaokun_ucsi_read_message_in, + .sync_control = ucsi_sync_control_common, + .async_control = gaokun_ucsi_async_control, + .update_connector = gaokun_ucsi_update_connector, + .connector_status = gaokun_ucsi_connector_status, +}; + +/* -------------------------------------------------------------------------- */ +/* For Altmode */ + +static void gaokun_ucsi_port_update(struct gaokun_ucsi_port *port, + const u8 *port_data) +{ + struct gaokun_ucsi *uec = port->ucsi; + int offset = port->idx * 2; /* every port has 2 Bytes data */ + unsigned long flags; + u8 dcc, ddi; + + dcc = port_data[offset]; + ddi = port_data[offset + 1]; + + spin_lock_irqsave(&port->lock, flags); + + port->ccx = FIELD_GET(GAOKUN_CCX_MASK, dcc); + port->mux = FIELD_GET(GAOKUN_MUX_MASK, dcc); + port->mode = FIELD_GET(GAOKUN_DPAM_MASK, ddi); + port->hpd_state = FIELD_GET(GAOKUN_HPD_STATE_MASK, ddi); + port->hpd_irq = FIELD_GET(GAOKUN_HPD_IRQ_MASK, ddi); + + /* Mode and SVID are unused; keeping them to make things clearer */ + switch (port->mode) { + case USBC_DPAM_PAN_C: + case USBC_DPAM_PAN_C_REVERSE: + port->mode = DP_PIN_ASSIGN_C; /* correct it for usb later */ + break; + case USBC_DPAM_PAN_D: + case USBC_DPAM_PAN_D_REVERSE: + port->mode = DP_PIN_ASSIGN_D; + break; + case USBC_DPAM_PAN_E: + case USBC_DPAM_PAN_E_REVERSE: + port->mode = DP_PIN_ASSIGN_E; + break; + case USBC_DPAM_PAN_NONE: + port->mode = TYPEC_STATE_SAFE; + break; + default: + dev_warn(uec->dev, "unknown mode %d\n", port->mode); + break; + } + + switch (port->mux) { + case USBC_MUX_NONE: + port->svid = 0; + break; + case USBC_MUX_USB_2L: + port->svid = USB_SID_PD; + port->mode = TYPEC_STATE_USB; /* same as PAN_C, correct it */ + break; + case USBC_MUX_DP_4L: + case USBC_MUX_USB_DP: + port->svid = USB_SID_DISPLAYPORT; + break; + default: + dev_warn(uec->dev, "unknown mux state %d\n", port->mux); + break; + } + + spin_unlock_irqrestore(&port->lock, flags); +} + +static int gaokun_ucsi_refresh(struct gaokun_ucsi *uec) +{ + struct gaokun_ucsi_reg ureg; + int ret, idx; + + ret = gaokun_ec_ucsi_get_reg(uec->ec, &ureg); + if (ret) + return GAOKUN_UCSI_NO_PORT_UPDATE; + + uec->num_ports = ureg.num_ports; + idx = GET_IDX(ureg.port_updt); + + if (idx < 0 || idx >= ureg.num_ports) + return GAOKUN_UCSI_NO_PORT_UPDATE; + + gaokun_ucsi_port_update(&uec->ports[idx], ureg.port_data); + return idx; +} + +static void gaokun_ucsi_handle_altmode(struct gaokun_ucsi_port *port) +{ + struct gaokun_ucsi *uec = port->ucsi; + int idx = port->idx; + + if (idx >= uec->ucsi->cap.num_connectors) { + dev_warn(uec->dev, "altmode port out of range: %d\n", idx); + return; + } + + /* UCSI callback .connector_status() have set orientation */ + if (port->bridge) + drm_aux_hpd_bridge_notify(&port->bridge->dev, + port->hpd_state ? + connector_status_connected : + connector_status_disconnected); + + gaokun_ec_ucsi_pan_ack(uec->ec, port->idx); +} + +static void gaokun_ucsi_altmode_notify_ind(struct gaokun_ucsi *uec) +{ + int idx; + + if (!uec->ucsi->connector) { /* slow to register */ + dev_err_ratelimited(uec->dev, "ucsi connector is not initialized yet\n"); + return; + } + + idx = gaokun_ucsi_refresh(uec); + if (idx == GAOKUN_UCSI_NO_PORT_UPDATE) + gaokun_ec_ucsi_pan_ack(uec->ec, idx); /* ack directly if no update */ + else + gaokun_ucsi_handle_altmode(&uec->ports[idx]); +} + +/* + * When inserting, 2 UCSI events(connector change) are followed by USB events. + * If we received one USB event, that means USB events are not blocked, so we + * can complelte for all ports, and we should signal all events. + */ +static void gaokun_ucsi_complete_usb_ack(struct gaokun_ucsi *uec) +{ + struct gaokun_ucsi_port *port; + int idx = 0; + + while (idx < uec->num_ports) { + port = &uec->ports[idx++]; + if (!completion_done(&port->usb_ack)) + complete_all(&port->usb_ack); + } +} + +/* + * USB event is necessary for enabling altmode, the event should follow + * UCSI event, if not after timeout(this notify may be disabled somehow), + * then force to enable altmode. + */ +static void gaokun_ucsi_handle_no_usb_event(struct gaokun_ucsi *uec, int idx) +{ + struct gaokun_ucsi_port *port; + + port = &uec->ports[idx]; + if (!wait_for_completion_timeout(&port->usb_ack, 2 * HZ)) { + dev_warn(uec->dev, "No USB EVENT, triggered by UCSI EVENT"); + gaokun_ucsi_altmode_notify_ind(uec); + } +} + +static int gaokun_ucsi_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + u32 cci; + struct gaokun_ucsi *uec = container_of(nb, struct gaokun_ucsi, nb); + + switch (action) { + case EC_EVENT_USB: + gaokun_ucsi_complete_usb_ack(uec); + gaokun_ucsi_altmode_notify_ind(uec); + return NOTIFY_OK; + + case EC_EVENT_UCSI: + gaokun_ucsi_read_cci(uec->ucsi, &cci); + ucsi_notify_common(uec->ucsi, cci); + if (UCSI_CCI_CONNECTOR(cci)) + gaokun_ucsi_handle_no_usb_event(uec, UCSI_CCI_CONNECTOR(cci) - 1); + + return NOTIFY_OK; + + default: + return NOTIFY_DONE; + } +} + +static int gaokun_ucsi_ports_init(struct gaokun_ucsi *uec) +{ + struct gaokun_ucsi_port *ucsi_port; + struct gaokun_ucsi_reg ureg = {}; + struct device *dev = uec->dev; + struct fwnode_handle *fwnode; + int i, ret, num_ports; + u32 port; + + gaokun_ec_ucsi_get_reg(uec->ec, &ureg); + num_ports = ureg.num_ports; + uec->ports = devm_kcalloc(dev, num_ports, sizeof(*uec->ports), + GFP_KERNEL); + if (!uec->ports) + return -ENOMEM; + + for (i = 0; i < num_ports; ++i) { + ucsi_port = &uec->ports[i]; + ucsi_port->ccx = USBC_CCX_NONE; + ucsi_port->idx = i; + ucsi_port->ucsi = uec; + init_completion(&ucsi_port->usb_ack); + spin_lock_init(&ucsi_port->lock); + } + + device_for_each_child_node(dev, fwnode) { + ret = fwnode_property_read_u32(fwnode, "reg", &port); + if (ret < 0) { + dev_err(dev, "missing reg property of %pOFn\n", fwnode); + fwnode_handle_put(fwnode); + return ret; + } + + if (port >= num_ports) { + dev_warn(dev, "invalid connector number %d, ignoring\n", port); + continue; + } + + ucsi_port = &uec->ports[port]; + ucsi_port->bridge = devm_drm_dp_hpd_bridge_alloc(dev, to_of_node(fwnode)); + if (IS_ERR(ucsi_port->bridge)) { + fwnode_handle_put(fwnode); + return PTR_ERR(ucsi_port->bridge); + } + } + + for (i = 0; i < num_ports; i++) { + if (!uec->ports[i].bridge) + continue; + + ret = devm_drm_dp_hpd_bridge_add(dev, uec->ports[i].bridge); + if (ret) + return ret; + } + + return 0; +} + +static void gaokun_ucsi_register_worker(struct work_struct *work) +{ + struct gaokun_ucsi *uec; + struct ucsi *ucsi; + int ret; + + uec = container_of(work, struct gaokun_ucsi, work.work); + ucsi = uec->ucsi; + + ret = gaokun_ec_register_notify(uec->ec, &uec->nb); + if (ret) { + dev_err_probe(ucsi->dev, ret, "notifier register failed\n"); + return; + } + + ret = ucsi_register(ucsi); + if (ret) + dev_err_probe(ucsi->dev, ret, "ucsi register failed\n"); +} + +static int gaokun_ucsi_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) +{ + struct gaokun_ec *ec = adev->dev.platform_data; + struct device *dev = &adev->dev; + struct gaokun_ucsi *uec; + int ret; + + uec = devm_kzalloc(dev, sizeof(*uec), GFP_KERNEL); + if (!uec) + return -ENOMEM; + + uec->ec = ec; + uec->dev = dev; + uec->version = UCSI_VERSION_1_0; + uec->nb.notifier_call = gaokun_ucsi_notify; + + INIT_DELAYED_WORK(&uec->work, gaokun_ucsi_register_worker); + + ret = gaokun_ucsi_ports_init(uec); + if (ret) + return ret; + + uec->ucsi = ucsi_create(dev, &gaokun_ucsi_ops); + if (IS_ERR(uec->ucsi)) + return PTR_ERR(uec->ucsi); + + ucsi_set_drvdata(uec->ucsi, uec); + auxiliary_set_drvdata(adev, uec); + + /* EC can't handle UCSI properly in the early stage */ + schedule_delayed_work(&uec->work, 3 * HZ); + + return 0; +} + +static void gaokun_ucsi_remove(struct auxiliary_device *adev) +{ + struct gaokun_ucsi *uec = auxiliary_get_drvdata(adev); + + gaokun_ec_unregister_notify(uec->ec, &uec->nb); + ucsi_unregister(uec->ucsi); + ucsi_destroy(uec->ucsi); +} + +static const struct auxiliary_device_id gaokun_ucsi_id_table[] = { + { .name = GAOKUN_MOD_NAME "." GAOKUN_DEV_UCSI, }, + {} +}; +MODULE_DEVICE_TABLE(auxiliary, gaokun_ucsi_id_table); + +static struct auxiliary_driver gaokun_ucsi_driver = { + .name = GAOKUN_DEV_UCSI, + .id_table = gaokun_ucsi_id_table, + .probe = gaokun_ucsi_probe, + .remove = gaokun_ucsi_remove, +}; + +module_auxiliary_driver(gaokun_ucsi_driver); + +MODULE_DESCRIPTION("HUAWEI Matebook E Go UCSI driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-59-g8ed1b From ac573b94073869a652b7d2335d0aaf88b762f713 Mon Sep 17 00:00:00 2001 From: Madhu M Date: Wed, 2 Apr 2025 12:38:17 +0530 Subject: usb: typec: ucsi: Add the UCSI commands in debugfs Added the UCSI commands UCSI_GET_CAM_SUPPORTED, UCSI_GET_PD_MESSAGE, UCSI_GET_ATTENTION_VDO and UCSI_SET_USB support in debugfs to enhance PD/TypeC debugging capability Signed-off-by: Madhu M Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20250402070817.1016635-1-madhu.m@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/debugfs.c | 4 ++++ drivers/usb/typec/ucsi/ucsi.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/drivers/usb/typec/ucsi/debugfs.c b/drivers/usb/typec/ucsi/debugfs.c index eae2b18a2d8a..92ebf1a2defd 100644 --- a/drivers/usb/typec/ucsi/debugfs.c +++ b/drivers/usb/typec/ucsi/debugfs.c @@ -34,16 +34,20 @@ static int ucsi_cmd(void *data, u64 val) case UCSI_CONNECTOR_RESET: case UCSI_SET_SINK_PATH: case UCSI_SET_NEW_CAM: + case UCSI_SET_USB: ret = ucsi_send_command(ucsi, val, NULL, 0); break; case UCSI_GET_CAPABILITY: case UCSI_GET_CONNECTOR_CAPABILITY: case UCSI_GET_ALTERNATE_MODES: + case UCSI_GET_CAM_SUPPORTED: case UCSI_GET_CURRENT_CAM: case UCSI_GET_PDOS: case UCSI_GET_CABLE_PROPERTY: case UCSI_GET_CONNECTOR_STATUS: case UCSI_GET_ERROR_STATUS: + case UCSI_GET_PD_MESSAGE: + case UCSI_GET_ATTENTION_VDO: case UCSI_GET_CAM_CS: case UCSI_GET_LPM_PPM_INFO: ret = ucsi_send_command(ucsi, val, diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h index 3a2c1762bec1..72b9d5a42961 100644 --- a/drivers/usb/typec/ucsi/ucsi.h +++ b/drivers/usb/typec/ucsi/ucsi.h @@ -123,9 +123,11 @@ void ucsi_connector_change(struct ucsi *ucsi, u8 num); #define UCSI_GET_CONNECTOR_STATUS 0x12 #define UCSI_GET_CONNECTOR_STATUS_SIZE 152 #define UCSI_GET_ERROR_STATUS 0x13 +#define UCSI_GET_ATTENTION_VDO 0x16 #define UCSI_GET_PD_MESSAGE 0x15 #define UCSI_GET_CAM_CS 0x18 #define UCSI_SET_SINK_PATH 0x1c +#define UCSI_SET_USB 0x21 #define UCSI_GET_LPM_PPM_INFO 0x22 #define UCSI_CONNECTOR_NUMBER(_num_) ((u64)(_num_) << 16) -- cgit v1.2.3-59-g8ed1b From 0f7bbef1794dc87141897f804e5871a293aa174b Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Fri, 4 Apr 2025 00:21:01 +0200 Subject: usb: typec: mux: do not return on EOPNOTSUPP in {mux, switch}_set Since the typec connectors can have many muxes or switches for different lanes (sbu, usb2, usb3) going into different modal states (usb2, usb3, audio, debug) all of them will be called on typec_switch_set and typec_mux_set. But not all of them will be handling the expected mode. If one of the mux or switch will come back with EOPTNOSUPP this is no reason to stop running through the next ones. Therefor we skip this particular error value and continue calling the next. Signed-off-by: Michael Grzeschik Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20250404-ml-topic-typec-mux-v1-1-22c0526381ba@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c index 49926d6e72c7..182c902c42f6 100644 --- a/drivers/usb/typec/mux.c +++ b/drivers/usb/typec/mux.c @@ -214,7 +214,7 @@ int typec_switch_set(struct typec_switch *sw, sw_dev = sw->sw_devs[i]; ret = sw_dev->set(sw_dev, orientation); - if (ret) + if (ret && ret != -EOPNOTSUPP) return ret; } @@ -378,7 +378,7 @@ int typec_mux_set(struct typec_mux *mux, struct typec_mux_state *state) mux_dev = mux->mux_devs[i]; ret = mux_dev->set(mux_dev, state); - if (ret) + if (ret && ret != -EOPNOTSUPP) return ret; } -- cgit v1.2.3-59-g8ed1b From 64843d0ba96d3eae297025562111d57585273366 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Fri, 4 Apr 2025 00:43:04 +0200 Subject: usb: typec: tcpm: allow to use sink in accessory mode Since the function tcpm_acc_attach is not setting the data and role for for the sink case we extend it to check for it first. Signed-off-by: Michael Grzeschik Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20250404-ml-topic-tcpm-v1-1-b99f44badce8@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index a99db4e025cd..839697c14265 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -4551,12 +4551,17 @@ static void tcpm_snk_detach(struct tcpm_port *port) static int tcpm_acc_attach(struct tcpm_port *port) { int ret; + enum typec_role role; + enum typec_data_role data; if (port->attached) return 0; - ret = tcpm_set_roles(port, true, TYPEC_SOURCE, - tcpm_data_role_for_source(port)); + role = tcpm_port_is_sink(port) ? TYPEC_SINK : TYPEC_SOURCE; + data = tcpm_port_is_sink(port) ? tcpm_data_role_for_sink(port) + : tcpm_data_role_for_source(port); + + ret = tcpm_set_roles(port, true, role, data); if (ret < 0) return ret; -- cgit v1.2.3-59-g8ed1b From 8db73e6a42b6f047c7ad50a7b98b862d19ea0056 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Fri, 4 Apr 2025 00:43:05 +0200 Subject: usb: typec: tcpm: allow sink (ufp) to toggle into accessory mode debug This patch extends the is_debug macro to cover the sink case (ufp). It also handles the transition to access the DEBUG_ACC_ATTACHED state in the sink case. It also handles the debounce case in which the cc pins are not immediately valid after the plug event. Signed-off-by: Michael Grzeschik Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20250404-ml-topic-tcpm-v1-2-b99f44badce8@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 839697c14265..01714a42f848 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -67,6 +67,7 @@ \ S(ACC_UNATTACHED), \ S(DEBUG_ACC_ATTACHED), \ + S(DEBUG_ACC_DEBOUNCE), \ S(AUDIO_ACC_ATTACHED), \ S(AUDIO_ACC_DEBOUNCE), \ \ @@ -621,7 +622,8 @@ static const char * const pd_rev[] = { !tcpm_cc_is_source((port)->cc1))) #define tcpm_port_is_debug(port) \ - (tcpm_cc_is_source((port)->cc1) && tcpm_cc_is_source((port)->cc2)) + ((tcpm_cc_is_source((port)->cc1) && tcpm_cc_is_source((port)->cc2)) || \ + (tcpm_cc_is_sink((port)->cc1) && tcpm_cc_is_sink((port)->cc2))) #define tcpm_port_is_audio(port) \ (tcpm_cc_is_audio((port)->cc1) && tcpm_cc_is_audio((port)->cc2)) @@ -4969,7 +4971,13 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_state(port, SRC_UNATTACHED, PD_T_DRP_SRC); break; case SNK_ATTACH_WAIT: - if ((port->cc1 == TYPEC_CC_OPEN && + if (tcpm_port_is_debug(port)) + tcpm_set_state(port, DEBUG_ACC_ATTACHED, + PD_T_CC_DEBOUNCE); + else if (tcpm_port_is_audio(port)) + tcpm_set_state(port, AUDIO_ACC_ATTACHED, + PD_T_CC_DEBOUNCE); + else if ((port->cc1 == TYPEC_CC_OPEN && port->cc2 != TYPEC_CC_OPEN) || (port->cc1 != TYPEC_CC_OPEN && port->cc2 == TYPEC_CC_OPEN)) @@ -4983,6 +4991,12 @@ static void run_state_machine(struct tcpm_port *port) if (tcpm_port_is_disconnected(port)) tcpm_set_state(port, SNK_UNATTACHED, PD_T_PD_DEBOUNCE); + else if (tcpm_port_is_debug(port)) + tcpm_set_state(port, DEBUG_ACC_ATTACHED, + PD_T_CC_DEBOUNCE); + else if (tcpm_port_is_audio(port)) + tcpm_set_state(port, AUDIO_ACC_ATTACHED, + PD_T_CC_DEBOUNCE); else if (port->vbus_present) tcpm_set_state(port, tcpm_try_src(port) ? SRC_TRY @@ -5281,7 +5295,10 @@ static void run_state_machine(struct tcpm_port *port) /* Accessory states */ case ACC_UNATTACHED: tcpm_acc_detach(port); - tcpm_set_state(port, SRC_UNATTACHED, 0); + if (port->port_type == TYPEC_PORT_SRC) + tcpm_set_state(port, SRC_UNATTACHED, 0); + else + tcpm_set_state(port, SNK_UNATTACHED, 0); break; case DEBUG_ACC_ATTACHED: case AUDIO_ACC_ATTACHED: @@ -5289,6 +5306,7 @@ static void run_state_machine(struct tcpm_port *port) if (ret < 0) tcpm_set_state(port, ACC_UNATTACHED, 0); break; + case DEBUG_ACC_DEBOUNCE: case AUDIO_ACC_DEBOUNCE: tcpm_set_state(port, ACC_UNATTACHED, port->timings.cc_debounce_time); break; @@ -5880,7 +5898,8 @@ static void _tcpm_cc_change(struct tcpm_port *port, enum typec_cc_status cc1, } break; case SNK_UNATTACHED: - if (tcpm_port_is_sink(port)) + if (tcpm_port_is_debug(port) || tcpm_port_is_audio(port) || + tcpm_port_is_sink(port)) tcpm_set_state(port, SNK_ATTACH_WAIT, 0); break; case SNK_ATTACH_WAIT: @@ -5943,7 +5962,12 @@ static void _tcpm_cc_change(struct tcpm_port *port, enum typec_cc_status cc1, case DEBUG_ACC_ATTACHED: if (cc1 == TYPEC_CC_OPEN || cc2 == TYPEC_CC_OPEN) - tcpm_set_state(port, ACC_UNATTACHED, 0); + tcpm_set_state(port, DEBUG_ACC_DEBOUNCE, 0); + break; + + case DEBUG_ACC_DEBOUNCE: + if (tcpm_port_is_debug(port)) + tcpm_set_state(port, DEBUG_ACC_ATTACHED, 0); break; case SNK_TRY: -- cgit v1.2.3-59-g8ed1b From 8a50da849151e7e12b43c1d8fe7ad302223aef6b Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Fri, 4 Apr 2025 00:43:06 +0200 Subject: usb: typec: tcpm: allow switching to mode accessory to mux properly The funciton tcpm_acc_attach is not setting the proper state when calling tcpm_set_role. The function tcpm_set_role is currently only handling TYPEC_STATE_USB. For the tcpm_acc_attach to switch into other modal states tcpm_set_role needs to be extended by an extra state parameter. This patch is handling the proper state change when calling tcpm_acc_attach. Signed-off-by: Michael Grzeschik Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20250404-ml-topic-tcpm-v1-3-b99f44badce8@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 01714a42f848..784fa23102f9 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -1153,7 +1153,7 @@ static int tcpm_set_attached_state(struct tcpm_port *port, bool attached) port->data_role); } -static int tcpm_set_roles(struct tcpm_port *port, bool attached, +static int tcpm_set_roles(struct tcpm_port *port, bool attached, int state, enum typec_role role, enum typec_data_role data) { enum typec_orientation orientation; @@ -1190,7 +1190,7 @@ static int tcpm_set_roles(struct tcpm_port *port, bool attached, } } - ret = tcpm_mux_set(port, TYPEC_STATE_USB, usb_role, orientation); + ret = tcpm_mux_set(port, state, usb_role, orientation); if (ret < 0) return ret; @@ -4355,7 +4355,8 @@ static int tcpm_src_attach(struct tcpm_port *port) tcpm_enable_auto_vbus_discharge(port, true); - ret = tcpm_set_roles(port, true, TYPEC_SOURCE, tcpm_data_role_for_source(port)); + ret = tcpm_set_roles(port, true, TYPEC_STATE_USB, + TYPEC_SOURCE, tcpm_data_role_for_source(port)); if (ret < 0) return ret; @@ -4530,7 +4531,8 @@ static int tcpm_snk_attach(struct tcpm_port *port) tcpm_enable_auto_vbus_discharge(port, true); - ret = tcpm_set_roles(port, true, TYPEC_SINK, tcpm_data_role_for_sink(port)); + ret = tcpm_set_roles(port, true, TYPEC_STATE_USB, + TYPEC_SINK, tcpm_data_role_for_sink(port)); if (ret < 0) return ret; @@ -4555,6 +4557,7 @@ static int tcpm_acc_attach(struct tcpm_port *port) int ret; enum typec_role role; enum typec_data_role data; + int state = TYPEC_STATE_USB; if (port->attached) return 0; @@ -4563,7 +4566,13 @@ static int tcpm_acc_attach(struct tcpm_port *port) data = tcpm_port_is_sink(port) ? tcpm_data_role_for_sink(port) : tcpm_data_role_for_source(port); - ret = tcpm_set_roles(port, true, role, data); + if (tcpm_port_is_audio(port)) + state = TYPEC_MODE_AUDIO; + + if (tcpm_port_is_debug(port)) + state = TYPEC_MODE_DEBUG; + + ret = tcpm_set_roles(port, true, state, role, data); if (ret < 0) return ret; @@ -5349,7 +5358,7 @@ static void run_state_machine(struct tcpm_port *port) */ tcpm_set_vconn(port, false); tcpm_set_vbus(port, false); - tcpm_set_roles(port, port->self_powered, TYPEC_SOURCE, + tcpm_set_roles(port, port->self_powered, TYPEC_STATE_USB, TYPEC_SOURCE, tcpm_data_role_for_source(port)); /* * If tcpc fails to notify vbus off, TCPM will wait for PD_T_SAFE_0V + @@ -5381,7 +5390,7 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_vconn(port, false); if (port->pd_capable) tcpm_set_charge(port, false); - tcpm_set_roles(port, port->self_powered, TYPEC_SINK, + tcpm_set_roles(port, port->self_powered, TYPEC_STATE_USB, TYPEC_SINK, tcpm_data_role_for_sink(port)); /* * VBUS may or may not toggle, depending on the adapter. @@ -5505,10 +5514,10 @@ static void run_state_machine(struct tcpm_port *port) case DR_SWAP_CHANGE_DR: tcpm_unregister_altmodes(port); if (port->data_role == TYPEC_HOST) - tcpm_set_roles(port, true, port->pwr_role, + tcpm_set_roles(port, true, TYPEC_STATE_USB, port->pwr_role, TYPEC_DEVICE); else - tcpm_set_roles(port, true, port->pwr_role, + tcpm_set_roles(port, true, TYPEC_STATE_USB, port->pwr_role, TYPEC_HOST); tcpm_ams_finish(port); tcpm_set_state(port, ready_state(port), 0); -- cgit v1.2.3-59-g8ed1b From a592e0673a20c57468d5296b57130a45cb0ff487 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Fri, 4 Apr 2025 01:17:20 +0200 Subject: usb: typec: tcpci: add regulator support The tcpci chip vbus pin is possibly driven by an regulator. This patch is adding support to enable an optional vdd regulator before probing. Signed-off-by: Michael Grzeschik Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20250404-ml-topic-tcpci-v1-1-4442c7d0ee1e@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index 19ab6647af70..a56e31b20c21 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -17,6 +17,7 @@ #include #include #include +#include #define PD_RETRY_COUNT_DEFAULT 3 #define PD_RETRY_COUNT_3_0_OR_HIGHER 2 @@ -905,6 +906,10 @@ static int tcpci_probe(struct i2c_client *client) int err; u16 val = 0; + err = devm_regulator_get_enable_optional(&client->dev, "vdd"); + if (err && err != -ENODEV) + return dev_err_probe(&client->dev, err, "Failed to get regulator\n"); + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; -- cgit v1.2.3-59-g8ed1b From 9fc5986fbcd7e1e63afb04be94cd4e8a536a4b04 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 6 Apr 2025 22:40:50 +0200 Subject: usb: typec: tcpci: Fix wakeup source leaks on device unbind Device can be unbound, so driver must also release memory for the wakeup source. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20250406204051.63446-1-krzysztof.kozlowski@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci_maxim_core.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/tcpm/tcpci_maxim_core.c b/drivers/usb/typec/tcpm/tcpci_maxim_core.c index fd1b80593367..29a4aa89d1a1 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim_core.c +++ b/drivers/usb/typec/tcpm/tcpci_maxim_core.c @@ -536,7 +536,10 @@ static int max_tcpci_probe(struct i2c_client *client) return dev_err_probe(&client->dev, ret, "IRQ initialization failed\n"); - device_init_wakeup(chip->dev, true); + ret = devm_device_init_wakeup(chip->dev); + if (ret) + return dev_err_probe(chip->dev, ret, "Failed to init wakeup\n"); + return 0; } -- cgit v1.2.3-59-g8ed1b From aaa8f2e959341fd4a3ccf111500eb1e6176678e0 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 6 Apr 2025 22:40:51 +0200 Subject: usb: typec: tipd: Fix wakeup source leaks on device unbind Device can be unbound, so driver must also release memory for the wakeup source. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20250406204051.63446-2-krzysztof.kozlowski@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 7ee721a877c1..dcf141ada078 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -1431,7 +1431,7 @@ static int tps6598x_probe(struct i2c_client *client) tps->wakeup = device_property_read_bool(tps->dev, "wakeup-source"); if (tps->wakeup && client->irq) { - device_init_wakeup(&client->dev, true); + devm_device_init_wakeup(&client->dev); enable_irq_wake(client->irq); } -- cgit v1.2.3-59-g8ed1b From b4b38ffb38c91afd4dc387608db26f6fc34ed40b Mon Sep 17 00:00:00 2001 From: Jos Wang Date: Sun, 9 Feb 2025 15:19:26 +0800 Subject: usb: typec: displayport: Receive DP Status Update NAK request exit dp altmode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Although some Type-C DRD devices that do not support the DP Sink function (such as Huawei Mate 40Pro), the Source Port initiates Enter Mode CMD, but the device responds to Enter Mode ACK, the Source port then initiates DP Status Update CMD, and the device responds to DP Status Update NAK. As PD2.0 spec ("6.4.4.3.4 Enter Mode Command"),A DR_Swap Message Shall Not be sent during Modal Operation between the Port Partners. At this time, the source port initiates DR_Swap message through the "echo device > /sys/class/typec/port0/data_role" command to switch the data role from host to device. The device will initiate a Hard Reset for recovery, resulting in the failure of data role swap. Therefore, when DP Status Update NAK is received, Exit Mode CMD is initiated to exit the currently entered DP altmode. Signed-off-by: Jos Wang Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20250209071926.69625-1-joswang1221@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/altmodes/displayport.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c index ac84a6d64c2f..b09b58d7311d 100644 --- a/drivers/usb/typec/altmodes/displayport.c +++ b/drivers/usb/typec/altmodes/displayport.c @@ -393,6 +393,10 @@ static int dp_altmode_vdm(struct typec_altmode *alt, break; case CMDT_RSP_NAK: switch (cmd) { + case DP_CMD_STATUS_UPDATE: + if (typec_altmode_exit(alt)) + dev_err(&dp->alt->dev, "Exit Mode Failed!\n"); + break; case DP_CMD_CONFIGURE: dp->data.conf = 0; ret = dp_altmode_configured(dp); -- cgit v1.2.3-59-g8ed1b From 850e634006f453d57fffe86ca07da15713dea3b8 Mon Sep 17 00:00:00 2001 From: Roy Luo Date: Wed, 12 Mar 2025 22:34:34 +0000 Subject: usb: dwc3: core: Avoid redundant system suspend/resume callbacks dwc3 device suspend/resume callbacks were being triggered during system suspend and resume even if the device was already runtime-suspended. This is redundant for device mode because the suspend and resume routines are essentially identical for system PM and runtime PM. To prevent these unnecessary callbacks, indicate to the PM core that it can safely leave the device in runtime suspend if it's already runtime-suspended in device mode by returning a positive value in prepare() callback. This optimization only applies to devices without pinctrl, as pinctrl has distinct logic tied to system suspend/resume. Signed-off-by: Roy Luo Acked-by: Thinh Nguyen Link: https://lore.kernel.org/r/20250312223434.3071598-1-royluo@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 17ae5c13fe36..b169913172fc 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -2670,14 +2671,31 @@ static void dwc3_complete(struct device *dev) dwc3_writel(dwc->regs, DWC3_GUCTL3, reg); } } + +static int dwc3_prepare(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + + /* + * Indicate to the PM core that it may safely leave the device in + * runtime suspend if runtime-suspended already in device mode. + */ + if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE && + pm_runtime_suspended(dev) && + !dev_pinctrl(dev)) + return 1; + + return 0; +} #else #define dwc3_complete NULL +#define dwc3_prepare NULL #endif /* CONFIG_PM_SLEEP */ static const struct dev_pm_ops dwc3_dev_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(dwc3_suspend, dwc3_resume) .complete = dwc3_complete, - + .prepare = dwc3_prepare, /* * Runtime suspend halts the controller on disconnection. It relies on * platforms with custom connection notification to start the controller -- cgit v1.2.3-59-g8ed1b From 2e8bbfc11201b857410955d0f983be9660d87397 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Wed, 9 Apr 2025 10:48:14 -0700 Subject: dt-bindings: usb: qcom,dwc3: Add SM8750 compatible SM8750 uses the Synopsys DWC3 controller. Add this to the compatibles list to utilize the DWC3 QCOM and DWC3 core framework. Other than a revision bump to DWC3 controller rev2.00a, the controller on SM8750 does not add any additional vendor specific features compared to previous chipsets. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Wesley Cheng Signed-off-by: Melody Olvera Link: https://lore.kernel.org/r/20250409-sm8750_usb_master-v4-3-6ec621c98be6@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/qcom,dwc3.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml index 64137c1619a6..a681208616f3 100644 --- a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml @@ -55,6 +55,7 @@ properties: - qcom,sm8450-dwc3 - qcom,sm8550-dwc3 - qcom,sm8650-dwc3 + - qcom,sm8750-dwc3 - qcom,x1e80100-dwc3 - qcom,x1e80100-dwc3-mp - const: qcom,dwc3 @@ -354,6 +355,7 @@ allOf: - qcom,sm8450-dwc3 - qcom,sm8550-dwc3 - qcom,sm8650-dwc3 + - qcom,sm8750-dwc3 then: properties: clocks: @@ -497,6 +499,7 @@ allOf: - qcom,sm8450-dwc3 - qcom,sm8550-dwc3 - qcom,sm8650-dwc3 + - qcom,sm8750-dwc3 then: properties: interrupts: -- cgit v1.2.3-59-g8ed1b From 6e07dd1354f4173f52a5a29e47ba6d0f32e70c6e Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Tue, 25 Mar 2025 14:18:48 +0100 Subject: dt-bindings: usb: dwc3: Allow connector in USB controller node Allow specifying the connector directly in the USB controller node, as allow in other USB controller bindings and commonly used for "gpio-usb-b-connector". Linux already supports this without driver changes. Signed-off-by: Matthias Schiffer Acked-by: Conor Dooley Link: https://lore.kernel.org/r/20250325131848.127438-1-matthias.schiffer@ew.tq-group.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/snps,dwc3-common.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/snps,dwc3-common.yaml b/Documentation/devicetree/bindings/usb/snps,dwc3-common.yaml index 71249b6ba616..6c0b8b653824 100644 --- a/Documentation/devicetree/bindings/usb/snps,dwc3-common.yaml +++ b/Documentation/devicetree/bindings/usb/snps,dwc3-common.yaml @@ -390,6 +390,12 @@ properties: maximum: 8 default: 1 + connector: + $ref: /schemas/connector/usb-connector.yaml# + description: Connector for dual role switch + type: object + unevaluatedProperties: false + port: $ref: /schemas/graph.yaml#/properties/port description: -- cgit v1.2.3-59-g8ed1b From bd3cf1a9396e6efe178ddbd92c3747a4cf820a4e Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Thu, 20 Mar 2025 17:56:44 +0100 Subject: USB: gadget: Replace deprecated strncpy() with strscpy() strncpy() is deprecated for NUL-terminated destination buffers; use strscpy() instead. Since kzalloc() already zeroes out the destination buffer, the potential NUL-padding by strncpy() is unnecessary. strscpy() copies only the required characters and guarantees NUL-termination. Since the destination buffer has a fixed length, strscpy() automatically determines its size using sizeof() when the argument is omitted. This makes an explicit sizeof() call unnecessary. The source string is also NUL-terminated and meets the __must_be_cstr() requirement of strscpy(). No functional changes intended. Link: https://github.com/KSPP/linux/issues/90 Cc: linux-hardening@vger.kernel.org Signed-off-by: Thorsten Blum Reviewed-by: Kees Cook Link: https://lore.kernel.org/r/20250320165647.34859-2-thorsten.blum@linux.dev Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/legacy/inode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c index b6a30d88a800..fcce84a726f2 100644 --- a/drivers/usb/gadget/legacy/inode.c +++ b/drivers/usb/gadget/legacy/inode.c @@ -1615,7 +1615,7 @@ static int activate_ep_files (struct dev_data *dev) mutex_init(&data->lock); init_waitqueue_head (&data->wait); - strncpy (data->name, ep->name, sizeof (data->name) - 1); + strscpy(data->name, ep->name); refcount_set (&data->count, 1); data->dev = dev; get_dev (dev); -- cgit v1.2.3-59-g8ed1b From 24454a11dd1551cd202a46a6fd69c65a1a368903 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Mon, 17 Mar 2025 11:22:51 +1030 Subject: usb: gadget: uvc: Avoid -Wflex-array-member-not-at-end warnings -Wflex-array-member-not-at-end was introduced in GCC-14, and we are getting ready to enable it, globally. Move the conflicting declaration to the end of the structure. Notice that `struct uvc_input_header_descriptor` is a flexible structure --a structure that contains a flexible-array member. With this, fix three of the following warnings: drivers/usb/gadget/function/uvc_configfs.h:77:57: warning: structure containing a flexible array member is not at the end of another structure [-Wflex-array-member-not-at-end] Signed-off-by: Gustavo A. R. Silva Reviewed-by: Kees Cook Link: https://lore.kernel.org/r/Z9dyY7_ydJiGqh_d@kspp Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/uvc_configfs.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/gadget/function/uvc_configfs.h b/drivers/usb/gadget/function/uvc_configfs.h index 2f78cd4f396f..9391614135e9 100644 --- a/drivers/usb/gadget/function/uvc_configfs.h +++ b/drivers/usb/gadget/function/uvc_configfs.h @@ -74,10 +74,12 @@ static inline struct uvcg_format *to_uvcg_format(struct config_item *item) struct uvcg_streaming_header { struct config_item item; - struct uvc_input_header_descriptor desc; unsigned linked; struct list_head formats; unsigned num_fmt; + + /* Must be last --ends in a flexible-array member. */ + struct uvc_input_header_descriptor desc; }; static inline struct uvcg_streaming_header *to_uvcg_streaming_header(struct config_item *item) -- cgit v1.2.3-59-g8ed1b From 937a8a3a8d46a3377b4195cd8f2aa656666ebc8b Mon Sep 17 00:00:00 2001 From: Peter Korsgaard Date: Tue, 18 Mar 2025 16:22:07 +0100 Subject: usb: gadget: f_hid: wake up readers on disable/unbind Similar to how it is done in the write path. Add a disabled flag to track the function state and use it to exit the read loops to ensure no readers get stuck when the function is disabled/unbound, protecting against corruption when the waitq and spinlocks are reinitialized in hidg_bind(). Signed-off-by: Peter Korsgaard Link: https://lore.kernel.org/r/20250318152207.330997-1-peter@korsgaard.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_hid.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c index 740311c4fa24..1bc40fc0ccf7 100644 --- a/drivers/usb/gadget/function/f_hid.c +++ b/drivers/usb/gadget/function/f_hid.c @@ -75,6 +75,7 @@ struct f_hidg { /* recv report */ spinlock_t read_spinlock; wait_queue_head_t read_queue; + bool disabled; /* recv report - interrupt out only (use_out_ep == 1) */ struct list_head completed_out_req; unsigned int qlen; @@ -329,7 +330,7 @@ static ssize_t f_hidg_intout_read(struct file *file, char __user *buffer, spin_lock_irqsave(&hidg->read_spinlock, flags); -#define READ_COND_INTOUT (!list_empty(&hidg->completed_out_req)) +#define READ_COND_INTOUT (!list_empty(&hidg->completed_out_req) || hidg->disabled) /* wait for at least one buffer to complete */ while (!READ_COND_INTOUT) { @@ -343,6 +344,11 @@ static ssize_t f_hidg_intout_read(struct file *file, char __user *buffer, spin_lock_irqsave(&hidg->read_spinlock, flags); } + if (hidg->disabled) { + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + return -ESHUTDOWN; + } + /* pick the first one */ list = list_first_entry(&hidg->completed_out_req, struct f_hidg_req_list, list); @@ -387,7 +393,7 @@ static ssize_t f_hidg_intout_read(struct file *file, char __user *buffer, return count; } -#define READ_COND_SSREPORT (hidg->set_report_buf != NULL) +#define READ_COND_SSREPORT (hidg->set_report_buf != NULL || hidg->disabled) static ssize_t f_hidg_ssreport_read(struct file *file, char __user *buffer, size_t count, loff_t *ptr) @@ -1012,6 +1018,11 @@ static void hidg_disable(struct usb_function *f) } spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); + spin_lock_irqsave(&hidg->read_spinlock, flags); + hidg->disabled = true; + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + wake_up(&hidg->read_queue); + spin_lock_irqsave(&hidg->write_spinlock, flags); if (!hidg->write_pending) { free_ep_req(hidg->in_ep, hidg->req); @@ -1097,6 +1108,10 @@ static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) } } + spin_lock_irqsave(&hidg->read_spinlock, flags); + hidg->disabled = false; + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + if (hidg->in_ep != NULL) { spin_lock_irqsave(&hidg->write_spinlock, flags); hidg->req = req_in; -- cgit v1.2.3-59-g8ed1b From b9e4b954542449f36e12263732fcea1740596166 Mon Sep 17 00:00:00 2001 From: Li Qiong Date: Fri, 14 Mar 2025 18:16:38 +0800 Subject: usb: cdns3: Remove the invalid comment The function don't return value, remove the invalid comment. Signed-off-by: Li Qiong Acked-by: Peter Chen Link: https://lore.kernel.org/r/20250314101639.424013-1-liqiong@nfschina.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/cdns3-plat.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/usb/cdns3/cdns3-plat.c b/drivers/usb/cdns3/cdns3-plat.c index 59ec505e198a..735df88774e4 100644 --- a/drivers/usb/cdns3/cdns3-plat.c +++ b/drivers/usb/cdns3/cdns3-plat.c @@ -179,8 +179,6 @@ err_phy3_init: /** * cdns3_plat_remove() - unbind drd driver and clean up * @pdev: Pointer to Linux platform device - * - * Returns 0 on success otherwise negative errno */ static void cdns3_plat_remove(struct platform_device *pdev) { -- cgit v1.2.3-59-g8ed1b From 015c0e63eb7cf9cd1a203ef99177cc66112d94a8 Mon Sep 17 00:00:00 2001 From: Li Qiong Date: Fri, 14 Mar 2025 18:16:39 +0800 Subject: usb: gadget: udc-xilinx: Remove the invalid comment The function don't return value, remove the invalid comment. Signed-off-by: Li Qiong Link: https://lore.kernel.org/r/20250314101639.424013-2-liqiong@nfschina.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/udc-xilinx.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/usb/gadget/udc/udc-xilinx.c b/drivers/usb/gadget/udc/udc-xilinx.c index ae2aeb271897..fa94cc065274 100644 --- a/drivers/usb/gadget/udc/udc-xilinx.c +++ b/drivers/usb/gadget/udc/udc-xilinx.c @@ -2178,8 +2178,6 @@ fail: /** * xudc_remove - Releases the resources allocated during the initialization. * @pdev: pointer to the platform device structure. - * - * Return: 0 always */ static void xudc_remove(struct platform_device *pdev) { -- cgit v1.2.3-59-g8ed1b From 1b4dab853768eef92fcffd57e18490e2d9641aaa Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 24 Mar 2025 13:51:42 +0100 Subject: dt-bindings: usb: smsc,usb3503: Correct indentation and style in DTS example DTS example in the bindings should be indented with 2- or 4-spaces and aligned with opening '- |', so correct any differences like 3-spaces or mixtures 2- and 4-spaces in one binding. No functional changes here, but saves some comments during reviews of new patches built on existing code. Signed-off-by: Krzysztof Kozlowski Acked-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20250324125142.81910-1-krzysztof.kozlowski@linaro.org Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/smsc,usb3503.yaml | 90 +++++++++++----------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/smsc,usb3503.yaml b/Documentation/devicetree/bindings/usb/smsc,usb3503.yaml index 6156dc26e65c..18e35122dc1f 100644 --- a/Documentation/devicetree/bindings/usb/smsc,usb3503.yaml +++ b/Documentation/devicetree/bindings/usb/smsc,usb3503.yaml @@ -106,54 +106,54 @@ additionalProperties: false examples: - | - i2c { - #address-cells = <1>; - #size-cells = <0>; - - usb-hub@8 { - compatible = "smsc,usb3503"; - reg = <0x08>; - connect-gpios = <&gpx3 0 1>; - disabled-ports = <2 3>; - intn-gpios = <&gpx3 4 1>; - reset-gpios = <&gpx3 5 1>; - initial-mode = <1>; - clocks = <&clks 80>; - clock-names = "refclk"; - }; - }; + i2c { + #address-cells = <1>; + #size-cells = <0>; + + usb-hub@8 { + compatible = "smsc,usb3503"; + reg = <0x08>; + connect-gpios = <&gpx3 0 1>; + disabled-ports = <2 3>; + intn-gpios = <&gpx3 4 1>; + reset-gpios = <&gpx3 5 1>; + initial-mode = <1>; + clocks = <&clks 80>; + clock-names = "refclk"; + }; + }; - | - i2c { - #address-cells = <1>; - #size-cells = <0>; - - usb-hub@8 { - compatible = "smsc,usb3803"; - reg = <0x08>; - connect-gpios = <&gpx3 0 1>; - disabled-ports = <2 3>; - intn-gpios = <&gpx3 4 1>; - reset-gpios = <&gpx3 5 1>; - bypass-gpios = <&gpx3 6 1>; - initial-mode = <3>; - clocks = <&clks 80>; - clock-names = "refclk"; - }; - }; + i2c { + #address-cells = <1>; + #size-cells = <0>; + + usb-hub@8 { + compatible = "smsc,usb3803"; + reg = <0x08>; + connect-gpios = <&gpx3 0 1>; + disabled-ports = <2 3>; + intn-gpios = <&gpx3 4 1>; + reset-gpios = <&gpx3 5 1>; + bypass-gpios = <&gpx3 6 1>; + initial-mode = <3>; + clocks = <&clks 80>; + clock-names = "refclk"; + }; + }; - | - #include - - usb-hub { - /* I2C is not connected */ - compatible = "smsc,usb3503"; - initial-mode = <1>; /* initialize in HUB mode */ - disabled-ports = <1>; - intn-gpios = <&pio 7 5 GPIO_ACTIVE_HIGH>; /* PH5 */ - reset-gpios = <&pio 4 16 GPIO_ACTIVE_LOW>; /* PE16 */ - connect-gpios = <&pio 4 17 GPIO_ACTIVE_HIGH>; /* PE17 */ - refclk-frequency = <19200000>; - }; + #include + + usb-hub { + /* I2C is not connected */ + compatible = "smsc,usb3503"; + initial-mode = <1>; /* initialize in HUB mode */ + disabled-ports = <1>; + intn-gpios = <&pio 7 5 GPIO_ACTIVE_HIGH>; /* PH5 */ + reset-gpios = <&pio 4 16 GPIO_ACTIVE_LOW>; /* PE16 */ + connect-gpios = <&pio 4 17 GPIO_ACTIVE_HIGH>; /* PE17 */ + refclk-frequency = <19200000>; + }; ... -- cgit v1.2.3-59-g8ed1b From 387602d8a75574fafb451b7a8215e78dfd67ee63 Mon Sep 17 00:00:00 2001 From: Robert Hodaszi Date: Thu, 3 Apr 2025 16:40:04 +0200 Subject: usb: cdc-wdm: avoid setting WDM_READ for ZLP-s Don't set WDM_READ flag in wdm_in_callback() for ZLP-s, otherwise when userspace tries to poll for available data, it might - incorrectly - believe there is something available, and when it tries to non-blocking read it, it might get stuck in the read loop. For example this is what glib does for non-blocking read (briefly): 1. poll() 2. if poll returns with non-zero, starts a read data loop: a. loop on poll() (EINTR disabled) b. if revents was set, reads data I. if read returns with EINTR or EAGAIN, goto 2.a. II. otherwise return with data So if ZLP sets WDM_READ (#1), we expect data, and try to read it (#2). But as that was a ZLP, and we are doing non-blocking read, wdm_read() returns with EAGAIN (#2.b.I), so loop again, and try to read again (#2.a.). With glib, we might stuck in this loop forever, as EINTR is disabled (#2.a). Signed-off-by: Robert Hodaszi Acked-by: Oliver Neukum Link: https://lore.kernel.org/r/20250403144004.3889125-1-robert.hodaszi@digi.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-wdm.c | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index 86ee39db013f..28c5ed840a40 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -92,7 +92,6 @@ struct wdm_device { u16 wMaxCommand; u16 wMaxPacketSize; __le16 inum; - int reslength; int length; int read; int count; @@ -214,6 +213,11 @@ static void wdm_in_callback(struct urb *urb) if (desc->rerr == 0 && status != -EPIPE) desc->rerr = status; + if (length == 0) { + dev_dbg(&desc->intf->dev, "received ZLP\n"); + goto skip_zlp; + } + if (length + desc->length > desc->wMaxCommand) { /* The buffer would overflow */ set_bit(WDM_OVERFLOW, &desc->flags); @@ -222,18 +226,18 @@ static void wdm_in_callback(struct urb *urb) if (!test_bit(WDM_OVERFLOW, &desc->flags)) { memmove(desc->ubuf + desc->length, desc->inbuf, length); desc->length += length; - desc->reslength = length; } } skip_error: if (desc->rerr) { /* - * Since there was an error, userspace may decide to not read - * any data after poll'ing. + * If there was a ZLP or an error, userspace may decide to not + * read any data after poll'ing. * We should respond to further attempts from the device to send * data, so that we can get unstuck. */ +skip_zlp: schedule_work(&desc->service_outs_intr); } else { set_bit(WDM_READ, &desc->flags); @@ -585,15 +589,6 @@ retry: goto retry; } - if (!desc->reslength) { /* zero length read */ - dev_dbg(&desc->intf->dev, "zero length - clearing WDM_READ\n"); - clear_bit(WDM_READ, &desc->flags); - rv = service_outstanding_interrupt(desc); - spin_unlock_irq(&desc->iuspin); - if (rv < 0) - goto err; - goto retry; - } cntr = desc->length; spin_unlock_irq(&desc->iuspin); } @@ -1005,7 +1000,7 @@ static void service_interrupt_work(struct work_struct *work) spin_lock_irq(&desc->iuspin); service_outstanding_interrupt(desc); - if (!desc->resp_count) { + if (!desc->resp_count && (desc->length || desc->rerr)) { set_bit(WDM_READ, &desc->flags); wake_up(&desc->wait); } -- cgit v1.2.3-59-g8ed1b From 54f9823ba75655220ee068feecd333dd3bf66d35 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Thu, 27 Mar 2025 15:31:15 -0400 Subject: usb: ehci-fsl: Fix use of private data to avoid -Wflex-array-member-not-at-end warning In the course of fixing up the usages of flexible arrays, Gustavo submitted a patch updating the ehci-fsl driver. However, the patch was wrong because the driver was using the .priv member of the ehci_hcd structure incorrectly. The private data is not supposed to be a wrapper containing the ehci_hcd structure; it is supposed to be a sub-structure stored in the .priv member. Fix the problem by replacing the ehci_fsl structure with ehci_fsl_priv, containing only the private data, along with a suitable conversion macro for accessing it. This removes the problem of having data follow a flexible array member. Reported-by: Gustavo A. R. Silva Signed-off-by: Alan Stern Link: https://lore.kernel.org/linux-usb/Z-R9BcnSzrRv5FX_@kspp/ Reviewed-by: Kees Cook Link: https://lore.kernel.org/r/8139e4cc-4e5c-40e2-9c4b-717ad3215868@rowland.harvard.edu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-fsl.c | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/drivers/usb/host/ehci-fsl.c b/drivers/usb/host/ehci-fsl.c index 26f13278d4d6..6ed2fa5418a4 100644 --- a/drivers/usb/host/ehci-fsl.c +++ b/drivers/usb/host/ehci-fsl.c @@ -410,15 +410,13 @@ static int ehci_fsl_setup(struct usb_hcd *hcd) return retval; } -struct ehci_fsl { - struct ehci_hcd ehci; - -#ifdef CONFIG_PM +struct ehci_fsl_priv { /* Saved USB PHY settings, need to restore after deep sleep. */ u32 usb_ctrl; -#endif }; +#define hcd_to_ehci_fsl_priv(h) ((struct ehci_fsl_priv *) hcd_to_ehci(h)->priv) + #ifdef CONFIG_PM #ifdef CONFIG_PPC_MPC512x @@ -566,17 +564,10 @@ static inline int ehci_fsl_mpc512x_drv_resume(struct device *dev) } #endif /* CONFIG_PPC_MPC512x */ -static struct ehci_fsl *hcd_to_ehci_fsl(struct usb_hcd *hcd) -{ - struct ehci_hcd *ehci = hcd_to_ehci(hcd); - - return container_of(ehci, struct ehci_fsl, ehci); -} - static int ehci_fsl_drv_suspend(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); - struct ehci_fsl *ehci_fsl = hcd_to_ehci_fsl(hcd); + struct ehci_fsl_priv *priv = hcd_to_ehci_fsl_priv(hcd); void __iomem *non_ehci = hcd->regs; if (of_device_is_compatible(dev->parent->of_node, @@ -589,14 +580,14 @@ static int ehci_fsl_drv_suspend(struct device *dev) if (!fsl_deep_sleep()) return 0; - ehci_fsl->usb_ctrl = ioread32be(non_ehci + FSL_SOC_USB_CTRL); + priv->usb_ctrl = ioread32be(non_ehci + FSL_SOC_USB_CTRL); return 0; } static int ehci_fsl_drv_resume(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); - struct ehci_fsl *ehci_fsl = hcd_to_ehci_fsl(hcd); + struct ehci_fsl_priv *priv = hcd_to_ehci_fsl_priv(hcd); struct ehci_hcd *ehci = hcd_to_ehci(hcd); void __iomem *non_ehci = hcd->regs; @@ -612,7 +603,7 @@ static int ehci_fsl_drv_resume(struct device *dev) usb_root_hub_lost_power(hcd->self.root_hub); /* Restore USB PHY settings and enable the controller. */ - iowrite32be(ehci_fsl->usb_ctrl, non_ehci + FSL_SOC_USB_CTRL); + iowrite32be(priv->usb_ctrl, non_ehci + FSL_SOC_USB_CTRL); ehci_reset(ehci); ehci_fsl_reinit(ehci); @@ -671,7 +662,7 @@ static int ehci_start_port_reset(struct usb_hcd *hcd, unsigned port) #endif /* CONFIG_USB_OTG */ static const struct ehci_driver_overrides ehci_fsl_overrides __initconst = { - .extra_priv_size = sizeof(struct ehci_fsl), + .extra_priv_size = sizeof(struct ehci_fsl_priv), .reset = ehci_fsl_setup, }; -- cgit v1.2.3-59-g8ed1b From 54f30ae4a367cb9c9a07df133de9ccac744688c8 Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Tue, 18 Mar 2025 23:09:05 +0800 Subject: dt-bindings: usb: chipidea: Add i.MX95 compatible string 'fsl,imx95-usb' The i.MX95 USB2.0 controller is mostly compatible with i.MX7D, except it requires a second interrupt for wakeup handling. Add the compatible string for the i.MX95 platform, add the iommus property, and enforce the interrupt property restriction. Keep the same restriction for existing compatible strings. Reviewed-by: Rob Herring (Arm) Reviewed-by: Frank Li Signed-off-by: Xu Yang Link: https://lore.kernel.org/r/20250318150908.1583652-1-xu.yang_2@nxp.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/usb/chipidea,usb2-common.yaml | 3 +++ .../devicetree/bindings/usb/chipidea,usb2-imx.yaml | 24 +++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/chipidea,usb2-common.yaml b/Documentation/devicetree/bindings/usb/chipidea,usb2-common.yaml index d2a7d2ecf48a..10020af15afc 100644 --- a/Documentation/devicetree/bindings/usb/chipidea,usb2-common.yaml +++ b/Documentation/devicetree/bindings/usb/chipidea,usb2-common.yaml @@ -42,6 +42,9 @@ properties: phy_type: true + iommus: + maxItems: 1 + itc-setting: description: interrupt threshold control register control, the setting should be diff --git a/Documentation/devicetree/bindings/usb/chipidea,usb2-imx.yaml b/Documentation/devicetree/bindings/usb/chipidea,usb2-imx.yaml index 8f6136f5d72e..51014955ab3c 100644 --- a/Documentation/devicetree/bindings/usb/chipidea,usb2-imx.yaml +++ b/Documentation/devicetree/bindings/usb/chipidea,usb2-imx.yaml @@ -41,6 +41,7 @@ properties: - fsl,imx8mm-usb - fsl,imx8mn-usb - fsl,imx93-usb + - fsl,imx95-usb - const: fsl,imx7d-usb - const: fsl,imx27-usb - items: @@ -54,7 +55,11 @@ properties: maxItems: 1 interrupts: - maxItems: 1 + minItems: 1 + items: + - description: USB controller interrupt or combine USB controller + and wakeup interrupts. + - description: Wakeup interrupt clocks: minItems: 1 @@ -191,6 +196,7 @@ allOf: contains: enum: - fsl,imx93-usb + - fsl,imx95-usb then: properties: clocks: @@ -238,6 +244,22 @@ allOf: maxItems: 1 clock-names: false + # imx95 soc use two interrupts + - if: + properties: + compatible: + contains: + enum: + - fsl,imx95-usb + then: + properties: + interrupts: + minItems: 2 + else: + properties: + interrupts: + maxItems: 1 + unevaluatedProperties: false examples: -- cgit v1.2.3-59-g8ed1b From bd3c096ce5e404ae26bf09d2383709fccb2062ba Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Tue, 18 Mar 2025 23:09:06 +0800 Subject: dt-bindings: usb: usbmisc-imx: add support for i.MX95 platform Add compatible string "fsl,imx95-usbmisc" for i.MX95 platform and restriction on reg property. Reviewed-by: Rob Herring (Arm) Reviewed-by: Frank Li Signed-off-by: Xu Yang Link: https://lore.kernel.org/r/20250318150908.1583652-2-xu.yang_2@nxp.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/fsl,usbmisc.yaml | 23 +++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/fsl,usbmisc.yaml b/Documentation/devicetree/bindings/usb/fsl,usbmisc.yaml index 0a6e7ac1b37e..019435540df0 100644 --- a/Documentation/devicetree/bindings/usb/fsl,usbmisc.yaml +++ b/Documentation/devicetree/bindings/usb/fsl,usbmisc.yaml @@ -34,6 +34,7 @@ properties: - fsl,imx8mm-usbmisc - fsl,imx8mn-usbmisc - fsl,imx8ulp-usbmisc + - fsl,imx95-usbmisc - const: fsl,imx7d-usbmisc - const: fsl,imx6q-usbmisc - items: @@ -45,7 +46,10 @@ properties: maxItems: 1 reg: - maxItems: 1 + minItems: 1 + items: + - description: Base and length of the Wrapper module register + - description: Base and length of the HSIO Block Control register '#index-cells': const: 1 @@ -56,6 +60,23 @@ required: - compatible - reg +allOf: + # imx95 soc needs use HSIO Block Control + - if: + properties: + compatible: + contains: + enum: + - fsl,imx95-usbmisc + then: + properties: + reg: + minItems: 2 + else: + properties: + reg: + maxItems: 1 + additionalProperties: false examples: -- cgit v1.2.3-59-g8ed1b From ee0dc2f7d5227956903b8b00cd57f4819375ec05 Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Tue, 18 Mar 2025 23:09:07 +0800 Subject: usb: chipidea: imx: add wakeup interrupt handling In previous imx platform, normal USB controller interrupt and wakeup interrupt are bound to one irq line. However, it changes on latest i.MX95 platform since it has a dedicated irq line for wakeup interrupt. This will add wakeup interrupt handling for i.MX95 to support various wakeup events. Acked-by: Peter Chen Signed-off-by: Xu Yang Link: https://lore.kernel.org/r/20250318150908.1583652-3-xu.yang_2@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/ci_hdrc_imx.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 1a7fc638213e..c34298ccc399 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -98,6 +99,7 @@ struct ci_hdrc_imx_data { struct clk *clk; struct clk *clk_wakeup; struct imx_usbmisc_data *usbmisc_data; + int wakeup_irq; bool supports_runtime_pm; bool override_phy_control; bool in_lpm; @@ -336,6 +338,16 @@ static int ci_hdrc_imx_notify_event(struct ci_hdrc *ci, unsigned int event) return ret; } +static irqreturn_t ci_wakeup_irq_handler(int irq, void *data) +{ + struct ci_hdrc_imx_data *imx_data = data; + + disable_irq_nosync(irq); + pm_runtime_resume(&imx_data->ci_pdev->dev); + + return IRQ_HANDLED; +} + static int ci_hdrc_imx_probe(struct platform_device *pdev) { struct ci_hdrc_imx_data *data; @@ -476,6 +488,16 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) if (pdata.flags & CI_HDRC_SUPPORTS_RUNTIME_PM) data->supports_runtime_pm = true; + data->wakeup_irq = platform_get_irq_optional(pdev, 1); + if (data->wakeup_irq > 0) { + ret = devm_request_threaded_irq(dev, data->wakeup_irq, + NULL, ci_wakeup_irq_handler, + IRQF_ONESHOT | IRQF_NO_AUTOEN, + pdata.name, data); + if (ret) + goto err_clk; + } + ret = imx_usbmisc_init(data->usbmisc_data); if (ret) { dev_err(dev, "usbmisc init failed, ret=%d\n", ret); @@ -584,6 +606,10 @@ static int imx_controller_suspend(struct device *dev, } imx_disable_unprepare_clks(dev); + + if (data->wakeup_irq > 0) + enable_irq(data->wakeup_irq); + if (data->plat_data->flags & CI_HDRC_PMQOS) cpu_latency_qos_remove_request(&data->pm_qos_req); @@ -608,6 +634,10 @@ static int imx_controller_resume(struct device *dev, if (data->plat_data->flags & CI_HDRC_PMQOS) cpu_latency_qos_add_request(&data->pm_qos_req, 0); + if (data->wakeup_irq > 0 && + !irqd_irq_disabled(irq_get_irq_data(data->wakeup_irq))) + disable_irq_nosync(data->wakeup_irq); + ret = imx_prepare_enable_clks(dev); if (ret) return ret; @@ -643,6 +673,10 @@ static int ci_hdrc_imx_suspend(struct device *dev) return ret; pinctrl_pm_select_sleep_state(dev); + + if (data->wakeup_irq > 0 && device_may_wakeup(dev)) + enable_irq_wake(data->wakeup_irq); + return ret; } @@ -651,6 +685,9 @@ static int ci_hdrc_imx_resume(struct device *dev) struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); int ret; + if (data->wakeup_irq > 0 && device_may_wakeup(dev)) + disable_irq_wake(data->wakeup_irq); + pinctrl_pm_select_default_state(dev); ret = imx_controller_resume(dev, PMSG_RESUME); if (!ret && data->supports_runtime_pm) { -- cgit v1.2.3-59-g8ed1b From 263d4fb2a2f1929c54aefcbb5cc32b08e5d38536 Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Tue, 18 Mar 2025 23:09:08 +0800 Subject: usb: chipidea: imx: add HSIO Block Control wakeup setting On i.MX95 platform, USB wakeup setting is controlled by HSIO Block Control: HSIO Block Control Overview: - The HSIO block control include configuration and status registers that provide miscellaneous top-level controls for clocking, beat limiter enables, wakeup signal enables and interrupt status for the PCIe and USB interfaces. The wakeup function of HSIO blkctl is basically same as non-core, except improvements about power lost cases. This will add the wakeup setting for HSIO blkctl on i.MX95. It will firstly ioremap hsio blkctl memory, then do wakeup setting as needs. Reviewed-by: Frank Li Reviewed-by: Jun Li Acked-by: Peter Chen Signed-off-by: Xu Yang Link: https://lore.kernel.org/r/20250318150908.1583652-4-xu.yang_2@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/usbmisc_imx.c | 77 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c index 6243d8005f5d..118b9a68496b 100644 --- a/drivers/usb/chipidea/usbmisc_imx.c +++ b/drivers/usb/chipidea/usbmisc_imx.c @@ -139,6 +139,22 @@ #define MX6_USB_OTG_WAKEUP_BITS (MX6_BM_WAKEUP_ENABLE | MX6_BM_VBUS_WAKEUP | \ MX6_BM_ID_WAKEUP | MX6SX_BM_DPDM_WAKEUP_EN) +/* + * HSIO Block Control Register + */ + +#define BLKCTL_USB_WAKEUP_CTRL 0x0 +#define BLKCTL_OTG_WAKE_ENABLE BIT(31) +#define BLKCTL_OTG_VBUS_SESSVALID BIT(4) +#define BLKCTL_OTG_ID_WAKEUP_EN BIT(2) +#define BLKCTL_OTG_VBUS_WAKEUP_EN BIT(1) +#define BLKCTL_OTG_DPDM_WAKEUP_EN BIT(0) + +#define BLKCTL_WAKEUP_SOURCE (BLKCTL_OTG_WAKE_ENABLE | \ + BLKCTL_OTG_ID_WAKEUP_EN | \ + BLKCTL_OTG_VBUS_WAKEUP_EN | \ + BLKCTL_OTG_DPDM_WAKEUP_EN) + struct usbmisc_ops { /* It's called once when probe a usb device */ int (*init)(struct imx_usbmisc_data *data); @@ -159,6 +175,7 @@ struct usbmisc_ops { struct imx_usbmisc { void __iomem *base; + void __iomem *blkctl; spinlock_t lock; const struct usbmisc_ops *ops; }; @@ -1016,6 +1033,44 @@ static int usbmisc_imx6sx_power_lost_check(struct imx_usbmisc_data *data) return 0; } +static u32 usbmisc_blkctl_wakeup_setting(struct imx_usbmisc_data *data) +{ + u32 wakeup_setting = BLKCTL_WAKEUP_SOURCE; + + if (data->ext_id || data->available_role != USB_DR_MODE_OTG) + wakeup_setting &= ~BLKCTL_OTG_ID_WAKEUP_EN; + + if (data->ext_vbus || data->available_role == USB_DR_MODE_HOST) + wakeup_setting &= ~BLKCTL_OTG_VBUS_WAKEUP_EN; + + /* Select session valid as VBUS wakeup source */ + wakeup_setting |= BLKCTL_OTG_VBUS_SESSVALID; + + return wakeup_setting; +} + +static int usbmisc_imx95_set_wakeup(struct imx_usbmisc_data *data, bool enabled) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 val; + + if (!usbmisc->blkctl) + return 0; + + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->blkctl + BLKCTL_USB_WAKEUP_CTRL); + val &= ~BLKCTL_WAKEUP_SOURCE; + + if (enabled) + val |= usbmisc_blkctl_wakeup_setting(data); + + writel(val, usbmisc->blkctl + BLKCTL_USB_WAKEUP_CTRL); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + return 0; +} + static const struct usbmisc_ops imx25_usbmisc_ops = { .init = usbmisc_imx25_init, .post = usbmisc_imx25_post, @@ -1068,6 +1123,14 @@ static const struct usbmisc_ops imx7ulp_usbmisc_ops = { .power_lost_check = usbmisc_imx7d_power_lost_check, }; +static const struct usbmisc_ops imx95_usbmisc_ops = { + .init = usbmisc_imx7d_init, + .set_wakeup = usbmisc_imx95_set_wakeup, + .charger_detection = imx7d_charger_detection, + .power_lost_check = usbmisc_imx7d_power_lost_check, + .vbus_comparator_on = usbmisc_imx7d_vbus_comparator_on, +}; + static inline bool is_imx53_usbmisc(struct imx_usbmisc_data *data) { struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); @@ -1289,6 +1352,10 @@ static const struct of_device_id usbmisc_imx_dt_ids[] = { .compatible = "fsl,imx8ulp-usbmisc", .data = &imx7ulp_usbmisc_ops, }, + { + .compatible = "fsl,imx95-usbmisc", + .data = &imx95_usbmisc_ops, + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, usbmisc_imx_dt_ids); @@ -1296,6 +1363,7 @@ MODULE_DEVICE_TABLE(of, usbmisc_imx_dt_ids); static int usbmisc_imx_probe(struct platform_device *pdev) { struct imx_usbmisc *data; + struct resource *res; data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (!data) @@ -1307,6 +1375,15 @@ static int usbmisc_imx_probe(struct platform_device *pdev) if (IS_ERR(data->base)) return PTR_ERR(data->base); + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (res) { + data->blkctl = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(data->blkctl)) + return PTR_ERR(data->blkctl); + } else if (device_is_compatible(&pdev->dev, "fsl,imx95-usbmisc")) { + dev_warn(&pdev->dev, "wakeup setting is missing\n"); + } + data->ops = of_device_get_match_data(&pdev->dev); platform_set_drvdata(pdev, data); -- cgit v1.2.3-59-g8ed1b From 82fe5107fa3d21d6c3fba091c9dbc50495588630 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 21 Mar 2025 18:49:49 +0200 Subject: usb: Add checks for snprintf() calls in usb_alloc_dev() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When creating a device path in the driver the snprintf() takes up to 16 characters long argument along with the additional up to 12 characters for the signed integer (as it can't see the actual limits) and tries to pack this into 16 bytes array. GCC complains about that when build with `make W=1`: drivers/usb/core/usb.c:705:25: note: ‘snprintf’ output between 3 and 28 bytes into a destination of size 16 Since everything works until now, let's just check for the potential buffer overflow and bail out. It is most likely a never happen situation, but at least it makes GCC happy. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250321164949.423957-1-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/usb.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 0b4685aad2d5..118fa4c93a79 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -695,15 +695,16 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent, device_set_of_node_from_dev(&dev->dev, bus->sysdev); dev_set_name(&dev->dev, "usb%d", bus->busnum); } else { + int n; + /* match any labeling on the hubs; it's one-based */ if (parent->devpath[0] == '0') { - snprintf(dev->devpath, sizeof dev->devpath, - "%d", port1); + n = snprintf(dev->devpath, sizeof(dev->devpath), "%d", port1); /* Root ports are not counted in route string */ dev->route = 0; } else { - snprintf(dev->devpath, sizeof dev->devpath, - "%s.%d", parent->devpath, port1); + n = snprintf(dev->devpath, sizeof(dev->devpath), "%s.%d", + parent->devpath, port1); /* Route string assumes hubs have less than 16 ports */ if (port1 < 15) dev->route = parent->route + @@ -712,6 +713,11 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent, dev->route = parent->route + (15 << ((parent->level - 1)*4)); } + if (n >= sizeof(dev->devpath)) { + usb_put_hcd(bus_to_hcd(bus)); + usb_put_dev(dev); + return NULL; + } dev->dev.parent = &parent->dev; dev_set_name(&dev->dev, "%d-%s", bus->busnum, dev->devpath); -- cgit v1.2.3-59-g8ed1b From 70b85914c02afe35c50d3c775ee9be0f95cb70af Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 14 Mar 2025 16:19:56 +0200 Subject: usb: hub: Block less in USB3 link power management LPM disable path Several usb requests are needed to allow or forbid a USB3 link from going into U1 or U2 hardware link power management (LPM) states. Fail fast on issues in LPM disabling path. LPM disabling is done in hub workqueue paths that are often already handling possible link issues. Enabling and disabling LPM involves four usb requests. Two requests sent to the upstream hub of the connected device: SetPortFeature(U1_TIMEOUT) SetPortFeature(U2_TIMEOUT) And two to the device itself: SetFeature(U1_ENABLE) SetFeature(U2_ENABLE) The requests to the hub sets the inactivity timeout used by the hub to know when to initiate U1 and U2 LPM link state transitions. These requests are also used prevent U1/U2 LPM transitions completely by passing zero timeout value. The requsts sent to the device only controls if device is allowed to initiate U1/U2 transitions. If not enabled then only hub initiates U1/U2 transitions. Hub may block these device initiated attempts. Reorder and send the hub requests first, these are more likely to succeed due to shorter path, and we can consider LPM disabled if these succeed as U1/U2 link state can not be entered after that. Fail immediately if a request fails, and don't try to enable back LPM after a failed request, that will just send more LPM requests over a bad link. If a device request controlling device initiateed LPM fails then exit immediately, but consider LPM disabled at this stage. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250314142000.93090-2-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 0e1dd6ef60a7..577516683318 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -4393,8 +4393,6 @@ static int usb_disable_link_state(struct usb_hcd *hcd, struct usb_device *udev, if (usb_set_lpm_timeout(udev, state, 0)) return -EBUSY; - usb_set_device_initiated_lpm(udev, state, false); - if (hcd->driver->disable_usb3_lpm_timeout(hcd, udev, state)) dev_warn(&udev->dev, "Could not disable xHCI %s timeout, " "bus schedule bandwidth may be impacted.\n", @@ -4424,6 +4422,7 @@ static int usb_disable_link_state(struct usb_hcd *hcd, struct usb_device *udev, int usb_disable_lpm(struct usb_device *udev) { struct usb_hcd *hcd; + int err; if (!udev || !udev->parent || udev->speed < USB_SPEED_SUPER || @@ -4441,14 +4440,19 @@ int usb_disable_lpm(struct usb_device *udev) /* If LPM is enabled, attempt to disable it. */ if (usb_disable_link_state(hcd, udev, USB3_LPM_U1)) - goto enable_lpm; + goto disable_failed; if (usb_disable_link_state(hcd, udev, USB3_LPM_U2)) - goto enable_lpm; + goto disable_failed; + + err = usb_set_device_initiated_lpm(udev, USB3_LPM_U1, false); + if (!err) + usb_set_device_initiated_lpm(udev, USB3_LPM_U2, false); return 0; -enable_lpm: - usb_enable_lpm(udev); +disable_failed: + udev->lpm_disable_count--; + return -EBUSY; } EXPORT_SYMBOL_GPL(usb_disable_lpm); -- cgit v1.2.3-59-g8ed1b From c8be504beb1e3110d3e037fb578f3c1cb586dc85 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 14 Mar 2025 16:19:57 +0200 Subject: usb: hub: verify device is configured in usb_device_may_initiate_lpm() Move device configured check into usb_device_may_initiate_lpm() instead of calling it before the function. No functional changes, helps rework to fail faster during link power management (LPM) enabling. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250314142000.93090-3-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 577516683318..9b5ef4147891 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -4234,9 +4234,9 @@ static int usb_set_lpm_timeout(struct usb_device *udev, } /* - * Don't allow device intiated U1/U2 if the system exit latency + one bus - * interval is greater than the minimum service interval of any active - * periodic endpoint. See USB 3.2 section 9.4.9 + * Don't allow device intiated U1/U2 if device isn't in the configured state, + * or the system exit latency + one bus interval is greater than the minimum + * service interval of any active periodic endpoint. See USB 3.2 section 9.4.9 */ static bool usb_device_may_initiate_lpm(struct usb_device *udev, enum usb3_link_state state) @@ -4244,7 +4244,7 @@ static bool usb_device_may_initiate_lpm(struct usb_device *udev, unsigned int sel; /* us */ int i, j; - if (!udev->lpm_devinit_allow) + if (!udev->lpm_devinit_allow || !udev->actconfig) return false; if (state == USB3_LPM_U1) @@ -4341,11 +4341,11 @@ static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev, return; } - /* Only a configured device will accept the Set Feature - * U1/U2_ENABLE + /* + * Enable device initiated U1/U2 with a SetFeature(U1/U2_ENABLE) request + * if system exit latency is short enough and device is configured */ - if (udev->actconfig && - usb_device_may_initiate_lpm(udev, state)) { + if (usb_device_may_initiate_lpm(udev, state)) { if (usb_set_device_initiated_lpm(udev, state, true)) { /* * Request to enable device initiated U1/U2 failed, -- cgit v1.2.3-59-g8ed1b From bf6e36a033140da67238bce15e1199e37065810d Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 14 Mar 2025 16:19:58 +0200 Subject: usb: hub: Don't disable LPM completely if device initiated LPM fails Enabling device initiated USB3 link power management (LPM) may fail for various reasons such as too long system exit latency, or link issues. These are not good reason to disable hub initiated LPM U1/U2 states, especially as it requires sending more requests over a possibly broken link, causing the hub work to block for even longer. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250314142000.93090-4-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 9b5ef4147891..1b89786ecb54 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -4345,17 +4345,8 @@ static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev, * Enable device initiated U1/U2 with a SetFeature(U1/U2_ENABLE) request * if system exit latency is short enough and device is configured */ - if (usb_device_may_initiate_lpm(udev, state)) { - if (usb_set_device_initiated_lpm(udev, state, true)) { - /* - * Request to enable device initiated U1/U2 failed, - * better to turn off lpm in this case. - */ - usb_set_lpm_timeout(udev, state, 0); - hcd->driver->disable_usb3_lpm_timeout(hcd, udev, state); - return; - } - } + if (usb_device_may_initiate_lpm(udev, state)) + usb_set_device_initiated_lpm(udev, state, true); if (state == USB3_LPM_U1) udev->usb3_lpm_u1_enabled = 1; -- cgit v1.2.3-59-g8ed1b From bf11662f71dc4af6ad4eb402542ad377898b5ac7 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 14 Mar 2025 16:19:59 +0200 Subject: usb: hub: reorder USB3 link power management enable requests Several usb requests are needed to allow a USB3 link to enter U1/U2 hardware link power management LPM states. Reorder these requests and send the more significant and likely to succeed first. This is similar to the change done for disabling LPM Enable LPM by first sending requests to the upstream hub of the device SetPortFeature(U1_TIMEOUT) SetPortFeature(U2_TIMEOUT) These are more likely to succeed due to the shorter path, and LPM can be considered enabled as link may go to U1/U2 LPM states after those. Send the requests to the device after this, they allow the device to initialte U1/U2 link transitions. Hub can already initiate U1/U2 SetFeature(U1_ENABLE) SetFeature(U2_ENABLE) Fail fast and bail out if a requests to the device fails. This changes device initated LPM policy a bit. Device is no longer able to initiate U2 if it failed or is not allowed to initiate U1. Enabling and disabling Link power management is done as part of hub work. Avoid trying to send additional USB requests to a device when there are known issues. It just causes hub work to block for even longer. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250314142000.93090-5-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 1b89786ecb54..e3929c0cd067 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -4160,7 +4160,7 @@ static int usb_set_device_initiated_lpm(struct usb_device *udev, "for unconfigured device.\n", __func__, str_enable_disable(enable), usb3_lpm_names[state]); - return 0; + return -EINVAL; } if (enable) { @@ -4341,13 +4341,6 @@ static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev, return; } - /* - * Enable device initiated U1/U2 with a SetFeature(U1/U2_ENABLE) request - * if system exit latency is short enough and device is configured - */ - if (usb_device_may_initiate_lpm(udev, state)) - usb_set_device_initiated_lpm(udev, state, true); - if (state == USB3_LPM_U1) udev->usb3_lpm_u1_enabled = 1; else if (state == USB3_LPM_U2) @@ -4508,6 +4501,18 @@ void usb_enable_lpm(struct usb_device *udev) if (port_dev->usb3_lpm_u2_permit) usb_enable_link_state(hcd, udev, USB3_LPM_U2); + + /* + * Enable device initiated U1/U2 with a SetFeature(U1/U2_ENABLE) request + * if system exit latency is short enough and device is configured + */ + if (usb_device_may_initiate_lpm(udev, USB3_LPM_U1)) { + if (usb_set_device_initiated_lpm(udev, USB3_LPM_U1, true)) + return; + + if (usb_device_may_initiate_lpm(udev, USB3_LPM_U2)) + usb_set_device_initiated_lpm(udev, USB3_LPM_U2, true); + } } EXPORT_SYMBOL_GPL(usb_enable_lpm); -- cgit v1.2.3-59-g8ed1b From a02dcd3b616ac481993efebde1bebb8529eea10c Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 14 Mar 2025 16:20:00 +0200 Subject: usb: hub: Fail fast in USB3 link power management enable path Enabling LPM is done in hub workqueue, often in paths handling possible link issues. So fail immediately on USB3 LPM issues and avoid hub wq from unnecessary blocking, thus allowing it to handle other port events faster. Detect errors when enabling U1/U2 link states, and return immediately if there is an issue. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250314142000.93090-6-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index e3929c0cd067..cfb3abafeacd 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -4292,7 +4292,7 @@ static bool usb_device_may_initiate_lpm(struct usb_device *udev, * driver know about it. If that call fails, it should be harmless, and just * take up more slightly more bus bandwidth for unnecessary U1/U2 exit latency. */ -static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev, +static int usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev, enum usb3_link_state state) { int timeout; @@ -4301,7 +4301,7 @@ static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev, /* Skip if the device BOS descriptor couldn't be read */ if (!udev->bos) - return; + return -EINVAL; u1_mel = udev->bos->ss_cap->bU1devExitLat; u2_mel = udev->bos->ss_cap->bU2DevExitLat; @@ -4312,7 +4312,7 @@ static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev, */ if ((state == USB3_LPM_U1 && u1_mel == 0) || (state == USB3_LPM_U2 && u2_mel == 0)) - return; + return -EINVAL; /* We allow the host controller to set the U1/U2 timeout internally * first, so that it can change its schedule to account for the @@ -4323,13 +4323,13 @@ static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev, /* xHCI host controller doesn't want to enable this LPM state. */ if (timeout == 0) - return; + return -EINVAL; if (timeout < 0) { dev_warn(&udev->dev, "Could not enable %s link state, " "xHCI error %i.\n", usb3_lpm_names[state], timeout); - return; + return timeout; } if (usb_set_lpm_timeout(udev, state, timeout)) { @@ -4338,13 +4338,15 @@ static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev, * host know that this link state won't be enabled. */ hcd->driver->disable_usb3_lpm_timeout(hcd, udev, state); - return; + return -EBUSY; } if (state == USB3_LPM_U1) udev->usb3_lpm_u1_enabled = 1; else if (state == USB3_LPM_U2) udev->usb3_lpm_u2_enabled = 1; + + return 0; } /* * Disable the hub-initiated U1/U2 idle timeouts, and disable device-initiated @@ -4497,10 +4499,12 @@ void usb_enable_lpm(struct usb_device *udev) port_dev = hub->ports[udev->portnum - 1]; if (port_dev->usb3_lpm_u1_permit) - usb_enable_link_state(hcd, udev, USB3_LPM_U1); + if (usb_enable_link_state(hcd, udev, USB3_LPM_U1)) + return; if (port_dev->usb3_lpm_u2_permit) - usb_enable_link_state(hcd, udev, USB3_LPM_U2); + if (usb_enable_link_state(hcd, udev, USB3_LPM_U2)) + return; /* * Enable device initiated U1/U2 with a SetFeature(U1/U2_ENABLE) request -- cgit v1.2.3-59-g8ed1b From b9cff71c509bd97880e277e798e7dbf8a853192b Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 7 Apr 2025 11:50:00 +0100 Subject: usb: renesas_usbhs: Correct function references in comment Update the comment to reference `usbhs_mod_probe()` instead of `usbhs_mod_init()`, and replace `dev_set_drvdata()` with `platform_set_drvdata()`, as these are the correct functions used in this context. Signed-off-by: Lad Prabhakar Acked-by: Kuninori Morimoto Reviewed-by: Yoshihiro Shimoda Link: https://lore.kernel.org/r/20250407105002.107181-2-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/renesas_usbhs/common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index 4b35ef216125..03d4d40c90b3 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -698,7 +698,7 @@ static int usbhs_probe(struct platform_device *pdev) if (ret < 0) goto probe_end_fifo_exit; - /* dev_set_drvdata should be called after usbhs_mod_init */ + /* platform_set_drvdata() should be called after usbhs_mod_probe() */ platform_set_drvdata(pdev, priv); ret = reset_control_deassert(priv->rsts); -- cgit v1.2.3-59-g8ed1b From 8fb4c9d7c113f9987bfae79f308b2e498cc9792e Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 7 Apr 2025 11:50:01 +0100 Subject: usb: renesas_usbhs: Fix typo in comment Fix a typo in the comment by correcting "deviece" to "device" for clarity and readability. Signed-off-by: Lad Prabhakar Acked-by: Kuninori Morimoto Reviewed-by: Yoshihiro Shimoda Link: https://lore.kernel.org/r/20250407105002.107181-3-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/renesas_usbhs/common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index 03d4d40c90b3..703cf5d0cb41 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -710,7 +710,7 @@ static int usbhs_probe(struct platform_device *pdev) goto probe_fail_clks; /* - * deviece reset here because + * device reset here because * USB device might be used in boot loader. */ usbhs_sys_clock_ctrl(priv, 0); -- cgit v1.2.3-59-g8ed1b From ffb34a60ce86656ba12d46e91f1ccc71dd221251 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 7 Apr 2025 11:50:02 +0100 Subject: usb: renesas_usbhs: Reorder clock handling and power management in probe Reorder the initialization sequence in `usbhs_probe()` to enable runtime PM before accessing registers, preventing potential crashes due to uninitialized clocks. Currently, in the probe path, registers are accessed before enabling the clocks, leading to a synchronous external abort on the RZ/V2H SoC. The problematic call flow is as follows: usbhs_probe() usbhs_sys_clock_ctrl() usbhs_bset() usbhs_write() iowrite16() <-- Register access before enabling clocks Since `iowrite16()` is performed without ensuring the required clocks are enabled, this can lead to access errors. To fix this, enable PM runtime early in the probe function and ensure clocks are acquired before register access, preventing crashes like the following on RZ/V2H: [13.272640] Internal error: synchronous external abort: 0000000096000010 [#1] PREEMPT SMP [13.280814] Modules linked in: cec renesas_usbhs(+) drm_kms_helper fuse drm backlight ipv6 [13.289088] CPU: 1 UID: 0 PID: 195 Comm: (udev-worker) Not tainted 6.14.0-rc7+ #98 [13.296640] Hardware name: Renesas RZ/V2H EVK Board based on r9a09g057h44 (DT) [13.303834] pstate: 60400005 (nZCv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--) [13.310770] pc : usbhs_bset+0x14/0x4c [renesas_usbhs] [13.315831] lr : usbhs_probe+0x2e4/0x5ac [renesas_usbhs] [13.321138] sp : ffff8000827e3850 [13.324438] x29: ffff8000827e3860 x28: 0000000000000000 x27: ffff8000827e3ca0 [13.331554] x26: ffff8000827e3ba0 x25: ffff800081729668 x24: 0000000000000025 [13.338670] x23: ffff0000c0f08000 x22: 0000000000000000 x21: ffff0000c0f08010 [13.345783] x20: 0000000000000000 x19: ffff0000c3b52080 x18: 00000000ffffffff [13.352895] x17: 0000000000000000 x16: 0000000000000000 x15: ffff8000827e36ce [13.360009] x14: 00000000000003d7 x13: 00000000000003d7 x12: 0000000000000000 [13.367122] x11: 0000000000000000 x10: 0000000000000aa0 x9 : ffff8000827e3750 [13.374235] x8 : ffff0000c1850b00 x7 : 0000000003826060 x6 : 000000000000001c [13.381347] x5 : 000000030d5fcc00 x4 : ffff8000825c0000 x3 : 0000000000000000 [13.388459] x2 : 0000000000000400 x1 : 0000000000000000 x0 : ffff0000c3b52080 [13.395574] Call trace: [13.398013] usbhs_bset+0x14/0x4c [renesas_usbhs] (P) [13.403076] platform_probe+0x68/0xdc [13.406738] really_probe+0xbc/0x2c0 [13.410306] __driver_probe_device+0x78/0x120 [13.414653] driver_probe_device+0x3c/0x154 [13.418825] __driver_attach+0x90/0x1a0 [13.422647] bus_for_each_dev+0x7c/0xe0 [13.426470] driver_attach+0x24/0x30 [13.430032] bus_add_driver+0xe4/0x208 [13.433766] driver_register+0x68/0x130 [13.437587] __platform_driver_register+0x24/0x30 [13.442273] renesas_usbhs_driver_init+0x20/0x1000 [renesas_usbhs] [13.448450] do_one_initcall+0x60/0x1d4 [13.452276] do_init_module+0x54/0x1f8 [13.456014] load_module+0x1754/0x1c98 [13.459750] init_module_from_file+0x88/0xcc [13.464004] __arm64_sys_finit_module+0x1c4/0x328 [13.468689] invoke_syscall+0x48/0x104 [13.472426] el0_svc_common.constprop.0+0xc0/0xe0 [13.477113] do_el0_svc+0x1c/0x28 [13.480415] el0_svc+0x30/0xcc [13.483460] el0t_64_sync_handler+0x10c/0x138 [13.487800] el0t_64_sync+0x198/0x19c [13.491453] Code: 2a0103e1 12003c42 12003c63 8b010084 (79400084) [13.497522] ---[ end trace 0000000000000000 ]--- Fixes: f1407d5c66240 ("usb: renesas_usbhs: Add Renesas USBHS common code") Signed-off-by: Lad Prabhakar Reviewed-by: Yoshihiro Shimoda Tested-by: Yoshihiro Shimoda Link: https://lore.kernel.org/r/20250407105002.107181-4-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/renesas_usbhs/common.c | 50 +++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index 703cf5d0cb41..f52418fe3fd4 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -685,10 +685,29 @@ static int usbhs_probe(struct platform_device *pdev) INIT_DELAYED_WORK(&priv->notify_hotplug_work, usbhsc_notify_hotplug); spin_lock_init(usbhs_priv_to_lock(priv)); + /* + * Acquire clocks and enable power management (PM) early in the + * probe process, as the driver accesses registers during + * initialization. Ensure the device is active before proceeding. + */ + pm_runtime_enable(dev); + + ret = usbhsc_clk_get(dev, priv); + if (ret) + goto probe_pm_disable; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + goto probe_clk_put; + + ret = usbhsc_clk_prepare_enable(priv); + if (ret) + goto probe_pm_put; + /* call pipe and module init */ ret = usbhs_pipe_probe(priv); if (ret < 0) - return ret; + goto probe_clk_dis_unprepare; ret = usbhs_fifo_probe(priv); if (ret < 0) @@ -705,10 +724,6 @@ static int usbhs_probe(struct platform_device *pdev) if (ret) goto probe_fail_rst; - ret = usbhsc_clk_get(dev, priv); - if (ret) - goto probe_fail_clks; - /* * device reset here because * USB device might be used in boot loader. @@ -721,7 +736,7 @@ static int usbhs_probe(struct platform_device *pdev) if (ret) { dev_warn(dev, "USB function not selected (GPIO)\n"); ret = -ENOTSUPP; - goto probe_end_mod_exit; + goto probe_assert_rest; } } @@ -735,14 +750,19 @@ static int usbhs_probe(struct platform_device *pdev) ret = usbhs_platform_call(priv, hardware_init, pdev); if (ret < 0) { dev_err(dev, "platform init failed.\n"); - goto probe_end_mod_exit; + goto probe_assert_rest; } /* reset phy for connection */ usbhs_platform_call(priv, phy_reset, pdev); - /* power control */ - pm_runtime_enable(dev); + /* + * Disable the clocks that were enabled earlier in the probe path, + * and let the driver handle the clocks beyond this point. + */ + usbhsc_clk_disable_unprepare(priv); + pm_runtime_put(dev); + if (!usbhs_get_dparam(priv, runtime_pwctrl)) { usbhsc_power_ctrl(priv, 1); usbhs_mod_autonomy_mode(priv); @@ -759,9 +779,7 @@ static int usbhs_probe(struct platform_device *pdev) return ret; -probe_end_mod_exit: - usbhsc_clk_put(priv); -probe_fail_clks: +probe_assert_rest: reset_control_assert(priv->rsts); probe_fail_rst: usbhs_mod_remove(priv); @@ -769,6 +787,14 @@ probe_end_fifo_exit: usbhs_fifo_remove(priv); probe_end_pipe_exit: usbhs_pipe_remove(priv); +probe_clk_dis_unprepare: + usbhsc_clk_disable_unprepare(priv); +probe_pm_put: + pm_runtime_put(dev); +probe_clk_put: + usbhsc_clk_put(priv); +probe_pm_disable: + pm_runtime_disable(dev); dev_info(dev, "probe failed (%d)\n", ret); -- cgit v1.2.3-59-g8ed1b From d4e5b10c55627e2f3fc9e5b337a28b4e2f02a55e Mon Sep 17 00:00:00 2001 From: Chance Yang Date: Fri, 11 Apr 2025 16:33:26 +0800 Subject: usb: common: usb-conn-gpio: use a unique name for usb connector device The current implementation of the usb-conn-gpio driver uses a fixed "usb-charger" name for all USB connector devices. This causes conflicts in the power supply subsystem when multiple USB connectors are present, as duplicate names are not allowed. Use IDA to manage unique IDs for naming usb connectors (e.g., usb-charger-0, usb-charger-1). Signed-off-by: Chance Yang Link: https://lore.kernel.org/r/20250411-work-next-v3-1-7cd9aa80190c@kneron.us Signed-off-by: Greg Kroah-Hartman --- drivers/usb/common/usb-conn-gpio.c | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/drivers/usb/common/usb-conn-gpio.c b/drivers/usb/common/usb-conn-gpio.c index 1e36be2a28fd..421c3af38e06 100644 --- a/drivers/usb/common/usb-conn-gpio.c +++ b/drivers/usb/common/usb-conn-gpio.c @@ -21,6 +21,9 @@ #include #include #include +#include + +static DEFINE_IDA(usb_conn_ida); #define USB_GPIO_DEB_MS 20 /* ms */ #define USB_GPIO_DEB_US ((USB_GPIO_DEB_MS) * 1000) /* us */ @@ -30,6 +33,7 @@ struct usb_conn_info { struct device *dev; + int conn_id; /* store the IDA-allocated ID */ struct usb_role_switch *role_sw; enum usb_role last_role; struct regulator *vbus; @@ -161,7 +165,17 @@ static int usb_conn_psy_register(struct usb_conn_info *info) .fwnode = dev_fwnode(dev), }; - desc->name = "usb-charger"; + info->conn_id = ida_alloc(&usb_conn_ida, GFP_KERNEL); + if (info->conn_id < 0) + return info->conn_id; + + desc->name = devm_kasprintf(dev, GFP_KERNEL, "usb-charger-%d", + info->conn_id); + if (!desc->name) { + ida_free(&usb_conn_ida, info->conn_id); + return -ENOMEM; + } + desc->properties = usb_charger_properties; desc->num_properties = ARRAY_SIZE(usb_charger_properties); desc->get_property = usb_charger_get_property; @@ -169,8 +183,10 @@ static int usb_conn_psy_register(struct usb_conn_info *info) cfg.drv_data = info; info->charger = devm_power_supply_register(dev, desc, &cfg); - if (IS_ERR(info->charger)) - dev_err(dev, "Unable to register charger\n"); + if (IS_ERR(info->charger)) { + dev_err(dev, "Unable to register charger %d\n", info->conn_id); + ida_free(&usb_conn_ida, info->conn_id); + } return PTR_ERR_OR_ZERO(info->charger); } @@ -278,6 +294,9 @@ static void usb_conn_remove(struct platform_device *pdev) cancel_delayed_work_sync(&info->dw_det); + if (info->charger) + ida_free(&usb_conn_ida, info->conn_id); + if (info->last_role == USB_ROLE_HOST && info->vbus) regulator_disable(info->vbus); -- cgit v1.2.3-59-g8ed1b From 1d73df245b19579109193372e7ffe1601ca19323 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Mon, 7 Apr 2025 14:17:39 -0500 Subject: usb: Remove orphaned UDC drivers These drivers have no way to probe as there are no match tables nor devices created with a matching name in the kernel tree. Marvell UDC was only ever supported by board files which were removed in 2022. For Marvell U3D, which was added in 2012, the PXA2128 aka MMP3 support was never upstreamed with board files and only revived in 2019 with DT support. No U3D DT support has been added since then. The PLX net2272 driver was formerly used on blackfin. It also has PCI support, but that appears to be only for a development board which is likely unused given this device dates back to 2006. Cc: Lubomir Rintel Acked-by: Arnd Bergmann Signed-off-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20250407191756.3584261-1-robh@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/Kconfig | 44 - drivers/usb/gadget/udc/Makefile | 5 - drivers/usb/gadget/udc/fusb300_udc.c | 1516 ------------------- drivers/usb/gadget/udc/fusb300_udc.h | 675 --------- drivers/usb/gadget/udc/mv_u3d.h | 317 ---- drivers/usb/gadget/udc/mv_u3d_core.c | 2062 ------------------------- drivers/usb/gadget/udc/mv_udc.h | 309 ---- drivers/usb/gadget/udc/mv_udc_core.c | 2426 ------------------------------ drivers/usb/gadget/udc/net2272.c | 2723 ---------------------------------- drivers/usb/gadget/udc/net2272.h | 584 -------- drivers/usb/phy/Kconfig | 12 - drivers/usb/phy/Makefile | 1 - drivers/usb/phy/phy-mv-usb.c | 881 ----------- 13 files changed, 11555 deletions(-) delete mode 100644 drivers/usb/gadget/udc/fusb300_udc.c delete mode 100644 drivers/usb/gadget/udc/fusb300_udc.h delete mode 100644 drivers/usb/gadget/udc/mv_u3d.h delete mode 100644 drivers/usb/gadget/udc/mv_u3d_core.c delete mode 100644 drivers/usb/gadget/udc/mv_udc.h delete mode 100644 drivers/usb/gadget/udc/mv_udc_core.c delete mode 100644 drivers/usb/gadget/udc/net2272.c delete mode 100644 drivers/usb/gadget/udc/net2272.h delete mode 100644 drivers/usb/phy/phy-mv-usb.c diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index aae1787320d4..26460340fbc9 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -102,12 +102,6 @@ config USB_FSL_USB2 dynamically linked module called "fsl_usb2_udc" and force all gadget drivers to also be dynamically linked. -config USB_FUSB300 - tristate "Faraday FUSB300 USB Peripheral Controller" - depends on !PHYS_ADDR_T_64BIT && HAS_DMA - help - Faraday usb device controller FUSB300 driver - config USB_GR_UDC tristate "Aeroflex Gaisler GRUSBDC USB Peripheral Controller Driver" depends on HAS_DMA @@ -228,21 +222,6 @@ config USB_PXA27X dynamically linked module called "pxa27x_udc" and force all gadget drivers to also be dynamically linked. -config USB_MV_UDC - tristate "Marvell USB2.0 Device Controller" - depends on HAS_DMA - help - Marvell Socs (including PXA and MMP series) include a high speed - USB2.0 OTG controller, which can be configured as high speed or - full speed USB peripheral. - -config USB_MV_U3D - depends on HAS_DMA - tristate "MARVELL PXA2128 USB 3.0 controller" - help - MARVELL PXA2128 Processor series include a super speed USB3.0 device - controller, which support super speed USB peripheral. - config USB_SNP_CORE depends on (USB_AMD5536UDC || USB_SNP_UDC_PLAT) depends on HAS_DMA @@ -326,29 +305,6 @@ config USB_FSL_QE Set CONFIG_USB_GADGET to "m" to build this driver as a dynamically linked module called "fsl_qe_udc". -config USB_NET2272 - depends on HAS_IOMEM - tristate "PLX NET2272" - help - PLX NET2272 is a USB peripheral controller which supports - both full and high speed USB 2.0 data transfers. - - It has three configurable endpoints, as well as endpoint zero - (for control transfer). - Say "y" to link the driver statically, or "m" to build a - dynamically linked module called "net2272" and force all - gadget drivers to also be dynamically linked. - -config USB_NET2272_DMA - bool "Support external DMA controller" - depends on USB_NET2272 && HAS_DMA - help - The NET2272 part can optionally support an external DMA - controller, but your board has to have support in the - driver itself. - - If unsure, say "N" here. The driver works fine in PIO mode. - config USB_NET2280 tristate "NetChip NET228x / PLX USB3x8x" depends on USB_PCI diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile index b52f93e9c61d..1b9b1a4f9c57 100644 --- a/drivers/usb/gadget/udc/Makefile +++ b/drivers/usb/gadget/udc/Makefile @@ -9,7 +9,6 @@ udc-core-y := core.o trace.o # obj-$(CONFIG_USB_GADGET) += udc-core.o obj-$(CONFIG_USB_DUMMY_HCD) += dummy_hcd.o -obj-$(CONFIG_USB_NET2272) += net2272.o obj-$(CONFIG_USB_NET2280) += net2280.o obj-$(CONFIG_USB_SNP_CORE) += snps_udc_core.o obj-$(CONFIG_USB_AMD5536UDC) += amd5536udc_pci.o @@ -31,10 +30,6 @@ obj-$(CONFIG_USB_RENESAS_USBF) += renesas_usbf.o obj-$(CONFIG_USB_FSL_QE) += fsl_qe_udc.o obj-$(CONFIG_USB_LPC32XX) += lpc32xx_udc.o obj-$(CONFIG_USB_EG20T) += pch_udc.o -obj-$(CONFIG_USB_MV_UDC) += mv_udc.o -mv_udc-y := mv_udc_core.o -obj-$(CONFIG_USB_FUSB300) += fusb300_udc.o -obj-$(CONFIG_USB_MV_U3D) += mv_u3d_core.o obj-$(CONFIG_USB_GR_UDC) += gr_udc.o obj-$(CONFIG_USB_GADGET_XILINX) += udc-xilinx.o obj-$(CONFIG_USB_SNP_UDC_PLAT) += snps_udc_plat.o diff --git a/drivers/usb/gadget/udc/fusb300_udc.c b/drivers/usb/gadget/udc/fusb300_udc.c deleted file mode 100644 index 5e94a99b3e53..000000000000 --- a/drivers/usb/gadget/udc/fusb300_udc.c +++ /dev/null @@ -1,1516 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Fusb300 UDC (USB gadget) - * - * Copyright (C) 2010 Faraday Technology Corp. - * - * Author : Yuan-hsin Chen - */ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "fusb300_udc.h" - -MODULE_DESCRIPTION("FUSB300 USB gadget driver"); -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Yuan-Hsin Chen, Feng-Hsin Chiang "); -MODULE_ALIAS("platform:fusb300_udc"); - -#define DRIVER_VERSION "20 October 2010" - -static const char udc_name[] = "fusb300_udc"; -static const char * const fusb300_ep_name[] = { - "ep0", "ep1", "ep2", "ep3", "ep4", "ep5", "ep6", "ep7", "ep8", "ep9", - "ep10", "ep11", "ep12", "ep13", "ep14", "ep15" -}; - -static void done(struct fusb300_ep *ep, struct fusb300_request *req, - int status); - -static void fusb300_enable_bit(struct fusb300 *fusb300, u32 offset, - u32 value) -{ - u32 reg = ioread32(fusb300->reg + offset); - - reg |= value; - iowrite32(reg, fusb300->reg + offset); -} - -static void fusb300_disable_bit(struct fusb300 *fusb300, u32 offset, - u32 value) -{ - u32 reg = ioread32(fusb300->reg + offset); - - reg &= ~value; - iowrite32(reg, fusb300->reg + offset); -} - - -static void fusb300_ep_setting(struct fusb300_ep *ep, - struct fusb300_ep_info info) -{ - ep->epnum = info.epnum; - ep->type = info.type; -} - -static int fusb300_ep_release(struct fusb300_ep *ep) -{ - if (!ep->epnum) - return 0; - ep->epnum = 0; - ep->stall = 0; - ep->wedged = 0; - return 0; -} - -static void fusb300_set_fifo_entry(struct fusb300 *fusb300, - u32 ep) -{ - u32 val = ioread32(fusb300->reg + FUSB300_OFFSET_EPSET1(ep)); - - val &= ~FUSB300_EPSET1_FIFOENTRY_MSK; - val |= FUSB300_EPSET1_FIFOENTRY(FUSB300_FIFO_ENTRY_NUM); - iowrite32(val, fusb300->reg + FUSB300_OFFSET_EPSET1(ep)); -} - -static void fusb300_set_start_entry(struct fusb300 *fusb300, - u8 ep) -{ - u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_EPSET1(ep)); - u32 start_entry = fusb300->fifo_entry_num * FUSB300_FIFO_ENTRY_NUM; - - reg &= ~FUSB300_EPSET1_START_ENTRY_MSK ; - reg |= FUSB300_EPSET1_START_ENTRY(start_entry); - iowrite32(reg, fusb300->reg + FUSB300_OFFSET_EPSET1(ep)); - if (fusb300->fifo_entry_num == FUSB300_MAX_FIFO_ENTRY) { - fusb300->fifo_entry_num = 0; - fusb300->addrofs = 0; - pr_err("fifo entry is over the maximum number!\n"); - } else - fusb300->fifo_entry_num++; -} - -/* set fusb300_set_start_entry first before fusb300_set_epaddrofs */ -static void fusb300_set_epaddrofs(struct fusb300 *fusb300, - struct fusb300_ep_info info) -{ - u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_EPSET2(info.epnum)); - - reg &= ~FUSB300_EPSET2_ADDROFS_MSK; - reg |= FUSB300_EPSET2_ADDROFS(fusb300->addrofs); - iowrite32(reg, fusb300->reg + FUSB300_OFFSET_EPSET2(info.epnum)); - fusb300->addrofs += (info.maxpacket + 7) / 8 * FUSB300_FIFO_ENTRY_NUM; -} - -static void ep_fifo_setting(struct fusb300 *fusb300, - struct fusb300_ep_info info) -{ - fusb300_set_fifo_entry(fusb300, info.epnum); - fusb300_set_start_entry(fusb300, info.epnum); - fusb300_set_epaddrofs(fusb300, info); -} - -static void fusb300_set_eptype(struct fusb300 *fusb300, - struct fusb300_ep_info info) -{ - u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_EPSET1(info.epnum)); - - reg &= ~FUSB300_EPSET1_TYPE_MSK; - reg |= FUSB300_EPSET1_TYPE(info.type); - iowrite32(reg, fusb300->reg + FUSB300_OFFSET_EPSET1(info.epnum)); -} - -static void fusb300_set_epdir(struct fusb300 *fusb300, - struct fusb300_ep_info info) -{ - u32 reg; - - if (!info.dir_in) - return; - reg = ioread32(fusb300->reg + FUSB300_OFFSET_EPSET1(info.epnum)); - reg &= ~FUSB300_EPSET1_DIR_MSK; - reg |= FUSB300_EPSET1_DIRIN; - iowrite32(reg, fusb300->reg + FUSB300_OFFSET_EPSET1(info.epnum)); -} - -static void fusb300_set_ep_active(struct fusb300 *fusb300, - u8 ep) -{ - u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_EPSET1(ep)); - - reg |= FUSB300_EPSET1_ACTEN; - iowrite32(reg, fusb300->reg + FUSB300_OFFSET_EPSET1(ep)); -} - -static void fusb300_set_epmps(struct fusb300 *fusb300, - struct fusb300_ep_info info) -{ - u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_EPSET2(info.epnum)); - - reg &= ~FUSB300_EPSET2_MPS_MSK; - reg |= FUSB300_EPSET2_MPS(info.maxpacket); - iowrite32(reg, fusb300->reg + FUSB300_OFFSET_EPSET2(info.epnum)); -} - -static void fusb300_set_interval(struct fusb300 *fusb300, - struct fusb300_ep_info info) -{ - u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_EPSET1(info.epnum)); - - reg &= ~FUSB300_EPSET1_INTERVAL(0x7); - reg |= FUSB300_EPSET1_INTERVAL(info.interval); - iowrite32(reg, fusb300->reg + FUSB300_OFFSET_EPSET1(info.epnum)); -} - -static void fusb300_set_bwnum(struct fusb300 *fusb300, - struct fusb300_ep_info info) -{ - u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_EPSET1(info.epnum)); - - reg &= ~FUSB300_EPSET1_BWNUM(0x3); - reg |= FUSB300_EPSET1_BWNUM(info.bw_num); - iowrite32(reg, fusb300->reg + FUSB300_OFFSET_EPSET1(info.epnum)); -} - -static void set_ep_reg(struct fusb300 *fusb300, - struct fusb300_ep_info info) -{ - fusb300_set_eptype(fusb300, info); - fusb300_set_epdir(fusb300, info); - fusb300_set_epmps(fusb300, info); - - if (info.interval) - fusb300_set_interval(fusb300, info); - - if (info.bw_num) - fusb300_set_bwnum(fusb300, info); - - fusb300_set_ep_active(fusb300, info.epnum); -} - -static int config_ep(struct fusb300_ep *ep, - const struct usb_endpoint_descriptor *desc) -{ - struct fusb300 *fusb300 = ep->fusb300; - struct fusb300_ep_info info; - - ep->ep.desc = desc; - - info.interval = 0; - info.addrofs = 0; - info.bw_num = 0; - - info.type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; - info.dir_in = (desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK) ? 1 : 0; - info.maxpacket = usb_endpoint_maxp(desc); - info.epnum = desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; - - if ((info.type == USB_ENDPOINT_XFER_INT) || - (info.type == USB_ENDPOINT_XFER_ISOC)) { - info.interval = desc->bInterval; - if (info.type == USB_ENDPOINT_XFER_ISOC) - info.bw_num = usb_endpoint_maxp_mult(desc); - } - - ep_fifo_setting(fusb300, info); - - set_ep_reg(fusb300, info); - - fusb300_ep_setting(ep, info); - - fusb300->ep[info.epnum] = ep; - - return 0; -} - -static int fusb300_enable(struct usb_ep *_ep, - const struct usb_endpoint_descriptor *desc) -{ - struct fusb300_ep *ep; - - ep = container_of(_ep, struct fusb300_ep, ep); - - if (ep->fusb300->reenum) { - ep->fusb300->fifo_entry_num = 0; - ep->fusb300->addrofs = 0; - ep->fusb300->reenum = 0; - } - - return config_ep(ep, desc); -} - -static int fusb300_disable(struct usb_ep *_ep) -{ - struct fusb300_ep *ep; - struct fusb300_request *req; - unsigned long flags; - - ep = container_of(_ep, struct fusb300_ep, ep); - - BUG_ON(!ep); - - while (!list_empty(&ep->queue)) { - req = list_entry(ep->queue.next, struct fusb300_request, queue); - spin_lock_irqsave(&ep->fusb300->lock, flags); - done(ep, req, -ECONNRESET); - spin_unlock_irqrestore(&ep->fusb300->lock, flags); - } - - return fusb300_ep_release(ep); -} - -static struct usb_request *fusb300_alloc_request(struct usb_ep *_ep, - gfp_t gfp_flags) -{ - struct fusb300_request *req; - - req = kzalloc(sizeof(struct fusb300_request), gfp_flags); - if (!req) - return NULL; - INIT_LIST_HEAD(&req->queue); - - return &req->req; -} - -static void fusb300_free_request(struct usb_ep *_ep, struct usb_request *_req) -{ - struct fusb300_request *req; - - req = container_of(_req, struct fusb300_request, req); - kfree(req); -} - -static int enable_fifo_int(struct fusb300_ep *ep) -{ - struct fusb300 *fusb300 = ep->fusb300; - - if (ep->epnum) { - fusb300_enable_bit(fusb300, FUSB300_OFFSET_IGER0, - FUSB300_IGER0_EEPn_FIFO_INT(ep->epnum)); - } else { - pr_err("can't enable_fifo_int ep0\n"); - return -EINVAL; - } - - return 0; -} - -static int disable_fifo_int(struct fusb300_ep *ep) -{ - struct fusb300 *fusb300 = ep->fusb300; - - if (ep->epnum) { - fusb300_disable_bit(fusb300, FUSB300_OFFSET_IGER0, - FUSB300_IGER0_EEPn_FIFO_INT(ep->epnum)); - } else { - pr_err("can't disable_fifo_int ep0\n"); - return -EINVAL; - } - - return 0; -} - -static void fusb300_set_cxlen(struct fusb300 *fusb300, u32 length) -{ - u32 reg; - - reg = ioread32(fusb300->reg + FUSB300_OFFSET_CSR); - reg &= ~FUSB300_CSR_LEN_MSK; - reg |= FUSB300_CSR_LEN(length); - iowrite32(reg, fusb300->reg + FUSB300_OFFSET_CSR); -} - -/* write data to cx fifo */ -static void fusb300_wrcxf(struct fusb300_ep *ep, - struct fusb300_request *req) -{ - int i = 0; - u8 *tmp; - u32 data; - struct fusb300 *fusb300 = ep->fusb300; - u32 length = req->req.length - req->req.actual; - - tmp = req->req.buf + req->req.actual; - - if (length > SS_CTL_MAX_PACKET_SIZE) { - fusb300_set_cxlen(fusb300, SS_CTL_MAX_PACKET_SIZE); - for (i = (SS_CTL_MAX_PACKET_SIZE >> 2); i > 0; i--) { - data = *tmp | *(tmp + 1) << 8 | *(tmp + 2) << 16 | - *(tmp + 3) << 24; - iowrite32(data, fusb300->reg + FUSB300_OFFSET_CXPORT); - tmp += 4; - } - req->req.actual += SS_CTL_MAX_PACKET_SIZE; - } else { /* length is less than max packet size */ - fusb300_set_cxlen(fusb300, length); - for (i = length >> 2; i > 0; i--) { - data = *tmp | *(tmp + 1) << 8 | *(tmp + 2) << 16 | - *(tmp + 3) << 24; - printk(KERN_DEBUG " 0x%x\n", data); - iowrite32(data, fusb300->reg + FUSB300_OFFSET_CXPORT); - tmp = tmp + 4; - } - switch (length % 4) { - case 1: - data = *tmp; - printk(KERN_DEBUG " 0x%x\n", data); - iowrite32(data, fusb300->reg + FUSB300_OFFSET_CXPORT); - break; - case 2: - data = *tmp | *(tmp + 1) << 8; - printk(KERN_DEBUG " 0x%x\n", data); - iowrite32(data, fusb300->reg + FUSB300_OFFSET_CXPORT); - break; - case 3: - data = *tmp | *(tmp + 1) << 8 | *(tmp + 2) << 16; - printk(KERN_DEBUG " 0x%x\n", data); - iowrite32(data, fusb300->reg + FUSB300_OFFSET_CXPORT); - break; - default: - break; - } - req->req.actual += length; - } -} - -static void fusb300_set_epnstall(struct fusb300 *fusb300, u8 ep) -{ - fusb300_enable_bit(fusb300, FUSB300_OFFSET_EPSET0(ep), - FUSB300_EPSET0_STL); -} - -static void fusb300_clear_epnstall(struct fusb300 *fusb300, u8 ep) -{ - u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_EPSET0(ep)); - - if (reg & FUSB300_EPSET0_STL) { - printk(KERN_DEBUG "EP%d stall... Clear!!\n", ep); - reg |= FUSB300_EPSET0_STL_CLR; - iowrite32(reg, fusb300->reg + FUSB300_OFFSET_EPSET0(ep)); - } -} - -static void ep0_queue(struct fusb300_ep *ep, struct fusb300_request *req) -{ - if (ep->fusb300->ep0_dir) { /* if IN */ - if (req->req.length) { - fusb300_wrcxf(ep, req); - } else - printk(KERN_DEBUG "%s : req->req.length = 0x%x\n", - __func__, req->req.length); - if ((req->req.length == req->req.actual) || - (req->req.actual < ep->ep.maxpacket)) - done(ep, req, 0); - } else { /* OUT */ - if (!req->req.length) - done(ep, req, 0); - else - fusb300_enable_bit(ep->fusb300, FUSB300_OFFSET_IGER1, - FUSB300_IGER1_CX_OUT_INT); - } -} - -static int fusb300_queue(struct usb_ep *_ep, struct usb_request *_req, - gfp_t gfp_flags) -{ - struct fusb300_ep *ep; - struct fusb300_request *req; - unsigned long flags; - int request = 0; - - ep = container_of(_ep, struct fusb300_ep, ep); - req = container_of(_req, struct fusb300_request, req); - - if (ep->fusb300->gadget.speed == USB_SPEED_UNKNOWN) - return -ESHUTDOWN; - - spin_lock_irqsave(&ep->fusb300->lock, flags); - - if (list_empty(&ep->queue)) - request = 1; - - list_add_tail(&req->queue, &ep->queue); - - req->req.actual = 0; - req->req.status = -EINPROGRESS; - - if (ep->ep.desc == NULL) /* ep0 */ - ep0_queue(ep, req); - else if (request && !ep->stall) - enable_fifo_int(ep); - - spin_unlock_irqrestore(&ep->fusb300->lock, flags); - - return 0; -} - -static int fusb300_dequeue(struct usb_ep *_ep, struct usb_request *_req) -{ - struct fusb300_ep *ep; - struct fusb300_request *req; - unsigned long flags; - - ep = container_of(_ep, struct fusb300_ep, ep); - req = container_of(_req, struct fusb300_request, req); - - spin_lock_irqsave(&ep->fusb300->lock, flags); - if (!list_empty(&ep->queue)) - done(ep, req, -ECONNRESET); - spin_unlock_irqrestore(&ep->fusb300->lock, flags); - - return 0; -} - -static int fusb300_set_halt_and_wedge(struct usb_ep *_ep, int value, int wedge) -{ - struct fusb300_ep *ep; - struct fusb300 *fusb300; - unsigned long flags; - int ret = 0; - - ep = container_of(_ep, struct fusb300_ep, ep); - - fusb300 = ep->fusb300; - - spin_lock_irqsave(&ep->fusb300->lock, flags); - - if (!list_empty(&ep->queue)) { - ret = -EAGAIN; - goto out; - } - - if (value) { - fusb300_set_epnstall(fusb300, ep->epnum); - ep->stall = 1; - if (wedge) - ep->wedged = 1; - } else { - fusb300_clear_epnstall(fusb300, ep->epnum); - ep->stall = 0; - ep->wedged = 0; - } - -out: - spin_unlock_irqrestore(&ep->fusb300->lock, flags); - return ret; -} - -static int fusb300_set_halt(struct usb_ep *_ep, int value) -{ - return fusb300_set_halt_and_wedge(_ep, value, 0); -} - -static int fusb300_set_wedge(struct usb_ep *_ep) -{ - return fusb300_set_halt_and_wedge(_ep, 1, 1); -} - -static void fusb300_fifo_flush(struct usb_ep *_ep) -{ -} - -static const struct usb_ep_ops fusb300_ep_ops = { - .enable = fusb300_enable, - .disable = fusb300_disable, - - .alloc_request = fusb300_alloc_request, - .free_request = fusb300_free_request, - - .queue = fusb300_queue, - .dequeue = fusb300_dequeue, - - .set_halt = fusb300_set_halt, - .fifo_flush = fusb300_fifo_flush, - .set_wedge = fusb300_set_wedge, -}; - -/*****************************************************************************/ -static void fusb300_clear_int(struct fusb300 *fusb300, u32 offset, - u32 value) -{ - iowrite32(value, fusb300->reg + offset); -} - -static void fusb300_reset(void) -{ -} - -static void fusb300_set_cxstall(struct fusb300 *fusb300) -{ - fusb300_enable_bit(fusb300, FUSB300_OFFSET_CSR, - FUSB300_CSR_STL); -} - -static void fusb300_set_cxdone(struct fusb300 *fusb300) -{ - fusb300_enable_bit(fusb300, FUSB300_OFFSET_CSR, - FUSB300_CSR_DONE); -} - -/* read data from cx fifo */ -static void fusb300_rdcxf(struct fusb300 *fusb300, - u8 *buffer, u32 length) -{ - int i = 0; - u8 *tmp; - u32 data; - - tmp = buffer; - - for (i = (length >> 2); i > 0; i--) { - data = ioread32(fusb300->reg + FUSB300_OFFSET_CXPORT); - printk(KERN_DEBUG " 0x%x\n", data); - *tmp = data & 0xFF; - *(tmp + 1) = (data >> 8) & 0xFF; - *(tmp + 2) = (data >> 16) & 0xFF; - *(tmp + 3) = (data >> 24) & 0xFF; - tmp = tmp + 4; - } - - switch (length % 4) { - case 1: - data = ioread32(fusb300->reg + FUSB300_OFFSET_CXPORT); - printk(KERN_DEBUG " 0x%x\n", data); - *tmp = data & 0xFF; - break; - case 2: - data = ioread32(fusb300->reg + FUSB300_OFFSET_CXPORT); - printk(KERN_DEBUG " 0x%x\n", data); - *tmp = data & 0xFF; - *(tmp + 1) = (data >> 8) & 0xFF; - break; - case 3: - data = ioread32(fusb300->reg + FUSB300_OFFSET_CXPORT); - printk(KERN_DEBUG " 0x%x\n", data); - *tmp = data & 0xFF; - *(tmp + 1) = (data >> 8) & 0xFF; - *(tmp + 2) = (data >> 16) & 0xFF; - break; - default: - break; - } -} - -static void fusb300_rdfifo(struct fusb300_ep *ep, - struct fusb300_request *req, - u32 length) -{ - int i = 0; - u8 *tmp; - u32 data, reg; - struct fusb300 *fusb300 = ep->fusb300; - - tmp = req->req.buf + req->req.actual; - req->req.actual += length; - - if (req->req.actual > req->req.length) - printk(KERN_DEBUG "req->req.actual > req->req.length\n"); - - for (i = (length >> 2); i > 0; i--) { - data = ioread32(fusb300->reg + - FUSB300_OFFSET_EPPORT(ep->epnum)); - *tmp = data & 0xFF; - *(tmp + 1) = (data >> 8) & 0xFF; - *(tmp + 2) = (data >> 16) & 0xFF; - *(tmp + 3) = (data >> 24) & 0xFF; - tmp = tmp + 4; - } - - switch (length % 4) { - case 1: - data = ioread32(fusb300->reg + - FUSB300_OFFSET_EPPORT(ep->epnum)); - *tmp = data & 0xFF; - break; - case 2: - data = ioread32(fusb300->reg + - FUSB300_OFFSET_EPPORT(ep->epnum)); - *tmp = data & 0xFF; - *(tmp + 1) = (data >> 8) & 0xFF; - break; - case 3: - data = ioread32(fusb300->reg + - FUSB300_OFFSET_EPPORT(ep->epnum)); - *tmp = data & 0xFF; - *(tmp + 1) = (data >> 8) & 0xFF; - *(tmp + 2) = (data >> 16) & 0xFF; - break; - default: - break; - } - - do { - reg = ioread32(fusb300->reg + FUSB300_OFFSET_IGR1); - reg &= FUSB300_IGR1_SYNF0_EMPTY_INT; - if (i) - printk(KERN_INFO "sync fifo is not empty!\n"); - i++; - } while (!reg); -} - -static u8 fusb300_get_epnstall(struct fusb300 *fusb300, u8 ep) -{ - u8 value; - u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_EPSET0(ep)); - - value = reg & FUSB300_EPSET0_STL; - - return value; -} - -static u8 fusb300_get_cxstall(struct fusb300 *fusb300) -{ - u8 value; - u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_CSR); - - value = (reg & FUSB300_CSR_STL) >> 1; - - return value; -} - -static void request_error(struct fusb300 *fusb300) -{ - fusb300_set_cxstall(fusb300); - printk(KERN_DEBUG "request error!!\n"); -} - -static void get_status(struct fusb300 *fusb300, struct usb_ctrlrequest *ctrl) -__releases(fusb300->lock) -__acquires(fusb300->lock) -{ - u8 ep; - u16 status = 0; - u16 w_index = ctrl->wIndex; - - switch (ctrl->bRequestType & USB_RECIP_MASK) { - case USB_RECIP_DEVICE: - status = 1 << USB_DEVICE_SELF_POWERED; - break; - case USB_RECIP_INTERFACE: - status = 0; - break; - case USB_RECIP_ENDPOINT: - ep = w_index & USB_ENDPOINT_NUMBER_MASK; - if (ep) { - if (fusb300_get_epnstall(fusb300, ep)) - status = 1 << USB_ENDPOINT_HALT; - } else { - if (fusb300_get_cxstall(fusb300)) - status = 0; - } - break; - - default: - request_error(fusb300); - return; /* exit */ - } - - fusb300->ep0_data = cpu_to_le16(status); - fusb300->ep0_req->buf = &fusb300->ep0_data; - fusb300->ep0_req->length = 2; - - spin_unlock(&fusb300->lock); - fusb300_queue(fusb300->gadget.ep0, fusb300->ep0_req, GFP_KERNEL); - spin_lock(&fusb300->lock); -} - -static void set_feature(struct fusb300 *fusb300, struct usb_ctrlrequest *ctrl) -{ - u8 ep; - - switch (ctrl->bRequestType & USB_RECIP_MASK) { - case USB_RECIP_DEVICE: - fusb300_set_cxdone(fusb300); - break; - case USB_RECIP_INTERFACE: - fusb300_set_cxdone(fusb300); - break; - case USB_RECIP_ENDPOINT: { - u16 w_index = le16_to_cpu(ctrl->wIndex); - - ep = w_index & USB_ENDPOINT_NUMBER_MASK; - if (ep) - fusb300_set_epnstall(fusb300, ep); - else - fusb300_set_cxstall(fusb300); - fusb300_set_cxdone(fusb300); - } - break; - default: - request_error(fusb300); - break; - } -} - -static void fusb300_clear_seqnum(struct fusb300 *fusb300, u8 ep) -{ - fusb300_enable_bit(fusb300, FUSB300_OFFSET_EPSET0(ep), - FUSB300_EPSET0_CLRSEQNUM); -} - -static void clear_feature(struct fusb300 *fusb300, struct usb_ctrlrequest *ctrl) -{ - struct fusb300_ep *ep = - fusb300->ep[ctrl->wIndex & USB_ENDPOINT_NUMBER_MASK]; - - switch (ctrl->bRequestType & USB_RECIP_MASK) { - case USB_RECIP_DEVICE: - fusb300_set_cxdone(fusb300); - break; - case USB_RECIP_INTERFACE: - fusb300_set_cxdone(fusb300); - break; - case USB_RECIP_ENDPOINT: - if (ctrl->wIndex & USB_ENDPOINT_NUMBER_MASK) { - if (ep->wedged) { - fusb300_set_cxdone(fusb300); - break; - } - if (ep->stall) { - ep->stall = 0; - fusb300_clear_seqnum(fusb300, ep->epnum); - fusb300_clear_epnstall(fusb300, ep->epnum); - if (!list_empty(&ep->queue)) - enable_fifo_int(ep); - } - } - fusb300_set_cxdone(fusb300); - break; - default: - request_error(fusb300); - break; - } -} - -static void fusb300_set_dev_addr(struct fusb300 *fusb300, u16 addr) -{ - u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_DAR); - - reg &= ~FUSB300_DAR_DRVADDR_MSK; - reg |= FUSB300_DAR_DRVADDR(addr); - - iowrite32(reg, fusb300->reg + FUSB300_OFFSET_DAR); -} - -static void set_address(struct fusb300 *fusb300, struct usb_ctrlrequest *ctrl) -{ - if (ctrl->wValue >= 0x0100) - request_error(fusb300); - else { - fusb300_set_dev_addr(fusb300, ctrl->wValue); - fusb300_set_cxdone(fusb300); - } -} - -#define UVC_COPY_DESCRIPTORS(mem, src) \ - do { \ - const struct usb_descriptor_header * const *__src; \ - for (__src = src; *__src; ++__src) { \ - memcpy(mem, *__src, (*__src)->bLength); \ - mem += (*__src)->bLength; \ - } \ - } while (0) - -static int setup_packet(struct fusb300 *fusb300, struct usb_ctrlrequest *ctrl) -{ - u8 *p = (u8 *)ctrl; - u8 ret = 0; - u8 i = 0; - - fusb300_rdcxf(fusb300, p, 8); - fusb300->ep0_dir = ctrl->bRequestType & USB_DIR_IN; - fusb300->ep0_length = ctrl->wLength; - - /* check request */ - if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { - switch (ctrl->bRequest) { - case USB_REQ_GET_STATUS: - get_status(fusb300, ctrl); - break; - case USB_REQ_CLEAR_FEATURE: - clear_feature(fusb300, ctrl); - break; - case USB_REQ_SET_FEATURE: - set_feature(fusb300, ctrl); - break; - case USB_REQ_SET_ADDRESS: - set_address(fusb300, ctrl); - break; - case USB_REQ_SET_CONFIGURATION: - fusb300_enable_bit(fusb300, FUSB300_OFFSET_DAR, - FUSB300_DAR_SETCONFG); - /* clear sequence number */ - for (i = 1; i <= FUSB300_MAX_NUM_EP; i++) - fusb300_clear_seqnum(fusb300, i); - fusb300->reenum = 1; - ret = 1; - break; - default: - ret = 1; - break; - } - } else - ret = 1; - - return ret; -} - -static void done(struct fusb300_ep *ep, struct fusb300_request *req, - int status) -{ - list_del_init(&req->queue); - - /* don't modify queue heads during completion callback */ - if (ep->fusb300->gadget.speed == USB_SPEED_UNKNOWN) - req->req.status = -ESHUTDOWN; - else - req->req.status = status; - - spin_unlock(&ep->fusb300->lock); - usb_gadget_giveback_request(&ep->ep, &req->req); - spin_lock(&ep->fusb300->lock); - - if (ep->epnum) { - disable_fifo_int(ep); - if (!list_empty(&ep->queue)) - enable_fifo_int(ep); - } else - fusb300_set_cxdone(ep->fusb300); -} - -static void fusb300_fill_idma_prdtbl(struct fusb300_ep *ep, dma_addr_t d, - u32 len) -{ - u32 value; - u32 reg; - - /* wait SW owner */ - do { - reg = ioread32(ep->fusb300->reg + - FUSB300_OFFSET_EPPRD_W0(ep->epnum)); - reg &= FUSB300_EPPRD0_H; - } while (reg); - - iowrite32(d, ep->fusb300->reg + FUSB300_OFFSET_EPPRD_W1(ep->epnum)); - - value = FUSB300_EPPRD0_BTC(len) | FUSB300_EPPRD0_H | - FUSB300_EPPRD0_F | FUSB300_EPPRD0_L | FUSB300_EPPRD0_I; - iowrite32(value, ep->fusb300->reg + FUSB300_OFFSET_EPPRD_W0(ep->epnum)); - - iowrite32(0x0, ep->fusb300->reg + FUSB300_OFFSET_EPPRD_W2(ep->epnum)); - - fusb300_enable_bit(ep->fusb300, FUSB300_OFFSET_EPPRDRDY, - FUSB300_EPPRDR_EP_PRD_RDY(ep->epnum)); -} - -static void fusb300_wait_idma_finished(struct fusb300_ep *ep) -{ - u32 reg; - - do { - reg = ioread32(ep->fusb300->reg + FUSB300_OFFSET_IGR1); - if ((reg & FUSB300_IGR1_VBUS_CHG_INT) || - (reg & FUSB300_IGR1_WARM_RST_INT) || - (reg & FUSB300_IGR1_HOT_RST_INT) || - (reg & FUSB300_IGR1_USBRST_INT) - ) - goto IDMA_RESET; - reg = ioread32(ep->fusb300->reg + FUSB300_OFFSET_IGR0); - reg &= FUSB300_IGR0_EPn_PRD_INT(ep->epnum); - } while (!reg); - - fusb300_clear_int(ep->fusb300, FUSB300_OFFSET_IGR0, - FUSB300_IGR0_EPn_PRD_INT(ep->epnum)); - return; - -IDMA_RESET: - reg = ioread32(ep->fusb300->reg + FUSB300_OFFSET_IGER0); - reg &= ~FUSB300_IGER0_EEPn_PRD_INT(ep->epnum); - iowrite32(reg, ep->fusb300->reg + FUSB300_OFFSET_IGER0); -} - -static void fusb300_set_idma(struct fusb300_ep *ep, - struct fusb300_request *req) -{ - int ret; - - ret = usb_gadget_map_request(&ep->fusb300->gadget, - &req->req, DMA_TO_DEVICE); - if (ret) - return; - - fusb300_enable_bit(ep->fusb300, FUSB300_OFFSET_IGER0, - FUSB300_IGER0_EEPn_PRD_INT(ep->epnum)); - - fusb300_fill_idma_prdtbl(ep, req->req.dma, req->req.length); - /* check idma is done */ - fusb300_wait_idma_finished(ep); - - usb_gadget_unmap_request(&ep->fusb300->gadget, - &req->req, DMA_TO_DEVICE); -} - -static void in_ep_fifo_handler(struct fusb300_ep *ep) -{ - struct fusb300_request *req = list_entry(ep->queue.next, - struct fusb300_request, queue); - - if (req->req.length) - fusb300_set_idma(ep, req); - done(ep, req, 0); -} - -static void out_ep_fifo_handler(struct fusb300_ep *ep) -{ - struct fusb300 *fusb300 = ep->fusb300; - struct fusb300_request *req = list_entry(ep->queue.next, - struct fusb300_request, queue); - u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_EPFFR(ep->epnum)); - u32 length = reg & FUSB300_FFR_BYCNT; - - fusb300_rdfifo(ep, req, length); - - /* finish out transfer */ - if ((req->req.length == req->req.actual) || (length < ep->ep.maxpacket)) - done(ep, req, 0); -} - -static void check_device_mode(struct fusb300 *fusb300) -{ - u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_GCR); - - switch (reg & FUSB300_GCR_DEVEN_MSK) { - case FUSB300_GCR_DEVEN_SS: - fusb300->gadget.speed = USB_SPEED_SUPER; - break; - case FUSB300_GCR_DEVEN_HS: - fusb300->gadget.speed = USB_SPEED_HIGH; - break; - case FUSB300_GCR_DEVEN_FS: - fusb300->gadget.speed = USB_SPEED_FULL; - break; - default: - fusb300->gadget.speed = USB_SPEED_UNKNOWN; - break; - } - printk(KERN_INFO "dev_mode = %d\n", (reg & FUSB300_GCR_DEVEN_MSK)); -} - - -static void fusb300_ep0out(struct fusb300 *fusb300) -{ - struct fusb300_ep *ep = fusb300->ep[0]; - u32 reg; - - if (!list_empty(&ep->queue)) { - struct fusb300_request *req; - - req = list_first_entry(&ep->queue, - struct fusb300_request, queue); - if (req->req.length) - fusb300_rdcxf(ep->fusb300, req->req.buf, - req->req.length); - done(ep, req, 0); - reg = ioread32(fusb300->reg + FUSB300_OFFSET_IGER1); - reg &= ~FUSB300_IGER1_CX_OUT_INT; - iowrite32(reg, fusb300->reg + FUSB300_OFFSET_IGER1); - } else - pr_err("%s : empty queue\n", __func__); -} - -static void fusb300_ep0in(struct fusb300 *fusb300) -{ - struct fusb300_request *req; - struct fusb300_ep *ep = fusb300->ep[0]; - - if ((!list_empty(&ep->queue)) && (fusb300->ep0_dir)) { - req = list_entry(ep->queue.next, - struct fusb300_request, queue); - if (req->req.length) - fusb300_wrcxf(ep, req); - if ((req->req.length - req->req.actual) < ep->ep.maxpacket) - done(ep, req, 0); - } else - fusb300_set_cxdone(fusb300); -} - -static void fusb300_grp2_handler(void) -{ -} - -static void fusb300_grp3_handler(void) -{ -} - -static void fusb300_grp4_handler(void) -{ -} - -static void fusb300_grp5_handler(void) -{ -} - -static irqreturn_t fusb300_irq(int irq, void *_fusb300) -{ - struct fusb300 *fusb300 = _fusb300; - u32 int_grp1 = ioread32(fusb300->reg + FUSB300_OFFSET_IGR1); - u32 int_grp1_en = ioread32(fusb300->reg + FUSB300_OFFSET_IGER1); - u32 int_grp0 = ioread32(fusb300->reg + FUSB300_OFFSET_IGR0); - u32 int_grp0_en = ioread32(fusb300->reg + FUSB300_OFFSET_IGER0); - struct usb_ctrlrequest ctrl; - u8 in; - u32 reg; - int i; - - spin_lock(&fusb300->lock); - - int_grp1 &= int_grp1_en; - int_grp0 &= int_grp0_en; - - if (int_grp1 & FUSB300_IGR1_WARM_RST_INT) { - fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, - FUSB300_IGR1_WARM_RST_INT); - printk(KERN_INFO"fusb300_warmreset\n"); - fusb300_reset(); - } - - if (int_grp1 & FUSB300_IGR1_HOT_RST_INT) { - fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, - FUSB300_IGR1_HOT_RST_INT); - printk(KERN_INFO"fusb300_hotreset\n"); - fusb300_reset(); - } - - if (int_grp1 & FUSB300_IGR1_USBRST_INT) { - fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, - FUSB300_IGR1_USBRST_INT); - fusb300_reset(); - } - /* COMABT_INT has a highest priority */ - - if (int_grp1 & FUSB300_IGR1_CX_COMABT_INT) { - fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, - FUSB300_IGR1_CX_COMABT_INT); - printk(KERN_INFO"fusb300_ep0abt\n"); - } - - if (int_grp1 & FUSB300_IGR1_VBUS_CHG_INT) { - fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, - FUSB300_IGR1_VBUS_CHG_INT); - printk(KERN_INFO"fusb300_vbus_change\n"); - } - - if (int_grp1 & FUSB300_IGR1_U3_EXIT_FAIL_INT) { - fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, - FUSB300_IGR1_U3_EXIT_FAIL_INT); - } - - if (int_grp1 & FUSB300_IGR1_U2_EXIT_FAIL_INT) { - fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, - FUSB300_IGR1_U2_EXIT_FAIL_INT); - } - - if (int_grp1 & FUSB300_IGR1_U1_EXIT_FAIL_INT) { - fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, - FUSB300_IGR1_U1_EXIT_FAIL_INT); - } - - if (int_grp1 & FUSB300_IGR1_U2_ENTRY_FAIL_INT) { - fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, - FUSB300_IGR1_U2_ENTRY_FAIL_INT); - } - - if (int_grp1 & FUSB300_IGR1_U1_ENTRY_FAIL_INT) { - fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, - FUSB300_IGR1_U1_ENTRY_FAIL_INT); - } - - if (int_grp1 & FUSB300_IGR1_U3_EXIT_INT) { - fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, - FUSB300_IGR1_U3_EXIT_INT); - printk(KERN_INFO "FUSB300_IGR1_U3_EXIT_INT\n"); - } - - if (int_grp1 & FUSB300_IGR1_U2_EXIT_INT) { - fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, - FUSB300_IGR1_U2_EXIT_INT); - printk(KERN_INFO "FUSB300_IGR1_U2_EXIT_INT\n"); - } - - if (int_grp1 & FUSB300_IGR1_U1_EXIT_INT) { - fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, - FUSB300_IGR1_U1_EXIT_INT); - printk(KERN_INFO "FUSB300_IGR1_U1_EXIT_INT\n"); - } - - if (int_grp1 & FUSB300_IGR1_U3_ENTRY_INT) { - fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, - FUSB300_IGR1_U3_ENTRY_INT); - printk(KERN_INFO "FUSB300_IGR1_U3_ENTRY_INT\n"); - fusb300_enable_bit(fusb300, FUSB300_OFFSET_SSCR1, - FUSB300_SSCR1_GO_U3_DONE); - } - - if (int_grp1 & FUSB300_IGR1_U2_ENTRY_INT) { - fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, - FUSB300_IGR1_U2_ENTRY_INT); - printk(KERN_INFO "FUSB300_IGR1_U2_ENTRY_INT\n"); - } - - if (int_grp1 & FUSB300_IGR1_U1_ENTRY_INT) { - fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, - FUSB300_IGR1_U1_ENTRY_INT); - printk(KERN_INFO "FUSB300_IGR1_U1_ENTRY_INT\n"); - } - - if (int_grp1 & FUSB300_IGR1_RESM_INT) { - fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, - FUSB300_IGR1_RESM_INT); - printk(KERN_INFO "fusb300_resume\n"); - } - - if (int_grp1 & FUSB300_IGR1_SUSP_INT) { - fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, - FUSB300_IGR1_SUSP_INT); - printk(KERN_INFO "fusb300_suspend\n"); - } - - if (int_grp1 & FUSB300_IGR1_HS_LPM_INT) { - fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, - FUSB300_IGR1_HS_LPM_INT); - printk(KERN_INFO "fusb300_HS_LPM_INT\n"); - } - - if (int_grp1 & FUSB300_IGR1_DEV_MODE_CHG_INT) { - fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, - FUSB300_IGR1_DEV_MODE_CHG_INT); - check_device_mode(fusb300); - } - - if (int_grp1 & FUSB300_IGR1_CX_COMFAIL_INT) { - fusb300_set_cxstall(fusb300); - printk(KERN_INFO "fusb300_ep0fail\n"); - } - - if (int_grp1 & FUSB300_IGR1_CX_SETUP_INT) { - printk(KERN_INFO "fusb300_ep0setup\n"); - if (setup_packet(fusb300, &ctrl)) { - spin_unlock(&fusb300->lock); - if (fusb300->driver->setup(&fusb300->gadget, &ctrl) < 0) - fusb300_set_cxstall(fusb300); - spin_lock(&fusb300->lock); - } - } - - if (int_grp1 & FUSB300_IGR1_CX_CMDEND_INT) - printk(KERN_INFO "fusb300_cmdend\n"); - - - if (int_grp1 & FUSB300_IGR1_CX_OUT_INT) { - printk(KERN_INFO "fusb300_cxout\n"); - fusb300_ep0out(fusb300); - } - - if (int_grp1 & FUSB300_IGR1_CX_IN_INT) { - printk(KERN_INFO "fusb300_cxin\n"); - fusb300_ep0in(fusb300); - } - - if (int_grp1 & FUSB300_IGR1_INTGRP5) - fusb300_grp5_handler(); - - if (int_grp1 & FUSB300_IGR1_INTGRP4) - fusb300_grp4_handler(); - - if (int_grp1 & FUSB300_IGR1_INTGRP3) - fusb300_grp3_handler(); - - if (int_grp1 & FUSB300_IGR1_INTGRP2) - fusb300_grp2_handler(); - - if (int_grp0) { - for (i = 1; i < FUSB300_MAX_NUM_EP; i++) { - if (int_grp0 & FUSB300_IGR0_EPn_FIFO_INT(i)) { - reg = ioread32(fusb300->reg + - FUSB300_OFFSET_EPSET1(i)); - in = (reg & FUSB300_EPSET1_DIRIN) ? 1 : 0; - if (in) - in_ep_fifo_handler(fusb300->ep[i]); - else - out_ep_fifo_handler(fusb300->ep[i]); - } - } - } - - spin_unlock(&fusb300->lock); - - return IRQ_HANDLED; -} - -static void fusb300_set_u2_timeout(struct fusb300 *fusb300, - u32 time) -{ - u32 reg; - - reg = ioread32(fusb300->reg + FUSB300_OFFSET_TT); - reg &= ~0xff; - reg |= FUSB300_SSCR2_U2TIMEOUT(time); - - iowrite32(reg, fusb300->reg + FUSB300_OFFSET_TT); -} - -static void fusb300_set_u1_timeout(struct fusb300 *fusb300, - u32 time) -{ - u32 reg; - - reg = ioread32(fusb300->reg + FUSB300_OFFSET_TT); - reg &= ~(0xff << 8); - reg |= FUSB300_SSCR2_U1TIMEOUT(time); - - iowrite32(reg, fusb300->reg + FUSB300_OFFSET_TT); -} - -static void init_controller(struct fusb300 *fusb300) -{ - u32 reg; - u32 mask = 0; - u32 val = 0; - - /* split on */ - mask = val = FUSB300_AHBBCR_S0_SPLIT_ON | FUSB300_AHBBCR_S1_SPLIT_ON; - reg = ioread32(fusb300->reg + FUSB300_OFFSET_AHBCR); - reg &= ~mask; - reg |= val; - iowrite32(reg, fusb300->reg + FUSB300_OFFSET_AHBCR); - - /* enable high-speed LPM */ - mask = val = FUSB300_HSCR_HS_LPM_PERMIT; - reg = ioread32(fusb300->reg + FUSB300_OFFSET_HSCR); - reg &= ~mask; - reg |= val; - iowrite32(reg, fusb300->reg + FUSB300_OFFSET_HSCR); - - /*set u1 u2 timer*/ - fusb300_set_u2_timeout(fusb300, 0xff); - fusb300_set_u1_timeout(fusb300, 0xff); - - /* enable all grp1 interrupt */ - iowrite32(0xcfffff9f, fusb300->reg + FUSB300_OFFSET_IGER1); -} -/*------------------------------------------------------------------------*/ -static int fusb300_udc_start(struct usb_gadget *g, - struct usb_gadget_driver *driver) -{ - struct fusb300 *fusb300 = to_fusb300(g); - - /* hook up the driver */ - fusb300->driver = driver; - - return 0; -} - -static int fusb300_udc_stop(struct usb_gadget *g) -{ - struct fusb300 *fusb300 = to_fusb300(g); - - init_controller(fusb300); - fusb300->driver = NULL; - - return 0; -} -/*--------------------------------------------------------------------------*/ - -static int fusb300_udc_pullup(struct usb_gadget *_gadget, int is_active) -{ - return 0; -} - -static const struct usb_gadget_ops fusb300_gadget_ops = { - .pullup = fusb300_udc_pullup, - .udc_start = fusb300_udc_start, - .udc_stop = fusb300_udc_stop, -}; - -static void fusb300_remove(struct platform_device *pdev) -{ - struct fusb300 *fusb300 = platform_get_drvdata(pdev); - int i; - - usb_del_gadget_udc(&fusb300->gadget); - iounmap(fusb300->reg); - free_irq(platform_get_irq(pdev, 0), fusb300); - free_irq(platform_get_irq(pdev, 1), fusb300); - - fusb300_free_request(&fusb300->ep[0]->ep, fusb300->ep0_req); - for (i = 0; i < FUSB300_MAX_NUM_EP; i++) - kfree(fusb300->ep[i]); - kfree(fusb300); -} - -static int fusb300_probe(struct platform_device *pdev) -{ - struct resource *res, *ires, *ires1; - void __iomem *reg = NULL; - struct fusb300 *fusb300 = NULL; - struct fusb300_ep *_ep[FUSB300_MAX_NUM_EP]; - int ret = 0; - int i; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - ret = -ENODEV; - pr_err("platform_get_resource error.\n"); - goto clean_up; - } - - ires = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (!ires) { - ret = -ENODEV; - dev_err(&pdev->dev, - "platform_get_resource IORESOURCE_IRQ error.\n"); - goto clean_up; - } - - ires1 = platform_get_resource(pdev, IORESOURCE_IRQ, 1); - if (!ires1) { - ret = -ENODEV; - dev_err(&pdev->dev, - "platform_get_resource IORESOURCE_IRQ 1 error.\n"); - goto clean_up; - } - - reg = ioremap(res->start, resource_size(res)); - if (reg == NULL) { - ret = -ENOMEM; - pr_err("ioremap error.\n"); - goto clean_up; - } - - /* initialize udc */ - fusb300 = kzalloc(sizeof(struct fusb300), GFP_KERNEL); - if (fusb300 == NULL) { - ret = -ENOMEM; - goto clean_up; - } - - for (i = 0; i < FUSB300_MAX_NUM_EP; i++) { - _ep[i] = kzalloc(sizeof(struct fusb300_ep), GFP_KERNEL); - if (_ep[i] == NULL) { - ret = -ENOMEM; - goto clean_up; - } - fusb300->ep[i] = _ep[i]; - } - - spin_lock_init(&fusb300->lock); - - platform_set_drvdata(pdev, fusb300); - - fusb300->gadget.ops = &fusb300_gadget_ops; - - fusb300->gadget.max_speed = USB_SPEED_HIGH; - fusb300->gadget.name = udc_name; - fusb300->reg = reg; - - ret = request_irq(ires->start, fusb300_irq, IRQF_SHARED, - udc_name, fusb300); - if (ret < 0) { - pr_err("request_irq error (%d)\n", ret); - goto clean_up; - } - - ret = request_irq(ires1->start, fusb300_irq, - IRQF_SHARED, udc_name, fusb300); - if (ret < 0) { - pr_err("request_irq1 error (%d)\n", ret); - goto err_request_irq1; - } - - INIT_LIST_HEAD(&fusb300->gadget.ep_list); - - for (i = 0; i < FUSB300_MAX_NUM_EP ; i++) { - struct fusb300_ep *ep = fusb300->ep[i]; - - if (i != 0) { - INIT_LIST_HEAD(&fusb300->ep[i]->ep.ep_list); - list_add_tail(&fusb300->ep[i]->ep.ep_list, - &fusb300->gadget.ep_list); - } - ep->fusb300 = fusb300; - INIT_LIST_HEAD(&ep->queue); - ep->ep.name = fusb300_ep_name[i]; - ep->ep.ops = &fusb300_ep_ops; - usb_ep_set_maxpacket_limit(&ep->ep, HS_BULK_MAX_PACKET_SIZE); - - if (i == 0) { - ep->ep.caps.type_control = true; - } else { - ep->ep.caps.type_iso = true; - ep->ep.caps.type_bulk = true; - ep->ep.caps.type_int = true; - } - - ep->ep.caps.dir_in = true; - ep->ep.caps.dir_out = true; - } - usb_ep_set_maxpacket_limit(&fusb300->ep[0]->ep, HS_CTL_MAX_PACKET_SIZE); - fusb300->ep[0]->epnum = 0; - fusb300->gadget.ep0 = &fusb300->ep[0]->ep; - INIT_LIST_HEAD(&fusb300->gadget.ep0->ep_list); - - fusb300->ep0_req = fusb300_alloc_request(&fusb300->ep[0]->ep, - GFP_KERNEL); - if (fusb300->ep0_req == NULL) { - ret = -ENOMEM; - goto err_alloc_request; - } - - init_controller(fusb300); - ret = usb_add_gadget_udc(&pdev->dev, &fusb300->gadget); - if (ret) - goto err_add_udc; - - dev_info(&pdev->dev, "version %s\n", DRIVER_VERSION); - - return 0; - -err_add_udc: - fusb300_free_request(&fusb300->ep[0]->ep, fusb300->ep0_req); - -err_alloc_request: - free_irq(ires1->start, fusb300); - -err_request_irq1: - free_irq(ires->start, fusb300); - -clean_up: - if (fusb300) { - if (fusb300->ep0_req) - fusb300_free_request(&fusb300->ep[0]->ep, - fusb300->ep0_req); - for (i = 0; i < FUSB300_MAX_NUM_EP; i++) - kfree(fusb300->ep[i]); - kfree(fusb300); - } - if (reg) - iounmap(reg); - - return ret; -} - -static struct platform_driver fusb300_driver = { - .probe = fusb300_probe, - .remove = fusb300_remove, - .driver = { - .name = udc_name, - }, -}; - -module_platform_driver(fusb300_driver); diff --git a/drivers/usb/gadget/udc/fusb300_udc.h b/drivers/usb/gadget/udc/fusb300_udc.h deleted file mode 100644 index eb3d6d379ba7..000000000000 --- a/drivers/usb/gadget/udc/fusb300_udc.h +++ /dev/null @@ -1,675 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Fusb300 UDC (USB gadget) - * - * Copyright (C) 2010 Faraday Technology Corp. - * - * Author : Yuan-hsin Chen - */ - - -#ifndef __FUSB300_UDC_H__ -#define __FUSB300_UDC_H__ - -#include - -#define FUSB300_OFFSET_GCR 0x00 -#define FUSB300_OFFSET_GTM 0x04 -#define FUSB300_OFFSET_DAR 0x08 -#define FUSB300_OFFSET_CSR 0x0C -#define FUSB300_OFFSET_CXPORT 0x10 -#define FUSB300_OFFSET_EPSET0(n) (0x20 + (n - 1) * 0x30) -#define FUSB300_OFFSET_EPSET1(n) (0x24 + (n - 1) * 0x30) -#define FUSB300_OFFSET_EPSET2(n) (0x28 + (n - 1) * 0x30) -#define FUSB300_OFFSET_EPFFR(n) (0x2c + (n - 1) * 0x30) -#define FUSB300_OFFSET_EPSTRID(n) (0x40 + (n - 1) * 0x30) -#define FUSB300_OFFSET_HSPTM 0x300 -#define FUSB300_OFFSET_HSCR 0x304 -#define FUSB300_OFFSET_SSCR0 0x308 -#define FUSB300_OFFSET_SSCR1 0x30C -#define FUSB300_OFFSET_TT 0x310 -#define FUSB300_OFFSET_DEVNOTF 0x314 -#define FUSB300_OFFSET_DNC1 0x318 -#define FUSB300_OFFSET_CS 0x31C -#define FUSB300_OFFSET_SOF 0x324 -#define FUSB300_OFFSET_EFCS 0x328 -#define FUSB300_OFFSET_IGR0 0x400 -#define FUSB300_OFFSET_IGR1 0x404 -#define FUSB300_OFFSET_IGR2 0x408 -#define FUSB300_OFFSET_IGR3 0x40C -#define FUSB300_OFFSET_IGR4 0x410 -#define FUSB300_OFFSET_IGR5 0x414 -#define FUSB300_OFFSET_IGER0 0x420 -#define FUSB300_OFFSET_IGER1 0x424 -#define FUSB300_OFFSET_IGER2 0x428 -#define FUSB300_OFFSET_IGER3 0x42C -#define FUSB300_OFFSET_IGER4 0x430 -#define FUSB300_OFFSET_IGER5 0x434 -#define FUSB300_OFFSET_DMAHMER 0x500 -#define FUSB300_OFFSET_EPPRDRDY 0x504 -#define FUSB300_OFFSET_DMAEPMR 0x508 -#define FUSB300_OFFSET_DMAENR 0x50C -#define FUSB300_OFFSET_DMAAPR 0x510 -#define FUSB300_OFFSET_AHBCR 0x514 -#define FUSB300_OFFSET_EPPRD_W0(n) (0x520 + (n - 1) * 0x10) -#define FUSB300_OFFSET_EPPRD_W1(n) (0x524 + (n - 1) * 0x10) -#define FUSB300_OFFSET_EPPRD_W2(n) (0x528 + (n - 1) * 0x10) -#define FUSB300_OFFSET_EPRD_PTR(n) (0x52C + (n - 1) * 0x10) -#define FUSB300_OFFSET_BUFDBG_START 0x800 -#define FUSB300_OFFSET_BUFDBG_END 0xBFC -#define FUSB300_OFFSET_EPPORT(n) (0x1010 + (n - 1) * 0x10) - -/* - * * Global Control Register (offset = 000H) - * */ -#define FUSB300_GCR_SF_RST (1 << 8) -#define FUSB300_GCR_VBUS_STATUS (1 << 7) -#define FUSB300_GCR_FORCE_HS_SUSP (1 << 6) -#define FUSB300_GCR_SYNC_FIFO1_CLR (1 << 5) -#define FUSB300_GCR_SYNC_FIFO0_CLR (1 << 4) -#define FUSB300_GCR_FIFOCLR (1 << 3) -#define FUSB300_GCR_GLINTEN (1 << 2) -#define FUSB300_GCR_DEVEN_FS 0x3 -#define FUSB300_GCR_DEVEN_HS 0x2 -#define FUSB300_GCR_DEVEN_SS 0x1 -#define FUSB300_GCR_DEVDIS 0x0 -#define FUSB300_GCR_DEVEN_MSK 0x3 - - -/* - * *Global Test Mode (offset = 004H) - * */ -#define FUSB300_GTM_TST_DIS_SOFGEN (1 << 16) -#define FUSB300_GTM_TST_CUR_EP_ENTRY(n) ((n & 0xF) << 12) -#define FUSB300_GTM_TST_EP_ENTRY(n) ((n & 0xF) << 8) -#define FUSB300_GTM_TST_EP_NUM(n) ((n & 0xF) << 4) -#define FUSB300_GTM_TST_FIFO_DEG (1 << 1) -#define FUSB300_GTM_TSTMODE (1 << 0) - -/* - * * Device Address Register (offset = 008H) - * */ -#define FUSB300_DAR_SETCONFG (1 << 7) -#define FUSB300_DAR_DRVADDR(x) (x & 0x7F) -#define FUSB300_DAR_DRVADDR_MSK 0x7F - -/* - * *Control Transfer Configuration and Status Register - * (CX_Config_Status, offset = 00CH) - * */ -#define FUSB300_CSR_LEN(x) ((x & 0xFFFF) << 8) -#define FUSB300_CSR_LEN_MSK (0xFFFF << 8) -#define FUSB300_CSR_EMP (1 << 4) -#define FUSB300_CSR_FUL (1 << 3) -#define FUSB300_CSR_CLR (1 << 2) -#define FUSB300_CSR_STL (1 << 1) -#define FUSB300_CSR_DONE (1 << 0) - -/* - * * EPn Setting 0 (EPn_SET0, offset = 020H+(n-1)*30H, n=1~15 ) - * */ -#define FUSB300_EPSET0_STL_CLR (1 << 3) -#define FUSB300_EPSET0_CLRSEQNUM (1 << 2) -#define FUSB300_EPSET0_STL (1 << 0) - -/* - * * EPn Setting 1 (EPn_SET1, offset = 024H+(n-1)*30H, n=1~15) - * */ -#define FUSB300_EPSET1_START_ENTRY(x) ((x & 0xFF) << 24) -#define FUSB300_EPSET1_START_ENTRY_MSK (0xFF << 24) -#define FUSB300_EPSET1_FIFOENTRY(x) ((x & 0x1F) << 12) -#define FUSB300_EPSET1_FIFOENTRY_MSK (0x1f << 12) -#define FUSB300_EPSET1_INTERVAL(x) ((x & 0x7) << 6) -#define FUSB300_EPSET1_BWNUM(x) ((x & 0x3) << 4) -#define FUSB300_EPSET1_TYPEISO (1 << 2) -#define FUSB300_EPSET1_TYPEBLK (2 << 2) -#define FUSB300_EPSET1_TYPEINT (3 << 2) -#define FUSB300_EPSET1_TYPE(x) ((x & 0x3) << 2) -#define FUSB300_EPSET1_TYPE_MSK (0x3 << 2) -#define FUSB300_EPSET1_DIROUT (0 << 1) -#define FUSB300_EPSET1_DIRIN (1 << 1) -#define FUSB300_EPSET1_DIR(x) ((x & 0x1) << 1) -#define FUSB300_EPSET1_DIRIN (1 << 1) -#define FUSB300_EPSET1_DIR_MSK ((0x1) << 1) -#define FUSB300_EPSET1_ACTDIS 0 -#define FUSB300_EPSET1_ACTEN 1 - -/* - * *EPn Setting 2 (EPn_SET2, offset = 028H+(n-1)*30H, n=1~15) - * */ -#define FUSB300_EPSET2_ADDROFS(x) ((x & 0x7FFF) << 16) -#define FUSB300_EPSET2_ADDROFS_MSK (0x7fff << 16) -#define FUSB300_EPSET2_MPS(x) (x & 0x7FF) -#define FUSB300_EPSET2_MPS_MSK 0x7FF - -/* - * * EPn FIFO Register (offset = 2cH+(n-1)*30H) - * */ -#define FUSB300_FFR_RST (1 << 31) -#define FUSB300_FF_FUL (1 << 30) -#define FUSB300_FF_EMPTY (1 << 29) -#define FUSB300_FFR_BYCNT 0x1FFFF - -/* - * *EPn Stream ID (EPn_STR_ID, offset = 040H+(n-1)*30H, n=1~15) - * */ -#define FUSB300_STRID_STREN (1 << 16) -#define FUSB300_STRID_STRID(x) (x & 0xFFFF) - -/* - * *HS PHY Test Mode (offset = 300H) - * */ -#define FUSB300_HSPTM_TSTPKDONE (1 << 4) -#define FUSB300_HSPTM_TSTPKT (1 << 3) -#define FUSB300_HSPTM_TSTSET0NAK (1 << 2) -#define FUSB300_HSPTM_TSTKSTA (1 << 1) -#define FUSB300_HSPTM_TSTJSTA (1 << 0) - -/* - * *HS Control Register (offset = 304H) - * */ -#define FUSB300_HSCR_HS_LPM_PERMIT (1 << 8) -#define FUSB300_HSCR_HS_LPM_RMWKUP (1 << 7) -#define FUSB300_HSCR_CAP_LPM_RMWKUP (1 << 6) -#define FUSB300_HSCR_HS_GOSUSP (1 << 5) -#define FUSB300_HSCR_HS_GORMWKU (1 << 4) -#define FUSB300_HSCR_CAP_RMWKUP (1 << 3) -#define FUSB300_HSCR_IDLECNT_0MS 0 -#define FUSB300_HSCR_IDLECNT_1MS 1 -#define FUSB300_HSCR_IDLECNT_2MS 2 -#define FUSB300_HSCR_IDLECNT_3MS 3 -#define FUSB300_HSCR_IDLECNT_4MS 4 -#define FUSB300_HSCR_IDLECNT_5MS 5 -#define FUSB300_HSCR_IDLECNT_6MS 6 -#define FUSB300_HSCR_IDLECNT_7MS 7 - -/* - * * SS Controller Register 0 (offset = 308H) - * */ -#define FUSB300_SSCR0_MAX_INTERVAL(x) ((x & 0x7) << 4) -#define FUSB300_SSCR0_U2_FUN_EN (1 << 1) -#define FUSB300_SSCR0_U1_FUN_EN (1 << 0) - -/* - * * SS Controller Register 1 (offset = 30CH) - * */ -#define FUSB300_SSCR1_GO_U3_DONE (1 << 8) -#define FUSB300_SSCR1_TXDEEMPH_LEVEL (1 << 7) -#define FUSB300_SSCR1_DIS_SCRMB (1 << 6) -#define FUSB300_SSCR1_FORCE_RECOVERY (1 << 5) -#define FUSB300_SSCR1_U3_WAKEUP_EN (1 << 4) -#define FUSB300_SSCR1_U2_EXIT_EN (1 << 3) -#define FUSB300_SSCR1_U1_EXIT_EN (1 << 2) -#define FUSB300_SSCR1_U2_ENTRY_EN (1 << 1) -#define FUSB300_SSCR1_U1_ENTRY_EN (1 << 0) - -/* - * *SS Controller Register 2 (offset = 310H) - * */ -#define FUSB300_SSCR2_SS_TX_SWING (1 << 25) -#define FUSB300_SSCR2_FORCE_LINKPM_ACCEPT (1 << 24) -#define FUSB300_SSCR2_U2_INACT_TIMEOUT(x) ((x & 0xFF) << 16) -#define FUSB300_SSCR2_U1TIMEOUT(x) ((x & 0xFF) << 8) -#define FUSB300_SSCR2_U2TIMEOUT(x) (x & 0xFF) - -/* - * *SS Device Notification Control (DEV_NOTF, offset = 314H) - * */ -#define FUSB300_DEVNOTF_CONTEXT0(x) ((x & 0xFFFFFF) << 8) -#define FUSB300_DEVNOTF_TYPE_DIS 0 -#define FUSB300_DEVNOTF_TYPE_FUNCWAKE 1 -#define FUSB300_DEVNOTF_TYPE_LTM 2 -#define FUSB300_DEVNOTF_TYPE_BUSINT_ADJMSG 3 - -/* - * *BFM Arbiter Priority Register (BFM_ARB offset = 31CH) - * */ -#define FUSB300_BFMARB_ARB_M1 (1 << 3) -#define FUSB300_BFMARB_ARB_M0 (1 << 2) -#define FUSB300_BFMARB_ARB_S1 (1 << 1) -#define FUSB300_BFMARB_ARB_S0 1 - -/* - * *Vendor Specific IO Control Register (offset = 320H) - * */ -#define FUSB300_VSIC_VCTLOAD_N (1 << 8) -#define FUSB300_VSIC_VCTL(x) (x & 0x3F) - -/* - * *SOF Mask Timer (offset = 324H) - * */ -#define FUSB300_SOF_MASK_TIMER_HS 0x044c -#define FUSB300_SOF_MASK_TIMER_FS 0x2710 - -/* - * *Error Flag and Control Status (offset = 328H) - * */ -#define FUSB300_EFCS_PM_STATE_U3 3 -#define FUSB300_EFCS_PM_STATE_U2 2 -#define FUSB300_EFCS_PM_STATE_U1 1 -#define FUSB300_EFCS_PM_STATE_U0 0 - -/* - * *Interrupt Group 0 Register (offset = 400H) - * */ -#define FUSB300_IGR0_EP15_PRD_INT (1 << 31) -#define FUSB300_IGR0_EP14_PRD_INT (1 << 30) -#define FUSB300_IGR0_EP13_PRD_INT (1 << 29) -#define FUSB300_IGR0_EP12_PRD_INT (1 << 28) -#define FUSB300_IGR0_EP11_PRD_INT (1 << 27) -#define FUSB300_IGR0_EP10_PRD_INT (1 << 26) -#define FUSB300_IGR0_EP9_PRD_INT (1 << 25) -#define FUSB300_IGR0_EP8_PRD_INT (1 << 24) -#define FUSB300_IGR0_EP7_PRD_INT (1 << 23) -#define FUSB300_IGR0_EP6_PRD_INT (1 << 22) -#define FUSB300_IGR0_EP5_PRD_INT (1 << 21) -#define FUSB300_IGR0_EP4_PRD_INT (1 << 20) -#define FUSB300_IGR0_EP3_PRD_INT (1 << 19) -#define FUSB300_IGR0_EP2_PRD_INT (1 << 18) -#define FUSB300_IGR0_EP1_PRD_INT (1 << 17) -#define FUSB300_IGR0_EPn_PRD_INT(n) (1 << (n + 16)) - -#define FUSB300_IGR0_EP15_FIFO_INT (1 << 15) -#define FUSB300_IGR0_EP14_FIFO_INT (1 << 14) -#define FUSB300_IGR0_EP13_FIFO_INT (1 << 13) -#define FUSB300_IGR0_EP12_FIFO_INT (1 << 12) -#define FUSB300_IGR0_EP11_FIFO_INT (1 << 11) -#define FUSB300_IGR0_EP10_FIFO_INT (1 << 10) -#define FUSB300_IGR0_EP9_FIFO_INT (1 << 9) -#define FUSB300_IGR0_EP8_FIFO_INT (1 << 8) -#define FUSB300_IGR0_EP7_FIFO_INT (1 << 7) -#define FUSB300_IGR0_EP6_FIFO_INT (1 << 6) -#define FUSB300_IGR0_EP5_FIFO_INT (1 << 5) -#define FUSB300_IGR0_EP4_FIFO_INT (1 << 4) -#define FUSB300_IGR0_EP3_FIFO_INT (1 << 3) -#define FUSB300_IGR0_EP2_FIFO_INT (1 << 2) -#define FUSB300_IGR0_EP1_FIFO_INT (1 << 1) -#define FUSB300_IGR0_EPn_FIFO_INT(n) (1 << n) - -/* - * *Interrupt Group 1 Register (offset = 404H) - * */ -#define FUSB300_IGR1_INTGRP5 (1 << 31) -#define FUSB300_IGR1_VBUS_CHG_INT (1 << 30) -#define FUSB300_IGR1_SYNF1_EMPTY_INT (1 << 29) -#define FUSB300_IGR1_SYNF0_EMPTY_INT (1 << 28) -#define FUSB300_IGR1_U3_EXIT_FAIL_INT (1 << 27) -#define FUSB300_IGR1_U2_EXIT_FAIL_INT (1 << 26) -#define FUSB300_IGR1_U1_EXIT_FAIL_INT (1 << 25) -#define FUSB300_IGR1_U2_ENTRY_FAIL_INT (1 << 24) -#define FUSB300_IGR1_U1_ENTRY_FAIL_INT (1 << 23) -#define FUSB300_IGR1_U3_EXIT_INT (1 << 22) -#define FUSB300_IGR1_U2_EXIT_INT (1 << 21) -#define FUSB300_IGR1_U1_EXIT_INT (1 << 20) -#define FUSB300_IGR1_U3_ENTRY_INT (1 << 19) -#define FUSB300_IGR1_U2_ENTRY_INT (1 << 18) -#define FUSB300_IGR1_U1_ENTRY_INT (1 << 17) -#define FUSB300_IGR1_HOT_RST_INT (1 << 16) -#define FUSB300_IGR1_WARM_RST_INT (1 << 15) -#define FUSB300_IGR1_RESM_INT (1 << 14) -#define FUSB300_IGR1_SUSP_INT (1 << 13) -#define FUSB300_IGR1_HS_LPM_INT (1 << 12) -#define FUSB300_IGR1_USBRST_INT (1 << 11) -#define FUSB300_IGR1_DEV_MODE_CHG_INT (1 << 9) -#define FUSB300_IGR1_CX_COMABT_INT (1 << 8) -#define FUSB300_IGR1_CX_COMFAIL_INT (1 << 7) -#define FUSB300_IGR1_CX_CMDEND_INT (1 << 6) -#define FUSB300_IGR1_CX_OUT_INT (1 << 5) -#define FUSB300_IGR1_CX_IN_INT (1 << 4) -#define FUSB300_IGR1_CX_SETUP_INT (1 << 3) -#define FUSB300_IGR1_INTGRP4 (1 << 2) -#define FUSB300_IGR1_INTGRP3 (1 << 1) -#define FUSB300_IGR1_INTGRP2 (1 << 0) - -/* - * *Interrupt Group 2 Register (offset = 408H) - * */ -#define FUSB300_IGR2_EP6_STR_ACCEPT_INT (1 << 29) -#define FUSB300_IGR2_EP6_STR_RESUME_INT (1 << 28) -#define FUSB300_IGR2_EP6_STR_REQ_INT (1 << 27) -#define FUSB300_IGR2_EP6_STR_NOTRDY_INT (1 << 26) -#define FUSB300_IGR2_EP6_STR_PRIME_INT (1 << 25) -#define FUSB300_IGR2_EP5_STR_ACCEPT_INT (1 << 24) -#define FUSB300_IGR2_EP5_STR_RESUME_INT (1 << 23) -#define FUSB300_IGR2_EP5_STR_REQ_INT (1 << 22) -#define FUSB300_IGR2_EP5_STR_NOTRDY_INT (1 << 21) -#define FUSB300_IGR2_EP5_STR_PRIME_INT (1 << 20) -#define FUSB300_IGR2_EP4_STR_ACCEPT_INT (1 << 19) -#define FUSB300_IGR2_EP4_STR_RESUME_INT (1 << 18) -#define FUSB300_IGR2_EP4_STR_REQ_INT (1 << 17) -#define FUSB300_IGR2_EP4_STR_NOTRDY_INT (1 << 16) -#define FUSB300_IGR2_EP4_STR_PRIME_INT (1 << 15) -#define FUSB300_IGR2_EP3_STR_ACCEPT_INT (1 << 14) -#define FUSB300_IGR2_EP3_STR_RESUME_INT (1 << 13) -#define FUSB300_IGR2_EP3_STR_REQ_INT (1 << 12) -#define FUSB300_IGR2_EP3_STR_NOTRDY_INT (1 << 11) -#define FUSB300_IGR2_EP3_STR_PRIME_INT (1 << 10) -#define FUSB300_IGR2_EP2_STR_ACCEPT_INT (1 << 9) -#define FUSB300_IGR2_EP2_STR_RESUME_INT (1 << 8) -#define FUSB300_IGR2_EP2_STR_REQ_INT (1 << 7) -#define FUSB300_IGR2_EP2_STR_NOTRDY_INT (1 << 6) -#define FUSB300_IGR2_EP2_STR_PRIME_INT (1 << 5) -#define FUSB300_IGR2_EP1_STR_ACCEPT_INT (1 << 4) -#define FUSB300_IGR2_EP1_STR_RESUME_INT (1 << 3) -#define FUSB300_IGR2_EP1_STR_REQ_INT (1 << 2) -#define FUSB300_IGR2_EP1_STR_NOTRDY_INT (1 << 1) -#define FUSB300_IGR2_EP1_STR_PRIME_INT (1 << 0) - -#define FUSB300_IGR2_EP_STR_ACCEPT_INT(n) (1 << (5 * n - 1)) -#define FUSB300_IGR2_EP_STR_RESUME_INT(n) (1 << (5 * n - 2)) -#define FUSB300_IGR2_EP_STR_REQ_INT(n) (1 << (5 * n - 3)) -#define FUSB300_IGR2_EP_STR_NOTRDY_INT(n) (1 << (5 * n - 4)) -#define FUSB300_IGR2_EP_STR_PRIME_INT(n) (1 << (5 * n - 5)) - -/* - * *Interrupt Group 3 Register (offset = 40CH) - * */ -#define FUSB300_IGR3_EP12_STR_ACCEPT_INT (1 << 29) -#define FUSB300_IGR3_EP12_STR_RESUME_INT (1 << 28) -#define FUSB300_IGR3_EP12_STR_REQ_INT (1 << 27) -#define FUSB300_IGR3_EP12_STR_NOTRDY_INT (1 << 26) -#define FUSB300_IGR3_EP12_STR_PRIME_INT (1 << 25) -#define FUSB300_IGR3_EP11_STR_ACCEPT_INT (1 << 24) -#define FUSB300_IGR3_EP11_STR_RESUME_INT (1 << 23) -#define FUSB300_IGR3_EP11_STR_REQ_INT (1 << 22) -#define FUSB300_IGR3_EP11_STR_NOTRDY_INT (1 << 21) -#define FUSB300_IGR3_EP11_STR_PRIME_INT (1 << 20) -#define FUSB300_IGR3_EP10_STR_ACCEPT_INT (1 << 19) -#define FUSB300_IGR3_EP10_STR_RESUME_INT (1 << 18) -#define FUSB300_IGR3_EP10_STR_REQ_INT (1 << 17) -#define FUSB300_IGR3_EP10_STR_NOTRDY_INT (1 << 16) -#define FUSB300_IGR3_EP10_STR_PRIME_INT (1 << 15) -#define FUSB300_IGR3_EP9_STR_ACCEPT_INT (1 << 14) -#define FUSB300_IGR3_EP9_STR_RESUME_INT (1 << 13) -#define FUSB300_IGR3_EP9_STR_REQ_INT (1 << 12) -#define FUSB300_IGR3_EP9_STR_NOTRDY_INT (1 << 11) -#define FUSB300_IGR3_EP9_STR_PRIME_INT (1 << 10) -#define FUSB300_IGR3_EP8_STR_ACCEPT_INT (1 << 9) -#define FUSB300_IGR3_EP8_STR_RESUME_INT (1 << 8) -#define FUSB300_IGR3_EP8_STR_REQ_INT (1 << 7) -#define FUSB300_IGR3_EP8_STR_NOTRDY_INT (1 << 6) -#define FUSB300_IGR3_EP8_STR_PRIME_INT (1 << 5) -#define FUSB300_IGR3_EP7_STR_ACCEPT_INT (1 << 4) -#define FUSB300_IGR3_EP7_STR_RESUME_INT (1 << 3) -#define FUSB300_IGR3_EP7_STR_REQ_INT (1 << 2) -#define FUSB300_IGR3_EP7_STR_NOTRDY_INT (1 << 1) -#define FUSB300_IGR3_EP7_STR_PRIME_INT (1 << 0) - -#define FUSB300_IGR3_EP_STR_ACCEPT_INT(n) (1 << (5 * (n - 6) - 1)) -#define FUSB300_IGR3_EP_STR_RESUME_INT(n) (1 << (5 * (n - 6) - 2)) -#define FUSB300_IGR3_EP_STR_REQ_INT(n) (1 << (5 * (n - 6) - 3)) -#define FUSB300_IGR3_EP_STR_NOTRDY_INT(n) (1 << (5 * (n - 6) - 4)) -#define FUSB300_IGR3_EP_STR_PRIME_INT(n) (1 << (5 * (n - 6) - 5)) - -/* - * *Interrupt Group 4 Register (offset = 410H) - * */ -#define FUSB300_IGR4_EP15_RX0_INT (1 << 31) -#define FUSB300_IGR4_EP14_RX0_INT (1 << 30) -#define FUSB300_IGR4_EP13_RX0_INT (1 << 29) -#define FUSB300_IGR4_EP12_RX0_INT (1 << 28) -#define FUSB300_IGR4_EP11_RX0_INT (1 << 27) -#define FUSB300_IGR4_EP10_RX0_INT (1 << 26) -#define FUSB300_IGR4_EP9_RX0_INT (1 << 25) -#define FUSB300_IGR4_EP8_RX0_INT (1 << 24) -#define FUSB300_IGR4_EP7_RX0_INT (1 << 23) -#define FUSB300_IGR4_EP6_RX0_INT (1 << 22) -#define FUSB300_IGR4_EP5_RX0_INT (1 << 21) -#define FUSB300_IGR4_EP4_RX0_INT (1 << 20) -#define FUSB300_IGR4_EP3_RX0_INT (1 << 19) -#define FUSB300_IGR4_EP2_RX0_INT (1 << 18) -#define FUSB300_IGR4_EP1_RX0_INT (1 << 17) -#define FUSB300_IGR4_EP_RX0_INT(x) (1 << (x + 16)) -#define FUSB300_IGR4_EP15_STR_ACCEPT_INT (1 << 14) -#define FUSB300_IGR4_EP15_STR_RESUME_INT (1 << 13) -#define FUSB300_IGR4_EP15_STR_REQ_INT (1 << 12) -#define FUSB300_IGR4_EP15_STR_NOTRDY_INT (1 << 11) -#define FUSB300_IGR4_EP15_STR_PRIME_INT (1 << 10) -#define FUSB300_IGR4_EP14_STR_ACCEPT_INT (1 << 9) -#define FUSB300_IGR4_EP14_STR_RESUME_INT (1 << 8) -#define FUSB300_IGR4_EP14_STR_REQ_INT (1 << 7) -#define FUSB300_IGR4_EP14_STR_NOTRDY_INT (1 << 6) -#define FUSB300_IGR4_EP14_STR_PRIME_INT (1 << 5) -#define FUSB300_IGR4_EP13_STR_ACCEPT_INT (1 << 4) -#define FUSB300_IGR4_EP13_STR_RESUME_INT (1 << 3) -#define FUSB300_IGR4_EP13_STR_REQ_INT (1 << 2) -#define FUSB300_IGR4_EP13_STR_NOTRDY_INT (1 << 1) -#define FUSB300_IGR4_EP13_STR_PRIME_INT (1 << 0) - -#define FUSB300_IGR4_EP_STR_ACCEPT_INT(n) (1 << (5 * (n - 12) - 1)) -#define FUSB300_IGR4_EP_STR_RESUME_INT(n) (1 << (5 * (n - 12) - 2)) -#define FUSB300_IGR4_EP_STR_REQ_INT(n) (1 << (5 * (n - 12) - 3)) -#define FUSB300_IGR4_EP_STR_NOTRDY_INT(n) (1 << (5 * (n - 12) - 4)) -#define FUSB300_IGR4_EP_STR_PRIME_INT(n) (1 << (5 * (n - 12) - 5)) - -/* - * *Interrupt Group 5 Register (offset = 414H) - * */ -#define FUSB300_IGR5_EP_STL_INT(n) (1 << n) - -/* - * *Interrupt Enable Group 0 Register (offset = 420H) - * */ -#define FUSB300_IGER0_EEP15_PRD_INT (1 << 31) -#define FUSB300_IGER0_EEP14_PRD_INT (1 << 30) -#define FUSB300_IGER0_EEP13_PRD_INT (1 << 29) -#define FUSB300_IGER0_EEP12_PRD_INT (1 << 28) -#define FUSB300_IGER0_EEP11_PRD_INT (1 << 27) -#define FUSB300_IGER0_EEP10_PRD_INT (1 << 26) -#define FUSB300_IGER0_EEP9_PRD_INT (1 << 25) -#define FUSB300_IGER0_EP8_PRD_INT (1 << 24) -#define FUSB300_IGER0_EEP7_PRD_INT (1 << 23) -#define FUSB300_IGER0_EEP6_PRD_INT (1 << 22) -#define FUSB300_IGER0_EEP5_PRD_INT (1 << 21) -#define FUSB300_IGER0_EEP4_PRD_INT (1 << 20) -#define FUSB300_IGER0_EEP3_PRD_INT (1 << 19) -#define FUSB300_IGER0_EEP2_PRD_INT (1 << 18) -#define FUSB300_IGER0_EEP1_PRD_INT (1 << 17) -#define FUSB300_IGER0_EEPn_PRD_INT(n) (1 << (n + 16)) - -#define FUSB300_IGER0_EEP15_FIFO_INT (1 << 15) -#define FUSB300_IGER0_EEP14_FIFO_INT (1 << 14) -#define FUSB300_IGER0_EEP13_FIFO_INT (1 << 13) -#define FUSB300_IGER0_EEP12_FIFO_INT (1 << 12) -#define FUSB300_IGER0_EEP11_FIFO_INT (1 << 11) -#define FUSB300_IGER0_EEP10_FIFO_INT (1 << 10) -#define FUSB300_IGER0_EEP9_FIFO_INT (1 << 9) -#define FUSB300_IGER0_EEP8_FIFO_INT (1 << 8) -#define FUSB300_IGER0_EEP7_FIFO_INT (1 << 7) -#define FUSB300_IGER0_EEP6_FIFO_INT (1 << 6) -#define FUSB300_IGER0_EEP5_FIFO_INT (1 << 5) -#define FUSB300_IGER0_EEP4_FIFO_INT (1 << 4) -#define FUSB300_IGER0_EEP3_FIFO_INT (1 << 3) -#define FUSB300_IGER0_EEP2_FIFO_INT (1 << 2) -#define FUSB300_IGER0_EEP1_FIFO_INT (1 << 1) -#define FUSB300_IGER0_EEPn_FIFO_INT(n) (1 << n) - -/* - * *Interrupt Enable Group 1 Register (offset = 424H) - * */ -#define FUSB300_IGER1_EINT_GRP5 (1 << 31) -#define FUSB300_IGER1_VBUS_CHG_INT (1 << 30) -#define FUSB300_IGER1_SYNF1_EMPTY_INT (1 << 29) -#define FUSB300_IGER1_SYNF0_EMPTY_INT (1 << 28) -#define FUSB300_IGER1_U3_EXIT_FAIL_INT (1 << 27) -#define FUSB300_IGER1_U2_EXIT_FAIL_INT (1 << 26) -#define FUSB300_IGER1_U1_EXIT_FAIL_INT (1 << 25) -#define FUSB300_IGER1_U2_ENTRY_FAIL_INT (1 << 24) -#define FUSB300_IGER1_U1_ENTRY_FAIL_INT (1 << 23) -#define FUSB300_IGER1_U3_EXIT_INT (1 << 22) -#define FUSB300_IGER1_U2_EXIT_INT (1 << 21) -#define FUSB300_IGER1_U1_EXIT_INT (1 << 20) -#define FUSB300_IGER1_U3_ENTRY_INT (1 << 19) -#define FUSB300_IGER1_U2_ENTRY_INT (1 << 18) -#define FUSB300_IGER1_U1_ENTRY_INT (1 << 17) -#define FUSB300_IGER1_HOT_RST_INT (1 << 16) -#define FUSB300_IGER1_WARM_RST_INT (1 << 15) -#define FUSB300_IGER1_RESM_INT (1 << 14) -#define FUSB300_IGER1_SUSP_INT (1 << 13) -#define FUSB300_IGER1_LPM_INT (1 << 12) -#define FUSB300_IGER1_HS_RST_INT (1 << 11) -#define FUSB300_IGER1_EDEV_MODE_CHG_INT (1 << 9) -#define FUSB300_IGER1_CX_COMABT_INT (1 << 8) -#define FUSB300_IGER1_CX_COMFAIL_INT (1 << 7) -#define FUSB300_IGER1_CX_CMDEND_INT (1 << 6) -#define FUSB300_IGER1_CX_OUT_INT (1 << 5) -#define FUSB300_IGER1_CX_IN_INT (1 << 4) -#define FUSB300_IGER1_CX_SETUP_INT (1 << 3) -#define FUSB300_IGER1_INTGRP4 (1 << 2) -#define FUSB300_IGER1_INTGRP3 (1 << 1) -#define FUSB300_IGER1_INTGRP2 (1 << 0) - -/* - * *Interrupt Enable Group 2 Register (offset = 428H) - * */ -#define FUSB300_IGER2_EEP_STR_ACCEPT_INT(n) (1 << (5 * n - 1)) -#define FUSB300_IGER2_EEP_STR_RESUME_INT(n) (1 << (5 * n - 2)) -#define FUSB300_IGER2_EEP_STR_REQ_INT(n) (1 << (5 * n - 3)) -#define FUSB300_IGER2_EEP_STR_NOTRDY_INT(n) (1 << (5 * n - 4)) -#define FUSB300_IGER2_EEP_STR_PRIME_INT(n) (1 << (5 * n - 5)) - -/* - * *Interrupt Enable Group 3 Register (offset = 42CH) - * */ - -#define FUSB300_IGER3_EEP_STR_ACCEPT_INT(n) (1 << (5 * (n - 6) - 1)) -#define FUSB300_IGER3_EEP_STR_RESUME_INT(n) (1 << (5 * (n - 6) - 2)) -#define FUSB300_IGER3_EEP_STR_REQ_INT(n) (1 << (5 * (n - 6) - 3)) -#define FUSB300_IGER3_EEP_STR_NOTRDY_INT(n) (1 << (5 * (n - 6) - 4)) -#define FUSB300_IGER3_EEP_STR_PRIME_INT(n) (1 << (5 * (n - 6) - 5)) - -/* - * *Interrupt Enable Group 4 Register (offset = 430H) - * */ - -#define FUSB300_IGER4_EEP_RX0_INT(n) (1 << (n + 16)) -#define FUSB300_IGER4_EEP_STR_ACCEPT_INT(n) (1 << (5 * (n - 6) - 1)) -#define FUSB300_IGER4_EEP_STR_RESUME_INT(n) (1 << (5 * (n - 6) - 2)) -#define FUSB300_IGER4_EEP_STR_REQ_INT(n) (1 << (5 * (n - 6) - 3)) -#define FUSB300_IGER4_EEP_STR_NOTRDY_INT(n) (1 << (5 * (n - 6) - 4)) -#define FUSB300_IGER4_EEP_STR_PRIME_INT(n) (1 << (5 * (n - 6) - 5)) - -/* EP PRD Ready (EP_PRD_RDY, offset = 504H) */ - -#define FUSB300_EPPRDR_EP15_PRD_RDY (1 << 15) -#define FUSB300_EPPRDR_EP14_PRD_RDY (1 << 14) -#define FUSB300_EPPRDR_EP13_PRD_RDY (1 << 13) -#define FUSB300_EPPRDR_EP12_PRD_RDY (1 << 12) -#define FUSB300_EPPRDR_EP11_PRD_RDY (1 << 11) -#define FUSB300_EPPRDR_EP10_PRD_RDY (1 << 10) -#define FUSB300_EPPRDR_EP9_PRD_RDY (1 << 9) -#define FUSB300_EPPRDR_EP8_PRD_RDY (1 << 8) -#define FUSB300_EPPRDR_EP7_PRD_RDY (1 << 7) -#define FUSB300_EPPRDR_EP6_PRD_RDY (1 << 6) -#define FUSB300_EPPRDR_EP5_PRD_RDY (1 << 5) -#define FUSB300_EPPRDR_EP4_PRD_RDY (1 << 4) -#define FUSB300_EPPRDR_EP3_PRD_RDY (1 << 3) -#define FUSB300_EPPRDR_EP2_PRD_RDY (1 << 2) -#define FUSB300_EPPRDR_EP1_PRD_RDY (1 << 1) -#define FUSB300_EPPRDR_EP_PRD_RDY(n) (1 << n) - -/* AHB Bus Control Register (offset = 514H) */ -#define FUSB300_AHBBCR_S1_SPLIT_ON (1 << 17) -#define FUSB300_AHBBCR_S0_SPLIT_ON (1 << 16) -#define FUSB300_AHBBCR_S1_1entry (0 << 12) -#define FUSB300_AHBBCR_S1_4entry (3 << 12) -#define FUSB300_AHBBCR_S1_8entry (5 << 12) -#define FUSB300_AHBBCR_S1_16entry (7 << 12) -#define FUSB300_AHBBCR_S0_1entry (0 << 8) -#define FUSB300_AHBBCR_S0_4entry (3 << 8) -#define FUSB300_AHBBCR_S0_8entry (5 << 8) -#define FUSB300_AHBBCR_S0_16entry (7 << 8) -#define FUSB300_AHBBCR_M1_BURST_SINGLE (0 << 4) -#define FUSB300_AHBBCR_M1_BURST_INCR (1 << 4) -#define FUSB300_AHBBCR_M1_BURST_INCR4 (3 << 4) -#define FUSB300_AHBBCR_M1_BURST_INCR8 (5 << 4) -#define FUSB300_AHBBCR_M1_BURST_INCR16 (7 << 4) -#define FUSB300_AHBBCR_M0_BURST_SINGLE 0 -#define FUSB300_AHBBCR_M0_BURST_INCR 1 -#define FUSB300_AHBBCR_M0_BURST_INCR4 3 -#define FUSB300_AHBBCR_M0_BURST_INCR8 5 -#define FUSB300_AHBBCR_M0_BURST_INCR16 7 -#define FUSB300_IGER5_EEP_STL_INT(n) (1 << n) - -/* WORD 0 Data Structure of PRD Table */ -#define FUSB300_EPPRD0_M (1 << 30) -#define FUSB300_EPPRD0_O (1 << 29) -/* The finished prd */ -#define FUSB300_EPPRD0_F (1 << 28) -#define FUSB300_EPPRD0_I (1 << 27) -#define FUSB300_EPPRD0_A (1 << 26) -/* To decide HW point to first prd at next time */ -#define FUSB300_EPPRD0_L (1 << 25) -#define FUSB300_EPPRD0_H (1 << 24) -#define FUSB300_EPPRD0_BTC(n) (n & 0xFFFFFF) - -/*----------------------------------------------------------------------*/ -#define FUSB300_MAX_NUM_EP 16 - -#define FUSB300_FIFO_ENTRY_NUM 8 -#define FUSB300_MAX_FIFO_ENTRY 8 - -#define SS_CTL_MAX_PACKET_SIZE 0x200 -#define SS_BULK_MAX_PACKET_SIZE 0x400 -#define SS_INT_MAX_PACKET_SIZE 0x400 -#define SS_ISO_MAX_PACKET_SIZE 0x400 - -#define HS_BULK_MAX_PACKET_SIZE 0x200 -#define HS_CTL_MAX_PACKET_SIZE 0x40 -#define HS_INT_MAX_PACKET_SIZE 0x400 -#define HS_ISO_MAX_PACKET_SIZE 0x400 - -struct fusb300_ep_info { - u8 epnum; - u8 type; - u8 interval; - u8 dir_in; - u16 maxpacket; - u16 addrofs; - u16 bw_num; -}; - -struct fusb300_request { - - struct usb_request req; - struct list_head queue; -}; - - -struct fusb300_ep { - struct usb_ep ep; - struct fusb300 *fusb300; - - struct list_head queue; - unsigned stall:1; - unsigned wedged:1; - unsigned use_dma:1; - - unsigned char epnum; - unsigned char type; -}; - -struct fusb300 { - spinlock_t lock; - void __iomem *reg; - - unsigned long irq_trigger; - - struct usb_gadget gadget; - struct usb_gadget_driver *driver; - - struct fusb300_ep *ep[FUSB300_MAX_NUM_EP]; - - struct usb_request *ep0_req; /* for internal request */ - __le16 ep0_data; - u32 ep0_length; /* for internal request */ - u8 ep0_dir; /* 0/0x80 out/in */ - - u8 fifo_entry_num; /* next start fifo entry */ - u32 addrofs; /* next fifo address offset */ - u8 reenum; /* if re-enumeration */ -}; - -#define to_fusb300(g) (container_of((g), struct fusb300, gadget)) - -#endif diff --git a/drivers/usb/gadget/udc/mv_u3d.h b/drivers/usb/gadget/udc/mv_u3d.h deleted file mode 100644 index 66b84f792f64..000000000000 --- a/drivers/usb/gadget/udc/mv_u3d.h +++ /dev/null @@ -1,317 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (C) 2011 Marvell International Ltd. All rights reserved. - */ - -#ifndef __MV_U3D_H -#define __MV_U3D_H - -#define MV_U3D_EP_CONTEXT_ALIGNMENT 32 -#define MV_U3D_TRB_ALIGNMENT 16 -#define MV_U3D_DMA_BOUNDARY 4096 -#define MV_U3D_EP0_MAX_PKT_SIZE 512 - -/* ep0 transfer state */ -#define MV_U3D_WAIT_FOR_SETUP 0 -#define MV_U3D_DATA_STATE_XMIT 1 -#define MV_U3D_DATA_STATE_NEED_ZLP 2 -#define MV_U3D_WAIT_FOR_OUT_STATUS 3 -#define MV_U3D_DATA_STATE_RECV 4 -#define MV_U3D_STATUS_STAGE 5 - -#define MV_U3D_EP_MAX_LENGTH_TRANSFER 0x10000 - -/* USB3 Interrupt Status */ -#define MV_U3D_USBINT_SETUP 0x00000001 -#define MV_U3D_USBINT_RX_COMPLETE 0x00000002 -#define MV_U3D_USBINT_TX_COMPLETE 0x00000004 -#define MV_U3D_USBINT_UNDER_RUN 0x00000008 -#define MV_U3D_USBINT_RXDESC_ERR 0x00000010 -#define MV_U3D_USBINT_TXDESC_ERR 0x00000020 -#define MV_U3D_USBINT_RX_TRB_COMPLETE 0x00000040 -#define MV_U3D_USBINT_TX_TRB_COMPLETE 0x00000080 -#define MV_U3D_USBINT_VBUS_VALID 0x00010000 -#define MV_U3D_USBINT_STORAGE_CMD_FULL 0x00020000 -#define MV_U3D_USBINT_LINK_CHG 0x01000000 - -/* USB3 Interrupt Enable */ -#define MV_U3D_INTR_ENABLE_SETUP 0x00000001 -#define MV_U3D_INTR_ENABLE_RX_COMPLETE 0x00000002 -#define MV_U3D_INTR_ENABLE_TX_COMPLETE 0x00000004 -#define MV_U3D_INTR_ENABLE_UNDER_RUN 0x00000008 -#define MV_U3D_INTR_ENABLE_RXDESC_ERR 0x00000010 -#define MV_U3D_INTR_ENABLE_TXDESC_ERR 0x00000020 -#define MV_U3D_INTR_ENABLE_RX_TRB_COMPLETE 0x00000040 -#define MV_U3D_INTR_ENABLE_TX_TRB_COMPLETE 0x00000080 -#define MV_U3D_INTR_ENABLE_RX_BUFFER_ERR 0x00000100 -#define MV_U3D_INTR_ENABLE_VBUS_VALID 0x00010000 -#define MV_U3D_INTR_ENABLE_STORAGE_CMD_FULL 0x00020000 -#define MV_U3D_INTR_ENABLE_LINK_CHG 0x01000000 -#define MV_U3D_INTR_ENABLE_PRIME_STATUS 0x02000000 - -/* USB3 Link Change */ -#define MV_U3D_LINK_CHANGE_LINK_UP 0x00000001 -#define MV_U3D_LINK_CHANGE_SUSPEND 0x00000002 -#define MV_U3D_LINK_CHANGE_RESUME 0x00000004 -#define MV_U3D_LINK_CHANGE_WRESET 0x00000008 -#define MV_U3D_LINK_CHANGE_HRESET 0x00000010 -#define MV_U3D_LINK_CHANGE_VBUS_INVALID 0x00000020 -#define MV_U3D_LINK_CHANGE_INACT 0x00000040 -#define MV_U3D_LINK_CHANGE_DISABLE_AFTER_U0 0x00000080 -#define MV_U3D_LINK_CHANGE_U1 0x00000100 -#define MV_U3D_LINK_CHANGE_U2 0x00000200 -#define MV_U3D_LINK_CHANGE_U3 0x00000400 - -/* bridge setting */ -#define MV_U3D_BRIDGE_SETTING_VBUS_VALID (1 << 16) - -/* Command Register Bit Masks */ -#define MV_U3D_CMD_RUN_STOP 0x00000001 -#define MV_U3D_CMD_CTRL_RESET 0x00000002 - -/* ep control register */ -#define MV_U3D_EPXCR_EP_TYPE_CONTROL 0 -#define MV_U3D_EPXCR_EP_TYPE_ISOC 1 -#define MV_U3D_EPXCR_EP_TYPE_BULK 2 -#define MV_U3D_EPXCR_EP_TYPE_INT 3 -#define MV_U3D_EPXCR_EP_ENABLE_SHIFT 4 -#define MV_U3D_EPXCR_MAX_BURST_SIZE_SHIFT 12 -#define MV_U3D_EPXCR_MAX_PACKET_SIZE_SHIFT 16 -#define MV_U3D_USB_BULK_BURST_OUT 6 -#define MV_U3D_USB_BULK_BURST_IN 14 - -#define MV_U3D_EPXCR_EP_FLUSH (1 << 7) -#define MV_U3D_EPXCR_EP_HALT (1 << 1) -#define MV_U3D_EPXCR_EP_INIT (1) - -/* TX/RX Status Register */ -#define MV_U3D_XFERSTATUS_COMPLETE_SHIFT 24 -#define MV_U3D_COMPLETE_INVALID 0 -#define MV_U3D_COMPLETE_SUCCESS 1 -#define MV_U3D_COMPLETE_BUFF_ERR 2 -#define MV_U3D_COMPLETE_SHORT_PACKET 3 -#define MV_U3D_COMPLETE_TRB_ERR 5 -#define MV_U3D_XFERSTATUS_TRB_LENGTH_MASK (0xFFFFFF) - -#define MV_U3D_USB_LINK_BYPASS_VBUS 0x8 - -#define MV_U3D_LTSSM_PHY_INIT_DONE 0x80000000 -#define MV_U3D_LTSSM_NEVER_GO_COMPLIANCE 0x40000000 - -#define MV_U3D_USB3_OP_REGS_OFFSET 0x100 -#define MV_U3D_USB3_PHY_OFFSET 0xB800 - -#define DCS_ENABLE 0x1 - -/* timeout */ -#define MV_U3D_RESET_TIMEOUT 10000 -#define MV_U3D_FLUSH_TIMEOUT 100000 -#define MV_U3D_OWN_TIMEOUT 10000 -#define LOOPS_USEC_SHIFT 4 -#define LOOPS_USEC (1 << LOOPS_USEC_SHIFT) -#define LOOPS(timeout) ((timeout) >> LOOPS_USEC_SHIFT) - -/* ep direction */ -#define MV_U3D_EP_DIR_IN 1 -#define MV_U3D_EP_DIR_OUT 0 -#define mv_u3d_ep_dir(ep) (((ep)->ep_num == 0) ? \ - ((ep)->u3d->ep0_dir) : ((ep)->direction)) - -/* usb capability registers */ -struct mv_u3d_cap_regs { - u32 rsvd[5]; - u32 dboff; /* doorbell register offset */ - u32 rtsoff; /* runtime register offset */ - u32 vuoff; /* vendor unique register offset */ -}; - -/* operation registers */ -struct mv_u3d_op_regs { - u32 usbcmd; /* Command register */ - u32 rsvd1[11]; - u32 dcbaapl; /* Device Context Base Address low register */ - u32 dcbaaph; /* Device Context Base Address high register */ - u32 rsvd2[243]; - u32 portsc; /* port status and control register*/ - u32 portlinkinfo; /* port link info register*/ - u32 rsvd3[9917]; - u32 doorbell; /* doorbell register */ -}; - -/* control endpoint enable registers */ -struct epxcr { - u32 epxoutcr0; /* ep out control 0 register */ - u32 epxoutcr1; /* ep out control 1 register */ - u32 epxincr0; /* ep in control 0 register */ - u32 epxincr1; /* ep in control 1 register */ -}; - -/* transfer status registers */ -struct xferstatus { - u32 curdeqlo; /* current TRB pointer low */ - u32 curdeqhi; /* current TRB pointer high */ - u32 statuslo; /* transfer status low */ - u32 statushi; /* transfer status high */ -}; - -/* vendor unique control registers */ -struct mv_u3d_vuc_regs { - u32 ctrlepenable; /* control endpoint enable register */ - u32 setuplock; /* setup lock register */ - u32 endcomplete; /* endpoint transfer complete register */ - u32 intrcause; /* interrupt cause register */ - u32 intrenable; /* interrupt enable register */ - u32 trbcomplete; /* TRB complete register */ - u32 linkchange; /* link change register */ - u32 rsvd1[5]; - u32 trbunderrun; /* TRB underrun register */ - u32 rsvd2[43]; - u32 bridgesetting; /* bridge setting register */ - u32 rsvd3[7]; - struct xferstatus txst[16]; /* TX status register */ - struct xferstatus rxst[16]; /* RX status register */ - u32 ltssm; /* LTSSM control register */ - u32 pipe; /* PIPE control register */ - u32 linkcr0; /* link control 0 register */ - u32 linkcr1; /* link control 1 register */ - u32 rsvd6[60]; - u32 mib0; /* MIB0 counter register */ - u32 usblink; /* usb link control register */ - u32 ltssmstate; /* LTSSM state register */ - u32 linkerrorcause; /* link error cause register */ - u32 rsvd7[60]; - u32 devaddrtiebrkr; /* device address and tie breaker */ - u32 itpinfo0; /* ITP info 0 register */ - u32 itpinfo1; /* ITP info 1 register */ - u32 rsvd8[61]; - struct epxcr epcr[16]; /* ep control register */ - u32 rsvd9[64]; - u32 phyaddr; /* PHY address register */ - u32 phydata; /* PHY data register */ -}; - -/* Endpoint context structure */ -struct mv_u3d_ep_context { - u32 rsvd0; - u32 rsvd1; - u32 trb_addr_lo; /* TRB address low 32 bit */ - u32 trb_addr_hi; /* TRB address high 32 bit */ - u32 rsvd2; - u32 rsvd3; - struct usb_ctrlrequest setup_buffer; /* setup data buffer */ -}; - -/* TRB control data structure */ -struct mv_u3d_trb_ctrl { - u32 own:1; /* owner of TRB */ - u32 rsvd1:3; - u32 chain:1; /* associate this TRB with the - next TRB on the Ring */ - u32 ioc:1; /* interrupt on complete */ - u32 rsvd2:4; - u32 type:6; /* TRB type */ -#define TYPE_NORMAL 1 -#define TYPE_DATA 3 -#define TYPE_LINK 6 - u32 dir:1; /* Working at data stage of control endpoint - operation. 0 is OUT and 1 is IN. */ - u32 rsvd3:15; -}; - -/* TRB data structure - * For multiple TRB, all the TRBs' physical address should be continuous. - */ -struct mv_u3d_trb_hw { - u32 buf_addr_lo; /* data buffer address low 32 bit */ - u32 buf_addr_hi; /* data buffer address high 32 bit */ - u32 trb_len; /* transfer length */ - struct mv_u3d_trb_ctrl ctrl; /* TRB control data */ -}; - -/* TRB structure */ -struct mv_u3d_trb { - struct mv_u3d_trb_hw *trb_hw; /* point to the trb_hw structure */ - dma_addr_t trb_dma; /* dma address for this trb_hw */ - struct list_head trb_list; /* trb list */ -}; - -/* device data structure */ -struct mv_u3d { - struct usb_gadget gadget; - struct usb_gadget_driver *driver; - spinlock_t lock; /* device lock */ - struct completion *done; - struct device *dev; - int irq; - - /* usb controller registers */ - struct mv_u3d_cap_regs __iomem *cap_regs; - struct mv_u3d_op_regs __iomem *op_regs; - struct mv_u3d_vuc_regs __iomem *vuc_regs; - void __iomem *phy_regs; - - unsigned int max_eps; - struct mv_u3d_ep_context *ep_context; - size_t ep_context_size; - dma_addr_t ep_context_dma; - - struct dma_pool *trb_pool; /* for TRB data structure */ - struct mv_u3d_ep *eps; - - struct mv_u3d_req *status_req; /* ep0 status request */ - struct usb_ctrlrequest local_setup_buff; /* store setup data*/ - - unsigned int resume_state; /* USB state to resume */ - unsigned int usb_state; /* USB current state */ - unsigned int ep0_state; /* Endpoint zero state */ - unsigned int ep0_dir; - - unsigned int dev_addr; /* device address */ - - unsigned int errors; - - unsigned softconnect:1; - unsigned vbus_active:1; /* vbus is active or not */ - unsigned remote_wakeup:1; /* support remote wakeup */ - unsigned clock_gating:1; /* clock gating or not */ - unsigned active:1; /* udc is active or not */ - unsigned vbus_valid_detect:1; /* udc vbus detection */ - - struct mv_usb_addon_irq *vbus; - unsigned int power; - - struct clk *clk; -}; - -/* endpoint data structure */ -struct mv_u3d_ep { - struct usb_ep ep; - struct mv_u3d *u3d; - struct list_head queue; /* ep request queued hardware */ - struct list_head req_list; /* list of ep request */ - struct mv_u3d_ep_context *ep_context; /* ep context */ - u32 direction; - char name[14]; - u32 processing; /* there is ep request - queued on haredware */ - spinlock_t req_lock; /* ep lock */ - unsigned wedge:1; - unsigned enabled:1; - unsigned ep_type:2; - unsigned ep_num:8; -}; - -/* request data structure */ -struct mv_u3d_req { - struct usb_request req; - struct mv_u3d_ep *ep; - struct list_head queue; /* ep requst queued on hardware */ - struct list_head list; /* ep request list */ - struct list_head trb_list; /* trb list of a request */ - - struct mv_u3d_trb *trb_head; /* point to first trb of a request */ - unsigned trb_count; /* TRB number in the chain */ - unsigned chain; /* TRB chain or not */ -}; - -#endif diff --git a/drivers/usb/gadget/udc/mv_u3d_core.c b/drivers/usb/gadget/udc/mv_u3d_core.c deleted file mode 100644 index 062f43e146aa..000000000000 --- a/drivers/usb/gadget/udc/mv_u3d_core.c +++ /dev/null @@ -1,2062 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (C) 2011 Marvell International Ltd. All rights reserved. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "mv_u3d.h" - -#define DRIVER_DESC "Marvell PXA USB3.0 Device Controller driver" - -static const char driver_name[] = "mv_u3d"; - -static void mv_u3d_nuke(struct mv_u3d_ep *ep, int status); -static void mv_u3d_stop_activity(struct mv_u3d *u3d, - struct usb_gadget_driver *driver); - -/* for endpoint 0 operations */ -static const struct usb_endpoint_descriptor mv_u3d_ep0_desc = { - .bLength = USB_DT_ENDPOINT_SIZE, - .bDescriptorType = USB_DT_ENDPOINT, - .bEndpointAddress = 0, - .bmAttributes = USB_ENDPOINT_XFER_CONTROL, - .wMaxPacketSize = MV_U3D_EP0_MAX_PKT_SIZE, -}; - -static void mv_u3d_ep0_reset(struct mv_u3d *u3d) -{ - struct mv_u3d_ep *ep; - u32 epxcr; - int i; - - for (i = 0; i < 2; i++) { - ep = &u3d->eps[i]; - ep->u3d = u3d; - - /* ep0 ep context, ep0 in and out share the same ep context */ - ep->ep_context = &u3d->ep_context[1]; - } - - /* reset ep state machine */ - /* reset ep0 out */ - epxcr = ioread32(&u3d->vuc_regs->epcr[0].epxoutcr0); - epxcr |= MV_U3D_EPXCR_EP_INIT; - iowrite32(epxcr, &u3d->vuc_regs->epcr[0].epxoutcr0); - udelay(5); - epxcr &= ~MV_U3D_EPXCR_EP_INIT; - iowrite32(epxcr, &u3d->vuc_regs->epcr[0].epxoutcr0); - - epxcr = ((MV_U3D_EP0_MAX_PKT_SIZE - << MV_U3D_EPXCR_MAX_PACKET_SIZE_SHIFT) - | (1 << MV_U3D_EPXCR_MAX_BURST_SIZE_SHIFT) - | (1 << MV_U3D_EPXCR_EP_ENABLE_SHIFT) - | MV_U3D_EPXCR_EP_TYPE_CONTROL); - iowrite32(epxcr, &u3d->vuc_regs->epcr[0].epxoutcr1); - - /* reset ep0 in */ - epxcr = ioread32(&u3d->vuc_regs->epcr[0].epxincr0); - epxcr |= MV_U3D_EPXCR_EP_INIT; - iowrite32(epxcr, &u3d->vuc_regs->epcr[0].epxincr0); - udelay(5); - epxcr &= ~MV_U3D_EPXCR_EP_INIT; - iowrite32(epxcr, &u3d->vuc_regs->epcr[0].epxincr0); - - epxcr = ((MV_U3D_EP0_MAX_PKT_SIZE - << MV_U3D_EPXCR_MAX_PACKET_SIZE_SHIFT) - | (1 << MV_U3D_EPXCR_MAX_BURST_SIZE_SHIFT) - | (1 << MV_U3D_EPXCR_EP_ENABLE_SHIFT) - | MV_U3D_EPXCR_EP_TYPE_CONTROL); - iowrite32(epxcr, &u3d->vuc_regs->epcr[0].epxincr1); -} - -static void mv_u3d_ep0_stall(struct mv_u3d *u3d) -{ - u32 tmp; - dev_dbg(u3d->dev, "%s\n", __func__); - - /* set TX and RX to stall */ - tmp = ioread32(&u3d->vuc_regs->epcr[0].epxoutcr0); - tmp |= MV_U3D_EPXCR_EP_HALT; - iowrite32(tmp, &u3d->vuc_regs->epcr[0].epxoutcr0); - - tmp = ioread32(&u3d->vuc_regs->epcr[0].epxincr0); - tmp |= MV_U3D_EPXCR_EP_HALT; - iowrite32(tmp, &u3d->vuc_regs->epcr[0].epxincr0); - - /* update ep0 state */ - u3d->ep0_state = MV_U3D_WAIT_FOR_SETUP; - u3d->ep0_dir = MV_U3D_EP_DIR_OUT; -} - -static int mv_u3d_process_ep_req(struct mv_u3d *u3d, int index, - struct mv_u3d_req *curr_req) -{ - struct mv_u3d_trb *curr_trb; - int actual, remaining_length = 0; - int direction, ep_num; - int retval = 0; - u32 tmp, status, length; - - direction = index % 2; - ep_num = index / 2; - - actual = curr_req->req.length; - - while (!list_empty(&curr_req->trb_list)) { - curr_trb = list_entry(curr_req->trb_list.next, - struct mv_u3d_trb, trb_list); - if (!curr_trb->trb_hw->ctrl.own) { - dev_err(u3d->dev, "%s, TRB own error!\n", - u3d->eps[index].name); - return 1; - } - - curr_trb->trb_hw->ctrl.own = 0; - if (direction == MV_U3D_EP_DIR_OUT) - tmp = ioread32(&u3d->vuc_regs->rxst[ep_num].statuslo); - else - tmp = ioread32(&u3d->vuc_regs->txst[ep_num].statuslo); - - status = tmp >> MV_U3D_XFERSTATUS_COMPLETE_SHIFT; - length = tmp & MV_U3D_XFERSTATUS_TRB_LENGTH_MASK; - - if (status == MV_U3D_COMPLETE_SUCCESS || - (status == MV_U3D_COMPLETE_SHORT_PACKET && - direction == MV_U3D_EP_DIR_OUT)) { - remaining_length += length; - actual -= remaining_length; - } else { - dev_err(u3d->dev, - "complete_tr error: ep=%d %s: error = 0x%x\n", - index >> 1, direction ? "SEND" : "RECV", - status); - retval = -EPROTO; - } - - list_del_init(&curr_trb->trb_list); - } - if (retval) - return retval; - - curr_req->req.actual = actual; - return 0; -} - -/* - * mv_u3d_done() - retire a request; caller blocked irqs - * @status : request status to be set, only works when - * request is still in progress. - */ -static -void mv_u3d_done(struct mv_u3d_ep *ep, struct mv_u3d_req *req, int status) - __releases(&ep->udc->lock) - __acquires(&ep->udc->lock) -{ - struct mv_u3d *u3d = (struct mv_u3d *)ep->u3d; - - dev_dbg(u3d->dev, "mv_u3d_done: remove req->queue\n"); - /* Removed the req from ep queue */ - list_del_init(&req->queue); - - /* req.status should be set as -EINPROGRESS in ep_queue() */ - if (req->req.status == -EINPROGRESS) - req->req.status = status; - else - status = req->req.status; - - /* Free trb for the request */ - if (!req->chain) - dma_pool_free(u3d->trb_pool, - req->trb_head->trb_hw, req->trb_head->trb_dma); - else { - dma_unmap_single(ep->u3d->gadget.dev.parent, - (dma_addr_t)req->trb_head->trb_dma, - req->trb_count * sizeof(struct mv_u3d_trb_hw), - DMA_BIDIRECTIONAL); - kfree(req->trb_head->trb_hw); - } - kfree(req->trb_head); - - usb_gadget_unmap_request(&u3d->gadget, &req->req, mv_u3d_ep_dir(ep)); - - if (status && (status != -ESHUTDOWN)) { - dev_dbg(u3d->dev, "complete %s req %p stat %d len %u/%u", - ep->ep.name, &req->req, status, - req->req.actual, req->req.length); - } - - spin_unlock(&ep->u3d->lock); - - usb_gadget_giveback_request(&ep->ep, &req->req); - - spin_lock(&ep->u3d->lock); -} - -static int mv_u3d_queue_trb(struct mv_u3d_ep *ep, struct mv_u3d_req *req) -{ - u32 tmp, direction; - struct mv_u3d *u3d; - struct mv_u3d_ep_context *ep_context; - int retval = 0; - - u3d = ep->u3d; - direction = mv_u3d_ep_dir(ep); - - /* ep0 in and out share the same ep context slot 1*/ - if (ep->ep_num == 0) - ep_context = &(u3d->ep_context[1]); - else - ep_context = &(u3d->ep_context[ep->ep_num * 2 + direction]); - - /* check if the pipe is empty or not */ - if (!list_empty(&ep->queue)) { - dev_err(u3d->dev, "add trb to non-empty queue!\n"); - retval = -ENOMEM; - WARN_ON(1); - } else { - ep_context->rsvd0 = cpu_to_le32(1); - ep_context->rsvd1 = 0; - - /* Configure the trb address and set the DCS bit. - * Both DCS bit and own bit in trb should be set. - */ - ep_context->trb_addr_lo = - cpu_to_le32(req->trb_head->trb_dma | DCS_ENABLE); - ep_context->trb_addr_hi = 0; - - /* Ensure that updates to the EP Context will - * occure before Ring Bell. - */ - wmb(); - - /* ring bell the ep */ - if (ep->ep_num == 0) - tmp = 0x1; - else - tmp = ep->ep_num * 2 - + ((direction == MV_U3D_EP_DIR_OUT) ? 0 : 1); - - iowrite32(tmp, &u3d->op_regs->doorbell); - } - return retval; -} - -static struct mv_u3d_trb *mv_u3d_build_trb_one(struct mv_u3d_req *req, - unsigned *length, dma_addr_t *dma) -{ - u32 temp; - unsigned int direction; - struct mv_u3d_trb *trb; - struct mv_u3d_trb_hw *trb_hw; - struct mv_u3d *u3d; - - /* how big will this transfer be? */ - *length = req->req.length - req->req.actual; - BUG_ON(*length > (unsigned)MV_U3D_EP_MAX_LENGTH_TRANSFER); - - u3d = req->ep->u3d; - - trb = kzalloc(sizeof(*trb), GFP_ATOMIC); - if (!trb) - return NULL; - - /* - * Be careful that no _GFP_HIGHMEM is set, - * or we can not use dma_to_virt - * cannot use GFP_KERNEL in spin lock - */ - trb_hw = dma_pool_alloc(u3d->trb_pool, GFP_ATOMIC, dma); - if (!trb_hw) { - kfree(trb); - dev_err(u3d->dev, - "%s, dma_pool_alloc fail\n", __func__); - return NULL; - } - trb->trb_dma = *dma; - trb->trb_hw = trb_hw; - - /* initialize buffer page pointers */ - temp = (u32)(req->req.dma + req->req.actual); - - trb_hw->buf_addr_lo = cpu_to_le32(temp); - trb_hw->buf_addr_hi = 0; - trb_hw->trb_len = cpu_to_le32(*length); - trb_hw->ctrl.own = 1; - - if (req->ep->ep_num == 0) - trb_hw->ctrl.type = TYPE_DATA; - else - trb_hw->ctrl.type = TYPE_NORMAL; - - req->req.actual += *length; - - direction = mv_u3d_ep_dir(req->ep); - if (direction == MV_U3D_EP_DIR_IN) - trb_hw->ctrl.dir = 1; - else - trb_hw->ctrl.dir = 0; - - /* Enable interrupt for the last trb of a request */ - if (!req->req.no_interrupt) - trb_hw->ctrl.ioc = 1; - - trb_hw->ctrl.chain = 0; - - wmb(); - return trb; -} - -static int mv_u3d_build_trb_chain(struct mv_u3d_req *req, unsigned *length, - struct mv_u3d_trb *trb, int *is_last) -{ - u32 temp; - unsigned int direction; - struct mv_u3d *u3d; - - /* how big will this transfer be? */ - *length = min(req->req.length - req->req.actual, - (unsigned)MV_U3D_EP_MAX_LENGTH_TRANSFER); - - u3d = req->ep->u3d; - - trb->trb_dma = 0; - - /* initialize buffer page pointers */ - temp = (u32)(req->req.dma + req->req.actual); - - trb->trb_hw->buf_addr_lo = cpu_to_le32(temp); - trb->trb_hw->buf_addr_hi = 0; - trb->trb_hw->trb_len = cpu_to_le32(*length); - trb->trb_hw->ctrl.own = 1; - - if (req->ep->ep_num == 0) - trb->trb_hw->ctrl.type = TYPE_DATA; - else - trb->trb_hw->ctrl.type = TYPE_NORMAL; - - req->req.actual += *length; - - direction = mv_u3d_ep_dir(req->ep); - if (direction == MV_U3D_EP_DIR_IN) - trb->trb_hw->ctrl.dir = 1; - else - trb->trb_hw->ctrl.dir = 0; - - /* zlp is needed if req->req.zero is set */ - if (req->req.zero) { - if (*length == 0 || (*length % req->ep->ep.maxpacket) != 0) - *is_last = 1; - else - *is_last = 0; - } else if (req->req.length == req->req.actual) - *is_last = 1; - else - *is_last = 0; - - /* Enable interrupt for the last trb of a request */ - if (*is_last && !req->req.no_interrupt) - trb->trb_hw->ctrl.ioc = 1; - - if (*is_last) - trb->trb_hw->ctrl.chain = 0; - else { - trb->trb_hw->ctrl.chain = 1; - dev_dbg(u3d->dev, "chain trb\n"); - } - - wmb(); - - return 0; -} - -/* generate TRB linked list for a request - * usb controller only supports continous trb chain, - * that trb structure physical address should be continous. - */ -static int mv_u3d_req_to_trb(struct mv_u3d_req *req) -{ - unsigned count; - int is_last; - struct mv_u3d_trb *trb; - struct mv_u3d_trb_hw *trb_hw; - struct mv_u3d *u3d; - dma_addr_t dma; - unsigned length; - unsigned trb_num; - - u3d = req->ep->u3d; - - INIT_LIST_HEAD(&req->trb_list); - - length = req->req.length - req->req.actual; - /* normally the request transfer length is less than 16KB. - * we use buil_trb_one() to optimize it. - */ - if (length <= (unsigned)MV_U3D_EP_MAX_LENGTH_TRANSFER) { - trb = mv_u3d_build_trb_one(req, &count, &dma); - list_add_tail(&trb->trb_list, &req->trb_list); - req->trb_head = trb; - req->trb_count = 1; - req->chain = 0; - } else { - trb_num = length / MV_U3D_EP_MAX_LENGTH_TRANSFER; - if (length % MV_U3D_EP_MAX_LENGTH_TRANSFER) - trb_num++; - - trb = kcalloc(trb_num, sizeof(*trb), GFP_ATOMIC); - if (!trb) - return -ENOMEM; - - trb_hw = kcalloc(trb_num, sizeof(*trb_hw), GFP_ATOMIC); - if (!trb_hw) { - kfree(trb); - return -ENOMEM; - } - - do { - trb->trb_hw = trb_hw; - if (mv_u3d_build_trb_chain(req, &count, - trb, &is_last)) { - dev_err(u3d->dev, - "%s, mv_u3d_build_trb_chain fail\n", - __func__); - return -EIO; - } - - list_add_tail(&trb->trb_list, &req->trb_list); - req->trb_count++; - trb++; - trb_hw++; - } while (!is_last); - - req->trb_head = list_entry(req->trb_list.next, - struct mv_u3d_trb, trb_list); - req->trb_head->trb_dma = dma_map_single(u3d->gadget.dev.parent, - req->trb_head->trb_hw, - trb_num * sizeof(*trb_hw), - DMA_BIDIRECTIONAL); - if (dma_mapping_error(u3d->gadget.dev.parent, - req->trb_head->trb_dma)) { - kfree(req->trb_head->trb_hw); - kfree(req->trb_head); - return -EFAULT; - } - - req->chain = 1; - } - - return 0; -} - -static int -mv_u3d_start_queue(struct mv_u3d_ep *ep) -{ - struct mv_u3d *u3d = ep->u3d; - struct mv_u3d_req *req; - int ret; - - if (!list_empty(&ep->req_list) && !ep->processing) - req = list_entry(ep->req_list.next, struct mv_u3d_req, list); - else - return 0; - - ep->processing = 1; - - /* set up dma mapping */ - ret = usb_gadget_map_request(&u3d->gadget, &req->req, - mv_u3d_ep_dir(ep)); - if (ret) - goto break_processing; - - req->req.status = -EINPROGRESS; - req->req.actual = 0; - req->trb_count = 0; - - /* build trbs */ - ret = mv_u3d_req_to_trb(req); - if (ret) { - dev_err(u3d->dev, "%s, mv_u3d_req_to_trb fail\n", __func__); - goto break_processing; - } - - /* and push them to device queue */ - ret = mv_u3d_queue_trb(ep, req); - if (ret) - goto break_processing; - - /* irq handler advances the queue */ - list_add_tail(&req->queue, &ep->queue); - - return 0; - -break_processing: - ep->processing = 0; - return ret; -} - -static int mv_u3d_ep_enable(struct usb_ep *_ep, - const struct usb_endpoint_descriptor *desc) -{ - struct mv_u3d *u3d; - struct mv_u3d_ep *ep; - u16 max = 0; - unsigned maxburst = 0; - u32 epxcr, direction; - - if (!_ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT) - return -EINVAL; - - ep = container_of(_ep, struct mv_u3d_ep, ep); - u3d = ep->u3d; - - if (!u3d->driver || u3d->gadget.speed == USB_SPEED_UNKNOWN) - return -ESHUTDOWN; - - direction = mv_u3d_ep_dir(ep); - max = le16_to_cpu(desc->wMaxPacketSize); - - if (!_ep->maxburst) - _ep->maxburst = 1; - maxburst = _ep->maxburst; - - /* Set the max burst size */ - switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { - case USB_ENDPOINT_XFER_BULK: - if (maxburst > 16) { - dev_dbg(u3d->dev, - "max burst should not be greater " - "than 16 on bulk ep\n"); - maxburst = 1; - _ep->maxburst = maxburst; - } - dev_dbg(u3d->dev, - "maxburst: %d on bulk %s\n", maxburst, ep->name); - break; - case USB_ENDPOINT_XFER_CONTROL: - /* control transfer only supports maxburst as one */ - maxburst = 1; - _ep->maxburst = maxburst; - break; - case USB_ENDPOINT_XFER_INT: - if (maxburst != 1) { - dev_dbg(u3d->dev, - "max burst should be 1 on int ep " - "if transfer size is not 1024\n"); - maxburst = 1; - _ep->maxburst = maxburst; - } - break; - case USB_ENDPOINT_XFER_ISOC: - if (maxburst != 1) { - dev_dbg(u3d->dev, - "max burst should be 1 on isoc ep " - "if transfer size is not 1024\n"); - maxburst = 1; - _ep->maxburst = maxburst; - } - break; - default: - goto en_done; - } - - ep->ep.maxpacket = max; - ep->ep.desc = desc; - ep->enabled = 1; - - /* Enable the endpoint for Rx or Tx and set the endpoint type */ - if (direction == MV_U3D_EP_DIR_OUT) { - epxcr = ioread32(&u3d->vuc_regs->epcr[ep->ep_num].epxoutcr0); - epxcr |= MV_U3D_EPXCR_EP_INIT; - iowrite32(epxcr, &u3d->vuc_regs->epcr[ep->ep_num].epxoutcr0); - udelay(5); - epxcr &= ~MV_U3D_EPXCR_EP_INIT; - iowrite32(epxcr, &u3d->vuc_regs->epcr[ep->ep_num].epxoutcr0); - - epxcr = ((max << MV_U3D_EPXCR_MAX_PACKET_SIZE_SHIFT) - | ((maxburst - 1) << MV_U3D_EPXCR_MAX_BURST_SIZE_SHIFT) - | (1 << MV_U3D_EPXCR_EP_ENABLE_SHIFT) - | (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)); - iowrite32(epxcr, &u3d->vuc_regs->epcr[ep->ep_num].epxoutcr1); - } else { - epxcr = ioread32(&u3d->vuc_regs->epcr[ep->ep_num].epxincr0); - epxcr |= MV_U3D_EPXCR_EP_INIT; - iowrite32(epxcr, &u3d->vuc_regs->epcr[ep->ep_num].epxincr0); - udelay(5); - epxcr &= ~MV_U3D_EPXCR_EP_INIT; - iowrite32(epxcr, &u3d->vuc_regs->epcr[ep->ep_num].epxincr0); - - epxcr = ((max << MV_U3D_EPXCR_MAX_PACKET_SIZE_SHIFT) - | ((maxburst - 1) << MV_U3D_EPXCR_MAX_BURST_SIZE_SHIFT) - | (1 << MV_U3D_EPXCR_EP_ENABLE_SHIFT) - | (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)); - iowrite32(epxcr, &u3d->vuc_regs->epcr[ep->ep_num].epxincr1); - } - - return 0; -en_done: - return -EINVAL; -} - -static int mv_u3d_ep_disable(struct usb_ep *_ep) -{ - struct mv_u3d *u3d; - struct mv_u3d_ep *ep; - u32 epxcr, direction; - unsigned long flags; - - if (!_ep) - return -EINVAL; - - ep = container_of(_ep, struct mv_u3d_ep, ep); - if (!ep->ep.desc) - return -EINVAL; - - u3d = ep->u3d; - - direction = mv_u3d_ep_dir(ep); - - /* nuke all pending requests (does flush) */ - spin_lock_irqsave(&u3d->lock, flags); - mv_u3d_nuke(ep, -ESHUTDOWN); - spin_unlock_irqrestore(&u3d->lock, flags); - - /* Disable the endpoint for Rx or Tx and reset the endpoint type */ - if (direction == MV_U3D_EP_DIR_OUT) { - epxcr = ioread32(&u3d->vuc_regs->epcr[ep->ep_num].epxoutcr1); - epxcr &= ~((1 << MV_U3D_EPXCR_EP_ENABLE_SHIFT) - | USB_ENDPOINT_XFERTYPE_MASK); - iowrite32(epxcr, &u3d->vuc_regs->epcr[ep->ep_num].epxoutcr1); - } else { - epxcr = ioread32(&u3d->vuc_regs->epcr[ep->ep_num].epxincr1); - epxcr &= ~((1 << MV_U3D_EPXCR_EP_ENABLE_SHIFT) - | USB_ENDPOINT_XFERTYPE_MASK); - iowrite32(epxcr, &u3d->vuc_regs->epcr[ep->ep_num].epxincr1); - } - - ep->enabled = 0; - - ep->ep.desc = NULL; - return 0; -} - -static struct usb_request * -mv_u3d_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) -{ - struct mv_u3d_req *req; - - req = kzalloc(sizeof *req, gfp_flags); - if (!req) - return NULL; - - INIT_LIST_HEAD(&req->queue); - - return &req->req; -} - -static void mv_u3d_free_request(struct usb_ep *_ep, struct usb_request *_req) -{ - struct mv_u3d_req *req = container_of(_req, struct mv_u3d_req, req); - - kfree(req); -} - -static void mv_u3d_ep_fifo_flush(struct usb_ep *_ep) -{ - struct mv_u3d *u3d; - u32 direction; - struct mv_u3d_ep *ep = container_of(_ep, struct mv_u3d_ep, ep); - unsigned int loops; - u32 tmp; - - /* if endpoint is not enabled, cannot flush endpoint */ - if (!ep->enabled) - return; - - u3d = ep->u3d; - direction = mv_u3d_ep_dir(ep); - - /* ep0 need clear bit after flushing fifo. */ - if (!ep->ep_num) { - if (direction == MV_U3D_EP_DIR_OUT) { - tmp = ioread32(&u3d->vuc_regs->epcr[0].epxoutcr0); - tmp |= MV_U3D_EPXCR_EP_FLUSH; - iowrite32(tmp, &u3d->vuc_regs->epcr[0].epxoutcr0); - udelay(10); - tmp &= ~MV_U3D_EPXCR_EP_FLUSH; - iowrite32(tmp, &u3d->vuc_regs->epcr[0].epxoutcr0); - } else { - tmp = ioread32(&u3d->vuc_regs->epcr[0].epxincr0); - tmp |= MV_U3D_EPXCR_EP_FLUSH; - iowrite32(tmp, &u3d->vuc_regs->epcr[0].epxincr0); - udelay(10); - tmp &= ~MV_U3D_EPXCR_EP_FLUSH; - iowrite32(tmp, &u3d->vuc_regs->epcr[0].epxincr0); - } - return; - } - - if (direction == MV_U3D_EP_DIR_OUT) { - tmp = ioread32(&u3d->vuc_regs->epcr[ep->ep_num].epxoutcr0); - tmp |= MV_U3D_EPXCR_EP_FLUSH; - iowrite32(tmp, &u3d->vuc_regs->epcr[ep->ep_num].epxoutcr0); - - /* Wait until flushing completed */ - loops = LOOPS(MV_U3D_FLUSH_TIMEOUT); - while (ioread32(&u3d->vuc_regs->epcr[ep->ep_num].epxoutcr0) & - MV_U3D_EPXCR_EP_FLUSH) { - /* - * EP_FLUSH bit should be cleared to indicate this - * operation is complete - */ - if (loops == 0) { - dev_dbg(u3d->dev, - "EP FLUSH TIMEOUT for ep%d%s\n", ep->ep_num, - direction ? "in" : "out"); - return; - } - loops--; - udelay(LOOPS_USEC); - } - } else { /* EP_DIR_IN */ - tmp = ioread32(&u3d->vuc_regs->epcr[ep->ep_num].epxincr0); - tmp |= MV_U3D_EPXCR_EP_FLUSH; - iowrite32(tmp, &u3d->vuc_regs->epcr[ep->ep_num].epxincr0); - - /* Wait until flushing completed */ - loops = LOOPS(MV_U3D_FLUSH_TIMEOUT); - while (ioread32(&u3d->vuc_regs->epcr[ep->ep_num].epxincr0) & - MV_U3D_EPXCR_EP_FLUSH) { - /* - * EP_FLUSH bit should be cleared to indicate this - * operation is complete - */ - if (loops == 0) { - dev_dbg(u3d->dev, - "EP FLUSH TIMEOUT for ep%d%s\n", ep->ep_num, - direction ? "in" : "out"); - return; - } - loops--; - udelay(LOOPS_USEC); - } - } -} - -/* queues (submits) an I/O request to an endpoint */ -static int -mv_u3d_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) -{ - struct mv_u3d_ep *ep; - struct mv_u3d_req *req; - struct mv_u3d *u3d; - unsigned long flags; - int is_first_req = 0; - - if (unlikely(!_ep || !_req)) - return -EINVAL; - - ep = container_of(_ep, struct mv_u3d_ep, ep); - u3d = ep->u3d; - - req = container_of(_req, struct mv_u3d_req, req); - - if (!ep->ep_num - && u3d->ep0_state == MV_U3D_STATUS_STAGE - && !_req->length) { - dev_dbg(u3d->dev, "ep0 status stage\n"); - u3d->ep0_state = MV_U3D_WAIT_FOR_SETUP; - return 0; - } - - dev_dbg(u3d->dev, "%s: %s, req: 0x%p\n", - __func__, _ep->name, req); - - /* catch various bogus parameters */ - if (!req->req.complete || !req->req.buf - || !list_empty(&req->queue)) { - dev_err(u3d->dev, - "%s, bad params, _req: 0x%p," - "req->req.complete: 0x%p, req->req.buf: 0x%p," - "list_empty: 0x%x\n", - __func__, _req, - req->req.complete, req->req.buf, - list_empty(&req->queue)); - return -EINVAL; - } - if (unlikely(!ep->ep.desc)) { - dev_err(u3d->dev, "%s, bad ep\n", __func__); - return -EINVAL; - } - if (ep->ep.desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { - if (req->req.length > ep->ep.maxpacket) - return -EMSGSIZE; - } - - if (!u3d->driver || u3d->gadget.speed == USB_SPEED_UNKNOWN) { - dev_err(u3d->dev, - "bad params of driver/speed\n"); - return -ESHUTDOWN; - } - - req->ep = ep; - - /* Software list handles usb request. */ - spin_lock_irqsave(&ep->req_lock, flags); - is_first_req = list_empty(&ep->req_list); - list_add_tail(&req->list, &ep->req_list); - spin_unlock_irqrestore(&ep->req_lock, flags); - if (!is_first_req) { - dev_dbg(u3d->dev, "list is not empty\n"); - return 0; - } - - dev_dbg(u3d->dev, "call mv_u3d_start_queue from usb_ep_queue\n"); - spin_lock_irqsave(&u3d->lock, flags); - mv_u3d_start_queue(ep); - spin_unlock_irqrestore(&u3d->lock, flags); - return 0; -} - -/* dequeues (cancels, unlinks) an I/O request from an endpoint */ -static int mv_u3d_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) -{ - struct mv_u3d_ep *ep; - struct mv_u3d_req *req = NULL, *iter; - struct mv_u3d *u3d; - struct mv_u3d_ep_context *ep_context; - struct mv_u3d_req *next_req; - - unsigned long flags; - int ret = 0; - - if (!_ep || !_req) - return -EINVAL; - - ep = container_of(_ep, struct mv_u3d_ep, ep); - u3d = ep->u3d; - - spin_lock_irqsave(&ep->u3d->lock, flags); - - /* make sure it's actually queued on this endpoint */ - list_for_each_entry(iter, &ep->queue, queue) { - if (&iter->req != _req) - continue; - req = iter; - break; - } - if (!req) { - ret = -EINVAL; - goto out; - } - - /* The request is in progress, or completed but not dequeued */ - if (ep->queue.next == &req->queue) { - _req->status = -ECONNRESET; - mv_u3d_ep_fifo_flush(_ep); - - /* The request isn't the last request in this ep queue */ - if (req->queue.next != &ep->queue) { - dev_dbg(u3d->dev, - "it is the last request in this ep queue\n"); - ep_context = ep->ep_context; - next_req = list_entry(req->queue.next, - struct mv_u3d_req, queue); - - /* Point first TRB of next request to the EP context. */ - iowrite32((unsigned long) next_req->trb_head, - &ep_context->trb_addr_lo); - } else { - struct mv_u3d_ep_context *ep_context; - ep_context = ep->ep_context; - ep_context->trb_addr_lo = 0; - ep_context->trb_addr_hi = 0; - } - - } else - WARN_ON(1); - - mv_u3d_done(ep, req, -ECONNRESET); - - /* remove the req from the ep req list */ - if (!list_empty(&ep->req_list)) { - struct mv_u3d_req *curr_req; - curr_req = list_entry(ep->req_list.next, - struct mv_u3d_req, list); - if (curr_req == req) { - list_del_init(&req->list); - ep->processing = 0; - } - } - -out: - spin_unlock_irqrestore(&ep->u3d->lock, flags); - return ret; -} - -static void -mv_u3d_ep_set_stall(struct mv_u3d *u3d, u8 ep_num, u8 direction, int stall) -{ - u32 tmp; - struct mv_u3d_ep *ep = u3d->eps; - - dev_dbg(u3d->dev, "%s\n", __func__); - if (direction == MV_U3D_EP_DIR_OUT) { - tmp = ioread32(&u3d->vuc_regs->epcr[ep->ep_num].epxoutcr0); - if (stall) - tmp |= MV_U3D_EPXCR_EP_HALT; - else - tmp &= ~MV_U3D_EPXCR_EP_HALT; - iowrite32(tmp, &u3d->vuc_regs->epcr[ep->ep_num].epxoutcr0); - } else { - tmp = ioread32(&u3d->vuc_regs->epcr[ep->ep_num].epxincr0); - if (stall) - tmp |= MV_U3D_EPXCR_EP_HALT; - else - tmp &= ~MV_U3D_EPXCR_EP_HALT; - iowrite32(tmp, &u3d->vuc_regs->epcr[ep->ep_num].epxincr0); - } -} - -static int mv_u3d_ep_set_halt_wedge(struct usb_ep *_ep, int halt, int wedge) -{ - struct mv_u3d_ep *ep; - unsigned long flags; - int status = 0; - struct mv_u3d *u3d; - - ep = container_of(_ep, struct mv_u3d_ep, ep); - u3d = ep->u3d; - if (!ep->ep.desc) { - status = -EINVAL; - goto out; - } - - if (ep->ep.desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { - status = -EOPNOTSUPP; - goto out; - } - - /* - * Attempt to halt IN ep will fail if any transfer requests - * are still queue - */ - if (halt && (mv_u3d_ep_dir(ep) == MV_U3D_EP_DIR_IN) - && !list_empty(&ep->queue)) { - status = -EAGAIN; - goto out; - } - - spin_lock_irqsave(&ep->u3d->lock, flags); - mv_u3d_ep_set_stall(u3d, ep->ep_num, mv_u3d_ep_dir(ep), halt); - if (halt && wedge) - ep->wedge = 1; - else if (!halt) - ep->wedge = 0; - spin_unlock_irqrestore(&ep->u3d->lock, flags); - - if (ep->ep_num == 0) - u3d->ep0_dir = MV_U3D_EP_DIR_OUT; -out: - return status; -} - -static int mv_u3d_ep_set_halt(struct usb_ep *_ep, int halt) -{ - return mv_u3d_ep_set_halt_wedge(_ep, halt, 0); -} - -static int mv_u3d_ep_set_wedge(struct usb_ep *_ep) -{ - return mv_u3d_ep_set_halt_wedge(_ep, 1, 1); -} - -static const struct usb_ep_ops mv_u3d_ep_ops = { - .enable = mv_u3d_ep_enable, - .disable = mv_u3d_ep_disable, - - .alloc_request = mv_u3d_alloc_request, - .free_request = mv_u3d_free_request, - - .queue = mv_u3d_ep_queue, - .dequeue = mv_u3d_ep_dequeue, - - .set_wedge = mv_u3d_ep_set_wedge, - .set_halt = mv_u3d_ep_set_halt, - .fifo_flush = mv_u3d_ep_fifo_flush, -}; - -static void mv_u3d_controller_stop(struct mv_u3d *u3d) -{ - u32 tmp; - - if (!u3d->clock_gating && u3d->vbus_valid_detect) - iowrite32(MV_U3D_INTR_ENABLE_VBUS_VALID, - &u3d->vuc_regs->intrenable); - else - iowrite32(0, &u3d->vuc_regs->intrenable); - iowrite32(~0x0, &u3d->vuc_regs->endcomplete); - iowrite32(~0x0, &u3d->vuc_regs->trbunderrun); - iowrite32(~0x0, &u3d->vuc_regs->trbcomplete); - iowrite32(~0x0, &u3d->vuc_regs->linkchange); - iowrite32(0x1, &u3d->vuc_regs->setuplock); - - /* Reset the RUN bit in the command register to stop USB */ - tmp = ioread32(&u3d->op_regs->usbcmd); - tmp &= ~MV_U3D_CMD_RUN_STOP; - iowrite32(tmp, &u3d->op_regs->usbcmd); - dev_dbg(u3d->dev, "after u3d_stop, USBCMD 0x%x\n", - ioread32(&u3d->op_regs->usbcmd)); -} - -static void mv_u3d_controller_start(struct mv_u3d *u3d) -{ - u32 usbintr; - u32 temp; - - /* enable link LTSSM state machine */ - temp = ioread32(&u3d->vuc_regs->ltssm); - temp |= MV_U3D_LTSSM_PHY_INIT_DONE; - iowrite32(temp, &u3d->vuc_regs->ltssm); - - /* Enable interrupts */ - usbintr = MV_U3D_INTR_ENABLE_LINK_CHG | MV_U3D_INTR_ENABLE_TXDESC_ERR | - MV_U3D_INTR_ENABLE_RXDESC_ERR | MV_U3D_INTR_ENABLE_TX_COMPLETE | - MV_U3D_INTR_ENABLE_RX_COMPLETE | MV_U3D_INTR_ENABLE_SETUP | - (u3d->vbus_valid_detect ? MV_U3D_INTR_ENABLE_VBUS_VALID : 0); - iowrite32(usbintr, &u3d->vuc_regs->intrenable); - - /* Enable ctrl ep */ - iowrite32(0x1, &u3d->vuc_regs->ctrlepenable); - - /* Set the Run bit in the command register */ - iowrite32(MV_U3D_CMD_RUN_STOP, &u3d->op_regs->usbcmd); - dev_dbg(u3d->dev, "after u3d_start, USBCMD 0x%x\n", - ioread32(&u3d->op_regs->usbcmd)); -} - -static int mv_u3d_controller_reset(struct mv_u3d *u3d) -{ - unsigned int loops; - u32 tmp; - - /* Stop the controller */ - tmp = ioread32(&u3d->op_regs->usbcmd); - tmp &= ~MV_U3D_CMD_RUN_STOP; - iowrite32(tmp, &u3d->op_regs->usbcmd); - - /* Reset the controller to get default values */ - iowrite32(MV_U3D_CMD_CTRL_RESET, &u3d->op_regs->usbcmd); - - /* wait for reset to complete */ - loops = LOOPS(MV_U3D_RESET_TIMEOUT); - while (ioread32(&u3d->op_regs->usbcmd) & MV_U3D_CMD_CTRL_RESET) { - if (loops == 0) { - dev_err(u3d->dev, - "Wait for RESET completed TIMEOUT\n"); - return -ETIMEDOUT; - } - loops--; - udelay(LOOPS_USEC); - } - - /* Configure the Endpoint Context Address */ - iowrite32(u3d->ep_context_dma, &u3d->op_regs->dcbaapl); - iowrite32(0, &u3d->op_regs->dcbaaph); - - return 0; -} - -static int mv_u3d_enable(struct mv_u3d *u3d) -{ - struct mv_usb_platform_data *pdata = dev_get_platdata(u3d->dev); - int retval; - - if (u3d->active) - return 0; - - if (!u3d->clock_gating) { - u3d->active = 1; - return 0; - } - - dev_dbg(u3d->dev, "enable u3d\n"); - clk_enable(u3d->clk); - if (pdata->phy_init) { - retval = pdata->phy_init(u3d->phy_regs); - if (retval) { - dev_err(u3d->dev, - "init phy error %d\n", retval); - clk_disable(u3d->clk); - return retval; - } - } - u3d->active = 1; - - return 0; -} - -static void mv_u3d_disable(struct mv_u3d *u3d) -{ - struct mv_usb_platform_data *pdata = dev_get_platdata(u3d->dev); - if (u3d->clock_gating && u3d->active) { - dev_dbg(u3d->dev, "disable u3d\n"); - if (pdata->phy_deinit) - pdata->phy_deinit(u3d->phy_regs); - clk_disable(u3d->clk); - u3d->active = 0; - } -} - -static int mv_u3d_vbus_session(struct usb_gadget *gadget, int is_active) -{ - struct mv_u3d *u3d; - unsigned long flags; - int retval = 0; - - u3d = container_of(gadget, struct mv_u3d, gadget); - - spin_lock_irqsave(&u3d->lock, flags); - - u3d->vbus_active = (is_active != 0); - dev_dbg(u3d->dev, "%s: softconnect %d, vbus_active %d\n", - __func__, u3d->softconnect, u3d->vbus_active); - /* - * 1. external VBUS detect: we can disable/enable clock on demand. - * 2. UDC VBUS detect: we have to enable clock all the time. - * 3. No VBUS detect: we have to enable clock all the time. - */ - if (u3d->driver && u3d->softconnect && u3d->vbus_active) { - retval = mv_u3d_enable(u3d); - if (retval == 0) { - /* - * after clock is disabled, we lost all the register - * context. We have to re-init registers - */ - mv_u3d_controller_reset(u3d); - mv_u3d_ep0_reset(u3d); - mv_u3d_controller_start(u3d); - } - } else if (u3d->driver && u3d->softconnect) { - if (!u3d->active) - goto out; - - /* stop all the transfer in queue*/ - mv_u3d_stop_activity(u3d, u3d->driver); - mv_u3d_controller_stop(u3d); - mv_u3d_disable(u3d); - } - -out: - spin_unlock_irqrestore(&u3d->lock, flags); - return retval; -} - -/* constrain controller's VBUS power usage - * This call is used by gadget drivers during SET_CONFIGURATION calls, - * reporting how much power the device may consume. For example, this - * could affect how quickly batteries are recharged. - * - * Returns zero on success, else negative errno. - */ -static int mv_u3d_vbus_draw(struct usb_gadget *gadget, unsigned mA) -{ - struct mv_u3d *u3d = container_of(gadget, struct mv_u3d, gadget); - - u3d->power = mA; - - return 0; -} - -static int mv_u3d_pullup(struct usb_gadget *gadget, int is_on) -{ - struct mv_u3d *u3d = container_of(gadget, struct mv_u3d, gadget); - unsigned long flags; - int retval = 0; - - spin_lock_irqsave(&u3d->lock, flags); - - dev_dbg(u3d->dev, "%s: softconnect %d, vbus_active %d\n", - __func__, u3d->softconnect, u3d->vbus_active); - u3d->softconnect = (is_on != 0); - if (u3d->driver && u3d->softconnect && u3d->vbus_active) { - retval = mv_u3d_enable(u3d); - if (retval == 0) { - /* - * after clock is disabled, we lost all the register - * context. We have to re-init registers - */ - mv_u3d_controller_reset(u3d); - mv_u3d_ep0_reset(u3d); - mv_u3d_controller_start(u3d); - } - } else if (u3d->driver && u3d->vbus_active) { - /* stop all the transfer in queue*/ - mv_u3d_stop_activity(u3d, u3d->driver); - mv_u3d_controller_stop(u3d); - mv_u3d_disable(u3d); - } - - spin_unlock_irqrestore(&u3d->lock, flags); - - return retval; -} - -static int mv_u3d_start(struct usb_gadget *g, - struct usb_gadget_driver *driver) -{ - struct mv_u3d *u3d = container_of(g, struct mv_u3d, gadget); - struct mv_usb_platform_data *pdata = dev_get_platdata(u3d->dev); - unsigned long flags; - - if (u3d->driver) - return -EBUSY; - - spin_lock_irqsave(&u3d->lock, flags); - - if (!u3d->clock_gating) { - clk_enable(u3d->clk); - if (pdata->phy_init) - pdata->phy_init(u3d->phy_regs); - } - - /* hook up the driver ... */ - u3d->driver = driver; - - u3d->ep0_dir = USB_DIR_OUT; - - spin_unlock_irqrestore(&u3d->lock, flags); - - u3d->vbus_valid_detect = 1; - - return 0; -} - -static int mv_u3d_stop(struct usb_gadget *g) -{ - struct mv_u3d *u3d = container_of(g, struct mv_u3d, gadget); - struct mv_usb_platform_data *pdata = dev_get_platdata(u3d->dev); - unsigned long flags; - - u3d->vbus_valid_detect = 0; - spin_lock_irqsave(&u3d->lock, flags); - - /* enable clock to access controller register */ - clk_enable(u3d->clk); - if (pdata->phy_init) - pdata->phy_init(u3d->phy_regs); - - mv_u3d_controller_stop(u3d); - /* stop all usb activities */ - u3d->gadget.speed = USB_SPEED_UNKNOWN; - mv_u3d_stop_activity(u3d, NULL); - mv_u3d_disable(u3d); - - if (pdata->phy_deinit) - pdata->phy_deinit(u3d->phy_regs); - clk_disable(u3d->clk); - - spin_unlock_irqrestore(&u3d->lock, flags); - - u3d->driver = NULL; - - return 0; -} - -/* device controller usb_gadget_ops structure */ -static const struct usb_gadget_ops mv_u3d_ops = { - /* notify controller that VBUS is powered or not */ - .vbus_session = mv_u3d_vbus_session, - - /* constrain controller's VBUS power usage */ - .vbus_draw = mv_u3d_vbus_draw, - - .pullup = mv_u3d_pullup, - .udc_start = mv_u3d_start, - .udc_stop = mv_u3d_stop, -}; - -static int mv_u3d_eps_init(struct mv_u3d *u3d) -{ - struct mv_u3d_ep *ep; - char name[14]; - int i; - - /* initialize ep0, ep0 in/out use eps[1] */ - ep = &u3d->eps[1]; - ep->u3d = u3d; - strscpy(ep->name, "ep0"); - ep->ep.name = ep->name; - ep->ep.ops = &mv_u3d_ep_ops; - ep->wedge = 0; - usb_ep_set_maxpacket_limit(&ep->ep, MV_U3D_EP0_MAX_PKT_SIZE); - ep->ep.caps.type_control = true; - ep->ep.caps.dir_in = true; - ep->ep.caps.dir_out = true; - ep->ep_num = 0; - ep->ep.desc = &mv_u3d_ep0_desc; - INIT_LIST_HEAD(&ep->queue); - INIT_LIST_HEAD(&ep->req_list); - ep->ep_type = USB_ENDPOINT_XFER_CONTROL; - - /* add ep0 ep_context */ - ep->ep_context = &u3d->ep_context[1]; - - /* initialize other endpoints */ - for (i = 2; i < u3d->max_eps * 2; i++) { - ep = &u3d->eps[i]; - if (i & 1) { - snprintf(name, sizeof(name), "ep%din", i >> 1); - ep->direction = MV_U3D_EP_DIR_IN; - ep->ep.caps.dir_in = true; - } else { - snprintf(name, sizeof(name), "ep%dout", i >> 1); - ep->direction = MV_U3D_EP_DIR_OUT; - ep->ep.caps.dir_out = true; - } - ep->u3d = u3d; - strscpy(ep->name, name); - ep->ep.name = ep->name; - - ep->ep.caps.type_iso = true; - ep->ep.caps.type_bulk = true; - ep->ep.caps.type_int = true; - - ep->ep.ops = &mv_u3d_ep_ops; - usb_ep_set_maxpacket_limit(&ep->ep, (unsigned short) ~0); - ep->ep_num = i / 2; - - INIT_LIST_HEAD(&ep->queue); - list_add_tail(&ep->ep.ep_list, &u3d->gadget.ep_list); - - INIT_LIST_HEAD(&ep->req_list); - spin_lock_init(&ep->req_lock); - ep->ep_context = &u3d->ep_context[i]; - } - - return 0; -} - -/* delete all endpoint requests, called with spinlock held */ -static void mv_u3d_nuke(struct mv_u3d_ep *ep, int status) -{ - /* endpoint fifo flush */ - mv_u3d_ep_fifo_flush(&ep->ep); - - while (!list_empty(&ep->queue)) { - struct mv_u3d_req *req = NULL; - req = list_entry(ep->queue.next, struct mv_u3d_req, queue); - mv_u3d_done(ep, req, status); - } -} - -/* stop all USB activities */ -static -void mv_u3d_stop_activity(struct mv_u3d *u3d, struct usb_gadget_driver *driver) -{ - struct mv_u3d_ep *ep; - - mv_u3d_nuke(&u3d->eps[1], -ESHUTDOWN); - - list_for_each_entry(ep, &u3d->gadget.ep_list, ep.ep_list) { - mv_u3d_nuke(ep, -ESHUTDOWN); - } - - /* report disconnect; the driver is already quiesced */ - if (driver) { - spin_unlock(&u3d->lock); - driver->disconnect(&u3d->gadget); - spin_lock(&u3d->lock); - } -} - -static void mv_u3d_irq_process_error(struct mv_u3d *u3d) -{ - /* Increment the error count */ - u3d->errors++; - dev_err(u3d->dev, "%s\n", __func__); -} - -static void mv_u3d_irq_process_link_change(struct mv_u3d *u3d) -{ - u32 linkchange; - - linkchange = ioread32(&u3d->vuc_regs->linkchange); - iowrite32(linkchange, &u3d->vuc_regs->linkchange); - - dev_dbg(u3d->dev, "linkchange: 0x%x\n", linkchange); - - if (linkchange & MV_U3D_LINK_CHANGE_LINK_UP) { - dev_dbg(u3d->dev, "link up: ltssm state: 0x%x\n", - ioread32(&u3d->vuc_regs->ltssmstate)); - - u3d->usb_state = USB_STATE_DEFAULT; - u3d->ep0_dir = MV_U3D_EP_DIR_OUT; - u3d->ep0_state = MV_U3D_WAIT_FOR_SETUP; - - /* set speed */ - u3d->gadget.speed = USB_SPEED_SUPER; - } - - if (linkchange & MV_U3D_LINK_CHANGE_SUSPEND) { - dev_dbg(u3d->dev, "link suspend\n"); - u3d->resume_state = u3d->usb_state; - u3d->usb_state = USB_STATE_SUSPENDED; - } - - if (linkchange & MV_U3D_LINK_CHANGE_RESUME) { - dev_dbg(u3d->dev, "link resume\n"); - u3d->usb_state = u3d->resume_state; - u3d->resume_state = 0; - } - - if (linkchange & MV_U3D_LINK_CHANGE_WRESET) { - dev_dbg(u3d->dev, "warm reset\n"); - u3d->usb_state = USB_STATE_POWERED; - } - - if (linkchange & MV_U3D_LINK_CHANGE_HRESET) { - dev_dbg(u3d->dev, "hot reset\n"); - u3d->usb_state = USB_STATE_DEFAULT; - } - - if (linkchange & MV_U3D_LINK_CHANGE_INACT) - dev_dbg(u3d->dev, "inactive\n"); - - if (linkchange & MV_U3D_LINK_CHANGE_DISABLE_AFTER_U0) - dev_dbg(u3d->dev, "ss.disabled\n"); - - if (linkchange & MV_U3D_LINK_CHANGE_VBUS_INVALID) { - dev_dbg(u3d->dev, "vbus invalid\n"); - u3d->usb_state = USB_STATE_ATTACHED; - u3d->vbus_valid_detect = 1; - /* if external vbus detect is not supported, - * we handle it here. - */ - if (!u3d->vbus) { - spin_unlock(&u3d->lock); - mv_u3d_vbus_session(&u3d->gadget, 0); - spin_lock(&u3d->lock); - } - } -} - -static void mv_u3d_ch9setaddress(struct mv_u3d *u3d, - struct usb_ctrlrequest *setup) -{ - u32 tmp; - - if (u3d->usb_state != USB_STATE_DEFAULT) { - dev_err(u3d->dev, - "%s, cannot setaddr in this state (%d)\n", - __func__, u3d->usb_state); - goto err; - } - - u3d->dev_addr = (u8)setup->wValue; - - dev_dbg(u3d->dev, "%s: 0x%x\n", __func__, u3d->dev_addr); - - if (u3d->dev_addr > 127) { - dev_err(u3d->dev, - "%s, u3d address is wrong (out of range)\n", __func__); - u3d->dev_addr = 0; - goto err; - } - - /* update usb state */ - u3d->usb_state = USB_STATE_ADDRESS; - - /* set the new address */ - tmp = ioread32(&u3d->vuc_regs->devaddrtiebrkr); - tmp &= ~0x7F; - tmp |= (u32)u3d->dev_addr; - iowrite32(tmp, &u3d->vuc_regs->devaddrtiebrkr); - - return; -err: - mv_u3d_ep0_stall(u3d); -} - -static int mv_u3d_is_set_configuration(struct usb_ctrlrequest *setup) -{ - if ((setup->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) - if (setup->bRequest == USB_REQ_SET_CONFIGURATION) - return 1; - - return 0; -} - -static void mv_u3d_handle_setup_packet(struct mv_u3d *u3d, u8 ep_num, - struct usb_ctrlrequest *setup) - __releases(&u3c->lock) - __acquires(&u3c->lock) -{ - bool delegate = false; - - mv_u3d_nuke(&u3d->eps[ep_num * 2 + MV_U3D_EP_DIR_IN], -ESHUTDOWN); - - dev_dbg(u3d->dev, "SETUP %02x.%02x v%04x i%04x l%04x\n", - setup->bRequestType, setup->bRequest, - setup->wValue, setup->wIndex, setup->wLength); - - /* We process some stardard setup requests here */ - if ((setup->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { - switch (setup->bRequest) { - case USB_REQ_GET_STATUS: - delegate = true; - break; - - case USB_REQ_SET_ADDRESS: - mv_u3d_ch9setaddress(u3d, setup); - break; - - case USB_REQ_CLEAR_FEATURE: - delegate = true; - break; - - case USB_REQ_SET_FEATURE: - delegate = true; - break; - - default: - delegate = true; - } - } else - delegate = true; - - /* delegate USB standard requests to the gadget driver */ - if (delegate) { - /* USB requests handled by gadget */ - if (setup->wLength) { - /* DATA phase from gadget, STATUS phase from u3d */ - u3d->ep0_dir = (setup->bRequestType & USB_DIR_IN) - ? MV_U3D_EP_DIR_IN : MV_U3D_EP_DIR_OUT; - spin_unlock(&u3d->lock); - if (u3d->driver->setup(&u3d->gadget, - &u3d->local_setup_buff) < 0) { - dev_err(u3d->dev, "setup error!\n"); - mv_u3d_ep0_stall(u3d); - } - spin_lock(&u3d->lock); - } else { - /* no DATA phase, STATUS phase from gadget */ - u3d->ep0_dir = MV_U3D_EP_DIR_IN; - u3d->ep0_state = MV_U3D_STATUS_STAGE; - spin_unlock(&u3d->lock); - if (u3d->driver->setup(&u3d->gadget, - &u3d->local_setup_buff) < 0) - mv_u3d_ep0_stall(u3d); - spin_lock(&u3d->lock); - } - - if (mv_u3d_is_set_configuration(setup)) { - dev_dbg(u3d->dev, "u3d configured\n"); - u3d->usb_state = USB_STATE_CONFIGURED; - } - } -} - -static void mv_u3d_get_setup_data(struct mv_u3d *u3d, u8 ep_num, u8 *buffer_ptr) -{ - struct mv_u3d_ep_context *epcontext; - - epcontext = &u3d->ep_context[ep_num * 2 + MV_U3D_EP_DIR_IN]; - - /* Copy the setup packet to local buffer */ - memcpy(buffer_ptr, (u8 *) &epcontext->setup_buffer, 8); -} - -static void mv_u3d_irq_process_setup(struct mv_u3d *u3d) -{ - u32 tmp, i; - /* Process all Setup packet received interrupts */ - tmp = ioread32(&u3d->vuc_regs->setuplock); - if (tmp) { - for (i = 0; i < u3d->max_eps; i++) { - if (tmp & (1 << i)) { - mv_u3d_get_setup_data(u3d, i, - (u8 *)(&u3d->local_setup_buff)); - mv_u3d_handle_setup_packet(u3d, i, - &u3d->local_setup_buff); - } - } - } - - iowrite32(tmp, &u3d->vuc_regs->setuplock); -} - -static void mv_u3d_irq_process_tr_complete(struct mv_u3d *u3d) -{ - u32 tmp, bit_pos; - int i, ep_num = 0, direction = 0; - struct mv_u3d_ep *curr_ep; - struct mv_u3d_req *curr_req, *temp_req; - int status; - - tmp = ioread32(&u3d->vuc_regs->endcomplete); - - dev_dbg(u3d->dev, "tr_complete: ep: 0x%x\n", tmp); - if (!tmp) - return; - iowrite32(tmp, &u3d->vuc_regs->endcomplete); - - for (i = 0; i < u3d->max_eps * 2; i++) { - ep_num = i >> 1; - direction = i % 2; - - bit_pos = 1 << (ep_num + 16 * direction); - - if (!(bit_pos & tmp)) - continue; - - if (i == 0) - curr_ep = &u3d->eps[1]; - else - curr_ep = &u3d->eps[i]; - - /* remove req out of ep request list after completion */ - dev_dbg(u3d->dev, "tr comp: check req_list\n"); - spin_lock(&curr_ep->req_lock); - if (!list_empty(&curr_ep->req_list)) { - struct mv_u3d_req *req; - req = list_entry(curr_ep->req_list.next, - struct mv_u3d_req, list); - list_del_init(&req->list); - curr_ep->processing = 0; - } - spin_unlock(&curr_ep->req_lock); - - /* process the req queue until an uncomplete request */ - list_for_each_entry_safe(curr_req, temp_req, - &curr_ep->queue, queue) { - status = mv_u3d_process_ep_req(u3d, i, curr_req); - if (status) - break; - /* write back status to req */ - curr_req->req.status = status; - - /* ep0 request completion */ - if (ep_num == 0) { - mv_u3d_done(curr_ep, curr_req, 0); - break; - } else { - mv_u3d_done(curr_ep, curr_req, status); - } - } - - dev_dbg(u3d->dev, "call mv_u3d_start_queue from ep complete\n"); - mv_u3d_start_queue(curr_ep); - } -} - -static irqreturn_t mv_u3d_irq(int irq, void *dev) -{ - struct mv_u3d *u3d = (struct mv_u3d *)dev; - u32 status, intr; - u32 bridgesetting; - u32 trbunderrun; - - spin_lock(&u3d->lock); - - status = ioread32(&u3d->vuc_regs->intrcause); - intr = ioread32(&u3d->vuc_regs->intrenable); - status &= intr; - - if (status == 0) { - spin_unlock(&u3d->lock); - dev_err(u3d->dev, "irq error!\n"); - return IRQ_NONE; - } - - if (status & MV_U3D_USBINT_VBUS_VALID) { - bridgesetting = ioread32(&u3d->vuc_regs->bridgesetting); - if (bridgesetting & MV_U3D_BRIDGE_SETTING_VBUS_VALID) { - /* write vbus valid bit of bridge setting to clear */ - bridgesetting = MV_U3D_BRIDGE_SETTING_VBUS_VALID; - iowrite32(bridgesetting, &u3d->vuc_regs->bridgesetting); - dev_dbg(u3d->dev, "vbus valid\n"); - - u3d->usb_state = USB_STATE_POWERED; - u3d->vbus_valid_detect = 0; - /* if external vbus detect is not supported, - * we handle it here. - */ - if (!u3d->vbus) { - spin_unlock(&u3d->lock); - mv_u3d_vbus_session(&u3d->gadget, 1); - spin_lock(&u3d->lock); - } - } else - dev_err(u3d->dev, "vbus bit is not set\n"); - } - - /* RX data is already in the 16KB FIFO.*/ - if (status & MV_U3D_USBINT_UNDER_RUN) { - trbunderrun = ioread32(&u3d->vuc_regs->trbunderrun); - dev_err(u3d->dev, "under run, ep%d\n", trbunderrun); - iowrite32(trbunderrun, &u3d->vuc_regs->trbunderrun); - mv_u3d_irq_process_error(u3d); - } - - if (status & (MV_U3D_USBINT_RXDESC_ERR | MV_U3D_USBINT_TXDESC_ERR)) { - /* write one to clear */ - iowrite32(status & (MV_U3D_USBINT_RXDESC_ERR - | MV_U3D_USBINT_TXDESC_ERR), - &u3d->vuc_regs->intrcause); - dev_err(u3d->dev, "desc err 0x%x\n", status); - mv_u3d_irq_process_error(u3d); - } - - if (status & MV_U3D_USBINT_LINK_CHG) - mv_u3d_irq_process_link_change(u3d); - - if (status & MV_U3D_USBINT_TX_COMPLETE) - mv_u3d_irq_process_tr_complete(u3d); - - if (status & MV_U3D_USBINT_RX_COMPLETE) - mv_u3d_irq_process_tr_complete(u3d); - - if (status & MV_U3D_USBINT_SETUP) - mv_u3d_irq_process_setup(u3d); - - spin_unlock(&u3d->lock); - return IRQ_HANDLED; -} - -static void mv_u3d_remove(struct platform_device *dev) -{ - struct mv_u3d *u3d = platform_get_drvdata(dev); - - BUG_ON(u3d == NULL); - - usb_del_gadget_udc(&u3d->gadget); - - /* free memory allocated in probe */ - dma_pool_destroy(u3d->trb_pool); - - if (u3d->ep_context) - dma_free_coherent(&dev->dev, u3d->ep_context_size, - u3d->ep_context, u3d->ep_context_dma); - - kfree(u3d->eps); - - if (u3d->irq) - free_irq(u3d->irq, u3d); - - if (u3d->cap_regs) - iounmap(u3d->cap_regs); - u3d->cap_regs = NULL; - - kfree(u3d->status_req); - - clk_put(u3d->clk); - - kfree(u3d); -} - -static int mv_u3d_probe(struct platform_device *dev) -{ - struct mv_u3d *u3d; - struct mv_usb_platform_data *pdata = dev_get_platdata(&dev->dev); - int retval = 0; - struct resource *r; - size_t size; - - if (!dev_get_platdata(&dev->dev)) { - dev_err(&dev->dev, "missing platform_data\n"); - retval = -ENODEV; - goto err_pdata; - } - - u3d = kzalloc(sizeof(*u3d), GFP_KERNEL); - if (!u3d) { - retval = -ENOMEM; - goto err_alloc_private; - } - - spin_lock_init(&u3d->lock); - - platform_set_drvdata(dev, u3d); - - u3d->dev = &dev->dev; - u3d->vbus = pdata->vbus; - - u3d->clk = clk_get(&dev->dev, NULL); - if (IS_ERR(u3d->clk)) { - retval = PTR_ERR(u3d->clk); - goto err_get_clk; - } - - r = platform_get_resource_byname(dev, IORESOURCE_MEM, "capregs"); - if (!r) { - dev_err(&dev->dev, "no I/O memory resource defined\n"); - retval = -ENODEV; - goto err_get_cap_regs; - } - - u3d->cap_regs = (struct mv_u3d_cap_regs __iomem *) - ioremap(r->start, resource_size(r)); - if (!u3d->cap_regs) { - dev_err(&dev->dev, "failed to map I/O memory\n"); - retval = -EBUSY; - goto err_map_cap_regs; - } else { - dev_dbg(&dev->dev, "cap_regs address: 0x%lx/0x%lx\n", - (unsigned long) r->start, - (unsigned long) u3d->cap_regs); - } - - /* we will access controller register, so enable the u3d controller */ - retval = clk_enable(u3d->clk); - if (retval) { - dev_err(&dev->dev, "clk_enable error %d\n", retval); - goto err_u3d_enable; - } - - if (pdata->phy_init) { - retval = pdata->phy_init(u3d->phy_regs); - if (retval) { - dev_err(&dev->dev, "init phy error %d\n", retval); - clk_disable(u3d->clk); - goto err_phy_init; - } - } - - u3d->op_regs = (struct mv_u3d_op_regs __iomem *)(u3d->cap_regs - + MV_U3D_USB3_OP_REGS_OFFSET); - - u3d->vuc_regs = (struct mv_u3d_vuc_regs __iomem *)(u3d->cap_regs - + ioread32(&u3d->cap_regs->vuoff)); - - u3d->max_eps = 16; - - /* - * some platform will use usb to download image, it may not disconnect - * usb gadget before loading kernel. So first stop u3d here. - */ - mv_u3d_controller_stop(u3d); - iowrite32(0xFFFFFFFF, &u3d->vuc_regs->intrcause); - - if (pdata->phy_deinit) - pdata->phy_deinit(u3d->phy_regs); - clk_disable(u3d->clk); - - size = u3d->max_eps * sizeof(struct mv_u3d_ep_context) * 2; - size = (size + MV_U3D_EP_CONTEXT_ALIGNMENT - 1) - & ~(MV_U3D_EP_CONTEXT_ALIGNMENT - 1); - u3d->ep_context = dma_alloc_coherent(&dev->dev, size, - &u3d->ep_context_dma, GFP_KERNEL); - if (!u3d->ep_context) { - dev_err(&dev->dev, "allocate ep context memory failed\n"); - retval = -ENOMEM; - goto err_alloc_ep_context; - } - u3d->ep_context_size = size; - - /* create TRB dma_pool resource */ - u3d->trb_pool = dma_pool_create("u3d_trb", - &dev->dev, - sizeof(struct mv_u3d_trb_hw), - MV_U3D_TRB_ALIGNMENT, - MV_U3D_DMA_BOUNDARY); - - if (!u3d->trb_pool) { - retval = -ENOMEM; - goto err_alloc_trb_pool; - } - - size = u3d->max_eps * sizeof(struct mv_u3d_ep) * 2; - u3d->eps = kzalloc(size, GFP_KERNEL); - if (!u3d->eps) { - retval = -ENOMEM; - goto err_alloc_eps; - } - - /* initialize ep0 status request structure */ - u3d->status_req = kzalloc(sizeof(struct mv_u3d_req) + 8, GFP_KERNEL); - if (!u3d->status_req) { - retval = -ENOMEM; - goto err_alloc_status_req; - } - INIT_LIST_HEAD(&u3d->status_req->queue); - - /* allocate a small amount of memory to get valid address */ - u3d->status_req->req.buf = (char *)u3d->status_req - + sizeof(struct mv_u3d_req); - u3d->status_req->req.dma = virt_to_phys(u3d->status_req->req.buf); - - u3d->resume_state = USB_STATE_NOTATTACHED; - u3d->usb_state = USB_STATE_ATTACHED; - u3d->ep0_dir = MV_U3D_EP_DIR_OUT; - u3d->remote_wakeup = 0; - - r = platform_get_resource(dev, IORESOURCE_IRQ, 0); - if (!r) { - dev_err(&dev->dev, "no IRQ resource defined\n"); - retval = -ENODEV; - goto err_get_irq; - } - u3d->irq = r->start; - - /* initialize gadget structure */ - u3d->gadget.ops = &mv_u3d_ops; /* usb_gadget_ops */ - u3d->gadget.ep0 = &u3d->eps[1].ep; /* gadget ep0 */ - INIT_LIST_HEAD(&u3d->gadget.ep_list); /* ep_list */ - u3d->gadget.speed = USB_SPEED_UNKNOWN; /* speed */ - - /* the "gadget" abstracts/virtualizes the controller */ - u3d->gadget.name = driver_name; /* gadget name */ - - mv_u3d_eps_init(u3d); - - if (request_irq(u3d->irq, mv_u3d_irq, - IRQF_SHARED, driver_name, u3d)) { - u3d->irq = 0; - dev_err(&dev->dev, "Request irq %d for u3d failed\n", - u3d->irq); - retval = -ENODEV; - goto err_request_irq; - } - - /* external vbus detection */ - if (u3d->vbus) { - u3d->clock_gating = 1; - dev_err(&dev->dev, "external vbus detection\n"); - } - - if (!u3d->clock_gating) - u3d->vbus_active = 1; - - /* enable usb3 controller vbus detection */ - u3d->vbus_valid_detect = 1; - - retval = usb_add_gadget_udc(&dev->dev, &u3d->gadget); - if (retval) - goto err_unregister; - - dev_dbg(&dev->dev, "successful probe usb3 device %s clock gating.\n", - u3d->clock_gating ? "with" : "without"); - - return 0; - -err_unregister: - free_irq(u3d->irq, u3d); -err_get_irq: -err_request_irq: - kfree(u3d->status_req); -err_alloc_status_req: - kfree(u3d->eps); -err_alloc_eps: - dma_pool_destroy(u3d->trb_pool); -err_alloc_trb_pool: - dma_free_coherent(&dev->dev, u3d->ep_context_size, - u3d->ep_context, u3d->ep_context_dma); -err_alloc_ep_context: -err_phy_init: -err_u3d_enable: - iounmap(u3d->cap_regs); -err_map_cap_regs: -err_get_cap_regs: - clk_put(u3d->clk); -err_get_clk: - kfree(u3d); -err_alloc_private: -err_pdata: - return retval; -} - -#ifdef CONFIG_PM_SLEEP -static int mv_u3d_suspend(struct device *dev) -{ - struct mv_u3d *u3d = dev_get_drvdata(dev); - - /* - * only cable is unplugged, usb can suspend. - * So do not care about clock_gating == 1, it is handled by - * vbus session. - */ - if (!u3d->clock_gating) { - mv_u3d_controller_stop(u3d); - - spin_lock_irq(&u3d->lock); - /* stop all usb activities */ - mv_u3d_stop_activity(u3d, u3d->driver); - spin_unlock_irq(&u3d->lock); - - mv_u3d_disable(u3d); - } - - return 0; -} - -static int mv_u3d_resume(struct device *dev) -{ - struct mv_u3d *u3d = dev_get_drvdata(dev); - int retval; - - if (!u3d->clock_gating) { - retval = mv_u3d_enable(u3d); - if (retval) - return retval; - - if (u3d->driver && u3d->softconnect) { - mv_u3d_controller_reset(u3d); - mv_u3d_ep0_reset(u3d); - mv_u3d_controller_start(u3d); - } - } - - return 0; -} -#endif - -static SIMPLE_DEV_PM_OPS(mv_u3d_pm_ops, mv_u3d_suspend, mv_u3d_resume); - -static void mv_u3d_shutdown(struct platform_device *dev) -{ - struct mv_u3d *u3d = platform_get_drvdata(dev); - u32 tmp; - - tmp = ioread32(&u3d->op_regs->usbcmd); - tmp &= ~MV_U3D_CMD_RUN_STOP; - iowrite32(tmp, &u3d->op_regs->usbcmd); -} - -static struct platform_driver mv_u3d_driver = { - .probe = mv_u3d_probe, - .remove = mv_u3d_remove, - .shutdown = mv_u3d_shutdown, - .driver = { - .name = "mv-u3d", - .pm = &mv_u3d_pm_ops, - }, -}; - -module_platform_driver(mv_u3d_driver); -MODULE_ALIAS("platform:mv-u3d"); -MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_AUTHOR("Yu Xu "); -MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/udc/mv_udc.h b/drivers/usb/gadget/udc/mv_udc.h deleted file mode 100644 index b3f759c0962c..000000000000 --- a/drivers/usb/gadget/udc/mv_udc.h +++ /dev/null @@ -1,309 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright (C) 2011 Marvell International Ltd. All rights reserved. - */ - -#ifndef __MV_UDC_H -#define __MV_UDC_H - -#define VUSBHS_MAX_PORTS 8 - -#define DQH_ALIGNMENT 2048 -#define DTD_ALIGNMENT 64 -#define DMA_BOUNDARY 4096 - -#define EP_DIR_IN 1 -#define EP_DIR_OUT 0 - -#define DMA_ADDR_INVALID (~(dma_addr_t)0) - -#define EP0_MAX_PKT_SIZE 64 -/* ep0 transfer state */ -#define WAIT_FOR_SETUP 0 -#define DATA_STATE_XMIT 1 -#define DATA_STATE_NEED_ZLP 2 -#define WAIT_FOR_OUT_STATUS 3 -#define DATA_STATE_RECV 4 - -#define CAPLENGTH_MASK (0xff) -#define DCCPARAMS_DEN_MASK (0x1f) - -#define HCSPARAMS_PPC (0x10) - -/* Frame Index Register Bit Masks */ -#define USB_FRINDEX_MASKS 0x3fff - -/* Command Register Bit Masks */ -#define USBCMD_RUN_STOP (0x00000001) -#define USBCMD_CTRL_RESET (0x00000002) -#define USBCMD_SETUP_TRIPWIRE_SET (0x00002000) -#define USBCMD_SETUP_TRIPWIRE_CLEAR (~USBCMD_SETUP_TRIPWIRE_SET) - -#define USBCMD_ATDTW_TRIPWIRE_SET (0x00004000) -#define USBCMD_ATDTW_TRIPWIRE_CLEAR (~USBCMD_ATDTW_TRIPWIRE_SET) - -/* bit 15,3,2 are for frame list size */ -#define USBCMD_FRAME_SIZE_1024 (0x00000000) /* 000 */ -#define USBCMD_FRAME_SIZE_512 (0x00000004) /* 001 */ -#define USBCMD_FRAME_SIZE_256 (0x00000008) /* 010 */ -#define USBCMD_FRAME_SIZE_128 (0x0000000C) /* 011 */ -#define USBCMD_FRAME_SIZE_64 (0x00008000) /* 100 */ -#define USBCMD_FRAME_SIZE_32 (0x00008004) /* 101 */ -#define USBCMD_FRAME_SIZE_16 (0x00008008) /* 110 */ -#define USBCMD_FRAME_SIZE_8 (0x0000800C) /* 111 */ - -#define EPCTRL_TX_ALL_MASK (0xFFFF0000) -#define EPCTRL_RX_ALL_MASK (0x0000FFFF) - -#define EPCTRL_TX_DATA_TOGGLE_RST (0x00400000) -#define EPCTRL_TX_EP_STALL (0x00010000) -#define EPCTRL_RX_EP_STALL (0x00000001) -#define EPCTRL_RX_DATA_TOGGLE_RST (0x00000040) -#define EPCTRL_RX_ENABLE (0x00000080) -#define EPCTRL_TX_ENABLE (0x00800000) -#define EPCTRL_CONTROL (0x00000000) -#define EPCTRL_ISOCHRONOUS (0x00040000) -#define EPCTRL_BULK (0x00080000) -#define EPCTRL_INT (0x000C0000) -#define EPCTRL_TX_TYPE (0x000C0000) -#define EPCTRL_RX_TYPE (0x0000000C) -#define EPCTRL_DATA_TOGGLE_INHIBIT (0x00000020) -#define EPCTRL_TX_EP_TYPE_SHIFT (18) -#define EPCTRL_RX_EP_TYPE_SHIFT (2) - -#define EPCOMPLETE_MAX_ENDPOINTS (16) - -/* endpoint list address bit masks */ -#define USB_EP_LIST_ADDRESS_MASK 0xfffff800 - -#define PORTSCX_W1C_BITS 0x2a -#define PORTSCX_PORT_RESET 0x00000100 -#define PORTSCX_PORT_POWER 0x00001000 -#define PORTSCX_FORCE_FULL_SPEED_CONNECT 0x01000000 -#define PORTSCX_PAR_XCVR_SELECT 0xC0000000 -#define PORTSCX_PORT_FORCE_RESUME 0x00000040 -#define PORTSCX_PORT_SUSPEND 0x00000080 -#define PORTSCX_PORT_SPEED_FULL 0x00000000 -#define PORTSCX_PORT_SPEED_LOW 0x04000000 -#define PORTSCX_PORT_SPEED_HIGH 0x08000000 -#define PORTSCX_PORT_SPEED_MASK 0x0C000000 - -/* USB MODE Register Bit Masks */ -#define USBMODE_CTRL_MODE_IDLE 0x00000000 -#define USBMODE_CTRL_MODE_DEVICE 0x00000002 -#define USBMODE_CTRL_MODE_HOST 0x00000003 -#define USBMODE_CTRL_MODE_RSV 0x00000001 -#define USBMODE_SETUP_LOCK_OFF 0x00000008 -#define USBMODE_STREAM_DISABLE 0x00000010 - -/* USB STS Register Bit Masks */ -#define USBSTS_INT 0x00000001 -#define USBSTS_ERR 0x00000002 -#define USBSTS_PORT_CHANGE 0x00000004 -#define USBSTS_FRM_LST_ROLL 0x00000008 -#define USBSTS_SYS_ERR 0x00000010 -#define USBSTS_IAA 0x00000020 -#define USBSTS_RESET 0x00000040 -#define USBSTS_SOF 0x00000080 -#define USBSTS_SUSPEND 0x00000100 -#define USBSTS_HC_HALTED 0x00001000 -#define USBSTS_RCL 0x00002000 -#define USBSTS_PERIODIC_SCHEDULE 0x00004000 -#define USBSTS_ASYNC_SCHEDULE 0x00008000 - - -/* Interrupt Enable Register Bit Masks */ -#define USBINTR_INT_EN (0x00000001) -#define USBINTR_ERR_INT_EN (0x00000002) -#define USBINTR_PORT_CHANGE_DETECT_EN (0x00000004) - -#define USBINTR_ASYNC_ADV_AAE (0x00000020) -#define USBINTR_ASYNC_ADV_AAE_ENABLE (0x00000020) -#define USBINTR_ASYNC_ADV_AAE_DISABLE (0xFFFFFFDF) - -#define USBINTR_RESET_EN (0x00000040) -#define USBINTR_SOF_UFRAME_EN (0x00000080) -#define USBINTR_DEVICE_SUSPEND (0x00000100) - -#define USB_DEVICE_ADDRESS_MASK (0xfe000000) -#define USB_DEVICE_ADDRESS_BIT_SHIFT (25) - -struct mv_cap_regs { - u32 caplength_hciversion; - u32 hcsparams; /* HC structural parameters */ - u32 hccparams; /* HC Capability Parameters*/ - u32 reserved[5]; - u32 dciversion; /* DC version number and reserved 16 bits */ - u32 dccparams; /* DC Capability Parameters */ -}; - -struct mv_op_regs { - u32 usbcmd; /* Command register */ - u32 usbsts; /* Status register */ - u32 usbintr; /* Interrupt enable */ - u32 frindex; /* Frame index */ - u32 reserved1[1]; - u32 deviceaddr; /* Device Address */ - u32 eplistaddr; /* Endpoint List Address */ - u32 ttctrl; /* HOST TT status and control */ - u32 burstsize; /* Programmable Burst Size */ - u32 txfilltuning; /* Host Transmit Pre-Buffer Packet Tuning */ - u32 reserved[4]; - u32 epnak; /* Endpoint NAK */ - u32 epnaken; /* Endpoint NAK Enable */ - u32 configflag; /* Configured Flag register */ - u32 portsc[VUSBHS_MAX_PORTS]; /* Port Status/Control x, x = 1..8 */ - u32 otgsc; - u32 usbmode; /* USB Host/Device mode */ - u32 epsetupstat; /* Endpoint Setup Status */ - u32 epprime; /* Endpoint Initialize */ - u32 epflush; /* Endpoint De-initialize */ - u32 epstatus; /* Endpoint Status */ - u32 epcomplete; /* Endpoint Interrupt On Complete */ - u32 epctrlx[16]; /* Endpoint Control, where x = 0.. 15 */ - u32 mcr; /* Mux Control */ - u32 isr; /* Interrupt Status */ - u32 ier; /* Interrupt Enable */ -}; - -struct mv_udc { - struct usb_gadget gadget; - struct usb_gadget_driver *driver; - spinlock_t lock; - struct completion *done; - struct platform_device *dev; - int irq; - - struct mv_cap_regs __iomem *cap_regs; - struct mv_op_regs __iomem *op_regs; - void __iomem *phy_regs; - unsigned int max_eps; - struct mv_dqh *ep_dqh; - size_t ep_dqh_size; - dma_addr_t ep_dqh_dma; - - struct dma_pool *dtd_pool; - struct mv_ep *eps; - - struct mv_dtd *dtd_head; - struct mv_dtd *dtd_tail; - unsigned int dtd_entries; - - struct mv_req *status_req; - struct usb_ctrlrequest local_setup_buff; - - unsigned int resume_state; /* USB state to resume */ - unsigned int usb_state; /* USB current state */ - unsigned int ep0_state; /* Endpoint zero state */ - unsigned int ep0_dir; - - unsigned int dev_addr; - unsigned int test_mode; - - int errors; - unsigned softconnect:1, - vbus_active:1, - remote_wakeup:1, - softconnected:1, - force_fs:1, - clock_gating:1, - active:1, - stopped:1; /* stop bit is setted */ - - struct work_struct vbus_work; - struct workqueue_struct *qwork; - - struct usb_phy *transceiver; - - struct mv_usb_platform_data *pdata; - - /* some SOC has mutiple clock sources for USB*/ - struct clk *clk; -}; - -/* endpoint data structure */ -struct mv_ep { - struct usb_ep ep; - struct mv_udc *udc; - struct list_head queue; - struct mv_dqh *dqh; - u32 direction; - char name[14]; - unsigned stopped:1, - wedge:1, - ep_type:2, - ep_num:8; -}; - -/* request data structure */ -struct mv_req { - struct usb_request req; - struct mv_dtd *dtd, *head, *tail; - struct mv_ep *ep; - struct list_head queue; - unsigned int test_mode; - unsigned dtd_count; - unsigned mapped:1; -}; - -#define EP_QUEUE_HEAD_MULT_POS 30 -#define EP_QUEUE_HEAD_ZLT_SEL 0x20000000 -#define EP_QUEUE_HEAD_MAX_PKT_LEN_POS 16 -#define EP_QUEUE_HEAD_MAX_PKT_LEN(ep_info) (((ep_info)>>16)&0x07ff) -#define EP_QUEUE_HEAD_IOS 0x00008000 -#define EP_QUEUE_HEAD_NEXT_TERMINATE 0x00000001 -#define EP_QUEUE_HEAD_IOC 0x00008000 -#define EP_QUEUE_HEAD_MULTO 0x00000C00 -#define EP_QUEUE_HEAD_STATUS_HALT 0x00000040 -#define EP_QUEUE_HEAD_STATUS_ACTIVE 0x00000080 -#define EP_QUEUE_CURRENT_OFFSET_MASK 0x00000FFF -#define EP_QUEUE_HEAD_NEXT_POINTER_MASK 0xFFFFFFE0 -#define EP_QUEUE_FRINDEX_MASK 0x000007FF -#define EP_MAX_LENGTH_TRANSFER 0x4000 - -struct mv_dqh { - /* Bits 16..26 Bit 15 is Interrupt On Setup */ - u32 max_packet_length; - u32 curr_dtd_ptr; /* Current dTD Pointer */ - u32 next_dtd_ptr; /* Next dTD Pointer */ - /* Total bytes (16..30), IOC (15), INT (8), STS (0-7) */ - u32 size_ioc_int_sts; - u32 buff_ptr0; /* Buffer pointer Page 0 (12-31) */ - u32 buff_ptr1; /* Buffer pointer Page 1 (12-31) */ - u32 buff_ptr2; /* Buffer pointer Page 2 (12-31) */ - u32 buff_ptr3; /* Buffer pointer Page 3 (12-31) */ - u32 buff_ptr4; /* Buffer pointer Page 4 (12-31) */ - u32 reserved1; - /* 8 bytes of setup data that follows the Setup PID */ - u8 setup_buffer[8]; - u32 reserved2[4]; -}; - - -#define DTD_NEXT_TERMINATE (0x00000001) -#define DTD_IOC (0x00008000) -#define DTD_STATUS_ACTIVE (0x00000080) -#define DTD_STATUS_HALTED (0x00000040) -#define DTD_STATUS_DATA_BUFF_ERR (0x00000020) -#define DTD_STATUS_TRANSACTION_ERR (0x00000008) -#define DTD_RESERVED_FIELDS (0x00007F00) -#define DTD_ERROR_MASK (0x68) -#define DTD_ADDR_MASK (0xFFFFFFE0) -#define DTD_PACKET_SIZE 0x7FFF0000 -#define DTD_LENGTH_BIT_POS (16) - -struct mv_dtd { - u32 dtd_next; - u32 size_ioc_sts; - u32 buff_ptr0; /* Buffer pointer Page 0 */ - u32 buff_ptr1; /* Buffer pointer Page 1 */ - u32 buff_ptr2; /* Buffer pointer Page 2 */ - u32 buff_ptr3; /* Buffer pointer Page 3 */ - u32 buff_ptr4; /* Buffer pointer Page 4 */ - u32 scratch_ptr; - /* 32 bytes */ - dma_addr_t td_dma; /* dma address for this td */ - struct mv_dtd *next_dtd_virt; -}; - -#endif diff --git a/drivers/usb/gadget/udc/mv_udc_core.c b/drivers/usb/gadget/udc/mv_udc_core.c deleted file mode 100644 index ff103e6b3048..000000000000 --- a/drivers/usb/gadget/udc/mv_udc_core.c +++ /dev/null @@ -1,2426 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright (C) 2011 Marvell International Ltd. All rights reserved. - * Author: Chao Xie - * Neil Zhang - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "mv_udc.h" - -#define DRIVER_DESC "Marvell PXA USB Device Controller driver" - -#define ep_dir(ep) (((ep)->ep_num == 0) ? \ - ((ep)->udc->ep0_dir) : ((ep)->direction)) - -/* timeout value -- usec */ -#define RESET_TIMEOUT 10000 -#define FLUSH_TIMEOUT 10000 -#define EPSTATUS_TIMEOUT 10000 -#define PRIME_TIMEOUT 10000 -#define READSAFE_TIMEOUT 1000 - -#define LOOPS_USEC_SHIFT 1 -#define LOOPS_USEC (1 << LOOPS_USEC_SHIFT) -#define LOOPS(timeout) ((timeout) >> LOOPS_USEC_SHIFT) - -static DECLARE_COMPLETION(release_done); - -static const char driver_name[] = "mv_udc"; - -static void nuke(struct mv_ep *ep, int status); -static void stop_activity(struct mv_udc *udc, struct usb_gadget_driver *driver); - -/* for endpoint 0 operations */ -static const struct usb_endpoint_descriptor mv_ep0_desc = { - .bLength = USB_DT_ENDPOINT_SIZE, - .bDescriptorType = USB_DT_ENDPOINT, - .bEndpointAddress = 0, - .bmAttributes = USB_ENDPOINT_XFER_CONTROL, - .wMaxPacketSize = EP0_MAX_PKT_SIZE, -}; - -static void ep0_reset(struct mv_udc *udc) -{ - struct mv_ep *ep; - u32 epctrlx; - int i = 0; - - /* ep0 in and out */ - for (i = 0; i < 2; i++) { - ep = &udc->eps[i]; - ep->udc = udc; - - /* ep0 dQH */ - ep->dqh = &udc->ep_dqh[i]; - - /* configure ep0 endpoint capabilities in dQH */ - ep->dqh->max_packet_length = - (EP0_MAX_PKT_SIZE << EP_QUEUE_HEAD_MAX_PKT_LEN_POS) - | EP_QUEUE_HEAD_IOS; - - ep->dqh->next_dtd_ptr = EP_QUEUE_HEAD_NEXT_TERMINATE; - - epctrlx = readl(&udc->op_regs->epctrlx[0]); - if (i) { /* TX */ - epctrlx |= EPCTRL_TX_ENABLE - | (USB_ENDPOINT_XFER_CONTROL - << EPCTRL_TX_EP_TYPE_SHIFT); - - } else { /* RX */ - epctrlx |= EPCTRL_RX_ENABLE - | (USB_ENDPOINT_XFER_CONTROL - << EPCTRL_RX_EP_TYPE_SHIFT); - } - - writel(epctrlx, &udc->op_regs->epctrlx[0]); - } -} - -/* protocol ep0 stall, will automatically be cleared on new transaction */ -static void ep0_stall(struct mv_udc *udc) -{ - u32 epctrlx; - - /* set TX and RX to stall */ - epctrlx = readl(&udc->op_regs->epctrlx[0]); - epctrlx |= EPCTRL_RX_EP_STALL | EPCTRL_TX_EP_STALL; - writel(epctrlx, &udc->op_regs->epctrlx[0]); - - /* update ep0 state */ - udc->ep0_state = WAIT_FOR_SETUP; - udc->ep0_dir = EP_DIR_OUT; -} - -static int process_ep_req(struct mv_udc *udc, int index, - struct mv_req *curr_req) -{ - struct mv_dtd *curr_dtd; - struct mv_dqh *curr_dqh; - int actual, remaining_length; - int i, direction; - int retval = 0; - u32 errors; - u32 bit_pos; - - curr_dqh = &udc->ep_dqh[index]; - direction = index % 2; - - curr_dtd = curr_req->head; - actual = curr_req->req.length; - - for (i = 0; i < curr_req->dtd_count; i++) { - if (curr_dtd->size_ioc_sts & DTD_STATUS_ACTIVE) { - dev_dbg(&udc->dev->dev, "%s, dTD not completed\n", - udc->eps[index].name); - return 1; - } - - errors = curr_dtd->size_ioc_sts & DTD_ERROR_MASK; - if (!errors) { - remaining_length = - (curr_dtd->size_ioc_sts & DTD_PACKET_SIZE) - >> DTD_LENGTH_BIT_POS; - actual -= remaining_length; - - if (remaining_length) { - if (direction) { - dev_dbg(&udc->dev->dev, - "TX dTD remains data\n"); - retval = -EPROTO; - break; - } else - break; - } - } else { - dev_info(&udc->dev->dev, - "complete_tr error: ep=%d %s: error = 0x%x\n", - index >> 1, direction ? "SEND" : "RECV", - errors); - if (errors & DTD_STATUS_HALTED) { - /* Clear the errors and Halt condition */ - curr_dqh->size_ioc_int_sts &= ~errors; - retval = -EPIPE; - } else if (errors & DTD_STATUS_DATA_BUFF_ERR) { - retval = -EPROTO; - } else if (errors & DTD_STATUS_TRANSACTION_ERR) { - retval = -EILSEQ; - } - } - if (i != curr_req->dtd_count - 1) - curr_dtd = (struct mv_dtd *)curr_dtd->next_dtd_virt; - } - if (retval) - return retval; - - if (direction == EP_DIR_OUT) - bit_pos = 1 << curr_req->ep->ep_num; - else - bit_pos = 1 << (16 + curr_req->ep->ep_num); - - while (curr_dqh->curr_dtd_ptr == curr_dtd->td_dma) { - if (curr_dtd->dtd_next == EP_QUEUE_HEAD_NEXT_TERMINATE) { - while (readl(&udc->op_regs->epstatus) & bit_pos) - udelay(1); - break; - } - udelay(1); - } - - curr_req->req.actual = actual; - - return 0; -} - -/* - * done() - retire a request; caller blocked irqs - * @status : request status to be set, only works when - * request is still in progress. - */ -static void done(struct mv_ep *ep, struct mv_req *req, int status) - __releases(&ep->udc->lock) - __acquires(&ep->udc->lock) -{ - struct mv_udc *udc = NULL; - unsigned char stopped = ep->stopped; - struct mv_dtd *curr_td, *next_td; - int j; - - udc = (struct mv_udc *)ep->udc; - /* Removed the req from fsl_ep->queue */ - list_del_init(&req->queue); - - /* req.status should be set as -EINPROGRESS in ep_queue() */ - if (req->req.status == -EINPROGRESS) - req->req.status = status; - else - status = req->req.status; - - /* Free dtd for the request */ - next_td = req->head; - for (j = 0; j < req->dtd_count; j++) { - curr_td = next_td; - if (j != req->dtd_count - 1) - next_td = curr_td->next_dtd_virt; - dma_pool_free(udc->dtd_pool, curr_td, curr_td->td_dma); - } - - usb_gadget_unmap_request(&udc->gadget, &req->req, ep_dir(ep)); - - if (status && (status != -ESHUTDOWN)) - dev_info(&udc->dev->dev, "complete %s req %p stat %d len %u/%u", - ep->ep.name, &req->req, status, - req->req.actual, req->req.length); - - ep->stopped = 1; - - spin_unlock(&ep->udc->lock); - - usb_gadget_giveback_request(&ep->ep, &req->req); - - spin_lock(&ep->udc->lock); - ep->stopped = stopped; -} - -static int queue_dtd(struct mv_ep *ep, struct mv_req *req) -{ - struct mv_udc *udc; - struct mv_dqh *dqh; - u32 bit_pos, direction; - u32 usbcmd, epstatus; - unsigned int loops; - int retval = 0; - - udc = ep->udc; - direction = ep_dir(ep); - dqh = &(udc->ep_dqh[ep->ep_num * 2 + direction]); - bit_pos = 1 << (((direction == EP_DIR_OUT) ? 0 : 16) + ep->ep_num); - - /* check if the pipe is empty */ - if (!(list_empty(&ep->queue))) { - struct mv_req *lastreq; - lastreq = list_entry(ep->queue.prev, struct mv_req, queue); - lastreq->tail->dtd_next = - req->head->td_dma & EP_QUEUE_HEAD_NEXT_POINTER_MASK; - - wmb(); - - if (readl(&udc->op_regs->epprime) & bit_pos) - goto done; - - loops = LOOPS(READSAFE_TIMEOUT); - while (1) { - /* start with setting the semaphores */ - usbcmd = readl(&udc->op_regs->usbcmd); - usbcmd |= USBCMD_ATDTW_TRIPWIRE_SET; - writel(usbcmd, &udc->op_regs->usbcmd); - - /* read the endpoint status */ - epstatus = readl(&udc->op_regs->epstatus) & bit_pos; - - /* - * Reread the ATDTW semaphore bit to check if it is - * cleared. When hardware see a hazard, it will clear - * the bit or else we remain set to 1 and we can - * proceed with priming of endpoint if not already - * primed. - */ - if (readl(&udc->op_regs->usbcmd) - & USBCMD_ATDTW_TRIPWIRE_SET) - break; - - loops--; - if (loops == 0) { - dev_err(&udc->dev->dev, - "Timeout for ATDTW_TRIPWIRE...\n"); - retval = -ETIME; - goto done; - } - udelay(LOOPS_USEC); - } - - /* Clear the semaphore */ - usbcmd = readl(&udc->op_regs->usbcmd); - usbcmd &= USBCMD_ATDTW_TRIPWIRE_CLEAR; - writel(usbcmd, &udc->op_regs->usbcmd); - - if (epstatus) - goto done; - } - - /* Write dQH next pointer and terminate bit to 0 */ - dqh->next_dtd_ptr = req->head->td_dma - & EP_QUEUE_HEAD_NEXT_POINTER_MASK; - - /* clear active and halt bit, in case set from a previous error */ - dqh->size_ioc_int_sts &= ~(DTD_STATUS_ACTIVE | DTD_STATUS_HALTED); - - /* Ensure that updates to the QH will occur before priming. */ - wmb(); - - /* Prime the Endpoint */ - writel(bit_pos, &udc->op_regs->epprime); - -done: - return retval; -} - -static struct mv_dtd *build_dtd(struct mv_req *req, unsigned *length, - dma_addr_t *dma, int *is_last) -{ - struct mv_dtd *dtd; - struct mv_udc *udc; - struct mv_dqh *dqh; - u32 temp, mult = 0; - - /* how big will this transfer be? */ - if (usb_endpoint_xfer_isoc(req->ep->ep.desc)) { - dqh = req->ep->dqh; - mult = (dqh->max_packet_length >> EP_QUEUE_HEAD_MULT_POS) - & 0x3; - *length = min(req->req.length - req->req.actual, - (unsigned)(mult * req->ep->ep.maxpacket)); - } else - *length = min(req->req.length - req->req.actual, - (unsigned)EP_MAX_LENGTH_TRANSFER); - - udc = req->ep->udc; - - /* - * Be careful that no _GFP_HIGHMEM is set, - * or we can not use dma_to_virt - */ - dtd = dma_pool_alloc(udc->dtd_pool, GFP_ATOMIC, dma); - if (dtd == NULL) - return dtd; - - dtd->td_dma = *dma; - /* initialize buffer page pointers */ - temp = (u32)(req->req.dma + req->req.actual); - dtd->buff_ptr0 = cpu_to_le32(temp); - temp &= ~0xFFF; - dtd->buff_ptr1 = cpu_to_le32(temp + 0x1000); - dtd->buff_ptr2 = cpu_to_le32(temp + 0x2000); - dtd->buff_ptr3 = cpu_to_le32(temp + 0x3000); - dtd->buff_ptr4 = cpu_to_le32(temp + 0x4000); - - req->req.actual += *length; - - /* zlp is needed if req->req.zero is set */ - if (req->req.zero) { - if (*length == 0 || (*length % req->ep->ep.maxpacket) != 0) - *is_last = 1; - else - *is_last = 0; - } else if (req->req.length == req->req.actual) - *is_last = 1; - else - *is_last = 0; - - /* Fill in the transfer size; set active bit */ - temp = ((*length << DTD_LENGTH_BIT_POS) | DTD_STATUS_ACTIVE); - - /* Enable interrupt for the last dtd of a request */ - if (*is_last && !req->req.no_interrupt) - temp |= DTD_IOC; - - temp |= mult << 10; - - dtd->size_ioc_sts = temp; - - mb(); - - return dtd; -} - -/* generate dTD linked list for a request */ -static int req_to_dtd(struct mv_req *req) -{ - unsigned count; - int is_last, is_first = 1; - struct mv_dtd *dtd, *last_dtd = NULL; - dma_addr_t dma; - - do { - dtd = build_dtd(req, &count, &dma, &is_last); - if (dtd == NULL) - return -ENOMEM; - - if (is_first) { - is_first = 0; - req->head = dtd; - } else { - last_dtd->dtd_next = dma; - last_dtd->next_dtd_virt = dtd; - } - last_dtd = dtd; - req->dtd_count++; - } while (!is_last); - - /* set terminate bit to 1 for the last dTD */ - dtd->dtd_next = DTD_NEXT_TERMINATE; - - req->tail = dtd; - - return 0; -} - -static int mv_ep_enable(struct usb_ep *_ep, - const struct usb_endpoint_descriptor *desc) -{ - struct mv_udc *udc; - struct mv_ep *ep; - struct mv_dqh *dqh; - u16 max = 0; - u32 bit_pos, epctrlx, direction; - const unsigned char zlt = 1; - unsigned char ios, mult; - unsigned long flags; - - ep = container_of(_ep, struct mv_ep, ep); - udc = ep->udc; - - if (!_ep || !desc - || desc->bDescriptorType != USB_DT_ENDPOINT) - return -EINVAL; - - if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) - return -ESHUTDOWN; - - direction = ep_dir(ep); - max = usb_endpoint_maxp(desc); - - /* - * disable HW zero length termination select - * driver handles zero length packet through req->req.zero - */ - bit_pos = 1 << ((direction == EP_DIR_OUT ? 0 : 16) + ep->ep_num); - - /* Check if the Endpoint is Primed */ - if ((readl(&udc->op_regs->epprime) & bit_pos) - || (readl(&udc->op_regs->epstatus) & bit_pos)) { - dev_info(&udc->dev->dev, - "ep=%d %s: Init ERROR: ENDPTPRIME=0x%x," - " ENDPTSTATUS=0x%x, bit_pos=0x%x\n", - (unsigned)ep->ep_num, direction ? "SEND" : "RECV", - (unsigned)readl(&udc->op_regs->epprime), - (unsigned)readl(&udc->op_regs->epstatus), - (unsigned)bit_pos); - goto en_done; - } - - /* Set the max packet length, interrupt on Setup and Mult fields */ - ios = 0; - mult = 0; - switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { - case USB_ENDPOINT_XFER_BULK: - case USB_ENDPOINT_XFER_INT: - break; - case USB_ENDPOINT_XFER_CONTROL: - ios = 1; - break; - case USB_ENDPOINT_XFER_ISOC: - /* Calculate transactions needed for high bandwidth iso */ - mult = usb_endpoint_maxp_mult(desc); - /* 3 transactions at most */ - if (mult > 3) - goto en_done; - break; - default: - goto en_done; - } - - spin_lock_irqsave(&udc->lock, flags); - /* Get the endpoint queue head address */ - dqh = ep->dqh; - dqh->max_packet_length = (max << EP_QUEUE_HEAD_MAX_PKT_LEN_POS) - | (mult << EP_QUEUE_HEAD_MULT_POS) - | (zlt ? EP_QUEUE_HEAD_ZLT_SEL : 0) - | (ios ? EP_QUEUE_HEAD_IOS : 0); - dqh->next_dtd_ptr = 1; - dqh->size_ioc_int_sts = 0; - - ep->ep.maxpacket = max; - ep->ep.desc = desc; - ep->stopped = 0; - - /* Enable the endpoint for Rx or Tx and set the endpoint type */ - epctrlx = readl(&udc->op_regs->epctrlx[ep->ep_num]); - if (direction == EP_DIR_IN) { - epctrlx &= ~EPCTRL_TX_ALL_MASK; - epctrlx |= EPCTRL_TX_ENABLE | EPCTRL_TX_DATA_TOGGLE_RST - | ((desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) - << EPCTRL_TX_EP_TYPE_SHIFT); - } else { - epctrlx &= ~EPCTRL_RX_ALL_MASK; - epctrlx |= EPCTRL_RX_ENABLE | EPCTRL_RX_DATA_TOGGLE_RST - | ((desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) - << EPCTRL_RX_EP_TYPE_SHIFT); - } - writel(epctrlx, &udc->op_regs->epctrlx[ep->ep_num]); - - /* - * Implement Guideline (GL# USB-7) The unused endpoint type must - * be programmed to bulk. - */ - epctrlx = readl(&udc->op_regs->epctrlx[ep->ep_num]); - if ((epctrlx & EPCTRL_RX_ENABLE) == 0) { - epctrlx |= (USB_ENDPOINT_XFER_BULK - << EPCTRL_RX_EP_TYPE_SHIFT); - writel(epctrlx, &udc->op_regs->epctrlx[ep->ep_num]); - } - - epctrlx = readl(&udc->op_regs->epctrlx[ep->ep_num]); - if ((epctrlx & EPCTRL_TX_ENABLE) == 0) { - epctrlx |= (USB_ENDPOINT_XFER_BULK - << EPCTRL_TX_EP_TYPE_SHIFT); - writel(epctrlx, &udc->op_regs->epctrlx[ep->ep_num]); - } - - spin_unlock_irqrestore(&udc->lock, flags); - - return 0; -en_done: - return -EINVAL; -} - -static int mv_ep_disable(struct usb_ep *_ep) -{ - struct mv_udc *udc; - struct mv_ep *ep; - struct mv_dqh *dqh; - u32 epctrlx, direction; - unsigned long flags; - - ep = container_of(_ep, struct mv_ep, ep); - if ((_ep == NULL) || !ep->ep.desc) - return -EINVAL; - - udc = ep->udc; - - /* Get the endpoint queue head address */ - dqh = ep->dqh; - - spin_lock_irqsave(&udc->lock, flags); - - direction = ep_dir(ep); - - /* Reset the max packet length and the interrupt on Setup */ - dqh->max_packet_length = 0; - - /* Disable the endpoint for Rx or Tx and reset the endpoint type */ - epctrlx = readl(&udc->op_regs->epctrlx[ep->ep_num]); - epctrlx &= ~((direction == EP_DIR_IN) - ? (EPCTRL_TX_ENABLE | EPCTRL_TX_TYPE) - : (EPCTRL_RX_ENABLE | EPCTRL_RX_TYPE)); - writel(epctrlx, &udc->op_regs->epctrlx[ep->ep_num]); - - /* nuke all pending requests (does flush) */ - nuke(ep, -ESHUTDOWN); - - ep->ep.desc = NULL; - ep->stopped = 1; - - spin_unlock_irqrestore(&udc->lock, flags); - - return 0; -} - -static struct usb_request * -mv_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) -{ - struct mv_req *req; - - req = kzalloc(sizeof *req, gfp_flags); - if (!req) - return NULL; - - req->req.dma = DMA_ADDR_INVALID; - INIT_LIST_HEAD(&req->queue); - - return &req->req; -} - -static void mv_free_request(struct usb_ep *_ep, struct usb_request *_req) -{ - struct mv_req *req = NULL; - - req = container_of(_req, struct mv_req, req); - - if (_req) - kfree(req); -} - -static void mv_ep_fifo_flush(struct usb_ep *_ep) -{ - struct mv_udc *udc; - u32 bit_pos, direction; - struct mv_ep *ep; - unsigned int loops; - - if (!_ep) - return; - - ep = container_of(_ep, struct mv_ep, ep); - if (!ep->ep.desc) - return; - - udc = ep->udc; - direction = ep_dir(ep); - - if (ep->ep_num == 0) - bit_pos = (1 << 16) | 1; - else if (direction == EP_DIR_OUT) - bit_pos = 1 << ep->ep_num; - else - bit_pos = 1 << (16 + ep->ep_num); - - loops = LOOPS(EPSTATUS_TIMEOUT); - do { - unsigned int inter_loops; - - if (loops == 0) { - dev_err(&udc->dev->dev, - "TIMEOUT for ENDPTSTATUS=0x%x, bit_pos=0x%x\n", - (unsigned)readl(&udc->op_regs->epstatus), - (unsigned)bit_pos); - return; - } - /* Write 1 to the Flush register */ - writel(bit_pos, &udc->op_regs->epflush); - - /* Wait until flushing completed */ - inter_loops = LOOPS(FLUSH_TIMEOUT); - while (readl(&udc->op_regs->epflush)) { - /* - * ENDPTFLUSH bit should be cleared to indicate this - * operation is complete - */ - if (inter_loops == 0) { - dev_err(&udc->dev->dev, - "TIMEOUT for ENDPTFLUSH=0x%x," - "bit_pos=0x%x\n", - (unsigned)readl(&udc->op_regs->epflush), - (unsigned)bit_pos); - return; - } - inter_loops--; - udelay(LOOPS_USEC); - } - loops--; - } while (readl(&udc->op_regs->epstatus) & bit_pos); -} - -/* queues (submits) an I/O request to an endpoint */ -static int -mv_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) -{ - struct mv_ep *ep = container_of(_ep, struct mv_ep, ep); - struct mv_req *req = container_of(_req, struct mv_req, req); - struct mv_udc *udc = ep->udc; - unsigned long flags; - int retval; - - /* catch various bogus parameters */ - if (!_req || !req->req.complete || !req->req.buf - || !list_empty(&req->queue)) { - dev_err(&udc->dev->dev, "%s, bad params", __func__); - return -EINVAL; - } - if (unlikely(!_ep || !ep->ep.desc)) { - dev_err(&udc->dev->dev, "%s, bad ep", __func__); - return -EINVAL; - } - - udc = ep->udc; - if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) - return -ESHUTDOWN; - - req->ep = ep; - - /* map virtual address to hardware */ - retval = usb_gadget_map_request(&udc->gadget, _req, ep_dir(ep)); - if (retval) - return retval; - - req->req.status = -EINPROGRESS; - req->req.actual = 0; - req->dtd_count = 0; - - spin_lock_irqsave(&udc->lock, flags); - - /* build dtds and push them to device queue */ - if (!req_to_dtd(req)) { - retval = queue_dtd(ep, req); - if (retval) { - spin_unlock_irqrestore(&udc->lock, flags); - dev_err(&udc->dev->dev, "Failed to queue dtd\n"); - goto err_unmap_dma; - } - } else { - spin_unlock_irqrestore(&udc->lock, flags); - dev_err(&udc->dev->dev, "Failed to dma_pool_alloc\n"); - retval = -ENOMEM; - goto err_unmap_dma; - } - - /* Update ep0 state */ - if (ep->ep_num == 0) - udc->ep0_state = DATA_STATE_XMIT; - - /* irq handler advances the queue */ - list_add_tail(&req->queue, &ep->queue); - spin_unlock_irqrestore(&udc->lock, flags); - - return 0; - -err_unmap_dma: - usb_gadget_unmap_request(&udc->gadget, _req, ep_dir(ep)); - - return retval; -} - -static void mv_prime_ep(struct mv_ep *ep, struct mv_req *req) -{ - struct mv_dqh *dqh = ep->dqh; - u32 bit_pos; - - /* Write dQH next pointer and terminate bit to 0 */ - dqh->next_dtd_ptr = req->head->td_dma - & EP_QUEUE_HEAD_NEXT_POINTER_MASK; - - /* clear active and halt bit, in case set from a previous error */ - dqh->size_ioc_int_sts &= ~(DTD_STATUS_ACTIVE | DTD_STATUS_HALTED); - - /* Ensure that updates to the QH will occure before priming. */ - wmb(); - - bit_pos = 1 << (((ep_dir(ep) == EP_DIR_OUT) ? 0 : 16) + ep->ep_num); - - /* Prime the Endpoint */ - writel(bit_pos, &ep->udc->op_regs->epprime); -} - -/* dequeues (cancels, unlinks) an I/O request from an endpoint */ -static int mv_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) -{ - struct mv_ep *ep = container_of(_ep, struct mv_ep, ep); - struct mv_req *req = NULL, *iter; - struct mv_udc *udc = ep->udc; - unsigned long flags; - int stopped, ret = 0; - u32 epctrlx; - - if (!_ep || !_req) - return -EINVAL; - - spin_lock_irqsave(&ep->udc->lock, flags); - stopped = ep->stopped; - - /* Stop the ep before we deal with the queue */ - ep->stopped = 1; - epctrlx = readl(&udc->op_regs->epctrlx[ep->ep_num]); - if (ep_dir(ep) == EP_DIR_IN) - epctrlx &= ~EPCTRL_TX_ENABLE; - else - epctrlx &= ~EPCTRL_RX_ENABLE; - writel(epctrlx, &udc->op_regs->epctrlx[ep->ep_num]); - - /* make sure it's actually queued on this endpoint */ - list_for_each_entry(iter, &ep->queue, queue) { - if (&iter->req != _req) - continue; - req = iter; - break; - } - if (!req) { - ret = -EINVAL; - goto out; - } - - /* The request is in progress, or completed but not dequeued */ - if (ep->queue.next == &req->queue) { - _req->status = -ECONNRESET; - mv_ep_fifo_flush(_ep); /* flush current transfer */ - - /* The request isn't the last request in this ep queue */ - if (req->queue.next != &ep->queue) { - struct mv_req *next_req; - - next_req = list_entry(req->queue.next, - struct mv_req, queue); - - /* Point the QH to the first TD of next request */ - mv_prime_ep(ep, next_req); - } else { - struct mv_dqh *qh; - - qh = ep->dqh; - qh->next_dtd_ptr = 1; - qh->size_ioc_int_sts = 0; - } - - /* The request hasn't been processed, patch up the TD chain */ - } else { - struct mv_req *prev_req; - - prev_req = list_entry(req->queue.prev, struct mv_req, queue); - writel(readl(&req->tail->dtd_next), - &prev_req->tail->dtd_next); - - } - - done(ep, req, -ECONNRESET); - - /* Enable EP */ -out: - epctrlx = readl(&udc->op_regs->epctrlx[ep->ep_num]); - if (ep_dir(ep) == EP_DIR_IN) - epctrlx |= EPCTRL_TX_ENABLE; - else - epctrlx |= EPCTRL_RX_ENABLE; - writel(epctrlx, &udc->op_regs->epctrlx[ep->ep_num]); - ep->stopped = stopped; - - spin_unlock_irqrestore(&ep->udc->lock, flags); - return ret; -} - -static void ep_set_stall(struct mv_udc *udc, u8 ep_num, u8 direction, int stall) -{ - u32 epctrlx; - - epctrlx = readl(&udc->op_regs->epctrlx[ep_num]); - - if (stall) { - if (direction == EP_DIR_IN) - epctrlx |= EPCTRL_TX_EP_STALL; - else - epctrlx |= EPCTRL_RX_EP_STALL; - } else { - if (direction == EP_DIR_IN) { - epctrlx &= ~EPCTRL_TX_EP_STALL; - epctrlx |= EPCTRL_TX_DATA_TOGGLE_RST; - } else { - epctrlx &= ~EPCTRL_RX_EP_STALL; - epctrlx |= EPCTRL_RX_DATA_TOGGLE_RST; - } - } - writel(epctrlx, &udc->op_regs->epctrlx[ep_num]); -} - -static int ep_is_stall(struct mv_udc *udc, u8 ep_num, u8 direction) -{ - u32 epctrlx; - - epctrlx = readl(&udc->op_regs->epctrlx[ep_num]); - - if (direction == EP_DIR_OUT) - return (epctrlx & EPCTRL_RX_EP_STALL) ? 1 : 0; - else - return (epctrlx & EPCTRL_TX_EP_STALL) ? 1 : 0; -} - -static int mv_ep_set_halt_wedge(struct usb_ep *_ep, int halt, int wedge) -{ - struct mv_ep *ep; - unsigned long flags; - int status = 0; - struct mv_udc *udc; - - ep = container_of(_ep, struct mv_ep, ep); - udc = ep->udc; - if (!_ep || !ep->ep.desc) { - status = -EINVAL; - goto out; - } - - if (ep->ep.desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { - status = -EOPNOTSUPP; - goto out; - } - - /* - * Attempt to halt IN ep will fail if any transfer requests - * are still queue - */ - if (halt && (ep_dir(ep) == EP_DIR_IN) && !list_empty(&ep->queue)) { - status = -EAGAIN; - goto out; - } - - spin_lock_irqsave(&ep->udc->lock, flags); - ep_set_stall(udc, ep->ep_num, ep_dir(ep), halt); - if (halt && wedge) - ep->wedge = 1; - else if (!halt) - ep->wedge = 0; - spin_unlock_irqrestore(&ep->udc->lock, flags); - - if (ep->ep_num == 0) { - udc->ep0_state = WAIT_FOR_SETUP; - udc->ep0_dir = EP_DIR_OUT; - } -out: - return status; -} - -static int mv_ep_set_halt(struct usb_ep *_ep, int halt) -{ - return mv_ep_set_halt_wedge(_ep, halt, 0); -} - -static int mv_ep_set_wedge(struct usb_ep *_ep) -{ - return mv_ep_set_halt_wedge(_ep, 1, 1); -} - -static const struct usb_ep_ops mv_ep_ops = { - .enable = mv_ep_enable, - .disable = mv_ep_disable, - - .alloc_request = mv_alloc_request, - .free_request = mv_free_request, - - .queue = mv_ep_queue, - .dequeue = mv_ep_dequeue, - - .set_wedge = mv_ep_set_wedge, - .set_halt = mv_ep_set_halt, - .fifo_flush = mv_ep_fifo_flush, /* flush fifo */ -}; - -static int udc_clock_enable(struct mv_udc *udc) -{ - return clk_prepare_enable(udc->clk); -} - -static void udc_clock_disable(struct mv_udc *udc) -{ - clk_disable_unprepare(udc->clk); -} - -static void udc_stop(struct mv_udc *udc) -{ - u32 tmp; - - /* Disable interrupts */ - tmp = readl(&udc->op_regs->usbintr); - tmp &= ~(USBINTR_INT_EN | USBINTR_ERR_INT_EN | - USBINTR_PORT_CHANGE_DETECT_EN | USBINTR_RESET_EN); - writel(tmp, &udc->op_regs->usbintr); - - udc->stopped = 1; - - /* Reset the Run the bit in the command register to stop VUSB */ - tmp = readl(&udc->op_regs->usbcmd); - tmp &= ~USBCMD_RUN_STOP; - writel(tmp, &udc->op_regs->usbcmd); -} - -static void udc_start(struct mv_udc *udc) -{ - u32 usbintr; - - usbintr = USBINTR_INT_EN | USBINTR_ERR_INT_EN - | USBINTR_PORT_CHANGE_DETECT_EN - | USBINTR_RESET_EN | USBINTR_DEVICE_SUSPEND; - /* Enable interrupts */ - writel(usbintr, &udc->op_regs->usbintr); - - udc->stopped = 0; - - /* Set the Run bit in the command register */ - writel(USBCMD_RUN_STOP, &udc->op_regs->usbcmd); -} - -static int udc_reset(struct mv_udc *udc) -{ - unsigned int loops; - u32 tmp, portsc; - - /* Stop the controller */ - tmp = readl(&udc->op_regs->usbcmd); - tmp &= ~USBCMD_RUN_STOP; - writel(tmp, &udc->op_regs->usbcmd); - - /* Reset the controller to get default values */ - writel(USBCMD_CTRL_RESET, &udc->op_regs->usbcmd); - - /* wait for reset to complete */ - loops = LOOPS(RESET_TIMEOUT); - while (readl(&udc->op_regs->usbcmd) & USBCMD_CTRL_RESET) { - if (loops == 0) { - dev_err(&udc->dev->dev, - "Wait for RESET completed TIMEOUT\n"); - return -ETIMEDOUT; - } - loops--; - udelay(LOOPS_USEC); - } - - /* set controller to device mode */ - tmp = readl(&udc->op_regs->usbmode); - tmp |= USBMODE_CTRL_MODE_DEVICE; - - /* turn setup lockout off, require setup tripwire in usbcmd */ - tmp |= USBMODE_SETUP_LOCK_OFF; - - writel(tmp, &udc->op_regs->usbmode); - - writel(0x0, &udc->op_regs->epsetupstat); - - /* Configure the Endpoint List Address */ - writel(udc->ep_dqh_dma & USB_EP_LIST_ADDRESS_MASK, - &udc->op_regs->eplistaddr); - - portsc = readl(&udc->op_regs->portsc[0]); - if (readl(&udc->cap_regs->hcsparams) & HCSPARAMS_PPC) - portsc &= (~PORTSCX_W1C_BITS | ~PORTSCX_PORT_POWER); - - if (udc->force_fs) - portsc |= PORTSCX_FORCE_FULL_SPEED_CONNECT; - else - portsc &= (~PORTSCX_FORCE_FULL_SPEED_CONNECT); - - writel(portsc, &udc->op_regs->portsc[0]); - - tmp = readl(&udc->op_regs->epctrlx[0]); - tmp &= ~(EPCTRL_TX_EP_STALL | EPCTRL_RX_EP_STALL); - writel(tmp, &udc->op_regs->epctrlx[0]); - - return 0; -} - -static int mv_udc_enable_internal(struct mv_udc *udc) -{ - int retval; - - if (udc->active) - return 0; - - dev_dbg(&udc->dev->dev, "enable udc\n"); - retval = udc_clock_enable(udc); - if (retval) - return retval; - - if (udc->pdata->phy_init) { - retval = udc->pdata->phy_init(udc->phy_regs); - if (retval) { - dev_err(&udc->dev->dev, - "init phy error %d\n", retval); - udc_clock_disable(udc); - return retval; - } - } - udc->active = 1; - - return 0; -} - -static int mv_udc_enable(struct mv_udc *udc) -{ - if (udc->clock_gating) - return mv_udc_enable_internal(udc); - - return 0; -} - -static void mv_udc_disable_internal(struct mv_udc *udc) -{ - if (udc->active) { - dev_dbg(&udc->dev->dev, "disable udc\n"); - if (udc->pdata->phy_deinit) - udc->pdata->phy_deinit(udc->phy_regs); - udc_clock_disable(udc); - udc->active = 0; - } -} - -static void mv_udc_disable(struct mv_udc *udc) -{ - if (udc->clock_gating) - mv_udc_disable_internal(udc); -} - -static int mv_udc_get_frame(struct usb_gadget *gadget) -{ - struct mv_udc *udc; - u16 retval; - - if (!gadget) - return -ENODEV; - - udc = container_of(gadget, struct mv_udc, gadget); - - retval = readl(&udc->op_regs->frindex) & USB_FRINDEX_MASKS; - - return retval; -} - -/* Tries to wake up the host connected to this gadget */ -static int mv_udc_wakeup(struct usb_gadget *gadget) -{ - struct mv_udc *udc = container_of(gadget, struct mv_udc, gadget); - u32 portsc; - - /* Remote wakeup feature not enabled by host */ - if (!udc->remote_wakeup) - return -ENOTSUPP; - - portsc = readl(&udc->op_regs->portsc); - /* not suspended? */ - if (!(portsc & PORTSCX_PORT_SUSPEND)) - return 0; - /* trigger force resume */ - portsc |= PORTSCX_PORT_FORCE_RESUME; - writel(portsc, &udc->op_regs->portsc[0]); - return 0; -} - -static int mv_udc_vbus_session(struct usb_gadget *gadget, int is_active) -{ - struct mv_udc *udc; - unsigned long flags; - int retval = 0; - - udc = container_of(gadget, struct mv_udc, gadget); - spin_lock_irqsave(&udc->lock, flags); - - udc->vbus_active = (is_active != 0); - - dev_dbg(&udc->dev->dev, "%s: softconnect %d, vbus_active %d\n", - __func__, udc->softconnect, udc->vbus_active); - - if (udc->driver && udc->softconnect && udc->vbus_active) { - retval = mv_udc_enable(udc); - if (retval == 0) { - /* Clock is disabled, need re-init registers */ - udc_reset(udc); - ep0_reset(udc); - udc_start(udc); - } - } else if (udc->driver && udc->softconnect) { - if (!udc->active) - goto out; - - /* stop all the transfer in queue*/ - stop_activity(udc, udc->driver); - udc_stop(udc); - mv_udc_disable(udc); - } - -out: - spin_unlock_irqrestore(&udc->lock, flags); - return retval; -} - -static int mv_udc_pullup(struct usb_gadget *gadget, int is_on) -{ - struct mv_udc *udc; - unsigned long flags; - int retval = 0; - - udc = container_of(gadget, struct mv_udc, gadget); - spin_lock_irqsave(&udc->lock, flags); - - udc->softconnect = (is_on != 0); - - dev_dbg(&udc->dev->dev, "%s: softconnect %d, vbus_active %d\n", - __func__, udc->softconnect, udc->vbus_active); - - if (udc->driver && udc->softconnect && udc->vbus_active) { - retval = mv_udc_enable(udc); - if (retval == 0) { - /* Clock is disabled, need re-init registers */ - udc_reset(udc); - ep0_reset(udc); - udc_start(udc); - } - } else if (udc->driver && udc->vbus_active) { - /* stop all the transfer in queue*/ - stop_activity(udc, udc->driver); - udc_stop(udc); - mv_udc_disable(udc); - } - - spin_unlock_irqrestore(&udc->lock, flags); - return retval; -} - -static int mv_udc_start(struct usb_gadget *, struct usb_gadget_driver *); -static int mv_udc_stop(struct usb_gadget *); -/* device controller usb_gadget_ops structure */ -static const struct usb_gadget_ops mv_ops = { - - /* returns the current frame number */ - .get_frame = mv_udc_get_frame, - - /* tries to wake up the host connected to this gadget */ - .wakeup = mv_udc_wakeup, - - /* notify controller that VBUS is powered or not */ - .vbus_session = mv_udc_vbus_session, - - /* D+ pullup, software-controlled connect/disconnect to USB host */ - .pullup = mv_udc_pullup, - .udc_start = mv_udc_start, - .udc_stop = mv_udc_stop, -}; - -static int eps_init(struct mv_udc *udc) -{ - struct mv_ep *ep; - char name[14]; - int i; - - /* initialize ep0 */ - ep = &udc->eps[0]; - ep->udc = udc; - strncpy(ep->name, "ep0", sizeof(ep->name)); - ep->ep.name = ep->name; - ep->ep.ops = &mv_ep_ops; - ep->wedge = 0; - ep->stopped = 0; - usb_ep_set_maxpacket_limit(&ep->ep, EP0_MAX_PKT_SIZE); - ep->ep.caps.type_control = true; - ep->ep.caps.dir_in = true; - ep->ep.caps.dir_out = true; - ep->ep_num = 0; - ep->ep.desc = &mv_ep0_desc; - INIT_LIST_HEAD(&ep->queue); - - ep->ep_type = USB_ENDPOINT_XFER_CONTROL; - - /* initialize other endpoints */ - for (i = 2; i < udc->max_eps * 2; i++) { - ep = &udc->eps[i]; - if (i % 2) { - snprintf(name, sizeof(name), "ep%din", i / 2); - ep->direction = EP_DIR_IN; - ep->ep.caps.dir_in = true; - } else { - snprintf(name, sizeof(name), "ep%dout", i / 2); - ep->direction = EP_DIR_OUT; - ep->ep.caps.dir_out = true; - } - ep->udc = udc; - strncpy(ep->name, name, sizeof(ep->name)); - ep->ep.name = ep->name; - - ep->ep.caps.type_iso = true; - ep->ep.caps.type_bulk = true; - ep->ep.caps.type_int = true; - - ep->ep.ops = &mv_ep_ops; - ep->stopped = 0; - usb_ep_set_maxpacket_limit(&ep->ep, (unsigned short) ~0); - ep->ep_num = i / 2; - - INIT_LIST_HEAD(&ep->queue); - list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); - - ep->dqh = &udc->ep_dqh[i]; - } - - return 0; -} - -/* delete all endpoint requests, called with spinlock held */ -static void nuke(struct mv_ep *ep, int status) -{ - /* called with spinlock held */ - ep->stopped = 1; - - /* endpoint fifo flush */ - mv_ep_fifo_flush(&ep->ep); - - while (!list_empty(&ep->queue)) { - struct mv_req *req = NULL; - req = list_entry(ep->queue.next, struct mv_req, queue); - done(ep, req, status); - } -} - -static void gadget_reset(struct mv_udc *udc, struct usb_gadget_driver *driver) -{ - struct mv_ep *ep; - - nuke(&udc->eps[0], -ESHUTDOWN); - - list_for_each_entry(ep, &udc->gadget.ep_list, ep.ep_list) { - nuke(ep, -ESHUTDOWN); - } - - /* report reset; the driver is already quiesced */ - if (driver) { - spin_unlock(&udc->lock); - usb_gadget_udc_reset(&udc->gadget, driver); - spin_lock(&udc->lock); - } -} -/* stop all USB activities */ -static void stop_activity(struct mv_udc *udc, struct usb_gadget_driver *driver) -{ - struct mv_ep *ep; - - nuke(&udc->eps[0], -ESHUTDOWN); - - list_for_each_entry(ep, &udc->gadget.ep_list, ep.ep_list) { - nuke(ep, -ESHUTDOWN); - } - - /* report disconnect; the driver is already quiesced */ - if (driver) { - spin_unlock(&udc->lock); - driver->disconnect(&udc->gadget); - spin_lock(&udc->lock); - } -} - -static int mv_udc_start(struct usb_gadget *gadget, - struct usb_gadget_driver *driver) -{ - struct mv_udc *udc; - int retval = 0; - unsigned long flags; - - udc = container_of(gadget, struct mv_udc, gadget); - - if (udc->driver) - return -EBUSY; - - spin_lock_irqsave(&udc->lock, flags); - - /* hook up the driver ... */ - udc->driver = driver; - - udc->usb_state = USB_STATE_ATTACHED; - udc->ep0_state = WAIT_FOR_SETUP; - udc->ep0_dir = EP_DIR_OUT; - - spin_unlock_irqrestore(&udc->lock, flags); - - if (udc->transceiver) { - retval = otg_set_peripheral(udc->transceiver->otg, - &udc->gadget); - if (retval) { - dev_err(&udc->dev->dev, - "unable to register peripheral to otg\n"); - udc->driver = NULL; - return retval; - } - } - - /* When boot with cable attached, there will be no vbus irq occurred */ - if (udc->qwork) - queue_work(udc->qwork, &udc->vbus_work); - - return 0; -} - -static int mv_udc_stop(struct usb_gadget *gadget) -{ - struct mv_udc *udc; - unsigned long flags; - - udc = container_of(gadget, struct mv_udc, gadget); - - spin_lock_irqsave(&udc->lock, flags); - - mv_udc_enable(udc); - udc_stop(udc); - - /* stop all usb activities */ - udc->gadget.speed = USB_SPEED_UNKNOWN; - stop_activity(udc, NULL); - mv_udc_disable(udc); - - spin_unlock_irqrestore(&udc->lock, flags); - - /* unbind gadget driver */ - udc->driver = NULL; - - return 0; -} - -static void mv_set_ptc(struct mv_udc *udc, u32 mode) -{ - u32 portsc; - - portsc = readl(&udc->op_regs->portsc[0]); - portsc |= mode << 16; - writel(portsc, &udc->op_regs->portsc[0]); -} - -static void prime_status_complete(struct usb_ep *ep, struct usb_request *_req) -{ - struct mv_ep *mvep = container_of(ep, struct mv_ep, ep); - struct mv_req *req = container_of(_req, struct mv_req, req); - struct mv_udc *udc; - unsigned long flags; - - udc = mvep->udc; - - dev_info(&udc->dev->dev, "switch to test mode %d\n", req->test_mode); - - spin_lock_irqsave(&udc->lock, flags); - if (req->test_mode) { - mv_set_ptc(udc, req->test_mode); - req->test_mode = 0; - } - spin_unlock_irqrestore(&udc->lock, flags); -} - -static int -udc_prime_status(struct mv_udc *udc, u8 direction, u16 status, bool empty) -{ - int retval = 0; - struct mv_req *req; - struct mv_ep *ep; - - ep = &udc->eps[0]; - udc->ep0_dir = direction; - udc->ep0_state = WAIT_FOR_OUT_STATUS; - - req = udc->status_req; - - /* fill in the request structure */ - if (empty == false) { - *((u16 *) req->req.buf) = cpu_to_le16(status); - req->req.length = 2; - } else - req->req.length = 0; - - req->ep = ep; - req->req.status = -EINPROGRESS; - req->req.actual = 0; - if (udc->test_mode) { - req->req.complete = prime_status_complete; - req->test_mode = udc->test_mode; - udc->test_mode = 0; - } else - req->req.complete = NULL; - req->dtd_count = 0; - - if (req->req.dma == DMA_ADDR_INVALID) { - req->req.dma = dma_map_single(ep->udc->gadget.dev.parent, - req->req.buf, req->req.length, - ep_dir(ep) ? DMA_TO_DEVICE : DMA_FROM_DEVICE); - req->mapped = 1; - } - - /* prime the data phase */ - if (!req_to_dtd(req)) { - retval = queue_dtd(ep, req); - if (retval) { - dev_err(&udc->dev->dev, - "Failed to queue dtd when prime status\n"); - goto out; - } - } else{ /* no mem */ - retval = -ENOMEM; - dev_err(&udc->dev->dev, - "Failed to dma_pool_alloc when prime status\n"); - goto out; - } - - list_add_tail(&req->queue, &ep->queue); - - return 0; -out: - usb_gadget_unmap_request(&udc->gadget, &req->req, ep_dir(ep)); - - return retval; -} - -static void mv_udc_testmode(struct mv_udc *udc, u16 index) -{ - if (index <= USB_TEST_FORCE_ENABLE) { - udc->test_mode = index; - if (udc_prime_status(udc, EP_DIR_IN, 0, true)) - ep0_stall(udc); - } else - dev_err(&udc->dev->dev, - "This test mode(%d) is not supported\n", index); -} - -static void ch9setaddress(struct mv_udc *udc, struct usb_ctrlrequest *setup) -{ - udc->dev_addr = (u8)setup->wValue; - - /* update usb state */ - udc->usb_state = USB_STATE_ADDRESS; - - if (udc_prime_status(udc, EP_DIR_IN, 0, true)) - ep0_stall(udc); -} - -static void ch9getstatus(struct mv_udc *udc, u8 ep_num, - struct usb_ctrlrequest *setup) -{ - u16 status = 0; - int retval; - - if ((setup->bRequestType & (USB_DIR_IN | USB_TYPE_MASK)) - != (USB_DIR_IN | USB_TYPE_STANDARD)) - return; - - if ((setup->bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) { - status = 1 << USB_DEVICE_SELF_POWERED; - status |= udc->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP; - } else if ((setup->bRequestType & USB_RECIP_MASK) - == USB_RECIP_INTERFACE) { - /* get interface status */ - status = 0; - } else if ((setup->bRequestType & USB_RECIP_MASK) - == USB_RECIP_ENDPOINT) { - u8 ep_num, direction; - - ep_num = setup->wIndex & USB_ENDPOINT_NUMBER_MASK; - direction = (setup->wIndex & USB_ENDPOINT_DIR_MASK) - ? EP_DIR_IN : EP_DIR_OUT; - status = ep_is_stall(udc, ep_num, direction) - << USB_ENDPOINT_HALT; - } - - retval = udc_prime_status(udc, EP_DIR_IN, status, false); - if (retval) - ep0_stall(udc); - else - udc->ep0_state = DATA_STATE_XMIT; -} - -static void ch9clearfeature(struct mv_udc *udc, struct usb_ctrlrequest *setup) -{ - u8 ep_num; - u8 direction; - struct mv_ep *ep; - - if ((setup->bRequestType & (USB_TYPE_MASK | USB_RECIP_MASK)) - == ((USB_TYPE_STANDARD | USB_RECIP_DEVICE))) { - switch (setup->wValue) { - case USB_DEVICE_REMOTE_WAKEUP: - udc->remote_wakeup = 0; - break; - default: - goto out; - } - } else if ((setup->bRequestType & (USB_TYPE_MASK | USB_RECIP_MASK)) - == ((USB_TYPE_STANDARD | USB_RECIP_ENDPOINT))) { - switch (setup->wValue) { - case USB_ENDPOINT_HALT: - ep_num = setup->wIndex & USB_ENDPOINT_NUMBER_MASK; - direction = (setup->wIndex & USB_ENDPOINT_DIR_MASK) - ? EP_DIR_IN : EP_DIR_OUT; - if (setup->wValue != 0 || setup->wLength != 0 - || ep_num > udc->max_eps) - goto out; - ep = &udc->eps[ep_num * 2 + direction]; - if (ep->wedge == 1) - break; - spin_unlock(&udc->lock); - ep_set_stall(udc, ep_num, direction, 0); - spin_lock(&udc->lock); - break; - default: - goto out; - } - } else - goto out; - - if (udc_prime_status(udc, EP_DIR_IN, 0, true)) - ep0_stall(udc); -out: - return; -} - -static void ch9setfeature(struct mv_udc *udc, struct usb_ctrlrequest *setup) -{ - u8 ep_num; - u8 direction; - - if ((setup->bRequestType & (USB_TYPE_MASK | USB_RECIP_MASK)) - == ((USB_TYPE_STANDARD | USB_RECIP_DEVICE))) { - switch (setup->wValue) { - case USB_DEVICE_REMOTE_WAKEUP: - udc->remote_wakeup = 1; - break; - case USB_DEVICE_TEST_MODE: - if (setup->wIndex & 0xFF - || udc->gadget.speed != USB_SPEED_HIGH) - ep0_stall(udc); - - if (udc->usb_state != USB_STATE_CONFIGURED - && udc->usb_state != USB_STATE_ADDRESS - && udc->usb_state != USB_STATE_DEFAULT) - ep0_stall(udc); - - mv_udc_testmode(udc, (setup->wIndex >> 8)); - goto out; - default: - goto out; - } - } else if ((setup->bRequestType & (USB_TYPE_MASK | USB_RECIP_MASK)) - == ((USB_TYPE_STANDARD | USB_RECIP_ENDPOINT))) { - switch (setup->wValue) { - case USB_ENDPOINT_HALT: - ep_num = setup->wIndex & USB_ENDPOINT_NUMBER_MASK; - direction = (setup->wIndex & USB_ENDPOINT_DIR_MASK) - ? EP_DIR_IN : EP_DIR_OUT; - if (setup->wValue != 0 || setup->wLength != 0 - || ep_num > udc->max_eps) - goto out; - spin_unlock(&udc->lock); - ep_set_stall(udc, ep_num, direction, 1); - spin_lock(&udc->lock); - break; - default: - goto out; - } - } else - goto out; - - if (udc_prime_status(udc, EP_DIR_IN, 0, true)) - ep0_stall(udc); -out: - return; -} - -static void handle_setup_packet(struct mv_udc *udc, u8 ep_num, - struct usb_ctrlrequest *setup) - __releases(&ep->udc->lock) - __acquires(&ep->udc->lock) -{ - bool delegate = false; - - nuke(&udc->eps[ep_num * 2 + EP_DIR_OUT], -ESHUTDOWN); - - dev_dbg(&udc->dev->dev, "SETUP %02x.%02x v%04x i%04x l%04x\n", - setup->bRequestType, setup->bRequest, - setup->wValue, setup->wIndex, setup->wLength); - /* We process some standard setup requests here */ - if ((setup->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { - switch (setup->bRequest) { - case USB_REQ_GET_STATUS: - ch9getstatus(udc, ep_num, setup); - break; - - case USB_REQ_SET_ADDRESS: - ch9setaddress(udc, setup); - break; - - case USB_REQ_CLEAR_FEATURE: - ch9clearfeature(udc, setup); - break; - - case USB_REQ_SET_FEATURE: - ch9setfeature(udc, setup); - break; - - default: - delegate = true; - } - } else - delegate = true; - - /* delegate USB standard requests to the gadget driver */ - if (delegate == true) { - /* USB requests handled by gadget */ - if (setup->wLength) { - /* DATA phase from gadget, STATUS phase from udc */ - udc->ep0_dir = (setup->bRequestType & USB_DIR_IN) - ? EP_DIR_IN : EP_DIR_OUT; - spin_unlock(&udc->lock); - if (udc->driver->setup(&udc->gadget, - &udc->local_setup_buff) < 0) - ep0_stall(udc); - spin_lock(&udc->lock); - udc->ep0_state = (setup->bRequestType & USB_DIR_IN) - ? DATA_STATE_XMIT : DATA_STATE_RECV; - } else { - /* no DATA phase, IN STATUS phase from gadget */ - udc->ep0_dir = EP_DIR_IN; - spin_unlock(&udc->lock); - if (udc->driver->setup(&udc->gadget, - &udc->local_setup_buff) < 0) - ep0_stall(udc); - spin_lock(&udc->lock); - udc->ep0_state = WAIT_FOR_OUT_STATUS; - } - } -} - -/* complete DATA or STATUS phase of ep0 prime status phase if needed */ -static void ep0_req_complete(struct mv_udc *udc, - struct mv_ep *ep0, struct mv_req *req) -{ - u32 new_addr; - - if (udc->usb_state == USB_STATE_ADDRESS) { - /* set the new address */ - new_addr = (u32)udc->dev_addr; - writel(new_addr << USB_DEVICE_ADDRESS_BIT_SHIFT, - &udc->op_regs->deviceaddr); - } - - done(ep0, req, 0); - - switch (udc->ep0_state) { - case DATA_STATE_XMIT: - /* receive status phase */ - if (udc_prime_status(udc, EP_DIR_OUT, 0, true)) - ep0_stall(udc); - break; - case DATA_STATE_RECV: - /* send status phase */ - if (udc_prime_status(udc, EP_DIR_IN, 0 , true)) - ep0_stall(udc); - break; - case WAIT_FOR_OUT_STATUS: - udc->ep0_state = WAIT_FOR_SETUP; - break; - case WAIT_FOR_SETUP: - dev_err(&udc->dev->dev, "unexpect ep0 packets\n"); - break; - default: - ep0_stall(udc); - break; - } -} - -static void get_setup_data(struct mv_udc *udc, u8 ep_num, u8 *buffer_ptr) -{ - u32 temp; - struct mv_dqh *dqh; - - dqh = &udc->ep_dqh[ep_num * 2 + EP_DIR_OUT]; - - /* Clear bit in ENDPTSETUPSTAT */ - writel((1 << ep_num), &udc->op_regs->epsetupstat); - - /* while a hazard exists when setup package arrives */ - do { - /* Set Setup Tripwire */ - temp = readl(&udc->op_regs->usbcmd); - writel(temp | USBCMD_SETUP_TRIPWIRE_SET, &udc->op_regs->usbcmd); - - /* Copy the setup packet to local buffer */ - memcpy(buffer_ptr, (u8 *) dqh->setup_buffer, 8); - } while (!(readl(&udc->op_regs->usbcmd) & USBCMD_SETUP_TRIPWIRE_SET)); - - /* Clear Setup Tripwire */ - temp = readl(&udc->op_regs->usbcmd); - writel(temp & ~USBCMD_SETUP_TRIPWIRE_SET, &udc->op_regs->usbcmd); -} - -static void irq_process_tr_complete(struct mv_udc *udc) -{ - u32 tmp, bit_pos; - int i, ep_num = 0, direction = 0; - struct mv_ep *curr_ep; - struct mv_req *curr_req, *temp_req; - int status; - - /* - * We use separate loops for ENDPTSETUPSTAT and ENDPTCOMPLETE - * because the setup packets are to be read ASAP - */ - - /* Process all Setup packet received interrupts */ - tmp = readl(&udc->op_regs->epsetupstat); - - if (tmp) { - for (i = 0; i < udc->max_eps; i++) { - if (tmp & (1 << i)) { - get_setup_data(udc, i, - (u8 *)(&udc->local_setup_buff)); - handle_setup_packet(udc, i, - &udc->local_setup_buff); - } - } - } - - /* Don't clear the endpoint setup status register here. - * It is cleared as a setup packet is read out of the buffer - */ - - /* Process non-setup transaction complete interrupts */ - tmp = readl(&udc->op_regs->epcomplete); - - if (!tmp) - return; - - writel(tmp, &udc->op_regs->epcomplete); - - for (i = 0; i < udc->max_eps * 2; i++) { - ep_num = i >> 1; - direction = i % 2; - - bit_pos = 1 << (ep_num + 16 * direction); - - if (!(bit_pos & tmp)) - continue; - - if (i == 1) - curr_ep = &udc->eps[0]; - else - curr_ep = &udc->eps[i]; - /* process the req queue until an uncomplete request */ - list_for_each_entry_safe(curr_req, temp_req, - &curr_ep->queue, queue) { - status = process_ep_req(udc, i, curr_req); - if (status) - break; - - /* write back status to req */ - curr_req->req.status = status; - - /* ep0 request completion */ - if (ep_num == 0) { - ep0_req_complete(udc, curr_ep, curr_req); - break; - } else { - done(curr_ep, curr_req, status); - } - } - } -} - -static void irq_process_reset(struct mv_udc *udc) -{ - u32 tmp; - unsigned int loops; - - udc->ep0_dir = EP_DIR_OUT; - udc->ep0_state = WAIT_FOR_SETUP; - udc->remote_wakeup = 0; /* default to 0 on reset */ - - /* The address bits are past bit 25-31. Set the address */ - tmp = readl(&udc->op_regs->deviceaddr); - tmp &= ~(USB_DEVICE_ADDRESS_MASK); - writel(tmp, &udc->op_regs->deviceaddr); - - /* Clear all the setup token semaphores */ - tmp = readl(&udc->op_regs->epsetupstat); - writel(tmp, &udc->op_regs->epsetupstat); - - /* Clear all the endpoint complete status bits */ - tmp = readl(&udc->op_regs->epcomplete); - writel(tmp, &udc->op_regs->epcomplete); - - /* wait until all endptprime bits cleared */ - loops = LOOPS(PRIME_TIMEOUT); - while (readl(&udc->op_regs->epprime) & 0xFFFFFFFF) { - if (loops == 0) { - dev_err(&udc->dev->dev, - "Timeout for ENDPTPRIME = 0x%x\n", - readl(&udc->op_regs->epprime)); - break; - } - loops--; - udelay(LOOPS_USEC); - } - - /* Write 1s to the Flush register */ - writel((u32)~0, &udc->op_regs->epflush); - - if (readl(&udc->op_regs->portsc[0]) & PORTSCX_PORT_RESET) { - dev_info(&udc->dev->dev, "usb bus reset\n"); - udc->usb_state = USB_STATE_DEFAULT; - /* reset all the queues, stop all USB activities */ - gadget_reset(udc, udc->driver); - } else { - dev_info(&udc->dev->dev, "USB reset portsc 0x%x\n", - readl(&udc->op_regs->portsc)); - - /* - * re-initialize - * controller reset - */ - udc_reset(udc); - - /* reset all the queues, stop all USB activities */ - stop_activity(udc, udc->driver); - - /* reset ep0 dQH and endptctrl */ - ep0_reset(udc); - - /* enable interrupt and set controller to run state */ - udc_start(udc); - - udc->usb_state = USB_STATE_ATTACHED; - } -} - -static void handle_bus_resume(struct mv_udc *udc) -{ - udc->usb_state = udc->resume_state; - udc->resume_state = 0; - - /* report resume to the driver */ - if (udc->driver) { - if (udc->driver->resume) { - spin_unlock(&udc->lock); - udc->driver->resume(&udc->gadget); - spin_lock(&udc->lock); - } - } -} - -static void irq_process_suspend(struct mv_udc *udc) -{ - udc->resume_state = udc->usb_state; - udc->usb_state = USB_STATE_SUSPENDED; - - if (udc->driver->suspend) { - spin_unlock(&udc->lock); - udc->driver->suspend(&udc->gadget); - spin_lock(&udc->lock); - } -} - -static void irq_process_port_change(struct mv_udc *udc) -{ - u32 portsc; - - portsc = readl(&udc->op_regs->portsc[0]); - if (!(portsc & PORTSCX_PORT_RESET)) { - /* Get the speed */ - u32 speed = portsc & PORTSCX_PORT_SPEED_MASK; - switch (speed) { - case PORTSCX_PORT_SPEED_HIGH: - udc->gadget.speed = USB_SPEED_HIGH; - break; - case PORTSCX_PORT_SPEED_FULL: - udc->gadget.speed = USB_SPEED_FULL; - break; - case PORTSCX_PORT_SPEED_LOW: - udc->gadget.speed = USB_SPEED_LOW; - break; - default: - udc->gadget.speed = USB_SPEED_UNKNOWN; - break; - } - } - - if (portsc & PORTSCX_PORT_SUSPEND) { - udc->resume_state = udc->usb_state; - udc->usb_state = USB_STATE_SUSPENDED; - if (udc->driver->suspend) { - spin_unlock(&udc->lock); - udc->driver->suspend(&udc->gadget); - spin_lock(&udc->lock); - } - } - - if (!(portsc & PORTSCX_PORT_SUSPEND) - && udc->usb_state == USB_STATE_SUSPENDED) { - handle_bus_resume(udc); - } - - if (!udc->resume_state) - udc->usb_state = USB_STATE_DEFAULT; -} - -static void irq_process_error(struct mv_udc *udc) -{ - /* Increment the error count */ - udc->errors++; -} - -static irqreturn_t mv_udc_irq(int irq, void *dev) -{ - struct mv_udc *udc = (struct mv_udc *)dev; - u32 status, intr; - - /* Disable ISR when stopped bit is set */ - if (udc->stopped) - return IRQ_NONE; - - spin_lock(&udc->lock); - - status = readl(&udc->op_regs->usbsts); - intr = readl(&udc->op_regs->usbintr); - status &= intr; - - if (status == 0) { - spin_unlock(&udc->lock); - return IRQ_NONE; - } - - /* Clear all the interrupts occurred */ - writel(status, &udc->op_regs->usbsts); - - if (status & USBSTS_ERR) - irq_process_error(udc); - - if (status & USBSTS_RESET) - irq_process_reset(udc); - - if (status & USBSTS_PORT_CHANGE) - irq_process_port_change(udc); - - if (status & USBSTS_INT) - irq_process_tr_complete(udc); - - if (status & USBSTS_SUSPEND) - irq_process_suspend(udc); - - spin_unlock(&udc->lock); - - return IRQ_HANDLED; -} - -static irqreturn_t mv_udc_vbus_irq(int irq, void *dev) -{ - struct mv_udc *udc = (struct mv_udc *)dev; - - /* polling VBUS and init phy may cause too much time*/ - if (udc->qwork) - queue_work(udc->qwork, &udc->vbus_work); - - return IRQ_HANDLED; -} - -static void mv_udc_vbus_work(struct work_struct *work) -{ - struct mv_udc *udc; - unsigned int vbus; - - udc = container_of(work, struct mv_udc, vbus_work); - if (!udc->pdata->vbus) - return; - - vbus = udc->pdata->vbus->poll(); - dev_info(&udc->dev->dev, "vbus is %d\n", vbus); - - if (vbus == VBUS_HIGH) - mv_udc_vbus_session(&udc->gadget, 1); - else if (vbus == VBUS_LOW) - mv_udc_vbus_session(&udc->gadget, 0); -} - -/* release device structure */ -static void gadget_release(struct device *_dev) -{ - struct mv_udc *udc; - - udc = dev_get_drvdata(_dev); - - complete(udc->done); -} - -static void mv_udc_remove(struct platform_device *pdev) -{ - struct mv_udc *udc; - - udc = platform_get_drvdata(pdev); - - usb_del_gadget_udc(&udc->gadget); - - if (udc->qwork) - destroy_workqueue(udc->qwork); - - /* free memory allocated in probe */ - dma_pool_destroy(udc->dtd_pool); - - if (udc->ep_dqh) - dma_free_coherent(&pdev->dev, udc->ep_dqh_size, - udc->ep_dqh, udc->ep_dqh_dma); - - mv_udc_disable(udc); - - /* free dev, wait for the release() finished */ - wait_for_completion(udc->done); -} - -static int mv_udc_probe(struct platform_device *pdev) -{ - struct mv_usb_platform_data *pdata = dev_get_platdata(&pdev->dev); - struct mv_udc *udc; - int retval = 0; - struct resource *r; - size_t size; - - if (pdata == NULL) { - dev_err(&pdev->dev, "missing platform_data\n"); - return -ENODEV; - } - - udc = devm_kzalloc(&pdev->dev, sizeof(*udc), GFP_KERNEL); - if (udc == NULL) - return -ENOMEM; - - udc->done = &release_done; - udc->pdata = dev_get_platdata(&pdev->dev); - spin_lock_init(&udc->lock); - - udc->dev = pdev; - - if (pdata->mode == MV_USB_MODE_OTG) { - udc->transceiver = devm_usb_get_phy(&pdev->dev, - USB_PHY_TYPE_USB2); - if (IS_ERR(udc->transceiver)) { - retval = PTR_ERR(udc->transceiver); - - if (retval == -ENXIO) - return retval; - - udc->transceiver = NULL; - return -EPROBE_DEFER; - } - } - - /* udc only have one sysclk. */ - udc->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(udc->clk)) - return PTR_ERR(udc->clk); - - r = platform_get_resource_byname(udc->dev, IORESOURCE_MEM, "capregs"); - if (r == NULL) { - dev_err(&pdev->dev, "no I/O memory resource defined\n"); - return -ENODEV; - } - - udc->cap_regs = (struct mv_cap_regs __iomem *) - devm_ioremap(&pdev->dev, r->start, resource_size(r)); - if (udc->cap_regs == NULL) { - dev_err(&pdev->dev, "failed to map I/O memory\n"); - return -EBUSY; - } - - r = platform_get_resource_byname(udc->dev, IORESOURCE_MEM, "phyregs"); - if (r == NULL) { - dev_err(&pdev->dev, "no phy I/O memory resource defined\n"); - return -ENODEV; - } - - udc->phy_regs = devm_ioremap(&pdev->dev, r->start, resource_size(r)); - if (udc->phy_regs == NULL) { - dev_err(&pdev->dev, "failed to map phy I/O memory\n"); - return -EBUSY; - } - - /* we will acces controller register, so enable the clk */ - retval = mv_udc_enable_internal(udc); - if (retval) - return retval; - - udc->op_regs = - (struct mv_op_regs __iomem *)((unsigned long)udc->cap_regs - + (readl(&udc->cap_regs->caplength_hciversion) - & CAPLENGTH_MASK)); - udc->max_eps = readl(&udc->cap_regs->dccparams) & DCCPARAMS_DEN_MASK; - - /* - * some platform will use usb to download image, it may not disconnect - * usb gadget before loading kernel. So first stop udc here. - */ - udc_stop(udc); - writel(0xFFFFFFFF, &udc->op_regs->usbsts); - - size = udc->max_eps * sizeof(struct mv_dqh) *2; - size = (size + DQH_ALIGNMENT - 1) & ~(DQH_ALIGNMENT - 1); - udc->ep_dqh = dma_alloc_coherent(&pdev->dev, size, - &udc->ep_dqh_dma, GFP_KERNEL); - - if (udc->ep_dqh == NULL) { - dev_err(&pdev->dev, "allocate dQH memory failed\n"); - retval = -ENOMEM; - goto err_disable_clock; - } - udc->ep_dqh_size = size; - - /* create dTD dma_pool resource */ - udc->dtd_pool = dma_pool_create("mv_dtd", - &pdev->dev, - sizeof(struct mv_dtd), - DTD_ALIGNMENT, - DMA_BOUNDARY); - - if (!udc->dtd_pool) { - retval = -ENOMEM; - goto err_free_dma; - } - - size = udc->max_eps * sizeof(struct mv_ep) *2; - udc->eps = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); - if (udc->eps == NULL) { - retval = -ENOMEM; - goto err_destroy_dma; - } - - /* initialize ep0 status request structure */ - udc->status_req = devm_kzalloc(&pdev->dev, sizeof(struct mv_req), - GFP_KERNEL); - if (!udc->status_req) { - retval = -ENOMEM; - goto err_destroy_dma; - } - INIT_LIST_HEAD(&udc->status_req->queue); - - /* allocate a small amount of memory to get valid address */ - udc->status_req->req.buf = devm_kzalloc(&pdev->dev, 8, GFP_KERNEL); - if (!udc->status_req->req.buf) { - retval = -ENOMEM; - goto err_destroy_dma; - } - udc->status_req->req.dma = DMA_ADDR_INVALID; - - udc->resume_state = USB_STATE_NOTATTACHED; - udc->usb_state = USB_STATE_POWERED; - udc->ep0_dir = EP_DIR_OUT; - udc->remote_wakeup = 0; - - r = platform_get_resource(udc->dev, IORESOURCE_IRQ, 0); - if (r == NULL) { - dev_err(&pdev->dev, "no IRQ resource defined\n"); - retval = -ENODEV; - goto err_destroy_dma; - } - udc->irq = r->start; - if (devm_request_irq(&pdev->dev, udc->irq, mv_udc_irq, - IRQF_SHARED, driver_name, udc)) { - dev_err(&pdev->dev, "Request irq %d for UDC failed\n", - udc->irq); - retval = -ENODEV; - goto err_destroy_dma; - } - - /* initialize gadget structure */ - udc->gadget.ops = &mv_ops; /* usb_gadget_ops */ - udc->gadget.ep0 = &udc->eps[0].ep; /* gadget ep0 */ - INIT_LIST_HEAD(&udc->gadget.ep_list); /* ep_list */ - udc->gadget.speed = USB_SPEED_UNKNOWN; /* speed */ - udc->gadget.max_speed = USB_SPEED_HIGH; /* support dual speed */ - - /* the "gadget" abstracts/virtualizes the controller */ - udc->gadget.name = driver_name; /* gadget name */ - - eps_init(udc); - - /* VBUS detect: we can disable/enable clock on demand.*/ - if (udc->transceiver) - udc->clock_gating = 1; - else if (pdata->vbus) { - udc->clock_gating = 1; - retval = devm_request_threaded_irq(&pdev->dev, - pdata->vbus->irq, NULL, - mv_udc_vbus_irq, IRQF_ONESHOT, "vbus", udc); - if (retval) { - dev_info(&pdev->dev, - "Can not request irq for VBUS, " - "disable clock gating\n"); - udc->clock_gating = 0; - } - - udc->qwork = create_singlethread_workqueue("mv_udc_queue"); - if (!udc->qwork) { - dev_err(&pdev->dev, "cannot create workqueue\n"); - retval = -ENOMEM; - goto err_destroy_dma; - } - - INIT_WORK(&udc->vbus_work, mv_udc_vbus_work); - } - - /* - * When clock gating is supported, we can disable clk and phy. - * If not, it means that VBUS detection is not supported, we - * have to enable vbus active all the time to let controller work. - */ - if (udc->clock_gating) - mv_udc_disable_internal(udc); - else - udc->vbus_active = 1; - - retval = usb_add_gadget_udc_release(&pdev->dev, &udc->gadget, - gadget_release); - if (retval) - goto err_create_workqueue; - - platform_set_drvdata(pdev, udc); - dev_info(&pdev->dev, "successful probe UDC device %s clock gating.\n", - udc->clock_gating ? "with" : "without"); - - return 0; - -err_create_workqueue: - if (udc->qwork) - destroy_workqueue(udc->qwork); -err_destroy_dma: - dma_pool_destroy(udc->dtd_pool); -err_free_dma: - dma_free_coherent(&pdev->dev, udc->ep_dqh_size, - udc->ep_dqh, udc->ep_dqh_dma); -err_disable_clock: - mv_udc_disable_internal(udc); - - return retval; -} - -#ifdef CONFIG_PM -static int mv_udc_suspend(struct device *dev) -{ - struct mv_udc *udc; - - udc = dev_get_drvdata(dev); - - /* if OTG is enabled, the following will be done in OTG driver*/ - if (udc->transceiver) - return 0; - - if (udc->pdata->vbus && udc->pdata->vbus->poll) - if (udc->pdata->vbus->poll() == VBUS_HIGH) { - dev_info(&udc->dev->dev, "USB cable is connected!\n"); - return -EAGAIN; - } - - /* - * only cable is unplugged, udc can suspend. - * So do not care about clock_gating == 1. - */ - if (!udc->clock_gating) { - udc_stop(udc); - - spin_lock_irq(&udc->lock); - /* stop all usb activities */ - stop_activity(udc, udc->driver); - spin_unlock_irq(&udc->lock); - - mv_udc_disable_internal(udc); - } - - return 0; -} - -static int mv_udc_resume(struct device *dev) -{ - struct mv_udc *udc; - int retval; - - udc = dev_get_drvdata(dev); - - /* if OTG is enabled, the following will be done in OTG driver*/ - if (udc->transceiver) - return 0; - - if (!udc->clock_gating) { - retval = mv_udc_enable_internal(udc); - if (retval) - return retval; - - if (udc->driver && udc->softconnect) { - udc_reset(udc); - ep0_reset(udc); - udc_start(udc); - } - } - - return 0; -} - -static const struct dev_pm_ops mv_udc_pm_ops = { - .suspend = mv_udc_suspend, - .resume = mv_udc_resume, -}; -#endif - -static void mv_udc_shutdown(struct platform_device *pdev) -{ - struct mv_udc *udc; - u32 mode; - - udc = platform_get_drvdata(pdev); - /* reset controller mode to IDLE */ - mv_udc_enable(udc); - mode = readl(&udc->op_regs->usbmode); - mode &= ~3; - writel(mode, &udc->op_regs->usbmode); - mv_udc_disable(udc); -} - -static struct platform_driver udc_driver = { - .probe = mv_udc_probe, - .remove = mv_udc_remove, - .shutdown = mv_udc_shutdown, - .driver = { - .name = "mv-udc", -#ifdef CONFIG_PM - .pm = &mv_udc_pm_ops, -#endif - }, -}; - -module_platform_driver(udc_driver); -MODULE_ALIAS("platform:mv-udc"); -MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_AUTHOR("Chao Xie "); -MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/udc/net2272.c b/drivers/usb/gadget/udc/net2272.c deleted file mode 100644 index 7ecddbf5c90d..000000000000 --- a/drivers/usb/gadget/udc/net2272.c +++ /dev/null @@ -1,2723 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Driver for PLX NET2272 USB device controller - * - * Copyright (C) 2005-2006 PLX Technology, Inc. - * Copyright (C) 2006-2011 Analog Devices, Inc. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "net2272.h" - -#define DRIVER_DESC "PLX NET2272 USB Peripheral Controller" - -static const char driver_name[] = "net2272"; -static const char driver_vers[] = "2006 October 17/mainline"; -static const char driver_desc[] = DRIVER_DESC; - -static const char ep0name[] = "ep0"; -static const char * const ep_name[] = { - ep0name, - "ep-a", "ep-b", "ep-c", -}; - -#ifdef CONFIG_USB_NET2272_DMA -/* - * use_dma: the NET2272 can use an external DMA controller. - * Note that since there is no generic DMA api, some functions, - * notably request_dma, start_dma, and cancel_dma will need to be - * modified for your platform's particular dma controller. - * - * If use_dma is disabled, pio will be used instead. - */ -static bool use_dma = false; -module_param(use_dma, bool, 0644); - -/* - * dma_ep: selects the endpoint for use with dma (1=ep-a, 2=ep-b) - * The NET2272 can only use dma for a single endpoint at a time. - * At some point this could be modified to allow either endpoint - * to take control of dma as it becomes available. - * - * Note that DMA should not be used on OUT endpoints unless it can - * be guaranteed that no short packets will arrive on an IN endpoint - * while the DMA operation is pending. Otherwise the OUT DMA will - * terminate prematurely (See NET2272 Errata 630-0213-0101) - */ -static ushort dma_ep = 1; -module_param(dma_ep, ushort, 0644); - -/* - * dma_mode: net2272 dma mode setting (see LOCCTL1 definition): - * mode 0 == Slow DREQ mode - * mode 1 == Fast DREQ mode - * mode 2 == Burst mode - */ -static ushort dma_mode = 2; -module_param(dma_mode, ushort, 0644); -#else -#define use_dma 0 -#define dma_ep 1 -#define dma_mode 2 -#endif - -/* - * fifo_mode: net2272 buffer configuration: - * mode 0 == ep-{a,b,c} 512db each - * mode 1 == ep-a 1k, ep-{b,c} 512db - * mode 2 == ep-a 1k, ep-b 1k, ep-c 512db - * mode 3 == ep-a 1k, ep-b disabled, ep-c 512db - */ -static ushort fifo_mode; -module_param(fifo_mode, ushort, 0644); - -/* - * enable_suspend: When enabled, the driver will respond to - * USB suspend requests by powering down the NET2272. Otherwise, - * USB suspend requests will be ignored. This is acceptable for - * self-powered devices. For bus powered devices set this to 1. - */ -static ushort enable_suspend; -module_param(enable_suspend, ushort, 0644); - -static void assert_out_naking(struct net2272_ep *ep, const char *where) -{ - u8 tmp; - -#ifndef DEBUG - return; -#endif - - tmp = net2272_ep_read(ep, EP_STAT0); - if ((tmp & (1 << NAK_OUT_PACKETS)) == 0) { - dev_dbg(ep->dev->dev, "%s %s %02x !NAK\n", - ep->ep.name, where, tmp); - net2272_ep_write(ep, EP_RSPSET, 1 << ALT_NAK_OUT_PACKETS); - } -} -#define ASSERT_OUT_NAKING(ep) assert_out_naking(ep, __func__) - -static void stop_out_naking(struct net2272_ep *ep) -{ - u8 tmp = net2272_ep_read(ep, EP_STAT0); - - if ((tmp & (1 << NAK_OUT_PACKETS)) != 0) - net2272_ep_write(ep, EP_RSPCLR, 1 << ALT_NAK_OUT_PACKETS); -} - -#define PIPEDIR(bAddress) (usb_pipein(bAddress) ? "in" : "out") - -static char *type_string(u8 bmAttributes) -{ - switch ((bmAttributes) & USB_ENDPOINT_XFERTYPE_MASK) { - case USB_ENDPOINT_XFER_BULK: return "bulk"; - case USB_ENDPOINT_XFER_ISOC: return "iso"; - case USB_ENDPOINT_XFER_INT: return "intr"; - default: return "control"; - } -} - -static char *buf_state_string(unsigned state) -{ - switch (state) { - case BUFF_FREE: return "free"; - case BUFF_VALID: return "valid"; - case BUFF_LCL: return "local"; - case BUFF_USB: return "usb"; - default: return "unknown"; - } -} - -static char *dma_mode_string(void) -{ - if (!use_dma) - return "PIO"; - switch (dma_mode) { - case 0: return "SLOW DREQ"; - case 1: return "FAST DREQ"; - case 2: return "BURST"; - default: return "invalid"; - } -} - -static void net2272_dequeue_all(struct net2272_ep *); -static int net2272_kick_dma(struct net2272_ep *, struct net2272_request *); -static int net2272_fifo_status(struct usb_ep *); - -static const struct usb_ep_ops net2272_ep_ops; - -/*---------------------------------------------------------------------------*/ - -static int -net2272_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) -{ - struct net2272 *dev; - struct net2272_ep *ep; - u32 max; - u8 tmp; - unsigned long flags; - - ep = container_of(_ep, struct net2272_ep, ep); - if (!_ep || !desc || ep->desc || _ep->name == ep0name - || desc->bDescriptorType != USB_DT_ENDPOINT) - return -EINVAL; - dev = ep->dev; - if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) - return -ESHUTDOWN; - - max = usb_endpoint_maxp(desc); - - spin_lock_irqsave(&dev->lock, flags); - _ep->maxpacket = max; - ep->desc = desc; - - /* net2272_ep_reset() has already been called */ - ep->stopped = 0; - ep->wedged = 0; - - /* set speed-dependent max packet */ - net2272_ep_write(ep, EP_MAXPKT0, max & 0xff); - net2272_ep_write(ep, EP_MAXPKT1, (max & 0xff00) >> 8); - - /* set type, direction, address; reset fifo counters */ - net2272_ep_write(ep, EP_STAT1, 1 << BUFFER_FLUSH); - tmp = usb_endpoint_type(desc); - if (usb_endpoint_xfer_bulk(desc)) { - /* catch some particularly blatant driver bugs */ - if ((dev->gadget.speed == USB_SPEED_HIGH && max != 512) || - (dev->gadget.speed == USB_SPEED_FULL && max > 64)) { - spin_unlock_irqrestore(&dev->lock, flags); - return -ERANGE; - } - } - ep->is_iso = usb_endpoint_xfer_isoc(desc) ? 1 : 0; - tmp <<= ENDPOINT_TYPE; - tmp |= ((desc->bEndpointAddress & 0x0f) << ENDPOINT_NUMBER); - tmp |= usb_endpoint_dir_in(desc) << ENDPOINT_DIRECTION; - tmp |= (1 << ENDPOINT_ENABLE); - - /* for OUT transfers, block the rx fifo until a read is posted */ - ep->is_in = usb_endpoint_dir_in(desc); - if (!ep->is_in) - net2272_ep_write(ep, EP_RSPSET, 1 << ALT_NAK_OUT_PACKETS); - - net2272_ep_write(ep, EP_CFG, tmp); - - /* enable irqs */ - tmp = (1 << ep->num) | net2272_read(dev, IRQENB0); - net2272_write(dev, IRQENB0, tmp); - - tmp = (1 << DATA_PACKET_RECEIVED_INTERRUPT_ENABLE) - | (1 << DATA_PACKET_TRANSMITTED_INTERRUPT_ENABLE) - | net2272_ep_read(ep, EP_IRQENB); - net2272_ep_write(ep, EP_IRQENB, tmp); - - tmp = desc->bEndpointAddress; - dev_dbg(dev->dev, "enabled %s (ep%d%s-%s) max %04x cfg %02x\n", - _ep->name, tmp & 0x0f, PIPEDIR(tmp), - type_string(desc->bmAttributes), max, - net2272_ep_read(ep, EP_CFG)); - - spin_unlock_irqrestore(&dev->lock, flags); - return 0; -} - -static void net2272_ep_reset(struct net2272_ep *ep) -{ - u8 tmp; - - ep->desc = NULL; - INIT_LIST_HEAD(&ep->queue); - - usb_ep_set_maxpacket_limit(&ep->ep, ~0); - ep->ep.ops = &net2272_ep_ops; - - /* disable irqs, endpoint */ - net2272_ep_write(ep, EP_IRQENB, 0); - - /* init to our chosen defaults, notably so that we NAK OUT - * packets until the driver queues a read. - */ - tmp = (1 << NAK_OUT_PACKETS_MODE) | (1 << ALT_NAK_OUT_PACKETS); - net2272_ep_write(ep, EP_RSPSET, tmp); - - tmp = (1 << INTERRUPT_MODE) | (1 << HIDE_STATUS_PHASE); - if (ep->num != 0) - tmp |= (1 << ENDPOINT_TOGGLE) | (1 << ENDPOINT_HALT); - - net2272_ep_write(ep, EP_RSPCLR, tmp); - - /* scrub most status bits, and flush any fifo state */ - net2272_ep_write(ep, EP_STAT0, - (1 << DATA_IN_TOKEN_INTERRUPT) - | (1 << DATA_OUT_TOKEN_INTERRUPT) - | (1 << DATA_PACKET_TRANSMITTED_INTERRUPT) - | (1 << DATA_PACKET_RECEIVED_INTERRUPT) - | (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT)); - - net2272_ep_write(ep, EP_STAT1, - (1 << TIMEOUT) - | (1 << USB_OUT_ACK_SENT) - | (1 << USB_OUT_NAK_SENT) - | (1 << USB_IN_ACK_RCVD) - | (1 << USB_IN_NAK_SENT) - | (1 << USB_STALL_SENT) - | (1 << LOCAL_OUT_ZLP) - | (1 << BUFFER_FLUSH)); - - /* fifo size is handled separately */ -} - -static int net2272_disable(struct usb_ep *_ep) -{ - struct net2272_ep *ep; - unsigned long flags; - - ep = container_of(_ep, struct net2272_ep, ep); - if (!_ep || !ep->desc || _ep->name == ep0name) - return -EINVAL; - - spin_lock_irqsave(&ep->dev->lock, flags); - net2272_dequeue_all(ep); - net2272_ep_reset(ep); - - dev_vdbg(ep->dev->dev, "disabled %s\n", _ep->name); - - spin_unlock_irqrestore(&ep->dev->lock, flags); - return 0; -} - -/*---------------------------------------------------------------------------*/ - -static struct usb_request * -net2272_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) -{ - struct net2272_request *req; - - if (!_ep) - return NULL; - - req = kzalloc(sizeof(*req), gfp_flags); - if (!req) - return NULL; - - INIT_LIST_HEAD(&req->queue); - - return &req->req; -} - -static void -net2272_free_request(struct usb_ep *_ep, struct usb_request *_req) -{ - struct net2272_request *req; - - if (!_ep || !_req) - return; - - req = container_of(_req, struct net2272_request, req); - WARN_ON(!list_empty(&req->queue)); - kfree(req); -} - -static void -net2272_done(struct net2272_ep *ep, struct net2272_request *req, int status) -{ - struct net2272 *dev; - unsigned stopped = ep->stopped; - - if (ep->num == 0) { - if (ep->dev->protocol_stall) { - ep->stopped = 1; - set_halt(ep); - } - allow_status(ep); - } - - list_del_init(&req->queue); - - if (req->req.status == -EINPROGRESS) - req->req.status = status; - else - status = req->req.status; - - dev = ep->dev; - if (use_dma && ep->dma) - usb_gadget_unmap_request(&dev->gadget, &req->req, - ep->is_in); - - if (status && status != -ESHUTDOWN) - dev_vdbg(dev->dev, "complete %s req %p stat %d len %u/%u buf %p\n", - ep->ep.name, &req->req, status, - req->req.actual, req->req.length, req->req.buf); - - /* don't modify queue heads during completion callback */ - ep->stopped = 1; - spin_unlock(&dev->lock); - usb_gadget_giveback_request(&ep->ep, &req->req); - spin_lock(&dev->lock); - ep->stopped = stopped; -} - -static int -net2272_write_packet(struct net2272_ep *ep, u8 *buf, - struct net2272_request *req, unsigned max) -{ - u16 __iomem *ep_data = net2272_reg_addr(ep->dev, EP_DATA); - u16 *bufp; - unsigned length, count; - u8 tmp; - - length = min(req->req.length - req->req.actual, max); - req->req.actual += length; - - dev_vdbg(ep->dev->dev, "write packet %s req %p max %u len %u avail %u\n", - ep->ep.name, req, max, length, - (net2272_ep_read(ep, EP_AVAIL1) << 8) | net2272_ep_read(ep, EP_AVAIL0)); - - count = length; - bufp = (u16 *)buf; - - while (likely(count >= 2)) { - /* no byte-swap required; chip endian set during init */ - writew(*bufp++, ep_data); - count -= 2; - } - buf = (u8 *)bufp; - - /* write final byte by placing the NET2272 into 8-bit mode */ - if (unlikely(count)) { - tmp = net2272_read(ep->dev, LOCCTL); - net2272_write(ep->dev, LOCCTL, tmp & ~(1 << DATA_WIDTH)); - writeb(*buf, ep_data); - net2272_write(ep->dev, LOCCTL, tmp); - } - return length; -} - -/* returns: 0: still running, 1: completed, negative: errno */ -static int -net2272_write_fifo(struct net2272_ep *ep, struct net2272_request *req) -{ - u8 *buf; - unsigned count, max; - int status; - - dev_vdbg(ep->dev->dev, "write_fifo %s actual %d len %d\n", - ep->ep.name, req->req.actual, req->req.length); - - /* - * Keep loading the endpoint until the final packet is loaded, - * or the endpoint buffer is full. - */ - top: - /* - * Clear interrupt status - * - Packet Transmitted interrupt will become set again when the - * host successfully takes another packet - */ - net2272_ep_write(ep, EP_STAT0, (1 << DATA_PACKET_TRANSMITTED_INTERRUPT)); - while (!(net2272_ep_read(ep, EP_STAT0) & (1 << BUFFER_FULL))) { - buf = req->req.buf + req->req.actual; - prefetch(buf); - - /* force pagesel */ - net2272_ep_read(ep, EP_STAT0); - - max = (net2272_ep_read(ep, EP_AVAIL1) << 8) | - (net2272_ep_read(ep, EP_AVAIL0)); - - if (max < ep->ep.maxpacket) - max = (net2272_ep_read(ep, EP_AVAIL1) << 8) - | (net2272_ep_read(ep, EP_AVAIL0)); - - count = net2272_write_packet(ep, buf, req, max); - /* see if we are done */ - if (req->req.length == req->req.actual) { - /* validate short or zlp packet */ - if (count < ep->ep.maxpacket) - set_fifo_bytecount(ep, 0); - net2272_done(ep, req, 0); - - if (!list_empty(&ep->queue)) { - req = list_entry(ep->queue.next, - struct net2272_request, - queue); - status = net2272_kick_dma(ep, req); - - if (status < 0) - if ((net2272_ep_read(ep, EP_STAT0) - & (1 << BUFFER_EMPTY))) - goto top; - } - return 1; - } - net2272_ep_write(ep, EP_STAT0, (1 << DATA_PACKET_TRANSMITTED_INTERRUPT)); - } - return 0; -} - -static void -net2272_out_flush(struct net2272_ep *ep) -{ - ASSERT_OUT_NAKING(ep); - - net2272_ep_write(ep, EP_STAT0, (1 << DATA_OUT_TOKEN_INTERRUPT) - | (1 << DATA_PACKET_RECEIVED_INTERRUPT)); - net2272_ep_write(ep, EP_STAT1, 1 << BUFFER_FLUSH); -} - -static int -net2272_read_packet(struct net2272_ep *ep, u8 *buf, - struct net2272_request *req, unsigned avail) -{ - u16 __iomem *ep_data = net2272_reg_addr(ep->dev, EP_DATA); - unsigned is_short; - u16 *bufp; - - req->req.actual += avail; - - dev_vdbg(ep->dev->dev, "read packet %s req %p len %u avail %u\n", - ep->ep.name, req, avail, - (net2272_ep_read(ep, EP_AVAIL1) << 8) | net2272_ep_read(ep, EP_AVAIL0)); - - is_short = (avail < ep->ep.maxpacket); - - if (unlikely(avail == 0)) { - /* remove any zlp from the buffer */ - (void)readw(ep_data); - return is_short; - } - - /* Ensure we get the final byte */ - if (unlikely(avail % 2)) - avail++; - bufp = (u16 *)buf; - - do { - *bufp++ = readw(ep_data); - avail -= 2; - } while (avail); - - /* - * To avoid false endpoint available race condition must read - * ep stat0 twice in the case of a short transfer - */ - if (net2272_ep_read(ep, EP_STAT0) & (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT)) - net2272_ep_read(ep, EP_STAT0); - - return is_short; -} - -static int -net2272_read_fifo(struct net2272_ep *ep, struct net2272_request *req) -{ - u8 *buf; - unsigned is_short; - int count; - int tmp; - int cleanup = 0; - - dev_vdbg(ep->dev->dev, "read_fifo %s actual %d len %d\n", - ep->ep.name, req->req.actual, req->req.length); - - top: - do { - buf = req->req.buf + req->req.actual; - prefetchw(buf); - - count = (net2272_ep_read(ep, EP_AVAIL1) << 8) - | net2272_ep_read(ep, EP_AVAIL0); - - net2272_ep_write(ep, EP_STAT0, - (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT) | - (1 << DATA_PACKET_RECEIVED_INTERRUPT)); - - tmp = req->req.length - req->req.actual; - - if (count > tmp) { - if ((tmp % ep->ep.maxpacket) != 0) { - dev_err(ep->dev->dev, - "%s out fifo %d bytes, expected %d\n", - ep->ep.name, count, tmp); - cleanup = 1; - } - count = (tmp > 0) ? tmp : 0; - } - - is_short = net2272_read_packet(ep, buf, req, count); - - /* completion */ - if (unlikely(cleanup || is_short || - req->req.actual == req->req.length)) { - - if (cleanup) { - net2272_out_flush(ep); - net2272_done(ep, req, -EOVERFLOW); - } else - net2272_done(ep, req, 0); - - /* re-initialize endpoint transfer registers - * otherwise they may result in erroneous pre-validation - * for subsequent control reads - */ - if (unlikely(ep->num == 0)) { - net2272_ep_write(ep, EP_TRANSFER2, 0); - net2272_ep_write(ep, EP_TRANSFER1, 0); - net2272_ep_write(ep, EP_TRANSFER0, 0); - } - - if (!list_empty(&ep->queue)) { - int status; - - req = list_entry(ep->queue.next, - struct net2272_request, queue); - status = net2272_kick_dma(ep, req); - if ((status < 0) && - !(net2272_ep_read(ep, EP_STAT0) & (1 << BUFFER_EMPTY))) - goto top; - } - return 1; - } - } while (!(net2272_ep_read(ep, EP_STAT0) & (1 << BUFFER_EMPTY))); - - return 0; -} - -static void -net2272_pio_advance(struct net2272_ep *ep) -{ - struct net2272_request *req; - - if (unlikely(list_empty(&ep->queue))) - return; - - req = list_entry(ep->queue.next, struct net2272_request, queue); - (ep->is_in ? net2272_write_fifo : net2272_read_fifo)(ep, req); -} - -/* returns 0 on success, else negative errno */ -static int -net2272_request_dma(struct net2272 *dev, unsigned ep, u32 buf, - unsigned len, unsigned dir) -{ - dev_vdbg(dev->dev, "request_dma ep %d buf %08x len %d dir %d\n", - ep, buf, len, dir); - - /* The NET2272 only supports a single dma channel */ - if (dev->dma_busy) - return -EBUSY; - /* - * EP_TRANSFER (used to determine the number of bytes received - * in an OUT transfer) is 24 bits wide; don't ask for more than that. - */ - if ((dir == 1) && (len > 0x1000000)) - return -EINVAL; - - dev->dma_busy = 1; - - /* initialize platform's dma */ -#ifdef CONFIG_USB_PCI - /* NET2272 addr, buffer addr, length, etc. */ - switch (dev->dev_id) { - case PCI_DEVICE_ID_RDK1: - /* Setup PLX 9054 DMA mode */ - writel((1 << LOCAL_BUS_WIDTH) | - (1 << TA_READY_INPUT_ENABLE) | - (0 << LOCAL_BURST_ENABLE) | - (1 << DONE_INTERRUPT_ENABLE) | - (1 << LOCAL_ADDRESSING_MODE) | - (1 << DEMAND_MODE) | - (1 << DMA_EOT_ENABLE) | - (1 << FAST_SLOW_TERMINATE_MODE_SELECT) | - (1 << DMA_CHANNEL_INTERRUPT_SELECT), - dev->rdk1.plx9054_base_addr + DMAMODE0); - - writel(0x100000, dev->rdk1.plx9054_base_addr + DMALADR0); - writel(buf, dev->rdk1.plx9054_base_addr + DMAPADR0); - writel(len, dev->rdk1.plx9054_base_addr + DMASIZ0); - writel((dir << DIRECTION_OF_TRANSFER) | - (1 << INTERRUPT_AFTER_TERMINAL_COUNT), - dev->rdk1.plx9054_base_addr + DMADPR0); - writel((1 << LOCAL_DMA_CHANNEL_0_INTERRUPT_ENABLE) | - readl(dev->rdk1.plx9054_base_addr + INTCSR), - dev->rdk1.plx9054_base_addr + INTCSR); - - break; - } -#endif - - net2272_write(dev, DMAREQ, - (0 << DMA_BUFFER_VALID) | - (1 << DMA_REQUEST_ENABLE) | - (1 << DMA_CONTROL_DACK) | - (dev->dma_eot_polarity << EOT_POLARITY) | - (dev->dma_dack_polarity << DACK_POLARITY) | - (dev->dma_dreq_polarity << DREQ_POLARITY) | - ((ep >> 1) << DMA_ENDPOINT_SELECT)); - - (void) net2272_read(dev, SCRATCH); - - return 0; -} - -static void -net2272_start_dma(struct net2272 *dev) -{ - /* start platform's dma controller */ -#ifdef CONFIG_USB_PCI - switch (dev->dev_id) { - case PCI_DEVICE_ID_RDK1: - writeb((1 << CHANNEL_ENABLE) | (1 << CHANNEL_START), - dev->rdk1.plx9054_base_addr + DMACSR0); - break; - } -#endif -} - -/* returns 0 on success, else negative errno */ -static int -net2272_kick_dma(struct net2272_ep *ep, struct net2272_request *req) -{ - unsigned size; - u8 tmp; - - if (!use_dma || (ep->num < 1) || (ep->num > 2) || !ep->dma) - return -EINVAL; - - /* don't use dma for odd-length transfers - * otherwise, we'd need to deal with the last byte with pio - */ - if (req->req.length & 1) - return -EINVAL; - - dev_vdbg(ep->dev->dev, "kick_dma %s req %p dma %08llx\n", - ep->ep.name, req, (unsigned long long) req->req.dma); - - net2272_ep_write(ep, EP_RSPSET, 1 << ALT_NAK_OUT_PACKETS); - - /* The NET2272 can only use DMA on one endpoint at a time */ - if (ep->dev->dma_busy) - return -EBUSY; - - /* Make sure we only DMA an even number of bytes (we'll use - * pio to complete the transfer) - */ - size = req->req.length; - size &= ~1; - - /* device-to-host transfer */ - if (ep->is_in) { - /* initialize platform's dma controller */ - if (net2272_request_dma(ep->dev, ep->num, req->req.dma, size, 0)) - /* unable to obtain DMA channel; return error and use pio mode */ - return -EBUSY; - req->req.actual += size; - - /* host-to-device transfer */ - } else { - tmp = net2272_ep_read(ep, EP_STAT0); - - /* initialize platform's dma controller */ - if (net2272_request_dma(ep->dev, ep->num, req->req.dma, size, 1)) - /* unable to obtain DMA channel; return error and use pio mode */ - return -EBUSY; - - if (!(tmp & (1 << BUFFER_EMPTY))) - ep->not_empty = 1; - else - ep->not_empty = 0; - - - /* allow the endpoint's buffer to fill */ - net2272_ep_write(ep, EP_RSPCLR, 1 << ALT_NAK_OUT_PACKETS); - - /* this transfer completed and data's already in the fifo - * return error so pio gets used. - */ - if (tmp & (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT)) { - - /* deassert dreq */ - net2272_write(ep->dev, DMAREQ, - (0 << DMA_BUFFER_VALID) | - (0 << DMA_REQUEST_ENABLE) | - (1 << DMA_CONTROL_DACK) | - (ep->dev->dma_eot_polarity << EOT_POLARITY) | - (ep->dev->dma_dack_polarity << DACK_POLARITY) | - (ep->dev->dma_dreq_polarity << DREQ_POLARITY) | - ((ep->num >> 1) << DMA_ENDPOINT_SELECT)); - - return -EBUSY; - } - } - - /* Don't use per-packet interrupts: use dma interrupts only */ - net2272_ep_write(ep, EP_IRQENB, 0); - - net2272_start_dma(ep->dev); - - return 0; -} - -static void net2272_cancel_dma(struct net2272 *dev) -{ -#ifdef CONFIG_USB_PCI - switch (dev->dev_id) { - case PCI_DEVICE_ID_RDK1: - writeb(0, dev->rdk1.plx9054_base_addr + DMACSR0); - writeb(1 << CHANNEL_ABORT, dev->rdk1.plx9054_base_addr + DMACSR0); - while (!(readb(dev->rdk1.plx9054_base_addr + DMACSR0) & - (1 << CHANNEL_DONE))) - continue; /* wait for dma to stabalize */ - - /* dma abort generates an interrupt */ - writeb(1 << CHANNEL_CLEAR_INTERRUPT, - dev->rdk1.plx9054_base_addr + DMACSR0); - break; - } -#endif - - dev->dma_busy = 0; -} - -/*---------------------------------------------------------------------------*/ - -static int -net2272_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) -{ - struct net2272_request *req; - struct net2272_ep *ep; - struct net2272 *dev; - unsigned long flags; - int status = -1; - u8 s; - - req = container_of(_req, struct net2272_request, req); - if (!_req || !_req->complete || !_req->buf - || !list_empty(&req->queue)) - return -EINVAL; - ep = container_of(_ep, struct net2272_ep, ep); - if (!_ep || (!ep->desc && ep->num != 0)) - return -EINVAL; - dev = ep->dev; - if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) - return -ESHUTDOWN; - - /* set up dma mapping in case the caller didn't */ - if (use_dma && ep->dma) { - status = usb_gadget_map_request(&dev->gadget, _req, - ep->is_in); - if (status) - return status; - } - - dev_vdbg(dev->dev, "%s queue req %p, len %d buf %p dma %08llx %s\n", - _ep->name, _req, _req->length, _req->buf, - (unsigned long long) _req->dma, _req->zero ? "zero" : "!zero"); - - spin_lock_irqsave(&dev->lock, flags); - - _req->status = -EINPROGRESS; - _req->actual = 0; - - /* kickstart this i/o queue? */ - if (list_empty(&ep->queue) && !ep->stopped) { - /* maybe there's no control data, just status ack */ - if (ep->num == 0 && _req->length == 0) { - net2272_done(ep, req, 0); - dev_vdbg(dev->dev, "%s status ack\n", ep->ep.name); - goto done; - } - - /* Return zlp, don't let it block subsequent packets */ - s = net2272_ep_read(ep, EP_STAT0); - if (s & (1 << BUFFER_EMPTY)) { - /* Buffer is empty check for a blocking zlp, handle it */ - if ((s & (1 << NAK_OUT_PACKETS)) && - net2272_ep_read(ep, EP_STAT1) & (1 << LOCAL_OUT_ZLP)) { - dev_dbg(dev->dev, "WARNING: returning ZLP short packet termination!\n"); - /* - * Request is going to terminate with a short packet ... - * hope the client is ready for it! - */ - status = net2272_read_fifo(ep, req); - /* clear short packet naking */ - net2272_ep_write(ep, EP_STAT0, (1 << NAK_OUT_PACKETS)); - goto done; - } - } - - /* try dma first */ - status = net2272_kick_dma(ep, req); - - if (status < 0) { - /* dma failed (most likely in use by another endpoint) - * fallback to pio - */ - status = 0; - - if (ep->is_in) - status = net2272_write_fifo(ep, req); - else { - s = net2272_ep_read(ep, EP_STAT0); - if ((s & (1 << BUFFER_EMPTY)) == 0) - status = net2272_read_fifo(ep, req); - } - - if (unlikely(status != 0)) { - if (status > 0) - status = 0; - req = NULL; - } - } - } - if (likely(req)) - list_add_tail(&req->queue, &ep->queue); - - if (likely(!list_empty(&ep->queue))) - net2272_ep_write(ep, EP_RSPCLR, 1 << ALT_NAK_OUT_PACKETS); - done: - spin_unlock_irqrestore(&dev->lock, flags); - - return 0; -} - -/* dequeue ALL requests */ -static void -net2272_dequeue_all(struct net2272_ep *ep) -{ - struct net2272_request *req; - - /* called with spinlock held */ - ep->stopped = 1; - - while (!list_empty(&ep->queue)) { - req = list_entry(ep->queue.next, - struct net2272_request, - queue); - net2272_done(ep, req, -ESHUTDOWN); - } -} - -/* dequeue JUST ONE request */ -static int -net2272_dequeue(struct usb_ep *_ep, struct usb_request *_req) -{ - struct net2272_ep *ep; - struct net2272_request *req = NULL, *iter; - unsigned long flags; - int stopped; - - ep = container_of(_ep, struct net2272_ep, ep); - if (!_ep || (!ep->desc && ep->num != 0) || !_req) - return -EINVAL; - - spin_lock_irqsave(&ep->dev->lock, flags); - stopped = ep->stopped; - ep->stopped = 1; - - /* make sure it's still queued on this endpoint */ - list_for_each_entry(iter, &ep->queue, queue) { - if (&iter->req != _req) - continue; - req = iter; - break; - } - if (!req) { - ep->stopped = stopped; - spin_unlock_irqrestore(&ep->dev->lock, flags); - return -EINVAL; - } - - /* queue head may be partially complete */ - if (ep->queue.next == &req->queue) { - dev_dbg(ep->dev->dev, "unlink (%s) pio\n", _ep->name); - net2272_done(ep, req, -ECONNRESET); - } - ep->stopped = stopped; - - spin_unlock_irqrestore(&ep->dev->lock, flags); - return 0; -} - -/*---------------------------------------------------------------------------*/ - -static int -net2272_set_halt_and_wedge(struct usb_ep *_ep, int value, int wedged) -{ - struct net2272_ep *ep; - unsigned long flags; - int ret = 0; - - ep = container_of(_ep, struct net2272_ep, ep); - if (!_ep || (!ep->desc && ep->num != 0)) - return -EINVAL; - if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN) - return -ESHUTDOWN; - if (ep->desc /* not ep0 */ && usb_endpoint_xfer_isoc(ep->desc)) - return -EINVAL; - - spin_lock_irqsave(&ep->dev->lock, flags); - if (!list_empty(&ep->queue)) - ret = -EAGAIN; - else if (ep->is_in && value && net2272_fifo_status(_ep) != 0) - ret = -EAGAIN; - else { - dev_vdbg(ep->dev->dev, "%s %s %s\n", _ep->name, - value ? "set" : "clear", - wedged ? "wedge" : "halt"); - /* set/clear */ - if (value) { - if (ep->num == 0) - ep->dev->protocol_stall = 1; - else - set_halt(ep); - if (wedged) - ep->wedged = 1; - } else { - clear_halt(ep); - ep->wedged = 0; - } - } - spin_unlock_irqrestore(&ep->dev->lock, flags); - - return ret; -} - -static int -net2272_set_halt(struct usb_ep *_ep, int value) -{ - return net2272_set_halt_and_wedge(_ep, value, 0); -} - -static int -net2272_set_wedge(struct usb_ep *_ep) -{ - if (!_ep || _ep->name == ep0name) - return -EINVAL; - return net2272_set_halt_and_wedge(_ep, 1, 1); -} - -static int -net2272_fifo_status(struct usb_ep *_ep) -{ - struct net2272_ep *ep; - u16 avail; - - ep = container_of(_ep, struct net2272_ep, ep); - if (!_ep || (!ep->desc && ep->num != 0)) - return -ENODEV; - if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN) - return -ESHUTDOWN; - - avail = net2272_ep_read(ep, EP_AVAIL1) << 8; - avail |= net2272_ep_read(ep, EP_AVAIL0); - if (avail > ep->fifo_size) - return -EOVERFLOW; - if (ep->is_in) - avail = ep->fifo_size - avail; - return avail; -} - -static void -net2272_fifo_flush(struct usb_ep *_ep) -{ - struct net2272_ep *ep; - - ep = container_of(_ep, struct net2272_ep, ep); - if (!_ep || (!ep->desc && ep->num != 0)) - return; - if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN) - return; - - net2272_ep_write(ep, EP_STAT1, 1 << BUFFER_FLUSH); -} - -static const struct usb_ep_ops net2272_ep_ops = { - .enable = net2272_enable, - .disable = net2272_disable, - - .alloc_request = net2272_alloc_request, - .free_request = net2272_free_request, - - .queue = net2272_queue, - .dequeue = net2272_dequeue, - - .set_halt = net2272_set_halt, - .set_wedge = net2272_set_wedge, - .fifo_status = net2272_fifo_status, - .fifo_flush = net2272_fifo_flush, -}; - -/*---------------------------------------------------------------------------*/ - -static int -net2272_get_frame(struct usb_gadget *_gadget) -{ - struct net2272 *dev; - unsigned long flags; - u16 ret; - - if (!_gadget) - return -ENODEV; - dev = container_of(_gadget, struct net2272, gadget); - spin_lock_irqsave(&dev->lock, flags); - - ret = net2272_read(dev, FRAME1) << 8; - ret |= net2272_read(dev, FRAME0); - - spin_unlock_irqrestore(&dev->lock, flags); - return ret; -} - -static int -net2272_wakeup(struct usb_gadget *_gadget) -{ - struct net2272 *dev; - u8 tmp; - unsigned long flags; - - if (!_gadget) - return 0; - dev = container_of(_gadget, struct net2272, gadget); - - spin_lock_irqsave(&dev->lock, flags); - tmp = net2272_read(dev, USBCTL0); - if (tmp & (1 << IO_WAKEUP_ENABLE)) - net2272_write(dev, USBCTL1, (1 << GENERATE_RESUME)); - - spin_unlock_irqrestore(&dev->lock, flags); - - return 0; -} - -static int -net2272_set_selfpowered(struct usb_gadget *_gadget, int value) -{ - if (!_gadget) - return -ENODEV; - - _gadget->is_selfpowered = (value != 0); - - return 0; -} - -static int -net2272_pullup(struct usb_gadget *_gadget, int is_on) -{ - struct net2272 *dev; - u8 tmp; - unsigned long flags; - - if (!_gadget) - return -ENODEV; - dev = container_of(_gadget, struct net2272, gadget); - - spin_lock_irqsave(&dev->lock, flags); - tmp = net2272_read(dev, USBCTL0); - dev->softconnect = (is_on != 0); - if (is_on) - tmp |= (1 << USB_DETECT_ENABLE); - else - tmp &= ~(1 << USB_DETECT_ENABLE); - net2272_write(dev, USBCTL0, tmp); - spin_unlock_irqrestore(&dev->lock, flags); - - return 0; -} - -static int net2272_start(struct usb_gadget *_gadget, - struct usb_gadget_driver *driver); -static int net2272_stop(struct usb_gadget *_gadget); -static void net2272_async_callbacks(struct usb_gadget *_gadget, bool enable); - -static const struct usb_gadget_ops net2272_ops = { - .get_frame = net2272_get_frame, - .wakeup = net2272_wakeup, - .set_selfpowered = net2272_set_selfpowered, - .pullup = net2272_pullup, - .udc_start = net2272_start, - .udc_stop = net2272_stop, - .udc_async_callbacks = net2272_async_callbacks, -}; - -/*---------------------------------------------------------------------------*/ - -static ssize_t -registers_show(struct device *_dev, struct device_attribute *attr, char *buf) -{ - struct net2272 *dev; - char *next; - unsigned size, t; - unsigned long flags; - u8 t1, t2; - int i; - const char *s; - - dev = dev_get_drvdata(_dev); - next = buf; - size = PAGE_SIZE; - spin_lock_irqsave(&dev->lock, flags); - - /* Main Control Registers */ - t = scnprintf(next, size, "%s version %s," - "chiprev %02x, locctl %02x\n" - "irqenb0 %02x irqenb1 %02x " - "irqstat0 %02x irqstat1 %02x\n", - driver_name, driver_vers, dev->chiprev, - net2272_read(dev, LOCCTL), - net2272_read(dev, IRQENB0), - net2272_read(dev, IRQENB1), - net2272_read(dev, IRQSTAT0), - net2272_read(dev, IRQSTAT1)); - size -= t; - next += t; - - /* DMA */ - t1 = net2272_read(dev, DMAREQ); - t = scnprintf(next, size, "\ndmareq %02x: %s %s%s%s%s\n", - t1, ep_name[(t1 & 0x01) + 1], - t1 & (1 << DMA_CONTROL_DACK) ? "dack " : "", - t1 & (1 << DMA_REQUEST_ENABLE) ? "reqenb " : "", - t1 & (1 << DMA_REQUEST) ? "req " : "", - t1 & (1 << DMA_BUFFER_VALID) ? "valid " : ""); - size -= t; - next += t; - - /* USB Control Registers */ - t1 = net2272_read(dev, USBCTL1); - if (t1 & (1 << VBUS_PIN)) { - if (t1 & (1 << USB_HIGH_SPEED)) - s = "high speed"; - else if (dev->gadget.speed == USB_SPEED_UNKNOWN) - s = "powered"; - else - s = "full speed"; - } else - s = "not attached"; - t = scnprintf(next, size, - "usbctl0 %02x usbctl1 %02x addr 0x%02x (%s)\n", - net2272_read(dev, USBCTL0), t1, - net2272_read(dev, OURADDR), s); - size -= t; - next += t; - - /* Endpoint Registers */ - for (i = 0; i < 4; ++i) { - struct net2272_ep *ep; - - ep = &dev->ep[i]; - if (i && !ep->desc) - continue; - - t1 = net2272_ep_read(ep, EP_CFG); - t2 = net2272_ep_read(ep, EP_RSPSET); - t = scnprintf(next, size, - "\n%s\tcfg %02x rsp (%02x) %s%s%s%s%s%s%s%s" - "irqenb %02x\n", - ep->ep.name, t1, t2, - (t2 & (1 << ALT_NAK_OUT_PACKETS)) ? "NAK " : "", - (t2 & (1 << HIDE_STATUS_PHASE)) ? "hide " : "", - (t2 & (1 << AUTOVALIDATE)) ? "auto " : "", - (t2 & (1 << INTERRUPT_MODE)) ? "interrupt " : "", - (t2 & (1 << CONTROL_STATUS_PHASE_HANDSHAKE)) ? "status " : "", - (t2 & (1 << NAK_OUT_PACKETS_MODE)) ? "NAKmode " : "", - (t2 & (1 << ENDPOINT_TOGGLE)) ? "DATA1 " : "DATA0 ", - (t2 & (1 << ENDPOINT_HALT)) ? "HALT " : "", - net2272_ep_read(ep, EP_IRQENB)); - size -= t; - next += t; - - t = scnprintf(next, size, - "\tstat0 %02x stat1 %02x avail %04x " - "(ep%d%s-%s)%s\n", - net2272_ep_read(ep, EP_STAT0), - net2272_ep_read(ep, EP_STAT1), - (net2272_ep_read(ep, EP_AVAIL1) << 8) | net2272_ep_read(ep, EP_AVAIL0), - t1 & 0x0f, - ep->is_in ? "in" : "out", - type_string(t1 >> 5), - ep->stopped ? "*" : ""); - size -= t; - next += t; - - t = scnprintf(next, size, - "\tep_transfer %06x\n", - ((net2272_ep_read(ep, EP_TRANSFER2) & 0xff) << 16) | - ((net2272_ep_read(ep, EP_TRANSFER1) & 0xff) << 8) | - ((net2272_ep_read(ep, EP_TRANSFER0) & 0xff))); - size -= t; - next += t; - - t1 = net2272_ep_read(ep, EP_BUFF_STATES) & 0x03; - t2 = (net2272_ep_read(ep, EP_BUFF_STATES) >> 2) & 0x03; - t = scnprintf(next, size, - "\tbuf-a %s buf-b %s\n", - buf_state_string(t1), - buf_state_string(t2)); - size -= t; - next += t; - } - - spin_unlock_irqrestore(&dev->lock, flags); - - return PAGE_SIZE - size; -} -static DEVICE_ATTR_RO(registers); - -/*---------------------------------------------------------------------------*/ - -static void -net2272_set_fifo_mode(struct net2272 *dev, int mode) -{ - u8 tmp; - - tmp = net2272_read(dev, LOCCTL) & 0x3f; - tmp |= (mode << 6); - net2272_write(dev, LOCCTL, tmp); - - INIT_LIST_HEAD(&dev->gadget.ep_list); - - /* always ep-a, ep-c ... maybe not ep-b */ - list_add_tail(&dev->ep[1].ep.ep_list, &dev->gadget.ep_list); - - switch (mode) { - case 0: - list_add_tail(&dev->ep[2].ep.ep_list, &dev->gadget.ep_list); - dev->ep[1].fifo_size = dev->ep[2].fifo_size = 512; - break; - case 1: - list_add_tail(&dev->ep[2].ep.ep_list, &dev->gadget.ep_list); - dev->ep[1].fifo_size = 1024; - dev->ep[2].fifo_size = 512; - break; - case 2: - list_add_tail(&dev->ep[2].ep.ep_list, &dev->gadget.ep_list); - dev->ep[1].fifo_size = dev->ep[2].fifo_size = 1024; - break; - case 3: - dev->ep[1].fifo_size = 1024; - break; - } - - /* ep-c is always 2 512 byte buffers */ - list_add_tail(&dev->ep[3].ep.ep_list, &dev->gadget.ep_list); - dev->ep[3].fifo_size = 512; -} - -/*---------------------------------------------------------------------------*/ - -static void -net2272_usb_reset(struct net2272 *dev) -{ - dev->gadget.speed = USB_SPEED_UNKNOWN; - - net2272_cancel_dma(dev); - - net2272_write(dev, IRQENB0, 0); - net2272_write(dev, IRQENB1, 0); - - /* clear irq state */ - net2272_write(dev, IRQSTAT0, 0xff); - net2272_write(dev, IRQSTAT1, ~(1 << SUSPEND_REQUEST_INTERRUPT)); - - net2272_write(dev, DMAREQ, - (0 << DMA_BUFFER_VALID) | - (0 << DMA_REQUEST_ENABLE) | - (1 << DMA_CONTROL_DACK) | - (dev->dma_eot_polarity << EOT_POLARITY) | - (dev->dma_dack_polarity << DACK_POLARITY) | - (dev->dma_dreq_polarity << DREQ_POLARITY) | - ((dma_ep >> 1) << DMA_ENDPOINT_SELECT)); - - net2272_cancel_dma(dev); - net2272_set_fifo_mode(dev, (fifo_mode <= 3) ? fifo_mode : 0); - - /* Set the NET2272 ep fifo data width to 16-bit mode and for correct byte swapping - * note that the higher level gadget drivers are expected to convert data to little endian. - * Enable byte swap for your local bus/cpu if needed by setting BYTE_SWAP in LOCCTL here - */ - net2272_write(dev, LOCCTL, net2272_read(dev, LOCCTL) | (1 << DATA_WIDTH)); - net2272_write(dev, LOCCTL1, (dma_mode << DMA_MODE)); -} - -static void -net2272_usb_reinit(struct net2272 *dev) -{ - int i; - - /* basic endpoint init */ - for (i = 0; i < 4; ++i) { - struct net2272_ep *ep = &dev->ep[i]; - - ep->ep.name = ep_name[i]; - ep->dev = dev; - ep->num = i; - ep->not_empty = 0; - - if (use_dma && ep->num == dma_ep) - ep->dma = 1; - - if (i > 0 && i <= 3) - ep->fifo_size = 512; - else - ep->fifo_size = 64; - net2272_ep_reset(ep); - - if (i == 0) { - ep->ep.caps.type_control = true; - } else { - ep->ep.caps.type_iso = true; - ep->ep.caps.type_bulk = true; - ep->ep.caps.type_int = true; - } - - ep->ep.caps.dir_in = true; - ep->ep.caps.dir_out = true; - } - usb_ep_set_maxpacket_limit(&dev->ep[0].ep, 64); - - dev->gadget.ep0 = &dev->ep[0].ep; - dev->ep[0].stopped = 0; - INIT_LIST_HEAD(&dev->gadget.ep0->ep_list); -} - -static void -net2272_ep0_start(struct net2272 *dev) -{ - struct net2272_ep *ep0 = &dev->ep[0]; - - net2272_ep_write(ep0, EP_RSPSET, - (1 << NAK_OUT_PACKETS_MODE) | - (1 << ALT_NAK_OUT_PACKETS)); - net2272_ep_write(ep0, EP_RSPCLR, - (1 << HIDE_STATUS_PHASE) | - (1 << CONTROL_STATUS_PHASE_HANDSHAKE)); - net2272_write(dev, USBCTL0, - (dev->softconnect << USB_DETECT_ENABLE) | - (1 << USB_ROOT_PORT_WAKEUP_ENABLE) | - (1 << IO_WAKEUP_ENABLE)); - net2272_write(dev, IRQENB0, - (1 << SETUP_PACKET_INTERRUPT_ENABLE) | - (1 << ENDPOINT_0_INTERRUPT_ENABLE) | - (1 << DMA_DONE_INTERRUPT_ENABLE)); - net2272_write(dev, IRQENB1, - (1 << VBUS_INTERRUPT_ENABLE) | - (1 << ROOT_PORT_RESET_INTERRUPT_ENABLE) | - (1 << SUSPEND_REQUEST_CHANGE_INTERRUPT_ENABLE)); -} - -/* when a driver is successfully registered, it will receive - * control requests including set_configuration(), which enables - * non-control requests. then usb traffic follows until a - * disconnect is reported. then a host may connect again, or - * the driver might get unbound. - */ -static int net2272_start(struct usb_gadget *_gadget, - struct usb_gadget_driver *driver) -{ - struct net2272 *dev; - unsigned i; - - if (!driver || !driver->setup || - driver->max_speed != USB_SPEED_HIGH) - return -EINVAL; - - dev = container_of(_gadget, struct net2272, gadget); - - for (i = 0; i < 4; ++i) - dev->ep[i].irqs = 0; - /* hook up the driver ... */ - dev->softconnect = 1; - dev->driver = driver; - - /* ... then enable host detection and ep0; and we're ready - * for set_configuration as well as eventual disconnect. - */ - net2272_ep0_start(dev); - - return 0; -} - -static void -stop_activity(struct net2272 *dev, struct usb_gadget_driver *driver) -{ - int i; - - /* don't disconnect if it's not connected */ - if (dev->gadget.speed == USB_SPEED_UNKNOWN) - driver = NULL; - - /* stop hardware; prevent new request submissions; - * and kill any outstanding requests. - */ - net2272_usb_reset(dev); - for (i = 0; i < 4; ++i) - net2272_dequeue_all(&dev->ep[i]); - - /* report disconnect; the driver is already quiesced */ - if (dev->async_callbacks && driver) { - spin_unlock(&dev->lock); - driver->disconnect(&dev->gadget); - spin_lock(&dev->lock); - } - - net2272_usb_reinit(dev); -} - -static int net2272_stop(struct usb_gadget *_gadget) -{ - struct net2272 *dev; - unsigned long flags; - - dev = container_of(_gadget, struct net2272, gadget); - - spin_lock_irqsave(&dev->lock, flags); - stop_activity(dev, NULL); - spin_unlock_irqrestore(&dev->lock, flags); - - dev->driver = NULL; - - return 0; -} - -static void net2272_async_callbacks(struct usb_gadget *_gadget, bool enable) -{ - struct net2272 *dev = container_of(_gadget, struct net2272, gadget); - - spin_lock_irq(&dev->lock); - dev->async_callbacks = enable; - spin_unlock_irq(&dev->lock); -} - -/*---------------------------------------------------------------------------*/ -/* handle ep-a/ep-b dma completions */ -static void -net2272_handle_dma(struct net2272_ep *ep) -{ - struct net2272_request *req; - unsigned len; - int status; - - if (!list_empty(&ep->queue)) - req = list_entry(ep->queue.next, - struct net2272_request, queue); - else - req = NULL; - - dev_vdbg(ep->dev->dev, "handle_dma %s req %p\n", ep->ep.name, req); - - /* Ensure DREQ is de-asserted */ - net2272_write(ep->dev, DMAREQ, - (0 << DMA_BUFFER_VALID) - | (0 << DMA_REQUEST_ENABLE) - | (1 << DMA_CONTROL_DACK) - | (ep->dev->dma_eot_polarity << EOT_POLARITY) - | (ep->dev->dma_dack_polarity << DACK_POLARITY) - | (ep->dev->dma_dreq_polarity << DREQ_POLARITY) - | (ep->dma << DMA_ENDPOINT_SELECT)); - - ep->dev->dma_busy = 0; - - net2272_ep_write(ep, EP_IRQENB, - (1 << DATA_PACKET_RECEIVED_INTERRUPT_ENABLE) - | (1 << DATA_PACKET_TRANSMITTED_INTERRUPT_ENABLE) - | net2272_ep_read(ep, EP_IRQENB)); - - /* device-to-host transfer completed */ - if (ep->is_in) { - /* validate a short packet or zlp if necessary */ - if ((req->req.length % ep->ep.maxpacket != 0) || - req->req.zero) - set_fifo_bytecount(ep, 0); - - net2272_done(ep, req, 0); - if (!list_empty(&ep->queue)) { - req = list_entry(ep->queue.next, - struct net2272_request, queue); - status = net2272_kick_dma(ep, req); - if (status < 0) - net2272_pio_advance(ep); - } - - /* host-to-device transfer completed */ - } else { - /* terminated with a short packet? */ - if (net2272_read(ep->dev, IRQSTAT0) & - (1 << DMA_DONE_INTERRUPT)) { - /* abort system dma */ - net2272_cancel_dma(ep->dev); - } - - /* EP_TRANSFER will contain the number of bytes - * actually received. - * NOTE: There is no overflow detection on EP_TRANSFER: - * We can't deal with transfers larger than 2^24 bytes! - */ - len = (net2272_ep_read(ep, EP_TRANSFER2) << 16) - | (net2272_ep_read(ep, EP_TRANSFER1) << 8) - | (net2272_ep_read(ep, EP_TRANSFER0)); - - if (ep->not_empty) - len += 4; - - req->req.actual += len; - - /* get any remaining data */ - net2272_pio_advance(ep); - } -} - -/*---------------------------------------------------------------------------*/ - -static void -net2272_handle_ep(struct net2272_ep *ep) -{ - struct net2272_request *req; - u8 stat0, stat1; - - if (!list_empty(&ep->queue)) - req = list_entry(ep->queue.next, - struct net2272_request, queue); - else - req = NULL; - - /* ack all, and handle what we care about */ - stat0 = net2272_ep_read(ep, EP_STAT0); - stat1 = net2272_ep_read(ep, EP_STAT1); - ep->irqs++; - - dev_vdbg(ep->dev->dev, "%s ack ep_stat0 %02x, ep_stat1 %02x, req %p\n", - ep->ep.name, stat0, stat1, req ? &req->req : NULL); - - net2272_ep_write(ep, EP_STAT0, stat0 & - ~((1 << NAK_OUT_PACKETS) - | (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT))); - net2272_ep_write(ep, EP_STAT1, stat1); - - /* data packet(s) received (in the fifo, OUT) - * direction must be validated, otherwise control read status phase - * could be interpreted as a valid packet - */ - if (!ep->is_in && (stat0 & (1 << DATA_PACKET_RECEIVED_INTERRUPT))) - net2272_pio_advance(ep); - /* data packet(s) transmitted (IN) */ - else if (stat0 & (1 << DATA_PACKET_TRANSMITTED_INTERRUPT)) - net2272_pio_advance(ep); -} - -static struct net2272_ep * -net2272_get_ep_by_addr(struct net2272 *dev, u16 wIndex) -{ - struct net2272_ep *ep; - - if ((wIndex & USB_ENDPOINT_NUMBER_MASK) == 0) - return &dev->ep[0]; - - list_for_each_entry(ep, &dev->gadget.ep_list, ep.ep_list) { - u8 bEndpointAddress; - - if (!ep->desc) - continue; - bEndpointAddress = ep->desc->bEndpointAddress; - if ((wIndex ^ bEndpointAddress) & USB_DIR_IN) - continue; - if ((wIndex & 0x0f) == (bEndpointAddress & 0x0f)) - return ep; - } - return NULL; -} - -/* - * USB Test Packet: - * JKJKJKJK * 9 - * JJKKJJKK * 8 - * JJJJKKKK * 8 - * JJJJJJJKKKKKKK * 8 - * JJJJJJJK * 8 - * {JKKKKKKK * 10}, JK - */ -static const u8 net2272_test_packet[] = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, - 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, - 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x7F, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, - 0xFC, 0x7E, 0xBF, 0xDF, 0xEF, 0xF7, 0xFD, 0x7E -}; - -static void -net2272_set_test_mode(struct net2272 *dev, int mode) -{ - int i; - - /* Disable all net2272 interrupts: - * Nothing but a power cycle should stop the test. - */ - net2272_write(dev, IRQENB0, 0x00); - net2272_write(dev, IRQENB1, 0x00); - - /* Force tranceiver to high-speed */ - net2272_write(dev, XCVRDIAG, 1 << FORCE_HIGH_SPEED); - - net2272_write(dev, PAGESEL, 0); - net2272_write(dev, EP_STAT0, 1 << DATA_PACKET_TRANSMITTED_INTERRUPT); - net2272_write(dev, EP_RSPCLR, - (1 << CONTROL_STATUS_PHASE_HANDSHAKE) - | (1 << HIDE_STATUS_PHASE)); - net2272_write(dev, EP_CFG, 1 << ENDPOINT_DIRECTION); - net2272_write(dev, EP_STAT1, 1 << BUFFER_FLUSH); - - /* wait for status phase to complete */ - while (!(net2272_read(dev, EP_STAT0) & - (1 << DATA_PACKET_TRANSMITTED_INTERRUPT))) - ; - - /* Enable test mode */ - net2272_write(dev, USBTEST, mode); - - /* load test packet */ - if (mode == USB_TEST_PACKET) { - /* switch to 8 bit mode */ - net2272_write(dev, LOCCTL, net2272_read(dev, LOCCTL) & - ~(1 << DATA_WIDTH)); - - for (i = 0; i < sizeof(net2272_test_packet); ++i) - net2272_write(dev, EP_DATA, net2272_test_packet[i]); - - /* Validate test packet */ - net2272_write(dev, EP_TRANSFER0, 0); - } -} - -static void -net2272_handle_stat0_irqs(struct net2272 *dev, u8 stat) -{ - struct net2272_ep *ep; - u8 num, scratch; - - /* starting a control request? */ - if (unlikely(stat & (1 << SETUP_PACKET_INTERRUPT))) { - union { - u8 raw[8]; - struct usb_ctrlrequest r; - } u; - int tmp = 0; - struct net2272_request *req; - - if (dev->gadget.speed == USB_SPEED_UNKNOWN) { - if (net2272_read(dev, USBCTL1) & (1 << USB_HIGH_SPEED)) - dev->gadget.speed = USB_SPEED_HIGH; - else - dev->gadget.speed = USB_SPEED_FULL; - dev_dbg(dev->dev, "%s\n", - usb_speed_string(dev->gadget.speed)); - } - - ep = &dev->ep[0]; - ep->irqs++; - - /* make sure any leftover interrupt state is cleared */ - stat &= ~(1 << ENDPOINT_0_INTERRUPT); - while (!list_empty(&ep->queue)) { - req = list_entry(ep->queue.next, - struct net2272_request, queue); - net2272_done(ep, req, - (req->req.actual == req->req.length) ? 0 : -EPROTO); - } - ep->stopped = 0; - dev->protocol_stall = 0; - net2272_ep_write(ep, EP_STAT0, - (1 << DATA_IN_TOKEN_INTERRUPT) - | (1 << DATA_OUT_TOKEN_INTERRUPT) - | (1 << DATA_PACKET_TRANSMITTED_INTERRUPT) - | (1 << DATA_PACKET_RECEIVED_INTERRUPT) - | (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT)); - net2272_ep_write(ep, EP_STAT1, - (1 << TIMEOUT) - | (1 << USB_OUT_ACK_SENT) - | (1 << USB_OUT_NAK_SENT) - | (1 << USB_IN_ACK_RCVD) - | (1 << USB_IN_NAK_SENT) - | (1 << USB_STALL_SENT) - | (1 << LOCAL_OUT_ZLP)); - - /* - * Ensure Control Read pre-validation setting is beyond maximum size - * - Control Writes can leave non-zero values in EP_TRANSFER. If - * an EP0 transfer following the Control Write is a Control Read, - * the NET2272 sees the non-zero EP_TRANSFER as an unexpected - * pre-validation count. - * - Setting EP_TRANSFER beyond the maximum EP0 transfer size ensures - * the pre-validation count cannot cause an unexpected validatation - */ - net2272_write(dev, PAGESEL, 0); - net2272_write(dev, EP_TRANSFER2, 0xff); - net2272_write(dev, EP_TRANSFER1, 0xff); - net2272_write(dev, EP_TRANSFER0, 0xff); - - u.raw[0] = net2272_read(dev, SETUP0); - u.raw[1] = net2272_read(dev, SETUP1); - u.raw[2] = net2272_read(dev, SETUP2); - u.raw[3] = net2272_read(dev, SETUP3); - u.raw[4] = net2272_read(dev, SETUP4); - u.raw[5] = net2272_read(dev, SETUP5); - u.raw[6] = net2272_read(dev, SETUP6); - u.raw[7] = net2272_read(dev, SETUP7); - /* - * If you have a big endian cpu make sure le16_to_cpus - * performs the proper byte swapping here... - */ - le16_to_cpus(&u.r.wValue); - le16_to_cpus(&u.r.wIndex); - le16_to_cpus(&u.r.wLength); - - /* ack the irq */ - net2272_write(dev, IRQSTAT0, 1 << SETUP_PACKET_INTERRUPT); - stat ^= (1 << SETUP_PACKET_INTERRUPT); - - /* watch control traffic at the token level, and force - * synchronization before letting the status phase happen. - */ - ep->is_in = (u.r.bRequestType & USB_DIR_IN) != 0; - if (ep->is_in) { - scratch = (1 << DATA_PACKET_TRANSMITTED_INTERRUPT_ENABLE) - | (1 << DATA_OUT_TOKEN_INTERRUPT_ENABLE) - | (1 << DATA_IN_TOKEN_INTERRUPT_ENABLE); - stop_out_naking(ep); - } else - scratch = (1 << DATA_PACKET_RECEIVED_INTERRUPT_ENABLE) - | (1 << DATA_OUT_TOKEN_INTERRUPT_ENABLE) - | (1 << DATA_IN_TOKEN_INTERRUPT_ENABLE); - net2272_ep_write(ep, EP_IRQENB, scratch); - - if ((u.r.bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD) - goto delegate; - switch (u.r.bRequest) { - case USB_REQ_GET_STATUS: { - struct net2272_ep *e; - u16 status = 0; - - switch (u.r.bRequestType & USB_RECIP_MASK) { - case USB_RECIP_ENDPOINT: - e = net2272_get_ep_by_addr(dev, u.r.wIndex); - if (!e || u.r.wLength > 2) - goto do_stall; - if (net2272_ep_read(e, EP_RSPSET) & (1 << ENDPOINT_HALT)) - status = cpu_to_le16(1); - else - status = cpu_to_le16(0); - - /* don't bother with a request object! */ - net2272_ep_write(&dev->ep[0], EP_IRQENB, 0); - writew(status, net2272_reg_addr(dev, EP_DATA)); - set_fifo_bytecount(&dev->ep[0], 0); - allow_status(ep); - dev_vdbg(dev->dev, "%s stat %02x\n", - ep->ep.name, status); - goto next_endpoints; - case USB_RECIP_DEVICE: - if (u.r.wLength > 2) - goto do_stall; - if (dev->gadget.is_selfpowered) - status = (1 << USB_DEVICE_SELF_POWERED); - - /* don't bother with a request object! */ - net2272_ep_write(&dev->ep[0], EP_IRQENB, 0); - writew(status, net2272_reg_addr(dev, EP_DATA)); - set_fifo_bytecount(&dev->ep[0], 0); - allow_status(ep); - dev_vdbg(dev->dev, "device stat %02x\n", status); - goto next_endpoints; - case USB_RECIP_INTERFACE: - if (u.r.wLength > 2) - goto do_stall; - - /* don't bother with a request object! */ - net2272_ep_write(&dev->ep[0], EP_IRQENB, 0); - writew(status, net2272_reg_addr(dev, EP_DATA)); - set_fifo_bytecount(&dev->ep[0], 0); - allow_status(ep); - dev_vdbg(dev->dev, "interface status %02x\n", status); - goto next_endpoints; - } - - break; - } - case USB_REQ_CLEAR_FEATURE: { - struct net2272_ep *e; - - if (u.r.bRequestType != USB_RECIP_ENDPOINT) - goto delegate; - if (u.r.wValue != USB_ENDPOINT_HALT || - u.r.wLength != 0) - goto do_stall; - e = net2272_get_ep_by_addr(dev, u.r.wIndex); - if (!e) - goto do_stall; - if (e->wedged) { - dev_vdbg(dev->dev, "%s wedged, halt not cleared\n", - ep->ep.name); - } else { - dev_vdbg(dev->dev, "%s clear halt\n", ep->ep.name); - clear_halt(e); - } - allow_status(ep); - goto next_endpoints; - } - case USB_REQ_SET_FEATURE: { - struct net2272_ep *e; - - if (u.r.bRequestType == USB_RECIP_DEVICE) { - if (u.r.wIndex != NORMAL_OPERATION) - net2272_set_test_mode(dev, (u.r.wIndex >> 8)); - allow_status(ep); - dev_vdbg(dev->dev, "test mode: %d\n", u.r.wIndex); - goto next_endpoints; - } else if (u.r.bRequestType != USB_RECIP_ENDPOINT) - goto delegate; - if (u.r.wValue != USB_ENDPOINT_HALT || - u.r.wLength != 0) - goto do_stall; - e = net2272_get_ep_by_addr(dev, u.r.wIndex); - if (!e) - goto do_stall; - set_halt(e); - allow_status(ep); - dev_vdbg(dev->dev, "%s set halt\n", ep->ep.name); - goto next_endpoints; - } - case USB_REQ_SET_ADDRESS: { - net2272_write(dev, OURADDR, u.r.wValue & 0xff); - allow_status(ep); - break; - } - default: - delegate: - dev_vdbg(dev->dev, "setup %02x.%02x v%04x i%04x " - "ep_cfg %08x\n", - u.r.bRequestType, u.r.bRequest, - u.r.wValue, u.r.wIndex, - net2272_ep_read(ep, EP_CFG)); - if (dev->async_callbacks) { - spin_unlock(&dev->lock); - tmp = dev->driver->setup(&dev->gadget, &u.r); - spin_lock(&dev->lock); - } - } - - /* stall ep0 on error */ - if (tmp < 0) { - do_stall: - dev_vdbg(dev->dev, "req %02x.%02x protocol STALL; stat %d\n", - u.r.bRequestType, u.r.bRequest, tmp); - dev->protocol_stall = 1; - } - /* endpoint dma irq? */ - } else if (stat & (1 << DMA_DONE_INTERRUPT)) { - net2272_cancel_dma(dev); - net2272_write(dev, IRQSTAT0, 1 << DMA_DONE_INTERRUPT); - stat &= ~(1 << DMA_DONE_INTERRUPT); - num = (net2272_read(dev, DMAREQ) & (1 << DMA_ENDPOINT_SELECT)) - ? 2 : 1; - - ep = &dev->ep[num]; - net2272_handle_dma(ep); - } - - next_endpoints: - /* endpoint data irq? */ - scratch = stat & 0x0f; - stat &= ~0x0f; - for (num = 0; scratch; num++) { - u8 t; - - /* does this endpoint's FIFO and queue need tending? */ - t = 1 << num; - if ((scratch & t) == 0) - continue; - scratch ^= t; - - ep = &dev->ep[num]; - net2272_handle_ep(ep); - } - - /* some interrupts we can just ignore */ - stat &= ~(1 << SOF_INTERRUPT); - - if (stat) - dev_dbg(dev->dev, "unhandled irqstat0 %02x\n", stat); -} - -static void -net2272_handle_stat1_irqs(struct net2272 *dev, u8 stat) -{ - u8 tmp, mask; - - /* after disconnect there's nothing else to do! */ - tmp = (1 << VBUS_INTERRUPT) | (1 << ROOT_PORT_RESET_INTERRUPT); - mask = (1 << USB_HIGH_SPEED) | (1 << USB_FULL_SPEED); - - if (stat & tmp) { - bool reset = false; - bool disconnect = false; - - /* - * Ignore disconnects and resets if the speed hasn't been set. - * VBUS can bounce and there's always an initial reset. - */ - net2272_write(dev, IRQSTAT1, tmp); - if (dev->gadget.speed != USB_SPEED_UNKNOWN) { - if ((stat & (1 << VBUS_INTERRUPT)) && - (net2272_read(dev, USBCTL1) & - (1 << VBUS_PIN)) == 0) { - disconnect = true; - dev_dbg(dev->dev, "disconnect %s\n", - dev->driver->driver.name); - } else if ((stat & (1 << ROOT_PORT_RESET_INTERRUPT)) && - (net2272_read(dev, USBCTL1) & mask) - == 0) { - reset = true; - dev_dbg(dev->dev, "reset %s\n", - dev->driver->driver.name); - } - - if (disconnect || reset) { - stop_activity(dev, dev->driver); - net2272_ep0_start(dev); - if (dev->async_callbacks) { - spin_unlock(&dev->lock); - if (reset) - usb_gadget_udc_reset(&dev->gadget, dev->driver); - else - (dev->driver->disconnect)(&dev->gadget); - spin_lock(&dev->lock); - } - return; - } - } - stat &= ~tmp; - - if (!stat) - return; - } - - tmp = (1 << SUSPEND_REQUEST_CHANGE_INTERRUPT); - if (stat & tmp) { - net2272_write(dev, IRQSTAT1, tmp); - if (stat & (1 << SUSPEND_REQUEST_INTERRUPT)) { - if (dev->async_callbacks && dev->driver->suspend) - dev->driver->suspend(&dev->gadget); - if (!enable_suspend) { - stat &= ~(1 << SUSPEND_REQUEST_INTERRUPT); - dev_dbg(dev->dev, "Suspend disabled, ignoring\n"); - } - } else { - if (dev->async_callbacks && dev->driver->resume) - dev->driver->resume(&dev->gadget); - } - stat &= ~tmp; - } - - /* clear any other status/irqs */ - if (stat) - net2272_write(dev, IRQSTAT1, stat); - - /* some status we can just ignore */ - stat &= ~((1 << CONTROL_STATUS_INTERRUPT) - | (1 << SUSPEND_REQUEST_INTERRUPT) - | (1 << RESUME_INTERRUPT)); - if (!stat) - return; - else - dev_dbg(dev->dev, "unhandled irqstat1 %02x\n", stat); -} - -static irqreturn_t net2272_irq(int irq, void *_dev) -{ - struct net2272 *dev = _dev; -#if defined(PLX_PCI_RDK) || defined(PLX_PCI_RDK2) - u32 intcsr; -#endif -#if defined(PLX_PCI_RDK) - u8 dmareq; -#endif - spin_lock(&dev->lock); -#if defined(PLX_PCI_RDK) - intcsr = readl(dev->rdk1.plx9054_base_addr + INTCSR); - - if ((intcsr & LOCAL_INTERRUPT_TEST) == LOCAL_INTERRUPT_TEST) { - writel(intcsr & ~(1 << PCI_INTERRUPT_ENABLE), - dev->rdk1.plx9054_base_addr + INTCSR); - net2272_handle_stat1_irqs(dev, net2272_read(dev, IRQSTAT1)); - net2272_handle_stat0_irqs(dev, net2272_read(dev, IRQSTAT0)); - intcsr = readl(dev->rdk1.plx9054_base_addr + INTCSR); - writel(intcsr | (1 << PCI_INTERRUPT_ENABLE), - dev->rdk1.plx9054_base_addr + INTCSR); - } - if ((intcsr & DMA_CHANNEL_0_TEST) == DMA_CHANNEL_0_TEST) { - writeb((1 << CHANNEL_CLEAR_INTERRUPT | (0 << CHANNEL_ENABLE)), - dev->rdk1.plx9054_base_addr + DMACSR0); - - dmareq = net2272_read(dev, DMAREQ); - if (dmareq & 0x01) - net2272_handle_dma(&dev->ep[2]); - else - net2272_handle_dma(&dev->ep[1]); - } -#endif -#if defined(PLX_PCI_RDK2) - /* see if PCI int for us by checking irqstat */ - intcsr = readl(dev->rdk2.fpga_base_addr + RDK2_IRQSTAT); - if (!(intcsr & (1 << NET2272_PCI_IRQ))) { - spin_unlock(&dev->lock); - return IRQ_NONE; - } - /* check dma interrupts */ -#endif - /* Platform/device interrupt handler */ -#if !defined(PLX_PCI_RDK) - net2272_handle_stat1_irqs(dev, net2272_read(dev, IRQSTAT1)); - net2272_handle_stat0_irqs(dev, net2272_read(dev, IRQSTAT0)); -#endif - spin_unlock(&dev->lock); - - return IRQ_HANDLED; -} - -static int net2272_present(struct net2272 *dev) -{ - /* - * Quick test to see if CPU can communicate properly with the NET2272. - * Verifies connection using writes and reads to write/read and - * read-only registers. - * - * This routine is strongly recommended especially during early bring-up - * of new hardware, however for designs that do not apply Power On System - * Tests (POST) it may discarded (or perhaps minimized). - */ - unsigned int ii; - u8 val, refval; - - /* Verify NET2272 write/read SCRATCH register can write and read */ - refval = net2272_read(dev, SCRATCH); - for (ii = 0; ii < 0x100; ii += 7) { - net2272_write(dev, SCRATCH, ii); - val = net2272_read(dev, SCRATCH); - if (val != ii) { - dev_dbg(dev->dev, - "%s: write/read SCRATCH register test failed: " - "wrote:0x%2.2x, read:0x%2.2x\n", - __func__, ii, val); - return -EINVAL; - } - } - /* To be nice, we write the original SCRATCH value back: */ - net2272_write(dev, SCRATCH, refval); - - /* Verify NET2272 CHIPREV register is read-only: */ - refval = net2272_read(dev, CHIPREV_2272); - for (ii = 0; ii < 0x100; ii += 7) { - net2272_write(dev, CHIPREV_2272, ii); - val = net2272_read(dev, CHIPREV_2272); - if (val != refval) { - dev_dbg(dev->dev, - "%s: write/read CHIPREV register test failed: " - "wrote 0x%2.2x, read:0x%2.2x expected:0x%2.2x\n", - __func__, ii, val, refval); - return -EINVAL; - } - } - - /* - * Verify NET2272's "NET2270 legacy revision" register - * - NET2272 has two revision registers. The NET2270 legacy revision - * register should read the same value, regardless of the NET2272 - * silicon revision. The legacy register applies to NET2270 - * firmware being applied to the NET2272. - */ - val = net2272_read(dev, CHIPREV_LEGACY); - if (val != NET2270_LEGACY_REV) { - /* - * Unexpected legacy revision value - * - Perhaps the chip is a NET2270? - */ - dev_dbg(dev->dev, - "%s: WARNING: UNEXPECTED NET2272 LEGACY REGISTER VALUE:\n" - " - CHIPREV_LEGACY: expected 0x%2.2x, got:0x%2.2x. (Not NET2272?)\n", - __func__, NET2270_LEGACY_REV, val); - return -EINVAL; - } - - /* - * Verify NET2272 silicon revision - * - This revision register is appropriate for the silicon version - * of the NET2272 - */ - val = net2272_read(dev, CHIPREV_2272); - switch (val) { - case CHIPREV_NET2272_R1: - /* - * NET2272 Rev 1 has DMA related errata: - * - Newer silicon (Rev 1A or better) required - */ - dev_dbg(dev->dev, - "%s: Rev 1 detected: newer silicon recommended for DMA support\n", - __func__); - break; - case CHIPREV_NET2272_R1A: - break; - default: - /* NET2272 silicon version *may* not work with this firmware */ - dev_dbg(dev->dev, - "%s: unexpected silicon revision register value: " - " CHIPREV_2272: 0x%2.2x\n", - __func__, val); - /* - * Return Success, even though the chip rev is not an expected value - * - Older, pre-built firmware can attempt to operate on newer silicon - * - Often, new silicon is perfectly compatible - */ - } - - /* Success: NET2272 checks out OK */ - return 0; -} - -static void -net2272_gadget_release(struct device *_dev) -{ - struct net2272 *dev = container_of(_dev, struct net2272, gadget.dev); - - kfree(dev); -} - -/*---------------------------------------------------------------------------*/ - -static void -net2272_remove(struct net2272 *dev) -{ - if (dev->added) - usb_del_gadget(&dev->gadget); - free_irq(dev->irq, dev); - iounmap(dev->base_addr); - device_remove_file(dev->dev, &dev_attr_registers); - - dev_info(dev->dev, "unbind\n"); -} - -static struct net2272 *net2272_probe_init(struct device *dev, unsigned int irq) -{ - struct net2272 *ret; - - if (!irq) { - dev_dbg(dev, "No IRQ!\n"); - return ERR_PTR(-ENODEV); - } - - /* alloc, and start init */ - ret = kzalloc(sizeof(*ret), GFP_KERNEL); - if (!ret) - return ERR_PTR(-ENOMEM); - - spin_lock_init(&ret->lock); - ret->irq = irq; - ret->dev = dev; - ret->gadget.ops = &net2272_ops; - ret->gadget.max_speed = USB_SPEED_HIGH; - - /* the "gadget" abstracts/virtualizes the controller */ - ret->gadget.name = driver_name; - usb_initialize_gadget(dev, &ret->gadget, net2272_gadget_release); - - return ret; -} - -static int -net2272_probe_fin(struct net2272 *dev, unsigned int irqflags) -{ - int ret; - - /* See if there... */ - if (net2272_present(dev)) { - dev_warn(dev->dev, "2272 not found!\n"); - ret = -ENODEV; - goto err; - } - - net2272_usb_reset(dev); - net2272_usb_reinit(dev); - - ret = request_irq(dev->irq, net2272_irq, irqflags, driver_name, dev); - if (ret) { - dev_err(dev->dev, "request interrupt %i failed\n", dev->irq); - goto err; - } - - dev->chiprev = net2272_read(dev, CHIPREV_2272); - - /* done */ - dev_info(dev->dev, "%s\n", driver_desc); - dev_info(dev->dev, "irq %i, mem %p, chip rev %04x, dma %s\n", - dev->irq, dev->base_addr, dev->chiprev, - dma_mode_string()); - dev_info(dev->dev, "version: %s\n", driver_vers); - - ret = device_create_file(dev->dev, &dev_attr_registers); - if (ret) - goto err_irq; - - ret = usb_add_gadget(&dev->gadget); - if (ret) - goto err_add_udc; - dev->added = 1; - - return 0; - -err_add_udc: - device_remove_file(dev->dev, &dev_attr_registers); - err_irq: - free_irq(dev->irq, dev); - err: - return ret; -} - -#ifdef CONFIG_USB_PCI - -/* - * wrap this driver around the specified device, but - * don't respond over USB until a gadget driver binds to us - */ - -static int -net2272_rdk1_probe(struct pci_dev *pdev, struct net2272 *dev) -{ - unsigned long resource, len, tmp; - void __iomem *mem_mapped_addr[4]; - int ret, i; - - /* - * BAR 0 holds PLX 9054 config registers - * BAR 1 is i/o memory; unused here - * BAR 2 holds EPLD config registers - * BAR 3 holds NET2272 registers - */ - - /* Find and map all address spaces */ - for (i = 0; i < 4; ++i) { - if (i == 1) - continue; /* BAR1 unused */ - - resource = pci_resource_start(pdev, i); - len = pci_resource_len(pdev, i); - - if (!request_mem_region(resource, len, driver_name)) { - dev_dbg(dev->dev, "controller already in use\n"); - ret = -EBUSY; - goto err; - } - - mem_mapped_addr[i] = ioremap(resource, len); - if (mem_mapped_addr[i] == NULL) { - release_mem_region(resource, len); - dev_dbg(dev->dev, "can't map memory\n"); - ret = -EFAULT; - goto err; - } - } - - dev->rdk1.plx9054_base_addr = mem_mapped_addr[0]; - dev->rdk1.epld_base_addr = mem_mapped_addr[2]; - dev->base_addr = mem_mapped_addr[3]; - - /* Set PLX 9054 bus width (16 bits) */ - tmp = readl(dev->rdk1.plx9054_base_addr + LBRD1); - writel((tmp & ~(3 << MEMORY_SPACE_LOCAL_BUS_WIDTH)) | W16_BIT, - dev->rdk1.plx9054_base_addr + LBRD1); - - /* Enable PLX 9054 Interrupts */ - writel(readl(dev->rdk1.plx9054_base_addr + INTCSR) | - (1 << PCI_INTERRUPT_ENABLE) | - (1 << LOCAL_INTERRUPT_INPUT_ENABLE), - dev->rdk1.plx9054_base_addr + INTCSR); - - writeb((1 << CHANNEL_CLEAR_INTERRUPT | (0 << CHANNEL_ENABLE)), - dev->rdk1.plx9054_base_addr + DMACSR0); - - /* reset */ - writeb((1 << EPLD_DMA_ENABLE) | - (1 << DMA_CTL_DACK) | - (1 << DMA_TIMEOUT_ENABLE) | - (1 << USER) | - (0 << MPX_MODE) | - (1 << BUSWIDTH) | - (1 << NET2272_RESET), - dev->base_addr + EPLD_IO_CONTROL_REGISTER); - - mb(); - writeb(readb(dev->base_addr + EPLD_IO_CONTROL_REGISTER) & - ~(1 << NET2272_RESET), - dev->base_addr + EPLD_IO_CONTROL_REGISTER); - udelay(200); - - return 0; - - err: - while (--i >= 0) { - if (i == 1) - continue; /* BAR1 unused */ - iounmap(mem_mapped_addr[i]); - release_mem_region(pci_resource_start(pdev, i), - pci_resource_len(pdev, i)); - } - - return ret; -} - -static int -net2272_rdk2_probe(struct pci_dev *pdev, struct net2272 *dev) -{ - unsigned long resource, len; - void __iomem *mem_mapped_addr[2]; - int ret, i; - - /* - * BAR 0 holds FGPA config registers - * BAR 1 holds NET2272 registers - */ - - /* Find and map all address spaces, bar2-3 unused in rdk 2 */ - for (i = 0; i < 2; ++i) { - resource = pci_resource_start(pdev, i); - len = pci_resource_len(pdev, i); - - if (!request_mem_region(resource, len, driver_name)) { - dev_dbg(dev->dev, "controller already in use\n"); - ret = -EBUSY; - goto err; - } - - mem_mapped_addr[i] = ioremap(resource, len); - if (mem_mapped_addr[i] == NULL) { - release_mem_region(resource, len); - dev_dbg(dev->dev, "can't map memory\n"); - ret = -EFAULT; - goto err; - } - } - - dev->rdk2.fpga_base_addr = mem_mapped_addr[0]; - dev->base_addr = mem_mapped_addr[1]; - - mb(); - /* Set 2272 bus width (16 bits) and reset */ - writel((1 << CHIP_RESET), dev->rdk2.fpga_base_addr + RDK2_LOCCTLRDK); - udelay(200); - writel((1 << BUS_WIDTH), dev->rdk2.fpga_base_addr + RDK2_LOCCTLRDK); - /* Print fpga version number */ - dev_info(dev->dev, "RDK2 FPGA version %08x\n", - readl(dev->rdk2.fpga_base_addr + RDK2_FPGAREV)); - /* Enable FPGA Interrupts */ - writel((1 << NET2272_PCI_IRQ), dev->rdk2.fpga_base_addr + RDK2_IRQENB); - - return 0; - - err: - while (--i >= 0) { - iounmap(mem_mapped_addr[i]); - release_mem_region(pci_resource_start(pdev, i), - pci_resource_len(pdev, i)); - } - - return ret; -} - -static int -net2272_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) -{ - struct net2272 *dev; - int ret; - - dev = net2272_probe_init(&pdev->dev, pdev->irq); - if (IS_ERR(dev)) - return PTR_ERR(dev); - dev->dev_id = pdev->device; - - if (pci_enable_device(pdev) < 0) { - ret = -ENODEV; - goto err_put; - } - - pci_set_master(pdev); - - switch (pdev->device) { - case PCI_DEVICE_ID_RDK1: ret = net2272_rdk1_probe(pdev, dev); break; - case PCI_DEVICE_ID_RDK2: ret = net2272_rdk2_probe(pdev, dev); break; - default: BUG(); - } - if (ret) - goto err_pci; - - ret = net2272_probe_fin(dev, 0); - if (ret) - goto err_pci; - - pci_set_drvdata(pdev, dev); - - return 0; - - err_pci: - pci_disable_device(pdev); - err_put: - usb_put_gadget(&dev->gadget); - - return ret; -} - -static void -net2272_rdk1_remove(struct pci_dev *pdev, struct net2272 *dev) -{ - int i; - - /* disable PLX 9054 interrupts */ - writel(readl(dev->rdk1.plx9054_base_addr + INTCSR) & - ~(1 << PCI_INTERRUPT_ENABLE), - dev->rdk1.plx9054_base_addr + INTCSR); - - /* clean up resources allocated during probe() */ - iounmap(dev->rdk1.plx9054_base_addr); - iounmap(dev->rdk1.epld_base_addr); - - for (i = 0; i < 4; ++i) { - if (i == 1) - continue; /* BAR1 unused */ - release_mem_region(pci_resource_start(pdev, i), - pci_resource_len(pdev, i)); - } -} - -static void -net2272_rdk2_remove(struct pci_dev *pdev, struct net2272 *dev) -{ - int i; - - /* disable fpga interrupts - writel(readl(dev->rdk1.plx9054_base_addr + INTCSR) & - ~(1 << PCI_INTERRUPT_ENABLE), - dev->rdk1.plx9054_base_addr + INTCSR); - */ - - /* clean up resources allocated during probe() */ - iounmap(dev->rdk2.fpga_base_addr); - - for (i = 0; i < 2; ++i) - release_mem_region(pci_resource_start(pdev, i), - pci_resource_len(pdev, i)); -} - -static void -net2272_pci_remove(struct pci_dev *pdev) -{ - struct net2272 *dev = pci_get_drvdata(pdev); - - net2272_remove(dev); - - switch (pdev->device) { - case PCI_DEVICE_ID_RDK1: net2272_rdk1_remove(pdev, dev); break; - case PCI_DEVICE_ID_RDK2: net2272_rdk2_remove(pdev, dev); break; - default: BUG(); - } - - pci_disable_device(pdev); - - usb_put_gadget(&dev->gadget); -} - -/* Table of matching PCI IDs */ -static struct pci_device_id pci_ids[] = { - { /* RDK 1 card */ - .class = ((PCI_CLASS_BRIDGE_OTHER << 8) | 0xfe), - .class_mask = 0, - .vendor = PCI_VENDOR_ID_PLX, - .device = PCI_DEVICE_ID_RDK1, - .subvendor = PCI_ANY_ID, - .subdevice = PCI_ANY_ID, - }, - { /* RDK 2 card */ - .class = ((PCI_CLASS_BRIDGE_OTHER << 8) | 0xfe), - .class_mask = 0, - .vendor = PCI_VENDOR_ID_PLX, - .device = PCI_DEVICE_ID_RDK2, - .subvendor = PCI_ANY_ID, - .subdevice = PCI_ANY_ID, - }, - { } -}; -MODULE_DEVICE_TABLE(pci, pci_ids); - -static struct pci_driver net2272_pci_driver = { - .name = driver_name, - .id_table = pci_ids, - - .probe = net2272_pci_probe, - .remove = net2272_pci_remove, -}; - -static int net2272_pci_register(void) -{ - return pci_register_driver(&net2272_pci_driver); -} - -static void net2272_pci_unregister(void) -{ - pci_unregister_driver(&net2272_pci_driver); -} - -#else -static inline int net2272_pci_register(void) { return 0; } -static inline void net2272_pci_unregister(void) { } -#endif - -/*---------------------------------------------------------------------------*/ - -static int -net2272_plat_probe(struct platform_device *pdev) -{ - struct net2272 *dev; - int ret; - unsigned int irqflags; - resource_size_t base, len; - struct resource *iomem, *iomem_bus, *irq_res; - - irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - iomem_bus = platform_get_resource(pdev, IORESOURCE_BUS, 0); - if (!irq_res || !iomem) { - dev_err(&pdev->dev, "must provide irq/base addr"); - return -EINVAL; - } - - dev = net2272_probe_init(&pdev->dev, irq_res->start); - if (IS_ERR(dev)) - return PTR_ERR(dev); - - irqflags = 0; - if (irq_res->flags & IORESOURCE_IRQ_HIGHEDGE) - irqflags |= IRQF_TRIGGER_RISING; - if (irq_res->flags & IORESOURCE_IRQ_LOWEDGE) - irqflags |= IRQF_TRIGGER_FALLING; - if (irq_res->flags & IORESOURCE_IRQ_HIGHLEVEL) - irqflags |= IRQF_TRIGGER_HIGH; - if (irq_res->flags & IORESOURCE_IRQ_LOWLEVEL) - irqflags |= IRQF_TRIGGER_LOW; - - base = iomem->start; - len = resource_size(iomem); - if (iomem_bus) - dev->base_shift = iomem_bus->start; - - if (!request_mem_region(base, len, driver_name)) { - dev_dbg(dev->dev, "get request memory region!\n"); - ret = -EBUSY; - goto err; - } - dev->base_addr = ioremap(base, len); - if (!dev->base_addr) { - dev_dbg(dev->dev, "can't map memory\n"); - ret = -EFAULT; - goto err_req; - } - - ret = net2272_probe_fin(dev, irqflags); - if (ret) - goto err_io; - - platform_set_drvdata(pdev, dev); - dev_info(&pdev->dev, "running in 16-bit, %sbyte swap local bus mode\n", - (net2272_read(dev, LOCCTL) & (1 << BYTE_SWAP)) ? "" : "no "); - - return 0; - - err_io: - iounmap(dev->base_addr); - err_req: - release_mem_region(base, len); - err: - usb_put_gadget(&dev->gadget); - - return ret; -} - -static void -net2272_plat_remove(struct platform_device *pdev) -{ - struct net2272 *dev = platform_get_drvdata(pdev); - - net2272_remove(dev); - - release_mem_region(pdev->resource[0].start, - resource_size(&pdev->resource[0])); - - usb_put_gadget(&dev->gadget); -} - -static struct platform_driver net2272_plat_driver = { - .probe = net2272_plat_probe, - .remove = net2272_plat_remove, - .driver = { - .name = driver_name, - }, - /* FIXME .suspend, .resume */ -}; -MODULE_ALIAS("platform:net2272"); - -static int __init net2272_init(void) -{ - int ret; - - ret = net2272_pci_register(); - if (ret) - return ret; - ret = platform_driver_register(&net2272_plat_driver); - if (ret) - goto err_pci; - return ret; - -err_pci: - net2272_pci_unregister(); - return ret; -} -module_init(net2272_init); - -static void __exit net2272_cleanup(void) -{ - net2272_pci_unregister(); - platform_driver_unregister(&net2272_plat_driver); -} -module_exit(net2272_cleanup); - -MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_AUTHOR("PLX Technology, Inc."); -MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/udc/net2272.h b/drivers/usb/gadget/udc/net2272.h deleted file mode 100644 index a9994f737588..000000000000 --- a/drivers/usb/gadget/udc/net2272.h +++ /dev/null @@ -1,584 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * PLX NET2272 high/full speed USB device controller - * - * Copyright (C) 2005-2006 PLX Technology, Inc. - * Copyright (C) 2006-2011 Analog Devices, Inc. - */ - -#ifndef __NET2272_H__ -#define __NET2272_H__ - -/* Main Registers */ -#define REGADDRPTR 0x00 -#define REGDATA 0x01 -#define IRQSTAT0 0x02 -#define ENDPOINT_0_INTERRUPT 0 -#define ENDPOINT_A_INTERRUPT 1 -#define ENDPOINT_B_INTERRUPT 2 -#define ENDPOINT_C_INTERRUPT 3 -#define VIRTUALIZED_ENDPOINT_INTERRUPT 4 -#define SETUP_PACKET_INTERRUPT 5 -#define DMA_DONE_INTERRUPT 6 -#define SOF_INTERRUPT 7 -#define IRQSTAT1 0x03 -#define CONTROL_STATUS_INTERRUPT 1 -#define VBUS_INTERRUPT 2 -#define SUSPEND_REQUEST_INTERRUPT 3 -#define SUSPEND_REQUEST_CHANGE_INTERRUPT 4 -#define RESUME_INTERRUPT 5 -#define ROOT_PORT_RESET_INTERRUPT 6 -#define RESET_STATUS 7 -#define PAGESEL 0x04 -#define DMAREQ 0x1c -#define DMA_ENDPOINT_SELECT 0 -#define DREQ_POLARITY 1 -#define DACK_POLARITY 2 -#define EOT_POLARITY 3 -#define DMA_CONTROL_DACK 4 -#define DMA_REQUEST_ENABLE 5 -#define DMA_REQUEST 6 -#define DMA_BUFFER_VALID 7 -#define SCRATCH 0x1d -#define IRQENB0 0x20 -#define ENDPOINT_0_INTERRUPT_ENABLE 0 -#define ENDPOINT_A_INTERRUPT_ENABLE 1 -#define ENDPOINT_B_INTERRUPT_ENABLE 2 -#define ENDPOINT_C_INTERRUPT_ENABLE 3 -#define VIRTUALIZED_ENDPOINT_INTERRUPT_ENABLE 4 -#define SETUP_PACKET_INTERRUPT_ENABLE 5 -#define DMA_DONE_INTERRUPT_ENABLE 6 -#define SOF_INTERRUPT_ENABLE 7 -#define IRQENB1 0x21 -#define VBUS_INTERRUPT_ENABLE 2 -#define SUSPEND_REQUEST_INTERRUPT_ENABLE 3 -#define SUSPEND_REQUEST_CHANGE_INTERRUPT_ENABLE 4 -#define RESUME_INTERRUPT_ENABLE 5 -#define ROOT_PORT_RESET_INTERRUPT_ENABLE 6 -#define LOCCTL 0x22 -#define DATA_WIDTH 0 -#define LOCAL_CLOCK_OUTPUT 1 -#define LOCAL_CLOCK_OUTPUT_OFF 0 -#define LOCAL_CLOCK_OUTPUT_3_75MHZ 1 -#define LOCAL_CLOCK_OUTPUT_7_5MHZ 2 -#define LOCAL_CLOCK_OUTPUT_15MHZ 3 -#define LOCAL_CLOCK_OUTPUT_30MHZ 4 -#define LOCAL_CLOCK_OUTPUT_60MHZ 5 -#define DMA_SPLIT_BUS_MODE 4 -#define BYTE_SWAP 5 -#define BUFFER_CONFIGURATION 6 -#define BUFFER_CONFIGURATION_EPA512_EPB512 0 -#define BUFFER_CONFIGURATION_EPA1024_EPB512 1 -#define BUFFER_CONFIGURATION_EPA1024_EPB1024 2 -#define BUFFER_CONFIGURATION_EPA1024DB 3 -#define CHIPREV_LEGACY 0x23 -#define NET2270_LEGACY_REV 0x40 -#define LOCCTL1 0x24 -#define DMA_MODE 0 -#define SLOW_DREQ 0 -#define FAST_DREQ 1 -#define BURST_MODE 2 -#define DMA_DACK_ENABLE 2 -#define CHIPREV_2272 0x25 -#define CHIPREV_NET2272_R1 0x10 -#define CHIPREV_NET2272_R1A 0x11 -/* USB Registers */ -#define USBCTL0 0x18 -#define IO_WAKEUP_ENABLE 1 -#define USB_DETECT_ENABLE 3 -#define USB_ROOT_PORT_WAKEUP_ENABLE 5 -#define USBCTL1 0x19 -#define VBUS_PIN 0 -#define USB_FULL_SPEED 1 -#define USB_HIGH_SPEED 2 -#define GENERATE_RESUME 3 -#define VIRTUAL_ENDPOINT_ENABLE 4 -#define FRAME0 0x1a -#define FRAME1 0x1b -#define OURADDR 0x30 -#define FORCE_IMMEDIATE 7 -#define USBDIAG 0x31 -#define FORCE_TRANSMIT_CRC_ERROR 0 -#define PREVENT_TRANSMIT_BIT_STUFF 1 -#define FORCE_RECEIVE_ERROR 2 -#define FAST_TIMES 4 -#define USBTEST 0x32 -#define TEST_MODE_SELECT 0 -#define NORMAL_OPERATION 0 -#define XCVRDIAG 0x33 -#define FORCE_FULL_SPEED 2 -#define FORCE_HIGH_SPEED 3 -#define OPMODE 4 -#define NORMAL_OPERATION 0 -#define NON_DRIVING 1 -#define DISABLE_BITSTUFF_AND_NRZI_ENCODE 2 -#define LINESTATE 6 -#define SE0_STATE 0 -#define J_STATE 1 -#define K_STATE 2 -#define SE1_STATE 3 -#define VIRTOUT0 0x34 -#define VIRTOUT1 0x35 -#define VIRTIN0 0x36 -#define VIRTIN1 0x37 -#define SETUP0 0x40 -#define SETUP1 0x41 -#define SETUP2 0x42 -#define SETUP3 0x43 -#define SETUP4 0x44 -#define SETUP5 0x45 -#define SETUP6 0x46 -#define SETUP7 0x47 -/* Endpoint Registers (Paged via PAGESEL) */ -#define EP_DATA 0x05 -#define EP_STAT0 0x06 -#define DATA_IN_TOKEN_INTERRUPT 0 -#define DATA_OUT_TOKEN_INTERRUPT 1 -#define DATA_PACKET_TRANSMITTED_INTERRUPT 2 -#define DATA_PACKET_RECEIVED_INTERRUPT 3 -#define SHORT_PACKET_TRANSFERRED_INTERRUPT 4 -#define NAK_OUT_PACKETS 5 -#define BUFFER_EMPTY 6 -#define BUFFER_FULL 7 -#define EP_STAT1 0x07 -#define TIMEOUT 0 -#define USB_OUT_ACK_SENT 1 -#define USB_OUT_NAK_SENT 2 -#define USB_IN_ACK_RCVD 3 -#define USB_IN_NAK_SENT 4 -#define USB_STALL_SENT 5 -#define LOCAL_OUT_ZLP 6 -#define BUFFER_FLUSH 7 -#define EP_TRANSFER0 0x08 -#define EP_TRANSFER1 0x09 -#define EP_TRANSFER2 0x0a -#define EP_IRQENB 0x0b -#define DATA_IN_TOKEN_INTERRUPT_ENABLE 0 -#define DATA_OUT_TOKEN_INTERRUPT_ENABLE 1 -#define DATA_PACKET_TRANSMITTED_INTERRUPT_ENABLE 2 -#define DATA_PACKET_RECEIVED_INTERRUPT_ENABLE 3 -#define SHORT_PACKET_TRANSFERRED_INTERRUPT_ENABLE 4 -#define EP_AVAIL0 0x0c -#define EP_AVAIL1 0x0d -#define EP_RSPCLR 0x0e -#define EP_RSPSET 0x0f -#define ENDPOINT_HALT 0 -#define ENDPOINT_TOGGLE 1 -#define NAK_OUT_PACKETS_MODE 2 -#define CONTROL_STATUS_PHASE_HANDSHAKE 3 -#define INTERRUPT_MODE 4 -#define AUTOVALIDATE 5 -#define HIDE_STATUS_PHASE 6 -#define ALT_NAK_OUT_PACKETS 7 -#define EP_MAXPKT0 0x28 -#define EP_MAXPKT1 0x29 -#define ADDITIONAL_TRANSACTION_OPPORTUNITIES 3 -#define NONE_ADDITIONAL_TRANSACTION 0 -#define ONE_ADDITIONAL_TRANSACTION 1 -#define TWO_ADDITIONAL_TRANSACTION 2 -#define EP_CFG 0x2a -#define ENDPOINT_NUMBER 0 -#define ENDPOINT_DIRECTION 4 -#define ENDPOINT_TYPE 5 -#define ENDPOINT_ENABLE 7 -#define EP_HBW 0x2b -#define HIGH_BANDWIDTH_OUT_TRANSACTION_PID 0 -#define DATA0_PID 0 -#define DATA1_PID 1 -#define DATA2_PID 2 -#define MDATA_PID 3 -#define EP_BUFF_STATES 0x2c -#define BUFFER_A_STATE 0 -#define BUFFER_B_STATE 2 -#define BUFF_FREE 0 -#define BUFF_VALID 1 -#define BUFF_LCL 2 -#define BUFF_USB 3 - -/*---------------------------------------------------------------------------*/ - -#define PCI_DEVICE_ID_RDK1 0x9054 - -/* PCI-RDK EPLD Registers */ -#define RDK_EPLD_IO_REGISTER1 0x00000000 -#define RDK_EPLD_USB_RESET 0 -#define RDK_EPLD_USB_POWERDOWN 1 -#define RDK_EPLD_USB_WAKEUP 2 -#define RDK_EPLD_USB_EOT 3 -#define RDK_EPLD_DPPULL 4 -#define RDK_EPLD_IO_REGISTER2 0x00000004 -#define RDK_EPLD_BUSWIDTH 0 -#define RDK_EPLD_USER 2 -#define RDK_EPLD_RESET_INTERRUPT_ENABLE 3 -#define RDK_EPLD_DMA_TIMEOUT_ENABLE 4 -#define RDK_EPLD_STATUS_REGISTER 0x00000008 -#define RDK_EPLD_USB_LRESET 0 -#define RDK_EPLD_REVISION_REGISTER 0x0000000c - -/* PCI-RDK PLX 9054 Registers */ -#define INTCSR 0x68 -#define PCI_INTERRUPT_ENABLE 8 -#define LOCAL_INTERRUPT_INPUT_ENABLE 11 -#define LOCAL_INPUT_INTERRUPT_ACTIVE 15 -#define LOCAL_DMA_CHANNEL_0_INTERRUPT_ENABLE 18 -#define LOCAL_DMA_CHANNEL_1_INTERRUPT_ENABLE 19 -#define DMA_CHANNEL_0_INTERRUPT_ACTIVE 21 -#define DMA_CHANNEL_1_INTERRUPT_ACTIVE 22 -#define CNTRL 0x6C -#define RELOAD_CONFIGURATION_REGISTERS 29 -#define PCI_ADAPTER_SOFTWARE_RESET 30 -#define DMAMODE0 0x80 -#define LOCAL_BUS_WIDTH 0 -#define INTERNAL_WAIT_STATES 2 -#define TA_READY_INPUT_ENABLE 6 -#define LOCAL_BURST_ENABLE 8 -#define SCATTER_GATHER_MODE 9 -#define DONE_INTERRUPT_ENABLE 10 -#define LOCAL_ADDRESSING_MODE 11 -#define DEMAND_MODE 12 -#define DMA_EOT_ENABLE 14 -#define FAST_SLOW_TERMINATE_MODE_SELECT 15 -#define DMA_CHANNEL_INTERRUPT_SELECT 17 -#define DMAPADR0 0x84 -#define DMALADR0 0x88 -#define DMASIZ0 0x8c -#define DMADPR0 0x90 -#define DESCRIPTOR_LOCATION 0 -#define END_OF_CHAIN 1 -#define INTERRUPT_AFTER_TERMINAL_COUNT 2 -#define DIRECTION_OF_TRANSFER 3 -#define DMACSR0 0xa8 -#define CHANNEL_ENABLE 0 -#define CHANNEL_START 1 -#define CHANNEL_ABORT 2 -#define CHANNEL_CLEAR_INTERRUPT 3 -#define CHANNEL_DONE 4 -#define DMATHR 0xb0 -#define LBRD1 0xf8 -#define MEMORY_SPACE_LOCAL_BUS_WIDTH 0 -#define W8_BIT 0 -#define W16_BIT 1 - -/* Special OR'ing of INTCSR bits */ -#define LOCAL_INTERRUPT_TEST \ - ((1 << LOCAL_INPUT_INTERRUPT_ACTIVE) | \ - (1 << LOCAL_INTERRUPT_INPUT_ENABLE)) - -#define DMA_CHANNEL_0_TEST \ - ((1 << DMA_CHANNEL_0_INTERRUPT_ACTIVE) | \ - (1 << LOCAL_DMA_CHANNEL_0_INTERRUPT_ENABLE)) - -#define DMA_CHANNEL_1_TEST \ - ((1 << DMA_CHANNEL_1_INTERRUPT_ACTIVE) | \ - (1 << LOCAL_DMA_CHANNEL_1_INTERRUPT_ENABLE)) - -/* EPLD Registers */ -#define RDK_EPLD_IO_REGISTER1 0x00000000 -#define RDK_EPLD_USB_RESET 0 -#define RDK_EPLD_USB_POWERDOWN 1 -#define RDK_EPLD_USB_WAKEUP 2 -#define RDK_EPLD_USB_EOT 3 -#define RDK_EPLD_DPPULL 4 -#define RDK_EPLD_IO_REGISTER2 0x00000004 -#define RDK_EPLD_BUSWIDTH 0 -#define RDK_EPLD_USER 2 -#define RDK_EPLD_RESET_INTERRUPT_ENABLE 3 -#define RDK_EPLD_DMA_TIMEOUT_ENABLE 4 -#define RDK_EPLD_STATUS_REGISTER 0x00000008 -#define RDK_EPLD_USB_LRESET 0 -#define RDK_EPLD_REVISION_REGISTER 0x0000000c - -#define EPLD_IO_CONTROL_REGISTER 0x400 -#define NET2272_RESET 0 -#define BUSWIDTH 1 -#define MPX_MODE 3 -#define USER 4 -#define DMA_TIMEOUT_ENABLE 5 -#define DMA_CTL_DACK 6 -#define EPLD_DMA_ENABLE 7 -#define EPLD_DMA_CONTROL_REGISTER 0x800 -#define SPLIT_DMA_MODE 0 -#define SPLIT_DMA_DIRECTION 1 -#define SPLIT_DMA_ENABLE 2 -#define SPLIT_DMA_INTERRUPT_ENABLE 3 -#define SPLIT_DMA_INTERRUPT 4 -#define EPLD_DMA_MODE 5 -#define EPLD_DMA_CONTROLLER_ENABLE 7 -#define SPLIT_DMA_ADDRESS_LOW 0xc00 -#define SPLIT_DMA_ADDRESS_HIGH 0x1000 -#define SPLIT_DMA_BYTE_COUNT_LOW 0x1400 -#define SPLIT_DMA_BYTE_COUNT_HIGH 0x1800 -#define EPLD_REVISION_REGISTER 0x1c00 -#define SPLIT_DMA_RAM 0x4000 -#define DMA_RAM_SIZE 0x1000 - -/*---------------------------------------------------------------------------*/ - -#define PCI_DEVICE_ID_RDK2 0x3272 - -/* PCI-RDK version 2 registers */ - -/* Main Control Registers */ - -#define RDK2_IRQENB 0x00 -#define RDK2_IRQSTAT 0x04 -#define PB7 23 -#define PB6 22 -#define PB5 21 -#define PB4 20 -#define PB3 19 -#define PB2 18 -#define PB1 17 -#define PB0 16 -#define GP3 23 -#define GP2 23 -#define GP1 23 -#define GP0 23 -#define DMA_RETRY_ABORT 6 -#define DMA_PAUSE_DONE 5 -#define DMA_ABORT_DONE 4 -#define DMA_OUT_FIFO_TRANSFER_DONE 3 -#define DMA_LOCAL_DONE 2 -#define DMA_PCI_DONE 1 -#define NET2272_PCI_IRQ 0 - -#define RDK2_LOCCTLRDK 0x08 -#define CHIP_RESET 3 -#define SPLIT_DMA 2 -#define MULTIPLEX_MODE 1 -#define BUS_WIDTH 0 - -#define RDK2_GPIOCTL 0x10 -#define GP3_OUT_ENABLE 7 -#define GP2_OUT_ENABLE 6 -#define GP1_OUT_ENABLE 5 -#define GP0_OUT_ENABLE 4 -#define GP3_DATA 3 -#define GP2_DATA 2 -#define GP1_DATA 1 -#define GP0_DATA 0 - -#define RDK2_LEDSW 0x14 -#define LED3 27 -#define LED2 26 -#define LED1 25 -#define LED0 24 -#define PBUTTON 16 -#define DIPSW 0 - -#define RDK2_DIAG 0x18 -#define RDK2_FAST_TIMES 2 -#define FORCE_PCI_SERR 1 -#define FORCE_PCI_INT 0 -#define RDK2_FPGAREV 0x1C - -/* Dma Control registers */ -#define RDK2_DMACTL 0x80 -#define ADDR_HOLD 24 -#define RETRY_COUNT 16 /* 23:16 */ -#define FIFO_THRESHOLD 11 /* 15:11 */ -#define MEM_WRITE_INVALIDATE 10 -#define READ_MULTIPLE 9 -#define READ_LINE 8 -#define RDK2_DMA_MODE 6 /* 7:6 */ -#define CONTROL_DACK 5 -#define EOT_ENABLE 4 -#define EOT_POLARITY 3 -#define DACK_POLARITY 2 -#define DREQ_POLARITY 1 -#define DMA_ENABLE 0 - -#define RDK2_DMASTAT 0x84 -#define GATHER_COUNT 12 /* 14:12 */ -#define FIFO_COUNT 6 /* 11:6 */ -#define FIFO_FLUSH 5 -#define FIFO_TRANSFER 4 -#define PAUSE_DONE 3 -#define ABORT_DONE 2 -#define DMA_ABORT 1 -#define DMA_START 0 - -#define RDK2_DMAPCICOUNT 0x88 -#define DMA_DIRECTION 31 -#define DMA_PCI_BYTE_COUNT 0 /* 0:23 */ - -#define RDK2_DMALOCCOUNT 0x8C /* 0:23 dma local byte count */ - -#define RDK2_DMAADDR 0x90 /* 2:31 PCI bus starting address */ - -/*---------------------------------------------------------------------------*/ - -#define REG_INDEXED_THRESHOLD (1 << 5) - -/* DRIVER DATA STRUCTURES and UTILITIES */ -struct net2272_ep { - struct usb_ep ep; - struct net2272 *dev; - unsigned long irqs; - - /* analogous to a host-side qh */ - struct list_head queue; - const struct usb_endpoint_descriptor *desc; - unsigned num:8, - fifo_size:12, - stopped:1, - wedged:1, - is_in:1, - is_iso:1, - dma:1, - not_empty:1; -}; - -struct net2272 { - /* each device provides one gadget, several endpoints */ - struct usb_gadget gadget; - struct device *dev; - unsigned short dev_id; - - spinlock_t lock; - struct net2272_ep ep[4]; - struct usb_gadget_driver *driver; - unsigned protocol_stall:1, - softconnect:1, - wakeup:1, - added:1, - async_callbacks:1, - dma_eot_polarity:1, - dma_dack_polarity:1, - dma_dreq_polarity:1, - dma_busy:1; - u16 chiprev; - u8 pagesel; - - unsigned int irq; - unsigned short fifo_mode; - - unsigned int base_shift; - u16 __iomem *base_addr; - union { -#ifdef CONFIG_USB_PCI - struct { - void __iomem *plx9054_base_addr; - void __iomem *epld_base_addr; - } rdk1; - struct { - /* Bar0, Bar1 is base_addr both mem-mapped */ - void __iomem *fpga_base_addr; - } rdk2; -#endif - }; -}; - -static void __iomem * -net2272_reg_addr(struct net2272 *dev, unsigned int reg) -{ - return dev->base_addr + (reg << dev->base_shift); -} - -static void -net2272_write(struct net2272 *dev, unsigned int reg, u8 value) -{ - if (reg >= REG_INDEXED_THRESHOLD) { - /* - * Indexed register; use REGADDRPTR/REGDATA - * - Save and restore REGADDRPTR. This prevents REGADDRPTR from - * changes between other code sections, but it is time consuming. - * - Performance tips: either do not save and restore REGADDRPTR (if it - * is safe) or do save/restore operations only in critical sections. - u8 tmp = readb(dev->base_addr + REGADDRPTR); - */ - writeb((u8)reg, net2272_reg_addr(dev, REGADDRPTR)); - writeb(value, net2272_reg_addr(dev, REGDATA)); - /* writeb(tmp, net2272_reg_addr(dev, REGADDRPTR)); */ - } else - writeb(value, net2272_reg_addr(dev, reg)); -} - -static u8 -net2272_read(struct net2272 *dev, unsigned int reg) -{ - u8 ret; - - if (reg >= REG_INDEXED_THRESHOLD) { - /* - * Indexed register; use REGADDRPTR/REGDATA - * - Save and restore REGADDRPTR. This prevents REGADDRPTR from - * changes between other code sections, but it is time consuming. - * - Performance tips: either do not save and restore REGADDRPTR (if it - * is safe) or do save/restore operations only in critical sections. - u8 tmp = readb(dev->base_addr + REGADDRPTR); - */ - writeb((u8)reg, net2272_reg_addr(dev, REGADDRPTR)); - ret = readb(net2272_reg_addr(dev, REGDATA)); - /* writeb(tmp, net2272_reg_addr(dev, REGADDRPTR)); */ - } else - ret = readb(net2272_reg_addr(dev, reg)); - - return ret; -} - -static void -net2272_ep_write(struct net2272_ep *ep, unsigned int reg, u8 value) -{ - struct net2272 *dev = ep->dev; - - if (dev->pagesel != ep->num) { - net2272_write(dev, PAGESEL, ep->num); - dev->pagesel = ep->num; - } - net2272_write(dev, reg, value); -} - -static u8 -net2272_ep_read(struct net2272_ep *ep, unsigned int reg) -{ - struct net2272 *dev = ep->dev; - - if (dev->pagesel != ep->num) { - net2272_write(dev, PAGESEL, ep->num); - dev->pagesel = ep->num; - } - return net2272_read(dev, reg); -} - -static void allow_status(struct net2272_ep *ep) -{ - /* ep0 only */ - net2272_ep_write(ep, EP_RSPCLR, - (1 << CONTROL_STATUS_PHASE_HANDSHAKE) | - (1 << ALT_NAK_OUT_PACKETS) | - (1 << NAK_OUT_PACKETS_MODE)); - ep->stopped = 1; -} - -static void set_halt(struct net2272_ep *ep) -{ - /* ep0 and bulk/intr endpoints */ - net2272_ep_write(ep, EP_RSPCLR, 1 << CONTROL_STATUS_PHASE_HANDSHAKE); - net2272_ep_write(ep, EP_RSPSET, 1 << ENDPOINT_HALT); -} - -static void clear_halt(struct net2272_ep *ep) -{ - /* ep0 and bulk/intr endpoints */ - net2272_ep_write(ep, EP_RSPCLR, - (1 << ENDPOINT_HALT) | (1 << ENDPOINT_TOGGLE)); -} - -/* count (<= 4) bytes in the next fifo write will be valid */ -static void set_fifo_bytecount(struct net2272_ep *ep, unsigned count) -{ - /* net2272_ep_write will truncate to u8 for us */ - net2272_ep_write(ep, EP_TRANSFER2, count >> 16); - net2272_ep_write(ep, EP_TRANSFER1, count >> 8); - net2272_ep_write(ep, EP_TRANSFER0, count); -} - -struct net2272_request { - struct usb_request req; - struct list_head queue; - unsigned mapped:1, - valid:1; -}; - -#endif diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 5f629d7cad64..b7acf3966cd7 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -126,18 +126,6 @@ config USB_ISP1301 To compile this driver as a module, choose M here: the module will be called phy-isp1301. -config USB_MV_OTG - tristate "Marvell USB OTG support" - depends on USB_EHCI_MV && USB_MV_UDC && PM && USB_OTG - depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't be 'y' - select USB_PHY - help - Say Y here if you want to build Marvell USB OTG transceiver - driver in kernel (including PXA and MMP series). This driver - implements role switch between EHCI host driver and gadget driver. - - To compile this driver as a module, choose M here. - config USB_MXS_PHY tristate "Freescale MXS USB PHY support" depends on ARCH_MXC || ARCH_MXS diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile index e5d619b4d8f6..ceae46c5341d 100644 --- a/drivers/usb/phy/Makefile +++ b/drivers/usb/phy/Makefile @@ -18,7 +18,6 @@ obj-$(CONFIG_TWL6030_USB) += phy-twl6030-usb.o obj-$(CONFIG_USB_TEGRA_PHY) += phy-tegra-usb.o obj-$(CONFIG_USB_GPIO_VBUS) += phy-gpio-vbus-usb.o obj-$(CONFIG_USB_ISP1301) += phy-isp1301.o -obj-$(CONFIG_USB_MV_OTG) += phy-mv-usb.o obj-$(CONFIG_USB_MXS_PHY) += phy-mxs-usb.o obj-$(CONFIG_USB_ULPI) += phy-ulpi.o obj-$(CONFIG_USB_ULPI_VIEWPORT) += phy-ulpi-viewport.o diff --git a/drivers/usb/phy/phy-mv-usb.c b/drivers/usb/phy/phy-mv-usb.c deleted file mode 100644 index 638fba58420c..000000000000 --- a/drivers/usb/phy/phy-mv-usb.c +++ /dev/null @@ -1,881 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright (C) 2011 Marvell International Ltd. All rights reserved. - * Author: Chao Xie - * Neil Zhang - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "phy-mv-usb.h" - -#define DRIVER_DESC "Marvell USB OTG transceiver driver" - -MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_LICENSE("GPL"); - -static const char driver_name[] = "mv-otg"; - -static char *state_string[] = { - "undefined", - "b_idle", - "b_srp_init", - "b_peripheral", - "b_wait_acon", - "b_host", - "a_idle", - "a_wait_vrise", - "a_wait_bcon", - "a_host", - "a_suspend", - "a_peripheral", - "a_wait_vfall", - "a_vbus_err" -}; - -static int mv_otg_set_vbus(struct usb_otg *otg, bool on) -{ - struct mv_otg *mvotg = container_of(otg->usb_phy, struct mv_otg, phy); - if (mvotg->pdata->set_vbus == NULL) - return -ENODEV; - - return mvotg->pdata->set_vbus(on); -} - -static int mv_otg_set_host(struct usb_otg *otg, - struct usb_bus *host) -{ - otg->host = host; - - return 0; -} - -static int mv_otg_set_peripheral(struct usb_otg *otg, - struct usb_gadget *gadget) -{ - otg->gadget = gadget; - - return 0; -} - -static void mv_otg_run_state_machine(struct mv_otg *mvotg, - unsigned long delay) -{ - dev_dbg(&mvotg->pdev->dev, "transceiver is updated\n"); - if (!mvotg->qwork) - return; - - queue_delayed_work(mvotg->qwork, &mvotg->work, delay); -} - -static void mv_otg_timer_await_bcon(struct timer_list *t) -{ - struct mv_otg *mvotg = from_timer(mvotg, t, - otg_ctrl.timer[A_WAIT_BCON_TIMER]); - - mvotg->otg_ctrl.a_wait_bcon_timeout = 1; - - dev_info(&mvotg->pdev->dev, "B Device No Response!\n"); - - if (spin_trylock(&mvotg->wq_lock)) { - mv_otg_run_state_machine(mvotg, 0); - spin_unlock(&mvotg->wq_lock); - } -} - -static int mv_otg_cancel_timer(struct mv_otg *mvotg, unsigned int id) -{ - struct timer_list *timer; - - if (id >= OTG_TIMER_NUM) - return -EINVAL; - - timer = &mvotg->otg_ctrl.timer[id]; - - if (timer_pending(timer)) - timer_delete(timer); - - return 0; -} - -static int mv_otg_set_timer(struct mv_otg *mvotg, unsigned int id, - unsigned long interval) -{ - struct timer_list *timer; - - if (id >= OTG_TIMER_NUM) - return -EINVAL; - - timer = &mvotg->otg_ctrl.timer[id]; - if (timer_pending(timer)) { - dev_err(&mvotg->pdev->dev, "Timer%d is already running\n", id); - return -EBUSY; - } - - timer->expires = jiffies + interval; - add_timer(timer); - - return 0; -} - -static int mv_otg_reset(struct mv_otg *mvotg) -{ - u32 tmp; - int ret; - - /* Stop the controller */ - tmp = readl(&mvotg->op_regs->usbcmd); - tmp &= ~USBCMD_RUN_STOP; - writel(tmp, &mvotg->op_regs->usbcmd); - - /* Reset the controller to get default values */ - writel(USBCMD_CTRL_RESET, &mvotg->op_regs->usbcmd); - - ret = readl_poll_timeout_atomic(&mvotg->op_regs->usbcmd, tmp, - (tmp & USBCMD_CTRL_RESET), 10, 10000); - if (ret < 0) { - dev_err(&mvotg->pdev->dev, - "Wait for RESET completed TIMEOUT\n"); - return ret; - } - - writel(0x0, &mvotg->op_regs->usbintr); - tmp = readl(&mvotg->op_regs->usbsts); - writel(tmp, &mvotg->op_regs->usbsts); - - return 0; -} - -static void mv_otg_init_irq(struct mv_otg *mvotg) -{ - u32 otgsc; - - mvotg->irq_en = OTGSC_INTR_A_SESSION_VALID - | OTGSC_INTR_A_VBUS_VALID; - mvotg->irq_status = OTGSC_INTSTS_A_SESSION_VALID - | OTGSC_INTSTS_A_VBUS_VALID; - - if (mvotg->pdata->vbus == NULL) { - mvotg->irq_en |= OTGSC_INTR_B_SESSION_VALID - | OTGSC_INTR_B_SESSION_END; - mvotg->irq_status |= OTGSC_INTSTS_B_SESSION_VALID - | OTGSC_INTSTS_B_SESSION_END; - } - - if (mvotg->pdata->id == NULL) { - mvotg->irq_en |= OTGSC_INTR_USB_ID; - mvotg->irq_status |= OTGSC_INTSTS_USB_ID; - } - - otgsc = readl(&mvotg->op_regs->otgsc); - otgsc |= mvotg->irq_en; - writel(otgsc, &mvotg->op_regs->otgsc); -} - -static void mv_otg_start_host(struct mv_otg *mvotg, int on) -{ -#ifdef CONFIG_USB - struct usb_otg *otg = mvotg->phy.otg; - struct usb_hcd *hcd; - - if (!otg->host) - return; - - dev_info(&mvotg->pdev->dev, "%s host\n", on ? "start" : "stop"); - - hcd = bus_to_hcd(otg->host); - - if (on) { - usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); - device_wakeup_enable(hcd->self.controller); - } else { - usb_remove_hcd(hcd); - } -#endif /* CONFIG_USB */ -} - -static void mv_otg_start_periphrals(struct mv_otg *mvotg, int on) -{ - struct usb_otg *otg = mvotg->phy.otg; - - if (!otg->gadget) - return; - - dev_info(mvotg->phy.dev, "gadget %s\n", str_on_off(on)); - - if (on) - usb_gadget_vbus_connect(otg->gadget); - else - usb_gadget_vbus_disconnect(otg->gadget); -} - -static void otg_clock_enable(struct mv_otg *mvotg) -{ - clk_prepare_enable(mvotg->clk); -} - -static void otg_clock_disable(struct mv_otg *mvotg) -{ - clk_disable_unprepare(mvotg->clk); -} - -static int mv_otg_enable_internal(struct mv_otg *mvotg) -{ - int retval = 0; - - if (mvotg->active) - return 0; - - dev_dbg(&mvotg->pdev->dev, "otg enabled\n"); - - otg_clock_enable(mvotg); - if (mvotg->pdata->phy_init) { - retval = mvotg->pdata->phy_init(mvotg->phy_regs); - if (retval) { - dev_err(&mvotg->pdev->dev, - "init phy error %d\n", retval); - otg_clock_disable(mvotg); - return retval; - } - } - mvotg->active = 1; - - return 0; - -} - -static int mv_otg_enable(struct mv_otg *mvotg) -{ - if (mvotg->clock_gating) - return mv_otg_enable_internal(mvotg); - - return 0; -} - -static void mv_otg_disable_internal(struct mv_otg *mvotg) -{ - if (mvotg->active) { - dev_dbg(&mvotg->pdev->dev, "otg disabled\n"); - if (mvotg->pdata->phy_deinit) - mvotg->pdata->phy_deinit(mvotg->phy_regs); - otg_clock_disable(mvotg); - mvotg->active = 0; - } -} - -static void mv_otg_disable(struct mv_otg *mvotg) -{ - if (mvotg->clock_gating) - mv_otg_disable_internal(mvotg); -} - -static void mv_otg_update_inputs(struct mv_otg *mvotg) -{ - struct mv_otg_ctrl *otg_ctrl = &mvotg->otg_ctrl; - u32 otgsc; - - otgsc = readl(&mvotg->op_regs->otgsc); - - if (mvotg->pdata->vbus) { - if (mvotg->pdata->vbus->poll() == VBUS_HIGH) { - otg_ctrl->b_sess_vld = 1; - otg_ctrl->b_sess_end = 0; - } else { - otg_ctrl->b_sess_vld = 0; - otg_ctrl->b_sess_end = 1; - } - } else { - otg_ctrl->b_sess_vld = !!(otgsc & OTGSC_STS_B_SESSION_VALID); - otg_ctrl->b_sess_end = !!(otgsc & OTGSC_STS_B_SESSION_END); - } - - if (mvotg->pdata->id) - otg_ctrl->id = !!mvotg->pdata->id->poll(); - else - otg_ctrl->id = !!(otgsc & OTGSC_STS_USB_ID); - - if (mvotg->pdata->otg_force_a_bus_req && !otg_ctrl->id) - otg_ctrl->a_bus_req = 1; - - otg_ctrl->a_sess_vld = !!(otgsc & OTGSC_STS_A_SESSION_VALID); - otg_ctrl->a_vbus_vld = !!(otgsc & OTGSC_STS_A_VBUS_VALID); - - dev_dbg(&mvotg->pdev->dev, "%s: ", __func__); - dev_dbg(&mvotg->pdev->dev, "id %d\n", otg_ctrl->id); - dev_dbg(&mvotg->pdev->dev, "b_sess_vld %d\n", otg_ctrl->b_sess_vld); - dev_dbg(&mvotg->pdev->dev, "b_sess_end %d\n", otg_ctrl->b_sess_end); - dev_dbg(&mvotg->pdev->dev, "a_vbus_vld %d\n", otg_ctrl->a_vbus_vld); - dev_dbg(&mvotg->pdev->dev, "a_sess_vld %d\n", otg_ctrl->a_sess_vld); -} - -static void mv_otg_update_state(struct mv_otg *mvotg) -{ - struct mv_otg_ctrl *otg_ctrl = &mvotg->otg_ctrl; - int old_state = mvotg->phy.otg->state; - - switch (old_state) { - case OTG_STATE_UNDEFINED: - mvotg->phy.otg->state = OTG_STATE_B_IDLE; - fallthrough; - case OTG_STATE_B_IDLE: - if (otg_ctrl->id == 0) - mvotg->phy.otg->state = OTG_STATE_A_IDLE; - else if (otg_ctrl->b_sess_vld) - mvotg->phy.otg->state = OTG_STATE_B_PERIPHERAL; - break; - case OTG_STATE_B_PERIPHERAL: - if (!otg_ctrl->b_sess_vld || otg_ctrl->id == 0) - mvotg->phy.otg->state = OTG_STATE_B_IDLE; - break; - case OTG_STATE_A_IDLE: - if (otg_ctrl->id) - mvotg->phy.otg->state = OTG_STATE_B_IDLE; - else if (!(otg_ctrl->a_bus_drop) && - (otg_ctrl->a_bus_req || otg_ctrl->a_srp_det)) - mvotg->phy.otg->state = OTG_STATE_A_WAIT_VRISE; - break; - case OTG_STATE_A_WAIT_VRISE: - if (otg_ctrl->a_vbus_vld) - mvotg->phy.otg->state = OTG_STATE_A_WAIT_BCON; - break; - case OTG_STATE_A_WAIT_BCON: - if (otg_ctrl->id || otg_ctrl->a_bus_drop - || otg_ctrl->a_wait_bcon_timeout) { - mv_otg_cancel_timer(mvotg, A_WAIT_BCON_TIMER); - mvotg->otg_ctrl.a_wait_bcon_timeout = 0; - mvotg->phy.otg->state = OTG_STATE_A_WAIT_VFALL; - otg_ctrl->a_bus_req = 0; - } else if (!otg_ctrl->a_vbus_vld) { - mv_otg_cancel_timer(mvotg, A_WAIT_BCON_TIMER); - mvotg->otg_ctrl.a_wait_bcon_timeout = 0; - mvotg->phy.otg->state = OTG_STATE_A_VBUS_ERR; - } else if (otg_ctrl->b_conn) { - mv_otg_cancel_timer(mvotg, A_WAIT_BCON_TIMER); - mvotg->otg_ctrl.a_wait_bcon_timeout = 0; - mvotg->phy.otg->state = OTG_STATE_A_HOST; - } - break; - case OTG_STATE_A_HOST: - if (otg_ctrl->id || !otg_ctrl->b_conn - || otg_ctrl->a_bus_drop) - mvotg->phy.otg->state = OTG_STATE_A_WAIT_BCON; - else if (!otg_ctrl->a_vbus_vld) - mvotg->phy.otg->state = OTG_STATE_A_VBUS_ERR; - break; - case OTG_STATE_A_WAIT_VFALL: - if (otg_ctrl->id - || (!otg_ctrl->b_conn && otg_ctrl->a_sess_vld) - || otg_ctrl->a_bus_req) - mvotg->phy.otg->state = OTG_STATE_A_IDLE; - break; - case OTG_STATE_A_VBUS_ERR: - if (otg_ctrl->id || otg_ctrl->a_clr_err - || otg_ctrl->a_bus_drop) { - otg_ctrl->a_clr_err = 0; - mvotg->phy.otg->state = OTG_STATE_A_WAIT_VFALL; - } - break; - default: - break; - } -} - -static void mv_otg_work(struct work_struct *work) -{ - struct mv_otg *mvotg; - struct usb_otg *otg; - int old_state; - - mvotg = container_of(to_delayed_work(work), struct mv_otg, work); - -run: - /* work queue is single thread, or we need spin_lock to protect */ - otg = mvotg->phy.otg; - old_state = otg->state; - - if (!mvotg->active) - return; - - mv_otg_update_inputs(mvotg); - mv_otg_update_state(mvotg); - - if (old_state != mvotg->phy.otg->state) { - dev_info(&mvotg->pdev->dev, "change from state %s to %s\n", - state_string[old_state], - state_string[mvotg->phy.otg->state]); - - switch (mvotg->phy.otg->state) { - case OTG_STATE_B_IDLE: - otg->default_a = 0; - if (old_state == OTG_STATE_B_PERIPHERAL) - mv_otg_start_periphrals(mvotg, 0); - mv_otg_reset(mvotg); - mv_otg_disable(mvotg); - usb_phy_set_event(&mvotg->phy, USB_EVENT_NONE); - break; - case OTG_STATE_B_PERIPHERAL: - mv_otg_enable(mvotg); - mv_otg_start_periphrals(mvotg, 1); - usb_phy_set_event(&mvotg->phy, USB_EVENT_ENUMERATED); - break; - case OTG_STATE_A_IDLE: - otg->default_a = 1; - mv_otg_enable(mvotg); - if (old_state == OTG_STATE_A_WAIT_VFALL) - mv_otg_start_host(mvotg, 0); - mv_otg_reset(mvotg); - break; - case OTG_STATE_A_WAIT_VRISE: - mv_otg_set_vbus(otg, 1); - break; - case OTG_STATE_A_WAIT_BCON: - if (old_state != OTG_STATE_A_HOST) - mv_otg_start_host(mvotg, 1); - mv_otg_set_timer(mvotg, A_WAIT_BCON_TIMER, - T_A_WAIT_BCON); - /* - * Now, we directly enter A_HOST. So set b_conn = 1 - * here. In fact, it need host driver to notify us. - */ - mvotg->otg_ctrl.b_conn = 1; - break; - case OTG_STATE_A_HOST: - break; - case OTG_STATE_A_WAIT_VFALL: - /* - * Now, we has exited A_HOST. So set b_conn = 0 - * here. In fact, it need host driver to notify us. - */ - mvotg->otg_ctrl.b_conn = 0; - mv_otg_set_vbus(otg, 0); - break; - case OTG_STATE_A_VBUS_ERR: - break; - default: - break; - } - goto run; - } -} - -static irqreturn_t mv_otg_irq(int irq, void *dev) -{ - struct mv_otg *mvotg = dev; - u32 otgsc; - - otgsc = readl(&mvotg->op_regs->otgsc); - writel(otgsc, &mvotg->op_regs->otgsc); - - /* - * if we have vbus, then the vbus detection for B-device - * will be done by mv_otg_inputs_irq(). - */ - if (mvotg->pdata->vbus) - if ((otgsc & OTGSC_STS_USB_ID) && - !(otgsc & OTGSC_INTSTS_USB_ID)) - return IRQ_NONE; - - if ((otgsc & mvotg->irq_status) == 0) - return IRQ_NONE; - - mv_otg_run_state_machine(mvotg, 0); - - return IRQ_HANDLED; -} - -static irqreturn_t mv_otg_inputs_irq(int irq, void *dev) -{ - struct mv_otg *mvotg = dev; - - /* The clock may disabled at this time */ - if (!mvotg->active) { - mv_otg_enable(mvotg); - mv_otg_init_irq(mvotg); - } - - mv_otg_run_state_machine(mvotg, 0); - - return IRQ_HANDLED; -} - -static ssize_t -a_bus_req_show(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct mv_otg *mvotg = dev_get_drvdata(dev); - return scnprintf(buf, PAGE_SIZE, "%d\n", - mvotg->otg_ctrl.a_bus_req); -} - -static ssize_t -a_bus_req_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct mv_otg *mvotg = dev_get_drvdata(dev); - - if (count > 2) - return -1; - - /* We will use this interface to change to A device */ - if (mvotg->phy.otg->state != OTG_STATE_B_IDLE - && mvotg->phy.otg->state != OTG_STATE_A_IDLE) - return -1; - - /* The clock may disabled and we need to set irq for ID detected */ - mv_otg_enable(mvotg); - mv_otg_init_irq(mvotg); - - if (buf[0] == '1') { - mvotg->otg_ctrl.a_bus_req = 1; - mvotg->otg_ctrl.a_bus_drop = 0; - dev_dbg(&mvotg->pdev->dev, - "User request: a_bus_req = 1\n"); - - if (spin_trylock(&mvotg->wq_lock)) { - mv_otg_run_state_machine(mvotg, 0); - spin_unlock(&mvotg->wq_lock); - } - } - - return count; -} - -static DEVICE_ATTR_RW(a_bus_req); - -static ssize_t -a_clr_err_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct mv_otg *mvotg = dev_get_drvdata(dev); - if (!mvotg->phy.otg->default_a) - return -1; - - if (count > 2) - return -1; - - if (buf[0] == '1') { - mvotg->otg_ctrl.a_clr_err = 1; - dev_dbg(&mvotg->pdev->dev, - "User request: a_clr_err = 1\n"); - } - - if (spin_trylock(&mvotg->wq_lock)) { - mv_otg_run_state_machine(mvotg, 0); - spin_unlock(&mvotg->wq_lock); - } - - return count; -} - -static DEVICE_ATTR_WO(a_clr_err); - -static ssize_t -a_bus_drop_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct mv_otg *mvotg = dev_get_drvdata(dev); - return scnprintf(buf, PAGE_SIZE, "%d\n", - mvotg->otg_ctrl.a_bus_drop); -} - -static ssize_t -a_bus_drop_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct mv_otg *mvotg = dev_get_drvdata(dev); - if (!mvotg->phy.otg->default_a) - return -1; - - if (count > 2) - return -1; - - if (buf[0] == '0') { - mvotg->otg_ctrl.a_bus_drop = 0; - dev_dbg(&mvotg->pdev->dev, - "User request: a_bus_drop = 0\n"); - } else if (buf[0] == '1') { - mvotg->otg_ctrl.a_bus_drop = 1; - mvotg->otg_ctrl.a_bus_req = 0; - dev_dbg(&mvotg->pdev->dev, - "User request: a_bus_drop = 1\n"); - dev_dbg(&mvotg->pdev->dev, - "User request: and a_bus_req = 0\n"); - } - - if (spin_trylock(&mvotg->wq_lock)) { - mv_otg_run_state_machine(mvotg, 0); - spin_unlock(&mvotg->wq_lock); - } - - return count; -} - -static DEVICE_ATTR_RW(a_bus_drop); - -static struct attribute *inputs_attrs[] = { - &dev_attr_a_bus_req.attr, - &dev_attr_a_clr_err.attr, - &dev_attr_a_bus_drop.attr, - NULL, -}; - -static const struct attribute_group inputs_attr_group = { - .name = "inputs", - .attrs = inputs_attrs, -}; - -static const struct attribute_group *mv_otg_groups[] = { - &inputs_attr_group, - NULL, -}; - -static void mv_otg_remove(struct platform_device *pdev) -{ - struct mv_otg *mvotg = platform_get_drvdata(pdev); - - if (mvotg->qwork) - destroy_workqueue(mvotg->qwork); - - mv_otg_disable(mvotg); - - usb_remove_phy(&mvotg->phy); -} - -static int mv_otg_probe(struct platform_device *pdev) -{ - struct mv_usb_platform_data *pdata = dev_get_platdata(&pdev->dev); - struct mv_otg *mvotg; - struct usb_otg *otg; - struct resource *r; - int retval = 0, i; - - if (pdata == NULL) { - dev_err(&pdev->dev, "failed to get platform data\n"); - return -ENODEV; - } - - mvotg = devm_kzalloc(&pdev->dev, sizeof(*mvotg), GFP_KERNEL); - if (!mvotg) - return -ENOMEM; - - otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL); - if (!otg) - return -ENOMEM; - - platform_set_drvdata(pdev, mvotg); - - mvotg->pdev = pdev; - mvotg->pdata = pdata; - - mvotg->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(mvotg->clk)) - return PTR_ERR(mvotg->clk); - - mvotg->qwork = create_singlethread_workqueue("mv_otg_queue"); - if (!mvotg->qwork) { - dev_dbg(&pdev->dev, "cannot create workqueue for OTG\n"); - return -ENOMEM; - } - - INIT_DELAYED_WORK(&mvotg->work, mv_otg_work); - - /* OTG common part */ - mvotg->pdev = pdev; - mvotg->phy.dev = &pdev->dev; - mvotg->phy.otg = otg; - mvotg->phy.label = driver_name; - - otg->state = OTG_STATE_UNDEFINED; - otg->usb_phy = &mvotg->phy; - otg->set_host = mv_otg_set_host; - otg->set_peripheral = mv_otg_set_peripheral; - otg->set_vbus = mv_otg_set_vbus; - - for (i = 0; i < OTG_TIMER_NUM; i++) - timer_setup(&mvotg->otg_ctrl.timer[i], - mv_otg_timer_await_bcon, 0); - - r = platform_get_resource_byname(mvotg->pdev, - IORESOURCE_MEM, "phyregs"); - if (r == NULL) { - dev_err(&pdev->dev, "no phy I/O memory resource defined\n"); - retval = -ENODEV; - goto err_destroy_workqueue; - } - - mvotg->phy_regs = devm_ioremap(&pdev->dev, r->start, resource_size(r)); - if (mvotg->phy_regs == NULL) { - dev_err(&pdev->dev, "failed to map phy I/O memory\n"); - retval = -EFAULT; - goto err_destroy_workqueue; - } - - r = platform_get_resource_byname(mvotg->pdev, - IORESOURCE_MEM, "capregs"); - if (r == NULL) { - dev_err(&pdev->dev, "no I/O memory resource defined\n"); - retval = -ENODEV; - goto err_destroy_workqueue; - } - - mvotg->cap_regs = devm_ioremap(&pdev->dev, r->start, resource_size(r)); - if (mvotg->cap_regs == NULL) { - dev_err(&pdev->dev, "failed to map I/O memory\n"); - retval = -EFAULT; - goto err_destroy_workqueue; - } - - /* we will acces controller register, so enable the udc controller */ - retval = mv_otg_enable_internal(mvotg); - if (retval) { - dev_err(&pdev->dev, "mv otg enable error %d\n", retval); - goto err_destroy_workqueue; - } - - mvotg->op_regs = - (struct mv_otg_regs __iomem *) ((unsigned long) mvotg->cap_regs - + (readl(mvotg->cap_regs) & CAPLENGTH_MASK)); - - if (pdata->id) { - retval = devm_request_threaded_irq(&pdev->dev, pdata->id->irq, - NULL, mv_otg_inputs_irq, - IRQF_ONESHOT, "id", mvotg); - if (retval) { - dev_info(&pdev->dev, - "Failed to request irq for ID\n"); - pdata->id = NULL; - } - } - - if (pdata->vbus) { - mvotg->clock_gating = 1; - retval = devm_request_threaded_irq(&pdev->dev, pdata->vbus->irq, - NULL, mv_otg_inputs_irq, - IRQF_ONESHOT, "vbus", mvotg); - if (retval) { - dev_info(&pdev->dev, - "Failed to request irq for VBUS, " - "disable clock gating\n"); - mvotg->clock_gating = 0; - pdata->vbus = NULL; - } - } - - if (pdata->disable_otg_clock_gating) - mvotg->clock_gating = 0; - - mv_otg_reset(mvotg); - mv_otg_init_irq(mvotg); - - r = platform_get_resource(mvotg->pdev, IORESOURCE_IRQ, 0); - if (r == NULL) { - dev_err(&pdev->dev, "no IRQ resource defined\n"); - retval = -ENODEV; - goto err_disable_clk; - } - - mvotg->irq = r->start; - if (devm_request_irq(&pdev->dev, mvotg->irq, mv_otg_irq, IRQF_SHARED, - driver_name, mvotg)) { - dev_err(&pdev->dev, "Request irq %d for OTG failed\n", - mvotg->irq); - mvotg->irq = 0; - retval = -ENODEV; - goto err_disable_clk; - } - - retval = usb_add_phy(&mvotg->phy, USB_PHY_TYPE_USB2); - if (retval < 0) { - dev_err(&pdev->dev, "can't register transceiver, %d\n", - retval); - goto err_disable_clk; - } - - spin_lock_init(&mvotg->wq_lock); - if (spin_trylock(&mvotg->wq_lock)) { - mv_otg_run_state_machine(mvotg, 2 * HZ); - spin_unlock(&mvotg->wq_lock); - } - - dev_info(&pdev->dev, - "successful probe OTG device %s clock gating.\n", - mvotg->clock_gating ? "with" : "without"); - - return 0; - -err_disable_clk: - mv_otg_disable_internal(mvotg); -err_destroy_workqueue: - destroy_workqueue(mvotg->qwork); - - return retval; -} - -#ifdef CONFIG_PM -static int mv_otg_suspend(struct platform_device *pdev, pm_message_t state) -{ - struct mv_otg *mvotg = platform_get_drvdata(pdev); - - if (mvotg->phy.otg->state != OTG_STATE_B_IDLE) { - dev_info(&pdev->dev, - "OTG state is not B_IDLE, it is %d!\n", - mvotg->phy.otg->state); - return -EAGAIN; - } - - if (!mvotg->clock_gating) - mv_otg_disable_internal(mvotg); - - return 0; -} - -static int mv_otg_resume(struct platform_device *pdev) -{ - struct mv_otg *mvotg = platform_get_drvdata(pdev); - u32 otgsc; - - if (!mvotg->clock_gating) { - mv_otg_enable_internal(mvotg); - - otgsc = readl(&mvotg->op_regs->otgsc); - otgsc |= mvotg->irq_en; - writel(otgsc, &mvotg->op_regs->otgsc); - - if (spin_trylock(&mvotg->wq_lock)) { - mv_otg_run_state_machine(mvotg, 0); - spin_unlock(&mvotg->wq_lock); - } - } - return 0; -} -#endif - -static struct platform_driver mv_otg_driver = { - .probe = mv_otg_probe, - .remove = mv_otg_remove, - .driver = { - .name = driver_name, - .dev_groups = mv_otg_groups, - }, -#ifdef CONFIG_PM - .suspend = mv_otg_suspend, - .resume = mv_otg_resume, -#endif -}; -module_platform_driver(mv_otg_driver); -- cgit v1.2.3-59-g8ed1b From 1692632146451184c4bcb68554098470a119fb01 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Tue, 8 Apr 2025 20:08:51 +0800 Subject: USB: core: Correct API usb_(enable|disable)_autosuspend() prototypes API usb_(enable|disable)_autosuspend() have inconsistent prototypes regarding if CONFIG_PM is defined. Correct prototypes when the macro is undefined by referring to those when the macro is defined. Signed-off-by: Zijun Hu Reviewed-by: Alan Stern Link: https://lore.kernel.org/r/20250408-fix_usb_hdr-v1-1-e785c5b49481@quicinc.com Signed-off-by: Greg Kroah-Hartman --- include/linux/usb.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/linux/usb.h b/include/linux/usb.h index b46738701f8d..1b2545b4363b 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -815,10 +815,10 @@ static inline void usb_mark_last_busy(struct usb_device *udev) #else -static inline int usb_enable_autosuspend(struct usb_device *udev) -{ return 0; } -static inline int usb_disable_autosuspend(struct usb_device *udev) -{ return 0; } +static inline void usb_enable_autosuspend(struct usb_device *udev) +{ } +static inline void usb_disable_autosuspend(struct usb_device *udev) +{ } static inline int usb_autopm_get_interface(struct usb_interface *intf) { return 0; } -- cgit v1.2.3-59-g8ed1b From 7dbd93137153dac561c74f4aa80d9280486011e7 Mon Sep 17 00:00:00 2001 From: "J. Neuschäfer" Date: Tue, 8 Apr 2025 21:13:12 +0200 Subject: dt-bindings: net: wireless: Add Realtek RTL8188ETV USB WiFi MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is an on-board USB device that requires a 3.3V supply. Signed-off-by: J. Neuschäfer Reviewed-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20250408-rtl-onboard-v2-1-0b6730b90e31@posteo.net Signed-off-by: Greg Kroah-Hartman --- .../bindings/net/wireless/realtek,rtl8188e.yaml | 50 ++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/wireless/realtek,rtl8188e.yaml diff --git a/Documentation/devicetree/bindings/net/wireless/realtek,rtl8188e.yaml b/Documentation/devicetree/bindings/net/wireless/realtek,rtl8188e.yaml new file mode 100644 index 000000000000..2769731e0708 --- /dev/null +++ b/Documentation/devicetree/bindings/net/wireless/realtek,rtl8188e.yaml @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/wireless/realtek,rtl8188e.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Realtek RTL8188E USB WiFi + +maintainers: + - J. Neuschäfer + +description: + Realtek RTL8188E is a family of USB-connected 2.4 GHz WiFi modules. + +allOf: + - $ref: /schemas/usb/usb-device.yaml# + +properties: + compatible: + const: usbbda,179 # RTL8188ETV + + reg: true + + vdd-supply: + description: + Regulator for the 3V3 supply. + +required: + - compatible + - reg + - vdd-supply + +additionalProperties: false + +examples: + - | + #include + + usb { + #address-cells = <1>; + #size-cells = <0>; + + wifi: wifi@1 { + compatible = "usbbda,179"; + reg = <1>; + vdd-supply = <&vcc3v3>; + }; + }; + +... -- cgit v1.2.3-59-g8ed1b From c6ec8f8625c1f92d1c6bf83c662f1ee8e914843d Mon Sep 17 00:00:00 2001 From: "J. Neuschäfer" Date: Tue, 8 Apr 2025 21:13:13 +0200 Subject: usb: misc: onboard_dev: Add Realtek RTL8188ETV WiFi (0bda:0179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Realtek RTL8188ETV 2.4 GHz WiFi modules (detected as RTL8188EU by the RTL8XXXXU driver) are found soldered into some embedded devices, such as the Fernsehfee 3.0 set-top box. They require a 3.3V power supply. Signed-off-by: J. Neuschäfer Link: https://lore.kernel.org/r/20250408-rtl-onboard-v2-2-0b6730b90e31@posteo.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/onboard_usb_dev.c | 1 + drivers/usb/misc/onboard_usb_dev.h | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/drivers/usb/misc/onboard_usb_dev.c b/drivers/usb/misc/onboard_usb_dev.c index 75ac3c6aa92d..2f9e8f8108d8 100644 --- a/drivers/usb/misc/onboard_usb_dev.c +++ b/drivers/usb/misc/onboard_usb_dev.c @@ -584,6 +584,7 @@ static const struct usb_device_id onboard_dev_id_table[] = { { USB_DEVICE(VENDOR_ID_REALTEK, 0x5411) }, /* RTS5411 USB 2.1 HUB */ { USB_DEVICE(VENDOR_ID_REALTEK, 0x0414) }, /* RTS5414 USB 3.2 HUB */ { USB_DEVICE(VENDOR_ID_REALTEK, 0x5414) }, /* RTS5414 USB 2.1 HUB */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x0179) }, /* RTL8188ETV 2.4GHz WiFi */ { USB_DEVICE(VENDOR_ID_TI, 0x8025) }, /* TI USB8020B 3.0 HUB */ { USB_DEVICE(VENDOR_ID_TI, 0x8027) }, /* TI USB8020B 2.0 HUB */ { USB_DEVICE(VENDOR_ID_TI, 0x8140) }, /* TI USB8041 3.0 HUB */ diff --git a/drivers/usb/misc/onboard_usb_dev.h b/drivers/usb/misc/onboard_usb_dev.h index 933797a7e084..4b023ddfbdd6 100644 --- a/drivers/usb/misc/onboard_usb_dev.h +++ b/drivers/usb/misc/onboard_usb_dev.h @@ -45,6 +45,13 @@ static const struct onboard_dev_pdata realtek_rts5411_data = { .is_hub = true, }; +static const struct onboard_dev_pdata realtek_rtl8188etv_data = { + .reset_us = 0, + .num_supplies = 1, + .supply_names = { "vdd" }, + .is_hub = false, +}; + static const struct onboard_dev_pdata ti_tusb8020b_data = { .reset_us = 3000, .num_supplies = 1, @@ -118,6 +125,7 @@ static const struct of_device_id onboard_dev_match[] = { { .compatible = "usb5e3,610", .data = &genesys_gl852g_data, }, { .compatible = "usb5e3,620", .data = &genesys_gl852g_data, }, { .compatible = "usb5e3,626", .data = &genesys_gl852g_data, }, + { .compatible = "usbbda,179", .data = &realtek_rtl8188etv_data, }, { .compatible = "usbbda,411", .data = &realtek_rts5411_data, }, { .compatible = "usbbda,5411", .data = &realtek_rts5411_data, }, { .compatible = "usbbda,414", .data = &realtek_rts5411_data, }, -- cgit v1.2.3-59-g8ed1b From 41c6960617b29ba16672d3ae607c7af0de24fe11 Mon Sep 17 00:00:00 2001 From: Ivaylo Ivanov Date: Sat, 12 Apr 2025 23:33:12 +0300 Subject: dt-bindings: usb: samsung,exynos-dwc3: add exynos2200 compatible The Exynos2200 SoC has a DWC3 compatible USB controller and can reuse the existing Exynos glue. Update the dt schema to include the samsung,exynos2200-dwusb3 compatible for it. Signed-off-by: Ivaylo Ivanov Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20250412203313.738429-2-ivo.ivanov.ivanov1@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/samsung,exynos-dwc3.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/samsung,exynos-dwc3.yaml b/Documentation/devicetree/bindings/usb/samsung,exynos-dwc3.yaml index 256bee2a03ca..892545b477ac 100644 --- a/Documentation/devicetree/bindings/usb/samsung,exynos-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/samsung,exynos-dwc3.yaml @@ -14,6 +14,7 @@ properties: oneOf: - enum: - google,gs101-dwusb3 + - samsung,exynos2200-dwusb3 - samsung,exynos5250-dwusb3 - samsung,exynos5433-dwusb3 - samsung,exynos7-dwusb3 @@ -79,6 +80,19 @@ allOf: required: - vdd10-supply + - if: + properties: + compatible: + contains: + const: samsung,exynos2200-dwusb3 + then: + properties: + clocks: + maxItems: 1 + clock-names: + items: + - const: link_aclk + - if: properties: compatible: -- cgit v1.2.3-59-g8ed1b From 00b157f8f64bedc1b619688e8abe659a79a63033 Mon Sep 17 00:00:00 2001 From: Ivaylo Ivanov Date: Sat, 12 Apr 2025 23:33:13 +0300 Subject: usb: dwc3: exynos: add support for Exynos2200 variant Add Exynos2200 compatible string and associated driver data. This SoC requires a Link interface AXI clock. Signed-off-by: Ivaylo Ivanov Reviewed-by: Krzysztof Kozlowski Acked-by: Thinh Nguyen Link: https://lore.kernel.org/r/20250412203313.738429-3-ivo.ivanov.ivanov1@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-exynos.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/usb/dwc3/dwc3-exynos.c b/drivers/usb/dwc3/dwc3-exynos.c index de686b9e6404..20abc6a4e824 100644 --- a/drivers/usb/dwc3/dwc3-exynos.c +++ b/drivers/usb/dwc3/dwc3-exynos.c @@ -145,6 +145,12 @@ static void dwc3_exynos_remove(struct platform_device *pdev) regulator_disable(exynos->vdd10); } +static const struct dwc3_exynos_driverdata exynos2200_drvdata = { + .clk_names = { "link_aclk" }, + .num_clks = 1, + .suspend_clk_idx = -1, +}; + static const struct dwc3_exynos_driverdata exynos5250_drvdata = { .clk_names = { "usbdrd30" }, .num_clks = 1, @@ -183,6 +189,9 @@ static const struct dwc3_exynos_driverdata gs101_drvdata = { static const struct of_device_id exynos_dwc3_match[] = { { + .compatible = "samsung,exynos2200-dwusb3", + .data = &exynos2200_drvdata, + }, { .compatible = "samsung,exynos5250-dwusb3", .data = &exynos5250_drvdata, }, { -- cgit v1.2.3-59-g8ed1b From b958b03c82d2a1c470225355e43248c679a949a5 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Mon, 14 Apr 2025 20:21:50 -0500 Subject: usb: dwc3: qcom: Snapshot driver for backwards compatibilty In order to more tightly integrate the Qualcomm glue driver with the dwc3 core the driver is redesigned to avoid splitting the implementation using the driver model. But due to the strong coupling to the Devicetree binding needs to be updated as well. Various ways to provide backwards compatibility with existing Devicetree blobs has been explored, but migrating the Devicetree information between the old and the new binding is non-trivial. For the vast majority of boards out there, the kernel and Devicetree are generated and handled together, which in practice means that backwards compatibility needs to be managed across about 1 kernel release. For some though, such as the various Snapdragon laptops, the Devicetree blobs live a life separate of the kernel. In each one of these, with the continued extension of new features, it's recommended that users would upgrade their Devicetree somewhat frequently. With this in mind, simply carrying a snapshot/copy of the current driver is simpler than creating and maintaining the migration code. The driver is kept under the same Kconfig option, to ensure that Linux distributions doesn't drop USB support on these platforms. The driver, which is going to be refactored to handle the newly introduced qcom,snps-dwc3 compatible, is updated to temporarily not match against any compatible. This driver should be removed after the next LTS release. Acked-by: Thinh Nguyen Tested-by: Neil Armstrong # on SM8650-QRD Signed-off-by: Bjorn Andersson Link: https://lore.kernel.org/r/20250414-dwc3-refactor-v7-1-f015b358722d@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/Makefile | 1 + drivers/usb/dwc3/dwc3-qcom-legacy.c | 935 ++++++++++++++++++++++++++++++++++++ drivers/usb/dwc3/dwc3-qcom.c | 1 - 3 files changed, 936 insertions(+), 1 deletion(-) create mode 100644 drivers/usb/dwc3/dwc3-qcom-legacy.c diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index 124eda2522d9..830e6c9e5fe0 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -52,6 +52,7 @@ obj-$(CONFIG_USB_DWC3_MESON_G12A) += dwc3-meson-g12a.o obj-$(CONFIG_USB_DWC3_OF_SIMPLE) += dwc3-of-simple.o obj-$(CONFIG_USB_DWC3_ST) += dwc3-st.o obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom.o +obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom-legacy.o obj-$(CONFIG_USB_DWC3_IMX8MP) += dwc3-imx8mp.o obj-$(CONFIG_USB_DWC3_XILINX) += dwc3-xilinx.o obj-$(CONFIG_USB_DWC3_OCTEON) += dwc3-octeon.o diff --git a/drivers/usb/dwc3/dwc3-qcom-legacy.c b/drivers/usb/dwc3/dwc3-qcom-legacy.c new file mode 100644 index 000000000000..d3fad0fcfdac --- /dev/null +++ b/drivers/usb/dwc3/dwc3-qcom-legacy.c @@ -0,0 +1,935 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2018, The Linux Foundation. All rights reserved. + * + * Inspired by dwc3-of-simple.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "core.h" + +/* USB QSCRATCH Hardware registers */ +#define QSCRATCH_HS_PHY_CTRL 0x10 +#define UTMI_OTG_VBUS_VALID BIT(20) +#define SW_SESSVLD_SEL BIT(28) + +#define QSCRATCH_SS_PHY_CTRL 0x30 +#define LANE0_PWR_PRESENT BIT(24) + +#define QSCRATCH_GENERAL_CFG 0x08 +#define PIPE_UTMI_CLK_SEL BIT(0) +#define PIPE3_PHYSTATUS_SW BIT(3) +#define PIPE_UTMI_CLK_DIS BIT(8) + +#define PWR_EVNT_LPM_IN_L2_MASK BIT(4) +#define PWR_EVNT_LPM_OUT_L2_MASK BIT(5) + +#define SDM845_QSCRATCH_BASE_OFFSET 0xf8800 +#define SDM845_QSCRATCH_SIZE 0x400 +#define SDM845_DWC3_CORE_SIZE 0xcd00 + +/* Interconnect path bandwidths in MBps */ +#define USB_MEMORY_AVG_HS_BW MBps_to_icc(240) +#define USB_MEMORY_PEAK_HS_BW MBps_to_icc(700) +#define USB_MEMORY_AVG_SS_BW MBps_to_icc(1000) +#define USB_MEMORY_PEAK_SS_BW MBps_to_icc(2500) +#define APPS_USB_AVG_BW 0 +#define APPS_USB_PEAK_BW MBps_to_icc(40) + +/* Qualcomm SoCs with multiport support has up to 4 ports */ +#define DWC3_QCOM_MAX_PORTS 4 + +static const u32 pwr_evnt_irq_stat_reg[DWC3_QCOM_MAX_PORTS] = { + 0x58, + 0x1dc, + 0x228, + 0x238, +}; + +struct dwc3_qcom_port { + int qusb2_phy_irq; + int dp_hs_phy_irq; + int dm_hs_phy_irq; + int ss_phy_irq; + enum usb_device_speed usb2_speed; +}; + +struct dwc3_qcom { + struct device *dev; + void __iomem *qscratch_base; + struct platform_device *dwc3; + struct clk **clks; + int num_clocks; + struct reset_control *resets; + struct dwc3_qcom_port ports[DWC3_QCOM_MAX_PORTS]; + u8 num_ports; + + struct extcon_dev *edev; + struct extcon_dev *host_edev; + struct notifier_block vbus_nb; + struct notifier_block host_nb; + + enum usb_dr_mode mode; + bool is_suspended; + bool pm_suspended; + struct icc_path *icc_path_ddr; + struct icc_path *icc_path_apps; +}; + +static inline void dwc3_qcom_setbits(void __iomem *base, u32 offset, u32 val) +{ + u32 reg; + + reg = readl(base + offset); + reg |= val; + writel(reg, base + offset); + + /* ensure that above write is through */ + readl(base + offset); +} + +static inline void dwc3_qcom_clrbits(void __iomem *base, u32 offset, u32 val) +{ + u32 reg; + + reg = readl(base + offset); + reg &= ~val; + writel(reg, base + offset); + + /* ensure that above write is through */ + readl(base + offset); +} + +static void dwc3_qcom_vbus_override_enable(struct dwc3_qcom *qcom, bool enable) +{ + if (enable) { + dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_SS_PHY_CTRL, + LANE0_PWR_PRESENT); + dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_HS_PHY_CTRL, + UTMI_OTG_VBUS_VALID | SW_SESSVLD_SEL); + } else { + dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_SS_PHY_CTRL, + LANE0_PWR_PRESENT); + dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_HS_PHY_CTRL, + UTMI_OTG_VBUS_VALID | SW_SESSVLD_SEL); + } +} + +static int dwc3_qcom_vbus_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3_qcom *qcom = container_of(nb, struct dwc3_qcom, vbus_nb); + + /* enable vbus override for device mode */ + dwc3_qcom_vbus_override_enable(qcom, event); + qcom->mode = event ? USB_DR_MODE_PERIPHERAL : USB_DR_MODE_HOST; + + return NOTIFY_DONE; +} + +static int dwc3_qcom_host_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3_qcom *qcom = container_of(nb, struct dwc3_qcom, host_nb); + + /* disable vbus override in host mode */ + dwc3_qcom_vbus_override_enable(qcom, !event); + qcom->mode = event ? USB_DR_MODE_HOST : USB_DR_MODE_PERIPHERAL; + + return NOTIFY_DONE; +} + +static int dwc3_qcom_register_extcon(struct dwc3_qcom *qcom) +{ + struct device *dev = qcom->dev; + struct extcon_dev *host_edev; + int ret; + + if (!of_property_present(dev->of_node, "extcon")) + return 0; + + qcom->edev = extcon_get_edev_by_phandle(dev, 0); + if (IS_ERR(qcom->edev)) + return dev_err_probe(dev, PTR_ERR(qcom->edev), + "Failed to get extcon\n"); + + qcom->vbus_nb.notifier_call = dwc3_qcom_vbus_notifier; + + qcom->host_edev = extcon_get_edev_by_phandle(dev, 1); + if (IS_ERR(qcom->host_edev)) + qcom->host_edev = NULL; + + ret = devm_extcon_register_notifier(dev, qcom->edev, EXTCON_USB, + &qcom->vbus_nb); + if (ret < 0) { + dev_err(dev, "VBUS notifier register failed\n"); + return ret; + } + + if (qcom->host_edev) + host_edev = qcom->host_edev; + else + host_edev = qcom->edev; + + qcom->host_nb.notifier_call = dwc3_qcom_host_notifier; + ret = devm_extcon_register_notifier(dev, host_edev, EXTCON_USB_HOST, + &qcom->host_nb); + if (ret < 0) { + dev_err(dev, "Host notifier register failed\n"); + return ret; + } + + /* Update initial VBUS override based on extcon state */ + if (extcon_get_state(qcom->edev, EXTCON_USB) || + !extcon_get_state(host_edev, EXTCON_USB_HOST)) + dwc3_qcom_vbus_notifier(&qcom->vbus_nb, true, qcom->edev); + else + dwc3_qcom_vbus_notifier(&qcom->vbus_nb, false, qcom->edev); + + return 0; +} + +static int dwc3_qcom_interconnect_enable(struct dwc3_qcom *qcom) +{ + int ret; + + ret = icc_enable(qcom->icc_path_ddr); + if (ret) + return ret; + + ret = icc_enable(qcom->icc_path_apps); + if (ret) + icc_disable(qcom->icc_path_ddr); + + return ret; +} + +static int dwc3_qcom_interconnect_disable(struct dwc3_qcom *qcom) +{ + int ret; + + ret = icc_disable(qcom->icc_path_ddr); + if (ret) + return ret; + + ret = icc_disable(qcom->icc_path_apps); + if (ret) + icc_enable(qcom->icc_path_ddr); + + return ret; +} + +/** + * dwc3_qcom_interconnect_init() - Get interconnect path handles + * and set bandwidth. + * @qcom: Pointer to the concerned usb core. + * + */ +static int dwc3_qcom_interconnect_init(struct dwc3_qcom *qcom) +{ + enum usb_device_speed max_speed; + struct device *dev = qcom->dev; + int ret; + + qcom->icc_path_ddr = of_icc_get(dev, "usb-ddr"); + if (IS_ERR(qcom->icc_path_ddr)) { + return dev_err_probe(dev, PTR_ERR(qcom->icc_path_ddr), + "failed to get usb-ddr path\n"); + } + + qcom->icc_path_apps = of_icc_get(dev, "apps-usb"); + if (IS_ERR(qcom->icc_path_apps)) { + ret = dev_err_probe(dev, PTR_ERR(qcom->icc_path_apps), + "failed to get apps-usb path\n"); + goto put_path_ddr; + } + + max_speed = usb_get_maximum_speed(&qcom->dwc3->dev); + if (max_speed >= USB_SPEED_SUPER || max_speed == USB_SPEED_UNKNOWN) { + ret = icc_set_bw(qcom->icc_path_ddr, + USB_MEMORY_AVG_SS_BW, USB_MEMORY_PEAK_SS_BW); + } else { + ret = icc_set_bw(qcom->icc_path_ddr, + USB_MEMORY_AVG_HS_BW, USB_MEMORY_PEAK_HS_BW); + } + if (ret) { + dev_err(dev, "failed to set bandwidth for usb-ddr path: %d\n", ret); + goto put_path_apps; + } + + ret = icc_set_bw(qcom->icc_path_apps, APPS_USB_AVG_BW, APPS_USB_PEAK_BW); + if (ret) { + dev_err(dev, "failed to set bandwidth for apps-usb path: %d\n", ret); + goto put_path_apps; + } + + return 0; + +put_path_apps: + icc_put(qcom->icc_path_apps); +put_path_ddr: + icc_put(qcom->icc_path_ddr); + return ret; +} + +/** + * dwc3_qcom_interconnect_exit() - Release interconnect path handles + * @qcom: Pointer to the concerned usb core. + * + * This function is used to release interconnect path handle. + */ +static void dwc3_qcom_interconnect_exit(struct dwc3_qcom *qcom) +{ + icc_put(qcom->icc_path_ddr); + icc_put(qcom->icc_path_apps); +} + +/* Only usable in contexts where the role can not change. */ +static bool dwc3_qcom_is_host(struct dwc3_qcom *qcom) +{ + struct dwc3 *dwc; + + /* + * FIXME: Fix this layering violation. + */ + dwc = platform_get_drvdata(qcom->dwc3); + + /* Core driver may not have probed yet. */ + if (!dwc) + return false; + + return dwc->xhci; +} + +static enum usb_device_speed dwc3_qcom_read_usb2_speed(struct dwc3_qcom *qcom, int port_index) +{ + struct dwc3 *dwc = platform_get_drvdata(qcom->dwc3); + struct usb_device *udev; + struct usb_hcd __maybe_unused *hcd; + + /* + * FIXME: Fix this layering violation. + */ + hcd = platform_get_drvdata(dwc->xhci); + +#ifdef CONFIG_USB + udev = usb_hub_find_child(hcd->self.root_hub, port_index + 1); +#else + udev = NULL; +#endif + if (!udev) + return USB_SPEED_UNKNOWN; + + return udev->speed; +} + +static void dwc3_qcom_enable_wakeup_irq(int irq, unsigned int polarity) +{ + if (!irq) + return; + + if (polarity) + irq_set_irq_type(irq, polarity); + + enable_irq(irq); + enable_irq_wake(irq); +} + +static void dwc3_qcom_disable_wakeup_irq(int irq) +{ + if (!irq) + return; + + disable_irq_wake(irq); + disable_irq_nosync(irq); +} + +static void dwc3_qcom_disable_port_interrupts(struct dwc3_qcom_port *port) +{ + dwc3_qcom_disable_wakeup_irq(port->qusb2_phy_irq); + + if (port->usb2_speed == USB_SPEED_LOW) { + dwc3_qcom_disable_wakeup_irq(port->dm_hs_phy_irq); + } else if ((port->usb2_speed == USB_SPEED_HIGH) || + (port->usb2_speed == USB_SPEED_FULL)) { + dwc3_qcom_disable_wakeup_irq(port->dp_hs_phy_irq); + } else { + dwc3_qcom_disable_wakeup_irq(port->dp_hs_phy_irq); + dwc3_qcom_disable_wakeup_irq(port->dm_hs_phy_irq); + } + + dwc3_qcom_disable_wakeup_irq(port->ss_phy_irq); +} + +static void dwc3_qcom_enable_port_interrupts(struct dwc3_qcom_port *port) +{ + dwc3_qcom_enable_wakeup_irq(port->qusb2_phy_irq, 0); + + /* + * Configure DP/DM line interrupts based on the USB2 device attached to + * the root hub port. When HS/FS device is connected, configure the DP line + * as falling edge to detect both disconnect and remote wakeup scenarios. When + * LS device is connected, configure DM line as falling edge to detect both + * disconnect and remote wakeup. When no device is connected, configure both + * DP and DM lines as rising edge to detect HS/HS/LS device connect scenario. + */ + + if (port->usb2_speed == USB_SPEED_LOW) { + dwc3_qcom_enable_wakeup_irq(port->dm_hs_phy_irq, + IRQ_TYPE_EDGE_FALLING); + } else if ((port->usb2_speed == USB_SPEED_HIGH) || + (port->usb2_speed == USB_SPEED_FULL)) { + dwc3_qcom_enable_wakeup_irq(port->dp_hs_phy_irq, + IRQ_TYPE_EDGE_FALLING); + } else { + dwc3_qcom_enable_wakeup_irq(port->dp_hs_phy_irq, + IRQ_TYPE_EDGE_RISING); + dwc3_qcom_enable_wakeup_irq(port->dm_hs_phy_irq, + IRQ_TYPE_EDGE_RISING); + } + + dwc3_qcom_enable_wakeup_irq(port->ss_phy_irq, 0); +} + +static void dwc3_qcom_disable_interrupts(struct dwc3_qcom *qcom) +{ + int i; + + for (i = 0; i < qcom->num_ports; i++) + dwc3_qcom_disable_port_interrupts(&qcom->ports[i]); +} + +static void dwc3_qcom_enable_interrupts(struct dwc3_qcom *qcom) +{ + int i; + + for (i = 0; i < qcom->num_ports; i++) + dwc3_qcom_enable_port_interrupts(&qcom->ports[i]); +} + +static int dwc3_qcom_suspend(struct dwc3_qcom *qcom, bool wakeup) +{ + u32 val; + int i, ret; + + if (qcom->is_suspended) + return 0; + + for (i = 0; i < qcom->num_ports; i++) { + val = readl(qcom->qscratch_base + pwr_evnt_irq_stat_reg[i]); + if (!(val & PWR_EVNT_LPM_IN_L2_MASK)) + dev_err(qcom->dev, "port-%d HS-PHY not in L2\n", i + 1); + } + + for (i = qcom->num_clocks - 1; i >= 0; i--) + clk_disable_unprepare(qcom->clks[i]); + + ret = dwc3_qcom_interconnect_disable(qcom); + if (ret) + dev_warn(qcom->dev, "failed to disable interconnect: %d\n", ret); + + /* + * The role is stable during suspend as role switching is done from a + * freezable workqueue. + */ + if (dwc3_qcom_is_host(qcom) && wakeup) { + for (i = 0; i < qcom->num_ports; i++) + qcom->ports[i].usb2_speed = dwc3_qcom_read_usb2_speed(qcom, i); + dwc3_qcom_enable_interrupts(qcom); + } + + qcom->is_suspended = true; + + return 0; +} + +static int dwc3_qcom_resume(struct dwc3_qcom *qcom, bool wakeup) +{ + int ret; + int i; + + if (!qcom->is_suspended) + return 0; + + if (dwc3_qcom_is_host(qcom) && wakeup) + dwc3_qcom_disable_interrupts(qcom); + + for (i = 0; i < qcom->num_clocks; i++) { + ret = clk_prepare_enable(qcom->clks[i]); + if (ret < 0) { + while (--i >= 0) + clk_disable_unprepare(qcom->clks[i]); + return ret; + } + } + + ret = dwc3_qcom_interconnect_enable(qcom); + if (ret) + dev_warn(qcom->dev, "failed to enable interconnect: %d\n", ret); + + /* Clear existing events from PHY related to L2 in/out */ + for (i = 0; i < qcom->num_ports; i++) { + dwc3_qcom_setbits(qcom->qscratch_base, + pwr_evnt_irq_stat_reg[i], + PWR_EVNT_LPM_IN_L2_MASK | PWR_EVNT_LPM_OUT_L2_MASK); + } + + qcom->is_suspended = false; + + return 0; +} + +static irqreturn_t qcom_dwc3_resume_irq(int irq, void *data) +{ + struct dwc3_qcom *qcom = data; + struct dwc3 *dwc = platform_get_drvdata(qcom->dwc3); + + /* If pm_suspended then let pm_resume take care of resuming h/w */ + if (qcom->pm_suspended) + return IRQ_HANDLED; + + /* + * This is safe as role switching is done from a freezable workqueue + * and the wakeup interrupts are disabled as part of resume. + */ + if (dwc3_qcom_is_host(qcom)) + pm_runtime_resume(&dwc->xhci->dev); + + return IRQ_HANDLED; +} + +static void dwc3_qcom_select_utmi_clk(struct dwc3_qcom *qcom) +{ + /* Configure dwc3 to use UTMI clock as PIPE clock not present */ + dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG, + PIPE_UTMI_CLK_DIS); + + usleep_range(100, 1000); + + dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG, + PIPE_UTMI_CLK_SEL | PIPE3_PHYSTATUS_SW); + + usleep_range(100, 1000); + + dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG, + PIPE_UTMI_CLK_DIS); +} + +static int dwc3_qcom_request_irq(struct dwc3_qcom *qcom, int irq, + const char *name) +{ + int ret; + + /* Keep wakeup interrupts disabled until suspend */ + ret = devm_request_threaded_irq(qcom->dev, irq, NULL, + qcom_dwc3_resume_irq, + IRQF_ONESHOT | IRQF_NO_AUTOEN, + name, qcom); + if (ret) + dev_err(qcom->dev, "failed to request irq %s: %d\n", name, ret); + + return ret; +} + +static int dwc3_qcom_setup_port_irq(struct platform_device *pdev, int port_index, bool is_multiport) +{ + struct dwc3_qcom *qcom = platform_get_drvdata(pdev); + const char *irq_name; + int irq; + int ret; + + if (is_multiport) + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "dp_hs_phy_%d", port_index + 1); + else + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "dp_hs_phy_irq"); + if (!irq_name) + return -ENOMEM; + + irq = platform_get_irq_byname_optional(pdev, irq_name); + if (irq > 0) { + ret = dwc3_qcom_request_irq(qcom, irq, irq_name); + if (ret) + return ret; + qcom->ports[port_index].dp_hs_phy_irq = irq; + } + + if (is_multiport) + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "dm_hs_phy_%d", port_index + 1); + else + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "dm_hs_phy_irq"); + if (!irq_name) + return -ENOMEM; + + irq = platform_get_irq_byname_optional(pdev, irq_name); + if (irq > 0) { + ret = dwc3_qcom_request_irq(qcom, irq, irq_name); + if (ret) + return ret; + qcom->ports[port_index].dm_hs_phy_irq = irq; + } + + if (is_multiport) + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "ss_phy_%d", port_index + 1); + else + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "ss_phy_irq"); + if (!irq_name) + return -ENOMEM; + + irq = platform_get_irq_byname_optional(pdev, irq_name); + if (irq > 0) { + ret = dwc3_qcom_request_irq(qcom, irq, irq_name); + if (ret) + return ret; + qcom->ports[port_index].ss_phy_irq = irq; + } + + if (is_multiport) + return 0; + + irq = platform_get_irq_byname_optional(pdev, "qusb2_phy"); + if (irq > 0) { + ret = dwc3_qcom_request_irq(qcom, irq, "qusb2_phy"); + if (ret) + return ret; + qcom->ports[port_index].qusb2_phy_irq = irq; + } + + return 0; +} + +static int dwc3_qcom_find_num_ports(struct platform_device *pdev) +{ + char irq_name[14]; + int port_num; + int irq; + + irq = platform_get_irq_byname_optional(pdev, "dp_hs_phy_1"); + if (irq <= 0) + return 1; + + for (port_num = 2; port_num <= DWC3_QCOM_MAX_PORTS; port_num++) { + sprintf(irq_name, "dp_hs_phy_%d", port_num); + + irq = platform_get_irq_byname_optional(pdev, irq_name); + if (irq <= 0) + return port_num - 1; + } + + return DWC3_QCOM_MAX_PORTS; +} + +static int dwc3_qcom_setup_irq(struct platform_device *pdev) +{ + struct dwc3_qcom *qcom = platform_get_drvdata(pdev); + bool is_multiport; + int ret; + int i; + + qcom->num_ports = dwc3_qcom_find_num_ports(pdev); + is_multiport = (qcom->num_ports > 1); + + for (i = 0; i < qcom->num_ports; i++) { + ret = dwc3_qcom_setup_port_irq(pdev, i, is_multiport); + if (ret) + return ret; + } + + return 0; +} + +static int dwc3_qcom_clk_init(struct dwc3_qcom *qcom, int count) +{ + struct device *dev = qcom->dev; + struct device_node *np = dev->of_node; + int i; + + if (!np || !count) + return 0; + + if (count < 0) + return count; + + qcom->num_clocks = count; + + qcom->clks = devm_kcalloc(dev, qcom->num_clocks, + sizeof(struct clk *), GFP_KERNEL); + if (!qcom->clks) + return -ENOMEM; + + for (i = 0; i < qcom->num_clocks; i++) { + struct clk *clk; + int ret; + + clk = of_clk_get(np, i); + if (IS_ERR(clk)) { + while (--i >= 0) + clk_put(qcom->clks[i]); + return PTR_ERR(clk); + } + + ret = clk_prepare_enable(clk); + if (ret < 0) { + while (--i >= 0) { + clk_disable_unprepare(qcom->clks[i]); + clk_put(qcom->clks[i]); + } + clk_put(clk); + + return ret; + } + + qcom->clks[i] = clk; + } + + return 0; +} + +static int dwc3_qcom_of_register_core(struct platform_device *pdev) +{ + struct dwc3_qcom *qcom = platform_get_drvdata(pdev); + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + int ret; + + struct device_node *dwc3_np __free(device_node) = of_get_compatible_child(np, + "snps,dwc3"); + if (!dwc3_np) { + dev_err(dev, "failed to find dwc3 core child\n"); + return -ENODEV; + } + + ret = of_platform_populate(np, NULL, NULL, dev); + if (ret) { + dev_err(dev, "failed to register dwc3 core - %d\n", ret); + return ret; + } + + qcom->dwc3 = of_find_device_by_node(dwc3_np); + if (!qcom->dwc3) { + ret = -ENODEV; + dev_err(dev, "failed to get dwc3 platform device\n"); + of_platform_depopulate(dev); + } + + return ret; +} + +static int dwc3_qcom_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct dwc3_qcom *qcom; + int ret, i; + bool ignore_pipe_clk; + bool wakeup_source; + + qcom = devm_kzalloc(&pdev->dev, sizeof(*qcom), GFP_KERNEL); + if (!qcom) + return -ENOMEM; + + platform_set_drvdata(pdev, qcom); + qcom->dev = &pdev->dev; + + qcom->resets = devm_reset_control_array_get_optional_exclusive(dev); + if (IS_ERR(qcom->resets)) { + return dev_err_probe(&pdev->dev, PTR_ERR(qcom->resets), + "failed to get resets\n"); + } + + ret = reset_control_assert(qcom->resets); + if (ret) { + dev_err(&pdev->dev, "failed to assert resets, err=%d\n", ret); + return ret; + } + + usleep_range(10, 1000); + + ret = reset_control_deassert(qcom->resets); + if (ret) { + dev_err(&pdev->dev, "failed to deassert resets, err=%d\n", ret); + goto reset_assert; + } + + ret = dwc3_qcom_clk_init(qcom, of_clk_get_parent_count(np)); + if (ret) { + dev_err_probe(dev, ret, "failed to get clocks\n"); + goto reset_assert; + } + + qcom->qscratch_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(qcom->qscratch_base)) { + ret = PTR_ERR(qcom->qscratch_base); + goto clk_disable; + } + + ret = dwc3_qcom_setup_irq(pdev); + if (ret) { + dev_err(dev, "failed to setup IRQs, err=%d\n", ret); + goto clk_disable; + } + + /* + * Disable pipe_clk requirement if specified. Used when dwc3 + * operates without SSPHY and only HS/FS/LS modes are supported. + */ + ignore_pipe_clk = device_property_read_bool(dev, + "qcom,select-utmi-as-pipe-clk"); + if (ignore_pipe_clk) + dwc3_qcom_select_utmi_clk(qcom); + + ret = dwc3_qcom_of_register_core(pdev); + if (ret) { + dev_err(dev, "failed to register DWC3 Core, err=%d\n", ret); + goto clk_disable; + } + + ret = dwc3_qcom_interconnect_init(qcom); + if (ret) + goto depopulate; + + qcom->mode = usb_get_dr_mode(&qcom->dwc3->dev); + + /* enable vbus override for device mode */ + if (qcom->mode != USB_DR_MODE_HOST) + dwc3_qcom_vbus_override_enable(qcom, true); + + /* register extcon to override sw_vbus on Vbus change later */ + ret = dwc3_qcom_register_extcon(qcom); + if (ret) + goto interconnect_exit; + + wakeup_source = of_property_read_bool(dev->of_node, "wakeup-source"); + device_init_wakeup(&pdev->dev, wakeup_source); + device_init_wakeup(&qcom->dwc3->dev, wakeup_source); + + qcom->is_suspended = false; + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_forbid(dev); + + return 0; + +interconnect_exit: + dwc3_qcom_interconnect_exit(qcom); +depopulate: + of_platform_depopulate(&pdev->dev); + platform_device_put(qcom->dwc3); +clk_disable: + for (i = qcom->num_clocks - 1; i >= 0; i--) { + clk_disable_unprepare(qcom->clks[i]); + clk_put(qcom->clks[i]); + } +reset_assert: + reset_control_assert(qcom->resets); + + return ret; +} + +static void dwc3_qcom_remove(struct platform_device *pdev) +{ + struct dwc3_qcom *qcom = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + int i; + + of_platform_depopulate(&pdev->dev); + platform_device_put(qcom->dwc3); + + for (i = qcom->num_clocks - 1; i >= 0; i--) { + clk_disable_unprepare(qcom->clks[i]); + clk_put(qcom->clks[i]); + } + qcom->num_clocks = 0; + + dwc3_qcom_interconnect_exit(qcom); + reset_control_assert(qcom->resets); + + pm_runtime_allow(dev); + pm_runtime_disable(dev); +} + +static int __maybe_unused dwc3_qcom_pm_suspend(struct device *dev) +{ + struct dwc3_qcom *qcom = dev_get_drvdata(dev); + bool wakeup = device_may_wakeup(dev); + int ret; + + ret = dwc3_qcom_suspend(qcom, wakeup); + if (ret) + return ret; + + qcom->pm_suspended = true; + + return 0; +} + +static int __maybe_unused dwc3_qcom_pm_resume(struct device *dev) +{ + struct dwc3_qcom *qcom = dev_get_drvdata(dev); + bool wakeup = device_may_wakeup(dev); + int ret; + + ret = dwc3_qcom_resume(qcom, wakeup); + if (ret) + return ret; + + qcom->pm_suspended = false; + + return 0; +} + +static int __maybe_unused dwc3_qcom_runtime_suspend(struct device *dev) +{ + struct dwc3_qcom *qcom = dev_get_drvdata(dev); + + return dwc3_qcom_suspend(qcom, true); +} + +static int __maybe_unused dwc3_qcom_runtime_resume(struct device *dev) +{ + struct dwc3_qcom *qcom = dev_get_drvdata(dev); + + return dwc3_qcom_resume(qcom, true); +} + +static const struct dev_pm_ops dwc3_qcom_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dwc3_qcom_pm_suspend, dwc3_qcom_pm_resume) + SET_RUNTIME_PM_OPS(dwc3_qcom_runtime_suspend, dwc3_qcom_runtime_resume, + NULL) +}; + +static const struct of_device_id dwc3_qcom_of_match[] = { + { .compatible = "qcom,dwc3" }, + { } +}; +MODULE_DEVICE_TABLE(of, dwc3_qcom_of_match); + +static struct platform_driver dwc3_qcom_driver = { + .probe = dwc3_qcom_probe, + .remove = dwc3_qcom_remove, + .driver = { + .name = "dwc3-qcom-legacy", + .pm = &dwc3_qcom_dev_pm_ops, + .of_match_table = dwc3_qcom_of_match, + }, +}; + +module_platform_driver(dwc3_qcom_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare DWC3 QCOM legacy glue Driver"); diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index 58683bb672e9..79f3600f25c4 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -914,7 +914,6 @@ static const struct dev_pm_ops dwc3_qcom_dev_pm_ops = { }; static const struct of_device_id dwc3_qcom_of_match[] = { - { .compatible = "qcom,dwc3" }, { } }; MODULE_DEVICE_TABLE(of, dwc3_qcom_of_match); -- cgit v1.2.3-59-g8ed1b From 6e762f7b8edc785405923d5dc002d2b76d4a9739 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Mon, 14 Apr 2025 20:21:51 -0500 Subject: dt-bindings: usb: Introduce qcom,snps-dwc3 The Qualcomm USB glue is not separate of the Synopsys DWC3 core and several of the snps,dwc3 properties (such as clocks and reset) conflicts in expectation with the Qualcomm integration. Using the newly split out Synopsys DWC3 core properties, describe the Qualcomm USB block in a single block. The new binding is a copy of qcom,dwc3 with the needed modifications. It would have been convenient to retain the two structures with the same compatibles, but as there exist no way to select a binding based on the absence of a subnode/patternProperty, a new generic compatible is introduced to describe this binding. To avoid redefining all the platform-specific compatibles, "select" is used to tell the DeviceTree validator which binding to use solely on the generic compatible. (Otherwise if the specific compatible matches during validation, the generic one must match as well) Mark qcom,dwc3 deprecated, to favor expressing future platforms using the new combined binding. Reviewed-by: Rob Herring (Arm) Tested-by: Neil Armstrong # on SM8650-QRD Signed-off-by: Bjorn Andersson Link: https://lore.kernel.org/r/20250414-dwc3-refactor-v7-2-f015b358722d@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/qcom,dwc3.yaml | 13 +- .../devicetree/bindings/usb/qcom,snps-dwc3.yaml | 622 +++++++++++++++++++++ 2 files changed, 634 insertions(+), 1 deletion(-) create mode 100644 Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml diff --git a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml index a681208616f3..a792434c59db 100644 --- a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml @@ -4,11 +4,22 @@ $id: http://devicetree.org/schemas/usb/qcom,dwc3.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Qualcomm SuperSpeed DWC3 USB SoC controller +title: Legacy Qualcomm SuperSpeed DWC3 USB SoC controller maintainers: - Wesley Cheng +# Use the combined qcom,snps-dwc3 instead +deprecated: true + +select: + properties: + compatible: + contains: + const: qcom,dwc3 + required: + - compatible + properties: compatible: items: diff --git a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml new file mode 100644 index 000000000000..8dac5eba61b4 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml @@ -0,0 +1,622 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/qcom,snps-dwc3.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm SuperSpeed DWC3 USB SoC controller + +maintainers: + - Wesley Cheng + +description: + Describes the Qualcomm USB block, based on Synopsys DWC3. + +select: + properties: + compatible: + contains: + const: qcom,snps-dwc3 + required: + - compatible + +properties: + compatible: + items: + - enum: + - qcom,ipq4019-dwc3 + - qcom,ipq5018-dwc3 + - qcom,ipq5332-dwc3 + - qcom,ipq5424-dwc3 + - qcom,ipq6018-dwc3 + - qcom,ipq8064-dwc3 + - qcom,ipq8074-dwc3 + - qcom,ipq9574-dwc3 + - qcom,msm8953-dwc3 + - qcom,msm8994-dwc3 + - qcom,msm8996-dwc3 + - qcom,msm8998-dwc3 + - qcom,qcm2290-dwc3 + - qcom,qcs404-dwc3 + - qcom,qcs615-dwc3 + - qcom,qcs8300-dwc3 + - qcom,qdu1000-dwc3 + - qcom,sa8775p-dwc3 + - qcom,sar2130p-dwc3 + - qcom,sc7180-dwc3 + - qcom,sc7280-dwc3 + - qcom,sc8180x-dwc3 + - qcom,sc8180x-dwc3-mp + - qcom,sc8280xp-dwc3 + - qcom,sc8280xp-dwc3-mp + - qcom,sdm660-dwc3 + - qcom,sdm670-dwc3 + - qcom,sdm845-dwc3 + - qcom,sdx55-dwc3 + - qcom,sdx65-dwc3 + - qcom,sdx75-dwc3 + - qcom,sm4250-dwc3 + - qcom,sm6115-dwc3 + - qcom,sm6125-dwc3 + - qcom,sm6350-dwc3 + - qcom,sm6375-dwc3 + - qcom,sm8150-dwc3 + - qcom,sm8250-dwc3 + - qcom,sm8350-dwc3 + - qcom,sm8450-dwc3 + - qcom,sm8550-dwc3 + - qcom,sm8650-dwc3 + - qcom,x1e80100-dwc3 + - const: qcom,snps-dwc3 + + reg: + maxItems: 1 + + power-domains: + maxItems: 1 + + required-opps: + maxItems: 1 + + clocks: + description: | + Several clocks are used, depending on the variant. Typical ones are:: + - cfg_noc:: System Config NOC clock. + - core:: Master/Core clock, has to be >= 125 MHz for SS operation and >= + 60MHz for HS operation. + - iface:: System bus AXI clock. + - sleep:: Sleep clock, used for wakeup when USB3 core goes into low + power mode (U3). + - mock_utmi:: Mock utmi clock needed for ITP/SOF generation in host + mode. Its frequency should be 19.2MHz. + minItems: 1 + maxItems: 9 + + clock-names: + minItems: 1 + maxItems: 9 + + dma-coherent: true + + iommus: + maxItems: 1 + + resets: + maxItems: 1 + + interconnects: + maxItems: 2 + + interconnect-names: + items: + - const: usb-ddr + - const: apps-usb + + interrupts: + description: | + Different types of interrupts are used based on HS PHY used on target: + - dwc_usb3: Core DWC3 interrupt + - pwr_event: Used for wakeup based on other power events. + - hs_phy_irq: Apart from DP/DM/QUSB2 PHY interrupts, there is + hs_phy_irq which is not triggered by default and its + functionality is mutually exclusive to that of + {dp/dm}_hs_phy_irq and qusb2_phy_irq. + - qusb2_phy: SoCs with QUSB2 PHY do not have separate DP/DM IRQs and + expose only a single IRQ whose behavior can be modified + by the QUSB2PHY_INTR_CTRL register. The required DPSE/ + DMSE configuration is done in QUSB2PHY_INTR_CTRL register + of PHY address space. + - {dp/dm}_hs_phy_irq: These IRQ's directly reflect changes on the DP/ + DM pads of the SoC. These are used for wakeup + only on SoCs with non-QUSB2 targets with + exception of SDM670/SDM845/SM6350. + - ss_phy_irq: Used for remote wakeup in Super Speed mode of operation. + minItems: 3 + maxItems: 19 + + interrupt-names: + minItems: 3 + maxItems: 19 + + qcom,select-utmi-as-pipe-clk: + description: + If present, disable USB3 pipe_clk requirement. + Used when dwc3 operates without SSPHY and only + HS/FS/LS modes are supported. + type: boolean + + wakeup-source: true + +# Required child node: + +required: + - compatible + - reg + - clocks + - clock-names + - interrupts + - interrupt-names + +allOf: + - $ref: snps,dwc3-common.yaml# + - if: + properties: + compatible: + contains: + enum: + - qcom,ipq4019-dwc3 + - qcom,ipq5332-dwc3 + then: + properties: + clocks: + maxItems: 3 + clock-names: + items: + - const: core + - const: sleep + - const: mock_utmi + + - if: + properties: + compatible: + contains: + enum: + - qcom,ipq8064-dwc3 + then: + properties: + clocks: + items: + - description: Master/Core clock, has to be >= 125 MHz + for SS operation and >= 60MHz for HS operation. + clock-names: + items: + - const: core + + - if: + properties: + compatible: + contains: + enum: + - qcom,ipq9574-dwc3 + - qcom,msm8953-dwc3 + - qcom,msm8996-dwc3 + - qcom,msm8998-dwc3 + - qcom,qcs8300-dwc3 + - qcom,sa8775p-dwc3 + - qcom,sc7180-dwc3 + - qcom,sc7280-dwc3 + - qcom,sdm670-dwc3 + - qcom,sdm845-dwc3 + - qcom,sdx55-dwc3 + - qcom,sdx65-dwc3 + - qcom,sdx75-dwc3 + - qcom,sm6350-dwc3 + then: + properties: + clocks: + maxItems: 5 + clock-names: + items: + - const: cfg_noc + - const: core + - const: iface + - const: sleep + - const: mock_utmi + + - if: + properties: + compatible: + contains: + enum: + - qcom,ipq6018-dwc3 + then: + properties: + clocks: + minItems: 3 + maxItems: 4 + clock-names: + oneOf: + - items: + - const: core + - const: sleep + - const: mock_utmi + - items: + - const: cfg_noc + - const: core + - const: sleep + - const: mock_utmi + + - if: + properties: + compatible: + contains: + enum: + - qcom,ipq8074-dwc3 + - qcom,qdu1000-dwc3 + then: + properties: + clocks: + maxItems: 4 + clock-names: + items: + - const: cfg_noc + - const: core + - const: sleep + - const: mock_utmi + + - if: + properties: + compatible: + contains: + enum: + - qcom,ipq5018-dwc3 + - qcom,msm8994-dwc3 + - qcom,qcs404-dwc3 + then: + properties: + clocks: + maxItems: 4 + clock-names: + items: + - const: core + - const: iface + - const: sleep + - const: mock_utmi + + - if: + properties: + compatible: + contains: + enum: + - qcom,sc8280xp-dwc3 + - qcom,sc8280xp-dwc3-mp + - qcom,x1e80100-dwc3 + - qcom,x1e80100-dwc3-mp + then: + properties: + clocks: + maxItems: 9 + clock-names: + items: + - const: cfg_noc + - const: core + - const: iface + - const: sleep + - const: mock_utmi + - const: noc_aggr + - const: noc_aggr_north + - const: noc_aggr_south + - const: noc_sys + + - if: + properties: + compatible: + contains: + enum: + - qcom,sdm660-dwc3 + then: + properties: + clocks: + minItems: 4 + maxItems: 5 + clock-names: + oneOf: + - items: + - const: cfg_noc + - const: core + - const: iface + - const: sleep + - const: mock_utmi + - items: + - const: cfg_noc + - const: core + - const: sleep + - const: mock_utmi + + - if: + properties: + compatible: + contains: + enum: + - qcom,qcm2290-dwc3 + - qcom,qcs615-dwc3 + - qcom,sar2130p-dwc3 + - qcom,sc8180x-dwc3 + - qcom,sc8180x-dwc3-mp + - qcom,sm6115-dwc3 + - qcom,sm6125-dwc3 + - qcom,sm8150-dwc3 + - qcom,sm8250-dwc3 + - qcom,sm8450-dwc3 + - qcom,sm8550-dwc3 + - qcom,sm8650-dwc3 + then: + properties: + clocks: + minItems: 6 + clock-names: + items: + - const: cfg_noc + - const: core + - const: iface + - const: sleep + - const: mock_utmi + - const: xo + + - if: + properties: + compatible: + contains: + enum: + - qcom,sm8350-dwc3 + then: + properties: + clocks: + minItems: 5 + maxItems: 6 + clock-names: + minItems: 5 + items: + - const: cfg_noc + - const: core + - const: iface + - const: sleep + - const: mock_utmi + - const: xo + + - if: + properties: + compatible: + contains: + enum: + - qcom,ipq5018-dwc3 + - qcom,ipq6018-dwc3 + - qcom,ipq8074-dwc3 + - qcom,msm8953-dwc3 + - qcom,msm8998-dwc3 + then: + properties: + interrupts: + minItems: 3 + maxItems: 4 + interrupt-names: + minItems: 3 + items: + - const: dwc_usb3 + - const: pwr_event + - const: qusb2_phy + - const: ss_phy_irq + + - if: + properties: + compatible: + contains: + enum: + - qcom,msm8996-dwc3 + - qcom,qcs404-dwc3 + - qcom,sdm660-dwc3 + - qcom,sm6115-dwc3 + - qcom,sm6125-dwc3 + then: + properties: + interrupts: + minItems: 4 + maxItems: 5 + interrupt-names: + minItems: 4 + items: + - const: dwc_usb3 + - const: pwr_event + - const: qusb2_phy + - const: hs_phy_irq + - const: ss_phy_irq + + - if: + properties: + compatible: + contains: + enum: + - qcom,ipq5332-dwc3 + then: + properties: + interrupts: + maxItems: 4 + interrupt-names: + items: + - const: dwc_usb3 + - const: pwr_event + - const: dp_hs_phy_irq + - const: dm_hs_phy_irq + + - if: + properties: + compatible: + contains: + enum: + - qcom,x1e80100-dwc3 + then: + properties: + interrupts: + maxItems: 5 + interrupt-names: + items: + - const: dwc_usb3 + - const: pwr_event + - const: dp_hs_phy_irq + - const: dm_hs_phy_irq + - const: ss_phy_irq + + - if: + properties: + compatible: + contains: + enum: + - qcom,ipq4019-dwc3 + - qcom,ipq8064-dwc3 + - qcom,msm8994-dwc3 + - qcom,qcs615-dwc3 + - qcom,qcs8300-dwc3 + - qcom,qdu1000-dwc3 + - qcom,sa8775p-dwc3 + - qcom,sc7180-dwc3 + - qcom,sc7280-dwc3 + - qcom,sc8180x-dwc3 + - qcom,sc8280xp-dwc3 + - qcom,sdm670-dwc3 + - qcom,sdm845-dwc3 + - qcom,sdx55-dwc3 + - qcom,sdx65-dwc3 + - qcom,sdx75-dwc3 + - qcom,sm4250-dwc3 + - qcom,sm6350-dwc3 + - qcom,sm8150-dwc3 + - qcom,sm8250-dwc3 + - qcom,sm8350-dwc3 + - qcom,sm8450-dwc3 + - qcom,sm8550-dwc3 + - qcom,sm8650-dwc3 + then: + properties: + interrupts: + minItems: 5 + maxItems: 6 + interrupt-names: + minItems: 5 + items: + - const: dwc_usb3 + - const: pwr_event + - const: hs_phy_irq + - const: dp_hs_phy_irq + - const: dm_hs_phy_irq + - const: ss_phy_irq + + - if: + properties: + compatible: + contains: + enum: + - qcom,sc8180x-dwc3-mp + - qcom,x1e80100-dwc3-mp + then: + properties: + interrupts: + minItems: 11 + maxItems: 11 + interrupt-names: + items: + - const: dwc_usb3 + - const: pwr_event_1 + - const: pwr_event_2 + - const: hs_phy_1 + - const: hs_phy_2 + - const: dp_hs_phy_1 + - const: dm_hs_phy_1 + - const: dp_hs_phy_2 + - const: dm_hs_phy_2 + - const: ss_phy_1 + - const: ss_phy_2 + + - if: + properties: + compatible: + contains: + enum: + - qcom,sc8280xp-dwc3-mp + then: + properties: + interrupts: + minItems: 19 + maxItems: 19 + interrupt-names: + items: + - const: dwc_usb3 + - const: pwr_event_1 + - const: pwr_event_2 + - const: pwr_event_3 + - const: pwr_event_4 + - const: hs_phy_1 + - const: hs_phy_2 + - const: hs_phy_3 + - const: hs_phy_4 + - const: dp_hs_phy_1 + - const: dm_hs_phy_1 + - const: dp_hs_phy_2 + - const: dm_hs_phy_2 + - const: dp_hs_phy_3 + - const: dm_hs_phy_3 + - const: dp_hs_phy_4 + - const: dm_hs_phy_4 + - const: ss_phy_1 + - const: ss_phy_2 + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + soc { + #address-cells = <2>; + #size-cells = <2>; + + usb@a600000 { + compatible = "qcom,sdm845-dwc3", "qcom,snps-dwc3"; + reg = <0 0x0a600000 0 0x100000>; + + clocks = <&gcc GCC_CFG_NOC_USB3_PRIM_AXI_CLK>, + <&gcc GCC_USB30_PRIM_MASTER_CLK>, + <&gcc GCC_AGGRE_USB3_PRIM_AXI_CLK>, + <&gcc GCC_USB30_PRIM_SLEEP_CLK>, + <&gcc GCC_USB30_PRIM_MOCK_UTMI_CLK>; + clock-names = "cfg_noc", + "core", + "iface", + "sleep", + "mock_utmi"; + + assigned-clocks = <&gcc GCC_USB30_PRIM_MOCK_UTMI_CLK>, + <&gcc GCC_USB30_PRIM_MASTER_CLK>; + assigned-clock-rates = <19200000>, <150000000>; + + interrupts = , + , + , + , + , + ; + interrupt-names = "dwc_usb3", "pwr_event", "hs_phy_irq", + "dp_hs_phy_irq", "dm_hs_phy_irq", "ss_phy_irq"; + + power-domains = <&gcc USB30_PRIM_GDSC>; + + resets = <&gcc GCC_USB30_PRIM_BCR>; + + iommus = <&apps_smmu 0x740 0>; + snps,dis_u2_susphy_quirk; + snps,dis_enblslpm_quirk; + phys = <&usb_1_hsphy>, <&usb_1_ssphy>; + phy-names = "usb2-phy", "usb3-phy"; + }; + }; +... -- cgit v1.2.3-59-g8ed1b From 613a2e655d4dc9cd64c846b36543a5ef4e8de004 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Mon, 14 Apr 2025 20:21:52 -0500 Subject: usb: dwc3: core: Expose core driver as library The DWC3 IP block is handled by three distinct device drivers: XHCI, DWC3 core and a platform specific (optional) DWC3 glue driver. This has resulted in, at least in the case of the Qualcomm glue, the presence of a number of layering violations, where the glue code either can't handle, or has to work around, the fact that core might not probe deterministically. An example of this is that the suspend path should operate slightly different depending on the device operating in host or peripheral mode, and the only way to determine the operating state is to peek into the core's drvdata. The Qualcomm glue driver is expected to make updates in the qscratch register region (the "glue" region) during role switch events, but with the glue and core split using the driver model, there is no reasonable way to introduce listeners for mode changes. Split the dwc3 core platform_driver callbacks and their implementation and export the implementation, to make it possible to deterministically instantiate the dwc3 core as part of the dwc3 glue drivers and to allow flattening of the DeviceTree representation. Acked-by: Thinh Nguyen Tested-by: Neil Armstrong # on SM8650-QRD Signed-off-by: Bjorn Andersson Link: https://lore.kernel.org/r/20250414-dwc3-refactor-v7-3-f015b358722d@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 155 +++++++++++++++++++++++++++++++++--------------- drivers/usb/dwc3/glue.h | 33 +++++++++++ 2 files changed, 140 insertions(+), 48 deletions(-) create mode 100644 drivers/usb/dwc3/glue.h diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index b169913172fc..81324d586c07 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -37,6 +37,7 @@ #include "core.h" #include "gadget.h" +#include "glue.h" #include "io.h" #include "debug.h" @@ -2161,27 +2162,16 @@ static struct power_supply *dwc3_get_usb_power_supply(struct dwc3 *dwc) return usb_psy; } -static int dwc3_probe(struct platform_device *pdev) +int dwc3_core_probe(const struct dwc3_probe_data *data) { - struct device *dev = &pdev->dev; - struct resource *res, dwc_res; + struct dwc3 *dwc = data->dwc; + struct device *dev = dwc->dev; + struct resource dwc_res; unsigned int hw_mode; void __iomem *regs; - struct dwc3 *dwc; + struct resource *res = data->res; int ret; - dwc = devm_kzalloc(dev, sizeof(*dwc), GFP_KERNEL); - if (!dwc) - return -ENOMEM; - - dwc->dev = dev; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(dev, "missing memory resource\n"); - return -ENODEV; - } - dwc->xhci_resources[0].start = res->start; dwc->xhci_resources[0].end = dwc->xhci_resources[0].start + DWC3_XHCI_REGS_END; @@ -2245,7 +2235,7 @@ static int dwc3_probe(struct platform_device *pdev) goto err_disable_clks; } - platform_set_drvdata(pdev, dwc); + dev_set_drvdata(dev, dwc); dwc3_cache_hwparams(dwc); if (!dwc->sysdev_is_parent && @@ -2340,12 +2330,35 @@ err_put_psy: return ret; } +EXPORT_SYMBOL_GPL(dwc3_core_probe); -static void dwc3_remove(struct platform_device *pdev) +static int dwc3_probe(struct platform_device *pdev) { - struct dwc3 *dwc = platform_get_drvdata(pdev); + struct dwc3_probe_data probe_data; + struct resource *res; + struct dwc3 *dwc; - pm_runtime_get_sync(&pdev->dev); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "missing memory resource\n"); + return -ENODEV; + } + + dwc = devm_kzalloc(&pdev->dev, sizeof(*dwc), GFP_KERNEL); + if (!dwc) + return -ENOMEM; + + dwc->dev = &pdev->dev; + + probe_data.dwc = dwc; + probe_data.res = res; + + return dwc3_core_probe(&probe_data); +} + +void dwc3_core_remove(struct dwc3 *dwc) +{ + pm_runtime_get_sync(dwc->dev); dwc3_core_exit_mode(dwc); dwc3_debugfs_exit(dwc); @@ -2353,22 +2366,28 @@ static void dwc3_remove(struct platform_device *pdev) dwc3_core_exit(dwc); dwc3_ulpi_exit(dwc); - pm_runtime_allow(&pdev->dev); - pm_runtime_disable(&pdev->dev); - pm_runtime_dont_use_autosuspend(&pdev->dev); - pm_runtime_put_noidle(&pdev->dev); + pm_runtime_allow(dwc->dev); + pm_runtime_disable(dwc->dev); + pm_runtime_dont_use_autosuspend(dwc->dev); + pm_runtime_put_noidle(dwc->dev); /* * HACK: Clear the driver data, which is currently accessed by parent * glue drivers, before allowing the parent to suspend. */ - platform_set_drvdata(pdev, NULL); - pm_runtime_set_suspended(&pdev->dev); + dev_set_drvdata(dwc->dev, NULL); + pm_runtime_set_suspended(dwc->dev); dwc3_free_event_buffers(dwc); if (dwc->usb_psy) power_supply_put(dwc->usb_psy); } +EXPORT_SYMBOL_GPL(dwc3_core_remove); + +static void dwc3_remove(struct platform_device *pdev) +{ + dwc3_core_remove(platform_get_drvdata(pdev)); +} #ifdef CONFIG_PM static int dwc3_core_init_for_resume(struct dwc3 *dwc) @@ -2557,9 +2576,8 @@ static int dwc3_runtime_checks(struct dwc3 *dwc) return 0; } -static int dwc3_runtime_suspend(struct device *dev) +int dwc3_runtime_suspend(struct dwc3 *dwc) { - struct dwc3 *dwc = dev_get_drvdata(dev); int ret; if (dwc3_runtime_checks(dwc)) @@ -2571,10 +2589,11 @@ static int dwc3_runtime_suspend(struct device *dev) return 0; } +EXPORT_SYMBOL_GPL(dwc3_runtime_suspend); -static int dwc3_runtime_resume(struct device *dev) +int dwc3_runtime_resume(struct dwc3 *dwc) { - struct dwc3 *dwc = dev_get_drvdata(dev); + struct device *dev = dwc->dev; int ret; ret = dwc3_resume_common(dwc, PMSG_AUTO_RESUME); @@ -2584,7 +2603,7 @@ static int dwc3_runtime_resume(struct device *dev) switch (dwc->current_dr_role) { case DWC3_GCTL_PRTCAP_DEVICE: if (dwc->pending_events) { - pm_runtime_put(dwc->dev); + pm_runtime_put(dev); dwc->pending_events = false; enable_irq(dwc->irq_gadget); } @@ -2599,10 +2618,11 @@ static int dwc3_runtime_resume(struct device *dev) return 0; } +EXPORT_SYMBOL_GPL(dwc3_runtime_resume); -static int dwc3_runtime_idle(struct device *dev) +int dwc3_runtime_idle(struct dwc3 *dwc) { - struct dwc3 *dwc = dev_get_drvdata(dev); + struct device *dev = dwc->dev; switch (dwc->current_dr_role) { case DWC3_GCTL_PRTCAP_DEVICE: @@ -2620,12 +2640,28 @@ static int dwc3_runtime_idle(struct device *dev) return 0; } +EXPORT_SYMBOL_GPL(dwc3_runtime_idle); + +static int dwc3_plat_runtime_suspend(struct device *dev) +{ + return dwc3_runtime_suspend(dev_get_drvdata(dev)); +} + +static int dwc3_plat_runtime_resume(struct device *dev) +{ + return dwc3_runtime_resume(dev_get_drvdata(dev)); +} + +static int dwc3_plat_runtime_idle(struct device *dev) +{ + return dwc3_runtime_idle(dev_get_drvdata(dev)); +} #endif /* CONFIG_PM */ #ifdef CONFIG_PM_SLEEP -static int dwc3_suspend(struct device *dev) +int dwc3_pm_suspend(struct dwc3 *dwc) { - struct dwc3 *dwc = dev_get_drvdata(dev); + struct device *dev = dwc->dev; int ret; ret = dwc3_suspend_common(dwc, PMSG_SUSPEND); @@ -2636,10 +2672,11 @@ static int dwc3_suspend(struct device *dev) return 0; } +EXPORT_SYMBOL_GPL(dwc3_pm_suspend); -static int dwc3_resume(struct device *dev) +int dwc3_pm_resume(struct dwc3 *dwc) { - struct dwc3 *dwc = dev_get_drvdata(dev); + struct device *dev = dwc->dev; int ret = 0; pinctrl_pm_select_default_state(dev); @@ -2658,10 +2695,10 @@ out: return ret; } +EXPORT_SYMBOL_GPL(dwc3_pm_resume); -static void dwc3_complete(struct device *dev) +void dwc3_pm_complete(struct dwc3 *dwc) { - struct dwc3 *dwc = dev_get_drvdata(dev); u32 reg; if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST && @@ -2671,10 +2708,11 @@ static void dwc3_complete(struct device *dev) dwc3_writel(dwc->regs, DWC3_GUCTL3, reg); } } +EXPORT_SYMBOL_GPL(dwc3_pm_complete); -static int dwc3_prepare(struct device *dev) +int dwc3_pm_prepare(struct dwc3 *dwc) { - struct dwc3 *dwc = dev_get_drvdata(dev); + struct device *dev = dwc->dev; /* * Indicate to the PM core that it may safely leave the device in @@ -2687,22 +2725,43 @@ static int dwc3_prepare(struct device *dev) return 0; } +EXPORT_SYMBOL_GPL(dwc3_pm_prepare); + +static int dwc3_plat_suspend(struct device *dev) +{ + return dwc3_pm_suspend(dev_get_drvdata(dev)); +} + +static int dwc3_plat_resume(struct device *dev) +{ + return dwc3_pm_resume(dev_get_drvdata(dev)); +} + +static void dwc3_plat_complete(struct device *dev) +{ + dwc3_pm_complete(dev_get_drvdata(dev)); +} + +static int dwc3_plat_prepare(struct device *dev) +{ + return dwc3_pm_prepare(dev_get_drvdata(dev)); +} #else -#define dwc3_complete NULL -#define dwc3_prepare NULL +#define dwc3_plat_complete NULL +#define dwc3_plat_prepare NULL #endif /* CONFIG_PM_SLEEP */ static const struct dev_pm_ops dwc3_dev_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(dwc3_suspend, dwc3_resume) - .complete = dwc3_complete, - .prepare = dwc3_prepare, + SET_SYSTEM_SLEEP_PM_OPS(dwc3_plat_suspend, dwc3_plat_resume) + .complete = dwc3_plat_complete, + .prepare = dwc3_plat_prepare, /* * Runtime suspend halts the controller on disconnection. It relies on * platforms with custom connection notification to start the controller * again. */ - SET_RUNTIME_PM_OPS(dwc3_runtime_suspend, dwc3_runtime_resume, - dwc3_runtime_idle) + SET_RUNTIME_PM_OPS(dwc3_plat_runtime_suspend, dwc3_plat_runtime_resume, + dwc3_plat_runtime_idle) }; #ifdef CONFIG_OF diff --git a/drivers/usb/dwc3/glue.h b/drivers/usb/dwc3/glue.h new file mode 100644 index 000000000000..bc446f92ec8b --- /dev/null +++ b/drivers/usb/dwc3/glue.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * glue.h - DesignWare USB3 DRD glue header + */ + +#ifndef __DRIVERS_USB_DWC3_GLUE_H +#define __DRIVERS_USB_DWC3_GLUE_H + +#include +#include "core.h" + +/** + * dwc3_probe_data: Initialization parameters passed to dwc3_core_probe() + * @dwc: Reference to dwc3 context structure + * @res: resource for the DWC3 core mmio region + */ +struct dwc3_probe_data { + struct dwc3 *dwc; + struct resource *res; +}; + +int dwc3_core_probe(const struct dwc3_probe_data *data); +void dwc3_core_remove(struct dwc3 *dwc); + +int dwc3_runtime_suspend(struct dwc3 *dwc); +int dwc3_runtime_resume(struct dwc3 *dwc); +int dwc3_runtime_idle(struct dwc3 *dwc); +int dwc3_pm_suspend(struct dwc3 *dwc); +int dwc3_pm_resume(struct dwc3 *dwc); +void dwc3_pm_complete(struct dwc3 *dwc); +int dwc3_pm_prepare(struct dwc3 *dwc); + +#endif -- cgit v1.2.3-59-g8ed1b From 170940f7e6859152d8579e7be57c6a2060438651 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Mon, 14 Apr 2025 20:21:53 -0500 Subject: usb: dwc3: core: Don't touch resets and clocks When the core is integrated with glue, it's reasonable to assume that the glue driver will have to touch the IP before/after the core takes the hardware out and into reset. As such the glue must own these resources and be allowed to turn them on/off outside the core's handling. Allow the platform or glue layer to indicate if the core logic for clocks and resets should be skipped to deal with this. Reviewed-by: Frank Li Acked-by: Thinh Nguyen Tested-by: Neil Armstrong # on SM8650-QRD Signed-off-by: Bjorn Andersson Link: https://lore.kernel.org/r/20250414-dwc3-refactor-v7-4-f015b358722d@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 20 +++++++++++--------- drivers/usb/dwc3/glue.h | 3 +++ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 81324d586c07..2bc775a747f2 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -2211,15 +2211,17 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) if (IS_ERR(dwc->usb_psy)) return dev_err_probe(dev, PTR_ERR(dwc->usb_psy), "couldn't get usb power supply\n"); - dwc->reset = devm_reset_control_array_get_optional_shared(dev); - if (IS_ERR(dwc->reset)) { - ret = PTR_ERR(dwc->reset); - goto err_put_psy; - } + if (!data->ignore_clocks_and_resets) { + dwc->reset = devm_reset_control_array_get_optional_shared(dev); + if (IS_ERR(dwc->reset)) { + ret = PTR_ERR(dwc->reset); + goto err_put_psy; + } - ret = dwc3_get_clocks(dwc); - if (ret) - goto err_put_psy; + ret = dwc3_get_clocks(dwc); + if (ret) + goto err_put_psy; + } ret = reset_control_deassert(dwc->reset); if (ret) @@ -2334,7 +2336,7 @@ EXPORT_SYMBOL_GPL(dwc3_core_probe); static int dwc3_probe(struct platform_device *pdev) { - struct dwc3_probe_data probe_data; + struct dwc3_probe_data probe_data = {}; struct resource *res; struct dwc3 *dwc; diff --git a/drivers/usb/dwc3/glue.h b/drivers/usb/dwc3/glue.h index bc446f92ec8b..2efd00e763be 100644 --- a/drivers/usb/dwc3/glue.h +++ b/drivers/usb/dwc3/glue.h @@ -13,10 +13,13 @@ * dwc3_probe_data: Initialization parameters passed to dwc3_core_probe() * @dwc: Reference to dwc3 context structure * @res: resource for the DWC3 core mmio region + * @ignore_clocks_and_resets: clocks and resets defined for the device should + * be ignored by the DWC3 core, as they are managed by the glue */ struct dwc3_probe_data { struct dwc3 *dwc; struct resource *res; + bool ignore_clocks_and_resets; }; int dwc3_core_probe(const struct dwc3_probe_data *data); -- cgit v1.2.3-59-g8ed1b From 2dc9f137e194267773f785cc0a352136fb9e42cc Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Mon, 14 Apr 2025 20:21:54 -0500 Subject: usb: dwc3: qcom: Don't rely on drvdata during probe With the upcoming transition to a model where DWC3 core and glue operate on a single struct device the drvdata datatype will change to be owned by the core. The drvdata is however used by the Qualcomm DWC3 glue to pass the qcom glue context around before the core is allocated. Remove this problem, and clean up the code, by passing the dwc3_qcom struct around during probe, instead of acquiring it from the drvdata. Acked-by: Thinh Nguyen Tested-by: Neil Armstrong # on SM8650-QRD Signed-off-by: Bjorn Andersson Link: https://lore.kernel.org/r/20250414-dwc3-refactor-v7-5-f015b358722d@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-qcom.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index 79f3600f25c4..9d04c2457433 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -547,9 +547,10 @@ static int dwc3_qcom_request_irq(struct dwc3_qcom *qcom, int irq, return ret; } -static int dwc3_qcom_setup_port_irq(struct platform_device *pdev, int port_index, bool is_multiport) +static int dwc3_qcom_setup_port_irq(struct dwc3_qcom *qcom, + struct platform_device *pdev, + int port_index, bool is_multiport) { - struct dwc3_qcom *qcom = platform_get_drvdata(pdev); const char *irq_name; int irq; int ret; @@ -634,9 +635,8 @@ static int dwc3_qcom_find_num_ports(struct platform_device *pdev) return DWC3_QCOM_MAX_PORTS; } -static int dwc3_qcom_setup_irq(struct platform_device *pdev) +static int dwc3_qcom_setup_irq(struct dwc3_qcom *qcom, struct platform_device *pdev) { - struct dwc3_qcom *qcom = platform_get_drvdata(pdev); bool is_multiport; int ret; int i; @@ -645,7 +645,7 @@ static int dwc3_qcom_setup_irq(struct platform_device *pdev) is_multiport = (qcom->num_ports > 1); for (i = 0; i < qcom->num_ports; i++) { - ret = dwc3_qcom_setup_port_irq(pdev, i, is_multiport); + ret = dwc3_qcom_setup_port_irq(qcom, pdev, i, is_multiport); if (ret) return ret; } @@ -700,9 +700,8 @@ static int dwc3_qcom_clk_init(struct dwc3_qcom *qcom, int count) return 0; } -static int dwc3_qcom_of_register_core(struct platform_device *pdev) +static int dwc3_qcom_of_register_core(struct dwc3_qcom *qcom, struct platform_device *pdev) { - struct dwc3_qcom *qcom = platform_get_drvdata(pdev); struct device_node *np = pdev->dev.of_node; struct device *dev = &pdev->dev; int ret; @@ -778,7 +777,7 @@ static int dwc3_qcom_probe(struct platform_device *pdev) goto clk_disable; } - ret = dwc3_qcom_setup_irq(pdev); + ret = dwc3_qcom_setup_irq(qcom, pdev); if (ret) { dev_err(dev, "failed to setup IRQs, err=%d\n", ret); goto clk_disable; @@ -793,7 +792,7 @@ static int dwc3_qcom_probe(struct platform_device *pdev) if (ignore_pipe_clk) dwc3_qcom_select_utmi_clk(qcom); - ret = dwc3_qcom_of_register_core(pdev); + ret = dwc3_qcom_of_register_core(qcom, pdev); if (ret) { dev_err(dev, "failed to register DWC3 Core, err=%d\n", ret); goto clk_disable; -- cgit v1.2.3-59-g8ed1b From 1881a32fe14df801838302aa15842957c76a4ebd Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Mon, 14 Apr 2025 20:21:55 -0500 Subject: usb: dwc3: qcom: Transition to flattened model The USB IP-block found in most Qualcomm platforms is modelled in the Linux kernel as 3 different independent device drivers, but as shown by the already existing layering violations in the Qualcomm glue driver they can not be operated independently. With the current implementation, the glue driver registers the core and has no way to know when this is done. As a result, e.g. the suspend callbacks needs to guard against NULL pointer dereferences when trying to peek into the struct dwc3 found in the drvdata of the child. Even with these checks, there are no way to fully protect ourselves from the race conditions that occur if the DWC3 is unbound. Missing from the upstream Qualcomm USB support is handling of role switching, in which the glue needs to be notified upon DRD mode changes. Several attempts has been made through the years to register callbacks etc, but they always fall short when it comes to handling of the core's probe deferral on resources etc. Moving to a model where the DWC3 core is instantiated in a synchronous fashion avoids above described race conditions. It is however not feasible to do so without also flattening the DeviceTree binding, as assumptions are made in the DWC3 core and frameworks used that the device's associated of_node will the that of the core. Furthermore, the DeviceTree binding is a direct representation of the Linux driver model, and doesn't necessarily describe "the USB IP-block". The Qualcomm DWC3 glue driver is therefor transitioned to initialize and operate the DWC3 within the one device context, in synchronous fashion. To provide a limited time backwards compatibility, a snapshot of the driver is retained in a previous commit. As such no care is taken in the dwc3-qcom driver for the qcom,dwc3 backwards compatibility. Acked-by: Thinh Nguyen Tested-by: Neil Armstrong # on SM8650-QRD Signed-off-by: Bjorn Andersson Link: https://lore.kernel.org/r/20250414-dwc3-refactor-v7-6-f015b358722d@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-qcom.c | 177 ++++++++++++++++++++++++------------------- 1 file changed, 99 insertions(+), 78 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index 9d04c2457433..d512002e1e88 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -4,7 +4,6 @@ * Inspired by dwc3-of-simple.c */ -#include #include #include #include @@ -14,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -23,6 +21,7 @@ #include #include #include "core.h" +#include "glue.h" /* USB QSCRATCH Hardware registers */ #define QSCRATCH_HS_PHY_CTRL 0x10 @@ -73,7 +72,7 @@ struct dwc3_qcom_port { struct dwc3_qcom { struct device *dev; void __iomem *qscratch_base; - struct platform_device *dwc3; + struct dwc3 dwc; struct clk **clks; int num_clocks; struct reset_control *resets; @@ -92,6 +91,8 @@ struct dwc3_qcom { struct icc_path *icc_path_apps; }; +#define to_dwc3_qcom(d) container_of((d), struct dwc3_qcom, dwc) + static inline void dwc3_qcom_setbits(void __iomem *base, u32 offset, u32 val) { u32 reg; @@ -116,6 +117,11 @@ static inline void dwc3_qcom_clrbits(void __iomem *base, u32 offset, u32 val) readl(base + offset); } +/* + * TODO: Make the in-core role switching code invoke dwc3_qcom_vbus_override_enable(), + * validate that the in-core extcon support is functional, and drop extcon + * handling from the glue + */ static void dwc3_qcom_vbus_override_enable(struct dwc3_qcom *qcom, bool enable) { if (enable) { @@ -260,7 +266,7 @@ static int dwc3_qcom_interconnect_init(struct dwc3_qcom *qcom) goto put_path_ddr; } - max_speed = usb_get_maximum_speed(&qcom->dwc3->dev); + max_speed = usb_get_maximum_speed(qcom->dwc.dev); if (max_speed >= USB_SPEED_SUPER || max_speed == USB_SPEED_UNKNOWN) { ret = icc_set_bw(qcom->icc_path_ddr, USB_MEMORY_AVG_SS_BW, USB_MEMORY_PEAK_SS_BW); @@ -303,25 +309,14 @@ static void dwc3_qcom_interconnect_exit(struct dwc3_qcom *qcom) /* Only usable in contexts where the role can not change. */ static bool dwc3_qcom_is_host(struct dwc3_qcom *qcom) { - struct dwc3 *dwc; - - /* - * FIXME: Fix this layering violation. - */ - dwc = platform_get_drvdata(qcom->dwc3); - - /* Core driver may not have probed yet. */ - if (!dwc) - return false; - - return dwc->xhci; + return qcom->dwc.xhci; } static enum usb_device_speed dwc3_qcom_read_usb2_speed(struct dwc3_qcom *qcom, int port_index) { - struct dwc3 *dwc = platform_get_drvdata(qcom->dwc3); struct usb_device *udev; struct usb_hcd __maybe_unused *hcd; + struct dwc3 *dwc = &qcom->dwc; /* * FIXME: Fix this layering violation. @@ -498,7 +493,7 @@ static int dwc3_qcom_resume(struct dwc3_qcom *qcom, bool wakeup) static irqreturn_t qcom_dwc3_resume_irq(int irq, void *data) { struct dwc3_qcom *qcom = data; - struct dwc3 *dwc = platform_get_drvdata(qcom->dwc3); + struct dwc3 *dwc = &qcom->dwc; /* If pm_suspended then let pm_resume take care of resuming h/w */ if (qcom->pm_suspended) @@ -700,40 +695,14 @@ static int dwc3_qcom_clk_init(struct dwc3_qcom *qcom, int count) return 0; } -static int dwc3_qcom_of_register_core(struct dwc3_qcom *qcom, struct platform_device *pdev) -{ - struct device_node *np = pdev->dev.of_node; - struct device *dev = &pdev->dev; - int ret; - - struct device_node *dwc3_np __free(device_node) = of_get_compatible_child(np, - "snps,dwc3"); - if (!dwc3_np) { - dev_err(dev, "failed to find dwc3 core child\n"); - return -ENODEV; - } - - ret = of_platform_populate(np, NULL, NULL, dev); - if (ret) { - dev_err(dev, "failed to register dwc3 core - %d\n", ret); - return ret; - } - - qcom->dwc3 = of_find_device_by_node(dwc3_np); - if (!qcom->dwc3) { - ret = -ENODEV; - dev_err(dev, "failed to get dwc3 platform device\n"); - of_platform_depopulate(dev); - } - - return ret; -} - static int dwc3_qcom_probe(struct platform_device *pdev) { + struct dwc3_probe_data probe_data = {}; struct device_node *np = pdev->dev.of_node; struct device *dev = &pdev->dev; struct dwc3_qcom *qcom; + struct resource res; + struct resource *r; int ret, i; bool ignore_pipe_clk; bool wakeup_source; @@ -742,7 +711,6 @@ static int dwc3_qcom_probe(struct platform_device *pdev) if (!qcom) return -ENOMEM; - platform_set_drvdata(pdev, qcom); qcom->dev = &pdev->dev; qcom->resets = devm_reset_control_array_get_optional_exclusive(dev); @@ -771,8 +739,15 @@ static int dwc3_qcom_probe(struct platform_device *pdev) goto reset_assert; } - qcom->qscratch_base = devm_platform_ioremap_resource(pdev, 0); + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) + goto clk_disable; + res = *r; + res.end = res.start + SDM845_QSCRATCH_BASE_OFFSET; + + qcom->qscratch_base = devm_ioremap(dev, res.end, SDM845_QSCRATCH_SIZE); if (IS_ERR(qcom->qscratch_base)) { + dev_err(dev, "failed to map qscratch region: %pe\n", qcom->qscratch_base); ret = PTR_ERR(qcom->qscratch_base); goto clk_disable; } @@ -792,17 +767,21 @@ static int dwc3_qcom_probe(struct platform_device *pdev) if (ignore_pipe_clk) dwc3_qcom_select_utmi_clk(qcom); - ret = dwc3_qcom_of_register_core(qcom, pdev); - if (ret) { - dev_err(dev, "failed to register DWC3 Core, err=%d\n", ret); + qcom->dwc.dev = dev; + probe_data.dwc = &qcom->dwc; + probe_data.res = &res; + probe_data.ignore_clocks_and_resets = true; + ret = dwc3_core_probe(&probe_data); + if (ret) { + ret = dev_err_probe(dev, ret, "failed to register DWC3 Core\n"); goto clk_disable; } ret = dwc3_qcom_interconnect_init(qcom); if (ret) - goto depopulate; + goto remove_core; - qcom->mode = usb_get_dr_mode(&qcom->dwc3->dev); + qcom->mode = usb_get_dr_mode(dev); /* enable vbus override for device mode */ if (qcom->mode != USB_DR_MODE_HOST) @@ -815,20 +794,15 @@ static int dwc3_qcom_probe(struct platform_device *pdev) wakeup_source = of_property_read_bool(dev->of_node, "wakeup-source"); device_init_wakeup(&pdev->dev, wakeup_source); - device_init_wakeup(&qcom->dwc3->dev, wakeup_source); qcom->is_suspended = false; - pm_runtime_set_active(dev); - pm_runtime_enable(dev); - pm_runtime_forbid(dev); return 0; interconnect_exit: dwc3_qcom_interconnect_exit(qcom); -depopulate: - of_platform_depopulate(&pdev->dev); - platform_device_put(qcom->dwc3); +remove_core: + dwc3_core_remove(&qcom->dwc); clk_disable: for (i = qcom->num_clocks - 1; i >= 0; i--) { clk_disable_unprepare(qcom->clks[i]); @@ -842,12 +816,11 @@ reset_assert: static void dwc3_qcom_remove(struct platform_device *pdev) { - struct dwc3_qcom *qcom = platform_get_drvdata(pdev); - struct device *dev = &pdev->dev; + struct dwc3 *dwc = platform_get_drvdata(pdev); + struct dwc3_qcom *qcom = to_dwc3_qcom(dwc); int i; - of_platform_depopulate(&pdev->dev); - platform_device_put(qcom->dwc3); + dwc3_core_remove(&qcom->dwc); for (i = qcom->num_clocks - 1; i >= 0; i--) { clk_disable_unprepare(qcom->clks[i]); @@ -857,17 +830,20 @@ static void dwc3_qcom_remove(struct platform_device *pdev) dwc3_qcom_interconnect_exit(qcom); reset_control_assert(qcom->resets); - - pm_runtime_allow(dev); - pm_runtime_disable(dev); } -static int __maybe_unused dwc3_qcom_pm_suspend(struct device *dev) +#ifdef CONFIG_PM_SLEEP +static int dwc3_qcom_pm_suspend(struct device *dev) { - struct dwc3_qcom *qcom = dev_get_drvdata(dev); + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_qcom *qcom = to_dwc3_qcom(dwc); bool wakeup = device_may_wakeup(dev); int ret; + ret = dwc3_pm_suspend(&qcom->dwc); + if (ret) + return ret; + ret = dwc3_qcom_suspend(qcom, wakeup); if (ret) return ret; @@ -877,9 +853,10 @@ static int __maybe_unused dwc3_qcom_pm_suspend(struct device *dev) return 0; } -static int __maybe_unused dwc3_qcom_pm_resume(struct device *dev) +static int dwc3_qcom_pm_resume(struct device *dev) { - struct dwc3_qcom *qcom = dev_get_drvdata(dev); + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_qcom *qcom = to_dwc3_qcom(dwc); bool wakeup = device_may_wakeup(dev); int ret; @@ -889,30 +866,74 @@ static int __maybe_unused dwc3_qcom_pm_resume(struct device *dev) qcom->pm_suspended = false; + ret = dwc3_pm_resume(&qcom->dwc); + if (ret) + return ret; + return 0; } -static int __maybe_unused dwc3_qcom_runtime_suspend(struct device *dev) +static void dwc3_qcom_complete(struct device *dev) { - struct dwc3_qcom *qcom = dev_get_drvdata(dev); + struct dwc3 *dwc = dev_get_drvdata(dev); + + dwc3_pm_complete(dwc); +} + +static int dwc3_qcom_prepare(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + + return dwc3_pm_prepare(dwc); +} +#else +#define dwc3_qcom_complete NULL +#define dwc3_qcom_prepare NULL +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM +static int dwc3_qcom_runtime_suspend(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_qcom *qcom = to_dwc3_qcom(dwc); + int ret; + + ret = dwc3_runtime_suspend(&qcom->dwc); + if (ret) + return ret; return dwc3_qcom_suspend(qcom, true); } -static int __maybe_unused dwc3_qcom_runtime_resume(struct device *dev) +static int dwc3_qcom_runtime_resume(struct device *dev) { - struct dwc3_qcom *qcom = dev_get_drvdata(dev); + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_qcom *qcom = to_dwc3_qcom(dwc); + int ret; - return dwc3_qcom_resume(qcom, true); + ret = dwc3_qcom_resume(qcom, true); + if (ret) + return ret; + + return dwc3_runtime_resume(&qcom->dwc); +} + +static int dwc3_qcom_runtime_idle(struct device *dev) +{ + return dwc3_runtime_idle(dev_get_drvdata(dev)); } +#endif /* CONFIG_PM */ static const struct dev_pm_ops dwc3_qcom_dev_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(dwc3_qcom_pm_suspend, dwc3_qcom_pm_resume) SET_RUNTIME_PM_OPS(dwc3_qcom_runtime_suspend, dwc3_qcom_runtime_resume, - NULL) + dwc3_qcom_runtime_idle) + .complete = dwc3_qcom_complete, + .prepare = dwc3_qcom_prepare, }; static const struct of_device_id dwc3_qcom_of_match[] = { + { .compatible = "qcom,snps-dwc3" }, { } }; MODULE_DEVICE_TABLE(of, dwc3_qcom_of_match); -- cgit v1.2.3-59-g8ed1b From 153874010354d050f62f8ae25cbb960c17633dc5 Mon Sep 17 00:00:00 2001 From: Chen Yufeng Date: Tue, 15 Apr 2025 14:58:57 +0800 Subject: usb: potential integer overflow in usbg_make_tpg() The variable tpgt in usbg_make_tpg() is defined as unsigned long and is assigned to tpgt->tport_tpgt, which is defined as u16. This may cause an integer overflow when tpgt is greater than USHRT_MAX (65535). I haven't tried to trigger it myself, but it is possible to trigger it by calling usbg_make_tpg() with a large value for tpgt. I modified the type of tpgt to match tpgt->tport_tpgt and adjusted the relevant code accordingly. This patch is similar to commit 59c816c1f24d ("vhost/scsi: potential memory corruption"). Signed-off-by: Chen Yufeng Link: https://lore.kernel.org/r/20250415065857.1619-1-chenyufeng@iie.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_tcm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c index 5a2e1237f85c..6e8804f04baa 100644 --- a/drivers/usb/gadget/function/f_tcm.c +++ b/drivers/usb/gadget/function/f_tcm.c @@ -1641,14 +1641,14 @@ static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn, struct usbg_tport *tport = container_of(wwn, struct usbg_tport, tport_wwn); struct usbg_tpg *tpg; - unsigned long tpgt; + u16 tpgt; int ret; struct f_tcm_opts *opts; unsigned i; if (strstr(name, "tpgt_") != name) return ERR_PTR(-EINVAL); - if (kstrtoul(name + 5, 0, &tpgt) || tpgt > UINT_MAX) + if (kstrtou16(name + 5, 0, &tpgt)) return ERR_PTR(-EINVAL); ret = -ENODEV; mutex_lock(&tpg_instances_lock); -- cgit v1.2.3-59-g8ed1b From 8176dd6e1cf4adff6422a0e3a335abef3f0256f3 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 15 Apr 2025 13:45:59 +0300 Subject: ALSA: usb-audio: qcom: delete a stray tab This code is indented one extra tab. Delete the tab. Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/Z_4458uUI3LURa8M@stanley.mountain Signed-off-by: Greg Kroah-Hartman --- sound/usb/qcom/qc_audio_offload.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c index 5874eb5ba827..8b096f37ad4c 100644 --- a/sound/usb/qcom/qc_audio_offload.c +++ b/sound/usb/qcom/qc_audio_offload.c @@ -379,7 +379,7 @@ static int uaudio_send_disconnect_ind(struct snd_usb_audio *chip) } else if (ret < 0) { dev_err(uaudio_qdev->data->dev, "failed with ret %d\n", ret); - atomic_set(&dev->in_use, 0); + atomic_set(&dev->in_use, 0); } mutex_lock(&qdev_mutex); mutex_lock(&chip->mutex); -- cgit v1.2.3-59-g8ed1b From ba6474f19fd1b91c172f6877b1af094e3720321e Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 15 Apr 2025 13:45:44 +0300 Subject: ASoC: qcom: qdsp6: Set error code in q6usb_hw_params() Propagate the error code if q6afe_port_get_from_id() fails. Don't return success. Fixes: 72b0b8b29980 ("ASoC: qcom: qdsp6: Add USB backend ASoC driver for Q6") Signed-off-by: Dan Carpenter Reviewed-by: Dmitry Baryshkov Acked-by: Mark Brown Link: https://lore.kernel.org/r/Z_442PWaMVoZcbbU@stanley.mountain Signed-off-by: Greg Kroah-Hartman --- sound/soc/qcom/qdsp6/q6usb.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/qcom/qdsp6/q6usb.c b/sound/soc/qcom/qdsp6/q6usb.c index 274c251e84dd..adba0446f301 100644 --- a/sound/soc/qcom/qdsp6/q6usb.c +++ b/sound/soc/qcom/qdsp6/q6usb.c @@ -74,8 +74,10 @@ static int q6usb_hw_params(struct snd_pcm_substream *substream, goto out; q6usb_afe = q6afe_port_get_from_id(cpu_dai->dev, USB_RX); - if (IS_ERR(q6usb_afe)) + if (IS_ERR(q6usb_afe)) { + ret = PTR_ERR(q6usb_afe); goto out; + } /* Notify audio DSP about the devices being offloaded */ ret = afe_port_send_usb_dev_param(q6usb_afe, sdev->card_idx, -- cgit v1.2.3-59-g8ed1b From f41f7b3d9daff26e34ee820af5a44d6c66e23412 Mon Sep 17 00:00:00 2001 From: Zhang Lixu Date: Fri, 21 Feb 2025 16:37:12 +0800 Subject: MAINTAINERS: Update Intel LJCA maintainer Wentong is no longer with Intel, I will take over as the maintainer of the Intel LJCA driver. Signed-off-by: Zhang Lixu Reviewed-by: Stanislaw Gruszka Acked-by: Sakari Ailus Acked-by: Wentong Wu Link: https://lore.kernel.org/r/20250221083713.25947-1-lixu.zhang@intel.com Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 03da81994d21..e5edbacf3ea2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12076,7 +12076,7 @@ F: drivers/crypto/intel/keembay/ocs-hcu.c F: drivers/crypto/intel/keembay/ocs-hcu.h INTEL LA JOLLA COVE ADAPTER (LJCA) USB I/O EXPANDER DRIVERS -M: Wentong Wu +M: Lixu Zhang M: Sakari Ailus S: Maintained F: drivers/gpio/gpio-ljca.c -- cgit v1.2.3-59-g8ed1b From 1a760d10ded372d113a0410c42be246315bbc2ff Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 11 Apr 2025 10:14:44 -0500 Subject: thunderbolt: Fix a logic error in wake on connect commit a5cfc9d65879c ("thunderbolt: Add wake on connect/disconnect on USB4 ports") introduced a sysfs file to control wake up policy for a given USB4 port that defaulted to disabled. However when testing commit 4bfeea6ec1c02 ("thunderbolt: Use wake on connect and disconnect over suspend") I found that it was working even without making changes to the power/wakeup file (which defaults to disabled). This is because of a logic error doing a bitwise or of the wake-on-connect flag with device_may_wakeup() which should have been a logical AND. Adjust the logic so that policy is only applied when wakeup is actually enabled. Fixes: a5cfc9d65879c ("thunderbolt: Add wake on connect/disconnect on USB4 ports") Signed-off-by: Mario Limonciello Signed-off-by: Mika Westerberg --- drivers/thunderbolt/usb4.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index e51d01671d8e..3e96f1afd426 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -440,10 +440,10 @@ int usb4_switch_set_wake(struct tb_switch *sw, unsigned int flags) bool configured = val & PORT_CS_19_PC; usb4 = port->usb4; - if (((flags & TB_WAKE_ON_CONNECT) | + if (((flags & TB_WAKE_ON_CONNECT) && device_may_wakeup(&usb4->dev)) && !configured) val |= PORT_CS_19_WOC; - if (((flags & TB_WAKE_ON_DISCONNECT) | + if (((flags & TB_WAKE_ON_DISCONNECT) && device_may_wakeup(&usb4->dev)) && configured) val |= PORT_CS_19_WOD; if ((flags & TB_WAKE_ON_USB4) && configured) -- cgit v1.2.3-59-g8ed1b From f93b5e24640cdb375d9dc282a244b488ce0d5a07 Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Mon, 14 Apr 2025 19:55:52 +0200 Subject: thunderbolt: Expose usb4_port_index() to other modules Make usb4_port_index() available to other files in the driver, rename and add function documentation. Signed-off-by: Alan Borzeszkowski Reviewed-by: Heikki Krogerus Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.h | 1 + drivers/thunderbolt/usb4.c | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index b54147a1ba87..4e2faa0d5dba 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -1468,6 +1468,7 @@ static inline struct usb4_port *tb_to_usb4_port_device(struct device *dev) struct usb4_port *usb4_port_device_add(struct tb_port *port); void usb4_port_device_remove(struct usb4_port *usb4); int usb4_port_device_resume(struct usb4_port *usb4); +int usb4_port_index(const struct tb_switch *sw, const struct tb_port *port); static inline bool usb4_port_device_is_offline(const struct usb4_port *usb4) { diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 3e96f1afd426..fce3c0f2354a 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -935,7 +935,15 @@ int usb4_switch_dealloc_dp_resource(struct tb_switch *sw, struct tb_port *in) return status ? -EIO : 0; } -static int usb4_port_idx(const struct tb_switch *sw, const struct tb_port *port) +/** + * usb4_port_index() - Finds matching USB4 port index + * @sw: USB4 router + * @port: USB4 protocol or lane adapter + * + * Finds matching USB4 port index (starting from %0) that given @port goes + * through. + */ +int usb4_port_index(const struct tb_switch *sw, const struct tb_port *port) { struct tb_port *p; int usb4_idx = 0; @@ -969,7 +977,7 @@ static int usb4_port_idx(const struct tb_switch *sw, const struct tb_port *port) struct tb_port *usb4_switch_map_pcie_down(struct tb_switch *sw, const struct tb_port *port) { - int usb4_idx = usb4_port_idx(sw, port); + int usb4_idx = usb4_port_index(sw, port); struct tb_port *p; int pcie_idx = 0; @@ -1000,7 +1008,7 @@ struct tb_port *usb4_switch_map_pcie_down(struct tb_switch *sw, struct tb_port *usb4_switch_map_usb3_down(struct tb_switch *sw, const struct tb_port *port) { - int usb4_idx = usb4_port_idx(sw, port); + int usb4_idx = usb4_port_index(sw, port); struct tb_port *p; int usb_idx = 0; -- cgit v1.2.3-59-g8ed1b From e80c235994fd1d7004a4e5d64123f8f07ec80ade Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Mon, 14 Apr 2025 19:55:53 +0200 Subject: thunderbolt: Add Thunderbolt/USB4 <-> USB3 match function This function checks whether given USB4 port device matches with USB3.x port device, using ACPI _DSD property. It is designed to be used by component framework to match USB4 ports with Type-C ports they are connected to. Also, added USB4 config stub in case mapping function is not reachable. Signed-off-by: Alan Borzeszkowski Reviewed-by: Heikki Krogerus Signed-off-by: Mika Westerberg --- drivers/thunderbolt/usb4_port.c | 56 +++++++++++++++++++++++++++++++++++------ include/linux/thunderbolt.h | 18 +++++++++++++ 2 files changed, 66 insertions(+), 8 deletions(-) diff --git a/drivers/thunderbolt/usb4_port.c b/drivers/thunderbolt/usb4_port.c index 5150879888ca..852a45fcd19d 100644 --- a/drivers/thunderbolt/usb4_port.c +++ b/drivers/thunderbolt/usb4_port.c @@ -105,6 +105,49 @@ static void usb4_port_online(struct usb4_port *usb4) tb_acpi_power_off_retimers(port); } +/** + * usb4_usb3_port_match() - Matches USB4 port device with USB 3.x port device + * @usb4_port_dev: USB4 port device + * @usb3_port_fwnode: USB 3.x port firmware node + * + * Checks if USB 3.x port @usb3_port_fwnode is tunneled through USB4 port @usb4_port_dev. + * Returns true if match is found, false otherwise. + * + * Function is designed to be used with component framework (component_match_add). + */ +bool usb4_usb3_port_match(struct device *usb4_port_dev, + const struct fwnode_handle *usb3_port_fwnode) +{ + struct fwnode_handle *nhi_fwnode __free(fwnode_handle) = NULL; + struct usb4_port *usb4; + struct tb_switch *sw; + struct tb_nhi *nhi; + u8 usb4_port_num; + struct tb *tb; + + usb4 = tb_to_usb4_port_device(usb4_port_dev); + if (!usb4) + return false; + + sw = usb4->port->sw; + tb = sw->tb; + nhi = tb->nhi; + + nhi_fwnode = fwnode_find_reference(usb3_port_fwnode, "usb4-host-interface", 0); + if (IS_ERR(nhi_fwnode)) + return false; + + /* Check if USB3 fwnode references same NHI where USB4 port resides */ + if (!device_match_fwnode(&nhi->pdev->dev, nhi_fwnode)) + return false; + + if (fwnode_property_read_u8(usb3_port_fwnode, "usb4-port-number", &usb4_port_num)) + return false; + + return usb4_port_index(sw, usb4->port) == usb4_port_num; +} +EXPORT_SYMBOL_GPL(usb4_usb3_port_match); + static ssize_t offline_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -276,12 +319,10 @@ struct usb4_port *usb4_port_device_add(struct tb_port *port) return ERR_PTR(ret); } - if (dev_fwnode(&usb4->dev)) { - ret = component_add(&usb4->dev, &connector_ops); - if (ret) { - dev_err(&usb4->dev, "failed to add component\n"); - device_unregister(&usb4->dev); - } + ret = component_add(&usb4->dev, &connector_ops); + if (ret) { + dev_err(&usb4->dev, "failed to add component\n"); + device_unregister(&usb4->dev); } if (!tb_is_upstream_port(port)) @@ -306,8 +347,7 @@ struct usb4_port *usb4_port_device_add(struct tb_port *port) */ void usb4_port_device_remove(struct usb4_port *usb4) { - if (dev_fwnode(&usb4->dev)) - component_del(&usb4->dev, &connector_ops); + component_del(&usb4->dev, &connector_ops); device_unregister(&usb4->dev); } diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index 7d902d8c054b..75247486616b 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -11,6 +11,13 @@ #ifndef THUNDERBOLT_H_ #define THUNDERBOLT_H_ +#include + +struct fwnode_handle; +struct device; + +#if IS_REACHABLE(CONFIG_USB4) + #include #include #include @@ -674,4 +681,15 @@ static inline struct device *tb_ring_dma_device(struct tb_ring *ring) return &ring->nhi->pdev->dev; } +bool usb4_usb3_port_match(struct device *usb4_port_dev, + const struct fwnode_handle *usb3_port_fwnode); + +#else /* CONFIG_USB4 */ +static inline bool usb4_usb3_port_match(struct device *usb4_port_dev, + const struct fwnode_handle *usb3_port_fwnode) +{ + return false; +} +#endif /* CONFIG_USB4 */ + #endif /* THUNDERBOLT_H_ */ -- cgit v1.2.3-59-g8ed1b From 4fd7a1f0f7f281dcbdf2e42a2e30b6d2159deaf4 Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Mon, 14 Apr 2025 19:55:54 +0200 Subject: usb: typec: Connect Type-C port with associated USB4 port If USB3.x device references USB4 host interface, USB4 port can be connected with appropriate Type-C port. By using component framework, and in turn by creating symlinks, userspace can benefit from having Thunderbolt/USB4 connection to Type-C ports. Note: This change introduces dependency on Thunderbolt driver as it's required to properly map USB4 port to Type-C port. Signed-off-by: Alan Borzeszkowski Reviewed-by: Heikki Krogerus Signed-off-by: Mika Westerberg --- drivers/usb/typec/port-mapper.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/port-mapper.c b/drivers/usb/typec/port-mapper.c index d42da5720a25..cdbb7c11d714 100644 --- a/drivers/usb/typec/port-mapper.c +++ b/drivers/usb/typec/port-mapper.c @@ -8,6 +8,7 @@ #include #include +#include #include #include "class.h" @@ -36,6 +37,11 @@ struct each_port_arg { struct component_match *match; }; +static int usb4_port_compare(struct device *dev, void *fwnode) +{ + return usb4_usb3_port_match(dev, fwnode); +} + static int typec_port_compare(struct device *dev, void *fwnode) { return device_match_fwnode(dev, fwnode); @@ -51,9 +57,22 @@ static int typec_port_match(struct device *dev, void *data) if (con_adev == adev) return 0; - if (con_adev->pld_crc == adev->pld_crc) + if (con_adev->pld_crc == adev->pld_crc) { + struct fwnode_handle *adev_fwnode = acpi_fwnode_handle(adev); + component_match_add(&arg->port->dev, &arg->match, typec_port_compare, - acpi_fwnode_handle(adev)); + adev_fwnode); + + /* + * If dev is USB 3.x port, it may have reference to the + * USB4 host interface in which case we can also link the + * Type-C port with the USB4 port. + */ + if (fwnode_property_present(adev_fwnode, "usb4-host-interface")) + component_match_add(&arg->port->dev, &arg->match, + usb4_port_compare, adev_fwnode); + } + return 0; } -- cgit v1.2.3-59-g8ed1b From cdf9956b6974206e1fd20e9bd30842df9714c5b8 Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Tue, 21 Jan 2025 18:46:20 +0100 Subject: thunderbolt: Introduce domain event message handler This patch introduces a function that can be used to send uevent notifications in the domain to userspace. For instance, it can indicate that a DisplayPort tunnel could not be established due to insufficient bandwidth. Userspace can then forward to user via dialog or similar. Convert boot_acl_store() to call this instead of open-coding. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/domain.c | 2 +- drivers/thunderbolt/tb.h | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 144d0232a70c..a3a7c8059eee 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -217,7 +217,7 @@ static ssize_t boot_acl_store(struct device *dev, struct device_attribute *attr, ret = tb->cm_ops->set_boot_acl(tb, acl, tb->nboot_acl); if (!ret) { /* Notify userspace about the change */ - kobject_uevent(&tb->dev.kobj, KOBJ_CHANGE); + tb_domain_event(tb, NULL); } mutex_unlock(&tb->lock); diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 4e2faa0d5dba..87afd5a7c504 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -804,6 +804,19 @@ static inline void tb_domain_put(struct tb *tb) put_device(&tb->dev); } +/** + * tb_domain_event() - Notify userspace about an event in domain + * @tb: Domain where event occurred + * @envp: Array of uevent environment strings (can be %NULL) + * + * This function provides a way to notify userspace about any events + * that take place in the domain. + */ +static inline void tb_domain_event(struct tb *tb, char *envp[]) +{ + kobject_uevent_env(&tb->dev.kobj, KOBJ_CHANGE, envp); +} + struct tb_nvm *tb_nvm_alloc(struct device *dev); int tb_nvm_read_version(struct tb_nvm *nvm); int tb_nvm_validate(struct tb_nvm *nvm); -- cgit v1.2.3-59-g8ed1b From 785da9e6a1bd9e00d7494e37cbca2f66e48375b8 Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Tue, 21 Jan 2025 18:46:21 +0100 Subject: thunderbolt: Notify userspace about software CM tunneling events This adds notification whenever software connection manager activates, changes or deactivates a tunnel, and also if there is limitation in bandwidth. The notification looks like below: TUNNEL_EVENT=activated|changed|deactivated|low bandwidth| insufficient bandwidth TUNNEL_DETAILS=0:12 <-> 1:20 (USB3) Userspace can then listen these and inform user if needed. For example if there is not enough bandwidth, it can show warning dialog to the user. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.c | 22 +++++++++-- drivers/thunderbolt/tunnel.c | 92 +++++++++++++++++++++++++++++++++++++++++--- drivers/thunderbolt/tunnel.h | 23 +++++++++++ 3 files changed, 129 insertions(+), 8 deletions(-) diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 8c527af98927..c14ab1fbeeaf 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -952,6 +952,15 @@ static int tb_tunnel_usb3(struct tb *tb, struct tb_switch *sw) tb_port_dbg(up, "available bandwidth for new USB3 tunnel %d/%d Mb/s\n", available_up, available_down); + /* + * If the available bandwidth is less than 1.5 Gb/s notify + * userspace that the connected isochronous device may not work + * properly. + */ + if (available_up < 1500 || available_down < 1500) + tb_tunnel_event(tb, TB_TUNNEL_LOW_BANDWIDTH, TB_TUNNEL_USB3, + down, up); + tunnel = tb_tunnel_alloc_usb3(tb, up, down, available_up, available_down); if (!tunnel) { @@ -2000,8 +2009,10 @@ static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, ret = tb_available_bandwidth(tb, in, out, &available_up, &available_down, true); - if (ret) + if (ret) { + tb_tunnel_event(tb, TB_TUNNEL_NO_BANDWIDTH, TB_TUNNEL_DP, in, out); goto err_reclaim_usb; + } tb_dbg(tb, "available bandwidth for new DP tunnel %u/%u Mb/s\n", available_up, available_down); @@ -2622,8 +2633,12 @@ static int tb_alloc_dp_bandwidth(struct tb_tunnel *tunnel, int *requested_up, } } - return tb_tunnel_alloc_bandwidth(tunnel, requested_up, - requested_down); + ret = tb_tunnel_alloc_bandwidth(tunnel, requested_up, + requested_down); + if (ret) + goto fail; + + return 0; } /* @@ -2699,6 +2714,7 @@ fail: "failing the request by rewriting allocated %d/%d Mb/s\n", allocated_up, allocated_down); tb_tunnel_alloc_bandwidth(tunnel, &allocated_up, &allocated_down); + tb_tunnel_event(tb, TB_TUNNEL_NO_BANDWIDTH, TB_TUNNEL_DP, in, out); } return ret; diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 76254ed3f47f..d52efe3f658c 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -100,6 +100,14 @@ MODULE_PARM_DESC(bw_alloc_mode, static const char * const tb_tunnel_names[] = { "PCI", "DP", "DMA", "USB3" }; +static const char * const tb_event_names[] = { + [TB_TUNNEL_ACTIVATED] = "activated", + [TB_TUNNEL_CHANGED] = "changed", + [TB_TUNNEL_DEACTIVATED] = "deactivated", + [TB_TUNNEL_LOW_BANDWIDTH] = "low bandwidth", + [TB_TUNNEL_NO_BANDWIDTH] = "insufficient bandwidth", +}; + /* Synchronizes kref_get()/put() of struct tb_tunnel */ static DEFINE_MUTEX(tb_tunnel_lock); @@ -220,6 +228,72 @@ void tb_tunnel_put(struct tb_tunnel *tunnel) mutex_unlock(&tb_tunnel_lock); } +/** + * tb_tunnel_event() - Notify userspace about tunneling event + * @tb: Domain where the event occurred + * @event: Event that happened + * @type: Type of the tunnel in question + * @src_port: Tunnel source port (can be %NULL) + * @dst_port: Tunnel destination port (can be %NULL) + * + * Notifies userspace about tunneling @event in the domain. The tunnel + * does not need to exist (e.g the tunnel was not activated because + * there is not enough bandwidth). If the @src_port and @dst_port are + * given fill in full %TUNNEL_DETAILS environment variable. Otherwise + * uses the shorter one (just the tunnel type). + */ +void tb_tunnel_event(struct tb *tb, enum tb_tunnel_event event, + enum tb_tunnel_type type, + const struct tb_port *src_port, + const struct tb_port *dst_port) +{ + char *envp[3] = { NULL }; + + if (WARN_ON_ONCE(event >= ARRAY_SIZE(tb_event_names))) + return; + if (WARN_ON_ONCE(type >= ARRAY_SIZE(tb_tunnel_names))) + return; + + envp[0] = kasprintf(GFP_KERNEL, "TUNNEL_EVENT=%s", tb_event_names[event]); + if (!envp[0]) + return; + + if (src_port != NULL && dst_port != NULL) { + envp[1] = kasprintf(GFP_KERNEL, "TUNNEL_DETAILS=%llx:%u <-> %llx:%u (%s)", + tb_route(src_port->sw), src_port->port, + tb_route(dst_port->sw), dst_port->port, + tb_tunnel_names[type]); + } else { + envp[1] = kasprintf(GFP_KERNEL, "TUNNEL_DETAILS=(%s)", + tb_tunnel_names[type]); + } + + if (envp[1]) + tb_domain_event(tb, envp); + + kfree(envp[1]); + kfree(envp[0]); +} + +static inline void tb_tunnel_set_active(struct tb_tunnel *tunnel, bool active) +{ + if (active) { + tunnel->state = TB_TUNNEL_ACTIVE; + tb_tunnel_event(tunnel->tb, TB_TUNNEL_ACTIVATED, tunnel->type, + tunnel->src_port, tunnel->dst_port); + } else { + tunnel->state = TB_TUNNEL_INACTIVE; + tb_tunnel_event(tunnel->tb, TB_TUNNEL_DEACTIVATED, tunnel->type, + tunnel->src_port, tunnel->dst_port); + } +} + +static inline void tb_tunnel_changed(struct tb_tunnel *tunnel) +{ + tb_tunnel_event(tunnel->tb, TB_TUNNEL_CHANGED, tunnel->type, + tunnel->src_port, tunnel->dst_port); +} + static int tb_pci_set_ext_encapsulation(struct tb_tunnel *tunnel, bool enable) { struct tb_port *port = tb_upstream_port(tunnel->dst_port->sw); @@ -992,7 +1066,7 @@ static void tb_dp_dprx_work(struct work_struct *work) return; } } else { - tunnel->state = TB_TUNNEL_ACTIVE; + tb_tunnel_set_active(tunnel, true); } mutex_unlock(&tb->lock); } @@ -2326,7 +2400,7 @@ int tb_tunnel_activate(struct tb_tunnel *tunnel) } } - tunnel->state = TB_TUNNEL_ACTIVE; + tb_tunnel_set_active(tunnel, true); return 0; err: @@ -2356,7 +2430,7 @@ void tb_tunnel_deactivate(struct tb_tunnel *tunnel) if (tunnel->post_deactivate) tunnel->post_deactivate(tunnel); - tunnel->state = TB_TUNNEL_INACTIVE; + tb_tunnel_set_active(tunnel, false); } /** @@ -2449,8 +2523,16 @@ int tb_tunnel_alloc_bandwidth(struct tb_tunnel *tunnel, int *alloc_up, if (!tb_tunnel_is_active(tunnel)) return -ENOTCONN; - if (tunnel->alloc_bandwidth) - return tunnel->alloc_bandwidth(tunnel, alloc_up, alloc_down); + if (tunnel->alloc_bandwidth) { + int ret; + + ret = tunnel->alloc_bandwidth(tunnel, alloc_up, alloc_down); + if (ret) + return ret; + + tb_tunnel_changed(tunnel); + return 0; + } return -EOPNOTSUPP; } diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h index 8a0a0cb21a89..5e9fb73d5220 100644 --- a/drivers/thunderbolt/tunnel.h +++ b/drivers/thunderbolt/tunnel.h @@ -194,6 +194,29 @@ static inline bool tb_tunnel_direction_downstream(const struct tb_tunnel *tunnel tunnel->dst_port); } +/** + * enum tb_tunnel_event - Tunnel related events + * @TB_TUNNEL_ACTIVATED: A tunnel was activated + * @TB_TUNNEL_CHANGED: There is a tunneling change in the domain. Includes + * full %TUNNEL_DETAILS if the tunnel in question is known + * (ICM does not provide that information). + * @TB_TUNNEL_DEACTIVATED: A tunnel was torn down + * @TB_TUNNEL_LOW_BANDWIDTH: Tunnel bandwidth is not optimal + * @TB_TUNNEL_NO_BANDWIDTH: There is not enough bandwidth for a tunnel + */ +enum tb_tunnel_event { + TB_TUNNEL_ACTIVATED, + TB_TUNNEL_CHANGED, + TB_TUNNEL_DEACTIVATED, + TB_TUNNEL_LOW_BANDWIDTH, + TB_TUNNEL_NO_BANDWIDTH, +}; + +void tb_tunnel_event(struct tb *tb, enum tb_tunnel_event event, + enum tb_tunnel_type type, + const struct tb_port *src_port, + const struct tb_port *dst_port); + const char *tb_tunnel_type_name(const struct tb_tunnel *tunnel); #define __TB_TUNNEL_PRINT(level, tunnel, fmt, arg...) \ -- cgit v1.2.3-59-g8ed1b From 607063f08e5c5aca9a40015843952389126b6be1 Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Mon, 20 Jan 2025 19:19:22 +0100 Subject: thunderbolt: Notify userspace about firmware CM tunneling events In the same way we do for software connection manager, send notifications about tunneling changes done by the firmware connection manager as well. There are some limitations with this though, for example we only get "DP Configuration Changed" message from the firmware without any indication if DisplayPort tunnel was activated or deactivated. Also we don't get information about the tunnel itself either so the event then looks like: TUNNEL_EVENT=changed TUNNEL_DETAILS=(DP) XDomain connections are similar to what the software connection manager sends. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/icm.c | 36 +++++++++++++++++++++++++++++++++++- drivers/thunderbolt/tb_msgs.h | 1 + 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index 7859bccc592d..f213d9174dc5 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -22,6 +22,7 @@ #include "ctl.h" #include "nhi_regs.h" #include "tb.h" +#include "tunnel.h" #define PCIE2CIO_CMD 0x30 #define PCIE2CIO_CMD_TIMEOUT BIT(31) @@ -379,6 +380,27 @@ static bool icm_firmware_running(const struct tb_nhi *nhi) return !!(val & REG_FW_STS_ICM_EN); } +static void icm_xdomain_activated(struct tb_xdomain *xd, bool activated) +{ + struct tb_port *nhi_port, *dst_port; + struct tb *tb = xd->tb; + + nhi_port = tb_switch_find_port(tb->root_switch, TB_TYPE_NHI); + dst_port = tb_xdomain_downstream_port(xd); + + if (activated) + tb_tunnel_event(tb, TB_TUNNEL_ACTIVATED, TB_TUNNEL_DMA, + nhi_port, dst_port); + else + tb_tunnel_event(tb, TB_TUNNEL_DEACTIVATED, TB_TUNNEL_DMA, + nhi_port, dst_port); +} + +static void icm_dp_event(struct tb *tb) +{ + tb_tunnel_event(tb, TB_TUNNEL_CHANGED, TB_TUNNEL_DP, NULL, NULL); +} + static bool icm_fr_is_supported(struct tb *tb) { return !x86_apple_machine; @@ -584,6 +606,7 @@ static int icm_fr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, if (reply.hdr.flags & ICM_FLAGS_ERROR) return -EIO; + icm_xdomain_activated(xd, true); return 0; } @@ -603,6 +626,8 @@ static int icm_fr_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, nhi_mailbox_cmd(tb->nhi, cmd, 1); usleep_range(10, 50); nhi_mailbox_cmd(tb->nhi, cmd, 2); + + icm_xdomain_activated(xd, false); return 0; } @@ -1151,6 +1176,7 @@ static int icm_tr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, if (reply.hdr.flags & ICM_FLAGS_ERROR) return -EIO; + icm_xdomain_activated(xd, true); return 0; } @@ -1191,7 +1217,12 @@ static int icm_tr_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, return ret; usleep_range(10, 50); - return icm_tr_xdomain_tear_down(tb, xd, 2); + ret = icm_tr_xdomain_tear_down(tb, xd, 2); + if (ret) + return ret; + + icm_xdomain_activated(xd, false); + return 0; } static void @@ -1718,6 +1749,9 @@ static void icm_handle_notification(struct work_struct *work) if (tb_is_xdomain_enabled()) icm->xdomain_disconnected(tb, n->pkg); break; + case ICM_EVENT_DP_CONFIG_CHANGED: + icm_dp_event(tb); + break; case ICM_EVENT_RTD3_VETO: icm->rtd3_veto(tb, n->pkg); break; diff --git a/drivers/thunderbolt/tb_msgs.h b/drivers/thunderbolt/tb_msgs.h index a1670a96cbdc..144f7332d5d2 100644 --- a/drivers/thunderbolt/tb_msgs.h +++ b/drivers/thunderbolt/tb_msgs.h @@ -118,6 +118,7 @@ enum icm_event_code { ICM_EVENT_DEVICE_DISCONNECTED = 0x4, ICM_EVENT_XDOMAIN_CONNECTED = 0x6, ICM_EVENT_XDOMAIN_DISCONNECTED = 0x7, + ICM_EVENT_DP_CONFIG_CHANGED = 0x8, ICM_EVENT_RTD3_VETO = 0xa, }; -- cgit v1.2.3-59-g8ed1b From 36f6f7e2d4d094c828977938eaa4949ec5439380 Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Thu, 10 Apr 2025 16:34:19 +0300 Subject: Documentation/admin-guide: Document Thunderbolt/USB4 tunneling events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add documentation about the Thunderbolt/USB4 tunneling events to the user’s and administrator’s guide. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- Documentation/admin-guide/thunderbolt.rst | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Documentation/admin-guide/thunderbolt.rst b/Documentation/admin-guide/thunderbolt.rst index d0502691dfa1..240fee618e06 100644 --- a/Documentation/admin-guide/thunderbolt.rst +++ b/Documentation/admin-guide/thunderbolt.rst @@ -296,6 +296,39 @@ information is missing. To recover from this mode, one needs to flash a valid NVM image to the host controller in the same way it is done in the previous chapter. +Tunneling events +---------------- +The driver sends ``KOBJ_CHANGE`` events to userspace when there is a +tunneling change in the ``thunderbolt_domain``. The notification carries +following environment variables:: + + TUNNEL_EVENT= + TUNNEL_DETAILS=0:12 <-> 1:20 (USB3) + +Possible values for ```` are: + + activated + The tunnel was activated (created). + + changed + There is a change in this tunnel. For example bandwidth allocation was + changed. + + deactivated + The tunnel was torn down. + + low bandwidth + The tunnel is not getting optimal bandwidth. + + insufficient bandwidth + There is not enough bandwidth for the current tunnel requirements. + +The ``TUNNEL_DETAILS`` is only provided if the tunnel is known. For +example, in case of Firmware Connection Manager this is missing or does +not provide full tunnel information. In case of Software Connection Manager +this includes full tunnel details. The format currently matches what the +driver uses when logging. This may change over time. + Networking over Thunderbolt cable --------------------------------- Thunderbolt technology allows software communication between two hosts -- cgit v1.2.3-59-g8ed1b From 89ecf105143b39c22f49c18da451e88578e08c03 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Fri, 4 Apr 2025 01:02:20 +0200 Subject: usb: typec: mux: fsa4480: add regulator support The fsa4480 vcc lane could be driven by some external regulator. This patch is adding support to enable the regulator before probing. Signed-off-by: Michael Grzeschik Reviewed-by: Heikki Krogerus Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250404-ml-topic-typec-mux-fs4480-v1-1-475377ef22a3@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux/fsa4480.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/usb/typec/mux/fsa4480.c b/drivers/usb/typec/mux/fsa4480.c index f71dba8bf07c..c54e42c7e6a1 100644 --- a/drivers/usb/typec/mux/fsa4480.c +++ b/drivers/usb/typec/mux/fsa4480.c @@ -12,6 +12,7 @@ #include #include #include +#include #define FSA4480_DEVICE_ID 0x00 #define FSA4480_DEVICE_ID_VENDOR_ID GENMASK(7, 6) @@ -273,6 +274,10 @@ static int fsa4480_probe(struct i2c_client *client) if (IS_ERR(fsa->regmap)) return dev_err_probe(dev, PTR_ERR(fsa->regmap), "failed to initialize regmap\n"); + ret = devm_regulator_get_enable_optional(dev, "vcc"); + if (ret && ret != -ENODEV) + return dev_err_probe(dev, ret, "Failed to get regulator\n"); + ret = regmap_read(fsa->regmap, FSA4480_DEVICE_ID, &val); if (ret) return dev_err_probe(dev, -ENODEV, "FSA4480 not found\n"); -- cgit v1.2.3-59-g8ed1b From c0a1d1e9a48f36086ef5a2863844175476bd3b8e Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 14 Apr 2025 17:52:01 +0100 Subject: dt-bindings: usb: renesas,usbhs: Add RZ/V2H(P) SoC support Document the Renesas USBHS controller found on the Renesas RZ/V2H(P) SoC. The USBHS block on RZ/V2H(P) is functionally identical to the one on the RZ/G2L family, so no driver changes are needed. The existing "renesas,rzg2l-usbhs" fallback compatible will continue to be used for handling this IP. In addition, update the schema validation logic by replacing the enum list of SoC-specific compatibles with a const "renesas,rzg2l-usbhs" as all listed SoCs share identical USBHS hardware and already include the fallback compatible. This will help to simplify the schema and avoid redundancy. Signed-off-by: Lad Prabhakar Acked-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20250414165201.362262-1-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/renesas,usbhs.yaml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml b/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml index 980f325341d4..6f4d41ba6ca7 100644 --- a/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml +++ b/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml @@ -27,6 +27,7 @@ properties: - renesas,usbhs-r9a07g044 # RZ/G2{L,LC} - renesas,usbhs-r9a07g054 # RZ/V2L - renesas,usbhs-r9a08g045 # RZ/G3S + - renesas,usbhs-r9a09g057 # RZ/V2H(P) - const: renesas,rzg2l-usbhs - items: @@ -127,11 +128,7 @@ allOf: properties: compatible: contains: - enum: - - renesas,usbhs-r9a07g043 - - renesas,usbhs-r9a07g044 - - renesas,usbhs-r9a07g054 - - renesas,usbhs-r9a08g045 + const: renesas,rzg2l-usbhs then: properties: interrupts: -- cgit v1.2.3-59-g8ed1b From db476ffab1df60ec18b87ff3b5ebec46f852de37 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 17 Apr 2025 09:46:34 +0200 Subject: USB: host: omap: Do not enable by default during compile testing Enabling the compile test should not cause automatic enabling of all drivers, but only allow to choose to compile them. Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20250417074634.81295-1-krzysztof.kozlowski@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 033a9a4b51fe..109100cc77a3 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -234,7 +234,7 @@ config USB_EHCI_HCD_OMAP tristate "EHCI support for OMAP3 and later chips" depends on ARCH_OMAP || COMPILE_TEST depends on NOP_USB_XCEIV - default y + default ARCH_OMAP help Enables support for the on-chip EHCI controller on OMAP3 and later chips. -- cgit v1.2.3-59-g8ed1b From e6f9fd8e63ddbb2bff56d2082242c948b8bca53f Mon Sep 17 00:00:00 2001 From: Pengyu Luo Date: Wed, 16 Apr 2025 01:20:05 +0800 Subject: usb: typec: ucsi: huawei_gaokun: add error checking 'cci' may be uninitialized, adding error checking to fix it. Reported-by: Dan Carpenter Closes: https://lore.kernel.org/all/Z_44zoTyLLdXNkKT@stanley.mountain Fixes: 00327d7f2c8c ("usb: typec: ucsi: add Huawei Matebook E Go ucsi driver") Signed-off-by: Pengyu Luo Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20250415172006.126740-1-mitltlatltl@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c index 344aa7aeaf02..7b5222081bbb 100644 --- a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c +++ b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c @@ -359,6 +359,7 @@ static int gaokun_ucsi_notify(struct notifier_block *nb, unsigned long action, void *data) { u32 cci; + int ret; struct gaokun_ucsi *uec = container_of(nb, struct gaokun_ucsi, nb); switch (action) { @@ -368,7 +369,10 @@ static int gaokun_ucsi_notify(struct notifier_block *nb, return NOTIFY_OK; case EC_EVENT_UCSI: - gaokun_ucsi_read_cci(uec->ucsi, &cci); + ret = gaokun_ucsi_read_cci(uec->ucsi, &cci); + if (ret) + return NOTIFY_DONE; + ucsi_notify_common(uec->ucsi, cci); if (UCSI_CCI_CONNECTOR(cci)) gaokun_ucsi_handle_no_usb_event(uec, UCSI_CCI_CONNECTOR(cci) - 1); -- cgit v1.2.3-59-g8ed1b From 0bc3e641157c32d3bf23d3e14aefb159842d74ab Mon Sep 17 00:00:00 2001 From: Yue Haibing Date: Thu, 17 Apr 2025 20:28:43 +0800 Subject: usb: typec: ucsi: Fix unmet dependencies for UCSI_HUAWEI_GAOKUN WARNING: unmet direct dependencies detected for DRM_AUX_HPD_BRIDGE Depends on [n]: HAS_IOMEM [=y] && DRM [=n] && DRM_BRIDGE [=n] && OF [=n] Selected by [m]: - UCSI_HUAWEI_GAOKUN [=m] && USB_SUPPORT [=y] && TYPEC [=m] && TYPEC_UCSI [=m] && EC_HUAWEI_GAOKUN [=m] DRM_AUX_HPD_BRIDGE depends on DRM_BRIDGE and OF, only select it with both for UCSI_HUAWEI_GAOKUN. Fixes: 00327d7f2c8c ("usb: typec: ucsi: add Huawei Matebook E Go ucsi driver") Signed-off-by: Yue Haibing Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20250417122843.2667008-1-yuehaibing@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig index e94956d27325..8bf8fefb4f07 100644 --- a/drivers/usb/typec/ucsi/Kconfig +++ b/drivers/usb/typec/ucsi/Kconfig @@ -94,7 +94,7 @@ config UCSI_LENOVO_YOGA_C630 config UCSI_HUAWEI_GAOKUN tristate "UCSI Interface Driver for Huawei Matebook E Go" depends on EC_HUAWEI_GAOKUN - select DRM_AUX_HPD_BRIDGE + select DRM_AUX_HPD_BRIDGE if DRM_BRIDGE && OF help This driver enables UCSI support on the Huawei Matebook E Go tablet, which is a sc8280xp-based 2-in-1 tablet. -- cgit v1.2.3-59-g8ed1b From af076a41f8a28faf9ceb9dd2d88aef2c202ef39a Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Thu, 17 Apr 2025 19:40:17 +0200 Subject: usb: dwc2: also exit clock_gating when stopping udc while suspended It is possible that the gadget will be disabled, while the udc is suspended. When enabling the udc in that case, the clock gating will not be enabled again. Leaving the phy unclocked. Even when the udc is not enabled, connecting this powered but not clocked phy leads to enumeration errors on the host side. To ensure that the clock gating will be in an valid state, we ensure that the clock gating will be enabled before stopping the udc. Signed-off-by: Michael Grzeschik Acked-by: Minas Harutyunyan Link: https://lore.kernel.org/r/20250417-dwc2_clock_gating-v1-1-8ea7c4d53d73@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/gadget.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 300ea4969f0c..f323fb5597b3 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -4604,6 +4604,12 @@ static int dwc2_hsotg_udc_stop(struct usb_gadget *gadget) if (!hsotg) return -ENODEV; + /* Exit clock gating when driver is stopped. */ + if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_NONE && + hsotg->bus_suspended && !hsotg->params.no_clock_gating) { + dwc2_gadget_exit_clock_gating(hsotg, 0); + } + /* all endpoints should be shutdown */ for (ep = 1; ep < hsotg->num_of_eps; ep++) { if (hsotg->eps_in[ep]) -- cgit v1.2.3-59-g8ed1b From 73fb0ec9436ae87bcae067ce35d6cdd72bade86c Mon Sep 17 00:00:00 2001 From: Chenyuan Yang Date: Thu, 17 Apr 2025 14:50:32 -0500 Subject: usb: acpi: Prevent null pointer dereference in usb_acpi_add_usb4_devlink() As demonstrated by the fix for update_port_device_state, commit 12783c0b9e2c ("usb: core: Prevent null pointer dereference in update_port_device_state"), usb_hub_to_struct_hub() can return NULL in certain scenarios, such as during hub driver unbind or teardown race conditions, even if the underlying usb_device structure exists. Plus, all other places that call usb_hub_to_struct_hub() in the same file do check for NULL return values. If usb_hub_to_struct_hub() returns NULL, the subsequent access to hub->ports[udev->portnum - 1] will cause a null pointer dereference. Signed-off-by: Chenyuan Yang Fixes: f1bfb4a6fed6 ("usb: acpi: add device link between tunneled USB3 device and USB4 Host Interface") Acked-by: Alan Stern Link: https://lore.kernel.org/r/20250417195032.1811338-1-chenyuan0y@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/usb-acpi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/core/usb-acpi.c b/drivers/usb/core/usb-acpi.c index 935c0efea0b6..ea1ce8beb0cb 100644 --- a/drivers/usb/core/usb-acpi.c +++ b/drivers/usb/core/usb-acpi.c @@ -165,6 +165,8 @@ static int usb_acpi_add_usb4_devlink(struct usb_device *udev) return 0; hub = usb_hub_to_struct_hub(udev->parent); + if (!hub) + return 0; port_dev = hub->ports[udev->portnum - 1]; struct fwnode_handle *nhi_fwnode __free(fwnode_handle) = -- cgit v1.2.3-59-g8ed1b From 5cf4f055c5ae257cbfe142ffe9b4b3f7cc7d3d74 Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Tue, 22 Apr 2025 15:37:14 +0800 Subject: usb: gadget: udc: renesas_usb3: remove unnecessary NULL check before phy_exit() phy_exit() checks for NULL pointers internally. Remove unneeded NULL check here. Signed-off-by: Chen Ni Link: https://lore.kernel.org/r/20250422073714.1334380-1-nichen@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/renesas_usb3.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/usb/gadget/udc/renesas_usb3.c b/drivers/usb/gadget/udc/renesas_usb3.c index 89b304cf6d03..3e4d56457597 100644 --- a/drivers/usb/gadget/udc/renesas_usb3.c +++ b/drivers/usb/gadget/udc/renesas_usb3.c @@ -2397,8 +2397,7 @@ static int renesas_usb3_stop(struct usb_gadget *gadget) rzv2m_usb3drd_reset(usb3_to_dev(usb3)->parent, false); renesas_usb3_stop_controller(usb3); - if (usb3->phy) - phy_exit(usb3->phy); + phy_exit(usb3->phy); pm_runtime_put(usb3_to_dev(usb3)); @@ -2984,8 +2983,7 @@ static int renesas_usb3_suspend(struct device *dev) return 0; renesas_usb3_stop_controller(usb3); - if (usb3->phy) - phy_exit(usb3->phy); + phy_exit(usb3->phy); pm_runtime_put(dev); return 0; -- cgit v1.2.3-59-g8ed1b From f4239ace2dd8606f6824757f192965a95746da05 Mon Sep 17 00:00:00 2001 From: Qasim Ijaz Date: Tue, 22 Apr 2025 14:47:17 +0100 Subject: usb: typec: ucsi: fix Clang -Wsign-conversion warning debugfs.c emits the following warnings when compiling with the -Wsign-conversion flag with clang 15: drivers/usb/typec/ucsi/debugfs.c:58:27: warning: implicit conversion changes signedness: 'int' to 'u32' (aka 'unsigned int') [-Wsign-conversion] ucsi->debugfs->status = ret; ~ ^~~ drivers/usb/typec/ucsi/debugfs.c:71:25: warning: implicit conversion changes signedness: 'u32' (aka 'unsigned int') to 'int' [-Wsign-conversion] return ucsi->debugfs->status; ~~~~~~ ~~~~~~~~~~~~~~~^~~~~~ During ucsi_cmd() we see: if (ret < 0) { ucsi->debugfs->status = ret; return ret; } But "status" is u32 meaning unsigned wrap-around occurs when assigning a value which is < 0 to it, this obscures the real status. To fix this make the "status" of type int since ret is also of type int. Fixes: df0383ffad64 ("usb: typec: ucsi: Add debugfs for ucsi commands") Cc: stable@vger.kernel.org Signed-off-by: Qasim Ijaz Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20250422134717.66218-1-qasdev00@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h index 72b9d5a42961..42ef106bdcad 100644 --- a/drivers/usb/typec/ucsi/ucsi.h +++ b/drivers/usb/typec/ucsi/ucsi.h @@ -434,7 +434,7 @@ struct ucsi_debugfs_entry { u64 low; u64 high; } response; - u32 status; + int status; struct dentry *dentry; }; -- cgit v1.2.3-59-g8ed1b From 495df45f38c8ba3d74c3180a0a13a0ecbfa717d1 Mon Sep 17 00:00:00 2001 From: Quentin Schulz Date: Fri, 18 Apr 2025 16:08:20 +0200 Subject: dt-bindings: usb: usb-device: relax compatible pattern to a contains The dt-core typically allows multiple compatibles[1] but usb-device currently forces a single compatible. This is an issue when multiple devices with slightly different productID all behave the same. This would require the driver to keep updating its compatible matching table to include this new productID instead of doing what is usually done: have two compatibles, the leftmost which matches exactly the HW device definition, and the rightmost one as a fallback which is assumed to be 100% compatible with the device at hand. If this assumption turns out to be wrong, it is easy to work around this without having to modify the device tree by handling the leftmost compatible in the driver. [1] https://github.com/devicetree-org/dt-schema/blob/main/dtschema/schemas/dt-core.yaml#L21-L25 Signed-off-by: Quentin Schulz Reviewed-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20250418-dt-binding-usb-device-compatibles-v2-1-b3029f14e800@cherry.de Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usb-device.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/usb-device.yaml b/Documentation/devicetree/bindings/usb/usb-device.yaml index c67695681033..09fceb469f10 100644 --- a/Documentation/devicetree/bindings/usb/usb-device.yaml +++ b/Documentation/devicetree/bindings/usb/usb-device.yaml @@ -28,7 +28,8 @@ description: | properties: compatible: - pattern: "^usb[0-9a-f]{1,4},[0-9a-f]{1,4}$" + contains: + pattern: "^usb[0-9a-f]{1,4},[0-9a-f]{1,4}$" description: Device nodes or combined nodes. "usbVID,PID", where VID is the vendor id and PID the product id. The textual representation of VID and PID shall be in lower case -- cgit v1.2.3-59-g8ed1b From 45ebb7baf2356c3d9118c40216ad5b3d0d516b15 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 23 Apr 2025 18:26:09 +0200 Subject: usb: dwc3: qcom: use modern PM macros The use of the old SET_SYSTEM_SLEEP_PM_OPS/SET_RUNTIME_PM_OPS macros without __maybe_unused annotations causes warnings when build testing without CONFIG_PM: drivers/usb/dwc3/dwc3-qcom.c:421:12: error: unused function 'dwc3_qcom_suspend' [-Werror,-Wunused-function] 421 | static int dwc3_qcom_suspend(struct dwc3_qcom *qcom, bool wakeup) | ^~~~~~~~~~~~~~~~~ drivers/usb/dwc3/dwc3-qcom.c:457:12: error: unused function 'dwc3_qcom_resume' [-Werror,-Wunused-function] 457 | static int dwc3_qcom_resume(struct dwc3_qcom *qcom, bool wakeup) Change these to the modern SYSTEM_SLEEP_PM_OPS/RUNTIME_PM_OPS/pm_ptr macros, which avoids the warnings and improves readability at the same time. Fixes: 1881a32fe14d ("usb: dwc3: qcom: Transition to flattened model") Signed-off-by: Arnd Bergmann Reviewed-by: Abel Vesa Acked-by: Thinh Nguyen Link: https://lore.kernel.org/r/20250423162613.2082417-1-arnd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-qcom.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index d512002e1e88..cbba11589cd0 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -832,7 +832,6 @@ static void dwc3_qcom_remove(struct platform_device *pdev) reset_control_assert(qcom->resets); } -#ifdef CONFIG_PM_SLEEP static int dwc3_qcom_pm_suspend(struct device *dev) { struct dwc3 *dwc = dev_get_drvdata(dev); @@ -886,12 +885,7 @@ static int dwc3_qcom_prepare(struct device *dev) return dwc3_pm_prepare(dwc); } -#else -#define dwc3_qcom_complete NULL -#define dwc3_qcom_prepare NULL -#endif /* CONFIG_PM_SLEEP */ -#ifdef CONFIG_PM static int dwc3_qcom_runtime_suspend(struct device *dev) { struct dwc3 *dwc = dev_get_drvdata(dev); @@ -922,14 +916,13 @@ static int dwc3_qcom_runtime_idle(struct device *dev) { return dwc3_runtime_idle(dev_get_drvdata(dev)); } -#endif /* CONFIG_PM */ static const struct dev_pm_ops dwc3_qcom_dev_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(dwc3_qcom_pm_suspend, dwc3_qcom_pm_resume) - SET_RUNTIME_PM_OPS(dwc3_qcom_runtime_suspend, dwc3_qcom_runtime_resume, + SYSTEM_SLEEP_PM_OPS(dwc3_qcom_pm_suspend, dwc3_qcom_pm_resume) + RUNTIME_PM_OPS(dwc3_qcom_runtime_suspend, dwc3_qcom_runtime_resume, dwc3_qcom_runtime_idle) - .complete = dwc3_qcom_complete, - .prepare = dwc3_qcom_prepare, + .complete = pm_sleep_ptr(dwc3_qcom_complete), + .prepare = pm_sleep_ptr(dwc3_qcom_prepare), }; static const struct of_device_id dwc3_qcom_of_match[] = { @@ -943,7 +936,7 @@ static struct platform_driver dwc3_qcom_driver = { .remove = dwc3_qcom_remove, .driver = { .name = "dwc3-qcom", - .pm = &dwc3_qcom_dev_pm_ops, + .pm = pm_ptr(&dwc3_qcom_dev_pm_ops), .of_match_table = dwc3_qcom_of_match, }, }; -- cgit v1.2.3-59-g8ed1b From 4c0fca65d10548f68c040ed49bf1860ce0dda9b9 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 23 Apr 2025 11:23:53 +0300 Subject: usb: dwc3: qcom: Fix error handling in probe There are two issues: 1) Return -EINVAL if platform_get_resource() fails. Don't return success. 2) The devm_ioremap() function doesn't return error pointers, it returns NULL. Update the check. Fixes: 1881a32fe14d ("usb: dwc3: qcom: Transition to flattened model") Signed-off-by: Dan Carpenter Reviewed-by: Bjorn Andersson Acked-by: Thinh Nguyen Link: https://lore.kernel.org/r/aAijmfAph0FlTqg6@stanley.mountain Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-qcom.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index cbba11589cd0..20c00ba3bc3d 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -740,15 +740,17 @@ static int dwc3_qcom_probe(struct platform_device *pdev) } r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!r) + if (!r) { + ret = -EINVAL; goto clk_disable; + } res = *r; res.end = res.start + SDM845_QSCRATCH_BASE_OFFSET; qcom->qscratch_base = devm_ioremap(dev, res.end, SDM845_QSCRATCH_SIZE); - if (IS_ERR(qcom->qscratch_base)) { - dev_err(dev, "failed to map qscratch region: %pe\n", qcom->qscratch_base); - ret = PTR_ERR(qcom->qscratch_base); + if (!qcom->qscratch_base) { + dev_err(dev, "failed to map qscratch region\n"); + ret = -ENOMEM; goto clk_disable; } -- cgit v1.2.3-59-g8ed1b From 384455c791740717f457409415d9e9c219436575 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Fri, 25 Apr 2025 14:07:31 +0400 Subject: dt-bindings: usb: generic-ehci: Add VIA/WonderMedia compatible VIA/WonderMedia SoCs use a plain vanilla EHCI controller with a compatible string "via,vt8500-ehci". This compatible is already used by the mainline Linux driver and relevant in-tree DTS files, so add it to the binding. Acked-by: Conor Dooley Signed-off-by: Alexey Charkov Link: https://lore.kernel.org/r/20250425-vt8500-ehci-binding-v2-1-b4a350335add@gmail.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/generic-ehci.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/usb/generic-ehci.yaml b/Documentation/devicetree/bindings/usb/generic-ehci.yaml index 223f2abd5e59..508d958e698c 100644 --- a/Documentation/devicetree/bindings/usb/generic-ehci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-ehci.yaml @@ -86,6 +86,7 @@ properties: - nuvoton,npcm845-ehci - ti,ehci-omap - usb-ehci + - via,vt8500-ehci reg: minItems: 1 -- cgit v1.2.3-59-g8ed1b From 11e80d371bbb02b993caff546a9217eef864232d Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Fri, 25 Apr 2025 09:08:12 +0200 Subject: dt-bindings: usb: usb-switch: Allow data-lanes property in port The ref /schemas/graph.yaml#/properties/port forbids extra properties which might be specified in subschemas, e.g. like in usb/fcs,fsa4480.yaml. Switch to port-base (and specify the endpoint with properties) to allow such properties. Fixes: fd2a052ccd69 ("dt-bindings: usb: add common Type-C USB Switch schema") Signed-off-by: Luca Weiss Link: https://lore.kernel.org/r/20250425-fp5-pmic-glink-dp-v3-1-cc9c2aeb42fb@fairphone.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usb-switch.yaml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/usb-switch.yaml b/Documentation/devicetree/bindings/usb/usb-switch.yaml index da76118e73a5..896201912630 100644 --- a/Documentation/devicetree/bindings/usb/usb-switch.yaml +++ b/Documentation/devicetree/bindings/usb/usb-switch.yaml @@ -26,11 +26,24 @@ properties: type: boolean port: - $ref: /schemas/graph.yaml#/properties/port + $ref: /schemas/graph.yaml#/$defs/port-base description: A port node to link the device to a TypeC controller for the purpose of handling altmode muxing and orientation switching. + properties: + endpoint: + $ref: /schemas/graph.yaml#/$defs/endpoint-base + unevaluatedProperties: false + properties: + data-lanes: + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 8 + uniqueItems: true + items: + maximum: 8 + ports: $ref: /schemas/graph.yaml#/properties/ports properties: -- cgit v1.2.3-59-g8ed1b From 3baea29dc0a7b561170d7082f831a613ae6aa56e Mon Sep 17 00:00:00 2001 From: Prashanth K Date: Thu, 24 Apr 2025 17:41:42 +0530 Subject: usb: gadget: u_serial: Implement remote wakeup capability Implement the remote wakeup capability for u_serial. The newly added function gserial_wakeup_host() wakes up the host when there is some data to be sent while the device is suspended. Add gser_get_status() callbacks to advertise f_serial interface as function wakeup capable. Signed-off-by: Prashanth K Link: https://lore.kernel.org/r/20250424121142.4180241-1-prashanth.k@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_serial.c | 7 ++++++ drivers/usb/gadget/function/u_serial.c | 43 ++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/drivers/usb/gadget/function/f_serial.c b/drivers/usb/gadget/function/f_serial.c index 8f7e7a2b2ff2..0f266bc067f5 100644 --- a/drivers/usb/gadget/function/f_serial.c +++ b/drivers/usb/gadget/function/f_serial.c @@ -364,6 +364,12 @@ static void gser_suspend(struct usb_function *f) gserial_suspend(&gser->port); } +static int gser_get_status(struct usb_function *f) +{ + return (f->func_wakeup_armed ? USB_INTRF_STAT_FUNC_RW : 0) | + USB_INTRF_STAT_FUNC_RW_CAP; +} + static struct usb_function *gser_alloc(struct usb_function_instance *fi) { struct f_gser *gser; @@ -387,6 +393,7 @@ static struct usb_function *gser_alloc(struct usb_function_instance *fi) gser->port.func.free_func = gser_free; gser->port.func.resume = gser_resume; gser->port.func.suspend = gser_suspend; + gser->port.func.get_status = gser_get_status; return &gser->port.func; } diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c index 36fff45e8c9b..41dee7c8cc7c 100644 --- a/drivers/usb/gadget/function/u_serial.c +++ b/drivers/usb/gadget/function/u_serial.c @@ -592,6 +592,17 @@ static int gs_start_io(struct gs_port *port) return status; } +static int gserial_wakeup_host(struct gserial *gser) +{ + struct usb_function *func = &gser->func; + struct usb_gadget *gadget = func->config->cdev->gadget; + + if (func->func_suspended) + return usb_func_wakeup(func); + else + return usb_gadget_wakeup(gadget); +} + /*-------------------------------------------------------------------------*/ /* TTY Driver */ @@ -746,6 +757,8 @@ static ssize_t gs_write(struct tty_struct *tty, const u8 *buf, size_t count) { struct gs_port *port = tty->driver_data; unsigned long flags; + int ret = 0; + struct gserial *gser = port->port_usb; pr_vdebug("gs_write: ttyGS%d (%p) writing %zu bytes\n", port->port_num, tty, count); @@ -753,6 +766,17 @@ static ssize_t gs_write(struct tty_struct *tty, const u8 *buf, size_t count) spin_lock_irqsave(&port->port_lock, flags); if (count) count = kfifo_in(&port->port_write_buf, buf, count); + + if (port->suspended) { + spin_unlock_irqrestore(&port->port_lock, flags); + ret = gserial_wakeup_host(gser); + if (ret) { + pr_debug("ttyGS%d: Remote wakeup failed:%d\n", port->port_num, ret); + return count; + } + spin_lock_irqsave(&port->port_lock, flags); + } + /* treat count == 0 as flush_chars() */ if (port->port_usb) gs_start_tx(port); @@ -781,10 +805,22 @@ static void gs_flush_chars(struct tty_struct *tty) { struct gs_port *port = tty->driver_data; unsigned long flags; + int ret = 0; + struct gserial *gser = port->port_usb; pr_vdebug("gs_flush_chars: (%d,%p)\n", port->port_num, tty); spin_lock_irqsave(&port->port_lock, flags); + if (port->suspended) { + spin_unlock_irqrestore(&port->port_lock, flags); + ret = gserial_wakeup_host(gser); + if (ret) { + pr_debug("ttyGS%d: Remote wakeup failed:%d\n", port->port_num, ret); + return; + } + spin_lock_irqsave(&port->port_lock, flags); + } + if (port->port_usb) gs_start_tx(port); spin_unlock_irqrestore(&port->port_lock, flags); @@ -1464,6 +1500,13 @@ void gserial_suspend(struct gserial *gser) return; } + if (port->write_busy || port->write_started) { + /* Wakeup to host if there are ongoing transfers */ + spin_unlock_irqrestore(&serial_port_lock, flags); + if (!gserial_wakeup_host(gser)) + return; + } + spin_lock(&port->port_lock); spin_unlock(&serial_port_lock); port->suspended = true; -- cgit v1.2.3-59-g8ed1b From 3fc0810497a697b84dfe36f6011bd04c5e21d39b Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Fri, 25 Apr 2025 23:21:26 -0700 Subject: usb: gadget: g_ffs: Adjust f_ffs[0] allocation type In preparation for making the kmalloc family of allocators type aware, we need to make sure that the returned type from the allocation matches the type of the variable being assigned. (Before, the allocator would always return "void *", which can be implicitly cast to any pointer type.) The assigned type is "struct usb_function **" but the returned type will be "struct usb_function ***". These are the same size allocation (pointer size), but different types. Adjust the allocation type to match the assignment. Signed-off-by: Kees Cook Link: https://lore.kernel.org/r/20250426062125.work.209-kees@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/legacy/g_ffs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/legacy/g_ffs.c b/drivers/usb/gadget/legacy/g_ffs.c index a9544fea8723..578556422ea3 100644 --- a/drivers/usb/gadget/legacy/g_ffs.c +++ b/drivers/usb/gadget/legacy/g_ffs.c @@ -188,7 +188,7 @@ static int __init gfs_init(void) /* * Allocate in one chunk for easier maintenance */ - f_ffs[0] = kcalloc(func_num * N_CONF, sizeof(*f_ffs), GFP_KERNEL); + f_ffs[0] = kcalloc(func_num * N_CONF, sizeof(*f_ffs[0]), GFP_KERNEL); if (!f_ffs[0]) { ret = -ENOMEM; goto no_func; -- cgit v1.2.3-59-g8ed1b From ea34925f5b2ee48d7b8b47bc041e381a3cb637cc Mon Sep 17 00:00:00 2001 From: Ben Hoff Date: Tue, 29 Apr 2025 14:28:09 -0400 Subject: usb: gadget: hid: allow dynamic interval configuration via configfs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch enhances the HID gadget driver to support dynamic configuration of the interrupt polling interval (bInterval) via configfs. A new ‘interval’ attribute is exposed under each HID function’s configfs directory, and any write to it will adjust the poll rate for all endpoints without requiring a rebuild. When the attribute has never been written, legacy defaults are preserved: • Full-Speed (FS) endpoints (IN & OUT) poll every 10 ms • High-Speed (HS) endpoints (IN & OUT) poll every 4 micro-frames (~1 ms) To implement this cleanly: • Add two new fields to f_hid_opts and f_hidg: – unsigned char interval – bool interval_user_set • Introduce dedicated f_hid_opts_interval_show/store functions. The store routine parses into an unsigned int, bounds‐checks, assigns to opts->interval, and sets opts->interval_user_set = true. • Initialize opts->interval = 4 and opts->interval_user_set = false in hidg_alloc_inst(), then copy both into the live f_hidg instance in hidg_alloc(). • In hidg_bind(), set each endpoint’s bInterval based on whether the user has written the attribute: – If interval_user_set == false, use FS=10 / HS=4 – If interval_user_set == true, use the user’s value for both FS & HS Signed-off-by: Ben Hoff Link: https://lore.kernel.org/r/20250429182809.811786-1-hoff.benjamin.k@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_hid.c | 119 ++++++++++++++++++++++++++---------- drivers/usb/gadget/function/u_hid.h | 2 + 2 files changed, 90 insertions(+), 31 deletions(-) diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c index 1bc40fc0ccf7..1b2a363f3177 100644 --- a/drivers/usb/gadget/function/f_hid.c +++ b/drivers/usb/gadget/function/f_hid.c @@ -62,6 +62,9 @@ struct f_hidg { unsigned short report_desc_length; char *report_desc; unsigned short report_length; + unsigned char interval; + bool interval_user_set; + /* * use_out_ep - if true, the OUT Endpoint (interrupt out method) * will be used to receive reports from the host @@ -157,10 +160,7 @@ static struct usb_endpoint_descriptor hidg_ss_in_ep_desc = { .bEndpointAddress = USB_DIR_IN, .bmAttributes = USB_ENDPOINT_XFER_INT, /*.wMaxPacketSize = DYNAMIC */ - .bInterval = 4, /* FIXME: Add this field in the - * HID gadget configuration? - * (struct hidg_func_descriptor) - */ + /*.bInterval = DYNAMIC */ }; static struct usb_ss_ep_comp_descriptor hidg_ss_in_comp_desc = { @@ -178,10 +178,7 @@ static struct usb_endpoint_descriptor hidg_ss_out_ep_desc = { .bEndpointAddress = USB_DIR_OUT, .bmAttributes = USB_ENDPOINT_XFER_INT, /*.wMaxPacketSize = DYNAMIC */ - .bInterval = 4, /* FIXME: Add this field in the - * HID gadget configuration? - * (struct hidg_func_descriptor) - */ + /*.bInterval = DYNAMIC */ }; static struct usb_ss_ep_comp_descriptor hidg_ss_out_comp_desc = { @@ -219,10 +216,7 @@ static struct usb_endpoint_descriptor hidg_hs_in_ep_desc = { .bEndpointAddress = USB_DIR_IN, .bmAttributes = USB_ENDPOINT_XFER_INT, /*.wMaxPacketSize = DYNAMIC */ - .bInterval = 4, /* FIXME: Add this field in the - * HID gadget configuration? - * (struct hidg_func_descriptor) - */ + /* .bInterval = DYNAMIC */ }; static struct usb_endpoint_descriptor hidg_hs_out_ep_desc = { @@ -231,10 +225,7 @@ static struct usb_endpoint_descriptor hidg_hs_out_ep_desc = { .bEndpointAddress = USB_DIR_OUT, .bmAttributes = USB_ENDPOINT_XFER_INT, /*.wMaxPacketSize = DYNAMIC */ - .bInterval = 4, /* FIXME: Add this field in the - * HID gadget configuration? - * (struct hidg_func_descriptor) - */ + /*.bInterval = DYNAMIC */ }; static struct usb_descriptor_header *hidg_hs_descriptors_intout[] = { @@ -260,10 +251,7 @@ static struct usb_endpoint_descriptor hidg_fs_in_ep_desc = { .bEndpointAddress = USB_DIR_IN, .bmAttributes = USB_ENDPOINT_XFER_INT, /*.wMaxPacketSize = DYNAMIC */ - .bInterval = 10, /* FIXME: Add this field in the - * HID gadget configuration? - * (struct hidg_func_descriptor) - */ + /*.bInterval = DYNAMIC */ }; static struct usb_endpoint_descriptor hidg_fs_out_ep_desc = { @@ -272,10 +260,7 @@ static struct usb_endpoint_descriptor hidg_fs_out_ep_desc = { .bEndpointAddress = USB_DIR_OUT, .bmAttributes = USB_ENDPOINT_XFER_INT, /*.wMaxPacketSize = DYNAMIC */ - .bInterval = 10, /* FIXME: Add this field in the - * HID gadget configuration? - * (struct hidg_func_descriptor) - */ + /*.bInterval = DYNAMIC */ }; static struct usb_descriptor_header *hidg_fs_descriptors_intout[] = { @@ -1217,6 +1202,16 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f) hidg_hs_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); hidg_fs_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); hidg_ss_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); + + /* IN endpoints: FS default=10ms, HS default=4µ-frame; user override if set */ + if (!hidg->interval_user_set) { + hidg_fs_in_ep_desc.bInterval = 10; + hidg_hs_in_ep_desc.bInterval = 4; + } else { + hidg_fs_in_ep_desc.bInterval = hidg->interval; + hidg_hs_in_ep_desc.bInterval = hidg->interval; + } + hidg_ss_out_comp_desc.wBytesPerInterval = cpu_to_le16(hidg->report_length); hidg_hs_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); @@ -1239,19 +1234,27 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f) hidg_ss_out_ep_desc.bEndpointAddress = hidg_fs_out_ep_desc.bEndpointAddress; - if (hidg->use_out_ep) + if (hidg->use_out_ep) { + /* OUT endpoints: same defaults (FS=10, HS=4) unless user set */ + if (!hidg->interval_user_set) { + hidg_fs_out_ep_desc.bInterval = 10; + hidg_hs_out_ep_desc.bInterval = 4; + } else { + hidg_fs_out_ep_desc.bInterval = hidg->interval; + hidg_hs_out_ep_desc.bInterval = hidg->interval; + } status = usb_assign_descriptors(f, - hidg_fs_descriptors_intout, - hidg_hs_descriptors_intout, - hidg_ss_descriptors_intout, - hidg_ss_descriptors_intout); - else + hidg_fs_descriptors_intout, + hidg_hs_descriptors_intout, + hidg_ss_descriptors_intout, + hidg_ss_descriptors_intout); + } else { status = usb_assign_descriptors(f, hidg_fs_descriptors_ssreport, hidg_hs_descriptors_ssreport, hidg_ss_descriptors_ssreport, hidg_ss_descriptors_ssreport); - + } if (status) goto fail; @@ -1423,6 +1426,53 @@ end: CONFIGFS_ATTR(f_hid_opts_, report_desc); +static ssize_t f_hid_opts_interval_show(struct config_item *item, char *page) +{ + struct f_hid_opts *opts = to_f_hid_opts(item); + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%d\n", opts->interval); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_hid_opts_interval_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_hid_opts *opts = to_f_hid_opts(item); + int ret; + unsigned int tmp; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + /* parse into a wider type first */ + ret = kstrtouint(page, 0, &tmp); + if (ret) + goto end; + + /* range-check against unsigned char max */ + if (tmp > 255) { + ret = -EINVAL; + goto end; + } + + opts->interval = (unsigned char)tmp; + opts->interval_user_set = true; + ret = len; + +end: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(f_hid_opts_, interval); + static ssize_t f_hid_opts_dev_show(struct config_item *item, char *page) { struct f_hid_opts *opts = to_f_hid_opts(item); @@ -1437,6 +1487,7 @@ static struct configfs_attribute *hid_attrs[] = { &f_hid_opts_attr_protocol, &f_hid_opts_attr_no_out_endpoint, &f_hid_opts_attr_report_length, + &f_hid_opts_attr_interval, &f_hid_opts_attr_report_desc, &f_hid_opts_attr_dev, NULL, @@ -1483,6 +1534,10 @@ static struct usb_function_instance *hidg_alloc_inst(void) if (!opts) return ERR_PTR(-ENOMEM); mutex_init(&opts->lock); + + opts->interval = 4; + opts->interval_user_set = false; + opts->func_inst.free_func_inst = hidg_free_inst; ret = &opts->func_inst; @@ -1561,6 +1616,8 @@ static struct usb_function *hidg_alloc(struct usb_function_instance *fi) hidg->bInterfaceProtocol = opts->protocol; hidg->report_length = opts->report_length; hidg->report_desc_length = opts->report_desc_length; + hidg->interval = opts->interval; + hidg->interval_user_set = opts->interval_user_set; if (opts->report_desc) { hidg->report_desc = kmemdup(opts->report_desc, opts->report_desc_length, diff --git a/drivers/usb/gadget/function/u_hid.h b/drivers/usb/gadget/function/u_hid.h index 84bb70292855..a9ed9720caee 100644 --- a/drivers/usb/gadget/function/u_hid.h +++ b/drivers/usb/gadget/function/u_hid.h @@ -25,6 +25,8 @@ struct f_hid_opts { unsigned short report_desc_length; unsigned char *report_desc; bool report_desc_alloc; + unsigned char interval; + bool interval_user_set; /* * Protect the data form concurrent access by read/write -- cgit v1.2.3-59-g8ed1b From 7fcdfaf37272669dec131963745bf51c360724ac Mon Sep 17 00:00:00 2001 From: Pin-yen Lin Date: Tue, 22 Apr 2025 16:28:27 +0800 Subject: dt-bindings: usb: Introduce usb-hub.yaml Introduce a general USB hub binding that describes downstream ports and hard wired USB devices for on-board USB hubs. Signed-off-by: Pin-yen Lin Reviewed-by: "Rob Herring (Arm)" Link: https://lore.kernel.org/r/20250422082957.2058229-2-treapking@chromium.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usb-hub.yaml | 84 ++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/usb-hub.yaml diff --git a/Documentation/devicetree/bindings/usb/usb-hub.yaml b/Documentation/devicetree/bindings/usb/usb-hub.yaml new file mode 100644 index 000000000000..5238ab105763 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/usb-hub.yaml @@ -0,0 +1,84 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/usb-hub.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Generic USB Hub + +maintainers: + - Pin-yen Lin + +allOf: + - $ref: usb-device.yaml# + +properties: + '#address-cells': + const: 1 + + peer-hub: + $ref: /schemas/types.yaml#/definitions/phandle + description: + phandle to the peer hub on the controller. + + ports: + $ref: /schemas/graph.yaml#/properties/ports + description: + The downstream facing USB ports + + patternProperties: + "^port@[1-9a-f][0-9a-f]*$": + $ref: /schemas/graph.yaml#/properties/port + +patternProperties: + '^.*@[1-9a-f][0-9a-f]*$': + description: The hard wired USB devices + type: object + $ref: /schemas/usb/usb-device.yaml + additionalProperties: true + +required: + - compatible + - reg + +additionalProperties: true + +examples: + - | + usb { + #address-cells = <1>; + #size-cells = <0>; + + /* 2.0 hub on port 1 */ + hub_2_0: hub@1 { + compatible = "usb123,4567"; + reg = <1>; + peer-hub = <&hub_3_0>; + #address-cells = <1>; + #size-cells = <0>; + /* USB 2.0 device on port 5 */ + device@5 { + reg = <5>; + compatible = "usb765,4321"; + }; + }; + + /* 3.0 hub on port 2 */ + hub_3_0: hub@2 { + compatible = "usb123,abcd"; + reg = <2>; + peer-hub = <&hub_2_0>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + /* Type-A connector on port 3 */ + port@3 { + reg = <3>; + endpoint { + remote-endpoint = <&usb_a0_ss>; + }; + }; + }; + }; + }; -- cgit v1.2.3-59-g8ed1b From fc259b024cb3e901d8f62ea4f305bccff587fd33 Mon Sep 17 00:00:00 2001 From: Pin-yen Lin Date: Tue, 22 Apr 2025 16:28:28 +0800 Subject: dt-bindings: usb: Add binding for PS5511 hub controller Parade PS5511 is USB hub with 4 USB 3.2 compliant 5Gbps downstream(DS) ports, and 1 extra USB 2.0 downstream port. The hub has one reset pin control and two power supplies (3V3 and 1V1). Signed-off-by: Pin-yen Lin Reviewed-by: "Rob Herring (Arm)" Link: https://lore.kernel.org/r/20250422082957.2058229-3-treapking@chromium.org Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/parade,ps5511.yaml | 108 +++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/parade,ps5511.yaml diff --git a/Documentation/devicetree/bindings/usb/parade,ps5511.yaml b/Documentation/devicetree/bindings/usb/parade,ps5511.yaml new file mode 100644 index 000000000000..10d002f09db8 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/parade,ps5511.yaml @@ -0,0 +1,108 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/parade,ps5511.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Parade PS5511 4+1 Port USB 3.2 Gen 1 Hub Controller + +maintainers: + - Pin-yen Lin + +properties: + compatible: + enum: + - usb1da0,5511 + - usb1da0,55a1 + + reset-gpios: + items: + - description: GPIO specifier for RESETB pin. + + vddd11-supply: + description: + 1V1 power supply to the hub + + vdd33-supply: + description: + 3V3 power supply to the hub + + peer-hub: true + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + patternProperties: + '^port@': + $ref: /schemas/graph.yaml#/properties/port + + properties: + reg: + minimum: 1 + maximum: 5 + +additionalProperties: + properties: + reg: + minimum: 1 + maximum: 5 + +required: + - peer-hub + +allOf: + - $ref: usb-hub.yaml# + - if: + not: + properties: + compatible: + enum: + - usb1da0,55a1 + then: + properties: + ports: + properties: + port@5: false + + patternProperties: + '^.*@5$': false + +examples: + - | + usb { + #address-cells = <1>; + #size-cells = <0>; + + /* 2.0 hub on port 1 */ + hub_2_0: hub@1 { + compatible = "usb1da0,55a1"; + reg = <1>; + peer-hub = <&hub_3_0>; + #address-cells = <1>; + #size-cells = <0>; + /* USB 2.0 device on port 5 */ + device@5 { + reg = <5>; + compatible = "usb123,4567"; + }; + }; + + /* 3.0 hub on port 2 */ + hub_3_0: hub@2 { + compatible = "usb1da0,5511"; + reg = <2>; + peer-hub = <&hub_2_0>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + /* Type-A connector on port 3 */ + port@3 { + reg = <3>; + endpoint { + remote-endpoint = <&usb_a0_ss>; + }; + }; + }; + }; + }; -- cgit v1.2.3-59-g8ed1b From 76cbb3eabf0b5f3048c4f988504e58beaf040eb7 Mon Sep 17 00:00:00 2001 From: Pin-yen Lin Date: Tue, 22 Apr 2025 16:28:29 +0800 Subject: dt-bindings: usb: realtek,rts5411: Adapt usb-hub.yaml Inherit usb-hub.yaml and remove duplicated schemas. Signed-off-by: Pin-yen Lin Reviewed-by: "Rob Herring (Arm)" Link: https://lore.kernel.org/r/20250422082957.2058229-4-treapking@chromium.org Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/realtek,rts5411.yaml | 52 ++++++---------------- 1 file changed, 13 insertions(+), 39 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/realtek,rts5411.yaml b/Documentation/devicetree/bindings/usb/realtek,rts5411.yaml index 6577a61cc075..a020afaf2d6e 100644 --- a/Documentation/devicetree/bindings/usb/realtek,rts5411.yaml +++ b/Documentation/devicetree/bindings/usb/realtek,rts5411.yaml @@ -10,7 +10,7 @@ maintainers: - Matthias Kaehlcke allOf: - - $ref: usb-device.yaml# + - $ref: usb-hub.yaml# properties: compatible: @@ -19,61 +19,35 @@ properties: - usbbda,5411 - usbbda,411 - reg: true - - '#address-cells': - const: 1 - - '#size-cells': - const: 0 - vdd-supply: description: phandle to the regulator that provides power to the hub. - peer-hub: - $ref: /schemas/types.yaml#/definitions/phandle - description: - phandle to the peer hub on the controller. + peer-hub: true ports: $ref: /schemas/graph.yaml#/properties/ports - properties: - port@1: - $ref: /schemas/graph.yaml#/properties/port - description: - 1st downstream facing USB port - - port@2: + patternProperties: + '^port@': $ref: /schemas/graph.yaml#/properties/port - description: - 2nd downstream facing USB port - port@3: - $ref: /schemas/graph.yaml#/properties/port - description: - 3rd downstream facing USB port + properties: + reg: + minimum: 1 + maximum: 4 - port@4: - $ref: /schemas/graph.yaml#/properties/port - description: - 4th downstream facing USB port - -patternProperties: - '^.*@[1-4]$': - description: The hard wired USB devices - type: object - $ref: /schemas/usb/usb-device.yaml - additionalProperties: true +additionalProperties: + properties: + reg: + minimum: 1 + maximum: 4 required: - peer-hub - compatible - reg -additionalProperties: false - examples: - | usb { -- cgit v1.2.3-59-g8ed1b From 588d032e9e566997db3213dee145dbe3bda297b6 Mon Sep 17 00:00:00 2001 From: Pin-yen Lin Date: Tue, 22 Apr 2025 16:28:30 +0800 Subject: usb: misc: onboard_usb_dev: Add Parade PS5511 hub support Parade PS5511 is 4+1 port USB 3.2 gen 1 hub with a reset pin and two power supplies (3V3 and 1V1). Add the support for this hub for the reset pin control and power supply. Signed-off-by: Pin-yen Lin Reviewed-by: Stephen Boyd Link: https://lore.kernel.org/r/20250422082957.2058229-5-treapking@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/onboard_usb_dev.c | 3 +++ drivers/usb/misc/onboard_usb_dev.h | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/drivers/usb/misc/onboard_usb_dev.c b/drivers/usb/misc/onboard_usb_dev.c index 2f9e8f8108d8..84659fbc0ec8 100644 --- a/drivers/usb/misc/onboard_usb_dev.c +++ b/drivers/usb/misc/onboard_usb_dev.c @@ -490,6 +490,7 @@ static struct platform_driver onboard_dev_driver = { #define VENDOR_ID_CYPRESS 0x04b4 #define VENDOR_ID_GENESYS 0x05e3 #define VENDOR_ID_MICROCHIP 0x0424 +#define VENDOR_ID_PARADE 0x1da0 #define VENDOR_ID_REALTEK 0x0bda #define VENDOR_ID_TI 0x0451 #define VENDOR_ID_VIA 0x2109 @@ -580,6 +581,8 @@ static const struct usb_device_id onboard_dev_id_table[] = { { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2517) }, /* USB2517 USB 2.0 HUB */ { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2744) }, /* USB5744 USB 2.0 HUB */ { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x5744) }, /* USB5744 USB 3.0 HUB */ + { USB_DEVICE(VENDOR_ID_PARADE, 0x5511) }, /* PS5511 USB 3.2 */ + { USB_DEVICE(VENDOR_ID_PARADE, 0x55a1) }, /* PS5511 USB 2.0 */ { USB_DEVICE(VENDOR_ID_REALTEK, 0x0411) }, /* RTS5411 USB 3.1 HUB */ { USB_DEVICE(VENDOR_ID_REALTEK, 0x5411) }, /* RTS5411 USB 2.1 HUB */ { USB_DEVICE(VENDOR_ID_REALTEK, 0x0414) }, /* RTS5414 USB 3.2 HUB */ diff --git a/drivers/usb/misc/onboard_usb_dev.h b/drivers/usb/misc/onboard_usb_dev.h index 4b023ddfbdd6..a5b18840c3f4 100644 --- a/drivers/usb/misc/onboard_usb_dev.h +++ b/drivers/usb/misc/onboard_usb_dev.h @@ -38,6 +38,13 @@ static const struct onboard_dev_pdata microchip_usb5744_data = { .is_hub = true, }; +static const struct onboard_dev_pdata parade_ps5511_data = { + .reset_us = 500, + .num_supplies = 2, + .supply_names = { "vddd11", "vdd33"}, + .is_hub = true, +}; + static const struct onboard_dev_pdata realtek_rts5411_data = { .reset_us = 0, .num_supplies = 1, @@ -130,6 +137,8 @@ static const struct of_device_id onboard_dev_match[] = { { .compatible = "usbbda,5411", .data = &realtek_rts5411_data, }, { .compatible = "usbbda,414", .data = &realtek_rts5411_data, }, { .compatible = "usbbda,5414", .data = &realtek_rts5411_data, }, + { .compatible = "usb1da0,5511", .data = ¶de_ps5511_data, }, + { .compatible = "usb1da0,55a1", .data = ¶de_ps5511_data, }, { .compatible = "usb2109,817", .data = &vialab_vl817_data, }, { .compatible = "usb2109,2817", .data = &vialab_vl817_data, }, { .compatible = "usb20b1,0013", .data = &xmos_xvf3500_data, }, -- cgit v1.2.3-59-g8ed1b From 692a497eb748fa597918f1faa11e77465b23cc00 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 24 Apr 2025 09:44:48 +0200 Subject: USB: serial: ti_usb_3410_5052: drop bogus read urb check The read urb pointer is dereferenced before checking that it is non-NULL during open(), but no check is needed as the existence of a bulk in endpoint is verified during attach() since commit ef079936d3cd ("USB: serial: ti_usb_3410_5052: fix NULL-deref at open"). Drop the bogus read urb sanity check. Signed-off-by: Johan Hovold --- drivers/usb/serial/ti_usb_3410_5052.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/usb/serial/ti_usb_3410_5052.c b/drivers/usb/serial/ti_usb_3410_5052.c index a0c244bc77c0..d671189ecee2 100644 --- a/drivers/usb/serial/ti_usb_3410_5052.c +++ b/drivers/usb/serial/ti_usb_3410_5052.c @@ -729,11 +729,6 @@ static int ti_open(struct tty_struct *tty, struct usb_serial_port *port) /* start read urb */ urb = port->read_urb; - if (!urb) { - dev_err(&port->dev, "%s - no read urb\n", __func__); - status = -EINVAL; - goto unlink_int_urb; - } tport->tp_read_urb_state = TI_READ_URB_RUNNING; urb->context = tport; status = usb_submit_urb(urb, GFP_KERNEL); -- cgit v1.2.3-59-g8ed1b From 0f73628e9da1ee39daf5f188190cdbaee5e0c98c Mon Sep 17 00:00:00 2001 From: Sergey Senozhatsky Date: Fri, 28 Mar 2025 00:03:50 +0900 Subject: thunderbolt: Do not double dequeue a configuration request Some of our devices crash in tb_cfg_request_dequeue(): general protection fault, probably for non-canonical address 0xdead000000000122 CPU: 6 PID: 91007 Comm: kworker/6:2 Tainted: G U W 6.6.65 RIP: 0010:tb_cfg_request_dequeue+0x2d/0xa0 Call Trace: ? tb_cfg_request_dequeue+0x2d/0xa0 tb_cfg_request_work+0x33/0x80 worker_thread+0x386/0x8f0 kthread+0xed/0x110 ret_from_fork+0x38/0x50 ret_from_fork_asm+0x1b/0x30 The circumstances are unclear, however, the theory is that tb_cfg_request_work() can be scheduled twice for a request: first time via frame.callback from ring_work() and second time from tb_cfg_request(). Both times kworkers will execute tb_cfg_request_dequeue(), which results in double list_del() from the ctl->request_queue (the list poison deference hints at it: 0xdead000000000122). Do not dequeue requests that don't have TB_CFG_REQUEST_ACTIVE bit set. Signed-off-by: Sergey Senozhatsky Cc: stable@vger.kernel.org Signed-off-by: Mika Westerberg --- drivers/thunderbolt/ctl.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index cd15e84c47f4..1db2e951b53f 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -151,6 +151,11 @@ static void tb_cfg_request_dequeue(struct tb_cfg_request *req) struct tb_ctl *ctl = req->ctl; mutex_lock(&ctl->request_queue_lock); + if (!test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags)) { + mutex_unlock(&ctl->request_queue_lock); + return; + } + list_del(&req->list); clear_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); if (test_bit(TB_CFG_REQUEST_CANCELED, &req->flags)) -- cgit v1.2.3-59-g8ed1b From 597f5c2f4128350bd5f549af6074417d296b4618 Mon Sep 17 00:00:00 2001 From: Michal Pecio Date: Thu, 15 May 2025 16:55:58 +0300 Subject: usb: xhci: Don't log transfer ring segment list on errors The error message above used to span two lines, rarely more. A recent cleanup concentrated useful information from it in one line, but then it added printing the list of all ring segments, which is even longer than before. It provides no new information in usual cases and little in unusual ones, but adds noise to the log. Drop it. Signed-off-by: Michal Pecio Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250515135621.335595-2-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index eefbc7b3681e..1d5268295396 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2977,9 +2977,6 @@ debug_finding_td: (unsigned long long)xhci_trb_virt_to_dma(td->start_seg, td->start_trb), (unsigned long long)xhci_trb_virt_to_dma(td->end_seg, td->end_trb)); - xhci_for_each_ring_seg(ep_ring->first_seg, ep_seg) - xhci_warn(xhci, "Ring seg %u dma %pad\n", ep_seg->num, &ep_seg->dma); - return -ESHUTDOWN; err_out: -- cgit v1.2.3-59-g8ed1b From 59d50e53e070ce03c811e31c4fb583d3b0f88cfc Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Thu, 15 May 2025 16:55:59 +0300 Subject: usb: xhci: Add debugfs support for xHCI port bandwidth In many projects, you need to obtain the available bandwidth of the xhci roothub port. Refer to xhci rev1_2 and use the TRB_GET_BW command to obtain it. hardware tested: 03:00.3 USB controller: Advanced Micro Devices, Inc. [AMD] Raven USB 3.1 (prog-if 30 [XHCI]) Subsystem: Huawei Technologies Co., Ltd. Raven USB 3.1 Flags: bus master, fast devsel, latency 0, IRQ 30 Memory at c0300000 (64-bit, non-prefetchable) [size=1M] Capabilities: [48] Vendor Specific Information: Len=08 Capabilities: [50] Power Management version 3 Capabilities: [64] Express Endpoint, MSI 00 Capabilities: [a0] MSI: Enable- Count=1/8 Maskable- 64bit+ Capabilities: [c0] MSI-X: Enable+ Count=8 Masked- Kernel driver in use: xhci_hcd test progress: 1. cd /sys/kernel/debug/usb/xhci/0000:03:00.3/port_bandwidth# ls FS_BW HS_BW SS_BW 2. test fs speed device cat FS_BW port[1] available bw: 90%. port[2] available bw: 90%. port[3] available bw: 90%. port[4] available bw: 90%. port[5] available bw: 0%. port[6] available bw: 0%. port[7] available bw: 0%. port[8] available bw: 0%. plug in fs usb audio ID 0d8c:013c cat FS_BW port[1] available bw: 76%. port[2] available bw: 76%. port[3] available bw: 76%. port[4] available bw: 76%. port[5] available bw: 0%. port[6] available bw: 0%. port[7] available bw: 0%. port[8] available bw: 0%. 3. test hs speed device cat HS_BW port[1] available bw: 79%. port[2] available bw: 79%. port[3] available bw: 79%. port[4] available bw: 79%. port[5] available bw: 0%. port[6] available bw: 0%. port[7] available bw: 0%. port[8] available bw: 0%. plug in hs usb video ID 0408:1040 cat HS_BW port[1] available bw: 39%. port[2] available bw: 39%. port[3] available bw: 39%. port[4] available bw: 39%. port[5] available bw: 0%. port[6] available bw: 0%. port[7] available bw: 0%. port[8] available bw: 0%. 4.cat SS_BW port[1] available bw: 0%. port[2] available bw: 0%. port[3] available bw: 0%. port[4] available bw: 0%. port[5] available bw: 90%. port[6] available bw: 90%. port[7] available bw: 90%. port[8] available bw: 90%. Signed-off-by: Xu Rao Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250515135621.335595-3-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-debugfs.c | 108 ++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/xhci-mem.c | 45 ++++++++++++++++- drivers/usb/host/xhci-ring.c | 13 +++++ drivers/usb/host/xhci.c | 36 ++++++++++++++ drivers/usb/host/xhci.h | 14 ++++++ 5 files changed, 215 insertions(+), 1 deletion(-) diff --git a/drivers/usb/host/xhci-debugfs.c b/drivers/usb/host/xhci-debugfs.c index 1f5ef174abea..c6d44977193f 100644 --- a/drivers/usb/host/xhci-debugfs.c +++ b/drivers/usb/host/xhci-debugfs.c @@ -631,6 +631,112 @@ static void xhci_debugfs_create_ports(struct xhci_hcd *xhci, } } +static int xhci_port_bw_show(struct xhci_hcd *xhci, u8 dev_speed, + struct seq_file *s) +{ + unsigned int num_ports; + unsigned int i; + int ret; + struct xhci_container_ctx *ctx; + struct usb_hcd *hcd = xhci_to_hcd(xhci); + struct device *dev = hcd->self.controller; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) + return ret; + + num_ports = HCS_MAX_PORTS(xhci->hcs_params1); + + ctx = xhci_alloc_port_bw_ctx(xhci, 0); + if (!ctx) { + pm_runtime_put_sync(dev); + return -ENOMEM; + } + + /* get roothub port bandwidth */ + ret = xhci_get_port_bandwidth(xhci, ctx, dev_speed); + if (ret) + goto err_out; + + /* print all roothub ports available bandwidth + * refer to xhci rev1_2 protocol 6.2.6 , byte 0 is reserved + */ + for (i = 1; i < num_ports+1; i++) + seq_printf(s, "port[%d] available bw: %d%%.\n", i, + ctx->bytes[i]); +err_out: + pm_runtime_put_sync(dev); + xhci_free_port_bw_ctx(xhci, ctx); + return ret; +} + +static int xhci_ss_bw_show(struct seq_file *s, void *unused) +{ + int ret; + struct xhci_hcd *xhci = (struct xhci_hcd *)s->private; + + ret = xhci_port_bw_show(xhci, USB_SPEED_SUPER, s); + return ret; +} + +static int xhci_hs_bw_show(struct seq_file *s, void *unused) +{ + int ret; + struct xhci_hcd *xhci = (struct xhci_hcd *)s->private; + + ret = xhci_port_bw_show(xhci, USB_SPEED_HIGH, s); + return ret; +} + +static int xhci_fs_bw_show(struct seq_file *s, void *unused) +{ + int ret; + struct xhci_hcd *xhci = (struct xhci_hcd *)s->private; + + ret = xhci_port_bw_show(xhci, USB_SPEED_FULL, s); + return ret; +} + +static struct xhci_file_map bw_context_files[] = { + {"SS_BW", xhci_ss_bw_show, }, + {"HS_BW", xhci_hs_bw_show, }, + {"FS_BW", xhci_fs_bw_show, }, +}; + +static int bw_context_open(struct inode *inode, struct file *file) +{ + int i; + struct xhci_file_map *f_map; + const char *file_name = file_dentry(file)->d_iname; + + for (i = 0; i < ARRAY_SIZE(bw_context_files); i++) { + f_map = &bw_context_files[i]; + + if (strcmp(f_map->name, file_name) == 0) + break; + } + + return single_open(file, f_map->show, inode->i_private); +} + +static const struct file_operations bw_fops = { + .open = bw_context_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void xhci_debugfs_create_bandwidth(struct xhci_hcd *xhci, + struct dentry *parent) +{ + parent = debugfs_create_dir("port_bandwidth", parent); + + xhci_debugfs_create_files(xhci, bw_context_files, + ARRAY_SIZE(bw_context_files), + xhci, + parent, &bw_fops); +} + void xhci_debugfs_init(struct xhci_hcd *xhci) { struct device *dev = xhci_to_hcd(xhci)->self.controller; @@ -681,6 +787,8 @@ void xhci_debugfs_init(struct xhci_hcd *xhci) xhci->debugfs_slots = debugfs_create_dir("devices", xhci->debugfs_root); xhci_debugfs_create_ports(xhci, xhci->debugfs_root); + + xhci_debugfs_create_bandwidth(xhci, xhci->debugfs_root); } void xhci_debugfs_exit(struct xhci_hcd *xhci) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index ed36df46b140..7ff6a47d3198 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -484,6 +484,35 @@ void xhci_free_container_ctx(struct xhci_hcd *xhci, kfree(ctx); } +struct xhci_container_ctx *xhci_alloc_port_bw_ctx(struct xhci_hcd *xhci, + gfp_t flags) +{ + struct xhci_container_ctx *ctx; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; + + ctx = kzalloc_node(sizeof(*ctx), flags, dev_to_node(dev)); + if (!ctx) + return NULL; + + ctx->size = GET_PORT_BW_ARRAY_SIZE; + + ctx->bytes = dma_pool_zalloc(xhci->port_bw_pool, flags, &ctx->dma); + if (!ctx->bytes) { + kfree(ctx); + return NULL; + } + return ctx; +} + +void xhci_free_port_bw_ctx(struct xhci_hcd *xhci, + struct xhci_container_ctx *ctx) +{ + if (!ctx) + return; + dma_pool_free(xhci->port_bw_pool, ctx->bytes, ctx->dma); + kfree(ctx); +} + struct xhci_input_control_ctx *xhci_get_input_control_ctx( struct xhci_container_ctx *ctx) { @@ -1912,6 +1941,11 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed small stream array pool"); + dma_pool_destroy(xhci->port_bw_pool); + xhci->port_bw_pool = NULL; + xhci_dbg_trace(xhci, trace_xhci_dbg_init, + "Freed xhci port bw array pool"); + dma_pool_destroy(xhci->medium_streams_pool); xhci->medium_streams_pool = NULL; xhci_dbg_trace(xhci, trace_xhci_dbg_init, @@ -2475,7 +2509,16 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) * will be allocated with dma_alloc_coherent() */ - if (!xhci->small_streams_pool || !xhci->medium_streams_pool) + /* refer to xhci rev1_2 protocol 5.3.3 max ports is 255. + * refer to xhci rev1_2 protocol 6.4.3.14 port bandwidth buffer need + * to be 16-byte aligned. + */ + xhci->port_bw_pool = + dma_pool_create("xHCI 256 port bw ctx arrays", + dev, GET_PORT_BW_ARRAY_SIZE, 16, 0); + + if (!xhci->small_streams_pool || !xhci->medium_streams_pool || + !xhci->port_bw_pool) goto fail; /* Set up the command ring to have one segments for now. */ diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 1d5268295396..d2002ecd27b0 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1898,6 +1898,8 @@ static void handle_cmd_completion(struct xhci_hcd *xhci, case TRB_NEC_GET_FW: xhci_handle_cmd_nec_get_fw(xhci, event); break; + case TRB_GET_BW: + break; default: /* Skip over unknown commands on the event ring */ xhci_info(xhci, "INFO unknown command type %d\n", cmd_type); @@ -4444,6 +4446,17 @@ int xhci_queue_configure_endpoint(struct xhci_hcd *xhci, command_must_succeed); } +/* Queue a get root hub port bandwidth command TRB */ +int xhci_queue_get_port_bw(struct xhci_hcd *xhci, + struct xhci_command *cmd, dma_addr_t in_ctx_ptr, + u8 dev_speed, bool command_must_succeed) +{ + return queue_command(xhci, cmd, lower_32_bits(in_ctx_ptr), + upper_32_bits(in_ctx_ptr), 0, + TRB_TYPE(TRB_GET_BW) | DEV_SPEED_FOR_TRB(dev_speed), + command_must_succeed); +} + /* Queue an evaluate context command TRB */ int xhci_queue_evaluate_context(struct xhci_hcd *xhci, struct xhci_command *cmd, dma_addr_t in_ctx_ptr, u32 slot_id, bool command_must_succeed) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index b6267fc37b08..fd9d41fe3224 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -3090,6 +3090,42 @@ void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) } EXPORT_SYMBOL_GPL(xhci_reset_bandwidth); +/* Get the available bandwidth of the ports under the xhci roothub */ +int xhci_get_port_bandwidth(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx, + u8 dev_speed) +{ + struct xhci_command *cmd; + unsigned long flags; + int ret; + + if (!ctx || !xhci) + return -EINVAL; + + cmd = xhci_alloc_command(xhci, true, GFP_KERNEL); + if (!cmd) + return -ENOMEM; + + cmd->in_ctx = ctx; + + /* get xhci port bandwidth, refer to xhci rev1_2 protocol 4.6.15 */ + spin_lock_irqsave(&xhci->lock, flags); + + ret = xhci_queue_get_port_bw(xhci, cmd, ctx->dma, dev_speed, 0); + if (ret) { + spin_unlock_irqrestore(&xhci->lock, flags); + goto err_out; + } + xhci_ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + + wait_for_completion(cmd->completion); +err_out: + kfree(cmd->completion); + kfree(cmd); + + return ret; +} + static void xhci_setup_input_ctx_for_config_ep(struct xhci_hcd *xhci, struct xhci_container_ctx *in_ctx, struct xhci_container_ctx *out_ctx, diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index df9ed8a74af6..f8198ec02981 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -589,6 +589,7 @@ struct xhci_stream_info { #define SMALL_STREAM_ARRAY_SIZE 256 #define MEDIUM_STREAM_ARRAY_SIZE 1024 +#define GET_PORT_BW_ARRAY_SIZE 256 /* Some Intel xHCI host controllers need software to keep track of the bus * bandwidth. Keep track of endpoint info here. Each root port is allocated @@ -1006,6 +1007,9 @@ enum xhci_setup_dev { /* bits 16:23 are the virtual function ID */ /* bits 24:31 are the slot ID */ +/* bits 19:16 are the dev speed */ +#define DEV_SPEED_FOR_TRB(p) ((p) << 16) + /* Stop Endpoint TRB - ep_index to endpoint ID for this TRB */ #define SUSPEND_PORT_FOR_TRB(p) (((p) & 1) << 23) #define TRB_TO_SUSPEND_PORT(p) (((p) & (1 << 23)) >> 23) @@ -1558,6 +1562,7 @@ struct xhci_hcd { struct dma_pool *device_pool; struct dma_pool *segment_pool; struct dma_pool *small_streams_pool; + struct dma_pool *port_bw_pool; struct dma_pool *medium_streams_pool; /* Host controller watchdog timer structures */ @@ -1850,6 +1855,10 @@ struct xhci_container_ctx *xhci_alloc_container_ctx(struct xhci_hcd *xhci, int type, gfp_t flags); void xhci_free_container_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx); +struct xhci_container_ctx *xhci_alloc_port_bw_ctx(struct xhci_hcd *xhci, + gfp_t flags); +void xhci_free_port_bw_ctx(struct xhci_hcd *xhci, + struct xhci_container_ctx *ctx); struct xhci_interrupter * xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs, u32 imod_interval, unsigned int intr_num); @@ -1923,6 +1932,11 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags, int xhci_queue_configure_endpoint(struct xhci_hcd *xhci, struct xhci_command *cmd, dma_addr_t in_ctx_ptr, u32 slot_id, bool command_must_succeed); +int xhci_queue_get_port_bw(struct xhci_hcd *xhci, + struct xhci_command *cmd, dma_addr_t in_ctx_ptr, + u8 dev_speed, bool command_must_succeed); +int xhci_get_port_bandwidth(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx, + u8 dev_speed); int xhci_queue_evaluate_context(struct xhci_hcd *xhci, struct xhci_command *cmd, dma_addr_t in_ctx_ptr, u32 slot_id, bool command_must_succeed); int xhci_queue_reset_ep(struct xhci_hcd *xhci, struct xhci_command *cmd, -- cgit v1.2.3-59-g8ed1b From 22f9b3c2f33d1dd8bf32f7c20f4eb16489259013 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 15 May 2025 16:56:00 +0300 Subject: usb: xhci: relocate pre-allocation initialization Move pre-allocation initialization from xhci_mem_init() to xhci_init(). This change is part of an ongoing effort to separate initialization from allocation within the xhci driver. By doing so, it will enable future patches to re-initialize xhci driver memory without the necessity of fully recreating it. Additionally, compliance mode recovery initialization has been adjusted to only occur after successful memory allocation. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250515135621.335595-4-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 28 ---------------------------- drivers/usb/host/xhci.c | 29 ++++++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 7ff6a47d3198..a7955e02905c 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2414,22 +2414,6 @@ xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs, } EXPORT_SYMBOL_GPL(xhci_create_secondary_interrupter); -static void xhci_hcd_page_size(struct xhci_hcd *xhci) -{ - u32 page_size; - - page_size = readl(&xhci->op_regs->page_size) & XHCI_PAGE_SIZE_MASK; - if (!is_power_of_2(page_size)) { - xhci_warn(xhci, "Invalid page size register = 0x%x\n", page_size); - /* Fallback to 4K page size, since that's common */ - page_size = 1; - } - - xhci->page_size = page_size << 12; - xhci_dbg_trace(xhci, trace_xhci_dbg_init, "HCD page size set to %iK", - xhci->page_size >> 10); -} - int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) { struct xhci_interrupter *ir; @@ -2438,15 +2422,6 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) unsigned int val, val2; u64 val_64; u32 temp; - int i; - - INIT_LIST_HEAD(&xhci->cmd_list); - - /* init command timeout work */ - INIT_DELAYED_WORK(&xhci->cmd_timer, xhci_handle_command_timeout); - init_completion(&xhci->cmd_ring_stop_completion); - - xhci_hcd_page_size(xhci); /* * Program the Number of Device Slots Enabled field in the CONFIG @@ -2567,9 +2542,6 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) ir->isoc_bei_interval = AVOID_BEI_INTERVAL_MAX; - for (i = 0; i < MAX_HC_SLOTS; i++) - xhci->devs[i] = NULL; - if (scratchpad_alloc(xhci, flags)) goto fail; if (xhci_setup_port_arrays(xhci, flags)) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index fd9d41fe3224..b073e9d91665 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -461,6 +461,21 @@ static int xhci_all_ports_seen_u0(struct xhci_hcd *xhci) return (xhci->port_status_u0 == ((1 << xhci->usb3_rhub.num_ports) - 1)); } +static void xhci_hcd_page_size(struct xhci_hcd *xhci) +{ + u32 page_size; + + page_size = readl(&xhci->op_regs->page_size) & XHCI_PAGE_SIZE_MASK; + if (!is_power_of_2(page_size)) { + xhci_warn(xhci, "Invalid page size register = 0x%x\n", page_size); + /* Fallback to 4K page size, since that's common */ + page_size = 1; + } + + xhci->page_size = page_size << 12; + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "HCD page size set to %iK", + xhci->page_size >> 10); +} /* * Initialize memory for HCD and xHC (one-time init). @@ -474,11 +489,18 @@ static int xhci_init(struct usb_hcd *hcd) struct xhci_hcd *xhci = hcd_to_xhci(hcd); int retval; - xhci_dbg_trace(xhci, trace_xhci_dbg_init, "xhci_init"); + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Starting %s", __func__); spin_lock_init(&xhci->lock); + INIT_LIST_HEAD(&xhci->cmd_list); + INIT_DELAYED_WORK(&xhci->cmd_timer, xhci_handle_command_timeout); + init_completion(&xhci->cmd_ring_stop_completion); + xhci_hcd_page_size(xhci); + memset(xhci->devs, 0, MAX_HC_SLOTS * sizeof(*xhci->devs)); + retval = xhci_mem_init(xhci, GFP_KERNEL); - xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Finished xhci_init"); + if (retval) + return retval; /* Initializing Compliance Mode Recovery Data If Needed */ if (xhci_compliance_mode_recovery_timer_quirk_check()) { @@ -486,7 +508,8 @@ static int xhci_init(struct usb_hcd *hcd) compliance_mode_recovery_timer_init(xhci); } - return retval; + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Finished %s", __func__); + return 0; } /*-------------------------------------------------------------------------*/ -- cgit v1.2.3-59-g8ed1b From 84f007707f21f46323ca310da0e4639d73c22b72 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 15 May 2025 16:56:01 +0300 Subject: usb: xhci: move device slot enabling register write Refactor the setting of the Number of Device Slots Enabled field into a separate function, relocating it to xhci_init(). The xHCI driver consistently sets the number of enabled device slots to the maximum value. The new function is named to reflect this behavior. Remove the "// " prefix from trace messages, as it is unnecessary and distracting. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250515135621.335595-5-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 15 +-------------- drivers/usb/host/xhci.c | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index a7955e02905c..e03109a24a50 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2419,23 +2419,10 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) struct xhci_interrupter *ir; struct device *dev = xhci_to_hcd(xhci)->self.sysdev; dma_addr_t dma; - unsigned int val, val2; + unsigned int val; u64 val_64; u32 temp; - /* - * Program the Number of Device Slots Enabled field in the CONFIG - * register with the max value of slots the HC can handle. - */ - val = HCS_MAX_SLOTS(readl(&xhci->cap_regs->hcs_params1)); - xhci_dbg_trace(xhci, trace_xhci_dbg_init, - "// xHC can handle at most %d device slots.", val); - val2 = readl(&xhci->op_regs->config_reg); - val |= (val2 & ~HCS_SLOTS_MASK); - xhci_dbg_trace(xhci, trace_xhci_dbg_init, - "// Setting Max device slots reg = 0x%x.", val); - writel(val, &xhci->op_regs->config_reg); - /* * xHCI section 5.4.6 - Device Context array must be * "physically contiguous and 64-byte (cache line) aligned". diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index b073e9d91665..ec0a2fa7d003 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -477,6 +477,24 @@ static void xhci_hcd_page_size(struct xhci_hcd *xhci) xhci->page_size >> 10); } +static void xhci_enable_max_dev_slots(struct xhci_hcd *xhci) +{ + u32 config_reg; + u32 max_slots; + + max_slots = HCS_MAX_SLOTS(xhci->hcs_params1); + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "xHC can handle at most %d device slots", + max_slots); + + config_reg = readl(&xhci->op_regs->config_reg); + config_reg &= ~HCS_SLOTS_MASK; + config_reg |= max_slots; + + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Setting Max device slots reg = 0x%x", + config_reg); + writel(config_reg, &xhci->op_regs->config_reg); +} + /* * Initialize memory for HCD and xHC (one-time init). * @@ -502,6 +520,9 @@ static int xhci_init(struct usb_hcd *hcd) if (retval) return retval; + /* Set the Number of Device Slots Enabled to the maximum supported value */ + xhci_enable_max_dev_slots(xhci); + /* Initializing Compliance Mode Recovery Data If Needed */ if (xhci_compliance_mode_recovery_timer_quirk_check()) { xhci->quirks |= XHCI_COMP_MODE_QUIRK; -- cgit v1.2.3-59-g8ed1b From 743cb737a62fc22d1e472bc07258a6f8430a399c Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 15 May 2025 16:56:02 +0300 Subject: usb: xhci: move command ring pointer write Move command ring pointer write from xhci_mem_init() to xhci_init(), and utilize the xhci_set_cmd_ring_deq() function. The xhci_set_cmd_ring_deq() function is nearly identical to the Command Ring Control register code in xhci_mem_init(). The only notable change is the use of: xhci_trb_virt_to_dma(xhci->cmd_ring->deq_seg, xhci->cmd_ring->dequeue) instead of: xhci->cmd_ring->first_seg->dma but they are effectively the same in this context. The former represents the exact position of the dequeue pointer, while the latter is the first DMA in the first segment. Before use, the dequeue pointer is at the first DMA in the first segment. The xhci_set_cmd_ring_deq() function is moved without modification, except for (long unsigned long) -> (unsigned long long) due to checkpatch.pl. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250515135621.335595-6-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 10 ---------- drivers/usb/host/xhci.c | 37 ++++++++++++++++++++----------------- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index e03109a24a50..c4b94f7bacfb 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2420,7 +2420,6 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) struct device *dev = xhci_to_hcd(xhci)->self.sysdev; dma_addr_t dma; unsigned int val; - u64 val_64; u32 temp; /* @@ -2492,15 +2491,6 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) xhci_dbg_trace(xhci, trace_xhci_dbg_init, "First segment DMA is 0x%pad", &xhci->cmd_ring->first_seg->dma); - /* Set the address in the Command Ring Control register */ - val_64 = xhci_read_64(xhci, &xhci->op_regs->cmd_ring); - val_64 = (val_64 & (u64) CMD_RING_RSVD_BITS) | - (xhci->cmd_ring->first_seg->dma & (u64) ~CMD_RING_RSVD_BITS) | - xhci->cmd_ring->cycle_state; - xhci_dbg_trace(xhci, trace_xhci_dbg_init, - "// Setting command ring address to 0x%016llx", val_64); - xhci_write_64(xhci, val_64, &xhci->op_regs->cmd_ring); - /* Reserve one command ring TRB for disabling LPM. * Since the USB core grabs the shared usb_bus bandwidth mutex before * disabling LPM, we only need to reserve one TRB for all devices. diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index ec0a2fa7d003..66a9106d8b31 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -495,6 +495,23 @@ static void xhci_enable_max_dev_slots(struct xhci_hcd *xhci) writel(config_reg, &xhci->op_regs->config_reg); } +static void xhci_set_cmd_ring_deq(struct xhci_hcd *xhci) +{ + u64 val_64; + + /* step 2: initialize command ring buffer */ + val_64 = xhci_read_64(xhci, &xhci->op_regs->cmd_ring); + val_64 = (val_64 & (u64) CMD_RING_RSVD_BITS) | + (xhci_trb_virt_to_dma(xhci->cmd_ring->deq_seg, + xhci->cmd_ring->dequeue) & + (u64) ~CMD_RING_RSVD_BITS) | + xhci->cmd_ring->cycle_state; + xhci_dbg_trace(xhci, trace_xhci_dbg_init, + "// Setting command ring address to 0x%llx", + (unsigned long long) val_64); + xhci_write_64(xhci, val_64, &xhci->op_regs->cmd_ring); +} + /* * Initialize memory for HCD and xHC (one-time init). * @@ -523,6 +540,9 @@ static int xhci_init(struct usb_hcd *hcd) /* Set the Number of Device Slots Enabled to the maximum supported value */ xhci_enable_max_dev_slots(xhci); + /* Set the address in the Command Ring Control register */ + xhci_set_cmd_ring_deq(xhci); + /* Initializing Compliance Mode Recovery Data If Needed */ if (xhci_compliance_mode_recovery_timer_quirk_check()) { xhci->quirks |= XHCI_COMP_MODE_QUIRK; @@ -793,23 +813,6 @@ static void xhci_restore_registers(struct xhci_hcd *xhci) } } -static void xhci_set_cmd_ring_deq(struct xhci_hcd *xhci) -{ - u64 val_64; - - /* step 2: initialize command ring buffer */ - val_64 = xhci_read_64(xhci, &xhci->op_regs->cmd_ring); - val_64 = (val_64 & (u64) CMD_RING_RSVD_BITS) | - (xhci_trb_virt_to_dma(xhci->cmd_ring->deq_seg, - xhci->cmd_ring->dequeue) & - (u64) ~CMD_RING_RSVD_BITS) | - xhci->cmd_ring->cycle_state; - xhci_dbg_trace(xhci, trace_xhci_dbg_init, - "// Setting command ring address to 0x%llx", - (long unsigned long) val_64); - xhci_write_64(xhci, val_64, &xhci->op_regs->cmd_ring); -} - /* * The whole command ring must be cleared to zero when we suspend the host. * -- cgit v1.2.3-59-g8ed1b From 1711b255484a1a0613a194ca7897efba6db17159 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 15 May 2025 16:56:03 +0300 Subject: usb: xhci: refactor xhci_set_cmd_ring_deq() Refactor xhci_set_cmd_ring_deq() making the code more understandable by using more descriptive constants and separating operations logically. - Remove 'CMD_RING_RSVD_BITS' the macro is misleading, the reserved bits are 5:4, yet the mask is for bits 5:0. - Introduce masks 'CMD_RING_PTR_MASK' and 'CMD_RING_CYCLE' to clearly define the bits for the Command Ring pointer and Command Ring Cycle. - Simplifying the process of setting the command ring address by separating the DMA address calculation and the Command Ring Control register (crcr) updates. - Remove the "// " prefix from trace messages, as it is unnecessary and distracting. Note: In the current implementation, the cycle bit is not cleared before applying the OR operation. Although this hasn't caused issues so far because the bit is '0' before reaching this function, the bit is now cleared before being set to prevent potential future problems and simplify the process. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250515135621.335595-7-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.c | 28 +++++++++++++++------------- drivers/usb/host/xhci.h | 8 ++++---- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 66a9106d8b31..4c9174c5c7c7 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -497,19 +497,21 @@ static void xhci_enable_max_dev_slots(struct xhci_hcd *xhci) static void xhci_set_cmd_ring_deq(struct xhci_hcd *xhci) { - u64 val_64; - - /* step 2: initialize command ring buffer */ - val_64 = xhci_read_64(xhci, &xhci->op_regs->cmd_ring); - val_64 = (val_64 & (u64) CMD_RING_RSVD_BITS) | - (xhci_trb_virt_to_dma(xhci->cmd_ring->deq_seg, - xhci->cmd_ring->dequeue) & - (u64) ~CMD_RING_RSVD_BITS) | - xhci->cmd_ring->cycle_state; - xhci_dbg_trace(xhci, trace_xhci_dbg_init, - "// Setting command ring address to 0x%llx", - (unsigned long long) val_64); - xhci_write_64(xhci, val_64, &xhci->op_regs->cmd_ring); + dma_addr_t deq_dma; + u64 crcr; + + deq_dma = xhci_trb_virt_to_dma(xhci->cmd_ring->deq_seg, xhci->cmd_ring->dequeue); + deq_dma &= CMD_RING_PTR_MASK; + + crcr = xhci_read_64(xhci, &xhci->op_regs->cmd_ring); + crcr &= ~CMD_RING_PTR_MASK; + crcr |= deq_dma; + + crcr &= ~CMD_RING_CYCLE; + crcr |= xhci->cmd_ring->cycle_state; + + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Setting command ring address to 0x%llx", crcr); + xhci_write_64(xhci, crcr, &xhci->op_regs->cmd_ring); } /* diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index f8198ec02981..6c1758f8fd01 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -191,16 +191,16 @@ struct xhci_op_regs { #define DEV_NOTE_FWAKE ENABLE_DEV_NOTE(1) /* CRCR - Command Ring Control Register - cmd_ring bitmasks */ -/* bit 0 is the command ring cycle state */ +/* bit 0 - Cycle bit indicates the ownership of the command ring */ +#define CMD_RING_CYCLE (1 << 0) /* stop ring operation after completion of the currently executing command */ #define CMD_RING_PAUSE (1 << 1) /* stop ring immediately - abort the currently executing command */ #define CMD_RING_ABORT (1 << 2) /* true: command ring is running */ #define CMD_RING_RUNNING (1 << 3) -/* bits 4:5 reserved and should be preserved */ -/* Command Ring pointer - bit mask for the lower 32 bits. */ -#define CMD_RING_RSVD_BITS (0x3f) +/* bits 63:6 - Command Ring pointer */ +#define CMD_RING_PTR_MASK GENMASK_ULL(63, 6) /* CONFIG - Configure Register - config_reg bitmasks */ /* bits 0:7 - maximum number of device slots enabled (NumSlotsEn) */ -- cgit v1.2.3-59-g8ed1b From 44455f666b4ba3c3394966827e8923c491274b38 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 15 May 2025 16:56:04 +0300 Subject: usb: xhci: move DCBAA pointer write Move the Device Context Base Address Array (DCBAA) pointer write from xhci_mem_init() to xhci_init(). This is part of the ongoing effort to separate allocation and initialization. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250515135621.335595-8-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 1 - drivers/usb/host/xhci.c | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index c4b94f7bacfb..ac96f0155cab 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2434,7 +2434,6 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) xhci_dbg_trace(xhci, trace_xhci_dbg_init, "// Device context base array address = 0x%pad (DMA), %p (virt)", &xhci->dcbaa->dma, xhci->dcbaa); - xhci_write_64(xhci, dma, &xhci->op_regs->dcbaa_ptr); /* * Initialize the ring segment pool. The ring must be a contiguous diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 4c9174c5c7c7..e8c262865188 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -545,6 +545,9 @@ static int xhci_init(struct usb_hcd *hcd) /* Set the address in the Command Ring Control register */ xhci_set_cmd_ring_deq(xhci); + /* Set Device Context Base Address Array pointer */ + xhci_write_64(xhci, xhci->dcbaa->dma, &xhci->op_regs->dcbaa_ptr); + /* Initializing Compliance Mode Recovery Data If Needed */ if (xhci_compliance_mode_recovery_timer_quirk_check()) { xhci->quirks |= XHCI_COMP_MODE_QUIRK; -- cgit v1.2.3-59-g8ed1b From 943f7fddaa49cc797a2ccdff84f69ae4e6b87d40 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 15 May 2025 16:56:05 +0300 Subject: usb: xhci: move doorbell array pointer assignment Move the assignment of the doorbell array pointer from xhci_mem_init() to xhci_init(). The assignment now utilizes the newly introduced xhci_set_doorbell_ptr() function. Doorbell Array Offset mask (DBOFF_MASK) is updated to directly specify its bit range as 31:2, rather than using inverted reserved bits 1:0. This change simplifies the mask representation, making it more intuitive and easier to understand. Remove the "// " prefix from trace messages, as it is unnecessary and distracting. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250515135621.335595-9-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-caps.h | 4 ++-- drivers/usb/host/xhci-mem.c | 8 -------- drivers/usb/host/xhci.c | 13 +++++++++++++ 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/drivers/usb/host/xhci-caps.h b/drivers/usb/host/xhci-caps.h index f6b9a00a0ab9..4b8ff4815644 100644 --- a/drivers/usb/host/xhci-caps.h +++ b/drivers/usb/host/xhci-caps.h @@ -62,8 +62,8 @@ #define CTX_SIZE(_hcc) (HCC_64BYTE_CONTEXT(_hcc) ? 64 : 32) -/* db_off bitmask - bits 0:1 reserved */ -#define DBOFF_MASK (~0x3) +/* db_off bitmask - bits 31:2 Doorbell Array Offset */ +#define DBOFF_MASK (0xfffffffc) /* run_regs_off bitmask - bits 0:4 reserved */ #define RTSOFF_MASK (~0x1f) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index ac96f0155cab..2f4dbb67b1bf 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2419,7 +2419,6 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) struct xhci_interrupter *ir; struct device *dev = xhci_to_hcd(xhci)->self.sysdev; dma_addr_t dma; - unsigned int val; u32 temp; /* @@ -2496,13 +2495,6 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) */ xhci->cmd_ring_reserved_trbs++; - val = readl(&xhci->cap_regs->db_off); - val &= DBOFF_MASK; - xhci_dbg_trace(xhci, trace_xhci_dbg_init, - "// Doorbell array is located at offset 0x%x from cap regs base addr", - val); - xhci->dba = (void __iomem *) xhci->cap_regs + val; - /* Allocate and set up primary interrupter 0 with an event ring. */ xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Allocating primary event ring"); diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index e8c262865188..0639d8b7372b 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -514,6 +514,16 @@ static void xhci_set_cmd_ring_deq(struct xhci_hcd *xhci) xhci_write_64(xhci, crcr, &xhci->op_regs->cmd_ring); } +static void xhci_set_doorbell_ptr(struct xhci_hcd *xhci) +{ + u32 offset; + + offset = readl(&xhci->cap_regs->db_off) & DBOFF_MASK; + xhci->dba = (void __iomem *)xhci->cap_regs + offset; + xhci_dbg_trace(xhci, trace_xhci_dbg_init, + "Doorbell array is located at offset 0x%x from cap regs base addr", offset); +} + /* * Initialize memory for HCD and xHC (one-time init). * @@ -548,6 +558,9 @@ static int xhci_init(struct usb_hcd *hcd) /* Set Device Context Base Address Array pointer */ xhci_write_64(xhci, xhci->dcbaa->dma, &xhci->op_regs->dcbaa_ptr); + /* Set Doorbell array pointer */ + xhci_set_doorbell_ptr(xhci); + /* Initializing Compliance Mode Recovery Data If Needed */ if (xhci_compliance_mode_recovery_timer_quirk_check()) { xhci->quirks |= XHCI_COMP_MODE_QUIRK; -- cgit v1.2.3-59-g8ed1b From d41031bc8d80116e21e948e2dd38c6c7238165c9 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 15 May 2025 16:56:06 +0300 Subject: usb: xhci: move enabling of USB 3 device notifications Relocated the enabling of USB 3.0 device notifications from xhci_mem_init() to xhci_init(). Introduced xhci_set_dev_notifications() function to handle the notification settings. Simplify 'DEV_NOTE_FWAKE' masks by directly using the 'ENABLE_DEV_NOTE' value (1 << 1) instead of using the 'ENABLE_DEV_NOTE' macro. Macro 'ENABLE_DEV_NOTE' is removed. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250515135621.335595-10-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 10 ---------- drivers/usb/host/xhci.c | 17 +++++++++++++++++ drivers/usb/host/xhci.h | 3 +-- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 2f4dbb67b1bf..718354bd7fb2 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2419,7 +2419,6 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) struct xhci_interrupter *ir; struct device *dev = xhci_to_hcd(xhci)->self.sysdev; dma_addr_t dma; - u32 temp; /* * xHCI section 5.4.6 - Device Context array must be @@ -2515,15 +2514,6 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) if (xhci_setup_port_arrays(xhci, flags)) goto fail; - /* Enable USB 3.0 device notifications for function remote wake, which - * is necessary for allowing USB 3.0 devices to do remote wakeup from - * U3 (device suspend). - */ - temp = readl(&xhci->op_regs->dev_notification); - temp &= ~DEV_NOTE_MASK; - temp |= DEV_NOTE_FWAKE; - writel(temp, &xhci->op_regs->dev_notification); - return 0; fail: diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 0639d8b7372b..fa80cc30c3fe 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -524,6 +524,20 @@ static void xhci_set_doorbell_ptr(struct xhci_hcd *xhci) "Doorbell array is located at offset 0x%x from cap regs base addr", offset); } +/* + * Enable USB 3.0 device notifications for function remote wake, which is necessary + * for allowing USB 3.0 devices to do remote wakeup from U3 (device suspend). + */ +static void xhci_set_dev_notifications(struct xhci_hcd *xhci) +{ + u32 dev_notf; + + dev_notf = readl(&xhci->op_regs->dev_notification); + dev_notf &= ~DEV_NOTE_MASK; + dev_notf |= DEV_NOTE_FWAKE; + writel(dev_notf, &xhci->op_regs->dev_notification); +} + /* * Initialize memory for HCD and xHC (one-time init). * @@ -561,6 +575,9 @@ static int xhci_init(struct usb_hcd *hcd) /* Set Doorbell array pointer */ xhci_set_doorbell_ptr(xhci); + /* Set USB 3.0 device notifications for function remote wake */ + xhci_set_dev_notifications(xhci); + /* Initializing Compliance Mode Recovery Data If Needed */ if (xhci_compliance_mode_recovery_timer_quirk_check()) { xhci->quirks |= XHCI_COMP_MODE_QUIRK; diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 6c1758f8fd01..31d945c4ac07 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -184,11 +184,10 @@ struct xhci_op_regs { * notification type that matches a bit set in this bit field. */ #define DEV_NOTE_MASK (0xffff) -#define ENABLE_DEV_NOTE(x) (1 << (x)) /* Most of the device notification types should only be used for debug. * SW does need to pay attention to function wake notifications. */ -#define DEV_NOTE_FWAKE ENABLE_DEV_NOTE(1) +#define DEV_NOTE_FWAKE (1 << 1) /* CRCR - Command Ring Control Register - cmd_ring bitmasks */ /* bit 0 - Cycle bit indicates the ownership of the command ring */ -- cgit v1.2.3-59-g8ed1b From 0ff49390aad8a6483687fc66f64c8c52b6d6f3a4 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 15 May 2025 16:56:07 +0300 Subject: usb: xhci: remove error handling from xhci_add_interrupter() Remove redundant error handling from xhci_add_interrupter() instead of trying to accommodate them in future changes. ======== Reasoning for the removal ======== Function xhci_add_interrupter() is invoked in two scenarios: Primary Interrupter Setup (ID 0): The maximum number of interrupters is always greater than zero, and the primary interrupter is always allocated as part of the driver's initialization process. In case of failure, the xHCI driver errors and exits. Secondary Interrupter Creation (ID >= 1): The interrupter is pre-allocated, and an empty slot is identified before invoking xhci_add_interrupter(). In both cases, the existing error handling within xhci_add_interrupter() is redundant and unnecessary. Upcoming Changes: In the subsequent commit, interrupter initialization will move from xhci_mem_init() to xhci_init(). This change is necessary to facilitate the ability to restart the xHCI driver without re-allocating memory. As a result, the allocated interrupter must be stored in the interrupters pointer array before initialization. Consequently, xhci_create_secondary_interrupter() would need to handle pointer removal for allocated 'interrupters' array upon failure, although xhci_add_interrupter() will never fail. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250515135621.335595-11-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 718354bd7fb2..bfb01c432b23 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2321,24 +2321,13 @@ xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int segs, gfp_t flags) return ir; } -static int +static void xhci_add_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir, unsigned int intr_num) { u64 erst_base; u32 erst_size; - if (intr_num >= xhci->max_interrupters) { - xhci_warn(xhci, "Can't add interrupter %d, max interrupters %d\n", - intr_num, xhci->max_interrupters); - return -EINVAL; - } - - if (xhci->interrupters[intr_num]) { - xhci_warn(xhci, "Interrupter %d\n already set up", intr_num); - return -EINVAL; - } - xhci->interrupters[intr_num] = ir; ir->intr_num = intr_num; ir->ir_set = &xhci->run_regs->ir_set[intr_num]; @@ -2359,8 +2348,6 @@ xhci_add_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir, /* Set the event ring dequeue address of this interrupter */ xhci_set_hc_event_deq(xhci, ir); - - return 0; } struct xhci_interrupter * @@ -2385,13 +2372,16 @@ xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs, /* Find available secondary interrupter, interrupter 0 is reserved for primary */ for (i = 1; i < xhci->max_interrupters; i++) { if (!xhci->interrupters[i]) { - err = xhci_add_interrupter(xhci, ir, i); + xhci_add_interrupter(xhci, ir, i); + err = 0; break; } } } else { - if (!xhci->interrupters[intr_num]) - err = xhci_add_interrupter(xhci, ir, intr_num); + if (!xhci->interrupters[intr_num]) { + xhci_add_interrupter(xhci, ir, intr_num); + err = 0; + } } spin_unlock_irq(&xhci->lock); @@ -2504,8 +2494,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) if (!ir) goto fail; - if (xhci_add_interrupter(xhci, ir, 0)) - goto fail; + xhci_add_interrupter(xhci, ir, 0); ir->isoc_bei_interval = AVOID_BEI_INTERVAL_MAX; -- cgit v1.2.3-59-g8ed1b From daed871b67abc156edbe099d5a4c573a65f8fc78 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 15 May 2025 16:56:08 +0300 Subject: usb: xhci: move initialization of the primary interrupter Move the primary interrupter (0) initialization from xhci_mem_init() to xhci_init(). This change requires us to save the allocated interrupter somewhere before initialization. Therefore, store it in the 'interrupters' array and rework xhci_add_interrupter() to retrieve the interrupter from the array. This is part of the ongoing effort to separate allocation and initialization. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250515135621.335595-12-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 22 +++++++++------------- drivers/usb/host/xhci.c | 4 ++++ drivers/usb/host/xhci.h | 1 + 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index bfb01c432b23..eb076f5ed1d0 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2321,14 +2321,13 @@ xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int segs, gfp_t flags) return ir; } -static void -xhci_add_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir, - unsigned int intr_num) +void xhci_add_interrupter(struct xhci_hcd *xhci, unsigned int intr_num) { + struct xhci_interrupter *ir; u64 erst_base; u32 erst_size; - xhci->interrupters[intr_num] = ir; + ir = xhci->interrupters[intr_num]; ir->intr_num = intr_num; ir->ir_set = &xhci->run_regs->ir_set[intr_num]; @@ -2372,14 +2371,16 @@ xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs, /* Find available secondary interrupter, interrupter 0 is reserved for primary */ for (i = 1; i < xhci->max_interrupters; i++) { if (!xhci->interrupters[i]) { - xhci_add_interrupter(xhci, ir, i); + xhci->interrupters[i] = ir; + xhci_add_interrupter(xhci, i); err = 0; break; } } } else { if (!xhci->interrupters[intr_num]) { - xhci_add_interrupter(xhci, ir, intr_num); + xhci->interrupters[intr_num] = ir; + xhci_add_interrupter(xhci, intr_num); err = 0; } } @@ -2406,7 +2407,6 @@ EXPORT_SYMBOL_GPL(xhci_create_secondary_interrupter); int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) { - struct xhci_interrupter *ir; struct device *dev = xhci_to_hcd(xhci)->self.sysdev; dma_addr_t dma; @@ -2490,14 +2490,10 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) xhci->interrupters = kcalloc_node(xhci->max_interrupters, sizeof(*xhci->interrupters), flags, dev_to_node(dev)); - ir = xhci_alloc_interrupter(xhci, 0, flags); - if (!ir) + xhci->interrupters[0] = xhci_alloc_interrupter(xhci, 0, flags); + if (!xhci->interrupters[0]) goto fail; - xhci_add_interrupter(xhci, ir, 0); - - ir->isoc_bei_interval = AVOID_BEI_INTERVAL_MAX; - if (scratchpad_alloc(xhci, flags)) goto fail; if (xhci_setup_port_arrays(xhci, flags)) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index fa80cc30c3fe..c6b517401c94 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -578,6 +578,10 @@ static int xhci_init(struct usb_hcd *hcd) /* Set USB 3.0 device notifications for function remote wake */ xhci_set_dev_notifications(xhci); + /* Initialize the Primary interrupter */ + xhci_add_interrupter(xhci, 0); + xhci->interrupters[0]->isoc_bei_interval = AVOID_BEI_INTERVAL_MAX; + /* Initializing Compliance Mode Recovery Data If Needed */ if (xhci_compliance_mode_recovery_timer_quirk_check()) { xhci->quirks |= XHCI_COMP_MODE_QUIRK; diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 31d945c4ac07..b0b16cd7df91 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1959,6 +1959,7 @@ void xhci_process_cancelled_tds(struct xhci_virt_ep *ep); void xhci_update_erst_dequeue(struct xhci_hcd *xhci, struct xhci_interrupter *ir, bool clear_ehb); +void xhci_add_interrupter(struct xhci_hcd *xhci, unsigned int intr_num); /* xHCI roothub code */ void xhci_set_link_state(struct xhci_hcd *xhci, struct xhci_port *port, -- cgit v1.2.3-59-g8ed1b From 83d98dea48eb61667465583022ae03b0c4056aa1 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 15 May 2025 16:56:09 +0300 Subject: usb: xhci: add individual allocation checks in xhci_mem_init() Break up the existing multi-allocation checks into individual checks. Add missing allocation check for 'xhci->interrupters'. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250515135621.335595-13-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index eb076f5ed1d0..8cadd785ac0e 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2437,11 +2437,13 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) else xhci->segment_pool = dma_pool_create("xHCI ring segments", dev, TRB_SEGMENT_SIZE, TRB_SEGMENT_SIZE, xhci->page_size); + if (!xhci->segment_pool) + goto fail; /* See Table 46 and Note on Figure 55 */ xhci->device_pool = dma_pool_create("xHCI input/output contexts", dev, 2112, 64, xhci->page_size); - if (!xhci->segment_pool || !xhci->device_pool) + if (!xhci->device_pool) goto fail; /* Linear stream context arrays don't have any boundary restrictions, @@ -2450,12 +2452,17 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) xhci->small_streams_pool = dma_pool_create("xHCI 256 byte stream ctx arrays", dev, SMALL_STREAM_ARRAY_SIZE, 16, 0); + if (!xhci->small_streams_pool) + goto fail; + xhci->medium_streams_pool = dma_pool_create("xHCI 1KB stream ctx arrays", dev, MEDIUM_STREAM_ARRAY_SIZE, 16, 0); /* Any stream context array bigger than MEDIUM_STREAM_ARRAY_SIZE * will be allocated with dma_alloc_coherent() */ + if (!xhci->medium_streams_pool) + goto fail; /* refer to xhci rev1_2 protocol 5.3.3 max ports is 255. * refer to xhci rev1_2 protocol 6.4.3.14 port bandwidth buffer need @@ -2464,9 +2471,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) xhci->port_bw_pool = dma_pool_create("xHCI 256 port bw ctx arrays", dev, GET_PORT_BW_ARRAY_SIZE, 16, 0); - - if (!xhci->small_streams_pool || !xhci->medium_streams_pool || - !xhci->port_bw_pool) + if (!xhci->port_bw_pool) goto fail; /* Set up the command ring to have one segments for now. */ @@ -2489,6 +2494,8 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) "Allocating primary event ring"); xhci->interrupters = kcalloc_node(xhci->max_interrupters, sizeof(*xhci->interrupters), flags, dev_to_node(dev)); + if (!xhci->interrupters) + goto fail; xhci->interrupters[0] = xhci_alloc_interrupter(xhci, 0, flags); if (!xhci->interrupters[0]) -- cgit v1.2.3-59-g8ed1b From 3d5b8a0e0af4df8fe70b244a5d84a559b6ad5558 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 15 May 2025 16:56:10 +0300 Subject: usb: xhci: cleanup xhci_mem_init() Cleanup indentation, spacing and comment formats. Remove the "// " prefix from trace messages, as it is unnecessary and distracting. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250515135621.335595-14-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 52 +++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 8cadd785ac0e..08513e5d321a 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2414,14 +2414,14 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) * xHCI section 5.4.6 - Device Context array must be * "physically contiguous and 64-byte (cache line) aligned". */ - xhci->dcbaa = dma_alloc_coherent(dev, sizeof(*xhci->dcbaa), &dma, - flags); + xhci->dcbaa = dma_alloc_coherent(dev, sizeof(*xhci->dcbaa), &dma, flags); if (!xhci->dcbaa) goto fail; + xhci->dcbaa->dma = dma; xhci_dbg_trace(xhci, trace_xhci_dbg_init, - "// Device context base array address = 0x%pad (DMA), %p (virt)", - &xhci->dcbaa->dma, xhci->dcbaa); + "Device context base array address = 0x%pad (DMA), %p (virt)", + &xhci->dcbaa->dma, xhci->dcbaa); /* * Initialize the ring segment pool. The ring must be a contiguous @@ -2441,36 +2441,37 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) goto fail; /* See Table 46 and Note on Figure 55 */ - xhci->device_pool = dma_pool_create("xHCI input/output contexts", dev, - 2112, 64, xhci->page_size); + xhci->device_pool = dma_pool_create("xHCI input/output contexts", dev, 2112, 64, + xhci->page_size); if (!xhci->device_pool) goto fail; - /* Linear stream context arrays don't have any boundary restrictions, + /* + * Linear stream context arrays don't have any boundary restrictions, * and only need to be 16-byte aligned. */ - xhci->small_streams_pool = - dma_pool_create("xHCI 256 byte stream ctx arrays", - dev, SMALL_STREAM_ARRAY_SIZE, 16, 0); + xhci->small_streams_pool = dma_pool_create("xHCI 256 byte stream ctx arrays", + dev, SMALL_STREAM_ARRAY_SIZE, 16, 0); if (!xhci->small_streams_pool) goto fail; - xhci->medium_streams_pool = - dma_pool_create("xHCI 1KB stream ctx arrays", - dev, MEDIUM_STREAM_ARRAY_SIZE, 16, 0); - /* Any stream context array bigger than MEDIUM_STREAM_ARRAY_SIZE - * will be allocated with dma_alloc_coherent() + /* + * Any stream context array bigger than MEDIUM_STREAM_ARRAY_SIZE will be + * allocated with dma_alloc_coherent(). */ + + xhci->medium_streams_pool = dma_pool_create("xHCI 1KB stream ctx arrays", + dev, MEDIUM_STREAM_ARRAY_SIZE, 16, 0); if (!xhci->medium_streams_pool) goto fail; - /* refer to xhci rev1_2 protocol 5.3.3 max ports is 255. + /* + * refer to xhci rev1_2 protocol 5.3.3 max ports is 255. * refer to xhci rev1_2 protocol 6.4.3.14 port bandwidth buffer need * to be 16-byte aligned. */ - xhci->port_bw_pool = - dma_pool_create("xHCI 256 port bw ctx arrays", - dev, GET_PORT_BW_ARRAY_SIZE, 16, 0); + xhci->port_bw_pool = dma_pool_create("xHCI 256 port bw ctx arrays", + dev, GET_PORT_BW_ARRAY_SIZE, 16, 0); if (!xhci->port_bw_pool) goto fail; @@ -2478,20 +2479,20 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) xhci->cmd_ring = xhci_ring_alloc(xhci, 1, TYPE_COMMAND, 0, flags); if (!xhci->cmd_ring) goto fail; - xhci_dbg_trace(xhci, trace_xhci_dbg_init, - "Allocated command ring at %p", xhci->cmd_ring); + + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Allocated command ring at %p", xhci->cmd_ring); xhci_dbg_trace(xhci, trace_xhci_dbg_init, "First segment DMA is 0x%pad", - &xhci->cmd_ring->first_seg->dma); + &xhci->cmd_ring->first_seg->dma); - /* Reserve one command ring TRB for disabling LPM. + /* + * Reserve one command ring TRB for disabling LPM. * Since the USB core grabs the shared usb_bus bandwidth mutex before * disabling LPM, we only need to reserve one TRB for all devices. */ xhci->cmd_ring_reserved_trbs++; /* Allocate and set up primary interrupter 0 with an event ring. */ - xhci_dbg_trace(xhci, trace_xhci_dbg_init, - "Allocating primary event ring"); + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Allocating primary event ring"); xhci->interrupters = kcalloc_node(xhci->max_interrupters, sizeof(*xhci->interrupters), flags, dev_to_node(dev)); if (!xhci->interrupters) @@ -2503,6 +2504,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) if (scratchpad_alloc(xhci, flags)) goto fail; + if (xhci_setup_port_arrays(xhci, flags)) goto fail; -- cgit v1.2.3-59-g8ed1b From 1fdeb069053f65e974c6fd945c85fcb854e6c199 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 15 May 2025 16:56:11 +0300 Subject: usb: xhci: set requested IMODI to the closest supported value The function configures the Interrupt Moderation Interval (IMODI) via bits 15:0 in the Interrupt Moderation Register. The IMODI value is specified in increments of 250 nanoseconds. For instance, an IMODI register value of 16 corresponds to 4000 nanoseconds, resulting in an interrupt every ~1ms. Currently, the function fails when a requested IMODI value is too large, only logging a warning message for secondary interrupters. Prevent this by automatically adjusting the IMODI value to the nearest supported value. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250515135621.335595-15-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 5 +---- drivers/usb/host/xhci.c | 7 +++++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 08513e5d321a..dcfe7774e9ed 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2393,10 +2393,7 @@ xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs, return NULL; } - err = xhci_set_interrupter_moderation(ir, imod_interval); - if (err) - xhci_warn(xhci, "Failed to set interrupter %d moderation to %uns\n", - i, imod_interval); + xhci_set_interrupter_moderation(ir, imod_interval); xhci_dbg(xhci, "Add secondary interrupter %d, max interrupters %d\n", ir->intr_num, xhci->max_interrupters); diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index c6b517401c94..c3a1a67b6563 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -355,12 +355,15 @@ int xhci_set_interrupter_moderation(struct xhci_interrupter *ir, { u32 imod; - if (!ir || !ir->ir_set || imod_interval > U16_MAX * 250) + if (!ir || !ir->ir_set) return -EINVAL; + /* IMODI value in IMOD register is in 250ns increments */ + imod_interval = umin(imod_interval / 250, ER_IRQ_INTERVAL_MASK); + imod = readl(&ir->ir_set->irq_control); imod &= ~ER_IRQ_INTERVAL_MASK; - imod |= (imod_interval / 250) & ER_IRQ_INTERVAL_MASK; + imod |= imod_interval; writel(imod, &ir->ir_set->irq_control); return 0; -- cgit v1.2.3-59-g8ed1b From 9f7f74735ac295c9c32ac9152c362414e18d35cc Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 15 May 2025 16:56:12 +0300 Subject: usb: xhci: improve Interrupt Management register macros The Interrupt Management register (IMAN), contains three fields: - Bit 0: Interrupt Pending (IP) - Bit 1: Interrupt Enable (IE) - Bits 31:2: RsvdP (Reserved and Preserved) Currently, there are multiple macros for both the IP and IE fields. Consolidates them into single mask macros for better clarity and maintainability. Comment "THIS IS BUGGY - FIXME - IP IS WRITE 1 TO CLEAR" refers to the fact that both macros 'ER_IRQ_ENABLE' and 'ER_IRQ_DISABLE' clear the IP bit by writing '0' before modifying the IE bit. However, the IP bit is actually cleared by writing '1'. To prevent any regression, this behavior has not been altered. Instead, when the IE bit is modified, the IP macro is used explicitly to highlight this "quirk". Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250515135621.335595-16-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.c | 8 ++++++-- drivers/usb/host/xhci.h | 14 ++++---------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index c3a1a67b6563..472589679af3 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -331,7 +331,9 @@ int xhci_enable_interrupter(struct xhci_interrupter *ir) return -EINVAL; iman = readl(&ir->ir_set->irq_pending); - writel(ER_IRQ_ENABLE(iman), &ir->ir_set->irq_pending); + iman &= ~IMAN_IP; + iman |= IMAN_IE; + writel(iman, &ir->ir_set->irq_pending); return 0; } @@ -344,7 +346,9 @@ int xhci_disable_interrupter(struct xhci_interrupter *ir) return -EINVAL; iman = readl(&ir->ir_set->irq_pending); - writel(ER_IRQ_DISABLE(iman), &ir->ir_set->irq_pending); + iman &= ~IMAN_IP; + iman &= ~IMAN_IE; + writel(iman, &ir->ir_set->irq_pending); return 0; } diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index b0b16cd7df91..28c4ad7534c1 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -152,10 +152,6 @@ struct xhci_op_regs { #define XHCI_RESET_LONG_USEC (10 * 1000 * 1000) #define XHCI_RESET_SHORT_USEC (250 * 1000) -/* IMAN - Interrupt Management Register */ -#define IMAN_IE (1 << 1) -#define IMAN_IP (1 << 0) - /* USBSTS - USB status - status bitmasks */ /* HC not running - set to 1 when run/stop bit is cleared. */ #define STS_HALT XHCI_STS_HALT @@ -240,12 +236,10 @@ struct xhci_intr_reg { }; /* irq_pending bitmasks */ -#define ER_IRQ_PENDING(p) ((p) & 0x1) -/* bits 2:31 need to be preserved */ -/* THIS IS BUGGY - FIXME - IP IS WRITE 1 TO CLEAR */ -#define ER_IRQ_CLEAR(p) ((p) & 0xfffffffe) -#define ER_IRQ_ENABLE(p) ((ER_IRQ_CLEAR(p)) | 0x2) -#define ER_IRQ_DISABLE(p) ((ER_IRQ_CLEAR(p)) & ~(0x2)) +/* bit 0 - Interrupt Pending (IP), whether there is an interrupt pending. Write-1-to-clear. */ +#define IMAN_IP (1 << 0) +/* bit 1 - Interrupt Enable (IE), whether the interrupter is capable of generating an interrupt */ +#define IMAN_IE (1 << 1) /* irq_control bitmasks */ /* Minimum interval between interrupts (in 250ns intervals). The interval -- cgit v1.2.3-59-g8ed1b From f5bce30ad25e74899c54dda01c80ca6e2e7dc01b Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 15 May 2025 16:56:13 +0300 Subject: usb: xhci: guarantee that IMAN register is flushed Add read call to guarantee that the write to the IMAN register has been flushed. xHCI specification 1.2, section 5.5.2.1, Note: "Most systems have write buffers that minimize overhead, but this may require a read operation to guarantee that the write has been flushed from the posted buffer." Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250515135621.335595-17-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 3 +++ drivers/usb/host/xhci.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index d2002ecd27b0..4a4410f7978f 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -3087,6 +3087,9 @@ static void xhci_clear_interrupt_pending(struct xhci_interrupter *ir) irq_pending = readl(&ir->ir_set->irq_pending); irq_pending |= IMAN_IP; writel(irq_pending, &ir->ir_set->irq_pending); + + /* Read operation to guarantee the write has been flushed from posted buffers */ + readl(&ir->ir_set->irq_pending); } } diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 472589679af3..8cdb1a01a3ed 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -335,6 +335,8 @@ int xhci_enable_interrupter(struct xhci_interrupter *ir) iman |= IMAN_IE; writel(iman, &ir->ir_set->irq_pending); + /* Read operation to guarantee the write has been flushed from posted buffers */ + readl(&ir->ir_set->irq_pending); return 0; } @@ -350,6 +352,7 @@ int xhci_disable_interrupter(struct xhci_interrupter *ir) iman &= ~IMAN_IE; writel(iman, &ir->ir_set->irq_pending); + readl(&ir->ir_set->irq_pending); return 0; } -- cgit v1.2.3-59-g8ed1b From e1db856bd28891d70008880d7f1d3b8d1ea948fd Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 15 May 2025 16:56:14 +0300 Subject: usb: xhci: remove '0' write to write-1-to-clear register xHCI specification 1.2, section 5.5.2.1. Interrupt Pending bit is RW1C (Write-1-to-clear), which means that writing '0' to is has no effect and is removed. The Interrupt Pending (IP) bit is cleared at the start of interrupt handling; xhci_clear_interrupt_pending(). This could theoretically cause a new interrupt to be issued before the xhci driver reaches the interrupter disable functions. To address this, the IP bit is read after Interrupt Enable is disabled, and a debug message is issued if the IP bit is still set. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250515135621.335595-18-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 2 +- drivers/usb/host/xhci-ring.c | 2 +- drivers/usb/host/xhci.c | 13 +++++++------ drivers/usb/host/xhci.h | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 486347776cb2..92bb84f8132a 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -1907,7 +1907,7 @@ int xhci_bus_resume(struct usb_hcd *hcd) * prevent port event interrupts from interfering * with usb2 port resume process */ - xhci_disable_interrupter(xhci->interrupters[0]); + xhci_disable_interrupter(xhci, xhci->interrupters[0]); disabled_irq = true; } } diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 4a4410f7978f..1cae4ec6c7e9 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -3166,7 +3166,7 @@ void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, dma_addr_t deq; /* disable irq, ack pending interrupt and ack all pending events */ - xhci_disable_interrupter(ir); + xhci_disable_interrupter(xhci, ir); /* last acked event trb is in erdp reg */ erdp_reg = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 8cdb1a01a3ed..6c4bbabc3a70 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -331,7 +331,6 @@ int xhci_enable_interrupter(struct xhci_interrupter *ir) return -EINVAL; iman = readl(&ir->ir_set->irq_pending); - iman &= ~IMAN_IP; iman |= IMAN_IE; writel(iman, &ir->ir_set->irq_pending); @@ -340,7 +339,7 @@ int xhci_enable_interrupter(struct xhci_interrupter *ir) return 0; } -int xhci_disable_interrupter(struct xhci_interrupter *ir) +int xhci_disable_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir) { u32 iman; @@ -348,11 +347,13 @@ int xhci_disable_interrupter(struct xhci_interrupter *ir) return -EINVAL; iman = readl(&ir->ir_set->irq_pending); - iman &= ~IMAN_IP; iman &= ~IMAN_IE; writel(iman, &ir->ir_set->irq_pending); - readl(&ir->ir_set->irq_pending); + iman = readl(&ir->ir_set->irq_pending); + if (iman & IMAN_IP) + xhci_dbg(xhci, "%s: Interrupt pending\n", __func__); + return 0; } @@ -754,7 +755,7 @@ void xhci_stop(struct usb_hcd *hcd) "// Disabling event ring interrupts"); temp = readl(&xhci->op_regs->status); writel((temp & ~0x1fff) | STS_EINT, &xhci->op_regs->status); - xhci_disable_interrupter(ir); + xhci_disable_interrupter(xhci, ir); xhci_dbg_trace(xhci, trace_xhci_dbg_init, "cleaning up memory"); xhci_mem_cleanup(xhci); @@ -1189,7 +1190,7 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume) xhci_dbg(xhci, "// Disabling event ring interrupts\n"); temp = readl(&xhci->op_regs->status); writel((temp & ~0x1fff) | STS_EINT, &xhci->op_regs->status); - xhci_disable_interrupter(xhci->interrupters[0]); + xhci_disable_interrupter(xhci, xhci->interrupters[0]); xhci_dbg(xhci, "cleaning up memory\n"); xhci_mem_cleanup(xhci); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 28c4ad7534c1..fc6b97add7fa 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1900,7 +1900,7 @@ int xhci_alloc_tt_info(struct xhci_hcd *xhci, int xhci_set_interrupter_moderation(struct xhci_interrupter *ir, u32 imod_interval); int xhci_enable_interrupter(struct xhci_interrupter *ir); -int xhci_disable_interrupter(struct xhci_interrupter *ir); +int xhci_disable_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir); /* xHCI ring, segment, TRB, and TD functions */ dma_addr_t xhci_trb_virt_to_dma(struct xhci_segment *seg, union xhci_trb *trb); -- cgit v1.2.3-59-g8ed1b From 7c6df26c3be7b1806cd2f2aad8fd4a5329d9145d Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 15 May 2025 16:56:15 +0300 Subject: usb: xhci: rework Event Ring Segment Table Size mask Event Ring Segment Table Size Register contain two fields: - Bits 15:0: Event Ring Segment Table Size - Bits 31:16: RsvdZ (Reserved and Zero) The current mask 'ERST_SIZE_MASK' refers to the RsvdZ bits (31:16). Change the mask to refer to bits 15:0, which are the Event Ring Segment Table Size bits. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250515135621.335595-19-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 4 ++-- drivers/usb/host/xhci.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index dcfe7774e9ed..ec2c4851c689 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1831,7 +1831,7 @@ xhci_remove_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir) */ if (ir->ir_set) { tmp = readl(&ir->ir_set->erst_size); - tmp &= ERST_SIZE_MASK; + tmp &= ~ERST_SIZE_MASK; writel(tmp, &ir->ir_set->erst_size); xhci_update_erst_dequeue(xhci, ir, true); @@ -2333,7 +2333,7 @@ void xhci_add_interrupter(struct xhci_hcd *xhci, unsigned int intr_num) /* set ERST count with the number of entries in the segment table */ erst_size = readl(&ir->ir_set->erst_size); - erst_size &= ERST_SIZE_MASK; + erst_size &= ~ERST_SIZE_MASK; erst_size |= ir->event_ring->num_segs; writel(erst_size, &ir->ir_set->erst_size); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index fc6b97add7fa..19dd47d76140 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -251,8 +251,8 @@ struct xhci_intr_reg { #define ER_IRQ_COUNTER_MASK (0xffff << 16) /* erst_size bitmasks */ -/* Preserve bits 16:31 of erst_size */ -#define ERST_SIZE_MASK (0xffff << 16) +/* bits 15:0 - Event Ring Segment Table Size, number of ERST entries */ +#define ERST_SIZE_MASK (0xffff) /* erst_base bitmasks */ #define ERST_BASE_RSVDP (GENMASK_ULL(5, 0)) -- cgit v1.2.3-59-g8ed1b From 74d7a757e8bcb753850c5e379c087c8424528f6a Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 15 May 2025 16:56:16 +0300 Subject: usb: xhci: rework Event Ring Segment Table Address mask Event Ring Segment Table Base Address Register contain two fields: - Bits 5:0: RsvdP (Reserved and Preserved) - Bits 63:6: Event Ring Segment Table Base Address Currently, an inverted RsvdP mask (ERST_BASE_RSVDP) is used to extract bits 63:6. Replaces the inverted mask with a non-inverted mask, 'ERST_BASE_ADDRESS_MASK', which makes the code easier to read. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250515135621.335595-20-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 4 ++-- drivers/usb/host/xhci.h | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index ec2c4851c689..bd745a0f2f78 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2338,8 +2338,8 @@ void xhci_add_interrupter(struct xhci_hcd *xhci, unsigned int intr_num) writel(erst_size, &ir->ir_set->erst_size); erst_base = xhci_read_64(xhci, &ir->ir_set->erst_base); - erst_base &= ERST_BASE_RSVDP; - erst_base |= ir->erst.erst_dma_addr & ~ERST_BASE_RSVDP; + erst_base &= ~ERST_BASE_ADDRESS_MASK; + erst_base |= ir->erst.erst_dma_addr & ERST_BASE_ADDRESS_MASK; if (xhci->quirks & XHCI_WRITE_64_HI_LO) hi_lo_writeq(erst_base, &ir->ir_set->erst_base); else diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 19dd47d76140..7865e21f0b1f 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -255,7 +255,8 @@ struct xhci_intr_reg { #define ERST_SIZE_MASK (0xffff) /* erst_base bitmasks */ -#define ERST_BASE_RSVDP (GENMASK_ULL(5, 0)) +/* bits 63:6 - Event Ring Segment Table Base Address Register */ +#define ERST_BASE_ADDRESS_MASK GENMASK_ULL(63, 6) /* erst_dequeue bitmasks */ /* Dequeue ERST Segment Index (DESI) - Segment number (or alias) -- cgit v1.2.3-59-g8ed1b From 5f5816d190c1adaf41efd5c2ca880c53adc0e9d3 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 15 May 2025 16:56:17 +0300 Subject: usb: xhci: cleanup IMOD register comments Patch does not contain any functional changes. Add missing macro descriptions with specific bit definitions for each data field and reordered them accordingly. Remove "HW use only" from Interrupt Moderation Counter. xHCI Specification 1.2, section 5.5.2.2, states "This counter may be directly written by software at any time to alter the interrupt rate." Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250515135621.335595-21-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.h | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 7865e21f0b1f..4a4ce6784bf0 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -210,14 +210,13 @@ struct xhci_op_regs { #define XHCI_PAGE_SIZE_MASK 0xffff /** - * struct xhci_intr_reg - Interrupt Register Set - * @irq_pending: IMAN - Interrupt Management Register. Used to enable + * struct xhci_intr_reg - Interrupt Register Set, v1.2 section 5.5.2. + * @irq_pending: IMAN - Interrupt Management Register. Used to enable * interrupts and check for pending interrupts. - * @irq_control: IMOD - Interrupt Moderation Register. - * Used to throttle interrupts. - * @erst_size: Number of segments in the Event Ring Segment Table (ERST). - * @erst_base: ERST base address. - * @erst_dequeue: Event ring dequeue pointer. + * @irq_control: IMOD - Interrupt Moderation Register. Used to throttle interrupts. + * @erst_size: ERSTSZ - Number of segments in the Event Ring Segment Table (ERST). + * @erst_base: ERSTBA - Event ring segment table base address. + * @erst_dequeue: ERDP - Event ring dequeue pointer. * * Each interrupter (defined by a MSI-X vector) has an event ring and an Event * Ring Segment Table (ERST) associated with it. The event ring is comprised of @@ -242,12 +241,13 @@ struct xhci_intr_reg { #define IMAN_IE (1 << 1) /* irq_control bitmasks */ -/* Minimum interval between interrupts (in 250ns intervals). The interval - * between interrupts will be longer if there are no events on the event ring. - * Default is 4000 (1 ms). +/* + * bits 15:0 - Interrupt Moderation Interval, the minimum interval between interrupts + * (in 250ns intervals). The interval between interrupts will be longer if there are no + * events on the event ring. Default is 4000 (1 ms). */ #define ER_IRQ_INTERVAL_MASK (0xffff) -/* Counter used to count down the time to the next interrupt - HW use only */ +/* bits 31:16 - Interrupt Moderation Counter, used to count down the time to the next interrupt */ #define ER_IRQ_COUNTER_MASK (0xffff << 16) /* erst_size bitmasks */ @@ -259,15 +259,18 @@ struct xhci_intr_reg { #define ERST_BASE_ADDRESS_MASK GENMASK_ULL(63, 6) /* erst_dequeue bitmasks */ -/* Dequeue ERST Segment Index (DESI) - Segment number (or alias) - * where the current dequeue pointer lies. This is an optional HW hint. +/* + * bits 2:0 - Dequeue ERST Segment Index (DESI), is the segment number (or alias) where the + * current dequeue pointer lies. This is an optional HW hint. */ #define ERST_DESI_MASK (0x7) -/* Event Handler Busy (EHB) - is the event ring scheduled to be serviced by +/* + * bit 3 - Event Handler Busy (EHB), whether the event ring is scheduled to be serviced by * a work queue (or delayed service routine)? */ #define ERST_EHB (1 << 3) -#define ERST_PTR_MASK (GENMASK_ULL(63, 4)) +/* bits 63:4 - Event Ring Dequeue Pointer */ +#define ERST_PTR_MASK GENMASK_ULL(63, 4) /** * struct xhci_run_regs -- cgit v1.2.3-59-g8ed1b From bf9cce90da311aae5723681d74495099e417e8c7 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 15 May 2025 16:56:18 +0300 Subject: usb: xhci: rename 'irq_pending' to 'iman' The Interrupt Register Set contains Interrupt Management register (IMAN). The IMAN register contains the following fields: - Bit 0: Interrupt Pending (IP) - Bit 1: Interrupt Enable (IE) - Bits 31:2: RsvdP (Reserved and Preserved) Tn the xhci driver, the pointer currently named 'irq_pending' refers to the IMAN register. However, the name "irq_pending" only describes one of the fields within the IMAN register, rather than the entire register itself. To improve clarity and better align with the xHCI specification, the pointer is renamed to 'iman'. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250515135621.335595-22-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 10 +++++----- drivers/usb/host/xhci.c | 16 ++++++++-------- drivers/usb/host/xhci.h | 8 ++++---- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 1cae4ec6c7e9..e038ad3375dc 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -3082,14 +3082,14 @@ void xhci_update_erst_dequeue(struct xhci_hcd *xhci, static void xhci_clear_interrupt_pending(struct xhci_interrupter *ir) { if (!ir->ip_autoclear) { - u32 irq_pending; + u32 iman; - irq_pending = readl(&ir->ir_set->irq_pending); - irq_pending |= IMAN_IP; - writel(irq_pending, &ir->ir_set->irq_pending); + iman = readl(&ir->ir_set->iman); + iman |= IMAN_IP; + writel(iman, &ir->ir_set->iman); /* Read operation to guarantee the write has been flushed from posted buffers */ - readl(&ir->ir_set->irq_pending); + readl(&ir->ir_set->iman); } } diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 6c4bbabc3a70..3450762fc7bd 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -330,12 +330,12 @@ int xhci_enable_interrupter(struct xhci_interrupter *ir) if (!ir || !ir->ir_set) return -EINVAL; - iman = readl(&ir->ir_set->irq_pending); + iman = readl(&ir->ir_set->iman); iman |= IMAN_IE; - writel(iman, &ir->ir_set->irq_pending); + writel(iman, &ir->ir_set->iman); /* Read operation to guarantee the write has been flushed from posted buffers */ - readl(&ir->ir_set->irq_pending); + readl(&ir->ir_set->iman); return 0; } @@ -346,11 +346,11 @@ int xhci_disable_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir) if (!ir || !ir->ir_set) return -EINVAL; - iman = readl(&ir->ir_set->irq_pending); + iman = readl(&ir->ir_set->iman); iman &= ~IMAN_IE; - writel(iman, &ir->ir_set->irq_pending); + writel(iman, &ir->ir_set->iman); - iman = readl(&ir->ir_set->irq_pending); + iman = readl(&ir->ir_set->iman); if (iman & IMAN_IP) xhci_dbg(xhci, "%s: Interrupt pending\n", __func__); @@ -834,7 +834,7 @@ static void xhci_save_registers(struct xhci_hcd *xhci) ir->s3_erst_size = readl(&ir->ir_set->erst_size); ir->s3_erst_base = xhci_read_64(xhci, &ir->ir_set->erst_base); ir->s3_erst_dequeue = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); - ir->s3_irq_pending = readl(&ir->ir_set->irq_pending); + ir->s3_iman = readl(&ir->ir_set->iman); ir->s3_irq_control = readl(&ir->ir_set->irq_control); } } @@ -858,7 +858,7 @@ static void xhci_restore_registers(struct xhci_hcd *xhci) writel(ir->s3_erst_size, &ir->ir_set->erst_size); xhci_write_64(xhci, ir->s3_erst_base, &ir->ir_set->erst_base); xhci_write_64(xhci, ir->s3_erst_dequeue, &ir->ir_set->erst_dequeue); - writel(ir->s3_irq_pending, &ir->ir_set->irq_pending); + writel(ir->s3_iman, &ir->ir_set->iman); writel(ir->s3_irq_control, &ir->ir_set->irq_control); } } diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 4a4ce6784bf0..62d12d23617f 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -211,7 +211,7 @@ struct xhci_op_regs { /** * struct xhci_intr_reg - Interrupt Register Set, v1.2 section 5.5.2. - * @irq_pending: IMAN - Interrupt Management Register. Used to enable + * @iman: IMAN - Interrupt Management Register. Used to enable * interrupts and check for pending interrupts. * @irq_control: IMOD - Interrupt Moderation Register. Used to throttle interrupts. * @erst_size: ERSTSZ - Number of segments in the Event Ring Segment Table (ERST). @@ -226,7 +226,7 @@ struct xhci_op_regs { * updates the dequeue pointer. */ struct xhci_intr_reg { - __le32 irq_pending; + __le32 iman; __le32 irq_control; __le32 erst_size; __le32 rsvd; @@ -234,7 +234,7 @@ struct xhci_intr_reg { __le64 erst_dequeue; }; -/* irq_pending bitmasks */ +/* iman bitmasks */ /* bit 0 - Interrupt Pending (IP), whether there is an interrupt pending. Write-1-to-clear. */ #define IMAN_IP (1 << 0) /* bit 1 - Interrupt Enable (IE), whether the interrupter is capable of generating an interrupt */ @@ -1452,7 +1452,7 @@ struct xhci_interrupter { bool ip_autoclear; u32 isoc_bei_interval; /* For interrupter registers save and restore over suspend/resume */ - u32 s3_irq_pending; + u32 s3_iman; u32 s3_irq_control; u32 s3_erst_size; u64 s3_erst_base; -- cgit v1.2.3-59-g8ed1b From f27c6da58f1129609a7de66e0034bde70f1349f4 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 15 May 2025 16:56:19 +0300 Subject: usb: xhci: rename 'irq_control' to 'imod' The Interrupt Register Set contains Interrupt Moderation register (IMOD). The IMOD register contains the following fields: - Bits 15:0: Interrupt Moderation Interval (IMODI) - Bits 31:16: Interrupt Moderation Counter (IMODC) In the xHCI driver, the pointer currently named 'irq_control' refers to the IMOD register. However, the name 'irq_control' does not accurately describe the register or its contents, and the xHCI specification does not use the term "irq control" or "interrupt control" for this register. To improve clarity and better align with the xHCI specification, the pointer is renamed to 'imod'. Additionally, the IMOD register fields IMODI & IMODC have their own masks, which are also renamed for consistency: * 'ER_IRQ_INTERVAL_MASK' -> 'IMODI_MASK' * 'ER_IRQ_COUNTER_MASK' -> 'IMODC_MASK' Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250515135621.335595-23-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.c | 12 ++++++------ drivers/usb/host/xhci.h | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 3450762fc7bd..9769c68b2e9f 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -367,12 +367,12 @@ int xhci_set_interrupter_moderation(struct xhci_interrupter *ir, return -EINVAL; /* IMODI value in IMOD register is in 250ns increments */ - imod_interval = umin(imod_interval / 250, ER_IRQ_INTERVAL_MASK); + imod_interval = umin(imod_interval / 250, IMODI_MASK); - imod = readl(&ir->ir_set->irq_control); - imod &= ~ER_IRQ_INTERVAL_MASK; + imod = readl(&ir->ir_set->imod); + imod &= ~IMODI_MASK; imod |= imod_interval; - writel(imod, &ir->ir_set->irq_control); + writel(imod, &ir->ir_set->imod); return 0; } @@ -835,7 +835,7 @@ static void xhci_save_registers(struct xhci_hcd *xhci) ir->s3_erst_base = xhci_read_64(xhci, &ir->ir_set->erst_base); ir->s3_erst_dequeue = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); ir->s3_iman = readl(&ir->ir_set->iman); - ir->s3_irq_control = readl(&ir->ir_set->irq_control); + ir->s3_imod = readl(&ir->ir_set->imod); } } @@ -859,7 +859,7 @@ static void xhci_restore_registers(struct xhci_hcd *xhci) xhci_write_64(xhci, ir->s3_erst_base, &ir->ir_set->erst_base); xhci_write_64(xhci, ir->s3_erst_dequeue, &ir->ir_set->erst_dequeue); writel(ir->s3_iman, &ir->ir_set->iman); - writel(ir->s3_irq_control, &ir->ir_set->irq_control); + writel(ir->s3_imod, &ir->ir_set->imod); } } diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 62d12d23617f..49887a303e43 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -213,7 +213,7 @@ struct xhci_op_regs { * struct xhci_intr_reg - Interrupt Register Set, v1.2 section 5.5.2. * @iman: IMAN - Interrupt Management Register. Used to enable * interrupts and check for pending interrupts. - * @irq_control: IMOD - Interrupt Moderation Register. Used to throttle interrupts. + * @imod: IMOD - Interrupt Moderation Register. Used to throttle interrupts. * @erst_size: ERSTSZ - Number of segments in the Event Ring Segment Table (ERST). * @erst_base: ERSTBA - Event ring segment table base address. * @erst_dequeue: ERDP - Event ring dequeue pointer. @@ -227,7 +227,7 @@ struct xhci_op_regs { */ struct xhci_intr_reg { __le32 iman; - __le32 irq_control; + __le32 imod; __le32 erst_size; __le32 rsvd; __le64 erst_base; @@ -240,15 +240,15 @@ struct xhci_intr_reg { /* bit 1 - Interrupt Enable (IE), whether the interrupter is capable of generating an interrupt */ #define IMAN_IE (1 << 1) -/* irq_control bitmasks */ +/* imod bitmasks */ /* * bits 15:0 - Interrupt Moderation Interval, the minimum interval between interrupts * (in 250ns intervals). The interval between interrupts will be longer if there are no * events on the event ring. Default is 4000 (1 ms). */ -#define ER_IRQ_INTERVAL_MASK (0xffff) +#define IMODI_MASK (0xffff) /* bits 31:16 - Interrupt Moderation Counter, used to count down the time to the next interrupt */ -#define ER_IRQ_COUNTER_MASK (0xffff << 16) +#define IMODC_MASK (0xffff << 16) /* erst_size bitmasks */ /* bits 15:0 - Event Ring Segment Table Size, number of ERST entries */ @@ -1453,7 +1453,7 @@ struct xhci_interrupter { u32 isoc_bei_interval; /* For interrupter registers save and restore over suspend/resume */ u32 s3_iman; - u32 s3_irq_control; + u32 s3_imod; u32 s3_erst_size; u64 s3_erst_base; u64 s3_erst_dequeue; -- cgit v1.2.3-59-g8ed1b From 85c4aa0a456409511f4383811a88a3cb6b3af75c Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Tue, 6 May 2025 11:31:01 +0800 Subject: xhci: Add missing parameter description to xhci_get_endpoint_index() Fix kernel-doc warning by documenting the @desc parameter: drivers/usb/host/xhci.c:1369: warning: Function parameter or struct member 'desc' not described in 'xhci_get_endpoint_index' Add detailed description of the @desc parameter and clarify the indexing logic for control endpoints vs other types. This brings the documentation in line with kernel-doc requirements while maintaining technical accuracy. Signed-off-by: Hans Zhang <18255117159@163.com> Link: https://lore.kernel.org/r/20250506033101.206180-1-18255117159@163.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 9769c68b2e9f..803047bec3e7 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1457,6 +1457,7 @@ static void xhci_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) * xhci_get_endpoint_index - Used for passing endpoint bitmasks between the core and * HCDs. Find the index for an endpoint given its descriptor. Use the return * value to right shift 1 for the bitmask. + * @desc: USB endpoint descriptor to determine index for * * Index = (epnum * 2) + direction - 1, * where direction = 0 for OUT, 1 for IN. -- cgit v1.2.3-59-g8ed1b From 0736299d090f5c6a1032678705c4bc0a9511a3db Mon Sep 17 00:00:00 2001 From: Amit Sunil Dhamne Date: Fri, 2 May 2025 16:57:03 -0700 Subject: usb: typec: tcpm/tcpci_maxim: Fix bounds check in process_rx() Register read of TCPC_RX_BYTE_CNT returns the total size consisting of: PD message (pending read) size + 1 Byte for Frame Type (SOP*) This is validated against the max PD message (`struct pd_message`) size without accounting for the extra byte for the frame type. Note that the struct pd_message does not contain a field for the frame_type. This results in false negatives when the "PD message (pending read)" is equal to the max PD message size. Fixes: 6f413b559f86 ("usb: typec: tcpci_maxim: Chip level TCPC driver") Signed-off-by: Amit Sunil Dhamne Signed-off-by: Badhri Jagan Sridharan Reviewed-by: Kyle Tso Cc: stable Link: https://lore.kernel.org/stable/20250502-b4-new-fix-pd-rx-count-v1-1-e5711ed09b3d%40google.com Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20250502-b4-new-fix-pd-rx-count-v1-1-e5711ed09b3d@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci_maxim_core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/tcpm/tcpci_maxim_core.c b/drivers/usb/typec/tcpm/tcpci_maxim_core.c index 29a4aa89d1a1..b5a5ed40faea 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim_core.c +++ b/drivers/usb/typec/tcpm/tcpci_maxim_core.c @@ -166,7 +166,8 @@ static void process_rx(struct max_tcpci_chip *chip, u16 status) return; } - if (count > sizeof(struct pd_message) || count + 1 > TCPC_RECEIVE_BUFFER_LEN) { + if (count > sizeof(struct pd_message) + 1 || + count + 1 > TCPC_RECEIVE_BUFFER_LEN) { dev_err(chip->dev, "Invalid TCPC_RX_BYTE_CNT %d\n", count); return; } -- cgit v1.2.3-59-g8ed1b From 324d45e53f1a36c88bc649dc39e0c8300a41be0a Mon Sep 17 00:00:00 2001 From: RD Babiera Date: Tue, 6 May 2025 23:28:53 +0000 Subject: usb: typec: tcpm: move tcpm_queue_vdm_unlocked to asynchronous work A state check was previously added to tcpm_queue_vdm_unlocked to prevent a deadlock where the DisplayPort Alt Mode driver would be executing work and attempting to grab the tcpm_lock while the TCPM was holding the lock and attempting to unregister the altmode, blocking on the altmode driver's cancel_work_sync call. Because the state check isn't protected, there is a small window where the Alt Mode driver could determine that the TCPM is in a ready state and attempt to grab the lock while the TCPM grabs the lock and changes the TCPM state to one that causes the deadlock. The callstack is provided below: [110121.667392][ C7] Call trace: [110121.667396][ C7] __switch_to+0x174/0x338 [110121.667406][ C7] __schedule+0x608/0x9f0 [110121.667414][ C7] schedule+0x7c/0xe8 [110121.667423][ C7] kernfs_drain+0xb0/0x114 [110121.667431][ C7] __kernfs_remove+0x16c/0x20c [110121.667436][ C7] kernfs_remove_by_name_ns+0x74/0xe8 [110121.667442][ C7] sysfs_remove_group+0x84/0xe8 [110121.667450][ C7] sysfs_remove_groups+0x34/0x58 [110121.667458][ C7] device_remove_groups+0x10/0x20 [110121.667464][ C7] device_release_driver_internal+0x164/0x2e4 [110121.667475][ C7] device_release_driver+0x18/0x28 [110121.667484][ C7] bus_remove_device+0xec/0x118 [110121.667491][ C7] device_del+0x1e8/0x4ac [110121.667498][ C7] device_unregister+0x18/0x38 [110121.667504][ C7] typec_unregister_altmode+0x30/0x44 [110121.667515][ C7] tcpm_reset_port+0xac/0x370 [110121.667523][ C7] tcpm_snk_detach+0x84/0xb8 [110121.667529][ C7] run_state_machine+0x4c0/0x1b68 [110121.667536][ C7] tcpm_state_machine_work+0x94/0xe4 [110121.667544][ C7] kthread_worker_fn+0x10c/0x244 [110121.667552][ C7] kthread+0x104/0x1d4 [110121.667557][ C7] ret_from_fork+0x10/0x20 [110121.667689][ C7] Workqueue: events dp_altmode_work [110121.667697][ C7] Call trace: [110121.667701][ C7] __switch_to+0x174/0x338 [110121.667710][ C7] __schedule+0x608/0x9f0 [110121.667717][ C7] schedule+0x7c/0xe8 [110121.667725][ C7] schedule_preempt_disabled+0x24/0x40 [110121.667733][ C7] __mutex_lock+0x408/0xdac [110121.667741][ C7] __mutex_lock_slowpath+0x14/0x24 [110121.667748][ C7] mutex_lock+0x40/0xec [110121.667757][ C7] tcpm_altmode_enter+0x78/0xb4 [110121.667764][ C7] typec_altmode_enter+0xdc/0x10c [110121.667769][ C7] dp_altmode_work+0x68/0x164 [110121.667775][ C7] process_one_work+0x1e4/0x43c [110121.667783][ C7] worker_thread+0x25c/0x430 [110121.667789][ C7] kthread+0x104/0x1d4 [110121.667794][ C7] ret_from_fork+0x10/0x20 Change tcpm_queue_vdm_unlocked to queue for tcpm_queue_vdm_work, which can perform the state check while holding the TCPM lock while the Alt Mode lock is no longer held. This requires a new struct to hold the vdm data, altmode_vdm_event. Fixes: cdc9946ea637 ("usb: typec: tcpm: enforce ready state when queueing alt mode vdm") Cc: stable Signed-off-by: RD Babiera Reviewed-by: Heikki Krogerus Reviewed-by: Badhri Jagan Sridharan Link: https://lore.kernel.org/r/20250506232853.1968304-2-rdbabiera@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 91 +++++++++++++++++++++++++++++++++---------- 1 file changed, 71 insertions(+), 20 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 87d56ac4565d..3669ffb01905 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -597,6 +597,15 @@ struct pd_rx_event { enum tcpm_transmit_type rx_sop_type; }; +struct altmode_vdm_event { + struct kthread_work work; + struct tcpm_port *port; + u32 header; + u32 *data; + int cnt; + enum tcpm_transmit_type tx_sop_type; +}; + static const char * const pd_rev[] = { [PD_REV10] = "rev1", [PD_REV20] = "rev2", @@ -1610,18 +1619,68 @@ static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header, mod_vdm_delayed_work(port, 0); } -static void tcpm_queue_vdm_unlocked(struct tcpm_port *port, const u32 header, - const u32 *data, int cnt, enum tcpm_transmit_type tx_sop_type) +static void tcpm_queue_vdm_work(struct kthread_work *work) { - if (port->state != SRC_READY && port->state != SNK_READY && - port->state != SRC_VDM_IDENTITY_REQUEST) - return; + struct altmode_vdm_event *event = container_of(work, + struct altmode_vdm_event, + work); + struct tcpm_port *port = event->port; mutex_lock(&port->lock); - tcpm_queue_vdm(port, header, data, cnt, tx_sop_type); + if (port->state != SRC_READY && port->state != SNK_READY && + port->state != SRC_VDM_IDENTITY_REQUEST) { + tcpm_log_force(port, "dropping altmode_vdm_event"); + goto port_unlock; + } + + tcpm_queue_vdm(port, event->header, event->data, event->cnt, event->tx_sop_type); + +port_unlock: + kfree(event->data); + kfree(event); mutex_unlock(&port->lock); } +static int tcpm_queue_vdm_unlocked(struct tcpm_port *port, const u32 header, + const u32 *data, int cnt, enum tcpm_transmit_type tx_sop_type) +{ + struct altmode_vdm_event *event; + u32 *data_cpy; + int ret = -ENOMEM; + + event = kzalloc(sizeof(*event), GFP_KERNEL); + if (!event) + goto err_event; + + data_cpy = kcalloc(cnt, sizeof(u32), GFP_KERNEL); + if (!data_cpy) + goto err_data; + + kthread_init_work(&event->work, tcpm_queue_vdm_work); + event->port = port; + event->header = header; + memcpy(data_cpy, data, sizeof(u32) * cnt); + event->data = data_cpy; + event->cnt = cnt; + event->tx_sop_type = tx_sop_type; + + ret = kthread_queue_work(port->wq, &event->work); + if (!ret) { + ret = -EBUSY; + goto err_queue; + } + + return 0; + +err_queue: + kfree(data_cpy); +err_data: + kfree(event); +err_event: + tcpm_log_force(port, "failed to queue altmode vdm, err:%d", ret); + return ret; +} + static void svdm_consume_identity(struct tcpm_port *port, const u32 *p, int cnt) { u32 vdo = p[VDO_INDEX_IDH]; @@ -2832,8 +2891,7 @@ static int tcpm_altmode_enter(struct typec_altmode *altmode, u32 *vdo) header = VDO(altmode->svid, vdo ? 2 : 1, svdm_version, CMD_ENTER_MODE); header |= VDO_OPOS(altmode->mode); - tcpm_queue_vdm_unlocked(port, header, vdo, vdo ? 1 : 0, TCPC_TX_SOP); - return 0; + return tcpm_queue_vdm_unlocked(port, header, vdo, vdo ? 1 : 0, TCPC_TX_SOP); } static int tcpm_altmode_exit(struct typec_altmode *altmode) @@ -2849,8 +2907,7 @@ static int tcpm_altmode_exit(struct typec_altmode *altmode) header = VDO(altmode->svid, 1, svdm_version, CMD_EXIT_MODE); header |= VDO_OPOS(altmode->mode); - tcpm_queue_vdm_unlocked(port, header, NULL, 0, TCPC_TX_SOP); - return 0; + return tcpm_queue_vdm_unlocked(port, header, NULL, 0, TCPC_TX_SOP); } static int tcpm_altmode_vdm(struct typec_altmode *altmode, @@ -2858,9 +2915,7 @@ static int tcpm_altmode_vdm(struct typec_altmode *altmode, { struct tcpm_port *port = typec_altmode_get_drvdata(altmode); - tcpm_queue_vdm_unlocked(port, header, data, count - 1, TCPC_TX_SOP); - - return 0; + return tcpm_queue_vdm_unlocked(port, header, data, count - 1, TCPC_TX_SOP); } static const struct typec_altmode_ops tcpm_altmode_ops = { @@ -2884,8 +2939,7 @@ static int tcpm_cable_altmode_enter(struct typec_altmode *altmode, enum typec_pl header = VDO(altmode->svid, vdo ? 2 : 1, svdm_version, CMD_ENTER_MODE); header |= VDO_OPOS(altmode->mode); - tcpm_queue_vdm_unlocked(port, header, vdo, vdo ? 1 : 0, TCPC_TX_SOP_PRIME); - return 0; + return tcpm_queue_vdm_unlocked(port, header, vdo, vdo ? 1 : 0, TCPC_TX_SOP_PRIME); } static int tcpm_cable_altmode_exit(struct typec_altmode *altmode, enum typec_plug_index sop) @@ -2901,8 +2955,7 @@ static int tcpm_cable_altmode_exit(struct typec_altmode *altmode, enum typec_plu header = VDO(altmode->svid, 1, svdm_version, CMD_EXIT_MODE); header |= VDO_OPOS(altmode->mode); - tcpm_queue_vdm_unlocked(port, header, NULL, 0, TCPC_TX_SOP_PRIME); - return 0; + return tcpm_queue_vdm_unlocked(port, header, NULL, 0, TCPC_TX_SOP_PRIME); } static int tcpm_cable_altmode_vdm(struct typec_altmode *altmode, enum typec_plug_index sop, @@ -2910,9 +2963,7 @@ static int tcpm_cable_altmode_vdm(struct typec_altmode *altmode, enum typec_plug { struct tcpm_port *port = typec_altmode_get_drvdata(altmode); - tcpm_queue_vdm_unlocked(port, header, data, count - 1, TCPC_TX_SOP_PRIME); - - return 0; + return tcpm_queue_vdm_unlocked(port, header, data, count - 1, TCPC_TX_SOP_PRIME); } static const struct typec_cable_ops tcpm_cable_ops = { -- cgit v1.2.3-59-g8ed1b From f9bd09ef2c04104a2be718fa8fc3939bb0ba1299 Mon Sep 17 00:00:00 2001 From: Cosmo Chou Date: Tue, 13 May 2025 21:08:34 +0800 Subject: usb: typec: tcpm: Use configured PD revision for negotiation Initialize negotiated_rev and negotiated_rev_prime based on the port's configured PD revision (rev_major) rather than always defaulting to PD_MAX_REV. This ensures ports start PD communication using their appropriate revision level. This allows proper communication with devices that require specific PD revision levels, especially for the hardware designed for PD 1.0 or 2.0 specifications. Signed-off-by: Cosmo Chou Reviewed-by: Heikki Krogerus Reviewed-by: Badhri Jagan Sridharan Link: https://lore.kernel.org/r/20250513130834.1612602-1-chou.cosmo@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 3669ffb01905..199a023b68e6 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -314,6 +314,10 @@ struct pd_data { unsigned int operating_snk_mw; }; +#define PD_CAP_REV10 0x1 +#define PD_CAP_REV20 0x2 +#define PD_CAP_REV30 0x3 + struct pd_revision_info { u8 rev_major; u8 rev_minor; @@ -4732,6 +4736,25 @@ static void tcpm_set_initial_svdm_version(struct tcpm_port *port) } } +static void tcpm_set_initial_negotiated_rev(struct tcpm_port *port) +{ + switch (port->pd_rev.rev_major) { + case PD_CAP_REV10: + port->negotiated_rev = PD_REV10; + break; + case PD_CAP_REV20: + port->negotiated_rev = PD_REV20; + break; + case PD_CAP_REV30: + port->negotiated_rev = PD_REV30; + break; + default: + port->negotiated_rev = PD_MAX_REV; + break; + } + port->negotiated_rev_prime = port->negotiated_rev; +} + static void run_state_machine(struct tcpm_port *port) { int ret; @@ -4849,8 +4872,7 @@ static void run_state_machine(struct tcpm_port *port) typec_set_pwr_opmode(port->typec_port, opmode); port->pwr_opmode = TYPEC_PWR_MODE_USB; port->caps_count = 0; - port->negotiated_rev = PD_MAX_REV; - port->negotiated_rev_prime = PD_MAX_REV; + tcpm_set_initial_negotiated_rev(port); port->message_id = 0; port->message_id_prime = 0; port->rx_msgid = -1; @@ -5127,8 +5149,7 @@ static void run_state_machine(struct tcpm_port *port) port->cc2 : port->cc1); typec_set_pwr_opmode(port->typec_port, opmode); port->pwr_opmode = TYPEC_PWR_MODE_USB; - port->negotiated_rev = PD_MAX_REV; - port->negotiated_rev_prime = PD_MAX_REV; + tcpm_set_initial_negotiated_rev(port); port->message_id = 0; port->message_id_prime = 0; port->rx_msgid = -1; -- cgit v1.2.3-59-g8ed1b From 19f795591947596b5b9efa86fd4b9058e45786e9 Mon Sep 17 00:00:00 2001 From: Jiayi Li Date: Thu, 8 May 2025 13:59:47 +0800 Subject: usb: quirks: Add NO_LPM quirk for SanDisk Extreme 55AE This device exhibits I/O errors during file transfers due to unstable link power management (LPM) behavior. The kernel logs show repeated warm resets and eventual disconnection when LPM is enabled: [ 3467.810740] hub 2-0:1.0: state 7 ports 6 chg 0000 evt 0020 [ 3467.810740] usb usb2-port5: do warm reset [ 3467.866444] usb usb2-port5: not warm reset yet, waiting 50ms [ 3467.907407] sd 0:0:0:0: [sda] tag#12 sense submit err -19 [ 3467.994423] usb usb2-port5: status 02c0, change 0001, 10.0 Gb/s [ 3467.994453] usb 2-5: USB disconnect, device number 4 The error -19 (ENODEV) occurs when the device disappears during write operations. Adding USB_QUIRK_NO_LPM disables link power management for this specific device, resolving the stability issues. Signed-off-by: Jiayi Li Cc: stable Link: https://lore.kernel.org/r/20250508055947.764538-1-lijiayi@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/quirks.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index 36d3df7d040c..53d68d20fb62 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -372,6 +372,9 @@ static const struct usb_device_id usb_quirk_list[] = { /* SanDisk Corp. SanDisk 3.2Gen1 */ { USB_DEVICE(0x0781, 0x55a3), .driver_info = USB_QUIRK_DELAY_INIT }, + /* SanDisk Extreme 55AE */ + { USB_DEVICE(0x0781, 0x55ae), .driver_info = USB_QUIRK_NO_LPM }, + /* Realforce 87U Keyboard */ { USB_DEVICE(0x0853, 0x011b), .driver_info = USB_QUIRK_NO_LPM }, -- cgit v1.2.3-59-g8ed1b From a541acceedf4f639f928f41fbb676b75946dc295 Mon Sep 17 00:00:00 2001 From: Hongyu Xie Date: Mon, 19 May 2025 10:33:28 +0800 Subject: usb: storage: Ignore UAS driver for SanDisk 3.2 Gen2 storage device SanDisk 3.2 Gen2 storage device(0781:55e8) doesn't work well with UAS. Log says, [ 6.507865][ 3] [ T159] usb 2-1.4: new SuperSpeed Gen 1 USB device number 4 using xhci_hcd [ 6.540314][ 3] [ T159] usb 2-1.4: New USB device found, idVendor=0781, idProduct=55e8, bcdDevice= 0.01 [ 6.576304][ 3] [ T159] usb 2-1.4: New USB device strings: Mfr=1, Product=2, SerialNumber=3 [ 6.584727][ 3] [ T159] usb 2-1.4: Product: SanDisk 3.2 Gen2 [ 6.590459][ 3] [ T159] usb 2-1.4: Manufacturer: SanDisk [ 6.595845][ 3] [ T159] usb 2-1.4: SerialNumber: 03021707022525140940 [ 7.230852][ 0] [ T265] usbcore: registered new interface driver usb-storage [ 7.251247][ 0] [ T265] scsi host3: uas [ 7.255280][ 0] [ T265] usbcore: registered new interface driver uas [ 7.270498][ 1] [ T192] scsi 3:0:0:0: Direct-Access SanDisk Extreme Pro DDE1 0110 PQ: 0 ANSI: 6 [ 7.299588][ 3] [ T192] scsi 3:0:0:1: Enclosure SanDisk SES Device 0110 PQ: 0 ANSI: 6 [ 7.321681][ 3] [ T192] sd 3:0:0:0: Attached scsi generic sg1 type 0 [ 7.328185][ 3] [ T192] scsi 3:0:0:1: Attached scsi generic sg2 type 13 [ 7.328804][ 0] [ T191] sd 3:0:0:0: [sda] 976773168 512-byte logical blocks: (500 GB/466 GiB) [ 7.343486][ 0] [ T191] sd 3:0:0:0: [sda] 4096-byte physical blocks [ 7.364611][ 0] [ T191] sd 3:0:0:0: [sda] Write Protect is off [ 7.370524][ 0] [ T191] sd 3:0:0:0: [sda] Mode Sense: 3d 00 10 00 [ 7.390655][ 0] [ T191] sd 3:0:0:0: [sda] Write cache: enabled, read cache: enabled, supports DPO and FUA [ 7.401363][ 0] [ T191] sd 3:0:0:0: [sda] Optimal transfer size 1048576 bytes [ 7.436010][ 0] [ T191] sda: sda1 [ 7.450850][ 0] [ T191] sd 3:0:0:0: [sda] Attached SCSI disk [ 7.470218][ 4] [ T262] scsi 3:0:0:1: Failed to get diagnostic page 0x1 [ 7.474869][ 0] [ C0] sd 3:0:0:0: [sda] tag#0 data cmplt err -75 uas-tag 2 inflight: CMD [ 7.476911][ 4] [ T262] scsi 3:0:0:1: Failed to bind enclosure -19 [ 7.485330][ 0] [ C0] sd 3:0:0:0: [sda] tag#0 CDB: Read(10) 28 00 00 00 00 28 00 00 10 00 [ 7.491593][ 4] [ T262] ses 3:0:0:1: Attached Enclosure device [ 38.066980][ 4] [ T192] sd 3:0:0:0: [sda] tag#4 uas_eh_abort_handler 0 uas-tag 5 inflight: CMD IN [ 38.076012][ 4] [ T192] sd 3:0:0:0: [sda] tag#4 CDB: Read(10) 28 00 00 00 01 08 00 00 f8 00 [ 38.086485][ 4] [ T192] sd 3:0:0:0: [sda] tag#3 uas_eh_abort_handler 0 uas-tag 1 inflight: CMD IN [ 38.095515][ 4] [ T192] sd 3:0:0:0: [sda] tag#3 CDB: Read(10) 28 00 00 00 00 10 00 00 08 00 [ 38.104122][ 4] [ T192] sd 3:0:0:0: [sda] tag#2 uas_eh_abort_handler 0 uas-tag 4 inflight: CMD IN [ 38.113152][ 4] [ T192] sd 3:0:0:0: [sda] tag#2 CDB: Read(10) 28 00 00 00 00 88 00 00 78 00 [ 38.121761][ 4] [ T192] sd 3:0:0:0: [sda] tag#1 uas_eh_abort_handler 0 uas-tag 3 inflight: CMD IN [ 38.130791][ 4] [ T192] sd 3:0:0:0: [sda] tag#1 CDB: Read(10) 28 00 00 00 00 48 00 00 30 00 [ 38.139401][ 4] [ T192] sd 3:0:0:0: [sda] tag#0 uas_eh_abort_handler 0 uas-tag 2 inflight: CMD [ 38.148170][ 4] [ T192] sd 3:0:0:0: [sda] tag#0 CDB: Read(10) 28 00 00 00 00 28 00 00 10 00 [ 38.178980][ 2] [ T304] scsi host3: uas_eh_device_reset_handler start [ 38.901540][ 2] [ T304] usb 2-1.4: reset SuperSpeed Gen 1 USB device number 4 using xhci_hcd [ 38.936791][ 2] [ T304] scsi host3: uas_eh_device_reset_handler success Device decriptor is below, Bus 002 Device 006: ID 0781:55e8 SanDisk Corp. SanDisk 3.2 Gen2 Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 3.20 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 9 idVendor 0x0781 SanDisk Corp. idProduct 0x55e8 bcdDevice 0.01 iManufacturer 1 SanDisk iProduct 2 SanDisk 3.2 Gen2 iSerial 3 03021707022525140940 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x0079 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0x80 (Bus Powered) MaxPower 896mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 8 Mass Storage bInterfaceSubClass 6 SCSI bInterfaceProtocol 80 Bulk-Only iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 15 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x01 EP 1 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 15 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 1 bNumEndpoints 4 bInterfaceClass 8 Mass Storage bInterfaceSubClass 6 SCSI bInterfaceProtocol 98 iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x01 EP 1 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 0 Command pipe (0x01) Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x84 EP 4 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 15 MaxStreams 32 Status pipe (0x02) Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 15 MaxStreams 32 Data-in pipe (0x03) Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x03 EP 3 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 15 MaxStreams 32 Data-out pipe (0x04) Binary Object Store Descriptor: bLength 5 bDescriptorType 15 wTotalLength 0x002a bNumDeviceCaps 3 USB 2.0 Extension Device Capability: bLength 7 bDescriptorType 16 bDevCapabilityType 2 bmAttributes 0x0000f41e BESL Link Power Management (LPM) Supported BESL value 1024 us Deep BESL value 61440 us SuperSpeed USB Device Capability: bLength 10 bDescriptorType 16 bDevCapabilityType 3 bmAttributes 0x00 wSpeedsSupported 0x000e Device can operate at Full Speed (12Mbps) Device can operate at High Speed (480Mbps) Device can operate at SuperSpeed (5Gbps) bFunctionalitySupport 1 Lowest fully-functional device speed is Full Speed (12Mbps) bU1DevExitLat 10 micro seconds bU2DevExitLat 2047 micro seconds SuperSpeedPlus USB Device Capability: bLength 20 bDescriptorType 16 bDevCapabilityType 10 bmAttributes 0x00000001 Sublink Speed Attribute count 1 Sublink Speed ID count 0 wFunctionalitySupport 0x1100 bmSublinkSpeedAttr[0] 0x000a4030 Speed Attribute ID: 0 10Gb/s Symmetric RX SuperSpeedPlus bmSublinkSpeedAttr[1] 0x000a40b0 Speed Attribute ID: 0 10Gb/s Symmetric TX SuperSpeedPlus Device Status: 0x0000 (Bus Powered) So ignore UAS driver for this device. Signed-off-by: Hongyu Xie Cc: stable Link: https://lore.kernel.org/r/20250519023328.1498856-1-xiehongyu1@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/unusual_uas.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/usb/storage/unusual_uas.h b/drivers/usb/storage/unusual_uas.h index d460d71b4257..1477e31d7763 100644 --- a/drivers/usb/storage/unusual_uas.h +++ b/drivers/usb/storage/unusual_uas.h @@ -52,6 +52,13 @@ UNUSUAL_DEV(0x059f, 0x1061, 0x0000, 0x9999, USB_SC_DEVICE, USB_PR_DEVICE, NULL, US_FL_NO_REPORT_OPCODES | US_FL_NO_SAME), +/* Reported-by: Zhihong Zhou */ +UNUSUAL_DEV(0x0781, 0x55e8, 0x0000, 0x9999, + "SanDisk", + "", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_UAS), + /* Reported-by: Hongling Zeng */ UNUSUAL_DEV(0x090c, 0x2000, 0x0000, 0x9999, "Hiksemi", -- cgit v1.2.3-59-g8ed1b From 1c06aff9b82fc3795a80feeb23935b9a44b07e71 Mon Sep 17 00:00:00 2001 From: Prashanth K Date: Tue, 6 May 2025 16:19:12 +0530 Subject: usb: gadget: u_serial: Avoid double unlock of serial_port_lock Avoid unlocking serial_port_lock twice in gserial_suspend(), this can occur if gserial_wakeup_host() fails. And since wakeup is performed outside spinlock, check if the port is valid before proceeding again. Fixes: 3baea29dc0a7 ("usb: gadget: u_serial: Implement remote wakeup capability") Reported-by: Dan Carpenter Closes: https://lore.kernel.org/all/aBHatifO5bjR1yPt@stanley.mountain/ Signed-off-by: Prashanth K Link: https://lore.kernel.org/r/20250506104912.3750934-1-prashanth.k@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/u_serial.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c index 41dee7c8cc7c..ab544f6824be 100644 --- a/drivers/usb/gadget/function/u_serial.c +++ b/drivers/usb/gadget/function/u_serial.c @@ -1505,6 +1505,13 @@ void gserial_suspend(struct gserial *gser) spin_unlock_irqrestore(&serial_port_lock, flags); if (!gserial_wakeup_host(gser)) return; + + /* Check if port is valid after acquiring lock back */ + spin_lock_irqsave(&serial_port_lock, flags); + if (!port) { + spin_unlock_irqrestore(&serial_port_lock, flags); + return; + } } spin_lock(&port->port_lock); -- cgit v1.2.3-59-g8ed1b From 4f78a9c7b8e86f8544e0363a22b367df6620b9ab Mon Sep 17 00:00:00 2001 From: Aleksandrs Vinarskis Date: Mon, 12 May 2025 11:20:28 +0200 Subject: dt-bindings: usb: Add Parade PS8833 Type-C retimer variant Appears to behave similarly to Parade PS8830. Found on some Qualcomm Snapdragon X1 devices, such as Asus Zenbook A14. Acked-by: Krzysztof Kozlowski Signed-off-by: Aleksandrs Vinarskis Link: https://lore.kernel.org/r/20250512092745.249293-2-alex.vinarskis@gmail.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/parade,ps8830.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/parade,ps8830.yaml b/Documentation/devicetree/bindings/usb/parade,ps8830.yaml index 935d57f5d26f..aeb33667818e 100644 --- a/Documentation/devicetree/bindings/usb/parade,ps8830.yaml +++ b/Documentation/devicetree/bindings/usb/parade,ps8830.yaml @@ -11,8 +11,11 @@ maintainers: properties: compatible: - enum: - - parade,ps8830 + oneOf: + - items: + - const: parade,ps8833 + - const: parade,ps8830 + - const: parade,ps8830 reg: maxItems: 1 -- cgit v1.2.3-59-g8ed1b From e33ebb133a245a48b543d6eb79768a66f233656b Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Thu, 8 May 2025 22:42:11 -0500 Subject: usb: dwc3: qcom: Use bulk clock API and devres The Qualcomm DWC3 glue driver duplicates the logic of the bulk clock API to acquire, prepare, and unprepare the controller's clocks. It also manages the life cycle of these handled explicitly. Transition to the bulk clock API and manage the resources using devres, to clean up the code. The resource acquisition is moved above the initial reset pulse, to handle resource issues before the state is touched - other than this, this no functional change. Signed-off-by: Bjorn Andersson Acked-by: Thinh Nguyen Link: https://lore.kernel.org/r/20250508-dwc3-clk-bulk-v2-1-bad3427e88d4@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-qcom.c | 90 ++++++++------------------------------------ 1 file changed, 15 insertions(+), 75 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index 20c00ba3bc3d..7334de85ad10 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -73,7 +73,7 @@ struct dwc3_qcom { struct device *dev; void __iomem *qscratch_base; struct dwc3 dwc; - struct clk **clks; + struct clk_bulk_data *clks; int num_clocks; struct reset_control *resets; struct dwc3_qcom_port ports[DWC3_QCOM_MAX_PORTS]; @@ -431,9 +431,7 @@ static int dwc3_qcom_suspend(struct dwc3_qcom *qcom, bool wakeup) if (!(val & PWR_EVNT_LPM_IN_L2_MASK)) dev_err(qcom->dev, "port-%d HS-PHY not in L2\n", i + 1); } - - for (i = qcom->num_clocks - 1; i >= 0; i--) - clk_disable_unprepare(qcom->clks[i]); + clk_bulk_disable_unprepare(qcom->num_clocks, qcom->clks); ret = dwc3_qcom_interconnect_disable(qcom); if (ret) @@ -465,14 +463,9 @@ static int dwc3_qcom_resume(struct dwc3_qcom *qcom, bool wakeup) if (dwc3_qcom_is_host(qcom) && wakeup) dwc3_qcom_disable_interrupts(qcom); - for (i = 0; i < qcom->num_clocks; i++) { - ret = clk_prepare_enable(qcom->clks[i]); - if (ret < 0) { - while (--i >= 0) - clk_disable_unprepare(qcom->clks[i]); - return ret; - } - } + ret = clk_bulk_prepare_enable(qcom->num_clocks, qcom->clks); + if (ret < 0) + return ret; ret = dwc3_qcom_interconnect_enable(qcom); if (ret) @@ -648,62 +641,14 @@ static int dwc3_qcom_setup_irq(struct dwc3_qcom *qcom, struct platform_device *p return 0; } -static int dwc3_qcom_clk_init(struct dwc3_qcom *qcom, int count) -{ - struct device *dev = qcom->dev; - struct device_node *np = dev->of_node; - int i; - - if (!np || !count) - return 0; - - if (count < 0) - return count; - - qcom->num_clocks = count; - - qcom->clks = devm_kcalloc(dev, qcom->num_clocks, - sizeof(struct clk *), GFP_KERNEL); - if (!qcom->clks) - return -ENOMEM; - - for (i = 0; i < qcom->num_clocks; i++) { - struct clk *clk; - int ret; - - clk = of_clk_get(np, i); - if (IS_ERR(clk)) { - while (--i >= 0) - clk_put(qcom->clks[i]); - return PTR_ERR(clk); - } - - ret = clk_prepare_enable(clk); - if (ret < 0) { - while (--i >= 0) { - clk_disable_unprepare(qcom->clks[i]); - clk_put(qcom->clks[i]); - } - clk_put(clk); - - return ret; - } - - qcom->clks[i] = clk; - } - - return 0; -} - static int dwc3_qcom_probe(struct platform_device *pdev) { struct dwc3_probe_data probe_data = {}; - struct device_node *np = pdev->dev.of_node; struct device *dev = &pdev->dev; struct dwc3_qcom *qcom; struct resource res; struct resource *r; - int ret, i; + int ret; bool ignore_pipe_clk; bool wakeup_source; @@ -719,6 +664,11 @@ static int dwc3_qcom_probe(struct platform_device *pdev) "failed to get resets\n"); } + ret = devm_clk_bulk_get_all(&pdev->dev, &qcom->clks); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get clocks\n"); + qcom->num_clocks = ret; + ret = reset_control_assert(qcom->resets); if (ret) { dev_err(&pdev->dev, "failed to assert resets, err=%d\n", ret); @@ -733,11 +683,9 @@ static int dwc3_qcom_probe(struct platform_device *pdev) goto reset_assert; } - ret = dwc3_qcom_clk_init(qcom, of_clk_get_parent_count(np)); - if (ret) { - dev_err_probe(dev, ret, "failed to get clocks\n"); + ret = clk_bulk_prepare_enable(qcom->num_clocks, qcom->clks); + if (ret < 0) goto reset_assert; - } r = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!r) { @@ -806,10 +754,7 @@ interconnect_exit: remove_core: dwc3_core_remove(&qcom->dwc); clk_disable: - for (i = qcom->num_clocks - 1; i >= 0; i--) { - clk_disable_unprepare(qcom->clks[i]); - clk_put(qcom->clks[i]); - } + clk_bulk_disable_unprepare(qcom->num_clocks, qcom->clks); reset_assert: reset_control_assert(qcom->resets); @@ -820,15 +765,10 @@ static void dwc3_qcom_remove(struct platform_device *pdev) { struct dwc3 *dwc = platform_get_drvdata(pdev); struct dwc3_qcom *qcom = to_dwc3_qcom(dwc); - int i; dwc3_core_remove(&qcom->dwc); - for (i = qcom->num_clocks - 1; i >= 0; i--) { - clk_disable_unprepare(qcom->clks[i]); - clk_put(qcom->clks[i]); - } - qcom->num_clocks = 0; + clk_bulk_disable_unprepare(qcom->num_clocks, qcom->clks); dwc3_qcom_interconnect_exit(qcom); reset_control_assert(qcom->resets); -- cgit v1.2.3-59-g8ed1b From 89bb3dc13ac29a563f4e4c555e422882f64742bd Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Wed, 14 May 2025 16:25:20 +0300 Subject: usb: Flush altsetting 0 endpoints before reinitializating them after reset. usb core avoids sending a Set-Interface altsetting 0 request after device reset, and instead relies on calling usb_disable_interface() and usb_enable_interface() to flush and reset host-side of those endpoints. xHCI hosts allocate and set up endpoint ring buffers and host_ep->hcpriv during usb_hcd_alloc_bandwidth() callback, which in this case is called before flushing the endpoint in usb_disable_interface(). Call usb_disable_interface() before usb_hcd_alloc_bandwidth() to ensure URBs are flushed before new ring buffers for the endpoints are allocated. Otherwise host driver will attempt to find and remove old stale URBs from a freshly allocated new ringbuffer. Cc: stable Fixes: 4fe0387afa89 ("USB: don't send Set-Interface after reset") Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250514132520.225345-1-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index cfb3abafeacd..416af6d76374 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -6137,6 +6137,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev) struct usb_hub *parent_hub; struct usb_hcd *hcd = bus_to_hcd(udev->bus); struct usb_device_descriptor descriptor; + struct usb_interface *intf; struct usb_host_bos *bos; int i, j, ret = 0; int port1 = udev->portnum; @@ -6194,6 +6195,18 @@ static int usb_reset_and_verify_device(struct usb_device *udev) if (!udev->actconfig) goto done; + /* + * Some devices can't handle setting default altsetting 0 with a + * Set-Interface request. Disable host-side endpoints of those + * interfaces here. Enable and reset them back after host has set + * its internal endpoint structures during usb_hcd_alloc_bandwith() + */ + for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) { + intf = udev->actconfig->interface[i]; + if (intf->cur_altsetting->desc.bAlternateSetting == 0) + usb_disable_interface(udev, intf, true); + } + mutex_lock(hcd->bandwidth_mutex); ret = usb_hcd_alloc_bandwidth(udev, udev->actconfig, NULL, NULL); if (ret < 0) { @@ -6225,12 +6238,11 @@ static int usb_reset_and_verify_device(struct usb_device *udev) */ for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) { struct usb_host_config *config = udev->actconfig; - struct usb_interface *intf = config->interface[i]; struct usb_interface_descriptor *desc; + intf = config->interface[i]; desc = &intf->cur_altsetting->desc; if (desc->bAlternateSetting == 0) { - usb_disable_interface(udev, intf, true); usb_enable_interface(udev, intf, true); ret = 0; } else { -- cgit v1.2.3-59-g8ed1b From 2852788cfbe9ca1ab68509d65804413871f741f9 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Tue, 13 May 2025 06:54:03 +0000 Subject: usb: cdnsp: Fix issue with detecting USB 3.2 speed Patch adds support for detecting SuperSpeedPlus Gen1 x2 and SuperSpeedPlus Gen2 x2 speed. Fixes: 3d82904559f4 ("usb: cdnsp: cdns3 Add main part of Cadence USBSSP DRD Driver") Cc: stable Signed-off-by: Pawel Laszczak Acked-by: Peter Chen Link: https://lore.kernel.org/r/PH7PR07MB95387AD98EDCA695FECE52BADD96A@PH7PR07MB9538.namprd07.prod.outlook.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/cdnsp-gadget.c | 3 ++- drivers/usb/cdns3/cdnsp-gadget.h | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c index 4824a10df07e..cd1e00daf43f 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.c +++ b/drivers/usb/cdns3/cdnsp-gadget.c @@ -29,7 +29,8 @@ unsigned int cdnsp_port_speed(unsigned int port_status) { /*Detect gadget speed based on PORTSC register*/ - if (DEV_SUPERSPEEDPLUS(port_status)) + if (DEV_SUPERSPEEDPLUS(port_status) || + DEV_SSP_GEN1x2(port_status) || DEV_SSP_GEN2x2(port_status)) return USB_SPEED_SUPER_PLUS; else if (DEV_SUPERSPEED(port_status)) return USB_SPEED_SUPER; diff --git a/drivers/usb/cdns3/cdnsp-gadget.h b/drivers/usb/cdns3/cdnsp-gadget.h index 12534be52f39..2afa3e558f85 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.h +++ b/drivers/usb/cdns3/cdnsp-gadget.h @@ -285,11 +285,15 @@ struct cdnsp_port_regs { #define XDEV_HS (0x3 << 10) #define XDEV_SS (0x4 << 10) #define XDEV_SSP (0x5 << 10) +#define XDEV_SSP1x2 (0x6 << 10) +#define XDEV_SSP2x2 (0x7 << 10) #define DEV_UNDEFSPEED(p) (((p) & DEV_SPEED_MASK) == (0x0 << 10)) #define DEV_FULLSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_FS) #define DEV_HIGHSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_HS) #define DEV_SUPERSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_SS) #define DEV_SUPERSPEEDPLUS(p) (((p) & DEV_SPEED_MASK) == XDEV_SSP) +#define DEV_SSP_GEN1x2(p) (((p) & DEV_SPEED_MASK) == XDEV_SSP1x2) +#define DEV_SSP_GEN2x2(p) (((p) & DEV_SPEED_MASK) == XDEV_SSP2x2) #define DEV_SUPERSPEED_ANY(p) (((p) & DEV_SPEED_MASK) >= XDEV_SS) #define DEV_PORT_SPEED(p) (((p) >> 10) & 0x0f) /* Port Link State Write Strobe - set this when changing link state */ -- cgit v1.2.3-59-g8ed1b From f4ecdc352646f7d23f348e5c544dbe3212c94fc8 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Tue, 13 May 2025 05:30:09 +0000 Subject: usb: cdnsp: Fix issue with detecting command completion event In some cases, there is a small-time gap in which CMD_RING_BUSY can be cleared by controller but adding command completion event to event ring will be delayed. As the result driver will return error code. This behavior has been detected on usbtest driver (test 9) with configuration including ep1in/ep1out bulk and ep2in/ep2out isoc endpoint. Probably this gap occurred because controller was busy with adding some other events to event ring. The CMD_RING_BUSY is cleared to '0' when the Command Descriptor has been executed and not when command completion event has been added to event ring. To fix this issue for this test the small delay is sufficient less than 10us) but to make sure the problem doesn't happen again in the future the patch introduces 10 retries to check with delay about 20us before returning error code. Fixes: 3d82904559f4 ("usb: cdnsp: cdns3 Add main part of Cadence USBSSP DRD Driver") Cc: stable Signed-off-by: Pawel Laszczak Acked-by: Peter Chen Link: https://lore.kernel.org/r/PH7PR07MB9538AA45362ACCF1B94EE9B7DD96A@PH7PR07MB9538.namprd07.prod.outlook.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/cdnsp-gadget.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c index cd1e00daf43f..55f95f41b3b4 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.c +++ b/drivers/usb/cdns3/cdnsp-gadget.c @@ -548,6 +548,7 @@ int cdnsp_wait_for_cmd_compl(struct cdnsp_device *pdev) dma_addr_t cmd_deq_dma; union cdnsp_trb *event; u32 cycle_state; + u32 retry = 10; int ret, val; u64 cmd_dma; u32 flags; @@ -579,8 +580,23 @@ int cdnsp_wait_for_cmd_compl(struct cdnsp_device *pdev) flags = le32_to_cpu(event->event_cmd.flags); /* Check the owner of the TRB. */ - if ((flags & TRB_CYCLE) != cycle_state) + if ((flags & TRB_CYCLE) != cycle_state) { + /* + * Give some extra time to get chance controller + * to finish command before returning error code. + * Checking CMD_RING_BUSY is not sufficient because + * this bit is cleared to '0' when the Command + * Descriptor has been executed by controller + * and not when command completion event has + * be added to event ring. + */ + if (retry--) { + udelay(20); + continue; + } + return -EINVAL; + } cmd_dma = le64_to_cpu(event->event_cmd.cmd_trb); -- cgit v1.2.3-59-g8ed1b From 6f399a100810a9c9af28e1d66e2c379ba03018d0 Mon Sep 17 00:00:00 2001 From: Pritam Manohar Sutar Date: Fri, 16 May 2025 12:43:32 +0530 Subject: dt-bindings: usb: samsung,exynos-dwc3: add dt-schema ExynosAutov920 Add a dedicated compatible for USB controller found in this SoC Reviewed-by: Krzysztof Kozlowski Reviewed-by: Alim Akhtar Signed-off-by: Pritam Manohar Sutar Link: https://lore.kernel.org/r/20250516071333.3223226-2-pritam.sutar@samsung.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/samsung,exynos-dwc3.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/samsung,exynos-dwc3.yaml b/Documentation/devicetree/bindings/usb/samsung,exynos-dwc3.yaml index 892545b477ac..6d39e5066944 100644 --- a/Documentation/devicetree/bindings/usb/samsung,exynos-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/samsung,exynos-dwc3.yaml @@ -20,6 +20,7 @@ properties: - samsung,exynos7-dwusb3 - samsung,exynos7870-dwusb3 - samsung,exynos850-dwusb3 + - samsung,exynosautov920-dwusb3 - items: - const: samsung,exynos990-dwusb3 - const: samsung,exynos850-dwusb3 @@ -179,6 +180,21 @@ allOf: required: - vdd10-supply + - if: + properties: + compatible: + contains: + const: samsung,exynosautov920-dwusb3 + then: + properties: + clocks: + minItems: 2 + maxItems: 2 + clock-names: + items: + - const: ref + - const: susp_clk + additionalProperties: false examples: -- cgit v1.2.3-59-g8ed1b From 657bfcbbfa38c8372408ca3f3085381b637838bd Mon Sep 17 00:00:00 2001 From: Pritam Manohar Sutar Date: Fri, 16 May 2025 12:43:33 +0530 Subject: usb: dwc3-exynos: add support for ExynosAutov920 This SoC has a DWC3 compatible controllers. It needs "ref" and "susp_clk" for it's operation. Add required changes in exynos dwc3 glue layer to support this SoC. Acked-by: Thinh Nguyen Reviewed-by: Alim Akhtar Signed-off-by: Pritam Manohar Sutar Link: https://lore.kernel.org/r/20250516071333.3223226-3-pritam.sutar@samsung.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-exynos.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/usb/dwc3/dwc3-exynos.c b/drivers/usb/dwc3/dwc3-exynos.c index 20abc6a4e824..e934f94e8fd8 100644 --- a/drivers/usb/dwc3/dwc3-exynos.c +++ b/drivers/usb/dwc3/dwc3-exynos.c @@ -187,6 +187,12 @@ static const struct dwc3_exynos_driverdata gs101_drvdata = { .suspend_clk_idx = 1, }; +static const struct dwc3_exynos_driverdata exynosautov920_drvdata = { + .clk_names = { "ref", "susp_clk"}, + .num_clks = 2, + .suspend_clk_idx = 1, +}; + static const struct of_device_id exynos_dwc3_match[] = { { .compatible = "samsung,exynos2200-dwusb3", @@ -206,6 +212,9 @@ static const struct of_device_id exynos_dwc3_match[] = { }, { .compatible = "samsung,exynos850-dwusb3", .data = &exynos850_drvdata, + }, { + .compatible = "samsung,exynosautov920-dwusb3", + .data = &exynosautov920_drvdata, }, { .compatible = "google,gs101-dwusb3", .data = &gs101_drvdata, -- cgit v1.2.3-59-g8ed1b From eb25dcf0c5946b4c167d5a9b049f0c1fec80ff80 Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Mon, 19 May 2025 14:13:17 +0800 Subject: usb: core: config: Use USB API functions rather than constants Use the function usb_endpoint_num() rather than constants. The Coccinelle semantic patch is as follows: @@ struct usb_endpoint_descriptor *epd; @@ - (epd->bEndpointAddress & \(USB_ENDPOINT_NUMBER_MASK\|0x0f\)) + usb_endpoint_num(epd) Signed-off-by: Chen Ni Link: https://lore.kernel.org/r/20250519061317.724602-1-nichen@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c index 13bd4ec4ea5f..fc0cfd94cbab 100644 --- a/drivers/usb/core/config.c +++ b/drivers/usb/core/config.c @@ -307,7 +307,7 @@ static int usb_parse_endpoint(struct device *ddev, int cfgno, goto skip_to_next_endpoint_or_interface_descriptor; } - i = d->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + i = usb_endpoint_num(d); if (i == 0) { dev_notice(ddev, "config %d interface %d altsetting %d has an " "invalid descriptor for endpoint zero, skipping\n", -- cgit v1.2.3-59-g8ed1b From 48175e2e6eaf8642fbd9d36746fc094099ee65cf Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Mon, 19 May 2025 14:22:29 +0800 Subject: usb: dwc2: gadget: Use USB API functions rather than constants Use the function usb_endpoint_type() rather than constants. The Coccinelle semantic patch is as follows: @@ struct usb_endpoint_descriptor *epd; @@ - (epd->bmAttributes & \(USB_ENDPOINT_XFERTYPE_MASK\|3\)) + usb_endpoint_type(epd) Signed-off-by: Chen Ni Link: https://lore.kernel.org/r/20250519062229.724664-1-nichen@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/gadget.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index f323fb5597b3..d5b622f78cf3 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -4049,7 +4049,7 @@ static int dwc2_hsotg_ep_enable(struct usb_ep *ep, return -EINVAL; } - ep_type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + ep_type = usb_endpoint_type(desc); mps = usb_endpoint_maxp(desc); mc = usb_endpoint_maxp_mult(desc); -- cgit v1.2.3-59-g8ed1b From ebaac1027a26ed62e0b6f24cd2d2cdf4a3ce73f2 Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Mon, 19 May 2025 14:25:44 +0800 Subject: usb: gadget: epautoconf: Use USB API functions rather than constants Use the function usb_endpoint_type() rather than constants. The Coccinelle semantic patch is as follows: @@ struct usb_endpoint_descriptor *epd; @@ - (epd->bmAttributes & \(USB_ENDPOINT_XFERTYPE_MASK\|3\)) + usb_endpoint_type(epd) Signed-off-by: Chen Ni Link: https://lore.kernel.org/r/20250519062545.724727-1-nichen@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/epautoconf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/epautoconf.c b/drivers/usb/gadget/epautoconf.c index ed5a92c474e5..30016b805bfd 100644 --- a/drivers/usb/gadget/epautoconf.c +++ b/drivers/usb/gadget/epautoconf.c @@ -158,7 +158,7 @@ struct usb_ep *usb_ep_autoconfig( if (!ep) return NULL; - type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + type = usb_endpoint_type(desc); /* report (variable) full speed bulk maxpacket */ if (type == USB_ENDPOINT_XFER_BULK) { -- cgit v1.2.3-59-g8ed1b From b48a2e0ba39838c2b6f7ee95d7b373050224c6f8 Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Mon, 19 May 2025 14:31:20 +0800 Subject: usb: gadget: lpc32xx_udc: Use USB API functions rather than constants Use the function usb_endpoint_type() rather than constants. The Coccinelle semantic patch is as follows: @@ struct usb_endpoint_descriptor *epd; @@ - (epd->bmAttributes & \(USB_ENDPOINT_XFERTYPE_MASK\|3\)) + usb_endpoint_type(epd) Signed-off-by: Chen Ni Link: https://lore.kernel.org/r/20250519063120.724793-1-nichen@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/lpc32xx_udc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/udc/lpc32xx_udc.c b/drivers/usb/gadget/udc/lpc32xx_udc.c index 89d6daf2bda7..1a7d3c4f652f 100644 --- a/drivers/usb/gadget/udc/lpc32xx_udc.c +++ b/drivers/usb/gadget/udc/lpc32xx_udc.c @@ -1629,7 +1629,7 @@ static int lpc32xx_ep_enable(struct usb_ep *_ep, return -ESHUTDOWN; } - tmp = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + tmp = usb_endpoint_type(desc); switch (tmp) { case USB_ENDPOINT_XFER_CONTROL: return -EINVAL; -- cgit v1.2.3-59-g8ed1b From ab44b9259bb39a09d92ab9ebff8e575c8ba0ff22 Mon Sep 17 00:00:00 2001 From: Mike Looijmans Date: Wed, 7 May 2025 15:11:42 +0200 Subject: usb: misc: onboard_usb_dev: Add support for TI TUSB8044 hub The TUSB8044 is similar to the TUSB8041. This adds the PID/VID values and allows to specify the reset GPIO signal on the board. Signed-off-by: Mike Looijmans Link: https://lore.kernel.org/r/20250507131143.2243079-1-mike.looijmans@topic.nl Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/onboard_usb_dev.c | 2 ++ drivers/usb/misc/onboard_usb_dev.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/drivers/usb/misc/onboard_usb_dev.c b/drivers/usb/misc/onboard_usb_dev.c index 15fa90f47c70..40bc98019e0b 100644 --- a/drivers/usb/misc/onboard_usb_dev.c +++ b/drivers/usb/misc/onboard_usb_dev.c @@ -598,6 +598,8 @@ static const struct usb_device_id onboard_dev_id_table[] = { { USB_DEVICE(VENDOR_ID_TI, 0x8027) }, /* TI USB8020B 2.0 HUB */ { USB_DEVICE(VENDOR_ID_TI, 0x8140) }, /* TI USB8041 3.0 HUB */ { USB_DEVICE(VENDOR_ID_TI, 0x8142) }, /* TI USB8041 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_TI, 0x8440) }, /* TI USB8044 3.0 HUB */ + { USB_DEVICE(VENDOR_ID_TI, 0x8442) }, /* TI USB8044 2.0 HUB */ { USB_DEVICE(VENDOR_ID_VIA, 0x0817) }, /* VIA VL817 3.1 HUB */ { USB_DEVICE(VENDOR_ID_VIA, 0x2817) }, /* VIA VL817 2.0 HUB */ { USB_DEVICE(VENDOR_ID_XMOS, 0x0013) }, /* XMOS XVF3500 Voice Processor */ diff --git a/drivers/usb/misc/onboard_usb_dev.h b/drivers/usb/misc/onboard_usb_dev.h index a5b18840c3f4..e017b8e22f93 100644 --- a/drivers/usb/misc/onboard_usb_dev.h +++ b/drivers/usb/misc/onboard_usb_dev.h @@ -125,6 +125,8 @@ static const struct of_device_id onboard_dev_match[] = { { .compatible = "usb451,8027", .data = &ti_tusb8020b_data, }, { .compatible = "usb451,8140", .data = &ti_tusb8041_data, }, { .compatible = "usb451,8142", .data = &ti_tusb8041_data, }, + { .compatible = "usb451,8440", .data = &ti_tusb8041_data, }, + { .compatible = "usb451,8442", .data = &ti_tusb8041_data, }, { .compatible = "usb4b4,6504", .data = &cypress_hx3_data, }, { .compatible = "usb4b4,6506", .data = &cypress_hx3_data, }, { .compatible = "usb4b4,6570", .data = &cypress_hx2vl_data, }, -- cgit v1.2.3-59-g8ed1b From d01ccb5b62b753c7ce527b1b38834c0ad2e9ae50 Mon Sep 17 00:00:00 2001 From: Mike Looijmans Date: Wed, 7 May 2025 15:11:43 +0200 Subject: dt-bindings: usb: ti,usb8041: Add binding for TI USB8044 hub controller The TI USB8044 is similar to the USB8041. Signed-off-by: Mike Looijmans Acked-by: Conor Dooley Link: https://lore.kernel.org/r/20250507131143.2243079-2-mike.looijmans@topic.nl Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/ti,usb8041.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/ti,usb8041.yaml b/Documentation/devicetree/bindings/usb/ti,usb8041.yaml index bce730a5e237..5e3eae9c2961 100644 --- a/Documentation/devicetree/bindings/usb/ti,usb8041.yaml +++ b/Documentation/devicetree/bindings/usb/ti,usb8041.yaml @@ -4,7 +4,7 @@ $id: http://devicetree.org/schemas/usb/ti,usb8041.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: TI USB8041 USB 3.0 hub controller +title: TI USB8041 and USB8044 USB 3.0 hub controllers maintainers: - Alexander Stein @@ -17,6 +17,8 @@ properties: enum: - usb451,8140 - usb451,8142 + - usb451,8440 + - usb451,8442 reg: true -- cgit v1.2.3-59-g8ed1b From 1143d41922c0f87504f095417ba1870167970143 Mon Sep 17 00:00:00 2001 From: Jonathan Stroud Date: Fri, 16 May 2025 18:02:40 +0530 Subject: usb: misc: onboard_usb_dev: Fix usb5744 initialization sequence Introduce i2c APIs to read/write for proper configuration register programming. It ensures that read-modify-write sequence is performed and reserved bit in Runtime Flags 2 register are not touched. Also legacy smbus block write inserted an extra count value into the i2c data stream which breaks the register write on the usb5744. Switching to new read/write i2c APIs fixes both issues. Fixes: 6782311d04df ("usb: misc: onboard_usb_dev: add Microchip usb5744 SMBus programming support") Cc: stable Signed-off-by: Jonathan Stroud Co-developed-by: Radhey Shyam Pandey Signed-off-by: Radhey Shyam Pandey Link: https://lore.kernel.org/r/1747398760-284021-1-git-send-email-radhey.shyam.pandey@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/onboard_usb_dev.c | 100 ++++++++++++++++++++++++++++++++----- 1 file changed, 87 insertions(+), 13 deletions(-) diff --git a/drivers/usb/misc/onboard_usb_dev.c b/drivers/usb/misc/onboard_usb_dev.c index 40bc98019e0b..1048e3912068 100644 --- a/drivers/usb/misc/onboard_usb_dev.c +++ b/drivers/usb/misc/onboard_usb_dev.c @@ -36,9 +36,10 @@ #define USB5744_CMD_CREG_ACCESS 0x99 #define USB5744_CMD_CREG_ACCESS_LSB 0x37 #define USB5744_CREG_MEM_ADDR 0x00 +#define USB5744_CREG_MEM_RD_ADDR 0x04 #define USB5744_CREG_WRITE 0x00 -#define USB5744_CREG_RUNTIMEFLAGS2 0x41 -#define USB5744_CREG_RUNTIMEFLAGS2_LSB 0x1D +#define USB5744_CREG_READ 0x01 +#define USB5744_CREG_RUNTIMEFLAGS2 0x411D #define USB5744_CREG_BYPASS_UDC_SUSPEND BIT(3) static void onboard_dev_attach_usb_driver(struct work_struct *work); @@ -309,11 +310,88 @@ static void onboard_dev_attach_usb_driver(struct work_struct *work) pr_err("Failed to attach USB driver: %pe\n", ERR_PTR(err)); } +static int onboard_dev_5744_i2c_read_byte(struct i2c_client *client, u16 addr, u8 *data) +{ + struct i2c_msg msg[2]; + u8 rd_buf[3]; + int ret; + + u8 wr_buf[7] = {0, USB5744_CREG_MEM_ADDR, 4, + USB5744_CREG_READ, 1, + addr >> 8 & 0xff, + addr & 0xff}; + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = sizeof(wr_buf); + msg[0].buf = wr_buf; + + ret = i2c_transfer(client->adapter, msg, 1); + if (ret < 0) + return ret; + + wr_buf[0] = USB5744_CMD_CREG_ACCESS; + wr_buf[1] = USB5744_CMD_CREG_ACCESS_LSB; + wr_buf[2] = 0; + msg[0].len = 3; + + ret = i2c_transfer(client->adapter, msg, 1); + if (ret < 0) + return ret; + + wr_buf[0] = 0; + wr_buf[1] = USB5744_CREG_MEM_RD_ADDR; + msg[0].len = 2; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 2; + msg[1].buf = rd_buf; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) + return ret; + *data = rd_buf[1]; + + return 0; +} + +static int onboard_dev_5744_i2c_write_byte(struct i2c_client *client, u16 addr, u8 data) +{ + struct i2c_msg msg[2]; + int ret; + + u8 wr_buf[8] = {0, USB5744_CREG_MEM_ADDR, 5, + USB5744_CREG_WRITE, 1, + addr >> 8 & 0xff, + addr & 0xff, + data}; + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = sizeof(wr_buf); + msg[0].buf = wr_buf; + + ret = i2c_transfer(client->adapter, msg, 1); + if (ret < 0) + return ret; + + msg[0].len = 3; + wr_buf[0] = USB5744_CMD_CREG_ACCESS; + wr_buf[1] = USB5744_CMD_CREG_ACCESS_LSB; + wr_buf[2] = 0; + + ret = i2c_transfer(client->adapter, msg, 1); + if (ret < 0) + return ret; + + return 0; +} + static int onboard_dev_5744_i2c_init(struct i2c_client *client) { #if IS_ENABLED(CONFIG_USB_ONBOARD_DEV_USB5744) struct device *dev = &client->dev; int ret; + u8 reg; /* * Set BYPASS_UDC_SUSPEND bit to ensure MCU is always enabled @@ -321,20 +399,16 @@ static int onboard_dev_5744_i2c_init(struct i2c_client *client) * The command writes 5 bytes to memory and single data byte in * configuration register. */ - char wr_buf[7] = {USB5744_CREG_MEM_ADDR, 5, - USB5744_CREG_WRITE, 1, - USB5744_CREG_RUNTIMEFLAGS2, - USB5744_CREG_RUNTIMEFLAGS2_LSB, - USB5744_CREG_BYPASS_UDC_SUSPEND}; - - ret = i2c_smbus_write_block_data(client, 0, sizeof(wr_buf), wr_buf); + ret = onboard_dev_5744_i2c_read_byte(client, + USB5744_CREG_RUNTIMEFLAGS2, ®); if (ret) - return dev_err_probe(dev, ret, "BYPASS_UDC_SUSPEND bit configuration failed\n"); + return dev_err_probe(dev, ret, "CREG_RUNTIMEFLAGS2 read failed\n"); - ret = i2c_smbus_write_word_data(client, USB5744_CMD_CREG_ACCESS, - USB5744_CMD_CREG_ACCESS_LSB); + reg |= USB5744_CREG_BYPASS_UDC_SUSPEND; + ret = onboard_dev_5744_i2c_write_byte(client, + USB5744_CREG_RUNTIMEFLAGS2, reg); if (ret) - return dev_err_probe(dev, ret, "Configuration Register Access Command failed\n"); + return dev_err_probe(dev, ret, "BYPASS_UDC_SUSPEND bit configuration failed\n"); /* Send SMBus command to boot hub. */ ret = i2c_smbus_write_word_data(client, USB5744_CMD_ATTACH, -- cgit v1.2.3-59-g8ed1b From e7144a2b3ac8d11fc106b0d161a03a898c8d6822 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Fri, 9 May 2025 12:19:40 +0200 Subject: ALSA: usb-audio: qcom: Fix an error handling path in qc_usb_audio_probe() If an error occurs after a successful qc_usb_audio_offload_init_qmi_dev() call, qc_usb_audio_cleanup_qmi_dev() should be called to release some resources as already done in the remove function. Add the missing qc_usb_audio_cleanup_qmi_dev(). Fixes: 326bbc348298 ("ALSA: usb-audio: qcom: Introduce QC USB SND offloading support") Signed-off-by: Christophe JAILLET Link: https://lore.kernel.org/r/335f54da099240d9b6c7aca0397c7d8c6bb629ac.1746785961.git.christophe.jaillet@wanadoo.fr Signed-off-by: Greg Kroah-Hartman --- sound/usb/qcom/qc_audio_offload.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c index 8b096f37ad4c..65ba4d90d438 100644 --- a/sound/usb/qcom/qc_audio_offload.c +++ b/sound/usb/qcom/qc_audio_offload.c @@ -1957,6 +1957,7 @@ static int qc_usb_audio_probe(struct auxiliary_device *auxdev, return 0; release_qmi: + qc_usb_audio_cleanup_qmi_dev(); qmi_handle_release(svc->uaudio_svc_hdl); free_svc: kfree(svc); -- cgit v1.2.3-59-g8ed1b From 485ae08592521893c9251b89d4098503934da024 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 13 May 2025 14:34:40 +0200 Subject: ALSA: qc_audio_offload: rename dma/iova/va/cpu/phys variables While trying to understand a bug in the audio offload code, I had to spend extra time due to unfortunate nameing of local variables and struct members. Change these to more conventional names that reflect the actual usage: - pointers to the CPU virtual addresses of a dma buffer get a _cpu suffix to disambiguate them for MMIO virtual addresses - MMIO virtual addresses that are mapped explicitly through the IOMMU get a _iova suffix consistently, rather than a mix of iova and va. - DMA addresses (dma_addr_t) that are in a device address space (linear or IOMMU) get a _dma suffix in place of the _pa suffix. - CPU physical (phys_addr_t) addresses get a _pa suffix. There is still a mixup with dma addresses here that I address in another patch. No functional changes are intended here. Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/20250513123442.159936-2-arnd@kernel.org Signed-off-by: Greg Kroah-Hartman --- sound/usb/qcom/qc_audio_offload.c | 136 ++++++++++++++++++------------------- sound/usb/qcom/usb_audio_qmi_v01.c | 4 +- sound/usb/qcom/usb_audio_qmi_v01.h | 4 +- 3 files changed, 72 insertions(+), 72 deletions(-) diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c index 65ba4d90d438..613e64f706ba 100644 --- a/sound/usb/qcom/qc_audio_offload.c +++ b/sound/usb/qcom/qc_audio_offload.c @@ -78,10 +78,10 @@ struct intf_info { size_t data_xfer_ring_size; unsigned long sync_xfer_ring_va; size_t sync_xfer_ring_size; - unsigned long xfer_buf_va; + unsigned long xfer_buf_iova; size_t xfer_buf_size; - phys_addr_t xfer_buf_pa; - u8 *xfer_buf; + phys_addr_t xfer_buf_dma; + u8 *xfer_buf_cpu; /* USB endpoint information */ unsigned int data_ep_pipe; @@ -396,7 +396,7 @@ static unsigned long uaudio_get_iova(unsigned long *curr_iova, struct iova_info *info, *new_info = NULL; struct list_head *curr_head; size_t tmp_size = size; - unsigned long va = 0; + unsigned long iova = 0; if (size % PAGE_SIZE) goto done; @@ -411,7 +411,7 @@ static unsigned long uaudio_get_iova(unsigned long *curr_iova, /* exact size iova_info */ if (!info->in_use && info->size == size) { info->in_use = true; - va = info->start_iova; + iova = info->start_iova; *curr_iova_size -= size; goto done; } else if (!info->in_use && tmp_size >= info->size) { @@ -421,7 +421,7 @@ static unsigned long uaudio_get_iova(unsigned long *curr_iova, if (tmp_size) continue; - va = new_info->start_iova; + iova = new_info->start_iova; for (curr_head = &new_info->list; curr_head != &info->list; curr_head = curr_head->next) { new_info = list_entry(curr_head, struct @@ -440,11 +440,11 @@ static unsigned long uaudio_get_iova(unsigned long *curr_iova, info = kzalloc(sizeof(*info), GFP_KERNEL); if (!info) { - va = 0; + iova = 0; goto done; } - va = *curr_iova; + iova = *curr_iova; info->start_iova = *curr_iova; info->size = size; info->in_use = true; @@ -453,10 +453,10 @@ static unsigned long uaudio_get_iova(unsigned long *curr_iova, list_add_tail(&info->list, head); done: - return va; + return iova; } -static void uaudio_put_iova(unsigned long va, size_t size, struct list_head +static void uaudio_put_iova(unsigned long iova, size_t size, struct list_head *head, size_t *curr_iova_size) { struct iova_info *info; @@ -464,7 +464,7 @@ static void uaudio_put_iova(unsigned long va, size_t size, struct list_head bool found = false; list_for_each_entry(info, head, list) { - if (info->start_iova == va) { + if (info->start_iova == iova) { if (!info->in_use) return; @@ -492,20 +492,20 @@ done: /** * uaudio_iommu_unmap() - unmaps iommu memory for adsp * @mtype: ring type - * @va: virtual address to unmap + * @iova: virtual address to unmap * @iova_size: region size * @mapped_iova_size: mapped region size * * Unmaps the memory region that was previously assigned to the adsp. * */ -static void uaudio_iommu_unmap(enum mem_type mtype, unsigned long va, +static void uaudio_iommu_unmap(enum mem_type mtype, unsigned long iova, size_t iova_size, size_t mapped_iova_size) { size_t umap_size; bool unmap = true; - if (!va || !iova_size) + if (!iova || !iova_size) return; switch (mtype) { @@ -517,11 +517,11 @@ static void uaudio_iommu_unmap(enum mem_type mtype, unsigned long va, break; case MEM_XFER_RING: - uaudio_put_iova(va, iova_size, &uaudio_qdev->xfer_ring_list, + uaudio_put_iova(iova, iova_size, &uaudio_qdev->xfer_ring_list, &uaudio_qdev->xfer_ring_iova_size); break; case MEM_XFER_BUF: - uaudio_put_iova(va, iova_size, &uaudio_qdev->xfer_buf_list, + uaudio_put_iova(iova, iova_size, &uaudio_qdev->xfer_buf_list, &uaudio_qdev->xfer_buf_iova_size); break; default: @@ -531,11 +531,11 @@ static void uaudio_iommu_unmap(enum mem_type mtype, unsigned long va, if (!unmap || !mapped_iova_size) return; - umap_size = iommu_unmap(uaudio_qdev->data->domain, va, mapped_iova_size); + umap_size = iommu_unmap(uaudio_qdev->data->domain, iova, mapped_iova_size); if (umap_size != mapped_iova_size) dev_err(uaudio_qdev->data->dev, "unmapped size %zu for iova 0x%08lx of mapped size %zu\n", - umap_size, va, mapped_iova_size); + umap_size, iova, mapped_iova_size); } /** @@ -556,9 +556,9 @@ static unsigned long uaudio_iommu_map(enum mem_type mtype, bool dma_coherent, struct sg_table *sgt) { struct scatterlist *sg; - unsigned long va = 0; + unsigned long iova = 0; size_t total_len = 0; - unsigned long va_sg; + unsigned long iova_sg; phys_addr_t pa_sg; bool map = true; size_t sg_len; @@ -573,18 +573,18 @@ static unsigned long uaudio_iommu_map(enum mem_type mtype, bool dma_coherent, switch (mtype) { case MEM_EVENT_RING: - va = IOVA_BASE; + iova = IOVA_BASE; /* er already mapped */ if (uaudio_qdev->er_mapped) map = false; break; case MEM_XFER_RING: - va = uaudio_get_iova(&uaudio_qdev->curr_xfer_ring_iova, + iova = uaudio_get_iova(&uaudio_qdev->curr_xfer_ring_iova, &uaudio_qdev->xfer_ring_iova_size, &uaudio_qdev->xfer_ring_list, size); break; case MEM_XFER_BUF: - va = uaudio_get_iova(&uaudio_qdev->curr_xfer_buf_iova, + iova = uaudio_get_iova(&uaudio_qdev->curr_xfer_buf_iova, &uaudio_qdev->xfer_buf_iova_size, &uaudio_qdev->xfer_buf_list, size); break; @@ -592,39 +592,39 @@ static unsigned long uaudio_iommu_map(enum mem_type mtype, bool dma_coherent, dev_err(uaudio_qdev->data->dev, "unknown mem type %d\n", mtype); } - if (!va || !map) + if (!iova || !map) goto done; if (!sgt) goto skip_sgt_map; - va_sg = va; + iova_sg = iova; for_each_sg(sgt->sgl, sg, sgt->nents, i) { sg_len = PAGE_ALIGN(sg->offset + sg->length); pa_sg = page_to_phys(sg_page(sg)); - ret = iommu_map(uaudio_qdev->data->domain, va_sg, pa_sg, sg_len, + ret = iommu_map(uaudio_qdev->data->domain, iova_sg, pa_sg, sg_len, prot, GFP_KERNEL); if (ret) { - uaudio_iommu_unmap(MEM_XFER_BUF, va, size, total_len); - va = 0; + uaudio_iommu_unmap(MEM_XFER_BUF, iova, size, total_len); + iova = 0; goto done; } - va_sg += sg_len; + iova_sg += sg_len; total_len += sg_len; } if (size != total_len) { - uaudio_iommu_unmap(MEM_XFER_BUF, va, size, total_len); - va = 0; + uaudio_iommu_unmap(MEM_XFER_BUF, iova, size, total_len); + iova = 0; } - return va; + return iova; skip_sgt_map: - iommu_map(uaudio_qdev->data->domain, va, pa, size, prot, GFP_KERNEL); + iommu_map(uaudio_qdev->data->domain, iova, pa, size, prot, GFP_KERNEL); done: - return va; + return iova; } /* looks up alias, if any, for controller DT node and returns the index */ @@ -658,15 +658,15 @@ static void uaudio_dev_intf_cleanup(struct usb_device *udev, struct intf_info *i info->sync_xfer_ring_va = 0; info->sync_xfer_ring_size = 0; - uaudio_iommu_unmap(MEM_XFER_BUF, info->xfer_buf_va, info->xfer_buf_size, + uaudio_iommu_unmap(MEM_XFER_BUF, info->xfer_buf_iova, info->xfer_buf_size, info->xfer_buf_size); - info->xfer_buf_va = 0; + info->xfer_buf_iova = 0; - usb_free_coherent(udev, info->xfer_buf_size, info->xfer_buf, - info->xfer_buf_pa); + usb_free_coherent(udev, info->xfer_buf_size, info->xfer_buf_cpu, + info->xfer_buf_dma); info->xfer_buf_size = 0; - info->xfer_buf = NULL; - info->xfer_buf_pa = 0; + info->xfer_buf_cpu = NULL; + info->xfer_buf_dma = 0; info->in_use = false; } @@ -1021,7 +1021,7 @@ static int uaudio_transfer_buffer_setup(struct snd_usb_substream *subs, phys_addr_t xfer_buf_pa; u32 len = xfer_buf_len; bool dma_coherent; - unsigned long va; + unsigned long iova; u32 remainder; u32 mult; int ret; @@ -1050,16 +1050,16 @@ static int uaudio_transfer_buffer_setup(struct snd_usb_substream *subs, dma_get_sgtable(subs->dev->bus->sysdev, &xfer_buf_sgt, xfer_buf, xfer_buf_pa, len); - va = uaudio_iommu_map(MEM_XFER_BUF, dma_coherent, xfer_buf_pa, len, + iova = uaudio_iommu_map(MEM_XFER_BUF, dma_coherent, xfer_buf_pa, len, &xfer_buf_sgt); - if (!va) { + if (!iova) { ret = -ENOMEM; goto unmap_sync; } - mem_info->pa = xfer_buf_pa; + mem_info->dma = xfer_buf_pa; mem_info->size = len; - mem_info->va = PREPEND_SID_TO_IOVA(va, uaudio_qdev->data->sid); + mem_info->iova = PREPEND_SID_TO_IOVA(iova, uaudio_qdev->data->sid); sg_free_table(&xfer_buf_sgt); return 0; @@ -1094,7 +1094,7 @@ uaudio_endpoint_setup(struct snd_usb_substream *subs, phys_addr_t tr_pa = 0; struct sg_table *sgt; bool dma_coherent; - unsigned long va; + unsigned long iova; struct page *pg; int ret = -ENODEV; @@ -1127,24 +1127,24 @@ uaudio_endpoint_setup(struct snd_usb_substream *subs, pg = sg_page(sgt->sgl); tr_pa = page_to_phys(pg); - mem_info->pa = sg_dma_address(sgt->sgl); + mem_info->dma = sg_dma_address(sgt->sgl); sg_free_table(sgt); /* data transfer ring */ - va = uaudio_iommu_map(MEM_XFER_RING, dma_coherent, tr_pa, + iova = uaudio_iommu_map(MEM_XFER_RING, dma_coherent, tr_pa, PAGE_SIZE, NULL); - if (!va) { + if (!iova) { ret = -ENOMEM; goto clear_pa; } - mem_info->va = PREPEND_SID_TO_IOVA(va, uaudio_qdev->data->sid); + mem_info->iova = PREPEND_SID_TO_IOVA(iova, uaudio_qdev->data->sid); mem_info->size = PAGE_SIZE; return 0; clear_pa: - mem_info->pa = 0; + mem_info->dma = 0; remove_ep: xhci_sideband_remove_endpoint(uadev[card_num].sb, ep); exit: @@ -1167,7 +1167,7 @@ static int uaudio_event_ring_setup(struct snd_usb_substream *subs, struct sg_table *sgt; phys_addr_t er_pa; bool dma_coherent; - unsigned long va; + unsigned long iova; struct page *pg; int ret; @@ -1192,23 +1192,23 @@ static int uaudio_event_ring_setup(struct snd_usb_substream *subs, pg = sg_page(sgt->sgl); er_pa = page_to_phys(pg); - mem_info->pa = sg_dma_address(sgt->sgl); + mem_info->dma = sg_dma_address(sgt->sgl); sg_free_table(sgt); - va = uaudio_iommu_map(MEM_EVENT_RING, dma_coherent, er_pa, + iova = uaudio_iommu_map(MEM_EVENT_RING, dma_coherent, er_pa, PAGE_SIZE, NULL); - if (!va) { + if (!iova) { ret = -ENOMEM; goto clear_pa; } - mem_info->va = PREPEND_SID_TO_IOVA(va, uaudio_qdev->data->sid); + mem_info->iova = PREPEND_SID_TO_IOVA(iova, uaudio_qdev->data->sid); mem_info->size = PAGE_SIZE; return 0; clear_pa: - mem_info->pa = 0; + mem_info->dma = 0; remove_interrupter: xhci_sideband_remove_interrupter(uadev[card_num].sb); exit: @@ -1340,7 +1340,7 @@ static int prepare_qmi_response(struct snd_usb_substream *subs, struct q6usb_offload *data; int pcm_dev_num; int card_num; - u8 *xfer_buf = NULL; + u8 *xfer_buf_cpu = NULL; int ret; pcm_dev_num = (req_msg->usb_token & QMI_STREAM_REQ_DEV_NUM_MASK) >> 8; @@ -1409,7 +1409,7 @@ static int prepare_qmi_response(struct snd_usb_substream *subs, resp->speed_info_valid = 1; - ret = uaudio_transfer_buffer_setup(subs, xfer_buf, req_msg->xfer_buff_size, + ret = uaudio_transfer_buffer_setup(subs, xfer_buf_cpu, req_msg->xfer_buff_size, &resp->xhci_mem_info.xfer_buff); if (ret < 0) { ret = -ENOMEM; @@ -1440,15 +1440,15 @@ static int prepare_qmi_response(struct snd_usb_substream *subs, /* cache intf specific info to use it for unmap and free xfer buf */ uadev[card_num].info[info_idx].data_xfer_ring_va = - IOVA_MASK(resp->xhci_mem_info.tr_data.va); + IOVA_MASK(resp->xhci_mem_info.tr_data.iova); uadev[card_num].info[info_idx].data_xfer_ring_size = PAGE_SIZE; uadev[card_num].info[info_idx].sync_xfer_ring_va = - IOVA_MASK(resp->xhci_mem_info.tr_sync.va); + IOVA_MASK(resp->xhci_mem_info.tr_sync.iova); uadev[card_num].info[info_idx].sync_xfer_ring_size = PAGE_SIZE; - uadev[card_num].info[info_idx].xfer_buf_va = - IOVA_MASK(resp->xhci_mem_info.xfer_buff.va); - uadev[card_num].info[info_idx].xfer_buf_pa = - resp->xhci_mem_info.xfer_buff.pa; + uadev[card_num].info[info_idx].xfer_buf_iova = + IOVA_MASK(resp->xhci_mem_info.xfer_buff.iova); + uadev[card_num].info[info_idx].xfer_buf_dma = + resp->xhci_mem_info.xfer_buff.dma; uadev[card_num].info[info_idx].xfer_buf_size = resp->xhci_mem_info.xfer_buff.size; uadev[card_num].info[info_idx].data_ep_pipe = subs->data_endpoint ? @@ -1459,7 +1459,7 @@ static int prepare_qmi_response(struct snd_usb_substream *subs, subs->data_endpoint->ep_num : 0; uadev[card_num].info[info_idx].sync_ep_idx = subs->sync_endpoint ? subs->sync_endpoint->ep_num : 0; - uadev[card_num].info[info_idx].xfer_buf = xfer_buf; + uadev[card_num].info[info_idx].xfer_buf_cpu = xfer_buf_cpu; uadev[card_num].info[info_idx].pcm_card_num = card_num; uadev[card_num].info[info_idx].pcm_dev_num = pcm_dev_num; uadev[card_num].info[info_idx].direction = subs->direction; @@ -1477,13 +1477,13 @@ free_sec_ring: drop_sync_ep: if (subs->sync_endpoint) { uaudio_iommu_unmap(MEM_XFER_RING, - IOVA_MASK(resp->xhci_mem_info.tr_sync.va), + IOVA_MASK(resp->xhci_mem_info.tr_sync.iova), PAGE_SIZE, PAGE_SIZE); xhci_sideband_remove_endpoint(uadev[card_num].sb, usb_pipe_endpoint(subs->dev, subs->sync_endpoint->pipe)); } drop_data_ep: - uaudio_iommu_unmap(MEM_XFER_RING, IOVA_MASK(resp->xhci_mem_info.tr_data.va), + uaudio_iommu_unmap(MEM_XFER_RING, IOVA_MASK(resp->xhci_mem_info.tr_data.iova), PAGE_SIZE, PAGE_SIZE); xhci_sideband_remove_endpoint(uadev[card_num].sb, usb_pipe_endpoint(subs->dev, subs->data_endpoint->pipe)); diff --git a/sound/usb/qcom/usb_audio_qmi_v01.c b/sound/usb/qcom/usb_audio_qmi_v01.c index 151ae7b591de..502857612277 100644 --- a/sound/usb/qcom/usb_audio_qmi_v01.c +++ b/sound/usb/qcom/usb_audio_qmi_v01.c @@ -14,7 +14,7 @@ static const struct qmi_elem_info mem_info_v01_ei[] = { .elem_size = sizeof(u64), .array_type = NO_ARRAY, .tlv_type = 0, - .offset = offsetof(struct mem_info_v01, va), + .offset = offsetof(struct mem_info_v01, iova), }, { .data_type = QMI_UNSIGNED_8_BYTE, @@ -22,7 +22,7 @@ static const struct qmi_elem_info mem_info_v01_ei[] = { .elem_size = sizeof(u64), .array_type = NO_ARRAY, .tlv_type = 0, - .offset = offsetof(struct mem_info_v01, pa), + .offset = offsetof(struct mem_info_v01, dma), }, { .data_type = QMI_UNSIGNED_4_BYTE, diff --git a/sound/usb/qcom/usb_audio_qmi_v01.h b/sound/usb/qcom/usb_audio_qmi_v01.h index f2563537ed40..a1298d75d9f8 100644 --- a/sound/usb/qcom/usb_audio_qmi_v01.h +++ b/sound/usb/qcom/usb_audio_qmi_v01.h @@ -14,8 +14,8 @@ #define QMI_UAUDIO_STREAM_IND_V01 0x0001 struct mem_info_v01 { - u64 va; - u64 pa; + u64 iova; /* mapped into sysdev */ + u64 dma; /* mapped into usb host */ u32 size; }; -- cgit v1.2.3-59-g8ed1b From 5c7ef5001292d70d09d90bc2251f7d869b9d135c Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 13 May 2025 14:34:41 +0200 Subject: ALSA: qc_audio_offload: avoid leaking xfer_buf allocation The info->xfer_buf_cpu member is set to a NULL value because the allocation happens in a different function and is only assigned to the function argument but never passed back. Pass it by reference instead to have a handle that can actually be freed by the final usb_free_coherent() call. Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/20250513123442.159936-3-arnd@kernel.org Signed-off-by: Greg Kroah-Hartman --- sound/usb/qcom/qc_audio_offload.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c index 613e64f706ba..b5f845ade6f3 100644 --- a/sound/usb/qcom/qc_audio_offload.c +++ b/sound/usb/qcom/qc_audio_offload.c @@ -1014,10 +1014,11 @@ put_suspend: * */ static int uaudio_transfer_buffer_setup(struct snd_usb_substream *subs, - u8 *xfer_buf, u32 xfer_buf_len, + void **xfer_buf_cpu, u32 xfer_buf_len, struct mem_info_v01 *mem_info) { struct sg_table xfer_buf_sgt; + void *xfer_buf; phys_addr_t xfer_buf_pa; u32 len = xfer_buf_len; bool dma_coherent; @@ -1060,6 +1061,7 @@ static int uaudio_transfer_buffer_setup(struct snd_usb_substream *subs, mem_info->dma = xfer_buf_pa; mem_info->size = len; mem_info->iova = PREPEND_SID_TO_IOVA(iova, uaudio_qdev->data->sid); + *xfer_buf_cpu = xfer_buf; sg_free_table(&xfer_buf_sgt); return 0; @@ -1340,7 +1342,7 @@ static int prepare_qmi_response(struct snd_usb_substream *subs, struct q6usb_offload *data; int pcm_dev_num; int card_num; - u8 *xfer_buf_cpu = NULL; + void *xfer_buf_cpu; int ret; pcm_dev_num = (req_msg->usb_token & QMI_STREAM_REQ_DEV_NUM_MASK) >> 8; @@ -1409,7 +1411,7 @@ static int prepare_qmi_response(struct snd_usb_substream *subs, resp->speed_info_valid = 1; - ret = uaudio_transfer_buffer_setup(subs, xfer_buf_cpu, req_msg->xfer_buff_size, + ret = uaudio_transfer_buffer_setup(subs, &xfer_buf_cpu, req_msg->xfer_buff_size, &resp->xhci_mem_info.xfer_buff); if (ret < 0) { ret = -ENOMEM; -- cgit v1.2.3-59-g8ed1b From 3335a1bbd62417fc60a043c7f64684c99cb841a2 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 13 May 2025 14:34:42 +0200 Subject: ALSA: qc_audio_offload: try to reduce address space confusion uaudio_transfer_buffer_setup() allocates a buffer for the subs->dev device, and the returned address for the buffer is a CPU local virtual address that may or may not be in the linear mapping, as well as a DMA address token that is accessible by the USB device, and this in turn may or may not correspond to the physical address. The use in the driver however assumes that these addresses are the linear map and the CPU physical address, respectively. Both are nonportable here, but in the end only the virtual address gets used by converting it to a physical address that gets mapped into a second iommu. Make this more explicit by pulling the conversion out first and warning if it is not part of the linear map, and using the actual physical address to map into the iommu in place of the dma address that may already be iommu-mapped into the usb host. Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/20250513123442.159936-4-arnd@kernel.org Signed-off-by: Greg Kroah-Hartman --- sound/usb/qcom/qc_audio_offload.c | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c index b5f845ade6f3..5bc27c82e0af 100644 --- a/sound/usb/qcom/qc_audio_offload.c +++ b/sound/usb/qcom/qc_audio_offload.c @@ -78,9 +78,9 @@ struct intf_info { size_t data_xfer_ring_size; unsigned long sync_xfer_ring_va; size_t sync_xfer_ring_size; - unsigned long xfer_buf_iova; + dma_addr_t xfer_buf_iova; size_t xfer_buf_size; - phys_addr_t xfer_buf_dma; + dma_addr_t xfer_buf_dma; u8 *xfer_buf_cpu; /* USB endpoint information */ @@ -1018,11 +1018,12 @@ static int uaudio_transfer_buffer_setup(struct snd_usb_substream *subs, struct mem_info_v01 *mem_info) { struct sg_table xfer_buf_sgt; + dma_addr_t xfer_buf_dma; void *xfer_buf; phys_addr_t xfer_buf_pa; u32 len = xfer_buf_len; bool dma_coherent; - unsigned long iova; + dma_addr_t xfer_buf_dma_sysdev; u32 remainder; u32 mult; int ret; @@ -1045,29 +1046,38 @@ static int uaudio_transfer_buffer_setup(struct snd_usb_substream *subs, len = MAX_XFER_BUFF_LEN; } - xfer_buf = usb_alloc_coherent(subs->dev, len, GFP_KERNEL, &xfer_buf_pa); + /* get buffer mapped into subs->dev */ + xfer_buf = usb_alloc_coherent(subs->dev, len, GFP_KERNEL, &xfer_buf_dma); if (!xfer_buf) return -ENOMEM; + /* Remapping is not possible if xfer_buf is outside of linear map */ + xfer_buf_pa = virt_to_phys(xfer_buf); + if (WARN_ON(!page_is_ram(PFN_DOWN(xfer_buf_pa)))) { + ret = -ENXIO; + goto unmap_sync; + } dma_get_sgtable(subs->dev->bus->sysdev, &xfer_buf_sgt, xfer_buf, - xfer_buf_pa, len); - iova = uaudio_iommu_map(MEM_XFER_BUF, dma_coherent, xfer_buf_pa, len, - &xfer_buf_sgt); - if (!iova) { + xfer_buf_dma, len); + + /* map the physical buffer into sysdev as well */ + xfer_buf_dma_sysdev = uaudio_iommu_map(MEM_XFER_BUF, dma_coherent, + xfer_buf_pa, len, &xfer_buf_sgt); + if (!xfer_buf_dma_sysdev) { ret = -ENOMEM; goto unmap_sync; } - mem_info->dma = xfer_buf_pa; + mem_info->dma = xfer_buf_dma; mem_info->size = len; - mem_info->iova = PREPEND_SID_TO_IOVA(iova, uaudio_qdev->data->sid); + mem_info->iova = PREPEND_SID_TO_IOVA(xfer_buf_dma_sysdev, uaudio_qdev->data->sid); *xfer_buf_cpu = xfer_buf; sg_free_table(&xfer_buf_sgt); return 0; unmap_sync: - usb_free_coherent(subs->dev, len, xfer_buf, xfer_buf_pa); + usb_free_coherent(subs->dev, len, xfer_buf, xfer_buf_dma); return ret; } -- cgit v1.2.3-59-g8ed1b From acb3dac2805d3342ded7dbbd164add32bbfdf21c Mon Sep 17 00:00:00 2001 From: Dave Penkler Date: Wed, 21 May 2025 14:16:55 +0200 Subject: usb: usbtmc: Fix read_stb function and get_stb ioctl The usbtmc488_ioctl_read_stb function relied on a positive return from usbtmc_get_stb to reset the srq condition in the driver. The USBTMC_IOCTL_GET_STB case tested for a positive return to return the stb to the user. Commit: ("usb: usbtmc: Fix erroneous get_stb ioctl error returns") changed the return value of usbtmc_get_stb to 0 on success instead of returning the value of usb_control_msg which is positive in the normal case. This change caused the function usbtmc488_ioctl_read_stb and the USBTMC_IOCTL_GET_STB ioctl to no longer function correctly. Change the test in usbtmc488_ioctl_read_stb to test for failure first and return the failure code immediately. Change the test for the USBTMC_IOCTL_GET_STB ioctl to test for 0 instead of a positive value. Fixes: cac01bd178d6 ("usb: usbtmc: Fix erroneous get_stb ioctl error returns") Cc: stable@vger.kernel.org Signed-off-by: Dave Penkler Link: https://lore.kernel.org/r/20250521121656.18174-3-dpenkler@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/usbtmc.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 740d2d2b19fb..08511442a27f 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -563,14 +563,15 @@ static int usbtmc488_ioctl_read_stb(struct usbtmc_file_data *file_data, rv = usbtmc_get_stb(file_data, &stb); - if (rv > 0) { - srq_asserted = atomic_xchg(&file_data->srq_asserted, - srq_asserted); - if (srq_asserted) - stb |= 0x40; /* Set RQS bit */ + if (rv < 0) + return rv; + + srq_asserted = atomic_xchg(&file_data->srq_asserted, srq_asserted); + if (srq_asserted) + stb |= 0x40; /* Set RQS bit */ + + rv = put_user(stb, (__u8 __user *)arg); - rv = put_user(stb, (__u8 __user *)arg); - } return rv; } @@ -2199,7 +2200,7 @@ static long usbtmc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) case USBTMC_IOCTL_GET_STB: retval = usbtmc_get_stb(file_data, &tmp_byte); - if (retval > 0) + if (!retval) retval = put_user(tmp_byte, (__u8 __user *)arg); break; -- cgit v1.2.3-59-g8ed1b From 342e4955a1f1ce28c70a589999b76365082dbf10 Mon Sep 17 00:00:00 2001 From: Dave Penkler Date: Wed, 21 May 2025 14:16:56 +0200 Subject: usb: usbtmc: Fix timeout value in get_stb wait_event_interruptible_timeout requires a timeout argument in units of jiffies. It was being called in usbtmc_get_stb with the usb timeout value which is in units of milliseconds. Pass the timeout argument converted to jiffies. Fixes: 048c6d88a021 ("usb: usbtmc: Add ioctls to set/get usb timeout") Cc: stable@vger.kernel.org Signed-off-by: Dave Penkler Link: https://lore.kernel.org/r/20250521121656.18174-4-dpenkler@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/usbtmc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 08511442a27f..75de29725a45 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -483,6 +483,7 @@ static int usbtmc_get_stb(struct usbtmc_file_data *file_data, __u8 *stb) u8 tag; int rv; long wait_rv; + unsigned long expire; dev_dbg(dev, "Enter ioctl_read_stb iin_ep_present: %d\n", data->iin_ep_present); @@ -512,10 +513,11 @@ static int usbtmc_get_stb(struct usbtmc_file_data *file_data, __u8 *stb) } if (data->iin_ep_present) { + expire = msecs_to_jiffies(file_data->timeout); wait_rv = wait_event_interruptible_timeout( data->waitq, atomic_read(&data->iin_data_valid) != 0, - file_data->timeout); + expire); if (wait_rv < 0) { dev_dbg(dev, "wait interrupted %ld\n", wait_rv); rv = wait_rv; -- cgit v1.2.3-59-g8ed1b From 92cd405b648605db4da866f3b9818b271ae84ef0 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 21 May 2025 15:41:34 +0200 Subject: USB: serial: bus: fix const issue in usb_serial_device_match() usb_serial_device_match() takes a const pointer, and then decides to cast it away into a non-const one, which is not a good thing to do overall. Fix this up by properly setting the pointers to be const to preserve that attribute. Fixes: d69d80484598 ("driver core: have match() callback in struct bus_type take a const *") Signed-off-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/serial/bus.c b/drivers/usb/serial/bus.c index 2fea1b1db4a2..9e2a18c0b218 100644 --- a/drivers/usb/serial/bus.c +++ b/drivers/usb/serial/bus.c @@ -17,7 +17,7 @@ static int usb_serial_device_match(struct device *dev, const struct device_driver *drv) { const struct usb_serial_port *port = to_usb_serial_port(dev); - struct usb_serial_driver *driver = to_usb_serial_driver(drv); + const struct usb_serial_driver *driver = to_usb_serial_driver(drv); /* * drivers are already assigned to ports in serial_probe so it's -- cgit v1.2.3-59-g8ed1b From d3a889482bd5abf2bbdc1ec3d2d49575aa160c9c Mon Sep 17 00:00:00 2001 From: Charles Yeh Date: Wed, 21 May 2025 21:23:54 +0800 Subject: USB: serial: pl2303: add new chip PL2303GC-Q20 and PL2303GT-2AB Add new bcd (0x905) to support PL2303GT-2AB (TYPE_HXN). Add new bcd (0x1005) to support PL2303GC-Q20 (TYPE_HXN). Signed-off-by: Charles Yeh Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/pl2303.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c index 010688dd9e49..22579d0d8ab8 100644 --- a/drivers/usb/serial/pl2303.c +++ b/drivers/usb/serial/pl2303.c @@ -458,6 +458,8 @@ static int pl2303_detect_type(struct usb_serial *serial) case 0x605: case 0x700: /* GR */ case 0x705: + case 0x905: /* GT-2AB */ + case 0x1005: /* GC-Q20 */ return TYPE_HXN; } break; -- cgit v1.2.3-59-g8ed1b From d1d89e8eee6f0e91cfad2a0375ad679fd5c1ae83 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 21 May 2025 15:41:46 +0200 Subject: USB: gadget: fix up const issue with struct usb_function_instance In struct usb_function, the struct usb_function_instance pointer variable "fi" is listed as const, but it is written to in numerous places, making the const marking of it a total lie. Fix this up by just removing the const pointer attribute as this is modified in numerous places. Link: https://lore.kernel.org/r/2025052145-undress-puma-f7cf@gregkh Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_mass_storage.h | 2 +- include/linux/usb/composite.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/function/f_mass_storage.h b/drivers/usb/gadget/function/f_mass_storage.h index 3b8c4ce2a40a..82ecd3fedb3a 100644 --- a/drivers/usb/gadget/function/f_mass_storage.h +++ b/drivers/usb/gadget/function/f_mass_storage.h @@ -110,7 +110,7 @@ struct fsg_config { }; static inline struct fsg_opts * -fsg_opts_from_func_inst(const struct usb_function_instance *fi) +fsg_opts_from_func_inst(struct usb_function_instance *fi) { return container_of(fi, struct fsg_opts, func_inst); } diff --git a/include/linux/usb/composite.h b/include/linux/usb/composite.h index 6e38fb9d2117..d8c4e9f73839 100644 --- a/include/linux/usb/composite.h +++ b/include/linux/usb/composite.h @@ -237,7 +237,7 @@ struct usb_function { /* internals */ struct list_head list; DECLARE_BITMAP(endpoints, 32); - const struct usb_function_instance *fi; + struct usb_function_instance *fi; unsigned int bind_deactivated:1; }; -- cgit v1.2.3-59-g8ed1b From 5f5cc794fac605afd3bef8065e33096aeacf6257 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 21 May 2025 15:41:40 +0200 Subject: USB: gadget: udc: fix const issue in gadget_match_driver() gadget_match_driver() takes a const pointer, and then decides to cast it away into a non-const one, which is not a good thing to do overall. Fix this up by properly setting the pointers to be const to preserve that attribute. Fixes: d69d80484598 ("driver core: have match() callback in struct bus_type take a const *") Link: https://lore.kernel.org/r/2025052139-rash-unsaddle-7c5e@gregkh Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index 4b3d5075621a..d709e24c1fd4 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -1570,7 +1570,7 @@ static int gadget_match_driver(struct device *dev, const struct device_driver *d { struct usb_gadget *gadget = dev_to_usb_gadget(dev); struct usb_udc *udc = gadget->udc; - struct usb_gadget_driver *driver = container_of(drv, + const struct usb_gadget_driver *driver = container_of(drv, struct usb_gadget_driver, driver); /* If the driver specifies a udc_name, it must match the UDC's name */ -- cgit v1.2.3-59-g8ed1b From ae4432e01dd967a64f6670a152d91d5328032726 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 21 May 2025 15:35:24 +0200 Subject: USB: typec: fix const issue in typec_match() typec_match() takes a const pointer, and then decides to cast it away into a non-const one, which is not a good thing to do overall. Fix this up by properly setting the pointers to be const to preserve that attribute. Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/2025052126-scholar-stainless-ad55@gregkh Fixes: d69d80484598 ("driver core: have match() callback in struct bus_type take a const *") Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/typec/bus.c b/drivers/usb/typec/bus.c index ae90688d23e4..a884cec9ab7e 100644 --- a/drivers/usb/typec/bus.c +++ b/drivers/usb/typec/bus.c @@ -449,7 +449,7 @@ ATTRIBUTE_GROUPS(typec); static int typec_match(struct device *dev, const struct device_driver *driver) { - struct typec_altmode_driver *drv = to_altmode_driver(driver); + const struct typec_altmode_driver *drv = to_altmode_driver(driver); struct typec_altmode *altmode = to_typec_altmode(dev); const struct typec_device_id *id; -- cgit v1.2.3-59-g8ed1b From e60acc420368e3fcb8c158207d37a502c4eee9e2 Mon Sep 17 00:00:00 2001 From: Jihed Chaibi Date: Wed, 21 May 2025 23:48:51 +0200 Subject: usb: typec: tipd: fix typo in TPS_STATUS_HIGH_VOLAGE_WARNING macro "VOLAGE" should become "VOLTAGE" Signed-off-by: Jihed Chaibi Reviewed-by: Brigham Campbell Link: https://lore.kernel.org/r/20250521214851.386796-1-jihed.chaibi.dev@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/tps6598x.h | 2 +- drivers/usb/typec/tipd/trace.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index 9b23e9017452..cecb8d11d239 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -27,7 +27,7 @@ #define TPS_STATUS_OVERCURRENT BIT(16) #define TPS_STATUS_GOTO_MIN_ACTIVE BIT(26) #define TPS_STATUS_BIST BIT(27) -#define TPS_STATUS_HIGH_VOLAGE_WARNING BIT(28) +#define TPS_STATUS_HIGH_VOLTAGE_WARNING BIT(28) #define TPS_STATUS_HIGH_LOW_VOLTAGE_WARNING BIT(29) #define TPS_STATUS_CONN_STATE_MASK GENMASK(3, 1) diff --git a/drivers/usb/typec/tipd/trace.h b/drivers/usb/typec/tipd/trace.h index 0669cca12ea1..bea383f2db9d 100644 --- a/drivers/usb/typec/tipd/trace.h +++ b/drivers/usb/typec/tipd/trace.h @@ -153,7 +153,7 @@ { TPS_STATUS_OVERCURRENT, "OVERCURRENT" }, \ { TPS_STATUS_GOTO_MIN_ACTIVE, "GOTO_MIN_ACTIVE" }, \ { TPS_STATUS_BIST, "BIST" }, \ - { TPS_STATUS_HIGH_VOLAGE_WARNING, "HIGH_VOLAGE_WARNING" }, \ + { TPS_STATUS_HIGH_VOLTAGE_WARNING, "HIGH_VOLTAGE_WARNING" }, \ { TPS_STATUS_HIGH_LOW_VOLTAGE_WARNING, "HIGH_LOW_VOLTAGE_WARNING" }) #define show_tps25750_status_flags(flags) \ -- cgit v1.2.3-59-g8ed1b From 662a9ece32add94469138ae66999ee16cb37a531 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 23 May 2025 14:09:43 +0200 Subject: usb: misc: onboard_usb_dev: fix build warning for CONFIG_USB_ONBOARD_DEV_USB5744=n When the USB5744 option is disabled, the onboard_usb driver warns about unused functions: drivers/usb/misc/onboard_usb_dev.c:358:12: error: 'onboard_dev_5744_i2c_write_byte' defined but not used [-Werror=unused-function] 358 | static int onboard_dev_5744_i2c_write_byte(struct i2c_client *client, u16 addr, u8 data) | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/usb/misc/onboard_usb_dev.c:313:12: error: 'onboard_dev_5744_i2c_read_byte' defined but not used [-Werror=unused-function] 313 | static int onboard_dev_5744_i2c_read_byte(struct i2c_client *client, u16 addr, u8 *data) | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Extend the #ifdef block a little further to cover all of these functions. Ideally we'd use use if(IS_ENABLED()) instead, but that doesn't currently work because the i2c_transfer() and i2c_smbus_write_word_data() function declarations are hidden when CONFIG_I2C is disabled. Fixes: 1143d41922c0 ("usb: misc: onboard_usb_dev: Fix usb5744 initialization sequence") Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/20250523120947.2170302-1-arnd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/onboard_usb_dev.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/usb/misc/onboard_usb_dev.c b/drivers/usb/misc/onboard_usb_dev.c index 1048e3912068..5b481876af1b 100644 --- a/drivers/usb/misc/onboard_usb_dev.c +++ b/drivers/usb/misc/onboard_usb_dev.c @@ -310,6 +310,7 @@ static void onboard_dev_attach_usb_driver(struct work_struct *work) pr_err("Failed to attach USB driver: %pe\n", ERR_PTR(err)); } +#if IS_ENABLED(CONFIG_USB_ONBOARD_DEV_USB5744) static int onboard_dev_5744_i2c_read_byte(struct i2c_client *client, u16 addr, u8 *data) { struct i2c_msg msg[2]; @@ -388,7 +389,6 @@ static int onboard_dev_5744_i2c_write_byte(struct i2c_client *client, u16 addr, static int onboard_dev_5744_i2c_init(struct i2c_client *client) { -#if IS_ENABLED(CONFIG_USB_ONBOARD_DEV_USB5744) struct device *dev = &client->dev; int ret; u8 reg; @@ -417,10 +417,13 @@ static int onboard_dev_5744_i2c_init(struct i2c_client *client) return dev_err_probe(dev, ret, "USB Attach with SMBus command failed\n"); return ret; +} #else +static int onboard_dev_5744_i2c_init(struct i2c_client *client) +{ return -ENODEV; -#endif } +#endif static int onboard_dev_probe(struct platform_device *pdev) { -- cgit v1.2.3-59-g8ed1b From e2d8ae899760ff71168d08047962caae07ab36a1 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 23 May 2025 14:11:47 +0200 Subject: ASoC: qdsp6: fix compile-testing without CONFIG_OF The driver builds cleanly only when CONFIG_OF is enabled, otherwise the compiler notices an unused symbol: sound/soc/qcom/qdsp6/q6usb.c:401:34: error: 'q6usb_dai_device_id' defined but not used [-Werror=unused-const-variable=] The driver does not support legacy board files, so the of_match_ptr() annotation has no use here and can be removed to avoid the warning. Fixes: e0dd9240f13a ("ASoC: qcom: qdsp6: Fetch USB offload mapped card and PCM device") Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/20250523121152.2292574-1-arnd@kernel.org Signed-off-by: Greg Kroah-Hartman --- sound/soc/qcom/qdsp6/q6usb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/qcom/qdsp6/q6usb.c b/sound/soc/qcom/qdsp6/q6usb.c index adba0446f301..ebe0c2425927 100644 --- a/sound/soc/qcom/qdsp6/q6usb.c +++ b/sound/soc/qcom/qdsp6/q6usb.c @@ -407,7 +407,7 @@ MODULE_DEVICE_TABLE(of, q6usb_dai_device_id); static struct platform_driver q6usb_dai_platform_driver = { .driver = { .name = "q6usb-dai", - .of_match_table = of_match_ptr(q6usb_dai_device_id), + .of_match_table = q6usb_dai_device_id, }, .probe = q6usb_dai_dev_probe, /* -- cgit v1.2.3-59-g8ed1b From 882826f58b2c48cafc7084a799207e76f2c74fe0 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 21 May 2025 15:55:43 +0200 Subject: ALSA: usb-audio: qcom: fix USB_XHCI dependency SND_USB_AUDIO_QMI depends on USB_XHCI_SIDEBAND, but that is a bool symbol and allows it to be built-in even when XHCI itself is in a loadable module. That configuration causes a link failure: arm-linux-gnueabi-ld: sound/usb/qcom/qc_audio_offload.o: in function `uaudio_event_ring_cleanup_free': qc_audio_offload.c:(.text+0x7dc): undefined reference to `xhci_sideband_remove_interrupter' arm-linux-gnueabi-ld: sound/usb/qcom/qc_audio_offload.o: in function `uaudio_endpoint_setup': qc_audio_offload.c:(.text+0xe88): undefined reference to `xhci_sideband_add_endpoint' Add the extra dependency on USB_XHCI itself. Fixes: 326bbc348298 ("ALSA: usb-audio: qcom: Introduce QC USB SND offloading support") Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/20250521135551.2111109-1-arnd@kernel.org Signed-off-by: Greg Kroah-Hartman --- sound/usb/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig index 6daa551738da..41c47301bc19 100644 --- a/sound/usb/Kconfig +++ b/sound/usb/Kconfig @@ -178,7 +178,8 @@ config SND_BCD2000 config SND_USB_AUDIO_QMI tristate "Qualcomm Audio Offload driver" - depends on QCOM_QMI_HELPERS && SND_USB_AUDIO && USB_XHCI_SIDEBAND && SND_SOC_USB + depends on QCOM_QMI_HELPERS && SND_USB_AUDIO && SND_SOC_USB + depends on USB_XHCI_HCD && USB_XHCI_SIDEBAND help Say Y here to enable the Qualcomm USB audio offloading feature. -- cgit v1.2.3-59-g8ed1b