diff options
-rw-r--r-- | Documentation/devicetree/bindings/usb/fsl,imx8mp-dwc3.yaml | 105 | ||||
-rw-r--r-- | arch/arm64/boot/dts/freescale/imx8mp-evk.dts | 21 | ||||
-rw-r--r-- | arch/arm64/boot/dts/freescale/imx8mp.dtsi | 82 | ||||
-rw-r--r-- | drivers/usb/class/usbtmc.c | 85 | ||||
-rw-r--r-- | drivers/usb/dwc3/Kconfig | 10 | ||||
-rw-r--r-- | drivers/usb/dwc3/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/dwc3/core.h | 1 | ||||
-rw-r--r-- | drivers/usb/dwc3/drd.c | 25 | ||||
-rw-r--r-- | drivers/usb/dwc3/dwc3-imx8mp.c | 363 | ||||
-rw-r--r-- | drivers/usb/dwc3/gadget.c | 128 | ||||
-rw-r--r-- | drivers/usb/gadget/composite.c | 21 | ||||
-rw-r--r-- | drivers/usb/gadget/configfs.c | 24 | ||||
-rw-r--r-- | drivers/usb/gadget/function/f_printer.c | 5 | ||||
-rw-r--r-- | drivers/usb/gadget/function/u_serial.c | 4 | ||||
-rw-r--r-- | drivers/usb/usbip/stub_main.c | 4 | ||||
-rw-r--r-- | drivers/usb/usbip/usbip_common.h | 29 | ||||
-rw-r--r-- | drivers/usb/usbip/vhci_rx.c | 2 | ||||
-rw-r--r-- | drivers/usb/usbip/vhci_sysfs.c | 1 | ||||
-rw-r--r-- | include/linux/usb/composite.h | 2 | ||||
-rw-r--r-- | include/uapi/linux/usb/tmc.h | 3 |
20 files changed, 827 insertions, 89 deletions
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 <jun.li@nxp.com> + +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 <dt-bindings/clock/imx8mp-clock.h> + #include <dt-bindings/interrupt-controller/arm-gic.h> + 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 = <GIC_SPI 148 IRQ_TYPE_LEVEL_HIGH>; + #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 = <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>; + phys = <&usb3_phy0>, <&usb3_phy0>; + phy-names = "usb2-phy", "usb3-phy"; + snps,dis-u2-freeclk-exists-quirk; + }; + }; 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 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 = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>; }; + + 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 = <GIC_SPI 148 IRQ_TYPE_LEVEL_HIGH>; + #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 = <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>; + 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 = <GIC_SPI 149 IRQ_TYPE_LEVEL_HIGH>; + #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 = <GIC_SPI 41 IRQ_TYPE_LEVEL_HIGH>; + phys = <&usb3_phy1>, <&usb3_phy1>; + phy-names = "usb2-phy", "usb3-phy"; + snps,dis-u2-freeclk-exists-quirk; + }; + }; }; }; diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index b222b777e6a4..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 @@ -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,53 @@ 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 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) { @@ -2145,6 +2175,17 @@ 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_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/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/core.h b/drivers/usb/dwc3/core.h index 1b241f937d8f..ac290d896638 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -1126,6 +1126,7 @@ struct dwc3 { u32 nr_scratch; u32 u1u2; u32 maximum_speed; + u32 gadget_max_speed; u32 ip; diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c index 3e1c1aacf002..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")) @@ -462,15 +462,22 @@ 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)) { + struct device_node *np_conn; - if (np_conn) - edev = extcon_find_edev_by_node(np_conn); - else - edev = NULL; - - 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; 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 <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> + +#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"); diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index ee44321fee38..ff14e5bbd152 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2038,6 +2038,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; @@ -2060,6 +2115,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; @@ -2111,6 +2167,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. */ @@ -2148,12 +2215,14 @@ 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; } else { __dwc3_gadget_start(dwc); } ret = dwc3_gadget_run_stop(dwc, is_on, false); spin_unlock_irqrestore(&dwc->lock, flags); + pm_runtime_put(dwc->dev); return ret; } @@ -2401,60 +2470,20 @@ 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); + dwc->gadget_max_speed = speed; + spin_unlock_irqrestore(&dwc->lock, flags); +} - /* - * 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); +static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned int mA) +{ + struct dwc3 *dwc = gadget_to_dwc(g); - if (DWC3_IP_IS(DWC3)) - reg |= DWC3_DCFG_SUPERSPEED; - else - reg |= DWC3_DCFG_SUPERSPEED_PLUS; - } - } - dwc3_writel(dwc->regs, DWC3_DCFG, reg); + if (dwc->usb2_phy) + return usb_phy_set_power(dwc->usb2_phy, mA); - spin_unlock_irqrestore(&dwc->lock, flags); + return 0; } static const struct usb_gadget_ops dwc3_gadget_ops = { @@ -2466,6 +2495,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, }; /* -------------------------------------------------------------------------- */ diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 1a556a628971..9a7cc08d6593 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -2042,7 +2042,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; @@ -2059,6 +2059,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, @@ -2379,7 +2396,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/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index 36ffb43f9c1a..0d56f33d63c2 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -1488,6 +1488,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; @@ -1537,7 +1559,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, diff --git a/drivers/usb/gadget/function/f_printer.c b/drivers/usb/gadget/function/f_printer.c index 2f1eb2e81d30..61ce8e68f7a3 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); @@ -1364,6 +1366,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)) { 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 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); } 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); 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); diff --git a/include/uapi/linux/usb/tmc.h b/include/uapi/linux/usb/tmc.h index fdd4d88a7b95..d791cc58a7f0 100644 --- a/include/uapi/linux/usb/tmc.h +++ b/include/uapi/linux/usb/tmc.h @@ -102,6 +102,9 @@ 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) +#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) #define USBTMC_IOCTL_CLEANUP_IO _IO(USBTMC_IOC_NR, 36) |