diff options
Diffstat (limited to 'drivers/usb/dwc3')
-rw-r--r-- | drivers/usb/dwc3/Kconfig | 12 | ||||
-rw-r--r-- | drivers/usb/dwc3/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/dwc3/core.c | 154 | ||||
-rw-r--r-- | drivers/usb/dwc3/core.h | 25 | ||||
-rw-r--r-- | drivers/usb/dwc3/debug.h | 26 | ||||
-rw-r--r-- | drivers/usb/dwc3/debugfs.c | 43 | ||||
-rw-r--r-- | drivers/usb/dwc3/drd.c | 34 | ||||
-rw-r--r-- | drivers/usb/dwc3/dwc3-of-simple.c | 2 | ||||
-rw-r--r-- | drivers/usb/dwc3/dwc3-qcom.c | 619 | ||||
-rw-r--r-- | drivers/usb/dwc3/ep0.c | 6 | ||||
-rw-r--r-- | drivers/usb/dwc3/gadget.c | 773 | ||||
-rw-r--r-- | drivers/usb/dwc3/gadget.h | 5 | ||||
-rw-r--r-- | drivers/usb/dwc3/trace.h | 12 |
13 files changed, 1204 insertions, 508 deletions
diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index ab8c0e0d3b60..451012ea1294 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -106,4 +106,16 @@ config USB_DWC3_ST inside (i.e. STiH407). Say 'Y' or 'M' if you have one such device. +config USB_DWC3_QCOM + tristate "Qualcomm Platform" + depends on ARCH_QCOM || COMPILE_TEST + depends on OF + default USB_DWC3 + help + Some Qualcomm SoCs use DesignWare Core IP for USB2/3 + functionality. + This driver also handles Qscratch wrapper which is needed + for peripheral mode support. + Say 'Y' or 'M' if you have one such device. + endif diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index 025bc68094fc..5c07d8f925e0 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -48,3 +48,4 @@ obj-$(CONFIG_USB_DWC3_PCI) += dwc3-pci.o obj-$(CONFIG_USB_DWC3_KEYSTONE) += dwc3-keystone.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 diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index a15648d25e30..ea91310113b9 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -8,6 +8,7 @@ * Sebastian Andrzej Siewior <bigeasy@linutronix.de> */ +#include <linux/clk.h> #include <linux/version.h> #include <linux/module.h> #include <linux/kernel.h> @@ -24,6 +25,7 @@ #include <linux/of.h> #include <linux/acpi.h> #include <linux/pinctrl/consumer.h> +#include <linux/reset.h> #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> @@ -266,6 +268,12 @@ done: return 0; } +static const struct clk_bulk_data dwc3_core_clks[] = { + { .id = "ref" }, + { .id = "bus_early" }, + { .id = "suspend" }, +}; + /* * dwc3_frame_length_adjustment - Adjusts frame length if required * @dwc3: Pointer to our controller context structure @@ -667,6 +675,9 @@ static void dwc3_core_exit(struct dwc3 *dwc) usb_phy_set_suspend(dwc->usb3_phy, 1); phy_power_off(dwc->usb2_generic_phy); phy_power_off(dwc->usb3_generic_phy); + clk_bulk_disable(dwc->num_clks, dwc->clks); + clk_bulk_unprepare(dwc->num_clks, dwc->clks); + reset_control_assert(dwc->reset); } static bool dwc3_core_is_valid(struct dwc3 *dwc) @@ -1245,7 +1256,7 @@ static void dwc3_check_params(struct dwc3 *dwc) static int dwc3_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct resource *res; + struct resource *res, dwc_res; struct dwc3 *dwc; int ret; @@ -1256,6 +1267,12 @@ static int dwc3_probe(struct platform_device *pdev) if (!dwc) return -ENOMEM; + dwc->clks = devm_kmemdup(dev, dwc3_core_clks, sizeof(dwc3_core_clks), + GFP_KERNEL); + if (!dwc->clks) + return -ENOMEM; + + dwc->num_clks = ARRAY_SIZE(dwc3_core_clks); dwc->dev = dev; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -1270,23 +1287,48 @@ static int dwc3_probe(struct platform_device *pdev) dwc->xhci_resources[0].flags = res->flags; dwc->xhci_resources[0].name = res->name; - res->start += DWC3_GLOBALS_REGS_START; - /* * Request memory region but exclude xHCI regs, * since it will be requested by the xhci-plat driver. */ - regs = devm_ioremap_resource(dev, res); - if (IS_ERR(regs)) { - ret = PTR_ERR(regs); - goto err0; - } + dwc_res = *res; + dwc_res.start += DWC3_GLOBALS_REGS_START; + + regs = devm_ioremap_resource(dev, &dwc_res); + if (IS_ERR(regs)) + return PTR_ERR(regs); dwc->regs = regs; - dwc->regs_size = resource_size(res); + dwc->regs_size = resource_size(&dwc_res); dwc3_get_properties(dwc); + dwc->reset = devm_reset_control_get_optional_shared(dev, NULL); + if (IS_ERR(dwc->reset)) + return PTR_ERR(dwc->reset); + + ret = clk_bulk_get(dev, dwc->num_clks, dwc->clks); + if (ret == -EPROBE_DEFER) + return ret; + /* + * Clocks are optional, but new DT platforms should support all clocks + * as required by the DT-binding. + */ + if (ret) + dwc->num_clks = 0; + + ret = reset_control_deassert(dwc->reset); + if (ret) + goto put_clks; + + ret = clk_bulk_prepare(dwc->num_clks, dwc->clks); + if (ret) + goto assert_reset; + + ret = clk_bulk_enable(dwc->num_clks, dwc->clks); + if (ret) + goto unprepare_clks; + platform_set_drvdata(pdev, dwc); dwc3_cache_hwparams(dwc); @@ -1350,13 +1392,13 @@ err1: pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); -err0: - /* - * restore res->start back to its original value so that, in case the - * probe is deferred, we don't end up getting error in request the - * memory region the next time probe is called. - */ - res->start -= DWC3_GLOBALS_REGS_START; + clk_bulk_disable(dwc->num_clks, dwc->clks); +unprepare_clks: + clk_bulk_unprepare(dwc->num_clks, dwc->clks); +assert_reset: + reset_control_assert(dwc->reset); +put_clks: + clk_bulk_put(dwc->num_clks, dwc->clks); return ret; } @@ -1364,15 +1406,8 @@ err0: static int dwc3_remove(struct platform_device *pdev) { struct dwc3 *dwc = platform_get_drvdata(pdev); - struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); pm_runtime_get_sync(&pdev->dev); - /* - * restore res->start back to its original value so that, in case the - * probe is deferred, we don't end up getting error in request the - * memory region the next time probe is called. - */ - res->start -= DWC3_GLOBALS_REGS_START; dwc3_debugfs_exit(dwc); dwc3_core_exit_mode(dwc); @@ -1386,14 +1421,48 @@ static int dwc3_remove(struct platform_device *pdev) dwc3_free_event_buffers(dwc); dwc3_free_scratch_buffers(dwc); + clk_bulk_put(dwc->num_clks, dwc->clks); return 0; } #ifdef CONFIG_PM +static int dwc3_core_init_for_resume(struct dwc3 *dwc) +{ + int ret; + + ret = reset_control_deassert(dwc->reset); + if (ret) + return ret; + + ret = clk_bulk_prepare(dwc->num_clks, dwc->clks); + if (ret) + goto assert_reset; + + ret = clk_bulk_enable(dwc->num_clks, dwc->clks); + if (ret) + goto unprepare_clks; + + ret = dwc3_core_init(dwc); + if (ret) + goto disable_clks; + + return 0; + +disable_clks: + clk_bulk_disable(dwc->num_clks, dwc->clks); +unprepare_clks: + clk_bulk_unprepare(dwc->num_clks, dwc->clks); +assert_reset: + reset_control_assert(dwc->reset); + + return ret; +} + static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg) { unsigned long flags; + u32 reg; switch (dwc->current_dr_role) { case DWC3_GCTL_PRTCAP_DEVICE: @@ -1403,9 +1472,25 @@ static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg) dwc3_core_exit(dwc); break; case DWC3_GCTL_PRTCAP_HOST: - /* do nothing during host runtime_suspend */ - if (!PMSG_IS_AUTO(msg)) + if (!PMSG_IS_AUTO(msg)) { dwc3_core_exit(dwc); + break; + } + + /* Let controller to suspend HSPHY before PHY driver suspends */ + if (dwc->dis_u2_susphy_quirk || + dwc->dis_enblslpm_quirk) { + reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); + reg |= DWC3_GUSB2PHYCFG_ENBLSLPM | + DWC3_GUSB2PHYCFG_SUSPHY; + dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); + + /* Give some time for USB2 PHY to suspend */ + usleep_range(5000, 6000); + } + + phy_pm_runtime_put_sync(dwc->usb2_generic_phy); + phy_pm_runtime_put_sync(dwc->usb3_generic_phy); break; case DWC3_GCTL_PRTCAP_OTG: /* do nothing during runtime_suspend */ @@ -1433,10 +1518,11 @@ static int dwc3_resume_common(struct dwc3 *dwc, pm_message_t msg) { unsigned long flags; int ret; + u32 reg; switch (dwc->current_dr_role) { case DWC3_GCTL_PRTCAP_DEVICE: - ret = dwc3_core_init(dwc); + ret = dwc3_core_init_for_resume(dwc); if (ret) return ret; @@ -1446,13 +1532,25 @@ static int dwc3_resume_common(struct dwc3 *dwc, pm_message_t msg) spin_unlock_irqrestore(&dwc->lock, flags); break; case DWC3_GCTL_PRTCAP_HOST: - /* nothing to do on host runtime_resume */ if (!PMSG_IS_AUTO(msg)) { - ret = dwc3_core_init(dwc); + ret = dwc3_core_init_for_resume(dwc); if (ret) return ret; dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST); + break; } + /* Restore GUSB2PHYCFG bits that were modified in suspend */ + reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); + if (dwc->dis_u2_susphy_quirk) + reg &= ~DWC3_GUSB2PHYCFG_SUSPHY; + + if (dwc->dis_enblslpm_quirk) + reg &= ~DWC3_GUSB2PHYCFG_ENBLSLPM; + + dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); + + phy_pm_runtime_get_sync(dwc->usb2_generic_phy); + phy_pm_runtime_get_sync(dwc->usb3_generic_phy); break; case DWC3_GCTL_PRTCAP_OTG: /* nothing to do on runtime_resume */ diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 4f3b43809917..285ce0ef3b91 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -639,8 +639,6 @@ struct dwc3_event_buffer { * @resource_index: Resource transfer index * @frame_number: set to the frame number we want this transfer to start (ISOC) * @interval: the interval on which the ISOC transfer is started - * @allocated_requests: number of requests allocated - * @queued_requests: number of requests queued for transfer * @name: a human readable name e.g. ep1out-bulk * @direction: true for TX, false for RX * @stream_capable: true when streams are enabled @@ -664,11 +662,9 @@ struct dwc3_ep { #define DWC3_EP_ENABLED BIT(0) #define DWC3_EP_STALL BIT(1) #define DWC3_EP_WEDGE BIT(2) -#define DWC3_EP_BUSY BIT(4) +#define DWC3_EP_TRANSFER_STARTED BIT(3) #define DWC3_EP_PENDING_REQUEST BIT(5) -#define DWC3_EP_MISSED_ISOC BIT(6) #define DWC3_EP_END_TRANSFER_PENDING BIT(7) -#define DWC3_EP_TRANSFER_STARTED BIT(8) /* This last one is specific to EP0 */ #define DWC3_EP0_DIR_IN BIT(31) @@ -688,8 +684,6 @@ struct dwc3_ep { u8 number; u8 type; u8 resource_index; - u32 allocated_requests; - u32 queued_requests; u32 frame_number; u32 interval; @@ -832,7 +826,9 @@ struct dwc3_hwparams { * @list: a list_head used for request queueing * @dep: struct dwc3_ep owning this request * @sg: pointer to first incomplete sg + * @start_sg: pointer to the sg which should be queued next * @num_pending_sgs: counter to pending sgs + * @num_queued_sgs: counter to the number of sgs which already got queued * @remaining: amount of data remaining * @epnum: endpoint number to which this request refers * @trb: pointer to struct dwc3_trb @@ -848,8 +844,10 @@ struct dwc3_request { struct list_head list; struct dwc3_ep *dep; struct scatterlist *sg; + struct scatterlist *start_sg; unsigned num_pending_sgs; + unsigned int num_queued_sgs; unsigned remaining; u8 epnum; struct dwc3_trb *trb; @@ -891,6 +889,9 @@ struct dwc3_scratchpad_array { * @eps: endpoint array * @gadget: device side representation of the peripheral controller * @gadget_driver: pointer to the gadget driver + * @clks: array of clocks + * @num_clks: number of clocks + * @reset: reset control * @regs: base address for our registers * @regs_size: address space size * @fladj: frame length adjustment @@ -1013,6 +1014,11 @@ struct dwc3 { struct usb_gadget gadget; struct usb_gadget_driver *gadget_driver; + struct clk_bulk_data *clks; + int num_clks; + + struct reset_control *reset; + struct usb_phy *usb2_phy; struct usb_phy *usb3_phy; @@ -1197,11 +1203,12 @@ struct dwc3_event_depevt { /* Within XferNotReady */ #define DEPEVT_STATUS_TRANSFER_ACTIVE BIT(3) -/* Within XferComplete */ +/* Within XferComplete or XferInProgress */ #define DEPEVT_STATUS_BUSERR BIT(0) #define DEPEVT_STATUS_SHORT BIT(1) #define DEPEVT_STATUS_IOC BIT(2) -#define DEPEVT_STATUS_LST BIT(3) +#define DEPEVT_STATUS_LST BIT(3) /* XferComplete */ +#define DEPEVT_STATUS_MISSED_ISOC BIT(3) /* XferInProgress */ /* Stream event only */ #define DEPEVT_STREAMEVT_FOUND 1 diff --git a/drivers/usb/dwc3/debug.h b/drivers/usb/dwc3/debug.h index bfb90c52d8fc..c66d216dcc30 100644 --- a/drivers/usb/dwc3/debug.h +++ b/drivers/usb/dwc3/debug.h @@ -475,21 +475,37 @@ dwc3_ep_event_string(char *str, const struct dwc3_event_depevt *event, if (ret < 0) return "UNKNOWN"; + status = event->status; + switch (event->endpoint_event) { case DWC3_DEPEVT_XFERCOMPLETE: - strcat(str, "Transfer Complete"); + len = strlen(str); + sprintf(str + len, "Transfer Complete (%c%c%c)", + status & DEPEVT_STATUS_SHORT ? 'S' : 's', + status & DEPEVT_STATUS_IOC ? 'I' : 'i', + status & DEPEVT_STATUS_LST ? 'L' : 'l'); + len = strlen(str); if (epnum <= 1) sprintf(str + len, " [%s]", dwc3_ep0_state_string(ep0state)); break; case DWC3_DEPEVT_XFERINPROGRESS: - strcat(str, "Transfer In-Progress"); + len = strlen(str); + + sprintf(str + len, "Transfer In Progress [%d] (%c%c%c)", + event->parameters, + status & DEPEVT_STATUS_SHORT ? 'S' : 's', + status & DEPEVT_STATUS_IOC ? 'I' : 'i', + status & DEPEVT_STATUS_LST ? 'M' : 'm'); break; case DWC3_DEPEVT_XFERNOTREADY: - strcat(str, "Transfer Not Ready"); - status = event->status & DEPEVT_STATUS_TRANSFER_ACTIVE; - strcat(str, status ? " (Active)" : " (Not Active)"); + len = strlen(str); + + sprintf(str + len, "Transfer Not Ready [%d]%s", + event->parameters, + status & DEPEVT_STATUS_TRANSFER_ACTIVE ? + " (Active)" : " (Not Active)"); /* Control Endpoints */ if (epnum <= 1) { diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c index 2f07be1e1f31..df8e73ec3342 100644 --- a/drivers/usb/dwc3/debugfs.c +++ b/drivers/usb/dwc3/debugfs.c @@ -716,9 +716,6 @@ static void dwc3_debugfs_create_endpoint_dir(struct dwc3_ep *dep, struct dentry *dir; dir = debugfs_create_dir(dep->name, parent); - if (IS_ERR_OR_NULL(dir)) - return; - dwc3_debugfs_create_endpoint_files(dep, dir); } @@ -740,49 +737,31 @@ static void dwc3_debugfs_create_endpoint_dirs(struct dwc3 *dwc, void dwc3_debugfs_init(struct dwc3 *dwc) { struct dentry *root; - struct dentry *file; - - root = debugfs_create_dir(dev_name(dwc->dev), NULL); - if (IS_ERR_OR_NULL(root)) { - if (!root) - dev_err(dwc->dev, "Can't create debugfs root\n"); - return; - } - dwc->root = root; dwc->regset = kzalloc(sizeof(*dwc->regset), GFP_KERNEL); - if (!dwc->regset) { - debugfs_remove_recursive(root); + if (!dwc->regset) return; - } dwc->regset->regs = dwc3_regs; dwc->regset->nregs = ARRAY_SIZE(dwc3_regs); dwc->regset->base = dwc->regs - DWC3_GLOBALS_REGS_START; - file = debugfs_create_regset32("regdump", S_IRUGO, root, dwc->regset); - if (!file) - dev_dbg(dwc->dev, "Can't create debugfs regdump\n"); + root = debugfs_create_dir(dev_name(dwc->dev), NULL); + dwc->root = root; + + debugfs_create_regset32("regdump", S_IRUGO, root, dwc->regset); if (IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)) { - file = debugfs_create_file("mode", S_IRUGO | S_IWUSR, root, - dwc, &dwc3_mode_fops); - if (!file) - dev_dbg(dwc->dev, "Can't create debugfs mode\n"); + debugfs_create_file("mode", S_IRUGO | S_IWUSR, root, dwc, + &dwc3_mode_fops); } if (IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) || IS_ENABLED(CONFIG_USB_DWC3_GADGET)) { - file = debugfs_create_file("testmode", S_IRUGO | S_IWUSR, root, - dwc, &dwc3_testmode_fops); - if (!file) - dev_dbg(dwc->dev, "Can't create debugfs testmode\n"); - - file = debugfs_create_file("link_state", S_IRUGO | S_IWUSR, - root, dwc, &dwc3_link_state_fops); - if (!file) - dev_dbg(dwc->dev, "Can't create debugfs link_state\n"); - + debugfs_create_file("testmode", S_IRUGO | S_IWUSR, root, dwc, + &dwc3_testmode_fops); + debugfs_create_file("link_state", S_IRUGO | S_IWUSR, root, dwc, + &dwc3_link_state_fops); dwc3_debugfs_create_endpoint_dirs(dwc, root); } } diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c index 1d8c557e97e0..218371f985ca 100644 --- a/drivers/usb/dwc3/drd.c +++ b/drivers/usb/dwc3/drd.c @@ -8,6 +8,7 @@ */ #include <linux/extcon.h> +#include <linux/of_graph.h> #include <linux/platform_device.h> #include "debug.h" @@ -439,17 +440,38 @@ static int dwc3_drd_notifier(struct notifier_block *nb, return NOTIFY_DONE; } +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; + + if (of_property_read_bool(dev->of_node, "extcon")) + return extcon_get_edev_by_phandle(dwc->dev, 0); + + np_phy = of_parse_phandle(dev->of_node, "phys", 0); + np_conn = of_graph_get_remote_node(np_phy, -1, -1); + + if (np_conn) + edev = extcon_find_edev_by_node(np_conn); + else + edev = NULL; + + of_node_put(np_conn); + of_node_put(np_phy); + + return edev; +} + int dwc3_drd_init(struct dwc3 *dwc) { int ret, irq; - if (dwc->dev->of_node && - of_property_read_bool(dwc->dev->of_node, "extcon")) { - dwc->edev = extcon_get_edev_by_phandle(dwc->dev, 0); - - if (IS_ERR(dwc->edev)) - return PTR_ERR(dwc->edev); + dwc->edev = dwc3_get_extcon(dwc); + if (IS_ERR(dwc->edev)) + return PTR_ERR(dwc->edev); + if (dwc->edev) { dwc->edev_nb.notifier_call = dwc3_drd_notifier; ret = extcon_register_notifier(dwc->edev, EXTCON_USB_HOST, &dwc->edev_nb); diff --git a/drivers/usb/dwc3/dwc3-of-simple.c b/drivers/usb/dwc3/dwc3-of-simple.c index cb2ee96fd3e8..6b3ccd542bd7 100644 --- a/drivers/usb/dwc3/dwc3-of-simple.c +++ b/drivers/usb/dwc3/dwc3-of-simple.c @@ -208,13 +208,13 @@ static const struct dev_pm_ops dwc3_of_simple_dev_pm_ops = { }; static const struct of_device_id of_dwc3_simple_match[] = { - { .compatible = "qcom,dwc3" }, { .compatible = "rockchip,rk3399-dwc3" }, { .compatible = "xlnx,zynqmp-dwc3" }, { .compatible = "cavium,octeon-7130-usb-uctl" }, { .compatible = "sprd,sc9860-dwc3" }, { .compatible = "amlogic,meson-axg-dwc3" }, { .compatible = "amlogic,meson-gxl-dwc3" }, + { .compatible = "allwinner,sun50i-h6-dwc3" }, { /* Sentinel */ } }; MODULE_DEVICE_TABLE(of, of_dwc3_simple_match); diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c new file mode 100644 index 000000000000..b0e67ab2f98c --- /dev/null +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -0,0 +1,619 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2018, The Linux Foundation. All rights reserved. + * + * Inspired by dwc3-of-simple.c + */ + +#include <linux/io.h> +#include <linux/of.h> +#include <linux/clk.h> +#include <linux/irq.h> +#include <linux/clk-provider.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/extcon.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/phy/phy.h> +#include <linux/usb/of.h> +#include <linux/reset.h> +#include <linux/iopoll.h> + +#include "core.h" + +/* USB QSCRATCH Hardware registers */ +#define QSCRATCH_HS_PHY_CTRL 0x10 +#define UTMI_OTG_VBUS_VALID BIT(20) +#define SW_SESSVLD_SEL BIT(28) + +#define QSCRATCH_SS_PHY_CTRL 0x30 +#define LANE0_PWR_PRESENT BIT(24) + +#define QSCRATCH_GENERAL_CFG 0x08 +#define PIPE_UTMI_CLK_SEL BIT(0) +#define PIPE3_PHYSTATUS_SW BIT(3) +#define PIPE_UTMI_CLK_DIS BIT(8) + +#define PWR_EVNT_IRQ_STAT_REG 0x58 +#define PWR_EVNT_LPM_IN_L2_MASK BIT(4) +#define PWR_EVNT_LPM_OUT_L2_MASK BIT(5) + +struct dwc3_qcom { + struct device *dev; + void __iomem *qscratch_base; + struct platform_device *dwc3; + struct clk **clks; + int num_clocks; + struct reset_control *resets; + + int hs_phy_irq; + int dp_hs_phy_irq; + int dm_hs_phy_irq; + int ss_phy_irq; + + struct extcon_dev *edev; + struct extcon_dev *host_edev; + struct notifier_block vbus_nb; + struct notifier_block host_nb; + + enum usb_dr_mode mode; + bool is_suspended; + bool pm_suspended; +}; + +static inline void dwc3_qcom_setbits(void __iomem *base, u32 offset, u32 val) +{ + u32 reg; + + reg = readl(base + offset); + reg |= val; + writel(reg, base + offset); + + /* ensure that above write is through */ + readl(base + offset); +} + +static inline void dwc3_qcom_clrbits(void __iomem *base, u32 offset, u32 val) +{ + u32 reg; + + reg = readl(base + offset); + reg &= ~val; + writel(reg, base + offset); + + /* ensure that above write is through */ + readl(base + offset); +} + +static void dwc3_qcom_vbus_overrride_enable(struct dwc3_qcom *qcom, bool enable) +{ + if (enable) { + dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_SS_PHY_CTRL, + LANE0_PWR_PRESENT); + dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_HS_PHY_CTRL, + UTMI_OTG_VBUS_VALID | SW_SESSVLD_SEL); + } else { + dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_SS_PHY_CTRL, + LANE0_PWR_PRESENT); + dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_HS_PHY_CTRL, + UTMI_OTG_VBUS_VALID | SW_SESSVLD_SEL); + } +} + +static int dwc3_qcom_vbus_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3_qcom *qcom = container_of(nb, struct dwc3_qcom, vbus_nb); + + /* enable vbus override for device mode */ + dwc3_qcom_vbus_overrride_enable(qcom, event); + qcom->mode = event ? USB_DR_MODE_PERIPHERAL : USB_DR_MODE_HOST; + + return NOTIFY_DONE; +} + +static int dwc3_qcom_host_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3_qcom *qcom = container_of(nb, struct dwc3_qcom, host_nb); + + /* disable vbus override in host mode */ + dwc3_qcom_vbus_overrride_enable(qcom, !event); + qcom->mode = event ? USB_DR_MODE_HOST : USB_DR_MODE_PERIPHERAL; + + return NOTIFY_DONE; +} + +static int dwc3_qcom_register_extcon(struct dwc3_qcom *qcom) +{ + struct device *dev = qcom->dev; + struct extcon_dev *host_edev; + int ret; + + if (!of_property_read_bool(dev->of_node, "extcon")) + return 0; + + qcom->edev = extcon_get_edev_by_phandle(dev, 0); + if (IS_ERR(qcom->edev)) + return PTR_ERR(qcom->edev); + + qcom->vbus_nb.notifier_call = dwc3_qcom_vbus_notifier; + + qcom->host_edev = extcon_get_edev_by_phandle(dev, 1); + if (IS_ERR(qcom->host_edev)) + qcom->host_edev = NULL; + + ret = devm_extcon_register_notifier(dev, qcom->edev, EXTCON_USB, + &qcom->vbus_nb); + if (ret < 0) { + dev_err(dev, "VBUS notifier register failed\n"); + return ret; + } + + if (qcom->host_edev) + host_edev = qcom->host_edev; + else + host_edev = qcom->edev; + + qcom->host_nb.notifier_call = dwc3_qcom_host_notifier; + ret = devm_extcon_register_notifier(dev, host_edev, EXTCON_USB_HOST, + &qcom->host_nb); + if (ret < 0) { + dev_err(dev, "Host notifier register failed\n"); + return ret; + } + + /* Update initial VBUS override based on extcon state */ + if (extcon_get_state(qcom->edev, EXTCON_USB) || + !extcon_get_state(host_edev, EXTCON_USB_HOST)) + dwc3_qcom_vbus_notifier(&qcom->vbus_nb, true, qcom->edev); + else + dwc3_qcom_vbus_notifier(&qcom->vbus_nb, false, qcom->edev); + + return 0; +} + +static void dwc3_qcom_disable_interrupts(struct dwc3_qcom *qcom) +{ + if (qcom->hs_phy_irq) { + disable_irq_wake(qcom->hs_phy_irq); + disable_irq_nosync(qcom->hs_phy_irq); + } + + if (qcom->dp_hs_phy_irq) { + disable_irq_wake(qcom->dp_hs_phy_irq); + disable_irq_nosync(qcom->dp_hs_phy_irq); + } + + if (qcom->dm_hs_phy_irq) { + disable_irq_wake(qcom->dm_hs_phy_irq); + disable_irq_nosync(qcom->dm_hs_phy_irq); + } + + if (qcom->ss_phy_irq) { + disable_irq_wake(qcom->ss_phy_irq); + disable_irq_nosync(qcom->ss_phy_irq); + } +} + +static void dwc3_qcom_enable_interrupts(struct dwc3_qcom *qcom) +{ + if (qcom->hs_phy_irq) { + enable_irq(qcom->hs_phy_irq); + enable_irq_wake(qcom->hs_phy_irq); + } + + if (qcom->dp_hs_phy_irq) { + enable_irq(qcom->dp_hs_phy_irq); + enable_irq_wake(qcom->dp_hs_phy_irq); + } + + if (qcom->dm_hs_phy_irq) { + enable_irq(qcom->dm_hs_phy_irq); + enable_irq_wake(qcom->dm_hs_phy_irq); + } + + if (qcom->ss_phy_irq) { + enable_irq(qcom->ss_phy_irq); + enable_irq_wake(qcom->ss_phy_irq); + } +} + +static int dwc3_qcom_suspend(struct dwc3_qcom *qcom) +{ + u32 val; + int i; + + if (qcom->is_suspended) + return 0; + + val = readl(qcom->qscratch_base + PWR_EVNT_IRQ_STAT_REG); + if (!(val & PWR_EVNT_LPM_IN_L2_MASK)) + dev_err(qcom->dev, "HS-PHY not in L2\n"); + + for (i = qcom->num_clocks - 1; i >= 0; i--) + clk_disable_unprepare(qcom->clks[i]); + + qcom->is_suspended = true; + dwc3_qcom_enable_interrupts(qcom); + + return 0; +} + +static int dwc3_qcom_resume(struct dwc3_qcom *qcom) +{ + int ret; + int i; + + if (!qcom->is_suspended) + return 0; + + dwc3_qcom_disable_interrupts(qcom); + + for (i = 0; i < qcom->num_clocks; i++) { + ret = clk_prepare_enable(qcom->clks[i]); + if (ret < 0) { + while (--i >= 0) + clk_disable_unprepare(qcom->clks[i]); + return ret; + } + } + + /* Clear existing events from PHY related to L2 in/out */ + dwc3_qcom_setbits(qcom->qscratch_base, PWR_EVNT_IRQ_STAT_REG, + PWR_EVNT_LPM_IN_L2_MASK | PWR_EVNT_LPM_OUT_L2_MASK); + + qcom->is_suspended = false; + + return 0; +} + +static irqreturn_t qcom_dwc3_resume_irq(int irq, void *data) +{ + struct dwc3_qcom *qcom = data; + struct dwc3 *dwc = platform_get_drvdata(qcom->dwc3); + + /* If pm_suspended then let pm_resume take care of resuming h/w */ + if (qcom->pm_suspended) + return IRQ_HANDLED; + + if (dwc->xhci) + pm_runtime_resume(&dwc->xhci->dev); + + return IRQ_HANDLED; +} + +static void dwc3_qcom_select_utmi_clk(struct dwc3_qcom *qcom) +{ + /* Configure dwc3 to use UTMI clock as PIPE clock not present */ + dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG, + PIPE_UTMI_CLK_DIS); + + usleep_range(100, 1000); + + dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG, + PIPE_UTMI_CLK_SEL | PIPE3_PHYSTATUS_SW); + + usleep_range(100, 1000); + + dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG, + PIPE_UTMI_CLK_DIS); +} + +static int dwc3_qcom_setup_irq(struct platform_device *pdev) +{ + struct dwc3_qcom *qcom = platform_get_drvdata(pdev); + int irq, ret; + + irq = platform_get_irq_byname(pdev, "hs_phy_irq"); + if (irq > 0) { + /* Keep wakeup interrupts disabled until suspend */ + irq_set_status_flags(irq, IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(qcom->dev, irq, NULL, + qcom_dwc3_resume_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "qcom_dwc3 HS", qcom); + if (ret) { + dev_err(qcom->dev, "hs_phy_irq failed: %d\n", ret); + return ret; + } + qcom->hs_phy_irq = irq; + } + + irq = platform_get_irq_byname(pdev, "dp_hs_phy_irq"); + if (irq > 0) { + irq_set_status_flags(irq, IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(qcom->dev, irq, NULL, + qcom_dwc3_resume_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "qcom_dwc3 DP_HS", qcom); + if (ret) { + dev_err(qcom->dev, "dp_hs_phy_irq failed: %d\n", ret); + return ret; + } + qcom->dp_hs_phy_irq = irq; + } + + irq = platform_get_irq_byname(pdev, "dm_hs_phy_irq"); + if (irq > 0) { + irq_set_status_flags(irq, IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(qcom->dev, irq, NULL, + qcom_dwc3_resume_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "qcom_dwc3 DM_HS", qcom); + if (ret) { + dev_err(qcom->dev, "dm_hs_phy_irq failed: %d\n", ret); + return ret; + } + qcom->dm_hs_phy_irq = irq; + } + + irq = platform_get_irq_byname(pdev, "ss_phy_irq"); + if (irq > 0) { + irq_set_status_flags(irq, IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(qcom->dev, irq, NULL, + qcom_dwc3_resume_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "qcom_dwc3 SS", qcom); + if (ret) { + dev_err(qcom->dev, "ss_phy_irq failed: %d\n", ret); + return ret; + } + qcom->ss_phy_irq = irq; + } + + return 0; +} + +static int dwc3_qcom_clk_init(struct dwc3_qcom *qcom, int count) +{ + struct device *dev = qcom->dev; + struct device_node *np = dev->of_node; + int i; + + qcom->num_clocks = count; + + if (!count) + return 0; + + qcom->clks = devm_kcalloc(dev, qcom->num_clocks, + sizeof(struct clk *), GFP_KERNEL); + if (!qcom->clks) + return -ENOMEM; + + for (i = 0; i < qcom->num_clocks; i++) { + struct clk *clk; + int ret; + + clk = of_clk_get(np, i); + if (IS_ERR(clk)) { + while (--i >= 0) + clk_put(qcom->clks[i]); + return PTR_ERR(clk); + } + + ret = clk_prepare_enable(clk); + if (ret < 0) { + while (--i >= 0) { + clk_disable_unprepare(qcom->clks[i]); + clk_put(qcom->clks[i]); + } + clk_put(clk); + + return ret; + } + + qcom->clks[i] = clk; + } + + return 0; +} + +static int dwc3_qcom_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node, *dwc3_np; + struct device *dev = &pdev->dev; + struct dwc3_qcom *qcom; + struct resource *res; + int ret, i; + bool ignore_pipe_clk; + + qcom = devm_kzalloc(&pdev->dev, sizeof(*qcom), GFP_KERNEL); + if (!qcom) + return -ENOMEM; + + platform_set_drvdata(pdev, qcom); + qcom->dev = &pdev->dev; + + qcom->resets = devm_reset_control_array_get_optional_exclusive(dev); + if (IS_ERR(qcom->resets)) { + ret = PTR_ERR(qcom->resets); + dev_err(&pdev->dev, "failed to get resets, err=%d\n", ret); + return ret; + } + + ret = reset_control_assert(qcom->resets); + if (ret) { + dev_err(&pdev->dev, "failed to assert resets, err=%d\n", ret); + return ret; + } + + usleep_range(10, 1000); + + ret = reset_control_deassert(qcom->resets); + if (ret) { + dev_err(&pdev->dev, "failed to deassert resets, err=%d\n", ret); + goto reset_assert; + } + + ret = dwc3_qcom_clk_init(qcom, of_count_phandle_with_args(np, + "clocks", "#clock-cells")); + if (ret) { + dev_err(dev, "failed to get clocks\n"); + goto reset_assert; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + qcom->qscratch_base = devm_ioremap_resource(dev, res); + if (IS_ERR(qcom->qscratch_base)) { + dev_err(dev, "failed to map qscratch, err=%d\n", ret); + ret = PTR_ERR(qcom->qscratch_base); + goto clk_disable; + } + + ret = dwc3_qcom_setup_irq(pdev); + if (ret) + goto clk_disable; + + dwc3_np = of_get_child_by_name(np, "dwc3"); + if (!dwc3_np) { + dev_err(dev, "failed to find dwc3 core child\n"); + ret = -ENODEV; + goto clk_disable; + } + + /* + * Disable pipe_clk requirement if specified. Used when dwc3 + * operates without SSPHY and only HS/FS/LS modes are supported. + */ + ignore_pipe_clk = device_property_read_bool(dev, + "qcom,select-utmi-as-pipe-clk"); + if (ignore_pipe_clk) + dwc3_qcom_select_utmi_clk(qcom); + + ret = of_platform_populate(np, NULL, NULL, dev); + if (ret) { + dev_err(dev, "failed to register dwc3 core - %d\n", ret); + goto clk_disable; + } + + qcom->dwc3 = of_find_device_by_node(dwc3_np); + if (!qcom->dwc3) { + dev_err(&pdev->dev, "failed to get dwc3 platform device\n"); + goto depopulate; + } + + qcom->mode = usb_get_dr_mode(&qcom->dwc3->dev); + + /* enable vbus override for device mode */ + if (qcom->mode == USB_DR_MODE_PERIPHERAL) + dwc3_qcom_vbus_overrride_enable(qcom, true); + + /* register extcon to override sw_vbus on Vbus change later */ + ret = dwc3_qcom_register_extcon(qcom); + if (ret) + goto depopulate; + + device_init_wakeup(&pdev->dev, 1); + qcom->is_suspended = false; + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_forbid(dev); + + return 0; + +depopulate: + of_platform_depopulate(&pdev->dev); +clk_disable: + for (i = qcom->num_clocks - 1; i >= 0; i--) { + clk_disable_unprepare(qcom->clks[i]); + clk_put(qcom->clks[i]); + } +reset_assert: + reset_control_assert(qcom->resets); + + return ret; +} + +static int dwc3_qcom_remove(struct platform_device *pdev) +{ + struct dwc3_qcom *qcom = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + int i; + + of_platform_depopulate(dev); + + for (i = qcom->num_clocks - 1; i >= 0; i--) { + clk_disable_unprepare(qcom->clks[i]); + clk_put(qcom->clks[i]); + } + qcom->num_clocks = 0; + + reset_control_assert(qcom->resets); + + pm_runtime_allow(dev); + pm_runtime_disable(dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int dwc3_qcom_pm_suspend(struct device *dev) +{ + struct dwc3_qcom *qcom = dev_get_drvdata(dev); + int ret = 0; + + ret = dwc3_qcom_suspend(qcom); + if (!ret) + qcom->pm_suspended = true; + + return ret; +} + +static int dwc3_qcom_pm_resume(struct device *dev) +{ + struct dwc3_qcom *qcom = dev_get_drvdata(dev); + int ret; + + ret = dwc3_qcom_resume(qcom); + if (!ret) + qcom->pm_suspended = false; + + return ret; +} +#endif + +#ifdef CONFIG_PM +static int dwc3_qcom_runtime_suspend(struct device *dev) +{ + struct dwc3_qcom *qcom = dev_get_drvdata(dev); + + return dwc3_qcom_suspend(qcom); +} + +static int dwc3_qcom_runtime_resume(struct device *dev) +{ + struct dwc3_qcom *qcom = dev_get_drvdata(dev); + + return dwc3_qcom_resume(qcom); +} +#endif + +static const struct dev_pm_ops dwc3_qcom_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dwc3_qcom_pm_suspend, dwc3_qcom_pm_resume) + SET_RUNTIME_PM_OPS(dwc3_qcom_runtime_suspend, dwc3_qcom_runtime_resume, + NULL) +}; + +static const struct of_device_id dwc3_qcom_of_match[] = { + { .compatible = "qcom,dwc3" }, + { .compatible = "qcom,msm8996-dwc3" }, + { .compatible = "qcom,sdm845-dwc3" }, + { } +}; +MODULE_DEVICE_TABLE(of, dwc3_qcom_of_match); + +static struct platform_driver dwc3_qcom_driver = { + .probe = dwc3_qcom_probe, + .remove = dwc3_qcom_remove, + .driver = { + .name = "dwc3-qcom", + .pm = &dwc3_qcom_dev_pm_ops, + .of_match_table = dwc3_qcom_of_match, + }, +}; + +module_platform_driver(dwc3_qcom_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare DWC3 QCOM Glue Driver"); diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 5a991bca8ed7..c77ff50a88a2 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -66,7 +66,7 @@ static int dwc3_ep0_start_trans(struct dwc3_ep *dep) struct dwc3 *dwc; int ret; - if (dep->flags & DWC3_EP_BUSY) + if (dep->flags & DWC3_EP_TRANSFER_STARTED) return 0; dwc = dep->dwc; @@ -79,8 +79,6 @@ static int dwc3_ep0_start_trans(struct dwc3_ep *dep) if (ret < 0) return ret; - dep->flags |= DWC3_EP_BUSY; - dep->resource_index = dwc3_gadget_ep_get_transfer_index(dep); dwc->ep0_next_event = DWC3_EP0_COMPLETE; return 0; @@ -913,7 +911,7 @@ static void dwc3_ep0_xfer_complete(struct dwc3 *dwc, { struct dwc3_ep *dep = dwc->eps[event->endpoint_number]; - dep->flags &= ~DWC3_EP_BUSY; + dep->flags &= ~DWC3_EP_TRANSFER_STARTED; dep->resource_index = 0; dwc->setup_packet_pending = false; diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 0dedf8a799f4..69bf137aab37 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -27,6 +27,9 @@ #include "gadget.h" #include "io.h" +#define DWC3_ALIGN_FRAME(d) (((d)->frame_number + (d)->interval) \ + & ~((d)->interval - 1)) + /** * dwc3_gadget_set_test_mode - enables usb2 test modes * @dwc: pointer to our context structure @@ -375,6 +378,7 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd, switch (DWC3_DEPCMD_CMD(cmd)) { case DWC3_DEPCMD_STARTTRANSFER: dep->flags |= DWC3_EP_TRANSFER_STARTED; + dwc3_gadget_ep_get_transfer_index(dep); break; case DWC3_DEPCMD_ENDTRANSFER: dep->flags &= ~DWC3_EP_TRANSFER_STARTED; @@ -455,7 +459,17 @@ static void dwc3_free_trb_pool(struct dwc3_ep *dep) dep->trb_pool_dma = 0; } -static int dwc3_gadget_set_xfer_resource(struct dwc3 *dwc, struct dwc3_ep *dep); +static int dwc3_gadget_set_xfer_resource(struct dwc3_ep *dep) +{ + struct dwc3_gadget_ep_cmd_params params; + + memset(¶ms, 0x00, sizeof(params)); + + params.param0 = DWC3_DEPXFERCFG_NUM_XFER_RES(1); + + return dwc3_send_gadget_ep_cmd(dep, DWC3_DEPCMD_SETTRANSFRESOURCE, + ¶ms); +} /** * dwc3_gadget_start_config - configure ep resources @@ -491,9 +505,10 @@ static int dwc3_gadget_set_xfer_resource(struct dwc3 *dwc, struct dwc3_ep *dep); * triggered only when called for EP0-out, which always happens first, and which * should only happen in one of the above conditions. */ -static int dwc3_gadget_start_config(struct dwc3 *dwc, struct dwc3_ep *dep) +static int dwc3_gadget_start_config(struct dwc3_ep *dep) { struct dwc3_gadget_ep_cmd_params params; + struct dwc3 *dwc; u32 cmd; int i; int ret; @@ -503,6 +518,7 @@ static int dwc3_gadget_start_config(struct dwc3 *dwc, struct dwc3_ep *dep) memset(¶ms, 0x00, sizeof(params)); cmd = DWC3_DEPCMD_DEPSTARTCFG; + dwc = dep->dwc; ret = dwc3_send_gadget_ep_cmd(dep, cmd, ¶ms); if (ret) @@ -514,7 +530,7 @@ static int dwc3_gadget_start_config(struct dwc3 *dwc, struct dwc3_ep *dep) if (!dep) continue; - ret = dwc3_gadget_set_xfer_resource(dwc, dep); + ret = dwc3_gadget_set_xfer_resource(dep); if (ret) return ret; } @@ -522,16 +538,12 @@ static int dwc3_gadget_start_config(struct dwc3 *dwc, struct dwc3_ep *dep) return 0; } -static int dwc3_gadget_set_ep_config(struct dwc3 *dwc, struct dwc3_ep *dep, - bool modify, bool restore) +static int dwc3_gadget_set_ep_config(struct dwc3_ep *dep, unsigned int action) { const struct usb_ss_ep_comp_descriptor *comp_desc; const struct usb_endpoint_descriptor *desc; struct dwc3_gadget_ep_cmd_params params; - - if (dev_WARN_ONCE(dwc->dev, modify && restore, - "Can't modify and restore\n")) - return -EINVAL; + struct dwc3 *dwc = dep->dwc; comp_desc = dep->endpoint.comp_desc; desc = dep->endpoint.desc; @@ -547,14 +559,9 @@ static int dwc3_gadget_set_ep_config(struct dwc3 *dwc, struct dwc3_ep *dep, params.param0 |= DWC3_DEPCFG_BURST_SIZE(burst - 1); } - if (modify) { - params.param0 |= DWC3_DEPCFG_ACTION_MODIFY; - } else if (restore) { - params.param0 |= DWC3_DEPCFG_ACTION_RESTORE; + params.param0 |= action; + if (action == DWC3_DEPCFG_ACTION_RESTORE) params.param2 |= dep->saved_state; - } else { - params.param0 |= DWC3_DEPCFG_ACTION_INIT; - } if (usb_endpoint_xfer_control(desc)) params.param1 = DWC3_DEPCFG_XFER_COMPLETE_EN; @@ -594,29 +601,15 @@ static int dwc3_gadget_set_ep_config(struct dwc3 *dwc, struct dwc3_ep *dep, return dwc3_send_gadget_ep_cmd(dep, DWC3_DEPCMD_SETEPCONFIG, ¶ms); } -static int dwc3_gadget_set_xfer_resource(struct dwc3 *dwc, struct dwc3_ep *dep) -{ - struct dwc3_gadget_ep_cmd_params params; - - memset(¶ms, 0x00, sizeof(params)); - - params.param0 = DWC3_DEPXFERCFG_NUM_XFER_RES(1); - - return dwc3_send_gadget_ep_cmd(dep, DWC3_DEPCMD_SETTRANSFRESOURCE, - ¶ms); -} - /** * __dwc3_gadget_ep_enable - initializes a hw endpoint * @dep: endpoint to be initialized - * @modify: if true, modify existing endpoint configuration - * @restore: if true, restore endpoint configuration from scratch buffer + * @action: one of INIT, MODIFY or RESTORE * * Caller should take care of locking. Execute all necessary commands to * initialize a HW endpoint so it can be used by a gadget driver. */ -static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, - bool modify, bool restore) +static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, unsigned int action) { const struct usb_endpoint_descriptor *desc = dep->endpoint.desc; struct dwc3 *dwc = dep->dwc; @@ -625,12 +618,12 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, int ret; if (!(dep->flags & DWC3_EP_ENABLED)) { - ret = dwc3_gadget_start_config(dwc, dep); + ret = dwc3_gadget_start_config(dep); if (ret) return ret; } - ret = dwc3_gadget_set_ep_config(dwc, dep, modify, restore); + ret = dwc3_gadget_set_ep_config(dep, action); if (ret) return ret; @@ -671,7 +664,8 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, * Issue StartTransfer here with no-op TRB so we can always rely on No * Response Update Transfer command. */ - if (usb_endpoint_xfer_bulk(desc)) { + if (usb_endpoint_xfer_bulk(desc) || + usb_endpoint_xfer_int(desc)) { struct dwc3_gadget_ep_cmd_params params; struct dwc3_trb *trb; dma_addr_t trb_dma; @@ -689,26 +683,20 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, ret = dwc3_send_gadget_ep_cmd(dep, cmd, ¶ms); if (ret < 0) return ret; - - dep->flags |= DWC3_EP_BUSY; - - dep->resource_index = dwc3_gadget_ep_get_transfer_index(dep); - WARN_ON_ONCE(!dep->resource_index); } - out: trace_dwc3_gadget_ep_enable(dep); return 0; } -static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum, bool force); +static void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force); static void dwc3_remove_requests(struct dwc3 *dwc, struct dwc3_ep *dep) { struct dwc3_request *req; - dwc3_stop_active_transfer(dwc, dep->number, true); + dwc3_stop_active_transfer(dep, true); /* - giveback all requests to gadget driver */ while (!list_empty(&dep->started_list)) { @@ -806,7 +794,7 @@ static int dwc3_gadget_ep_enable(struct usb_ep *ep, return 0; spin_lock_irqsave(&dwc->lock, flags); - ret = __dwc3_gadget_ep_enable(dep, false, false); + ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT); spin_unlock_irqrestore(&dwc->lock, flags); return ret; @@ -840,7 +828,7 @@ static int dwc3_gadget_ep_disable(struct usb_ep *ep) } static struct usb_request *dwc3_gadget_ep_alloc_request(struct usb_ep *ep, - gfp_t gfp_flags) + gfp_t gfp_flags) { struct dwc3_request *req; struct dwc3_ep *dep = to_dwc3_ep(ep); @@ -849,11 +837,10 @@ static struct usb_request *dwc3_gadget_ep_alloc_request(struct usb_ep *ep, if (!req) return NULL; + req->direction = dep->direction; req->epnum = dep->number; req->dep = dep; - dep->allocated_requests++; - trace_dwc3_alloc_request(req); return &req->request; @@ -863,14 +850,58 @@ static void dwc3_gadget_ep_free_request(struct usb_ep *ep, struct usb_request *request) { struct dwc3_request *req = to_dwc3_request(request); - struct dwc3_ep *dep = to_dwc3_ep(ep); - dep->allocated_requests--; trace_dwc3_free_request(req); kfree(req); } -static u32 dwc3_calc_trbs_left(struct dwc3_ep *dep); +/** + * dwc3_ep_prev_trb - returns the previous TRB in the ring + * @dep: The endpoint with the TRB ring + * @index: The index of the current TRB in the ring + * + * Returns the TRB prior to the one pointed to by the index. If the + * index is 0, we will wrap backwards, skip the link TRB, and return + * the one just before that. + */ +static struct dwc3_trb *dwc3_ep_prev_trb(struct dwc3_ep *dep, u8 index) +{ + u8 tmp = index; + + if (!tmp) + tmp = DWC3_TRB_NUM - 1; + + return &dep->trb_pool[tmp - 1]; +} + +static u32 dwc3_calc_trbs_left(struct dwc3_ep *dep) +{ + struct dwc3_trb *tmp; + u8 trbs_left; + + /* + * If enqueue & dequeue are equal than it is either full or empty. + * + * One way to know for sure is if the TRB right before us has HWO bit + * set or not. If it has, then we're definitely full and can't fit any + * more transfers in our ring. + */ + if (dep->trb_enqueue == dep->trb_dequeue) { + tmp = dwc3_ep_prev_trb(dep, dep->trb_enqueue); + if (tmp->ctrl & DWC3_TRB_CTRL_HWO) + return 0; + + return DWC3_TRB_NUM - 1; + } + + trbs_left = dep->trb_dequeue - dep->trb_enqueue; + trbs_left &= (DWC3_TRB_NUM - 1); + + if (dep->trb_dequeue < dep->trb_enqueue) + trbs_left--; + + return trbs_left; +} static void __dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_trb *trb, dma_addr_t dma, unsigned length, unsigned chain, unsigned node, @@ -985,11 +1016,19 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_request *req, unsigned chain, unsigned node) { struct dwc3_trb *trb; - unsigned length = req->request.length; + unsigned int length; + dma_addr_t dma; unsigned stream_id = req->request.stream_id; unsigned short_not_ok = req->request.short_not_ok; unsigned no_interrupt = req->request.no_interrupt; - dma_addr_t dma = req->request.dma; + + if (req->request.num_sgs > 0) { + length = sg_dma_len(req->start_sg); + dma = sg_dma_address(req->start_sg); + } else { + length = req->request.length; + dma = req->request.dma; + } trb = &dep->trb_pool[dep->trb_enqueue]; @@ -997,69 +1036,23 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep, dwc3_gadget_move_started_request(req); req->trb = trb; req->trb_dma = dwc3_trb_dma_offset(dep, trb); - dep->queued_requests++; } __dwc3_prepare_one_trb(dep, trb, dma, length, chain, node, stream_id, short_not_ok, no_interrupt); } -/** - * dwc3_ep_prev_trb - returns the previous TRB in the ring - * @dep: The endpoint with the TRB ring - * @index: The index of the current TRB in the ring - * - * Returns the TRB prior to the one pointed to by the index. If the - * index is 0, we will wrap backwards, skip the link TRB, and return - * the one just before that. - */ -static struct dwc3_trb *dwc3_ep_prev_trb(struct dwc3_ep *dep, u8 index) -{ - u8 tmp = index; - - if (!tmp) - tmp = DWC3_TRB_NUM - 1; - - return &dep->trb_pool[tmp - 1]; -} - -static u32 dwc3_calc_trbs_left(struct dwc3_ep *dep) -{ - struct dwc3_trb *tmp; - u8 trbs_left; - - /* - * If enqueue & dequeue are equal than it is either full or empty. - * - * One way to know for sure is if the TRB right before us has HWO bit - * set or not. If it has, then we're definitely full and can't fit any - * more transfers in our ring. - */ - if (dep->trb_enqueue == dep->trb_dequeue) { - tmp = dwc3_ep_prev_trb(dep, dep->trb_enqueue); - if (tmp->ctrl & DWC3_TRB_CTRL_HWO) - return 0; - - return DWC3_TRB_NUM - 1; - } - - trbs_left = dep->trb_dequeue - dep->trb_enqueue; - trbs_left &= (DWC3_TRB_NUM - 1); - - if (dep->trb_dequeue < dep->trb_enqueue) - trbs_left--; - - return trbs_left; -} - static void dwc3_prepare_one_trb_sg(struct dwc3_ep *dep, struct dwc3_request *req) { - struct scatterlist *sg = req->sg; + struct scatterlist *sg = req->start_sg; struct scatterlist *s; int i; - for_each_sg(sg, s, req->num_pending_sgs, i) { + unsigned int remaining = req->request.num_mapped_sgs + - req->num_queued_sgs; + + for_each_sg(sg, s, remaining, i) { unsigned int length = req->request.length; unsigned int maxp = usb_endpoint_maxp(dep->endpoint.desc); unsigned int rem = length % maxp; @@ -1088,6 +1081,18 @@ static void dwc3_prepare_one_trb_sg(struct dwc3_ep *dep, dwc3_prepare_one_trb(dep, req, chain, i); } + /* + * There can be a situation where all sgs in sglist are not + * queued because of insufficient trb number. To handle this + * case, update start_sg to next sg to be queued, so that + * we have free trbs we can continue queuing from where we + * previously stopped + */ + if (chain) + req->start_sg = sg_next(s); + + req->num_queued_sgs++; + if (!dwc3_calc_trbs_left(dep)) break; } @@ -1178,6 +1183,8 @@ static void dwc3_prepare_trbs(struct dwc3_ep *dep) return; req->sg = req->request.sg; + req->start_sg = req->sg; + req->num_queued_sgs = 0; req->num_pending_sgs = req->request.num_mapped_sgs; if (req->num_pending_sgs > 0) @@ -1201,7 +1208,7 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep) if (!dwc3_calc_trbs_left(dep)) return 0; - starting = !(dep->flags & DWC3_EP_BUSY); + starting = !(dep->flags & DWC3_EP_TRANSFER_STARTED); dwc3_prepare_trbs(dep); req = next_request(&dep->started_list); @@ -1233,18 +1240,10 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep) */ if (req->trb) memset(req->trb, 0, sizeof(struct dwc3_trb)); - dep->queued_requests--; dwc3_gadget_del_and_unmap_request(dep, req, ret); return ret; } - dep->flags |= DWC3_EP_BUSY; - - if (starting) { - dep->resource_index = dwc3_gadget_ep_get_transfer_index(dep); - WARN_ON_ONCE(!dep->resource_index); - } - return 0; } @@ -1256,35 +1255,19 @@ static int __dwc3_gadget_get_frame(struct dwc3 *dwc) return DWC3_DSTS_SOFFN(reg); } -static void __dwc3_gadget_start_isoc(struct dwc3 *dwc, - struct dwc3_ep *dep, u32 cur_uf) +static void __dwc3_gadget_start_isoc(struct dwc3_ep *dep) { if (list_empty(&dep->pending_list)) { - dev_info(dwc->dev, "%s: ran out of requests\n", + dev_info(dep->dwc->dev, "%s: ran out of requests\n", dep->name); dep->flags |= DWC3_EP_PENDING_REQUEST; return; } - /* - * Schedule the first trb for one interval in the future or at - * least 4 microframes. - */ - dep->frame_number = cur_uf + max_t(u32, 4, dep->interval); + dep->frame_number = DWC3_ALIGN_FRAME(dep); __dwc3_gadget_kick_transfer(dep); } -static void dwc3_gadget_start_isoc(struct dwc3 *dwc, - struct dwc3_ep *dep, const struct dwc3_event_depevt *event) -{ - u32 cur_uf, mask; - - mask = ~(dep->interval - 1); - cur_uf = event->parameters & mask; - - __dwc3_gadget_start_isoc(dwc, dep, cur_uf); -} - static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) { struct dwc3 *dwc = dep->dwc; @@ -1303,8 +1286,6 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) req->request.actual = 0; req->request.status = -EINPROGRESS; - req->direction = dep->direction; - req->epnum = dep->number; trace_dwc3_ep_queue(req); @@ -1319,28 +1300,18 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) * errors which will force us issue EndTransfer command. */ if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { - if ((dep->flags & DWC3_EP_PENDING_REQUEST)) { - if (dep->flags & DWC3_EP_TRANSFER_STARTED) { - dwc3_stop_active_transfer(dwc, dep->number, true); - dep->flags = DWC3_EP_ENABLED; - } else { - u32 cur_uf; + if (!(dep->flags & DWC3_EP_PENDING_REQUEST) && + !(dep->flags & DWC3_EP_TRANSFER_STARTED)) + return 0; - cur_uf = __dwc3_gadget_get_frame(dwc); - __dwc3_gadget_start_isoc(dwc, dep, cur_uf); - dep->flags &= ~DWC3_EP_PENDING_REQUEST; + if ((dep->flags & DWC3_EP_PENDING_REQUEST)) { + if (!(dep->flags & DWC3_EP_TRANSFER_STARTED)) { + __dwc3_gadget_start_isoc(dep); + return 0; } - return 0; } - - if ((dep->flags & DWC3_EP_BUSY) && - !(dep->flags & DWC3_EP_MISSED_ISOC)) - goto out; - - return 0; } -out: return __dwc3_gadget_kick_transfer(dep); } @@ -1390,7 +1361,7 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep, } if (r == req) { /* wait until it is processed */ - dwc3_stop_active_transfer(dwc, dep->number, true); + dwc3_stop_active_transfer(dep, true); /* * If request was already started, this means we had to @@ -1463,7 +1434,7 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep, out1: /* giveback the request */ - dep->queued_requests--; + dwc3_gadget_giveback(dep, req, -ECONNRESET); out0: @@ -1878,14 +1849,14 @@ static int __dwc3_gadget_start(struct dwc3 *dwc) dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); dep = dwc->eps[0]; - ret = __dwc3_gadget_ep_enable(dep, false, false); + ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT); if (ret) { dev_err(dwc->dev, "failed to enable %s\n", dep->name); goto err0; } dep = dwc->eps[1]; - ret = __dwc3_gadget_ep_enable(dep, false, false); + ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT); if (ret) { dev_err(dwc->dev, "failed to enable %s\n", dep->name); goto err1; @@ -2082,113 +2053,142 @@ static const struct usb_gadget_ops dwc3_gadget_ops = { /* -------------------------------------------------------------------------- */ -static int dwc3_gadget_init_endpoints(struct dwc3 *dwc, u8 total) +static int dwc3_gadget_init_control_endpoint(struct dwc3_ep *dep) { - struct dwc3_ep *dep; - u8 epnum; + struct dwc3 *dwc = dep->dwc; - INIT_LIST_HEAD(&dwc->gadget.ep_list); + usb_ep_set_maxpacket_limit(&dep->endpoint, 512); + dep->endpoint.maxburst = 1; + dep->endpoint.ops = &dwc3_gadget_ep0_ops; + if (!dep->direction) + dwc->gadget.ep0 = &dep->endpoint; - for (epnum = 0; epnum < total; epnum++) { - bool direction = epnum & 1; - u8 num = epnum >> 1; + dep->endpoint.caps.type_control = true; - dep = kzalloc(sizeof(*dep), GFP_KERNEL); - if (!dep) - return -ENOMEM; + return 0; +} - dep->dwc = dwc; - dep->number = epnum; - dep->direction = direction; - dep->regs = dwc->regs + DWC3_DEP_BASE(epnum); - dwc->eps[epnum] = dep; +static int dwc3_gadget_init_in_endpoint(struct dwc3_ep *dep) +{ + struct dwc3 *dwc = dep->dwc; + int mdwidth; + int kbytes; + int size; - snprintf(dep->name, sizeof(dep->name), "ep%u%s", num, - direction ? "in" : "out"); + mdwidth = DWC3_MDWIDTH(dwc->hwparams.hwparams0); + /* MDWIDTH is represented in bits, we need it in bytes */ + mdwidth /= 8; - dep->endpoint.name = dep->name; + size = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(dep->number >> 1)); + if (dwc3_is_usb31(dwc)) + size = DWC31_GTXFIFOSIZ_TXFDEF(size); + else + size = DWC3_GTXFIFOSIZ_TXFDEF(size); - if (!(dep->number > 1)) { - dep->endpoint.desc = &dwc3_gadget_ep0_desc; - dep->endpoint.comp_desc = NULL; - } + /* FIFO Depth is in MDWDITH bytes. Multiply */ + size *= mdwidth; - spin_lock_init(&dep->lock); - - if (num == 0) { - usb_ep_set_maxpacket_limit(&dep->endpoint, 512); - dep->endpoint.maxburst = 1; - dep->endpoint.ops = &dwc3_gadget_ep0_ops; - if (!direction) - dwc->gadget.ep0 = &dep->endpoint; - } else if (direction) { - int mdwidth; - int kbytes; - int size; - int ret; - - mdwidth = DWC3_MDWIDTH(dwc->hwparams.hwparams0); - /* MDWIDTH is represented in bits, we need it in bytes */ - mdwidth /= 8; - - size = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(num)); - if (dwc3_is_usb31(dwc)) - size = DWC31_GTXFIFOSIZ_TXFDEF(size); - else - size = DWC3_GTXFIFOSIZ_TXFDEF(size); + kbytes = size / 1024; + if (kbytes == 0) + kbytes = 1; - /* FIFO Depth is in MDWDITH bytes. Multiply */ - size *= mdwidth; + /* + * FIFO sizes account an extra MDWIDTH * (kbytes + 1) bytes for + * internal overhead. We don't really know how these are used, + * but documentation say it exists. + */ + size -= mdwidth * (kbytes + 1); + size /= kbytes; - kbytes = size / 1024; - if (kbytes == 0) - kbytes = 1; + usb_ep_set_maxpacket_limit(&dep->endpoint, size); - /* - * FIFO sizes account an extra MDWIDTH * (kbytes + 1) bytes for - * internal overhead. We don't really know how these are used, - * but documentation say it exists. - */ - size -= mdwidth * (kbytes + 1); - size /= kbytes; + dep->endpoint.max_streams = 15; + dep->endpoint.ops = &dwc3_gadget_ep_ops; + list_add_tail(&dep->endpoint.ep_list, + &dwc->gadget.ep_list); + dep->endpoint.caps.type_iso = true; + dep->endpoint.caps.type_bulk = true; + dep->endpoint.caps.type_int = true; - usb_ep_set_maxpacket_limit(&dep->endpoint, size); + return dwc3_alloc_trb_pool(dep); +} - dep->endpoint.max_streams = 15; - dep->endpoint.ops = &dwc3_gadget_ep_ops; - list_add_tail(&dep->endpoint.ep_list, - &dwc->gadget.ep_list); +static int dwc3_gadget_init_out_endpoint(struct dwc3_ep *dep) +{ + struct dwc3 *dwc = dep->dwc; - ret = dwc3_alloc_trb_pool(dep); - if (ret) - return ret; - } else { - int ret; + usb_ep_set_maxpacket_limit(&dep->endpoint, 1024); + dep->endpoint.max_streams = 15; + dep->endpoint.ops = &dwc3_gadget_ep_ops; + list_add_tail(&dep->endpoint.ep_list, + &dwc->gadget.ep_list); + dep->endpoint.caps.type_iso = true; + dep->endpoint.caps.type_bulk = true; + dep->endpoint.caps.type_int = true; - usb_ep_set_maxpacket_limit(&dep->endpoint, 1024); - dep->endpoint.max_streams = 15; - dep->endpoint.ops = &dwc3_gadget_ep_ops; - list_add_tail(&dep->endpoint.ep_list, - &dwc->gadget.ep_list); + return dwc3_alloc_trb_pool(dep); +} - ret = dwc3_alloc_trb_pool(dep); - if (ret) - return ret; - } +static int dwc3_gadget_init_endpoint(struct dwc3 *dwc, u8 epnum) +{ + struct dwc3_ep *dep; + bool direction = epnum & 1; + int ret; + u8 num = epnum >> 1; - if (num == 0) { - dep->endpoint.caps.type_control = true; - } else { - dep->endpoint.caps.type_iso = true; - dep->endpoint.caps.type_bulk = true; - dep->endpoint.caps.type_int = true; - } + dep = kzalloc(sizeof(*dep), GFP_KERNEL); + if (!dep) + return -ENOMEM; - dep->endpoint.caps.dir_in = direction; - dep->endpoint.caps.dir_out = !direction; + dep->dwc = dwc; + dep->number = epnum; + dep->direction = direction; + dep->regs = dwc->regs + DWC3_DEP_BASE(epnum); + dwc->eps[epnum] = dep; - INIT_LIST_HEAD(&dep->pending_list); - INIT_LIST_HEAD(&dep->started_list); + snprintf(dep->name, sizeof(dep->name), "ep%u%s", num, + direction ? "in" : "out"); + + dep->endpoint.name = dep->name; + + if (!(dep->number > 1)) { + dep->endpoint.desc = &dwc3_gadget_ep0_desc; + dep->endpoint.comp_desc = NULL; + } + + spin_lock_init(&dep->lock); + + if (num == 0) + ret = dwc3_gadget_init_control_endpoint(dep); + else if (direction) + ret = dwc3_gadget_init_in_endpoint(dep); + else + ret = dwc3_gadget_init_out_endpoint(dep); + + if (ret) + return ret; + + dep->endpoint.caps.dir_in = direction; + dep->endpoint.caps.dir_out = !direction; + + INIT_LIST_HEAD(&dep->pending_list); + INIT_LIST_HEAD(&dep->started_list); + + return 0; +} + +static int dwc3_gadget_init_endpoints(struct dwc3 *dwc, u8 total) +{ + u8 epnum; + + INIT_LIST_HEAD(&dwc->gadget.ep_list); + + for (epnum = 0; epnum < total; epnum++) { + int ret; + + ret = dwc3_gadget_init_endpoint(dwc, epnum); + if (ret) + return ret; } return 0; @@ -2223,20 +2223,14 @@ static void dwc3_gadget_free_endpoints(struct dwc3 *dwc) /* -------------------------------------------------------------------------- */ -static int __dwc3_cleanup_done_trbs(struct dwc3 *dwc, struct dwc3_ep *dep, +static int dwc3_gadget_ep_reclaim_completed_trb(struct dwc3_ep *dep, struct dwc3_request *req, struct dwc3_trb *trb, - const struct dwc3_event_depevt *event, int status, - int chain) + const struct dwc3_event_depevt *event, int status, int chain) { unsigned int count; - unsigned int s_pkt = 0; - unsigned int trb_status; dwc3_ep_inc_deq(dep); - if (req->trb == trb) - dep->queued_requests--; - trace_dwc3_complete_trb(dep, trb); /* @@ -2268,159 +2262,140 @@ static int __dwc3_cleanup_done_trbs(struct dwc3 *dwc, struct dwc3_ep *dep, if ((trb->ctrl & DWC3_TRB_CTRL_HWO) && status != -ESHUTDOWN) return 1; - if (dep->direction) { - if (count) { - trb_status = DWC3_TRB_SIZE_TRBSTS(trb->size); - if (trb_status == DWC3_TRBSTS_MISSED_ISOC) { - /* - * If missed isoc occurred and there is - * no request queued then issue END - * TRANSFER, so that core generates - * next xfernotready and we will issue - * a fresh START TRANSFER. - * If there are still queued request - * then wait, do not issue either END - * or UPDATE TRANSFER, just attach next - * request in pending_list during - * giveback.If any future queued request - * is successfully transferred then we - * will issue UPDATE TRANSFER for all - * request in the pending_list. - */ - dep->flags |= DWC3_EP_MISSED_ISOC; - } else { - dev_err(dwc->dev, "incomplete IN transfer %s\n", - dep->name); - status = -ECONNRESET; - } - } else { - dep->flags &= ~DWC3_EP_MISSED_ISOC; - } - } else { - if (count && (event->status & DEPEVT_STATUS_SHORT)) - s_pkt = 1; - } - - if (s_pkt && !chain) + if (event->status & DEPEVT_STATUS_SHORT && !chain) return 1; - if ((event->status & DEPEVT_STATUS_IOC) && - (trb->ctrl & DWC3_TRB_CTRL_IOC)) + if (event->status & DEPEVT_STATUS_IOC) return 1; return 0; } -static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep, - const struct dwc3_event_depevt *event, int status) +static int dwc3_gadget_ep_reclaim_trb_sg(struct dwc3_ep *dep, + struct dwc3_request *req, const struct dwc3_event_depevt *event, + int status) { - struct dwc3_request *req, *n; - struct dwc3_trb *trb; - bool ioc = false; - int ret = 0; + struct dwc3_trb *trb = &dep->trb_pool[dep->trb_dequeue]; + struct scatterlist *sg = req->sg; + struct scatterlist *s; + unsigned int pending = req->num_pending_sgs; + unsigned int i; + int ret = 0; - list_for_each_entry_safe(req, n, &dep->started_list, list) { - unsigned length; - int chain; + for_each_sg(sg, s, pending, i) { + trb = &dep->trb_pool[dep->trb_dequeue]; - length = req->request.length; - chain = req->num_pending_sgs > 0; - if (chain) { - struct scatterlist *sg = req->sg; - struct scatterlist *s; - unsigned int pending = req->num_pending_sgs; - unsigned int i; - - for_each_sg(sg, s, pending, i) { - trb = &dep->trb_pool[dep->trb_dequeue]; - - if (trb->ctrl & DWC3_TRB_CTRL_HWO) - break; - - req->sg = sg_next(s); - req->num_pending_sgs--; - - ret = __dwc3_cleanup_done_trbs(dwc, dep, req, trb, - event, status, chain); - if (ret) - break; - } - } else { - trb = &dep->trb_pool[dep->trb_dequeue]; - ret = __dwc3_cleanup_done_trbs(dwc, dep, req, trb, - event, status, chain); - } + if (trb->ctrl & DWC3_TRB_CTRL_HWO) + break; - if (req->unaligned || req->zero) { - trb = &dep->trb_pool[dep->trb_dequeue]; - ret = __dwc3_cleanup_done_trbs(dwc, dep, req, trb, - event, status, false); - req->unaligned = false; - req->zero = false; - } + req->sg = sg_next(s); + req->num_pending_sgs--; - req->request.actual = length - req->remaining; + ret = dwc3_gadget_ep_reclaim_completed_trb(dep, req, + trb, event, status, true); + if (ret) + break; + } + + return ret; +} - if ((req->request.actual < length) && req->num_pending_sgs) - return __dwc3_gadget_kick_transfer(dep); +static int dwc3_gadget_ep_reclaim_trb_linear(struct dwc3_ep *dep, + struct dwc3_request *req, const struct dwc3_event_depevt *event, + int status) +{ + struct dwc3_trb *trb = &dep->trb_pool[dep->trb_dequeue]; - dwc3_gadget_giveback(dep, req, status); + return dwc3_gadget_ep_reclaim_completed_trb(dep, req, trb, + event, status, false); +} - if (ret) { - if ((event->status & DEPEVT_STATUS_IOC) && - (trb->ctrl & DWC3_TRB_CTRL_IOC)) - ioc = true; - break; - } +static bool dwc3_gadget_ep_request_completed(struct dwc3_request *req) +{ + return req->request.actual == req->request.length; +} + +static int dwc3_gadget_ep_cleanup_completed_request(struct dwc3_ep *dep, + const struct dwc3_event_depevt *event, + struct dwc3_request *req, int status) +{ + int ret; + + if (req->num_pending_sgs) + ret = dwc3_gadget_ep_reclaim_trb_sg(dep, req, event, + status); + else + ret = dwc3_gadget_ep_reclaim_trb_linear(dep, req, event, + status); + + if (req->unaligned || req->zero) { + ret = dwc3_gadget_ep_reclaim_trb_linear(dep, req, event, + status); + req->unaligned = false; + req->zero = false; } - /* - * Our endpoint might get disabled by another thread during - * dwc3_gadget_giveback(). If that happens, we're just gonna return 1 - * early on so DWC3_EP_BUSY flag gets cleared - */ - if (!dep->endpoint.desc) - return 1; + req->request.actual = req->request.length - req->remaining; - if (usb_endpoint_xfer_isoc(dep->endpoint.desc) && - list_empty(&dep->started_list)) { - if (list_empty(&dep->pending_list)) { - /* - * If there is no entry in request list then do - * not issue END TRANSFER now. Just set PENDING - * flag, so that END TRANSFER is issued when an - * entry is added into request list. - */ - dep->flags = DWC3_EP_PENDING_REQUEST; - } else { - dwc3_stop_active_transfer(dwc, dep->number, true); - dep->flags = DWC3_EP_ENABLED; - } - return 1; + if (!dwc3_gadget_ep_request_completed(req) && + req->num_pending_sgs) { + __dwc3_gadget_kick_transfer(dep); + goto out; } - if (usb_endpoint_xfer_isoc(dep->endpoint.desc) && ioc) - return 0; + dwc3_gadget_giveback(dep, req, status); + +out: + return ret; +} + +static void dwc3_gadget_ep_cleanup_completed_requests(struct dwc3_ep *dep, + const struct dwc3_event_depevt *event, int status) +{ + struct dwc3_request *req; + struct dwc3_request *tmp; + + list_for_each_entry_safe(req, tmp, &dep->started_list, list) { + int ret; + + ret = dwc3_gadget_ep_cleanup_completed_request(dep, event, + req, status); + if (ret) + break; + } +} - return 1; +static void dwc3_gadget_endpoint_frame_from_event(struct dwc3_ep *dep, + const struct dwc3_event_depevt *event) +{ + dep->frame_number = event->parameters; } -static void dwc3_endpoint_transfer_complete(struct dwc3 *dwc, - struct dwc3_ep *dep, const struct dwc3_event_depevt *event) +static void dwc3_gadget_endpoint_transfer_in_progress(struct dwc3_ep *dep, + const struct dwc3_event_depevt *event) { + struct dwc3 *dwc = dep->dwc; unsigned status = 0; - int clean_busy; - u32 is_xfer_complete; + bool stop = false; - is_xfer_complete = (event->endpoint_event == DWC3_DEPEVT_XFERCOMPLETE); + dwc3_gadget_endpoint_frame_from_event(dep, event); if (event->status & DEPEVT_STATUS_BUSERR) status = -ECONNRESET; - clean_busy = dwc3_cleanup_done_reqs(dwc, dep, event, status); - if (clean_busy && (!dep->endpoint.desc || is_xfer_complete || - usb_endpoint_xfer_isoc(dep->endpoint.desc))) - dep->flags &= ~DWC3_EP_BUSY; + if (event->status & DEPEVT_STATUS_MISSED_ISOC) { + status = -EXDEV; + + if (list_empty(&dep->started_list)) + stop = true; + } + + dwc3_gadget_ep_cleanup_completed_requests(dep, event, status); + + if (stop) { + dwc3_stop_active_transfer(dep, true); + dep->flags = DWC3_EP_ENABLED; + } /* * WORKAROUND: This is the 2nd half of U1/U2 -> U0 workaround. @@ -2446,17 +2421,13 @@ static void dwc3_endpoint_transfer_complete(struct dwc3 *dwc, dwc->u1u2 = 0; } +} - /* - * Our endpoint might get disabled by another thread during - * dwc3_gadget_giveback(). If that happens, we're just gonna return 1 - * early on so DWC3_EP_BUSY flag gets cleared - */ - if (!dep->endpoint.desc) - return; - - if (!usb_endpoint_xfer_isoc(dep->endpoint.desc)) - __dwc3_gadget_kick_transfer(dep); +static void dwc3_gadget_endpoint_transfer_not_ready(struct dwc3_ep *dep, + const struct dwc3_event_depevt *event) +{ + dwc3_gadget_endpoint_frame_from_event(dep, event); + __dwc3_gadget_start_isoc(dep); } static void dwc3_endpoint_interrupt(struct dwc3 *dwc, @@ -2483,32 +2454,11 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, } switch (event->endpoint_event) { - case DWC3_DEPEVT_XFERCOMPLETE: - dep->resource_index = 0; - - if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { - dev_err(dwc->dev, "XferComplete for Isochronous endpoint\n"); - return; - } - - dwc3_endpoint_transfer_complete(dwc, dep, event); - break; case DWC3_DEPEVT_XFERINPROGRESS: - dwc3_endpoint_transfer_complete(dwc, dep, event); + dwc3_gadget_endpoint_transfer_in_progress(dep, event); break; case DWC3_DEPEVT_XFERNOTREADY: - if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) - dwc3_gadget_start_isoc(dwc, dep, event); - else - __dwc3_gadget_kick_transfer(dep); - - break; - case DWC3_DEPEVT_STREAMEVT: - if (!usb_endpoint_xfer_bulk(dep->endpoint.desc)) { - dev_err(dwc->dev, "Stream event for non-Bulk %s\n", - dep->name); - return; - } + dwc3_gadget_endpoint_transfer_not_ready(dep, event); break; case DWC3_DEPEVT_EPCMDCMPLT: cmd = DEPEVT_PARAMETER_CMD(event->parameters); @@ -2518,6 +2468,8 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, wake_up(&dep->wait_end_transfer); } break; + case DWC3_DEPEVT_STREAMEVT: + case DWC3_DEPEVT_XFERCOMPLETE: case DWC3_DEPEVT_RXTXFIFOEVT: break; } @@ -2562,15 +2514,13 @@ static void dwc3_reset_gadget(struct dwc3 *dwc) } } -static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum, bool force) +static void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force) { - struct dwc3_ep *dep; + struct dwc3 *dwc = dep->dwc; struct dwc3_gadget_ep_cmd_params params; u32 cmd; int ret; - dep = dwc->eps[epnum]; - if ((dep->flags & DWC3_EP_END_TRANSFER_PENDING) || !dep->resource_index) return; @@ -2614,7 +2564,6 @@ static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum, bool force) ret = dwc3_send_gadget_ep_cmd(dep, cmd, ¶ms); WARN_ON_ONCE(ret); dep->resource_index = 0; - dep->flags &= ~DWC3_EP_BUSY; if (dwc3_is_usb31(dwc) || dwc->revision < DWC3_REVISION_310A) { dep->flags |= DWC3_EP_END_TRANSFER_PENDING; @@ -2816,14 +2765,14 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc) } dep = dwc->eps[0]; - ret = __dwc3_gadget_ep_enable(dep, true, false); + ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_MODIFY); if (ret) { dev_err(dwc->dev, "failed to enable %s\n", dep->name); return; } dep = dwc->eps[1]; - ret = __dwc3_gadget_ep_enable(dep, true, false); + ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_MODIFY); if (ret) { dev_err(dwc->dev, "failed to enable %s\n", dep->name); return; diff --git a/drivers/usb/dwc3/gadget.h b/drivers/usb/dwc3/gadget.h index 578aa856f986..db610c56f1d6 100644 --- a/drivers/usb/dwc3/gadget.h +++ b/drivers/usb/dwc3/gadget.h @@ -98,13 +98,12 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol); * Caller should take care of locking. Returns the transfer resource * index for a given endpoint. */ -static inline u32 dwc3_gadget_ep_get_transfer_index(struct dwc3_ep *dep) +static inline void dwc3_gadget_ep_get_transfer_index(struct dwc3_ep *dep) { u32 res_id; res_id = dwc3_readl(dep->regs, DWC3_DEPCMD); - - return DWC3_DEPCMD_GET_RSC_IDX(res_id); + dep->resource_index = DWC3_DEPCMD_GET_RSC_IDX(res_id); } #endif /* __DRIVERS_USB_DWC3_GADGET_H */ diff --git a/drivers/usb/dwc3/trace.h b/drivers/usb/dwc3/trace.h index babaee981aa7..f22714cce070 100644 --- a/drivers/usb/dwc3/trace.h +++ b/drivers/usb/dwc3/trace.h @@ -230,17 +230,14 @@ DECLARE_EVENT_CLASS(dwc3_log_trb, TP_fast_assign( __assign_str(name, dep->name); __entry->trb = trb; - __entry->allocated = dep->allocated_requests; - __entry->queued = dep->queued_requests; __entry->bpl = trb->bpl; __entry->bph = trb->bph; __entry->size = trb->size; __entry->ctrl = trb->ctrl; __entry->type = usb_endpoint_type(dep->endpoint.desc); ), - TP_printk("%s: %d/%d trb %p buf %08x%08x size %s%d ctrl %08x (%c%c%c%c:%c%c:%s)", - __get_str(name), __entry->queued, __entry->allocated, - __entry->trb, __entry->bph, __entry->bpl, + TP_printk("%s: trb %p buf %08x%08x size %s%d ctrl %08x (%c%c%c%c:%c%c:%s)", + __get_str(name), __entry->trb, __entry->bph, __entry->bpl, ({char *s; int pcm = ((__entry->size >> 24) & 3) + 1; switch (__entry->type) { @@ -306,7 +303,7 @@ DECLARE_EVENT_CLASS(dwc3_log_ep, __entry->trb_enqueue = dep->trb_enqueue; __entry->trb_dequeue = dep->trb_dequeue; ), - TP_printk("%s: mps %d/%d streams %d burst %d ring %d/%d flags %c:%c%c%c%c%c:%c:%c", + TP_printk("%s: mps %d/%d streams %d burst %d ring %d/%d flags %c:%c%c%c%c:%c:%c", __get_str(name), __entry->maxpacket, __entry->maxpacket_limit, __entry->max_streams, __entry->maxburst, __entry->trb_enqueue, @@ -314,9 +311,8 @@ DECLARE_EVENT_CLASS(dwc3_log_ep, __entry->flags & DWC3_EP_ENABLED ? 'E' : 'e', __entry->flags & DWC3_EP_STALL ? 'S' : 's', __entry->flags & DWC3_EP_WEDGE ? 'W' : 'w', - __entry->flags & DWC3_EP_BUSY ? 'B' : 'b', + __entry->flags & DWC3_EP_TRANSFER_STARTED ? 'B' : 'b', __entry->flags & DWC3_EP_PENDING_REQUEST ? 'P' : 'p', - __entry->flags & DWC3_EP_MISSED_ISOC ? 'M' : 'm', __entry->flags & DWC3_EP_END_TRANSFER_PENDING ? 'E' : 'e', __entry->direction ? '<' : '>' ) |