From 82096ecf589d8224447213c4592e54598fea0b9d Mon Sep 17 00:00:00 2001 From: Tian Tao Date: Wed, 23 Dec 2020 09:26:33 +0800 Subject: thunderbolt: Use kmemdup instead of kzalloc and memcpy Fixes coccicheck warning: drivers/thunderbolt/dma_test.c:302:13-20: WARNING opportunity for kmemdup. Signed-off-by: Tian Tao Signed-off-by: Mika Westerberg --- drivers/thunderbolt/dma_test.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/thunderbolt/dma_test.c b/drivers/thunderbolt/dma_test.c index f924423fa180..955f980986ca 100644 --- a/drivers/thunderbolt/dma_test.c +++ b/drivers/thunderbolt/dma_test.c @@ -299,14 +299,12 @@ static int dma_test_submit_tx(struct dma_test *dt, size_t npackets) tf->frame.size = 0; /* means 4096 */ tf->dma_test = dt; - tf->data = kzalloc(DMA_TEST_FRAME_SIZE, GFP_KERNEL); + tf->data = kmemdup(dma_test_pattern, DMA_TEST_FRAME_SIZE, GFP_KERNEL); if (!tf->data) { kfree(tf); return -ENOMEM; } - memcpy(tf->data, dma_test_pattern, DMA_TEST_FRAME_SIZE); - dma_addr = dma_map_single(dma_dev, tf->data, DMA_TEST_FRAME_SIZE, DMA_TO_DEVICE); if (dma_mapping_error(dma_dev, dma_addr)) { -- cgit v1.2.3 From 894f1f4f49a31887358025db03cb106efb36d8b4 Mon Sep 17 00:00:00 2001 From: Nazime Hande Harputluoglu Date: Tue, 24 Nov 2020 00:50:52 +0100 Subject: kcov, usbip: collect coverage from vhci_rx_loop Add kcov_remote_start()/kcov_remote_stop() annotations to the vhci_rx_loop() function, which is responsible for parsing USB/IP packets coming into USB/IP client. Since vhci_rx_loop() threads are spawned per vhci_hcd device instance, the common kcov handle is used for kcov_remote_start()/stop() annotations (see Documentation/dev-tools/kcov.rst for details). As the result kcov can now be used to collect coverage from vhci_rx_loop() threads. Co-developed-by: Andrey Konovalov Acked-by: Shuah Khan Signed-off-by: Nazime Hande Harputluoglu Signed-off-by: Andrey Konovalov Link: https://lore.kernel.org/r/f8114050f8d65aa0bc801318b1db532d9f432447.1606175386.git.andreyknvl@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/usbip_common.h | 29 +++++++++++++++++++++++++++++ drivers/usb/usbip/vhci_rx.c | 2 ++ drivers/usb/usbip/vhci_sysfs.c | 1 + 3 files changed, 32 insertions(+) diff --git a/drivers/usb/usbip/usbip_common.h b/drivers/usb/usbip/usbip_common.h index 8be857a4fa13..d60ce17d3dd2 100644 --- a/drivers/usb/usbip/usbip_common.h +++ b/drivers/usb/usbip/usbip_common.h @@ -277,6 +277,10 @@ struct usbip_device { void (*reset)(struct usbip_device *); void (*unusable)(struct usbip_device *); } eh_ops; + +#ifdef CONFIG_KCOV + u64 kcov_handle; +#endif }; #define kthread_get_run(threadfn, data, namefmt, ...) \ @@ -337,4 +341,29 @@ static inline int interface_to_devnum(struct usb_interface *interface) return udev->devnum; } +#ifdef CONFIG_KCOV + +static inline void usbip_kcov_handle_init(struct usbip_device *ud) +{ + ud->kcov_handle = kcov_common_handle(); +} + +static inline void usbip_kcov_remote_start(struct usbip_device *ud) +{ + kcov_remote_start_common(ud->kcov_handle); +} + +static inline void usbip_kcov_remote_stop(void) +{ + kcov_remote_stop(); +} + +#else /* CONFIG_KCOV */ + +static inline void usbip_kcov_handle_init(struct usbip_device *ud) { } +static inline void usbip_kcov_remote_start(struct usbip_device *ud) { } +static inline void usbip_kcov_remote_stop(void) { } + +#endif /* CONFIG_KCOV */ + #endif /* __USBIP_COMMON_H */ diff --git a/drivers/usb/usbip/vhci_rx.c b/drivers/usb/usbip/vhci_rx.c index 266024cbb64f..7f2d1c241559 100644 --- a/drivers/usb/usbip/vhci_rx.c +++ b/drivers/usb/usbip/vhci_rx.c @@ -261,7 +261,9 @@ int vhci_rx_loop(void *data) if (usbip_event_happened(ud)) break; + usbip_kcov_remote_start(ud); vhci_rx_pdu(ud); + usbip_kcov_remote_stop(); } return 0; diff --git a/drivers/usb/usbip/vhci_sysfs.c b/drivers/usb/usbip/vhci_sysfs.c index be37aec250c2..96e5371dc335 100644 --- a/drivers/usb/usbip/vhci_sysfs.c +++ b/drivers/usb/usbip/vhci_sysfs.c @@ -383,6 +383,7 @@ static ssize_t attach_store(struct device *dev, struct device_attribute *attr, vdev->ud.sockfd = sockfd; vdev->ud.tcp_socket = socket; vdev->ud.status = VDEV_ST_NOTASSIGNED; + usbip_kcov_handle_init(&vdev->ud); spin_unlock(&vdev->ud.lock); spin_unlock_irqrestore(&vhci->lock, flags); -- cgit v1.2.3 From 3c1037e2b6a94898f81ed1a68bea146a9db750a5 Mon Sep 17 00:00:00 2001 From: Dave Penkler Date: Tue, 15 Dec 2020 16:56:18 +0100 Subject: USB: usbtmc: Fix reading stale status byte The ioctl USBTMC488_IOCTL_READ_STB either returns a cached status byte (STB) sent by the device due to a service request (SRQ) condition or the STB obtained from a query to the device with a READ_STATUS_BYTE control message. When the query is interrupted by an SRQ message on the interrupt pipe, the ioctl still returns the requested STB while the STB of the out-of-band SRQ message is cached for the next call of this ioctl. However the cached SRQ STB represents a state that was previous to the last returned STB. Furthermore the cached SRQ STB can be stale and not reflect the current state of the device. The fixed ioctl now always reads the STB from the device and if the associated file descriptor has the srq_asserted bit set it ors in the RQS bit to the returned STB and clears the srq_asserted bit conformant to subclass USB488 devices. Tested-by: Jian-Wei Wu Reviewed-by: Guido Kiener Signed-off-by: Dave Penkler Link: https://lore.kernel.org/r/20201215155621.9592-2-dpenkler@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/usbtmc.c | 46 +++++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index b222b777e6a4..189f06dcb7d3 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -475,33 +475,17 @@ static int usbtmc_ioctl_abort_bulk_out(struct usbtmc_device_data *data) return usbtmc_ioctl_abort_bulk_out_tag(data, data->bTag_last_write); } -static int usbtmc488_ioctl_read_stb(struct usbtmc_file_data *file_data, - void __user *arg) +static int usbtmc_get_stb(struct usbtmc_file_data *file_data, __u8 *stb) { struct usbtmc_device_data *data = file_data->data; struct device *dev = &data->intf->dev; - int srq_asserted = 0; u8 *buffer; u8 tag; - __u8 stb; int rv; dev_dbg(dev, "Enter ioctl_read_stb iin_ep_present: %d\n", data->iin_ep_present); - spin_lock_irq(&data->dev_lock); - srq_asserted = atomic_xchg(&file_data->srq_asserted, srq_asserted); - if (srq_asserted) { - /* a STB with SRQ is already received */ - stb = file_data->srq_byte; - spin_unlock_irq(&data->dev_lock); - rv = put_user(stb, (__u8 __user *)arg); - dev_dbg(dev, "stb:0x%02x with srq received %d\n", - (unsigned int)stb, rv); - return rv; - } - spin_unlock_irq(&data->dev_lock); - buffer = kmalloc(8, GFP_KERNEL); if (!buffer) return -ENOMEM; @@ -548,13 +532,12 @@ static int usbtmc488_ioctl_read_stb(struct usbtmc_file_data *file_data, data->iin_bTag, tag); } - stb = data->bNotify2; + *stb = data->bNotify2; } else { - stb = buffer[2]; + *stb = buffer[2]; } - rv = put_user(stb, (__u8 __user *)arg); - dev_dbg(dev, "stb:0x%02x received %d\n", (unsigned int)stb, rv); + dev_dbg(dev, "stb:0x%02x received %d\n", (unsigned int)*stb, rv); exit: /* bump interrupt bTag */ @@ -567,6 +550,27 @@ static int usbtmc488_ioctl_read_stb(struct usbtmc_file_data *file_data, return rv; } +static int usbtmc488_ioctl_read_stb(struct usbtmc_file_data *file_data, + void __user *arg) +{ + int srq_asserted = 0; + __u8 stb; + int rv; + + 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 */ + + rv = put_user(stb, (__u8 __user *)arg); + } + return rv; + +} + static int usbtmc488_ioctl_wait_srq(struct usbtmc_file_data *file_data, __u32 __user *arg) { -- cgit v1.2.3 From c9784e23c1020e63d6dba5e10ca8bf3d8b85c19c Mon Sep 17 00:00:00 2001 From: Dave Penkler Date: Tue, 15 Dec 2020 16:56:19 +0100 Subject: USB: usbtmc: Add USBTMC_IOCTL_GET_STB This new ioctl reads the status byte (STB) from the device and returns the STB unmodified to the application. The srq_asserted bit is not taken into account and not changed. This ioctl is useful to support non USBTMC-488 compliant devices. Tested-by: Jian-Wei Wu Reviewed-by: Guido Kiener Signed-off-by: Dave Penkler Link: https://lore.kernel.org/r/20201215155621.9592-3-dpenkler@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/usbtmc.c | 6 ++++++ include/uapi/linux/usb/tmc.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 189f06dcb7d3..8918e2182eca 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -2149,6 +2149,12 @@ static long usbtmc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) file_data->auto_abort = !!tmp_byte; break; + case USBTMC_IOCTL_GET_STB: + retval = usbtmc_get_stb(file_data, &tmp_byte); + if (retval > 0) + retval = put_user(tmp_byte, (__u8 __user *)arg); + break; + case USBTMC_IOCTL_CANCEL_IO: retval = usbtmc_ioctl_cancel_io(file_data); break; diff --git a/include/uapi/linux/usb/tmc.h b/include/uapi/linux/usb/tmc.h index fdd4d88a7b95..1e7878fe591f 100644 --- a/include/uapi/linux/usb/tmc.h +++ b/include/uapi/linux/usb/tmc.h @@ -102,6 +102,8 @@ struct usbtmc_message { #define USBTMC_IOCTL_MSG_IN_ATTR _IOR(USBTMC_IOC_NR, 24, __u8) #define USBTMC_IOCTL_AUTO_ABORT _IOW(USBTMC_IOC_NR, 25, __u8) +#define USBTMC_IOCTL_GET_STB _IOR(USBTMC_IOC_NR, 26, __u8) + /* Cancel and cleanup asynchronous calls */ #define USBTMC_IOCTL_CANCEL_IO _IO(USBTMC_IOC_NR, 35) #define USBTMC_IOCTL_CLEANUP_IO _IO(USBTMC_IOC_NR, 36) -- cgit v1.2.3 From d1d9defdc6d582119d29f5d88f810b72bb1837fa Mon Sep 17 00:00:00 2001 From: Dave Penkler Date: Tue, 15 Dec 2020 16:56:20 +0100 Subject: USB: usbtmc: Add separate USBTMC_IOCTL_GET_SRQ_STB This new ioctl only returns the status byte (STB) that was originally sent by the device due to a service request (SRQ) condition. This ioctl checks the srq_asserted bit of the associated file descriptor. If set, the srq_asserted bit is reset and the cached STB with original SRQ information is returned. Otherwise the ioctl returns the error code ENOMSG. This ioctl is useful to support non USBTMC-488 compliant devices. Time sensitive applications can read the cached STB without incurring the cost of an urb transaction over the bus. Tested-by: Jian-Wei Wu Reviewed-by: Guido Kiener Signed-off-by: Dave Penkler Link: https://lore.kernel.org/r/20201215155621.9592-4-dpenkler@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/usbtmc.c | 31 +++++++++++++++++++++++++++++++ include/uapi/linux/usb/tmc.h | 1 + 2 files changed, 32 insertions(+) diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 8918e2182eca..d2fcc698c745 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -571,6 +571,32 @@ static int usbtmc488_ioctl_read_stb(struct usbtmc_file_data *file_data, } +static int usbtmc_ioctl_get_srq_stb(struct usbtmc_file_data *file_data, + void __user *arg) +{ + struct usbtmc_device_data *data = file_data->data; + struct device *dev = &data->intf->dev; + int srq_asserted = 0; + __u8 stb = 0; + int rv; + + spin_lock_irq(&data->dev_lock); + srq_asserted = atomic_xchg(&file_data->srq_asserted, srq_asserted); + + if (srq_asserted) { + stb = file_data->srq_byte; + spin_unlock_irq(&data->dev_lock); + rv = put_user(stb, (__u8 __user *)arg); + } else { + spin_unlock_irq(&data->dev_lock); + rv = -ENOMSG; + } + + dev_dbg(dev, "stb:0x%02x with srq received %d\n", (unsigned int)stb, rv); + + return rv; +} + static int usbtmc488_ioctl_wait_srq(struct usbtmc_file_data *file_data, __u32 __user *arg) { @@ -2155,6 +2181,11 @@ static long usbtmc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) retval = put_user(tmp_byte, (__u8 __user *)arg); break; + case USBTMC_IOCTL_GET_SRQ_STB: + retval = usbtmc_ioctl_get_srq_stb(file_data, + (void __user *)arg); + break; + case USBTMC_IOCTL_CANCEL_IO: retval = usbtmc_ioctl_cancel_io(file_data); break; diff --git a/include/uapi/linux/usb/tmc.h b/include/uapi/linux/usb/tmc.h index 1e7878fe591f..d791cc58a7f0 100644 --- a/include/uapi/linux/usb/tmc.h +++ b/include/uapi/linux/usb/tmc.h @@ -103,6 +103,7 @@ struct usbtmc_message { #define USBTMC_IOCTL_AUTO_ABORT _IOW(USBTMC_IOC_NR, 25, __u8) #define USBTMC_IOCTL_GET_STB _IOR(USBTMC_IOC_NR, 26, __u8) +#define USBTMC_IOCTL_GET_SRQ_STB _IOR(USBTMC_IOC_NR, 27, __u8) /* Cancel and cleanup asynchronous calls */ #define USBTMC_IOCTL_CANCEL_IO _IO(USBTMC_IOC_NR, 35) -- cgit v1.2.3 From 614b388c34265948fbb3c5803ad72aa1898f2f93 Mon Sep 17 00:00:00 2001 From: Dave Penkler Date: Tue, 15 Dec 2020 16:56:21 +0100 Subject: USB: usbtmc: Bump USBTMC_API_VERSION value The previous patches in this series have changed the behaviour of the driver and added new calls. Tested-by: Jian-Wei Wu Reviewed-by: Guido Kiener Signed-off-by: Dave Penkler Link: https://lore.kernel.org/r/20201215155621.9592-5-dpenkler@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/usbtmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index d2fcc698c745..74d5a9c5238a 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -25,7 +25,7 @@ /* Increment API VERSION when changing tmc.h with new flags or ioctls * or when changing a significant behavior of the driver. */ -#define USBTMC_API_VERSION (2) +#define USBTMC_API_VERSION (3) #define USBTMC_HEADER_SIZE 12 #define USBTMC_MINOR_BASE 176 -- cgit v1.2.3 From c824c73a5e08b9ebbf5e4a293336f61a4c7f67a2 Mon Sep 17 00:00:00 2001 From: Sam Protsenko Date: Mon, 14 Dec 2020 13:07:40 +0200 Subject: usb: dwc3: drd: Avoid error when extcon is missing If "port" node is missing in PHY controller node, dwc3_get_extcon() isn't able to find extcon devices. This is perfectly fine in case when "usb-role-switch" or OTG is used, but next misleading error message is printed in that case, from of_graph_get_remote_node(): OF: graph: no port node found in /phy@1234abcd Avoid printing that message by checking if the port node exists in PHY node before calling of_graph_get_remote_node(). While at it, add the comment from mentioned code block, explaining how checking the port availability helps to avoid the misleading error. Cc: Andy Shevchenko Reviewed-by: Andy Shevchenko Signed-off-by: Sam Protsenko Link: https://lore.kernel.org/r/20201214110741.8512-2-semen.protsenko@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/drd.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c index 3e1c1aacf002..da428cf2eb5b 100644 --- a/drivers/usb/dwc3/drd.c +++ b/drivers/usb/dwc3/drd.c @@ -462,8 +462,18 @@ static struct extcon_dev *dwc3_get_extcon(struct dwc3 *dwc) return edev; } + /* + * Try to get an extcon device from the USB PHY controller's "port" + * node. Check if it has the "port" node first, to avoid printing the + * error message from underlying code, as it's a valid case: extcon + * device (and "port" node) may be missing in case of "usb-role-switch" + * or OTG mode. + */ np_phy = of_parse_phandle(dev->of_node, "phys", 0); - np_conn = of_graph_get_remote_node(np_phy, -1, -1); + if (of_graph_is_present(np_phy)) + np_conn = of_graph_get_remote_node(np_phy, -1, -1); + else + np_conn = NULL; if (np_conn) edev = extcon_find_edev_by_node(np_conn); -- cgit v1.2.3 From cb4d9b52ec06acc6eafd82e53ca90a1368fee7e6 Mon Sep 17 00:00:00 2001 From: Sam Protsenko Date: Mon, 14 Dec 2020 13:07:41 +0200 Subject: usb: dwc3: drd: Improve dwc3_get_extcon() style The previous change ("usb: dwc3: drd: Avoid error when extcon is missing") changed the code flow in dwc3_get_extcon() function, leading to unnecessary if-branch. This patch does housekeeping by reworking the code for obtaining an extcon device from the "port" node. Cc: Andy Shevchenko Signed-off-by: Sam Protsenko Link: https://lore.kernel.org/r/20201214110741.8512-3-semen.protsenko@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/drd.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c index da428cf2eb5b..e2b68bb770d1 100644 --- a/drivers/usb/dwc3/drd.c +++ b/drivers/usb/dwc3/drd.c @@ -441,8 +441,8 @@ static int dwc3_drd_notifier(struct notifier_block *nb, static struct extcon_dev *dwc3_get_extcon(struct dwc3 *dwc) { struct device *dev = dwc->dev; - struct device_node *np_phy, *np_conn; - struct extcon_dev *edev; + struct device_node *np_phy; + struct extcon_dev *edev = NULL; const char *name; if (device_property_read_bool(dev, "extcon")) @@ -470,17 +470,14 @@ static struct extcon_dev *dwc3_get_extcon(struct dwc3 *dwc) * or OTG mode. */ np_phy = of_parse_phandle(dev->of_node, "phys", 0); - if (of_graph_is_present(np_phy)) - np_conn = of_graph_get_remote_node(np_phy, -1, -1); - else - np_conn = NULL; - - if (np_conn) - edev = extcon_find_edev_by_node(np_conn); - else - edev = NULL; + if (of_graph_is_present(np_phy)) { + struct device_node *np_conn; - of_node_put(np_conn); + np_conn = of_graph_get_remote_node(np_phy, -1, -1); + if (np_conn) + edev = extcon_find_edev_by_node(np_conn); + of_node_put(np_conn); + } of_node_put(np_phy); return edev; -- cgit v1.2.3 From db8892bb1bb64b6e3d1381ac342a2ee31e1b76b6 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Mon, 7 Dec 2020 11:32:18 +0100 Subject: usb: cdns3: Add support for DRD CDNSP Patch adds support for Cadence DRD Super Speed Plus controller(CDNSP). CDNSP DRD is a part of Cadence CDNSP controller. The DRD CDNSP controller has a lot of difference on hardware level but on software level is quite compatible with CDNS3 DRD. For this reason CDNS3 DRD part of CDNS3 driver was reused for CDNSP driver. Signed-off-by: Pawel Laszczak Tested-by: Aswath Govindraju Signed-off-by: Peter Chen --- drivers/usb/cdns3/core.c | 24 +++++++---- drivers/usb/cdns3/core.h | 5 +++ drivers/usb/cdns3/drd.c | 101 +++++++++++++++++++++++++++++++++-------------- drivers/usb/cdns3/drd.h | 67 +++++++++++++++++++++++++------ 4 files changed, 148 insertions(+), 49 deletions(-) diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c index 1991cb5cf6bf..04990812181d 100644 --- a/drivers/usb/cdns3/core.c +++ b/drivers/usb/cdns3/core.c @@ -97,13 +97,23 @@ static int cdns3_core_init_role(struct cdns3 *cdns) * can be restricted later depending on strap pin configuration. */ if (dr_mode == USB_DR_MODE_UNKNOWN) { - if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) && - IS_ENABLED(CONFIG_USB_CDNS3_GADGET)) - dr_mode = USB_DR_MODE_OTG; - else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST)) - dr_mode = USB_DR_MODE_HOST; - else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET)) - dr_mode = USB_DR_MODE_PERIPHERAL; + if (cdns->version == CDNSP_CONTROLLER_V2) { + if (IS_ENABLED(CONFIG_USB_CDNSP_HOST) && + IS_ENABLED(CONFIG_USB_CDNSP_GADGET)) + dr_mode = USB_DR_MODE_OTG; + else if (IS_ENABLED(CONFIG_USB_CDNSP_HOST)) + dr_mode = USB_DR_MODE_HOST; + else if (IS_ENABLED(CONFIG_USB_CDNSP_GADGET)) + dr_mode = USB_DR_MODE_PERIPHERAL; + } else { + if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) && + IS_ENABLED(CONFIG_USB_CDNS3_GADGET)) + dr_mode = USB_DR_MODE_OTG; + else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST)) + dr_mode = USB_DR_MODE_HOST; + else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET)) + dr_mode = USB_DR_MODE_PERIPHERAL; + } } /* diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h index 3176f924293a..0d87871499ea 100644 --- a/drivers/usb/cdns3/core.h +++ b/drivers/usb/cdns3/core.h @@ -55,7 +55,9 @@ struct cdns3_platform_data { * @otg_res: the resource for otg * @otg_v0_regs: pointer to base of v0 otg registers * @otg_v1_regs: pointer to base of v1 otg registers + * @otg_cdnsp_regs: pointer to base of CDNSP otg registers * @otg_regs: pointer to base of otg registers + * @otg_irq_regs: pointer to interrupt registers * @otg_irq: irq number for otg controller * @dev_irq: irq number for device controller * @wakeup_irq: irq number for wakeup event, it is optional @@ -86,9 +88,12 @@ struct cdns3 { struct resource otg_res; struct cdns3_otg_legacy_regs *otg_v0_regs; struct cdns3_otg_regs *otg_v1_regs; + struct cdnsp_otg_regs *otg_cdnsp_regs; struct cdns3_otg_common_regs *otg_regs; + struct cdns3_otg_irq_regs *otg_irq_regs; #define CDNS3_CONTROLLER_V0 0 #define CDNS3_CONTROLLER_V1 1 +#define CDNSP_CONTROLLER_V2 2 u32 version; bool phyrst_a_enable; diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c index 38ccd29e4cde..95863d44e3e0 100644 --- a/drivers/usb/cdns3/drd.c +++ b/drivers/usb/cdns3/drd.c @@ -2,13 +2,12 @@ /* * Cadence USBSS DRD Driver. * - * Copyright (C) 2018-2019 Cadence. + * Copyright (C) 2018-2020 Cadence. * Copyright (C) 2019 Texas Instruments * * Author: Pawel Laszczak * Roger Quadros * - * */ #include #include @@ -28,8 +27,9 @@ * * Returns 0 on success otherwise negative errno */ -int cdns3_set_mode(struct cdns3 *cdns, enum usb_dr_mode mode) +static int cdns3_set_mode(struct cdns3 *cdns, enum usb_dr_mode mode) { + u32 __iomem *override_reg; u32 reg; switch (mode) { @@ -39,11 +39,24 @@ int cdns3_set_mode(struct cdns3 *cdns, enum usb_dr_mode mode) break; case USB_DR_MODE_OTG: dev_dbg(cdns->dev, "Set controller to OTG mode\n"); - if (cdns->version == CDNS3_CONTROLLER_V1) { - reg = readl(&cdns->otg_v1_regs->override); + + if (cdns->version == CDNSP_CONTROLLER_V2) + override_reg = &cdns->otg_cdnsp_regs->override; + else if (cdns->version == CDNS3_CONTROLLER_V1) + override_reg = &cdns->otg_v1_regs->override; + else + override_reg = &cdns->otg_v0_regs->ctrl1; + + reg = readl(override_reg); + + if (cdns->version != CDNS3_CONTROLLER_V0) reg |= OVERRIDE_IDPULLUP; - writel(reg, &cdns->otg_v1_regs->override); + else + reg |= OVERRIDE_IDPULLUP_V0; + writel(reg, override_reg); + + if (cdns->version == CDNS3_CONTROLLER_V1) { /* * Enable work around feature built into the * controller to address issue with RX Sensitivity @@ -55,10 +68,6 @@ int cdns3_set_mode(struct cdns3 *cdns, enum usb_dr_mode mode) reg |= PHYRST_CFG_PHYRST_A_ENABLE; writel(reg, &cdns->otg_v1_regs->phyrst_cfg); } - } else { - reg = readl(&cdns->otg_v0_regs->ctrl1); - reg |= OVERRIDE_IDPULLUP_V0; - writel(reg, &cdns->otg_v0_regs->ctrl1); } /* @@ -123,7 +132,7 @@ bool cdns3_is_device(struct cdns3 *cdns) */ static void cdns3_otg_disable_irq(struct cdns3 *cdns) { - writel(0, &cdns->otg_regs->ien); + writel(0, &cdns->otg_irq_regs->ien); } /** @@ -133,7 +142,7 @@ static void cdns3_otg_disable_irq(struct cdns3 *cdns) static void cdns3_otg_enable_irq(struct cdns3 *cdns) { writel(OTGIEN_ID_CHANGE_INT | OTGIEN_VBUSVALID_RISE_INT | - OTGIEN_VBUSVALID_FALL_INT, &cdns->otg_regs->ien); + OTGIEN_VBUSVALID_FALL_INT, &cdns->otg_irq_regs->ien); } /** @@ -144,16 +153,21 @@ static void cdns3_otg_enable_irq(struct cdns3 *cdns) */ int cdns3_drd_host_on(struct cdns3 *cdns) { - u32 val; + u32 val, ready_bit; int ret; /* Enable host mode. */ writel(OTGCMD_HOST_BUS_REQ | OTGCMD_OTG_DIS, &cdns->otg_regs->cmd); + if (cdns->version == CDNSP_CONTROLLER_V2) + ready_bit = OTGSTS_CDNSP_XHCI_READY; + else + ready_bit = OTGSTS_CDNS3_XHCI_READY; + dev_dbg(cdns->dev, "Waiting till Host mode is turned on\n"); ret = readl_poll_timeout_atomic(&cdns->otg_regs->sts, val, - val & OTGSTS_XHCI_READY, 1, 100000); + val & ready_bit, 1, 100000); if (ret) dev_err(cdns->dev, "timeout waiting for xhci_ready\n"); @@ -189,17 +203,22 @@ void cdns3_drd_host_off(struct cdns3 *cdns) */ int cdns3_drd_gadget_on(struct cdns3 *cdns) { - int ret, val; u32 reg = OTGCMD_OTG_DIS; + u32 ready_bit; + int ret, val; /* switch OTG core */ writel(OTGCMD_DEV_BUS_REQ | reg, &cdns->otg_regs->cmd); dev_dbg(cdns->dev, "Waiting till Device mode is turned on\n"); + if (cdns->version == CDNSP_CONTROLLER_V2) + ready_bit = OTGSTS_CDNSP_DEV_READY; + else + ready_bit = OTGSTS_CDNS3_DEV_READY; + ret = readl_poll_timeout_atomic(&cdns->otg_regs->sts, val, - val & OTGSTS_DEV_READY, - 1, 100000); + val & ready_bit, 1, 100000); if (ret) { dev_err(cdns->dev, "timeout waiting for dev_ready\n"); return ret; @@ -244,7 +263,7 @@ static int cdns3_init_otg_mode(struct cdns3 *cdns) cdns3_otg_disable_irq(cdns); /* clear all interrupts */ - writel(~0, &cdns->otg_regs->ivect); + writel(~0, &cdns->otg_irq_regs->ivect); ret = cdns3_set_mode(cdns, USB_DR_MODE_OTG); if (ret) @@ -313,7 +332,7 @@ static irqreturn_t cdns3_drd_irq(int irq, void *data) if (cdns->in_lpm) return ret; - reg = readl(&cdns->otg_regs->ivect); + reg = readl(&cdns->otg_irq_regs->ivect); if (!reg) return IRQ_NONE; @@ -332,7 +351,7 @@ static irqreturn_t cdns3_drd_irq(int irq, void *data) ret = IRQ_WAKE_THREAD; } - writel(~0, &cdns->otg_regs->ivect); + writel(~0, &cdns->otg_irq_regs->ivect); return ret; } @@ -347,28 +366,43 @@ int cdns3_drd_init(struct cdns3 *cdns) return PTR_ERR(regs); /* Detection of DRD version. Controller has been released - * in two versions. Both are similar, but they have same changes - * in register maps. - * The first register in old version is command register and it's read - * only, so driver should read 0 from it. On the other hand, in v1 - * the first register contains device ID number which is not set to 0. - * Driver uses this fact to detect the proper version of + * in three versions. All are very similar and are software compatible, + * but they have same changes in register maps. + * The first register in oldest version is command register and it's + * read only. Driver should read 0 from it. On the other hand, in v1 + * and v2 the first register contains device ID number which is not + * set to 0. Driver uses this fact to detect the proper version of * controller. */ cdns->otg_v0_regs = regs; if (!readl(&cdns->otg_v0_regs->cmd)) { cdns->version = CDNS3_CONTROLLER_V0; cdns->otg_v1_regs = NULL; + cdns->otg_cdnsp_regs = NULL; cdns->otg_regs = regs; + cdns->otg_irq_regs = (struct cdns3_otg_irq_regs *) + &cdns->otg_v0_regs->ien; writel(1, &cdns->otg_v0_regs->simulate); dev_dbg(cdns->dev, "DRD version v0 (%08x)\n", readl(&cdns->otg_v0_regs->version)); } else { cdns->otg_v0_regs = NULL; cdns->otg_v1_regs = regs; + cdns->otg_cdnsp_regs = regs; + cdns->otg_regs = (void *)&cdns->otg_v1_regs->cmd; - cdns->version = CDNS3_CONTROLLER_V1; - writel(1, &cdns->otg_v1_regs->simulate); + + if (cdns->otg_cdnsp_regs->did == OTG_CDNSP_DID) { + cdns->otg_irq_regs = (struct cdns3_otg_irq_regs *) + &cdns->otg_cdnsp_regs->ien; + cdns->version = CDNSP_CONTROLLER_V2; + } else { + cdns->otg_irq_regs = (struct cdns3_otg_irq_regs *) + &cdns->otg_v1_regs->ien; + writel(1, &cdns->otg_v1_regs->simulate); + cdns->version = CDNS3_CONTROLLER_V1; + } + dev_dbg(cdns->dev, "DRD version v1 (ID: %08x, rev: %08x)\n", readl(&cdns->otg_v1_regs->did), readl(&cdns->otg_v1_regs->rid)); @@ -378,10 +412,17 @@ int cdns3_drd_init(struct cdns3 *cdns) /* Update dr_mode according to STRAP configuration. */ cdns->dr_mode = USB_DR_MODE_OTG; - if (state == OTGSTS_STRAP_HOST) { + + if ((cdns->version == CDNSP_CONTROLLER_V2 && + state == OTGSTS_CDNSP_STRAP_HOST) || + (cdns->version != CDNSP_CONTROLLER_V2 && + state == OTGSTS_STRAP_HOST)) { dev_dbg(cdns->dev, "Controller strapped to HOST\n"); cdns->dr_mode = USB_DR_MODE_HOST; - } else if (state == OTGSTS_STRAP_GADGET) { + } else if ((cdns->version == CDNSP_CONTROLLER_V2 && + state == OTGSTS_CDNSP_STRAP_GADGET) || + (cdns->version != CDNSP_CONTROLLER_V2 && + state == OTGSTS_STRAP_GADGET)) { dev_dbg(cdns->dev, "Controller strapped to PERIPHERAL\n"); cdns->dr_mode = USB_DR_MODE_PERIPHERAL; } diff --git a/drivers/usb/cdns3/drd.h b/drivers/usb/cdns3/drd.h index f1ccae285a16..a767b6893938 100644 --- a/drivers/usb/cdns3/drd.h +++ b/drivers/usb/cdns3/drd.h @@ -1,8 +1,8 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Cadence USB3 DRD header file. + * Cadence USB3 and USBSSP DRD header file. * - * Copyright (C) 2018-2019 Cadence. + * Copyright (C) 2018-2020 Cadence. * * Author: Pawel Laszczak */ @@ -13,7 +13,7 @@ #include #include "core.h" -/* DRD register interface for version v1. */ +/* DRD register interface for version v1 of cdns3 driver. */ struct cdns3_otg_regs { __le32 did; __le32 rid; @@ -38,7 +38,7 @@ struct cdns3_otg_regs { __le32 ctrl2; }; -/* DRD register interface for version v0. */ +/* DRD register interface for version v0 of cdns3 driver. */ struct cdns3_otg_legacy_regs { __le32 cmd; __le32 sts; @@ -57,14 +57,45 @@ struct cdns3_otg_legacy_regs { __le32 ctrl1; }; +/* DRD register interface for cdnsp driver */ +struct cdnsp_otg_regs { + __le32 did; + __le32 rid; + __le32 cfgs1; + __le32 cfgs2; + __le32 cmd; + __le32 sts; + __le32 state; + __le32 ien; + __le32 ivect; + __le32 tmr; + __le32 simulate; + __le32 adpbc_sts; + __le32 adp_ramp_time; + __le32 adpbc_ctrl1; + __le32 adpbc_ctrl2; + __le32 override; + __le32 vbusvalid_dbnc_cfg; + __le32 sessvalid_dbnc_cfg; + __le32 susp_timing_ctrl; +}; + +#define OTG_CDNSP_DID 0x0004034E + /* - * Common registers interface for both version of DRD. + * Common registers interface for both CDNS3 and CDNSP version of DRD. */ struct cdns3_otg_common_regs { __le32 cmd; __le32 sts; __le32 state; - __le32 different1; +}; + +/* + * Interrupt related registers. This registers are mapped in different + * location for CDNSP controller. + */ +struct cdns3_otg_irq_regs { __le32 ien; __le32 ivect; }; @@ -92,9 +123,9 @@ struct cdns3_otg_common_regs { #define OTGCMD_DEV_BUS_DROP BIT(8) /* Drop the bus for Host mode*/ #define OTGCMD_HOST_BUS_DROP BIT(9) -/* Power Down USBSS-DEV. */ +/* Power Down USBSS-DEV - only for CDNS3.*/ #define OTGCMD_DEV_POWER_OFF BIT(11) -/* Power Down CDNSXHCI. */ +/* Power Down CDNSXHCI - only for CDNS3. */ #define OTGCMD_HOST_POWER_OFF BIT(12) /* OTGIEN - bitmasks */ @@ -123,20 +154,31 @@ struct cdns3_otg_common_regs { #define OTGSTS_OTG_NRDY_MASK BIT(11) #define OTGSTS_OTG_NRDY(p) ((p) & OTGSTS_OTG_NRDY_MASK) /* - * Value of the strap pins. + * Value of the strap pins for: + * CDNS3: * 000 - no default configuration * 010 - Controller initiall configured as Host * 100 - Controller initially configured as Device + * CDNSP: + * 000 - No default configuration. + * 010 - Controller initiall configured as Host. + * 100 - Controller initially configured as Device. */ #define OTGSTS_STRAP(p) (((p) & GENMASK(14, 12)) >> 12) #define OTGSTS_STRAP_NO_DEFAULT_CFG 0x00 #define OTGSTS_STRAP_HOST_OTG 0x01 #define OTGSTS_STRAP_HOST 0x02 #define OTGSTS_STRAP_GADGET 0x04 +#define OTGSTS_CDNSP_STRAP_HOST 0x01 +#define OTGSTS_CDNSP_STRAP_GADGET 0x02 + /* Host mode is turned on. */ -#define OTGSTS_XHCI_READY BIT(26) +#define OTGSTS_CDNS3_XHCI_READY BIT(26) +#define OTGSTS_CDNSP_XHCI_READY BIT(27) + /* "Device mode is turned on .*/ -#define OTGSTS_DEV_READY BIT(27) +#define OTGSTS_CDNS3_DEV_READY BIT(27) +#define OTGSTS_CDNSP_DEV_READY BIT(26) /* OTGSTATE- bitmasks */ #define OTGSTATE_DEV_STATE_MASK GENMASK(2, 0) @@ -152,6 +194,8 @@ struct cdns3_otg_common_regs { #define OVERRIDE_IDPULLUP BIT(0) /* Only for CDNS3_CONTROLLER_V0 version */ #define OVERRIDE_IDPULLUP_V0 BIT(24) +/* Vbusvalid/Sesvalid override select. */ +#define OVERRIDE_SESS_VLD_SEL BIT(10) /* PHYRST_CFG - bitmasks */ #define PHYRST_CFG_PHYRST_A_ENABLE BIT(0) @@ -170,6 +214,5 @@ int cdns3_drd_gadget_on(struct cdns3 *cdns); void cdns3_drd_gadget_off(struct cdns3 *cdns); int cdns3_drd_host_on(struct cdns3 *cdns); void cdns3_drd_host_off(struct cdns3 *cdns); -int cdns3_set_mode(struct cdns3 *cdns, enum usb_dr_mode mode); #endif /* __LINUX_CDNS3_DRD */ -- cgit v1.2.3 From f738957277bad824b422399a214b630d7832f884 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Mon, 7 Dec 2020 11:32:19 +0100 Subject: usb: cdns3: Split core.c into cdns3-plat and core.c file Patch splits file core.c into core.c containing the common reusable code and cnd3-plat.c containing device platform specific code. These changes are required to make possible reuse DRD part of CDNS3 driver in CDNSP driver. Signed-off-by: Pawel Laszczak Tested-by: Aswath Govindraju Signed-off-by: Peter Chen --- drivers/usb/cdns3/Makefile | 2 +- drivers/usb/cdns3/cdns3-plat.c | 312 +++++++++++++++++++++++++++++++++++++++++ drivers/usb/cdns3/core.c | 284 ++++--------------------------------- drivers/usb/cdns3/core.h | 6 + drivers/usb/cdns3/drd.c | 1 - drivers/usb/cdns3/drd.h | 1 - 6 files changed, 343 insertions(+), 263 deletions(-) create mode 100644 drivers/usb/cdns3/cdns3-plat.c diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile index d47e341a6f39..a1fe9612053a 100644 --- a/drivers/usb/cdns3/Makefile +++ b/drivers/usb/cdns3/Makefile @@ -2,7 +2,7 @@ # define_trace.h needs to know how to find our header CFLAGS_trace.o := -I$(src) -cdns3-y := core.o drd.o +cdns3-y := cdns3-plat.o core.o drd.o obj-$(CONFIG_USB_CDNS3) += cdns3.o cdns3-$(CONFIG_USB_CDNS3_GADGET) += gadget.o ep0.o diff --git a/drivers/usb/cdns3/cdns3-plat.c b/drivers/usb/cdns3/cdns3-plat.c new file mode 100644 index 000000000000..5dcb83af6c86 --- /dev/null +++ b/drivers/usb/cdns3/cdns3-plat.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence USBSS DRD Driver. + * + * Copyright (C) 2018-2020 Cadence. + * Copyright (C) 2017-2018 NXP + * Copyright (C) 2019 Texas Instruments + * + * + * Author: Peter Chen + * Pawel Laszczak + * Roger Quadros + */ + +#include +#include +#include +#include + +#include "core.h" + +static int set_phy_power_on(struct cdns3 *cdns) +{ + int ret; + + ret = phy_power_on(cdns->usb2_phy); + if (ret) + return ret; + + ret = phy_power_on(cdns->usb3_phy); + if (ret) + phy_power_off(cdns->usb2_phy); + + return ret; +} + +static void set_phy_power_off(struct cdns3 *cdns) +{ + phy_power_off(cdns->usb3_phy); + phy_power_off(cdns->usb2_phy); +} + +/** + * cdns3_plat_probe - probe for cdns3 core device + * @pdev: Pointer to cdns3 core platform device + * + * Returns 0 on success otherwise negative errno + */ +static int cdns3_plat_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct cdns3 *cdns; + void __iomem *regs; + int ret; + + cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL); + if (!cdns) + return -ENOMEM; + + cdns->dev = dev; + cdns->pdata = dev_get_platdata(dev); + + platform_set_drvdata(pdev, cdns); + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "host"); + if (!res) { + dev_err(dev, "missing host IRQ\n"); + return -ENODEV; + } + + cdns->xhci_res[0] = *res; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "xhci"); + if (!res) { + dev_err(dev, "couldn't get xhci resource\n"); + return -ENXIO; + } + + cdns->xhci_res[1] = *res; + + cdns->dev_irq = platform_get_irq_byname(pdev, "peripheral"); + + if (cdns->dev_irq < 0) + return cdns->dev_irq; + + regs = devm_platform_ioremap_resource_byname(pdev, "dev"); + if (IS_ERR(regs)) + return PTR_ERR(regs); + cdns->dev_regs = regs; + + cdns->otg_irq = platform_get_irq_byname(pdev, "otg"); + if (cdns->otg_irq < 0) + return cdns->otg_irq; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otg"); + if (!res) { + dev_err(dev, "couldn't get otg resource\n"); + return -ENXIO; + } + + cdns->phyrst_a_enable = device_property_read_bool(dev, "cdns,phyrst-a-enable"); + + cdns->otg_res = *res; + + cdns->wakeup_irq = platform_get_irq_byname_optional(pdev, "wakeup"); + if (cdns->wakeup_irq == -EPROBE_DEFER) + return cdns->wakeup_irq; + else if (cdns->wakeup_irq == 0) + return -EINVAL; + + if (cdns->wakeup_irq < 0) { + dev_dbg(dev, "couldn't get wakeup irq\n"); + cdns->wakeup_irq = 0x0; + } + + cdns->usb2_phy = devm_phy_optional_get(dev, "cdns3,usb2-phy"); + if (IS_ERR(cdns->usb2_phy)) + return PTR_ERR(cdns->usb2_phy); + + ret = phy_init(cdns->usb2_phy); + if (ret) + return ret; + + cdns->usb3_phy = devm_phy_optional_get(dev, "cdns3,usb3-phy"); + if (IS_ERR(cdns->usb3_phy)) + return PTR_ERR(cdns->usb3_phy); + + ret = phy_init(cdns->usb3_phy); + if (ret) + goto err_phy3_init; + + ret = set_phy_power_on(cdns); + if (ret) + goto err_phy_power_on; + + ret = cdns3_init(cdns); + if (ret) + goto err_cdns_init; + + device_set_wakeup_capable(dev, true); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + if (!(cdns->pdata && (cdns->pdata->quirks & CDNS3_DEFAULT_PM_RUNTIME_ALLOW))) + pm_runtime_forbid(dev); + + /* + * The controller needs less time between bus and controller suspend, + * and we also needs a small delay to avoid frequently entering low + * power mode. + */ + pm_runtime_set_autosuspend_delay(dev, 20); + pm_runtime_mark_last_busy(dev); + pm_runtime_use_autosuspend(dev); + + return 0; + +err_cdns_init: + set_phy_power_off(cdns); +err_phy_power_on: + phy_exit(cdns->usb3_phy); +err_phy3_init: + phy_exit(cdns->usb2_phy); + + return ret; +} + +/** + * cdns3_remove - unbind drd driver and clean up + * @pdev: Pointer to Linux platform device + * + * Returns 0 on success otherwise negative errno + */ +static int cdns3_plat_remove(struct platform_device *pdev) +{ + struct cdns3 *cdns = platform_get_drvdata(pdev); + struct device *dev = cdns->dev; + + pm_runtime_get_sync(dev); + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); + cdns3_remove(cdns); + set_phy_power_off(cdns); + phy_exit(cdns->usb2_phy); + phy_exit(cdns->usb3_phy); + return 0; +} + +#ifdef CONFIG_PM + +static int cdns3_set_platform_suspend(struct device *dev, + bool suspend, bool wakeup) +{ + struct cdns3 *cdns = dev_get_drvdata(dev); + int ret = 0; + + if (cdns->pdata && cdns->pdata->platform_suspend) + ret = cdns->pdata->platform_suspend(dev, suspend, wakeup); + + return ret; +} + +static int cdns3_controller_suspend(struct device *dev, pm_message_t msg) +{ + struct cdns3 *cdns = dev_get_drvdata(dev); + bool wakeup; + unsigned long flags; + + if (cdns->in_lpm) + return 0; + + if (PMSG_IS_AUTO(msg)) + wakeup = true; + else + wakeup = device_may_wakeup(dev); + + cdns3_set_platform_suspend(cdns->dev, true, wakeup); + set_phy_power_off(cdns); + spin_lock_irqsave(&cdns->lock, flags); + cdns->in_lpm = true; + spin_unlock_irqrestore(&cdns->lock, flags); + dev_dbg(cdns->dev, "%s ends\n", __func__); + + return 0; +} + +static int cdns3_controller_resume(struct device *dev, pm_message_t msg) +{ + struct cdns3 *cdns = dev_get_drvdata(dev); + int ret; + unsigned long flags; + + if (!cdns->in_lpm) + return 0; + + ret = set_phy_power_on(cdns); + if (ret) + return ret; + + cdns3_set_platform_suspend(cdns->dev, false, false); + + spin_lock_irqsave(&cdns->lock, flags); + cdns3_resume(cdns, !PMSG_IS_AUTO(msg)); + cdns->in_lpm = false; + spin_unlock_irqrestore(&cdns->lock, flags); + if (cdns->wakeup_pending) { + cdns->wakeup_pending = false; + enable_irq(cdns->wakeup_irq); + } + dev_dbg(cdns->dev, "%s ends\n", __func__); + + return ret; +} + +static int cdns3_plat_runtime_suspend(struct device *dev) +{ + return cdns3_controller_suspend(dev, PMSG_AUTO_SUSPEND); +} + +static int cdns3_plat_runtime_resume(struct device *dev) +{ + return cdns3_controller_resume(dev, PMSG_AUTO_RESUME); +} + +#ifdef CONFIG_PM_SLEEP + +static int cdns3_plat_suspend(struct device *dev) +{ + struct cdns3 *cdns = dev_get_drvdata(dev); + + cdns3_suspend(cdns); + + return cdns3_controller_suspend(dev, PMSG_SUSPEND); +} + +static int cdns3_plat_resume(struct device *dev) +{ + return cdns3_controller_resume(dev, PMSG_RESUME); +} +#endif /* CONFIG_PM_SLEEP */ +#endif /* CONFIG_PM */ + +static const struct dev_pm_ops cdns3_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(cdns3_plat_suspend, cdns3_plat_resume) + SET_RUNTIME_PM_OPS(cdns3_plat_runtime_suspend, + cdns3_plat_runtime_resume, NULL) +}; + +#ifdef CONFIG_OF +static const struct of_device_id of_cdns3_match[] = { + { .compatible = "cdns,usb3" }, + { }, +}; +MODULE_DEVICE_TABLE(of, of_cdns3_match); +#endif + +static struct platform_driver cdns3_driver = { + .probe = cdns3_plat_probe, + .remove = cdns3_plat_remove, + .driver = { + .name = "cdns-usb3", + .of_match_table = of_match_ptr(of_cdns3_match), + .pm = &cdns3_pm_ops, + }, +}; + +module_platform_driver(cdns3_driver); + +MODULE_ALIAS("platform:cdns3"); +MODULE_AUTHOR("Pawel Laszczak "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver"); diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c index 04990812181d..7b67a7c74586 100644 --- a/drivers/usb/cdns3/core.c +++ b/drivers/usb/cdns3/core.c @@ -385,26 +385,6 @@ pm_put: return ret; } -static int set_phy_power_on(struct cdns3 *cdns) -{ - int ret; - - ret = phy_power_on(cdns->usb2_phy); - if (ret) - return ret; - - ret = phy_power_on(cdns->usb3_phy); - if (ret) - phy_power_off(cdns->usb2_phy); - - return ret; -} - -static void set_phy_power_off(struct cdns3 *cdns) -{ - phy_power_off(cdns->usb3_phy); - phy_power_off(cdns->usb2_phy); -} /** * cdns3_wakeup_irq - interrupt handler for wakeup events @@ -431,16 +411,13 @@ static irqreturn_t cdns3_wakeup_irq(int irq, void *data) /** * cdns3_probe - probe for cdns3 core device - * @pdev: Pointer to cdns3 core platform device + * @cdns: Pointer to cdnsp structure. * * Returns 0 on success otherwise negative errno */ -static int cdns3_probe(struct platform_device *pdev) +int cdns3_init(struct cdns3 *cdns) { - struct device *dev = &pdev->dev; - struct resource *res; - struct cdns3 *cdns; - void __iomem *regs; + struct device *dev = cdns->dev; int ret; ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); @@ -449,87 +426,8 @@ static int cdns3_probe(struct platform_device *pdev) return ret; } - cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL); - if (!cdns) - return -ENOMEM; - - cdns->dev = dev; - cdns->pdata = dev_get_platdata(dev); - - platform_set_drvdata(pdev, cdns); - - res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "host"); - if (!res) { - dev_err(dev, "missing host IRQ\n"); - return -ENODEV; - } - - cdns->xhci_res[0] = *res; - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "xhci"); - if (!res) { - dev_err(dev, "couldn't get xhci resource\n"); - return -ENXIO; - } - - cdns->xhci_res[1] = *res; - - cdns->dev_irq = platform_get_irq_byname(pdev, "peripheral"); - if (cdns->dev_irq < 0) - return cdns->dev_irq; - - regs = devm_platform_ioremap_resource_byname(pdev, "dev"); - if (IS_ERR(regs)) - return PTR_ERR(regs); - cdns->dev_regs = regs; - - cdns->otg_irq = platform_get_irq_byname(pdev, "otg"); - if (cdns->otg_irq < 0) - return cdns->otg_irq; - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otg"); - if (!res) { - dev_err(dev, "couldn't get otg resource\n"); - return -ENXIO; - } - - cdns->phyrst_a_enable = device_property_read_bool(dev, "cdns,phyrst-a-enable"); - - cdns->otg_res = *res; - - cdns->wakeup_irq = platform_get_irq_byname_optional(pdev, "wakeup"); - if (cdns->wakeup_irq == -EPROBE_DEFER) - return cdns->wakeup_irq; - else if (cdns->wakeup_irq == 0) - return -EINVAL; - - if (cdns->wakeup_irq < 0) { - dev_dbg(dev, "couldn't get wakeup irq\n"); - cdns->wakeup_irq = 0x0; - } - mutex_init(&cdns->mutex); - cdns->usb2_phy = devm_phy_optional_get(dev, "cdns3,usb2-phy"); - if (IS_ERR(cdns->usb2_phy)) - return PTR_ERR(cdns->usb2_phy); - - ret = phy_init(cdns->usb2_phy); - if (ret) - return ret; - - cdns->usb3_phy = devm_phy_optional_get(dev, "cdns3,usb3-phy"); - if (IS_ERR(cdns->usb3_phy)) - return PTR_ERR(cdns->usb3_phy); - - ret = phy_init(cdns->usb3_phy); - if (ret) - goto err1; - - ret = set_phy_power_on(cdns); - if (ret) - goto err2; - if (device_property_read_bool(dev, "usb-role-switch")) { struct usb_role_switch_desc sw_desc = { }; @@ -541,9 +439,8 @@ static int cdns3_probe(struct platform_device *pdev) cdns->role_sw = usb_role_switch_register(dev, &sw_desc); if (IS_ERR(cdns->role_sw)) { - ret = PTR_ERR(cdns->role_sw); dev_warn(dev, "Unable to register Role Switch\n"); - goto err3; + return PTR_ERR(cdns->role_sw); } } @@ -555,153 +452,50 @@ static int cdns3_probe(struct platform_device *pdev) if (ret) { dev_err(cdns->dev, "couldn't register wakeup irq handler\n"); - goto err4; + goto role_switch_unregister; } } ret = cdns3_drd_init(cdns); if (ret) - goto err4; + goto init_failed; ret = cdns3_core_init_role(cdns); if (ret) - goto err4; + goto init_failed; spin_lock_init(&cdns->lock); - device_set_wakeup_capable(dev, true); - pm_runtime_set_active(dev); - pm_runtime_enable(dev); - if (!(cdns->pdata && (cdns->pdata->quirks & CDNS3_DEFAULT_PM_RUNTIME_ALLOW))) - pm_runtime_forbid(dev); - /* - * The controller needs less time between bus and controller suspend, - * and we also needs a small delay to avoid frequently entering low - * power mode. - */ - pm_runtime_set_autosuspend_delay(dev, 20); - pm_runtime_mark_last_busy(dev); - pm_runtime_use_autosuspend(dev); dev_dbg(dev, "Cadence USB3 core: probe succeed\n"); return 0; -err4: +init_failed: cdns3_drd_exit(cdns); +role_switch_unregister: if (cdns->role_sw) usb_role_switch_unregister(cdns->role_sw); -err3: - set_phy_power_off(cdns); -err2: - phy_exit(cdns->usb3_phy); -err1: - phy_exit(cdns->usb2_phy); return ret; } /** * cdns3_remove - unbind drd driver and clean up - * @pdev: Pointer to Linux platform device + * @cdns: Pointer to cdnsp structure. * * Returns 0 on success otherwise negative errno */ -static int cdns3_remove(struct platform_device *pdev) +int cdns3_remove(struct cdns3 *cdns) { - struct cdns3 *cdns = platform_get_drvdata(pdev); - - pm_runtime_get_sync(&pdev->dev); - pm_runtime_disable(&pdev->dev); - pm_runtime_put_noidle(&pdev->dev); cdns3_exit_roles(cdns); usb_role_switch_unregister(cdns->role_sw); - set_phy_power_off(cdns); - phy_exit(cdns->usb2_phy); - phy_exit(cdns->usb3_phy); - return 0; -} - -#ifdef CONFIG_PM - -static int cdns3_set_platform_suspend(struct device *dev, - bool suspend, bool wakeup) -{ - struct cdns3 *cdns = dev_get_drvdata(dev); - int ret = 0; - - if (cdns->pdata && cdns->pdata->platform_suspend) - ret = cdns->pdata->platform_suspend(dev, suspend, wakeup); - - return ret; -} - -static int cdns3_controller_suspend(struct device *dev, pm_message_t msg) -{ - struct cdns3 *cdns = dev_get_drvdata(dev); - bool wakeup; - unsigned long flags; - - if (cdns->in_lpm) - return 0; - - if (PMSG_IS_AUTO(msg)) - wakeup = true; - else - wakeup = device_may_wakeup(dev); - - cdns3_set_platform_suspend(cdns->dev, true, wakeup); - set_phy_power_off(cdns); - spin_lock_irqsave(&cdns->lock, flags); - cdns->in_lpm = true; - spin_unlock_irqrestore(&cdns->lock, flags); - dev_dbg(cdns->dev, "%s ends\n", __func__); return 0; } -static int cdns3_controller_resume(struct device *dev, pm_message_t msg) -{ - struct cdns3 *cdns = dev_get_drvdata(dev); - int ret; - unsigned long flags; - - if (!cdns->in_lpm) - return 0; - - ret = set_phy_power_on(cdns); - if (ret) - return ret; - - cdns3_set_platform_suspend(cdns->dev, false, false); - - spin_lock_irqsave(&cdns->lock, flags); - if (cdns->roles[cdns->role]->resume && !PMSG_IS_AUTO(msg)) - cdns->roles[cdns->role]->resume(cdns, false); - - cdns->in_lpm = false; - spin_unlock_irqrestore(&cdns->lock, flags); - if (cdns->wakeup_pending) { - cdns->wakeup_pending = false; - enable_irq(cdns->wakeup_irq); - } - dev_dbg(cdns->dev, "%s ends\n", __func__); - - return ret; -} - -static int cdns3_runtime_suspend(struct device *dev) -{ - return cdns3_controller_suspend(dev, PMSG_AUTO_SUSPEND); -} - -static int cdns3_runtime_resume(struct device *dev) -{ - return cdns3_controller_resume(dev, PMSG_AUTO_RESUME); -} #ifdef CONFIG_PM_SLEEP - -static int cdns3_suspend(struct device *dev) +int cdns3_suspend(struct cdns3 *cdns) { - struct cdns3 *cdns = dev_get_drvdata(dev); + struct device *dev = cdns->dev; unsigned long flags; if (pm_runtime_status_suspended(dev)) @@ -713,52 +507,22 @@ static int cdns3_suspend(struct device *dev) spin_unlock_irqrestore(&cdns->lock, flags); } - return cdns3_controller_suspend(dev, PMSG_SUSPEND); + return 0; } -static int cdns3_resume(struct device *dev) +int cdns3_resume(struct cdns3 *cdns, u8 set_active) { - int ret; + struct device *dev = cdns->dev; - ret = cdns3_controller_resume(dev, PMSG_RESUME); - if (ret) - return ret; + if (cdns->roles[cdns->role]->resume) + cdns->roles[cdns->role]->resume(cdns, false); - pm_runtime_disable(dev); - pm_runtime_set_active(dev); - pm_runtime_enable(dev); + if (set_active) { + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + } - return ret; + return 0; } #endif /* CONFIG_PM_SLEEP */ -#endif /* CONFIG_PM */ - -static const struct dev_pm_ops cdns3_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(cdns3_suspend, cdns3_resume) - SET_RUNTIME_PM_OPS(cdns3_runtime_suspend, cdns3_runtime_resume, NULL) -}; - -#ifdef CONFIG_OF -static const struct of_device_id of_cdns3_match[] = { - { .compatible = "cdns,usb3" }, - { }, -}; -MODULE_DEVICE_TABLE(of, of_cdns3_match); -#endif - -static struct platform_driver cdns3_driver = { - .probe = cdns3_probe, - .remove = cdns3_remove, - .driver = { - .name = "cdns-usb3", - .of_match_table = of_match_ptr(of_cdns3_match), - .pm = &cdns3_pm_ops, - }, -}; - -module_platform_driver(cdns3_driver); - -MODULE_ALIAS("platform:cdns3"); -MODULE_AUTHOR("Pawel Laszczak "); -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver"); diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h index 0d87871499ea..c97c2bb1582f 100644 --- a/drivers/usb/cdns3/core.h +++ b/drivers/usb/cdns3/core.h @@ -118,5 +118,11 @@ struct cdns3 { }; int cdns3_hw_role_switch(struct cdns3 *cdns); +int cdns3_init(struct cdns3 *cdns); +int cdns3_remove(struct cdns3 *cdns); +#ifdef CONFIG_PM_SLEEP +int cdns3_resume(struct cdns3 *cdns, u8 set_active); +int cdns3_suspend(struct cdns3 *cdns); +#endif /* CONFIG_PM_SLEEP */ #endif /* __LINUX_CDNS3_CORE_H */ diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c index 95863d44e3e0..ed8cde91a02c 100644 --- a/drivers/usb/cdns3/drd.c +++ b/drivers/usb/cdns3/drd.c @@ -14,7 +14,6 @@ #include #include #include -#include #include "gadget.h" #include "drd.h" diff --git a/drivers/usb/cdns3/drd.h b/drivers/usb/cdns3/drd.h index a767b6893938..8f3625ad4ef8 100644 --- a/drivers/usb/cdns3/drd.h +++ b/drivers/usb/cdns3/drd.h @@ -10,7 +10,6 @@ #define __LINUX_CDNS3_DRD #include -#include #include "core.h" /* DRD register interface for version v1 of cdns3 driver. */ -- cgit v1.2.3 From 394c3a144de89e994c8a2c5ba5dc64fa4e5aa870 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Mon, 7 Dec 2020 11:32:20 +0100 Subject: usb: cdns3: Moves reusable code to separate module Patch moves common reusable code used by cdns3 and cdnsp driver to cdns-usb-common library. This library include core.c, drd.c and host.c files. Signed-off-by: Pawel Laszczak Tested-by: Aswath Govindraju Signed-off-by: Peter Chen --- drivers/usb/cdns3/Kconfig | 8 ++++++++ drivers/usb/cdns3/Makefile | 8 +++++--- drivers/usb/cdns3/cdns3-plat.c | 2 ++ drivers/usb/cdns3/core.c | 18 +++++++++++++++--- drivers/usb/cdns3/core.h | 3 +++ drivers/usb/cdns3/drd.c | 3 ++- 6 files changed, 35 insertions(+), 7 deletions(-) diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig index 84716d216ae5..58154c0a73ac 100644 --- a/drivers/usb/cdns3/Kconfig +++ b/drivers/usb/cdns3/Kconfig @@ -1,8 +1,15 @@ +config CDNS_USB_COMMON + tristate + +config CDNS_USB_HOST + bool + config USB_CDNS3 tristate "Cadence USB3 Dual-Role Controller" depends on USB_SUPPORT && (USB || USB_GADGET) && HAS_DMA select USB_XHCI_PLATFORM if USB_XHCI_HCD select USB_ROLE_SWITCH + select CDNS_USB_COMMON help Say Y here if your system has a Cadence USB3 dual-role controller. It supports: dual-role switch, Host-only, and Peripheral-only. @@ -25,6 +32,7 @@ config USB_CDNS3_GADGET config USB_CDNS3_HOST bool "Cadence USB3 host controller" depends on USB=y || USB=USB_CDNS3 + select CDNS_USB_HOST help Say Y here to enable host controller functionality of the Cadence driver. diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile index a1fe9612053a..16df87abf3cf 100644 --- a/drivers/usb/cdns3/Makefile +++ b/drivers/usb/cdns3/Makefile @@ -2,17 +2,19 @@ # define_trace.h needs to know how to find our header CFLAGS_trace.o := -I$(src) -cdns3-y := cdns3-plat.o core.o drd.o +cdns-usb-common-y := core.o drd.o +cdns3-y := cdns3-plat.o obj-$(CONFIG_USB_CDNS3) += cdns3.o +obj-$(CONFIG_CDNS_USB_COMMON) += cdns-usb-common.o + +cdns-usb-common-$(CONFIG_CDNS_USB_HOST) += host.o cdns3-$(CONFIG_USB_CDNS3_GADGET) += gadget.o ep0.o ifneq ($(CONFIG_USB_CDNS3_GADGET),) cdns3-$(CONFIG_TRACING) += trace.o endif -cdns3-$(CONFIG_USB_CDNS3_HOST) += host.o - obj-$(CONFIG_USB_CDNS3_PCI_WRAP) += cdns3-pci-wrap.o obj-$(CONFIG_USB_CDNS3_TI) += cdns3-ti.o obj-$(CONFIG_USB_CDNS3_IMX) += cdns3-imx.o diff --git a/drivers/usb/cdns3/cdns3-plat.c b/drivers/usb/cdns3/cdns3-plat.c index 5dcb83af6c86..d7b07f1729d5 100644 --- a/drivers/usb/cdns3/cdns3-plat.c +++ b/drivers/usb/cdns3/cdns3-plat.c @@ -18,6 +18,7 @@ #include #include "core.h" +#include "gadget-export.h" static int set_phy_power_on(struct cdns3 *cdns) { @@ -134,6 +135,7 @@ static int cdns3_plat_probe(struct platform_device *pdev) if (ret) goto err_phy_power_on; + cdns->gadget_init = cdns3_gadget_init; ret = cdns3_init(cdns); if (ret) goto err_cdns_init; diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c index 7b67a7c74586..81ea035c0e11 100644 --- a/drivers/usb/cdns3/core.c +++ b/drivers/usb/cdns3/core.c @@ -19,10 +19,8 @@ #include #include -#include "gadget.h" #include "core.h" #include "host-export.h" -#include "gadget-export.h" #include "drd.h" static int cdns3_idle_init(struct cdns3 *cdns); @@ -147,7 +145,11 @@ static int cdns3_core_init_role(struct cdns3 *cdns) } if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) { - ret = cdns3_gadget_init(cdns); + if (cdns->gadget_init) + ret = cdns->gadget_init(cdns); + else + ret = -ENXIO; + if (ret) { dev_err(dev, "Device initialization failed with %d\n", ret); @@ -477,6 +479,7 @@ role_switch_unregister: return ret; } +EXPORT_SYMBOL_GPL(cdns3_init); /** * cdns3_remove - unbind drd driver and clean up @@ -491,6 +494,7 @@ int cdns3_remove(struct cdns3 *cdns) return 0; } +EXPORT_SYMBOL_GPL(cdns3_remove); #ifdef CONFIG_PM_SLEEP int cdns3_suspend(struct cdns3 *cdns) @@ -509,6 +513,7 @@ int cdns3_suspend(struct cdns3 *cdns) return 0; } +EXPORT_SYMBOL_GPL(cdns3_suspend); int cdns3_resume(struct cdns3 *cdns, u8 set_active) { @@ -525,4 +530,11 @@ int cdns3_resume(struct cdns3 *cdns, u8 set_active) return 0; } +EXPORT_SYMBOL_GPL(cdns3_resume); #endif /* CONFIG_PM_SLEEP */ + +MODULE_AUTHOR("Peter Chen "); +MODULE_AUTHOR("Pawel Laszczak "); +MODULE_AUTHOR("Roger Quadros "); +MODULE_DESCRIPTION("Cadence USBSS and USBSSP DRD Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h index c97c2bb1582f..2fe482dee4d7 100644 --- a/drivers/usb/cdns3/core.h +++ b/drivers/usb/cdns3/core.h @@ -78,6 +78,7 @@ struct cdns3_platform_data { * @pdata: platform data from glue layer * @lock: spinlock structure * @xhci_plat_data: xhci private data structure pointer + * @gadget_init: pointer to gadget initialization function */ struct cdns3 { struct device *dev; @@ -115,6 +116,8 @@ struct cdns3 { struct cdns3_platform_data *pdata; spinlock_t lock; struct xhci_plat_priv *xhci_plat_data; + + int (*gadget_init)(struct cdns3 *cdns); }; int cdns3_hw_role_switch(struct cdns3 *cdns); diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c index ed8cde91a02c..1874dc6018f0 100644 --- a/drivers/usb/cdns3/drd.c +++ b/drivers/usb/cdns3/drd.c @@ -15,7 +15,6 @@ #include #include -#include "gadget.h" #include "drd.h" #include "core.h" @@ -226,6 +225,7 @@ int cdns3_drd_gadget_on(struct cdns3 *cdns) phy_set_mode(cdns->usb3_phy, PHY_MODE_USB_DEVICE); return 0; } +EXPORT_SYMBOL_GPL(cdns3_drd_gadget_on); /** * cdns3_drd_gadget_off - stop gadget. @@ -249,6 +249,7 @@ void cdns3_drd_gadget_off(struct cdns3 *cdns) 1, 2000000); phy_set_mode(cdns->usb3_phy, PHY_MODE_INVALID); } +EXPORT_SYMBOL_GPL(cdns3_drd_gadget_off); /** * cdns3_init_otg_mode - initialize drd controller -- cgit v1.2.3 From 0b490046d8d7c035177ca4f5380f0c3275c4697d Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Mon, 7 Dec 2020 11:32:21 +0100 Subject: usb: cdns3: Refactoring names in reusable code Patch change the functions and objects names in reusable code. The reusable code includes core.c, core.h, drd.c and drd.h files. It also changes the names of all references to these functions and objects in other cdns3 files. There are a lot of changes, but all changes are very trivial. The reason of this patch is to avoid of mixing prefix cdns3 and cdnsp in in cdnsp driver what could introduce some confusion in understanding of cdnsp driver. This patch assumes to use three different prefixes in Cadence USB drivers: cdns: for common reusable code cdnsp: for names related only with cdnsp driver cdns3: for names related only with cdns3 driver Signed-off-by: Pawel Laszczak Tested-by: Aswath Govindraju Signed-off-by: Peter Chen --- drivers/usb/cdns3/cdns3-imx.c | 2 +- drivers/usb/cdns3/cdns3-plat.c | 25 +++---- drivers/usb/cdns3/core.c | 142 +++++++++++++++++++------------------- drivers/usb/cdns3/core.h | 46 ++++++------ drivers/usb/cdns3/drd.c | 100 +++++++++++++-------------- drivers/usb/cdns3/drd.h | 26 +++---- drivers/usb/cdns3/gadget-export.h | 4 +- drivers/usb/cdns3/gadget.c | 24 +++---- drivers/usb/cdns3/host-export.h | 6 +- drivers/usb/cdns3/host.c | 22 +++--- 10 files changed, 199 insertions(+), 198 deletions(-) diff --git a/drivers/usb/cdns3/cdns3-imx.c b/drivers/usb/cdns3/cdns3-imx.c index 22a56c4dce67..d9fb68766a15 100644 --- a/drivers/usb/cdns3/cdns3-imx.c +++ b/drivers/usb/cdns3/cdns3-imx.c @@ -250,7 +250,7 @@ static void cdns3_set_wakeup(struct cdns_imx *data, bool enable) static int cdns_imx_platform_suspend(struct device *dev, bool suspend, bool wakeup) { - struct cdns3 *cdns = dev_get_drvdata(dev); + struct cdns *cdns = dev_get_drvdata(dev); struct device *parent = dev->parent; struct cdns_imx *data = dev_get_drvdata(parent); void __iomem *otg_regs = (void __iomem *)(cdns->otg_regs); diff --git a/drivers/usb/cdns3/cdns3-plat.c b/drivers/usb/cdns3/cdns3-plat.c index d7b07f1729d5..4b18e1c6a4bb 100644 --- a/drivers/usb/cdns3/cdns3-plat.c +++ b/drivers/usb/cdns3/cdns3-plat.c @@ -20,7 +20,7 @@ #include "core.h" #include "gadget-export.h" -static int set_phy_power_on(struct cdns3 *cdns) +static int set_phy_power_on(struct cdns *cdns) { int ret; @@ -35,7 +35,7 @@ static int set_phy_power_on(struct cdns3 *cdns) return ret; } -static void set_phy_power_off(struct cdns3 *cdns) +static void set_phy_power_off(struct cdns *cdns) { phy_power_off(cdns->usb3_phy); phy_power_off(cdns->usb2_phy); @@ -51,7 +51,7 @@ static int cdns3_plat_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct resource *res; - struct cdns3 *cdns; + struct cdns *cdns; void __iomem *regs; int ret; @@ -136,7 +136,8 @@ static int cdns3_plat_probe(struct platform_device *pdev) goto err_phy_power_on; cdns->gadget_init = cdns3_gadget_init; - ret = cdns3_init(cdns); + + ret = cdns_init(cdns); if (ret) goto err_cdns_init; @@ -175,13 +176,13 @@ err_phy3_init: */ static int cdns3_plat_remove(struct platform_device *pdev) { - struct cdns3 *cdns = platform_get_drvdata(pdev); + struct cdns *cdns = platform_get_drvdata(pdev); struct device *dev = cdns->dev; pm_runtime_get_sync(dev); pm_runtime_disable(dev); pm_runtime_put_noidle(dev); - cdns3_remove(cdns); + cdns_remove(cdns); set_phy_power_off(cdns); phy_exit(cdns->usb2_phy); phy_exit(cdns->usb3_phy); @@ -193,7 +194,7 @@ static int cdns3_plat_remove(struct platform_device *pdev) static int cdns3_set_platform_suspend(struct device *dev, bool suspend, bool wakeup) { - struct cdns3 *cdns = dev_get_drvdata(dev); + struct cdns *cdns = dev_get_drvdata(dev); int ret = 0; if (cdns->pdata && cdns->pdata->platform_suspend) @@ -204,7 +205,7 @@ static int cdns3_set_platform_suspend(struct device *dev, static int cdns3_controller_suspend(struct device *dev, pm_message_t msg) { - struct cdns3 *cdns = dev_get_drvdata(dev); + struct cdns *cdns = dev_get_drvdata(dev); bool wakeup; unsigned long flags; @@ -228,7 +229,7 @@ static int cdns3_controller_suspend(struct device *dev, pm_message_t msg) static int cdns3_controller_resume(struct device *dev, pm_message_t msg) { - struct cdns3 *cdns = dev_get_drvdata(dev); + struct cdns *cdns = dev_get_drvdata(dev); int ret; unsigned long flags; @@ -242,7 +243,7 @@ static int cdns3_controller_resume(struct device *dev, pm_message_t msg) cdns3_set_platform_suspend(cdns->dev, false, false); spin_lock_irqsave(&cdns->lock, flags); - cdns3_resume(cdns, !PMSG_IS_AUTO(msg)); + cdns_resume(cdns, !PMSG_IS_AUTO(msg)); cdns->in_lpm = false; spin_unlock_irqrestore(&cdns->lock, flags); if (cdns->wakeup_pending) { @@ -268,9 +269,9 @@ static int cdns3_plat_runtime_resume(struct device *dev) static int cdns3_plat_suspend(struct device *dev) { - struct cdns3 *cdns = dev_get_drvdata(dev); + struct cdns *cdns = dev_get_drvdata(dev); - cdns3_suspend(cdns); + cdns_suspend(cdns); return cdns3_controller_suspend(dev, PMSG_SUSPEND); } diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c index 81ea035c0e11..85c491f4c6d5 100644 --- a/drivers/usb/cdns3/core.c +++ b/drivers/usb/cdns3/core.c @@ -23,9 +23,9 @@ #include "host-export.h" #include "drd.h" -static int cdns3_idle_init(struct cdns3 *cdns); +static int cdns_idle_init(struct cdns *cdns); -static int cdns3_role_start(struct cdns3 *cdns, enum usb_role role) +static int cdns_role_start(struct cdns *cdns, enum usb_role role) { int ret; @@ -39,47 +39,47 @@ static int cdns3_role_start(struct cdns3 *cdns, enum usb_role role) if (!cdns->roles[role]) return -ENXIO; - if (cdns->roles[role]->state == CDNS3_ROLE_STATE_ACTIVE) + if (cdns->roles[role]->state == CDNS_ROLE_STATE_ACTIVE) return 0; mutex_lock(&cdns->mutex); ret = cdns->roles[role]->start(cdns); if (!ret) - cdns->roles[role]->state = CDNS3_ROLE_STATE_ACTIVE; + cdns->roles[role]->state = CDNS_ROLE_STATE_ACTIVE; mutex_unlock(&cdns->mutex); return ret; } -static void cdns3_role_stop(struct cdns3 *cdns) +static void cdns_role_stop(struct cdns *cdns) { enum usb_role role = cdns->role; if (WARN_ON(role > USB_ROLE_DEVICE)) return; - if (cdns->roles[role]->state == CDNS3_ROLE_STATE_INACTIVE) + if (cdns->roles[role]->state == CDNS_ROLE_STATE_INACTIVE) return; mutex_lock(&cdns->mutex); cdns->roles[role]->stop(cdns); - cdns->roles[role]->state = CDNS3_ROLE_STATE_INACTIVE; + cdns->roles[role]->state = CDNS_ROLE_STATE_INACTIVE; mutex_unlock(&cdns->mutex); } -static void cdns3_exit_roles(struct cdns3 *cdns) +static void cdns_exit_roles(struct cdns *cdns) { - cdns3_role_stop(cdns); - cdns3_drd_exit(cdns); + cdns_role_stop(cdns); + cdns_drd_exit(cdns); } /** - * cdns3_core_init_role - initialize role of operation - * @cdns: Pointer to cdns3 structure + * cdns_core_init_role - initialize role of operation + * @cdns: Pointer to cdns structure * * Returns 0 on success otherwise negative errno */ -static int cdns3_core_init_role(struct cdns3 *cdns) +static int cdns_core_init_role(struct cdns *cdns) { struct device *dev = cdns->dev; enum usb_dr_mode best_dr_mode; @@ -120,7 +120,7 @@ static int cdns3_core_init_role(struct cdns3 *cdns) */ best_dr_mode = cdns->dr_mode; - ret = cdns3_idle_init(cdns); + ret = cdns_idle_init(cdns); if (ret) return ret; @@ -136,7 +136,7 @@ static int cdns3_core_init_role(struct cdns3 *cdns) dr_mode = best_dr_mode; if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) { - ret = cdns3_host_init(cdns); + ret = cdns_host_init(cdns); if (ret) { dev_err(dev, "Host initialization failed with %d\n", ret); @@ -159,28 +159,28 @@ static int cdns3_core_init_role(struct cdns3 *cdns) cdns->dr_mode = dr_mode; - ret = cdns3_drd_update_mode(cdns); + ret = cdns_drd_update_mode(cdns); if (ret) goto err; /* Initialize idle role to start with */ - ret = cdns3_role_start(cdns, USB_ROLE_NONE); + ret = cdns_role_start(cdns, USB_ROLE_NONE); if (ret) goto err; switch (cdns->dr_mode) { case USB_DR_MODE_OTG: - ret = cdns3_hw_role_switch(cdns); + ret = cdns_hw_role_switch(cdns); if (ret) goto err; break; case USB_DR_MODE_PERIPHERAL: - ret = cdns3_role_start(cdns, USB_ROLE_DEVICE); + ret = cdns_role_start(cdns, USB_ROLE_DEVICE); if (ret) goto err; break; case USB_DR_MODE_HOST: - ret = cdns3_role_start(cdns, USB_ROLE_HOST); + ret = cdns_role_start(cdns, USB_ROLE_HOST); if (ret) goto err; break; @@ -191,32 +191,32 @@ static int cdns3_core_init_role(struct cdns3 *cdns) return 0; err: - cdns3_exit_roles(cdns); + cdns_exit_roles(cdns); return ret; } /** - * cdns3_hw_role_state_machine - role switch state machine based on hw events. + * cdns_hw_role_state_machine - role switch state machine based on hw events. * @cdns: Pointer to controller structure. * * Returns next role to be entered based on hw events. */ -static enum usb_role cdns3_hw_role_state_machine(struct cdns3 *cdns) +static enum usb_role cdns_hw_role_state_machine(struct cdns *cdns) { enum usb_role role = USB_ROLE_NONE; int id, vbus; if (cdns->dr_mode != USB_DR_MODE_OTG) { - if (cdns3_is_host(cdns)) + if (cdns_is_host(cdns)) role = USB_ROLE_HOST; - if (cdns3_is_device(cdns)) + if (cdns_is_device(cdns)) role = USB_ROLE_DEVICE; return role; } - id = cdns3_get_id(cdns); - vbus = cdns3_get_vbus(cdns); + id = cdns_get_id(cdns); + vbus = cdns_get_vbus(cdns); /* * Role change state machine @@ -252,28 +252,28 @@ static enum usb_role cdns3_hw_role_state_machine(struct cdns3 *cdns) return role; } -static int cdns3_idle_role_start(struct cdns3 *cdns) +static int cdns_idle_role_start(struct cdns *cdns) { return 0; } -static void cdns3_idle_role_stop(struct cdns3 *cdns) +static void cdns_idle_role_stop(struct cdns *cdns) { /* Program Lane swap and bring PHY out of RESET */ phy_reset(cdns->usb3_phy); } -static int cdns3_idle_init(struct cdns3 *cdns) +static int cdns_idle_init(struct cdns *cdns) { - struct cdns3_role_driver *rdrv; + struct cdns_role_driver *rdrv; rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL); if (!rdrv) return -ENOMEM; - rdrv->start = cdns3_idle_role_start; - rdrv->stop = cdns3_idle_role_stop; - rdrv->state = CDNS3_ROLE_STATE_INACTIVE; + rdrv->start = cdns_idle_role_start; + rdrv->stop = cdns_idle_role_stop; + rdrv->state = CDNS_ROLE_STATE_INACTIVE; rdrv->suspend = NULL; rdrv->resume = NULL; rdrv->name = "idle"; @@ -284,10 +284,10 @@ static int cdns3_idle_init(struct cdns3 *cdns) } /** - * cdns3_hw_role_switch - switch roles based on HW state + * cdns_hw_role_switch - switch roles based on HW state * @cdns: controller */ -int cdns3_hw_role_switch(struct cdns3 *cdns) +int cdns_hw_role_switch(struct cdns *cdns) { enum usb_role real_role, current_role; int ret = 0; @@ -299,22 +299,22 @@ int cdns3_hw_role_switch(struct cdns3 *cdns) pm_runtime_get_sync(cdns->dev); current_role = cdns->role; - real_role = cdns3_hw_role_state_machine(cdns); + real_role = cdns_hw_role_state_machine(cdns); /* Do nothing if nothing changed */ if (current_role == real_role) goto exit; - cdns3_role_stop(cdns); + cdns_role_stop(cdns); dev_dbg(cdns->dev, "Switching role %d -> %d", current_role, real_role); - ret = cdns3_role_start(cdns, real_role); + ret = cdns_role_start(cdns, real_role); if (ret) { /* Back to current role */ dev_err(cdns->dev, "set %d has failed, back to %d\n", real_role, current_role); - ret = cdns3_role_start(cdns, current_role); + ret = cdns_role_start(cdns, current_role); if (ret) dev_err(cdns->dev, "back to %d failed too\n", current_role); @@ -331,15 +331,15 @@ exit: * * Returns role */ -static enum usb_role cdns3_role_get(struct usb_role_switch *sw) +static enum usb_role cdns_role_get(struct usb_role_switch *sw) { - struct cdns3 *cdns = usb_role_switch_get_drvdata(sw); + struct cdns *cdns = usb_role_switch_get_drvdata(sw); return cdns->role; } /** - * cdns3_role_set - set current role of controller. + * cdns_role_set - set current role of controller. * * @sw: pointer to USB role switch structure * @role: the previous role @@ -347,9 +347,9 @@ static enum usb_role cdns3_role_get(struct usb_role_switch *sw) * - Role switch for dual-role devices * - USB_ROLE_GADGET <--> USB_ROLE_NONE for peripheral-only devices */ -static int cdns3_role_set(struct usb_role_switch *sw, enum usb_role role) +static int cdns_role_set(struct usb_role_switch *sw, enum usb_role role) { - struct cdns3 *cdns = usb_role_switch_get_drvdata(sw); + struct cdns *cdns = usb_role_switch_get_drvdata(sw); int ret = 0; pm_runtime_get_sync(cdns->dev); @@ -377,8 +377,8 @@ static int cdns3_role_set(struct usb_role_switch *sw, enum usb_role role) } } - cdns3_role_stop(cdns); - ret = cdns3_role_start(cdns, role); + cdns_role_stop(cdns); + ret = cdns_role_start(cdns, role); if (ret) dev_err(cdns->dev, "set role %d has failed\n", role); @@ -389,15 +389,15 @@ pm_put: /** - * cdns3_wakeup_irq - interrupt handler for wakeup events - * @irq: irq number for cdns3 core device - * @data: structure of cdns3 + * cdns_wakeup_irq - interrupt handler for wakeup events + * @irq: irq number for cdns3/cdnsp core device + * @data: structure of cdns * * Returns IRQ_HANDLED or IRQ_NONE */ -static irqreturn_t cdns3_wakeup_irq(int irq, void *data) +static irqreturn_t cdns_wakeup_irq(int irq, void *data) { - struct cdns3 *cdns = data; + struct cdns *cdns = data; if (cdns->in_lpm) { disable_irq_nosync(irq); @@ -412,12 +412,12 @@ static irqreturn_t cdns3_wakeup_irq(int irq, void *data) } /** - * cdns3_probe - probe for cdns3 core device - * @cdns: Pointer to cdnsp structure. + * cdns_probe - probe for cdns3/cdnsp core device + * @cdns: Pointer to cdns structure. * * Returns 0 on success otherwise negative errno */ -int cdns3_init(struct cdns3 *cdns) +int cdns_init(struct cdns *cdns) { struct device *dev = cdns->dev; int ret; @@ -433,8 +433,8 @@ int cdns3_init(struct cdns3 *cdns) if (device_property_read_bool(dev, "usb-role-switch")) { struct usb_role_switch_desc sw_desc = { }; - sw_desc.set = cdns3_role_set; - sw_desc.get = cdns3_role_get; + sw_desc.set = cdns_role_set; + sw_desc.get = cdns_role_get; sw_desc.allow_userspace_control = true; sw_desc.driver_data = cdns; sw_desc.fwnode = dev->fwnode; @@ -448,7 +448,7 @@ int cdns3_init(struct cdns3 *cdns) if (cdns->wakeup_irq) { ret = devm_request_irq(cdns->dev, cdns->wakeup_irq, - cdns3_wakeup_irq, + cdns_wakeup_irq, IRQF_SHARED, dev_name(cdns->dev), cdns); @@ -458,11 +458,11 @@ int cdns3_init(struct cdns3 *cdns) } } - ret = cdns3_drd_init(cdns); + ret = cdns_drd_init(cdns); if (ret) goto init_failed; - ret = cdns3_core_init_role(cdns); + ret = cdns_core_init_role(cdns); if (ret) goto init_failed; @@ -472,32 +472,32 @@ int cdns3_init(struct cdns3 *cdns) return 0; init_failed: - cdns3_drd_exit(cdns); + cdns_drd_exit(cdns); role_switch_unregister: if (cdns->role_sw) usb_role_switch_unregister(cdns->role_sw); return ret; } -EXPORT_SYMBOL_GPL(cdns3_init); +EXPORT_SYMBOL_GPL(cdns_init); /** - * cdns3_remove - unbind drd driver and clean up - * @cdns: Pointer to cdnsp structure. + * cdns_remove - unbind drd driver and clean up + * @cdns: Pointer to cdns structure. * * Returns 0 on success otherwise negative errno */ -int cdns3_remove(struct cdns3 *cdns) +int cdns_remove(struct cdns *cdns) { - cdns3_exit_roles(cdns); + cdns_exit_roles(cdns); usb_role_switch_unregister(cdns->role_sw); return 0; } -EXPORT_SYMBOL_GPL(cdns3_remove); +EXPORT_SYMBOL_GPL(cdns_remove); #ifdef CONFIG_PM_SLEEP -int cdns3_suspend(struct cdns3 *cdns) +int cdns_suspend(struct cdns *cdns) { struct device *dev = cdns->dev; unsigned long flags; @@ -513,9 +513,9 @@ int cdns3_suspend(struct cdns3 *cdns) return 0; } -EXPORT_SYMBOL_GPL(cdns3_suspend); +EXPORT_SYMBOL_GPL(cdns_suspend); -int cdns3_resume(struct cdns3 *cdns, u8 set_active) +int cdns_resume(struct cdns *cdns, u8 set_active) { struct device *dev = cdns->dev; @@ -530,7 +530,7 @@ int cdns3_resume(struct cdns3 *cdns, u8 set_active) return 0; } -EXPORT_SYMBOL_GPL(cdns3_resume); +EXPORT_SYMBOL_GPL(cdns_resume); #endif /* CONFIG_PM_SLEEP */ MODULE_AUTHOR("Peter Chen "); diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h index 2fe482dee4d7..f664eb2d8df4 100644 --- a/drivers/usb/cdns3/core.h +++ b/drivers/usb/cdns3/core.h @@ -14,10 +14,10 @@ #ifndef __LINUX_CDNS3_CORE_H #define __LINUX_CDNS3_CORE_H -struct cdns3; +struct cdns; /** - * struct cdns3_role_driver - host/gadget role driver + * struct cdns_role_driver - host/gadget role driver * @start: start this role * @stop: stop this role * @suspend: suspend callback for this role @@ -26,18 +26,18 @@ struct cdns3; * @name: role name string (host/gadget) * @state: current state */ -struct cdns3_role_driver { - int (*start)(struct cdns3 *cdns); - void (*stop)(struct cdns3 *cdns); - int (*suspend)(struct cdns3 *cdns, bool do_wakeup); - int (*resume)(struct cdns3 *cdns, bool hibernated); +struct cdns_role_driver { + int (*start)(struct cdns *cdns); + void (*stop)(struct cdns *cdns); + int (*suspend)(struct cdns *cdns, bool do_wakeup); + int (*resume)(struct cdns *cdns, bool hibernated); const char *name; -#define CDNS3_ROLE_STATE_INACTIVE 0 -#define CDNS3_ROLE_STATE_ACTIVE 1 +#define CDNS_ROLE_STATE_INACTIVE 0 +#define CDNS_ROLE_STATE_ACTIVE 1 int state; }; -#define CDNS3_XHCI_RESOURCES_NUM 2 +#define CDNS_XHCI_RESOURCES_NUM 2 struct cdns3_platform_data { int (*platform_suspend)(struct device *dev, @@ -47,7 +47,7 @@ struct cdns3_platform_data { }; /** - * struct cdns3 - Representation of Cadence USB3 DRD controller. + * struct cdns - Representation of Cadence USB3 DRD controller. * @dev: pointer to Cadence device struct * @xhci_regs: pointer to base of xhci registers * @xhci_res: the resource for xhci @@ -63,7 +63,7 @@ struct cdns3_platform_data { * @wakeup_irq: irq number for wakeup event, it is optional * @roles: array of supported roles for this controller * @role: current role - * @host_dev: the child host device pointer for cdns3 core + * @host_dev: the child host device pointer for cdns core * @gadget_dev: the child gadget device pointer for cdns3 core * @usb2_phy: pointer to USB2 PHY * @usb3_phy: pointer to USB3 PHY @@ -80,18 +80,18 @@ struct cdns3_platform_data { * @xhci_plat_data: xhci private data structure pointer * @gadget_init: pointer to gadget initialization function */ -struct cdns3 { +struct cdns { struct device *dev; void __iomem *xhci_regs; - struct resource xhci_res[CDNS3_XHCI_RESOURCES_NUM]; + struct resource xhci_res[CDNS_XHCI_RESOURCES_NUM]; struct cdns3_usb_regs __iomem *dev_regs; struct resource otg_res; struct cdns3_otg_legacy_regs *otg_v0_regs; struct cdns3_otg_regs *otg_v1_regs; struct cdnsp_otg_regs *otg_cdnsp_regs; - struct cdns3_otg_common_regs *otg_regs; - struct cdns3_otg_irq_regs *otg_irq_regs; + struct cdns_otg_common_regs *otg_regs; + struct cdns_otg_irq_regs *otg_irq_regs; #define CDNS3_CONTROLLER_V0 0 #define CDNS3_CONTROLLER_V1 1 #define CDNSP_CONTROLLER_V2 2 @@ -101,7 +101,7 @@ struct cdns3 { int otg_irq; int dev_irq; int wakeup_irq; - struct cdns3_role_driver *roles[USB_ROLE_DEVICE + 1]; + struct cdns_role_driver *roles[USB_ROLE_DEVICE + 1]; enum usb_role role; struct platform_device *host_dev; struct cdns3_device *gadget_dev; @@ -117,15 +117,15 @@ struct cdns3 { spinlock_t lock; struct xhci_plat_priv *xhci_plat_data; - int (*gadget_init)(struct cdns3 *cdns); + int (*gadget_init)(struct cdns *cdns); }; -int cdns3_hw_role_switch(struct cdns3 *cdns); -int cdns3_init(struct cdns3 *cdns); -int cdns3_remove(struct cdns3 *cdns); +int cdns_hw_role_switch(struct cdns *cdns); +int cdns_init(struct cdns *cdns); +int cdns_remove(struct cdns *cdns); #ifdef CONFIG_PM_SLEEP -int cdns3_resume(struct cdns3 *cdns, u8 set_active); -int cdns3_suspend(struct cdns3 *cdns); +int cdns_resume(struct cdns *cdns, u8 set_active); +int cdns_suspend(struct cdns *cdns); #endif /* CONFIG_PM_SLEEP */ #endif /* __LINUX_CDNS3_CORE_H */ diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c index 1874dc6018f0..0fd465094101 100644 --- a/drivers/usb/cdns3/drd.c +++ b/drivers/usb/cdns3/drd.c @@ -19,13 +19,13 @@ #include "core.h" /** - * cdns3_set_mode - change mode of OTG Core + * cdns_set_mode - change mode of OTG Core * @cdns: pointer to context structure * @mode: selected mode from cdns_role * * Returns 0 on success otherwise negative errno */ -static int cdns3_set_mode(struct cdns3 *cdns, enum usb_dr_mode mode) +static int cdns_set_mode(struct cdns *cdns, enum usb_dr_mode mode) { u32 __iomem *override_reg; u32 reg; @@ -83,7 +83,7 @@ static int cdns3_set_mode(struct cdns3 *cdns, enum usb_dr_mode mode) return 0; } -int cdns3_get_id(struct cdns3 *cdns) +int cdns_get_id(struct cdns *cdns) { int id; @@ -93,7 +93,7 @@ int cdns3_get_id(struct cdns3 *cdns) return id; } -int cdns3_get_vbus(struct cdns3 *cdns) +int cdns_get_vbus(struct cdns *cdns) { int vbus; @@ -103,53 +103,53 @@ int cdns3_get_vbus(struct cdns3 *cdns) return vbus; } -bool cdns3_is_host(struct cdns3 *cdns) +bool cdns_is_host(struct cdns *cdns) { if (cdns->dr_mode == USB_DR_MODE_HOST) return true; - else if (cdns3_get_id(cdns) == CDNS3_ID_HOST) + else if (cdns_get_id(cdns) == CDNS3_ID_HOST) return true; return false; } -bool cdns3_is_device(struct cdns3 *cdns) +bool cdns_is_device(struct cdns *cdns) { if (cdns->dr_mode == USB_DR_MODE_PERIPHERAL) return true; else if (cdns->dr_mode == USB_DR_MODE_OTG) - if (cdns3_get_id(cdns) == CDNS3_ID_PERIPHERAL) + if (cdns_get_id(cdns) == CDNS3_ID_PERIPHERAL) return true; return false; } /** - * cdns3_otg_disable_irq - Disable all OTG interrupts + * cdns_otg_disable_irq - Disable all OTG interrupts * @cdns: Pointer to controller context structure */ -static void cdns3_otg_disable_irq(struct cdns3 *cdns) +static void cdns_otg_disable_irq(struct cdns *cdns) { writel(0, &cdns->otg_irq_regs->ien); } /** - * cdns3_otg_enable_irq - enable id and sess_valid interrupts + * cdns_otg_enable_irq - enable id and sess_valid interrupts * @cdns: Pointer to controller context structure */ -static void cdns3_otg_enable_irq(struct cdns3 *cdns) +static void cdns_otg_enable_irq(struct cdns *cdns) { writel(OTGIEN_ID_CHANGE_INT | OTGIEN_VBUSVALID_RISE_INT | OTGIEN_VBUSVALID_FALL_INT, &cdns->otg_irq_regs->ien); } /** - * cdns3_drd_host_on - start host. + * cdns_drd_host_on - start host. * @cdns: Pointer to controller context structure. * * Returns 0 on success otherwise negative errno. */ -int cdns3_drd_host_on(struct cdns3 *cdns) +int cdns_drd_host_on(struct cdns *cdns) { u32 val, ready_bit; int ret; @@ -175,10 +175,10 @@ int cdns3_drd_host_on(struct cdns3 *cdns) } /** - * cdns3_drd_host_off - stop host. + * cdns_drd_host_off - stop host. * @cdns: Pointer to controller context structure. */ -void cdns3_drd_host_off(struct cdns3 *cdns) +void cdns_drd_host_off(struct cdns *cdns) { u32 val; @@ -194,12 +194,12 @@ void cdns3_drd_host_off(struct cdns3 *cdns) } /** - * cdns3_drd_gadget_on - start gadget. + * cdns_drd_gadget_on - start gadget. * @cdns: Pointer to controller context structure. * * Returns 0 on success otherwise negative errno */ -int cdns3_drd_gadget_on(struct cdns3 *cdns) +int cdns_drd_gadget_on(struct cdns *cdns) { u32 reg = OTGCMD_OTG_DIS; u32 ready_bit; @@ -225,13 +225,13 @@ int cdns3_drd_gadget_on(struct cdns3 *cdns) phy_set_mode(cdns->usb3_phy, PHY_MODE_USB_DEVICE); return 0; } -EXPORT_SYMBOL_GPL(cdns3_drd_gadget_on); +EXPORT_SYMBOL_GPL(cdns_drd_gadget_on); /** - * cdns3_drd_gadget_off - stop gadget. + * cdns_drd_gadget_off - stop gadget. * @cdns: Pointer to controller context structure. */ -void cdns3_drd_gadget_off(struct cdns3 *cdns) +void cdns_drd_gadget_off(struct cdns *cdns) { u32 val; @@ -249,50 +249,50 @@ void cdns3_drd_gadget_off(struct cdns3 *cdns) 1, 2000000); phy_set_mode(cdns->usb3_phy, PHY_MODE_INVALID); } -EXPORT_SYMBOL_GPL(cdns3_drd_gadget_off); +EXPORT_SYMBOL_GPL(cdns_drd_gadget_off); /** - * cdns3_init_otg_mode - initialize drd controller + * cdns_init_otg_mode - initialize drd controller * @cdns: Pointer to controller context structure * * Returns 0 on success otherwise negative errno */ -static int cdns3_init_otg_mode(struct cdns3 *cdns) +static int cdns_init_otg_mode(struct cdns *cdns) { int ret; - cdns3_otg_disable_irq(cdns); + cdns_otg_disable_irq(cdns); /* clear all interrupts */ writel(~0, &cdns->otg_irq_regs->ivect); - ret = cdns3_set_mode(cdns, USB_DR_MODE_OTG); + ret = cdns_set_mode(cdns, USB_DR_MODE_OTG); if (ret) return ret; - cdns3_otg_enable_irq(cdns); + cdns_otg_enable_irq(cdns); return 0; } /** - * cdns3_drd_update_mode - initialize mode of operation + * cdns_drd_update_mode - initialize mode of operation * @cdns: Pointer to controller context structure * * Returns 0 on success otherwise negative errno */ -int cdns3_drd_update_mode(struct cdns3 *cdns) +int cdns_drd_update_mode(struct cdns *cdns) { int ret; switch (cdns->dr_mode) { case USB_DR_MODE_PERIPHERAL: - ret = cdns3_set_mode(cdns, USB_DR_MODE_PERIPHERAL); + ret = cdns_set_mode(cdns, USB_DR_MODE_PERIPHERAL); break; case USB_DR_MODE_HOST: - ret = cdns3_set_mode(cdns, USB_DR_MODE_HOST); + ret = cdns_set_mode(cdns, USB_DR_MODE_HOST); break; case USB_DR_MODE_OTG: - ret = cdns3_init_otg_mode(cdns); + ret = cdns_init_otg_mode(cdns); break; default: dev_err(cdns->dev, "Unsupported mode of operation %d\n", @@ -303,27 +303,27 @@ int cdns3_drd_update_mode(struct cdns3 *cdns) return ret; } -static irqreturn_t cdns3_drd_thread_irq(int irq, void *data) +static irqreturn_t cdns_drd_thread_irq(int irq, void *data) { - struct cdns3 *cdns = data; + struct cdns *cdns = data; - cdns3_hw_role_switch(cdns); + cdns_hw_role_switch(cdns); return IRQ_HANDLED; } /** - * cdns3_drd_irq - interrupt handler for OTG events + * cdns_drd_irq - interrupt handler for OTG events * - * @irq: irq number for cdns3 core device - * @data: structure of cdns3 + * @irq: irq number for cdns core device + * @data: structure of cdns * * Returns IRQ_HANDLED or IRQ_NONE */ -static irqreturn_t cdns3_drd_irq(int irq, void *data) +static irqreturn_t cdns_drd_irq(int irq, void *data) { irqreturn_t ret = IRQ_NONE; - struct cdns3 *cdns = data; + struct cdns *cdns = data; u32 reg; if (cdns->dr_mode != USB_DR_MODE_OTG) @@ -339,14 +339,14 @@ static irqreturn_t cdns3_drd_irq(int irq, void *data) if (reg & OTGIEN_ID_CHANGE_INT) { dev_dbg(cdns->dev, "OTG IRQ: new ID: %d\n", - cdns3_get_id(cdns)); + cdns_get_id(cdns)); ret = IRQ_WAKE_THREAD; } if (reg & (OTGIEN_VBUSVALID_RISE_INT | OTGIEN_VBUSVALID_FALL_INT)) { dev_dbg(cdns->dev, "OTG IRQ: new VBUS: %d\n", - cdns3_get_vbus(cdns)); + cdns_get_vbus(cdns)); ret = IRQ_WAKE_THREAD; } @@ -355,7 +355,7 @@ static irqreturn_t cdns3_drd_irq(int irq, void *data) return ret; } -int cdns3_drd_init(struct cdns3 *cdns) +int cdns_drd_init(struct cdns *cdns) { void __iomem *regs; u32 state; @@ -380,7 +380,7 @@ int cdns3_drd_init(struct cdns3 *cdns) cdns->otg_v1_regs = NULL; cdns->otg_cdnsp_regs = NULL; cdns->otg_regs = regs; - cdns->otg_irq_regs = (struct cdns3_otg_irq_regs *) + cdns->otg_irq_regs = (struct cdns_otg_irq_regs *) &cdns->otg_v0_regs->ien; writel(1, &cdns->otg_v0_regs->simulate); dev_dbg(cdns->dev, "DRD version v0 (%08x)\n", @@ -393,11 +393,11 @@ int cdns3_drd_init(struct cdns3 *cdns) cdns->otg_regs = (void *)&cdns->otg_v1_regs->cmd; if (cdns->otg_cdnsp_regs->did == OTG_CDNSP_DID) { - cdns->otg_irq_regs = (struct cdns3_otg_irq_regs *) + cdns->otg_irq_regs = (struct cdns_otg_irq_regs *) &cdns->otg_cdnsp_regs->ien; cdns->version = CDNSP_CONTROLLER_V2; } else { - cdns->otg_irq_regs = (struct cdns3_otg_irq_regs *) + cdns->otg_irq_regs = (struct cdns_otg_irq_regs *) &cdns->otg_v1_regs->ien; writel(1, &cdns->otg_v1_regs->simulate); cdns->version = CDNS3_CONTROLLER_V1; @@ -428,8 +428,8 @@ int cdns3_drd_init(struct cdns3 *cdns) } ret = devm_request_threaded_irq(cdns->dev, cdns->otg_irq, - cdns3_drd_irq, - cdns3_drd_thread_irq, + cdns_drd_irq, + cdns_drd_thread_irq, IRQF_SHARED, dev_name(cdns->dev), cdns); if (ret) { @@ -446,8 +446,8 @@ int cdns3_drd_init(struct cdns3 *cdns) return 0; } -int cdns3_drd_exit(struct cdns3 *cdns) +int cdns_drd_exit(struct cdns *cdns) { - cdns3_otg_disable_irq(cdns); + cdns_otg_disable_irq(cdns); return 0; } diff --git a/drivers/usb/cdns3/drd.h b/drivers/usb/cdns3/drd.h index 8f3625ad4ef8..838b17c6a45b 100644 --- a/drivers/usb/cdns3/drd.h +++ b/drivers/usb/cdns3/drd.h @@ -84,7 +84,7 @@ struct cdnsp_otg_regs { /* * Common registers interface for both CDNS3 and CDNSP version of DRD. */ -struct cdns3_otg_common_regs { +struct cdns_otg_common_regs { __le32 cmd; __le32 sts; __le32 state; @@ -94,7 +94,7 @@ struct cdns3_otg_common_regs { * Interrupt related registers. This registers are mapped in different * location for CDNSP controller. */ -struct cdns3_otg_irq_regs { +struct cdns_otg_irq_regs { __le32 ien; __le32 ivect; }; @@ -202,16 +202,16 @@ struct cdns3_otg_irq_regs { #define CDNS3_ID_PERIPHERAL 1 #define CDNS3_ID_HOST 0 -bool cdns3_is_host(struct cdns3 *cdns); -bool cdns3_is_device(struct cdns3 *cdns); -int cdns3_get_id(struct cdns3 *cdns); -int cdns3_get_vbus(struct cdns3 *cdns); -int cdns3_drd_init(struct cdns3 *cdns); -int cdns3_drd_exit(struct cdns3 *cdns); -int cdns3_drd_update_mode(struct cdns3 *cdns); -int cdns3_drd_gadget_on(struct cdns3 *cdns); -void cdns3_drd_gadget_off(struct cdns3 *cdns); -int cdns3_drd_host_on(struct cdns3 *cdns); -void cdns3_drd_host_off(struct cdns3 *cdns); +bool cdns_is_host(struct cdns *cdns); +bool cdns_is_device(struct cdns *cdns); +int cdns_get_id(struct cdns *cdns); +int cdns_get_vbus(struct cdns *cdns); +int cdns_drd_init(struct cdns *cdns); +int cdns_drd_exit(struct cdns *cdns); +int cdns_drd_update_mode(struct cdns *cdns); +int cdns_drd_gadget_on(struct cdns *cdns); +void cdns_drd_gadget_off(struct cdns *cdns); +int cdns_drd_host_on(struct cdns *cdns); +void cdns_drd_host_off(struct cdns *cdns); #endif /* __LINUX_CDNS3_DRD */ diff --git a/drivers/usb/cdns3/gadget-export.h b/drivers/usb/cdns3/gadget-export.h index 702c5a267a92..0f7cb2a92c9a 100644 --- a/drivers/usb/cdns3/gadget-export.h +++ b/drivers/usb/cdns3/gadget-export.h @@ -12,10 +12,10 @@ #ifdef CONFIG_USB_CDNS3_GADGET -int cdns3_gadget_init(struct cdns3 *cdns); +int cdns3_gadget_init(struct cdns *cdns); #else -static inline int cdns3_gadget_init(struct cdns3 *cdns) +static inline int cdns3_gadget_init(struct cdns *cdns) { return -ENXIO; } diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c index 08a4e693c470..5890e535aefc 100644 --- a/drivers/usb/cdns3/gadget.c +++ b/drivers/usb/cdns3/gadget.c @@ -1844,7 +1844,7 @@ __must_hold(&priv_dev->lock) static irqreturn_t cdns3_device_irq_handler(int irq, void *data) { struct cdns3_device *priv_dev = data; - struct cdns3 *cdns = dev_get_drvdata(priv_dev->dev); + struct cdns *cdns = dev_get_drvdata(priv_dev->dev); irqreturn_t ret = IRQ_NONE; u32 reg; @@ -3084,7 +3084,7 @@ static void cdns3_gadget_release(struct device *dev) kfree(priv_dev); } -static void cdns3_gadget_exit(struct cdns3 *cdns) +static void cdns3_gadget_exit(struct cdns *cdns) { struct cdns3_device *priv_dev; @@ -3117,10 +3117,10 @@ static void cdns3_gadget_exit(struct cdns3 *cdns) kfree(priv_dev->zlp_buf); usb_put_gadget(&priv_dev->gadget); cdns->gadget_dev = NULL; - cdns3_drd_gadget_off(cdns); + cdns_drd_gadget_off(cdns); } -static int cdns3_gadget_start(struct cdns3 *cdns) +static int cdns3_gadget_start(struct cdns *cdns) { struct cdns3_device *priv_dev; u32 max_speed; @@ -3240,7 +3240,7 @@ err1: return ret; } -static int __cdns3_gadget_init(struct cdns3 *cdns) +static int __cdns3_gadget_init(struct cdns *cdns) { int ret = 0; @@ -3251,7 +3251,7 @@ static int __cdns3_gadget_init(struct cdns3 *cdns) return ret; } - cdns3_drd_gadget_on(cdns); + cdns_drd_gadget_on(cdns); pm_runtime_get_sync(cdns->dev); ret = cdns3_gadget_start(cdns); @@ -3277,7 +3277,7 @@ err0: return ret; } -static int cdns3_gadget_suspend(struct cdns3 *cdns, bool do_wakeup) +static int cdns3_gadget_suspend(struct cdns *cdns, bool do_wakeup) __must_hold(&cdns->lock) { struct cdns3_device *priv_dev = cdns->gadget_dev; @@ -3296,7 +3296,7 @@ __must_hold(&cdns->lock) return 0; } -static int cdns3_gadget_resume(struct cdns3 *cdns, bool hibernated) +static int cdns3_gadget_resume(struct cdns *cdns, bool hibernated) { struct cdns3_device *priv_dev = cdns->gadget_dev; @@ -3311,13 +3311,13 @@ static int cdns3_gadget_resume(struct cdns3 *cdns, bool hibernated) /** * cdns3_gadget_init - initialize device structure * - * @cdns: cdns3 instance + * @cdns: cdns instance * * This function initializes the gadget. */ -int cdns3_gadget_init(struct cdns3 *cdns) +int cdns3_gadget_init(struct cdns *cdns) { - struct cdns3_role_driver *rdrv; + struct cdns_role_driver *rdrv; rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL); if (!rdrv) @@ -3327,7 +3327,7 @@ int cdns3_gadget_init(struct cdns3 *cdns) rdrv->stop = cdns3_gadget_exit; rdrv->suspend = cdns3_gadget_suspend; rdrv->resume = cdns3_gadget_resume; - rdrv->state = CDNS3_ROLE_STATE_INACTIVE; + rdrv->state = CDNS_ROLE_STATE_INACTIVE; rdrv->name = "gadget"; cdns->roles[USB_ROLE_DEVICE] = rdrv; diff --git a/drivers/usb/cdns3/host-export.h b/drivers/usb/cdns3/host-export.h index 26041718a086..744a4d4c4fb8 100644 --- a/drivers/usb/cdns3/host-export.h +++ b/drivers/usb/cdns3/host-export.h @@ -12,17 +12,17 @@ struct usb_hcd; #ifdef CONFIG_USB_CDNS3_HOST -int cdns3_host_init(struct cdns3 *cdns); +int cdns_host_init(struct cdns *cdns); int xhci_cdns3_suspend_quirk(struct usb_hcd *hcd); #else -static inline int cdns3_host_init(struct cdns3 *cdns) +static inline int cdns_host_init(struct cdns *cdns) { return -ENXIO; } -static inline void cdns3_host_exit(struct cdns3 *cdns) { } +static inline void cdns_host_exit(struct cdns *cdns) { } static inline int xhci_cdns3_suspend_quirk(struct usb_hcd *hcd) { return 0; diff --git a/drivers/usb/cdns3/host.c b/drivers/usb/cdns3/host.c index ec89f2e5430f..3ff19182b0c0 100644 --- a/drivers/usb/cdns3/host.c +++ b/drivers/usb/cdns3/host.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Cadence USBSS DRD Driver - host side + * Cadence USBSS and USBSSP DRD Driver - host side * * Copyright (C) 2018-2019 Cadence Design Systems. * Copyright (C) 2017-2018 NXP @@ -28,13 +28,13 @@ static const struct xhci_plat_priv xhci_plat_cdns3_xhci = { .suspend_quirk = xhci_cdns3_suspend_quirk, }; -static int __cdns3_host_init(struct cdns3 *cdns) +static int __cdns_host_init(struct cdns *cdns) { struct platform_device *xhci; int ret; struct usb_hcd *hcd; - cdns3_drd_host_on(cdns); + cdns_drd_host_on(cdns); xhci = platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO); if (!xhci) { @@ -46,7 +46,7 @@ static int __cdns3_host_init(struct cdns3 *cdns) cdns->host_dev = xhci; ret = platform_device_add_resources(xhci, cdns->xhci_res, - CDNS3_XHCI_RESOURCES_NUM); + CDNS_XHCI_RESOURCES_NUM); if (ret) { dev_err(cdns->dev, "couldn't add resources to xHCI device\n"); goto err1; @@ -113,25 +113,25 @@ int xhci_cdns3_suspend_quirk(struct usb_hcd *hcd) return 0; } -static void cdns3_host_exit(struct cdns3 *cdns) +static void cdns_host_exit(struct cdns *cdns) { kfree(cdns->xhci_plat_data); platform_device_unregister(cdns->host_dev); cdns->host_dev = NULL; - cdns3_drd_host_off(cdns); + cdns_drd_host_off(cdns); } -int cdns3_host_init(struct cdns3 *cdns) +int cdns_host_init(struct cdns *cdns) { - struct cdns3_role_driver *rdrv; + struct cdns_role_driver *rdrv; rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL); if (!rdrv) return -ENOMEM; - rdrv->start = __cdns3_host_init; - rdrv->stop = cdns3_host_exit; - rdrv->state = CDNS3_ROLE_STATE_INACTIVE; + rdrv->start = __cdns_host_init; + rdrv->stop = cdns_host_exit; + rdrv->state = CDNS_ROLE_STATE_INACTIVE; rdrv->name = "host"; cdns->roles[USB_ROLE_HOST] = rdrv; -- cgit v1.2.3 From ac5bca142759db36bbff2e0834c37fe956171233 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Mon, 7 Dec 2020 11:32:22 +0100 Subject: usb: cdns3: Changed type of gadget_dev in cdns structure Patch changes the type for gadget_dev pointer in cdns structure from pointer to cdns3_device structure to void pointer. This filed is in reusable code and after this change it will be used to point to both cdns3_device or cdnsp_device objects. Signed-off-by: Pawel Laszczak Tested-by: Aswath Govindraju Signed-off-by: Peter Chen --- drivers/usb/cdns3/core.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h index f664eb2d8df4..cbd2e1cc8eb1 100644 --- a/drivers/usb/cdns3/core.h +++ b/drivers/usb/cdns3/core.h @@ -64,7 +64,7 @@ struct cdns3_platform_data { * @roles: array of supported roles for this controller * @role: current role * @host_dev: the child host device pointer for cdns core - * @gadget_dev: the child gadget device pointer for cdns3 core + * @gadget_dev: the child gadget device pointer * @usb2_phy: pointer to USB2 PHY * @usb3_phy: pointer to USB3 PHY * @mutex: the mutex for concurrent code at driver @@ -104,7 +104,7 @@ struct cdns { struct cdns_role_driver *roles[USB_ROLE_DEVICE + 1]; enum usb_role role; struct platform_device *host_dev; - struct cdns3_device *gadget_dev; + void *gadget_dev; struct phy *usb2_phy; struct phy *usb3_phy; /* mutext used in workqueue*/ -- cgit v1.2.3 From e93e58d2740282d32c0278fab283eb0ae158bb59 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Mon, 7 Dec 2020 11:32:23 +0100 Subject: usb: cdnsp: Device side header file for CDNSP driver Patch defines macros, registers and structures used by Device side driver. Because the size of main patch is very big, I’ve decided to create separate patch for cdnsp-gadget.h. It should simplify reviewing the code. Signed-off-by: Pawel Laszczak Signed-off-by: Peter Chen --- drivers/usb/cdns3/cdnsp-gadget.h | 1463 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 1463 insertions(+) create mode 100644 drivers/usb/cdns3/cdnsp-gadget.h diff --git a/drivers/usb/cdns3/cdnsp-gadget.h b/drivers/usb/cdns3/cdnsp-gadget.h new file mode 100644 index 000000000000..93da1dcdad60 --- /dev/null +++ b/drivers/usb/cdns3/cdnsp-gadget.h @@ -0,0 +1,1463 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Cadence CDNSP DRD Driver. + * + * Copyright (C) 2020 Cadence. + * + * Author: Pawel Laszczak + * + * Code based on Linux XHCI driver. + * Origin: Copyright (C) 2008 Intel Corp. + */ +#ifndef __LINUX_CDNSP_GADGET_H +#define __LINUX_CDNSP_GADGET_H + +#include +#include +#include + +/* Max number slots - only 1 is allowed. */ +#define CDNSP_DEV_MAX_SLOTS 1 + +#define CDNSP_EP0_SETUP_SIZE 512 + +/* One control and 15 for in and 15 for out endpoints. */ +#define CDNSP_ENDPOINTS_NUM 31 + +/* Best Effort Service Latency. */ +#define CDNSP_DEFAULT_BESL 0 + +/* Device Controller command default timeout value in us */ +#define CDNSP_CMD_TIMEOUT (15 * 1000) + +/* Up to 16 ms to halt an device controller */ +#define CDNSP_MAX_HALT_USEC (16 * 1000) + +#define CDNSP_CTX_SIZE 2112 + +/* + * Controller register interface. + */ + +/** + * struct cdnsp_cap_regs - CDNSP Registers. + * @hc_capbase: Length of the capabilities register and controller + * version number + * @hcs_params1: HCSPARAMS1 - Structural Parameters 1 + * @hcs_params2: HCSPARAMS2 - Structural Parameters 2 + * @hcs_params3: HCSPARAMS3 - Structural Parameters 3 + * @hcc_params: HCCPARAMS - Capability Parameters + * @db_off: DBOFF - Doorbell array offset + * @run_regs_off: RTSOFF - Runtime register space offset + * @hcc_params2: HCCPARAMS2 Capability Parameters 2, + */ +struct cdnsp_cap_regs { + __le32 hc_capbase; + __le32 hcs_params1; + __le32 hcs_params2; + __le32 hcs_params3; + __le32 hcc_params; + __le32 db_off; + __le32 run_regs_off; + __le32 hcc_params2; + /* Reserved up to (CAPLENGTH - 0x1C) */ +}; + +/* hc_capbase bitmasks. */ +/* bits 7:0 - how long is the Capabilities register. */ +#define HC_LENGTH(p) (((p) >> 00) & GENMASK(7, 0)) +/* bits 31:16 */ +#define HC_VERSION(p) (((p) >> 16) & GENMASK(15, 1)) + +/* HCSPARAMS1 - hcs_params1 - bitmasks */ +/* bits 0:7, Max Device Endpoints */ +#define HCS_ENDPOINTS_MASK GENMASK(7, 0) +#define HCS_ENDPOINTS(p) (((p) & HCS_ENDPOINTS_MASK) >> 0) + +/* HCCPARAMS offset from PCI base address */ +#define HCC_PARAMS_OFFSET 0x10 + +/* HCCPARAMS - hcc_params - bitmasks */ +/* 1: device controller can use 64-bit address pointers. */ +#define HCC_64BIT_ADDR(p) ((p) & BIT(0)) +/* 1: device controller uses 64-byte Device Context structures. */ +#define HCC_64BYTE_CONTEXT(p) ((p) & BIT(2)) +/* Max size for Primary Stream Arrays - 2^(n+1), where n is bits 12:15. */ +#define HCC_MAX_PSA(p) ((((p) >> 12) & 0xf) + 1) +/* Extended Capabilities pointer from PCI base. */ +#define HCC_EXT_CAPS(p) (((p) & GENMASK(31, 16)) >> 16) + +#define CTX_SIZE(_hcc) (HCC_64BYTE_CONTEXT(_hcc) ? 64 : 32) + +/* db_off bitmask - bits 0:1 reserved. */ +#define DBOFF_MASK GENMASK(31, 2) + +/* run_regs_off bitmask - bits 0:4 reserved. */ +#define RTSOFF_MASK GENMASK(31, 5) + +/** + * struct cdnsp_op_regs - Device Controller Operational Registers. + * @command: USBCMD - Controller command register. + * @status: USBSTS - Controller status register. + * @page_size: This indicates the page size that the device controller supports. + * If bit n is set, the controller supports a page size of 2^(n+12), + * up to a 128MB page size. 4K is the minimum page size. + * @dnctrl: DNCTRL - Device notification control register. + * @cmd_ring: CRP - 64-bit Command Ring Pointer. + * @dcbaa_ptr: DCBAAP - 64-bit Device Context Base Address Array Pointer. + * @config_reg: CONFIG - Configure Register + * @port_reg_base: PORTSCn - base address for Port Status and Control + * Each port has a Port Status and Control register, + * followed by a Port Power Management Status and Control + * register, a Port Link Info register, and a reserved + * register. + */ +struct cdnsp_op_regs { + __le32 command; + __le32 status; + __le32 page_size; + __le32 reserved1; + __le32 reserved2; + __le32 dnctrl; + __le64 cmd_ring; + /* rsvd: offset 0x20-2F. */ + __le32 reserved3[4]; + __le64 dcbaa_ptr; + __le32 config_reg; + /* rsvd: offset 0x3C-3FF. */ + __le32 reserved4[241]; + /* port 1 registers, which serve as a base address for other ports. */ + __le32 port_reg_base; +}; + +/* Number of registers per port. */ +#define NUM_PORT_REGS 4 + +/** + * struct cdnsp_port_regs - Port Registers. + * @portsc: PORTSC - Port Status and Control Register. + * @portpmsc: PORTPMSC - Port Power Managements Status and Control Register. + * @portli: PORTLI - Port Link Info register. + */ +struct cdnsp_port_regs { + __le32 portsc; + __le32 portpmsc; + __le32 portli; + __le32 reserved; +}; + +/* + * These bits are Read Only (RO) and should be saved and written to the + * registers: 0 (connect status) and 10:13 (port speed). + * These bits are also sticky - meaning they're in the AUX well and they aren't + * changed by a hot and warm. + */ +#define CDNSP_PORT_RO (PORT_CONNECT | DEV_SPEED_MASK) + +/* + * These bits are RW; writing a 0 clears the bit, writing a 1 sets the bit: + * bits 5:8 (link state), 25:26 ("wake on" enable state) + */ +#define CDNSP_PORT_RWS (PORT_PLS_MASK | PORT_WKCONN_E | PORT_WKDISC_E) + +/* + * These bits are RW; writing a 1 clears the bit, writing a 0 has no effect: + * bits 1 (port enable/disable), 17 ( connect changed), + * 21 (port reset changed) , 22 (Port Link State Change), + */ +#define CDNSP_PORT_RW1CS (PORT_PED | PORT_CSC | PORT_RC | PORT_PLC) + +/* USBCMD - USB command - bitmasks. */ +/* Run/Stop, controller execution - do not write unless controller is halted.*/ +#define CMD_R_S BIT(0) +/* + * Reset device controller - resets internal controller state machine and all + * registers (except PCI config regs). + */ +#define CMD_RESET BIT(1) +/* Event Interrupt Enable - a '1' allows interrupts from the controller. */ +#define CMD_INTE BIT(2) +/* + * Device System Error Interrupt Enable - get out-of-band signal for + * controller errors. + */ +#define CMD_DSEIE BIT(3) +/* device controller save/restore state. */ +#define CMD_CSS BIT(8) +#define CMD_CRS BIT(9) +/* + * Enable Wrap Event - '1' means device controller generates an event + * when MFINDEX wraps. + */ +#define CMD_EWE BIT(10) +/* 1: device enabled */ +#define CMD_DEVEN BIT(17) +/* bits 18:31 are reserved (and should be preserved on writes). */ + +/* Command register values to disable interrupts. */ +#define CDNSP_IRQS (CMD_INTE | CMD_DSEIE | CMD_EWE) + +/* USBSTS - USB status - bitmasks */ +/* controller not running - set to 1 when run/stop bit is cleared. */ +#define STS_HALT BIT(0) +/* + * serious error, e.g. PCI parity error. The controller will clear + * the run/stop bit. + */ +#define STS_FATAL BIT(2) +/* event interrupt - clear this prior to clearing any IP flags in IR set.*/ +#define STS_EINT BIT(3) +/* port change detect */ +#define STS_PCD BIT(4) +/* save state status - '1' means device controller is saving state. */ +#define STS_SSS BIT(8) +/* restore state status - '1' means controllers is restoring state. */ +#define STS_RSS BIT(9) +/* 1: save or restore error */ +#define STS_SRE BIT(10) +/* 1: device Not Ready to accept doorbell or op reg writes after reset. */ +#define STS_CNR BIT(11) +/* 1: internal Device Controller Error.*/ +#define STS_HCE BIT(12) + +/* CRCR - Command Ring Control Register - cmd_ring bitmasks. */ +/* bit 0 is the command ring cycle state. */ +#define CMD_RING_CS BIT(0) +/* stop ring immediately - abort the currently executing command. */ +#define CMD_RING_ABORT BIT(2) +/* + * Command Ring Busy. + * Set when Doorbell register is written with DB for command and cleared when + * the controller reached end of CR. + */ +#define CMD_RING_BUSY(p) ((p) & BIT(4)) +/* 1: command ring is running */ +#define CMD_RING_RUNNING BIT(3) +/* Command Ring pointer - bit mask for the lower 32 bits. */ +#define CMD_RING_RSVD_BITS GENMASK(5, 0) + +/* CONFIG - Configure Register - config_reg bitmasks. */ +/* bits 0:7 - maximum number of device slots enabled. */ +#define MAX_DEVS GENMASK(7, 0) +/* bit 8: U3 Entry Enabled, assert PLC when controller enters U3. */ +#define CONFIG_U3E BIT(8) + +/* PORTSC - Port Status and Control Register - port_reg_base bitmasks */ +/* 1: device connected. */ +#define PORT_CONNECT BIT(0) +/* 1: port enabled. */ +#define PORT_PED BIT(1) +/* 1: port reset signaling asserted. */ +#define PORT_RESET BIT(4) +/* + * Port Link State - bits 5:8 + * A read gives the current link PM state of the port, + * a write with Link State Write Strobe sets the link state. + */ +#define PORT_PLS_MASK GENMASK(8, 5) +#define XDEV_U0 (0x0 << 5) +#define XDEV_U1 (0x1 << 5) +#define XDEV_U2 (0x2 << 5) +#define XDEV_U3 (0x3 << 5) +#define XDEV_DISABLED (0x4 << 5) +#define XDEV_RXDETECT (0x5 << 5) +#define XDEV_INACTIVE (0x6 << 5) +#define XDEV_POLLING (0x7 << 5) +#define XDEV_RECOVERY (0x8 << 5) +#define XDEV_HOT_RESET (0x9 << 5) +#define XDEV_COMP_MODE (0xa << 5) +#define XDEV_TEST_MODE (0xb << 5) +#define XDEV_RESUME (0xf << 5) +/* 1: port has power. */ +#define PORT_POWER BIT(9) +/* + * bits 10:13 indicate device speed: + * 0 - undefined speed - port hasn't be initialized by a reset yet + * 1 - full speed + * 2 - Reserved (Low Speed not supported + * 3 - high speed + * 4 - super speed + * 5 - super speed + * 6-15 reserved + */ +#define DEV_SPEED_MASK GENMASK(13, 10) +#define XDEV_FS (0x1 << 10) +#define XDEV_HS (0x3 << 10) +#define XDEV_SS (0x4 << 10) +#define XDEV_SSP (0x5 << 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_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 */ +#define PORT_LINK_STROBE BIT(16) +/* 1: connect status change */ +#define PORT_CSC BIT(17) +/* 1: warm reset for a USB 3.0 device is done. */ +#define PORT_WRC BIT(19) +/* 1: reset change - 1 to 0 transition of PORT_RESET */ +#define PORT_RC BIT(21) +/* + * port link status change - set on some port link state transitions: + * Transition Reason + * ---------------------------------------------------------------------------- + * - U3 to Resume Wakeup signaling from a device + * - Resume to Recovery to U0 USB 3.0 device resume + * - Resume to U0 USB 2.0 device resume + * - U3 to Recovery to U0 Software resume of USB 3.0 device complete + * - U3 to U0 Software resume of USB 2.0 device complete + * - U2 to U0 L1 resume of USB 2.1 device complete + * - U0 to U0 L1 entry rejection by USB 2.1 device + * - U0 to disabled L1 entry error with USB 2.1 device + * - Any state to inactive Error on USB 3.0 port + */ +#define PORT_PLC BIT(22) +/* Port configure error change - port failed to configure its link partner. */ +#define PORT_CEC BIT(23) +/* Wake on connect (enable). */ +#define PORT_WKCONN_E BIT(25) +/* Wake on disconnect (enable). */ +#define PORT_WKDISC_E BIT(26) +/* Indicates if Warm Reset is being received. */ +#define PORT_WR BIT(31) + +#define PORT_CHANGE_BITS (PORT_CSC | PORT_WRC | PORT_RC | PORT_PLC | PORT_CEC) + +/* PORTPMSCUSB3 - Port Power Management Status and Control - bitmasks. */ +/* Enables U1 entry. */ +#define PORT_U1_TIMEOUT_MASK GENMASK(7, 0) +#define PORT_U1_TIMEOUT(p) ((p) & PORT_U1_TIMEOUT_MASK) +/* Enables U2 entry .*/ +#define PORT_U2_TIMEOUT_MASK GENMASK(14, 8) +#define PORT_U2_TIMEOUT(p) (((p) << 8) & PORT_U2_TIMEOUT_MASK) + +/* PORTPMSCUSB2 - Port Power Management Status and Control - bitmasks. */ +#define PORT_L1S_MASK GENMASK(2, 0) +#define PORT_L1S(p) ((p) & PORT_L1S_MASK) +#define PORT_L1S_ACK PORT_L1S(1) +#define PORT_L1S_NYET PORT_L1S(2) +#define PORT_L1S_STALL PORT_L1S(3) +#define PORT_L1S_TIMEOUT PORT_L1S(4) +/* Remote Wake Enable. */ +#define PORT_RWE BIT(3) +/* Best Effort Service Latency (BESL). */ +#define PORT_BESL(p) (((p) << 4) & GENMASK(7, 4)) +/* Hardware LPM Enable (HLE). */ +#define PORT_HLE BIT(16) +/* Received Best Effort Service Latency (BESL). */ +#define PORT_RRBESL(p) (((p) & GENMASK(20, 17)) >> 17) +/* Port Test Control. */ +#define PORT_TEST_MODE_MASK GENMASK(31, 28) +#define PORT_TEST_MODE(p) (((p) << 28) & PORT_TEST_MODE_MASK) + +/** + * struct cdnsp_intr_reg - Interrupt Register Set. + * @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. + * + * 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 + * multiple segments of the same size. The controller places events on the ring + * and "updates the Cycle bit in the TRBs to indicate to software the current + * position of the Enqueue Pointer." The driver processes those events and + * updates the dequeue pointer. + */ +struct cdnsp_intr_reg { + __le32 irq_pending; + __le32 irq_control; + __le32 erst_size; + __le32 rsvd; + __le64 erst_base; + __le64 erst_dequeue; +}; + +/* IMAN - Interrupt Management Register - irq_pending bitmasks l. */ +#define IMAN_IE BIT(1) +#define IMAN_IP BIT(0) +/* bits 2:31 need to be preserved */ +#define IMAN_IE_SET(p) (((p) & IMAN_IE) | 0x2) +#define IMAN_IE_CLEAR(p) (((p) & IMAN_IE) & ~(0x2)) + +/* IMOD - Interrupter Moderation Register - 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). + */ +#define IMOD_INTERVAL_MASK GENMASK(15, 0) +/* Counter used to count down the time to the next interrupt - HW use only */ +#define IMOD_COUNTER_MASK GENMASK(31, 16) +#define IMOD_DEFAULT_INTERVAL 0 + +/* erst_size bitmasks. */ +/* Preserve bits 16:31 of erst_size. */ +#define ERST_SIZE_MASK GENMASK(31, 16) + +/* erst_dequeue bitmasks. */ +/* + * Dequeue ERST Segment Index (DESI) - Segment number (or alias) + * where the current dequeue pointer lies. This is an optional HW hint. + */ +#define ERST_DESI_MASK GENMASK(2, 0) +/* Event Handler Busy (EHB) - is the event ring scheduled to be serviced. */ +#define ERST_EHB BIT(3) +#define ERST_PTR_MASK GENMASK(3, 0) + +/** + * struct cdnsp_run_regs + * @microframe_index: MFINDEX - current microframe number. + * @ir_set: Array of Interrupter registers. + * + * Device Controller Runtime Registers: + * "Software should read and write these registers using only Dword (32 bit) + * or larger accesses" + */ +struct cdnsp_run_regs { + __le32 microframe_index; + __le32 rsvd[7]; + struct cdnsp_intr_reg ir_set[128]; +}; + +/** + * USB2.0 Port Peripheral Configuration Registers. + * @ext_cap: Header register for Extended Capability. + * @port_reg1: Timer Configuration Register. + * @port_reg2: Timer Configuration Register. + * @port_reg3: Timer Configuration Register. + * @port_reg4: Timer Configuration Register. + * @port_reg5: Timer Configuration Register. + * @port_reg6: Chicken bits for USB20PPP. + */ +struct cdnsp_20port_cap { + __le32 ext_cap; + __le32 port_reg1; + __le32 port_reg2; + __le32 port_reg3; + __le32 port_reg4; + __le32 port_reg5; + __le32 port_reg6; +}; + +/* Extended capability register fields */ +#define EXT_CAPS_ID(p) (((p) >> 0) & GENMASK(7, 0)) +#define EXT_CAPS_NEXT(p) (((p) >> 8) & GENMASK(7, 0)) +/* Extended capability IDs - ID 0 reserved */ +#define EXT_CAPS_PROTOCOL 2 + +/* USB 2.0 Port Peripheral Configuration Extended Capability */ +#define EXT_CAP_CFG_DEV_20PORT_CAP_ID 0xC1 +/* + * Setting this bit to '1' enables automatic wakeup from L1 state on transfer + * TRB prepared when USBSSP operates in USB2.0 mode. + */ +#define PORT_REG6_L1_L0_HW_EN BIT(1) +/* + * Setting this bit to '1' forces Full Speed when USBSSP operates in USB2.0 + * mode (disables High Speed). + */ +#define PORT_REG6_FORCE_FS BIT(0) + +/** + * USB3.x Port Peripheral Configuration Registers. + * @ext_cap: Header register for Extended Capability. + * @mode_addr: Miscellaneous 3xPORT operation mode configuration register. + * @mode_2: 3x Port Control Register 2. + */ +struct cdnsp_3xport_cap { + __le32 ext_cap; + __le32 mode_addr; + __le32 reserved[52]; + __le32 mode_2; +}; + +/* Extended Capability Header for 3XPort Configuration Registers. */ +#define D_XEC_CFG_3XPORT_CAP 0xC0 +#define CFG_3XPORT_SSP_SUPPORT BIT(31) +#define CFG_3XPORT_U1_PIPE_CLK_GATE_EN BIT(0) + +/* Revision Extended Capability ID */ +#define RTL_REV_CAP 0xC4 +#define RTL_REV_CAP_RX_BUFF_CMD_SIZE BITMASK(31, 24) +#define RTL_REV_CAP_RX_BUFF_SIZE BITMASK(15, 0) +#define RTL_REV_CAP_TX_BUFF_CMD_SIZE BITMASK(31, 24) +#define RTL_REV_CAP_TX_BUFF_SIZE BITMASK(15, 0) + +#define CDNSP_VER_1 0x00000000 +#define CDNSP_VER_2 0x10000000 + +#define CDNSP_IF_EP_EXIST(pdev, ep_num, dir) ((pdev)->rev_cap.ep_supported & \ + (BIT(ep_num) << ((dir) ? 0 : 16))) + +/** + * struct cdnsp_rev_cap - controller capabilities . + * @ext_cap: Header for RTL Revision Extended Capability. + * @rtl_revision: RTL revision. + * @rx_buff_size: Rx buffer sizes. + * @tx_buff_size: Tx buffer sizes. + * @ep_supported: Supported endpoints. + * @ctrl_revision: Controller revision ID. + */ +struct cdnsp_rev_cap { + __le32 ext_cap; + __le32 rtl_revision; + __le32 rx_buff_size; + __le32 tx_buff_size; + __le32 ep_supported; + __le32 ctrl_revision; +}; + +/* USB2.0 Port Peripheral Configuration Registers. */ +#define D_XEC_PRE_REGS_CAP 0xC8 +#define REG_CHICKEN_BITS_2_OFFSET 0x48 +#define CHICKEN_XDMA_2_TP_CACHE_DIS BIT(28) + +/* XBUF Extended Capability ID. */ +#define XBUF_CAP_ID 0xCB +#define XBUF_RX_TAG_MASK_0_OFFSET 0x1C +#define XBUF_RX_TAG_MASK_1_OFFSET 0x24 +#define XBUF_TX_CMD_OFFSET 0x2C + +/** + * struct cdnsp_doorbell_array. + * @cmd_db: Command ring doorbell register. + * @ep_db: Endpoint ring doorbell register. + * Bits 0 - 7: Endpoint target. + * Bits 8 - 15: RsvdZ. + * Bits 16 - 31: Stream ID. + */ +struct cdnsp_doorbell_array { + __le32 cmd_db; + __le32 ep_db; +}; + +#define DB_VALUE(ep, stream) ((((ep) + 1) & 0xff) | ((stream) << 16)) +#define DB_VALUE_EP0_OUT(ep, stream) ((ep) & 0xff) +#define DB_VALUE_CMD 0x00000000 + +/** + * struct cdnsp_container_ctx. + * @type: Type of context. Used to calculated offsets to contained contexts. + * @size: Size of the context data. + * @ctx_size: context data structure size - 64 or 32 bits. + * @dma: dma address of the bytes. + * @bytes: The raw context data given to HW. + * + * Represents either a Device or Input context. Holds a pointer to the raw + * memory used for the context (bytes) and dma address of it (dma). + */ +struct cdnsp_container_ctx { + unsigned int type; +#define CDNSP_CTX_TYPE_DEVICE 0x1 +#define CDNSP_CTX_TYPE_INPUT 0x2 + int size; + int ctx_size; + dma_addr_t dma; + u8 *bytes; +}; + +/** + * struct cdnsp_slot_ctx + * @dev_info: Device speed, and last valid endpoint. + * @dev_port: Device port number that is needed to access the USB device. + * @int_target: Interrupter target number. + * @dev_state: Slot state and device address. + * + * Slot Context - This assumes the controller uses 32-byte context + * structures. If the controller uses 64-byte contexts, there is an additional + * 32 bytes reserved at the end of the slot context for controller internal use. + */ +struct cdnsp_slot_ctx { + __le32 dev_info; + __le32 dev_port; + __le32 int_target; + __le32 dev_state; + /* offset 0x10 to 0x1f reserved for controller internal use. */ + __le32 reserved[4]; +}; + +/* Bits 20:23 in the Slot Context are the speed for the device. */ +#define SLOT_SPEED_FS (XDEV_FS << 10) +#define SLOT_SPEED_HS (XDEV_HS << 10) +#define SLOT_SPEED_SS (XDEV_SS << 10) +#define SLOT_SPEED_SSP (XDEV_SSP << 10) + +/* dev_info bitmasks. */ +/* Device speed - values defined by PORTSC Device Speed field - 20:23. */ +#define DEV_SPEED GENMASK(23, 20) +#define GET_DEV_SPEED(n) (((n) & DEV_SPEED) >> 20) +/* Index of the last valid endpoint context in this device context - 27:31. */ +#define LAST_CTX_MASK GENMASK(31, 27) +#define LAST_CTX(p) ((p) << 27) +#define LAST_CTX_TO_EP_NUM(p) (((p) >> 27) - 1) +#define SLOT_FLAG BIT(0) +#define EP0_FLAG BIT(1) + +/* dev_port bitmasks */ +/* Device port number that is needed to access the USB device. */ +#define DEV_PORT(p) (((p) & 0xff) << 16) + +/* dev_state bitmasks */ +/* USB device address - assigned by the controller. */ +#define DEV_ADDR_MASK GENMASK(7, 0) +/* Slot state */ +#define SLOT_STATE GENMASK(31, 27) +#define GET_SLOT_STATE(p) (((p) & SLOT_STATE) >> 27) + +#define SLOT_STATE_DISABLED 0 +#define SLOT_STATE_ENABLED SLOT_STATE_DISABLED +#define SLOT_STATE_DEFAULT 1 +#define SLOT_STATE_ADDRESSED 2 +#define SLOT_STATE_CONFIGURED 3 + +/** + * struct cdnsp_ep_ctx. + * @ep_info: Endpoint state, streams, mult, and interval information. + * @ep_info2: Information on endpoint type, max packet size, max burst size, + * error count, and whether the controller will force an event for + * all transactions. + * @deq: 64-bit ring dequeue pointer address. If the endpoint only + * defines one stream, this points to the endpoint transfer ring. + * Otherwise, it points to a stream context array, which has a + * ring pointer for each flow. + * @tx_info: Average TRB lengths for the endpoint ring and + * max payload within an Endpoint Service Interval Time (ESIT). + * + * Endpoint Context - This assumes the controller uses 32-byte context + * structures. If the controller uses 64-byte contexts, there is an additional + * 32 bytes reserved at the end of the endpoint context for controller internal + * use. + */ +struct cdnsp_ep_ctx { + __le32 ep_info; + __le32 ep_info2; + __le64 deq; + __le32 tx_info; + /* offset 0x14 - 0x1f reserved for controller internal use. */ + __le32 reserved[3]; +}; + +/* ep_info bitmasks. */ +/* + * Endpoint State - bits 0:2: + * 0 - disabled + * 1 - running + * 2 - halted due to halt condition + * 3 - stopped + * 4 - TRB error + * 5-7 - reserved + */ +#define EP_STATE_MASK GENMASK(3, 0) +#define EP_STATE_DISABLED 0 +#define EP_STATE_RUNNING 1 +#define EP_STATE_HALTED 2 +#define EP_STATE_STOPPED 3 +#define EP_STATE_ERROR 4 +#define GET_EP_CTX_STATE(ctx) (le32_to_cpu((ctx)->ep_info) & EP_STATE_MASK) + +/* Mult - Max number of burst within an interval, in EP companion desc. */ +#define EP_MULT(p) (((p) << 8) & GENMASK(9, 8)) +#define CTX_TO_EP_MULT(p) (((p) & GENMASK(9, 8)) >> 8) +/* bits 10:14 are Max Primary Streams. */ +/* bit 15 is Linear Stream Array. */ +/* Interval - period between requests to an endpoint - 125u increments. */ +#define EP_INTERVAL(p) (((p) << 16) & GENMASK(23, 16)) +#define EP_INTERVAL_TO_UFRAMES(p) (1 << (((p) & GENMASK(23, 16)) >> 16)) +#define CTX_TO_EP_INTERVAL(p) (((p) & GENMASK(23, 16)) >> 16) +#define EP_MAXPSTREAMS_MASK GENMASK(14, 10) +#define EP_MAXPSTREAMS(p) (((p) << 10) & EP_MAXPSTREAMS_MASK) +#define CTX_TO_EP_MAXPSTREAMS(p) (((p) & EP_MAXPSTREAMS_MASK) >> 10) +/* Endpoint is set up with a Linear Stream Array (vs. Secondary Stream Array) */ +#define EP_HAS_LSA BIT(15) + +/* ep_info2 bitmasks */ +#define ERROR_COUNT(p) (((p) & 0x3) << 1) +#define CTX_TO_EP_TYPE(p) (((p) >> 3) & 0x7) +#define EP_TYPE(p) ((p) << 3) +#define ISOC_OUT_EP 1 +#define BULK_OUT_EP 2 +#define INT_OUT_EP 3 +#define CTRL_EP 4 +#define ISOC_IN_EP 5 +#define BULK_IN_EP 6 +#define INT_IN_EP 7 +/* bit 6 reserved. */ +/* bit 7 is Device Initiate Disable - for disabling stream selection. */ +#define MAX_BURST(p) (((p) << 8) & GENMASK(15, 8)) +#define CTX_TO_MAX_BURST(p) (((p) & GENMASK(15, 8)) >> 8) +#define MAX_PACKET(p) (((p) << 16) & GENMASK(31, 16)) +#define MAX_PACKET_MASK GENMASK(31, 16) +#define MAX_PACKET_DECODED(p) (((p) & GENMASK(31, 16)) >> 16) + +/* tx_info bitmasks. */ +#define EP_AVG_TRB_LENGTH(p) ((p) & GENMASK(15, 0)) +#define EP_MAX_ESIT_PAYLOAD_LO(p) (((p) << 16) & GENMASK(31, 16)) +#define EP_MAX_ESIT_PAYLOAD_HI(p) ((((p) & GENMASK(23, 16)) >> 16) << 24) +#define CTX_TO_MAX_ESIT_PAYLOAD_LO(p) (((p) & GENMASK(31, 16)) >> 16) +#define CTX_TO_MAX_ESIT_PAYLOAD_HI(p) (((p) & GENMASK(31, 24)) >> 24) + +/* deq bitmasks. */ +#define EP_CTX_CYCLE_MASK BIT(0) +#define CTX_DEQ_MASK (~0xfL) + +/** + * struct cdnsp_input_control_context + * Input control context; + * + * @drop_context: Set the bit of the endpoint context you want to disable. + * @add_context: Set the bit of the endpoint context you want to enable. + */ +struct cdnsp_input_control_ctx { + __le32 drop_flags; + __le32 add_flags; + __le32 rsvd2[6]; +}; + +/** + * Represents everything that is needed to issue a command on the command ring. + * + * @in_ctx: Pointer to input context structure. + * @status: Command Completion Code for last command. + * @command_trb: Pointer to command TRB. + */ +struct cdnsp_command { + /* Input context for changing device state. */ + struct cdnsp_container_ctx *in_ctx; + u32 status; + union cdnsp_trb *command_trb; +}; + +/** + * Stream context structure. + * + * @stream_ring: 64-bit stream ring address, cycle state, and stream type. + * @reserved: offset 0x14 - 0x1f reserved for controller internal use. + */ +struct cdnsp_stream_ctx { + __le64 stream_ring; + __le32 reserved[2]; +}; + +/* Stream Context Types - bits 3:1 of stream ctx deq ptr. */ +#define SCT_FOR_CTX(p) (((p) << 1) & GENMASK(3, 1)) +/* Secondary stream array type, dequeue pointer is to a transfer ring. */ +#define SCT_SEC_TR 0 +/* Primary stream array type, dequeue pointer is to a transfer ring. */ +#define SCT_PRI_TR 1 + +/** + * struct cdnsp_stream_info: Representing everything that is needed to + * supports stream capable endpoints. + * @stream_rings: Array of pointers containing Transfer rings for all + * supported streams. + * @num_streams: Number of streams, including stream 0. + * @stream_ctx_array: The stream context array may be bigger than the number + * of streams the driver asked for. + * @num_stream_ctxs: Number of streams. + * @ctx_array_dma: Dma address of Context Stream Array. + * @trb_address_map: For mapping physical TRB addresses to segments in + * stream rings. + * @td_count: Number of TDs associated with endpoint. + * @first_prime_det: First PRIME packet detected. + * @drbls_count: Number of allowed doorbells. + */ +struct cdnsp_stream_info { + struct cdnsp_ring **stream_rings; + unsigned int num_streams; + struct cdnsp_stream_ctx *stream_ctx_array; + unsigned int num_stream_ctxs; + dma_addr_t ctx_array_dma; + struct radix_tree_root trb_address_map; + int td_count; + u8 first_prime_det; +#define STREAM_DRBL_FIFO_DEPTH 2 + u8 drbls_count; +}; + +#define STREAM_LOG_STREAMS 4 +#define STREAM_NUM_STREAMS BIT(STREAM_LOG_STREAMS) + +#if STREAM_LOG_STREAMS > 16 && STREAM_LOG_STREAMS < 1 +#error "Not suupported stream value" +#endif + +/** + * struct cdnsp_ep - extended device side representation of USB endpoint. + * @endpoint: usb endpoint + * @pending_req_list: List of requests queuing on transfer ring. + * @pdev: Device associated with this endpoint. + * @number: Endpoint number (1 - 15). + * idx: The device context index (DCI). + * interval: Interval between packets used for ISOC endpoint. + * @name: A human readable name e.g. ep1out. + * @direction: Endpoint direction. + * @buffering: Number of on-chip buffers related to endpoint. + * @buffering_period; Number of on-chip buffers related to periodic endpoint. + * @in_ctx: Pointer to input endpoint context structure. + * @out_ctx: Pointer to output endpoint context structure. + * @ring: Pointer to transfer ring. + * @stream_info: Hold stream information. + * @ep_state: Current state of endpoint. + * @skip: Sometimes the controller can not process isochronous endpoint ring + * quickly enough, and it will miss some isoc tds on the ring and + * generate Missed Service Error Event. + * Set skip flag when receive a Missed Service Error Event and + * process the missed tds on the endpoint ring. + */ +struct cdnsp_ep { + struct usb_ep endpoint; + struct list_head pending_list; + struct cdnsp_device *pdev; + u8 number; + u8 idx; + u32 interval; + char name[20]; + u8 direction; + u8 buffering; + u8 buffering_period; + struct cdnsp_ep_ctx *in_ctx; + struct cdnsp_ep_ctx *out_ctx; + struct cdnsp_ring *ring; + struct cdnsp_stream_info stream_info; + unsigned int ep_state; +#define EP_ENABLED BIT(0) +#define EP_DIS_IN_RROGRESS BIT(1) +#define EP_HALTED BIT(2) +#define EP_STOPPED BIT(3) +#define EP_WEDGE BIT(4) +#define EP0_HALTED_STATUS BIT(5) +#define EP_HAS_STREAMS BIT(6) + + bool skip; +}; + +/** + * struct cdnsp_device_context_array + * @dev_context_ptr: Array of 64-bit DMA addresses for device contexts. + * @dma: DMA address for device contexts structure. + */ +struct cdnsp_device_context_array { + __le64 dev_context_ptrs[CDNSP_DEV_MAX_SLOTS + 1]; + dma_addr_t dma; +}; + +/** + * struct cdnsp_transfer_event. + * @buffer: 64-bit buffer address, or immediate data. + * @transfer_len: Data length transferred. + * @flags: Field is interpreted differently based on the type of TRB. + */ +struct cdnsp_transfer_event { + __le64 buffer; + __le32 transfer_len; + __le32 flags; +}; + +/* Invalidate event after disabling endpoint. */ +#define TRB_EVENT_INVALIDATE 8 + +/* Transfer event TRB length bit mask. */ +/* bits 0:23 */ +#define EVENT_TRB_LEN(p) ((p) & GENMASK(23, 0)) +/* Completion Code - only applicable for some types of TRBs */ +#define COMP_CODE_MASK (0xff << 24) +#define GET_COMP_CODE(p) (((p) & COMP_CODE_MASK) >> 24) +#define COMP_INVALID 0 +#define COMP_SUCCESS 1 +#define COMP_DATA_BUFFER_ERROR 2 +#define COMP_BABBLE_DETECTED_ERROR 3 +#define COMP_TRB_ERROR 5 +#define COMP_RESOURCE_ERROR 7 +#define COMP_NO_SLOTS_AVAILABLE_ERROR 9 +#define COMP_INVALID_STREAM_TYPE_ERROR 10 +#define COMP_SLOT_NOT_ENABLED_ERROR 11 +#define COMP_ENDPOINT_NOT_ENABLED_ERROR 12 +#define COMP_SHORT_PACKET 13 +#define COMP_RING_UNDERRUN 14 +#define COMP_RING_OVERRUN 15 +#define COMP_VF_EVENT_RING_FULL_ERROR 16 +#define COMP_PARAMETER_ERROR 17 +#define COMP_CONTEXT_STATE_ERROR 19 +#define COMP_EVENT_RING_FULL_ERROR 21 +#define COMP_INCOMPATIBLE_DEVICE_ERROR 22 +#define COMP_MISSED_SERVICE_ERROR 23 +#define COMP_COMMAND_RING_STOPPED 24 +#define COMP_COMMAND_ABORTED 25 +#define COMP_STOPPED 26 +#define COMP_STOPPED_LENGTH_INVALID 27 +#define COMP_STOPPED_SHORT_PACKET 28 +#define COMP_MAX_EXIT_LATENCY_TOO_LARGE_ERROR 29 +#define COMP_ISOCH_BUFFER_OVERRUN 31 +#define COMP_EVENT_LOST_ERROR 32 +#define COMP_UNDEFINED_ERROR 33 +#define COMP_INVALID_STREAM_ID_ERROR 34 + +/*Transfer Event NRDY bit fields */ +#define TRB_TO_DEV_STREAM(p) ((p) & GENMASK(16, 0)) +#define TRB_TO_HOST_STREAM(p) ((p) & GENMASK(16, 0)) +#define STREAM_PRIME_ACK 0xFFFE +#define STREAM_REJECTED 0xFFFF + +/** Transfer Event bit fields **/ +#define TRB_TO_EP_ID(p) (((p) & GENMASK(20, 16)) >> 16) + +/** + * struct cdnsp_link_trb + * @segment_ptr: 64-bit segment pointer. + * @intr_target: Interrupter target. + * @control: Flags. + */ +struct cdnsp_link_trb { + __le64 segment_ptr; + __le32 intr_target; + __le32 control; +}; + +/* control bitfields */ +#define LINK_TOGGLE BIT(1) + +/** + * struct cdnsp_event_cmd - Command completion event TRB. + * cmd_trb: Pointer to command TRB, or the value passed by the event data trb + * status: Command completion parameters and error code. + * flags: Flags. + */ +struct cdnsp_event_cmd { + __le64 cmd_trb; + __le32 status; + __le32 flags; +}; + +/* flags bitmasks */ + +/* Address device - disable SetAddress. */ +#define TRB_BSR BIT(9) + +/* Configure Endpoint - Deconfigure. */ +#define TRB_DC BIT(9) + +/* Force Header */ +#define TRB_FH_TO_PACKET_TYPE(p) ((p) & GENMASK(4, 0)) +#define TRB_FH_TR_PACKET 0x4 +#define TRB_FH_TO_DEVICE_ADDRESS(p) (((p) << 25) & GENMASK(31, 25)) +#define TRB_FH_TR_PACKET_DEV_NOT 0x6 +#define TRB_FH_TO_NOT_TYPE(p) (((p) << 4) & GENMASK(7, 4)) +#define TRB_FH_TR_PACKET_FUNCTION_WAKE 0x1 +#define TRB_FH_TO_INTERFACE(p) (((p) << 8) & GENMASK(15, 8)) + +enum cdnsp_setup_dev { + SETUP_CONTEXT_ONLY, + SETUP_CONTEXT_ADDRESS, +}; + +/* bits 24:31 are the slot ID. */ +#define TRB_TO_SLOT_ID(p) (((p) & GENMASK(31, 24)) >> 24) +#define SLOT_ID_FOR_TRB(p) (((p) << 24) & GENMASK(31, 24)) + +/* Stop Endpoint TRB - ep_index to endpoint ID for this TRB. */ +#define TRB_TO_EP_INDEX(p) (((p) >> 16) & 0x1f) + +#define EP_ID_FOR_TRB(p) ((((p) + 1) << 16) & GENMASK(20, 16)) + +#define SUSPEND_PORT_FOR_TRB(p) (((p) & 1) << 23) +#define TRB_TO_SUSPEND_PORT(p) (((p) >> 23) & 0x1) +#define LAST_EP_INDEX 30 + +/* Set TR Dequeue Pointer command TRB fields. */ +#define TRB_TO_STREAM_ID(p) ((((p) & GENMASK(31, 16)) >> 16)) +#define STREAM_ID_FOR_TRB(p) ((((p)) << 16) & GENMASK(31, 16)) +#define SCT_FOR_TRB(p) (((p) << 1) & 0x7) + +/* Link TRB specific fields. */ +#define TRB_TC BIT(1) + +/* Port Status Change Event TRB fields. */ +/* Port ID - bits 31:24. */ +#define GET_PORT_ID(p) (((p) & GENMASK(31, 24)) >> 24) +#define SET_PORT_ID(p) (((p) << 24) & GENMASK(31, 24)) +#define EVENT_DATA BIT(2) + +/* Normal TRB fields. */ +/* transfer_len bitmasks - bits 0:16. */ +#define TRB_LEN(p) ((p) & GENMASK(16, 0)) +/* TD Size, packets remaining in this TD, bits 21:17 (5 bits, so max 31). */ +#define TRB_TD_SIZE(p) (min((p), (u32)31) << 17) +#define GET_TD_SIZE(p) (((p) & GENMASK(21, 17)) >> 17) +/* + * Controller uses the TD_SIZE field for TBC if Extended TBC + * is enabled (ETE). + */ +#define TRB_TD_SIZE_TBC(p) (min((p), (u32)31) << 17) +/* Interrupter Target - which MSI-X vector to target the completion event at. */ +#define TRB_INTR_TARGET(p) (((p) << 22) & GENMASK(31, 22)) +#define GET_INTR_TARGET(p) (((p) & GENMASK(31, 22)) >> 22) +/* + * Total burst count field, Rsvdz on controller with Extended TBC + * enabled (ETE). + */ +#define TRB_TBC(p) (((p) & 0x3) << 7) +#define TRB_TLBPC(p) (((p) & 0xf) << 16) + +/* Cycle bit - indicates TRB ownership by driver or driver.*/ +#define TRB_CYCLE BIT(0) +/* + * Force next event data TRB to be evaluated before task switch. + * Used to pass OS data back after a TD completes. + */ +#define TRB_ENT BIT(1) +/* Interrupt on short packet. */ +#define TRB_ISP BIT(2) +/* Set PCIe no snoop attribute. */ +#define TRB_NO_SNOOP BIT(3) +/* Chain multiple TRBs into a TD. */ +#define TRB_CHAIN BIT(4) +/* Interrupt on completion. */ +#define TRB_IOC BIT(5) +/* The buffer pointer contains immediate data. */ +#define TRB_IDT BIT(6) +/* 0 - NRDY during data stage, 1 - NRDY during status stage (only control). */ +#define TRB_STAT BIT(7) +/* Block Event Interrupt. */ +#define TRB_BEI BIT(9) + +/* Control transfer TRB specific fields. */ +#define TRB_DIR_IN BIT(16) + +/* TRB bit mask in Data Stage TRB */ +#define TRB_SETUPID_BITMASK GENMASK(9, 8) +#define TRB_SETUPID(p) ((p) << 8) +#define TRB_SETUPID_TO_TYPE(p) (((p) & TRB_SETUPID_BITMASK) >> 8) + +#define TRB_SETUP_SPEEDID_USB3 0x1 +#define TRB_SETUP_SPEEDID_USB2 0x0 +#define TRB_SETUP_SPEEDID(p) ((p) & (1 << 7)) + +#define TRB_SETUPSTAT_ACK 0x1 +#define TRB_SETUPSTAT_STALL 0x0 +#define TRB_SETUPSTAT(p) ((p) << 6) + +/* Isochronous TRB specific fields */ +#define TRB_SIA BIT(31) +#define TRB_FRAME_ID(p) (((p) << 20) & GENMASK(30, 20)) + +struct cdnsp_generic_trb { + __le32 field[4]; +}; + +union cdnsp_trb { + struct cdnsp_link_trb link; + struct cdnsp_transfer_event trans_event; + struct cdnsp_event_cmd event_cmd; + struct cdnsp_generic_trb generic; +}; + +/* TRB bit mask. */ +#define TRB_TYPE_BITMASK GENMASK(15, 10) +#define TRB_TYPE(p) ((p) << 10) +#define TRB_FIELD_TO_TYPE(p) (((p) & TRB_TYPE_BITMASK) >> 10) + +/* TRB type IDs. */ +/* bulk, interrupt, isoc scatter/gather, and control data stage. */ +#define TRB_NORMAL 1 +/* Setup Stage for control transfers. */ +#define TRB_SETUP 2 +/* Data Stage for control transfers. */ +#define TRB_DATA 3 +/* Status Stage for control transfers. */ +#define TRB_STATUS 4 +/* ISOC transfers. */ +#define TRB_ISOC 5 +/* TRB for linking ring segments. */ +#define TRB_LINK 6 +#define TRB_EVENT_DATA 7 +/* Transfer Ring No-op (not for the command ring). */ +#define TRB_TR_NOOP 8 + +/* Command TRBs */ +/* Enable Slot Command. */ +#define TRB_ENABLE_SLOT 9 +/* Disable Slot Command. */ +#define TRB_DISABLE_SLOT 10 +/* Address Device Command. */ +#define TRB_ADDR_DEV 11 +/* Configure Endpoint Command. */ +#define TRB_CONFIG_EP 12 +/* Evaluate Context Command. */ +#define TRB_EVAL_CONTEXT 13 +/* Reset Endpoint Command. */ +#define TRB_RESET_EP 14 +/* Stop Transfer Ring Command. */ +#define TRB_STOP_RING 15 +/* Set Transfer Ring Dequeue Pointer Command. */ +#define TRB_SET_DEQ 16 +/* Reset Device Command. */ +#define TRB_RESET_DEV 17 +/* Force Event Command (opt). */ +#define TRB_FORCE_EVENT 18 +/* Force Header Command - generate a transaction or link management packet. */ +#define TRB_FORCE_HEADER 22 +/* No-op Command - not for transfer rings. */ +#define TRB_CMD_NOOP 23 +/* TRB IDs 24-31 reserved. */ + +/* Event TRBS. */ +/* Transfer Event. */ +#define TRB_TRANSFER 32 +/* Command Completion Event. */ +#define TRB_COMPLETION 33 +/* Port Status Change Event. */ +#define TRB_PORT_STATUS 34 +/* Device Controller Event. */ +#define TRB_HC_EVENT 37 +/* MFINDEX Wrap Event - microframe counter wrapped. */ +#define TRB_MFINDEX_WRAP 39 +/* TRB IDs 40-47 reserved. */ +/* Endpoint Not Ready Event. */ +#define TRB_ENDPOINT_NRDY 48 +/* TRB IDs 49-53 reserved. */ +/* Halt Endpoint Command. */ +#define TRB_HALT_ENDPOINT 54 +/* Doorbell Overflow Event. */ +#define TRB_DRB_OVERFLOW 57 +/* Flush Endpoint Command. */ +#define TRB_FLUSH_ENDPOINT 58 + +#define TRB_TYPE_LINK(x) (((x) & TRB_TYPE_BITMASK) == TRB_TYPE(TRB_LINK)) +#define TRB_TYPE_LINK_LE32(x) (((x) & cpu_to_le32(TRB_TYPE_BITMASK)) == \ + cpu_to_le32(TRB_TYPE(TRB_LINK))) +#define TRB_TYPE_NOOP_LE32(x) (((x) & cpu_to_le32(TRB_TYPE_BITMASK)) == \ + cpu_to_le32(TRB_TYPE(TRB_TR_NOOP))) + +/* + * TRBS_PER_SEGMENT must be a multiple of 4. + * The command ring is 64-byte aligned, so it must also be greater than 16. + */ +#define TRBS_PER_SEGMENT 256 +#define TRBS_PER_EVENT_SEGMENT 256 +#define TRBS_PER_EV_DEQ_UPDATE 100 +#define TRB_SEGMENT_SIZE (TRBS_PER_SEGMENT * 16) +#define TRB_SEGMENT_SHIFT (ilog2(TRB_SEGMENT_SIZE)) +/* TRB buffer pointers can't cross 64KB boundaries. */ +#define TRB_MAX_BUFF_SHIFT 16 +#define TRB_MAX_BUFF_SIZE BIT(TRB_MAX_BUFF_SHIFT) +/* How much data is left before the 64KB boundary? */ +#define TRB_BUFF_LEN_UP_TO_BOUNDARY(addr) (TRB_MAX_BUFF_SIZE - \ + ((addr) & (TRB_MAX_BUFF_SIZE - 1))) + +/** + * struct cdnsp_segment - segment related data. + * @trbs: Array of Transfer Request Blocks. + * @next: Pointer to the next segment. + * @dma: DMA address of current segment. + * @bounce_dma: Bounce buffer DMA address . + * @bounce_buf: Bounce buffer virtual address. + * bounce_offs: Bounce buffer offset. + * bounce_len: Bounce buffer length. + */ +struct cdnsp_segment { + union cdnsp_trb *trbs; + struct cdnsp_segment *next; + dma_addr_t dma; + /* Max packet sized bounce buffer for td-fragmant alignment */ + dma_addr_t bounce_dma; + void *bounce_buf; + unsigned int bounce_offs; + unsigned int bounce_len; +}; + +/** + * struct cdnsp_td - Transfer Descriptor object. + * @td_list: Used for binding TD with ep_ring->td_list. + * @preq: Request associated with this TD + * @start_seg: Segment containing the first_trb in TD. + * @first_trb: First TRB for this TD. + * @last_trb: Last TRB related with TD. + * @bounce_seg: Bounce segment for this TD. + * @request_length_set: actual_length of the request has already been set. + * @drbl - TD has been added to HW scheduler - only for stream capable + * endpoints. + */ +struct cdnsp_td { + struct list_head td_list; + struct cdnsp_request *preq; + struct cdnsp_segment *start_seg; + union cdnsp_trb *first_trb; + union cdnsp_trb *last_trb; + struct cdnsp_segment *bounce_seg; + bool request_length_set; + bool drbl; +}; + +/** + * struct cdnsp_dequeue_state - New dequeue pointer for Transfer Ring. + * @new_deq_seg: New dequeue segment. + * @new_deq_ptr: New dequeue pointer. + * @new_cycle_state: New cycle state. + * @stream_id: stream id for which new dequeue pointer has been selected. + */ +struct cdnsp_dequeue_state { + struct cdnsp_segment *new_deq_seg; + union cdnsp_trb *new_deq_ptr; + int new_cycle_state; + unsigned int stream_id; +}; + +enum cdnsp_ring_type { + TYPE_CTRL = 0, + TYPE_ISOC, + TYPE_BULK, + TYPE_INTR, + TYPE_STREAM, + TYPE_COMMAND, + TYPE_EVENT, +}; + +/** + * struct cdnsp_ring - information describing transfer, command or event ring. + * @first_seg: First segment on transfer ring. + * @last_seg: Last segment on transfer ring. + * @enqueue: SW enqueue pointer address. + * @enq_seg: SW enqueue segment address. + * @dequeue: SW dequeue pointer address. + * @deq_seg: SW dequeue segment address. + * @td_list: transfer descriptor list associated with this ring. + * @cycle_state: Current cycle bit. Write the cycle state into the TRB cycle + * field to give ownership of the TRB to the device controller + * (if we are the producer) or to check if we own the TRB + * (if we are the consumer). + * @stream_id: Stream id + * @stream_active: Stream is active - PRIME packet has been detected. + * @stream_rejected: This ring has been rejected by host. + * @num_tds: Number of TDs associated with ring. + * @num_segs: Number of segments. + * @num_trbs_free: Number of free TRBs on the ring. + * @bounce_buf_len: Length of bounce buffer. + * @type: Ring type - event, transfer, or command ring. + * @last_td_was_short - TD is short TD. + * @trb_address_map: For mapping physical TRB addresses to segments in + * stream rings. + */ +struct cdnsp_ring { + struct cdnsp_segment *first_seg; + struct cdnsp_segment *last_seg; + union cdnsp_trb *enqueue; + struct cdnsp_segment *enq_seg; + union cdnsp_trb *dequeue; + struct cdnsp_segment *deq_seg; + struct list_head td_list; + u32 cycle_state; + unsigned int stream_id; + unsigned int stream_active; + unsigned int stream_rejected; + int num_tds; + unsigned int num_segs; + unsigned int num_trbs_free; + unsigned int bounce_buf_len; + enum cdnsp_ring_type type; + bool last_td_was_short; + struct radix_tree_root *trb_address_map; +}; + +/** + * struct cdnsp_erst_entry - even ring segment table entry object. + * @seg_addr: 64-bit event ring segment address. + * seg_size: Number of TRBs in segment.; + */ +struct cdnsp_erst_entry { + __le64 seg_addr; + __le32 seg_size; + /* Set to zero */ + __le32 rsvd; +}; + +/** + * struct cdnsp_erst - even ring segment table for event ring. + * @entries: Array of event ring segments + * @num_entries: Number of segments in entries array. + * @erst_dma_addr: DMA address for entries array. + */ +struct cdnsp_erst { + struct cdnsp_erst_entry *entries; + unsigned int num_entries; + dma_addr_t erst_dma_addr; +}; + +/** + * struct cdnsp_request - extended device side representation of usb_request + * object . + * @td: Transfer descriptor associated with this request. + * @request: Generic usb_request object describing single I/O request. + * @list: Used to adding request to endpoint pending_list. + * @pep: Extended representation of usb_ep object + * @epnum: Endpoint number associated with usb request. + * @direction: Endpoint direction for usb request. + */ +struct cdnsp_request { + struct cdnsp_td td; + struct usb_request request; + struct list_head list; + struct cdnsp_ep *pep; + u8 epnum; + unsigned direction:1; +}; + +#define ERST_NUM_SEGS 1 + +/* Stages used during enumeration process.*/ +enum cdnsp_ep0_stage { + CDNSP_SETUP_STAGE, + CDNSP_DATA_STAGE, + CDNSP_STATUS_STAGE, +}; + +/** + * struct cdnsp_port - holds information about detected ports. + * @port_num: Port number. + * @exist: Indicate if port exist. + * maj_rev: Major revision. + * min_rev: Minor revision. + */ +struct cdnsp_port { + struct cdnsp_port_regs __iomem *regs; + u8 port_num; + u8 exist; + u8 maj_rev; + u8 min_rev; +}; + +#define CDNSP_EXT_PORT_MAJOR(x) (((x) >> 24) & 0xff) +#define CDNSP_EXT_PORT_MINOR(x) (((x) >> 16) & 0xff) +#define CDNSP_EXT_PORT_OFF(x) ((x) & 0xff) +#define CDNSP_EXT_PORT_COUNT(x) (((x) >> 8) & 0xff) + +/** + * struct cdnsp_device - represent USB device. + * @dev: Pointer to device structure associated whit this controller. + * @gadget: Device side representation of the peripheral controller. + * @gadget_driver: Pointer to the gadget driver. + * @irq: IRQ line number used by device side. + * @regs:IO device memory. + * @cap_regs: Capability registers. + * @op_regs: Operational registers. + * @run_regs: Runtime registers. + * @dba: Device base address register. + * @ir_set: Current interrupter register set. + * @port20_regs: Port 2.0 Peripheral Configuration Registers. + * @port3x_regs: USB3.x Port Peripheral Configuration Registers. + * @hcs_params1: Cached register copies of read-only HCSPARAMS1 + * @hcc_params: Cached register copies of read-only HCCPARAMS1 + * @rev_cap: Controller capability. + * @setup: Temporary buffer for setup packet. + * @ep0_preq: Internal allocated request used during enumeration. + * @ep0_stage: ep0 stage during enumeration process. + * @three_stage_setup: Three state or two state setup. + * @ep0_expect_in: Data IN expected for control transfer. + * @setup_id: Setup identifier. + * @setup_speed - Speed detected for current SETUP packet. + * @setup_buf: Buffer for SETUP packet. + * @device_address: Current device address. + * @may_wakeup: remote wakeup enabled/disabled. + * @lock: Lock used in interrupt thread context. + * @hci_version: device controller version. + * @dcbaa: Device context base address array. + * @cmd_ring: Command ring. + * @cmd: Represent all what is needed to issue command on Command Ring. + * @event_ring: Event ring. + * @erst: Event Ring Segment table + * @slot_id: Current Slot ID. Should be 0 or 1. + * @out_ctx: Output context. + * @in_ctx: Input context. + * @eps: array of endpoints object associated with device. + * @usb2_hw_lpm_capable: hardware lpm is enabled; + * @u1_allowed: Allow device transition to U1 state. + * @u2_allowed: Allow device transition to U2 state + * @device_pool: DMA pool for allocating input and output context. + * @segment_pool: DMA pool for allocating new segments. + * @cdnsp_state: Current state of controller. + * @link_state: Current link state. + * @usb2_port - Port USB 2.0. + * @usb3_port - Port USB 3.0. + * @active_port - Current selected Port. + * @test_mode: selected Test Mode. + */ +struct cdnsp_device { + struct device *dev; + struct usb_gadget gadget; + struct usb_gadget_driver *gadget_driver; + unsigned int irq; + void __iomem *regs; + + /* Registers map */ + struct cdnsp_cap_regs __iomem *cap_regs; + struct cdnsp_op_regs __iomem *op_regs; + struct cdnsp_run_regs __iomem *run_regs; + struct cdnsp_doorbell_array __iomem *dba; + struct cdnsp_intr_reg __iomem *ir_set; + struct cdnsp_20port_cap __iomem *port20_regs; + struct cdnsp_3xport_cap __iomem *port3x_regs; + + /* Cached register copies of read-only CDNSP data */ + __u32 hcs_params1; + __u32 hcs_params3; + __u32 hcc_params; + struct cdnsp_rev_cap rev_cap; + /* Lock used in interrupt thread context. */ + spinlock_t lock; + struct usb_ctrlrequest setup; + struct cdnsp_request ep0_preq; + enum cdnsp_ep0_stage ep0_stage; + u8 three_stage_setup; + u8 ep0_expect_in; + u8 setup_id; + u8 setup_speed; + void *setup_buf; + u8 device_address; + int may_wakeup; + u16 hci_version; + + /* data structures */ + struct cdnsp_device_context_array *dcbaa; + struct cdnsp_ring *cmd_ring; + struct cdnsp_command cmd; + struct cdnsp_ring *event_ring; + struct cdnsp_erst erst; + int slot_id; + + /* + * Commands to the hardware are passed an "input context" that + * tells the hardware what to change in its data structures. + * The hardware will return changes in an "output context" that + * software must allocate for the hardware. . + */ + struct cdnsp_container_ctx out_ctx; + struct cdnsp_container_ctx in_ctx; + struct cdnsp_ep eps[CDNSP_ENDPOINTS_NUM]; + u8 usb2_hw_lpm_capable:1; + u8 u1_allowed:1; + u8 u2_allowed:1; + + /* DMA pools */ + struct dma_pool *device_pool; + struct dma_pool *segment_pool; + +#define CDNSP_STATE_HALTED BIT(1) +#define CDNSP_STATE_DYING BIT(2) +#define CDNSP_STATE_DISCONNECT_PENDING BIT(3) +#define CDNSP_WAKEUP_PENDING BIT(4) + unsigned int cdnsp_state; + unsigned int link_state; + + struct cdnsp_port usb2_port; + struct cdnsp_port usb3_port; + struct cdnsp_port *active_port; + u16 test_mode; +}; + +#endif /* __LINUX_CDNSP_GADGET_H */ -- cgit v1.2.3 From 3d82904559f4f5a2622db1b21de3edf2eded7664 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Mon, 7 Dec 2020 11:32:24 +0100 Subject: usb: cdnsp: cdns3 Add main part of Cadence USBSSP DRD Driver This patch introduces the main part of Cadence USBSSP DRD driver to Linux kernel. To reduce the patch size a little bit, the header file gadget.h was intentionally added as separate patch. The Cadence USBSSP DRD Controller is a highly configurable IP Core which can be instantiated as Dual-Role Device (DRD), Peripheral Only and Host Only (XHCI)configurations. The current driver has been validated with FPGA platform. We have support for PCIe bus, which is used on FPGA prototyping. The host side of USBSS DRD controller is compliant with XHCI. The architecture for device side is almost the same as for host side, and most of the XHCI specification can be used to understand how this controller operates. Signed-off-by: Pawel Laszczak Signed-off-by: Peter Chen --- drivers/usb/Makefile | 2 + drivers/usb/cdns3/Kconfig | 64 +- drivers/usb/cdns3/Makefile | 27 +- drivers/usb/cdns3/cdnsp-ep0.c | 477 ++++++++ drivers/usb/cdns3/cdnsp-gadget.c | 1954 ++++++++++++++++++++++++++++++ drivers/usb/cdns3/cdnsp-gadget.h | 137 +++ drivers/usb/cdns3/cdnsp-mem.c | 1310 ++++++++++++++++++++ drivers/usb/cdns3/cdnsp-pci.c | 254 ++++ drivers/usb/cdns3/cdnsp-ring.c | 2376 +++++++++++++++++++++++++++++++++++++ drivers/usb/cdns3/core.c | 11 +- drivers/usb/cdns3/core.h | 2 +- drivers/usb/cdns3/drd.c | 29 +- drivers/usb/cdns3/drd.h | 2 + drivers/usb/cdns3/gadget-export.h | 18 +- drivers/usb/cdns3/host-export.h | 7 +- 15 files changed, 6641 insertions(+), 29 deletions(-) create mode 100644 drivers/usb/cdns3/cdnsp-ep0.c create mode 100644 drivers/usb/cdns3/cdnsp-gadget.c create mode 100644 drivers/usb/cdns3/cdnsp-mem.c create mode 100644 drivers/usb/cdns3/cdnsp-pci.c create mode 100644 drivers/usb/cdns3/cdnsp-ring.c diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index ba5706ccc188..3e2cc95b7b0b 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -13,7 +13,9 @@ obj-$(CONFIG_USB_DWC3) += dwc3/ obj-$(CONFIG_USB_DWC2) += dwc2/ obj-$(CONFIG_USB_ISP1760) += isp1760/ +obj-$(CONFIG_USB_CDNS_SUPPORT) += cdns3/ obj-$(CONFIG_USB_CDNS3) += cdns3/ +obj-$(CONFIG_USB_CDNSP_PCI) += cdns3/ obj-$(CONFIG_USB_MON) += mon/ obj-$(CONFIG_USB_MTU3) += mtu3/ diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig index 58154c0a73ac..b98ca0a1352a 100644 --- a/drivers/usb/cdns3/Kconfig +++ b/drivers/usb/cdns3/Kconfig @@ -1,21 +1,28 @@ -config CDNS_USB_COMMON - tristate +config USB_CDNS_SUPPORT + tristate "Cadence USB Support" + depends on USB_SUPPORT && (USB || USB_GADGET) && HAS_DMA + select USB_XHCI_PLATFORM if USB_XHCI_HCD + select USB_ROLE_SWITCH + help + Say Y here if your system has a Cadence USBSS or USBSSP + dual-role controller. + It supports: dual-role switch, Host-only, and Peripheral-only. -config CDNS_USB_HOST +config USB_CDNS_HOST bool +if USB_CDNS_SUPPORT + config USB_CDNS3 tristate "Cadence USB3 Dual-Role Controller" - depends on USB_SUPPORT && (USB || USB_GADGET) && HAS_DMA - select USB_XHCI_PLATFORM if USB_XHCI_HCD - select USB_ROLE_SWITCH - select CDNS_USB_COMMON + depends on USB_CDNS_SUPPORT help Say Y here if your system has a Cadence USB3 dual-role controller. It supports: dual-role switch, Host-only, and Peripheral-only. If you choose to build this driver is a dynamically linked as module, the module will be called cdns3.ko. +endif if USB_CDNS3 @@ -32,7 +39,7 @@ config USB_CDNS3_GADGET config USB_CDNS3_HOST bool "Cadence USB3 host controller" depends on USB=y || USB=USB_CDNS3 - select CDNS_USB_HOST + select USB_CDNS_HOST help Say Y here to enable host controller functionality of the Cadence driver. @@ -72,3 +79,44 @@ config USB_CDNS3_IMX For example, imx8qm and imx8qxp. endif + +if USB_CDNS_SUPPORT + +config USB_CDNSP_PCI + tristate "Cadence CDNSP Dual-Role Controller" + depends on USB_CDNS_SUPPORT && USB_PCI && ACPI + help + Say Y here if your system has a Cadence CDNSP dual-role controller. + It supports: dual-role switch Host-only, and Peripheral-only. + + If you choose to build this driver is a dynamically linked + module, the module will be called cdnsp.ko. +endif + +if USB_CDNSP_PCI + +config USB_CDNSP_GADGET + bool "Cadence CDNSP device controller" + depends on USB_GADGET=y || USB_GADGET=USB_CDNSP_PCI + help + Say Y here to enable device controller functionality of the + Cadence CDNSP-DEV driver. + + Cadence CDNSP Device Controller in device mode is + very similar to XHCI controller. Therefore some algorithms + used has been taken from host driver. + This controller supports FF, HS, SS and SSP mode. + It doesn't support LS. + +config USB_CDNSP_HOST + bool "Cadence CDNSP host controller" + depends on USB=y || USB=USB_CDNSP_PCI + select USB_CDNS_HOST + help + Say Y here to enable host controller functionality of the + Cadence driver. + + Host controller is compliant with XHCI so it uses + standard XHCI driver. + +endif diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile index 16df87abf3cf..a84b129f14b8 100644 --- a/drivers/usb/cdns3/Makefile +++ b/drivers/usb/cdns3/Makefile @@ -1,20 +1,25 @@ # SPDX-License-Identifier: GPL-2.0 # define_trace.h needs to know how to find our header -CFLAGS_trace.o := -I$(src) +CFLAGS_trace.o := -I$(src) -cdns-usb-common-y := core.o drd.o -cdns3-y := cdns3-plat.o +cdns-usb-common-y := core.o drd.o +cdns3-y := cdns3-plat.o -obj-$(CONFIG_USB_CDNS3) += cdns3.o -obj-$(CONFIG_CDNS_USB_COMMON) += cdns-usb-common.o +obj-$(CONFIG_USB_CDNS3) += cdns3.o +obj-$(CONFIG_USB_CDNS_SUPPORT) += cdns-usb-common.o -cdns-usb-common-$(CONFIG_CDNS_USB_HOST) += host.o -cdns3-$(CONFIG_USB_CDNS3_GADGET) += gadget.o ep0.o +cdns-usb-common-$(CONFIG_USB_CDNS_HOST) += host.o +cdns3-$(CONFIG_USB_CDNS3_GADGET) += gadget.o ep0.o ifneq ($(CONFIG_USB_CDNS3_GADGET),) -cdns3-$(CONFIG_TRACING) += trace.o +cdns3-$(CONFIG_TRACING) += trace.o endif -obj-$(CONFIG_USB_CDNS3_PCI_WRAP) += cdns3-pci-wrap.o -obj-$(CONFIG_USB_CDNS3_TI) += cdns3-ti.o -obj-$(CONFIG_USB_CDNS3_IMX) += cdns3-imx.o +obj-$(CONFIG_USB_CDNS3_PCI_WRAP) += cdns3-pci-wrap.o +obj-$(CONFIG_USB_CDNS3_TI) += cdns3-ti.o +obj-$(CONFIG_USB_CDNS3_IMX) += cdns3-imx.o + +cdnsp-udc-pci-y := cdnsp-pci.o +obj-$(CONFIG_USB_CDNSP_PCI) += cdnsp-udc-pci.o +cdnsp-udc-pci-$(CONFIG_USB_CDNSP_GADGET) += cdnsp-ring.o cdnsp-gadget.o \ + cdnsp-mem.o cdnsp-ep0.o diff --git a/drivers/usb/cdns3/cdnsp-ep0.c b/drivers/usb/cdns3/cdnsp-ep0.c new file mode 100644 index 000000000000..aa220d06d887 --- /dev/null +++ b/drivers/usb/cdns3/cdnsp-ep0.c @@ -0,0 +1,477 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence CDNSP DRD Driver. + * + * Copyright (C) 2020 Cadence. + * + * Author: Pawel Laszczak + * + */ + +#include +#include +#include + +#include "cdnsp-gadget.h" + +static void cdnsp_ep0_stall(struct cdnsp_device *pdev) +{ + struct cdnsp_request *preq; + struct cdnsp_ep *pep; + + pep = &pdev->eps[0]; + preq = next_request(&pep->pending_list); + + if (pdev->three_stage_setup) { + cdnsp_halt_endpoint(pdev, pep, true); + + if (preq) + cdnsp_gadget_giveback(pep, preq, -ECONNRESET); + } else { + pep->ep_state |= EP0_HALTED_STATUS; + + if (preq) + list_del(&preq->list); + + cdnsp_status_stage(pdev); + } +} + +static int cdnsp_ep0_delegate_req(struct cdnsp_device *pdev, + struct usb_ctrlrequest *ctrl) +{ + int ret; + + spin_unlock(&pdev->lock); + ret = pdev->gadget_driver->setup(&pdev->gadget, ctrl); + spin_lock(&pdev->lock); + + return ret; +} + +static int cdnsp_ep0_set_config(struct cdnsp_device *pdev, + struct usb_ctrlrequest *ctrl) +{ + enum usb_device_state state = pdev->gadget.state; + u32 cfg; + int ret; + + cfg = le16_to_cpu(ctrl->wValue); + + switch (state) { + case USB_STATE_ADDRESS: + break; + case USB_STATE_CONFIGURED: + break; + default: + dev_err(pdev->dev, "Set Configuration - bad device state\n"); + return -EINVAL; + } + + ret = cdnsp_ep0_delegate_req(pdev, ctrl); + if (ret) + return ret; + + if (!cfg) + usb_gadget_set_state(&pdev->gadget, USB_STATE_ADDRESS); + + return 0; +} + +static int cdnsp_ep0_set_address(struct cdnsp_device *pdev, + struct usb_ctrlrequest *ctrl) +{ + enum usb_device_state state = pdev->gadget.state; + struct cdnsp_slot_ctx *slot_ctx; + unsigned int slot_state; + int ret; + u32 addr; + + addr = le16_to_cpu(ctrl->wValue); + + if (addr > 127) { + dev_err(pdev->dev, "Invalid device address %d\n", addr); + return -EINVAL; + } + + slot_ctx = cdnsp_get_slot_ctx(&pdev->out_ctx); + + if (state == USB_STATE_CONFIGURED) { + dev_err(pdev->dev, "Can't Set Address from Configured State\n"); + return -EINVAL; + } + + pdev->device_address = le16_to_cpu(ctrl->wValue); + + slot_ctx = cdnsp_get_slot_ctx(&pdev->out_ctx); + slot_state = GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state)); + if (slot_state == SLOT_STATE_ADDRESSED) + cdnsp_reset_device(pdev); + + /*set device address*/ + ret = cdnsp_setup_device(pdev, SETUP_CONTEXT_ADDRESS); + if (ret) + return ret; + + if (addr) + usb_gadget_set_state(&pdev->gadget, USB_STATE_ADDRESS); + else + usb_gadget_set_state(&pdev->gadget, USB_STATE_DEFAULT); + + return 0; +} + +int cdnsp_status_stage(struct cdnsp_device *pdev) +{ + pdev->ep0_stage = CDNSP_STATUS_STAGE; + pdev->ep0_preq.request.length = 0; + + return cdnsp_ep_enqueue(pdev->ep0_preq.pep, &pdev->ep0_preq); +} + +static int cdnsp_w_index_to_ep_index(__le32 wIndex) +{ + wIndex = le32_to_cpu(wIndex); + + if (!(wIndex & USB_ENDPOINT_NUMBER_MASK)) + return 0; + + return ((wIndex & USB_ENDPOINT_NUMBER_MASK) * 2) + + (wIndex & USB_ENDPOINT_DIR_MASK ? 1 : 0) - 1; +} + +static int cdnsp_ep0_handle_status(struct cdnsp_device *pdev, + struct usb_ctrlrequest *ctrl) +{ + struct cdnsp_ep *pep; + __le16 *response; + int ep_sts = 0; + u16 status = 0; + u32 recipient; + + recipient = ctrl->bRequestType & USB_RECIP_MASK; + + switch (recipient) { + case USB_RECIP_DEVICE: + status = pdev->gadget.is_selfpowered; + status |= pdev->may_wakeup << USB_DEVICE_REMOTE_WAKEUP; + + if (pdev->gadget.speed >= USB_SPEED_SUPER) { + status |= pdev->u1_allowed << USB_DEV_STAT_U1_ENABLED; + status |= pdev->u2_allowed << USB_DEV_STAT_U2_ENABLED; + } + break; + case USB_RECIP_INTERFACE: + /* + * Function Remote Wake Capable D0 + * Function Remote Wakeup D1 + */ + return cdnsp_ep0_delegate_req(pdev, ctrl); + case USB_RECIP_ENDPOINT: + pep = &pdev->eps[cdnsp_w_index_to_ep_index(ctrl->wIndex)]; + ep_sts = GET_EP_CTX_STATE(pep->out_ctx); + + /* check if endpoint is stalled */ + if (ep_sts == EP_STATE_HALTED) + status = BIT(USB_ENDPOINT_HALT); + break; + default: + return -EINVAL; + } + + response = (__le16 *)pdev->setup_buf; + *response = cpu_to_le16(status); + + pdev->ep0_preq.request.length = sizeof(*response); + pdev->ep0_preq.request.buf = pdev->setup_buf; + + return cdnsp_ep_enqueue(pdev->ep0_preq.pep, &pdev->ep0_preq); +} + +static void cdnsp_enter_test_mode(struct cdnsp_device *pdev) +{ + u32 temp; + + temp = readl(&pdev->active_port->regs->portpmsc) & ~GENMASK(31, 28); + temp |= PORT_TEST_MODE(pdev->test_mode); + writel(temp, &pdev->active_port->regs->portpmsc); +} + +static int cdnsp_ep0_handle_feature_device(struct cdnsp_device *pdev, + struct usb_ctrlrequest *ctrl, + int set) +{ + enum usb_device_state state; + enum usb_device_speed speed; + u16 tmode; + + state = pdev->gadget.state; + speed = pdev->gadget.speed; + + switch (le16_to_cpu(ctrl->wValue)) { + case USB_DEVICE_REMOTE_WAKEUP: + pdev->may_wakeup = !!set; + break; + case USB_DEVICE_U1_ENABLE: + if (state != USB_STATE_CONFIGURED || speed < USB_SPEED_SUPER) + return -EINVAL; + + pdev->u1_allowed = !!set; + break; + case USB_DEVICE_U2_ENABLE: + if (state != USB_STATE_CONFIGURED || speed < USB_SPEED_SUPER) + return -EINVAL; + + pdev->u2_allowed = !!set; + break; + case USB_DEVICE_LTM_ENABLE: + return -EINVAL; + case USB_DEVICE_TEST_MODE: + if (state != USB_STATE_CONFIGURED || speed > USB_SPEED_HIGH) + return -EINVAL; + + tmode = le16_to_cpu(ctrl->wIndex); + + if (!set || (tmode & 0xff) != 0) + return -EINVAL; + + tmode = tmode >> 8; + + if (tmode > USB_TEST_FORCE_ENABLE || tmode < USB_TEST_J) + return -EINVAL; + + pdev->test_mode = tmode; + + /* + * Test mode must be set before Status Stage but controller + * will start testing sequence after Status Stage. + */ + cdnsp_enter_test_mode(pdev); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int cdnsp_ep0_handle_feature_intf(struct cdnsp_device *pdev, + struct usb_ctrlrequest *ctrl, + int set) +{ + u16 wValue, wIndex; + int ret; + + wValue = le16_to_cpu(ctrl->wValue); + wIndex = le16_to_cpu(ctrl->wIndex); + + switch (wValue) { + case USB_INTRF_FUNC_SUSPEND: + ret = cdnsp_ep0_delegate_req(pdev, ctrl); + if (ret) + return ret; + + /* + * Remote wakeup is enabled when any function within a device + * is enabled for function remote wakeup. + */ + if (wIndex & USB_INTRF_FUNC_SUSPEND_RW) + pdev->may_wakeup++; + else + if (pdev->may_wakeup > 0) + pdev->may_wakeup--; + + return 0; + default: + return -EINVAL; + } + + return 0; +} + +static int cdnsp_ep0_handle_feature_endpoint(struct cdnsp_device *pdev, + struct usb_ctrlrequest *ctrl, + int set) +{ + struct cdnsp_ep *pep; + u32 wValue; + + wValue = le16_to_cpu(ctrl->wValue); + pep = &pdev->eps[cdnsp_w_index_to_ep_index(ctrl->wIndex)]; + + switch (wValue) { + case USB_ENDPOINT_HALT: + if (!set && (pep->ep_state & EP_WEDGE)) { + /* Resets Sequence Number */ + cdnsp_halt_endpoint(pdev, pep, 0); + cdnsp_halt_endpoint(pdev, pep, 1); + break; + } + + return cdnsp_halt_endpoint(pdev, pep, set); + default: + dev_warn(pdev->dev, "WARN Incorrect wValue %04x\n", wValue); + return -EINVAL; + } + + return 0; +} + +static int cdnsp_ep0_handle_feature(struct cdnsp_device *pdev, + struct usb_ctrlrequest *ctrl, + int set) +{ + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + return cdnsp_ep0_handle_feature_device(pdev, ctrl, set); + case USB_RECIP_INTERFACE: + return cdnsp_ep0_handle_feature_intf(pdev, ctrl, set); + case USB_RECIP_ENDPOINT: + return cdnsp_ep0_handle_feature_endpoint(pdev, ctrl, set); + default: + return -EINVAL; + } +} + +static int cdnsp_ep0_set_sel(struct cdnsp_device *pdev, + struct usb_ctrlrequest *ctrl) +{ + enum usb_device_state state = pdev->gadget.state; + u16 wLength; + + if (state == USB_STATE_DEFAULT) + return -EINVAL; + + wLength = le16_to_cpu(ctrl->wLength); + + if (wLength != 6) { + dev_err(pdev->dev, "Set SEL should be 6 bytes, got %d\n", + wLength); + return -EINVAL; + } + + /* + * To handle Set SEL we need to receive 6 bytes from Host. So let's + * queue a usb_request for 6 bytes. + */ + pdev->ep0_preq.request.length = 6; + pdev->ep0_preq.request.buf = pdev->setup_buf; + + return cdnsp_ep_enqueue(pdev->ep0_preq.pep, &pdev->ep0_preq); +} + +static int cdnsp_ep0_set_isoch_delay(struct cdnsp_device *pdev, + struct usb_ctrlrequest *ctrl) +{ + if (le16_to_cpu(ctrl->wIndex) || le16_to_cpu(ctrl->wLength)) + return -EINVAL; + + pdev->gadget.isoch_delay = le16_to_cpu(ctrl->wValue); + + return 0; +} + +static int cdnsp_ep0_std_request(struct cdnsp_device *pdev, + struct usb_ctrlrequest *ctrl) +{ + int ret; + + switch (ctrl->bRequest) { + case USB_REQ_GET_STATUS: + ret = cdnsp_ep0_handle_status(pdev, ctrl); + break; + case USB_REQ_CLEAR_FEATURE: + ret = cdnsp_ep0_handle_feature(pdev, ctrl, 0); + break; + case USB_REQ_SET_FEATURE: + ret = cdnsp_ep0_handle_feature(pdev, ctrl, 1); + break; + case USB_REQ_SET_ADDRESS: + ret = cdnsp_ep0_set_address(pdev, ctrl); + break; + case USB_REQ_SET_CONFIGURATION: + ret = cdnsp_ep0_set_config(pdev, ctrl); + break; + case USB_REQ_SET_SEL: + ret = cdnsp_ep0_set_sel(pdev, ctrl); + break; + case USB_REQ_SET_ISOCH_DELAY: + ret = cdnsp_ep0_set_isoch_delay(pdev, ctrl); + break; + case USB_REQ_SET_INTERFACE: + /* + * Add request into pending list to block sending status stage + * by libcomposite. + */ + list_add_tail(&pdev->ep0_preq.list, + &pdev->ep0_preq.pep->pending_list); + + ret = cdnsp_ep0_delegate_req(pdev, ctrl); + if (ret == -EBUSY) + ret = 0; + + list_del(&pdev->ep0_preq.list); + break; + default: + ret = cdnsp_ep0_delegate_req(pdev, ctrl); + break; + } + + return ret; +} + +void cdnsp_setup_analyze(struct cdnsp_device *pdev) +{ + struct usb_ctrlrequest *ctrl = &pdev->setup; + int ret = 0; + __le16 len; + + if (!pdev->gadget_driver) + goto out; + + if (pdev->gadget.state == USB_STATE_NOTATTACHED) { + dev_err(pdev->dev, "ERR: Setup detected in unattached state\n"); + ret = -EINVAL; + goto out; + } + + /* Restore the ep0 to Stopped/Running state. */ + if (pdev->eps[0].ep_state & EP_HALTED) + cdnsp_halt_endpoint(pdev, &pdev->eps[0], 0); + + /* + * Finishing previous SETUP transfer by removing request from + * list and informing upper layer + */ + if (!list_empty(&pdev->eps[0].pending_list)) { + struct cdnsp_request *req; + + req = next_request(&pdev->eps[0].pending_list); + cdnsp_ep_dequeue(&pdev->eps[0], req); + } + + len = le16_to_cpu(ctrl->wLength); + if (!len) { + pdev->three_stage_setup = false; + pdev->ep0_expect_in = false; + } else { + pdev->three_stage_setup = true; + pdev->ep0_expect_in = !!(ctrl->bRequestType & USB_DIR_IN); + } + + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) + ret = cdnsp_ep0_std_request(pdev, ctrl); + else + ret = cdnsp_ep0_delegate_req(pdev, ctrl); + + if (!len) + pdev->ep0_stage = CDNSP_STATUS_STAGE; + + if (ret == USB_GADGET_DELAYED_STATUS) + return; +out: + if (ret < 0) + cdnsp_ep0_stall(pdev); + else if (pdev->ep0_stage == CDNSP_STATUS_STAGE) + cdnsp_status_stage(pdev); +} diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c new file mode 100644 index 000000000000..18bb4c0d1e08 --- /dev/null +++ b/drivers/usb/cdns3/cdnsp-gadget.c @@ -0,0 +1,1954 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence CDNSP DRD Driver. + * + * Copyright (C) 2020 Cadence. + * + * Author: Pawel Laszczak + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "gadget-export.h" +#include "drd.h" +#include "cdnsp-gadget.h" + +unsigned int cdnsp_port_speed(unsigned int port_status) +{ + /*Detect gadget speed based on PORTSC register*/ + if (DEV_SUPERSPEEDPLUS(port_status)) + return USB_SPEED_SUPER_PLUS; + else if (DEV_SUPERSPEED(port_status)) + return USB_SPEED_SUPER; + else if (DEV_HIGHSPEED(port_status)) + return USB_SPEED_HIGH; + else if (DEV_FULLSPEED(port_status)) + return USB_SPEED_FULL; + + /* If device is detached then speed will be USB_SPEED_UNKNOWN.*/ + return USB_SPEED_UNKNOWN; +} + +/* + * Given a port state, this function returns a value that would result in the + * port being in the same state, if the value was written to the port status + * control register. + * Save Read Only (RO) bits and save read/write bits where + * writing a 0 clears the bit and writing a 1 sets the bit (RWS). + * For all other types (RW1S, RW1CS, RW, and RZ), writing a '0' has no effect. + */ +u32 cdnsp_port_state_to_neutral(u32 state) +{ + /* Save read-only status and port state. */ + return (state & CDNSP_PORT_RO) | (state & CDNSP_PORT_RWS); +} + +/** + * Find the offset of the extended capabilities with capability ID id. + * @base: PCI MMIO registers base address. + * @start: Address at which to start looking, (0 or HCC_PARAMS to start at + * beginning of list) + * @id: Extended capability ID to search for. + * + * Returns the offset of the next matching extended capability structure. + * Some capabilities can occur several times, + * e.g., the EXT_CAPS_PROTOCOL, and this provides a way to find them all. + */ +int cdnsp_find_next_ext_cap(void __iomem *base, u32 start, int id) +{ + u32 offset = start; + u32 next; + u32 val; + + if (!start || start == HCC_PARAMS_OFFSET) { + val = readl(base + HCC_PARAMS_OFFSET); + if (val == ~0) + return 0; + + offset = HCC_EXT_CAPS(val) << 2; + if (!offset) + return 0; + }; + + do { + val = readl(base + offset); + if (val == ~0) + return 0; + + if (EXT_CAPS_ID(val) == id && offset != start) + return offset; + + next = EXT_CAPS_NEXT(val); + offset += next << 2; + } while (next); + + return 0; +} + +void cdnsp_set_link_state(struct cdnsp_device *pdev, + __le32 __iomem *port_regs, + u32 link_state) +{ + u32 temp; + + temp = readl(port_regs); + temp = cdnsp_port_state_to_neutral(temp); + temp |= PORT_WKCONN_E | PORT_WKDISC_E; + writel(temp, port_regs); + + temp &= ~PORT_PLS_MASK; + temp |= PORT_LINK_STROBE | link_state; + + writel(temp, port_regs); +} + +static void cdnsp_disable_port(struct cdnsp_device *pdev, + __le32 __iomem *port_regs) +{ + u32 temp = cdnsp_port_state_to_neutral(readl(port_regs)); + + writel(temp | PORT_PED, port_regs); +} + +static void cdnsp_clear_port_change_bit(struct cdnsp_device *pdev, + __le32 __iomem *port_regs) +{ + u32 portsc = readl(port_regs); + + writel(cdnsp_port_state_to_neutral(portsc) | + (portsc & PORT_CHANGE_BITS), port_regs); +} + +static void cdnsp_set_chicken_bits_2(struct cdnsp_device *pdev, u32 bit) +{ + __le32 __iomem *reg; + void __iomem *base; + u32 offset = 0; + + base = &pdev->cap_regs->hc_capbase; + offset = cdnsp_find_next_ext_cap(base, offset, D_XEC_PRE_REGS_CAP); + reg = base + offset + REG_CHICKEN_BITS_2_OFFSET; + + bit = readl(reg) | bit; + writel(bit, reg); +} + +static void cdnsp_clear_chicken_bits_2(struct cdnsp_device *pdev, u32 bit) +{ + __le32 __iomem *reg; + void __iomem *base; + u32 offset = 0; + + base = &pdev->cap_regs->hc_capbase; + offset = cdnsp_find_next_ext_cap(base, offset, D_XEC_PRE_REGS_CAP); + reg = base + offset + REG_CHICKEN_BITS_2_OFFSET; + + bit = readl(reg) & ~bit; + writel(bit, reg); +} + +/* + * Disable interrupts and begin the controller halting process. + */ +static void cdnsp_quiesce(struct cdnsp_device *pdev) +{ + u32 halted; + u32 mask; + u32 cmd; + + mask = ~(u32)(CDNSP_IRQS); + + halted = readl(&pdev->op_regs->status) & STS_HALT; + if (!halted) + mask &= ~(CMD_R_S | CMD_DEVEN); + + cmd = readl(&pdev->op_regs->command); + cmd &= mask; + writel(cmd, &pdev->op_regs->command); +} + +/* + * Force controller into halt state. + * + * Disable any IRQs and clear the run/stop bit. + * Controller will complete any current and actively pipelined transactions, and + * should halt within 16 ms of the run/stop bit being cleared. + * Read controller Halted bit in the status register to see when the + * controller is finished. + */ +int cdnsp_halt(struct cdnsp_device *pdev) +{ + int ret; + u32 val; + + cdnsp_quiesce(pdev); + + ret = readl_poll_timeout_atomic(&pdev->op_regs->status, val, + val & STS_HALT, 1, + CDNSP_MAX_HALT_USEC); + if (ret) { + dev_err(pdev->dev, "ERROR: Device halt failed\n"); + return ret; + } + + pdev->cdnsp_state |= CDNSP_STATE_HALTED; + + return 0; +} + +/* + * device controller died, register read returns 0xffffffff, or command never + * ends. + */ +void cdnsp_died(struct cdnsp_device *pdev) +{ + dev_err(pdev->dev, "ERROR: CDNSP controller not responding\n"); + pdev->cdnsp_state |= CDNSP_STATE_DYING; + cdnsp_halt(pdev); +} + +/* + * Set the run bit and wait for the device to be running. + */ +static int cdnsp_start(struct cdnsp_device *pdev) +{ + u32 temp; + int ret; + + temp = readl(&pdev->op_regs->command); + temp |= (CMD_R_S | CMD_DEVEN); + writel(temp, &pdev->op_regs->command); + + pdev->cdnsp_state = 0; + + /* + * Wait for the STS_HALT Status bit to be 0 to indicate the device is + * running. + */ + ret = readl_poll_timeout_atomic(&pdev->op_regs->status, temp, + !(temp & STS_HALT), 1, + CDNSP_MAX_HALT_USEC); + if (ret) { + pdev->cdnsp_state = CDNSP_STATE_DYING; + dev_err(pdev->dev, "ERROR: Controller run failed\n"); + } + + return ret; +} + +/* + * Reset a halted controller. + * + * This resets pipelines, timers, counters, state machines, etc. + * Transactions will be terminated immediately, and operational registers + * will be set to their defaults. + */ +int cdnsp_reset(struct cdnsp_device *pdev) +{ + u32 command; + u32 temp; + int ret; + + temp = readl(&pdev->op_regs->status); + + if (temp == ~(u32)0) { + dev_err(pdev->dev, "Device not accessible, reset failed.\n"); + return -ENODEV; + } + + if ((temp & STS_HALT) == 0) { + dev_err(pdev->dev, "Controller not halted, aborting reset.\n"); + return -EINVAL; + } + + command = readl(&pdev->op_regs->command); + command |= CMD_RESET; + writel(command, &pdev->op_regs->command); + + ret = readl_poll_timeout_atomic(&pdev->op_regs->command, temp, + !(temp & CMD_RESET), 1, + 10 * 1000); + if (ret) { + dev_err(pdev->dev, "ERROR: Controller reset failed\n"); + return ret; + } + + /* + * CDNSP cannot write any doorbells or operational registers other + * than status until the "Controller Not Ready" flag is cleared. + */ + ret = readl_poll_timeout_atomic(&pdev->op_regs->status, temp, + !(temp & STS_CNR), 1, + 10 * 1000); + + if (ret) { + dev_err(pdev->dev, "ERROR: Controller not ready to work\n"); + return ret; + } + + dev_dbg(pdev->dev, "Controller ready to work"); + + return ret; +} + +/* + * cdnsp_get_endpoint_index - Find the index for an endpoint given its + * descriptor.Use the return value to right shift 1 for the bitmask. + * + * Index = (epnum * 2) + direction - 1, + * where direction = 0 for OUT, 1 for IN. + * For control endpoints, the IN index is used (OUT index is unused), so + * index = (epnum * 2) + direction - 1 = (epnum * 2) + 1 - 1 = (epnum * 2) + */ +static unsigned int + cdnsp_get_endpoint_index(const struct usb_endpoint_descriptor *desc) +{ + unsigned int index = (unsigned int)usb_endpoint_num(desc); + + if (usb_endpoint_xfer_control(desc)) + return index * 2; + + return (index * 2) + (usb_endpoint_dir_in(desc) ? 1 : 0) - 1; +} + +/* + * Find the flag for this endpoint (for use in the control context). Use the + * endpoint index to create a bitmask. The slot context is bit 0, endpoint 0 is + * bit 1, etc. + */ +static unsigned int + cdnsp_get_endpoint_flag(const struct usb_endpoint_descriptor *desc) +{ + return 1 << (cdnsp_get_endpoint_index(desc) + 1); +} + +int cdnsp_ep_enqueue(struct cdnsp_ep *pep, struct cdnsp_request *preq) +{ + struct cdnsp_device *pdev = pep->pdev; + struct usb_request *request; + int ret; + + if (preq->epnum == 0 && !list_empty(&pep->pending_list)) + return -EBUSY; + + request = &preq->request; + request->actual = 0; + request->status = -EINPROGRESS; + preq->direction = pep->direction; + preq->epnum = pep->number; + preq->td.drbl = 0; + + ret = usb_gadget_map_request_by_dev(pdev->dev, request, pep->direction); + if (ret) + return ret; + + list_add_tail(&preq->list, &pep->pending_list); + + switch (usb_endpoint_type(pep->endpoint.desc)) { + case USB_ENDPOINT_XFER_CONTROL: + ret = cdnsp_queue_ctrl_tx(pdev, preq); + break; + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_INT: + ret = cdnsp_queue_bulk_tx(pdev, preq); + break; + case USB_ENDPOINT_XFER_ISOC: + ret = cdnsp_queue_isoc_tx_prepare(pdev, preq); + } + + if (ret) + goto unmap; + + return 0; + +unmap: + usb_gadget_unmap_request_by_dev(pdev->dev, &preq->request, + pep->direction); + list_del(&preq->list); + + return ret; +} + +/* + * Remove the request's TD from the endpoint ring. This may cause the + * controller to stop USB transfers, potentially stopping in the middle of a + * TRB buffer. The controller should pick up where it left off in the TD, + * unless a Set Transfer Ring Dequeue Pointer is issued. + * + * The TRBs that make up the buffers for the canceled request will be "removed" + * from the ring. Since the ring is a contiguous structure, they can't be + * physically removed. Instead, there are two options: + * + * 1) If the controller is in the middle of processing the request to be + * canceled, we simply move the ring's dequeue pointer past those TRBs + * using the Set Transfer Ring Dequeue Pointer command. This will be + * the common case, when drivers timeout on the last submitted request + * and attempt to cancel. + * + * 2) If the controller is in the middle of a different TD, we turn the TRBs + * into a series of 1-TRB transfer no-op TDs. No-ops shouldn't be chained. + * The controller will need to invalidate the any TRBs it has cached after + * the stop endpoint command. + * + * 3) The TD may have completed by the time the Stop Endpoint Command + * completes, so software needs to handle that case too. + * + */ +int cdnsp_ep_dequeue(struct cdnsp_ep *pep, struct cdnsp_request *preq) +{ + struct cdnsp_device *pdev = pep->pdev; + int ret; + + if (GET_EP_CTX_STATE(pep->out_ctx) == EP_STATE_RUNNING) { + ret = cdnsp_cmd_stop_ep(pdev, pep); + if (ret) + return ret; + } + + return cdnsp_remove_request(pdev, preq, pep); +} + +static void cdnsp_zero_in_ctx(struct cdnsp_device *pdev) +{ + struct cdnsp_input_control_ctx *ctrl_ctx; + struct cdnsp_slot_ctx *slot_ctx; + struct cdnsp_ep_ctx *ep_ctx; + int i; + + ctrl_ctx = cdnsp_get_input_control_ctx(&pdev->in_ctx); + + /* + * When a device's add flag and drop flag are zero, any subsequent + * configure endpoint command will leave that endpoint's state + * untouched. Make sure we don't leave any old state in the input + * endpoint contexts. + */ + ctrl_ctx->drop_flags = 0; + ctrl_ctx->add_flags = 0; + slot_ctx = cdnsp_get_slot_ctx(&pdev->in_ctx); + slot_ctx->dev_info &= cpu_to_le32(~LAST_CTX_MASK); + + /* Endpoint 0 is always valid */ + slot_ctx->dev_info |= cpu_to_le32(LAST_CTX(1)); + for (i = 1; i < CDNSP_ENDPOINTS_NUM; ++i) { + ep_ctx = cdnsp_get_ep_ctx(&pdev->in_ctx, i); + ep_ctx->ep_info = 0; + ep_ctx->ep_info2 = 0; + ep_ctx->deq = 0; + ep_ctx->tx_info = 0; + } +} + +/* Issue a configure endpoint command and wait for it to finish. */ +static int cdnsp_configure_endpoint(struct cdnsp_device *pdev) +{ + int ret; + + cdnsp_queue_configure_endpoint(pdev, pdev->cmd.in_ctx->dma); + cdnsp_ring_cmd_db(pdev); + ret = cdnsp_wait_for_cmd_compl(pdev); + if (ret) { + dev_err(pdev->dev, + "ERR: unexpected command completion code 0x%x.\n", ret); + return -EINVAL; + } + + return ret; +} + +static void cdnsp_invalidate_ep_events(struct cdnsp_device *pdev, + struct cdnsp_ep *pep) +{ + struct cdnsp_segment *segment; + union cdnsp_trb *event; + u32 cycle_state; + __le32 data; + + event = pdev->event_ring->dequeue; + segment = pdev->event_ring->deq_seg; + cycle_state = pdev->event_ring->cycle_state; + + while (1) { + data = le32_to_cpu(event->trans_event.flags); + + /* Check the owner of the TRB. */ + if ((data & TRB_CYCLE) != cycle_state) + break; + + if (TRB_FIELD_TO_TYPE(data) == TRB_TRANSFER && + TRB_TO_EP_ID(data) == (pep->idx + 1)) { + data |= TRB_EVENT_INVALIDATE; + event->trans_event.flags = cpu_to_le32(data); + } + + if (cdnsp_last_trb_on_seg(segment, event)) { + cycle_state ^= 1; + segment = pdev->event_ring->deq_seg->next; + event = segment->trbs; + } else { + event++; + } + } +} + +int cdnsp_wait_for_cmd_compl(struct cdnsp_device *pdev) +{ + struct cdnsp_segment *event_deq_seg; + union cdnsp_trb *cmd_trb; + dma_addr_t cmd_deq_dma; + union cdnsp_trb *event; + u32 cycle_state; + __le32 flags; + int ret, val; + u64 cmd_dma; + + cmd_trb = pdev->cmd.command_trb; + pdev->cmd.status = 0; + + ret = readl_poll_timeout_atomic(&pdev->op_regs->cmd_ring, val, + !CMD_RING_BUSY(val), 1, + CDNSP_CMD_TIMEOUT); + if (ret) { + dev_err(pdev->dev, "ERR: Timeout while waiting for command\n"); + pdev->cdnsp_state = CDNSP_STATE_DYING; + return -ETIMEDOUT; + } + + event = pdev->event_ring->dequeue; + event_deq_seg = pdev->event_ring->deq_seg; + cycle_state = pdev->event_ring->cycle_state; + + cmd_deq_dma = cdnsp_trb_virt_to_dma(pdev->cmd_ring->deq_seg, cmd_trb); + if (!cmd_deq_dma) + return -EINVAL; + + while (1) { + flags = le32_to_cpu(event->event_cmd.flags); + + /* Check the owner of the TRB. */ + if ((flags & TRB_CYCLE) != cycle_state) + return -EINVAL; + + cmd_dma = le64_to_cpu(event->event_cmd.cmd_trb); + + /* + * Check whether the completion event is for last queued + * command. + */ + if (TRB_FIELD_TO_TYPE(flags) != TRB_COMPLETION || + cmd_dma != (u64)cmd_deq_dma) { + if (!cdnsp_last_trb_on_seg(event_deq_seg, event)) { + event++; + continue; + } + + if (cdnsp_last_trb_on_ring(pdev->event_ring, + event_deq_seg, event)) + cycle_state ^= 1; + + event_deq_seg = event_deq_seg->next; + event = event_deq_seg->trbs; + continue; + } + + pdev->cmd.status = GET_COMP_CODE(le32_to_cpu(event->event_cmd.status)); + if (pdev->cmd.status == COMP_SUCCESS) + return 0; + + return -pdev->cmd.status; + } +} + +int cdnsp_halt_endpoint(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + int value) +{ + int ret; + + if (value) { + ret = cdnsp_cmd_stop_ep(pdev, pep); + if (ret) + return ret; + + if (GET_EP_CTX_STATE(pep->out_ctx) == EP_STATE_STOPPED) { + cdnsp_queue_halt_endpoint(pdev, pep->idx); + cdnsp_ring_cmd_db(pdev); + ret = cdnsp_wait_for_cmd_compl(pdev); + } + + pep->ep_state |= EP_HALTED; + } else { + /* + * In device mode driver can call reset endpoint command + * from any endpoint state. + */ + cdnsp_queue_reset_ep(pdev, pep->idx); + cdnsp_ring_cmd_db(pdev); + ret = cdnsp_wait_for_cmd_compl(pdev); + if (ret) + return ret; + + pep->ep_state &= ~EP_HALTED; + + if (pep->idx != 0 && !(pep->ep_state & EP_WEDGE)) + cdnsp_ring_doorbell_for_active_rings(pdev, pep); + + pep->ep_state &= ~EP_WEDGE; + } + + return 0; +} + +static int cdnsp_update_eps_configuration(struct cdnsp_device *pdev, + struct cdnsp_ep *pep) +{ + struct cdnsp_input_control_ctx *ctrl_ctx; + struct cdnsp_slot_ctx *slot_ctx; + int ret = 0; + u32 ep_sts; + int i; + + ctrl_ctx = cdnsp_get_input_control_ctx(&pdev->in_ctx); + + /* Don't issue the command if there's no endpoints to update. */ + if (ctrl_ctx->add_flags == 0 && ctrl_ctx->drop_flags == 0) + return 0; + + ctrl_ctx->add_flags |= cpu_to_le32(SLOT_FLAG); + ctrl_ctx->add_flags &= cpu_to_le32(~EP0_FLAG); + ctrl_ctx->drop_flags &= cpu_to_le32(~(SLOT_FLAG | EP0_FLAG)); + + /* Fix up Context Entries field. Minimum value is EP0 == BIT(1). */ + slot_ctx = cdnsp_get_slot_ctx(&pdev->in_ctx); + for (i = CDNSP_ENDPOINTS_NUM; i >= 1; i--) { + __le32 le32 = cpu_to_le32(BIT(i)); + + if ((pdev->eps[i - 1].ring && !(ctrl_ctx->drop_flags & le32)) || + (ctrl_ctx->add_flags & le32) || i == 1) { + slot_ctx->dev_info &= cpu_to_le32(~LAST_CTX_MASK); + slot_ctx->dev_info |= cpu_to_le32(LAST_CTX(i)); + break; + } + } + + ep_sts = GET_EP_CTX_STATE(pep->out_ctx); + + if ((ctrl_ctx->add_flags != cpu_to_le32(SLOT_FLAG) && + ep_sts == EP_STATE_DISABLED) || + (ep_sts != EP_STATE_DISABLED && ctrl_ctx->drop_flags)) + ret = cdnsp_configure_endpoint(pdev); + + cdnsp_zero_in_ctx(pdev); + + return ret; +} + +/* + * This submits a Reset Device Command, which will set the device state to 0, + * set the device address to 0, and disable all the endpoints except the default + * control endpoint. The USB core should come back and call + * cdnsp_setup_device(), and then re-set up the configuration. + */ +int cdnsp_reset_device(struct cdnsp_device *pdev) +{ + struct cdnsp_slot_ctx *slot_ctx; + int slot_state; + int ret, i; + + slot_ctx = cdnsp_get_slot_ctx(&pdev->in_ctx); + slot_ctx->dev_info = 0; + pdev->device_address = 0; + + /* If device is not setup, there is no point in resetting it. */ + slot_ctx = cdnsp_get_slot_ctx(&pdev->out_ctx); + slot_state = GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state)); + + if (slot_state <= SLOT_STATE_DEFAULT && + pdev->eps[0].ep_state & EP_HALTED) { + cdnsp_halt_endpoint(pdev, &pdev->eps[0], 0); + } + + /* + * During Reset Device command controller shall transition the + * endpoint ep0 to the Running State. + */ + pdev->eps[0].ep_state &= ~(EP_STOPPED | EP_HALTED); + pdev->eps[0].ep_state |= EP_ENABLED; + + if (slot_state <= SLOT_STATE_DEFAULT) + return 0; + + cdnsp_queue_reset_device(pdev); + cdnsp_ring_cmd_db(pdev); + ret = cdnsp_wait_for_cmd_compl(pdev); + + /* + * After Reset Device command all not default endpoints + * are in Disabled state. + */ + for (i = 1; i < CDNSP_ENDPOINTS_NUM; ++i) + pdev->eps[i].ep_state |= EP_STOPPED; + + if (ret) + dev_err(pdev->dev, "Reset device failed with error code %d", + ret); + + return ret; +} + +/* + * Sets the MaxPStreams field and the Linear Stream Array field. + * Sets the dequeue pointer to the stream context array. + */ +static void cdnsp_setup_streams_ep_input_ctx(struct cdnsp_device *pdev, + struct cdnsp_ep_ctx *ep_ctx, + struct cdnsp_stream_info *stream_info) +{ + u32 max_primary_streams; + + /* MaxPStreams is the number of stream context array entries, not the + * number we're actually using. Must be in 2^(MaxPstreams + 1) format. + * fls(0) = 0, fls(0x1) = 1, fls(0x10) = 2, fls(0x100) = 3, etc. + */ + max_primary_streams = fls(stream_info->num_stream_ctxs) - 2; + ep_ctx->ep_info &= cpu_to_le32(~EP_MAXPSTREAMS_MASK); + ep_ctx->ep_info |= cpu_to_le32(EP_MAXPSTREAMS(max_primary_streams) + | EP_HAS_LSA); + ep_ctx->deq = cpu_to_le64(stream_info->ctx_array_dma); +} + +/* + * The drivers use this function to prepare a bulk endpoints to use streams. + * + * Don't allow the call to succeed if endpoint only supports one stream + * (which means it doesn't support streams at all). + */ +int cdnsp_alloc_streams(struct cdnsp_device *pdev, struct cdnsp_ep *pep) +{ + unsigned int num_streams = usb_ss_max_streams(pep->endpoint.comp_desc); + unsigned int num_stream_ctxs; + int ret; + + if (num_streams == 0) + return 0; + + if (num_streams > STREAM_NUM_STREAMS) + return -EINVAL; + + /* + * Add two to the number of streams requested to account for + * stream 0 that is reserved for controller usage and one additional + * for TASK SET FULL response. + */ + num_streams += 2; + + /* The stream context array size must be a power of two */ + num_stream_ctxs = roundup_pow_of_two(num_streams); + + ret = cdnsp_alloc_stream_info(pdev, pep, num_stream_ctxs, num_streams); + if (ret) + return ret; + + cdnsp_setup_streams_ep_input_ctx(pdev, pep->in_ctx, &pep->stream_info); + + pep->ep_state |= EP_HAS_STREAMS; + pep->stream_info.td_count = 0; + pep->stream_info.first_prime_det = 0; + + /* Subtract 1 for stream 0, which drivers can't use. */ + return num_streams - 1; +} + +int cdnsp_disable_slot(struct cdnsp_device *pdev) +{ + int ret; + + cdnsp_queue_slot_control(pdev, TRB_DISABLE_SLOT); + cdnsp_ring_cmd_db(pdev); + ret = cdnsp_wait_for_cmd_compl(pdev); + + pdev->slot_id = 0; + pdev->active_port = NULL; + + memset(pdev->in_ctx.bytes, 0, CDNSP_CTX_SIZE); + memset(pdev->out_ctx.bytes, 0, CDNSP_CTX_SIZE); + + return ret; +} + +int cdnsp_enable_slot(struct cdnsp_device *pdev) +{ + struct cdnsp_slot_ctx *slot_ctx; + int slot_state; + int ret; + + /* If device is not setup, there is no point in resetting it */ + slot_ctx = cdnsp_get_slot_ctx(&pdev->out_ctx); + slot_state = GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state)); + + if (slot_state != SLOT_STATE_DISABLED) + return 0; + + cdnsp_queue_slot_control(pdev, TRB_ENABLE_SLOT); + cdnsp_ring_cmd_db(pdev); + ret = cdnsp_wait_for_cmd_compl(pdev); + if (ret) + return ret; + + pdev->slot_id = 1; + + return 0; +} + +/* + * Issue an Address Device command with BSR=0 if setup is SETUP_CONTEXT_ONLY + * or with BSR = 1 if set_address is SETUP_CONTEXT_ADDRESS. + */ +int cdnsp_setup_device(struct cdnsp_device *pdev, enum cdnsp_setup_dev setup) +{ + struct cdnsp_input_control_ctx *ctrl_ctx; + struct cdnsp_slot_ctx *slot_ctx; + int dev_state = 0; + int ret; + + if (!pdev->slot_id) + return -EINVAL; + + if (!pdev->active_port->port_num) + return -EINVAL; + + slot_ctx = cdnsp_get_slot_ctx(&pdev->out_ctx); + dev_state = GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state)); + + if (setup == SETUP_CONTEXT_ONLY && dev_state == SLOT_STATE_DEFAULT) + return 0; + + slot_ctx = cdnsp_get_slot_ctx(&pdev->in_ctx); + ctrl_ctx = cdnsp_get_input_control_ctx(&pdev->in_ctx); + + if (!slot_ctx->dev_info || dev_state == SLOT_STATE_DEFAULT) { + ret = cdnsp_setup_addressable_priv_dev(pdev); + if (ret) + return ret; + } + + cdnsp_copy_ep0_dequeue_into_input_ctx(pdev); + + ctrl_ctx->add_flags = cpu_to_le32(SLOT_FLAG | EP0_FLAG); + ctrl_ctx->drop_flags = 0; + + cdnsp_queue_address_device(pdev, pdev->in_ctx.dma, setup); + cdnsp_ring_cmd_db(pdev); + ret = cdnsp_wait_for_cmd_compl(pdev); + + /* Zero the input context control for later use. */ + ctrl_ctx->add_flags = 0; + ctrl_ctx->drop_flags = 0; + + return ret; +} + +void cdnsp_set_usb2_hardware_lpm(struct cdnsp_device *pdev, + struct usb_request *req, + int enable) +{ + if (pdev->active_port != &pdev->usb2_port || !pdev->gadget.lpm_capable) + return; + + if (enable) + writel(PORT_BESL(CDNSP_DEFAULT_BESL) | PORT_L1S_NYET | PORT_HLE, + &pdev->active_port->regs->portpmsc); + else + writel(PORT_L1S_NYET, &pdev->active_port->regs->portpmsc); +} + +static int cdnsp_get_frame(struct cdnsp_device *pdev) +{ + return readl(&pdev->run_regs->microframe_index) >> 3; +} + +static int cdnsp_gadget_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct cdnsp_input_control_ctx *ctrl_ctx; + struct cdnsp_device *pdev; + struct cdnsp_ep *pep; + unsigned long flags; + u32 added_ctxs; + int ret; + + if (!ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT || + !desc->wMaxPacketSize) + return -EINVAL; + + pep = to_cdnsp_ep(ep); + pdev = pep->pdev; + + if (dev_WARN_ONCE(pdev->dev, pep->ep_state & EP_ENABLED, + "%s is already enabled\n", pep->name)) + return 0; + + spin_lock_irqsave(&pdev->lock, flags); + + added_ctxs = cdnsp_get_endpoint_flag(desc); + if (added_ctxs == SLOT_FLAG || added_ctxs == EP0_FLAG) { + dev_err(pdev->dev, "ERROR: Bad endpoint number\n"); + ret = -EINVAL; + goto unlock; + } + + pep->interval = desc->bInterval ? BIT(desc->bInterval - 1) : 0; + + if (pdev->gadget.speed == USB_SPEED_FULL) { + if (usb_endpoint_type(desc) == USB_ENDPOINT_XFER_INT) + pep->interval = desc->bInterval << 3; + if (usb_endpoint_type(desc) == USB_ENDPOINT_XFER_ISOC) + pep->interval = BIT(desc->bInterval - 1) << 3; + } + + if (usb_endpoint_type(desc) == USB_ENDPOINT_XFER_ISOC) { + if (pep->interval > BIT(12)) { + dev_err(pdev->dev, "bInterval %d not supported\n", + desc->bInterval); + ret = -EINVAL; + goto unlock; + } + cdnsp_set_chicken_bits_2(pdev, CHICKEN_XDMA_2_TP_CACHE_DIS); + } + + ret = cdnsp_endpoint_init(pdev, pep, GFP_ATOMIC); + if (ret) + goto unlock; + + ctrl_ctx = cdnsp_get_input_control_ctx(&pdev->in_ctx); + ctrl_ctx->add_flags = cpu_to_le32(added_ctxs); + ctrl_ctx->drop_flags = 0; + + ret = cdnsp_update_eps_configuration(pdev, pep); + if (ret) { + cdnsp_free_endpoint_rings(pdev, pep); + goto unlock; + } + + pep->ep_state |= EP_ENABLED; + pep->ep_state &= ~EP_STOPPED; + +unlock: + spin_unlock_irqrestore(&pdev->lock, flags); + + return ret; +} + +static int cdnsp_gadget_ep_disable(struct usb_ep *ep) +{ + struct cdnsp_input_control_ctx *ctrl_ctx; + struct cdnsp_request *preq; + struct cdnsp_device *pdev; + struct cdnsp_ep *pep; + unsigned long flags; + u32 drop_flag; + int ret = 0; + + if (!ep) + return -EINVAL; + + pep = to_cdnsp_ep(ep); + pdev = pep->pdev; + + spin_lock_irqsave(&pdev->lock, flags); + + if (!(pep->ep_state & EP_ENABLED)) { + dev_err(pdev->dev, "%s is already disabled\n", pep->name); + ret = -EINVAL; + goto finish; + } + + cdnsp_cmd_stop_ep(pdev, pep); + pep->ep_state |= EP_DIS_IN_RROGRESS; + cdnsp_cmd_flush_ep(pdev, pep); + + /* Remove all queued USB requests. */ + while (!list_empty(&pep->pending_list)) { + preq = next_request(&pep->pending_list); + cdnsp_ep_dequeue(pep, preq); + } + + cdnsp_invalidate_ep_events(pdev, pep); + + pep->ep_state &= ~EP_DIS_IN_RROGRESS; + drop_flag = cdnsp_get_endpoint_flag(pep->endpoint.desc); + ctrl_ctx = cdnsp_get_input_control_ctx(&pdev->in_ctx); + ctrl_ctx->drop_flags = cpu_to_le32(drop_flag); + ctrl_ctx->add_flags = 0; + + cdnsp_endpoint_zero(pdev, pep); + + ret = cdnsp_update_eps_configuration(pdev, pep); + cdnsp_free_endpoint_rings(pdev, pep); + + pep->ep_state &= ~EP_ENABLED; + pep->ep_state |= EP_STOPPED; + +finish: + spin_unlock_irqrestore(&pdev->lock, flags); + + return ret; +} + +static struct usb_request *cdnsp_gadget_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct cdnsp_ep *pep = to_cdnsp_ep(ep); + struct cdnsp_request *preq; + + preq = kzalloc(sizeof(*preq), gfp_flags); + if (!preq) + return NULL; + + preq->epnum = pep->number; + preq->pep = pep; + + return &preq->request; +} + +static void cdnsp_gadget_ep_free_request(struct usb_ep *ep, + struct usb_request *request) +{ + struct cdnsp_request *preq = to_cdnsp_request(request); + + kfree(preq); +} + +static int cdnsp_gadget_ep_queue(struct usb_ep *ep, + struct usb_request *request, + gfp_t gfp_flags) +{ + struct cdnsp_request *preq; + struct cdnsp_device *pdev; + struct cdnsp_ep *pep; + unsigned long flags; + int ret; + + if (!request || !ep) + return -EINVAL; + + pep = to_cdnsp_ep(ep); + pdev = pep->pdev; + + if (!(pep->ep_state & EP_ENABLED)) { + dev_err(pdev->dev, "%s: can't queue to disabled endpoint\n", + pep->name); + return -EINVAL; + } + + preq = to_cdnsp_request(request); + spin_lock_irqsave(&pdev->lock, flags); + ret = cdnsp_ep_enqueue(pep, preq); + spin_unlock_irqrestore(&pdev->lock, flags); + + return ret; +} + +static int cdnsp_gadget_ep_dequeue(struct usb_ep *ep, + struct usb_request *request) +{ + struct cdnsp_ep *pep = to_cdnsp_ep(ep); + struct cdnsp_device *pdev = pep->pdev; + unsigned long flags; + int ret; + + if (!pep->endpoint.desc) { + dev_err(pdev->dev, + "%s: can't dequeue to disabled endpoint\n", + pep->name); + return -ESHUTDOWN; + } + + spin_lock_irqsave(&pdev->lock, flags); + ret = cdnsp_ep_dequeue(pep, to_cdnsp_request(request)); + spin_unlock_irqrestore(&pdev->lock, flags); + + return ret; +} + +static int cdnsp_gadget_ep_set_halt(struct usb_ep *ep, int value) +{ + struct cdnsp_ep *pep = to_cdnsp_ep(ep); + struct cdnsp_device *pdev = pep->pdev; + struct cdnsp_request *preq; + unsigned long flags = 0; + int ret; + + spin_lock_irqsave(&pdev->lock, flags); + + preq = next_request(&pep->pending_list); + if (value) { + if (preq) { + ret = -EAGAIN; + goto done; + } + } + + ret = cdnsp_halt_endpoint(pdev, pep, value); + +done: + spin_unlock_irqrestore(&pdev->lock, flags); + return ret; +} + +static int cdnsp_gadget_ep_set_wedge(struct usb_ep *ep) +{ + struct cdnsp_ep *pep = to_cdnsp_ep(ep); + struct cdnsp_device *pdev = pep->pdev; + unsigned long flags = 0; + int ret; + + spin_lock_irqsave(&pdev->lock, flags); + pep->ep_state |= EP_WEDGE; + ret = cdnsp_halt_endpoint(pdev, pep, 1); + spin_unlock_irqrestore(&pdev->lock, flags); + + return ret; +} + +static const struct usb_ep_ops cdnsp_gadget_ep0_ops = { + .enable = cdnsp_gadget_ep_enable, + .disable = cdnsp_gadget_ep_disable, + .alloc_request = cdnsp_gadget_ep_alloc_request, + .free_request = cdnsp_gadget_ep_free_request, + .queue = cdnsp_gadget_ep_queue, + .dequeue = cdnsp_gadget_ep_dequeue, + .set_halt = cdnsp_gadget_ep_set_halt, + .set_wedge = cdnsp_gadget_ep_set_wedge, +}; + +static const struct usb_ep_ops cdnsp_gadget_ep_ops = { + .enable = cdnsp_gadget_ep_enable, + .disable = cdnsp_gadget_ep_disable, + .alloc_request = cdnsp_gadget_ep_alloc_request, + .free_request = cdnsp_gadget_ep_free_request, + .queue = cdnsp_gadget_ep_queue, + .dequeue = cdnsp_gadget_ep_dequeue, + .set_halt = cdnsp_gadget_ep_set_halt, + .set_wedge = cdnsp_gadget_ep_set_wedge, +}; + +void cdnsp_gadget_giveback(struct cdnsp_ep *pep, + struct cdnsp_request *preq, + int status) +{ + struct cdnsp_device *pdev = pep->pdev; + + list_del(&preq->list); + + if (preq->request.status == -EINPROGRESS) + preq->request.status = status; + + usb_gadget_unmap_request_by_dev(pdev->dev, &preq->request, + preq->direction); + + if (preq != &pdev->ep0_preq) { + spin_unlock(&pdev->lock); + usb_gadget_giveback_request(&pep->endpoint, &preq->request); + spin_lock(&pdev->lock); + } +} + +static struct usb_endpoint_descriptor cdnsp_gadget_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, +}; + +static int cdnsp_run(struct cdnsp_device *pdev, + enum usb_device_speed speed) +{ + u32 fs_speed = 0; + u64 temp_64; + u32 temp; + int ret; + + temp_64 = cdnsp_read_64(&pdev->ir_set->erst_dequeue); + temp_64 &= ~ERST_PTR_MASK; + temp = readl(&pdev->ir_set->irq_control); + temp &= ~IMOD_INTERVAL_MASK; + temp |= ((IMOD_DEFAULT_INTERVAL / 250) & IMOD_INTERVAL_MASK); + writel(temp, &pdev->ir_set->irq_control); + + temp = readl(&pdev->port3x_regs->mode_addr); + + switch (speed) { + case USB_SPEED_SUPER_PLUS: + temp |= CFG_3XPORT_SSP_SUPPORT; + break; + case USB_SPEED_SUPER: + temp &= ~CFG_3XPORT_SSP_SUPPORT; + break; + case USB_SPEED_HIGH: + break; + case USB_SPEED_FULL: + fs_speed = PORT_REG6_FORCE_FS; + break; + default: + dev_err(pdev->dev, "invalid maximum_speed parameter %d\n", + speed); + fallthrough; + case USB_SPEED_UNKNOWN: + /* Default to superspeed. */ + speed = USB_SPEED_SUPER; + break; + } + + if (speed >= USB_SPEED_SUPER) { + writel(temp, &pdev->port3x_regs->mode_addr); + cdnsp_set_link_state(pdev, &pdev->usb3_port.regs->portsc, + XDEV_RXDETECT); + } else { + cdnsp_disable_port(pdev, &pdev->usb3_port.regs->portsc); + } + + cdnsp_set_link_state(pdev, &pdev->usb2_port.regs->portsc, + XDEV_RXDETECT); + + cdnsp_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); + + writel(PORT_REG6_L1_L0_HW_EN | fs_speed, &pdev->port20_regs->port_reg6); + + ret = cdnsp_start(pdev); + if (ret) { + ret = -ENODEV; + goto err; + } + + temp = readl(&pdev->op_regs->command); + temp |= (CMD_INTE); + writel(temp, &pdev->op_regs->command); + + temp = readl(&pdev->ir_set->irq_pending); + writel(IMAN_IE_SET(temp), &pdev->ir_set->irq_pending); + + return 0; +err: + cdnsp_halt(pdev); + return ret; +} + +static int cdnsp_gadget_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + enum usb_device_speed max_speed = driver->max_speed; + struct cdnsp_device *pdev = gadget_to_cdnsp(g); + unsigned long flags; + int ret; + + spin_lock_irqsave(&pdev->lock, flags); + pdev->gadget_driver = driver; + + /* limit speed if necessary */ + max_speed = min(driver->max_speed, g->max_speed); + ret = cdnsp_run(pdev, max_speed); + + spin_unlock_irqrestore(&pdev->lock, flags); + + return ret; +} + +/* + * Update Event Ring Dequeue Pointer: + * - When all events have finished + * - To avoid "Event Ring Full Error" condition + */ +void cdnsp_update_erst_dequeue(struct cdnsp_device *pdev, + union cdnsp_trb *event_ring_deq, + u8 clear_ehb) +{ + u64 temp_64; + dma_addr_t deq; + + temp_64 = cdnsp_read_64(&pdev->ir_set->erst_dequeue); + + /* If necessary, update the HW's version of the event ring deq ptr. */ + if (event_ring_deq != pdev->event_ring->dequeue) { + deq = cdnsp_trb_virt_to_dma(pdev->event_ring->deq_seg, + pdev->event_ring->dequeue); + temp_64 &= ERST_PTR_MASK; + temp_64 |= ((u64)deq & (u64)~ERST_PTR_MASK); + } + + /* Clear the event handler busy flag (RW1C). */ + if (clear_ehb) + temp_64 |= ERST_EHB; + else + temp_64 &= ~ERST_EHB; + + cdnsp_write_64(temp_64, &pdev->ir_set->erst_dequeue); +} + +static void cdnsp_clear_cmd_ring(struct cdnsp_device *pdev) +{ + struct cdnsp_segment *seg; + u64 val_64; + int i; + + cdnsp_initialize_ring_info(pdev->cmd_ring); + + seg = pdev->cmd_ring->first_seg; + for (i = 0; i < pdev->cmd_ring->num_segs; i++) { + memset(seg->trbs, 0, + sizeof(union cdnsp_trb) * (TRBS_PER_SEGMENT - 1)); + seg = seg->next; + } + + /* Set the address in the Command Ring Control register. */ + val_64 = cdnsp_read_64(&pdev->op_regs->cmd_ring); + val_64 = (val_64 & (u64)CMD_RING_RSVD_BITS) | + (pdev->cmd_ring->first_seg->dma & (u64)~CMD_RING_RSVD_BITS) | + pdev->cmd_ring->cycle_state; + cdnsp_write_64(val_64, &pdev->op_regs->cmd_ring); +} + +static void cdnsp_consume_all_events(struct cdnsp_device *pdev) +{ + struct cdnsp_segment *event_deq_seg; + union cdnsp_trb *event_ring_deq; + union cdnsp_trb *event; + u32 cycle_bit; + + event_ring_deq = pdev->event_ring->dequeue; + event_deq_seg = pdev->event_ring->deq_seg; + event = pdev->event_ring->dequeue; + + /* Update ring dequeue pointer. */ + while (1) { + cycle_bit = (le32_to_cpu(event->event_cmd.flags) & TRB_CYCLE); + + /* Does the controller or driver own the TRB? */ + if (cycle_bit != pdev->event_ring->cycle_state) + break; + + cdnsp_inc_deq(pdev, pdev->event_ring); + + if (!cdnsp_last_trb_on_seg(event_deq_seg, event)) { + event++; + continue; + } + + if (cdnsp_last_trb_on_ring(pdev->event_ring, event_deq_seg, + event)) + cycle_bit ^= 1; + + event_deq_seg = event_deq_seg->next; + event = event_deq_seg->trbs; + } + + cdnsp_update_erst_dequeue(pdev, event_ring_deq, 1); +} + +static void cdnsp_stop(struct cdnsp_device *pdev) +{ + u32 temp; + + cdnsp_cmd_flush_ep(pdev, &pdev->eps[0]); + + /* Remove internally queued request for ep0. */ + if (!list_empty(&pdev->eps[0].pending_list)) { + struct cdnsp_request *req; + + req = next_request(&pdev->eps[0].pending_list); + if (req == &pdev->ep0_preq) + cdnsp_ep_dequeue(&pdev->eps[0], req); + } + + cdnsp_disable_port(pdev, &pdev->usb2_port.regs->portsc); + cdnsp_disable_port(pdev, &pdev->usb3_port.regs->portsc); + cdnsp_disable_slot(pdev); + cdnsp_halt(pdev); + + temp = readl(&pdev->op_regs->status); + writel((temp & ~0x1fff) | STS_EINT, &pdev->op_regs->status); + temp = readl(&pdev->ir_set->irq_pending); + writel(IMAN_IE_CLEAR(temp), &pdev->ir_set->irq_pending); + + cdnsp_clear_port_change_bit(pdev, &pdev->usb2_port.regs->portsc); + cdnsp_clear_port_change_bit(pdev, &pdev->usb3_port.regs->portsc); + + /* Clear interrupt line */ + temp = readl(&pdev->ir_set->irq_pending); + temp |= IMAN_IP; + writel(temp, &pdev->ir_set->irq_pending); + + cdnsp_consume_all_events(pdev); + cdnsp_clear_cmd_ring(pdev); +} + +/* + * Stop controller. + * This function is called by the gadget core when the driver is removed. + * Disable slot, disable IRQs, and quiesce the controller. + */ +static int cdnsp_gadget_udc_stop(struct usb_gadget *g) +{ + struct cdnsp_device *pdev = gadget_to_cdnsp(g); + unsigned long flags; + + spin_lock_irqsave(&pdev->lock, flags); + cdnsp_stop(pdev); + pdev->gadget_driver = NULL; + spin_unlock_irqrestore(&pdev->lock, flags); + + return 0; +} + +static int cdnsp_gadget_get_frame(struct usb_gadget *g) +{ + struct cdnsp_device *pdev = gadget_to_cdnsp(g); + + return cdnsp_get_frame(pdev); +} + +static void __cdnsp_gadget_wakeup(struct cdnsp_device *pdev) +{ + struct cdnsp_port_regs __iomem *port_regs; + u32 portpm, portsc; + + port_regs = pdev->active_port->regs; + portsc = readl(&port_regs->portsc) & PORT_PLS_MASK; + + /* Remote wakeup feature is not enabled by host. */ + if (pdev->gadget.speed < USB_SPEED_SUPER && portsc == XDEV_U2) { + portpm = readl(&port_regs->portpmsc); + + if (!(portpm & PORT_RWE)) + return; + } + + if (portsc == XDEV_U3 && !pdev->may_wakeup) + return; + + cdnsp_set_link_state(pdev, &port_regs->portsc, XDEV_U0); + + pdev->cdnsp_state |= CDNSP_WAKEUP_PENDING; +} + +static int cdnsp_gadget_wakeup(struct usb_gadget *g) +{ + struct cdnsp_device *pdev = gadget_to_cdnsp(g); + unsigned long flags; + + spin_lock_irqsave(&pdev->lock, flags); + __cdnsp_gadget_wakeup(pdev); + spin_unlock_irqrestore(&pdev->lock, flags); + + return 0; +} + +static int cdnsp_gadget_set_selfpowered(struct usb_gadget *g, + int is_selfpowered) +{ + struct cdnsp_device *pdev = gadget_to_cdnsp(g); + unsigned long flags; + + spin_lock_irqsave(&pdev->lock, flags); + g->is_selfpowered = !!is_selfpowered; + spin_unlock_irqrestore(&pdev->lock, flags); + + return 0; +} + +static int cdnsp_gadget_pullup(struct usb_gadget *gadget, int is_on) +{ + struct cdnsp_device *pdev = gadget_to_cdnsp(gadget); + struct cdns *cdns = dev_get_drvdata(pdev->dev); + + if (!is_on) { + cdnsp_reset_device(pdev); + cdns_clear_vbus(cdns); + } else { + cdns_set_vbus(cdns); + } + return 0; +} + +const struct usb_gadget_ops cdnsp_gadget_ops = { + .get_frame = cdnsp_gadget_get_frame, + .wakeup = cdnsp_gadget_wakeup, + .set_selfpowered = cdnsp_gadget_set_selfpowered, + .pullup = cdnsp_gadget_pullup, + .udc_start = cdnsp_gadget_udc_start, + .udc_stop = cdnsp_gadget_udc_stop, +}; + +static void cdnsp_get_ep_buffering(struct cdnsp_device *pdev, + struct cdnsp_ep *pep) +{ + void __iomem *reg = &pdev->cap_regs->hc_capbase; + int endpoints; + + reg += cdnsp_find_next_ext_cap(reg, 0, XBUF_CAP_ID); + + if (!pep->direction) { + pep->buffering = readl(reg + XBUF_RX_TAG_MASK_0_OFFSET); + pep->buffering_period = readl(reg + XBUF_RX_TAG_MASK_1_OFFSET); + pep->buffering = (pep->buffering + 1) / 2; + pep->buffering_period = (pep->buffering_period + 1) / 2; + return; + } + + endpoints = HCS_ENDPOINTS(readl(&pdev->hcs_params1)) / 2; + + /* Set to XBUF_TX_TAG_MASK_0 register. */ + reg += XBUF_TX_CMD_OFFSET + (endpoints * 2 + 2) * sizeof(u32); + /* Set reg to XBUF_TX_TAG_MASK_N related with this endpoint. */ + reg += pep->number * sizeof(u32) * 2; + + pep->buffering = (readl(reg) + 1) / 2; + pep->buffering_period = pep->buffering; +} + +static int cdnsp_gadget_init_endpoints(struct cdnsp_device *pdev) +{ + int max_streams = HCC_MAX_PSA(pdev->hcc_params); + struct cdnsp_ep *pep; + int i; + + INIT_LIST_HEAD(&pdev->gadget.ep_list); + + if (max_streams < STREAM_LOG_STREAMS) { + dev_err(pdev->dev, "Stream size %d not supported\n", + max_streams); + return -EINVAL; + } + + max_streams = STREAM_LOG_STREAMS; + + for (i = 0; i < CDNSP_ENDPOINTS_NUM; i++) { + bool direction = !(i & 1); /* Start from OUT endpoint. */ + u8 epnum = ((i + 1) >> 1); + + if (!CDNSP_IF_EP_EXIST(pdev, epnum, direction)) + continue; + + pep = &pdev->eps[i]; + pep->pdev = pdev; + pep->number = epnum; + pep->direction = direction; /* 0 for OUT, 1 for IN. */ + + /* + * Ep0 is bidirectional, so ep0in and ep0out are represented by + * pdev->eps[0] + */ + if (epnum == 0) { + snprintf(pep->name, sizeof(pep->name), "ep%d%s", + epnum, "BiDir"); + + pep->idx = 0; + usb_ep_set_maxpacket_limit(&pep->endpoint, 512); + pep->endpoint.maxburst = 1; + pep->endpoint.ops = &cdnsp_gadget_ep0_ops; + pep->endpoint.desc = &cdnsp_gadget_ep0_desc; + pep->endpoint.comp_desc = NULL; + pep->endpoint.caps.type_control = true; + pep->endpoint.caps.dir_in = true; + pep->endpoint.caps.dir_out = true; + + pdev->ep0_preq.epnum = pep->number; + pdev->ep0_preq.pep = pep; + pdev->gadget.ep0 = &pep->endpoint; + } else { + snprintf(pep->name, sizeof(pep->name), "ep%d%s", + epnum, (pep->direction) ? "in" : "out"); + + pep->idx = (epnum * 2 + (direction ? 1 : 0)) - 1; + usb_ep_set_maxpacket_limit(&pep->endpoint, 1024); + + pep->endpoint.max_streams = max_streams; + pep->endpoint.ops = &cdnsp_gadget_ep_ops; + list_add_tail(&pep->endpoint.ep_list, + &pdev->gadget.ep_list); + + pep->endpoint.caps.type_iso = true; + pep->endpoint.caps.type_bulk = true; + pep->endpoint.caps.type_int = true; + + pep->endpoint.caps.dir_in = direction; + pep->endpoint.caps.dir_out = !direction; + } + + pep->endpoint.name = pep->name; + pep->in_ctx = cdnsp_get_ep_ctx(&pdev->in_ctx, pep->idx); + pep->out_ctx = cdnsp_get_ep_ctx(&pdev->out_ctx, pep->idx); + cdnsp_get_ep_buffering(pdev, pep); + + dev_dbg(pdev->dev, "Init %s, MPS: %04x SupType: " + "CTRL: %s, INT: %s, BULK: %s, ISOC %s, " + "SupDir IN: %s, OUT: %s\n", + pep->name, 1024, + (pep->endpoint.caps.type_control) ? "yes" : "no", + (pep->endpoint.caps.type_int) ? "yes" : "no", + (pep->endpoint.caps.type_bulk) ? "yes" : "no", + (pep->endpoint.caps.type_iso) ? "yes" : "no", + (pep->endpoint.caps.dir_in) ? "yes" : "no", + (pep->endpoint.caps.dir_out) ? "yes" : "no"); + + INIT_LIST_HEAD(&pep->pending_list); + } + + return 0; +} + +static void cdnsp_gadget_free_endpoints(struct cdnsp_device *pdev) +{ + struct cdnsp_ep *pep; + int i; + + for (i = 0; i < CDNSP_ENDPOINTS_NUM; i++) { + pep = &pdev->eps[i]; + if (pep->number != 0 && pep->out_ctx) + list_del(&pep->endpoint.ep_list); + } +} + +void cdnsp_disconnect_gadget(struct cdnsp_device *pdev) +{ + pdev->cdnsp_state |= CDNSP_STATE_DISCONNECT_PENDING; + + if (pdev->gadget_driver && pdev->gadget_driver->disconnect) { + spin_unlock(&pdev->lock); + pdev->gadget_driver->disconnect(&pdev->gadget); + spin_lock(&pdev->lock); + } + + pdev->gadget.speed = USB_SPEED_UNKNOWN; + usb_gadget_set_state(&pdev->gadget, USB_STATE_NOTATTACHED); + + pdev->cdnsp_state &= ~CDNSP_STATE_DISCONNECT_PENDING; +} + +void cdnsp_suspend_gadget(struct cdnsp_device *pdev) +{ + if (pdev->gadget_driver && pdev->gadget_driver->suspend) { + spin_unlock(&pdev->lock); + pdev->gadget_driver->suspend(&pdev->gadget); + spin_lock(&pdev->lock); + } +} + +void cdnsp_resume_gadget(struct cdnsp_device *pdev) +{ + if (pdev->gadget_driver && pdev->gadget_driver->resume) { + spin_unlock(&pdev->lock); + pdev->gadget_driver->resume(&pdev->gadget); + spin_lock(&pdev->lock); + } +} + +void cdnsp_irq_reset(struct cdnsp_device *pdev) +{ + struct cdnsp_port_regs __iomem *port_regs; + + cdnsp_reset_device(pdev); + + port_regs = pdev->active_port->regs; + pdev->gadget.speed = cdnsp_port_speed(readl(port_regs)); + + spin_unlock(&pdev->lock); + usb_gadget_udc_reset(&pdev->gadget, pdev->gadget_driver); + spin_lock(&pdev->lock); + + switch (pdev->gadget.speed) { + case USB_SPEED_SUPER_PLUS: + case USB_SPEED_SUPER: + cdnsp_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); + pdev->gadget.ep0->maxpacket = 512; + break; + case USB_SPEED_HIGH: + case USB_SPEED_FULL: + cdnsp_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64); + pdev->gadget.ep0->maxpacket = 64; + break; + default: + /* Low speed is not supported. */ + dev_err(pdev->dev, "Unknown device speed\n"); + break; + } + + cdnsp_clear_chicken_bits_2(pdev, CHICKEN_XDMA_2_TP_CACHE_DIS); + cdnsp_setup_device(pdev, SETUP_CONTEXT_ONLY); + usb_gadget_set_state(&pdev->gadget, USB_STATE_DEFAULT); +} + +static void cdnsp_get_rev_cap(struct cdnsp_device *pdev) +{ + void __iomem *reg = &pdev->cap_regs->hc_capbase; + struct cdnsp_rev_cap *rev_cap; + + reg += cdnsp_find_next_ext_cap(reg, 0, RTL_REV_CAP); + rev_cap = reg; + + pdev->rev_cap.ctrl_revision = readl(&rev_cap->ctrl_revision); + pdev->rev_cap.rtl_revision = readl(&rev_cap->rtl_revision); + pdev->rev_cap.ep_supported = readl(&rev_cap->ep_supported); + pdev->rev_cap.ext_cap = readl(&rev_cap->ext_cap); + pdev->rev_cap.rx_buff_size = readl(&rev_cap->rx_buff_size); + pdev->rev_cap.tx_buff_size = readl(&rev_cap->tx_buff_size); + + dev_info(pdev->dev, "Rev: %08x/%08x, eps: %08x, buff: %08x/%08x\n", + pdev->rev_cap.ctrl_revision, pdev->rev_cap.rtl_revision, + pdev->rev_cap.ep_supported, pdev->rev_cap.rx_buff_size, + pdev->rev_cap.tx_buff_size); +} + +static int cdnsp_gen_setup(struct cdnsp_device *pdev) +{ + int ret; + u32 reg; + + pdev->cap_regs = pdev->regs; + pdev->op_regs = pdev->regs + + HC_LENGTH(readl(&pdev->cap_regs->hc_capbase)); + pdev->run_regs = pdev->regs + + (readl(&pdev->cap_regs->run_regs_off) & RTSOFF_MASK); + + /* Cache read-only capability registers */ + pdev->hcs_params1 = readl(&pdev->cap_regs->hcs_params1); + pdev->hcc_params = readl(&pdev->cap_regs->hc_capbase); + pdev->hci_version = HC_VERSION(pdev->hcc_params); + pdev->hcc_params = readl(&pdev->cap_regs->hcc_params); + + cdnsp_get_rev_cap(pdev); + + /* Make sure the Device Controller is halted. */ + ret = cdnsp_halt(pdev); + if (ret) + return ret; + + /* Reset the internal controller memory state and registers. */ + ret = cdnsp_reset(pdev); + if (ret) + return ret; + + /* + * Set dma_mask and coherent_dma_mask to 64-bits, + * if controller supports 64-bit addressing. + */ + if (HCC_64BIT_ADDR(pdev->hcc_params) && + !dma_set_mask(pdev->dev, DMA_BIT_MASK(64))) { + dev_dbg(pdev->dev, "Enabling 64-bit DMA addresses.\n"); + dma_set_coherent_mask(pdev->dev, DMA_BIT_MASK(64)); + } else { + /* + * This is to avoid error in cases where a 32-bit USB + * controller is used on a 64-bit capable system. + */ + ret = dma_set_mask(pdev->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + dev_dbg(pdev->dev, "Enabling 32-bit DMA addresses.\n"); + dma_set_coherent_mask(pdev->dev, DMA_BIT_MASK(32)); + } + + spin_lock_init(&pdev->lock); + + ret = cdnsp_mem_init(pdev, GFP_KERNEL); + if (ret) + return ret; + + /* + * Software workaround for U1: after transition + * to U1 the controller starts gating clock, and in some cases, + * it causes that controller stack. + */ + reg = readl(&pdev->port3x_regs->mode_2); + reg &= ~CFG_3XPORT_U1_PIPE_CLK_GATE_EN; + writel(reg, &pdev->port3x_regs->mode_2); + + return 0; +} + +static int __cdnsp_gadget_init(struct cdns *cdns) +{ + struct cdnsp_device *pdev; + u32 max_speed; + int ret = -ENOMEM; + + cdns_drd_gadget_on(cdns); + + pdev = kzalloc(sizeof(*pdev), GFP_KERNEL); + if (!pdev) + return -ENOMEM; + + pm_runtime_get_sync(cdns->dev); + + cdns->gadget_dev = pdev; + pdev->dev = cdns->dev; + pdev->regs = cdns->dev_regs; + max_speed = usb_get_maximum_speed(cdns->dev); + + switch (max_speed) { + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + case USB_SPEED_SUPER: + case USB_SPEED_SUPER_PLUS: + break; + default: + dev_err(cdns->dev, "invalid speed parameter %d\n", max_speed); + fallthrough; + case USB_SPEED_UNKNOWN: + /* Default to SSP */ + max_speed = USB_SPEED_SUPER_PLUS; + break; + } + + pdev->gadget.ops = &cdnsp_gadget_ops; + pdev->gadget.name = "cdnsp-gadget"; + pdev->gadget.speed = USB_SPEED_UNKNOWN; + pdev->gadget.sg_supported = 1; + pdev->gadget.max_speed = USB_SPEED_SUPER_PLUS; + pdev->gadget.lpm_capable = 1; + + pdev->setup_buf = kzalloc(CDNSP_EP0_SETUP_SIZE, GFP_KERNEL); + if (!pdev->setup_buf) + goto free_pdev; + + /* + * Controller supports not aligned buffer but it should improve + * performance. + */ + pdev->gadget.quirk_ep_out_aligned_size = true; + + ret = cdnsp_gen_setup(pdev); + if (ret) { + dev_err(pdev->dev, "Generic initialization failed %d\n", ret); + goto free_setup; + } + + ret = cdnsp_gadget_init_endpoints(pdev); + if (ret) { + dev_err(pdev->dev, "failed to initialize endpoints\n"); + goto halt_pdev; + } + + ret = usb_add_gadget_udc(pdev->dev, &pdev->gadget); + if (ret) { + dev_err(pdev->dev, "failed to register udc\n"); + goto free_endpoints; + } + + ret = devm_request_threaded_irq(pdev->dev, cdns->dev_irq, + cdnsp_irq_handler, + cdnsp_thread_irq_handler, IRQF_SHARED, + dev_name(pdev->dev), pdev); + if (ret) + goto del_gadget; + + return 0; + +del_gadget: + usb_del_gadget_udc(&pdev->gadget); +free_endpoints: + cdnsp_gadget_free_endpoints(pdev); +halt_pdev: + cdnsp_halt(pdev); + cdnsp_reset(pdev); + cdnsp_mem_cleanup(pdev); +free_setup: + kfree(pdev->setup_buf); +free_pdev: + kfree(pdev); + + return ret; +} + +static void cdnsp_gadget_exit(struct cdns *cdns) +{ + struct cdnsp_device *pdev = cdns->gadget_dev; + + devm_free_irq(pdev->dev, cdns->dev_irq, pdev); + pm_runtime_mark_last_busy(cdns->dev); + pm_runtime_put_autosuspend(cdns->dev); + usb_del_gadget_udc(&pdev->gadget); + cdnsp_gadget_free_endpoints(pdev); + cdnsp_mem_cleanup(pdev); + kfree(pdev); + cdns->gadget_dev = NULL; + cdns_drd_gadget_off(cdns); +} + +static int cdnsp_gadget_suspend(struct cdns *cdns, bool do_wakeup) +{ + struct cdnsp_device *pdev = cdns->gadget_dev; + unsigned long flags; + + if (pdev->link_state == XDEV_U3) + return 0; + + spin_lock_irqsave(&pdev->lock, flags); + cdnsp_disconnect_gadget(pdev); + cdnsp_stop(pdev); + spin_unlock_irqrestore(&pdev->lock, flags); + + return 0; +} + +static int cdnsp_gadget_resume(struct cdns *cdns, bool hibernated) +{ + struct cdnsp_device *pdev = cdns->gadget_dev; + enum usb_device_speed max_speed; + unsigned long flags; + int ret; + + if (!pdev->gadget_driver) + return 0; + + spin_lock_irqsave(&pdev->lock, flags); + max_speed = pdev->gadget_driver->max_speed; + + /* Limit speed if necessary. */ + max_speed = min(max_speed, pdev->gadget.max_speed); + + ret = cdnsp_run(pdev, max_speed); + + if (pdev->link_state == XDEV_U3) + __cdnsp_gadget_wakeup(pdev); + + spin_unlock_irqrestore(&pdev->lock, flags); + + return ret; +} + +/** + * cdnsp_gadget_init - initialize device structure + * @cdns: cdnsp instance + * + * This function initializes the gadget. + */ +int cdnsp_gadget_init(struct cdns *cdns) +{ + struct cdns_role_driver *rdrv; + + rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL); + if (!rdrv) + return -ENOMEM; + + rdrv->start = __cdnsp_gadget_init; + rdrv->stop = cdnsp_gadget_exit; + rdrv->suspend = cdnsp_gadget_suspend; + rdrv->resume = cdnsp_gadget_resume; + rdrv->state = CDNS_ROLE_STATE_INACTIVE; + rdrv->name = "gadget"; + cdns->roles[USB_ROLE_DEVICE] = rdrv; + + return 0; +} diff --git a/drivers/usb/cdns3/cdnsp-gadget.h b/drivers/usb/cdns3/cdnsp-gadget.h index 93da1dcdad60..5f8629eae41f 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.h +++ b/drivers/usb/cdns3/cdnsp-gadget.h @@ -1460,4 +1460,141 @@ struct cdnsp_device { u16 test_mode; }; +/* + * Registers should always be accessed with double word or quad word accesses. + * + * Registers with 64-bit address pointers should be written to with + * dword accesses by writing the low dword first (ptr[0]), then the high dword + * (ptr[1]) second. controller implementations that do not support 64-bit + * address pointers will ignore the high dword, and write order is irrelevant. + */ +static inline u64 cdnsp_read_64(__le64 __iomem *regs) +{ + return lo_hi_readq(regs); +} + +static inline void cdnsp_write_64(const u64 val, __le64 __iomem *regs) +{ + lo_hi_writeq(val, regs); +} + +/* CDNSP memory management functions. */ +void cdnsp_mem_cleanup(struct cdnsp_device *pdev); +int cdnsp_mem_init(struct cdnsp_device *pdev, gfp_t flags); +int cdnsp_setup_addressable_priv_dev(struct cdnsp_device *pdev); +void cdnsp_copy_ep0_dequeue_into_input_ctx(struct cdnsp_device *pdev); +void cdnsp_endpoint_zero(struct cdnsp_device *pdev, struct cdnsp_ep *ep); +int cdnsp_endpoint_init(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + gfp_t mem_flags); +int cdnsp_ring_expansion(struct cdnsp_device *pdev, + struct cdnsp_ring *ring, + unsigned int num_trbs, gfp_t flags); +struct cdnsp_ring *cdnsp_dma_to_transfer_ring(struct cdnsp_ep *ep, u64 address); +int cdnsp_alloc_stream_info(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + unsigned int num_stream_ctxs, + unsigned int num_streams); +int cdnsp_alloc_streams(struct cdnsp_device *pdev, struct cdnsp_ep *pep); +void cdnsp_free_endpoint_rings(struct cdnsp_device *pdev, struct cdnsp_ep *pep); + +/* Device controller glue. */ +int cdnsp_find_next_ext_cap(void __iomem *base, u32 start, int id); +int cdnsp_halt(struct cdnsp_device *pdev); +void cdnsp_died(struct cdnsp_device *pdev); +int cdnsp_reset(struct cdnsp_device *pdev); +irqreturn_t cdnsp_irq_handler(int irq, void *priv); +int cdnsp_setup_device(struct cdnsp_device *pdev, enum cdnsp_setup_dev setup); +void cdnsp_set_usb2_hardware_lpm(struct cdnsp_device *usbsssp_data, + struct usb_request *req, int enable); +irqreturn_t cdnsp_thread_irq_handler(int irq, void *data); + +/* Ring, segment, TRB, and TD functions. */ +dma_addr_t cdnsp_trb_virt_to_dma(struct cdnsp_segment *seg, + union cdnsp_trb *trb); +bool cdnsp_last_trb_on_seg(struct cdnsp_segment *seg, union cdnsp_trb *trb); +bool cdnsp_last_trb_on_ring(struct cdnsp_ring *ring, + struct cdnsp_segment *seg, + union cdnsp_trb *trb); +int cdnsp_wait_for_cmd_compl(struct cdnsp_device *pdev); +void cdnsp_update_erst_dequeue(struct cdnsp_device *pdev, + union cdnsp_trb *event_ring_deq, + u8 clear_ehb); +void cdnsp_initialize_ring_info(struct cdnsp_ring *ring); +void cdnsp_ring_cmd_db(struct cdnsp_device *pdev); +void cdnsp_queue_slot_control(struct cdnsp_device *pdev, u32 trb_type); +void cdnsp_queue_address_device(struct cdnsp_device *pdev, + dma_addr_t in_ctx_ptr, + enum cdnsp_setup_dev setup); +void cdnsp_queue_stop_endpoint(struct cdnsp_device *pdev, + unsigned int ep_index); +int cdnsp_queue_ctrl_tx(struct cdnsp_device *pdev, struct cdnsp_request *preq); +int cdnsp_queue_bulk_tx(struct cdnsp_device *pdev, struct cdnsp_request *preq); +int cdnsp_queue_isoc_tx_prepare(struct cdnsp_device *pdev, + struct cdnsp_request *preq); +void cdnsp_queue_configure_endpoint(struct cdnsp_device *pdev, + dma_addr_t in_ctx_ptr); +void cdnsp_queue_reset_ep(struct cdnsp_device *pdev, unsigned int ep_index); +void cdnsp_queue_halt_endpoint(struct cdnsp_device *pdev, + unsigned int ep_index); +void cdnsp_queue_flush_endpoint(struct cdnsp_device *pdev, + unsigned int ep_index); +void cdnsp_force_header_wakeup(struct cdnsp_device *pdev, int intf_num); +void cdnsp_queue_reset_device(struct cdnsp_device *pdev); +void cdnsp_queue_new_dequeue_state(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + struct cdnsp_dequeue_state *deq_state); +void cdnsp_ring_doorbell_for_active_rings(struct cdnsp_device *pdev, + struct cdnsp_ep *pep); +void cdnsp_inc_deq(struct cdnsp_device *pdev, struct cdnsp_ring *ring); +void cdnsp_set_link_state(struct cdnsp_device *pdev, + __le32 __iomem *port_regs, u32 link_state); +u32 cdnsp_port_state_to_neutral(u32 state); + +/* CDNSP device controller contexts. */ +int cdnsp_enable_slot(struct cdnsp_device *pdev); +int cdnsp_disable_slot(struct cdnsp_device *pdev); +struct cdnsp_input_control_ctx + *cdnsp_get_input_control_ctx(struct cdnsp_container_ctx *ctx); +struct cdnsp_slot_ctx *cdnsp_get_slot_ctx(struct cdnsp_container_ctx *ctx); +struct cdnsp_ep_ctx *cdnsp_get_ep_ctx(struct cdnsp_container_ctx *ctx, + unsigned int ep_index); +/* CDNSP gadget interface. */ +void cdnsp_suspend_gadget(struct cdnsp_device *pdev); +void cdnsp_resume_gadget(struct cdnsp_device *pdev); +void cdnsp_disconnect_gadget(struct cdnsp_device *pdev); +void cdnsp_gadget_giveback(struct cdnsp_ep *pep, struct cdnsp_request *preq, + int status); +int cdnsp_ep_enqueue(struct cdnsp_ep *pep, struct cdnsp_request *preq); +int cdnsp_ep_dequeue(struct cdnsp_ep *pep, struct cdnsp_request *preq); +unsigned int cdnsp_port_speed(unsigned int port_status); +void cdnsp_irq_reset(struct cdnsp_device *pdev); +int cdnsp_halt_endpoint(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, int value); +int cdnsp_cmd_stop_ep(struct cdnsp_device *pdev, struct cdnsp_ep *pep); +int cdnsp_cmd_flush_ep(struct cdnsp_device *pdev, struct cdnsp_ep *pep); +void cdnsp_setup_analyze(struct cdnsp_device *pdev); +int cdnsp_status_stage(struct cdnsp_device *pdev); +int cdnsp_reset_device(struct cdnsp_device *pdev); + +/** + * next_request - gets the next request on the given list + * @list: the request list to operate on + * + * Caller should take care of locking. This function return NULL or the first + * request available on list. + */ +static inline struct cdnsp_request *next_request(struct list_head *list) +{ + return list_first_entry_or_null(list, struct cdnsp_request, list); +} + +#define to_cdnsp_ep(ep) (container_of(ep, struct cdnsp_ep, endpoint)) +#define gadget_to_cdnsp(g) (container_of(g, struct cdnsp_device, gadget)) +#define request_to_cdnsp_request(r) (container_of(r, struct cdnsp_request, \ + request)) +#define to_cdnsp_request(r) (container_of(r, struct cdnsp_request, request)) +int cdnsp_remove_request(struct cdnsp_device *pdev, struct cdnsp_request *preq, + struct cdnsp_ep *pep); + #endif /* __LINUX_CDNSP_GADGET_H */ diff --git a/drivers/usb/cdns3/cdnsp-mem.c b/drivers/usb/cdns3/cdnsp-mem.c new file mode 100644 index 000000000000..ccabf3e5cce9 --- /dev/null +++ b/drivers/usb/cdns3/cdnsp-mem.c @@ -0,0 +1,1310 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence CDNSP DRD Driver. + * + * Copyright (C) 2020 Cadence. + * + * Author: Pawel Laszczak + * + * Code based on Linux XHCI driver. + * Origin: Copyright (C) 2008 Intel Corp. + */ + +#include +#include +#include +#include + +#include "cdnsp-gadget.h" + +static void cdnsp_free_stream_info(struct cdnsp_device *pdev, + struct cdnsp_ep *pep); +/* + * Allocates a generic ring segment from the ring pool, sets the dma address, + * initializes the segment to zero, and sets the private next pointer to NULL. + * + * "All components of all Command and Transfer TRBs shall be initialized to '0'" + */ +static struct cdnsp_segment *cdnsp_segment_alloc(struct cdnsp_device *pdev, + unsigned int cycle_state, + unsigned int max_packet, + gfp_t flags) +{ + struct cdnsp_segment *seg; + dma_addr_t dma; + int i; + + seg = kzalloc(sizeof(*seg), flags); + if (!seg) + return NULL; + + seg->trbs = dma_pool_zalloc(pdev->segment_pool, flags, &dma); + if (!seg->trbs) { + kfree(seg); + return NULL; + } + + if (max_packet) { + seg->bounce_buf = kzalloc(max_packet, flags | GFP_DMA); + if (!seg->bounce_buf) + goto free_dma; + } + + /* If the cycle state is 0, set the cycle bit to 1 for all the TRBs. */ + if (cycle_state == 0) { + for (i = 0; i < TRBS_PER_SEGMENT; i++) + seg->trbs[i].link.control |= cpu_to_le32(TRB_CYCLE); + } + seg->dma = dma; + seg->next = NULL; + + return seg; + +free_dma: + dma_pool_free(pdev->segment_pool, seg->trbs, dma); + kfree(seg); + + return NULL; +} + +static void cdnsp_segment_free(struct cdnsp_device *pdev, + struct cdnsp_segment *seg) +{ + if (seg->trbs) + dma_pool_free(pdev->segment_pool, seg->trbs, seg->dma); + + kfree(seg->bounce_buf); + kfree(seg); +} + +static void cdnsp_free_segments_for_ring(struct cdnsp_device *pdev, + struct cdnsp_segment *first) +{ + struct cdnsp_segment *seg; + + seg = first->next; + + while (seg != first) { + struct cdnsp_segment *next = seg->next; + + cdnsp_segment_free(pdev, seg); + seg = next; + } + + cdnsp_segment_free(pdev, first); +} + +/* + * Make the prev segment point to the next segment. + * + * Change the last TRB in the prev segment to be a Link TRB which points to the + * DMA address of the next segment. The caller needs to set any Link TRB + * related flags, such as End TRB, Toggle Cycle, and no snoop. + */ +static void cdnsp_link_segments(struct cdnsp_device *pdev, + struct cdnsp_segment *prev, + struct cdnsp_segment *next, + enum cdnsp_ring_type type) +{ + struct cdnsp_link_trb *link; + u32 val; + + if (!prev || !next) + return; + + prev->next = next; + if (type != TYPE_EVENT) { + link = &prev->trbs[TRBS_PER_SEGMENT - 1].link; + link->segment_ptr = cpu_to_le64(next->dma); + + /* + * Set the last TRB in the segment to have a TRB type ID + * of Link TRB + */ + val = le32_to_cpu(link->control); + val &= ~TRB_TYPE_BITMASK; + val |= TRB_TYPE(TRB_LINK); + link->control = cpu_to_le32(val); + } +} + +/* + * Link the ring to the new segments. + * Set Toggle Cycle for the new ring if needed. + */ +static void cdnsp_link_rings(struct cdnsp_device *pdev, + struct cdnsp_ring *ring, + struct cdnsp_segment *first, + struct cdnsp_segment *last, + unsigned int num_segs) +{ + struct cdnsp_segment *next; + + if (!ring || !first || !last) + return; + + next = ring->enq_seg->next; + cdnsp_link_segments(pdev, ring->enq_seg, first, ring->type); + cdnsp_link_segments(pdev, last, next, ring->type); + ring->num_segs += num_segs; + ring->num_trbs_free += (TRBS_PER_SEGMENT - 1) * num_segs; + + if (ring->type != TYPE_EVENT && ring->enq_seg == ring->last_seg) { + ring->last_seg->trbs[TRBS_PER_SEGMENT - 1].link.control &= + ~cpu_to_le32(LINK_TOGGLE); + last->trbs[TRBS_PER_SEGMENT - 1].link.control |= + cpu_to_le32(LINK_TOGGLE); + ring->last_seg = last; + } +} + +/* + * We need a radix tree for mapping physical addresses of TRBs to which stream + * ID they belong to. We need to do this because the device controller won't + * tell us which stream ring the TRB came from. We could store the stream ID + * in an event data TRB, but that doesn't help us for the cancellation case, + * since the endpoint may stop before it reaches that event data TRB. + * + * The radix tree maps the upper portion of the TRB DMA address to a ring + * segment that has the same upper portion of DMA addresses. For example, + * say I have segments of size 1KB, that are always 1KB aligned. A segment may + * start at 0x10c91000 and end at 0x10c913f0. If I use the upper 10 bits, the + * key to the stream ID is 0x43244. I can use the DMA address of the TRB to + * pass the radix tree a key to get the right stream ID: + * + * 0x10c90fff >> 10 = 0x43243 + * 0x10c912c0 >> 10 = 0x43244 + * 0x10c91400 >> 10 = 0x43245 + * + * Obviously, only those TRBs with DMA addresses that are within the segment + * will make the radix tree return the stream ID for that ring. + * + * Caveats for the radix tree: + * + * The radix tree uses an unsigned long as a key pair. On 32-bit systems, an + * unsigned long will be 32-bits; on a 64-bit system an unsigned long will be + * 64-bits. Since we only request 32-bit DMA addresses, we can use that as the + * key on 32-bit or 64-bit systems (it would also be fine if we asked for 64-bit + * PCI DMA addresses on a 64-bit system). There might be a problem on 32-bit + * extended systems (where the DMA address can be bigger than 32-bits), + * if we allow the PCI dma mask to be bigger than 32-bits. So don't do that. + */ +static int cdnsp_insert_segment_mapping(struct radix_tree_root *trb_address_map, + struct cdnsp_ring *ring, + struct cdnsp_segment *seg, + gfp_t mem_flags) +{ + unsigned long key; + int ret; + + key = (unsigned long)(seg->dma >> TRB_SEGMENT_SHIFT); + + /* Skip any segments that were already added. */ + if (radix_tree_lookup(trb_address_map, key)) + return 0; + + ret = radix_tree_maybe_preload(mem_flags); + if (ret) + return ret; + + ret = radix_tree_insert(trb_address_map, key, ring); + radix_tree_preload_end(); + + return ret; +} + +static void cdnsp_remove_segment_mapping(struct radix_tree_root *trb_address_map, + struct cdnsp_segment *seg) +{ + unsigned long key; + + key = (unsigned long)(seg->dma >> TRB_SEGMENT_SHIFT); + if (radix_tree_lookup(trb_address_map, key)) + radix_tree_delete(trb_address_map, key); +} + +static int cdnsp_update_stream_segment_mapping(struct radix_tree_root *trb_address_map, + struct cdnsp_ring *ring, + struct cdnsp_segment *first_seg, + struct cdnsp_segment *last_seg, + gfp_t mem_flags) +{ + struct cdnsp_segment *failed_seg; + struct cdnsp_segment *seg; + int ret; + + seg = first_seg; + do { + ret = cdnsp_insert_segment_mapping(trb_address_map, ring, seg, + mem_flags); + if (ret) + goto remove_streams; + if (seg == last_seg) + return 0; + seg = seg->next; + } while (seg != first_seg); + + return 0; + +remove_streams: + failed_seg = seg; + seg = first_seg; + do { + cdnsp_remove_segment_mapping(trb_address_map, seg); + if (seg == failed_seg) + return ret; + seg = seg->next; + } while (seg != first_seg); + + return ret; +} + +static void cdnsp_remove_stream_mapping(struct cdnsp_ring *ring) +{ + struct cdnsp_segment *seg; + + seg = ring->first_seg; + do { + cdnsp_remove_segment_mapping(ring->trb_address_map, seg); + seg = seg->next; + } while (seg != ring->first_seg); +} + +static int cdnsp_update_stream_mapping(struct cdnsp_ring *ring) +{ + return cdnsp_update_stream_segment_mapping(ring->trb_address_map, ring, + ring->first_seg, ring->last_seg, GFP_ATOMIC); +} + +static void cdnsp_ring_free(struct cdnsp_device *pdev, struct cdnsp_ring *ring) +{ + if (!ring) + return; + + if (ring->first_seg) { + if (ring->type == TYPE_STREAM) + cdnsp_remove_stream_mapping(ring); + + cdnsp_free_segments_for_ring(pdev, ring->first_seg); + } + + kfree(ring); +} + +void cdnsp_initialize_ring_info(struct cdnsp_ring *ring) +{ + ring->enqueue = ring->first_seg->trbs; + ring->enq_seg = ring->first_seg; + ring->dequeue = ring->enqueue; + ring->deq_seg = ring->first_seg; + + /* + * The ring is initialized to 0. The producer must write 1 to the cycle + * bit to handover ownership of the TRB, so PCS = 1. The consumer must + * compare CCS to the cycle bit to check ownership, so CCS = 1. + * + * New rings are initialized with cycle state equal to 1; if we are + * handling ring expansion, set the cycle state equal to the old ring. + */ + ring->cycle_state = 1; + + /* + * Each segment has a link TRB, and leave an extra TRB for SW + * accounting purpose + */ + ring->num_trbs_free = ring->num_segs * (TRBS_PER_SEGMENT - 1) - 1; +} + +/* Allocate segments and link them for a ring. */ +static int cdnsp_alloc_segments_for_ring(struct cdnsp_device *pdev, + struct cdnsp_segment **first, + struct cdnsp_segment **last, + unsigned int num_segs, + unsigned int cycle_state, + enum cdnsp_ring_type type, + unsigned int max_packet, + gfp_t flags) +{ + struct cdnsp_segment *prev; + + /* Allocate first segment. */ + prev = cdnsp_segment_alloc(pdev, cycle_state, max_packet, flags); + if (!prev) + return -ENOMEM; + + num_segs--; + *first = prev; + + /* Allocate all other segments. */ + while (num_segs > 0) { + struct cdnsp_segment *next; + + next = cdnsp_segment_alloc(pdev, cycle_state, + max_packet, flags); + if (!next) { + cdnsp_free_segments_for_ring(pdev, *first); + return -ENOMEM; + } + + cdnsp_link_segments(pdev, prev, next, type); + + prev = next; + num_segs--; + } + + cdnsp_link_segments(pdev, prev, *first, type); + *last = prev; + + return 0; +} + +/* + * Create a new ring with zero or more segments. + * + * Link each segment together into a ring. + * Set the end flag and the cycle toggle bit on the last segment. + */ +static struct cdnsp_ring *cdnsp_ring_alloc(struct cdnsp_device *pdev, + unsigned int num_segs, + enum cdnsp_ring_type type, + unsigned int max_packet, + gfp_t flags) +{ + struct cdnsp_ring *ring; + int ret; + + ring = kzalloc(sizeof *(ring), flags); + if (!ring) + return NULL; + + ring->num_segs = num_segs; + ring->bounce_buf_len = max_packet; + INIT_LIST_HEAD(&ring->td_list); + ring->type = type; + + if (num_segs == 0) + return ring; + + ret = cdnsp_alloc_segments_for_ring(pdev, &ring->first_seg, + &ring->last_seg, num_segs, + 1, type, max_packet, flags); + if (ret) + goto fail; + + /* Only event ring does not use link TRB. */ + if (type != TYPE_EVENT) + ring->last_seg->trbs[TRBS_PER_SEGMENT - 1].link.control |= + cpu_to_le32(LINK_TOGGLE); + + cdnsp_initialize_ring_info(ring); + + return ring; +fail: + kfree(ring); + return NULL; +} + +void cdnsp_free_endpoint_rings(struct cdnsp_device *pdev, struct cdnsp_ep *pep) +{ + cdnsp_ring_free(pdev, pep->ring); + pep->ring = NULL; + cdnsp_free_stream_info(pdev, pep); +} + +/* + * Expand an existing ring. + * Allocate a new ring which has same segment numbers and link the two rings. + */ +int cdnsp_ring_expansion(struct cdnsp_device *pdev, + struct cdnsp_ring *ring, + unsigned int num_trbs, + gfp_t flags) +{ + unsigned int num_segs_needed; + struct cdnsp_segment *first; + struct cdnsp_segment *last; + unsigned int num_segs; + int ret; + + num_segs_needed = (num_trbs + (TRBS_PER_SEGMENT - 1) - 1) / + (TRBS_PER_SEGMENT - 1); + + /* Allocate number of segments we needed, or double the ring size. */ + num_segs = max(ring->num_segs, num_segs_needed); + + ret = cdnsp_alloc_segments_for_ring(pdev, &first, &last, num_segs, + ring->cycle_state, ring->type, + ring->bounce_buf_len, flags); + if (ret) + return -ENOMEM; + + if (ring->type == TYPE_STREAM) + ret = cdnsp_update_stream_segment_mapping(ring->trb_address_map, + ring, first, + last, flags); + + if (ret) { + cdnsp_free_segments_for_ring(pdev, first); + + return ret; + } + + cdnsp_link_rings(pdev, ring, first, last, num_segs); + + return 0; +} + +static int cdnsp_init_device_ctx(struct cdnsp_device *pdev) +{ + int size = HCC_64BYTE_CONTEXT(pdev->hcc_params) ? 2048 : 1024; + + pdev->out_ctx.type = CDNSP_CTX_TYPE_DEVICE; + pdev->out_ctx.size = size; + pdev->out_ctx.ctx_size = CTX_SIZE(pdev->hcc_params); + pdev->out_ctx.bytes = dma_pool_zalloc(pdev->device_pool, GFP_ATOMIC, + &pdev->out_ctx.dma); + + if (!pdev->out_ctx.bytes) + return -ENOMEM; + + pdev->in_ctx.type = CDNSP_CTX_TYPE_INPUT; + pdev->in_ctx.ctx_size = pdev->out_ctx.ctx_size; + pdev->in_ctx.size = size + pdev->out_ctx.ctx_size; + pdev->in_ctx.bytes = dma_pool_zalloc(pdev->device_pool, GFP_ATOMIC, + &pdev->in_ctx.dma); + + if (!pdev->in_ctx.bytes) { + dma_pool_free(pdev->device_pool, pdev->out_ctx.bytes, + pdev->out_ctx.dma); + return -ENOMEM; + } + + return 0; +} + +struct cdnsp_input_control_ctx + *cdnsp_get_input_control_ctx(struct cdnsp_container_ctx *ctx) +{ + if (ctx->type != CDNSP_CTX_TYPE_INPUT) + return NULL; + + return (struct cdnsp_input_control_ctx *)ctx->bytes; +} + +struct cdnsp_slot_ctx *cdnsp_get_slot_ctx(struct cdnsp_container_ctx *ctx) +{ + if (ctx->type == CDNSP_CTX_TYPE_DEVICE) + return (struct cdnsp_slot_ctx *)ctx->bytes; + + return (struct cdnsp_slot_ctx *)(ctx->bytes + ctx->ctx_size); +} + +struct cdnsp_ep_ctx *cdnsp_get_ep_ctx(struct cdnsp_container_ctx *ctx, + unsigned int ep_index) +{ + /* Increment ep index by offset of start of ep ctx array. */ + ep_index++; + if (ctx->type == CDNSP_CTX_TYPE_INPUT) + ep_index++; + + return (struct cdnsp_ep_ctx *)(ctx->bytes + (ep_index * ctx->ctx_size)); +} + +static void cdnsp_free_stream_ctx(struct cdnsp_device *pdev, + struct cdnsp_ep *pep) +{ + dma_pool_free(pdev->device_pool, pep->stream_info.stream_ctx_array, + pep->stream_info.ctx_array_dma); +} + +/* The stream context array must be a power of 2. */ +static struct cdnsp_stream_ctx + *cdnsp_alloc_stream_ctx(struct cdnsp_device *pdev, struct cdnsp_ep *pep) +{ + size_t size = sizeof(struct cdnsp_stream_ctx) * + pep->stream_info.num_stream_ctxs; + + if (size > CDNSP_CTX_SIZE) + return NULL; + + /** + * Driver uses intentionally the device_pool to allocated stream + * context array. Device Pool has 2048 bytes of size what gives us + * 128 entries. + */ + return dma_pool_zalloc(pdev->device_pool, GFP_DMA32 | GFP_ATOMIC, + &pep->stream_info.ctx_array_dma); +} + +struct cdnsp_ring *cdnsp_dma_to_transfer_ring(struct cdnsp_ep *pep, u64 address) +{ + if (pep->ep_state & EP_HAS_STREAMS) + return radix_tree_lookup(&pep->stream_info.trb_address_map, + address >> TRB_SEGMENT_SHIFT); + + return pep->ring; +} + +/* + * Change an endpoint's internal structure so it supports stream IDs. + * The number of requested streams includes stream 0, which cannot be used by + * driver. + * + * The number of stream contexts in the stream context array may be bigger than + * the number of streams the driver wants to use. This is because the number of + * stream context array entries must be a power of two. + */ +int cdnsp_alloc_stream_info(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + unsigned int num_stream_ctxs, + unsigned int num_streams) +{ + struct cdnsp_stream_info *stream_info; + struct cdnsp_ring *cur_ring; + u32 cur_stream; + u64 addr; + int ret; + int mps; + + stream_info = &pep->stream_info; + stream_info->num_streams = num_streams; + stream_info->num_stream_ctxs = num_stream_ctxs; + + /* Initialize the array of virtual pointers to stream rings. */ + stream_info->stream_rings = kcalloc(num_streams, + sizeof(struct cdnsp_ring *), + GFP_ATOMIC); + if (!stream_info->stream_rings) + return -ENOMEM; + + /* Initialize the array of DMA addresses for stream rings for the HW. */ + stream_info->stream_ctx_array = cdnsp_alloc_stream_ctx(pdev, pep); + if (!stream_info->stream_ctx_array) + goto cleanup_stream_rings; + + memset(stream_info->stream_ctx_array, 0, + sizeof(struct cdnsp_stream_ctx) * num_stream_ctxs); + INIT_RADIX_TREE(&stream_info->trb_address_map, GFP_ATOMIC); + mps = usb_endpoint_maxp(pep->endpoint.desc); + + /* + * Allocate rings for all the streams that the driver will use, + * and add their segment DMA addresses to the radix tree. + * Stream 0 is reserved. + */ + for (cur_stream = 1; cur_stream < num_streams; cur_stream++) { + cur_ring = cdnsp_ring_alloc(pdev, 2, TYPE_STREAM, mps, + GFP_ATOMIC); + stream_info->stream_rings[cur_stream] = cur_ring; + + if (!cur_ring) + goto cleanup_rings; + + cur_ring->stream_id = cur_stream; + cur_ring->trb_address_map = &stream_info->trb_address_map; + + /* Set deq ptr, cycle bit, and stream context type. */ + addr = cur_ring->first_seg->dma | SCT_FOR_CTX(SCT_PRI_TR) | + cur_ring->cycle_state; + + stream_info->stream_ctx_array[cur_stream].stream_ring = + cpu_to_le64(addr); + + ret = cdnsp_update_stream_mapping(cur_ring); + if (ret) + goto cleanup_rings; + } + + return 0; + +cleanup_rings: + for (cur_stream = 1; cur_stream < num_streams; cur_stream++) { + cur_ring = stream_info->stream_rings[cur_stream]; + if (cur_ring) { + cdnsp_ring_free(pdev, cur_ring); + stream_info->stream_rings[cur_stream] = NULL; + } + } + +cleanup_stream_rings: + kfree(pep->stream_info.stream_rings); + + return -ENOMEM; +} + +/* Frees all stream contexts associated with the endpoint. */ +static void cdnsp_free_stream_info(struct cdnsp_device *pdev, + struct cdnsp_ep *pep) +{ + struct cdnsp_stream_info *stream_info = &pep->stream_info; + struct cdnsp_ring *cur_ring; + int cur_stream; + + if (!(pep->ep_state & EP_HAS_STREAMS)) + return; + + for (cur_stream = 1; cur_stream < stream_info->num_streams; + cur_stream++) { + cur_ring = stream_info->stream_rings[cur_stream]; + if (cur_ring) { + cdnsp_ring_free(pdev, cur_ring); + stream_info->stream_rings[cur_stream] = NULL; + } + } + + if (stream_info->stream_ctx_array) + cdnsp_free_stream_ctx(pdev, pep); + + kfree(stream_info->stream_rings); + pep->ep_state &= ~EP_HAS_STREAMS; +} + +/* All the cdnsp_tds in the ring's TD list should be freed at this point.*/ +static void cdnsp_free_priv_device(struct cdnsp_device *pdev) +{ + pdev->dcbaa->dev_context_ptrs[1] = 0; + + cdnsp_free_endpoint_rings(pdev, &pdev->eps[0]); + + if (pdev->in_ctx.bytes) + dma_pool_free(pdev->device_pool, pdev->in_ctx.bytes, + pdev->in_ctx.dma); + + if (pdev->out_ctx.bytes) + dma_pool_free(pdev->device_pool, pdev->out_ctx.bytes, + pdev->out_ctx.dma); + + pdev->in_ctx.bytes = NULL; + pdev->out_ctx.bytes = NULL; +} + +static int cdnsp_alloc_priv_device(struct cdnsp_device *pdev, gfp_t flags) +{ + int ret = -ENOMEM; + + ret = cdnsp_init_device_ctx(pdev); + if (ret) + return ret; + + /* Allocate endpoint 0 ring. */ + pdev->eps[0].ring = cdnsp_ring_alloc(pdev, 2, TYPE_CTRL, 0, flags); + if (!pdev->eps[0].ring) + goto fail; + + /* Point to output device context in dcbaa. */ + pdev->dcbaa->dev_context_ptrs[1] = cpu_to_le64(pdev->out_ctx.dma); + pdev->cmd.in_ctx = &pdev->in_ctx; + + return 0; +fail: + dma_pool_free(pdev->device_pool, pdev->out_ctx.bytes, + pdev->out_ctx.dma); + dma_pool_free(pdev->device_pool, pdev->in_ctx.bytes, + pdev->in_ctx.dma); + + return ret; +} + +void cdnsp_copy_ep0_dequeue_into_input_ctx(struct cdnsp_device *pdev) +{ + struct cdnsp_ep_ctx *ep0_ctx = pdev->eps[0].in_ctx; + struct cdnsp_ring *ep_ring = pdev->eps[0].ring; + dma_addr_t dma; + + dma = cdnsp_trb_virt_to_dma(ep_ring->enq_seg, ep_ring->enqueue); + ep0_ctx->deq = cpu_to_le64(dma | ep_ring->cycle_state); +} + +/* Setup an controller private device for a Set Address command. */ +int cdnsp_setup_addressable_priv_dev(struct cdnsp_device *pdev) +{ + struct cdnsp_slot_ctx *slot_ctx; + struct cdnsp_ep_ctx *ep0_ctx; + u32 max_packets, port; + + ep0_ctx = cdnsp_get_ep_ctx(&pdev->in_ctx, 0); + slot_ctx = cdnsp_get_slot_ctx(&pdev->in_ctx); + + /* Only the control endpoint is valid - one endpoint context. */ + slot_ctx->dev_info |= cpu_to_le32(LAST_CTX(1)); + + switch (pdev->gadget.speed) { + case USB_SPEED_SUPER_PLUS: + slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_SSP); + max_packets = MAX_PACKET(512); + break; + case USB_SPEED_SUPER: + slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_SS); + max_packets = MAX_PACKET(512); + break; + case USB_SPEED_HIGH: + slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_HS); + max_packets = MAX_PACKET(64); + break; + case USB_SPEED_FULL: + slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_FS); + max_packets = MAX_PACKET(64); + break; + default: + /* Speed was not set , this shouldn't happen. */ + return -EINVAL; + } + + port = DEV_PORT(pdev->active_port->port_num); + slot_ctx->dev_port |= cpu_to_le32(port); + slot_ctx->dev_state = (pdev->device_address & DEV_ADDR_MASK); + ep0_ctx->tx_info = EP_AVG_TRB_LENGTH(0x8); + ep0_ctx->ep_info2 = cpu_to_le32(EP_TYPE(CTRL_EP)); + ep0_ctx->ep_info2 |= cpu_to_le32(MAX_BURST(0) | ERROR_COUNT(3) | + max_packets); + + ep0_ctx->deq = cpu_to_le64(pdev->eps[0].ring->first_seg->dma | + pdev->eps[0].ring->cycle_state); + + return 0; +} + +/* + * Convert interval expressed as 2^(bInterval - 1) == interval into + * straight exponent value 2^n == interval. + */ +static unsigned int cdnsp_parse_exponent_interval(struct usb_gadget *g, + struct cdnsp_ep *pep) +{ + unsigned int interval; + + interval = clamp_val(pep->endpoint.desc->bInterval, 1, 16) - 1; + if (interval != pep->endpoint.desc->bInterval - 1) + dev_warn(&g->dev, "ep %s - rounding interval to %d %sframes\n", + pep->name, 1 << interval, + g->speed == USB_SPEED_FULL ? "" : "micro"); + + /* + * Full speed isoc endpoints specify interval in frames, + * not microframes. We are using microframes everywhere, + * so adjust accordingly. + */ + if (g->speed == USB_SPEED_FULL) + interval += 3; /* 1 frame = 2^3 uframes */ + + /* Controller handles only up to 512ms (2^12). */ + if (interval > 12) + interval = 12; + + return interval; +} + +/* + * Convert bInterval expressed in microframes (in 1-255 range) to exponent of + * microframes, rounded down to nearest power of 2. + */ +static unsigned int cdnsp_microframes_to_exponent(struct usb_gadget *g, + struct cdnsp_ep *pep, + unsigned int desc_interval, + unsigned int min_exponent, + unsigned int max_exponent) +{ + unsigned int interval; + + interval = fls(desc_interval) - 1; + return clamp_val(interval, min_exponent, max_exponent); +} + +/* + * Return the polling interval. + * + * The polling interval is expressed in "microframes". If controllers's Interval + * field is set to N, it will service the endpoint every 2^(Interval)*125us. + */ +static unsigned int cdnsp_get_endpoint_interval(struct usb_gadget *g, + struct cdnsp_ep *pep) +{ + unsigned int interval = 0; + + switch (g->speed) { + case USB_SPEED_HIGH: + case USB_SPEED_SUPER_PLUS: + case USB_SPEED_SUPER: + if (usb_endpoint_xfer_int(pep->endpoint.desc) || + usb_endpoint_xfer_isoc(pep->endpoint.desc)) + interval = cdnsp_parse_exponent_interval(g, pep); + break; + case USB_SPEED_FULL: + if (usb_endpoint_xfer_isoc(pep->endpoint.desc)) { + interval = cdnsp_parse_exponent_interval(g, pep); + } else if (usb_endpoint_xfer_int(pep->endpoint.desc)) { + interval = pep->endpoint.desc->bInterval << 3; + interval = cdnsp_microframes_to_exponent(g, pep, + interval, + 3, 10); + } + + break; + default: + WARN_ON(1); + } + + return interval; +} + +/* + * The "Mult" field in the endpoint context is only set for SuperSpeed isoc eps. + * High speed endpoint descriptors can define "the number of additional + * transaction opportunities per microframe", but that goes in the Max Burst + * endpoint context field. + */ +static u32 cdnsp_get_endpoint_mult(struct usb_gadget *g, struct cdnsp_ep *pep) +{ + if (g->speed < USB_SPEED_SUPER || + !usb_endpoint_xfer_isoc(pep->endpoint.desc)) + return 0; + + return pep->endpoint.comp_desc->bmAttributes; +} + +static u32 cdnsp_get_endpoint_max_burst(struct usb_gadget *g, + struct cdnsp_ep *pep) +{ + /* Super speed and Plus have max burst in ep companion desc */ + if (g->speed >= USB_SPEED_SUPER) + return pep->endpoint.comp_desc->bMaxBurst; + + if (g->speed == USB_SPEED_HIGH && + (usb_endpoint_xfer_isoc(pep->endpoint.desc) || + usb_endpoint_xfer_int(pep->endpoint.desc))) + return (usb_endpoint_maxp(pep->endpoint.desc) & 0x1800) >> 11; + + return 0; +} + +static u32 cdnsp_get_endpoint_type(const struct usb_endpoint_descriptor *desc) +{ + int in; + + in = usb_endpoint_dir_in(desc); + + switch (usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_CONTROL: + return CTRL_EP; + case USB_ENDPOINT_XFER_BULK: + return in ? BULK_IN_EP : BULK_OUT_EP; + case USB_ENDPOINT_XFER_ISOC: + return in ? ISOC_IN_EP : ISOC_OUT_EP; + case USB_ENDPOINT_XFER_INT: + return in ? INT_IN_EP : INT_OUT_EP; + } + + return 0; +} + +/* + * Return the maximum endpoint service interval time (ESIT) payload. + * Basically, this is the maxpacket size, multiplied by the burst size + * and mult size. + */ +static u32 cdnsp_get_max_esit_payload(struct usb_gadget *g, + struct cdnsp_ep *pep) +{ + int max_packet; + int max_burst; + + /* Only applies for interrupt or isochronous endpoints*/ + if (usb_endpoint_xfer_control(pep->endpoint.desc) || + usb_endpoint_xfer_bulk(pep->endpoint.desc)) + return 0; + + /* SuperSpeedPlus Isoc ep sending over 48k per EIST. */ + if (g->speed >= USB_SPEED_SUPER_PLUS && + USB_SS_SSP_ISOC_COMP(pep->endpoint.desc->bmAttributes)) + return le32_to_cpu(pep->endpoint.comp_desc->wBytesPerInterval); + /* SuperSpeed or SuperSpeedPlus Isoc ep with less than 48k per esit */ + else if (g->speed >= USB_SPEED_SUPER) + return le16_to_cpu(pep->endpoint.comp_desc->wBytesPerInterval); + + max_packet = usb_endpoint_maxp(pep->endpoint.desc); + max_burst = usb_endpoint_maxp_mult(pep->endpoint.desc); + + /* A 0 in max burst means 1 transfer per ESIT */ + return max_packet * max_burst; +} + +int cdnsp_endpoint_init(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + gfp_t mem_flags) +{ + enum cdnsp_ring_type ring_type; + struct cdnsp_ep_ctx *ep_ctx; + unsigned int err_count = 0; + unsigned int avg_trb_len; + unsigned int max_packet; + unsigned int max_burst; + unsigned int interval; + u32 max_esit_payload; + unsigned int mult; + u32 endpoint_type; + int ret; + + ep_ctx = pep->in_ctx; + + endpoint_type = cdnsp_get_endpoint_type(pep->endpoint.desc); + if (!endpoint_type) + return -EINVAL; + + ring_type = usb_endpoint_type(pep->endpoint.desc); + + /* + * Get values to fill the endpoint context, mostly from ep descriptor. + * The average TRB buffer length for bulk endpoints is unclear as we + * have no clue on scatter gather list entry size. For Isoc and Int, + * set it to max available. + */ + max_esit_payload = cdnsp_get_max_esit_payload(&pdev->gadget, pep); + interval = cdnsp_get_endpoint_interval(&pdev->gadget, pep); + mult = cdnsp_get_endpoint_mult(&pdev->gadget, pep); + max_packet = usb_endpoint_maxp(pep->endpoint.desc); + max_burst = cdnsp_get_endpoint_max_burst(&pdev->gadget, pep); + avg_trb_len = max_esit_payload; + + /* Allow 3 retries for everything but isoc, set CErr = 3. */ + if (!usb_endpoint_xfer_isoc(pep->endpoint.desc)) + err_count = 3; + if (usb_endpoint_xfer_bulk(pep->endpoint.desc) && + pdev->gadget.speed == USB_SPEED_HIGH) + max_packet = 512; + /* Controller spec indicates that ctrl ep avg TRB Length should be 8. */ + if (usb_endpoint_xfer_control(pep->endpoint.desc)) + avg_trb_len = 8; + + /* Set up the endpoint ring. */ + pep->ring = cdnsp_ring_alloc(pdev, 2, ring_type, max_packet, mem_flags); + pep->skip = false; + + /* Fill the endpoint context */ + ep_ctx->ep_info = cpu_to_le32(EP_MAX_ESIT_PAYLOAD_HI(max_esit_payload) | + EP_INTERVAL(interval) | EP_MULT(mult)); + ep_ctx->ep_info2 = cpu_to_le32(EP_TYPE(endpoint_type) | + MAX_PACKET(max_packet) | MAX_BURST(max_burst) | + ERROR_COUNT(err_count)); + ep_ctx->deq = cpu_to_le64(pep->ring->first_seg->dma | + pep->ring->cycle_state); + + ep_ctx->tx_info = cpu_to_le32(EP_MAX_ESIT_PAYLOAD_LO(max_esit_payload) | + EP_AVG_TRB_LENGTH(avg_trb_len)); + + if (usb_endpoint_xfer_bulk(pep->endpoint.desc) && + pdev->gadget.speed > USB_SPEED_HIGH) { + ret = cdnsp_alloc_streams(pdev, pep); + if (ret < 0) + return ret; + } + + return 0; +} + +void cdnsp_endpoint_zero(struct cdnsp_device *pdev, struct cdnsp_ep *pep) +{ + pep->in_ctx->ep_info = 0; + pep->in_ctx->ep_info2 = 0; + pep->in_ctx->deq = 0; + pep->in_ctx->tx_info = 0; +} + +static int cdnsp_alloc_erst(struct cdnsp_device *pdev, + struct cdnsp_ring *evt_ring, + struct cdnsp_erst *erst, + gfp_t flags) +{ + struct cdnsp_erst_entry *entry; + struct cdnsp_segment *seg; + unsigned int val; + size_t size; + + size = sizeof(struct cdnsp_erst_entry) * evt_ring->num_segs; + erst->entries = dma_alloc_coherent(pdev->dev, size, + &erst->erst_dma_addr, flags); + if (!erst->entries) + return -ENOMEM; + + erst->num_entries = evt_ring->num_segs; + + seg = evt_ring->first_seg; + for (val = 0; val < evt_ring->num_segs; val++) { + entry = &erst->entries[val]; + entry->seg_addr = cpu_to_le64(seg->dma); + entry->seg_size = cpu_to_le32(TRBS_PER_SEGMENT); + entry->rsvd = 0; + seg = seg->next; + } + + return 0; +} + +static void cdnsp_free_erst(struct cdnsp_device *pdev, struct cdnsp_erst *erst) +{ + size_t size = sizeof(struct cdnsp_erst_entry) * (erst->num_entries); + struct device *dev = pdev->dev; + + if (erst->entries) + dma_free_coherent(dev, size, erst->entries, + erst->erst_dma_addr); + + erst->entries = NULL; +} + +void cdnsp_mem_cleanup(struct cdnsp_device *pdev) +{ + struct device *dev = pdev->dev; + + cdnsp_free_priv_device(pdev); + cdnsp_free_erst(pdev, &pdev->erst); + + if (pdev->event_ring) + cdnsp_ring_free(pdev, pdev->event_ring); + + pdev->event_ring = NULL; + + if (pdev->cmd_ring) + cdnsp_ring_free(pdev, pdev->cmd_ring); + + pdev->cmd_ring = NULL; + + dma_pool_destroy(pdev->segment_pool); + pdev->segment_pool = NULL; + dma_pool_destroy(pdev->device_pool); + pdev->device_pool = NULL; + + if (pdev->dcbaa) + dma_free_coherent(dev, sizeof(*pdev->dcbaa), + pdev->dcbaa, pdev->dcbaa->dma); + + pdev->dcbaa = NULL; + + pdev->usb2_port.exist = 0; + pdev->usb3_port.exist = 0; + pdev->usb2_port.port_num = 0; + pdev->usb3_port.port_num = 0; + pdev->active_port = NULL; +} + +static void cdnsp_set_event_deq(struct cdnsp_device *pdev) +{ + dma_addr_t deq; + u64 temp; + + deq = cdnsp_trb_virt_to_dma(pdev->event_ring->deq_seg, + pdev->event_ring->dequeue); + + /* Update controller event ring dequeue pointer */ + temp = cdnsp_read_64(&pdev->ir_set->erst_dequeue); + temp &= ERST_PTR_MASK; + + /* + * Don't clear the EHB bit (which is RW1C) because + * there might be more events to service. + */ + temp &= ~ERST_EHB; + + cdnsp_write_64(((u64)deq & (u64)~ERST_PTR_MASK) | temp, + &pdev->ir_set->erst_dequeue); +} + +static void cdnsp_add_in_port(struct cdnsp_device *pdev, + struct cdnsp_port *port, + __le32 __iomem *addr) +{ + u32 temp, port_offset; + + temp = readl(addr); + port->maj_rev = CDNSP_EXT_PORT_MAJOR(temp); + port->min_rev = CDNSP_EXT_PORT_MINOR(temp); + + /* Port offset and count in the third dword.*/ + temp = readl(addr + 2); + port_offset = CDNSP_EXT_PORT_OFF(temp); + + port->port_num = port_offset; + port->exist = 1; +} + +/* + * Scan the Extended Capabilities for the "Supported Protocol Capabilities" that + * specify what speeds each port is supposed to be. + */ +static int cdnsp_setup_port_arrays(struct cdnsp_device *pdev, gfp_t flags) +{ + void __iomem *base; + u32 offset; + int i; + + base = &pdev->cap_regs->hc_capbase; + offset = cdnsp_find_next_ext_cap(base, 0, + EXT_CAP_CFG_DEV_20PORT_CAP_ID); + pdev->port20_regs = base + offset; + + offset = cdnsp_find_next_ext_cap(base, 0, D_XEC_CFG_3XPORT_CAP); + pdev->port3x_regs = base + offset; + + offset = 0; + base = &pdev->cap_regs->hc_capbase; + + /* Driver expects max 2 extended protocol capability. */ + for (i = 0; i < 2; i++) { + u32 temp; + + offset = cdnsp_find_next_ext_cap(base, offset, + EXT_CAPS_PROTOCOL); + temp = readl(base + offset); + + if (CDNSP_EXT_PORT_MAJOR(temp) == 0x03 && + !pdev->usb3_port.port_num) + cdnsp_add_in_port(pdev, &pdev->usb3_port, + base + offset); + + if (CDNSP_EXT_PORT_MAJOR(temp) == 0x02 && + !pdev->usb2_port.port_num) + cdnsp_add_in_port(pdev, &pdev->usb2_port, + base + offset); + } + + if (!pdev->usb2_port.exist || !pdev->usb3_port.exist) { + dev_err(pdev->dev, "Error: Only one port detected\n"); + return -ENODEV; + } + + pdev->usb2_port.regs = (struct cdnsp_port_regs *) + (&pdev->op_regs->port_reg_base + NUM_PORT_REGS * + (pdev->usb2_port.port_num - 1)); + + pdev->usb3_port.regs = (struct cdnsp_port_regs *) + (&pdev->op_regs->port_reg_base + NUM_PORT_REGS * + (pdev->usb3_port.port_num - 1)); + + return 0; +} + +/* + * Initialize memory for CDNSP (one-time init). + * + * Program the PAGESIZE register, initialize the device context array, create + * device contexts, set up a command ring segment, create event + * ring (one for now). + */ +int cdnsp_mem_init(struct cdnsp_device *pdev, gfp_t flags) +{ + struct device *dev = pdev->dev; + int ret = -ENOMEM; + unsigned int val; + dma_addr_t dma; + u32 page_size; + u64 val_64; + + /* + * Use 4K pages, since that's common and the minimum the + * controller supports + */ + page_size = 1 << 12; + + val = readl(&pdev->op_regs->config_reg); + val |= ((val & ~MAX_DEVS) | CDNSP_DEV_MAX_SLOTS) | CONFIG_U3E; + writel(val, &pdev->op_regs->config_reg); + + /* + * Doorbell array must be physically contiguous + * and 64-byte (cache line) aligned. + */ + pdev->dcbaa = dma_alloc_coherent(dev, sizeof(*pdev->dcbaa), + &dma, GFP_KERNEL); + if (!pdev->dcbaa) + goto mem_init_fail; + + memset(pdev->dcbaa, 0, sizeof(*pdev->dcbaa)); + pdev->dcbaa->dma = dma; + + cdnsp_write_64(dma, &pdev->op_regs->dcbaa_ptr); + + /* + * Initialize the ring segment pool. The ring must be a contiguous + * structure comprised of TRBs. The TRBs must be 16 byte aligned, + * however, the command ring segment needs 64-byte aligned segments + * and our use of dma addresses in the trb_address_map radix tree needs + * TRB_SEGMENT_SIZE alignment, so driver pick the greater alignment + * need. + */ + pdev->segment_pool = dma_pool_create("CDNSP ring segments", dev, + TRB_SEGMENT_SIZE, TRB_SEGMENT_SIZE, + page_size); + + pdev->device_pool = dma_pool_create("CDNSP input/output contexts", dev, + CDNSP_CTX_SIZE, 64, page_size); + + if (!pdev->segment_pool || !pdev->device_pool) + goto mem_init_fail; + + /* Set up the command ring to have one segments for now. */ + pdev->cmd_ring = cdnsp_ring_alloc(pdev, 1, TYPE_COMMAND, 0, flags); + if (!pdev->cmd_ring) + goto mem_init_fail; + + /* Set the address in the Command Ring Control register */ + val_64 = cdnsp_read_64(&pdev->op_regs->cmd_ring); + val_64 = (val_64 & (u64)CMD_RING_RSVD_BITS) | + (pdev->cmd_ring->first_seg->dma & (u64)~CMD_RING_RSVD_BITS) | + pdev->cmd_ring->cycle_state; + cdnsp_write_64(val_64, &pdev->op_regs->cmd_ring); + + val = readl(&pdev->cap_regs->db_off); + val &= DBOFF_MASK; + pdev->dba = (void __iomem *)pdev->cap_regs + val; + + /* Set ir_set to interrupt register set 0 */ + pdev->ir_set = &pdev->run_regs->ir_set[0]; + + /* + * Event ring setup: Allocate a normal ring, but also setup + * the event ring segment table (ERST). + */ + pdev->event_ring = cdnsp_ring_alloc(pdev, ERST_NUM_SEGS, TYPE_EVENT, + 0, flags); + if (!pdev->event_ring) + goto mem_init_fail; + + ret = cdnsp_alloc_erst(pdev, pdev->event_ring, &pdev->erst, flags); + if (ret) + goto mem_init_fail; + + /* Set ERST count with the number of entries in the segment table. */ + val = readl(&pdev->ir_set->erst_size); + val &= ERST_SIZE_MASK; + val |= ERST_NUM_SEGS; + writel(val, &pdev->ir_set->erst_size); + + /* Set the segment table base address. */ + val_64 = cdnsp_read_64(&pdev->ir_set->erst_base); + val_64 &= ERST_PTR_MASK; + val_64 |= (pdev->erst.erst_dma_addr & (u64)~ERST_PTR_MASK); + cdnsp_write_64(val_64, &pdev->ir_set->erst_base); + + /* Set the event ring dequeue address. */ + cdnsp_set_event_deq(pdev); + + ret = cdnsp_setup_port_arrays(pdev, flags); + if (ret) + goto mem_init_fail; + + ret = cdnsp_alloc_priv_device(pdev, GFP_ATOMIC); + if (ret) { + dev_err(pdev->dev, + "Could not allocate cdnsp_device data structures\n"); + goto mem_init_fail; + } + + return 0; + +mem_init_fail: + dev_err(pdev->dev, "Couldn't initialize memory\n"); + cdnsp_halt(pdev); + cdnsp_reset(pdev); + cdnsp_mem_cleanup(pdev); + + return ret; +} diff --git a/drivers/usb/cdns3/cdnsp-pci.c b/drivers/usb/cdns3/cdnsp-pci.c new file mode 100644 index 000000000000..fe8a114c586c --- /dev/null +++ b/drivers/usb/cdns3/cdnsp-pci.c @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence PCI Glue driver. + * + * Copyright (C) 2019 Cadence. + * + * Author: Pawel Laszczak + * + */ + +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "gadget-export.h" + +#define PCI_BAR_HOST 0 +#define PCI_BAR_OTG 0 +#define PCI_BAR_DEV 2 + +#define PCI_DEV_FN_HOST_DEVICE 0 +#define PCI_DEV_FN_OTG 1 + +#define PCI_DRIVER_NAME "cdns-pci-usbssp" +#define PLAT_DRIVER_NAME "cdns-usbssp" + +#define CDNS_VENDOR_ID 0x17cd +#define CDNS_DEVICE_ID 0x0100 +#define CDNS_DRD_IF (PCI_CLASS_SERIAL_USB << 8 | 0x80) + +static struct pci_dev *cdnsp_get_second_fun(struct pci_dev *pdev) +{ + struct pci_dev *func; + + /* + * Gets the second function. + * It's little tricky, but this platform has two function. + * The fist keeps resources for Host/Device while the second + * keeps resources for DRD/OTG. + */ + func = pci_get_device(pdev->vendor, pdev->device, NULL); + if (!func) + return NULL; + + if (func->devfn == pdev->devfn) { + func = pci_get_device(pdev->vendor, pdev->device, func); + if (!func) + return NULL; + } + + return func; +} + +static int cdnsp_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct device *dev = &pdev->dev; + struct pci_dev *func; + struct resource *res; + struct cdns *cdnsp; + int ret; + + /* + * For GADGET/HOST PCI (devfn) function number is 0, + * for OTG PCI (devfn) function number is 1. + */ + if (!id || (pdev->devfn != PCI_DEV_FN_HOST_DEVICE && + pdev->devfn != PCI_DEV_FN_OTG)) + return -EINVAL; + + func = cdnsp_get_second_fun(pdev); + if (!func) + return -EINVAL; + + if (func->class == PCI_CLASS_SERIAL_USB_XHCI || + pdev->class == PCI_CLASS_SERIAL_USB_XHCI) { + ret = -EINVAL; + goto put_pci; + } + + ret = pcim_enable_device(pdev); + if (ret) { + dev_err(&pdev->dev, "Enabling PCI device has failed %d\n", ret); + goto put_pci; + } + + pci_set_master(pdev); + if (pci_is_enabled(func)) { + cdnsp = pci_get_drvdata(func); + } else { + cdnsp = kzalloc(sizeof(*cdnsp), GFP_KERNEL); + if (!cdnsp) { + ret = -ENOMEM; + goto disable_pci; + } + } + + /* For GADGET device function number is 0. */ + if (pdev->devfn == 0) { + resource_size_t rsrc_start, rsrc_len; + + /* Function 0: host(BAR_0) + device(BAR_1).*/ + dev_dbg(dev, "Initialize resources\n"); + rsrc_start = pci_resource_start(pdev, PCI_BAR_DEV); + rsrc_len = pci_resource_len(pdev, PCI_BAR_DEV); + res = devm_request_mem_region(dev, rsrc_start, rsrc_len, "dev"); + if (!res) { + dev_dbg(dev, "controller already in use\n"); + ret = -EBUSY; + goto free_cdnsp; + } + + cdnsp->dev_regs = devm_ioremap(dev, rsrc_start, rsrc_len); + if (!cdnsp->dev_regs) { + dev_dbg(dev, "error mapping memory\n"); + ret = -EFAULT; + goto free_cdnsp; + } + + cdnsp->dev_irq = pdev->irq; + dev_dbg(dev, "USBSS-DEV physical base addr: %pa\n", + &rsrc_start); + + res = &cdnsp->xhci_res[0]; + res->start = pci_resource_start(pdev, PCI_BAR_HOST); + res->end = pci_resource_end(pdev, PCI_BAR_HOST); + res->name = "xhci"; + res->flags = IORESOURCE_MEM; + dev_dbg(dev, "USBSS-XHCI physical base addr: %pa\n", + &res->start); + + /* Interrupt for XHCI, */ + res = &cdnsp->xhci_res[1]; + res->start = pdev->irq; + res->name = "host"; + res->flags = IORESOURCE_IRQ; + } else { + res = &cdnsp->otg_res; + res->start = pci_resource_start(pdev, PCI_BAR_OTG); + res->end = pci_resource_end(pdev, PCI_BAR_OTG); + res->name = "otg"; + res->flags = IORESOURCE_MEM; + dev_dbg(dev, "CDNSP-DRD physical base addr: %pa\n", + &res->start); + + /* Interrupt for OTG/DRD. */ + cdnsp->otg_irq = pdev->irq; + } + + if (pci_is_enabled(func)) { + cdnsp->dev = dev; + cdnsp->gadget_init = cdnsp_gadget_init; + + ret = cdns_init(cdnsp); + if (ret) + goto free_cdnsp; + } + + pci_set_drvdata(pdev, cdnsp); + + device_wakeup_enable(&pdev->dev); + if (pci_dev_run_wake(pdev)) + pm_runtime_put_noidle(&pdev->dev); + + return 0; + +free_cdnsp: + if (!pci_is_enabled(func)) + kfree(cdnsp); + +disable_pci: + pci_disable_device(pdev); + +put_pci: + pci_dev_put(func); + + return ret; +} + +static void cdnsp_pci_remove(struct pci_dev *pdev) +{ + struct cdns *cdnsp; + struct pci_dev *func; + + func = cdnsp_get_second_fun(pdev); + cdnsp = (struct cdns *)pci_get_drvdata(pdev); + + if (pci_dev_run_wake(pdev)) + pm_runtime_get_noresume(&pdev->dev); + + if (!pci_is_enabled(func)) { + kfree(cdnsp); + goto pci_put; + } + + cdns_remove(cdnsp); + +pci_put: + pci_dev_put(func); +} + +static int __maybe_unused cdnsp_pci_suspend(struct device *dev) +{ + struct cdns *cdns = dev_get_drvdata(dev); + + return cdns_suspend(cdns); +} + +static int __maybe_unused cdnsp_pci_resume(struct device *dev) +{ + struct cdns *cdns = dev_get_drvdata(dev); + unsigned long flags; + int ret; + + spin_lock_irqsave(&cdns->lock, flags); + ret = cdns_resume(cdns, 1); + spin_unlock_irqrestore(&cdns->lock, flags); + + return ret; +} + +static const struct dev_pm_ops cdnsp_pci_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(cdnsp_pci_suspend, cdnsp_pci_resume) +}; + +static const struct pci_device_id cdnsp_pci_ids[] = { + { PCI_VENDOR_ID_CDNS, CDNS_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, + PCI_CLASS_SERIAL_USB_DEVICE, PCI_ANY_ID }, + { PCI_VENDOR_ID_CDNS, CDNS_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, + CDNS_DRD_IF, PCI_ANY_ID }, + { 0, } +}; + +static struct pci_driver cdnsp_pci_driver = { + .name = "cdnsp-pci", + .id_table = &cdnsp_pci_ids[0], + .probe = cdnsp_pci_probe, + .remove = cdnsp_pci_remove, + .driver = { + .pm = &cdnsp_pci_pm_ops, + } +}; + +module_pci_driver(cdnsp_pci_driver); +MODULE_DEVICE_TABLE(pci, cdnsp_pci_ids); + +MODULE_ALIAS("pci:cdnsp"); +MODULE_AUTHOR("Pawel Laszczak "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Cadence CDNSP PCI driver"); diff --git a/drivers/usb/cdns3/cdnsp-ring.c b/drivers/usb/cdns3/cdnsp-ring.c new file mode 100644 index 000000000000..a28faca41a8f --- /dev/null +++ b/drivers/usb/cdns3/cdnsp-ring.c @@ -0,0 +1,2376 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence CDNSP DRD Driver. + * + * Copyright (C) 2020 Cadence. + * + * Author: Pawel Laszczak + * + * Code based on Linux XHCI driver. + * Origin: Copyright (C) 2008 Intel Corp + */ + +/* + * Ring initialization rules: + * 1. Each segment is initialized to zero, except for link TRBs. + * 2. Ring cycle state = 0. This represents Producer Cycle State (PCS) or + * Consumer Cycle State (CCS), depending on ring function. + * 3. Enqueue pointer = dequeue pointer = address of first TRB in the segment. + * + * Ring behavior rules: + * 1. A ring is empty if enqueue == dequeue. This means there will always be at + * least one free TRB in the ring. This is useful if you want to turn that + * into a link TRB and expand the ring. + * 2. When incrementing an enqueue or dequeue pointer, if the next TRB is a + * link TRB, then load the pointer with the address in the link TRB. If the + * link TRB had its toggle bit set, you may need to update the ring cycle + * state (see cycle bit rules). You may have to do this multiple times + * until you reach a non-link TRB. + * 3. A ring is full if enqueue++ (for the definition of increment above) + * equals the dequeue pointer. + * + * Cycle bit rules: + * 1. When a consumer increments a dequeue pointer and encounters a toggle bit + * in a link TRB, it must toggle the ring cycle state. + * 2. When a producer increments an enqueue pointer and encounters a toggle bit + * in a link TRB, it must toggle the ring cycle state. + * + * Producer rules: + * 1. Check if ring is full before you enqueue. + * 2. Write the ring cycle state to the cycle bit in the TRB you're enqueuing. + * Update enqueue pointer between each write (which may update the ring + * cycle state). + * 3. Notify consumer. If SW is producer, it rings the doorbell for command + * and endpoint rings. If controller is the producer for the event ring, + * and it generates an interrupt according to interrupt modulation rules. + * + * Consumer rules: + * 1. Check if TRB belongs to you. If the cycle bit == your ring cycle state, + * the TRB is owned by the consumer. + * 2. Update dequeue pointer (which may update the ring cycle state) and + * continue processing TRBs until you reach a TRB which is not owned by you. + * 3. Notify the producer. SW is the consumer for the event ring, and it + * updates event ring dequeue pointer. Controller is the consumer for the + * command and endpoint rings; it generates events on the event ring + * for these. + */ + +#include +#include +#include +#include +#include + +#include "cdnsp-gadget.h" + +/* + * Returns zero if the TRB isn't in this segment, otherwise it returns the DMA + * address of the TRB. + */ +dma_addr_t cdnsp_trb_virt_to_dma(struct cdnsp_segment *seg, + union cdnsp_trb *trb) +{ + unsigned long segment_offset = trb - seg->trbs; + + if (trb < seg->trbs || segment_offset >= TRBS_PER_SEGMENT) + return 0; + + return seg->dma + (segment_offset * sizeof(*trb)); +} + +static bool cdnsp_trb_is_noop(union cdnsp_trb *trb) +{ + return TRB_TYPE_NOOP_LE32(trb->generic.field[3]); +} + +static bool cdnsp_trb_is_link(union cdnsp_trb *trb) +{ + return TRB_TYPE_LINK_LE32(trb->link.control); +} + +bool cdnsp_last_trb_on_seg(struct cdnsp_segment *seg, union cdnsp_trb *trb) +{ + return trb == &seg->trbs[TRBS_PER_SEGMENT - 1]; +} + +bool cdnsp_last_trb_on_ring(struct cdnsp_ring *ring, + struct cdnsp_segment *seg, + union cdnsp_trb *trb) +{ + return cdnsp_last_trb_on_seg(seg, trb) && (seg->next == ring->first_seg); +} + +static bool cdnsp_link_trb_toggles_cycle(union cdnsp_trb *trb) +{ + return le32_to_cpu(trb->link.control) & LINK_TOGGLE; +} + +static void cdnsp_trb_to_noop(union cdnsp_trb *trb, u32 noop_type) +{ + if (cdnsp_trb_is_link(trb)) { + /* Unchain chained link TRBs. */ + trb->link.control &= cpu_to_le32(~TRB_CHAIN); + } else { + trb->generic.field[0] = 0; + trb->generic.field[1] = 0; + trb->generic.field[2] = 0; + /* Preserve only the cycle bit of this TRB. */ + trb->generic.field[3] &= cpu_to_le32(TRB_CYCLE); + trb->generic.field[3] |= cpu_to_le32(TRB_TYPE(noop_type)); + } +} + +/* + * Updates trb to point to the next TRB in the ring, and updates seg if the next + * TRB is in a new segment. This does not skip over link TRBs, and it does not + * effect the ring dequeue or enqueue pointers. + */ +static void cdnsp_next_trb(struct cdnsp_device *pdev, + struct cdnsp_ring *ring, + struct cdnsp_segment **seg, + union cdnsp_trb **trb) +{ + if (cdnsp_trb_is_link(*trb)) { + *seg = (*seg)->next; + *trb = ((*seg)->trbs); + } else { + (*trb)++; + } +} + +/* + * See Cycle bit rules. SW is the consumer for the event ring only. + * Don't make a ring full of link TRBs. That would be dumb and this would loop. + */ +void cdnsp_inc_deq(struct cdnsp_device *pdev, struct cdnsp_ring *ring) +{ + /* event ring doesn't have link trbs, check for last trb. */ + if (ring->type == TYPE_EVENT) { + if (!cdnsp_last_trb_on_seg(ring->deq_seg, ring->dequeue)) { + ring->dequeue++; + return; + } + + if (cdnsp_last_trb_on_ring(ring, ring->deq_seg, ring->dequeue)) + ring->cycle_state ^= 1; + + ring->deq_seg = ring->deq_seg->next; + ring->dequeue = ring->deq_seg->trbs; + return; + } + + /* All other rings have link trbs. */ + if (!cdnsp_trb_is_link(ring->dequeue)) { + ring->dequeue++; + ring->num_trbs_free++; + } + while (cdnsp_trb_is_link(ring->dequeue)) { + ring->deq_seg = ring->deq_seg->next; + ring->dequeue = ring->deq_seg->trbs; + } +} + +/* + * See Cycle bit rules. SW is the consumer for the event ring only. + * Don't make a ring full of link TRBs. That would be dumb and this would loop. + * + * If we've just enqueued a TRB that is in the middle of a TD (meaning the + * chain bit is set), then set the chain bit in all the following link TRBs. + * If we've enqueued the last TRB in a TD, make sure the following link TRBs + * have their chain bit cleared (so that each Link TRB is a separate TD). + * + * @more_trbs_coming: Will you enqueue more TRBs before ringing the doorbell. + */ +static void cdnsp_inc_enq(struct cdnsp_device *pdev, + struct cdnsp_ring *ring, + bool more_trbs_coming) +{ + union cdnsp_trb *next; + u32 chain; + + chain = le32_to_cpu(ring->enqueue->generic.field[3]) & TRB_CHAIN; + + /* If this is not event ring, there is one less usable TRB. */ + if (!cdnsp_trb_is_link(ring->enqueue)) + ring->num_trbs_free--; + next = ++(ring->enqueue); + + /* Update the dequeue pointer further if that was a link TRB */ + while (cdnsp_trb_is_link(next)) { + /* + * If the caller doesn't plan on enqueuing more TDs before + * ringing the doorbell, then we don't want to give the link TRB + * to the hardware just yet. We'll give the link TRB back in + * cdnsp_prepare_ring() just before we enqueue the TD at the + * top of the ring. + */ + if (!chain && !more_trbs_coming) + break; + + next->link.control &= cpu_to_le32(~TRB_CHAIN); + next->link.control |= cpu_to_le32(chain); + + /* Give this link TRB to the hardware */ + wmb(); + next->link.control ^= cpu_to_le32(TRB_CYCLE); + + /* Toggle the cycle bit after the last ring segment. */ + if (cdnsp_link_trb_toggles_cycle(next)) + ring->cycle_state ^= 1; + + ring->enq_seg = ring->enq_seg->next; + ring->enqueue = ring->enq_seg->trbs; + next = ring->enqueue; + } +} + +/* + * Check to see if there's room to enqueue num_trbs on the ring and make sure + * enqueue pointer will not advance into dequeue segment. + */ +static bool cdnsp_room_on_ring(struct cdnsp_device *pdev, + struct cdnsp_ring *ring, + unsigned int num_trbs) +{ + int num_trbs_in_deq_seg; + + if (ring->num_trbs_free < num_trbs) + return false; + + if (ring->type != TYPE_COMMAND && ring->type != TYPE_EVENT) { + num_trbs_in_deq_seg = ring->dequeue - ring->deq_seg->trbs; + + if (ring->num_trbs_free < num_trbs + num_trbs_in_deq_seg) + return false; + } + + return true; +} + +/* + * Workaround for L1: controller has issue with resuming from L1 after + * setting doorbell for endpoint during L1 state. This function forces + * resume signal in such case. + */ +static void cdnsp_force_l0_go(struct cdnsp_device *pdev) +{ + if (pdev->active_port == &pdev->usb2_port && pdev->gadget.lpm_capable) + cdnsp_set_link_state(pdev, &pdev->active_port->regs->portsc, XDEV_U0); +} + +/* Ring the doorbell after placing a command on the ring. */ +void cdnsp_ring_cmd_db(struct cdnsp_device *pdev) +{ + writel(DB_VALUE_CMD, &pdev->dba->cmd_db); +} + +/* + * Ring the doorbell after placing a transfer on the ring. + * Returns true if doorbell was set, otherwise false. + */ +static bool cdnsp_ring_ep_doorbell(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + unsigned int stream_id) +{ + __le32 __iomem *reg_addr = &pdev->dba->ep_db; + unsigned int ep_state = pep->ep_state; + unsigned int db_value; + + /* + * Don't ring the doorbell for this endpoint if endpoint is halted or + * disabled. + */ + if (ep_state & EP_HALTED || !(ep_state & EP_ENABLED)) + return false; + + /* For stream capable endpoints driver can ring doorbell only twice. */ + if (pep->ep_state & EP_HAS_STREAMS) { + if (pep->stream_info.drbls_count >= 2) + return false; + + pep->stream_info.drbls_count++; + } + + pep->ep_state &= ~EP_STOPPED; + + if (pep->idx == 0 && pdev->ep0_stage == CDNSP_DATA_STAGE && + !pdev->ep0_expect_in) + db_value = DB_VALUE_EP0_OUT(pep->idx, stream_id); + else + db_value = DB_VALUE(pep->idx, stream_id); + + writel(db_value, reg_addr); + + cdnsp_force_l0_go(pdev); + + /* Doorbell was set. */ + return true; +} + +/* + * Get the right ring for the given pep and stream_id. + * If the endpoint supports streams, boundary check the USB request's stream ID. + * If the endpoint doesn't support streams, return the singular endpoint ring. + */ +static struct cdnsp_ring *cdnsp_get_transfer_ring(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + unsigned int stream_id) +{ + if (!(pep->ep_state & EP_HAS_STREAMS)) + return pep->ring; + + if (stream_id == 0 || stream_id >= pep->stream_info.num_streams) { + dev_err(pdev->dev, "ERR: %s ring doesn't exist for SID: %d.\n", + pep->name, stream_id); + return NULL; + } + + return pep->stream_info.stream_rings[stream_id]; +} + +static struct cdnsp_ring * + cdnsp_request_to_transfer_ring(struct cdnsp_device *pdev, + struct cdnsp_request *preq) +{ + return cdnsp_get_transfer_ring(pdev, preq->pep, + preq->request.stream_id); +} + +/* Ring the doorbell for any rings with pending requests. */ +void cdnsp_ring_doorbell_for_active_rings(struct cdnsp_device *pdev, + struct cdnsp_ep *pep) +{ + struct cdnsp_stream_info *stream_info; + unsigned int stream_id; + int ret; + + if (pep->ep_state & EP_DIS_IN_RROGRESS) + return; + + /* A ring has pending Request if its TD list is not empty. */ + if (!(pep->ep_state & EP_HAS_STREAMS) && pep->number) { + if (pep->ring && !list_empty(&pep->ring->td_list)) + cdnsp_ring_ep_doorbell(pdev, pep, 0); + return; + } + + stream_info = &pep->stream_info; + + for (stream_id = 1; stream_id < stream_info->num_streams; stream_id++) { + struct cdnsp_td *td, *td_temp; + struct cdnsp_ring *ep_ring; + + if (stream_info->drbls_count >= 2) + return; + + ep_ring = cdnsp_get_transfer_ring(pdev, pep, stream_id); + if (!ep_ring) + continue; + + if (!ep_ring->stream_active || ep_ring->stream_rejected) + continue; + + list_for_each_entry_safe(td, td_temp, &ep_ring->td_list, + td_list) { + if (td->drbl) + continue; + + ret = cdnsp_ring_ep_doorbell(pdev, pep, stream_id); + if (ret) + td->drbl = 1; + } + } +} + +/* + * Get the hw dequeue pointer controller stopped on, either directly from the + * endpoint context, or if streams are in use from the stream context. + * The returned hw_dequeue contains the lowest four bits with cycle state + * and possible stream context type. + */ +static u64 cdnsp_get_hw_deq(struct cdnsp_device *pdev, + unsigned int ep_index, + unsigned int stream_id) +{ + struct cdnsp_stream_ctx *st_ctx; + struct cdnsp_ep *pep; + + pep = &pdev->eps[stream_id]; + + if (pep->ep_state & EP_HAS_STREAMS) { + st_ctx = &pep->stream_info.stream_ctx_array[stream_id]; + return le64_to_cpu(st_ctx->stream_ring); + } + + return le64_to_cpu(pep->out_ctx->deq); +} + +/* + * Move the controller endpoint ring dequeue pointer past cur_td. + * Record the new state of the controller endpoint ring dequeue segment, + * dequeue pointer, and new consumer cycle state in state. + * Update internal representation of the ring's dequeue pointer. + * + * We do this in three jumps: + * - First we update our new ring state to be the same as when the + * controller stopped. + * - Then we traverse the ring to find the segment that contains + * the last TRB in the TD. We toggle the controller new cycle state + * when we pass any link TRBs with the toggle cycle bit set. + * - Finally we move the dequeue state one TRB further, toggling the cycle bit + * if we've moved it past a link TRB with the toggle cycle bit set. + */ +static void cdnsp_find_new_dequeue_state(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + unsigned int stream_id, + struct cdnsp_td *cur_td, + struct cdnsp_dequeue_state *state) +{ + bool td_last_trb_found = false; + struct cdnsp_segment *new_seg; + struct cdnsp_ring *ep_ring; + union cdnsp_trb *new_deq; + bool cycle_found = false; + u64 hw_dequeue; + + ep_ring = cdnsp_get_transfer_ring(pdev, pep, stream_id); + if (!ep_ring) + return; + + /* + * Dig out the cycle state saved by the controller during the + * stop endpoint command. + */ + hw_dequeue = cdnsp_get_hw_deq(pdev, pep->idx, stream_id); + new_seg = ep_ring->deq_seg; + new_deq = ep_ring->dequeue; + state->new_cycle_state = hw_dequeue & 0x1; + state->stream_id = stream_id; + + /* + * We want to find the pointer, segment and cycle state of the new trb + * (the one after current TD's last_trb). We know the cycle state at + * hw_dequeue, so walk the ring until both hw_dequeue and last_trb are + * found. + */ + do { + if (!cycle_found && cdnsp_trb_virt_to_dma(new_seg, new_deq) + == (dma_addr_t)(hw_dequeue & ~0xf)) { + cycle_found = true; + + if (td_last_trb_found) + break; + } + + if (new_deq == cur_td->last_trb) + td_last_trb_found = true; + + if (cycle_found && cdnsp_trb_is_link(new_deq) && + cdnsp_link_trb_toggles_cycle(new_deq)) + state->new_cycle_state ^= 0x1; + + cdnsp_next_trb(pdev, ep_ring, &new_seg, &new_deq); + + /* Search wrapped around, bail out. */ + if (new_deq == pep->ring->dequeue) { + dev_err(pdev->dev, + "Error: Failed finding new dequeue state\n"); + state->new_deq_seg = NULL; + state->new_deq_ptr = NULL; + return; + } + + } while (!cycle_found || !td_last_trb_found); + + state->new_deq_seg = new_seg; + state->new_deq_ptr = new_deq; +} + +/* + * flip_cycle means flip the cycle bit of all but the first and last TRB. + * (The last TRB actually points to the ring enqueue pointer, which is not part + * of this TD.) This is used to remove partially enqueued isoc TDs from a ring. + */ +static void cdnsp_td_to_noop(struct cdnsp_device *pdev, + struct cdnsp_ring *ep_ring, + struct cdnsp_td *td, + bool flip_cycle) +{ + struct cdnsp_segment *seg = td->start_seg; + union cdnsp_trb *trb = td->first_trb; + + while (1) { + cdnsp_trb_to_noop(trb, TRB_TR_NOOP); + + /* flip cycle if asked to */ + if (flip_cycle && trb != td->first_trb && trb != td->last_trb) + trb->generic.field[3] ^= cpu_to_le32(TRB_CYCLE); + + if (trb == td->last_trb) + break; + + cdnsp_next_trb(pdev, ep_ring, &seg, &trb); + } +} + +/* + * This TD is defined by the TRBs starting at start_trb in start_seg and ending + * at end_trb, which may be in another segment. If the suspect DMA address is a + * TRB in this TD, this function returns that TRB's segment. Otherwise it + * returns 0. + */ +static struct cdnsp_segment *cdnsp_trb_in_td(struct cdnsp_device *pdev, + struct cdnsp_segment *start_seg, + union cdnsp_trb *start_trb, + union cdnsp_trb *end_trb, + dma_addr_t suspect_dma) +{ + struct cdnsp_segment *cur_seg; + union cdnsp_trb *temp_trb; + dma_addr_t end_seg_dma; + dma_addr_t end_trb_dma; + dma_addr_t start_dma; + + start_dma = cdnsp_trb_virt_to_dma(start_seg, start_trb); + cur_seg = start_seg; + + do { + if (start_dma == 0) + return NULL; + + temp_trb = &cur_seg->trbs[TRBS_PER_SEGMENT - 1]; + /* We may get an event for a Link TRB in the middle of a TD */ + end_seg_dma = cdnsp_trb_virt_to_dma(cur_seg, temp_trb); + /* If the end TRB isn't in this segment, this is set to 0 */ + end_trb_dma = cdnsp_trb_virt_to_dma(cur_seg, end_trb); + + if (end_trb_dma > 0) { + /* + * The end TRB is in this segment, so suspect should + * be here + */ + if (start_dma <= end_trb_dma) { + if (suspect_dma >= start_dma && + suspect_dma <= end_trb_dma) { + return cur_seg; + } + } else { + /* + * Case for one segment with a + * TD wrapped around to the top + */ + if ((suspect_dma >= start_dma && + suspect_dma <= end_seg_dma) || + (suspect_dma >= cur_seg->dma && + suspect_dma <= end_trb_dma)) { + return cur_seg; + } + } + + return NULL; + } + + /* Might still be somewhere in this segment */ + if (suspect_dma >= start_dma && suspect_dma <= end_seg_dma) + return cur_seg; + + cur_seg = cur_seg->next; + start_dma = cdnsp_trb_virt_to_dma(cur_seg, &cur_seg->trbs[0]); + } while (cur_seg != start_seg); + + return NULL; +} + +static void cdnsp_unmap_td_bounce_buffer(struct cdnsp_device *pdev, + struct cdnsp_ring *ring, + struct cdnsp_td *td) +{ + struct cdnsp_segment *seg = td->bounce_seg; + struct cdnsp_request *preq; + size_t len; + + if (!seg) + return; + + preq = td->preq; + + if (!preq->direction) { + dma_unmap_single(pdev->dev, seg->bounce_dma, + ring->bounce_buf_len, DMA_TO_DEVICE); + return; + } + + dma_unmap_single(pdev->dev, seg->bounce_dma, ring->bounce_buf_len, + DMA_FROM_DEVICE); + + /* For in transfers we need to copy the data from bounce to sg */ + len = sg_pcopy_from_buffer(preq->request.sg, preq->request.num_sgs, + seg->bounce_buf, seg->bounce_len, + seg->bounce_offs); + if (len != seg->bounce_len) + dev_warn(pdev->dev, "WARN Wrong bounce buffer read length: %zu != %d\n", + len, seg->bounce_len); + + seg->bounce_len = 0; + seg->bounce_offs = 0; +} + +static int cdnsp_cmd_set_deq(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + struct cdnsp_dequeue_state *deq_state) +{ + struct cdnsp_ring *ep_ring; + int ret; + + if (!deq_state->new_deq_ptr || !deq_state->new_deq_seg) { + cdnsp_ring_doorbell_for_active_rings(pdev, pep); + return 0; + } + + cdnsp_queue_new_dequeue_state(pdev, pep, deq_state); + cdnsp_ring_cmd_db(pdev); + ret = cdnsp_wait_for_cmd_compl(pdev); + + /* + * Update the ring's dequeue segment and dequeue pointer + * to reflect the new position. + */ + ep_ring = cdnsp_get_transfer_ring(pdev, pep, deq_state->stream_id); + + if (cdnsp_trb_is_link(ep_ring->dequeue)) { + ep_ring->deq_seg = ep_ring->deq_seg->next; + ep_ring->dequeue = ep_ring->deq_seg->trbs; + } + + while (ep_ring->dequeue != deq_state->new_deq_ptr) { + ep_ring->num_trbs_free++; + ep_ring->dequeue++; + + if (cdnsp_trb_is_link(ep_ring->dequeue)) { + if (ep_ring->dequeue == deq_state->new_deq_ptr) + break; + + ep_ring->deq_seg = ep_ring->deq_seg->next; + ep_ring->dequeue = ep_ring->deq_seg->trbs; + } + } + + /* + * Probably there was TIMEOUT during handling Set Dequeue Pointer + * command. It's critical error and controller will be stopped. + */ + if (ret) + return -ESHUTDOWN; + + /* Restart any rings with pending requests */ + cdnsp_ring_doorbell_for_active_rings(pdev, pep); + + return 0; +} + +int cdnsp_remove_request(struct cdnsp_device *pdev, + struct cdnsp_request *preq, + struct cdnsp_ep *pep) +{ + struct cdnsp_dequeue_state deq_state; + struct cdnsp_td *cur_td = NULL; + struct cdnsp_ring *ep_ring; + struct cdnsp_segment *seg; + int status = -ECONNRESET; + int ret = 0; + u64 hw_deq; + + memset(&deq_state, 0, sizeof(deq_state)); + + cur_td = &preq->td; + ep_ring = cdnsp_request_to_transfer_ring(pdev, preq); + + /* + * If we stopped on the TD we need to cancel, then we have to + * move the controller endpoint ring dequeue pointer past + * this TD. + */ + hw_deq = cdnsp_get_hw_deq(pdev, pep->idx, preq->request.stream_id); + hw_deq &= ~0xf; + + seg = cdnsp_trb_in_td(pdev, cur_td->start_seg, cur_td->first_trb, + cur_td->last_trb, hw_deq); + + if (seg && (pep->ep_state & EP_ENABLED)) + cdnsp_find_new_dequeue_state(pdev, pep, preq->request.stream_id, + cur_td, &deq_state); + else + cdnsp_td_to_noop(pdev, ep_ring, cur_td, false); + + /* + * The event handler won't see a completion for this TD anymore, + * so remove it from the endpoint ring's TD list. + */ + list_del_init(&cur_td->td_list); + ep_ring->num_tds--; + pep->stream_info.td_count--; + + /* + * During disconnecting all endpoint will be disabled so we don't + * have to worry about updating dequeue pointer. + */ + if (pdev->cdnsp_state & CDNSP_STATE_DISCONNECT_PENDING) { + status = -ESHUTDOWN; + ret = cdnsp_cmd_set_deq(pdev, pep, &deq_state); + } + + cdnsp_unmap_td_bounce_buffer(pdev, ep_ring, cur_td); + cdnsp_gadget_giveback(pep, cur_td->preq, status); + + return ret; +} + +static int cdnsp_update_port_id(struct cdnsp_device *pdev, u32 port_id) +{ + struct cdnsp_port *port = pdev->active_port; + u8 old_port = 0; + + if (port && port->port_num == port_id) + return 0; + + if (port) + old_port = port->port_num; + + if (port_id == pdev->usb2_port.port_num) { + port = &pdev->usb2_port; + } else if (port_id == pdev->usb3_port.port_num) { + port = &pdev->usb3_port; + } else { + dev_err(pdev->dev, "Port event with invalid port ID %d\n", + port_id); + return -EINVAL; + } + + if (port_id != old_port) { + cdnsp_disable_slot(pdev); + pdev->active_port = port; + cdnsp_enable_slot(pdev); + } + + if (port_id == pdev->usb2_port.port_num) + cdnsp_set_usb2_hardware_lpm(pdev, NULL, 1); + else + writel(PORT_U1_TIMEOUT(1) | PORT_U2_TIMEOUT(1), + &pdev->usb3_port.regs->portpmsc); + + return 0; +} + +static void cdnsp_handle_port_status(struct cdnsp_device *pdev, + union cdnsp_trb *event) +{ + struct cdnsp_port_regs __iomem *port_regs; + u32 portsc, cmd_regs; + bool port2 = false; + u32 link_state; + u32 port_id; + + /* Port status change events always have a successful completion code */ + if (GET_COMP_CODE(le32_to_cpu(event->generic.field[2])) != COMP_SUCCESS) + dev_err(pdev->dev, "ERR: incorrect PSC event\n"); + + port_id = GET_PORT_ID(le32_to_cpu(event->generic.field[0])); + + if (cdnsp_update_port_id(pdev, port_id)) + goto cleanup; + + port_regs = pdev->active_port->regs; + + if (port_id == pdev->usb2_port.port_num) + port2 = true; + +new_event: + portsc = readl(&port_regs->portsc); + writel(cdnsp_port_state_to_neutral(portsc) | + (portsc & PORT_CHANGE_BITS), &port_regs->portsc); + + pdev->gadget.speed = cdnsp_port_speed(portsc); + link_state = portsc & PORT_PLS_MASK; + + /* Port Link State change detected. */ + if ((portsc & PORT_PLC)) { + if (!(pdev->cdnsp_state & CDNSP_WAKEUP_PENDING) && + link_state == XDEV_RESUME) { + cmd_regs = readl(&pdev->op_regs->command); + if (!(cmd_regs & CMD_R_S)) + goto cleanup; + + if (DEV_SUPERSPEED_ANY(portsc)) { + cdnsp_set_link_state(pdev, &port_regs->portsc, + XDEV_U0); + + cdnsp_resume_gadget(pdev); + } + } + + if ((pdev->cdnsp_state & CDNSP_WAKEUP_PENDING) && + link_state == XDEV_U0) { + pdev->cdnsp_state &= ~CDNSP_WAKEUP_PENDING; + + cdnsp_force_header_wakeup(pdev, 1); + cdnsp_ring_cmd_db(pdev); + cdnsp_wait_for_cmd_compl(pdev); + } + + if (link_state == XDEV_U0 && pdev->link_state == XDEV_U3 && + !DEV_SUPERSPEED_ANY(portsc)) + cdnsp_resume_gadget(pdev); + + if (link_state == XDEV_U3 && pdev->link_state != XDEV_U3) + cdnsp_suspend_gadget(pdev); + + pdev->link_state = link_state; + } + + if (portsc & PORT_CSC) { + /* Detach device. */ + if (pdev->gadget.connected && !(portsc & PORT_CONNECT)) + cdnsp_disconnect_gadget(pdev); + + /* Attach device. */ + if (portsc & PORT_CONNECT) { + if (!port2) + cdnsp_irq_reset(pdev); + + usb_gadget_set_state(&pdev->gadget, USB_STATE_ATTACHED); + } + } + + /* Port reset. */ + if ((portsc & (PORT_RC | PORT_WRC)) && (portsc & PORT_CONNECT)) { + cdnsp_irq_reset(pdev); + pdev->u1_allowed = 0; + pdev->u2_allowed = 0; + pdev->may_wakeup = 0; + } + + if (portsc & PORT_CEC) + dev_err(pdev->dev, "Port Over Current detected\n"); + + if (portsc & PORT_CEC) + dev_err(pdev->dev, "Port Configure Error detected\n"); + + if (readl(&port_regs->portsc) & PORT_CHANGE_BITS) + goto new_event; + +cleanup: + cdnsp_inc_deq(pdev, pdev->event_ring); +} + +static void cdnsp_td_cleanup(struct cdnsp_device *pdev, + struct cdnsp_td *td, + struct cdnsp_ring *ep_ring, + int *status) +{ + struct cdnsp_request *preq = td->preq; + + /* if a bounce buffer was used to align this td then unmap it */ + cdnsp_unmap_td_bounce_buffer(pdev, ep_ring, td); + + /* + * If the controller said we transferred more data than the buffer + * length, Play it safe and say we didn't transfer anything. + */ + if (preq->request.actual > preq->request.length) { + preq->request.actual = 0; + *status = 0; + } + + list_del_init(&td->td_list); + ep_ring->num_tds--; + preq->pep->stream_info.td_count--; + + cdnsp_gadget_giveback(preq->pep, preq, *status); +} + +static void cdnsp_finish_td(struct cdnsp_device *pdev, + struct cdnsp_td *td, + struct cdnsp_transfer_event *event, + struct cdnsp_ep *ep, + int *status) +{ + struct cdnsp_ring *ep_ring; + u32 trb_comp_code; + + ep_ring = cdnsp_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer)); + trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); + + if (trb_comp_code == COMP_STOPPED_LENGTH_INVALID || + trb_comp_code == COMP_STOPPED || + trb_comp_code == COMP_STOPPED_SHORT_PACKET) { + /* + * The Endpoint Stop Command completion will take care of any + * stopped TDs. A stopped TD may be restarted, so don't update + * the ring dequeue pointer or take this TD off any lists yet. + */ + return; + } + + /* Update ring dequeue pointer */ + while (ep_ring->dequeue != td->last_trb) + cdnsp_inc_deq(pdev, ep_ring); + + cdnsp_inc_deq(pdev, ep_ring); + + cdnsp_td_cleanup(pdev, td, ep_ring, status); +} + +/* sum trb lengths from ring dequeue up to stop_trb, _excluding_ stop_trb */ +static int cdnsp_sum_trb_lengths(struct cdnsp_device *pdev, + struct cdnsp_ring *ring, + union cdnsp_trb *stop_trb) +{ + struct cdnsp_segment *seg = ring->deq_seg; + union cdnsp_trb *trb = ring->dequeue; + u32 sum; + + for (sum = 0; trb != stop_trb; cdnsp_next_trb(pdev, ring, &seg, &trb)) { + if (!cdnsp_trb_is_noop(trb) && !cdnsp_trb_is_link(trb)) + sum += TRB_LEN(le32_to_cpu(trb->generic.field[2])); + } + return sum; +} + +static int cdnsp_giveback_first_trb(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + unsigned int stream_id, + int start_cycle, + struct cdnsp_generic_trb *start_trb) +{ + /* + * Pass all the TRBs to the hardware at once and make sure this write + * isn't reordered. + */ + wmb(); + + if (start_cycle) + start_trb->field[3] |= cpu_to_le32(start_cycle); + else + start_trb->field[3] &= cpu_to_le32(~TRB_CYCLE); + + if ((pep->ep_state & EP_HAS_STREAMS) && + !pep->stream_info.first_prime_det) + return 0; + + return cdnsp_ring_ep_doorbell(pdev, pep, stream_id); +} + +/* + * Process control tds, update USB request status and actual_length. + */ +static void cdnsp_process_ctrl_td(struct cdnsp_device *pdev, + struct cdnsp_td *td, + union cdnsp_trb *event_trb, + struct cdnsp_transfer_event *event, + struct cdnsp_ep *pep, + int *status) +{ + struct cdnsp_ring *ep_ring; + u32 remaining; + u32 trb_type; + + trb_type = TRB_FIELD_TO_TYPE(le32_to_cpu(event_trb->generic.field[3])); + ep_ring = cdnsp_dma_to_transfer_ring(pep, le64_to_cpu(event->buffer)); + remaining = EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); + + /* + * if on data stage then update the actual_length of the USB + * request and flag it as set, so it won't be overwritten in the event + * for the last TRB. + */ + if (trb_type == TRB_DATA) { + td->request_length_set = true; + td->preq->request.actual = td->preq->request.length - remaining; + } + + /* at status stage */ + if (!td->request_length_set) + td->preq->request.actual = td->preq->request.length; + + if (pdev->ep0_stage == CDNSP_DATA_STAGE && pep->number == 0 && + pdev->three_stage_setup) { + td = list_entry(ep_ring->td_list.next, struct cdnsp_td, + td_list); + pdev->ep0_stage = CDNSP_STATUS_STAGE; + + cdnsp_giveback_first_trb(pdev, pep, 0, ep_ring->cycle_state, + &td->last_trb->generic); + return; + } + + cdnsp_finish_td(pdev, td, event, pep, status); +} + +/* + * Process isochronous tds, update usb request status and actual_length. + */ +static void cdnsp_process_isoc_td(struct cdnsp_device *pdev, + struct cdnsp_td *td, + union cdnsp_trb *ep_trb, + struct cdnsp_transfer_event *event, + struct cdnsp_ep *pep, + int status) +{ + struct cdnsp_request *preq = td->preq; + u32 remaining, requested, ep_trb_len; + bool sum_trbs_for_length = false; + struct cdnsp_ring *ep_ring; + u32 trb_comp_code; + u32 td_length; + + ep_ring = cdnsp_dma_to_transfer_ring(pep, le64_to_cpu(event->buffer)); + trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); + remaining = EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); + ep_trb_len = TRB_LEN(le32_to_cpu(ep_trb->generic.field[2])); + + requested = preq->request.length; + + /* handle completion code */ + switch (trb_comp_code) { + case COMP_SUCCESS: + preq->request.status = 0; + break; + case COMP_SHORT_PACKET: + preq->request.status = 0; + sum_trbs_for_length = true; + break; + case COMP_ISOCH_BUFFER_OVERRUN: + case COMP_BABBLE_DETECTED_ERROR: + preq->request.status = -EOVERFLOW; + break; + case COMP_STOPPED: + sum_trbs_for_length = true; + break; + case COMP_STOPPED_SHORT_PACKET: + /* field normally containing residue now contains transferred */ + preq->request.status = 0; + requested = remaining; + break; + case COMP_STOPPED_LENGTH_INVALID: + requested = 0; + remaining = 0; + break; + default: + sum_trbs_for_length = true; + preq->request.status = -1; + break; + } + + if (sum_trbs_for_length) { + td_length = cdnsp_sum_trb_lengths(pdev, ep_ring, ep_trb); + td_length += ep_trb_len - remaining; + } else { + td_length = requested; + } + + td->preq->request.actual += td_length; + + cdnsp_finish_td(pdev, td, event, pep, &status); +} + +static void cdnsp_skip_isoc_td(struct cdnsp_device *pdev, + struct cdnsp_td *td, + struct cdnsp_transfer_event *event, + struct cdnsp_ep *pep, + int status) +{ + struct cdnsp_ring *ep_ring; + + ep_ring = cdnsp_dma_to_transfer_ring(pep, le64_to_cpu(event->buffer)); + td->preq->request.status = -EXDEV; + td->preq->request.actual = 0; + + /* Update ring dequeue pointer */ + while (ep_ring->dequeue != td->last_trb) + cdnsp_inc_deq(pdev, ep_ring); + + cdnsp_inc_deq(pdev, ep_ring); + + cdnsp_td_cleanup(pdev, td, ep_ring, &status); +} + +/* + * Process bulk and interrupt tds, update usb request status and actual_length. + */ +static void cdnsp_process_bulk_intr_td(struct cdnsp_device *pdev, + struct cdnsp_td *td, + union cdnsp_trb *ep_trb, + struct cdnsp_transfer_event *event, + struct cdnsp_ep *ep, + int *status) +{ + u32 remaining, requested, ep_trb_len; + struct cdnsp_ring *ep_ring; + u32 trb_comp_code; + + ep_ring = cdnsp_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer)); + trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); + remaining = EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); + ep_trb_len = TRB_LEN(le32_to_cpu(ep_trb->generic.field[2])); + requested = td->preq->request.length; + + switch (trb_comp_code) { + case COMP_SUCCESS: + case COMP_SHORT_PACKET: + *status = 0; + break; + case COMP_STOPPED_SHORT_PACKET: + td->preq->request.actual = remaining; + goto finish_td; + case COMP_STOPPED_LENGTH_INVALID: + /* Stopped on ep trb with invalid length, exclude it. */ + ep_trb_len = 0; + remaining = 0; + break; + } + + if (ep_trb == td->last_trb) + ep_trb_len = requested - remaining; + else + ep_trb_len = cdnsp_sum_trb_lengths(pdev, ep_ring, ep_trb) + + ep_trb_len - remaining; + td->preq->request.actual = ep_trb_len; + +finish_td: + ep->stream_info.drbls_count--; + + cdnsp_finish_td(pdev, td, event, ep, status); +} + +static void cdnsp_handle_tx_nrdy(struct cdnsp_device *pdev, + struct cdnsp_transfer_event *event) +{ + struct cdnsp_generic_trb *generic; + struct cdnsp_ring *ep_ring; + struct cdnsp_ep *pep; + int cur_stream; + int ep_index; + int host_sid; + int dev_sid; + + generic = (struct cdnsp_generic_trb *)event; + ep_index = TRB_TO_EP_ID(le32_to_cpu(event->flags)) - 1; + dev_sid = TRB_TO_DEV_STREAM(le32_to_cpu(generic->field[0])); + host_sid = TRB_TO_HOST_STREAM(le32_to_cpu(generic->field[2])); + + pep = &pdev->eps[ep_index]; + + if (!(pep->ep_state & EP_HAS_STREAMS)) + return; + + if (host_sid == STREAM_PRIME_ACK) { + pep->stream_info.first_prime_det = 1; + for (cur_stream = 1; cur_stream < pep->stream_info.num_streams; + cur_stream++) { + ep_ring = pep->stream_info.stream_rings[cur_stream]; + ep_ring->stream_active = 1; + ep_ring->stream_rejected = 0; + } + } + + if (host_sid == STREAM_REJECTED) { + struct cdnsp_td *td, *td_temp; + + pep->stream_info.drbls_count--; + ep_ring = pep->stream_info.stream_rings[dev_sid]; + ep_ring->stream_active = 0; + ep_ring->stream_rejected = 1; + + list_for_each_entry_safe(td, td_temp, &ep_ring->td_list, + td_list) { + td->drbl = 0; + } + } + + cdnsp_ring_doorbell_for_active_rings(pdev, pep); +} + +/* + * If this function returns an error condition, it means it got a Transfer + * event with a corrupted TRB DMA address or endpoint is disabled. + */ +static int cdnsp_handle_tx_event(struct cdnsp_device *pdev, + struct cdnsp_transfer_event *event) +{ + const struct usb_endpoint_descriptor *desc; + bool handling_skipped_tds = false; + struct cdnsp_segment *ep_seg; + struct cdnsp_ring *ep_ring; + int status = -EINPROGRESS; + union cdnsp_trb *ep_trb; + dma_addr_t ep_trb_dma; + struct cdnsp_ep *pep; + struct cdnsp_td *td; + u32 trb_comp_code; + int invalidate; + int ep_index; + + invalidate = le32_to_cpu(event->flags) & TRB_EVENT_INVALIDATE; + ep_index = TRB_TO_EP_ID(le32_to_cpu(event->flags)) - 1; + trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); + ep_trb_dma = le64_to_cpu(event->buffer); + + pep = &pdev->eps[ep_index]; + ep_ring = cdnsp_dma_to_transfer_ring(pep, le64_to_cpu(event->buffer)); + + /* + * If device is disconnect then all requests will be dequeued + * by upper layers as part of disconnect sequence. + * We don't want handle such event to avoid racing. + */ + if (invalidate || !pdev->gadget.connected) + goto cleanup; + + if (GET_EP_CTX_STATE(pep->out_ctx) == EP_STATE_DISABLED) + goto err_out; + + /* Some transfer events don't always point to a trb*/ + if (!ep_ring) { + switch (trb_comp_code) { + case COMP_INVALID_STREAM_TYPE_ERROR: + case COMP_INVALID_STREAM_ID_ERROR: + case COMP_RING_UNDERRUN: + case COMP_RING_OVERRUN: + goto cleanup; + default: + dev_err(pdev->dev, "ERROR: %s event for unknown ring\n", + pep->name); + goto err_out; + } + } + + /* Look for some error cases that need special treatment. */ + switch (trb_comp_code) { + case COMP_BABBLE_DETECTED_ERROR: + status = -EOVERFLOW; + break; + case COMP_RING_UNDERRUN: + case COMP_RING_OVERRUN: + /* + * When the Isoch ring is empty, the controller will generate + * a Ring Overrun Event for IN Isoch endpoint or Ring + * Underrun Event for OUT Isoch endpoint. + */ + goto cleanup; + case COMP_MISSED_SERVICE_ERROR: + /* + * When encounter missed service error, one or more isoc tds + * may be missed by controller. + * Set skip flag of the ep_ring; Complete the missed tds as + * short transfer when process the ep_ring next time. + */ + pep->skip = true; + break; + } + + do { + /* + * This TRB should be in the TD at the head of this ring's TD + * list. + */ + if (list_empty(&ep_ring->td_list)) { + if (pep->skip) + pep->skip = false; + + goto cleanup; + } + + td = list_entry(ep_ring->td_list.next, struct cdnsp_td, + td_list); + + /* Is this a TRB in the currently executing TD? */ + ep_seg = cdnsp_trb_in_td(pdev, ep_ring->deq_seg, + ep_ring->dequeue, td->last_trb, + ep_trb_dma); + + /* + * Skip the Force Stopped Event. The event_trb(ep_trb_dma) + * of FSE is not in the current TD pointed by ep_ring->dequeue + * because that the hardware dequeue pointer still at the + * previous TRB of the current TD. The previous TRB maybe a + * Link TD or the last TRB of the previous TD. The command + * completion handle will take care the rest. + */ + if (!ep_seg && (trb_comp_code == COMP_STOPPED || + trb_comp_code == COMP_STOPPED_LENGTH_INVALID)) { + pep->skip = false; + goto cleanup; + } + + desc = td->preq->pep->endpoint.desc; + if (!ep_seg) { + if (!pep->skip || !usb_endpoint_xfer_isoc(desc)) { + /* Something is busted, give up! */ + dev_err(pdev->dev, + "ERROR Transfer event TRB DMA ptr not " + "part of current TD ep_index %d " + "comp_code %u\n", ep_index, + trb_comp_code); + return -EINVAL; + } + + cdnsp_skip_isoc_td(pdev, td, event, pep, status); + goto cleanup; + } + + if (trb_comp_code == COMP_SHORT_PACKET) + ep_ring->last_td_was_short = true; + else + ep_ring->last_td_was_short = false; + + if (pep->skip) { + pep->skip = false; + cdnsp_skip_isoc_td(pdev, td, event, pep, status); + goto cleanup; + } + + ep_trb = &ep_seg->trbs[(ep_trb_dma - ep_seg->dma) + / sizeof(*ep_trb)]; + + if (cdnsp_trb_is_noop(ep_trb)) + goto cleanup; + + if (usb_endpoint_xfer_control(desc)) + cdnsp_process_ctrl_td(pdev, td, ep_trb, event, pep, + &status); + else if (usb_endpoint_xfer_isoc(desc)) + cdnsp_process_isoc_td(pdev, td, ep_trb, event, pep, + status); + else + cdnsp_process_bulk_intr_td(pdev, td, ep_trb, event, pep, + &status); +cleanup: + handling_skipped_tds = pep->skip; + + /* + * Do not update event ring dequeue pointer if we're in a loop + * processing missed tds. + */ + if (!handling_skipped_tds) + cdnsp_inc_deq(pdev, pdev->event_ring); + + /* + * If ep->skip is set, it means there are missed tds on the + * endpoint ring need to take care of. + * Process them as short transfer until reach the td pointed by + * the event. + */ + } while (handling_skipped_tds); + return 0; + +err_out: + dev_err(pdev->dev, "@%016llx %08x %08x %08x %08x\n", + (unsigned long long) + cdnsp_trb_virt_to_dma(pdev->event_ring->deq_seg, + pdev->event_ring->dequeue), + lower_32_bits(le64_to_cpu(event->buffer)), + upper_32_bits(le64_to_cpu(event->buffer)), + le32_to_cpu(event->transfer_len), + le32_to_cpu(event->flags)); + return -EINVAL; +} + +/* + * This function handles all events on the event ring. + * Returns true for "possibly more events to process" (caller should call + * again), otherwise false if done. + */ +static bool cdnsp_handle_event(struct cdnsp_device *pdev) +{ + unsigned int comp_code; + union cdnsp_trb *event; + bool update_ptrs = true; + __le32 cycle_bit; + int ret = 0; + u32 flags; + + event = pdev->event_ring->dequeue; + flags = le32_to_cpu(event->event_cmd.flags); + cycle_bit = (flags & TRB_CYCLE); + + /* Does the controller or driver own the TRB? */ + if (cycle_bit != pdev->event_ring->cycle_state) + return false; + + /* + * Barrier between reading the TRB_CYCLE (valid) flag above and any + * reads of the event's flags/data below. + */ + rmb(); + + switch (flags & TRB_TYPE_BITMASK) { + case TRB_TYPE(TRB_COMPLETION): + /* + * Command can't be handled in interrupt context so just + * increment command ring dequeue pointer. + */ + cdnsp_inc_deq(pdev, pdev->cmd_ring); + break; + case TRB_TYPE(TRB_PORT_STATUS): + cdnsp_handle_port_status(pdev, event); + update_ptrs = false; + break; + case TRB_TYPE(TRB_TRANSFER): + ret = cdnsp_handle_tx_event(pdev, &event->trans_event); + if (ret >= 0) + update_ptrs = false; + break; + case TRB_TYPE(TRB_SETUP): + pdev->ep0_stage = CDNSP_SETUP_STAGE; + pdev->setup_id = TRB_SETUPID_TO_TYPE(flags); + pdev->setup_speed = TRB_SETUP_SPEEDID(flags); + pdev->setup = *((struct usb_ctrlrequest *) + &event->trans_event.buffer); + + cdnsp_setup_analyze(pdev); + break; + case TRB_TYPE(TRB_ENDPOINT_NRDY): + cdnsp_handle_tx_nrdy(pdev, &event->trans_event); + break; + case TRB_TYPE(TRB_HC_EVENT): { + comp_code = GET_COMP_CODE(le32_to_cpu(event->generic.field[2])); + + switch (comp_code) { + case COMP_EVENT_RING_FULL_ERROR: + dev_err(pdev->dev, "Event Ring Full\n"); + break; + default: + dev_err(pdev->dev, "Controller error code 0x%02x\n", + comp_code); + } + + break; + } + case TRB_TYPE(TRB_MFINDEX_WRAP): + case TRB_TYPE(TRB_DRB_OVERFLOW): + break; + default: + dev_warn(pdev->dev, "ERROR unknown event type %ld\n", + TRB_FIELD_TO_TYPE(flags)); + } + + if (update_ptrs) + /* Update SW event ring dequeue pointer. */ + cdnsp_inc_deq(pdev, pdev->event_ring); + + /* + * Caller will call us again to check if there are more items + * on the event ring. + */ + return true; +} + +irqreturn_t cdnsp_thread_irq_handler(int irq, void *data) +{ + struct cdnsp_device *pdev = (struct cdnsp_device *)data; + union cdnsp_trb *event_ring_deq; + int counter = 0; + + spin_lock(&pdev->lock); + + if (pdev->cdnsp_state & (CDNSP_STATE_HALTED | CDNSP_STATE_DYING)) { + cdnsp_died(pdev); + spin_unlock(&pdev->lock); + return IRQ_HANDLED; + } + + event_ring_deq = pdev->event_ring->dequeue; + + while (cdnsp_handle_event(pdev)) { + if (++counter >= TRBS_PER_EV_DEQ_UPDATE) { + cdnsp_update_erst_dequeue(pdev, event_ring_deq, 0); + event_ring_deq = pdev->event_ring->dequeue; + counter = 0; + } + } + + cdnsp_update_erst_dequeue(pdev, event_ring_deq, 1); + + spin_unlock(&pdev->lock); + + return IRQ_HANDLED; +} + +irqreturn_t cdnsp_irq_handler(int irq, void *priv) +{ + struct cdnsp_device *pdev = (struct cdnsp_device *)priv; + u32 irq_pending; + u32 status; + + status = readl(&pdev->op_regs->status); + + if (status == ~(u32)0) { + cdnsp_died(pdev); + return IRQ_HANDLED; + } + + if (!(status & STS_EINT)) + return IRQ_NONE; + + writel(status | STS_EINT, &pdev->op_regs->status); + irq_pending = readl(&pdev->ir_set->irq_pending); + irq_pending |= IMAN_IP; + writel(irq_pending, &pdev->ir_set->irq_pending); + + if (status & STS_FATAL) { + cdnsp_died(pdev); + return IRQ_HANDLED; + } + + return IRQ_WAKE_THREAD; +} + +/* + * Generic function for queuing a TRB on a ring. + * The caller must have checked to make sure there's room on the ring. + * + * @more_trbs_coming: Will you enqueue more TRBs before setting doorbell? + */ +static void cdnsp_queue_trb(struct cdnsp_device *pdev, struct cdnsp_ring *ring, + bool more_trbs_coming, u32 field1, u32 field2, + u32 field3, u32 field4) +{ + struct cdnsp_generic_trb *trb; + + trb = &ring->enqueue->generic; + + trb->field[0] = cpu_to_le32(field1); + trb->field[1] = cpu_to_le32(field2); + trb->field[2] = cpu_to_le32(field3); + trb->field[3] = cpu_to_le32(field4); + + cdnsp_inc_enq(pdev, ring, more_trbs_coming); +} + +/* + * Does various checks on the endpoint ring, and makes it ready to + * queue num_trbs. + */ +static int cdnsp_prepare_ring(struct cdnsp_device *pdev, + struct cdnsp_ring *ep_ring, + u32 ep_state, unsigned + int num_trbs, + gfp_t mem_flags) +{ + unsigned int num_trbs_needed; + + /* Make sure the endpoint has been added to controller schedule. */ + switch (ep_state) { + case EP_STATE_STOPPED: + case EP_STATE_RUNNING: + case EP_STATE_HALTED: + break; + default: + dev_err(pdev->dev, "ERROR: incorrect endpoint state\n"); + return -EINVAL; + } + + while (1) { + if (cdnsp_room_on_ring(pdev, ep_ring, num_trbs)) + break; + + num_trbs_needed = num_trbs - ep_ring->num_trbs_free; + if (cdnsp_ring_expansion(pdev, ep_ring, num_trbs_needed, + mem_flags)) { + dev_err(pdev->dev, "Ring expansion failed\n"); + return -ENOMEM; + } + } + + while (cdnsp_trb_is_link(ep_ring->enqueue)) { + ep_ring->enqueue->link.control |= cpu_to_le32(TRB_CHAIN); + /* The cycle bit must be set as the last operation. */ + wmb(); + ep_ring->enqueue->link.control ^= cpu_to_le32(TRB_CYCLE); + + /* Toggle the cycle bit after the last ring segment. */ + if (cdnsp_link_trb_toggles_cycle(ep_ring->enqueue)) + ep_ring->cycle_state ^= 1; + ep_ring->enq_seg = ep_ring->enq_seg->next; + ep_ring->enqueue = ep_ring->enq_seg->trbs; + } + return 0; +} + +static int cdnsp_prepare_transfer(struct cdnsp_device *pdev, + struct cdnsp_request *preq, + unsigned int num_trbs) +{ + struct cdnsp_ring *ep_ring; + int ret; + + ep_ring = cdnsp_get_transfer_ring(pdev, preq->pep, + preq->request.stream_id); + if (!ep_ring) + return -EINVAL; + + ret = cdnsp_prepare_ring(pdev, ep_ring, + GET_EP_CTX_STATE(preq->pep->out_ctx), + num_trbs, GFP_ATOMIC); + if (ret) + return ret; + + INIT_LIST_HEAD(&preq->td.td_list); + preq->td.preq = preq; + + /* Add this TD to the tail of the endpoint ring's TD list. */ + list_add_tail(&preq->td.td_list, &ep_ring->td_list); + ep_ring->num_tds++; + preq->pep->stream_info.td_count++; + + preq->td.start_seg = ep_ring->enq_seg; + preq->td.first_trb = ep_ring->enqueue; + + return 0; +} + +static unsigned int cdnsp_count_trbs(u64 addr, u64 len) +{ + unsigned int num_trbs; + + num_trbs = DIV_ROUND_UP(len + (addr & (TRB_MAX_BUFF_SIZE - 1)), + TRB_MAX_BUFF_SIZE); + if (num_trbs == 0) + num_trbs++; + + return num_trbs; +} + +static unsigned int count_trbs_needed(struct cdnsp_request *preq) +{ + return cdnsp_count_trbs(preq->request.dma, preq->request.length); +} + +static unsigned int count_sg_trbs_needed(struct cdnsp_request *preq) +{ + unsigned int i, len, full_len, num_trbs = 0; + struct scatterlist *sg; + + full_len = preq->request.length; + + for_each_sg(preq->request.sg, sg, preq->request.num_sgs, i) { + len = sg_dma_len(sg); + num_trbs += cdnsp_count_trbs(sg_dma_address(sg), len); + len = min(len, full_len); + full_len -= len; + if (full_len == 0) + break; + } + + return num_trbs; +} + +static unsigned int count_isoc_trbs_needed(struct cdnsp_request *preq) +{ + return cdnsp_count_trbs(preq->request.dma, preq->request.length); +} + +static void cdnsp_check_trb_math(struct cdnsp_request *preq, int running_total) +{ + if (running_total != preq->request.length) + dev_err(preq->pep->pdev->dev, + "%s - Miscalculated tx length, " + "queued %#x, asked for %#x (%d)\n", + preq->pep->name, running_total, + preq->request.length, preq->request.actual); +} + +/* + * TD size is the number of max packet sized packets remaining in the TD + * (*not* including this TRB). + * + * Total TD packet count = total_packet_count = + * DIV_ROUND_UP(TD size in bytes / wMaxPacketSize) + * + * Packets transferred up to and including this TRB = packets_transferred = + * rounddown(total bytes transferred including this TRB / wMaxPacketSize) + * + * TD size = total_packet_count - packets_transferred + * + * It must fit in bits 21:17, so it can't be bigger than 31. + * This is taken care of in the TRB_TD_SIZE() macro + * + * The last TRB in a TD must have the TD size set to zero. + */ +static u32 cdnsp_td_remainder(struct cdnsp_device *pdev, + int transferred, + int trb_buff_len, + unsigned int td_total_len, + struct cdnsp_request *preq, + bool more_trbs_coming) +{ + u32 maxp, total_packet_count; + + /* One TRB with a zero-length data packet. */ + if (!more_trbs_coming || (transferred == 0 && trb_buff_len == 0) || + trb_buff_len == td_total_len) + return 0; + + maxp = usb_endpoint_maxp(preq->pep->endpoint.desc); + total_packet_count = DIV_ROUND_UP(td_total_len, maxp); + + /* Queuing functions don't count the current TRB into transferred. */ + return (total_packet_count - ((transferred + trb_buff_len) / maxp)); +} + +static int cdnsp_align_td(struct cdnsp_device *pdev, + struct cdnsp_request *preq, u32 enqd_len, + u32 *trb_buff_len, struct cdnsp_segment *seg) +{ + struct device *dev = pdev->dev; + unsigned int unalign; + unsigned int max_pkt; + u32 new_buff_len; + + max_pkt = usb_endpoint_maxp(preq->pep->endpoint.desc); + unalign = (enqd_len + *trb_buff_len) % max_pkt; + + /* We got lucky, last normal TRB data on segment is packet aligned. */ + if (unalign == 0) + return 0; + + /* Is the last nornal TRB alignable by splitting it. */ + if (*trb_buff_len > unalign) { + *trb_buff_len -= unalign; + return 0; + } + + /* + * We want enqd_len + trb_buff_len to sum up to a number aligned to + * number which is divisible by the endpoint's wMaxPacketSize. IOW: + * (size of currently enqueued TRBs + remainder) % wMaxPacketSize == 0. + */ + new_buff_len = max_pkt - (enqd_len % max_pkt); + + if (new_buff_len > (preq->request.length - enqd_len)) + new_buff_len = (preq->request.length - enqd_len); + + /* Create a max max_pkt sized bounce buffer pointed to by last trb. */ + if (preq->direction) { + sg_pcopy_to_buffer(preq->request.sg, + preq->request.num_mapped_sgs, + seg->bounce_buf, new_buff_len, enqd_len); + seg->bounce_dma = dma_map_single(dev, seg->bounce_buf, + max_pkt, DMA_TO_DEVICE); + } else { + seg->bounce_dma = dma_map_single(dev, seg->bounce_buf, + max_pkt, DMA_FROM_DEVICE); + } + + if (dma_mapping_error(dev, seg->bounce_dma)) { + /* Try without aligning.*/ + dev_warn(pdev->dev, + "Failed mapping bounce buffer, not aligning\n"); + return 0; + } + + *trb_buff_len = new_buff_len; + seg->bounce_len = new_buff_len; + seg->bounce_offs = enqd_len; + + /* + * Bounce buffer successful aligned and seg->bounce_dma will be used + * in transfer TRB as new transfer buffer address. + */ + return 1; +} + +int cdnsp_queue_bulk_tx(struct cdnsp_device *pdev, struct cdnsp_request *preq) +{ + unsigned int enqd_len, block_len, trb_buff_len, full_len; + unsigned int start_cycle, num_sgs = 0; + struct cdnsp_generic_trb *start_trb; + u32 field, length_field, remainder; + struct scatterlist *sg = NULL; + bool more_trbs_coming = true; + bool need_zero_pkt = false; + bool zero_len_trb = false; + struct cdnsp_ring *ring; + bool first_trb = true; + unsigned int num_trbs; + struct cdnsp_ep *pep; + u64 addr, send_addr; + int sent_len, ret; + + ring = cdnsp_request_to_transfer_ring(pdev, preq); + if (!ring) + return -EINVAL; + + full_len = preq->request.length; + + if (preq->request.num_sgs) { + num_sgs = preq->request.num_sgs; + sg = preq->request.sg; + addr = (u64)sg_dma_address(sg); + block_len = sg_dma_len(sg); + num_trbs = count_sg_trbs_needed(preq); + } else { + num_trbs = count_trbs_needed(preq); + addr = (u64)preq->request.dma; + block_len = full_len; + } + + pep = preq->pep; + + /* Deal with request.zero - need one more td/trb. */ + if (preq->request.zero && preq->request.length && + IS_ALIGNED(full_len, usb_endpoint_maxp(pep->endpoint.desc))) { + need_zero_pkt = true; + num_trbs++; + } + + ret = cdnsp_prepare_transfer(pdev, preq, num_trbs); + if (ret) + return ret; + + /* + * Don't give the first TRB to the hardware (by toggling the cycle bit) + * until we've finished creating all the other TRBs. The ring's cycle + * state may change as we enqueue the other TRBs, so save it too. + */ + start_trb = &ring->enqueue->generic; + start_cycle = ring->cycle_state; + send_addr = addr; + + /* Queue the TRBs, even if they are zero-length */ + for (enqd_len = 0; zero_len_trb || first_trb || enqd_len < full_len; + enqd_len += trb_buff_len) { + field = TRB_TYPE(TRB_NORMAL); + + /* TRB buffer should not cross 64KB boundaries */ + trb_buff_len = TRB_BUFF_LEN_UP_TO_BOUNDARY(addr); + trb_buff_len = min(trb_buff_len, block_len); + if (enqd_len + trb_buff_len > full_len) + trb_buff_len = full_len - enqd_len; + + /* Don't change the cycle bit of the first TRB until later */ + if (first_trb) { + first_trb = false; + if (start_cycle == 0) + field |= TRB_CYCLE; + } else { + field |= ring->cycle_state; + } + + /* + * Chain all the TRBs together; clear the chain bit in the last + * TRB to indicate it's the last TRB in the chain. + */ + if (enqd_len + trb_buff_len < full_len || need_zero_pkt) { + field |= TRB_CHAIN; + if (cdnsp_trb_is_link(ring->enqueue + 1)) { + if (cdnsp_align_td(pdev, preq, enqd_len, + &trb_buff_len, + ring->enq_seg)) { + send_addr = ring->enq_seg->bounce_dma; + /* Assuming TD won't span 2 segs */ + preq->td.bounce_seg = ring->enq_seg; + } + } + } + + if (enqd_len + trb_buff_len >= full_len) { + if (need_zero_pkt && zero_len_trb) { + zero_len_trb = true; + } else { + field &= ~TRB_CHAIN; + field |= TRB_IOC; + more_trbs_coming = false; + need_zero_pkt = false; + preq->td.last_trb = ring->enqueue; + } + } + + /* Only set interrupt on short packet for OUT endpoints. */ + if (!preq->direction) + field |= TRB_ISP; + + /* Set the TRB length, TD size, and interrupter fields. */ + remainder = cdnsp_td_remainder(pdev, enqd_len, trb_buff_len, + full_len, preq, + more_trbs_coming); + + length_field = TRB_LEN(trb_buff_len) | TRB_TD_SIZE(remainder) | + TRB_INTR_TARGET(0); + + cdnsp_queue_trb(pdev, ring, more_trbs_coming | need_zero_pkt, + lower_32_bits(send_addr), + upper_32_bits(send_addr), + length_field, + field); + + addr += trb_buff_len; + sent_len = trb_buff_len; + while (sg && sent_len >= block_len) { + /* New sg entry */ + --num_sgs; + sent_len -= block_len; + if (num_sgs != 0) { + sg = sg_next(sg); + block_len = sg_dma_len(sg); + addr = (u64)sg_dma_address(sg); + addr += sent_len; + } + } + block_len -= sent_len; + send_addr = addr; + } + + cdnsp_check_trb_math(preq, enqd_len); + ret = cdnsp_giveback_first_trb(pdev, pep, preq->request.stream_id, + start_cycle, start_trb); + + if (ret) + preq->td.drbl = 1; + + return 0; +} + +int cdnsp_queue_ctrl_tx(struct cdnsp_device *pdev, struct cdnsp_request *preq) +{ + u32 field, length_field, remainder; + struct cdnsp_ep *pep = preq->pep; + struct cdnsp_ring *ep_ring; + int num_trbs; + int ret; + + ep_ring = cdnsp_request_to_transfer_ring(pdev, preq); + if (!ep_ring) + return -EINVAL; + + /* 1 TRB for data, 1 for status */ + num_trbs = (pdev->three_stage_setup) ? 2 : 1; + + ret = cdnsp_prepare_transfer(pdev, preq, num_trbs); + if (ret) + return ret; + + /* If there's data, queue data TRBs */ + if (pdev->ep0_expect_in) + field = TRB_TYPE(TRB_DATA) | TRB_IOC; + else + field = TRB_ISP | TRB_TYPE(TRB_DATA) | TRB_IOC; + + if (preq->request.length > 0) { + remainder = cdnsp_td_remainder(pdev, 0, preq->request.length, + preq->request.length, preq, 1); + + length_field = TRB_LEN(preq->request.length) | + TRB_TD_SIZE(remainder) | TRB_INTR_TARGET(0); + + if (pdev->ep0_expect_in) + field |= TRB_DIR_IN; + + cdnsp_queue_trb(pdev, ep_ring, true, + lower_32_bits(preq->request.dma), + upper_32_bits(preq->request.dma), length_field, + field | ep_ring->cycle_state | + TRB_SETUPID(pdev->setup_id) | + pdev->setup_speed); + + pdev->ep0_stage = CDNSP_DATA_STAGE; + } + + /* Save the DMA address of the last TRB in the TD. */ + preq->td.last_trb = ep_ring->enqueue; + + /* Queue status TRB. */ + if (preq->request.length == 0) + field = ep_ring->cycle_state; + else + field = (ep_ring->cycle_state ^ 1); + + if (preq->request.length > 0 && pdev->ep0_expect_in) + field |= TRB_DIR_IN; + + if (pep->ep_state & EP0_HALTED_STATUS) { + pep->ep_state &= ~EP0_HALTED_STATUS; + field |= TRB_SETUPSTAT(TRB_SETUPSTAT_STALL); + } else { + field |= TRB_SETUPSTAT(TRB_SETUPSTAT_ACK); + } + + cdnsp_queue_trb(pdev, ep_ring, false, 0, 0, TRB_INTR_TARGET(0), + field | TRB_IOC | TRB_SETUPID(pdev->setup_id) | + TRB_TYPE(TRB_STATUS) | pdev->setup_speed); + + cdnsp_ring_ep_doorbell(pdev, pep, preq->request.stream_id); + + return 0; +} + +int cdnsp_cmd_stop_ep(struct cdnsp_device *pdev, struct cdnsp_ep *pep) +{ + u32 ep_state = GET_EP_CTX_STATE(pep->out_ctx); + int ret = 0; + + if (ep_state == EP_STATE_STOPPED || ep_state == EP_STATE_DISABLED) + goto ep_stopped; + + cdnsp_queue_stop_endpoint(pdev, pep->idx); + cdnsp_ring_cmd_db(pdev); + ret = cdnsp_wait_for_cmd_compl(pdev); + +ep_stopped: + pep->ep_state |= EP_STOPPED; + return ret; +} + +int cdnsp_cmd_flush_ep(struct cdnsp_device *pdev, struct cdnsp_ep *pep) +{ + int ret; + + cdnsp_queue_flush_endpoint(pdev, pep->idx); + cdnsp_ring_cmd_db(pdev); + ret = cdnsp_wait_for_cmd_compl(pdev); + + return ret; +} + +/* + * The transfer burst count field of the isochronous TRB defines the number of + * bursts that are required to move all packets in this TD. Only SuperSpeed + * devices can burst up to bMaxBurst number of packets per service interval. + * This field is zero based, meaning a value of zero in the field means one + * burst. Basically, for everything but SuperSpeed devices, this field will be + * zero. + */ +static unsigned int cdnsp_get_burst_count(struct cdnsp_device *pdev, + struct cdnsp_request *preq, + unsigned int total_packet_count) +{ + unsigned int max_burst; + + if (pdev->gadget.speed < USB_SPEED_SUPER) + return 0; + + max_burst = preq->pep->endpoint.comp_desc->bMaxBurst; + return DIV_ROUND_UP(total_packet_count, max_burst + 1) - 1; +} + +/* + * Returns the number of packets in the last "burst" of packets. This field is + * valid for all speeds of devices. USB 2.0 devices can only do one "burst", so + * the last burst packet count is equal to the total number of packets in the + * TD. SuperSpeed endpoints can have up to 3 bursts. All but the last burst + * must contain (bMaxBurst + 1) number of packets, but the last burst can + * contain 1 to (bMaxBurst + 1) packets. + */ +static unsigned int + cdnsp_get_last_burst_packet_count(struct cdnsp_device *pdev, + struct cdnsp_request *preq, + unsigned int total_packet_count) +{ + unsigned int max_burst; + unsigned int residue; + + if (pdev->gadget.speed >= USB_SPEED_SUPER) { + /* bMaxBurst is zero based: 0 means 1 packet per burst. */ + max_burst = preq->pep->endpoint.comp_desc->bMaxBurst; + residue = total_packet_count % (max_burst + 1); + + /* + * If residue is zero, the last burst contains (max_burst + 1) + * number of packets, but the TLBPC field is zero-based. + */ + if (residue == 0) + return max_burst; + + return residue - 1; + } + if (total_packet_count == 0) + return 0; + + return total_packet_count - 1; +} + +/* Queue function isoc transfer */ +static int cdnsp_queue_isoc_tx(struct cdnsp_device *pdev, + struct cdnsp_request *preq) +{ + int trb_buff_len, td_len, td_remain_len, ret; + unsigned int burst_count, last_burst_pkt; + unsigned int total_pkt_count, max_pkt; + struct cdnsp_generic_trb *start_trb; + bool more_trbs_coming = true; + struct cdnsp_ring *ep_ring; + int running_total = 0; + u32 field, length_field; + int start_cycle; + int trbs_per_td; + u64 addr; + int i; + + ep_ring = preq->pep->ring; + start_trb = &ep_ring->enqueue->generic; + start_cycle = ep_ring->cycle_state; + td_len = preq->request.length; + addr = (u64)preq->request.dma; + td_remain_len = td_len; + + max_pkt = usb_endpoint_maxp(preq->pep->endpoint.desc); + total_pkt_count = DIV_ROUND_UP(td_len, max_pkt); + + /* A zero-length transfer still involves at least one packet. */ + if (total_pkt_count == 0) + total_pkt_count++; + + burst_count = cdnsp_get_burst_count(pdev, preq, total_pkt_count); + last_burst_pkt = cdnsp_get_last_burst_packet_count(pdev, preq, + total_pkt_count); + trbs_per_td = count_isoc_trbs_needed(preq); + + ret = cdnsp_prepare_transfer(pdev, preq, trbs_per_td); + if (ret) + goto cleanup; + + /* + * Set isoc specific data for the first TRB in a TD. + * Prevent HW from getting the TRBs by keeping the cycle state + * inverted in the first TDs isoc TRB. + */ + field = TRB_TYPE(TRB_ISOC) | TRB_TLBPC(last_burst_pkt) | + !start_cycle | TRB_SIA | TRB_TBC(burst_count); + + /* Fill the rest of the TRB fields, and remaining normal TRBs. */ + for (i = 0; i < trbs_per_td; i++) { + u32 remainder; + + /* Calculate TRB length. */ + trb_buff_len = TRB_BUFF_LEN_UP_TO_BOUNDARY(addr); + if (trb_buff_len > td_remain_len) + trb_buff_len = td_remain_len; + + /* Set the TRB length, TD size, & interrupter fields. */ + remainder = cdnsp_td_remainder(pdev, running_total, + trb_buff_len, td_len, preq, + more_trbs_coming); + + length_field = TRB_LEN(trb_buff_len) | TRB_INTR_TARGET(0); + + /* Only first TRB is isoc, overwrite otherwise. */ + if (i) { + field = TRB_TYPE(TRB_NORMAL) | ep_ring->cycle_state; + length_field |= TRB_TD_SIZE(remainder); + } else { + length_field |= TRB_TD_SIZE_TBC(burst_count); + } + + /* Only set interrupt on short packet for OUT EPs. */ + if (usb_endpoint_dir_out(preq->pep->endpoint.desc)) + field |= TRB_ISP; + + /* Set the chain bit for all except the last TRB. */ + if (i < trbs_per_td - 1) { + more_trbs_coming = true; + field |= TRB_CHAIN; + } else { + more_trbs_coming = false; + preq->td.last_trb = ep_ring->enqueue; + field |= TRB_IOC; + } + + cdnsp_queue_trb(pdev, ep_ring, more_trbs_coming, + lower_32_bits(addr), upper_32_bits(addr), + length_field, field); + + running_total += trb_buff_len; + addr += trb_buff_len; + td_remain_len -= trb_buff_len; + } + + /* Check TD length */ + if (running_total != td_len) { + dev_err(pdev->dev, "ISOC TD length unmatch\n"); + ret = -EINVAL; + goto cleanup; + } + + cdnsp_giveback_first_trb(pdev, preq->pep, preq->request.stream_id, + start_cycle, start_trb); + + return 0; + +cleanup: + /* Clean up a partially enqueued isoc transfer. */ + list_del_init(&preq->td.td_list); + ep_ring->num_tds--; + + /* + * Use the first TD as a temporary variable to turn the TDs we've + * queued into No-ops with a software-owned cycle bit. + * That way the hardware won't accidentally start executing bogus TDs + * when we partially overwrite them. + * td->first_trb and td->start_seg are already set. + */ + preq->td.last_trb = ep_ring->enqueue; + /* Every TRB except the first & last will have its cycle bit flipped. */ + cdnsp_td_to_noop(pdev, ep_ring, &preq->td, true); + + /* Reset the ring enqueue back to the first TRB and its cycle bit. */ + ep_ring->enqueue = preq->td.first_trb; + ep_ring->enq_seg = preq->td.start_seg; + ep_ring->cycle_state = start_cycle; + return ret; +} + +int cdnsp_queue_isoc_tx_prepare(struct cdnsp_device *pdev, + struct cdnsp_request *preq) +{ + struct cdnsp_ring *ep_ring; + u32 ep_state; + int num_trbs; + int ret; + + ep_ring = preq->pep->ring; + ep_state = GET_EP_CTX_STATE(preq->pep->out_ctx); + num_trbs = count_isoc_trbs_needed(preq); + + /* + * Check the ring to guarantee there is enough room for the whole + * request. Do not insert any td of the USB Request to the ring if the + * check failed. + */ + ret = cdnsp_prepare_ring(pdev, ep_ring, ep_state, num_trbs, GFP_ATOMIC); + if (ret) + return ret; + + return cdnsp_queue_isoc_tx(pdev, preq); +} + +/**** Command Ring Operations ****/ +/* + * Generic function for queuing a command TRB on the command ring. + * Driver queue only one command to ring in the moment. + */ +static void cdnsp_queue_command(struct cdnsp_device *pdev, + u32 field1, + u32 field2, + u32 field3, + u32 field4) +{ + cdnsp_prepare_ring(pdev, pdev->cmd_ring, EP_STATE_RUNNING, 1, + GFP_ATOMIC); + + pdev->cmd.command_trb = pdev->cmd_ring->enqueue; + + cdnsp_queue_trb(pdev, pdev->cmd_ring, false, field1, field2, + field3, field4 | pdev->cmd_ring->cycle_state); +} + +/* Queue a slot enable or disable request on the command ring */ +void cdnsp_queue_slot_control(struct cdnsp_device *pdev, u32 trb_type) +{ + cdnsp_queue_command(pdev, 0, 0, 0, TRB_TYPE(trb_type) | + SLOT_ID_FOR_TRB(pdev->slot_id)); +} + +/* Queue an address device command TRB */ +void cdnsp_queue_address_device(struct cdnsp_device *pdev, + dma_addr_t in_ctx_ptr, + enum cdnsp_setup_dev setup) +{ + cdnsp_queue_command(pdev, lower_32_bits(in_ctx_ptr), + upper_32_bits(in_ctx_ptr), 0, + TRB_TYPE(TRB_ADDR_DEV) | + SLOT_ID_FOR_TRB(pdev->slot_id) | + (setup == SETUP_CONTEXT_ONLY ? TRB_BSR : 0)); +} + +/* Queue a reset device command TRB */ +void cdnsp_queue_reset_device(struct cdnsp_device *pdev) +{ + cdnsp_queue_command(pdev, 0, 0, 0, TRB_TYPE(TRB_RESET_DEV) | + SLOT_ID_FOR_TRB(pdev->slot_id)); +} + +/* Queue a configure endpoint command TRB */ +void cdnsp_queue_configure_endpoint(struct cdnsp_device *pdev, + dma_addr_t in_ctx_ptr) +{ + cdnsp_queue_command(pdev, lower_32_bits(in_ctx_ptr), + upper_32_bits(in_ctx_ptr), 0, + TRB_TYPE(TRB_CONFIG_EP) | + SLOT_ID_FOR_TRB(pdev->slot_id)); +} + +/* + * Suspend is set to indicate "Stop Endpoint Command" is being issued to stop + * activity on an endpoint that is about to be suspended. + */ +void cdnsp_queue_stop_endpoint(struct cdnsp_device *pdev, unsigned int ep_index) +{ + cdnsp_queue_command(pdev, 0, 0, 0, SLOT_ID_FOR_TRB(pdev->slot_id) | + EP_ID_FOR_TRB(ep_index) | TRB_TYPE(TRB_STOP_RING)); +} + +/* Set Transfer Ring Dequeue Pointer command. */ +void cdnsp_queue_new_dequeue_state(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + struct cdnsp_dequeue_state *deq_state) +{ + u32 trb_stream_id = STREAM_ID_FOR_TRB(deq_state->stream_id); + u32 trb_slot_id = SLOT_ID_FOR_TRB(pdev->slot_id); + u32 type = TRB_TYPE(TRB_SET_DEQ); + u32 trb_sct = 0; + dma_addr_t addr; + + addr = cdnsp_trb_virt_to_dma(deq_state->new_deq_seg, + deq_state->new_deq_ptr); + + if (deq_state->stream_id) + trb_sct = SCT_FOR_TRB(SCT_PRI_TR); + + cdnsp_queue_command(pdev, lower_32_bits(addr) | trb_sct | + deq_state->new_cycle_state, upper_32_bits(addr), + trb_stream_id, trb_slot_id | + EP_ID_FOR_TRB(pep->idx) | type); +} + +void cdnsp_queue_reset_ep(struct cdnsp_device *pdev, unsigned int ep_index) +{ + return cdnsp_queue_command(pdev, 0, 0, 0, + SLOT_ID_FOR_TRB(pdev->slot_id) | + EP_ID_FOR_TRB(ep_index) | + TRB_TYPE(TRB_RESET_EP)); +} + +/* + * Queue a halt endpoint request on the command ring. + */ +void cdnsp_queue_halt_endpoint(struct cdnsp_device *pdev, unsigned int ep_index) +{ + cdnsp_queue_command(pdev, 0, 0, 0, TRB_TYPE(TRB_HALT_ENDPOINT) | + SLOT_ID_FOR_TRB(pdev->slot_id) | + EP_ID_FOR_TRB(ep_index)); +} + +/* + * Queue a flush endpoint request on the command ring. + */ +void cdnsp_queue_flush_endpoint(struct cdnsp_device *pdev, + unsigned int ep_index) +{ + cdnsp_queue_command(pdev, 0, 0, 0, TRB_TYPE(TRB_FLUSH_ENDPOINT) | + SLOT_ID_FOR_TRB(pdev->slot_id) | + EP_ID_FOR_TRB(ep_index)); +} + +void cdnsp_force_header_wakeup(struct cdnsp_device *pdev, int intf_num) +{ + u32 lo, mid; + + lo = TRB_FH_TO_PACKET_TYPE(TRB_FH_TR_PACKET) | + TRB_FH_TO_DEVICE_ADDRESS(pdev->device_address); + mid = TRB_FH_TR_PACKET_DEV_NOT | + TRB_FH_TO_NOT_TYPE(TRB_FH_TR_PACKET_FUNCTION_WAKE) | + TRB_FH_TO_INTERFACE(intf_num); + + cdnsp_queue_command(pdev, lo, mid, 0, + TRB_TYPE(TRB_FORCE_HEADER) | SET_PORT_ID(2)); +} diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c index 85c491f4c6d5..199713769289 100644 --- a/drivers/usb/cdns3/core.c +++ b/drivers/usb/cdns3/core.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Cadence USBSS DRD Driver. + * Cadence USBSS and USBSSP DRD Driver. * * Copyright (C) 2018-2019 Cadence. * Copyright (C) 2017-2018 NXP @@ -136,7 +136,14 @@ static int cdns_core_init_role(struct cdns *cdns) dr_mode = best_dr_mode; if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) { - ret = cdns_host_init(cdns); + if ((cdns->version == CDNSP_CONTROLLER_V2 && + IS_ENABLED(CONFIG_USB_CDNSP_HOST)) || + (cdns->version < CDNSP_CONTROLLER_V2 && + IS_ENABLED(CONFIG_USB_CDNS3_HOST))) + ret = cdns_host_init(cdns); + else + ret = -ENXIO; + if (ret) { dev_err(dev, "Host initialization failed with %d\n", ret); diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h index cbd2e1cc8eb1..56b568678980 100644 --- a/drivers/usb/cdns3/core.h +++ b/drivers/usb/cdns3/core.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Cadence USBSS DRD Header File. + * Cadence USBSS and USBSSP DRD Header File. * * Copyright (C) 2017-2018 NXP * Copyright (C) 2018-2019 Cadence. diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c index 0fd465094101..605a413db727 100644 --- a/drivers/usb/cdns3/drd.c +++ b/drivers/usb/cdns3/drd.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Cadence USBSS DRD Driver. + * Cadence USBSS and USBSSP DRD Driver. * * Copyright (C) 2018-2020 Cadence. * Copyright (C) 2019 Texas Instruments @@ -103,6 +103,32 @@ int cdns_get_vbus(struct cdns *cdns) return vbus; } +void cdns_clear_vbus(struct cdns *cdns) +{ + u32 reg; + + if (cdns->version != CDNSP_CONTROLLER_V2) + return; + + reg = readl(&cdns->otg_cdnsp_regs->override); + reg |= OVERRIDE_SESS_VLD_SEL; + writel(reg, &cdns->otg_cdnsp_regs->override); +} +EXPORT_SYMBOL_GPL(cdns_clear_vbus); + +void cdns_set_vbus(struct cdns *cdns) +{ + u32 reg; + + if (cdns->version != CDNSP_CONTROLLER_V2) + return; + + reg = readl(&cdns->otg_cdnsp_regs->override); + reg &= ~OVERRIDE_SESS_VLD_SEL; + writel(reg, &cdns->otg_cdnsp_regs->override); +} +EXPORT_SYMBOL_GPL(cdns_set_vbus); + bool cdns_is_host(struct cdns *cdns) { if (cdns->dr_mode == USB_DR_MODE_HOST) @@ -449,5 +475,6 @@ int cdns_drd_init(struct cdns *cdns) int cdns_drd_exit(struct cdns *cdns) { cdns_otg_disable_irq(cdns); + return 0; } diff --git a/drivers/usb/cdns3/drd.h b/drivers/usb/cdns3/drd.h index 838b17c6a45b..9724acdecbbb 100644 --- a/drivers/usb/cdns3/drd.h +++ b/drivers/usb/cdns3/drd.h @@ -206,6 +206,8 @@ bool cdns_is_host(struct cdns *cdns); bool cdns_is_device(struct cdns *cdns); int cdns_get_id(struct cdns *cdns); int cdns_get_vbus(struct cdns *cdns); +void cdns_clear_vbus(struct cdns *cdns); +void cdns_set_vbus(struct cdns *cdns); int cdns_drd_init(struct cdns *cdns); int cdns_drd_exit(struct cdns *cdns); int cdns_drd_update_mode(struct cdns *cdns); diff --git a/drivers/usb/cdns3/gadget-export.h b/drivers/usb/cdns3/gadget-export.h index 0f7cb2a92c9a..c37b6269b001 100644 --- a/drivers/usb/cdns3/gadget-export.h +++ b/drivers/usb/cdns3/gadget-export.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Cadence USBSS DRD Driver - Gadget Export APIs. + * Cadence USBSS and USBSSP DRD Driver - Gadget Export APIs. * * Copyright (C) 2017 NXP * Copyright (C) 2017-2018 NXP @@ -10,7 +10,19 @@ #ifndef __LINUX_CDNS3_GADGET_EXPORT #define __LINUX_CDNS3_GADGET_EXPORT -#ifdef CONFIG_USB_CDNS3_GADGET +#if IS_ENABLED(CONFIG_USB_CDNSP_GADGET) + +int cdnsp_gadget_init(struct cdns *cdns); +#else + +static inline int cdnsp_gadget_init(struct cdns *cdns) +{ + return -ENXIO; +} + +#endif /* CONFIG_USB_CDNSP_GADGET */ + +#if IS_ENABLED(CONFIG_USB_CDNS3_GADGET) int cdns3_gadget_init(struct cdns *cdns); #else @@ -20,6 +32,6 @@ static inline int cdns3_gadget_init(struct cdns *cdns) return -ENXIO; } -#endif +#endif /* CONFIG_USB_CDNS3_GADGET */ #endif /* __LINUX_CDNS3_GADGET_EXPORT */ diff --git a/drivers/usb/cdns3/host-export.h b/drivers/usb/cdns3/host-export.h index 744a4d4c4fb8..087e1921e1f5 100644 --- a/drivers/usb/cdns3/host-export.h +++ b/drivers/usb/cdns3/host-export.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Cadence USBSS DRD Driver - Host Export APIs + * Cadence USBSS and USBSSP DRD Driver - Host Export APIs * * Copyright (C) 2017-2018 NXP * @@ -9,8 +9,9 @@ #ifndef __LINUX_CDNS3_HOST_EXPORT #define __LINUX_CDNS3_HOST_EXPORT +#if IS_ENABLED(CONFIG_USB_CDNS_HOST) + struct usb_hcd; -#ifdef CONFIG_USB_CDNS3_HOST int cdns_host_init(struct cdns *cdns); int xhci_cdns3_suspend_quirk(struct usb_hcd *hcd); @@ -28,6 +29,6 @@ static inline int xhci_cdns3_suspend_quirk(struct usb_hcd *hcd) return 0; } -#endif /* CONFIG_USB_CDNS3_HOST */ +#endif /* USB_CDNS_HOST */ #endif /* __LINUX_CDNS3_HOST_EXPORT */ -- cgit v1.2.3 From 118b2a3237cf499727649b1c018dd2f1d329af08 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Mon, 7 Dec 2020 11:32:25 +0100 Subject: usb: cdnsp: Add tracepoints for CDNSP driver Patch adds the series of tracepoints that can be used for debugging issues detected in driver. Signed-off-by: Pawel Laszczak Reviewed-by: Peter Chen Signed-off-by: Peter Chen --- drivers/usb/cdns3/Makefile | 5 + drivers/usb/cdns3/cdnsp-debug.h | 583 +++++++++++++++++++++++++++ drivers/usb/cdns3/cdnsp-ep0.c | 22 +- drivers/usb/cdns3/cdnsp-gadget.c | 75 +++- drivers/usb/cdns3/cdnsp-mem.c | 18 +- drivers/usb/cdns3/cdnsp-ring.c | 75 +++- drivers/usb/cdns3/cdnsp-trace.c | 12 + drivers/usb/cdns3/cdnsp-trace.h | 840 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 1614 insertions(+), 16 deletions(-) create mode 100644 drivers/usb/cdns3/cdnsp-debug.h create mode 100644 drivers/usb/cdns3/cdnsp-trace.c create mode 100644 drivers/usb/cdns3/cdnsp-trace.h diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile index a84b129f14b8..a4fdaabdbe18 100644 --- a/drivers/usb/cdns3/Makefile +++ b/drivers/usb/cdns3/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 # define_trace.h needs to know how to find our header CFLAGS_trace.o := -I$(src) +CFLAGS_cdnsp-trace.o := -I$(src) cdns-usb-common-y := core.o drd.o cdns3-y := cdns3-plat.o @@ -23,3 +24,7 @@ cdnsp-udc-pci-y := cdnsp-pci.o obj-$(CONFIG_USB_CDNSP_PCI) += cdnsp-udc-pci.o cdnsp-udc-pci-$(CONFIG_USB_CDNSP_GADGET) += cdnsp-ring.o cdnsp-gadget.o \ cdnsp-mem.o cdnsp-ep0.o + +ifneq ($(CONFIG_USB_CDNSP_GADGET),) +cdnsp-udc-pci-$(CONFIG_TRACING) += cdnsp-trace.o +endif diff --git a/drivers/usb/cdns3/cdnsp-debug.h b/drivers/usb/cdns3/cdnsp-debug.h new file mode 100644 index 000000000000..d6345d4d2911 --- /dev/null +++ b/drivers/usb/cdns3/cdnsp-debug.h @@ -0,0 +1,583 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Cadence CDNSP DRD Driver. + * + * Copyright (C) 2020 Cadence. + * + * Author: Pawel Laszczak + * + */ +#ifndef __LINUX_CDNSP_DEBUG +#define __LINUX_CDNSP_DEBUG + +static inline const char *cdnsp_trb_comp_code_string(u8 status) +{ + switch (status) { + case COMP_INVALID: + return "Invalid"; + case COMP_SUCCESS: + return "Success"; + case COMP_DATA_BUFFER_ERROR: + return "Data Buffer Error"; + case COMP_BABBLE_DETECTED_ERROR: + return "Babble Detected"; + case COMP_TRB_ERROR: + return "TRB Error"; + case COMP_RESOURCE_ERROR: + return "Resource Error"; + case COMP_NO_SLOTS_AVAILABLE_ERROR: + return "No Slots Available Error"; + case COMP_INVALID_STREAM_TYPE_ERROR: + return "Invalid Stream Type Error"; + case COMP_SLOT_NOT_ENABLED_ERROR: + return "Slot Not Enabled Error"; + case COMP_ENDPOINT_NOT_ENABLED_ERROR: + return "Endpoint Not Enabled Error"; + case COMP_SHORT_PACKET: + return "Short Packet"; + case COMP_RING_UNDERRUN: + return "Ring Underrun"; + case COMP_RING_OVERRUN: + return "Ring Overrun"; + case COMP_VF_EVENT_RING_FULL_ERROR: + return "VF Event Ring Full Error"; + case COMP_PARAMETER_ERROR: + return "Parameter Error"; + case COMP_CONTEXT_STATE_ERROR: + return "Context State Error"; + case COMP_EVENT_RING_FULL_ERROR: + return "Event Ring Full Error"; + case COMP_INCOMPATIBLE_DEVICE_ERROR: + return "Incompatible Device Error"; + case COMP_MISSED_SERVICE_ERROR: + return "Missed Service Error"; + case COMP_COMMAND_RING_STOPPED: + return "Command Ring Stopped"; + case COMP_COMMAND_ABORTED: + return "Command Aborted"; + case COMP_STOPPED: + return "Stopped"; + case COMP_STOPPED_LENGTH_INVALID: + return "Stopped - Length Invalid"; + case COMP_STOPPED_SHORT_PACKET: + return "Stopped - Short Packet"; + case COMP_MAX_EXIT_LATENCY_TOO_LARGE_ERROR: + return "Max Exit Latency Too Large Error"; + case COMP_ISOCH_BUFFER_OVERRUN: + return "Isoch Buffer Overrun"; + case COMP_EVENT_LOST_ERROR: + return "Event Lost Error"; + case COMP_UNDEFINED_ERROR: + return "Undefined Error"; + case COMP_INVALID_STREAM_ID_ERROR: + return "Invalid Stream ID Error"; + default: + return "Unknown!!"; + } +} + +static inline const char *cdnsp_trb_type_string(u8 type) +{ + switch (type) { + case TRB_NORMAL: + return "Normal"; + case TRB_SETUP: + return "Setup Stage"; + case TRB_DATA: + return "Data Stage"; + case TRB_STATUS: + return "Status Stage"; + case TRB_ISOC: + return "Isoch"; + case TRB_LINK: + return "Link"; + case TRB_EVENT_DATA: + return "Event Data"; + case TRB_TR_NOOP: + return "No-Op"; + case TRB_ENABLE_SLOT: + return "Enable Slot Command"; + case TRB_DISABLE_SLOT: + return "Disable Slot Command"; + case TRB_ADDR_DEV: + return "Address Device Command"; + case TRB_CONFIG_EP: + return "Configure Endpoint Command"; + case TRB_EVAL_CONTEXT: + return "Evaluate Context Command"; + case TRB_RESET_EP: + return "Reset Endpoint Command"; + case TRB_STOP_RING: + return "Stop Ring Command"; + case TRB_SET_DEQ: + return "Set TR Dequeue Pointer Command"; + case TRB_RESET_DEV: + return "Reset Device Command"; + case TRB_FORCE_HEADER: + return "Force Header Command"; + case TRB_CMD_NOOP: + return "No-Op Command"; + case TRB_TRANSFER: + return "Transfer Event"; + case TRB_COMPLETION: + return "Command Completion Event"; + case TRB_PORT_STATUS: + return "Port Status Change Event"; + case TRB_HC_EVENT: + return "Device Controller Event"; + case TRB_MFINDEX_WRAP: + return "MFINDEX Wrap Event"; + case TRB_ENDPOINT_NRDY: + return "Endpoint Not ready"; + case TRB_HALT_ENDPOINT: + return "Halt Endpoint"; + case TRB_FLUSH_ENDPOINT: + return "FLush Endpoint"; + default: + return "UNKNOWN"; + } +} + +static inline const char *cdnsp_ring_type_string(enum cdnsp_ring_type type) +{ + switch (type) { + case TYPE_CTRL: + return "CTRL"; + case TYPE_ISOC: + return "ISOC"; + case TYPE_BULK: + return "BULK"; + case TYPE_INTR: + return "INTR"; + case TYPE_STREAM: + return "STREAM"; + case TYPE_COMMAND: + return "CMD"; + case TYPE_EVENT: + return "EVENT"; + } + + return "UNKNOWN"; +} + +static inline char *cdnsp_slot_state_string(u32 state) +{ + switch (state) { + case SLOT_STATE_ENABLED: + return "enabled/disabled"; + case SLOT_STATE_DEFAULT: + return "default"; + case SLOT_STATE_ADDRESSED: + return "addressed"; + case SLOT_STATE_CONFIGURED: + return "configured"; + default: + return "reserved"; + } +} + +static inline const char *cdnsp_decode_trb(char *str, size_t size, u32 field0, + u32 field1, u32 field2, u32 field3) +{ + int ep_id = TRB_TO_EP_INDEX(field3) - 1; + int type = TRB_FIELD_TO_TYPE(field3); + unsigned int ep_num; + int ret = 0; + u32 temp; + + ep_num = DIV_ROUND_UP(ep_id, 2); + + switch (type) { + case TRB_LINK: + ret += snprintf(str, size, + "LINK %08x%08x intr %ld type '%s' flags %c:%c:%c:%c", + field1, field0, GET_INTR_TARGET(field2), + cdnsp_trb_type_string(type), + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CHAIN ? 'C' : 'c', + field3 & TRB_TC ? 'T' : 't', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_TRANSFER: + case TRB_COMPLETION: + case TRB_PORT_STATUS: + case TRB_HC_EVENT: + ret += snprintf(str, size, + "ep%d%s(%d) type '%s' TRB %08x%08x status '%s'" + " len %ld slot %ld flags %c:%c", + ep_num, ep_id % 2 ? "out" : "in", + TRB_TO_EP_INDEX(field3), + cdnsp_trb_type_string(type), field1, field0, + cdnsp_trb_comp_code_string(GET_COMP_CODE(field2)), + EVENT_TRB_LEN(field2), TRB_TO_SLOT_ID(field3), + field3 & EVENT_DATA ? 'E' : 'e', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_MFINDEX_WRAP: + ret += snprintf(str, size, "%s: flags %c", + cdnsp_trb_type_string(type), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_SETUP: + ret += snprintf(str, size, + "type '%s' bRequestType %02x bRequest %02x " + "wValue %02x%02x wIndex %02x%02x wLength %d " + "length %ld TD size %ld intr %ld Setup ID %ld " + "flags %c:%c:%c", + cdnsp_trb_type_string(type), + field0 & 0xff, + (field0 & 0xff00) >> 8, + (field0 & 0xff000000) >> 24, + (field0 & 0xff0000) >> 16, + (field1 & 0xff00) >> 8, + field1 & 0xff, + (field1 & 0xff000000) >> 16 | + (field1 & 0xff0000) >> 16, + TRB_LEN(field2), GET_TD_SIZE(field2), + GET_INTR_TARGET(field2), + TRB_SETUPID_TO_TYPE(field3), + field3 & TRB_IDT ? 'D' : 'd', + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_DATA: + ret += snprintf(str, size, + "type '%s' Buffer %08x%08x length %ld TD size %ld " + "intr %ld flags %c:%c:%c:%c:%c:%c:%c", + cdnsp_trb_type_string(type), + field1, field0, TRB_LEN(field2), + GET_TD_SIZE(field2), + GET_INTR_TARGET(field2), + field3 & TRB_IDT ? 'D' : 'i', + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CHAIN ? 'C' : 'c', + field3 & TRB_NO_SNOOP ? 'S' : 's', + field3 & TRB_ISP ? 'I' : 'i', + field3 & TRB_ENT ? 'E' : 'e', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_STATUS: + ret += snprintf(str, size, + "Buffer %08x%08x length %ld TD size %ld intr" + "%ld type '%s' flags %c:%c:%c:%c", + field1, field0, TRB_LEN(field2), + GET_TD_SIZE(field2), + GET_INTR_TARGET(field2), + cdnsp_trb_type_string(type), + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CHAIN ? 'C' : 'c', + field3 & TRB_ENT ? 'E' : 'e', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_NORMAL: + case TRB_ISOC: + case TRB_EVENT_DATA: + case TRB_TR_NOOP: + ret += snprintf(str, size, + "type '%s' Buffer %08x%08x length %ld " + "TD size %ld intr %ld " + "flags %c:%c:%c:%c:%c:%c:%c:%c:%c", + cdnsp_trb_type_string(type), + field1, field0, TRB_LEN(field2), + GET_TD_SIZE(field2), + GET_INTR_TARGET(field2), + field3 & TRB_BEI ? 'B' : 'b', + field3 & TRB_IDT ? 'T' : 't', + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CHAIN ? 'C' : 'c', + field3 & TRB_NO_SNOOP ? 'S' : 's', + field3 & TRB_ISP ? 'I' : 'i', + field3 & TRB_ENT ? 'E' : 'e', + field3 & TRB_CYCLE ? 'C' : 'c', + !(field3 & TRB_EVENT_INVALIDATE) ? 'V' : 'v'); + break; + case TRB_CMD_NOOP: + case TRB_ENABLE_SLOT: + ret += snprintf(str, size, "%s: flags %c", + cdnsp_trb_type_string(type), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_DISABLE_SLOT: + ret += snprintf(str, size, "%s: slot %ld flags %c", + cdnsp_trb_type_string(type), + TRB_TO_SLOT_ID(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_ADDR_DEV: + ret += snprintf(str, size, + "%s: ctx %08x%08x slot %ld flags %c:%c", + cdnsp_trb_type_string(type), field1, field0, + TRB_TO_SLOT_ID(field3), + field3 & TRB_BSR ? 'B' : 'b', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_CONFIG_EP: + ret += snprintf(str, size, + "%s: ctx %08x%08x slot %ld flags %c:%c", + cdnsp_trb_type_string(type), field1, field0, + TRB_TO_SLOT_ID(field3), + field3 & TRB_DC ? 'D' : 'd', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_EVAL_CONTEXT: + ret += snprintf(str, size, + "%s: ctx %08x%08x slot %ld flags %c", + cdnsp_trb_type_string(type), field1, field0, + TRB_TO_SLOT_ID(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_RESET_EP: + case TRB_HALT_ENDPOINT: + case TRB_FLUSH_ENDPOINT: + ret += snprintf(str, size, + "%s: ep%d%s(%d) ctx %08x%08x slot %ld flags %c", + cdnsp_trb_type_string(type), + ep_num, ep_id % 2 ? "out" : "in", + TRB_TO_EP_INDEX(field3), field1, field0, + TRB_TO_SLOT_ID(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_STOP_RING: + ret += snprintf(str, size, + "%s: ep%d%s(%d) slot %ld sp %d flags %c", + cdnsp_trb_type_string(type), + ep_num, ep_id % 2 ? "out" : "in", + TRB_TO_EP_INDEX(field3), + TRB_TO_SLOT_ID(field3), + TRB_TO_SUSPEND_PORT(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_SET_DEQ: + ret += snprintf(str, size, + "%s: ep%d%s(%d) deq %08x%08x stream %ld slot %ld flags %c", + cdnsp_trb_type_string(type), + ep_num, ep_id % 2 ? "out" : "in", + TRB_TO_EP_INDEX(field3), field1, field0, + TRB_TO_STREAM_ID(field2), + TRB_TO_SLOT_ID(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_RESET_DEV: + ret += snprintf(str, size, "%s: slot %ld flags %c", + cdnsp_trb_type_string(type), + TRB_TO_SLOT_ID(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_ENDPOINT_NRDY: + temp = TRB_TO_HOST_STREAM(field2); + + ret += snprintf(str, size, + "%s: ep%d%s(%d) H_SID %x%s%s D_SID %lx flags %c:%c", + cdnsp_trb_type_string(type), + ep_num, ep_id % 2 ? "out" : "in", + TRB_TO_EP_INDEX(field3), temp, + temp == STREAM_PRIME_ACK ? "(PRIME)" : "", + temp == STREAM_REJECTED ? "(REJECTED)" : "", + TRB_TO_DEV_STREAM(field0), + field3 & TRB_STAT ? 'S' : 's', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + default: + ret += snprintf(str, size, + "type '%s' -> raw %08x %08x %08x %08x", + cdnsp_trb_type_string(type), + field0, field1, field2, field3); + } + + return str; +} + +static inline const char *cdnsp_decode_slot_context(u32 info, u32 info2, + u32 int_target, u32 state) +{ + static char str[1024]; + int ret = 0; + u32 speed; + char *s; + + speed = info & DEV_SPEED; + + switch (speed) { + case SLOT_SPEED_FS: + s = "full-speed"; + break; + case SLOT_SPEED_HS: + s = "high-speed"; + break; + case SLOT_SPEED_SS: + s = "super-speed"; + break; + case SLOT_SPEED_SSP: + s = "super-speed plus"; + break; + default: + s = "UNKNOWN speed"; + } + + ret = sprintf(str, "%s Ctx Entries %ld", + s, (info & LAST_CTX_MASK) >> 27); + + ret += sprintf(str + ret, " [Intr %ld] Addr %ld State %s", + GET_INTR_TARGET(int_target), state & DEV_ADDR_MASK, + cdnsp_slot_state_string(GET_SLOT_STATE(state))); + + return str; +} + +static inline const char *cdnsp_portsc_link_state_string(u32 portsc) +{ + switch (portsc & PORT_PLS_MASK) { + case XDEV_U0: + return "U0"; + case XDEV_U1: + return "U1"; + case XDEV_U2: + return "U2"; + case XDEV_U3: + return "U3"; + case XDEV_DISABLED: + return "Disabled"; + case XDEV_RXDETECT: + return "RxDetect"; + case XDEV_INACTIVE: + return "Inactive"; + case XDEV_POLLING: + return "Polling"; + case XDEV_RECOVERY: + return "Recovery"; + case XDEV_HOT_RESET: + return "Hot Reset"; + case XDEV_COMP_MODE: + return "Compliance mode"; + case XDEV_TEST_MODE: + return "Test mode"; + case XDEV_RESUME: + return "Resume"; + default: + break; + } + + return "Unknown"; +} + +static inline const char *cdnsp_decode_portsc(char *str, size_t size, + u32 portsc) +{ + int ret; + + ret = snprintf(str, size, "%s %s %s Link:%s PortSpeed:%d ", + portsc & PORT_POWER ? "Powered" : "Powered-off", + portsc & PORT_CONNECT ? "Connected" : "Not-connected", + portsc & PORT_PED ? "Enabled" : "Disabled", + cdnsp_portsc_link_state_string(portsc), + DEV_PORT_SPEED(portsc)); + + if (portsc & PORT_RESET) + ret += snprintf(str + ret, size - ret, "In-Reset "); + + ret += snprintf(str + ret, size - ret, "Change: "); + if (portsc & PORT_CSC) + ret += snprintf(str + ret, size - ret, "CSC "); + if (portsc & PORT_WRC) + ret += snprintf(str + ret, size - ret, "WRC "); + if (portsc & PORT_RC) + ret += snprintf(str + ret, size - ret, "PRC "); + if (portsc & PORT_PLC) + ret += snprintf(str + ret, size - ret, "PLC "); + if (portsc & PORT_CEC) + ret += snprintf(str + ret, size - ret, "CEC "); + ret += snprintf(str + ret, size - ret, "Wake: "); + if (portsc & PORT_WKCONN_E) + ret += snprintf(str + ret, size - ret, "WCE "); + if (portsc & PORT_WKDISC_E) + ret += snprintf(str + ret, size - ret, "WDE "); + + return str; +} + +static inline const char *cdnsp_ep_state_string(u8 state) +{ + switch (state) { + case EP_STATE_DISABLED: + return "disabled"; + case EP_STATE_RUNNING: + return "running"; + case EP_STATE_HALTED: + return "halted"; + case EP_STATE_STOPPED: + return "stopped"; + case EP_STATE_ERROR: + return "error"; + default: + return "INVALID"; + } +} + +static inline const char *cdnsp_ep_type_string(u8 type) +{ + switch (type) { + case ISOC_OUT_EP: + return "Isoc OUT"; + case BULK_OUT_EP: + return "Bulk OUT"; + case INT_OUT_EP: + return "Int OUT"; + case CTRL_EP: + return "Ctrl"; + case ISOC_IN_EP: + return "Isoc IN"; + case BULK_IN_EP: + return "Bulk IN"; + case INT_IN_EP: + return "Int IN"; + default: + return "INVALID"; + } +} + +static inline const char *cdnsp_decode_ep_context(char *str, size_t size, + u32 info, u32 info2, + u64 deq, u32 tx_info) +{ + u8 max_pstr, ep_state, interval, ep_type, burst, cerr, mult; + bool lsa, hid; + u16 maxp, avg; + u32 esit; + int ret; + + esit = CTX_TO_MAX_ESIT_PAYLOAD_HI(info) << 16 | + CTX_TO_MAX_ESIT_PAYLOAD_LO(tx_info); + + ep_state = info & EP_STATE_MASK; + max_pstr = CTX_TO_EP_MAXPSTREAMS(info); + interval = CTX_TO_EP_INTERVAL(info); + mult = CTX_TO_EP_MULT(info) + 1; + lsa = !!(info & EP_HAS_LSA); + + cerr = (info2 & (3 << 1)) >> 1; + ep_type = CTX_TO_EP_TYPE(info2); + hid = !!(info2 & (1 << 7)); + burst = CTX_TO_MAX_BURST(info2); + maxp = MAX_PACKET_DECODED(info2); + + avg = EP_AVG_TRB_LENGTH(tx_info); + + ret = snprintf(str, size, "State %s mult %d max P. Streams %d %s", + cdnsp_ep_state_string(ep_state), mult, + max_pstr, lsa ? "LSA " : ""); + + ret += snprintf(str + ret, size - ret, + "interval %d us max ESIT payload %d CErr %d ", + (1 << interval) * 125, esit, cerr); + + ret += snprintf(str + ret, size - ret, + "Type %s %sburst %d maxp %d deq %016llx ", + cdnsp_ep_type_string(ep_type), hid ? "HID" : "", + burst, maxp, deq); + + ret += snprintf(str + ret, size - ret, "avg trb len %d", avg); + + return str; +} + +#endif /*__LINUX_CDNSP_DEBUG*/ diff --git a/drivers/usb/cdns3/cdnsp-ep0.c b/drivers/usb/cdns3/cdnsp-ep0.c index aa220d06d887..d55b59ed7381 100644 --- a/drivers/usb/cdns3/cdnsp-ep0.c +++ b/drivers/usb/cdns3/cdnsp-ep0.c @@ -13,6 +13,7 @@ #include #include "cdnsp-gadget.h" +#include "cdnsp-trace.h" static void cdnsp_ep0_stall(struct cdnsp_device *pdev) { @@ -23,11 +24,13 @@ static void cdnsp_ep0_stall(struct cdnsp_device *pdev) preq = next_request(&pep->pending_list); if (pdev->three_stage_setup) { + trace_cdnsp_ep0_data_stage("send stall"); cdnsp_halt_endpoint(pdev, pep, true); if (preq) cdnsp_gadget_giveback(pep, preq, -ECONNRESET); } else { + trace_cdnsp_ep0_status_stage("send stall"); pep->ep_state |= EP0_HALTED_STATUS; if (preq) @@ -42,6 +45,8 @@ static int cdnsp_ep0_delegate_req(struct cdnsp_device *pdev, { int ret; + trace_cdnsp_ep0_request("delagete"); + spin_unlock(&pdev->lock); ret = pdev->gadget_driver->setup(&pdev->gadget, ctrl); spin_lock(&pdev->lock); @@ -60,8 +65,10 @@ static int cdnsp_ep0_set_config(struct cdnsp_device *pdev, switch (state) { case USB_STATE_ADDRESS: + trace_cdnsp_ep0_set_config("from Address state"); break; case USB_STATE_CONFIGURED: + trace_cdnsp_ep0_set_config("from Configured state"); break; default: dev_err(pdev->dev, "Set Configuration - bad device state\n"); @@ -123,6 +130,7 @@ static int cdnsp_ep0_set_address(struct cdnsp_device *pdev, int cdnsp_status_stage(struct cdnsp_device *pdev) { + trace_cdnsp_ep0_status_stage("preparing"); pdev->ep0_stage = CDNSP_STATUS_STAGE; pdev->ep0_preq.request.length = 0; @@ -211,18 +219,21 @@ static int cdnsp_ep0_handle_feature_device(struct cdnsp_device *pdev, switch (le16_to_cpu(ctrl->wValue)) { case USB_DEVICE_REMOTE_WAKEUP: pdev->may_wakeup = !!set; + trace_cdnsp_may_wakeup(set); break; case USB_DEVICE_U1_ENABLE: if (state != USB_STATE_CONFIGURED || speed < USB_SPEED_SUPER) return -EINVAL; pdev->u1_allowed = !!set; + trace_cdnsp_u1(set); break; case USB_DEVICE_U2_ENABLE: if (state != USB_STATE_CONFIGURED || speed < USB_SPEED_SUPER) return -EINVAL; pdev->u2_allowed = !!set; + trace_cdnsp_u2(set); break; case USB_DEVICE_LTM_ENABLE: return -EINVAL; @@ -426,6 +437,8 @@ void cdnsp_setup_analyze(struct cdnsp_device *pdev) int ret = 0; __le16 len; + trace_cdnsp_ctrl_req(ctrl); + if (!pdev->gadget_driver) goto out; @@ -436,8 +449,10 @@ void cdnsp_setup_analyze(struct cdnsp_device *pdev) } /* Restore the ep0 to Stopped/Running state. */ - if (pdev->eps[0].ep_state & EP_HALTED) + if (pdev->eps[0].ep_state & EP_HALTED) { + trace_cdnsp_ep0_halted("Restore to normal state"); cdnsp_halt_endpoint(pdev, &pdev->eps[0], 0); + } /* * Finishing previous SETUP transfer by removing request from @@ -446,6 +461,7 @@ void cdnsp_setup_analyze(struct cdnsp_device *pdev) if (!list_empty(&pdev->eps[0].pending_list)) { struct cdnsp_request *req; + trace_cdnsp_ep0_request("Remove previous"); req = next_request(&pdev->eps[0].pending_list); cdnsp_ep_dequeue(&pdev->eps[0], req); } @@ -467,8 +483,10 @@ void cdnsp_setup_analyze(struct cdnsp_device *pdev) if (!len) pdev->ep0_stage = CDNSP_STATUS_STAGE; - if (ret == USB_GADGET_DELAYED_STATUS) + if (ret == USB_GADGET_DELAYED_STATUS) { + trace_cdnsp_ep0_status_stage("delayed"); return; + } out: if (ret < 0) cdnsp_ep0_stall(pdev); diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c index 18bb4c0d1e08..97162739a5ae 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.c +++ b/drivers/usb/cdns3/cdnsp-gadget.c @@ -23,6 +23,7 @@ #include "gadget-export.h" #include "drd.h" #include "cdnsp-gadget.h" +#include "cdnsp-trace.h" unsigned int cdnsp_port_speed(unsigned int port_status) { @@ -100,6 +101,7 @@ void cdnsp_set_link_state(struct cdnsp_device *pdev, __le32 __iomem *port_regs, u32 link_state) { + int port_num = 0xFF; u32 temp; temp = readl(port_regs); @@ -110,7 +112,12 @@ void cdnsp_set_link_state(struct cdnsp_device *pdev, temp &= ~PORT_PLS_MASK; temp |= PORT_LINK_STROBE | link_state; + if (pdev->active_port) + port_num = pdev->active_port->port_num; + + trace_cdnsp_handle_port_status(port_num, readl(port_regs)); writel(temp, port_regs); + trace_cdnsp_link_state_changed(port_num, readl(port_regs)); } static void cdnsp_disable_port(struct cdnsp_device *pdev, @@ -230,6 +237,8 @@ static int cdnsp_start(struct cdnsp_device *pdev) temp |= (CMD_R_S | CMD_DEVEN); writel(temp, &pdev->op_regs->command); + trace_cdnsp_init("Turn on controller"); + pdev->cdnsp_state = 0; /* @@ -339,8 +348,10 @@ int cdnsp_ep_enqueue(struct cdnsp_ep *pep, struct cdnsp_request *preq) struct usb_request *request; int ret; - if (preq->epnum == 0 && !list_empty(&pep->pending_list)) + if (preq->epnum == 0 && !list_empty(&pep->pending_list)) { + trace_cdnsp_request_enqueue_busy(preq); return -EBUSY; + } request = &preq->request; request->actual = 0; @@ -350,11 +361,15 @@ int cdnsp_ep_enqueue(struct cdnsp_ep *pep, struct cdnsp_request *preq) preq->td.drbl = 0; ret = usb_gadget_map_request_by_dev(pdev->dev, request, pep->direction); - if (ret) + if (ret) { + trace_cdnsp_request_enqueue_error(preq); return ret; + } list_add_tail(&preq->list, &pep->pending_list); + trace_cdnsp_request_enqueue(preq); + switch (usb_endpoint_type(pep->endpoint.desc)) { case USB_ENDPOINT_XFER_CONTROL: ret = cdnsp_queue_ctrl_tx(pdev, preq); @@ -376,6 +391,7 @@ unmap: usb_gadget_unmap_request_by_dev(pdev->dev, &preq->request, pep->direction); list_del(&preq->list); + trace_cdnsp_request_enqueue_error(preq); return ret; } @@ -410,6 +426,8 @@ int cdnsp_ep_dequeue(struct cdnsp_ep *pep, struct cdnsp_request *preq) struct cdnsp_device *pdev = pep->pdev; int ret; + trace_cdnsp_request_dequeue(preq); + if (GET_EP_CTX_STATE(pep->out_ctx) == EP_STATE_RUNNING) { ret = cdnsp_cmd_stop_ep(pdev, pep); if (ret) @@ -516,11 +534,14 @@ int cdnsp_wait_for_cmd_compl(struct cdnsp_device *pdev) cmd_trb = pdev->cmd.command_trb; pdev->cmd.status = 0; + trace_cdnsp_cmd_wait_for_compl(pdev->cmd_ring, &cmd_trb->generic); + ret = readl_poll_timeout_atomic(&pdev->op_regs->cmd_ring, val, !CMD_RING_BUSY(val), 1, CDNSP_CMD_TIMEOUT); if (ret) { dev_err(pdev->dev, "ERR: Timeout while waiting for command\n"); + trace_cdnsp_cmd_timeout(pdev->cmd_ring, &cmd_trb->generic); pdev->cdnsp_state = CDNSP_STATE_DYING; return -ETIMEDOUT; } @@ -562,6 +583,8 @@ int cdnsp_wait_for_cmd_compl(struct cdnsp_device *pdev) continue; } + trace_cdnsp_handle_command(pdev->cmd_ring, &cmd_trb->generic); + pdev->cmd.status = GET_COMP_CODE(le32_to_cpu(event->event_cmd.status)); if (pdev->cmd.status == COMP_SUCCESS) return 0; @@ -576,6 +599,8 @@ int cdnsp_halt_endpoint(struct cdnsp_device *pdev, { int ret; + trace_cdnsp_ep_halt(value ? "Set" : "Clear"); + if (value) { ret = cdnsp_cmd_stop_ep(pdev, pep); if (ret) @@ -596,6 +621,8 @@ int cdnsp_halt_endpoint(struct cdnsp_device *pdev, cdnsp_queue_reset_ep(pdev, pep->idx); cdnsp_ring_cmd_db(pdev); ret = cdnsp_wait_for_cmd_compl(pdev); + trace_cdnsp_handle_cmd_reset_ep(pep->out_ctx); + if (ret) return ret; @@ -649,6 +676,9 @@ static int cdnsp_update_eps_configuration(struct cdnsp_device *pdev, (ep_sts != EP_STATE_DISABLED && ctrl_ctx->drop_flags)) ret = cdnsp_configure_endpoint(pdev); + trace_cdnsp_configure_endpoint(cdnsp_get_slot_ctx(&pdev->out_ctx)); + trace_cdnsp_handle_cmd_config_ep(pep->out_ctx); + cdnsp_zero_in_ctx(pdev); return ret; @@ -673,6 +703,7 @@ int cdnsp_reset_device(struct cdnsp_device *pdev) /* If device is not setup, there is no point in resetting it. */ slot_ctx = cdnsp_get_slot_ctx(&pdev->out_ctx); slot_state = GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state)); + trace_cdnsp_reset_device(slot_ctx); if (slot_state <= SLOT_STATE_DEFAULT && pdev->eps[0].ep_state & EP_HALTED) { @@ -700,6 +731,8 @@ int cdnsp_reset_device(struct cdnsp_device *pdev) for (i = 1; i < CDNSP_ENDPOINTS_NUM; ++i) pdev->eps[i].ep_state |= EP_STOPPED; + trace_cdnsp_handle_cmd_reset_dev(slot_ctx); + if (ret) dev_err(pdev->dev, "Reset device failed with error code %d", ret); @@ -756,6 +789,8 @@ int cdnsp_alloc_streams(struct cdnsp_device *pdev, struct cdnsp_ep *pep) /* The stream context array size must be a power of two */ num_stream_ctxs = roundup_pow_of_two(num_streams); + trace_cdnsp_stream_number(pep, num_stream_ctxs, num_streams); + ret = cdnsp_alloc_stream_info(pdev, pep, num_stream_ctxs, num_streams); if (ret) return ret; @@ -781,6 +816,8 @@ int cdnsp_disable_slot(struct cdnsp_device *pdev) pdev->slot_id = 0; pdev->active_port = NULL; + trace_cdnsp_handle_cmd_disable_slot(cdnsp_get_slot_ctx(&pdev->out_ctx)); + memset(pdev->in_ctx.bytes, 0, CDNSP_CTX_SIZE); memset(pdev->out_ctx.bytes, 0, CDNSP_CTX_SIZE); @@ -804,11 +841,14 @@ int cdnsp_enable_slot(struct cdnsp_device *pdev) cdnsp_ring_cmd_db(pdev); ret = cdnsp_wait_for_cmd_compl(pdev); if (ret) - return ret; + goto show_trace; pdev->slot_id = 1; - return 0; +show_trace: + trace_cdnsp_handle_cmd_enable_slot(cdnsp_get_slot_ctx(&pdev->out_ctx)); + + return ret; } /* @@ -822,8 +862,10 @@ int cdnsp_setup_device(struct cdnsp_device *pdev, enum cdnsp_setup_dev setup) int dev_state = 0; int ret; - if (!pdev->slot_id) + if (!pdev->slot_id) { + trace_cdnsp_slot_id("incorrect"); return -EINVAL; + } if (!pdev->active_port->port_num) return -EINVAL; @@ -831,8 +873,10 @@ int cdnsp_setup_device(struct cdnsp_device *pdev, enum cdnsp_setup_dev setup) slot_ctx = cdnsp_get_slot_ctx(&pdev->out_ctx); dev_state = GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state)); - if (setup == SETUP_CONTEXT_ONLY && dev_state == SLOT_STATE_DEFAULT) + if (setup == SETUP_CONTEXT_ONLY && dev_state == SLOT_STATE_DEFAULT) { + trace_cdnsp_slot_already_in_default(slot_ctx); return 0; + } slot_ctx = cdnsp_get_slot_ctx(&pdev->in_ctx); ctrl_ctx = cdnsp_get_input_control_ctx(&pdev->in_ctx); @@ -848,10 +892,14 @@ int cdnsp_setup_device(struct cdnsp_device *pdev, enum cdnsp_setup_dev setup) ctrl_ctx->add_flags = cpu_to_le32(SLOT_FLAG | EP0_FLAG); ctrl_ctx->drop_flags = 0; + trace_cdnsp_setup_device_slot(slot_ctx); + cdnsp_queue_address_device(pdev, pdev->in_ctx.dma, setup); cdnsp_ring_cmd_db(pdev); ret = cdnsp_wait_for_cmd_compl(pdev); + trace_cdnsp_handle_cmd_addr_dev(cdnsp_get_slot_ctx(&pdev->out_ctx)); + /* Zero the input context control for later use. */ ctrl_ctx->add_flags = 0; ctrl_ctx->drop_flags = 0; @@ -866,6 +914,8 @@ void cdnsp_set_usb2_hardware_lpm(struct cdnsp_device *pdev, if (pdev->active_port != &pdev->usb2_port || !pdev->gadget.lpm_capable) return; + trace_cdnsp_lpm(enable); + if (enable) writel(PORT_BESL(CDNSP_DEFAULT_BESL) | PORT_L1S_NYET | PORT_HLE, &pdev->active_port->regs->portpmsc); @@ -945,6 +995,7 @@ static int cdnsp_gadget_ep_enable(struct usb_ep *ep, pep->ep_state &= ~EP_STOPPED; unlock: + trace_cdnsp_ep_enable_end(pep, 0); spin_unlock_irqrestore(&pdev->lock, flags); return ret; @@ -1001,6 +1052,7 @@ static int cdnsp_gadget_ep_disable(struct usb_ep *ep) pep->ep_state |= EP_STOPPED; finish: + trace_cdnsp_ep_disable_end(pep, 0); spin_unlock_irqrestore(&pdev->lock, flags); return ret; @@ -1019,6 +1071,8 @@ static struct usb_request *cdnsp_gadget_ep_alloc_request(struct usb_ep *ep, preq->epnum = pep->number; preq->pep = pep; + trace_cdnsp_alloc_request(preq); + return &preq->request; } @@ -1027,6 +1081,7 @@ static void cdnsp_gadget_ep_free_request(struct usb_ep *ep, { struct cdnsp_request *preq = to_cdnsp_request(request); + trace_cdnsp_free_request(preq); kfree(preq); } @@ -1095,6 +1150,7 @@ static int cdnsp_gadget_ep_set_halt(struct usb_ep *ep, int value) preq = next_request(&pep->pending_list); if (value) { if (preq) { + trace_cdnsp_ep_busy_try_halt_again(pep, 0); ret = -EAGAIN; goto done; } @@ -1158,6 +1214,8 @@ void cdnsp_gadget_giveback(struct cdnsp_ep *pep, usb_gadget_unmap_request_by_dev(pdev->dev, &preq->request, preq->direction); + trace_cdnsp_request_giveback(preq); + if (preq != &pdev->ep0_preq) { spin_unlock(&pdev->lock); usb_gadget_giveback_request(&pep->endpoint, &preq->request); @@ -1238,6 +1296,7 @@ static int cdnsp_run(struct cdnsp_device *pdev, temp = readl(&pdev->ir_set->irq_pending); writel(IMAN_IE_SET(temp), &pdev->ir_set->irq_pending); + trace_cdnsp_init("Controller ready to work"); return 0; err: cdnsp_halt(pdev); @@ -1390,6 +1449,8 @@ static void cdnsp_stop(struct cdnsp_device *pdev) cdnsp_consume_all_events(pdev); cdnsp_clear_cmd_ring(pdev); + + trace_cdnsp_exit("Controller stopped."); } /* @@ -1471,6 +1532,8 @@ static int cdnsp_gadget_pullup(struct usb_gadget *gadget, int is_on) struct cdnsp_device *pdev = gadget_to_cdnsp(gadget); struct cdns *cdns = dev_get_drvdata(pdev->dev); + trace_cdnsp_pullup(is_on); + if (!is_on) { cdnsp_reset_device(pdev); cdns_clear_vbus(cdns); diff --git a/drivers/usb/cdns3/cdnsp-mem.c b/drivers/usb/cdns3/cdnsp-mem.c index ccabf3e5cce9..6a0a12e1f54c 100644 --- a/drivers/usb/cdns3/cdnsp-mem.c +++ b/drivers/usb/cdns3/cdnsp-mem.c @@ -16,6 +16,7 @@ #include #include "cdnsp-gadget.h" +#include "cdnsp-trace.h" static void cdnsp_free_stream_info(struct cdnsp_device *pdev, struct cdnsp_ep *pep); @@ -281,6 +282,8 @@ static void cdnsp_ring_free(struct cdnsp_device *pdev, struct cdnsp_ring *ring) if (!ring) return; + trace_cdnsp_ring_free(ring); + if (ring->first_seg) { if (ring->type == TYPE_STREAM) cdnsp_remove_stream_mapping(ring); @@ -397,7 +400,7 @@ static struct cdnsp_ring *cdnsp_ring_alloc(struct cdnsp_device *pdev, cpu_to_le32(LINK_TOGGLE); cdnsp_initialize_ring_info(ring); - + trace_cdnsp_ring_alloc(ring); return ring; fail: kfree(ring); @@ -450,6 +453,7 @@ int cdnsp_ring_expansion(struct cdnsp_device *pdev, } cdnsp_link_rings(pdev, ring, first, last, num_segs); + trace_cdnsp_ring_expansion(ring); return 0; } @@ -610,6 +614,8 @@ int cdnsp_alloc_stream_info(struct cdnsp_device *pdev, stream_info->stream_ctx_array[cur_stream].stream_ring = cpu_to_le64(addr); + trace_cdnsp_set_stream_ring(cur_ring); + ret = cdnsp_update_stream_mapping(cur_ring); if (ret) goto cleanup_rings; @@ -695,6 +701,7 @@ static int cdnsp_alloc_priv_device(struct cdnsp_device *pdev, gfp_t flags) pdev->dcbaa->dev_context_ptrs[1] = cpu_to_le64(pdev->out_ctx.dma); pdev->cmd.in_ctx = &pdev->in_ctx; + trace_cdnsp_alloc_priv_device(pdev); return 0; fail: dma_pool_free(pdev->device_pool, pdev->out_ctx.bytes, @@ -761,6 +768,8 @@ int cdnsp_setup_addressable_priv_dev(struct cdnsp_device *pdev) ep0_ctx->deq = cpu_to_le64(pdev->eps[0].ring->first_seg->dma | pdev->eps[0].ring->cycle_state); + trace_cdnsp_setup_addressable_priv_device(pdev); + return 0; } @@ -1112,7 +1121,7 @@ static void cdnsp_add_in_port(struct cdnsp_device *pdev, struct cdnsp_port *port, __le32 __iomem *addr) { - u32 temp, port_offset; + u32 temp, port_offset, port_count; temp = readl(addr); port->maj_rev = CDNSP_EXT_PORT_MAJOR(temp); @@ -1121,6 +1130,9 @@ static void cdnsp_add_in_port(struct cdnsp_device *pdev, /* Port offset and count in the third dword.*/ temp = readl(addr + 2); port_offset = CDNSP_EXT_PORT_OFF(temp); + port_count = CDNSP_EXT_PORT_COUNT(temp); + + trace_cdnsp_port_info(addr, port_offset, port_count, port->maj_rev); port->port_num = port_offset; port->exist = 1; @@ -1171,6 +1183,8 @@ static int cdnsp_setup_port_arrays(struct cdnsp_device *pdev, gfp_t flags) return -ENODEV; } + trace_cdnsp_init("Found USB 2.0 ports and USB 3.0 ports."); + pdev->usb2_port.regs = (struct cdnsp_port_regs *) (&pdev->op_regs->port_reg_base + NUM_PORT_REGS * (pdev->usb2_port.port_num - 1)); diff --git a/drivers/usb/cdns3/cdnsp-ring.c b/drivers/usb/cdns3/cdnsp-ring.c index a28faca41a8f..874d9ff5406c 100644 --- a/drivers/usb/cdns3/cdnsp-ring.c +++ b/drivers/usb/cdns3/cdnsp-ring.c @@ -61,6 +61,7 @@ #include #include +#include "cdnsp-trace.h" #include "cdnsp-gadget.h" /* @@ -148,7 +149,7 @@ void cdnsp_inc_deq(struct cdnsp_device *pdev, struct cdnsp_ring *ring) if (ring->type == TYPE_EVENT) { if (!cdnsp_last_trb_on_seg(ring->deq_seg, ring->dequeue)) { ring->dequeue++; - return; + goto out; } if (cdnsp_last_trb_on_ring(ring, ring->deq_seg, ring->dequeue)) @@ -156,7 +157,7 @@ void cdnsp_inc_deq(struct cdnsp_device *pdev, struct cdnsp_ring *ring) ring->deq_seg = ring->deq_seg->next; ring->dequeue = ring->deq_seg->trbs; - return; + goto out; } /* All other rings have link trbs. */ @@ -168,6 +169,8 @@ void cdnsp_inc_deq(struct cdnsp_device *pdev, struct cdnsp_ring *ring) ring->deq_seg = ring->deq_seg->next; ring->dequeue = ring->deq_seg->trbs; } +out: + trace_cdnsp_inc_deq(ring); } /* @@ -222,6 +225,8 @@ static void cdnsp_inc_enq(struct cdnsp_device *pdev, ring->enqueue = ring->enq_seg->trbs; next = ring->enqueue; } + + trace_cdnsp_inc_enq(ring); } /* @@ -261,6 +266,7 @@ static void cdnsp_force_l0_go(struct cdnsp_device *pdev) /* Ring the doorbell after placing a command on the ring. */ void cdnsp_ring_cmd_db(struct cdnsp_device *pdev) { + trace_cdnsp_cmd_drbl("Ding Dong"); writel(DB_VALUE_CMD, &pdev->dba->cmd_db); } @@ -299,6 +305,8 @@ static bool cdnsp_ring_ep_doorbell(struct cdnsp_device *pdev, else db_value = DB_VALUE(pep->idx, stream_id); + trace_cdnsp_tr_drbl(pep, stream_id); + writel(db_value, reg_addr); cdnsp_force_l0_go(pdev); @@ -484,6 +492,8 @@ static void cdnsp_find_new_dequeue_state(struct cdnsp_device *pdev, state->new_deq_seg = new_seg; state->new_deq_ptr = new_deq; + + trace_cdnsp_new_deq_state(state); } /* @@ -544,6 +554,10 @@ static struct cdnsp_segment *cdnsp_trb_in_td(struct cdnsp_device *pdev, /* If the end TRB isn't in this segment, this is set to 0 */ end_trb_dma = cdnsp_trb_virt_to_dma(cur_seg, end_trb); + trace_cdnsp_looking_trb_in_td(suspect_dma, start_dma, + end_trb_dma, cur_seg->dma, + end_seg_dma); + if (end_trb_dma > 0) { /* * The end TRB is in this segment, so suspect should @@ -594,6 +608,9 @@ static void cdnsp_unmap_td_bounce_buffer(struct cdnsp_device *pdev, preq = td->preq; + trace_cdnsp_bounce_unmap(td->preq, seg->bounce_len, seg->bounce_offs, + seg->bounce_dma, 0); + if (!preq->direction) { dma_unmap_single(pdev->dev, seg->bounce_dma, ring->bounce_buf_len, DMA_TO_DEVICE); @@ -631,6 +648,9 @@ static int cdnsp_cmd_set_deq(struct cdnsp_device *pdev, cdnsp_ring_cmd_db(pdev); ret = cdnsp_wait_for_cmd_compl(pdev); + trace_cdnsp_handle_cmd_set_deq(cdnsp_get_slot_ctx(&pdev->out_ctx)); + trace_cdnsp_handle_cmd_set_deq_ep(pep->out_ctx); + /* * Update the ring's dequeue segment and dequeue pointer * to reflect the new position. @@ -682,6 +702,9 @@ int cdnsp_remove_request(struct cdnsp_device *pdev, memset(&deq_state, 0, sizeof(deq_state)); + trace_cdnsp_remove_request(pep->out_ctx); + trace_cdnsp_remove_request_td(preq); + cur_td = &preq->td; ep_ring = cdnsp_request_to_transfer_ring(pdev, preq); @@ -789,6 +812,8 @@ new_event: writel(cdnsp_port_state_to_neutral(portsc) | (portsc & PORT_CHANGE_BITS), &port_regs->portsc); + trace_cdnsp_handle_port_status(pdev->active_port->port_num, portsc); + pdev->gadget.speed = cdnsp_port_speed(portsc); link_state = portsc & PORT_PLS_MASK; @@ -954,8 +979,10 @@ static int cdnsp_giveback_first_trb(struct cdnsp_device *pdev, start_trb->field[3] &= cpu_to_le32(~TRB_CYCLE); if ((pep->ep_state & EP_HAS_STREAMS) && - !pep->stream_info.first_prime_det) + !pep->stream_info.first_prime_det) { + trace_cdnsp_wait_for_prime(pep, stream_id); return 0; + } return cdnsp_ring_ep_doorbell(pdev, pep, stream_id); } @@ -1226,8 +1253,10 @@ static int cdnsp_handle_tx_event(struct cdnsp_device *pdev, if (invalidate || !pdev->gadget.connected) goto cleanup; - if (GET_EP_CTX_STATE(pep->out_ctx) == EP_STATE_DISABLED) + if (GET_EP_CTX_STATE(pep->out_ctx) == EP_STATE_DISABLED) { + trace_cdnsp_ep_disabled(pep->out_ctx); goto err_out; + } /* Some transfer events don't always point to a trb*/ if (!ep_ring) { @@ -1274,8 +1303,23 @@ static int cdnsp_handle_tx_event(struct cdnsp_device *pdev, * list. */ if (list_empty(&ep_ring->td_list)) { - if (pep->skip) + /* + * Don't print warnings if it's due to a stopped + * endpoint generating an extra completion event, or + * a event for the last TRB of a short TD we already + * got a short event for. + * The short TD is already removed from the TD list. + */ + if (!(trb_comp_code == COMP_STOPPED || + trb_comp_code == COMP_STOPPED_LENGTH_INVALID || + ep_ring->last_td_was_short)) + trace_cdnsp_trb_without_td(ep_ring, + (struct cdnsp_generic_trb *)event); + + if (pep->skip) { pep->skip = false; + trace_cdnsp_ep_list_empty_with_skip(pep, 0); + } goto cleanup; } @@ -1332,6 +1376,9 @@ static int cdnsp_handle_tx_event(struct cdnsp_device *pdev, ep_trb = &ep_seg->trbs[(ep_trb_dma - ep_seg->dma) / sizeof(*ep_trb)]; + trace_cdnsp_handle_transfer(ep_ring, + (struct cdnsp_generic_trb *)ep_trb); + if (cdnsp_trb_is_noop(ep_trb)) goto cleanup; @@ -1397,6 +1444,8 @@ static bool cdnsp_handle_event(struct cdnsp_device *pdev) if (cycle_bit != pdev->event_ring->cycle_state) return false; + trace_cdnsp_handle_event(pdev->event_ring, &event->generic); + /* * Barrier between reading the TRB_CYCLE (valid) flag above and any * reads of the event's flags/data below. @@ -1544,6 +1593,7 @@ static void cdnsp_queue_trb(struct cdnsp_device *pdev, struct cdnsp_ring *ring, trb->field[2] = cpu_to_le32(field3); trb->field[3] = cpu_to_le32(field4); + trace_cdnsp_queue_trb(ring, trb); cdnsp_inc_enq(pdev, ring, more_trbs_coming); } @@ -1574,6 +1624,8 @@ static int cdnsp_prepare_ring(struct cdnsp_device *pdev, if (cdnsp_room_on_ring(pdev, ep_ring, num_trbs)) break; + trace_cdnsp_no_room_on_ring("try ring expansion"); + num_trbs_needed = num_trbs - ep_ring->num_trbs_free; if (cdnsp_ring_expansion(pdev, ep_ring, num_trbs_needed, mem_flags)) { @@ -1737,6 +1789,8 @@ static int cdnsp_align_td(struct cdnsp_device *pdev, /* Is the last nornal TRB alignable by splitting it. */ if (*trb_buff_len > unalign) { *trb_buff_len -= unalign; + trace_cdnsp_bounce_align_td_split(preq, *trb_buff_len, + enqd_len, 0, unalign); return 0; } @@ -1773,6 +1827,9 @@ static int cdnsp_align_td(struct cdnsp_device *pdev, seg->bounce_len = new_buff_len; seg->bounce_offs = enqd_len; + trace_cdnsp_bounce_map(preq, new_buff_len, enqd_len, seg->bounce_dma, + unalign); + /* * Bounce buffer successful aligned and seg->bounce_dma will be used * in transfer TRB as new transfer buffer address. @@ -2009,13 +2066,17 @@ int cdnsp_cmd_stop_ep(struct cdnsp_device *pdev, struct cdnsp_ep *pep) u32 ep_state = GET_EP_CTX_STATE(pep->out_ctx); int ret = 0; - if (ep_state == EP_STATE_STOPPED || ep_state == EP_STATE_DISABLED) + if (ep_state == EP_STATE_STOPPED || ep_state == EP_STATE_DISABLED) { + trace_cdnsp_ep_stopped_or_disabled(pep->out_ctx); goto ep_stopped; + } cdnsp_queue_stop_endpoint(pdev, pep->idx); cdnsp_ring_cmd_db(pdev); ret = cdnsp_wait_for_cmd_compl(pdev); + trace_cdnsp_handle_cmd_stop_ep(pep->out_ctx); + ep_stopped: pep->ep_state |= EP_STOPPED; return ret; @@ -2029,6 +2090,8 @@ int cdnsp_cmd_flush_ep(struct cdnsp_device *pdev, struct cdnsp_ep *pep) cdnsp_ring_cmd_db(pdev); ret = cdnsp_wait_for_cmd_compl(pdev); + trace_cdnsp_handle_cmd_flush_ep(pep->out_ctx); + return ret; } diff --git a/drivers/usb/cdns3/cdnsp-trace.c b/drivers/usb/cdns3/cdnsp-trace.c new file mode 100644 index 000000000000..e50ab799ad95 --- /dev/null +++ b/drivers/usb/cdns3/cdnsp-trace.c @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence CDNSP DRD Driver. + * + * Copyright (C) 2020 Cadence. + * + * Author: Pawel Laszczak + * + */ + +#define CREATE_TRACE_POINTS +#include "cdnsp-trace.h" diff --git a/drivers/usb/cdns3/cdnsp-trace.h b/drivers/usb/cdns3/cdnsp-trace.h new file mode 100644 index 000000000000..b68e282464d2 --- /dev/null +++ b/drivers/usb/cdns3/cdnsp-trace.h @@ -0,0 +1,840 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Cadence CDNSP DRD Driver. + * Trace support header file + * + * Copyright (C) 2020 Cadence. + * + * Author: Pawel Laszczak + * + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM cdnsp-dev + +/* + * The TRACE_SYSTEM_VAR defaults to TRACE_SYSTEM, but must be a + * legitimate C variable. It is not exported to user space. + */ +#undef TRACE_SYSTEM_VAR +#define TRACE_SYSTEM_VAR cdnsp_dev + +#if !defined(__CDNSP_DEV_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define __CDNSP_DEV_TRACE_H + +#include +#include "cdnsp-gadget.h" +#include "cdnsp-debug.h" + +/* + * There is limitation for single buffer size in TRACEPOINT subsystem. + * By default TRACE_BUF_SIZE is 1024, so no all data will be logged. + * To show more data this must be increased. In most cases the default + * value is sufficient. + */ +#define CDNSP_MSG_MAX 500 + +DECLARE_EVENT_CLASS(cdnsp_log_ep, + TP_PROTO(struct cdnsp_ep *pep, u32 stream_id), + TP_ARGS(pep, stream_id), + TP_STRUCT__entry( + __string(name, pep->name) + __field(unsigned int, state) + __field(u32, stream_id) + __field(u8, enabled) + __field(unsigned int, num_streams) + __field(int, td_count) + __field(u8, first_prime_det) + __field(u8, drbls_count) + ), + TP_fast_assign( + __assign_str(name, pep->name); + __entry->state = pep->ep_state; + __entry->stream_id = stream_id; + __entry->enabled = pep->ep_state & EP_HAS_STREAMS; + __entry->num_streams = pep->stream_info.num_streams; + __entry->td_count = pep->stream_info.td_count; + __entry->first_prime_det = pep->stream_info.first_prime_det; + __entry->drbls_count = pep->stream_info.drbls_count; + ), + TP_printk("%s: SID: %08x ep state: %x stream: enabled: %d num %d " + "tds %d, first prime: %d drbls %d", + __get_str(name), __entry->state, __entry->stream_id, + __entry->enabled, __entry->num_streams, __entry->td_count, + __entry->first_prime_det, __entry->drbls_count) +); + +DEFINE_EVENT(cdnsp_log_ep, cdnsp_tr_drbl, + TP_PROTO(struct cdnsp_ep *pep, u32 stream_id), + TP_ARGS(pep, stream_id) +); + +DEFINE_EVENT(cdnsp_log_ep, cdnsp_wait_for_prime, + TP_PROTO(struct cdnsp_ep *pep, u32 stream_id), + TP_ARGS(pep, stream_id) +); + +DEFINE_EVENT(cdnsp_log_ep, cdnsp_ep_list_empty_with_skip, + TP_PROTO(struct cdnsp_ep *pep, u32 stream_id), + TP_ARGS(pep, stream_id) +); + +DEFINE_EVENT(cdnsp_log_ep, cdnsp_ep_enable_end, + TP_PROTO(struct cdnsp_ep *pep, u32 stream_id), + TP_ARGS(pep, stream_id) +); + +DEFINE_EVENT(cdnsp_log_ep, cdnsp_ep_disable_end, + TP_PROTO(struct cdnsp_ep *pep, u32 stream_id), + TP_ARGS(pep, stream_id) +); + +DEFINE_EVENT(cdnsp_log_ep, cdnsp_ep_busy_try_halt_again, + TP_PROTO(struct cdnsp_ep *pep, u32 stream_id), + TP_ARGS(pep, stream_id) +); + +DECLARE_EVENT_CLASS(cdnsp_log_enable_disable, + TP_PROTO(int set), + TP_ARGS(set), + TP_STRUCT__entry( + __field(int, set) + ), + TP_fast_assign( + __entry->set = set; + ), + TP_printk("%s", __entry->set ? "enabled" : "disabled") +); + +DEFINE_EVENT(cdnsp_log_enable_disable, cdnsp_pullup, + TP_PROTO(int set), + TP_ARGS(set) +); + +DEFINE_EVENT(cdnsp_log_enable_disable, cdnsp_u1, + TP_PROTO(int set), + TP_ARGS(set) +); + +DEFINE_EVENT(cdnsp_log_enable_disable, cdnsp_u2, + TP_PROTO(int set), + TP_ARGS(set) +); + +DEFINE_EVENT(cdnsp_log_enable_disable, cdnsp_lpm, + TP_PROTO(int set), + TP_ARGS(set) +); + +DEFINE_EVENT(cdnsp_log_enable_disable, cdnsp_may_wakeup, + TP_PROTO(int set), + TP_ARGS(set) +); + +DECLARE_EVENT_CLASS(cdnsp_log_simple, + TP_PROTO(char *msg), + TP_ARGS(msg), + TP_STRUCT__entry( + __string(text, msg) + ), + TP_fast_assign( + __assign_str(text, msg) + ), + TP_printk("%s", __get_str(text)) +); + +DEFINE_EVENT(cdnsp_log_simple, cdnsp_exit, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdnsp_log_simple, cdnsp_init, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdnsp_log_simple, cdnsp_slot_id, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdnsp_log_simple, cdnsp_cmd_drbl, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdnsp_log_simple, cdnsp_no_room_on_ring, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep0_status_stage, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep0_data_stage, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep0_request, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep0_set_config, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep0_halted, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep_halt, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +TRACE_EVENT(cdnsp_looking_trb_in_td, + TP_PROTO(dma_addr_t suspect, dma_addr_t trb_start, dma_addr_t trb_end, + dma_addr_t curr_seg, dma_addr_t end_seg), + TP_ARGS(suspect, trb_start, trb_end, curr_seg, end_seg), + TP_STRUCT__entry( + __field(dma_addr_t, suspect) + __field(dma_addr_t, trb_start) + __field(dma_addr_t, trb_end) + __field(dma_addr_t, curr_seg) + __field(dma_addr_t, end_seg) + ), + TP_fast_assign( + __entry->suspect = suspect; + __entry->trb_start = trb_start; + __entry->trb_end = trb_end; + __entry->curr_seg = curr_seg; + __entry->end_seg = end_seg; + ), + TP_printk("DMA: suspect event: %pad, trb-start: %pad, trb-end %pad, " + "seg-start %pad, seg-end %pad", + &__entry->suspect, &__entry->trb_start, &__entry->trb_end, + &__entry->curr_seg, &__entry->end_seg) +); + +TRACE_EVENT(cdnsp_port_info, + TP_PROTO(__le32 __iomem *addr, u32 offset, u32 count, u32 rev), + TP_ARGS(addr, offset, count, rev), + TP_STRUCT__entry( + __field(__le32 __iomem *, addr) + __field(u32, offset) + __field(u32, count) + __field(u32, rev) + ), + TP_fast_assign( + __entry->addr = addr; + __entry->offset = offset; + __entry->count = count; + __entry->rev = rev; + ), + TP_printk("Ext Cap %p, port offset = %u, count = %u, rev = 0x%x", + __entry->addr, __entry->offset, __entry->count, __entry->rev) +); + +DECLARE_EVENT_CLASS(cdnsp_log_deq_state, + TP_PROTO(struct cdnsp_dequeue_state *state), + TP_ARGS(state), + TP_STRUCT__entry( + __field(int, new_cycle_state) + __field(struct cdnsp_segment *, new_deq_seg) + __field(dma_addr_t, deq_seg_dma) + __field(union cdnsp_trb *, new_deq_ptr) + __field(dma_addr_t, deq_ptr_dma) + ), + TP_fast_assign( + __entry->new_cycle_state = state->new_cycle_state; + __entry->new_deq_seg = state->new_deq_seg; + __entry->deq_seg_dma = state->new_deq_seg->dma; + __entry->new_deq_ptr = state->new_deq_ptr, + __entry->deq_ptr_dma = cdnsp_trb_virt_to_dma(state->new_deq_seg, + state->new_deq_ptr); + ), + TP_printk("New cycle state = 0x%x, New dequeue segment = %p (0x%pad dma), " + "New dequeue pointer = %p (0x%pad dma)", + __entry->new_cycle_state, __entry->new_deq_seg, + &__entry->deq_seg_dma, __entry->new_deq_ptr, + &__entry->deq_ptr_dma + ) +); + +DEFINE_EVENT(cdnsp_log_deq_state, cdnsp_new_deq_state, + TP_PROTO(struct cdnsp_dequeue_state *state), + TP_ARGS(state) +); + +DECLARE_EVENT_CLASS(cdnsp_log_ctrl, + TP_PROTO(struct usb_ctrlrequest *ctrl), + TP_ARGS(ctrl), + TP_STRUCT__entry( + __field(u8, bRequestType) + __field(u8, bRequest) + __field(u16, wValue) + __field(u16, wIndex) + __field(u16, wLength) + __dynamic_array(char, str, CDNSP_MSG_MAX) + ), + TP_fast_assign( + __entry->bRequestType = ctrl->bRequestType; + __entry->bRequest = ctrl->bRequest; + __entry->wValue = le16_to_cpu(ctrl->wValue); + __entry->wIndex = le16_to_cpu(ctrl->wIndex); + __entry->wLength = le16_to_cpu(ctrl->wLength); + ), + TP_printk("%s", usb_decode_ctrl(__get_str(str), CDNSP_MSG_MAX, + __entry->bRequestType, + __entry->bRequest, __entry->wValue, + __entry->wIndex, __entry->wLength) + ) +); + +DEFINE_EVENT(cdnsp_log_ctrl, cdnsp_ctrl_req, + TP_PROTO(struct usb_ctrlrequest *ctrl), + TP_ARGS(ctrl) +); + +DECLARE_EVENT_CLASS(cdnsp_log_bounce, + TP_PROTO(struct cdnsp_request *preq, u32 new_buf_len, u32 offset, + dma_addr_t dma, unsigned int unalign), + TP_ARGS(preq, new_buf_len, offset, dma, unalign), + TP_STRUCT__entry( + __string(name, preq->pep->name) + __field(u32, new_buf_len) + __field(u32, offset) + __field(dma_addr_t, dma) + __field(unsigned int, unalign) + ), + TP_fast_assign( + __assign_str(name, preq->pep->name); + __entry->new_buf_len = new_buf_len; + __entry->offset = offset; + __entry->dma = dma; + __entry->unalign = unalign; + ), + TP_printk("%s buf len %d, offset %d, dma %pad, unalign %d", + __get_str(name), __entry->new_buf_len, + __entry->offset, &__entry->dma, __entry->unalign + ) +); + +DEFINE_EVENT(cdnsp_log_bounce, cdnsp_bounce_align_td_split, + TP_PROTO(struct cdnsp_request *preq, u32 new_buf_len, u32 offset, + dma_addr_t dma, unsigned int unalign), + TP_ARGS(preq, new_buf_len, offset, dma, unalign) +); + +DEFINE_EVENT(cdnsp_log_bounce, cdnsp_bounce_map, + TP_PROTO(struct cdnsp_request *preq, u32 new_buf_len, u32 offset, + dma_addr_t dma, unsigned int unalign), + TP_ARGS(preq, new_buf_len, offset, dma, unalign) +); + +DEFINE_EVENT(cdnsp_log_bounce, cdnsp_bounce_unmap, + TP_PROTO(struct cdnsp_request *preq, u32 new_buf_len, u32 offset, + dma_addr_t dma, unsigned int unalign), + TP_ARGS(preq, new_buf_len, offset, dma, unalign) +); + +DECLARE_EVENT_CLASS(cdnsp_log_trb, + TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb), + TP_ARGS(ring, trb), + TP_STRUCT__entry( + __field(u32, type) + __field(u32, field0) + __field(u32, field1) + __field(u32, field2) + __field(u32, field3) + __field(union cdnsp_trb *, trb) + __field(dma_addr_t, trb_dma) + __dynamic_array(char, str, CDNSP_MSG_MAX) + ), + TP_fast_assign( + __entry->type = ring->type; + __entry->field0 = le32_to_cpu(trb->field[0]); + __entry->field1 = le32_to_cpu(trb->field[1]); + __entry->field2 = le32_to_cpu(trb->field[2]); + __entry->field3 = le32_to_cpu(trb->field[3]); + __entry->trb = (union cdnsp_trb *)trb; + __entry->trb_dma = cdnsp_trb_virt_to_dma(ring->deq_seg, + (union cdnsp_trb *)trb); + + ), + TP_printk("%s: %s trb: %p(%pad)", cdnsp_ring_type_string(__entry->type), + cdnsp_decode_trb(__get_str(str), CDNSP_MSG_MAX, + __entry->field0, __entry->field1, + __entry->field2, __entry->field3), + __entry->trb, &__entry->trb_dma + ) +); + +DEFINE_EVENT(cdnsp_log_trb, cdnsp_handle_event, + TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb), + TP_ARGS(ring, trb) +); + +DEFINE_EVENT(cdnsp_log_trb, cdnsp_trb_without_td, + TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb), + TP_ARGS(ring, trb) +); + +DEFINE_EVENT(cdnsp_log_trb, cdnsp_handle_command, + TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb), + TP_ARGS(ring, trb) +); + +DEFINE_EVENT(cdnsp_log_trb, cdnsp_handle_transfer, + TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb), + TP_ARGS(ring, trb) +); + +DEFINE_EVENT(cdnsp_log_trb, cdnsp_queue_trb, + TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb), + TP_ARGS(ring, trb) +); + +DEFINE_EVENT(cdnsp_log_trb, cdnsp_cmd_wait_for_compl, + TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb), + TP_ARGS(ring, trb) +); + +DEFINE_EVENT(cdnsp_log_trb, cdnsp_cmd_timeout, + TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb), + TP_ARGS(ring, trb) +); + +DEFINE_EVENT(cdnsp_log_trb, cdnsp_defered_event, + TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb), + TP_ARGS(ring, trb) +); + +DECLARE_EVENT_CLASS(cdnsp_log_pdev, + TP_PROTO(struct cdnsp_device *pdev), + TP_ARGS(pdev), + TP_STRUCT__entry( + __field(struct cdnsp_device *, pdev) + __field(struct usb_gadget *, gadget) + __field(dma_addr_t, out_ctx) + __field(dma_addr_t, in_ctx) + __field(u8, port_num) + ), + TP_fast_assign( + __entry->pdev = pdev; + __entry->gadget = &pdev->gadget; + __entry->in_ctx = pdev->in_ctx.dma; + __entry->out_ctx = pdev->out_ctx.dma; + __entry->port_num = pdev->active_port ? + pdev->active_port->port_num : 0xFF; + ), + TP_printk("pdev %p gadget %p ctx %pad | %pad, port %d ", + __entry->pdev, __entry->gadget, &__entry->in_ctx, + &__entry->out_ctx, __entry->port_num + ) +); + +DEFINE_EVENT(cdnsp_log_pdev, cdnsp_alloc_priv_device, + TP_PROTO(struct cdnsp_device *vdev), + TP_ARGS(vdev) +); + +DEFINE_EVENT(cdnsp_log_pdev, cdnsp_free_priv_device, + TP_PROTO(struct cdnsp_device *vdev), + TP_ARGS(vdev) +); + +DEFINE_EVENT(cdnsp_log_pdev, cdnsp_setup_device, + TP_PROTO(struct cdnsp_device *vdev), + TP_ARGS(vdev) +); + +DEFINE_EVENT(cdnsp_log_pdev, cdnsp_setup_addressable_priv_device, + TP_PROTO(struct cdnsp_device *vdev), + TP_ARGS(vdev) +); + +DECLARE_EVENT_CLASS(cdnsp_log_request, + TP_PROTO(struct cdnsp_request *req), + TP_ARGS(req), + TP_STRUCT__entry( + __string(name, req->pep->name) + __field(struct usb_request *, request) + __field(struct cdnsp_request *, preq) + __field(void *, buf) + __field(unsigned int, actual) + __field(unsigned int, length) + __field(int, status) + __field(dma_addr_t, dma) + __field(unsigned int, stream_id) + __field(unsigned int, zero) + __field(unsigned int, short_not_ok) + __field(unsigned int, no_interrupt) + __field(struct scatterlist*, sg) + __field(unsigned int, num_sgs) + __field(unsigned int, num_mapped_sgs) + + ), + TP_fast_assign( + __assign_str(name, req->pep->name); + __entry->request = &req->request; + __entry->preq = req; + __entry->buf = req->request.buf; + __entry->actual = req->request.actual; + __entry->length = req->request.length; + __entry->status = req->request.status; + __entry->dma = req->request.dma; + __entry->stream_id = req->request.stream_id; + __entry->zero = req->request.zero; + __entry->short_not_ok = req->request.short_not_ok; + __entry->no_interrupt = req->request.no_interrupt; + __entry->sg = req->request.sg; + __entry->num_sgs = req->request.num_sgs; + __entry->num_mapped_sgs = req->request.num_mapped_sgs; + ), + TP_printk("%s; req U:%p/P:%p, req buf %p, length %u/%u, status %d, " + "buf dma (%pad), SID %u, %s%s%s, sg %p, num_sg %d," + " num_m_sg %d", + __get_str(name), __entry->request, __entry->preq, + __entry->buf, __entry->actual, __entry->length, + __entry->status, &__entry->dma, + __entry->stream_id, __entry->zero ? "Z" : "z", + __entry->short_not_ok ? "S" : "s", + __entry->no_interrupt ? "I" : "i", + __entry->sg, __entry->num_sgs, __entry->num_mapped_sgs + ) +); + +DEFINE_EVENT(cdnsp_log_request, cdnsp_request_enqueue, + TP_PROTO(struct cdnsp_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdnsp_log_request, cdnsp_request_enqueue_busy, + TP_PROTO(struct cdnsp_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdnsp_log_request, cdnsp_request_enqueue_error, + TP_PROTO(struct cdnsp_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdnsp_log_request, cdnsp_request_dequeue, + TP_PROTO(struct cdnsp_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdnsp_log_request, cdnsp_request_giveback, + TP_PROTO(struct cdnsp_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdnsp_log_request, cdnsp_alloc_request, + TP_PROTO(struct cdnsp_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdnsp_log_request, cdnsp_free_request, + TP_PROTO(struct cdnsp_request *req), + TP_ARGS(req) +); + +DECLARE_EVENT_CLASS(cdnsp_log_ep_ctx, + TP_PROTO(struct cdnsp_ep_ctx *ctx), + TP_ARGS(ctx), + TP_STRUCT__entry( + __field(u32, info) + __field(u32, info2) + __field(u64, deq) + __field(u32, tx_info) + __dynamic_array(char, str, CDNSP_MSG_MAX) + ), + TP_fast_assign( + __entry->info = le32_to_cpu(ctx->ep_info); + __entry->info2 = le32_to_cpu(ctx->ep_info2); + __entry->deq = le64_to_cpu(ctx->deq); + __entry->tx_info = le32_to_cpu(ctx->tx_info); + ), + TP_printk("%s", cdnsp_decode_ep_context(__get_str(str), CDNSP_MSG_MAX, + __entry->info, __entry->info2, + __entry->deq, __entry->tx_info) + ) +); + +DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_ep_disabled, + TP_PROTO(struct cdnsp_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_ep_stopped_or_disabled, + TP_PROTO(struct cdnsp_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_remove_request, + TP_PROTO(struct cdnsp_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_stop_ep, + TP_PROTO(struct cdnsp_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_flush_ep, + TP_PROTO(struct cdnsp_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_set_deq_ep, + TP_PROTO(struct cdnsp_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_reset_ep, + TP_PROTO(struct cdnsp_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_config_ep, + TP_PROTO(struct cdnsp_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DECLARE_EVENT_CLASS(cdnsp_log_slot_ctx, + TP_PROTO(struct cdnsp_slot_ctx *ctx), + TP_ARGS(ctx), + TP_STRUCT__entry( + __field(u32, info) + __field(u32, info2) + __field(u32, int_target) + __field(u32, state) + ), + TP_fast_assign( + __entry->info = le32_to_cpu(ctx->dev_info); + __entry->info2 = le32_to_cpu(ctx->dev_port); + __entry->int_target = le64_to_cpu(ctx->int_target); + __entry->state = le32_to_cpu(ctx->dev_state); + ), + TP_printk("%s", cdnsp_decode_slot_context(__entry->info, + __entry->info2, + __entry->int_target, + __entry->state) + ) +); + +DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_slot_already_in_default, + TP_PROTO(struct cdnsp_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_handle_cmd_enable_slot, + TP_PROTO(struct cdnsp_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_handle_cmd_disable_slot, + TP_PROTO(struct cdnsp_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_reset_device, + TP_PROTO(struct cdnsp_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_setup_device_slot, + TP_PROTO(struct cdnsp_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_handle_cmd_addr_dev, + TP_PROTO(struct cdnsp_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_handle_cmd_reset_dev, + TP_PROTO(struct cdnsp_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_handle_cmd_set_deq, + TP_PROTO(struct cdnsp_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_configure_endpoint, + TP_PROTO(struct cdnsp_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DECLARE_EVENT_CLASS(cdnsp_log_td_info, + TP_PROTO(struct cdnsp_request *preq), + TP_ARGS(preq), + TP_STRUCT__entry( + __string(name, preq->pep->name) + __field(struct usb_request *, request) + __field(struct cdnsp_request *, preq) + __field(union cdnsp_trb *, first_trb) + __field(union cdnsp_trb *, last_trb) + __field(dma_addr_t, trb_dma) + ), + TP_fast_assign( + __assign_str(name, preq->pep->name); + __entry->request = &preq->request; + __entry->preq = preq; + __entry->first_trb = preq->td.first_trb; + __entry->last_trb = preq->td.last_trb; + __entry->trb_dma = cdnsp_trb_virt_to_dma(preq->td.start_seg, + preq->td.first_trb) + ), + TP_printk("%s req/preq: %p/%p, first trb %p[vir]/%pad(dma), last trb %p", + __get_str(name), __entry->request, __entry->preq, + __entry->first_trb, &__entry->trb_dma, + __entry->last_trb + ) +); + +DEFINE_EVENT(cdnsp_log_td_info, cdnsp_remove_request_td, + TP_PROTO(struct cdnsp_request *preq), + TP_ARGS(preq) +); + +DECLARE_EVENT_CLASS(cdnsp_log_ring, + TP_PROTO(struct cdnsp_ring *ring), + TP_ARGS(ring), + TP_STRUCT__entry( + __field(u32, type) + __field(void *, ring) + __field(dma_addr_t, enq) + __field(dma_addr_t, deq) + __field(dma_addr_t, enq_seg) + __field(dma_addr_t, deq_seg) + __field(unsigned int, num_segs) + __field(unsigned int, stream_id) + __field(unsigned int, cycle_state) + __field(unsigned int, num_trbs_free) + __field(unsigned int, bounce_buf_len) + ), + TP_fast_assign( + __entry->ring = ring; + __entry->type = ring->type; + __entry->num_segs = ring->num_segs; + __entry->stream_id = ring->stream_id; + __entry->enq_seg = ring->enq_seg->dma; + __entry->deq_seg = ring->deq_seg->dma; + __entry->cycle_state = ring->cycle_state; + __entry->num_trbs_free = ring->num_trbs_free; + __entry->bounce_buf_len = ring->bounce_buf_len; + __entry->enq = cdnsp_trb_virt_to_dma(ring->enq_seg, + ring->enqueue); + __entry->deq = cdnsp_trb_virt_to_dma(ring->deq_seg, + ring->dequeue); + ), + TP_printk("%s %p: enq %pad(%pad) deq %pad(%pad) segs %d stream %d" + " free_trbs %d bounce %d cycle %d", + cdnsp_ring_type_string(__entry->type), __entry->ring, + &__entry->enq, &__entry->enq_seg, + &__entry->deq, &__entry->deq_seg, + __entry->num_segs, + __entry->stream_id, + __entry->num_trbs_free, + __entry->bounce_buf_len, + __entry->cycle_state + ) +); + +DEFINE_EVENT(cdnsp_log_ring, cdnsp_ring_alloc, + TP_PROTO(struct cdnsp_ring *ring), + TP_ARGS(ring) +); + +DEFINE_EVENT(cdnsp_log_ring, cdnsp_ring_free, + TP_PROTO(struct cdnsp_ring *ring), + TP_ARGS(ring) +); + +DEFINE_EVENT(cdnsp_log_ring, cdnsp_set_stream_ring, + TP_PROTO(struct cdnsp_ring *ring), + TP_ARGS(ring) +); + +DEFINE_EVENT(cdnsp_log_ring, cdnsp_ring_expansion, + TP_PROTO(struct cdnsp_ring *ring), + TP_ARGS(ring) +); + +DEFINE_EVENT(cdnsp_log_ring, cdnsp_inc_enq, + TP_PROTO(struct cdnsp_ring *ring), + TP_ARGS(ring) +); + +DEFINE_EVENT(cdnsp_log_ring, cdnsp_inc_deq, + TP_PROTO(struct cdnsp_ring *ring), + TP_ARGS(ring) +); + +DECLARE_EVENT_CLASS(cdnsp_log_portsc, + TP_PROTO(u32 portnum, u32 portsc), + TP_ARGS(portnum, portsc), + TP_STRUCT__entry( + __field(u32, portnum) + __field(u32, portsc) + __dynamic_array(char, str, CDNSP_MSG_MAX) + ), + TP_fast_assign( + __entry->portnum = portnum; + __entry->portsc = portsc; + ), + TP_printk("port-%d: %s", + __entry->portnum, + cdnsp_decode_portsc(__get_str(str), CDNSP_MSG_MAX, + __entry->portsc) + ) +); + +DEFINE_EVENT(cdnsp_log_portsc, cdnsp_handle_port_status, + TP_PROTO(u32 portnum, u32 portsc), + TP_ARGS(portnum, portsc) +); + +DEFINE_EVENT(cdnsp_log_portsc, cdnsp_link_state_changed, + TP_PROTO(u32 portnum, u32 portsc), + TP_ARGS(portnum, portsc) +); + +TRACE_EVENT(cdnsp_stream_number, + TP_PROTO(struct cdnsp_ep *pep, int num_stream_ctxs, int num_streams), + TP_ARGS(pep, num_stream_ctxs, num_streams), + TP_STRUCT__entry( + __string(name, pep->name) + __field(int, num_stream_ctxs) + __field(int, num_streams) + ), + TP_fast_assign( + __entry->num_stream_ctxs = num_stream_ctxs; + __entry->num_streams = num_streams; + ), + TP_printk("%s Need %u stream ctx entries for %u stream IDs.", + __get_str(name), __entry->num_stream_ctxs, + __entry->num_streams) +); + +#endif /* __CDNSP_TRACE_H */ + +/* this part must be outside header guard */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE cdnsp-trace + +#include -- cgit v1.2.3 From 64b558f597d1e564c67ba55444da4aaf621dc809 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Mon, 7 Dec 2020 11:32:26 +0100 Subject: usb: cdns3: Change file names for cdns3 driver. Patch adds prefix cdns3- to all file names related only to cdns3 driver. Signed-off-by: Pawel Laszczak Signed-off-by: Peter Chen --- drivers/usb/cdns3/Makefile | 6 +- drivers/usb/cdns3/cdns3-debug.h | 161 ++ drivers/usb/cdns3/cdns3-ep0.c | 894 ++++++++++ drivers/usb/cdns3/cdns3-gadget.c | 3335 ++++++++++++++++++++++++++++++++++++++ drivers/usb/cdns3/cdns3-gadget.h | 1368 ++++++++++++++++ drivers/usb/cdns3/cdns3-trace.c | 11 + drivers/usb/cdns3/cdns3-trace.h | 570 +++++++ drivers/usb/cdns3/debug.h | 161 -- drivers/usb/cdns3/ep0.c | 894 ---------- drivers/usb/cdns3/gadget.c | 3335 -------------------------------------- drivers/usb/cdns3/gadget.h | 1368 ---------------- drivers/usb/cdns3/trace.c | 11 - drivers/usb/cdns3/trace.h | 570 ------- 13 files changed, 6342 insertions(+), 6342 deletions(-) create mode 100644 drivers/usb/cdns3/cdns3-debug.h create mode 100644 drivers/usb/cdns3/cdns3-ep0.c create mode 100644 drivers/usb/cdns3/cdns3-gadget.c create mode 100644 drivers/usb/cdns3/cdns3-gadget.h create mode 100644 drivers/usb/cdns3/cdns3-trace.c create mode 100644 drivers/usb/cdns3/cdns3-trace.h delete mode 100644 drivers/usb/cdns3/debug.h delete mode 100644 drivers/usb/cdns3/ep0.c delete mode 100644 drivers/usb/cdns3/gadget.c delete mode 100644 drivers/usb/cdns3/gadget.h delete mode 100644 drivers/usb/cdns3/trace.c delete mode 100644 drivers/usb/cdns3/trace.h diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile index a4fdaabdbe18..01a9a9620044 100644 --- a/drivers/usb/cdns3/Makefile +++ b/drivers/usb/cdns3/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 # define_trace.h needs to know how to find our header -CFLAGS_trace.o := -I$(src) +CFLAGS_cdns3-trace.o := -I$(src) CFLAGS_cdnsp-trace.o := -I$(src) cdns-usb-common-y := core.o drd.o @@ -10,10 +10,10 @@ obj-$(CONFIG_USB_CDNS3) += cdns3.o obj-$(CONFIG_USB_CDNS_SUPPORT) += cdns-usb-common.o cdns-usb-common-$(CONFIG_USB_CDNS_HOST) += host.o -cdns3-$(CONFIG_USB_CDNS3_GADGET) += gadget.o ep0.o +cdns3-$(CONFIG_USB_CDNS3_GADGET) += cdns3-gadget.o cdns3-ep0.o ifneq ($(CONFIG_USB_CDNS3_GADGET),) -cdns3-$(CONFIG_TRACING) += trace.o +cdns3-$(CONFIG_TRACING) += cdns3-trace.o endif obj-$(CONFIG_USB_CDNS3_PCI_WRAP) += cdns3-pci-wrap.o diff --git a/drivers/usb/cdns3/cdns3-debug.h b/drivers/usb/cdns3/cdns3-debug.h new file mode 100644 index 000000000000..a5c6a29e1340 --- /dev/null +++ b/drivers/usb/cdns3/cdns3-debug.h @@ -0,0 +1,161 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Cadence USBSS DRD Driver. + * Debug header file. + * + * Copyright (C) 2018-2019 Cadence. + * + * Author: Pawel Laszczak + */ +#ifndef __LINUX_CDNS3_DEBUG +#define __LINUX_CDNS3_DEBUG + +#include "core.h" + +static inline char *cdns3_decode_usb_irq(char *str, + enum usb_device_speed speed, + u32 usb_ists) +{ + int ret; + + ret = sprintf(str, "IRQ %08x = ", usb_ists); + + if (usb_ists & (USB_ISTS_CON2I | USB_ISTS_CONI)) { + ret += sprintf(str + ret, "Connection %s\n", + usb_speed_string(speed)); + } + if (usb_ists & USB_ISTS_DIS2I || usb_ists & USB_ISTS_DISI) + ret += sprintf(str + ret, "Disconnection "); + if (usb_ists & USB_ISTS_L2ENTI) + ret += sprintf(str + ret, "suspended "); + if (usb_ists & USB_ISTS_L1ENTI) + ret += sprintf(str + ret, "L1 enter "); + if (usb_ists & USB_ISTS_L1EXTI) + ret += sprintf(str + ret, "L1 exit "); + if (usb_ists & USB_ISTS_L2ENTI) + ret += sprintf(str + ret, "L2 enter "); + if (usb_ists & USB_ISTS_L2EXTI) + ret += sprintf(str + ret, "L2 exit "); + if (usb_ists & USB_ISTS_U3EXTI) + ret += sprintf(str + ret, "U3 exit "); + if (usb_ists & USB_ISTS_UWRESI) + ret += sprintf(str + ret, "Warm Reset "); + if (usb_ists & USB_ISTS_UHRESI) + ret += sprintf(str + ret, "Hot Reset "); + if (usb_ists & USB_ISTS_U2RESI) + ret += sprintf(str + ret, "Reset"); + + return str; +} + +static inline char *cdns3_decode_ep_irq(char *str, + u32 ep_sts, + const char *ep_name) +{ + int ret; + + ret = sprintf(str, "IRQ for %s: %08x ", ep_name, ep_sts); + + if (ep_sts & EP_STS_SETUP) + ret += sprintf(str + ret, "SETUP "); + if (ep_sts & EP_STS_IOC) + ret += sprintf(str + ret, "IOC "); + if (ep_sts & EP_STS_ISP) + ret += sprintf(str + ret, "ISP "); + if (ep_sts & EP_STS_DESCMIS) + ret += sprintf(str + ret, "DESCMIS "); + if (ep_sts & EP_STS_STREAMR) + ret += sprintf(str + ret, "STREAMR "); + if (ep_sts & EP_STS_MD_EXIT) + ret += sprintf(str + ret, "MD_EXIT "); + if (ep_sts & EP_STS_TRBERR) + ret += sprintf(str + ret, "TRBERR "); + if (ep_sts & EP_STS_NRDY) + ret += sprintf(str + ret, "NRDY "); + if (ep_sts & EP_STS_PRIME) + ret += sprintf(str + ret, "PRIME "); + if (ep_sts & EP_STS_SIDERR) + ret += sprintf(str + ret, "SIDERRT "); + if (ep_sts & EP_STS_OUTSMM) + ret += sprintf(str + ret, "OUTSMM "); + if (ep_sts & EP_STS_ISOERR) + ret += sprintf(str + ret, "ISOERR "); + if (ep_sts & EP_STS_IOT) + ret += sprintf(str + ret, "IOT "); + + return str; +} + +static inline char *cdns3_decode_epx_irq(char *str, + char *ep_name, + u32 ep_sts) +{ + return cdns3_decode_ep_irq(str, ep_sts, ep_name); +} + +static inline char *cdns3_decode_ep0_irq(char *str, + int dir, + u32 ep_sts) +{ + return cdns3_decode_ep_irq(str, ep_sts, + dir ? "ep0IN" : "ep0OUT"); +} + +/** + * Debug a transfer ring. + * + * Prints out all TRBs in the endpoint ring, even those after the Link TRB. + *. + */ +static inline char *cdns3_dbg_ring(struct cdns3_endpoint *priv_ep, + struct cdns3_trb *ring, char *str) +{ + dma_addr_t addr = priv_ep->trb_pool_dma; + struct cdns3_trb *trb; + int trb_per_sector; + int ret = 0; + int i; + + trb_per_sector = GET_TRBS_PER_SEGMENT(priv_ep->type); + + trb = &priv_ep->trb_pool[priv_ep->dequeue]; + ret += sprintf(str + ret, "\n\t\tRing contents for %s:", priv_ep->name); + + ret += sprintf(str + ret, + "\n\t\tRing deq index: %d, trb: %p (virt), 0x%llx (dma)\n", + priv_ep->dequeue, trb, + (unsigned long long)cdns3_trb_virt_to_dma(priv_ep, trb)); + + trb = &priv_ep->trb_pool[priv_ep->enqueue]; + ret += sprintf(str + ret, + "\t\tRing enq index: %d, trb: %p (virt), 0x%llx (dma)\n", + priv_ep->enqueue, trb, + (unsigned long long)cdns3_trb_virt_to_dma(priv_ep, trb)); + + ret += sprintf(str + ret, + "\t\tfree trbs: %d, CCS=%d, PCS=%d\n", + priv_ep->free_trbs, priv_ep->ccs, priv_ep->pcs); + + if (trb_per_sector > TRBS_PER_SEGMENT) + trb_per_sector = TRBS_PER_SEGMENT; + + if (trb_per_sector > TRBS_PER_SEGMENT) { + sprintf(str + ret, "\t\tTransfer ring %d too big\n", + trb_per_sector); + return str; + } + + for (i = 0; i < trb_per_sector; ++i) { + trb = &ring[i]; + ret += sprintf(str + ret, + "\t\t@%pad %08x %08x %08x\n", &addr, + le32_to_cpu(trb->buffer), + le32_to_cpu(trb->length), + le32_to_cpu(trb->control)); + addr += sizeof(*trb); + } + + return str; +} + +#endif /*__LINUX_CDNS3_DEBUG*/ diff --git a/drivers/usb/cdns3/cdns3-ep0.c b/drivers/usb/cdns3/cdns3-ep0.c new file mode 100644 index 000000000000..b0390fe9a396 --- /dev/null +++ b/drivers/usb/cdns3/cdns3-ep0.c @@ -0,0 +1,894 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence USBSS DRD Driver - gadget side. + * + * Copyright (C) 2018 Cadence Design Systems. + * Copyright (C) 2017-2018 NXP + * + * Authors: Pawel Jez , + * Pawel Laszczak + * Peter Chen + */ + +#include +#include + +#include "cdns3-gadget.h" +#include "cdns3-trace.h" + +static struct usb_endpoint_descriptor cdns3_gadget_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, +}; + +/** + * cdns3_ep0_run_transfer - Do transfer on default endpoint hardware + * @priv_dev: extended gadget object + * @dma_addr: physical address where data is/will be stored + * @length: data length + * @erdy: set it to 1 when ERDY packet should be sent - + * exit from flow control state + * @zlp: add zero length packet + */ +static void cdns3_ep0_run_transfer(struct cdns3_device *priv_dev, + dma_addr_t dma_addr, + unsigned int length, int erdy, int zlp) +{ + struct cdns3_usb_regs __iomem *regs = priv_dev->regs; + struct cdns3_endpoint *priv_ep = priv_dev->eps[0]; + + priv_ep->trb_pool[0].buffer = cpu_to_le32(TRB_BUFFER(dma_addr)); + priv_ep->trb_pool[0].length = cpu_to_le32(TRB_LEN(length)); + + if (zlp) { + priv_ep->trb_pool[0].control = cpu_to_le32(TRB_CYCLE | TRB_TYPE(TRB_NORMAL)); + priv_ep->trb_pool[1].buffer = cpu_to_le32(TRB_BUFFER(dma_addr)); + priv_ep->trb_pool[1].length = cpu_to_le32(TRB_LEN(0)); + priv_ep->trb_pool[1].control = cpu_to_le32(TRB_CYCLE | TRB_IOC | + TRB_TYPE(TRB_NORMAL)); + } else { + priv_ep->trb_pool[0].control = cpu_to_le32(TRB_CYCLE | TRB_IOC | + TRB_TYPE(TRB_NORMAL)); + priv_ep->trb_pool[1].control = 0; + } + + trace_cdns3_prepare_trb(priv_ep, priv_ep->trb_pool); + + cdns3_select_ep(priv_dev, priv_dev->ep0_data_dir); + + writel(EP_STS_TRBERR, ®s->ep_sts); + writel(EP_TRADDR_TRADDR(priv_ep->trb_pool_dma), ®s->ep_traddr); + trace_cdns3_doorbell_ep0(priv_dev->ep0_data_dir ? "ep0in" : "ep0out", + readl(®s->ep_traddr)); + + /* TRB should be prepared before starting transfer. */ + writel(EP_CMD_DRDY, ®s->ep_cmd); + + /* Resume controller before arming transfer. */ + __cdns3_gadget_wakeup(priv_dev); + + if (erdy) + writel(EP_CMD_ERDY, &priv_dev->regs->ep_cmd); +} + +/** + * cdns3_ep0_delegate_req - Returns status of handling setup packet + * Setup is handled by gadget driver + * @priv_dev: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns zero on success or negative value on failure + */ +static int cdns3_ep0_delegate_req(struct cdns3_device *priv_dev, + struct usb_ctrlrequest *ctrl_req) +{ + int ret; + + spin_unlock(&priv_dev->lock); + priv_dev->setup_pending = 1; + ret = priv_dev->gadget_driver->setup(&priv_dev->gadget, ctrl_req); + priv_dev->setup_pending = 0; + spin_lock(&priv_dev->lock); + return ret; +} + +static void cdns3_prepare_setup_packet(struct cdns3_device *priv_dev) +{ + priv_dev->ep0_data_dir = 0; + priv_dev->ep0_stage = CDNS3_SETUP_STAGE; + cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma, + sizeof(struct usb_ctrlrequest), 0, 0); +} + +static void cdns3_ep0_complete_setup(struct cdns3_device *priv_dev, + u8 send_stall, u8 send_erdy) +{ + struct cdns3_endpoint *priv_ep = priv_dev->eps[0]; + struct usb_request *request; + + request = cdns3_next_request(&priv_ep->pending_req_list); + if (request) + list_del_init(&request->list); + + if (send_stall) { + trace_cdns3_halt(priv_ep, send_stall, 0); + /* set_stall on ep0 */ + cdns3_select_ep(priv_dev, 0x00); + writel(EP_CMD_SSTALL, &priv_dev->regs->ep_cmd); + } else { + cdns3_prepare_setup_packet(priv_dev); + } + + priv_dev->ep0_stage = CDNS3_SETUP_STAGE; + writel((send_erdy ? EP_CMD_ERDY : 0) | EP_CMD_REQ_CMPL, + &priv_dev->regs->ep_cmd); +} + +/** + * cdns3_req_ep0_set_configuration - Handling of SET_CONFIG standard USB request + * @priv_dev: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns 0 if success, USB_GADGET_DELAYED_STATUS on deferred status stage, + * error code on error + */ +static int cdns3_req_ep0_set_configuration(struct cdns3_device *priv_dev, + struct usb_ctrlrequest *ctrl_req) +{ + enum usb_device_state device_state = priv_dev->gadget.state; + u32 config = le16_to_cpu(ctrl_req->wValue); + int result = 0; + + switch (device_state) { + case USB_STATE_ADDRESS: + result = cdns3_ep0_delegate_req(priv_dev, ctrl_req); + + if (result || !config) + goto reset_config; + + break; + case USB_STATE_CONFIGURED: + result = cdns3_ep0_delegate_req(priv_dev, ctrl_req); + if (!config && !result) + goto reset_config; + + break; + default: + return -EINVAL; + } + + return 0; + +reset_config: + if (result != USB_GADGET_DELAYED_STATUS) + cdns3_hw_reset_eps_config(priv_dev); + + usb_gadget_set_state(&priv_dev->gadget, + USB_STATE_ADDRESS); + + return result; +} + +/** + * cdns3_req_ep0_set_address - Handling of SET_ADDRESS standard USB request + * @priv_dev: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns 0 if success, error code on error + */ +static int cdns3_req_ep0_set_address(struct cdns3_device *priv_dev, + struct usb_ctrlrequest *ctrl_req) +{ + enum usb_device_state device_state = priv_dev->gadget.state; + u32 reg; + u32 addr; + + addr = le16_to_cpu(ctrl_req->wValue); + + if (addr > USB_DEVICE_MAX_ADDRESS) { + dev_err(priv_dev->dev, + "Device address (%d) cannot be greater than %d\n", + addr, USB_DEVICE_MAX_ADDRESS); + return -EINVAL; + } + + if (device_state == USB_STATE_CONFIGURED) { + dev_err(priv_dev->dev, + "can't set_address from configured state\n"); + return -EINVAL; + } + + reg = readl(&priv_dev->regs->usb_cmd); + + writel(reg | USB_CMD_FADDR(addr) | USB_CMD_SET_ADDR, + &priv_dev->regs->usb_cmd); + + usb_gadget_set_state(&priv_dev->gadget, + (addr ? USB_STATE_ADDRESS : USB_STATE_DEFAULT)); + + return 0; +} + +/** + * cdns3_req_ep0_get_status - Handling of GET_STATUS standard USB request + * @priv_dev: extended gadget object + * @ctrl: pointer to received setup packet + * + * Returns 0 if success, error code on error + */ +static int cdns3_req_ep0_get_status(struct cdns3_device *priv_dev, + struct usb_ctrlrequest *ctrl) +{ + struct cdns3_endpoint *priv_ep; + __le16 *response_pkt; + u16 usb_status = 0; + u32 recip; + u8 index; + + recip = ctrl->bRequestType & USB_RECIP_MASK; + + switch (recip) { + case USB_RECIP_DEVICE: + /* self powered */ + if (priv_dev->is_selfpowered) + usb_status = BIT(USB_DEVICE_SELF_POWERED); + + if (priv_dev->wake_up_flag) + usb_status |= BIT(USB_DEVICE_REMOTE_WAKEUP); + + if (priv_dev->gadget.speed != USB_SPEED_SUPER) + break; + + if (priv_dev->u1_allowed) + usb_status |= BIT(USB_DEV_STAT_U1_ENABLED); + + if (priv_dev->u2_allowed) + usb_status |= BIT(USB_DEV_STAT_U2_ENABLED); + + break; + case USB_RECIP_INTERFACE: + return cdns3_ep0_delegate_req(priv_dev, ctrl); + case USB_RECIP_ENDPOINT: + index = cdns3_ep_addr_to_index(le16_to_cpu(ctrl->wIndex)); + priv_ep = priv_dev->eps[index]; + + /* check if endpoint is stalled or stall is pending */ + cdns3_select_ep(priv_dev, le16_to_cpu(ctrl->wIndex)); + if (EP_STS_STALL(readl(&priv_dev->regs->ep_sts)) || + (priv_ep->flags & EP_STALL_PENDING)) + usb_status = BIT(USB_ENDPOINT_HALT); + break; + default: + return -EINVAL; + } + + response_pkt = (__le16 *)priv_dev->setup_buf; + *response_pkt = cpu_to_le16(usb_status); + + cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma, + sizeof(*response_pkt), 1, 0); + return 0; +} + +static int cdns3_ep0_feature_handle_device(struct cdns3_device *priv_dev, + struct usb_ctrlrequest *ctrl, + int set) +{ + enum usb_device_state state; + enum usb_device_speed speed; + int ret = 0; + u32 wValue; + u16 tmode; + + wValue = le16_to_cpu(ctrl->wValue); + state = priv_dev->gadget.state; + speed = priv_dev->gadget.speed; + + switch (wValue) { + case USB_DEVICE_REMOTE_WAKEUP: + priv_dev->wake_up_flag = !!set; + break; + case USB_DEVICE_U1_ENABLE: + if (state != USB_STATE_CONFIGURED || speed != USB_SPEED_SUPER) + return -EINVAL; + + priv_dev->u1_allowed = !!set; + break; + case USB_DEVICE_U2_ENABLE: + if (state != USB_STATE_CONFIGURED || speed != USB_SPEED_SUPER) + return -EINVAL; + + priv_dev->u2_allowed = !!set; + break; + case USB_DEVICE_LTM_ENABLE: + ret = -EINVAL; + break; + case USB_DEVICE_TEST_MODE: + if (state != USB_STATE_CONFIGURED || speed > USB_SPEED_HIGH) + return -EINVAL; + + tmode = le16_to_cpu(ctrl->wIndex); + + if (!set || (tmode & 0xff) != 0) + return -EINVAL; + + tmode >>= 8; + switch (tmode) { + case USB_TEST_J: + case USB_TEST_K: + case USB_TEST_SE0_NAK: + case USB_TEST_PACKET: + cdns3_set_register_bit(&priv_dev->regs->usb_cmd, + USB_CMD_STMODE | + USB_STS_TMODE_SEL(tmode - 1)); + break; + default: + ret = -EINVAL; + } + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int cdns3_ep0_feature_handle_intf(struct cdns3_device *priv_dev, + struct usb_ctrlrequest *ctrl, + int set) +{ + u32 wValue; + int ret = 0; + + wValue = le16_to_cpu(ctrl->wValue); + + switch (wValue) { + case USB_INTRF_FUNC_SUSPEND: + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int cdns3_ep0_feature_handle_endpoint(struct cdns3_device *priv_dev, + struct usb_ctrlrequest *ctrl, + int set) +{ + struct cdns3_endpoint *priv_ep; + int ret = 0; + u8 index; + + if (le16_to_cpu(ctrl->wValue) != USB_ENDPOINT_HALT) + return -EINVAL; + + if (!(ctrl->wIndex & ~USB_DIR_IN)) + return 0; + + index = cdns3_ep_addr_to_index(le16_to_cpu(ctrl->wIndex)); + priv_ep = priv_dev->eps[index]; + + cdns3_select_ep(priv_dev, le16_to_cpu(ctrl->wIndex)); + + if (set) + __cdns3_gadget_ep_set_halt(priv_ep); + else if (!(priv_ep->flags & EP_WEDGE)) + ret = __cdns3_gadget_ep_clear_halt(priv_ep); + + cdns3_select_ep(priv_dev, 0x00); + + return ret; +} + +/** + * cdns3_req_ep0_handle_feature - + * Handling of GET/SET_FEATURE standard USB request + * + * @priv_dev: extended gadget object + * @ctrl: pointer to received setup packet + * @set: must be set to 1 for SET_FEATURE request + * + * Returns 0 if success, error code on error + */ +static int cdns3_req_ep0_handle_feature(struct cdns3_device *priv_dev, + struct usb_ctrlrequest *ctrl, + int set) +{ + int ret = 0; + u32 recip; + + recip = ctrl->bRequestType & USB_RECIP_MASK; + + switch (recip) { + case USB_RECIP_DEVICE: + ret = cdns3_ep0_feature_handle_device(priv_dev, ctrl, set); + break; + case USB_RECIP_INTERFACE: + ret = cdns3_ep0_feature_handle_intf(priv_dev, ctrl, set); + break; + case USB_RECIP_ENDPOINT: + ret = cdns3_ep0_feature_handle_endpoint(priv_dev, ctrl, set); + break; + default: + return -EINVAL; + } + + return ret; +} + +/** + * cdns3_req_ep0_set_sel - Handling of SET_SEL standard USB request + * @priv_dev: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns 0 if success, error code on error + */ +static int cdns3_req_ep0_set_sel(struct cdns3_device *priv_dev, + struct usb_ctrlrequest *ctrl_req) +{ + if (priv_dev->gadget.state < USB_STATE_ADDRESS) + return -EINVAL; + + if (le16_to_cpu(ctrl_req->wLength) != 6) { + dev_err(priv_dev->dev, "Set SEL should be 6 bytes, got %d\n", + ctrl_req->wLength); + return -EINVAL; + } + + cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma, 6, 1, 0); + return 0; +} + +/** + * cdns3_req_ep0_set_isoch_delay - + * Handling of GET_ISOCH_DELAY standard USB request + * @priv_dev: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns 0 if success, error code on error + */ +static int cdns3_req_ep0_set_isoch_delay(struct cdns3_device *priv_dev, + struct usb_ctrlrequest *ctrl_req) +{ + if (ctrl_req->wIndex || ctrl_req->wLength) + return -EINVAL; + + priv_dev->isoch_delay = le16_to_cpu(ctrl_req->wValue); + + return 0; +} + +/** + * cdns3_ep0_standard_request - Handling standard USB requests + * @priv_dev: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns 0 if success, error code on error + */ +static int cdns3_ep0_standard_request(struct cdns3_device *priv_dev, + struct usb_ctrlrequest *ctrl_req) +{ + int ret; + + switch (ctrl_req->bRequest) { + case USB_REQ_SET_ADDRESS: + ret = cdns3_req_ep0_set_address(priv_dev, ctrl_req); + break; + case USB_REQ_SET_CONFIGURATION: + ret = cdns3_req_ep0_set_configuration(priv_dev, ctrl_req); + break; + case USB_REQ_GET_STATUS: + ret = cdns3_req_ep0_get_status(priv_dev, ctrl_req); + break; + case USB_REQ_CLEAR_FEATURE: + ret = cdns3_req_ep0_handle_feature(priv_dev, ctrl_req, 0); + break; + case USB_REQ_SET_FEATURE: + ret = cdns3_req_ep0_handle_feature(priv_dev, ctrl_req, 1); + break; + case USB_REQ_SET_SEL: + ret = cdns3_req_ep0_set_sel(priv_dev, ctrl_req); + break; + case USB_REQ_SET_ISOCH_DELAY: + ret = cdns3_req_ep0_set_isoch_delay(priv_dev, ctrl_req); + break; + default: + ret = cdns3_ep0_delegate_req(priv_dev, ctrl_req); + break; + } + + return ret; +} + +static void __pending_setup_status_handler(struct cdns3_device *priv_dev) +{ + struct usb_request *request = priv_dev->pending_status_request; + + if (priv_dev->status_completion_no_call && request && + request->complete) { + request->complete(&priv_dev->eps[0]->endpoint, request); + priv_dev->status_completion_no_call = 0; + } +} + +void cdns3_pending_setup_status_handler(struct work_struct *work) +{ + struct cdns3_device *priv_dev = container_of(work, struct cdns3_device, + pending_status_wq); + unsigned long flags; + + spin_lock_irqsave(&priv_dev->lock, flags); + __pending_setup_status_handler(priv_dev); + spin_unlock_irqrestore(&priv_dev->lock, flags); +} + +/** + * cdns3_ep0_setup_phase - Handling setup USB requests + * @priv_dev: extended gadget object + */ +static void cdns3_ep0_setup_phase(struct cdns3_device *priv_dev) +{ + struct usb_ctrlrequest *ctrl = priv_dev->setup_buf; + struct cdns3_endpoint *priv_ep = priv_dev->eps[0]; + int result; + + priv_dev->ep0_data_dir = ctrl->bRequestType & USB_DIR_IN; + + trace_cdns3_ctrl_req(ctrl); + + if (!list_empty(&priv_ep->pending_req_list)) { + struct usb_request *request; + + request = cdns3_next_request(&priv_ep->pending_req_list); + priv_ep->dir = priv_dev->ep0_data_dir; + cdns3_gadget_giveback(priv_ep, to_cdns3_request(request), + -ECONNRESET); + } + + if (le16_to_cpu(ctrl->wLength)) + priv_dev->ep0_stage = CDNS3_DATA_STAGE; + else + priv_dev->ep0_stage = CDNS3_STATUS_STAGE; + + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) + result = cdns3_ep0_standard_request(priv_dev, ctrl); + else + result = cdns3_ep0_delegate_req(priv_dev, ctrl); + + if (result == USB_GADGET_DELAYED_STATUS) + return; + + if (result < 0) + cdns3_ep0_complete_setup(priv_dev, 1, 1); + else if (priv_dev->ep0_stage == CDNS3_STATUS_STAGE) + cdns3_ep0_complete_setup(priv_dev, 0, 1); +} + +static void cdns3_transfer_completed(struct cdns3_device *priv_dev) +{ + struct cdns3_endpoint *priv_ep = priv_dev->eps[0]; + + if (!list_empty(&priv_ep->pending_req_list)) { + struct usb_request *request; + + trace_cdns3_complete_trb(priv_ep, priv_ep->trb_pool); + request = cdns3_next_request(&priv_ep->pending_req_list); + + request->actual = + TRB_LEN(le32_to_cpu(priv_ep->trb_pool->length)); + + priv_ep->dir = priv_dev->ep0_data_dir; + cdns3_gadget_giveback(priv_ep, to_cdns3_request(request), 0); + } + + cdns3_ep0_complete_setup(priv_dev, 0, 0); +} + +/** + * cdns3_check_new_setup - Check if controller receive new SETUP packet. + * @priv_dev: extended gadget object + * + * The SETUP packet can be kept in on-chip memory or in system memory. + */ +static bool cdns3_check_new_setup(struct cdns3_device *priv_dev) +{ + u32 ep_sts_reg; + + cdns3_select_ep(priv_dev, USB_DIR_OUT); + ep_sts_reg = readl(&priv_dev->regs->ep_sts); + + return !!(ep_sts_reg & (EP_STS_SETUP | EP_STS_STPWAIT)); +} + +/** + * cdns3_check_ep0_interrupt_proceed - Processes interrupt related to endpoint 0 + * @priv_dev: extended gadget object + * @dir: USB_DIR_IN for IN direction, USB_DIR_OUT for OUT direction + */ +void cdns3_check_ep0_interrupt_proceed(struct cdns3_device *priv_dev, int dir) +{ + u32 ep_sts_reg; + + cdns3_select_ep(priv_dev, dir); + + ep_sts_reg = readl(&priv_dev->regs->ep_sts); + writel(ep_sts_reg, &priv_dev->regs->ep_sts); + + trace_cdns3_ep0_irq(priv_dev, ep_sts_reg); + + __pending_setup_status_handler(priv_dev); + + if (ep_sts_reg & EP_STS_SETUP) + priv_dev->wait_for_setup = 1; + + if (priv_dev->wait_for_setup && ep_sts_reg & EP_STS_IOC) { + priv_dev->wait_for_setup = 0; + cdns3_ep0_setup_phase(priv_dev); + } else if ((ep_sts_reg & EP_STS_IOC) || (ep_sts_reg & EP_STS_ISP)) { + priv_dev->ep0_data_dir = dir; + cdns3_transfer_completed(priv_dev); + } + + if (ep_sts_reg & EP_STS_DESCMIS) { + if (dir == 0 && !priv_dev->setup_pending) + cdns3_prepare_setup_packet(priv_dev); + } +} + +/** + * cdns3_gadget_ep0_enable + * @ep: pointer to endpoint zero object + * @desc: pointer to usb endpoint descriptor + * + * Function shouldn't be called by gadget driver, + * endpoint 0 is allways active + */ +static int cdns3_gadget_ep0_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + return -EINVAL; +} + +/** + * cdns3_gadget_ep0_disable + * @ep: pointer to endpoint zero object + * + * Function shouldn't be called by gadget driver, + * endpoint 0 is allways active + */ +static int cdns3_gadget_ep0_disable(struct usb_ep *ep) +{ + return -EINVAL; +} + +/** + * cdns3_gadget_ep0_set_halt + * @ep: pointer to endpoint zero object + * @value: 1 for set stall, 0 for clear stall + * + * Returns 0 + */ +static int cdns3_gadget_ep0_set_halt(struct usb_ep *ep, int value) +{ + /* TODO */ + return 0; +} + +/** + * cdns3_gadget_ep0_queue Transfer data on endpoint zero + * @ep: pointer to endpoint zero object + * @request: pointer to request object + * @gfp_flags: gfp flags + * + * Returns 0 on success, error code elsewhere + */ +static int cdns3_gadget_ep0_queue(struct usb_ep *ep, + struct usb_request *request, + gfp_t gfp_flags) +{ + struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep); + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + unsigned long flags; + int ret = 0; + u8 zlp = 0; + int i; + + spin_lock_irqsave(&priv_dev->lock, flags); + trace_cdns3_ep0_queue(priv_dev, request); + + /* cancel the request if controller receive new SETUP packet. */ + if (cdns3_check_new_setup(priv_dev)) { + spin_unlock_irqrestore(&priv_dev->lock, flags); + return -ECONNRESET; + } + + /* send STATUS stage. Should be called only for SET_CONFIGURATION */ + if (priv_dev->ep0_stage == CDNS3_STATUS_STAGE) { + u32 val; + + cdns3_select_ep(priv_dev, 0x00); + + /* + * Configure all non-control EPs which are not enabled by class driver + */ + for (i = 0; i < CDNS3_ENDPOINTS_MAX_COUNT; i++) { + priv_ep = priv_dev->eps[i]; + if (priv_ep && priv_ep->flags & EP_CLAIMED && + !(priv_ep->flags & EP_ENABLED)) + cdns3_ep_config(priv_ep, 0); + } + + cdns3_set_hw_configuration(priv_dev); + cdns3_ep0_complete_setup(priv_dev, 0, 1); + /* wait until configuration set */ + ret = readl_poll_timeout_atomic(&priv_dev->regs->usb_sts, val, + val & USB_STS_CFGSTS_MASK, 1, 100); + if (ret == -ETIMEDOUT) + dev_warn(priv_dev->dev, "timeout for waiting configuration set\n"); + + request->actual = 0; + priv_dev->status_completion_no_call = true; + priv_dev->pending_status_request = request; + spin_unlock_irqrestore(&priv_dev->lock, flags); + + /* + * Since there is no completion interrupt for status stage, + * it needs to call ->completion in software after + * ep0_queue is back. + */ + queue_work(system_freezable_wq, &priv_dev->pending_status_wq); + return ret; + } + + if (!list_empty(&priv_ep->pending_req_list)) { + dev_err(priv_dev->dev, + "can't handle multiple requests for ep0\n"); + spin_unlock_irqrestore(&priv_dev->lock, flags); + return -EBUSY; + } + + ret = usb_gadget_map_request_by_dev(priv_dev->sysdev, request, + priv_dev->ep0_data_dir); + if (ret) { + spin_unlock_irqrestore(&priv_dev->lock, flags); + dev_err(priv_dev->dev, "failed to map request\n"); + return -EINVAL; + } + + request->status = -EINPROGRESS; + list_add_tail(&request->list, &priv_ep->pending_req_list); + + if (request->zero && request->length && + (request->length % ep->maxpacket == 0)) + zlp = 1; + + cdns3_ep0_run_transfer(priv_dev, request->dma, request->length, 1, zlp); + + spin_unlock_irqrestore(&priv_dev->lock, flags); + + return ret; +} + +/** + * cdns3_gadget_ep_set_wedge Set wedge on selected endpoint + * @ep: endpoint object + * + * Returns 0 + */ +int cdns3_gadget_ep_set_wedge(struct usb_ep *ep) +{ + struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep); + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + + dev_dbg(priv_dev->dev, "Wedge for %s\n", ep->name); + cdns3_gadget_ep_set_halt(ep, 1); + priv_ep->flags |= EP_WEDGE; + + return 0; +} + +const struct usb_ep_ops cdns3_gadget_ep0_ops = { + .enable = cdns3_gadget_ep0_enable, + .disable = cdns3_gadget_ep0_disable, + .alloc_request = cdns3_gadget_ep_alloc_request, + .free_request = cdns3_gadget_ep_free_request, + .queue = cdns3_gadget_ep0_queue, + .dequeue = cdns3_gadget_ep_dequeue, + .set_halt = cdns3_gadget_ep0_set_halt, + .set_wedge = cdns3_gadget_ep_set_wedge, +}; + +/** + * cdns3_ep0_config - Configures default endpoint + * @priv_dev: extended gadget object + * + * Functions sets parameters: maximal packet size and enables interrupts + */ +void cdns3_ep0_config(struct cdns3_device *priv_dev) +{ + struct cdns3_usb_regs __iomem *regs; + struct cdns3_endpoint *priv_ep; + u32 max_packet_size = 64; + u32 ep_cfg; + + regs = priv_dev->regs; + + if (priv_dev->gadget.speed == USB_SPEED_SUPER) + max_packet_size = 512; + + priv_ep = priv_dev->eps[0]; + + if (!list_empty(&priv_ep->pending_req_list)) { + struct usb_request *request; + + request = cdns3_next_request(&priv_ep->pending_req_list); + list_del_init(&request->list); + } + + priv_dev->u1_allowed = 0; + priv_dev->u2_allowed = 0; + + priv_dev->gadget.ep0->maxpacket = max_packet_size; + cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(max_packet_size); + + /* init ep out */ + cdns3_select_ep(priv_dev, USB_DIR_OUT); + + if (priv_dev->dev_ver >= DEV_VER_V3) { + cdns3_set_register_bit(&priv_dev->regs->dtrans, + BIT(0) | BIT(16)); + cdns3_set_register_bit(&priv_dev->regs->tdl_from_trb, + BIT(0) | BIT(16)); + } + + ep_cfg = EP_CFG_ENABLE | EP_CFG_MAXPKTSIZE(max_packet_size); + + if (!(priv_ep->flags & EP_CONFIGURED)) + writel(ep_cfg, ®s->ep_cfg); + + writel(EP_STS_EN_SETUPEN | EP_STS_EN_DESCMISEN | EP_STS_EN_TRBERREN, + ®s->ep_sts_en); + + /* init ep in */ + cdns3_select_ep(priv_dev, USB_DIR_IN); + + if (!(priv_ep->flags & EP_CONFIGURED)) + writel(ep_cfg, ®s->ep_cfg); + + priv_ep->flags |= EP_CONFIGURED; + + writel(EP_STS_EN_SETUPEN | EP_STS_EN_TRBERREN, ®s->ep_sts_en); + + cdns3_set_register_bit(®s->usb_conf, USB_CONF_U1DS | USB_CONF_U2DS); +} + +/** + * cdns3_init_ep0 Initializes software endpoint 0 of gadget + * @priv_dev: extended gadget object + * @priv_ep: extended endpoint object + * + * Returns 0 on success else error code. + */ +int cdns3_init_ep0(struct cdns3_device *priv_dev, + struct cdns3_endpoint *priv_ep) +{ + sprintf(priv_ep->name, "ep0"); + + /* fill linux fields */ + priv_ep->endpoint.ops = &cdns3_gadget_ep0_ops; + priv_ep->endpoint.maxburst = 1; + usb_ep_set_maxpacket_limit(&priv_ep->endpoint, + CDNS3_EP0_MAX_PACKET_LIMIT); + priv_ep->endpoint.address = 0; + priv_ep->endpoint.caps.type_control = 1; + priv_ep->endpoint.caps.dir_in = 1; + priv_ep->endpoint.caps.dir_out = 1; + priv_ep->endpoint.name = priv_ep->name; + priv_ep->endpoint.desc = &cdns3_gadget_ep0_desc; + priv_dev->gadget.ep0 = &priv_ep->endpoint; + priv_ep->type = USB_ENDPOINT_XFER_CONTROL; + + return cdns3_allocate_trb_pool(priv_ep); +} diff --git a/drivers/usb/cdns3/cdns3-gadget.c b/drivers/usb/cdns3/cdns3-gadget.c new file mode 100644 index 000000000000..9b8b0cd3d2c2 --- /dev/null +++ b/drivers/usb/cdns3/cdns3-gadget.c @@ -0,0 +1,3335 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence USBSS DRD Driver - gadget side. + * + * Copyright (C) 2018-2019 Cadence Design Systems. + * Copyright (C) 2017-2018 NXP + * + * Authors: Pawel Jez , + * Pawel Laszczak + * Peter Chen + */ + +/* + * Work around 1: + * At some situations, the controller may get stale data address in TRB + * at below sequences: + * 1. Controller read TRB includes data address + * 2. Software updates TRBs includes data address and Cycle bit + * 3. Controller read TRB which includes Cycle bit + * 4. DMA run with stale data address + * + * To fix this problem, driver needs to make the first TRB in TD as invalid. + * After preparing all TRBs driver needs to check the position of DMA and + * if the DMA point to the first just added TRB and doorbell is 1, + * then driver must defer making this TRB as valid. This TRB will be make + * as valid during adding next TRB only if DMA is stopped or at TRBERR + * interrupt. + * + * Issue has been fixed in DEV_VER_V3 version of controller. + * + * Work around 2: + * Controller for OUT endpoints has shared on-chip buffers for all incoming + * packets, including ep0out. It's FIFO buffer, so packets must be handle by DMA + * in correct order. If the first packet in the buffer will not be handled, + * then the following packets directed for other endpoints and functions + * will be blocked. + * Additionally the packets directed to one endpoint can block entire on-chip + * buffers. In this case transfer to other endpoints also will blocked. + * + * To resolve this issue after raising the descriptor missing interrupt + * driver prepares internal usb_request object and use it to arm DMA transfer. + * + * The problematic situation was observed in case when endpoint has been enabled + * but no usb_request were queued. Driver try detects such endpoints and will + * use this workaround only for these endpoint. + * + * Driver use limited number of buffer. This number can be set by macro + * CDNS3_WA2_NUM_BUFFERS. + * + * Such blocking situation was observed on ACM gadget. For this function + * host send OUT data packet but ACM function is not prepared for this packet. + * It's cause that buffer placed in on chip memory block transfer to other + * endpoints. + * + * Issue has been fixed in DEV_VER_V2 version of controller. + * + */ + +#include +#include +#include +#include + +#include "core.h" +#include "gadget-export.h" +#include "cdns3-gadget.h" +#include "cdns3-trace.h" +#include "drd.h" + +static int __cdns3_gadget_ep_queue(struct usb_ep *ep, + struct usb_request *request, + gfp_t gfp_flags); + +static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, + struct usb_request *request); + +static int cdns3_ep_run_stream_transfer(struct cdns3_endpoint *priv_ep, + struct usb_request *request); + +/** + * cdns3_clear_register_bit - clear bit in given register. + * @ptr: address of device controller register to be read and changed + * @mask: bits requested to clar + */ +static void cdns3_clear_register_bit(void __iomem *ptr, u32 mask) +{ + mask = readl(ptr) & ~mask; + writel(mask, ptr); +} + +/** + * cdns3_set_register_bit - set bit in given register. + * @ptr: address of device controller register to be read and changed + * @mask: bits requested to set + */ +void cdns3_set_register_bit(void __iomem *ptr, u32 mask) +{ + mask = readl(ptr) | mask; + writel(mask, ptr); +} + +/** + * cdns3_ep_addr_to_index - Macro converts endpoint address to + * index of endpoint object in cdns3_device.eps[] container + * @ep_addr: endpoint address for which endpoint object is required + * + */ +u8 cdns3_ep_addr_to_index(u8 ep_addr) +{ + return (((ep_addr & 0x7F)) + ((ep_addr & USB_DIR_IN) ? 16 : 0)); +} + +static int cdns3_get_dma_pos(struct cdns3_device *priv_dev, + struct cdns3_endpoint *priv_ep) +{ + int dma_index; + + dma_index = readl(&priv_dev->regs->ep_traddr) - priv_ep->trb_pool_dma; + + return dma_index / TRB_SIZE; +} + +/** + * cdns3_next_request - returns next request from list + * @list: list containing requests + * + * Returns request or NULL if no requests in list + */ +struct usb_request *cdns3_next_request(struct list_head *list) +{ + return list_first_entry_or_null(list, struct usb_request, list); +} + +/** + * cdns3_next_align_buf - returns next buffer from list + * @list: list containing buffers + * + * Returns buffer or NULL if no buffers in list + */ +static struct cdns3_aligned_buf *cdns3_next_align_buf(struct list_head *list) +{ + return list_first_entry_or_null(list, struct cdns3_aligned_buf, list); +} + +/** + * cdns3_next_priv_request - returns next request from list + * @list: list containing requests + * + * Returns request or NULL if no requests in list + */ +static struct cdns3_request *cdns3_next_priv_request(struct list_head *list) +{ + return list_first_entry_or_null(list, struct cdns3_request, list); +} + +/** + * select_ep - selects endpoint + * @priv_dev: extended gadget object + * @ep: endpoint address + */ +void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep) +{ + if (priv_dev->selected_ep == ep) + return; + + priv_dev->selected_ep = ep; + writel(ep, &priv_dev->regs->ep_sel); +} + +/** + * cdns3_get_tdl - gets current tdl for selected endpoint. + * @priv_dev: extended gadget object + * + * Before calling this function the appropriate endpoint must + * be selected by means of cdns3_select_ep function. + */ +static int cdns3_get_tdl(struct cdns3_device *priv_dev) +{ + if (priv_dev->dev_ver < DEV_VER_V3) + return EP_CMD_TDL_GET(readl(&priv_dev->regs->ep_cmd)); + else + return readl(&priv_dev->regs->ep_tdl); +} + +dma_addr_t cdns3_trb_virt_to_dma(struct cdns3_endpoint *priv_ep, + struct cdns3_trb *trb) +{ + u32 offset = (char *)trb - (char *)priv_ep->trb_pool; + + return priv_ep->trb_pool_dma + offset; +} + +static int cdns3_ring_size(struct cdns3_endpoint *priv_ep) +{ + switch (priv_ep->type) { + case USB_ENDPOINT_XFER_ISOC: + return TRB_ISO_RING_SIZE; + case USB_ENDPOINT_XFER_CONTROL: + return TRB_CTRL_RING_SIZE; + default: + if (priv_ep->use_streams) + return TRB_STREAM_RING_SIZE; + else + return TRB_RING_SIZE; + } +} + +static void cdns3_free_trb_pool(struct cdns3_endpoint *priv_ep) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + + if (priv_ep->trb_pool) { + dma_free_coherent(priv_dev->sysdev, + cdns3_ring_size(priv_ep), + priv_ep->trb_pool, priv_ep->trb_pool_dma); + priv_ep->trb_pool = NULL; + } +} + +/** + * cdns3_allocate_trb_pool - Allocates TRB's pool for selected endpoint + * @priv_ep: endpoint object + * + * Function will return 0 on success or -ENOMEM on allocation error + */ +int cdns3_allocate_trb_pool(struct cdns3_endpoint *priv_ep) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + int ring_size = cdns3_ring_size(priv_ep); + int num_trbs = ring_size / TRB_SIZE; + struct cdns3_trb *link_trb; + + if (priv_ep->trb_pool && priv_ep->alloc_ring_size < ring_size) + cdns3_free_trb_pool(priv_ep); + + if (!priv_ep->trb_pool) { + priv_ep->trb_pool = dma_alloc_coherent(priv_dev->sysdev, + ring_size, + &priv_ep->trb_pool_dma, + GFP_DMA32 | GFP_ATOMIC); + if (!priv_ep->trb_pool) + return -ENOMEM; + + priv_ep->alloc_ring_size = ring_size; + } + + memset(priv_ep->trb_pool, 0, ring_size); + + priv_ep->num_trbs = num_trbs; + + if (!priv_ep->num) + return 0; + + /* Initialize the last TRB as Link TRB */ + link_trb = (priv_ep->trb_pool + (priv_ep->num_trbs - 1)); + + if (priv_ep->use_streams) { + /* + * For stream capable endpoints driver use single correct TRB. + * The last trb has zeroed cycle bit + */ + link_trb->control = 0; + } else { + link_trb->buffer = cpu_to_le32(TRB_BUFFER(priv_ep->trb_pool_dma)); + link_trb->control = cpu_to_le32(TRB_CYCLE | TRB_TYPE(TRB_LINK) | TRB_TOGGLE); + } + return 0; +} + +/** + * cdns3_ep_stall_flush - Stalls and flushes selected endpoint + * @priv_ep: endpoint object + * + * Endpoint must be selected before call to this function + */ +static void cdns3_ep_stall_flush(struct cdns3_endpoint *priv_ep) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + int val; + + trace_cdns3_halt(priv_ep, 1, 1); + + writel(EP_CMD_DFLUSH | EP_CMD_ERDY | EP_CMD_SSTALL, + &priv_dev->regs->ep_cmd); + + /* wait for DFLUSH cleared */ + readl_poll_timeout_atomic(&priv_dev->regs->ep_cmd, val, + !(val & EP_CMD_DFLUSH), 1, 1000); + priv_ep->flags |= EP_STALLED; + priv_ep->flags &= ~EP_STALL_PENDING; +} + +/** + * cdns3_hw_reset_eps_config - reset endpoints configuration kept by controller. + * @priv_dev: extended gadget object + */ +void cdns3_hw_reset_eps_config(struct cdns3_device *priv_dev) +{ + int i; + + writel(USB_CONF_CFGRST, &priv_dev->regs->usb_conf); + + cdns3_allow_enable_l1(priv_dev, 0); + priv_dev->hw_configured_flag = 0; + priv_dev->onchip_used_size = 0; + priv_dev->out_mem_is_allocated = 0; + priv_dev->wait_for_setup = 0; + priv_dev->using_streams = 0; + + for (i = 0; i < CDNS3_ENDPOINTS_MAX_COUNT; i++) + if (priv_dev->eps[i]) + priv_dev->eps[i]->flags &= ~EP_CONFIGURED; +} + +/** + * cdns3_ep_inc_trb - increment a trb index. + * @index: Pointer to the TRB index to increment. + * @cs: Cycle state + * @trb_in_seg: number of TRBs in segment + * + * The index should never point to the link TRB. After incrementing, + * if it is point to the link TRB, wrap around to the beginning and revert + * cycle state bit The + * link TRB is always at the last TRB entry. + */ +static void cdns3_ep_inc_trb(int *index, u8 *cs, int trb_in_seg) +{ + (*index)++; + if (*index == (trb_in_seg - 1)) { + *index = 0; + *cs ^= 1; + } +} + +/** + * cdns3_ep_inc_enq - increment endpoint's enqueue pointer + * @priv_ep: The endpoint whose enqueue pointer we're incrementing + */ +static void cdns3_ep_inc_enq(struct cdns3_endpoint *priv_ep) +{ + priv_ep->free_trbs--; + cdns3_ep_inc_trb(&priv_ep->enqueue, &priv_ep->pcs, priv_ep->num_trbs); +} + +/** + * cdns3_ep_inc_deq - increment endpoint's dequeue pointer + * @priv_ep: The endpoint whose dequeue pointer we're incrementing + */ +static void cdns3_ep_inc_deq(struct cdns3_endpoint *priv_ep) +{ + priv_ep->free_trbs++; + cdns3_ep_inc_trb(&priv_ep->dequeue, &priv_ep->ccs, priv_ep->num_trbs); +} + +static void cdns3_move_deq_to_next_trb(struct cdns3_request *priv_req) +{ + struct cdns3_endpoint *priv_ep = priv_req->priv_ep; + int current_trb = priv_req->start_trb; + + while (current_trb != priv_req->end_trb) { + cdns3_ep_inc_deq(priv_ep); + current_trb = priv_ep->dequeue; + } + + cdns3_ep_inc_deq(priv_ep); +} + +/** + * cdns3_allow_enable_l1 - enable/disable permits to transition to L1. + * @priv_dev: Extended gadget object + * @enable: Enable/disable permit to transition to L1. + * + * If bit USB_CONF_L1EN is set and device receive Extended Token packet, + * then controller answer with ACK handshake. + * If bit USB_CONF_L1DS is set and device receive Extended Token packet, + * then controller answer with NYET handshake. + */ +void cdns3_allow_enable_l1(struct cdns3_device *priv_dev, int enable) +{ + if (enable) + writel(USB_CONF_L1EN, &priv_dev->regs->usb_conf); + else + writel(USB_CONF_L1DS, &priv_dev->regs->usb_conf); +} + +enum usb_device_speed cdns3_get_speed(struct cdns3_device *priv_dev) +{ + u32 reg; + + reg = readl(&priv_dev->regs->usb_sts); + + if (DEV_SUPERSPEED(reg)) + return USB_SPEED_SUPER; + else if (DEV_HIGHSPEED(reg)) + return USB_SPEED_HIGH; + else if (DEV_FULLSPEED(reg)) + return USB_SPEED_FULL; + else if (DEV_LOWSPEED(reg)) + return USB_SPEED_LOW; + return USB_SPEED_UNKNOWN; +} + +/** + * cdns3_start_all_request - add to ring all request not started + * @priv_dev: Extended gadget object + * @priv_ep: The endpoint for whom request will be started. + * + * Returns return ENOMEM if transfer ring i not enough TRBs to start + * all requests. + */ +static int cdns3_start_all_request(struct cdns3_device *priv_dev, + struct cdns3_endpoint *priv_ep) +{ + struct usb_request *request; + int ret = 0; + u8 pending_empty = list_empty(&priv_ep->pending_req_list); + + /* + * If the last pending transfer is INTERNAL + * OR streams are enabled for this endpoint + * do NOT start new transfer till the last one is pending + */ + if (!pending_empty) { + struct cdns3_request *priv_req; + + request = cdns3_next_request(&priv_ep->pending_req_list); + priv_req = to_cdns3_request(request); + if ((priv_req->flags & REQUEST_INTERNAL) || + (priv_ep->flags & EP_TDLCHK_EN) || + priv_ep->use_streams) { + dev_dbg(priv_dev->dev, "Blocking external request\n"); + return ret; + } + } + + while (!list_empty(&priv_ep->deferred_req_list)) { + request = cdns3_next_request(&priv_ep->deferred_req_list); + + if (!priv_ep->use_streams) { + ret = cdns3_ep_run_transfer(priv_ep, request); + } else { + priv_ep->stream_sg_idx = 0; + ret = cdns3_ep_run_stream_transfer(priv_ep, request); + } + if (ret) + return ret; + + list_del(&request->list); + list_add_tail(&request->list, + &priv_ep->pending_req_list); + if (request->stream_id != 0 || (priv_ep->flags & EP_TDLCHK_EN)) + break; + } + + priv_ep->flags &= ~EP_RING_FULL; + return ret; +} + +/* + * WA2: Set flag for all not ISOC OUT endpoints. If this flag is set + * driver try to detect whether endpoint need additional internal + * buffer for unblocking on-chip FIFO buffer. This flag will be cleared + * if before first DESCMISS interrupt the DMA will be armed. + */ +#define cdns3_wa2_enable_detection(priv_dev, priv_ep, reg) do { \ + if (!priv_ep->dir && priv_ep->type != USB_ENDPOINT_XFER_ISOC) { \ + priv_ep->flags |= EP_QUIRK_EXTRA_BUF_DET; \ + (reg) |= EP_STS_EN_DESCMISEN; \ + } } while (0) + +static void __cdns3_descmiss_copy_data(struct usb_request *request, + struct usb_request *descmiss_req) +{ + int length = request->actual + descmiss_req->actual; + struct scatterlist *s = request->sg; + + if (!s) { + if (length <= request->length) { + memcpy(&((u8 *)request->buf)[request->actual], + descmiss_req->buf, + descmiss_req->actual); + request->actual = length; + } else { + /* It should never occures */ + request->status = -ENOMEM; + } + } else { + if (length <= sg_dma_len(s)) { + void *p = phys_to_virt(sg_dma_address(s)); + + memcpy(&((u8 *)p)[request->actual], + descmiss_req->buf, + descmiss_req->actual); + request->actual = length; + } else { + request->status = -ENOMEM; + } + } +} + +/** + * cdns3_wa2_descmiss_copy_data copy data from internal requests to + * request queued by class driver. + * @priv_ep: extended endpoint object + * @request: request object + */ +static void cdns3_wa2_descmiss_copy_data(struct cdns3_endpoint *priv_ep, + struct usb_request *request) +{ + struct usb_request *descmiss_req; + struct cdns3_request *descmiss_priv_req; + + while (!list_empty(&priv_ep->wa2_descmiss_req_list)) { + int chunk_end; + + descmiss_priv_req = + cdns3_next_priv_request(&priv_ep->wa2_descmiss_req_list); + descmiss_req = &descmiss_priv_req->request; + + /* driver can't touch pending request */ + if (descmiss_priv_req->flags & REQUEST_PENDING) + break; + + chunk_end = descmiss_priv_req->flags & REQUEST_INTERNAL_CH; + request->status = descmiss_req->status; + __cdns3_descmiss_copy_data(request, descmiss_req); + list_del_init(&descmiss_priv_req->list); + kfree(descmiss_req->buf); + cdns3_gadget_ep_free_request(&priv_ep->endpoint, descmiss_req); + --priv_ep->wa2_counter; + + if (!chunk_end) + break; + } +} + +static struct usb_request *cdns3_wa2_gadget_giveback(struct cdns3_device *priv_dev, + struct cdns3_endpoint *priv_ep, + struct cdns3_request *priv_req) +{ + if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_EN && + priv_req->flags & REQUEST_INTERNAL) { + struct usb_request *req; + + req = cdns3_next_request(&priv_ep->deferred_req_list); + + priv_ep->descmis_req = NULL; + + if (!req) + return NULL; + + /* unmap the gadget request before copying data */ + usb_gadget_unmap_request_by_dev(priv_dev->sysdev, req, + priv_ep->dir); + + cdns3_wa2_descmiss_copy_data(priv_ep, req); + if (!(priv_ep->flags & EP_QUIRK_END_TRANSFER) && + req->length != req->actual) { + /* wait for next part of transfer */ + /* re-map the gadget request buffer*/ + usb_gadget_map_request_by_dev(priv_dev->sysdev, req, + usb_endpoint_dir_in(priv_ep->endpoint.desc)); + return NULL; + } + + if (req->status == -EINPROGRESS) + req->status = 0; + + list_del_init(&req->list); + cdns3_start_all_request(priv_dev, priv_ep); + return req; + } + + return &priv_req->request; +} + +static int cdns3_wa2_gadget_ep_queue(struct cdns3_device *priv_dev, + struct cdns3_endpoint *priv_ep, + struct cdns3_request *priv_req) +{ + int deferred = 0; + + /* + * If transfer was queued before DESCMISS appear than we + * can disable handling of DESCMISS interrupt. Driver assumes that it + * can disable special treatment for this endpoint. + */ + if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_DET) { + u32 reg; + + cdns3_select_ep(priv_dev, priv_ep->num | priv_ep->dir); + priv_ep->flags &= ~EP_QUIRK_EXTRA_BUF_DET; + reg = readl(&priv_dev->regs->ep_sts_en); + reg &= ~EP_STS_EN_DESCMISEN; + trace_cdns3_wa2(priv_ep, "workaround disabled\n"); + writel(reg, &priv_dev->regs->ep_sts_en); + } + + if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_EN) { + u8 pending_empty = list_empty(&priv_ep->pending_req_list); + u8 descmiss_empty = list_empty(&priv_ep->wa2_descmiss_req_list); + + /* + * DESCMISS transfer has been finished, so data will be + * directly copied from internal allocated usb_request + * objects. + */ + if (pending_empty && !descmiss_empty && + !(priv_req->flags & REQUEST_INTERNAL)) { + cdns3_wa2_descmiss_copy_data(priv_ep, + &priv_req->request); + + trace_cdns3_wa2(priv_ep, "get internal stored data"); + + list_add_tail(&priv_req->request.list, + &priv_ep->pending_req_list); + cdns3_gadget_giveback(priv_ep, priv_req, + priv_req->request.status); + + /* + * Intentionally driver returns positive value as + * correct value. It informs that transfer has + * been finished. + */ + return EINPROGRESS; + } + + /* + * Driver will wait for completion DESCMISS transfer, + * before starts new, not DESCMISS transfer. + */ + if (!pending_empty && !descmiss_empty) { + trace_cdns3_wa2(priv_ep, "wait for pending transfer\n"); + deferred = 1; + } + + if (priv_req->flags & REQUEST_INTERNAL) + list_add_tail(&priv_req->list, + &priv_ep->wa2_descmiss_req_list); + } + + return deferred; +} + +static void cdns3_wa2_remove_old_request(struct cdns3_endpoint *priv_ep) +{ + struct cdns3_request *priv_req; + + while (!list_empty(&priv_ep->wa2_descmiss_req_list)) { + u8 chain; + + priv_req = cdns3_next_priv_request(&priv_ep->wa2_descmiss_req_list); + chain = !!(priv_req->flags & REQUEST_INTERNAL_CH); + + trace_cdns3_wa2(priv_ep, "removes eldest request"); + + kfree(priv_req->request.buf); + cdns3_gadget_ep_free_request(&priv_ep->endpoint, + &priv_req->request); + list_del_init(&priv_req->list); + --priv_ep->wa2_counter; + + if (!chain) + break; + } +} + +/** + * cdns3_wa2_descmissing_packet - handles descriptor missing event. + * @priv_ep: extended gadget object + * + * This function is used only for WA2. For more information see Work around 2 + * description. + */ +static void cdns3_wa2_descmissing_packet(struct cdns3_endpoint *priv_ep) +{ + struct cdns3_request *priv_req; + struct usb_request *request; + u8 pending_empty = list_empty(&priv_ep->pending_req_list); + + /* check for pending transfer */ + if (!pending_empty) { + trace_cdns3_wa2(priv_ep, "Ignoring Descriptor missing IRQ\n"); + return; + } + + if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_DET) { + priv_ep->flags &= ~EP_QUIRK_EXTRA_BUF_DET; + priv_ep->flags |= EP_QUIRK_EXTRA_BUF_EN; + } + + trace_cdns3_wa2(priv_ep, "Description Missing detected\n"); + + if (priv_ep->wa2_counter >= CDNS3_WA2_NUM_BUFFERS) { + trace_cdns3_wa2(priv_ep, "WA2 overflow\n"); + cdns3_wa2_remove_old_request(priv_ep); + } + + request = cdns3_gadget_ep_alloc_request(&priv_ep->endpoint, + GFP_ATOMIC); + if (!request) + goto err; + + priv_req = to_cdns3_request(request); + priv_req->flags |= REQUEST_INTERNAL; + + /* if this field is still assigned it indicate that transfer related + * with this request has not been finished yet. Driver in this + * case simply allocate next request and assign flag REQUEST_INTERNAL_CH + * flag to previous one. It will indicate that current request is + * part of the previous one. + */ + if (priv_ep->descmis_req) + priv_ep->descmis_req->flags |= REQUEST_INTERNAL_CH; + + priv_req->request.buf = kzalloc(CDNS3_DESCMIS_BUF_SIZE, + GFP_ATOMIC); + priv_ep->wa2_counter++; + + if (!priv_req->request.buf) { + cdns3_gadget_ep_free_request(&priv_ep->endpoint, request); + goto err; + } + + priv_req->request.length = CDNS3_DESCMIS_BUF_SIZE; + priv_ep->descmis_req = priv_req; + + __cdns3_gadget_ep_queue(&priv_ep->endpoint, + &priv_ep->descmis_req->request, + GFP_ATOMIC); + + return; + +err: + dev_err(priv_ep->cdns3_dev->dev, + "Failed: No sufficient memory for DESCMIS\n"); +} + +static void cdns3_wa2_reset_tdl(struct cdns3_device *priv_dev) +{ + u16 tdl = EP_CMD_TDL_GET(readl(&priv_dev->regs->ep_cmd)); + + if (tdl) { + u16 reset_val = EP_CMD_TDL_MAX + 1 - tdl; + + writel(EP_CMD_TDL_SET(reset_val) | EP_CMD_STDL, + &priv_dev->regs->ep_cmd); + } +} + +static void cdns3_wa2_check_outq_status(struct cdns3_device *priv_dev) +{ + u32 ep_sts_reg; + + /* select EP0-out */ + cdns3_select_ep(priv_dev, 0); + + ep_sts_reg = readl(&priv_dev->regs->ep_sts); + + if (EP_STS_OUTQ_VAL(ep_sts_reg)) { + u32 outq_ep_num = EP_STS_OUTQ_NO(ep_sts_reg); + struct cdns3_endpoint *outq_ep = priv_dev->eps[outq_ep_num]; + + if ((outq_ep->flags & EP_ENABLED) && !(outq_ep->use_streams) && + outq_ep->type != USB_ENDPOINT_XFER_ISOC && outq_ep_num) { + u8 pending_empty = list_empty(&outq_ep->pending_req_list); + + if ((outq_ep->flags & EP_QUIRK_EXTRA_BUF_DET) || + (outq_ep->flags & EP_QUIRK_EXTRA_BUF_EN) || + !pending_empty) { + } else { + u32 ep_sts_en_reg; + u32 ep_cmd_reg; + + cdns3_select_ep(priv_dev, outq_ep->num | + outq_ep->dir); + ep_sts_en_reg = readl(&priv_dev->regs->ep_sts_en); + ep_cmd_reg = readl(&priv_dev->regs->ep_cmd); + + outq_ep->flags |= EP_TDLCHK_EN; + cdns3_set_register_bit(&priv_dev->regs->ep_cfg, + EP_CFG_TDL_CHK); + + cdns3_wa2_enable_detection(priv_dev, outq_ep, + ep_sts_en_reg); + writel(ep_sts_en_reg, + &priv_dev->regs->ep_sts_en); + /* reset tdl value to zero */ + cdns3_wa2_reset_tdl(priv_dev); + /* + * Memory barrier - Reset tdl before ringing the + * doorbell. + */ + wmb(); + if (EP_CMD_DRDY & ep_cmd_reg) { + trace_cdns3_wa2(outq_ep, "Enabling WA2 skipping doorbell\n"); + + } else { + trace_cdns3_wa2(outq_ep, "Enabling WA2 ringing doorbell\n"); + /* + * ring doorbell to generate DESCMIS irq + */ + writel(EP_CMD_DRDY, + &priv_dev->regs->ep_cmd); + } + } + } + } +} + +/** + * cdns3_gadget_giveback - call struct usb_request's ->complete callback + * @priv_ep: The endpoint to whom the request belongs to + * @priv_req: The request we're giving back + * @status: completion code for the request + * + * Must be called with controller's lock held and interrupts disabled. This + * function will unmap @req and call its ->complete() callback to notify upper + * layers that it has completed. + */ +void cdns3_gadget_giveback(struct cdns3_endpoint *priv_ep, + struct cdns3_request *priv_req, + int status) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + struct usb_request *request = &priv_req->request; + + list_del_init(&request->list); + + if (request->status == -EINPROGRESS) + request->status = status; + + usb_gadget_unmap_request_by_dev(priv_dev->sysdev, request, + priv_ep->dir); + + if ((priv_req->flags & REQUEST_UNALIGNED) && + priv_ep->dir == USB_DIR_OUT && !request->status) + memcpy(request->buf, priv_req->aligned_buf->buf, + request->length); + + priv_req->flags &= ~(REQUEST_PENDING | REQUEST_UNALIGNED); + /* All TRBs have finished, clear the counter */ + priv_req->finished_trb = 0; + trace_cdns3_gadget_giveback(priv_req); + + if (priv_dev->dev_ver < DEV_VER_V2) { + request = cdns3_wa2_gadget_giveback(priv_dev, priv_ep, + priv_req); + if (!request) + return; + } + + if (request->complete) { + spin_unlock(&priv_dev->lock); + usb_gadget_giveback_request(&priv_ep->endpoint, + request); + spin_lock(&priv_dev->lock); + } + + if (request->buf == priv_dev->zlp_buf) + cdns3_gadget_ep_free_request(&priv_ep->endpoint, request); +} + +static void cdns3_wa1_restore_cycle_bit(struct cdns3_endpoint *priv_ep) +{ + /* Work around for stale data address in TRB*/ + if (priv_ep->wa1_set) { + trace_cdns3_wa1(priv_ep, "restore cycle bit"); + + priv_ep->wa1_set = 0; + priv_ep->wa1_trb_index = 0xFFFF; + if (priv_ep->wa1_cycle_bit) { + priv_ep->wa1_trb->control = + priv_ep->wa1_trb->control | cpu_to_le32(0x1); + } else { + priv_ep->wa1_trb->control = + priv_ep->wa1_trb->control & cpu_to_le32(~0x1); + } + } +} + +static void cdns3_free_aligned_request_buf(struct work_struct *work) +{ + struct cdns3_device *priv_dev = container_of(work, struct cdns3_device, + aligned_buf_wq); + struct cdns3_aligned_buf *buf, *tmp; + unsigned long flags; + + spin_lock_irqsave(&priv_dev->lock, flags); + + list_for_each_entry_safe(buf, tmp, &priv_dev->aligned_buf_list, list) { + if (!buf->in_use) { + list_del(&buf->list); + + /* + * Re-enable interrupts to free DMA capable memory. + * Driver can't free this memory with disabled + * interrupts. + */ + spin_unlock_irqrestore(&priv_dev->lock, flags); + dma_free_coherent(priv_dev->sysdev, buf->size, + buf->buf, buf->dma); + kfree(buf); + spin_lock_irqsave(&priv_dev->lock, flags); + } + } + + spin_unlock_irqrestore(&priv_dev->lock, flags); +} + +static int cdns3_prepare_aligned_request_buf(struct cdns3_request *priv_req) +{ + struct cdns3_endpoint *priv_ep = priv_req->priv_ep; + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + struct cdns3_aligned_buf *buf; + + /* check if buffer is aligned to 8. */ + if (!((uintptr_t)priv_req->request.buf & 0x7)) + return 0; + + buf = priv_req->aligned_buf; + + if (!buf || priv_req->request.length > buf->size) { + buf = kzalloc(sizeof(*buf), GFP_ATOMIC); + if (!buf) + return -ENOMEM; + + buf->size = priv_req->request.length; + + buf->buf = dma_alloc_coherent(priv_dev->sysdev, + buf->size, + &buf->dma, + GFP_ATOMIC); + if (!buf->buf) { + kfree(buf); + return -ENOMEM; + } + + if (priv_req->aligned_buf) { + trace_cdns3_free_aligned_request(priv_req); + priv_req->aligned_buf->in_use = 0; + queue_work(system_freezable_wq, + &priv_dev->aligned_buf_wq); + } + + buf->in_use = 1; + priv_req->aligned_buf = buf; + + list_add_tail(&buf->list, + &priv_dev->aligned_buf_list); + } + + if (priv_ep->dir == USB_DIR_IN) { + memcpy(buf->buf, priv_req->request.buf, + priv_req->request.length); + } + + priv_req->flags |= REQUEST_UNALIGNED; + trace_cdns3_prepare_aligned_request(priv_req); + + return 0; +} + +static int cdns3_wa1_update_guard(struct cdns3_endpoint *priv_ep, + struct cdns3_trb *trb) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + + if (!priv_ep->wa1_set) { + u32 doorbell; + + doorbell = !!(readl(&priv_dev->regs->ep_cmd) & EP_CMD_DRDY); + + if (doorbell) { + priv_ep->wa1_cycle_bit = priv_ep->pcs ? TRB_CYCLE : 0; + priv_ep->wa1_set = 1; + priv_ep->wa1_trb = trb; + priv_ep->wa1_trb_index = priv_ep->enqueue; + trace_cdns3_wa1(priv_ep, "set guard"); + return 0; + } + } + return 1; +} + +static void cdns3_wa1_tray_restore_cycle_bit(struct cdns3_device *priv_dev, + struct cdns3_endpoint *priv_ep) +{ + int dma_index; + u32 doorbell; + + doorbell = !!(readl(&priv_dev->regs->ep_cmd) & EP_CMD_DRDY); + dma_index = cdns3_get_dma_pos(priv_dev, priv_ep); + + if (!doorbell || dma_index != priv_ep->wa1_trb_index) + cdns3_wa1_restore_cycle_bit(priv_ep); +} + +static int cdns3_ep_run_stream_transfer(struct cdns3_endpoint *priv_ep, + struct usb_request *request) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + struct cdns3_request *priv_req; + struct cdns3_trb *trb; + dma_addr_t trb_dma; + int address; + u32 control; + u32 length; + u32 tdl; + unsigned int sg_idx = priv_ep->stream_sg_idx; + + priv_req = to_cdns3_request(request); + address = priv_ep->endpoint.desc->bEndpointAddress; + + priv_ep->flags |= EP_PENDING_REQUEST; + + /* must allocate buffer aligned to 8 */ + if (priv_req->flags & REQUEST_UNALIGNED) + trb_dma = priv_req->aligned_buf->dma; + else + trb_dma = request->dma; + + /* For stream capable endpoints driver use only single TD. */ + trb = priv_ep->trb_pool + priv_ep->enqueue; + priv_req->start_trb = priv_ep->enqueue; + priv_req->end_trb = priv_req->start_trb; + priv_req->trb = trb; + + cdns3_select_ep(priv_ep->cdns3_dev, address); + + control = TRB_TYPE(TRB_NORMAL) | TRB_CYCLE | + TRB_STREAM_ID(priv_req->request.stream_id) | TRB_ISP; + + if (!request->num_sgs) { + trb->buffer = cpu_to_le32(TRB_BUFFER(trb_dma)); + length = request->length; + } else { + trb->buffer = cpu_to_le32(TRB_BUFFER(request->sg[sg_idx].dma_address)); + length = request->sg[sg_idx].length; + } + + tdl = DIV_ROUND_UP(length, priv_ep->endpoint.maxpacket); + + trb->length = cpu_to_le32(TRB_BURST_LEN(16) | TRB_LEN(length)); + + /* + * For DEV_VER_V2 controller version we have enabled + * USB_CONF2_EN_TDL_TRB in DMULT configuration. + * This enables TDL calculation based on TRB, hence setting TDL in TRB. + */ + if (priv_dev->dev_ver >= DEV_VER_V2) { + if (priv_dev->gadget.speed == USB_SPEED_SUPER) + trb->length |= cpu_to_le32(TRB_TDL_SS_SIZE(tdl)); + } + priv_req->flags |= REQUEST_PENDING; + + trb->control = cpu_to_le32(control); + + trace_cdns3_prepare_trb(priv_ep, priv_req->trb); + + /* + * Memory barrier - Cycle Bit must be set before trb->length and + * trb->buffer fields. + */ + wmb(); + + /* always first element */ + writel(EP_TRADDR_TRADDR(priv_ep->trb_pool_dma), + &priv_dev->regs->ep_traddr); + + if (!(priv_ep->flags & EP_STALLED)) { + trace_cdns3_ring(priv_ep); + /*clearing TRBERR and EP_STS_DESCMIS before seting DRDY*/ + writel(EP_STS_TRBERR | EP_STS_DESCMIS, &priv_dev->regs->ep_sts); + + priv_ep->prime_flag = false; + + /* + * Controller version DEV_VER_V2 tdl calculation + * is based on TRB + */ + + if (priv_dev->dev_ver < DEV_VER_V2) + writel(EP_CMD_TDL_SET(tdl) | EP_CMD_STDL, + &priv_dev->regs->ep_cmd); + else if (priv_dev->dev_ver > DEV_VER_V2) + writel(tdl, &priv_dev->regs->ep_tdl); + + priv_ep->last_stream_id = priv_req->request.stream_id; + writel(EP_CMD_DRDY, &priv_dev->regs->ep_cmd); + writel(EP_CMD_ERDY_SID(priv_req->request.stream_id) | + EP_CMD_ERDY, &priv_dev->regs->ep_cmd); + + trace_cdns3_doorbell_epx(priv_ep->name, + readl(&priv_dev->regs->ep_traddr)); + } + + /* WORKAROUND for transition to L0 */ + __cdns3_gadget_wakeup(priv_dev); + + return 0; +} + +/** + * cdns3_ep_run_transfer - start transfer on no-default endpoint hardware + * @priv_ep: endpoint object + * @request: request object + * + * Returns zero on success or negative value on failure + */ +static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, + struct usb_request *request) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + struct cdns3_request *priv_req; + struct cdns3_trb *trb; + struct cdns3_trb *link_trb = NULL; + dma_addr_t trb_dma; + u32 togle_pcs = 1; + int sg_iter = 0; + int num_trb; + int address; + u32 control; + int pcs; + u16 total_tdl = 0; + struct scatterlist *s = NULL; + bool sg_supported = !!(request->num_mapped_sgs); + + if (priv_ep->type == USB_ENDPOINT_XFER_ISOC) + num_trb = priv_ep->interval; + else + num_trb = sg_supported ? request->num_mapped_sgs : 1; + + if (num_trb > priv_ep->free_trbs) { + priv_ep->flags |= EP_RING_FULL; + return -ENOBUFS; + } + + priv_req = to_cdns3_request(request); + address = priv_ep->endpoint.desc->bEndpointAddress; + + priv_ep->flags |= EP_PENDING_REQUEST; + + /* must allocate buffer aligned to 8 */ + if (priv_req->flags & REQUEST_UNALIGNED) + trb_dma = priv_req->aligned_buf->dma; + else + trb_dma = request->dma; + + trb = priv_ep->trb_pool + priv_ep->enqueue; + priv_req->start_trb = priv_ep->enqueue; + priv_req->trb = trb; + + cdns3_select_ep(priv_ep->cdns3_dev, address); + + /* prepare ring */ + if ((priv_ep->enqueue + num_trb) >= (priv_ep->num_trbs - 1)) { + int doorbell, dma_index; + u32 ch_bit = 0; + + doorbell = !!(readl(&priv_dev->regs->ep_cmd) & EP_CMD_DRDY); + dma_index = cdns3_get_dma_pos(priv_dev, priv_ep); + + /* Driver can't update LINK TRB if it is current processed. */ + if (doorbell && dma_index == priv_ep->num_trbs - 1) { + priv_ep->flags |= EP_DEFERRED_DRDY; + return -ENOBUFS; + } + + /*updating C bt in Link TRB before starting DMA*/ + link_trb = priv_ep->trb_pool + (priv_ep->num_trbs - 1); + /* + * For TRs size equal 2 enabling TRB_CHAIN for epXin causes + * that DMA stuck at the LINK TRB. + * On the other hand, removing TRB_CHAIN for longer TRs for + * epXout cause that DMA stuck after handling LINK TRB. + * To eliminate this strange behavioral driver set TRB_CHAIN + * bit only for TR size > 2. + */ + if (priv_ep->type == USB_ENDPOINT_XFER_ISOC || + TRBS_PER_SEGMENT > 2) + ch_bit = TRB_CHAIN; + + link_trb->control = cpu_to_le32(((priv_ep->pcs) ? TRB_CYCLE : 0) | + TRB_TYPE(TRB_LINK) | TRB_TOGGLE | ch_bit); + } + + if (priv_dev->dev_ver <= DEV_VER_V2) + togle_pcs = cdns3_wa1_update_guard(priv_ep, trb); + + if (sg_supported) + s = request->sg; + + /* set incorrect Cycle Bit for first trb*/ + control = priv_ep->pcs ? 0 : TRB_CYCLE; + trb->length = 0; + if (priv_dev->dev_ver >= DEV_VER_V2) { + u16 td_size; + + td_size = DIV_ROUND_UP(request->length, + priv_ep->endpoint.maxpacket); + if (priv_dev->gadget.speed == USB_SPEED_SUPER) + trb->length = TRB_TDL_SS_SIZE(td_size); + else + control |= TRB_TDL_HS_SIZE(td_size); + } + + do { + u32 length; + + /* fill TRB */ + control |= TRB_TYPE(TRB_NORMAL); + if (sg_supported) { + trb->buffer = cpu_to_le32(TRB_BUFFER(sg_dma_address(s))); + length = sg_dma_len(s); + } else { + trb->buffer = cpu_to_le32(TRB_BUFFER(trb_dma)); + length = request->length; + } + + if (priv_ep->flags & EP_TDLCHK_EN) + total_tdl += DIV_ROUND_UP(length, + priv_ep->endpoint.maxpacket); + + trb->length |= cpu_to_le32(TRB_BURST_LEN(priv_ep->trb_burst_size) | + TRB_LEN(length)); + pcs = priv_ep->pcs ? TRB_CYCLE : 0; + + /* + * first trb should be prepared as last to avoid processing + * transfer to early + */ + if (sg_iter != 0) + control |= pcs; + + if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && !priv_ep->dir) { + control |= TRB_IOC | TRB_ISP; + } else { + /* for last element in TD or in SG list */ + if (sg_iter == (num_trb - 1) && sg_iter != 0) + control |= pcs | TRB_IOC | TRB_ISP; + } + + if (sg_iter) + trb->control = cpu_to_le32(control); + else + priv_req->trb->control = cpu_to_le32(control); + + if (sg_supported) { + trb->control |= TRB_ISP; + /* Don't set chain bit for last TRB */ + if (sg_iter < num_trb - 1) + trb->control |= TRB_CHAIN; + + s = sg_next(s); + } + + control = 0; + ++sg_iter; + priv_req->end_trb = priv_ep->enqueue; + cdns3_ep_inc_enq(priv_ep); + trb = priv_ep->trb_pool + priv_ep->enqueue; + trb->length = 0; + } while (sg_iter < num_trb); + + trb = priv_req->trb; + + priv_req->flags |= REQUEST_PENDING; + priv_req->num_of_trb = num_trb; + + if (sg_iter == 1) + trb->control |= cpu_to_le32(TRB_IOC | TRB_ISP); + + if (priv_dev->dev_ver < DEV_VER_V2 && + (priv_ep->flags & EP_TDLCHK_EN)) { + u16 tdl = total_tdl; + u16 old_tdl = EP_CMD_TDL_GET(readl(&priv_dev->regs->ep_cmd)); + + if (tdl > EP_CMD_TDL_MAX) { + tdl = EP_CMD_TDL_MAX; + priv_ep->pending_tdl = total_tdl - EP_CMD_TDL_MAX; + } + + if (old_tdl < tdl) { + tdl -= old_tdl; + writel(EP_CMD_TDL_SET(tdl) | EP_CMD_STDL, + &priv_dev->regs->ep_cmd); + } + } + + /* + * Memory barrier - cycle bit must be set before other filds in trb. + */ + wmb(); + + /* give the TD to the consumer*/ + if (togle_pcs) + trb->control = trb->control ^ cpu_to_le32(1); + + if (priv_dev->dev_ver <= DEV_VER_V2) + cdns3_wa1_tray_restore_cycle_bit(priv_dev, priv_ep); + + if (num_trb > 1) { + int i = 0; + + while (i < num_trb) { + trace_cdns3_prepare_trb(priv_ep, trb + i); + if (trb + i == link_trb) { + trb = priv_ep->trb_pool; + num_trb = num_trb - i; + i = 0; + } else { + i++; + } + } + } else { + trace_cdns3_prepare_trb(priv_ep, priv_req->trb); + } + + /* + * Memory barrier - Cycle Bit must be set before trb->length and + * trb->buffer fields. + */ + wmb(); + + /* + * For DMULT mode we can set address to transfer ring only once after + * enabling endpoint. + */ + if (priv_ep->flags & EP_UPDATE_EP_TRBADDR) { + /* + * Until SW is not ready to handle the OUT transfer the ISO OUT + * Endpoint should be disabled (EP_CFG.ENABLE = 0). + * EP_CFG_ENABLE must be set before updating ep_traddr. + */ + if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && !priv_ep->dir && + !(priv_ep->flags & EP_QUIRK_ISO_OUT_EN)) { + priv_ep->flags |= EP_QUIRK_ISO_OUT_EN; + cdns3_set_register_bit(&priv_dev->regs->ep_cfg, + EP_CFG_ENABLE); + } + + writel(EP_TRADDR_TRADDR(priv_ep->trb_pool_dma + + priv_req->start_trb * TRB_SIZE), + &priv_dev->regs->ep_traddr); + + priv_ep->flags &= ~EP_UPDATE_EP_TRBADDR; + } + + if (!priv_ep->wa1_set && !(priv_ep->flags & EP_STALLED)) { + trace_cdns3_ring(priv_ep); + /*clearing TRBERR and EP_STS_DESCMIS before seting DRDY*/ + writel(EP_STS_TRBERR | EP_STS_DESCMIS, &priv_dev->regs->ep_sts); + writel(EP_CMD_DRDY, &priv_dev->regs->ep_cmd); + trace_cdns3_doorbell_epx(priv_ep->name, + readl(&priv_dev->regs->ep_traddr)); + } + + /* WORKAROUND for transition to L0 */ + __cdns3_gadget_wakeup(priv_dev); + + return 0; +} + +void cdns3_set_hw_configuration(struct cdns3_device *priv_dev) +{ + struct cdns3_endpoint *priv_ep; + struct usb_ep *ep; + + if (priv_dev->hw_configured_flag) + return; + + writel(USB_CONF_CFGSET, &priv_dev->regs->usb_conf); + + cdns3_set_register_bit(&priv_dev->regs->usb_conf, + USB_CONF_U1EN | USB_CONF_U2EN); + + priv_dev->hw_configured_flag = 1; + + list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) { + if (ep->enabled) { + priv_ep = ep_to_cdns3_ep(ep); + cdns3_start_all_request(priv_dev, priv_ep); + } + } + + cdns3_allow_enable_l1(priv_dev, 1); +} + +/** + * cdns3_trb_handled - check whether trb has been handled by DMA + * + * @priv_ep: extended endpoint object. + * @priv_req: request object for checking + * + * Endpoint must be selected before invoking this function. + * + * Returns false if request has not been handled by DMA, else returns true. + * + * SR - start ring + * ER - end ring + * DQ = priv_ep->dequeue - dequeue position + * EQ = priv_ep->enqueue - enqueue position + * ST = priv_req->start_trb - index of first TRB in transfer ring + * ET = priv_req->end_trb - index of last TRB in transfer ring + * CI = current_index - index of processed TRB by DMA. + * + * As first step, we check if the TRB between the ST and ET. + * Then, we check if cycle bit for index priv_ep->dequeue + * is correct. + * + * some rules: + * 1. priv_ep->dequeue never equals to current_index. + * 2 priv_ep->enqueue never exceed priv_ep->dequeue + * 3. exception: priv_ep->enqueue == priv_ep->dequeue + * and priv_ep->free_trbs is zero. + * This case indicate that TR is full. + * + * At below two cases, the request have been handled. + * Case 1 - priv_ep->dequeue < current_index + * SR ... EQ ... DQ ... CI ... ER + * SR ... DQ ... CI ... EQ ... ER + * + * Case 2 - priv_ep->dequeue > current_index + * This situation takes place when CI go through the LINK TRB at the end of + * transfer ring. + * SR ... CI ... EQ ... DQ ... ER + */ +static bool cdns3_trb_handled(struct cdns3_endpoint *priv_ep, + struct cdns3_request *priv_req) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + struct cdns3_trb *trb; + int current_index = 0; + int handled = 0; + int doorbell; + + current_index = cdns3_get_dma_pos(priv_dev, priv_ep); + doorbell = !!(readl(&priv_dev->regs->ep_cmd) & EP_CMD_DRDY); + + /* current trb doesn't belong to this request */ + if (priv_req->start_trb < priv_req->end_trb) { + if (priv_ep->dequeue > priv_req->end_trb) + goto finish; + + if (priv_ep->dequeue < priv_req->start_trb) + goto finish; + } + + if ((priv_req->start_trb > priv_req->end_trb) && + (priv_ep->dequeue > priv_req->end_trb) && + (priv_ep->dequeue < priv_req->start_trb)) + goto finish; + + if ((priv_req->start_trb == priv_req->end_trb) && + (priv_ep->dequeue != priv_req->end_trb)) + goto finish; + + trb = &priv_ep->trb_pool[priv_ep->dequeue]; + + if ((le32_to_cpu(trb->control) & TRB_CYCLE) != priv_ep->ccs) + goto finish; + + if (doorbell == 1 && current_index == priv_ep->dequeue) + goto finish; + + /* The corner case for TRBS_PER_SEGMENT equal 2). */ + if (TRBS_PER_SEGMENT == 2 && priv_ep->type != USB_ENDPOINT_XFER_ISOC) { + handled = 1; + goto finish; + } + + if (priv_ep->enqueue == priv_ep->dequeue && + priv_ep->free_trbs == 0) { + handled = 1; + } else if (priv_ep->dequeue < current_index) { + if ((current_index == (priv_ep->num_trbs - 1)) && + !priv_ep->dequeue) + goto finish; + + handled = 1; + } else if (priv_ep->dequeue > current_index) { + handled = 1; + } + +finish: + trace_cdns3_request_handled(priv_req, current_index, handled); + + return handled; +} + +static void cdns3_transfer_completed(struct cdns3_device *priv_dev, + struct cdns3_endpoint *priv_ep) +{ + struct cdns3_request *priv_req; + struct usb_request *request; + struct cdns3_trb *trb; + bool request_handled = false; + bool transfer_end = false; + + while (!list_empty(&priv_ep->pending_req_list)) { + request = cdns3_next_request(&priv_ep->pending_req_list); + priv_req = to_cdns3_request(request); + + trb = priv_ep->trb_pool + priv_ep->dequeue; + + /* Request was dequeued and TRB was changed to TRB_LINK. */ + if (TRB_FIELD_TO_TYPE(le32_to_cpu(trb->control)) == TRB_LINK) { + trace_cdns3_complete_trb(priv_ep, trb); + cdns3_move_deq_to_next_trb(priv_req); + } + + if (!request->stream_id) { + /* Re-select endpoint. It could be changed by other CPU + * during handling usb_gadget_giveback_request. + */ + cdns3_select_ep(priv_dev, priv_ep->endpoint.address); + + while (cdns3_trb_handled(priv_ep, priv_req)) { + priv_req->finished_trb++; + if (priv_req->finished_trb >= priv_req->num_of_trb) + request_handled = true; + + trb = priv_ep->trb_pool + priv_ep->dequeue; + trace_cdns3_complete_trb(priv_ep, trb); + + if (!transfer_end) + request->actual += + TRB_LEN(le32_to_cpu(trb->length)); + + if (priv_req->num_of_trb > 1 && + le32_to_cpu(trb->control) & TRB_SMM) + transfer_end = true; + + cdns3_ep_inc_deq(priv_ep); + } + + if (request_handled) { + cdns3_gadget_giveback(priv_ep, priv_req, 0); + request_handled = false; + transfer_end = false; + } else { + goto prepare_next_td; + } + + if (priv_ep->type != USB_ENDPOINT_XFER_ISOC && + TRBS_PER_SEGMENT == 2) + break; + } else { + /* Re-select endpoint. It could be changed by other CPU + * during handling usb_gadget_giveback_request. + */ + cdns3_select_ep(priv_dev, priv_ep->endpoint.address); + + trb = priv_ep->trb_pool; + trace_cdns3_complete_trb(priv_ep, trb); + + if (trb != priv_req->trb) + dev_warn(priv_dev->dev, + "request_trb=0x%p, queue_trb=0x%p\n", + priv_req->trb, trb); + + request->actual += TRB_LEN(le32_to_cpu(trb->length)); + + if (!request->num_sgs || + (request->num_sgs == (priv_ep->stream_sg_idx + 1))) { + priv_ep->stream_sg_idx = 0; + cdns3_gadget_giveback(priv_ep, priv_req, 0); + } else { + priv_ep->stream_sg_idx++; + cdns3_ep_run_stream_transfer(priv_ep, request); + } + break; + } + } + priv_ep->flags &= ~EP_PENDING_REQUEST; + +prepare_next_td: + if (!(priv_ep->flags & EP_STALLED) && + !(priv_ep->flags & EP_STALL_PENDING)) + cdns3_start_all_request(priv_dev, priv_ep); +} + +void cdns3_rearm_transfer(struct cdns3_endpoint *priv_ep, u8 rearm) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + + cdns3_wa1_restore_cycle_bit(priv_ep); + + if (rearm) { + trace_cdns3_ring(priv_ep); + + /* Cycle Bit must be updated before arming DMA. */ + wmb(); + writel(EP_CMD_DRDY, &priv_dev->regs->ep_cmd); + + __cdns3_gadget_wakeup(priv_dev); + + trace_cdns3_doorbell_epx(priv_ep->name, + readl(&priv_dev->regs->ep_traddr)); + } +} + +static void cdns3_reprogram_tdl(struct cdns3_endpoint *priv_ep) +{ + u16 tdl = priv_ep->pending_tdl; + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + + if (tdl > EP_CMD_TDL_MAX) { + tdl = EP_CMD_TDL_MAX; + priv_ep->pending_tdl -= EP_CMD_TDL_MAX; + } else { + priv_ep->pending_tdl = 0; + } + + writel(EP_CMD_TDL_SET(tdl) | EP_CMD_STDL, &priv_dev->regs->ep_cmd); +} + +/** + * cdns3_check_ep_interrupt_proceed - Processes interrupt related to endpoint + * @priv_ep: endpoint object + * + * Returns 0 + */ +static int cdns3_check_ep_interrupt_proceed(struct cdns3_endpoint *priv_ep) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + u32 ep_sts_reg; + struct usb_request *deferred_request; + struct usb_request *pending_request; + u32 tdl = 0; + + cdns3_select_ep(priv_dev, priv_ep->endpoint.address); + + trace_cdns3_epx_irq(priv_dev, priv_ep); + + ep_sts_reg = readl(&priv_dev->regs->ep_sts); + writel(ep_sts_reg, &priv_dev->regs->ep_sts); + + if ((ep_sts_reg & EP_STS_PRIME) && priv_ep->use_streams) { + bool dbusy = !!(ep_sts_reg & EP_STS_DBUSY); + + tdl = cdns3_get_tdl(priv_dev); + + /* + * Continue the previous transfer: + * There is some racing between ERDY and PRIME. The device send + * ERDY and almost in the same time Host send PRIME. It cause + * that host ignore the ERDY packet and driver has to send it + * again. + */ + if (tdl && (dbusy || !EP_STS_BUFFEMPTY(ep_sts_reg) || + EP_STS_HOSTPP(ep_sts_reg))) { + writel(EP_CMD_ERDY | + EP_CMD_ERDY_SID(priv_ep->last_stream_id), + &priv_dev->regs->ep_cmd); + ep_sts_reg &= ~(EP_STS_MD_EXIT | EP_STS_IOC); + } else { + priv_ep->prime_flag = true; + + pending_request = cdns3_next_request(&priv_ep->pending_req_list); + deferred_request = cdns3_next_request(&priv_ep->deferred_req_list); + + if (deferred_request && !pending_request) { + cdns3_start_all_request(priv_dev, priv_ep); + } + } + } + + if (ep_sts_reg & EP_STS_TRBERR) { + if (priv_ep->flags & EP_STALL_PENDING && + !(ep_sts_reg & EP_STS_DESCMIS && + priv_dev->dev_ver < DEV_VER_V2)) { + cdns3_ep_stall_flush(priv_ep); + } + + /* + * For isochronous transfer driver completes request on + * IOC or on TRBERR. IOC appears only when device receive + * OUT data packet. If host disable stream or lost some packet + * then the only way to finish all queued transfer is to do it + * on TRBERR event. + */ + if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && + !priv_ep->wa1_set) { + if (!priv_ep->dir) { + u32 ep_cfg = readl(&priv_dev->regs->ep_cfg); + + ep_cfg &= ~EP_CFG_ENABLE; + writel(ep_cfg, &priv_dev->regs->ep_cfg); + priv_ep->flags &= ~EP_QUIRK_ISO_OUT_EN; + } + cdns3_transfer_completed(priv_dev, priv_ep); + } else if (!(priv_ep->flags & EP_STALLED) && + !(priv_ep->flags & EP_STALL_PENDING)) { + if (priv_ep->flags & EP_DEFERRED_DRDY) { + priv_ep->flags &= ~EP_DEFERRED_DRDY; + cdns3_start_all_request(priv_dev, priv_ep); + } else { + cdns3_rearm_transfer(priv_ep, + priv_ep->wa1_set); + } + } + } + + if ((ep_sts_reg & EP_STS_IOC) || (ep_sts_reg & EP_STS_ISP) || + (ep_sts_reg & EP_STS_IOT)) { + if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_EN) { + if (ep_sts_reg & EP_STS_ISP) + priv_ep->flags |= EP_QUIRK_END_TRANSFER; + else + priv_ep->flags &= ~EP_QUIRK_END_TRANSFER; + } + + if (!priv_ep->use_streams) { + if ((ep_sts_reg & EP_STS_IOC) || + (ep_sts_reg & EP_STS_ISP)) { + cdns3_transfer_completed(priv_dev, priv_ep); + } else if ((priv_ep->flags & EP_TDLCHK_EN) & + priv_ep->pending_tdl) { + /* handle IOT with pending tdl */ + cdns3_reprogram_tdl(priv_ep); + } + } else if (priv_ep->dir == USB_DIR_OUT) { + priv_ep->ep_sts_pending |= ep_sts_reg; + } else if (ep_sts_reg & EP_STS_IOT) { + cdns3_transfer_completed(priv_dev, priv_ep); + } + } + + /* + * MD_EXIT interrupt sets when stream capable endpoint exits + * from MOVE DATA state of Bulk IN/OUT stream protocol state machine + */ + if (priv_ep->dir == USB_DIR_OUT && (ep_sts_reg & EP_STS_MD_EXIT) && + (priv_ep->ep_sts_pending & EP_STS_IOT) && priv_ep->use_streams) { + priv_ep->ep_sts_pending = 0; + cdns3_transfer_completed(priv_dev, priv_ep); + } + + /* + * WA2: this condition should only be meet when + * priv_ep->flags & EP_QUIRK_EXTRA_BUF_DET or + * priv_ep->flags & EP_QUIRK_EXTRA_BUF_EN. + * In other cases this interrupt will be disabled. + */ + if (ep_sts_reg & EP_STS_DESCMIS && priv_dev->dev_ver < DEV_VER_V2 && + !(priv_ep->flags & EP_STALLED)) + cdns3_wa2_descmissing_packet(priv_ep); + + return 0; +} + +static void cdns3_disconnect_gadget(struct cdns3_device *priv_dev) +{ + if (priv_dev->gadget_driver && priv_dev->gadget_driver->disconnect) + priv_dev->gadget_driver->disconnect(&priv_dev->gadget); +} + +/** + * cdns3_check_usb_interrupt_proceed - Processes interrupt related to device + * @priv_dev: extended gadget object + * @usb_ists: bitmap representation of device's reported interrupts + * (usb_ists register value) + */ +static void cdns3_check_usb_interrupt_proceed(struct cdns3_device *priv_dev, + u32 usb_ists) +__must_hold(&priv_dev->lock) +{ + int speed = 0; + + trace_cdns3_usb_irq(priv_dev, usb_ists); + if (usb_ists & USB_ISTS_L1ENTI) { + /* + * WORKAROUND: CDNS3 controller has issue with hardware resuming + * from L1. To fix it, if any DMA transfer is pending driver + * must starts driving resume signal immediately. + */ + if (readl(&priv_dev->regs->drbl)) + __cdns3_gadget_wakeup(priv_dev); + } + + /* Connection detected */ + if (usb_ists & (USB_ISTS_CON2I | USB_ISTS_CONI)) { + speed = cdns3_get_speed(priv_dev); + priv_dev->gadget.speed = speed; + usb_gadget_set_state(&priv_dev->gadget, USB_STATE_POWERED); + cdns3_ep0_config(priv_dev); + } + + /* Disconnection detected */ + if (usb_ists & (USB_ISTS_DIS2I | USB_ISTS_DISI)) { + spin_unlock(&priv_dev->lock); + cdns3_disconnect_gadget(priv_dev); + spin_lock(&priv_dev->lock); + priv_dev->gadget.speed = USB_SPEED_UNKNOWN; + usb_gadget_set_state(&priv_dev->gadget, USB_STATE_NOTATTACHED); + cdns3_hw_reset_eps_config(priv_dev); + } + + if (usb_ists & (USB_ISTS_L2ENTI | USB_ISTS_U3ENTI)) { + if (priv_dev->gadget_driver && + priv_dev->gadget_driver->suspend) { + spin_unlock(&priv_dev->lock); + priv_dev->gadget_driver->suspend(&priv_dev->gadget); + spin_lock(&priv_dev->lock); + } + } + + if (usb_ists & (USB_ISTS_L2EXTI | USB_ISTS_U3EXTI)) { + if (priv_dev->gadget_driver && + priv_dev->gadget_driver->resume) { + spin_unlock(&priv_dev->lock); + priv_dev->gadget_driver->resume(&priv_dev->gadget); + spin_lock(&priv_dev->lock); + } + } + + /* reset*/ + if (usb_ists & (USB_ISTS_UWRESI | USB_ISTS_UHRESI | USB_ISTS_U2RESI)) { + if (priv_dev->gadget_driver) { + spin_unlock(&priv_dev->lock); + usb_gadget_udc_reset(&priv_dev->gadget, + priv_dev->gadget_driver); + spin_lock(&priv_dev->lock); + + /*read again to check the actual speed*/ + speed = cdns3_get_speed(priv_dev); + priv_dev->gadget.speed = speed; + cdns3_hw_reset_eps_config(priv_dev); + cdns3_ep0_config(priv_dev); + } + } +} + +/** + * cdns3_device_irq_handler- interrupt handler for device part of controller + * + * @irq: irq number for cdns3 core device + * @data: structure of cdns3 + * + * Returns IRQ_HANDLED or IRQ_NONE + */ +static irqreturn_t cdns3_device_irq_handler(int irq, void *data) +{ + struct cdns3_device *priv_dev = data; + struct cdns *cdns = dev_get_drvdata(priv_dev->dev); + irqreturn_t ret = IRQ_NONE; + u32 reg; + + if (cdns->in_lpm) + return ret; + + /* check USB device interrupt */ + reg = readl(&priv_dev->regs->usb_ists); + if (reg) { + /* After masking interrupts the new interrupts won't be + * reported in usb_ists/ep_ists. In order to not lose some + * of them driver disables only detected interrupts. + * They will be enabled ASAP after clearing source of + * interrupt. This an unusual behavior only applies to + * usb_ists register. + */ + reg = ~reg & readl(&priv_dev->regs->usb_ien); + /* mask deferred interrupt. */ + writel(reg, &priv_dev->regs->usb_ien); + ret = IRQ_WAKE_THREAD; + } + + /* check endpoint interrupt */ + reg = readl(&priv_dev->regs->ep_ists); + if (reg) { + writel(0, &priv_dev->regs->ep_ien); + ret = IRQ_WAKE_THREAD; + } + + return ret; +} + +/** + * cdns3_device_thread_irq_handler- interrupt handler for device part + * of controller + * + * @irq: irq number for cdns3 core device + * @data: structure of cdns3 + * + * Returns IRQ_HANDLED or IRQ_NONE + */ +static irqreturn_t cdns3_device_thread_irq_handler(int irq, void *data) +{ + struct cdns3_device *priv_dev = data; + irqreturn_t ret = IRQ_NONE; + unsigned long flags; + unsigned int bit; + unsigned long reg; + + spin_lock_irqsave(&priv_dev->lock, flags); + + reg = readl(&priv_dev->regs->usb_ists); + if (reg) { + writel(reg, &priv_dev->regs->usb_ists); + writel(USB_IEN_INIT, &priv_dev->regs->usb_ien); + cdns3_check_usb_interrupt_proceed(priv_dev, reg); + ret = IRQ_HANDLED; + } + + reg = readl(&priv_dev->regs->ep_ists); + + /* handle default endpoint OUT */ + if (reg & EP_ISTS_EP_OUT0) { + cdns3_check_ep0_interrupt_proceed(priv_dev, USB_DIR_OUT); + ret = IRQ_HANDLED; + } + + /* handle default endpoint IN */ + if (reg & EP_ISTS_EP_IN0) { + cdns3_check_ep0_interrupt_proceed(priv_dev, USB_DIR_IN); + ret = IRQ_HANDLED; + } + + /* check if interrupt from non default endpoint, if no exit */ + reg &= ~(EP_ISTS_EP_OUT0 | EP_ISTS_EP_IN0); + if (!reg) + goto irqend; + + for_each_set_bit(bit, ®, + sizeof(u32) * BITS_PER_BYTE) { + cdns3_check_ep_interrupt_proceed(priv_dev->eps[bit]); + ret = IRQ_HANDLED; + } + + if (priv_dev->dev_ver < DEV_VER_V2 && priv_dev->using_streams) + cdns3_wa2_check_outq_status(priv_dev); + +irqend: + writel(~0, &priv_dev->regs->ep_ien); + spin_unlock_irqrestore(&priv_dev->lock, flags); + + return ret; +} + +/** + * cdns3_ep_onchip_buffer_reserve - Try to reserve onchip buf for EP + * + * The real reservation will occur during write to EP_CFG register, + * this function is used to check if the 'size' reservation is allowed. + * + * @priv_dev: extended gadget object + * @size: the size (KB) for EP would like to allocate + * @is_in: endpoint direction + * + * Return 0 if the required size can met or negative value on failure + */ +static int cdns3_ep_onchip_buffer_reserve(struct cdns3_device *priv_dev, + int size, int is_in) +{ + int remained; + + /* 2KB are reserved for EP0*/ + remained = priv_dev->onchip_buffers - priv_dev->onchip_used_size - 2; + + if (is_in) { + if (remained < size) + return -EPERM; + + priv_dev->onchip_used_size += size; + } else { + int required; + + /** + * ALL OUT EPs are shared the same chunk onchip memory, so + * driver checks if it already has assigned enough buffers + */ + if (priv_dev->out_mem_is_allocated >= size) + return 0; + + required = size - priv_dev->out_mem_is_allocated; + + if (required > remained) + return -EPERM; + + priv_dev->out_mem_is_allocated += required; + priv_dev->onchip_used_size += required; + } + + return 0; +} + +static void cdns3_configure_dmult(struct cdns3_device *priv_dev, + struct cdns3_endpoint *priv_ep) +{ + struct cdns3_usb_regs __iomem *regs = priv_dev->regs; + + /* For dev_ver > DEV_VER_V2 DMULT is configured per endpoint */ + if (priv_dev->dev_ver <= DEV_VER_V2) + writel(USB_CONF_DMULT, ®s->usb_conf); + + if (priv_dev->dev_ver == DEV_VER_V2) + writel(USB_CONF2_EN_TDL_TRB, ®s->usb_conf2); + + if (priv_dev->dev_ver >= DEV_VER_V3 && priv_ep) { + u32 mask; + + if (priv_ep->dir) + mask = BIT(priv_ep->num + 16); + else + mask = BIT(priv_ep->num); + + if (priv_ep->type != USB_ENDPOINT_XFER_ISOC) { + cdns3_set_register_bit(®s->tdl_from_trb, mask); + cdns3_set_register_bit(®s->tdl_beh, mask); + cdns3_set_register_bit(®s->tdl_beh2, mask); + cdns3_set_register_bit(®s->dma_adv_td, mask); + } + + if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && !priv_ep->dir) + cdns3_set_register_bit(®s->tdl_from_trb, mask); + + cdns3_set_register_bit(®s->dtrans, mask); + } +} + +/** + * cdns3_ep_config Configure hardware endpoint + * @priv_ep: extended endpoint object + * @enable: set EP_CFG_ENABLE bit in ep_cfg register. + */ +int cdns3_ep_config(struct cdns3_endpoint *priv_ep, bool enable) +{ + bool is_iso_ep = (priv_ep->type == USB_ENDPOINT_XFER_ISOC); + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + u32 bEndpointAddress = priv_ep->num | priv_ep->dir; + u32 max_packet_size = 0; + u8 maxburst = 0; + u32 ep_cfg = 0; + u8 buffering; + u8 mult = 0; + int ret; + + buffering = CDNS3_EP_BUF_SIZE - 1; + + cdns3_configure_dmult(priv_dev, priv_ep); + + switch (priv_ep->type) { + case USB_ENDPOINT_XFER_INT: + ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_INT); + + if ((priv_dev->dev_ver == DEV_VER_V2 && !priv_ep->dir) || + priv_dev->dev_ver > DEV_VER_V2) + ep_cfg |= EP_CFG_TDL_CHK; + break; + case USB_ENDPOINT_XFER_BULK: + ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_BULK); + + if ((priv_dev->dev_ver == DEV_VER_V2 && !priv_ep->dir) || + priv_dev->dev_ver > DEV_VER_V2) + ep_cfg |= EP_CFG_TDL_CHK; + break; + default: + ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_ISOC); + mult = CDNS3_EP_ISO_HS_MULT - 1; + buffering = mult + 1; + } + + switch (priv_dev->gadget.speed) { + case USB_SPEED_FULL: + max_packet_size = is_iso_ep ? 1023 : 64; + break; + case USB_SPEED_HIGH: + max_packet_size = is_iso_ep ? 1024 : 512; + break; + case USB_SPEED_SUPER: + /* It's limitation that driver assumes in driver. */ + mult = 0; + max_packet_size = 1024; + if (priv_ep->type == USB_ENDPOINT_XFER_ISOC) { + maxburst = CDNS3_EP_ISO_SS_BURST - 1; + buffering = (mult + 1) * + (maxburst + 1); + + if (priv_ep->interval > 1) + buffering++; + } else { + maxburst = CDNS3_EP_BUF_SIZE - 1; + } + break; + default: + /* all other speed are not supported */ + return -EINVAL; + } + + if (max_packet_size == 1024) + priv_ep->trb_burst_size = 128; + else if (max_packet_size >= 512) + priv_ep->trb_burst_size = 64; + else + priv_ep->trb_burst_size = 16; + + /* onchip buffer is only allocated before configuration */ + if (!priv_dev->hw_configured_flag) { + ret = cdns3_ep_onchip_buffer_reserve(priv_dev, buffering + 1, + !!priv_ep->dir); + if (ret) { + dev_err(priv_dev->dev, "onchip mem is full, ep is invalid\n"); + return ret; + } + } + + if (enable) + ep_cfg |= EP_CFG_ENABLE; + + if (priv_ep->use_streams && priv_dev->gadget.speed >= USB_SPEED_SUPER) { + if (priv_dev->dev_ver >= DEV_VER_V3) { + u32 mask = BIT(priv_ep->num + (priv_ep->dir ? 16 : 0)); + + /* + * Stream capable endpoints are handled by using ep_tdl + * register. Other endpoints use TDL from TRB feature. + */ + cdns3_clear_register_bit(&priv_dev->regs->tdl_from_trb, + mask); + } + + /* Enable Stream Bit TDL chk and SID chk */ + ep_cfg |= EP_CFG_STREAM_EN | EP_CFG_TDL_CHK | EP_CFG_SID_CHK; + } + + ep_cfg |= EP_CFG_MAXPKTSIZE(max_packet_size) | + EP_CFG_MULT(mult) | + EP_CFG_BUFFERING(buffering) | + EP_CFG_MAXBURST(maxburst); + + cdns3_select_ep(priv_dev, bEndpointAddress); + writel(ep_cfg, &priv_dev->regs->ep_cfg); + priv_ep->flags |= EP_CONFIGURED; + + dev_dbg(priv_dev->dev, "Configure %s: with val %08x\n", + priv_ep->name, ep_cfg); + + return 0; +} + +/* Find correct direction for HW endpoint according to description */ +static int cdns3_ep_dir_is_correct(struct usb_endpoint_descriptor *desc, + struct cdns3_endpoint *priv_ep) +{ + return (priv_ep->endpoint.caps.dir_in && usb_endpoint_dir_in(desc)) || + (priv_ep->endpoint.caps.dir_out && usb_endpoint_dir_out(desc)); +} + +static struct +cdns3_endpoint *cdns3_find_available_ep(struct cdns3_device *priv_dev, + struct usb_endpoint_descriptor *desc) +{ + struct usb_ep *ep; + struct cdns3_endpoint *priv_ep; + + list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) { + unsigned long num; + int ret; + /* ep name pattern likes epXin or epXout */ + char c[2] = {ep->name[2], '\0'}; + + ret = kstrtoul(c, 10, &num); + if (ret) + return ERR_PTR(ret); + + priv_ep = ep_to_cdns3_ep(ep); + if (cdns3_ep_dir_is_correct(desc, priv_ep)) { + if (!(priv_ep->flags & EP_CLAIMED)) { + priv_ep->num = num; + return priv_ep; + } + } + } + + return ERR_PTR(-ENOENT); +} + +/* + * Cadence IP has one limitation that all endpoints must be configured + * (Type & MaxPacketSize) before setting configuration through hardware + * register, it means we can't change endpoints configuration after + * set_configuration. + * + * This function set EP_CLAIMED flag which is added when the gadget driver + * uses usb_ep_autoconfig to configure specific endpoint; + * When the udc driver receives set_configurion request, + * it goes through all claimed endpoints, and configure all endpoints + * accordingly. + * + * At usb_ep_ops.enable/disable, we only enable and disable endpoint through + * ep_cfg register which can be changed after set_configuration, and do + * some software operation accordingly. + */ +static struct +usb_ep *cdns3_gadget_match_ep(struct usb_gadget *gadget, + struct usb_endpoint_descriptor *desc, + struct usb_ss_ep_comp_descriptor *comp_desc) +{ + struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); + struct cdns3_endpoint *priv_ep; + unsigned long flags; + + priv_ep = cdns3_find_available_ep(priv_dev, desc); + if (IS_ERR(priv_ep)) { + dev_err(priv_dev->dev, "no available ep\n"); + return NULL; + } + + dev_dbg(priv_dev->dev, "match endpoint: %s\n", priv_ep->name); + + spin_lock_irqsave(&priv_dev->lock, flags); + priv_ep->endpoint.desc = desc; + priv_ep->dir = usb_endpoint_dir_in(desc) ? USB_DIR_IN : USB_DIR_OUT; + priv_ep->type = usb_endpoint_type(desc); + priv_ep->flags |= EP_CLAIMED; + priv_ep->interval = desc->bInterval ? BIT(desc->bInterval - 1) : 0; + + spin_unlock_irqrestore(&priv_dev->lock, flags); + return &priv_ep->endpoint; +} + +/** + * cdns3_gadget_ep_alloc_request Allocates request + * @ep: endpoint object associated with request + * @gfp_flags: gfp flags + * + * Returns allocated request address, NULL on allocation error + */ +struct usb_request *cdns3_gadget_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep); + struct cdns3_request *priv_req; + + priv_req = kzalloc(sizeof(*priv_req), gfp_flags); + if (!priv_req) + return NULL; + + priv_req->priv_ep = priv_ep; + + trace_cdns3_alloc_request(priv_req); + return &priv_req->request; +} + +/** + * cdns3_gadget_ep_free_request Free memory occupied by request + * @ep: endpoint object associated with request + * @request: request to free memory + */ +void cdns3_gadget_ep_free_request(struct usb_ep *ep, + struct usb_request *request) +{ + struct cdns3_request *priv_req = to_cdns3_request(request); + + if (priv_req->aligned_buf) + priv_req->aligned_buf->in_use = 0; + + trace_cdns3_free_request(priv_req); + kfree(priv_req); +} + +/** + * cdns3_gadget_ep_enable Enable endpoint + * @ep: endpoint object + * @desc: endpoint descriptor + * + * Returns 0 on success, error code elsewhere + */ +static int cdns3_gadget_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct cdns3_endpoint *priv_ep; + struct cdns3_device *priv_dev; + const struct usb_ss_ep_comp_descriptor *comp_desc; + u32 reg = EP_STS_EN_TRBERREN; + u32 bEndpointAddress; + unsigned long flags; + int enable = 1; + int ret = 0; + int val; + + priv_ep = ep_to_cdns3_ep(ep); + priv_dev = priv_ep->cdns3_dev; + comp_desc = priv_ep->endpoint.comp_desc; + + if (!ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT) { + dev_dbg(priv_dev->dev, "usbss: invalid parameters\n"); + return -EINVAL; + } + + if (!desc->wMaxPacketSize) { + dev_err(priv_dev->dev, "usbss: missing wMaxPacketSize\n"); + return -EINVAL; + } + + if (dev_WARN_ONCE(priv_dev->dev, priv_ep->flags & EP_ENABLED, + "%s is already enabled\n", priv_ep->name)) + return 0; + + spin_lock_irqsave(&priv_dev->lock, flags); + + priv_ep->endpoint.desc = desc; + priv_ep->type = usb_endpoint_type(desc); + priv_ep->interval = desc->bInterval ? BIT(desc->bInterval - 1) : 0; + + if (priv_ep->interval > ISO_MAX_INTERVAL && + priv_ep->type == USB_ENDPOINT_XFER_ISOC) { + dev_err(priv_dev->dev, "Driver is limited to %d period\n", + ISO_MAX_INTERVAL); + + ret = -EINVAL; + goto exit; + } + + bEndpointAddress = priv_ep->num | priv_ep->dir; + cdns3_select_ep(priv_dev, bEndpointAddress); + + /* + * For some versions of controller at some point during ISO OUT traffic + * DMA reads Transfer Ring for the EP which has never got doorbell. + * This issue was detected only on simulation, but to avoid this issue + * driver add protection against it. To fix it driver enable ISO OUT + * endpoint before setting DRBL. This special treatment of ISO OUT + * endpoints are recommended by controller specification. + */ + if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && !priv_ep->dir) + enable = 0; + + if (usb_ss_max_streams(comp_desc) && usb_endpoint_xfer_bulk(desc)) { + /* + * Enable stream support (SS mode) related interrupts + * in EP_STS_EN Register + */ + if (priv_dev->gadget.speed >= USB_SPEED_SUPER) { + reg |= EP_STS_EN_IOTEN | EP_STS_EN_PRIMEEEN | + EP_STS_EN_SIDERREN | EP_STS_EN_MD_EXITEN | + EP_STS_EN_STREAMREN; + priv_ep->use_streams = true; + ret = cdns3_ep_config(priv_ep, enable); + priv_dev->using_streams |= true; + } + } else { + ret = cdns3_ep_config(priv_ep, enable); + } + + if (ret) + goto exit; + + ret = cdns3_allocate_trb_pool(priv_ep); + if (ret) + goto exit; + + bEndpointAddress = priv_ep->num | priv_ep->dir; + cdns3_select_ep(priv_dev, bEndpointAddress); + + trace_cdns3_gadget_ep_enable(priv_ep); + + writel(EP_CMD_EPRST, &priv_dev->regs->ep_cmd); + + ret = readl_poll_timeout_atomic(&priv_dev->regs->ep_cmd, val, + !(val & (EP_CMD_CSTALL | EP_CMD_EPRST)), + 1, 1000); + + if (unlikely(ret)) { + cdns3_free_trb_pool(priv_ep); + ret = -EINVAL; + goto exit; + } + + /* enable interrupt for selected endpoint */ + cdns3_set_register_bit(&priv_dev->regs->ep_ien, + BIT(cdns3_ep_addr_to_index(bEndpointAddress))); + + if (priv_dev->dev_ver < DEV_VER_V2) + cdns3_wa2_enable_detection(priv_dev, priv_ep, reg); + + writel(reg, &priv_dev->regs->ep_sts_en); + + ep->desc = desc; + priv_ep->flags &= ~(EP_PENDING_REQUEST | EP_STALLED | EP_STALL_PENDING | + EP_QUIRK_ISO_OUT_EN | EP_QUIRK_EXTRA_BUF_EN); + priv_ep->flags |= EP_ENABLED | EP_UPDATE_EP_TRBADDR; + priv_ep->wa1_set = 0; + priv_ep->enqueue = 0; + priv_ep->dequeue = 0; + reg = readl(&priv_dev->regs->ep_sts); + priv_ep->pcs = !!EP_STS_CCS(reg); + priv_ep->ccs = !!EP_STS_CCS(reg); + /* one TRB is reserved for link TRB used in DMULT mode*/ + priv_ep->free_trbs = priv_ep->num_trbs - 1; +exit: + spin_unlock_irqrestore(&priv_dev->lock, flags); + + return ret; +} + +/** + * cdns3_gadget_ep_disable Disable endpoint + * @ep: endpoint object + * + * Returns 0 on success, error code elsewhere + */ +static int cdns3_gadget_ep_disable(struct usb_ep *ep) +{ + struct cdns3_endpoint *priv_ep; + struct cdns3_request *priv_req; + struct cdns3_device *priv_dev; + struct usb_request *request; + unsigned long flags; + int ret = 0; + u32 ep_cfg; + int val; + + if (!ep) { + pr_err("usbss: invalid parameters\n"); + return -EINVAL; + } + + priv_ep = ep_to_cdns3_ep(ep); + priv_dev = priv_ep->cdns3_dev; + + if (dev_WARN_ONCE(priv_dev->dev, !(priv_ep->flags & EP_ENABLED), + "%s is already disabled\n", priv_ep->name)) + return 0; + + spin_lock_irqsave(&priv_dev->lock, flags); + + trace_cdns3_gadget_ep_disable(priv_ep); + + cdns3_select_ep(priv_dev, ep->desc->bEndpointAddress); + + ep_cfg = readl(&priv_dev->regs->ep_cfg); + ep_cfg &= ~EP_CFG_ENABLE; + writel(ep_cfg, &priv_dev->regs->ep_cfg); + + /** + * Driver needs some time before resetting endpoint. + * It need waits for clearing DBUSY bit or for timeout expired. + * 10us is enough time for controller to stop transfer. + */ + readl_poll_timeout_atomic(&priv_dev->regs->ep_sts, val, + !(val & EP_STS_DBUSY), 1, 10); + writel(EP_CMD_EPRST, &priv_dev->regs->ep_cmd); + + readl_poll_timeout_atomic(&priv_dev->regs->ep_cmd, val, + !(val & (EP_CMD_CSTALL | EP_CMD_EPRST)), + 1, 1000); + if (unlikely(ret)) + dev_err(priv_dev->dev, "Timeout: %s resetting failed.\n", + priv_ep->name); + + while (!list_empty(&priv_ep->pending_req_list)) { + request = cdns3_next_request(&priv_ep->pending_req_list); + + cdns3_gadget_giveback(priv_ep, to_cdns3_request(request), + -ESHUTDOWN); + } + + while (!list_empty(&priv_ep->wa2_descmiss_req_list)) { + priv_req = cdns3_next_priv_request(&priv_ep->wa2_descmiss_req_list); + + kfree(priv_req->request.buf); + cdns3_gadget_ep_free_request(&priv_ep->endpoint, + &priv_req->request); + list_del_init(&priv_req->list); + --priv_ep->wa2_counter; + } + + while (!list_empty(&priv_ep->deferred_req_list)) { + request = cdns3_next_request(&priv_ep->deferred_req_list); + + cdns3_gadget_giveback(priv_ep, to_cdns3_request(request), + -ESHUTDOWN); + } + + priv_ep->descmis_req = NULL; + + ep->desc = NULL; + priv_ep->flags &= ~EP_ENABLED; + priv_ep->use_streams = false; + + spin_unlock_irqrestore(&priv_dev->lock, flags); + + return ret; +} + +/** + * cdns3_gadget_ep_queue Transfer data on endpoint + * @ep: endpoint object + * @request: request object + * @gfp_flags: gfp flags + * + * Returns 0 on success, error code elsewhere + */ +static int __cdns3_gadget_ep_queue(struct usb_ep *ep, + struct usb_request *request, + gfp_t gfp_flags) +{ + struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep); + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + struct cdns3_request *priv_req; + int ret = 0; + + request->actual = 0; + request->status = -EINPROGRESS; + priv_req = to_cdns3_request(request); + trace_cdns3_ep_queue(priv_req); + + if (priv_dev->dev_ver < DEV_VER_V2) { + ret = cdns3_wa2_gadget_ep_queue(priv_dev, priv_ep, + priv_req); + + if (ret == EINPROGRESS) + return 0; + } + + ret = cdns3_prepare_aligned_request_buf(priv_req); + if (ret < 0) + return ret; + + ret = usb_gadget_map_request_by_dev(priv_dev->sysdev, request, + usb_endpoint_dir_in(ep->desc)); + if (ret) + return ret; + + list_add_tail(&request->list, &priv_ep->deferred_req_list); + + /* + * For stream capable endpoint if prime irq flag is set then only start + * request. + * If hardware endpoint configuration has not been set yet then + * just queue request in deferred list. Transfer will be started in + * cdns3_set_hw_configuration. + */ + if (!request->stream_id) { + if (priv_dev->hw_configured_flag && + !(priv_ep->flags & EP_STALLED) && + !(priv_ep->flags & EP_STALL_PENDING)) + cdns3_start_all_request(priv_dev, priv_ep); + } else { + if (priv_dev->hw_configured_flag && priv_ep->prime_flag) + cdns3_start_all_request(priv_dev, priv_ep); + } + + return 0; +} + +static int cdns3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, + gfp_t gfp_flags) +{ + struct usb_request *zlp_request; + struct cdns3_endpoint *priv_ep; + struct cdns3_device *priv_dev; + unsigned long flags; + int ret; + + if (!request || !ep) + return -EINVAL; + + priv_ep = ep_to_cdns3_ep(ep); + priv_dev = priv_ep->cdns3_dev; + + spin_lock_irqsave(&priv_dev->lock, flags); + + ret = __cdns3_gadget_ep_queue(ep, request, gfp_flags); + + if (ret == 0 && request->zero && request->length && + (request->length % ep->maxpacket == 0)) { + struct cdns3_request *priv_req; + + zlp_request = cdns3_gadget_ep_alloc_request(ep, GFP_ATOMIC); + zlp_request->buf = priv_dev->zlp_buf; + zlp_request->length = 0; + + priv_req = to_cdns3_request(zlp_request); + priv_req->flags |= REQUEST_ZLP; + + dev_dbg(priv_dev->dev, "Queuing ZLP for endpoint: %s\n", + priv_ep->name); + ret = __cdns3_gadget_ep_queue(ep, zlp_request, gfp_flags); + } + + spin_unlock_irqrestore(&priv_dev->lock, flags); + return ret; +} + +/** + * cdns3_gadget_ep_dequeue Remove request from transfer queue + * @ep: endpoint object associated with request + * @request: request object + * + * Returns 0 on success, error code elsewhere + */ +int cdns3_gadget_ep_dequeue(struct usb_ep *ep, + struct usb_request *request) +{ + struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep); + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + struct usb_request *req, *req_temp; + struct cdns3_request *priv_req; + struct cdns3_trb *link_trb; + u8 req_on_hw_ring = 0; + unsigned long flags; + int ret = 0; + + if (!ep || !request || !ep->desc) + return -EINVAL; + + spin_lock_irqsave(&priv_dev->lock, flags); + + priv_req = to_cdns3_request(request); + + trace_cdns3_ep_dequeue(priv_req); + + cdns3_select_ep(priv_dev, ep->desc->bEndpointAddress); + + list_for_each_entry_safe(req, req_temp, &priv_ep->pending_req_list, + list) { + if (request == req) { + req_on_hw_ring = 1; + goto found; + } + } + + list_for_each_entry_safe(req, req_temp, &priv_ep->deferred_req_list, + list) { + if (request == req) + goto found; + } + + goto not_found; + +found: + link_trb = priv_req->trb; + + /* Update ring only if removed request is on pending_req_list list */ + if (req_on_hw_ring && link_trb) { + link_trb->buffer = cpu_to_le32(TRB_BUFFER(priv_ep->trb_pool_dma + + ((priv_req->end_trb + 1) * TRB_SIZE))); + link_trb->control = cpu_to_le32((le32_to_cpu(link_trb->control) & TRB_CYCLE) | + TRB_TYPE(TRB_LINK) | TRB_CHAIN); + + if (priv_ep->wa1_trb == priv_req->trb) + cdns3_wa1_restore_cycle_bit(priv_ep); + } + + cdns3_gadget_giveback(priv_ep, priv_req, -ECONNRESET); + +not_found: + spin_unlock_irqrestore(&priv_dev->lock, flags); + return ret; +} + +/** + * __cdns3_gadget_ep_set_halt Sets stall on selected endpoint + * Should be called after acquiring spin_lock and selecting ep + * @priv_ep: endpoint object to set stall on. + */ +void __cdns3_gadget_ep_set_halt(struct cdns3_endpoint *priv_ep) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + + trace_cdns3_halt(priv_ep, 1, 0); + + if (!(priv_ep->flags & EP_STALLED)) { + u32 ep_sts_reg = readl(&priv_dev->regs->ep_sts); + + if (!(ep_sts_reg & EP_STS_DBUSY)) + cdns3_ep_stall_flush(priv_ep); + else + priv_ep->flags |= EP_STALL_PENDING; + } +} + +/** + * __cdns3_gadget_ep_clear_halt Clears stall on selected endpoint + * Should be called after acquiring spin_lock and selecting ep + * @priv_ep: endpoint object to clear stall on + */ +int __cdns3_gadget_ep_clear_halt(struct cdns3_endpoint *priv_ep) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + struct usb_request *request; + struct cdns3_request *priv_req; + struct cdns3_trb *trb = NULL; + int ret; + int val; + + trace_cdns3_halt(priv_ep, 0, 0); + + request = cdns3_next_request(&priv_ep->pending_req_list); + if (request) { + priv_req = to_cdns3_request(request); + trb = priv_req->trb; + if (trb) + trb->control = trb->control ^ cpu_to_le32(TRB_CYCLE); + } + + writel(EP_CMD_CSTALL | EP_CMD_EPRST, &priv_dev->regs->ep_cmd); + + /* wait for EPRST cleared */ + ret = readl_poll_timeout_atomic(&priv_dev->regs->ep_cmd, val, + !(val & EP_CMD_EPRST), 1, 100); + if (ret) + return -EINVAL; + + priv_ep->flags &= ~(EP_STALLED | EP_STALL_PENDING); + + if (request) { + if (trb) + trb->control = trb->control ^ cpu_to_le32(TRB_CYCLE); + + cdns3_rearm_transfer(priv_ep, 1); + } + + cdns3_start_all_request(priv_dev, priv_ep); + return ret; +} + +/** + * cdns3_gadget_ep_set_halt Sets/clears stall on selected endpoint + * @ep: endpoint object to set/clear stall on + * @value: 1 for set stall, 0 for clear stall + * + * Returns 0 on success, error code elsewhere + */ +int cdns3_gadget_ep_set_halt(struct usb_ep *ep, int value) +{ + struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep); + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + unsigned long flags; + int ret = 0; + + if (!(priv_ep->flags & EP_ENABLED)) + return -EPERM; + + spin_lock_irqsave(&priv_dev->lock, flags); + + cdns3_select_ep(priv_dev, ep->desc->bEndpointAddress); + + if (!value) { + priv_ep->flags &= ~EP_WEDGE; + ret = __cdns3_gadget_ep_clear_halt(priv_ep); + } else { + __cdns3_gadget_ep_set_halt(priv_ep); + } + + spin_unlock_irqrestore(&priv_dev->lock, flags); + + return ret; +} + +extern const struct usb_ep_ops cdns3_gadget_ep0_ops; + +static const struct usb_ep_ops cdns3_gadget_ep_ops = { + .enable = cdns3_gadget_ep_enable, + .disable = cdns3_gadget_ep_disable, + .alloc_request = cdns3_gadget_ep_alloc_request, + .free_request = cdns3_gadget_ep_free_request, + .queue = cdns3_gadget_ep_queue, + .dequeue = cdns3_gadget_ep_dequeue, + .set_halt = cdns3_gadget_ep_set_halt, + .set_wedge = cdns3_gadget_ep_set_wedge, +}; + +/** + * cdns3_gadget_get_frame Returns number of actual ITP frame + * @gadget: gadget object + * + * Returns number of actual ITP frame + */ +static int cdns3_gadget_get_frame(struct usb_gadget *gadget) +{ + struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); + + return readl(&priv_dev->regs->usb_itpn); +} + +int __cdns3_gadget_wakeup(struct cdns3_device *priv_dev) +{ + enum usb_device_speed speed; + + speed = cdns3_get_speed(priv_dev); + + if (speed >= USB_SPEED_SUPER) + return 0; + + /* Start driving resume signaling to indicate remote wakeup. */ + writel(USB_CONF_LGO_L0, &priv_dev->regs->usb_conf); + + return 0; +} + +static int cdns3_gadget_wakeup(struct usb_gadget *gadget) +{ + struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&priv_dev->lock, flags); + ret = __cdns3_gadget_wakeup(priv_dev); + spin_unlock_irqrestore(&priv_dev->lock, flags); + return ret; +} + +static int cdns3_gadget_set_selfpowered(struct usb_gadget *gadget, + int is_selfpowered) +{ + struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); + unsigned long flags; + + spin_lock_irqsave(&priv_dev->lock, flags); + priv_dev->is_selfpowered = !!is_selfpowered; + spin_unlock_irqrestore(&priv_dev->lock, flags); + return 0; +} + +static int cdns3_gadget_pullup(struct usb_gadget *gadget, int is_on) +{ + struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); + + if (is_on) { + writel(USB_CONF_DEVEN, &priv_dev->regs->usb_conf); + } else { + writel(~0, &priv_dev->regs->ep_ists); + writel(~0, &priv_dev->regs->usb_ists); + writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf); + } + + return 0; +} + +static void cdns3_gadget_config(struct cdns3_device *priv_dev) +{ + struct cdns3_usb_regs __iomem *regs = priv_dev->regs; + u32 reg; + + cdns3_ep0_config(priv_dev); + + /* enable interrupts for endpoint 0 (in and out) */ + writel(EP_IEN_EP_OUT0 | EP_IEN_EP_IN0, ®s->ep_ien); + + /* + * Driver needs to modify LFPS minimal U1 Exit time for DEV_VER_TI_V1 + * revision of controller. + */ + if (priv_dev->dev_ver == DEV_VER_TI_V1) { + reg = readl(®s->dbg_link1); + + reg &= ~DBG_LINK1_LFPS_MIN_GEN_U1_EXIT_MASK; + reg |= DBG_LINK1_LFPS_MIN_GEN_U1_EXIT(0x55) | + DBG_LINK1_LFPS_MIN_GEN_U1_EXIT_SET; + writel(reg, ®s->dbg_link1); + } + + /* + * By default some platforms has set protected access to memory. + * This cause problem with cache, so driver restore non-secure + * access to memory. + */ + reg = readl(®s->dma_axi_ctrl); + reg |= DMA_AXI_CTRL_MARPROT(DMA_AXI_CTRL_NON_SECURE) | + DMA_AXI_CTRL_MAWPROT(DMA_AXI_CTRL_NON_SECURE); + writel(reg, ®s->dma_axi_ctrl); + + /* enable generic interrupt*/ + writel(USB_IEN_INIT, ®s->usb_ien); + writel(USB_CONF_CLK2OFFDS | USB_CONF_L1DS, ®s->usb_conf); + /* keep Fast Access bit */ + writel(PUSB_PWR_FST_REG_ACCESS, &priv_dev->regs->usb_pwr); + + cdns3_configure_dmult(priv_dev, NULL); +} + +/** + * cdns3_gadget_udc_start Gadget start + * @gadget: gadget object + * @driver: driver which operates on this gadget + * + * Returns 0 on success, error code elsewhere + */ +static int cdns3_gadget_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); + unsigned long flags; + enum usb_device_speed max_speed = driver->max_speed; + + spin_lock_irqsave(&priv_dev->lock, flags); + priv_dev->gadget_driver = driver; + + /* limit speed if necessary */ + max_speed = min(driver->max_speed, gadget->max_speed); + + switch (max_speed) { + case USB_SPEED_FULL: + writel(USB_CONF_SFORCE_FS, &priv_dev->regs->usb_conf); + writel(USB_CONF_USB3DIS, &priv_dev->regs->usb_conf); + break; + case USB_SPEED_HIGH: + writel(USB_CONF_USB3DIS, &priv_dev->regs->usb_conf); + break; + case USB_SPEED_SUPER: + break; + default: + dev_err(priv_dev->dev, + "invalid maximum_speed parameter %d\n", + max_speed); + fallthrough; + case USB_SPEED_UNKNOWN: + /* default to superspeed */ + max_speed = USB_SPEED_SUPER; + break; + } + + cdns3_gadget_config(priv_dev); + spin_unlock_irqrestore(&priv_dev->lock, flags); + return 0; +} + +/** + * cdns3_gadget_udc_stop Stops gadget + * @gadget: gadget object + * + * Returns 0 + */ +static int cdns3_gadget_udc_stop(struct usb_gadget *gadget) +{ + struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); + struct cdns3_endpoint *priv_ep; + u32 bEndpointAddress; + struct usb_ep *ep; + int val; + + priv_dev->gadget_driver = NULL; + + priv_dev->onchip_used_size = 0; + priv_dev->out_mem_is_allocated = 0; + priv_dev->gadget.speed = USB_SPEED_UNKNOWN; + + list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) { + priv_ep = ep_to_cdns3_ep(ep); + bEndpointAddress = priv_ep->num | priv_ep->dir; + cdns3_select_ep(priv_dev, bEndpointAddress); + writel(EP_CMD_EPRST, &priv_dev->regs->ep_cmd); + readl_poll_timeout_atomic(&priv_dev->regs->ep_cmd, val, + !(val & EP_CMD_EPRST), 1, 100); + + priv_ep->flags &= ~EP_CLAIMED; + } + + /* disable interrupt for device */ + writel(0, &priv_dev->regs->usb_ien); + writel(0, &priv_dev->regs->usb_pwr); + writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf); + + return 0; +} + +static const struct usb_gadget_ops cdns3_gadget_ops = { + .get_frame = cdns3_gadget_get_frame, + .wakeup = cdns3_gadget_wakeup, + .set_selfpowered = cdns3_gadget_set_selfpowered, + .pullup = cdns3_gadget_pullup, + .udc_start = cdns3_gadget_udc_start, + .udc_stop = cdns3_gadget_udc_stop, + .match_ep = cdns3_gadget_match_ep, +}; + +static void cdns3_free_all_eps(struct cdns3_device *priv_dev) +{ + int i; + + /* ep0 OUT point to ep0 IN. */ + priv_dev->eps[16] = NULL; + + for (i = 0; i < CDNS3_ENDPOINTS_MAX_COUNT; i++) + if (priv_dev->eps[i]) { + cdns3_free_trb_pool(priv_dev->eps[i]); + devm_kfree(priv_dev->dev, priv_dev->eps[i]); + } +} + +/** + * cdns3_init_eps Initializes software endpoints of gadget + * @priv_dev: extended gadget object + * + * Returns 0 on success, error code elsewhere + */ +static int cdns3_init_eps(struct cdns3_device *priv_dev) +{ + u32 ep_enabled_reg, iso_ep_reg; + struct cdns3_endpoint *priv_ep; + int ep_dir, ep_number; + u32 ep_mask; + int ret = 0; + int i; + + /* Read it from USB_CAP3 to USB_CAP5 */ + ep_enabled_reg = readl(&priv_dev->regs->usb_cap3); + iso_ep_reg = readl(&priv_dev->regs->usb_cap4); + + dev_dbg(priv_dev->dev, "Initializing non-zero endpoints\n"); + + for (i = 0; i < CDNS3_ENDPOINTS_MAX_COUNT; i++) { + ep_dir = i >> 4; /* i div 16 */ + ep_number = i & 0xF; /* i % 16 */ + ep_mask = BIT(i); + + if (!(ep_enabled_reg & ep_mask)) + continue; + + if (ep_dir && !ep_number) { + priv_dev->eps[i] = priv_dev->eps[0]; + continue; + } + + priv_ep = devm_kzalloc(priv_dev->dev, sizeof(*priv_ep), + GFP_KERNEL); + if (!priv_ep) + goto err; + + /* set parent of endpoint object */ + priv_ep->cdns3_dev = priv_dev; + priv_dev->eps[i] = priv_ep; + priv_ep->num = ep_number; + priv_ep->dir = ep_dir ? USB_DIR_IN : USB_DIR_OUT; + + if (!ep_number) { + ret = cdns3_init_ep0(priv_dev, priv_ep); + if (ret) { + dev_err(priv_dev->dev, "Failed to init ep0\n"); + goto err; + } + } else { + snprintf(priv_ep->name, sizeof(priv_ep->name), "ep%d%s", + ep_number, !!ep_dir ? "in" : "out"); + priv_ep->endpoint.name = priv_ep->name; + + usb_ep_set_maxpacket_limit(&priv_ep->endpoint, + CDNS3_EP_MAX_PACKET_LIMIT); + priv_ep->endpoint.max_streams = CDNS3_EP_MAX_STREAMS; + priv_ep->endpoint.ops = &cdns3_gadget_ep_ops; + if (ep_dir) + priv_ep->endpoint.caps.dir_in = 1; + else + priv_ep->endpoint.caps.dir_out = 1; + + if (iso_ep_reg & ep_mask) + priv_ep->endpoint.caps.type_iso = 1; + + priv_ep->endpoint.caps.type_bulk = 1; + priv_ep->endpoint.caps.type_int = 1; + + list_add_tail(&priv_ep->endpoint.ep_list, + &priv_dev->gadget.ep_list); + } + + priv_ep->flags = 0; + + dev_dbg(priv_dev->dev, "Initialized %s support: %s %s\n", + priv_ep->name, + priv_ep->endpoint.caps.type_bulk ? "BULK, INT" : "", + priv_ep->endpoint.caps.type_iso ? "ISO" : ""); + + INIT_LIST_HEAD(&priv_ep->pending_req_list); + INIT_LIST_HEAD(&priv_ep->deferred_req_list); + INIT_LIST_HEAD(&priv_ep->wa2_descmiss_req_list); + } + + return 0; +err: + cdns3_free_all_eps(priv_dev); + return -ENOMEM; +} + +static void cdns3_gadget_release(struct device *dev) +{ + struct cdns3_device *priv_dev = container_of(dev, + struct cdns3_device, gadget.dev); + + kfree(priv_dev); +} + +static void cdns3_gadget_exit(struct cdns *cdns) +{ + struct cdns3_device *priv_dev; + + priv_dev = cdns->gadget_dev; + + + pm_runtime_mark_last_busy(cdns->dev); + pm_runtime_put_autosuspend(cdns->dev); + + usb_del_gadget(&priv_dev->gadget); + devm_free_irq(cdns->dev, cdns->dev_irq, priv_dev); + + cdns3_free_all_eps(priv_dev); + + while (!list_empty(&priv_dev->aligned_buf_list)) { + struct cdns3_aligned_buf *buf; + + buf = cdns3_next_align_buf(&priv_dev->aligned_buf_list); + dma_free_coherent(priv_dev->sysdev, buf->size, + buf->buf, + buf->dma); + + list_del(&buf->list); + kfree(buf); + } + + dma_free_coherent(priv_dev->sysdev, 8, priv_dev->setup_buf, + priv_dev->setup_dma); + + kfree(priv_dev->zlp_buf); + usb_put_gadget(&priv_dev->gadget); + cdns->gadget_dev = NULL; + cdns_drd_gadget_off(cdns); +} + +static int cdns3_gadget_start(struct cdns *cdns) +{ + struct cdns3_device *priv_dev; + u32 max_speed; + int ret; + + priv_dev = kzalloc(sizeof(*priv_dev), GFP_KERNEL); + if (!priv_dev) + return -ENOMEM; + + usb_initialize_gadget(cdns->dev, &priv_dev->gadget, + cdns3_gadget_release); + cdns->gadget_dev = priv_dev; + priv_dev->sysdev = cdns->dev; + priv_dev->dev = cdns->dev; + priv_dev->regs = cdns->dev_regs; + + device_property_read_u16(priv_dev->dev, "cdns,on-chip-buff-size", + &priv_dev->onchip_buffers); + + if (priv_dev->onchip_buffers <= 0) { + u32 reg = readl(&priv_dev->regs->usb_cap2); + + priv_dev->onchip_buffers = USB_CAP2_ACTUAL_MEM_SIZE(reg); + } + + if (!priv_dev->onchip_buffers) + priv_dev->onchip_buffers = 256; + + max_speed = usb_get_maximum_speed(cdns->dev); + + /* Check the maximum_speed parameter */ + switch (max_speed) { + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + case USB_SPEED_SUPER: + break; + default: + dev_err(cdns->dev, "invalid maximum_speed parameter %d\n", + max_speed); + fallthrough; + case USB_SPEED_UNKNOWN: + /* default to superspeed */ + max_speed = USB_SPEED_SUPER; + break; + } + + /* fill gadget fields */ + priv_dev->gadget.max_speed = max_speed; + priv_dev->gadget.speed = USB_SPEED_UNKNOWN; + priv_dev->gadget.ops = &cdns3_gadget_ops; + priv_dev->gadget.name = "usb-ss-gadget"; + priv_dev->gadget.quirk_avoids_skb_reserve = 1; + priv_dev->gadget.irq = cdns->dev_irq; + + spin_lock_init(&priv_dev->lock); + INIT_WORK(&priv_dev->pending_status_wq, + cdns3_pending_setup_status_handler); + + INIT_WORK(&priv_dev->aligned_buf_wq, + cdns3_free_aligned_request_buf); + + /* initialize endpoint container */ + INIT_LIST_HEAD(&priv_dev->gadget.ep_list); + INIT_LIST_HEAD(&priv_dev->aligned_buf_list); + + ret = cdns3_init_eps(priv_dev); + if (ret) { + dev_err(priv_dev->dev, "Failed to create endpoints\n"); + goto err1; + } + + /* allocate memory for setup packet buffer */ + priv_dev->setup_buf = dma_alloc_coherent(priv_dev->sysdev, 8, + &priv_dev->setup_dma, GFP_DMA); + if (!priv_dev->setup_buf) { + ret = -ENOMEM; + goto err2; + } + + priv_dev->dev_ver = readl(&priv_dev->regs->usb_cap6); + + dev_dbg(priv_dev->dev, "Device Controller version: %08x\n", + readl(&priv_dev->regs->usb_cap6)); + dev_dbg(priv_dev->dev, "USB Capabilities:: %08x\n", + readl(&priv_dev->regs->usb_cap1)); + dev_dbg(priv_dev->dev, "On-Chip memory configuration: %08x\n", + readl(&priv_dev->regs->usb_cap2)); + + priv_dev->dev_ver = GET_DEV_BASE_VERSION(priv_dev->dev_ver); + if (priv_dev->dev_ver >= DEV_VER_V2) + priv_dev->gadget.sg_supported = 1; + + priv_dev->zlp_buf = kzalloc(CDNS3_EP_ZLP_BUF_SIZE, GFP_KERNEL); + if (!priv_dev->zlp_buf) { + ret = -ENOMEM; + goto err3; + } + + /* add USB gadget device */ + ret = usb_add_gadget(&priv_dev->gadget); + if (ret < 0) { + dev_err(priv_dev->dev, "Failed to add gadget\n"); + goto err4; + } + + return 0; +err4: + kfree(priv_dev->zlp_buf); +err3: + dma_free_coherent(priv_dev->sysdev, 8, priv_dev->setup_buf, + priv_dev->setup_dma); +err2: + cdns3_free_all_eps(priv_dev); +err1: + usb_put_gadget(&priv_dev->gadget); + cdns->gadget_dev = NULL; + return ret; +} + +static int __cdns3_gadget_init(struct cdns *cdns) +{ + int ret = 0; + + /* Ensure 32-bit DMA Mask in case we switched back from Host mode */ + ret = dma_set_mask_and_coherent(cdns->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(cdns->dev, "Failed to set dma mask: %d\n", ret); + return ret; + } + + cdns_drd_gadget_on(cdns); + pm_runtime_get_sync(cdns->dev); + + ret = cdns3_gadget_start(cdns); + if (ret) + return ret; + + /* + * Because interrupt line can be shared with other components in + * driver it can't use IRQF_ONESHOT flag here. + */ + ret = devm_request_threaded_irq(cdns->dev, cdns->dev_irq, + cdns3_device_irq_handler, + cdns3_device_thread_irq_handler, + IRQF_SHARED, dev_name(cdns->dev), + cdns->gadget_dev); + + if (ret) + goto err0; + + return 0; +err0: + cdns3_gadget_exit(cdns); + return ret; +} + +static int cdns3_gadget_suspend(struct cdns *cdns, bool do_wakeup) +__must_hold(&cdns->lock) +{ + struct cdns3_device *priv_dev = cdns->gadget_dev; + + spin_unlock(&cdns->lock); + cdns3_disconnect_gadget(priv_dev); + spin_lock(&cdns->lock); + + priv_dev->gadget.speed = USB_SPEED_UNKNOWN; + usb_gadget_set_state(&priv_dev->gadget, USB_STATE_NOTATTACHED); + cdns3_hw_reset_eps_config(priv_dev); + + /* disable interrupt for device */ + writel(0, &priv_dev->regs->usb_ien); + + return 0; +} + +static int cdns3_gadget_resume(struct cdns *cdns, bool hibernated) +{ + struct cdns3_device *priv_dev = cdns->gadget_dev; + + if (!priv_dev->gadget_driver) + return 0; + + cdns3_gadget_config(priv_dev); + + return 0; +} + +/** + * cdns3_gadget_init - initialize device structure + * + * @cdns: cdns instance + * + * This function initializes the gadget. + */ +int cdns3_gadget_init(struct cdns *cdns) +{ + struct cdns_role_driver *rdrv; + + rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL); + if (!rdrv) + return -ENOMEM; + + rdrv->start = __cdns3_gadget_init; + rdrv->stop = cdns3_gadget_exit; + rdrv->suspend = cdns3_gadget_suspend; + rdrv->resume = cdns3_gadget_resume; + rdrv->state = CDNS_ROLE_STATE_INACTIVE; + rdrv->name = "gadget"; + cdns->roles[USB_ROLE_DEVICE] = rdrv; + + return 0; +} diff --git a/drivers/usb/cdns3/cdns3-gadget.h b/drivers/usb/cdns3/cdns3-gadget.h new file mode 100644 index 000000000000..21fa461c518e --- /dev/null +++ b/drivers/usb/cdns3/cdns3-gadget.h @@ -0,0 +1,1368 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * USBSS device controller driver header file + * + * Copyright (C) 2018-2019 Cadence. + * Copyright (C) 2017-2018 NXP + * + * Author: Pawel Laszczak + * Pawel Jez + * Peter Chen + */ +#ifndef __LINUX_CDNS3_GADGET +#define __LINUX_CDNS3_GADGET +#include + +/* + * USBSS-DEV register interface. + * This corresponds to the USBSS Device Controller Interface + */ + +/** + * struct cdns3_usb_regs - device controller registers. + * @usb_conf: Global Configuration. + * @usb_sts: Global Status. + * @usb_cmd: Global Command. + * @usb_itpn: ITP/SOF number. + * @usb_lpm: Global Command. + * @usb_ien: USB Interrupt Enable. + * @usb_ists: USB Interrupt Status. + * @ep_sel: Endpoint Select. + * @ep_traddr: Endpoint Transfer Ring Address. + * @ep_cfg: Endpoint Configuration. + * @ep_cmd: Endpoint Command. + * @ep_sts: Endpoint Status. + * @ep_sts_sid: Endpoint Status. + * @ep_sts_en: Endpoint Status Enable. + * @drbl: Doorbell. + * @ep_ien: EP Interrupt Enable. + * @ep_ists: EP Interrupt Status. + * @usb_pwr: Global Power Configuration. + * @usb_conf2: Global Configuration 2. + * @usb_cap1: Capability 1. + * @usb_cap2: Capability 2. + * @usb_cap3: Capability 3. + * @usb_cap4: Capability 4. + * @usb_cap5: Capability 5. + * @usb_cap6: Capability 6. + * @usb_cpkt1: Custom Packet 1. + * @usb_cpkt2: Custom Packet 2. + * @usb_cpkt3: Custom Packet 3. + * @ep_dma_ext_addr: Upper address for DMA operations. + * @buf_addr: Address for On-chip Buffer operations. + * @buf_data: Data for On-chip Buffer operations. + * @buf_ctrl: On-chip Buffer Access Control. + * @dtrans: DMA Transfer Mode. + * @tdl_from_trb: Source of TD Configuration. + * @tdl_beh: TDL Behavior Configuration. + * @ep_tdl: Endpoint TDL. + * @tdl_beh2: TDL Behavior 2 Configuration. + * @dma_adv_td: DMA Advance TD Configuration. + * @reserved1: Reserved. + * @cfg_regs: Configuration. + * @reserved2: Reserved. + * @dma_axi_ctrl: AXI Control. + * @dma_axi_id: AXI ID register. + * @dma_axi_cap: AXI Capability. + * @dma_axi_ctrl0: AXI Control 0. + * @dma_axi_ctrl1: AXI Control 1. + */ +struct cdns3_usb_regs { + __le32 usb_conf; + __le32 usb_sts; + __le32 usb_cmd; + __le32 usb_itpn; + __le32 usb_lpm; + __le32 usb_ien; + __le32 usb_ists; + __le32 ep_sel; + __le32 ep_traddr; + __le32 ep_cfg; + __le32 ep_cmd; + __le32 ep_sts; + __le32 ep_sts_sid; + __le32 ep_sts_en; + __le32 drbl; + __le32 ep_ien; + __le32 ep_ists; + __le32 usb_pwr; + __le32 usb_conf2; + __le32 usb_cap1; + __le32 usb_cap2; + __le32 usb_cap3; + __le32 usb_cap4; + __le32 usb_cap5; + __le32 usb_cap6; + __le32 usb_cpkt1; + __le32 usb_cpkt2; + __le32 usb_cpkt3; + __le32 ep_dma_ext_addr; + __le32 buf_addr; + __le32 buf_data; + __le32 buf_ctrl; + __le32 dtrans; + __le32 tdl_from_trb; + __le32 tdl_beh; + __le32 ep_tdl; + __le32 tdl_beh2; + __le32 dma_adv_td; + __le32 reserved1[26]; + __le32 cfg_reg1; + __le32 dbg_link1; + __le32 dbg_link2; + __le32 cfg_regs[74]; + __le32 reserved2[51]; + __le32 dma_axi_ctrl; + __le32 dma_axi_id; + __le32 dma_axi_cap; + __le32 dma_axi_ctrl0; + __le32 dma_axi_ctrl1; +}; + +/* USB_CONF - bitmasks */ +/* Reset USB device configuration. */ +#define USB_CONF_CFGRST BIT(0) +/* Set Configuration. */ +#define USB_CONF_CFGSET BIT(1) +/* Disconnect USB device in SuperSpeed. */ +#define USB_CONF_USB3DIS BIT(3) +/* Disconnect USB device in HS/FS */ +#define USB_CONF_USB2DIS BIT(4) +/* Little Endian access - default */ +#define USB_CONF_LENDIAN BIT(5) +/* + * Big Endian access. Driver assume that byte order for + * SFRs access always is as Little Endian so this bit + * is not used. + */ +#define USB_CONF_BENDIAN BIT(6) +/* Device software reset. */ +#define USB_CONF_SWRST BIT(7) +/* Singular DMA transfer mode. Only for VER < DEV_VER_V3*/ +#define USB_CONF_DSING BIT(8) +/* Multiple DMA transfers mode. Only for VER < DEV_VER_V3 */ +#define USB_CONF_DMULT BIT(9) +/* DMA clock turn-off enable. */ +#define USB_CONF_DMAOFFEN BIT(10) +/* DMA clock turn-off disable. */ +#define USB_CONF_DMAOFFDS BIT(11) +/* Clear Force Full Speed. */ +#define USB_CONF_CFORCE_FS BIT(12) +/* Set Force Full Speed. */ +#define USB_CONF_SFORCE_FS BIT(13) +/* Device enable. */ +#define USB_CONF_DEVEN BIT(14) +/* Device disable. */ +#define USB_CONF_DEVDS BIT(15) +/* L1 LPM state entry enable (used in HS/FS mode). */ +#define USB_CONF_L1EN BIT(16) +/* L1 LPM state entry disable (used in HS/FS mode). */ +#define USB_CONF_L1DS BIT(17) +/* USB 2.0 clock gate disable. */ +#define USB_CONF_CLK2OFFEN BIT(18) +/* USB 2.0 clock gate enable. */ +#define USB_CONF_CLK2OFFDS BIT(19) +/* L0 LPM state entry request (used in HS/FS mode). */ +#define USB_CONF_LGO_L0 BIT(20) +/* USB 3.0 clock gate disable. */ +#define USB_CONF_CLK3OFFEN BIT(21) +/* USB 3.0 clock gate enable. */ +#define USB_CONF_CLK3OFFDS BIT(22) +/* Bit 23 is reserved*/ +/* U1 state entry enable (used in SS mode). */ +#define USB_CONF_U1EN BIT(24) +/* U1 state entry disable (used in SS mode). */ +#define USB_CONF_U1DS BIT(25) +/* U2 state entry enable (used in SS mode). */ +#define USB_CONF_U2EN BIT(26) +/* U2 state entry disable (used in SS mode). */ +#define USB_CONF_U2DS BIT(27) +/* U0 state entry request (used in SS mode). */ +#define USB_CONF_LGO_U0 BIT(28) +/* U1 state entry request (used in SS mode). */ +#define USB_CONF_LGO_U1 BIT(29) +/* U2 state entry request (used in SS mode). */ +#define USB_CONF_LGO_U2 BIT(30) +/* SS.Inactive state entry request (used in SS mode) */ +#define USB_CONF_LGO_SSINACT BIT(31) + +/* USB_STS - bitmasks */ +/* + * Configuration status. + * 1 - device is in the configured state. + * 0 - device is not configured. + */ +#define USB_STS_CFGSTS_MASK BIT(0) +#define USB_STS_CFGSTS(p) ((p) & USB_STS_CFGSTS_MASK) +/* + * On-chip memory overflow. + * 0 - On-chip memory status OK. + * 1 - On-chip memory overflow. + */ +#define USB_STS_OV_MASK BIT(1) +#define USB_STS_OV(p) ((p) & USB_STS_OV_MASK) +/* + * SuperSpeed connection status. + * 0 - USB in SuperSpeed mode disconnected. + * 1 - USB in SuperSpeed mode connected. + */ +#define USB_STS_USB3CONS_MASK BIT(2) +#define USB_STS_USB3CONS(p) ((p) & USB_STS_USB3CONS_MASK) +/* + * DMA transfer configuration status. + * 0 - single request. + * 1 - multiple TRB chain + * Supported only for controller version < DEV_VER_V3 + */ +#define USB_STS_DTRANS_MASK BIT(3) +#define USB_STS_DTRANS(p) ((p) & USB_STS_DTRANS_MASK) +/* + * Device speed. + * 0 - Undefined (value after reset). + * 1 - Low speed + * 2 - Full speed + * 3 - High speed + * 4 - Super speed + */ +#define USB_STS_USBSPEED_MASK GENMASK(6, 4) +#define USB_STS_USBSPEED(p) (((p) & USB_STS_USBSPEED_MASK) >> 4) +#define USB_STS_LS (0x1 << 4) +#define USB_STS_FS (0x2 << 4) +#define USB_STS_HS (0x3 << 4) +#define USB_STS_SS (0x4 << 4) +#define DEV_UNDEFSPEED(p) (((p) & USB_STS_USBSPEED_MASK) == (0x0 << 4)) +#define DEV_LOWSPEED(p) (((p) & USB_STS_USBSPEED_MASK) == USB_STS_LS) +#define DEV_FULLSPEED(p) (((p) & USB_STS_USBSPEED_MASK) == USB_STS_FS) +#define DEV_HIGHSPEED(p) (((p) & USB_STS_USBSPEED_MASK) == USB_STS_HS) +#define DEV_SUPERSPEED(p) (((p) & USB_STS_USBSPEED_MASK) == USB_STS_SS) +/* + * Endianness for SFR access. + * 0 - Little Endian order (default after hardware reset). + * 1 - Big Endian order + */ +#define USB_STS_ENDIAN_MASK BIT(7) +#define USB_STS_ENDIAN(p) ((p) & USB_STS_ENDIAN_MASK) +/* + * HS/FS clock turn-off status. + * 0 - hsfs clock is always on. + * 1 - hsfs clock turn-off in L2 (HS/FS mode) is enabled + * (default after hardware reset). + */ +#define USB_STS_CLK2OFF_MASK BIT(8) +#define USB_STS_CLK2OFF(p) ((p) & USB_STS_CLK2OFF_MASK) +/* + * PCLK clock turn-off status. + * 0 - pclk clock is always on. + * 1 - pclk clock turn-off in U3 (SS mode) is enabled + * (default after hardware reset). + */ +#define USB_STS_CLK3OFF_MASK BIT(9) +#define USB_STS_CLK3OFF(p) ((p) & USB_STS_CLK3OFF_MASK) +/* + * Controller in reset state. + * 0 - Internal reset is active. + * 1 - Internal reset is not active and controller is fully operational. + */ +#define USB_STS_IN_RST_MASK BIT(10) +#define USB_STS_IN_RST(p) ((p) & USB_STS_IN_RST_MASK) +/* + * Status of the "TDL calculation basing on TRB" feature. + * 0 - disabled + * 1 - enabled + * Supported only for DEV_VER_V2 controller version. + */ +#define USB_STS_TDL_TRB_ENABLED BIT(11) +/* + * Device enable Status. + * 0 - USB device is disabled (VBUS input is disconnected from internal logic). + * 1 - USB device is enabled (VBUS input is connected to the internal logic). + */ +#define USB_STS_DEVS_MASK BIT(14) +#define USB_STS_DEVS(p) ((p) & USB_STS_DEVS_MASK) +/* + * Address status. + * 0 - USB device is default state. + * 1 - USB device is at least in address state. + */ +#define USB_STS_ADDRESSED_MASK BIT(15) +#define USB_STS_ADDRESSED(p) ((p) & USB_STS_ADDRESSED_MASK) +/* + * L1 LPM state enable status (used in HS/FS mode). + * 0 - Entering to L1 LPM state disabled. + * 1 - Entering to L1 LPM state enabled. + */ +#define USB_STS_L1ENS_MASK BIT(16) +#define USB_STS_L1ENS(p) ((p) & USB_STS_L1ENS_MASK) +/* + * Internal VBUS connection status (used both in HS/FS and SS mode). + * 0 - internal VBUS is not detected. + * 1 - internal VBUS is detected. + */ +#define USB_STS_VBUSS_MASK BIT(17) +#define USB_STS_VBUSS(p) ((p) & USB_STS_VBUSS_MASK) +/* + * HS/FS LPM state (used in FS/HS mode). + * 0 - L0 State + * 1 - L1 State + * 2 - L2 State + * 3 - L3 State + */ +#define USB_STS_LPMST_MASK GENMASK(19, 18) +#define DEV_L0_STATE(p) (((p) & USB_STS_LPMST_MASK) == (0x0 << 18)) +#define DEV_L1_STATE(p) (((p) & USB_STS_LPMST_MASK) == (0x1 << 18)) +#define DEV_L2_STATE(p) (((p) & USB_STS_LPMST_MASK) == (0x2 << 18)) +#define DEV_L3_STATE(p) (((p) & USB_STS_LPMST_MASK) == (0x3 << 18)) +/* + * Disable HS status (used in FS/HS mode). + * 0 - the disconnect bit for HS/FS mode is set . + * 1 - the disconnect bit for HS/FS mode is not set. + */ +#define USB_STS_USB2CONS_MASK BIT(20) +#define USB_STS_USB2CONS(p) ((p) & USB_STS_USB2CONS_MASK) +/* + * HS/FS mode connection status (used in FS/HS mode). + * 0 - High Speed operations in USB2.0 (FS/HS) mode not disabled. + * 1 - High Speed operations in USB2.0 (FS/HS). + */ +#define USB_STS_DISABLE_HS_MASK BIT(21) +#define USB_STS_DISABLE_HS(p) ((p) & USB_STS_DISABLE_HS_MASK) +/* + * U1 state enable status (used in SS mode). + * 0 - Entering to U1 state disabled. + * 1 - Entering to U1 state enabled. + */ +#define USB_STS_U1ENS_MASK BIT(24) +#define USB_STS_U1ENS(p) ((p) & USB_STS_U1ENS_MASK) +/* + * U2 state enable status (used in SS mode). + * 0 - Entering to U2 state disabled. + * 1 - Entering to U2 state enabled. + */ +#define USB_STS_U2ENS_MASK BIT(25) +#define USB_STS_U2ENS(p) ((p) & USB_STS_U2ENS_MASK) +/* + * SuperSpeed Link LTSSM state. This field reflects USBSS-DEV current + * SuperSpeed link state + */ +#define USB_STS_LST_MASK GENMASK(29, 26) +#define DEV_LST_U0 (((p) & USB_STS_LST_MASK) == (0x0 << 26)) +#define DEV_LST_U1 (((p) & USB_STS_LST_MASK) == (0x1 << 26)) +#define DEV_LST_U2 (((p) & USB_STS_LST_MASK) == (0x2 << 26)) +#define DEV_LST_U3 (((p) & USB_STS_LST_MASK) == (0x3 << 26)) +#define DEV_LST_DISABLED (((p) & USB_STS_LST_MASK) == (0x4 << 26)) +#define DEV_LST_RXDETECT (((p) & USB_STS_LST_MASK) == (0x5 << 26)) +#define DEV_LST_INACTIVE (((p) & USB_STS_LST_MASK) == (0x6 << 26)) +#define DEV_LST_POLLING (((p) & USB_STS_LST_MASK) == (0x7 << 26)) +#define DEV_LST_RECOVERY (((p) & USB_STS_LST_MASK) == (0x8 << 26)) +#define DEV_LST_HOT_RESET (((p) & USB_STS_LST_MASK) == (0x9 << 26)) +#define DEV_LST_COMP_MODE (((p) & USB_STS_LST_MASK) == (0xa << 26)) +#define DEV_LST_LB_STATE (((p) & USB_STS_LST_MASK) == (0xb << 26)) +/* + * DMA clock turn-off status. + * 0 - DMA clock is always on (default after hardware reset). + * 1 - DMA clock turn-off in U1, U2 and U3 (SS mode) is enabled. + */ +#define USB_STS_DMAOFF_MASK BIT(30) +#define USB_STS_DMAOFF(p) ((p) & USB_STS_DMAOFF_MASK) +/* + * SFR Endian status. + * 0 - Little Endian order (default after hardware reset). + * 1 - Big Endian order. + */ +#define USB_STS_ENDIAN2_MASK BIT(31) +#define USB_STS_ENDIAN2(p) ((p) & USB_STS_ENDIAN2_MASK) + +/* USB_CMD - bitmasks */ +/* Set Function Address */ +#define USB_CMD_SET_ADDR BIT(0) +/* + * Function Address This field is saved to the device only when the field + * SET_ADDR is set '1 ' during write to USB_CMD register. + * Software is responsible for entering the address of the device during + * SET_ADDRESS request service. This field should be set immediately after + * the SETUP packet is decoded, and prior to confirmation of the status phase + */ +#define USB_CMD_FADDR_MASK GENMASK(7, 1) +#define USB_CMD_FADDR(p) (((p) << 1) & USB_CMD_FADDR_MASK) +/* Send Function Wake Device Notification TP (used only in SS mode). */ +#define USB_CMD_SDNFW BIT(8) +/* Set Test Mode (used only in HS/FS mode). */ +#define USB_CMD_STMODE BIT(9) +/* Test mode selector (used only in HS/FS mode) */ +#define USB_STS_TMODE_SEL_MASK GENMASK(11, 10) +#define USB_STS_TMODE_SEL(p) (((p) << 10) & USB_STS_TMODE_SEL_MASK) +/* + * Send Latency Tolerance Message Device Notification TP (used only + * in SS mode). + */ +#define USB_CMD_SDNLTM BIT(12) +/* Send Custom Transaction Packet (used only in SS mode) */ +#define USB_CMD_SPKT BIT(13) +/*Device Notification 'Function Wake' - Interface value (only in SS mode. */ +#define USB_CMD_DNFW_INT_MASK GENMASK(23, 16) +#define USB_STS_DNFW_INT(p) (((p) << 16) & USB_CMD_DNFW_INT_MASK) +/* + * Device Notification 'Latency Tolerance Message' -373 BELT value [7:0] + * (used only in SS mode). + */ +#define USB_CMD_DNLTM_BELT_MASK GENMASK(27, 16) +#define USB_STS_DNLTM_BELT(p) (((p) << 16) & USB_CMD_DNLTM_BELT_MASK) + +/* USB_ITPN - bitmasks */ +/* + * ITP(SS) / SOF (HS/FS) number + * In SS mode this field represent number of last ITP received from host. + * In HS/FS mode this field represent number of last SOF received from host. + */ +#define USB_ITPN_MASK GENMASK(13, 0) +#define USB_ITPN(p) ((p) & USB_ITPN_MASK) + +/* USB_LPM - bitmasks */ +/* Host Initiated Resume Duration. */ +#define USB_LPM_HIRD_MASK GENMASK(3, 0) +#define USB_LPM_HIRD(p) ((p) & USB_LPM_HIRD_MASK) +/* Remote Wakeup Enable (bRemoteWake). */ +#define USB_LPM_BRW BIT(4) + +/* USB_IEN - bitmasks */ +/* SS connection interrupt enable */ +#define USB_IEN_CONIEN BIT(0) +/* SS disconnection interrupt enable. */ +#define USB_IEN_DISIEN BIT(1) +/* USB SS warm reset interrupt enable. */ +#define USB_IEN_UWRESIEN BIT(2) +/* USB SS hot reset interrupt enable */ +#define USB_IEN_UHRESIEN BIT(3) +/* SS link U3 state enter interrupt enable (suspend).*/ +#define USB_IEN_U3ENTIEN BIT(4) +/* SS link U3 state exit interrupt enable (wakeup). */ +#define USB_IEN_U3EXTIEN BIT(5) +/* SS link U2 state enter interrupt enable.*/ +#define USB_IEN_U2ENTIEN BIT(6) +/* SS link U2 state exit interrupt enable.*/ +#define USB_IEN_U2EXTIEN BIT(7) +/* SS link U1 state enter interrupt enable.*/ +#define USB_IEN_U1ENTIEN BIT(8) +/* SS link U1 state exit interrupt enable.*/ +#define USB_IEN_U1EXTIEN BIT(9) +/* ITP/SOF packet detected interrupt enable.*/ +#define USB_IEN_ITPIEN BIT(10) +/* Wakeup interrupt enable.*/ +#define USB_IEN_WAKEIEN BIT(11) +/* Send Custom Packet interrupt enable.*/ +#define USB_IEN_SPKTIEN BIT(12) +/* HS/FS mode connection interrupt enable.*/ +#define USB_IEN_CON2IEN BIT(16) +/* HS/FS mode disconnection interrupt enable.*/ +#define USB_IEN_DIS2IEN BIT(17) +/* USB reset (HS/FS mode) interrupt enable.*/ +#define USB_IEN_U2RESIEN BIT(18) +/* LPM L2 state enter interrupt enable.*/ +#define USB_IEN_L2ENTIEN BIT(20) +/* LPM L2 state exit interrupt enable.*/ +#define USB_IEN_L2EXTIEN BIT(21) +/* LPM L1 state enter interrupt enable.*/ +#define USB_IEN_L1ENTIEN BIT(24) +/* LPM L1 state exit interrupt enable.*/ +#define USB_IEN_L1EXTIEN BIT(25) +/* Configuration reset interrupt enable.*/ +#define USB_IEN_CFGRESIEN BIT(26) +/* Start of the USB SS warm reset interrupt enable.*/ +#define USB_IEN_UWRESSIEN BIT(28) +/* End of the USB SS warm reset interrupt enable.*/ +#define USB_IEN_UWRESEIEN BIT(29) + +#define USB_IEN_INIT (USB_IEN_U2RESIEN | USB_ISTS_DIS2I | USB_IEN_CON2IEN \ + | USB_IEN_UHRESIEN | USB_IEN_UWRESIEN | USB_IEN_DISIEN \ + | USB_IEN_CONIEN | USB_IEN_U3EXTIEN | USB_IEN_L2ENTIEN \ + | USB_IEN_L2EXTIEN | USB_IEN_L1ENTIEN | USB_IEN_U3ENTIEN) + +/* USB_ISTS - bitmasks */ +/* SS Connection detected. */ +#define USB_ISTS_CONI BIT(0) +/* SS Disconnection detected. */ +#define USB_ISTS_DISI BIT(1) +/* UUSB warm reset detectede. */ +#define USB_ISTS_UWRESI BIT(2) +/* USB hot reset detected. */ +#define USB_ISTS_UHRESI BIT(3) +/* U3 link state enter detected (suspend).*/ +#define USB_ISTS_U3ENTI BIT(4) +/* U3 link state exit detected (wakeup). */ +#define USB_ISTS_U3EXTI BIT(5) +/* U2 link state enter detected.*/ +#define USB_ISTS_U2ENTI BIT(6) +/* U2 link state exit detected.*/ +#define USB_ISTS_U2EXTI BIT(7) +/* U1 link state enter detected.*/ +#define USB_ISTS_U1ENTI BIT(8) +/* U1 link state exit detected.*/ +#define USB_ISTS_U1EXTI BIT(9) +/* ITP/SOF packet detected.*/ +#define USB_ISTS_ITPI BIT(10) +/* Wakeup detected.*/ +#define USB_ISTS_WAKEI BIT(11) +/* Send Custom Packet detected.*/ +#define USB_ISTS_SPKTI BIT(12) +/* HS/FS mode connection detected.*/ +#define USB_ISTS_CON2I BIT(16) +/* HS/FS mode disconnection detected.*/ +#define USB_ISTS_DIS2I BIT(17) +/* USB reset (HS/FS mode) detected.*/ +#define USB_ISTS_U2RESI BIT(18) +/* LPM L2 state enter detected.*/ +#define USB_ISTS_L2ENTI BIT(20) +/* LPM L2 state exit detected.*/ +#define USB_ISTS_L2EXTI BIT(21) +/* LPM L1 state enter detected.*/ +#define USB_ISTS_L1ENTI BIT(24) +/* LPM L1 state exit detected.*/ +#define USB_ISTS_L1EXTI BIT(25) +/* USB configuration reset detected.*/ +#define USB_ISTS_CFGRESI BIT(26) +/* Start of the USB warm reset detected.*/ +#define USB_ISTS_UWRESSI BIT(28) +/* End of the USB warm reset detected.*/ +#define USB_ISTS_UWRESEI BIT(29) + +/* USB_SEL - bitmasks */ +#define EP_SEL_EPNO_MASK GENMASK(3, 0) +/* Endpoint number. */ +#define EP_SEL_EPNO(p) ((p) & EP_SEL_EPNO_MASK) +/* Endpoint direction bit - 0 - OUT, 1 - IN. */ +#define EP_SEL_DIR BIT(7) + +#define select_ep_in(nr) (EP_SEL_EPNO(p) | EP_SEL_DIR) +#define select_ep_out (EP_SEL_EPNO(p)) + +/* EP_TRADDR - bitmasks */ +/* Transfer Ring address. */ +#define EP_TRADDR_TRADDR(p) ((p)) + +/* EP_CFG - bitmasks */ +/* Endpoint enable */ +#define EP_CFG_ENABLE BIT(0) +/* + * Endpoint type. + * 1 - isochronous + * 2 - bulk + * 3 - interrupt + */ +#define EP_CFG_EPTYPE_MASK GENMASK(2, 1) +#define EP_CFG_EPTYPE(p) (((p) << 1) & EP_CFG_EPTYPE_MASK) +/* Stream support enable (only in SS mode). */ +#define EP_CFG_STREAM_EN BIT(3) +/* TDL check (only in SS mode for BULK EP). */ +#define EP_CFG_TDL_CHK BIT(4) +/* SID check (only in SS mode for BULK OUT EP). */ +#define EP_CFG_SID_CHK BIT(5) +/* DMA transfer endianness. */ +#define EP_CFG_EPENDIAN BIT(7) +/* Max burst size (used only in SS mode). */ +#define EP_CFG_MAXBURST_MASK GENMASK(11, 8) +#define EP_CFG_MAXBURST(p) (((p) << 8) & EP_CFG_MAXBURST_MASK) +/* ISO max burst. */ +#define EP_CFG_MULT_MASK GENMASK(15, 14) +#define EP_CFG_MULT(p) (((p) << 14) & EP_CFG_MULT_MASK) +/* ISO max burst. */ +#define EP_CFG_MAXPKTSIZE_MASK GENMASK(26, 16) +#define EP_CFG_MAXPKTSIZE(p) (((p) << 16) & EP_CFG_MAXPKTSIZE_MASK) +/* Max number of buffered packets. */ +#define EP_CFG_BUFFERING_MASK GENMASK(31, 27) +#define EP_CFG_BUFFERING(p) (((p) << 27) & EP_CFG_BUFFERING_MASK) + +/* EP_CMD - bitmasks */ +/* Endpoint reset. */ +#define EP_CMD_EPRST BIT(0) +/* Endpoint STALL set. */ +#define EP_CMD_SSTALL BIT(1) +/* Endpoint STALL clear. */ +#define EP_CMD_CSTALL BIT(2) +/* Send ERDY TP. */ +#define EP_CMD_ERDY BIT(3) +/* Request complete. */ +#define EP_CMD_REQ_CMPL BIT(5) +/* Transfer descriptor ready. */ +#define EP_CMD_DRDY BIT(6) +/* Data flush. */ +#define EP_CMD_DFLUSH BIT(7) +/* + * Transfer Descriptor Length write (used only for Bulk Stream capable + * endpoints in SS mode). + * Bit Removed from DEV_VER_V3 controller version. + */ +#define EP_CMD_STDL BIT(8) +/* + * Transfer Descriptor Length (used only in SS mode for bulk endpoints). + * Bits Removed from DEV_VER_V3 controller version. + */ +#define EP_CMD_TDL_MASK GENMASK(15, 9) +#define EP_CMD_TDL_SET(p) (((p) << 9) & EP_CMD_TDL_MASK) +#define EP_CMD_TDL_GET(p) (((p) & EP_CMD_TDL_MASK) >> 9) +#define EP_CMD_TDL_MAX (EP_CMD_TDL_MASK >> 9) + +/* ERDY Stream ID value (used in SS mode). */ +#define EP_CMD_ERDY_SID_MASK GENMASK(31, 16) +#define EP_CMD_ERDY_SID(p) (((p) << 16) & EP_CMD_ERDY_SID_MASK) + +/* EP_STS - bitmasks */ +/* Setup transfer complete. */ +#define EP_STS_SETUP BIT(0) +/* Endpoint STALL status. */ +#define EP_STS_STALL(p) ((p) & BIT(1)) +/* Interrupt On Complete. */ +#define EP_STS_IOC BIT(2) +/* Interrupt on Short Packet. */ +#define EP_STS_ISP BIT(3) +/* Transfer descriptor missing. */ +#define EP_STS_DESCMIS BIT(4) +/* Stream Rejected (used only in SS mode) */ +#define EP_STS_STREAMR BIT(5) +/* EXIT from MOVE DATA State (used only for stream transfers in SS mode). */ +#define EP_STS_MD_EXIT BIT(6) +/* TRB error. */ +#define EP_STS_TRBERR BIT(7) +/* Not ready (used only in SS mode). */ +#define EP_STS_NRDY BIT(8) +/* DMA busy bit. */ +#define EP_STS_DBUSY BIT(9) +/* Endpoint Buffer Empty */ +#define EP_STS_BUFFEMPTY(p) ((p) & BIT(10)) +/* Current Cycle Status */ +#define EP_STS_CCS(p) ((p) & BIT(11)) +/* Prime (used only in SS mode. */ +#define EP_STS_PRIME BIT(12) +/* Stream error (used only in SS mode). */ +#define EP_STS_SIDERR BIT(13) +/* OUT size mismatch. */ +#define EP_STS_OUTSMM BIT(14) +/* ISO transmission error. */ +#define EP_STS_ISOERR BIT(15) +/* Host Packet Pending (only for SS mode). */ +#define EP_STS_HOSTPP(p) ((p) & BIT(16)) +/* Stream Protocol State Machine State (only for Bulk stream endpoints). */ +#define EP_STS_SPSMST_MASK GENMASK(18, 17) +#define EP_STS_SPSMST_DISABLED(p) (((p) & EP_STS_SPSMST_MASK) >> 17) +#define EP_STS_SPSMST_IDLE(p) (((p) & EP_STS_SPSMST_MASK) >> 17) +#define EP_STS_SPSMST_START_STREAM(p) (((p) & EP_STS_SPSMST_MASK) >> 17) +#define EP_STS_SPSMST_MOVE_DATA(p) (((p) & EP_STS_SPSMST_MASK) >> 17) +/* Interrupt On Transfer complete. */ +#define EP_STS_IOT BIT(19) +/* OUT queue endpoint number. */ +#define EP_STS_OUTQ_NO_MASK GENMASK(27, 24) +#define EP_STS_OUTQ_NO(p) (((p) & EP_STS_OUTQ_NO_MASK) >> 24) +/* OUT queue valid flag. */ +#define EP_STS_OUTQ_VAL_MASK BIT(28) +#define EP_STS_OUTQ_VAL(p) ((p) & EP_STS_OUTQ_VAL_MASK) +/* SETUP WAIT. */ +#define EP_STS_STPWAIT BIT(31) + +/* EP_STS_SID - bitmasks */ +/* Stream ID (used only in SS mode). */ +#define EP_STS_SID_MASK GENMASK(15, 0) +#define EP_STS_SID(p) ((p) & EP_STS_SID_MASK) + +/* EP_STS_EN - bitmasks */ +/* SETUP interrupt enable. */ +#define EP_STS_EN_SETUPEN BIT(0) +/* OUT transfer missing descriptor enable. */ +#define EP_STS_EN_DESCMISEN BIT(4) +/* Stream Rejected enable. */ +#define EP_STS_EN_STREAMREN BIT(5) +/* Move Data Exit enable.*/ +#define EP_STS_EN_MD_EXITEN BIT(6) +/* TRB enable. */ +#define EP_STS_EN_TRBERREN BIT(7) +/* NRDY enable. */ +#define EP_STS_EN_NRDYEN BIT(8) +/* Prime enable. */ +#define EP_STS_EN_PRIMEEEN BIT(12) +/* Stream error enable. */ +#define EP_STS_EN_SIDERREN BIT(13) +/* OUT size mismatch enable. */ +#define EP_STS_EN_OUTSMMEN BIT(14) +/* ISO transmission error enable. */ +#define EP_STS_EN_ISOERREN BIT(15) +/* Interrupt on Transmission complete enable. */ +#define EP_STS_EN_IOTEN BIT(19) +/* Setup Wait interrupt enable. */ +#define EP_STS_EN_STPWAITEN BIT(31) + +/* DRBL- bitmasks */ +#define DB_VALUE_BY_INDEX(index) (1 << (index)) +#define DB_VALUE_EP0_OUT BIT(0) +#define DB_VALUE_EP0_IN BIT(16) + +/* EP_IEN - bitmasks */ +#define EP_IEN(index) (1 << (index)) +#define EP_IEN_EP_OUT0 BIT(0) +#define EP_IEN_EP_IN0 BIT(16) + +/* EP_ISTS - bitmasks */ +#define EP_ISTS(index) (1 << (index)) +#define EP_ISTS_EP_OUT0 BIT(0) +#define EP_ISTS_EP_IN0 BIT(16) + +/* USB_PWR- bitmasks */ +/*Power Shut Off capability enable*/ +#define PUSB_PWR_PSO_EN BIT(0) +/*Power Shut Off capability disable*/ +#define PUSB_PWR_PSO_DS BIT(1) +/* + * Enables turning-off Reference Clock. + * This bit is optional and implemented only when support for OTG is + * implemented (indicated by OTG_READY bit set to '1'). + */ +#define PUSB_PWR_STB_CLK_SWITCH_EN BIT(8) +/* + * Status bit indicating that operation required by STB_CLK_SWITCH_EN write + * is completed + */ +#define PUSB_PWR_STB_CLK_SWITCH_DONE BIT(9) +/* This bit informs if Fast Registers Access is enabled. */ +#define PUSB_PWR_FST_REG_ACCESS_STAT BIT(30) +/* Fast Registers Access Enable. */ +#define PUSB_PWR_FST_REG_ACCESS BIT(31) + +/* USB_CONF2- bitmasks */ +/* + * Writing 1 disables TDL calculation basing on TRB feature in controller + * for DMULT mode. + * Bit supported only for DEV_VER_V2 version. + */ +#define USB_CONF2_DIS_TDL_TRB BIT(1) +/* + * Writing 1 enables TDL calculation basing on TRB feature in controller + * for DMULT mode. + * Bit supported only for DEV_VER_V2 version. + */ +#define USB_CONF2_EN_TDL_TRB BIT(2) + +/* USB_CAP1- bitmasks */ +/* + * SFR Interface type + * These field reflects type of SFR interface implemented: + * 0x0 - OCP + * 0x1 - AHB, + * 0x2 - PLB + * 0x3 - AXI + * 0x4-0xF - reserved + */ +#define USB_CAP1_SFR_TYPE_MASK GENMASK(3, 0) +#define DEV_SFR_TYPE_OCP(p) (((p) & USB_CAP1_SFR_TYPE_MASK) == 0x0) +#define DEV_SFR_TYPE_AHB(p) (((p) & USB_CAP1_SFR_TYPE_MASK) == 0x1) +#define DEV_SFR_TYPE_PLB(p) (((p) & USB_CAP1_SFR_TYPE_MASK) == 0x2) +#define DEV_SFR_TYPE_AXI(p) (((p) & USB_CAP1_SFR_TYPE_MASK) == 0x3) +/* + * SFR Interface width + * These field reflects width of SFR interface implemented: + * 0x0 - 8 bit interface, + * 0x1 - 16 bit interface, + * 0x2 - 32 bit interface + * 0x3 - 64 bit interface + * 0x4-0xF - reserved + */ +#define USB_CAP1_SFR_WIDTH_MASK GENMASK(7, 4) +#define DEV_SFR_WIDTH_8(p) (((p) & USB_CAP1_SFR_WIDTH_MASK) == (0x0 << 4)) +#define DEV_SFR_WIDTH_16(p) (((p) & USB_CAP1_SFR_WIDTH_MASK) == (0x1 << 4)) +#define DEV_SFR_WIDTH_32(p) (((p) & USB_CAP1_SFR_WIDTH_MASK) == (0x2 << 4)) +#define DEV_SFR_WIDTH_64(p) (((p) & USB_CAP1_SFR_WIDTH_MASK) == (0x3 << 4)) +/* + * DMA Interface type + * These field reflects type of DMA interface implemented: + * 0x0 - OCP + * 0x1 - AHB, + * 0x2 - PLB + * 0x3 - AXI + * 0x4-0xF - reserved + */ +#define USB_CAP1_DMA_TYPE_MASK GENMASK(11, 8) +#define DEV_DMA_TYPE_OCP(p) (((p) & USB_CAP1_DMA_TYPE_MASK) == (0x0 << 8)) +#define DEV_DMA_TYPE_AHB(p) (((p) & USB_CAP1_DMA_TYPE_MASK) == (0x1 << 8)) +#define DEV_DMA_TYPE_PLB(p) (((p) & USB_CAP1_DMA_TYPE_MASK) == (0x2 << 8)) +#define DEV_DMA_TYPE_AXI(p) (((p) & USB_CAP1_DMA_TYPE_MASK) == (0x3 << 8)) +/* + * DMA Interface width + * These field reflects width of DMA interface implemented: + * 0x0 - reserved, + * 0x1 - reserved, + * 0x2 - 32 bit interface + * 0x3 - 64 bit interface + * 0x4-0xF - reserved + */ +#define USB_CAP1_DMA_WIDTH_MASK GENMASK(15, 12) +#define DEV_DMA_WIDTH_32(p) (((p) & USB_CAP1_DMA_WIDTH_MASK) == (0x2 << 12)) +#define DEV_DMA_WIDTH_64(p) (((p) & USB_CAP1_DMA_WIDTH_MASK) == (0x3 << 12)) +/* + * USB3 PHY Interface type + * These field reflects type of USB3 PHY interface implemented: + * 0x0 - USB PIPE, + * 0x1 - RMMI, + * 0x2-0xF - reserved + */ +#define USB_CAP1_U3PHY_TYPE_MASK GENMASK(19, 16) +#define DEV_U3PHY_PIPE(p) (((p) & USB_CAP1_U3PHY_TYPE_MASK) == (0x0 << 16)) +#define DEV_U3PHY_RMMI(p) (((p) & USB_CAP1_U3PHY_TYPE_MASK) == (0x1 << 16)) +/* + * USB3 PHY Interface width + * These field reflects width of USB3 PHY interface implemented: + * 0x0 - 8 bit PIPE interface, + * 0x1 - 16 bit PIPE interface, + * 0x2 - 32 bit PIPE interface, + * 0x3 - 64 bit PIPE interface + * 0x4-0xF - reserved + * Note: When SSIC interface is implemented this field shows the width of + * internal PIPE interface. The RMMI interface is always 20bit wide. + */ +#define USB_CAP1_U3PHY_WIDTH_MASK GENMASK(23, 20) +#define DEV_U3PHY_WIDTH_8(p) \ + (((p) & USB_CAP1_U3PHY_WIDTH_MASK) == (0x0 << 20)) +#define DEV_U3PHY_WIDTH_16(p) \ + (((p) & USB_CAP1_U3PHY_WIDTH_MASK) == (0x1 << 16)) +#define DEV_U3PHY_WIDTH_32(p) \ + (((p) & USB_CAP1_U3PHY_WIDTH_MASK) == (0x2 << 20)) +#define DEV_U3PHY_WIDTH_64(p) \ + (((p) & USB_CAP1_U3PHY_WIDTH_MASK) == (0x3 << 16)) + +/* + * USB2 PHY Interface enable + * These field informs if USB2 PHY interface is implemented: + * 0x0 - interface NOT implemented, + * 0x1 - interface implemented + */ +#define USB_CAP1_U2PHY_EN(p) ((p) & BIT(24)) +/* + * USB2 PHY Interface type + * These field reflects type of USB2 PHY interface implemented: + * 0x0 - UTMI, + * 0x1 - ULPI + */ +#define DEV_U2PHY_ULPI(p) ((p) & BIT(25)) +/* + * USB2 PHY Interface width + * These field reflects width of USB2 PHY interface implemented: + * 0x0 - 8 bit interface, + * 0x1 - 16 bit interface, + * Note: The ULPI interface is always 8bit wide. + */ +#define DEV_U2PHY_WIDTH_16(p) ((p) & BIT(26)) +/* + * OTG Ready + * 0x0 - pure device mode + * 0x1 - some features and ports for CDNS USB OTG controller are implemented. + */ +#define USB_CAP1_OTG_READY(p) ((p) & BIT(27)) + +/* + * When set, indicates that controller supports automatic internal TDL + * calculation basing on the size provided in TRB (TRB[22:17]) for DMULT mode + * Supported only for DEV_VER_V2 controller version. + */ +#define USB_CAP1_TDL_FROM_TRB(p) ((p) & BIT(28)) + +/* USB_CAP2- bitmasks */ +/* + * The actual size of the connected On-chip RAM memory in kB: + * - 0 means 256 kB (max supported mem size) + * - value other than 0 reflects the mem size in kB + */ +#define USB_CAP2_ACTUAL_MEM_SIZE(p) ((p) & GENMASK(7, 0)) +/* + * Max supported mem size + * These field reflects width of on-chip RAM address bus width, + * which determines max supported mem size: + * 0x0-0x7 - reserved, + * 0x8 - support for 4kB mem, + * 0x9 - support for 8kB mem, + * 0xA - support for 16kB mem, + * 0xB - support for 32kB mem, + * 0xC - support for 64kB mem, + * 0xD - support for 128kB mem, + * 0xE - support for 256kB mem, + * 0xF - reserved + */ +#define USB_CAP2_MAX_MEM_SIZE(p) ((p) & GENMASK(11, 8)) + +/* USB_CAP3- bitmasks */ +#define EP_IS_IMPLEMENTED(reg, index) ((reg) & (1 << (index))) + +/* USB_CAP4- bitmasks */ +#define EP_SUPPORT_ISO(reg, index) ((reg) & (1 << (index))) + +/* USB_CAP5- bitmasks */ +#define EP_SUPPORT_STREAM(reg, index) ((reg) & (1 << (index))) + +/* USB_CAP6- bitmasks */ +/* The USBSS-DEV Controller Internal build number. */ +#define GET_DEV_BASE_VERSION(p) ((p) & GENMASK(23, 0)) +/* The USBSS-DEV Controller version number. */ +#define GET_DEV_CUSTOM_VERSION(p) ((p) & GENMASK(31, 24)) + +#define DEV_VER_NXP_V1 0x00024502 +#define DEV_VER_TI_V1 0x00024509 +#define DEV_VER_V2 0x0002450C +#define DEV_VER_V3 0x0002450d + +/* DBG_LINK1- bitmasks */ +/* + * LFPS_MIN_DET_U1_EXIT value This parameter configures the minimum + * time required for decoding the received LFPS as an LFPS.U1_Exit. + */ +#define DBG_LINK1_LFPS_MIN_DET_U1_EXIT(p) ((p) & GENMASK(7, 0)) +/* + * LFPS_MIN_GEN_U1_EXIT value This parameter configures the minimum time for + * phytxelecidle deassertion when LFPS.U1_Exit + */ +#define DBG_LINK1_LFPS_MIN_GEN_U1_EXIT_MASK GENMASK(15, 8) +#define DBG_LINK1_LFPS_MIN_GEN_U1_EXIT(p) (((p) << 8) & GENMASK(15, 8)) +/* + * RXDET_BREAK_DIS value This parameter configures terminating the Far-end + * Receiver termination detection sequence: + * 0: it is possible that USBSS_DEV will terminate Farend receiver + * termination detection sequence + * 1: USBSS_DEV will not terminate Far-end receiver termination + * detection sequence + */ +#define DBG_LINK1_RXDET_BREAK_DIS BIT(16) +/* LFPS_GEN_PING value This parameter configures the LFPS.Ping generation */ +#define DBG_LINK1_LFPS_GEN_PING(p) (((p) << 17) & GENMASK(21, 17)) +/* + * Set the LFPS_MIN_DET_U1_EXIT value Writing '1' to this bit writes the + * LFPS_MIN_DET_U1_EXIT field value to the device. This bit is automatically + * cleared. Writing '0' has no effect + */ +#define DBG_LINK1_LFPS_MIN_DET_U1_EXIT_SET BIT(24) +/* + * Set the LFPS_MIN_GEN_U1_EXIT value. Writing '1' to this bit writes the + * LFPS_MIN_GEN_U1_EXIT field value to the device. This bit is automatically + * cleared. Writing '0' has no effect + */ +#define DBG_LINK1_LFPS_MIN_GEN_U1_EXIT_SET BIT(25) +/* + * Set the RXDET_BREAK_DIS value Writing '1' to this bit writes + * the RXDET_BREAK_DIS field value to the device. This bit is automatically + * cleared. Writing '0' has no effect + */ +#define DBG_LINK1_RXDET_BREAK_DIS_SET BIT(26) +/* + * Set the LFPS_GEN_PING_SET value Writing '1' to this bit writes + * the LFPS_GEN_PING field value to the device. This bit is automatically + * cleared. Writing '0' has no effect." + */ +#define DBG_LINK1_LFPS_GEN_PING_SET BIT(27) + +/* DMA_AXI_CTRL- bitmasks */ +/* The mawprot pin configuration. */ +#define DMA_AXI_CTRL_MARPROT(p) ((p) & GENMASK(2, 0)) +/* The marprot pin configuration. */ +#define DMA_AXI_CTRL_MAWPROT(p) (((p) & GENMASK(2, 0)) << 16) +#define DMA_AXI_CTRL_NON_SECURE 0x02 + +#define gadget_to_cdns3_device(g) (container_of(g, struct cdns3_device, gadget)) + +#define ep_to_cdns3_ep(ep) (container_of(ep, struct cdns3_endpoint, endpoint)) + +/*-------------------------------------------------------------------------*/ +/* + * USBSS-DEV DMA interface. + */ +#define TRBS_PER_SEGMENT 600 + +#define ISO_MAX_INTERVAL 10 + +#define MAX_TRB_LENGTH BIT(16) + +#if TRBS_PER_SEGMENT < 2 +#error "Incorrect TRBS_PER_SEGMENT. Minimal Transfer Ring size is 2." +#endif + +#define TRBS_PER_STREAM_SEGMENT 2 + +#if TRBS_PER_STREAM_SEGMENT < 2 +#error "Incorrect TRBS_PER_STREAMS_SEGMENT. Minimal Transfer Ring size is 2." +#endif + +/* + *Only for ISOC endpoints - maximum number of TRBs is calculated as + * pow(2, bInterval-1) * number of usb requests. It is limitation made by + * driver to save memory. Controller must prepare TRB for each ITP even + * if bInterval > 1. It's the reason why driver needs so many TRBs for + * isochronous endpoints. + */ +#define TRBS_PER_ISOC_SEGMENT (ISO_MAX_INTERVAL * 8) + +#define GET_TRBS_PER_SEGMENT(ep_type) ((ep_type) == USB_ENDPOINT_XFER_ISOC ? \ + TRBS_PER_ISOC_SEGMENT : TRBS_PER_SEGMENT) +/** + * struct cdns3_trb - represent Transfer Descriptor block. + * @buffer: pointer to buffer data + * @length: length of data + * @control: control flags. + * + * This structure describes transfer block serviced by DMA module. + */ +struct cdns3_trb { + __le32 buffer; + __le32 length; + __le32 control; +}; + +#define TRB_SIZE (sizeof(struct cdns3_trb)) +#define TRB_RING_SIZE (TRB_SIZE * TRBS_PER_SEGMENT) +#define TRB_STREAM_RING_SIZE (TRB_SIZE * TRBS_PER_STREAM_SEGMENT) +#define TRB_ISO_RING_SIZE (TRB_SIZE * TRBS_PER_ISOC_SEGMENT) +#define TRB_CTRL_RING_SIZE (TRB_SIZE * 2) + +/* TRB bit mask */ +#define TRB_TYPE_BITMASK GENMASK(15, 10) +#define TRB_TYPE(p) ((p) << 10) +#define TRB_FIELD_TO_TYPE(p) (((p) & TRB_TYPE_BITMASK) >> 10) + +/* TRB type IDs */ +/* bulk, interrupt, isoc , and control data stage */ +#define TRB_NORMAL 1 +/* TRB for linking ring segments */ +#define TRB_LINK 6 + +/* Cycle bit - indicates TRB ownership by driver or hw*/ +#define TRB_CYCLE BIT(0) +/* + * When set to '1', the device will toggle its interpretation of the Cycle bit + */ +#define TRB_TOGGLE BIT(1) +/* + * The controller will set it if OUTSMM (OUT size mismatch) is detected, + * this bit is for normal TRB + */ +#define TRB_SMM BIT(1) + +/* + * Short Packet (SP). OUT EPs at DMULT=1 only. Indicates if the TRB was + * processed while USB short packet was received. No more buffers defined by + * the TD will be used. DMA will automatically advance to next TD. + * - Shall be set to 0 by Software when putting TRB on the Transfer Ring + * - Shall be set to 1 by Controller when Short Packet condition for this TRB + * is detected independent if ISP is set or not. + */ +#define TRB_SP BIT(1) + +/* Interrupt on short packet*/ +#define TRB_ISP BIT(2) +/*Setting this bit enables FIFO DMA operation mode*/ +#define TRB_FIFO_MODE BIT(3) +/* Set PCIe no snoop attribute */ +#define TRB_CHAIN BIT(4) +/* Interrupt on completion */ +#define TRB_IOC BIT(5) + +/* stream ID bitmasks. */ +#define TRB_STREAM_ID_BITMASK GENMASK(31, 16) +#define TRB_STREAM_ID(p) ((p) << 16) +#define TRB_FIELD_TO_STREAMID(p) (((p) & TRB_STREAM_ID_BITMASK) >> 16) + +/* Size of TD expressed in USB packets for HS/FS mode. */ +#define TRB_TDL_HS_SIZE(p) (((p) << 16) & GENMASK(31, 16)) +#define TRB_TDL_HS_SIZE_GET(p) (((p) & GENMASK(31, 16)) >> 16) + +/* transfer_len bitmasks. */ +#define TRB_LEN(p) ((p) & GENMASK(16, 0)) + +/* Size of TD expressed in USB packets for SS mode. */ +#define TRB_TDL_SS_SIZE(p) (((p) << 17) & GENMASK(23, 17)) +#define TRB_TDL_SS_SIZE_GET(p) (((p) & GENMASK(23, 17)) >> 17) + +/* transfer_len bitmasks - bits 31:24 */ +#define TRB_BURST_LEN(p) ((unsigned int)((p) << 24) & GENMASK(31, 24)) +#define TRB_BURST_LEN_GET(p) (((p) & GENMASK(31, 24)) >> 24) + +/* Data buffer pointer bitmasks*/ +#define TRB_BUFFER(p) ((p) & GENMASK(31, 0)) + +/*-------------------------------------------------------------------------*/ +/* Driver numeric constants */ + +/* Such declaration should be added to ch9.h */ +#define USB_DEVICE_MAX_ADDRESS 127 + +/* Endpoint init values */ +#define CDNS3_EP_MAX_PACKET_LIMIT 1024 +#define CDNS3_EP_MAX_STREAMS 15 +#define CDNS3_EP0_MAX_PACKET_LIMIT 512 + +/* All endpoints including EP0 */ +#define CDNS3_ENDPOINTS_MAX_COUNT 32 +#define CDNS3_EP_ZLP_BUF_SIZE 1024 + +#define CDNS3_EP_BUF_SIZE 4 /* KB */ +#define CDNS3_EP_ISO_HS_MULT 3 +#define CDNS3_EP_ISO_SS_BURST 3 +#define CDNS3_MAX_NUM_DESCMISS_BUF 32 +#define CDNS3_DESCMIS_BUF_SIZE 2048 /* Bytes */ +#define CDNS3_WA2_NUM_BUFFERS 128 +/*-------------------------------------------------------------------------*/ +/* Used structs */ + +struct cdns3_device; + +/** + * struct cdns3_endpoint - extended device side representation of USB endpoint. + * @endpoint: usb endpoint + * @pending_req_list: list of requests queuing on transfer ring. + * @deferred_req_list: list of requests waiting for queuing on transfer ring. + * @wa2_descmiss_req_list: list of requests internally allocated by driver. + * @trb_pool: transfer ring - array of transaction buffers + * @trb_pool_dma: dma address of transfer ring + * @cdns3_dev: device associated with this endpoint + * @name: a human readable name e.g. ep1out + * @flags: specify the current state of endpoint + * @descmis_req: internal transfer object used for getting data from on-chip + * buffer. It can happen only if function driver doesn't send usb_request + * object on time. + * @dir: endpoint direction + * @num: endpoint number (1 - 15) + * @type: set to bmAttributes & USB_ENDPOINT_XFERTYPE_MASK + * @interval: interval between packets used for ISOC endpoint. + * @free_trbs: number of free TRBs in transfer ring + * @num_trbs: number of all TRBs in transfer ring + * @alloc_ring_size: size of the allocated TRB ring + * @pcs: producer cycle state + * @ccs: consumer cycle state + * @enqueue: enqueue index in transfer ring + * @dequeue: dequeue index in transfer ring + * @trb_burst_size: number of burst used in trb. + */ +struct cdns3_endpoint { + struct usb_ep endpoint; + struct list_head pending_req_list; + struct list_head deferred_req_list; + struct list_head wa2_descmiss_req_list; + int wa2_counter; + + struct cdns3_trb *trb_pool; + dma_addr_t trb_pool_dma; + + struct cdns3_device *cdns3_dev; + char name[20]; + +#define EP_ENABLED BIT(0) +#define EP_STALLED BIT(1) +#define EP_STALL_PENDING BIT(2) +#define EP_WEDGE BIT(3) +#define EP_TRANSFER_STARTED BIT(4) +#define EP_UPDATE_EP_TRBADDR BIT(5) +#define EP_PENDING_REQUEST BIT(6) +#define EP_RING_FULL BIT(7) +#define EP_CLAIMED BIT(8) +#define EP_DEFERRED_DRDY BIT(9) +#define EP_QUIRK_ISO_OUT_EN BIT(10) +#define EP_QUIRK_END_TRANSFER BIT(11) +#define EP_QUIRK_EXTRA_BUF_DET BIT(12) +#define EP_QUIRK_EXTRA_BUF_EN BIT(13) +#define EP_TDLCHK_EN BIT(15) +#define EP_CONFIGURED BIT(16) + u32 flags; + + struct cdns3_request *descmis_req; + + u8 dir; + u8 num; + u8 type; + int interval; + + int free_trbs; + int num_trbs; + int alloc_ring_size; + u8 pcs; + u8 ccs; + int enqueue; + int dequeue; + u8 trb_burst_size; + + unsigned int wa1_set:1; + struct cdns3_trb *wa1_trb; + unsigned int wa1_trb_index; + unsigned int wa1_cycle_bit:1; + + /* Stream related */ + unsigned int use_streams:1; + unsigned int prime_flag:1; + u32 ep_sts_pending; + u16 last_stream_id; + u16 pending_tdl; + unsigned int stream_sg_idx; +}; + +/** + * struct cdns3_aligned_buf - represent aligned buffer used for DMA transfer + * @buf: aligned to 8 bytes data buffer. Buffer address used in + * TRB shall be aligned to 8. + * @dma: dma address + * @size: size of buffer + * @in_use: inform if this buffer is associated with usb_request + * @list: used to adding instance of this object to list + */ +struct cdns3_aligned_buf { + void *buf; + dma_addr_t dma; + u32 size; + unsigned in_use:1; + struct list_head list; +}; + +/** + * struct cdns3_request - extended device side representation of usb_request + * object . + * @request: generic usb_request object describing single I/O request. + * @priv_ep: extended representation of usb_ep object + * @trb: the first TRB association with this request + * @start_trb: number of the first TRB in transfer ring + * @end_trb: number of the last TRB in transfer ring + * @aligned_buf: object holds information about aligned buffer associated whit + * this endpoint + * @flags: flag specifying special usage of request + * @list: used by internally allocated request to add to wa2_descmiss_req_list. + * @finished_trb: number of trb has already finished per request + * @num_of_trb: how many trbs in this request + */ +struct cdns3_request { + struct usb_request request; + struct cdns3_endpoint *priv_ep; + struct cdns3_trb *trb; + int start_trb; + int end_trb; + struct cdns3_aligned_buf *aligned_buf; +#define REQUEST_PENDING BIT(0) +#define REQUEST_INTERNAL BIT(1) +#define REQUEST_INTERNAL_CH BIT(2) +#define REQUEST_ZLP BIT(3) +#define REQUEST_UNALIGNED BIT(4) + u32 flags; + struct list_head list; + int finished_trb; + int num_of_trb; +}; + +#define to_cdns3_request(r) (container_of(r, struct cdns3_request, request)) + +/*Stages used during enumeration process.*/ +#define CDNS3_SETUP_STAGE 0x0 +#define CDNS3_DATA_STAGE 0x1 +#define CDNS3_STATUS_STAGE 0x2 + +/** + * struct cdns3_device - represent USB device. + * @dev: pointer to device structure associated whit this controller + * @sysdev: pointer to the DMA capable device + * @gadget: device side representation of the peripheral controller + * @gadget_driver: pointer to the gadget driver + * @dev_ver: device controller version. + * @lock: for synchronizing + * @regs: base address for device side registers + * @setup_buf: used while processing usb control requests + * @setup_dma: dma address for setup_buf + * @zlp_buf - zlp buffer + * @ep0_stage: ep0 stage during enumeration process. + * @ep0_data_dir: direction for control transfer + * @eps: array of pointers to all endpoints with exclusion ep0 + * @aligned_buf_list: list of aligned buffers internally allocated by driver + * @aligned_buf_wq: workqueue freeing no longer used aligned buf. + * @selected_ep: actually selected endpoint. It's used only to improve + * performance. + * @isoch_delay: value from Set Isoch Delay request. Only valid on SS/SSP. + * @u1_allowed: allow device transition to u1 state + * @u2_allowed: allow device transition to u2 state + * @is_selfpowered: device is self powered + * @setup_pending: setup packet is processing by gadget driver + * @hw_configured_flag: hardware endpoint configuration was set. + * @wake_up_flag: allow device to remote up the host + * @status_completion_no_call: indicate that driver is waiting for status s + * stage completion. It's used in deferred SET_CONFIGURATION request. + * @onchip_buffers: number of available on-chip buffers. + * @onchip_used_size: actual size of on-chip memory assigned to endpoints. + * @pending_status_wq: workqueue handling status stage for deferred requests. + * @pending_status_request: request for which status stage was deferred + */ +struct cdns3_device { + struct device *dev; + struct device *sysdev; + + struct usb_gadget gadget; + struct usb_gadget_driver *gadget_driver; + +#define CDNS_REVISION_V0 0x00024501 +#define CDNS_REVISION_V1 0x00024509 + u32 dev_ver; + + /* generic spin-lock for drivers */ + spinlock_t lock; + + struct cdns3_usb_regs __iomem *regs; + + struct usb_ctrlrequest *setup_buf; + dma_addr_t setup_dma; + void *zlp_buf; + + u8 ep0_stage; + int ep0_data_dir; + + struct cdns3_endpoint *eps[CDNS3_ENDPOINTS_MAX_COUNT]; + + struct list_head aligned_buf_list; + struct work_struct aligned_buf_wq; + + u32 selected_ep; + u16 isoch_delay; + + unsigned wait_for_setup:1; + unsigned u1_allowed:1; + unsigned u2_allowed:1; + unsigned is_selfpowered:1; + unsigned setup_pending:1; + unsigned hw_configured_flag:1; + unsigned wake_up_flag:1; + unsigned status_completion_no_call:1; + unsigned using_streams:1; + int out_mem_is_allocated; + + struct work_struct pending_status_wq; + struct usb_request *pending_status_request; + + /*in KB */ + u16 onchip_buffers; + u16 onchip_used_size; +}; + +void cdns3_set_register_bit(void __iomem *ptr, u32 mask); +dma_addr_t cdns3_trb_virt_to_dma(struct cdns3_endpoint *priv_ep, + struct cdns3_trb *trb); +enum usb_device_speed cdns3_get_speed(struct cdns3_device *priv_dev); +void cdns3_pending_setup_status_handler(struct work_struct *work); +void cdns3_hw_reset_eps_config(struct cdns3_device *priv_dev); +void cdns3_set_hw_configuration(struct cdns3_device *priv_dev); +void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep); +void cdns3_allow_enable_l1(struct cdns3_device *priv_dev, int enable); +struct usb_request *cdns3_next_request(struct list_head *list); +void cdns3_rearm_transfer(struct cdns3_endpoint *priv_ep, u8 rearm); +int cdns3_allocate_trb_pool(struct cdns3_endpoint *priv_ep); +u8 cdns3_ep_addr_to_index(u8 ep_addr); +int cdns3_gadget_ep_set_wedge(struct usb_ep *ep); +int cdns3_gadget_ep_set_halt(struct usb_ep *ep, int value); +void __cdns3_gadget_ep_set_halt(struct cdns3_endpoint *priv_ep); +int __cdns3_gadget_ep_clear_halt(struct cdns3_endpoint *priv_ep); +struct usb_request *cdns3_gadget_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags); +void cdns3_gadget_ep_free_request(struct usb_ep *ep, + struct usb_request *request); +int cdns3_gadget_ep_dequeue(struct usb_ep *ep, struct usb_request *request); +void cdns3_gadget_giveback(struct cdns3_endpoint *priv_ep, + struct cdns3_request *priv_req, + int status); + +int cdns3_init_ep0(struct cdns3_device *priv_dev, + struct cdns3_endpoint *priv_ep); +void cdns3_ep0_config(struct cdns3_device *priv_dev); +int cdns3_ep_config(struct cdns3_endpoint *priv_ep, bool enable); +void cdns3_check_ep0_interrupt_proceed(struct cdns3_device *priv_dev, int dir); +int __cdns3_gadget_wakeup(struct cdns3_device *priv_dev); + +#endif /* __LINUX_CDNS3_GADGET */ diff --git a/drivers/usb/cdns3/cdns3-trace.c b/drivers/usb/cdns3/cdns3-trace.c new file mode 100644 index 000000000000..b9858acaef02 --- /dev/null +++ b/drivers/usb/cdns3/cdns3-trace.c @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USBSS device controller driver Trace Support + * + * Copyright (C) 2018-2019 Cadence. + * + * Author: Pawel Laszczak + */ + +#define CREATE_TRACE_POINTS +#include "cdns3-trace.h" diff --git a/drivers/usb/cdns3/cdns3-trace.h b/drivers/usb/cdns3/cdns3-trace.h new file mode 100644 index 000000000000..8648c7a7a9dd --- /dev/null +++ b/drivers/usb/cdns3/cdns3-trace.h @@ -0,0 +1,570 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * USBSS device controller driver. + * Trace support header file. + * + * Copyright (C) 2018-2019 Cadence. + * + * Author: Pawel Laszczak + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM cdns3 + +#if !defined(__LINUX_CDNS3_TRACE) || defined(TRACE_HEADER_MULTI_READ) +#define __LINUX_CDNS3_TRACE + +#include +#include +#include +#include +#include "core.h" +#include "cdns3-gadget.h" +#include "cdns3-debug.h" + +#define CDNS3_MSG_MAX 500 + +TRACE_EVENT(cdns3_halt, + TP_PROTO(struct cdns3_endpoint *ep_priv, u8 halt, u8 flush), + TP_ARGS(ep_priv, halt, flush), + TP_STRUCT__entry( + __string(name, ep_priv->name) + __field(u8, halt) + __field(u8, flush) + ), + TP_fast_assign( + __assign_str(name, ep_priv->name); + __entry->halt = halt; + __entry->flush = flush; + ), + TP_printk("Halt %s for %s: %s", __entry->flush ? " and flush" : "", + __get_str(name), __entry->halt ? "set" : "cleared") +); + +TRACE_EVENT(cdns3_wa1, + TP_PROTO(struct cdns3_endpoint *ep_priv, char *msg), + TP_ARGS(ep_priv, msg), + TP_STRUCT__entry( + __string(ep_name, ep_priv->name) + __string(msg, msg) + ), + TP_fast_assign( + __assign_str(ep_name, ep_priv->name); + __assign_str(msg, msg); + ), + TP_printk("WA1: %s %s", __get_str(ep_name), __get_str(msg)) +); + +TRACE_EVENT(cdns3_wa2, + TP_PROTO(struct cdns3_endpoint *ep_priv, char *msg), + TP_ARGS(ep_priv, msg), + TP_STRUCT__entry( + __string(ep_name, ep_priv->name) + __string(msg, msg) + ), + TP_fast_assign( + __assign_str(ep_name, ep_priv->name); + __assign_str(msg, msg); + ), + TP_printk("WA2: %s %s", __get_str(ep_name), __get_str(msg)) +); + +DECLARE_EVENT_CLASS(cdns3_log_doorbell, + TP_PROTO(const char *ep_name, u32 ep_trbaddr), + TP_ARGS(ep_name, ep_trbaddr), + TP_STRUCT__entry( + __string(name, ep_name) + __field(u32, ep_trbaddr) + ), + TP_fast_assign( + __assign_str(name, ep_name); + __entry->ep_trbaddr = ep_trbaddr; + ), + TP_printk("%s, ep_trbaddr %08x", __get_str(name), + __entry->ep_trbaddr) +); + +DEFINE_EVENT(cdns3_log_doorbell, cdns3_doorbell_ep0, + TP_PROTO(const char *ep_name, u32 ep_trbaddr), + TP_ARGS(ep_name, ep_trbaddr) +); + +DEFINE_EVENT(cdns3_log_doorbell, cdns3_doorbell_epx, + TP_PROTO(const char *ep_name, u32 ep_trbaddr), + TP_ARGS(ep_name, ep_trbaddr) +); + +DECLARE_EVENT_CLASS(cdns3_log_usb_irq, + TP_PROTO(struct cdns3_device *priv_dev, u32 usb_ists), + TP_ARGS(priv_dev, usb_ists), + TP_STRUCT__entry( + __field(enum usb_device_speed, speed) + __field(u32, usb_ists) + __dynamic_array(char, str, CDNS3_MSG_MAX) + ), + TP_fast_assign( + __entry->speed = cdns3_get_speed(priv_dev); + __entry->usb_ists = usb_ists; + ), + TP_printk("%s", cdns3_decode_usb_irq(__get_str(str), __entry->speed, + __entry->usb_ists)) +); + +DEFINE_EVENT(cdns3_log_usb_irq, cdns3_usb_irq, + TP_PROTO(struct cdns3_device *priv_dev, u32 usb_ists), + TP_ARGS(priv_dev, usb_ists) +); + +DECLARE_EVENT_CLASS(cdns3_log_epx_irq, + TP_PROTO(struct cdns3_device *priv_dev, struct cdns3_endpoint *priv_ep), + TP_ARGS(priv_dev, priv_ep), + TP_STRUCT__entry( + __string(ep_name, priv_ep->name) + __field(u32, ep_sts) + __field(u32, ep_traddr) + __field(u32, ep_last_sid) + __field(u32, use_streams) + __dynamic_array(char, str, CDNS3_MSG_MAX) + ), + TP_fast_assign( + __assign_str(ep_name, priv_ep->name); + __entry->ep_sts = readl(&priv_dev->regs->ep_sts); + __entry->ep_traddr = readl(&priv_dev->regs->ep_traddr); + __entry->ep_last_sid = priv_ep->last_stream_id; + __entry->use_streams = priv_ep->use_streams; + ), + TP_printk("%s, ep_traddr: %08x ep_last_sid: %08x use_streams: %d", + cdns3_decode_epx_irq(__get_str(str), + __get_str(ep_name), + __entry->ep_sts), + __entry->ep_traddr, + __entry->ep_last_sid, + __entry->use_streams) +); + +DEFINE_EVENT(cdns3_log_epx_irq, cdns3_epx_irq, + TP_PROTO(struct cdns3_device *priv_dev, struct cdns3_endpoint *priv_ep), + TP_ARGS(priv_dev, priv_ep) +); + +DECLARE_EVENT_CLASS(cdns3_log_ep0_irq, + TP_PROTO(struct cdns3_device *priv_dev, u32 ep_sts), + TP_ARGS(priv_dev, ep_sts), + TP_STRUCT__entry( + __field(int, ep_dir) + __field(u32, ep_sts) + __dynamic_array(char, str, CDNS3_MSG_MAX) + ), + TP_fast_assign( + __entry->ep_dir = priv_dev->selected_ep; + __entry->ep_sts = ep_sts; + ), + TP_printk("%s", cdns3_decode_ep0_irq(__get_str(str), + __entry->ep_dir, + __entry->ep_sts)) +); + +DEFINE_EVENT(cdns3_log_ep0_irq, cdns3_ep0_irq, + TP_PROTO(struct cdns3_device *priv_dev, u32 ep_sts), + TP_ARGS(priv_dev, ep_sts) +); + +DECLARE_EVENT_CLASS(cdns3_log_ctrl, + TP_PROTO(struct usb_ctrlrequest *ctrl), + TP_ARGS(ctrl), + TP_STRUCT__entry( + __field(u8, bRequestType) + __field(u8, bRequest) + __field(u16, wValue) + __field(u16, wIndex) + __field(u16, wLength) + __dynamic_array(char, str, CDNS3_MSG_MAX) + ), + TP_fast_assign( + __entry->bRequestType = ctrl->bRequestType; + __entry->bRequest = ctrl->bRequest; + __entry->wValue = le16_to_cpu(ctrl->wValue); + __entry->wIndex = le16_to_cpu(ctrl->wIndex); + __entry->wLength = le16_to_cpu(ctrl->wLength); + ), + TP_printk("%s", usb_decode_ctrl(__get_str(str), CDNS3_MSG_MAX, + __entry->bRequestType, + __entry->bRequest, __entry->wValue, + __entry->wIndex, __entry->wLength) + ) +); + +DEFINE_EVENT(cdns3_log_ctrl, cdns3_ctrl_req, + TP_PROTO(struct usb_ctrlrequest *ctrl), + TP_ARGS(ctrl) +); + +DECLARE_EVENT_CLASS(cdns3_log_request, + TP_PROTO(struct cdns3_request *req), + TP_ARGS(req), + TP_STRUCT__entry( + __string(name, req->priv_ep->name) + __field(struct cdns3_request *, req) + __field(void *, buf) + __field(unsigned int, actual) + __field(unsigned int, length) + __field(int, status) + __field(int, zero) + __field(int, short_not_ok) + __field(int, no_interrupt) + __field(int, start_trb) + __field(int, end_trb) + __field(struct cdns3_trb *, start_trb_addr) + __field(int, flags) + __field(unsigned int, stream_id) + ), + TP_fast_assign( + __assign_str(name, req->priv_ep->name); + __entry->req = req; + __entry->buf = req->request.buf; + __entry->actual = req->request.actual; + __entry->length = req->request.length; + __entry->status = req->request.status; + __entry->zero = req->request.zero; + __entry->short_not_ok = req->request.short_not_ok; + __entry->no_interrupt = req->request.no_interrupt; + __entry->start_trb = req->start_trb; + __entry->end_trb = req->end_trb; + __entry->start_trb_addr = req->trb; + __entry->flags = req->flags; + __entry->stream_id = req->request.stream_id; + ), + TP_printk("%s: req: %p, req buff %p, length: %u/%u %s%s%s, status: %d," + " trb: [start:%d, end:%d: virt addr %pa], flags:%x SID: %u", + __get_str(name), __entry->req, __entry->buf, __entry->actual, + __entry->length, + __entry->zero ? "Z" : "z", + __entry->short_not_ok ? "S" : "s", + __entry->no_interrupt ? "I" : "i", + __entry->status, + __entry->start_trb, + __entry->end_trb, + __entry->start_trb_addr, + __entry->flags, + __entry->stream_id + ) +); + +DEFINE_EVENT(cdns3_log_request, cdns3_alloc_request, + TP_PROTO(struct cdns3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdns3_log_request, cdns3_free_request, + TP_PROTO(struct cdns3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdns3_log_request, cdns3_ep_queue, + TP_PROTO(struct cdns3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdns3_log_request, cdns3_ep_dequeue, + TP_PROTO(struct cdns3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdns3_log_request, cdns3_gadget_giveback, + TP_PROTO(struct cdns3_request *req), + TP_ARGS(req) +); + +TRACE_EVENT(cdns3_ep0_queue, + TP_PROTO(struct cdns3_device *dev_priv, struct usb_request *request), + TP_ARGS(dev_priv, request), + TP_STRUCT__entry( + __field(int, dir) + __field(int, length) + ), + TP_fast_assign( + __entry->dir = dev_priv->ep0_data_dir; + __entry->length = request->length; + ), + TP_printk("Queue to ep0%s length: %u", __entry->dir ? "in" : "out", + __entry->length) +); + +DECLARE_EVENT_CLASS(cdns3_stream_split_transfer_len, + TP_PROTO(struct cdns3_request *req), + TP_ARGS(req), + TP_STRUCT__entry( + __string(name, req->priv_ep->name) + __field(struct cdns3_request *, req) + __field(unsigned int, length) + __field(unsigned int, actual) + __field(unsigned int, stream_id) + ), + TP_fast_assign( + __assign_str(name, req->priv_ep->name); + __entry->req = req; + __entry->actual = req->request.length; + __entry->length = req->request.actual; + __entry->stream_id = req->request.stream_id; + ), + TP_printk("%s: req: %p,request length: %u actual length: %u SID: %u", + __get_str(name), __entry->req, __entry->length, + __entry->actual, __entry->stream_id) +); + +DEFINE_EVENT(cdns3_stream_split_transfer_len, cdns3_stream_transfer_split, + TP_PROTO(struct cdns3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdns3_stream_split_transfer_len, + cdns3_stream_transfer_split_next_part, + TP_PROTO(struct cdns3_request *req), + TP_ARGS(req) +); + +DECLARE_EVENT_CLASS(cdns3_log_aligned_request, + TP_PROTO(struct cdns3_request *priv_req), + TP_ARGS(priv_req), + TP_STRUCT__entry( + __string(name, priv_req->priv_ep->name) + __field(struct usb_request *, req) + __field(void *, buf) + __field(dma_addr_t, dma) + __field(void *, aligned_buf) + __field(dma_addr_t, aligned_dma) + __field(u32, aligned_buf_size) + ), + TP_fast_assign( + __assign_str(name, priv_req->priv_ep->name); + __entry->req = &priv_req->request; + __entry->buf = priv_req->request.buf; + __entry->dma = priv_req->request.dma; + __entry->aligned_buf = priv_req->aligned_buf->buf; + __entry->aligned_dma = priv_req->aligned_buf->dma; + __entry->aligned_buf_size = priv_req->aligned_buf->size; + ), + TP_printk("%s: req: %p, req buf %p, dma %pad a_buf %p a_dma %pad, size %d", + __get_str(name), __entry->req, __entry->buf, &__entry->dma, + __entry->aligned_buf, &__entry->aligned_dma, + __entry->aligned_buf_size + ) +); + +DEFINE_EVENT(cdns3_log_aligned_request, cdns3_free_aligned_request, + TP_PROTO(struct cdns3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdns3_log_aligned_request, cdns3_prepare_aligned_request, + TP_PROTO(struct cdns3_request *req), + TP_ARGS(req) +); + +DECLARE_EVENT_CLASS(cdns3_log_map_request, + TP_PROTO(struct cdns3_request *priv_req), + TP_ARGS(priv_req), + TP_STRUCT__entry( + __string(name, priv_req->priv_ep->name) + __field(struct usb_request *, req) + __field(void *, buf) + __field(dma_addr_t, dma) + ), + TP_fast_assign( + __assign_str(name, priv_req->priv_ep->name); + __entry->req = &priv_req->request; + __entry->buf = priv_req->request.buf; + __entry->dma = priv_req->request.dma; + ), + TP_printk("%s: req: %p, req buf %p, dma %p", + __get_str(name), __entry->req, __entry->buf, &__entry->dma + ) +); +DEFINE_EVENT(cdns3_log_map_request, cdns3_map_request, + TP_PROTO(struct cdns3_request *req), + TP_ARGS(req) +); +DEFINE_EVENT(cdns3_log_map_request, cdns3_mapped_request, + TP_PROTO(struct cdns3_request *req), + TP_ARGS(req) +); + +DECLARE_EVENT_CLASS(cdns3_log_trb, + TP_PROTO(struct cdns3_endpoint *priv_ep, struct cdns3_trb *trb), + TP_ARGS(priv_ep, trb), + TP_STRUCT__entry( + __string(name, priv_ep->name) + __field(struct cdns3_trb *, trb) + __field(u32, buffer) + __field(u32, length) + __field(u32, control) + __field(u32, type) + __field(unsigned int, last_stream_id) + ), + TP_fast_assign( + __assign_str(name, priv_ep->name); + __entry->trb = trb; + __entry->buffer = le32_to_cpu(trb->buffer); + __entry->length = le32_to_cpu(trb->length); + __entry->control = le32_to_cpu(trb->control); + __entry->type = usb_endpoint_type(priv_ep->endpoint.desc); + __entry->last_stream_id = priv_ep->last_stream_id; + ), + TP_printk("%s: trb %p, dma buf: 0x%08x, size: %ld, burst: %d ctrl: 0x%08x (%s%s%s%s%s%s%s) SID:%lu LAST_SID:%u", + __get_str(name), __entry->trb, __entry->buffer, + TRB_LEN(__entry->length), + (u8)TRB_BURST_LEN_GET(__entry->length), + __entry->control, + __entry->control & TRB_CYCLE ? "C=1, " : "C=0, ", + __entry->control & TRB_TOGGLE ? "T=1, " : "T=0, ", + __entry->control & TRB_ISP ? "ISP, " : "", + __entry->control & TRB_FIFO_MODE ? "FIFO, " : "", + __entry->control & TRB_CHAIN ? "CHAIN, " : "", + __entry->control & TRB_IOC ? "IOC, " : "", + TRB_FIELD_TO_TYPE(__entry->control) == TRB_NORMAL ? "Normal" : "LINK", + TRB_FIELD_TO_STREAMID(__entry->control), + __entry->last_stream_id + ) +); + +DEFINE_EVENT(cdns3_log_trb, cdns3_prepare_trb, + TP_PROTO(struct cdns3_endpoint *priv_ep, struct cdns3_trb *trb), + TP_ARGS(priv_ep, trb) +); + +DEFINE_EVENT(cdns3_log_trb, cdns3_complete_trb, + TP_PROTO(struct cdns3_endpoint *priv_ep, struct cdns3_trb *trb), + TP_ARGS(priv_ep, trb) +); + +DECLARE_EVENT_CLASS(cdns3_log_ring, + TP_PROTO(struct cdns3_endpoint *priv_ep), + TP_ARGS(priv_ep), + TP_STRUCT__entry( + __dynamic_array(u8, ring, TRB_RING_SIZE) + __dynamic_array(u8, priv_ep, sizeof(struct cdns3_endpoint)) + __dynamic_array(char, buffer, + (TRBS_PER_SEGMENT * 65) + CDNS3_MSG_MAX) + ), + TP_fast_assign( + memcpy(__get_dynamic_array(priv_ep), priv_ep, + sizeof(struct cdns3_endpoint)); + memcpy(__get_dynamic_array(ring), priv_ep->trb_pool, + TRB_RING_SIZE); + ), + + TP_printk("%s", + cdns3_dbg_ring((struct cdns3_endpoint *)__get_str(priv_ep), + (struct cdns3_trb *)__get_str(ring), + __get_str(buffer))) +); + +DEFINE_EVENT(cdns3_log_ring, cdns3_ring, + TP_PROTO(struct cdns3_endpoint *priv_ep), + TP_ARGS(priv_ep) +); + +DECLARE_EVENT_CLASS(cdns3_log_ep, + TP_PROTO(struct cdns3_endpoint *priv_ep), + TP_ARGS(priv_ep), + TP_STRUCT__entry( + __string(name, priv_ep->name) + __field(unsigned int, maxpacket) + __field(unsigned int, maxpacket_limit) + __field(unsigned int, max_streams) + __field(unsigned int, use_streams) + __field(unsigned int, maxburst) + __field(unsigned int, flags) + __field(unsigned int, dir) + __field(u8, enqueue) + __field(u8, dequeue) + ), + TP_fast_assign( + __assign_str(name, priv_ep->name); + __entry->maxpacket = priv_ep->endpoint.maxpacket; + __entry->maxpacket_limit = priv_ep->endpoint.maxpacket_limit; + __entry->max_streams = priv_ep->endpoint.max_streams; + __entry->use_streams = priv_ep->use_streams; + __entry->maxburst = priv_ep->endpoint.maxburst; + __entry->flags = priv_ep->flags; + __entry->dir = priv_ep->dir; + __entry->enqueue = priv_ep->enqueue; + __entry->dequeue = priv_ep->dequeue; + ), + TP_printk("%s: mps: %d/%d. streams: %d, stream enable: %d, burst: %d, " + "enq idx: %d, deq idx: %d, flags %s%s%s%s%s%s%s%s, dir: %s", + __get_str(name), __entry->maxpacket, + __entry->maxpacket_limit, __entry->max_streams, + __entry->use_streams, + __entry->maxburst, __entry->enqueue, + __entry->dequeue, + __entry->flags & EP_ENABLED ? "EN | " : "", + __entry->flags & EP_STALLED ? "STALLED | " : "", + __entry->flags & EP_WEDGE ? "WEDGE | " : "", + __entry->flags & EP_TRANSFER_STARTED ? "STARTED | " : "", + __entry->flags & EP_UPDATE_EP_TRBADDR ? "UPD TRB | " : "", + __entry->flags & EP_PENDING_REQUEST ? "REQ PEN | " : "", + __entry->flags & EP_RING_FULL ? "RING FULL |" : "", + __entry->flags & EP_CLAIMED ? "CLAIMED " : "", + __entry->dir ? "IN" : "OUT" + ) +); + +DEFINE_EVENT(cdns3_log_ep, cdns3_gadget_ep_enable, + TP_PROTO(struct cdns3_endpoint *priv_ep), + TP_ARGS(priv_ep) +); + +DEFINE_EVENT(cdns3_log_ep, cdns3_gadget_ep_disable, + TP_PROTO(struct cdns3_endpoint *priv_ep), + TP_ARGS(priv_ep) +); + +DECLARE_EVENT_CLASS(cdns3_log_request_handled, + TP_PROTO(struct cdns3_request *priv_req, int current_index, + int handled), + TP_ARGS(priv_req, current_index, handled), + TP_STRUCT__entry( + __field(struct cdns3_request *, priv_req) + __field(unsigned int, dma_position) + __field(unsigned int, handled) + __field(unsigned int, dequeue_idx) + __field(unsigned int, enqueue_idx) + __field(unsigned int, start_trb) + __field(unsigned int, end_trb) + ), + TP_fast_assign( + __entry->priv_req = priv_req; + __entry->dma_position = current_index; + __entry->handled = handled; + __entry->dequeue_idx = priv_req->priv_ep->dequeue; + __entry->enqueue_idx = priv_req->priv_ep->enqueue; + __entry->start_trb = priv_req->start_trb; + __entry->end_trb = priv_req->end_trb; + ), + TP_printk("Req: %p %s, DMA pos: %d, ep deq: %d, ep enq: %d," + " start trb: %d, end trb: %d", + __entry->priv_req, + __entry->handled ? "handled" : "not handled", + __entry->dma_position, __entry->dequeue_idx, + __entry->enqueue_idx, __entry->start_trb, + __entry->end_trb + ) +); + +DEFINE_EVENT(cdns3_log_request_handled, cdns3_request_handled, + TP_PROTO(struct cdns3_request *priv_req, int current_index, + int handled), + TP_ARGS(priv_req, current_index, handled) +); +#endif /* __LINUX_CDNS3_TRACE */ + +/* this part must be outside header guard */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE cdns3-trace + +#include diff --git a/drivers/usb/cdns3/debug.h b/drivers/usb/cdns3/debug.h deleted file mode 100644 index a5c6a29e1340..000000000000 --- a/drivers/usb/cdns3/debug.h +++ /dev/null @@ -1,161 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Cadence USBSS DRD Driver. - * Debug header file. - * - * Copyright (C) 2018-2019 Cadence. - * - * Author: Pawel Laszczak - */ -#ifndef __LINUX_CDNS3_DEBUG -#define __LINUX_CDNS3_DEBUG - -#include "core.h" - -static inline char *cdns3_decode_usb_irq(char *str, - enum usb_device_speed speed, - u32 usb_ists) -{ - int ret; - - ret = sprintf(str, "IRQ %08x = ", usb_ists); - - if (usb_ists & (USB_ISTS_CON2I | USB_ISTS_CONI)) { - ret += sprintf(str + ret, "Connection %s\n", - usb_speed_string(speed)); - } - if (usb_ists & USB_ISTS_DIS2I || usb_ists & USB_ISTS_DISI) - ret += sprintf(str + ret, "Disconnection "); - if (usb_ists & USB_ISTS_L2ENTI) - ret += sprintf(str + ret, "suspended "); - if (usb_ists & USB_ISTS_L1ENTI) - ret += sprintf(str + ret, "L1 enter "); - if (usb_ists & USB_ISTS_L1EXTI) - ret += sprintf(str + ret, "L1 exit "); - if (usb_ists & USB_ISTS_L2ENTI) - ret += sprintf(str + ret, "L2 enter "); - if (usb_ists & USB_ISTS_L2EXTI) - ret += sprintf(str + ret, "L2 exit "); - if (usb_ists & USB_ISTS_U3EXTI) - ret += sprintf(str + ret, "U3 exit "); - if (usb_ists & USB_ISTS_UWRESI) - ret += sprintf(str + ret, "Warm Reset "); - if (usb_ists & USB_ISTS_UHRESI) - ret += sprintf(str + ret, "Hot Reset "); - if (usb_ists & USB_ISTS_U2RESI) - ret += sprintf(str + ret, "Reset"); - - return str; -} - -static inline char *cdns3_decode_ep_irq(char *str, - u32 ep_sts, - const char *ep_name) -{ - int ret; - - ret = sprintf(str, "IRQ for %s: %08x ", ep_name, ep_sts); - - if (ep_sts & EP_STS_SETUP) - ret += sprintf(str + ret, "SETUP "); - if (ep_sts & EP_STS_IOC) - ret += sprintf(str + ret, "IOC "); - if (ep_sts & EP_STS_ISP) - ret += sprintf(str + ret, "ISP "); - if (ep_sts & EP_STS_DESCMIS) - ret += sprintf(str + ret, "DESCMIS "); - if (ep_sts & EP_STS_STREAMR) - ret += sprintf(str + ret, "STREAMR "); - if (ep_sts & EP_STS_MD_EXIT) - ret += sprintf(str + ret, "MD_EXIT "); - if (ep_sts & EP_STS_TRBERR) - ret += sprintf(str + ret, "TRBERR "); - if (ep_sts & EP_STS_NRDY) - ret += sprintf(str + ret, "NRDY "); - if (ep_sts & EP_STS_PRIME) - ret += sprintf(str + ret, "PRIME "); - if (ep_sts & EP_STS_SIDERR) - ret += sprintf(str + ret, "SIDERRT "); - if (ep_sts & EP_STS_OUTSMM) - ret += sprintf(str + ret, "OUTSMM "); - if (ep_sts & EP_STS_ISOERR) - ret += sprintf(str + ret, "ISOERR "); - if (ep_sts & EP_STS_IOT) - ret += sprintf(str + ret, "IOT "); - - return str; -} - -static inline char *cdns3_decode_epx_irq(char *str, - char *ep_name, - u32 ep_sts) -{ - return cdns3_decode_ep_irq(str, ep_sts, ep_name); -} - -static inline char *cdns3_decode_ep0_irq(char *str, - int dir, - u32 ep_sts) -{ - return cdns3_decode_ep_irq(str, ep_sts, - dir ? "ep0IN" : "ep0OUT"); -} - -/** - * Debug a transfer ring. - * - * Prints out all TRBs in the endpoint ring, even those after the Link TRB. - *. - */ -static inline char *cdns3_dbg_ring(struct cdns3_endpoint *priv_ep, - struct cdns3_trb *ring, char *str) -{ - dma_addr_t addr = priv_ep->trb_pool_dma; - struct cdns3_trb *trb; - int trb_per_sector; - int ret = 0; - int i; - - trb_per_sector = GET_TRBS_PER_SEGMENT(priv_ep->type); - - trb = &priv_ep->trb_pool[priv_ep->dequeue]; - ret += sprintf(str + ret, "\n\t\tRing contents for %s:", priv_ep->name); - - ret += sprintf(str + ret, - "\n\t\tRing deq index: %d, trb: %p (virt), 0x%llx (dma)\n", - priv_ep->dequeue, trb, - (unsigned long long)cdns3_trb_virt_to_dma(priv_ep, trb)); - - trb = &priv_ep->trb_pool[priv_ep->enqueue]; - ret += sprintf(str + ret, - "\t\tRing enq index: %d, trb: %p (virt), 0x%llx (dma)\n", - priv_ep->enqueue, trb, - (unsigned long long)cdns3_trb_virt_to_dma(priv_ep, trb)); - - ret += sprintf(str + ret, - "\t\tfree trbs: %d, CCS=%d, PCS=%d\n", - priv_ep->free_trbs, priv_ep->ccs, priv_ep->pcs); - - if (trb_per_sector > TRBS_PER_SEGMENT) - trb_per_sector = TRBS_PER_SEGMENT; - - if (trb_per_sector > TRBS_PER_SEGMENT) { - sprintf(str + ret, "\t\tTransfer ring %d too big\n", - trb_per_sector); - return str; - } - - for (i = 0; i < trb_per_sector; ++i) { - trb = &ring[i]; - ret += sprintf(str + ret, - "\t\t@%pad %08x %08x %08x\n", &addr, - le32_to_cpu(trb->buffer), - le32_to_cpu(trb->length), - le32_to_cpu(trb->control)); - addr += sizeof(*trb); - } - - return str; -} - -#endif /*__LINUX_CDNS3_DEBUG*/ diff --git a/drivers/usb/cdns3/ep0.c b/drivers/usb/cdns3/ep0.c deleted file mode 100644 index d3121a32cc68..000000000000 --- a/drivers/usb/cdns3/ep0.c +++ /dev/null @@ -1,894 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Cadence USBSS DRD Driver - gadget side. - * - * Copyright (C) 2018 Cadence Design Systems. - * Copyright (C) 2017-2018 NXP - * - * Authors: Pawel Jez , - * Pawel Laszczak - * Peter Chen - */ - -#include -#include - -#include "gadget.h" -#include "trace.h" - -static struct usb_endpoint_descriptor cdns3_gadget_ep0_desc = { - .bLength = USB_DT_ENDPOINT_SIZE, - .bDescriptorType = USB_DT_ENDPOINT, - .bmAttributes = USB_ENDPOINT_XFER_CONTROL, -}; - -/** - * cdns3_ep0_run_transfer - Do transfer on default endpoint hardware - * @priv_dev: extended gadget object - * @dma_addr: physical address where data is/will be stored - * @length: data length - * @erdy: set it to 1 when ERDY packet should be sent - - * exit from flow control state - * @zlp: add zero length packet - */ -static void cdns3_ep0_run_transfer(struct cdns3_device *priv_dev, - dma_addr_t dma_addr, - unsigned int length, int erdy, int zlp) -{ - struct cdns3_usb_regs __iomem *regs = priv_dev->regs; - struct cdns3_endpoint *priv_ep = priv_dev->eps[0]; - - priv_ep->trb_pool[0].buffer = cpu_to_le32(TRB_BUFFER(dma_addr)); - priv_ep->trb_pool[0].length = cpu_to_le32(TRB_LEN(length)); - - if (zlp) { - priv_ep->trb_pool[0].control = cpu_to_le32(TRB_CYCLE | TRB_TYPE(TRB_NORMAL)); - priv_ep->trb_pool[1].buffer = cpu_to_le32(TRB_BUFFER(dma_addr)); - priv_ep->trb_pool[1].length = cpu_to_le32(TRB_LEN(0)); - priv_ep->trb_pool[1].control = cpu_to_le32(TRB_CYCLE | TRB_IOC | - TRB_TYPE(TRB_NORMAL)); - } else { - priv_ep->trb_pool[0].control = cpu_to_le32(TRB_CYCLE | TRB_IOC | - TRB_TYPE(TRB_NORMAL)); - priv_ep->trb_pool[1].control = 0; - } - - trace_cdns3_prepare_trb(priv_ep, priv_ep->trb_pool); - - cdns3_select_ep(priv_dev, priv_dev->ep0_data_dir); - - writel(EP_STS_TRBERR, ®s->ep_sts); - writel(EP_TRADDR_TRADDR(priv_ep->trb_pool_dma), ®s->ep_traddr); - trace_cdns3_doorbell_ep0(priv_dev->ep0_data_dir ? "ep0in" : "ep0out", - readl(®s->ep_traddr)); - - /* TRB should be prepared before starting transfer. */ - writel(EP_CMD_DRDY, ®s->ep_cmd); - - /* Resume controller before arming transfer. */ - __cdns3_gadget_wakeup(priv_dev); - - if (erdy) - writel(EP_CMD_ERDY, &priv_dev->regs->ep_cmd); -} - -/** - * cdns3_ep0_delegate_req - Returns status of handling setup packet - * Setup is handled by gadget driver - * @priv_dev: extended gadget object - * @ctrl_req: pointer to received setup packet - * - * Returns zero on success or negative value on failure - */ -static int cdns3_ep0_delegate_req(struct cdns3_device *priv_dev, - struct usb_ctrlrequest *ctrl_req) -{ - int ret; - - spin_unlock(&priv_dev->lock); - priv_dev->setup_pending = 1; - ret = priv_dev->gadget_driver->setup(&priv_dev->gadget, ctrl_req); - priv_dev->setup_pending = 0; - spin_lock(&priv_dev->lock); - return ret; -} - -static void cdns3_prepare_setup_packet(struct cdns3_device *priv_dev) -{ - priv_dev->ep0_data_dir = 0; - priv_dev->ep0_stage = CDNS3_SETUP_STAGE; - cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma, - sizeof(struct usb_ctrlrequest), 0, 0); -} - -static void cdns3_ep0_complete_setup(struct cdns3_device *priv_dev, - u8 send_stall, u8 send_erdy) -{ - struct cdns3_endpoint *priv_ep = priv_dev->eps[0]; - struct usb_request *request; - - request = cdns3_next_request(&priv_ep->pending_req_list); - if (request) - list_del_init(&request->list); - - if (send_stall) { - trace_cdns3_halt(priv_ep, send_stall, 0); - /* set_stall on ep0 */ - cdns3_select_ep(priv_dev, 0x00); - writel(EP_CMD_SSTALL, &priv_dev->regs->ep_cmd); - } else { - cdns3_prepare_setup_packet(priv_dev); - } - - priv_dev->ep0_stage = CDNS3_SETUP_STAGE; - writel((send_erdy ? EP_CMD_ERDY : 0) | EP_CMD_REQ_CMPL, - &priv_dev->regs->ep_cmd); -} - -/** - * cdns3_req_ep0_set_configuration - Handling of SET_CONFIG standard USB request - * @priv_dev: extended gadget object - * @ctrl_req: pointer to received setup packet - * - * Returns 0 if success, USB_GADGET_DELAYED_STATUS on deferred status stage, - * error code on error - */ -static int cdns3_req_ep0_set_configuration(struct cdns3_device *priv_dev, - struct usb_ctrlrequest *ctrl_req) -{ - enum usb_device_state device_state = priv_dev->gadget.state; - u32 config = le16_to_cpu(ctrl_req->wValue); - int result = 0; - - switch (device_state) { - case USB_STATE_ADDRESS: - result = cdns3_ep0_delegate_req(priv_dev, ctrl_req); - - if (result || !config) - goto reset_config; - - break; - case USB_STATE_CONFIGURED: - result = cdns3_ep0_delegate_req(priv_dev, ctrl_req); - if (!config && !result) - goto reset_config; - - break; - default: - return -EINVAL; - } - - return 0; - -reset_config: - if (result != USB_GADGET_DELAYED_STATUS) - cdns3_hw_reset_eps_config(priv_dev); - - usb_gadget_set_state(&priv_dev->gadget, - USB_STATE_ADDRESS); - - return result; -} - -/** - * cdns3_req_ep0_set_address - Handling of SET_ADDRESS standard USB request - * @priv_dev: extended gadget object - * @ctrl_req: pointer to received setup packet - * - * Returns 0 if success, error code on error - */ -static int cdns3_req_ep0_set_address(struct cdns3_device *priv_dev, - struct usb_ctrlrequest *ctrl_req) -{ - enum usb_device_state device_state = priv_dev->gadget.state; - u32 reg; - u32 addr; - - addr = le16_to_cpu(ctrl_req->wValue); - - if (addr > USB_DEVICE_MAX_ADDRESS) { - dev_err(priv_dev->dev, - "Device address (%d) cannot be greater than %d\n", - addr, USB_DEVICE_MAX_ADDRESS); - return -EINVAL; - } - - if (device_state == USB_STATE_CONFIGURED) { - dev_err(priv_dev->dev, - "can't set_address from configured state\n"); - return -EINVAL; - } - - reg = readl(&priv_dev->regs->usb_cmd); - - writel(reg | USB_CMD_FADDR(addr) | USB_CMD_SET_ADDR, - &priv_dev->regs->usb_cmd); - - usb_gadget_set_state(&priv_dev->gadget, - (addr ? USB_STATE_ADDRESS : USB_STATE_DEFAULT)); - - return 0; -} - -/** - * cdns3_req_ep0_get_status - Handling of GET_STATUS standard USB request - * @priv_dev: extended gadget object - * @ctrl: pointer to received setup packet - * - * Returns 0 if success, error code on error - */ -static int cdns3_req_ep0_get_status(struct cdns3_device *priv_dev, - struct usb_ctrlrequest *ctrl) -{ - struct cdns3_endpoint *priv_ep; - __le16 *response_pkt; - u16 usb_status = 0; - u32 recip; - u8 index; - - recip = ctrl->bRequestType & USB_RECIP_MASK; - - switch (recip) { - case USB_RECIP_DEVICE: - /* self powered */ - if (priv_dev->is_selfpowered) - usb_status = BIT(USB_DEVICE_SELF_POWERED); - - if (priv_dev->wake_up_flag) - usb_status |= BIT(USB_DEVICE_REMOTE_WAKEUP); - - if (priv_dev->gadget.speed != USB_SPEED_SUPER) - break; - - if (priv_dev->u1_allowed) - usb_status |= BIT(USB_DEV_STAT_U1_ENABLED); - - if (priv_dev->u2_allowed) - usb_status |= BIT(USB_DEV_STAT_U2_ENABLED); - - break; - case USB_RECIP_INTERFACE: - return cdns3_ep0_delegate_req(priv_dev, ctrl); - case USB_RECIP_ENDPOINT: - index = cdns3_ep_addr_to_index(le16_to_cpu(ctrl->wIndex)); - priv_ep = priv_dev->eps[index]; - - /* check if endpoint is stalled or stall is pending */ - cdns3_select_ep(priv_dev, le16_to_cpu(ctrl->wIndex)); - if (EP_STS_STALL(readl(&priv_dev->regs->ep_sts)) || - (priv_ep->flags & EP_STALL_PENDING)) - usb_status = BIT(USB_ENDPOINT_HALT); - break; - default: - return -EINVAL; - } - - response_pkt = (__le16 *)priv_dev->setup_buf; - *response_pkt = cpu_to_le16(usb_status); - - cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma, - sizeof(*response_pkt), 1, 0); - return 0; -} - -static int cdns3_ep0_feature_handle_device(struct cdns3_device *priv_dev, - struct usb_ctrlrequest *ctrl, - int set) -{ - enum usb_device_state state; - enum usb_device_speed speed; - int ret = 0; - u32 wValue; - u16 tmode; - - wValue = le16_to_cpu(ctrl->wValue); - state = priv_dev->gadget.state; - speed = priv_dev->gadget.speed; - - switch (wValue) { - case USB_DEVICE_REMOTE_WAKEUP: - priv_dev->wake_up_flag = !!set; - break; - case USB_DEVICE_U1_ENABLE: - if (state != USB_STATE_CONFIGURED || speed != USB_SPEED_SUPER) - return -EINVAL; - - priv_dev->u1_allowed = !!set; - break; - case USB_DEVICE_U2_ENABLE: - if (state != USB_STATE_CONFIGURED || speed != USB_SPEED_SUPER) - return -EINVAL; - - priv_dev->u2_allowed = !!set; - break; - case USB_DEVICE_LTM_ENABLE: - ret = -EINVAL; - break; - case USB_DEVICE_TEST_MODE: - if (state != USB_STATE_CONFIGURED || speed > USB_SPEED_HIGH) - return -EINVAL; - - tmode = le16_to_cpu(ctrl->wIndex); - - if (!set || (tmode & 0xff) != 0) - return -EINVAL; - - tmode >>= 8; - switch (tmode) { - case USB_TEST_J: - case USB_TEST_K: - case USB_TEST_SE0_NAK: - case USB_TEST_PACKET: - cdns3_set_register_bit(&priv_dev->regs->usb_cmd, - USB_CMD_STMODE | - USB_STS_TMODE_SEL(tmode - 1)); - break; - default: - ret = -EINVAL; - } - break; - default: - ret = -EINVAL; - } - - return ret; -} - -static int cdns3_ep0_feature_handle_intf(struct cdns3_device *priv_dev, - struct usb_ctrlrequest *ctrl, - int set) -{ - u32 wValue; - int ret = 0; - - wValue = le16_to_cpu(ctrl->wValue); - - switch (wValue) { - case USB_INTRF_FUNC_SUSPEND: - break; - default: - ret = -EINVAL; - } - - return ret; -} - -static int cdns3_ep0_feature_handle_endpoint(struct cdns3_device *priv_dev, - struct usb_ctrlrequest *ctrl, - int set) -{ - struct cdns3_endpoint *priv_ep; - int ret = 0; - u8 index; - - if (le16_to_cpu(ctrl->wValue) != USB_ENDPOINT_HALT) - return -EINVAL; - - if (!(ctrl->wIndex & ~USB_DIR_IN)) - return 0; - - index = cdns3_ep_addr_to_index(le16_to_cpu(ctrl->wIndex)); - priv_ep = priv_dev->eps[index]; - - cdns3_select_ep(priv_dev, le16_to_cpu(ctrl->wIndex)); - - if (set) - __cdns3_gadget_ep_set_halt(priv_ep); - else if (!(priv_ep->flags & EP_WEDGE)) - ret = __cdns3_gadget_ep_clear_halt(priv_ep); - - cdns3_select_ep(priv_dev, 0x00); - - return ret; -} - -/** - * cdns3_req_ep0_handle_feature - - * Handling of GET/SET_FEATURE standard USB request - * - * @priv_dev: extended gadget object - * @ctrl: pointer to received setup packet - * @set: must be set to 1 for SET_FEATURE request - * - * Returns 0 if success, error code on error - */ -static int cdns3_req_ep0_handle_feature(struct cdns3_device *priv_dev, - struct usb_ctrlrequest *ctrl, - int set) -{ - int ret = 0; - u32 recip; - - recip = ctrl->bRequestType & USB_RECIP_MASK; - - switch (recip) { - case USB_RECIP_DEVICE: - ret = cdns3_ep0_feature_handle_device(priv_dev, ctrl, set); - break; - case USB_RECIP_INTERFACE: - ret = cdns3_ep0_feature_handle_intf(priv_dev, ctrl, set); - break; - case USB_RECIP_ENDPOINT: - ret = cdns3_ep0_feature_handle_endpoint(priv_dev, ctrl, set); - break; - default: - return -EINVAL; - } - - return ret; -} - -/** - * cdns3_req_ep0_set_sel - Handling of SET_SEL standard USB request - * @priv_dev: extended gadget object - * @ctrl_req: pointer to received setup packet - * - * Returns 0 if success, error code on error - */ -static int cdns3_req_ep0_set_sel(struct cdns3_device *priv_dev, - struct usb_ctrlrequest *ctrl_req) -{ - if (priv_dev->gadget.state < USB_STATE_ADDRESS) - return -EINVAL; - - if (le16_to_cpu(ctrl_req->wLength) != 6) { - dev_err(priv_dev->dev, "Set SEL should be 6 bytes, got %d\n", - ctrl_req->wLength); - return -EINVAL; - } - - cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma, 6, 1, 0); - return 0; -} - -/** - * cdns3_req_ep0_set_isoch_delay - - * Handling of GET_ISOCH_DELAY standard USB request - * @priv_dev: extended gadget object - * @ctrl_req: pointer to received setup packet - * - * Returns 0 if success, error code on error - */ -static int cdns3_req_ep0_set_isoch_delay(struct cdns3_device *priv_dev, - struct usb_ctrlrequest *ctrl_req) -{ - if (ctrl_req->wIndex || ctrl_req->wLength) - return -EINVAL; - - priv_dev->isoch_delay = le16_to_cpu(ctrl_req->wValue); - - return 0; -} - -/** - * cdns3_ep0_standard_request - Handling standard USB requests - * @priv_dev: extended gadget object - * @ctrl_req: pointer to received setup packet - * - * Returns 0 if success, error code on error - */ -static int cdns3_ep0_standard_request(struct cdns3_device *priv_dev, - struct usb_ctrlrequest *ctrl_req) -{ - int ret; - - switch (ctrl_req->bRequest) { - case USB_REQ_SET_ADDRESS: - ret = cdns3_req_ep0_set_address(priv_dev, ctrl_req); - break; - case USB_REQ_SET_CONFIGURATION: - ret = cdns3_req_ep0_set_configuration(priv_dev, ctrl_req); - break; - case USB_REQ_GET_STATUS: - ret = cdns3_req_ep0_get_status(priv_dev, ctrl_req); - break; - case USB_REQ_CLEAR_FEATURE: - ret = cdns3_req_ep0_handle_feature(priv_dev, ctrl_req, 0); - break; - case USB_REQ_SET_FEATURE: - ret = cdns3_req_ep0_handle_feature(priv_dev, ctrl_req, 1); - break; - case USB_REQ_SET_SEL: - ret = cdns3_req_ep0_set_sel(priv_dev, ctrl_req); - break; - case USB_REQ_SET_ISOCH_DELAY: - ret = cdns3_req_ep0_set_isoch_delay(priv_dev, ctrl_req); - break; - default: - ret = cdns3_ep0_delegate_req(priv_dev, ctrl_req); - break; - } - - return ret; -} - -static void __pending_setup_status_handler(struct cdns3_device *priv_dev) -{ - struct usb_request *request = priv_dev->pending_status_request; - - if (priv_dev->status_completion_no_call && request && - request->complete) { - request->complete(&priv_dev->eps[0]->endpoint, request); - priv_dev->status_completion_no_call = 0; - } -} - -void cdns3_pending_setup_status_handler(struct work_struct *work) -{ - struct cdns3_device *priv_dev = container_of(work, struct cdns3_device, - pending_status_wq); - unsigned long flags; - - spin_lock_irqsave(&priv_dev->lock, flags); - __pending_setup_status_handler(priv_dev); - spin_unlock_irqrestore(&priv_dev->lock, flags); -} - -/** - * cdns3_ep0_setup_phase - Handling setup USB requests - * @priv_dev: extended gadget object - */ -static void cdns3_ep0_setup_phase(struct cdns3_device *priv_dev) -{ - struct usb_ctrlrequest *ctrl = priv_dev->setup_buf; - struct cdns3_endpoint *priv_ep = priv_dev->eps[0]; - int result; - - priv_dev->ep0_data_dir = ctrl->bRequestType & USB_DIR_IN; - - trace_cdns3_ctrl_req(ctrl); - - if (!list_empty(&priv_ep->pending_req_list)) { - struct usb_request *request; - - request = cdns3_next_request(&priv_ep->pending_req_list); - priv_ep->dir = priv_dev->ep0_data_dir; - cdns3_gadget_giveback(priv_ep, to_cdns3_request(request), - -ECONNRESET); - } - - if (le16_to_cpu(ctrl->wLength)) - priv_dev->ep0_stage = CDNS3_DATA_STAGE; - else - priv_dev->ep0_stage = CDNS3_STATUS_STAGE; - - if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) - result = cdns3_ep0_standard_request(priv_dev, ctrl); - else - result = cdns3_ep0_delegate_req(priv_dev, ctrl); - - if (result == USB_GADGET_DELAYED_STATUS) - return; - - if (result < 0) - cdns3_ep0_complete_setup(priv_dev, 1, 1); - else if (priv_dev->ep0_stage == CDNS3_STATUS_STAGE) - cdns3_ep0_complete_setup(priv_dev, 0, 1); -} - -static void cdns3_transfer_completed(struct cdns3_device *priv_dev) -{ - struct cdns3_endpoint *priv_ep = priv_dev->eps[0]; - - if (!list_empty(&priv_ep->pending_req_list)) { - struct usb_request *request; - - trace_cdns3_complete_trb(priv_ep, priv_ep->trb_pool); - request = cdns3_next_request(&priv_ep->pending_req_list); - - request->actual = - TRB_LEN(le32_to_cpu(priv_ep->trb_pool->length)); - - priv_ep->dir = priv_dev->ep0_data_dir; - cdns3_gadget_giveback(priv_ep, to_cdns3_request(request), 0); - } - - cdns3_ep0_complete_setup(priv_dev, 0, 0); -} - -/** - * cdns3_check_new_setup - Check if controller receive new SETUP packet. - * @priv_dev: extended gadget object - * - * The SETUP packet can be kept in on-chip memory or in system memory. - */ -static bool cdns3_check_new_setup(struct cdns3_device *priv_dev) -{ - u32 ep_sts_reg; - - cdns3_select_ep(priv_dev, USB_DIR_OUT); - ep_sts_reg = readl(&priv_dev->regs->ep_sts); - - return !!(ep_sts_reg & (EP_STS_SETUP | EP_STS_STPWAIT)); -} - -/** - * cdns3_check_ep0_interrupt_proceed - Processes interrupt related to endpoint 0 - * @priv_dev: extended gadget object - * @dir: USB_DIR_IN for IN direction, USB_DIR_OUT for OUT direction - */ -void cdns3_check_ep0_interrupt_proceed(struct cdns3_device *priv_dev, int dir) -{ - u32 ep_sts_reg; - - cdns3_select_ep(priv_dev, dir); - - ep_sts_reg = readl(&priv_dev->regs->ep_sts); - writel(ep_sts_reg, &priv_dev->regs->ep_sts); - - trace_cdns3_ep0_irq(priv_dev, ep_sts_reg); - - __pending_setup_status_handler(priv_dev); - - if (ep_sts_reg & EP_STS_SETUP) - priv_dev->wait_for_setup = 1; - - if (priv_dev->wait_for_setup && ep_sts_reg & EP_STS_IOC) { - priv_dev->wait_for_setup = 0; - cdns3_ep0_setup_phase(priv_dev); - } else if ((ep_sts_reg & EP_STS_IOC) || (ep_sts_reg & EP_STS_ISP)) { - priv_dev->ep0_data_dir = dir; - cdns3_transfer_completed(priv_dev); - } - - if (ep_sts_reg & EP_STS_DESCMIS) { - if (dir == 0 && !priv_dev->setup_pending) - cdns3_prepare_setup_packet(priv_dev); - } -} - -/** - * cdns3_gadget_ep0_enable - * @ep: pointer to endpoint zero object - * @desc: pointer to usb endpoint descriptor - * - * Function shouldn't be called by gadget driver, - * endpoint 0 is allways active - */ -static int cdns3_gadget_ep0_enable(struct usb_ep *ep, - const struct usb_endpoint_descriptor *desc) -{ - return -EINVAL; -} - -/** - * cdns3_gadget_ep0_disable - * @ep: pointer to endpoint zero object - * - * Function shouldn't be called by gadget driver, - * endpoint 0 is allways active - */ -static int cdns3_gadget_ep0_disable(struct usb_ep *ep) -{ - return -EINVAL; -} - -/** - * cdns3_gadget_ep0_set_halt - * @ep: pointer to endpoint zero object - * @value: 1 for set stall, 0 for clear stall - * - * Returns 0 - */ -static int cdns3_gadget_ep0_set_halt(struct usb_ep *ep, int value) -{ - /* TODO */ - return 0; -} - -/** - * cdns3_gadget_ep0_queue Transfer data on endpoint zero - * @ep: pointer to endpoint zero object - * @request: pointer to request object - * @gfp_flags: gfp flags - * - * Returns 0 on success, error code elsewhere - */ -static int cdns3_gadget_ep0_queue(struct usb_ep *ep, - struct usb_request *request, - gfp_t gfp_flags) -{ - struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep); - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - unsigned long flags; - int ret = 0; - u8 zlp = 0; - int i; - - spin_lock_irqsave(&priv_dev->lock, flags); - trace_cdns3_ep0_queue(priv_dev, request); - - /* cancel the request if controller receive new SETUP packet. */ - if (cdns3_check_new_setup(priv_dev)) { - spin_unlock_irqrestore(&priv_dev->lock, flags); - return -ECONNRESET; - } - - /* send STATUS stage. Should be called only for SET_CONFIGURATION */ - if (priv_dev->ep0_stage == CDNS3_STATUS_STAGE) { - u32 val; - - cdns3_select_ep(priv_dev, 0x00); - - /* - * Configure all non-control EPs which are not enabled by class driver - */ - for (i = 0; i < CDNS3_ENDPOINTS_MAX_COUNT; i++) { - priv_ep = priv_dev->eps[i]; - if (priv_ep && priv_ep->flags & EP_CLAIMED && - !(priv_ep->flags & EP_ENABLED)) - cdns3_ep_config(priv_ep, 0); - } - - cdns3_set_hw_configuration(priv_dev); - cdns3_ep0_complete_setup(priv_dev, 0, 1); - /* wait until configuration set */ - ret = readl_poll_timeout_atomic(&priv_dev->regs->usb_sts, val, - val & USB_STS_CFGSTS_MASK, 1, 100); - if (ret == -ETIMEDOUT) - dev_warn(priv_dev->dev, "timeout for waiting configuration set\n"); - - request->actual = 0; - priv_dev->status_completion_no_call = true; - priv_dev->pending_status_request = request; - spin_unlock_irqrestore(&priv_dev->lock, flags); - - /* - * Since there is no completion interrupt for status stage, - * it needs to call ->completion in software after - * ep0_queue is back. - */ - queue_work(system_freezable_wq, &priv_dev->pending_status_wq); - return ret; - } - - if (!list_empty(&priv_ep->pending_req_list)) { - dev_err(priv_dev->dev, - "can't handle multiple requests for ep0\n"); - spin_unlock_irqrestore(&priv_dev->lock, flags); - return -EBUSY; - } - - ret = usb_gadget_map_request_by_dev(priv_dev->sysdev, request, - priv_dev->ep0_data_dir); - if (ret) { - spin_unlock_irqrestore(&priv_dev->lock, flags); - dev_err(priv_dev->dev, "failed to map request\n"); - return -EINVAL; - } - - request->status = -EINPROGRESS; - list_add_tail(&request->list, &priv_ep->pending_req_list); - - if (request->zero && request->length && - (request->length % ep->maxpacket == 0)) - zlp = 1; - - cdns3_ep0_run_transfer(priv_dev, request->dma, request->length, 1, zlp); - - spin_unlock_irqrestore(&priv_dev->lock, flags); - - return ret; -} - -/** - * cdns3_gadget_ep_set_wedge Set wedge on selected endpoint - * @ep: endpoint object - * - * Returns 0 - */ -int cdns3_gadget_ep_set_wedge(struct usb_ep *ep) -{ - struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep); - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - - dev_dbg(priv_dev->dev, "Wedge for %s\n", ep->name); - cdns3_gadget_ep_set_halt(ep, 1); - priv_ep->flags |= EP_WEDGE; - - return 0; -} - -const struct usb_ep_ops cdns3_gadget_ep0_ops = { - .enable = cdns3_gadget_ep0_enable, - .disable = cdns3_gadget_ep0_disable, - .alloc_request = cdns3_gadget_ep_alloc_request, - .free_request = cdns3_gadget_ep_free_request, - .queue = cdns3_gadget_ep0_queue, - .dequeue = cdns3_gadget_ep_dequeue, - .set_halt = cdns3_gadget_ep0_set_halt, - .set_wedge = cdns3_gadget_ep_set_wedge, -}; - -/** - * cdns3_ep0_config - Configures default endpoint - * @priv_dev: extended gadget object - * - * Functions sets parameters: maximal packet size and enables interrupts - */ -void cdns3_ep0_config(struct cdns3_device *priv_dev) -{ - struct cdns3_usb_regs __iomem *regs; - struct cdns3_endpoint *priv_ep; - u32 max_packet_size = 64; - u32 ep_cfg; - - regs = priv_dev->regs; - - if (priv_dev->gadget.speed == USB_SPEED_SUPER) - max_packet_size = 512; - - priv_ep = priv_dev->eps[0]; - - if (!list_empty(&priv_ep->pending_req_list)) { - struct usb_request *request; - - request = cdns3_next_request(&priv_ep->pending_req_list); - list_del_init(&request->list); - } - - priv_dev->u1_allowed = 0; - priv_dev->u2_allowed = 0; - - priv_dev->gadget.ep0->maxpacket = max_packet_size; - cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(max_packet_size); - - /* init ep out */ - cdns3_select_ep(priv_dev, USB_DIR_OUT); - - if (priv_dev->dev_ver >= DEV_VER_V3) { - cdns3_set_register_bit(&priv_dev->regs->dtrans, - BIT(0) | BIT(16)); - cdns3_set_register_bit(&priv_dev->regs->tdl_from_trb, - BIT(0) | BIT(16)); - } - - ep_cfg = EP_CFG_ENABLE | EP_CFG_MAXPKTSIZE(max_packet_size); - - if (!(priv_ep->flags & EP_CONFIGURED)) - writel(ep_cfg, ®s->ep_cfg); - - writel(EP_STS_EN_SETUPEN | EP_STS_EN_DESCMISEN | EP_STS_EN_TRBERREN, - ®s->ep_sts_en); - - /* init ep in */ - cdns3_select_ep(priv_dev, USB_DIR_IN); - - if (!(priv_ep->flags & EP_CONFIGURED)) - writel(ep_cfg, ®s->ep_cfg); - - priv_ep->flags |= EP_CONFIGURED; - - writel(EP_STS_EN_SETUPEN | EP_STS_EN_TRBERREN, ®s->ep_sts_en); - - cdns3_set_register_bit(®s->usb_conf, USB_CONF_U1DS | USB_CONF_U2DS); -} - -/** - * cdns3_init_ep0 Initializes software endpoint 0 of gadget - * @priv_dev: extended gadget object - * @priv_ep: extended endpoint object - * - * Returns 0 on success else error code. - */ -int cdns3_init_ep0(struct cdns3_device *priv_dev, - struct cdns3_endpoint *priv_ep) -{ - sprintf(priv_ep->name, "ep0"); - - /* fill linux fields */ - priv_ep->endpoint.ops = &cdns3_gadget_ep0_ops; - priv_ep->endpoint.maxburst = 1; - usb_ep_set_maxpacket_limit(&priv_ep->endpoint, - CDNS3_EP0_MAX_PACKET_LIMIT); - priv_ep->endpoint.address = 0; - priv_ep->endpoint.caps.type_control = 1; - priv_ep->endpoint.caps.dir_in = 1; - priv_ep->endpoint.caps.dir_out = 1; - priv_ep->endpoint.name = priv_ep->name; - priv_ep->endpoint.desc = &cdns3_gadget_ep0_desc; - priv_dev->gadget.ep0 = &priv_ep->endpoint; - priv_ep->type = USB_ENDPOINT_XFER_CONTROL; - - return cdns3_allocate_trb_pool(priv_ep); -} diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c deleted file mode 100644 index 5890e535aefc..000000000000 --- a/drivers/usb/cdns3/gadget.c +++ /dev/null @@ -1,3335 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Cadence USBSS DRD Driver - gadget side. - * - * Copyright (C) 2018-2019 Cadence Design Systems. - * Copyright (C) 2017-2018 NXP - * - * Authors: Pawel Jez , - * Pawel Laszczak - * Peter Chen - */ - -/* - * Work around 1: - * At some situations, the controller may get stale data address in TRB - * at below sequences: - * 1. Controller read TRB includes data address - * 2. Software updates TRBs includes data address and Cycle bit - * 3. Controller read TRB which includes Cycle bit - * 4. DMA run with stale data address - * - * To fix this problem, driver needs to make the first TRB in TD as invalid. - * After preparing all TRBs driver needs to check the position of DMA and - * if the DMA point to the first just added TRB and doorbell is 1, - * then driver must defer making this TRB as valid. This TRB will be make - * as valid during adding next TRB only if DMA is stopped or at TRBERR - * interrupt. - * - * Issue has been fixed in DEV_VER_V3 version of controller. - * - * Work around 2: - * Controller for OUT endpoints has shared on-chip buffers for all incoming - * packets, including ep0out. It's FIFO buffer, so packets must be handle by DMA - * in correct order. If the first packet in the buffer will not be handled, - * then the following packets directed for other endpoints and functions - * will be blocked. - * Additionally the packets directed to one endpoint can block entire on-chip - * buffers. In this case transfer to other endpoints also will blocked. - * - * To resolve this issue after raising the descriptor missing interrupt - * driver prepares internal usb_request object and use it to arm DMA transfer. - * - * The problematic situation was observed in case when endpoint has been enabled - * but no usb_request were queued. Driver try detects such endpoints and will - * use this workaround only for these endpoint. - * - * Driver use limited number of buffer. This number can be set by macro - * CDNS3_WA2_NUM_BUFFERS. - * - * Such blocking situation was observed on ACM gadget. For this function - * host send OUT data packet but ACM function is not prepared for this packet. - * It's cause that buffer placed in on chip memory block transfer to other - * endpoints. - * - * Issue has been fixed in DEV_VER_V2 version of controller. - * - */ - -#include -#include -#include -#include - -#include "core.h" -#include "gadget-export.h" -#include "gadget.h" -#include "trace.h" -#include "drd.h" - -static int __cdns3_gadget_ep_queue(struct usb_ep *ep, - struct usb_request *request, - gfp_t gfp_flags); - -static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, - struct usb_request *request); - -static int cdns3_ep_run_stream_transfer(struct cdns3_endpoint *priv_ep, - struct usb_request *request); - -/** - * cdns3_clear_register_bit - clear bit in given register. - * @ptr: address of device controller register to be read and changed - * @mask: bits requested to clar - */ -static void cdns3_clear_register_bit(void __iomem *ptr, u32 mask) -{ - mask = readl(ptr) & ~mask; - writel(mask, ptr); -} - -/** - * cdns3_set_register_bit - set bit in given register. - * @ptr: address of device controller register to be read and changed - * @mask: bits requested to set - */ -void cdns3_set_register_bit(void __iomem *ptr, u32 mask) -{ - mask = readl(ptr) | mask; - writel(mask, ptr); -} - -/** - * cdns3_ep_addr_to_index - Macro converts endpoint address to - * index of endpoint object in cdns3_device.eps[] container - * @ep_addr: endpoint address for which endpoint object is required - * - */ -u8 cdns3_ep_addr_to_index(u8 ep_addr) -{ - return (((ep_addr & 0x7F)) + ((ep_addr & USB_DIR_IN) ? 16 : 0)); -} - -static int cdns3_get_dma_pos(struct cdns3_device *priv_dev, - struct cdns3_endpoint *priv_ep) -{ - int dma_index; - - dma_index = readl(&priv_dev->regs->ep_traddr) - priv_ep->trb_pool_dma; - - return dma_index / TRB_SIZE; -} - -/** - * cdns3_next_request - returns next request from list - * @list: list containing requests - * - * Returns request or NULL if no requests in list - */ -struct usb_request *cdns3_next_request(struct list_head *list) -{ - return list_first_entry_or_null(list, struct usb_request, list); -} - -/** - * cdns3_next_align_buf - returns next buffer from list - * @list: list containing buffers - * - * Returns buffer or NULL if no buffers in list - */ -static struct cdns3_aligned_buf *cdns3_next_align_buf(struct list_head *list) -{ - return list_first_entry_or_null(list, struct cdns3_aligned_buf, list); -} - -/** - * cdns3_next_priv_request - returns next request from list - * @list: list containing requests - * - * Returns request or NULL if no requests in list - */ -static struct cdns3_request *cdns3_next_priv_request(struct list_head *list) -{ - return list_first_entry_or_null(list, struct cdns3_request, list); -} - -/** - * select_ep - selects endpoint - * @priv_dev: extended gadget object - * @ep: endpoint address - */ -void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep) -{ - if (priv_dev->selected_ep == ep) - return; - - priv_dev->selected_ep = ep; - writel(ep, &priv_dev->regs->ep_sel); -} - -/** - * cdns3_get_tdl - gets current tdl for selected endpoint. - * @priv_dev: extended gadget object - * - * Before calling this function the appropriate endpoint must - * be selected by means of cdns3_select_ep function. - */ -static int cdns3_get_tdl(struct cdns3_device *priv_dev) -{ - if (priv_dev->dev_ver < DEV_VER_V3) - return EP_CMD_TDL_GET(readl(&priv_dev->regs->ep_cmd)); - else - return readl(&priv_dev->regs->ep_tdl); -} - -dma_addr_t cdns3_trb_virt_to_dma(struct cdns3_endpoint *priv_ep, - struct cdns3_trb *trb) -{ - u32 offset = (char *)trb - (char *)priv_ep->trb_pool; - - return priv_ep->trb_pool_dma + offset; -} - -static int cdns3_ring_size(struct cdns3_endpoint *priv_ep) -{ - switch (priv_ep->type) { - case USB_ENDPOINT_XFER_ISOC: - return TRB_ISO_RING_SIZE; - case USB_ENDPOINT_XFER_CONTROL: - return TRB_CTRL_RING_SIZE; - default: - if (priv_ep->use_streams) - return TRB_STREAM_RING_SIZE; - else - return TRB_RING_SIZE; - } -} - -static void cdns3_free_trb_pool(struct cdns3_endpoint *priv_ep) -{ - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - - if (priv_ep->trb_pool) { - dma_free_coherent(priv_dev->sysdev, - cdns3_ring_size(priv_ep), - priv_ep->trb_pool, priv_ep->trb_pool_dma); - priv_ep->trb_pool = NULL; - } -} - -/** - * cdns3_allocate_trb_pool - Allocates TRB's pool for selected endpoint - * @priv_ep: endpoint object - * - * Function will return 0 on success or -ENOMEM on allocation error - */ -int cdns3_allocate_trb_pool(struct cdns3_endpoint *priv_ep) -{ - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - int ring_size = cdns3_ring_size(priv_ep); - int num_trbs = ring_size / TRB_SIZE; - struct cdns3_trb *link_trb; - - if (priv_ep->trb_pool && priv_ep->alloc_ring_size < ring_size) - cdns3_free_trb_pool(priv_ep); - - if (!priv_ep->trb_pool) { - priv_ep->trb_pool = dma_alloc_coherent(priv_dev->sysdev, - ring_size, - &priv_ep->trb_pool_dma, - GFP_DMA32 | GFP_ATOMIC); - if (!priv_ep->trb_pool) - return -ENOMEM; - - priv_ep->alloc_ring_size = ring_size; - } - - memset(priv_ep->trb_pool, 0, ring_size); - - priv_ep->num_trbs = num_trbs; - - if (!priv_ep->num) - return 0; - - /* Initialize the last TRB as Link TRB */ - link_trb = (priv_ep->trb_pool + (priv_ep->num_trbs - 1)); - - if (priv_ep->use_streams) { - /* - * For stream capable endpoints driver use single correct TRB. - * The last trb has zeroed cycle bit - */ - link_trb->control = 0; - } else { - link_trb->buffer = cpu_to_le32(TRB_BUFFER(priv_ep->trb_pool_dma)); - link_trb->control = cpu_to_le32(TRB_CYCLE | TRB_TYPE(TRB_LINK) | TRB_TOGGLE); - } - return 0; -} - -/** - * cdns3_ep_stall_flush - Stalls and flushes selected endpoint - * @priv_ep: endpoint object - * - * Endpoint must be selected before call to this function - */ -static void cdns3_ep_stall_flush(struct cdns3_endpoint *priv_ep) -{ - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - int val; - - trace_cdns3_halt(priv_ep, 1, 1); - - writel(EP_CMD_DFLUSH | EP_CMD_ERDY | EP_CMD_SSTALL, - &priv_dev->regs->ep_cmd); - - /* wait for DFLUSH cleared */ - readl_poll_timeout_atomic(&priv_dev->regs->ep_cmd, val, - !(val & EP_CMD_DFLUSH), 1, 1000); - priv_ep->flags |= EP_STALLED; - priv_ep->flags &= ~EP_STALL_PENDING; -} - -/** - * cdns3_hw_reset_eps_config - reset endpoints configuration kept by controller. - * @priv_dev: extended gadget object - */ -void cdns3_hw_reset_eps_config(struct cdns3_device *priv_dev) -{ - int i; - - writel(USB_CONF_CFGRST, &priv_dev->regs->usb_conf); - - cdns3_allow_enable_l1(priv_dev, 0); - priv_dev->hw_configured_flag = 0; - priv_dev->onchip_used_size = 0; - priv_dev->out_mem_is_allocated = 0; - priv_dev->wait_for_setup = 0; - priv_dev->using_streams = 0; - - for (i = 0; i < CDNS3_ENDPOINTS_MAX_COUNT; i++) - if (priv_dev->eps[i]) - priv_dev->eps[i]->flags &= ~EP_CONFIGURED; -} - -/** - * cdns3_ep_inc_trb - increment a trb index. - * @index: Pointer to the TRB index to increment. - * @cs: Cycle state - * @trb_in_seg: number of TRBs in segment - * - * The index should never point to the link TRB. After incrementing, - * if it is point to the link TRB, wrap around to the beginning and revert - * cycle state bit The - * link TRB is always at the last TRB entry. - */ -static void cdns3_ep_inc_trb(int *index, u8 *cs, int trb_in_seg) -{ - (*index)++; - if (*index == (trb_in_seg - 1)) { - *index = 0; - *cs ^= 1; - } -} - -/** - * cdns3_ep_inc_enq - increment endpoint's enqueue pointer - * @priv_ep: The endpoint whose enqueue pointer we're incrementing - */ -static void cdns3_ep_inc_enq(struct cdns3_endpoint *priv_ep) -{ - priv_ep->free_trbs--; - cdns3_ep_inc_trb(&priv_ep->enqueue, &priv_ep->pcs, priv_ep->num_trbs); -} - -/** - * cdns3_ep_inc_deq - increment endpoint's dequeue pointer - * @priv_ep: The endpoint whose dequeue pointer we're incrementing - */ -static void cdns3_ep_inc_deq(struct cdns3_endpoint *priv_ep) -{ - priv_ep->free_trbs++; - cdns3_ep_inc_trb(&priv_ep->dequeue, &priv_ep->ccs, priv_ep->num_trbs); -} - -static void cdns3_move_deq_to_next_trb(struct cdns3_request *priv_req) -{ - struct cdns3_endpoint *priv_ep = priv_req->priv_ep; - int current_trb = priv_req->start_trb; - - while (current_trb != priv_req->end_trb) { - cdns3_ep_inc_deq(priv_ep); - current_trb = priv_ep->dequeue; - } - - cdns3_ep_inc_deq(priv_ep); -} - -/** - * cdns3_allow_enable_l1 - enable/disable permits to transition to L1. - * @priv_dev: Extended gadget object - * @enable: Enable/disable permit to transition to L1. - * - * If bit USB_CONF_L1EN is set and device receive Extended Token packet, - * then controller answer with ACK handshake. - * If bit USB_CONF_L1DS is set and device receive Extended Token packet, - * then controller answer with NYET handshake. - */ -void cdns3_allow_enable_l1(struct cdns3_device *priv_dev, int enable) -{ - if (enable) - writel(USB_CONF_L1EN, &priv_dev->regs->usb_conf); - else - writel(USB_CONF_L1DS, &priv_dev->regs->usb_conf); -} - -enum usb_device_speed cdns3_get_speed(struct cdns3_device *priv_dev) -{ - u32 reg; - - reg = readl(&priv_dev->regs->usb_sts); - - if (DEV_SUPERSPEED(reg)) - return USB_SPEED_SUPER; - else if (DEV_HIGHSPEED(reg)) - return USB_SPEED_HIGH; - else if (DEV_FULLSPEED(reg)) - return USB_SPEED_FULL; - else if (DEV_LOWSPEED(reg)) - return USB_SPEED_LOW; - return USB_SPEED_UNKNOWN; -} - -/** - * cdns3_start_all_request - add to ring all request not started - * @priv_dev: Extended gadget object - * @priv_ep: The endpoint for whom request will be started. - * - * Returns return ENOMEM if transfer ring i not enough TRBs to start - * all requests. - */ -static int cdns3_start_all_request(struct cdns3_device *priv_dev, - struct cdns3_endpoint *priv_ep) -{ - struct usb_request *request; - int ret = 0; - u8 pending_empty = list_empty(&priv_ep->pending_req_list); - - /* - * If the last pending transfer is INTERNAL - * OR streams are enabled for this endpoint - * do NOT start new transfer till the last one is pending - */ - if (!pending_empty) { - struct cdns3_request *priv_req; - - request = cdns3_next_request(&priv_ep->pending_req_list); - priv_req = to_cdns3_request(request); - if ((priv_req->flags & REQUEST_INTERNAL) || - (priv_ep->flags & EP_TDLCHK_EN) || - priv_ep->use_streams) { - dev_dbg(priv_dev->dev, "Blocking external request\n"); - return ret; - } - } - - while (!list_empty(&priv_ep->deferred_req_list)) { - request = cdns3_next_request(&priv_ep->deferred_req_list); - - if (!priv_ep->use_streams) { - ret = cdns3_ep_run_transfer(priv_ep, request); - } else { - priv_ep->stream_sg_idx = 0; - ret = cdns3_ep_run_stream_transfer(priv_ep, request); - } - if (ret) - return ret; - - list_del(&request->list); - list_add_tail(&request->list, - &priv_ep->pending_req_list); - if (request->stream_id != 0 || (priv_ep->flags & EP_TDLCHK_EN)) - break; - } - - priv_ep->flags &= ~EP_RING_FULL; - return ret; -} - -/* - * WA2: Set flag for all not ISOC OUT endpoints. If this flag is set - * driver try to detect whether endpoint need additional internal - * buffer for unblocking on-chip FIFO buffer. This flag will be cleared - * if before first DESCMISS interrupt the DMA will be armed. - */ -#define cdns3_wa2_enable_detection(priv_dev, priv_ep, reg) do { \ - if (!priv_ep->dir && priv_ep->type != USB_ENDPOINT_XFER_ISOC) { \ - priv_ep->flags |= EP_QUIRK_EXTRA_BUF_DET; \ - (reg) |= EP_STS_EN_DESCMISEN; \ - } } while (0) - -static void __cdns3_descmiss_copy_data(struct usb_request *request, - struct usb_request *descmiss_req) -{ - int length = request->actual + descmiss_req->actual; - struct scatterlist *s = request->sg; - - if (!s) { - if (length <= request->length) { - memcpy(&((u8 *)request->buf)[request->actual], - descmiss_req->buf, - descmiss_req->actual); - request->actual = length; - } else { - /* It should never occures */ - request->status = -ENOMEM; - } - } else { - if (length <= sg_dma_len(s)) { - void *p = phys_to_virt(sg_dma_address(s)); - - memcpy(&((u8 *)p)[request->actual], - descmiss_req->buf, - descmiss_req->actual); - request->actual = length; - } else { - request->status = -ENOMEM; - } - } -} - -/** - * cdns3_wa2_descmiss_copy_data copy data from internal requests to - * request queued by class driver. - * @priv_ep: extended endpoint object - * @request: request object - */ -static void cdns3_wa2_descmiss_copy_data(struct cdns3_endpoint *priv_ep, - struct usb_request *request) -{ - struct usb_request *descmiss_req; - struct cdns3_request *descmiss_priv_req; - - while (!list_empty(&priv_ep->wa2_descmiss_req_list)) { - int chunk_end; - - descmiss_priv_req = - cdns3_next_priv_request(&priv_ep->wa2_descmiss_req_list); - descmiss_req = &descmiss_priv_req->request; - - /* driver can't touch pending request */ - if (descmiss_priv_req->flags & REQUEST_PENDING) - break; - - chunk_end = descmiss_priv_req->flags & REQUEST_INTERNAL_CH; - request->status = descmiss_req->status; - __cdns3_descmiss_copy_data(request, descmiss_req); - list_del_init(&descmiss_priv_req->list); - kfree(descmiss_req->buf); - cdns3_gadget_ep_free_request(&priv_ep->endpoint, descmiss_req); - --priv_ep->wa2_counter; - - if (!chunk_end) - break; - } -} - -static struct usb_request *cdns3_wa2_gadget_giveback(struct cdns3_device *priv_dev, - struct cdns3_endpoint *priv_ep, - struct cdns3_request *priv_req) -{ - if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_EN && - priv_req->flags & REQUEST_INTERNAL) { - struct usb_request *req; - - req = cdns3_next_request(&priv_ep->deferred_req_list); - - priv_ep->descmis_req = NULL; - - if (!req) - return NULL; - - /* unmap the gadget request before copying data */ - usb_gadget_unmap_request_by_dev(priv_dev->sysdev, req, - priv_ep->dir); - - cdns3_wa2_descmiss_copy_data(priv_ep, req); - if (!(priv_ep->flags & EP_QUIRK_END_TRANSFER) && - req->length != req->actual) { - /* wait for next part of transfer */ - /* re-map the gadget request buffer*/ - usb_gadget_map_request_by_dev(priv_dev->sysdev, req, - usb_endpoint_dir_in(priv_ep->endpoint.desc)); - return NULL; - } - - if (req->status == -EINPROGRESS) - req->status = 0; - - list_del_init(&req->list); - cdns3_start_all_request(priv_dev, priv_ep); - return req; - } - - return &priv_req->request; -} - -static int cdns3_wa2_gadget_ep_queue(struct cdns3_device *priv_dev, - struct cdns3_endpoint *priv_ep, - struct cdns3_request *priv_req) -{ - int deferred = 0; - - /* - * If transfer was queued before DESCMISS appear than we - * can disable handling of DESCMISS interrupt. Driver assumes that it - * can disable special treatment for this endpoint. - */ - if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_DET) { - u32 reg; - - cdns3_select_ep(priv_dev, priv_ep->num | priv_ep->dir); - priv_ep->flags &= ~EP_QUIRK_EXTRA_BUF_DET; - reg = readl(&priv_dev->regs->ep_sts_en); - reg &= ~EP_STS_EN_DESCMISEN; - trace_cdns3_wa2(priv_ep, "workaround disabled\n"); - writel(reg, &priv_dev->regs->ep_sts_en); - } - - if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_EN) { - u8 pending_empty = list_empty(&priv_ep->pending_req_list); - u8 descmiss_empty = list_empty(&priv_ep->wa2_descmiss_req_list); - - /* - * DESCMISS transfer has been finished, so data will be - * directly copied from internal allocated usb_request - * objects. - */ - if (pending_empty && !descmiss_empty && - !(priv_req->flags & REQUEST_INTERNAL)) { - cdns3_wa2_descmiss_copy_data(priv_ep, - &priv_req->request); - - trace_cdns3_wa2(priv_ep, "get internal stored data"); - - list_add_tail(&priv_req->request.list, - &priv_ep->pending_req_list); - cdns3_gadget_giveback(priv_ep, priv_req, - priv_req->request.status); - - /* - * Intentionally driver returns positive value as - * correct value. It informs that transfer has - * been finished. - */ - return EINPROGRESS; - } - - /* - * Driver will wait for completion DESCMISS transfer, - * before starts new, not DESCMISS transfer. - */ - if (!pending_empty && !descmiss_empty) { - trace_cdns3_wa2(priv_ep, "wait for pending transfer\n"); - deferred = 1; - } - - if (priv_req->flags & REQUEST_INTERNAL) - list_add_tail(&priv_req->list, - &priv_ep->wa2_descmiss_req_list); - } - - return deferred; -} - -static void cdns3_wa2_remove_old_request(struct cdns3_endpoint *priv_ep) -{ - struct cdns3_request *priv_req; - - while (!list_empty(&priv_ep->wa2_descmiss_req_list)) { - u8 chain; - - priv_req = cdns3_next_priv_request(&priv_ep->wa2_descmiss_req_list); - chain = !!(priv_req->flags & REQUEST_INTERNAL_CH); - - trace_cdns3_wa2(priv_ep, "removes eldest request"); - - kfree(priv_req->request.buf); - cdns3_gadget_ep_free_request(&priv_ep->endpoint, - &priv_req->request); - list_del_init(&priv_req->list); - --priv_ep->wa2_counter; - - if (!chain) - break; - } -} - -/** - * cdns3_wa2_descmissing_packet - handles descriptor missing event. - * @priv_ep: extended gadget object - * - * This function is used only for WA2. For more information see Work around 2 - * description. - */ -static void cdns3_wa2_descmissing_packet(struct cdns3_endpoint *priv_ep) -{ - struct cdns3_request *priv_req; - struct usb_request *request; - u8 pending_empty = list_empty(&priv_ep->pending_req_list); - - /* check for pending transfer */ - if (!pending_empty) { - trace_cdns3_wa2(priv_ep, "Ignoring Descriptor missing IRQ\n"); - return; - } - - if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_DET) { - priv_ep->flags &= ~EP_QUIRK_EXTRA_BUF_DET; - priv_ep->flags |= EP_QUIRK_EXTRA_BUF_EN; - } - - trace_cdns3_wa2(priv_ep, "Description Missing detected\n"); - - if (priv_ep->wa2_counter >= CDNS3_WA2_NUM_BUFFERS) { - trace_cdns3_wa2(priv_ep, "WA2 overflow\n"); - cdns3_wa2_remove_old_request(priv_ep); - } - - request = cdns3_gadget_ep_alloc_request(&priv_ep->endpoint, - GFP_ATOMIC); - if (!request) - goto err; - - priv_req = to_cdns3_request(request); - priv_req->flags |= REQUEST_INTERNAL; - - /* if this field is still assigned it indicate that transfer related - * with this request has not been finished yet. Driver in this - * case simply allocate next request and assign flag REQUEST_INTERNAL_CH - * flag to previous one. It will indicate that current request is - * part of the previous one. - */ - if (priv_ep->descmis_req) - priv_ep->descmis_req->flags |= REQUEST_INTERNAL_CH; - - priv_req->request.buf = kzalloc(CDNS3_DESCMIS_BUF_SIZE, - GFP_ATOMIC); - priv_ep->wa2_counter++; - - if (!priv_req->request.buf) { - cdns3_gadget_ep_free_request(&priv_ep->endpoint, request); - goto err; - } - - priv_req->request.length = CDNS3_DESCMIS_BUF_SIZE; - priv_ep->descmis_req = priv_req; - - __cdns3_gadget_ep_queue(&priv_ep->endpoint, - &priv_ep->descmis_req->request, - GFP_ATOMIC); - - return; - -err: - dev_err(priv_ep->cdns3_dev->dev, - "Failed: No sufficient memory for DESCMIS\n"); -} - -static void cdns3_wa2_reset_tdl(struct cdns3_device *priv_dev) -{ - u16 tdl = EP_CMD_TDL_GET(readl(&priv_dev->regs->ep_cmd)); - - if (tdl) { - u16 reset_val = EP_CMD_TDL_MAX + 1 - tdl; - - writel(EP_CMD_TDL_SET(reset_val) | EP_CMD_STDL, - &priv_dev->regs->ep_cmd); - } -} - -static void cdns3_wa2_check_outq_status(struct cdns3_device *priv_dev) -{ - u32 ep_sts_reg; - - /* select EP0-out */ - cdns3_select_ep(priv_dev, 0); - - ep_sts_reg = readl(&priv_dev->regs->ep_sts); - - if (EP_STS_OUTQ_VAL(ep_sts_reg)) { - u32 outq_ep_num = EP_STS_OUTQ_NO(ep_sts_reg); - struct cdns3_endpoint *outq_ep = priv_dev->eps[outq_ep_num]; - - if ((outq_ep->flags & EP_ENABLED) && !(outq_ep->use_streams) && - outq_ep->type != USB_ENDPOINT_XFER_ISOC && outq_ep_num) { - u8 pending_empty = list_empty(&outq_ep->pending_req_list); - - if ((outq_ep->flags & EP_QUIRK_EXTRA_BUF_DET) || - (outq_ep->flags & EP_QUIRK_EXTRA_BUF_EN) || - !pending_empty) { - } else { - u32 ep_sts_en_reg; - u32 ep_cmd_reg; - - cdns3_select_ep(priv_dev, outq_ep->num | - outq_ep->dir); - ep_sts_en_reg = readl(&priv_dev->regs->ep_sts_en); - ep_cmd_reg = readl(&priv_dev->regs->ep_cmd); - - outq_ep->flags |= EP_TDLCHK_EN; - cdns3_set_register_bit(&priv_dev->regs->ep_cfg, - EP_CFG_TDL_CHK); - - cdns3_wa2_enable_detection(priv_dev, outq_ep, - ep_sts_en_reg); - writel(ep_sts_en_reg, - &priv_dev->regs->ep_sts_en); - /* reset tdl value to zero */ - cdns3_wa2_reset_tdl(priv_dev); - /* - * Memory barrier - Reset tdl before ringing the - * doorbell. - */ - wmb(); - if (EP_CMD_DRDY & ep_cmd_reg) { - trace_cdns3_wa2(outq_ep, "Enabling WA2 skipping doorbell\n"); - - } else { - trace_cdns3_wa2(outq_ep, "Enabling WA2 ringing doorbell\n"); - /* - * ring doorbell to generate DESCMIS irq - */ - writel(EP_CMD_DRDY, - &priv_dev->regs->ep_cmd); - } - } - } - } -} - -/** - * cdns3_gadget_giveback - call struct usb_request's ->complete callback - * @priv_ep: The endpoint to whom the request belongs to - * @priv_req: The request we're giving back - * @status: completion code for the request - * - * Must be called with controller's lock held and interrupts disabled. This - * function will unmap @req and call its ->complete() callback to notify upper - * layers that it has completed. - */ -void cdns3_gadget_giveback(struct cdns3_endpoint *priv_ep, - struct cdns3_request *priv_req, - int status) -{ - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - struct usb_request *request = &priv_req->request; - - list_del_init(&request->list); - - if (request->status == -EINPROGRESS) - request->status = status; - - usb_gadget_unmap_request_by_dev(priv_dev->sysdev, request, - priv_ep->dir); - - if ((priv_req->flags & REQUEST_UNALIGNED) && - priv_ep->dir == USB_DIR_OUT && !request->status) - memcpy(request->buf, priv_req->aligned_buf->buf, - request->length); - - priv_req->flags &= ~(REQUEST_PENDING | REQUEST_UNALIGNED); - /* All TRBs have finished, clear the counter */ - priv_req->finished_trb = 0; - trace_cdns3_gadget_giveback(priv_req); - - if (priv_dev->dev_ver < DEV_VER_V2) { - request = cdns3_wa2_gadget_giveback(priv_dev, priv_ep, - priv_req); - if (!request) - return; - } - - if (request->complete) { - spin_unlock(&priv_dev->lock); - usb_gadget_giveback_request(&priv_ep->endpoint, - request); - spin_lock(&priv_dev->lock); - } - - if (request->buf == priv_dev->zlp_buf) - cdns3_gadget_ep_free_request(&priv_ep->endpoint, request); -} - -static void cdns3_wa1_restore_cycle_bit(struct cdns3_endpoint *priv_ep) -{ - /* Work around for stale data address in TRB*/ - if (priv_ep->wa1_set) { - trace_cdns3_wa1(priv_ep, "restore cycle bit"); - - priv_ep->wa1_set = 0; - priv_ep->wa1_trb_index = 0xFFFF; - if (priv_ep->wa1_cycle_bit) { - priv_ep->wa1_trb->control = - priv_ep->wa1_trb->control | cpu_to_le32(0x1); - } else { - priv_ep->wa1_trb->control = - priv_ep->wa1_trb->control & cpu_to_le32(~0x1); - } - } -} - -static void cdns3_free_aligned_request_buf(struct work_struct *work) -{ - struct cdns3_device *priv_dev = container_of(work, struct cdns3_device, - aligned_buf_wq); - struct cdns3_aligned_buf *buf, *tmp; - unsigned long flags; - - spin_lock_irqsave(&priv_dev->lock, flags); - - list_for_each_entry_safe(buf, tmp, &priv_dev->aligned_buf_list, list) { - if (!buf->in_use) { - list_del(&buf->list); - - /* - * Re-enable interrupts to free DMA capable memory. - * Driver can't free this memory with disabled - * interrupts. - */ - spin_unlock_irqrestore(&priv_dev->lock, flags); - dma_free_coherent(priv_dev->sysdev, buf->size, - buf->buf, buf->dma); - kfree(buf); - spin_lock_irqsave(&priv_dev->lock, flags); - } - } - - spin_unlock_irqrestore(&priv_dev->lock, flags); -} - -static int cdns3_prepare_aligned_request_buf(struct cdns3_request *priv_req) -{ - struct cdns3_endpoint *priv_ep = priv_req->priv_ep; - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - struct cdns3_aligned_buf *buf; - - /* check if buffer is aligned to 8. */ - if (!((uintptr_t)priv_req->request.buf & 0x7)) - return 0; - - buf = priv_req->aligned_buf; - - if (!buf || priv_req->request.length > buf->size) { - buf = kzalloc(sizeof(*buf), GFP_ATOMIC); - if (!buf) - return -ENOMEM; - - buf->size = priv_req->request.length; - - buf->buf = dma_alloc_coherent(priv_dev->sysdev, - buf->size, - &buf->dma, - GFP_ATOMIC); - if (!buf->buf) { - kfree(buf); - return -ENOMEM; - } - - if (priv_req->aligned_buf) { - trace_cdns3_free_aligned_request(priv_req); - priv_req->aligned_buf->in_use = 0; - queue_work(system_freezable_wq, - &priv_dev->aligned_buf_wq); - } - - buf->in_use = 1; - priv_req->aligned_buf = buf; - - list_add_tail(&buf->list, - &priv_dev->aligned_buf_list); - } - - if (priv_ep->dir == USB_DIR_IN) { - memcpy(buf->buf, priv_req->request.buf, - priv_req->request.length); - } - - priv_req->flags |= REQUEST_UNALIGNED; - trace_cdns3_prepare_aligned_request(priv_req); - - return 0; -} - -static int cdns3_wa1_update_guard(struct cdns3_endpoint *priv_ep, - struct cdns3_trb *trb) -{ - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - - if (!priv_ep->wa1_set) { - u32 doorbell; - - doorbell = !!(readl(&priv_dev->regs->ep_cmd) & EP_CMD_DRDY); - - if (doorbell) { - priv_ep->wa1_cycle_bit = priv_ep->pcs ? TRB_CYCLE : 0; - priv_ep->wa1_set = 1; - priv_ep->wa1_trb = trb; - priv_ep->wa1_trb_index = priv_ep->enqueue; - trace_cdns3_wa1(priv_ep, "set guard"); - return 0; - } - } - return 1; -} - -static void cdns3_wa1_tray_restore_cycle_bit(struct cdns3_device *priv_dev, - struct cdns3_endpoint *priv_ep) -{ - int dma_index; - u32 doorbell; - - doorbell = !!(readl(&priv_dev->regs->ep_cmd) & EP_CMD_DRDY); - dma_index = cdns3_get_dma_pos(priv_dev, priv_ep); - - if (!doorbell || dma_index != priv_ep->wa1_trb_index) - cdns3_wa1_restore_cycle_bit(priv_ep); -} - -static int cdns3_ep_run_stream_transfer(struct cdns3_endpoint *priv_ep, - struct usb_request *request) -{ - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - struct cdns3_request *priv_req; - struct cdns3_trb *trb; - dma_addr_t trb_dma; - int address; - u32 control; - u32 length; - u32 tdl; - unsigned int sg_idx = priv_ep->stream_sg_idx; - - priv_req = to_cdns3_request(request); - address = priv_ep->endpoint.desc->bEndpointAddress; - - priv_ep->flags |= EP_PENDING_REQUEST; - - /* must allocate buffer aligned to 8 */ - if (priv_req->flags & REQUEST_UNALIGNED) - trb_dma = priv_req->aligned_buf->dma; - else - trb_dma = request->dma; - - /* For stream capable endpoints driver use only single TD. */ - trb = priv_ep->trb_pool + priv_ep->enqueue; - priv_req->start_trb = priv_ep->enqueue; - priv_req->end_trb = priv_req->start_trb; - priv_req->trb = trb; - - cdns3_select_ep(priv_ep->cdns3_dev, address); - - control = TRB_TYPE(TRB_NORMAL) | TRB_CYCLE | - TRB_STREAM_ID(priv_req->request.stream_id) | TRB_ISP; - - if (!request->num_sgs) { - trb->buffer = cpu_to_le32(TRB_BUFFER(trb_dma)); - length = request->length; - } else { - trb->buffer = cpu_to_le32(TRB_BUFFER(request->sg[sg_idx].dma_address)); - length = request->sg[sg_idx].length; - } - - tdl = DIV_ROUND_UP(length, priv_ep->endpoint.maxpacket); - - trb->length = cpu_to_le32(TRB_BURST_LEN(16) | TRB_LEN(length)); - - /* - * For DEV_VER_V2 controller version we have enabled - * USB_CONF2_EN_TDL_TRB in DMULT configuration. - * This enables TDL calculation based on TRB, hence setting TDL in TRB. - */ - if (priv_dev->dev_ver >= DEV_VER_V2) { - if (priv_dev->gadget.speed == USB_SPEED_SUPER) - trb->length |= cpu_to_le32(TRB_TDL_SS_SIZE(tdl)); - } - priv_req->flags |= REQUEST_PENDING; - - trb->control = cpu_to_le32(control); - - trace_cdns3_prepare_trb(priv_ep, priv_req->trb); - - /* - * Memory barrier - Cycle Bit must be set before trb->length and - * trb->buffer fields. - */ - wmb(); - - /* always first element */ - writel(EP_TRADDR_TRADDR(priv_ep->trb_pool_dma), - &priv_dev->regs->ep_traddr); - - if (!(priv_ep->flags & EP_STALLED)) { - trace_cdns3_ring(priv_ep); - /*clearing TRBERR and EP_STS_DESCMIS before seting DRDY*/ - writel(EP_STS_TRBERR | EP_STS_DESCMIS, &priv_dev->regs->ep_sts); - - priv_ep->prime_flag = false; - - /* - * Controller version DEV_VER_V2 tdl calculation - * is based on TRB - */ - - if (priv_dev->dev_ver < DEV_VER_V2) - writel(EP_CMD_TDL_SET(tdl) | EP_CMD_STDL, - &priv_dev->regs->ep_cmd); - else if (priv_dev->dev_ver > DEV_VER_V2) - writel(tdl, &priv_dev->regs->ep_tdl); - - priv_ep->last_stream_id = priv_req->request.stream_id; - writel(EP_CMD_DRDY, &priv_dev->regs->ep_cmd); - writel(EP_CMD_ERDY_SID(priv_req->request.stream_id) | - EP_CMD_ERDY, &priv_dev->regs->ep_cmd); - - trace_cdns3_doorbell_epx(priv_ep->name, - readl(&priv_dev->regs->ep_traddr)); - } - - /* WORKAROUND for transition to L0 */ - __cdns3_gadget_wakeup(priv_dev); - - return 0; -} - -/** - * cdns3_ep_run_transfer - start transfer on no-default endpoint hardware - * @priv_ep: endpoint object - * @request: request object - * - * Returns zero on success or negative value on failure - */ -static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, - struct usb_request *request) -{ - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - struct cdns3_request *priv_req; - struct cdns3_trb *trb; - struct cdns3_trb *link_trb = NULL; - dma_addr_t trb_dma; - u32 togle_pcs = 1; - int sg_iter = 0; - int num_trb; - int address; - u32 control; - int pcs; - u16 total_tdl = 0; - struct scatterlist *s = NULL; - bool sg_supported = !!(request->num_mapped_sgs); - - if (priv_ep->type == USB_ENDPOINT_XFER_ISOC) - num_trb = priv_ep->interval; - else - num_trb = sg_supported ? request->num_mapped_sgs : 1; - - if (num_trb > priv_ep->free_trbs) { - priv_ep->flags |= EP_RING_FULL; - return -ENOBUFS; - } - - priv_req = to_cdns3_request(request); - address = priv_ep->endpoint.desc->bEndpointAddress; - - priv_ep->flags |= EP_PENDING_REQUEST; - - /* must allocate buffer aligned to 8 */ - if (priv_req->flags & REQUEST_UNALIGNED) - trb_dma = priv_req->aligned_buf->dma; - else - trb_dma = request->dma; - - trb = priv_ep->trb_pool + priv_ep->enqueue; - priv_req->start_trb = priv_ep->enqueue; - priv_req->trb = trb; - - cdns3_select_ep(priv_ep->cdns3_dev, address); - - /* prepare ring */ - if ((priv_ep->enqueue + num_trb) >= (priv_ep->num_trbs - 1)) { - int doorbell, dma_index; - u32 ch_bit = 0; - - doorbell = !!(readl(&priv_dev->regs->ep_cmd) & EP_CMD_DRDY); - dma_index = cdns3_get_dma_pos(priv_dev, priv_ep); - - /* Driver can't update LINK TRB if it is current processed. */ - if (doorbell && dma_index == priv_ep->num_trbs - 1) { - priv_ep->flags |= EP_DEFERRED_DRDY; - return -ENOBUFS; - } - - /*updating C bt in Link TRB before starting DMA*/ - link_trb = priv_ep->trb_pool + (priv_ep->num_trbs - 1); - /* - * For TRs size equal 2 enabling TRB_CHAIN for epXin causes - * that DMA stuck at the LINK TRB. - * On the other hand, removing TRB_CHAIN for longer TRs for - * epXout cause that DMA stuck after handling LINK TRB. - * To eliminate this strange behavioral driver set TRB_CHAIN - * bit only for TR size > 2. - */ - if (priv_ep->type == USB_ENDPOINT_XFER_ISOC || - TRBS_PER_SEGMENT > 2) - ch_bit = TRB_CHAIN; - - link_trb->control = cpu_to_le32(((priv_ep->pcs) ? TRB_CYCLE : 0) | - TRB_TYPE(TRB_LINK) | TRB_TOGGLE | ch_bit); - } - - if (priv_dev->dev_ver <= DEV_VER_V2) - togle_pcs = cdns3_wa1_update_guard(priv_ep, trb); - - if (sg_supported) - s = request->sg; - - /* set incorrect Cycle Bit for first trb*/ - control = priv_ep->pcs ? 0 : TRB_CYCLE; - trb->length = 0; - if (priv_dev->dev_ver >= DEV_VER_V2) { - u16 td_size; - - td_size = DIV_ROUND_UP(request->length, - priv_ep->endpoint.maxpacket); - if (priv_dev->gadget.speed == USB_SPEED_SUPER) - trb->length = TRB_TDL_SS_SIZE(td_size); - else - control |= TRB_TDL_HS_SIZE(td_size); - } - - do { - u32 length; - - /* fill TRB */ - control |= TRB_TYPE(TRB_NORMAL); - if (sg_supported) { - trb->buffer = cpu_to_le32(TRB_BUFFER(sg_dma_address(s))); - length = sg_dma_len(s); - } else { - trb->buffer = cpu_to_le32(TRB_BUFFER(trb_dma)); - length = request->length; - } - - if (priv_ep->flags & EP_TDLCHK_EN) - total_tdl += DIV_ROUND_UP(length, - priv_ep->endpoint.maxpacket); - - trb->length |= cpu_to_le32(TRB_BURST_LEN(priv_ep->trb_burst_size) | - TRB_LEN(length)); - pcs = priv_ep->pcs ? TRB_CYCLE : 0; - - /* - * first trb should be prepared as last to avoid processing - * transfer to early - */ - if (sg_iter != 0) - control |= pcs; - - if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && !priv_ep->dir) { - control |= TRB_IOC | TRB_ISP; - } else { - /* for last element in TD or in SG list */ - if (sg_iter == (num_trb - 1) && sg_iter != 0) - control |= pcs | TRB_IOC | TRB_ISP; - } - - if (sg_iter) - trb->control = cpu_to_le32(control); - else - priv_req->trb->control = cpu_to_le32(control); - - if (sg_supported) { - trb->control |= TRB_ISP; - /* Don't set chain bit for last TRB */ - if (sg_iter < num_trb - 1) - trb->control |= TRB_CHAIN; - - s = sg_next(s); - } - - control = 0; - ++sg_iter; - priv_req->end_trb = priv_ep->enqueue; - cdns3_ep_inc_enq(priv_ep); - trb = priv_ep->trb_pool + priv_ep->enqueue; - trb->length = 0; - } while (sg_iter < num_trb); - - trb = priv_req->trb; - - priv_req->flags |= REQUEST_PENDING; - priv_req->num_of_trb = num_trb; - - if (sg_iter == 1) - trb->control |= cpu_to_le32(TRB_IOC | TRB_ISP); - - if (priv_dev->dev_ver < DEV_VER_V2 && - (priv_ep->flags & EP_TDLCHK_EN)) { - u16 tdl = total_tdl; - u16 old_tdl = EP_CMD_TDL_GET(readl(&priv_dev->regs->ep_cmd)); - - if (tdl > EP_CMD_TDL_MAX) { - tdl = EP_CMD_TDL_MAX; - priv_ep->pending_tdl = total_tdl - EP_CMD_TDL_MAX; - } - - if (old_tdl < tdl) { - tdl -= old_tdl; - writel(EP_CMD_TDL_SET(tdl) | EP_CMD_STDL, - &priv_dev->regs->ep_cmd); - } - } - - /* - * Memory barrier - cycle bit must be set before other filds in trb. - */ - wmb(); - - /* give the TD to the consumer*/ - if (togle_pcs) - trb->control = trb->control ^ cpu_to_le32(1); - - if (priv_dev->dev_ver <= DEV_VER_V2) - cdns3_wa1_tray_restore_cycle_bit(priv_dev, priv_ep); - - if (num_trb > 1) { - int i = 0; - - while (i < num_trb) { - trace_cdns3_prepare_trb(priv_ep, trb + i); - if (trb + i == link_trb) { - trb = priv_ep->trb_pool; - num_trb = num_trb - i; - i = 0; - } else { - i++; - } - } - } else { - trace_cdns3_prepare_trb(priv_ep, priv_req->trb); - } - - /* - * Memory barrier - Cycle Bit must be set before trb->length and - * trb->buffer fields. - */ - wmb(); - - /* - * For DMULT mode we can set address to transfer ring only once after - * enabling endpoint. - */ - if (priv_ep->flags & EP_UPDATE_EP_TRBADDR) { - /* - * Until SW is not ready to handle the OUT transfer the ISO OUT - * Endpoint should be disabled (EP_CFG.ENABLE = 0). - * EP_CFG_ENABLE must be set before updating ep_traddr. - */ - if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && !priv_ep->dir && - !(priv_ep->flags & EP_QUIRK_ISO_OUT_EN)) { - priv_ep->flags |= EP_QUIRK_ISO_OUT_EN; - cdns3_set_register_bit(&priv_dev->regs->ep_cfg, - EP_CFG_ENABLE); - } - - writel(EP_TRADDR_TRADDR(priv_ep->trb_pool_dma + - priv_req->start_trb * TRB_SIZE), - &priv_dev->regs->ep_traddr); - - priv_ep->flags &= ~EP_UPDATE_EP_TRBADDR; - } - - if (!priv_ep->wa1_set && !(priv_ep->flags & EP_STALLED)) { - trace_cdns3_ring(priv_ep); - /*clearing TRBERR and EP_STS_DESCMIS before seting DRDY*/ - writel(EP_STS_TRBERR | EP_STS_DESCMIS, &priv_dev->regs->ep_sts); - writel(EP_CMD_DRDY, &priv_dev->regs->ep_cmd); - trace_cdns3_doorbell_epx(priv_ep->name, - readl(&priv_dev->regs->ep_traddr)); - } - - /* WORKAROUND for transition to L0 */ - __cdns3_gadget_wakeup(priv_dev); - - return 0; -} - -void cdns3_set_hw_configuration(struct cdns3_device *priv_dev) -{ - struct cdns3_endpoint *priv_ep; - struct usb_ep *ep; - - if (priv_dev->hw_configured_flag) - return; - - writel(USB_CONF_CFGSET, &priv_dev->regs->usb_conf); - - cdns3_set_register_bit(&priv_dev->regs->usb_conf, - USB_CONF_U1EN | USB_CONF_U2EN); - - priv_dev->hw_configured_flag = 1; - - list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) { - if (ep->enabled) { - priv_ep = ep_to_cdns3_ep(ep); - cdns3_start_all_request(priv_dev, priv_ep); - } - } - - cdns3_allow_enable_l1(priv_dev, 1); -} - -/** - * cdns3_trb_handled - check whether trb has been handled by DMA - * - * @priv_ep: extended endpoint object. - * @priv_req: request object for checking - * - * Endpoint must be selected before invoking this function. - * - * Returns false if request has not been handled by DMA, else returns true. - * - * SR - start ring - * ER - end ring - * DQ = priv_ep->dequeue - dequeue position - * EQ = priv_ep->enqueue - enqueue position - * ST = priv_req->start_trb - index of first TRB in transfer ring - * ET = priv_req->end_trb - index of last TRB in transfer ring - * CI = current_index - index of processed TRB by DMA. - * - * As first step, we check if the TRB between the ST and ET. - * Then, we check if cycle bit for index priv_ep->dequeue - * is correct. - * - * some rules: - * 1. priv_ep->dequeue never equals to current_index. - * 2 priv_ep->enqueue never exceed priv_ep->dequeue - * 3. exception: priv_ep->enqueue == priv_ep->dequeue - * and priv_ep->free_trbs is zero. - * This case indicate that TR is full. - * - * At below two cases, the request have been handled. - * Case 1 - priv_ep->dequeue < current_index - * SR ... EQ ... DQ ... CI ... ER - * SR ... DQ ... CI ... EQ ... ER - * - * Case 2 - priv_ep->dequeue > current_index - * This situation takes place when CI go through the LINK TRB at the end of - * transfer ring. - * SR ... CI ... EQ ... DQ ... ER - */ -static bool cdns3_trb_handled(struct cdns3_endpoint *priv_ep, - struct cdns3_request *priv_req) -{ - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - struct cdns3_trb *trb; - int current_index = 0; - int handled = 0; - int doorbell; - - current_index = cdns3_get_dma_pos(priv_dev, priv_ep); - doorbell = !!(readl(&priv_dev->regs->ep_cmd) & EP_CMD_DRDY); - - /* current trb doesn't belong to this request */ - if (priv_req->start_trb < priv_req->end_trb) { - if (priv_ep->dequeue > priv_req->end_trb) - goto finish; - - if (priv_ep->dequeue < priv_req->start_trb) - goto finish; - } - - if ((priv_req->start_trb > priv_req->end_trb) && - (priv_ep->dequeue > priv_req->end_trb) && - (priv_ep->dequeue < priv_req->start_trb)) - goto finish; - - if ((priv_req->start_trb == priv_req->end_trb) && - (priv_ep->dequeue != priv_req->end_trb)) - goto finish; - - trb = &priv_ep->trb_pool[priv_ep->dequeue]; - - if ((le32_to_cpu(trb->control) & TRB_CYCLE) != priv_ep->ccs) - goto finish; - - if (doorbell == 1 && current_index == priv_ep->dequeue) - goto finish; - - /* The corner case for TRBS_PER_SEGMENT equal 2). */ - if (TRBS_PER_SEGMENT == 2 && priv_ep->type != USB_ENDPOINT_XFER_ISOC) { - handled = 1; - goto finish; - } - - if (priv_ep->enqueue == priv_ep->dequeue && - priv_ep->free_trbs == 0) { - handled = 1; - } else if (priv_ep->dequeue < current_index) { - if ((current_index == (priv_ep->num_trbs - 1)) && - !priv_ep->dequeue) - goto finish; - - handled = 1; - } else if (priv_ep->dequeue > current_index) { - handled = 1; - } - -finish: - trace_cdns3_request_handled(priv_req, current_index, handled); - - return handled; -} - -static void cdns3_transfer_completed(struct cdns3_device *priv_dev, - struct cdns3_endpoint *priv_ep) -{ - struct cdns3_request *priv_req; - struct usb_request *request; - struct cdns3_trb *trb; - bool request_handled = false; - bool transfer_end = false; - - while (!list_empty(&priv_ep->pending_req_list)) { - request = cdns3_next_request(&priv_ep->pending_req_list); - priv_req = to_cdns3_request(request); - - trb = priv_ep->trb_pool + priv_ep->dequeue; - - /* Request was dequeued and TRB was changed to TRB_LINK. */ - if (TRB_FIELD_TO_TYPE(le32_to_cpu(trb->control)) == TRB_LINK) { - trace_cdns3_complete_trb(priv_ep, trb); - cdns3_move_deq_to_next_trb(priv_req); - } - - if (!request->stream_id) { - /* Re-select endpoint. It could be changed by other CPU - * during handling usb_gadget_giveback_request. - */ - cdns3_select_ep(priv_dev, priv_ep->endpoint.address); - - while (cdns3_trb_handled(priv_ep, priv_req)) { - priv_req->finished_trb++; - if (priv_req->finished_trb >= priv_req->num_of_trb) - request_handled = true; - - trb = priv_ep->trb_pool + priv_ep->dequeue; - trace_cdns3_complete_trb(priv_ep, trb); - - if (!transfer_end) - request->actual += - TRB_LEN(le32_to_cpu(trb->length)); - - if (priv_req->num_of_trb > 1 && - le32_to_cpu(trb->control) & TRB_SMM) - transfer_end = true; - - cdns3_ep_inc_deq(priv_ep); - } - - if (request_handled) { - cdns3_gadget_giveback(priv_ep, priv_req, 0); - request_handled = false; - transfer_end = false; - } else { - goto prepare_next_td; - } - - if (priv_ep->type != USB_ENDPOINT_XFER_ISOC && - TRBS_PER_SEGMENT == 2) - break; - } else { - /* Re-select endpoint. It could be changed by other CPU - * during handling usb_gadget_giveback_request. - */ - cdns3_select_ep(priv_dev, priv_ep->endpoint.address); - - trb = priv_ep->trb_pool; - trace_cdns3_complete_trb(priv_ep, trb); - - if (trb != priv_req->trb) - dev_warn(priv_dev->dev, - "request_trb=0x%p, queue_trb=0x%p\n", - priv_req->trb, trb); - - request->actual += TRB_LEN(le32_to_cpu(trb->length)); - - if (!request->num_sgs || - (request->num_sgs == (priv_ep->stream_sg_idx + 1))) { - priv_ep->stream_sg_idx = 0; - cdns3_gadget_giveback(priv_ep, priv_req, 0); - } else { - priv_ep->stream_sg_idx++; - cdns3_ep_run_stream_transfer(priv_ep, request); - } - break; - } - } - priv_ep->flags &= ~EP_PENDING_REQUEST; - -prepare_next_td: - if (!(priv_ep->flags & EP_STALLED) && - !(priv_ep->flags & EP_STALL_PENDING)) - cdns3_start_all_request(priv_dev, priv_ep); -} - -void cdns3_rearm_transfer(struct cdns3_endpoint *priv_ep, u8 rearm) -{ - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - - cdns3_wa1_restore_cycle_bit(priv_ep); - - if (rearm) { - trace_cdns3_ring(priv_ep); - - /* Cycle Bit must be updated before arming DMA. */ - wmb(); - writel(EP_CMD_DRDY, &priv_dev->regs->ep_cmd); - - __cdns3_gadget_wakeup(priv_dev); - - trace_cdns3_doorbell_epx(priv_ep->name, - readl(&priv_dev->regs->ep_traddr)); - } -} - -static void cdns3_reprogram_tdl(struct cdns3_endpoint *priv_ep) -{ - u16 tdl = priv_ep->pending_tdl; - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - - if (tdl > EP_CMD_TDL_MAX) { - tdl = EP_CMD_TDL_MAX; - priv_ep->pending_tdl -= EP_CMD_TDL_MAX; - } else { - priv_ep->pending_tdl = 0; - } - - writel(EP_CMD_TDL_SET(tdl) | EP_CMD_STDL, &priv_dev->regs->ep_cmd); -} - -/** - * cdns3_check_ep_interrupt_proceed - Processes interrupt related to endpoint - * @priv_ep: endpoint object - * - * Returns 0 - */ -static int cdns3_check_ep_interrupt_proceed(struct cdns3_endpoint *priv_ep) -{ - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - u32 ep_sts_reg; - struct usb_request *deferred_request; - struct usb_request *pending_request; - u32 tdl = 0; - - cdns3_select_ep(priv_dev, priv_ep->endpoint.address); - - trace_cdns3_epx_irq(priv_dev, priv_ep); - - ep_sts_reg = readl(&priv_dev->regs->ep_sts); - writel(ep_sts_reg, &priv_dev->regs->ep_sts); - - if ((ep_sts_reg & EP_STS_PRIME) && priv_ep->use_streams) { - bool dbusy = !!(ep_sts_reg & EP_STS_DBUSY); - - tdl = cdns3_get_tdl(priv_dev); - - /* - * Continue the previous transfer: - * There is some racing between ERDY and PRIME. The device send - * ERDY and almost in the same time Host send PRIME. It cause - * that host ignore the ERDY packet and driver has to send it - * again. - */ - if (tdl && (dbusy || !EP_STS_BUFFEMPTY(ep_sts_reg) || - EP_STS_HOSTPP(ep_sts_reg))) { - writel(EP_CMD_ERDY | - EP_CMD_ERDY_SID(priv_ep->last_stream_id), - &priv_dev->regs->ep_cmd); - ep_sts_reg &= ~(EP_STS_MD_EXIT | EP_STS_IOC); - } else { - priv_ep->prime_flag = true; - - pending_request = cdns3_next_request(&priv_ep->pending_req_list); - deferred_request = cdns3_next_request(&priv_ep->deferred_req_list); - - if (deferred_request && !pending_request) { - cdns3_start_all_request(priv_dev, priv_ep); - } - } - } - - if (ep_sts_reg & EP_STS_TRBERR) { - if (priv_ep->flags & EP_STALL_PENDING && - !(ep_sts_reg & EP_STS_DESCMIS && - priv_dev->dev_ver < DEV_VER_V2)) { - cdns3_ep_stall_flush(priv_ep); - } - - /* - * For isochronous transfer driver completes request on - * IOC or on TRBERR. IOC appears only when device receive - * OUT data packet. If host disable stream or lost some packet - * then the only way to finish all queued transfer is to do it - * on TRBERR event. - */ - if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && - !priv_ep->wa1_set) { - if (!priv_ep->dir) { - u32 ep_cfg = readl(&priv_dev->regs->ep_cfg); - - ep_cfg &= ~EP_CFG_ENABLE; - writel(ep_cfg, &priv_dev->regs->ep_cfg); - priv_ep->flags &= ~EP_QUIRK_ISO_OUT_EN; - } - cdns3_transfer_completed(priv_dev, priv_ep); - } else if (!(priv_ep->flags & EP_STALLED) && - !(priv_ep->flags & EP_STALL_PENDING)) { - if (priv_ep->flags & EP_DEFERRED_DRDY) { - priv_ep->flags &= ~EP_DEFERRED_DRDY; - cdns3_start_all_request(priv_dev, priv_ep); - } else { - cdns3_rearm_transfer(priv_ep, - priv_ep->wa1_set); - } - } - } - - if ((ep_sts_reg & EP_STS_IOC) || (ep_sts_reg & EP_STS_ISP) || - (ep_sts_reg & EP_STS_IOT)) { - if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_EN) { - if (ep_sts_reg & EP_STS_ISP) - priv_ep->flags |= EP_QUIRK_END_TRANSFER; - else - priv_ep->flags &= ~EP_QUIRK_END_TRANSFER; - } - - if (!priv_ep->use_streams) { - if ((ep_sts_reg & EP_STS_IOC) || - (ep_sts_reg & EP_STS_ISP)) { - cdns3_transfer_completed(priv_dev, priv_ep); - } else if ((priv_ep->flags & EP_TDLCHK_EN) & - priv_ep->pending_tdl) { - /* handle IOT with pending tdl */ - cdns3_reprogram_tdl(priv_ep); - } - } else if (priv_ep->dir == USB_DIR_OUT) { - priv_ep->ep_sts_pending |= ep_sts_reg; - } else if (ep_sts_reg & EP_STS_IOT) { - cdns3_transfer_completed(priv_dev, priv_ep); - } - } - - /* - * MD_EXIT interrupt sets when stream capable endpoint exits - * from MOVE DATA state of Bulk IN/OUT stream protocol state machine - */ - if (priv_ep->dir == USB_DIR_OUT && (ep_sts_reg & EP_STS_MD_EXIT) && - (priv_ep->ep_sts_pending & EP_STS_IOT) && priv_ep->use_streams) { - priv_ep->ep_sts_pending = 0; - cdns3_transfer_completed(priv_dev, priv_ep); - } - - /* - * WA2: this condition should only be meet when - * priv_ep->flags & EP_QUIRK_EXTRA_BUF_DET or - * priv_ep->flags & EP_QUIRK_EXTRA_BUF_EN. - * In other cases this interrupt will be disabled. - */ - if (ep_sts_reg & EP_STS_DESCMIS && priv_dev->dev_ver < DEV_VER_V2 && - !(priv_ep->flags & EP_STALLED)) - cdns3_wa2_descmissing_packet(priv_ep); - - return 0; -} - -static void cdns3_disconnect_gadget(struct cdns3_device *priv_dev) -{ - if (priv_dev->gadget_driver && priv_dev->gadget_driver->disconnect) - priv_dev->gadget_driver->disconnect(&priv_dev->gadget); -} - -/** - * cdns3_check_usb_interrupt_proceed - Processes interrupt related to device - * @priv_dev: extended gadget object - * @usb_ists: bitmap representation of device's reported interrupts - * (usb_ists register value) - */ -static void cdns3_check_usb_interrupt_proceed(struct cdns3_device *priv_dev, - u32 usb_ists) -__must_hold(&priv_dev->lock) -{ - int speed = 0; - - trace_cdns3_usb_irq(priv_dev, usb_ists); - if (usb_ists & USB_ISTS_L1ENTI) { - /* - * WORKAROUND: CDNS3 controller has issue with hardware resuming - * from L1. To fix it, if any DMA transfer is pending driver - * must starts driving resume signal immediately. - */ - if (readl(&priv_dev->regs->drbl)) - __cdns3_gadget_wakeup(priv_dev); - } - - /* Connection detected */ - if (usb_ists & (USB_ISTS_CON2I | USB_ISTS_CONI)) { - speed = cdns3_get_speed(priv_dev); - priv_dev->gadget.speed = speed; - usb_gadget_set_state(&priv_dev->gadget, USB_STATE_POWERED); - cdns3_ep0_config(priv_dev); - } - - /* Disconnection detected */ - if (usb_ists & (USB_ISTS_DIS2I | USB_ISTS_DISI)) { - spin_unlock(&priv_dev->lock); - cdns3_disconnect_gadget(priv_dev); - spin_lock(&priv_dev->lock); - priv_dev->gadget.speed = USB_SPEED_UNKNOWN; - usb_gadget_set_state(&priv_dev->gadget, USB_STATE_NOTATTACHED); - cdns3_hw_reset_eps_config(priv_dev); - } - - if (usb_ists & (USB_ISTS_L2ENTI | USB_ISTS_U3ENTI)) { - if (priv_dev->gadget_driver && - priv_dev->gadget_driver->suspend) { - spin_unlock(&priv_dev->lock); - priv_dev->gadget_driver->suspend(&priv_dev->gadget); - spin_lock(&priv_dev->lock); - } - } - - if (usb_ists & (USB_ISTS_L2EXTI | USB_ISTS_U3EXTI)) { - if (priv_dev->gadget_driver && - priv_dev->gadget_driver->resume) { - spin_unlock(&priv_dev->lock); - priv_dev->gadget_driver->resume(&priv_dev->gadget); - spin_lock(&priv_dev->lock); - } - } - - /* reset*/ - if (usb_ists & (USB_ISTS_UWRESI | USB_ISTS_UHRESI | USB_ISTS_U2RESI)) { - if (priv_dev->gadget_driver) { - spin_unlock(&priv_dev->lock); - usb_gadget_udc_reset(&priv_dev->gadget, - priv_dev->gadget_driver); - spin_lock(&priv_dev->lock); - - /*read again to check the actual speed*/ - speed = cdns3_get_speed(priv_dev); - priv_dev->gadget.speed = speed; - cdns3_hw_reset_eps_config(priv_dev); - cdns3_ep0_config(priv_dev); - } - } -} - -/** - * cdns3_device_irq_handler- interrupt handler for device part of controller - * - * @irq: irq number for cdns3 core device - * @data: structure of cdns3 - * - * Returns IRQ_HANDLED or IRQ_NONE - */ -static irqreturn_t cdns3_device_irq_handler(int irq, void *data) -{ - struct cdns3_device *priv_dev = data; - struct cdns *cdns = dev_get_drvdata(priv_dev->dev); - irqreturn_t ret = IRQ_NONE; - u32 reg; - - if (cdns->in_lpm) - return ret; - - /* check USB device interrupt */ - reg = readl(&priv_dev->regs->usb_ists); - if (reg) { - /* After masking interrupts the new interrupts won't be - * reported in usb_ists/ep_ists. In order to not lose some - * of them driver disables only detected interrupts. - * They will be enabled ASAP after clearing source of - * interrupt. This an unusual behavior only applies to - * usb_ists register. - */ - reg = ~reg & readl(&priv_dev->regs->usb_ien); - /* mask deferred interrupt. */ - writel(reg, &priv_dev->regs->usb_ien); - ret = IRQ_WAKE_THREAD; - } - - /* check endpoint interrupt */ - reg = readl(&priv_dev->regs->ep_ists); - if (reg) { - writel(0, &priv_dev->regs->ep_ien); - ret = IRQ_WAKE_THREAD; - } - - return ret; -} - -/** - * cdns3_device_thread_irq_handler- interrupt handler for device part - * of controller - * - * @irq: irq number for cdns3 core device - * @data: structure of cdns3 - * - * Returns IRQ_HANDLED or IRQ_NONE - */ -static irqreturn_t cdns3_device_thread_irq_handler(int irq, void *data) -{ - struct cdns3_device *priv_dev = data; - irqreturn_t ret = IRQ_NONE; - unsigned long flags; - unsigned int bit; - unsigned long reg; - - spin_lock_irqsave(&priv_dev->lock, flags); - - reg = readl(&priv_dev->regs->usb_ists); - if (reg) { - writel(reg, &priv_dev->regs->usb_ists); - writel(USB_IEN_INIT, &priv_dev->regs->usb_ien); - cdns3_check_usb_interrupt_proceed(priv_dev, reg); - ret = IRQ_HANDLED; - } - - reg = readl(&priv_dev->regs->ep_ists); - - /* handle default endpoint OUT */ - if (reg & EP_ISTS_EP_OUT0) { - cdns3_check_ep0_interrupt_proceed(priv_dev, USB_DIR_OUT); - ret = IRQ_HANDLED; - } - - /* handle default endpoint IN */ - if (reg & EP_ISTS_EP_IN0) { - cdns3_check_ep0_interrupt_proceed(priv_dev, USB_DIR_IN); - ret = IRQ_HANDLED; - } - - /* check if interrupt from non default endpoint, if no exit */ - reg &= ~(EP_ISTS_EP_OUT0 | EP_ISTS_EP_IN0); - if (!reg) - goto irqend; - - for_each_set_bit(bit, ®, - sizeof(u32) * BITS_PER_BYTE) { - cdns3_check_ep_interrupt_proceed(priv_dev->eps[bit]); - ret = IRQ_HANDLED; - } - - if (priv_dev->dev_ver < DEV_VER_V2 && priv_dev->using_streams) - cdns3_wa2_check_outq_status(priv_dev); - -irqend: - writel(~0, &priv_dev->regs->ep_ien); - spin_unlock_irqrestore(&priv_dev->lock, flags); - - return ret; -} - -/** - * cdns3_ep_onchip_buffer_reserve - Try to reserve onchip buf for EP - * - * The real reservation will occur during write to EP_CFG register, - * this function is used to check if the 'size' reservation is allowed. - * - * @priv_dev: extended gadget object - * @size: the size (KB) for EP would like to allocate - * @is_in: endpoint direction - * - * Return 0 if the required size can met or negative value on failure - */ -static int cdns3_ep_onchip_buffer_reserve(struct cdns3_device *priv_dev, - int size, int is_in) -{ - int remained; - - /* 2KB are reserved for EP0*/ - remained = priv_dev->onchip_buffers - priv_dev->onchip_used_size - 2; - - if (is_in) { - if (remained < size) - return -EPERM; - - priv_dev->onchip_used_size += size; - } else { - int required; - - /** - * ALL OUT EPs are shared the same chunk onchip memory, so - * driver checks if it already has assigned enough buffers - */ - if (priv_dev->out_mem_is_allocated >= size) - return 0; - - required = size - priv_dev->out_mem_is_allocated; - - if (required > remained) - return -EPERM; - - priv_dev->out_mem_is_allocated += required; - priv_dev->onchip_used_size += required; - } - - return 0; -} - -static void cdns3_configure_dmult(struct cdns3_device *priv_dev, - struct cdns3_endpoint *priv_ep) -{ - struct cdns3_usb_regs __iomem *regs = priv_dev->regs; - - /* For dev_ver > DEV_VER_V2 DMULT is configured per endpoint */ - if (priv_dev->dev_ver <= DEV_VER_V2) - writel(USB_CONF_DMULT, ®s->usb_conf); - - if (priv_dev->dev_ver == DEV_VER_V2) - writel(USB_CONF2_EN_TDL_TRB, ®s->usb_conf2); - - if (priv_dev->dev_ver >= DEV_VER_V3 && priv_ep) { - u32 mask; - - if (priv_ep->dir) - mask = BIT(priv_ep->num + 16); - else - mask = BIT(priv_ep->num); - - if (priv_ep->type != USB_ENDPOINT_XFER_ISOC) { - cdns3_set_register_bit(®s->tdl_from_trb, mask); - cdns3_set_register_bit(®s->tdl_beh, mask); - cdns3_set_register_bit(®s->tdl_beh2, mask); - cdns3_set_register_bit(®s->dma_adv_td, mask); - } - - if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && !priv_ep->dir) - cdns3_set_register_bit(®s->tdl_from_trb, mask); - - cdns3_set_register_bit(®s->dtrans, mask); - } -} - -/** - * cdns3_ep_config Configure hardware endpoint - * @priv_ep: extended endpoint object - * @enable: set EP_CFG_ENABLE bit in ep_cfg register. - */ -int cdns3_ep_config(struct cdns3_endpoint *priv_ep, bool enable) -{ - bool is_iso_ep = (priv_ep->type == USB_ENDPOINT_XFER_ISOC); - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - u32 bEndpointAddress = priv_ep->num | priv_ep->dir; - u32 max_packet_size = 0; - u8 maxburst = 0; - u32 ep_cfg = 0; - u8 buffering; - u8 mult = 0; - int ret; - - buffering = CDNS3_EP_BUF_SIZE - 1; - - cdns3_configure_dmult(priv_dev, priv_ep); - - switch (priv_ep->type) { - case USB_ENDPOINT_XFER_INT: - ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_INT); - - if ((priv_dev->dev_ver == DEV_VER_V2 && !priv_ep->dir) || - priv_dev->dev_ver > DEV_VER_V2) - ep_cfg |= EP_CFG_TDL_CHK; - break; - case USB_ENDPOINT_XFER_BULK: - ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_BULK); - - if ((priv_dev->dev_ver == DEV_VER_V2 && !priv_ep->dir) || - priv_dev->dev_ver > DEV_VER_V2) - ep_cfg |= EP_CFG_TDL_CHK; - break; - default: - ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_ISOC); - mult = CDNS3_EP_ISO_HS_MULT - 1; - buffering = mult + 1; - } - - switch (priv_dev->gadget.speed) { - case USB_SPEED_FULL: - max_packet_size = is_iso_ep ? 1023 : 64; - break; - case USB_SPEED_HIGH: - max_packet_size = is_iso_ep ? 1024 : 512; - break; - case USB_SPEED_SUPER: - /* It's limitation that driver assumes in driver. */ - mult = 0; - max_packet_size = 1024; - if (priv_ep->type == USB_ENDPOINT_XFER_ISOC) { - maxburst = CDNS3_EP_ISO_SS_BURST - 1; - buffering = (mult + 1) * - (maxburst + 1); - - if (priv_ep->interval > 1) - buffering++; - } else { - maxburst = CDNS3_EP_BUF_SIZE - 1; - } - break; - default: - /* all other speed are not supported */ - return -EINVAL; - } - - if (max_packet_size == 1024) - priv_ep->trb_burst_size = 128; - else if (max_packet_size >= 512) - priv_ep->trb_burst_size = 64; - else - priv_ep->trb_burst_size = 16; - - /* onchip buffer is only allocated before configuration */ - if (!priv_dev->hw_configured_flag) { - ret = cdns3_ep_onchip_buffer_reserve(priv_dev, buffering + 1, - !!priv_ep->dir); - if (ret) { - dev_err(priv_dev->dev, "onchip mem is full, ep is invalid\n"); - return ret; - } - } - - if (enable) - ep_cfg |= EP_CFG_ENABLE; - - if (priv_ep->use_streams && priv_dev->gadget.speed >= USB_SPEED_SUPER) { - if (priv_dev->dev_ver >= DEV_VER_V3) { - u32 mask = BIT(priv_ep->num + (priv_ep->dir ? 16 : 0)); - - /* - * Stream capable endpoints are handled by using ep_tdl - * register. Other endpoints use TDL from TRB feature. - */ - cdns3_clear_register_bit(&priv_dev->regs->tdl_from_trb, - mask); - } - - /* Enable Stream Bit TDL chk and SID chk */ - ep_cfg |= EP_CFG_STREAM_EN | EP_CFG_TDL_CHK | EP_CFG_SID_CHK; - } - - ep_cfg |= EP_CFG_MAXPKTSIZE(max_packet_size) | - EP_CFG_MULT(mult) | - EP_CFG_BUFFERING(buffering) | - EP_CFG_MAXBURST(maxburst); - - cdns3_select_ep(priv_dev, bEndpointAddress); - writel(ep_cfg, &priv_dev->regs->ep_cfg); - priv_ep->flags |= EP_CONFIGURED; - - dev_dbg(priv_dev->dev, "Configure %s: with val %08x\n", - priv_ep->name, ep_cfg); - - return 0; -} - -/* Find correct direction for HW endpoint according to description */ -static int cdns3_ep_dir_is_correct(struct usb_endpoint_descriptor *desc, - struct cdns3_endpoint *priv_ep) -{ - return (priv_ep->endpoint.caps.dir_in && usb_endpoint_dir_in(desc)) || - (priv_ep->endpoint.caps.dir_out && usb_endpoint_dir_out(desc)); -} - -static struct -cdns3_endpoint *cdns3_find_available_ep(struct cdns3_device *priv_dev, - struct usb_endpoint_descriptor *desc) -{ - struct usb_ep *ep; - struct cdns3_endpoint *priv_ep; - - list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) { - unsigned long num; - int ret; - /* ep name pattern likes epXin or epXout */ - char c[2] = {ep->name[2], '\0'}; - - ret = kstrtoul(c, 10, &num); - if (ret) - return ERR_PTR(ret); - - priv_ep = ep_to_cdns3_ep(ep); - if (cdns3_ep_dir_is_correct(desc, priv_ep)) { - if (!(priv_ep->flags & EP_CLAIMED)) { - priv_ep->num = num; - return priv_ep; - } - } - } - - return ERR_PTR(-ENOENT); -} - -/* - * Cadence IP has one limitation that all endpoints must be configured - * (Type & MaxPacketSize) before setting configuration through hardware - * register, it means we can't change endpoints configuration after - * set_configuration. - * - * This function set EP_CLAIMED flag which is added when the gadget driver - * uses usb_ep_autoconfig to configure specific endpoint; - * When the udc driver receives set_configurion request, - * it goes through all claimed endpoints, and configure all endpoints - * accordingly. - * - * At usb_ep_ops.enable/disable, we only enable and disable endpoint through - * ep_cfg register which can be changed after set_configuration, and do - * some software operation accordingly. - */ -static struct -usb_ep *cdns3_gadget_match_ep(struct usb_gadget *gadget, - struct usb_endpoint_descriptor *desc, - struct usb_ss_ep_comp_descriptor *comp_desc) -{ - struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); - struct cdns3_endpoint *priv_ep; - unsigned long flags; - - priv_ep = cdns3_find_available_ep(priv_dev, desc); - if (IS_ERR(priv_ep)) { - dev_err(priv_dev->dev, "no available ep\n"); - return NULL; - } - - dev_dbg(priv_dev->dev, "match endpoint: %s\n", priv_ep->name); - - spin_lock_irqsave(&priv_dev->lock, flags); - priv_ep->endpoint.desc = desc; - priv_ep->dir = usb_endpoint_dir_in(desc) ? USB_DIR_IN : USB_DIR_OUT; - priv_ep->type = usb_endpoint_type(desc); - priv_ep->flags |= EP_CLAIMED; - priv_ep->interval = desc->bInterval ? BIT(desc->bInterval - 1) : 0; - - spin_unlock_irqrestore(&priv_dev->lock, flags); - return &priv_ep->endpoint; -} - -/** - * cdns3_gadget_ep_alloc_request Allocates request - * @ep: endpoint object associated with request - * @gfp_flags: gfp flags - * - * Returns allocated request address, NULL on allocation error - */ -struct usb_request *cdns3_gadget_ep_alloc_request(struct usb_ep *ep, - gfp_t gfp_flags) -{ - struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep); - struct cdns3_request *priv_req; - - priv_req = kzalloc(sizeof(*priv_req), gfp_flags); - if (!priv_req) - return NULL; - - priv_req->priv_ep = priv_ep; - - trace_cdns3_alloc_request(priv_req); - return &priv_req->request; -} - -/** - * cdns3_gadget_ep_free_request Free memory occupied by request - * @ep: endpoint object associated with request - * @request: request to free memory - */ -void cdns3_gadget_ep_free_request(struct usb_ep *ep, - struct usb_request *request) -{ - struct cdns3_request *priv_req = to_cdns3_request(request); - - if (priv_req->aligned_buf) - priv_req->aligned_buf->in_use = 0; - - trace_cdns3_free_request(priv_req); - kfree(priv_req); -} - -/** - * cdns3_gadget_ep_enable Enable endpoint - * @ep: endpoint object - * @desc: endpoint descriptor - * - * Returns 0 on success, error code elsewhere - */ -static int cdns3_gadget_ep_enable(struct usb_ep *ep, - const struct usb_endpoint_descriptor *desc) -{ - struct cdns3_endpoint *priv_ep; - struct cdns3_device *priv_dev; - const struct usb_ss_ep_comp_descriptor *comp_desc; - u32 reg = EP_STS_EN_TRBERREN; - u32 bEndpointAddress; - unsigned long flags; - int enable = 1; - int ret = 0; - int val; - - priv_ep = ep_to_cdns3_ep(ep); - priv_dev = priv_ep->cdns3_dev; - comp_desc = priv_ep->endpoint.comp_desc; - - if (!ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT) { - dev_dbg(priv_dev->dev, "usbss: invalid parameters\n"); - return -EINVAL; - } - - if (!desc->wMaxPacketSize) { - dev_err(priv_dev->dev, "usbss: missing wMaxPacketSize\n"); - return -EINVAL; - } - - if (dev_WARN_ONCE(priv_dev->dev, priv_ep->flags & EP_ENABLED, - "%s is already enabled\n", priv_ep->name)) - return 0; - - spin_lock_irqsave(&priv_dev->lock, flags); - - priv_ep->endpoint.desc = desc; - priv_ep->type = usb_endpoint_type(desc); - priv_ep->interval = desc->bInterval ? BIT(desc->bInterval - 1) : 0; - - if (priv_ep->interval > ISO_MAX_INTERVAL && - priv_ep->type == USB_ENDPOINT_XFER_ISOC) { - dev_err(priv_dev->dev, "Driver is limited to %d period\n", - ISO_MAX_INTERVAL); - - ret = -EINVAL; - goto exit; - } - - bEndpointAddress = priv_ep->num | priv_ep->dir; - cdns3_select_ep(priv_dev, bEndpointAddress); - - /* - * For some versions of controller at some point during ISO OUT traffic - * DMA reads Transfer Ring for the EP which has never got doorbell. - * This issue was detected only on simulation, but to avoid this issue - * driver add protection against it. To fix it driver enable ISO OUT - * endpoint before setting DRBL. This special treatment of ISO OUT - * endpoints are recommended by controller specification. - */ - if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && !priv_ep->dir) - enable = 0; - - if (usb_ss_max_streams(comp_desc) && usb_endpoint_xfer_bulk(desc)) { - /* - * Enable stream support (SS mode) related interrupts - * in EP_STS_EN Register - */ - if (priv_dev->gadget.speed >= USB_SPEED_SUPER) { - reg |= EP_STS_EN_IOTEN | EP_STS_EN_PRIMEEEN | - EP_STS_EN_SIDERREN | EP_STS_EN_MD_EXITEN | - EP_STS_EN_STREAMREN; - priv_ep->use_streams = true; - ret = cdns3_ep_config(priv_ep, enable); - priv_dev->using_streams |= true; - } - } else { - ret = cdns3_ep_config(priv_ep, enable); - } - - if (ret) - goto exit; - - ret = cdns3_allocate_trb_pool(priv_ep); - if (ret) - goto exit; - - bEndpointAddress = priv_ep->num | priv_ep->dir; - cdns3_select_ep(priv_dev, bEndpointAddress); - - trace_cdns3_gadget_ep_enable(priv_ep); - - writel(EP_CMD_EPRST, &priv_dev->regs->ep_cmd); - - ret = readl_poll_timeout_atomic(&priv_dev->regs->ep_cmd, val, - !(val & (EP_CMD_CSTALL | EP_CMD_EPRST)), - 1, 1000); - - if (unlikely(ret)) { - cdns3_free_trb_pool(priv_ep); - ret = -EINVAL; - goto exit; - } - - /* enable interrupt for selected endpoint */ - cdns3_set_register_bit(&priv_dev->regs->ep_ien, - BIT(cdns3_ep_addr_to_index(bEndpointAddress))); - - if (priv_dev->dev_ver < DEV_VER_V2) - cdns3_wa2_enable_detection(priv_dev, priv_ep, reg); - - writel(reg, &priv_dev->regs->ep_sts_en); - - ep->desc = desc; - priv_ep->flags &= ~(EP_PENDING_REQUEST | EP_STALLED | EP_STALL_PENDING | - EP_QUIRK_ISO_OUT_EN | EP_QUIRK_EXTRA_BUF_EN); - priv_ep->flags |= EP_ENABLED | EP_UPDATE_EP_TRBADDR; - priv_ep->wa1_set = 0; - priv_ep->enqueue = 0; - priv_ep->dequeue = 0; - reg = readl(&priv_dev->regs->ep_sts); - priv_ep->pcs = !!EP_STS_CCS(reg); - priv_ep->ccs = !!EP_STS_CCS(reg); - /* one TRB is reserved for link TRB used in DMULT mode*/ - priv_ep->free_trbs = priv_ep->num_trbs - 1; -exit: - spin_unlock_irqrestore(&priv_dev->lock, flags); - - return ret; -} - -/** - * cdns3_gadget_ep_disable Disable endpoint - * @ep: endpoint object - * - * Returns 0 on success, error code elsewhere - */ -static int cdns3_gadget_ep_disable(struct usb_ep *ep) -{ - struct cdns3_endpoint *priv_ep; - struct cdns3_request *priv_req; - struct cdns3_device *priv_dev; - struct usb_request *request; - unsigned long flags; - int ret = 0; - u32 ep_cfg; - int val; - - if (!ep) { - pr_err("usbss: invalid parameters\n"); - return -EINVAL; - } - - priv_ep = ep_to_cdns3_ep(ep); - priv_dev = priv_ep->cdns3_dev; - - if (dev_WARN_ONCE(priv_dev->dev, !(priv_ep->flags & EP_ENABLED), - "%s is already disabled\n", priv_ep->name)) - return 0; - - spin_lock_irqsave(&priv_dev->lock, flags); - - trace_cdns3_gadget_ep_disable(priv_ep); - - cdns3_select_ep(priv_dev, ep->desc->bEndpointAddress); - - ep_cfg = readl(&priv_dev->regs->ep_cfg); - ep_cfg &= ~EP_CFG_ENABLE; - writel(ep_cfg, &priv_dev->regs->ep_cfg); - - /** - * Driver needs some time before resetting endpoint. - * It need waits for clearing DBUSY bit or for timeout expired. - * 10us is enough time for controller to stop transfer. - */ - readl_poll_timeout_atomic(&priv_dev->regs->ep_sts, val, - !(val & EP_STS_DBUSY), 1, 10); - writel(EP_CMD_EPRST, &priv_dev->regs->ep_cmd); - - readl_poll_timeout_atomic(&priv_dev->regs->ep_cmd, val, - !(val & (EP_CMD_CSTALL | EP_CMD_EPRST)), - 1, 1000); - if (unlikely(ret)) - dev_err(priv_dev->dev, "Timeout: %s resetting failed.\n", - priv_ep->name); - - while (!list_empty(&priv_ep->pending_req_list)) { - request = cdns3_next_request(&priv_ep->pending_req_list); - - cdns3_gadget_giveback(priv_ep, to_cdns3_request(request), - -ESHUTDOWN); - } - - while (!list_empty(&priv_ep->wa2_descmiss_req_list)) { - priv_req = cdns3_next_priv_request(&priv_ep->wa2_descmiss_req_list); - - kfree(priv_req->request.buf); - cdns3_gadget_ep_free_request(&priv_ep->endpoint, - &priv_req->request); - list_del_init(&priv_req->list); - --priv_ep->wa2_counter; - } - - while (!list_empty(&priv_ep->deferred_req_list)) { - request = cdns3_next_request(&priv_ep->deferred_req_list); - - cdns3_gadget_giveback(priv_ep, to_cdns3_request(request), - -ESHUTDOWN); - } - - priv_ep->descmis_req = NULL; - - ep->desc = NULL; - priv_ep->flags &= ~EP_ENABLED; - priv_ep->use_streams = false; - - spin_unlock_irqrestore(&priv_dev->lock, flags); - - return ret; -} - -/** - * cdns3_gadget_ep_queue Transfer data on endpoint - * @ep: endpoint object - * @request: request object - * @gfp_flags: gfp flags - * - * Returns 0 on success, error code elsewhere - */ -static int __cdns3_gadget_ep_queue(struct usb_ep *ep, - struct usb_request *request, - gfp_t gfp_flags) -{ - struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep); - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - struct cdns3_request *priv_req; - int ret = 0; - - request->actual = 0; - request->status = -EINPROGRESS; - priv_req = to_cdns3_request(request); - trace_cdns3_ep_queue(priv_req); - - if (priv_dev->dev_ver < DEV_VER_V2) { - ret = cdns3_wa2_gadget_ep_queue(priv_dev, priv_ep, - priv_req); - - if (ret == EINPROGRESS) - return 0; - } - - ret = cdns3_prepare_aligned_request_buf(priv_req); - if (ret < 0) - return ret; - - ret = usb_gadget_map_request_by_dev(priv_dev->sysdev, request, - usb_endpoint_dir_in(ep->desc)); - if (ret) - return ret; - - list_add_tail(&request->list, &priv_ep->deferred_req_list); - - /* - * For stream capable endpoint if prime irq flag is set then only start - * request. - * If hardware endpoint configuration has not been set yet then - * just queue request in deferred list. Transfer will be started in - * cdns3_set_hw_configuration. - */ - if (!request->stream_id) { - if (priv_dev->hw_configured_flag && - !(priv_ep->flags & EP_STALLED) && - !(priv_ep->flags & EP_STALL_PENDING)) - cdns3_start_all_request(priv_dev, priv_ep); - } else { - if (priv_dev->hw_configured_flag && priv_ep->prime_flag) - cdns3_start_all_request(priv_dev, priv_ep); - } - - return 0; -} - -static int cdns3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, - gfp_t gfp_flags) -{ - struct usb_request *zlp_request; - struct cdns3_endpoint *priv_ep; - struct cdns3_device *priv_dev; - unsigned long flags; - int ret; - - if (!request || !ep) - return -EINVAL; - - priv_ep = ep_to_cdns3_ep(ep); - priv_dev = priv_ep->cdns3_dev; - - spin_lock_irqsave(&priv_dev->lock, flags); - - ret = __cdns3_gadget_ep_queue(ep, request, gfp_flags); - - if (ret == 0 && request->zero && request->length && - (request->length % ep->maxpacket == 0)) { - struct cdns3_request *priv_req; - - zlp_request = cdns3_gadget_ep_alloc_request(ep, GFP_ATOMIC); - zlp_request->buf = priv_dev->zlp_buf; - zlp_request->length = 0; - - priv_req = to_cdns3_request(zlp_request); - priv_req->flags |= REQUEST_ZLP; - - dev_dbg(priv_dev->dev, "Queuing ZLP for endpoint: %s\n", - priv_ep->name); - ret = __cdns3_gadget_ep_queue(ep, zlp_request, gfp_flags); - } - - spin_unlock_irqrestore(&priv_dev->lock, flags); - return ret; -} - -/** - * cdns3_gadget_ep_dequeue Remove request from transfer queue - * @ep: endpoint object associated with request - * @request: request object - * - * Returns 0 on success, error code elsewhere - */ -int cdns3_gadget_ep_dequeue(struct usb_ep *ep, - struct usb_request *request) -{ - struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep); - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - struct usb_request *req, *req_temp; - struct cdns3_request *priv_req; - struct cdns3_trb *link_trb; - u8 req_on_hw_ring = 0; - unsigned long flags; - int ret = 0; - - if (!ep || !request || !ep->desc) - return -EINVAL; - - spin_lock_irqsave(&priv_dev->lock, flags); - - priv_req = to_cdns3_request(request); - - trace_cdns3_ep_dequeue(priv_req); - - cdns3_select_ep(priv_dev, ep->desc->bEndpointAddress); - - list_for_each_entry_safe(req, req_temp, &priv_ep->pending_req_list, - list) { - if (request == req) { - req_on_hw_ring = 1; - goto found; - } - } - - list_for_each_entry_safe(req, req_temp, &priv_ep->deferred_req_list, - list) { - if (request == req) - goto found; - } - - goto not_found; - -found: - link_trb = priv_req->trb; - - /* Update ring only if removed request is on pending_req_list list */ - if (req_on_hw_ring && link_trb) { - link_trb->buffer = cpu_to_le32(TRB_BUFFER(priv_ep->trb_pool_dma + - ((priv_req->end_trb + 1) * TRB_SIZE))); - link_trb->control = cpu_to_le32((le32_to_cpu(link_trb->control) & TRB_CYCLE) | - TRB_TYPE(TRB_LINK) | TRB_CHAIN); - - if (priv_ep->wa1_trb == priv_req->trb) - cdns3_wa1_restore_cycle_bit(priv_ep); - } - - cdns3_gadget_giveback(priv_ep, priv_req, -ECONNRESET); - -not_found: - spin_unlock_irqrestore(&priv_dev->lock, flags); - return ret; -} - -/** - * __cdns3_gadget_ep_set_halt Sets stall on selected endpoint - * Should be called after acquiring spin_lock and selecting ep - * @priv_ep: endpoint object to set stall on. - */ -void __cdns3_gadget_ep_set_halt(struct cdns3_endpoint *priv_ep) -{ - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - - trace_cdns3_halt(priv_ep, 1, 0); - - if (!(priv_ep->flags & EP_STALLED)) { - u32 ep_sts_reg = readl(&priv_dev->regs->ep_sts); - - if (!(ep_sts_reg & EP_STS_DBUSY)) - cdns3_ep_stall_flush(priv_ep); - else - priv_ep->flags |= EP_STALL_PENDING; - } -} - -/** - * __cdns3_gadget_ep_clear_halt Clears stall on selected endpoint - * Should be called after acquiring spin_lock and selecting ep - * @priv_ep: endpoint object to clear stall on - */ -int __cdns3_gadget_ep_clear_halt(struct cdns3_endpoint *priv_ep) -{ - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - struct usb_request *request; - struct cdns3_request *priv_req; - struct cdns3_trb *trb = NULL; - int ret; - int val; - - trace_cdns3_halt(priv_ep, 0, 0); - - request = cdns3_next_request(&priv_ep->pending_req_list); - if (request) { - priv_req = to_cdns3_request(request); - trb = priv_req->trb; - if (trb) - trb->control = trb->control ^ cpu_to_le32(TRB_CYCLE); - } - - writel(EP_CMD_CSTALL | EP_CMD_EPRST, &priv_dev->regs->ep_cmd); - - /* wait for EPRST cleared */ - ret = readl_poll_timeout_atomic(&priv_dev->regs->ep_cmd, val, - !(val & EP_CMD_EPRST), 1, 100); - if (ret) - return -EINVAL; - - priv_ep->flags &= ~(EP_STALLED | EP_STALL_PENDING); - - if (request) { - if (trb) - trb->control = trb->control ^ cpu_to_le32(TRB_CYCLE); - - cdns3_rearm_transfer(priv_ep, 1); - } - - cdns3_start_all_request(priv_dev, priv_ep); - return ret; -} - -/** - * cdns3_gadget_ep_set_halt Sets/clears stall on selected endpoint - * @ep: endpoint object to set/clear stall on - * @value: 1 for set stall, 0 for clear stall - * - * Returns 0 on success, error code elsewhere - */ -int cdns3_gadget_ep_set_halt(struct usb_ep *ep, int value) -{ - struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep); - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - unsigned long flags; - int ret = 0; - - if (!(priv_ep->flags & EP_ENABLED)) - return -EPERM; - - spin_lock_irqsave(&priv_dev->lock, flags); - - cdns3_select_ep(priv_dev, ep->desc->bEndpointAddress); - - if (!value) { - priv_ep->flags &= ~EP_WEDGE; - ret = __cdns3_gadget_ep_clear_halt(priv_ep); - } else { - __cdns3_gadget_ep_set_halt(priv_ep); - } - - spin_unlock_irqrestore(&priv_dev->lock, flags); - - return ret; -} - -extern const struct usb_ep_ops cdns3_gadget_ep0_ops; - -static const struct usb_ep_ops cdns3_gadget_ep_ops = { - .enable = cdns3_gadget_ep_enable, - .disable = cdns3_gadget_ep_disable, - .alloc_request = cdns3_gadget_ep_alloc_request, - .free_request = cdns3_gadget_ep_free_request, - .queue = cdns3_gadget_ep_queue, - .dequeue = cdns3_gadget_ep_dequeue, - .set_halt = cdns3_gadget_ep_set_halt, - .set_wedge = cdns3_gadget_ep_set_wedge, -}; - -/** - * cdns3_gadget_get_frame Returns number of actual ITP frame - * @gadget: gadget object - * - * Returns number of actual ITP frame - */ -static int cdns3_gadget_get_frame(struct usb_gadget *gadget) -{ - struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); - - return readl(&priv_dev->regs->usb_itpn); -} - -int __cdns3_gadget_wakeup(struct cdns3_device *priv_dev) -{ - enum usb_device_speed speed; - - speed = cdns3_get_speed(priv_dev); - - if (speed >= USB_SPEED_SUPER) - return 0; - - /* Start driving resume signaling to indicate remote wakeup. */ - writel(USB_CONF_LGO_L0, &priv_dev->regs->usb_conf); - - return 0; -} - -static int cdns3_gadget_wakeup(struct usb_gadget *gadget) -{ - struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); - unsigned long flags; - int ret = 0; - - spin_lock_irqsave(&priv_dev->lock, flags); - ret = __cdns3_gadget_wakeup(priv_dev); - spin_unlock_irqrestore(&priv_dev->lock, flags); - return ret; -} - -static int cdns3_gadget_set_selfpowered(struct usb_gadget *gadget, - int is_selfpowered) -{ - struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); - unsigned long flags; - - spin_lock_irqsave(&priv_dev->lock, flags); - priv_dev->is_selfpowered = !!is_selfpowered; - spin_unlock_irqrestore(&priv_dev->lock, flags); - return 0; -} - -static int cdns3_gadget_pullup(struct usb_gadget *gadget, int is_on) -{ - struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); - - if (is_on) { - writel(USB_CONF_DEVEN, &priv_dev->regs->usb_conf); - } else { - writel(~0, &priv_dev->regs->ep_ists); - writel(~0, &priv_dev->regs->usb_ists); - writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf); - } - - return 0; -} - -static void cdns3_gadget_config(struct cdns3_device *priv_dev) -{ - struct cdns3_usb_regs __iomem *regs = priv_dev->regs; - u32 reg; - - cdns3_ep0_config(priv_dev); - - /* enable interrupts for endpoint 0 (in and out) */ - writel(EP_IEN_EP_OUT0 | EP_IEN_EP_IN0, ®s->ep_ien); - - /* - * Driver needs to modify LFPS minimal U1 Exit time for DEV_VER_TI_V1 - * revision of controller. - */ - if (priv_dev->dev_ver == DEV_VER_TI_V1) { - reg = readl(®s->dbg_link1); - - reg &= ~DBG_LINK1_LFPS_MIN_GEN_U1_EXIT_MASK; - reg |= DBG_LINK1_LFPS_MIN_GEN_U1_EXIT(0x55) | - DBG_LINK1_LFPS_MIN_GEN_U1_EXIT_SET; - writel(reg, ®s->dbg_link1); - } - - /* - * By default some platforms has set protected access to memory. - * This cause problem with cache, so driver restore non-secure - * access to memory. - */ - reg = readl(®s->dma_axi_ctrl); - reg |= DMA_AXI_CTRL_MARPROT(DMA_AXI_CTRL_NON_SECURE) | - DMA_AXI_CTRL_MAWPROT(DMA_AXI_CTRL_NON_SECURE); - writel(reg, ®s->dma_axi_ctrl); - - /* enable generic interrupt*/ - writel(USB_IEN_INIT, ®s->usb_ien); - writel(USB_CONF_CLK2OFFDS | USB_CONF_L1DS, ®s->usb_conf); - /* keep Fast Access bit */ - writel(PUSB_PWR_FST_REG_ACCESS, &priv_dev->regs->usb_pwr); - - cdns3_configure_dmult(priv_dev, NULL); -} - -/** - * cdns3_gadget_udc_start Gadget start - * @gadget: gadget object - * @driver: driver which operates on this gadget - * - * Returns 0 on success, error code elsewhere - */ -static int cdns3_gadget_udc_start(struct usb_gadget *gadget, - struct usb_gadget_driver *driver) -{ - struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); - unsigned long flags; - enum usb_device_speed max_speed = driver->max_speed; - - spin_lock_irqsave(&priv_dev->lock, flags); - priv_dev->gadget_driver = driver; - - /* limit speed if necessary */ - max_speed = min(driver->max_speed, gadget->max_speed); - - switch (max_speed) { - case USB_SPEED_FULL: - writel(USB_CONF_SFORCE_FS, &priv_dev->regs->usb_conf); - writel(USB_CONF_USB3DIS, &priv_dev->regs->usb_conf); - break; - case USB_SPEED_HIGH: - writel(USB_CONF_USB3DIS, &priv_dev->regs->usb_conf); - break; - case USB_SPEED_SUPER: - break; - default: - dev_err(priv_dev->dev, - "invalid maximum_speed parameter %d\n", - max_speed); - fallthrough; - case USB_SPEED_UNKNOWN: - /* default to superspeed */ - max_speed = USB_SPEED_SUPER; - break; - } - - cdns3_gadget_config(priv_dev); - spin_unlock_irqrestore(&priv_dev->lock, flags); - return 0; -} - -/** - * cdns3_gadget_udc_stop Stops gadget - * @gadget: gadget object - * - * Returns 0 - */ -static int cdns3_gadget_udc_stop(struct usb_gadget *gadget) -{ - struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); - struct cdns3_endpoint *priv_ep; - u32 bEndpointAddress; - struct usb_ep *ep; - int val; - - priv_dev->gadget_driver = NULL; - - priv_dev->onchip_used_size = 0; - priv_dev->out_mem_is_allocated = 0; - priv_dev->gadget.speed = USB_SPEED_UNKNOWN; - - list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) { - priv_ep = ep_to_cdns3_ep(ep); - bEndpointAddress = priv_ep->num | priv_ep->dir; - cdns3_select_ep(priv_dev, bEndpointAddress); - writel(EP_CMD_EPRST, &priv_dev->regs->ep_cmd); - readl_poll_timeout_atomic(&priv_dev->regs->ep_cmd, val, - !(val & EP_CMD_EPRST), 1, 100); - - priv_ep->flags &= ~EP_CLAIMED; - } - - /* disable interrupt for device */ - writel(0, &priv_dev->regs->usb_ien); - writel(0, &priv_dev->regs->usb_pwr); - writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf); - - return 0; -} - -static const struct usb_gadget_ops cdns3_gadget_ops = { - .get_frame = cdns3_gadget_get_frame, - .wakeup = cdns3_gadget_wakeup, - .set_selfpowered = cdns3_gadget_set_selfpowered, - .pullup = cdns3_gadget_pullup, - .udc_start = cdns3_gadget_udc_start, - .udc_stop = cdns3_gadget_udc_stop, - .match_ep = cdns3_gadget_match_ep, -}; - -static void cdns3_free_all_eps(struct cdns3_device *priv_dev) -{ - int i; - - /* ep0 OUT point to ep0 IN. */ - priv_dev->eps[16] = NULL; - - for (i = 0; i < CDNS3_ENDPOINTS_MAX_COUNT; i++) - if (priv_dev->eps[i]) { - cdns3_free_trb_pool(priv_dev->eps[i]); - devm_kfree(priv_dev->dev, priv_dev->eps[i]); - } -} - -/** - * cdns3_init_eps Initializes software endpoints of gadget - * @priv_dev: extended gadget object - * - * Returns 0 on success, error code elsewhere - */ -static int cdns3_init_eps(struct cdns3_device *priv_dev) -{ - u32 ep_enabled_reg, iso_ep_reg; - struct cdns3_endpoint *priv_ep; - int ep_dir, ep_number; - u32 ep_mask; - int ret = 0; - int i; - - /* Read it from USB_CAP3 to USB_CAP5 */ - ep_enabled_reg = readl(&priv_dev->regs->usb_cap3); - iso_ep_reg = readl(&priv_dev->regs->usb_cap4); - - dev_dbg(priv_dev->dev, "Initializing non-zero endpoints\n"); - - for (i = 0; i < CDNS3_ENDPOINTS_MAX_COUNT; i++) { - ep_dir = i >> 4; /* i div 16 */ - ep_number = i & 0xF; /* i % 16 */ - ep_mask = BIT(i); - - if (!(ep_enabled_reg & ep_mask)) - continue; - - if (ep_dir && !ep_number) { - priv_dev->eps[i] = priv_dev->eps[0]; - continue; - } - - priv_ep = devm_kzalloc(priv_dev->dev, sizeof(*priv_ep), - GFP_KERNEL); - if (!priv_ep) - goto err; - - /* set parent of endpoint object */ - priv_ep->cdns3_dev = priv_dev; - priv_dev->eps[i] = priv_ep; - priv_ep->num = ep_number; - priv_ep->dir = ep_dir ? USB_DIR_IN : USB_DIR_OUT; - - if (!ep_number) { - ret = cdns3_init_ep0(priv_dev, priv_ep); - if (ret) { - dev_err(priv_dev->dev, "Failed to init ep0\n"); - goto err; - } - } else { - snprintf(priv_ep->name, sizeof(priv_ep->name), "ep%d%s", - ep_number, !!ep_dir ? "in" : "out"); - priv_ep->endpoint.name = priv_ep->name; - - usb_ep_set_maxpacket_limit(&priv_ep->endpoint, - CDNS3_EP_MAX_PACKET_LIMIT); - priv_ep->endpoint.max_streams = CDNS3_EP_MAX_STREAMS; - priv_ep->endpoint.ops = &cdns3_gadget_ep_ops; - if (ep_dir) - priv_ep->endpoint.caps.dir_in = 1; - else - priv_ep->endpoint.caps.dir_out = 1; - - if (iso_ep_reg & ep_mask) - priv_ep->endpoint.caps.type_iso = 1; - - priv_ep->endpoint.caps.type_bulk = 1; - priv_ep->endpoint.caps.type_int = 1; - - list_add_tail(&priv_ep->endpoint.ep_list, - &priv_dev->gadget.ep_list); - } - - priv_ep->flags = 0; - - dev_dbg(priv_dev->dev, "Initialized %s support: %s %s\n", - priv_ep->name, - priv_ep->endpoint.caps.type_bulk ? "BULK, INT" : "", - priv_ep->endpoint.caps.type_iso ? "ISO" : ""); - - INIT_LIST_HEAD(&priv_ep->pending_req_list); - INIT_LIST_HEAD(&priv_ep->deferred_req_list); - INIT_LIST_HEAD(&priv_ep->wa2_descmiss_req_list); - } - - return 0; -err: - cdns3_free_all_eps(priv_dev); - return -ENOMEM; -} - -static void cdns3_gadget_release(struct device *dev) -{ - struct cdns3_device *priv_dev = container_of(dev, - struct cdns3_device, gadget.dev); - - kfree(priv_dev); -} - -static void cdns3_gadget_exit(struct cdns *cdns) -{ - struct cdns3_device *priv_dev; - - priv_dev = cdns->gadget_dev; - - - pm_runtime_mark_last_busy(cdns->dev); - pm_runtime_put_autosuspend(cdns->dev); - - usb_del_gadget(&priv_dev->gadget); - devm_free_irq(cdns->dev, cdns->dev_irq, priv_dev); - - cdns3_free_all_eps(priv_dev); - - while (!list_empty(&priv_dev->aligned_buf_list)) { - struct cdns3_aligned_buf *buf; - - buf = cdns3_next_align_buf(&priv_dev->aligned_buf_list); - dma_free_coherent(priv_dev->sysdev, buf->size, - buf->buf, - buf->dma); - - list_del(&buf->list); - kfree(buf); - } - - dma_free_coherent(priv_dev->sysdev, 8, priv_dev->setup_buf, - priv_dev->setup_dma); - - kfree(priv_dev->zlp_buf); - usb_put_gadget(&priv_dev->gadget); - cdns->gadget_dev = NULL; - cdns_drd_gadget_off(cdns); -} - -static int cdns3_gadget_start(struct cdns *cdns) -{ - struct cdns3_device *priv_dev; - u32 max_speed; - int ret; - - priv_dev = kzalloc(sizeof(*priv_dev), GFP_KERNEL); - if (!priv_dev) - return -ENOMEM; - - usb_initialize_gadget(cdns->dev, &priv_dev->gadget, - cdns3_gadget_release); - cdns->gadget_dev = priv_dev; - priv_dev->sysdev = cdns->dev; - priv_dev->dev = cdns->dev; - priv_dev->regs = cdns->dev_regs; - - device_property_read_u16(priv_dev->dev, "cdns,on-chip-buff-size", - &priv_dev->onchip_buffers); - - if (priv_dev->onchip_buffers <= 0) { - u32 reg = readl(&priv_dev->regs->usb_cap2); - - priv_dev->onchip_buffers = USB_CAP2_ACTUAL_MEM_SIZE(reg); - } - - if (!priv_dev->onchip_buffers) - priv_dev->onchip_buffers = 256; - - max_speed = usb_get_maximum_speed(cdns->dev); - - /* Check the maximum_speed parameter */ - switch (max_speed) { - case USB_SPEED_FULL: - case USB_SPEED_HIGH: - case USB_SPEED_SUPER: - break; - default: - dev_err(cdns->dev, "invalid maximum_speed parameter %d\n", - max_speed); - fallthrough; - case USB_SPEED_UNKNOWN: - /* default to superspeed */ - max_speed = USB_SPEED_SUPER; - break; - } - - /* fill gadget fields */ - priv_dev->gadget.max_speed = max_speed; - priv_dev->gadget.speed = USB_SPEED_UNKNOWN; - priv_dev->gadget.ops = &cdns3_gadget_ops; - priv_dev->gadget.name = "usb-ss-gadget"; - priv_dev->gadget.quirk_avoids_skb_reserve = 1; - priv_dev->gadget.irq = cdns->dev_irq; - - spin_lock_init(&priv_dev->lock); - INIT_WORK(&priv_dev->pending_status_wq, - cdns3_pending_setup_status_handler); - - INIT_WORK(&priv_dev->aligned_buf_wq, - cdns3_free_aligned_request_buf); - - /* initialize endpoint container */ - INIT_LIST_HEAD(&priv_dev->gadget.ep_list); - INIT_LIST_HEAD(&priv_dev->aligned_buf_list); - - ret = cdns3_init_eps(priv_dev); - if (ret) { - dev_err(priv_dev->dev, "Failed to create endpoints\n"); - goto err1; - } - - /* allocate memory for setup packet buffer */ - priv_dev->setup_buf = dma_alloc_coherent(priv_dev->sysdev, 8, - &priv_dev->setup_dma, GFP_DMA); - if (!priv_dev->setup_buf) { - ret = -ENOMEM; - goto err2; - } - - priv_dev->dev_ver = readl(&priv_dev->regs->usb_cap6); - - dev_dbg(priv_dev->dev, "Device Controller version: %08x\n", - readl(&priv_dev->regs->usb_cap6)); - dev_dbg(priv_dev->dev, "USB Capabilities:: %08x\n", - readl(&priv_dev->regs->usb_cap1)); - dev_dbg(priv_dev->dev, "On-Chip memory configuration: %08x\n", - readl(&priv_dev->regs->usb_cap2)); - - priv_dev->dev_ver = GET_DEV_BASE_VERSION(priv_dev->dev_ver); - if (priv_dev->dev_ver >= DEV_VER_V2) - priv_dev->gadget.sg_supported = 1; - - priv_dev->zlp_buf = kzalloc(CDNS3_EP_ZLP_BUF_SIZE, GFP_KERNEL); - if (!priv_dev->zlp_buf) { - ret = -ENOMEM; - goto err3; - } - - /* add USB gadget device */ - ret = usb_add_gadget(&priv_dev->gadget); - if (ret < 0) { - dev_err(priv_dev->dev, "Failed to add gadget\n"); - goto err4; - } - - return 0; -err4: - kfree(priv_dev->zlp_buf); -err3: - dma_free_coherent(priv_dev->sysdev, 8, priv_dev->setup_buf, - priv_dev->setup_dma); -err2: - cdns3_free_all_eps(priv_dev); -err1: - usb_put_gadget(&priv_dev->gadget); - cdns->gadget_dev = NULL; - return ret; -} - -static int __cdns3_gadget_init(struct cdns *cdns) -{ - int ret = 0; - - /* Ensure 32-bit DMA Mask in case we switched back from Host mode */ - ret = dma_set_mask_and_coherent(cdns->dev, DMA_BIT_MASK(32)); - if (ret) { - dev_err(cdns->dev, "Failed to set dma mask: %d\n", ret); - return ret; - } - - cdns_drd_gadget_on(cdns); - pm_runtime_get_sync(cdns->dev); - - ret = cdns3_gadget_start(cdns); - if (ret) - return ret; - - /* - * Because interrupt line can be shared with other components in - * driver it can't use IRQF_ONESHOT flag here. - */ - ret = devm_request_threaded_irq(cdns->dev, cdns->dev_irq, - cdns3_device_irq_handler, - cdns3_device_thread_irq_handler, - IRQF_SHARED, dev_name(cdns->dev), - cdns->gadget_dev); - - if (ret) - goto err0; - - return 0; -err0: - cdns3_gadget_exit(cdns); - return ret; -} - -static int cdns3_gadget_suspend(struct cdns *cdns, bool do_wakeup) -__must_hold(&cdns->lock) -{ - struct cdns3_device *priv_dev = cdns->gadget_dev; - - spin_unlock(&cdns->lock); - cdns3_disconnect_gadget(priv_dev); - spin_lock(&cdns->lock); - - priv_dev->gadget.speed = USB_SPEED_UNKNOWN; - usb_gadget_set_state(&priv_dev->gadget, USB_STATE_NOTATTACHED); - cdns3_hw_reset_eps_config(priv_dev); - - /* disable interrupt for device */ - writel(0, &priv_dev->regs->usb_ien); - - return 0; -} - -static int cdns3_gadget_resume(struct cdns *cdns, bool hibernated) -{ - struct cdns3_device *priv_dev = cdns->gadget_dev; - - if (!priv_dev->gadget_driver) - return 0; - - cdns3_gadget_config(priv_dev); - - return 0; -} - -/** - * cdns3_gadget_init - initialize device structure - * - * @cdns: cdns instance - * - * This function initializes the gadget. - */ -int cdns3_gadget_init(struct cdns *cdns) -{ - struct cdns_role_driver *rdrv; - - rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL); - if (!rdrv) - return -ENOMEM; - - rdrv->start = __cdns3_gadget_init; - rdrv->stop = cdns3_gadget_exit; - rdrv->suspend = cdns3_gadget_suspend; - rdrv->resume = cdns3_gadget_resume; - rdrv->state = CDNS_ROLE_STATE_INACTIVE; - rdrv->name = "gadget"; - cdns->roles[USB_ROLE_DEVICE] = rdrv; - - return 0; -} diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h deleted file mode 100644 index 21fa461c518e..000000000000 --- a/drivers/usb/cdns3/gadget.h +++ /dev/null @@ -1,1368 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * USBSS device controller driver header file - * - * Copyright (C) 2018-2019 Cadence. - * Copyright (C) 2017-2018 NXP - * - * Author: Pawel Laszczak - * Pawel Jez - * Peter Chen - */ -#ifndef __LINUX_CDNS3_GADGET -#define __LINUX_CDNS3_GADGET -#include - -/* - * USBSS-DEV register interface. - * This corresponds to the USBSS Device Controller Interface - */ - -/** - * struct cdns3_usb_regs - device controller registers. - * @usb_conf: Global Configuration. - * @usb_sts: Global Status. - * @usb_cmd: Global Command. - * @usb_itpn: ITP/SOF number. - * @usb_lpm: Global Command. - * @usb_ien: USB Interrupt Enable. - * @usb_ists: USB Interrupt Status. - * @ep_sel: Endpoint Select. - * @ep_traddr: Endpoint Transfer Ring Address. - * @ep_cfg: Endpoint Configuration. - * @ep_cmd: Endpoint Command. - * @ep_sts: Endpoint Status. - * @ep_sts_sid: Endpoint Status. - * @ep_sts_en: Endpoint Status Enable. - * @drbl: Doorbell. - * @ep_ien: EP Interrupt Enable. - * @ep_ists: EP Interrupt Status. - * @usb_pwr: Global Power Configuration. - * @usb_conf2: Global Configuration 2. - * @usb_cap1: Capability 1. - * @usb_cap2: Capability 2. - * @usb_cap3: Capability 3. - * @usb_cap4: Capability 4. - * @usb_cap5: Capability 5. - * @usb_cap6: Capability 6. - * @usb_cpkt1: Custom Packet 1. - * @usb_cpkt2: Custom Packet 2. - * @usb_cpkt3: Custom Packet 3. - * @ep_dma_ext_addr: Upper address for DMA operations. - * @buf_addr: Address for On-chip Buffer operations. - * @buf_data: Data for On-chip Buffer operations. - * @buf_ctrl: On-chip Buffer Access Control. - * @dtrans: DMA Transfer Mode. - * @tdl_from_trb: Source of TD Configuration. - * @tdl_beh: TDL Behavior Configuration. - * @ep_tdl: Endpoint TDL. - * @tdl_beh2: TDL Behavior 2 Configuration. - * @dma_adv_td: DMA Advance TD Configuration. - * @reserved1: Reserved. - * @cfg_regs: Configuration. - * @reserved2: Reserved. - * @dma_axi_ctrl: AXI Control. - * @dma_axi_id: AXI ID register. - * @dma_axi_cap: AXI Capability. - * @dma_axi_ctrl0: AXI Control 0. - * @dma_axi_ctrl1: AXI Control 1. - */ -struct cdns3_usb_regs { - __le32 usb_conf; - __le32 usb_sts; - __le32 usb_cmd; - __le32 usb_itpn; - __le32 usb_lpm; - __le32 usb_ien; - __le32 usb_ists; - __le32 ep_sel; - __le32 ep_traddr; - __le32 ep_cfg; - __le32 ep_cmd; - __le32 ep_sts; - __le32 ep_sts_sid; - __le32 ep_sts_en; - __le32 drbl; - __le32 ep_ien; - __le32 ep_ists; - __le32 usb_pwr; - __le32 usb_conf2; - __le32 usb_cap1; - __le32 usb_cap2; - __le32 usb_cap3; - __le32 usb_cap4; - __le32 usb_cap5; - __le32 usb_cap6; - __le32 usb_cpkt1; - __le32 usb_cpkt2; - __le32 usb_cpkt3; - __le32 ep_dma_ext_addr; - __le32 buf_addr; - __le32 buf_data; - __le32 buf_ctrl; - __le32 dtrans; - __le32 tdl_from_trb; - __le32 tdl_beh; - __le32 ep_tdl; - __le32 tdl_beh2; - __le32 dma_adv_td; - __le32 reserved1[26]; - __le32 cfg_reg1; - __le32 dbg_link1; - __le32 dbg_link2; - __le32 cfg_regs[74]; - __le32 reserved2[51]; - __le32 dma_axi_ctrl; - __le32 dma_axi_id; - __le32 dma_axi_cap; - __le32 dma_axi_ctrl0; - __le32 dma_axi_ctrl1; -}; - -/* USB_CONF - bitmasks */ -/* Reset USB device configuration. */ -#define USB_CONF_CFGRST BIT(0) -/* Set Configuration. */ -#define USB_CONF_CFGSET BIT(1) -/* Disconnect USB device in SuperSpeed. */ -#define USB_CONF_USB3DIS BIT(3) -/* Disconnect USB device in HS/FS */ -#define USB_CONF_USB2DIS BIT(4) -/* Little Endian access - default */ -#define USB_CONF_LENDIAN BIT(5) -/* - * Big Endian access. Driver assume that byte order for - * SFRs access always is as Little Endian so this bit - * is not used. - */ -#define USB_CONF_BENDIAN BIT(6) -/* Device software reset. */ -#define USB_CONF_SWRST BIT(7) -/* Singular DMA transfer mode. Only for VER < DEV_VER_V3*/ -#define USB_CONF_DSING BIT(8) -/* Multiple DMA transfers mode. Only for VER < DEV_VER_V3 */ -#define USB_CONF_DMULT BIT(9) -/* DMA clock turn-off enable. */ -#define USB_CONF_DMAOFFEN BIT(10) -/* DMA clock turn-off disable. */ -#define USB_CONF_DMAOFFDS BIT(11) -/* Clear Force Full Speed. */ -#define USB_CONF_CFORCE_FS BIT(12) -/* Set Force Full Speed. */ -#define USB_CONF_SFORCE_FS BIT(13) -/* Device enable. */ -#define USB_CONF_DEVEN BIT(14) -/* Device disable. */ -#define USB_CONF_DEVDS BIT(15) -/* L1 LPM state entry enable (used in HS/FS mode). */ -#define USB_CONF_L1EN BIT(16) -/* L1 LPM state entry disable (used in HS/FS mode). */ -#define USB_CONF_L1DS BIT(17) -/* USB 2.0 clock gate disable. */ -#define USB_CONF_CLK2OFFEN BIT(18) -/* USB 2.0 clock gate enable. */ -#define USB_CONF_CLK2OFFDS BIT(19) -/* L0 LPM state entry request (used in HS/FS mode). */ -#define USB_CONF_LGO_L0 BIT(20) -/* USB 3.0 clock gate disable. */ -#define USB_CONF_CLK3OFFEN BIT(21) -/* USB 3.0 clock gate enable. */ -#define USB_CONF_CLK3OFFDS BIT(22) -/* Bit 23 is reserved*/ -/* U1 state entry enable (used in SS mode). */ -#define USB_CONF_U1EN BIT(24) -/* U1 state entry disable (used in SS mode). */ -#define USB_CONF_U1DS BIT(25) -/* U2 state entry enable (used in SS mode). */ -#define USB_CONF_U2EN BIT(26) -/* U2 state entry disable (used in SS mode). */ -#define USB_CONF_U2DS BIT(27) -/* U0 state entry request (used in SS mode). */ -#define USB_CONF_LGO_U0 BIT(28) -/* U1 state entry request (used in SS mode). */ -#define USB_CONF_LGO_U1 BIT(29) -/* U2 state entry request (used in SS mode). */ -#define USB_CONF_LGO_U2 BIT(30) -/* SS.Inactive state entry request (used in SS mode) */ -#define USB_CONF_LGO_SSINACT BIT(31) - -/* USB_STS - bitmasks */ -/* - * Configuration status. - * 1 - device is in the configured state. - * 0 - device is not configured. - */ -#define USB_STS_CFGSTS_MASK BIT(0) -#define USB_STS_CFGSTS(p) ((p) & USB_STS_CFGSTS_MASK) -/* - * On-chip memory overflow. - * 0 - On-chip memory status OK. - * 1 - On-chip memory overflow. - */ -#define USB_STS_OV_MASK BIT(1) -#define USB_STS_OV(p) ((p) & USB_STS_OV_MASK) -/* - * SuperSpeed connection status. - * 0 - USB in SuperSpeed mode disconnected. - * 1 - USB in SuperSpeed mode connected. - */ -#define USB_STS_USB3CONS_MASK BIT(2) -#define USB_STS_USB3CONS(p) ((p) & USB_STS_USB3CONS_MASK) -/* - * DMA transfer configuration status. - * 0 - single request. - * 1 - multiple TRB chain - * Supported only for controller version < DEV_VER_V3 - */ -#define USB_STS_DTRANS_MASK BIT(3) -#define USB_STS_DTRANS(p) ((p) & USB_STS_DTRANS_MASK) -/* - * Device speed. - * 0 - Undefined (value after reset). - * 1 - Low speed - * 2 - Full speed - * 3 - High speed - * 4 - Super speed - */ -#define USB_STS_USBSPEED_MASK GENMASK(6, 4) -#define USB_STS_USBSPEED(p) (((p) & USB_STS_USBSPEED_MASK) >> 4) -#define USB_STS_LS (0x1 << 4) -#define USB_STS_FS (0x2 << 4) -#define USB_STS_HS (0x3 << 4) -#define USB_STS_SS (0x4 << 4) -#define DEV_UNDEFSPEED(p) (((p) & USB_STS_USBSPEED_MASK) == (0x0 << 4)) -#define DEV_LOWSPEED(p) (((p) & USB_STS_USBSPEED_MASK) == USB_STS_LS) -#define DEV_FULLSPEED(p) (((p) & USB_STS_USBSPEED_MASK) == USB_STS_FS) -#define DEV_HIGHSPEED(p) (((p) & USB_STS_USBSPEED_MASK) == USB_STS_HS) -#define DEV_SUPERSPEED(p) (((p) & USB_STS_USBSPEED_MASK) == USB_STS_SS) -/* - * Endianness for SFR access. - * 0 - Little Endian order (default after hardware reset). - * 1 - Big Endian order - */ -#define USB_STS_ENDIAN_MASK BIT(7) -#define USB_STS_ENDIAN(p) ((p) & USB_STS_ENDIAN_MASK) -/* - * HS/FS clock turn-off status. - * 0 - hsfs clock is always on. - * 1 - hsfs clock turn-off in L2 (HS/FS mode) is enabled - * (default after hardware reset). - */ -#define USB_STS_CLK2OFF_MASK BIT(8) -#define USB_STS_CLK2OFF(p) ((p) & USB_STS_CLK2OFF_MASK) -/* - * PCLK clock turn-off status. - * 0 - pclk clock is always on. - * 1 - pclk clock turn-off in U3 (SS mode) is enabled - * (default after hardware reset). - */ -#define USB_STS_CLK3OFF_MASK BIT(9) -#define USB_STS_CLK3OFF(p) ((p) & USB_STS_CLK3OFF_MASK) -/* - * Controller in reset state. - * 0 - Internal reset is active. - * 1 - Internal reset is not active and controller is fully operational. - */ -#define USB_STS_IN_RST_MASK BIT(10) -#define USB_STS_IN_RST(p) ((p) & USB_STS_IN_RST_MASK) -/* - * Status of the "TDL calculation basing on TRB" feature. - * 0 - disabled - * 1 - enabled - * Supported only for DEV_VER_V2 controller version. - */ -#define USB_STS_TDL_TRB_ENABLED BIT(11) -/* - * Device enable Status. - * 0 - USB device is disabled (VBUS input is disconnected from internal logic). - * 1 - USB device is enabled (VBUS input is connected to the internal logic). - */ -#define USB_STS_DEVS_MASK BIT(14) -#define USB_STS_DEVS(p) ((p) & USB_STS_DEVS_MASK) -/* - * Address status. - * 0 - USB device is default state. - * 1 - USB device is at least in address state. - */ -#define USB_STS_ADDRESSED_MASK BIT(15) -#define USB_STS_ADDRESSED(p) ((p) & USB_STS_ADDRESSED_MASK) -/* - * L1 LPM state enable status (used in HS/FS mode). - * 0 - Entering to L1 LPM state disabled. - * 1 - Entering to L1 LPM state enabled. - */ -#define USB_STS_L1ENS_MASK BIT(16) -#define USB_STS_L1ENS(p) ((p) & USB_STS_L1ENS_MASK) -/* - * Internal VBUS connection status (used both in HS/FS and SS mode). - * 0 - internal VBUS is not detected. - * 1 - internal VBUS is detected. - */ -#define USB_STS_VBUSS_MASK BIT(17) -#define USB_STS_VBUSS(p) ((p) & USB_STS_VBUSS_MASK) -/* - * HS/FS LPM state (used in FS/HS mode). - * 0 - L0 State - * 1 - L1 State - * 2 - L2 State - * 3 - L3 State - */ -#define USB_STS_LPMST_MASK GENMASK(19, 18) -#define DEV_L0_STATE(p) (((p) & USB_STS_LPMST_MASK) == (0x0 << 18)) -#define DEV_L1_STATE(p) (((p) & USB_STS_LPMST_MASK) == (0x1 << 18)) -#define DEV_L2_STATE(p) (((p) & USB_STS_LPMST_MASK) == (0x2 << 18)) -#define DEV_L3_STATE(p) (((p) & USB_STS_LPMST_MASK) == (0x3 << 18)) -/* - * Disable HS status (used in FS/HS mode). - * 0 - the disconnect bit for HS/FS mode is set . - * 1 - the disconnect bit for HS/FS mode is not set. - */ -#define USB_STS_USB2CONS_MASK BIT(20) -#define USB_STS_USB2CONS(p) ((p) & USB_STS_USB2CONS_MASK) -/* - * HS/FS mode connection status (used in FS/HS mode). - * 0 - High Speed operations in USB2.0 (FS/HS) mode not disabled. - * 1 - High Speed operations in USB2.0 (FS/HS). - */ -#define USB_STS_DISABLE_HS_MASK BIT(21) -#define USB_STS_DISABLE_HS(p) ((p) & USB_STS_DISABLE_HS_MASK) -/* - * U1 state enable status (used in SS mode). - * 0 - Entering to U1 state disabled. - * 1 - Entering to U1 state enabled. - */ -#define USB_STS_U1ENS_MASK BIT(24) -#define USB_STS_U1ENS(p) ((p) & USB_STS_U1ENS_MASK) -/* - * U2 state enable status (used in SS mode). - * 0 - Entering to U2 state disabled. - * 1 - Entering to U2 state enabled. - */ -#define USB_STS_U2ENS_MASK BIT(25) -#define USB_STS_U2ENS(p) ((p) & USB_STS_U2ENS_MASK) -/* - * SuperSpeed Link LTSSM state. This field reflects USBSS-DEV current - * SuperSpeed link state - */ -#define USB_STS_LST_MASK GENMASK(29, 26) -#define DEV_LST_U0 (((p) & USB_STS_LST_MASK) == (0x0 << 26)) -#define DEV_LST_U1 (((p) & USB_STS_LST_MASK) == (0x1 << 26)) -#define DEV_LST_U2 (((p) & USB_STS_LST_MASK) == (0x2 << 26)) -#define DEV_LST_U3 (((p) & USB_STS_LST_MASK) == (0x3 << 26)) -#define DEV_LST_DISABLED (((p) & USB_STS_LST_MASK) == (0x4 << 26)) -#define DEV_LST_RXDETECT (((p) & USB_STS_LST_MASK) == (0x5 << 26)) -#define DEV_LST_INACTIVE (((p) & USB_STS_LST_MASK) == (0x6 << 26)) -#define DEV_LST_POLLING (((p) & USB_STS_LST_MASK) == (0x7 << 26)) -#define DEV_LST_RECOVERY (((p) & USB_STS_LST_MASK) == (0x8 << 26)) -#define DEV_LST_HOT_RESET (((p) & USB_STS_LST_MASK) == (0x9 << 26)) -#define DEV_LST_COMP_MODE (((p) & USB_STS_LST_MASK) == (0xa << 26)) -#define DEV_LST_LB_STATE (((p) & USB_STS_LST_MASK) == (0xb << 26)) -/* - * DMA clock turn-off status. - * 0 - DMA clock is always on (default after hardware reset). - * 1 - DMA clock turn-off in U1, U2 and U3 (SS mode) is enabled. - */ -#define USB_STS_DMAOFF_MASK BIT(30) -#define USB_STS_DMAOFF(p) ((p) & USB_STS_DMAOFF_MASK) -/* - * SFR Endian status. - * 0 - Little Endian order (default after hardware reset). - * 1 - Big Endian order. - */ -#define USB_STS_ENDIAN2_MASK BIT(31) -#define USB_STS_ENDIAN2(p) ((p) & USB_STS_ENDIAN2_MASK) - -/* USB_CMD - bitmasks */ -/* Set Function Address */ -#define USB_CMD_SET_ADDR BIT(0) -/* - * Function Address This field is saved to the device only when the field - * SET_ADDR is set '1 ' during write to USB_CMD register. - * Software is responsible for entering the address of the device during - * SET_ADDRESS request service. This field should be set immediately after - * the SETUP packet is decoded, and prior to confirmation of the status phase - */ -#define USB_CMD_FADDR_MASK GENMASK(7, 1) -#define USB_CMD_FADDR(p) (((p) << 1) & USB_CMD_FADDR_MASK) -/* Send Function Wake Device Notification TP (used only in SS mode). */ -#define USB_CMD_SDNFW BIT(8) -/* Set Test Mode (used only in HS/FS mode). */ -#define USB_CMD_STMODE BIT(9) -/* Test mode selector (used only in HS/FS mode) */ -#define USB_STS_TMODE_SEL_MASK GENMASK(11, 10) -#define USB_STS_TMODE_SEL(p) (((p) << 10) & USB_STS_TMODE_SEL_MASK) -/* - * Send Latency Tolerance Message Device Notification TP (used only - * in SS mode). - */ -#define USB_CMD_SDNLTM BIT(12) -/* Send Custom Transaction Packet (used only in SS mode) */ -#define USB_CMD_SPKT BIT(13) -/*Device Notification 'Function Wake' - Interface value (only in SS mode. */ -#define USB_CMD_DNFW_INT_MASK GENMASK(23, 16) -#define USB_STS_DNFW_INT(p) (((p) << 16) & USB_CMD_DNFW_INT_MASK) -/* - * Device Notification 'Latency Tolerance Message' -373 BELT value [7:0] - * (used only in SS mode). - */ -#define USB_CMD_DNLTM_BELT_MASK GENMASK(27, 16) -#define USB_STS_DNLTM_BELT(p) (((p) << 16) & USB_CMD_DNLTM_BELT_MASK) - -/* USB_ITPN - bitmasks */ -/* - * ITP(SS) / SOF (HS/FS) number - * In SS mode this field represent number of last ITP received from host. - * In HS/FS mode this field represent number of last SOF received from host. - */ -#define USB_ITPN_MASK GENMASK(13, 0) -#define USB_ITPN(p) ((p) & USB_ITPN_MASK) - -/* USB_LPM - bitmasks */ -/* Host Initiated Resume Duration. */ -#define USB_LPM_HIRD_MASK GENMASK(3, 0) -#define USB_LPM_HIRD(p) ((p) & USB_LPM_HIRD_MASK) -/* Remote Wakeup Enable (bRemoteWake). */ -#define USB_LPM_BRW BIT(4) - -/* USB_IEN - bitmasks */ -/* SS connection interrupt enable */ -#define USB_IEN_CONIEN BIT(0) -/* SS disconnection interrupt enable. */ -#define USB_IEN_DISIEN BIT(1) -/* USB SS warm reset interrupt enable. */ -#define USB_IEN_UWRESIEN BIT(2) -/* USB SS hot reset interrupt enable */ -#define USB_IEN_UHRESIEN BIT(3) -/* SS link U3 state enter interrupt enable (suspend).*/ -#define USB_IEN_U3ENTIEN BIT(4) -/* SS link U3 state exit interrupt enable (wakeup). */ -#define USB_IEN_U3EXTIEN BIT(5) -/* SS link U2 state enter interrupt enable.*/ -#define USB_IEN_U2ENTIEN BIT(6) -/* SS link U2 state exit interrupt enable.*/ -#define USB_IEN_U2EXTIEN BIT(7) -/* SS link U1 state enter interrupt enable.*/ -#define USB_IEN_U1ENTIEN BIT(8) -/* SS link U1 state exit interrupt enable.*/ -#define USB_IEN_U1EXTIEN BIT(9) -/* ITP/SOF packet detected interrupt enable.*/ -#define USB_IEN_ITPIEN BIT(10) -/* Wakeup interrupt enable.*/ -#define USB_IEN_WAKEIEN BIT(11) -/* Send Custom Packet interrupt enable.*/ -#define USB_IEN_SPKTIEN BIT(12) -/* HS/FS mode connection interrupt enable.*/ -#define USB_IEN_CON2IEN BIT(16) -/* HS/FS mode disconnection interrupt enable.*/ -#define USB_IEN_DIS2IEN BIT(17) -/* USB reset (HS/FS mode) interrupt enable.*/ -#define USB_IEN_U2RESIEN BIT(18) -/* LPM L2 state enter interrupt enable.*/ -#define USB_IEN_L2ENTIEN BIT(20) -/* LPM L2 state exit interrupt enable.*/ -#define USB_IEN_L2EXTIEN BIT(21) -/* LPM L1 state enter interrupt enable.*/ -#define USB_IEN_L1ENTIEN BIT(24) -/* LPM L1 state exit interrupt enable.*/ -#define USB_IEN_L1EXTIEN BIT(25) -/* Configuration reset interrupt enable.*/ -#define USB_IEN_CFGRESIEN BIT(26) -/* Start of the USB SS warm reset interrupt enable.*/ -#define USB_IEN_UWRESSIEN BIT(28) -/* End of the USB SS warm reset interrupt enable.*/ -#define USB_IEN_UWRESEIEN BIT(29) - -#define USB_IEN_INIT (USB_IEN_U2RESIEN | USB_ISTS_DIS2I | USB_IEN_CON2IEN \ - | USB_IEN_UHRESIEN | USB_IEN_UWRESIEN | USB_IEN_DISIEN \ - | USB_IEN_CONIEN | USB_IEN_U3EXTIEN | USB_IEN_L2ENTIEN \ - | USB_IEN_L2EXTIEN | USB_IEN_L1ENTIEN | USB_IEN_U3ENTIEN) - -/* USB_ISTS - bitmasks */ -/* SS Connection detected. */ -#define USB_ISTS_CONI BIT(0) -/* SS Disconnection detected. */ -#define USB_ISTS_DISI BIT(1) -/* UUSB warm reset detectede. */ -#define USB_ISTS_UWRESI BIT(2) -/* USB hot reset detected. */ -#define USB_ISTS_UHRESI BIT(3) -/* U3 link state enter detected (suspend).*/ -#define USB_ISTS_U3ENTI BIT(4) -/* U3 link state exit detected (wakeup). */ -#define USB_ISTS_U3EXTI BIT(5) -/* U2 link state enter detected.*/ -#define USB_ISTS_U2ENTI BIT(6) -/* U2 link state exit detected.*/ -#define USB_ISTS_U2EXTI BIT(7) -/* U1 link state enter detected.*/ -#define USB_ISTS_U1ENTI BIT(8) -/* U1 link state exit detected.*/ -#define USB_ISTS_U1EXTI BIT(9) -/* ITP/SOF packet detected.*/ -#define USB_ISTS_ITPI BIT(10) -/* Wakeup detected.*/ -#define USB_ISTS_WAKEI BIT(11) -/* Send Custom Packet detected.*/ -#define USB_ISTS_SPKTI BIT(12) -/* HS/FS mode connection detected.*/ -#define USB_ISTS_CON2I BIT(16) -/* HS/FS mode disconnection detected.*/ -#define USB_ISTS_DIS2I BIT(17) -/* USB reset (HS/FS mode) detected.*/ -#define USB_ISTS_U2RESI BIT(18) -/* LPM L2 state enter detected.*/ -#define USB_ISTS_L2ENTI BIT(20) -/* LPM L2 state exit detected.*/ -#define USB_ISTS_L2EXTI BIT(21) -/* LPM L1 state enter detected.*/ -#define USB_ISTS_L1ENTI BIT(24) -/* LPM L1 state exit detected.*/ -#define USB_ISTS_L1EXTI BIT(25) -/* USB configuration reset detected.*/ -#define USB_ISTS_CFGRESI BIT(26) -/* Start of the USB warm reset detected.*/ -#define USB_ISTS_UWRESSI BIT(28) -/* End of the USB warm reset detected.*/ -#define USB_ISTS_UWRESEI BIT(29) - -/* USB_SEL - bitmasks */ -#define EP_SEL_EPNO_MASK GENMASK(3, 0) -/* Endpoint number. */ -#define EP_SEL_EPNO(p) ((p) & EP_SEL_EPNO_MASK) -/* Endpoint direction bit - 0 - OUT, 1 - IN. */ -#define EP_SEL_DIR BIT(7) - -#define select_ep_in(nr) (EP_SEL_EPNO(p) | EP_SEL_DIR) -#define select_ep_out (EP_SEL_EPNO(p)) - -/* EP_TRADDR - bitmasks */ -/* Transfer Ring address. */ -#define EP_TRADDR_TRADDR(p) ((p)) - -/* EP_CFG - bitmasks */ -/* Endpoint enable */ -#define EP_CFG_ENABLE BIT(0) -/* - * Endpoint type. - * 1 - isochronous - * 2 - bulk - * 3 - interrupt - */ -#define EP_CFG_EPTYPE_MASK GENMASK(2, 1) -#define EP_CFG_EPTYPE(p) (((p) << 1) & EP_CFG_EPTYPE_MASK) -/* Stream support enable (only in SS mode). */ -#define EP_CFG_STREAM_EN BIT(3) -/* TDL check (only in SS mode for BULK EP). */ -#define EP_CFG_TDL_CHK BIT(4) -/* SID check (only in SS mode for BULK OUT EP). */ -#define EP_CFG_SID_CHK BIT(5) -/* DMA transfer endianness. */ -#define EP_CFG_EPENDIAN BIT(7) -/* Max burst size (used only in SS mode). */ -#define EP_CFG_MAXBURST_MASK GENMASK(11, 8) -#define EP_CFG_MAXBURST(p) (((p) << 8) & EP_CFG_MAXBURST_MASK) -/* ISO max burst. */ -#define EP_CFG_MULT_MASK GENMASK(15, 14) -#define EP_CFG_MULT(p) (((p) << 14) & EP_CFG_MULT_MASK) -/* ISO max burst. */ -#define EP_CFG_MAXPKTSIZE_MASK GENMASK(26, 16) -#define EP_CFG_MAXPKTSIZE(p) (((p) << 16) & EP_CFG_MAXPKTSIZE_MASK) -/* Max number of buffered packets. */ -#define EP_CFG_BUFFERING_MASK GENMASK(31, 27) -#define EP_CFG_BUFFERING(p) (((p) << 27) & EP_CFG_BUFFERING_MASK) - -/* EP_CMD - bitmasks */ -/* Endpoint reset. */ -#define EP_CMD_EPRST BIT(0) -/* Endpoint STALL set. */ -#define EP_CMD_SSTALL BIT(1) -/* Endpoint STALL clear. */ -#define EP_CMD_CSTALL BIT(2) -/* Send ERDY TP. */ -#define EP_CMD_ERDY BIT(3) -/* Request complete. */ -#define EP_CMD_REQ_CMPL BIT(5) -/* Transfer descriptor ready. */ -#define EP_CMD_DRDY BIT(6) -/* Data flush. */ -#define EP_CMD_DFLUSH BIT(7) -/* - * Transfer Descriptor Length write (used only for Bulk Stream capable - * endpoints in SS mode). - * Bit Removed from DEV_VER_V3 controller version. - */ -#define EP_CMD_STDL BIT(8) -/* - * Transfer Descriptor Length (used only in SS mode for bulk endpoints). - * Bits Removed from DEV_VER_V3 controller version. - */ -#define EP_CMD_TDL_MASK GENMASK(15, 9) -#define EP_CMD_TDL_SET(p) (((p) << 9) & EP_CMD_TDL_MASK) -#define EP_CMD_TDL_GET(p) (((p) & EP_CMD_TDL_MASK) >> 9) -#define EP_CMD_TDL_MAX (EP_CMD_TDL_MASK >> 9) - -/* ERDY Stream ID value (used in SS mode). */ -#define EP_CMD_ERDY_SID_MASK GENMASK(31, 16) -#define EP_CMD_ERDY_SID(p) (((p) << 16) & EP_CMD_ERDY_SID_MASK) - -/* EP_STS - bitmasks */ -/* Setup transfer complete. */ -#define EP_STS_SETUP BIT(0) -/* Endpoint STALL status. */ -#define EP_STS_STALL(p) ((p) & BIT(1)) -/* Interrupt On Complete. */ -#define EP_STS_IOC BIT(2) -/* Interrupt on Short Packet. */ -#define EP_STS_ISP BIT(3) -/* Transfer descriptor missing. */ -#define EP_STS_DESCMIS BIT(4) -/* Stream Rejected (used only in SS mode) */ -#define EP_STS_STREAMR BIT(5) -/* EXIT from MOVE DATA State (used only for stream transfers in SS mode). */ -#define EP_STS_MD_EXIT BIT(6) -/* TRB error. */ -#define EP_STS_TRBERR BIT(7) -/* Not ready (used only in SS mode). */ -#define EP_STS_NRDY BIT(8) -/* DMA busy bit. */ -#define EP_STS_DBUSY BIT(9) -/* Endpoint Buffer Empty */ -#define EP_STS_BUFFEMPTY(p) ((p) & BIT(10)) -/* Current Cycle Status */ -#define EP_STS_CCS(p) ((p) & BIT(11)) -/* Prime (used only in SS mode. */ -#define EP_STS_PRIME BIT(12) -/* Stream error (used only in SS mode). */ -#define EP_STS_SIDERR BIT(13) -/* OUT size mismatch. */ -#define EP_STS_OUTSMM BIT(14) -/* ISO transmission error. */ -#define EP_STS_ISOERR BIT(15) -/* Host Packet Pending (only for SS mode). */ -#define EP_STS_HOSTPP(p) ((p) & BIT(16)) -/* Stream Protocol State Machine State (only for Bulk stream endpoints). */ -#define EP_STS_SPSMST_MASK GENMASK(18, 17) -#define EP_STS_SPSMST_DISABLED(p) (((p) & EP_STS_SPSMST_MASK) >> 17) -#define EP_STS_SPSMST_IDLE(p) (((p) & EP_STS_SPSMST_MASK) >> 17) -#define EP_STS_SPSMST_START_STREAM(p) (((p) & EP_STS_SPSMST_MASK) >> 17) -#define EP_STS_SPSMST_MOVE_DATA(p) (((p) & EP_STS_SPSMST_MASK) >> 17) -/* Interrupt On Transfer complete. */ -#define EP_STS_IOT BIT(19) -/* OUT queue endpoint number. */ -#define EP_STS_OUTQ_NO_MASK GENMASK(27, 24) -#define EP_STS_OUTQ_NO(p) (((p) & EP_STS_OUTQ_NO_MASK) >> 24) -/* OUT queue valid flag. */ -#define EP_STS_OUTQ_VAL_MASK BIT(28) -#define EP_STS_OUTQ_VAL(p) ((p) & EP_STS_OUTQ_VAL_MASK) -/* SETUP WAIT. */ -#define EP_STS_STPWAIT BIT(31) - -/* EP_STS_SID - bitmasks */ -/* Stream ID (used only in SS mode). */ -#define EP_STS_SID_MASK GENMASK(15, 0) -#define EP_STS_SID(p) ((p) & EP_STS_SID_MASK) - -/* EP_STS_EN - bitmasks */ -/* SETUP interrupt enable. */ -#define EP_STS_EN_SETUPEN BIT(0) -/* OUT transfer missing descriptor enable. */ -#define EP_STS_EN_DESCMISEN BIT(4) -/* Stream Rejected enable. */ -#define EP_STS_EN_STREAMREN BIT(5) -/* Move Data Exit enable.*/ -#define EP_STS_EN_MD_EXITEN BIT(6) -/* TRB enable. */ -#define EP_STS_EN_TRBERREN BIT(7) -/* NRDY enable. */ -#define EP_STS_EN_NRDYEN BIT(8) -/* Prime enable. */ -#define EP_STS_EN_PRIMEEEN BIT(12) -/* Stream error enable. */ -#define EP_STS_EN_SIDERREN BIT(13) -/* OUT size mismatch enable. */ -#define EP_STS_EN_OUTSMMEN BIT(14) -/* ISO transmission error enable. */ -#define EP_STS_EN_ISOERREN BIT(15) -/* Interrupt on Transmission complete enable. */ -#define EP_STS_EN_IOTEN BIT(19) -/* Setup Wait interrupt enable. */ -#define EP_STS_EN_STPWAITEN BIT(31) - -/* DRBL- bitmasks */ -#define DB_VALUE_BY_INDEX(index) (1 << (index)) -#define DB_VALUE_EP0_OUT BIT(0) -#define DB_VALUE_EP0_IN BIT(16) - -/* EP_IEN - bitmasks */ -#define EP_IEN(index) (1 << (index)) -#define EP_IEN_EP_OUT0 BIT(0) -#define EP_IEN_EP_IN0 BIT(16) - -/* EP_ISTS - bitmasks */ -#define EP_ISTS(index) (1 << (index)) -#define EP_ISTS_EP_OUT0 BIT(0) -#define EP_ISTS_EP_IN0 BIT(16) - -/* USB_PWR- bitmasks */ -/*Power Shut Off capability enable*/ -#define PUSB_PWR_PSO_EN BIT(0) -/*Power Shut Off capability disable*/ -#define PUSB_PWR_PSO_DS BIT(1) -/* - * Enables turning-off Reference Clock. - * This bit is optional and implemented only when support for OTG is - * implemented (indicated by OTG_READY bit set to '1'). - */ -#define PUSB_PWR_STB_CLK_SWITCH_EN BIT(8) -/* - * Status bit indicating that operation required by STB_CLK_SWITCH_EN write - * is completed - */ -#define PUSB_PWR_STB_CLK_SWITCH_DONE BIT(9) -/* This bit informs if Fast Registers Access is enabled. */ -#define PUSB_PWR_FST_REG_ACCESS_STAT BIT(30) -/* Fast Registers Access Enable. */ -#define PUSB_PWR_FST_REG_ACCESS BIT(31) - -/* USB_CONF2- bitmasks */ -/* - * Writing 1 disables TDL calculation basing on TRB feature in controller - * for DMULT mode. - * Bit supported only for DEV_VER_V2 version. - */ -#define USB_CONF2_DIS_TDL_TRB BIT(1) -/* - * Writing 1 enables TDL calculation basing on TRB feature in controller - * for DMULT mode. - * Bit supported only for DEV_VER_V2 version. - */ -#define USB_CONF2_EN_TDL_TRB BIT(2) - -/* USB_CAP1- bitmasks */ -/* - * SFR Interface type - * These field reflects type of SFR interface implemented: - * 0x0 - OCP - * 0x1 - AHB, - * 0x2 - PLB - * 0x3 - AXI - * 0x4-0xF - reserved - */ -#define USB_CAP1_SFR_TYPE_MASK GENMASK(3, 0) -#define DEV_SFR_TYPE_OCP(p) (((p) & USB_CAP1_SFR_TYPE_MASK) == 0x0) -#define DEV_SFR_TYPE_AHB(p) (((p) & USB_CAP1_SFR_TYPE_MASK) == 0x1) -#define DEV_SFR_TYPE_PLB(p) (((p) & USB_CAP1_SFR_TYPE_MASK) == 0x2) -#define DEV_SFR_TYPE_AXI(p) (((p) & USB_CAP1_SFR_TYPE_MASK) == 0x3) -/* - * SFR Interface width - * These field reflects width of SFR interface implemented: - * 0x0 - 8 bit interface, - * 0x1 - 16 bit interface, - * 0x2 - 32 bit interface - * 0x3 - 64 bit interface - * 0x4-0xF - reserved - */ -#define USB_CAP1_SFR_WIDTH_MASK GENMASK(7, 4) -#define DEV_SFR_WIDTH_8(p) (((p) & USB_CAP1_SFR_WIDTH_MASK) == (0x0 << 4)) -#define DEV_SFR_WIDTH_16(p) (((p) & USB_CAP1_SFR_WIDTH_MASK) == (0x1 << 4)) -#define DEV_SFR_WIDTH_32(p) (((p) & USB_CAP1_SFR_WIDTH_MASK) == (0x2 << 4)) -#define DEV_SFR_WIDTH_64(p) (((p) & USB_CAP1_SFR_WIDTH_MASK) == (0x3 << 4)) -/* - * DMA Interface type - * These field reflects type of DMA interface implemented: - * 0x0 - OCP - * 0x1 - AHB, - * 0x2 - PLB - * 0x3 - AXI - * 0x4-0xF - reserved - */ -#define USB_CAP1_DMA_TYPE_MASK GENMASK(11, 8) -#define DEV_DMA_TYPE_OCP(p) (((p) & USB_CAP1_DMA_TYPE_MASK) == (0x0 << 8)) -#define DEV_DMA_TYPE_AHB(p) (((p) & USB_CAP1_DMA_TYPE_MASK) == (0x1 << 8)) -#define DEV_DMA_TYPE_PLB(p) (((p) & USB_CAP1_DMA_TYPE_MASK) == (0x2 << 8)) -#define DEV_DMA_TYPE_AXI(p) (((p) & USB_CAP1_DMA_TYPE_MASK) == (0x3 << 8)) -/* - * DMA Interface width - * These field reflects width of DMA interface implemented: - * 0x0 - reserved, - * 0x1 - reserved, - * 0x2 - 32 bit interface - * 0x3 - 64 bit interface - * 0x4-0xF - reserved - */ -#define USB_CAP1_DMA_WIDTH_MASK GENMASK(15, 12) -#define DEV_DMA_WIDTH_32(p) (((p) & USB_CAP1_DMA_WIDTH_MASK) == (0x2 << 12)) -#define DEV_DMA_WIDTH_64(p) (((p) & USB_CAP1_DMA_WIDTH_MASK) == (0x3 << 12)) -/* - * USB3 PHY Interface type - * These field reflects type of USB3 PHY interface implemented: - * 0x0 - USB PIPE, - * 0x1 - RMMI, - * 0x2-0xF - reserved - */ -#define USB_CAP1_U3PHY_TYPE_MASK GENMASK(19, 16) -#define DEV_U3PHY_PIPE(p) (((p) & USB_CAP1_U3PHY_TYPE_MASK) == (0x0 << 16)) -#define DEV_U3PHY_RMMI(p) (((p) & USB_CAP1_U3PHY_TYPE_MASK) == (0x1 << 16)) -/* - * USB3 PHY Interface width - * These field reflects width of USB3 PHY interface implemented: - * 0x0 - 8 bit PIPE interface, - * 0x1 - 16 bit PIPE interface, - * 0x2 - 32 bit PIPE interface, - * 0x3 - 64 bit PIPE interface - * 0x4-0xF - reserved - * Note: When SSIC interface is implemented this field shows the width of - * internal PIPE interface. The RMMI interface is always 20bit wide. - */ -#define USB_CAP1_U3PHY_WIDTH_MASK GENMASK(23, 20) -#define DEV_U3PHY_WIDTH_8(p) \ - (((p) & USB_CAP1_U3PHY_WIDTH_MASK) == (0x0 << 20)) -#define DEV_U3PHY_WIDTH_16(p) \ - (((p) & USB_CAP1_U3PHY_WIDTH_MASK) == (0x1 << 16)) -#define DEV_U3PHY_WIDTH_32(p) \ - (((p) & USB_CAP1_U3PHY_WIDTH_MASK) == (0x2 << 20)) -#define DEV_U3PHY_WIDTH_64(p) \ - (((p) & USB_CAP1_U3PHY_WIDTH_MASK) == (0x3 << 16)) - -/* - * USB2 PHY Interface enable - * These field informs if USB2 PHY interface is implemented: - * 0x0 - interface NOT implemented, - * 0x1 - interface implemented - */ -#define USB_CAP1_U2PHY_EN(p) ((p) & BIT(24)) -/* - * USB2 PHY Interface type - * These field reflects type of USB2 PHY interface implemented: - * 0x0 - UTMI, - * 0x1 - ULPI - */ -#define DEV_U2PHY_ULPI(p) ((p) & BIT(25)) -/* - * USB2 PHY Interface width - * These field reflects width of USB2 PHY interface implemented: - * 0x0 - 8 bit interface, - * 0x1 - 16 bit interface, - * Note: The ULPI interface is always 8bit wide. - */ -#define DEV_U2PHY_WIDTH_16(p) ((p) & BIT(26)) -/* - * OTG Ready - * 0x0 - pure device mode - * 0x1 - some features and ports for CDNS USB OTG controller are implemented. - */ -#define USB_CAP1_OTG_READY(p) ((p) & BIT(27)) - -/* - * When set, indicates that controller supports automatic internal TDL - * calculation basing on the size provided in TRB (TRB[22:17]) for DMULT mode - * Supported only for DEV_VER_V2 controller version. - */ -#define USB_CAP1_TDL_FROM_TRB(p) ((p) & BIT(28)) - -/* USB_CAP2- bitmasks */ -/* - * The actual size of the connected On-chip RAM memory in kB: - * - 0 means 256 kB (max supported mem size) - * - value other than 0 reflects the mem size in kB - */ -#define USB_CAP2_ACTUAL_MEM_SIZE(p) ((p) & GENMASK(7, 0)) -/* - * Max supported mem size - * These field reflects width of on-chip RAM address bus width, - * which determines max supported mem size: - * 0x0-0x7 - reserved, - * 0x8 - support for 4kB mem, - * 0x9 - support for 8kB mem, - * 0xA - support for 16kB mem, - * 0xB - support for 32kB mem, - * 0xC - support for 64kB mem, - * 0xD - support for 128kB mem, - * 0xE - support for 256kB mem, - * 0xF - reserved - */ -#define USB_CAP2_MAX_MEM_SIZE(p) ((p) & GENMASK(11, 8)) - -/* USB_CAP3- bitmasks */ -#define EP_IS_IMPLEMENTED(reg, index) ((reg) & (1 << (index))) - -/* USB_CAP4- bitmasks */ -#define EP_SUPPORT_ISO(reg, index) ((reg) & (1 << (index))) - -/* USB_CAP5- bitmasks */ -#define EP_SUPPORT_STREAM(reg, index) ((reg) & (1 << (index))) - -/* USB_CAP6- bitmasks */ -/* The USBSS-DEV Controller Internal build number. */ -#define GET_DEV_BASE_VERSION(p) ((p) & GENMASK(23, 0)) -/* The USBSS-DEV Controller version number. */ -#define GET_DEV_CUSTOM_VERSION(p) ((p) & GENMASK(31, 24)) - -#define DEV_VER_NXP_V1 0x00024502 -#define DEV_VER_TI_V1 0x00024509 -#define DEV_VER_V2 0x0002450C -#define DEV_VER_V3 0x0002450d - -/* DBG_LINK1- bitmasks */ -/* - * LFPS_MIN_DET_U1_EXIT value This parameter configures the minimum - * time required for decoding the received LFPS as an LFPS.U1_Exit. - */ -#define DBG_LINK1_LFPS_MIN_DET_U1_EXIT(p) ((p) & GENMASK(7, 0)) -/* - * LFPS_MIN_GEN_U1_EXIT value This parameter configures the minimum time for - * phytxelecidle deassertion when LFPS.U1_Exit - */ -#define DBG_LINK1_LFPS_MIN_GEN_U1_EXIT_MASK GENMASK(15, 8) -#define DBG_LINK1_LFPS_MIN_GEN_U1_EXIT(p) (((p) << 8) & GENMASK(15, 8)) -/* - * RXDET_BREAK_DIS value This parameter configures terminating the Far-end - * Receiver termination detection sequence: - * 0: it is possible that USBSS_DEV will terminate Farend receiver - * termination detection sequence - * 1: USBSS_DEV will not terminate Far-end receiver termination - * detection sequence - */ -#define DBG_LINK1_RXDET_BREAK_DIS BIT(16) -/* LFPS_GEN_PING value This parameter configures the LFPS.Ping generation */ -#define DBG_LINK1_LFPS_GEN_PING(p) (((p) << 17) & GENMASK(21, 17)) -/* - * Set the LFPS_MIN_DET_U1_EXIT value Writing '1' to this bit writes the - * LFPS_MIN_DET_U1_EXIT field value to the device. This bit is automatically - * cleared. Writing '0' has no effect - */ -#define DBG_LINK1_LFPS_MIN_DET_U1_EXIT_SET BIT(24) -/* - * Set the LFPS_MIN_GEN_U1_EXIT value. Writing '1' to this bit writes the - * LFPS_MIN_GEN_U1_EXIT field value to the device. This bit is automatically - * cleared. Writing '0' has no effect - */ -#define DBG_LINK1_LFPS_MIN_GEN_U1_EXIT_SET BIT(25) -/* - * Set the RXDET_BREAK_DIS value Writing '1' to this bit writes - * the RXDET_BREAK_DIS field value to the device. This bit is automatically - * cleared. Writing '0' has no effect - */ -#define DBG_LINK1_RXDET_BREAK_DIS_SET BIT(26) -/* - * Set the LFPS_GEN_PING_SET value Writing '1' to this bit writes - * the LFPS_GEN_PING field value to the device. This bit is automatically - * cleared. Writing '0' has no effect." - */ -#define DBG_LINK1_LFPS_GEN_PING_SET BIT(27) - -/* DMA_AXI_CTRL- bitmasks */ -/* The mawprot pin configuration. */ -#define DMA_AXI_CTRL_MARPROT(p) ((p) & GENMASK(2, 0)) -/* The marprot pin configuration. */ -#define DMA_AXI_CTRL_MAWPROT(p) (((p) & GENMASK(2, 0)) << 16) -#define DMA_AXI_CTRL_NON_SECURE 0x02 - -#define gadget_to_cdns3_device(g) (container_of(g, struct cdns3_device, gadget)) - -#define ep_to_cdns3_ep(ep) (container_of(ep, struct cdns3_endpoint, endpoint)) - -/*-------------------------------------------------------------------------*/ -/* - * USBSS-DEV DMA interface. - */ -#define TRBS_PER_SEGMENT 600 - -#define ISO_MAX_INTERVAL 10 - -#define MAX_TRB_LENGTH BIT(16) - -#if TRBS_PER_SEGMENT < 2 -#error "Incorrect TRBS_PER_SEGMENT. Minimal Transfer Ring size is 2." -#endif - -#define TRBS_PER_STREAM_SEGMENT 2 - -#if TRBS_PER_STREAM_SEGMENT < 2 -#error "Incorrect TRBS_PER_STREAMS_SEGMENT. Minimal Transfer Ring size is 2." -#endif - -/* - *Only for ISOC endpoints - maximum number of TRBs is calculated as - * pow(2, bInterval-1) * number of usb requests. It is limitation made by - * driver to save memory. Controller must prepare TRB for each ITP even - * if bInterval > 1. It's the reason why driver needs so many TRBs for - * isochronous endpoints. - */ -#define TRBS_PER_ISOC_SEGMENT (ISO_MAX_INTERVAL * 8) - -#define GET_TRBS_PER_SEGMENT(ep_type) ((ep_type) == USB_ENDPOINT_XFER_ISOC ? \ - TRBS_PER_ISOC_SEGMENT : TRBS_PER_SEGMENT) -/** - * struct cdns3_trb - represent Transfer Descriptor block. - * @buffer: pointer to buffer data - * @length: length of data - * @control: control flags. - * - * This structure describes transfer block serviced by DMA module. - */ -struct cdns3_trb { - __le32 buffer; - __le32 length; - __le32 control; -}; - -#define TRB_SIZE (sizeof(struct cdns3_trb)) -#define TRB_RING_SIZE (TRB_SIZE * TRBS_PER_SEGMENT) -#define TRB_STREAM_RING_SIZE (TRB_SIZE * TRBS_PER_STREAM_SEGMENT) -#define TRB_ISO_RING_SIZE (TRB_SIZE * TRBS_PER_ISOC_SEGMENT) -#define TRB_CTRL_RING_SIZE (TRB_SIZE * 2) - -/* TRB bit mask */ -#define TRB_TYPE_BITMASK GENMASK(15, 10) -#define TRB_TYPE(p) ((p) << 10) -#define TRB_FIELD_TO_TYPE(p) (((p) & TRB_TYPE_BITMASK) >> 10) - -/* TRB type IDs */ -/* bulk, interrupt, isoc , and control data stage */ -#define TRB_NORMAL 1 -/* TRB for linking ring segments */ -#define TRB_LINK 6 - -/* Cycle bit - indicates TRB ownership by driver or hw*/ -#define TRB_CYCLE BIT(0) -/* - * When set to '1', the device will toggle its interpretation of the Cycle bit - */ -#define TRB_TOGGLE BIT(1) -/* - * The controller will set it if OUTSMM (OUT size mismatch) is detected, - * this bit is for normal TRB - */ -#define TRB_SMM BIT(1) - -/* - * Short Packet (SP). OUT EPs at DMULT=1 only. Indicates if the TRB was - * processed while USB short packet was received. No more buffers defined by - * the TD will be used. DMA will automatically advance to next TD. - * - Shall be set to 0 by Software when putting TRB on the Transfer Ring - * - Shall be set to 1 by Controller when Short Packet condition for this TRB - * is detected independent if ISP is set or not. - */ -#define TRB_SP BIT(1) - -/* Interrupt on short packet*/ -#define TRB_ISP BIT(2) -/*Setting this bit enables FIFO DMA operation mode*/ -#define TRB_FIFO_MODE BIT(3) -/* Set PCIe no snoop attribute */ -#define TRB_CHAIN BIT(4) -/* Interrupt on completion */ -#define TRB_IOC BIT(5) - -/* stream ID bitmasks. */ -#define TRB_STREAM_ID_BITMASK GENMASK(31, 16) -#define TRB_STREAM_ID(p) ((p) << 16) -#define TRB_FIELD_TO_STREAMID(p) (((p) & TRB_STREAM_ID_BITMASK) >> 16) - -/* Size of TD expressed in USB packets for HS/FS mode. */ -#define TRB_TDL_HS_SIZE(p) (((p) << 16) & GENMASK(31, 16)) -#define TRB_TDL_HS_SIZE_GET(p) (((p) & GENMASK(31, 16)) >> 16) - -/* transfer_len bitmasks. */ -#define TRB_LEN(p) ((p) & GENMASK(16, 0)) - -/* Size of TD expressed in USB packets for SS mode. */ -#define TRB_TDL_SS_SIZE(p) (((p) << 17) & GENMASK(23, 17)) -#define TRB_TDL_SS_SIZE_GET(p) (((p) & GENMASK(23, 17)) >> 17) - -/* transfer_len bitmasks - bits 31:24 */ -#define TRB_BURST_LEN(p) ((unsigned int)((p) << 24) & GENMASK(31, 24)) -#define TRB_BURST_LEN_GET(p) (((p) & GENMASK(31, 24)) >> 24) - -/* Data buffer pointer bitmasks*/ -#define TRB_BUFFER(p) ((p) & GENMASK(31, 0)) - -/*-------------------------------------------------------------------------*/ -/* Driver numeric constants */ - -/* Such declaration should be added to ch9.h */ -#define USB_DEVICE_MAX_ADDRESS 127 - -/* Endpoint init values */ -#define CDNS3_EP_MAX_PACKET_LIMIT 1024 -#define CDNS3_EP_MAX_STREAMS 15 -#define CDNS3_EP0_MAX_PACKET_LIMIT 512 - -/* All endpoints including EP0 */ -#define CDNS3_ENDPOINTS_MAX_COUNT 32 -#define CDNS3_EP_ZLP_BUF_SIZE 1024 - -#define CDNS3_EP_BUF_SIZE 4 /* KB */ -#define CDNS3_EP_ISO_HS_MULT 3 -#define CDNS3_EP_ISO_SS_BURST 3 -#define CDNS3_MAX_NUM_DESCMISS_BUF 32 -#define CDNS3_DESCMIS_BUF_SIZE 2048 /* Bytes */ -#define CDNS3_WA2_NUM_BUFFERS 128 -/*-------------------------------------------------------------------------*/ -/* Used structs */ - -struct cdns3_device; - -/** - * struct cdns3_endpoint - extended device side representation of USB endpoint. - * @endpoint: usb endpoint - * @pending_req_list: list of requests queuing on transfer ring. - * @deferred_req_list: list of requests waiting for queuing on transfer ring. - * @wa2_descmiss_req_list: list of requests internally allocated by driver. - * @trb_pool: transfer ring - array of transaction buffers - * @trb_pool_dma: dma address of transfer ring - * @cdns3_dev: device associated with this endpoint - * @name: a human readable name e.g. ep1out - * @flags: specify the current state of endpoint - * @descmis_req: internal transfer object used for getting data from on-chip - * buffer. It can happen only if function driver doesn't send usb_request - * object on time. - * @dir: endpoint direction - * @num: endpoint number (1 - 15) - * @type: set to bmAttributes & USB_ENDPOINT_XFERTYPE_MASK - * @interval: interval between packets used for ISOC endpoint. - * @free_trbs: number of free TRBs in transfer ring - * @num_trbs: number of all TRBs in transfer ring - * @alloc_ring_size: size of the allocated TRB ring - * @pcs: producer cycle state - * @ccs: consumer cycle state - * @enqueue: enqueue index in transfer ring - * @dequeue: dequeue index in transfer ring - * @trb_burst_size: number of burst used in trb. - */ -struct cdns3_endpoint { - struct usb_ep endpoint; - struct list_head pending_req_list; - struct list_head deferred_req_list; - struct list_head wa2_descmiss_req_list; - int wa2_counter; - - struct cdns3_trb *trb_pool; - dma_addr_t trb_pool_dma; - - struct cdns3_device *cdns3_dev; - char name[20]; - -#define EP_ENABLED BIT(0) -#define EP_STALLED BIT(1) -#define EP_STALL_PENDING BIT(2) -#define EP_WEDGE BIT(3) -#define EP_TRANSFER_STARTED BIT(4) -#define EP_UPDATE_EP_TRBADDR BIT(5) -#define EP_PENDING_REQUEST BIT(6) -#define EP_RING_FULL BIT(7) -#define EP_CLAIMED BIT(8) -#define EP_DEFERRED_DRDY BIT(9) -#define EP_QUIRK_ISO_OUT_EN BIT(10) -#define EP_QUIRK_END_TRANSFER BIT(11) -#define EP_QUIRK_EXTRA_BUF_DET BIT(12) -#define EP_QUIRK_EXTRA_BUF_EN BIT(13) -#define EP_TDLCHK_EN BIT(15) -#define EP_CONFIGURED BIT(16) - u32 flags; - - struct cdns3_request *descmis_req; - - u8 dir; - u8 num; - u8 type; - int interval; - - int free_trbs; - int num_trbs; - int alloc_ring_size; - u8 pcs; - u8 ccs; - int enqueue; - int dequeue; - u8 trb_burst_size; - - unsigned int wa1_set:1; - struct cdns3_trb *wa1_trb; - unsigned int wa1_trb_index; - unsigned int wa1_cycle_bit:1; - - /* Stream related */ - unsigned int use_streams:1; - unsigned int prime_flag:1; - u32 ep_sts_pending; - u16 last_stream_id; - u16 pending_tdl; - unsigned int stream_sg_idx; -}; - -/** - * struct cdns3_aligned_buf - represent aligned buffer used for DMA transfer - * @buf: aligned to 8 bytes data buffer. Buffer address used in - * TRB shall be aligned to 8. - * @dma: dma address - * @size: size of buffer - * @in_use: inform if this buffer is associated with usb_request - * @list: used to adding instance of this object to list - */ -struct cdns3_aligned_buf { - void *buf; - dma_addr_t dma; - u32 size; - unsigned in_use:1; - struct list_head list; -}; - -/** - * struct cdns3_request - extended device side representation of usb_request - * object . - * @request: generic usb_request object describing single I/O request. - * @priv_ep: extended representation of usb_ep object - * @trb: the first TRB association with this request - * @start_trb: number of the first TRB in transfer ring - * @end_trb: number of the last TRB in transfer ring - * @aligned_buf: object holds information about aligned buffer associated whit - * this endpoint - * @flags: flag specifying special usage of request - * @list: used by internally allocated request to add to wa2_descmiss_req_list. - * @finished_trb: number of trb has already finished per request - * @num_of_trb: how many trbs in this request - */ -struct cdns3_request { - struct usb_request request; - struct cdns3_endpoint *priv_ep; - struct cdns3_trb *trb; - int start_trb; - int end_trb; - struct cdns3_aligned_buf *aligned_buf; -#define REQUEST_PENDING BIT(0) -#define REQUEST_INTERNAL BIT(1) -#define REQUEST_INTERNAL_CH BIT(2) -#define REQUEST_ZLP BIT(3) -#define REQUEST_UNALIGNED BIT(4) - u32 flags; - struct list_head list; - int finished_trb; - int num_of_trb; -}; - -#define to_cdns3_request(r) (container_of(r, struct cdns3_request, request)) - -/*Stages used during enumeration process.*/ -#define CDNS3_SETUP_STAGE 0x0 -#define CDNS3_DATA_STAGE 0x1 -#define CDNS3_STATUS_STAGE 0x2 - -/** - * struct cdns3_device - represent USB device. - * @dev: pointer to device structure associated whit this controller - * @sysdev: pointer to the DMA capable device - * @gadget: device side representation of the peripheral controller - * @gadget_driver: pointer to the gadget driver - * @dev_ver: device controller version. - * @lock: for synchronizing - * @regs: base address for device side registers - * @setup_buf: used while processing usb control requests - * @setup_dma: dma address for setup_buf - * @zlp_buf - zlp buffer - * @ep0_stage: ep0 stage during enumeration process. - * @ep0_data_dir: direction for control transfer - * @eps: array of pointers to all endpoints with exclusion ep0 - * @aligned_buf_list: list of aligned buffers internally allocated by driver - * @aligned_buf_wq: workqueue freeing no longer used aligned buf. - * @selected_ep: actually selected endpoint. It's used only to improve - * performance. - * @isoch_delay: value from Set Isoch Delay request. Only valid on SS/SSP. - * @u1_allowed: allow device transition to u1 state - * @u2_allowed: allow device transition to u2 state - * @is_selfpowered: device is self powered - * @setup_pending: setup packet is processing by gadget driver - * @hw_configured_flag: hardware endpoint configuration was set. - * @wake_up_flag: allow device to remote up the host - * @status_completion_no_call: indicate that driver is waiting for status s - * stage completion. It's used in deferred SET_CONFIGURATION request. - * @onchip_buffers: number of available on-chip buffers. - * @onchip_used_size: actual size of on-chip memory assigned to endpoints. - * @pending_status_wq: workqueue handling status stage for deferred requests. - * @pending_status_request: request for which status stage was deferred - */ -struct cdns3_device { - struct device *dev; - struct device *sysdev; - - struct usb_gadget gadget; - struct usb_gadget_driver *gadget_driver; - -#define CDNS_REVISION_V0 0x00024501 -#define CDNS_REVISION_V1 0x00024509 - u32 dev_ver; - - /* generic spin-lock for drivers */ - spinlock_t lock; - - struct cdns3_usb_regs __iomem *regs; - - struct usb_ctrlrequest *setup_buf; - dma_addr_t setup_dma; - void *zlp_buf; - - u8 ep0_stage; - int ep0_data_dir; - - struct cdns3_endpoint *eps[CDNS3_ENDPOINTS_MAX_COUNT]; - - struct list_head aligned_buf_list; - struct work_struct aligned_buf_wq; - - u32 selected_ep; - u16 isoch_delay; - - unsigned wait_for_setup:1; - unsigned u1_allowed:1; - unsigned u2_allowed:1; - unsigned is_selfpowered:1; - unsigned setup_pending:1; - unsigned hw_configured_flag:1; - unsigned wake_up_flag:1; - unsigned status_completion_no_call:1; - unsigned using_streams:1; - int out_mem_is_allocated; - - struct work_struct pending_status_wq; - struct usb_request *pending_status_request; - - /*in KB */ - u16 onchip_buffers; - u16 onchip_used_size; -}; - -void cdns3_set_register_bit(void __iomem *ptr, u32 mask); -dma_addr_t cdns3_trb_virt_to_dma(struct cdns3_endpoint *priv_ep, - struct cdns3_trb *trb); -enum usb_device_speed cdns3_get_speed(struct cdns3_device *priv_dev); -void cdns3_pending_setup_status_handler(struct work_struct *work); -void cdns3_hw_reset_eps_config(struct cdns3_device *priv_dev); -void cdns3_set_hw_configuration(struct cdns3_device *priv_dev); -void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep); -void cdns3_allow_enable_l1(struct cdns3_device *priv_dev, int enable); -struct usb_request *cdns3_next_request(struct list_head *list); -void cdns3_rearm_transfer(struct cdns3_endpoint *priv_ep, u8 rearm); -int cdns3_allocate_trb_pool(struct cdns3_endpoint *priv_ep); -u8 cdns3_ep_addr_to_index(u8 ep_addr); -int cdns3_gadget_ep_set_wedge(struct usb_ep *ep); -int cdns3_gadget_ep_set_halt(struct usb_ep *ep, int value); -void __cdns3_gadget_ep_set_halt(struct cdns3_endpoint *priv_ep); -int __cdns3_gadget_ep_clear_halt(struct cdns3_endpoint *priv_ep); -struct usb_request *cdns3_gadget_ep_alloc_request(struct usb_ep *ep, - gfp_t gfp_flags); -void cdns3_gadget_ep_free_request(struct usb_ep *ep, - struct usb_request *request); -int cdns3_gadget_ep_dequeue(struct usb_ep *ep, struct usb_request *request); -void cdns3_gadget_giveback(struct cdns3_endpoint *priv_ep, - struct cdns3_request *priv_req, - int status); - -int cdns3_init_ep0(struct cdns3_device *priv_dev, - struct cdns3_endpoint *priv_ep); -void cdns3_ep0_config(struct cdns3_device *priv_dev); -int cdns3_ep_config(struct cdns3_endpoint *priv_ep, bool enable); -void cdns3_check_ep0_interrupt_proceed(struct cdns3_device *priv_dev, int dir); -int __cdns3_gadget_wakeup(struct cdns3_device *priv_dev); - -#endif /* __LINUX_CDNS3_GADGET */ diff --git a/drivers/usb/cdns3/trace.c b/drivers/usb/cdns3/trace.c deleted file mode 100644 index 459fa72d9c74..000000000000 --- a/drivers/usb/cdns3/trace.c +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * USBSS device controller driver Trace Support - * - * Copyright (C) 2018-2019 Cadence. - * - * Author: Pawel Laszczak - */ - -#define CREATE_TRACE_POINTS -#include "trace.h" diff --git a/drivers/usb/cdns3/trace.h b/drivers/usb/cdns3/trace.h deleted file mode 100644 index 0a2a3269bfac..000000000000 --- a/drivers/usb/cdns3/trace.h +++ /dev/null @@ -1,570 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * USBSS device controller driver. - * Trace support header file. - * - * Copyright (C) 2018-2019 Cadence. - * - * Author: Pawel Laszczak - */ - -#undef TRACE_SYSTEM -#define TRACE_SYSTEM cdns3 - -#if !defined(__LINUX_CDNS3_TRACE) || defined(TRACE_HEADER_MULTI_READ) -#define __LINUX_CDNS3_TRACE - -#include -#include -#include -#include -#include "core.h" -#include "gadget.h" -#include "debug.h" - -#define CDNS3_MSG_MAX 500 - -TRACE_EVENT(cdns3_halt, - TP_PROTO(struct cdns3_endpoint *ep_priv, u8 halt, u8 flush), - TP_ARGS(ep_priv, halt, flush), - TP_STRUCT__entry( - __string(name, ep_priv->name) - __field(u8, halt) - __field(u8, flush) - ), - TP_fast_assign( - __assign_str(name, ep_priv->name); - __entry->halt = halt; - __entry->flush = flush; - ), - TP_printk("Halt %s for %s: %s", __entry->flush ? " and flush" : "", - __get_str(name), __entry->halt ? "set" : "cleared") -); - -TRACE_EVENT(cdns3_wa1, - TP_PROTO(struct cdns3_endpoint *ep_priv, char *msg), - TP_ARGS(ep_priv, msg), - TP_STRUCT__entry( - __string(ep_name, ep_priv->name) - __string(msg, msg) - ), - TP_fast_assign( - __assign_str(ep_name, ep_priv->name); - __assign_str(msg, msg); - ), - TP_printk("WA1: %s %s", __get_str(ep_name), __get_str(msg)) -); - -TRACE_EVENT(cdns3_wa2, - TP_PROTO(struct cdns3_endpoint *ep_priv, char *msg), - TP_ARGS(ep_priv, msg), - TP_STRUCT__entry( - __string(ep_name, ep_priv->name) - __string(msg, msg) - ), - TP_fast_assign( - __assign_str(ep_name, ep_priv->name); - __assign_str(msg, msg); - ), - TP_printk("WA2: %s %s", __get_str(ep_name), __get_str(msg)) -); - -DECLARE_EVENT_CLASS(cdns3_log_doorbell, - TP_PROTO(const char *ep_name, u32 ep_trbaddr), - TP_ARGS(ep_name, ep_trbaddr), - TP_STRUCT__entry( - __string(name, ep_name) - __field(u32, ep_trbaddr) - ), - TP_fast_assign( - __assign_str(name, ep_name); - __entry->ep_trbaddr = ep_trbaddr; - ), - TP_printk("%s, ep_trbaddr %08x", __get_str(name), - __entry->ep_trbaddr) -); - -DEFINE_EVENT(cdns3_log_doorbell, cdns3_doorbell_ep0, - TP_PROTO(const char *ep_name, u32 ep_trbaddr), - TP_ARGS(ep_name, ep_trbaddr) -); - -DEFINE_EVENT(cdns3_log_doorbell, cdns3_doorbell_epx, - TP_PROTO(const char *ep_name, u32 ep_trbaddr), - TP_ARGS(ep_name, ep_trbaddr) -); - -DECLARE_EVENT_CLASS(cdns3_log_usb_irq, - TP_PROTO(struct cdns3_device *priv_dev, u32 usb_ists), - TP_ARGS(priv_dev, usb_ists), - TP_STRUCT__entry( - __field(enum usb_device_speed, speed) - __field(u32, usb_ists) - __dynamic_array(char, str, CDNS3_MSG_MAX) - ), - TP_fast_assign( - __entry->speed = cdns3_get_speed(priv_dev); - __entry->usb_ists = usb_ists; - ), - TP_printk("%s", cdns3_decode_usb_irq(__get_str(str), __entry->speed, - __entry->usb_ists)) -); - -DEFINE_EVENT(cdns3_log_usb_irq, cdns3_usb_irq, - TP_PROTO(struct cdns3_device *priv_dev, u32 usb_ists), - TP_ARGS(priv_dev, usb_ists) -); - -DECLARE_EVENT_CLASS(cdns3_log_epx_irq, - TP_PROTO(struct cdns3_device *priv_dev, struct cdns3_endpoint *priv_ep), - TP_ARGS(priv_dev, priv_ep), - TP_STRUCT__entry( - __string(ep_name, priv_ep->name) - __field(u32, ep_sts) - __field(u32, ep_traddr) - __field(u32, ep_last_sid) - __field(u32, use_streams) - __dynamic_array(char, str, CDNS3_MSG_MAX) - ), - TP_fast_assign( - __assign_str(ep_name, priv_ep->name); - __entry->ep_sts = readl(&priv_dev->regs->ep_sts); - __entry->ep_traddr = readl(&priv_dev->regs->ep_traddr); - __entry->ep_last_sid = priv_ep->last_stream_id; - __entry->use_streams = priv_ep->use_streams; - ), - TP_printk("%s, ep_traddr: %08x ep_last_sid: %08x use_streams: %d", - cdns3_decode_epx_irq(__get_str(str), - __get_str(ep_name), - __entry->ep_sts), - __entry->ep_traddr, - __entry->ep_last_sid, - __entry->use_streams) -); - -DEFINE_EVENT(cdns3_log_epx_irq, cdns3_epx_irq, - TP_PROTO(struct cdns3_device *priv_dev, struct cdns3_endpoint *priv_ep), - TP_ARGS(priv_dev, priv_ep) -); - -DECLARE_EVENT_CLASS(cdns3_log_ep0_irq, - TP_PROTO(struct cdns3_device *priv_dev, u32 ep_sts), - TP_ARGS(priv_dev, ep_sts), - TP_STRUCT__entry( - __field(int, ep_dir) - __field(u32, ep_sts) - __dynamic_array(char, str, CDNS3_MSG_MAX) - ), - TP_fast_assign( - __entry->ep_dir = priv_dev->selected_ep; - __entry->ep_sts = ep_sts; - ), - TP_printk("%s", cdns3_decode_ep0_irq(__get_str(str), - __entry->ep_dir, - __entry->ep_sts)) -); - -DEFINE_EVENT(cdns3_log_ep0_irq, cdns3_ep0_irq, - TP_PROTO(struct cdns3_device *priv_dev, u32 ep_sts), - TP_ARGS(priv_dev, ep_sts) -); - -DECLARE_EVENT_CLASS(cdns3_log_ctrl, - TP_PROTO(struct usb_ctrlrequest *ctrl), - TP_ARGS(ctrl), - TP_STRUCT__entry( - __field(u8, bRequestType) - __field(u8, bRequest) - __field(u16, wValue) - __field(u16, wIndex) - __field(u16, wLength) - __dynamic_array(char, str, CDNS3_MSG_MAX) - ), - TP_fast_assign( - __entry->bRequestType = ctrl->bRequestType; - __entry->bRequest = ctrl->bRequest; - __entry->wValue = le16_to_cpu(ctrl->wValue); - __entry->wIndex = le16_to_cpu(ctrl->wIndex); - __entry->wLength = le16_to_cpu(ctrl->wLength); - ), - TP_printk("%s", usb_decode_ctrl(__get_str(str), CDNS3_MSG_MAX, - __entry->bRequestType, - __entry->bRequest, __entry->wValue, - __entry->wIndex, __entry->wLength) - ) -); - -DEFINE_EVENT(cdns3_log_ctrl, cdns3_ctrl_req, - TP_PROTO(struct usb_ctrlrequest *ctrl), - TP_ARGS(ctrl) -); - -DECLARE_EVENT_CLASS(cdns3_log_request, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req), - TP_STRUCT__entry( - __string(name, req->priv_ep->name) - __field(struct cdns3_request *, req) - __field(void *, buf) - __field(unsigned int, actual) - __field(unsigned int, length) - __field(int, status) - __field(int, zero) - __field(int, short_not_ok) - __field(int, no_interrupt) - __field(int, start_trb) - __field(int, end_trb) - __field(struct cdns3_trb *, start_trb_addr) - __field(int, flags) - __field(unsigned int, stream_id) - ), - TP_fast_assign( - __assign_str(name, req->priv_ep->name); - __entry->req = req; - __entry->buf = req->request.buf; - __entry->actual = req->request.actual; - __entry->length = req->request.length; - __entry->status = req->request.status; - __entry->zero = req->request.zero; - __entry->short_not_ok = req->request.short_not_ok; - __entry->no_interrupt = req->request.no_interrupt; - __entry->start_trb = req->start_trb; - __entry->end_trb = req->end_trb; - __entry->start_trb_addr = req->trb; - __entry->flags = req->flags; - __entry->stream_id = req->request.stream_id; - ), - TP_printk("%s: req: %p, req buff %p, length: %u/%u %s%s%s, status: %d," - " trb: [start:%d, end:%d: virt addr %pa], flags:%x SID: %u", - __get_str(name), __entry->req, __entry->buf, __entry->actual, - __entry->length, - __entry->zero ? "Z" : "z", - __entry->short_not_ok ? "S" : "s", - __entry->no_interrupt ? "I" : "i", - __entry->status, - __entry->start_trb, - __entry->end_trb, - __entry->start_trb_addr, - __entry->flags, - __entry->stream_id - ) -); - -DEFINE_EVENT(cdns3_log_request, cdns3_alloc_request, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req) -); - -DEFINE_EVENT(cdns3_log_request, cdns3_free_request, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req) -); - -DEFINE_EVENT(cdns3_log_request, cdns3_ep_queue, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req) -); - -DEFINE_EVENT(cdns3_log_request, cdns3_ep_dequeue, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req) -); - -DEFINE_EVENT(cdns3_log_request, cdns3_gadget_giveback, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req) -); - -TRACE_EVENT(cdns3_ep0_queue, - TP_PROTO(struct cdns3_device *dev_priv, struct usb_request *request), - TP_ARGS(dev_priv, request), - TP_STRUCT__entry( - __field(int, dir) - __field(int, length) - ), - TP_fast_assign( - __entry->dir = dev_priv->ep0_data_dir; - __entry->length = request->length; - ), - TP_printk("Queue to ep0%s length: %u", __entry->dir ? "in" : "out", - __entry->length) -); - -DECLARE_EVENT_CLASS(cdns3_stream_split_transfer_len, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req), - TP_STRUCT__entry( - __string(name, req->priv_ep->name) - __field(struct cdns3_request *, req) - __field(unsigned int, length) - __field(unsigned int, actual) - __field(unsigned int, stream_id) - ), - TP_fast_assign( - __assign_str(name, req->priv_ep->name); - __entry->req = req; - __entry->actual = req->request.length; - __entry->length = req->request.actual; - __entry->stream_id = req->request.stream_id; - ), - TP_printk("%s: req: %p,request length: %u actual length: %u SID: %u", - __get_str(name), __entry->req, __entry->length, - __entry->actual, __entry->stream_id) -); - -DEFINE_EVENT(cdns3_stream_split_transfer_len, cdns3_stream_transfer_split, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req) -); - -DEFINE_EVENT(cdns3_stream_split_transfer_len, - cdns3_stream_transfer_split_next_part, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req) -); - -DECLARE_EVENT_CLASS(cdns3_log_aligned_request, - TP_PROTO(struct cdns3_request *priv_req), - TP_ARGS(priv_req), - TP_STRUCT__entry( - __string(name, priv_req->priv_ep->name) - __field(struct usb_request *, req) - __field(void *, buf) - __field(dma_addr_t, dma) - __field(void *, aligned_buf) - __field(dma_addr_t, aligned_dma) - __field(u32, aligned_buf_size) - ), - TP_fast_assign( - __assign_str(name, priv_req->priv_ep->name); - __entry->req = &priv_req->request; - __entry->buf = priv_req->request.buf; - __entry->dma = priv_req->request.dma; - __entry->aligned_buf = priv_req->aligned_buf->buf; - __entry->aligned_dma = priv_req->aligned_buf->dma; - __entry->aligned_buf_size = priv_req->aligned_buf->size; - ), - TP_printk("%s: req: %p, req buf %p, dma %pad a_buf %p a_dma %pad, size %d", - __get_str(name), __entry->req, __entry->buf, &__entry->dma, - __entry->aligned_buf, &__entry->aligned_dma, - __entry->aligned_buf_size - ) -); - -DEFINE_EVENT(cdns3_log_aligned_request, cdns3_free_aligned_request, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req) -); - -DEFINE_EVENT(cdns3_log_aligned_request, cdns3_prepare_aligned_request, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req) -); - -DECLARE_EVENT_CLASS(cdns3_log_map_request, - TP_PROTO(struct cdns3_request *priv_req), - TP_ARGS(priv_req), - TP_STRUCT__entry( - __string(name, priv_req->priv_ep->name) - __field(struct usb_request *, req) - __field(void *, buf) - __field(dma_addr_t, dma) - ), - TP_fast_assign( - __assign_str(name, priv_req->priv_ep->name); - __entry->req = &priv_req->request; - __entry->buf = priv_req->request.buf; - __entry->dma = priv_req->request.dma; - ), - TP_printk("%s: req: %p, req buf %p, dma %p", - __get_str(name), __entry->req, __entry->buf, &__entry->dma - ) -); -DEFINE_EVENT(cdns3_log_map_request, cdns3_map_request, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req) -); -DEFINE_EVENT(cdns3_log_map_request, cdns3_mapped_request, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req) -); - -DECLARE_EVENT_CLASS(cdns3_log_trb, - TP_PROTO(struct cdns3_endpoint *priv_ep, struct cdns3_trb *trb), - TP_ARGS(priv_ep, trb), - TP_STRUCT__entry( - __string(name, priv_ep->name) - __field(struct cdns3_trb *, trb) - __field(u32, buffer) - __field(u32, length) - __field(u32, control) - __field(u32, type) - __field(unsigned int, last_stream_id) - ), - TP_fast_assign( - __assign_str(name, priv_ep->name); - __entry->trb = trb; - __entry->buffer = le32_to_cpu(trb->buffer); - __entry->length = le32_to_cpu(trb->length); - __entry->control = le32_to_cpu(trb->control); - __entry->type = usb_endpoint_type(priv_ep->endpoint.desc); - __entry->last_stream_id = priv_ep->last_stream_id; - ), - TP_printk("%s: trb %p, dma buf: 0x%08x, size: %ld, burst: %d ctrl: 0x%08x (%s%s%s%s%s%s%s) SID:%lu LAST_SID:%u", - __get_str(name), __entry->trb, __entry->buffer, - TRB_LEN(__entry->length), - (u8)TRB_BURST_LEN_GET(__entry->length), - __entry->control, - __entry->control & TRB_CYCLE ? "C=1, " : "C=0, ", - __entry->control & TRB_TOGGLE ? "T=1, " : "T=0, ", - __entry->control & TRB_ISP ? "ISP, " : "", - __entry->control & TRB_FIFO_MODE ? "FIFO, " : "", - __entry->control & TRB_CHAIN ? "CHAIN, " : "", - __entry->control & TRB_IOC ? "IOC, " : "", - TRB_FIELD_TO_TYPE(__entry->control) == TRB_NORMAL ? "Normal" : "LINK", - TRB_FIELD_TO_STREAMID(__entry->control), - __entry->last_stream_id - ) -); - -DEFINE_EVENT(cdns3_log_trb, cdns3_prepare_trb, - TP_PROTO(struct cdns3_endpoint *priv_ep, struct cdns3_trb *trb), - TP_ARGS(priv_ep, trb) -); - -DEFINE_EVENT(cdns3_log_trb, cdns3_complete_trb, - TP_PROTO(struct cdns3_endpoint *priv_ep, struct cdns3_trb *trb), - TP_ARGS(priv_ep, trb) -); - -DECLARE_EVENT_CLASS(cdns3_log_ring, - TP_PROTO(struct cdns3_endpoint *priv_ep), - TP_ARGS(priv_ep), - TP_STRUCT__entry( - __dynamic_array(u8, ring, TRB_RING_SIZE) - __dynamic_array(u8, priv_ep, sizeof(struct cdns3_endpoint)) - __dynamic_array(char, buffer, - (TRBS_PER_SEGMENT * 65) + CDNS3_MSG_MAX) - ), - TP_fast_assign( - memcpy(__get_dynamic_array(priv_ep), priv_ep, - sizeof(struct cdns3_endpoint)); - memcpy(__get_dynamic_array(ring), priv_ep->trb_pool, - TRB_RING_SIZE); - ), - - TP_printk("%s", - cdns3_dbg_ring((struct cdns3_endpoint *)__get_str(priv_ep), - (struct cdns3_trb *)__get_str(ring), - __get_str(buffer))) -); - -DEFINE_EVENT(cdns3_log_ring, cdns3_ring, - TP_PROTO(struct cdns3_endpoint *priv_ep), - TP_ARGS(priv_ep) -); - -DECLARE_EVENT_CLASS(cdns3_log_ep, - TP_PROTO(struct cdns3_endpoint *priv_ep), - TP_ARGS(priv_ep), - TP_STRUCT__entry( - __string(name, priv_ep->name) - __field(unsigned int, maxpacket) - __field(unsigned int, maxpacket_limit) - __field(unsigned int, max_streams) - __field(unsigned int, use_streams) - __field(unsigned int, maxburst) - __field(unsigned int, flags) - __field(unsigned int, dir) - __field(u8, enqueue) - __field(u8, dequeue) - ), - TP_fast_assign( - __assign_str(name, priv_ep->name); - __entry->maxpacket = priv_ep->endpoint.maxpacket; - __entry->maxpacket_limit = priv_ep->endpoint.maxpacket_limit; - __entry->max_streams = priv_ep->endpoint.max_streams; - __entry->use_streams = priv_ep->use_streams; - __entry->maxburst = priv_ep->endpoint.maxburst; - __entry->flags = priv_ep->flags; - __entry->dir = priv_ep->dir; - __entry->enqueue = priv_ep->enqueue; - __entry->dequeue = priv_ep->dequeue; - ), - TP_printk("%s: mps: %d/%d. streams: %d, stream enable: %d, burst: %d, " - "enq idx: %d, deq idx: %d, flags %s%s%s%s%s%s%s%s, dir: %s", - __get_str(name), __entry->maxpacket, - __entry->maxpacket_limit, __entry->max_streams, - __entry->use_streams, - __entry->maxburst, __entry->enqueue, - __entry->dequeue, - __entry->flags & EP_ENABLED ? "EN | " : "", - __entry->flags & EP_STALLED ? "STALLED | " : "", - __entry->flags & EP_WEDGE ? "WEDGE | " : "", - __entry->flags & EP_TRANSFER_STARTED ? "STARTED | " : "", - __entry->flags & EP_UPDATE_EP_TRBADDR ? "UPD TRB | " : "", - __entry->flags & EP_PENDING_REQUEST ? "REQ PEN | " : "", - __entry->flags & EP_RING_FULL ? "RING FULL |" : "", - __entry->flags & EP_CLAIMED ? "CLAIMED " : "", - __entry->dir ? "IN" : "OUT" - ) -); - -DEFINE_EVENT(cdns3_log_ep, cdns3_gadget_ep_enable, - TP_PROTO(struct cdns3_endpoint *priv_ep), - TP_ARGS(priv_ep) -); - -DEFINE_EVENT(cdns3_log_ep, cdns3_gadget_ep_disable, - TP_PROTO(struct cdns3_endpoint *priv_ep), - TP_ARGS(priv_ep) -); - -DECLARE_EVENT_CLASS(cdns3_log_request_handled, - TP_PROTO(struct cdns3_request *priv_req, int current_index, - int handled), - TP_ARGS(priv_req, current_index, handled), - TP_STRUCT__entry( - __field(struct cdns3_request *, priv_req) - __field(unsigned int, dma_position) - __field(unsigned int, handled) - __field(unsigned int, dequeue_idx) - __field(unsigned int, enqueue_idx) - __field(unsigned int, start_trb) - __field(unsigned int, end_trb) - ), - TP_fast_assign( - __entry->priv_req = priv_req; - __entry->dma_position = current_index; - __entry->handled = handled; - __entry->dequeue_idx = priv_req->priv_ep->dequeue; - __entry->enqueue_idx = priv_req->priv_ep->enqueue; - __entry->start_trb = priv_req->start_trb; - __entry->end_trb = priv_req->end_trb; - ), - TP_printk("Req: %p %s, DMA pos: %d, ep deq: %d, ep enq: %d," - " start trb: %d, end trb: %d", - __entry->priv_req, - __entry->handled ? "handled" : "not handled", - __entry->dma_position, __entry->dequeue_idx, - __entry->enqueue_idx, __entry->start_trb, - __entry->end_trb - ) -); - -DEFINE_EVENT(cdns3_log_request_handled, cdns3_request_handled, - TP_PROTO(struct cdns3_request *priv_req, int current_index, - int handled), - TP_ARGS(priv_req, current_index, handled) -); -#endif /* __LINUX_CDNS3_TRACE */ - -/* this part must be outside header guard */ - -#undef TRACE_INCLUDE_PATH -#define TRACE_INCLUDE_PATH . - -#undef TRACE_INCLUDE_FILE -#define TRACE_INCLUDE_FILE trace - -#include -- cgit v1.2.3 From bbf60daee7de3969502f8b8a67f9f2dca80c94c6 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Mon, 7 Dec 2020 11:32:27 +0100 Subject: MAINTAINERS: add Cadence USBSSP DRD IP driver entry Patch adds entry for USBSSP (CDNSP) driver into MAINTARNERS file. Signed-off-by: Pawel Laszczak Signed-off-by: Peter Chen --- MAINTAINERS | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 546aa66428c9..f8bf5457306f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3890,6 +3890,15 @@ S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/usb.git F: Documentation/devicetree/bindings/usb/cdns,usb3.yaml F: drivers/usb/cdns3/ +X: drivers/usb/cdns3/cdnsp* + +CADENCE USBSSP DRD IP DRIVER +M: Pawel Laszczak +L: linux-usb@vger.kernel.org +S: Maintained +T: git git://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/usb.git +F: drivers/usb/cdns3/ +X: drivers/usb/cdns3/cdns3* CADET FM/AM RADIO RECEIVER DRIVER M: Hans Verkuil -- cgit v1.2.3 From 7650778e79b9daf09cfccb46445036ae0ea984ac Mon Sep 17 00:00:00 2001 From: Zou Wei Date: Tue, 8 Dec 2020 20:30:49 +0800 Subject: usb: cdnsp: Mark cdnsp_gadget_ops with static keyword Fix the following sparse warning: drivers/usb/cdns3/cdnsp-gadget.c:1546:29: warning: symbol 'cdnsp_gadget_ops' was not declared. Should it be static? Signed-off-by: Zou Wei Signed-off-by: Peter Chen --- drivers/usb/cdns3/cdnsp-gadget.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c index 97162739a5ae..c7c5d0a29f4d 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.c +++ b/drivers/usb/cdns3/cdnsp-gadget.c @@ -1543,7 +1543,7 @@ static int cdnsp_gadget_pullup(struct usb_gadget *gadget, int is_on) return 0; } -const struct usb_gadget_ops cdnsp_gadget_ops = { +static const struct usb_gadget_ops cdnsp_gadget_ops = { .get_frame = cdnsp_gadget_get_frame, .wakeup = cdnsp_gadget_wakeup, .set_selfpowered = cdnsp_gadget_set_selfpowered, -- cgit v1.2.3 From 28a25ba3e59263a66b2a2cd3e153351b2db2a6b6 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Tue, 8 Dec 2020 11:55:35 -0800 Subject: usb: cdns3: fix build when PM_SLEEP is not set Fix build error when CONFIG_PM_SLEEP is not enabled by adding stubs for the PM_SLEEP functions. ../drivers/usb/cdns3/cdns3-plat.c: In function ‘cdns3_controller_resume’: ../drivers/usb/cdns3/cdns3-plat.c:246:2: error: implicit declaration of function ‘cdns_resume’; did you mean ‘cdns_remove’? [-Werror=implicit-function-declaration] cdns_resume(cdns, !PMSG_IS_AUTO(msg)); ^~~~~~~~~~~ Signed-off-by: Randy Dunlap Cc: Peter Chen Cc: Pawel Laszczak Cc: Roger Quadros Cc: linux-usb@vger.kernel.org Cc: Greg Kroah-Hartman Signed-off-by: Peter Chen --- drivers/usb/cdns3/core.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h index 56b568678980..f8e350cef699 100644 --- a/drivers/usb/cdns3/core.h +++ b/drivers/usb/cdns3/core.h @@ -127,5 +127,10 @@ int cdns_remove(struct cdns *cdns); #ifdef CONFIG_PM_SLEEP int cdns_resume(struct cdns *cdns, u8 set_active); int cdns_suspend(struct cdns *cdns); +#else /* CONFIG_PM_SLEEP */ +static inline int cdns_resume(struct cdns *cdns, u8 set_active) +{ return 0; } +static inline int cdns_suspend(struct cdns *cdns) +{ return 0; } #endif /* CONFIG_PM_SLEEP */ #endif /* __LINUX_CDNS3_CORE_H */ -- cgit v1.2.3 From b13f020d0c9f03d8c6f63f06ac4cfea76a212afb Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Tue, 8 Dec 2020 11:55:47 -0800 Subject: usb: cdns3: fix warning when USB_CDNS_HOST is not set Fix a build warning when USB_CDNS_HOST is not enabled: In file included from ../drivers/usb/cdns3/core.c:23:0: ../drivers/usb/cdns3/host-export.h:27:51: warning: ‘struct usb_hcd’ declared inside parameter list will not be visible outside of this definition or declaration static inline int xhci_cdns3_suspend_quirk(struct usb_hcd *hcd) ^~~~~~~ Signed-off-by: Randy Dunlap Cc: Peter Chen Cc: Pawel Laszczak Cc: Roger Quadros Cc: linux-usb@vger.kernel.org Cc: Greg Kroah-Hartman Signed-off-by: Peter Chen --- drivers/usb/cdns3/host-export.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/cdns3/host-export.h b/drivers/usb/cdns3/host-export.h index 087e1921e1f5..fb8541b8adbc 100644 --- a/drivers/usb/cdns3/host-export.h +++ b/drivers/usb/cdns3/host-export.h @@ -9,10 +9,10 @@ #ifndef __LINUX_CDNS3_HOST_EXPORT #define __LINUX_CDNS3_HOST_EXPORT -#if IS_ENABLED(CONFIG_USB_CDNS_HOST) - struct usb_hcd; +#if IS_ENABLED(CONFIG_USB_CDNS_HOST) + int cdns_host_init(struct cdns *cdns); int xhci_cdns3_suspend_quirk(struct usb_hcd *hcd); -- cgit v1.2.3 From dc68ba6c72366e0402fbcc4783a8b6ab610265df Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Wed, 9 Dec 2020 13:18:58 +0100 Subject: usb: cdnsp: Removes some not useful function arguments This patch removes 'flags' argument from some functions and use indirect the correct GFP_XXX flag to fix the following warning: 'The patch d40a169aab24: "usb: cdnsp: cdns3 Add main part of Cadence USBSSP DRD Driver" from Dec 7, 2020, leads to the following static checker warning: drivers/usb/cdns3/cdnsp-mem.c:1229 cdnsp_mem_init() warn: use 'flags' here instead of GFP_XXX? drivers/usb/cdns3/cdnsp-mem.c 1206 int cdnsp_mem_init(struct cdnsp_device *pdev, gfp_t flags);' ^^^^^^^^^^^ Signed-off-by: Pawel Laszczak Reported-by: Dan Carpenter Signed-off-by: Peter Chen --- drivers/usb/cdns3/cdnsp-gadget.c | 2 +- drivers/usb/cdns3/cdnsp-gadget.h | 2 +- drivers/usb/cdns3/cdnsp-mem.c | 23 +++++++++++------------ 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c index c7c5d0a29f4d..1668f72fdf30 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.c +++ b/drivers/usb/cdns3/cdnsp-gadget.c @@ -1824,7 +1824,7 @@ static int cdnsp_gen_setup(struct cdnsp_device *pdev) spin_lock_init(&pdev->lock); - ret = cdnsp_mem_init(pdev, GFP_KERNEL); + ret = cdnsp_mem_init(pdev); if (ret) return ret; diff --git a/drivers/usb/cdns3/cdnsp-gadget.h b/drivers/usb/cdns3/cdnsp-gadget.h index 5f8629eae41f..8eb1b85a08b4 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.h +++ b/drivers/usb/cdns3/cdnsp-gadget.h @@ -1480,7 +1480,7 @@ static inline void cdnsp_write_64(const u64 val, __le64 __iomem *regs) /* CDNSP memory management functions. */ void cdnsp_mem_cleanup(struct cdnsp_device *pdev); -int cdnsp_mem_init(struct cdnsp_device *pdev, gfp_t flags); +int cdnsp_mem_init(struct cdnsp_device *pdev); int cdnsp_setup_addressable_priv_dev(struct cdnsp_device *pdev); void cdnsp_copy_ep0_dequeue_into_input_ctx(struct cdnsp_device *pdev); void cdnsp_endpoint_zero(struct cdnsp_device *pdev, struct cdnsp_ep *ep); diff --git a/drivers/usb/cdns3/cdnsp-mem.c b/drivers/usb/cdns3/cdnsp-mem.c index 6a0a12e1f54c..980047b7e416 100644 --- a/drivers/usb/cdns3/cdnsp-mem.c +++ b/drivers/usb/cdns3/cdnsp-mem.c @@ -684,7 +684,7 @@ static void cdnsp_free_priv_device(struct cdnsp_device *pdev) pdev->out_ctx.bytes = NULL; } -static int cdnsp_alloc_priv_device(struct cdnsp_device *pdev, gfp_t flags) +static int cdnsp_alloc_priv_device(struct cdnsp_device *pdev) { int ret = -ENOMEM; @@ -693,7 +693,7 @@ static int cdnsp_alloc_priv_device(struct cdnsp_device *pdev, gfp_t flags) return ret; /* Allocate endpoint 0 ring. */ - pdev->eps[0].ring = cdnsp_ring_alloc(pdev, 2, TYPE_CTRL, 0, flags); + pdev->eps[0].ring = cdnsp_ring_alloc(pdev, 2, TYPE_CTRL, 0, GFP_ATOMIC); if (!pdev->eps[0].ring) goto fail; @@ -1020,8 +1020,7 @@ void cdnsp_endpoint_zero(struct cdnsp_device *pdev, struct cdnsp_ep *pep) static int cdnsp_alloc_erst(struct cdnsp_device *pdev, struct cdnsp_ring *evt_ring, - struct cdnsp_erst *erst, - gfp_t flags) + struct cdnsp_erst *erst) { struct cdnsp_erst_entry *entry; struct cdnsp_segment *seg; @@ -1030,7 +1029,7 @@ static int cdnsp_alloc_erst(struct cdnsp_device *pdev, size = sizeof(struct cdnsp_erst_entry) * evt_ring->num_segs; erst->entries = dma_alloc_coherent(pdev->dev, size, - &erst->erst_dma_addr, flags); + &erst->erst_dma_addr, GFP_KERNEL); if (!erst->entries) return -ENOMEM; @@ -1142,7 +1141,7 @@ static void cdnsp_add_in_port(struct cdnsp_device *pdev, * Scan the Extended Capabilities for the "Supported Protocol Capabilities" that * specify what speeds each port is supposed to be. */ -static int cdnsp_setup_port_arrays(struct cdnsp_device *pdev, gfp_t flags) +static int cdnsp_setup_port_arrays(struct cdnsp_device *pdev) { void __iomem *base; u32 offset; @@ -1203,7 +1202,7 @@ static int cdnsp_setup_port_arrays(struct cdnsp_device *pdev, gfp_t flags) * device contexts, set up a command ring segment, create event * ring (one for now). */ -int cdnsp_mem_init(struct cdnsp_device *pdev, gfp_t flags) +int cdnsp_mem_init(struct cdnsp_device *pdev) { struct device *dev = pdev->dev; int ret = -ENOMEM; @@ -1255,7 +1254,7 @@ int cdnsp_mem_init(struct cdnsp_device *pdev, gfp_t flags) goto mem_init_fail; /* Set up the command ring to have one segments for now. */ - pdev->cmd_ring = cdnsp_ring_alloc(pdev, 1, TYPE_COMMAND, 0, flags); + pdev->cmd_ring = cdnsp_ring_alloc(pdev, 1, TYPE_COMMAND, 0, GFP_KERNEL); if (!pdev->cmd_ring) goto mem_init_fail; @@ -1278,11 +1277,11 @@ int cdnsp_mem_init(struct cdnsp_device *pdev, gfp_t flags) * the event ring segment table (ERST). */ pdev->event_ring = cdnsp_ring_alloc(pdev, ERST_NUM_SEGS, TYPE_EVENT, - 0, flags); + 0, GFP_KERNEL); if (!pdev->event_ring) goto mem_init_fail; - ret = cdnsp_alloc_erst(pdev, pdev->event_ring, &pdev->erst, flags); + ret = cdnsp_alloc_erst(pdev, pdev->event_ring, &pdev->erst); if (ret) goto mem_init_fail; @@ -1301,11 +1300,11 @@ int cdnsp_mem_init(struct cdnsp_device *pdev, gfp_t flags) /* Set the event ring dequeue address. */ cdnsp_set_event_deq(pdev); - ret = cdnsp_setup_port_arrays(pdev, flags); + ret = cdnsp_setup_port_arrays(pdev); if (ret) goto mem_init_fail; - ret = cdnsp_alloc_priv_device(pdev, GFP_ATOMIC); + ret = cdnsp_alloc_priv_device(pdev); if (ret) { dev_err(pdev->dev, "Could not allocate cdnsp_device data structures\n"); -- cgit v1.2.3 From 88171f67a2c15461d29942b0103d539f52367844 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Fri, 11 Dec 2020 06:09:54 +0100 Subject: usb: cdns3: Removes xhci_cdns3_suspend_quirk from host-export.h Function xhci_cdns3_suspend_quirk is used only in host.c file. We can make it as static and removes it from host-export.h. Signed-off-by: Pawel Laszczak Acked-by: Roger Quadros Signed-off-by: Peter Chen --- drivers/usb/cdns3/host-export.h | 7 ------- drivers/usb/cdns3/host.c | 4 +++- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/usb/cdns3/host-export.h b/drivers/usb/cdns3/host-export.h index fb8541b8adbc..cf92173ecf00 100644 --- a/drivers/usb/cdns3/host-export.h +++ b/drivers/usb/cdns3/host-export.h @@ -9,12 +9,9 @@ #ifndef __LINUX_CDNS3_HOST_EXPORT #define __LINUX_CDNS3_HOST_EXPORT -struct usb_hcd; - #if IS_ENABLED(CONFIG_USB_CDNS_HOST) int cdns_host_init(struct cdns *cdns); -int xhci_cdns3_suspend_quirk(struct usb_hcd *hcd); #else @@ -24,10 +21,6 @@ static inline int cdns_host_init(struct cdns *cdns) } static inline void cdns_host_exit(struct cdns *cdns) { } -static inline int xhci_cdns3_suspend_quirk(struct usb_hcd *hcd) -{ - return 0; -} #endif /* USB_CDNS_HOST */ diff --git a/drivers/usb/cdns3/host.c b/drivers/usb/cdns3/host.c index 3ff19182b0c0..84dadfa726aa 100644 --- a/drivers/usb/cdns3/host.c +++ b/drivers/usb/cdns3/host.c @@ -23,6 +23,8 @@ #define CFG_RXDET_P3_EN BIT(15) #define LPM_2_STB_SWITCH_EN BIT(25) +static int xhci_cdns3_suspend_quirk(struct usb_hcd *hcd); + static const struct xhci_plat_priv xhci_plat_cdns3_xhci = { .quirks = XHCI_SKIP_PHY_INIT | XHCI_AVOID_BEI, .suspend_quirk = xhci_cdns3_suspend_quirk, @@ -87,7 +89,7 @@ err1: return ret; } -int xhci_cdns3_suspend_quirk(struct usb_hcd *hcd) +static int xhci_cdns3_suspend_quirk(struct usb_hcd *hcd) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); u32 value; -- cgit v1.2.3 From e2d60f8c475a4955b8c39bda4cf6b10b09460772 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Fri, 11 Dec 2020 10:50:53 +0100 Subject: usb: cdnsp: fix error handling in cdnsp_mem_init() This function uses "One Function Cleans up Everything" style and that's basically impossible to do correctly. It's cleaner to write it with "clean up the most recent allocation". Patch fixes two isues: 1. If pdev->dcbaa = dma_alloc_coherent() fails then that leads to a NULL dereference inside the cdnsp_free_priv_device() function. 2. if cdnsp_alloc_priv_device() fails that leads to a double free because we free pdev->out_ctx.bytes in several places. Signed-off-by: Dan Carpenter Signed-off-by: Pawel Laszczak Reported-by: Dan Carpenter Tested-by: Pawel Laszczak Signed-off-by: Peter Chen --- drivers/usb/cdns3/cdnsp-mem.c | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/drivers/usb/cdns3/cdnsp-mem.c b/drivers/usb/cdns3/cdnsp-mem.c index 980047b7e416..4c7d77fb097e 100644 --- a/drivers/usb/cdns3/cdnsp-mem.c +++ b/drivers/usb/cdns3/cdnsp-mem.c @@ -1228,7 +1228,7 @@ int cdnsp_mem_init(struct cdnsp_device *pdev) pdev->dcbaa = dma_alloc_coherent(dev, sizeof(*pdev->dcbaa), &dma, GFP_KERNEL); if (!pdev->dcbaa) - goto mem_init_fail; + return -ENOMEM; memset(pdev->dcbaa, 0, sizeof(*pdev->dcbaa)); pdev->dcbaa->dma = dma; @@ -1246,17 +1246,19 @@ int cdnsp_mem_init(struct cdnsp_device *pdev) pdev->segment_pool = dma_pool_create("CDNSP ring segments", dev, TRB_SEGMENT_SIZE, TRB_SEGMENT_SIZE, page_size); + if (!pdev->segment_pool) + goto release_dcbaa; pdev->device_pool = dma_pool_create("CDNSP input/output contexts", dev, CDNSP_CTX_SIZE, 64, page_size); + if (!pdev->device_pool) + goto destroy_segment_pool; - if (!pdev->segment_pool || !pdev->device_pool) - goto mem_init_fail; /* Set up the command ring to have one segments for now. */ pdev->cmd_ring = cdnsp_ring_alloc(pdev, 1, TYPE_COMMAND, 0, GFP_KERNEL); if (!pdev->cmd_ring) - goto mem_init_fail; + goto destroy_device_pool; /* Set the address in the Command Ring Control register */ val_64 = cdnsp_read_64(&pdev->op_regs->cmd_ring); @@ -1279,11 +1281,11 @@ int cdnsp_mem_init(struct cdnsp_device *pdev) pdev->event_ring = cdnsp_ring_alloc(pdev, ERST_NUM_SEGS, TYPE_EVENT, 0, GFP_KERNEL); if (!pdev->event_ring) - goto mem_init_fail; + goto free_cmd_ring; ret = cdnsp_alloc_erst(pdev, pdev->event_ring, &pdev->erst); if (ret) - goto mem_init_fail; + goto free_event_ring; /* Set ERST count with the number of entries in the segment table. */ val = readl(&pdev->ir_set->erst_size); @@ -1302,22 +1304,32 @@ int cdnsp_mem_init(struct cdnsp_device *pdev) ret = cdnsp_setup_port_arrays(pdev); if (ret) - goto mem_init_fail; + goto free_erst; ret = cdnsp_alloc_priv_device(pdev); if (ret) { dev_err(pdev->dev, "Could not allocate cdnsp_device data structures\n"); - goto mem_init_fail; + goto free_erst; } return 0; -mem_init_fail: - dev_err(pdev->dev, "Couldn't initialize memory\n"); - cdnsp_halt(pdev); +free_erst: + cdnsp_free_erst(pdev, &pdev->erst); +free_event_ring: + cdnsp_ring_free(pdev, pdev->event_ring); +free_cmd_ring: + cdnsp_ring_free(pdev, pdev->cmd_ring); +destroy_device_pool: + dma_pool_destroy(pdev->device_pool); +destroy_segment_pool: + dma_pool_destroy(pdev->segment_pool); +release_dcbaa: + dma_free_coherent(dev, sizeof(*pdev->dcbaa), pdev->dcbaa, + pdev->dcbaa->dma); + cdnsp_reset(pdev); - cdnsp_mem_cleanup(pdev); return ret; } -- cgit v1.2.3 From fba8701baed76eac00b84b59f09f6a077f24c534 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Mon, 14 Dec 2020 12:04:33 +0100 Subject: usb: cdns3: Fixes for sparse warnings Patch fixes the following warnings: cdns3-gadget.c:1203: sparse: warning: incorrect type in assignment (different base types) cdns3-gadget.c:1203: sparse: expected restricted __le32 [usertype] length cdns3-gadget.c:1203: sparse: got unsigned long cdns3-gadget.c:1250: sparse: warning: invalid assignment: |= cdns3-gadget.c:1250: sparse: left side has type restricted __le32 cdns3-gadget.c:1250: sparse: right side has type unsigned long cdns3-gadget.c:1253: sparse: warning: invalid assignment: |= cdns3-gadget.c:1253: sparse: left side has type restricted __le32 cdns3-gadget.c:1253: sparse: right side has type unsigned long cdns3-ep0.c:367: sparse: warning: restricted __le16 degrades to integer cdns3-ep0.c:792: sparse: warning: symbol 'cdns3_gadget_ep0_ops' was not declared. Should it be static? Reported-by: kernel test robot Signed-off-by: Pawel Laszczak Signed-off-by: Peter Chen --- drivers/usb/cdns3/cdns3-ep0.c | 4 ++-- drivers/usb/cdns3/cdns3-gadget.c | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/usb/cdns3/cdns3-ep0.c b/drivers/usb/cdns3/cdns3-ep0.c index b0390fe9a396..9a17802275d5 100644 --- a/drivers/usb/cdns3/cdns3-ep0.c +++ b/drivers/usb/cdns3/cdns3-ep0.c @@ -364,7 +364,7 @@ static int cdns3_ep0_feature_handle_endpoint(struct cdns3_device *priv_dev, if (le16_to_cpu(ctrl->wValue) != USB_ENDPOINT_HALT) return -EINVAL; - if (!(ctrl->wIndex & ~USB_DIR_IN)) + if (!(le16_to_cpu(ctrl->wIndex) & ~USB_DIR_IN)) return 0; index = cdns3_ep_addr_to_index(le16_to_cpu(ctrl->wIndex)); @@ -789,7 +789,7 @@ int cdns3_gadget_ep_set_wedge(struct usb_ep *ep) return 0; } -const struct usb_ep_ops cdns3_gadget_ep0_ops = { +static const struct usb_ep_ops cdns3_gadget_ep0_ops = { .enable = cdns3_gadget_ep0_enable, .disable = cdns3_gadget_ep0_disable, .alloc_request = cdns3_gadget_ep_alloc_request, diff --git a/drivers/usb/cdns3/cdns3-gadget.c b/drivers/usb/cdns3/cdns3-gadget.c index 9b8b0cd3d2c2..582bfeceedb4 100644 --- a/drivers/usb/cdns3/cdns3-gadget.c +++ b/drivers/usb/cdns3/cdns3-gadget.c @@ -1200,7 +1200,7 @@ static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, td_size = DIV_ROUND_UP(request->length, priv_ep->endpoint.maxpacket); if (priv_dev->gadget.speed == USB_SPEED_SUPER) - trb->length = TRB_TDL_SS_SIZE(td_size); + trb->length = cpu_to_le32(TRB_TDL_SS_SIZE(td_size)); else control |= TRB_TDL_HS_SIZE(td_size); } @@ -1247,10 +1247,10 @@ static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, priv_req->trb->control = cpu_to_le32(control); if (sg_supported) { - trb->control |= TRB_ISP; + trb->control |= cpu_to_le32(TRB_ISP); /* Don't set chain bit for last TRB */ if (sg_iter < num_trb - 1) - trb->control |= TRB_CHAIN; + trb->control |= cpu_to_le32(TRB_CHAIN); s = sg_next(s); } -- cgit v1.2.3 From 16e3610154d0c992a0558d684213201f5355bd76 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Mon, 14 Dec 2020 13:03:44 +0100 Subject: usb: cdnsp: Fixes for sparse warnings Patch fixes all sparse warnings in cdsnp driver. It fixes the following warnings: cdnsp-ring.c:1441: warning: incorrect type in assignment cdnsp-ring.c:1444: warning: restricted __le32 degrades to integer cdnsp-ring.c:2200: warning: dubious: x | !y cdnsp-gadget.c:501: warning: incorrect type in assignment cdnsp-gadget.c:504: warning: restricted __le32 degrades to integer cdnsp-gadget.c:507: warning: restricted __le32 degrades to integer cdnsp-gadget.c:508: warning: restricted __le32 degrades to integer cdnsp-gadget.c:509: warning: invalid assignment: |= cdnsp-gadget.c:510: warning: cast from restricted __le32 cdnsp-gadget.c:558: warning: incorrect type in assignment cdnsp-gadget.c:561: warning: restricted __le32 degrades to integer cdnsp-gadget.c:570: warning: restricted __le32 degrades to integer cdnsp-gadget.c:1571: warning: incorrect type in argument 1 cdnsp-gadget.c:1602: warning: restricted __le32 degrades to integer cdnsp-gadget.c:1760: warning: incorrect type in assignment cdnsp-gadget.c:1762: warning: incorrect type in assignment cdnsp-gadget.c:1763: warning: incorrect type in assignment cdnsp-gadget.c:1764: warning: incorrect type in assignment cdnsp-gadget.c:1765: warning: incorrect type in assignment cdnsp-gadget.c:1766: warning: incorrect type in assignment cdnsp-gadget.c:1767: warning: incorrect type in assignment cdnsp-gadget.c:458: warning: cast truncates bits from constant value (ffffffff07ffffff becomes 7ffffff) cdnsp-gadget.c:666: warning: cast truncates bits from constant value (ffffffff07ffffff becomes 7ffffff) cdnsp-mem.c:762: warning: incorrect type in assignment cdnsp-mem.c:763: warning: incorrect type in assignment cdnsp-mem.c:928: warning: cast from restricted __le16 cdnsp-mem.c:1187: warning: incorrect type in assignment cdnsp-mem.c:1191: warning: incorrect type in assignment cdnsp-ep0.c:142: warning: incorrect type in assignment cdnsp-ep0.c:144: warning: restricted __le32 degrades to integer cdnsp-ep0.c:147: warning: restricted __le32 degrades to integer cdnsp-ep0.c:148: warning: restricted __le32 degrades to integer cdnsp-ep0.c:179: warning: incorrect type in argument 1 cdnsp-ep0.c:311: warning: incorrect type in argument 1 cdnsp-ep0.c:469: warning: incorrect type in assignment cdnsp-trace.h:611:1: warning: cast from restricted __le32 Reported-by: kernel test robot Signed-off-by: Pawel Laszczak Signed-off-by: Peter Chen --- drivers/usb/cdns3/cdnsp-debug.h | 2 +- drivers/usb/cdns3/cdnsp-ep0.c | 13 ++++++------- drivers/usb/cdns3/cdnsp-gadget.c | 24 +++++++++--------------- drivers/usb/cdns3/cdnsp-gadget.h | 13 +++++++------ drivers/usb/cdns3/cdnsp-mem.c | 11 ++++++----- drivers/usb/cdns3/cdnsp-ring.c | 4 ++-- drivers/usb/cdns3/cdnsp-trace.h | 2 +- 7 files changed, 32 insertions(+), 37 deletions(-) diff --git a/drivers/usb/cdns3/cdnsp-debug.h b/drivers/usb/cdns3/cdnsp-debug.h index d6345d4d2911..a8776df2d4e0 100644 --- a/drivers/usb/cdns3/cdnsp-debug.h +++ b/drivers/usb/cdns3/cdnsp-debug.h @@ -414,7 +414,7 @@ static inline const char *cdnsp_decode_slot_context(u32 info, u32 info2, s = "UNKNOWN speed"; } - ret = sprintf(str, "%s Ctx Entries %ld", + ret = sprintf(str, "%s Ctx Entries %d", s, (info & LAST_CTX_MASK) >> 27); ret += sprintf(str + ret, " [Intr %ld] Addr %ld State %s", diff --git a/drivers/usb/cdns3/cdnsp-ep0.c b/drivers/usb/cdns3/cdnsp-ep0.c index d55b59ed7381..e2b1bcb3f80e 100644 --- a/drivers/usb/cdns3/cdnsp-ep0.c +++ b/drivers/usb/cdns3/cdnsp-ep0.c @@ -137,10 +137,8 @@ int cdnsp_status_stage(struct cdnsp_device *pdev) return cdnsp_ep_enqueue(pdev->ep0_preq.pep, &pdev->ep0_preq); } -static int cdnsp_w_index_to_ep_index(__le32 wIndex) +static int cdnsp_w_index_to_ep_index(u16 wIndex) { - wIndex = le32_to_cpu(wIndex); - if (!(wIndex & USB_ENDPOINT_NUMBER_MASK)) return 0; @@ -176,7 +174,8 @@ static int cdnsp_ep0_handle_status(struct cdnsp_device *pdev, */ return cdnsp_ep0_delegate_req(pdev, ctrl); case USB_RECIP_ENDPOINT: - pep = &pdev->eps[cdnsp_w_index_to_ep_index(ctrl->wIndex)]; + ep_sts = cdnsp_w_index_to_ep_index(le16_to_cpu(ctrl->wIndex)); + pep = &pdev->eps[ep_sts]; ep_sts = GET_EP_CTX_STATE(pep->out_ctx); /* check if endpoint is stalled */ @@ -305,10 +304,10 @@ static int cdnsp_ep0_handle_feature_endpoint(struct cdnsp_device *pdev, int set) { struct cdnsp_ep *pep; - u32 wValue; + u16 wValue; wValue = le16_to_cpu(ctrl->wValue); - pep = &pdev->eps[cdnsp_w_index_to_ep_index(ctrl->wIndex)]; + pep = &pdev->eps[cdnsp_w_index_to_ep_index(le16_to_cpu(ctrl->wIndex))]; switch (wValue) { case USB_ENDPOINT_HALT: @@ -435,7 +434,7 @@ void cdnsp_setup_analyze(struct cdnsp_device *pdev) { struct usb_ctrlrequest *ctrl = &pdev->setup; int ret = 0; - __le16 len; + u16 len; trace_cdnsp_ctrl_req(ctrl); diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c index 1668f72fdf30..f28f1508f049 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.c +++ b/drivers/usb/cdns3/cdnsp-gadget.c @@ -491,7 +491,7 @@ static void cdnsp_invalidate_ep_events(struct cdnsp_device *pdev, struct cdnsp_segment *segment; union cdnsp_trb *event; u32 cycle_state; - __le32 data; + u32 data; event = pdev->event_ring->dequeue; segment = pdev->event_ring->deq_seg; @@ -527,9 +527,9 @@ int cdnsp_wait_for_cmd_compl(struct cdnsp_device *pdev) dma_addr_t cmd_deq_dma; union cdnsp_trb *event; u32 cycle_state; - __le32 flags; int ret, val; u64 cmd_dma; + u32 flags; cmd_trb = pdev->cmd.command_trb; pdev->cmd.status = 0; @@ -1568,7 +1568,7 @@ static void cdnsp_get_ep_buffering(struct cdnsp_device *pdev, return; } - endpoints = HCS_ENDPOINTS(readl(&pdev->hcs_params1)) / 2; + endpoints = HCS_ENDPOINTS(pdev->hcs_params1) / 2; /* Set to XBUF_TX_TAG_MASK_0 register. */ reg += XBUF_TX_CMD_OFFSET + (endpoints * 2 + 2) * sizeof(u32); @@ -1754,22 +1754,16 @@ void cdnsp_irq_reset(struct cdnsp_device *pdev) static void cdnsp_get_rev_cap(struct cdnsp_device *pdev) { void __iomem *reg = &pdev->cap_regs->hc_capbase; - struct cdnsp_rev_cap *rev_cap; reg += cdnsp_find_next_ext_cap(reg, 0, RTL_REV_CAP); - rev_cap = reg; - - pdev->rev_cap.ctrl_revision = readl(&rev_cap->ctrl_revision); - pdev->rev_cap.rtl_revision = readl(&rev_cap->rtl_revision); - pdev->rev_cap.ep_supported = readl(&rev_cap->ep_supported); - pdev->rev_cap.ext_cap = readl(&rev_cap->ext_cap); - pdev->rev_cap.rx_buff_size = readl(&rev_cap->rx_buff_size); - pdev->rev_cap.tx_buff_size = readl(&rev_cap->tx_buff_size); + pdev->rev_cap = reg; dev_info(pdev->dev, "Rev: %08x/%08x, eps: %08x, buff: %08x/%08x\n", - pdev->rev_cap.ctrl_revision, pdev->rev_cap.rtl_revision, - pdev->rev_cap.ep_supported, pdev->rev_cap.rx_buff_size, - pdev->rev_cap.tx_buff_size); + readl(&pdev->rev_cap->ctrl_revision), + readl(&pdev->rev_cap->rtl_revision), + readl(&pdev->rev_cap->ep_supported), + readl(&pdev->rev_cap->rx_buff_size), + readl(&pdev->rev_cap->tx_buff_size)); } static int cdnsp_gen_setup(struct cdnsp_device *pdev) diff --git a/drivers/usb/cdns3/cdnsp-gadget.h b/drivers/usb/cdns3/cdnsp-gadget.h index 8eb1b85a08b4..6bbb26548c04 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.h +++ b/drivers/usb/cdns3/cdnsp-gadget.h @@ -493,11 +493,12 @@ struct cdnsp_3xport_cap { #define CDNSP_VER_1 0x00000000 #define CDNSP_VER_2 0x10000000 -#define CDNSP_IF_EP_EXIST(pdev, ep_num, dir) ((pdev)->rev_cap.ep_supported & \ - (BIT(ep_num) << ((dir) ? 0 : 16))) +#define CDNSP_IF_EP_EXIST(pdev, ep_num, dir) \ + (readl(&(pdev)->rev_cap->ep_supported) & \ + (BIT(ep_num) << ((dir) ? 0 : 16))) /** - * struct cdnsp_rev_cap - controller capabilities . + * struct cdnsp_rev_cap - controller capabilities. * @ext_cap: Header for RTL Revision Extended Capability. * @rtl_revision: RTL revision. * @rx_buff_size: Rx buffer sizes. @@ -594,7 +595,7 @@ struct cdnsp_slot_ctx { #define DEV_SPEED GENMASK(23, 20) #define GET_DEV_SPEED(n) (((n) & DEV_SPEED) >> 20) /* Index of the last valid endpoint context in this device context - 27:31. */ -#define LAST_CTX_MASK GENMASK(31, 27) +#define LAST_CTX_MASK ((unsigned int)GENMASK(31, 27)) #define LAST_CTX(p) ((p) << 27) #define LAST_CTX_TO_EP_NUM(p) (((p) >> 27) - 1) #define SLOT_FLAG BIT(0) @@ -1351,9 +1352,9 @@ struct cdnsp_port { * @ir_set: Current interrupter register set. * @port20_regs: Port 2.0 Peripheral Configuration Registers. * @port3x_regs: USB3.x Port Peripheral Configuration Registers. + * @rev_cap: Controller Capabilities Registers. * @hcs_params1: Cached register copies of read-only HCSPARAMS1 * @hcc_params: Cached register copies of read-only HCCPARAMS1 - * @rev_cap: Controller capability. * @setup: Temporary buffer for setup packet. * @ep0_preq: Internal allocated request used during enumeration. * @ep0_stage: ep0 stage during enumeration process. @@ -1402,12 +1403,12 @@ struct cdnsp_device { struct cdnsp_intr_reg __iomem *ir_set; struct cdnsp_20port_cap __iomem *port20_regs; struct cdnsp_3xport_cap __iomem *port3x_regs; + struct cdnsp_rev_cap __iomem *rev_cap; /* Cached register copies of read-only CDNSP data */ __u32 hcs_params1; __u32 hcs_params3; __u32 hcc_params; - struct cdnsp_rev_cap rev_cap; /* Lock used in interrupt thread context. */ spinlock_t lock; struct usb_ctrlrequest setup; diff --git a/drivers/usb/cdns3/cdnsp-mem.c b/drivers/usb/cdns3/cdnsp-mem.c index 4c7d77fb097e..7a84e928710e 100644 --- a/drivers/usb/cdns3/cdnsp-mem.c +++ b/drivers/usb/cdns3/cdnsp-mem.c @@ -759,8 +759,9 @@ int cdnsp_setup_addressable_priv_dev(struct cdnsp_device *pdev) port = DEV_PORT(pdev->active_port->port_num); slot_ctx->dev_port |= cpu_to_le32(port); - slot_ctx->dev_state = (pdev->device_address & DEV_ADDR_MASK); - ep0_ctx->tx_info = EP_AVG_TRB_LENGTH(0x8); + slot_ctx->dev_state = cpu_to_le32((pdev->device_address & + DEV_ADDR_MASK)); + ep0_ctx->tx_info = cpu_to_le32(EP_AVG_TRB_LENGTH(0x8)); ep0_ctx->ep_info2 = cpu_to_le32(EP_TYPE(CTRL_EP)); ep0_ctx->ep_info2 |= cpu_to_le32(MAX_BURST(0) | ERROR_COUNT(3) | max_packets); @@ -925,7 +926,7 @@ static u32 cdnsp_get_max_esit_payload(struct usb_gadget *g, /* SuperSpeedPlus Isoc ep sending over 48k per EIST. */ if (g->speed >= USB_SPEED_SUPER_PLUS && USB_SS_SSP_ISOC_COMP(pep->endpoint.desc->bmAttributes)) - return le32_to_cpu(pep->endpoint.comp_desc->wBytesPerInterval); + return le16_to_cpu(pep->endpoint.comp_desc->wBytesPerInterval); /* SuperSpeed or SuperSpeedPlus Isoc ep with less than 48k per esit */ else if (g->speed >= USB_SPEED_SUPER) return le16_to_cpu(pep->endpoint.comp_desc->wBytesPerInterval); @@ -1184,11 +1185,11 @@ static int cdnsp_setup_port_arrays(struct cdnsp_device *pdev) trace_cdnsp_init("Found USB 2.0 ports and USB 3.0 ports."); - pdev->usb2_port.regs = (struct cdnsp_port_regs *) + pdev->usb2_port.regs = (struct cdnsp_port_regs __iomem *) (&pdev->op_regs->port_reg_base + NUM_PORT_REGS * (pdev->usb2_port.port_num - 1)); - pdev->usb3_port.regs = (struct cdnsp_port_regs *) + pdev->usb3_port.regs = (struct cdnsp_port_regs __iomem *) (&pdev->op_regs->port_reg_base + NUM_PORT_REGS * (pdev->usb3_port.port_num - 1)); diff --git a/drivers/usb/cdns3/cdnsp-ring.c b/drivers/usb/cdns3/cdnsp-ring.c index 874d9ff5406c..e15e13ba27dc 100644 --- a/drivers/usb/cdns3/cdnsp-ring.c +++ b/drivers/usb/cdns3/cdnsp-ring.c @@ -1432,7 +1432,7 @@ static bool cdnsp_handle_event(struct cdnsp_device *pdev) unsigned int comp_code; union cdnsp_trb *event; bool update_ptrs = true; - __le32 cycle_bit; + u32 cycle_bit; int ret = 0; u32 flags; @@ -2198,7 +2198,7 @@ static int cdnsp_queue_isoc_tx(struct cdnsp_device *pdev, * inverted in the first TDs isoc TRB. */ field = TRB_TYPE(TRB_ISOC) | TRB_TLBPC(last_burst_pkt) | - !start_cycle | TRB_SIA | TRB_TBC(burst_count); + start_cycle ? 0 : 1 | TRB_SIA | TRB_TBC(burst_count); /* Fill the rest of the TRB fields, and remaining normal TRBs. */ for (i = 0; i < trbs_per_td; i++) { diff --git a/drivers/usb/cdns3/cdnsp-trace.h b/drivers/usb/cdns3/cdnsp-trace.h index b68e282464d2..a9de1daadf07 100644 --- a/drivers/usb/cdns3/cdnsp-trace.h +++ b/drivers/usb/cdns3/cdnsp-trace.h @@ -620,7 +620,7 @@ DECLARE_EVENT_CLASS(cdnsp_log_slot_ctx, TP_fast_assign( __entry->info = le32_to_cpu(ctx->dev_info); __entry->info2 = le32_to_cpu(ctx->dev_port); - __entry->int_target = le64_to_cpu(ctx->int_target); + __entry->int_target = le32_to_cpu(ctx->int_target); __entry->state = le32_to_cpu(ctx->dev_state); ), TP_printk("%s", cdnsp_decode_slot_context(__entry->info, -- cgit v1.2.3 From 826a9584d14a7561f44eac86fe7d3c75c6bd2ad9 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Thu, 10 Dec 2020 17:22:58 +0100 Subject: usb: cdnsp: Fix for undefined reference to `usb_hcd_is_primary_hcd' Patch fixes the following compilation error: ld: drivers/usb/cdns3/host.o: in function `xhci_cdns3_suspend_quirk': host.c:(.text+0x9): undefined reference to `usb_hcd_is_primary_hcd' This reference to 'usb_hdc_is_primary_hcd' is from hcd_to_xhci(), which is being built as a loadable module: int xhci_cdns3_suspend_quirk(struct usb_hcd *hcd) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); Issue occurrd for following kernel configuration: CONFIG_USB_GADGET=y CONFIG_USB_SUPPORT=y CONFIG_USB_COMMON=y CONFIG_USB_ARCH_HAS_HCD=y CONFIG_USB=m CONFIG_USB_CDNS_SUPPORT=y CONFIG_USB_CDNS_HOST=y CONFIG_USB_CDNS3=m CONFIG_USB_CDNS3_GADGET=y CONFIG_USB_CDNS3_HOST=y Signed-off-by: Pawel Laszczak Reported-by: Randy Dunlap Acked-by: Randy Dunlap # build-tested Signed-off-by: Peter Chen --- drivers/usb/cdns3/Makefile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile index 01a9a9620044..3f9b7fa8a594 100644 --- a/drivers/usb/cdns3/Makefile +++ b/drivers/usb/cdns3/Makefile @@ -6,8 +6,13 @@ CFLAGS_cdnsp-trace.o := -I$(src) cdns-usb-common-y := core.o drd.o cdns3-y := cdns3-plat.o -obj-$(CONFIG_USB_CDNS3) += cdns3.o +ifeq ($(CONFIG_USB),m) +obj-m += cdns-usb-common.o +obj-m += cdns3.o +else obj-$(CONFIG_USB_CDNS_SUPPORT) += cdns-usb-common.o +obj-$(CONFIG_USB_CDNS3) += cdns3.o +endif cdns-usb-common-$(CONFIG_USB_CDNS_HOST) += host.o cdns3-$(CONFIG_USB_CDNS3_GADGET) += cdns3-gadget.o cdns3-ep0.o -- cgit v1.2.3 From d2a704e297117cce7863b24a4fabe65930209cd3 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Tue, 29 Dec 2020 19:37:43 +0800 Subject: dt-bindings: usb: dwc3-imx8mp: add imx8mp dwc3 glue bindings NXP imx8mp integrates 2 dwc3 3.30b IP and add some wakeup logic to support low power mode, the glue layer is for this wakeup functionality, which has a separated interrupt, can support wakeup from U3 and connect events for host, and vbus wakeup for device. Reviewed-by: Krzysztof Kozlowski Reviewed-by: Rob Herring Signed-off-by: Li Jun Link: https://lore.kernel.org/r/1609241866-9508-2-git-send-email-jun.li@nxp.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/fsl,imx8mp-dwc3.yaml | 105 +++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/fsl,imx8mp-dwc3.yaml diff --git a/Documentation/devicetree/bindings/usb/fsl,imx8mp-dwc3.yaml b/Documentation/devicetree/bindings/usb/fsl,imx8mp-dwc3.yaml new file mode 100644 index 000000000000..cb4c6f6d3a33 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/fsl,imx8mp-dwc3.yaml @@ -0,0 +1,105 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (c) 2020 NXP +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/fsl,imx8mp-dwc3.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NXP iMX8MP Soc USB Controller + +maintainers: + - Li Jun + +properties: + compatible: + const: fsl,imx8mp-dwc3 + + reg: + maxItems: 1 + description: Address and length of the register set for the wrapper of + dwc3 core on the SOC. + + "#address-cells": + enum: [ 1, 2 ] + + "#size-cells": + enum: [ 1, 2 ] + + dma-ranges: + description: + See section 2.3.9 of the DeviceTree Specification. + + ranges: true + + interrupts: + maxItems: 1 + description: The interrupt that is asserted when a wakeup event is + received. + + clocks: + description: + A list of phandle and clock-specifier pairs for the clocks + listed in clock-names. + items: + - description: system hsio root clock. + - description: suspend clock, used for usb wakeup logic. + + clock-names: + items: + - const: hsio + - const: suspend + +# Required child node: + +patternProperties: + "^dwc3@[0-9a-f]+$": + type: object + description: + A child node must exist to represent the core DWC3 IP block + The content of the node is defined in dwc3.txt. + +required: + - compatible + - reg + - "#address-cells" + - "#size-cells" + - dma-ranges + - ranges + - clocks + - clock-names + - interrupts + +additionalProperties: false + +examples: + - | + #include + #include + usb3_0: usb@32f10100 { + compatible = "fsl,imx8mp-dwc3"; + reg = <0x32f10100 0x8>; + clocks = <&clk IMX8MP_CLK_HSIO_ROOT>, + <&clk IMX8MP_CLK_USB_ROOT>; + clock-names = "hsio", "suspend"; + interrupts = ; + #address-cells = <1>; + #size-cells = <1>; + dma-ranges = <0x40000000 0x40000000 0xc0000000>; + ranges; + + dwc3@38100000 { + compatible = "snps,dwc3"; + reg = <0x38100000 0x10000>; + clocks = <&clk IMX8MP_CLK_HSIO_AXI>, + <&clk IMX8MP_CLK_USB_CORE_REF>, + <&clk IMX8MP_CLK_USB_ROOT>; + clock-names = "bus_early", "ref", "suspend"; + assigned-clocks = <&clk IMX8MP_CLK_HSIO_AXI>; + assigned-clock-parents = <&clk IMX8MP_SYS_PLL2_500M>; + assigned-clock-rates = <500000000>; + interrupts = ; + phys = <&usb3_phy0>, <&usb3_phy0>; + phy-names = "usb2-phy", "usb3-phy"; + snps,dis-u2-freeclk-exists-quirk; + }; + }; -- cgit v1.2.3 From 6dd2565989b4dca09eeae45a3694ec533c6a99c0 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Tue, 29 Dec 2020 19:37:44 +0800 Subject: usb: dwc3: add imx8mp dwc3 glue layer driver imx8mp SoC integrate dwc3 3.30b IP and has some customizations to support low power, which has a seprated wakeup irq and additional logic to wakeup usb from low power mode both for host mode and device mode. Signed-off-by: Li Jun Link: https://lore.kernel.org/r/1609241866-9508-3-git-send-email-jun.li@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/Kconfig | 10 ++ drivers/usb/dwc3/Makefile | 1 + drivers/usb/dwc3/dwc3-imx8mp.c | 363 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 374 insertions(+) create mode 100644 drivers/usb/dwc3/dwc3-imx8mp.c diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index 7a2304565a73..2133acf8ee69 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -139,4 +139,14 @@ config USB_DWC3_QCOM for peripheral mode support. Say 'Y' or 'M' if you have one such device. +config USB_DWC3_IMX8MP + tristate "NXP iMX8MP Platform" + depends on OF && COMMON_CLK + depends on (ARCH_MXC && ARM64) || COMPILE_TEST + default USB_DWC3 + help + NXP iMX8M Plus SoC use DesignWare Core IP for USB2/3 + functionality. + Say 'Y' or 'M' if you have one such device. + endif diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index ae86da0dc5bd..2259f8876fb2 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -51,3 +51,4 @@ 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_IMX8MP) += dwc3-imx8mp.o diff --git a/drivers/usb/dwc3/dwc3-imx8mp.c b/drivers/usb/dwc3/dwc3-imx8mp.c new file mode 100644 index 000000000000..75f0042b998b --- /dev/null +++ b/drivers/usb/dwc3/dwc3-imx8mp.c @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * dwc3-imx8mp.c - NXP imx8mp Specific Glue layer + * + * Copyright (c) 2020 NXP. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" + +/* USB wakeup registers */ +#define USB_WAKEUP_CTRL 0x00 + +/* Global wakeup interrupt enable, also used to clear interrupt */ +#define USB_WAKEUP_EN BIT(31) +/* Wakeup from connect or disconnect, only for superspeed */ +#define USB_WAKEUP_SS_CONN BIT(5) +/* 0 select vbus_valid, 1 select sessvld */ +#define USB_WAKEUP_VBUS_SRC_SESS_VAL BIT(4) +/* Enable signal for wake up from u3 state */ +#define USB_WAKEUP_U3_EN BIT(3) +/* Enable signal for wake up from id change */ +#define USB_WAKEUP_ID_EN BIT(2) +/* Enable signal for wake up from vbus change */ +#define USB_WAKEUP_VBUS_EN BIT(1) +/* Enable signal for wake up from dp/dm change */ +#define USB_WAKEUP_DPDM_EN BIT(0) + +#define USB_WAKEUP_EN_MASK GENMASK(5, 0) + +struct dwc3_imx8mp { + struct device *dev; + struct platform_device *dwc3; + void __iomem *glue_base; + struct clk *hsio_clk; + struct clk *suspend_clk; + int irq; + bool pm_suspended; + bool wakeup_pending; +}; + +static void dwc3_imx8mp_wakeup_enable(struct dwc3_imx8mp *dwc3_imx) +{ + struct dwc3 *dwc3 = platform_get_drvdata(dwc3_imx->dwc3); + u32 val; + + if (!dwc3) + return; + + val = readl(dwc3_imx->glue_base + USB_WAKEUP_CTRL); + + if ((dwc3->current_dr_role == DWC3_GCTL_PRTCAP_HOST) && dwc3->xhci) + val |= USB_WAKEUP_EN | USB_WAKEUP_SS_CONN | + USB_WAKEUP_U3_EN | USB_WAKEUP_DPDM_EN; + else if (dwc3->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE) + val |= USB_WAKEUP_EN | USB_WAKEUP_VBUS_EN | + USB_WAKEUP_VBUS_SRC_SESS_VAL; + + writel(val, dwc3_imx->glue_base + USB_WAKEUP_CTRL); +} + +static void dwc3_imx8mp_wakeup_disable(struct dwc3_imx8mp *dwc3_imx) +{ + u32 val; + + val = readl(dwc3_imx->glue_base + USB_WAKEUP_CTRL); + val &= ~(USB_WAKEUP_EN | USB_WAKEUP_EN_MASK); + writel(val, dwc3_imx->glue_base + USB_WAKEUP_CTRL); +} + +static irqreturn_t dwc3_imx8mp_interrupt(int irq, void *_dwc3_imx) +{ + struct dwc3_imx8mp *dwc3_imx = _dwc3_imx; + struct dwc3 *dwc = platform_get_drvdata(dwc3_imx->dwc3); + + if (!dwc3_imx->pm_suspended) + return IRQ_HANDLED; + + disable_irq_nosync(dwc3_imx->irq); + dwc3_imx->wakeup_pending = true; + + if ((dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST) && dwc->xhci) + pm_runtime_resume(&dwc->xhci->dev); + else if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE) + pm_runtime_get(dwc->dev); + + return IRQ_HANDLED; +} + +static int dwc3_imx8mp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *dwc3_np, *node = dev->of_node; + struct dwc3_imx8mp *dwc3_imx; + int err, irq; + + if (!node) { + dev_err(dev, "device node not found\n"); + return -EINVAL; + } + + dwc3_imx = devm_kzalloc(dev, sizeof(*dwc3_imx), GFP_KERNEL); + if (!dwc3_imx) + return -ENOMEM; + + platform_set_drvdata(pdev, dwc3_imx); + + dwc3_imx->dev = dev; + + dwc3_imx->glue_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dwc3_imx->glue_base)) + return PTR_ERR(dwc3_imx->glue_base); + + dwc3_imx->hsio_clk = devm_clk_get(dev, "hsio"); + if (IS_ERR(dwc3_imx->hsio_clk)) { + err = PTR_ERR(dwc3_imx->hsio_clk); + dev_err(dev, "Failed to get hsio clk, err=%d\n", err); + return err; + } + + err = clk_prepare_enable(dwc3_imx->hsio_clk); + if (err) { + dev_err(dev, "Failed to enable hsio clk, err=%d\n", err); + return err; + } + + dwc3_imx->suspend_clk = devm_clk_get(dev, "suspend"); + if (IS_ERR(dwc3_imx->suspend_clk)) { + err = PTR_ERR(dwc3_imx->suspend_clk); + dev_err(dev, "Failed to get suspend clk, err=%d\n", err); + goto disable_hsio_clk; + } + + err = clk_prepare_enable(dwc3_imx->suspend_clk); + if (err) { + dev_err(dev, "Failed to enable suspend clk, err=%d\n", err); + goto disable_hsio_clk; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + err = irq; + goto disable_clks; + } + dwc3_imx->irq = irq; + + err = devm_request_threaded_irq(dev, irq, NULL, dwc3_imx8mp_interrupt, + IRQF_ONESHOT, dev_name(dev), dwc3_imx); + if (err) { + dev_err(dev, "failed to request IRQ #%d --> %d\n", irq, err); + goto disable_clks; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + err = pm_runtime_get_sync(dev); + if (err < 0) + goto disable_rpm; + + dwc3_np = of_get_child_by_name(node, "dwc3"); + if (!dwc3_np) { + dev_err(dev, "failed to find dwc3 core child\n"); + goto disable_rpm; + } + + err = of_platform_populate(node, NULL, NULL, dev); + if (err) { + dev_err(&pdev->dev, "failed to create dwc3 core\n"); + goto err_node_put; + } + + dwc3_imx->dwc3 = of_find_device_by_node(dwc3_np); + if (!dwc3_imx->dwc3) { + dev_err(dev, "failed to get dwc3 platform device\n"); + err = -ENODEV; + goto depopulate; + } + of_node_put(dwc3_np); + + device_set_wakeup_capable(dev, true); + pm_runtime_put(dev); + + return 0; + +depopulate: + of_platform_depopulate(dev); +err_node_put: + of_node_put(dwc3_np); +disable_rpm: + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); +disable_clks: + clk_disable_unprepare(dwc3_imx->suspend_clk); +disable_hsio_clk: + clk_disable_unprepare(dwc3_imx->hsio_clk); + + return err; +} + +static int dwc3_imx8mp_remove(struct platform_device *pdev) +{ + struct dwc3_imx8mp *dwc3_imx = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + pm_runtime_get_sync(dev); + of_platform_depopulate(dev); + + clk_disable_unprepare(dwc3_imx->suspend_clk); + clk_disable_unprepare(dwc3_imx->hsio_clk); + + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static int __maybe_unused dwc3_imx8mp_suspend(struct dwc3_imx8mp *dwc3_imx, + pm_message_t msg) +{ + if (dwc3_imx->pm_suspended) + return 0; + + /* Wakeup enable */ + if (PMSG_IS_AUTO(msg) || device_may_wakeup(dwc3_imx->dev)) + dwc3_imx8mp_wakeup_enable(dwc3_imx); + + dwc3_imx->pm_suspended = true; + + return 0; +} + +static int __maybe_unused dwc3_imx8mp_resume(struct dwc3_imx8mp *dwc3_imx, + pm_message_t msg) +{ + struct dwc3 *dwc = platform_get_drvdata(dwc3_imx->dwc3); + int ret = 0; + + if (!dwc3_imx->pm_suspended) + return 0; + + /* Wakeup disable */ + dwc3_imx8mp_wakeup_disable(dwc3_imx); + dwc3_imx->pm_suspended = false; + + if (dwc3_imx->wakeup_pending) { + dwc3_imx->wakeup_pending = false; + if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE) { + pm_runtime_mark_last_busy(dwc->dev); + pm_runtime_put_autosuspend(dwc->dev); + } else { + /* + * Add wait for xhci switch from suspend + * clock to normal clock to detect connection. + */ + usleep_range(9000, 10000); + } + enable_irq(dwc3_imx->irq); + } + + return ret; +} + +static int __maybe_unused dwc3_imx8mp_pm_suspend(struct device *dev) +{ + struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev); + int ret; + + ret = dwc3_imx8mp_suspend(dwc3_imx, PMSG_SUSPEND); + + if (device_may_wakeup(dwc3_imx->dev)) + enable_irq_wake(dwc3_imx->irq); + else + clk_disable_unprepare(dwc3_imx->suspend_clk); + + clk_disable_unprepare(dwc3_imx->hsio_clk); + dev_dbg(dev, "dwc3 imx8mp pm suspend.\n"); + + return ret; +} + +static int __maybe_unused dwc3_imx8mp_pm_resume(struct device *dev) +{ + struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev); + int ret; + + if (device_may_wakeup(dwc3_imx->dev)) { + disable_irq_wake(dwc3_imx->irq); + } else { + ret = clk_prepare_enable(dwc3_imx->suspend_clk); + if (ret) + return ret; + } + + ret = clk_prepare_enable(dwc3_imx->hsio_clk); + if (ret) + return ret; + + ret = dwc3_imx8mp_resume(dwc3_imx, PMSG_RESUME); + + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + dev_dbg(dev, "dwc3 imx8mp pm resume.\n"); + + return ret; +} + +static int __maybe_unused dwc3_imx8mp_runtime_suspend(struct device *dev) +{ + struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev); + + dev_dbg(dev, "dwc3 imx8mp runtime suspend.\n"); + + return dwc3_imx8mp_suspend(dwc3_imx, PMSG_AUTO_SUSPEND); +} + +static int __maybe_unused dwc3_imx8mp_runtime_resume(struct device *dev) +{ + struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev); + + dev_dbg(dev, "dwc3 imx8mp runtime resume.\n"); + + return dwc3_imx8mp_resume(dwc3_imx, PMSG_AUTO_RESUME); +} + +static const struct dev_pm_ops dwc3_imx8mp_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dwc3_imx8mp_pm_suspend, dwc3_imx8mp_pm_resume) + SET_RUNTIME_PM_OPS(dwc3_imx8mp_runtime_suspend, + dwc3_imx8mp_runtime_resume, NULL) +}; + +static const struct of_device_id dwc3_imx8mp_of_match[] = { + { .compatible = "fsl,imx8mp-dwc3", }, + {}, +}; +MODULE_DEVICE_TABLE(of, dwc3_imx8mp_of_match); + +static struct platform_driver dwc3_imx8mp_driver = { + .probe = dwc3_imx8mp_probe, + .remove = dwc3_imx8mp_remove, + .driver = { + .name = "imx8mp-dwc3", + .pm = &dwc3_imx8mp_dev_pm_ops, + .of_match_table = dwc3_imx8mp_of_match, + }, +}; + +module_platform_driver(dwc3_imx8mp_driver); + +MODULE_ALIAS("platform:imx8mp-dwc3"); +MODULE_AUTHOR("jun.li@nxp.com"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare USB3 imx8mp Glue Layer"); -- cgit v1.2.3 From fb8587a2c16577d430423df2ddb2f374bb12317f Mon Sep 17 00:00:00 2001 From: Li Jun Date: Tue, 29 Dec 2020 19:37:45 +0800 Subject: arm64: dtsi: imx8mp: add usb nodes imx8mp integrates 2 identical dwc3 based USB3 controllers and Synopsys phys, each instance has additional wakeup logic to support low power mode, so the glue layer need a node with dwc3 core sub node. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Li Jun Link: https://lore.kernel.org/r/1609241866-9508-4-git-send-email-jun.li@nxp.com Signed-off-by: Greg Kroah-Hartman --- arch/arm64/boot/dts/freescale/imx8mp.dtsi | 82 +++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/arch/arm64/boot/dts/freescale/imx8mp.dtsi b/arch/arm64/boot/dts/freescale/imx8mp.dtsi index ecccfbb4f5ad..22db53f6438e 100644 --- a/arch/arm64/boot/dts/freescale/imx8mp.dtsi +++ b/arch/arm64/boot/dts/freescale/imx8mp.dtsi @@ -788,5 +788,87 @@ reg = <0x3d800000 0x400000>; interrupts = ; }; + + usb3_phy0: usb-phy@381f0040 { + compatible = "fsl,imx8mp-usb-phy"; + reg = <0x381f0040 0x40>; + clocks = <&clk IMX8MP_CLK_USB_PHY_ROOT>; + clock-names = "phy"; + assigned-clocks = <&clk IMX8MP_CLK_USB_PHY_REF>; + assigned-clock-parents = <&clk IMX8MP_CLK_24M>; + #phy-cells = <0>; + status = "disabled"; + }; + + usb3_0: usb@32f10100 { + compatible = "fsl,imx8mp-dwc3"; + reg = <0x32f10100 0x8>; + clocks = <&clk IMX8MP_CLK_HSIO_ROOT>, + <&clk IMX8MP_CLK_USB_ROOT>; + clock-names = "hsio", "suspend"; + interrupts = ; + #address-cells = <1>; + #size-cells = <1>; + dma-ranges = <0x40000000 0x40000000 0xc0000000>; + ranges; + status = "disabled"; + + usb_dwc3_0: dwc3@38100000 { + compatible = "snps,dwc3"; + reg = <0x38100000 0x10000>; + clocks = <&clk IMX8MP_CLK_HSIO_AXI>, + <&clk IMX8MP_CLK_USB_CORE_REF>, + <&clk IMX8MP_CLK_USB_ROOT>; + clock-names = "bus_early", "ref", "suspend"; + assigned-clocks = <&clk IMX8MP_CLK_HSIO_AXI>; + assigned-clock-parents = <&clk IMX8MP_SYS_PLL2_500M>; + assigned-clock-rates = <500000000>; + interrupts = ; + phys = <&usb3_phy0>, <&usb3_phy0>; + phy-names = "usb2-phy", "usb3-phy"; + snps,dis-u2-freeclk-exists-quirk; + }; + + }; + + usb3_phy1: usb-phy@382f0040 { + compatible = "fsl,imx8mp-usb-phy"; + reg = <0x382f0040 0x40>; + clocks = <&clk IMX8MP_CLK_USB_PHY_ROOT>; + clock-names = "phy"; + assigned-clocks = <&clk IMX8MP_CLK_USB_PHY_REF>; + assigned-clock-parents = <&clk IMX8MP_CLK_24M>; + #phy-cells = <0>; + }; + + usb3_1: usb@32f10108 { + compatible = "fsl,imx8mp-dwc3"; + reg = <0x32f10108 0x8>; + clocks = <&clk IMX8MP_CLK_HSIO_ROOT>, + <&clk IMX8MP_CLK_USB_ROOT>; + clock-names = "hsio", "suspend"; + interrupts = ; + #address-cells = <1>; + #size-cells = <1>; + dma-ranges = <0x40000000 0x40000000 0xc0000000>; + ranges; + status = "disabled"; + + usb_dwc3_1: dwc3@38200000 { + compatible = "snps,dwc3"; + reg = <0x38200000 0x10000>; + clocks = <&clk IMX8MP_CLK_HSIO_AXI>, + <&clk IMX8MP_CLK_USB_CORE_REF>, + <&clk IMX8MP_CLK_USB_ROOT>; + clock-names = "bus_early", "ref", "suspend"; + assigned-clocks = <&clk IMX8MP_CLK_HSIO_AXI>; + assigned-clock-parents = <&clk IMX8MP_SYS_PLL2_500M>; + assigned-clock-rates = <500000000>; + interrupts = ; + phys = <&usb3_phy1>, <&usb3_phy1>; + phy-names = "usb2-phy", "usb3-phy"; + snps,dis-u2-freeclk-exists-quirk; + }; + }; }; }; -- cgit v1.2.3 From 43da4f92a611d0cccbe577384d1de9d7a70fd5b9 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Tue, 29 Dec 2020 19:37:46 +0800 Subject: arm64: dts: imx8mp-evk: enable usb1 as host mode Enable usb host port with type-A connector on imx8mp-evk board. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Li Jun Link: https://lore.kernel.org/r/1609241866-9508-5-git-send-email-jun.li@nxp.com Signed-off-by: Greg Kroah-Hartman --- arch/arm64/boot/dts/freescale/imx8mp-evk.dts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/arch/arm64/boot/dts/freescale/imx8mp-evk.dts b/arch/arm64/boot/dts/freescale/imx8mp-evk.dts index b10dce8767a4..7db4273cc88b 100644 --- a/arch/arm64/boot/dts/freescale/imx8mp-evk.dts +++ b/arch/arm64/boot/dts/freescale/imx8mp-evk.dts @@ -127,6 +127,21 @@ status = "okay"; }; +&usb3_phy1 { + status = "okay"; +}; + +&usb3_1 { + status = "okay"; +}; + +&usb_dwc3_1 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usb1_vbus>; + dr_mode = "host"; + status = "okay"; +}; + &usdhc2 { assigned-clocks = <&clk IMX8MP_CLK_USDHC2>; assigned-clock-rates = <400000000>; @@ -232,6 +247,12 @@ >; }; + pinctrl_usb1_vbus: usb1grp { + fsl,pins = < + MX8MP_IOMUXC_GPIO1_IO14__USB2_OTG_PWR 0x19 + >; + }; + pinctrl_usdhc2: usdhc2grp { fsl,pins = < MX8MP_IOMUXC_SD2_CLK__USDHC2_CLK 0x190 -- cgit v1.2.3 From 9b3bd898421bd9fca5c88a3c3291d9f60df3bb69 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 28 Dec 2020 22:02:03 +0200 Subject: usb: gadget: u_serial: use %*ph to print small buffer Use %*ph format to print small buffer as hex string. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20201228200203.58525-1-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/u_serial.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c index 2caccbb6e014..768f883f486c 100644 --- a/drivers/usb/gadget/function/u_serial.c +++ b/drivers/usb/gadget/function/u_serial.c @@ -258,9 +258,7 @@ __acquires(&port->port_lock) list_del(&req->list); req->zero = kfifo_is_empty(&port->port_write_buf); - pr_vdebug("ttyGS%d: tx len=%d, 0x%02x 0x%02x 0x%02x ...\n", - port->port_num, len, *((u8 *)req->buf), - *((u8 *)req->buf+1), *((u8 *)req->buf+2)); + pr_vdebug("ttyGS%d: tx len=%d, %3ph ...\n", port->port_num, len, req->buf); /* Drop lock while we call out of driver; completions * could be issued while we do so. Disconnection may -- cgit v1.2.3 From 82c46b8ed9dc395df902c1857e908f08f8395ca7 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Tue, 29 Dec 2020 15:03:29 -0800 Subject: usb: dwc3: gadget: Introduce a DWC3 VBUS draw callback Some devices support charging while in device mode. In these situations, the USB gadget will notify the DWC3 gadget driver to modify the current based on the enumeration and device state. The usb_phy_set_power() API will allow external charger entities to adjust the charge current through the notifier block. Reviewed-by: Peter Chen Signed-off-by: Wesley Cheng Link: https://lore.kernel.org/r/1609283011-21997-2-git-send-email-wcheng@codeaurora.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/gadget.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 78cb4db8a6e4..950a67c9dabc 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2463,6 +2463,16 @@ static void dwc3_gadget_set_speed(struct usb_gadget *g, spin_unlock_irqrestore(&dwc->lock, flags); } +static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned int mA) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + + if (dwc->usb2_phy) + return usb_phy_set_power(dwc->usb2_phy, mA); + + return 0; +} + static const struct usb_gadget_ops dwc3_gadget_ops = { .get_frame = dwc3_gadget_get_frame, .wakeup = dwc3_gadget_wakeup, @@ -2472,6 +2482,7 @@ static const struct usb_gadget_ops dwc3_gadget_ops = { .udc_stop = dwc3_gadget_stop, .udc_set_speed = dwc3_gadget_set_speed, .get_config_params = dwc3_gadget_config_params, + .vbus_draw = dwc3_gadget_vbus_draw, }; /* -------------------------------------------------------------------------- */ -- cgit v1.2.3 From 8280de6ab07b4c63eb607662754f151e539031a1 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Tue, 29 Dec 2020 15:03:30 -0800 Subject: usb: gadget: composite: Split composite reset and disconnect Add a specific composite reset API to differentiate between disconnect and reset events. This is needed for adjusting the current draw accordingly based on the USB battery charging specification. The device is only allowed to draw the 500/900 mA (HS/SS) while in the CONFIGURED state, and only 100 mA in the connected and UNCONFIGURED state. Reviewed-by: Peter Chen Signed-off-by: Wesley Cheng Link: https://lore.kernel.org/r/1609283011-21997-3-git-send-email-wcheng@codeaurora.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/composite.c | 21 +++++++++++++++++++-- include/linux/usb/composite.h | 2 ++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index c6d455f2bb92..86e86a005e25 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -2036,7 +2036,7 @@ done: return value; } -void composite_disconnect(struct usb_gadget *gadget) +static void __composite_disconnect(struct usb_gadget *gadget) { struct usb_composite_dev *cdev = get_gadget_data(gadget); unsigned long flags; @@ -2053,6 +2053,23 @@ void composite_disconnect(struct usb_gadget *gadget) spin_unlock_irqrestore(&cdev->lock, flags); } +void composite_disconnect(struct usb_gadget *gadget) +{ + usb_gadget_vbus_draw(gadget, 0); + __composite_disconnect(gadget); +} + +void composite_reset(struct usb_gadget *gadget) +{ + /* + * Section 1.4.13 Standard Downstream Port of the USB battery charging + * specification v1.2 states that a device connected on a SDP shall only + * draw at max 100mA while in a connected, but unconfigured state. + */ + usb_gadget_vbus_draw(gadget, 100); + __composite_disconnect(gadget); +} + /*-------------------------------------------------------------------------*/ static ssize_t suspended_show(struct device *dev, struct device_attribute *attr, @@ -2373,7 +2390,7 @@ static const struct usb_gadget_driver composite_driver_template = { .unbind = composite_unbind, .setup = composite_setup, - .reset = composite_disconnect, + .reset = composite_reset, .disconnect = composite_disconnect, .suspend = composite_suspend, diff --git a/include/linux/usb/composite.h b/include/linux/usb/composite.h index a2d229ab63ba..5646dad886e6 100644 --- a/include/linux/usb/composite.h +++ b/include/linux/usb/composite.h @@ -525,6 +525,8 @@ extern struct usb_string *usb_gstrings_attach(struct usb_composite_dev *cdev, extern int usb_string_ids_n(struct usb_composite_dev *c, unsigned n); extern void composite_disconnect(struct usb_gadget *gadget); +extern void composite_reset(struct usb_gadget *gadget); + extern int composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl); extern void composite_suspend(struct usb_gadget *gadget); -- cgit v1.2.3 From 77adb8bdf4227257e26b7ff67272678e66a0b250 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Tue, 29 Dec 2020 15:05:35 -0800 Subject: usb: dwc3: gadget: Allow runtime suspend if UDC unbinded The DWC3 runtime suspend routine checks for the USB connected parameter to determine if the controller can enter into a low power state. The connected state is only set to false after receiving a disconnect event. However, in the case of a device initiated disconnect (i.e. UDC unbind), the controller is halted and a disconnect event is never generated. Set the connected flag to false if issuing a device initiated disconnect to allow the controller to be suspended. Signed-off-by: Wesley Cheng Link: https://lore.kernel.org/r/1609283136-22140-2-git-send-email-wcheng@codeaurora.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/gadget.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 950a67c9dabc..97b33d3c7a1c 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2107,6 +2107,17 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) } } + /* + * Check the return value for successful resume, or error. For a + * successful resume, the DWC3 runtime PM resume routine will handle + * the run stop sequence, so avoid duplicate operations here. + */ + ret = pm_runtime_get_sync(dwc->dev); + if (!ret || ret < 0) { + pm_runtime_put(dwc->dev); + return 0; + } + /* * Synchronize any pending event handling before executing the controller * halt routine. @@ -2145,10 +2156,12 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) dwc->ev_buf->lpos = (dwc->ev_buf->lpos + count) % dwc->ev_buf->length; } + dwc->connected = false; } ret = dwc3_gadget_run_stop(dwc, is_on, false); spin_unlock_irqrestore(&dwc->lock, flags); + pm_runtime_put(dwc->dev); return ret; } -- cgit v1.2.3 From 7c9a2598463a7803629a90e9e425af7ed241ec65 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Tue, 29 Dec 2020 15:05:36 -0800 Subject: usb: dwc3: gadget: Preserve UDC max speed setting The USB gadget/UDC driver can restrict the DWC3 controller speed using dwc3_gadget_set_speed(). Store this setting into a variable, in order for this setting to persist across controller resets due to runtime PM. Signed-off-by: Wesley Cheng Link: https://lore.kernel.org/r/1609283136-22140-3-git-send-email-wcheng@codeaurora.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.h | 1 + drivers/usb/dwc3/gadget.c | 108 ++++++++++++++++++++++++---------------------- 2 files changed, 58 insertions(+), 51 deletions(-) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 2f95f08ca511..178479ef16b1 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -1125,6 +1125,7 @@ struct dwc3 { u32 nr_scratch; u32 u1u2; u32 maximum_speed; + u32 gadget_max_speed; u32 ip; diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 97b33d3c7a1c..85736dd6673b 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2036,6 +2036,61 @@ static void dwc3_stop_active_transfers(struct dwc3 *dwc) } } +static void __dwc3_gadget_set_speed(struct dwc3 *dwc) +{ + u32 reg; + + reg = dwc3_readl(dwc->regs, DWC3_DCFG); + reg &= ~(DWC3_DCFG_SPEED_MASK); + + /* + * WORKAROUND: DWC3 revision < 2.20a have an issue + * which would cause metastability state on Run/Stop + * bit if we try to force the IP to USB2-only mode. + * + * Because of that, we cannot configure the IP to any + * speed other than the SuperSpeed + * + * Refers to: + * + * STAR#9000525659: Clock Domain Crossing on DCTL in + * USB 2.0 Mode + */ + if (DWC3_VER_IS_PRIOR(DWC3, 220A) && + !dwc->dis_metastability_quirk) { + reg |= DWC3_DCFG_SUPERSPEED; + } else { + switch (dwc->gadget_max_speed) { + case USB_SPEED_LOW: + reg |= DWC3_DCFG_LOWSPEED; + break; + case USB_SPEED_FULL: + reg |= DWC3_DCFG_FULLSPEED; + break; + case USB_SPEED_HIGH: + reg |= DWC3_DCFG_HIGHSPEED; + break; + case USB_SPEED_SUPER: + reg |= DWC3_DCFG_SUPERSPEED; + break; + case USB_SPEED_SUPER_PLUS: + if (DWC3_IP_IS(DWC3)) + reg |= DWC3_DCFG_SUPERSPEED; + else + reg |= DWC3_DCFG_SUPERSPEED_PLUS; + break; + default: + dev_err(dwc->dev, "invalid speed (%d)\n", dwc->gadget_max_speed); + + if (DWC3_IP_IS(DWC3)) + reg |= DWC3_DCFG_SUPERSPEED; + else + reg |= DWC3_DCFG_SUPERSPEED_PLUS; + } + } + dwc3_writel(dwc->regs, DWC3_DCFG, reg); +} + static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend) { u32 reg; @@ -2058,6 +2113,7 @@ static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend) if (dwc->has_hibernation) reg |= DWC3_DCTL_KEEP_CONNECT; + __dwc3_gadget_set_speed(dwc); dwc->pullups_connected = true; } else { reg &= ~DWC3_DCTL_RUN_STOP; @@ -2420,59 +2476,9 @@ static void dwc3_gadget_set_speed(struct usb_gadget *g, { struct dwc3 *dwc = gadget_to_dwc(g); unsigned long flags; - u32 reg; spin_lock_irqsave(&dwc->lock, flags); - reg = dwc3_readl(dwc->regs, DWC3_DCFG); - reg &= ~(DWC3_DCFG_SPEED_MASK); - - /* - * WORKAROUND: DWC3 revision < 2.20a have an issue - * which would cause metastability state on Run/Stop - * bit if we try to force the IP to USB2-only mode. - * - * Because of that, we cannot configure the IP to any - * speed other than the SuperSpeed - * - * Refers to: - * - * STAR#9000525659: Clock Domain Crossing on DCTL in - * USB 2.0 Mode - */ - if (DWC3_VER_IS_PRIOR(DWC3, 220A) && - !dwc->dis_metastability_quirk) { - reg |= DWC3_DCFG_SUPERSPEED; - } else { - switch (speed) { - case USB_SPEED_LOW: - reg |= DWC3_DCFG_LOWSPEED; - break; - case USB_SPEED_FULL: - reg |= DWC3_DCFG_FULLSPEED; - break; - case USB_SPEED_HIGH: - reg |= DWC3_DCFG_HIGHSPEED; - break; - case USB_SPEED_SUPER: - reg |= DWC3_DCFG_SUPERSPEED; - break; - case USB_SPEED_SUPER_PLUS: - if (DWC3_IP_IS(DWC3)) - reg |= DWC3_DCFG_SUPERSPEED; - else - reg |= DWC3_DCFG_SUPERSPEED_PLUS; - break; - default: - dev_err(dwc->dev, "invalid speed (%d)\n", speed); - - if (DWC3_IP_IS(DWC3)) - reg |= DWC3_DCFG_SUPERSPEED; - else - reg |= DWC3_DCFG_SUPERSPEED_PLUS; - } - } - dwc3_writel(dwc->regs, DWC3_DCFG, reg); - + dwc->gadget_max_speed = speed; spin_unlock_irqrestore(&dwc->lock, flags); } -- cgit v1.2.3 From ed054e4e95d6cb0582f1283707ddb40f18d14bfb Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Mon, 28 Dec 2020 12:18:24 -0500 Subject: USB: gadget: f_printer: set a default q_len The usb_f_printer gadget driver uses a default q_len value of *0* which prevents any IO from occurring. Moreover, once the driver is instantiated it is impossible to change the q_len value. The following patch uses a default q_len value of 10 which matches the legacy g_printer gadget driver. This minimizes the possibility that you end up with a non-working printer gadget. It is still possible to set the q_len to a different value using the configfs path of the same name. Signed-off-by: Michael R Sweet Link: https://lore.kernel.org/r/9DFB1605-63A5-46DB-A5A4-B59B315D8115@msweet.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_printer.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/usb/gadget/function/f_printer.c b/drivers/usb/gadget/function/f_printer.c index 64a4112068fc..31eba2a43b1a 100644 --- a/drivers/usb/gadget/function/f_printer.c +++ b/drivers/usb/gadget/function/f_printer.c @@ -51,6 +51,8 @@ #define GET_PORT_STATUS 1 #define SOFT_RESET 2 +#define DEFAULT_Q_LEN 10 /* same as legacy g_printer gadget */ + static int major, minors; static struct class *usb_gadget_class; static DEFINE_IDA(printer_ida); @@ -1363,6 +1365,9 @@ static struct usb_function_instance *gprinter_alloc_inst(void) opts->func_inst.free_func_inst = gprinter_free_inst; ret = &opts->func_inst; + /* Make sure q_len is initialized, otherwise the bound device can't support read/write! */ + opts->q_len = DEFAULT_Q_LEN; + mutex_lock(&printer_ida_lock); if (ida_is_empty(&printer_ida)) { -- cgit v1.2.3 From a906f45d148074274ab4f5df4ebc33412bfa9973 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Mon, 16 Nov 2020 12:11:44 -0800 Subject: platform/chrome: cros_ec_typec: Make disc_done flag partner-only Change the disc_done flag, which indicates whether PD discovery is complete, to sop_disc_done instead, since we will process SOP and SOP' discovery data separately. Signed-off-by: Prashant Malani Reviewed-by: Greg Kroah-Hartman Reviewed-by: Heikki Krogerus Signed-off-by: Benson Leung Link: https://lore.kernel.org/r/20201116201150.2919178-5-pmalani@chromium.org --- drivers/platform/chrome/cros_ec_typec.c | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index c43868615790..2d8bda491a0c 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -57,8 +57,8 @@ struct cros_typec_port { /* Port alt modes. */ struct typec_altmode p_altmode[CROS_EC_ALTMODE_MAX]; - /* Flag indicating that PD discovery data parsing is completed. */ - bool disc_done; + /* Flag indicating that PD partner discovery data parsing is completed. */ + bool sop_disc_done; struct ec_response_typec_discovery *sop_disc; struct list_head partner_mode_list; }; @@ -210,7 +210,7 @@ static void cros_typec_remove_partner(struct cros_typec_data *typec, typec_unregister_partner(port->partner); port->partner = NULL; memset(&port->p_identity, 0, sizeof(port->p_identity)); - port->disc_done = false; + port->sop_disc_done = false; } static void cros_unregister_ports(struct cros_typec_data *typec) @@ -725,18 +725,13 @@ static void cros_typec_handle_status(struct cros_typec_data *typec, int port_num return; } - if (typec->ports[port_num]->disc_done) - return; - /* Handle any events appropriately. */ - if (resp.events & PD_STATUS_EVENT_SOP_DISC_DONE) { + if (resp.events & PD_STATUS_EVENT_SOP_DISC_DONE && !typec->ports[port_num]->sop_disc_done) { ret = cros_typec_handle_sop_disc(typec, port_num); - if (ret < 0) { + if (ret < 0) dev_err(typec->dev, "Couldn't parse SOP Disc data, port: %d\n", port_num); - return; - } - - typec->ports[port_num]->disc_done = true; + else + typec->ports[port_num]->sop_disc_done = true; } } -- cgit v1.2.3 From 8fab2755191f86d4f4195ceb5ade4d4c17052265 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Mon, 16 Nov 2020 12:11:46 -0800 Subject: platform/chrome: cros_ec_typec: Factor out PD identity parsing Factor out the PD identity parsing code into a separate function. This way it can be re-used for Cable PD identity parsing in future patches. No functional changes are introduced by this patch. Signed-off-by: Prashant Malani Reviewed-by: Greg Kroah-Hartman Reviewed-by: Heikki Krogerus Signed-off-by: Benson Leung Link: https://lore.kernel.org/r/20201116201150.2919178-6-pmalani@chromium.org --- drivers/platform/chrome/cros_ec_typec.c | 35 ++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 2d8bda491a0c..7a70138e63d1 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -655,6 +655,28 @@ err_cleanup: return ret; } +/* + * Parse the PD identity data from the EC PD discovery responses and copy that to the supplied + * PD identity struct. + */ +static void cros_typec_parse_pd_identity(struct usb_pd_identity *id, + struct ec_response_typec_discovery *disc) +{ + int i; + + /* First, update the PD identity VDOs for the partner. */ + if (disc->identity_count > 0) + id->id_header = disc->discovery_vdo[0]; + if (disc->identity_count > 1) + id->cert_stat = disc->discovery_vdo[1]; + if (disc->identity_count > 2) + id->product = disc->discovery_vdo[2]; + + /* Copy the remaining identity VDOs till a maximum of 6. */ + for (i = 3; i < disc->identity_count && i < VDO_MAX_OBJECTS; i++) + id->vdo[i - 3] = disc->discovery_vdo[i]; +} + static int cros_typec_handle_sop_disc(struct cros_typec_data *typec, int port_num) { struct cros_typec_port *port = typec->ports[port_num]; @@ -664,7 +686,6 @@ static int cros_typec_handle_sop_disc(struct cros_typec_data *typec, int port_nu .partner_type = TYPEC_PARTNER_SOP, }; int ret = 0; - int i; if (!port->partner) { dev_err(typec->dev, @@ -682,17 +703,7 @@ static int cros_typec_handle_sop_disc(struct cros_typec_data *typec, int port_nu goto disc_exit; } - /* First, update the PD identity VDOs for the partner. */ - if (sop_disc->identity_count > 0) - port->p_identity.id_header = sop_disc->discovery_vdo[0]; - if (sop_disc->identity_count > 1) - port->p_identity.cert_stat = sop_disc->discovery_vdo[1]; - if (sop_disc->identity_count > 2) - port->p_identity.product = sop_disc->discovery_vdo[2]; - - /* Copy the remaining identity VDOs till a maximum of 6. */ - for (i = 3; i < sop_disc->identity_count && i < VDO_MAX_OBJECTS; i++) - port->p_identity.vdo[i - 3] = sop_disc->discovery_vdo[i]; + cros_typec_parse_pd_identity(&port->p_identity, sop_disc); ret = typec_partner_set_identity(port->partner); if (ret < 0) { -- cgit v1.2.3 From c097f229b71e06e6ef21e4e175b322b3fab06268 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Mon, 16 Nov 2020 12:11:48 -0800 Subject: platform/chrome: cros_ec_typec: Rename discovery struct Rename the sop_disc data struct which is used to store PD discovery data to the more generic name of disc_data. It can then be re-used to store and process cable discovery data. Signed-off-by: Prashant Malani Reviewed-by: Greg Kroah-Hartman Reviewed-by: Heikki Krogerus Signed-off-by: Benson Leung Link: https://lore.kernel.org/r/20201116201150.2919178-7-pmalani@chromium.org --- drivers/platform/chrome/cros_ec_typec.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 7a70138e63d1..bf98e2ecda3f 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -59,7 +59,7 @@ struct cros_typec_port { /* Flag indicating that PD partner discovery data parsing is completed. */ bool sop_disc_done; - struct ec_response_typec_discovery *sop_disc; + struct ec_response_typec_discovery *disc_data; struct list_head partner_mode_list; }; @@ -323,8 +323,8 @@ static int cros_typec_init_ports(struct cros_typec_data *typec) cros_typec_register_port_altmodes(typec, port_num); - cros_port->sop_disc = devm_kzalloc(dev, EC_PROTO2_MAX_RESPONSE_SIZE, GFP_KERNEL); - if (!cros_port->sop_disc) { + cros_port->disc_data = devm_kzalloc(dev, EC_PROTO2_MAX_RESPONSE_SIZE, GFP_KERNEL); + if (!cros_port->disc_data) { ret = -ENOMEM; goto unregister_ports; } @@ -615,7 +615,7 @@ static int cros_typec_get_mux_info(struct cros_typec_data *typec, int port_num, static int cros_typec_register_altmodes(struct cros_typec_data *typec, int port_num) { struct cros_typec_port *port = typec->ports[port_num]; - struct ec_response_typec_discovery *sop_disc = port->sop_disc; + struct ec_response_typec_discovery *sop_disc = port->disc_data; struct cros_typec_altmode_node *node; struct typec_altmode_desc desc; struct typec_altmode *amode; @@ -680,7 +680,7 @@ static void cros_typec_parse_pd_identity(struct usb_pd_identity *id, static int cros_typec_handle_sop_disc(struct cros_typec_data *typec, int port_num) { struct cros_typec_port *port = typec->ports[port_num]; - struct ec_response_typec_discovery *sop_disc = port->sop_disc; + struct ec_response_typec_discovery *sop_disc = port->disc_data; struct ec_params_typec_discovery req = { .port = port_num, .partner_type = TYPEC_PARTNER_SOP, -- cgit v1.2.3 From 8b46a212ad11f25e5f77e3a2c6df1d0f758583b7 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Mon, 16 Nov 2020 12:11:50 -0800 Subject: platform/chrome: cros_ec_typec: Register cable When the Chrome Embedded Controller notifies the driver that SOP' discovery is complete, retrieve the PD discovery data and register a cable object with the Type C connector class framework. Cc: Heikki Krogerus Signed-off-by: Prashant Malani Reviewed-by: Greg Kroah-Hartman Reviewed-by: Heikki Krogerus Signed-off-by: Benson Leung Link: https://lore.kernel.org/r/20201116201150.2919178-8-pmalani@chromium.org --- drivers/platform/chrome/cros_ec_typec.c | 67 +++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index bf98e2ecda3f..8ecf2ede9c19 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -44,8 +44,11 @@ struct cros_typec_port { /* Initial capabilities for the port. */ struct typec_capability caps; struct typec_partner *partner; + struct typec_cable *cable; /* Port partner PD identity info. */ struct usb_pd_identity p_identity; + /* Port cable PD identity info. */ + struct usb_pd_identity c_identity; struct typec_switch *ori_sw; struct typec_mux *mux; struct usb_role_switch *role_sw; @@ -59,6 +62,7 @@ struct cros_typec_port { /* Flag indicating that PD partner discovery data parsing is completed. */ bool sop_disc_done; + bool sop_prime_disc_done; struct ec_response_typec_discovery *disc_data; struct list_head partner_mode_list; }; @@ -213,6 +217,17 @@ static void cros_typec_remove_partner(struct cros_typec_data *typec, port->sop_disc_done = false; } +static void cros_typec_remove_cable(struct cros_typec_data *typec, + int port_num) +{ + struct cros_typec_port *port = typec->ports[port_num]; + + typec_unregister_cable(port->cable); + port->cable = NULL; + memset(&port->c_identity, 0, sizeof(port->c_identity)); + port->sop_prime_disc_done = false; +} + static void cros_unregister_ports(struct cros_typec_data *typec) { int i; @@ -224,6 +239,9 @@ static void cros_unregister_ports(struct cros_typec_data *typec) if (typec->ports[i]->partner) cros_typec_remove_partner(typec, i); + if (typec->ports[i]->cable) + cros_typec_remove_cable(typec, i); + usb_role_switch_put(typec->ports[i]->role_sw); typec_switch_put(typec->ports[i]->ori_sw); typec_mux_put(typec->ports[i]->mux); @@ -598,6 +616,9 @@ static void cros_typec_set_port_params_v1(struct cros_typec_data *typec, if (!typec->ports[port_num]->partner) return; cros_typec_remove_partner(typec, port_num); + + if (typec->ports[port_num]->cable) + cros_typec_remove_cable(typec, port_num); } } @@ -677,6 +698,43 @@ static void cros_typec_parse_pd_identity(struct usb_pd_identity *id, id->vdo[i - 3] = disc->discovery_vdo[i]; } +static int cros_typec_handle_sop_prime_disc(struct cros_typec_data *typec, int port_num) +{ + struct cros_typec_port *port = typec->ports[port_num]; + struct ec_response_typec_discovery *disc = port->disc_data; + struct typec_cable_desc desc = {}; + struct ec_params_typec_discovery req = { + .port = port_num, + .partner_type = TYPEC_PARTNER_SOP_PRIME, + }; + int ret = 0; + + memset(disc, 0, EC_PROTO2_MAX_RESPONSE_SIZE); + ret = cros_typec_ec_command(typec, 0, EC_CMD_TYPEC_DISCOVERY, &req, sizeof(req), + disc, EC_PROTO2_MAX_RESPONSE_SIZE); + if (ret < 0) { + dev_err(typec->dev, "Failed to get SOP' discovery data for port: %d\n", port_num); + goto sop_prime_disc_exit; + } + + /* Parse the PD identity data, even if only 0s were returned. */ + cros_typec_parse_pd_identity(&port->c_identity, disc); + + if (disc->identity_count != 0) + desc.active = PD_IDH_PTYPE(port->c_identity.id_header) == IDH_PTYPE_ACABLE; + + desc.identity = &port->c_identity; + + port->cable = typec_register_cable(port->port, &desc); + if (IS_ERR(port->cable)) { + ret = PTR_ERR(port->cable); + port->cable = NULL; + } + +sop_prime_disc_exit: + return ret; +} + static int cros_typec_handle_sop_disc(struct cros_typec_data *typec, int port_num) { struct cros_typec_port *port = typec->ports[port_num]; @@ -744,6 +802,15 @@ static void cros_typec_handle_status(struct cros_typec_data *typec, int port_num else typec->ports[port_num]->sop_disc_done = true; } + + if (resp.events & PD_STATUS_EVENT_SOP_PRIME_DISC_DONE && + !typec->ports[port_num]->sop_prime_disc_done) { + ret = cros_typec_handle_sop_prime_disc(typec, port_num); + if (ret < 0) + dev_err(typec->dev, "Couldn't parse SOP' Disc data, port: %d\n", port_num); + else + typec->ports[port_num]->sop_prime_disc_done = true; + } } static int cros_typec_port_update(struct cros_typec_data *typec, int port_num) -- cgit v1.2.3 From 72d6e32bd85bd1e5cb5aa467f4eb5d0a69559953 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Mon, 16 Nov 2020 12:11:52 -0800 Subject: platform/chrome: cros_ec_typec: Store cable plug type Use the PD VDO Type C cable plug type macro to retrieve and store the cable plug type in the cable descriptor. Cc: Heikki Krogerus Cc: Greg Kroah-Hartman Signed-off-by: Prashant Malani Reviewed-by: Greg Kroah-Hartman Reviewed-by: Heikki Krogerus Signed-off-by: Benson Leung Link: https://lore.kernel.org/r/20201116201150.2919178-9-pmalani@chromium.org --- drivers/platform/chrome/cros_ec_typec.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 8ecf2ede9c19..c0f34aa9e6dd 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -707,6 +707,7 @@ static int cros_typec_handle_sop_prime_disc(struct cros_typec_data *typec, int p .port = port_num, .partner_type = TYPEC_PARTNER_SOP_PRIME, }; + u32 cable_plug_type; int ret = 0; memset(disc, 0, EC_PROTO2_MAX_RESPONSE_SIZE); @@ -720,8 +721,26 @@ static int cros_typec_handle_sop_prime_disc(struct cros_typec_data *typec, int p /* Parse the PD identity data, even if only 0s were returned. */ cros_typec_parse_pd_identity(&port->c_identity, disc); - if (disc->identity_count != 0) + if (disc->identity_count != 0) { + cable_plug_type = VDO_TYPEC_CABLE_TYPE(port->c_identity.vdo[0]); + switch (cable_plug_type) { + case CABLE_ATYPE: + desc.type = USB_PLUG_TYPE_A; + break; + case CABLE_BTYPE: + desc.type = USB_PLUG_TYPE_B; + break; + case CABLE_CTYPE: + desc.type = USB_PLUG_TYPE_C; + break; + case CABLE_CAPTIVE: + desc.type = USB_PLUG_CAPTIVE; + break; + default: + desc.type = USB_PLUG_NONE; + } desc.active = PD_IDH_PTYPE(port->c_identity.id_header) == IDH_PTYPE_ACABLE; + } desc.identity = &port->c_identity; -- cgit v1.2.3 From 5992297639115785e167b9977101287e45106515 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Mon, 16 Nov 2020 12:11:54 -0800 Subject: platform/chrome: cros_ec_typec: Set partner num_altmodes Set the number of altmodes available for a registered partner using the Type C connector class framework routine. Cc: Heikki Krogerus Signed-off-by: Prashant Malani Reviewed-by: Heikki Krogerus Signed-off-by: Benson Leung Link: https://lore.kernel.org/r/20201116201150.2919178-10-pmalani@chromium.org --- drivers/platform/chrome/cros_ec_typec.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index c0f34aa9e6dd..9466a092904e 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -640,6 +640,7 @@ static int cros_typec_register_altmodes(struct cros_typec_data *typec, int port_ struct cros_typec_altmode_node *node; struct typec_altmode_desc desc; struct typec_altmode *amode; + int num_altmodes = 0; int ret = 0; int i, j; @@ -666,9 +667,16 @@ static int cros_typec_register_altmodes(struct cros_typec_data *typec, int port_ node->amode = amode; list_add_tail(&node->list, &port->partner_mode_list); + num_altmodes++; } } + ret = typec_partner_set_num_altmodes(port->partner, num_altmodes); + if (ret < 0) { + dev_err(typec->dev, "Unable to set partner num_altmodes for port: %d\n", port_num); + goto err_cleanup; + } + return 0; err_cleanup: -- cgit v1.2.3 From f4edab68e10119a71a9e62a7fe537685d0f41478 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Mon, 16 Nov 2020 12:11:56 -0800 Subject: platform/chrome: cros_ec_typec: Register SOP' cable plug In order to register cable alternate modes, we need to first register a plug object. Use the Type C connector class framework to register a SOP' plug for this purpose. Since a cable and plug go hand in hand, we can handle the registration and removal together. Signed-off-by: Prashant Malani Reviewed-by: Heikki Krogerus Signed-off-by: Benson Leung Link: https://lore.kernel.org/r/20201116201150.2919178-11-pmalani@chromium.org --- drivers/platform/chrome/cros_ec_typec.c | 35 ++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 9466a092904e..afba902b6491 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -45,6 +45,8 @@ struct cros_typec_port { struct typec_capability caps; struct typec_partner *partner; struct typec_cable *cable; + /* SOP' plug. */ + struct typec_plug *plug; /* Port partner PD identity info. */ struct usb_pd_identity p_identity; /* Port cable PD identity info. */ @@ -222,6 +224,8 @@ static void cros_typec_remove_cable(struct cros_typec_data *typec, { struct cros_typec_port *port = typec->ports[port_num]; + typec_unregister_plug(port->plug); + port->plug = NULL; typec_unregister_cable(port->cable); port->cable = NULL; memset(&port->c_identity, 0, sizeof(port->c_identity)); @@ -710,7 +714,8 @@ static int cros_typec_handle_sop_prime_disc(struct cros_typec_data *typec, int p { struct cros_typec_port *port = typec->ports[port_num]; struct ec_response_typec_discovery *disc = port->disc_data; - struct typec_cable_desc desc = {}; + struct typec_cable_desc c_desc = {}; + struct typec_plug_desc p_desc; struct ec_params_typec_discovery req = { .port = port_num, .partner_type = TYPEC_PARTNER_SOP_PRIME, @@ -733,32 +738,44 @@ static int cros_typec_handle_sop_prime_disc(struct cros_typec_data *typec, int p cable_plug_type = VDO_TYPEC_CABLE_TYPE(port->c_identity.vdo[0]); switch (cable_plug_type) { case CABLE_ATYPE: - desc.type = USB_PLUG_TYPE_A; + c_desc.type = USB_PLUG_TYPE_A; break; case CABLE_BTYPE: - desc.type = USB_PLUG_TYPE_B; + c_desc.type = USB_PLUG_TYPE_B; break; case CABLE_CTYPE: - desc.type = USB_PLUG_TYPE_C; + c_desc.type = USB_PLUG_TYPE_C; break; case CABLE_CAPTIVE: - desc.type = USB_PLUG_CAPTIVE; + c_desc.type = USB_PLUG_CAPTIVE; break; default: - desc.type = USB_PLUG_NONE; + c_desc.type = USB_PLUG_NONE; } - desc.active = PD_IDH_PTYPE(port->c_identity.id_header) == IDH_PTYPE_ACABLE; + c_desc.active = PD_IDH_PTYPE(port->c_identity.id_header) == IDH_PTYPE_ACABLE; } - desc.identity = &port->c_identity; + c_desc.identity = &port->c_identity; - port->cable = typec_register_cable(port->port, &desc); + port->cable = typec_register_cable(port->port, &c_desc); if (IS_ERR(port->cable)) { ret = PTR_ERR(port->cable); port->cable = NULL; + goto sop_prime_disc_exit; + } + + p_desc.index = TYPEC_PLUG_SOP_P; + port->plug = typec_register_plug(port->cable, &p_desc); + if (IS_ERR(port->plug)) { + ret = PTR_ERR(port->plug); + port->plug = NULL; + goto sop_prime_disc_exit; } + return 0; + sop_prime_disc_exit: + cros_typec_remove_cable(typec, port_num); return ret; } -- cgit v1.2.3 From 1563090965421f7e96cc006c150180f1526a24f7 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Mon, 16 Nov 2020 12:11:58 -0800 Subject: platform/chrome: cros_ec_typec: Register plug altmodes Modify the altmode registration (and unregistration) code so that it can be used by both partners and plugs. Then, add code to register plug altmodes using the newly parameterized function. Also set the number of alternate modes for the plug using the associated Type C connector class function typec_plug_set_num_altmodes(). Signed-off-by: Prashant Malani Reviewed-by: Heikki Krogerus Signed-off-by: Benson Leung Link: https://lore.kernel.org/r/20201116201150.2919178-12-pmalani@chromium.org --- drivers/platform/chrome/cros_ec_typec.c | 50 ++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index afba902b6491..fd446f9029a0 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -67,6 +67,7 @@ struct cros_typec_port { bool sop_prime_disc_done; struct ec_response_typec_discovery *disc_data; struct list_head partner_mode_list; + struct list_head plug_mode_list; }; /* Platform-specific data for the Chrome OS EC Type C controller. */ @@ -186,12 +187,15 @@ static int cros_typec_add_partner(struct cros_typec_data *typec, int port_num, return ret; } -static void cros_typec_unregister_altmodes(struct cros_typec_data *typec, int port_num) +static void cros_typec_unregister_altmodes(struct cros_typec_data *typec, int port_num, + bool is_partner) { struct cros_typec_port *port = typec->ports[port_num]; struct cros_typec_altmode_node *node, *tmp; + struct list_head *head; - list_for_each_entry_safe(node, tmp, &port->partner_mode_list, list) { + head = is_partner ? &port->partner_mode_list : &port->plug_mode_list; + list_for_each_entry_safe(node, tmp, head, list) { list_del(&node->list); typec_unregister_altmode(node->amode); devm_kfree(typec->dev, node); @@ -203,7 +207,7 @@ static void cros_typec_remove_partner(struct cros_typec_data *typec, { struct cros_typec_port *port = typec->ports[port_num]; - cros_typec_unregister_altmodes(typec, port_num); + cros_typec_unregister_altmodes(typec, port_num, true); port->state.alt = NULL; port->state.mode = TYPEC_STATE_USB; @@ -224,6 +228,8 @@ static void cros_typec_remove_cable(struct cros_typec_data *typec, { struct cros_typec_port *port = typec->ports[port_num]; + cros_typec_unregister_altmodes(typec, port_num, false); + typec_unregister_plug(port->plug); port->plug = NULL; typec_unregister_cable(port->cable); @@ -352,6 +358,7 @@ static int cros_typec_init_ports(struct cros_typec_data *typec) } INIT_LIST_HEAD(&cros_port->partner_mode_list); + INIT_LIST_HEAD(&cros_port->plug_mode_list); } return 0; @@ -637,7 +644,11 @@ static int cros_typec_get_mux_info(struct cros_typec_data *typec, int port_num, sizeof(req), resp, sizeof(*resp)); } -static int cros_typec_register_altmodes(struct cros_typec_data *typec, int port_num) +/* + * Helper function to register partner/plug altmodes. + */ +static int cros_typec_register_altmodes(struct cros_typec_data *typec, int port_num, + bool is_partner) { struct cros_typec_port *port = typec->ports[port_num]; struct ec_response_typec_discovery *sop_disc = port->disc_data; @@ -655,7 +666,11 @@ static int cros_typec_register_altmodes(struct cros_typec_data *typec, int port_ desc.mode = j; desc.vdo = sop_disc->svids[i].mode_vdo[j]; - amode = typec_partner_register_altmode(port->partner, &desc); + if (is_partner) + amode = typec_partner_register_altmode(port->partner, &desc); + else + amode = typec_plug_register_altmode(port->plug, &desc); + if (IS_ERR(amode)) { ret = PTR_ERR(amode); goto err_cleanup; @@ -670,21 +685,30 @@ static int cros_typec_register_altmodes(struct cros_typec_data *typec, int port_ } node->amode = amode; - list_add_tail(&node->list, &port->partner_mode_list); + + if (is_partner) + list_add_tail(&node->list, &port->partner_mode_list); + else + list_add_tail(&node->list, &port->plug_mode_list); num_altmodes++; } } - ret = typec_partner_set_num_altmodes(port->partner, num_altmodes); + if (is_partner) + ret = typec_partner_set_num_altmodes(port->partner, num_altmodes); + else + ret = typec_plug_set_num_altmodes(port->plug, num_altmodes); + if (ret < 0) { - dev_err(typec->dev, "Unable to set partner num_altmodes for port: %d\n", port_num); + dev_err(typec->dev, "Unable to set %s num_altmodes for port: %d\n", + is_partner ? "partner" : "plug", port_num); goto err_cleanup; } return 0; err_cleanup: - cros_typec_unregister_altmodes(typec, port_num); + cros_typec_unregister_altmodes(typec, port_num, is_partner); return ret; } @@ -772,6 +796,12 @@ static int cros_typec_handle_sop_prime_disc(struct cros_typec_data *typec, int p goto sop_prime_disc_exit; } + ret = cros_typec_register_altmodes(typec, port_num, false); + if (ret < 0) { + dev_err(typec->dev, "Failed to register plug altmodes, port: %d\n", port_num); + goto sop_prime_disc_exit; + } + return 0; sop_prime_disc_exit: @@ -813,7 +843,7 @@ static int cros_typec_handle_sop_disc(struct cros_typec_data *typec, int port_nu goto disc_exit; } - ret = cros_typec_register_altmodes(typec, port_num); + ret = cros_typec_register_altmodes(typec, port_num, true); if (ret < 0) { dev_err(typec->dev, "Failed to register partner altmodes, port: %d\n", port_num); goto disc_exit; -- cgit v1.2.3 From ba8ce515454e1fc5e73ff8989c18c596a3449fef Mon Sep 17 00:00:00 2001 From: Utkarsh Patel Date: Wed, 9 Dec 2020 22:09:02 -0800 Subject: platform/chrome: cros_ec_typec: Parameterize cros_typec_cmds_supported() cros_typec_cmds_supported() is currently being used to check only one feature flag. Add a new feature parameter to it so that it can be used to check multiple feature flags supported in cros_ec. Rename cros_typec_cmds_supported() to cros_typec_feature_supported(). Signed-off-by: Utkarsh Patel Reviewed-by: Prashant Malani Signed-off-by: Benson Leung Link: https://lore.kernel.org/r/20201210060903.2205-2-utkarsh.h.patel@intel.com --- drivers/platform/chrome/cros_ec_typec.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index fd446f9029a0..6068433dd2e2 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -974,8 +974,8 @@ static int cros_typec_get_cmd_version(struct cros_typec_data *typec) return 0; } -/* Check the EC feature flags to see if TYPEC_* commands are supported. */ -static int cros_typec_cmds_supported(struct cros_typec_data *typec) +/* Check the EC feature flags to see if TYPEC_* features are supported. */ +static int cros_typec_feature_supported(struct cros_typec_data *typec, enum ec_feature_code feature) { struct ec_response_get_features resp = {}; int ret; @@ -984,11 +984,12 @@ static int cros_typec_cmds_supported(struct cros_typec_data *typec) &resp, sizeof(resp)); if (ret < 0) { dev_warn(typec->dev, - "Failed to get features, assuming typec commands unsupported.\n"); + "Failed to get features, assuming typec feature=%d unsupported.\n", + feature); return 0; } - return resp.flags[EC_FEATURE_TYPEC_CMD / 32] & EC_FEATURE_MASK_1(EC_FEATURE_TYPEC_CMD); + return resp.flags[feature / 32] & EC_FEATURE_MASK_1(feature); } static void cros_typec_port_work(struct work_struct *work) @@ -1050,7 +1051,8 @@ static int cros_typec_probe(struct platform_device *pdev) return ret; } - typec->typec_cmd_supported = !!cros_typec_cmds_supported(typec); + typec->typec_cmd_supported = !!cros_typec_feature_supported(typec, + EC_FEATURE_TYPEC_CMD); ret = cros_typec_ec_command(typec, 0, EC_CMD_USB_PD_PORTS, NULL, 0, &resp, sizeof(resp)); -- cgit v1.2.3 From 8553a979fcd03448a4096c7d431b7ee1a52bfca3 Mon Sep 17 00:00:00 2001 From: Utkarsh Patel Date: Wed, 9 Dec 2020 22:09:03 -0800 Subject: platform/chrome: cros_ec_typec: Send mux configuration acknowledgment to EC In some corner cases downgrade of the superspeed typec device(e.g. Dell typec Dock, apple dongle) was seen because before the SOC mux configuration finishes, EC starts configuring the next mux state. With this change, once the SOC mux is configured, kernel will send an acknowledgment to EC via Host command EC_CMD_USB_PD_MUX_ACK [1]. After sending the host event EC will wait for the acknowledgment from kernel before starting the PD negotiation for the next mux state. This helps to have a framework to build better error handling along with the synchronization of timing sensitive mux states. This change also brings in corresponding EC header updates from the EC code base [1]. [1]: https://chromium.googlesource.com/chromiumos/platform/ec/+/refs/heads/master/include/ec_commands.h Signed-off-by: Utkarsh Patel Reviewed-by: Prashant Malani Signed-off-by: Benson Leung Link: https://lore.kernel.org/r/20201210060903.2205-3-utkarsh.h.patel@intel.com --- drivers/platform/chrome/cros_ec_typec.c | 16 ++++++++++++++++ include/linux/platform_data/cros_ec_commands.h | 17 +++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 6068433dd2e2..e724a5eaef1c 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -81,6 +81,7 @@ struct cros_typec_data { struct notifier_block nb; struct work_struct port_work; bool typec_cmd_supported; + bool needs_mux_ack; }; static int cros_typec_parse_port_props(struct typec_capability *cap, @@ -531,6 +532,7 @@ static int cros_typec_configure_mux(struct cros_typec_data *typec, int port_num, struct ec_response_usb_pd_control_v2 *pd_ctrl) { struct cros_typec_port *port = typec->ports[port_num]; + struct ec_params_usb_pd_mux_ack mux_ack; enum typec_orientation orientation; int ret; @@ -570,6 +572,18 @@ static int cros_typec_configure_mux(struct cros_typec_data *typec, int port_num, mux_flags); } + if (!typec->needs_mux_ack) + return ret; + + /* Sending Acknowledgment to EC */ + mux_ack.port = port_num; + + if (cros_typec_ec_command(typec, 0, EC_CMD_USB_PD_MUX_ACK, &mux_ack, + sizeof(mux_ack), NULL, 0) < 0) + dev_warn(typec->dev, + "Failed to send Mux ACK to EC for port: %d\n", + port_num); + return ret; } @@ -1053,6 +1067,8 @@ static int cros_typec_probe(struct platform_device *pdev) typec->typec_cmd_supported = !!cros_typec_feature_supported(typec, EC_FEATURE_TYPEC_CMD); + typec->needs_mux_ack = !!cros_typec_feature_supported(typec, + EC_FEATURE_TYPEC_MUX_REQUIRE_AP_ACK); ret = cros_typec_ec_command(typec, 0, EC_CMD_USB_PD_PORTS, NULL, 0, &resp, sizeof(resp)); diff --git a/include/linux/platform_data/cros_ec_commands.h b/include/linux/platform_data/cros_ec_commands.h index 86376779ab31..9787715540c7 100644 --- a/include/linux/platform_data/cros_ec_commands.h +++ b/include/linux/platform_data/cros_ec_commands.h @@ -1286,6 +1286,16 @@ enum ec_feature_code { EC_FEATURE_ISH = 40, /* New TCPMv2 TYPEC_ prefaced commands supported */ EC_FEATURE_TYPEC_CMD = 41, + /* + * The EC will wait for direction from the AP to enter Type-C alternate + * modes or USB4. + */ + EC_FEATURE_TYPEC_REQUIRE_AP_MODE_ENTRY = 42, + /* + * The EC will wait for an acknowledge from the AP after setting the + * mux. + */ + EC_FEATURE_TYPEC_MUX_REQUIRE_AP_ACK = 43, }; #define EC_FEATURE_MASK_0(event_code) BIT(event_code % 32) @@ -6054,6 +6064,13 @@ struct ec_params_charger_control { uint8_t allow_charging; } __ec_align_size1; +/* Get ACK from the USB-C SS muxes */ +#define EC_CMD_USB_PD_MUX_ACK 0x0603 + +struct ec_params_usb_pd_mux_ack { + uint8_t port; /* USB-C port number */ +} __ec_align1; + /*****************************************************************************/ /* * Reserve a range of host commands for board-specific, experimental, or -- cgit v1.2.3 From 4d7aae9f7a18f27ade0fc1d275f272f23529d6ba Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Tue, 29 Dec 2020 15:03:31 -0800 Subject: usb: gadget: configfs: Add a specific configFS reset callback In order for configFS based USB gadgets to set the proper charge current for bus reset scenarios, expose a separate reset callback to set the current to 100mA based on the USB battery charging specification. Reviewed-by: Peter Chen Signed-off-by: Wesley Cheng Link: https://lore.kernel.org/r/1609283011-21997-4-git-send-email-wcheng@codeaurora.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/configfs.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index 56051bb97349..80ca7ff2fb97 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -1481,6 +1481,28 @@ static void configfs_composite_disconnect(struct usb_gadget *gadget) spin_unlock_irqrestore(&gi->spinlock, flags); } +static void configfs_composite_reset(struct usb_gadget *gadget) +{ + struct usb_composite_dev *cdev; + struct gadget_info *gi; + unsigned long flags; + + cdev = get_gadget_data(gadget); + if (!cdev) + return; + + gi = container_of(cdev, struct gadget_info, cdev); + spin_lock_irqsave(&gi->spinlock, flags); + cdev = get_gadget_data(gadget); + if (!cdev || gi->unbind) { + spin_unlock_irqrestore(&gi->spinlock, flags); + return; + } + + composite_reset(gadget); + spin_unlock_irqrestore(&gi->spinlock, flags); +} + static void configfs_composite_suspend(struct usb_gadget *gadget) { struct usb_composite_dev *cdev; @@ -1530,7 +1552,7 @@ static const struct usb_gadget_driver configfs_driver_template = { .unbind = configfs_composite_unbind, .setup = configfs_composite_setup, - .reset = configfs_composite_disconnect, + .reset = configfs_composite_reset, .disconnect = configfs_composite_disconnect, .suspend = configfs_composite_suspend, -- cgit v1.2.3 From 841081d89d5adf96759f2df50185de950f5d8694 Mon Sep 17 00:00:00 2001 From: Zheng Yongjun Date: Wed, 23 Dec 2020 22:14:31 +0800 Subject: usb: usbip: Use DEFINE_SPINLOCK() for spinlock spinlock can be initialized automatically with DEFINE_SPINLOCK() rather than explicitly calling spin_lock_init(). Signed-off-by: Zheng Yongjun Link: https://lore.kernel.org/r/20201223141431.835-1-zhengyongjun3@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/stub_main.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/usb/usbip/stub_main.c b/drivers/usb/usbip/stub_main.c index c1c0bbc9f8b1..77a5b3f8736a 100644 --- a/drivers/usb/usbip/stub_main.c +++ b/drivers/usb/usbip/stub_main.c @@ -23,7 +23,7 @@ struct kmem_cache *stub_priv_cache; */ #define MAX_BUSID 16 static struct bus_id_priv busid_table[MAX_BUSID]; -static spinlock_t busid_table_lock; +static DEFINE_SPINLOCK(busid_table_lock); static void init_busid_table(void) { @@ -35,8 +35,6 @@ static void init_busid_table(void) */ memset(busid_table, 0, sizeof(busid_table)); - spin_lock_init(&busid_table_lock); - for (i = 0; i < MAX_BUSID; i++) spin_lock_init(&busid_table[i].busid_lock); } -- cgit v1.2.3 From 6889e00f0e138beeb775e38decf59959b079ae6f Mon Sep 17 00:00:00 2001 From: Rikard Falkeborn Date: Sat, 9 Jan 2021 00:09:19 +0100 Subject: thunderbolt: Constify static attribute_group structs The only usage of these is to put their addresses in arrays of pointers to const attribute_groups. Make them const to allow the compiler to put them in read-only memory. Signed-off-by: Rikard Falkeborn Signed-off-by: Mika Westerberg --- drivers/thunderbolt/domain.c | 2 +- drivers/thunderbolt/switch.c | 2 +- drivers/thunderbolt/xdomain.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index f0de94f7acbf..d2b92a8be577 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -289,7 +289,7 @@ static umode_t domain_attr_is_visible(struct kobject *kobj, return attr->mode; } -static struct attribute_group domain_attr_group = { +static const struct attribute_group domain_attr_group = { .is_visible = domain_attr_is_visible, .attrs = domain_attrs, }; diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index a8572f49d3ad..602e1835bf61 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -1771,7 +1771,7 @@ static umode_t switch_attr_is_visible(struct kobject *kobj, return sw->safe_mode ? 0 : attr->mode; } -static struct attribute_group switch_group = { +static const struct attribute_group switch_group = { .is_visible = switch_attr_is_visible, .attrs = switch_attrs, }; diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index 9b3a299a1202..f2d4db1cd84d 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -756,7 +756,7 @@ static struct attribute *tb_service_attrs[] = { NULL, }; -static struct attribute_group tb_service_attr_group = { +static const struct attribute_group tb_service_attr_group = { .attrs = tb_service_attrs, }; @@ -1239,7 +1239,7 @@ static struct attribute *xdomain_attrs[] = { NULL, }; -static struct attribute_group xdomain_attr_group = { +static const struct attribute_group xdomain_attr_group = { .attrs = xdomain_attrs, }; -- cgit v1.2.3 From fdb0887c5a87c3a98958d3c5c90f871aa6d1a562 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 26 Nov 2020 12:52:43 +0300 Subject: thunderbolt: Start lane initialization after sleep USB4 spec says that for TBT3 compatible device routers the connection manager needs to set SLI (Start Lane Initialization) to get the lanes that were not connected back to functional state after sleep. Same needs to be done if the link was XDomain. Signed-off-by: Mika Westerberg Acked-by: Yehezkel Bernat --- drivers/thunderbolt/lc.c | 35 +++++++++++++++++++++++++++++++++++ drivers/thunderbolt/switch.c | 27 ++++++++++++++++++++++++++- drivers/thunderbolt/tb.h | 1 + drivers/thunderbolt/tb_regs.h | 1 + 4 files changed, 63 insertions(+), 1 deletion(-) diff --git a/drivers/thunderbolt/lc.c b/drivers/thunderbolt/lc.c index 41e6c738f6c8..bc671730a11f 100644 --- a/drivers/thunderbolt/lc.c +++ b/drivers/thunderbolt/lc.c @@ -158,6 +158,41 @@ void tb_lc_unconfigure_xdomain(struct tb_port *port) tb_lc_set_xdomain_configured(port, false); } +/** + * tb_lc_start_lane_initialization() - Start lane initialization + * @port: Device router lane 0 adapter + * + * Starts lane initialization for @port after the router resumed from + * sleep. Should be called for those downstream lane adapters that were + * not connected (tb_lc_configure_port() was not called) before sleep. + * + * Returns %0 in success and negative errno in case of failure. + */ +int tb_lc_start_lane_initialization(struct tb_port *port) +{ + struct tb_switch *sw = port->sw; + int ret, cap; + u32 ctrl; + + if (!tb_route(sw)) + return 0; + + if (sw->generation < 2) + return 0; + + cap = find_port_lc_cap(port); + if (cap < 0) + return cap; + + ret = tb_sw_read(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1); + if (ret) + return ret; + + ctrl |= TB_LC_SX_CTRL_SLI; + + return tb_sw_write(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1); +} + static int tb_lc_set_wake_one(struct tb_switch *sw, unsigned int offset, unsigned int flags) { diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 602e1835bf61..ad992e6204d9 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -1065,6 +1065,17 @@ void tb_port_lane_bonding_disable(struct tb_port *port) tb_port_set_link_width(port, 1); } +static int tb_port_start_lane_initialization(struct tb_port *port) +{ + int ret; + + if (tb_switch_is_usb4(port->sw)) + return 0; + + ret = tb_lc_start_lane_initialization(port); + return ret == -EINVAL ? 0 : ret; +} + /** * tb_port_is_enabled() - Is the adapter port enabled * @port: Port to check @@ -2694,8 +2705,22 @@ int tb_switch_resume(struct tb_switch *sw) /* check for surviving downstream switches */ tb_switch_for_each_port(sw, port) { - if (!tb_port_has_remote(port) && !port->xdomain) + if (!tb_port_has_remote(port) && !port->xdomain) { + /* + * For disconnected downstream lane adapters + * start lane initialization now so we detect + * future connects. + */ + if (!tb_is_upstream_port(port) && tb_port_is_null(port)) + tb_port_start_lane_initialization(port); continue; + } else if (port->xdomain) { + /* + * Start lane initialization for XDomain so the + * link gets re-established. + */ + tb_port_start_lane_initialization(port); + } if (tb_wait_for_port(port, true) <= 0) { tb_port_warn(port, diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 554feda1e359..34ae83b9e52a 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -923,6 +923,7 @@ int tb_lc_configure_port(struct tb_port *port); void tb_lc_unconfigure_port(struct tb_port *port); int tb_lc_configure_xdomain(struct tb_port *port); void tb_lc_unconfigure_xdomain(struct tb_port *port); +int tb_lc_start_lane_initialization(struct tb_port *port); int tb_lc_set_wake(struct tb_switch *sw, unsigned int flags); int tb_lc_set_sleep(struct tb_switch *sw); bool tb_lc_lane_bonding_possible(struct tb_switch *sw); diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h index ae427a953489..626751e06292 100644 --- a/drivers/thunderbolt/tb_regs.h +++ b/drivers/thunderbolt/tb_regs.h @@ -464,6 +464,7 @@ struct tb_regs_hop { #define TB_LC_SX_CTRL_L1D BIT(17) #define TB_LC_SX_CTRL_L2C BIT(20) #define TB_LC_SX_CTRL_L2D BIT(21) +#define TB_LC_SX_CTRL_SLI BIT(29) #define TB_LC_SX_CTRL_UPSTREAM BIT(30) #define TB_LC_SX_CTRL_SLP BIT(31) -- cgit v1.2.3 From 161a582bd1d8681095f158d11bc679a58f1d026b Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Mon, 11 Jan 2021 14:09:04 -0800 Subject: USB: serial: mos7720: improve OOM-handling in read_mos_reg() clang static analysis reports this problem mos7720.c:352:2: warning: Undefined or garbage value returned to caller return d; ^~~~~~~~ In the parport_mos7715_read_data()'s call to read_mos_reg(), 'd' is only set after the alloc block. buf = kmalloc(1, GFP_KERNEL); if (!buf) return -ENOMEM; Although the problem is reported in parport_most7715_read_data(), none of the callee's of read_mos_reg() check the return status. Make sure to clear the return-value buffer also on allocation failures. Fixes: 0d130367abf5 ("USB: serial: mos7720: fix control-message error handling") Signed-off-by: Tom Rix Link: https://lore.kernel.org/r/20210111220904.1035957-1-trix@redhat.com [ johan: only clear the buffer on errors, amend commit message ] Signed-off-by: Johan Hovold --- drivers/usb/serial/mos7720.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/serial/mos7720.c b/drivers/usb/serial/mos7720.c index 41ee2984a0df..ed347a6d50ba 100644 --- a/drivers/usb/serial/mos7720.c +++ b/drivers/usb/serial/mos7720.c @@ -215,8 +215,10 @@ static int read_mos_reg(struct usb_serial *serial, unsigned int serial_portnum, int status; buf = kmalloc(1, GFP_KERNEL); - if (!buf) + if (!buf) { + *data = 0; return -ENOMEM; + } status = usb_control_msg(usbdev, pipe, request, requesttype, value, index, buf, 1, MOS_WDR_TIMEOUT); -- cgit v1.2.3 From aa62401644b32f5229183c375bcc03e02a0888ac Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 10 Dec 2020 12:09:25 +0300 Subject: dt-bindings: usb: usb-hcd: Detach generic USB controller properties There can be three distinctive types of the USB controllers: USB hosts, USB peripherals/gadgets and USB OTG, which can switch from one role to another. In order to have that hierarchy handled in the DT binding files, we need to collect common properties in a common DT schema and specific properties in dedicated schemas. Seeing the usb-hcd.yaml DT schema is dedicated for the USB host controllers only, let's move some common properties from there into the usb.yaml schema. So the later would be available to evaluate all currently supported types of the USB controllers. While at it add an explicit "additionalProperties: true" into the usb-hcd.yaml as setting the additionalProperties/unevaluateProperties properties is going to be get mandatory soon. Reviewed-by: Rob Herring Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20201210090944.16283-2-Sergey.Semin@baikalelectronics.ru Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usb-hcd.yaml | 14 ++--------- Documentation/devicetree/bindings/usb/usb.yaml | 29 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 12 deletions(-) create mode 100644 Documentation/devicetree/bindings/usb/usb.yaml diff --git a/Documentation/devicetree/bindings/usb/usb-hcd.yaml b/Documentation/devicetree/bindings/usb/usb-hcd.yaml index b545b087b342..81f3ad1419d8 100644 --- a/Documentation/devicetree/bindings/usb/usb-hcd.yaml +++ b/Documentation/devicetree/bindings/usb/usb-hcd.yaml @@ -9,18 +9,8 @@ title: Generic USB Host Controller Device Tree Bindings maintainers: - Greg Kroah-Hartman -properties: - $nodename: - pattern: "^usb(@.*)?" - - phys: - $ref: /schemas/types.yaml#/definitions/phandle-array - description: - List of all the USB PHYs on this HCD - - phy-names: - description: - Name specifier for the USB PHY +allOf: + - $ref: usb.yaml# additionalProperties: true diff --git a/Documentation/devicetree/bindings/usb/usb.yaml b/Documentation/devicetree/bindings/usb/usb.yaml new file mode 100644 index 000000000000..941ad59fbac5 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/usb.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/usb.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Generic USB Controller Device Tree Bindings + +maintainers: + - Greg Kroah-Hartman + +select: false + +properties: + $nodename: + pattern: "^usb(@.*)?" + + phys: + $ref: /schemas/types.yaml#/definitions/phandle-array + description: + List of all the USB PHYs on this HCD + + phy-names: + description: + Name specifier for the USB PHY + +additionalProperties: true + +... -- cgit v1.2.3 From b0864e1a4d9d2f030cf143f324f52f77d7bc68ab Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 10 Dec 2020 12:09:26 +0300 Subject: dt-bindings: usb: Convert generic USB properties to DT schemas The generic USB properties have been described in the legacy bindings text file: Documentation/devicetree/bindings/usb/generic.txt . Let's convert its content into the generic USB, USB HCD and USB DRD DT schemas. So the Generic USB schema will be applicable to all USB controllers, USB HCD - for the generic USB Host controllers and the USB DRD - for the USB Dual-role controllers. Note the USB DRD schema is supposed to work in conjunction with the USB peripheral/gadget and USB host controllers DT schemas. Reviewed-by: Rob Herring Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20201210090944.16283-3-Sergey.Semin@baikalelectronics.ru Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/generic.txt | 57 ---------------- Documentation/devicetree/bindings/usb/usb-drd.yaml | 77 ++++++++++++++++++++++ Documentation/devicetree/bindings/usb/usb-hcd.yaml | 5 ++ Documentation/devicetree/bindings/usb/usb.yaml | 22 +++++++ 4 files changed, 104 insertions(+), 57 deletions(-) delete mode 100644 Documentation/devicetree/bindings/usb/generic.txt create mode 100644 Documentation/devicetree/bindings/usb/usb-drd.yaml diff --git a/Documentation/devicetree/bindings/usb/generic.txt b/Documentation/devicetree/bindings/usb/generic.txt deleted file mode 100644 index ba472e7aefc9..000000000000 --- a/Documentation/devicetree/bindings/usb/generic.txt +++ /dev/null @@ -1,57 +0,0 @@ -Generic USB Properties - -Optional properties: - - maximum-speed: tells USB controllers we want to work up to a certain - speed. Valid arguments are "super-speed-plus", - "super-speed", "high-speed", "full-speed" and - "low-speed". In case this isn't passed via DT, USB - controllers should default to their maximum HW - capability. - - dr_mode: tells Dual-Role USB controllers that we want to work on a - particular mode. Valid arguments are "host", - "peripheral" and "otg". In case this attribute isn't - passed via DT, USB DRD controllers should default to - OTG. - - phy_type: tells USB controllers that we want to configure the core to support - a UTMI+ PHY with an 8- or 16-bit interface if UTMI+ is - selected. Valid arguments are "utmi" and "utmi_wide". - In case this isn't passed via DT, USB controllers should - default to HW capability. - - otg-rev: tells usb driver the release number of the OTG and EH supplement - with which the device and its descriptors are compliant, - in binary-coded decimal (i.e. 2.0 is 0200H). This - property is used if any real OTG features(HNP/SRP/ADP) - is enabled, if ADP is required, otg-rev should be - 0x0200 or above. - - companion: phandle of a companion - - hnp-disable: tells OTG controllers we want to disable OTG HNP, normally HNP - is the basic function of real OTG except you want it - to be a srp-capable only B device. - - srp-disable: tells OTG controllers we want to disable OTG SRP, SRP is - optional for OTG device. - - adp-disable: tells OTG controllers we want to disable OTG ADP, ADP is - optional for OTG device. - - usb-role-switch: boolean, indicates that the device is capable of assigning - the USB data role (USB host or USB device) for a given - USB connector, such as Type-C, Type-B(micro). - see connector/usb-connector.yaml. - - role-switch-default-mode: indicating if usb-role-switch is enabled, the - device default operation mode of controller while usb - role is USB_ROLE_NONE. Valid arguments are "host" and - "peripheral". Defaults to "peripheral" if not - specified. - - -This is an attribute to a USB controller such as: - -dwc3@4a030000 { - compatible = "synopsys,dwc3"; - reg = <0x4a030000 0xcfff>; - interrupts = <0 92 4> - usb-phy = <&usb2_phy>, <&usb3,phy>; - maximum-speed = "super-speed"; - dr_mode = "otg"; - phy_type = "utmi_wide"; - otg-rev = <0x0200>; - adp-disable; -}; diff --git a/Documentation/devicetree/bindings/usb/usb-drd.yaml b/Documentation/devicetree/bindings/usb/usb-drd.yaml new file mode 100644 index 000000000000..f3a64c46dcd0 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/usb-drd.yaml @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/usb-drd.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Generic USB OTG Controller Device Tree Bindings + +maintainers: + - Greg Kroah-Hartman + +properties: + otg-rev: + description: + Tells usb driver the release number of the OTG and EH supplement with + which the device and its descriptors are compliant, in binary-coded + decimal (i.e. 2.0 is 0200H). This property is used if any real OTG + features (HNP/SRP/ADP) is enabled. If ADP is required, otg-rev should be + 0x0200 or above. + $ref: /schemas/types.yaml#/definitions/uint32 + + dr_mode: + description: + Tells Dual-Role USB controllers that we want to work on a particular + mode. In case this attribute isn't passed via DT, USB DRD controllers + should default to OTG. + $ref: /schemas/types.yaml#/definitions/string + enum: [host, peripheral, otg] + + hnp-disable: + description: + Tells OTG controllers we want to disable OTG HNP. Normally HNP is the + basic function of real OTG except you want it to be a srp-capable only B + device. + type: boolean + + srp-disable: + description: + Tells OTG controllers we want to disable OTG SRP. SRP is optional for OTG + device. + type: boolean + + adp-disable: + description: + Tells OTG controllers we want to disable OTG ADP. ADP is optional for OTG + device. + type: boolean + + usb-role-switch: + description: + Indicates that the device is capable of assigning the USB data role + (USB host or USB device) for a given USB connector, such as Type-C, + Type-B(micro). See connector/usb-connector.yaml. + + role-switch-default-mode: + description: + Indicates if usb-role-switch is enabled, the device default operation + mode of controller while usb role is USB_ROLE_NONE. + $ref: /schemas/types.yaml#/definitions/string + enum: [host, peripheral] + default: peripheral + +additionalProperties: true + +examples: + - | + usb@4a030000 { + compatible = "snps,dwc3"; + reg = <0x4a030000 0xcfff>; + interrupts = <0 92 4>; + usb-phy = <&usb2_phy>, <&usb3_phy>; + maximum-speed = "super-speed"; + dr_mode = "otg"; + phy_type = "utmi_wide"; + otg-rev = <0x0200>; + adp-disable; + }; diff --git a/Documentation/devicetree/bindings/usb/usb-hcd.yaml b/Documentation/devicetree/bindings/usb/usb-hcd.yaml index 81f3ad1419d8..52cc84c400c0 100644 --- a/Documentation/devicetree/bindings/usb/usb-hcd.yaml +++ b/Documentation/devicetree/bindings/usb/usb-hcd.yaml @@ -12,6 +12,11 @@ maintainers: allOf: - $ref: usb.yaml# +properties: + companion: + description: Phandle of a companion device + $ref: /schemas/types.yaml#/definitions/phandle + additionalProperties: true examples: diff --git a/Documentation/devicetree/bindings/usb/usb.yaml b/Documentation/devicetree/bindings/usb/usb.yaml index 941ad59fbac5..aab74c671ccc 100644 --- a/Documentation/devicetree/bindings/usb/usb.yaml +++ b/Documentation/devicetree/bindings/usb/usb.yaml @@ -24,6 +24,28 @@ properties: description: Name specifier for the USB PHY + phy_type: + description: + Tells USB controllers that we want to configure the core to support a + UTMI+ PHY with an 8- or 16-bit interface if UTMI+ is selected. In case + this isn't passed via DT, USB controllers should default to HW + capability. + $ref: /schemas/types.yaml#/definitions/string + enum: [utmi, utmi_wide] + + maximum-speed: + description: + Tells USB controllers we want to work up to a certain speed. In case this + isn't passed via DT, USB controllers should default to their maximum HW + capability. + $ref: /schemas/types.yaml#/definitions/string + enum: + - low-speed + - full-speed + - high-speed + - super-speed + - super-speed-plus + additionalProperties: true ... -- cgit v1.2.3 From 5c67b97ecfa72b75bf5b562ce6448b18c309bf56 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 10 Dec 2020 12:09:27 +0300 Subject: dt-bindings: usb: usb-drd: Add "otg-rev" property constraints There are only four OTG revisions are currently supported by the kernel: 0x0100, 0x0120, 0x0130, 0x0200. Any another value is considered as invalid. Reviewed-by: Rob Herring Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20201210090944.16283-4-Sergey.Semin@baikalelectronics.ru Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usb-drd.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/usb/usb-drd.yaml b/Documentation/devicetree/bindings/usb/usb-drd.yaml index f3a64c46dcd0..f229fc8068d9 100644 --- a/Documentation/devicetree/bindings/usb/usb-drd.yaml +++ b/Documentation/devicetree/bindings/usb/usb-drd.yaml @@ -18,6 +18,7 @@ properties: features (HNP/SRP/ADP) is enabled. If ADP is required, otg-rev should be 0x0200 or above. $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0x0100, 0x0120, 0x0130, 0x0200] dr_mode: description: -- cgit v1.2.3 From 99581ba863f914bec762f679bd2c2c9031a2ba2a Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 10 Dec 2020 12:09:28 +0300 Subject: dt-bindings: usb: Add "ulpi/serial/hsic" PHY types Aside from the UTMI+ there are also ULPI, Serial and HSIC PHY types that can be specified in the phy_type HCD property. Add them to the enumeration of the acceptable values. Reviewed-by: Rob Herring Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20201210090944.16283-5-Sergey.Semin@baikalelectronics.ru Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usb.yaml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/usb.yaml b/Documentation/devicetree/bindings/usb/usb.yaml index aab74c671ccc..53144c4600c0 100644 --- a/Documentation/devicetree/bindings/usb/usb.yaml +++ b/Documentation/devicetree/bindings/usb/usb.yaml @@ -27,11 +27,13 @@ properties: phy_type: description: Tells USB controllers that we want to configure the core to support a - UTMI+ PHY with an 8- or 16-bit interface if UTMI+ is selected. In case - this isn't passed via DT, USB controllers should default to HW - capability. + UTMI+ PHY with an 8- or 16-bit interface if UTMI+ is selected, UTMI+ low + pin interface if ULPI is specified, Serial core/PHY interconnect if + serial is specified and High-Speed Inter-Chip feature if HSIC is + selected. In case this isn't passed via DT, USB controllers should + default to HW capability. $ref: /schemas/types.yaml#/definitions/string - enum: [utmi, utmi_wide] + enum: [utmi, utmi_wide, ulpi, serial, hsic] maximum-speed: description: -- cgit v1.2.3 From e692cc354415d6ad8dc6d05be8c2e6435b281066 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 10 Dec 2020 12:09:29 +0300 Subject: dt-bindings: usb: usb-hcd: Add "tpl-support" property The host controller device might be designed to work for the particular products or applications. In that case its DT node is supposed to be equipped with the tpl-support property. Reviewed-by: Rob Herring Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20201210090944.16283-6-Sergey.Semin@baikalelectronics.ru Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usb-hcd.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/usb-hcd.yaml b/Documentation/devicetree/bindings/usb/usb-hcd.yaml index 52cc84c400c0..9881ac10380d 100644 --- a/Documentation/devicetree/bindings/usb/usb-hcd.yaml +++ b/Documentation/devicetree/bindings/usb/usb-hcd.yaml @@ -17,6 +17,12 @@ properties: description: Phandle of a companion device $ref: /schemas/types.yaml#/definitions/phandle + tpl-support: + description: + Indicates if the Targeted Peripheral List is supported for given + targeted hosts (non-PC hosts). + type: boolean + additionalProperties: true examples: -- cgit v1.2.3 From c26835071c18b9285eec1e8c6f4b36935d0d9fcf Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 10 Dec 2020 12:09:30 +0300 Subject: dt-bindings: usb: Add generic "usb-phy" property Even though the Generic PHY framework is the more preferable way of setting the USB PHY up, there are still many dts-files and DT bindings which rely on having the legacy "usb-phy" specified to attach particular USB PHYs to USB cores. Let's have the "usb-phy" property described in the generic USB HCD binding file so it would be validated against the nodes in which it's specified. Mark the property as deprecated to discourage the developers from using it. Reviewed-by: Rob Herring Acked-by: Martin Blumenstingl Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20201210090944.16283-7-Sergey.Semin@baikalelectronics.ru Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usb.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/usb.yaml b/Documentation/devicetree/bindings/usb/usb.yaml index 53144c4600c0..ebe7f4275c59 100644 --- a/Documentation/devicetree/bindings/usb/usb.yaml +++ b/Documentation/devicetree/bindings/usb/usb.yaml @@ -24,6 +24,13 @@ properties: description: Name specifier for the USB PHY + usb-phy: + $ref: /schemas/types.yaml#/definitions/phandle-array + description: + List of all the USB PHYs on this HCD to be accepted by the legacy USB + Physical Layer subsystem. + deprecated: true + phy_type: description: Tells USB controllers that we want to configure the core to support a -- cgit v1.2.3 From 17c01b82819e38cbd8a7720ae2b378a11b4e9699 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 10 Dec 2020 12:09:31 +0300 Subject: dt-bindings: usb: Convert xHCI bindings to DT schema Currently the DT bindings of Generic xHCI Controllers are described by means of the legacy text file. Since such format is deprecated in favor of the DT schema, let's convert the Generic xHCI Controllers bindings file to the corresponding yaml files. There will be two of them: a DT schema for the xHCI controllers on a generic platform and a DT schema validating a generic xHCI controllers properties. The later will be used to validate the xHCI controllers, which aside from some vendor-specific features support the basic xHCI functionality. An xHCI-compatible DT node shall support the standard USB HCD properties and custom ones like: usb2-lpm-disable, usb3-lpm-capable, quirk-broken-port-ped and imod-interval-ns. In addition if a generic xHCI controller is being validated against the DT schema it is also supposed to be equipped with mandatory compatible string, single registers range, single interrupts source, and is supposed to optionally contain up to two reference clocks for the controller core and CSRs. Reviewed-by: Rob Herring Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20201210090944.16283-8-Sergey.Semin@baikalelectronics.ru Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/generic-xhci.yaml | 63 ++++++++++++++++++++++ Documentation/devicetree/bindings/usb/usb-xhci.txt | 41 -------------- .../devicetree/bindings/usb/usb-xhci.yaml | 42 +++++++++++++++ 3 files changed, 105 insertions(+), 41 deletions(-) create mode 100644 Documentation/devicetree/bindings/usb/generic-xhci.yaml delete mode 100644 Documentation/devicetree/bindings/usb/usb-xhci.txt create mode 100644 Documentation/devicetree/bindings/usb/usb-xhci.yaml diff --git a/Documentation/devicetree/bindings/usb/generic-xhci.yaml b/Documentation/devicetree/bindings/usb/generic-xhci.yaml new file mode 100644 index 000000000000..1ea1d49a8175 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/generic-xhci.yaml @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/generic-xhci.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: USB xHCI Controller Device Tree Bindings + +maintainers: + - Mathias Nyman + +allOf: + - $ref: "usb-xhci.yaml#" + +properties: + compatible: + oneOf: + - description: Generic xHCI device + const: generic-xhci + - description: Armada 37xx/375/38x/8k SoCs + items: + - enum: + - marvell,armada3700-xhci + - marvell,armada-375-xhci + - marvell,armada-380-xhci + - marvell,armada-8k-xhci + - const: generic-xhci + - description: Broadcom STB SoCs with xHCI + const: brcm,bcm7445-xhci + - description: Generic xHCI device + const: xhci-platform + deprecated: true + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + minItems: 1 + maxItems: 2 + + clock-names: + minItems: 1 + items: + - const: core + - const: reg + +unevaluatedProperties: false + +required: + - compatible + - reg + - interrupts + +examples: + - | + usb@f0931000 { + compatible = "generic-xhci"; + reg = <0xf0931000 0x8c8>; + interrupts = <0x0 0x4e 0x0>; + }; diff --git a/Documentation/devicetree/bindings/usb/usb-xhci.txt b/Documentation/devicetree/bindings/usb/usb-xhci.txt deleted file mode 100644 index 0c5cff84a969..000000000000 --- a/Documentation/devicetree/bindings/usb/usb-xhci.txt +++ /dev/null @@ -1,41 +0,0 @@ -USB xHCI controllers - -Required properties: - - compatible: should be one or more of - - - "generic-xhci" for generic XHCI device - - "marvell,armada3700-xhci" for Armada 37xx SoCs - - "marvell,armada-375-xhci" for Armada 375 SoCs - - "marvell,armada-380-xhci" for Armada 38x SoCs - - "brcm,bcm7445-xhci" for Broadcom STB SoCs with XHCI - - "xhci-platform" (deprecated) - - When compatible with the generic version, nodes must list the - SoC-specific version corresponding to the platform first - followed by the generic version. - - - reg: should contain address and length of the standard XHCI - register set for the device. - - interrupts: one XHCI interrupt should be described here. - -Optional properties: - - clocks: reference to the clocks - - clock-names: mandatory if there is a second clock, in this case - the name must be "core" for the first clock and "reg" for the - second one - - usb2-lpm-disable: indicate if we don't want to enable USB2 HW LPM - - usb3-lpm-capable: determines if platform is USB3 LPM capable - - quirk-broken-port-ped: set if the controller has broken port disable mechanism - - imod-interval-ns: default interrupt moderation interval is 5000ns - - phys : see usb-hcd.yaml in the current directory - -additionally the properties from usb-hcd.yaml (in the current directory) are -supported. - - -Example: - usb@f0931000 { - compatible = "generic-xhci"; - reg = <0xf0931000 0x8c8>; - interrupts = <0x0 0x4e 0x0>; - }; diff --git a/Documentation/devicetree/bindings/usb/usb-xhci.yaml b/Documentation/devicetree/bindings/usb/usb-xhci.yaml new file mode 100644 index 000000000000..965f87fef702 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/usb-xhci.yaml @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/usb-xhci.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Generic USB xHCI Controller Device Tree Bindings + +maintainers: + - Mathias Nyman + +allOf: + - $ref: "usb-hcd.yaml#" + +properties: + usb2-lpm-disable: + description: Indicates if we don't want to enable USB2 HW LPM + type: boolean + + usb3-lpm-capable: + description: Determines if platform is USB3 LPM capable + type: boolean + + quirk-broken-port-ped: + description: Set if the controller has broken port disable mechanism + type: boolean + + imod-interval-ns: + description: Interrupt moderation interval + default: 5000 + +additionalProperties: true + +examples: + - | + usb@f0930000 { + compatible = "generic-xhci"; + reg = <0xf0930000 0x8c8>; + interrupts = <0x0 0x4e 0x0>; + usb2-lpm-disable; + usb3-lpm-capable; + }; -- cgit v1.2.3 From 5b7e1bfd882ff6c0440eb5b25153253d5db1c552 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 10 Dec 2020 12:09:32 +0300 Subject: dt-bindings: usb: xhci: Add Broadcom STB v2 compatible device For some reason the "brcm,xhci-brcm-v2" compatible string has been missing in the original bindings file. Add it to the Generic xHCI Controllers DT schema since the controller driver expects it to be supported. Reviewed-by: Rob Herring Acked-by: Florian Fainelli Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20201210090944.16283-9-Sergey.Semin@baikalelectronics.ru Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/generic-xhci.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/generic-xhci.yaml b/Documentation/devicetree/bindings/usb/generic-xhci.yaml index 1ea1d49a8175..23d73df96ea3 100644 --- a/Documentation/devicetree/bindings/usb/generic-xhci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-xhci.yaml @@ -26,7 +26,9 @@ properties: - marvell,armada-8k-xhci - const: generic-xhci - description: Broadcom STB SoCs with xHCI - const: brcm,bcm7445-xhci + enum: + - brcm,xhci-brcm-v2 + - brcm,bcm7445-xhci - description: Generic xHCI device const: xhci-platform deprecated: true -- cgit v1.2.3 From 55e945593b0af8220af90761d511a4844c463983 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 10 Dec 2020 12:09:33 +0300 Subject: dt-bindings: usb: renesas-xhci: Refer to the usb-xhci.yaml file With minor peculiarities (like uploading some vendor-specific firmware) these are just Generic xHCI controllers fully compatible with its properties. Make sure the Renesas USB xHCI DT nodes are also validated against the Generic xHCI DT schema. Reviewed-by: Rob Herring Reviewed-by: Yoshihiro Shimoda Reviewed-by: Lad Prabhakar Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20201210090944.16283-10-Sergey.Semin@baikalelectronics.ru Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/renesas,usb-xhci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/renesas,usb-xhci.yaml b/Documentation/devicetree/bindings/usb/renesas,usb-xhci.yaml index 22603256ddf8..4c5efaf02308 100644 --- a/Documentation/devicetree/bindings/usb/renesas,usb-xhci.yaml +++ b/Documentation/devicetree/bindings/usb/renesas,usb-xhci.yaml @@ -11,7 +11,7 @@ maintainers: - Yoshihiro Shimoda allOf: - - $ref: "usb-hcd.yaml" + - $ref: "usb-xhci.yaml" properties: compatible: @@ -68,7 +68,7 @@ required: - power-domains - resets -additionalProperties: false +unevaluatedProperties: false examples: - | -- cgit v1.2.3 From 389d7765880143e18139a9c5428b67bc9d3a617e Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 10 Dec 2020 12:09:34 +0300 Subject: dt-bindings: usb: Convert DWC USB3 bindings to DT schema DWC USB3 DT node is supposed to be compliant with the Generic xHCI Controller schema, but with additional vendor-specific properties, the controller-specific reference clocks and PHYs. So let's convert the currently available legacy text-based DWC USB3 bindings to the DT schema and make sure the DWC USB3 nodes are also validated against the usb-xhci.yaml schema. Note 1. we have to discard the nodename restriction of being prefixed with "dwc3@" string, since in accordance with the usb-hcd.yaml schema USB nodes are supposed to be named as "^usb(@.*)". Note 2. The clock-related properties are marked as optional to match the DWC USB3 driver expectation and to improve the bindings mainainability so in case if there is a glue-node it would the responsible for the clocks initialization. Reviewed-by: Rob Herring Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20201210090944.16283-11-Sergey.Semin@baikalelectronics.ru Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/dwc3.txt | 128 --------- .../devicetree/bindings/usb/snps,dwc3.yaml | 312 +++++++++++++++++++++ 2 files changed, 312 insertions(+), 128 deletions(-) delete mode 100644 Documentation/devicetree/bindings/usb/dwc3.txt create mode 100644 Documentation/devicetree/bindings/usb/snps,dwc3.yaml diff --git a/Documentation/devicetree/bindings/usb/dwc3.txt b/Documentation/devicetree/bindings/usb/dwc3.txt deleted file mode 100644 index 1aae2b6160c1..000000000000 --- a/Documentation/devicetree/bindings/usb/dwc3.txt +++ /dev/null @@ -1,128 +0,0 @@ -synopsys DWC3 CORE - -DWC3- USB3 CONTROLLER. Complies to the generic USB binding properties - as described in 'usb/generic.txt' - -Required properties: - - compatible: must be "snps,dwc3" - - reg : Address and length of the register set for the device - - interrupts: Interrupts used by the dwc3 controller. - - clock-names: list of clock names. Ideally should be "ref", - "bus_early", "suspend" but may be less or more. - - clocks: list of phandle and clock specifier pairs corresponding to - entries in the clock-names property. - -Exception for clocks: - clocks are optional if the parent node (i.e. glue-layer) is compatible to - one of the following: - "cavium,octeon-7130-usb-uctl" - "qcom,dwc3" - "samsung,exynos5250-dwusb3" - "samsung,exynos5433-dwusb3" - "samsung,exynos7-dwusb3" - "sprd,sc9860-dwc3" - "st,stih407-dwc3" - "ti,am437x-dwc3" - "ti,dwc3" - "ti,keystone-dwc3" - "rockchip,rk3399-dwc3" - "xlnx,zynqmp-dwc3" - -Optional properties: - - usb-phy : array of phandle for the PHY device. The first element - in the array is expected to be a handle to the USB2/HS PHY and - the second element is expected to be a handle to the USB3/SS PHY - - phys: from the *Generic PHY* bindings - - phy-names: from the *Generic PHY* bindings; supported names are "usb2-phy" - or "usb3-phy". - - resets: set of phandle and reset specifier pairs - - snps,usb2-lpm-disable: indicate if we don't want to enable USB2 HW LPM - - snps,usb3_lpm_capable: determines if platform is USB3 LPM capable - - snps,dis-start-transfer-quirk: when set, disable isoc START TRANSFER command - failure SW work-around for DWC_usb31 version 1.70a-ea06 - and prior. - - snps,disable_scramble_quirk: true when SW should disable data scrambling. - Only really useful for FPGA builds. - - snps,has-lpm-erratum: true when DWC3 was configured with LPM Erratum enabled - - snps,lpm-nyet-threshold: LPM NYET threshold - - snps,u2exit_lfps_quirk: set if we want to enable u2exit lfps quirk - - snps,u2ss_inp3_quirk: set if we enable P3 OK for U2/SS Inactive quirk - - snps,req_p1p2p3_quirk: when set, the core will always request for - P1/P2/P3 transition sequence. - - snps,del_p1p2p3_quirk: when set core will delay P1/P2/P3 until a certain - amount of 8B10B errors occur. - - snps,del_phy_power_chg_quirk: when set core will delay PHY power change - from P0 to P1/P2/P3. - - snps,lfps_filter_quirk: when set core will filter LFPS reception. - - snps,rx_detect_poll_quirk: when set core will disable a 400us delay to start - Polling LFPS after RX.Detect. - - snps,tx_de_emphasis_quirk: when set core will set Tx de-emphasis value. - - snps,tx_de_emphasis: the value driven to the PHY is controlled by the - LTSSM during USB3 Compliance mode. - - snps,dis_u3_susphy_quirk: when set core will disable USB3 suspend phy. - - snps,dis_u2_susphy_quirk: when set core will disable USB2 suspend phy. - - snps,dis_enblslpm_quirk: when set clears the enblslpm in GUSB2PHYCFG, - disabling the suspend signal to the PHY. - - snps,dis-u1-entry-quirk: set if link entering into U1 needs to be disabled. - - snps,dis-u2-entry-quirk: set if link entering into U2 needs to be disabled. - - snps,dis_rxdet_inp3_quirk: when set core will disable receiver detection - in PHY P3 power state. - - snps,dis-u2-freeclk-exists-quirk: when set, clear the u2_freeclk_exists - in GUSB2PHYCFG, specify that USB2 PHY doesn't provide - a free-running PHY clock. - - snps,dis-del-phy-power-chg-quirk: when set core will change PHY power - from P0 to P1/P2/P3 without delay. - - snps,dis-tx-ipgap-linecheck-quirk: when set, disable u2mac linestate check - during HS transmit. - - snps,parkmode-disable-ss-quirk: when set, all SuperSpeed bus instances in - park mode are disabled. - - snps,dis_metastability_quirk: when set, disable metastability workaround. - CAUTION: use only if you are absolutely sure of it. - - snps,dis-split-quirk: when set, change the way URBs are handled by the - driver. Needed to avoid -EPROTO errors with usbhid - on some devices (Hikey 970). - - snps,is-utmi-l1-suspend: true when DWC3 asserts output signal - utmi_l1_suspend_n, false when asserts utmi_sleep_n - - snps,hird-threshold: HIRD threshold - - snps,hsphy_interface: High-Speed PHY interface selection between "utmi" for - UTMI+ and "ulpi" for ULPI when the DWC_USB3_HSPHY_INTERFACE has value 3. - - snps,quirk-frame-length-adjustment: Value for GFLADJ_30MHZ field of GFLADJ - register for post-silicon frame length adjustment when the - fladj_30mhz_sdbnd signal is invalid or incorrect. - - snps,rx-thr-num-pkt-prd: periodic ESS RX packet threshold count - host mode - only. Set this and rx-max-burst-prd to a valid, - non-zero value 1-16 (DWC_usb31 programming guide - section 1.2.4) to enable periodic ESS RX threshold. - - snps,rx-max-burst-prd: max periodic ESS RX burst size - host mode only. Set - this and rx-thr-num-pkt-prd to a valid, non-zero value - 1-16 (DWC_usb31 programming guide section 1.2.4) to - enable periodic ESS RX threshold. - - snps,tx-thr-num-pkt-prd: periodic ESS TX packet threshold count - host mode - only. Set this and tx-max-burst-prd to a valid, - non-zero value 1-16 (DWC_usb31 programming guide - section 1.2.3) to enable periodic ESS TX threshold. - - snps,tx-max-burst-prd: max periodic ESS TX burst size - host mode only. Set - this and tx-thr-num-pkt-prd to a valid, non-zero value - 1-16 (DWC_usb31 programming guide section 1.2.3) to - enable periodic ESS TX threshold. - - - tx-fifo-resize: determines if the FIFO *has* to be reallocated. - - snps,incr-burst-type-adjustment: Value for INCR burst type of GSBUSCFG0 - register, undefined length INCR burst type enable and INCRx type. - When just one value, which means INCRX burst mode enabled. When - more than one value, which means undefined length INCR burst type - enabled. The values can be 1, 4, 8, 16, 32, 64, 128 and 256. - - - in addition all properties from usb-xhci.txt from the current directory are - supported as well - - -This is usually a subnode to DWC3 glue to which it is connected. - -dwc3@4a030000 { - compatible = "snps,dwc3"; - reg = <0x4a030000 0xcfff>; - interrupts = <0 92 4> - usb-phy = <&usb2_phy>, <&usb3,phy>; - snps,incr-burst-type-adjustment = <1>, <4>, <8>, <16>; -}; diff --git a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml new file mode 100644 index 000000000000..57caca8339ae --- /dev/null +++ b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml @@ -0,0 +1,312 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/snps,dwc3.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Synopsys DesignWare USB3 Controller + +maintainers: + - Felipe Balbi + +description: + This is usually a subnode to DWC3 glue to which it is connected, but can also + be presented as a standalone DT node with an optional vendor-specific + compatible string. + +allOf: + - $ref: usb-drd.yaml# + - if: + properties: + dr_mode: + const: peripheral + + required: + - dr_mode + then: + $ref: usb.yaml# + else: + $ref: usb-xhci.yaml# + +properties: + compatible: + contains: + const: snps,dwc3 + + interrupts: + minItems: 1 + maxItems: 3 + + clocks: + description: + In general the core supports three types of clocks. bus_early is a + SoC Bus Clock (AHB/AXI/Native). ref generates ITP when the UTMI/ULPI + PHY is suspended. suspend clocks a small part of the USB3 core when + SS PHY in P3. But particular cases may differ from that having less + or more clock sources with another names. + + clock-names: + contains: + anyOf: + - enum: [bus_early, ref, suspend] + - true + + usb-phy: + minItems: 1 + items: + - description: USB2/HS PHY + - description: USB3/SS PHY + + phys: + minItems: 1 + items: + - description: USB2/HS PHY + - description: USB3/SS PHY + + phy-names: + minItems: 1 + items: + - const: usb2-phy + - const: usb3-phy + + resets: + minItems: 1 + + snps,usb2-lpm-disable: + description: Indicate if we don't want to enable USB2 HW LPM + type: boolean + + snps,usb3_lpm_capable: + description: Determines if platform is USB3 LPM capable + type: boolean + + snps,dis-start-transfer-quirk: + description: + When set, disable isoc START TRANSFER command failure SW work-around + for DWC_usb31 version 1.70a-ea06 and prior. + type: boolean + + snps,disable_scramble_quirk: + description: + True when SW should disable data scrambling. Only really useful for FPGA + builds. + type: boolean + + snps,has-lpm-erratum: + description: True when DWC3 was configured with LPM Erratum enabled + type: boolean + + snps,lpm-nyet-threshold: + description: LPM NYET threshold + $ref: /schemas/types.yaml#/definitions/uint8 + + snps,u2exit_lfps_quirk: + description: Set if we want to enable u2exit lfps quirk + type: boolean + + snps,u2ss_inp3_quirk: + description: Set if we enable P3 OK for U2/SS Inactive quirk + type: boolean + + snps,req_p1p2p3_quirk: + description: + When set, the core will always request for P1/P2/P3 transition sequence. + type: boolean + + snps,del_p1p2p3_quirk: + description: + When set core will delay P1/P2/P3 until a certain amount of 8B10B errors + occur. + type: boolean + + snps,del_phy_power_chg_quirk: + description: When set core will delay PHY power change from P0 to P1/P2/P3. + type: boolean + + snps,lfps_filter_quirk: + description: When set core will filter LFPS reception. + type: boolean + + snps,rx_detect_poll_quirk: + description: + when set core will disable a 400us delay to start Polling LFPS after + RX.Detect. + type: boolean + + snps,tx_de_emphasis_quirk: + description: When set core will set Tx de-emphasis value + type: boolean + + snps,tx_de_emphasis: + description: + The value driven to the PHY is controlled by the LTSSM during USB3 + Compliance mode. + $ref: /schemas/types.yaml#/definitions/uint8 + + snps,dis_u3_susphy_quirk: + description: When set core will disable USB3 suspend phy + type: boolean + + snps,dis_u2_susphy_quirk: + description: When set core will disable USB2 suspend phy + type: boolean + + snps,dis_enblslpm_quirk: + description: + When set clears the enblslpm in GUSB2PHYCFG, disabling the suspend signal + to the PHY. + type: boolean + + snps,dis-u1-entry-quirk: + description: Set if link entering into U1 needs to be disabled + type: boolean + + snps,dis-u2-entry-quirk: + description: Set if link entering into U2 needs to be disabled + type: boolean + + snps,dis_rxdet_inp3_quirk: + description: + When set core will disable receiver detection in PHY P3 power state. + type: boolean + + snps,dis-u2-freeclk-exists-quirk: + description: + When set, clear the u2_freeclk_exists in GUSB2PHYCFG, specify that USB2 + PHY doesn't provide a free-running PHY clock. + type: boolean + + snps,dis-del-phy-power-chg-quirk: + description: + When set core will change PHY power from P0 to P1/P2/P3 without delay. + type: boolean + + snps,dis-tx-ipgap-linecheck-quirk: + description: When set, disable u2mac linestate check during HS transmit + type: boolean + + snps,parkmode-disable-ss-quirk: + description: + When set, all SuperSpeed bus instances in park mode are disabled. + type: boolean + + snps,dis_metastability_quirk: + description: + When set, disable metastability workaround. CAUTION! Use only if you are + absolutely sure of it. + type: boolean + + snps,dis-split-quirk: + description: + When set, change the way URBs are handled by the driver. Needed to + avoid -EPROTO errors with usbhid on some devices (Hikey 970). + type: boolean + + snps,is-utmi-l1-suspend: + description: + True when DWC3 asserts output signal utmi_l1_suspend_n, false when + asserts utmi_sleep_n. + type: boolean + + snps,hird-threshold: + description: HIRD threshold + $ref: /schemas/types.yaml#/definitions/uint8 + + snps,hsphy_interface: + description: + High-Speed PHY interface selection between UTMI+ and ULPI when the + DWC_USB3_HSPHY_INTERFACE has value 3. + $ref: /schemas/types.yaml#/definitions/uint8 + enum: [utmi, ulpi] + + snps,quirk-frame-length-adjustment: + description: + Value for GFLADJ_30MHZ field of GFLADJ register for post-silicon frame + length adjustment when the fladj_30mhz_sdbnd signal is invalid or + incorrect. + $ref: /schemas/types.yaml#/definitions/uint32 + + snps,rx-thr-num-pkt-prd: + description: + Periodic ESS RX packet threshold count (host mode only). Set this and + snps,rx-max-burst-prd to a valid, non-zero value 1-16 (DWC_usb31 + programming guide section 1.2.4) to enable periodic ESS RX threshold. + $ref: /schemas/types.yaml#/definitions/uint8 + minimum: 1 + maximum: 16 + + snps,rx-max-burst-prd: + description: + Max periodic ESS RX burst size (host mode only). Set this and + snps,rx-thr-num-pkt-prd to a valid, non-zero value 1-16 (DWC_usb31 + programming guide section 1.2.4) to enable periodic ESS RX threshold. + $ref: /schemas/types.yaml#/definitions/uint8 + minimum: 1 + maximum: 16 + + snps,tx-thr-num-pkt-prd: + description: + Periodic ESS TX packet threshold count (host mode only). Set this and + snps,tx-max-burst-prd to a valid, non-zero value 1-16 (DWC_usb31 + programming guide section 1.2.3) to enable periodic ESS TX threshold. + $ref: /schemas/types.yaml#/definitions/uint8 + minimum: 1 + maximum: 16 + + snps,tx-max-burst-prd: + description: + Max periodic ESS TX burst size (host mode only). Set this and + snps,tx-thr-num-pkt-prd to a valid, non-zero value 1-16 (DWC_usb31 + programming guide section 1.2.3) to enable periodic ESS TX threshold. + $ref: /schemas/types.yaml#/definitions/uint8 + minimum: 1 + maximum: 16 + + tx-fifo-resize: + description: Determines if the FIFO *has* to be reallocated + deprecated: true + type: boolean + + snps,incr-burst-type-adjustment: + description: + Value for INCR burst type of GSBUSCFG0 register, undefined length INCR + burst type enable and INCRx type. A single value means INCRX burst mode + enabled. If more than one value specified, undefined length INCR burst + type will be enabled with burst lengths utilized up to the maximum + of the values passed in this property. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 8 + uniqueItems: true + items: + enum: [1, 4, 8, 16, 32, 64, 128, 256] + +unevaluatedProperties: false + +required: + - compatible + - reg + - interrupts + +examples: + - | + usb@4a030000 { + compatible = "snps,dwc3"; + reg = <0x4a030000 0xcfff>; + interrupts = <0 92 4>; + usb-phy = <&usb2_phy>, <&usb3_phy>; + snps,incr-burst-type-adjustment = <1>, <4>, <8>, <16>; + }; + - | + usb@4a000000 { + compatible = "snps,dwc3"; + reg = <0x4a000000 0xcfff>; + interrupts = <0 92 4>; + clocks = <&clk 1>, <&clk 2>, <&clk 3>; + clock-names = "bus_early", "ref", "suspend"; + phys = <&usb2_phy>, <&usb3_phy>; + phy-names = "usb2-phy", "usb3-phy"; + snps,dis_u2_susphy_quirk; + snps,dis_enblslpm_quirk; + }; +... -- cgit v1.2.3 From 53f5ef5d622b7f2b5cf3e53d965b12c38082f974 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 10 Dec 2020 12:09:35 +0300 Subject: dt-bindings: usb: dwc3: Add interrupt-names property support The controller driver supports two types of DWC USB3 devices: with a common interrupt lane and with individual interrupts for each mode. Add support for both these cases to the DWC USB3 DT schema. Reviewed-by: Rob Herring Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20201210090944.16283-12-Sergey.Semin@baikalelectronics.ru Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/snps,dwc3.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml index 57caca8339ae..87a92e313d24 100644 --- a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml @@ -34,8 +34,19 @@ properties: const: snps,dwc3 interrupts: + description: + It's either a single common DWC3 interrupt (dwc_usb3) or individual + interrupts for the host, gadget and DRD modes. + minItems: 1 + maxItems: 3 + + interrupt-names: minItems: 1 maxItems: 3 + oneOf: + - const: dwc_usb3 + - items: + enum: [host, peripheral, otg] clocks: description: -- cgit v1.2.3 From f82dc55719963a214a33946c8c2cd9932ca19b8d Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 10 Dec 2020 12:09:36 +0300 Subject: dt-bindings: usb: dwc3: Add synopsys, dwc3 compatible string The DWC USB3 driver and some DTS files like Exynos 5250, Keystone k2e, etc expects the DWC USB3 DT node to have the compatible string with the "synopsys" vendor prefix. Let's add the corresponding compatible string to the controller DT schema, but mark it as deprecated seeing the Synopsys, Inc. is presented with just "snps" vendor prefix. Reviewed-by: Rob Herring Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20201210090944.16283-13-Sergey.Semin@baikalelectronics.ru Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/snps,dwc3.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml index 87a92e313d24..6253bc5fb18e 100644 --- a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml @@ -31,7 +31,10 @@ allOf: properties: compatible: contains: - const: snps,dwc3 + oneOf: + - const: snps,dwc3 + - const: synopsys,dwc3 + deprecated: true interrupts: description: -- cgit v1.2.3 From 6f84a28df802cd23e3a526a49a6417462bdbbed3 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 10 Dec 2020 12:09:37 +0300 Subject: dt-bindings: usb: dwc3: Add Tx De-emphasis constraints In accordance with the driver comments the PIPE3 de-emphasis can be tuned to be either -6dB, -2.5dB or disabled. Let's add the de-emphasis property constraints so the DT schema would make sure the controller DT node is equipped with correct value. Reviewed-by: Rob Herring Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20201210090944.16283-14-Sergey.Semin@baikalelectronics.ru Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/snps,dwc3.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml index 6253bc5fb18e..e01a9a93d74a 100644 --- a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml @@ -156,6 +156,10 @@ properties: The value driven to the PHY is controlled by the LTSSM during USB3 Compliance mode. $ref: /schemas/types.yaml#/definitions/uint8 + enum: + - 0 # -6dB de-emphasis + - 1 # -3.5dB de-emphasis + - 2 # No de-emphasis snps,dis_u3_susphy_quirk: description: When set core will disable USB3 suspend phy -- cgit v1.2.3 From dc87c87126d14fdce1986c4b3e8674a6dcbe7bfb Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 10 Dec 2020 12:09:38 +0300 Subject: dt-bindings: usb: dwc3: Add Frame Length Adj constraints In accordance with the IP core databook the snps,quirk-frame-length-adjustment property can be set within [0, 0x3F]. Let's make sure the DT schema applies a correct constraints on the property. Reviewed-by: Rob Herring Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20201210090944.16283-15-Sergey.Semin@baikalelectronics.ru Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/snps,dwc3.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml index e01a9a93d74a..2247da77eac1 100644 --- a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml @@ -243,6 +243,8 @@ properties: length adjustment when the fladj_30mhz_sdbnd signal is invalid or incorrect. $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 0x3f snps,rx-thr-num-pkt-prd: description: -- cgit v1.2.3 From 042cdcd6c9234e464792234ca95870ad797580be Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 10 Dec 2020 12:09:39 +0300 Subject: dt-bindings: usb: meson-g12a-usb: Fix FL-adj property value An empty snps,quirk-frame-length-adjustment won't cause any change performed by the driver. Moreover the DT schema validation will fail, since it expects the property being assigned with some value. So set fix the example by setting a valid FL-adj value in accordance with Neil Armstrong comment. Link: https://lore.kernel.org/linux-usb/20201010224121.12672-16-Sergey.Semin@baikalelectronics.ru/ Reviewed-by: Rob Herring Reviewed-by: Martin Blumenstingl Acked-by: Neil Armstrong Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20201210090944.16283-16-Sergey.Semin@baikalelectronics.ru Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/amlogic,meson-g12a-usb-ctrl.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/amlogic,meson-g12a-usb-ctrl.yaml b/Documentation/devicetree/bindings/usb/amlogic,meson-g12a-usb-ctrl.yaml index c0058332b967..1eda16dd4ee0 100644 --- a/Documentation/devicetree/bindings/usb/amlogic,meson-g12a-usb-ctrl.yaml +++ b/Documentation/devicetree/bindings/usb/amlogic,meson-g12a-usb-ctrl.yaml @@ -229,6 +229,6 @@ examples: interrupts = <30>; dr_mode = "host"; snps,dis_u2_susphy_quirk; - snps,quirk-frame-length-adjustment; + snps,quirk-frame-length-adjustment = <0x20>; }; }; -- cgit v1.2.3 From 3b34a58969b2b6e3831d0117fd2a3a0839ada988 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 10 Dec 2020 12:09:40 +0300 Subject: dt-bindings: usb: meson-g12a-usb: Validate DWC2/DWC3 sub-nodes Amlogic G12A USB DT sub-nodes are supposed to be compatible with the generic DWC USB2 and USB3 devices. Since now we've got DT schemas for both of the later IP cores let's make sure that the Amlogic G12A USB DT nodes are fully evaluated including the DWC sub-nodes. Reviewed-by: Neil Armstrong Reviewed-by: Rob Herring Reviewed-by: Martin Blumenstingl Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20201210090944.16283-17-Sergey.Semin@baikalelectronics.ru Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/amlogic,meson-g12a-usb-ctrl.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/amlogic,meson-g12a-usb-ctrl.yaml b/Documentation/devicetree/bindings/usb/amlogic,meson-g12a-usb-ctrl.yaml index 1eda16dd4ee0..e349fa5de606 100644 --- a/Documentation/devicetree/bindings/usb/amlogic,meson-g12a-usb-ctrl.yaml +++ b/Documentation/devicetree/bindings/usb/amlogic,meson-g12a-usb-ctrl.yaml @@ -79,7 +79,9 @@ properties: patternProperties: "^usb@[0-9a-f]+$": - type: object + oneOf: + - $ref: dwc2.yaml# + - $ref: snps,dwc3.yaml# additionalProperties: false -- cgit v1.2.3 From e9cd063547a115acd6ca8ad436748c16d8d98821 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 10 Dec 2020 12:09:41 +0300 Subject: dt-bindings: usb: keystone-dwc3: Validate DWC3 sub-node TI Keystone DWC3 compatible DT node is supposed to have a DWC USB3 compatible sub-node to describe a fully functioning USB interface. Since DWC USB3 has now got a DT schema describing its DT node, let's make sure the TI Keystone DWC3 sub-node passes validation against it. Reviewed-by: Rob Herring Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20201210090944.16283-18-Sergey.Semin@baikalelectronics.ru Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/ti,keystone-dwc3.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/ti,keystone-dwc3.yaml b/Documentation/devicetree/bindings/usb/ti,keystone-dwc3.yaml index c1b19fc5d0a2..ca7fbe3ed22e 100644 --- a/Documentation/devicetree/bindings/usb/ti,keystone-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/ti,keystone-dwc3.yaml @@ -64,9 +64,7 @@ properties: patternProperties: "usb@[a-f0-9]+$": - type: object - description: This is the node representing the DWC3 controller instance - Documentation/devicetree/bindings/usb/dwc3.txt + $ref: snps,dwc3.yaml# required: - compatible -- cgit v1.2.3 From 1fd7b103451e7662712a315faf8e5f41e0e8bb00 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 10 Dec 2020 12:09:42 +0300 Subject: dt-bindings: usb: qcom,dwc3: Validate DWC3 sub-node Qualcomm msm8996/sc7180/sdm845 DWC3 compatible DT nodes are supposed to have a DWC USB3 compatible sub-node to describe a fully functioning USB interface. Let's use the available DWC USB3 DT schema to validate the Qualcomm DWC3 sub-nodes. Note since the generic DWC USB3 DT node is supposed to be named as generic USB HCD ("^usb(@.*)?") one we have to accordingly fix the sub-nodes name regexp and fix the DT node example. Reviewed-by: Rob Herring Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20201210090944.16283-19-Sergey.Semin@baikalelectronics.ru Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/qcom,dwc3.yaml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml index 2cf525d21e05..b336662e838c 100644 --- a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml @@ -103,11 +103,8 @@ properties: # Required child node: patternProperties: - "^dwc3@[0-9a-f]+$": - type: object - description: - A child node must exist to represent the core DWC3 IP block - The content of the node is defined in dwc3.txt. + "^usb@[0-9a-f]+$": + $ref: snps,dwc3.yaml# required: - compatible @@ -162,7 +159,7 @@ examples: resets = <&gcc GCC_USB30_PRIM_BCR>; - dwc3@a600000 { + usb@a600000 { compatible = "snps,dwc3"; reg = <0 0x0a600000 0 0xcd00>; interrupts = ; -- cgit v1.2.3 From 492d3d246203592f7ce4c21ab069a59be106b07a Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 10 Dec 2020 12:09:43 +0300 Subject: dt-bindings: usb: intel, keembay-dwc3: Validate DWC3 sub-node Intel Keem Bay DWC3 compatible DT nodes are supposed to have a DWC USB3 compatible sub-node to describe a fully functioning USB interface. Let's use the available DWC USB3 DT schema to validate the Intel Keem Bay DWC3 sub-nodes. Note since the generic DWC USB3 DT node is supposed to be named as generic USB HCD ("^usb(@.*)?") one we have to accordingly fix the sub-nodes name regexp and fix the DT node example. Reviewed-by: Rob Herring Acked-by: Wan Ahmad Zainie Signed-off-by: Serge Semin Link: https://lore.kernel.org/r/20201210090944.16283-20-Sergey.Semin@baikalelectronics.ru Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/intel,keembay-dwc3.yaml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/intel,keembay-dwc3.yaml b/Documentation/devicetree/bindings/usb/intel,keembay-dwc3.yaml index dd32c10ce6c7..43b91ab62004 100644 --- a/Documentation/devicetree/bindings/usb/intel,keembay-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/intel,keembay-dwc3.yaml @@ -34,11 +34,8 @@ properties: # Required child node: patternProperties: - "^dwc3@[0-9a-f]+$": - type: object - description: - A child node must exist to represent the core DWC3 IP block. - The content of the node is defined in dwc3.txt. + "^usb@[0-9a-f]+$": + $ref: snps,dwc3.yaml# required: - compatible @@ -68,7 +65,7 @@ examples: #address-cells = <1>; #size-cells = <1>; - dwc3@34000000 { + usb@34000000 { compatible = "snps,dwc3"; reg = <0x34000000 0x10000>; interrupts = ; -- cgit v1.2.3 From babbdfc9d2297b641ba8c91de1655673507736e4 Mon Sep 17 00:00:00 2001 From: Yejune Deng Date: Tue, 3 Nov 2020 11:57:43 +0800 Subject: usb: dwc3: core: Replace devm_reset_control_array_get() devm_reset_control_array_get_optional_shared() looks more readable Reviewed-by: Dmitry Osipenko Acked-by: Felipe Balbi Signed-off-by: Yejune Deng Link: https://lore.kernel.org/r/1604375863-6649-1-git-send-email-yejune.deng@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 841daec70b6e..b87acf06ec8e 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1490,7 +1490,7 @@ static int dwc3_probe(struct platform_device *pdev) dwc3_get_properties(dwc); - dwc->reset = devm_reset_control_array_get(dev, true, true); + dwc->reset = devm_reset_control_array_get_optional_shared(dev); if (IS_ERR(dwc->reset)) return PTR_ERR(dwc->reset); -- cgit v1.2.3 From 49d08cfc7830440517216b6d51bd0c38b5314c7f Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Mon, 11 Jan 2021 12:38:05 -0800 Subject: usb: udc: core: Introduce started state For some UDCs, the initialization sequence by udc_start() should not be repeated until it is properly cleaned up with udc_stop() and vise versa. We may run into some cleanup failure as seen with the DWC3 driver during the irq cleanup. This issue can occur when the user triggers soft-connect/soft-disconnect from the soft_connect sysfs. To avoid adding checks to every UDC driver, at the UDC framework, introduce a "started" state to track and prevent the UDC from repeating the udc_start() and udc_stop() if it had already started/stopped. Acked-by: Felipe Balbi Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/a7c4112fcd4dc2f0169af94a24f5685ca77f09fd.1610395599.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/core.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index 6a62bbd01324..98cf9216f3cb 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -29,6 +29,7 @@ * @list: for use by the udc class driver * @vbus: for udcs who care about vbus status, this value is real vbus status; * for udcs who do not care about vbus status, this value is always true + * @started: the UDC's started state. True if the UDC had started. * * This represents the internal data structure which is used by the UDC-class * to hold information about udc driver and gadget together. @@ -39,6 +40,7 @@ struct usb_udc { struct device dev; struct list_head list; bool vbus; + bool started; }; static struct class *udc_class; @@ -1082,7 +1084,18 @@ EXPORT_SYMBOL_GPL(usb_gadget_udc_reset); */ static inline int usb_gadget_udc_start(struct usb_udc *udc) { - return udc->gadget->ops->udc_start(udc->gadget, udc->driver); + int ret; + + if (udc->started) { + dev_err(&udc->dev, "UDC had already started\n"); + return -EBUSY; + } + + ret = udc->gadget->ops->udc_start(udc->gadget, udc->driver); + if (!ret) + udc->started = true; + + return ret; } /** @@ -1098,7 +1111,13 @@ static inline int usb_gadget_udc_start(struct usb_udc *udc) */ static inline void usb_gadget_udc_stop(struct usb_udc *udc) { + if (!udc->started) { + dev_err(&udc->dev, "UDC had already stopped\n"); + return; + } + udc->gadget->ops->udc_stop(udc->gadget); + udc->started = false; } /** @@ -1222,6 +1241,8 @@ int usb_add_gadget(struct usb_gadget *gadget) udc->gadget = gadget; gadget->udc = udc; + udc->started = false; + mutex_lock(&udc_lock); list_add_tail(&udc->list, &udc_list); -- cgit v1.2.3 From 370e3d5b711ded835dfb8d32d917a7c652783984 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 11 Jan 2021 15:55:39 +0200 Subject: usb: dwc3: keystone: Simplify with dev_err_probe() Common pattern of handling deferred probe can be simplified with dev_err_probe(). Less code and the error value gets printed. Acked-by: Felipe Balbi Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210111135539.57234-1-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-keystone.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-keystone.c b/drivers/usb/dwc3/dwc3-keystone.c index 9a99253d5ba3..057056c0975e 100644 --- a/drivers/usb/dwc3/dwc3-keystone.c +++ b/drivers/usb/dwc3/dwc3-keystone.c @@ -99,13 +99,8 @@ static int kdwc3_probe(struct platform_device *pdev) /* PSC dependency on AM65 needs SERDES0 to be powered before USB0 */ kdwc->usb3_phy = devm_phy_optional_get(dev, "usb3-phy"); - if (IS_ERR(kdwc->usb3_phy)) { - error = PTR_ERR(kdwc->usb3_phy); - if (error != -EPROBE_DEFER) - dev_err(dev, "couldn't get usb3 phy: %d\n", error); - - return error; - } + if (IS_ERR(kdwc->usb3_phy)) + return dev_err_probe(dev, PTR_ERR(kdwc->usb3_phy), "couldn't get usb3 phy\n"); phy_pm_runtime_get_sync(kdwc->usb3_phy); -- cgit v1.2.3 From 0c0a20f6da04780b94e3ccc43101dde6f536ab37 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 11 Jan 2021 15:54:58 +0200 Subject: usb: dwc3: Simplify with dev_err_probe() Common pattern of handling deferred probe can be simplified with dev_err_probe(). Less code and the error value gets printed. Acked-by: Felipe Balbi Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210111135458.57084-1-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 44 +++++++++++--------------------------------- 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index b87acf06ec8e..6969196fccd6 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1126,11 +1126,8 @@ static int dwc3_core_get_phy(struct dwc3 *dwc) ret = PTR_ERR(dwc->usb2_phy); if (ret == -ENXIO || ret == -ENODEV) { dwc->usb2_phy = NULL; - } else if (ret == -EPROBE_DEFER) { - return ret; } else { - dev_err(dev, "no usb2 phy configured\n"); - return ret; + return dev_err_probe(dev, ret, "no usb2 phy configured\n"); } } @@ -1138,11 +1135,8 @@ static int dwc3_core_get_phy(struct dwc3 *dwc) ret = PTR_ERR(dwc->usb3_phy); if (ret == -ENXIO || ret == -ENODEV) { dwc->usb3_phy = NULL; - } else if (ret == -EPROBE_DEFER) { - return ret; } else { - dev_err(dev, "no usb3 phy configured\n"); - return ret; + return dev_err_probe(dev, ret, "no usb3 phy configured\n"); } } @@ -1151,11 +1145,8 @@ static int dwc3_core_get_phy(struct dwc3 *dwc) ret = PTR_ERR(dwc->usb2_generic_phy); if (ret == -ENOSYS || ret == -ENODEV) { dwc->usb2_generic_phy = NULL; - } else if (ret == -EPROBE_DEFER) { - return ret; } else { - dev_err(dev, "no usb2 phy configured\n"); - return ret; + return dev_err_probe(dev, ret, "no usb2 phy configured\n"); } } @@ -1164,11 +1155,8 @@ static int dwc3_core_get_phy(struct dwc3 *dwc) ret = PTR_ERR(dwc->usb3_generic_phy); if (ret == -ENOSYS || ret == -ENODEV) { dwc->usb3_generic_phy = NULL; - } else if (ret == -EPROBE_DEFER) { - return ret; } else { - dev_err(dev, "no usb3 phy configured\n"); - return ret; + return dev_err_probe(dev, ret, "no usb3 phy configured\n"); } } @@ -1190,11 +1178,8 @@ static int dwc3_core_init_mode(struct dwc3 *dwc) phy_set_mode(dwc->usb3_generic_phy, PHY_MODE_USB_DEVICE); ret = dwc3_gadget_init(dwc); - if (ret) { - if (ret != -EPROBE_DEFER) - dev_err(dev, "failed to initialize gadget\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "failed to initialize gadget\n"); break; case USB_DR_MODE_HOST: dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST); @@ -1205,20 +1190,14 @@ static int dwc3_core_init_mode(struct dwc3 *dwc) phy_set_mode(dwc->usb3_generic_phy, PHY_MODE_USB_HOST); ret = dwc3_host_init(dwc); - if (ret) { - if (ret != -EPROBE_DEFER) - dev_err(dev, "failed to initialize host\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "failed to initialize host\n"); break; case USB_DR_MODE_OTG: INIT_WORK(&dwc->drd_work, __dwc3_set_mode); ret = dwc3_drd_init(dwc); - if (ret) { - if (ret != -EPROBE_DEFER) - dev_err(dev, "failed to initialize dual-role\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "failed to initialize dual-role\n"); break; default: dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode); @@ -1555,8 +1534,7 @@ static int dwc3_probe(struct platform_device *pdev) ret = dwc3_core_init(dwc); if (ret) { - if (ret != -EPROBE_DEFER) - dev_err(dev, "failed to initialize core: %d\n", ret); + dev_err_probe(dev, ret, "failed to initialize core\n"); goto err4; } -- cgit v1.2.3 From 8653d71ce3763aedcf3d2331f59beda3fecd79e4 Mon Sep 17 00:00:00 2001 From: Davidlohr Bueso Date: Sun, 10 Jan 2021 20:28:55 -0800 Subject: usb/gadget: f_midi: Replace tasklet with work Currently a tasklet is used to transmit input substream buffer data. However, tasklets have long been deprecated as being too heavy on the system by running in irq context - and this is not a performance critical path. If a higher priority process wants to run, it must wait for the tasklet to finish before doing so. Deferring work to a workqueue and executing in process context should be fine considering the callback already does f_midi_do_transmit() under the transmit_lock and thus changes in semantics are ok regarding concurrency - tasklets being serialized against itself. Cc: Takashi Iwai Reviewed-by: Takashi Iwai Acked-by: Felipe Balbi Signed-off-by: Davidlohr Bueso Link: https://lore.kernel.org/r/20210111042855.73289-1-dave@stgolabs.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_midi.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c index 8fff995b8dd5..71a1a26e85c7 100644 --- a/drivers/usb/gadget/function/f_midi.c +++ b/drivers/usb/gadget/function/f_midi.c @@ -87,7 +87,7 @@ struct f_midi { struct snd_rawmidi_substream *out_substream[MAX_PORTS]; unsigned long out_triggered; - struct tasklet_struct tasklet; + struct work_struct work; unsigned int in_ports; unsigned int out_ports; int index; @@ -698,9 +698,11 @@ drop_out: f_midi_drop_out_substreams(midi); } -static void f_midi_in_tasklet(struct tasklet_struct *t) +static void f_midi_in_work(struct work_struct *work) { - struct f_midi *midi = from_tasklet(midi, t, tasklet); + struct f_midi *midi; + + midi = container_of(work, struct f_midi, work); f_midi_transmit(midi); } @@ -737,7 +739,7 @@ static void f_midi_in_trigger(struct snd_rawmidi_substream *substream, int up) VDBG(midi, "%s() %d\n", __func__, up); midi->in_ports_array[substream->number].active = up; if (up) - tasklet_hi_schedule(&midi->tasklet); + queue_work(system_highpri_wq, &midi->work); } static int f_midi_out_open(struct snd_rawmidi_substream *substream) @@ -875,7 +877,7 @@ static int f_midi_bind(struct usb_configuration *c, struct usb_function *f) int status, n, jack = 1, i = 0, endpoint_descriptor_index = 0; midi->gadget = cdev->gadget; - tasklet_setup(&midi->tasklet, f_midi_in_tasklet); + INIT_WORK(&midi->work, f_midi_in_work); status = f_midi_register_card(midi); if (status < 0) goto fail_register; -- cgit v1.2.3 From 2979ee7a91127a2d6fddd4e98d450ee782115772 Mon Sep 17 00:00:00 2001 From: Amelie Delaunay Date: Tue, 5 Jan 2021 10:48:53 +0100 Subject: usb: dwc2: set ahbcfg parameter for STM32MP15 OTG HS and FS STM32MP15 ahbcfg register default value sets Burst length/type (HBSTLEN) to Single (32-bit accesses on AHB), which is not recommended, according to STM32MP157 Reference manual [1]. This patch sets Burst length/type (HBSTLEN) so that bus transactions target 16x32 bit accesses. This improves OTG controller performance. [1] https://www.st.com/resource/en/reference_manual/dm00327659.pdf, p.3149 Acked-by: Minas Harutyunyan Signed-off-by: Amelie Delaunay Link: https://lore.kernel.org/r/20210105094855.30763-2-amelie.delaunay@foss.st.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/params.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c index 267543c3dc38..0df693319f0a 100644 --- a/drivers/usb/dwc2/params.c +++ b/drivers/usb/dwc2/params.c @@ -177,6 +177,7 @@ static void dwc2_set_stm32mp15_fsotg_params(struct dwc2_hsotg *hsotg) p->i2c_enable = false; p->activate_stm_fs_transceiver = true; p->activate_stm_id_vb_detection = true; + p->ahbcfg = GAHBCFG_HBSTLEN_INCR16 << GAHBCFG_HBSTLEN_SHIFT; p->power_down = DWC2_POWER_DOWN_PARAM_NONE; } @@ -189,6 +190,7 @@ static void dwc2_set_stm32mp15_hsotg_params(struct dwc2_hsotg *hsotg) p->host_rx_fifo_size = 440; p->host_nperio_tx_fifo_size = 256; p->host_perio_tx_fifo_size = 256; + p->ahbcfg = GAHBCFG_HBSTLEN_INCR16 << GAHBCFG_HBSTLEN_SHIFT; p->power_down = DWC2_POWER_DOWN_PARAM_NONE; } -- cgit v1.2.3 From f228cb27c56163dcb55dfb62e176a08f45020faf Mon Sep 17 00:00:00 2001 From: Amelie Delaunay Date: Tue, 5 Jan 2021 10:48:54 +0100 Subject: usb: dwc2: enable FS/LS PHY clock select on STM32MP15 FS OTG When the core is in FS host mode, using the FS transceiver, and a Low-Speed device is connected, transceiver clock is 6Mhz. So, to support Low-Speed devices, enable support of FS/LS Low Power mode, so that the PHY supplies a 6 MHz clock during Low-Speed mode. Acked-by: Minas Harutyunyan Signed-off-by: Amelie Delaunay Link: https://lore.kernel.org/r/20210105094855.30763-3-amelie.delaunay@foss.st.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/params.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c index 0df693319f0a..9e5dd7f3f2f6 100644 --- a/drivers/usb/dwc2/params.c +++ b/drivers/usb/dwc2/params.c @@ -179,6 +179,8 @@ static void dwc2_set_stm32mp15_fsotg_params(struct dwc2_hsotg *hsotg) p->activate_stm_id_vb_detection = true; p->ahbcfg = GAHBCFG_HBSTLEN_INCR16 << GAHBCFG_HBSTLEN_SHIFT; p->power_down = DWC2_POWER_DOWN_PARAM_NONE; + p->host_support_fs_ls_low_power = true; + p->host_ls_low_power_phy_clk = true; } static void dwc2_set_stm32mp15_hsotg_params(struct dwc2_hsotg *hsotg) -- cgit v1.2.3 From 53febc9569008859c8bcdfead2c7ce46451466b0 Mon Sep 17 00:00:00 2001 From: Amelie Delaunay Date: Tue, 5 Jan 2021 10:48:55 +0100 Subject: usb: dwc2: disable Link Power Management on STM32MP15 HS OTG Link Power Management (LPM) on STM32MP15 OTG HS encounters instabilities with some Host controllers. OTG core fails to exit L1 state in 200us: "dwc2 49000000.usb-otg: Failed to exit L1 sleep state in 200us." Then the device is still not enumerated. To avoid this issue, disable Link Power Management on STM32MP15 HS OTG. Acked-by: Minas Harutyunyan Signed-off-by: Amelie Delaunay Link: https://lore.kernel.org/r/20210105094855.30763-4-amelie.delaunay@foss.st.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/params.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c index 9e5dd7f3f2f6..92df3d620f7d 100644 --- a/drivers/usb/dwc2/params.c +++ b/drivers/usb/dwc2/params.c @@ -194,6 +194,10 @@ static void dwc2_set_stm32mp15_hsotg_params(struct dwc2_hsotg *hsotg) p->host_perio_tx_fifo_size = 256; p->ahbcfg = GAHBCFG_HBSTLEN_INCR16 << GAHBCFG_HBSTLEN_SHIFT; p->power_down = DWC2_POWER_DOWN_PARAM_NONE; + p->lpm = false; + p->lpm_clock_gating = false; + p->besl = false; + p->hird_threshold_en = false; } const struct of_device_id dwc2_of_match_table[] = { -- cgit v1.2.3 From 89795852c9c46b9b0701f7376d30a1c5ab4d146c Mon Sep 17 00:00:00 2001 From: Mayank Rana Date: Mon, 11 Jan 2021 13:55:20 -0800 Subject: usb: typec: ucsi: Add support for USB role switch UCSI already conveys the information about a port's connection status, whether it is operating in UFP or DFP mode, and whether the partner supports USB data or not. This information can be used to notify a dual-role controller to start up its host or peripheral mode accordingly. Add optional support for this by querying each port's fwnode to look for an associated USB role switch device. If present, call usb_role_switch_set() with the determined data role upon Connect Change or Connector Partner Change updates. Reviewed-by: Heikki Krogerus Signed-off-by: Mayank Rana Signed-off-by: Jack Pham Link: https://lore.kernel.org/r/20210111215520.18476-1-jackp@codeaurora.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 55 ++++++++++++++++++++++++++++++++++++++++--- drivers/usb/typec/ucsi/ucsi.h | 3 +++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index f02958927cbd..ca3f4194ad90 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -588,6 +588,7 @@ static void ucsi_unregister_partner(struct ucsi_connector *con) static void ucsi_partner_change(struct ucsi_connector *con) { + enum usb_role u_role = USB_ROLE_NONE; int ret; if (!con->partner) @@ -595,11 +596,14 @@ static void ucsi_partner_change(struct ucsi_connector *con) switch (UCSI_CONSTAT_PARTNER_TYPE(con->status.flags)) { case UCSI_CONSTAT_PARTNER_TYPE_UFP: - case UCSI_CONSTAT_PARTNER_TYPE_CABLE: case UCSI_CONSTAT_PARTNER_TYPE_CABLE_AND_UFP: + u_role = USB_ROLE_HOST; + fallthrough; + case UCSI_CONSTAT_PARTNER_TYPE_CABLE: typec_set_data_role(con->port, TYPEC_HOST); break; case UCSI_CONSTAT_PARTNER_TYPE_DFP: + u_role = USB_ROLE_DEVICE; typec_set_data_role(con->port, TYPEC_DEVICE); break; default: @@ -610,6 +614,15 @@ static void ucsi_partner_change(struct ucsi_connector *con) if (!completion_done(&con->complete)) complete(&con->complete); + /* Only notify USB controller if partner supports USB data */ + if (!(UCSI_CONSTAT_PARTNER_FLAGS(con->status.flags) & UCSI_CONSTAT_PARTNER_FLAG_USB)) + u_role = USB_ROLE_NONE; + + ret = usb_role_switch_set_role(con->usb_role_sw, u_role); + if (ret) + dev_err(con->ucsi->dev, "con:%d: failed to set usb role:%d\n", + con->num, u_role); + /* Can't rely on Partner Flags field. Always checking the alt modes. */ ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_SOP); if (ret) @@ -628,6 +641,7 @@ static void ucsi_handle_connector_change(struct work_struct *work) struct ucsi_connector_status pre_ack_status; struct ucsi_connector_status post_ack_status; enum typec_role role; + enum usb_role u_role = USB_ROLE_NONE; u16 inferred_changes; u16 changed_flags; u64 command; @@ -753,11 +767,14 @@ static void ucsi_handle_connector_change(struct work_struct *work) switch (UCSI_CONSTAT_PARTNER_TYPE(con->status.flags)) { case UCSI_CONSTAT_PARTNER_TYPE_UFP: - case UCSI_CONSTAT_PARTNER_TYPE_CABLE: case UCSI_CONSTAT_PARTNER_TYPE_CABLE_AND_UFP: + u_role = USB_ROLE_HOST; + fallthrough; + case UCSI_CONSTAT_PARTNER_TYPE_CABLE: typec_set_data_role(con->port, TYPEC_HOST); break; case UCSI_CONSTAT_PARTNER_TYPE_DFP: + u_role = USB_ROLE_DEVICE; typec_set_data_role(con->port, TYPEC_DEVICE); break; default: @@ -770,6 +787,16 @@ static void ucsi_handle_connector_change(struct work_struct *work) ucsi_unregister_partner(con); ucsi_port_psy_changed(con); + + /* Only notify USB controller if partner supports USB data */ + if (!(UCSI_CONSTAT_PARTNER_FLAGS(con->status.flags) & + UCSI_CONSTAT_PARTNER_FLAG_USB)) + u_role = USB_ROLE_NONE; + + ret = usb_role_switch_set_role(con->usb_role_sw, u_role); + if (ret) + dev_err(ucsi->dev, "con:%d: failed to set usb role:%d\n", + con->num, u_role); } if (con->status.change & UCSI_CONSTAT_PARTNER_CHANGE) @@ -988,6 +1015,7 @@ static int ucsi_register_port(struct ucsi *ucsi, int index) struct ucsi_connector *con = &ucsi->connector[index]; struct typec_capability *cap = &con->typec_cap; enum typec_accessory *accessory = cap->accessory; + enum usb_role u_role = USB_ROLE_NONE; u64 command; int ret; @@ -1066,11 +1094,14 @@ static int ucsi_register_port(struct ucsi *ucsi, int index) switch (UCSI_CONSTAT_PARTNER_TYPE(con->status.flags)) { case UCSI_CONSTAT_PARTNER_TYPE_UFP: - case UCSI_CONSTAT_PARTNER_TYPE_CABLE: case UCSI_CONSTAT_PARTNER_TYPE_CABLE_AND_UFP: + u_role = USB_ROLE_HOST; + fallthrough; + case UCSI_CONSTAT_PARTNER_TYPE_CABLE: typec_set_data_role(con->port, TYPEC_HOST); break; case UCSI_CONSTAT_PARTNER_TYPE_DFP: + u_role = USB_ROLE_DEVICE; typec_set_data_role(con->port, TYPEC_DEVICE); break; default: @@ -1086,6 +1117,24 @@ static int ucsi_register_port(struct ucsi *ucsi, int index) ucsi_port_psy_changed(con); } + con->usb_role_sw = fwnode_usb_role_switch_get(cap->fwnode); + if (IS_ERR(con->usb_role_sw)) { + dev_err(ucsi->dev, "con%d: failed to get usb role switch\n", + con->num); + con->usb_role_sw = NULL; + } + + /* Only notify USB controller if partner supports USB data */ + if (!(UCSI_CONSTAT_PARTNER_FLAGS(con->status.flags) & UCSI_CONSTAT_PARTNER_FLAG_USB)) + u_role = USB_ROLE_NONE; + + ret = usb_role_switch_set_role(con->usb_role_sw, u_role); + if (ret) { + dev_err(ucsi->dev, "con:%d: failed to set usb role:%d\n", + con->num, u_role); + ret = 0; + } + if (con->partner) { ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_SOP); if (ret) { diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h index dd9ba60ab4a3..3920e20a9e9e 100644 --- a/drivers/usb/typec/ucsi/ucsi.h +++ b/drivers/usb/typec/ucsi/ucsi.h @@ -8,6 +8,7 @@ #include #include #include +#include /* -------------------------------------------------------------------------- */ @@ -331,6 +332,8 @@ struct ucsi_connector { u32 rdo; u32 src_pdos[UCSI_MAX_PDOS]; int num_pdos; + + struct usb_role_switch *usb_role_sw; }; int ucsi_send_command(struct ucsi *ucsi, u64 command, -- cgit v1.2.3 From 3aaefb76c50f71b83cc82dccadf86c432a1316ec Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Wed, 13 Jan 2021 10:42:18 +0800 Subject: usb: gadget: bdc: fix improper SPDX comment style for header file For C header files Documentation/process/license-rules.rst mandates C-like comments (opposed to C source files where C++ style should be used). Cc: Florian Fainelli Acked-by: Florian Fainelli Acked-by: Felipe Balbi Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1610505748-30616-1-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/bdc/bdc.h | 2 +- drivers/usb/gadget/udc/bdc/bdc_cmd.h | 2 +- drivers/usb/gadget/udc/bdc/bdc_dbg.h | 2 +- drivers/usb/gadget/udc/bdc/bdc_ep.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/gadget/udc/bdc/bdc.h b/drivers/usb/gadget/udc/bdc/bdc.h index ac75e25c3b6a..fcba77e42fd1 100644 --- a/drivers/usb/gadget/udc/bdc/bdc.h +++ b/drivers/usb/gadget/udc/bdc/bdc.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0+ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * bdc.h - header for the BRCM BDC USB3.0 device controller * diff --git a/drivers/usb/gadget/udc/bdc/bdc_cmd.h b/drivers/usb/gadget/udc/bdc/bdc_cmd.h index 29cc988a671a..373e674809e9 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_cmd.h +++ b/drivers/usb/gadget/udc/bdc/bdc_cmd.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0+ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * bdc_cmd.h - header for the BDC debug functions * diff --git a/drivers/usb/gadget/udc/bdc/bdc_dbg.h b/drivers/usb/gadget/udc/bdc/bdc_dbg.h index 373d5abffbb8..859d588e209d 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_dbg.h +++ b/drivers/usb/gadget/udc/bdc/bdc_dbg.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0+ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * bdc_dbg.h - header for the BDC debug functions * diff --git a/drivers/usb/gadget/udc/bdc/bdc_ep.h b/drivers/usb/gadget/udc/bdc/bdc_ep.h index a37ff8033b4f..5bbd73f99c6f 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_ep.h +++ b/drivers/usb/gadget/udc/bdc/bdc_ep.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0+ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * bdc_ep.h - header for the BDC debug functions * -- cgit v1.2.3 From e634ae98b807dd591a6c904c293df4285752f0a4 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Wed, 13 Jan 2021 10:42:19 +0800 Subject: usb: gadget: bdc: remove bdc_ep_set_halt() declaration No definition for bdc_ep_set_halt(), so remove it. Cc: Florian Fainelli Acked-by: Florian Fainelli Acked-by: Felipe Balbi Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1610505748-30616-2-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/bdc/bdc_cmd.h | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/usb/gadget/udc/bdc/bdc_cmd.h b/drivers/usb/gadget/udc/bdc/bdc_cmd.h index 373e674809e9..a3a6dbd0022c 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_cmd.h +++ b/drivers/usb/gadget/udc/bdc/bdc_cmd.h @@ -16,7 +16,6 @@ int bdc_dconfig_ep(struct bdc *, struct bdc_ep *); int bdc_stop_ep(struct bdc *, int); int bdc_ep_set_stall(struct bdc *, int); int bdc_ep_clear_stall(struct bdc *, int); -int bdc_ep_set_halt(struct bdc_ep *, u32 , int); int bdc_ep_bla(struct bdc *, struct bdc_ep *, dma_addr_t); int bdc_function_wake(struct bdc*, u8); int bdc_function_wake_fh(struct bdc*, u8); -- cgit v1.2.3 From 1c6e01bbe7712f81c2f81e7da8c08d0147b092be Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Wed, 13 Jan 2021 10:42:20 +0800 Subject: usb: gadget: bdc: prefer pointer dereference to pointer type Prefer kzalloc(sizeof(*bd_table)...) over kzalloc(sizeof(struct bd_table) Cc: Florian Fainelli Acked-by: Florian Fainelli Acked-by: Felipe Balbi Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1610505748-30616-3-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/bdc/bdc_ep.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/udc/bdc/bdc_ep.c b/drivers/usb/gadget/udc/bdc/bdc_ep.c index fafdc9fdb4a5..76463de75595 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_ep.c +++ b/drivers/usb/gadget/udc/bdc/bdc_ep.c @@ -147,7 +147,7 @@ static int ep_bd_list_alloc(struct bdc_ep *ep) /* Allocate memory for each table */ for (index = 0; index < num_tabs; index++) { /* Allocate memory for bd_table structure */ - bd_table = kzalloc(sizeof(struct bd_table), GFP_ATOMIC); + bd_table = kzalloc(sizeof(*bd_table), GFP_ATOMIC); if (!bd_table) goto fail; -- cgit v1.2.3 From ddafe4b9c6bd4ff5e88dc60876ab7f9ff1aa909c Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Wed, 13 Jan 2021 10:42:21 +0800 Subject: usb: gadget: bdc: fix warning of embedded function name Use '"%s...", __func__' to replace embedded function name Cc: Florian Fainelli Acked-by: Florian Fainelli Acked-by: Felipe Balbi Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1610505748-30616-4-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/bdc/bdc_dbg.c | 2 +- drivers/usb/gadget/udc/bdc/bdc_ep.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/gadget/udc/bdc/bdc_dbg.c b/drivers/usb/gadget/udc/bdc/bdc_dbg.c index 7ba7448ad743..9c03e13308ca 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_dbg.c +++ b/drivers/usb/gadget/udc/bdc/bdc_dbg.c @@ -68,7 +68,7 @@ void bdc_dbg_srr(struct bdc *bdc, u32 srr_num) sr = bdc->srr.sr_bds; addr = bdc->srr.dma_addr; - dev_vdbg(bdc->dev, "bdc_dbg_srr sr:%p dqp_index:%d\n", + dev_vdbg(bdc->dev, "%s sr:%p dqp_index:%d\n", __func__, sr, bdc->srr.dqp_index); for (i = 0; i < NUM_SR_ENTRIES; i++) { sr = &bdc->srr.sr_bds[i]; diff --git a/drivers/usb/gadget/udc/bdc/bdc_ep.c b/drivers/usb/gadget/udc/bdc/bdc_ep.c index 76463de75595..44f3a122546d 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_ep.c +++ b/drivers/usb/gadget/udc/bdc/bdc_ep.c @@ -756,7 +756,7 @@ static int ep_dequeue(struct bdc_ep *ep, struct bdc_req *req) dev_dbg(bdc->dev, "%s ep:%s start:%d end:%d\n", __func__, ep->name, start_bdi, end_bdi); - dev_dbg(bdc->dev, "ep_dequeue ep=%p ep->desc=%p\n", + dev_dbg(bdc->dev, "%s ep=%p ep->desc=%p\n", __func__, ep, (void *)ep->usb_ep.desc); /* if still connected, stop the ep to see where the HW is ? */ if (!(bdc_readl(bdc->regs, BDC_USPC) & BDC_PST_MASK)) { @@ -1858,12 +1858,12 @@ static int bdc_gadget_ep_enable(struct usb_ep *_ep, int ret; if (!_ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT) { - pr_debug("bdc_gadget_ep_enable invalid parameters\n"); + pr_debug("%s invalid parameters\n", __func__); return -EINVAL; } if (!desc->wMaxPacketSize) { - pr_debug("bdc_gadget_ep_enable missing wMaxPacketSize\n"); + pr_debug("%s missing wMaxPacketSize\n", __func__); return -EINVAL; } -- cgit v1.2.3 From feed6252a526d492f04613931486e6d4cd4bae3d Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Wed, 13 Jan 2021 10:42:22 +0800 Subject: usb: gadget: bdc: fix check warning of block comments alignment fix the warning: WARNING:BLOCK_COMMENT_STYLE: Block comments should align the * on each line Cc: Florian Fainelli Acked-by: Florian Fainelli Acked-by: Felipe Balbi Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1610505748-30616-5-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/bdc/bdc.h | 2 +- drivers/usb/gadget/udc/bdc/bdc_ep.c | 2 +- drivers/usb/gadget/udc/bdc/bdc_udc.c | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/usb/gadget/udc/bdc/bdc.h b/drivers/usb/gadget/udc/bdc/bdc.h index fcba77e42fd1..c0ee735bd887 100644 --- a/drivers/usb/gadget/udc/bdc/bdc.h +++ b/drivers/usb/gadget/udc/bdc/bdc.h @@ -35,7 +35,7 @@ /* * Maximum size of ep0 response buffer for ch9 requests, * the set_sel request uses 6 so far, the max. -*/ + */ #define EP0_RESPONSE_BUFF 6 /* Start with SS as default */ #define EP0_MAX_PKT_SIZE 512 diff --git a/drivers/usb/gadget/udc/bdc/bdc_ep.c b/drivers/usb/gadget/udc/bdc/bdc_ep.c index 44f3a122546d..3fb36c8454fc 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_ep.c +++ b/drivers/usb/gadget/udc/bdc/bdc_ep.c @@ -68,7 +68,7 @@ static void ep_bd_list_free(struct bdc_ep *ep, u32 num_tabs) * check if the bd_table struct is allocated ? * if yes, then check if bd memory has been allocated, then * free the dma_pool and also the bd_table struct memory - */ + */ bd_table = bd_list->bd_table_array[index]; dev_dbg(bdc->dev, "bd_table:%p index:%d\n", bd_table, index); if (!bd_table) { diff --git a/drivers/usb/gadget/udc/bdc/bdc_udc.c b/drivers/usb/gadget/udc/bdc/bdc_udc.c index 248426a3e88a..0c1ab9548786 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_udc.c +++ b/drivers/usb/gadget/udc/bdc/bdc_udc.c @@ -164,7 +164,7 @@ static void bdc_func_wake_timer(struct work_struct *work) /* * Check if host has started transferring on endpoints * FUNC_WAKE_ISSUED is cleared when transfer has started after resume - */ + */ if (bdc->devstatus & FUNC_WAKE_ISSUED) { dev_dbg(bdc->dev, "FUNC_WAKE_ISSUED FLAG IS STILL SET\n"); /* flag is still set, so again send func wake */ @@ -205,7 +205,7 @@ static void handle_link_state_change(struct bdc *bdc, u32 uspc) * if not then send function wake again every * TNotification secs until host initiates * transfer to BDC, USB3 spec Table 8.13 - */ + */ schedule_delayed_work( &bdc->func_wake_notify, msecs_to_jiffies(BDC_TNOTIFY)); @@ -379,7 +379,7 @@ static int bdc_udc_start(struct usb_gadget *gadget, * Run the controller from here and when BDC is connected to * Host then driver will receive a USPC SR with VBUS present * and then driver will do a softconnect. - */ + */ ret = bdc_run(bdc); if (ret) { dev_err(bdc->dev, "%s bdc run fail\n", __func__); -- cgit v1.2.3 From 5ae5f76a4535555c73b0c31557db0b1468116cb9 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Wed, 13 Jan 2021 10:42:23 +0800 Subject: usb: gadget: bdc: add identifier name for function declaraion This is used to avoid the warning of function arguments, e.g. WARNING:FUNCTION_ARGUMENTS: function definition argument 'u32' should also have an identifier name Cc: Florian Fainelli Acked-by: Florian Fainelli Acked-by: Felipe Balbi Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1610505748-30616-6-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/bdc/bdc.h | 28 ++++++++++++++-------------- drivers/usb/gadget/udc/bdc/bdc_cmd.h | 18 +++++++++--------- drivers/usb/gadget/udc/bdc/bdc_dbg.h | 8 ++++---- drivers/usb/gadget/udc/bdc/bdc_ep.h | 8 ++++---- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/drivers/usb/gadget/udc/bdc/bdc.h b/drivers/usb/gadget/udc/bdc/bdc.h index c0ee735bd887..658abeff59d4 100644 --- a/drivers/usb/gadget/udc/bdc/bdc.h +++ b/drivers/usb/gadget/udc/bdc/bdc.h @@ -466,24 +466,24 @@ static inline void bdc_writel(void __iomem *base, u32 offset, u32 value) } /* Buffer descriptor list operations */ -void bdc_notify_xfr(struct bdc *, u32); -void bdc_softconn(struct bdc *); -void bdc_softdisconn(struct bdc *); -int bdc_run(struct bdc *); -int bdc_stop(struct bdc *); -int bdc_reset(struct bdc *); -int bdc_udc_init(struct bdc *); -void bdc_udc_exit(struct bdc *); -int bdc_reinit(struct bdc *); +void bdc_notify_xfr(struct bdc *bdc, u32 epnum); +void bdc_softconn(struct bdc *bdc); +void bdc_softdisconn(struct bdc *bdc); +int bdc_run(struct bdc *bdc); +int bdc_stop(struct bdc *bdc); +int bdc_reset(struct bdc *bdc); +int bdc_udc_init(struct bdc *bdc); +void bdc_udc_exit(struct bdc *bdc); +int bdc_reinit(struct bdc *bdc); /* Status report handlers */ /* Upstream port status change sr */ -void bdc_sr_uspc(struct bdc *, struct bdc_sr *); +void bdc_sr_uspc(struct bdc *bdc, struct bdc_sr *sreport); /* transfer sr */ -void bdc_sr_xsf(struct bdc *, struct bdc_sr *); +void bdc_sr_xsf(struct bdc *bdc, struct bdc_sr *sreport); /* EP0 XSF handlers */ -void bdc_xsf_ep0_setup_recv(struct bdc *, struct bdc_sr *); -void bdc_xsf_ep0_data_start(struct bdc *, struct bdc_sr *); -void bdc_xsf_ep0_status_start(struct bdc *, struct bdc_sr *); +void bdc_xsf_ep0_setup_recv(struct bdc *bdc, struct bdc_sr *sreport); +void bdc_xsf_ep0_data_start(struct bdc *bdc, struct bdc_sr *sreport); +void bdc_xsf_ep0_status_start(struct bdc *bdc, struct bdc_sr *sreport); #endif /* __LINUX_BDC_H__ */ diff --git a/drivers/usb/gadget/udc/bdc/bdc_cmd.h b/drivers/usb/gadget/udc/bdc/bdc_cmd.h index a3a6dbd0022c..533ad52fa6d0 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_cmd.h +++ b/drivers/usb/gadget/udc/bdc/bdc_cmd.h @@ -10,14 +10,14 @@ #define __LINUX_BDC_CMD_H__ /* Command operations */ -int bdc_address_device(struct bdc *, u32); -int bdc_config_ep(struct bdc *, struct bdc_ep *); -int bdc_dconfig_ep(struct bdc *, struct bdc_ep *); -int bdc_stop_ep(struct bdc *, int); -int bdc_ep_set_stall(struct bdc *, int); -int bdc_ep_clear_stall(struct bdc *, int); -int bdc_ep_bla(struct bdc *, struct bdc_ep *, dma_addr_t); -int bdc_function_wake(struct bdc*, u8); -int bdc_function_wake_fh(struct bdc*, u8); +int bdc_address_device(struct bdc *bdc, u32 add); +int bdc_config_ep(struct bdc *bdc, struct bdc_ep *ep); +int bdc_dconfig_ep(struct bdc *bdc, struct bdc_ep *ep); +int bdc_stop_ep(struct bdc *bdc, int epnum); +int bdc_ep_set_stall(struct bdc *bdc, int epnum); +int bdc_ep_clear_stall(struct bdc *bdc, int epnum); +int bdc_ep_bla(struct bdc *bdc, struct bdc_ep *ep, dma_addr_t dma_addr); +int bdc_function_wake(struct bdc *bdc, u8 intf); +int bdc_function_wake_fh(struct bdc *bdc, u8 intf); #endif /* __LINUX_BDC_CMD_H__ */ diff --git a/drivers/usb/gadget/udc/bdc/bdc_dbg.h b/drivers/usb/gadget/udc/bdc/bdc_dbg.h index 859d588e209d..acd8332f8584 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_dbg.h +++ b/drivers/usb/gadget/udc/bdc/bdc_dbg.h @@ -12,10 +12,10 @@ #include "bdc.h" #ifdef CONFIG_USB_GADGET_VERBOSE -void bdc_dbg_bd_list(struct bdc *, struct bdc_ep*); -void bdc_dbg_srr(struct bdc *, u32); -void bdc_dbg_regs(struct bdc *); -void bdc_dump_epsts(struct bdc *); +void bdc_dbg_bd_list(struct bdc *bdc, struct bdc_ep *ep); +void bdc_dbg_srr(struct bdc *bdc, u32 srr_num); +void bdc_dbg_regs(struct bdc *bdc); +void bdc_dump_epsts(struct bdc *bdc); #else static inline void bdc_dbg_regs(struct bdc *bdc) { } diff --git a/drivers/usb/gadget/udc/bdc/bdc_ep.h b/drivers/usb/gadget/udc/bdc/bdc_ep.h index 5bbd73f99c6f..4d3affd07a65 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_ep.h +++ b/drivers/usb/gadget/udc/bdc/bdc_ep.h @@ -9,9 +9,9 @@ #ifndef __LINUX_BDC_EP_H__ #define __LINUX_BDC_EP_H__ -int bdc_init_ep(struct bdc *); -int bdc_ep_disable(struct bdc_ep *); -int bdc_ep_enable(struct bdc_ep *); -void bdc_free_ep(struct bdc *); +int bdc_init_ep(struct bdc *bdc); +int bdc_ep_disable(struct bdc_ep *ep); +int bdc_ep_enable(struct bdc_ep *ep); +void bdc_free_ep(struct bdc *bdc); #endif /* __LINUX_BDC_EP_H__ */ -- cgit v1.2.3 From 913a4a7d13df8ec3104f442de2d5dcbaef14e90f Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Wed, 13 Jan 2021 10:42:24 +0800 Subject: usb: gadget: bdc: avoid precedence issues Add () around macro argument to avoid precedence issues Cc: Florian Fainelli Acked-by: Florian Fainelli Acked-by: Felipe Balbi Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1610505748-30616-7-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/bdc/bdc.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/usb/gadget/udc/bdc/bdc.h b/drivers/usb/gadget/udc/bdc/bdc.h index 658abeff59d4..f8d595804258 100644 --- a/drivers/usb/gadget/udc/bdc/bdc.h +++ b/drivers/usb/gadget/udc/bdc/bdc.h @@ -86,20 +86,20 @@ #define BDC_EPSTS5 0x74 #define BDC_EPSTS6 0x78 #define BDC_EPSTS7 0x7c -#define BDC_SRRBAL(n) (0x200 + (n * 0x10)) -#define BDC_SRRBAH(n) (0x204 + (n * 0x10)) -#define BDC_SRRINT(n) (0x208 + (n * 0x10)) -#define BDC_INTCTLS(n) (0x20c + (n * 0x10)) +#define BDC_SRRBAL(n) (0x200 + ((n) * 0x10)) +#define BDC_SRRBAH(n) (0x204 + ((n) * 0x10)) +#define BDC_SRRINT(n) (0x208 + ((n) * 0x10)) +#define BDC_INTCTLS(n) (0x20c + ((n) * 0x10)) /* Extended capability regs */ #define BDC_FSCNOC 0xcd4 #define BDC_FSCNIC 0xce4 -#define NUM_NCS(p) (p >> 28) +#define NUM_NCS(p) ((p) >> 28) /* Register bit fields and Masks */ /* BDC Configuration 0 */ #define BDC_PGS(p) (((p) & (0x7 << 8)) >> 8) -#define BDC_SPB(p) (p & 0x7) +#define BDC_SPB(p) ((p) & 0x7) /* BDC Capability1 */ #define BDC_P64 (1 << 0) @@ -113,7 +113,7 @@ #define BDC_CMD_DVC 0x1 #define BDC_CMD_CWS (0x1 << 5) #define BDC_CMD_CST(p) (((p) & (0xf << 6))>>6) -#define BDC_CMD_EPN(p) ((p & 0x1f) << 10) +#define BDC_CMD_EPN(p) (((p) & 0x1f) << 10) #define BDC_SUB_CMD_ADD (0x1 << 17) #define BDC_SUB_CMD_FWK (0x4 << 17) /* Reset sequence number */ @@ -163,7 +163,7 @@ #define BDC_SPEED_HS 0x3 #define BDC_SPEED_SS 0x4 -#define BDC_PST(p) (p & 0xf) +#define BDC_PST(p) ((p) & 0xf) #define BDC_PST_MASK 0xf /* USPPMS */ @@ -228,7 +228,7 @@ /* status report defines */ #define SR_XSF 0 #define SR_USPC 4 -#define SR_BD_LEN(p) (p & 0xffffff) +#define SR_BD_LEN(p) ((p) & 0xffffff) #define XSF_SUCC 0x1 #define XSF_SHORT 0x3 -- cgit v1.2.3 From cb387becbf16ebac6dc50799f72974ba0ced5b4c Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Wed, 13 Jan 2021 10:42:25 +0800 Subject: usb: gadget: bdc: use the BIT macro to define bit filed Prefer using the BIT macro to define bit fileds Cc: Florian Fainelli Reported-by: kernel test robot Acked-by: Florian Fainelli Acked-by: Felipe Balbi Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1610505748-30616-8-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/bdc/bdc.h | 84 ++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/drivers/usb/gadget/udc/bdc/bdc.h b/drivers/usb/gadget/udc/bdc/bdc.h index f8d595804258..8d00b1239f21 100644 --- a/drivers/usb/gadget/udc/bdc/bdc.h +++ b/drivers/usb/gadget/udc/bdc/bdc.h @@ -102,7 +102,7 @@ #define BDC_SPB(p) ((p) & 0x7) /* BDC Capability1 */ -#define BDC_P64 (1 << 0) +#define BDC_P64 BIT(0) /* BDC Command register */ #define BDC_CMD_FH 0xe @@ -111,7 +111,7 @@ #define BDC_CMD_BLA 0x3 #define BDC_CMD_EPC 0x2 #define BDC_CMD_DVC 0x1 -#define BDC_CMD_CWS (0x1 << 5) +#define BDC_CMD_CWS BIT(5) #define BDC_CMD_CST(p) (((p) & (0xf << 6))>>6) #define BDC_CMD_EPN(p) (((p) & 0x1f) << 10) #define BDC_SUB_CMD_ADD (0x1 << 17) @@ -124,7 +124,7 @@ #define BDC_SUB_CMD_EP_STP (0x2 << 17) #define BDC_SUB_CMD_EP_STL (0x4 << 17) #define BDC_SUB_CMD_EP_RST (0x1 << 17) -#define BDC_CMD_SRD (1 << 27) +#define BDC_CMD_SRD BIT(27) /* CMD completion status */ #define BDC_CMDS_SUCC 0x1 @@ -141,19 +141,19 @@ #define EPM_SHIFT 4 /* BDC USPSC */ -#define BDC_VBC (1 << 31) -#define BDC_PRC (1 << 30) -#define BDC_PCE (1 << 29) -#define BDC_CFC (1 << 28) -#define BDC_PCC (1 << 27) -#define BDC_PSC (1 << 26) -#define BDC_VBS (1 << 25) -#define BDC_PRS (1 << 24) -#define BDC_PCS (1 << 23) +#define BDC_VBC BIT(31) +#define BDC_PRC BIT(30) +#define BDC_PCE BIT(29) +#define BDC_CFC BIT(28) +#define BDC_PCC BIT(27) +#define BDC_PSC BIT(26) +#define BDC_VBS BIT(25) +#define BDC_PRS BIT(24) +#define BDC_PCS BIT(23) #define BDC_PSP(p) (((p) & (0x7 << 20))>>20) -#define BDC_SCN (1 << 8) -#define BDC_SDC (1 << 7) -#define BDC_SWS (1 << 4) +#define BDC_SCN BIT(8) +#define BDC_SDC BIT(7) +#define BDC_SWS BIT(4) #define BDC_USPSC_RW (BDC_SCN|BDC_SDC|BDC_SWS|0xf) #define BDC_PSP(p) (((p) & (0x7 << 20))>>20) @@ -167,17 +167,17 @@ #define BDC_PST_MASK 0xf /* USPPMS */ -#define BDC_U2E (0x1 << 31) -#define BDC_U1E (0x1 << 30) -#define BDC_U2A (0x1 << 29) -#define BDC_PORT_W1S (0x1 << 17) +#define BDC_U2E BIT(31) +#define BDC_U1E BIT(30) +#define BDC_U2A BIT(29) +#define BDC_PORT_W1S BIT(17) #define BDC_U1T(p) ((p) & 0xff) #define BDC_U2T(p) (((p) & 0xff) << 8) #define BDC_U1T_MASK 0xff /* USBPM2 */ /* Hardware LPM Enable */ -#define BDC_HLE (1 << 16) +#define BDC_HLE BIT(16) /* BDC Status and Control */ #define BDC_COP_RST (1 << 29) @@ -186,11 +186,11 @@ #define BDC_COP_MASK (BDC_COP_RST|BDC_COP_RUN|BDC_COP_STP) -#define BDC_COS (1 << 28) +#define BDC_COS BIT(28) #define BDC_CSTS(p) (((p) & (0x7 << 20)) >> 20) -#define BDC_MASK_MCW (1 << 7) -#define BDC_GIE (1 << 1) -#define BDC_GIP (1 << 0) +#define BDC_MASK_MCW BIT(7) +#define BDC_GIE BIT(1) +#define BDC_GIP BIT(0) #define BDC_HLT 1 #define BDC_NOR 2 @@ -201,19 +201,19 @@ #define BD_CHAIN 0xf #define BD_TFS_SHIFT 4 -#define BD_SOT (1 << 26) -#define BD_EOT (1 << 27) -#define BD_ISP (1 << 29) -#define BD_IOC (1 << 30) -#define BD_SBF (1 << 31) +#define BD_SOT BIT(26) +#define BD_EOT BIT(27) +#define BD_ISP BIT(29) +#define BD_IOC BIT(30) +#define BD_SBF BIT(31) #define BD_INTR_TARGET(p) (((p) & 0x1f) << 27) -#define BDC_SRR_RWS (1 << 4) -#define BDC_SRR_RST (1 << 3) -#define BDC_SRR_ISR (1 << 2) -#define BDC_SRR_IE (1 << 1) -#define BDC_SRR_IP (1 << 0) +#define BDC_SRR_RWS BIT(4) +#define BDC_SRR_RST BIT(3) +#define BDC_SRR_ISR BIT(2) +#define BDC_SRR_IE BIT(1) +#define BDC_SRR_IP BIT(0) #define BDC_SRR_EPI(p) (((p) & (0xff << 24)) >> 24) #define BDC_SRR_DPI(p) (((p) & (0xff << 16)) >> 16) #define BDC_SRR_DPI_MASK 0x00ff0000 @@ -221,7 +221,7 @@ #define MARK_CHAIN_BD (BD_CHAIN|BD_EOT|BD_SOT) /* Control transfer BD specific fields */ -#define BD_DIR_IN (1 << 25) +#define BD_DIR_IN BIT(25) #define BDC_PTC_MASK 0xf0000000 @@ -241,13 +241,13 @@ /* Transfer BD fields */ #define BD_LEN(p) ((p) & 0x1ffff) -#define BD_LTF (1 << 25) +#define BD_LTF BIT(25) #define BD_TYPE_DS 0x1 #define BD_TYPE_SS 0x2 -#define BDC_EP_ENABLED (1 << 0) -#define BDC_EP_STALL (1 << 1) -#define BDC_EP_STOP (1 << 2) +#define BDC_EP_ENABLED BIT(0) +#define BDC_EP_STALL BIT(1) +#define BDC_EP_STOP BIT(2) /* One BD can transfer max 65536 bytes */ #define BD_MAX_BUFF_SIZE (1 << 16) @@ -266,9 +266,9 @@ /* FUNCTION WAKE DEV NOTIFICATION interval, USB3 spec table 8.13 */ #define BDC_TNOTIFY 2500 /*in ms*/ /* Devstatus bitfields */ -#define REMOTE_WAKEUP_ISSUED (1 << 16) -#define DEVICE_SUSPENDED (1 << 17) -#define FUNC_WAKE_ISSUED (1 << 18) +#define REMOTE_WAKEUP_ISSUED BIT(16) +#define DEVICE_SUSPENDED BIT(17) +#define FUNC_WAKE_ISSUED BIT(18) #define REMOTE_WAKE_ENABLE (1 << USB_DEVICE_REMOTE_WAKEUP) /* On disconnect, preserve these bits and clear rest */ -- cgit v1.2.3 From fc43a80f7f394fa20d98acffa89d6d63d74750d7 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Wed, 13 Jan 2021 10:42:26 +0800 Subject: usb: gadget: bdc: fix checkpatch.pl tab warning WARNING:SUSPECT_CODE_INDENT: suspect code indent for conditional statements WARNING:TABSTOP: Statements should start on a tabstop Cc: Florian Fainelli Acked-by: Florian Fainelli Acked-by: Felipe Balbi Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1610505748-30616-9-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/bdc/bdc_cmd.c | 2 +- drivers/usb/gadget/udc/bdc/bdc_ep.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/gadget/udc/bdc/bdc_cmd.c b/drivers/usb/gadget/udc/bdc/bdc_cmd.c index 44c2a5eef785..995f79c79f96 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_cmd.c +++ b/drivers/usb/gadget/udc/bdc/bdc_cmd.c @@ -163,7 +163,7 @@ int bdc_config_ep(struct bdc *bdc, struct bdc_ep *ep) usb_endpoint_xfer_isoc(desc)) { param2 |= si; if (usb_endpoint_xfer_isoc(desc) && comp_desc) - mul = comp_desc->bmAttributes; + mul = comp_desc->bmAttributes; } param2 |= mul << EPM_SHIFT; diff --git a/drivers/usb/gadget/udc/bdc/bdc_ep.c b/drivers/usb/gadget/udc/bdc/bdc_ep.c index 3fb36c8454fc..d227d2682dd8 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_ep.c +++ b/drivers/usb/gadget/udc/bdc/bdc_ep.c @@ -275,7 +275,7 @@ static inline int find_end_bdi(struct bdc_ep *ep, int next_hwd_bdi) end_bdi = next_hwd_bdi - 1; if (end_bdi < 0) end_bdi = ep->bd_list.max_bdi - 1; - else if ((end_bdi % (ep->bd_list.num_bds_table-1)) == 0) + else if ((end_bdi % (ep->bd_list.num_bds_table-1)) == 0) end_bdi--; return end_bdi; @@ -795,7 +795,7 @@ static int ep_dequeue(struct bdc_ep *ep, struct bdc_req *req) start_pending = true; end_pending = true; } else if (end_bdi >= curr_hw_dqpi || end_bdi <= eqp_bdi) { - end_pending = true; + end_pending = true; } } else { if (start_bdi >= curr_hw_dqpi) { -- cgit v1.2.3 From 14a46f82d74e9bd8ce03cdbfcdb9f8408c1fc205 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Wed, 13 Jan 2021 10:42:27 +0800 Subject: usb: gadget: bdc: fix checkpatch.pl spacing error fix checkpatch.pl error: ERROR:SPACING: space prohibited before that ',' Cc: Florian Fainelli Acked-by: Florian Fainelli Acked-by: Felipe Balbi Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1610505748-30616-10-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/bdc/bdc_udc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/udc/bdc/bdc_udc.c b/drivers/usb/gadget/udc/bdc/bdc_udc.c index 0c1ab9548786..5ac0ef88334e 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_udc.c +++ b/drivers/usb/gadget/udc/bdc/bdc_udc.c @@ -530,7 +530,7 @@ int bdc_udc_init(struct bdc *bdc) bdc->gadget.name = BRCM_BDC_NAME; ret = devm_request_irq(bdc->dev, bdc->irq, bdc_udc_interrupt, - IRQF_SHARED , BRCM_BDC_NAME, bdc); + IRQF_SHARED, BRCM_BDC_NAME, bdc); if (ret) { dev_err(bdc->dev, "failed to request irq #%d %d\n", -- cgit v1.2.3 From ba9fc77cbf00943b3ae100c4315980a74faf345a Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Wed, 13 Jan 2021 10:42:28 +0800 Subject: usb: gadget: bdc: fix checkpatch.pl repeated word warning fix the warning: WARNING:REPEATED_WORD: Possible repeated word: 'and' Cc: Florian Fainelli Acked-by: Florian Fainelli Acked-by: Felipe Balbi Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1610505748-30616-11-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/bdc/bdc_ep.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/udc/bdc/bdc_ep.c b/drivers/usb/gadget/udc/bdc/bdc_ep.c index d227d2682dd8..8e2f20b12519 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_ep.c +++ b/drivers/usb/gadget/udc/bdc/bdc_ep.c @@ -1405,7 +1405,7 @@ static int ep0_set_sel(struct bdc *bdc, } /* - * Queue a 0 byte bd only if wLength is more than the length and and length is + * Queue a 0 byte bd only if wLength is more than the length and length is * a multiple of MaxPacket then queue 0 byte BD */ static int ep0_queue_zlp(struct bdc *bdc) -- cgit v1.2.3 From b100402e93dcae0a88fe59d88d8114ce06d01f1e Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Fri, 18 Dec 2020 15:02:38 +0300 Subject: usb: phy: tegra: Add delay after power up The PHY hardware needs the delay of 2ms after power up, otherwise initial interrupt may be lost if USB controller is accessed before PHY is settled down. Previously this issue was masked by implicit delays, but now it pops up after squashing the older ehci-tegra driver into the ChipIdea driver. Tested-by: Matt Merhar Tested-by: Nicolas Chauvet Tested-by: Peter Geis Tested-by: Ion Agorria Acked-by: Thierry Reding Acked-by: Peter Chen Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20201218120246.7759-2-digetx@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/phy/phy-tegra-usb.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/phy/phy-tegra-usb.c b/drivers/usb/phy/phy-tegra-usb.c index 03a333797382..1296524e1bee 100644 --- a/drivers/usb/phy/phy-tegra-usb.c +++ b/drivers/usb/phy/phy-tegra-usb.c @@ -784,6 +784,9 @@ static int tegra_usb_phy_power_on(struct tegra_usb_phy *phy) phy->powered_on = true; + /* Let PHY settle down */ + usleep_range(2000, 2500); + return 0; } -- cgit v1.2.3 From 35192007d28d528aae74ddf5b26eaebf8e9a720c Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Fri, 18 Dec 2020 15:02:39 +0300 Subject: usb: phy: tegra: Support waking up from a low power mode Support programming of waking up from a low power mode by implementing the generic set_wakeup() callback of the USB PHY API. Tested-by: Matt Merhar Tested-by: Nicolas Chauvet Tested-by: Peter Geis Tested-by: Ion Agorria Acked-by: Thierry Reding Acked-by: Peter Chen Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20201218120246.7759-3-digetx@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/phy/phy-tegra-usb.c | 100 +++++++++++++++++++++++++++++++++----- include/linux/usb/tegra_usb_phy.h | 2 + 2 files changed, 91 insertions(+), 11 deletions(-) diff --git a/drivers/usb/phy/phy-tegra-usb.c b/drivers/usb/phy/phy-tegra-usb.c index 1296524e1bee..a48452a6172b 100644 --- a/drivers/usb/phy/phy-tegra-usb.c +++ b/drivers/usb/phy/phy-tegra-usb.c @@ -45,6 +45,7 @@ #define TEGRA_PORTSC1_RWC_BITS (PORT_CSC | PORT_PEC | PORT_OCC) #define USB_SUSP_CTRL 0x400 +#define USB_WAKE_ON_RESUME_EN BIT(2) #define USB_WAKE_ON_CNNT_EN_DEV BIT(3) #define USB_WAKE_ON_DISCON_EN_DEV BIT(4) #define USB_SUSP_CLR BIT(5) @@ -56,6 +57,15 @@ #define USB_SUSP_SET BIT(14) #define USB_WAKEUP_DEBOUNCE_COUNT(x) (((x) & 0x7) << 16) +#define USB_PHY_VBUS_SENSORS 0x404 +#define B_SESS_VLD_WAKEUP_EN BIT(6) +#define B_VBUS_VLD_WAKEUP_EN BIT(14) +#define A_SESS_VLD_WAKEUP_EN BIT(22) +#define A_VBUS_VLD_WAKEUP_EN BIT(30) + +#define USB_PHY_VBUS_WAKEUP_ID 0x408 +#define VBUS_WAKEUP_WAKEUP_EN BIT(30) + #define USB1_LEGACY_CTRL 0x410 #define USB1_NO_LEGACY_MODE BIT(0) #define USB1_VBUS_SENSE_CTL_MASK (3 << 1) @@ -334,6 +344,11 @@ static int utmip_pad_power_on(struct tegra_usb_phy *phy) writel_relaxed(val, base + UTMIP_BIAS_CFG0); } + if (phy->pad_wakeup) { + phy->pad_wakeup = false; + utmip_pad_count--; + } + spin_unlock(&utmip_pad_lock); clk_disable_unprepare(phy->pad_clk); @@ -359,6 +374,17 @@ static int utmip_pad_power_off(struct tegra_usb_phy *phy) goto ulock; } + /* + * In accordance to TRM, OTG and Bias pad circuits could be turned off + * to save power if wake is enabled, but the VBUS-change detection + * method is board-specific and these circuits may need to be enabled + * to generate wakeup event, hence we will just keep them both enabled. + */ + if (phy->wakeup_enabled) { + phy->pad_wakeup = true; + utmip_pad_count++; + } + if (--utmip_pad_count == 0) { val = readl_relaxed(base + UTMIP_BIAS_CFG0); val |= UTMIP_OTGPD | UTMIP_BIASPD; @@ -503,11 +529,24 @@ static int utmi_phy_power_on(struct tegra_usb_phy *phy) writel_relaxed(val, base + UTMIP_PLL_CFG1); } + val = readl_relaxed(base + USB_SUSP_CTRL); + val &= ~USB_WAKE_ON_RESUME_EN; + writel_relaxed(val, base + USB_SUSP_CTRL); + if (phy->mode == USB_DR_MODE_PERIPHERAL) { val = readl_relaxed(base + USB_SUSP_CTRL); val &= ~(USB_WAKE_ON_CNNT_EN_DEV | USB_WAKE_ON_DISCON_EN_DEV); writel_relaxed(val, base + USB_SUSP_CTRL); + val = readl_relaxed(base + USB_PHY_VBUS_WAKEUP_ID); + val &= ~VBUS_WAKEUP_WAKEUP_EN; + writel_relaxed(val, base + USB_PHY_VBUS_WAKEUP_ID); + + val = readl_relaxed(base + USB_PHY_VBUS_SENSORS); + val &= ~(A_VBUS_VLD_WAKEUP_EN | A_SESS_VLD_WAKEUP_EN); + val &= ~(B_VBUS_VLD_WAKEUP_EN | B_SESS_VLD_WAKEUP_EN); + writel_relaxed(val, base + USB_PHY_VBUS_SENSORS); + val = readl_relaxed(base + UTMIP_BAT_CHRG_CFG0); val &= ~UTMIP_PD_CHRG; writel_relaxed(val, base + UTMIP_BAT_CHRG_CFG0); @@ -605,31 +644,51 @@ static int utmi_phy_power_off(struct tegra_usb_phy *phy) utmi_phy_clk_disable(phy); - if (phy->mode == USB_DR_MODE_PERIPHERAL) { + /* PHY won't resume if reset is asserted */ + if (!phy->wakeup_enabled) { val = readl_relaxed(base + USB_SUSP_CTRL); - val &= ~USB_WAKEUP_DEBOUNCE_COUNT(~0); - val |= USB_WAKE_ON_CNNT_EN_DEV | USB_WAKEUP_DEBOUNCE_COUNT(5); + val |= UTMIP_RESET; writel_relaxed(val, base + USB_SUSP_CTRL); } - val = readl_relaxed(base + USB_SUSP_CTRL); - val |= UTMIP_RESET; - writel_relaxed(val, base + USB_SUSP_CTRL); - val = readl_relaxed(base + UTMIP_BAT_CHRG_CFG0); val |= UTMIP_PD_CHRG; writel_relaxed(val, base + UTMIP_BAT_CHRG_CFG0); - val = readl_relaxed(base + UTMIP_XCVR_CFG0); - val |= UTMIP_FORCE_PD_POWERDOWN | UTMIP_FORCE_PD2_POWERDOWN | - UTMIP_FORCE_PDZI_POWERDOWN; - writel_relaxed(val, base + UTMIP_XCVR_CFG0); + if (!phy->wakeup_enabled) { + val = readl_relaxed(base + UTMIP_XCVR_CFG0); + val |= UTMIP_FORCE_PD_POWERDOWN | UTMIP_FORCE_PD2_POWERDOWN | + UTMIP_FORCE_PDZI_POWERDOWN; + writel_relaxed(val, base + UTMIP_XCVR_CFG0); + } val = readl_relaxed(base + UTMIP_XCVR_CFG1); val |= UTMIP_FORCE_PDDISC_POWERDOWN | UTMIP_FORCE_PDCHRP_POWERDOWN | UTMIP_FORCE_PDDR_POWERDOWN; writel_relaxed(val, base + UTMIP_XCVR_CFG1); + if (phy->wakeup_enabled) { + val = readl_relaxed(base + USB_SUSP_CTRL); + val &= ~USB_WAKEUP_DEBOUNCE_COUNT(~0); + val |= USB_WAKEUP_DEBOUNCE_COUNT(5); + val |= USB_WAKE_ON_RESUME_EN; + writel_relaxed(val, base + USB_SUSP_CTRL); + + /* + * Ask VBUS sensor to generate wake event once cable is + * connected. + */ + if (phy->mode == USB_DR_MODE_PERIPHERAL) { + val = readl_relaxed(base + USB_PHY_VBUS_WAKEUP_ID); + val |= VBUS_WAKEUP_WAKEUP_EN; + writel_relaxed(val, base + USB_PHY_VBUS_WAKEUP_ID); + + val = readl_relaxed(base + USB_PHY_VBUS_SENSORS); + val |= A_VBUS_VLD_WAKEUP_EN; + writel_relaxed(val, base + USB_PHY_VBUS_SENSORS); + } + } + return utmip_pad_power_off(phy); } @@ -765,6 +824,15 @@ static int ulpi_phy_power_off(struct tegra_usb_phy *phy) usleep_range(5000, 6000); clk_disable_unprepare(phy->clk); + /* + * Wakeup currently unimplemented for ULPI, thus PHY needs to be + * force-resumed. + */ + if (WARN_ON_ONCE(phy->wakeup_enabled)) { + ulpi_phy_power_on(phy); + return -EOPNOTSUPP; + } + return 0; } @@ -827,6 +895,15 @@ static void tegra_usb_phy_shutdown(struct usb_phy *u_phy) phy->freq = NULL; } +static int tegra_usb_phy_set_wakeup(struct usb_phy *u_phy, bool enable) +{ + struct tegra_usb_phy *phy = to_tegra_usb_phy(u_phy); + + phy->wakeup_enabled = enable; + + return 0; +} + static int tegra_usb_phy_set_suspend(struct usb_phy *u_phy, int suspend) { struct tegra_usb_phy *phy = to_tegra_usb_phy(u_phy); @@ -1198,6 +1275,7 @@ static int tegra_usb_phy_probe(struct platform_device *pdev) tegra_phy->u_phy.dev = &pdev->dev; tegra_phy->u_phy.init = tegra_usb_phy_init; tegra_phy->u_phy.shutdown = tegra_usb_phy_shutdown; + tegra_phy->u_phy.set_wakeup = tegra_usb_phy_set_wakeup; tegra_phy->u_phy.set_suspend = tegra_usb_phy_set_suspend; platform_set_drvdata(pdev, tegra_phy); diff --git a/include/linux/usb/tegra_usb_phy.h b/include/linux/usb/tegra_usb_phy.h index c29d1b4c9381..fd1c9f6a4e37 100644 --- a/include/linux/usb/tegra_usb_phy.h +++ b/include/linux/usb/tegra_usb_phy.h @@ -79,6 +79,8 @@ struct tegra_usb_phy { bool is_ulpi_phy; struct gpio_desc *reset_gpio; struct reset_control *pad_rst; + bool wakeup_enabled; + bool pad_wakeup; bool powered_on; }; -- cgit v1.2.3 From c49f958b8d57a499df3f5a4a98835eec92b597db Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Fri, 18 Dec 2020 15:02:40 +0300 Subject: usb: chipidea: tegra: Remove MODULE_ALIAS The OF core adds an alias based on the OF device ID table, which is enough to have the driver autoloaded. The legacy MODULE_ALIAS macro was relevant to a pre-OF board files which manually created platform devices, this is irrelevant to the modern ARM kernels since devices are created by the OF core. Remove the unnecessary macro in order to keep the driver's code cleaner. Tested-by: Matt Merhar Tested-by: Nicolas Chauvet Tested-by: Peter Geis Tested-by: Ion Agorria Acked-by: Thierry Reding Acked-by: Peter Chen Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20201218120246.7759-4-digetx@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/ci_hdrc_tegra.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/usb/chipidea/ci_hdrc_tegra.c b/drivers/usb/chipidea/ci_hdrc_tegra.c index 7455df0ede49..10eaaba2a3f0 100644 --- a/drivers/usb/chipidea/ci_hdrc_tegra.c +++ b/drivers/usb/chipidea/ci_hdrc_tegra.c @@ -128,5 +128,4 @@ module_platform_driver(tegra_udc_driver); MODULE_DESCRIPTION("NVIDIA Tegra USB device mode driver"); MODULE_AUTHOR("Thierry Reding "); -MODULE_ALIAS("platform:tegra-udc"); MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 711e2344279179b5b81511e411b9b4b0ef1b9548 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Fri, 18 Dec 2020 15:02:41 +0300 Subject: usb: chipidea: tegra: Rename UDC to USB Rename all occurrences in the code from "udc" to "usb" and change the Kconfig entry in order to show that this driver supports USB modes other than device-only mode. The follow up patch will add host-mode support and it will be cleaner to perform the renaming separately, i.e. in this patch. Tested-by: Matt Merhar Tested-by: Nicolas Chauvet Tested-by: Peter Geis Tested-by: Ion Agorria Acked-by: Thierry Reding Acked-by: Peter Chen Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20201218120246.7759-5-digetx@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/Kconfig | 2 +- drivers/usb/chipidea/ci_hdrc_tegra.c | 78 ++++++++++++++++++------------------ 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig index 8bafcfc6080d..8685ead6ccc7 100644 --- a/drivers/usb/chipidea/Kconfig +++ b/drivers/usb/chipidea/Kconfig @@ -53,7 +53,7 @@ config USB_CHIPIDEA_GENERIC default USB_CHIPIDEA config USB_CHIPIDEA_TEGRA - tristate "Enable Tegra UDC glue driver" if EMBEDDED + tristate "Enable Tegra USB glue driver" if EMBEDDED depends on OF depends on USB_CHIPIDEA_UDC default USB_CHIPIDEA diff --git a/drivers/usb/chipidea/ci_hdrc_tegra.c b/drivers/usb/chipidea/ci_hdrc_tegra.c index 10eaaba2a3f0..d8efa80aa1c2 100644 --- a/drivers/usb/chipidea/ci_hdrc_tegra.c +++ b/drivers/usb/chipidea/ci_hdrc_tegra.c @@ -12,7 +12,7 @@ #include "ci.h" -struct tegra_udc { +struct tegra_usb { struct ci_hdrc_platform_data data; struct platform_device *dev; @@ -20,15 +20,15 @@ struct tegra_udc { struct clk *clk; }; -struct tegra_udc_soc_info { +struct tegra_usb_soc_info { unsigned long flags; }; -static const struct tegra_udc_soc_info tegra_udc_soc_info = { +static const struct tegra_usb_soc_info tegra_udc_soc_info = { .flags = CI_HDRC_REQUIRES_ALIGNED_DMA, }; -static const struct of_device_id tegra_udc_of_match[] = { +static const struct of_device_id tegra_usb_of_match[] = { { .compatible = "nvidia,tegra20-udc", .data = &tegra_udc_soc_info, @@ -45,16 +45,16 @@ static const struct of_device_id tegra_udc_of_match[] = { /* sentinel */ } }; -MODULE_DEVICE_TABLE(of, tegra_udc_of_match); +MODULE_DEVICE_TABLE(of, tegra_usb_of_match); -static int tegra_udc_probe(struct platform_device *pdev) +static int tegra_usb_probe(struct platform_device *pdev) { - const struct tegra_udc_soc_info *soc; - struct tegra_udc *udc; + const struct tegra_usb_soc_info *soc; + struct tegra_usb *usb; int err; - udc = devm_kzalloc(&pdev->dev, sizeof(*udc), GFP_KERNEL); - if (!udc) + usb = devm_kzalloc(&pdev->dev, sizeof(*usb), GFP_KERNEL); + if (!usb) return -ENOMEM; soc = of_device_get_match_data(&pdev->dev); @@ -63,69 +63,69 @@ static int tegra_udc_probe(struct platform_device *pdev) return -EINVAL; } - udc->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "nvidia,phy", 0); - if (IS_ERR(udc->phy)) { - err = PTR_ERR(udc->phy); + usb->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "nvidia,phy", 0); + if (IS_ERR(usb->phy)) { + err = PTR_ERR(usb->phy); dev_err(&pdev->dev, "failed to get PHY: %d\n", err); return err; } - udc->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(udc->clk)) { - err = PTR_ERR(udc->clk); + usb->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(usb->clk)) { + err = PTR_ERR(usb->clk); dev_err(&pdev->dev, "failed to get clock: %d\n", err); return err; } - err = clk_prepare_enable(udc->clk); + err = clk_prepare_enable(usb->clk); if (err < 0) { dev_err(&pdev->dev, "failed to enable clock: %d\n", err); return err; } /* setup and register ChipIdea HDRC device */ - udc->data.name = "tegra-udc"; - udc->data.flags = soc->flags; - udc->data.usb_phy = udc->phy; - udc->data.capoffset = DEF_CAPOFFSET; - - udc->dev = ci_hdrc_add_device(&pdev->dev, pdev->resource, - pdev->num_resources, &udc->data); - if (IS_ERR(udc->dev)) { - err = PTR_ERR(udc->dev); + usb->data.name = "tegra-usb"; + usb->data.flags = soc->flags; + usb->data.usb_phy = usb->phy; + usb->data.capoffset = DEF_CAPOFFSET; + + usb->dev = ci_hdrc_add_device(&pdev->dev, pdev->resource, + pdev->num_resources, &usb->data); + if (IS_ERR(usb->dev)) { + err = PTR_ERR(usb->dev); dev_err(&pdev->dev, "failed to add HDRC device: %d\n", err); goto fail_power_off; } - platform_set_drvdata(pdev, udc); + platform_set_drvdata(pdev, usb); return 0; fail_power_off: - clk_disable_unprepare(udc->clk); + clk_disable_unprepare(usb->clk); return err; } -static int tegra_udc_remove(struct platform_device *pdev) +static int tegra_usb_remove(struct platform_device *pdev) { - struct tegra_udc *udc = platform_get_drvdata(pdev); + struct tegra_usb *usb = platform_get_drvdata(pdev); - ci_hdrc_remove_device(udc->dev); - clk_disable_unprepare(udc->clk); + ci_hdrc_remove_device(usb->dev); + clk_disable_unprepare(usb->clk); return 0; } -static struct platform_driver tegra_udc_driver = { +static struct platform_driver tegra_usb_driver = { .driver = { - .name = "tegra-udc", - .of_match_table = tegra_udc_of_match, + .name = "tegra-usb", + .of_match_table = tegra_usb_of_match, }, - .probe = tegra_udc_probe, - .remove = tegra_udc_remove, + .probe = tegra_usb_probe, + .remove = tegra_usb_remove, }; -module_platform_driver(tegra_udc_driver); +module_platform_driver(tegra_usb_driver); -MODULE_DESCRIPTION("NVIDIA Tegra USB device mode driver"); +MODULE_DESCRIPTION("NVIDIA Tegra USB driver"); MODULE_AUTHOR("Thierry Reding "); MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From fc53d5279094e38e6363506339772a7021da2df8 Mon Sep 17 00:00:00 2001 From: Peter Geis Date: Fri, 18 Dec 2020 15:02:42 +0300 Subject: usb: chipidea: tegra: Support host mode Add USB host mode to the Tegra HDRC driver. This allows us to benefit from support provided by the generic ChipIdea driver instead of duplicating the effort in a separate ehci-tegra driver. Tested-by: Matt Merhar Tested-by: Nicolas Chauvet Tested-by: Ion Agorria Acked-by: Thierry Reding Acked-by: Peter Chen Signed-off-by: Peter Geis Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20201218120246.7759-6-digetx@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/Kconfig | 1 - drivers/usb/chipidea/ci_hdrc_tegra.c | 243 ++++++++++++++++++++++++++++++++++- drivers/usb/chipidea/core.c | 10 +- drivers/usb/chipidea/host.c | 104 ++++++++++++++- include/linux/usb/chipidea.h | 6 + 5 files changed, 356 insertions(+), 8 deletions(-) diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig index 8685ead6ccc7..661818e8fed6 100644 --- a/drivers/usb/chipidea/Kconfig +++ b/drivers/usb/chipidea/Kconfig @@ -55,7 +55,6 @@ config USB_CHIPIDEA_GENERIC config USB_CHIPIDEA_TEGRA tristate "Enable Tegra USB glue driver" if EMBEDDED depends on OF - depends on USB_CHIPIDEA_UDC default USB_CHIPIDEA endif diff --git a/drivers/usb/chipidea/ci_hdrc_tegra.c b/drivers/usb/chipidea/ci_hdrc_tegra.c index d8efa80aa1c2..5fccdeeefc64 100644 --- a/drivers/usb/chipidea/ci_hdrc_tegra.c +++ b/drivers/usb/chipidea/ci_hdrc_tegra.c @@ -4,11 +4,18 @@ */ #include +#include #include #include #include +#include #include +#include +#include +#include + +#include "../host/ehci.h" #include "ci.h" @@ -16,20 +23,47 @@ struct tegra_usb { struct ci_hdrc_platform_data data; struct platform_device *dev; + const struct tegra_usb_soc_info *soc; struct usb_phy *phy; struct clk *clk; + + bool needs_double_reset; }; struct tegra_usb_soc_info { unsigned long flags; + unsigned int txfifothresh; + enum usb_dr_mode dr_mode; +}; + +static const struct tegra_usb_soc_info tegra20_ehci_soc_info = { + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | + CI_HDRC_OVERRIDE_PHY_CONTROL, + .dr_mode = USB_DR_MODE_HOST, + .txfifothresh = 10, +}; + +static const struct tegra_usb_soc_info tegra30_ehci_soc_info = { + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | + CI_HDRC_OVERRIDE_PHY_CONTROL, + .dr_mode = USB_DR_MODE_HOST, + .txfifothresh = 16, }; static const struct tegra_usb_soc_info tegra_udc_soc_info = { - .flags = CI_HDRC_REQUIRES_ALIGNED_DMA, + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | + CI_HDRC_OVERRIDE_PHY_CONTROL, + .dr_mode = USB_DR_MODE_UNKNOWN, }; static const struct of_device_id tegra_usb_of_match[] = { { + .compatible = "nvidia,tegra20-ehci", + .data = &tegra20_ehci_soc_info, + }, { + .compatible = "nvidia,tegra30-ehci", + .data = &tegra30_ehci_soc_info, + }, { .compatible = "nvidia,tegra20-udc", .data = &tegra_udc_soc_info, }, { @@ -47,6 +81,181 @@ static const struct of_device_id tegra_usb_of_match[] = { }; MODULE_DEVICE_TABLE(of, tegra_usb_of_match); +static int tegra_usb_reset_controller(struct device *dev) +{ + struct reset_control *rst, *rst_utmi; + struct device_node *phy_np; + int err; + + rst = devm_reset_control_get_shared(dev, "usb"); + if (IS_ERR(rst)) { + dev_err(dev, "can't get ehci reset: %pe\n", rst); + return PTR_ERR(rst); + } + + phy_np = of_parse_phandle(dev->of_node, "nvidia,phy", 0); + if (!phy_np) + return -ENOENT; + + /* + * The 1st USB controller contains some UTMI pad registers that are + * global for all the controllers on the chip. Those registers are + * also cleared when reset is asserted to the 1st controller. + */ + rst_utmi = of_reset_control_get_shared(phy_np, "utmi-pads"); + if (IS_ERR(rst_utmi)) { + dev_warn(dev, "can't get utmi-pads reset from the PHY\n"); + dev_warn(dev, "continuing, but please update your DT\n"); + } else { + /* + * PHY driver performs UTMI-pads reset in a case of a + * non-legacy DT. + */ + reset_control_put(rst_utmi); + } + + of_node_put(phy_np); + + /* reset control is shared, hence initialize it first */ + err = reset_control_deassert(rst); + if (err) + return err; + + err = reset_control_assert(rst); + if (err) + return err; + + udelay(1); + + err = reset_control_deassert(rst); + if (err) + return err; + + return 0; +} + +static int tegra_usb_notify_event(struct ci_hdrc *ci, unsigned int event) +{ + struct tegra_usb *usb = dev_get_drvdata(ci->dev->parent); + struct ehci_hcd *ehci; + + switch (event) { + case CI_HDRC_CONTROLLER_RESET_EVENT: + if (ci->hcd) { + ehci = hcd_to_ehci(ci->hcd); + ehci->has_tdi_phy_lpm = false; + ehci_writel(ehci, usb->soc->txfifothresh << 16, + &ehci->regs->txfill_tuning); + } + break; + } + + return 0; +} + +static int tegra_usb_internal_port_reset(struct ehci_hcd *ehci, + u32 __iomem *portsc_reg, + unsigned long *flags) +{ + u32 saved_usbintr, temp; + unsigned int i, tries; + int retval = 0; + + saved_usbintr = ehci_readl(ehci, &ehci->regs->intr_enable); + /* disable USB interrupt */ + ehci_writel(ehci, 0, &ehci->regs->intr_enable); + spin_unlock_irqrestore(&ehci->lock, *flags); + + /* + * Here we have to do Port Reset at most twice for + * Port Enable bit to be set. + */ + for (i = 0; i < 2; i++) { + temp = ehci_readl(ehci, portsc_reg); + temp |= PORT_RESET; + ehci_writel(ehci, temp, portsc_reg); + fsleep(10000); + temp &= ~PORT_RESET; + ehci_writel(ehci, temp, portsc_reg); + fsleep(1000); + tries = 100; + do { + fsleep(1000); + /* + * Up to this point, Port Enable bit is + * expected to be set after 2 ms waiting. + * USB1 usually takes extra 45 ms, for safety, + * we take 100 ms as timeout. + */ + temp = ehci_readl(ehci, portsc_reg); + } while (!(temp & PORT_PE) && tries--); + if (temp & PORT_PE) + break; + } + if (i == 2) + retval = -ETIMEDOUT; + + /* + * Clear Connect Status Change bit if it's set. + * We can't clear PORT_PEC. It will also cause PORT_PE to be cleared. + */ + if (temp & PORT_CSC) + ehci_writel(ehci, PORT_CSC, portsc_reg); + + /* + * Write to clear any interrupt status bits that might be set + * during port reset. + */ + temp = ehci_readl(ehci, &ehci->regs->status); + ehci_writel(ehci, temp, &ehci->regs->status); + + /* restore original interrupt-enable bits */ + spin_lock_irqsave(&ehci->lock, *flags); + ehci_writel(ehci, saved_usbintr, &ehci->regs->intr_enable); + + return retval; +} + +static int tegra_ehci_hub_control(struct ci_hdrc *ci, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength, + bool *done, unsigned long *flags) +{ + struct tegra_usb *usb = dev_get_drvdata(ci->dev->parent); + struct ehci_hcd *ehci = hcd_to_ehci(ci->hcd); + u32 __iomem *status_reg; + int retval = 0; + + status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1]; + + switch (typeReq) { + case SetPortFeature: + if (wValue != USB_PORT_FEAT_RESET || !usb->needs_double_reset) + break; + + /* for USB1 port we need to issue Port Reset twice internally */ + retval = tegra_usb_internal_port_reset(ehci, status_reg, flags); + *done = true; + break; + } + + return retval; +} + +static void tegra_usb_enter_lpm(struct ci_hdrc *ci, bool enable) +{ + /* + * Touching any register which belongs to AHB clock domain will + * hang CPU if USB controller is put into low power mode because + * AHB USB clock is gated on Tegra in the LPM. + * + * Tegra PHY has a separate register for checking the clock status + * and usb_phy_set_suspend() takes care of gating/ungating the clocks + * and restoring the PHY state on Tegra. Hence DEVLC/PORTSC registers + * shouldn't be touched directly by the CI driver. + */ + usb_phy_set_suspend(ci->usb_phy, enable); +} + static int tegra_usb_probe(struct platform_device *pdev) { const struct tegra_usb_soc_info *soc; @@ -83,24 +292,49 @@ static int tegra_usb_probe(struct platform_device *pdev) return err; } + if (device_property_present(&pdev->dev, "nvidia,needs-double-reset")) + usb->needs_double_reset = true; + + err = tegra_usb_reset_controller(&pdev->dev); + if (err) { + dev_err(&pdev->dev, "failed to reset controller: %d\n", err); + goto fail_power_off; + } + + /* + * USB controller registers shouldn't be touched before PHY is + * initialized, otherwise CPU will hang because clocks are gated. + * PHY driver controls gating of internal USB clocks on Tegra. + */ + err = usb_phy_init(usb->phy); + if (err) + goto fail_power_off; + + platform_set_drvdata(pdev, usb); + /* setup and register ChipIdea HDRC device */ + usb->soc = soc; usb->data.name = "tegra-usb"; usb->data.flags = soc->flags; usb->data.usb_phy = usb->phy; + usb->data.dr_mode = soc->dr_mode; usb->data.capoffset = DEF_CAPOFFSET; + usb->data.enter_lpm = tegra_usb_enter_lpm; + usb->data.hub_control = tegra_ehci_hub_control; + usb->data.notify_event = tegra_usb_notify_event; usb->dev = ci_hdrc_add_device(&pdev->dev, pdev->resource, pdev->num_resources, &usb->data); if (IS_ERR(usb->dev)) { err = PTR_ERR(usb->dev); dev_err(&pdev->dev, "failed to add HDRC device: %d\n", err); - goto fail_power_off; + goto phy_shutdown; } - platform_set_drvdata(pdev, usb); - return 0; +phy_shutdown: + usb_phy_shutdown(usb->phy); fail_power_off: clk_disable_unprepare(usb->clk); return err; @@ -111,6 +345,7 @@ static int tegra_usb_remove(struct platform_device *pdev) struct tegra_usb *usb = platform_get_drvdata(pdev); ci_hdrc_remove_device(usb->dev); + usb_phy_shutdown(usb->phy); clk_disable_unprepare(usb->clk); return 0; diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index aa40e510b806..3f6c21406dbd 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -195,7 +195,7 @@ static void hw_wait_phy_stable(void) } /* The PHY enters/leaves low power mode */ -static void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable) +static void ci_hdrc_enter_lpm_common(struct ci_hdrc *ci, bool enable) { enum ci_hw_regs reg = ci->hw_bank.lpm ? OP_DEVLC : OP_PORTSC; bool lpm = !!(hw_read(ci, reg, PORTSC_PHCD(ci->hw_bank.lpm))); @@ -208,6 +208,11 @@ static void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable) 0); } +static void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable) +{ + return ci->platdata->enter_lpm(ci, enable); +} + static int hw_device_init(struct ci_hdrc *ci, void __iomem *base) { u32 reg; @@ -790,6 +795,9 @@ static int ci_get_platdata(struct device *dev, platdata->pins_device = p; } + if (!platdata->enter_lpm) + platdata->enter_lpm = ci_hdrc_enter_lpm_common; + return 0; } diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c index 48e4a5ca1835..67247d2ac07a 100644 --- a/drivers/usb/chipidea/host.c +++ b/drivers/usb/chipidea/host.c @@ -29,6 +29,12 @@ struct ehci_ci_priv { bool enabled; }; +struct ci_hdrc_dma_aligned_buffer { + void *kmalloc_ptr; + void *old_xfer_buffer; + u8 data[0]; +}; + static int ehci_ci_portpower(struct usb_hcd *hcd, int portnum, bool enable) { struct ehci_hcd *ehci = hcd_to_ehci(hcd); @@ -160,14 +166,15 @@ static int host_start(struct ci_hdrc *ci) pinctrl_select_state(ci->platdata->pctl, ci->platdata->pins_host); + ci->hcd = hcd; + ret = usb_add_hcd(hcd, 0, 0); if (ret) { + ci->hcd = NULL; goto disable_reg; } else { struct usb_otg *otg = &ci->otg; - ci->hcd = hcd; - if (ci_otg_is_fsm_mode(ci)) { otg->host = &hcd->self; hcd->self.otg_port = 1; @@ -237,6 +244,7 @@ static int ci_ehci_hub_control( u32 temp; unsigned long flags; int retval = 0; + bool done = false; struct device *dev = hcd->self.controller; struct ci_hdrc *ci = dev_get_drvdata(dev); @@ -244,6 +252,13 @@ static int ci_ehci_hub_control( spin_lock_irqsave(&ehci->lock, flags); + if (ci->platdata->hub_control) { + retval = ci->platdata->hub_control(ci, typeReq, wValue, wIndex, + buf, wLength, &done, &flags); + if (done) + goto done; + } + if (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_SUSPEND) { temp = ehci_readl(ehci, status_reg); if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) != 0) { @@ -349,6 +364,86 @@ static int ci_ehci_bus_suspend(struct usb_hcd *hcd) return 0; } +static void ci_hdrc_free_dma_aligned_buffer(struct urb *urb) +{ + struct ci_hdrc_dma_aligned_buffer *temp; + size_t length; + + if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER)) + return; + + temp = container_of(urb->transfer_buffer, + struct ci_hdrc_dma_aligned_buffer, data); + + if (usb_urb_dir_in(urb)) { + if (usb_pipeisoc(urb->pipe)) + length = urb->transfer_buffer_length; + else + length = urb->actual_length; + + memcpy(temp->old_xfer_buffer, temp->data, length); + } + urb->transfer_buffer = temp->old_xfer_buffer; + kfree(temp->kmalloc_ptr); + + urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER; +} + +static int ci_hdrc_alloc_dma_aligned_buffer(struct urb *urb, gfp_t mem_flags) +{ + struct ci_hdrc_dma_aligned_buffer *temp, *kmalloc_ptr; + const unsigned int ci_hdrc_usb_dma_align = 32; + size_t kmalloc_size; + + if (urb->num_sgs || urb->sg || urb->transfer_buffer_length == 0 || + !((uintptr_t)urb->transfer_buffer & (ci_hdrc_usb_dma_align - 1))) + return 0; + + /* Allocate a buffer with enough padding for alignment */ + kmalloc_size = urb->transfer_buffer_length + + sizeof(struct ci_hdrc_dma_aligned_buffer) + + ci_hdrc_usb_dma_align - 1; + + kmalloc_ptr = kmalloc(kmalloc_size, mem_flags); + if (!kmalloc_ptr) + return -ENOMEM; + + /* Position our struct dma_aligned_buffer such that data is aligned */ + temp = PTR_ALIGN(kmalloc_ptr + 1, ci_hdrc_usb_dma_align) - 1; + temp->kmalloc_ptr = kmalloc_ptr; + temp->old_xfer_buffer = urb->transfer_buffer; + if (usb_urb_dir_out(urb)) + memcpy(temp->data, urb->transfer_buffer, + urb->transfer_buffer_length); + urb->transfer_buffer = temp->data; + + urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER; + + return 0; +} + +static int ci_hdrc_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + int ret; + + ret = ci_hdrc_alloc_dma_aligned_buffer(urb, mem_flags); + if (ret) + return ret; + + ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); + if (ret) + ci_hdrc_free_dma_aligned_buffer(urb); + + return ret; +} + +static void ci_hdrc_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) +{ + usb_hcd_unmap_urb_for_dma(hcd, urb); + ci_hdrc_free_dma_aligned_buffer(urb); +} + int ci_hdrc_host_init(struct ci_hdrc *ci) { struct ci_role_driver *rdrv; @@ -366,6 +461,11 @@ int ci_hdrc_host_init(struct ci_hdrc *ci) rdrv->name = "host"; ci->roles[CI_ROLE_HOST] = rdrv; + if (ci->platdata->flags & CI_HDRC_REQUIRES_ALIGNED_DMA) { + ci_ehci_hc_driver.map_urb_for_dma = ci_hdrc_map_urb_for_dma; + ci_ehci_hc_driver.unmap_urb_for_dma = ci_hdrc_unmap_urb_for_dma; + } + return 0; } diff --git a/include/linux/usb/chipidea.h b/include/linux/usb/chipidea.h index 025b41687ce9..edf3342507f1 100644 --- a/include/linux/usb/chipidea.h +++ b/include/linux/usb/chipidea.h @@ -88,6 +88,12 @@ struct ci_hdrc_platform_data { struct pinctrl_state *pins_default; struct pinctrl_state *pins_host; struct pinctrl_state *pins_device; + + /* platform-specific hooks */ + int (*hub_control)(struct ci_hdrc *ci, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength, + bool *done, unsigned long *flags); + void (*enter_lpm)(struct ci_hdrc *ci, bool enable); }; /* Default offset of capability registers */ -- cgit v1.2.3 From a728f91bcc70dc9c7f50ac25a37806c0bbb7108b Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Fri, 18 Dec 2020 15:02:43 +0300 Subject: usb: chipidea: tegra: Support runtime PM Tegra PHY driver now supports waking up controller from a low power mode. Enable runtime PM in order to put controller into the LPM during idle. Tested-by: Matt Merhar Tested-by: Nicolas Chauvet Tested-by: Peter Geis Tested-by: Ion Agorria Acked-by: Peter Chen Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20201218120246.7759-7-digetx@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/ci_hdrc_tegra.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/usb/chipidea/ci_hdrc_tegra.c b/drivers/usb/chipidea/ci_hdrc_tegra.c index 5fccdeeefc64..655671159511 100644 --- a/drivers/usb/chipidea/ci_hdrc_tegra.c +++ b/drivers/usb/chipidea/ci_hdrc_tegra.c @@ -38,21 +38,24 @@ struct tegra_usb_soc_info { static const struct tegra_usb_soc_info tegra20_ehci_soc_info = { .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | - CI_HDRC_OVERRIDE_PHY_CONTROL, + CI_HDRC_OVERRIDE_PHY_CONTROL | + CI_HDRC_SUPPORTS_RUNTIME_PM, .dr_mode = USB_DR_MODE_HOST, .txfifothresh = 10, }; static const struct tegra_usb_soc_info tegra30_ehci_soc_info = { .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | - CI_HDRC_OVERRIDE_PHY_CONTROL, + CI_HDRC_OVERRIDE_PHY_CONTROL | + CI_HDRC_SUPPORTS_RUNTIME_PM, .dr_mode = USB_DR_MODE_HOST, .txfifothresh = 16, }; static const struct tegra_usb_soc_info tegra_udc_soc_info = { .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | - CI_HDRC_OVERRIDE_PHY_CONTROL, + CI_HDRC_OVERRIDE_PHY_CONTROL | + CI_HDRC_SUPPORTS_RUNTIME_PM, .dr_mode = USB_DR_MODE_UNKNOWN, }; @@ -323,6 +326,10 @@ static int tegra_usb_probe(struct platform_device *pdev) usb->data.hub_control = tegra_ehci_hub_control; usb->data.notify_event = tegra_usb_notify_event; + /* Tegra PHY driver currently doesn't support LPM for ULPI */ + if (of_usb_get_phy_mode(pdev->dev.of_node) == USBPHY_INTERFACE_MODE_ULPI) + usb->data.flags &= ~CI_HDRC_SUPPORTS_RUNTIME_PM; + usb->dev = ci_hdrc_add_device(&pdev->dev, pdev->resource, pdev->num_resources, &usb->data); if (IS_ERR(usb->dev)) { -- cgit v1.2.3 From a1fdd107cd0c7cf3a575c994cc2766c67b6689e0 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Fri, 18 Dec 2020 15:02:44 +0300 Subject: usb: chipidea: tegra: Specify TX FIFO threshold in UDC SoC info The UDC/OTG controller could be switched to a host mode and the TXFILLTUNING register needs to be programmed properly for the host mode. Hence specify the TX FIFO threshold in the UDC SoC info. Acked-by: Peter Chen Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20201218120246.7759-8-digetx@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/ci_hdrc_tegra.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/usb/chipidea/ci_hdrc_tegra.c b/drivers/usb/chipidea/ci_hdrc_tegra.c index 655671159511..90f2a8b786be 100644 --- a/drivers/usb/chipidea/ci_hdrc_tegra.c +++ b/drivers/usb/chipidea/ci_hdrc_tegra.c @@ -52,11 +52,20 @@ static const struct tegra_usb_soc_info tegra30_ehci_soc_info = { .txfifothresh = 16, }; -static const struct tegra_usb_soc_info tegra_udc_soc_info = { +static const struct tegra_usb_soc_info tegra20_udc_soc_info = { .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | CI_HDRC_OVERRIDE_PHY_CONTROL | CI_HDRC_SUPPORTS_RUNTIME_PM, .dr_mode = USB_DR_MODE_UNKNOWN, + .txfifothresh = 10, +}; + +static const struct tegra_usb_soc_info tegra30_udc_soc_info = { + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | + CI_HDRC_OVERRIDE_PHY_CONTROL | + CI_HDRC_SUPPORTS_RUNTIME_PM, + .dr_mode = USB_DR_MODE_UNKNOWN, + .txfifothresh = 16, }; static const struct of_device_id tegra_usb_of_match[] = { @@ -68,16 +77,16 @@ static const struct of_device_id tegra_usb_of_match[] = { .data = &tegra30_ehci_soc_info, }, { .compatible = "nvidia,tegra20-udc", - .data = &tegra_udc_soc_info, + .data = &tegra20_udc_soc_info, }, { .compatible = "nvidia,tegra30-udc", - .data = &tegra_udc_soc_info, + .data = &tegra30_udc_soc_info, }, { .compatible = "nvidia,tegra114-udc", - .data = &tegra_udc_soc_info, + .data = &tegra30_udc_soc_info, }, { .compatible = "nvidia,tegra124-udc", - .data = &tegra_udc_soc_info, + .data = &tegra30_udc_soc_info, }, { /* sentinel */ } -- cgit v1.2.3 From c3590c7656fb103db193e3538cb52b420985482c Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Fri, 18 Dec 2020 15:02:45 +0300 Subject: usb: host: ehci-tegra: Remove the driver The ChipIdea driver now provides USB2 host mode support for NVIDIA Tegra SoCs. The ehci-tegra driver is obsolete now, remove it and redirect the older Kconfig entry to the CI driver. Tested-by: Matt Merhar Tested-by: Nicolas Chauvet Tested-by: Peter Geis Tested-by: Ion Agorria Acked-by: Thierry Reding Acked-by: Alan Stern Acked-by: Peter Chen Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20201218120246.7759-9-digetx@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/Kconfig | 10 +- drivers/usb/host/Makefile | 1 - drivers/usb/host/ehci-tegra.c | 604 ------------------------------------------ 3 files changed, 7 insertions(+), 608 deletions(-) delete mode 100644 drivers/usb/host/ehci-tegra.c diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 31e59309da1f..b94f2a070c05 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -269,10 +269,14 @@ config USB_EHCI_HCD_AT91 config USB_EHCI_TEGRA tristate "NVIDIA Tegra HCD support" depends on ARCH_TEGRA - select USB_EHCI_ROOT_HUB_TT - select USB_TEGRA_PHY + select USB_CHIPIDEA + select USB_CHIPIDEA_HOST + select USB_CHIPIDEA_TEGRA help - This driver enables support for the internal USB Host Controllers + This option is deprecated now and the driver was removed, use + USB_CHIPIDEA_TEGRA instead. + + Enable support for the internal USB Host Controllers found in NVIDIA Tegra SoCs. The controllers are EHCI compliant. config USB_EHCI_HCD_PPC_OF diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index c1b08703af10..3e4d298d851f 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -47,7 +47,6 @@ obj-$(CONFIG_USB_EHCI_HCD_SPEAR) += ehci-spear.o obj-$(CONFIG_USB_EHCI_HCD_STI) += ehci-st.o obj-$(CONFIG_USB_EHCI_EXYNOS) += ehci-exynos.o obj-$(CONFIG_USB_EHCI_HCD_AT91) += ehci-atmel.o -obj-$(CONFIG_USB_EHCI_TEGRA) += ehci-tegra.o obj-$(CONFIG_USB_EHCI_BRCMSTB) += ehci-brcm.o obj-$(CONFIG_USB_OXU210HP_HCD) += oxu210hp-hcd.o diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c deleted file mode 100644 index 869d9c4de5fc..000000000000 --- a/drivers/usb/host/ehci-tegra.c +++ /dev/null @@ -1,604 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * EHCI-compliant USB host controller driver for NVIDIA Tegra SoCs - * - * Copyright (C) 2010 Google, Inc. - * Copyright (C) 2009 - 2013 NVIDIA Corporation - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ehci.h" - -#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) - -#define TEGRA_USB_DMA_ALIGN 32 - -#define DRIVER_DESC "Tegra EHCI driver" -#define DRV_NAME "tegra-ehci" - -static struct hc_driver __read_mostly tegra_ehci_hc_driver; - -struct tegra_ehci_soc_config { - bool has_hostpc; -}; - -struct tegra_ehci_hcd { - struct clk *clk; - struct reset_control *rst; - int port_resuming; - bool needs_double_reset; -}; - -static int tegra_reset_usb_controller(struct platform_device *pdev) -{ - struct device_node *phy_np; - struct usb_hcd *hcd = platform_get_drvdata(pdev); - struct tegra_ehci_hcd *tegra = - (struct tegra_ehci_hcd *)hcd_to_ehci(hcd)->priv; - struct reset_control *rst; - int err; - - phy_np = of_parse_phandle(pdev->dev.of_node, "nvidia,phy", 0); - if (!phy_np) - return -ENOENT; - - /* - * The 1st USB controller contains some UTMI pad registers that are - * global for all the controllers on the chip. Those registers are - * also cleared when reset is asserted to the 1st controller. - */ - rst = of_reset_control_get_shared(phy_np, "utmi-pads"); - if (IS_ERR(rst)) { - dev_warn(&pdev->dev, - "can't get utmi-pads reset from the PHY\n"); - dev_warn(&pdev->dev, - "continuing, but please update your DT\n"); - } else { - /* - * PHY driver performs UTMI-pads reset in a case of - * non-legacy DT. - */ - reset_control_put(rst); - } - - of_node_put(phy_np); - - /* reset control is shared, hence initialize it first */ - err = reset_control_deassert(tegra->rst); - if (err) - return err; - - err = reset_control_assert(tegra->rst); - if (err) - return err; - - udelay(1); - - err = reset_control_deassert(tegra->rst); - if (err) - return err; - - return 0; -} - -static int tegra_ehci_internal_port_reset( - struct ehci_hcd *ehci, - u32 __iomem *portsc_reg -) -{ - u32 temp; - unsigned long flags; - int retval = 0; - int i, tries; - u32 saved_usbintr; - - spin_lock_irqsave(&ehci->lock, flags); - saved_usbintr = ehci_readl(ehci, &ehci->regs->intr_enable); - /* disable USB interrupt */ - ehci_writel(ehci, 0, &ehci->regs->intr_enable); - spin_unlock_irqrestore(&ehci->lock, flags); - - /* - * Here we have to do Port Reset at most twice for - * Port Enable bit to be set. - */ - for (i = 0; i < 2; i++) { - temp = ehci_readl(ehci, portsc_reg); - temp |= PORT_RESET; - ehci_writel(ehci, temp, portsc_reg); - mdelay(10); - temp &= ~PORT_RESET; - ehci_writel(ehci, temp, portsc_reg); - mdelay(1); - tries = 100; - do { - mdelay(1); - /* - * Up to this point, Port Enable bit is - * expected to be set after 2 ms waiting. - * USB1 usually takes extra 45 ms, for safety, - * we take 100 ms as timeout. - */ - temp = ehci_readl(ehci, portsc_reg); - } while (!(temp & PORT_PE) && tries--); - if (temp & PORT_PE) - break; - } - if (i == 2) - retval = -ETIMEDOUT; - - /* - * Clear Connect Status Change bit if it's set. - * We can't clear PORT_PEC. It will also cause PORT_PE to be cleared. - */ - if (temp & PORT_CSC) - ehci_writel(ehci, PORT_CSC, portsc_reg); - - /* - * Write to clear any interrupt status bits that might be set - * during port reset. - */ - temp = ehci_readl(ehci, &ehci->regs->status); - ehci_writel(ehci, temp, &ehci->regs->status); - - /* restore original interrupt enable bits */ - ehci_writel(ehci, saved_usbintr, &ehci->regs->intr_enable); - return retval; -} - -static int tegra_ehci_hub_control( - struct usb_hcd *hcd, - u16 typeReq, - u16 wValue, - u16 wIndex, - char *buf, - u16 wLength -) -{ - struct ehci_hcd *ehci = hcd_to_ehci(hcd); - struct tegra_ehci_hcd *tegra = (struct tegra_ehci_hcd *)ehci->priv; - u32 __iomem *status_reg; - u32 temp; - unsigned long flags; - int retval = 0; - - status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1]; - - spin_lock_irqsave(&ehci->lock, flags); - - if (typeReq == GetPortStatus) { - temp = ehci_readl(ehci, status_reg); - if (tegra->port_resuming && !(temp & PORT_SUSPEND)) { - /* Resume completed, re-enable disconnect detection */ - tegra->port_resuming = 0; - tegra_usb_phy_postresume(hcd->usb_phy); - } - } - - else if (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_SUSPEND) { - temp = ehci_readl(ehci, status_reg); - if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) != 0) { - retval = -EPIPE; - goto done; - } - - temp &= ~(PORT_RWC_BITS | PORT_WKCONN_E); - temp |= PORT_WKDISC_E | PORT_WKOC_E; - ehci_writel(ehci, temp | PORT_SUSPEND, status_reg); - - /* - * If a transaction is in progress, there may be a delay in - * suspending the port. Poll until the port is suspended. - */ - if (ehci_handshake(ehci, status_reg, PORT_SUSPEND, - PORT_SUSPEND, 5000)) - pr_err("%s: timeout waiting for SUSPEND\n", __func__); - - set_bit((wIndex & 0xff) - 1, &ehci->suspended_ports); - goto done; - } - - /* For USB1 port we need to issue Port Reset twice internally */ - if (tegra->needs_double_reset && - (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_RESET)) { - spin_unlock_irqrestore(&ehci->lock, flags); - return tegra_ehci_internal_port_reset(ehci, status_reg); - } - - /* - * Tegra host controller will time the resume operation to clear the bit - * when the port control state switches to HS or FS Idle. This behavior - * is different from EHCI where the host controller driver is required - * to set this bit to a zero after the resume duration is timed in the - * driver. - */ - else if (typeReq == ClearPortFeature && - wValue == USB_PORT_FEAT_SUSPEND) { - temp = ehci_readl(ehci, status_reg); - if ((temp & PORT_RESET) || !(temp & PORT_PE)) { - retval = -EPIPE; - goto done; - } - - if (!(temp & PORT_SUSPEND)) - goto done; - - /* Disable disconnect detection during port resume */ - tegra_usb_phy_preresume(hcd->usb_phy); - - ehci->reset_done[wIndex-1] = jiffies + msecs_to_jiffies(25); - - temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); - /* start resume signalling */ - ehci_writel(ehci, temp | PORT_RESUME, status_reg); - set_bit(wIndex-1, &ehci->resuming_ports); - - spin_unlock_irqrestore(&ehci->lock, flags); - msleep(20); - spin_lock_irqsave(&ehci->lock, flags); - - /* Poll until the controller clears RESUME and SUSPEND */ - if (ehci_handshake(ehci, status_reg, PORT_RESUME, 0, 2000)) - pr_err("%s: timeout waiting for RESUME\n", __func__); - if (ehci_handshake(ehci, status_reg, PORT_SUSPEND, 0, 2000)) - pr_err("%s: timeout waiting for SUSPEND\n", __func__); - - ehci->reset_done[wIndex-1] = 0; - clear_bit(wIndex-1, &ehci->resuming_ports); - - tegra->port_resuming = 1; - goto done; - } - - spin_unlock_irqrestore(&ehci->lock, flags); - - /* Handle the hub control events here */ - return ehci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); - -done: - spin_unlock_irqrestore(&ehci->lock, flags); - return retval; -} - -struct dma_aligned_buffer { - void *kmalloc_ptr; - void *old_xfer_buffer; - u8 data[]; -}; - -static void free_dma_aligned_buffer(struct urb *urb) -{ - struct dma_aligned_buffer *temp; - size_t length; - - if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER)) - return; - - temp = container_of(urb->transfer_buffer, - struct dma_aligned_buffer, data); - - if (usb_urb_dir_in(urb)) { - if (usb_pipeisoc(urb->pipe)) - length = urb->transfer_buffer_length; - else - length = urb->actual_length; - - memcpy(temp->old_xfer_buffer, temp->data, length); - } - urb->transfer_buffer = temp->old_xfer_buffer; - kfree(temp->kmalloc_ptr); - - urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER; -} - -static int alloc_dma_aligned_buffer(struct urb *urb, gfp_t mem_flags) -{ - struct dma_aligned_buffer *temp, *kmalloc_ptr; - size_t kmalloc_size; - - if (urb->num_sgs || urb->sg || - urb->transfer_buffer_length == 0 || - !((uintptr_t)urb->transfer_buffer & (TEGRA_USB_DMA_ALIGN - 1))) - return 0; - - /* Allocate a buffer with enough padding for alignment */ - kmalloc_size = urb->transfer_buffer_length + - sizeof(struct dma_aligned_buffer) + TEGRA_USB_DMA_ALIGN - 1; - - kmalloc_ptr = kmalloc(kmalloc_size, mem_flags); - if (!kmalloc_ptr) - return -ENOMEM; - - /* Position our struct dma_aligned_buffer such that data is aligned */ - temp = PTR_ALIGN(kmalloc_ptr + 1, TEGRA_USB_DMA_ALIGN) - 1; - temp->kmalloc_ptr = kmalloc_ptr; - temp->old_xfer_buffer = urb->transfer_buffer; - if (usb_urb_dir_out(urb)) - memcpy(temp->data, urb->transfer_buffer, - urb->transfer_buffer_length); - urb->transfer_buffer = temp->data; - - urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER; - - return 0; -} - -static int tegra_ehci_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, - gfp_t mem_flags) -{ - int ret; - - ret = alloc_dma_aligned_buffer(urb, mem_flags); - if (ret) - return ret; - - ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); - if (ret) - free_dma_aligned_buffer(urb); - - return ret; -} - -static void tegra_ehci_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) -{ - usb_hcd_unmap_urb_for_dma(hcd, urb); - free_dma_aligned_buffer(urb); -} - -static const struct tegra_ehci_soc_config tegra30_soc_config = { - .has_hostpc = true, -}; - -static const struct tegra_ehci_soc_config tegra20_soc_config = { - .has_hostpc = false, -}; - -static const struct of_device_id tegra_ehci_of_match[] = { - { .compatible = "nvidia,tegra30-ehci", .data = &tegra30_soc_config }, - { .compatible = "nvidia,tegra20-ehci", .data = &tegra20_soc_config }, - { }, -}; - -static int tegra_ehci_probe(struct platform_device *pdev) -{ - const struct of_device_id *match; - const struct tegra_ehci_soc_config *soc_config; - struct resource *res; - struct usb_hcd *hcd; - struct ehci_hcd *ehci; - struct tegra_ehci_hcd *tegra; - int err = 0; - int irq; - struct usb_phy *u_phy; - - match = of_match_device(tegra_ehci_of_match, &pdev->dev); - if (!match) { - dev_err(&pdev->dev, "Error: No device match found\n"); - return -ENODEV; - } - soc_config = match->data; - - /* Right now device-tree probed devices don't get dma_mask set. - * Since shared usb code relies on it, set it here for now. - * Once we have dma capability bindings this can go away. - */ - err = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); - if (err) - return err; - - hcd = usb_create_hcd(&tegra_ehci_hc_driver, &pdev->dev, - dev_name(&pdev->dev)); - if (!hcd) { - dev_err(&pdev->dev, "Unable to create HCD\n"); - return -ENOMEM; - } - platform_set_drvdata(pdev, hcd); - ehci = hcd_to_ehci(hcd); - tegra = (struct tegra_ehci_hcd *)ehci->priv; - - hcd->has_tt = 1; - - tegra->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(tegra->clk)) { - dev_err(&pdev->dev, "Can't get ehci clock\n"); - err = PTR_ERR(tegra->clk); - goto cleanup_hcd_create; - } - - tegra->rst = devm_reset_control_get_shared(&pdev->dev, "usb"); - if (IS_ERR(tegra->rst)) { - dev_err(&pdev->dev, "Can't get ehci reset\n"); - err = PTR_ERR(tegra->rst); - goto cleanup_hcd_create; - } - - err = clk_prepare_enable(tegra->clk); - if (err) - goto cleanup_hcd_create; - - err = tegra_reset_usb_controller(pdev); - if (err) { - dev_err(&pdev->dev, "Failed to reset controller\n"); - goto cleanup_clk_en; - } - - u_phy = devm_usb_get_phy_by_phandle(&pdev->dev, "nvidia,phy", 0); - if (IS_ERR(u_phy)) { - err = -EPROBE_DEFER; - goto cleanup_clk_en; - } - hcd->usb_phy = u_phy; - hcd->skip_phy_initialization = 1; - - tegra->needs_double_reset = of_property_read_bool(pdev->dev.of_node, - "nvidia,needs-double-reset"); - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - hcd->regs = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(hcd->regs)) { - err = PTR_ERR(hcd->regs); - goto cleanup_clk_en; - } - hcd->rsrc_start = res->start; - hcd->rsrc_len = resource_size(res); - - ehci->caps = hcd->regs + 0x100; - ehci->has_hostpc = soc_config->has_hostpc; - - err = usb_phy_init(hcd->usb_phy); - if (err) { - dev_err(&pdev->dev, "Failed to initialize phy\n"); - goto cleanup_clk_en; - } - - u_phy->otg = devm_kzalloc(&pdev->dev, sizeof(struct usb_otg), - GFP_KERNEL); - if (!u_phy->otg) { - err = -ENOMEM; - goto cleanup_phy; - } - u_phy->otg->host = hcd_to_bus(hcd); - - irq = platform_get_irq(pdev, 0); - if (irq < 0) { - err = irq; - goto cleanup_phy; - } - - otg_set_host(u_phy->otg, &hcd->self); - - err = usb_add_hcd(hcd, irq, IRQF_SHARED); - if (err) { - dev_err(&pdev->dev, "Failed to add USB HCD\n"); - goto cleanup_otg_set_host; - } - device_wakeup_enable(hcd->self.controller); - - return err; - -cleanup_otg_set_host: - otg_set_host(u_phy->otg, NULL); -cleanup_phy: - usb_phy_shutdown(hcd->usb_phy); -cleanup_clk_en: - clk_disable_unprepare(tegra->clk); -cleanup_hcd_create: - usb_put_hcd(hcd); - return err; -} - -static int tegra_ehci_remove(struct platform_device *pdev) -{ - struct usb_hcd *hcd = platform_get_drvdata(pdev); - struct tegra_ehci_hcd *tegra = - (struct tegra_ehci_hcd *)hcd_to_ehci(hcd)->priv; - - usb_remove_hcd(hcd); - otg_set_host(hcd->usb_phy->otg, NULL); - usb_phy_shutdown(hcd->usb_phy); - clk_disable_unprepare(tegra->clk); - usb_put_hcd(hcd); - - return 0; -} - -static void tegra_ehci_hcd_shutdown(struct platform_device *pdev) -{ - struct usb_hcd *hcd = platform_get_drvdata(pdev); - - if (hcd->driver->shutdown) - hcd->driver->shutdown(hcd); -} - -static struct platform_driver tegra_ehci_driver = { - .probe = tegra_ehci_probe, - .remove = tegra_ehci_remove, - .shutdown = tegra_ehci_hcd_shutdown, - .driver = { - .name = DRV_NAME, - .of_match_table = tegra_ehci_of_match, - } -}; - -static int tegra_ehci_reset(struct usb_hcd *hcd) -{ - struct ehci_hcd *ehci = hcd_to_ehci(hcd); - int retval; - int txfifothresh; - - retval = ehci_setup(hcd); - if (retval) - return retval; - - /* - * We should really pull this value out of tegra_ehci_soc_config, but - * to avoid needing access to it, make use of the fact that Tegra20 is - * the only one so far that needs a value of 10, and Tegra20 is the - * only one which doesn't set has_hostpc. - */ - txfifothresh = ehci->has_hostpc ? 0x10 : 10; - ehci_writel(ehci, txfifothresh << 16, &ehci->regs->txfill_tuning); - - return 0; -} - -static const struct ehci_driver_overrides tegra_overrides __initconst = { - .extra_priv_size = sizeof(struct tegra_ehci_hcd), - .reset = tegra_ehci_reset, -}; - -static int __init ehci_tegra_init(void) -{ - if (usb_disabled()) - return -ENODEV; - - pr_info(DRV_NAME ": " DRIVER_DESC "\n"); - - ehci_init_driver(&tegra_ehci_hc_driver, &tegra_overrides); - - /* - * The Tegra HW has some unusual quirks, which require Tegra-specific - * workarounds. We override certain hc_driver functions here to - * achieve that. We explicitly do not enhance ehci_driver_overrides to - * allow this more easily, since this is an unusual case, and we don't - * want to encourage others to override these functions by making it - * too easy. - */ - - tegra_ehci_hc_driver.map_urb_for_dma = tegra_ehci_map_urb_for_dma; - tegra_ehci_hc_driver.unmap_urb_for_dma = tegra_ehci_unmap_urb_for_dma; - tegra_ehci_hc_driver.hub_control = tegra_ehci_hub_control; - - return platform_driver_register(&tegra_ehci_driver); -} -module_init(ehci_tegra_init); - -static void __exit ehci_tegra_cleanup(void) -{ - platform_driver_unregister(&tegra_ehci_driver); -} -module_exit(ehci_tegra_cleanup); - -MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:" DRV_NAME); -MODULE_DEVICE_TABLE(of, tegra_ehci_of_match); -- cgit v1.2.3 From 67004e130aafad4c9e0ad3fff9cf67227b6347be Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Fri, 18 Dec 2020 15:02:46 +0300 Subject: ARM: tegra_defconfig: Enable USB_CHIPIDEA_HOST and remove USB_EHCI_TEGRA The ehci-tegra driver was superseded by the generic ChipIdea USB driver, update the tegra's defconfig accordingly. Acked-by: Thierry Reding Acked-by: Peter Chen Signed-off-by: Dmitry Osipenko Link: https://lore.kernel.org/r/20201218120246.7759-10-digetx@gmail.com Signed-off-by: Greg Kroah-Hartman --- arch/arm/configs/tegra_defconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm/configs/tegra_defconfig b/arch/arm/configs/tegra_defconfig index 74739a52a8ad..ae0709265493 100644 --- a/arch/arm/configs/tegra_defconfig +++ b/arch/arm/configs/tegra_defconfig @@ -237,12 +237,13 @@ CONFIG_USB=y CONFIG_USB_XHCI_HCD=y CONFIG_USB_XHCI_TEGRA=y CONFIG_USB_EHCI_HCD=y -CONFIG_USB_EHCI_TEGRA=y CONFIG_USB_ACM=y CONFIG_USB_WDM=y CONFIG_USB_STORAGE=y CONFIG_USB_CHIPIDEA=y CONFIG_USB_CHIPIDEA_UDC=y +CONFIG_USB_CHIPIDEA_HOST=y +CONFIG_USB_CHIPIDEA_TEGRA=y CONFIG_USB_GADGET=y CONFIG_MMC=y CONFIG_MMC_BLOCK_MINORS=16 -- cgit v1.2.3 From 6500f30b1642288f5564a508752bc20a44219e8d Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Tue, 15 Dec 2020 11:27:09 +0100 Subject: usb: cdns3: Adds missing __iomem markers Patch adds missing __iomem markers in core.h file and makes some changes in drd.c file related with these markers. The lack of __iomem has reported by sparse checker on parsic architecture. Reported-by: kernel test robot Signed-off-by: Pawel Laszczak Signed-off-by: Peter Chen --- drivers/usb/cdns3/core.h | 12 ++++++------ drivers/usb/cdns3/drd.c | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h index f8e350cef699..ab0cb68acd23 100644 --- a/drivers/usb/cdns3/core.h +++ b/drivers/usb/cdns3/core.h @@ -86,12 +86,12 @@ struct cdns { struct resource xhci_res[CDNS_XHCI_RESOURCES_NUM]; struct cdns3_usb_regs __iomem *dev_regs; - struct resource otg_res; - struct cdns3_otg_legacy_regs *otg_v0_regs; - struct cdns3_otg_regs *otg_v1_regs; - struct cdnsp_otg_regs *otg_cdnsp_regs; - struct cdns_otg_common_regs *otg_regs; - struct cdns_otg_irq_regs *otg_irq_regs; + struct resource otg_res; + struct cdns3_otg_legacy_regs __iomem *otg_v0_regs; + struct cdns3_otg_regs __iomem *otg_v1_regs; + struct cdnsp_otg_regs __iomem *otg_cdnsp_regs; + struct cdns_otg_common_regs __iomem *otg_regs; + struct cdns_otg_irq_regs __iomem *otg_irq_regs; #define CDNS3_CONTROLLER_V0 0 #define CDNS3_CONTROLLER_V1 1 #define CDNSP_CONTROLLER_V2 2 diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c index 605a413db727..fa5318ade3e1 100644 --- a/drivers/usb/cdns3/drd.c +++ b/drivers/usb/cdns3/drd.c @@ -27,7 +27,7 @@ */ static int cdns_set_mode(struct cdns *cdns, enum usb_dr_mode mode) { - u32 __iomem *override_reg; + void __iomem *override_reg; u32 reg; switch (mode) { @@ -406,7 +406,7 @@ int cdns_drd_init(struct cdns *cdns) cdns->otg_v1_regs = NULL; cdns->otg_cdnsp_regs = NULL; cdns->otg_regs = regs; - cdns->otg_irq_regs = (struct cdns_otg_irq_regs *) + cdns->otg_irq_regs = (struct cdns_otg_irq_regs __iomem *) &cdns->otg_v0_regs->ien; writel(1, &cdns->otg_v0_regs->simulate); dev_dbg(cdns->dev, "DRD version v0 (%08x)\n", @@ -416,14 +416,14 @@ int cdns_drd_init(struct cdns *cdns) cdns->otg_v1_regs = regs; cdns->otg_cdnsp_regs = regs; - cdns->otg_regs = (void *)&cdns->otg_v1_regs->cmd; + cdns->otg_regs = (void __iomem *)&cdns->otg_v1_regs->cmd; - if (cdns->otg_cdnsp_regs->did == OTG_CDNSP_DID) { - cdns->otg_irq_regs = (struct cdns_otg_irq_regs *) + if (readl(&cdns->otg_cdnsp_regs->did) == OTG_CDNSP_DID) { + cdns->otg_irq_regs = (struct cdns_otg_irq_regs __iomem *) &cdns->otg_cdnsp_regs->ien; cdns->version = CDNSP_CONTROLLER_V2; } else { - cdns->otg_irq_regs = (struct cdns_otg_irq_regs *) + cdns->otg_irq_regs = (struct cdns_otg_irq_regs __iomem *) &cdns->otg_v1_regs->ien; writel(1, &cdns->otg_v1_regs->simulate); cdns->version = CDNS3_CONTROLLER_V1; -- cgit v1.2.3 From 9fd53a5bd1294fe9780306469a6d0a60a5158a27 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Wed, 13 Jan 2021 15:14:07 +0100 Subject: usb: cdnsp: fixes undefined reference to cdns_remove Patch fixes the following errors: ld: drivers/usb/cdns3/cdnsp-pci.o: in function `cdnsp_pci_remove': cdnsp-pci.c:(.text+0x80): undefined reference to `cdns_remove' ld: drivers/usb/cdns3/cdnsp-pci.o: in function `cdnsp_pci_probe': cdnsp-pci.c:(.text+0x34c): undefined reference to `cdns_init' Issue occurs for USB/CDNS3/CDNSP kernel configuration: CONFIG_USB=m CONFIG_USB_CDNS_SUPPORT=y CONFIG_USB_CDNS3=m CONFIG_USB_CDNS3_PCI_WRAP=m CONFIG_USB_CDNSP_PCI=y Acked-by: Randy Dunlap Reported-by: Randy Dunlap Signed-off-by: Pawel Laszczak Signed-off-by: Peter Chen --- drivers/usb/cdns3/Makefile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile index 3f9b7fa8a594..61edb2f89276 100644 --- a/drivers/usb/cdns3/Makefile +++ b/drivers/usb/cdns3/Makefile @@ -26,7 +26,15 @@ obj-$(CONFIG_USB_CDNS3_TI) += cdns3-ti.o obj-$(CONFIG_USB_CDNS3_IMX) += cdns3-imx.o cdnsp-udc-pci-y := cdnsp-pci.o + +ifdef CONFIG_USB_CDNSP_PCI +ifeq ($(CONFIG_USB),m) +obj-m += cdnsp-udc-pci.o +else obj-$(CONFIG_USB_CDNSP_PCI) += cdnsp-udc-pci.o +endif +endif + cdnsp-udc-pci-$(CONFIG_USB_CDNSP_GADGET) += cdnsp-ring.o cdnsp-gadget.o \ cdnsp-mem.o cdnsp-ep0.o -- cgit v1.2.3 From 132ee0da6e9f55e6c8b2102a04a5553e20391824 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Wed, 13 Jan 2021 19:55:29 -0800 Subject: usb: dwc3: gadget: Disable Vendor Test LMP Received event Some users questioned why Vendor Test LMP Received event was enabled. The driver currently doesn't handle this event. Let's disable it to avoid confusion. Acked-by: Felipe Balbi Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/4e785ba5d5e95801b6fcf96116f6090216e70760.1610596478.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/gadget.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index ff14e5bbd152..86f257f12d45 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2232,8 +2232,7 @@ static void dwc3_gadget_enable_irq(struct dwc3 *dwc) u32 reg; /* Enable all but Start and End of Frame IRQs */ - reg = (DWC3_DEVTEN_VNDRDEVTSTRCVEDEN | - DWC3_DEVTEN_EVNTOVERFLOWEN | + reg = (DWC3_DEVTEN_EVNTOVERFLOWEN | DWC3_DEVTEN_CMDCMPLTEN | DWC3_DEVTEN_ERRTICERREN | DWC3_DEVTEN_WKUPEVTEN | -- cgit v1.2.3 From 16bcc58e1dabc4db0e14de259cd5c60c77f47250 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Thu, 14 Jan 2021 07:25:59 +0100 Subject: Documentation/devicetree/bindings/usb/dwc3-st.txt: update usb-drd.yaml reference Changeset b0864e1a4d9d ("dt-bindings: usb: Convert generic USB properties to DT schemas") renamed: Documentation/devicetree/bindings/usb/generic.txt to: Documentation/devicetree/bindings/usb/usb-drd.yaml. Update its cross-reference accordingly. Signed-off-by: Mauro Carvalho Chehab Link: https://lore.kernel.org/r/43d5049a8ed688980bee12ecf18ef9937981de39.1610605373.git.mchehab+huawei@kernel.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/dwc3-st.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/dwc3-st.txt b/Documentation/devicetree/bindings/usb/dwc3-st.txt index df0e02e1ee43..4ec74c1ee33f 100644 --- a/Documentation/devicetree/bindings/usb/dwc3-st.txt +++ b/Documentation/devicetree/bindings/usb/dwc3-st.txt @@ -37,7 +37,7 @@ NB: The dr_mode property described in [1] is NOT optional for this driver, as th is "otg", which isn't supported by this SoC. Valid dr_mode values for dwc3-st are either "host" or "device". -[1] Documentation/devicetree/bindings/usb/generic.txt +[1] Documentation/devicetree/bindings/usb/usb-drd.yaml Example: -- cgit v1.2.3 From e793c2a3d376fdb2fd3524215ca9f4927d933118 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Thu, 14 Jan 2021 07:25:58 +0100 Subject: dt-bindings: usb: update snps,dwc3.yaml references Changeset 389d77658801 ("dt-bindings: usb: Convert DWC USB3 bindings to DT schema") renamed: Documentation/devicetree/bindings/usb/dwc3.txt to: Documentation/devicetree/bindings/usb/snps,dwc3.yaml. Update its cross-references accordingly. Signed-off-by: Mauro Carvalho Chehab Link: https://lore.kernel.org/r/97704f110f0282fb47eb85dea430cc94cfd93a4b.1610605373.git.mchehab+huawei@kernel.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/dwc3-st.txt | 2 +- Documentation/devicetree/bindings/usb/exynos-usb.txt | 2 +- Documentation/devicetree/bindings/usb/omap-usb.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/dwc3-st.txt b/Documentation/devicetree/bindings/usb/dwc3-st.txt index 4ec74c1ee33f..bf73de0d5b4a 100644 --- a/Documentation/devicetree/bindings/usb/dwc3-st.txt +++ b/Documentation/devicetree/bindings/usb/dwc3-st.txt @@ -31,7 +31,7 @@ See: Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt Sub-nodes: The dwc3 core should be added as subnode to ST DWC3 glue as shown in the example below. The DT binding details of dwc3 can be found in: -Documentation/devicetree/bindings/usb/dwc3.txt +Documentation/devicetree/bindings/usb/snps,dwc3.yaml NB: The dr_mode property described in [1] is NOT optional for this driver, as the default value is "otg", which isn't supported by this SoC. Valid dr_mode values for dwc3-st are either "host" diff --git a/Documentation/devicetree/bindings/usb/exynos-usb.txt b/Documentation/devicetree/bindings/usb/exynos-usb.txt index 6aae1544f240..f7ae79825d7d 100644 --- a/Documentation/devicetree/bindings/usb/exynos-usb.txt +++ b/Documentation/devicetree/bindings/usb/exynos-usb.txt @@ -93,7 +93,7 @@ Sub-nodes: The dwc3 core should be added as subnode to Exynos dwc3 glue. - dwc3 : The binding details of dwc3 can be found in: - Documentation/devicetree/bindings/usb/dwc3.txt + Documentation/devicetree/bindings/usb/snps,dwc3.yaml Example: usb@12000000 { diff --git a/Documentation/devicetree/bindings/usb/omap-usb.txt b/Documentation/devicetree/bindings/usb/omap-usb.txt index 38d9bb8507cf..f0dbc5ae45ae 100644 --- a/Documentation/devicetree/bindings/usb/omap-usb.txt +++ b/Documentation/devicetree/bindings/usb/omap-usb.txt @@ -65,7 +65,7 @@ Sub-nodes: The dwc3 core should be added as subnode to omap dwc3 glue. - dwc3 : The binding details of dwc3 can be found in: - Documentation/devicetree/bindings/usb/dwc3.txt + Documentation/devicetree/bindings/usb/snps,dwc3.yaml omap_dwc3 { compatible = "ti,dwc3"; -- cgit v1.2.3 From 5dc71f1eb8706450b6a30ffd0cd1bd67dd131daa Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Thu, 14 Jan 2021 08:53:42 +0100 Subject: USB: dwc3: document gadget_max_speed This new field was added to struct dwc3_scratchpad_array, but a documentation for it was missed: ../drivers/usb/dwc3/core.h:1259: warning: Function parameter or member 'gadget_max_speed' not described in 'dwc3' Signed-off-by: Mauro Carvalho Chehab Reported-by: Stephen Rothwell Acked-by: Felipe Balbi Link: https://lore.kernel.org/r/e9332e31bec9bcead2c7ced2b25462120488ca85.1610610444.git.mchehab+huawei@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.h | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index ac290d896638..eec1cf4ba268 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -964,6 +964,7 @@ struct dwc3_scratchpad_array { * @nr_scratch: number of scratch buffers * @u1u2: only used on revisions <1.83a for workaround * @maximum_speed: maximum speed requested (mainly for testing purposes) + * @gadget_max_speed: maximum gadget speed requested * @ip: controller's ID * @revision: controller's version of an IP * @version_type: VERSIONTYPE register contents, a sub release of a revision -- cgit v1.2.3 From 3da88be249973f7b74e7b24ed559e6abc2fc5af4 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 10 Nov 2020 11:47:14 +0300 Subject: thunderbolt: Add support for de-authorizing devices In some cases it is useful to be able de-authorize devices. For example if user logs out the userspace can have a policy that disconnects PCIe devices until logged in again. This is only possible for software based connection manager as it directly controls the tunnels. For this reason make the authorized attribute accept writing 0 which makes the software connection manager to tear down the corresponding PCIe tunnel. Userspace can check if this is supported by reading a new domain attribute deauthorization, that holds 1 in that case. While there correct tb_domain_approve_switch() kernel-doc and description of authorized attribute to mention that it is only about PCIe tunnels. Cc: Christian Kellner Signed-off-by: Mika Westerberg Acked-by: Yehezkel Bernat --- Documentation/ABI/testing/sysfs-bus-thunderbolt | 20 ++++++++++++--- Documentation/admin-guide/thunderbolt.rst | 16 ++++++++++++ drivers/thunderbolt/domain.c | 32 +++++++++++++++++++++-- drivers/thunderbolt/switch.c | 34 ++++++++++++++++++++++++- drivers/thunderbolt/tb.c | 20 +++++++++++++++ drivers/thunderbolt/tb.h | 3 +++ 6 files changed, 118 insertions(+), 7 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-bus-thunderbolt b/Documentation/ABI/testing/sysfs-bus-thunderbolt index a91b4b24496e..581dea95245b 100644 --- a/Documentation/ABI/testing/sysfs-bus-thunderbolt +++ b/Documentation/ABI/testing/sysfs-bus-thunderbolt @@ -49,6 +49,15 @@ Description: Holds a comma separated list of device unique_ids that If a device is authorized automatically during boot its boot attribute is set to 1. +What: /sys/bus/thunderbolt/devices/.../domainX/deauthorization +Date: May 2021 +KernelVersion: 5.12 +Contact: Mika Westerberg +Description: This attribute tells whether the system supports + de-authorization of devices. Value of 1 means user can + de-authorize PCIe tunnel by writing 0 to authorized + attribute under each device. + What: /sys/bus/thunderbolt/devices/.../domainX/iommu_dma_protection Date: Mar 2019 KernelVersion: 4.21 @@ -84,22 +93,25 @@ KernelVersion: 4.13 Contact: thunderbolt-software@lists.01.org Description: This attribute is used to authorize Thunderbolt devices after they have been connected. If the device is not - authorized, no devices such as PCIe and Display port are - available to the system. + authorized, no PCIe devices are available to the system. Contents of this attribute will be 0 when the device is not yet authorized. Possible values are supported: - == =========================================== + == =================================================== + 0 The device will be de-authorized (only supported if + deauthorization attribute under domain contains 1) 1 The device will be authorized and connected - == =========================================== + == =================================================== When key attribute contains 32 byte hex string the possible values are: == ======================================================== + 0 The device will be de-authorized (only supported if + deauthorization attribute under domain contains 1) 1 The 32 byte hex string is added to the device NVM and the device is authorized. 2 Send a challenge based on the 32 byte hex string. If the diff --git a/Documentation/admin-guide/thunderbolt.rst b/Documentation/admin-guide/thunderbolt.rst index 613cb24c76c7..0d4348445f91 100644 --- a/Documentation/admin-guide/thunderbolt.rst +++ b/Documentation/admin-guide/thunderbolt.rst @@ -153,6 +153,22 @@ If the user still wants to connect the device they can either approve the device without a key or write a new key and write 1 to the ``authorized`` file to get the new key stored on the device NVM. +De-authorizing devices +---------------------- +It is possible to de-authorize devices by writing ``0`` to their +``authorized`` attribute. This requires support from the connection +manager implementation and can be checked by reading domain +``deauthorization`` attribute. If it reads ``1`` then the feature is +supported. + +When a device is de-authorized the PCIe tunnel from the parent device +PCIe downstream (or root) port to the device PCIe upstream port is torn +down. This is essentially the same thing as PCIe hot-remove and the PCIe +toplogy in question will not be accessible anymore until the device is +authorized again. If there is storage such as NVMe or similar involved, +there is a risk for data loss if the filesystem on that storage is not +properly shut down. You have been warned! + DMA protection utilizing IOMMU ------------------------------ Recent systems from 2018 and forward with Thunderbolt ports may natively diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index d2b92a8be577..9ba2181464cc 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -238,6 +238,16 @@ err_free_str: } static DEVICE_ATTR_RW(boot_acl); +static ssize_t deauthorization_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const struct tb *tb = container_of(dev, struct tb, dev); + + return sprintf(buf, "%d\n", !!tb->cm_ops->disapprove_switch); +} +static DEVICE_ATTR_RO(deauthorization); + static ssize_t iommu_dma_protection_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -267,6 +277,7 @@ static DEVICE_ATTR_RO(security); static struct attribute *domain_attrs[] = { &dev_attr_boot_acl.attr, + &dev_attr_deauthorization.attr, &dev_attr_iommu_dma_protection.attr, &dev_attr_security.attr, NULL, @@ -601,14 +612,31 @@ int tb_domain_runtime_resume(struct tb *tb) return 0; } +/** + * tb_domain_disapprove_switch() - Disapprove switch + * @tb: Domain the switch belongs to + * @sw: Switch to disapprove + * + * This will disconnect PCIe tunnel from parent to this @sw. + * + * Return: %0 on success and negative errno in case of failure. + */ +int tb_domain_disapprove_switch(struct tb *tb, struct tb_switch *sw) +{ + if (!tb->cm_ops->disapprove_switch) + return -EPERM; + + return tb->cm_ops->disapprove_switch(tb, sw); +} + /** * tb_domain_approve_switch() - Approve switch * @tb: Domain the switch belongs to * @sw: Switch to approve * * This will approve switch by connection manager specific means. In - * case of success the connection manager will create tunnels for all - * supported protocols. + * case of success the connection manager will create PCIe tunnel from + * parent to @sw. */ int tb_domain_approve_switch(struct tb *tb, struct tb_switch *sw) { diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index ad992e6204d9..cdba05e72486 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -1387,6 +1387,30 @@ static ssize_t authorized_show(struct device *dev, return sprintf(buf, "%u\n", sw->authorized); } +static int disapprove_switch(struct device *dev, void *not_used) +{ + struct tb_switch *sw; + + sw = tb_to_switch(dev); + if (sw && sw->authorized) { + int ret; + + /* First children */ + ret = device_for_each_child_reverse(&sw->dev, NULL, disapprove_switch); + if (ret) + return ret; + + ret = tb_domain_disapprove_switch(sw->tb, sw); + if (ret) + return ret; + + sw->authorized = 0; + kobject_uevent(&sw->dev.kobj, KOBJ_CHANGE); + } + + return 0; +} + static int tb_switch_set_authorized(struct tb_switch *sw, unsigned int val) { int ret = -EINVAL; @@ -1394,10 +1418,18 @@ static int tb_switch_set_authorized(struct tb_switch *sw, unsigned int val) if (!mutex_trylock(&sw->tb->lock)) return restart_syscall(); - if (sw->authorized) + if (!!sw->authorized == !!val) goto unlock; switch (val) { + /* Disapprove switch */ + case 0: + if (tb_route(sw)) { + ret = disapprove_switch(&sw->dev, NULL); + goto unlock; + } + break; + /* Approve switch */ case 1: if (sw->key) diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 51d5b031cada..d08879849abe 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -1002,6 +1002,25 @@ static void tb_disconnect_and_release_dp(struct tb *tb) } } +static int tb_disconnect_pci(struct tb *tb, struct tb_switch *sw) +{ + struct tb_tunnel *tunnel; + struct tb_port *up; + + up = tb_switch_find_port(sw, TB_TYPE_PCIE_UP); + if (WARN_ON(!up)) + return -ENODEV; + + tunnel = tb_find_tunnel(tb, TB_TUNNEL_PCI, NULL, up); + if (WARN_ON(!tunnel)) + return -ENODEV; + + tb_tunnel_deactivate(tunnel); + list_del(&tunnel->list); + tb_tunnel_free(tunnel); + return 0; +} + static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw) { struct tb_port *up, *down, *port; @@ -1512,6 +1531,7 @@ static const struct tb_cm_ops tb_cm_ops = { .runtime_suspend = tb_runtime_suspend, .runtime_resume = tb_runtime_resume, .handle_event = tb_handle_event, + .disapprove_switch = tb_disconnect_pci, .approve_switch = tb_tunnel_pci, .approve_xdomain_paths = tb_approve_xdomain_paths, .disconnect_xdomain_paths = tb_disconnect_xdomain_paths, diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 34ae83b9e52a..31468de658e4 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -361,6 +361,7 @@ struct tb_path { * @handle_event: Handle thunderbolt event * @get_boot_acl: Get boot ACL list * @set_boot_acl: Set boot ACL list + * @disapprove_switch: Disapprove switch (disconnect PCIe tunnel) * @approve_switch: Approve switch * @add_switch_key: Add key to switch * @challenge_switch_key: Challenge switch using key @@ -394,6 +395,7 @@ struct tb_cm_ops { const void *buf, size_t size); int (*get_boot_acl)(struct tb *tb, uuid_t *uuids, size_t nuuids); int (*set_boot_acl)(struct tb *tb, const uuid_t *uuids, size_t nuuids); + int (*disapprove_switch)(struct tb *tb, struct tb_switch *sw); int (*approve_switch)(struct tb *tb, struct tb_switch *sw); int (*add_switch_key)(struct tb *tb, struct tb_switch *sw); int (*challenge_switch_key)(struct tb *tb, struct tb_switch *sw, @@ -629,6 +631,7 @@ int tb_domain_thaw_noirq(struct tb *tb); void tb_domain_complete(struct tb *tb); int tb_domain_runtime_suspend(struct tb *tb); int tb_domain_runtime_resume(struct tb *tb); +int tb_domain_disapprove_switch(struct tb *tb, struct tb_switch *sw); int tb_domain_approve_switch(struct tb *tb, struct tb_switch *sw); int tb_domain_approve_switch_key(struct tb *tb, struct tb_switch *sw); int tb_domain_challenge_switch_key(struct tb *tb, struct tb_switch *sw); -- cgit v1.2.3 From c86cad04dcc8cc07e0befece5e87175c5c71dd4c Mon Sep 17 00:00:00 2001 From: Jiapeng Zhong Date: Thu, 14 Jan 2021 17:03:22 +0800 Subject: drivers/usb/gadget/udc: Assign boolean values to a bool variable Fix the following coccicheck warnings: ./drivers/usb/gadget/udc/udc-xilinx.c:1957:2-18: WARNING: Assignment of 0/1 to bool variable. Reported-by: Abaci Robot Acked-by: Felipe Balbi Signed-off-by: Jiapeng Zhong Link: https://lore.kernel.org/r/1610615002-66235-1-git-send-email-abaci-bugfix@linux.alibaba.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/udc-xilinx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/udc/udc-xilinx.c b/drivers/usb/gadget/udc/udc-xilinx.c index d5e9d20c097d..77610b5f7db5 100644 --- a/drivers/usb/gadget/udc/udc-xilinx.c +++ b/drivers/usb/gadget/udc/udc-xilinx.c @@ -1954,7 +1954,7 @@ static void xudc_nonctrl_ep_handler(struct xusb_udc *udc, u8 epnum, if (intrstatus & (XUSB_STATUS_EP0_BUFF1_COMP_MASK << epnum)) ep->buffer0ready = 0; if (intrstatus & (XUSB_STATUS_EP0_BUFF2_COMP_MASK << epnum)) - ep->buffer1ready = 0; + ep->buffer1ready = false; if (list_empty(&ep->queue)) return; -- cgit v1.2.3 From e68d0119e3284334de5650a1ac42ef4e179f895e Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Fri, 15 Jan 2021 12:49:11 +0300 Subject: software node: Introduce device_add_software_node() This helper will register a software node and then assign it to device at the same time. The function will also make sure that the device can't have more than one software node. Acked-by: Felipe Balbi Signed-off-by: Heikki Krogerus Link: https://lore.kernel.org/r/20210115094914.88401-2-heikki.krogerus@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/swnode.c | 71 ++++++++++++++++++++++++++++++++++++++++++------ include/linux/property.h | 3 ++ 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/drivers/base/swnode.c b/drivers/base/swnode.c index 4a4b2008fbc2..4842dd678200 100644 --- a/drivers/base/swnode.c +++ b/drivers/base/swnode.c @@ -48,6 +48,19 @@ EXPORT_SYMBOL_GPL(is_software_node); struct swnode, fwnode) : NULL; \ }) +static inline struct swnode *dev_to_swnode(struct device *dev) +{ + struct fwnode_handle *fwnode = dev_fwnode(dev); + + if (!fwnode) + return NULL; + + if (!is_software_node(fwnode)) + fwnode = fwnode->secondary; + + return to_swnode(fwnode); +} + static struct swnode * software_node_to_swnode(const struct software_node *node) { @@ -843,22 +856,62 @@ void fwnode_remove_software_node(struct fwnode_handle *fwnode) } EXPORT_SYMBOL_GPL(fwnode_remove_software_node); +/** + * device_add_software_node - Assign software node to a device + * @dev: The device the software node is meant for. + * @swnode: The software node. + * + * This function will register @swnode and make it the secondary firmware node + * pointer of @dev. If @dev has no primary node, then @swnode will become the primary + * node. + */ +int device_add_software_node(struct device *dev, const struct software_node *swnode) +{ + int ret; + + /* Only one software node per device. */ + if (dev_to_swnode(dev)) + return -EBUSY; + + ret = software_node_register(swnode); + if (ret) + return ret; + + set_secondary_fwnode(dev, software_node_fwnode(swnode)); + + return 0; +} +EXPORT_SYMBOL_GPL(device_add_software_node); + +/** + * device_remove_software_node - Remove device's software node + * @dev: The device with the software node. + * + * This function will unregister the software node of @dev. + */ +void device_remove_software_node(struct device *dev) +{ + struct swnode *swnode; + + swnode = dev_to_swnode(dev); + if (!swnode) + return; + + software_node_notify(dev, KOBJ_REMOVE); + set_secondary_fwnode(dev, NULL); + kobject_put(&swnode->kobj); +} +EXPORT_SYMBOL_GPL(device_remove_software_node); + int software_node_notify(struct device *dev, unsigned long action) { - struct fwnode_handle *fwnode = dev_fwnode(dev); struct swnode *swnode; int ret; - if (!fwnode) - return 0; - - if (!is_software_node(fwnode)) - fwnode = fwnode->secondary; - if (!is_software_node(fwnode)) + swnode = dev_to_swnode(dev); + if (!swnode) return 0; - swnode = to_swnode(fwnode); - switch (action) { case KOBJ_ADD: ret = sysfs_create_link(&dev->kobj, &swnode->kobj, diff --git a/include/linux/property.h b/include/linux/property.h index 0a9001fe7aea..b0e413dc5927 100644 --- a/include/linux/property.h +++ b/include/linux/property.h @@ -488,4 +488,7 @@ fwnode_create_software_node(const struct property_entry *properties, const struct fwnode_handle *parent); void fwnode_remove_software_node(struct fwnode_handle *fwnode); +int device_add_software_node(struct device *dev, const struct software_node *swnode); +void device_remove_software_node(struct device *dev); + #endif /* _LINUX_PROPERTY_H_ */ -- cgit v1.2.3 From e492ce9bcaa1c9661cd3dd6cff0eedf2fa640f31 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Fri, 15 Jan 2021 12:49:12 +0300 Subject: usb: dwc3: pci: Register a software node for the dwc3 platform device By registering the software node directly instead of just the properties in it, the driver can take advantage of also the other features the software nodes have. Acked-by: Felipe Balbi Signed-off-by: Heikki Krogerus Link: https://lore.kernel.org/r/20210115094914.88401-3-heikki.krogerus@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-pci.c | 61 +++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-pci.c b/drivers/usb/dwc3/dwc3-pci.c index bae6a70664c8..037bc21bffa6 100644 --- a/drivers/usb/dwc3/dwc3-pci.c +++ b/drivers/usb/dwc3/dwc3-pci.c @@ -142,6 +142,18 @@ static const struct property_entry dwc3_pci_amd_properties[] = { {} }; +static const struct software_node dwc3_pci_intel_swnode = { + .properties = dwc3_pci_intel_properties, +}; + +static const struct software_node dwc3_pci_intel_mrfld_swnode = { + .properties = dwc3_pci_mrfld_properties, +}; + +static const struct software_node dwc3_pci_amd_swnode = { + .properties = dwc3_pci_amd_properties, +}; + static int dwc3_pci_quirks(struct dwc3_pci *dwc) { struct pci_dev *pdev = dwc->pci; @@ -222,7 +234,6 @@ static void dwc3_pci_resume_work(struct work_struct *work) static int dwc3_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) { - struct property_entry *p = (struct property_entry *)id->driver_data; struct dwc3_pci *dwc; struct resource res[2]; int ret; @@ -265,7 +276,7 @@ static int dwc3_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) dwc->dwc3->dev.parent = dev; ACPI_COMPANION_SET(&dwc->dwc3->dev, ACPI_COMPANION(dev)); - ret = platform_device_add_properties(dwc->dwc3, p); + ret = device_add_software_node(&dwc->dwc3->dev, (void *)id->driver_data); if (ret < 0) goto err; @@ -288,6 +299,7 @@ static int dwc3_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) return 0; err: + device_remove_software_node(&dwc->dwc3->dev); platform_device_put(dwc->dwc3); return ret; } @@ -304,75 +316,76 @@ static void dwc3_pci_remove(struct pci_dev *pci) #endif device_init_wakeup(&pci->dev, false); pm_runtime_get(&pci->dev); + device_remove_software_node(&dwc->dwc3->dev); platform_device_unregister(dwc->dwc3); } static const struct pci_device_id dwc3_pci_id_table[] = { { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BSW), - (kernel_ulong_t) &dwc3_pci_intel_properties }, + (kernel_ulong_t) &dwc3_pci_intel_swnode, }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BYT), - (kernel_ulong_t) &dwc3_pci_intel_properties, }, + (kernel_ulong_t) &dwc3_pci_intel_swnode, }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MRFLD), - (kernel_ulong_t) &dwc3_pci_mrfld_properties, }, + (kernel_ulong_t) &dwc3_pci_intel_mrfld_swnode, }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CMLLP), - (kernel_ulong_t) &dwc3_pci_intel_properties, }, + (kernel_ulong_t) &dwc3_pci_intel_swnode, }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CMLH), - (kernel_ulong_t) &dwc3_pci_intel_properties, }, + (kernel_ulong_t) &dwc3_pci_intel_swnode, }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_SPTLP), - (kernel_ulong_t) &dwc3_pci_intel_properties, }, + (kernel_ulong_t) &dwc3_pci_intel_swnode, }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_SPTH), - (kernel_ulong_t) &dwc3_pci_intel_properties, }, + (kernel_ulong_t) &dwc3_pci_intel_swnode, }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BXT), - (kernel_ulong_t) &dwc3_pci_intel_properties, }, + (kernel_ulong_t) &dwc3_pci_intel_swnode, }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BXT_M), - (kernel_ulong_t) &dwc3_pci_intel_properties, }, + (kernel_ulong_t) &dwc3_pci_intel_swnode, }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_APL), - (kernel_ulong_t) &dwc3_pci_intel_properties, }, + (kernel_ulong_t) &dwc3_pci_intel_swnode, }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_KBP), - (kernel_ulong_t) &dwc3_pci_intel_properties, }, + (kernel_ulong_t) &dwc3_pci_intel_swnode, }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_GLK), - (kernel_ulong_t) &dwc3_pci_intel_properties, }, + (kernel_ulong_t) &dwc3_pci_intel_swnode, }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CNPLP), - (kernel_ulong_t) &dwc3_pci_intel_properties, }, + (kernel_ulong_t) &dwc3_pci_intel_swnode, }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CNPH), - (kernel_ulong_t) &dwc3_pci_intel_properties, }, + (kernel_ulong_t) &dwc3_pci_intel_swnode, }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CNPV), - (kernel_ulong_t) &dwc3_pci_intel_properties, }, + (kernel_ulong_t) &dwc3_pci_intel_swnode, }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICLLP), - (kernel_ulong_t) &dwc3_pci_intel_properties, }, + (kernel_ulong_t) &dwc3_pci_intel_swnode, }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_EHLLP), - (kernel_ulong_t) &dwc3_pci_intel_properties, }, + (kernel_ulong_t) &dwc3_pci_intel_swnode }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGPLP), - (kernel_ulong_t) &dwc3_pci_intel_properties, }, + (kernel_ulong_t) &dwc3_pci_intel_swnode, }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGPH), - (kernel_ulong_t) &dwc3_pci_intel_properties, }, + (kernel_ulong_t) &dwc3_pci_intel_swnode, }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_JSP), - (kernel_ulong_t) &dwc3_pci_intel_properties, }, + (kernel_ulong_t) &dwc3_pci_intel_swnode, }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADLS), - (kernel_ulong_t) &dwc3_pci_intel_properties, }, + (kernel_ulong_t) &dwc3_pci_intel_swnode, }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_NL_USB), - (kernel_ulong_t) &dwc3_pci_amd_properties, }, + (kernel_ulong_t) &dwc3_pci_amd_swnode, }, { } /* Terminating Entry */ }; MODULE_DEVICE_TABLE(pci, dwc3_pci_id_table); -- cgit v1.2.3 From 73203bde3a95a48f27b2454dc6b955280c641afe Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Fri, 15 Jan 2021 12:49:13 +0300 Subject: usb: dwc3: pci: ID for Tiger Lake CPU Tiger Lake SOC (the versions of it that have integrated USB4 controller) may have two DWC3 controllers. One is part of the PCH (Platform Controller Hub, i.e. the chipset) as usual, and the other is inside the actual CPU block. On all Intel platforms that have the two separate DWC3 controllers, the one inside the CPU handles USB3 and only USB3 traffic, while the PCH version handles USB2 and USB2 alone. The reason for splitting the two busses like this is to allow easy USB3 tunneling over USB4 connections. As USB2 is not tunneled over USB4, it has dedicated USB controllers (both xHCI and DWC3). Acked-by: Felipe Balbi Signed-off-by: Heikki Krogerus Link: https://lore.kernel.org/r/20210115094914.88401-4-heikki.krogerus@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-pci.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/dwc3/dwc3-pci.c b/drivers/usb/dwc3/dwc3-pci.c index 037bc21bffa6..51029cec119e 100644 --- a/drivers/usb/dwc3/dwc3-pci.c +++ b/drivers/usb/dwc3/dwc3-pci.c @@ -41,6 +41,7 @@ #define PCI_DEVICE_ID_INTEL_TGPH 0x43ee #define PCI_DEVICE_ID_INTEL_JSP 0x4dee #define PCI_DEVICE_ID_INTEL_ADLS 0x7ae1 +#define PCI_DEVICE_ID_INTEL_TGL 0x9a15 #define PCI_INTEL_BXT_DSM_GUID "732b85d5-b7a7-4a1b-9ba0-4bbd00ffd511" #define PCI_INTEL_BXT_FUNC_PMU_PWR 4 @@ -384,6 +385,9 @@ static const struct pci_device_id dwc3_pci_id_table[] = { { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADLS), (kernel_ulong_t) &dwc3_pci_intel_swnode, }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL), + (kernel_ulong_t) &dwc3_pci_intel_swnode, }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_NL_USB), (kernel_ulong_t) &dwc3_pci_amd_swnode, }, { } /* Terminating Entry */ -- cgit v1.2.3 From f08fc2c30e7806443143db16ac1457145d39b7dd Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Fri, 15 Jan 2021 12:49:14 +0300 Subject: usb: dwc3: pci: add support for the Intel Alder Lake-P This patch adds the necessary PCI ID for Intel Alder Lake-P devices. Acked-by: Felipe Balbi Signed-off-by: Heikki Krogerus Link: https://lore.kernel.org/r/20210115094914.88401-5-heikki.krogerus@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-pci.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/dwc3/dwc3-pci.c b/drivers/usb/dwc3/dwc3-pci.c index 51029cec119e..3d3918a8d5fb 100644 --- a/drivers/usb/dwc3/dwc3-pci.c +++ b/drivers/usb/dwc3/dwc3-pci.c @@ -40,6 +40,7 @@ #define PCI_DEVICE_ID_INTEL_TGPLP 0xa0ee #define PCI_DEVICE_ID_INTEL_TGPH 0x43ee #define PCI_DEVICE_ID_INTEL_JSP 0x4dee +#define PCI_DEVICE_ID_INTEL_ADLP 0x51ee #define PCI_DEVICE_ID_INTEL_ADLS 0x7ae1 #define PCI_DEVICE_ID_INTEL_TGL 0x9a15 @@ -382,6 +383,9 @@ static const struct pci_device_id dwc3_pci_id_table[] = { { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_JSP), (kernel_ulong_t) &dwc3_pci_intel_swnode, }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADLP), + (kernel_ulong_t) &dwc3_pci_intel_swnode, }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADLS), (kernel_ulong_t) &dwc3_pci_intel_swnode, }, -- cgit v1.2.3 From 63d152149b2d0860ccf8c4e6596b6175b2b7ace6 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Thu, 14 Jan 2021 08:42:22 +0900 Subject: usb: gadget: u_ether: support configuring interface names. This patch allows the administrator to configure the interface name of a function using u_ether (e.g., eem, ncm, rndis). Currently, all such interfaces, regardless of function type, are always called usb0, usb1, etc. This makes it very cumbersome to use more than one such type at a time, because userspace cannnot easily tell the interfaces apart and apply the right configuration to each one. Interface renaming in userspace based on driver doesn't help, because the interfaces all have the same driver. Without this patch, doing this require hacks/workarounds such as setting fixed MAC addresses on the functions, and then renaming by MAC address, or scraping configfs after each interface is created to find out what it is. Setting the interface name is done by writing to the same "ifname" configfs attribute that reports the interface name after the function is bound. The write must contain an interface pattern such as "usb%d" (which will cause the net core to pick the next available interface name starting with "usb"). This patch does not allow writing an exact interface name (as opposed to a pattern) because if the interface already exists at bind time, the bind will fail and the whole gadget will fail to activate. This could be allowed in a future patch. For compatibility with current userspace, when reading an ifname that has not currently been set, the result is still "(unnamed net_device)". Once a write to ifname happens, then reading ifname will return whatever was last written. Tested by configuring an rndis function and an ncm function on the same gadget, and writing "rndis%d" to ifname on the rndis function and "ncm%d" to ifname on the ncm function. When the gadget was bound, the rndis interface was rndis0 and the ncm interface was ncm0. Signed-off-by: Lorenzo Colitti Link: https://lore.kernel.org/r/20210113234222.3272933-1-lorenzo@google.com Signed-off-by: Greg Kroah-Hartman --- Documentation/usb/gadget-testing.rst | 30 +++++++++++------------ drivers/usb/gadget/function/u_ether.c | 33 +++++++++++++++++++++++++- drivers/usb/gadget/function/u_ether.h | 12 ++++++++++ drivers/usb/gadget/function/u_ether_configfs.h | 15 +++++++++++- 4 files changed, 73 insertions(+), 17 deletions(-) diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst index 2eeb3e9299e4..2085e7b24eeb 100644 --- a/Documentation/usb/gadget-testing.rst +++ b/Documentation/usb/gadget-testing.rst @@ -91,9 +91,9 @@ The ECM function provides these attributes in its function directory: and after creating the functions/ecm. they contain default values: qmult is 5, dev_addr and host_addr are randomly selected. -Except for ifname they can be written to until the function is linked to a -configuration. The ifname is read-only and contains the name of the interface -which was assigned by the net core, e. g. usb0. +The ifname can be written to if the function is not bound. A write must be an +interface pattern such as "usb%d", which will cause the net core to choose the +next free usbX interface. By default, it is set to "usb%d". Testing the ECM function ------------------------ @@ -131,9 +131,9 @@ The ECM subset function provides these attributes in its function directory: and after creating the functions/ecm. they contain default values: qmult is 5, dev_addr and host_addr are randomly selected. -Except for ifname they can be written to until the function is linked to a -configuration. The ifname is read-only and contains the name of the interface -which was assigned by the net core, e. g. usb0. +The ifname can be written to if the function is not bound. A write must be an +interface pattern such as "usb%d", which will cause the net core to choose the +next free usbX interface. By default, it is set to "usb%d". Testing the ECM subset function ------------------------------- @@ -171,9 +171,9 @@ The EEM function provides these attributes in its function directory: and after creating the functions/eem. they contain default values: qmult is 5, dev_addr and host_addr are randomly selected. -Except for ifname they can be written to until the function is linked to a -configuration. The ifname is read-only and contains the name of the interface -which was assigned by the net core, e. g. usb0. +The ifname can be written to if the function is not bound. A write must be an +interface pattern such as "usb%d", which will cause the net core to choose the +next free usbX interface. By default, it is set to "usb%d". Testing the EEM function ------------------------ @@ -453,9 +453,9 @@ The NCM function provides these attributes in its function directory: and after creating the functions/ncm. they contain default values: qmult is 5, dev_addr and host_addr are randomly selected. -Except for ifname they can be written to until the function is linked to a -configuration. The ifname is read-only and contains the name of the interface -which was assigned by the net core, e. g. usb0. +The ifname can be written to if the function is not bound. A write must be an +interface pattern such as "usb%d", which will cause the net core to choose the +next free usbX interface. By default, it is set to "usb%d". Testing the NCM function ------------------------ @@ -591,9 +591,9 @@ The RNDIS function provides these attributes in its function directory: and after creating the functions/rndis. they contain default values: qmult is 5, dev_addr and host_addr are randomly selected. -Except for ifname they can be written to until the function is linked to a -configuration. The ifname is read-only and contains the name of the interface -which was assigned by the net core, e. g. usb0. +The ifname can be written to if the function is not bound. A write must be an +interface pattern such as "usb%d", which will cause the net core to choose the +next free usbX interface. By default, it is set to "usb%d". Testing the RNDIS function -------------------------- diff --git a/drivers/usb/gadget/function/u_ether.c b/drivers/usb/gadget/function/u_ether.c index c019f2b0c0af..d1d044d9f859 100644 --- a/drivers/usb/gadget/function/u_ether.c +++ b/drivers/usb/gadget/function/u_ether.c @@ -80,6 +80,7 @@ struct eth_dev { bool zlp; bool no_skb_reserve; + bool ifname_set; u8 host_mac[ETH_ALEN]; u8 dev_mac[ETH_ALEN]; }; @@ -1004,15 +1005,45 @@ EXPORT_SYMBOL_GPL(gether_get_qmult); int gether_get_ifname(struct net_device *net, char *name, int len) { + struct eth_dev *dev = netdev_priv(net); int ret; rtnl_lock(); - ret = scnprintf(name, len, "%s\n", netdev_name(net)); + ret = scnprintf(name, len, "%s\n", + dev->ifname_set ? net->name : netdev_name(net)); rtnl_unlock(); return ret; } EXPORT_SYMBOL_GPL(gether_get_ifname); +int gether_set_ifname(struct net_device *net, const char *name, int len) +{ + struct eth_dev *dev = netdev_priv(net); + char tmp[IFNAMSIZ]; + const char *p; + + if (name[len - 1] == '\n') + len--; + + if (len >= sizeof(tmp)) + return -E2BIG; + + strscpy(tmp, name, len + 1); + if (!dev_valid_name(tmp)) + return -EINVAL; + + /* Require exactly one %d, so binding will not fail with EEXIST. */ + p = strchr(name, '%'); + if (!p || p[1] != 'd' || strchr(p + 2, '%')) + return -EINVAL; + + strncpy(net->name, tmp, sizeof(net->name)); + dev->ifname_set = true; + + return 0; +} +EXPORT_SYMBOL_GPL(gether_set_ifname); + /* * gether_cleanup - remove Ethernet-over-USB device * Context: may sleep diff --git a/drivers/usb/gadget/function/u_ether.h b/drivers/usb/gadget/function/u_ether.h index 10dd640684e2..40144546d1b0 100644 --- a/drivers/usb/gadget/function/u_ether.h +++ b/drivers/usb/gadget/function/u_ether.h @@ -244,6 +244,18 @@ unsigned gether_get_qmult(struct net_device *net); */ int gether_get_ifname(struct net_device *net, char *name, int len); +/** + * gether_set_ifname - set an ethernet-over-usb link interface name + * @net: device representing this link + * @name: new interface name + * @len: length of @name + * + * This sets the interface name of this ethernet-over-usb link. + * A single terminating newline, if any, is ignored. + * Returns zero on success, else negative errno. + */ +int gether_set_ifname(struct net_device *net, const char *name, int len); + void gether_cleanup(struct eth_dev *dev); /* connect/disconnect is handled by individual functions */ diff --git a/drivers/usb/gadget/function/u_ether_configfs.h b/drivers/usb/gadget/function/u_ether_configfs.h index bd92b5703013..3dfb460908fa 100644 --- a/drivers/usb/gadget/function/u_ether_configfs.h +++ b/drivers/usb/gadget/function/u_ether_configfs.h @@ -148,7 +148,20 @@ out: \ return ret; \ } \ \ - CONFIGFS_ATTR_RO(_f_##_opts_, ifname) + static ssize_t _f_##_opts_ifname_store(struct config_item *item, \ + const char *page, size_t len)\ + { \ + struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \ + int ret = -EBUSY; \ + \ + mutex_lock(&opts->lock); \ + if (!opts->refcnt) \ + ret = gether_set_ifname(opts->net, page, len); \ + mutex_unlock(&opts->lock); \ + return ret ?: len; \ + } \ + \ + CONFIGFS_ATTR(_f_##_opts_, ifname) #define USB_ETHER_CONFIGFS_ITEM_ATTR_U8_RW(_f_, _n_) \ static ssize_t _f_##_opts_##_n_##_show(struct config_item *item,\ -- cgit v1.2.3 From 23bf6fc7046c8c694ff774f0532329dd78efe0a2 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Fri, 25 Dec 2020 15:52:48 +0800 Subject: dt-bindings: usb: convert usb-device.txt to YAML schema Convert usb-device.txt to YAML schema usb-device.yaml Reviewed-by: Rob Herring Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20201225075258.33352-1-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/usb-device.txt | 102 ----------------- .../devicetree/bindings/usb/usb-device.yaml | 124 +++++++++++++++++++++ Documentation/devicetree/bindings/usb/usb-hcd.yaml | 19 ++++ 3 files changed, 143 insertions(+), 102 deletions(-) delete mode 100644 Documentation/devicetree/bindings/usb/usb-device.txt create mode 100644 Documentation/devicetree/bindings/usb/usb-device.yaml diff --git a/Documentation/devicetree/bindings/usb/usb-device.txt b/Documentation/devicetree/bindings/usb/usb-device.txt deleted file mode 100644 index 036be172b1ae..000000000000 --- a/Documentation/devicetree/bindings/usb/usb-device.txt +++ /dev/null @@ -1,102 +0,0 @@ -Generic USB Device Properties - -Usually, we only use device tree for hard wired USB device. -The reference binding doc is from: -http://www.devicetree.org/open-firmware/bindings/usb/usb-1_0.ps - -Four types of device-tree nodes are defined: "host-controller nodes" -representing USB host controllers, "device nodes" representing USB devices, -"interface nodes" representing USB interfaces and "combined nodes" -representing simple USB devices. - -A combined node shall be used instead of a device node and an interface node -for devices of class 0 or 9 (hub) with a single configuration and a single -interface. - -A "hub node" is a combined node or an interface node that represents a USB -hub. - - -Required properties for device nodes: -- compatible: "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 hexadecimal - with leading zeroes suppressed. The other compatible strings from the above - standard binding could also be used, but a device adhering to this binding - may leave out all except for "usbVID,PID". -- reg: the number of the USB hub port or the USB host-controller port to which - this device is attached. The range is 1-255. - - -Required properties for device nodes with interface nodes: -- #address-cells: shall be 2 -- #size-cells: shall be 0 - - -Required properties for interface nodes: -- compatible: "usbifVID,PID.configCN.IN", where VID is the vendor id, PID is - the product id, CN is the configuration value and IN is the interface - number. The textual representation of VID, PID, CN and IN shall be in lower - case hexadecimal with leading zeroes suppressed. The other compatible - strings from the above standard binding could also be used, but a device - adhering to this binding may leave out all except for - "usbifVID,PID.configCN.IN". -- reg: the interface number and configuration value - -The configuration component is not included in the textual representation of -an interface-node unit address for configuration 1. - - -Required properties for combined nodes: -- compatible: "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 hexadecimal - with leading zeroes suppressed. The other compatible strings from the above - standard binding could also be used, but a device adhering to this binding - may leave out all except for "usbVID,PID". -- reg: the number of the USB hub port or the USB host-controller port to which - this device is attached. The range is 1-255. - - -Required properties for hub nodes with device nodes: -- #address-cells: shall be 1 -- #size-cells: shall be 0 - - -Required properties for host-controller nodes with device nodes: -- #address-cells: shall be 1 -- #size-cells: shall be 0 - - -Example: - -&usb1 { /* host controller */ - #address-cells = <1>; - #size-cells = <0>; - - hub@1 { /* hub connected to port 1 */ - compatible = "usb5e3,608"; - reg = <1>; - }; - - device@2 { /* device connected to port 2 */ - compatible = "usb123,4567"; - reg = <2>; - }; - - device@3 { /* device connected to port 3 */ - compatible = "usb123,abcd"; - reg = <3>; - - #address-cells = <2>; - #size-cells = <0>; - - interface@0 { /* interface 0 of configuration 1 */ - compatible = "usbif123,abcd.config1.0"; - reg = <0 1>; - }; - - interface@0,2 { /* interface 0 of configuration 2 */ - compatible = "usbif123,abcd.config2.0"; - reg = <0 2>; - }; - }; -}; diff --git a/Documentation/devicetree/bindings/usb/usb-device.yaml b/Documentation/devicetree/bindings/usb/usb-device.yaml new file mode 100644 index 000000000000..7bb25a45427d --- /dev/null +++ b/Documentation/devicetree/bindings/usb/usb-device.yaml @@ -0,0 +1,124 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/usb-device.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: The device tree bindings for the Generic USB Device + +maintainers: + - Greg Kroah-Hartman + +description: | + Usually, we only use device tree for hard wired USB device. + The reference binding doc is from: + http://www.devicetree.org/open-firmware/bindings/usb/usb-1_0.ps + + Four types of device-tree nodes are defined: "host-controller nodes" + representing USB host controllers, "device nodes" representing USB devices, + "interface nodes" representing USB interfaces and "combined nodes" + representing simple USB devices. + + A combined node shall be used instead of a device node and an interface node + for devices of class 0 or 9 (hub) with a single configuration and a single + interface. + + A "hub node" is a combined node or an interface node that represents a USB + hub. + +properties: + compatible: + 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 + hexadecimal with leading zeroes suppressed. The other compatible + strings from the above standard binding could also be used, + but a device adhering to this binding may leave out all except + for "usbVID,PID". + + reg: + description: the number of the USB hub port or the USB host-controller + port to which this device is attached. The range is 1-255. + maxItems: 1 + + "#address-cells": + description: should be 1 for hub nodes with device nodes, + should be 2 for device nodes with interface nodes. + enum: [1, 2] + + "#size-cells": + const: 0 + +patternProperties: + "^interface@[0-9a-f]{1,2}(,[0-9a-f]{1,2})$": + type: object + description: USB interface nodes. + The configuration component is not included in the textual + representation of an interface-node unit address for configuration 1. + + properties: + compatible: + pattern: "^usbif[0-9a-f]{1,4},[0-9a-f]{1,4}.config[0-9a-f]{1,2}.[0-9a-f]{1,2}$" + description: Interface nodes. + "usbifVID,PID.configCN.IN", where VID is the vendor id, PID is + the product id, CN is the configuration value and IN is the interface + number. The textual representation of VID, PID, CN and IN shall be + in lower case hexadecimal with leading zeroes suppressed. + The other compatible strings from the above standard binding could + also be used, but a device adhering to this binding may leave out + all except for "usbifVID,PID.configCN.IN". + + reg: + description: should be 2 cells long, the first cell represents + the interface number and the second cell represents the + configuration value. + maxItems: 1 + +required: + - compatile + - reg + +additionalProperties: true + +examples: + #hub connected to port 1 + #device connected to port 2 + #device connected to port 3 + # interface 0 of configuration 1 + # interface 0 of configuration 2 + - | + usb@11270000 { + reg = <0x11270000 0x1000>; + interrupts = <0x0 0x4e 0x0>; + #address-cells = <1>; + #size-cells = <0>; + + hub@1 { + compatible = "usb5e3,608"; + reg = <1>; + }; + + device@2 { + compatible = "usb123,4567"; + reg = <2>; + }; + + device@3 { + compatible = "usb123,abcd"; + reg = <3>; + + #address-cells = <2>; + #size-cells = <0>; + + interface@0 { + compatible = "usbif123,abcd.config1.0"; + reg = <0 1>; + }; + + interface@0,2 { + compatible = "usbif123,abcd.config2.0"; + reg = <0 2>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/usb/usb-hcd.yaml b/Documentation/devicetree/bindings/usb/usb-hcd.yaml index 9881ac10380d..56853c17af66 100644 --- a/Documentation/devicetree/bindings/usb/usb-hcd.yaml +++ b/Documentation/devicetree/bindings/usb/usb-hcd.yaml @@ -23,6 +23,18 @@ properties: targeted hosts (non-PC hosts). type: boolean + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + +patternProperties: + "^.*@[0-9a-f]{1,2}$": + description: The hard wired USB devices + type: object + $ref: /usb/usb-device.yaml + additionalProperties: true examples: @@ -30,4 +42,11 @@ examples: usb { phys = <&usb2_phy1>, <&usb3_phy1>; phy-names = "usb"; + #address-cells = <1>; + #size-cells = <0>; + + hub@1 { + compatible = "usb5e3,610"; + reg = <1>; + }; }; -- cgit v1.2.3 From 6a0d64fc5a4ecd968188cb4b4842a3d29634f027 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Fri, 25 Dec 2020 15:52:49 +0800 Subject: dt-bindings: net: btusb: change reference file name Due to usb-device.txt is converted into usb-device.yaml, so modify reference file names at the same time. Acked-by: Rob Herring Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20201225075258.33352-2-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/net/btusb.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/net/btusb.txt b/Documentation/devicetree/bindings/net/btusb.txt index b1ad6ee68e90..a9c3f4277f69 100644 --- a/Documentation/devicetree/bindings/net/btusb.txt +++ b/Documentation/devicetree/bindings/net/btusb.txt @@ -4,7 +4,7 @@ Generic Bluetooth controller over USB (btusb driver) Required properties: - compatible : should comply with the format "usbVID,PID" specified in - Documentation/devicetree/bindings/usb/usb-device.txt + Documentation/devicetree/bindings/usb/usb-device.yaml At the time of writing, the only OF supported devices (more may be added later) are: -- cgit v1.2.3 From f9924caf5d952594b2d912e2ec318189ce64cf04 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Fri, 25 Dec 2020 15:52:55 +0800 Subject: dt-bindings: usb: convert mediatek, musb.txt to YAML schema Convert mediatek,musb.txt to YAML schema mediatek,musb.yaml Cc: Min Guo Reviewed-by: Rob Herring Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20201225075258.33352-8-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/mediatek,musb.txt | 57 ----------- .../devicetree/bindings/usb/mediatek,musb.yaml | 113 +++++++++++++++++++++ 2 files changed, 113 insertions(+), 57 deletions(-) delete mode 100644 Documentation/devicetree/bindings/usb/mediatek,musb.txt create mode 100644 Documentation/devicetree/bindings/usb/mediatek,musb.yaml diff --git a/Documentation/devicetree/bindings/usb/mediatek,musb.txt b/Documentation/devicetree/bindings/usb/mediatek,musb.txt deleted file mode 100644 index 5eedb0296562..000000000000 --- a/Documentation/devicetree/bindings/usb/mediatek,musb.txt +++ /dev/null @@ -1,57 +0,0 @@ -MediaTek musb DRD/OTG controller -------------------------------------------- - -Required properties: - - compatible : should be one of: - "mediatek,mt2701-musb" - ... - followed by "mediatek,mtk-musb" - - reg : specifies physical base address and size of - the registers - - interrupts : interrupt used by musb controller - - interrupt-names : must be "mc" - - phys : PHY specifier for the OTG phy - - dr_mode : should be one of "host", "peripheral" or "otg", - refer to usb/generic.txt - - clocks : a list of phandle + clock-specifier pairs, one for - each entry in clock-names - - clock-names : must contain "main", "mcu", "univpll" - for clocks of controller - -Optional properties: - - power-domains : a phandle to USB power domain node to control USB's - MTCMOS - -Required child nodes: - usb connector node as defined in bindings/connector/usb-connector.yaml -Optional properties: - - id-gpios : input GPIO for USB ID pin. - - vbus-gpios : input GPIO for USB VBUS pin. - - vbus-supply : reference to the VBUS regulator, needed when supports - dual-role mode - - usb-role-switch : use USB Role Switch to support dual-role switch, see - usb/generic.txt. - -Example: - -usb2: usb@11200000 { - compatible = "mediatek,mt2701-musb", - "mediatek,mtk-musb"; - reg = <0 0x11200000 0 0x1000>; - interrupts = ; - interrupt-names = "mc"; - phys = <&u2port2 PHY_TYPE_USB2>; - dr_mode = "otg"; - clocks = <&pericfg CLK_PERI_USB0>, - <&pericfg CLK_PERI_USB0_MCU>, - <&pericfg CLK_PERI_USB_SLV>; - clock-names = "main","mcu","univpll"; - power-domains = <&scpsys MT2701_POWER_DOMAIN_IFR_MSC>; - usb-role-switch; - connector{ - compatible = "gpio-usb-b-connector", "usb-b-connector"; - type = "micro"; - id-gpios = <&pio 44 GPIO_ACTIVE_HIGH>; - vbus-supply = <&usb_vbus>; - }; -}; diff --git a/Documentation/devicetree/bindings/usb/mediatek,musb.yaml b/Documentation/devicetree/bindings/usb/mediatek,musb.yaml new file mode 100644 index 000000000000..790efe8b6274 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/mediatek,musb.yaml @@ -0,0 +1,113 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (c) 2020 MediaTek +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/mediatek,musb.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MediaTek MUSB DRD/OTG Controller Device Tree Bindings + +maintainers: + - Min Guo + +properties: + $nodename: + pattern: '^usb@[0-9a-f]+$' + + compatible: + items: + - enum: + - mediatek,mt2701-musb + - const: mediatek,mtk-musb + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + interrupt-names: + items: + - const: mc + + clocks: + items: + - description: The main/core clock + - description: The system bus clock + - description: The 48Mhz clock + + clock-names: + items: + - const: main + - const: mcu + - const: univpll + + phys: + maxItems: 1 + + usb-role-switch: + $ref: /schemas/types.yaml#/definitions/flag + description: Support role switch. See usb/generic.txt + type: boolean + + dr_mode: + enum: + - host + - otg + - peripheral + + power-domains: + description: A phandle to USB power domain node to control USB's MTCMOS + maxItems: 1 + + connector: + $ref: /connector/usb-connector.yaml# + description: Connector for dual role switch + type: object + +dependencies: + usb-role-switch: [ 'connector' ] + connector: [ 'usb-role-switch' ] + +required: + - compatible + - reg + - interrupts + - interrupt-names + - phys + - clocks + - clock-names + +additionalProperties: false + +examples: + - | + #include + #include + #include + #include + #include + #include + + usb@11200000 { + compatible = "mediatek,mt2701-musb", "mediatek,mtk-musb"; + reg = <0x11200000 0x1000>; + interrupts = ; + interrupt-names = "mc"; + phys = <&u2port2 PHY_TYPE_USB2>; + dr_mode = "otg"; + clocks = <&pericfg CLK_PERI_USB0>, + <&pericfg CLK_PERI_USB0_MCU>, + <&pericfg CLK_PERI_USB_SLV>; + clock-names = "main","mcu","univpll"; + power-domains = <&scpsys MT2701_POWER_DOMAIN_IFR_MSC>; + usb-role-switch; + + connector { + compatible = "gpio-usb-b-connector", "usb-b-connector"; + type = "micro"; + id-gpios = <&pio 44 GPIO_ACTIVE_HIGH>; + vbus-supply = <&usb_vbus>; + }; + }; +... -- cgit v1.2.3 From d93b29c8097144d9911ad0116610d971937748a8 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Fri, 25 Dec 2020 15:52:56 +0800 Subject: dt-bindings: usb: convert mediatek, mtk-xhci.txt to YAML schema Convert mediatek,mtk-xhci.txt to YAML schema mediatek,mtk-xhci.yaml Reviewed-by: Rob Herring Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20201225075258.33352-9-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/mediatek,mtk-xhci.txt | 121 -------------- .../devicetree/bindings/usb/mediatek,mtk-xhci.yaml | 178 +++++++++++++++++++++ 2 files changed, 178 insertions(+), 121 deletions(-) delete mode 100644 Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.txt create mode 100644 Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml diff --git a/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.txt b/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.txt deleted file mode 100644 index 42d8814f903a..000000000000 --- a/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.txt +++ /dev/null @@ -1,121 +0,0 @@ -MT8173 xHCI - -The device node for Mediatek SOC USB3.0 host controller - -There are two scenarios: the first one only supports xHCI driver; -the second one supports dual-role mode, and the host is based on xHCI -driver. Take account of backward compatibility, we divide bindings -into two parts. - -1st: only supports xHCI driver ------------------------------------------------------------------------- - -Required properties: - - compatible : should be "mediatek,-xhci", "mediatek,mtk-xhci", - soc-model is the name of SoC, such as mt8173, mt2712 etc, when using - "mediatek,mtk-xhci" compatible string, you need SoC specific ones in - addition, one of: - - "mediatek,mt8173-xhci" - - reg : specifies physical base address and size of the registers - - reg-names: should be "mac" for xHCI MAC and "ippc" for IP port control - - interrupts : interrupt used by the controller - - power-domains : a phandle to USB power domain node to control USB's - mtcmos - - vusb33-supply : regulator of USB avdd3.3v - - - clocks : a list of phandle + clock-specifier pairs, one for each - entry in clock-names - - clock-names : must contain - "sys_ck": controller clock used by normal mode, - the following ones are optional: - "ref_ck": reference clock used by low power mode etc, - "mcu_ck": mcu_bus clock for register access, - "dma_ck": dma_bus clock for data transfer by DMA, - "xhci_ck": controller clock - - - phys : see usb-hcd.yaml in the current directory - -Optional properties: - - wakeup-source : enable USB remote wakeup; - - mediatek,syscon-wakeup : phandle to syscon used to access the register - of the USB wakeup glue layer between xHCI and SPM; it depends on - "wakeup-source", and has two arguments: - - the first one : register base address of the glue layer in syscon; - - the second one : hardware version of the glue layer - - 1 : used by mt8173 etc - - 2 : used by mt2712 etc - - mediatek,u3p-dis-msk : mask to disable u3ports, bit0 for u3port0, - bit1 for u3port1, ... etc; - - vbus-supply : reference to the VBUS regulator; - - usb3-lpm-capable : supports USB3.0 LPM - - pinctrl-names : a pinctrl state named "default" must be defined - - pinctrl-0 : pin control group - See: Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt - - imod-interval-ns: default interrupt moderation interval is 5000ns - -additionally the properties from usb-hcd.yaml (in the current directory) are -supported. - -Example: -usb30: usb@11270000 { - compatible = "mediatek,mt8173-xhci"; - reg = <0 0x11270000 0 0x1000>, - <0 0x11280700 0 0x0100>; - reg-names = "mac", "ippc"; - interrupts = ; - power-domains = <&scpsys MT8173_POWER_DOMAIN_USB>; - clocks = <&topckgen CLK_TOP_USB30_SEL>, <&clk26m>, - <&pericfg CLK_PERI_USB0>, - <&pericfg CLK_PERI_USB1>; - clock-names = "sys_ck", "ref_ck"; - phys = <&phy_port0 PHY_TYPE_USB3>, - <&phy_port1 PHY_TYPE_USB2>; - vusb33-supply = <&mt6397_vusb_reg>; - vbus-supply = <&usb_p1_vbus>; - usb3-lpm-capable; - mediatek,syscon-wakeup = <&pericfg 0x400 1>; - wakeup-source; - imod-interval-ns = <10000>; -}; - -2nd: dual-role mode with xHCI driver ------------------------------------------------------------------------- - -In the case, xhci is added as subnode to mtu3. An example and the DT binding -details of mtu3 can be found in: -Documentation/devicetree/bindings/usb/mediatek,mtu3.txt - -Required properties: - - compatible : should be "mediatek,-xhci", "mediatek,mtk-xhci", - soc-model is the name of SoC, such as mt8173, mt2712 etc, when using - "mediatek,mtk-xhci" compatible string, you need SoC specific ones in - addition, one of: - - "mediatek,mt8173-xhci" - - reg : specifies physical base address and size of the registers - - reg-names: should be "mac" for xHCI MAC - - interrupts : interrupt used by the host controller - - power-domains : a phandle to USB power domain node to control USB's - mtcmos - - vusb33-supply : regulator of USB avdd3.3v - - - clocks : a list of phandle + clock-specifier pairs, one for each - entry in clock-names - - clock-names : must contain "sys_ck", and the following ones are optional: - "ref_ck", "mcu_ck" and "dma_ck", "xhci_ck" - -Optional properties: - - vbus-supply : reference to the VBUS regulator; - - usb3-lpm-capable : supports USB3.0 LPM - -Example: -usb30: usb@11270000 { - compatible = "mediatek,mt8173-xhci"; - reg = <0 0x11270000 0 0x1000>; - reg-names = "mac"; - interrupts = ; - power-domains = <&scpsys MT8173_POWER_DOMAIN_USB>; - clocks = <&topckgen CLK_TOP_USB30_SEL>, <&clk26m>; - clock-names = "sys_ck", "ref_ck"; - vusb33-supply = <&mt6397_vusb_reg>; - usb3-lpm-capable; -}; diff --git a/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml b/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml new file mode 100644 index 000000000000..38b1fe18aa79 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml @@ -0,0 +1,178 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (c) 2020 MediaTek +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/mediatek,mtk-xhci.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MediaTek USB3 xHCI Device Tree Bindings + +maintainers: + - Chunfeng Yun + +allOf: + - $ref: "usb-xhci.yaml" + +description: | + There are two scenarios: + case 1: only supports xHCI driver; + case 2: supports dual-role mode, and the host is based on xHCI driver. + +properties: + # common properties for both case 1 and case 2 + compatible: + items: + - enum: + - mediatek,mt2712-xhci + - mediatek,mt7622-xhci + - mediatek,mt7629-xhci + - mediatek,mt8173-xhci + - mediatek,mt8183-xhci + - const: mediatek,mtk-xhci + + reg: + minItems: 1 + items: + - description: the registers of xHCI MAC + - description: the registers of IP Port Control + + reg-names: + minItems: 1 + items: + - const: mac + - const: ippc # optional, only needed for case 1. + + interrupts: + maxItems: 1 + + power-domains: + description: A phandle to USB power domain node to control USB's MTCMOS + maxItems: 1 + + clocks: + minItems: 1 + items: + - description: Controller clock used by normal mode + - description: Reference clock used by low power mode etc + - description: Mcu bus clock for register access + - description: DMA bus clock for data transfer + - description: controller clock + + clock-names: + minItems: 1 + items: + - const: sys_ck # required, the following ones are optional + - const: ref_ck + - const: mcu_ck + - const: dma_ck + - const: xhci_ck + + phys: + description: + List of all PHYs used on this HCD, it's better to keep PHYs in order + as the hardware layout + minItems: 1 + items: + - description: USB2/HS PHY # required, others are optional + - description: USB3/SS(P) PHY + - description: USB2/HS PHY + - description: USB3/SS(P) PHY + - description: USB2/HS PHY + - description: USB3/SS(P) PHY + - description: USB2/HS PHY + - description: USB3/SS(P) PHY + - description: USB2/HS PHY + + vusb33-supply: + description: Regulator of USB AVDD3.3v + + vbus-supply: + description: Regulator of USB VBUS5v + + usb3-lpm-capable: + description: supports USB3.0 LPM + type: boolean + + imod-interval-ns: + description: + Interrupt moderation interval value, it is 8 times as much as that + defined in the xHCI spec on MTK's controller. + default: 5000 + + # the following properties are only used for case 1 + wakeup-source: + description: enable USB remote wakeup, see power/wakeup-source.txt + type: boolean + + mediatek,syscon-wakeup: + $ref: /schemas/types.yaml#/definitions/phandle-array + maxItems: 1 + description: + A phandle to syscon used to access the register of the USB wakeup glue + layer between xHCI and SPM, the field should always be 3 cells long. + items: + items: + - description: + The first cell represents a phandle to syscon + - description: + The second cell represents the register base address of the glue + layer in syscon + - description: + The third cell represents the hardware version of the glue layer, + 1 is used by mt8173 etc, 2 is used by mt2712 etc + enum: [1, 2] + + mediatek,u3p-dis-msk: + $ref: /schemas/types.yaml#/definitions/uint32 + description: The mask to disable u3ports, bit0 for u3port0, + bit1 for u3port1, ... etc + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + +patternProperties: + "@[0-9a-f]{1}$": + type: object + description: The hard wired USB devices. + +dependencies: + wakeup-source: [ 'mediatek,syscon-wakeup' ] + +required: + - compatible + - reg + - reg-names + - interrupts + - clocks + - clock-names + +additionalProperties: false + +examples: + - | + #include + #include + #include + #include + #include + + usb@11270000 { + compatible = "mediatek,mt8173-xhci", "mediatek,mtk-xhci"; + reg = <0x11270000 0x1000>, <0x11280700 0x0100>; + reg-names = "mac", "ippc"; + interrupts = ; + power-domains = <&scpsys MT8173_POWER_DOMAIN_USB>; + clocks = <&topckgen CLK_TOP_USB30_SEL>, <&clk26m>; + clock-names = "sys_ck", "ref_ck"; + phys = <&u3port0 PHY_TYPE_USB3>, <&u2port1 PHY_TYPE_USB2>; + vusb33-supply = <&mt6397_vusb_reg>; + vbus-supply = <&usb_p1_vbus>; + imod-interval-ns = <10000>; + mediatek,syscon-wakeup = <&pericfg 0x400 1>; + wakeup-source; + usb3-lpm-capable; + }; +... -- cgit v1.2.3 From 717774eb5273658e01543f6f648a4f6427741ed0 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Fri, 25 Dec 2020 15:52:57 +0800 Subject: dt-bindings: usb: convert mediatek, mtu3.txt to YAML schema Convert mediatek,mtu3.txt to YAML schema mediatek,mtu3.yaml Reviewed-by: Rob Herring Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20201225075258.33352-10-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/mediatek,mtu3.txt | 108 -------- .../devicetree/bindings/usb/mediatek,mtu3.yaml | 287 +++++++++++++++++++++ 2 files changed, 287 insertions(+), 108 deletions(-) delete mode 100644 Documentation/devicetree/bindings/usb/mediatek,mtu3.txt create mode 100644 Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml diff --git a/Documentation/devicetree/bindings/usb/mediatek,mtu3.txt b/Documentation/devicetree/bindings/usb/mediatek,mtu3.txt deleted file mode 100644 index a82ca438aec1..000000000000 --- a/Documentation/devicetree/bindings/usb/mediatek,mtu3.txt +++ /dev/null @@ -1,108 +0,0 @@ -The device node for Mediatek USB3.0 DRD controller - -Required properties: - - compatible : should be "mediatek,-mtu3", "mediatek,mtu3", - soc-model is the name of SoC, such as mt8173, mt2712 etc, - when using "mediatek,mtu3" compatible string, you need SoC specific - ones in addition, one of: - - "mediatek,mt8173-mtu3" - - reg : specifies physical base address and size of the registers - - reg-names: should be "mac" for device IP and "ippc" for IP port control - - interrupts : interrupt used by the device IP - - power-domains : a phandle to USB power domain node to control USB's - mtcmos - - vusb33-supply : regulator of USB avdd3.3v - - clocks : a list of phandle + clock-specifier pairs, one for each - entry in clock-names - - clock-names : must contain "sys_ck" for clock of controller, - the following clocks are optional: - "ref_ck", "mcu_ck" and "dma_ck"; - - phys : see usb-hcd.yaml in the current directory - - dr_mode : should be one of "host", "peripheral" or "otg", - refer to usb/generic.txt - -Optional properties: - - #address-cells, #size-cells : should be '2' if the device has sub-nodes - with 'reg' property - - ranges : allows valid 1:1 translation between child's address space and - parent's address space - - extcon : external connector for vbus and idpin changes detection, needed - when supports dual-role mode. - it's considered valid for compatibility reasons, not allowed for - new bindings, and use "usb-role-switch" property instead. - - vbus-supply : reference to the VBUS regulator, needed when supports - dual-role mode. - it's considered valid for compatibility reasons, not allowed for - new bindings, and put into a usb-connector node. - see connector/usb-connector.yaml. - - pinctrl-names : a pinctrl state named "default" is optional, and need be - defined if auto drd switch is enabled, that means the property dr_mode - is set as "otg", and meanwhile the property "mediatek,enable-manual-drd" - is not set. - - pinctrl-0 : pin control group - See: Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt - - - maximum-speed : valid arguments are "super-speed", "high-speed" and - "full-speed"; refer to usb/generic.txt - - usb-role-switch : use USB Role Switch to support dual-role switch, but - not extcon; see usb/generic.txt. - - enable-manual-drd : supports manual dual-role switch via debugfs; usually - used when receptacle is TYPE-A and also wants to support dual-role - mode. - - wakeup-source: enable USB remote wakeup of host mode. - - mediatek,syscon-wakeup : phandle to syscon used to access the register - of the USB wakeup glue layer between SSUSB and SPM; it depends on - "wakeup-source", and has two arguments: - - the first one : register base address of the glue layer in syscon; - - the second one : hardware version of the glue layer - - 1 : used by mt8173 etc - - 2 : used by mt2712 etc - - mediatek,u3p-dis-msk : mask to disable u3ports, bit0 for u3port0, - bit1 for u3port1, ... etc; - -additionally the properties from usb-hcd.yaml (in the current directory) are -supported. - -Sub-nodes: -The xhci should be added as subnode to mtu3 as shown in the following example -if host mode is enabled. The DT binding details of xhci can be found in: -Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.txt - -The port would be added as subnode if use "usb-role-switch" property. - see graph.txt - -Example: -ssusb: usb@11271000 { - compatible = "mediatek,mt8173-mtu3"; - reg = <0 0x11271000 0 0x3000>, - <0 0x11280700 0 0x0100>; - reg-names = "mac", "ippc"; - interrupts = ; - phys = <&phy_port0 PHY_TYPE_USB3>, - <&phy_port1 PHY_TYPE_USB2>; - power-domains = <&scpsys MT8173_POWER_DOMAIN_USB>; - clocks = <&topckgen CLK_TOP_USB30_SEL>, <&clk26m>, - <&pericfg CLK_PERI_USB0>, - <&pericfg CLK_PERI_USB1>; - clock-names = "sys_ck", "ref_ck"; - vusb33-supply = <&mt6397_vusb_reg>; - vbus-supply = <&usb_p0_vbus>; - extcon = <&extcon_usb>; - dr_mode = "otg"; - wakeup-source; - mediatek,syscon-wakeup = <&pericfg 0x400 1>; - #address-cells = <2>; - #size-cells = <2>; - ranges; - - usb_host: xhci@11270000 { - compatible = "mediatek,mt8173-xhci"; - reg = <0 0x11270000 0 0x1000>; - reg-names = "mac"; - interrupts = ; - power-domains = <&scpsys MT8173_POWER_DOMAIN_USB>; - clocks = <&topckgen CLK_TOP_USB30_SEL>, <&clk26m>; - clock-names = "sys_ck", "ref_ck"; - vusb33-supply = <&mt6397_vusb_reg>; - }; -}; diff --git a/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml b/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml new file mode 100644 index 000000000000..f5c04b9d2de9 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml @@ -0,0 +1,287 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (c) 2020 MediaTek +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/mediatek,mtu3.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MediaTek USB3 DRD Controller Device Tree Bindings + +maintainers: + - Chunfeng Yun + +allOf: + - $ref: "usb-drd.yaml" + +description: | + The DRD controller has a glue layer IPPC (IP Port Control), and its host is + based on xHCI. + +properties: + compatible: + items: + - enum: + - mediatek,mt2712-mtu3 + - mediatek,mt8173-mtu3 + - mediatek,mt8183-mtu3 + - const: mediatek,mtu3 + + reg: + items: + - description: the registers of device MAC + - description: the registers of IP Port Control + + reg-names: + items: + - const: mac + - const: ippc + + interrupts: + maxItems: 1 + + power-domains: + description: A phandle to USB power domain node to control USB's MTCMOS + maxItems: 1 + + clocks: + minItems: 1 + items: + - description: Controller clock used by normal mode + - description: Reference clock used by low power mode etc + - description: Mcu bus clock for register access + - description: DMA bus clock for data transfer + + clock-names: + minItems: 1 + items: + - const: sys_ck # required, others are optional + - const: ref_ck + - const: mcu_ck + - const: dma_ck + + phys: + description: + List of all the USB PHYs used, it's better to keep the sequence + as the hardware layout. + minItems: 1 + items: + - description: USB2/HS PHY # required, others are optional + - description: USB3/SS(P) PHY + - description: USB2/HS PHY # the following for backward compatible + - description: USB3/SS(P) PHY + - description: USB2/HS PHY + - description: USB3/SS(P) PHY + - description: USB2/HS PHY + - description: USB3/SS(P) PHY + - description: USB2/HS PHY + + vusb33-supply: + description: Regulator of USB AVDD3.3v + + vbus-supply: + deprecated: true + description: | + Regulator of USB VBUS5v, needed when supports dual-role mode. + Particularly, if use an output GPIO to control a VBUS regulator, should + model it as a regulator. See bindings/regulator/fixed-regulator.yaml + It's considered valid for compatibility reasons, not allowed for + new bindings, and put into a usb-connector node. + + dr_mode: + enum: [host, peripheral, otg] + default: otg + + maximum-speed: + enum: [super-speed-plus, super-speed, high-speed, full-speed] + + "#address-cells": + enum: [1, 2] + + "#size-cells": + enum: [1, 2] + + ranges: true + + extcon: + deprecated: true + description: | + Phandle to the extcon device detecting the IDDIG/VBUS state, neede + when supports dual-role mode. + It's considered valid for compatibility reasons, not allowed for + new bindings, and use "usb-role-switch" property instead. + + usb-role-switch: + $ref: /schemas/types.yaml#/definitions/flag + description: Support role switch. + type: boolean + + connector: + $ref: /connector/usb-connector.yaml# + description: + Connector for dual role switch, especially for "gpio-usb-b-connector" + type: object + + port: + description: + Any connector to the data bus of this controller should be modelled + using the OF graph bindings specified, if the "usb-role-switch" + property is used. See graph.txt + type: object + + enable-manual-drd: + $ref: /schemas/types.yaml#/definitions/flag + description: + supports manual dual-role switch via debugfs; usually used when + receptacle is TYPE-A and also wants to support dual-role mode. + type: boolean + + wakeup-source: + description: enable USB remote wakeup, see power/wakeup-source.txt + type: boolean + + mediatek,syscon-wakeup: + $ref: /schemas/types.yaml#/definitions/phandle-array + maxItems: 1 + description: + A phandle to syscon used to access the register of the USB wakeup glue + layer between xHCI and SPM, the field should always be 3 cells long. + items: + items: + - description: + The first cell represents a phandle to syscon + - description: + The second cell represents the register base address of the glue + layer in syscon + - description: + The third cell represents the hardware version of the glue layer, + 1 is used by mt8173 etc, 2 is used by mt2712 etc + enum: [1, 2] + + mediatek,u3p-dis-msk: + $ref: /schemas/types.yaml#/definitions/uint32 + description: The mask to disable u3ports, bit0 for u3port0, + bit1 for u3port1, ... etc + +# Required child node when support dual-role +patternProperties: + "^usb@[0-9a-f]+$": + type: object + $ref: /usb/mediatek,mtk-xhci.yaml# + description: + The xhci should be added as subnode to mtu3 as shown in the following + example if the host mode is enabled. + +dependencies: + connector: [ 'usb-role-switch' ] + port: [ 'usb-role-switch' ] + wakeup-source: [ 'mediatek,syscon-wakeup' ] + +required: + - compatible + - reg + - reg-names + - interrupts + - clocks + - clock-names + +additionalProperties: false + +examples: + # Dual role switch by extcon + - | + #include + #include + #include + #include + #include + + usb@11271000 { + compatible = "mediatek,mt8173-mtu3", "mediatek,mtu3"; + reg = <0x11271000 0x3000>, <0x11280700 0x0100>; + reg-names = "mac", "ippc"; + interrupts = ; + phys = <&phy_port0 PHY_TYPE_USB3>, <&phy_port1 PHY_TYPE_USB2>; + power-domains = <&scpsys MT8173_POWER_DOMAIN_USB>; + clocks = <&topckgen CLK_TOP_USB30_SEL>; + clock-names = "sys_ck"; + vusb33-supply = <&mt6397_vusb_reg>; + vbus-supply = <&usb_p0_vbus>; + extcon = <&extcon_usb>; + dr_mode = "otg"; + wakeup-source; + mediatek,syscon-wakeup = <&pericfg 0x400 1>; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + xhci: usb@11270000 { + compatible = "mediatek,mt8173-xhci", "mediatek,mtk-xhci"; + reg = <0x11270000 0x1000>; + reg-names = "mac"; + interrupts = ; + power-domains = <&scpsys MT8173_POWER_DOMAIN_USB>; + clocks = <&topckgen CLK_TOP_USB30_SEL>, <&clk26m>; + clock-names = "sys_ck", "ref_ck"; + vusb33-supply = <&mt6397_vusb_reg>; + }; + }; + + # Enable/disable device by an input gpio for VBUS pin + - | + #include + #include + + usb@112c1000 { + compatible = "mediatek,mt2712-mtu3", "mediatek,mtu3"; + reg = <0x112c1000 0x3000>, <0x112d0700 0x0100>; + reg-names = "mac", "ippc"; + interrupts = ; + phys = <&u2port2 PHY_TYPE_USB2>; + power-domains = <&scpsys MT2712_POWER_DOMAIN_USB2>; + clocks = <&topckgen CLK_TOP_USB30_SEL>; + clock-names = "sys_ck"; + dr_mode = "peripheral"; + usb-role-switch; + + connector { + compatible = "gpio-usb-b-connector", "usb-b-connector"; + type = "micro"; + vbus-gpios = <&pio 13 GPIO_ACTIVE_HIGH>; + }; + }; + + # Dual role switch with type-c + - | + usb@11201000 { + compatible ="mediatek,mt8183-mtu3", "mediatek,mtu3"; + reg = <0x11201000 0x2e00>, <0x11203e00 0x0100>; + reg-names = "mac", "ippc"; + interrupts = ; + phys = <&u2port0 PHY_TYPE_USB2>; + clocks = <&clk26m>; + clock-names = "sys_ck"; + mediatek,syscon-wakeup = <&pericfg 0x400 1>; + wakeup-source; + dr_mode = "otg"; + usb-role-switch; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + host: usb@11200000 { + compatible = "mediatek,mt8183-xhci", "mediatek,mtk-xhci"; + reg = <0x11200000 0x1000>; + reg-names = "mac"; + interrupts = ; + clocks = <&clk26m>; + clock-names = "sys_ck"; + }; + + port { + usb_role_sw: endpoint { + remote-endpoint = <&hs_ep>; + }; + }; + }; + +... -- cgit v1.2.3 From 91369720557bbb0a0792d5bc23f855fd9802bc9c Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Fri, 25 Dec 2020 15:52:58 +0800 Subject: MAINTAINERS: update MediaTek PHY/USB entry Due to the phy/usb bindings are converted into YAML schema and also renamed, update entries. Meanwhile add drivers/usb/host/mtk-xhci* files. Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20201225075258.33352-11-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index cc1e6a5ee6e6..70f6d6111e07 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2091,7 +2091,7 @@ M: Chunfeng Yun L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) L: linux-mediatek@lists.infradead.org (moderated for non-subscribers) S: Maintained -F: Documentation/devicetree/bindings/phy/phy-mtk-* +F: Documentation/devicetree/bindings/phy/mediatek,* F: drivers/phy/mediatek/ ARM/Microchip (AT91) SoC support @@ -11269,6 +11269,8 @@ L: linux-usb@vger.kernel.org L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) L: linux-mediatek@lists.infradead.org (moderated for non-subscribers) S: Maintained +F: Documentation/devicetree/bindings/usb/mediatek,* +F: drivers/usb/host/xhci-mtk* F: drivers/usb/mtu3/ MEGACHIPS STDPXXXX-GE-B850V3-FW LVDS/DP++ BRIDGES -- cgit v1.2.3 From c25c210f590e7a37eecd865d84f97d1f40e39786 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Fri, 15 Jan 2021 11:50:57 +0800 Subject: usb: dwc3: qcom: add URS Host support for sdm845 ACPI boot For sdm845 ACPI boot, the URS (USB Role Switch) node in ACPI DSDT table holds the memory resource, while interrupt resources reside in the child nodes USB0 and UFN0. It adds USB0 host support by probing URS node, creating platform device for USB0 node, and then retrieve interrupt resources from USB0 platform device. Reviewed-by: Bjorn Andersson Signed-off-by: Shawn Guo Link: https://lore.kernel.org/r/20210115035057.10994-1-shawn.guo@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-qcom.c | 59 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index c703d552bbcf..d803ee98c628 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -60,12 +60,14 @@ struct dwc3_acpi_pdata { int dp_hs_phy_irq_index; int dm_hs_phy_irq_index; int ss_phy_irq_index; + bool is_urs; }; struct dwc3_qcom { struct device *dev; void __iomem *qscratch_base; struct platform_device *dwc3; + struct platform_device *urs_usb; struct clk **clks; int num_clocks; struct reset_control *resets; @@ -429,13 +431,15 @@ static void dwc3_qcom_select_utmi_clk(struct dwc3_qcom *qcom) static int dwc3_qcom_get_irq(struct platform_device *pdev, const char *name, int num) { + struct dwc3_qcom *qcom = platform_get_drvdata(pdev); + struct platform_device *pdev_irq = qcom->urs_usb ? qcom->urs_usb : pdev; struct device_node *np = pdev->dev.of_node; int ret; if (np) - ret = platform_get_irq_byname(pdev, name); + ret = platform_get_irq_byname(pdev_irq, name); else - ret = platform_get_irq(pdev, num); + ret = platform_get_irq(pdev_irq, num); return ret; } @@ -568,6 +572,8 @@ static int dwc3_qcom_acpi_register_core(struct platform_device *pdev) struct dwc3_qcom *qcom = platform_get_drvdata(pdev); struct device *dev = &pdev->dev; struct resource *res, *child_res = NULL; + struct platform_device *pdev_irq = qcom->urs_usb ? qcom->urs_usb : + pdev; int irq; int ret; @@ -597,7 +603,7 @@ static int dwc3_qcom_acpi_register_core(struct platform_device *pdev) child_res[0].end = child_res[0].start + qcom->acpi_pdata->dwc3_core_base_size; - irq = platform_get_irq(pdev, 0); + irq = platform_get_irq(pdev_irq, 0); child_res[1].flags = IORESOURCE_IRQ; child_res[1].start = child_res[1].end = irq; @@ -651,6 +657,33 @@ static int dwc3_qcom_of_register_core(struct platform_device *pdev) return 0; } +static struct platform_device * +dwc3_qcom_create_urs_usb_platdev(struct device *dev) +{ + struct fwnode_handle *fwh; + struct acpi_device *adev; + char name[8]; + int ret; + int id; + + /* Figure out device id */ + ret = sscanf(fwnode_get_name(dev->fwnode), "URS%d", &id); + if (!ret) + return NULL; + + /* Find the child using name */ + snprintf(name, sizeof(name), "USB%d", id); + fwh = fwnode_get_named_child_node(dev->fwnode, name); + if (!fwh) + return NULL; + + adev = to_acpi_device_node(fwh); + if (!adev) + return NULL; + + return acpi_create_platform_device(adev, NULL); +} + static int dwc3_qcom_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; @@ -715,6 +748,14 @@ static int dwc3_qcom_probe(struct platform_device *pdev) qcom->acpi_pdata->qscratch_base_offset; parent_res->end = parent_res->start + qcom->acpi_pdata->qscratch_base_size; + + if (qcom->acpi_pdata->is_urs) { + qcom->urs_usb = dwc3_qcom_create_urs_usb_platdev(dev); + if (!qcom->urs_usb) { + dev_err(dev, "failed to create URS USB platdev\n"); + return -ENODEV; + } + } } qcom->qscratch_base = devm_ioremap_resource(dev, parent_res); @@ -877,8 +918,20 @@ static const struct dwc3_acpi_pdata sdm845_acpi_pdata = { .ss_phy_irq_index = 2 }; +static const struct dwc3_acpi_pdata sdm845_acpi_urs_pdata = { + .qscratch_base_offset = SDM845_QSCRATCH_BASE_OFFSET, + .qscratch_base_size = SDM845_QSCRATCH_SIZE, + .dwc3_core_base_size = SDM845_DWC3_CORE_SIZE, + .hs_phy_irq_index = 1, + .dp_hs_phy_irq_index = 4, + .dm_hs_phy_irq_index = 3, + .ss_phy_irq_index = 2, + .is_urs = true, +}; + static const struct acpi_device_id dwc3_qcom_acpi_match[] = { { "QCOM2430", (unsigned long)&sdm845_acpi_pdata }, + { "QCOM0304", (unsigned long)&sdm845_acpi_urs_pdata }, { }, }; MODULE_DEVICE_TABLE(acpi, dwc3_qcom_acpi_match); -- cgit v1.2.3 From 979d9cbe75b922ab1695b8ad5576115774f72e62 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 11 Jan 2021 18:00:19 +0100 Subject: USB: serial: pl2303: fix line-speed handling on newer chips The latest chip family (HXN) apparently does not support setting the line speed using divisors and instead needs to use the direct encoding scheme for all rates. This specifically enables 50, 110, 134, 200 bps and other rates not supported by the original chip type. Fixes: ebd09f1cd417 ("USB: serial: pl2303: add support for PL2303HXN") Cc: stable@vger.kernel.org # 5.5 Cc: Charles Yeh Signed-off-by: Johan Hovold --- drivers/usb/serial/pl2303.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c index be8067017eaa..29dda60e3bcd 100644 --- a/drivers/usb/serial/pl2303.c +++ b/drivers/usb/serial/pl2303.c @@ -183,6 +183,7 @@ struct pl2303_type_data { speed_t max_baud_rate; unsigned long quirks; unsigned int no_autoxonxoff:1; + unsigned int no_divisors:1; }; struct pl2303_serial_private { @@ -209,6 +210,7 @@ static const struct pl2303_type_data pl2303_type_data[TYPE_COUNT] = { }, [TYPE_HXN] = { .max_baud_rate = 12000000, + .no_divisors = true, }, }; @@ -571,8 +573,12 @@ static void pl2303_encode_baud_rate(struct tty_struct *tty, baud = min_t(speed_t, baud, spriv->type->max_baud_rate); /* * Use direct method for supported baud rates, otherwise use divisors. + * Newer chip types do not support divisor encoding. */ - baud_sup = pl2303_get_supported_baud_rate(baud); + if (spriv->type->no_divisors) + baud_sup = baud; + else + baud_sup = pl2303_get_supported_baud_rate(baud); if (baud == baud_sup) baud = pl2303_encode_baud_rate_direct(buf, baud); -- cgit v1.2.3 From 7748feffcd80f3ee25dae5e6acd3cf90e8e838d8 Mon Sep 17 00:00:00 2001 From: Wang Sheng Long Date: Mon, 18 Jan 2021 12:13:26 +0100 Subject: USB: serial: cp210x: add support for software flow control When data is transmitted between two serial ports, the phenomenon of data loss often occurs. The two kinds of flow control commonly used in serial communication are hardware flow control and software flow control. In serial communication, If you only use RX/TX/GND Pins, you can't do hardware flow. So we often used software flow control and prevent data loss. The user sets the software flow control through the application program, and the application program sets the software flow control mode for the serial port chip through the driver. For the cp210 serial port chip, its driver lacks the software flow control setting code, so the user cannot set the software flow control function through the application program. This adds the missing software flow control. Signed-off-by: Wang Sheng Long Link: https://lore.kernel.org/r/20210104094502.3942-1-china_shenglong@163.com [ johan: rework properly on top of recent termios changes ] Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 67 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index fbb10dfc56e3..5bd14770065b 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -377,6 +377,16 @@ static struct usb_serial_driver * const serial_drivers[] = { #define CONTROL_WRITE_DTR 0x0100 #define CONTROL_WRITE_RTS 0x0200 +/* CP210X_(GET|SET)_CHARS */ +struct cp210x_special_chars { + u8 bEofChar; + u8 bErrorChar; + u8 bBreakChar; + u8 bEventChar; + u8 bXonChar; + u8 bXoffChar; +}; + /* CP210X_VENDOR_SPECIFIC values */ #define CP210X_READ_2NCONFIG 0x000E #define CP210X_READ_LATCH 0x00C2 @@ -1074,11 +1084,38 @@ static void cp210x_disable_event_mode(struct usb_serial_port *port) port_priv->event_mode = false; } +static int cp210x_set_chars(struct usb_serial_port *port, + struct cp210x_special_chars *chars) +{ + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + void *dmabuf; + int result; + + dmabuf = kmemdup(chars, sizeof(*chars), GFP_KERNEL); + if (!dmabuf) + return -ENOMEM; + + result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + CP210X_SET_CHARS, REQTYPE_HOST_TO_INTERFACE, 0, + port_priv->bInterfaceNumber, + dmabuf, sizeof(*chars), USB_CTRL_SET_TIMEOUT); + + kfree(dmabuf); + + if (result < 0) { + dev_err(&port->dev, "failed to set special chars: %d\n", result); + return result; + } + + return 0; +} + static bool cp210x_termios_change(const struct ktermios *a, const struct ktermios *b) { bool iflag_change; - iflag_change = ((a->c_iflag ^ b->c_iflag) & INPCK); + iflag_change = ((a->c_iflag ^ b->c_iflag) & (INPCK | IXON | IXOFF)); return tty_termios_hw_change(a, b) || iflag_change; } @@ -1086,13 +1123,29 @@ static bool cp210x_termios_change(const struct ktermios *a, const struct ktermio static void cp210x_set_flow_control(struct tty_struct *tty, struct usb_serial_port *port, struct ktermios *old_termios) { + struct cp210x_special_chars chars; struct cp210x_flow_ctl flow_ctl; u32 flow_repl; u32 ctl_hs; int ret; - if (old_termios && C_CRTSCTS(tty) == (old_termios->c_cflag & CRTSCTS)) + if (old_termios && + C_CRTSCTS(tty) == (old_termios->c_cflag & CRTSCTS) && + I_IXON(tty) == (old_termios->c_iflag & IXON) && + I_IXOFF(tty) == (old_termios->c_iflag & IXOFF)) { return; + } + + if (I_IXON(tty) || I_IXOFF(tty)) { + memset(&chars, 0, sizeof(chars)); + + chars.bXonChar = START_CHAR(tty); + chars.bXoffChar = STOP_CHAR(tty); + + ret = cp210x_set_chars(port, &chars); + if (ret) + return; + } ret = cp210x_read_reg_block(port, CP210X_GET_FLOW, &flow_ctl, sizeof(flow_ctl)); @@ -1118,6 +1171,16 @@ static void cp210x_set_flow_control(struct tty_struct *tty, flow_repl |= CP210X_SERIAL_RTS_SHIFT(CP210X_SERIAL_RTS_ACTIVE); } + if (I_IXOFF(tty)) + flow_repl |= CP210X_SERIAL_AUTO_RECEIVE; + else + flow_repl &= ~CP210X_SERIAL_AUTO_RECEIVE; + + if (I_IXON(tty)) + flow_repl |= CP210X_SERIAL_AUTO_TRANSMIT; + else + flow_repl &= ~CP210X_SERIAL_AUTO_TRANSMIT; + dev_dbg(&port->dev, "%s - ulControlHandshake=0x%08x, ulFlowReplace=0x%08x\n", __func__, ctl_hs, flow_repl); -- cgit v1.2.3 From f61309d9c96a308465bec9d2e5206da265b075a0 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 18 Jan 2021 12:13:27 +0100 Subject: USB: serial: cp210x: set IXOFF thresholds At least CP2102 requires the XON/XOFF limits to be initialised in order for software input flow control (IXOFF) to work. Specifically, XOFF is never sent if the XOFF limit is left at its default value of zero. Set the limits so that input is throttled when the FIFO free level drops below 128 bytes and restarted when the FIFO fill level drops below 128 bytes. Note that the threshold values have been chosen so that they can be used also with CP2105 which has the smallest FIFO of the currently supported device types (288 byte for the SCI port). If needed the limits can be made device specific later. Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 5bd14770065b..ee0139eb6636 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -1181,6 +1181,9 @@ static void cp210x_set_flow_control(struct tty_struct *tty, else flow_repl &= ~CP210X_SERIAL_AUTO_TRANSMIT; + flow_ctl.ulXonLimit = cpu_to_le32(128); + flow_ctl.ulXoffLimit = cpu_to_le32(128); + dev_dbg(&port->dev, "%s - ulControlHandshake=0x%08x, ulFlowReplace=0x%08x\n", __func__, ctl_hs, flow_repl); -- cgit v1.2.3 From 03f32d7cb51b62f6cc7fd884d6978fe1a6ad3f8d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 18 Jan 2021 12:13:28 +0100 Subject: USB: serial: cp210x: update control-characters on every change Update the XON/XOFF control characters also when no other flow-control flag has changed and software flow control is enabled. Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index ee0139eb6636..4f90573c0d2b 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -1113,11 +1113,13 @@ static int cp210x_set_chars(struct usb_serial_port *port, static bool cp210x_termios_change(const struct ktermios *a, const struct ktermios *b) { - bool iflag_change; + bool iflag_change, cc_change; iflag_change = ((a->c_iflag ^ b->c_iflag) & (INPCK | IXON | IXOFF)); + cc_change = a->c_cc[VSTART] != b->c_cc[VSTART] || + a->c_cc[VSTOP] != b->c_cc[VSTOP]; - return tty_termios_hw_change(a, b) || iflag_change; + return tty_termios_hw_change(a, b) || iflag_change || cc_change; } static void cp210x_set_flow_control(struct tty_struct *tty, @@ -1132,7 +1134,9 @@ static void cp210x_set_flow_control(struct tty_struct *tty, if (old_termios && C_CRTSCTS(tty) == (old_termios->c_cflag & CRTSCTS) && I_IXON(tty) == (old_termios->c_iflag & IXON) && - I_IXOFF(tty) == (old_termios->c_iflag & IXOFF)) { + I_IXOFF(tty) == (old_termios->c_iflag & IXOFF) && + START_CHAR(tty) == old_termios->c_cc[VSTART] && + STOP_CHAR(tty) == old_termios->c_cc[VSTOP]) { return; } -- cgit v1.2.3 From dc5338fc64b23e37b000d1a29b88dc8209acebf9 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 18 Jan 2021 12:13:29 +0100 Subject: USB: serial: cp210x: drop short control-transfer checks There's no need to check for short control transfers when sending data so remove the redundant sanity checks. Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 4f90573c0d2b..360398665c17 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -674,16 +674,13 @@ static int cp210x_write_reg_block(struct usb_serial_port *port, u8 req, kfree(dmabuf); - if (result == bufsize) { - result = 0; - } else { + if (result < 0) { dev_err(&port->dev, "failed set req 0x%x size %d status: %d\n", req, bufsize, result); - if (result >= 0) - result = -EIO; + return result; } - return result; + return 0; } /* @@ -720,17 +717,14 @@ static int cp210x_write_vendor_block(struct usb_serial *serial, u8 type, kfree(dmabuf); - if (result == bufsize) { - result = 0; - } else { + if (result < 0) { dev_err(&serial->interface->dev, "failed to set vendor val 0x%04x size %d: %d\n", val, bufsize, result); - if (result >= 0) - result = -EIO; + return result; } - return result; + return 0; } #endif -- cgit v1.2.3 From 4c0a84cb09045b18fc4d6e01238fa946ba39dc74 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 18 Jan 2021 12:13:30 +0100 Subject: USB: serial: cp210x: drop unused includes Drop include directives that are no longer used. Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 360398665c17..0d0fc1f9e99b 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -16,13 +16,10 @@ #include #include #include -#include #include -#include #include #include #include -#include #define DRIVER_DESC "Silicon Labs CP210x RS232 serial adaptor driver" -- cgit v1.2.3 From 90fa41ee4a671d678fcd2c3f0ffdcaa10a1b080b Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 18 Jan 2021 12:13:31 +0100 Subject: USB: serial: cp210x: add copyright notice Add a copyright notice for myself. Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 0d0fc1f9e99b..d813a052738f 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -3,6 +3,7 @@ * Silicon Laboratories CP210x USB to RS232 serial adaptor driver * * Copyright (C) 2005 Craig Shelley (craig@microtron.org.uk) + * Copyright (C) 2010-2021 Johan Hovold (johan@kernel.org) * * Support to set flow control line levels using TIOCMGET and TIOCMSET * thanks to Karl Hiramoto karl@hiramoto.org. RTSCTS hardware flow -- cgit v1.2.3 From f7de9b64265faafe96c2533ddfcc1ad7b2e8080d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 18 Jan 2021 12:14:21 +0100 Subject: USB: serial: mxuport: drop short control-transfer check There's no need to check for short control transfers when sending data so remove the redundant sanity check. Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/mxuport.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/usb/serial/mxuport.c b/drivers/usb/serial/mxuport.c index 5d38c2a0f590..eb45a9b0005c 100644 --- a/drivers/usb/serial/mxuport.c +++ b/drivers/usb/serial/mxuport.c @@ -261,13 +261,6 @@ static int mxuport_send_ctrl_data_urb(struct usb_serial *serial, return status; } - if (status != size) { - dev_err(&serial->interface->dev, - "%s - short write (%d / %zd)\n", - __func__, status, size); - return -EIO; - } - return 0; } -- cgit v1.2.3 From 2dc0e7c37549038444a5202e028ff5625c764012 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 18 Jan 2021 12:14:22 +0100 Subject: USB: serial: upd78f0730: drop short control-transfer check There's no need to check for short control transfers when sending data so remove the redundant sanity check. Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/upd78f0730.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/usb/serial/upd78f0730.c b/drivers/usb/serial/upd78f0730.c index 0a2268c479af..1ca9c1881621 100644 --- a/drivers/usb/serial/upd78f0730.c +++ b/drivers/usb/serial/upd78f0730.c @@ -145,14 +145,11 @@ static int upd78f0730_send_ctl(struct usb_serial_port *port, kfree(buf); - if (res != size) { + if (res < 0) { struct device *dev = &port->dev; dev_err(dev, "failed to send control request %02x: %d\n", *(u8 *)data, res); - /* The maximum expected length of a transfer is 6 bytes */ - if (res >= 0) - res = -EIO; return res; } -- cgit v1.2.3 From 66db94786e94fe2c060ab31bd8ed0308810ab610 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 18 Jan 2021 12:14:23 +0100 Subject: USB: serial: io_ti: drop short control-transfer check There's no need to check for short control transfers when sending data so remove the redundant sanity check. Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/io_ti.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/usb/serial/io_ti.c b/drivers/usb/serial/io_ti.c index c327d4cf7928..0c4062698603 100644 --- a/drivers/usb/serial/io_ti.c +++ b/drivers/usb/serial/io_ti.c @@ -283,11 +283,7 @@ static int ti_vsend_sync(struct usb_device *dev, u8 request, u16 value, value, index, data, size, timeout); if (status < 0) return status; - if (status != size) { - dev_dbg(&dev->dev, "%s - wanted to write %d, but only wrote %d\n", - __func__, size, status); - return -ECOMM; - } + return 0; } -- cgit v1.2.3 From 0765590f91a2b88dc3b946b954c47f774a051636 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 18 Jan 2021 12:14:24 +0100 Subject: USB: serial: io_ti: fix a debug-message copy-paste error Fix a copy-paste error in the ti_vread_sync() debug message. Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/io_ti.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/serial/io_ti.c b/drivers/usb/serial/io_ti.c index 0c4062698603..0bbfa47e04b7 100644 --- a/drivers/usb/serial/io_ti.c +++ b/drivers/usb/serial/io_ti.c @@ -266,7 +266,7 @@ static int ti_vread_sync(struct usb_device *dev, __u8 request, if (status < 0) return status; if (status != size) { - dev_dbg(&dev->dev, "%s - wanted to write %d, but only wrote %d\n", + dev_dbg(&dev->dev, "%s - wanted to read %d, but only read %d\n", __func__, size, status); return -ECOMM; } -- cgit v1.2.3 From 18d8fe614fad9e82ad45b6984f8c6cf733d38bc3 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 18 Jan 2021 12:14:25 +0100 Subject: USB: serial: f81232: drop short control-transfer checks There's no need to check for short control transfers when sending data so remove the redundant sanity checks. Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/f81232.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/drivers/usb/serial/f81232.c b/drivers/usb/serial/f81232.c index 0c7eacc630e0..6a8f39147d8e 100644 --- a/drivers/usb/serial/f81232.c +++ b/drivers/usb/serial/f81232.c @@ -192,13 +192,9 @@ static int f81232_set_register(struct usb_serial_port *port, u16 reg, u8 val) tmp, sizeof(val), USB_CTRL_SET_TIMEOUT); - if (status != sizeof(val)) { + if (status < 0) { dev_err(&port->dev, "%s failed status: %d\n", __func__, status); - - if (status < 0) - status = usb_translate_errors(status); - else - status = -EIO; + status = usb_translate_errors(status); } else { status = 0; } @@ -886,10 +882,6 @@ static int f81534a_ctrl_set_register(struct usb_interface *intf, u16 reg, status = usb_translate_errors(status); if (status == -EIO) continue; - } else if (status != size) { - /* Retry on short transfers */ - status = -EIO; - continue; } else { status = 0; } -- cgit v1.2.3 From cfb0fde7a7fab4509fdb4f7ab72d5fd7157c0aa0 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 18 Jan 2021 12:14:26 +0100 Subject: USB: serial: f81534: drop short control-transfer check There's no need to check for short control transfers when sending data so remove the redundant sanity check. Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/f81534.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/usb/serial/f81534.c b/drivers/usb/serial/f81534.c index 5661fd03e545..dd7e55e822ef 100644 --- a/drivers/usb/serial/f81534.c +++ b/drivers/usb/serial/f81534.c @@ -235,11 +235,9 @@ static int f81534_set_register(struct usb_serial *serial, u16 reg, u8 data) USB_TYPE_VENDOR | USB_DIR_OUT, reg, 0, tmp, sizeof(u8), F81534_USB_TIMEOUT); - if (status > 0) { + if (status == sizeof(u8)) { status = 0; break; - } else if (status == 0) { - status = -EIO; } } -- cgit v1.2.3 From 2e559638f729dc00322ef08b7f8dab40627de905 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Mon, 18 Jan 2021 12:26:15 -0300 Subject: usb: phy: phy-mxs-usb: Use of_device_get_match_data() The retrieval of driver data via of_device_get_match_data() can make the code simpler. Use of_device_get_match_data() to simplify the code. Acked-by: Peter Chen Acked-by: Felipe Balbi Signed-off-by: Fabio Estevam Link: https://lore.kernel.org/r/20210118152615.1644861-1-festevam@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/phy/phy-mxs-usb.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index 67b39dc62b37..8a262c5a0408 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -714,14 +714,9 @@ static int mxs_phy_probe(struct platform_device *pdev) struct clk *clk; struct mxs_phy *mxs_phy; int ret; - const struct of_device_id *of_id; struct device_node *np = pdev->dev.of_node; u32 val; - of_id = of_match_device(mxs_phy_dt_ids, &pdev->dev); - if (!of_id) - return -ENODEV; - base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(base)) return PTR_ERR(base); @@ -797,7 +792,7 @@ static int mxs_phy_probe(struct platform_device *pdev) mxs_phy->phy.charger_detect = mxs_phy_charger_detect; mxs_phy->clk = clk; - mxs_phy->data = of_id->data; + mxs_phy->data = of_device_get_match_data(&pdev->dev); platform_set_drvdata(pdev, mxs_phy); -- cgit v1.2.3 From 7766cafea0eca6a7cc0ffc947bc95be19295575f Mon Sep 17 00:00:00 2001 From: Al Cooper Date: Fri, 15 Jan 2021 16:31:42 -0500 Subject: usb: bdc: Remove the BDC PCI driver The BDC PCI driver was only used for design verification with an PCI/FPGA board. The board no longer exists and is not in use anywhere. All instances of this core now exist as a memory mapped device on the platform bus. NOTE: This only removes the PCI driver and does not remove the platform driver. Acked-by: Florian Fainelli Signed-off-by: Al Cooper Link: https://lore.kernel.org/r/20210115213142.35003-1-alcooperx@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/bdc/Kconfig | 11 --- drivers/usb/gadget/udc/bdc/Makefile | 2 - drivers/usb/gadget/udc/bdc/bdc_pci.c | 128 ----------------------------------- 3 files changed, 141 deletions(-) delete mode 100644 drivers/usb/gadget/udc/bdc/bdc_pci.c diff --git a/drivers/usb/gadget/udc/bdc/Kconfig b/drivers/usb/gadget/udc/bdc/Kconfig index 3e88c7670b2e..8bedb7f64eba 100644 --- a/drivers/usb/gadget/udc/bdc/Kconfig +++ b/drivers/usb/gadget/udc/bdc/Kconfig @@ -11,14 +11,3 @@ config USB_BDC_UDC Say "y" here to link the driver statically, or "m" to build a dynamically linked module called "bdc". - -if USB_BDC_UDC - -comment "Platform Support" -config USB_BDC_PCI - tristate "BDC support for PCIe based platforms" - depends on USB_PCI - default USB_BDC_UDC - help - Enable support for platforms which have BDC connected through PCIe, such as Lego3 FPGA platform. -endif diff --git a/drivers/usb/gadget/udc/bdc/Makefile b/drivers/usb/gadget/udc/bdc/Makefile index 52cb5ea48bbe..1b21c9518efc 100644 --- a/drivers/usb/gadget/udc/bdc/Makefile +++ b/drivers/usb/gadget/udc/bdc/Makefile @@ -5,5 +5,3 @@ bdc-y := bdc_core.o bdc_cmd.o bdc_ep.o bdc_udc.o ifneq ($(CONFIG_USB_GADGET_VERBOSE),) bdc-y += bdc_dbg.o endif - -obj-$(CONFIG_USB_BDC_PCI) += bdc_pci.o diff --git a/drivers/usb/gadget/udc/bdc/bdc_pci.c b/drivers/usb/gadget/udc/bdc/bdc_pci.c deleted file mode 100644 index 6dbc489513cd..000000000000 --- a/drivers/usb/gadget/udc/bdc/bdc_pci.c +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * bdc_pci.c - BRCM BDC USB3.0 device controller PCI interface file. - * - * Copyright (C) 2014 Broadcom Corporation - * - * Author: Ashwini Pahuja - * - * Based on drivers under drivers/usb/ - */ - -#include -#include -#include -#include -#include -#include - -#include "bdc.h" - -#define BDC_PCI_PID 0x1570 - -struct bdc_pci { - struct device *dev; - struct platform_device *bdc; -}; - -static int bdc_setup_msi(struct pci_dev *pci) -{ - int ret; - - ret = pci_enable_msi(pci); - if (ret) { - pr_err("failed to allocate MSI entry\n"); - return ret; - } - - return ret; -} - -static int bdc_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) -{ - struct resource res[2]; - struct platform_device *bdc; - struct bdc_pci *glue; - int ret = -ENOMEM; - - glue = devm_kzalloc(&pci->dev, sizeof(*glue), GFP_KERNEL); - if (!glue) - return -ENOMEM; - - glue->dev = &pci->dev; - ret = pci_enable_device(pci); - if (ret) { - dev_err(&pci->dev, "failed to enable pci device\n"); - return -ENODEV; - } - pci_set_master(pci); - - bdc = platform_device_alloc(BRCM_BDC_NAME, PLATFORM_DEVID_AUTO); - if (!bdc) - return -ENOMEM; - - memset(res, 0x00, sizeof(struct resource) * ARRAY_SIZE(res)); - bdc_setup_msi(pci); - - res[0].start = pci_resource_start(pci, 0); - res[0].end = pci_resource_end(pci, 0); - res[0].name = BRCM_BDC_NAME; - res[0].flags = IORESOURCE_MEM; - - res[1].start = pci->irq; - res[1].name = BRCM_BDC_NAME; - res[1].flags = IORESOURCE_IRQ; - - ret = platform_device_add_resources(bdc, res, ARRAY_SIZE(res)); - if (ret) { - dev_err(&pci->dev, - "couldn't add resources to bdc device\n"); - platform_device_put(bdc); - return ret; - } - - pci_set_drvdata(pci, glue); - - dma_set_coherent_mask(&bdc->dev, pci->dev.coherent_dma_mask); - - bdc->dev.dma_mask = pci->dev.dma_mask; - bdc->dev.dma_parms = pci->dev.dma_parms; - bdc->dev.parent = &pci->dev; - glue->bdc = bdc; - - ret = platform_device_add(bdc); - if (ret) { - dev_err(&pci->dev, "failed to register bdc device\n"); - platform_device_put(bdc); - return ret; - } - - return 0; -} - -static void bdc_pci_remove(struct pci_dev *pci) -{ - struct bdc_pci *glue = pci_get_drvdata(pci); - - platform_device_unregister(glue->bdc); - pci_disable_msi(pci); -} - -static struct pci_device_id bdc_pci_id_table[] = { - { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, BDC_PCI_PID), }, - {} /* Terminating Entry */ -}; - -MODULE_DEVICE_TABLE(pci, bdc_pci_id_table); - -static struct pci_driver bdc_pci_driver = { - .name = "bdc-pci", - .id_table = bdc_pci_id_table, - .probe = bdc_pci_probe, - .remove = bdc_pci_remove, -}; - -MODULE_AUTHOR("Ashwini Pahuja "); -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("BRCM BDC USB3 PCI Glue layer"); -module_pci_driver(bdc_pci_driver); -- cgit v1.2.3 From f2fc9ff28d1c9bef7760516feadd38164044caae Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Wed, 13 Jan 2021 18:52:46 -0800 Subject: usb: ch9: Add USB 3.2 SSP attributes In preparation for USB 3.2 dual-lane support, add sublink speed attribute macros and enum usb_ssp_rate. A USB device that operates in SuperSpeed Plus may operate at different speed and lane count. These additional macros and enum values help specifying that. Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/ae9293ebd63a29f2a2035054753534d9eb123d74.1610592135.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- include/linux/usb/ch9.h | 9 +++++++++ include/uapi/linux/usb/ch9.h | 13 +++++++++++++ 2 files changed, 22 insertions(+) diff --git a/include/linux/usb/ch9.h b/include/linux/usb/ch9.h index 604c6c514a50..86c50907634e 100644 --- a/include/linux/usb/ch9.h +++ b/include/linux/usb/ch9.h @@ -36,6 +36,15 @@ #include #include +/* USB 3.2 SuperSpeed Plus phy signaling rate generation and lane count */ + +enum usb_ssp_rate { + USB_SSP_GEN_UNKNOWN = 0, + USB_SSP_GEN_2x1, + USB_SSP_GEN_1x2, + USB_SSP_GEN_2x2, +}; + /** * usb_ep_type_string() - Returns human readable-name of the endpoint type. * @ep_type: The endpoint type to return human-readable name for. If it's not diff --git a/include/uapi/linux/usb/ch9.h b/include/uapi/linux/usb/ch9.h index 0f865ae4ba89..17ce56198c9a 100644 --- a/include/uapi/linux/usb/ch9.h +++ b/include/uapi/linux/usb/ch9.h @@ -968,9 +968,22 @@ struct usb_ssp_cap_descriptor { __le32 bmSublinkSpeedAttr[1]; /* list of sublink speed attrib entries */ #define USB_SSP_SUBLINK_SPEED_SSID (0xf) /* sublink speed ID */ #define USB_SSP_SUBLINK_SPEED_LSE (0x3 << 4) /* Lanespeed exponent */ +#define USB_SSP_SUBLINK_SPEED_LSE_BPS 0 +#define USB_SSP_SUBLINK_SPEED_LSE_KBPS 1 +#define USB_SSP_SUBLINK_SPEED_LSE_MBPS 2 +#define USB_SSP_SUBLINK_SPEED_LSE_GBPS 3 + #define USB_SSP_SUBLINK_SPEED_ST (0x3 << 6) /* Sublink type */ +#define USB_SSP_SUBLINK_SPEED_ST_SYM_RX 0 +#define USB_SSP_SUBLINK_SPEED_ST_ASYM_RX 1 +#define USB_SSP_SUBLINK_SPEED_ST_SYM_TX 2 +#define USB_SSP_SUBLINK_SPEED_ST_ASYM_TX 3 + #define USB_SSP_SUBLINK_SPEED_RSVD (0x3f << 8) /* Reserved */ #define USB_SSP_SUBLINK_SPEED_LP (0x3 << 14) /* Link protocol */ +#define USB_SSP_SUBLINK_SPEED_LP_SS 0 +#define USB_SSP_SUBLINK_SPEED_LP_SSP 1 + #define USB_SSP_SUBLINK_SPEED_LSM (0xff << 16) /* Lanespeed mantissa */ } __attribute__((packed)); -- cgit v1.2.3 From 121fc3ac2f02a2316c6451f1ee9d8ef5932441d2 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Wed, 13 Jan 2021 18:52:54 -0800 Subject: usb: gadget: composite: Use SSP sublink speed macros Use SuperSpeed Plus sublink speed macros to fill the BOS descriptor sublink speed attributes in the composite driver. They're self-documented so we can remove some of the comments, and this helps with readability by removing the magic numbers. Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/6f74d446aa164f66fbe4161e28547e28851f6a02.1610592135.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/composite.c | 45 ++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 9a7cc08d6593..bc17302a9e85 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -749,32 +750,34 @@ static int bos_desc(struct usb_composite_dev *cdev) ssp_cap->bReserved = 0; ssp_cap->wReserved = 0; - /* SSAC = 1 (2 attributes) */ - ssp_cap->bmAttributes = cpu_to_le32(1); + ssp_cap->bmAttributes = + cpu_to_le32(FIELD_PREP(USB_SSP_SUBLINK_SPEED_ATTRIBS, 1) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_IDS, 0)); - /* Min RX/TX Lane Count = 1 */ ssp_cap->wFunctionalitySupport = - cpu_to_le16((1 << 8) | (1 << 12)); + cpu_to_le16(FIELD_PREP(USB_SSP_MIN_SUBLINK_SPEED_ATTRIBUTE_ID, 0) | + FIELD_PREP(USB_SSP_MIN_RX_LANE_COUNT, 1) | + FIELD_PREP(USB_SSP_MIN_TX_LANE_COUNT, 1)); - /* - * bmSublinkSpeedAttr[0]: - * ST = Symmetric, RX - * LSE = 3 (Gbps) - * LP = 1 (SuperSpeedPlus) - * LSM = 10 (10 Gbps) - */ ssp_cap->bmSublinkSpeedAttr[0] = - cpu_to_le32((3 << 4) | (1 << 14) | (0xa << 16)); - /* - * bmSublinkSpeedAttr[1] = - * ST = Symmetric, TX - * LSE = 3 (Gbps) - * LP = 1 (SuperSpeedPlus) - * LSM = 10 (10 Gbps) - */ + cpu_to_le32(FIELD_PREP(USB_SSP_SUBLINK_SPEED_SSID, 0) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSE, + USB_SSP_SUBLINK_SPEED_LSE_GBPS) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST, + USB_SSP_SUBLINK_SPEED_ST_SYM_RX) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_LP, + USB_SSP_SUBLINK_SPEED_LP_SSP) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSM, 10)); + ssp_cap->bmSublinkSpeedAttr[1] = - cpu_to_le32((3 << 4) | (1 << 14) | - (0xa << 16) | (1 << 7)); + cpu_to_le32(FIELD_PREP(USB_SSP_SUBLINK_SPEED_SSID, 0) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSE, + USB_SSP_SUBLINK_SPEED_LSE_GBPS) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST, + USB_SSP_SUBLINK_SPEED_ST_SYM_TX) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_LP, + USB_SSP_SUBLINK_SPEED_LP_SSP) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSM, 10)); } return le16_to_cpu(bos->wTotalLength); -- cgit v1.2.3 From db615c6264cffcd8f117c5628db1794afbd0f254 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Wed, 13 Jan 2021 18:53:00 -0800 Subject: usb: gadget: Introduce SSP rates and lanes A USB device controller operating in SuperSpeed Plus may support gen2x1, gen1x2, and/or gen2x2. Introduce SuperSpeed Plus signaling rate generation and lane count to usb_gadget with the fields ssp_rate and max_ssp_rate. The gadget driver can use these to setup the device BOS descriptor and select the desire operating speed and number of lanes. Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/b6d2196dcc3c73747f91abf9a082b20bbe276cc4.1610592135.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- include/linux/usb/gadget.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index e7351d64f11f..02483c862444 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -339,6 +339,10 @@ struct usb_gadget_ops { * @speed: Speed of current connection to USB host. * @max_speed: Maximal speed the UDC can handle. UDC must support this * and all slower speeds. + * @ssp_rate: Current connected SuperSpeed Plus signaling rate and lane count. + * @max_ssp_rate: Maximum SuperSpeed Plus signaling rate and lane count the UDC + * can handle. The UDC must support this and all slower speeds and lower + * number of lanes. * @state: the state we are now (attached, suspended, configured, etc) * @name: Identifies the controller hardware type. Used in diagnostics * and sometimes configuration. @@ -406,6 +410,11 @@ struct usb_gadget { struct list_head ep_list; /* of usb_ep */ enum usb_device_speed speed; enum usb_device_speed max_speed; + + /* USB SuperSpeed Plus only */ + enum usb_ssp_rate ssp_rate; + enum usb_ssp_rate max_ssp_rate; + enum usb_device_state state; const char *name; struct device dev; -- cgit v1.2.3 From ead4c124852e66b6aa033e34cf9c4f08d40aeffc Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Wed, 13 Jan 2021 18:53:07 -0800 Subject: usb: gadget: Introduce udc_set_ssp_rate() for SSP A SuperSpeed Plus device may operate at different speed and lane count (i.e. gen2x2, gen1x2, or gen2x1). Introduce gadget ops udc_set_ssp_rate() to set the desire corresponding usb_ssp_rate for SuperSpeed Plus capable devices. If the USB device supports different speeds at SuperSpeed Plus, set the device to operate with the maximum number of lanes and speed. Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/9b85357cdadc02e3f0d653fd05f89eb46af836e1.1610592135.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/core.c | 16 +++++++++++----- include/linux/usb/gadget.h | 2 ++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index 98cf9216f3cb..4173acdcd35b 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -1133,12 +1133,18 @@ static inline void usb_gadget_udc_stop(struct usb_udc *udc) static inline void usb_gadget_udc_set_speed(struct usb_udc *udc, enum usb_device_speed speed) { - if (udc->gadget->ops->udc_set_speed) { - enum usb_device_speed s; + struct usb_gadget *gadget = udc->gadget; + enum usb_device_speed s; - s = min(speed, udc->gadget->max_speed); - udc->gadget->ops->udc_set_speed(udc->gadget, s); - } + if (speed == USB_SPEED_UNKNOWN) + s = gadget->max_speed; + else + s = min(speed, gadget->max_speed); + + if (s == USB_SPEED_SUPER_PLUS && gadget->ops->udc_set_ssp_rate) + gadget->ops->udc_set_ssp_rate(gadget, gadget->max_ssp_rate); + else if (gadget->ops->udc_set_speed) + gadget->ops->udc_set_speed(gadget, s); } /** diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index 02483c862444..ee04ef214ce8 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -323,6 +323,8 @@ struct usb_gadget_ops { struct usb_gadget_driver *); int (*udc_stop)(struct usb_gadget *); void (*udc_set_speed)(struct usb_gadget *, enum usb_device_speed); + void (*udc_set_ssp_rate)(struct usb_gadget *gadget, + enum usb_ssp_rate rate); struct usb_ep *(*match_ep)(struct usb_gadget *, struct usb_endpoint_descriptor *, struct usb_ss_ep_comp_descriptor *); -- cgit v1.2.3 From 7bf0fc5a6b6e45924141c34c065da10bb6858fc2 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Wed, 13 Jan 2021 18:53:14 -0800 Subject: usb: gadget: composite: Report various SSP sublink speeds If a gadget supports SuperSpeed Plus, then it may operate in different sublink speeds. For example, if the gadget supports SuperSpeed Plus gen2x2, then it can support 2 sublink speeds gen1 and gen2. Inform the host of these speeds in the BOS descriptor. Use 1 SSID if the gadget supports up to gen2x1, or not specified: - SSID 0 for symmetric RX/TX sublink speed of 10 Gbps. Use 1 SSID if the gadget supports up to gen1x2: - SSID 0 for symmetric RX/TX sublink speed of 5 Gbps. Use 2 SSIDs if the gadget supports up to gen2x2: - SSID 0 for symmetric RX/TX sublink speed of 5 Gbps. - SSID 1 for symmetric RX/TX sublink speed of 10 Gbps. Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/eb0386fdd5d87a858281e8006a72723d3732240f.1610592135.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/composite.c | 80 ++++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 26 deletions(-) diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index bc17302a9e85..72a9797dbbae 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -735,49 +735,77 @@ static int bos_desc(struct usb_composite_dev *cdev) /* The SuperSpeedPlus USB Device Capability descriptor */ if (gadget_is_superspeed_plus(cdev->gadget)) { struct usb_ssp_cap_descriptor *ssp_cap; + u8 ssac = 1; + u8 ssic; + int i; - ssp_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength); - bos->bNumDeviceCaps++; + if (cdev->gadget->max_ssp_rate == USB_SSP_GEN_2x2) + ssac = 3; /* - * Report typical values. + * Paired RX and TX sublink speed attributes share + * the same SSID. */ + ssic = (ssac + 1) / 2 - 1; - le16_add_cpu(&bos->wTotalLength, USB_DT_USB_SSP_CAP_SIZE(1)); - ssp_cap->bLength = USB_DT_USB_SSP_CAP_SIZE(1); + ssp_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength); + bos->bNumDeviceCaps++; + + le16_add_cpu(&bos->wTotalLength, USB_DT_USB_SSP_CAP_SIZE(ssac)); + ssp_cap->bLength = USB_DT_USB_SSP_CAP_SIZE(ssac); ssp_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY; ssp_cap->bDevCapabilityType = USB_SSP_CAP_TYPE; ssp_cap->bReserved = 0; ssp_cap->wReserved = 0; ssp_cap->bmAttributes = - cpu_to_le32(FIELD_PREP(USB_SSP_SUBLINK_SPEED_ATTRIBS, 1) | - FIELD_PREP(USB_SSP_SUBLINK_SPEED_IDS, 0)); + cpu_to_le32(FIELD_PREP(USB_SSP_SUBLINK_SPEED_ATTRIBS, ssac) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_IDS, ssic)); ssp_cap->wFunctionalitySupport = cpu_to_le16(FIELD_PREP(USB_SSP_MIN_SUBLINK_SPEED_ATTRIBUTE_ID, 0) | FIELD_PREP(USB_SSP_MIN_RX_LANE_COUNT, 1) | FIELD_PREP(USB_SSP_MIN_TX_LANE_COUNT, 1)); - ssp_cap->bmSublinkSpeedAttr[0] = - cpu_to_le32(FIELD_PREP(USB_SSP_SUBLINK_SPEED_SSID, 0) | - FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSE, - USB_SSP_SUBLINK_SPEED_LSE_GBPS) | - FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST, - USB_SSP_SUBLINK_SPEED_ST_SYM_RX) | - FIELD_PREP(USB_SSP_SUBLINK_SPEED_LP, - USB_SSP_SUBLINK_SPEED_LP_SSP) | - FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSM, 10)); - - ssp_cap->bmSublinkSpeedAttr[1] = - cpu_to_le32(FIELD_PREP(USB_SSP_SUBLINK_SPEED_SSID, 0) | - FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSE, - USB_SSP_SUBLINK_SPEED_LSE_GBPS) | - FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST, - USB_SSP_SUBLINK_SPEED_ST_SYM_TX) | - FIELD_PREP(USB_SSP_SUBLINK_SPEED_LP, - USB_SSP_SUBLINK_SPEED_LP_SSP) | - FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSM, 10)); + /* + * Use 1 SSID if the gadget supports up to gen2x1 or not + * specified: + * - SSID 0 for symmetric RX/TX sublink speed of 10 Gbps. + * + * Use 1 SSID if the gadget supports up to gen1x2: + * - SSID 0 for symmetric RX/TX sublink speed of 5 Gbps. + * + * Use 2 SSIDs if the gadget supports up to gen2x2: + * - SSID 0 for symmetric RX/TX sublink speed of 5 Gbps. + * - SSID 1 for symmetric RX/TX sublink speed of 10 Gbps. + */ + for (i = 0; i < ssac + 1; i++) { + u8 ssid; + u8 mantissa; + u8 type; + + ssid = i >> 1; + + if (cdev->gadget->max_ssp_rate == USB_SSP_GEN_2x1 || + cdev->gadget->max_ssp_rate == USB_SSP_GEN_UNKNOWN) + mantissa = 10; + else + mantissa = 5 << ssid; + + if (i % 2) + type = USB_SSP_SUBLINK_SPEED_ST_SYM_TX; + else + type = USB_SSP_SUBLINK_SPEED_ST_SYM_RX; + + ssp_cap->bmSublinkSpeedAttr[i] = + cpu_to_le32(FIELD_PREP(USB_SSP_SUBLINK_SPEED_SSID, ssid) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSE, + USB_SSP_SUBLINK_SPEED_LSE_GBPS) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST, type) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_LP, + USB_SSP_SUBLINK_SPEED_LP_SSP) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSM, mantissa)); + } } return le16_to_cpu(bos->wTotalLength); -- cgit v1.2.3 From 7de8681be2cde9f6953d3be1fa6ce05f9fe6e637 Mon Sep 17 00:00:00 2001 From: Jack Pham Date: Mon, 18 Jan 2021 09:46:39 +0100 Subject: usb: gadget: u_audio: Free requests only after callback As per the kernel doc for usb_ep_dequeue(), it states that "this routine is asynchronous, that is, it may return before the completion routine runs". And indeed since v5.0 the dwc3 gadget driver updated its behavior to place dequeued requests on to a cancelled list to be given back later after the endpoint is stopped. The free_ep() was incorrectly assuming that a request was ready to be freed after calling dequeue which results in a use-after-free in dwc3 when it traverses its cancelled list. Fix this by moving the usb_ep_free_request() call to the callback itself in case the ep is disabled. Fixes: eb9fecb9e69b0 ("usb: gadget: f_uac2: split out audio core") Reported-and-tested-by: Ferry Toth Reviewed-and-tested-by: Peter Chen Acked-by: Felipe Balbi Signed-off-by: Jack Pham Signed-off-by: Jerome Brunet Link: https://lore.kernel.org/r/20210118084642.322510-2-jbrunet@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/u_audio.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c index e6d32c536781..908e49dafd62 100644 --- a/drivers/usb/gadget/function/u_audio.c +++ b/drivers/usb/gadget/function/u_audio.c @@ -89,7 +89,12 @@ static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req) struct snd_uac_chip *uac = prm->uac; /* i/f shutting down */ - if (!prm->ep_enabled || req->status == -ESHUTDOWN) + if (!prm->ep_enabled) { + usb_ep_free_request(ep, req); + return; + } + + if (req->status == -ESHUTDOWN) return; /* @@ -336,8 +341,14 @@ static inline void free_ep(struct uac_rtd_params *prm, struct usb_ep *ep) for (i = 0; i < params->req_number; i++) { if (prm->ureq[i].req) { - usb_ep_dequeue(ep, prm->ureq[i].req); - usb_ep_free_request(ep, prm->ureq[i].req); + if (usb_ep_dequeue(ep, prm->ureq[i].req)) + usb_ep_free_request(ep, prm->ureq[i].req); + /* + * If usb_ep_dequeue() cannot successfully dequeue the + * request, the request will be freed by the completion + * callback. + */ + prm->ureq[i].req = NULL; } } -- cgit v1.2.3 From 25dbd75dd506cd19ab4405a7ef0816042fe6892f Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Mon, 18 Jan 2021 09:46:40 +0100 Subject: usb: gadget: u_audio: factorize ssize to alsa fmt conversion Factorize format related code common to the capture and playback path. Acked-by: Felipe Balbi Signed-off-by: Jerome Brunet Link: https://lore.kernel.org/r/20210118084642.322510-3-jbrunet@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/u_audio.c | 43 +++++++++++++++++------------------ 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c index 908e49dafd62..27f941f71a9d 100644 --- a/drivers/usb/gadget/function/u_audio.c +++ b/drivers/usb/gadget/function/u_audio.c @@ -244,6 +244,25 @@ static snd_pcm_uframes_t uac_pcm_pointer(struct snd_pcm_substream *substream) return bytes_to_frames(substream->runtime, prm->hw_ptr); } +static u64 uac_ssize_to_fmt(int ssize) +{ + u64 ret; + + switch (ssize) { + case 3: + ret = SNDRV_PCM_FMTBIT_S24_3LE; + break; + case 4: + ret = SNDRV_PCM_FMTBIT_S32_LE; + break; + default: + ret = SNDRV_PCM_FMTBIT_S16_LE; + break; + } + + return ret; +} + static int uac_pcm_open(struct snd_pcm_substream *substream) { struct snd_uac_chip *uac = snd_pcm_substream_chip(substream); @@ -269,34 +288,14 @@ static int uac_pcm_open(struct snd_pcm_substream *substream) if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { spin_lock_init(&uac->p_prm.lock); runtime->hw.rate_min = p_srate; - switch (p_ssize) { - case 3: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE; - break; - case 4: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; - break; - default: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; - break; - } + runtime->hw.formats = uac_ssize_to_fmt(p_ssize); runtime->hw.channels_min = num_channels(p_chmask); runtime->hw.period_bytes_min = 2 * uac->p_prm.max_psize / runtime->hw.periods_min; } else { spin_lock_init(&uac->c_prm.lock); runtime->hw.rate_min = c_srate; - switch (c_ssize) { - case 3: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE; - break; - case 4: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; - break; - default: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; - break; - } + runtime->hw.formats = uac_ssize_to_fmt(c_ssize); runtime->hw.channels_min = num_channels(c_chmask); runtime->hw.period_bytes_min = 2 * uac->c_prm.max_psize / runtime->hw.periods_min; -- cgit v1.2.3 From 2986511780438274646f63a171ac60e61313032c Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Mon, 18 Jan 2021 09:46:41 +0100 Subject: usb: gadget: u_audio: remove struct uac_req 'struct uac_req' purpose is to link 'struct usb_request' to the corresponding 'struct uac_rtd_params'. However member req is never used. Using the context of the usb request, we can keep track of the corresponding 'struct uac_rtd_params' just as well, without allocating extra memory. Acked-by: Felipe Balbi Signed-off-by: Jerome Brunet Link: https://lore.kernel.org/r/20210118084642.322510-4-jbrunet@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/u_audio.c | 58 ++++++++++++++++------------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c index 27f941f71a9d..a1a1f4c8685c 100644 --- a/drivers/usb/gadget/function/u_audio.c +++ b/drivers/usb/gadget/function/u_audio.c @@ -23,11 +23,6 @@ #define PRD_SIZE_MAX PAGE_SIZE #define MIN_PERIODS 4 -struct uac_req { - struct uac_rtd_params *pp; /* parent param */ - struct usb_request *req; -}; - /* Runtime data params for one stream */ struct uac_rtd_params { struct snd_uac_chip *uac; /* parent chip */ @@ -41,7 +36,7 @@ struct uac_rtd_params { void *rbuf; unsigned int max_psize; /* MaxPacketSize of endpoint */ - struct uac_req *ureq; + struct usb_request **reqs; spinlock_t lock; }; @@ -82,10 +77,9 @@ static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req) unsigned long flags, flags2; unsigned int hw_ptr; int status = req->status; - struct uac_req *ur = req->context; struct snd_pcm_substream *substream; struct snd_pcm_runtime *runtime; - struct uac_rtd_params *prm = ur->pp; + struct uac_rtd_params *prm = req->context; struct snd_uac_chip *uac = prm->uac; /* i/f shutting down */ @@ -339,16 +333,16 @@ static inline void free_ep(struct uac_rtd_params *prm, struct usb_ep *ep) params = &audio_dev->params; for (i = 0; i < params->req_number; i++) { - if (prm->ureq[i].req) { - if (usb_ep_dequeue(ep, prm->ureq[i].req)) - usb_ep_free_request(ep, prm->ureq[i].req); + if (prm->reqs[i]) { + if (usb_ep_dequeue(ep, prm->reqs[i])) + usb_ep_free_request(ep, prm->reqs[i]); /* * If usb_ep_dequeue() cannot successfully dequeue the * request, the request will be freed by the completion * callback. */ - prm->ureq[i].req = NULL; + prm->reqs[i] = NULL; } } @@ -377,22 +371,21 @@ int u_audio_start_capture(struct g_audio *audio_dev) usb_ep_enable(ep); for (i = 0; i < params->req_number; i++) { - if (!prm->ureq[i].req) { + if (!prm->reqs[i]) { req = usb_ep_alloc_request(ep, GFP_ATOMIC); if (req == NULL) return -ENOMEM; - prm->ureq[i].req = req; - prm->ureq[i].pp = prm; + prm->reqs[i] = req; req->zero = 0; - req->context = &prm->ureq[i]; + req->context = prm; req->length = req_len; req->complete = u_audio_iso_complete; req->buf = prm->rbuf + i * ep->maxpacket; } - if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC)) + if (usb_ep_queue(ep, prm->reqs[i], GFP_ATOMIC)) dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); } @@ -455,22 +448,21 @@ int u_audio_start_playback(struct g_audio *audio_dev) usb_ep_enable(ep); for (i = 0; i < params->req_number; i++) { - if (!prm->ureq[i].req) { + if (!prm->reqs[i]) { req = usb_ep_alloc_request(ep, GFP_ATOMIC); if (req == NULL) return -ENOMEM; - prm->ureq[i].req = req; - prm->ureq[i].pp = prm; + prm->reqs[i] = req; req->zero = 0; - req->context = &prm->ureq[i]; + req->context = prm; req->length = req_len; req->complete = u_audio_iso_complete; req->buf = prm->rbuf + i * ep->maxpacket; } - if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC)) + if (usb_ep_queue(ep, prm->reqs[i], GFP_ATOMIC)) dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); } @@ -515,9 +507,10 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, uac->c_prm.uac = uac; prm->max_psize = g_audio->out_ep_maxpsize; - prm->ureq = kcalloc(params->req_number, sizeof(struct uac_req), - GFP_KERNEL); - if (!prm->ureq) { + prm->reqs = kcalloc(params->req_number, + sizeof(struct usb_request *), + GFP_KERNEL); + if (!prm->reqs) { err = -ENOMEM; goto fail; } @@ -537,9 +530,10 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, uac->p_prm.uac = uac; prm->max_psize = g_audio->in_ep_maxpsize; - prm->ureq = kcalloc(params->req_number, sizeof(struct uac_req), - GFP_KERNEL); - if (!prm->ureq) { + prm->reqs = kcalloc(params->req_number, + sizeof(struct usb_request *), + GFP_KERNEL); + if (!prm->reqs) { err = -ENOMEM; goto fail; } @@ -592,8 +586,8 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, snd_fail: snd_card_free(card); fail: - kfree(uac->p_prm.ureq); - kfree(uac->c_prm.ureq); + kfree(uac->p_prm.reqs); + kfree(uac->c_prm.reqs); kfree(uac->p_prm.rbuf); kfree(uac->c_prm.rbuf); kfree(uac); @@ -615,8 +609,8 @@ void g_audio_cleanup(struct g_audio *g_audio) if (card) snd_card_free(card); - kfree(uac->p_prm.ureq); - kfree(uac->c_prm.ureq); + kfree(uac->p_prm.reqs); + kfree(uac->c_prm.reqs); kfree(uac->p_prm.rbuf); kfree(uac->c_prm.rbuf); kfree(uac); -- cgit v1.2.3 From d70f7598c4583c6326cbd499b66bc9b3dd821e12 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Mon, 18 Jan 2021 09:49:31 +0100 Subject: usb: gadget: u_audio: clean up locking snd_pcm_stream_lock() is held when the ALSA .trigger() callback is called. The lock of 'struct uac_rtd_params' is not necessary since all its locking operation are done under the snd_pcm_stream_lock() too. Also, usb_request .complete() is called with irqs disabled, so saving and restoring the irqs is not necessary. Acked-by: Felipe Balbi Signed-off-by: Jerome Brunet Link: https://lore.kernel.org/r/20210118084931.322861-1-jbrunet@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/u_audio.c | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c index a1a1f4c8685c..265c4d805f81 100644 --- a/drivers/usb/gadget/function/u_audio.c +++ b/drivers/usb/gadget/function/u_audio.c @@ -36,9 +36,8 @@ struct uac_rtd_params { void *rbuf; unsigned int max_psize; /* MaxPacketSize of endpoint */ - struct usb_request **reqs; - spinlock_t lock; + struct usb_request **reqs; }; struct snd_uac_chip { @@ -74,7 +73,6 @@ static const struct snd_pcm_hardware uac_pcm_hardware = { static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req) { unsigned int pending; - unsigned long flags, flags2; unsigned int hw_ptr; int status = req->status; struct snd_pcm_substream *substream; @@ -105,16 +103,14 @@ static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req) if (!substream) goto exit; - snd_pcm_stream_lock_irqsave(substream, flags2); + snd_pcm_stream_lock(substream); runtime = substream->runtime; if (!runtime || !snd_pcm_running(substream)) { - snd_pcm_stream_unlock_irqrestore(substream, flags2); + snd_pcm_stream_unlock(substream); goto exit; } - spin_lock_irqsave(&prm->lock, flags); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { /* * For each IN packet, take the quotient of the current data @@ -141,8 +137,6 @@ static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req) hw_ptr = prm->hw_ptr; - spin_unlock_irqrestore(&prm->lock, flags); - /* Pack USB load in ALSA ring buffer */ pending = runtime->dma_bytes - hw_ptr; @@ -166,12 +160,10 @@ static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req) } } - spin_lock_irqsave(&prm->lock, flags); /* update hw_ptr after data is copied to memory */ prm->hw_ptr = (hw_ptr + req->actual) % runtime->dma_bytes; hw_ptr = prm->hw_ptr; - spin_unlock_irqrestore(&prm->lock, flags); - snd_pcm_stream_unlock_irqrestore(substream, flags2); + snd_pcm_stream_unlock(substream); if ((hw_ptr % snd_pcm_lib_period_bytes(substream)) < req->actual) snd_pcm_period_elapsed(substream); @@ -187,7 +179,6 @@ static int uac_pcm_trigger(struct snd_pcm_substream *substream, int cmd) struct uac_rtd_params *prm; struct g_audio *audio_dev; struct uac_params *params; - unsigned long flags; int err = 0; audio_dev = uac->audio_dev; @@ -198,8 +189,6 @@ static int uac_pcm_trigger(struct snd_pcm_substream *substream, int cmd) else prm = &uac->c_prm; - spin_lock_irqsave(&prm->lock, flags); - /* Reset */ prm->hw_ptr = 0; @@ -216,8 +205,6 @@ static int uac_pcm_trigger(struct snd_pcm_substream *substream, int cmd) err = -EINVAL; } - spin_unlock_irqrestore(&prm->lock, flags); - /* Clear buffer after Play stops */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !prm->ss) memset(prm->rbuf, 0, prm->max_psize * params->req_number); @@ -280,14 +267,12 @@ static int uac_pcm_open(struct snd_pcm_substream *substream) runtime->hw = uac_pcm_hardware; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - spin_lock_init(&uac->p_prm.lock); runtime->hw.rate_min = p_srate; runtime->hw.formats = uac_ssize_to_fmt(p_ssize); runtime->hw.channels_min = num_channels(p_chmask); runtime->hw.period_bytes_min = 2 * uac->p_prm.max_psize / runtime->hw.periods_min; } else { - spin_lock_init(&uac->c_prm.lock); runtime->hw.rate_min = c_srate; runtime->hw.formats = uac_ssize_to_fmt(c_ssize); runtime->hw.channels_min = num_channels(c_chmask); -- cgit v1.2.3 From c00243e7cd5c5c018f8addd6d7c234e2ef16d202 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Tue, 19 Jan 2021 11:34:05 +0300 Subject: usb: typec: ucsi: Add conditional dependency on USB role switch Preventing the driver from being built-in when USB Role Switch Class is being build as module. That fixes a potential undefined reference error. Fixes: 89795852c9c4 ("usb: typec: ucsi: Add support for USB role switch") Reported-by: kernel test robot Signed-off-by: Heikki Krogerus Link: https://lore.kernel.org/r/20210119083405.18325-1-heikki.krogerus@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig index 2192d7c4fec7..5e9b37b3f25e 100644 --- a/drivers/usb/typec/ucsi/Kconfig +++ b/drivers/usb/typec/ucsi/Kconfig @@ -3,6 +3,7 @@ config TYPEC_UCSI tristate "USB Type-C Connector System Software Interface driver" depends on !CPU_BIG_ENDIAN + depends on USB_ROLE_SWITCH || !USB_ROLE_SWITCH help USB Type-C Connector System Software Interface (UCSI) is a specification for an interface that allows the operating system to -- cgit v1.2.3 From 79f06f04db653f57fd9e344c20b1a8b70c75ec50 Mon Sep 17 00:00:00 2001 From: Davidlohr Bueso Date: Mon, 18 Jan 2021 16:13:21 -0800 Subject: usb: gadget: u_serial: Remove old tasklet comments Update old comments as of 8b4c62aef6f (usb: gadget: u_serial: process RX in workqueue instead of tasklet). Acked-by: Felipe Balbi Signed-off-by: Davidlohr Bueso Link: https://lore.kernel.org/r/20210119001321.127750-1-dave@stgolabs.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/u_serial.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c index 768f883f486c..1e59204ec7aa 100644 --- a/drivers/usb/gadget/function/u_serial.c +++ b/drivers/usb/gadget/function/u_serial.c @@ -344,7 +344,7 @@ __acquires(&port->port_lock) } /* - * RX tasklet takes data out of the RX queue and hands it up to the TTY + * RX work takes data out of the RX queue and hands it up to the TTY * layer until it refuses to take any more data (or is throttled back). * Then it issues reads for any further data. * @@ -707,7 +707,7 @@ raced_with_open: /* Iff we're disconnected, there can be no I/O in flight so it's * ok to free the circular buffer; else just scrub it. And don't - * let the push tasklet fire again until we're re-opened. + * let the push async work fire again until we're re-opened. */ if (gser == NULL) kfifo_free(&port->port_write_buf); -- cgit v1.2.3 From 908f6e2b8a7987ae761d80ea05738aa2a42c4474 Mon Sep 17 00:00:00 2001 From: Davidlohr Bueso Date: Mon, 18 Jan 2021 16:16:53 -0800 Subject: USB: gadget: udc: Process disconnect synchronously As the comment in usb_disconnect() hints, do not defer the disconnect processing, and instead just do it directly in the irq handler. This allows the driver to avoid using a nowadays deprecated tasklet. Acked-by: Felipe Balbi Signed-off-by: Davidlohr Bueso Link: https://lore.kernel.org/r/20210119001653.127975-1-dave@stgolabs.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/snps_udc_core.c | 30 +++--------------------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/drivers/usb/gadget/udc/snps_udc_core.c b/drivers/usb/gadget/udc/snps_udc_core.c index 6c726d2e1788..d046c09fa566 100644 --- a/drivers/usb/gadget/udc/snps_udc_core.c +++ b/drivers/usb/gadget/udc/snps_udc_core.c @@ -36,7 +36,6 @@ #include #include "amd5536udc.h" -static void udc_tasklet_disconnect(unsigned long); static void udc_setup_endpoints(struct udc *dev); static void udc_soft_reset(struct udc *dev); static struct udc_request *udc_alloc_bna_dummy(struct udc_ep *ep); @@ -95,9 +94,6 @@ static struct timer_list udc_pollstall_timer; static int stop_pollstall_timer; static DECLARE_COMPLETION(on_pollstall_exit); -/* tasklet for usb disconnect */ -static DECLARE_TASKLET_OLD(disconnect_tasklet, udc_tasklet_disconnect); - /* endpoint names used for print */ static const char ep0_string[] = "ep0in"; static const struct { @@ -1637,6 +1633,8 @@ static void usb_connect(struct udc *dev) */ static void usb_disconnect(struct udc *dev) { + u32 tmp; + /* Return if already disconnected */ if (!dev->connected) return; @@ -1648,23 +1646,6 @@ static void usb_disconnect(struct udc *dev) /* mask interrupts */ udc_mask_unused_interrupts(dev); - /* REVISIT there doesn't seem to be a point to having this - * talk to a tasklet ... do it directly, we already hold - * the spinlock needed to process the disconnect. - */ - - tasklet_schedule(&disconnect_tasklet); -} - -/* Tasklet for disconnect to be outside of interrupt context */ -static void udc_tasklet_disconnect(unsigned long par) -{ - struct udc *dev = udc; - u32 tmp; - - DBG(dev, "Tasklet disconnect\n"); - spin_lock_irq(&dev->lock); - if (dev->driver) { spin_unlock(&dev->lock); dev->driver->disconnect(&dev->gadget); @@ -1673,13 +1654,10 @@ static void udc_tasklet_disconnect(unsigned long par) /* empty queues */ for (tmp = 0; tmp < UDC_EP_NUM; tmp++) empty_req_queue(&dev->ep[tmp]); - } /* disable ep0 */ - ep_init(dev->regs, - &dev->ep[UDC_EP0IN_IX]); - + ep_init(dev->regs, &dev->ep[UDC_EP0IN_IX]); if (!soft_reset_occured) { /* init controller by soft reset */ @@ -1695,8 +1673,6 @@ static void udc_tasklet_disconnect(unsigned long par) tmp = AMD_ADDBITS(tmp, UDC_DEVCFG_SPD_FS, UDC_DEVCFG_SPD); writel(tmp, &dev->regs->cfg); } - - spin_unlock_irq(&dev->lock); } /* Reset the UDC core */ -- cgit v1.2.3 From 1c17cc47d764c802c4fa74b46e29fa515ea7acc7 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Mon, 18 Jan 2021 10:39:59 +0530 Subject: dt-bindings: usb: qcom,dwc3: Add binding for SDX55 Add devicetree binding for SDX55 USB controller based on Qcom designware IP. Cc: Rob Herring Cc: devicetree@vger.kernel.org Cc: linux-usb@vger.kernel.org Acked-by: Felipe Balbi Signed-off-by: Manivannan Sadhasivam Link: https://lore.kernel.org/r/20210118051005.55958-2-manivannan.sadhasivam@linaro.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/qcom,dwc3.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml index b336662e838c..dd1d8bcd9254 100644 --- a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml @@ -17,6 +17,7 @@ properties: - qcom,msm8998-dwc3 - qcom,sc7180-dwc3 - qcom,sdm845-dwc3 + - qcom,sdx55-dwc3 - const: qcom,dwc3 reg: -- cgit v1.2.3 From 7a79f1f7f7e75e532c5a803ab3ebf42a3e79497c Mon Sep 17 00:00:00 2001 From: Jack Pham Date: Tue, 19 Jan 2021 09:37:48 -0800 Subject: dt-bindings: usb: qcom,dwc3: Add bindings for SM8150, SM8250, SM8350 Add compatible strings for the USB DWC3 controller on QCOM SM8150, SM8250 and SM8350 SoCs. Note the SM8150 & SM8250 compatibles are already being used in the dts but was missing from the documentation. Acked-by: Felipe Balbi Signed-off-by: Jack Pham Link: https://lore.kernel.org/r/20210119173748.6729-1-jackp@codeaurora.org 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 dd1d8bcd9254..c3cbd1fa9944 100644 --- a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml @@ -18,6 +18,9 @@ properties: - qcom,sc7180-dwc3 - qcom,sdm845-dwc3 - qcom,sdx55-dwc3 + - qcom,sm8150-dwc3 + - qcom,sm8250-dwc3 + - qcom,sm8350-dwc3 - const: qcom,dwc3 reg: -- cgit v1.2.3 From c2d405aa86b451f197ee95cb08887130b86b765e Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Sun, 22 Nov 2020 22:38:20 +0530 Subject: USB: serial: add MaxLinear/Exar USB to Serial driver Add support for MaxLinear/Exar USB to Serial converters. This driver only supports XR21V141X series but it can be extended to other series from Exar as well in future. This driver is inspired from the initial one submitted by Patong Yang: https://lore.kernel.org/r/20180404070634.nhspvmxcjwfgjkcv@advantechmxl-desktop While the initial driver was a custom tty USB driver exposing whole new serial interface ttyXRUSBn, this version is completely based on USB serial core thus exposing the interfaces as ttyUSBn. This will avoid the overhead of exposing a new USB serial interface which the userspace tools are unaware of. The Exar XR21V141X can be used in either ACM mode using the cdc-acm driver or in "custom driver" mode in which further features such as hardware and software flow control, GPIO control and in-band line-status reporting are available. In ACM mode the device always enables RTS/CTS flow control, something which could prevent transmission in case the CTS input isn't wired up corrently. A follow-on patch will prevent cdc_acm from binding whenever this driver is enabled. Signed-off-by: Manivannan Sadhasivam Link: https://lore.kernel.org/r/20201122170822.21715-2-mani@kernel.org [ johan: fix some style nits, group related functions, drop unused callbacks, and amend commit message; a few remaining non-trivial issues will be fixed separately ] Signed-off-by: Johan Hovold --- drivers/usb/serial/Kconfig | 9 + drivers/usb/serial/Makefile | 1 + drivers/usb/serial/xr_serial.c | 595 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 605 insertions(+) create mode 100644 drivers/usb/serial/xr_serial.c diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig index a21ff5ab6df9..de5c01257060 100644 --- a/drivers/usb/serial/Kconfig +++ b/drivers/usb/serial/Kconfig @@ -633,6 +633,15 @@ config USB_SERIAL_UPD78F0730 To compile this driver as a module, choose M here: the module will be called upd78f0730. +config USB_SERIAL_XR + tristate "USB MaxLinear/Exar USB to Serial driver" + help + Say Y here if you want to use MaxLinear/Exar USB to Serial converter + devices. + + To compile this driver as a module, choose M here: the + module will be called xr_serial. + config USB_SERIAL_DEBUG tristate "USB Debugging Device" help diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile index 50c53aed787a..c7bb1a88173e 100644 --- a/drivers/usb/serial/Makefile +++ b/drivers/usb/serial/Makefile @@ -61,4 +61,5 @@ obj-$(CONFIG_USB_SERIAL_UPD78F0730) += upd78f0730.o obj-$(CONFIG_USB_SERIAL_VISOR) += visor.o obj-$(CONFIG_USB_SERIAL_WISHBONE) += wishbone-serial.o obj-$(CONFIG_USB_SERIAL_WHITEHEAT) += whiteheat.o +obj-$(CONFIG_USB_SERIAL_XR) += xr_serial.o obj-$(CONFIG_USB_SERIAL_XSENS_MT) += xsens_mt.o diff --git a/drivers/usb/serial/xr_serial.c b/drivers/usb/serial/xr_serial.c new file mode 100644 index 000000000000..bdb2df27b50b --- /dev/null +++ b/drivers/usb/serial/xr_serial.c @@ -0,0 +1,595 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * MaxLinear/Exar USB to Serial driver + * + * Copyright (c) 2020 Manivannan Sadhasivam + * + * Based on the initial driver written by Patong Yang: + * + * https://lore.kernel.org/r/20180404070634.nhspvmxcjwfgjkcv@advantechmxl-desktop + * + * Copyright (c) 2018 Patong Yang + */ + +#include +#include +#include +#include +#include +#include + +struct xr_txrx_clk_mask { + u16 tx; + u16 rx0; + u16 rx1; +}; + +#define XR_INT_OSC_HZ 48000000U +#define XR21V141X_MIN_SPEED 46U +#define XR21V141X_MAX_SPEED XR_INT_OSC_HZ + +/* USB Requests */ +#define XR21V141X_SET_REQ 0 +#define XR21V141X_GET_REQ 1 + +#define XR21V141X_CLOCK_DIVISOR_0 0x04 +#define XR21V141X_CLOCK_DIVISOR_1 0x05 +#define XR21V141X_CLOCK_DIVISOR_2 0x06 +#define XR21V141X_TX_CLOCK_MASK_0 0x07 +#define XR21V141X_TX_CLOCK_MASK_1 0x08 +#define XR21V141X_RX_CLOCK_MASK_0 0x09 +#define XR21V141X_RX_CLOCK_MASK_1 0x0a + +/* XR21V141X register blocks */ +#define XR21V141X_UART_REG_BLOCK 0 +#define XR21V141X_UM_REG_BLOCK 4 +#define XR21V141X_UART_CUSTOM_BLOCK 0x66 + +/* XR21V141X UART Manager Registers */ +#define XR21V141X_UM_FIFO_ENABLE_REG 0x10 +#define XR21V141X_UM_ENABLE_TX_FIFO 0x01 +#define XR21V141X_UM_ENABLE_RX_FIFO 0x02 + +#define XR21V141X_UM_RX_FIFO_RESET 0x18 +#define XR21V141X_UM_TX_FIFO_RESET 0x1c + +#define XR21V141X_UART_ENABLE_TX 0x1 +#define XR21V141X_UART_ENABLE_RX 0x2 + +#define XR21V141X_UART_MODE_RI BIT(0) +#define XR21V141X_UART_MODE_CD BIT(1) +#define XR21V141X_UART_MODE_DSR BIT(2) +#define XR21V141X_UART_MODE_DTR BIT(3) +#define XR21V141X_UART_MODE_CTS BIT(4) +#define XR21V141X_UART_MODE_RTS BIT(5) + +#define XR21V141X_UART_BREAK_ON 0xff +#define XR21V141X_UART_BREAK_OFF 0 + +#define XR21V141X_UART_DATA_MASK GENMASK(3, 0) +#define XR21V141X_UART_DATA_7 0x7 +#define XR21V141X_UART_DATA_8 0x8 + +#define XR21V141X_UART_PARITY_MASK GENMASK(6, 4) +#define XR21V141X_UART_PARITY_SHIFT 0x4 +#define XR21V141X_UART_PARITY_NONE 0x0 +#define XR21V141X_UART_PARITY_ODD 0x1 +#define XR21V141X_UART_PARITY_EVEN 0x2 +#define XR21V141X_UART_PARITY_MARK 0x3 +#define XR21V141X_UART_PARITY_SPACE 0x4 + +#define XR21V141X_UART_STOP_MASK BIT(7) +#define XR21V141X_UART_STOP_SHIFT 0x7 +#define XR21V141X_UART_STOP_1 0x0 +#define XR21V141X_UART_STOP_2 0x1 + +#define XR21V141X_UART_FLOW_MODE_NONE 0x0 +#define XR21V141X_UART_FLOW_MODE_HW 0x1 +#define XR21V141X_UART_FLOW_MODE_SW 0x2 + +#define XR21V141X_UART_MODE_GPIO_MASK GENMASK(2, 0) +#define XR21V141X_UART_MODE_RTS_CTS 0x1 +#define XR21V141X_UART_MODE_DTR_DSR 0x2 +#define XR21V141X_UART_MODE_RS485 0x3 +#define XR21V141X_UART_MODE_RS485_ADDR 0x4 + +#define XR21V141X_REG_ENABLE 0x03 +#define XR21V141X_REG_FORMAT 0x0b +#define XR21V141X_REG_FLOW_CTRL 0x0c +#define XR21V141X_REG_XON_CHAR 0x10 +#define XR21V141X_REG_XOFF_CHAR 0x11 +#define XR21V141X_REG_LOOPBACK 0x12 +#define XR21V141X_REG_TX_BREAK 0x14 +#define XR21V141X_REG_RS845_DELAY 0x15 +#define XR21V141X_REG_GPIO_MODE 0x1a +#define XR21V141X_REG_GPIO_DIR 0x1b +#define XR21V141X_REG_GPIO_INT_MASK 0x1c +#define XR21V141X_REG_GPIO_SET 0x1d +#define XR21V141X_REG_GPIO_CLR 0x1e +#define XR21V141X_REG_GPIO_STATUS 0x1f + +static int xr_set_reg(struct usb_serial_port *port, u8 block, u8 reg, u8 val) +{ + struct usb_serial *serial = port->serial; + int ret; + + ret = usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + XR21V141X_SET_REQ, + USB_DIR_OUT | USB_TYPE_VENDOR, val, + reg | (block << 8), NULL, 0, + USB_CTRL_SET_TIMEOUT); + if (ret < 0) { + dev_err(&port->dev, "Failed to set reg 0x%02x: %d\n", reg, ret); + return ret; + } + + return 0; +} + +static int xr_get_reg(struct usb_serial_port *port, u8 block, u8 reg, u8 *val) +{ + struct usb_serial *serial = port->serial; + u8 *dmabuf; + int ret; + + dmabuf = kmalloc(1, GFP_KERNEL); + if (!dmabuf) + return -ENOMEM; + + ret = usb_control_msg(serial->dev, + usb_rcvctrlpipe(serial->dev, 0), + XR21V141X_GET_REQ, + USB_DIR_IN | USB_TYPE_VENDOR, 0, + reg | (block << 8), dmabuf, 1, + USB_CTRL_GET_TIMEOUT); + if (ret == 1) { + *val = *dmabuf; + ret = 0; + } else { + dev_err(&port->dev, "Failed to get reg 0x%02x: %d\n", reg, ret); + if (ret >= 0) + ret = -EIO; + } + + kfree(dmabuf); + + return ret; +} + +static int xr_set_reg_uart(struct usb_serial_port *port, u8 reg, u8 val) +{ + return xr_set_reg(port, XR21V141X_UART_REG_BLOCK, reg, val); +} + +static int xr_get_reg_uart(struct usb_serial_port *port, u8 reg, u8 *val) +{ + return xr_get_reg(port, XR21V141X_UART_REG_BLOCK, reg, val); +} + +static int xr_set_reg_um(struct usb_serial_port *port, u8 reg, u8 val) +{ + return xr_set_reg(port, XR21V141X_UM_REG_BLOCK, reg, val); +} + +/* + * According to datasheet, below is the recommended sequence for enabling UART + * module in XR21V141X: + * + * Enable Tx FIFO + * Enable Tx and Rx + * Enable Rx FIFO + */ +static int xr_uart_enable(struct usb_serial_port *port) +{ + int ret; + + ret = xr_set_reg_um(port, XR21V141X_UM_FIFO_ENABLE_REG, + XR21V141X_UM_ENABLE_TX_FIFO); + if (ret) + return ret; + + ret = xr_set_reg_uart(port, XR21V141X_REG_ENABLE, + XR21V141X_UART_ENABLE_TX | XR21V141X_UART_ENABLE_RX); + if (ret) + return ret; + + ret = xr_set_reg_um(port, XR21V141X_UM_FIFO_ENABLE_REG, + XR21V141X_UM_ENABLE_TX_FIFO | XR21V141X_UM_ENABLE_RX_FIFO); + + if (ret) + xr_set_reg_uart(port, XR21V141X_REG_ENABLE, 0); + + return ret; +} + +static int xr_uart_disable(struct usb_serial_port *port) +{ + int ret; + + ret = xr_set_reg_uart(port, XR21V141X_REG_ENABLE, 0); + if (ret) + return ret; + + ret = xr_set_reg_um(port, XR21V141X_UM_FIFO_ENABLE_REG, 0); + + return ret; +} + +static int xr_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + u8 status; + int ret; + + ret = xr_get_reg_uart(port, XR21V141X_REG_GPIO_STATUS, &status); + if (ret) + return ret; + + /* + * Modem control pins are active low, so reading '0' means it is active + * and '1' means not active. + */ + ret = ((status & XR21V141X_UART_MODE_DTR) ? 0 : TIOCM_DTR) | + ((status & XR21V141X_UART_MODE_RTS) ? 0 : TIOCM_RTS) | + ((status & XR21V141X_UART_MODE_CTS) ? 0 : TIOCM_CTS) | + ((status & XR21V141X_UART_MODE_DSR) ? 0 : TIOCM_DSR) | + ((status & XR21V141X_UART_MODE_RI) ? 0 : TIOCM_RI) | + ((status & XR21V141X_UART_MODE_CD) ? 0 : TIOCM_CD); + + return ret; +} + +static int xr_tiocmset_port(struct usb_serial_port *port, + unsigned int set, unsigned int clear) +{ + u8 gpio_set = 0; + u8 gpio_clr = 0; + int ret = 0; + + /* Modem control pins are active low, so set & clr are swapped */ + if (set & TIOCM_RTS) + gpio_clr |= XR21V141X_UART_MODE_RTS; + if (set & TIOCM_DTR) + gpio_clr |= XR21V141X_UART_MODE_DTR; + if (clear & TIOCM_RTS) + gpio_set |= XR21V141X_UART_MODE_RTS; + if (clear & TIOCM_DTR) + gpio_set |= XR21V141X_UART_MODE_DTR; + + /* Writing '0' to gpio_{set/clr} bits has no effect, so no need to do */ + if (gpio_clr) + ret = xr_set_reg_uart(port, XR21V141X_REG_GPIO_CLR, gpio_clr); + + if (gpio_set) + ret = xr_set_reg_uart(port, XR21V141X_REG_GPIO_SET, gpio_set); + + return ret; +} + +static int xr_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + + return xr_tiocmset_port(port, set, clear); +} + +static void xr_dtr_rts(struct usb_serial_port *port, int on) +{ + if (on) + xr_tiocmset_port(port, TIOCM_DTR | TIOCM_RTS, 0); + else + xr_tiocmset_port(port, 0, TIOCM_DTR | TIOCM_RTS); +} + +static void xr_break_ctl(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + u8 state; + + if (break_state == 0) + state = XR21V141X_UART_BREAK_OFF; + else + state = XR21V141X_UART_BREAK_ON; + + dev_dbg(&port->dev, "Turning break %s\n", + state == XR21V141X_UART_BREAK_OFF ? "off" : "on"); + xr_set_reg_uart(port, XR21V141X_REG_TX_BREAK, state); +} + +/* Tx and Rx clock mask values obtained from section 3.3.4 of datasheet */ +static const struct xr_txrx_clk_mask xr21v141x_txrx_clk_masks[] = { + { 0x000, 0x000, 0x000 }, + { 0x000, 0x000, 0x000 }, + { 0x100, 0x000, 0x100 }, + { 0x020, 0x400, 0x020 }, + { 0x010, 0x100, 0x010 }, + { 0x208, 0x040, 0x208 }, + { 0x104, 0x820, 0x108 }, + { 0x844, 0x210, 0x884 }, + { 0x444, 0x110, 0x444 }, + { 0x122, 0x888, 0x224 }, + { 0x912, 0x448, 0x924 }, + { 0x492, 0x248, 0x492 }, + { 0x252, 0x928, 0x292 }, + { 0x94a, 0x4a4, 0xa52 }, + { 0x52a, 0xaa4, 0x54a }, + { 0xaaa, 0x954, 0x4aa }, + { 0xaaa, 0x554, 0xaaa }, + { 0x555, 0xad4, 0x5aa }, + { 0xb55, 0xab4, 0x55a }, + { 0x6b5, 0x5ac, 0xb56 }, + { 0x5b5, 0xd6c, 0x6d6 }, + { 0xb6d, 0xb6a, 0xdb6 }, + { 0x76d, 0x6da, 0xbb6 }, + { 0xedd, 0xdda, 0x76e }, + { 0xddd, 0xbba, 0xeee }, + { 0x7bb, 0xf7a, 0xdde }, + { 0xf7b, 0xef6, 0x7de }, + { 0xdf7, 0xbf6, 0xf7e }, + { 0x7f7, 0xfee, 0xefe }, + { 0xfdf, 0xfbe, 0x7fe }, + { 0xf7f, 0xefe, 0xffe }, + { 0xfff, 0xffe, 0xffd }, +}; + +static int xr_set_baudrate(struct tty_struct *tty, + struct usb_serial_port *port) +{ + u32 divisor, baud, idx; + u16 tx_mask, rx_mask; + int ret; + + baud = clamp(tty->termios.c_ospeed, XR21V141X_MIN_SPEED, + XR21V141X_MAX_SPEED); + divisor = XR_INT_OSC_HZ / baud; + idx = ((32 * XR_INT_OSC_HZ) / baud) & 0x1f; + tx_mask = xr21v141x_txrx_clk_masks[idx].tx; + + if (divisor & 0x01) + rx_mask = xr21v141x_txrx_clk_masks[idx].rx1; + else + rx_mask = xr21v141x_txrx_clk_masks[idx].rx0; + + dev_dbg(&port->dev, "Setting baud rate: %u\n", baud); + /* + * XR21V141X uses fractional baud rate generator with 48MHz internal + * oscillator and 19-bit programmable divisor. So theoretically it can + * generate most commonly used baud rates with high accuracy. + */ + ret = xr_set_reg_uart(port, XR21V141X_CLOCK_DIVISOR_0, + divisor & 0xff); + if (ret) + return ret; + + ret = xr_set_reg_uart(port, XR21V141X_CLOCK_DIVISOR_1, + (divisor >> 8) & 0xff); + if (ret) + return ret; + + ret = xr_set_reg_uart(port, XR21V141X_CLOCK_DIVISOR_2, + (divisor >> 16) & 0xff); + if (ret) + return ret; + + ret = xr_set_reg_uart(port, XR21V141X_TX_CLOCK_MASK_0, + tx_mask & 0xff); + if (ret) + return ret; + + ret = xr_set_reg_uart(port, XR21V141X_TX_CLOCK_MASK_1, + (tx_mask >> 8) & 0xff); + if (ret) + return ret; + + ret = xr_set_reg_uart(port, XR21V141X_RX_CLOCK_MASK_0, + rx_mask & 0xff); + if (ret) + return ret; + + ret = xr_set_reg_uart(port, XR21V141X_RX_CLOCK_MASK_1, + (rx_mask >> 8) & 0xff); + if (ret) + return ret; + + tty_encode_baud_rate(tty, baud, baud); + + return 0; +} + +static void xr_set_flow_mode(struct tty_struct *tty, + struct usb_serial_port *port) +{ + unsigned int cflag = tty->termios.c_cflag; + u8 flow, gpio_mode; + int ret; + + ret = xr_get_reg_uart(port, XR21V141X_REG_GPIO_MODE, &gpio_mode); + if (ret) + return; + + if (cflag & CRTSCTS) { + dev_dbg(&port->dev, "Enabling hardware flow ctrl\n"); + + /* + * RTS/CTS is the default flow control mode, so set GPIO mode + * for controlling the pins manually by default. + */ + gpio_mode &= ~XR21V141X_UART_MODE_GPIO_MASK; + gpio_mode |= XR21V141X_UART_MODE_RTS_CTS; + flow = XR21V141X_UART_FLOW_MODE_HW; + } else if (I_IXON(tty)) { + u8 start_char = START_CHAR(tty); + u8 stop_char = STOP_CHAR(tty); + + dev_dbg(&port->dev, "Enabling sw flow ctrl\n"); + flow = XR21V141X_UART_FLOW_MODE_SW; + + xr_set_reg_uart(port, XR21V141X_REG_XON_CHAR, start_char); + xr_set_reg_uart(port, XR21V141X_REG_XOFF_CHAR, stop_char); + } else { + dev_dbg(&port->dev, "Disabling flow ctrl\n"); + flow = XR21V141X_UART_FLOW_MODE_NONE; + } + + /* + * As per the datasheet, UART needs to be disabled while writing to + * FLOW_CONTROL register. + */ + xr_uart_disable(port); + xr_set_reg_uart(port, XR21V141X_REG_FLOW_CTRL, flow); + xr_uart_enable(port); + + xr_set_reg_uart(port, XR21V141X_REG_GPIO_MODE, gpio_mode); +} + +static void xr_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + struct ktermios *old_termios) +{ + struct ktermios *termios = &tty->termios; + u8 bits = 0; + int ret; + + if ((old_termios && tty->termios.c_ospeed != old_termios->c_ospeed) || + !old_termios) + xr_set_baudrate(tty, port); + + switch (C_CSIZE(tty)) { + case CS5: + case CS6: + /* CS5 and CS6 are not supported, so just restore old setting */ + termios->c_cflag &= ~CSIZE; + if (old_termios) + termios->c_cflag |= old_termios->c_cflag & CSIZE; + else + bits |= XR21V141X_UART_DATA_8; + break; + case CS7: + bits |= XR21V141X_UART_DATA_7; + break; + case CS8: + default: + bits |= XR21V141X_UART_DATA_8; + break; + } + + if (C_PARENB(tty)) { + if (C_CMSPAR(tty)) { + if (C_PARODD(tty)) + bits |= XR21V141X_UART_PARITY_MARK << + XR21V141X_UART_PARITY_SHIFT; + else + bits |= XR21V141X_UART_PARITY_SPACE << + XR21V141X_UART_PARITY_SHIFT; + } else { + if (C_PARODD(tty)) + bits |= XR21V141X_UART_PARITY_ODD << + XR21V141X_UART_PARITY_SHIFT; + else + bits |= XR21V141X_UART_PARITY_EVEN << + XR21V141X_UART_PARITY_SHIFT; + } + } + + if (C_CSTOPB(tty)) + bits |= XR21V141X_UART_STOP_2 << XR21V141X_UART_STOP_SHIFT; + else + bits |= XR21V141X_UART_STOP_1 << XR21V141X_UART_STOP_SHIFT; + + ret = xr_set_reg_uart(port, XR21V141X_REG_FORMAT, bits); + if (ret) + return; + + /* If baud rate is B0, clear DTR and RTS */ + if (C_BAUD(tty) == B0) + xr_dtr_rts(port, 0); + + xr_set_flow_mode(tty, port); +} + +static int xr_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + int ret; + + ret = xr_uart_enable(port); + if (ret) { + dev_err(&port->dev, "Failed to enable UART\n"); + return ret; + } + + /* Setup termios */ + if (tty) + xr_set_termios(tty, port, NULL); + + ret = usb_serial_generic_open(tty, port); + if (ret) { + xr_uart_disable(port); + return ret; + } + + return 0; +} + +static void xr_close(struct usb_serial_port *port) +{ + usb_serial_generic_close(port); + + xr_uart_disable(port); +} + +static int xr_probe(struct usb_serial *serial, const struct usb_device_id *id) +{ + struct usb_device *usb_dev = interface_to_usbdev(serial->interface); + struct usb_driver *driver = serial->type->usb_driver; + struct usb_interface *control_interface; + int ret; + + /* Don't bind to control interface */ + if (serial->interface->cur_altsetting->desc.bInterfaceNumber == 0) + return -ENODEV; + + /* But claim the control interface during data interface probe */ + control_interface = usb_ifnum_to_if(usb_dev, 0); + ret = usb_driver_claim_interface(driver, control_interface, NULL); + if (ret) { + dev_err(&serial->interface->dev, "Failed to claim control interface\n"); + return ret; + } + + return 0; +} + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x04e2, 0x1410) }, /* XR21V141X */ + { } +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_serial_driver xr_device = { + .driver = { + .owner = THIS_MODULE, + .name = "xr_serial", + }, + .id_table = id_table, + .num_ports = 1, + .probe = xr_probe, + .open = xr_open, + .close = xr_close, + .break_ctl = xr_break_ctl, + .set_termios = xr_set_termios, + .tiocmget = xr_tiocmget, + .tiocmset = xr_tiocmset, + .dtr_rts = xr_dtr_rts +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &xr_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_AUTHOR("Manivannan Sadhasivam "); +MODULE_DESCRIPTION("MaxLinear/Exar USB to Serial driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 5f6225a7fb2c863ca40ca282478933c0afdb3512 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Sun, 22 Nov 2020 22:38:22 +0530 Subject: USB: cdc-acm: ignore Exar XR21V141X when serial driver is built The Exar XR21V141X can be used in either ACM mode using the cdc-acm driver or in "custom driver" mode in which further features such as hardware and software flow control, GPIO control and in-band line-status reporting are available. In ACM mode the device always enables RTS/CTS flow control, something which could prevent transmission in case the CTS input isn't wired up correctly. Ensure that cdc_acm will not bind to the device if the custom USB-serial driver is enabled. Signed-off-by: Mauro Carvalho Chehab Signed-off-by: Manivannan Sadhasivam Link: https://lore.kernel.org/r/20201122170822.21715-4-mani@kernel.org [ johan: rewrite commit message ] Signed-off-by: Johan Hovold --- drivers/usb/class/cdc-acm.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 781905745812..37f824b59daa 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -1901,6 +1901,12 @@ static const struct usb_device_id acm_ids[] = { }, #endif +#if IS_ENABLED(CONFIG_USB_SERIAL_XR) + { USB_DEVICE(0x04e2, 0x1410), /* Ignore XR21V141X USB to Serial converter */ + .driver_info = IGNORE_DEVICE, + }, +#endif + /*Samsung phone in firmware update mode */ { USB_DEVICE(0x04e8, 0x685d), .driver_info = IGNORE_DEVICE, -- cgit v1.2.3 From 0908c5aca31eb5e0c72d7a5dba422629b88e877d Mon Sep 17 00:00:00 2001 From: Kyle Tso Date: Thu, 14 Jan 2021 22:50:51 +0800 Subject: usb: typec: tcpm: AMS and Collision Avoidance This patch provides the implementation of Collision Avoidance introduced in PD3.0. The start of each Atomic Message Sequence (AMS) initiated by the port will be denied if the current AMS is not interruptible. The Source port will set the CC to SinkTxNG if it is going to initiate an AMS, and SinkTxOk otherwise. Meanwhile, any AMS initiated by a Sink port will be denied in TCPM if the port partner (Source) sets SinkTxNG except for HARD_RESET and SOFT_RESET. Tested-by: Hans de Goede Reviewed-by: Heikki Krogerus Signed-off-by: Kyle Tso Link: https://lore.kernel.org/r/20210114145053.1952756-2-kyletso@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 620 +++++++++++++++++++++++++++++++++++------- include/linux/usb/pd.h | 1 + include/linux/usb/tcpm.h | 4 + 3 files changed, 527 insertions(+), 98 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 22a85b396f69..2b16d2764092 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -76,6 +76,8 @@ S(SNK_HARD_RESET_SINK_ON), \ \ S(SOFT_RESET), \ + S(SRC_SOFT_RESET_WAIT_SNK_TX), \ + S(SNK_SOFT_RESET), \ S(SOFT_RESET_SEND), \ \ S(DR_SWAP_ACCEPT), \ @@ -139,7 +141,45 @@ \ S(ERROR_RECOVERY), \ S(PORT_RESET), \ - S(PORT_RESET_WAIT_OFF) + S(PORT_RESET_WAIT_OFF), \ + \ + S(AMS_START) + +#define FOREACH_AMS(S) \ + S(NONE_AMS), \ + S(POWER_NEGOTIATION), \ + S(GOTOMIN), \ + S(SOFT_RESET_AMS), \ + S(HARD_RESET), \ + S(CABLE_RESET), \ + S(GET_SOURCE_CAPABILITIES), \ + S(GET_SINK_CAPABILITIES), \ + S(POWER_ROLE_SWAP), \ + S(FAST_ROLE_SWAP), \ + S(DATA_ROLE_SWAP), \ + S(VCONN_SWAP), \ + S(SOURCE_ALERT), \ + S(GETTING_SOURCE_EXTENDED_CAPABILITIES),\ + S(GETTING_SOURCE_SINK_STATUS), \ + S(GETTING_BATTERY_CAPABILITIES), \ + S(GETTING_BATTERY_STATUS), \ + S(GETTING_MANUFACTURER_INFORMATION), \ + S(SECURITY), \ + S(FIRMWARE_UPDATE), \ + S(DISCOVER_IDENTITY), \ + S(SOURCE_STARTUP_CABLE_PLUG_DISCOVER_IDENTITY), \ + S(DISCOVER_SVIDS), \ + S(DISCOVER_MODES), \ + S(DFP_TO_UFP_ENTER_MODE), \ + S(DFP_TO_UFP_EXIT_MODE), \ + S(DFP_TO_CABLE_PLUG_ENTER_MODE), \ + S(DFP_TO_CABLE_PLUG_EXIT_MODE), \ + S(ATTENTION), \ + S(BIST), \ + S(UNSTRUCTURED_VDMS), \ + S(STRUCTURED_VDMS), \ + S(COUNTRY_INFO), \ + S(COUNTRY_CODES) #define GENERATE_ENUM(e) e #define GENERATE_STRING(s) #s @@ -152,6 +192,14 @@ static const char * const tcpm_states[] = { FOREACH_STATE(GENERATE_STRING) }; +enum tcpm_ams { + FOREACH_AMS(GENERATE_ENUM) +}; + +static const char * const tcpm_ams_str[] = { + FOREACH_AMS(GENERATE_STRING) +}; + enum vdm_states { VDM_STATE_ERR_BUSY = -3, VDM_STATE_ERR_SEND = -2, @@ -161,6 +209,7 @@ enum vdm_states { VDM_STATE_READY = 1, VDM_STATE_BUSY = 2, VDM_STATE_WAIT_RSP_BUSY = 3, + VDM_STATE_SEND_MESSAGE = 4, }; enum pd_msg_request { @@ -381,6 +430,11 @@ struct tcpm_port { /* Sink caps have been queried */ bool sink_cap_done; + /* Collision Avoidance and Atomic Message Sequence */ + enum tcpm_state upcoming_state; + enum tcpm_ams ams; + bool in_ams; + #ifdef CONFIG_DEBUG_FS struct dentry *dentry; struct mutex logbuffer_lock; /* log buffer access lock */ @@ -396,6 +450,12 @@ struct pd_rx_event { struct pd_message msg; }; +static const char * const pd_rev[] = { + [PD_REV10] = "rev1", + [PD_REV20] = "rev2", + [PD_REV30] = "rev3", +}; + #define tcpm_cc_is_sink(cc) \ ((cc) == TYPEC_CC_RP_DEF || (cc) == TYPEC_CC_RP_1_5 || \ (cc) == TYPEC_CC_RP_3_0) @@ -440,6 +500,10 @@ struct pd_rx_event { ((port)->typec_caps.data == TYPEC_PORT_DFP ? \ TYPEC_HOST : TYPEC_DEVICE) +#define tcpm_sink_tx_ok(port) \ + (tcpm_port_is_sink(port) && \ + ((port)->cc1 == TYPEC_CC_RP_3_0 || (port)->cc2 == TYPEC_CC_RP_3_0)) + static enum tcpm_state tcpm_default_state(struct tcpm_port *port) { if (port->port_type == TYPEC_PORT_DRP) { @@ -666,6 +730,67 @@ static void tcpm_debugfs_exit(const struct tcpm_port *port) { } #endif +static void tcpm_set_cc(struct tcpm_port *port, enum typec_cc_status cc) +{ + tcpm_log(port, "cc:=%d", cc); + port->cc_req = cc; + port->tcpc->set_cc(port->tcpc, cc); +} + +/* + * Determine RP value to set based on maximum current supported + * by a port if configured as source. + * Returns CC value to report to link partner. + */ +static enum typec_cc_status tcpm_rp_cc(struct tcpm_port *port) +{ + const u32 *src_pdo = port->src_pdo; + int nr_pdo = port->nr_src_pdo; + int i; + + /* + * Search for first entry with matching voltage. + * It should report the maximum supported current. + */ + for (i = 0; i < nr_pdo; i++) { + const u32 pdo = src_pdo[i]; + + if (pdo_type(pdo) == PDO_TYPE_FIXED && + pdo_fixed_voltage(pdo) == 5000) { + unsigned int curr = pdo_max_current(pdo); + + if (curr >= 3000) + return TYPEC_CC_RP_3_0; + else if (curr >= 1500) + return TYPEC_CC_RP_1_5; + return TYPEC_CC_RP_DEF; + } + } + + return TYPEC_CC_RP_DEF; +} + +static int tcpm_ams_finish(struct tcpm_port *port) +{ + int ret = 0; + + tcpm_log(port, "AMS %s finished", tcpm_ams_str[port->ams]); + + if (port->pd_capable && port->pwr_role == TYPEC_SOURCE) { + if (port->negotiated_rev >= PD_REV30) + tcpm_set_cc(port, SINK_TX_OK); + else + tcpm_set_cc(port, SINK_TX_NG); + } else if (port->pwr_role == TYPEC_SOURCE) { + tcpm_set_cc(port, tcpm_rp_cc(port)); + } + + port->in_ams = false; + port->ams = NONE_AMS; + + return ret; +} + static int tcpm_pd_transmit(struct tcpm_port *port, enum tcpm_transmit_type type, const struct pd_message *msg) @@ -693,13 +818,30 @@ static int tcpm_pd_transmit(struct tcpm_port *port, switch (port->tx_status) { case TCPC_TX_SUCCESS: port->message_id = (port->message_id + 1) & PD_HEADER_ID_MASK; - return 0; + /* + * USB PD rev 2.0, 8.3.2.2.1: + * USB PD rev 3.0, 8.3.2.1.3: + * "... Note that every AMS is Interruptible until the first + * Message in the sequence has been successfully sent (GoodCRC + * Message received)." + */ + if (port->ams != NONE_AMS) + port->in_ams = true; + break; case TCPC_TX_DISCARDED: - return -EAGAIN; + ret = -EAGAIN; + break; case TCPC_TX_FAILED: default: - return -EIO; + ret = -EIO; + break; } + + /* Some AMS don't expect responses. Finish them here. */ + if (port->ams == ATTENTION || port->ams == SOURCE_ALERT) + tcpm_ams_finish(port); + + return ret; } void tcpm_pd_transmit_complete(struct tcpm_port *port, @@ -804,39 +946,6 @@ static int tcpm_set_current_limit(struct tcpm_port *port, u32 max_ma, u32 mv) return ret; } -/* - * Determine RP value to set based on maximum current supported - * by a port if configured as source. - * Returns CC value to report to link partner. - */ -static enum typec_cc_status tcpm_rp_cc(struct tcpm_port *port) -{ - const u32 *src_pdo = port->src_pdo; - int nr_pdo = port->nr_src_pdo; - int i; - - /* - * Search for first entry with matching voltage. - * It should report the maximum supported current. - */ - for (i = 0; i < nr_pdo; i++) { - const u32 pdo = src_pdo[i]; - - if (pdo_type(pdo) == PDO_TYPE_FIXED && - pdo_fixed_voltage(pdo) == 5000) { - unsigned int curr = pdo_max_current(pdo); - - if (curr >= 3000) - return TYPEC_CC_RP_3_0; - else if (curr >= 1500) - return TYPEC_CC_RP_1_5; - return TYPEC_CC_RP_DEF; - } - } - - return TYPEC_CC_RP_DEF; -} - static int tcpm_set_attached_state(struct tcpm_port *port, bool attached) { return port->tcpc->set_roles(port->tcpc, attached, port->pwr_role, @@ -1000,16 +1109,17 @@ static void tcpm_set_state(struct tcpm_port *port, enum tcpm_state state, unsigned int delay_ms) { if (delay_ms) { - tcpm_log(port, "pending state change %s -> %s @ %u ms", - tcpm_states[port->state], tcpm_states[state], - delay_ms); + tcpm_log(port, "pending state change %s -> %s @ %u ms [%s %s]", + tcpm_states[port->state], tcpm_states[state], delay_ms, + pd_rev[port->negotiated_rev], tcpm_ams_str[port->ams]); port->delayed_state = state; mod_tcpm_delayed_work(port, delay_ms); port->delayed_runtime = ktime_add(ktime_get(), ms_to_ktime(delay_ms)); port->delay_ms = delay_ms; } else { - tcpm_log(port, "state change %s -> %s", - tcpm_states[port->state], tcpm_states[state]); + tcpm_log(port, "state change %s -> %s [%s %s]", + tcpm_states[port->state], tcpm_states[state], + pd_rev[port->negotiated_rev], tcpm_ams_str[port->ams]); port->delayed_state = INVALID_STATE; port->prev_state = port->state; port->state = state; @@ -1031,10 +1141,11 @@ static void tcpm_set_state_cond(struct tcpm_port *port, enum tcpm_state state, tcpm_set_state(port, state, delay_ms); else tcpm_log(port, - "skipped %sstate change %s -> %s [%u ms], context state %s", + "skipped %sstate change %s -> %s [%u ms], context state %s [%s %s]", delay_ms ? "delayed " : "", tcpm_states[port->state], tcpm_states[state], - delay_ms, tcpm_states[port->enter_state]); + delay_ms, tcpm_states[port->enter_state], + pd_rev[port->negotiated_rev], tcpm_ams_str[port->ams]); } static void tcpm_queue_message(struct tcpm_port *port, @@ -1044,6 +1155,149 @@ static void tcpm_queue_message(struct tcpm_port *port, mod_tcpm_delayed_work(port, 0); } +static bool tcpm_vdm_ams(struct tcpm_port *port) +{ + switch (port->ams) { + case DISCOVER_IDENTITY: + case SOURCE_STARTUP_CABLE_PLUG_DISCOVER_IDENTITY: + case DISCOVER_SVIDS: + case DISCOVER_MODES: + case DFP_TO_UFP_ENTER_MODE: + case DFP_TO_UFP_EXIT_MODE: + case DFP_TO_CABLE_PLUG_ENTER_MODE: + case DFP_TO_CABLE_PLUG_EXIT_MODE: + case ATTENTION: + case UNSTRUCTURED_VDMS: + case STRUCTURED_VDMS: + break; + default: + return false; + } + + return true; +} + +static bool tcpm_ams_interruptible(struct tcpm_port *port) +{ + switch (port->ams) { + /* Interruptible AMS */ + case NONE_AMS: + case SECURITY: + case FIRMWARE_UPDATE: + case DISCOVER_IDENTITY: + case SOURCE_STARTUP_CABLE_PLUG_DISCOVER_IDENTITY: + case DISCOVER_SVIDS: + case DISCOVER_MODES: + case DFP_TO_UFP_ENTER_MODE: + case DFP_TO_UFP_EXIT_MODE: + case DFP_TO_CABLE_PLUG_ENTER_MODE: + case DFP_TO_CABLE_PLUG_EXIT_MODE: + case UNSTRUCTURED_VDMS: + case STRUCTURED_VDMS: + case COUNTRY_INFO: + case COUNTRY_CODES: + break; + /* Non-Interruptible AMS */ + default: + if (port->in_ams) + return false; + break; + } + + return true; +} + +static int tcpm_ams_start(struct tcpm_port *port, enum tcpm_ams ams) +{ + int ret = 0; + + tcpm_log(port, "AMS %s start", tcpm_ams_str[ams]); + + if (!tcpm_ams_interruptible(port) && ams != HARD_RESET) { + port->upcoming_state = INVALID_STATE; + tcpm_log(port, "AMS %s not interruptible, aborting", + tcpm_ams_str[port->ams]); + return -EAGAIN; + } + + if (port->pwr_role == TYPEC_SOURCE) { + enum typec_cc_status cc_req = port->cc_req; + + port->ams = ams; + + if (ams == HARD_RESET) { + tcpm_set_cc(port, tcpm_rp_cc(port)); + tcpm_pd_transmit(port, TCPC_TX_HARD_RESET, NULL); + tcpm_set_state(port, HARD_RESET_START, 0); + return ret; + } else if (ams == SOFT_RESET_AMS) { + if (!port->explicit_contract) { + port->upcoming_state = INVALID_STATE; + tcpm_set_cc(port, tcpm_rp_cc(port)); + return ret; + } + } else if (tcpm_vdm_ams(port)) { + /* tSinkTx is enforced in vdm_run_state_machine */ + if (port->negotiated_rev >= PD_REV30) + tcpm_set_cc(port, SINK_TX_NG); + return ret; + } + + if (port->negotiated_rev >= PD_REV30) + tcpm_set_cc(port, SINK_TX_NG); + + switch (port->state) { + case SRC_READY: + case SRC_STARTUP: + case SRC_SOFT_RESET_WAIT_SNK_TX: + case SOFT_RESET: + case SOFT_RESET_SEND: + if (port->negotiated_rev >= PD_REV30) + tcpm_set_state(port, AMS_START, + cc_req == SINK_TX_OK ? + PD_T_SINK_TX : 0); + else + tcpm_set_state(port, AMS_START, 0); + break; + default: + if (port->negotiated_rev >= PD_REV30) + tcpm_set_state(port, SRC_READY, + cc_req == SINK_TX_OK ? + PD_T_SINK_TX : 0); + else + tcpm_set_state(port, SRC_READY, 0); + break; + } + } else { + if (port->negotiated_rev >= PD_REV30 && + !tcpm_sink_tx_ok(port) && + ams != SOFT_RESET_AMS && + ams != HARD_RESET) { + port->upcoming_state = INVALID_STATE; + tcpm_log(port, "Sink TX No Go"); + return -EAGAIN; + } + + port->ams = ams; + + if (ams == HARD_RESET) { + tcpm_pd_transmit(port, TCPC_TX_HARD_RESET, NULL); + tcpm_set_state(port, HARD_RESET_START, 0); + return ret; + } else if (tcpm_vdm_ams(port)) { + return ret; + } + + if (port->state == SNK_READY || + port->state == SNK_SOFT_RESET) + tcpm_set_state(port, AMS_START, 0); + else + tcpm_set_state(port, SNK_READY, 0); + } + + return ret; +} + /* * VDM/VDO handling functions */ @@ -1236,6 +1490,8 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, if (IS_ERR_OR_NULL(port->partner)) break; + tcpm_ams_finish(port); + switch (cmd) { case CMD_DISCOVER_IDENT: /* 6.4.4.3.1 */ @@ -1286,6 +1542,7 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, } break; case CMDT_RSP_NAK: + tcpm_ams_finish(port); switch (cmd) { case CMD_ENTER_MODE: /* Back to USB Operation */ @@ -1435,7 +1692,8 @@ static unsigned int vdm_ready_timeout(u32 vdm_hdr) static void vdm_run_state_machine(struct tcpm_port *port) { struct pd_message msg; - int i, res; + int i, res = 0; + u32 vdo_hdr = port->vdo_data[0]; switch (port->vdm_state) { case VDM_STATE_READY: @@ -1452,26 +1710,45 @@ static void vdm_run_state_machine(struct tcpm_port *port) if (port->state != SRC_READY && port->state != SNK_READY) break; - /* Prepare and send VDM */ - memset(&msg, 0, sizeof(msg)); - msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF, - port->pwr_role, - port->data_role, - port->negotiated_rev, - port->message_id, port->vdo_count); - for (i = 0; i < port->vdo_count; i++) - msg.payload[i] = cpu_to_le32(port->vdo_data[i]); - res = tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); - if (res < 0) { - port->vdm_state = VDM_STATE_ERR_SEND; - } else { - unsigned long timeout; + /* TODO: AMS operation for Unstructured VDM */ + if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMDT(vdo_hdr) == CMDT_INIT) { + switch (PD_VDO_CMD(vdo_hdr)) { + case CMD_DISCOVER_IDENT: + res = tcpm_ams_start(port, DISCOVER_IDENTITY); + break; + case CMD_DISCOVER_SVID: + res = tcpm_ams_start(port, DISCOVER_SVIDS); + break; + case CMD_DISCOVER_MODES: + res = tcpm_ams_start(port, DISCOVER_MODES); + break; + case CMD_ENTER_MODE: + res = tcpm_ams_start(port, DFP_TO_UFP_ENTER_MODE); + break; + case CMD_EXIT_MODE: + res = tcpm_ams_start(port, DFP_TO_UFP_EXIT_MODE); + break; + case CMD_ATTENTION: + res = tcpm_ams_start(port, ATTENTION); + break; + case VDO_CMD_VENDOR(0) ... VDO_CMD_VENDOR(15): + res = tcpm_ams_start(port, STRUCTURED_VDMS); + break; + default: + res = -EOPNOTSUPP; + break; + } - port->vdm_retries = 0; - port->vdm_state = VDM_STATE_BUSY; - timeout = vdm_ready_timeout(port->vdo_data[0]); - mod_vdm_delayed_work(port, timeout); + if (res < 0) + return; } + + port->vdm_state = VDM_STATE_SEND_MESSAGE; + mod_vdm_delayed_work(port, (port->negotiated_rev >= PD_REV30 && + port->pwr_role == TYPEC_SOURCE && + PD_VDO_SVDM(vdo_hdr) && + PD_VDO_CMDT(vdo_hdr) == CMDT_INIT) ? + PD_T_SINK_TX : 0); break; case VDM_STATE_WAIT_RSP_BUSY: port->vdo_data[0] = port->vdo_retry; @@ -1480,6 +1757,8 @@ static void vdm_run_state_machine(struct tcpm_port *port) break; case VDM_STATE_BUSY: port->vdm_state = VDM_STATE_ERR_TMOUT; + if (port->ams != NONE_AMS) + tcpm_ams_finish(port); break; case VDM_STATE_ERR_SEND: /* @@ -1492,6 +1771,29 @@ static void vdm_run_state_machine(struct tcpm_port *port) tcpm_log(port, "VDM Tx error, retry"); port->vdm_retries++; port->vdm_state = VDM_STATE_READY; + tcpm_ams_finish(port); + } + break; + case VDM_STATE_SEND_MESSAGE: + /* Prepare and send VDM */ + memset(&msg, 0, sizeof(msg)); + msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, port->vdo_count); + for (i = 0; i < port->vdo_count; i++) + msg.payload[i] = cpu_to_le32(port->vdo_data[i]); + res = tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); + if (res < 0) { + port->vdm_state = VDM_STATE_ERR_SEND; + } else { + unsigned long timeout; + + port->vdm_retries = 0; + port->vdm_state = VDM_STATE_BUSY; + timeout = vdm_ready_timeout(vdo_hdr); + mod_vdm_delayed_work(port, timeout); } break; default: @@ -1514,7 +1816,8 @@ static void vdm_state_machine_work(struct kthread_work *work) prev_state = port->vdm_state; vdm_run_state_machine(port); } while (port->vdm_state != prev_state && - port->vdm_state != VDM_STATE_BUSY); + port->vdm_state != VDM_STATE_BUSY && + port->vdm_state != VDM_STATE_SEND_MESSAGE); mutex_unlock(&port->lock); } @@ -1997,11 +2300,14 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, case SOFT_RESET_SEND: port->message_id = 0; port->rx_msgid = -1; - if (port->pwr_role == TYPEC_SOURCE) - next_state = SRC_SEND_CAPABILITIES; - else - next_state = SNK_WAIT_CAPABILITIES; - tcpm_set_state(port, next_state, 0); + if (port->ams == SOFT_RESET_AMS) + tcpm_ams_finish(port); + if (port->pwr_role == TYPEC_SOURCE) { + port->upcoming_state = SRC_SEND_CAPABILITIES; + tcpm_ams_start(port, POWER_NEGOTIATION); + } else { + tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0); + } break; case DR_SWAP_SEND: tcpm_set_state(port, DR_SWAP_CHANGE_DR, 0); @@ -2776,13 +3082,6 @@ static bool tcpm_start_toggling(struct tcpm_port *port, enum typec_cc_status cc) return ret == 0; } -static void tcpm_set_cc(struct tcpm_port *port, enum typec_cc_status cc) -{ - tcpm_log(port, "cc:=%d", cc); - port->cc_req = cc; - port->tcpc->set_cc(port->tcpc, cc); -} - static int tcpm_init_vbus(struct tcpm_port *port) { int ret; @@ -2912,6 +3211,8 @@ static void tcpm_reset_port(struct tcpm_port *port) ret = port->tcpc->enable_auto_vbus_discharge(port->tcpc, false); tcpm_log_force(port, "Disable vbus discharge ret:%d", ret); } + port->in_ams = false; + port->ams = NONE_AMS; tcpm_unregister_altmodes(port); tcpm_typec_disconnect(port); port->attached = false; @@ -3090,6 +3391,7 @@ static void run_state_machine(struct tcpm_port *port) int ret; enum typec_pwr_opmode opmode; unsigned int msecs; + enum tcpm_state upcoming_state; port->enter_state = port->state; switch (port->state) { @@ -3190,7 +3492,12 @@ static void run_state_machine(struct tcpm_port *port) port->message_id = 0; port->rx_msgid = -1; port->explicit_contract = false; - tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); + /* SNK -> SRC POWER/FAST_ROLE_SWAP finished */ + if (port->ams == POWER_ROLE_SWAP || + port->ams == FAST_ROLE_SWAP) + tcpm_ams_finish(port); + port->upcoming_state = SRC_SEND_CAPABILITIES; + tcpm_ams_start(port, POWER_NEGOTIATION); break; case SRC_SEND_CAPABILITIES: port->caps_count++; @@ -3272,6 +3579,19 @@ static void run_state_machine(struct tcpm_port *port) tcpm_swap_complete(port, 0); tcpm_typec_connect(port); + if (port->ams != NONE_AMS) + tcpm_ams_finish(port); + /* + * If previous AMS is interrupted, switch to the upcoming + * state. + */ + if (port->upcoming_state != INVALID_STATE) { + upcoming_state = port->upcoming_state; + port->upcoming_state = INVALID_STATE; + tcpm_set_state(port, upcoming_state, 0); + break; + } + tcpm_check_send_discover(port); /* * 6.3.5 @@ -3389,6 +3709,12 @@ static void run_state_machine(struct tcpm_port *port) port->message_id = 0; port->rx_msgid = -1; port->explicit_contract = false; + + if (port->ams == POWER_ROLE_SWAP || + port->ams == FAST_ROLE_SWAP) + /* SRC -> SNK POWER/FAST_ROLE_SWAP finished */ + tcpm_ams_finish(port); + tcpm_set_state(port, SNK_DISCOVERY, 0); break; case SNK_DISCOVERY: @@ -3437,7 +3763,7 @@ static void run_state_machine(struct tcpm_port *port) */ if (port->vbus_never_low) { port->vbus_never_low = false; - tcpm_set_state(port, SOFT_RESET_SEND, + tcpm_set_state(port, SNK_SOFT_RESET, PD_T_SINK_WAIT_CAP); } else { tcpm_set_state(port, hard_reset_state(port), @@ -3490,9 +3816,23 @@ static void run_state_machine(struct tcpm_port *port) tcpm_swap_complete(port, 0); tcpm_typec_connect(port); - tcpm_check_send_discover(port); mod_enable_frs_delayed_work(port, 0); tcpm_pps_complete(port, port->pps_status); + + if (port->ams != NONE_AMS) + tcpm_ams_finish(port); + /* + * If previous AMS is interrupted, switch to the upcoming + * state. + */ + if (port->upcoming_state != INVALID_STATE) { + upcoming_state = port->upcoming_state; + port->upcoming_state = INVALID_STATE; + tcpm_set_state(port, upcoming_state, 0); + break; + } + + tcpm_check_send_discover(port); power_supply_changed(port->psy); break; @@ -3513,8 +3853,14 @@ static void run_state_machine(struct tcpm_port *port) /* Hard_Reset states */ case HARD_RESET_SEND: - tcpm_pd_transmit(port, TCPC_TX_HARD_RESET, NULL); - tcpm_set_state(port, HARD_RESET_START, 0); + if (port->ams != NONE_AMS) + tcpm_ams_finish(port); + /* + * State machine will be directed to HARD_RESET_START, + * thus set upcoming_state to INVALID_STATE. + */ + port->upcoming_state = INVALID_STATE; + tcpm_ams_start(port, HARD_RESET); break; case HARD_RESET_START: port->sink_cap_done = false; @@ -3558,6 +3904,8 @@ static void run_state_machine(struct tcpm_port *port) case SRC_HARD_RESET_VBUS_ON: tcpm_set_vconn(port, true); tcpm_set_vbus(port, true); + if (port->ams == HARD_RESET) + tcpm_ams_finish(port); port->tcpc->set_pd_rx(port->tcpc, true); tcpm_set_attached_state(port, true); tcpm_set_state(port, SRC_UNATTACHED, PD_T_PS_SOURCE_ON); @@ -3579,6 +3927,8 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_state(port, SNK_HARD_RESET_SINK_ON, PD_T_SAFE_0V); break; case SNK_HARD_RESET_WAIT_VBUS: + if (port->ams == HARD_RESET) + tcpm_ams_finish(port); /* Assume we're disconnected if VBUS doesn't come back. */ tcpm_set_state(port, SNK_UNATTACHED, PD_T_SRC_RECOVER_MAX + PD_T_SRC_TURN_ON); @@ -3606,6 +3956,8 @@ static void run_state_machine(struct tcpm_port *port) 5000); tcpm_set_charge(port, true); } + if (port->ams == HARD_RESET) + tcpm_ams_finish(port); tcpm_set_attached_state(port, true); tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB, false, VSAFE5V); tcpm_set_state(port, SNK_STARTUP, 0); @@ -3616,10 +3968,19 @@ static void run_state_machine(struct tcpm_port *port) port->message_id = 0; port->rx_msgid = -1; tcpm_pd_send_control(port, PD_CTRL_ACCEPT); - if (port->pwr_role == TYPEC_SOURCE) - tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); - else + if (port->pwr_role == TYPEC_SOURCE) { + port->upcoming_state = SRC_SEND_CAPABILITIES; + tcpm_ams_start(port, POWER_NEGOTIATION); + } else { tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0); + } + break; + case SRC_SOFT_RESET_WAIT_SNK_TX: + case SNK_SOFT_RESET: + if (port->ams != NONE_AMS) + tcpm_ams_finish(port); + port->upcoming_state = SOFT_RESET_SEND; + tcpm_ams_start(port, SOFT_RESET_AMS); break; case SOFT_RESET_SEND: port->message_id = 0; @@ -3886,6 +4247,19 @@ static void run_state_machine(struct tcpm_port *port) tcpm_default_state(port), port->vbus_present ? PD_T_PS_SOURCE_OFF : 0); break; + + /* AMS intermediate state */ + case AMS_START: + if (port->upcoming_state == INVALID_STATE) { + tcpm_set_state(port, port->pwr_role == TYPEC_SOURCE ? + SRC_READY : SNK_READY, 0); + break; + } + + upcoming_state = port->upcoming_state; + port->upcoming_state = INVALID_STATE; + tcpm_set_state(port, upcoming_state, 0); + break; default: WARN(1, "Unexpected port state %d\n", port->state); break; @@ -4313,6 +4687,8 @@ static void _tcpm_pd_hard_reset(struct tcpm_port *port) if (port->bist_request == BDO_MODE_TESTDATA && port->tcpc->set_bist_data) port->tcpc->set_bist_data(port->tcpc, false); + if (port->ams != NONE_AMS) + port->ams = NONE_AMS; /* * If we keep receiving hard reset requests, executing the hard reset * must have failed. Revert to error recovery if that happens. @@ -4363,10 +4739,16 @@ static void tcpm_pd_event_handler(struct kthread_work *work) _tcpm_cc_change(port, cc1, cc2); } if (events & TCPM_FRS_EVENT) { - if (port->state == SNK_READY) - tcpm_set_state(port, FR_SWAP_SEND, 0); - else + if (port->state == SNK_READY) { + int ret; + + port->upcoming_state = FR_SWAP_SEND; + ret = tcpm_ams_start(port, FAST_ROLE_SWAP); + if (ret == -EAGAIN) + port->upcoming_state = INVALID_STATE; + } else { tcpm_log(port, "Discarding FRS_SIGNAL! Not in sink ready"); + } } if (events & TCPM_SOURCING_VBUS) { tcpm_log(port, "sourcing vbus"); @@ -4435,6 +4817,7 @@ EXPORT_SYMBOL_GPL(tcpm_sourcing_vbus); static void tcpm_enable_frs_work(struct kthread_work *work) { struct tcpm_port *port = container_of(work, struct tcpm_port, enable_frs); + int ret; mutex_lock(&port->lock); /* Not FRS capable */ @@ -4449,9 +4832,14 @@ static void tcpm_enable_frs_work(struct kthread_work *work) if (port->state != SNK_READY || port->vdm_state != VDM_STATE_DONE || port->send_discover) goto resched; - tcpm_set_state(port, GET_SINK_CAP, 0); - port->sink_cap_done = true; - + port->upcoming_state = GET_SINK_CAP; + ret = tcpm_ams_start(port, GET_SINK_CAPABILITIES); + if (ret == -EAGAIN) { + port->upcoming_state = INVALID_STATE; + } else { + port->sink_cap_done = true; + goto unlock; + } resched: mod_enable_frs_delayed_work(port, GET_SINK_CAP_RETRY_MS); unlock: @@ -4501,7 +4889,12 @@ static int tcpm_dr_set(struct typec_port *p, enum typec_data_role data) port->non_pd_role_swap = true; tcpm_set_state(port, PORT_RESET, 0); } else { - tcpm_set_state(port, DR_SWAP_SEND, 0); + port->upcoming_state = DR_SWAP_SEND; + ret = tcpm_ams_start(port, DATA_ROLE_SWAP); + if (ret == -EAGAIN) { + port->upcoming_state = INVALID_STATE; + goto port_unlock; + } } port->swap_status = 0; @@ -4547,10 +4940,16 @@ static int tcpm_pr_set(struct typec_port *p, enum typec_role role) goto port_unlock; } + port->upcoming_state = PR_SWAP_SEND; + ret = tcpm_ams_start(port, POWER_ROLE_SWAP); + if (ret == -EAGAIN) { + port->upcoming_state = INVALID_STATE; + goto port_unlock; + } + port->swap_status = 0; port->swap_pending = true; reinit_completion(&port->swap_complete); - tcpm_set_state(port, PR_SWAP_SEND, 0); mutex_unlock(&port->lock); if (!wait_for_completion_timeout(&port->swap_complete, @@ -4586,10 +4985,16 @@ static int tcpm_vconn_set(struct typec_port *p, enum typec_role role) goto port_unlock; } + port->upcoming_state = VCONN_SWAP_SEND; + ret = tcpm_ams_start(port, VCONN_SWAP); + if (ret == -EAGAIN) { + port->upcoming_state = INVALID_STATE; + goto port_unlock; + } + port->swap_status = 0; port->swap_pending = true; reinit_completion(&port->swap_complete); - tcpm_set_state(port, VCONN_SWAP_SEND, 0); mutex_unlock(&port->lock); if (!wait_for_completion_timeout(&port->swap_complete, @@ -4654,6 +5059,13 @@ static int tcpm_pps_set_op_curr(struct tcpm_port *port, u16 op_curr) goto port_unlock; } + port->upcoming_state = SNK_NEGOTIATE_PPS_CAPABILITIES; + ret = tcpm_ams_start(port, POWER_NEGOTIATION); + if (ret == -EAGAIN) { + port->upcoming_state = INVALID_STATE; + goto port_unlock; + } + /* Round down operating current to align with PPS valid steps */ op_curr = op_curr - (op_curr % RDO_PROG_CURR_MA_STEP); @@ -4661,7 +5073,6 @@ static int tcpm_pps_set_op_curr(struct tcpm_port *port, u16 op_curr) port->pps_data.op_curr = op_curr; port->pps_status = 0; port->pps_pending = true; - tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0); mutex_unlock(&port->lock); if (!wait_for_completion_timeout(&port->pps_complete, @@ -4710,6 +5121,13 @@ static int tcpm_pps_set_out_volt(struct tcpm_port *port, u16 out_volt) goto port_unlock; } + port->upcoming_state = SNK_NEGOTIATE_PPS_CAPABILITIES; + ret = tcpm_ams_start(port, POWER_NEGOTIATION); + if (ret == -EAGAIN) { + port->upcoming_state = INVALID_STATE; + goto port_unlock; + } + /* Round down output voltage to align with PPS valid steps */ out_volt = out_volt - (out_volt % RDO_PROG_VOLT_MV_STEP); @@ -4717,7 +5135,6 @@ static int tcpm_pps_set_out_volt(struct tcpm_port *port, u16 out_volt) port->pps_data.out_volt = out_volt; port->pps_status = 0; port->pps_pending = true; - tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0); mutex_unlock(&port->lock); if (!wait_for_completion_timeout(&port->pps_complete, @@ -4757,6 +5174,16 @@ static int tcpm_pps_activate(struct tcpm_port *port, bool activate) goto port_unlock; } + if (activate) + port->upcoming_state = SNK_NEGOTIATE_PPS_CAPABILITIES; + else + port->upcoming_state = SNK_NEGOTIATE_CAPABILITIES; + ret = tcpm_ams_start(port, POWER_NEGOTIATION); + if (ret == -EAGAIN) { + port->upcoming_state = INVALID_STATE; + goto port_unlock; + } + reinit_completion(&port->pps_complete); port->pps_status = 0; port->pps_pending = true; @@ -4765,9 +5192,6 @@ static int tcpm_pps_activate(struct tcpm_port *port, bool activate) if (activate) { port->pps_data.out_volt = port->supply_voltage; port->pps_data.op_curr = port->current_limit; - tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0); - } else { - tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); } mutex_unlock(&port->lock); diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h index bb9a782e1411..79599b90ba55 100644 --- a/include/linux/usb/pd.h +++ b/include/linux/usb/pd.h @@ -479,6 +479,7 @@ static inline unsigned int rdo_max_power(u32 rdo) #define PD_T_NEWSRC 250 /* Maximum of 275ms */ #define PD_T_SWAP_SRC_START 20 /* Minimum of 20ms */ #define PD_T_BIST_CONT_MODE 50 /* 30 - 60 ms */ +#define PD_T_SINK_TX 16 /* 16 - 20 ms */ #define PD_T_DRP_TRY 100 /* 75 - 150 ms */ #define PD_T_DRP_TRYWAIT 600 /* 400 - 800 ms */ diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h index f4a18427f5c4..3af99f85e8b9 100644 --- a/include/linux/usb/tcpm.h +++ b/include/linux/usb/tcpm.h @@ -19,6 +19,10 @@ enum typec_cc_status { TYPEC_CC_RP_3_0, }; +/* Collision Avoidance */ +#define SINK_TX_NG TYPEC_CC_RP_1_5 +#define SINK_TX_OK TYPEC_CC_RP_3_0 + enum typec_cc_polarity { TYPEC_POLARITY_CC1, TYPEC_POLARITY_CC2, -- cgit v1.2.3 From 8dea75e11380fc59656fe965766ac192a831455f Mon Sep 17 00:00:00 2001 From: Kyle Tso Date: Thu, 14 Jan 2021 22:50:52 +0800 Subject: usb: typec: tcpm: Protocol Error handling PD3.0 Spec 6.8.1 describes how to handle Protocol Error. There are general rules defined in Table 6-61 which regulate incoming Message handling. If the incoming Message is unexpected, unsupported, or unrecognized, Protocol Error occurs. Follow the rules to handle these situations. Also consider PD2.0 connection (PD2.0 Spec Table 6-36) for backward compatibilities. To know the types of AMS in all the recipient's states, identify those AMS who are initiated by the port partner but not yet recorded in the current code. Besides, introduce a new state CHUNK_NOT_SUPP to delay the NOT_SUPPORTED message after receiving a chunked message. Tested-by: Hans de Goede Reviewed-by: Heikki Krogerus Signed-off-by: Kyle Tso Link: https://lore.kernel.org/r/20210114145053.1952756-3-kyletso@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 335 ++++++++++++++++++++++++++++++------------ include/linux/usb/pd.h | 1 + 2 files changed, 246 insertions(+), 90 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 2b16d2764092..70922723da4b 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -143,7 +143,8 @@ S(PORT_RESET), \ S(PORT_RESET_WAIT_OFF), \ \ - S(AMS_START) + S(AMS_START), \ + S(CHUNK_NOT_SUPP) #define FOREACH_AMS(S) \ S(NONE_AMS), \ @@ -433,6 +434,7 @@ struct tcpm_port { /* Collision Avoidance and Atomic Message Sequence */ enum tcpm_state upcoming_state; enum tcpm_ams ams; + enum tcpm_ams next_ams; bool in_ams; #ifdef CONFIG_DEBUG_FS @@ -1213,7 +1215,8 @@ static int tcpm_ams_start(struct tcpm_port *port, enum tcpm_ams ams) tcpm_log(port, "AMS %s start", tcpm_ams_str[ams]); - if (!tcpm_ams_interruptible(port) && ams != HARD_RESET) { + if (!tcpm_ams_interruptible(port) && + !(ams == HARD_RESET || ams == SOFT_RESET_AMS)) { port->upcoming_state = INVALID_STATE; tcpm_log(port, "AMS %s not interruptible, aborting", tcpm_ams_str[port->ams]); @@ -1231,11 +1234,10 @@ static int tcpm_ams_start(struct tcpm_port *port, enum tcpm_ams ams) tcpm_set_state(port, HARD_RESET_START, 0); return ret; } else if (ams == SOFT_RESET_AMS) { - if (!port->explicit_contract) { - port->upcoming_state = INVALID_STATE; + if (!port->explicit_contract) tcpm_set_cc(port, tcpm_rp_cc(port)); - return ret; - } + tcpm_set_state(port, SOFT_RESET_SEND, 0); + return ret; } else if (tcpm_vdm_ams(port)) { /* tSinkTx is enforced in vdm_run_state_machine */ if (port->negotiated_rev >= PD_REV30) @@ -1452,6 +1454,9 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, case CMDT_INIT: switch (cmd) { case CMD_DISCOVER_IDENT: + if (PD_VDO_VID(p[0]) != USB_SID_PD) + break; + /* 6.4.4.3.1: Only respond as UFP (device) */ if (port->data_role == TYPEC_DEVICE && port->nr_snk_vdo) { @@ -1537,22 +1542,37 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, return 0; } break; + case VDO_CMD_VENDOR(0) ... VDO_CMD_VENDOR(15): + break; default: + /* Unrecognized SVDM */ + response[0] = p[0] | VDO_CMDT(CMDT_RSP_NAK); + rlen = 1; break; } break; case CMDT_RSP_NAK: tcpm_ams_finish(port); switch (cmd) { + case CMD_DISCOVER_IDENT: + case CMD_DISCOVER_SVID: + case CMD_DISCOVER_MODES: + case VDO_CMD_VENDOR(0) ... VDO_CMD_VENDOR(15): + break; case CMD_ENTER_MODE: /* Back to USB Operation */ *adev_action = ADEV_NOTIFY_USB_AND_QUEUE_VDM; return 0; default: + /* Unrecognized SVDM */ + response[0] = p[0] | VDO_CMDT(CMDT_RSP_NAK); + rlen = 1; break; } break; default: + response[0] = p[0] | VDO_CMDT(CMDT_RSP_NAK); + rlen = 1; break; } @@ -1588,8 +1608,12 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port, port->vdm_state = VDM_STATE_DONE; } - if (PD_VDO_SVDM(p[0])) + if (PD_VDO_SVDM(p[0])) { rlen = tcpm_pd_svdm(port, adev, p, cnt, response, &adev_action); + } else { + if (port->negotiated_rev >= PD_REV30) + tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP); + } /* * We are done with any state stored in the port struct now, except @@ -2039,6 +2063,71 @@ static int tcpm_set_auto_vbus_discharge_threshold(struct tcpm_port *port, return ret; } +static void tcpm_pd_handle_state(struct tcpm_port *port, + enum tcpm_state state, + enum tcpm_ams ams, + unsigned int delay_ms) +{ + switch (port->state) { + case SRC_READY: + case SNK_READY: + port->ams = ams; + tcpm_set_state(port, state, delay_ms); + break; + /* 8.3.3.4.1.1 and 6.8.1 power transitioning */ + case SNK_TRANSITION_SINK: + case SNK_TRANSITION_SINK_VBUS: + case SRC_TRANSITION_SUPPLY: + tcpm_set_state(port, HARD_RESET_SEND, 0); + break; + default: + if (!tcpm_ams_interruptible(port)) { + tcpm_set_state(port, port->pwr_role == TYPEC_SOURCE ? + SRC_SOFT_RESET_WAIT_SNK_TX : + SNK_SOFT_RESET, + 0); + } else { + /* process the Message 6.8.1 */ + port->upcoming_state = state; + port->next_ams = ams; + tcpm_set_state(port, ready_state(port), delay_ms); + } + break; + } +} + +static void tcpm_pd_handle_msg(struct tcpm_port *port, + enum pd_msg_request message, + enum tcpm_ams ams) +{ + switch (port->state) { + case SRC_READY: + case SNK_READY: + port->ams = ams; + tcpm_queue_message(port, message); + break; + /* PD 3.0 Spec 8.3.3.4.1.1 and 6.8.1 */ + case SNK_TRANSITION_SINK: + case SNK_TRANSITION_SINK_VBUS: + case SRC_TRANSITION_SUPPLY: + tcpm_set_state(port, HARD_RESET_SEND, 0); + break; + default: + if (!tcpm_ams_interruptible(port)) { + tcpm_set_state(port, port->pwr_role == TYPEC_SOURCE ? + SRC_SOFT_RESET_WAIT_SNK_TX : + SNK_SOFT_RESET, + 0); + } else { + port->next_ams = ams; + tcpm_set_state(port, ready_state(port), 0); + /* 6.8.1 process the Message */ + tcpm_queue_message(port, message); + } + break; + } +} + static void tcpm_pd_data_request(struct tcpm_port *port, const struct pd_message *msg) { @@ -2052,9 +2141,6 @@ static void tcpm_pd_data_request(struct tcpm_port *port, switch (type) { case PD_DATA_SOURCE_CAP: - if (port->pwr_role != TYPEC_SINK) - break; - for (i = 0; i < cnt; i++) port->source_caps[i] = le32_to_cpu(msg->payload[i]); @@ -2070,12 +2156,26 @@ static void tcpm_pd_data_request(struct tcpm_port *port, * to comply with 6.2.1.1.5 of the USB PD 3.0 spec. We don't * support Rev 1.0 so just do nothing in that scenario. */ - if (rev == PD_REV10) + if (rev == PD_REV10) { + if (port->ams == GET_SOURCE_CAPABILITIES) + tcpm_ams_finish(port); break; + } if (rev < PD_MAX_REV) port->negotiated_rev = rev; + if (port->pwr_role == TYPEC_SOURCE) { + if (port->ams == GET_SOURCE_CAPABILITIES) + tcpm_pd_handle_state(port, SRC_READY, NONE_AMS, 0); + /* Unexpected Source Capabilities */ + else + tcpm_pd_handle_msg(port, + port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + } else if (port->state == SNK_WAIT_CAPABILITIES) { /* * This message may be received even if VBUS is not * present. This is quite unexpected; see USB PD @@ -2089,30 +2189,48 @@ static void tcpm_pd_data_request(struct tcpm_port *port, * but be prepared to keep waiting for VBUS after it was * handled. */ - tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); + port->ams = POWER_NEGOTIATION; + tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); + } else { + if (port->ams == GET_SOURCE_CAPABILITIES) + tcpm_ams_finish(port); + tcpm_pd_handle_state(port, SNK_NEGOTIATE_CAPABILITIES, + POWER_NEGOTIATION, 0); + } break; case PD_DATA_REQUEST: - if (port->pwr_role != TYPEC_SOURCE || - cnt != 1) { - tcpm_queue_message(port, PD_MSG_CTRL_REJECT); - break; - } - /* * Adjust revision in subsequent message headers, as required, * to comply with 6.2.1.1.5 of the USB PD 3.0 spec. We don't * support Rev 1.0 so just reject in that scenario. */ if (rev == PD_REV10) { - tcpm_queue_message(port, PD_MSG_CTRL_REJECT); + tcpm_pd_handle_msg(port, + port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); break; } if (rev < PD_MAX_REV) port->negotiated_rev = rev; + if (port->pwr_role != TYPEC_SOURCE || cnt != 1) { + tcpm_pd_handle_msg(port, + port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + break; + } + port->sink_request = le32_to_cpu(msg->payload[0]); - tcpm_set_state(port, SRC_NEGOTIATE_CAPABILITIES, 0); + if (port->state == SRC_SEND_CAPABILITIES) + tcpm_set_state(port, SRC_NEGOTIATE_CAPABILITIES, 0); + else + tcpm_pd_handle_state(port, SRC_NEGOTIATE_CAPABILITIES, + POWER_NEGOTIATION, 0); break; case PD_DATA_SINK_CAP: /* We don't do anything with this at the moment... */ @@ -2133,16 +2251,22 @@ static void tcpm_pd_data_request(struct tcpm_port *port, port->nr_sink_caps = cnt; port->sink_cap_done = true; - tcpm_set_state(port, SNK_READY, 0); + if (port->ams == GET_SINK_CAPABILITIES) + tcpm_pd_handle_state(port, ready_state(port), NONE_AMS, 0); + /* Unexpected Sink Capabilities */ + else + tcpm_pd_handle_msg(port, + port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); break; case PD_DATA_VENDOR_DEF: tcpm_handle_vdm_request(port, msg->payload, cnt); break; case PD_DATA_BIST: - if (port->state == SRC_READY || port->state == SNK_READY) { - port->bist_request = le32_to_cpu(msg->payload[0]); - tcpm_set_state(port, BIST_RX, 0); - } + port->bist_request = le32_to_cpu(msg->payload[0]); + tcpm_pd_handle_state(port, BIST_RX, BIST, 0); break; case PD_DATA_ALERT: tcpm_handle_alert(port, msg->payload, cnt); @@ -2150,10 +2274,17 @@ static void tcpm_pd_data_request(struct tcpm_port *port, case PD_DATA_BATT_STATUS: case PD_DATA_GET_COUNTRY_INFO: /* Currently unsupported */ - tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP); + tcpm_pd_handle_msg(port, port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); break; default: - tcpm_log(port, "Unhandled data message type %#x", type); + tcpm_pd_handle_msg(port, port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + tcpm_log(port, "Unrecognized data message type %#x", type); break; } } @@ -2178,26 +2309,10 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, case PD_CTRL_PING: break; case PD_CTRL_GET_SOURCE_CAP: - switch (port->state) { - case SRC_READY: - case SNK_READY: - tcpm_queue_message(port, PD_MSG_DATA_SOURCE_CAP); - break; - default: - tcpm_queue_message(port, PD_MSG_CTRL_REJECT); - break; - } + tcpm_pd_handle_msg(port, PD_MSG_DATA_SOURCE_CAP, GET_SOURCE_CAPABILITIES); break; case PD_CTRL_GET_SINK_CAP: - switch (port->state) { - case SRC_READY: - case SNK_READY: - tcpm_queue_message(port, PD_MSG_DATA_SINK_CAP); - break; - default: - tcpm_queue_message(port, PD_MSG_CTRL_REJECT); - break; - } + tcpm_pd_handle_msg(port, PD_MSG_DATA_SINK_CAP, GET_SINK_CAPABILITIES); break; case PD_CTRL_GOTO_MIN: break; @@ -2236,6 +2351,11 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, tcpm_set_state(port, FR_SWAP_SNK_SRC_NEW_SINK_READY, 0); break; default: + tcpm_pd_handle_state(port, + port->pwr_role == TYPEC_SOURCE ? + SRC_SOFT_RESET_WAIT_SNK_TX : + SNK_SOFT_RESET, + NONE_AMS, 0); break; } break; @@ -2282,6 +2402,11 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, tcpm_set_state(port, ready_state(port), 0); break; default: + tcpm_pd_handle_state(port, + port->pwr_role == TYPEC_SOURCE ? + SRC_SOFT_RESET_WAIT_SNK_TX : + SNK_SOFT_RESET, + NONE_AMS, 0); break; } break; @@ -2298,8 +2423,6 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, tcpm_set_state(port, SNK_TRANSITION_SINK, 0); break; case SOFT_RESET_SEND: - port->message_id = 0; - port->rx_msgid = -1; if (port->ams == SOFT_RESET_AMS) tcpm_ams_finish(port); if (port->pwr_role == TYPEC_SOURCE) { @@ -2322,57 +2445,45 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, tcpm_set_state(port, FR_SWAP_SNK_SRC_TRANSITION_TO_OFF, 0); break; default: + tcpm_pd_handle_state(port, + port->pwr_role == TYPEC_SOURCE ? + SRC_SOFT_RESET_WAIT_SNK_TX : + SNK_SOFT_RESET, + NONE_AMS, 0); break; } break; case PD_CTRL_SOFT_RESET: + port->ams = SOFT_RESET_AMS; tcpm_set_state(port, SOFT_RESET, 0); break; case PD_CTRL_DR_SWAP: - if (port->typec_caps.data != TYPEC_PORT_DRD) { - tcpm_queue_message(port, PD_MSG_CTRL_REJECT); - break; - } /* * XXX * 6.3.9: If an alternate mode is active, a request to swap * alternate modes shall trigger a port reset. */ - switch (port->state) { - case SRC_READY: - case SNK_READY: - tcpm_set_state(port, DR_SWAP_ACCEPT, 0); - break; - default: - tcpm_queue_message(port, PD_MSG_CTRL_WAIT); - break; - } + if (port->typec_caps.data != TYPEC_PORT_DRD) + tcpm_pd_handle_msg(port, + port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + else + tcpm_pd_handle_state(port, DR_SWAP_ACCEPT, DATA_ROLE_SWAP, 0); break; case PD_CTRL_PR_SWAP: - if (port->port_type != TYPEC_PORT_DRP) { - tcpm_queue_message(port, PD_MSG_CTRL_REJECT); - break; - } - switch (port->state) { - case SRC_READY: - case SNK_READY: - tcpm_set_state(port, PR_SWAP_ACCEPT, 0); - break; - default: - tcpm_queue_message(port, PD_MSG_CTRL_WAIT); - break; - } + if (port->port_type != TYPEC_PORT_DRP) + tcpm_pd_handle_msg(port, + port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + else + tcpm_pd_handle_state(port, PR_SWAP_ACCEPT, POWER_ROLE_SWAP, 0); break; case PD_CTRL_VCONN_SWAP: - switch (port->state) { - case SRC_READY: - case SNK_READY: - tcpm_set_state(port, VCONN_SWAP_ACCEPT, 0); - break; - default: - tcpm_queue_message(port, PD_MSG_CTRL_WAIT); - break; - } + tcpm_pd_handle_state(port, VCONN_SWAP_ACCEPT, VCONN_SWAP, 0); break; case PD_CTRL_GET_SOURCE_CAP_EXT: case PD_CTRL_GET_STATUS: @@ -2380,10 +2491,19 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, case PD_CTRL_GET_PPS_STATUS: case PD_CTRL_GET_COUNTRY_CODES: /* Currently not supported */ - tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP); + tcpm_pd_handle_msg(port, + port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); break; default: - tcpm_log(port, "Unhandled ctrl message type %#x", type); + tcpm_pd_handle_msg(port, + port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + tcpm_log(port, "Unrecognized ctrl message type %#x", type); break; } } @@ -2395,11 +2515,13 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, unsigned int data_size = pd_ext_header_data_size_le(msg->ext_msg.header); if (!(msg->ext_msg.header & PD_EXT_HDR_CHUNKED)) { + tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); tcpm_log(port, "Unchunked extended messages unsupported"); return; } if (data_size > PD_EXT_MAX_CHUNK_DATA) { + tcpm_pd_handle_state(port, CHUNK_NOT_SUPP, NONE_AMS, PD_T_CHUNK_NOT_SUPP); tcpm_log(port, "Chunk handling not yet supported"); return; } @@ -2412,16 +2534,18 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, */ if (msg->ext_msg.data[USB_PD_EXT_SDB_EVENT_FLAGS] & USB_PD_EXT_SDB_PPS_EVENTS) - tcpm_set_state(port, GET_PPS_STATUS_SEND, 0); + tcpm_pd_handle_state(port, GET_PPS_STATUS_SEND, + GETTING_SOURCE_SINK_STATUS, 0); + else - tcpm_set_state(port, ready_state(port), 0); + tcpm_pd_handle_state(port, ready_state(port), NONE_AMS, 0); break; case PD_EXT_PPS_STATUS: /* * For now the PPS status message is used to clear events * and nothing more. */ - tcpm_set_state(port, ready_state(port), 0); + tcpm_pd_handle_state(port, ready_state(port), NONE_AMS, 0); break; case PD_EXT_SOURCE_CAP_EXT: case PD_EXT_GET_BATT_CAP: @@ -2435,10 +2559,11 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, case PD_EXT_FW_UPDATE_RESPONSE: case PD_EXT_COUNTRY_INFO: case PD_EXT_COUNTRY_CODES: - tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP); + tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); break; default: - tcpm_log(port, "Unhandled extended message type %#x", type); + tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); + tcpm_log(port, "Unrecognized extended message type %#x", type); break; } } @@ -2551,7 +2676,12 @@ static bool tcpm_send_queued_message(struct tcpm_port *port) tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP); break; case PD_MSG_DATA_SINK_CAP: - tcpm_pd_send_sink_caps(port); + ret = tcpm_pd_send_sink_caps(port); + if (ret < 0) { + tcpm_log(port, "Unable to send snk caps, ret=%d", ret); + tcpm_set_state(port, SNK_SOFT_RESET, 0); + } + tcpm_ams_finish(port); break; case PD_MSG_DATA_SOURCE_CAP: ret = tcpm_pd_send_source_caps(port); @@ -2561,8 +2691,11 @@ static bool tcpm_send_queued_message(struct tcpm_port *port) ret); tcpm_set_state(port, SOFT_RESET_SEND, 0); } else if (port->pwr_role == TYPEC_SOURCE) { + tcpm_ams_finish(port); tcpm_set_state(port, HARD_RESET_SEND, PD_T_SENDER_RESPONSE); + } else { + tcpm_ams_finish(port); } break; default: @@ -3581,6 +3714,11 @@ static void run_state_machine(struct tcpm_port *port) if (port->ams != NONE_AMS) tcpm_ams_finish(port); + if (port->next_ams != NONE_AMS) { + port->ams = port->next_ams; + port->next_ams = NONE_AMS; + } + /* * If previous AMS is interrupted, switch to the upcoming * state. @@ -3821,6 +3959,11 @@ static void run_state_machine(struct tcpm_port *port) if (port->ams != NONE_AMS) tcpm_ams_finish(port); + if (port->next_ams != NONE_AMS) { + port->ams = port->next_ams; + port->next_ams = NONE_AMS; + } + /* * If previous AMS is interrupted, switch to the upcoming * state. @@ -3968,6 +4111,7 @@ static void run_state_machine(struct tcpm_port *port) port->message_id = 0; port->rx_msgid = -1; tcpm_pd_send_control(port, PD_CTRL_ACCEPT); + tcpm_ams_finish(port); if (port->pwr_role == TYPEC_SOURCE) { port->upcoming_state = SRC_SEND_CAPABILITIES; tcpm_ams_start(port, POWER_NEGOTIATION); @@ -4004,6 +4148,7 @@ static void run_state_machine(struct tcpm_port *port) break; case DR_SWAP_SEND_TIMEOUT: tcpm_swap_complete(port, -ETIMEDOUT); + tcpm_ams_finish(port); tcpm_set_state(port, ready_state(port), 0); break; case DR_SWAP_CHANGE_DR: @@ -4016,6 +4161,7 @@ static void run_state_machine(struct tcpm_port *port) TYPEC_HOST); port->send_discover = true; } + tcpm_ams_finish(port); tcpm_set_state(port, ready_state(port), 0); break; @@ -4143,6 +4289,7 @@ static void run_state_machine(struct tcpm_port *port) case VCONN_SWAP_ACCEPT: tcpm_pd_send_control(port, PD_CTRL_ACCEPT); + tcpm_ams_finish(port); tcpm_set_state(port, VCONN_SWAP_START, 0); break; case VCONN_SWAP_SEND: @@ -4260,6 +4407,12 @@ static void run_state_machine(struct tcpm_port *port) port->upcoming_state = INVALID_STATE; tcpm_set_state(port, upcoming_state, 0); break; + + /* Chunk state */ + case CHUNK_NOT_SUPP: + tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP); + tcpm_set_state(port, port->pwr_role == TYPEC_SOURCE ? SRC_READY : SNK_READY, 0); + break; default: WARN(1, "Unexpected port state %d\n", port->state); break; @@ -4689,6 +4842,8 @@ static void _tcpm_pd_hard_reset(struct tcpm_port *port) if (port->ams != NONE_AMS) port->ams = NONE_AMS; + if (port->hard_reset_count < PD_N_HARD_RESET_COUNT) + port->ams = HARD_RESET; /* * If we keep receiving hard reset requests, executing the hard reset * must have failed. Revert to error recovery if that happens. diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h index 79599b90ba55..272454f9cd67 100644 --- a/include/linux/usb/pd.h +++ b/include/linux/usb/pd.h @@ -480,6 +480,7 @@ static inline unsigned int rdo_max_power(u32 rdo) #define PD_T_SWAP_SRC_START 20 /* Minimum of 20ms */ #define PD_T_BIST_CONT_MODE 50 /* 30 - 60 ms */ #define PD_T_SINK_TX 16 /* 16 - 20 ms */ +#define PD_T_CHUNK_NOT_SUPP 42 /* 40 - 50 ms */ #define PD_T_DRP_TRY 100 /* 75 - 150 ms */ #define PD_T_DRP_TRYWAIT 600 /* 400 - 800 ms */ -- cgit v1.2.3 From 8d3a0578ad1aaadb1c2a2fcc4c51454cbbce2eca Mon Sep 17 00:00:00 2001 From: Kyle Tso Date: Thu, 14 Jan 2021 22:50:53 +0800 Subject: usb: typec: tcpm: Respond Wait if VDM state machine is running Port partner could send PR_SWAP/DR_SWAP/VCONN_SWAP/Request just after it enters Ready states. This will cause conficts if the port is going to send DISC_IDENT in the Ready states of TCPM. Set a flag indicating that the state machine is processing VDM and respond Wait messages until the VDM state machine stops. Tested-by: Hans de Goede Reviewed-by: Heikki Krogerus Signed-off-by: Kyle Tso Link: https://lore.kernel.org/r/20210114145053.1952756-4-kyletso@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 80 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 73 insertions(+), 7 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 70922723da4b..0dd932fe08d0 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -352,6 +352,7 @@ struct tcpm_port { struct hrtimer enable_frs_timer; struct kthread_work enable_frs; bool state_machine_running; + bool vdm_sm_running; struct completion tx_complete; enum tcpm_transmit_status tx_status; @@ -1526,6 +1527,7 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, rlen = 1; } else { tcpm_register_partner_altmodes(port); + port->vdm_sm_running = false; } break; case CMD_ENTER_MODE: @@ -1569,10 +1571,12 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, rlen = 1; break; } + port->vdm_sm_running = false; break; default: response[0] = p[0] | VDO_CMDT(CMDT_RSP_NAK); rlen = 1; + port->vdm_sm_running = false; break; } @@ -1739,6 +1743,8 @@ static void vdm_run_state_machine(struct tcpm_port *port) switch (PD_VDO_CMD(vdo_hdr)) { case CMD_DISCOVER_IDENT: res = tcpm_ams_start(port, DISCOVER_IDENTITY); + if (res == 0) + port->send_discover = false; break; case CMD_DISCOVER_SVID: res = tcpm_ams_start(port, DISCOVER_SVIDS); @@ -1763,8 +1769,10 @@ static void vdm_run_state_machine(struct tcpm_port *port) break; } - if (res < 0) + if (res < 0) { + port->vdm_sm_running = false; return; + } } port->vdm_state = VDM_STATE_SEND_MESSAGE; @@ -1843,6 +1851,9 @@ static void vdm_state_machine_work(struct kthread_work *work) port->vdm_state != VDM_STATE_BUSY && port->vdm_state != VDM_STATE_SEND_MESSAGE); + if (port->vdm_state == VDM_STATE_ERR_TMOUT) + port->vdm_sm_running = false; + mutex_unlock(&port->lock); } @@ -2226,6 +2237,12 @@ static void tcpm_pd_data_request(struct tcpm_port *port, } port->sink_request = le32_to_cpu(msg->payload[0]); + + if (port->vdm_sm_running && port->explicit_contract) { + tcpm_pd_handle_msg(port, PD_MSG_CTRL_WAIT, port->ams); + break; + } + if (port->state == SRC_SEND_CAPABILITIES) tcpm_set_state(port, SRC_NEGOTIATE_CAPABILITIES, 0); else @@ -2328,6 +2345,10 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, TYPEC_PWR_MODE_PD, port->pps_data.active, port->supply_voltage); + /* Set VDM running flag ASAP */ + if (port->data_role == TYPEC_HOST && + port->send_discover) + port->vdm_sm_running = true; tcpm_set_state(port, SNK_READY, 0); } else { /* @@ -2365,10 +2386,14 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, switch (port->state) { case SNK_NEGOTIATE_CAPABILITIES: /* USB PD specification, Figure 8-43 */ - if (port->explicit_contract) + if (port->explicit_contract) { next_state = SNK_READY; - else + if (port->data_role == TYPEC_HOST && + port->send_discover) + port->vdm_sm_running = true; + } else { next_state = SNK_WAIT_CAPABILITIES; + } tcpm_set_state(port, next_state, 0); break; case SNK_NEGOTIATE_PPS_CAPABILITIES: @@ -2377,6 +2402,11 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, port->pps_data.op_curr = port->current_limit; port->pps_status = (type == PD_CTRL_WAIT ? -EAGAIN : -EOPNOTSUPP); + + if (port->data_role == TYPEC_HOST && + port->send_discover) + port->vdm_sm_running = true; + tcpm_set_state(port, SNK_READY, 0); break; case DR_SWAP_SEND: @@ -2433,6 +2463,10 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, } break; case DR_SWAP_SEND: + if (port->data_role == TYPEC_DEVICE && + port->send_discover) + port->vdm_sm_running = true; + tcpm_set_state(port, DR_SWAP_CHANGE_DR, 0); break; case PR_SWAP_SEND: @@ -2463,26 +2497,43 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, * 6.3.9: If an alternate mode is active, a request to swap * alternate modes shall trigger a port reset. */ - if (port->typec_caps.data != TYPEC_PORT_DRD) + if (port->typec_caps.data != TYPEC_PORT_DRD) { tcpm_pd_handle_msg(port, port->negotiated_rev < PD_REV30 ? PD_MSG_CTRL_REJECT : PD_MSG_CTRL_NOT_SUPP, NONE_AMS); - else + } else { + if (port->vdm_sm_running) { + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } + tcpm_pd_handle_state(port, DR_SWAP_ACCEPT, DATA_ROLE_SWAP, 0); + } break; case PD_CTRL_PR_SWAP: - if (port->port_type != TYPEC_PORT_DRP) + if (port->port_type != TYPEC_PORT_DRP) { tcpm_pd_handle_msg(port, port->negotiated_rev < PD_REV30 ? PD_MSG_CTRL_REJECT : PD_MSG_CTRL_NOT_SUPP, NONE_AMS); - else + } else { + if (port->vdm_sm_running) { + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } + tcpm_pd_handle_state(port, PR_SWAP_ACCEPT, POWER_ROLE_SWAP, 0); + } break; case PD_CTRL_VCONN_SWAP: + if (port->vdm_sm_running) { + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } + tcpm_pd_handle_state(port, VCONN_SWAP_ACCEPT, VCONN_SWAP, 0); break; case PD_CTRL_GET_SOURCE_CAP_EXT: @@ -3346,6 +3397,7 @@ static void tcpm_reset_port(struct tcpm_port *port) } port->in_ams = false; port->ams = NONE_AMS; + port->vdm_sm_running = false; tcpm_unregister_altmodes(port); tcpm_typec_disconnect(port); port->attached = false; @@ -4144,6 +4196,9 @@ static void run_state_machine(struct tcpm_port *port) break; case DR_SWAP_ACCEPT: tcpm_pd_send_control(port, PD_CTRL_ACCEPT); + /* Set VDM state machine running flag ASAP */ + if (port->data_role == TYPEC_DEVICE && port->send_discover) + port->vdm_sm_running = true; tcpm_set_state_cond(port, DR_SWAP_CHANGE_DR, 0); break; case DR_SWAP_SEND_TIMEOUT: @@ -4299,6 +4354,8 @@ static void run_state_machine(struct tcpm_port *port) break; case VCONN_SWAP_SEND_TIMEOUT: tcpm_swap_complete(port, -ETIMEDOUT); + if (port->data_role == TYPEC_HOST && port->send_discover) + port->vdm_sm_running = true; tcpm_set_state(port, ready_state(port), 0); break; case VCONN_SWAP_START: @@ -4314,10 +4371,14 @@ static void run_state_machine(struct tcpm_port *port) case VCONN_SWAP_TURN_ON_VCONN: tcpm_set_vconn(port, true); tcpm_pd_send_control(port, PD_CTRL_PS_RDY); + if (port->data_role == TYPEC_HOST && port->send_discover) + port->vdm_sm_running = true; tcpm_set_state(port, ready_state(port), 0); break; case VCONN_SWAP_TURN_OFF_VCONN: tcpm_set_vconn(port, false); + if (port->data_role == TYPEC_HOST && port->send_discover) + port->vdm_sm_running = true; tcpm_set_state(port, ready_state(port), 0); break; @@ -4325,6 +4386,8 @@ static void run_state_machine(struct tcpm_port *port) case PR_SWAP_CANCEL: case VCONN_SWAP_CANCEL: tcpm_swap_complete(port, port->swap_status); + if (port->data_role == TYPEC_HOST && port->send_discover) + port->vdm_sm_running = true; if (port->pwr_role == TYPEC_SOURCE) tcpm_set_state(port, SRC_READY, 0); else @@ -4654,6 +4717,9 @@ static void _tcpm_pd_vbus_on(struct tcpm_port *port) switch (port->state) { case SNK_TRANSITION_SINK_VBUS: port->explicit_contract = true; + /* Set the VDM flag ASAP */ + if (port->data_role == TYPEC_HOST && port->send_discover) + port->vdm_sm_running = true; tcpm_set_state(port, SNK_READY, 0); break; case SNK_DISCOVERY: -- cgit v1.2.3 From 386ce1d748edfdf57f21310647e2d42dd5f737f2 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 20 Jan 2021 23:50:45 +0100 Subject: usb: typec: tcpci_maxim: Drop GPIO includes This driver includes the legacy GPIO header , the new GPIO header and the deprecated OF GPIO header yet fail to use symbols from any of them, so drop these includes. Cc: Guenter Roeck Cc: Heikki Krogerus Reviewed-by: Guenter Roeck Reviewed-by: Heikki Krogerus Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20210120225045.173556-1-linus.walleij@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci_maxim.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.c b/drivers/usb/typec/tcpm/tcpci_maxim.c index 319266329b42..0e2bb1235ab5 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim.c +++ b/drivers/usb/typec/tcpm/tcpci_maxim.c @@ -5,13 +5,10 @@ * MAXIM TCPCI based TCPC driver */ -#include -#include #include #include #include #include -#include #include #include #include -- cgit v1.2.3 From 4f30b9d2315fafa41841e5cfd5e48ea1d4a14148 Mon Sep 17 00:00:00 2001 From: Aswath Govindraju Date: Tue, 19 Jan 2021 10:58:10 +0530 Subject: usb: cdns3: Add support for TI's AM64 SoC Add support for USB controller present on the AM64x SoC. Signed-off-by: Aswath Govindraju Signed-off-by: Peter Chen --- drivers/usb/cdns3/cdns3-ti.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/cdns3/cdns3-ti.c b/drivers/usb/cdns3/cdns3-ti.c index 90e246601537..eccb1c766bba 100644 --- a/drivers/usb/cdns3/cdns3-ti.c +++ b/drivers/usb/cdns3/cdns3-ti.c @@ -214,6 +214,7 @@ static int cdns_ti_remove(struct platform_device *pdev) static const struct of_device_id cdns_ti_of_match[] = { { .compatible = "ti,j721e-usb", }, + { .compatible = "ti,am64-usb", }, {}, }; MODULE_DEVICE_TABLE(of, cdns_ti_of_match); -- cgit v1.2.3 From f75a1025c0b94d58de56c9421104ed6e987ddcd2 Mon Sep 17 00:00:00 2001 From: Kyle Tso Date: Sat, 16 Jan 2021 00:33:11 +0800 Subject: usb: typec: tcpm: Create legacy PDOs for PD2 connection If the port partner is PD2, the PDOs of the local port should follow the format defined in PD2 Spec. Dynamically modify the pre-defined PD3 PDOs and transform them into PD2 format before sending them to the PD2 port partner. Reviewed-by: Guenter Roeck Signed-off-by: Kyle Tso Link: https://lore.kernel.org/r/20210115163311.391332-1-kyletso@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 62 ++++++++++++++++++++++++++++++++++++------- include/linux/usb/pd.h | 1 + 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 0dd932fe08d0..0afd8ef692e8 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -1023,13 +1023,47 @@ static int tcpm_set_pwr_role(struct tcpm_port *port, enum typec_role role) return 0; } +/* + * Transform the PDO to be compliant to PD rev2.0. + * Return 0 if the PDO type is not defined in PD rev2.0. + * Otherwise, return the converted PDO. + */ +static u32 tcpm_forge_legacy_pdo(struct tcpm_port *port, u32 pdo, enum typec_role role) +{ + switch (pdo_type(pdo)) { + case PDO_TYPE_FIXED: + if (role == TYPEC_SINK) + return pdo & ~PDO_FIXED_FRS_CURR_MASK; + else + return pdo & ~PDO_FIXED_UNCHUNK_EXT; + case PDO_TYPE_VAR: + case PDO_TYPE_BATT: + return pdo; + case PDO_TYPE_APDO: + default: + return 0; + } +} + static int tcpm_pd_send_source_caps(struct tcpm_port *port) { struct pd_message msg; - int i; + u32 pdo; + unsigned int i, nr_pdo = 0; memset(&msg, 0, sizeof(msg)); - if (!port->nr_src_pdo) { + + for (i = 0; i < port->nr_src_pdo; i++) { + if (port->negotiated_rev >= PD_REV30) { + msg.payload[nr_pdo++] = cpu_to_le32(port->src_pdo[i]); + } else { + pdo = tcpm_forge_legacy_pdo(port, port->src_pdo[i], TYPEC_SOURCE); + if (pdo) + msg.payload[nr_pdo++] = cpu_to_le32(pdo); + } + } + + if (!nr_pdo) { /* No source capabilities defined, sink only */ msg.header = PD_HEADER_LE(PD_CTRL_REJECT, port->pwr_role, @@ -1042,10 +1076,8 @@ static int tcpm_pd_send_source_caps(struct tcpm_port *port) port->data_role, port->negotiated_rev, port->message_id, - port->nr_src_pdo); + nr_pdo); } - for (i = 0; i < port->nr_src_pdo; i++) - msg.payload[i] = cpu_to_le32(port->src_pdo[i]); return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); } @@ -1053,10 +1085,22 @@ static int tcpm_pd_send_source_caps(struct tcpm_port *port) static int tcpm_pd_send_sink_caps(struct tcpm_port *port) { struct pd_message msg; - int i; + u32 pdo; + unsigned int i, nr_pdo = 0; memset(&msg, 0, sizeof(msg)); - if (!port->nr_snk_pdo) { + + for (i = 0; i < port->nr_snk_pdo; i++) { + if (port->negotiated_rev >= PD_REV30) { + msg.payload[nr_pdo++] = cpu_to_le32(port->snk_pdo[i]); + } else { + pdo = tcpm_forge_legacy_pdo(port, port->snk_pdo[i], TYPEC_SINK); + if (pdo) + msg.payload[nr_pdo++] = cpu_to_le32(pdo); + } + } + + if (!nr_pdo) { /* No sink capabilities defined, source only */ msg.header = PD_HEADER_LE(PD_CTRL_REJECT, port->pwr_role, @@ -1069,10 +1113,8 @@ static int tcpm_pd_send_sink_caps(struct tcpm_port *port) port->data_role, port->negotiated_rev, port->message_id, - port->nr_snk_pdo); + nr_pdo); } - for (i = 0; i < port->nr_snk_pdo; i++) - msg.payload[i] = cpu_to_le32(port->snk_pdo[i]); return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); } diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h index 272454f9cd67..70d681918d01 100644 --- a/include/linux/usb/pd.h +++ b/include/linux/usb/pd.h @@ -225,6 +225,7 @@ enum pd_pdo_type { #define PDO_FIXED_EXTPOWER BIT(27) /* Externally powered */ #define PDO_FIXED_USB_COMM BIT(26) /* USB communications capable */ #define PDO_FIXED_DATA_SWAP BIT(25) /* Data role swap supported */ +#define PDO_FIXED_UNCHUNK_EXT BIT(24) /* Unchunked Extended Message supported (Source) */ #define PDO_FIXED_FRS_CURR_MASK (BIT(24) | BIT(23)) /* FR_Swap Current (Sink) */ #define PDO_FIXED_FRS_CURR_SHIFT 23 #define PDO_FIXED_VOLT_SHIFT 10 /* 50mV units */ -- cgit v1.2.3 From a63b53e19bdffd9338fab4536e8bc422ea812b4d Mon Sep 17 00:00:00 2001 From: Junlin Yang Date: Sun, 24 Jan 2021 22:38:53 +0800 Subject: usb: typec: tcpci_maxim: remove redundant assignment PTR_ERR(chip->tcpci) has been used as a return value, it is not necessary to assign it to ret, so remove it. Reviewed-by: Guenter Roeck Reviewed-by: Heikki Krogerus Signed-off-by: Junlin Yang Link: https://lore.kernel.org/r/20210124143853.1630-1-angkery@163.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci_maxim.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.c b/drivers/usb/typec/tcpm/tcpci_maxim.c index 0e2bb1235ab5..63d8eff336e4 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim.c +++ b/drivers/usb/typec/tcpm/tcpci_maxim.c @@ -458,7 +458,6 @@ static int max_tcpci_probe(struct i2c_client *client, const struct i2c_device_id chip->tcpci = tcpci_register_port(chip->dev, &chip->data); if (IS_ERR(chip->tcpci)) { dev_err(&client->dev, "TCPCI port registration failed"); - ret = PTR_ERR(chip->tcpci); return PTR_ERR(chip->tcpci); } chip->port = tcpci_get_tcpm_port(chip->tcpci); -- cgit v1.2.3 From 7cbcd008e104d16849e5054e69f0a3d55eaeb664 Mon Sep 17 00:00:00 2001 From: Junlin Yang Date: Sun, 24 Jan 2021 22:39:47 +0800 Subject: usb: typec: tcpci_maxim: add terminating newlines to logging Add terminating '\n' to the formats where missed. Reviewed-by: Guenter Roeck Signed-off-by: Junlin Yang Link: https://lore.kernel.org/r/20210124143947.1688-1-angkery@163.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci_maxim.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.c b/drivers/usb/typec/tcpm/tcpci_maxim.c index 63d8eff336e4..f1674a611033 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim.c +++ b/drivers/usb/typec/tcpm/tcpci_maxim.c @@ -155,7 +155,7 @@ static void process_rx(struct max_tcpci_chip *chip, u16 status) */ ret = regmap_raw_read(chip->data.regmap, TCPC_RX_BYTE_CNT, rx_buf, 2); if (ret < 0) { - dev_err(chip->dev, "TCPC_RX_BYTE_CNT read failed ret:%d", ret); + dev_err(chip->dev, "TCPC_RX_BYTE_CNT read failed ret:%d\n", ret); return; } @@ -164,13 +164,13 @@ static void process_rx(struct max_tcpci_chip *chip, u16 status) if (count == 0 || frame_type != TCPC_RX_BUF_FRAME_TYPE_SOP) { max_tcpci_write16(chip, TCPC_ALERT, TCPC_ALERT_RX_STATUS); - dev_err(chip->dev, "%s", count == 0 ? "error: count is 0" : + dev_err(chip->dev, "%s\n", count == 0 ? "error: count is 0" : "error frame_type is not SOP"); return; } if (count > sizeof(struct pd_message) || count + 1 > TCPC_RECEIVE_BUFFER_LEN) { - dev_err(chip->dev, "Invalid TCPC_RX_BYTE_CNT %d", count); + dev_err(chip->dev, "Invalid TCPC_RX_BYTE_CNT %d\n", count); return; } @@ -181,7 +181,7 @@ static void process_rx(struct max_tcpci_chip *chip, u16 status) count += 1; ret = regmap_raw_read(chip->data.regmap, TCPC_RX_BYTE_CNT, rx_buf, count); if (ret < 0) { - dev_err(chip->dev, "Error: TCPC_RX_BYTE_CNT read failed: %d", ret); + dev_err(chip->dev, "Error: TCPC_RX_BYTE_CNT read failed: %d\n", ret); return; } @@ -314,7 +314,7 @@ static irqreturn_t _max_tcpci_irq(struct max_tcpci_chip *chip, u16 status) return ret; if (reg_status & TCPC_SINK_FAST_ROLE_SWAP) { - dev_info(chip->dev, "FRS Signal"); + dev_info(chip->dev, "FRS Signal\n"); tcpm_sink_frs(chip->port); } } @@ -457,7 +457,7 @@ static int max_tcpci_probe(struct i2c_client *client, const struct i2c_device_id max_tcpci_init_regs(chip); chip->tcpci = tcpci_register_port(chip->dev, &chip->data); if (IS_ERR(chip->tcpci)) { - dev_err(&client->dev, "TCPCI port registration failed"); + dev_err(&client->dev, "TCPCI port registration failed\n"); return PTR_ERR(chip->tcpci); } chip->port = tcpci_get_tcpm_port(chip->tcpci); -- cgit v1.2.3 From a70aa7dc60099bbdcbd6faca42a915d80f31161e Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 26 Jan 2021 13:26:54 +0300 Subject: USB: serial: mos7840: fix error code in mos7840_write() This should return -ENOMEM instead of 0 if the kmalloc() fails. Fixes: 3f5429746d91 ("USB: Moschip 7840 USB-Serial Driver") Signed-off-by: Dan Carpenter Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/mos7840.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/serial/mos7840.c b/drivers/usb/serial/mos7840.c index 23f91d658cb4..30c25ef0dacd 100644 --- a/drivers/usb/serial/mos7840.c +++ b/drivers/usb/serial/mos7840.c @@ -883,8 +883,10 @@ static int mos7840_write(struct tty_struct *tty, struct usb_serial_port *port, if (urb->transfer_buffer == NULL) { urb->transfer_buffer = kmalloc(URB_TRANSFER_BUFFER_SIZE, GFP_ATOMIC); - if (!urb->transfer_buffer) + if (!urb->transfer_buffer) { + bytes_sent = -ENOMEM; goto exit; + } } transfer_size = min(count, URB_TRANSFER_BUFFER_SIZE); -- cgit v1.2.3 From a38d21488097f9823ebd297d56b24f431ee7acaa Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 21 Jan 2021 11:29:13 +0100 Subject: USB: serial: xr: fix NULL-deref at probe Make sure that the probed device has an interface 0 to avoid dereferencing a NULL pointer in case of a malicious device or during USB-descriptor fuzzing. Fixes: c2d405aa86b4 ("USB: serial: add MaxLinear/Exar USB to Serial driver") Signed-off-by: Johan Hovold --- drivers/usb/serial/xr_serial.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/serial/xr_serial.c b/drivers/usb/serial/xr_serial.c index bdb2df27b50b..7be6da6a5cf3 100644 --- a/drivers/usb/serial/xr_serial.c +++ b/drivers/usb/serial/xr_serial.c @@ -552,6 +552,9 @@ static int xr_probe(struct usb_serial *serial, const struct usb_device_id *id) /* But claim the control interface during data interface probe */ control_interface = usb_ifnum_to_if(usb_dev, 0); + if (!control_interface) + return -ENODEV; + ret = usb_driver_claim_interface(driver, control_interface, NULL); if (ret) { dev_err(&serial->interface->dev, "Failed to claim control interface\n"); -- cgit v1.2.3 From 54c98d9d7ba48c66d64f72e3d5a7586601705611 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 21 Jan 2021 11:29:14 +0100 Subject: USB: serial: xr: fix interface leak at disconnect Make sure to release the control interface at disconnect so that the driver can be unbound without leaking resources (and later rebound). Fixes: c2d405aa86b4 ("USB: serial: add MaxLinear/Exar USB to Serial driver") Signed-off-by: Johan Hovold --- drivers/usb/serial/xr_serial.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/usb/serial/xr_serial.c b/drivers/usb/serial/xr_serial.c index 7be6da6a5cf3..5e110b0c8e71 100644 --- a/drivers/usb/serial/xr_serial.c +++ b/drivers/usb/serial/xr_serial.c @@ -564,6 +564,15 @@ static int xr_probe(struct usb_serial *serial, const struct usb_device_id *id) return 0; } +static void xr_disconnect(struct usb_serial *serial) +{ + struct usb_driver *driver = serial->type->usb_driver; + struct usb_interface *control_interface; + + control_interface = usb_ifnum_to_if(serial->dev, 0); + usb_driver_release_interface(driver, control_interface); +} + static const struct usb_device_id id_table[] = { { USB_DEVICE(0x04e2, 0x1410) }, /* XR21V141X */ { } @@ -578,6 +587,7 @@ static struct usb_serial_driver xr_device = { .id_table = id_table, .num_ports = 1, .probe = xr_probe, + .disconnect = xr_disconnect, .open = xr_open, .close = xr_close, .break_ctl = xr_break_ctl, -- cgit v1.2.3 From 9ffa6ec51ce8595ffaa5a634bd5618e129d038c0 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 21 Jan 2021 11:29:15 +0100 Subject: USB: serial: xr: use subsystem usb_device at probe Use the subsystem struct usb_device pointer at probe instead of deriving it from the interface pointer. Signed-off-by: Johan Hovold --- drivers/usb/serial/xr_serial.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/serial/xr_serial.c b/drivers/usb/serial/xr_serial.c index 5e110b0c8e71..8f81f866d681 100644 --- a/drivers/usb/serial/xr_serial.c +++ b/drivers/usb/serial/xr_serial.c @@ -541,7 +541,6 @@ static void xr_close(struct usb_serial_port *port) static int xr_probe(struct usb_serial *serial, const struct usb_device_id *id) { - struct usb_device *usb_dev = interface_to_usbdev(serial->interface); struct usb_driver *driver = serial->type->usb_driver; struct usb_interface *control_interface; int ret; @@ -551,7 +550,7 @@ static int xr_probe(struct usb_serial *serial, const struct usb_device_id *id) return -ENODEV; /* But claim the control interface during data interface probe */ - control_interface = usb_ifnum_to_if(usb_dev, 0); + control_interface = usb_ifnum_to_if(serial->dev, 0); if (!control_interface) return -ENODEV; -- cgit v1.2.3 From 5c5d9af683f61bf0984f06db0d1357e94b5e2655 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 21 Jan 2021 11:29:16 +0100 Subject: USB: serial: xr: use termios flag helpers Use the termios flag helpers consistently, including for CRTSCTS. Signed-off-by: Johan Hovold --- drivers/usb/serial/xr_serial.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/serial/xr_serial.c b/drivers/usb/serial/xr_serial.c index 8f81f866d681..52909dccb4dc 100644 --- a/drivers/usb/serial/xr_serial.c +++ b/drivers/usb/serial/xr_serial.c @@ -401,7 +401,6 @@ static int xr_set_baudrate(struct tty_struct *tty, static void xr_set_flow_mode(struct tty_struct *tty, struct usb_serial_port *port) { - unsigned int cflag = tty->termios.c_cflag; u8 flow, gpio_mode; int ret; @@ -409,7 +408,7 @@ static void xr_set_flow_mode(struct tty_struct *tty, if (ret) return; - if (cflag & CRTSCTS) { + if (C_CRTSCTS(tty)) { dev_dbg(&port->dev, "Enabling hardware flow ctrl\n"); /* -- cgit v1.2.3 From 72fc7fc7f36501103a5ffa0d29c9672b2314ce29 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 21 Jan 2021 11:29:17 +0100 Subject: USB: serial: xr: document vendor-request recipient Add the missing device-recipient define to the vendor control requests for completeness. Signed-off-by: Johan Hovold --- drivers/usb/serial/xr_serial.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/serial/xr_serial.c b/drivers/usb/serial/xr_serial.c index 52909dccb4dc..202263211ba9 100644 --- a/drivers/usb/serial/xr_serial.c +++ b/drivers/usb/serial/xr_serial.c @@ -116,8 +116,8 @@ static int xr_set_reg(struct usb_serial_port *port, u8 block, u8 reg, u8 val) ret = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), XR21V141X_SET_REQ, - USB_DIR_OUT | USB_TYPE_VENDOR, val, - reg | (block << 8), NULL, 0, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + val, reg | (block << 8), NULL, 0, USB_CTRL_SET_TIMEOUT); if (ret < 0) { dev_err(&port->dev, "Failed to set reg 0x%02x: %d\n", reg, ret); @@ -140,8 +140,8 @@ static int xr_get_reg(struct usb_serial_port *port, u8 block, u8 reg, u8 *val) ret = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), XR21V141X_GET_REQ, - USB_DIR_IN | USB_TYPE_VENDOR, 0, - reg | (block << 8), dmabuf, 1, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, reg | (block << 8), dmabuf, 1, USB_CTRL_GET_TIMEOUT); if (ret == 1) { *val = *dmabuf; -- cgit v1.2.3 From 35567511595052a2c6fc020d4abab944fb4204b0 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 21 Jan 2021 11:29:18 +0100 Subject: USB: serial: xr: clean up line-settings handling Shift the line-setting values when defining them rather than in set_termios() for consistency and improved readability. Signed-off-by: Johan Hovold --- drivers/usb/serial/xr_serial.c | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/drivers/usb/serial/xr_serial.c b/drivers/usb/serial/xr_serial.c index 202263211ba9..2000277bacc1 100644 --- a/drivers/usb/serial/xr_serial.c +++ b/drivers/usb/serial/xr_serial.c @@ -71,17 +71,17 @@ struct xr_txrx_clk_mask { #define XR21V141X_UART_DATA_8 0x8 #define XR21V141X_UART_PARITY_MASK GENMASK(6, 4) -#define XR21V141X_UART_PARITY_SHIFT 0x4 -#define XR21V141X_UART_PARITY_NONE 0x0 -#define XR21V141X_UART_PARITY_ODD 0x1 -#define XR21V141X_UART_PARITY_EVEN 0x2 -#define XR21V141X_UART_PARITY_MARK 0x3 -#define XR21V141X_UART_PARITY_SPACE 0x4 +#define XR21V141X_UART_PARITY_SHIFT 4 +#define XR21V141X_UART_PARITY_NONE (0x0 << XR21V141X_UART_PARITY_SHIFT) +#define XR21V141X_UART_PARITY_ODD (0x1 << XR21V141X_UART_PARITY_SHIFT) +#define XR21V141X_UART_PARITY_EVEN (0x2 << XR21V141X_UART_PARITY_SHIFT) +#define XR21V141X_UART_PARITY_MARK (0x3 << XR21V141X_UART_PARITY_SHIFT) +#define XR21V141X_UART_PARITY_SPACE (0x4 << XR21V141X_UART_PARITY_SHIFT) #define XR21V141X_UART_STOP_MASK BIT(7) -#define XR21V141X_UART_STOP_SHIFT 0x7 -#define XR21V141X_UART_STOP_1 0x0 -#define XR21V141X_UART_STOP_2 0x1 +#define XR21V141X_UART_STOP_SHIFT 7 +#define XR21V141X_UART_STOP_1 (0x0 << XR21V141X_UART_STOP_SHIFT) +#define XR21V141X_UART_STOP_2 (0x1 << XR21V141X_UART_STOP_SHIFT) #define XR21V141X_UART_FLOW_MODE_NONE 0x0 #define XR21V141X_UART_FLOW_MODE_HW 0x1 @@ -477,25 +477,21 @@ static void xr_set_termios(struct tty_struct *tty, if (C_PARENB(tty)) { if (C_CMSPAR(tty)) { if (C_PARODD(tty)) - bits |= XR21V141X_UART_PARITY_MARK << - XR21V141X_UART_PARITY_SHIFT; + bits |= XR21V141X_UART_PARITY_MARK; else - bits |= XR21V141X_UART_PARITY_SPACE << - XR21V141X_UART_PARITY_SHIFT; + bits |= XR21V141X_UART_PARITY_SPACE; } else { if (C_PARODD(tty)) - bits |= XR21V141X_UART_PARITY_ODD << - XR21V141X_UART_PARITY_SHIFT; + bits |= XR21V141X_UART_PARITY_ODD; else - bits |= XR21V141X_UART_PARITY_EVEN << - XR21V141X_UART_PARITY_SHIFT; + bits |= XR21V141X_UART_PARITY_EVEN; } } if (C_CSTOPB(tty)) - bits |= XR21V141X_UART_STOP_2 << XR21V141X_UART_STOP_SHIFT; + bits |= XR21V141X_UART_STOP_2; else - bits |= XR21V141X_UART_STOP_1 << XR21V141X_UART_STOP_SHIFT; + bits |= XR21V141X_UART_STOP_1; ret = xr_set_reg_uart(port, XR21V141X_REG_FORMAT, bits); if (ret) -- cgit v1.2.3 From 736c09316c905622c548582a47eaa2c8c542b520 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 21 Jan 2021 11:29:19 +0100 Subject: USB: serial: xr: simplify line-speed logic Simplify the changed-line-speed conditional expression. Signed-off-by: Johan Hovold --- drivers/usb/serial/xr_serial.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/serial/xr_serial.c b/drivers/usb/serial/xr_serial.c index 2000277bacc1..fc727f4283f2 100644 --- a/drivers/usb/serial/xr_serial.c +++ b/drivers/usb/serial/xr_serial.c @@ -451,8 +451,7 @@ static void xr_set_termios(struct tty_struct *tty, u8 bits = 0; int ret; - if ((old_termios && tty->termios.c_ospeed != old_termios->c_ospeed) || - !old_termios) + if (!old_termios || (tty->termios.c_ospeed != old_termios->c_ospeed)) xr_set_baudrate(tty, port); switch (C_CSIZE(tty)) { -- cgit v1.2.3 From 465d3b3a0d311680d0e42258fd25454433667e9d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 21 Jan 2021 11:29:20 +0100 Subject: USB: serial: xr: fix gpio-mode handling Fix the gpio-mode handling so that all the pins are under driver control (i.e. in gpio mode) when hardware flow control is disabled. This is specifically needed to be able to control RTS. Fixes: c2d405aa86b4 ("USB: serial: add MaxLinear/Exar USB to Serial driver") Signed-off-by: Johan Hovold --- drivers/usb/serial/xr_serial.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/usb/serial/xr_serial.c b/drivers/usb/serial/xr_serial.c index fc727f4283f2..183731cd2ef7 100644 --- a/drivers/usb/serial/xr_serial.c +++ b/drivers/usb/serial/xr_serial.c @@ -408,14 +408,11 @@ static void xr_set_flow_mode(struct tty_struct *tty, if (ret) return; + /* Set GPIO mode for controlling the pins manually by default. */ + gpio_mode &= ~XR21V141X_UART_MODE_GPIO_MASK; + if (C_CRTSCTS(tty)) { dev_dbg(&port->dev, "Enabling hardware flow ctrl\n"); - - /* - * RTS/CTS is the default flow control mode, so set GPIO mode - * for controlling the pins manually by default. - */ - gpio_mode &= ~XR21V141X_UART_MODE_GPIO_MASK; gpio_mode |= XR21V141X_UART_MODE_RTS_CTS; flow = XR21V141X_UART_FLOW_MODE_HW; } else if (I_IXON(tty)) { -- cgit v1.2.3 From 0d05d7d913892cd093acc5a0ac884ebab9fda67c Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 21 Jan 2021 11:29:21 +0100 Subject: USB: serial: xr: fix pin configuration Make sure that the modem pins are set up correctly when opening the port to avoid leaving, for example, DTR and RTS configured as inputs, which is the device default. This is specifically needed to be able to control DTR and RTS when hardware flow control is disabled. Fixes: c2d405aa86b4 ("USB: serial: add MaxLinear/Exar USB to Serial driver") Signed-off-by: Johan Hovold --- drivers/usb/serial/xr_serial.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/usb/serial/xr_serial.c b/drivers/usb/serial/xr_serial.c index 183731cd2ef7..f67e7dba9509 100644 --- a/drivers/usb/serial/xr_serial.c +++ b/drivers/usb/serial/xr_serial.c @@ -502,6 +502,7 @@ static void xr_set_termios(struct tty_struct *tty, static int xr_open(struct tty_struct *tty, struct usb_serial_port *port) { + u8 gpio_dir; int ret; ret = xr_uart_enable(port); @@ -510,6 +511,13 @@ static int xr_open(struct tty_struct *tty, struct usb_serial_port *port) return ret; } + /* + * Configure DTR and RTS as outputs and RI, CD, DSR and CTS as + * inputs. + */ + gpio_dir = XR21V141X_UART_MODE_DTR | XR21V141X_UART_MODE_RTS; + xr_set_reg_uart(port, XR21V141X_REG_GPIO_DIR, gpio_dir); + /* Setup termios */ if (tty) xr_set_termios(tty, port, NULL); -- cgit v1.2.3 From 55317e22391ffc5aa297c3a617c8c3302fb184b6 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 21 Jan 2021 11:29:22 +0100 Subject: USB: serial: xr: fix B0 handling Fix up B0 handling which should leave the baud rate unchanged and specifically not report back a non-B0 rate when B0 is requested; must temporarily disable hardware flow control so that RTS can be deasserted; and should reassert DTR/RTS when moving from B0. Fixes: c2d405aa86b4 ("USB: serial: add MaxLinear/Exar USB to Serial driver") Signed-off-by: Johan Hovold --- drivers/usb/serial/xr_serial.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/drivers/usb/serial/xr_serial.c b/drivers/usb/serial/xr_serial.c index f67e7dba9509..483d07dee19d 100644 --- a/drivers/usb/serial/xr_serial.c +++ b/drivers/usb/serial/xr_serial.c @@ -341,8 +341,11 @@ static int xr_set_baudrate(struct tty_struct *tty, u16 tx_mask, rx_mask; int ret; - baud = clamp(tty->termios.c_ospeed, XR21V141X_MIN_SPEED, - XR21V141X_MAX_SPEED); + baud = tty->termios.c_ospeed; + if (!baud) + return 0; + + baud = clamp(baud, XR21V141X_MIN_SPEED, XR21V141X_MAX_SPEED); divisor = XR_INT_OSC_HZ / baud; idx = ((32 * XR_INT_OSC_HZ) / baud) & 0x1f; tx_mask = xr21v141x_txrx_clk_masks[idx].tx; @@ -399,7 +402,8 @@ static int xr_set_baudrate(struct tty_struct *tty, } static void xr_set_flow_mode(struct tty_struct *tty, - struct usb_serial_port *port) + struct usb_serial_port *port, + struct ktermios *old_termios) { u8 flow, gpio_mode; int ret; @@ -411,7 +415,7 @@ static void xr_set_flow_mode(struct tty_struct *tty, /* Set GPIO mode for controlling the pins manually by default. */ gpio_mode &= ~XR21V141X_UART_MODE_GPIO_MASK; - if (C_CRTSCTS(tty)) { + if (C_CRTSCTS(tty) && C_BAUD(tty) != B0) { dev_dbg(&port->dev, "Enabling hardware flow ctrl\n"); gpio_mode |= XR21V141X_UART_MODE_RTS_CTS; flow = XR21V141X_UART_FLOW_MODE_HW; @@ -438,6 +442,11 @@ static void xr_set_flow_mode(struct tty_struct *tty, xr_uart_enable(port); xr_set_reg_uart(port, XR21V141X_REG_GPIO_MODE, gpio_mode); + + if (C_BAUD(tty) == B0) + xr_dtr_rts(port, 0); + else if (old_termios && (old_termios->c_cflag & CBAUD) == B0) + xr_dtr_rts(port, 1); } static void xr_set_termios(struct tty_struct *tty, @@ -493,11 +502,7 @@ static void xr_set_termios(struct tty_struct *tty, if (ret) return; - /* If baud rate is B0, clear DTR and RTS */ - if (C_BAUD(tty) == B0) - xr_dtr_rts(port, 0); - - xr_set_flow_mode(tty, port); + xr_set_flow_mode(tty, port, old_termios); } static int xr_open(struct tty_struct *tty, struct usb_serial_port *port) -- cgit v1.2.3 From 60b4c9d5c6feaf6020a424b05ca7e0c6bebe7b76 Mon Sep 17 00:00:00 2001 From: Davidlohr Bueso Date: Tue, 12 Jan 2021 19:15:37 -0800 Subject: usb/c67x00: Replace tasklet with work Tasklets have long been deprecated as being too heavy on the system by running in irq context - and this is not a performance critical path. If a higher priority process wants to run, it must wait for the tasklet to finish before doing so. c67x00_do_work() will now run in process context and have further concurrency (tasklets being serialized among themselves), but this is done holding the c67x00->lock, so it should be fine. Furthermore, this patch fixes the usage of the lock in the callback as otherwise it would need to be irq-safe. Signed-off-by: Davidlohr Bueso Link: https://lore.kernel.org/r/20210113031537.79859-1-dave@stgolabs.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/c67x00/c67x00-hcd.h | 2 +- drivers/usb/c67x00/c67x00-sched.c | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/usb/c67x00/c67x00-hcd.h b/drivers/usb/c67x00/c67x00-hcd.h index 6b6b04a3fe0f..6332a6b5dce6 100644 --- a/drivers/usb/c67x00/c67x00-hcd.h +++ b/drivers/usb/c67x00/c67x00-hcd.h @@ -76,7 +76,7 @@ struct c67x00_hcd { u16 next_td_addr; u16 next_buf_addr; - struct tasklet_struct tasklet; + struct work_struct work; struct completion endpoint_disable; diff --git a/drivers/usb/c67x00/c67x00-sched.c b/drivers/usb/c67x00/c67x00-sched.c index e65f1a0ae80b..c7d3e907be81 100644 --- a/drivers/usb/c67x00/c67x00-sched.c +++ b/drivers/usb/c67x00/c67x00-sched.c @@ -1123,24 +1123,26 @@ static void c67x00_do_work(struct c67x00_hcd *c67x00) /* -------------------------------------------------------------------------- */ -static void c67x00_sched_tasklet(struct tasklet_struct *t) +static void c67x00_sched_work(struct work_struct *work) { - struct c67x00_hcd *c67x00 = from_tasklet(c67x00, t, tasklet); + struct c67x00_hcd *c67x00; + + c67x00 = container_of(work, struct c67x00_hcd, work); c67x00_do_work(c67x00); } void c67x00_sched_kick(struct c67x00_hcd *c67x00) { - tasklet_hi_schedule(&c67x00->tasklet); + queue_work(system_highpri_wq, &c67x00->work); } int c67x00_sched_start_scheduler(struct c67x00_hcd *c67x00) { - tasklet_setup(&c67x00->tasklet, c67x00_sched_tasklet); + INIT_WORK(&c67x00->work, c67x00_sched_work); return 0; } void c67x00_sched_stop_scheduler(struct c67x00_hcd *c67x00) { - tasklet_kill(&c67x00->tasklet); + cancel_work_sync(&c67x00->work); } -- cgit v1.2.3 From 4c1934bda88aa85bb1191e96dbd3ac2313732ada Mon Sep 17 00:00:00 2001 From: Andrey Konovalov Date: Sat, 23 Jan 2021 20:14:16 +0100 Subject: usb: raw-gadget: add copyright Add copyright to drivers/usb/gadget/legacy/raw_gadget.c. Signed-off-by: Andrey Konovalov Link: https://lore.kernel.org/r/c8937266c4a5da073ac81cd471b18d869c984dfe.1611429174.git.andreyknvl@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/legacy/raw_gadget.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/gadget/legacy/raw_gadget.c b/drivers/usb/gadget/legacy/raw_gadget.c index 062dfac30399..c5a2c734234a 100644 --- a/drivers/usb/gadget/legacy/raw_gadget.c +++ b/drivers/usb/gadget/legacy/raw_gadget.c @@ -3,7 +3,8 @@ * USB Raw Gadget driver. * See Documentation/usb/raw-gadget.rst for more details. * - * Andrey Konovalov + * Copyright (c) 2020 Google, Inc. + * Author: Andrey Konovalov */ #include -- cgit v1.2.3 From 7a35a5ca26376f0c0e7ac44c5f1324d5d980b2ef Mon Sep 17 00:00:00 2001 From: Andrey Konovalov Date: Sat, 23 Jan 2021 20:14:17 +0100 Subject: usb: raw-gadget: update documentation and Kconfig Update Raw Gadget documentation and Kconfig. Make the description more precise and clear, fix typos and grammar mistakes, and do other cleanups. Signed-off-by: Andrey Konovalov Link: https://lore.kernel.org/r/f4c650c94ae2b910e38819d51109cd5f0b251a2a.1611429174.git.andreyknvl@google.com Signed-off-by: Greg Kroah-Hartman --- Documentation/usb/raw-gadget.rst | 102 +++++++++++++++++++++----------------- drivers/usb/gadget/legacy/Kconfig | 13 +++-- 2 files changed, 65 insertions(+), 50 deletions(-) diff --git a/Documentation/usb/raw-gadget.rst b/Documentation/usb/raw-gadget.rst index 68d879a8009e..818a1648b387 100644 --- a/Documentation/usb/raw-gadget.rst +++ b/Documentation/usb/raw-gadget.rst @@ -2,83 +2,93 @@ USB Raw Gadget ============== -USB Raw Gadget is a kernel module that provides a userspace interface for -the USB Gadget subsystem. Essentially it allows to emulate USB devices -from userspace. Enabled with CONFIG_USB_RAW_GADGET. Raw Gadget is -currently a strictly debugging feature and shouldn't be used in -production, use GadgetFS instead. +USB Raw Gadget is a gadget driver that gives userspace low-level control over +the gadget's communication process. + +Like any other gadget driver, Raw Gadget implements USB devices via the +USB gadget API. Unlike most gadget drivers, Raw Gadget does not implement +any concrete USB functions itself but requires userspace to do that. + +Raw Gadget is currently a strictly debugging feature and should not be used +in production. Use GadgetFS instead. + +Enabled with CONFIG_USB_RAW_GADGET. Comparison to GadgetFS ~~~~~~~~~~~~~~~~~~~~~~ -Raw Gadget is similar to GadgetFS, but provides a more low-level and -direct access to the USB Gadget layer for the userspace. The key -differences are: +Raw Gadget is similar to GadgetFS but provides more direct access to the +USB gadget layer for userspace. The key differences are: -1. Every USB request is passed to the userspace to get a response, while +1. Raw Gadget passes every USB request to userspace to get a response, while GadgetFS responds to some USB requests internally based on the provided - descriptors. However note, that the UDC driver might respond to some - requests on its own and never forward them to the Gadget layer. + descriptors. Note that the UDC driver might respond to some requests on + its own and never forward them to the gadget layer. -2. GadgetFS performs some sanity checks on the provided USB descriptors, - while Raw Gadget allows you to provide arbitrary data as responses to - USB requests. +2. Raw Gadget allows providing arbitrary data as responses to USB requests, + while GadgetFS performs sanity checks on the provided USB descriptors. + This makes Raw Gadget suitable for fuzzing by providing malformed data as + responses to USB requests. 3. Raw Gadget provides a way to select a UDC device/driver to bind to, - while GadgetFS currently binds to the first available UDC. + while GadgetFS currently binds to the first available UDC. This allows + having multiple Raw Gadget instances bound to different UDCs. 4. Raw Gadget explicitly exposes information about endpoints addresses and - capabilities allowing a user to write UDC-agnostic gadgets. + capabilities. This allows the user to write UDC-agnostic gadgets. -5. Raw Gadget has ioctl-based interface instead of a filesystem-based one. +5. Raw Gadget has an ioctl-based interface instead of a filesystem-based + one. Userspace interface ~~~~~~~~~~~~~~~~~~~ -To create a Raw Gadget instance open /dev/raw-gadget. Multiple raw-gadget -instances (bound to different UDCs) can be used at the same time. The -interaction with the opened file happens through the ioctl() calls, see -comments in include/uapi/linux/usb/raw_gadget.h for details. +The user can interact with Raw Gadget by opening ``/dev/raw-gadget`` and +issuing ioctl calls; see the comments in include/uapi/linux/usb/raw_gadget.h +for details. Multiple Raw Gadget instances (bound to different UDCs) can be +used at the same time. -The typical usage of Raw Gadget looks like: +A typical usage scenario of Raw Gadget: -1. Open Raw Gadget instance via /dev/raw-gadget. -2. Initialize the instance via USB_RAW_IOCTL_INIT. -3. Launch the instance with USB_RAW_IOCTL_RUN. -4. In a loop issue USB_RAW_IOCTL_EVENT_FETCH calls to receive events from - Raw Gadget and react to those depending on what kind of USB device - needs to be emulated. +1. Create a Raw Gadget instance by opening ``/dev/raw-gadget``. +2. Initialize the instance via ``USB_RAW_IOCTL_INIT``. +3. Launch the instance with ``USB_RAW_IOCTL_RUN``. +4. In a loop issue ``USB_RAW_IOCTL_EVENT_FETCH`` to receive events from + Raw Gadget and react to those depending on what kind of USB gadget must + be implemented. -Note, that some UDC drivers have fixed addresses assigned to endpoints, and -therefore arbitrary endpoint addresses can't be used in the descriptors. -Nevertheles, Raw Gadget provides a UDC-agnostic way to write USB gadgets. -Once a USB_RAW_EVENT_CONNECT event is received via USB_RAW_IOCTL_EVENT_FETCH, -the USB_RAW_IOCTL_EPS_INFO ioctl can be used to find out information about -endpoints that the UDC driver has. Based on that information, the user must -chose UDC endpoints that will be used for the gadget being emulated, and -properly assign addresses in endpoint descriptors. +Note that some UDC drivers have fixed addresses assigned to endpoints, and +therefore arbitrary endpoint addresses cannot be used in the descriptors. +Nevertheless, Raw Gadget provides a UDC-agnostic way to write USB gadgets. +Once ``USB_RAW_EVENT_CONNECT`` is received via ``USB_RAW_IOCTL_EVENT_FETCH``, +``USB_RAW_IOCTL_EPS_INFO`` can be used to find out information about the +endpoints that the UDC driver has. Based on that, userspace must choose UDC +endpoints for the gadget and assign addresses in the endpoint descriptors +correspondingly. -You can find usage examples (along with a test suite) here: +Raw Gadget usage examples and a test suite: https://github.com/xairy/raw-gadget Internal details ~~~~~~~~~~~~~~~~ -Currently every endpoint read/write ioctl submits a USB request and waits until -its completion. This is the desired mode for coverage-guided fuzzing (as we'd -like all USB request processing happen during the lifetime of a syscall), -and must be kept in the implementation. (This might be slow for real world -applications, thus the O_NONBLOCK improvement suggestion below.) +Every Raw Gadget endpoint read/write ioctl submits a USB request and waits +until its completion. This is done deliberately to assist with coverage-guided +fuzzing by having a single syscall fully process a single USB request. This +feature must be kept in the implementation. Potential future improvements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- Report more events (suspend, resume, etc.) through USB_RAW_IOCTL_EVENT_FETCH. +- Report more events (suspend, resume, etc.) through + ``USB_RAW_IOCTL_EVENT_FETCH``. -- Support O_NONBLOCK I/O. +- Support ``O_NONBLOCK`` I/O. This would be another mode of operation, where + Raw Gadget would not wait until the completion of each USB request. - Support USB 3 features (accept SS endpoint companion descriptor when - enabling endpoints; allow providing stream_id for bulk transfers). + enabling endpoints; allow providing ``stream_id`` for bulk transfers). -- Support ISO transfer features (expose frame_number for completed requests). +- Support ISO transfer features (expose ``frame_number`` for completed + requests). diff --git a/drivers/usb/gadget/legacy/Kconfig b/drivers/usb/gadget/legacy/Kconfig index f02c38b32a2b..11dd6e8adc8a 100644 --- a/drivers/usb/gadget/legacy/Kconfig +++ b/drivers/usb/gadget/legacy/Kconfig @@ -515,10 +515,15 @@ config USB_G_WEBCAM config USB_RAW_GADGET tristate "USB Raw Gadget" help - USB Raw Gadget is a kernel module that provides a userspace interface - for the USB Gadget subsystem. Essentially it allows to emulate USB - devices from userspace. See Documentation/usb/raw-gadget.rst for - details. + USB Raw Gadget is a gadget driver that gives userspace low-level + control over the gadget's communication process. + + Like any other gadget driver, Raw Gadget implements USB devices via + the USB gadget API. Unlike most gadget drivers, Raw Gadget does not + implement any concrete USB functions itself but requires userspace + to do that. + + See Documentation/usb/raw-gadget.rst for details. Say "y" to link the driver statically, or "m" to build a dynamically linked module called "raw_gadget". -- cgit v1.2.3 From 7961b77c0d489764166d789adb2c1d21b71b5aad Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Thu, 21 Jan 2021 13:42:49 +0100 Subject: dt-bindings: dwc3-xilinx: Add missing comma in example Trivial example fix. Signed-off-by: Michal Simek Link: https://lore.kernel.org/r/8fa5edcaa6b93859cfda97d080aad378e89c1b44.1611232967.git.michal.simek@xilinx.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/dwc3-xilinx.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/dwc3-xilinx.txt b/Documentation/devicetree/bindings/usb/dwc3-xilinx.txt index 4aae5b2cef56..a668f43bedf5 100644 --- a/Documentation/devicetree/bindings/usb/dwc3-xilinx.txt +++ b/Documentation/devicetree/bindings/usb/dwc3-xilinx.txt @@ -19,7 +19,7 @@ Example device node: #address-cells = <0x2>; #size-cells = <0x1>; compatible = "xlnx,zynqmp-dwc3"; - clock-names = "bus_clk" "ref_clk"; + clock-names = "bus_clk", "ref_clk"; clocks = <&clk125>, <&clk125>; ranges; -- cgit v1.2.3 From 415fa1c7305dedbb345e2cc8ac91769bc1c83f1a Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 13 Jan 2021 12:20:49 +0100 Subject: usb: dwc2: Do not update data length if it is 0 on inbound transfers The DWC2 documentation states that transfers with zero data length should set the number of packets to 1 and the transfer length to 0. This is not currently the case for inbound transfers: the transfer length is set to the maximum packet length. This can have adverse effects if the chip actually does transfer data as it is programmed to do. Follow chip documentation and keep the transfer length set to 0 in that situation. Fixes: 56f5b1cff22a1 ("staging: Core files for the DWC2 driver") Tested-by: Nicolas Saenz Julienne Reviewed-by: Douglas Anderson Signed-off-by: Guenter Roeck Signed-off-by: Nicolas Saenz Julienne Link: https://lore.kernel.org/r/20210113112052.17063-2-nsaenzjulienne@suse.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/hcd.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index e9ac215b9663..fc3269f5faf1 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -1313,19 +1313,20 @@ static void dwc2_hc_start_transfer(struct dwc2_hsotg *hsotg, if (num_packets > max_hc_pkt_count) { num_packets = max_hc_pkt_count; chan->xfer_len = num_packets * chan->max_packet; + } else if (chan->ep_is_in) { + /* + * Always program an integral # of max packets + * for IN transfers. + * Note: This assumes that the input buffer is + * aligned and sized accordingly. + */ + chan->xfer_len = num_packets * chan->max_packet; } } else { /* Need 1 packet for transfer length of 0 */ num_packets = 1; } - if (chan->ep_is_in) - /* - * Always program an integral # of max packets for IN - * transfers - */ - chan->xfer_len = num_packets * chan->max_packet; - if (chan->ep_type == USB_ENDPOINT_XFER_INT || chan->ep_type == USB_ENDPOINT_XFER_ISOC) /* -- cgit v1.2.3 From f74b68c61cbc4b2245022fcce038509333d63f6f Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 13 Jan 2021 12:20:50 +0100 Subject: usb: dwc2: Abort transaction after errors with unknown reason In some situations, the following error messages are reported. dwc2 ff540000.usb: dwc2_hc_chhltd_intr_dma: Channel 1 - ChHltd set, but reason is unknown dwc2 ff540000.usb: hcint 0x00000002, intsts 0x04000021 This is sometimes followed by: dwc2 ff540000.usb: dwc2_update_urb_state_abn(): trimming xfer length and then: WARNING: CPU: 0 PID: 0 at kernel/v4.19/drivers/usb/dwc2/hcd.c:2913 dwc2_assign_and_init_hc+0x98c/0x990 The warning suggests that an odd buffer address is to be used for DMA. After an error is observed, the receive buffer may be full (urb->actual_length >= urb->length). However, the urb is still left in the queue unless three errors were observed in a row. When it is queued again, the dwc2 hcd code translates this into a 1-block transfer. If urb->actual_length (ie the total expected receive length) is not DMA-aligned, the buffer pointer programmed into the chip will be unaligned. This results in the observed warning. To solve the problem, abort input transactions after an error with unknown cause if the entire packet was already received. This may be a bit drastic, but we don't really know why the transfer was aborted even though the entire packet was received. Aborting the transfer in this situation is less risky than accepting a potentially corrupted packet. With this patch in place, the 'ChHltd set' and 'trimming xfer length' messages are still observed, but there are no more transfer attempts with odd buffer addresses. Fixes: 151d0cbdbe860 ("usb: dwc2: make the scheduler handle excessive NAKs better") Cc: Boris ARZUR Cc: Douglas Anderson Tested-by: Nicolas Saenz Julienne Reviewed-by: Douglas Anderson Signed-off-by: Guenter Roeck Signed-off-by: Nicolas Saenz Julienne Link: https://lore.kernel.org/r/20210113112052.17063-3-nsaenzjulienne@suse.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/hcd_intr.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/usb/dwc2/hcd_intr.c b/drivers/usb/dwc2/hcd_intr.c index a052d39b4375..12819e019e13 100644 --- a/drivers/usb/dwc2/hcd_intr.c +++ b/drivers/usb/dwc2/hcd_intr.c @@ -1977,6 +1977,18 @@ error: qtd->error_count++; dwc2_update_urb_state_abn(hsotg, chan, chnum, qtd->urb, qtd, DWC2_HC_XFER_XACT_ERR); + /* + * We can get here after a completed transaction + * (urb->actual_length >= urb->length) which was not reported + * as completed. If that is the case, and we do not abort + * the transfer, a transfer of size 0 will be enqueued + * subsequently. If urb->actual_length is not DMA-aligned, + * the buffer will then point to an unaligned address, and + * the resulting behavior is undefined. Bail out in that + * situation. + */ + if (qtd->urb->actual_length >= qtd->urb->length) + qtd->error_count = 3; dwc2_hcd_save_data_toggle(hsotg, chan, chnum, qtd); dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_XACT_ERR); } -- cgit v1.2.3 From 1a9e38cabd80356ffb98c2c88fec528ea9644fd5 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 13 Jan 2021 12:20:51 +0100 Subject: usb: dwc2: Make "trimming xfer length" a debug message With some USB network adapters, such as DM96xx, the following message is seen for each maximum size receive packet. dwc2 ff540000.usb: dwc2_update_urb_state(): trimming xfer length This happens because the packet size requested by the driver is 1522 bytes, wMaxPacketSize is 64, the dwc2 driver configures the chip to receive 24*64 = 1536 bytes, and the chip does indeed send more than 1522 bytes of data. Since the event does not indicate an error condition, the message is just noise. Demote it to debug level. Fixes: 7359d482eb4d3 ("staging: HCD files for the DWC2 driver") Tested-by: Nicolas Saenz Julienne Reviewed-by: Douglas Anderson Signed-off-by: Guenter Roeck Signed-off-by: Nicolas Saenz Julienne Link: https://lore.kernel.org/r/20210113112052.17063-4-nsaenzjulienne@suse.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/hcd_intr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/dwc2/hcd_intr.c b/drivers/usb/dwc2/hcd_intr.c index 12819e019e13..d5f4ec1b73b1 100644 --- a/drivers/usb/dwc2/hcd_intr.c +++ b/drivers/usb/dwc2/hcd_intr.c @@ -500,7 +500,7 @@ static int dwc2_update_urb_state(struct dwc2_hsotg *hsotg, &short_read); if (urb->actual_length + xfer_length > urb->length) { - dev_warn(hsotg->dev, "%s(): trimming xfer length\n", __func__); + dev_dbg(hsotg->dev, "%s(): trimming xfer length\n", __func__); xfer_length = urb->length - urb->actual_length; } -- cgit v1.2.3 From f679a41fafc8cdc5c7879aa4726a28439fca7656 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Thu, 28 Jan 2021 08:52:33 +0000 Subject: thunderbolt: dma_port: Check 'dma_port_flash_write_block()'s return value ... and take the error path if it fails. Fixes the following W=1 kernel build warning(s): drivers/thunderbolt/dma_port.c: In function ‘dma_port_flash_write_block’: drivers/thunderbolt/dma_port.c:331:6: warning: variable ‘ret’ set but not used [-Wunused-but-set-variable] Cc: Andreas Noever Cc: Michael Jamet Cc: Yehezkel Bernat Cc: linux-usb@vger.kernel.org Signed-off-by: Lee Jones Signed-off-by: Mika Westerberg --- drivers/thunderbolt/dma_port.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/thunderbolt/dma_port.c b/drivers/thunderbolt/dma_port.c index 847dd07a7b17..7288aaf01ae6 100644 --- a/drivers/thunderbolt/dma_port.c +++ b/drivers/thunderbolt/dma_port.c @@ -335,6 +335,8 @@ static int dma_port_flash_write_block(struct tb_dma_port *dma, u32 address, /* Write the block to MAIL_DATA registers */ ret = dma_port_write(sw->tb->ctl, buf, tb_route(sw), dma->port, dma->base + MAIL_DATA, dwords, DMA_PORT_TIMEOUT); + if (ret) + return ret; in = MAIL_IN_CMD_FLASH_WRITE << MAIL_IN_CMD_SHIFT; -- cgit v1.2.3 From 4366979fdd437d90b5efc44b368869b1c9493863 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Wed, 27 Jan 2021 11:25:44 +0000 Subject: thunderbolt: cap: Fix kernel-doc formatting issue Fixes the following W=1 kernel build warning(s): drivers/thunderbolt/cap.c:189: warning: Function parameter or member 'sw' not described in 'tb_switch_find_cap' Cc: Andreas Noever Cc: Michael Jamet Cc: Yehezkel Bernat Cc: linux-usb@vger.kernel.org Signed-off-by: Lee Jones Signed-off-by: Mika Westerberg --- drivers/thunderbolt/cap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thunderbolt/cap.c b/drivers/thunderbolt/cap.c index 6f571e912cf2..8ecd610c62d5 100644 --- a/drivers/thunderbolt/cap.c +++ b/drivers/thunderbolt/cap.c @@ -178,7 +178,7 @@ int tb_switch_next_cap(struct tb_switch *sw, unsigned int offset) /** * tb_switch_find_cap() - Find switch capability - * @sw Switch to find the capability for + * @sw: Switch to find the capability for * @cap: Capability to look * * Returns offset to start of capability or %-ENOENT if no such -- cgit v1.2.3 From aa60825391b6b7b6b65fb72676145db4befcc6f9 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Wed, 27 Jan 2021 11:25:45 +0000 Subject: thunderbolt: ctl: Demote non-conformant kernel-doc headers Fixes the following W=1 kernel build warning(s): drivers/thunderbolt/ctl.c:38: warning: expecting prototype for struct tb_cfg. Prototype was for struct tb_ctl instead drivers/thunderbolt/ctl.c:350: warning: Function parameter or member 'ctl' not described in 'tb_ctl_tx' drivers/thunderbolt/ctl.c:350: warning: Function parameter or member 'data' not described in 'tb_ctl_tx' drivers/thunderbolt/ctl.c:350: warning: Function parameter or member 'len' not described in 'tb_ctl_tx' drivers/thunderbolt/ctl.c:350: warning: Function parameter or member 'type' not described in 'tb_ctl_tx' drivers/thunderbolt/ctl.c:350: warning: expecting prototype for tb_cfg_tx(). Prototype was for tb_ctl_tx() instead drivers/thunderbolt/ctl.c:383: warning: Function parameter or member 'ctl' not described in 'tb_ctl_handle_event' drivers/thunderbolt/ctl.c:383: warning: Function parameter or member 'type' not described in 'tb_ctl_handle_event' drivers/thunderbolt/ctl.c:383: warning: Function parameter or member 'pkg' not described in 'tb_ctl_handle_event' drivers/thunderbolt/ctl.c:383: warning: Function parameter or member 'size' not described in 'tb_ctl_handle_event' Cc: Andreas Noever Cc: Michael Jamet Cc: Yehezkel Bernat Cc: linux-usb@vger.kernel.org Signed-off-by: Lee Jones [ mw: Demote only static functions ] Signed-off-by: Mika Westerberg --- drivers/thunderbolt/ctl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index bac08b820015..2f9d4fc8aa23 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -338,7 +338,7 @@ static void tb_ctl_tx_callback(struct tb_ring *ring, struct ring_frame *frame, tb_ctl_pkg_free(pkg); } -/** +/* * tb_cfg_tx() - transmit a packet on the control channel * * len must be a multiple of four. @@ -375,7 +375,7 @@ static int tb_ctl_tx(struct tb_ctl *ctl, const void *data, size_t len, return res; } -/** +/* * tb_ctl_handle_event() - acknowledge a plug event, invoke ctl->callback */ static bool tb_ctl_handle_event(struct tb_ctl *ctl, enum tb_cfg_pkg_type type, -- cgit v1.2.3 From fea7372cbc40869876df0f045e367f6f97a1666c Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 28 Jan 2021 12:35:23 +0300 Subject: USB: serial: mos7720: fix error code in mos7720_write() This code should return -ENOMEM if the kmalloc() fails but instead it returns success. Signed-off-by: Dan Carpenter Fixes: 0f64478cbc7a ("USB: add USB serial mos7720 driver") Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/mos7720.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/serial/mos7720.c b/drivers/usb/serial/mos7720.c index ed347a6d50ba..aa55169796a3 100644 --- a/drivers/usb/serial/mos7720.c +++ b/drivers/usb/serial/mos7720.c @@ -1094,8 +1094,10 @@ static int mos7720_write(struct tty_struct *tty, struct usb_serial_port *port, if (urb->transfer_buffer == NULL) { urb->transfer_buffer = kmalloc(URB_TRANSFER_BUFFER_SIZE, GFP_ATOMIC); - if (!urb->transfer_buffer) + if (!urb->transfer_buffer) { + bytes_sent = -ENOMEM; goto exit; + } } transfer_size = min(count, URB_TRANSFER_BUFFER_SIZE); -- cgit v1.2.3 From ff48bc4477735d8f1cd5409eba84f3139e217e9f Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Wed, 27 Jan 2021 11:25:46 +0000 Subject: thunderbolt: eeprom: Demote non-conformant kernel-doc headers to standard comment blocks Fixes the following W=1 kernel build warning(s): drivers/thunderbolt/eeprom.c:19: warning: Function parameter or member 'sw' not described in 'tb_eeprom_ctl_write' drivers/thunderbolt/eeprom.c:19: warning: Function parameter or member 'ctl' not described in 'tb_eeprom_ctl_write' drivers/thunderbolt/eeprom.c:27: warning: Function parameter or member 'sw' not described in 'tb_eeprom_ctl_read' drivers/thunderbolt/eeprom.c:27: warning: Function parameter or member 'ctl' not described in 'tb_eeprom_ctl_read' drivers/thunderbolt/eeprom.c:27: warning: expecting prototype for tb_eeprom_ctl_write(). Prototype was for tb_eeprom_ctl_read() instead drivers/thunderbolt/eeprom.c:43: warning: Function parameter or member 'sw' not described in 'tb_eeprom_active' drivers/thunderbolt/eeprom.c:43: warning: Function parameter or member 'enable' not described in 'tb_eeprom_active' drivers/thunderbolt/eeprom.c:73: warning: Function parameter or member 'sw' not described in 'tb_eeprom_transfer' drivers/thunderbolt/eeprom.c:73: warning: Function parameter or member 'ctl' not described in 'tb_eeprom_transfer' drivers/thunderbolt/eeprom.c:73: warning: Function parameter or member 'direction' not described in 'tb_eeprom_transfer' drivers/thunderbolt/eeprom.c:97: warning: Function parameter or member 'sw' not described in 'tb_eeprom_out' drivers/thunderbolt/eeprom.c:97: warning: Function parameter or member 'val' not described in 'tb_eeprom_out' drivers/thunderbolt/eeprom.c:117: warning: Function parameter or member 'sw' not described in 'tb_eeprom_in' drivers/thunderbolt/eeprom.c:117: warning: Function parameter or member 'val' not described in 'tb_eeprom_in' drivers/thunderbolt/eeprom.c:138: warning: Function parameter or member 'sw' not described in 'tb_eeprom_get_drom_offset' drivers/thunderbolt/eeprom.c:138: warning: Function parameter or member 'offset' not described in 'tb_eeprom_get_drom_offset' drivers/thunderbolt/eeprom.c:170: warning: Function parameter or member 'sw' not described in 'tb_eeprom_read_n' drivers/thunderbolt/eeprom.c:170: warning: Function parameter or member 'offset' not described in 'tb_eeprom_read_n' drivers/thunderbolt/eeprom.c:170: warning: Function parameter or member 'val' not described in 'tb_eeprom_read_n' drivers/thunderbolt/eeprom.c:170: warning: Function parameter or member 'count' not described in 'tb_eeprom_read_n' drivers/thunderbolt/eeprom.c:383: warning: Function parameter or member 'sw' not described in 'tb_drom_parse_entries' drivers/thunderbolt/eeprom.c:417: warning: Function parameter or member 'sw' not described in 'tb_drom_copy_efi' drivers/thunderbolt/eeprom.c:417: warning: Function parameter or member 'size' not described in 'tb_drom_copy_efi' Cc: Andreas Noever Cc: Michael Jamet Cc: Yehezkel Bernat Cc: linux-usb@vger.kernel.org Signed-off-by: Lee Jones [ mw: Demote only static functions ] Signed-off-by: Mika Westerberg --- drivers/thunderbolt/eeprom.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index 0c8471be3e32..63c64f503faa 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -12,7 +12,7 @@ #include #include "tb.h" -/** +/* * tb_eeprom_ctl_write() - write control word */ static int tb_eeprom_ctl_write(struct tb_switch *sw, struct tb_eeprom_ctl *ctl) @@ -20,7 +20,7 @@ static int tb_eeprom_ctl_write(struct tb_switch *sw, struct tb_eeprom_ctl *ctl) return tb_sw_write(sw, ctl, TB_CFG_SWITCH, sw->cap_plug_events + 4, 1); } -/** +/* * tb_eeprom_ctl_write() - read control word */ static int tb_eeprom_ctl_read(struct tb_switch *sw, struct tb_eeprom_ctl *ctl) @@ -33,7 +33,7 @@ enum tb_eeprom_transfer { TB_EEPROM_OUT, }; -/** +/* * tb_eeprom_active - enable rom access * * WARNING: Always disable access after usage. Otherwise the controller will @@ -62,7 +62,7 @@ static int tb_eeprom_active(struct tb_switch *sw, bool enable) } } -/** +/* * tb_eeprom_transfer - transfer one bit * * If TB_EEPROM_IN is passed, then the bit can be retrieved from ctl->data_in. @@ -90,7 +90,7 @@ static int tb_eeprom_transfer(struct tb_switch *sw, struct tb_eeprom_ctl *ctl, return tb_eeprom_ctl_write(sw, ctl); } -/** +/* * tb_eeprom_out - write one byte to the bus */ static int tb_eeprom_out(struct tb_switch *sw, u8 val) @@ -110,7 +110,7 @@ static int tb_eeprom_out(struct tb_switch *sw, u8 val) return 0; } -/** +/* * tb_eeprom_in - read one byte from the bus */ static int tb_eeprom_in(struct tb_switch *sw, u8 *val) @@ -131,7 +131,7 @@ static int tb_eeprom_in(struct tb_switch *sw, u8 *val) return 0; } -/** +/* * tb_eeprom_get_drom_offset - get drom offset within eeprom */ static int tb_eeprom_get_drom_offset(struct tb_switch *sw, u16 *offset) @@ -162,7 +162,7 @@ static int tb_eeprom_get_drom_offset(struct tb_switch *sw, u16 *offset) return 0; } -/** +/* * tb_eeprom_read_n - read count bytes from offset into val */ static int tb_eeprom_read_n(struct tb_switch *sw, u16 offset, u8 *val, @@ -374,7 +374,7 @@ static int tb_drom_parse_entry_port(struct tb_switch *sw, return 0; } -/** +/* * tb_drom_parse_entries - parse the linked list of drom entries * * Drom must have been copied to sw->drom. @@ -410,7 +410,7 @@ static int tb_drom_parse_entries(struct tb_switch *sw) return 0; } -/** +/* * tb_drom_copy_efi - copy drom supplied by EFI to sw->drom if present */ static int tb_drom_copy_efi(struct tb_switch *sw, u16 *size) -- cgit v1.2.3 From 925a2af9b3629a5476db620ce945b515aff0e9f3 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Wed, 27 Jan 2021 11:25:48 +0000 Subject: thunderbolt: xdomain: Fix 'tb_unregister_service_driver()'s 'drv' param Fixes the following W=1 kernel build warning(s): drivers/thunderbolt/xdomain.c:678: warning: Function parameter or member 'drv' not described in 'tb_unregister_service_driver' drivers/thunderbolt/xdomain.c:678: warning: Excess function parameter 'xdrv' description in 'tb_unregister_service_driver' Cc: Andreas Noever Cc: Michael Jamet Cc: Yehezkel Bernat Cc: Alexei Starovoitov Cc: Daniel Borkmann Cc: "David S. Miller" Cc: Jakub Kicinski Cc: Jesper Dangaard Brouer Cc: John Fastabend Cc: linux-usb@vger.kernel.org Cc: netdev@vger.kernel.org Cc: bpf@vger.kernel.org Signed-off-by: Lee Jones Signed-off-by: Mika Westerberg --- drivers/thunderbolt/xdomain.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index f2d4db1cd84d..6e8bea6a7d39 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -670,7 +670,7 @@ EXPORT_SYMBOL_GPL(tb_register_service_driver); /** * tb_unregister_service_driver() - Unregister XDomain service driver - * @xdrv: Driver to unregister + * @drv: Driver to unregister * * Unregisters XDomain service driver from the bus. */ -- cgit v1.2.3 From a7bfb27bee62ca597fd747f1316f40b1f195e03f Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Wed, 27 Jan 2021 11:25:49 +0000 Subject: thunderbolt: nhi: Demote some non-conformant kernel-doc headers Fixes the following W=1 kernel build warning(s): drivers/thunderbolt/nhi.c:53: warning: Function parameter or member 'ring' not described in 'ring_interrupt_active' drivers/thunderbolt/nhi.c:53: warning: Function parameter or member 'active' not described in 'ring_interrupt_active' drivers/thunderbolt/nhi.c:114: warning: Function parameter or member 'nhi' not described in 'nhi_disable_interrupts' drivers/thunderbolt/nhi.c:191: warning: Function parameter or member 'ring' not described in 'ring_write_descriptors' drivers/thunderbolt/nhi.c:225: warning: Function parameter or member 'work' not described in 'ring_work' Cc: Andreas Noever Cc: Michael Jamet Cc: Yehezkel Bernat Cc: linux-usb@vger.kernel.org Signed-off-by: Lee Jones [ mw: Demote only static functions ] Signed-off-by: Mika Westerberg --- drivers/thunderbolt/nhi.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index cfc622da4f83..7073c25248b3 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -44,7 +44,7 @@ static int ring_interrupt_index(struct tb_ring *ring) return bit; } -/** +/* * ring_interrupt_active() - activate/deactivate interrupts for a single ring * * ring->nhi->lock must be held. @@ -105,7 +105,7 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) iowrite32(new, ring->nhi->iobase + reg); } -/** +/* * nhi_disable_interrupts() - disable interrupts for all rings * * Use only during init and shutdown. @@ -182,7 +182,7 @@ static bool ring_empty(struct tb_ring *ring) return ring->head == ring->tail; } -/** +/* * ring_write_descriptors() - post frames from ring->queue to the controller * * ring->lock is held. @@ -212,7 +212,7 @@ static void ring_write_descriptors(struct tb_ring *ring) } } -/** +/* * ring_work() - progress completed frames * * If the ring is shutting down then all frames are marked as canceled and @@ -754,7 +754,7 @@ void tb_ring_free(struct tb_ring *ring) dev_dbg(&ring->nhi->pdev->dev, "freeing %s %d\n", RING_TYPE(ring), ring->hop); - /** + /* * ring->work can no longer be scheduled (it is scheduled only * by nhi_interrupt_work, ring_stop and ring_msix). Wait for it * to finish before freeing the ring. -- cgit v1.2.3 From 877e50b358c88be649583a1589525bd5ca163998 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Wed, 27 Jan 2021 11:25:50 +0000 Subject: thunderbolt: tb: Kernel-doc function headers should document their parameters Fixes the following W=1 kernel build warning(s): drivers/thunderbolt/tb.c:535: warning: Function parameter or member 'sw' not described in 'tb_scan_switch' drivers/thunderbolt/tb.c:551: warning: Function parameter or member 'port' not described in 'tb_scan_port' drivers/thunderbolt/tb.c:711: warning: Function parameter or member 'tb' not described in 'tb_free_invalid_tunnels' drivers/thunderbolt/tb.c:726: warning: Function parameter or member 'sw' not described in 'tb_free_unplugged_children' drivers/thunderbolt/tb.c:1129: warning: Function parameter or member 'work' not described in 'tb_handle_hotplug' drivers/thunderbolt/tb.c:1239: warning: Function parameter or member 'tb' not described in 'tb_handle_event' drivers/thunderbolt/tb.c:1239: warning: Function parameter or member 'type' not described in 'tb_handle_event' drivers/thunderbolt/tb.c:1239: warning: Function parameter or member 'buf' not described in 'tb_handle_event' drivers/thunderbolt/tb.c:1239: warning: Function parameter or member 'size' not described in 'tb_handle_event' drivers/thunderbolt/tb.c:1239: warning: expecting prototype for tb_schedule_hotplug_handler(). Prototype was for tb_handle_event() instead Cc: Andreas Noever Cc: Michael Jamet Cc: Yehezkel Bernat Cc: linux-usb@vger.kernel.org Signed-off-by: Lee Jones Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index d08879849abe..413955aa6a94 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -528,7 +528,7 @@ static int tb_create_usb3_tunnels(struct tb_switch *sw) static void tb_scan_port(struct tb_port *port); -/** +/* * tb_scan_switch() - scan for and initialize downstream switches */ static void tb_scan_switch(struct tb_switch *sw) @@ -544,7 +544,7 @@ static void tb_scan_switch(struct tb_switch *sw) pm_runtime_put_autosuspend(&sw->dev); } -/** +/* * tb_scan_port() - check for and initialize switches below port */ static void tb_scan_port(struct tb_port *port) @@ -704,7 +704,7 @@ static void tb_deactivate_and_free_tunnel(struct tb_tunnel *tunnel) tb_tunnel_free(tunnel); } -/** +/* * tb_free_invalid_tunnels() - destroy tunnels of devices that have gone away */ static void tb_free_invalid_tunnels(struct tb *tb) @@ -719,7 +719,7 @@ static void tb_free_invalid_tunnels(struct tb *tb) } } -/** +/* * tb_free_unplugged_children() - traverse hierarchy and free unplugged switches */ static void tb_free_unplugged_children(struct tb_switch *sw) @@ -1120,7 +1120,7 @@ static int tb_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd) /* hotplug handling */ -/** +/* * tb_handle_hotplug() - handle hotplug event * * Executes on tb->wq. @@ -1229,7 +1229,7 @@ out: kfree(ev); } -/** +/* * tb_schedule_hotplug_handler() - callback function for the control channel * * Delegates to tb_handle_hotplug. -- cgit v1.2.3 From 47ba5ae46bb995d0766be2fa2068070f61f8f24c Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Wed, 27 Jan 2021 11:25:51 +0000 Subject: thunderbolt: switch: Demote a bunch of non-conformant kernel-doc headers Fixes the following W=1 kernel build warning(s): drivers/thunderbolt/switch.c:730: warning: Function parameter or member 'port' not described in 'tb_init_port' drivers/thunderbolt/switch.c:1348: warning: Function parameter or member 'sw' not described in 'tb_plug_events_active' drivers/thunderbolt/switch.c:1348: warning: Function parameter or member 'active' not described in 'tb_plug_events_active' Cc: Andreas Noever Cc: Michael Jamet Cc: Yehezkel Bernat Cc: linux-usb@vger.kernel.org Signed-off-by: Lee Jones [ mw: Demote only static functions ] Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index cdba05e72486..4fbc03199424 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -718,7 +718,7 @@ int tb_port_disable(struct tb_port *port) return __tb_port_enable(port, false); } -/** +/* * tb_init_port() - initialize a port * * This is a helper method for tb_switch_alloc. Does not check or initialize @@ -1337,7 +1337,7 @@ int tb_switch_reset(struct tb_switch *sw) return res.err; } -/** +/* * tb_plug_events_active() - enable/disable plug events on a switch * * Also configures a sane plug_events_delay of 255ms. -- cgit v1.2.3 From af6bd59ea5b73a8c6310a7de1f991b85ba29c77f Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Wed, 27 Jan 2021 11:25:52 +0000 Subject: thunderbolt: icm: Fix a couple of formatting issues Fixes the following W=1 kernel build warning(s): drivers/thunderbolt/icm.c:122: warning: Function parameter or member 'xdomain_connected' not described in 'icm' drivers/thunderbolt/icm.c:122: warning: Function parameter or member 'xdomain_disconnected' not described in 'icm' Cc: Andreas Noever Cc: Michael Jamet Cc: Yehezkel Bernat Cc: linux-usb@vger.kernel.org Signed-off-by: Lee Jones Signed-off-by: Mika Westerberg --- drivers/thunderbolt/icm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index 8b7f941a9bb7..14340ec61703 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -85,8 +85,8 @@ struct usb4_switch_nvm_auth { * @set_uuid: Set UUID for the root switch (optional) * @device_connected: Handle device connected ICM message * @device_disconnected: Handle device disconnected ICM message - * @xdomain_connected - Handle XDomain connected ICM message - * @xdomain_disconnected - Handle XDomain disconnected ICM message + * @xdomain_connected: Handle XDomain connected ICM message + * @xdomain_disconnected: Handle XDomain disconnected ICM message * @rtd3_veto: Handle RTD3 veto notification ICM message */ struct icm { -- cgit v1.2.3 From a27ea0dfc1cdac5f1e5e1cb45cc1d92b476adb75 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Wed, 27 Jan 2021 11:25:53 +0000 Subject: thunderbolt: tunnel: Fix misspelling of 'receive_path' Fixes the following W=1 kernel build warning(s): drivers/thunderbolt/tunnel.c:841: warning: Function parameter or member 'receive_path' not described in 'tb_tunnel_alloc_dma' drivers/thunderbolt/tunnel.c:841: warning: Excess function parameter 'reveive_path' description in 'tb_tunnel_alloc_dma' Cc: Andreas Noever Cc: Michael Jamet Cc: Yehezkel Bernat Cc: linux-usb@vger.kernel.org Signed-off-by: Lee Jones Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tunnel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index dcdf9c7a9cae..37943b0379ab 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -830,7 +830,7 @@ static void tb_dma_init_path(struct tb_path *path, unsigned int isb, * @transmit_path: HopID used for transmitting packets * @receive_ring: NHI ring number used to receive packets from the * other domain. Set to %0 if RX path is not needed. - * @reveive_path: HopID used for receiving packets + * @receive_path: HopID used for receiving packets * * Return: Returns a tb_tunnel on success or NULL on failure. */ -- cgit v1.2.3 From 2c2a2327bdb509c96737258ca6c91f176acc837d Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Wed, 27 Jan 2021 11:25:54 +0000 Subject: thunderbolt: switch: Fix function name in the header Fixes the following W=1 kernel build warning(s): drivers/thunderbolt/switch.c:1322: warning: expecting prototype for reset_switch(). Prototype was for tb_switch_reset() instead Cc: Andreas Noever Cc: Michael Jamet Cc: Yehezkel Bernat Cc: linux-usb@vger.kernel.org Signed-off-by: Lee Jones Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 4fbc03199424..6ca3efba4520 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -1313,7 +1313,7 @@ static void tb_dump_switch(const struct tb *tb, const struct tb_switch *sw) } /** - * reset_switch() - reconfigure route, enable and send TB_CFG_PKG_RESET + * tb_switch_reset() - reconfigure route, enable and send TB_CFG_PKG_RESET * @sw: Switch to reset * * Return: Returns 0 on success or an error code on failure. -- cgit v1.2.3 From ab58f3bb6aaaf98ba81d5c627ac25c08ff4ed4f1 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:18 +0200 Subject: xhci: Avoid parsing transfer events several times When handling transfer events the event is passed along the handling callpath and parsed again in several occasions. The event contains slot_id and endpoint index, from which the driver endpoint structure can be found. There wasn't however a way to get the endpoint index or parent usb device from this endpoint structure. A lot of extra event parsing, and thus some DMA doublefetch cases, and excess variables and code can be avoided by adding endpoint index and parent usb virt device pointer to the endpoint structure. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-2-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 2 ++ drivers/usb/host/xhci-ring.c | 28 ++++++++-------------------- drivers/usb/host/xhci.h | 2 ++ 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 3589b49b6c8b..d6e2ee1f8964 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1012,6 +1012,8 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, /* Initialize the cancellation list and watchdog timers for each ep */ for (i = 0; i < 31; i++) { + dev->eps[i].ep_index = i; + dev->eps[i].vdev = dev; xhci_init_endpoint_timer(xhci, &dev->eps[i]); INIT_LIST_HEAD(&dev->eps[i].cancelled_td_list); INIT_LIST_HEAD(&dev->eps[i].bw_endpoint_list); diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index cf0c93a90200..cac79a3e3b1b 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1861,7 +1861,7 @@ static void xhci_cleanup_halted_endpoint(struct xhci_hcd *xhci, * Avoid resetting endpoint if link is inactive. Can cause host hang. * Device will be reset soon to recover the link so don't do anything */ - if (xhci->devs[slot_id]->flags & VDEV_PORT_ERROR) + if (ep->vdev->flags & VDEV_PORT_ERROR) return; command = xhci_alloc_command(xhci, false, GFP_ATOMIC); @@ -1970,18 +1970,14 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, struct xhci_transfer_event *event, struct xhci_virt_ep *ep, int *status) { - struct xhci_virt_device *xdev; struct xhci_ep_ctx *ep_ctx; struct xhci_ring *ep_ring; unsigned int slot_id; u32 trb_comp_code; - int ep_index; slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags)); - xdev = xhci->devs[slot_id]; - ep_index = TRB_TO_EP_ID(le32_to_cpu(event->flags)) - 1; ep_ring = xhci_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer)); - ep_ctx = xhci_get_ep_ctx(xhci, xdev->out_ctx, ep_index); + ep_ctx = xhci_get_ep_ctx(xhci, ep->vdev->out_ctx, ep->ep_index); trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); if (trb_comp_code == COMP_STOPPED_LENGTH_INVALID || @@ -2006,9 +2002,9 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, * stall later. Hub TT buffer should only be cleared for FS/LS * devices behind HS hubs for functional stalls. */ - if ((ep_index != 0) || (trb_comp_code != COMP_STALL_ERROR)) + if ((ep->ep_index != 0) || (trb_comp_code != COMP_STALL_ERROR)) xhci_clear_hub_tt_buffer(xhci, td, ep); - xhci_cleanup_halted_endpoint(xhci, slot_id, ep_index, + xhci_cleanup_halted_endpoint(xhci, slot_id, ep->ep_index, ep_ring->stream_id, td, EP_HARD_RESET); } else { /* Update ring dequeue pointer */ @@ -2042,19 +2038,13 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td, union xhci_trb *ep_trb, struct xhci_transfer_event *event, struct xhci_virt_ep *ep, int *status) { - struct xhci_virt_device *xdev; - unsigned int slot_id; - int ep_index; struct xhci_ep_ctx *ep_ctx; u32 trb_comp_code; u32 remaining, requested; u32 trb_type; trb_type = TRB_FIELD_TO_TYPE(le32_to_cpu(ep_trb->generic.field[3])); - slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags)); - xdev = xhci->devs[slot_id]; - ep_index = TRB_TO_EP_ID(le32_to_cpu(event->flags)) - 1; - ep_ctx = xhci_get_ep_ctx(xhci, xdev->out_ctx, ep_index); + ep_ctx = xhci_get_ep_ctx(xhci, ep->vdev->out_ctx, ep->ep_index); trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); requested = td->urb->transfer_buffer_length; remaining = EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); @@ -2102,7 +2092,7 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td, ep_ctx, trb_comp_code)) break; xhci_dbg(xhci, "TRB error %u, halted endpoint index = %u\n", - trb_comp_code, ep_index); + trb_comp_code, ep->ep_index); fallthrough; case COMP_STALL_ERROR: /* Did we transfer part of the data (middle) phase? */ @@ -2264,11 +2254,9 @@ static int process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_td *td, u32 trb_comp_code; u32 remaining, requested, ep_trb_len; unsigned int slot_id; - int ep_index; slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags)); - slot_ctx = xhci_get_slot_ctx(xhci, xhci->devs[slot_id]->out_ctx); - ep_index = TRB_TO_EP_ID(le32_to_cpu(event->flags)) - 1; + slot_ctx = xhci_get_slot_ctx(xhci, ep->vdev->out_ctx); ep_ring = xhci_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer)); trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); remaining = EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); @@ -2306,7 +2294,7 @@ static int process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_td *td, le32_to_cpu(slot_ctx->tt_info) & TT_SLOT) break; *status = 0; - xhci_cleanup_halted_endpoint(xhci, slot_id, ep_index, + xhci_cleanup_halted_endpoint(xhci, slot_id, ep->ep_index, ep_ring->stream_id, td, EP_SOFT_RESET); return 0; default: diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 25e57bc9c3cc..c76381f341ff 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -918,6 +918,8 @@ struct xhci_bw_info { #define SS_BW_RESERVED 10 struct xhci_virt_ep { + struct xhci_virt_device *vdev; /* parent */ + unsigned int ep_index; struct xhci_ring *ring; /* Related to endpoints that are configured to use stream IDs only */ struct xhci_stream_info *stream_info; -- cgit v1.2.3 From d4dff8043ea5b93a30cb9b19d4407bd506a6877a Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:19 +0200 Subject: xhci: get isochronous ring directly from endpoint structure isochronous endpoints do not support streams, meaning that there is only one ring per endpoint. Avoid double-fetching the transfer event DMA to get the ring. Also makes passing the event to skip_isoc_td() uncecessary. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-3-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index cac79a3e3b1b..987fed93ddfb 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2134,7 +2134,6 @@ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, union xhci_trb *ep_trb, struct xhci_transfer_event *event, struct xhci_virt_ep *ep, int *status) { - struct xhci_ring *ep_ring; struct urb_priv *urb_priv; int idx; struct usb_iso_packet_descriptor *frame; @@ -2143,7 +2142,6 @@ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, u32 remaining, requested, ep_trb_len; int short_framestatus; - ep_ring = xhci_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer)); trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); urb_priv = td->urb->hcpriv; idx = urb_priv->num_tds_done; @@ -2204,7 +2202,7 @@ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, } if (sum_trbs_for_length) - frame->actual_length = sum_trb_lengths(xhci, ep_ring, ep_trb) + + frame->actual_length = sum_trb_lengths(xhci, ep->ring, ep_trb) + ep_trb_len - remaining; else frame->actual_length = requested; @@ -2215,15 +2213,12 @@ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, } static int skip_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, - struct xhci_transfer_event *event, struct xhci_virt_ep *ep, int *status) { - struct xhci_ring *ep_ring; struct urb_priv *urb_priv; struct usb_iso_packet_descriptor *frame; int idx; - ep_ring = xhci_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer)); urb_priv = td->urb->hcpriv; idx = urb_priv->num_tds_done; frame = &td->urb->iso_frame_desc[idx]; @@ -2235,11 +2230,11 @@ static int skip_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, frame->actual_length = 0; /* Update ring dequeue pointer */ - while (ep_ring->dequeue != td->last_trb) - inc_deq(xhci, ep_ring); - inc_deq(xhci, ep_ring); + while (ep->ring->dequeue != td->last_trb) + inc_deq(xhci, ep->ring); + inc_deq(xhci, ep->ring); - return xhci_td_cleanup(xhci, td, ep_ring, status); + return xhci_td_cleanup(xhci, td, ep->ring, status); } /* @@ -2619,7 +2614,7 @@ static int handle_tx_event(struct xhci_hcd *xhci, return -ESHUTDOWN; } - skip_isoc_td(xhci, td, event, ep, &status); + skip_isoc_td(xhci, td, ep, &status); goto cleanup; } if (trb_comp_code == COMP_SHORT_PACKET) -- cgit v1.2.3 From d70f4231b81eeb6dd78bd913ff42729b524eec51 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:20 +0200 Subject: xhci: adjust parameters passed to cleanup_halted_endpoint() Instead of passing slot id and endpoint index to cleanup_halted_endpoint() pass the endpoint structure pointer as it's already known. Avoids again digging out the endpoint structure based on slot id and endpoint index, and passing them along the call chain for this purpose only. Add slot_id to the virt_dev structure so that it can easily be found from a virt_dev, or its child, the virt_ep endpoint structure. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-4-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 2 ++ drivers/usb/host/xhci-ring.c | 35 ++++++++++++++--------------------- drivers/usb/host/xhci.h | 1 + 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index d6e2ee1f8964..c136ca0502e3 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -994,6 +994,8 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, if (!dev) return 0; + dev->slot_id = slot_id; + /* Allocate the (output) device context that will be used in the HC. */ dev->out_ctx = xhci_alloc_container_ctx(xhci, XHCI_CTX_TYPE_DEVICE, flags); if (!dev->out_ctx) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 987fed93ddfb..7bcc5793a8b8 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1850,13 +1850,12 @@ static void xhci_clear_hub_tt_buffer(struct xhci_hcd *xhci, struct xhci_td *td, } static void xhci_cleanup_halted_endpoint(struct xhci_hcd *xhci, - unsigned int slot_id, unsigned int ep_index, - unsigned int stream_id, struct xhci_td *td, - enum xhci_ep_reset_type reset_type) + struct xhci_virt_ep *ep, unsigned int stream_id, + struct xhci_td *td, + enum xhci_ep_reset_type reset_type) { - struct xhci_virt_ep *ep = &xhci->devs[slot_id]->eps[ep_index]; struct xhci_command *command; - + unsigned int slot_id = ep->vdev->slot_id; /* * Avoid resetting endpoint if link is inactive. Can cause host hang. * Device will be reset soon to recover the link so don't do anything @@ -1870,11 +1869,11 @@ static void xhci_cleanup_halted_endpoint(struct xhci_hcd *xhci, ep->ep_state |= EP_HALTED; - xhci_queue_reset_ep(xhci, command, slot_id, ep_index, reset_type); + xhci_queue_reset_ep(xhci, command, slot_id, ep->ep_index, reset_type); if (reset_type == EP_HARD_RESET) { ep->ep_state |= EP_HARD_CLEAR_TOGGLE; - xhci_cleanup_stalled_ring(xhci, slot_id, ep_index, stream_id, + xhci_cleanup_stalled_ring(xhci, slot_id, ep->ep_index, stream_id, td); } xhci_ring_cmd_db(xhci); @@ -1972,10 +1971,8 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, { struct xhci_ep_ctx *ep_ctx; struct xhci_ring *ep_ring; - unsigned int slot_id; u32 trb_comp_code; - slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags)); ep_ring = xhci_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer)); ep_ctx = xhci_get_ep_ctx(xhci, ep->vdev->out_ctx, ep->ep_index); trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); @@ -2004,8 +2001,8 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, */ if ((ep->ep_index != 0) || (trb_comp_code != COMP_STALL_ERROR)) xhci_clear_hub_tt_buffer(xhci, td, ep); - xhci_cleanup_halted_endpoint(xhci, slot_id, ep->ep_index, - ep_ring->stream_id, td, EP_HARD_RESET); + xhci_cleanup_halted_endpoint(xhci, ep, ep_ring->stream_id, td, + EP_HARD_RESET); } else { /* Update ring dequeue pointer */ while (ep_ring->dequeue != td->last_trb) @@ -2248,9 +2245,7 @@ static int process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_td *td, struct xhci_ring *ep_ring; u32 trb_comp_code; u32 remaining, requested, ep_trb_len; - unsigned int slot_id; - slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags)); slot_ctx = xhci_get_slot_ctx(xhci, ep->vdev->out_ctx); ep_ring = xhci_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer)); trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); @@ -2289,8 +2284,8 @@ static int process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_td *td, le32_to_cpu(slot_ctx->tt_info) & TT_SLOT) break; *status = 0; - xhci_cleanup_halted_endpoint(xhci, slot_id, ep->ep_index, - ep_ring->stream_id, td, EP_SOFT_RESET); + xhci_cleanup_halted_endpoint(xhci, ep, ep_ring->stream_id, td, + EP_SOFT_RESET); return 0; default: /* do nothing */ @@ -2366,8 +2361,8 @@ static int handle_tx_event(struct xhci_hcd *xhci, case COMP_USB_TRANSACTION_ERROR: case COMP_INVALID_STREAM_TYPE_ERROR: case COMP_INVALID_STREAM_ID_ERROR: - xhci_cleanup_halted_endpoint(xhci, slot_id, ep_index, 0, - NULL, EP_SOFT_RESET); + xhci_cleanup_halted_endpoint(xhci, ep, 0, NULL, + EP_SOFT_RESET); goto cleanup; case COMP_RING_UNDERRUN: case COMP_RING_OVERRUN: @@ -2551,8 +2546,7 @@ static int handle_tx_event(struct xhci_hcd *xhci, if (trb_comp_code == COMP_STALL_ERROR || xhci_requires_manual_halt_cleanup(xhci, ep_ctx, trb_comp_code)) { - xhci_cleanup_halted_endpoint(xhci, slot_id, - ep_index, + xhci_cleanup_halted_endpoint(xhci, ep, ep_ring->stream_id, NULL, EP_HARD_RESET); @@ -2646,8 +2640,7 @@ static int handle_tx_event(struct xhci_hcd *xhci, if (trb_comp_code == COMP_STALL_ERROR || xhci_requires_manual_halt_cleanup(xhci, ep_ctx, trb_comp_code)) - xhci_cleanup_halted_endpoint(xhci, slot_id, - ep_index, + xhci_cleanup_halted_endpoint(xhci, ep, ep_ring->stream_id, td, EP_HARD_RESET); goto cleanup; diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index c76381f341ff..123ccdd3fea5 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -997,6 +997,7 @@ struct xhci_interval_bw_table { struct xhci_virt_device { + int slot_id; struct usb_device *udev; /* * Commands to the hardware are passed an "input context" that -- cgit v1.2.3 From a181030703df185c20a8eed2313beb1b0b025e0d Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:21 +0200 Subject: xhci: remove unused event parameter from completion handlers several command completion handlers are passed the event trb as a paramtere even if it't not used. Remove it. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-5-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 7bcc5793a8b8..28f5245af971 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -719,7 +719,7 @@ static void xhci_unmap_td_bounce_buffer(struct xhci_hcd *xhci, * bit cleared) so that the HW will skip over them. */ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, - union xhci_trb *trb, struct xhci_event_cmd *event) + union xhci_trb *trb) { unsigned int ep_index; struct xhci_ring *ep_ring; @@ -1226,7 +1226,7 @@ static void xhci_handle_cmd_disable_slot(struct xhci_hcd *xhci, int slot_id) } static void xhci_handle_cmd_config_ep(struct xhci_hcd *xhci, int slot_id, - struct xhci_event_cmd *event, u32 cmd_comp_code) + u32 cmd_comp_code) { struct xhci_virt_device *virt_dev; struct xhci_input_control_ctx *ctrl_ctx; @@ -1292,8 +1292,7 @@ static void xhci_handle_cmd_addr_dev(struct xhci_hcd *xhci, int slot_id) trace_xhci_handle_cmd_addr_dev(slot_ctx); } -static void xhci_handle_cmd_reset_dev(struct xhci_hcd *xhci, int slot_id, - struct xhci_event_cmd *event) +static void xhci_handle_cmd_reset_dev(struct xhci_hcd *xhci, int slot_id) { struct xhci_virt_device *vdev; struct xhci_slot_ctx *slot_ctx; @@ -1466,8 +1465,7 @@ static void handle_cmd_completion(struct xhci_hcd *xhci, break; case TRB_CONFIG_EP: if (!cmd->completion) - xhci_handle_cmd_config_ep(xhci, slot_id, event, - cmd_comp_code); + xhci_handle_cmd_config_ep(xhci, slot_id, cmd_comp_code); break; case TRB_EVAL_CONTEXT: break; @@ -1478,7 +1476,7 @@ static void handle_cmd_completion(struct xhci_hcd *xhci, WARN_ON(slot_id != TRB_TO_SLOT_ID( le32_to_cpu(cmd_trb->generic.field[3]))); if (!cmd->completion) - xhci_handle_cmd_stop_ep(xhci, slot_id, cmd_trb, event); + xhci_handle_cmd_stop_ep(xhci, slot_id, cmd_trb); break; case TRB_SET_DEQ: WARN_ON(slot_id != TRB_TO_SLOT_ID( @@ -1501,7 +1499,7 @@ static void handle_cmd_completion(struct xhci_hcd *xhci, */ slot_id = TRB_TO_SLOT_ID( le32_to_cpu(cmd_trb->generic.field[3])); - xhci_handle_cmd_reset_dev(xhci, slot_id, event); + xhci_handle_cmd_reset_dev(xhci, slot_id); break; case TRB_NEC_GET_FW: xhci_handle_cmd_nec_get_fw(xhci, event); -- cgit v1.2.3 From b1adc42d440df3233255e313a45ab7e9b2b74096 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:22 +0200 Subject: xhci: add xhci_get_virt_ep() helper In several event handlers we need to find the right endpoint structure from slot_id and ep_index in the event. Add a helper for this, check that slot_id and ep_index are valid. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-6-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 74 ++++++++++++++++++++++++++++++-------------- drivers/usb/host/xhci.h | 3 +- 2 files changed, 52 insertions(+), 25 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 28f5245af971..c9c4c559188f 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -446,6 +446,26 @@ void xhci_ring_doorbell_for_active_rings(struct xhci_hcd *xhci, ring_doorbell_for_active_rings(xhci, slot_id, ep_index); } +static struct xhci_virt_ep *xhci_get_virt_ep(struct xhci_hcd *xhci, + unsigned int slot_id, + unsigned int ep_index) +{ + if (slot_id == 0 || slot_id >= MAX_HC_SLOTS) { + xhci_warn(xhci, "Invalid slot_id %u\n", slot_id); + return NULL; + } + if (ep_index >= EP_CTX_PER_DEV) { + xhci_warn(xhci, "Invalid endpoint index %u\n", ep_index); + return NULL; + } + if (!xhci->devs[slot_id]) { + xhci_warn(xhci, "No xhci virt device for slot_id %u\n", slot_id); + return NULL; + } + + return &xhci->devs[slot_id]->eps[ep_index]; +} + /* Get the right ring for the given slot_id, ep_index and stream_id. * If the endpoint supports streams, boundary check the URB's stream ID. * If the endpoint doesn't support streams, return the singular endpoint ring. @@ -456,7 +476,10 @@ struct xhci_ring *xhci_triad_to_transfer_ring(struct xhci_hcd *xhci, { struct xhci_virt_ep *ep; - ep = &xhci->devs[slot_id]->eps[ep_index]; + ep = xhci_get_virt_ep(xhci, slot_id, ep_index); + if (!ep) + return NULL; + /* Common case: no streams */ if (!(ep->ep_state & EP_HAS_STREAMS)) return ep->ring; @@ -742,11 +765,14 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, memset(&deq_state, 0, sizeof(deq_state)); ep_index = TRB_TO_EP_INDEX(le32_to_cpu(trb->generic.field[3])); - vdev = xhci->devs[slot_id]; + ep = xhci_get_virt_ep(xhci, slot_id, ep_index); + if (!ep) + return; + + vdev = ep->vdev; ep_ctx = xhci_get_ep_ctx(xhci, vdev->out_ctx, ep_index); trace_xhci_handle_cmd_stop_ep(ep_ctx); - ep = &xhci->devs[slot_id]->eps[ep_index]; last_unlinked_td = list_last_entry(&ep->cancelled_td_list, struct xhci_td, cancelled_td_list); @@ -1064,17 +1090,17 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, unsigned int ep_index; unsigned int stream_id; struct xhci_ring *ep_ring; - struct xhci_virt_device *dev; struct xhci_virt_ep *ep; struct xhci_ep_ctx *ep_ctx; struct xhci_slot_ctx *slot_ctx; ep_index = TRB_TO_EP_INDEX(le32_to_cpu(trb->generic.field[3])); stream_id = TRB_TO_STREAM_ID(le32_to_cpu(trb->generic.field[2])); - dev = xhci->devs[slot_id]; - ep = &dev->eps[ep_index]; + ep = xhci_get_virt_ep(xhci, slot_id, ep_index); + if (!ep) + return; - ep_ring = xhci_stream_id_to_ring(dev, ep_index, stream_id); + ep_ring = xhci_stream_id_to_ring(ep->vdev, ep_index, stream_id); if (!ep_ring) { xhci_warn(xhci, "WARN Set TR deq ptr command for freed stream ID %u\n", stream_id); @@ -1082,8 +1108,8 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, goto cleanup; } - ep_ctx = xhci_get_ep_ctx(xhci, dev->out_ctx, ep_index); - slot_ctx = xhci_get_slot_ctx(xhci, dev->out_ctx); + ep_ctx = xhci_get_ep_ctx(xhci, ep->vdev->out_ctx, ep_index); + slot_ctx = xhci_get_slot_ctx(xhci, ep->vdev->out_ctx); trace_xhci_handle_cmd_set_deq(slot_ctx); trace_xhci_handle_cmd_set_deq_ep(ep_ctx); @@ -1136,7 +1162,7 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, /* Update the ring's dequeue segment and dequeue pointer * to reflect the new position. */ - update_ring_for_set_deq_completion(xhci, dev, + update_ring_for_set_deq_completion(xhci, ep->vdev, ep_ring, ep_index); } else { xhci_warn(xhci, "Mismatch between completed Set TR Deq Ptr command & xHCI internal state.\n"); @@ -1146,9 +1172,9 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, } cleanup: - dev->eps[ep_index].ep_state &= ~SET_DEQ_PENDING; - dev->eps[ep_index].queued_deq_seg = NULL; - dev->eps[ep_index].queued_deq_ptr = NULL; + ep->ep_state &= ~SET_DEQ_PENDING; + ep->queued_deq_seg = NULL; + ep->queued_deq_ptr = NULL; /* Restart any rings with pending URBs */ ring_doorbell_for_active_rings(xhci, slot_id, ep_index); } @@ -1156,13 +1182,16 @@ cleanup: static void xhci_handle_cmd_reset_ep(struct xhci_hcd *xhci, int slot_id, union xhci_trb *trb, u32 cmd_comp_code) { - struct xhci_virt_device *vdev; + struct xhci_virt_ep *ep; struct xhci_ep_ctx *ep_ctx; unsigned int ep_index; ep_index = TRB_TO_EP_INDEX(le32_to_cpu(trb->generic.field[3])); - vdev = xhci->devs[slot_id]; - ep_ctx = xhci_get_ep_ctx(xhci, vdev->out_ctx, ep_index); + ep = xhci_get_virt_ep(xhci, slot_id, ep_index); + if (!ep) + return; + + ep_ctx = xhci_get_ep_ctx(xhci, ep->vdev->out_ctx, ep_index); trace_xhci_handle_cmd_reset_ep(ep_ctx); /* This command will only fail if the endpoint wasn't halted, @@ -1190,7 +1219,7 @@ static void xhci_handle_cmd_reset_ep(struct xhci_hcd *xhci, int slot_id, xhci_ring_cmd_db(xhci); } else { /* Clear our internal halted state */ - xhci->devs[slot_id]->eps[ep_index].ep_state &= ~EP_HALTED; + ep->ep_state &= ~EP_HALTED; } /* if this was a soft reset, then restart */ @@ -2313,7 +2342,6 @@ finish_td: static int handle_tx_event(struct xhci_hcd *xhci, struct xhci_transfer_event *event) { - struct xhci_virt_device *xdev; struct xhci_virt_ep *ep; struct xhci_ring *ep_ring; unsigned int slot_id; @@ -2334,16 +2362,14 @@ static int handle_tx_event(struct xhci_hcd *xhci, trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); ep_trb_dma = le64_to_cpu(event->buffer); - xdev = xhci->devs[slot_id]; - if (!xdev) { - xhci_err(xhci, "ERROR Transfer event pointed to bad slot %u\n", - slot_id); + ep = xhci_get_virt_ep(xhci, slot_id, ep_index); + if (!ep) { + xhci_err(xhci, "ERROR Invalid Transfer event\n"); goto err_out; } - ep = &xdev->eps[ep_index]; ep_ring = xhci_dma_to_transfer_ring(ep, ep_trb_dma); - ep_ctx = xhci_get_ep_ctx(xhci, xdev->out_ctx, ep_index); + ep_ctx = xhci_get_ep_ctx(xhci, ep->vdev->out_ctx, ep_index); if (GET_EP_CTX_STATE(ep_ctx) == EP_STATE_DISABLED) { xhci_err(xhci, diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 123ccdd3fea5..a9862f1083ad 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -995,6 +995,7 @@ struct xhci_interval_bw_table { unsigned int ss_bw_out; }; +#define EP_CTX_PER_DEV 31 struct xhci_virt_device { int slot_id; @@ -1010,7 +1011,7 @@ struct xhci_virt_device { struct xhci_container_ctx *out_ctx; /* Used for addressing devices and configuration changes */ struct xhci_container_ctx *in_ctx; - struct xhci_virt_ep eps[31]; + struct xhci_virt_ep eps[EP_CTX_PER_DEV]; u8 fake_port; u8 real_port; struct xhci_interval_bw_table *bw_table; -- cgit v1.2.3 From 03ed579d9d51aa018830b0de3e8b463faf6b87db Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:23 +0200 Subject: xhci: check virt_dev is valid before dereferencing it Check that the xhci_virt_dev structure that we dug out based on a slot_id value from a command completion is valid before dereferencing it. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-7-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index c9c4c559188f..7364be95204a 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1273,6 +1273,8 @@ static void xhci_handle_cmd_config_ep(struct xhci_hcd *xhci, int slot_id, * is not waiting on the configure endpoint command. */ virt_dev = xhci->devs[slot_id]; + if (!virt_dev) + return; ctrl_ctx = xhci_get_input_control_ctx(virt_dev->in_ctx); if (!ctrl_ctx) { xhci_warn(xhci, "Could not get input context, bad type.\n"); @@ -1317,6 +1319,8 @@ static void xhci_handle_cmd_addr_dev(struct xhci_hcd *xhci, int slot_id) struct xhci_slot_ctx *slot_ctx; vdev = xhci->devs[slot_id]; + if (!vdev) + return; slot_ctx = xhci_get_slot_ctx(xhci, vdev->out_ctx); trace_xhci_handle_cmd_addr_dev(slot_ctx); } @@ -1327,13 +1331,15 @@ static void xhci_handle_cmd_reset_dev(struct xhci_hcd *xhci, int slot_id) struct xhci_slot_ctx *slot_ctx; vdev = xhci->devs[slot_id]; + if (!vdev) { + xhci_warn(xhci, "Reset device command completion for disabled slot %u\n", + slot_id); + return; + } slot_ctx = xhci_get_slot_ctx(xhci, vdev->out_ctx); trace_xhci_handle_cmd_reset_dev(slot_ctx); xhci_dbg(xhci, "Completed reset device command.\n"); - if (!xhci->devs[slot_id]) - xhci_warn(xhci, "Reset device command completion " - "for disabled slot %u\n", slot_id); } static void xhci_handle_cmd_nec_get_fw(struct xhci_hcd *xhci, -- cgit v1.2.3 From 42f2890aa998d14a4b406644486d8e246bc1e4b2 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:24 +0200 Subject: xhci: add xhci_virt_ep_to_ring() helper Two existing ring helpers, xhci_triad_to_transfer_ring() and xhci_stream_id_to_ring() have partially similar functionality. Both have some limitation, especieally with boundary checking. Add a new xhci_virt_ep_to_ring() helper with proper boundary checking that can replace parts of one helper, and later will completely replace the other helper. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-8-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 46 +++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 7364be95204a..4cc1dc580ec6 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -466,6 +466,26 @@ static struct xhci_virt_ep *xhci_get_virt_ep(struct xhci_hcd *xhci, return &xhci->devs[slot_id]->eps[ep_index]; } +static struct xhci_ring *xhci_virt_ep_to_ring(struct xhci_hcd *xhci, + struct xhci_virt_ep *ep, + unsigned int stream_id) +{ + /* common case, no streams */ + if (!(ep->ep_state & EP_HAS_STREAMS)) + return ep->ring; + + if (!ep->stream_info) + return NULL; + + if (stream_id == 0 || stream_id >= ep->stream_info->num_streams) { + xhci_warn(xhci, "Invalid stream_id %u request for slot_id %u ep_index %u\n", + stream_id, ep->vdev->slot_id, ep->ep_index); + return NULL; + } + + return ep->stream_info->stream_rings[stream_id]; +} + /* Get the right ring for the given slot_id, ep_index and stream_id. * If the endpoint supports streams, boundary check the URB's stream ID. * If the endpoint doesn't support streams, return the singular endpoint ring. @@ -480,29 +500,7 @@ struct xhci_ring *xhci_triad_to_transfer_ring(struct xhci_hcd *xhci, if (!ep) return NULL; - /* Common case: no streams */ - if (!(ep->ep_state & EP_HAS_STREAMS)) - return ep->ring; - - if (stream_id == 0) { - xhci_warn(xhci, - "WARN: Slot ID %u, ep index %u has streams, " - "but URB has no stream ID.\n", - slot_id, ep_index); - return NULL; - } - - if (stream_id < ep->stream_info->num_streams) - return ep->stream_info->stream_rings[stream_id]; - - xhci_warn(xhci, - "WARN: Slot ID %u, ep index %u has " - "stream IDs 1 to %u allocated, " - "but stream ID %u is requested.\n", - slot_id, ep_index, - ep->stream_info->num_streams - 1, - stream_id); - return NULL; + return xhci_virt_ep_to_ring(xhci, ep, stream_id); } @@ -1100,7 +1098,7 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, if (!ep) return; - ep_ring = xhci_stream_id_to_ring(ep->vdev, ep_index, stream_id); + ep_ring = xhci_virt_ep_to_ring(xhci, ep, stream_id); if (!ep_ring) { xhci_warn(xhci, "WARN Set TR deq ptr command for freed stream ID %u\n", stream_id); -- cgit v1.2.3 From c089cadaa0dfb3a02c848197ef9106a04d445604 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:25 +0200 Subject: xhci: remove xhci_stream_id_to_ring() helper The one case that used this function can use the xhci_triad_to_transfer_ring() helper instead. Avoid having several functions doing basically the same thing. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-9-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 17 ----------------- drivers/usb/host/xhci-ring.c | 3 ++- drivers/usb/host/xhci.h | 4 ---- 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index c136ca0502e3..f2c4ee7c4786 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -592,23 +592,6 @@ struct xhci_ring *xhci_dma_to_transfer_ring( return ep->ring; } -struct xhci_ring *xhci_stream_id_to_ring( - struct xhci_virt_device *dev, - unsigned int ep_index, - unsigned int stream_id) -{ - struct xhci_virt_ep *ep = &dev->eps[ep_index]; - - if (stream_id == 0) - return ep->ring; - if (!ep->stream_info) - return NULL; - - if (stream_id >= ep->stream_info->num_streams) - return NULL; - return ep->stream_info->stream_rings[stream_id]; -} - /* * Change an endpoint's internal structure so it supports stream IDs. The * number of requested streams includes stream 0, which cannot be used by device diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 4cc1dc580ec6..54fcb907b2ba 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -3042,7 +3042,8 @@ static int prepare_transfer(struct xhci_hcd *xhci, struct xhci_ring *ep_ring; struct xhci_ep_ctx *ep_ctx = xhci_get_ep_ctx(xhci, xdev->out_ctx, ep_index); - ep_ring = xhci_stream_id_to_ring(xdev, ep_index, stream_id); + ep_ring = xhci_triad_to_transfer_ring(xhci, xdev->slot_id, ep_index, + stream_id); if (!ep_ring) { xhci_dbg(xhci, "Can't prepare ring for bad stream ID %u\n", stream_id); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index a9862f1083ad..d8a84455aabf 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -2050,10 +2050,6 @@ void xhci_free_device_endpoint_resources(struct xhci_hcd *xhci, struct xhci_ring *xhci_dma_to_transfer_ring( struct xhci_virt_ep *ep, u64 address); -struct xhci_ring *xhci_stream_id_to_ring( - struct xhci_virt_device *dev, - unsigned int ep_index, - unsigned int stream_id); struct xhci_command *xhci_alloc_command(struct xhci_hcd *xhci, bool allocate_completion, gfp_t mem_flags); struct xhci_command *xhci_alloc_command_with_ctx(struct xhci_hcd *xhci, -- cgit v1.2.3 From 04d21f7219acec66751f5512aa8a69f528c5b36a Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:26 +0200 Subject: xhci: prevent a theoretical endless loop while preparing rings. xhci driver links together segments in a ring buffer by turning the last TRB of a segment into a link TRB, pointing to the beginning of the next segment. If the first TRB of every segment for some unknown reason is a link TRB pointing to the next segment, then prepare_ring() loops indefinitely. This isn't something the xhci driver would do. xHC hardware has access to these rings, it sholdn't be writing link TRBs either, but with broken xHC hardware this could in theory be possible. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-10-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 54fcb907b2ba..2ef55484317e 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2952,6 +2952,7 @@ static int prepare_ring(struct xhci_hcd *xhci, struct xhci_ring *ep_ring, u32 ep_state, unsigned int num_trbs, gfp_t mem_flags) { unsigned int num_trbs_needed; + unsigned int link_trb_count = 0; /* Make sure the endpoint has been added to xHC schedule */ switch (ep_state) { @@ -3023,6 +3024,12 @@ static int prepare_ring(struct xhci_hcd *xhci, struct xhci_ring *ep_ring, ep_ring->enq_seg = ep_ring->enq_seg->next; ep_ring->enqueue = ep_ring->enq_seg->trbs; + + /* prevent infinite loop if all first trbs are link trbs */ + if (link_trb_count++ > ep_ring->num_segs) { + xhci_warn(xhci, "Ring is an endless link TRB loop\n"); + return -EINVAL; + } } return 0; } -- cgit v1.2.3 From 296fcdab3321de0aca7a033e4469f4a2dd55fe96 Mon Sep 17 00:00:00 2001 From: Lalithambika Krishna Kumar Date: Fri, 29 Jan 2021 15:00:27 +0200 Subject: xhci: check slot_id is valid before gathering slot info Check that the slot_id that we dug out from command completion event TRB, is valid before using it to identify the slot associated with the command that generated the event. Signed-off-by: Lalithambika Krishna Kumar Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-11-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 2ef55484317e..69c7c5ab1565 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1430,7 +1430,7 @@ time_out_completed: static void handle_cmd_completion(struct xhci_hcd *xhci, struct xhci_event_cmd *event) { - int slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags)); + unsigned int slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags)); u64 cmd_dma; dma_addr_t cmd_dequeue_dma; u32 cmd_comp_code; @@ -1438,6 +1438,11 @@ static void handle_cmd_completion(struct xhci_hcd *xhci, struct xhci_command *cmd; u32 cmd_type; + if (slot_id >= MAX_HC_SLOTS) { + xhci_warn(xhci, "Invalid slot_id %u\n", slot_id); + return; + } + cmd_dma = le64_to_cpu(event->cmd_trb); cmd_trb = xhci->cmd_ring->dequeue; -- cgit v1.2.3 From 55f6153d8cc8eff0852d108f80087fdf41dc2169 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:28 +0200 Subject: xhci: remove extra loop in interrupt context When finishing a TD we walk the endpoint dequeue trb pointer until it matches the last TRB of the TD. TDs can contain over 100 TRBs, meaning we call a function 100 times, do a few comparisons and increase a couple values for each of these calls, all in interrupt context. This can all be avoided by adding a pointer to the last TRB segment, and a number of TRBs in the TD. So instead of walking through each TRB just set the new dequeue segment, pointer, and number of free TRBs directly. Getting rid of the while loop also reduces the risk of getting stuck in a infinite loop in the interrupt handler. Loop relied on valid matching dequeue and last_trb values to break. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-12-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 21 ++++++++++++++------- drivers/usb/host/xhci.h | 2 ++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 69c7c5ab1565..372a3c98db97 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2041,8 +2041,9 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, EP_HARD_RESET); } else { /* Update ring dequeue pointer */ - while (ep_ring->dequeue != td->last_trb) - inc_deq(xhci, ep_ring); + ep_ring->dequeue = td->last_trb; + ep_ring->deq_seg = td->last_trb_seg; + ep_ring->num_trbs_free += td->num_trbs - 1; inc_deq(xhci, ep_ring); } @@ -2263,8 +2264,9 @@ static int skip_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, frame->actual_length = 0; /* Update ring dequeue pointer */ - while (ep->ring->dequeue != td->last_trb) - inc_deq(xhci, ep->ring); + ep->ring->dequeue = td->last_trb; + ep->ring->deq_seg = td->last_trb_seg; + ep->ring->num_trbs_free += td->num_trbs - 1; inc_deq(xhci, ep->ring); return xhci_td_cleanup(xhci, td, ep->ring, status); @@ -3420,7 +3422,7 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, field |= TRB_IOC; more_trbs_coming = false; td->last_trb = ring->enqueue; - + td->last_trb_seg = ring->enq_seg; if (xhci_urb_suitable_for_idt(urb)) { memcpy(&send_addr, urb->transfer_buffer, trb_buff_len); @@ -3446,7 +3448,7 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, upper_32_bits(send_addr), length_field, field); - + td->num_trbs++; addr += trb_buff_len; sent_len = trb_buff_len; @@ -3470,8 +3472,10 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, ep_index, urb->stream_id, 1, urb, 1, mem_flags); urb_priv->td[1].last_trb = ring->enqueue; + urb_priv->td[1].last_trb_seg = ring->enq_seg; field = TRB_TYPE(TRB_NORMAL) | ring->cycle_state | TRB_IOC; queue_trb(xhci, ring, 0, 0, 0, TRB_INTR_TARGET(0), field); + urb_priv->td[1].num_trbs++; } check_trb_math(urb, enqd_len); @@ -3522,6 +3526,7 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, urb_priv = urb->hcpriv; td = &urb_priv->td[0]; + td->num_trbs = num_trbs; /* * Don't give the first TRB to the hardware (by toggling the cycle bit) @@ -3594,6 +3599,7 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, /* Save the DMA address of the last TRB in the TD */ td->last_trb = ep_ring->enqueue; + td->last_trb_seg = ep_ring->enq_seg; /* Queue status TRB - see Table 7 and sections 4.11.2.2 and 6.4.1.2.3 */ /* If the device sent data, the status stage is an OUT transfer */ @@ -3838,7 +3844,7 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, goto cleanup; } td = &urb_priv->td[i]; - + td->num_trbs = trbs_per_td; /* use SIA as default, if frame id is used overwrite it */ sia_frame_id = TRB_SIA; if (!(urb->transfer_flags & URB_ISO_ASAP) && @@ -3881,6 +3887,7 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, } else { more_trbs_coming = false; td->last_trb = ep_ring->enqueue; + td->last_trb_seg = ep_ring->enq_seg; field |= TRB_IOC; if (trb_block_event_intr(xhci, num_tds, i)) field |= TRB_BEI; diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index d8a84455aabf..b28a2d9fe35e 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1546,9 +1546,11 @@ struct xhci_td { struct xhci_segment *start_seg; union xhci_trb *first_trb; union xhci_trb *last_trb; + struct xhci_segment *last_trb_seg; struct xhci_segment *bounce_seg; /* actual_length of the URB has already been set */ bool urb_length_set; + unsigned int num_trbs; }; /* xHCI command default timeout value */ -- cgit v1.2.3 From 0353810a047ef0101af01665f60597d7f5cc6b02 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:29 +0200 Subject: xhci: avoid DMA double fetch when reading event trb type. Instead of re-reading, masking and endianness correcting the same trb several times to get the trb type from an event, just do it once and store it in a local variable. Also pass the trb_type directly to the vendor specific event handler, avoiding one more similar read. In addition to the security benefit this also cleans up the code and helps readability. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-13-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 27 ++++++++++++--------------- drivers/usb/host/xhci.h | 2 +- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 372a3c98db97..a078f465770f 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1564,11 +1564,8 @@ event_handled: } static void handle_vendor_event(struct xhci_hcd *xhci, - union xhci_trb *event) + union xhci_trb *event, u32 trb_type) { - u32 trb_type; - - trb_type = TRB_FIELD_TO_TYPE(le32_to_cpu(event->generic.field[3])); xhci_dbg(xhci, "Vendor specific event TRB type = %u\n", trb_type); if (trb_type == TRB_NEC_CMD_COMP && (xhci->quirks & XHCI_NEC_HOST)) handle_cmd_completion(xhci, &event->event_cmd); @@ -2733,6 +2730,7 @@ static int xhci_handle_event(struct xhci_hcd *xhci) { union xhci_trb *event; int update_ptrs = 1; + u32 trb_type; int ret; /* Event ring hasn't been allocated yet. */ @@ -2754,31 +2752,30 @@ static int xhci_handle_event(struct xhci_hcd *xhci) * speculative reads of the event's flags/data below. */ rmb(); + trb_type = TRB_FIELD_TO_TYPE(le32_to_cpu(event->event_cmd.flags)); /* FIXME: Handle more event types. */ - switch (le32_to_cpu(event->event_cmd.flags) & TRB_TYPE_BITMASK) { - case TRB_TYPE(TRB_COMPLETION): + + switch (trb_type) { + case TRB_COMPLETION: handle_cmd_completion(xhci, &event->event_cmd); break; - case TRB_TYPE(TRB_PORT_STATUS): + case TRB_PORT_STATUS: handle_port_status(xhci, event); update_ptrs = 0; break; - case TRB_TYPE(TRB_TRANSFER): + case TRB_TRANSFER: ret = handle_tx_event(xhci, &event->trans_event); if (ret >= 0) update_ptrs = 0; break; - case TRB_TYPE(TRB_DEV_NOTE): + case TRB_DEV_NOTE: handle_device_notification(xhci, event); break; default: - if ((le32_to_cpu(event->event_cmd.flags) & TRB_TYPE_BITMASK) >= - TRB_TYPE(48)) - handle_vendor_event(xhci, event); + if (trb_type >= TRB_VENDOR_DEFINED_LOW) + handle_vendor_event(xhci, event, trb_type); else - xhci_warn(xhci, "ERROR unknown event type %d\n", - TRB_FIELD_TO_TYPE( - le32_to_cpu(event->event_cmd.flags))); + xhci_warn(xhci, "ERROR unknown event type %d\n", trb_type); } /* Any of the above functions may drop and re-acquire the lock, so check * to make sure a watchdog timer didn't mark the host as non-responsive. diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index b28a2d9fe35e..87af91447985 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1419,7 +1419,7 @@ union xhci_trb { /* MFINDEX Wrap Event - microframe counter wrapped */ #define TRB_MFINDEX_WRAP 39 /* TRB IDs 40-47 reserved, 48-63 is vendor-defined */ - +#define TRB_VENDOR_DEFINED_LOW 48 /* Nec vendor-specific command completion event. */ #define TRB_NEC_CMD_COMP 48 /* Get NEC firmware revision. */ -- cgit v1.2.3 From c716e8a5fada15df495aa4891f4f22e51b95de11 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:30 +0200 Subject: xhci: Check link TRBs when updating ring enqueue and dequeue pointers. xhci driver relies on link TRBs existing in the correct places in TRB ring buffers shared with the host controller. The controller should not modify these link TRBs, but in theory a faulty xHC could do it. Add some basic sanity checks to avoid infinite loops in interrupt handler, or accessing unallocated memory outside a ring segment due to missing or misplaced link TRBs. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-14-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index a078f465770f..2cbdc4537e73 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -151,10 +151,11 @@ static void next_trb(struct xhci_hcd *xhci, /* * See Cycle bit rules. SW is the consumer for the event ring only. - * Don't make a ring full of link TRBs. That would be dumb and this would loop. */ void inc_deq(struct xhci_hcd *xhci, struct xhci_ring *ring) { + unsigned int link_trb_count = 0; + /* event ring doesn't have link trbs, check for last trb */ if (ring->type == TYPE_EVENT) { if (!last_trb_on_seg(ring->deq_seg, ring->dequeue)) { @@ -170,14 +171,23 @@ void inc_deq(struct xhci_hcd *xhci, struct xhci_ring *ring) /* All other rings have link trbs */ if (!trb_is_link(ring->dequeue)) { - ring->dequeue++; - ring->num_trbs_free++; + if (last_trb_on_seg(ring->deq_seg, ring->dequeue)) { + xhci_warn(xhci, "Missing link TRB at end of segment\n"); + } else { + ring->dequeue++; + ring->num_trbs_free++; + } } + while (trb_is_link(ring->dequeue)) { ring->deq_seg = ring->deq_seg->next; ring->dequeue = ring->deq_seg->trbs; - } + if (link_trb_count++ > ring->num_segs) { + xhci_warn(xhci, "Ring is an endless link TRB loop\n"); + break; + } + } out: trace_xhci_inc_deq(ring); @@ -186,7 +196,6 @@ out: /* * See Cycle bit rules. SW is the consumer for the event ring only. - * Don't make a ring full of link TRBs. That would be dumb and this would loop. * * If we've just enqueued a TRB that is in the middle of a TD (meaning the * chain bit is set), then set the chain bit in all the following link TRBs. @@ -206,11 +215,18 @@ static void inc_enq(struct xhci_hcd *xhci, struct xhci_ring *ring, { u32 chain; union xhci_trb *next; + unsigned int link_trb_count = 0; chain = le32_to_cpu(ring->enqueue->generic.field[3]) & TRB_CHAIN; /* If this is not event ring, there is one less usable TRB */ if (!trb_is_link(ring->enqueue)) ring->num_trbs_free--; + + if (last_trb_on_seg(ring->enq_seg, ring->enqueue)) { + xhci_err(xhci, "Tried to move enqueue past ring segment\n"); + return; + } + next = ++(ring->enqueue); /* Update the dequeue pointer further if that was a link TRB */ @@ -247,6 +263,11 @@ static void inc_enq(struct xhci_hcd *xhci, struct xhci_ring *ring, ring->enq_seg = ring->enq_seg->next; ring->enqueue = ring->enq_seg->trbs; next = ring->enqueue; + + if (link_trb_count++ > ring->num_segs) { + xhci_warn(xhci, "%s: Ring link TRB loop\n", __func__); + break; + } } trace_xhci_inc_enq(ring); @@ -3035,6 +3056,12 @@ static int prepare_ring(struct xhci_hcd *xhci, struct xhci_ring *ep_ring, return -EINVAL; } } + + if (last_trb_on_seg(ep_ring->enq_seg, ep_ring->enqueue)) { + xhci_warn(xhci, "Missing link TRB at end of ring segment\n"); + return -EINVAL; + } + return 0; } -- cgit v1.2.3 From b05dadb28f8779898229e399c21e5192c24beaf2 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:31 +0200 Subject: xhci: flush endpoint start to reduce race risk with stop endpoint command. Stop endpoint command fails with "context state error" if the endpoint is already stopped. This case was observed when a previous URB cancel had just completed and rang the doorbell to restart the ring, when a new URB cancel queued a stop endpoint command. >From xHC hardware pov the endpoint had not yet started, so the stop endpoint command failed with context state error. Right after this the doorbell ring took effect and ring was restarted. Interrupt handler saw a stop endpoint command completion event with "context state error" and discovered that the ring was back up in running state. flushing the write reduces these cases in stress testing, but does not completely remove the issue. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-15-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 2cbdc4537e73..d9714cea6dfa 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -429,9 +429,8 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, trace_xhci_ring_ep_doorbell(slot_id, DB_VALUE(ep_index, stream_id)); writel(DB_VALUE(ep_index, stream_id), db_addr); - /* The CPU has better things to do at this point than wait for a - * write-posting flush. It'll get there soon enough. - */ + /* flush the write */ + readl(db_addr); } /* Ring the doorbell for any rings with pending URBs */ -- cgit v1.2.3 From d8ac95001bea683d2088acb3e61613a27b8d2d5f Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:32 +0200 Subject: xhci: Add xhci_reset_halted_ep() helper function Create a separate helper function to issue reset endpont commands to clear halted endpoints. This is useful for cases where a halted endpoint is discovered while completing another command, and the endpoint halt needs to be cleared with a endpoint reset first. No functional changes Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-16-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index d9714cea6dfa..7011b1640a35 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -749,6 +749,26 @@ static void xhci_unmap_td_bounce_buffer(struct xhci_hcd *xhci, seg->bounce_offs = 0; } +static int xhci_reset_halted_ep(struct xhci_hcd *xhci, unsigned int slot_id, + unsigned int ep_index, enum xhci_ep_reset_type reset_type) +{ + struct xhci_command *command; + int ret = 0; + + command = xhci_alloc_command(xhci, false, GFP_ATOMIC); + if (!command) { + ret = -ENOMEM; + goto done; + } + + ret = xhci_queue_reset_ep(xhci, command, slot_id, ep_index, reset_type); +done: + if (ret) + xhci_err(xhci, "ERROR queuing reset endpoint for slot %d ep_index %d, %d\n", + slot_id, ep_index, ret); + return ret; +} + /* * When we get a command completion for a Stop Endpoint Command, we need to * unlink any cancelled TDs from the ring. There are two ways to do that: @@ -1907,8 +1927,9 @@ static void xhci_cleanup_halted_endpoint(struct xhci_hcd *xhci, struct xhci_td *td, enum xhci_ep_reset_type reset_type) { - struct xhci_command *command; unsigned int slot_id = ep->vdev->slot_id; + int err; + /* * Avoid resetting endpoint if link is inactive. Can cause host hang. * Device will be reset soon to recover the link so don't do anything @@ -1916,13 +1937,11 @@ static void xhci_cleanup_halted_endpoint(struct xhci_hcd *xhci, if (ep->vdev->flags & VDEV_PORT_ERROR) return; - command = xhci_alloc_command(xhci, false, GFP_ATOMIC); - if (!command) - return; - ep->ep_state |= EP_HALTED; - xhci_queue_reset_ep(xhci, command, slot_id, ep->ep_index, reset_type); + err = xhci_reset_halted_ep(xhci, slot_id, ep->ep_index, reset_type); + if (err) + return; if (reset_type == EP_HARD_RESET) { ep->ep_state |= EP_HARD_CLEAR_TOGGLE; -- cgit v1.2.3 From 69eaf9e79fa7c7ff4b1eb626493ce5a81e467520 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:33 +0200 Subject: xhci: move xhci_td_cleanup so it can be called by more functions No funtional changes Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-17-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 92 ++++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 7011b1640a35..044c1f3176e1 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -749,6 +749,52 @@ static void xhci_unmap_td_bounce_buffer(struct xhci_hcd *xhci, seg->bounce_offs = 0; } +static int xhci_td_cleanup(struct xhci_hcd *xhci, struct xhci_td *td, + struct xhci_ring *ep_ring, int *status) +{ + struct urb *urb = NULL; + + /* Clean up the endpoint's TD list */ + urb = td->urb; + + /* if a bounce buffer was used to align this td then unmap it */ + xhci_unmap_td_bounce_buffer(xhci, ep_ring, td); + + /* Do one last check of the actual transfer length. + * If the host controller said we transferred more data than the buffer + * length, urb->actual_length will be a very big number (since it's + * unsigned). Play it safe and say we didn't transfer anything. + */ + if (urb->actual_length > urb->transfer_buffer_length) { + xhci_warn(xhci, "URB req %u and actual %u transfer length mismatch\n", + urb->transfer_buffer_length, urb->actual_length); + urb->actual_length = 0; + *status = 0; + } + list_del_init(&td->td_list); + /* Was this TD slated to be cancelled but completed anyway? */ + if (!list_empty(&td->cancelled_td_list)) + list_del_init(&td->cancelled_td_list); + + inc_td_cnt(urb); + /* Giveback the urb when all the tds are completed */ + if (last_td_in_urb(td)) { + if ((urb->actual_length != urb->transfer_buffer_length && + (urb->transfer_flags & URB_SHORT_NOT_OK)) || + (*status != 0 && !usb_endpoint_xfer_isoc(&urb->ep->desc))) + xhci_dbg(xhci, "Giveback URB %p, len = %d, expected = %d, status = %d\n", + urb, urb->actual_length, + urb->transfer_buffer_length, *status); + + /* set isoc urb status to 0 just as EHCI, UHCI, and OHCI */ + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) + *status = 0; + xhci_giveback_urb_in_irq(xhci, td, *status); + } + + return 0; +} + static int xhci_reset_halted_ep(struct xhci_hcd *xhci, unsigned int slot_id, unsigned int ep_index, enum xhci_ep_reset_type reset_type) { @@ -1991,52 +2037,6 @@ int xhci_is_vendor_info_code(struct xhci_hcd *xhci, unsigned int trb_comp_code) return 0; } -static int xhci_td_cleanup(struct xhci_hcd *xhci, struct xhci_td *td, - struct xhci_ring *ep_ring, int *status) -{ - struct urb *urb = NULL; - - /* Clean up the endpoint's TD list */ - urb = td->urb; - - /* if a bounce buffer was used to align this td then unmap it */ - xhci_unmap_td_bounce_buffer(xhci, ep_ring, td); - - /* Do one last check of the actual transfer length. - * If the host controller said we transferred more data than the buffer - * length, urb->actual_length will be a very big number (since it's - * unsigned). Play it safe and say we didn't transfer anything. - */ - if (urb->actual_length > urb->transfer_buffer_length) { - xhci_warn(xhci, "URB req %u and actual %u transfer length mismatch\n", - urb->transfer_buffer_length, urb->actual_length); - urb->actual_length = 0; - *status = 0; - } - list_del_init(&td->td_list); - /* Was this TD slated to be cancelled but completed anyway? */ - if (!list_empty(&td->cancelled_td_list)) - list_del_init(&td->cancelled_td_list); - - inc_td_cnt(urb); - /* Giveback the urb when all the tds are completed */ - if (last_td_in_urb(td)) { - if ((urb->actual_length != urb->transfer_buffer_length && - (urb->transfer_flags & URB_SHORT_NOT_OK)) || - (*status != 0 && !usb_endpoint_xfer_isoc(&urb->ep->desc))) - xhci_dbg(xhci, "Giveback URB %p, len = %d, expected = %d, status = %d\n", - urb, urb->actual_length, - urb->transfer_buffer_length, *status); - - /* set isoc urb status to 0 just as EHCI, UHCI, and OHCI */ - if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) - *status = 0; - xhci_giveback_urb_in_irq(xhci, td, *status); - } - - return 0; -} - static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, struct xhci_transfer_event *event, struct xhci_virt_ep *ep, int *status) -- cgit v1.2.3 From e1a298390e987ddeb767cad18d913cb2782fda15 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:34 +0200 Subject: xhci: use xhci_td_cleanup() helper when giving back cancelled URBs use the existing xhci_td_cleanup() to give back cancelled TDs when a ring is stopped. A minor change to make sure we don't try to remove an already removed td from the list is needed as cancelled TDs are already removed from the td_list immediatelty when it's cancelled. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-18-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 044c1f3176e1..47597af50b5d 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -771,8 +771,10 @@ static int xhci_td_cleanup(struct xhci_hcd *xhci, struct xhci_td *td, urb->actual_length = 0; *status = 0; } - list_del_init(&td->td_list); - /* Was this TD slated to be cancelled but completed anyway? */ + /* TD might be removed from td_list if we are giving back a cancelled URB */ + if (!list_empty(&td->td_list)) + list_del_init(&td->td_list); + /* Giving back a cancelled URB, or if a slated TD completed anyway */ if (!list_empty(&td->cancelled_td_list)) list_del_init(&td->cancelled_td_list); @@ -944,15 +946,11 @@ remove_finished_td: struct xhci_td, cancelled_td_list); list_del_init(&cur_td->cancelled_td_list); - /* Clean up the cancelled URB */ /* Doesn't matter what we pass for status, since the core will * just overwrite it (because the URB has been unlinked). */ ep_ring = xhci_urb_to_transfer_ring(xhci, cur_td->urb); - xhci_unmap_td_bounce_buffer(xhci, ep_ring, cur_td); - inc_td_cnt(cur_td->urb); - if (last_td_in_urb(cur_td)) - xhci_giveback_urb_in_irq(xhci, cur_td, 0); + xhci_td_cleanup(xhci, cur_td, ep_ring, 0); /* Stop processing the cancelled list if the watchdog timer is * running. -- cgit v1.2.3 From a6ccd1fd4bd4fca37eaa3d76bef940d6332919bc Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:35 +0200 Subject: xhci: store TD status in the td struct instead of passing it along In cases where the TD can't be given back in current handler we want to be able to store it until its time to return the TD. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-19-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 56 +++++++++++++++++++++++--------------------- drivers/usb/host/xhci.h | 1 + 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 47597af50b5d..26b2d1e9b55a 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -750,7 +750,7 @@ static void xhci_unmap_td_bounce_buffer(struct xhci_hcd *xhci, } static int xhci_td_cleanup(struct xhci_hcd *xhci, struct xhci_td *td, - struct xhci_ring *ep_ring, int *status) + struct xhci_ring *ep_ring, int status) { struct urb *urb = NULL; @@ -769,7 +769,7 @@ static int xhci_td_cleanup(struct xhci_hcd *xhci, struct xhci_td *td, xhci_warn(xhci, "URB req %u and actual %u transfer length mismatch\n", urb->transfer_buffer_length, urb->actual_length); urb->actual_length = 0; - *status = 0; + status = 0; } /* TD might be removed from td_list if we are giving back a cancelled URB */ if (!list_empty(&td->td_list)) @@ -783,15 +783,15 @@ static int xhci_td_cleanup(struct xhci_hcd *xhci, struct xhci_td *td, if (last_td_in_urb(td)) { if ((urb->actual_length != urb->transfer_buffer_length && (urb->transfer_flags & URB_SHORT_NOT_OK)) || - (*status != 0 && !usb_endpoint_xfer_isoc(&urb->ep->desc))) + (status != 0 && !usb_endpoint_xfer_isoc(&urb->ep->desc))) xhci_dbg(xhci, "Giveback URB %p, len = %d, expected = %d, status = %d\n", urb, urb->actual_length, - urb->transfer_buffer_length, *status); + urb->transfer_buffer_length, status); /* set isoc urb status to 0 just as EHCI, UHCI, and OHCI */ if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) - *status = 0; - xhci_giveback_urb_in_irq(xhci, td, *status); + status = 0; + xhci_giveback_urb_in_irq(xhci, td, status); } return 0; @@ -2036,8 +2036,7 @@ int xhci_is_vendor_info_code(struct xhci_hcd *xhci, unsigned int trb_comp_code) } static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, - struct xhci_transfer_event *event, - struct xhci_virt_ep *ep, int *status) + struct xhci_transfer_event *event, struct xhci_virt_ep *ep) { struct xhci_ep_ctx *ep_ctx; struct xhci_ring *ep_ring; @@ -2081,7 +2080,7 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, inc_deq(xhci, ep_ring); } - return xhci_td_cleanup(xhci, td, ep_ring, status); + return xhci_td_cleanup(xhci, td, ep_ring, td->status); } /* sum trb lengths from ring dequeue up to stop_trb, _excluding_ stop_trb */ @@ -2104,7 +2103,7 @@ static int sum_trb_lengths(struct xhci_hcd *xhci, struct xhci_ring *ring, */ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td, union xhci_trb *ep_trb, struct xhci_transfer_event *event, - struct xhci_virt_ep *ep, int *status) + struct xhci_virt_ep *ep) { struct xhci_ep_ctx *ep_ctx; u32 trb_comp_code; @@ -2122,13 +2121,13 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td, if (trb_type != TRB_STATUS) { xhci_warn(xhci, "WARN: Success on ctrl %s TRB without IOC set?\n", (trb_type == TRB_DATA) ? "data" : "setup"); - *status = -ESHUTDOWN; + td->status = -ESHUTDOWN; break; } - *status = 0; + td->status = 0; break; case COMP_SHORT_PACKET: - *status = 0; + td->status = 0; break; case COMP_STOPPED_SHORT_PACKET: if (trb_type == TRB_DATA || trb_type == TRB_NORMAL) @@ -2192,7 +2191,7 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td, td->urb->actual_length = requested; finish_td: - return finish_td(xhci, td, event, ep, status); + return finish_td(xhci, td, event, ep); } /* @@ -2200,7 +2199,7 @@ finish_td: */ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, union xhci_trb *ep_trb, struct xhci_transfer_event *event, - struct xhci_virt_ep *ep, int *status) + struct xhci_virt_ep *ep) { struct urb_priv *urb_priv; int idx; @@ -2277,11 +2276,11 @@ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, td->urb->actual_length += frame->actual_length; - return finish_td(xhci, td, event, ep, status); + return finish_td(xhci, td, event, ep); } static int skip_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, - struct xhci_virt_ep *ep, int *status) + struct xhci_virt_ep *ep, int status) { struct urb_priv *urb_priv; struct usb_iso_packet_descriptor *frame; @@ -2311,7 +2310,7 @@ static int skip_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, */ static int process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_td *td, union xhci_trb *ep_trb, struct xhci_transfer_event *event, - struct xhci_virt_ep *ep, int *status) + struct xhci_virt_ep *ep) { struct xhci_slot_ctx *slot_ctx; struct xhci_ring *ep_ring; @@ -2335,13 +2334,13 @@ static int process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_td *td, td->urb->ep->desc.bEndpointAddress, requested, remaining); } - *status = 0; + td->status = 0; break; case COMP_SHORT_PACKET: xhci_dbg(xhci, "ep %#x - asked for %d bytes, %d bytes untransferred\n", td->urb->ep->desc.bEndpointAddress, requested, remaining); - *status = 0; + td->status = 0; break; case COMP_STOPPED_SHORT_PACKET: td->urb->actual_length = remaining; @@ -2355,7 +2354,8 @@ static int process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_td *td, if ((ep_ring->err_count++ > MAX_SOFT_RETRY) || le32_to_cpu(slot_ctx->tt_info) & TT_SLOT) break; - *status = 0; + + td->status = 0; xhci_cleanup_halted_endpoint(xhci, ep, ep_ring->stream_id, td, EP_SOFT_RESET); return 0; @@ -2376,7 +2376,7 @@ finish_td: remaining); td->urb->actual_length = 0; } - return finish_td(xhci, td, event, ep, status); + return finish_td(xhci, td, event, ep); } /* @@ -2677,7 +2677,7 @@ static int handle_tx_event(struct xhci_hcd *xhci, return -ESHUTDOWN; } - skip_isoc_td(xhci, td, ep, &status); + skip_isoc_td(xhci, td, ep, status); goto cleanup; } if (trb_comp_code == COMP_SHORT_PACKET) @@ -2705,6 +2705,7 @@ static int handle_tx_event(struct xhci_hcd *xhci, * endpoint. Otherwise, the endpoint remains stalled * indefinitely. */ + if (trb_is_noop(ep_trb)) { if (trb_comp_code == COMP_STALL_ERROR || xhci_requires_manual_halt_cleanup(xhci, ep_ctx, @@ -2715,14 +2716,15 @@ static int handle_tx_event(struct xhci_hcd *xhci, goto cleanup; } + td->status = status; + /* update the urb's actual_length and give back to the core */ if (usb_endpoint_xfer_control(&td->urb->ep->desc)) - process_ctrl_td(xhci, td, ep_trb, event, ep, &status); + process_ctrl_td(xhci, td, ep_trb, event, ep); else if (usb_endpoint_xfer_isoc(&td->urb->ep->desc)) - process_isoc_td(xhci, td, ep_trb, event, ep, &status); + process_isoc_td(xhci, td, ep_trb, event, ep); else - process_bulk_intr_td(xhci, td, ep_trb, event, ep, - &status); + process_bulk_intr_td(xhci, td, ep_trb, event, ep); cleanup: handling_skipped_tds = ep->skip && trb_comp_code != COMP_MISSED_SERVICE_ERROR && diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 87af91447985..fe0bf77e8cd8 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1542,6 +1542,7 @@ struct xhci_segment { struct xhci_td { struct list_head td_list; struct list_head cancelled_td_list; + int status; struct urb *urb; struct xhci_segment *start_seg; union xhci_trb *first_trb; -- cgit v1.2.3 From 4db356924a50f72a00834ae04f11202d9703faeb Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:36 +0200 Subject: xhci: turn cancelled td cleanup to its own function Refactor handler for stop endpoint command completion. Yank out the part that invalidates cancelled TDs and turn it into a separate function. Invalidating cancelled TDs should be done while the ring is stopped, but not exclusively in the stop endpoint command completeion handler. We will need to invalidate TDs after resetting endpoints as well. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-20-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 108 +++++++++++++++++++++---------------------- 1 file changed, 53 insertions(+), 55 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 26b2d1e9b55a..2101ec9cbed5 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -817,6 +817,58 @@ done: return ret; } +/* + * Fix up the ep ring first, so HW stops executing cancelled TDs. + * We have the xHCI lock, so nothing can modify this list until we drop it. + * We're also in the event handler, so we can't get re-interrupted if another + * Stop Endpoint command completes. + */ + +static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep, + struct xhci_dequeue_state *deq_state) +{ + struct xhci_hcd *xhci; + struct xhci_td *td = NULL; + struct xhci_td *tmp_td = NULL; + struct xhci_ring *ring; + u64 hw_deq; + + xhci = ep->xhci; + + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) { + xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, + "Removing canceled TD starting at 0x%llx (dma).", + (unsigned long long)xhci_trb_virt_to_dma( + td->start_seg, td->first_trb)); + list_del_init(&td->td_list); + ring = xhci_urb_to_transfer_ring(xhci, td->urb); + if (!ring) { + xhci_warn(xhci, "WARN Cancelled URB %p has invalid stream ID %u.\n", + td->urb, td->urb->stream_id); + continue; + } + /* + * If ring stopped on the TD we need to cancel, then we have to + * move the xHC endpoint ring dequeue pointer past this TD. + */ + hw_deq = xhci_get_hw_deq(xhci, ep->vdev, ep->ep_index, + td->urb->stream_id); + hw_deq &= ~0xf; + + if (trb_in_td(xhci, td->start_seg, td->first_trb, + td->last_trb, hw_deq, false)) { + xhci_find_new_dequeue_state(xhci, ep->vdev->slot_id, + ep->ep_index, + td->urb->stream_id, + td, deq_state); + } else { + td_to_noop(xhci, ring, td, false); + } + + } + return 0; +} + /* * When we get a command completion for a Stop Endpoint Command, we need to * unlink any cancelled TDs from the ring. There are two ways to do that: @@ -837,7 +889,6 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, struct xhci_td *last_unlinked_td; struct xhci_ep_ctx *ep_ctx; struct xhci_virt_device *vdev; - u64 hw_deq; struct xhci_dequeue_state deq_state; if (unlikely(TRB_TO_SUSPEND_PORT(le32_to_cpu(trb->generic.field[3])))) { @@ -868,60 +919,7 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, return; } - /* Fix up the ep ring first, so HW stops executing cancelled TDs. - * We have the xHCI lock, so nothing can modify this list until we drop - * it. We're also in the event handler, so we can't get re-interrupted - * if another Stop Endpoint command completes - */ - list_for_each_entry(cur_td, &ep->cancelled_td_list, cancelled_td_list) { - xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, - "Removing canceled TD starting at 0x%llx (dma).", - (unsigned long long)xhci_trb_virt_to_dma( - cur_td->start_seg, cur_td->first_trb)); - ep_ring = xhci_urb_to_transfer_ring(xhci, cur_td->urb); - if (!ep_ring) { - /* This shouldn't happen unless a driver is mucking - * with the stream ID after submission. This will - * leave the TD on the hardware ring, and the hardware - * will try to execute it, and may access a buffer - * that has already been freed. In the best case, the - * hardware will execute it, and the event handler will - * ignore the completion event for that TD, since it was - * removed from the td_list for that endpoint. In - * short, don't muck with the stream ID after - * submission. - */ - xhci_warn(xhci, "WARN Cancelled URB %p " - "has invalid stream ID %u.\n", - cur_td->urb, - cur_td->urb->stream_id); - goto remove_finished_td; - } - /* - * If we stopped on the TD we need to cancel, then we have to - * move the xHC endpoint ring dequeue pointer past this TD. - */ - hw_deq = xhci_get_hw_deq(xhci, vdev, ep_index, - cur_td->urb->stream_id); - hw_deq &= ~0xf; - - if (trb_in_td(xhci, cur_td->start_seg, cur_td->first_trb, - cur_td->last_trb, hw_deq, false)) { - xhci_find_new_dequeue_state(xhci, slot_id, ep_index, - cur_td->urb->stream_id, - cur_td, &deq_state); - } else { - td_to_noop(xhci, ep_ring, cur_td, false); - } - -remove_finished_td: - /* - * The event handler won't see a completion for this TD anymore, - * so remove it from the endpoint ring's TD list. Keep it in - * the cancelled TD list for URB completion later. - */ - list_del_init(&cur_td->td_list); - } + xhci_invalidate_cancelled_tds(ep, &deq_state); xhci_stop_watchdog_timer_in_irq(xhci, ep); -- cgit v1.2.3 From 7c6c334e6fc8cd99e780fd74cd29687886a81862 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:37 +0200 Subject: xhci: move and rename xhci_cleanup_halted_endpoint() Halted endpoints can be discoverd both when handling transfer events and command completion events. Move code that handles halted endpoints before both of those event handlers. Rename the function to xhci_handle_halted_ep() to better describe what it does. Try to reserve "cleanup" word in function names for last stage cleanup activities. No functional changes Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-21-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 84 +++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 2101ec9cbed5..42639deac4ad 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -817,6 +817,35 @@ done: return ret; } +static void xhci_handle_halted_endpoint(struct xhci_hcd *xhci, + struct xhci_virt_ep *ep, unsigned int stream_id, + struct xhci_td *td, + enum xhci_ep_reset_type reset_type) +{ + unsigned int slot_id = ep->vdev->slot_id; + int err; + + /* + * Avoid resetting endpoint if link is inactive. Can cause host hang. + * Device will be reset soon to recover the link so don't do anything + */ + if (ep->vdev->flags & VDEV_PORT_ERROR) + return; + + ep->ep_state |= EP_HALTED; + + err = xhci_reset_halted_ep(xhci, slot_id, ep->ep_index, reset_type); + if (err) + return; + + if (reset_type == EP_HARD_RESET) { + ep->ep_state |= EP_HARD_CLEAR_TOGGLE; + xhci_cleanup_stalled_ring(xhci, slot_id, ep->ep_index, stream_id, + td); + } + xhci_ring_cmd_db(xhci); +} + /* * Fix up the ep ring first, so HW stops executing cancelled TDs. * We have the xHCI lock, so nothing can modify this list until we drop it. @@ -1964,35 +1993,6 @@ static void xhci_clear_hub_tt_buffer(struct xhci_hcd *xhci, struct xhci_td *td, } } -static void xhci_cleanup_halted_endpoint(struct xhci_hcd *xhci, - struct xhci_virt_ep *ep, unsigned int stream_id, - struct xhci_td *td, - enum xhci_ep_reset_type reset_type) -{ - unsigned int slot_id = ep->vdev->slot_id; - int err; - - /* - * Avoid resetting endpoint if link is inactive. Can cause host hang. - * Device will be reset soon to recover the link so don't do anything - */ - if (ep->vdev->flags & VDEV_PORT_ERROR) - return; - - ep->ep_state |= EP_HALTED; - - err = xhci_reset_halted_ep(xhci, slot_id, ep->ep_index, reset_type); - if (err) - return; - - if (reset_type == EP_HARD_RESET) { - ep->ep_state |= EP_HARD_CLEAR_TOGGLE; - xhci_cleanup_stalled_ring(xhci, slot_id, ep->ep_index, stream_id, - td); - } - xhci_ring_cmd_db(xhci); -} - /* Check if an error has halted the endpoint ring. The class driver will * cleanup the halt for a non-default control endpoint if we indicate a stall. * However, a babble and other errors also halt the endpoint ring, and the class @@ -2068,7 +2068,8 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, */ if ((ep->ep_index != 0) || (trb_comp_code != COMP_STALL_ERROR)) xhci_clear_hub_tt_buffer(xhci, td, ep); - xhci_cleanup_halted_endpoint(xhci, ep, ep_ring->stream_id, td, + + xhci_handle_halted_endpoint(xhci, ep, ep_ring->stream_id, td, EP_HARD_RESET); } else { /* Update ring dequeue pointer */ @@ -2354,8 +2355,9 @@ static int process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_td *td, break; td->status = 0; - xhci_cleanup_halted_endpoint(xhci, ep, ep_ring->stream_id, td, - EP_SOFT_RESET); + + xhci_handle_halted_endpoint(xhci, ep, ep_ring->stream_id, td, + EP_SOFT_RESET); return 0; default: /* do nothing */ @@ -2428,8 +2430,8 @@ static int handle_tx_event(struct xhci_hcd *xhci, case COMP_USB_TRANSACTION_ERROR: case COMP_INVALID_STREAM_TYPE_ERROR: case COMP_INVALID_STREAM_ID_ERROR: - xhci_cleanup_halted_endpoint(xhci, ep, 0, NULL, - EP_SOFT_RESET); + xhci_handle_halted_endpoint(xhci, ep, 0, NULL, + EP_SOFT_RESET); goto cleanup; case COMP_RING_UNDERRUN: case COMP_RING_OVERRUN: @@ -2613,10 +2615,10 @@ static int handle_tx_event(struct xhci_hcd *xhci, if (trb_comp_code == COMP_STALL_ERROR || xhci_requires_manual_halt_cleanup(xhci, ep_ctx, trb_comp_code)) { - xhci_cleanup_halted_endpoint(xhci, ep, - ep_ring->stream_id, - NULL, - EP_HARD_RESET); + xhci_handle_halted_endpoint(xhci, ep, + ep_ring->stream_id, + NULL, + EP_HARD_RESET); } goto cleanup; } @@ -2708,9 +2710,9 @@ static int handle_tx_event(struct xhci_hcd *xhci, if (trb_comp_code == COMP_STALL_ERROR || xhci_requires_manual_halt_cleanup(xhci, ep_ctx, trb_comp_code)) - xhci_cleanup_halted_endpoint(xhci, ep, - ep_ring->stream_id, - td, EP_HARD_RESET); + xhci_handle_halted_endpoint(xhci, ep, + ep_ring->stream_id, + td, EP_HARD_RESET); goto cleanup; } -- cgit v1.2.3 From 674f8438c12125d6b4fe51d44b9316bb02b286b5 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:38 +0200 Subject: xhci: split handling halted endpoints into two steps Don't queue both a reset endpoint command and a set TR deq command at once when handling a halted endpoint. split this into two steps. Initially only queue a reset endpoint command, and then if needed queue a set TR deq command in the reset endpoint handler. Note: This removes the RESET_EP_QUIRK handling which was added in commit ac9d8fe7c6a8 ("USB: xhci: Add quirk for Fresco Logic xHCI hardware.") This quirk was added in 2009 for prototype xHCI hardware meant for evaluation purposes only, and should not reach consumers. This hardware could not handle two commands queued at once, and had bad data in the output context after a reset endpoint command. After this patch two command are no longer queued at once, so that part is solved in this rewrite, but the workaround for bad data in the output context solved by issuing an extra configure endpoint command is bluntly removed. Adding this workaround to the new rewrite just adds complexity, and I think it's time to let this quirk go. Print a debug message instead. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-22-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 178 +++++++++++++++++++++---------------------- drivers/usb/host/xhci.c | 94 ++--------------------- drivers/usb/host/xhci.h | 8 ++ 3 files changed, 101 insertions(+), 179 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 42639deac4ad..0788ee977557 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -797,6 +797,30 @@ static int xhci_td_cleanup(struct xhci_hcd *xhci, struct xhci_td *td, return 0; } + +/* Complete the cancelled URBs we unlinked from td_list. */ +static void xhci_giveback_invalidated_tds(struct xhci_virt_ep *ep) +{ + struct xhci_ring *ring; + struct xhci_td *td, *tmp_td; + + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, + cancelled_td_list) { + + /* + * Doesn't matter what we pass for status, since the core will + * just overwrite it (because the URB has been unlinked). + */ + ring = xhci_urb_to_transfer_ring(ep->xhci, td->urb); + + if (td->cancel_status == TD_CLEARED) + xhci_td_cleanup(ep->xhci, td, ring, 0); + + if (ep->xhci->xhc_state & XHCI_STATE_DYING) + return; + } +} + static int xhci_reset_halted_ep(struct xhci_hcd *xhci, unsigned int slot_id, unsigned int ep_index, enum xhci_ep_reset_type reset_type) { @@ -834,15 +858,19 @@ static void xhci_handle_halted_endpoint(struct xhci_hcd *xhci, ep->ep_state |= EP_HALTED; + /* add td to cancelled list and let reset ep handler take care of it */ + if (reset_type == EP_HARD_RESET) { + ep->ep_state |= EP_HARD_CLEAR_TOGGLE; + if (td && list_empty(&td->cancelled_td_list)) { + list_add_tail(&td->cancelled_td_list, &ep->cancelled_td_list); + td->cancel_status = TD_HALTED; + } + } + err = xhci_reset_halted_ep(xhci, slot_id, ep->ep_index, reset_type); if (err) return; - if (reset_type == EP_HARD_RESET) { - ep->ep_state |= EP_HARD_CLEAR_TOGGLE; - xhci_cleanup_stalled_ring(xhci, slot_id, ep->ep_index, stream_id, - td); - } xhci_ring_cmd_db(xhci); } @@ -851,16 +879,20 @@ static void xhci_handle_halted_endpoint(struct xhci_hcd *xhci, * We have the xHCI lock, so nothing can modify this list until we drop it. * We're also in the event handler, so we can't get re-interrupted if another * Stop Endpoint command completes. + * + * only call this when ring is not in a running state */ -static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep, - struct xhci_dequeue_state *deq_state) +static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) { struct xhci_hcd *xhci; struct xhci_td *td = NULL; struct xhci_td *tmp_td = NULL; + struct xhci_td *cached_td = NULL; struct xhci_ring *ring; + struct xhci_dequeue_state deq_state; u64 hw_deq; + unsigned int slot_id = ep->vdev->slot_id; xhci = ep->xhci; @@ -886,14 +918,28 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep, if (trb_in_td(xhci, td->start_seg, td->first_trb, td->last_trb, hw_deq, false)) { - xhci_find_new_dequeue_state(xhci, ep->vdev->slot_id, - ep->ep_index, - td->urb->stream_id, - td, deq_state); + switch (td->cancel_status) { + case TD_CLEARED: /* TD is already no-op */ + case TD_CLEARING_CACHE: /* set TR deq command already queued */ + break; + case TD_DIRTY: /* TD is cached, clear it */ + case TD_HALTED: + /* FIXME stream case, several stopped rings */ + cached_td = td; + break; + } } else { td_to_noop(xhci, ring, td, false); + td->cancel_status = TD_CLEARED; } - + } + if (cached_td) { + cached_td->cancel_status = TD_CLEARING_CACHE; + xhci_find_new_dequeue_state(xhci, slot_id, ep->ep_index, + cached_td->urb->stream_id, + cached_td, &deq_state); + xhci_queue_new_dequeue_state(xhci, slot_id, ep->ep_index, + &deq_state); } return 0; } @@ -912,81 +958,32 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, union xhci_trb *trb) { unsigned int ep_index; - struct xhci_ring *ep_ring; struct xhci_virt_ep *ep; - struct xhci_td *cur_td = NULL; - struct xhci_td *last_unlinked_td; struct xhci_ep_ctx *ep_ctx; - struct xhci_virt_device *vdev; - struct xhci_dequeue_state deq_state; if (unlikely(TRB_TO_SUSPEND_PORT(le32_to_cpu(trb->generic.field[3])))) { if (!xhci->devs[slot_id]) - xhci_warn(xhci, "Stop endpoint command " - "completion for disabled slot %u\n", - slot_id); + xhci_warn(xhci, "Stop endpoint command completion for disabled slot %u\n", + slot_id); return; } - memset(&deq_state, 0, sizeof(deq_state)); ep_index = TRB_TO_EP_INDEX(le32_to_cpu(trb->generic.field[3])); - ep = xhci_get_virt_ep(xhci, slot_id, ep_index); if (!ep) return; - vdev = ep->vdev; - ep_ctx = xhci_get_ep_ctx(xhci, vdev->out_ctx, ep_index); - trace_xhci_handle_cmd_stop_ep(ep_ctx); - - last_unlinked_td = list_last_entry(&ep->cancelled_td_list, - struct xhci_td, cancelled_td_list); - - if (list_empty(&ep->cancelled_td_list)) { - xhci_stop_watchdog_timer_in_irq(xhci, ep); - ring_doorbell_for_active_rings(xhci, slot_id, ep_index); - return; - } + ep_ctx = xhci_get_ep_ctx(xhci, ep->vdev->out_ctx, ep_index); - xhci_invalidate_cancelled_tds(ep, &deq_state); + trace_xhci_handle_cmd_stop_ep(ep_ctx); + /* will queue a set TR deq if stopped on a cancelled, uncleared TD */ + xhci_invalidate_cancelled_tds(ep); xhci_stop_watchdog_timer_in_irq(xhci, ep); - /* If necessary, queue a Set Transfer Ring Dequeue Pointer command */ - if (deq_state.new_deq_ptr && deq_state.new_deq_seg) { - xhci_queue_new_dequeue_state(xhci, slot_id, ep_index, - &deq_state); - xhci_ring_cmd_db(xhci); - } else { - /* Otherwise ring the doorbell(s) to restart queued transfers */ - ring_doorbell_for_active_rings(xhci, slot_id, ep_index); - } - - /* - * Drop the lock and complete the URBs in the cancelled TD list. - * New TDs to be cancelled might be added to the end of the list before - * we can complete all the URBs for the TDs we already unlinked. - * So stop when we've completed the URB for the last TD we unlinked. - */ - do { - cur_td = list_first_entry(&ep->cancelled_td_list, - struct xhci_td, cancelled_td_list); - list_del_init(&cur_td->cancelled_td_list); - - /* Doesn't matter what we pass for status, since the core will - * just overwrite it (because the URB has been unlinked). - */ - ep_ring = xhci_urb_to_transfer_ring(xhci, cur_td->urb); - xhci_td_cleanup(xhci, cur_td, ep_ring, 0); - - /* Stop processing the cancelled list if the watchdog timer is - * running. - */ - if (xhci->xhc_state & XHCI_STATE_DYING) - return; - } while (cur_td != last_unlinked_td); - - /* Return to the event handler with xhci->lock re-acquired */ + /* Otherwise ring the doorbell(s) to restart queued transfers */ + xhci_giveback_invalidated_tds(ep); + ring_doorbell_for_active_rings(xhci, slot_id, ep_index); } static void xhci_kill_ring_urbs(struct xhci_hcd *xhci, struct xhci_ring *ring) @@ -1202,6 +1199,7 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, struct xhci_virt_ep *ep; struct xhci_ep_ctx *ep_ctx; struct xhci_slot_ctx *slot_ctx; + struct xhci_td *td, *tmp_td; ep_index = TRB_TO_EP_INDEX(le32_to_cpu(trb->generic.field[3])); stream_id = TRB_TO_STREAM_ID(le32_to_cpu(trb->generic.field[2])); @@ -1279,7 +1277,15 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, ep->queued_deq_seg, ep->queued_deq_ptr); } } - + /* HW cached TDs cleared from cache, give them back */ + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, + cancelled_td_list) { + ep_ring = xhci_urb_to_transfer_ring(ep->xhci, td->urb); + if (td->cancel_status == TD_CLEARING_CACHE) { + td->cancel_status = TD_CLEARED; + xhci_td_cleanup(ep->xhci, td, ep_ring, td->status); + } + } cleanup: ep->ep_state &= ~SET_DEQ_PENDING; ep->queued_deq_seg = NULL; @@ -1309,27 +1315,15 @@ static void xhci_handle_cmd_reset_ep(struct xhci_hcd *xhci, int slot_id, xhci_dbg_trace(xhci, trace_xhci_dbg_reset_ep, "Ignoring reset ep completion code of %u", cmd_comp_code); - /* HW with the reset endpoint quirk needs to have a configure endpoint - * command complete before the endpoint can be used. Queue that here - * because the HW can't handle two commands being queued in a row. - */ - if (xhci->quirks & XHCI_RESET_EP_QUIRK) { - struct xhci_command *command; + /* Cleanup cancelled TDs as ep is stopped. May queue a Set TR Deq cmd */ + xhci_invalidate_cancelled_tds(ep); - command = xhci_alloc_command(xhci, false, GFP_ATOMIC); - if (!command) - return; + if (xhci->quirks & XHCI_RESET_EP_QUIRK) + xhci_dbg(xhci, "Note: Removed workaround to queue config ep for this hw"); + /* Clear our internal halted state */ + ep->ep_state &= ~EP_HALTED; - xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, - "Queueing configure endpoint command"); - xhci_queue_configure_endpoint(xhci, command, - xhci->devs[slot_id]->in_ctx->dma, slot_id, - false); - xhci_ring_cmd_db(xhci); - } else { - /* Clear our internal halted state */ - ep->ep_state &= ~EP_HALTED; - } + xhci_giveback_invalidated_tds(ep); /* if this was a soft reset, then restart */ if ((le32_to_cpu(trb->generic.field[3])) & TRB_TSP) @@ -2070,7 +2064,9 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, xhci_clear_hub_tt_buffer(xhci, td, ep); xhci_handle_halted_endpoint(xhci, ep, ep_ring->stream_id, td, - EP_HARD_RESET); + EP_HARD_RESET); + + return 0; /* xhci_handle_halted_endpoint marked td cancelled */ } else { /* Update ring dequeue pointer */ ep_ring->dequeue = td->last_trb; diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index e86940571b4c..f232dc9c172d 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1440,15 +1440,6 @@ static unsigned int xhci_get_endpoint_flag(struct usb_endpoint_descriptor *desc) return 1 << (xhci_get_endpoint_index(desc) + 1); } -/* Find the flag for this endpoint (for use in the control context). Use the - * endpoint index to create a bitmask. The slot context is bit 0, endpoint 0 is - * bit 1, etc. - */ -static unsigned int xhci_get_endpoint_flag_from_index(unsigned int ep_index) -{ - return 1 << (ep_index + 1); -} - /* Compute the last valid endpoint context index. Basically, this is the * endpoint index plus one. For slot contexts with more than valid endpoint, * we find the most significant bit set in the added contexts flags. @@ -1810,7 +1801,12 @@ static int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) for (; i < urb_priv->num_tds; i++) { td = &urb_priv->td[i]; - list_add_tail(&td->cancelled_td_list, &ep->cancelled_td_list); + /* TD can already be on cancelled list if ep halted on it */ + if (list_empty(&td->cancelled_td_list)) { + td->cancel_status = TD_DIRTY; + list_add_tail(&td->cancelled_td_list, + &ep->cancelled_td_list); + } } /* Queue a stop endpoint command, but only if this is @@ -3119,84 +3115,6 @@ static void xhci_setup_input_ctx_for_config_ep(struct xhci_hcd *xhci, ctrl_ctx->add_flags |= cpu_to_le32(SLOT_FLAG); } -static void xhci_setup_input_ctx_for_quirk(struct xhci_hcd *xhci, - unsigned int slot_id, unsigned int ep_index, - struct xhci_dequeue_state *deq_state) -{ - struct xhci_input_control_ctx *ctrl_ctx; - struct xhci_container_ctx *in_ctx; - struct xhci_ep_ctx *ep_ctx; - u32 added_ctxs; - dma_addr_t addr; - - in_ctx = xhci->devs[slot_id]->in_ctx; - ctrl_ctx = xhci_get_input_control_ctx(in_ctx); - if (!ctrl_ctx) { - xhci_warn(xhci, "%s: Could not get input context, bad type.\n", - __func__); - return; - } - - xhci_endpoint_copy(xhci, xhci->devs[slot_id]->in_ctx, - xhci->devs[slot_id]->out_ctx, ep_index); - ep_ctx = xhci_get_ep_ctx(xhci, in_ctx, ep_index); - addr = xhci_trb_virt_to_dma(deq_state->new_deq_seg, - deq_state->new_deq_ptr); - if (addr == 0) { - xhci_warn(xhci, "WARN Cannot submit config ep after " - "reset ep command\n"); - xhci_warn(xhci, "WARN deq seg = %p, deq ptr = %p\n", - deq_state->new_deq_seg, - deq_state->new_deq_ptr); - return; - } - ep_ctx->deq = cpu_to_le64(addr | deq_state->new_cycle_state); - - added_ctxs = xhci_get_endpoint_flag_from_index(ep_index); - xhci_setup_input_ctx_for_config_ep(xhci, xhci->devs[slot_id]->in_ctx, - xhci->devs[slot_id]->out_ctx, ctrl_ctx, - added_ctxs, added_ctxs); -} - -void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci, unsigned int slot_id, - unsigned int ep_index, unsigned int stream_id, - struct xhci_td *td) -{ - struct xhci_dequeue_state deq_state; - - xhci_dbg_trace(xhci, trace_xhci_dbg_reset_ep, - "Cleaning up stalled endpoint ring"); - /* We need to move the HW's dequeue pointer past this TD, - * or it will attempt to resend it on the next doorbell ring. - */ - xhci_find_new_dequeue_state(xhci, slot_id, ep_index, stream_id, td, - &deq_state); - - if (!deq_state.new_deq_ptr || !deq_state.new_deq_seg) - return; - - /* HW with the reset endpoint quirk will use the saved dequeue state to - * issue a configure endpoint command later. - */ - if (!(xhci->quirks & XHCI_RESET_EP_QUIRK)) { - xhci_dbg_trace(xhci, trace_xhci_dbg_reset_ep, - "Queueing new dequeue state"); - xhci_queue_new_dequeue_state(xhci, slot_id, - ep_index, &deq_state); - } else { - /* Better hope no one uses the input context between now and the - * reset endpoint completion! - * XXX: No idea how this hardware will react when stream rings - * are enabled. - */ - xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, - "Setting up input context for " - "configure endpoint command"); - xhci_setup_input_ctx_for_quirk(xhci, slot_id, - ep_index, &deq_state); - } -} - static void xhci_endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *host_ep) { diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index fe0bf77e8cd8..b3eaa24c9a0d 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1539,10 +1539,18 @@ struct xhci_segment { unsigned int bounce_len; }; +enum xhci_cancelled_td_status { + TD_DIRTY = 0, + TD_HALTED, + TD_CLEARING_CACHE, + TD_CLEARED, +}; + struct xhci_td { struct list_head td_list; struct list_head cancelled_td_list; int status; + enum xhci_cancelled_td_status cancel_status; struct urb *urb; struct xhci_segment *start_seg; union xhci_trb *first_trb; -- cgit v1.2.3 From 9ebf300078584bec2309464fbb249be62b339a2a Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:39 +0200 Subject: xhci: Fix halted endpoint at stop endpoint command completion xhci 4.6.9: "A busy endpoint may asynchronously transition from the Running to the Halted or Error state due to error conditions detected while processing TRBs. A possible race condition may occur if software, thinking an endpoint is in the running state, issues a Stop Endpoint Command, however at the same time the xHC asynchronously transitions the endpoint to the Halted or Error state. In this case, a Context State Error may be generated for the command completion. Software may verify that this case occurred by inspecting the EP State for Halted or Error when a Stop Endpoint Command results in a Context State Error." Halted endpoints were not detected or handled at all in the stop endpoint completion handler. A set TR Deq ptr command was bluntly queued instead of resetting the endpoint first. The set TR Deq command would fail with a context state error. Fix this case by resetting the halted endpoint first to get it to a stopped state instead of the halted (error) state. Handle cancelled TDs once endpoint reset completes, invalidating cancelled TDs on ring either by turning them to no-op, or in case ring stopped on cancelled TD then move hardware dequeue pointer past it, which will clear the cancelled TD from hw cache, and make sure HW does not process it Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-23-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 62 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 0788ee977557..3ba91580e222 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -944,6 +944,27 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) return 0; } + +/* + * Returns the TD the endpoint ring halted on. + * Only call for non-running rings without streams. + */ +static struct xhci_td *find_halted_td(struct xhci_virt_ep *ep) +{ + struct xhci_td *td; + u64 hw_deq; + + if (!list_empty(&ep->ring->td_list)) { /* Not streams compatible */ + hw_deq = xhci_get_hw_deq(ep->xhci, ep->vdev, ep->ep_index, 0); + hw_deq &= ~0xf; + td = list_first_entry(&ep->ring->td_list, struct xhci_td, td_list); + if (trb_in_td(ep->xhci, td->start_seg, td->first_trb, + td->last_trb, hw_deq, false)) + return td; + } + return NULL; +} + /* * When we get a command completion for a Stop Endpoint Command, we need to * unlink any cancelled TDs from the ring. There are two ways to do that: @@ -955,11 +976,13 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) * bit cleared) so that the HW will skip over them. */ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, - union xhci_trb *trb) + union xhci_trb *trb, u32 comp_code) { unsigned int ep_index; struct xhci_virt_ep *ep; struct xhci_ep_ctx *ep_ctx; + struct xhci_td *td = NULL; + enum xhci_ep_reset_type reset_type; if (unlikely(TRB_TO_SUSPEND_PORT(le32_to_cpu(trb->generic.field[3])))) { if (!xhci->devs[slot_id]) @@ -977,6 +1000,40 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, trace_xhci_handle_cmd_stop_ep(ep_ctx); + if (comp_code == COMP_CONTEXT_STATE_ERROR) { + /* + * If stop endpoint command raced with a halting endpoint we need to + * reset the host side endpoint first. + * If the TD we halted on isn't cancelled the TD should be given back + * with a proper error code, and the ring dequeue moved past the TD. + * If streams case we can't find hw_deq, or the TD we halted on so do a + * soft reset. + * + * Proper error code is unknown here, it would be -EPIPE if device side + * of enadpoit halted (aka STALL), and -EPROTO if not (transaction error) + * We use -EPROTO, if device is stalled it should return a stall error on + * next transfer, which then will return -EPIPE, and device side stall is + * noted and cleared by class driver. + */ + switch (GET_EP_CTX_STATE(ep_ctx)) { + case EP_STATE_HALTED: + xhci_dbg(xhci, "Stop ep completion raced with stall, reset ep\n"); + if (ep->ep_state & EP_HAS_STREAMS) { + reset_type = EP_SOFT_RESET; + } else { + reset_type = EP_HARD_RESET; + td = find_halted_td(ep); + if (td) + td->status = -EPROTO; + } + /* reset ep, reset handler cleans up cancelled tds */ + xhci_handle_halted_endpoint(xhci, ep, 0, td, reset_type); + xhci_stop_watchdog_timer_in_irq(xhci, ep); + return; + default: + break; + } + } /* will queue a set TR deq if stopped on a cancelled, uncleared TD */ xhci_invalidate_cancelled_tds(ep); xhci_stop_watchdog_timer_in_irq(xhci, ep); @@ -1619,7 +1676,8 @@ static void handle_cmd_completion(struct xhci_hcd *xhci, WARN_ON(slot_id != TRB_TO_SLOT_ID( le32_to_cpu(cmd_trb->generic.field[3]))); if (!cmd->completion) - xhci_handle_cmd_stop_ep(xhci, slot_id, cmd_trb); + xhci_handle_cmd_stop_ep(xhci, slot_id, cmd_trb, + cmd_comp_code); break; case TRB_SET_DEQ: WARN_ON(slot_id != TRB_TO_SLOT_ID( -- cgit v1.2.3 From 1174d44906d517ddb4f98bbe58a44d4554d0ef90 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:40 +0200 Subject: xhci: handle stop endpoint command completion with endpoint in running state. Handle race where a stop endpoint command fails with "context state error" as hardware hasn't actually started the ring yet after a previous urb cancellation completed and restarted the endpoint. Flushing the doorbell write that restart the endpoint reduced these cases, but didn't completely resolve them. Check if the ring is running in the stop endpoint completion handler, and issue a new stop endpoint command in this case. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-24-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 3ba91580e222..307ccd40ccac 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -983,6 +983,7 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, struct xhci_ep_ctx *ep_ctx; struct xhci_td *td = NULL; enum xhci_ep_reset_type reset_type; + struct xhci_command *command; if (unlikely(TRB_TO_SUSPEND_PORT(le32_to_cpu(trb->generic.field[3])))) { if (!xhci->devs[slot_id]) @@ -1029,6 +1030,18 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, /* reset ep, reset handler cleans up cancelled tds */ xhci_handle_halted_endpoint(xhci, ep, 0, td, reset_type); xhci_stop_watchdog_timer_in_irq(xhci, ep); + return; + case EP_STATE_RUNNING: + /* Race, HW handled stop ep cmd before ep was running */ + command = xhci_alloc_command(xhci, false, GFP_ATOMIC); + if (!command) + xhci_stop_watchdog_timer_in_irq(xhci, ep); + + mod_timer(&ep->stop_cmd_timer, + jiffies + XHCI_STOP_EP_CMD_TIMEOUT * HZ); + xhci_queue_stop_endpoint(xhci, command, slot_id, ep_index, 0); + xhci_ring_cmd_db(xhci); + return; default: break; -- cgit v1.2.3 From d1dbfb942c33bff563af7222418cff3f01c9fbc9 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:41 +0200 Subject: xhci: introduce a new move_dequeue_past_td() function to replace old code. Replace xhci_find_new_dequeue_state() and xhci_queue_new_dequeue_state() functions with one combined function. These function were always called after each other, and had a lot of extra code just to pass the newly found dequeue state from the first function to the other. The new function also returns error in case there is a failure to queue the new dequeue state. This way the caller can decide on recovery measures to handle it. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-25-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 152 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 145 insertions(+), 7 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 307ccd40ccac..e3edc8f2c2cb 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -59,6 +59,10 @@ #include "xhci-trace.h" #include "xhci-mtk.h" +static int queue_command(struct xhci_hcd *xhci, struct xhci_command *cmd, + u32 field1, u32 field2, + u32 field3, u32 field4, bool command_must_succeed); + /* * Returns zero if the TRB isn't in this segment, otherwise it returns the DMA * address of the TRB. @@ -664,6 +668,136 @@ done: (unsigned long long) addr); } +static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci, + unsigned int slot_id, unsigned int ep_index, + unsigned int stream_id, struct xhci_td *td) +{ + struct xhci_virt_device *dev = xhci->devs[slot_id]; + struct xhci_virt_ep *ep = &dev->eps[ep_index]; + struct xhci_ring *ep_ring; + struct xhci_command *cmd; + struct xhci_segment *new_seg; + union xhci_trb *new_deq; + int new_cycle; + dma_addr_t addr; + u64 hw_dequeue; + bool cycle_found = false; + bool td_last_trb_found = false; + u32 trb_sct = 0; + int ret; + + ep_ring = xhci_triad_to_transfer_ring(xhci, slot_id, + ep_index, stream_id); + if (!ep_ring) { + xhci_warn(xhci, "WARN can't find new dequeue, invalid stream ID %u\n", + stream_id); + return -ENODEV; + } + /* + * A cancelled TD can complete with a stall if HW cached the trb. + * In this case driver can't find td, but if the ring is empty we + * can move the dequeue pointer to the current enqueue position. + * We shouldn't hit this anymore as cached cancelled TRBs are given back + * after clearing the cache, but be on the safe side and keep it anyway + */ + if (!td) { + if (list_empty(&ep_ring->td_list)) { + new_seg = ep_ring->enq_seg; + new_deq = ep_ring->enqueue; + new_cycle = ep_ring->cycle_state; + xhci_dbg(xhci, "ep ring empty, Set new dequeue = enqueue"); + goto deq_found; + } else { + xhci_warn(xhci, "Can't find new dequeue state, missing td\n"); + return -EINVAL; + } + } + + hw_dequeue = xhci_get_hw_deq(xhci, dev, ep_index, stream_id); + new_seg = ep_ring->deq_seg; + new_deq = ep_ring->dequeue; + new_cycle = hw_dequeue & 0x1; + + /* + * We want to find the pointer, segment and cycle state of the new trb + * (the one after current TD's last_trb). We know the cycle state at + * hw_dequeue, so walk the ring until both hw_dequeue and last_trb are + * found. + */ + do { + if (!cycle_found && xhci_trb_virt_to_dma(new_seg, new_deq) + == (dma_addr_t)(hw_dequeue & ~0xf)) { + cycle_found = true; + if (td_last_trb_found) + break; + } + if (new_deq == td->last_trb) + td_last_trb_found = true; + + if (cycle_found && trb_is_link(new_deq) && + link_trb_toggles_cycle(new_deq)) + new_cycle ^= 0x1; + + next_trb(xhci, ep_ring, &new_seg, &new_deq); + + /* Search wrapped around, bail out */ + if (new_deq == ep->ring->dequeue) { + xhci_err(xhci, "Error: Failed finding new dequeue state\n"); + return -EINVAL; + } + + } while (!cycle_found || !td_last_trb_found); + +deq_found: + + /* Don't update the ring cycle state for the producer (us). */ + addr = xhci_trb_virt_to_dma(new_seg, new_deq); + if (addr == 0) { + xhci_warn(xhci, "Can't find dma of new dequeue ptr\n"); + xhci_warn(xhci, "deq seg = %p, deq ptr = %p\n", new_seg, new_deq); + return -EINVAL; + } + + if ((ep->ep_state & SET_DEQ_PENDING)) { + xhci_warn(xhci, "Set TR Deq already pending, don't submit for 0x%pad\n", + &addr); + return -EBUSY; + } + + /* This function gets called from contexts where it cannot sleep */ + cmd = xhci_alloc_command(xhci, false, GFP_ATOMIC); + if (!cmd) { + xhci_warn(xhci, "Can't alloc Set TR Deq cmd 0x%pad\n", &addr); + return -ENOMEM; + } + + if (stream_id) + trb_sct = SCT_FOR_TRB(SCT_PRI_TR); + ret = queue_command(xhci, cmd, + lower_32_bits(addr) | trb_sct | new_cycle, + upper_32_bits(addr), + STREAM_ID_FOR_TRB(stream_id), SLOT_ID_FOR_TRB(slot_id) | + EP_ID_FOR_TRB(ep_index) | TRB_TYPE(TRB_SET_DEQ), false); + if (ret < 0) { + xhci_free_command(xhci, cmd); + return ret; + } + ep->queued_deq_seg = new_seg; + ep->queued_deq_ptr = new_deq; + + xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, + "Set TR Deq ptr 0x%llx, cycle %u\n", addr, new_cycle); + + /* Stop the TD queueing code from ringing the doorbell until + * this command completes. The HC won't set the dequeue pointer + * if the ring is running, and ringing the doorbell starts the + * ring running. + */ + ep->ep_state |= SET_DEQ_PENDING; + xhci_ring_cmd_db(xhci); + return 0; +} + /* flip_cycle means flip the cycle bit of all but the first and last TRB. * (The last TRB actually points to the ring enqueue pointer, which is not part * of this TD.) This is used to remove partially enqueued isoc TDs from a ring. @@ -890,9 +1024,9 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) struct xhci_td *tmp_td = NULL; struct xhci_td *cached_td = NULL; struct xhci_ring *ring; - struct xhci_dequeue_state deq_state; u64 hw_deq; unsigned int slot_id = ep->vdev->slot_id; + int err; xhci = ep->xhci; @@ -935,16 +1069,20 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) } if (cached_td) { cached_td->cancel_status = TD_CLEARING_CACHE; - xhci_find_new_dequeue_state(xhci, slot_id, ep->ep_index, - cached_td->urb->stream_id, - cached_td, &deq_state); - xhci_queue_new_dequeue_state(xhci, slot_id, ep->ep_index, - &deq_state); + + err = xhci_move_dequeue_past_td(xhci, slot_id, ep->ep_index, + cached_td->urb->stream_id, + cached_td); + /* Failed to move past cached td, try just setting it noop */ + if (err) { + td_to_noop(xhci, ring, cached_td, false); + cached_td->cancel_status = TD_CLEARED; + } + cached_td = NULL; } return 0; } - /* * Returns the TD the endpoint ring halted on. * Only call for non-running rings without streams. -- cgit v1.2.3 From 741eafb3457ca3c5159e01809a3fd7c754079d25 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:42 +0200 Subject: xhci: remove obsolete dequeue pointer moving code xhci_find_new_dequeue_state() and xhci_queue_new_dequeue_state() are no longer used afer introducing the move_dequeue_past_td() function. also remove struct xhci_dequeue_state as its no longer used. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-26-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 182 ------------------------------------------- drivers/usb/host/xhci.h | 14 ---- 2 files changed, 196 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index e3edc8f2c2cb..1440041acae2 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -551,123 +551,6 @@ static u64 xhci_get_hw_deq(struct xhci_hcd *xhci, struct xhci_virt_device *vdev, return le64_to_cpu(ep_ctx->deq); } -/* - * Move the xHC's endpoint ring dequeue pointer past cur_td. - * Record the new state of the xHC's endpoint ring dequeue segment, - * dequeue pointer, stream id, and new consumer cycle state in state. - * Update our internal representation of the ring's dequeue pointer. - * - * We do this in three jumps: - * - First we update our new ring state to be the same as when the xHC stopped. - * - Then we traverse the ring to find the segment that contains - * the last TRB in the TD. We toggle the xHC's new cycle state when we pass - * any link TRBs with the toggle cycle bit set. - * - Finally we move the dequeue state one TRB further, toggling the cycle bit - * if we've moved it past a link TRB with the toggle cycle bit set. - * - * Some of the uses of xhci_generic_trb are grotty, but if they're done - * with correct __le32 accesses they should work fine. Only users of this are - * in here. - */ -void xhci_find_new_dequeue_state(struct xhci_hcd *xhci, - unsigned int slot_id, unsigned int ep_index, - unsigned int stream_id, struct xhci_td *cur_td, - struct xhci_dequeue_state *state) -{ - struct xhci_virt_device *dev = xhci->devs[slot_id]; - struct xhci_virt_ep *ep = &dev->eps[ep_index]; - struct xhci_ring *ep_ring; - struct xhci_segment *new_seg; - union xhci_trb *new_deq; - dma_addr_t addr; - u64 hw_dequeue; - bool cycle_found = false; - bool td_last_trb_found = false; - - ep_ring = xhci_triad_to_transfer_ring(xhci, slot_id, - ep_index, stream_id); - if (!ep_ring) { - xhci_warn(xhci, "WARN can't find new dequeue state " - "for invalid stream ID %u.\n", - stream_id); - return; - } - /* - * A cancelled TD can complete with a stall if HW cached the trb. - * In this case driver can't find cur_td, but if the ring is empty we - * can move the dequeue pointer to the current enqueue position. - */ - if (!cur_td) { - if (list_empty(&ep_ring->td_list)) { - state->new_deq_seg = ep_ring->enq_seg; - state->new_deq_ptr = ep_ring->enqueue; - state->new_cycle_state = ep_ring->cycle_state; - goto done; - } else { - xhci_warn(xhci, "Can't find new dequeue state, missing cur_td\n"); - return; - } - } - - /* Dig out the cycle state saved by the xHC during the stop ep cmd */ - xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, - "Finding endpoint context"); - - hw_dequeue = xhci_get_hw_deq(xhci, dev, ep_index, stream_id); - new_seg = ep_ring->deq_seg; - new_deq = ep_ring->dequeue; - state->new_cycle_state = hw_dequeue & 0x1; - state->stream_id = stream_id; - - /* - * We want to find the pointer, segment and cycle state of the new trb - * (the one after current TD's last_trb). We know the cycle state at - * hw_dequeue, so walk the ring until both hw_dequeue and last_trb are - * found. - */ - do { - if (!cycle_found && xhci_trb_virt_to_dma(new_seg, new_deq) - == (dma_addr_t)(hw_dequeue & ~0xf)) { - cycle_found = true; - if (td_last_trb_found) - break; - } - if (new_deq == cur_td->last_trb) - td_last_trb_found = true; - - if (cycle_found && trb_is_link(new_deq) && - link_trb_toggles_cycle(new_deq)) - state->new_cycle_state ^= 0x1; - - next_trb(xhci, ep_ring, &new_seg, &new_deq); - - /* Search wrapped around, bail out */ - if (new_deq == ep->ring->dequeue) { - xhci_err(xhci, "Error: Failed finding new dequeue state\n"); - state->new_deq_seg = NULL; - state->new_deq_ptr = NULL; - return; - } - - } while (!cycle_found || !td_last_trb_found); - - state->new_deq_seg = new_seg; - state->new_deq_ptr = new_deq; - -done: - /* Don't update the ring cycle state for the producer (us). */ - xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, - "Cycle state = 0x%x", state->new_cycle_state); - - xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, - "New dequeue segment = %p (virtual)", - state->new_deq_seg); - addr = xhci_trb_virt_to_dma(state->new_deq_seg, state->new_deq_ptr); - xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, - "New dequeue pointer = 0x%llx (DMA)", - (unsigned long long) addr); -} - static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci, unsigned int slot_id, unsigned int ep_index, unsigned int stream_id, struct xhci_td *td) @@ -4418,71 +4301,6 @@ int xhci_queue_stop_endpoint(struct xhci_hcd *xhci, struct xhci_command *cmd, trb_slot_id | trb_ep_index | type | trb_suspend, false); } -/* Set Transfer Ring Dequeue Pointer command */ -void xhci_queue_new_dequeue_state(struct xhci_hcd *xhci, - unsigned int slot_id, unsigned int ep_index, - struct xhci_dequeue_state *deq_state) -{ - dma_addr_t addr; - u32 trb_slot_id = SLOT_ID_FOR_TRB(slot_id); - u32 trb_ep_index = EP_ID_FOR_TRB(ep_index); - u32 trb_stream_id = STREAM_ID_FOR_TRB(deq_state->stream_id); - u32 trb_sct = 0; - u32 type = TRB_TYPE(TRB_SET_DEQ); - struct xhci_virt_ep *ep; - struct xhci_command *cmd; - int ret; - - xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, - "Set TR Deq Ptr cmd, new deq seg = %p (0x%llx dma), new deq ptr = %p (0x%llx dma), new cycle = %u", - deq_state->new_deq_seg, - (unsigned long long)deq_state->new_deq_seg->dma, - deq_state->new_deq_ptr, - (unsigned long long)xhci_trb_virt_to_dma( - deq_state->new_deq_seg, deq_state->new_deq_ptr), - deq_state->new_cycle_state); - - addr = xhci_trb_virt_to_dma(deq_state->new_deq_seg, - deq_state->new_deq_ptr); - if (addr == 0) { - xhci_warn(xhci, "WARN Cannot submit Set TR Deq Ptr\n"); - xhci_warn(xhci, "WARN deq seg = %p, deq pt = %p\n", - deq_state->new_deq_seg, deq_state->new_deq_ptr); - return; - } - ep = &xhci->devs[slot_id]->eps[ep_index]; - if ((ep->ep_state & SET_DEQ_PENDING)) { - xhci_warn(xhci, "WARN Cannot submit Set TR Deq Ptr\n"); - xhci_warn(xhci, "A Set TR Deq Ptr command is pending.\n"); - return; - } - - /* This function gets called from contexts where it cannot sleep */ - cmd = xhci_alloc_command(xhci, false, GFP_ATOMIC); - if (!cmd) - return; - - ep->queued_deq_seg = deq_state->new_deq_seg; - ep->queued_deq_ptr = deq_state->new_deq_ptr; - if (deq_state->stream_id) - trb_sct = SCT_FOR_TRB(SCT_PRI_TR); - ret = queue_command(xhci, cmd, - lower_32_bits(addr) | trb_sct | deq_state->new_cycle_state, - upper_32_bits(addr), trb_stream_id, - trb_slot_id | trb_ep_index | type, false); - if (ret < 0) { - xhci_free_command(xhci, cmd); - return; - } - - /* Stop the TD queueing code from ringing the doorbell until - * this command completes. The HC won't set the dequeue pointer - * if the ring is running, and ringing the doorbell starts the - * ring running. - */ - ep->ep_state |= SET_DEQ_PENDING; -} - int xhci_queue_reset_ep(struct xhci_hcd *xhci, struct xhci_command *cmd, int slot_id, unsigned int ep_index, enum xhci_ep_reset_type reset_type) diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index b3eaa24c9a0d..d17342a60298 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1571,13 +1571,6 @@ struct xhci_cd { union xhci_trb *cmd_trb; }; -struct xhci_dequeue_state { - struct xhci_segment *new_deq_seg; - union xhci_trb *new_deq_ptr; - int new_cycle_state; - unsigned int stream_id; -}; - enum xhci_ring_type { TYPE_CTRL = 0, TYPE_ISOC, @@ -2132,13 +2125,6 @@ int xhci_queue_reset_ep(struct xhci_hcd *xhci, struct xhci_command *cmd, enum xhci_ep_reset_type reset_type); int xhci_queue_reset_device(struct xhci_hcd *xhci, struct xhci_command *cmd, u32 slot_id); -void xhci_find_new_dequeue_state(struct xhci_hcd *xhci, - unsigned int slot_id, unsigned int ep_index, - unsigned int stream_id, struct xhci_td *cur_td, - struct xhci_dequeue_state *state); -void xhci_queue_new_dequeue_state(struct xhci_hcd *xhci, - unsigned int slot_id, unsigned int ep_index, - struct xhci_dequeue_state *deq_state); void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci, unsigned int slot_id, unsigned int ep_index, unsigned int stream_id, struct xhci_td *td); -- cgit v1.2.3 From 51ee4a84300200c51303ef15b84981b2edb6ec47 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:43 +0200 Subject: xhci: Check for pending reset endpoint command before queueing a new one. A halted endpoint can be detected both when transfer events complete, and in stop endpoint command completion. Both these handlers will start clearing up the halted endpoint and queue a reset endpoint command. It's possible to get both events for the same halted endpoint if right after a URB cancel queues a stop endpoint command the endpoint stalls. Use the EP_HALTED flag to prevent resetting the endpoint twice. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-27-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 1440041acae2..26f5557ed7c6 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -873,8 +873,6 @@ static void xhci_handle_halted_endpoint(struct xhci_hcd *xhci, if (ep->vdev->flags & VDEV_PORT_ERROR) return; - ep->ep_state |= EP_HALTED; - /* add td to cancelled list and let reset ep handler take care of it */ if (reset_type == EP_HARD_RESET) { ep->ep_state |= EP_HARD_CLEAR_TOGGLE; @@ -884,10 +882,17 @@ static void xhci_handle_halted_endpoint(struct xhci_hcd *xhci, } } + if (ep->ep_state & EP_HALTED) { + xhci_dbg(xhci, "Reset ep command already pending\n"); + return; + } + err = xhci_reset_halted_ep(xhci, slot_id, ep->ep_index, reset_type); if (err) return; + ep->ep_state |= EP_HALTED; + xhci_ring_cmd_db(xhci); } @@ -2575,7 +2580,6 @@ static int handle_tx_event(struct xhci_hcd *xhci, case COMP_STALL_ERROR: xhci_dbg(xhci, "Stalled endpoint for slot %u ep %u\n", slot_id, ep_index); - ep->ep_state |= EP_HALTED; status = -EPIPE; break; case COMP_SPLIT_TRANSACTION_ERROR: -- cgit v1.2.3 From 3c648d3deb0f95c360c9b91f49c0f313db0cef31 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 29 Jan 2021 15:00:44 +0200 Subject: xhci: handle halting transfer event properly after endpoint stop and halt raced. If we receive a transfer event indicating that an endpoint should be halted, but current endpoint state doesn't match it, then the halt might be just resolved by the stop endpoint completion handler that detects the halted endpoint due to a context state error. In this case the TD we halted on is already moved to the cancelled TD list, and should not be successfully completed and given back anymore. Let the stop endpoint completion handler reset the endpoint, and then let the reset endpoint handler give back the cancelled TD among all other ones on the cancelled TD list Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210129130044.206855-28-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 68 +++++++++++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 26f5557ed7c6..1c0466922523 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2135,18 +2135,52 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, ep_ctx = xhci_get_ep_ctx(xhci, ep->vdev->out_ctx, ep->ep_index); trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); - if (trb_comp_code == COMP_STOPPED_LENGTH_INVALID || - trb_comp_code == COMP_STOPPED || - trb_comp_code == COMP_STOPPED_SHORT_PACKET) { - /* The Endpoint Stop Command completion will take care of any - * stopped TDs. A stopped TD may be restarted, so don't update + switch (trb_comp_code) { + case COMP_STOPPED_LENGTH_INVALID: + case COMP_STOPPED_SHORT_PACKET: + case COMP_STOPPED: + /* + * The "Stop Endpoint" completion will take care of any + * stopped TDs. A stopped TD may be restarted, so don't update * the ring dequeue pointer or take this TD off any lists yet. */ return 0; - } - if (trb_comp_code == COMP_STALL_ERROR || - xhci_requires_manual_halt_cleanup(xhci, ep_ctx, - trb_comp_code)) { + case COMP_USB_TRANSACTION_ERROR: + case COMP_BABBLE_DETECTED_ERROR: + case COMP_SPLIT_TRANSACTION_ERROR: + /* + * If endpoint context state is not halted we might be + * racing with a reset endpoint command issued by a unsuccessful + * stop endpoint completion (context error). In that case the + * td should be on the cancelled list, and EP_HALTED flag set. + * + * Or then it's not halted due to the 0.95 spec stating that a + * babbling control endpoint should not halt. The 0.96 spec + * again says it should. Some HW claims to be 0.95 compliant, + * but it halts the control endpoint anyway. + */ + if (GET_EP_CTX_STATE(ep_ctx) != EP_STATE_HALTED) { + /* + * If EP_HALTED is set and TD is on the cancelled list + * the TD and dequeue pointer will be handled by reset + * ep command completion + */ + if ((ep->ep_state & EP_HALTED) && + !list_empty(&td->cancelled_td_list)) { + xhci_dbg(xhci, "Already resolving halted ep for 0x%llx\n", + (unsigned long long)xhci_trb_virt_to_dma( + td->start_seg, td->first_trb)); + return 0; + } + /* endpoint not halted, don't reset it */ + break; + } + /* Almost same procedure as for STALL_ERROR below */ + xhci_clear_hub_tt_buffer(xhci, td, ep); + xhci_handle_halted_endpoint(xhci, ep, ep_ring->stream_id, td, + EP_HARD_RESET); + return 0; + case COMP_STALL_ERROR: /* * xhci internal endpoint state will go to a "halt" state for * any stall, including default control pipe protocol stall. @@ -2157,21 +2191,23 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, * stall later. Hub TT buffer should only be cleared for FS/LS * devices behind HS hubs for functional stalls. */ - if ((ep->ep_index != 0) || (trb_comp_code != COMP_STALL_ERROR)) + if (ep->ep_index != 0) xhci_clear_hub_tt_buffer(xhci, td, ep); xhci_handle_halted_endpoint(xhci, ep, ep_ring->stream_id, td, EP_HARD_RESET); return 0; /* xhci_handle_halted_endpoint marked td cancelled */ - } else { - /* Update ring dequeue pointer */ - ep_ring->dequeue = td->last_trb; - ep_ring->deq_seg = td->last_trb_seg; - ep_ring->num_trbs_free += td->num_trbs - 1; - inc_deq(xhci, ep_ring); + default: + break; } + /* Update ring dequeue pointer */ + ep_ring->dequeue = td->last_trb; + ep_ring->deq_seg = td->last_trb_seg; + ep_ring->num_trbs_free += td->num_trbs - 1; + inc_deq(xhci, ep_ring); + return xhci_td_cleanup(xhci, td, ep_ring, td->status); } -- cgit v1.2.3 From 5951b8508855799fbb2d6a9553ab3b7af595ea94 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 25 Jan 2021 14:48:11 +0100 Subject: USB: serial: cp210x: suppress modem-control errors The CP210X_SET_MHS request cannot be used to control RTS when hardware flow control (auto-RTS) is enabled and instead returns an error which is currently logged as: cp210x ttyUSB0: failed set request 0x7 status: -32 when opening and closing a port (and on TIOCMSET requests). Add a crtscts flag to keep track of the hardware flow-control setting and use it to suppress any request to change RTS when auto-RTS is enabled. Note that RTS is still deasserted when disabling the UART as part of close. Reported-by: Pho Tran Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index d813a052738f..7e4a09b42c99 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -21,6 +21,7 @@ #include #include #include +#include #define DRIVER_DESC "Silicon Labs CP210x RS232 serial adaptor driver" @@ -264,7 +265,10 @@ struct cp210x_port_private { u8 bInterfaceNumber; bool event_mode; enum cp210x_event_state event_state; - u8 lsr; + u8 lsr; + + struct mutex mutex; + bool crtscts; }; static struct usb_serial_driver cp210x_device = { @@ -1117,6 +1121,7 @@ static bool cp210x_termios_change(const struct ktermios *a, const struct ktermio static void cp210x_set_flow_control(struct tty_struct *tty, struct usb_serial_port *port, struct ktermios *old_termios) { + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); struct cp210x_special_chars chars; struct cp210x_flow_ctl flow_ctl; u32 flow_repl; @@ -1143,10 +1148,12 @@ static void cp210x_set_flow_control(struct tty_struct *tty, return; } + mutex_lock(&port_priv->mutex); + ret = cp210x_read_reg_block(port, CP210X_GET_FLOW, &flow_ctl, sizeof(flow_ctl)); if (ret) - return; + goto out_unlock; ctl_hs = le32_to_cpu(flow_ctl.ulControlHandshake); flow_repl = le32_to_cpu(flow_ctl.ulFlowReplace); @@ -1161,10 +1168,12 @@ static void cp210x_set_flow_control(struct tty_struct *tty, ctl_hs |= CP210X_SERIAL_CTS_HANDSHAKE; flow_repl &= ~CP210X_SERIAL_RTS_MASK; flow_repl |= CP210X_SERIAL_RTS_SHIFT(CP210X_SERIAL_RTS_FLOW_CTL); + port_priv->crtscts = true; } else { ctl_hs &= ~CP210X_SERIAL_CTS_HANDSHAKE; flow_repl &= ~CP210X_SERIAL_RTS_MASK; flow_repl |= CP210X_SERIAL_RTS_SHIFT(CP210X_SERIAL_RTS_ACTIVE); + port_priv->crtscts = false; } if (I_IXOFF(tty)) @@ -1188,6 +1197,8 @@ static void cp210x_set_flow_control(struct tty_struct *tty, cp210x_write_reg_block(port, CP210X_SET_FLOW, &flow_ctl, sizeof(flow_ctl)); +out_unlock: + mutex_unlock(&port_priv->mutex); } static void cp210x_set_termios(struct tty_struct *tty, @@ -1272,7 +1283,9 @@ static int cp210x_tiocmset(struct tty_struct *tty, static int cp210x_tiocmset_port(struct usb_serial_port *port, unsigned int set, unsigned int clear) { + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); u16 control = 0; + int ret; if (set & TIOCM_RTS) { control |= CONTROL_RTS; @@ -1291,9 +1304,22 @@ static int cp210x_tiocmset_port(struct usb_serial_port *port, control |= CONTROL_WRITE_DTR; } + mutex_lock(&port_priv->mutex); + + /* + * SET_MHS cannot be used to control RTS when auto-RTS is enabled. + * Note that RTS is still deasserted when disabling the UART on close. + */ + if (port_priv->crtscts) + control &= ~CONTROL_WRITE_RTS; + dev_dbg(&port->dev, "%s - control = 0x%.4x\n", __func__, control); - return cp210x_write_u16_reg(port, CP210X_SET_MHS, control); + ret = cp210x_write_u16_reg(port, CP210X_SET_MHS, control); + + mutex_unlock(&port_priv->mutex); + + return ret; } static void cp210x_dtr_rts(struct usb_serial_port *port, int on) @@ -1770,6 +1796,7 @@ static int cp210x_port_probe(struct usb_serial_port *port) return -ENOMEM; port_priv->bInterfaceNumber = cp210x_interface_num(serial); + mutex_init(&port_priv->mutex); usb_set_serial_port_data(port, port_priv); -- cgit v1.2.3 From 8cce3bbfb4cffce097c823c29ba487d5a7422d37 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 25 Jan 2021 14:48:12 +0100 Subject: USB: serial: cp210x: fix modem-control handling The vendor request used to set the flow-control settings also sets the state of the modem-control lines. Add state variables to keep track of the modem-control lines to avoid always asserting the lines whenever the flow-control settings are updated. This specifically also avoids asserting DTR/RTS when opening a port with the line speed set to B0. Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 7e4a09b42c99..9378b4bba34b 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -269,6 +269,8 @@ struct cp210x_port_private { struct mutex mutex; bool crtscts; + bool dtr; + bool rts; }; static struct usb_serial_driver cp210x_device = { @@ -1162,7 +1164,10 @@ static void cp210x_set_flow_control(struct tty_struct *tty, ctl_hs &= ~CP210X_SERIAL_DCD_HANDSHAKE; ctl_hs &= ~CP210X_SERIAL_DSR_SENSITIVITY; ctl_hs &= ~CP210X_SERIAL_DTR_MASK; - ctl_hs |= CP210X_SERIAL_DTR_SHIFT(CP210X_SERIAL_DTR_ACTIVE); + if (port_priv->dtr) + ctl_hs |= CP210X_SERIAL_DTR_SHIFT(CP210X_SERIAL_DTR_ACTIVE); + else + ctl_hs |= CP210X_SERIAL_DTR_SHIFT(CP210X_SERIAL_DTR_INACTIVE); if (C_CRTSCTS(tty)) { ctl_hs |= CP210X_SERIAL_CTS_HANDSHAKE; @@ -1172,7 +1177,10 @@ static void cp210x_set_flow_control(struct tty_struct *tty, } else { ctl_hs &= ~CP210X_SERIAL_CTS_HANDSHAKE; flow_repl &= ~CP210X_SERIAL_RTS_MASK; - flow_repl |= CP210X_SERIAL_RTS_SHIFT(CP210X_SERIAL_RTS_ACTIVE); + if (port_priv->rts) + flow_repl |= CP210X_SERIAL_RTS_SHIFT(CP210X_SERIAL_RTS_ACTIVE); + else + flow_repl |= CP210X_SERIAL_RTS_SHIFT(CP210X_SERIAL_RTS_INACTIVE); port_priv->crtscts = false; } @@ -1287,25 +1295,29 @@ static int cp210x_tiocmset_port(struct usb_serial_port *port, u16 control = 0; int ret; + mutex_lock(&port_priv->mutex); + if (set & TIOCM_RTS) { + port_priv->rts = true; control |= CONTROL_RTS; control |= CONTROL_WRITE_RTS; } if (set & TIOCM_DTR) { + port_priv->dtr = true; control |= CONTROL_DTR; control |= CONTROL_WRITE_DTR; } if (clear & TIOCM_RTS) { + port_priv->rts = false; control &= ~CONTROL_RTS; control |= CONTROL_WRITE_RTS; } if (clear & TIOCM_DTR) { + port_priv->dtr = false; control &= ~CONTROL_DTR; control |= CONTROL_WRITE_DTR; } - mutex_lock(&port_priv->mutex); - /* * SET_MHS cannot be used to control RTS when auto-RTS is enabled. * Note that RTS is still deasserted when disabling the UART on close. -- cgit v1.2.3 From 568400b15a5145cb5d1479ece14e7b6d3a3cb554 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 25 Jan 2021 14:48:13 +0100 Subject: USB: serial: cp210x: drop shift macros Drop the macros used to shift the flow-control settings to make the code more readable for consistency with the other requests. Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 9378b4bba34b..aa874641374a 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -449,17 +449,14 @@ struct cp210x_flow_ctl { /* cp210x_flow_ctl::ulControlHandshake */ #define CP210X_SERIAL_DTR_MASK GENMASK(1, 0) -#define CP210X_SERIAL_DTR_SHIFT(_mode) (_mode) +#define CP210X_SERIAL_DTR_INACTIVE (0 << 0) +#define CP210X_SERIAL_DTR_ACTIVE (1 << 0) +#define CP210X_SERIAL_DTR_FLOW_CTL (2 << 0) #define CP210X_SERIAL_CTS_HANDSHAKE BIT(3) #define CP210X_SERIAL_DSR_HANDSHAKE BIT(4) #define CP210X_SERIAL_DCD_HANDSHAKE BIT(5) #define CP210X_SERIAL_DSR_SENSITIVITY BIT(6) -/* values for cp210x_flow_ctl::ulControlHandshake::CP210X_SERIAL_DTR_MASK */ -#define CP210X_SERIAL_DTR_INACTIVE 0 -#define CP210X_SERIAL_DTR_ACTIVE 1 -#define CP210X_SERIAL_DTR_FLOW_CTL 2 - /* cp210x_flow_ctl::ulFlowReplace */ #define CP210X_SERIAL_AUTO_TRANSMIT BIT(0) #define CP210X_SERIAL_AUTO_RECEIVE BIT(1) @@ -467,14 +464,11 @@ struct cp210x_flow_ctl { #define CP210X_SERIAL_NULL_STRIPPING BIT(3) #define CP210X_SERIAL_BREAK_CHAR BIT(4) #define CP210X_SERIAL_RTS_MASK GENMASK(7, 6) -#define CP210X_SERIAL_RTS_SHIFT(_mode) (_mode << 6) +#define CP210X_SERIAL_RTS_INACTIVE (0 << 6) +#define CP210X_SERIAL_RTS_ACTIVE (1 << 6) +#define CP210X_SERIAL_RTS_FLOW_CTL (2 << 6) #define CP210X_SERIAL_XOFF_CONTINUE BIT(31) -/* values for cp210x_flow_ctl::ulFlowReplace::CP210X_SERIAL_RTS_MASK */ -#define CP210X_SERIAL_RTS_INACTIVE 0 -#define CP210X_SERIAL_RTS_ACTIVE 1 -#define CP210X_SERIAL_RTS_FLOW_CTL 2 - /* CP210X_VENDOR_SPECIFIC, CP210X_GET_DEVICEMODE call reads these 0x2 bytes. */ struct cp210x_pin_mode { u8 eci; @@ -1165,22 +1159,22 @@ static void cp210x_set_flow_control(struct tty_struct *tty, ctl_hs &= ~CP210X_SERIAL_DSR_SENSITIVITY; ctl_hs &= ~CP210X_SERIAL_DTR_MASK; if (port_priv->dtr) - ctl_hs |= CP210X_SERIAL_DTR_SHIFT(CP210X_SERIAL_DTR_ACTIVE); + ctl_hs |= CP210X_SERIAL_DTR_ACTIVE; else - ctl_hs |= CP210X_SERIAL_DTR_SHIFT(CP210X_SERIAL_DTR_INACTIVE); + ctl_hs |= CP210X_SERIAL_DTR_INACTIVE; if (C_CRTSCTS(tty)) { ctl_hs |= CP210X_SERIAL_CTS_HANDSHAKE; flow_repl &= ~CP210X_SERIAL_RTS_MASK; - flow_repl |= CP210X_SERIAL_RTS_SHIFT(CP210X_SERIAL_RTS_FLOW_CTL); + flow_repl |= CP210X_SERIAL_RTS_FLOW_CTL; port_priv->crtscts = true; } else { ctl_hs &= ~CP210X_SERIAL_CTS_HANDSHAKE; flow_repl &= ~CP210X_SERIAL_RTS_MASK; if (port_priv->rts) - flow_repl |= CP210X_SERIAL_RTS_SHIFT(CP210X_SERIAL_RTS_ACTIVE); + flow_repl |= CP210X_SERIAL_RTS_ACTIVE; else - flow_repl |= CP210X_SERIAL_RTS_SHIFT(CP210X_SERIAL_RTS_INACTIVE); + flow_repl |= CP210X_SERIAL_RTS_INACTIVE; port_priv->crtscts = false; } -- cgit v1.2.3 From f191c63779a0debf2a7f85a5c8d0c09d35b50ddb Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 25 Jan 2021 14:48:14 +0100 Subject: USB: serial: cp210x: clean up flow-control debug message Shorten the flow-control debug message by abbreviating the field names and reducing the value width to two characters. The latter improves readability since all but the least significant byte will almost always be zero anyway. Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index aa874641374a..36ae44818c13 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -1191,8 +1191,8 @@ static void cp210x_set_flow_control(struct tty_struct *tty, flow_ctl.ulXonLimit = cpu_to_le32(128); flow_ctl.ulXoffLimit = cpu_to_le32(128); - dev_dbg(&port->dev, "%s - ulControlHandshake=0x%08x, ulFlowReplace=0x%08x\n", - __func__, ctl_hs, flow_repl); + dev_dbg(&port->dev, "%s - ctrl = 0x%02x, flow = 0x%02x\n", __func__, + ctl_hs, flow_repl); flow_ctl.ulControlHandshake = cpu_to_le32(ctl_hs); flow_ctl.ulFlowReplace = cpu_to_le32(flow_repl); -- cgit v1.2.3 From 6b667274f41a0269a8b493079fcacd4f55183f60 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 25 Jan 2021 14:48:15 +0100 Subject: USB: serial: cp210x: clean up printk zero padding Use the 0-flag and a field width to specify zero-padding consistently in printk messages. Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 36ae44818c13..4ba3fb096bf1 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -1319,7 +1319,7 @@ static int cp210x_tiocmset_port(struct usb_serial_port *port, if (port_priv->crtscts) control &= ~CONTROL_WRITE_RTS; - dev_dbg(&port->dev, "%s - control = 0x%.4x\n", __func__, control); + dev_dbg(&port->dev, "%s - control = 0x%04x\n", __func__, control); ret = cp210x_write_u16_reg(port, CP210X_SET_MHS, control); @@ -1353,7 +1353,7 @@ static int cp210x_tiocmget(struct tty_struct *tty) |((control & CONTROL_RING)? TIOCM_RI : 0) |((control & CONTROL_DCD) ? TIOCM_CD : 0); - dev_dbg(&port->dev, "%s - control = 0x%.2x\n", __func__, control); + dev_dbg(&port->dev, "%s - control = 0x%02x\n", __func__, control); return result; } -- cgit v1.2.3 From cf00ead0bde8e47ccd3aa8a4e51cfa59bbf5e055 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 25 Jan 2021 14:48:16 +0100 Subject: USB: serial: cp210x: fix RTS handling Clearing TIOCM_RTS should always deassert RTS and setting the same bit should enable auto-RTS if hardware flow control is enabled. This allows user space to throttle input directly at the source also when hardware-assisted flow control is enabled and makes dtr_rts() always deassert both lines during close (when HUPCL is set). Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 47 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 4ba3fb096bf1..f00b736f3cd3 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -1166,7 +1166,10 @@ static void cp210x_set_flow_control(struct tty_struct *tty, if (C_CRTSCTS(tty)) { ctl_hs |= CP210X_SERIAL_CTS_HANDSHAKE; flow_repl &= ~CP210X_SERIAL_RTS_MASK; - flow_repl |= CP210X_SERIAL_RTS_FLOW_CTL; + if (port_priv->rts) + flow_repl |= CP210X_SERIAL_RTS_FLOW_CTL; + else + flow_repl |= CP210X_SERIAL_RTS_INACTIVE; port_priv->crtscts = true; } else { ctl_hs &= ~CP210X_SERIAL_CTS_HANDSHAKE; @@ -1286,6 +1289,8 @@ static int cp210x_tiocmset_port(struct usb_serial_port *port, unsigned int set, unsigned int clear) { struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + struct cp210x_flow_ctl flow_ctl; + u32 ctl_hs, flow_repl; u16 control = 0; int ret; @@ -1313,16 +1318,44 @@ static int cp210x_tiocmset_port(struct usb_serial_port *port, } /* - * SET_MHS cannot be used to control RTS when auto-RTS is enabled. - * Note that RTS is still deasserted when disabling the UART on close. + * Use SET_FLOW to set DTR and enable/disable auto-RTS when hardware + * flow control is enabled. */ - if (port_priv->crtscts) - control &= ~CONTROL_WRITE_RTS; + if (port_priv->crtscts && control & CONTROL_WRITE_RTS) { + ret = cp210x_read_reg_block(port, CP210X_GET_FLOW, &flow_ctl, + sizeof(flow_ctl)); + if (ret) + goto out_unlock; - dev_dbg(&port->dev, "%s - control = 0x%04x\n", __func__, control); + ctl_hs = le32_to_cpu(flow_ctl.ulControlHandshake); + flow_repl = le32_to_cpu(flow_ctl.ulFlowReplace); - ret = cp210x_write_u16_reg(port, CP210X_SET_MHS, control); + ctl_hs &= ~CP210X_SERIAL_DTR_MASK; + if (port_priv->dtr) + ctl_hs |= CP210X_SERIAL_DTR_ACTIVE; + else + ctl_hs |= CP210X_SERIAL_DTR_INACTIVE; + flow_repl &= ~CP210X_SERIAL_RTS_MASK; + if (port_priv->rts) + flow_repl |= CP210X_SERIAL_RTS_FLOW_CTL; + else + flow_repl |= CP210X_SERIAL_RTS_INACTIVE; + + flow_ctl.ulControlHandshake = cpu_to_le32(ctl_hs); + flow_ctl.ulFlowReplace = cpu_to_le32(flow_repl); + + dev_dbg(&port->dev, "%s - ctrl = 0x%02x, flow = 0x%02x\n", + __func__, ctl_hs, flow_repl); + + ret = cp210x_write_reg_block(port, CP210X_SET_FLOW, &flow_ctl, + sizeof(flow_ctl)); + } else { + dev_dbg(&port->dev, "%s - control = 0x%04x\n", __func__, control); + + ret = cp210x_write_u16_reg(port, CP210X_SET_MHS, control); + } +out_unlock: mutex_unlock(&port_priv->mutex); return ret; -- cgit v1.2.3 From e2f2dea34cf16e67b347ea7e9805864f03d16dcc Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 25 Jan 2021 14:48:17 +0100 Subject: USB: serial: cp210x: clean up auto-RTS handling Clear the RTS bits of the flow-control request before determining the new value when updating the settings. Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index f00b736f3cd3..cc4f63a06f9e 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -1163,9 +1163,9 @@ static void cp210x_set_flow_control(struct tty_struct *tty, else ctl_hs |= CP210X_SERIAL_DTR_INACTIVE; + flow_repl &= ~CP210X_SERIAL_RTS_MASK; if (C_CRTSCTS(tty)) { ctl_hs |= CP210X_SERIAL_CTS_HANDSHAKE; - flow_repl &= ~CP210X_SERIAL_RTS_MASK; if (port_priv->rts) flow_repl |= CP210X_SERIAL_RTS_FLOW_CTL; else @@ -1173,7 +1173,6 @@ static void cp210x_set_flow_control(struct tty_struct *tty, port_priv->crtscts = true; } else { ctl_hs &= ~CP210X_SERIAL_CTS_HANDSHAKE; - flow_repl &= ~CP210X_SERIAL_RTS_MASK; if (port_priv->rts) flow_repl |= CP210X_SERIAL_RTS_ACTIVE; else -- cgit v1.2.3 From 528222d0c8ce93e435a95cd1e476b60409dd5381 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 26 Jan 2021 14:59:17 +0100 Subject: USB: serial: ftdi_sio: fix FTX sub-integer prescaler The most-significant bit of the sub-integer-prescaler index is set in the high byte of the baudrate request wIndex also for FTX devices. This fixes rates like 1152000 which got mapped to 1.2 MBd. Reported-by: Vladimir Link: https://bugzilla.kernel.org/show_bug.cgi?id=210351 Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/ftdi_sio.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index 94398f89e600..4168801b9595 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -1386,8 +1386,9 @@ static int change_speed(struct tty_struct *tty, struct usb_serial_port *port) index_value = get_ftdi_divisor(tty, port); value = (u16)index_value; index = (u16)(index_value >> 16); - if ((priv->chip_type == FT2232C) || (priv->chip_type == FT2232H) || - (priv->chip_type == FT4232H) || (priv->chip_type == FT232H)) { + if (priv->chip_type == FT2232C || priv->chip_type == FT2232H || + priv->chip_type == FT4232H || priv->chip_type == FT232H || + priv->chip_type == FTX) { /* Probably the BM type needs the MSB of the encoded fractional * divider also moved like for the chips above. Any infos? */ index = (u16)((index << 8) | priv->interface); -- cgit v1.2.3 From 1ef268039b79945a9284dbc34eedcbad21415106 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 27 Jan 2021 12:00:28 +0100 Subject: USB: serial: ftdi_sio: restore divisor-encoding comments Add back a few explanatory comments related to the divisor encoding which got lost in a coding-style clean up many years ago. Signed-off-by: Johan Hovold --- drivers/usb/serial/ftdi_sio.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index 4168801b9595..d61703d858a1 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -1153,13 +1153,13 @@ static unsigned short int ftdi_232am_baud_base_to_divisor(int baud, int base) divisor = divisor3 >> 3; divisor3 &= 0x7; if (divisor3 == 1) - divisor |= 0xc000; + divisor |= 0xc000; /* +0.125 */ else if (divisor3 >= 4) - divisor |= 0x4000; + divisor |= 0x4000; /* +0.5 */ else if (divisor3 != 0) - divisor |= 0x8000; + divisor |= 0x8000; /* +0.25 */ else if (divisor == 1) - divisor = 0; /* special case for maximum baud rate */ + divisor = 0; /* special case for maximum baud rate */ return divisor; } @@ -1177,9 +1177,9 @@ static u32 ftdi_232bm_baud_base_to_divisor(int baud, int base) divisor = divisor3 >> 3; divisor |= (u32)divfrac[divisor3 & 0x7] << 14; /* Deal with special cases for highest baud rates. */ - if (divisor == 1) + if (divisor == 1) /* 1.0 */ divisor = 0; - else if (divisor == 0x4001) + else if (divisor == 0x4001) /* 1.5 */ divisor = 1; return divisor; } @@ -1201,9 +1201,9 @@ static u32 ftdi_2232h_baud_base_to_divisor(int baud, int base) divisor = divisor3 >> 3; divisor |= (u32)divfrac[divisor3 & 0x7] << 14; /* Deal with special cases for highest baud rates. */ - if (divisor == 1) + if (divisor == 1) /* 1.0 */ divisor = 0; - else if (divisor == 0x4001) + else if (divisor == 0x4001) /* 1.5 */ divisor = 1; /* * Set this bit to turn off a divide by 2.5 on baud rate generator -- cgit v1.2.3 From 1ed8459d8f1060c87c7d66fe2d3cbbe4bc9cdd24 Mon Sep 17 00:00:00 2001 From: Benson Leung Date: Thu, 28 Jan 2021 22:14:01 -0800 Subject: usb: typec: Standardize PD Revision format with Type-C Revision The Type-C Revision was in a specific BCD format "0120H" for 1.2. USB PD revision numbers follow a similar pattern with "0300H" for 3.0. Standardizes the sysfs format for usb_power_delivery_revision to align with the BCD format used for usb_typec_revision. Example values: - "2.0": USB Power Delivery Release 2.0 - "3.0": USB Power Delivery Release 3.0 - "3.1": USB Power Delivery Release 3.1 Reviewed-by: Heikki Krogerus Signed-off-by: Benson Leung Link: https://lore.kernel.org/r/20210129061406.2680146-2-bleung@chromium.org Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-typec | 7 ++++++- drivers/usb/typec/class.c | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec index 8eab41e79ce6..b61480535fdc 100644 --- a/Documentation/ABI/testing/sysfs-class-typec +++ b/Documentation/ABI/testing/sysfs-class-typec @@ -105,7 +105,12 @@ Date: April 2017 Contact: Heikki Krogerus Description: Revision number of the supported USB Power Delivery - specification, or 0 when USB Power Delivery is not supported. + specification, or 0.0 when USB Power Delivery is not supported. + + Example values: + - "2.0": USB Power Delivery Release 2.0 + - "3.0": USB Power Delivery Release 3.0 + - "3.1": USB Power Delivery Release 3.1 What: /sys/class/typec//usb_typec_revision Date: April 2017 diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 8f77669f9cf4..4f60ee7ba76a 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -1500,8 +1500,9 @@ static ssize_t usb_power_delivery_revision_show(struct device *dev, char *buf) { struct typec_port *p = to_typec_port(dev); + u16 rev = p->cap->pd_revision; - return sprintf(buf, "%d\n", (p->cap->pd_revision >> 8) & 0xff); + return sprintf(buf, "%d.%d\n", (rev >> 8) & 0xff, (rev >> 4) & 0xf); } static DEVICE_ATTR_RO(usb_power_delivery_revision); -- cgit v1.2.3 From f5030e252687be6e999bd52feb1f79d515b2f684 Mon Sep 17 00:00:00 2001 From: Benson Leung Date: Thu, 28 Jan 2021 22:14:02 -0800 Subject: usb: typec: Provide PD Specification Revision for cable and partner The USB Power Delivery specification Section 6.2.1.1.5 outlines revision backward compatibility requirements starting from Revision 3.0. The Port, the Cable Plug, and the Port Partner may support either revision 2 or revision 3 independently, and communication between ports, partners, and cables of different revisions are allowed under rules that the parties agree to communicate between each other using the lowest common operating revision. This may mean that Port-to-Partner operating revision comms may be different than Port-to-CablePlug operating revision comms. For example, it is possible for a R3.0 port to communicate with a R3.0 partner using R3.0 messages, while the R3.0 port (in the same session) must communicate with the R2.0 cable using R2.0 messages only. Introduce individual revision number properties for cable and port partner so that the port can track them independently. Reviewed-by: Heikki Krogerus Signed-off-by: Benson Leung Link: https://lore.kernel.org/r/20210129061406.2680146-3-bleung@chromium.org Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-typec | 13 +++++++++++++ drivers/usb/typec/class.c | 30 +++++++++++++++++++++++++---- include/linux/usb/typec.h | 10 ++++++++++ 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec index b61480535fdc..40122d915ae1 100644 --- a/Documentation/ABI/testing/sysfs-class-typec +++ b/Documentation/ABI/testing/sysfs-class-typec @@ -112,6 +112,19 @@ Description: - "3.0": USB Power Delivery Release 3.0 - "3.1": USB Power Delivery Release 3.1 +What: /sys/class/typec/-{partner|cable}/usb_power_delivery_revision +Date: January 2021 +Contact: Benson Leung +Description: + Revision number of the supported USB Power Delivery + specification of the port partner or cable, or 0.0 when USB + Power Delivery is not supported. + + Example values: + - "2.0": USB Power Delivery Release 2.0 + - "3.0": USB Power Delivery Release 3.0 + - "3.1": USB Power Delivery Release 3.1 + What: /sys/class/typec//usb_typec_revision Date: April 2017 Contact: Heikki Krogerus diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 4f60ee7ba76a..b5241f4756c2 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -27,6 +27,7 @@ struct typec_cable { enum typec_plug_type type; struct usb_pd_identity *identity; unsigned int active:1; + u16 pd_revision; /* 0300H = "3.0" */ }; struct typec_partner { @@ -36,6 +37,7 @@ struct typec_partner { enum typec_accessory accessory; struct ida mode_ids; int num_altmodes; + u16 pd_revision; /* 0300H = "3.0" */ }; struct typec_port { @@ -264,6 +266,11 @@ type_show(struct device *dev, struct device_attribute *attr, char *buf) } static DEVICE_ATTR_RO(type); +static ssize_t usb_power_delivery_revision_show(struct device *dev, + struct device_attribute *attr, + char *buf); +static DEVICE_ATTR_RO(usb_power_delivery_revision); + /* ------------------------------------------------------------------------- */ /* Alternate Modes */ @@ -680,6 +687,7 @@ static struct attribute *typec_partner_attrs[] = { &dev_attr_supports_usb_power_delivery.attr, &dev_attr_number_of_alternate_modes.attr, &dev_attr_type.attr, + &dev_attr_usb_power_delivery_revision.attr, NULL }; @@ -815,6 +823,7 @@ struct typec_partner *typec_register_partner(struct typec_port *port, partner->usb_pd = desc->usb_pd; partner->accessory = desc->accessory; partner->num_altmodes = -1; + partner->pd_revision = desc->pd_revision; if (desc->identity) { /* @@ -1028,6 +1037,7 @@ static DEVICE_ATTR_RO(plug_type); static struct attribute *typec_cable_attrs[] = { &dev_attr_type.attr, &dev_attr_plug_type.attr, + &dev_attr_usb_power_delivery_revision.attr, NULL }; ATTRIBUTE_GROUPS(typec_cable); @@ -1130,6 +1140,7 @@ struct typec_cable *typec_register_cable(struct typec_port *port, cable->type = desc->type; cable->active = desc->active; + cable->pd_revision = desc->pd_revision; if (desc->identity) { /* @@ -1499,12 +1510,23 @@ static ssize_t usb_power_delivery_revision_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct typec_port *p = to_typec_port(dev); - u16 rev = p->cap->pd_revision; + u16 rev = 0; - return sprintf(buf, "%d.%d\n", (rev >> 8) & 0xff, (rev >> 4) & 0xf); + if (is_typec_partner(dev)) { + struct typec_partner *partner = to_typec_partner(dev); + + rev = partner->pd_revision; + } else if (is_typec_cable(dev)) { + struct typec_cable *cable = to_typec_cable(dev); + + rev = cable->pd_revision; + } else if (is_typec_port(dev)) { + struct typec_port *p = to_typec_port(dev); + + rev = p->cap->pd_revision; + } + return sysfs_emit(buf, "%d.%d\n", (rev >> 8) & 0xff, (rev >> 4) & 0xf); } -static DEVICE_ATTR_RO(usb_power_delivery_revision); static ssize_t orientation_show(struct device *dev, struct device_attribute *attr, diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index 54475323f83b..42c6b7c07a99 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -164,6 +164,7 @@ struct typec_plug_desc { * @type: The plug type from USB PD Cable VDO * @active: Is the cable active or passive * @identity: Result of Discover Identity command + * @pd_revision: USB Power Delivery Specification revision if supported * * Represents USB Type-C Cable attached to USB Type-C port. */ @@ -171,6 +172,8 @@ struct typec_cable_desc { enum typec_plug_type type; unsigned int active:1; struct usb_pd_identity *identity; + u16 pd_revision; /* 0300H = "3.0" */ + }; /* @@ -178,15 +181,22 @@ struct typec_cable_desc { * @usb_pd: USB Power Delivery support * @accessory: Audio, Debug or none. * @identity: Discover Identity command data + * @pd_revision: USB Power Delivery Specification Revision if supported * * Details about a partner that is attached to USB Type-C port. If @identity * member exists when partner is registered, a directory named "identity" is * created to sysfs for the partner device. + * + * @pd_revision is based on the setting of the "Specification Revision" field + * in the message header on the initial "Source Capabilities" message received + * from the partner, or a "Request" message received from the partner, depending + * on whether our port is a Sink or a Source. */ struct typec_partner_desc { unsigned int usb_pd:1; enum typec_accessory accessory; struct usb_pd_identity *identity; + u16 pd_revision; /* 0300H = "3.0" */ }; /** -- cgit v1.2.3 From 29b01295a829fba7399ee84afff4e64660e49f04 Mon Sep 17 00:00:00 2001 From: Benson Leung Date: Thu, 28 Jan 2021 22:14:03 -0800 Subject: usb: typec: Add typec_partner_set_pd_revision The partner's PD revision may be resolved later than the port partner registration since the port partner creation may take place once Type-C detects the port has changed state, but before PD communication is completed. Add a setter so that the partner's PD revision can be attached to it once it becomes available. If the revision is set to a valid version (not 0), the setter will also refresh the partner's usb_pd flag and notify on "supports_usb_power_delivery" sysfs property as well. Reviewed-by: Heikki Krogerus Signed-off-by: Benson Leung Link: https://lore.kernel.org/r/20210129061406.2680146-4-bleung@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/class.c | 30 ++++++++++++++++++++++++++++++ include/linux/usb/typec.h | 1 + 2 files changed, 31 insertions(+) diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index b5241f4756c2..b6ceab3dc16b 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -748,6 +748,36 @@ int typec_partner_set_identity(struct typec_partner *partner) } EXPORT_SYMBOL_GPL(typec_partner_set_identity); +/** + * typec_partner_set_pd_revision - Set the PD revision supported by the partner + * @partner: The partner to be updated. + * @pd_revision: USB Power Delivery Specification Revision supported by partner + * + * This routine is used to report that the PD revision of the port partner has + * become available. + * + * Returns 0 on success or negative error number on failure. + */ +int typec_partner_set_pd_revision(struct typec_partner *partner, u16 pd_revision) +{ + int ret; + + if (partner->pd_revision == pd_revision) + return 0; + + partner->pd_revision = pd_revision; + sysfs_notify(&partner->dev.kobj, NULL, "usb_power_delivery_revision"); + if (pd_revision != 0 && !partner->usb_pd) { + partner->usb_pd = 1; + sysfs_notify(&partner->dev.kobj, NULL, + "supports_usb_power_delivery"); + } + kobject_uevent(&partner->dev.kobj, KOBJ_CHANGE); + + return 0; +} +EXPORT_SYMBOL_GPL(typec_partner_set_pd_revision); + /** * typec_partner_set_num_altmodes - Set the number of available partner altmodes * @partner: The partner to be updated. diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index 42c6b7c07a99..4946eca742d5 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -126,6 +126,7 @@ struct typec_altmode_desc { enum typec_port_data roles; }; +int typec_partner_set_pd_revision(struct typec_partner *partner, u16 pd_revision); int typec_partner_set_num_altmodes(struct typec_partner *partner, int num_altmodes); struct typec_altmode *typec_partner_register_altmode(struct typec_partner *partner, -- cgit v1.2.3 From 3b3dd1f0dbfe92781c60f36ea5c22b26360f9909 Mon Sep 17 00:00:00 2001 From: Benson Leung Date: Thu, 28 Jan 2021 22:14:04 -0800 Subject: platform/chrome: cros_ec_typec: Report SOP' PD revision from status cros_typec_handle_sop_prime_disc now takes the PD revision provided by the EC_CMD_TYPEC_STATUS command response for the SOP'. Attach the properly formatted pd_revision to the cable desc before registering the cable. Signed-off-by: Benson Leung Acked-by: Heikki Krogerus Acked-by: Enric Balletbo i Serra Reviewed-by: Prashant Malani Link: https://lore.kernel.org/r/20210129061406.2680146-5-bleung@chromium.org Signed-off-by: Benson Leung --- drivers/platform/chrome/cros_ec_typec.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index e724a5eaef1c..30600e9454e1 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -748,7 +748,7 @@ static void cros_typec_parse_pd_identity(struct usb_pd_identity *id, id->vdo[i - 3] = disc->discovery_vdo[i]; } -static int cros_typec_handle_sop_prime_disc(struct cros_typec_data *typec, int port_num) +static int cros_typec_handle_sop_prime_disc(struct cros_typec_data *typec, int port_num, u16 pd_revision) { struct cros_typec_port *port = typec->ports[port_num]; struct ec_response_typec_discovery *disc = port->disc_data; @@ -794,6 +794,7 @@ static int cros_typec_handle_sop_prime_disc(struct cros_typec_data *typec, int p } c_desc.identity = &port->c_identity; + c_desc.pd_revision = pd_revision; port->cable = typec_register_cable(port->port, &c_desc); if (IS_ERR(port->cable)) { @@ -893,7 +894,11 @@ static void cros_typec_handle_status(struct cros_typec_data *typec, int port_num if (resp.events & PD_STATUS_EVENT_SOP_PRIME_DISC_DONE && !typec->ports[port_num]->sop_prime_disc_done) { - ret = cros_typec_handle_sop_prime_disc(typec, port_num); + u16 sop_prime_revision; + + /* Convert BCD to the format preferred by the TypeC framework */ + sop_prime_revision = (le16_to_cpu(resp.sop_prime_revision) & 0xff00) >> 4; + ret = cros_typec_handle_sop_prime_disc(typec, port_num, sop_prime_revision); if (ret < 0) dev_err(typec->dev, "Couldn't parse SOP' Disc data, port: %d\n", port_num); else -- cgit v1.2.3 From cefc011f8daf0ff3003208349b85174cda0b708d Mon Sep 17 00:00:00 2001 From: Benson Leung Date: Thu, 28 Jan 2021 22:14:05 -0800 Subject: platform/chrome: cros_ec_typec: Set Partner PD revision from status Status provides sop_revision. Process it, and set it using the new setter in the typec class. Signed-off-by: Benson Leung Acked-by: Heikki Krogerus Acked-by: Enric Balletbo i Serra Reviewed-by: Prashant Malani Link: https://lore.kernel.org/r/20210129061406.2680146-6-bleung@chromium.org Signed-off-by: Benson Leung --- drivers/platform/chrome/cros_ec_typec.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 30600e9454e1..6bc6fafd54a4 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -824,7 +824,7 @@ sop_prime_disc_exit: return ret; } -static int cros_typec_handle_sop_disc(struct cros_typec_data *typec, int port_num) +static int cros_typec_handle_sop_disc(struct cros_typec_data *typec, int port_num, u16 pd_revision) { struct cros_typec_port *port = typec->ports[port_num]; struct ec_response_typec_discovery *sop_disc = port->disc_data; @@ -842,6 +842,12 @@ static int cros_typec_handle_sop_disc(struct cros_typec_data *typec, int port_nu goto disc_exit; } + ret = typec_partner_set_pd_revision(port->partner, pd_revision); + if (ret < 0) { + dev_err(typec->dev, "Failed to update partner PD revision, port: %d\n", port_num); + goto disc_exit; + } + memset(sop_disc, 0, EC_PROTO2_MAX_RESPONSE_SIZE); ret = cros_typec_ec_command(typec, 0, EC_CMD_TYPEC_DISCOVERY, &req, sizeof(req), sop_disc, EC_PROTO2_MAX_RESPONSE_SIZE); @@ -885,7 +891,11 @@ static void cros_typec_handle_status(struct cros_typec_data *typec, int port_num /* Handle any events appropriately. */ if (resp.events & PD_STATUS_EVENT_SOP_DISC_DONE && !typec->ports[port_num]->sop_disc_done) { - ret = cros_typec_handle_sop_disc(typec, port_num); + u16 sop_revision; + + /* Convert BCD to the format preferred by the TypeC framework */ + sop_revision = (le16_to_cpu(resp.sop_revision) & 0xff00) >> 4; + ret = cros_typec_handle_sop_disc(typec, port_num, sop_revision); if (ret < 0) dev_err(typec->dev, "Couldn't parse SOP Disc data, port: %d\n", port_num); else -- cgit v1.2.3 From 0371616d8bef6926e9aa05757f35b901268d3724 Mon Sep 17 00:00:00 2001 From: Benson Leung Date: Thu, 28 Jan 2021 22:14:06 -0800 Subject: platform/chrome: cros_ec_typec: Set opmode to PD on SOP connected When SOP Discovery is done, set the opmode to PD if status indicates SOP is connected. SOP connected indicates a PD contract is in place, and is a solid indication we have transitioned to PD power negotiation, either as source or sink. Signed-off-by: Benson Leung Acked-by: Heikki Krogerus Acked-by: Enric Balletbo i Serra Reviewed-by: Prashant Malani Link: https://lore.kernel.org/r/20210129061406.2680146-7-bleung@chromium.org Signed-off-by: Benson Leung --- drivers/platform/chrome/cros_ec_typec.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 6bc6fafd54a4..a7778258d0a0 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -900,6 +900,9 @@ static void cros_typec_handle_status(struct cros_typec_data *typec, int port_num dev_err(typec->dev, "Couldn't parse SOP Disc data, port: %d\n", port_num); else typec->ports[port_num]->sop_disc_done = true; + + if (resp.sop_connected) + typec_set_pwr_opmode(typec->ports[port_num]->port, TYPEC_PWR_MODE_PD); } if (resp.events & PD_STATUS_EVENT_SOP_PRIME_DISC_DONE && -- cgit v1.2.3 From 7771bcc7f5a727d6e3f7a80b0b075a75cb664fb2 Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Mon, 1 Feb 2021 02:02:12 -0800 Subject: usb: typec: tcpm: Handle vbus shutoff when in source mode While in source mode, vbus could be shutoff by protections circuits. TCPM does not move back to toggling state to re-initiate connection. Fix this by moving to SRC_UNATTACHED state when vbus shuts off while in source mode. Reviewed-by: Guenter Roeck Reviewed-by: Heikki Krogerus Signed-off-by: Badhri Jagan Sridharan Link: https://lore.kernel.org/r/20210201100212.49863-1-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 0afd8ef692e8..ff0732c12b8a 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -4897,6 +4897,17 @@ static void _tcpm_pd_vbus_off(struct tcpm_port *port) /* Do nothing, waiting for sink detection */ break; + case SRC_STARTUP: + case SRC_SEND_CAPABILITIES: + case SRC_SEND_CAPABILITIES_TIMEOUT: + case SRC_NEGOTIATE_CAPABILITIES: + case SRC_TRANSITION_SUPPLY: + case SRC_READY: + case SRC_WAIT_NEW_CAPABILITIES: + /* Force to unattached state to re-initiate connection */ + tcpm_set_state(port, SRC_UNATTACHED, 0); + break; + case PORT_RESET: /* * State set back to default mode once the timer completes. -- cgit v1.2.3 From 2b8ff93fd7443d7bd4c085ac0249d87238c755ba Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Mon, 1 Feb 2021 19:38:59 -0800 Subject: usb: typec: tcpm: Set in_ams flag when Source caps have been received Receiving the first packet in an AMS sequence signals the beginning of AMS. Set in_ams flag to true when SRC_CAPS are received during POWER_NEGOTIATION AMS. This fixes the failure flagged while running TD.PD.SNK.E9 compliance test. >From Deterministic PD compliance MOI spec: TD.PD.SNK.E9. GetSinkCap in Place of Accept Description: As Provider, the Tester intentionally sends a GetSinkCap message in place of Accept message and verifies the UUT will send a SoftReset and recover from the error. Steps: a) Run PROC.PD.E1 Bring-up according to the UUT role. b) The Tester cycles VBus. c) The Tester sends a Source Capabilities message to the UUT. d) Upon receipt of a Request message from the UUT, the Tester replies with a GoodCRC message. e) The Tester sends a GetSinkCap message to the UUT. f) If a SoftReset is not received within 15 ms after the GetSinkCap EOP was sent, the test fails. g) If a SoftReset is received timely, the Tester replies with an Accept message. h) The Tester sends Source Capabilities message to the UUT repeatedly until nCapsCount reached or a GoodCRC is received. If nCapsCount reached, the test fails. i) If a Request is not received timely within 30 ms after the GoodCRC EOP corresponding to Source Capabilities message was received, the test fails. Reviewed-by: Guenter Roeck Reviewed-by: Heikki Krogerus Signed-off-by: Badhri Jagan Sridharan Link: https://lore.kernel.org/r/20210202033859.258491-1-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index ff0732c12b8a..7747c7a154a4 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -2243,6 +2243,7 @@ static void tcpm_pd_data_request(struct tcpm_port *port, * handled. */ port->ams = POWER_NEGOTIATION; + port->in_ams = true; tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); } else { if (port->ams == GET_SOURCE_CAPABILITIES) -- cgit v1.2.3 From a69bdb283f79949b67632878ef1822badae9299f Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Mon, 1 Feb 2021 16:30:59 -0800 Subject: usb: typec: tcpm: Add Callback to Usb Communication capable partner The USB Communications Capable bit indicates if port partner is capable of communication over the USB data lines (e.g. D+/- or SS Tx/Rx). Notify the status of the bit to low level drivers to perform chip specific operation. For instance, low level driver enables USB switches on D+/D- lines to set up data path when the bit is set. Refactored from patch initially authored by Kyle Tso Reviewed-by: Heikki Krogerus Reviewed-by: Guenter Roeck Signed-off-by: Badhri Jagan Sridharan Link: https://lore.kernel.org/r/20210202003101.221145-1-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 13 +++++++++++++ include/linux/usb/tcpm.h | 5 +++++ 2 files changed, 18 insertions(+) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 7747c7a154a4..8558ab006885 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -3430,6 +3430,14 @@ static void tcpm_unregister_altmodes(struct tcpm_port *port) memset(modep, 0, sizeof(*modep)); } +static void tcpm_set_partner_usb_comm_capable(struct tcpm_port *port, bool capable) +{ + tcpm_log(port, "Setting usb_comm capable %s", capable ? "true" : "false"); + + if (port->tcpc->set_partner_usb_comm_capable) + port->tcpc->set_partner_usb_comm_capable(port->tcpc, capable); +} + static void tcpm_reset_port(struct tcpm_port *port) { int ret; @@ -3446,6 +3454,7 @@ static void tcpm_reset_port(struct tcpm_port *port) port->attached = false; port->pd_capable = false; port->pps_data.supported = false; + tcpm_set_partner_usb_comm_capable(port, false); /* * First Rx ID should be 0; set this to a sentinel of -1 so that @@ -3786,6 +3795,8 @@ static void run_state_machine(struct tcpm_port *port) } } else { tcpm_pd_send_control(port, PD_CTRL_ACCEPT); + tcpm_set_partner_usb_comm_capable(port, + !!(port->sink_request & RDO_USB_COMM)); tcpm_set_state(port, SRC_TRANSITION_SUPPLY, PD_T_SRC_TRANSITION); } @@ -4005,6 +4016,8 @@ static void run_state_machine(struct tcpm_port *port) break; case SNK_NEGOTIATE_CAPABILITIES: port->pd_capable = true; + tcpm_set_partner_usb_comm_capable(port, + !!(port->source_caps[0] & PDO_FIXED_USB_COMM)); port->hard_reset_count = 0; ret = tcpm_pd_send_request(port); if (ret < 0) { diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h index 3af99f85e8b9..42fcfbe10590 100644 --- a/include/linux/usb/tcpm.h +++ b/include/linux/usb/tcpm.h @@ -108,6 +108,10 @@ enum tcpm_transmit_type { * is supported by TCPC, set this callback for TCPM to query * whether vbus is at VSAFE0V when needed. * Returns true when vbus is at VSAFE0V, false otherwise. + * @set_partner_usb_comm_capable: + * Optional; The USB Communications Capable bit indicates if port + * partner is capable of communication over the USB data lines + * (e.g. D+/- or SS Tx/Rx). Called to notify the status of the bit. */ struct tcpc_dev { struct fwnode_handle *fwnode; @@ -139,6 +143,7 @@ struct tcpc_dev { int (*set_auto_vbus_discharge_threshold)(struct tcpc_dev *dev, enum typec_pwr_opmode mode, bool pps_active, u32 requested_vbus_voltage); bool (*is_vbus_vsafe0v)(struct tcpc_dev *dev); + void (*set_partner_usb_comm_capable)(struct tcpc_dev *dev, bool enable); }; struct tcpm_port; -- cgit v1.2.3 From 372a3d0b6b1e92d8138eeaed7366845a235475ef Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Mon, 1 Feb 2021 16:31:00 -0800 Subject: usb: typec: tcpci: Add Callback to Usb Communication capable partner The USB Communications Capable bit indicates if port partner is capable of communication over the USB data lines (e.g. D+/- or SS Tx/Rx). TCPM passes this information for chip specific operations. Reviewed-by: Heikki Krogerus Reviewed-by: Guenter Roeck Signed-off-by: Badhri Jagan Sridharan Link: https://lore.kernel.org/r/20210202003101.221145-2-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci.c | 9 +++++++++ drivers/usb/typec/tcpm/tcpci.h | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index f676abab044b..a27deb0b5f03 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -255,6 +255,14 @@ static int tcpci_set_polarity(struct tcpc_dev *tcpc, TCPC_TCPC_CTRL_ORIENTATION : 0); } +static void tcpci_set_partner_usb_comm_capable(struct tcpc_dev *tcpc, bool capable) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + + if (tcpci->data->set_partner_usb_comm_capable) + tcpci->data->set_partner_usb_comm_capable(tcpci, tcpci->data, capable); +} + static int tcpci_set_vconn(struct tcpc_dev *tcpc, bool enable) { struct tcpci *tcpci = tcpc_to_tcpci(tcpc); @@ -720,6 +728,7 @@ struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data) tcpci->tcpc.set_bist_data = tcpci_set_bist_data; tcpci->tcpc.enable_frs = tcpci_enable_frs; tcpci->tcpc.frs_sourcing_vbus = tcpci_frs_sourcing_vbus; + tcpci->tcpc.set_partner_usb_comm_capable = tcpci_set_partner_usb_comm_capable; if (tcpci->data->auto_discharge_disconnect) { tcpci->tcpc.enable_auto_vbus_discharge = tcpci_enable_auto_vbus_discharge; diff --git a/drivers/usb/typec/tcpm/tcpci.h b/drivers/usb/typec/tcpm/tcpci.h index c3c7d07d9b4e..57b6e24e0a0c 100644 --- a/drivers/usb/typec/tcpm/tcpci.h +++ b/drivers/usb/typec/tcpm/tcpci.h @@ -161,6 +161,10 @@ struct tcpci; * Optional; Enables TCPC to autonously discharge vbus on disconnect. * @vbus_vsafe0v: * optional; Set when TCPC can detect whether vbus is at VSAFE0V. + * @set_partner_usb_comm_capable: + * Optional; The USB Communications Capable bit indicates if port + * partner is capable of communication over the USB data lines + * (e.g. D+/- or SS Tx/Rx). Called to notify the status of the bit. */ struct tcpci_data { struct regmap *regmap; @@ -175,6 +179,8 @@ struct tcpci_data { enum typec_cc_status cc); int (*set_vbus)(struct tcpci *tcpci, struct tcpci_data *data, bool source, bool sink); void (*frs_sourcing_vbus)(struct tcpci *tcpci, struct tcpci_data *data); + void (*set_partner_usb_comm_capable)(struct tcpci *tcpci, struct tcpci_data *data, + bool capable); }; struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data); -- cgit v1.2.3 From 2a16e18c3400f7ab1deb826a98cf52153d03653e Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Mon, 1 Feb 2021 16:31:01 -0800 Subject: usb: typec: tcpci_maxim: Enable data path when partner is USB Comm capable Configure USB switches when partner is USB Communication capable. The is enabled USB data communication over D+/D- pins. Reviewed-by: Heikki Krogerus Reviewed-by: Guenter Roeck Signed-off-by: Badhri Jagan Sridharan Link: https://lore.kernel.org/r/20210202003101.221145-3-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci_maxim.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.c b/drivers/usb/typec/tcpm/tcpci_maxim.c index f1674a611033..041a1c393594 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim.c +++ b/drivers/usb/typec/tcpm/tcpci_maxim.c @@ -19,6 +19,9 @@ #define PD_ACTIVITY_TIMEOUT_MS 10000 #define TCPC_VENDOR_ALERT 0x80 +#define TCPC_VENDOR_USBSW_CTRL 0x93 +#define TCPC_VENDOR_USBSW_CTRL_ENABLE_USB_DATA 0x9 +#define TCPC_VENDOR_USBSW_CTRL_DISABLE_USB_DATA 0 #define TCPC_RECEIVE_BUFFER_COUNT_OFFSET 0 #define TCPC_RECEIVE_BUFFER_FRAME_TYPE_OFFSET 1 @@ -274,6 +277,21 @@ static void process_tx(struct max_tcpci_chip *chip, u16 status) max_tcpci_init_regs(chip); } +/* Enable USB switches when partner is USB communications capable */ +static void max_tcpci_set_partner_usb_comm_capable(struct tcpci *tcpci, struct tcpci_data *data, + bool capable) +{ + struct max_tcpci_chip *chip = tdata_to_max_tcpci(data); + int ret; + + ret = max_tcpci_write8(chip, TCPC_VENDOR_USBSW_CTRL, capable ? + TCPC_VENDOR_USBSW_CTRL_ENABLE_USB_DATA : + TCPC_VENDOR_USBSW_CTRL_DISABLE_USB_DATA); + + if (ret < 0) + dev_err(chip->dev, "Failed to enable USB switches"); +} + static irqreturn_t _max_tcpci_irq(struct max_tcpci_chip *chip, u16 status) { u16 mask; @@ -453,6 +471,7 @@ static int max_tcpci_probe(struct i2c_client *client, const struct i2c_device_id chip->data.frs_sourcing_vbus = max_tcpci_frs_sourcing_vbus; chip->data.auto_discharge_disconnect = true; chip->data.vbus_vsafe0v = true; + chip->data.set_partner_usb_comm_capable = max_tcpci_set_partner_usb_comm_capable; max_tcpci_init_regs(chip); chip->tcpci = tcpci_register_port(chip->dev, &chip->data); -- cgit v1.2.3 From 32e9b48d110ef5fae850036eafaf7895a25b37e3 Mon Sep 17 00:00:00 2001 From: Kyle Tso Date: Tue, 2 Feb 2021 17:55:12 +0800 Subject: usb: typec: Return void in typec_partner_set_pd_revision typec_partner_set_pd_revision doesn't need any return value. Fixes: 29b01295a829 ("usb: typec: Add typec_partner_set_pd_revision") Reviewed-by: Heikki Krogerus Reviewed-by: Benson Leung Signed-off-by: Kyle Tso Link: https://lore.kernel.org/r/20210202095512.761214-1-kyletso@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/class.c | 10 ++-------- include/linux/usb/typec.h | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index b6ceab3dc16b..a7d1bc83c2d4 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -755,15 +755,11 @@ EXPORT_SYMBOL_GPL(typec_partner_set_identity); * * This routine is used to report that the PD revision of the port partner has * become available. - * - * Returns 0 on success or negative error number on failure. */ -int typec_partner_set_pd_revision(struct typec_partner *partner, u16 pd_revision) +void typec_partner_set_pd_revision(struct typec_partner *partner, u16 pd_revision) { - int ret; - if (partner->pd_revision == pd_revision) - return 0; + return; partner->pd_revision = pd_revision; sysfs_notify(&partner->dev.kobj, NULL, "usb_power_delivery_revision"); @@ -773,8 +769,6 @@ int typec_partner_set_pd_revision(struct typec_partner *partner, u16 pd_revision "supports_usb_power_delivery"); } kobject_uevent(&partner->dev.kobj, KOBJ_CHANGE); - - return 0; } EXPORT_SYMBOL_GPL(typec_partner_set_pd_revision); diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index 4946eca742d5..a94df82ab62f 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -126,7 +126,7 @@ struct typec_altmode_desc { enum typec_port_data roles; }; -int typec_partner_set_pd_revision(struct typec_partner *partner, u16 pd_revision); +void typec_partner_set_pd_revision(struct typec_partner *partner, u16 pd_revision); int typec_partner_set_num_altmodes(struct typec_partner *partner, int num_altmodes); struct typec_altmode *typec_partner_register_altmode(struct typec_partner *partner, -- cgit v1.2.3 From 64eaa0fa66ac55965f793a8b65730299854e55cd Mon Sep 17 00:00:00 2001 From: Benson Leung Date: Tue, 2 Feb 2021 08:45:31 -0800 Subject: platform/chrome: cros_ec_typec: Fix call to typec_partner_set_pd_revision typec_partner_set_pd_revision returns void now. Fixes: cefc011f8daf ("platform/chrome: cros_ec_typec: Set Partner PD revision from status") Signed-off-by: Benson Leung Link: https://lore.kernel.org/r/20210202164531.3982778-1-bleung@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/platform/chrome/cros_ec_typec.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index a7778258d0a0..7b93dfd02999 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -842,11 +842,7 @@ static int cros_typec_handle_sop_disc(struct cros_typec_data *typec, int port_nu goto disc_exit; } - ret = typec_partner_set_pd_revision(port->partner, pd_revision); - if (ret < 0) { - dev_err(typec->dev, "Failed to update partner PD revision, port: %d\n", port_num); - goto disc_exit; - } + typec_partner_set_pd_revision(port->partner, pd_revision); memset(sop_disc, 0, EC_PROTO2_MAX_RESPONSE_SIZE); ret = cros_typec_ec_command(typec, 0, EC_CMD_TYPEC_DISCOVERY, &req, sizeof(req), -- cgit v1.2.3 From 0e1d6f55a12e47942ce207dfb93e23049b454c9e Mon Sep 17 00:00:00 2001 From: Kyle Tso Date: Wed, 3 Feb 2021 00:17:27 +0800 Subject: usb: pd: Update VDO definitions "PD Spec Revision 3.0 Version 2.0 + ECNs 2020-12-10" introduces several changes regarding the ID Header VDO and the Product Type VDOs. Signed-off-by: Kyle Tso Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20210202161733.932215-3-kyletso@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/class.c | 8 +- include/linux/usb/pd_vdo.h | 308 ++++++++++++++++++++++++++++++++++----------- 2 files changed, 242 insertions(+), 74 deletions(-) diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index a7d1bc83c2d4..b4a5d9d4564f 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -88,7 +88,7 @@ static const char * const typec_accessory_modes[] = { /* Product types defined in USB PD Specification R3.0 V2.0 */ static const char * const product_type_ufp[8] = { - [IDH_PTYPE_UNDEF] = "undefined", + [IDH_PTYPE_NOT_UFP] = "not_ufp", [IDH_PTYPE_HUB] = "hub", [IDH_PTYPE_PERIPH] = "peripheral", [IDH_PTYPE_PSD] = "psd", @@ -96,17 +96,17 @@ static const char * const product_type_ufp[8] = { }; static const char * const product_type_dfp[8] = { - [IDH_PTYPE_DFP_UNDEF] = "undefined", + [IDH_PTYPE_NOT_DFP] = "not_dfp", [IDH_PTYPE_DFP_HUB] = "hub", [IDH_PTYPE_DFP_HOST] = "host", [IDH_PTYPE_DFP_PB] = "power_brick", - [IDH_PTYPE_DFP_AMC] = "amc", }; static const char * const product_type_cable[8] = { - [IDH_PTYPE_UNDEF] = "undefined", + [IDH_PTYPE_NOT_CABLE] = "not_cable", [IDH_PTYPE_PCABLE] = "passive", [IDH_PTYPE_ACABLE] = "active", + [IDH_PTYPE_VPD] = "vpd", }; static struct usb_pd_identity *get_pd_identity(struct device *dev) diff --git a/include/linux/usb/pd_vdo.h b/include/linux/usb/pd_vdo.h index 8c08eeb9a74b..e9b6822c54c2 100644 --- a/include/linux/usb/pd_vdo.h +++ b/include/linux/usb/pd_vdo.h @@ -103,34 +103,46 @@ * -------------------- * <31> :: data capable as a USB host * <30> :: data capable as a USB device - * <29:27> :: product type (UFP / Cable) + * <29:27> :: product type (UFP / Cable / VPD) * <26> :: modal operation supported (1b == yes) - * <25:16> :: product type (DFP) + * <25:23> :: product type (DFP) (SVDM version 2.0+ only; set to zero in version 1.0) + * <22:21> :: connector type (SVDM version 2.0+ only; set to zero in version 1.0) + * <20:16> :: Reserved, Shall be set to zero * <15:0> :: USB-IF assigned VID for this cable vendor */ -#define IDH_PTYPE_UNDEF 0 +/* SOP Product Type (UFP) */ +#define IDH_PTYPE_NOT_UFP 0 #define IDH_PTYPE_HUB 1 #define IDH_PTYPE_PERIPH 2 #define IDH_PTYPE_PSD 3 #define IDH_PTYPE_AMA 5 +/* SOP' Product Type (Cable Plug / VPD) */ +#define IDH_PTYPE_NOT_CABLE 0 #define IDH_PTYPE_PCABLE 3 #define IDH_PTYPE_ACABLE 4 +#define IDH_PTYPE_VPD 6 -#define IDH_PTYPE_DFP_UNDEF 0 +/* SOP Product Type (DFP) */ +#define IDH_PTYPE_NOT_DFP 0 #define IDH_PTYPE_DFP_HUB 1 #define IDH_PTYPE_DFP_HOST 2 #define IDH_PTYPE_DFP_PB 3 -#define IDH_PTYPE_DFP_AMC 4 -#define VDO_IDH(usbh, usbd, ptype, is_modal, vid) \ - ((usbh) << 31 | (usbd) << 30 | ((ptype) & 0x7) << 27 \ - | (is_modal) << 26 | ((vid) & 0xffff)) +/* ID Header Mask */ +#define IDH_DFP_MASK GENMASK(25, 23) +#define IDH_CONN_MASK GENMASK(22, 21) + +#define VDO_IDH(usbh, usbd, ufp_cable, is_modal, dfp, conn, vid) \ + ((usbh) << 31 | (usbd) << 30 | ((ufp_cable) & 0x7) << 27 \ + | (is_modal) << 26 | ((dfp) & 0x7) << 23 | ((conn) & 0x3) << 21 \ + | ((vid) & 0xffff)) #define PD_IDH_PTYPE(vdo) (((vdo) >> 27) & 0x7) #define PD_IDH_VID(vdo) ((vdo) & 0xffff) #define PD_IDH_MODAL_SUPP(vdo) ((vdo) & (1 << 26)) #define PD_IDH_DFP_PTYPE(vdo) (((vdo) >> 23) & 0x7) +#define PD_IDH_CONN_TYPE(vdo) (((vdo) >> 21) & 0x3) /* * Cert Stat VDO @@ -138,6 +150,7 @@ * <31:0> : USB-IF assigned XID for this cable */ #define PD_CSTAT_XID(vdo) (vdo) +#define VDO_CERT(xid) ((xid) & 0xffffffff) /* * Product VDO @@ -149,112 +162,267 @@ #define PD_PRODUCT_PID(vdo) (((vdo) >> 16) & 0xffff) /* - * UFP VDO1 + * UFP VDO (PD Revision 3.0+ only) * -------- * <31:29> :: UFP VDO version * <28> :: Reserved * <27:24> :: Device capability - * <23:6> :: Reserved + * <23:22> :: Connector type (10b == receptacle, 11b == captive plug) + * <21:11> :: Reserved + * <10:8> :: Vconn power (AMA only) + * <7> :: Vconn required (AMA only, 0b == no, 1b == yes) + * <6> :: Vbus required (AMA only, 0b == yes, 1b == no) * <5:3> :: Alternate modes * <2:0> :: USB highest speed */ -#define PD_VDO1_UFP_DEVCAP(vdo) (((vdo) & GENMASK(27, 24)) >> 24) +#define PD_VDO_UFP_DEVCAP(vdo) (((vdo) & GENMASK(27, 24)) >> 24) + +/* UFP VDO Version */ +#define UFP_VDO_VER1_2 2 +/* Device Capability */ #define DEV_USB2_CAPABLE BIT(0) #define DEV_USB2_BILLBOARD BIT(1) #define DEV_USB3_CAPABLE BIT(2) #define DEV_USB4_CAPABLE BIT(3) +/* Connector Type */ +#define UFP_RECEPTACLE 2 +#define UFP_CAPTIVE 3 + +/* Vconn Power (AMA only, set to AMA_VCONN_NOT_REQ if Vconn is not required) */ +#define AMA_VCONN_PWR_1W 0 +#define AMA_VCONN_PWR_1W5 1 +#define AMA_VCONN_PWR_2W 2 +#define AMA_VCONN_PWR_3W 3 +#define AMA_VCONN_PWR_4W 4 +#define AMA_VCONN_PWR_5W 5 +#define AMA_VCONN_PWR_6W 6 + +/* Vconn Required (AMA only) */ +#define AMA_VCONN_NOT_REQ 0 +#define AMA_VCONN_REQ 1 + +/* Vbus Required (AMA only) */ +#define AMA_VBUS_REQ 0 +#define AMA_VBUS_NOT_REQ 1 + +/* Alternate Modes */ +#define UFP_ALTMODE_NOT_SUPP 0 +#define UFP_ALTMODE_TBT3 BIT(0) +#define UFP_ALTMODE_RECFG BIT(1) +#define UFP_ALTMODE_NO_RECFG BIT(2) + +/* USB Highest Speed */ +#define UFP_USB2_ONLY 0 +#define UFP_USB32_GEN1 1 +#define UFP_USB32_4_GEN2 2 +#define UFP_USB4_GEN3 3 + +#define VDO_UFP(ver, cap, conn, vcpwr, vcr, vbr, alt, spd) \ + (((ver) & 0x7) << 29 | ((cap) & 0xf) << 24 | ((conn) & 0x3) << 22 \ + | ((vcpwr) & 0x7) << 8 | (vcr) << 7 | (vbr) << 6 | ((alt) & 0x7) << 3 \ + | ((spd) & 0x7)) + /* - * DFP VDO + * DFP VDO (PD Revision 3.0+ only) * -------- * <31:29> :: DFP VDO version * <28:27> :: Reserved * <26:24> :: Host capability - * <23:5> :: Reserved + * <23:22> :: Connector type (10b == receptacle, 11b == captive plug) + * <21:5> :: Reserved * <4:0> :: Port number */ #define PD_VDO_DFP_HOSTCAP(vdo) (((vdo) & GENMASK(26, 24)) >> 24) +#define DFP_VDO_VER1_1 1 #define HOST_USB2_CAPABLE BIT(0) #define HOST_USB3_CAPABLE BIT(1) #define HOST_USB4_CAPABLE BIT(2) +#define DFP_RECEPTACLE 2 +#define DFP_CAPTIVE 3 + +#define VDO_DFP(ver, cap, conn, pnum) \ + (((ver) & 0x7) << 29 | ((cap) & 0x7) << 24 | ((conn) & 0x3) << 22 \ + | ((pnum) & 0x1f)) /* - * Cable VDO + * Passive Cable VDO * --------- * <31:28> :: Cable HW version * <27:24> :: Cable FW version - * <23:20> :: Reserved, Shall be set to zero - * <19:18> :: type-C to Type-A/B/C/Captive (00b == A, 01 == B, 10 == C, 11 == Captive) - * <17> :: Type-C to Plug/Receptacle (0b == plug, 1b == receptacle) + * <23:21> :: VDO version + * <20> :: Reserved, Shall be set to zero + * <19:18> :: Type-C to Type-C/Captive (10b == C, 11b == Captive) + * <17> :: Reserved, Shall be set to zero * <16:13> :: cable latency (0001 == <10ns(~1m length)) - * <12:11> :: cable termination type (11b == both ends active VCONN req) - * <10> :: SSTX1 Directionality support (0b == fixed, 1b == cfgable) - * <9> :: SSTX2 Directionality support - * <8> :: SSRX1 Directionality support - * <7> :: SSRX2 Directionality support - * <6:5> :: Vbus current handling capability + * <12:11> :: cable termination type (10b == Vconn not req, 01b == Vconn req) + * <10:9> :: Maximum Vbus voltage (00b == 20V, 01b == 30V, 10b == 40V, 11b == 50V) + * <8:7> :: Reserved, Shall be set to zero + * <6:5> :: Vbus current handling capability (01b == 3A, 10b == 5A) + * <4:3> :: Reserved, Shall be set to zero + * <2:0> :: USB highest speed + * + * Active Cable VDO 1 + * --------- + * <31:28> :: Cable HW version + * <27:24> :: Cable FW version + * <23:21> :: VDO version + * <20> :: Reserved, Shall be set to zero + * <19:18> :: Connector type (10b == C, 11b == Captive) + * <17> :: Reserved, Shall be set to zero + * <16:13> :: cable latency (0001 == <10ns(~1m length)) + * <12:11> :: cable termination type (10b == one end active, 11b == both ends active VCONN req) + * <10:9> :: Maximum Vbus voltage (00b == 20V, 01b == 30V, 10b == 40V, 11b == 50V) + * <8> :: SBU supported (0b == supported, 1b == not supported) + * <7> :: SBU type (0b == passive, 1b == active) + * <6:5> :: Vbus current handling capability (01b == 3A, 10b == 5A) * <4> :: Vbus through cable (0b == no, 1b == yes) * <3> :: SOP" controller present? (0b == no, 1b == yes) - * <2:0> :: USB SS Signaling support + * <2:0> :: USB highest speed */ -#define CABLE_ATYPE 0 -#define CABLE_BTYPE 1 +/* Cable VDO Version */ +#define CABLE_VDO_VER1_0 0 +#define CABLE_VDO_VER1_3 3 + +/* Connector Type */ #define CABLE_CTYPE 2 #define CABLE_CAPTIVE 3 -#define CABLE_PLUG 0 -#define CABLE_RECEPTACLE 1 -#define CABLE_CURR_1A5 0 + +/* Cable Latency */ +#define CABLE_LATENCY_1M 1 +#define CABLE_LATENCY_2M 2 +#define CABLE_LATENCY_3M 3 +#define CABLE_LATENCY_4M 4 +#define CABLE_LATENCY_5M 5 +#define CABLE_LATENCY_6M 6 +#define CABLE_LATENCY_7M 7 +#define CABLE_LATENCY_7M_PLUS 8 + +/* Cable Termination Type */ +#define PCABLE_VCONN_NOT_REQ 0 +#define PCABLE_VCONN_REQ 1 +#define ACABLE_ONE_END 2 +#define ACABLE_BOTH_END 3 + +/* Maximum Vbus Voltage */ +#define CABLE_MAX_VBUS_20V 0 +#define CABLE_MAX_VBUS_30V 1 +#define CABLE_MAX_VBUS_40V 2 +#define CABLE_MAX_VBUS_50V 3 + +/* Active Cable SBU Supported/Type */ +#define ACABLE_SBU_SUPP 0 +#define ACABLE_SBU_NOT_SUPP 1 +#define ACABLE_SBU_PASSIVE 0 +#define ACABLE_SBU_ACTIVE 1 + +/* Vbus Current Handling Capability */ +#define CABLE_CURR_DEF 0 #define CABLE_CURR_3A 1 #define CABLE_CURR_5A 2 -#define CABLE_USBSS_U2_ONLY 0 -#define CABLE_USBSS_U31_GEN1 1 -#define CABLE_USBSS_U31_GEN2 2 -#define VDO_CABLE(hw, fw, cbl, gdr, lat, term, tx1d, tx2d, rx1d, rx2d, cur,\ - vps, sopp, usbss) \ - (((hw) & 0x7) << 28 | ((fw) & 0x7) << 24 | ((cbl) & 0x3) << 18 \ - | (gdr) << 17 | ((lat) & 0x7) << 13 | ((term) & 0x3) << 11 \ - | (tx1d) << 10 | (tx2d) << 9 | (rx1d) << 8 | (rx2d) << 7 \ - | ((cur) & 0x3) << 5 | (vps) << 4 | (sopp) << 3 \ - | ((usbss) & 0x7)) + +/* USB Highest Speed */ +#define CABLE_USB2_ONLY 0 +#define CABLE_USB32_GEN1 1 +#define CABLE_USB32_4_GEN2 2 +#define CABLE_USB4_GEN3 3 + +#define VDO_PCABLE(hw, fw, ver, conn, lat, term, vbm, cur, spd) \ + (((hw) & 0xf) << 28 | ((fw) & 0xf) << 24 | ((ver) & 0x7) << 21 \ + | ((conn) & 0x3) << 18 | ((lat) & 0xf) << 13 | ((term) & 0x3) << 11 \ + | ((vbm) & 0x3) << 9 | ((cur) & 0x3) << 5 | ((spd) & 0x7)) +#define VDO_ACABLE1(hw, fw, ver, conn, lat, term, vbm, sbu, sbut, cur, vbt, sopp, spd) \ + (((hw) & 0xf) << 28 | ((fw) & 0xf) << 24 | ((ver) & 0x7) << 21 \ + | ((conn) & 0x3) << 18 | ((lat) & 0xf) << 13 | ((term) & 0x3) << 11 \ + | ((vbm) & 0x3) << 9 | (sbu) << 8 | (sbut) << 7 | ((cur) & 0x3) << 5 \ + | (vbt) << 4 | (sopp) << 3 | ((spd) & 0x7)) + #define VDO_TYPEC_CABLE_TYPE(vdo) (((vdo) >> 18) & 0x3) /* - * AMA VDO + * Active Cable VDO 2 * --------- - * <31:28> :: Cable HW version - * <27:24> :: Cable FW version - * <23:12> :: Reserved, Shall be set to zero - * <11> :: SSTX1 Directionality support (0b == fixed, 1b == cfgable) - * <10> :: SSTX2 Directionality support - * <9> :: SSRX1 Directionality support - * <8> :: SSRX2 Directionality support - * <7:5> :: Vconn power - * <4> :: Vconn power required - * <3> :: Vbus power required - * <2:0> :: USB SS Signaling support + * <31:24> :: Maximum operating temperature + * <23:16> :: Shutdown temperature + * <15> :: Reserved, Shall be set to zero + * <14:12> :: U3/CLd power + * <11> :: U3 to U0 transition mode (0b == direct, 1b == through U3S) + * <10> :: Physical connection (0b == copper, 1b == optical) + * <9> :: Active element (0b == redriver, 1b == retimer) + * <8> :: USB4 supported (0b == yes, 1b == no) + * <7:6> :: USB2 hub hops consumed + * <5> :: USB2 supported (0b == yes, 1b == no) + * <4> :: USB3.2 supported (0b == yes, 1b == no) + * <3> :: USB lanes supported (0b == one lane, 1b == two lanes) + * <2> :: Optically isolated active cable (0b == no, 1b == yes) + * <1> :: Reserved, Shall be set to zero + * <0> :: USB gen (0b == gen1, 1b == gen2+) */ -#define VDO_AMA(hw, fw, tx1d, tx2d, rx1d, rx2d, vcpwr, vcr, vbr, usbss) \ - (((hw) & 0x7) << 28 | ((fw) & 0x7) << 24 \ - | (tx1d) << 11 | (tx2d) << 10 | (rx1d) << 9 | (rx2d) << 8 \ - | ((vcpwr) & 0x7) << 5 | (vcr) << 4 | (vbr) << 3 \ - | ((usbss) & 0x7)) - -#define PD_VDO_AMA_VCONN_REQ(vdo) (((vdo) >> 4) & 1) -#define PD_VDO_AMA_VBUS_REQ(vdo) (((vdo) >> 3) & 1) +/* U3/CLd Power*/ +#define ACAB2_U3_CLD_10MW_PLUS 0 +#define ACAB2_U3_CLD_10MW 1 +#define ACAB2_U3_CLD_5MW 2 +#define ACAB2_U3_CLD_1MW 3 +#define ACAB2_U3_CLD_500UW 4 +#define ACAB2_U3_CLD_200UW 5 +#define ACAB2_U3_CLD_50UW 6 + +/* Other Active Cable VDO 2 Fields */ +#define ACAB2_U3U0_DIRECT 0 +#define ACAB2_U3U0_U3S 1 +#define ACAB2_PHY_COPPER 0 +#define ACAB2_PHY_OPTICAL 1 +#define ACAB2_REDRIVER 0 +#define ACAB2_RETIMER 1 +#define ACAB2_USB4_SUPP 0 +#define ACAB2_USB4_NOT_SUPP 1 +#define ACAB2_USB2_SUPP 0 +#define ACAB2_USB2_NOT_SUPP 1 +#define ACAB2_USB32_SUPP 0 +#define ACAB2_USB32_NOT_SUPP 1 +#define ACAB2_LANES_ONE 0 +#define ACAB2_LANES_TWO 1 +#define ACAB2_OPT_ISO_NO 0 +#define ACAB2_OPT_ISO_YES 1 +#define ACAB2_GEN_1 0 +#define ACAB2_GEN_2_PLUS 1 + +#define VDO_ACABLE2(mtemp, stemp, u3p, trans, phy, ele, u4, hops, u2, u32, lane, iso, gen) \ + (((mtemp) & 0xff) << 24 | ((stemp) & 0xff) << 16 | ((u3p) & 0x7) << 12 \ + | (trans) << 11 | (phy) << 10 | (ele) << 9 | (u4) << 8 \ + | ((hops) & 0x3) << 6 | (u2) << 5 | (u32) << 4 | (lane) << 3 \ + | (iso) << 2 | (gen)) -#define AMA_VCONN_PWR_1W 0 -#define AMA_VCONN_PWR_1W5 1 -#define AMA_VCONN_PWR_2W 2 -#define AMA_VCONN_PWR_3W 3 -#define AMA_VCONN_PWR_4W 4 -#define AMA_VCONN_PWR_5W 5 -#define AMA_VCONN_PWR_6W 6 -#define AMA_USBSS_U2_ONLY 0 -#define AMA_USBSS_U31_GEN1 1 -#define AMA_USBSS_U31_GEN2 2 -#define AMA_USBSS_BBONLY 3 +/* + * VPD VDO + * --------- + * <31:28> :: HW version + * <27:24> :: FW version + * <23:21> :: VDO version + * <20:17> :: Reserved, Shall be set to zero + * <16:15> :: Maximum Vbus voltage (00b == 20V, 01b == 30V, 10b == 40V, 11b == 50V) + * <14> :: Charge through current support (0b == 3A, 1b == 5A) + * <13> :: Reserved, Shall be set to zero + * <12:7> :: Vbus impedance + * <6:1> :: Ground impedance + * <0> :: Charge through support (0b == no, 1b == yes) + */ +#define VPD_VDO_VER1_0 0 +#define VPD_MAX_VBUS_20V 0 +#define VPD_MAX_VBUS_30V 1 +#define VPD_MAX_VBUS_40V 2 +#define VPD_MAX_VBUS_50V 3 +#define VPDCT_CURR_3A 0 +#define VPDCT_CURR_5A 1 +#define VPDCT_NOT_SUPP 0 +#define VPDCT_SUPP 1 + +#define VDO_VPD(hw, fw, ver, vbm, curr, vbi, gi, ct) \ + (((hw) & 0xf) << 28 | ((fw) & 0xf) << 24 | ((ver) & 0x7) << 21 \ + | ((vbm) & 0x3) << 15 | (curr) << 14 | ((vbi) & 0x3f) << 7 \ + | ((gi) & 0x3f) << 1 | (ct)) /* * SVDM Discover SVIDs request -> response -- cgit v1.2.3 From 5ae01e760d7128682f4e1b7fa2c7fc7acc254db7 Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Tue, 2 Feb 2021 11:54:38 -0600 Subject: dt-bindings: usb: generic-ehci: Add missing compatible strings The generic EHCI binding needs to document all the specific compatible strings so we can track undocumented compatible strings. Add all the compatible strings from in tree users. Turns out we also have the generic 'usb-ehci' compatible which is pretty much the same binding and the correct one for the example, so let's add it. Cc: Greg Kroah-Hartman Cc: linux-usb@vger.kernel.org Signed-off-by: Rob Herring Link: https://lore.kernel.org/r/20210202175439.3904060-1-robh@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/generic-ehci.yaml | 51 ++++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/generic-ehci.yaml b/Documentation/devicetree/bindings/usb/generic-ehci.yaml index 247ef00381ea..6816de7dfc00 100644 --- a/Documentation/devicetree/bindings/usb/generic-ehci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-ehci.yaml @@ -24,8 +24,53 @@ allOf: properties: compatible: - contains: - const: generic-ehci + oneOf: + - items: + - enum: + - allwinner,sun4i-a10-ehci + - allwinner,sun50i-a64-ehci + - allwinner,sun50i-h6-ehci + - allwinner,sun5i-a13-ehci + - allwinner,sun6i-a31-ehci + - allwinner,sun7i-a20-ehci + - allwinner,sun8i-a23-ehci + - allwinner,sun8i-h3-ehci + - allwinner,sun8i-r40-ehci + - allwinner,sun9i-a80-ehci + - aspeed,ast2400-ehci + - aspeed,ast2500-ehci + - aspeed,ast2600-ehci + - brcm,bcm3384-ehci + - brcm,bcm63268-ehci + - brcm,bcm6328-ehci + - brcm,bcm6358-ehci + - brcm,bcm6362-ehci + - brcm,bcm6368-ehci + - brcm,bcm7125-ehci + - brcm,bcm7346-ehci + - brcm,bcm7358-ehci + - brcm,bcm7360-ehci + - brcm,bcm7362-ehci + - brcm,bcm7420-ehci + - brcm,bcm7425-ehci + - brcm,bcm7435-ehci + - ibm,476gtr-ehci + - nxp,lpc1850-ehci + - qca,ar7100-ehci + - snps,hsdk-v1.0-ehci + - socionext,uniphier-ehci + - const: generic-ehci + - items: + - enum: + - cavium,octeon-6335-ehci + - ibm,usb-ehci-440epx + - ibm,usb-ehci-460ex + - nintendo,hollywood-usb-ehci + - st,spear600-ehci + - const: usb-ehci + - enum: + - generic-ehci + - usb-ehci reg: minItems: 1 @@ -101,7 +146,7 @@ additionalProperties: false examples: - | usb@e0000300 { - compatible = "ibm,usb-ehci-440epx", "generic-ehci"; + compatible = "ibm,usb-ehci-440epx", "usb-ehci"; interrupt-parent = <&UIC0>; interrupts = <0x1a 4>; reg = <0xe0000300 90>, <0xe0000390 70>; -- cgit v1.2.3 From 8a61bbfe88812d1b539480fa73c0d579d70c2bb7 Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Tue, 2 Feb 2021 11:54:39 -0600 Subject: dt-bindings: usb: generic-ohci: Add missing compatible strings The generic OHCI binding needs to document all the specific compatible strings so we can track undocumented compatible strings. Add all the compatible strings from in tree users. Cc: Greg Kroah-Hartman Cc: linux-usb@vger.kernel.org Acked-by: Greg Kroah-Hartman Signed-off-by: Rob Herring Link: https://lore.kernel.org/r/20210202175439.3904060-2-robh@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/generic-ohci.yaml | 34 ++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/generic-ohci.yaml b/Documentation/devicetree/bindings/usb/generic-ohci.yaml index 2178bcc401bc..53df281f618c 100644 --- a/Documentation/devicetree/bindings/usb/generic-ohci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-ohci.yaml @@ -14,8 +14,38 @@ maintainers: properties: compatible: - contains: - const: generic-ohci + oneOf: + - items: + - enum: + - allwinner,sun4i-a10-ohci + - allwinner,sun50i-a64-ohci + - allwinner,sun50i-h6-ohci + - allwinner,sun5i-a13-ohci + - allwinner,sun6i-a31-ohci + - allwinner,sun7i-a20-ohci + - allwinner,sun8i-a23-ohci + - allwinner,sun8i-h3-ohci + - allwinner,sun8i-r40-ohci + - allwinner,sun9i-a80-ohci + - brcm,bcm3384-ohci + - brcm,bcm63268-ohci + - brcm,bcm6328-ohci + - brcm,bcm6358-ohci + - brcm,bcm6362-ohci + - brcm,bcm6368-ohci + - brcm,bcm7125-ohci + - brcm,bcm7346-ohci + - brcm,bcm7358-ohci + - brcm,bcm7360-ohci + - brcm,bcm7362-ohci + - brcm,bcm7420-ohci + - brcm,bcm7425-ohci + - brcm,bcm7435-ohci + - ibm,476gtr-ohci + - ingenic,jz4740-ohci + - snps,hsdk-v1.0-ohci + - const: generic-ohci + - const: generic-ohci reg: maxItems: 1 -- cgit v1.2.3 From d021e0694d77ee3cdc5d3fca2c8d53ae7575499a Mon Sep 17 00:00:00 2001 From: Bhaskar Chowdhury Date: Wed, 3 Feb 2021 21:04:14 +0530 Subject: doc: devicetree: bindings: usb: Change descibe to describe in usbmisc-imx.txt s/descibe/describe/ Acked-by: Randy Dunlap Signed-off-by: Bhaskar Chowdhury Link: https://lore.kernel.org/r/20210203153414.17044-1-unixbhaskar@gmail.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usbmisc-imx.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/usbmisc-imx.txt b/Documentation/devicetree/bindings/usb/usbmisc-imx.txt index b353b9816487..b796836d2ce7 100644 --- a/Documentation/devicetree/bindings/usb/usbmisc-imx.txt +++ b/Documentation/devicetree/bindings/usb/usbmisc-imx.txt @@ -1,7 +1,7 @@ * Freescale i.MX non-core registers Required properties: -- #index-cells: Cells used to descibe usb controller index. Should be <1> +- #index-cells: Cells used to describe usb controller index. Should be <1> - compatible: Should be one of below: "fsl,imx6q-usbmisc" for imx6q "fsl,vf610-usbmisc" for Vybrid vf610 -- cgit v1.2.3 From 2a1673f0f1de78b146bfdbe8c8a773c4a0499790 Mon Sep 17 00:00:00 2001 From: Kyle Tso Date: Thu, 4 Feb 2021 08:50:36 +0800 Subject: usb: pd: Reland VDO definitions of PD2.0 Reland VDO definitions of PD Revision 2.0 as they are still used in PD2.0 products. Fixes: 0e1d6f55a12e ("usb: pd: Update VDO definitions") Signed-off-by: Kyle Tso Link: https://lore.kernel.org/r/20210204005036.1555294-1-kyletso@google.com Signed-off-by: Greg Kroah-Hartman --- include/linux/usb/pd_vdo.h | 69 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/include/linux/usb/pd_vdo.h b/include/linux/usb/pd_vdo.h index e9b6822c54c2..5de7f550f93e 100644 --- a/include/linux/usb/pd_vdo.h +++ b/include/linux/usb/pd_vdo.h @@ -110,6 +110,10 @@ * <20:16> :: Reserved, Shall be set to zero * <15:0> :: USB-IF assigned VID for this cable vendor */ + +/* PD Rev2.0 definition */ +#define IDH_PTYPE_UNDEF 0 + /* SOP Product Type (UFP) */ #define IDH_PTYPE_NOT_UFP 0 #define IDH_PTYPE_HUB 1 @@ -248,7 +252,25 @@ | ((pnum) & 0x1f)) /* - * Passive Cable VDO + * Cable VDO (for both Passive and Active Cable VDO in PD Rev2.0) + * --------- + * <31:28> :: Cable HW version + * <27:24> :: Cable FW version + * <23:20> :: Reserved, Shall be set to zero + * <19:18> :: type-C to Type-A/B/C/Captive (00b == A, 01 == B, 10 == C, 11 == Captive) + * <17> :: Reserved, Shall be set to zero + * <16:13> :: cable latency (0001 == <10ns(~1m length)) + * <12:11> :: cable termination type (11b == both ends active VCONN req) + * <10> :: SSTX1 Directionality support (0b == fixed, 1b == cfgable) + * <9> :: SSTX2 Directionality support + * <8> :: SSRX1 Directionality support + * <7> :: SSRX2 Directionality support + * <6:5> :: Vbus current handling capability (01b == 3A, 10b == 5A) + * <4> :: Vbus through cable (0b == no, 1b == yes) + * <3> :: SOP" controller present? (0b == no, 1b == yes) + * <2:0> :: USB SS Signaling support + * + * Passive Cable VDO (PD Rev3.0+) * --------- * <31:28> :: Cable HW version * <27:24> :: Cable FW version @@ -264,7 +286,7 @@ * <4:3> :: Reserved, Shall be set to zero * <2:0> :: USB highest speed * - * Active Cable VDO 1 + * Active Cable VDO 1 (PD Rev3.0+) * --------- * <31:28> :: Cable HW version * <27:24> :: Cable FW version @@ -286,7 +308,9 @@ #define CABLE_VDO_VER1_0 0 #define CABLE_VDO_VER1_3 3 -/* Connector Type */ +/* Connector Type (_ATYPE and _BTYPE are for PD Rev2.0 only) */ +#define CABLE_ATYPE 0 +#define CABLE_BTYPE 1 #define CABLE_CTYPE 2 #define CABLE_CAPTIVE 3 @@ -323,12 +347,22 @@ #define CABLE_CURR_3A 1 #define CABLE_CURR_5A 2 +/* USB SuperSpeed Signaling Support (PD Rev2.0) */ +#define CABLE_USBSS_U2_ONLY 0 +#define CABLE_USBSS_U31_GEN1 1 +#define CABLE_USBSS_U31_GEN2 2 + /* USB Highest Speed */ #define CABLE_USB2_ONLY 0 #define CABLE_USB32_GEN1 1 #define CABLE_USB32_4_GEN2 2 #define CABLE_USB4_GEN3 3 +#define VDO_CABLE(hw, fw, cbl, lat, term, tx1d, tx2d, rx1d, rx2d, cur, vps, sopp, usbss) \ + (((hw) & 0x7) << 28 | ((fw) & 0x7) << 24 | ((cbl) & 0x3) << 18 \ + | ((lat) & 0x7) << 13 | ((term) & 0x3) << 11 | (tx1d) << 10 \ + | (tx2d) << 9 | (rx1d) << 8 | (rx2d) << 7 | ((cur) & 0x3) << 5 \ + | (vps) << 4 | (sopp) << 3 | ((usbss) & 0x7)) #define VDO_PCABLE(hw, fw, ver, conn, lat, term, vbm, cur, spd) \ (((hw) & 0xf) << 28 | ((fw) & 0xf) << 24 | ((ver) & 0x7) << 21 \ | ((conn) & 0x3) << 18 | ((lat) & 0xf) << 13 | ((term) & 0x3) << 11 \ @@ -395,6 +429,35 @@ | ((hops) & 0x3) << 6 | (u2) << 5 | (u32) << 4 | (lane) << 3 \ | (iso) << 2 | (gen)) +/* + * AMA VDO (PD Rev2.0) + * --------- + * <31:28> :: Cable HW version + * <27:24> :: Cable FW version + * <23:12> :: Reserved, Shall be set to zero + * <11> :: SSTX1 Directionality support (0b == fixed, 1b == cfgable) + * <10> :: SSTX2 Directionality support + * <9> :: SSRX1 Directionality support + * <8> :: SSRX2 Directionality support + * <7:5> :: Vconn power + * <4> :: Vconn power required + * <3> :: Vbus power required + * <2:0> :: USB SS Signaling support + */ +#define VDO_AMA(hw, fw, tx1d, tx2d, rx1d, rx2d, vcpwr, vcr, vbr, usbss) \ + (((hw) & 0x7) << 28 | ((fw) & 0x7) << 24 \ + | (tx1d) << 11 | (tx2d) << 10 | (rx1d) << 9 | (rx2d) << 8 \ + | ((vcpwr) & 0x7) << 5 | (vcr) << 4 | (vbr) << 3 \ + | ((usbss) & 0x7)) + +#define PD_VDO_AMA_VCONN_REQ(vdo) (((vdo) >> 4) & 1) +#define PD_VDO_AMA_VBUS_REQ(vdo) (((vdo) >> 3) & 1) + +#define AMA_USBSS_U2_ONLY 0 +#define AMA_USBSS_U31_GEN1 1 +#define AMA_USBSS_U31_GEN2 2 +#define AMA_USBSS_BBONLY 3 + /* * VPD VDO * --------- -- cgit v1.2.3 From 9c3db0b7b29a5078f34b1e41c76d22a736f95c24 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 28 Jan 2021 13:05:55 +0300 Subject: thunderbolt: ctl: Fix kernel-doc descriptions of non-static functions Fix kernel-doc descriptions of all non-static functions and struct tb_cfg. Gets rid of several warnings on W=1 builds too. Reported-by: Lee Jones Signed-off-by: Mika Westerberg Reviewed-by: Lee Jones --- drivers/thunderbolt/ctl.c | 47 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index 2f9d4fc8aa23..f1aeaff9f368 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -20,7 +20,17 @@ #define TB_CTL_RETRIES 4 /** - * struct tb_cfg - thunderbolt control channel + * struct tb_ctl - Thunderbolt control channel + * @nhi: Pointer to the NHI structure + * @tx: Transmit ring + * @rx: Receive ring + * @frame_pool: DMA pool for control messages + * @rx_packets: Received control messages + * @request_queue_lock: Lock protecting @request_queue + * @request_queue: List of outstanding requests + * @running: Is the control channel running at the moment + * @callback: Callback called when hotplug message is received + * @callback_data: Data passed to @callback */ struct tb_ctl { struct tb_nhi *nhi; @@ -602,6 +612,9 @@ struct tb_cfg_result tb_cfg_request_sync(struct tb_ctl *ctl, /** * tb_ctl_alloc() - allocate a control channel + * @nhi: Pointer to NHI + * @cb: Callback called for plug events + * @cb_data: Data passed to @cb * * cb will be invoked once for every hot plug event. * @@ -649,6 +662,7 @@ err: /** * tb_ctl_free() - free a control channel + * @ctl: Control channel to free * * Must be called after tb_ctl_stop. * @@ -677,6 +691,7 @@ void tb_ctl_free(struct tb_ctl *ctl) /** * tb_cfg_start() - start/resume the control channel + * @ctl: Control channel to start */ void tb_ctl_start(struct tb_ctl *ctl) { @@ -691,7 +706,8 @@ void tb_ctl_start(struct tb_ctl *ctl) } /** - * control() - pause the control channel + * tb_ctrl_stop() - pause the control channel + * @ctl: Control channel to stop * * All invocations of ctl->callback will have finished after this method * returns. @@ -784,6 +800,9 @@ static bool tb_cfg_copy(struct tb_cfg_request *req, const struct ctl_pkg *pkg) /** * tb_cfg_reset() - send a reset packet and wait for a response + * @ctl: Control channel pointer + * @route: Router string for the router to send reset + * @timeout_msec: Timeout in ms how long to wait for the response * * If the switch at route is incorrectly configured then we will not receive a * reply (even though the switch will reset). The caller should check for @@ -820,9 +839,17 @@ struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route, } /** - * tb_cfg_read() - read from config space into buffer + * tb_cfg_read_raw() - read from config space into buffer + * @ctl: Pointer to the control channel + * @buffer: Buffer where the data is read + * @route: Route string of the router + * @port: Port number when reading from %TB_CFG_PORT, %0 otherwise + * @space: Config space selector + * @offset: Dword word offset of the register to start reading + * @length: Number of dwords to read + * @timeout_msec: Timeout in ms how long to wait for the response * - * Offset and length are in dwords. + * Reads from router config space without translating the possible error. */ struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer, u64 route, u32 port, enum tb_cfg_space space, @@ -884,8 +911,16 @@ struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer, /** * tb_cfg_write() - write from buffer into config space + * @ctl: Pointer to the control channel + * @buffer: Data to write + * @route: Route string of the router + * @port: Port number when writing to %TB_CFG_PORT, %0 otherwise + * @space: Config space selector + * @offset: Dword word offset of the register to start writing + * @length: Number of dwords to write + * @timeout_msec: Timeout in ms how long to wait for the response * - * Offset and length are in dwords. + * Writes to router config space without translating the possible error. */ struct tb_cfg_result tb_cfg_write_raw(struct tb_ctl *ctl, const void *buffer, u64 route, u32 port, enum tb_cfg_space space, @@ -1022,6 +1057,8 @@ int tb_cfg_write(struct tb_ctl *ctl, const void *buffer, u64 route, u32 port, /** * tb_cfg_get_upstream_port() - get upstream port number of switch at route + * @ctl: Pointer to the control channel + * @route: Route string of the router * * Reads the first dword from the switches TB_CFG_SWITCH config area and * returns the port number from which the reply originated. -- cgit v1.2.3 From b12e4824f14073520ff8a65ab4ffee03ad42569b Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 28 Jan 2021 13:18:13 +0300 Subject: thunderbolt: eeprom: Fix kernel-doc descriptions of non-static functions Fix kernel-doc descriptions of the two non-static functions. This also gets rid of the rest of the warnings on W=1 build. Reported-by: Lee Jones Signed-off-by: Mika Westerberg Reviewed-by: Lee Jones --- drivers/thunderbolt/eeprom.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index 63c64f503faa..dd03d3096653 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -279,7 +279,9 @@ struct tb_drom_entry_port { /** - * tb_drom_read_uid_only - read uid directly from drom + * tb_drom_read_uid_only() - Read UID directly from DROM + * @sw: Router whose UID to read + * @uid: UID is placed here * * Does not use the cached copy in sw->drom. Used during resume to check switch * identity. @@ -520,7 +522,14 @@ static int tb_drom_read_n(struct tb_switch *sw, u16 offset, u8 *val, } /** - * tb_drom_read - copy drom to sw->drom and parse it + * tb_drom_read() - Copy DROM to sw->drom and parse it + * @sw: Router whose DROM to read and parse + * + * This function reads router DROM and if successful parses the entries and + * populates the fields in @sw accordingly. Can be called for any router + * generation. + * + * Returns %0 in case of success and negative errno otherwise. */ int tb_drom_read(struct tb_switch *sw) { -- cgit v1.2.3 From 5fbcb2d1278233be73ec0a439263fbd4353b3990 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 28 Jan 2021 13:23:44 +0300 Subject: thunderbolt: path: Fix kernel-doc descriptions of non-static functions Fix kernel-doc descriptions of the two non-static functions. This also gets rid of the warnings on W=1 build. Reported-by: Lee Jones Signed-off-by: Mika Westerberg Reviewed-by: Lee Jones --- drivers/thunderbolt/path.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/thunderbolt/path.c b/drivers/thunderbolt/path.c index ca7d738d66de..f63e205a35d9 100644 --- a/drivers/thunderbolt/path.c +++ b/drivers/thunderbolt/path.c @@ -466,6 +466,7 @@ void tb_path_deactivate(struct tb_path *path) /** * tb_path_activate() - activate a path + * @path: Path to activate * * Activate a path starting with the last hop and iterating backwards. The * caller must fill path->hops before calling tb_path_activate(). @@ -561,6 +562,7 @@ err: /** * tb_path_is_invalid() - check whether any ports on the path are invalid + * @path: Path to check * * Return: Returns true if the path is invalid, false otherwise. */ -- cgit v1.2.3 From 6894bd37537d579b7f6fc9f1229777c682fd4ba9 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 28 Jan 2021 13:39:56 +0300 Subject: thunderbolt: nhi: Fix kernel-doc descriptions of non-static functions Fix kernel-doc descriptions of the two non-static functions. This also gets rids of the warnings on W=1 build. Reported-by: Lee Jones Signed-off-by: Mika Westerberg Reviewed-by: Lee Jones --- drivers/thunderbolt/nhi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 7073c25248b3..782404eb10b0 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -592,6 +592,7 @@ EXPORT_SYMBOL_GPL(tb_ring_alloc_rx); /** * tb_ring_start() - enable a ring + * @ring: Ring to start * * Must not be invoked in parallel with tb_ring_stop(). */ @@ -667,6 +668,7 @@ EXPORT_SYMBOL_GPL(tb_ring_start); /** * tb_ring_stop() - shutdown a ring + * @ring: Ring to stop * * Must not be invoked from a callback. * -- cgit v1.2.3 From 5c6b471b6ca26c8c67385e1a90e635b4205738a9 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 28 Jan 2021 13:51:03 +0300 Subject: thunderbolt: switch: Fix kernel-doc descriptions of non-static functions Fix kernel-doc descriptions of all non-static functions. This also gets rid of the warnings on W=1 build. Reported-by: Lee Jones Signed-off-by: Mika Westerberg Reviewed-by: Lee Jones --- drivers/thunderbolt/switch.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 6ca3efba4520..5377d0a3390f 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -525,6 +525,8 @@ int tb_port_state(struct tb_port *port) /** * tb_wait_for_port() - wait for a port to become ready + * @port: Port to wait + * @wait_if_unplugged: Wait also when port is unplugged * * Wait up to 1 second for a port to reach state TB_PORT_UP. If * wait_if_unplugged is set then we also wait if the port is in state @@ -589,6 +591,8 @@ int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged) /** * tb_port_add_nfc_credits() - add/remove non flow controlled credits to port + * @port: Port to add/remove NFC credits + * @credits: Credits to add/remove * * Change the number of NFC credits allocated to @port by @credits. To remove * NFC credits pass a negative amount of credits. @@ -646,6 +650,8 @@ int tb_port_set_initial_credits(struct tb_port *port, u32 credits) /** * tb_port_clear_counter() - clear a counter in TB_CFG_COUNTER + * @port: Port whose counters to clear + * @counter: Counter index to clear * * Return: Returns 0 on success or an error code on failure. */ @@ -2649,6 +2655,7 @@ void tb_switch_remove(struct tb_switch *sw) /** * tb_sw_set_unplugged() - set is_unplugged on switch and downstream switches + * @sw: Router to mark unplugged */ void tb_sw_set_unplugged(struct tb_switch *sw) { -- cgit v1.2.3 From c3963a5563d3baa8e46f6af7926893358a7319e6 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Mon, 1 Feb 2021 15:03:00 +0300 Subject: thunderbolt: Add clarifying comments about USB4 terms router and adapter USB4 spec talks about routers and adapters whereas Thunderbolt 1-3 talked about CIO (Converged I/O) switches and ports. These are the same thing but might cause confusion so add clarifying comments to struct tb_switch and struct tb_port about the USB4 terms. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 31468de658e4..37679b54808d 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -138,6 +138,8 @@ struct tb_switch_tmu { * * When the switch is being added or removed to the domain (other * switches) you need to have domain lock held. + * + * In USB4 terminology this structure represents a router. */ struct tb_switch { struct device dev; @@ -196,6 +198,9 @@ struct tb_switch { * @in_hopids: Currently allocated input HopIDs * @out_hopids: Currently allocated output HopIDs * @list: Used to link ports to DP resources list + * + * In USB4 terminology this structure represents an adapter (protocol or + * lane adapter). */ struct tb_port { struct tb_regs_port_header config; -- cgit v1.2.3 From 8b0ab503c07ecd0f9754796bf0ae5c3c1a2a6388 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Fri, 15 Jan 2021 11:01:21 +0300 Subject: thunderbolt: dma_test: Drop unnecessary include It seems is not actually needed in this driver so we can drop it. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/dma_test.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/thunderbolt/dma_test.c b/drivers/thunderbolt/dma_test.c index 955f980986ca..6debaf5a6604 100644 --- a/drivers/thunderbolt/dma_test.c +++ b/drivers/thunderbolt/dma_test.c @@ -7,7 +7,6 @@ * Mika Westerberg */ -#include #include #include #include -- cgit v1.2.3 From 3cd542e6e6afb6fa6c34d4094d498f42e22110f5 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 3 Sep 2020 13:13:21 +0300 Subject: thunderbolt: Add support for PCIe tunneling disabled (SL5) Recent Intel Thunderbolt firmware connection manager has support for another security level, SL5, that disables PCIe tunneling. This option can be turned on from the BIOS. When this is set the driver exposes a new security level "nopcie" to the userspace and hides the authorized attribute under connected devices. While there we also hide it when "dponly" security level is enabled since it is not really usable in that case anyway. Signed-off-by: Mika Westerberg Acked-by: Yehezkel Bernat --- Documentation/ABI/testing/sysfs-bus-thunderbolt | 2 ++ Documentation/admin-guide/thunderbolt.rst | 7 +++++++ drivers/thunderbolt/domain.c | 12 +++++++++++- drivers/thunderbolt/switch.c | 6 +++++- include/linux/thunderbolt.h | 3 +++ 5 files changed, 28 insertions(+), 2 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-bus-thunderbolt b/Documentation/ABI/testing/sysfs-bus-thunderbolt index 581dea95245b..d7f09d011b6d 100644 --- a/Documentation/ABI/testing/sysfs-bus-thunderbolt +++ b/Documentation/ABI/testing/sysfs-bus-thunderbolt @@ -85,6 +85,8 @@ Description: This attribute holds current Thunderbolt security level usbonly Automatically tunnel USB controller of the connected Thunderbolt dock (and Display Port). All PCIe links downstream of the dock are removed. + nopcie USB4 system where PCIe tunneling is disabled from + the BIOS. ======= ================================================== What: /sys/bus/thunderbolt/devices/.../authorized diff --git a/Documentation/admin-guide/thunderbolt.rst b/Documentation/admin-guide/thunderbolt.rst index 0d4348445f91..f18e881373c4 100644 --- a/Documentation/admin-guide/thunderbolt.rst +++ b/Documentation/admin-guide/thunderbolt.rst @@ -47,6 +47,9 @@ be DMA masters and thus read contents of the host memory without CPU and OS knowing about it. There are ways to prevent this by setting up an IOMMU but it is not always available for various reasons. +Some USB4 systems have a BIOS setting to disable PCIe tunneling. This is +treated as another security level (nopcie). + The security levels are as follows: none @@ -77,6 +80,10 @@ The security levels are as follows: Display Port in a dock. All PCIe links downstream of the dock are removed. + nopcie + PCIe tunneling is disabled/forbidden from the BIOS. Available in some + USB4 systems. + The current security level can be read from ``/sys/bus/thunderbolt/devices/domainX/security`` where ``domainX`` is the Thunderbolt domain the host controller manages. There is typically diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 9ba2181464cc..a1c79c9c4f66 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -118,6 +118,7 @@ static const char * const tb_security_names[] = { [TB_SECURITY_SECURE] = "secure", [TB_SECURITY_DPONLY] = "dponly", [TB_SECURITY_USBONLY] = "usbonly", + [TB_SECURITY_NOPCIE] = "nopcie", }; static ssize_t boot_acl_show(struct device *dev, struct device_attribute *attr, @@ -243,8 +244,14 @@ static ssize_t deauthorization_show(struct device *dev, char *buf) { const struct tb *tb = container_of(dev, struct tb, dev); + bool deauthorization = false; - return sprintf(buf, "%d\n", !!tb->cm_ops->disapprove_switch); + /* Only meaningful if authorization is supported */ + if (tb->security_level == TB_SECURITY_USER || + tb->security_level == TB_SECURITY_SECURE) + deauthorization = !!tb->cm_ops->disapprove_switch; + + return sprintf(buf, "%d\n", deauthorization); } static DEVICE_ATTR_RO(deauthorization); @@ -452,6 +459,9 @@ int tb_domain_add(struct tb *tb) goto err_ctl_stop; } + tb_dbg(tb, "security level set to %s\n", + tb_security_names[tb->security_level]); + ret = device_add(&tb->dev); if (ret) goto err_ctl_stop; diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 5377d0a3390f..b63fecca6c2a 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -1774,7 +1774,11 @@ static umode_t switch_attr_is_visible(struct kobject *kobj, struct device *dev = kobj_to_dev(kobj); struct tb_switch *sw = tb_to_switch(dev); - if (attr == &dev_attr_device.attr) { + if (attr == &dev_attr_authorized.attr) { + if (sw->tb->security_level == TB_SECURITY_NOPCIE || + sw->tb->security_level == TB_SECURITY_DPONLY) + return 0; + } else if (attr == &dev_attr_device.attr) { if (!sw->device) return 0; } else if (attr == &dev_attr_device_name.attr) { diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index 034dccf93955..659a0a810fa1 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -45,6 +45,8 @@ enum tb_cfg_pkg_type { * @TB_SECURITY_USBONLY: Only tunnel USB controller of the connected * Thunderbolt dock (and Display Port). All PCIe * links downstream of the dock are removed. + * @TB_SECURITY_NOPCIE: For USB4 systems this level is used when the + * PCIe tunneling is disabled from the BIOS. */ enum tb_security_level { TB_SECURITY_NONE, @@ -52,6 +54,7 @@ enum tb_security_level { TB_SECURITY_SECURE, TB_SECURITY_DPONLY, TB_SECURITY_USBONLY, + TB_SECURITY_NOPCIE, }; /** -- cgit v1.2.3 From 5ca67688256a0aeede5cba288eaef4d8e4a9e622 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 22 Oct 2020 13:22:06 +0300 Subject: thunderbolt: Allow disabling XDomain protocol This allows disabling XDomain protocol completely if the user does not plan to use the USB4/Thunderbolt peer-to-peer functionality, or for security reasons. XDomain protocol is enabled by default but with this commit it is possible to disable it by passing "xdomain=0" as module parameter (or through the kernel command line). Signed-off-by: Mika Westerberg Acked-by: Yehezkel Bernat --- drivers/thunderbolt/domain.c | 4 +++- drivers/thunderbolt/icm.c | 6 ++++-- drivers/thunderbolt/tb.c | 3 +++ drivers/thunderbolt/tb.h | 1 + drivers/thunderbolt/xdomain.c | 9 +++++++++ 5 files changed, 20 insertions(+), 3 deletions(-) diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index a1c79c9c4f66..89ae614eaba2 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -412,7 +412,9 @@ static bool tb_domain_event_cb(void *data, enum tb_cfg_pkg_type type, switch (type) { case TB_CFG_PKG_XDOMAIN_REQ: case TB_CFG_PKG_XDOMAIN_RESP: - return tb_xdomain_handle_request(tb, type, buf, size); + if (tb_is_xdomain_enabled()) + return tb_xdomain_handle_request(tb, type, buf, size); + break; default: tb->cm_ops->handle_event(tb, type, buf, size); diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index 14340ec61703..8cd7b3054d14 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -1701,10 +1701,12 @@ static void icm_handle_notification(struct work_struct *work) icm->device_disconnected(tb, n->pkg); break; case ICM_EVENT_XDOMAIN_CONNECTED: - icm->xdomain_connected(tb, n->pkg); + if (tb_is_xdomain_enabled()) + icm->xdomain_connected(tb, n->pkg); break; case ICM_EVENT_XDOMAIN_DISCONNECTED: - icm->xdomain_disconnected(tb, n->pkg); + if (tb_is_xdomain_enabled()) + icm->xdomain_disconnected(tb, n->pkg); break; case ICM_EVENT_RTD3_VETO: icm->rtd3_veto(tb, n->pkg); diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 413955aa6a94..ad3c285026d5 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -179,6 +179,9 @@ static void tb_scan_xdomain(struct tb_port *port) struct tb_xdomain *xd; u64 route; + if (!tb_is_xdomain_enabled()) + return; + route = tb_downstream_route(port); xd = tb_xdomain_find_by_route(tb, route); if (xd) { diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 37679b54808d..84416d8eb988 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -958,6 +958,7 @@ static inline u64 tb_downstream_route(struct tb_port *port) | ((u64) port->port << (port->sw->config.depth * 8)); } +bool tb_is_xdomain_enabled(void); bool tb_xdomain_handle_request(struct tb *tb, enum tb_cfg_pkg_type type, const void *buf, size_t size); struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent, diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index 6e8bea6a7d39..b242161868c2 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -30,6 +30,10 @@ struct xdomain_request_work { struct tb *tb; }; +static bool tb_xdomain_enabled = true; +module_param_named(xdomain, tb_xdomain_enabled, bool, 0444); +MODULE_PARM_DESC(xdomain, "allow XDomain protocol (default: true)"); + /* Serializes access to the properties and protocol handlers below */ static DEFINE_MUTEX(xdomain_lock); @@ -47,6 +51,11 @@ static const uuid_t tb_xdp_uuid = UUID_INIT(0xb638d70e, 0x42ff, 0x40bb, 0x97, 0xc2, 0x90, 0xe2, 0xc0, 0xb2, 0xff, 0x07); +bool tb_is_xdomain_enabled(void) +{ + return tb_xdomain_enabled; +} + static bool tb_xdomain_match(const struct tb_cfg_request *req, const struct ctl_pkg *pkg) { -- cgit v1.2.3 From 719e1f561afbe020ed175825a9bd25ed62ed1697 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Mon, 26 Oct 2020 18:01:05 +0300 Subject: ACPI: Execute platform _OSC also with query bit clear The platform _OSC can change the hardware state when query bit is not set. According to ACPI spec it is recommended that the OS runs _OSC with query bit set until the platform does not mask any of the capabilities. Then it should run it with query bit clear in order to actually commit the changes. Linux has not been doing this for the reasons that there has not been anything to commit, until now. The ACPI 6.4 introduced _OSC for USB4 to allow the OS to negotiate native control over USB4 tunneling. The platform might implement this so that it only activates the software connection manager path when the OS calls the _OSC with the query bit clear. Otherwise it may default to the firmware connection manager, for instance. For this reason modify the _OSC support so that we first execute it with query bit set, then use the returned value as base of the features we want to control and run the _OSC again with query bit clear. This also follows what Windows is doing. Also rename the function to better match what it does. Signed-off-by: Mario Limonciello Acked-by: Rafael J. Wysocki Signed-off-by: Mika Westerberg --- drivers/acpi/bus.c | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index 1682f8b454a2..a52cb28c40d8 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -282,9 +282,9 @@ bool osc_pc_lpi_support_confirmed; EXPORT_SYMBOL_GPL(osc_pc_lpi_support_confirmed); static u8 sb_uuid_str[] = "0811B06E-4A27-44F9-8D60-3CBBC22E7B48"; -static void acpi_bus_osc_support(void) +static void acpi_bus_osc_negotiate_platform_control(void) { - u32 capbuf[2]; + u32 capbuf[2], *capbuf_ret; struct acpi_osc_context context = { .uuid_str = sb_uuid_str, .rev = 1, @@ -321,17 +321,36 @@ static void acpi_bus_osc_support(void) capbuf[OSC_SUPPORT_DWORD] |= OSC_SB_APEI_SUPPORT; if (ACPI_FAILURE(acpi_get_handle(NULL, "\\_SB", &handle))) return; - if (ACPI_SUCCESS(acpi_run_osc(handle, &context))) { - u32 *capbuf_ret = context.ret.pointer; - if (context.ret.length > OSC_SUPPORT_DWORD) { - osc_sb_apei_support_acked = - capbuf_ret[OSC_SUPPORT_DWORD] & OSC_SB_APEI_SUPPORT; - osc_pc_lpi_support_confirmed = - capbuf_ret[OSC_SUPPORT_DWORD] & OSC_SB_PCLPI_SUPPORT; - } + + if (ACPI_FAILURE(acpi_run_osc(handle, &context))) + return; + + capbuf_ret = context.ret.pointer; + if (context.ret.length <= OSC_SUPPORT_DWORD) { kfree(context.ret.pointer); + return; } - /* do we need to check other returned cap? Sounds no */ + + /* + * Now run _OSC again with query flag clear and with the caps + * supported by both the OS and the platform. + */ + capbuf[OSC_QUERY_DWORD] = 0; + capbuf[OSC_SUPPORT_DWORD] = capbuf_ret[OSC_SUPPORT_DWORD]; + kfree(context.ret.pointer); + + if (ACPI_FAILURE(acpi_run_osc(handle, &context))) + return; + + capbuf_ret = context.ret.pointer; + if (context.ret.length > OSC_SUPPORT_DWORD) { + osc_sb_apei_support_acked = + capbuf_ret[OSC_SUPPORT_DWORD] & OSC_SB_APEI_SUPPORT; + osc_pc_lpi_support_confirmed = + capbuf_ret[OSC_SUPPORT_DWORD] & OSC_SB_PCLPI_SUPPORT; + } + + kfree(context.ret.pointer); } /* -------------------------------------------------------------------------- @@ -1168,7 +1187,7 @@ static int __init acpi_bus_init(void) * _OSC method may exist in module level code, * so it must be run after ACPI_FULL_INITIALIZATION */ - acpi_bus_osc_support(); + acpi_bus_osc_negotiate_platform_control(); /* * _PDC control method may load dynamic SSDT tables, -- cgit v1.2.3 From 5a6a2c0f0f43676df27632d657a3f18b151a7ef8 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 18 Feb 2020 16:02:45 +0200 Subject: ACPI: Add support for native USB4 control _OSC ACPI 6.4 introduced a new _OSC capability that is used negotiate native connection manager support. Connection manager is the entity that is responsible for tunneling over the USB4 fabric. If the platform rejects the native access then firmware based connection manager is used. The new _OSC also includes a set of bits that can be used to disable certain tunnel types such as PCIe for security reasons for instance. This implements the new USB4 _OSC so that we try to negotiate native USB4 support if the Thunderbolt/USB4 (CONFIG_USB4) driver is enabled. Drivers can determine what was negotiated by checking two new variables exposed in this patch. Signed-off-by: Mika Westerberg Reviewed-by: Rafael J. Wysocki --- drivers/acpi/bus.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/acpi.h | 10 +++++++ 2 files changed, 86 insertions(+) diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index a52cb28c40d8..9c3fe08e8f18 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -281,6 +281,12 @@ bool osc_sb_apei_support_acked; bool osc_pc_lpi_support_confirmed; EXPORT_SYMBOL_GPL(osc_pc_lpi_support_confirmed); +/* + * ACPI 6.4 Operating System Capabilities for USB. + */ +bool osc_sb_native_usb4_support_confirmed; +EXPORT_SYMBOL_GPL(osc_sb_native_usb4_support_confirmed); + static u8 sb_uuid_str[] = "0811B06E-4A27-44F9-8D60-3CBBC22E7B48"; static void acpi_bus_osc_negotiate_platform_control(void) { @@ -317,6 +323,9 @@ static void acpi_bus_osc_negotiate_platform_control(void) if (IS_ENABLED(CONFIG_SCHED_MC_PRIO)) capbuf[OSC_SUPPORT_DWORD] |= OSC_SB_CPC_DIVERSE_HIGH_SUPPORT; + if (IS_ENABLED(CONFIG_USB4)) + capbuf[OSC_SUPPORT_DWORD] |= OSC_SB_NATIVE_USB4_SUPPORT; + if (!ghes_disable) capbuf[OSC_SUPPORT_DWORD] |= OSC_SB_APEI_SUPPORT; if (ACPI_FAILURE(acpi_get_handle(NULL, "\\_SB", &handle))) @@ -348,8 +357,74 @@ static void acpi_bus_osc_negotiate_platform_control(void) capbuf_ret[OSC_SUPPORT_DWORD] & OSC_SB_APEI_SUPPORT; osc_pc_lpi_support_confirmed = capbuf_ret[OSC_SUPPORT_DWORD] & OSC_SB_PCLPI_SUPPORT; + osc_sb_native_usb4_support_confirmed = + capbuf_ret[OSC_SUPPORT_DWORD] & OSC_SB_NATIVE_USB4_SUPPORT; + } + + kfree(context.ret.pointer); +} + +/* + * Native control of USB4 capabilities. If any of the tunneling bits is + * set it means OS is in control and we use software based connection + * manager. + */ +u32 osc_sb_native_usb4_control; +EXPORT_SYMBOL_GPL(osc_sb_native_usb4_control); + +static void acpi_bus_decode_usb_osc(const char *msg, u32 bits) +{ + printk(KERN_INFO PREFIX "%s USB3%c DisplayPort%c PCIe%c XDomain%c\n", msg, + (bits & OSC_USB_USB3_TUNNELING) ? '+' : '-', + (bits & OSC_USB_DP_TUNNELING) ? '+' : '-', + (bits & OSC_USB_PCIE_TUNNELING) ? '+' : '-', + (bits & OSC_USB_XDOMAIN) ? '+' : '-'); +} + +static u8 sb_usb_uuid_str[] = "23A0D13A-26AB-486C-9C5F-0FFA525A575A"; +static void acpi_bus_osc_negotiate_usb_control(void) +{ + u32 capbuf[3]; + struct acpi_osc_context context = { + .uuid_str = sb_usb_uuid_str, + .rev = 1, + .cap.length = sizeof(capbuf), + .cap.pointer = capbuf, + }; + acpi_handle handle; + acpi_status status; + u32 control; + + if (!osc_sb_native_usb4_support_confirmed) + return; + + if (ACPI_FAILURE(acpi_get_handle(NULL, "\\_SB", &handle))) + return; + + control = OSC_USB_USB3_TUNNELING | OSC_USB_DP_TUNNELING | + OSC_USB_PCIE_TUNNELING | OSC_USB_XDOMAIN; + + capbuf[OSC_QUERY_DWORD] = 0; + capbuf[OSC_SUPPORT_DWORD] = 0; + capbuf[OSC_CONTROL_DWORD] = control; + + status = acpi_run_osc(handle, &context); + if (ACPI_FAILURE(status)) + return; + + if (context.ret.length != sizeof(capbuf)) { + printk(KERN_INFO PREFIX "USB4 _OSC: returned invalid length buffer\n"); + goto out_free; } + osc_sb_native_usb4_control = + control & ((u32 *)context.ret.pointer)[OSC_CONTROL_DWORD]; + + acpi_bus_decode_usb_osc("USB4 _OSC: OS supports", control); + acpi_bus_decode_usb_osc("USB4 _OSC: OS controls", + osc_sb_native_usb4_control); + +out_free: kfree(context.ret.pointer); } @@ -1188,6 +1263,7 @@ static int __init acpi_bus_init(void) * so it must be run after ACPI_FULL_INITIALIZATION */ acpi_bus_osc_negotiate_platform_control(); + acpi_bus_osc_negotiate_usb_control(); /* * _PDC control method may load dynamic SSDT tables, diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 2630c2e953f7..ac68c2d4e393 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -546,9 +546,19 @@ acpi_status acpi_run_osc(acpi_handle handle, struct acpi_osc_context *context); #define OSC_SB_OSLPI_SUPPORT 0x00000100 #define OSC_SB_CPC_DIVERSE_HIGH_SUPPORT 0x00001000 #define OSC_SB_GENERIC_INITIATOR_SUPPORT 0x00002000 +#define OSC_SB_NATIVE_USB4_SUPPORT 0x00040000 extern bool osc_sb_apei_support_acked; extern bool osc_pc_lpi_support_confirmed; +extern bool osc_sb_native_usb4_support_confirmed; + +/* USB4 Capabilities */ +#define OSC_USB_USB3_TUNNELING 0x00000001 +#define OSC_USB_DP_TUNNELING 0x00000002 +#define OSC_USB_PCIE_TUNNELING 0x00000004 +#define OSC_USB_XDOMAIN 0x00000008 + +extern u32 osc_sb_native_usb4_control; /* PCI Host Bridge _OSC: Capabilities DWORD 2: Support Field */ #define OSC_PCI_EXT_CONFIG_SUPPORT 0x00000001 -- cgit v1.2.3 From c6da62a219d028de10f2e22e93a34c7ee2b88d03 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 18 Feb 2020 16:14:42 +0200 Subject: thunderbolt: Add support for native USB4 _OSC ACPI 6.4 introduced a new _OSC capability used to negotiate whether the OS is supposed to use Software (native) or Firmware based Connection Manager. If the native support is granted then there are set of bits that enable/disable different tunnel types that the Software Connection Manager is allowed to tunnel. This adds support for this new USB4 _OSC accordingly. When PCIe tunneling is disabled then the driver switches security level to be "nopcie" following the security level 5 used in Firmware based Connection Manager. Signed-off-by: Mika Westerberg Acked-by: Yehezkel Bernat --- drivers/thunderbolt/acpi.c | 65 +++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/nhi.c | 27 ++++++++++++++++-- drivers/thunderbolt/tb.c | 19 ++++++++++++- drivers/thunderbolt/tb.h | 12 ++++++++ drivers/thunderbolt/tunnel.c | 10 ++++--- drivers/thunderbolt/usb4.c | 11 ++++++-- drivers/thunderbolt/xdomain.c | 2 +- 7 files changed, 134 insertions(+), 12 deletions(-) diff --git a/drivers/thunderbolt/acpi.c b/drivers/thunderbolt/acpi.c index a5f988a9f948..6ed003961308 100644 --- a/drivers/thunderbolt/acpi.c +++ b/drivers/thunderbolt/acpi.c @@ -115,3 +115,68 @@ void tb_acpi_add_links(struct tb_nhi *nhi) if (ACPI_FAILURE(status)) dev_warn(&nhi->pdev->dev, "failed to enumerate tunneled ports\n"); } + +/** + * tb_acpi_is_native() - Did the platform grant native TBT/USB4 control + * + * Returns %true if the platform granted OS native control over + * TBT/USB4. In this case software based connection manager can be used, + * otherwise there is firmware based connection manager running. + */ +bool tb_acpi_is_native(void) +{ + return osc_sb_native_usb4_support_confirmed && + osc_sb_native_usb4_control; +} + +/** + * tb_acpi_may_tunnel_usb3() - Is USB3 tunneling allowed by the platform + * + * When software based connection manager is used, this function + * returns %true if platform allows native USB3 tunneling. + */ +bool tb_acpi_may_tunnel_usb3(void) +{ + if (tb_acpi_is_native()) + return osc_sb_native_usb4_control & OSC_USB_USB3_TUNNELING; + return true; +} + +/** + * tb_acpi_may_tunnel_dp() - Is DisplayPort tunneling allowed by the platform + * + * When software based connection manager is used, this function + * returns %true if platform allows native DP tunneling. + */ +bool tb_acpi_may_tunnel_dp(void) +{ + if (tb_acpi_is_native()) + return osc_sb_native_usb4_control & OSC_USB_DP_TUNNELING; + return true; +} + +/** + * tb_acpi_may_tunnel_pcie() - Is PCIe tunneling allowed by the platform + * + * When software based connection manager is used, this function + * returns %true if platform allows native PCIe tunneling. + */ +bool tb_acpi_may_tunnel_pcie(void) +{ + if (tb_acpi_is_native()) + return osc_sb_native_usb4_control & OSC_USB_PCIE_TUNNELING; + return true; +} + +/** + * tb_acpi_is_xdomain_allowed() - Are XDomain connections allowed + * + * When software based connection manager is used, this function + * returns %true if platform allows XDomain connections. + */ +bool tb_acpi_is_xdomain_allowed(void) +{ + if (tb_acpi_is_native()) + return osc_sb_native_usb4_control & OSC_USB_XDOMAIN; + return true; +} diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 782404eb10b0..a0386d1e3fc9 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -1190,6 +1190,29 @@ static void tb_apple_add_links(struct tb_nhi *nhi) } } +static struct tb *nhi_select_cm(struct tb_nhi *nhi) +{ + struct tb *tb; + + /* + * USB4 case is simple. If we got control of any of the + * capabilities, we use software CM. + */ + if (tb_acpi_is_native()) + return tb_probe(nhi); + + /* + * Either firmware based CM is running (we did not get control + * from the firmware) or this is pre-USB4 PC so try first + * firmware CM and then fallback to software CM. + */ + tb = icm_probe(nhi); + if (!tb) + tb = tb_probe(nhi); + + return tb; +} + static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct tb_nhi *nhi; @@ -1258,9 +1281,7 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) tb_apple_add_links(nhi); tb_acpi_add_links(nhi); - tb = icm_probe(nhi); - if (!tb) - tb = tb_probe(nhi); + tb = nhi_select_cm(nhi); if (!tb) { dev_err(&nhi->pdev->dev, "failed to determine connection manager, aborting\n"); diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index ad3c285026d5..1f000ac1728b 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -437,6 +437,11 @@ static int tb_tunnel_usb3(struct tb *tb, struct tb_switch *sw) struct tb_cm *tcm = tb_priv(tb); struct tb_tunnel *tunnel; + if (!tb_acpi_may_tunnel_usb3()) { + tb_dbg(tb, "USB3 tunneling disabled, not creating tunnel\n"); + return 0; + } + up = tb_switch_find_port(sw, TB_TYPE_USB3_UP); if (!up) return 0; @@ -512,6 +517,9 @@ static int tb_create_usb3_tunnels(struct tb_switch *sw) struct tb_port *port; int ret; + if (!tb_acpi_may_tunnel_usb3()) + return 0; + if (tb_route(sw)) { ret = tb_tunnel_usb3(sw->tb, sw); if (ret) @@ -841,6 +849,11 @@ static void tb_tunnel_dp(struct tb *tb) struct tb_port *port, *in, *out; struct tb_tunnel *tunnel; + if (!tb_acpi_may_tunnel_dp()) { + tb_dbg(tb, "DP tunneling disabled, not creating tunnel\n"); + return; + } + /* * Find pair of inactive DP IN and DP OUT adapters and then * establish a DP tunnel between them. @@ -1549,7 +1562,11 @@ struct tb *tb_probe(struct tb_nhi *nhi) if (!tb) return NULL; - tb->security_level = TB_SECURITY_USER; + if (tb_acpi_may_tunnel_pcie()) + tb->security_level = TB_SECURITY_USER; + else + tb->security_level = TB_SECURITY_NOPCIE; + tb->cm_ops = &tb_cm_ops; tcm = tb_priv(tb); diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 84416d8eb988..beea88c34c0f 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -1044,8 +1044,20 @@ void tb_check_quirks(struct tb_switch *sw); #ifdef CONFIG_ACPI void tb_acpi_add_links(struct tb_nhi *nhi); + +bool tb_acpi_is_native(void); +bool tb_acpi_may_tunnel_usb3(void); +bool tb_acpi_may_tunnel_dp(void); +bool tb_acpi_may_tunnel_pcie(void); +bool tb_acpi_is_xdomain_allowed(void); #else static inline void tb_acpi_add_links(struct tb_nhi *nhi) { } + +static inline bool tb_acpi_is_native(void) { return true; } +static inline bool tb_acpi_may_tunnel_usb3(void) { return true; } +static inline bool tb_acpi_may_tunnel_dp(void) { return true; } +static inline bool tb_acpi_may_tunnel_pcie(void) { return true; } +static inline bool tb_acpi_is_xdomain_allowed(void) { return true; } #endif #ifdef CONFIG_DEBUG_FS diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 37943b0379ab..6557b6e07009 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -932,12 +932,14 @@ static int tb_usb3_activate(struct tb_tunnel *tunnel, bool activate) static int tb_usb3_consumed_bandwidth(struct tb_tunnel *tunnel, int *consumed_up, int *consumed_down) { + int pcie_enabled = tb_acpi_may_tunnel_pcie(); + /* - * PCIe tunneling affects the USB3 bandwidth so take that it - * into account here. + * PCIe tunneling, if enabled, affects the USB3 bandwidth so + * take that it into account here. */ - *consumed_up = tunnel->allocated_up * (3 + 1) / 3; - *consumed_down = tunnel->allocated_down * (3 + 1) / 3; + *consumed_up = tunnel->allocated_up * (3 + pcie_enabled) / 3; + *consumed_down = tunnel->allocated_down * (3 + pcie_enabled) / 3; return 0; } diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 67a2867382ed..680bc738dd66 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -331,13 +331,18 @@ int usb4_switch_setup(struct tb_switch *sw) if (ret) return ret; - if (sw->link_usb4 && tb_switch_find_port(parent, TB_TYPE_USB3_DOWN)) { + if (tb_acpi_may_tunnel_usb3() && sw->link_usb4 && + tb_switch_find_port(parent, TB_TYPE_USB3_DOWN)) { val |= ROUTER_CS_5_UTO; xhci = false; } - /* Only enable PCIe tunneling if the parent router supports it */ - if (tb_switch_find_port(parent, TB_TYPE_PCIE_DOWN)) { + /* + * Only enable PCIe tunneling if the parent router supports it + * and it is not disabled. + */ + if (tb_acpi_may_tunnel_pcie() && + tb_switch_find_port(parent, TB_TYPE_PCIE_DOWN)) { val |= ROUTER_CS_5_PTO; /* * xHCI can be enabled if PCIe tunneling is supported diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index b242161868c2..7cf8b9c85ab7 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -53,7 +53,7 @@ static const uuid_t tb_xdp_uuid = bool tb_is_xdomain_enabled(void) { - return tb_xdomain_enabled; + return tb_xdomain_enabled && tb_acpi_is_xdomain_allowed(); } static bool tb_xdomain_match(const struct tb_cfg_request *req, -- cgit v1.2.3 From e81dee5554171adf11a45533ab8631ba060a02b7 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Thu, 4 Feb 2021 17:17:07 +0300 Subject: usb: dwc2: pci: Drop the empty quirk function The function dwc2_pci_quirks() does nothing. Removing. Signed-off-by: Heikki Krogerus Cc: Minas Harutyunyan Link: https://lore.kernel.org/r/20210204141711.53775-3-heikki.krogerus@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/pci.c | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/drivers/usb/dwc2/pci.c b/drivers/usb/dwc2/pci.c index 7afc10872f1f..0000151e3ca9 100644 --- a/drivers/usb/dwc2/pci.c +++ b/drivers/usb/dwc2/pci.c @@ -63,20 +63,6 @@ struct dwc2_pci_glue { struct platform_device *phy; }; -static int dwc2_pci_quirks(struct pci_dev *pdev, struct platform_device *dwc2) -{ - if (pdev->vendor == PCI_VENDOR_ID_SYNOPSYS && - pdev->device == PCI_PRODUCT_ID_HAPS_HSOTG) { - struct property_entry properties[] = { - { }, - }; - - return platform_device_add_properties(dwc2, properties); - } - - return 0; -} - /** * dwc2_pci_probe() - Provides the cleanup entry points for the DWC_otg PCI * driver @@ -143,10 +129,6 @@ static int dwc2_pci_probe(struct pci_dev *pci, dwc2->dev.parent = dev; - ret = dwc2_pci_quirks(pci, dwc2); - if (ret) - goto err; - glue = devm_kzalloc(dev, sizeof(*glue), GFP_KERNEL); if (!glue) { ret = -ENOMEM; -- cgit v1.2.3 From efc9812edc61761708e6e703a48ed18e1af6bd12 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Thu, 4 Feb 2021 17:17:08 +0300 Subject: usb: dwc3: haps: Constify the software node What platform_device_add_properties() does is it allocates dynamically a software node that will contain the device properties supplied to it, and then couples that node with the device. Since that node is always created, it might as well be constant. Signed-off-by: Heikki Krogerus Link: https://lore.kernel.org/r/20210204141711.53775-4-heikki.krogerus@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-haps.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/usb/dwc3/dwc3-haps.c b/drivers/usb/dwc3/dwc3-haps.c index 55b4a901168e..f6e3817fa7af 100644 --- a/drivers/usb/dwc3/dwc3-haps.c +++ b/drivers/usb/dwc3/dwc3-haps.c @@ -33,6 +33,10 @@ static const struct property_entry initial_properties[] = { { }, }; +static const struct software_node dwc3_haps_swnode = { + .properties = initial_properties, +}; + static int dwc3_haps_probe(struct pci_dev *pci, const struct pci_device_id *id) { @@ -77,7 +81,7 @@ static int dwc3_haps_probe(struct pci_dev *pci, dwc->pci = pci; dwc->dwc3->dev.parent = dev; - ret = platform_device_add_properties(dwc->dwc3, initial_properties); + ret = device_add_software_node(&dwc->dwc3->dev, &dwc3_haps_swnode); if (ret) goto err; @@ -91,6 +95,7 @@ static int dwc3_haps_probe(struct pci_dev *pci, return 0; err: + device_remove_software_node(&dwc->dwc3->dev); platform_device_put(dwc->dwc3); return ret; } @@ -99,6 +104,7 @@ static void dwc3_haps_remove(struct pci_dev *pci) { struct dwc3_haps *dwc = pci_get_drvdata(pci); + device_remove_software_node(&dwc->dwc3->dev); platform_device_unregister(dwc->dwc3); } -- cgit v1.2.3 From 8dc6e6dd1bee39cd65a232a17d51240fc65a0f4a Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Thu, 4 Feb 2021 17:17:09 +0300 Subject: usb: dwc3: qcom: Constify the software node What platform_device_add_properties() does is it allocates dynamically a software node that will contain the device properties supplied to it, and then couples that node with the device. If the properties are constant, the node can be constant as well. Signed-off-by: Heikki Krogerus Link: https://lore.kernel.org/r/20210204141711.53775-5-heikki.krogerus@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-qcom.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index d803ee98c628..846a47be6df7 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -567,6 +567,10 @@ static const struct property_entry dwc3_qcom_acpi_properties[] = { {} }; +static const struct software_node dwc3_qcom_swnode = { + .properties = dwc3_qcom_acpi_properties, +}; + static int dwc3_qcom_acpi_register_core(struct platform_device *pdev) { struct dwc3_qcom *qcom = platform_get_drvdata(pdev); @@ -613,16 +617,17 @@ static int dwc3_qcom_acpi_register_core(struct platform_device *pdev) goto out; } - ret = platform_device_add_properties(qcom->dwc3, - dwc3_qcom_acpi_properties); + ret = device_add_software_node(&qcom->dwc3->dev, &dwc3_qcom_swnode); if (ret < 0) { dev_err(&pdev->dev, "failed to add properties\n"); goto out; } ret = platform_device_add(qcom->dwc3); - if (ret) + if (ret) { dev_err(&pdev->dev, "failed to add device\n"); + device_remove_software_node(&qcom->dwc3->dev); + } out: kfree(child_res); @@ -837,6 +842,7 @@ static int dwc3_qcom_remove(struct platform_device *pdev) struct device *dev = &pdev->dev; int i; + device_remove_software_node(&qcom->dwc3->dev); of_platform_depopulate(dev); for (i = qcom->num_clocks - 1; i >= 0; i--) { -- cgit v1.2.3 From b64afd949ee3a61e180813859b50aced26023c55 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Tue, 2 Feb 2021 18:15:37 -0800 Subject: platform/chrome: cros_ec: Import Type C control command This command is used to communicate with the Chrome Embedded Controller (EC) regarding USB Type C events and state. These header updates are included in the latest Chrome OS EC headers [1] [1] https://chromium.googlesource.com/chromiumos/platform/ec/+/refs/heads/main/include/ec_commands.h Signed-off-by: Prashant Malani Link: https://lore.kernel.org/r/20210203021539.745239-1-pmalani@chromium.org Signed-off-by: Benson Leung --- include/linux/platform_data/cros_ec_commands.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/include/linux/platform_data/cros_ec_commands.h b/include/linux/platform_data/cros_ec_commands.h index 9787715540c7..50e1caad81be 100644 --- a/include/linux/platform_data/cros_ec_commands.h +++ b/include/linux/platform_data/cros_ec_commands.h @@ -5577,6 +5577,32 @@ struct ec_response_typec_discovery { struct svid_mode_info svids[0]; } __ec_align1; +/* USB Type-C commands for AP-controlled device policy. */ +#define EC_CMD_TYPEC_CONTROL 0x0132 + +enum typec_control_command { + TYPEC_CONTROL_COMMAND_EXIT_MODES, + TYPEC_CONTROL_COMMAND_CLEAR_EVENTS, + TYPEC_CONTROL_COMMAND_ENTER_MODE, +}; + +struct ec_params_typec_control { + uint8_t port; + uint8_t command; /* enum typec_control_command */ + uint16_t reserved; + + /* + * This section will be interpreted based on |command|. Define a + * placeholder structure to avoid having to increase the size and bump + * the command version when adding new sub-commands. + */ + union { + uint32_t clear_events_mask; + uint8_t mode_to_enter; /* enum typec_mode */ + uint8_t placeholder[128]; + }; +} __ec_align1; + /* * Gather all status information for a port. * -- cgit v1.2.3 From c8ec21c6d25c2a8895614ea38575dadb8570c2f9 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Tue, 2 Feb 2021 18:15:39 -0800 Subject: platform/chrome: cros_ec_typec: Clear Type C disc events Clear USB Type C discovery events from the Chrome EC once they've been successfully handled. Signed-off-by: Prashant Malani Reported-by: Benson Leung Tested-by: Benson Leung Link: https://lore.kernel.org/r/20210203021539.745239-2-pmalani@chromium.org Signed-off-by: Benson Leung --- drivers/platform/chrome/cros_ec_typec.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 7b93dfd02999..0abd21044882 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -870,6 +870,18 @@ disc_exit: return ret; } +static int cros_typec_send_clear_event(struct cros_typec_data *typec, int port_num, u32 events_mask) +{ + struct ec_params_typec_control req = { + .port = port_num, + .command = TYPEC_CONTROL_COMMAND_CLEAR_EVENTS, + .clear_events_mask = events_mask, + }; + + return cros_typec_ec_command(typec, 0, EC_CMD_TYPEC_CONTROL, &req, + sizeof(req), NULL, 0); +} + static void cros_typec_handle_status(struct cros_typec_data *typec, int port_num) { struct ec_response_typec_status resp; @@ -894,9 +906,14 @@ static void cros_typec_handle_status(struct cros_typec_data *typec, int port_num ret = cros_typec_handle_sop_disc(typec, port_num, sop_revision); if (ret < 0) dev_err(typec->dev, "Couldn't parse SOP Disc data, port: %d\n", port_num); - else + else { typec->ports[port_num]->sop_disc_done = true; - + ret = cros_typec_send_clear_event(typec, port_num, + PD_STATUS_EVENT_SOP_DISC_DONE); + if (ret < 0) + dev_warn(typec->dev, + "Failed SOP Disc event clear, port: %d\n", port_num); + } if (resp.sop_connected) typec_set_pwr_opmode(typec->ports[port_num]->port, TYPEC_PWR_MODE_PD); } @@ -910,8 +927,14 @@ static void cros_typec_handle_status(struct cros_typec_data *typec, int port_num ret = cros_typec_handle_sop_prime_disc(typec, port_num, sop_prime_revision); if (ret < 0) dev_err(typec->dev, "Couldn't parse SOP' Disc data, port: %d\n", port_num); - else + else { typec->ports[port_num]->sop_prime_disc_done = true; + ret = cros_typec_send_clear_event(typec, port_num, + PD_STATUS_EVENT_SOP_PRIME_DISC_DONE); + if (ret < 0) + dev_warn(typec->dev, + "Failed SOP Disc event clear, port: %d\n", port_num); + } } } -- cgit v1.2.3 From 151f6ff78cdf1d6de76e90556cfc43f1e48abe18 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Thu, 4 Feb 2021 17:17:06 +0300 Subject: software node: Provide replacement for device_add_properties() At the moment the function device_del() is calling device_remove_properties() unconditionally. That will result into the reference count of the software node attached to the device being decremented, and in most cases it will hit 0 at that point. So in practice device_del() will unregister the software node attached to the device, even if that was not the intention of the caller. Right now software nodes can not be reused or shared because of that. So device_del() can not unregister the software nodes unconditionally like that. Unfortunately some of the users of device_add_properties() are now relying on this behaviour. Because of that, and also in general, we do need a function that can offer similar behaviour where the lifetime of the software node is bound to the lifetime of the device. But it just has to be a separate function so the behaviour is optional. We can not remove the device_remove_properties() call from device_del() before we have that new function, and before we have replaced device_add_properties() calls with it in all the places that require that behaviour. This adds function device_create_managed_software_node() that can be used for exactly that purpose. Software nodes created with it are declared "managed", and separate handling for those nodes is added to the software node code. The reference count of the "managed" nodes is decremented when the device they are attached to is removed. This will not affect the other nodes that are not declared "managed". The function device_create_managed_software_node() has also one additional feature that device_add_properties() does not have. It allows the software nodes created with it to be part of a node hierarchy by taking also an optional parent node as parameter. Reviewed-by: Rafael J. Wysocki Signed-off-by: Heikki Krogerus Link: https://lore.kernel.org/r/20210204141711.53775-2-heikki.krogerus@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/swnode.c | 43 +++++++++++++++++++++++++++++++++++++++++++ include/linux/property.h | 4 ++++ 2 files changed, 47 insertions(+) diff --git a/drivers/base/swnode.c b/drivers/base/swnode.c index 4842dd678200..45cf8c879d71 100644 --- a/drivers/base/swnode.c +++ b/drivers/base/swnode.c @@ -24,6 +24,7 @@ struct swnode { struct swnode *parent; unsigned int allocated:1; + unsigned int managed:1; }; static DEFINE_IDA(swnode_root_ids); @@ -903,6 +904,43 @@ void device_remove_software_node(struct device *dev) } EXPORT_SYMBOL_GPL(device_remove_software_node); +/** + * device_create_managed_software_node - Create a software node for a device + * @dev: The device the software node is assigned to. + * @properties: Device properties for the software node. + * @parent: Parent of the software node. + * + * Creates a software node as a managed resource for @dev, which means the + * lifetime of the newly created software node is tied to the lifetime of @dev. + * Software nodes created with this function should not be reused or shared + * because of that. The function takes a deep copy of @properties for the + * software node. + * + * Since the new software node is assigned directly to @dev, and since it should + * not be shared, it is not returned to the caller. The function returns 0 on + * success, and errno in case of an error. + */ +int device_create_managed_software_node(struct device *dev, + const struct property_entry *properties, + const struct software_node *parent) +{ + struct fwnode_handle *p = software_node_fwnode(parent); + struct fwnode_handle *fwnode; + + if (parent && !p) + return -EINVAL; + + fwnode = fwnode_create_software_node(properties, p); + if (IS_ERR(fwnode)) + return PTR_ERR(fwnode); + + to_swnode(fwnode)->managed = true; + set_secondary_fwnode(dev, fwnode); + + return 0; +} +EXPORT_SYMBOL_GPL(device_create_managed_software_node); + int software_node_notify(struct device *dev, unsigned long action) { struct swnode *swnode; @@ -931,6 +969,11 @@ int software_node_notify(struct device *dev, unsigned long action) sysfs_remove_link(&swnode->kobj, dev_name(dev)); sysfs_remove_link(&dev->kobj, "software_node"); kobject_put(&swnode->kobj); + + if (swnode->managed) { + set_secondary_fwnode(dev, NULL); + kobject_put(&swnode->kobj); + } break; default: break; diff --git a/include/linux/property.h b/include/linux/property.h index b0e413dc5927..dafccfce0262 100644 --- a/include/linux/property.h +++ b/include/linux/property.h @@ -491,4 +491,8 @@ void fwnode_remove_software_node(struct fwnode_handle *fwnode); int device_add_software_node(struct device *dev, const struct software_node *swnode); void device_remove_software_node(struct device *dev); +int device_create_managed_software_node(struct device *dev, + const struct property_entry *properties, + const struct software_node *parent); + #endif /* _LINUX_PROPERTY_H_ */ -- cgit v1.2.3 From b1638ee56c1a3fddee062beac93d2fe528eb2eca Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Thu, 4 Feb 2021 17:17:10 +0300 Subject: usb: dwc3: host: Use software node API with the properties This replaces the platform_device_add_properties() call with the safer device_create_managed_software_node() that does exactly the same, but can also guarantee that the lifetime of the node that is created for the device is tied to the lifetime of device itself. Signed-off-by: Heikki Krogerus Link: https://lore.kernel.org/r/20210204141711.53775-6-heikki.krogerus@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/host.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c index e195176580de..f29a264635aa 100644 --- a/drivers/usb/dwc3/host.c +++ b/drivers/usb/dwc3/host.c @@ -108,7 +108,7 @@ int dwc3_host_init(struct dwc3 *dwc) props[prop_idx++] = PROPERTY_ENTRY_BOOL("quirk-broken-port-ped"); if (prop_idx) { - ret = platform_device_add_properties(xhci, props); + ret = device_create_managed_software_node(&xhci->dev, props, NULL); if (ret) { dev_err(dwc->dev, "failed to add properties to xHCI\n"); goto err; -- cgit v1.2.3 From c8134c004ccf131ec41e8c58b096927bb8ba1cd8 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Thu, 4 Feb 2021 17:17:11 +0300 Subject: xhci: ext-caps: Use software node API with the properties This replaces the platform_device_add_properties() call with the safer device_create_managed_software_node() that does exactly the same, but can also guarantee that the lifetime of the node that is created for the device is tied to the lifetime of device itself. Reviewed-by: Hans de Goede Signed-off-by: Heikki Krogerus Link: https://lore.kernel.org/r/20210204141711.53775-7-heikki.krogerus@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ext-caps.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/host/xhci-ext-caps.c b/drivers/usb/host/xhci-ext-caps.c index 3351d07c431f..7a4c2c4ad50e 100644 --- a/drivers/usb/host/xhci-ext-caps.c +++ b/drivers/usb/host/xhci-ext-caps.c @@ -54,7 +54,8 @@ static int xhci_create_intel_xhci_sw_pdev(struct xhci_hcd *xhci, u32 cap_offset) } if (pci->device == PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI) { - ret = platform_device_add_properties(pdev, role_switch_props); + ret = device_create_managed_software_node(&pdev->dev, role_switch_props, + NULL); if (ret) { dev_err(dev, "failed to register device properties\n"); platform_device_put(pdev); -- cgit v1.2.3 From 0eaa1a3714db34a59ce121de5733c3909c529463 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Sat, 23 Jan 2021 14:24:59 +0000 Subject: usb: musb: Fix runtime PM race in musb_queue_resume_work musb_queue_resume_work() would call the provided callback if the runtime PM status was 'active'. Otherwise, it would enqueue the request if the hardware was still suspended (musb->is_runtime_suspended is true). This causes a race with the runtime PM handlers, as it is possible to be in the case where the runtime PM status is not yet 'active', but the hardware has been awaken (PM resume function has been called). When hitting the race, the resume work was not enqueued, which probably triggered other bugs further down the stack. For instance, a telnet connection on Ingenic SoCs would result in a 50/50 chance of a segmentation fault somewhere in the musb code. Rework the code so that either we call the callback directly if (musb->is_runtime_suspended == 0), or enqueue the query otherwise. Fixes: ea2f35c01d5e ("usb: musb: Fix sleeping function called from invalid context for hdrc glue") Cc: stable@vger.kernel.org # v4.9+ Tested-by: Tony Lindgren Reviewed-by: Tony Lindgren Signed-off-by: Paul Cercueil Link: https://lore.kernel.org/r/20210123142502.16980-1-paul@crapouillou.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/musb_core.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index 849e0b770130..1cd87729ba60 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -2240,32 +2240,35 @@ int musb_queue_resume_work(struct musb *musb, { struct musb_pending_work *w; unsigned long flags; + bool is_suspended; int error; if (WARN_ON(!callback)) return -EINVAL; - if (pm_runtime_active(musb->controller)) - return callback(musb, data); + spin_lock_irqsave(&musb->list_lock, flags); + is_suspended = musb->is_runtime_suspended; + + if (is_suspended) { + w = devm_kzalloc(musb->controller, sizeof(*w), GFP_ATOMIC); + if (!w) { + error = -ENOMEM; + goto out_unlock; + } - w = devm_kzalloc(musb->controller, sizeof(*w), GFP_ATOMIC); - if (!w) - return -ENOMEM; + w->callback = callback; + w->data = data; - w->callback = callback; - w->data = data; - spin_lock_irqsave(&musb->list_lock, flags); - if (musb->is_runtime_suspended) { list_add_tail(&w->node, &musb->pending_list); error = 0; - } else { - dev_err(musb->controller, "could not add resume work %p\n", - callback); - devm_kfree(musb->controller, w); - error = -EINPROGRESS; } + +out_unlock: spin_unlock_irqrestore(&musb->list_lock, flags); + if (!is_suspended) + error = callback(musb, data); + return error; } EXPORT_SYMBOL_GPL(musb_queue_resume_work); -- cgit v1.2.3 From ae2938c358430beab098fbfac843532c25b36372 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Sat, 23 Jan 2021 14:25:00 +0000 Subject: usb: musb: Fix NULL check on struct musb_request field The 'request' variable is a pointer to the 'request' field of the struct musb_request 'req' pointer. It only worked until now because the 'request' field is the first one in the musb_request structure, but as soon as that changes, the check will be invalid. Fix it preventively by doing the NULL-check on the 'req' pointer instead. Suggested-by: Maarten ter Huurne Acked-by: Tony Lindgren Signed-off-by: Paul Cercueil Link: https://lore.kernel.org/r/20210123142502.16980-2-paul@crapouillou.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/musb_gadget.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c index f62ffaede1ab..ef374d4dd94a 100644 --- a/drivers/usb/musb/musb_gadget.c +++ b/drivers/usb/musb/musb_gadget.c @@ -451,7 +451,7 @@ void musb_g_tx(struct musb *musb, u8 epnum) return; } - if (request) { + if (req) { trace_musb_req_tx(req); -- cgit v1.2.3 From eb44cef954277584d8c046fe4806937c46487c31 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Sat, 23 Jan 2021 14:25:01 +0000 Subject: usb: musb: dma: Remove unused variable Remove unused-but-set devctl variable. Signed-off-by: Paul Cercueil Link: https://lore.kernel.org/r/20210123142502.16980-3-paul@crapouillou.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/musbhsdma.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/usb/musb/musbhsdma.c b/drivers/usb/musb/musbhsdma.c index 0aacfc8be5a1..7acd1635850d 100644 --- a/drivers/usb/musb/musbhsdma.c +++ b/drivers/usb/musb/musbhsdma.c @@ -321,8 +321,6 @@ irqreturn_t dma_controller_irq(int irq, void *private_data) musb_channel->channel.status = MUSB_DMA_STATUS_BUS_ABORT; } else { - u8 devctl; - addr = musb_read_hsdma_addr(mbase, bchannel); channel->actual_len = addr @@ -336,8 +334,6 @@ irqreturn_t dma_controller_irq(int irq, void *private_data) < musb_channel->len) ? "=> reconfig 0" : "=> complete"); - devctl = musb_readb(mbase, MUSB_DEVCTL); - channel->status = MUSB_DMA_STATUS_FREE; /* completed */ -- cgit v1.2.3 From 23e32a595e115c09152488aa0508539e6f6a4237 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Sat, 23 Jan 2021 14:25:02 +0000 Subject: usb: musb: jz4740: Add missing CR to error strings If you pass a string that is not terminated with a carriage return to dev_err(), it will eventually be printed with a carriage return, but not right away, since the kernel will wait for a pr_cont(). Signed-off-by: Paul Cercueil Link: https://lore.kernel.org/r/20210123142502.16980-4-paul@crapouillou.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/jz4740.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/usb/musb/jz4740.c b/drivers/usb/musb/jz4740.c index c4fe1f4cd17a..5b7d576bf6ee 100644 --- a/drivers/usb/musb/jz4740.c +++ b/drivers/usb/musb/jz4740.c @@ -116,13 +116,13 @@ static int jz4740_musb_init(struct musb *musb) if (IS_ERR(musb->xceiv)) { err = PTR_ERR(musb->xceiv); if (err != -EPROBE_DEFER) - dev_err(dev, "No transceiver configured: %d", err); + dev_err(dev, "No transceiver configured: %d\n", err); return err; } glue->role_sw = usb_role_switch_register(dev, &role_sw_desc); if (IS_ERR(glue->role_sw)) { - dev_err(dev, "Failed to register USB role switch"); + dev_err(dev, "Failed to register USB role switch\n"); return PTR_ERR(glue->role_sw); } @@ -205,26 +205,26 @@ static int jz4740_probe(struct platform_device *pdev) pdata = of_device_get_match_data(dev); if (!pdata) { - dev_err(dev, "missing platform data"); + dev_err(dev, "missing platform data\n"); return -EINVAL; } musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO); if (!musb) { - dev_err(dev, "failed to allocate musb device"); + dev_err(dev, "failed to allocate musb device\n"); return -ENOMEM; } clk = devm_clk_get(dev, "udc"); if (IS_ERR(clk)) { - dev_err(dev, "failed to get clock"); + dev_err(dev, "failed to get clock\n"); ret = PTR_ERR(clk); goto err_platform_device_put; } ret = clk_prepare_enable(clk); if (ret) { - dev_err(dev, "failed to enable clock"); + dev_err(dev, "failed to enable clock\n"); goto err_platform_device_put; } @@ -240,19 +240,19 @@ static int jz4740_probe(struct platform_device *pdev) ret = platform_device_add_resources(musb, pdev->resource, pdev->num_resources); if (ret) { - dev_err(dev, "failed to add resources"); + dev_err(dev, "failed to add resources\n"); goto err_clk_disable; } ret = platform_device_add_data(musb, pdata, sizeof(*pdata)); if (ret) { - dev_err(dev, "failed to add platform_data"); + dev_err(dev, "failed to add platform_data\n"); goto err_clk_disable; } ret = platform_device_add(musb); if (ret) { - dev_err(dev, "failed to register musb device"); + dev_err(dev, "failed to register musb device\n"); goto err_clk_disable; } -- cgit v1.2.3 From 1ee18ded86ece87227a76d7101034fd305b8c803 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Thu, 4 Feb 2021 10:20:35 +0100 Subject: usb: cdnsp: Removes some useless trace events Patch removes some useless trace events that can be replaced by ftrace. Acked-by: Greg Kroah-Hartman Reported-by: Dan Carpenter Signed-off-by: Pawel Laszczak Signed-off-by: Peter Chen --- drivers/usb/cdns3/cdnsp-ep0.c | 5 ----- drivers/usb/cdns3/cdnsp-gadget.c | 2 -- drivers/usb/cdns3/cdnsp-ring.c | 1 - drivers/usb/cdns3/cdnsp-trace.h | 10 ---------- 4 files changed, 18 deletions(-) diff --git a/drivers/usb/cdns3/cdnsp-ep0.c b/drivers/usb/cdns3/cdnsp-ep0.c index e2b1bcb3f80e..9b8325f82499 100644 --- a/drivers/usb/cdns3/cdnsp-ep0.c +++ b/drivers/usb/cdns3/cdnsp-ep0.c @@ -24,13 +24,11 @@ static void cdnsp_ep0_stall(struct cdnsp_device *pdev) preq = next_request(&pep->pending_list); if (pdev->three_stage_setup) { - trace_cdnsp_ep0_data_stage("send stall"); cdnsp_halt_endpoint(pdev, pep, true); if (preq) cdnsp_gadget_giveback(pep, preq, -ECONNRESET); } else { - trace_cdnsp_ep0_status_stage("send stall"); pep->ep_state |= EP0_HALTED_STATUS; if (preq) @@ -45,8 +43,6 @@ static int cdnsp_ep0_delegate_req(struct cdnsp_device *pdev, { int ret; - trace_cdnsp_ep0_request("delagete"); - spin_unlock(&pdev->lock); ret = pdev->gadget_driver->setup(&pdev->gadget, ctrl); spin_lock(&pdev->lock); @@ -130,7 +126,6 @@ static int cdnsp_ep0_set_address(struct cdnsp_device *pdev, int cdnsp_status_stage(struct cdnsp_device *pdev) { - trace_cdnsp_ep0_status_stage("preparing"); pdev->ep0_stage = CDNSP_STATUS_STAGE; pdev->ep0_preq.request.length = 0; diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c index f28f1508f049..f2ebbacd932e 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.c +++ b/drivers/usb/cdns3/cdnsp-gadget.c @@ -237,8 +237,6 @@ static int cdnsp_start(struct cdnsp_device *pdev) temp |= (CMD_R_S | CMD_DEVEN); writel(temp, &pdev->op_regs->command); - trace_cdnsp_init("Turn on controller"); - pdev->cdnsp_state = 0; /* diff --git a/drivers/usb/cdns3/cdnsp-ring.c b/drivers/usb/cdns3/cdnsp-ring.c index e15e13ba27dc..f9170d177a89 100644 --- a/drivers/usb/cdns3/cdnsp-ring.c +++ b/drivers/usb/cdns3/cdnsp-ring.c @@ -266,7 +266,6 @@ static void cdnsp_force_l0_go(struct cdnsp_device *pdev) /* Ring the doorbell after placing a command on the ring. */ void cdnsp_ring_cmd_db(struct cdnsp_device *pdev) { - trace_cdnsp_cmd_drbl("Ding Dong"); writel(DB_VALUE_CMD, &pdev->dba->cmd_db); } diff --git a/drivers/usb/cdns3/cdnsp-trace.h b/drivers/usb/cdns3/cdnsp-trace.h index a9de1daadf07..5aa88ca012de 100644 --- a/drivers/usb/cdns3/cdnsp-trace.h +++ b/drivers/usb/cdns3/cdnsp-trace.h @@ -158,11 +158,6 @@ DEFINE_EVENT(cdnsp_log_simple, cdnsp_slot_id, TP_ARGS(msg) ); -DEFINE_EVENT(cdnsp_log_simple, cdnsp_cmd_drbl, - TP_PROTO(char *msg), - TP_ARGS(msg) -); - DEFINE_EVENT(cdnsp_log_simple, cdnsp_no_room_on_ring, TP_PROTO(char *msg), TP_ARGS(msg) @@ -173,11 +168,6 @@ DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep0_status_stage, TP_ARGS(msg) ); -DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep0_data_stage, - TP_PROTO(char *msg), - TP_ARGS(msg) -); - DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep0_request, TP_PROTO(char *msg), TP_ARGS(msg) -- cgit v1.2.3 From de4d9ea789530ac0ce3409878422e9389c3a7cd3 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Tue, 19 Jan 2021 17:36:08 -0800 Subject: dt-binding: usb: Include USB SSP rates in GenXxY According to the USB 3.2 spec, a SuperSpeed Plus device can operate at gen2x2, gen2x1, or gen1x2. If the USB controller device supports multiple lanes at different transfer rates, the user can specify the HW capability via these new speed strings: "super-speed-plus-gen2x2" "super-speed-plus-gen2x1" "super-speed-plus-gen1x2" If the argument is simply "super-speed-plus", USB controllers should default to their maximum transfer rate and number of lanes. Reviewed-by: Rob Herring Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/cc7cc15f87e209c9963f19129f51398cdc374358.1611106162.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usb.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/usb.yaml b/Documentation/devicetree/bindings/usb/usb.yaml index ebe7f4275c59..78491e66ed24 100644 --- a/Documentation/devicetree/bindings/usb/usb.yaml +++ b/Documentation/devicetree/bindings/usb/usb.yaml @@ -54,6 +54,9 @@ properties: - high-speed - super-speed - super-speed-plus + - super-speed-plus-gen2x1 + - super-speed-plus-gen1x2 + - super-speed-plus-gen2x2 additionalProperties: true -- cgit v1.2.3 From 52c2d15703c3a900d5f78cd599b823db40d5100b Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Tue, 19 Jan 2021 17:36:14 -0800 Subject: usb: common: Parse for USB SSP genXxY The USB "maximum-speed" property can now take the SSP signaling rate generation and lane count with these new strings: "super-speed-plus-gen2x2" "super-speed-plus-gen2x1" "super-speed-plus-gen1x2" Introduce usb_get_maximum_ssp_rate() to parse for the corresponding usb_ssp_rate enum. The original usb_get_maximum_speed() will return USB_SPEED_SUPER_PLUS if it matches one of these new strings. Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/f8ed896313d8cd8e2d2b540fc82db92b3ddf8a47.1611106162.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/common/common.c | 26 +++++++++++++++++++++++++- include/linux/usb/ch9.h | 11 +++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/drivers/usb/common/common.c b/drivers/usb/common/common.c index 1433260d99b4..fc21cf2d36f6 100644 --- a/drivers/usb/common/common.c +++ b/drivers/usb/common/common.c @@ -69,6 +69,13 @@ static const char *const speed_names[] = { [USB_SPEED_SUPER_PLUS] = "super-speed-plus", }; +static const char *const ssp_rate[] = { + [USB_SSP_GEN_UNKNOWN] = "UNKNOWN", + [USB_SSP_GEN_2x1] = "super-speed-plus-gen2x1", + [USB_SSP_GEN_1x2] = "super-speed-plus-gen1x2", + [USB_SSP_GEN_2x2] = "super-speed-plus-gen2x2", +}; + const char *usb_speed_string(enum usb_device_speed speed) { if (speed < 0 || speed >= ARRAY_SIZE(speed_names)) @@ -86,12 +93,29 @@ enum usb_device_speed usb_get_maximum_speed(struct device *dev) if (ret < 0) return USB_SPEED_UNKNOWN; - ret = match_string(speed_names, ARRAY_SIZE(speed_names), maximum_speed); + ret = match_string(ssp_rate, ARRAY_SIZE(ssp_rate), maximum_speed); + if (ret > 0) + return USB_SPEED_SUPER_PLUS; + ret = match_string(speed_names, ARRAY_SIZE(speed_names), maximum_speed); return (ret < 0) ? USB_SPEED_UNKNOWN : ret; } EXPORT_SYMBOL_GPL(usb_get_maximum_speed); +enum usb_ssp_rate usb_get_maximum_ssp_rate(struct device *dev) +{ + const char *maximum_speed; + int ret; + + ret = device_property_read_string(dev, "maximum-speed", &maximum_speed); + if (ret < 0) + return USB_SSP_GEN_UNKNOWN; + + ret = match_string(ssp_rate, ARRAY_SIZE(ssp_rate), maximum_speed); + return (ret < 0) ? USB_SSP_GEN_UNKNOWN : ret; +} +EXPORT_SYMBOL_GPL(usb_get_maximum_ssp_rate); + const char *usb_state_string(enum usb_device_state state) { static const char *const names[] = { diff --git a/include/linux/usb/ch9.h b/include/linux/usb/ch9.h index 86c50907634e..abdd310c77f0 100644 --- a/include/linux/usb/ch9.h +++ b/include/linux/usb/ch9.h @@ -71,6 +71,17 @@ extern const char *usb_speed_string(enum usb_device_speed speed); */ extern enum usb_device_speed usb_get_maximum_speed(struct device *dev); +/** + * usb_get_maximum_ssp_rate - Get the signaling rate generation and lane count + * of a SuperSpeed Plus capable device. + * @dev: Pointer to the given USB controller device + * + * If the string from "maximum-speed" property is super-speed-plus-genXxY where + * 'X' is the generation number and 'Y' is the number of lanes, then this + * function returns the corresponding enum usb_ssp_rate. + */ +extern enum usb_ssp_rate usb_get_maximum_ssp_rate(struct device *dev); + /** * usb_state_string - Returns human readable name for the state. * @state: The state to return a human-readable name for. If it's not -- cgit v1.2.3 From 678481467d2e1460a49e626d8e9ba0c7e9742f53 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Tue, 19 Jan 2021 17:36:21 -0800 Subject: usb: dwc3: core: Check maximum_speed SSP genXxY The DWC_usb32 controller supports dual-lane SuperSpeed Plus. Check the maximum_speed property for any limitation in the HW to initialize and validate the maximum number of lanes and speed the device will operate. Currently the controller has no visibility into the HW parameter to determine the maximum number of lanes the HW supports. If the number of lanes is not specified for SSP, then set the default rate to gen2x2 for DWC_usb32 and gen2x1 for DWC_usb31. Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/08d43f2a99198bed84895c272340449a6d03710e.1611106162.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 37 +++++++++++++++++++++++++++++++++++++ drivers/usb/dwc3/core.h | 2 ++ drivers/usb/dwc3/gadget.c | 1 + 3 files changed, 40 insertions(+) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 6969196fccd6..931ccf93eabd 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1252,6 +1252,7 @@ static void dwc3_get_properties(struct dwc3 *dwc) hird_threshold = 12; 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); dwc->hsphy_mode = of_usb_get_phy_mode(dev->of_node); @@ -1423,6 +1424,42 @@ static void dwc3_check_params(struct dwc3 *dwc) } break; } + + /* + * Currently the controller does not have visibility into the HW + * parameter to determine the maximum number of lanes the HW supports. + * If the number of lanes is not specified in the device property, then + * set the default to support dual-lane for DWC_usb32 and single-lane + * for DWC_usb31 for super-speed-plus. + */ + if (dwc->maximum_speed == USB_SPEED_SUPER_PLUS) { + switch (dwc->max_ssp_rate) { + case USB_SSP_GEN_2x1: + if (hwparam_gen == DWC3_GHWPARAMS3_SSPHY_IFC_GEN1) + dev_warn(dev, "UDC only supports Gen 1\n"); + break; + case USB_SSP_GEN_1x2: + case USB_SSP_GEN_2x2: + if (DWC3_IP_IS(DWC31)) + dev_warn(dev, "UDC only supports single lane\n"); + break; + case USB_SSP_GEN_UNKNOWN: + default: + switch (hwparam_gen) { + case DWC3_GHWPARAMS3_SSPHY_IFC_GEN2: + if (DWC3_IP_IS(DWC32)) + dwc->max_ssp_rate = USB_SSP_GEN_2x2; + else + dwc->max_ssp_rate = USB_SSP_GEN_2x1; + break; + case DWC3_GHWPARAMS3_SSPHY_IFC_GEN1: + if (DWC3_IP_IS(DWC32)) + dwc->max_ssp_rate = USB_SSP_GEN_1x2; + break; + } + break; + } + } } static int dwc3_probe(struct platform_device *pdev) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index eec1cf4ba268..a482f4e954cb 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -964,6 +964,7 @@ struct dwc3_scratchpad_array { * @nr_scratch: number of scratch buffers * @u1u2: only used on revisions <1.83a for workaround * @maximum_speed: maximum speed requested (mainly for testing purposes) + * @max_ssp_rate: SuperSpeed Plus maximum signaling rate and lane count * @gadget_max_speed: maximum gadget speed requested * @ip: controller's ID * @revision: controller's version of an IP @@ -1128,6 +1129,7 @@ struct dwc3 { u32 u1u2; u32 maximum_speed; u32 gadget_max_speed; + enum usb_ssp_rate max_ssp_rate; u32 ip; diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 86f257f12d45..00ba8f11ca32 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -3888,6 +3888,7 @@ int dwc3_gadget_init(struct dwc3 *dwc) dwc->revision); dwc->gadget->max_speed = dwc->maximum_speed; + dwc->gadget->max_ssp_rate = dwc->max_ssp_rate; /* * REVISIT: Here we should clear all pending IRQs to be -- cgit v1.2.3 From 072cab8a0fe276282272e57138d83299e35455eb Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Tue, 19 Jan 2021 17:36:28 -0800 Subject: usb: dwc3: gadget: Implement setting of SSP rate Implement gadget ops udc_set_ssp_rate(). This allows the gadget/core driver to select SSP signaling rate and number of lanes to for DWC_usb32 controller. Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/8998b65fddfa02cab57bfc6aa35e9f101b252068.1611106162.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.h | 5 +++++ drivers/usb/dwc3/gadget.c | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index a482f4e954cb..61ac298f3e8e 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -386,6 +386,8 @@ #define DWC3_GUCTL3_SPLITDISABLE BIT(14) /* Device Configuration Register */ +#define DWC3_DCFG_NUMLANES(n) (((n) & 0x3) << 30) /* DWC_usb32 only */ + #define DWC3_DCFG_DEVADDR(addr) ((addr) << 3) #define DWC3_DCFG_DEVADDR_MASK DWC3_DCFG_DEVADDR(0x7f) @@ -966,6 +968,8 @@ struct dwc3_scratchpad_array { * @maximum_speed: maximum speed requested (mainly for testing purposes) * @max_ssp_rate: SuperSpeed Plus maximum signaling rate and lane count * @gadget_max_speed: maximum gadget speed requested + * @gadget_ssp_rate: Gadget driver's maximum supported SuperSpeed Plus signaling + * rate and lane count. * @ip: controller's ID * @revision: controller's version of an IP * @version_type: VERSIONTYPE register contents, a sub release of a revision @@ -1130,6 +1134,7 @@ struct dwc3 { u32 maximum_speed; u32 gadget_max_speed; enum usb_ssp_rate max_ssp_rate; + enum usb_ssp_rate gadget_ssp_rate; u32 ip; diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 00ba8f11ca32..14ab5594a595 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2038,10 +2038,40 @@ static void dwc3_stop_active_transfers(struct dwc3 *dwc) } } +static void __dwc3_gadget_set_ssp_rate(struct dwc3 *dwc) +{ + enum usb_ssp_rate ssp_rate = dwc->gadget_ssp_rate; + u32 reg; + + if (ssp_rate == USB_SSP_GEN_UNKNOWN) + ssp_rate = dwc->max_ssp_rate; + + reg = dwc3_readl(dwc->regs, DWC3_DCFG); + reg &= ~DWC3_DCFG_SPEED_MASK; + reg &= ~DWC3_DCFG_NUMLANES(~0); + + if (ssp_rate == USB_SSP_GEN_1x2) + reg |= DWC3_DCFG_SUPERSPEED; + else if (dwc->max_ssp_rate != USB_SSP_GEN_1x2) + reg |= DWC3_DCFG_SUPERSPEED_PLUS; + + if (ssp_rate != USB_SSP_GEN_2x1 && + dwc->max_ssp_rate != USB_SSP_GEN_2x1) + reg |= DWC3_DCFG_NUMLANES(1); + + dwc3_writel(dwc->regs, DWC3_DCFG, reg); +} + static void __dwc3_gadget_set_speed(struct dwc3 *dwc) { u32 reg; + if (dwc->gadget_max_speed == USB_SPEED_SUPER_PLUS && + DWC3_IP_IS(DWC32)) { + __dwc3_gadget_set_ssp_rate(dwc); + return; + } + reg = dwc3_readl(dwc->regs, DWC3_DCFG); reg &= ~(DWC3_DCFG_SPEED_MASK); @@ -2475,6 +2505,17 @@ static void dwc3_gadget_set_speed(struct usb_gadget *g, spin_unlock_irqrestore(&dwc->lock, flags); } +static void dwc3_gadget_set_ssp_rate(struct usb_gadget *g, + enum usb_ssp_rate rate) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + dwc->gadget_ssp_rate = rate; + spin_unlock_irqrestore(&dwc->lock, flags); +} + static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned int mA) { struct dwc3 *dwc = gadget_to_dwc(g); @@ -2493,6 +2534,7 @@ static const struct usb_gadget_ops dwc3_gadget_ops = { .udc_start = dwc3_gadget_start, .udc_stop = dwc3_gadget_stop, .udc_set_speed = dwc3_gadget_set_speed, + .udc_set_ssp_rate = dwc3_gadget_set_ssp_rate, .get_config_params = dwc3_gadget_config_params, .vbus_draw = dwc3_gadget_vbus_draw, }; @@ -3905,7 +3947,10 @@ int dwc3_gadget_init(struct dwc3 *dwc) goto err5; } - dwc3_gadget_set_speed(dwc->gadget, dwc->maximum_speed); + if (DWC3_IP_IS(DWC32) && dwc->maximum_speed == USB_SPEED_SUPER_PLUS) + dwc3_gadget_set_ssp_rate(dwc->gadget, dwc->max_ssp_rate); + else + dwc3_gadget_set_speed(dwc->gadget, dwc->maximum_speed); return 0; -- cgit v1.2.3 From f551037c0770d07e4669d7d4fa423c235291dc59 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Tue, 19 Jan 2021 17:36:34 -0800 Subject: usb: dwc3: gadget: Track connected SSP rate and lane count Track the number of connected lanes and speed in corresponding enum usb_ssp_rate for SuperSpeed Plus capable device. Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/2389592188d2e37a2ee45edaf04d942b19f3af82.1611106162.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.h | 2 ++ drivers/usb/dwc3/gadget.c | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 61ac298f3e8e..052b20d52651 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -461,6 +461,8 @@ #define DWC3_DEVTEN_USBRSTEN BIT(1) #define DWC3_DEVTEN_DISCONNEVTEN BIT(0) +#define DWC3_DSTS_CONNLANES(n) (((n) >> 30) & 0x3) /* DWC_usb32 only */ + /* Device Status Register */ #define DWC3_DSTS_DCNRD BIT(29) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 14ab5594a595..0aa89e704a55 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2120,6 +2120,12 @@ static void __dwc3_gadget_set_speed(struct dwc3 *dwc) reg |= DWC3_DCFG_SUPERSPEED_PLUS; } } + + if (DWC3_IP_IS(DWC32) && + dwc->gadget_max_speed > USB_SPEED_UNKNOWN && + dwc->gadget_max_speed < USB_SPEED_SUPER_PLUS) + reg &= ~DWC3_DCFG_NUMLANES(~0); + dwc3_writel(dwc->regs, DWC3_DCFG, reg); } @@ -3369,12 +3375,18 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc) struct dwc3_ep *dep; int ret; u32 reg; + u8 lanes = 1; u8 speed; reg = dwc3_readl(dwc->regs, DWC3_DSTS); speed = reg & DWC3_DSTS_CONNECTSPD; dwc->speed = speed; + if (DWC3_IP_IS(DWC32)) + lanes = DWC3_DSTS_CONNLANES(reg) + 1; + + dwc->gadget->ssp_rate = USB_SSP_GEN_UNKNOWN; + /* * RAMClkSel is reset to 0 after USB reset, so it must be reprogrammed * each time on Connect Done. @@ -3389,6 +3401,11 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc) dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); dwc->gadget->ep0->maxpacket = 512; dwc->gadget->speed = USB_SPEED_SUPER_PLUS; + + if (lanes > 1) + dwc->gadget->ssp_rate = USB_SSP_GEN_2x2; + else + dwc->gadget->ssp_rate = USB_SSP_GEN_2x1; break; case DWC3_DSTS_SUPERSPEED: /* @@ -3410,6 +3427,11 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc) dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); dwc->gadget->ep0->maxpacket = 512; dwc->gadget->speed = USB_SPEED_SUPER; + + if (lanes > 1) { + dwc->gadget->speed = USB_SPEED_SUPER_PLUS; + dwc->gadget->ssp_rate = USB_SSP_GEN_1x2; + } break; case DWC3_DSTS_HIGHSPEED: dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64); @@ -3904,6 +3926,7 @@ int dwc3_gadget_init(struct dwc3 *dwc) dev->platform_data = dwc; dwc->gadget->ops = &dwc3_gadget_ops; dwc->gadget->speed = USB_SPEED_UNKNOWN; + dwc->gadget->ssp_rate = USB_SSP_GEN_UNKNOWN; dwc->gadget->sg_supported = true; dwc->gadget->name = "dwc3-gadget"; dwc->gadget->lpm_capable = true; -- cgit v1.2.3 From 450b9e9fabd80d7d5fd29e6f915b696a15acd2bd Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Tue, 19 Jan 2021 17:36:40 -0800 Subject: usb: dwc3: gadget: Set speed only up to the max supported The setting of the device speed should be limited by the device's maximum_speed. Check and prevent the driver from attempting to configure higher than the maximum_speed. Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/4fae4a9ebb60464d64d8b8f6fdfc2777a2206a69.1611106162.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/gadget.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 0aa89e704a55..9acf515f6a67 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2064,9 +2064,14 @@ static void __dwc3_gadget_set_ssp_rate(struct dwc3 *dwc) static void __dwc3_gadget_set_speed(struct dwc3 *dwc) { + enum usb_device_speed speed; u32 reg; - if (dwc->gadget_max_speed == USB_SPEED_SUPER_PLUS && + speed = dwc->gadget_max_speed; + if (speed > dwc->maximum_speed) + speed = dwc->maximum_speed; + + if (speed == USB_SPEED_SUPER_PLUS && DWC3_IP_IS(DWC32)) { __dwc3_gadget_set_ssp_rate(dwc); return; @@ -2092,7 +2097,7 @@ static void __dwc3_gadget_set_speed(struct dwc3 *dwc) !dwc->dis_metastability_quirk) { reg |= DWC3_DCFG_SUPERSPEED; } else { - switch (dwc->gadget_max_speed) { + switch (speed) { case USB_SPEED_LOW: reg |= DWC3_DCFG_LOWSPEED; break; @@ -2112,7 +2117,7 @@ static void __dwc3_gadget_set_speed(struct dwc3 *dwc) reg |= DWC3_DCFG_SUPERSPEED_PLUS; break; default: - dev_err(dwc->dev, "invalid speed (%d)\n", dwc->gadget_max_speed); + dev_err(dwc->dev, "invalid speed (%d)\n", speed); if (DWC3_IP_IS(DWC3)) reg |= DWC3_DCFG_SUPERSPEED; @@ -2122,8 +2127,8 @@ static void __dwc3_gadget_set_speed(struct dwc3 *dwc) } if (DWC3_IP_IS(DWC32) && - dwc->gadget_max_speed > USB_SPEED_UNKNOWN && - dwc->gadget_max_speed < USB_SPEED_SUPER_PLUS) + speed > USB_SPEED_UNKNOWN && + speed < USB_SPEED_SUPER_PLUS) reg &= ~DWC3_DCFG_NUMLANES(~0); dwc3_writel(dwc->regs, DWC3_DCFG, reg); -- cgit v1.2.3 From 8cf9045b91382df9fb1eb420daa4d1c2697d2f44 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Fri, 5 Feb 2021 01:53:47 -0800 Subject: usb: dwc3: gadget: Remove check for bounded driver The check for bounded gadget driver in dwc3_gadget_start() was to prevent going through the initialization again without any cleanup. The recent commit 49d08cfc7830 ("usb: udc: core: Introduce started state") updated the UDC framework and guarantees this won't happen while the UDC is started. Also, this check doesn't prevent requesting threaded irq to the same dev_id, which will mess up the irq freeing logic. Let's remove it. Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/ccc90f316cf78bb5f7d46d3fd84f4c7f2c3020b1.1612518764.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/gadget.c | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 9acf515f6a67..97d707b4f384 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2411,7 +2411,7 @@ static int dwc3_gadget_start(struct usb_gadget *g, { struct dwc3 *dwc = gadget_to_dwc(g); unsigned long flags; - int ret = 0; + int ret; int irq; irq = dwc->irq_gadget; @@ -2420,29 +2420,14 @@ static int dwc3_gadget_start(struct usb_gadget *g, if (ret) { dev_err(dwc->dev, "failed to request irq #%d --> %d\n", irq, ret); - goto err0; + return ret; } spin_lock_irqsave(&dwc->lock, flags); - if (dwc->gadget_driver) { - dev_err(dwc->dev, "%s is already bound to %s\n", - dwc->gadget->name, - dwc->gadget_driver->driver.name); - ret = -EBUSY; - goto err1; - } - dwc->gadget_driver = driver; spin_unlock_irqrestore(&dwc->lock, flags); return 0; - -err1: - spin_unlock_irqrestore(&dwc->lock, flags); - free_irq(irq, dwc); - -err0: - return ret; } static void __dwc3_gadget_stop(struct dwc3 *dwc) -- cgit v1.2.3 From 6420a569504e212d618d4a4736e2c59ed80a8478 Mon Sep 17 00:00:00 2001 From: Lech Perczak Date: Sun, 7 Feb 2021 01:54:43 +0100 Subject: USB: serial: option: update interface mapping for ZTE P685M This patch prepares for qmi_wwan driver support for the device. Previously "option" driver mapped itself to interfaces 0 and 3 (matching ff/ff/ff), while interface 3 is in fact a QMI port. Interfaces 1 and 2 (matching ff/00/00) expose AT commands, and weren't supported previously at all. Without this patch, a possible conflict would exist if device ID was added to qmi_wwan driver for interface 3. Update and simplify device ID to match interfaces 0-2 directly, to expose QCDM (0), PCUI (1), and modem (2) ports and avoid conflict with QMI (3), and ADB (4). The modem is used inside ZTE MF283+ router and carriers identify it as such. Interface mapping is: 0: QCDM, 1: AT (PCUI), 2: AT (Modem), 3: QMI, 4: ADB T: Bus=02 Lev=02 Prnt=02 Port=05 Cnt=01 Dev#= 3 Spd=480 MxCh= 0 D: Ver= 2.01 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1 P: Vendor=19d2 ProdID=1275 Rev=f0.00 S: Manufacturer=ZTE,Incorporated S: Product=ZTE Technologies MSM S: SerialNumber=P685M510ZTED0000CP&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&0 C:* #Ifs= 5 Cfg#= 1 Atr=a0 MxPwr=500mA I:* If#= 0 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=ff Driver=option E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 1 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=00 Prot=00 Driver=option E: Ad=83(I) Atr=03(Int.) MxPS= 10 Ivl=32ms E: Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=00 Prot=00 Driver=option E: Ad=85(I) Atr=03(Int.) MxPS= 10 Ivl=32ms E: Ad=84(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=ff Driver=qmi_wwan E: Ad=87(I) Atr=03(Int.) MxPS= 8 Ivl=32ms E: Ad=86(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 4 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=42 Prot=01 Driver=(none) E: Ad=88(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=05(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms Cc: Johan Hovold Cc: Bjørn Mork Signed-off-by: Lech Perczak Link: https://lore.kernel.org/r/20210207005443.12936-1-lech.perczak@gmail.com Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/option.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 3fe959104311..6716dfcf2610 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -1567,7 +1567,8 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1272, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1273, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1274, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1275, 0xff, 0xff, 0xff) }, + { USB_DEVICE(ZTE_VENDOR_ID, 0x1275), /* ZTE P685M */ + .driver_info = RSVD(3) | RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1276, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1277, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1278, 0xff, 0xff, 0xff) }, -- cgit v1.2.3 From a1679af85b2ae35a2b78ad04c18bb069c37330cc Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Mon, 8 Feb 2021 13:53:10 -0800 Subject: usb: dwc3: gadget: Fix setting of DEPCFG.bInterval_m1 Valid range for DEPCFG.bInterval_m1 is from 0 to 13, and it must be set to 0 when the controller operates in full-speed. See the programming guide for DEPCFG command section 3.2.2.1 (v3.30a). Fixes: 72246da40f37 ("usb: Introduce DesignWare USB3 DRD Driver") Cc: Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/3f57026f993c0ce71498dbb06e49b3a47c4d0265.1612820995.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/gadget.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 97d707b4f384..d0f8d3ec855f 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -605,7 +605,17 @@ static int dwc3_gadget_set_ep_config(struct dwc3_ep *dep, unsigned int action) params.param0 |= DWC3_DEPCFG_FIFO_NUMBER(dep->number >> 1); if (desc->bInterval) { - params.param1 |= DWC3_DEPCFG_BINTERVAL_M1(desc->bInterval - 1); + u8 bInterval_m1; + + /* + * Valid range for DEPCFG.bInterval_m1 is from 0 to 13, and it + * must be set to 0 when the controller operates in full-speed. + */ + bInterval_m1 = min_t(u8, desc->bInterval - 1, 13); + if (dwc->gadget->speed == USB_SPEED_FULL) + bInterval_m1 = 0; + + params.param1 |= DWC3_DEPCFG_BINTERVAL_M1(bInterval_m1); dep->interval = 1 << (desc->bInterval - 1); } -- cgit v1.2.3 From 4b049f55ed95cd889bcdb3034fd75e1f01852b38 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Mon, 8 Feb 2021 13:53:16 -0800 Subject: usb: dwc3: gadget: Fix dep->interval for fullspeed interrupt The dep->interval captures the number of frames/microframes per interval from bInterval. Fullspeed interrupt endpoint bInterval is the number of frames per interval and not 2^(bInterval - 1). So fix it here. This change is only for debugging purpose and should not affect the interrupt endpoint operation. Fixes: 72246da40f37 ("usb: Introduce DesignWare USB3 DRD Driver") Cc: Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/1263b563dedc4ab8b0fb854fba06ce4bc56bd495.1612820995.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/gadget.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index d0f8d3ec855f..aebcf8ec0716 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -615,8 +615,13 @@ static int dwc3_gadget_set_ep_config(struct dwc3_ep *dep, unsigned int action) if (dwc->gadget->speed == USB_SPEED_FULL) bInterval_m1 = 0; + if (usb_endpoint_type(desc) == USB_ENDPOINT_XFER_INT && + dwc->gadget->speed == USB_SPEED_FULL) + dep->interval = desc->bInterval; + else + dep->interval = 1 << (desc->bInterval - 1); + params.param1 |= DWC3_DEPCFG_BINTERVAL_M1(bInterval_m1); - dep->interval = 1 << (desc->bInterval - 1); } return dwc3_send_gadget_ep_cmd(dep, DWC3_DEPCMD_SETEPCONFIG, ¶ms); -- cgit v1.2.3 From 3c5960c0559c44c6628341a82167ee0d3e40ee50 Mon Sep 17 00:00:00 2001 From: Kyle Tso Date: Fri, 5 Feb 2021 11:34:09 +0800 Subject: usb: typec: Manage SVDM version PD Spec Revision 3.0 Version 2.0 + ECNs 2020-12-10 6.4.4.2.3 Structured VDM Version "The Structured VDM Version field of the Discover Identity Command sent and received during VDM discovery Shall be used to determine the lowest common Structured VDM Version supported by the Port Partners or Cable Plug and Shall continue to operate using this Specification Revision until they are Detached." Add a variable in typec_capability to specify the highest SVDM version supported by the port and another variable in typec_partner to cache the negotiated SVDM version between the port and the partner. Also add setter/getter functions for the negotiated SVDM version. Acked-by: Heikki Krogerus Signed-off-by: Kyle Tso Link: https://lore.kernel.org/r/20210205033415.3320439-2-kyletso@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/class.c | 43 +++++++++++++++++++++++++++++++++++++++ include/linux/usb/typec.h | 12 +++++++++++ include/linux/usb/typec_altmode.h | 10 +++++++++ 3 files changed, 65 insertions(+) diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index b4a5d9d4564f..45f0bf65e9ab 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -38,6 +38,7 @@ struct typec_partner { struct ida mode_ids; int num_altmodes; u16 pd_revision; /* 0300H = "3.0" */ + enum usb_pd_svdm_ver svdm_version; }; struct typec_port { @@ -824,6 +825,20 @@ typec_partner_register_altmode(struct typec_partner *partner, } EXPORT_SYMBOL_GPL(typec_partner_register_altmode); +/** + * typec_partner_set_svdm_version - Set negotiated Structured VDM (SVDM) Version + * @partner: USB Type-C Partner that supports SVDM + * @svdm_version: Negotiated SVDM Version + * + * This routine is used to save the negotiated SVDM Version. + */ +void typec_partner_set_svdm_version(struct typec_partner *partner, + enum usb_pd_svdm_ver svdm_version) +{ + partner->svdm_version = svdm_version; +} +EXPORT_SYMBOL_GPL(typec_partner_set_svdm_version); + /** * typec_register_partner - Register a USB Type-C Partner * @port: The USB Type-C Port the partner is connected to @@ -848,6 +863,7 @@ struct typec_partner *typec_register_partner(struct typec_port *port, partner->accessory = desc->accessory; partner->num_altmodes = -1; partner->pd_revision = desc->pd_revision; + partner->svdm_version = port->cap->svdm_version; if (desc->identity) { /* @@ -1894,6 +1910,33 @@ EXPORT_SYMBOL_GPL(typec_set_mode); /* --------------------------------------- */ +/** + * typec_get_negotiated_svdm_version - Get negotiated SVDM Version + * @port: USB Type-C Port. + * + * Get the negotiated SVDM Version. The Version is set to the port default + * value stored in typec_capability on partner registration, and updated after + * a successful Discover Identity if the negotiated value is less than the + * default value. + * + * Returns usb_pd_svdm_ver if the partner has been registered otherwise -ENODEV. + */ +int typec_get_negotiated_svdm_version(struct typec_port *port) +{ + enum usb_pd_svdm_ver svdm_version; + struct device *partner_dev; + + partner_dev = device_find_child(&port->dev, NULL, partner_match); + if (!partner_dev) + return -ENODEV; + + svdm_version = to_typec_partner(partner_dev)->svdm_version; + put_device(partner_dev); + + return svdm_version; +} +EXPORT_SYMBOL_GPL(typec_get_negotiated_svdm_version); + /** * typec_get_drvdata - Return private driver data pointer * @port: USB Type-C port diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index a94df82ab62f..91b4303ca305 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -217,12 +217,19 @@ struct typec_operations { enum typec_port_type type); }; +enum usb_pd_svdm_ver { + SVDM_VER_1_0 = 0, + SVDM_VER_2_0 = 1, + SVDM_VER_MAX = SVDM_VER_2_0, +}; + /* * struct typec_capability - USB Type-C Port Capabilities * @type: Supported power role of the port * @data: Supported data role of the port * @revision: USB Type-C Specification release. Binary coded decimal * @pd_revision: USB Power Delivery Specification revision if supported + * @svdm_version: USB PD Structured VDM version if supported * @prefer_role: Initial role preference (DRP ports). * @accessory: Supported Accessory Modes * @fwnode: Optional fwnode of the port @@ -236,6 +243,7 @@ struct typec_capability { enum typec_port_data data; u16 revision; /* 0120H = "1.2" */ u16 pd_revision; /* 0300H = "3.0" */ + enum usb_pd_svdm_ver svdm_version; int prefer_role; enum typec_accessory accessory[TYPEC_MAX_ACCESSORY]; unsigned int orientation_aware:1; @@ -286,4 +294,8 @@ int typec_find_orientation(const char *name); int typec_find_port_power_role(const char *name); int typec_find_power_role(const char *name); int typec_find_port_data_role(const char *name); + +void typec_partner_set_svdm_version(struct typec_partner *partner, + enum usb_pd_svdm_ver svdm_version); +int typec_get_negotiated_svdm_version(struct typec_port *port); #endif /* __LINUX_USB_TYPEC_H */ diff --git a/include/linux/usb/typec_altmode.h b/include/linux/usb/typec_altmode.h index 5e0a7b7647c3..65933cbe9129 100644 --- a/include/linux/usb/typec_altmode.h +++ b/include/linux/usb/typec_altmode.h @@ -132,6 +132,16 @@ typec_altmode_get_orientation(struct typec_altmode *altmode) return typec_get_orientation(typec_altmode2port(altmode)); } +/** + * typec_altmode_get_svdm_version - Get negotiated SVDM version + * @altmode: Handle to the alternate mode + */ +static inline int +typec_altmode_get_svdm_version(struct typec_altmode *altmode) +{ + return typec_get_negotiated_svdm_version(typec_altmode2port(altmode)); +} + /** * struct typec_altmode_driver - USB Type-C alternate mode device driver * @id_table: Null terminated array of SVIDs -- cgit v1.2.3 From 31737c27d665bb3bc8ad9396c63fae2543dd8818 Mon Sep 17 00:00:00 2001 From: Kyle Tso Date: Fri, 5 Feb 2021 11:34:10 +0800 Subject: usb: pd: Make SVDM Version configurable in VDM header PD Rev 3.0 introduces SVDM Version 2.0. This patch makes the field configuable in the header in order to be able to be compatible with older SVDM version. Acked-by: Heikki Krogerus Signed-off-by: Kyle Tso Link: https://lore.kernel.org/r/20210205033415.3320439-3-kyletso@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/altmodes/displayport.c | 2 +- drivers/usb/typec/tcpm/tcpm.c | 16 ++++++++-------- drivers/usb/typec/ucsi/displayport.c | 6 +++--- include/linux/usb/pd_vdo.h | 7 +++++-- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c index e62e5e3da01e..0abc3121238f 100644 --- a/drivers/usb/typec/altmodes/displayport.c +++ b/drivers/usb/typec/altmodes/displayport.c @@ -15,7 +15,7 @@ #include #include "displayport.h" -#define DP_HEADER(_dp, cmd) (VDO((_dp)->alt->svid, 1, cmd) | \ +#define DP_HEADER(_dp, cmd) (VDO((_dp)->alt->svid, 1, SVDM_VER_1_0, cmd) | \ VDO_OPOS(USB_TYPEC_DP_MODE)) enum { diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 8558ab006885..9aadb1e1bec5 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -1544,17 +1544,17 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, case CMD_DISCOVER_IDENT: /* 6.4.4.3.1 */ svdm_consume_identity(port, p, cnt); - response[0] = VDO(USB_SID_PD, 1, CMD_DISCOVER_SVID); + response[0] = VDO(USB_SID_PD, 1, SVDM_VER_1_0, CMD_DISCOVER_SVID); rlen = 1; break; case CMD_DISCOVER_SVID: /* 6.4.4.3.2 */ if (svdm_consume_svids(port, p, cnt)) { - response[0] = VDO(USB_SID_PD, 1, + response[0] = VDO(USB_SID_PD, 1, SVDM_VER_1_0, CMD_DISCOVER_SVID); rlen = 1; } else if (modep->nsvids && supports_modal(port)) { - response[0] = VDO(modep->svids[0], 1, + response[0] = VDO(modep->svids[0], 1, SVDM_VER_1_0, CMD_DISCOVER_MODES); rlen = 1; } @@ -1565,7 +1565,7 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, modep->svid_index++; if (modep->svid_index < modep->nsvids) { u16 svid = modep->svids[modep->svid_index]; - response[0] = VDO(svid, 1, CMD_DISCOVER_MODES); + response[0] = VDO(svid, 1, SVDM_VER_1_0, CMD_DISCOVER_MODES); rlen = 1; } else { tcpm_register_partner_altmodes(port); @@ -1695,7 +1695,7 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port, break; case ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL: if (typec_altmode_vdm(adev, p[0], &p[1], cnt)) { - response[0] = VDO(adev->svid, 1, CMD_EXIT_MODE); + response[0] = VDO(adev->svid, 1, SVDM_VER_1_0, CMD_EXIT_MODE); response[0] |= VDO_OPOS(adev->mode); rlen = 1; } @@ -1729,7 +1729,7 @@ static void tcpm_send_vdm(struct tcpm_port *port, u32 vid, int cmd, /* set VDM header with VID & CMD */ header = VDO(vid, ((vid & USB_SID_PD) == USB_SID_PD) ? - 1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION), cmd); + 1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION), SVDM_VER_1_0, cmd); tcpm_queue_vdm(port, header, data, count); } @@ -2024,7 +2024,7 @@ static int tcpm_altmode_enter(struct typec_altmode *altmode, u32 *vdo) struct tcpm_port *port = typec_altmode_get_drvdata(altmode); u32 header; - header = VDO(altmode->svid, vdo ? 2 : 1, CMD_ENTER_MODE); + header = VDO(altmode->svid, vdo ? 2 : 1, SVDM_VER_1_0, CMD_ENTER_MODE); header |= VDO_OPOS(altmode->mode); tcpm_queue_vdm_unlocked(port, header, vdo, vdo ? 1 : 0); @@ -2036,7 +2036,7 @@ static int tcpm_altmode_exit(struct typec_altmode *altmode) struct tcpm_port *port = typec_altmode_get_drvdata(altmode); u32 header; - header = VDO(altmode->svid, 1, CMD_EXIT_MODE); + header = VDO(altmode->svid, 1, SVDM_VER_1_0, CMD_EXIT_MODE); header |= VDO_OPOS(altmode->mode); tcpm_queue_vdm_unlocked(port, header, NULL, 0); diff --git a/drivers/usb/typec/ucsi/displayport.c b/drivers/usb/typec/ucsi/displayport.c index 261131c9e37c..1d387bddefb9 100644 --- a/drivers/usb/typec/ucsi/displayport.c +++ b/drivers/usb/typec/ucsi/displayport.c @@ -83,7 +83,7 @@ static int ucsi_displayport_enter(struct typec_altmode *alt, u32 *vdo) * mode, and letting the alt mode driver continue. */ - dp->header = VDO(USB_TYPEC_DP_SID, 1, CMD_ENTER_MODE); + dp->header = VDO(USB_TYPEC_DP_SID, 1, SVDM_VER_1_0, CMD_ENTER_MODE); dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE); dp->header |= VDO_CMDT(CMDT_RSP_ACK); @@ -120,7 +120,7 @@ static int ucsi_displayport_exit(struct typec_altmode *alt) if (ret < 0) goto out_unlock; - dp->header = VDO(USB_TYPEC_DP_SID, 1, CMD_EXIT_MODE); + dp->header = VDO(USB_TYPEC_DP_SID, 1, SVDM_VER_1_0, CMD_EXIT_MODE); dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE); dp->header |= VDO_CMDT(CMDT_RSP_ACK); @@ -200,7 +200,7 @@ static int ucsi_displayport_vdm(struct typec_altmode *alt, switch (cmd_type) { case CMDT_INIT: - dp->header = VDO(USB_TYPEC_DP_SID, 1, cmd); + dp->header = VDO(USB_TYPEC_DP_SID, 1, SVDM_VER_1_0, cmd); dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE); switch (cmd) { diff --git a/include/linux/usb/pd_vdo.h b/include/linux/usb/pd_vdo.h index 5de7f550f93e..b057250704e8 100644 --- a/include/linux/usb/pd_vdo.h +++ b/include/linux/usb/pd_vdo.h @@ -21,22 +21,24 @@ * ---------- * <31:16> :: SVID * <15> :: VDM type ( 1b == structured, 0b == unstructured ) - * <14:13> :: Structured VDM version (can only be 00 == 1.0 currently) + * <14:13> :: Structured VDM version * <12:11> :: reserved * <10:8> :: object position (1-7 valid ... used for enter/exit mode only) * <7:6> :: command type (SVDM only?) * <5> :: reserved (SVDM), command type (UVDM) * <4:0> :: command */ -#define VDO(vid, type, custom) \ +#define VDO(vid, type, ver, custom) \ (((vid) << 16) | \ ((type) << 15) | \ + ((ver) << 13) | \ ((custom) & 0x7FFF)) #define VDO_SVDM_TYPE (1 << 15) #define VDO_SVDM_VERS(x) ((x) << 13) #define VDO_OPOS(x) ((x) << 8) #define VDO_CMDT(x) ((x) << 6) +#define VDO_SVDM_VERS_MASK VDO_SVDM_VERS(0x3) #define VDO_OPOS_MASK VDO_OPOS(0x7) #define VDO_CMDT_MASK VDO_CMDT(0x3) @@ -74,6 +76,7 @@ #define PD_VDO_VID(vdo) ((vdo) >> 16) #define PD_VDO_SVDM(vdo) (((vdo) >> 15) & 1) +#define PD_VDO_SVDM_VER(vdo) (((vdo) >> 13) & 0x3) #define PD_VDO_OPOS(vdo) (((vdo) >> 8) & 0x7) #define PD_VDO_CMD(vdo) ((vdo) & 0x1f) #define PD_VDO_CMDT(vdo) (((vdo) >> 6) & 0x3) -- cgit v1.2.3 From 5e1d4c49fbc86dab6e005d66f066bd53c9479cde Mon Sep 17 00:00:00 2001 From: Kyle Tso Date: Fri, 5 Feb 2021 11:34:11 +0800 Subject: usb: typec: tcpm: Determine common SVDM Version PD Spec Revision 3.0 Version 2.0 + ECNs 2020-12-10 6.4.4.2.3 Structured VDM Version "The Structured VDM Version field of the Discover Identity Command sent and received during VDM discovery Shall be used to determine the lowest common Structured VDM Version supported by the Port Partners or Cable Plug and Shall continue to operate using this Specification Revision until they are Detached." Also clear the fields newly defined in SVDM version 2.0 if the negotiated SVDM version is 1.0. Acked-by: Heikki Krogerus Signed-off-by: Kyle Tso Link: https://lore.kernel.org/r/20210205033415.3320439-4-kyletso@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 71 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 10 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 9aadb1e1bec5..b45cd191a8a4 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -1475,8 +1475,10 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, const u32 *p, int cnt, u32 *response, enum adev_actions *adev_action) { + struct typec_port *typec = port->typec_port; struct typec_altmode *pdev; struct pd_mode_data *modep; + int svdm_version; int rlen = 0; int cmd_type; int cmd; @@ -1493,6 +1495,10 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, pdev = typec_match_altmode(port->partner_altmode, ALTMODE_DISCOVERY_MAX, PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0])); + svdm_version = typec_get_negotiated_svdm_version(typec); + if (svdm_version < 0) + return 0; + switch (cmd_type) { case CMDT_INIT: switch (cmd) { @@ -1500,10 +1506,22 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, if (PD_VDO_VID(p[0]) != USB_SID_PD) break; + if (PD_VDO_SVDM_VER(p[0]) < svdm_version) + typec_partner_set_svdm_version(port->partner, + PD_VDO_SVDM_VER(p[0])); /* 6.4.4.3.1: Only respond as UFP (device) */ if (port->data_role == TYPEC_DEVICE && port->nr_snk_vdo) { - for (i = 0; i < port->nr_snk_vdo; i++) + /* + * Product Type DFP and Connector Type are not defined in SVDM + * version 1.0 and shall be set to zero. + */ + if (typec_get_negotiated_svdm_version(typec) < SVDM_VER_2_0) + response[1] = port->snk_vdo[0] & ~IDH_DFP_MASK + & ~IDH_CONN_MASK; + else + response[1] = port->snk_vdo[0]; + for (i = 1; i < port->nr_snk_vdo; i++) response[i + 1] = port->snk_vdo[i]; rlen = port->nr_snk_vdo + 1; } @@ -1532,6 +1550,8 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, response[0] = p[0] | VDO_CMDT(CMDT_RSP_BUSY); rlen = 1; } + response[0] = (response[0] & ~VDO_SVDM_VERS_MASK) | + (VDO_SVDM_VERS(typec_get_negotiated_svdm_version(typec))); break; case CMDT_RSP_ACK: /* silently drop message if we are not connected */ @@ -1542,19 +1562,22 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, switch (cmd) { case CMD_DISCOVER_IDENT: + if (PD_VDO_SVDM_VER(p[0]) < svdm_version) + typec_partner_set_svdm_version(port->partner, + PD_VDO_SVDM_VER(p[0])); /* 6.4.4.3.1 */ svdm_consume_identity(port, p, cnt); - response[0] = VDO(USB_SID_PD, 1, SVDM_VER_1_0, CMD_DISCOVER_SVID); + response[0] = VDO(USB_SID_PD, 1, typec_get_negotiated_svdm_version(typec), + CMD_DISCOVER_SVID); rlen = 1; break; case CMD_DISCOVER_SVID: /* 6.4.4.3.2 */ if (svdm_consume_svids(port, p, cnt)) { - response[0] = VDO(USB_SID_PD, 1, SVDM_VER_1_0, - CMD_DISCOVER_SVID); + response[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); rlen = 1; } else if (modep->nsvids && supports_modal(port)) { - response[0] = VDO(modep->svids[0], 1, SVDM_VER_1_0, + response[0] = VDO(modep->svids[0], 1, svdm_version, CMD_DISCOVER_MODES); rlen = 1; } @@ -1565,7 +1588,7 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, modep->svid_index++; if (modep->svid_index < modep->nsvids) { u16 svid = modep->svids[modep->svid_index]; - response[0] = VDO(svid, 1, SVDM_VER_1_0, CMD_DISCOVER_MODES); + response[0] = VDO(svid, 1, svdm_version, CMD_DISCOVER_MODES); rlen = 1; } else { tcpm_register_partner_altmodes(port); @@ -1592,6 +1615,8 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, /* Unrecognized SVDM */ response[0] = p[0] | VDO_CMDT(CMDT_RSP_NAK); rlen = 1; + response[0] = (response[0] & ~VDO_SVDM_VERS_MASK) | + (VDO_SVDM_VERS(svdm_version)); break; } break; @@ -1611,6 +1636,8 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, /* Unrecognized SVDM */ response[0] = p[0] | VDO_CMDT(CMDT_RSP_NAK); rlen = 1; + response[0] = (response[0] & ~VDO_SVDM_VERS_MASK) | + (VDO_SVDM_VERS(svdm_version)); break; } port->vdm_sm_running = false; @@ -1618,6 +1645,8 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, default: response[0] = p[0] | VDO_CMDT(CMDT_RSP_NAK); rlen = 1; + response[0] = (response[0] & ~VDO_SVDM_VERS_MASK) | + (VDO_SVDM_VERS(svdm_version)); port->vdm_sm_running = false; break; } @@ -1695,7 +1724,13 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port, break; case ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL: if (typec_altmode_vdm(adev, p[0], &p[1], cnt)) { - response[0] = VDO(adev->svid, 1, SVDM_VER_1_0, CMD_EXIT_MODE); + int svdm_version = typec_get_negotiated_svdm_version( + port->typec_port); + if (svdm_version < 0) + break; + + response[0] = VDO(adev->svid, 1, svdm_version, + CMD_EXIT_MODE); response[0] |= VDO_OPOS(adev->mode); rlen = 1; } @@ -1722,14 +1757,19 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port, static void tcpm_send_vdm(struct tcpm_port *port, u32 vid, int cmd, const u32 *data, int count) { + int svdm_version = typec_get_negotiated_svdm_version(port->typec_port); u32 header; + if (svdm_version < 0) + return; + if (WARN_ON(count > VDO_MAX_SIZE - 1)) count = VDO_MAX_SIZE - 1; /* set VDM header with VID & CMD */ header = VDO(vid, ((vid & USB_SID_PD) == USB_SID_PD) ? - 1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION), SVDM_VER_1_0, cmd); + 1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION), + svdm_version, cmd); tcpm_queue_vdm(port, header, data, count); } @@ -2022,9 +2062,14 @@ static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo, static int tcpm_altmode_enter(struct typec_altmode *altmode, u32 *vdo) { struct tcpm_port *port = typec_altmode_get_drvdata(altmode); + int svdm_version; u32 header; - header = VDO(altmode->svid, vdo ? 2 : 1, SVDM_VER_1_0, CMD_ENTER_MODE); + svdm_version = typec_get_negotiated_svdm_version(port->typec_port); + if (svdm_version < 0) + return svdm_version; + + 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); @@ -2034,9 +2079,14 @@ static int tcpm_altmode_enter(struct typec_altmode *altmode, u32 *vdo) static int tcpm_altmode_exit(struct typec_altmode *altmode) { struct tcpm_port *port = typec_altmode_get_drvdata(altmode); + int svdm_version; u32 header; - header = VDO(altmode->svid, 1, SVDM_VER_1_0, CMD_EXIT_MODE); + svdm_version = typec_get_negotiated_svdm_version(port->typec_port); + if (svdm_version < 0) + return svdm_version; + + header = VDO(altmode->svid, 1, svdm_version, CMD_EXIT_MODE); header |= VDO_OPOS(altmode->mode); tcpm_queue_vdm_unlocked(port, header, NULL, 0); @@ -5977,6 +6027,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) port->typec_caps.fwnode = tcpc->fwnode; port->typec_caps.revision = 0x0120; /* Type-C spec release 1.2 */ port->typec_caps.pd_revision = 0x0300; /* USB-PD spec release 3.0 */ + port->typec_caps.svdm_version = SVDM_VER_2_0; port->typec_caps.driver_data = port; port->typec_caps.ops = &tcpm_ops; port->typec_caps.orientation_aware = 1; -- cgit v1.2.3 From 372adf075a43028a62e6123b9cd969f8f20e4afc Mon Sep 17 00:00:00 2001 From: Kyle Tso Date: Fri, 5 Feb 2021 11:34:12 +0800 Subject: usb: typec: ucsi: Determine common SVDM Version This patch implements the following requirement in the Spec. PD Spec Revision 3.0 Version 2.0 + ECNs 2020-12-10 6.4.4.2.3 Structured VDM Version "The Structured VDM Version field of the Discover Identity Command sent and received during VDM discovery Shall be used to determine the lowest common Structured VDM Version supported by the Port Partners or Cable Plug and Shall continue to operate using this Specification Revision until they are Detached." Acked-by: Heikki Krogerus Signed-off-by: Kyle Tso Link: https://lore.kernel.org/r/20210205033415.3320439-5-kyletso@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/displayport.c | 32 +++++++++++++++++++++++++++++--- drivers/usb/typec/ucsi/ucsi.c | 1 + 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/drivers/usb/typec/ucsi/displayport.c b/drivers/usb/typec/ucsi/displayport.c index 1d387bddefb9..73cd5bf35047 100644 --- a/drivers/usb/typec/ucsi/displayport.c +++ b/drivers/usb/typec/ucsi/displayport.c @@ -49,6 +49,7 @@ static int ucsi_displayport_enter(struct typec_altmode *alt, u32 *vdo) { struct ucsi_dp *dp = typec_altmode_get_drvdata(alt); struct ucsi *ucsi = dp->con->ucsi; + int svdm_version; u64 command; u8 cur = 0; int ret; @@ -83,7 +84,13 @@ static int ucsi_displayport_enter(struct typec_altmode *alt, u32 *vdo) * mode, and letting the alt mode driver continue. */ - dp->header = VDO(USB_TYPEC_DP_SID, 1, SVDM_VER_1_0, CMD_ENTER_MODE); + svdm_version = typec_altmode_get_svdm_version(alt); + if (svdm_version < 0) { + ret = svdm_version; + goto err_unlock; + } + + dp->header = VDO(USB_TYPEC_DP_SID, 1, svdm_version, CMD_ENTER_MODE); dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE); dp->header |= VDO_CMDT(CMDT_RSP_ACK); @@ -101,6 +108,7 @@ err_unlock: static int ucsi_displayport_exit(struct typec_altmode *alt) { struct ucsi_dp *dp = typec_altmode_get_drvdata(alt); + int svdm_version; u64 command; int ret = 0; @@ -120,7 +128,13 @@ static int ucsi_displayport_exit(struct typec_altmode *alt) if (ret < 0) goto out_unlock; - dp->header = VDO(USB_TYPEC_DP_SID, 1, SVDM_VER_1_0, CMD_EXIT_MODE); + svdm_version = typec_altmode_get_svdm_version(alt); + if (svdm_version < 0) { + ret = svdm_version; + goto out_unlock; + } + + dp->header = VDO(USB_TYPEC_DP_SID, 1, svdm_version, CMD_EXIT_MODE); dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE); dp->header |= VDO_CMDT(CMDT_RSP_ACK); @@ -186,6 +200,7 @@ static int ucsi_displayport_vdm(struct typec_altmode *alt, struct ucsi_dp *dp = typec_altmode_get_drvdata(alt); int cmd_type = PD_VDO_CMDT(header); int cmd = PD_VDO_CMD(header); + int svdm_version; mutex_lock(&dp->con->lock); @@ -198,9 +213,20 @@ static int ucsi_displayport_vdm(struct typec_altmode *alt, return -EOPNOTSUPP; } + svdm_version = typec_altmode_get_svdm_version(alt); + if (svdm_version < 0) { + mutex_unlock(&dp->con->lock); + return svdm_version; + } + switch (cmd_type) { case CMDT_INIT: - dp->header = VDO(USB_TYPEC_DP_SID, 1, SVDM_VER_1_0, cmd); + if (PD_VDO_SVDM_VER(header) < svdm_version) { + typec_partner_set_svdm_version(dp->con->partner, PD_VDO_SVDM_VER(header)); + svdm_version = PD_VDO_SVDM_VER(header); + } + + dp->header = VDO(USB_TYPEC_DP_SID, 1, svdm_version, cmd); dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE); switch (cmd) { diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index ca3f4194ad90..244270755ae6 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -1052,6 +1052,7 @@ static int ucsi_register_port(struct ucsi *ucsi, int index) cap->revision = ucsi->cap.typec_version; cap->pd_revision = ucsi->cap.pd_version; + cap->svdm_version = SVDM_VER_2_0; cap->prefer_role = TYPEC_NO_PREFERRED_ROLE; if (con->cap.op_mode & UCSI_CONCAP_OPMODE_AUDIO_ACCESSORY) -- cgit v1.2.3 From 4c93cad8cc78bd382aded3155cf05736ecfd0910 Mon Sep 17 00:00:00 2001 From: Kyle Tso Date: Fri, 5 Feb 2021 11:34:13 +0800 Subject: usb: typec: displayport: Fill the negotiated SVDM Version in the header VDM header now requires SVDM Version. Get it from typec_partner. Acked-by: Heikki Krogerus Signed-off-by: Kyle Tso Link: https://lore.kernel.org/r/20210205033415.3320439-6-kyletso@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/altmodes/displayport.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c index 0abc3121238f..b7f094435b00 100644 --- a/drivers/usb/typec/altmodes/displayport.c +++ b/drivers/usb/typec/altmodes/displayport.c @@ -15,8 +15,8 @@ #include #include "displayport.h" -#define DP_HEADER(_dp, cmd) (VDO((_dp)->alt->svid, 1, SVDM_VER_1_0, cmd) | \ - VDO_OPOS(USB_TYPEC_DP_MODE)) +#define DP_HEADER(_dp, ver, cmd) (VDO((_dp)->alt->svid, 1, ver, cmd) \ + | VDO_OPOS(USB_TYPEC_DP_MODE)) enum { DP_CONF_USB, @@ -156,9 +156,14 @@ static int dp_altmode_configured(struct dp_altmode *dp) static int dp_altmode_configure_vdm(struct dp_altmode *dp, u32 conf) { - u32 header = DP_HEADER(dp, DP_CMD_CONFIGURE); + int svdm_version = typec_altmode_get_svdm_version(dp->alt); + u32 header; int ret; + if (svdm_version < 0) + return svdm_version; + + header = DP_HEADER(dp, svdm_version, DP_CMD_CONFIGURE); ret = typec_altmode_notify(dp->alt, TYPEC_STATE_SAFE, &dp->data); if (ret) { dev_err(&dp->alt->dev, @@ -181,6 +186,7 @@ static int dp_altmode_configure_vdm(struct dp_altmode *dp, u32 conf) static void dp_altmode_work(struct work_struct *work) { struct dp_altmode *dp = container_of(work, struct dp_altmode, work); + int svdm_version; u32 header; u32 vdo; int ret; @@ -194,7 +200,10 @@ static void dp_altmode_work(struct work_struct *work) dev_err(&dp->alt->dev, "failed to enter mode\n"); break; case DP_STATE_UPDATE: - header = DP_HEADER(dp, DP_CMD_STATUS_UPDATE); + svdm_version = typec_altmode_get_svdm_version(dp->alt); + if (svdm_version < 0) + break; + header = DP_HEADER(dp, svdm_version, DP_CMD_STATUS_UPDATE); vdo = 1; ret = typec_altmode_vdm(dp->alt, header, &vdo, 2); if (ret) -- cgit v1.2.3 From 630dce2810b9f09d312aed4189300e785254c24b Mon Sep 17 00:00:00 2001 From: Kyle Tso Date: Fri, 5 Feb 2021 11:34:14 +0800 Subject: dt-bindings: connector: Add SVDM VDO properties Add bindings of VDO properties of USB PD SVDM so that they can be used in device tree. Reviewed-by: Rob Herring Acked-by: Heikki Krogerus Signed-off-by: Kyle Tso Link: https://lore.kernel.org/r/20210205033415.3320439-7-kyletso@google.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/connector/usb-connector.yaml | 11 + include/dt-bindings/usb/pd.h | 311 ++++++++++++++++++++- 2 files changed, 321 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/connector/usb-connector.yaml b/Documentation/devicetree/bindings/connector/usb-connector.yaml index 4286ed767a0a..d385026944ec 100644 --- a/Documentation/devicetree/bindings/connector/usb-connector.yaml +++ b/Documentation/devicetree/bindings/connector/usb-connector.yaml @@ -137,6 +137,17 @@ properties: maxItems: 7 $ref: /schemas/types.yaml#/definitions/uint32-array + sink-vdos: + description: An array of u32 with each entry (VDM Objects) providing additional information + corresponding to the product, the detailed bit definitions and the order of each VDO can be + found in "USB Power Delivery Specification Revision 3.0, Version 2.0 + ECNs 2020-12-10" + chapter 6.4.4.3.1 Discover Identity. User can specify the VDO array via + VDO_IDH/_CERT/_PRODUCT/_UFP/_DFP/_PCABLE/_ACABLE(1/2)/_VPD() defined in + dt-bindings/usb/pd.h. + minItems: 3 + maxItems: 6 + $ref: /schemas/types.yaml#/definitions/uint32-array + op-sink-microwatt: description: Sink required operating power in microwatt, if source can't offer the power, Capability Mismatch is set. Required for power sink and diff --git a/include/dt-bindings/usb/pd.h b/include/dt-bindings/usb/pd.h index 0352893697f0..fef3ef65967f 100644 --- a/include/dt-bindings/usb/pd.h +++ b/include/dt-bindings/usb/pd.h @@ -93,4 +93,313 @@ #define FRS_DEFAULT_POWER 1 #define FRS_5V_1P5A 2 #define FRS_5V_3A 3 - #endif /* __DT_POWER_DELIVERY_H */ + +/* + * SVDM Identity Header + * -------------------- + * <31> :: data capable as a USB host + * <30> :: data capable as a USB device + * <29:27> :: product type (UFP / Cable / VPD) + * <26> :: modal operation supported (1b == yes) + * <25:23> :: product type (DFP) (SVDM version 2.0+ only; set to zero in version 1.0) + * <22:21> :: connector type (SVDM version 2.0+ only; set to zero in version 1.0) + * <20:16> :: Reserved, Shall be set to zero + * <15:0> :: USB-IF assigned VID for this cable vendor + */ +/* SOP Product Type (UFP) */ +#define IDH_PTYPE_NOT_UFP 0 +#define IDH_PTYPE_HUB 1 +#define IDH_PTYPE_PERIPH 2 +#define IDH_PTYPE_PSD 3 +#define IDH_PTYPE_AMA 5 + +/* SOP' Product Type (Cable Plug / VPD) */ +#define IDH_PTYPE_NOT_CABLE 0 +#define IDH_PTYPE_PCABLE 3 +#define IDH_PTYPE_ACABLE 4 +#define IDH_PTYPE_VPD 6 + +/* SOP Product Type (DFP) */ +#define IDH_PTYPE_NOT_DFP 0 +#define IDH_PTYPE_DFP_HUB 1 +#define IDH_PTYPE_DFP_HOST 2 +#define IDH_PTYPE_DFP_PB 3 + +#define VDO_IDH(usbh, usbd, ufp_cable, is_modal, dfp, conn, vid) \ + ((usbh) << 31 | (usbd) << 30 | ((ufp_cable) & 0x7) << 27 \ + | (is_modal) << 26 | ((dfp) & 0x7) << 23 | ((conn) & 0x3) << 21 \ + | ((vid) & 0xffff)) + +/* + * Cert Stat VDO + * ------------- + * <31:0> : USB-IF assigned XID for this cable + */ +#define VDO_CERT(xid) ((xid) & 0xffffffff) + +/* + * Product VDO + * ----------- + * <31:16> : USB Product ID + * <15:0> : USB bcdDevice + */ +#define VDO_PRODUCT(pid, bcd) (((pid) & 0xffff) << 16 | ((bcd) & 0xffff)) + +/* + * UFP VDO (PD Revision 3.0+ only) + * -------- + * <31:29> :: UFP VDO version + * <28> :: Reserved + * <27:24> :: Device capability + * <23:22> :: Connector type (10b == receptacle, 11b == captive plug) + * <21:11> :: Reserved + * <10:8> :: Vconn power (AMA only) + * <7> :: Vconn required (AMA only, 0b == no, 1b == yes) + * <6> :: Vbus required (AMA only, 0b == yes, 1b == no) + * <5:3> :: Alternate modes + * <2:0> :: USB highest speed + */ +/* UFP VDO Version */ +#define UFP_VDO_VER1_2 2 + +/* Device Capability */ +#define DEV_USB2_CAPABLE BIT(0) +#define DEV_USB2_BILLBOARD BIT(1) +#define DEV_USB3_CAPABLE BIT(2) +#define DEV_USB4_CAPABLE BIT(3) + +/* Connector Type */ +#define UFP_RECEPTACLE 2 +#define UFP_CAPTIVE 3 + +/* Vconn Power (AMA only, set to AMA_VCONN_NOT_REQ if Vconn is not required) */ +#define AMA_VCONN_PWR_1W 0 +#define AMA_VCONN_PWR_1W5 1 +#define AMA_VCONN_PWR_2W 2 +#define AMA_VCONN_PWR_3W 3 +#define AMA_VCONN_PWR_4W 4 +#define AMA_VCONN_PWR_5W 5 +#define AMA_VCONN_PWR_6W 6 + +/* Vconn Required (AMA only) */ +#define AMA_VCONN_NOT_REQ 0 +#define AMA_VCONN_REQ 1 + +/* Vbus Required (AMA only) */ +#define AMA_VBUS_REQ 0 +#define AMA_VBUS_NOT_REQ 1 + +/* Alternate Modes */ +#define UFP_ALTMODE_NOT_SUPP 0 +#define UFP_ALTMODE_TBT3 BIT(0) +#define UFP_ALTMODE_RECFG BIT(1) +#define UFP_ALTMODE_NO_RECFG BIT(2) + +/* USB Highest Speed */ +#define UFP_USB2_ONLY 0 +#define UFP_USB32_GEN1 1 +#define UFP_USB32_4_GEN2 2 +#define UFP_USB4_GEN3 3 + +#define VDO_UFP(ver, cap, conn, vcpwr, vcr, vbr, alt, spd) \ + (((ver) & 0x7) << 29 | ((cap) & 0xf) << 24 | ((conn) & 0x3) << 22 \ + | ((vcpwr) & 0x7) << 8 | (vcr) << 7 | (vbr) << 6 | ((alt) & 0x7) << 3 \ + | ((spd) & 0x7)) + +/* + * DFP VDO (PD Revision 3.0+ only) + * -------- + * <31:29> :: DFP VDO version + * <28:27> :: Reserved + * <26:24> :: Host capability + * <23:22> :: Connector type (10b == receptacle, 11b == captive plug) + * <21:5> :: Reserved + * <4:0> :: Port number + */ +#define DFP_VDO_VER1_1 1 +#define HOST_USB2_CAPABLE BIT(0) +#define HOST_USB3_CAPABLE BIT(1) +#define HOST_USB4_CAPABLE BIT(2) +#define DFP_RECEPTACLE 2 +#define DFP_CAPTIVE 3 + +#define VDO_DFP(ver, cap, conn, pnum) \ + (((ver) & 0x7) << 29 | ((cap) & 0x7) << 24 | ((conn) & 0x3) << 22 \ + | ((pnum) & 0x1f)) + +/* + * Passive Cable VDO + * --------- + * <31:28> :: Cable HW version + * <27:24> :: Cable FW version + * <23:21> :: VDO version + * <20> :: Reserved, Shall be set to zero + * <19:18> :: Type-C to Type-C/Captive (10b == C, 11b == Captive) + * <17> :: Reserved, Shall be set to zero + * <16:13> :: cable latency (0001 == <10ns(~1m length)) + * <12:11> :: cable termination type (10b == Vconn not req, 01b == Vconn req) + * <10:9> :: Maximum Vbus voltage (00b == 20V, 01b == 30V, 10b == 40V, 11b == 50V) + * <8:7> :: Reserved, Shall be set to zero + * <6:5> :: Vbus current handling capability (01b == 3A, 10b == 5A) + * <4:3> :: Reserved, Shall be set to zero + * <2:0> :: USB highest speed + * + * Active Cable VDO 1 + * --------- + * <31:28> :: Cable HW version + * <27:24> :: Cable FW version + * <23:21> :: VDO version + * <20> :: Reserved, Shall be set to zero + * <19:18> :: Connector type (10b == C, 11b == Captive) + * <17> :: Reserved, Shall be set to zero + * <16:13> :: cable latency (0001 == <10ns(~1m length)) + * <12:11> :: cable termination type (10b == one end active, 11b == both ends active VCONN req) + * <10:9> :: Maximum Vbus voltage (00b == 20V, 01b == 30V, 10b == 40V, 11b == 50V) + * <8> :: SBU supported (0b == supported, 1b == not supported) + * <7> :: SBU type (0b == passive, 1b == active) + * <6:5> :: Vbus current handling capability (01b == 3A, 10b == 5A) + * <4> :: Vbus through cable (0b == no, 1b == yes) + * <3> :: SOP" controller present? (0b == no, 1b == yes) + * <2:0> :: USB highest speed + */ +/* Cable VDO Version */ +#define CABLE_VDO_VER1_0 0 +#define CABLE_VDO_VER1_3 3 + +/* Connector Type */ +#define CABLE_CTYPE 2 +#define CABLE_CAPTIVE 3 + +/* Cable Latency */ +#define CABLE_LATENCY_1M 1 +#define CABLE_LATENCY_2M 2 +#define CABLE_LATENCY_3M 3 +#define CABLE_LATENCY_4M 4 +#define CABLE_LATENCY_5M 5 +#define CABLE_LATENCY_6M 6 +#define CABLE_LATENCY_7M 7 +#define CABLE_LATENCY_7M_PLUS 8 + +/* Cable Termination Type */ +#define PCABLE_VCONN_NOT_REQ 0 +#define PCABLE_VCONN_REQ 1 +#define ACABLE_ONE_END 2 +#define ACABLE_BOTH_END 3 + +/* Maximum Vbus Voltage */ +#define CABLE_MAX_VBUS_20V 0 +#define CABLE_MAX_VBUS_30V 1 +#define CABLE_MAX_VBUS_40V 2 +#define CABLE_MAX_VBUS_50V 3 + +/* Active Cable SBU Supported/Type */ +#define ACABLE_SBU_SUPP 0 +#define ACABLE_SBU_NOT_SUPP 1 +#define ACABLE_SBU_PASSIVE 0 +#define ACABLE_SBU_ACTIVE 1 + +/* Vbus Current Handling Capability */ +#define CABLE_CURR_DEF 0 +#define CABLE_CURR_3A 1 +#define CABLE_CURR_5A 2 + +/* USB Highest Speed */ +#define CABLE_USB2_ONLY 0 +#define CABLE_USB32_GEN1 1 +#define CABLE_USB32_4_GEN2 2 +#define CABLE_USB4_GEN3 3 + +#define VDO_PCABLE(hw, fw, ver, conn, lat, term, vbm, cur, spd) \ + (((hw) & 0xf) << 28 | ((fw) & 0xf) << 24 | ((ver) & 0x7) << 21 \ + | ((conn) & 0x3) << 18 | ((lat) & 0xf) << 13 | ((term) & 0x3) << 11 \ + | ((vbm) & 0x3) << 9 | ((cur) & 0x3) << 5 | ((spd) & 0x7)) +#define VDO_ACABLE1(hw, fw, ver, conn, lat, term, vbm, sbu, sbut, cur, vbt, sopp, spd) \ + (((hw) & 0xf) << 28 | ((fw) & 0xf) << 24 | ((ver) & 0x7) << 21 \ + | ((conn) & 0x3) << 18 | ((lat) & 0xf) << 13 | ((term) & 0x3) << 11 \ + | ((vbm) & 0x3) << 9 | (sbu) << 8 | (sbut) << 7 | ((cur) & 0x3) << 5 \ + | (vbt) << 4 | (sopp) << 3 | ((spd) & 0x7)) + +/* + * Active Cable VDO 2 + * --------- + * <31:24> :: Maximum operating temperature + * <23:16> :: Shutdown temperature + * <15> :: Reserved, Shall be set to zero + * <14:12> :: U3/CLd power + * <11> :: U3 to U0 transition mode (0b == direct, 1b == through U3S) + * <10> :: Physical connection (0b == copper, 1b == optical) + * <9> :: Active element (0b == redriver, 1b == retimer) + * <8> :: USB4 supported (0b == yes, 1b == no) + * <7:6> :: USB2 hub hops consumed + * <5> :: USB2 supported (0b == yes, 1b == no) + * <4> :: USB3.2 supported (0b == yes, 1b == no) + * <3> :: USB lanes supported (0b == one lane, 1b == two lanes) + * <2> :: Optically isolated active cable (0b == no, 1b == yes) + * <1> :: Reserved, Shall be set to zero + * <0> :: USB gen (0b == gen1, 1b == gen2+) + */ +/* U3/CLd Power*/ +#define ACAB2_U3_CLD_10MW_PLUS 0 +#define ACAB2_U3_CLD_10MW 1 +#define ACAB2_U3_CLD_5MW 2 +#define ACAB2_U3_CLD_1MW 3 +#define ACAB2_U3_CLD_500UW 4 +#define ACAB2_U3_CLD_200UW 5 +#define ACAB2_U3_CLD_50UW 6 + +/* Other Active Cable VDO 2 Fields */ +#define ACAB2_U3U0_DIRECT 0 +#define ACAB2_U3U0_U3S 1 +#define ACAB2_PHY_COPPER 0 +#define ACAB2_PHY_OPTICAL 1 +#define ACAB2_REDRIVER 0 +#define ACAB2_RETIMER 1 +#define ACAB2_USB4_SUPP 0 +#define ACAB2_USB4_NOT_SUPP 1 +#define ACAB2_USB2_SUPP 0 +#define ACAB2_USB2_NOT_SUPP 1 +#define ACAB2_USB32_SUPP 0 +#define ACAB2_USB32_NOT_SUPP 1 +#define ACAB2_LANES_ONE 0 +#define ACAB2_LANES_TWO 1 +#define ACAB2_OPT_ISO_NO 0 +#define ACAB2_OPT_ISO_YES 1 +#define ACAB2_GEN_1 0 +#define ACAB2_GEN_2_PLUS 1 + +#define VDO_ACABLE2(mtemp, stemp, u3p, trans, phy, ele, u4, hops, u2, u32, lane, iso, gen) \ + (((mtemp) & 0xff) << 24 | ((stemp) & 0xff) << 16 | ((u3p) & 0x7) << 12 \ + | (trans) << 11 | (phy) << 10 | (ele) << 9 | (u4) << 8 \ + | ((hops) & 0x3) << 6 | (u2) << 5 | (u32) << 4 | (lane) << 3 \ + | (iso) << 2 | (gen)) + +/* + * VPD VDO + * --------- + * <31:28> :: HW version + * <27:24> :: FW version + * <23:21> :: VDO version + * <20:17> :: Reserved, Shall be set to zero + * <16:15> :: Maximum Vbus voltage (00b == 20V, 01b == 30V, 10b == 40V, 11b == 50V) + * <14> :: Charge through current support (0b == 3A, 1b == 5A) + * <13> :: Reserved, Shall be set to zero + * <12:7> :: Vbus impedance + * <6:1> :: Ground impedance + * <0> :: Charge through support (0b == no, 1b == yes) + */ +#define VPD_VDO_VER1_0 0 +#define VPD_MAX_VBUS_20V 0 +#define VPD_MAX_VBUS_30V 1 +#define VPD_MAX_VBUS_40V 2 +#define VPD_MAX_VBUS_50V 3 +#define VPDCT_CURR_3A 0 +#define VPDCT_CURR_5A 1 +#define VPDCT_NOT_SUPP 0 +#define VPDCT_SUPP 1 + +#define VDO_VPD(hw, fw, ver, vbm, curr, vbi, gi, ct) \ + (((hw) & 0xf) << 28 | ((fw) & 0xf) << 24 | ((ver) & 0x7) << 21 \ + | ((vbm) & 0x3) << 15 | (curr) << 14 | ((vbi) & 0x3f) << 7 \ + | ((gi) & 0x3f) << 1 | (ct)) + +#endif /* __DT_POWER_DELIVERY_H */ -- cgit v1.2.3 From f5d1d63e7359c6b3c65097b4941756b3d8ac0be0 Mon Sep 17 00:00:00 2001 From: Kyle Tso Date: Fri, 5 Feb 2021 11:34:15 +0800 Subject: usb: typec: tcpm: Get Sink VDO from fwnode Commit a079973f462a ("usb: typec: tcpm: Remove tcpc_config configuration mechanism") removed the tcpc_config which includes the Sink VDO and it is not yet added back with fwnode. Add it now. Acked-by: Heikki Krogerus Signed-off-by: Kyle Tso Link: https://lore.kernel.org/r/20210205033415.3320439-8-kyletso@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index b45cd191a8a4..be0b6469dd3d 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -5722,6 +5722,20 @@ sink: port->new_source_frs_current = frs_current; } + /* sink-vdos is optional */ + ret = fwnode_property_count_u32(fwnode, "sink-vdos"); + if (ret < 0) + ret = 0; + + port->nr_snk_vdo = min(ret, VDO_MAX_OBJECTS); + if (port->nr_snk_vdo) { + ret = fwnode_property_read_u32_array(fwnode, "sink-vdos", + port->snk_vdo, + port->nr_snk_vdo); + if (ret < 0) + return ret; + } + return 0; } -- cgit v1.2.3 From fe6ad68e4a6235aef254233c2630096f1cc8e505 Mon Sep 17 00:00:00 2001 From: Jiapeng Chong Date: Sun, 7 Feb 2021 14:18:16 +0800 Subject: usb: gadget: Assign boolean values to a bool variable Fix the following coccicheck warnings: ./drivers/usb/gadget/udc/udc-xilinx.c:846:1-17: WARNING: Assignment of 0/1 to bool variable. Reported-by: Abaci Robot Signed-off-by: Jiapeng Chong Link: https://lore.kernel.org/r/1612678696-15794-1-git-send-email-jiapeng.chong@linux.alibaba.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/udc-xilinx.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/gadget/udc/udc-xilinx.c b/drivers/usb/gadget/udc/udc-xilinx.c index 77610b5f7db5..72f2ea062d55 100644 --- a/drivers/usb/gadget/udc/udc-xilinx.c +++ b/drivers/usb/gadget/udc/udc-xilinx.c @@ -843,8 +843,8 @@ static int __xudc_ep_enable(struct xusb_ep *ep, break; } - ep->buffer0ready = 0; - ep->buffer1ready = 0; + ep->buffer0ready = false; + ep->buffer1ready = false; ep->curbufnum = 0; ep->rambase = rambase[ep->epnumber]; xudc_epconfig(ep, udc); @@ -868,11 +868,11 @@ static int __xudc_ep_enable(struct xusb_ep *ep, if (ep->epnumber && !ep->is_in) { udc->write_fn(udc->addr, XUSB_BUFFREADY_OFFSET, 1 << ep->epnumber); - ep->buffer0ready = 1; + ep->buffer0ready = true; udc->write_fn(udc->addr, XUSB_BUFFREADY_OFFSET, (1 << (ep->epnumber + XUSB_STATUS_EP_BUFF2_SHIFT))); - ep->buffer1ready = 1; + ep->buffer1ready = true; } return 0; -- cgit v1.2.3 From a54af1b7d667927162d13083a8e2d470fb8722e2 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Mon, 8 Feb 2021 15:31:48 +0100 Subject: USB: serial: drop if with an always false condition In a bus remove function the passed device is always valid, so there is no need to check for it being NULL. (Side note: The check for port being non-NULL is broken anyhow, because to_usb_serial_port() is a wrapper around container_of() for a member that is not the first one. So port can hardly become NULL.) Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20210208143149.963644-1-uwe@kleine-koenig.org Signed-off-by: Johan Hovold --- drivers/usb/serial/bus.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/usb/serial/bus.c b/drivers/usb/serial/bus.c index eb0195cf37dd..58206ef0a780 100644 --- a/drivers/usb/serial/bus.c +++ b/drivers/usb/serial/bus.c @@ -86,16 +86,12 @@ err_autopm_put: static int usb_serial_device_remove(struct device *dev) { + struct usb_serial_port *port = to_usb_serial_port(dev); struct usb_serial_driver *driver; - struct usb_serial_port *port; int retval = 0; int minor; int autopm_err; - port = to_usb_serial_port(dev); - if (!port) - return -ENODEV; - /* * Make sure suspend/resume doesn't race against port_remove. * -- cgit v1.2.3 From c5d1448fa353242635fa3e1fed6ab4558e0e7d9a Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Mon, 8 Feb 2021 15:31:49 +0100 Subject: USB: serial: make remove callback return void All usb_serial drivers return 0 in their remove callbacks and driver core ignores the value returned by usb_serial_device_remove(). So change the remove callback to return void and return 0 unconditionally in usb_serial_device_remove(). Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20210208143149.963644-2-uwe@kleine-koenig.org Signed-off-by: Johan Hovold --- drivers/usb/serial/ark3116.c | 4 +--- drivers/usb/serial/belkin_sa.c | 6 ++---- drivers/usb/serial/bus.c | 5 ++--- drivers/usb/serial/ch341.c | 4 +--- drivers/usb/serial/cp210x.c | 6 ++---- drivers/usb/serial/cyberjack.c | 6 ++---- drivers/usb/serial/cypress_m8.c | 6 ++---- drivers/usb/serial/digi_acceleport.c | 6 ++---- drivers/usb/serial/f81534.c | 3 +-- drivers/usb/serial/ftdi_sio.c | 6 ++---- drivers/usb/serial/garmin_gps.c | 3 +-- drivers/usb/serial/io_edgeport.c | 6 ++---- drivers/usb/serial/io_ti.c | 4 +--- drivers/usb/serial/iuu_phoenix.c | 4 +--- drivers/usb/serial/keyspan.c | 6 ++---- drivers/usb/serial/keyspan_pda.c | 4 +--- drivers/usb/serial/kl5kusb105.c | 6 ++---- drivers/usb/serial/kobil_sct.c | 6 ++---- drivers/usb/serial/mct_u232.c | 6 ++---- drivers/usb/serial/metro-usb.c | 4 +--- drivers/usb/serial/mos7720.c | 4 +--- drivers/usb/serial/mos7840.c | 4 +--- drivers/usb/serial/omninet.c | 6 ++---- drivers/usb/serial/opticon.c | 4 +--- drivers/usb/serial/oti6858.c | 6 ++---- drivers/usb/serial/pl2303.c | 4 +--- drivers/usb/serial/quatech2.c | 4 +--- drivers/usb/serial/sierra.c | 4 +--- drivers/usb/serial/spcp8x5.c | 4 +--- drivers/usb/serial/ssu100.c | 4 +--- drivers/usb/serial/symbolserial.c | 4 +--- drivers/usb/serial/ti_usb_3410_5052.c | 6 ++---- drivers/usb/serial/upd78f0730.c | 4 +--- drivers/usb/serial/usb-wwan.h | 2 +- drivers/usb/serial/usb_wwan.c | 4 +--- drivers/usb/serial/whiteheat.c | 6 ++---- include/linux/usb/serial.h | 2 +- 37 files changed, 53 insertions(+), 120 deletions(-) diff --git a/drivers/usb/serial/ark3116.c b/drivers/usb/serial/ark3116.c index 71a9206ea1e2..f0ac7bb07ac1 100644 --- a/drivers/usb/serial/ark3116.c +++ b/drivers/usb/serial/ark3116.c @@ -178,15 +178,13 @@ static int ark3116_port_probe(struct usb_serial_port *port) return 0; } -static int ark3116_port_remove(struct usb_serial_port *port) +static void ark3116_port_remove(struct usb_serial_port *port) { struct ark3116_private *priv = usb_get_serial_port_data(port); /* device is closed, so URBs and DMA should be down */ mutex_destroy(&priv->hw_lock); kfree(priv); - - return 0; } static void ark3116_set_termios(struct tty_struct *tty, diff --git a/drivers/usb/serial/belkin_sa.c b/drivers/usb/serial/belkin_sa.c index 9bb123ab9bc9..ed9193f3bb1a 100644 --- a/drivers/usb/serial/belkin_sa.c +++ b/drivers/usb/serial/belkin_sa.c @@ -37,7 +37,7 @@ /* function prototypes for a Belkin USB Serial Adapter F5U103 */ static int belkin_sa_port_probe(struct usb_serial_port *port); -static int belkin_sa_port_remove(struct usb_serial_port *port); +static void belkin_sa_port_remove(struct usb_serial_port *port); static int belkin_sa_open(struct tty_struct *tty, struct usb_serial_port *port); static void belkin_sa_close(struct usb_serial_port *port); @@ -134,14 +134,12 @@ static int belkin_sa_port_probe(struct usb_serial_port *port) return 0; } -static int belkin_sa_port_remove(struct usb_serial_port *port) +static void belkin_sa_port_remove(struct usb_serial_port *port) { struct belkin_sa_private *priv; priv = usb_get_serial_port_data(port); kfree(priv); - - return 0; } static int belkin_sa_open(struct tty_struct *tty, diff --git a/drivers/usb/serial/bus.c b/drivers/usb/serial/bus.c index 58206ef0a780..1a8c7821d00a 100644 --- a/drivers/usb/serial/bus.c +++ b/drivers/usb/serial/bus.c @@ -88,7 +88,6 @@ static int usb_serial_device_remove(struct device *dev) { struct usb_serial_port *port = to_usb_serial_port(dev); struct usb_serial_driver *driver; - int retval = 0; int minor; int autopm_err; @@ -105,7 +104,7 @@ static int usb_serial_device_remove(struct device *dev) driver = port->serial->type; if (driver->port_remove) - retval = driver->port_remove(port); + driver->port_remove(port); dev_info(dev, "%s converter now disconnected from ttyUSB%d\n", driver->description, minor); @@ -113,7 +112,7 @@ static int usb_serial_device_remove(struct device *dev) if (!autopm_err) usb_autopm_put_interface(port->serial->interface); - return retval; + return 0; } static ssize_t new_id_store(struct device_driver *driver, diff --git a/drivers/usb/serial/ch341.c b/drivers/usb/serial/ch341.c index 28deaaec581f..8d997b71056f 100644 --- a/drivers/usb/serial/ch341.c +++ b/drivers/usb/serial/ch341.c @@ -419,14 +419,12 @@ error: kfree(priv); return r; } -static int ch341_port_remove(struct usb_serial_port *port) +static void ch341_port_remove(struct usb_serial_port *port) { struct ch341_private *priv; priv = usb_get_serial_port_data(port); kfree(priv); - - return 0; } static int ch341_carrier_raised(struct usb_serial_port *port) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index cc4f63a06f9e..665798043d3c 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -44,7 +44,7 @@ static int cp210x_attach(struct usb_serial *); static void cp210x_disconnect(struct usb_serial *); static void cp210x_release(struct usb_serial *); static int cp210x_port_probe(struct usb_serial_port *); -static int cp210x_port_remove(struct usb_serial_port *); +static void cp210x_port_remove(struct usb_serial_port *); static void cp210x_dtr_rts(struct usb_serial_port *port, int on); static void cp210x_process_read_urb(struct urb *urb); static void cp210x_enable_event_mode(struct usb_serial_port *port); @@ -1841,14 +1841,12 @@ static int cp210x_port_probe(struct usb_serial_port *port) return 0; } -static int cp210x_port_remove(struct usb_serial_port *port) +static void cp210x_port_remove(struct usb_serial_port *port) { struct cp210x_port_private *port_priv; port_priv = usb_get_serial_port_data(port); kfree(port_priv); - - return 0; } static void cp210x_init_max_speed(struct usb_serial *serial) diff --git a/drivers/usb/serial/cyberjack.c b/drivers/usb/serial/cyberjack.c index 2e40908963da..cf389224d528 100644 --- a/drivers/usb/serial/cyberjack.c +++ b/drivers/usb/serial/cyberjack.c @@ -47,7 +47,7 @@ /* Function prototypes */ static int cyberjack_port_probe(struct usb_serial_port *port); -static int cyberjack_port_remove(struct usb_serial_port *port); +static void cyberjack_port_remove(struct usb_serial_port *port); static int cyberjack_open(struct tty_struct *tty, struct usb_serial_port *port); static void cyberjack_close(struct usb_serial_port *port); @@ -120,7 +120,7 @@ static int cyberjack_port_probe(struct usb_serial_port *port) return 0; } -static int cyberjack_port_remove(struct usb_serial_port *port) +static void cyberjack_port_remove(struct usb_serial_port *port) { struct cyberjack_private *priv; @@ -128,8 +128,6 @@ static int cyberjack_port_remove(struct usb_serial_port *port) priv = usb_get_serial_port_data(port); kfree(priv); - - return 0; } static int cyberjack_open(struct tty_struct *tty, diff --git a/drivers/usb/serial/cypress_m8.c b/drivers/usb/serial/cypress_m8.c index cc028601c388..166ee2286fda 100644 --- a/drivers/usb/serial/cypress_m8.c +++ b/drivers/usb/serial/cypress_m8.c @@ -115,7 +115,7 @@ struct cypress_private { static int cypress_earthmate_port_probe(struct usb_serial_port *port); static int cypress_hidcom_port_probe(struct usb_serial_port *port); static int cypress_ca42v2_port_probe(struct usb_serial_port *port); -static int cypress_port_remove(struct usb_serial_port *port); +static void cypress_port_remove(struct usb_serial_port *port); static int cypress_open(struct tty_struct *tty, struct usb_serial_port *port); static void cypress_close(struct usb_serial_port *port); static void cypress_dtr_rts(struct usb_serial_port *port, int on); @@ -564,7 +564,7 @@ static int cypress_ca42v2_port_probe(struct usb_serial_port *port) return 0; } -static int cypress_port_remove(struct usb_serial_port *port) +static void cypress_port_remove(struct usb_serial_port *port) { struct cypress_private *priv; @@ -572,8 +572,6 @@ static int cypress_port_remove(struct usb_serial_port *port) kfifo_free(&priv->write_fifo); kfree(priv); - - return 0; } static int cypress_open(struct tty_struct *tty, struct usb_serial_port *port) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 0ecd5316d85f..8b2f06539f2c 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -233,7 +233,7 @@ static int digi_startup(struct usb_serial *serial); static void digi_disconnect(struct usb_serial *serial); static void digi_release(struct usb_serial *serial); static int digi_port_probe(struct usb_serial_port *port); -static int digi_port_remove(struct usb_serial_port *port); +static void digi_port_remove(struct usb_serial_port *port); static void digi_read_bulk_callback(struct urb *urb); static int digi_read_inb_callback(struct urb *urb); static int digi_read_oob_callback(struct urb *urb); @@ -1281,14 +1281,12 @@ static int digi_port_probe(struct usb_serial_port *port) return digi_port_init(port, port->port_number); } -static int digi_port_remove(struct usb_serial_port *port) +static void digi_port_remove(struct usb_serial_port *port) { struct digi_port *priv; priv = usb_get_serial_port_data(port); kfree(priv); - - return 0; } static void digi_read_bulk_callback(struct urb *urb) diff --git a/drivers/usb/serial/f81534.c b/drivers/usb/serial/f81534.c index dd7e55e822ef..a763b362f081 100644 --- a/drivers/usb/serial/f81534.c +++ b/drivers/usb/serial/f81534.c @@ -1430,12 +1430,11 @@ static int f81534_port_probe(struct usb_serial_port *port) return f81534_set_port_output_pin(port); } -static int f81534_port_remove(struct usb_serial_port *port) +static void f81534_port_remove(struct usb_serial_port *port) { struct f81534_port_private *port_priv = usb_get_serial_port_data(port); flush_work(&port_priv->lsr_work); - return 0; } static int f81534_tiocmget(struct tty_struct *tty) diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index d61703d858a1..c867592477c9 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -1069,7 +1069,7 @@ static const char *ftdi_chip_name[] = { static int ftdi_sio_probe(struct usb_serial *serial, const struct usb_device_id *id); static int ftdi_sio_port_probe(struct usb_serial_port *port); -static int ftdi_sio_port_remove(struct usb_serial_port *port); +static void ftdi_sio_port_remove(struct usb_serial_port *port); static int ftdi_open(struct tty_struct *tty, struct usb_serial_port *port); static void ftdi_dtr_rts(struct usb_serial_port *port, int on); static void ftdi_process_read_urb(struct urb *urb); @@ -2400,7 +2400,7 @@ static int ftdi_stmclite_probe(struct usb_serial *serial) return 0; } -static int ftdi_sio_port_remove(struct usb_serial_port *port) +static void ftdi_sio_port_remove(struct usb_serial_port *port) { struct ftdi_private *priv = usb_get_serial_port_data(port); @@ -2409,8 +2409,6 @@ static int ftdi_sio_port_remove(struct usb_serial_port *port) remove_sysfs_attrs(port); kfree(priv); - - return 0; } static int ftdi_open(struct tty_struct *tty, struct usb_serial_port *port) diff --git a/drivers/usb/serial/garmin_gps.c b/drivers/usb/serial/garmin_gps.c index c02c19bb1183..50e8bdc77e71 100644 --- a/drivers/usb/serial/garmin_gps.c +++ b/drivers/usb/serial/garmin_gps.c @@ -1401,7 +1401,7 @@ err_free: } -static int garmin_port_remove(struct usb_serial_port *port) +static void garmin_port_remove(struct usb_serial_port *port) { struct garmin_data *garmin_data_p = usb_get_serial_port_data(port); @@ -1409,7 +1409,6 @@ static int garmin_port_remove(struct usb_serial_port *port) usb_kill_urb(port->interrupt_in_urb); del_timer_sync(&garmin_data_p->timer); kfree(garmin_data_p); - return 0; } diff --git a/drivers/usb/serial/io_edgeport.c b/drivers/usb/serial/io_edgeport.c index ba5d8df69518..a493670c06e6 100644 --- a/drivers/usb/serial/io_edgeport.c +++ b/drivers/usb/serial/io_edgeport.c @@ -293,7 +293,7 @@ static int edge_startup(struct usb_serial *serial); static void edge_disconnect(struct usb_serial *serial); static void edge_release(struct usb_serial *serial); static int edge_port_probe(struct usb_serial_port *port); -static int edge_port_remove(struct usb_serial_port *port); +static void edge_port_remove(struct usb_serial_port *port); /* function prototypes for all of our local functions */ @@ -3078,14 +3078,12 @@ static int edge_port_probe(struct usb_serial_port *port) return 0; } -static int edge_port_remove(struct usb_serial_port *port) +static void edge_port_remove(struct usb_serial_port *port) { struct edgeport_port *edge_port; edge_port = usb_get_serial_port_data(port); kfree(edge_port); - - return 0; } static struct usb_serial_driver edgeport_2port_device = { diff --git a/drivers/usb/serial/io_ti.c b/drivers/usb/serial/io_ti.c index 0bbfa47e04b7..e800547be9e0 100644 --- a/drivers/usb/serial/io_ti.c +++ b/drivers/usb/serial/io_ti.c @@ -2625,15 +2625,13 @@ err: return ret; } -static int edge_port_remove(struct usb_serial_port *port) +static void edge_port_remove(struct usb_serial_port *port) { struct edgeport_port *edge_port; edge_port = usb_get_serial_port_data(port); edge_remove_sysfs_attrs(port); kfree(edge_port); - - return 0; } /* Sysfs Attributes */ diff --git a/drivers/usb/serial/iuu_phoenix.c b/drivers/usb/serial/iuu_phoenix.c index e8f06b41a503..093afd67a664 100644 --- a/drivers/usb/serial/iuu_phoenix.c +++ b/drivers/usb/serial/iuu_phoenix.c @@ -100,7 +100,7 @@ static int iuu_port_probe(struct usb_serial_port *port) return 0; } -static int iuu_port_remove(struct usb_serial_port *port) +static void iuu_port_remove(struct usb_serial_port *port) { struct iuu_private *priv = usb_get_serial_port_data(port); @@ -108,8 +108,6 @@ static int iuu_port_remove(struct usb_serial_port *port) kfree(priv->writebuf); kfree(priv->buf); kfree(priv); - - return 0; } static int iuu_tiocmset(struct tty_struct *tty, diff --git a/drivers/usb/serial/keyspan.c b/drivers/usb/serial/keyspan.c index aa3dbce22cfb..622077dcc344 100644 --- a/drivers/usb/serial/keyspan.c +++ b/drivers/usb/serial/keyspan.c @@ -49,7 +49,7 @@ static int keyspan_startup(struct usb_serial *serial); static void keyspan_disconnect(struct usb_serial *serial); static void keyspan_release(struct usb_serial *serial); static int keyspan_port_probe(struct usb_serial_port *port); -static int keyspan_port_remove(struct usb_serial_port *port); +static void keyspan_port_remove(struct usb_serial_port *port); static int keyspan_write_room(struct tty_struct *tty); static int keyspan_write(struct tty_struct *tty, struct usb_serial_port *port, const unsigned char *buf, int count); @@ -2985,7 +2985,7 @@ err_in_buffer: return -ENOMEM; } -static int keyspan_port_remove(struct usb_serial_port *port) +static void keyspan_port_remove(struct usb_serial_port *port) { struct keyspan_port_private *p_priv; int i; @@ -3014,8 +3014,6 @@ static int keyspan_port_remove(struct usb_serial_port *port) kfree(p_priv->in_buffer[i]); kfree(p_priv); - - return 0; } /* Structs for the devices, pre and post renumeration. */ diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index e6f933e8d25f..39b0f5f344c2 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -672,14 +672,12 @@ static int keyspan_pda_port_probe(struct usb_serial_port *port) return 0; } -static int keyspan_pda_port_remove(struct usb_serial_port *port) +static void keyspan_pda_port_remove(struct usb_serial_port *port) { struct keyspan_pda_private *priv; priv = usb_get_serial_port_data(port); kfree(priv); - - return 0; } static struct usb_serial_driver keyspan_pda_fake_device = { diff --git a/drivers/usb/serial/kl5kusb105.c b/drivers/usb/serial/kl5kusb105.c index 5f6b82ebccc5..f1e9628a9907 100644 --- a/drivers/usb/serial/kl5kusb105.c +++ b/drivers/usb/serial/kl5kusb105.c @@ -52,7 +52,7 @@ * Function prototypes */ static int klsi_105_port_probe(struct usb_serial_port *port); -static int klsi_105_port_remove(struct usb_serial_port *port); +static void klsi_105_port_remove(struct usb_serial_port *port); static int klsi_105_open(struct tty_struct *tty, struct usb_serial_port *port); static void klsi_105_close(struct usb_serial_port *port); static void klsi_105_set_termios(struct tty_struct *tty, @@ -231,14 +231,12 @@ static int klsi_105_port_probe(struct usb_serial_port *port) return 0; } -static int klsi_105_port_remove(struct usb_serial_port *port) +static void klsi_105_port_remove(struct usb_serial_port *port) { struct klsi_105_private *priv; priv = usb_get_serial_port_data(port); kfree(priv); - - return 0; } static int klsi_105_open(struct tty_struct *tty, struct usb_serial_port *port) diff --git a/drivers/usb/serial/kobil_sct.c b/drivers/usb/serial/kobil_sct.c index 49aacb0a327c..a9bc546626ab 100644 --- a/drivers/usb/serial/kobil_sct.c +++ b/drivers/usb/serial/kobil_sct.c @@ -48,7 +48,7 @@ /* Function prototypes */ static int kobil_port_probe(struct usb_serial_port *probe); -static int kobil_port_remove(struct usb_serial_port *probe); +static void kobil_port_remove(struct usb_serial_port *probe); static int kobil_open(struct tty_struct *tty, struct usb_serial_port *port); static void kobil_close(struct usb_serial_port *port); static int kobil_write(struct tty_struct *tty, struct usb_serial_port *port, @@ -143,14 +143,12 @@ static int kobil_port_probe(struct usb_serial_port *port) } -static int kobil_port_remove(struct usb_serial_port *port) +static void kobil_port_remove(struct usb_serial_port *port) { struct kobil_private *priv; priv = usb_get_serial_port_data(port); kfree(priv); - - return 0; } static void kobil_init_termios(struct tty_struct *tty) diff --git a/drivers/usb/serial/mct_u232.c b/drivers/usb/serial/mct_u232.c index 7887c312d9a9..ecd5b921e374 100644 --- a/drivers/usb/serial/mct_u232.c +++ b/drivers/usb/serial/mct_u232.c @@ -39,7 +39,7 @@ * Function prototypes */ static int mct_u232_port_probe(struct usb_serial_port *port); -static int mct_u232_port_remove(struct usb_serial_port *remove); +static void mct_u232_port_remove(struct usb_serial_port *remove); static int mct_u232_open(struct tty_struct *tty, struct usb_serial_port *port); static void mct_u232_close(struct usb_serial_port *port); static void mct_u232_dtr_rts(struct usb_serial_port *port, int on); @@ -400,14 +400,12 @@ static int mct_u232_port_probe(struct usb_serial_port *port) return 0; } -static int mct_u232_port_remove(struct usb_serial_port *port) +static void mct_u232_port_remove(struct usb_serial_port *port) { struct mct_u232_private *priv; priv = usb_get_serial_port_data(port); kfree(priv); - - return 0; } static int mct_u232_open(struct tty_struct *tty, struct usb_serial_port *port) diff --git a/drivers/usb/serial/metro-usb.c b/drivers/usb/serial/metro-usb.c index e63cea02cfd8..0bfe4459c37f 100644 --- a/drivers/usb/serial/metro-usb.c +++ b/drivers/usb/serial/metro-usb.c @@ -256,14 +256,12 @@ static int metrousb_port_probe(struct usb_serial_port *port) return 0; } -static int metrousb_port_remove(struct usb_serial_port *port) +static void metrousb_port_remove(struct usb_serial_port *port) { struct metrousb_private *metro_priv; metro_priv = usb_get_serial_port_data(port); kfree(metro_priv); - - return 0; } static void metrousb_throttle(struct tty_struct *tty) diff --git a/drivers/usb/serial/mos7720.c b/drivers/usb/serial/mos7720.c index aa55169796a3..701dfb32b129 100644 --- a/drivers/usb/serial/mos7720.c +++ b/drivers/usb/serial/mos7720.c @@ -1760,14 +1760,12 @@ static int mos7720_port_probe(struct usb_serial_port *port) return 0; } -static int mos7720_port_remove(struct usb_serial_port *port) +static void mos7720_port_remove(struct usb_serial_port *port) { struct moschip_port *mos7720_port; mos7720_port = usb_get_serial_port_data(port); kfree(mos7720_port); - - return 0; } static struct usb_serial_driver moschip7720_2port_driver = { diff --git a/drivers/usb/serial/mos7840.c b/drivers/usb/serial/mos7840.c index 30c25ef0dacd..1bf0d066f55a 100644 --- a/drivers/usb/serial/mos7840.c +++ b/drivers/usb/serial/mos7840.c @@ -1745,7 +1745,7 @@ error: return status; } -static int mos7840_port_remove(struct usb_serial_port *port) +static void mos7840_port_remove(struct usb_serial_port *port) { struct moschip_port *mos7840_port = usb_get_serial_port_data(port); @@ -1762,8 +1762,6 @@ static int mos7840_port_remove(struct usb_serial_port *port) } kfree(mos7840_port); - - return 0; } static struct usb_serial_driver moschip7840_4port_device = { diff --git a/drivers/usb/serial/omninet.c b/drivers/usb/serial/omninet.c index 5b6e982a9376..83c62f920c50 100644 --- a/drivers/usb/serial/omninet.c +++ b/drivers/usb/serial/omninet.c @@ -36,7 +36,7 @@ static int omninet_prepare_write_buffer(struct usb_serial_port *port, static int omninet_calc_num_ports(struct usb_serial *serial, struct usb_serial_endpoints *epds); static int omninet_port_probe(struct usb_serial_port *port); -static int omninet_port_remove(struct usb_serial_port *port); +static void omninet_port_remove(struct usb_serial_port *port); static const struct usb_device_id id_table[] = { { USB_DEVICE(ZYXEL_VENDOR_ID, ZYXEL_OMNINET_ID) }, @@ -121,14 +121,12 @@ static int omninet_port_probe(struct usb_serial_port *port) return 0; } -static int omninet_port_remove(struct usb_serial_port *port) +static void omninet_port_remove(struct usb_serial_port *port) { struct omninet_data *od; od = usb_get_serial_port_data(port); kfree(od); - - return 0; } #define OMNINET_HEADERLEN 4 diff --git a/drivers/usb/serial/opticon.c b/drivers/usb/serial/opticon.c index 0af76800bd78..eecb72aef83e 100644 --- a/drivers/usb/serial/opticon.c +++ b/drivers/usb/serial/opticon.c @@ -385,13 +385,11 @@ static int opticon_port_probe(struct usb_serial_port *port) return 0; } -static int opticon_port_remove(struct usb_serial_port *port) +static void opticon_port_remove(struct usb_serial_port *port) { struct opticon_private *priv = usb_get_serial_port_data(port); kfree(priv); - - return 0; } static struct usb_serial_driver opticon_device = { diff --git a/drivers/usb/serial/oti6858.c b/drivers/usb/serial/oti6858.c index 8151dd7a45e8..65cd0341fa78 100644 --- a/drivers/usb/serial/oti6858.c +++ b/drivers/usb/serial/oti6858.c @@ -132,7 +132,7 @@ static int oti6858_tiocmget(struct tty_struct *tty); static int oti6858_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear); static int oti6858_port_probe(struct usb_serial_port *port); -static int oti6858_port_remove(struct usb_serial_port *port); +static void oti6858_port_remove(struct usb_serial_port *port); /* device info */ static struct usb_serial_driver oti6858_device = { @@ -344,14 +344,12 @@ static int oti6858_port_probe(struct usb_serial_port *port) return 0; } -static int oti6858_port_remove(struct usb_serial_port *port) +static void oti6858_port_remove(struct usb_serial_port *port) { struct oti6858_private *priv; priv = usb_get_serial_port_data(port); kfree(priv); - - return 0; } static int oti6858_write(struct tty_struct *tty, struct usb_serial_port *port, diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c index 29dda60e3bcd..eed9acd1ae08 100644 --- a/drivers/usb/serial/pl2303.c +++ b/drivers/usb/serial/pl2303.c @@ -450,13 +450,11 @@ static int pl2303_port_probe(struct usb_serial_port *port) return 0; } -static int pl2303_port_remove(struct usb_serial_port *port) +static void pl2303_port_remove(struct usb_serial_port *port) { struct pl2303_private *priv = usb_get_serial_port_data(port); kfree(priv); - - return 0; } static int pl2303_set_control_lines(struct usb_serial_port *port, u8 value) diff --git a/drivers/usb/serial/quatech2.c b/drivers/usb/serial/quatech2.c index 872d1bc86ab4..599dcb2e374d 100644 --- a/drivers/usb/serial/quatech2.c +++ b/drivers/usb/serial/quatech2.c @@ -727,7 +727,7 @@ err_buf: return -ENOMEM; } -static int qt2_port_remove(struct usb_serial_port *port) +static void qt2_port_remove(struct usb_serial_port *port) { struct qt2_port_private *port_priv; @@ -735,8 +735,6 @@ static int qt2_port_remove(struct usb_serial_port *port) usb_free_urb(port_priv->write_urb); kfree(port_priv->write_buffer); kfree(port_priv); - - return 0; } static int qt2_tiocmget(struct tty_struct *tty) diff --git a/drivers/usb/serial/sierra.c b/drivers/usb/serial/sierra.c index 57fc3c31712e..54e16ffc30a0 100644 --- a/drivers/usb/serial/sierra.c +++ b/drivers/usb/serial/sierra.c @@ -901,15 +901,13 @@ static int sierra_port_probe(struct usb_serial_port *port) return 0; } -static int sierra_port_remove(struct usb_serial_port *port) +static void sierra_port_remove(struct usb_serial_port *port) { struct sierra_port_private *portdata; portdata = usb_get_serial_port_data(port); usb_set_serial_port_data(port, NULL); kfree(portdata); - - return 0; } #ifdef CONFIG_PM diff --git a/drivers/usb/serial/spcp8x5.c b/drivers/usb/serial/spcp8x5.c index 3bac55bd9bd9..7039dc918827 100644 --- a/drivers/usb/serial/spcp8x5.c +++ b/drivers/usb/serial/spcp8x5.c @@ -169,14 +169,12 @@ static int spcp8x5_port_probe(struct usb_serial_port *port) return 0; } -static int spcp8x5_port_remove(struct usb_serial_port *port) +static void spcp8x5_port_remove(struct usb_serial_port *port) { struct spcp8x5_private *priv; priv = usb_get_serial_port_data(port); kfree(priv); - - return 0; } static int spcp8x5_set_ctrl_line(struct usb_serial_port *port, u8 mcr) diff --git a/drivers/usb/serial/ssu100.c b/drivers/usb/serial/ssu100.c index 7d39d35e52a1..89fdc5c19285 100644 --- a/drivers/usb/serial/ssu100.c +++ b/drivers/usb/serial/ssu100.c @@ -366,14 +366,12 @@ static int ssu100_port_probe(struct usb_serial_port *port) return 0; } -static int ssu100_port_remove(struct usb_serial_port *port) +static void ssu100_port_remove(struct usb_serial_port *port) { struct ssu100_port_private *priv; priv = usb_get_serial_port_data(port); kfree(priv); - - return 0; } static int ssu100_tiocmget(struct tty_struct *tty) diff --git a/drivers/usb/serial/symbolserial.c b/drivers/usb/serial/symbolserial.c index 6ca24e86f686..d7f73ad6e778 100644 --- a/drivers/usb/serial/symbolserial.c +++ b/drivers/usb/serial/symbolserial.c @@ -160,13 +160,11 @@ static int symbol_port_probe(struct usb_serial_port *port) return 0; } -static int symbol_port_remove(struct usb_serial_port *port) +static void symbol_port_remove(struct usb_serial_port *port) { struct symbol_private *priv = usb_get_serial_port_data(port); kfree(priv); - - return 0; } static struct usb_serial_driver symbol_device = { diff --git a/drivers/usb/serial/ti_usb_3410_5052.c b/drivers/usb/serial/ti_usb_3410_5052.c index 73075b9351c5..7252b0ce75a6 100644 --- a/drivers/usb/serial/ti_usb_3410_5052.c +++ b/drivers/usb/serial/ti_usb_3410_5052.c @@ -303,7 +303,7 @@ struct ti_device { static int ti_startup(struct usb_serial *serial); static void ti_release(struct usb_serial *serial); static int ti_port_probe(struct usb_serial_port *port); -static int ti_port_remove(struct usb_serial_port *port); +static void ti_port_remove(struct usb_serial_port *port); static int ti_open(struct tty_struct *tty, struct usb_serial_port *port); static void ti_close(struct usb_serial_port *port); static int ti_write(struct tty_struct *tty, struct usb_serial_port *port, @@ -629,14 +629,12 @@ static int ti_port_probe(struct usb_serial_port *port) return 0; } -static int ti_port_remove(struct usb_serial_port *port) +static void ti_port_remove(struct usb_serial_port *port) { struct ti_port *tport; tport = usb_get_serial_port_data(port); kfree(tport); - - return 0; } static int ti_open(struct tty_struct *tty, struct usb_serial_port *port) diff --git a/drivers/usb/serial/upd78f0730.c b/drivers/usb/serial/upd78f0730.c index 1ca9c1881621..26d7b003b7e3 100644 --- a/drivers/usb/serial/upd78f0730.c +++ b/drivers/usb/serial/upd78f0730.c @@ -171,15 +171,13 @@ static int upd78f0730_port_probe(struct usb_serial_port *port) return 0; } -static int upd78f0730_port_remove(struct usb_serial_port *port) +static void upd78f0730_port_remove(struct usb_serial_port *port) { struct upd78f0730_port_private *private; private = usb_get_serial_port_data(port); mutex_destroy(&private->lock); kfree(private); - - return 0; } static int upd78f0730_tiocmget(struct tty_struct *tty) diff --git a/drivers/usb/serial/usb-wwan.h b/drivers/usb/serial/usb-wwan.h index 934e9361cf6b..79dafd98e0a1 100644 --- a/drivers/usb/serial/usb-wwan.h +++ b/drivers/usb/serial/usb-wwan.h @@ -10,7 +10,7 @@ extern void usb_wwan_dtr_rts(struct usb_serial_port *port, int on); extern int usb_wwan_open(struct tty_struct *tty, struct usb_serial_port *port); extern void usb_wwan_close(struct usb_serial_port *port); extern int usb_wwan_port_probe(struct usb_serial_port *port); -extern int usb_wwan_port_remove(struct usb_serial_port *port); +extern void usb_wwan_port_remove(struct usb_serial_port *port); extern int usb_wwan_write_room(struct tty_struct *tty); extern int usb_wwan_tiocmget(struct tty_struct *tty); extern int usb_wwan_tiocmset(struct tty_struct *tty, diff --git a/drivers/usb/serial/usb_wwan.c b/drivers/usb/serial/usb_wwan.c index 4b9845807bee..46d46a4f99c9 100644 --- a/drivers/usb/serial/usb_wwan.c +++ b/drivers/usb/serial/usb_wwan.c @@ -544,7 +544,7 @@ bail_out_error: } EXPORT_SYMBOL_GPL(usb_wwan_port_probe); -int usb_wwan_port_remove(struct usb_serial_port *port) +void usb_wwan_port_remove(struct usb_serial_port *port) { int i; struct usb_wwan_port_private *portdata; @@ -562,8 +562,6 @@ int usb_wwan_port_remove(struct usb_serial_port *port) } kfree(portdata); - - return 0; } EXPORT_SYMBOL(usb_wwan_port_remove); diff --git a/drivers/usb/serial/whiteheat.c b/drivers/usb/serial/whiteheat.c index ca3bd58f2025..ccfd5ed652cd 100644 --- a/drivers/usb/serial/whiteheat.c +++ b/drivers/usb/serial/whiteheat.c @@ -79,7 +79,7 @@ static int whiteheat_firmware_attach(struct usb_serial *serial); static int whiteheat_attach(struct usb_serial *serial); static void whiteheat_release(struct usb_serial *serial); static int whiteheat_port_probe(struct usb_serial_port *port); -static int whiteheat_port_remove(struct usb_serial_port *port); +static void whiteheat_port_remove(struct usb_serial_port *port); static int whiteheat_open(struct tty_struct *tty, struct usb_serial_port *port); static void whiteheat_close(struct usb_serial_port *port); @@ -345,14 +345,12 @@ static int whiteheat_port_probe(struct usb_serial_port *port) return 0; } -static int whiteheat_port_remove(struct usb_serial_port *port) +static void whiteheat_port_remove(struct usb_serial_port *port) { struct whiteheat_private *info; info = usb_get_serial_port_data(port); kfree(info); - - return 0; } static int whiteheat_open(struct tty_struct *tty, struct usb_serial_port *port) diff --git a/include/linux/usb/serial.h b/include/linux/usb/serial.h index 1c09b922f8b0..952272002e48 100644 --- a/include/linux/usb/serial.h +++ b/include/linux/usb/serial.h @@ -260,7 +260,7 @@ struct usb_serial_driver { void (*release)(struct usb_serial *serial); int (*port_probe)(struct usb_serial_port *port); - int (*port_remove)(struct usb_serial_port *port); + void (*port_remove)(struct usb_serial_port *port); int (*suspend)(struct usb_serial *serial, pm_message_t message); int (*resume)(struct usb_serial *serial); -- cgit v1.2.3 From 1542d1324be1191d970f69c55e885af5dd810b84 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 8 Feb 2021 16:48:06 +0100 Subject: USB: serial: drop bogus to_usb_serial_port() checks The to_usb_serial_port() macro is implemented using container_of() so there's no need to check for NULL. Note that neither bus match() or probe() is ever called with a NULL struct device pointer so the checks weren't just misplaced. Reviewed-by: Greg Kroah-Hartman Reviewed-by: Uwe Kleine-König Signed-off-by: Johan Hovold --- drivers/usb/serial/bus.c | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/drivers/usb/serial/bus.c b/drivers/usb/serial/bus.c index 1a8c7821d00a..7133818a58b9 100644 --- a/drivers/usb/serial/bus.c +++ b/drivers/usb/serial/bus.c @@ -16,19 +16,13 @@ static int usb_serial_device_match(struct device *dev, struct device_driver *drv) { - struct usb_serial_driver *driver; - const struct usb_serial_port *port; + const struct usb_serial_port *port = to_usb_serial_port(dev); + struct usb_serial_driver *driver = to_usb_serial_driver(drv); /* * drivers are already assigned to ports in serial_probe so it's * a simple check here. */ - port = to_usb_serial_port(dev); - if (!port) - return 0; - - driver = to_usb_serial_driver(drv); - if (driver == port->serial->type) return 1; @@ -37,16 +31,12 @@ static int usb_serial_device_match(struct device *dev, static int usb_serial_device_probe(struct device *dev) { + struct usb_serial_port *port = to_usb_serial_port(dev); struct usb_serial_driver *driver; - struct usb_serial_port *port; struct device *tty_dev; int retval = 0; int minor; - port = to_usb_serial_port(dev); - if (!port) - return -ENODEV; - /* make sure suspend/resume doesn't race against port_probe */ retval = usb_autopm_get_interface(port->serial->interface); if (retval) -- cgit v1.2.3 From 43861d29c0810a70792bf69d37482efb7bb6677d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 10 Feb 2021 12:17:46 +0100 Subject: USB: quirks: sort quirk entries Move the last entry to its proper place to maintain the VID/PID sort order. Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold Link: https://lore.kernel.org/r/20210210111746.13360-1-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/quirks.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index 1b4eb7046b07..66a0dc618dfc 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -415,6 +415,9 @@ static const struct usb_device_id usb_quirk_list[] = { { USB_DEVICE(0x10d6, 0x2200), .driver_info = USB_QUIRK_STRING_FETCH_255 }, + /* novation SoundControl XL */ + { USB_DEVICE(0x1235, 0x0061), .driver_info = USB_QUIRK_RESET_RESUME }, + /* Huawei 4G LTE module */ { USB_DEVICE(0x12d1, 0x15bb), .driver_info = USB_QUIRK_DISCONNECT_SUSPEND }, @@ -495,9 +498,6 @@ static const struct usb_device_id usb_quirk_list[] = { /* INTEL VALUE SSD */ { USB_DEVICE(0x8086, 0xf1a5), .driver_info = USB_QUIRK_RESET_RESUME }, - /* novation SoundControl XL */ - { USB_DEVICE(0x1235, 0x0061), .driver_info = USB_QUIRK_RESET_RESUME }, - { } /* terminating entry must be last */ }; -- cgit v1.2.3 From 1ebe718bb48278105816ba03a0408ecc2d6cf47f Mon Sep 17 00:00:00 2001 From: Stefan Ursella Date: Wed, 10 Feb 2021 15:07:11 +0100 Subject: usb: quirks: add quirk to start video capture on ELMO L-12F document camera reliable Without this quirk starting a video capture from the device often fails with kernel: uvcvideo: Failed to set UVC probe control : -110 (exp. 34). Signed-off-by: Stefan Ursella Link: https://lore.kernel.org/r/20210210140713.18711-1-stefan.ursella@wolfvision.net Cc: stable 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 66a0dc618dfc..6ade3daf7858 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -391,6 +391,9 @@ static const struct usb_device_id usb_quirk_list[] = { /* X-Rite/Gretag-Macbeth Eye-One Pro display colorimeter */ { USB_DEVICE(0x0971, 0x2000), .driver_info = USB_QUIRK_NO_SET_INTF }, + /* ELMO L-12F document camera */ + { USB_DEVICE(0x09a1, 0x0028), .driver_info = USB_QUIRK_DELAY_CTRL_MSG }, + /* Broadcom BCM92035DGROM BT dongle */ { USB_DEVICE(0x0a5c, 0x2021), .driver_info = USB_QUIRK_RESET_RESUME }, -- cgit v1.2.3 From 2f8e928408885dad5d8d6afefacb82100b6b62c7 Mon Sep 17 00:00:00 2001 From: Johan Jonker Date: Tue, 9 Feb 2021 20:23:43 +0100 Subject: dt-bindings: usb: convert rockchip,dwc3.txt to yaml In the past Rockchip dwc3 usb nodes were manually checked. With the conversion of snps,dwc3.yaml as common document we now can convert rockchip,dwc3.txt to yaml as well. Remove node wrapper. Added properties for rk3399 are: power-domains resets reset-names Signed-off-by: Johan Jonker Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20210209192350.7130-1-jbx6244@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/rockchip,dwc3.txt | 56 ----------- .../devicetree/bindings/usb/rockchip,dwc3.yaml | 104 +++++++++++++++++++++ 2 files changed, 104 insertions(+), 56 deletions(-) delete mode 100644 Documentation/devicetree/bindings/usb/rockchip,dwc3.txt create mode 100644 Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml diff --git a/Documentation/devicetree/bindings/usb/rockchip,dwc3.txt b/Documentation/devicetree/bindings/usb/rockchip,dwc3.txt deleted file mode 100644 index 94520493233b..000000000000 --- a/Documentation/devicetree/bindings/usb/rockchip,dwc3.txt +++ /dev/null @@ -1,56 +0,0 @@ -Rockchip SuperSpeed DWC3 USB SoC controller - -Required properties: -- compatible: should contain "rockchip,rk3399-dwc3" for rk3399 SoC -- clocks: A list of phandle + clock-specifier pairs for the - clocks listed in clock-names -- clock-names: Should contain the following: - "ref_clk" Controller reference clk, have to be 24 MHz - "suspend_clk" Controller suspend clk, have to be 24 MHz or 32 KHz - "bus_clk" Master/Core clock, have to be >= 62.5 MHz for SS - operation and >= 30MHz for HS operation - "grf_clk" Controller grf clk - -Required child node: -A child node must exist to represent the core DWC3 IP block. The name of -the node is not important. The content of the node is defined in dwc3.txt. - -Phy documentation is provided in the following places: -Documentation/devicetree/bindings/phy/phy-rockchip-inno-usb2.yaml - USB2.0 PHY -Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt - Type-C PHY - -Example device nodes: - - usbdrd3_0: usb@fe800000 { - compatible = "rockchip,rk3399-dwc3"; - clocks = <&cru SCLK_USB3OTG0_REF>, <&cru SCLK_USB3OTG0_SUSPEND>, - <&cru ACLK_USB3OTG0>, <&cru ACLK_USB3_GRF>; - clock-names = "ref_clk", "suspend_clk", - "bus_clk", "grf_clk"; - #address-cells = <2>; - #size-cells = <2>; - ranges; - usbdrd_dwc3_0: dwc3@fe800000 { - compatible = "snps,dwc3"; - reg = <0x0 0xfe800000 0x0 0x100000>; - interrupts = ; - dr_mode = "otg"; - }; - }; - - usbdrd3_1: usb@fe900000 { - compatible = "rockchip,rk3399-dwc3"; - clocks = <&cru SCLK_USB3OTG1_REF>, <&cru SCLK_USB3OTG1_SUSPEND>, - <&cru ACLK_USB3OTG1>, <&cru ACLK_USB3_GRF>; - clock-names = "ref_clk", "suspend_clk", - "bus_clk", "grf_clk"; - #address-cells = <2>; - #size-cells = <2>; - ranges; - usbdrd_dwc3_1: dwc3@fe900000 { - compatible = "snps,dwc3"; - reg = <0x0 0xfe900000 0x0 0x100000>; - interrupts = ; - dr_mode = "otg"; - }; - }; diff --git a/Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml b/Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml new file mode 100644 index 000000000000..9908270a907e --- /dev/null +++ b/Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml @@ -0,0 +1,104 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/rockchip,dwc3.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Rockchip SuperSpeed DWC3 USB SoC controller + +maintainers: + - Heiko Stuebner + +description: + The common content of the node is defined in snps,dwc3.yaml. + + Phy documentation is provided in the following places. + + USB2.0 PHY + Documentation/devicetree/bindings/phy/phy-rockchip-inno-usb2.yaml + + Type-C PHY + Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt + +allOf: + - $ref: snps,dwc3.yaml# + +select: + properties: + compatible: + contains: + enum: + - rockchip,rk3399-dwc3 + required: + - compatible + +properties: + compatible: + items: + - enum: + - rockchip,rk3399-dwc3 + - const: snps,dwc3 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + items: + - description: + Controller reference clock, must to be 24 MHz + - description: + Controller suspend clock, must to be 24 MHz or 32 KHz + - description: + Master/Core clock, must to be >= 62.5 MHz for SS + operation and >= 30MHz for HS operation + - description: + Controller grf clock + + clock-names: + items: + - const: ref_clk + - const: suspend_clk + - const: bus_clk + - const: grf_clk + + power-domains: + maxItems: 1 + + resets: + maxItems: 1 + + reset-names: + const: usb3-otg + +unevaluatedProperties: false + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + +examples: + - | + #include + #include + + bus { + #address-cells = <2>; + #size-cells = <2>; + + usbdrd3_0: usb@fe800000 { + compatible = "rockchip,rk3399-dwc3", "snps,dwc3"; + reg = <0x0 0xfe800000 0x0 0x100000>; + interrupts = ; + clocks = <&cru SCLK_USB3OTG0_REF>, <&cru SCLK_USB3OTG0_SUSPEND>, + <&cru ACLK_USB3OTG0>, <&cru ACLK_USB3_GRF>; + clock-names = "ref_clk", "suspend_clk", + "bus_clk", "grf_clk"; + dr_mode = "otg"; + }; + }; -- cgit v1.2.3 From 08f4a6b903369ee0147b557931b7075c17e015f6 Mon Sep 17 00:00:00 2001 From: Johan Jonker Date: Tue, 9 Feb 2021 20:23:48 +0100 Subject: dt-bindings: usb: dwc3: add description for rk3328 Add description for "rockchip,rk3328-dwc3". Signed-off-by: Johan Jonker Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20210209192350.7130-6-jbx6244@gmail.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml b/Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml index 9908270a907e..04077f2d7faf 100644 --- a/Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml @@ -28,6 +28,7 @@ select: compatible: contains: enum: + - rockchip,rk3328-dwc3 - rockchip,rk3399-dwc3 required: - compatible @@ -36,6 +37,7 @@ properties: compatible: items: - enum: + - rockchip,rk3328-dwc3 - rockchip,rk3399-dwc3 - const: snps,dwc3 @@ -46,6 +48,7 @@ properties: maxItems: 1 clocks: + minItems: 3 items: - description: Controller reference clock, must to be 24 MHz @@ -58,6 +61,7 @@ properties: Controller grf clock clock-names: + minItems: 3 items: - const: ref_clk - const: suspend_clk -- cgit v1.2.3 From b9dd1962d9bf6c5247ba97a8b9e1689a10e22be1 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Wed, 10 Feb 2021 15:53:30 -0800 Subject: usb: Replace lkml.org links with lore As started by commit 05a5f51ca566 ("Documentation: Replace lkml.org links with lore"), replace lkml.org links with lore to better use a single source that's more likely to stay available long-term. Acked-by: Johan Hovold Signed-off-by: Kees Cook Link: https://lore.kernel.org/r/20210210235330.3292719-1-keescook@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/ark3116.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/ark3116.c b/drivers/usb/serial/ark3116.c index f0ac7bb07ac1..b9bedfe9bd09 100644 --- a/drivers/usb/serial/ark3116.c +++ b/drivers/usb/serial/ark3116.c @@ -717,9 +717,10 @@ MODULE_DESCRIPTION(DRIVER_DESC); * hardware bug or something. * * According to a patch provided here - * (http://lkml.org/lkml/2009/7/26/56), the ARK3116 can also be used - * as an IrDA dongle. Since I do not have such a thing, I could not - * investigate that aspect. However, I can speculate ;-). + * https://lore.kernel.org/lkml/200907261419.50702.linux@rainbow-software.org + * the ARK3116 can also be used as an IrDA dongle. Since I do not have + * such a thing, I could not investigate that aspect. However, I can + * speculate ;-). * * - IrDA encodes data differently than RS232. Most likely, one of * the bits in registers 9..E enables the IR ENDEC (encoder/decoder). -- cgit v1.2.3 From cdf71946a9b12b04ffcd89662dd7b91a52ab5186 Mon Sep 17 00:00:00 2001 From: Mans Rullgard Date: Tue, 9 Feb 2021 18:12:40 +0000 Subject: dt-bindings: usb: usb-device: fix typo in required properties Fix typo in 'compatible' property name. Signed-off-by: Mans Rullgard Link: https://lore.kernel.org/r/20210209181240.28017-1-mans@mansr.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usb-device.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/usb-device.yaml b/Documentation/devicetree/bindings/usb/usb-device.yaml index 7bb25a45427d..d4c99809ee9a 100644 --- a/Documentation/devicetree/bindings/usb/usb-device.yaml +++ b/Documentation/devicetree/bindings/usb/usb-device.yaml @@ -76,7 +76,7 @@ patternProperties: maxItems: 1 required: - - compatile + - compatible - reg additionalProperties: true -- cgit v1.2.3 From 7a1e838d0cdce7d09a0bd81d45c7b5a660e71ac7 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Tue, 9 Feb 2021 13:32:19 +0100 Subject: usb: misc: usb3503: Fix logic in usb3503_init() Based on https://lore.kernel.org/linux-arm-kernel/YCJv59g3Tq2haDSa@kroah.com/ initialization should fail if any registration fails. Signed-off-by: Michal Simek Link: https://lore.kernel.org/r/a8547f6fe698014df08cad3bcc9c5d9a7137d8b8.1612873935.git.michal.simek@xilinx.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/usb3503.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/usb/misc/usb3503.c b/drivers/usb/misc/usb3503.c index 48099c6bf04c..330f494cd158 100644 --- a/drivers/usb/misc/usb3503.c +++ b/drivers/usb/misc/usb3503.c @@ -409,13 +409,18 @@ static int __init usb3503_init(void) int err; err = i2c_add_driver(&usb3503_i2c_driver); - if (err != 0) + if (err) { pr_err("usb3503: Failed to register I2C driver: %d\n", err); + return err; + } err = platform_driver_register(&usb3503_platform_driver); - if (err != 0) + if (err) { pr_err("usb3503: Failed to register platform driver: %d\n", err); + i2c_del_driver(&usb3503_i2c_driver); + return err; + } return 0; } -- cgit v1.2.3 From 4b59b60d896f3ed94921974e916db091bc3a9ba8 Mon Sep 17 00:00:00 2001 From: Kyle Tso Date: Fri, 12 Feb 2021 15:37:43 +0800 Subject: Documentation: connector: Update the description of sink-vdos Remove the acronym "VDM" and replace it with the full name "Vendor Defined Message". Reviewed-by: Guenter Roeck Signed-off-by: Kyle Tso Link: https://lore.kernel.org/r/20210212073743.665038-1-kyletso@google.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/connector/usb-connector.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/connector/usb-connector.yaml b/Documentation/devicetree/bindings/connector/usb-connector.yaml index d385026944ec..ce11b2027199 100644 --- a/Documentation/devicetree/bindings/connector/usb-connector.yaml +++ b/Documentation/devicetree/bindings/connector/usb-connector.yaml @@ -138,9 +138,10 @@ properties: $ref: /schemas/types.yaml#/definitions/uint32-array sink-vdos: - description: An array of u32 with each entry (VDM Objects) providing additional information - corresponding to the product, the detailed bit definitions and the order of each VDO can be - found in "USB Power Delivery Specification Revision 3.0, Version 2.0 + ECNs 2020-12-10" + description: An array of u32 with each entry, a Vendor Defined Message Object (VDO), + providing additional information corresponding to the product, the detailed bit + definitions and the order of each VDO can be found in + "USB Power Delivery Specification Revision 3.0, Version 2.0 + ECNs 2020-12-10" chapter 6.4.4.3.1 Discover Identity. User can specify the VDO array via VDO_IDH/_CERT/_PRODUCT/_UFP/_DFP/_PCABLE/_ACABLE(1/2)/_VPD() defined in dt-bindings/usb/pd.h. -- cgit v1.2.3 From 2b9f3ed9378198d98fe6cfc5291281ec34dd6501 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Mon, 1 Feb 2021 15:00:06 +0800 Subject: dt-bindings: usb: mtk-xhci: add optional assigned clock properties Add optional property "assigned-clock" and "assigned-clock-parents" used by mt7629. Reviewed-by: Rob Herring Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20210201070016.41721-6-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml b/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml index 38b1fe18aa79..1f0c5aeba26e 100644 --- a/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml +++ b/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml @@ -67,6 +67,14 @@ properties: - const: dma_ck - const: xhci_ck + assigned-clocks: + minItems: 1 + maxItems: 5 + + assigned-clock-parents: + minItems: 1 + maxItems: 5 + phys: description: List of all PHYs used on this HCD, it's better to keep PHYs in order -- cgit v1.2.3 From fcad8dd5b9955493ca3f5483394cdb46bdd57852 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Mon, 1 Feb 2021 15:00:07 +0800 Subject: dt-bindings: usb: mtk-xhci: add compatible for mt2701 and mt7623 Add two compatible for mt2701 and mt7623; Reviewed-by: Rob Herring Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20210201070016.41721-7-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml b/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml index 1f0c5aeba26e..14f40efb3b22 100644 --- a/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml +++ b/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml @@ -23,8 +23,10 @@ properties: compatible: items: - enum: + - mediatek,mt2701-xhci - mediatek,mt2712-xhci - mediatek,mt7622-xhci + - mediatek,mt7623-xhci - mediatek,mt7629-xhci - mediatek,mt8173-xhci - mediatek,mt8183-xhci -- cgit v1.2.3 From b5a12546e779d4f5586f58e60e0ef5070a833a64 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Mon, 1 Feb 2021 15:00:08 +0800 Subject: dt-bindings: usb: mediatek: musb: add mt8516 compatbile Add support mt8516 compatbile Reviewed-by: Rob Herring Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20210201070016.41721-8-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/mediatek,musb.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/usb/mediatek,musb.yaml b/Documentation/devicetree/bindings/usb/mediatek,musb.yaml index 790efe8b6274..84ddacfdbe9b 100644 --- a/Documentation/devicetree/bindings/usb/mediatek,musb.yaml +++ b/Documentation/devicetree/bindings/usb/mediatek,musb.yaml @@ -17,6 +17,7 @@ properties: compatible: items: - enum: + - mediatek,mt8516-musb - mediatek,mt2701-musb - const: mediatek,mtk-musb -- cgit v1.2.3