diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2018-04-05 03:55:35 +0300 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2018-04-05 03:55:35 +0300 |
commit | ac9053d2dcb9e8c3fa35ce458dfca8fddc141680 (patch) | |
tree | 3ffa30d58dac22ee0a80e2dd32f41b71da91132b | |
parent | f9ca6a561d40115696a54f16085c4edb17effc74 (diff) | |
parent | 5267c5e09c25e2ee6242b37833a9bdf9d97f54a2 (diff) | |
download | linux-ac9053d2dcb9e8c3fa35ce458dfca8fddc141680.tar.xz |
Merge tag 'usb-4.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
Pull USB/PHY updates from Greg KH:
"Here is the big set of USB and PHY driver patches for 4.17-rc1.
Lots of USB typeC work happened this round, with code moving from the
staging directory into the "real" part of the kernel, as well as new
infrastructure being added to be able to handle the different types of
"roles" that typeC requires.
There is also the normal huge set of USB gadget controller and driver
updates, along with XHCI changes, and a raft of other tiny fixes all
over the USB tree. And the PHY driver updates are merged in here as
well as they interacted with the USB drivers in some places.
All of these have been in linux-next for a while with no reported
issues"
* tag 'usb-4.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (250 commits)
Revert "USB: serial: ftdi_sio: add Id for Physik Instrumente E-870"
usb: musb: gadget: misplaced out of bounds check
usb: chipidea: imx: Fix ULPI on imx53
usb: chipidea: imx: Cleanup ci_hdrc_imx_platform_flag
usb: chipidea: usbmisc: small clean up
usb: chipidea: usbmisc: evdo can be set e/o reset
usb: chipidea: usbmisc: evdo is only specific to OTG port
USB: serial: ftdi_sio: add Id for Physik Instrumente E-870
usb: dwc3: gadget: never call ->complete() from ->ep_queue()
usb: gadget: udc: core: update usb_ep_queue() documentation
usb: host: Remove the deprecated ATH79 USB host config options
usb: roles: Fix return value check in intel_xhci_usb_probe()
USB: gadget: f_midi: fixing a possible double-free in f_midi
usb: core: Add USB_QUIRK_DELAY_CTRL_MSG to usbcore quirks
usb: core: Copy parameter string correctly and remove superfluous null check
USB: announce bcdDevice as well as idVendor, idProduct.
USB:fix USB3 devices behind USB3 hubs not resuming at hibernate thaw
usb: hub: Reduce warning to notice on power loss
USB: serial: ftdi_sio: add support for Harman FirmwareHubEmulator
USB: serial: cp210x: add ELDAT Easywave RX09 id
...
203 files changed, 9264 insertions, 3082 deletions
diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index 0bd731cbb50c..c702c78f24d8 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -189,6 +189,16 @@ Description: The file will read "hotplug", "wired" and "not used" if the information is available, and "unknown" otherwise. +What: /sys/bus/usb/devices/.../(hub interface)/portX/over_current_count +Date: February 2018 +Contact: Richard Leitner <richard.leitner@skidata.com> +Description: + Most hubs are able to detect over-current situations on their + ports and report them to the kernel. This attribute is to expose + the number of over-current situation occurred on a specific port + to user space. This file will contain an unsigned 32 bit value + which wraps to 0 after its maximum is reached. + What: /sys/bus/usb/devices/.../(hub interface)/portX/usb3_lpm_permit Date: November 2015 Contact: Lu Baolu <baolu.lu@linux.intel.com> diff --git a/Documentation/ABI/testing/sysfs-class-usb_role b/Documentation/ABI/testing/sysfs-class-usb_role new file mode 100644 index 000000000000..3b810a425a52 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-usb_role @@ -0,0 +1,21 @@ +What: /sys/class/usb_role/ +Date: Jan 2018 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + Place in sysfs for USB Role Switches. USB Role Switch is a + device that can select the data role (host or device) for USB + port. + +What: /sys/class/usb_role/<switch>/role +Date: Jan 2018 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + The current role of the switch. This attribute can be used for + requesting role swapping with non-USB Type-C ports. With USB + Type-C ports, the ABI defined for USB Type-C connector class + must be used. + + Valid values: + - none + - host + - device diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index e6d33fa8fed4..5ffe4c4121bd 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -4392,6 +4392,64 @@ usbcore.nousb [USB] Disable the USB subsystem + usbcore.quirks= + [USB] A list of quirk entries to augment the built-in + usb core quirk list. List entries are separated by + commas. Each entry has the form + VendorID:ProductID:Flags. The IDs are 4-digit hex + numbers and Flags is a set of letters. Each letter + will change the built-in quirk; setting it if it is + clear and clearing it if it is set. The letters have + the following meanings: + a = USB_QUIRK_STRING_FETCH_255 (string + descriptors must not be fetched using + a 255-byte read); + b = USB_QUIRK_RESET_RESUME (device can't resume + correctly so reset it instead); + c = USB_QUIRK_NO_SET_INTF (device can't handle + Set-Interface requests); + d = USB_QUIRK_CONFIG_INTF_STRINGS (device can't + handle its Configuration or Interface + strings); + e = USB_QUIRK_RESET (device can't be reset + (e.g morph devices), don't use reset); + f = USB_QUIRK_HONOR_BNUMINTERFACES (device has + more interface descriptions than the + bNumInterfaces count, and can't handle + talking to these interfaces); + g = USB_QUIRK_DELAY_INIT (device needs a pause + during initialization, after we read + the device descriptor); + h = USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL (For + high speed and super speed interrupt + endpoints, the USB 2.0 and USB 3.0 spec + require the interval in microframes (1 + microframe = 125 microseconds) to be + calculated as interval = 2 ^ + (bInterval-1). + Devices with this quirk report their + bInterval as the result of this + calculation instead of the exponent + variable used in the calculation); + i = USB_QUIRK_DEVICE_QUALIFIER (device can't + handle device_qualifier descriptor + requests); + j = USB_QUIRK_IGNORE_REMOTE_WAKEUP (device + generates spurious wakeup, ignore + remote wakeup capability); + k = USB_QUIRK_NO_LPM (device can't handle Link + Power Management); + l = USB_QUIRK_LINEAR_FRAME_INTR_BINTERVAL + (Device reports its bInterval as linear + frames instead of the USB 2.0 + calculation); + m = USB_QUIRK_DISCONNECT_SUSPEND (Device needs + to be disconnected before suspend to + prevent spurious wakeup); + n = USB_QUIRK_DELAY_CTRL_MSG (Device needs a + pause after every control message); + Example: quirks=0781:5580:bk,0a5c:5834:gij + usbhid.mousepoll= [USBHID] The interval which mice are to be polled at. diff --git a/Documentation/devicetree/bindings/arm/hisilicon/hisilicon.txt b/Documentation/devicetree/bindings/arm/hisilicon/hisilicon.txt index 7111fbc82a4e..199cd36fe1ba 100644 --- a/Documentation/devicetree/bindings/arm/hisilicon/hisilicon.txt +++ b/Documentation/devicetree/bindings/arm/hisilicon/hisilicon.txt @@ -75,6 +75,29 @@ Example: }; ----------------------------------------------------------------------- +Hisilicon Hi3798CV200 Peripheral Controller + +The Hi3798CV200 Peripheral Controller controls peripherals, queries +their status, and configures some functions of peripherals. + +Required properties: +- compatible: Should contain "hisilicon,hi3798cv200-perictrl", "syscon" + and "simple-mfd". +- reg: Register address and size of Peripheral Controller. +- #address-cells: Should be 1. +- #size-cells: Should be 1. + +Examples: + + perictrl: peripheral-controller@8a20000 { + compatible = "hisilicon,hi3798cv200-perictrl", "syscon", + "simple-mfd"; + reg = <0x8a20000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + }; + +----------------------------------------------------------------------- Hisilicon Hi6220 system controller Required properties: diff --git a/Documentation/devicetree/bindings/phy/meson-gxl-usb2-phy.txt b/Documentation/devicetree/bindings/phy/meson-gxl-usb2-phy.txt index a105494a0fc9..b84a02ebffdf 100644 --- a/Documentation/devicetree/bindings/phy/meson-gxl-usb2-phy.txt +++ b/Documentation/devicetree/bindings/phy/meson-gxl-usb2-phy.txt @@ -6,6 +6,10 @@ Required properties: - #phys-cells: must be 0 (see phy-bindings.txt in this directory) Optional properties: +- clocks: a phandle to the clock of this PHY +- clock-names: must be "phy" +- resets: a phandle to the reset line of this PHY +- reset-names: must be "phy" - phy-supply: see phy-bindings.txt in this directory diff --git a/Documentation/devicetree/bindings/phy/meson-gxl-usb3-phy.txt b/Documentation/devicetree/bindings/phy/meson-gxl-usb3-phy.txt new file mode 100644 index 000000000000..114947e1de3d --- /dev/null +++ b/Documentation/devicetree/bindings/phy/meson-gxl-usb3-phy.txt @@ -0,0 +1,31 @@ +* Amlogic Meson GXL and GXM USB3 PHY and OTG detection binding + +Required properties: +- compatible: Should be "amlogic,meson-gxl-usb3-phy" +- #phys-cells: must be 0 (see phy-bindings.txt in this directory) +- reg: The base address and length of the registers +- interrupts: the interrupt specifier for the OTG detection +- clocks: phandles to the clocks for + - the USB3 PHY + - and peripheral mode/OTG detection +- clock-names: must contain "phy" and "peripheral" +- resets: phandle to the reset lines for: + - the USB3 PHY and + - peripheral mode/OTG detection +- reset-names: must contain "phy" and "peripheral" + +Optional properties: +- phy-supply: see phy-bindings.txt in this directory + + +Example: + usb3_phy0: phy@78080 { + compatible = "amlogic,meson-gxl-usb3-phy"; + #phy-cells = <0>; + reg = <0x0 0x78080 0x0 0x20>; + interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clkc CLKID_USB_OTG>, <&clkc_AO CLKID_AO_CEC_32K>; + clock-names = "phy", "peripheral"; + resets = <&reset RESET_USB_OTG>, <&reset RESET_USB_OTG>; + reset-names = "phy", "peripheral"; + }; diff --git a/Documentation/devicetree/bindings/phy/phy-hi3798cv200-combphy.txt b/Documentation/devicetree/bindings/phy/phy-hi3798cv200-combphy.txt new file mode 100644 index 000000000000..17b0c761370a --- /dev/null +++ b/Documentation/devicetree/bindings/phy/phy-hi3798cv200-combphy.txt @@ -0,0 +1,59 @@ +HiSilicon STB PCIE/SATA/USB3 PHY + +Required properties: +- compatible: Should be "hisilicon,hi3798cv200-combphy" +- reg: Should be the address space for COMBPHY configuration and state + registers in peripheral controller, e.g. PERI_COMBPHY0_CFG and + PERI_COMBPHY0_STATE for COMBPHY0 Hi3798CV200 SoC. +- #phy-cells: Should be 1. The cell number is used to select the phy mode + as defined in <dt-bindings/phy/phy.h>. +- clocks: The phandle to clock provider and clock specifier pair. +- resets: The phandle to reset controller and reset specifier pair. + +Refer to phy/phy-bindings.txt for the generic PHY binding properties. + +Optional properties: +- hisilicon,fixed-mode: If the phy device doesn't support mode select + but a fixed mode setting, the property should be present to specify + the particular mode. +- hisilicon,mode-select-bits: If the phy device support mode select, + this property should be present to specify the register bits in + peripheral controller, as a 3 integers tuple: + <register_offset bit_shift bit_mask>. + +Notes: +- Between hisilicon,fixed-mode and hisilicon,mode-select-bits, one and only + one of them should be present. +- The device node should be a child of peripheral controller that contains + COMBPHY configuration/state and PERI_CTRL register used to select PHY mode. + Refer to arm/hisilicon/hisilicon.txt for the parent peripheral controller + bindings. + +Examples: + +perictrl: peripheral-controller@8a20000 { + compatible = "hisilicon,hi3798cv200-perictrl", "syscon", + "simple-mfd"; + reg = <0x8a20000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x8a20000 0x1000>; + + combphy0: phy@850 { + compatible = "hisilicon,hi3798cv200-combphy"; + reg = <0x850 0x8>; + #phy-cells = <1>; + clocks = <&crg HISTB_COMBPHY0_CLK>; + resets = <&crg 0x188 4>; + hisilicon,fixed-mode = <PHY_TYPE_USB3>; + }; + + combphy1: phy@858 { + compatible = "hisilicon,hi3798cv200-combphy"; + reg = <0x858 0x8>; + #phy-cells = <1>; + clocks = <&crg HISTB_COMBPHY1_CLK>; + resets = <&crg 0x188 12>; + hisilicon,mode-select-bits = <0x0008 11 (0x3 << 11)>; + }; +}; diff --git a/Documentation/devicetree/bindings/phy/phy-hisi-inno-usb2.txt b/Documentation/devicetree/bindings/phy/phy-hisi-inno-usb2.txt new file mode 100644 index 000000000000..0d70c8341095 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/phy-hisi-inno-usb2.txt @@ -0,0 +1,71 @@ +Device tree bindings for HiSilicon INNO USB2 PHY + +Required properties: +- compatible: Should be one of the following strings: + "hisilicon,inno-usb2-phy", + "hisilicon,hi3798cv200-usb2-phy". +- reg: Should be the address space for PHY configuration register in peripheral + controller, e.g. PERI_USB0 for USB 2.0 PHY01 on Hi3798CV200 SoC. +- clocks: The phandle and clock specifier pair for INNO USB2 PHY device + reference clock. +- resets: The phandle and reset specifier pair for INNO USB2 PHY device reset + signal. +- #address-cells: Must be 1. +- #size-cells: Must be 0. + +The INNO USB2 PHY device should be a child node of peripheral controller that +contains the PHY configuration register, and each device suppports up to 2 PHY +ports which are represented as child nodes of INNO USB2 PHY device. + +Required properties for PHY port node: +- reg: The PHY port instance number. +- #phy-cells: Defined by generic PHY bindings. Must be 0. +- resets: The phandle and reset specifier pair for PHY port reset signal. + +Refer to phy/phy-bindings.txt for the generic PHY binding properties + +Example: + +perictrl: peripheral-controller@8a20000 { + compatible = "hisilicon,hi3798cv200-perictrl", "simple-mfd"; + reg = <0x8a20000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x8a20000 0x1000>; + + usb2_phy1: usb2-phy@120 { + compatible = "hisilicon,hi3798cv200-usb2-phy"; + reg = <0x120 0x4>; + clocks = <&crg HISTB_USB2_PHY1_REF_CLK>; + resets = <&crg 0xbc 4>; + #address-cells = <1>; + #size-cells = <0>; + + usb2_phy1_port0: phy@0 { + reg = <0>; + #phy-cells = <0>; + resets = <&crg 0xbc 8>; + }; + + usb2_phy1_port1: phy@1 { + reg = <1>; + #phy-cells = <0>; + resets = <&crg 0xbc 9>; + }; + }; + + usb2_phy2: usb2-phy@124 { + compatible = "hisilicon,hi3798cv200-usb2-phy"; + reg = <0x124 0x4>; + clocks = <&crg HISTB_USB2_PHY2_REF_CLK>; + resets = <&crg 0xbc 6>; + #address-cells = <1>; + #size-cells = <0>; + + usb2_phy2_port0: phy@0 { + reg = <0>; + #phy-cells = <0>; + resets = <&crg 0xbc 10>; + }; + }; +}; diff --git a/Documentation/devicetree/bindings/phy/phy-mapphone-mdm6600.txt b/Documentation/devicetree/bindings/phy/phy-mapphone-mdm6600.txt new file mode 100644 index 000000000000..29427d4f047a --- /dev/null +++ b/Documentation/devicetree/bindings/phy/phy-mapphone-mdm6600.txt @@ -0,0 +1,29 @@ +Device tree binding documentation for Motorola Mapphone MDM6600 USB PHY + +Required properties: +- compatible Must be "motorola,mapphone-mdm6600" +- enable-gpios GPIO to enable the USB PHY +- power-gpios GPIO to power on the device +- reset-gpios GPIO to reset the device +- motorola,mode-gpios Two GPIOs to configure MDM6600 USB start-up mode for + normal mode versus USB flashing mode +- motorola,cmd-gpios Three GPIOs to control the power state of the MDM6600 +- motorola,status-gpios Three GPIOs to read the power state of the MDM6600 + +Example: + +usb-phy { + compatible = "motorola,mapphone-mdm6600"; + enable-gpios = <&gpio3 31 GPIO_ACTIVE_LOW>; + power-gpios = <&gpio2 22 GPIO_ACTIVE_HIGH>; + reset-gpios = <&gpio2 17 GPIO_ACTIVE_HIGH>; + motorola,mode-gpios = <&gpio5 20 GPIO_ACTIVE_HIGH>, + <&gpio5 21 GPIO_ACTIVE_HIGH>; + motorola,cmd-gpios = <&gpio4 7 GPIO_ACTIVE_HIGH>, + <&gpio4 8 GPIO_ACTIVE_HIGH>, + <&gpio5 14 GPIO_ACTIVE_HIGH>; + motorola,status-gpios = <&gpio2 20 GPIO_ACTIVE_HIGH>, + <&gpio2 21 GPIO_ACTIVE_HIGH>, + <&gpio2 23 GPIO_ACTIVE_HIGH>; + #phy-cells = <0>; +}; diff --git a/Documentation/devicetree/bindings/phy/phy-mtk-tphy.txt b/Documentation/devicetree/bindings/phy/phy-mtk-tphy.txt index 41e09ed2ca70..0d34b2b4a6b7 100644 --- a/Documentation/devicetree/bindings/phy/phy-mtk-tphy.txt +++ b/Documentation/devicetree/bindings/phy/phy-mtk-tphy.txt @@ -27,6 +27,10 @@ Optional properties (controller (parent) node): - reg : offset and length of register shared by multiple ports, exclude port's private register. It is needed on mt2701 and mt8173, but not on mt2712. + - mediatek,src-ref-clk-mhz : frequency of reference clock for slew rate + calibrate + - mediatek,src-coef : coefficient for slew rate calibrate, depends on + SoC process Required properties (port (child) node): - reg : address and length of the register set for the port. diff --git a/Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt b/Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt index 6ea867e3176f..960da7fcaa9e 100644 --- a/Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt +++ b/Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt @@ -14,25 +14,9 @@ Required properties: - resets : a list of phandle + reset specifier pairs - reset-names : string reset name, must be: "uphy", "uphy-pipe", "uphy-tcphy" - - extcon : extcon specifier for the Power Delivery -Note, there are 2 type-c phys for RK3399, and they are almost identical, except -these registers(description below), every register node contains 3 sections: -offset, enable bit, write mask bit. - - rockchip,typec-conn-dir : the register of type-c connector direction, - for type-c phy0, it must be <0xe580 0 16>; - for type-c phy1, it must be <0xe58c 0 16>; - - rockchip,usb3tousb2-en : the register of type-c force usb3 to usb2 enable - control. - for type-c phy0, it must be <0xe580 3 19>; - for type-c phy1, it must be <0xe58c 3 19>; - - rockchip,external-psm : the register of type-c phy external psm clock - selection. - for type-c phy0, it must be <0xe588 14 30>; - for type-c phy1, it must be <0xe594 14 30>; - - rockchip,pipe-status : the register of type-c phy pipe status. - for type-c phy0, it must be <0xe5c0 0 0>; - for type-c phy1, it must be <0xe5c0 16 16>; +Optional properties: + - extcon : extcon specifier for the Power Delivery Required nodes : a sub-node is required for each port the phy provides. The sub-node name is used to identify dp or usb3 port, @@ -43,6 +27,13 @@ Required nodes : a sub-node is required for each port the phy provides. Required properties (port (child) node): - #phy-cells : must be 0, See ./phy-bindings.txt for details. +Deprecated properties, do not use in new device tree sources, these +properties are determined by the compatible value: + - rockchip,typec-conn-dir + - rockchip,usb3tousb2-en + - rockchip,external-psm + - rockchip,pipe-status + Example: tcphy0: phy@ff7c0000 { compatible = "rockchip,rk3399-typec-phy"; @@ -58,10 +49,6 @@ Example: <&cru SRST_UPHY0_PIPE_L00>, <&cru SRST_P_UPHY0_TCPHY>; reset-names = "uphy", "uphy-pipe", "uphy-tcphy"; - rockchip,typec-conn-dir = <0xe580 0 16>; - rockchip,usb3tousb2-en = <0xe580 3 19>; - rockchip,external-psm = <0xe588 14 30>; - rockchip,pipe-status = <0xe5c0 0 0>; tcphy0_dp: dp-port { #phy-cells = <0>; @@ -86,10 +73,6 @@ Example: <&cru SRST_UPHY1_PIPE_L00>, <&cru SRST_P_UPHY1_TCPHY>; reset-names = "uphy", "uphy-pipe", "uphy-tcphy"; - rockchip,typec-conn-dir = <0xe58c 0 16>; - rockchip,usb3tousb2-en = <0xe58c 3 19>; - rockchip,external-psm = <0xe594 14 30>; - rockchip,pipe-status = <0xe5c0 16 16>; tcphy1_dp: dp-port { #phy-cells = <0>; diff --git a/Documentation/devicetree/bindings/phy/phy-stm32-usbphyc.txt b/Documentation/devicetree/bindings/phy/phy-stm32-usbphyc.txt new file mode 100644 index 000000000000..725ae71ae653 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/phy-stm32-usbphyc.txt @@ -0,0 +1,73 @@ +STMicroelectronics STM32 USB HS PHY controller + +The STM32 USBPHYC block contains a dual port High Speed UTMI+ PHY and a UTMI +switch. It controls PHY configuration and status, and the UTMI+ switch that +selects either OTG or HOST controller for the second PHY port. It also sets +PLL configuration. + +USBPHYC + |_ PLL + | + |_ PHY port#1 _________________ HOST controller + | _ | + | / 1|________________| + |_ PHY port#2 ----| |________________ + | \_0| | + |_ UTMI switch_______| OTG controller + + +Phy provider node +================= + +Required properties: +- compatible: must be "st,stm32mp1-usbphyc" +- reg: address and length of the usb phy control register set +- clocks: phandle + clock specifier for the PLL phy clock +- #address-cells: number of address cells for phys sub-nodes, must be <1> +- #size-cells: number of size cells for phys sub-nodes, must be <0> + +Optional properties: +- assigned-clocks: phandle + clock specifier for the PLL phy clock +- assigned-clock-parents: the PLL phy clock parent +- resets: phandle + reset specifier + +Required nodes: one sub-node per port the controller provides. + +Phy sub-nodes +============== + +Required properties: +- reg: phy port index +- phy-supply: phandle to the regulator providing 3V3 power to the PHY, + see phy-bindings.txt in the same directory. +- vdda1v1-supply: phandle to the regulator providing 1V1 power to the PHY +- vdda1v8-supply: phandle to the regulator providing 1V8 power to the PHY +- #phy-cells: see phy-bindings.txt in the same directory, must be <0> for PHY + port#1 and must be <1> for PHY port#2, to select USB controller + + +Example: + usbphyc: usb-phy@5a006000 { + compatible = "st,stm32mp1-usbphyc"; + reg = <0x5a006000 0x1000>; + clocks = <&rcc_clk USBPHY_K>; + resets = <&rcc_rst USBPHY_R>; + #address-cells = <1>; + #size-cells = <0>; + + usbphyc_port0: usb-phy@0 { + reg = <0>; + phy-supply = <&vdd_usb>; + vdda1v1-supply = <®11>; + vdda1v8-supply = <®18> + #phy-cells = <0>; + }; + + usbphyc_port1: usb-phy@1 { + reg = <1>; + phy-supply = <&vdd_usb>; + vdda1v1-supply = <®11>; + vdda1v8-supply = <®18> + #phy-cells = <1>; + }; + }; diff --git a/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt b/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt index b6a9f2b92bab..dcf1b8f691d5 100644 --- a/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt +++ b/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt @@ -8,7 +8,8 @@ Required properties: - compatible: compatible list, contains: "qcom,ipq8074-qmp-pcie-phy" for PCIe phy on IPQ8074 "qcom,msm8996-qmp-pcie-phy" for 14nm PCIe phy on msm8996, - "qcom,msm8996-qmp-usb3-phy" for 14nm USB3 phy on msm8996. + "qcom,msm8996-qmp-usb3-phy" for 14nm USB3 phy on msm8996, + "qcom,qmp-v3-usb3-phy" for USB3 QMP V3 phy. - reg: offset and length of register set for PHY's common serdes block. @@ -25,10 +26,13 @@ Required properties: - clock-names: "cfg_ahb" for phy config clock, "aux" for phy aux clock, "ref" for 19.2 MHz ref clk, + "com_aux" for phy common block aux clock, For "qcom,msm8996-qmp-pcie-phy" must contain: "aux", "cfg_ahb", "ref". For "qcom,msm8996-qmp-usb3-phy" must contain: "aux", "cfg_ahb", "ref". + For "qcom,qmp-v3-usb3-phy" must contain: + "aux", "cfg_ahb", "ref", "com_aux". - resets: a list of phandles and reset controller specifier pairs, one for each entry in reset-names. diff --git a/Documentation/devicetree/bindings/phy/qcom-qusb2-phy.txt b/Documentation/devicetree/bindings/phy/qcom-qusb2-phy.txt index aa0fcb05acb3..42c97426836e 100644 --- a/Documentation/devicetree/bindings/phy/qcom-qusb2-phy.txt +++ b/Documentation/devicetree/bindings/phy/qcom-qusb2-phy.txt @@ -4,7 +4,10 @@ Qualcomm QUSB2 phy controller QUSB2 controller supports LS/FS/HS usb connectivity on Qualcomm chipsets. Required properties: - - compatible: compatible list, contains "qcom,msm8996-qusb2-phy". + - compatible: compatible list, contains + "qcom,msm8996-qusb2-phy" for 14nm PHY on msm8996, + "qcom,qusb2-v2-phy" for QUSB2 V2 PHY. + - reg: offset and length of the PHY register set. - #phy-cells: must be 0. diff --git a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt index 99b651b33110..dbd137c079e2 100644 --- a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt +++ b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt @@ -8,6 +8,8 @@ Required properties: SoC. "renesas,usb2-phy-r8a7796" if the device is a part of an R8A7796 SoC. + "renesas,usb2-phy-r8a77965" if the device is a part of an + R8A77965 SoC. "renesas,usb2-phy-r8a77995" if the device is a part of an R8A77995 SoC. "renesas,rcar-gen3-usb2-phy" for a generic R-Car Gen3 compatible device. diff --git a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb3.txt b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb3.txt index f94cea48f6b1..47dd296ecead 100644 --- a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb3.txt +++ b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb3.txt @@ -11,6 +11,8 @@ Required properties: SoC. "renesas,r8a7796-usb3-phy" if the device is a part of an R8A7796 SoC. + "renesas,r8a77965-usb3-phy" if the device is a part of an + R8A77965 SoC. "renesas,rcar-gen3-usb3-phy" for a generic R-Car Gen3 compatible device. diff --git a/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt b/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt index c1ce5a0a652e..07ca4ec4a745 100644 --- a/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt +++ b/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt @@ -11,6 +11,7 @@ Required properties: * allwinner,sun8i-a33-usb-phy * allwinner,sun8i-a83t-usb-phy * allwinner,sun8i-h3-usb-phy + * allwinner,sun8i-r40-usb-phy * allwinner,sun8i-v3s-usb-phy * allwinner,sun50i-a64-usb-phy - reg : a list of offset + length pairs diff --git a/Documentation/devicetree/bindings/usb/amlogic,dwc3.txt b/Documentation/devicetree/bindings/usb/amlogic,dwc3.txt new file mode 100644 index 000000000000..9a8b631904fd --- /dev/null +++ b/Documentation/devicetree/bindings/usb/amlogic,dwc3.txt @@ -0,0 +1,42 @@ +Amlogic Meson GX DWC3 USB SoC controller + +Required properties: +- compatible: depending on the SoC this should contain one of: + * amlogic,meson-axg-dwc3 + * amlogic,meson-gxl-dwc3 +- clocks: a handle for the "USB general" clock +- clock-names: must be "usb_general" +- resets: a handle for the shared "USB OTG" reset line +- reset-names: must be "usb_otg" + +Required child node: +A child node must exist to represent the core DWC3 IP block. The name of +the node is not important. The content of the node is defined in dwc3.txt. + +PHY documentation is provided in the following places: +- Documentation/devicetree/bindings/phy/meson-gxl-usb2-phy.txt +- Documentation/devicetree/bindings/phy/meson-gxl-usb3-phy.txt + +Example device nodes: + usb0: usb@ff500000 { + compatible = "amlogic,meson-axg-dwc3"; + #address-cells = <2>; + #size-cells = <2>; + ranges; + + clocks = <&clkc CLKID_USB>; + clock-names = "usb_general"; + resets = <&reset RESET_USB_OTG>; + reset-names = "usb_otg"; + + dwc3: dwc3@ff500000 { + compatible = "snps,dwc3"; + reg = <0x0 0xff500000 0x0 0x100000>; + interrupts = <GIC_SPI 30 IRQ_TYPE_LEVEL_HIGH>; + dr_mode = "host"; + maximum-speed = "high-speed"; + snps,dis_u2_susphy_quirk; + phys = <&usb3_phy>, <&usb2_phy0>; + phy-names = "usb2-phy", "usb3-phy"; + }; + }; diff --git a/Documentation/devicetree/bindings/usb/dwc3.txt b/Documentation/devicetree/bindings/usb/dwc3.txt index 44e8bab159ad..0dbd3083e7dd 100644 --- a/Documentation/devicetree/bindings/usb/dwc3.txt +++ b/Documentation/devicetree/bindings/usb/dwc3.txt @@ -57,6 +57,22 @@ Optional properties: - snps,quirk-frame-length-adjustment: Value for GFLADJ_30MHZ field of GFLADJ register for post-silicon frame length adjustment when the fladj_30mhz_sdbnd signal is invalid or incorrect. + - snps,rx-thr-num-pkt-prd: periodic ESS RX packet threshold count - host mode + only. Set this and rx-max-burst-prd to a valid, + non-zero value 1-16 (DWC_usb31 programming guide + section 1.2.4) to enable periodic ESS RX threshold. + - snps,rx-max-burst-prd: max periodic ESS RX burst size - host mode only. Set + this and rx-thr-num-pkt-prd to a valid, non-zero value + 1-16 (DWC_usb31 programming guide section 1.2.4) to + enable periodic ESS RX threshold. + - snps,tx-thr-num-pkt-prd: periodic ESS TX packet threshold count - host mode + only. Set this and tx-max-burst-prd to a valid, + non-zero value 1-16 (DWC_usb31 programming guide + section 1.2.3) to enable periodic ESS TX threshold. + - snps,tx-max-burst-prd: max periodic ESS TX burst size - host mode only. Set + this and tx-thr-num-pkt-prd to a valid, non-zero value + 1-16 (DWC_usb31 programming guide section 1.2.3) to + enable periodic ESS TX threshold. - <DEPRECATED> tx-fifo-resize: determines if the FIFO *has* to be reallocated. diff --git a/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.txt b/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.txt index 88d9f4a4b280..266c2d917a28 100644 --- a/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.txt +++ b/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.txt @@ -32,7 +32,7 @@ Required properties: "mcu_ck": mcu_bus clock for register access, "dma_ck": dma_bus clock for data transfer by DMA - - phys : a list of phandle + phy specifier pairs + - phys : see usb-hcd.txt in the current directory Optional properties: - wakeup-source : enable USB remote wakeup; @@ -52,6 +52,9 @@ Optional properties: See: Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt - imod-interval-ns: default interrupt moderation interval is 5000ns +additionally the properties from usb-hcd.txt (in the current directory) are +supported. + Example: usb30: usb@11270000 { compatible = "mediatek,mt8173-xhci"; diff --git a/Documentation/devicetree/bindings/usb/mediatek,mtu3.txt b/Documentation/devicetree/bindings/usb/mediatek,mtu3.txt index d589a1ef96a1..3382b5cb471d 100644 --- a/Documentation/devicetree/bindings/usb/mediatek,mtu3.txt +++ b/Documentation/devicetree/bindings/usb/mediatek,mtu3.txt @@ -17,7 +17,7 @@ Required properties: - clock-names : must contain "sys_ck" for clock of controller, the following clocks are optional: "ref_ck", "mcu_ck" and "dam_ck"; - - phys : a list of phandle + phy specifier pairs + - phys : see usb-hcd.txt in the current directory - dr_mode : should be one of "host", "peripheral" or "otg", refer to usb/generic.txt @@ -53,6 +53,9 @@ Optional properties: - mediatek,u3p-dis-msk : mask to disable u3ports, bit0 for u3port0, bit1 for u3port1, ... etc; +additionally the properties from usb-hcd.txt (in the current directory) are +supported. + Sub-nodes: The xhci should be added as subnode to mtu3 as shown in the following example if host mode is enabled. The DT binding details of xhci can be found in: diff --git a/Documentation/devicetree/bindings/usb/usb-ehci.txt b/Documentation/devicetree/bindings/usb/usb-ehci.txt index 3efde12b5d68..0f1b75386207 100644 --- a/Documentation/devicetree/bindings/usb/usb-ehci.txt +++ b/Documentation/devicetree/bindings/usb/usb-ehci.txt @@ -16,10 +16,12 @@ Optional properties: - has-transaction-translator : boolean, set this if EHCI have a Transaction Translator built into the root hub. - clocks : a list of phandle + clock specifier pairs - - phys : phandle + phy specifier pair - - phy-names : "usb" + - phys : see usb-hcd.txt in the current directory - resets : phandle + reset specifier pair +additionally the properties from usb-hcd.txt (in the current directory) are +supported. + Example (Sequoia 440EPx): ehci@e0000300 { compatible = "ibm,usb-ehci-440epx", "usb-ehci"; diff --git a/Documentation/devicetree/bindings/usb/usb-hcd.txt b/Documentation/devicetree/bindings/usb/usb-hcd.txt new file mode 100644 index 000000000000..50529b838c9c --- /dev/null +++ b/Documentation/devicetree/bindings/usb/usb-hcd.txt @@ -0,0 +1,9 @@ +Generic USB HCD (Host Controller Device) Properties + +Optional properties: +- phys: a list of all USB PHYs on this HCD + +Example: + &usb1 { + phys = <&usb2_phy1>, <&usb3_phy1>; + }; diff --git a/Documentation/devicetree/bindings/usb/usb-ohci.txt b/Documentation/devicetree/bindings/usb/usb-ohci.txt index 09e70c875bc6..a8d2103d1f3d 100644 --- a/Documentation/devicetree/bindings/usb/usb-ohci.txt +++ b/Documentation/devicetree/bindings/usb/usb-ohci.txt @@ -13,10 +13,12 @@ Optional properties: - remote-wakeup-connected: remote wakeup is wired on the platform - num-ports : u32, to override the detected port count - clocks : a list of phandle + clock specifier pairs -- phys : phandle + phy specifier pair -- phy-names : "usb" +- phys : see usb-hcd.txt in the current directory - resets : a list of phandle + reset specifier pairs +additionally the properties from usb-hcd.txt (in the current directory) are +supported. + Example: ohci0: usb@1c14400 { diff --git a/Documentation/devicetree/bindings/usb/usb-uhci.txt b/Documentation/devicetree/bindings/usb/usb-uhci.txt index 298133416c97..cc2e6f7d602e 100644 --- a/Documentation/devicetree/bindings/usb/usb-uhci.txt +++ b/Documentation/devicetree/bindings/usb/usb-uhci.txt @@ -6,6 +6,9 @@ Required properties: - reg : Should contain 1 register ranges(address and length) - interrupts : UHCI controller interrupt +additionally the properties from usb-hcd.txt (in the current directory) are +supported. + Example: uhci@d8007b00 { diff --git a/Documentation/devicetree/bindings/usb/usb-xhci.txt b/Documentation/devicetree/bindings/usb/usb-xhci.txt index 1651483a7048..c4c00dff4b56 100644 --- a/Documentation/devicetree/bindings/usb/usb-xhci.txt +++ b/Documentation/devicetree/bindings/usb/usb-xhci.txt @@ -33,6 +33,11 @@ Optional properties: - usb3-lpm-capable: determines if platform is USB3 LPM capable - quirk-broken-port-ped: set if the controller has broken port disable mechanism - imod-interval-ns: default interrupt moderation interval is 5000ns + - phys : see usb-hcd.txt in the current directory + +additionally the properties from usb-hcd.txt (in the current directory) are +supported. + Example: usb@f0931000 { diff --git a/Documentation/driver-api/device_connection.rst b/Documentation/driver-api/device_connection.rst new file mode 100644 index 000000000000..affbc5566ab0 --- /dev/null +++ b/Documentation/driver-api/device_connection.rst @@ -0,0 +1,43 @@ +================== +Device connections +================== + +Introduction +------------ + +Devices often have connections to other devices that are outside of the direct +child/parent relationship. A serial or network communication controller, which +could be a PCI device, may need to be able to get a reference to its PHY +component, which could be attached for example to the I2C bus. Some device +drivers need to be able to control the clocks or the GPIOs for their devices, +and so on. + +Device connections are generic descriptions of any type of connection between +two separate devices. + +Device connections alone do not create a dependency between the two devices. +They are only descriptions which are not tied to either of the devices directly. +A dependency between the two devices exists only if one of the two endpoint +devices requests a reference to the other. The descriptions themselves can be +defined in firmware (not yet supported) or they can be built-in. + +Usage +----- + +Device connections should exist before device ``->probe`` callback is called for +either endpoint device in the description. If the connections are defined in +firmware, this is not a problem. It should be considered if the connection +descriptions are "built-in", and need to be added separately. + +The connection description consists of the names of the two devices with the +connection, i.e. the endpoints, and unique identifier for the connection which +is needed if there are multiple connections between the two devices. + +After a description exists, the devices in it can request reference to the other +endpoint device, or they can request the description itself. + +API +--- + +.. kernel-doc:: drivers/base/devcon.c + : functions: device_connection_find_match device_connection_find device_connection_add device_connection_remove diff --git a/Documentation/driver-api/usb/typec.rst b/Documentation/driver-api/usb/typec.rst index 8a7249f2ff04..feb31946490b 100644 --- a/Documentation/driver-api/usb/typec.rst +++ b/Documentation/driver-api/usb/typec.rst @@ -61,7 +61,7 @@ Registering the ports The port drivers will describe every Type-C port they control with struct typec_capability data structure, and register them with the following API: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_register_port typec_unregister_port When registering the ports, the prefer_role member in struct typec_capability @@ -80,7 +80,7 @@ typec_partner_desc. The class copies the details of the partner during registration. The class offers the following API for registering/unregistering partners. -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_register_partner typec_unregister_partner The class will provide a handle to struct typec_partner if the registration was @@ -92,7 +92,7 @@ should include handle to struct usb_pd_identity instance. The class will then create a sysfs directory for the identity under the partner device. The result of Discover Identity command can then be reported with the following API: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_partner_set_identity Registering Cables @@ -113,7 +113,7 @@ typec_cable_desc and about a plug in struct typec_plug_desc. The class copies the details during registration. The class offers the following API for registering/unregistering cables and their plugs: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_register_cable typec_unregister_cable typec_register_plug typec_unregister_plug The class will provide a handle to struct typec_cable and struct typec_plug if @@ -125,7 +125,7 @@ include handle to struct usb_pd_identity instance. The class will then create a sysfs directory for the identity under the cable device. The result of Discover Identity command can then be reported with the following API: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_cable_set_identity Notifications @@ -135,7 +135,7 @@ When the partner has executed a role change, or when the default roles change during connection of a partner or cable, the port driver must use the following APIs to report it to the class: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_set_data_role typec_set_pwr_role typec_set_vconn_role typec_set_pwr_opmode Alternate Modes @@ -150,7 +150,7 @@ and struct typec_altmode_desc which is a container for all the supported modes. Ports that support Alternate Modes need to register each SVID they support with the following API: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_port_register_altmode If a partner or cable plug provides a list of SVIDs as response to USB Power @@ -159,12 +159,12 @@ registered. API for the partners: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_partner_register_altmode API for the Cable Plugs: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_plug_register_altmode So ports, partners and cable plugs will register the alternate modes with their @@ -172,11 +172,62 @@ own functions, but the registration will always return a handle to struct typec_altmode on success, or NULL. The unregistration will happen with the same function: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_unregister_altmode If a partner or cable plug enters or exits a mode, the port driver needs to notify the class with the following API: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_altmode_update_active + +Multiplexer/DeMultiplexer Switches +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +USB Type-C connectors may have one or more mux/demux switches behind them. Since +the plugs can be inserted right-side-up or upside-down, a switch is needed to +route the correct data pairs from the connector to the USB controllers. If +Alternate or Accessory Modes are supported, another switch is needed that can +route the pins on the connector to some other component besides USB. USB Type-C +Connector Class supplies an API for registering those switches. + +.. kernel-doc:: drivers/usb/typec/mux.c + :functions: typec_switch_register typec_switch_unregister typec_mux_register typec_mux_unregister + +In most cases the same physical mux will handle both the orientation and mode. +However, as the port drivers will be responsible for the orientation, and the +alternate mode drivers for the mode, the two are always separated into their +own logical components: "mux" for the mode and "switch" for the orientation. + +When a port is registered, USB Type-C Connector Class requests both the mux and +the switch for the port. The drivers can then use the following API for +controlling them: + +.. kernel-doc:: drivers/usb/typec/class.c + :functions: typec_set_orientation typec_set_mode + +If the connector is dual-role capable, there may also be a switch for the data +role. USB Type-C Connector Class does not supply separate API for them. The +port drivers can use USB Role Class API with those. + +Illustration of the muxes behind a connector that supports an alternate mode: + + ------------------------ + | Connector | + ------------------------ + | | + ------------------------ + \ Orientation / + -------------------- + | + -------------------- + / Mode \ + ------------------------ + / \ + ------------------------ -------------------- + | Alt Mode | / USB Role \ + ------------------------ ------------------------ + / \ + ------------------------ ------------------------ + | USB Host | | USB Device | + ------------------------ ------------------------ diff --git a/MAINTAINERS b/MAINTAINERS index 9d42bb8bb120..f01147af694b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4079,7 +4079,7 @@ S: Supported F: drivers/mtd/nand/denali* DESIGNWARE USB2 DRD IP DRIVER -M: John Youn <johnyoun@synopsys.com> +M: Minas Harutyunyan <hminas@synopsys.com> L: linux-usb@vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/balbi/usb.git S: Maintained @@ -14476,6 +14476,12 @@ S: Maintained F: Documentation/hid/hiddev.txt F: drivers/hid/usbhid/ +USB INTEL XHCI ROLE MUX DRIVER +M: Hans de Goede <hdegoede@redhat.com> +L: linux-usb@vger.kernel.org +S: Maintained +F: drivers/usb/roles/intel-xhci-usb-role-switch.c + USB ISP116X DRIVER M: Olav Kongas <ok@artecdesign.ee> L: linux-usb@vger.kernel.org @@ -14606,6 +14612,12 @@ F: drivers/usb/ F: include/linux/usb.h F: include/linux/usb/ +USB TYPEC PI3USB30532 MUX DRIVER +M: Hans de Goede <hdegoede@redhat.com> +L: linux-usb@vger.kernel.org +S: Maintained +F: drivers/usb/typec/mux/pi3usb30532.c + USB TYPEC SUBSYSTEM M: Heikki Krogerus <heikki.krogerus@linux.intel.com> L: linux-usb@vger.kernel.org diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index 8128c3b68d6b..61e9a24297b7 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -200,6 +200,7 @@ config ATH79 select SYS_SUPPORTS_MIPS16 select SYS_SUPPORTS_ZBOOT_UART_PROM select USE_OF + select USB_EHCI_ROOT_HUB_TT if USB_EHCI_HCD_PLATFORM help Support for the Atheros AR71XX/AR724X/AR913X SoCs. diff --git a/drivers/base/Makefile b/drivers/base/Makefile index e32a52490051..12a7f64d35a9 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -5,7 +5,8 @@ obj-y := component.o core.o bus.o dd.o syscore.o \ driver.o class.o platform.o \ cpu.o firmware.o init.o map.o devres.o \ attribute_container.o transport_class.o \ - topology.o container.o property.o cacheinfo.o + topology.o container.o property.o cacheinfo.o \ + devcon.o obj-$(CONFIG_DEVTMPFS) += devtmpfs.o obj-$(CONFIG_DMA_CMA) += dma-contiguous.o obj-y += power/ diff --git a/drivers/base/devcon.c b/drivers/base/devcon.c new file mode 100644 index 000000000000..d427e806cd73 --- /dev/null +++ b/drivers/base/devcon.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Device connections + * + * Copyright (C) 2018 Intel Corporation + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> + */ + +#include <linux/device.h> + +static DEFINE_MUTEX(devcon_lock); +static LIST_HEAD(devcon_list); + +/** + * device_connection_find_match - Find physical connection to a device + * @dev: Device with the connection + * @con_id: Identifier for the connection + * @data: Data for the match function + * @match: Function to check and convert the connection description + * + * Find a connection with unique identifier @con_id between @dev and another + * device. @match will be used to convert the connection description to data the + * caller is expecting to be returned. + */ +void *device_connection_find_match(struct device *dev, const char *con_id, + void *data, + void *(*match)(struct device_connection *con, + int ep, void *data)) +{ + const char *devname = dev_name(dev); + struct device_connection *con; + void *ret = NULL; + int ep; + + if (!match) + return NULL; + + mutex_lock(&devcon_lock); + + list_for_each_entry(con, &devcon_list, list) { + ep = match_string(con->endpoint, 2, devname); + if (ep < 0) + continue; + + if (con_id && strcmp(con->id, con_id)) + continue; + + ret = match(con, !ep, data); + if (ret) + break; + } + + mutex_unlock(&devcon_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(device_connection_find_match); + +extern struct bus_type platform_bus_type; +extern struct bus_type pci_bus_type; +extern struct bus_type i2c_bus_type; +extern struct bus_type spi_bus_type; + +static struct bus_type *generic_match_buses[] = { + &platform_bus_type, +#ifdef CONFIG_PCI + &pci_bus_type, +#endif +#ifdef CONFIG_I2C + &i2c_bus_type, +#endif +#ifdef CONFIG_SPI_MASTER + &spi_bus_type, +#endif + NULL, +}; + +/* This tries to find the device from the most common bus types by name. */ +static void *generic_match(struct device_connection *con, int ep, void *data) +{ + struct bus_type *bus; + struct device *dev; + + for (bus = generic_match_buses[0]; bus; bus++) { + dev = bus_find_device_by_name(bus, NULL, con->endpoint[ep]); + if (dev) + return dev; + } + + /* + * We only get called if a connection was found, tell the caller to + * wait for the other device to show up. + */ + return ERR_PTR(-EPROBE_DEFER); +} + +/** + * device_connection_find - Find two devices connected together + * @dev: Device with the connection + * @con_id: Identifier for the connection + * + * Find a connection with unique identifier @con_id between @dev and + * another device. On success returns handle to the device that is connected + * to @dev, with the reference count for the found device incremented. Returns + * NULL if no matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a + * connection was found but the other device has not been enumerated yet. + */ +struct device *device_connection_find(struct device *dev, const char *con_id) +{ + return device_connection_find_match(dev, con_id, NULL, generic_match); +} +EXPORT_SYMBOL_GPL(device_connection_find); + +/** + * device_connection_add - Register a connection description + * @con: The connection description to be registered + */ +void device_connection_add(struct device_connection *con) +{ + mutex_lock(&devcon_lock); + list_add_tail(&con->list, &devcon_list); + mutex_unlock(&devcon_lock); +} +EXPORT_SYMBOL_GPL(device_connection_add); + +/** + * device_connections_remove - Unregister connection description + * @con: The connection description to be unregistered + */ +void device_connection_remove(struct device_connection *con) +{ + mutex_lock(&devcon_lock); + list_del(&con->list); + mutex_unlock(&devcon_lock); +} +EXPORT_SYMBOL_GPL(device_connection_remove); diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index a7bca4207f44..de15bf55895b 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -30,7 +30,8 @@ config EXTCON_ARIZONA config EXTCON_AXP288 tristate "X-Power AXP288 EXTCON support" - depends on MFD_AXP20X && USB_PHY + depends on MFD_AXP20X && USB_SUPPORT && X86 + select USB_ROLE_SWITCH help Say Y here to enable support for USB peripheral detection and USB MUX switching by X-Power AXP288 PMIC. diff --git a/drivers/extcon/extcon-axp288.c b/drivers/extcon/extcon-axp288.c index 3ec4c715e240..a983708b77a6 100644 --- a/drivers/extcon/extcon-axp288.c +++ b/drivers/extcon/extcon-axp288.c @@ -1,6 +1,7 @@ /* * extcon-axp288.c - X-Power AXP288 PMIC extcon cable detection driver * + * Copyright (c) 2017-2018 Hans de Goede <hdegoede@redhat.com> * Copyright (C) 2015 Intel Corporation * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com> * @@ -14,6 +15,7 @@ * GNU General Public License for more details. */ +#include <linux/acpi.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/io.h> @@ -25,6 +27,11 @@ #include <linux/extcon-provider.h> #include <linux/regmap.h> #include <linux/mfd/axp20x.h> +#include <linux/usb/role.h> +#include <linux/workqueue.h> + +#include <asm/cpu_device_id.h> +#include <asm/intel-family.h> /* Power source status register */ #define PS_STAT_VBUS_TRIGGER BIT(0) @@ -97,9 +104,19 @@ struct axp288_extcon_info { struct device *dev; struct regmap *regmap; struct regmap_irq_chip_data *regmap_irqc; + struct usb_role_switch *role_sw; + struct work_struct role_work; int irq[EXTCON_IRQ_END]; struct extcon_dev *edev; + struct extcon_dev *id_extcon; + struct notifier_block id_nb; unsigned int previous_cable; + bool vbus_attach; +}; + +static const struct x86_cpu_id cherry_trail_cpu_ids[] = { + { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_AIRMONT, X86_FEATURE_ANY }, + {} }; /* Power up/down reason string array */ @@ -137,20 +154,74 @@ static void axp288_extcon_log_rsi(struct axp288_extcon_info *info) regmap_write(info->regmap, AXP288_PS_BOOT_REASON_REG, clear_mask); } -static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info) +/* + * The below code to control the USB role-switch on devices with an AXP288 + * may seem out of place, but there are 2 reasons why this is the best place + * to control the USB role-switch on such devices: + * 1) On many devices the USB role is controlled by AML code, but the AML code + * only switches between the host and none roles, because of Windows not + * really using device mode. To make device mode work we need to toggle + * between the none/device roles based on Vbus presence, and this driver + * gets interrupts on Vbus insertion / removal. + * 2) In order for our BC1.2 charger detection to work properly the role + * mux must be properly set to device mode before we do the detection. + */ + +/* Returns the id-pin value, note pulled low / false == host-mode */ +static bool axp288_get_id_pin(struct axp288_extcon_info *info) { - int ret, stat, cfg, pwr_stat; - u8 chrg_type; - unsigned int cable = info->previous_cable; - bool vbus_attach = false; + enum usb_role role; + + if (info->id_extcon) + return extcon_get_state(info->id_extcon, EXTCON_USB_HOST) <= 0; + + /* We cannot access the id-pin, see what mode the AML code has set */ + role = usb_role_switch_get_role(info->role_sw); + return role != USB_ROLE_HOST; +} + +static void axp288_usb_role_work(struct work_struct *work) +{ + struct axp288_extcon_info *info = + container_of(work, struct axp288_extcon_info, role_work); + enum usb_role role; + bool id_pin; + int ret; + + id_pin = axp288_get_id_pin(info); + if (!id_pin) + role = USB_ROLE_HOST; + else if (info->vbus_attach) + role = USB_ROLE_DEVICE; + else + role = USB_ROLE_NONE; + + ret = usb_role_switch_set_role(info->role_sw, role); + if (ret) + dev_err(info->dev, "failed to set role: %d\n", ret); +} + +static bool axp288_get_vbus_attach(struct axp288_extcon_info *info) +{ + int ret, pwr_stat; ret = regmap_read(info->regmap, AXP288_PS_STAT_REG, &pwr_stat); if (ret < 0) { dev_err(info->dev, "failed to read vbus status\n"); - return ret; + return false; } - vbus_attach = (pwr_stat & PS_STAT_VBUS_VALID); + return !!(pwr_stat & PS_STAT_VBUS_VALID); +} + +static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info) +{ + int ret, stat, cfg; + u8 chrg_type; + unsigned int cable = info->previous_cable; + bool vbus_attach = false; + + vbus_attach = axp288_get_vbus_attach(info); if (!vbus_attach) goto no_vbus; @@ -201,6 +272,12 @@ no_vbus: info->previous_cable = cable; } + if (info->role_sw && info->vbus_attach != vbus_attach) { + info->vbus_attach = vbus_attach; + /* Setting the role can take a while */ + queue_work(system_long_wq, &info->role_work); + } + return 0; dev_det_ret: @@ -210,6 +287,18 @@ dev_det_ret: return ret; } +static int axp288_extcon_id_evt(struct notifier_block *nb, + unsigned long event, void *param) +{ + struct axp288_extcon_info *info = + container_of(nb, struct axp288_extcon_info, id_nb); + + /* We may not sleep and setting the role can take a while */ + queue_work(system_long_wq, &info->role_work); + + return NOTIFY_OK; +} + static irqreturn_t axp288_extcon_isr(int irq, void *data) { struct axp288_extcon_info *info = data; @@ -231,10 +320,20 @@ static void axp288_extcon_enable(struct axp288_extcon_info *info) BC_GLOBAL_RUN, BC_GLOBAL_RUN); } +static void axp288_put_role_sw(void *data) +{ + struct axp288_extcon_info *info = data; + + cancel_work_sync(&info->role_work); + usb_role_switch_put(info->role_sw); +} + static int axp288_extcon_probe(struct platform_device *pdev) { struct axp288_extcon_info *info; struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); + struct device *dev = &pdev->dev; + const char *name; int ret, i, pirq; info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); @@ -245,9 +344,33 @@ static int axp288_extcon_probe(struct platform_device *pdev) info->regmap = axp20x->regmap; info->regmap_irqc = axp20x->regmap_irqc; info->previous_cable = EXTCON_NONE; + INIT_WORK(&info->role_work, axp288_usb_role_work); + info->id_nb.notifier_call = axp288_extcon_id_evt; platform_set_drvdata(pdev, info); + info->role_sw = usb_role_switch_get(dev); + if (IS_ERR(info->role_sw)) + return PTR_ERR(info->role_sw); + if (info->role_sw) { + ret = devm_add_action_or_reset(dev, axp288_put_role_sw, info); + if (ret) + return ret; + + name = acpi_dev_get_first_match_name("INT3496", NULL, -1); + if (name) { + info->id_extcon = extcon_get_extcon_dev(name); + if (!info->id_extcon) + return -EPROBE_DEFER; + + dev_info(dev, "controlling USB role\n"); + } else { + dev_info(dev, "controlling USB role based on Vbus presence\n"); + } + } + + info->vbus_attach = axp288_get_vbus_attach(info); + axp288_extcon_log_rsi(info); /* Initialize extcon device */ @@ -289,6 +412,19 @@ static int axp288_extcon_probe(struct platform_device *pdev) } } + if (info->id_extcon) { + ret = devm_extcon_register_notifier_all(dev, info->id_extcon, + &info->id_nb); + if (ret) + return ret; + } + + /* Make sure the role-sw is set correctly before doing BC detection */ + if (info->role_sw) { + queue_work(system_long_wq, &info->role_work); + flush_work(&info->role_work); + } + /* Start charger cable type detection */ axp288_extcon_enable(info); @@ -308,8 +444,32 @@ static struct platform_driver axp288_extcon_driver = { .name = "axp288_extcon", }, }; -module_platform_driver(axp288_extcon_driver); + +static struct device_connection axp288_extcon_role_sw_conn = { + .endpoint[0] = "axp288_extcon", + .endpoint[1] = "intel_xhci_usb_sw-role-switch", + .id = "usb-role-switch", +}; + +static int __init axp288_extcon_init(void) +{ + if (x86_match_cpu(cherry_trail_cpu_ids)) + device_connection_add(&axp288_extcon_role_sw_conn); + + return platform_driver_register(&axp288_extcon_driver); +} +module_init(axp288_extcon_init); + +static void __exit axp288_extcon_exit(void) +{ + if (x86_match_cpu(cherry_trail_cpu_ids)) + device_connection_remove(&axp288_extcon_role_sw_conn); + + platform_driver_unregister(&axp288_extcon_driver); +} +module_exit(axp288_extcon_exit); MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>"); +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); MODULE_DESCRIPTION("X-Powers AXP288 extcon driver"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/allwinner/phy-sun4i-usb.c b/drivers/phy/allwinner/phy-sun4i-usb.c index aa857be692cf..d4dcd39b8d76 100644 --- a/drivers/phy/allwinner/phy-sun4i-usb.c +++ b/drivers/phy/allwinner/phy-sun4i-usb.c @@ -112,6 +112,7 @@ enum sun4i_usb_phy_type { sun8i_a33_phy, sun8i_a83t_phy, sun8i_h3_phy, + sun8i_r40_phy, sun8i_v3s_phy, sun50i_a64_phy, }; @@ -410,11 +411,13 @@ static bool sun4i_usb_phy0_poll(struct sun4i_usb_phy_data *data) return true; /* - * The A31 companion pmic (axp221) does not generate vbus change - * interrupts when the board is driving vbus, so we must poll + * The A31/A23/A33 companion pmics (AXP221/AXP223) do not + * generate vbus change interrupts when the board is driving + * vbus using the N_VBUSEN pin on the pmic, so we must poll * when using the pmic for vbus-det _and_ we're driving vbus. */ - if (data->cfg->type == sun6i_a31_phy && + if ((data->cfg->type == sun6i_a31_phy || + data->cfg->type == sun8i_a33_phy) && data->vbus_power_supply && data->phys[0].regulator_on) return true; @@ -885,7 +888,7 @@ static const struct sun4i_usb_phy_cfg sun7i_a20_cfg = { static const struct sun4i_usb_phy_cfg sun8i_a23_cfg = { .num_phys = 2, - .type = sun4i_a10_phy, + .type = sun6i_a31_phy, .disc_thresh = 3, .phyctl_offset = REG_PHYCTL_A10, .dedicated_clocks = true, @@ -919,6 +922,16 @@ static const struct sun4i_usb_phy_cfg sun8i_h3_cfg = { .phy0_dual_route = true, }; +static const struct sun4i_usb_phy_cfg sun8i_r40_cfg = { + .num_phys = 3, + .type = sun8i_r40_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A33, + .dedicated_clocks = true, + .enable_pmu_unk1 = true, + .phy0_dual_route = true, +}; + static const struct sun4i_usb_phy_cfg sun8i_v3s_cfg = { .num_phys = 1, .type = sun8i_v3s_phy, @@ -948,6 +961,7 @@ static const struct of_device_id sun4i_usb_phy_of_match[] = { { .compatible = "allwinner,sun8i-a33-usb-phy", .data = &sun8i_a33_cfg }, { .compatible = "allwinner,sun8i-a83t-usb-phy", .data = &sun8i_a83t_cfg }, { .compatible = "allwinner,sun8i-h3-usb-phy", .data = &sun8i_h3_cfg }, + { .compatible = "allwinner,sun8i-r40-usb-phy", .data = &sun8i_r40_cfg }, { .compatible = "allwinner,sun8i-v3s-usb-phy", .data = &sun8i_v3s_cfg }, { .compatible = "allwinner,sun50i-a64-usb-phy", .data = &sun50i_a64_cfg}, diff --git a/drivers/phy/amlogic/Kconfig b/drivers/phy/amlogic/Kconfig index cb8f4501652b..23fe1cda2f70 100644 --- a/drivers/phy/amlogic/Kconfig +++ b/drivers/phy/amlogic/Kconfig @@ -18,10 +18,21 @@ config PHY_MESON_GXL_USB2 default ARCH_MESON depends on OF && (ARCH_MESON || COMPILE_TEST) depends on USB_SUPPORT - select USB_COMMON select GENERIC_PHY select REGMAP_MMIO help Enable this to support the Meson USB2 PHYs found in Meson GXL and GXM SoCs. If unsure, say N. + +config PHY_MESON_GXL_USB3 + tristate "Meson GXL and GXM USB3 PHY drivers" + default ARCH_MESON + depends on OF && (ARCH_MESON || COMPILE_TEST) + depends on USB_SUPPORT + select GENERIC_PHY + select REGMAP_MMIO + help + Enable this to support the Meson USB3 PHY and OTG detection + IP block found in Meson GXL and GXM SoCs. + If unsure, say N. diff --git a/drivers/phy/amlogic/Makefile b/drivers/phy/amlogic/Makefile index cfdc98715c30..4fd8848c194d 100644 --- a/drivers/phy/amlogic/Makefile +++ b/drivers/phy/amlogic/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_PHY_MESON8B_USB2) += phy-meson8b-usb2.o obj-$(CONFIG_PHY_MESON_GXL_USB2) += phy-meson-gxl-usb2.o +obj-$(CONFIG_PHY_MESON_GXL_USB3) += phy-meson-gxl-usb3.o diff --git a/drivers/phy/amlogic/phy-meson-gxl-usb2.c b/drivers/phy/amlogic/phy-meson-gxl-usb2.c index e90c4ee25dfe..9f9b5414b97a 100644 --- a/drivers/phy/amlogic/phy-meson-gxl-usb2.c +++ b/drivers/phy/amlogic/phy-meson-gxl-usb2.c @@ -11,14 +11,15 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <linux/clk.h> #include <linux/delay.h> #include <linux/io.h> #include <linux/module.h> #include <linux/of_device.h> #include <linux/regmap.h> +#include <linux/reset.h> #include <linux/phy/phy.h> #include <linux/platform_device.h> -#include <linux/usb/of.h> /* bits [31:27] are read-only */ #define U2P_R0 0x0 @@ -70,12 +71,11 @@ /* bits [31:14] are read-only */ #define U2P_R2 0x8 - #define U2P_R2_DATA_IN_MASK GENMASK(3, 0) - #define U2P_R2_DATA_IN_EN_MASK GENMASK(7, 4) - #define U2P_R2_ADDR_MASK GENMASK(11, 8) - #define U2P_R2_DATA_OUT_SEL BIT(12) - #define U2P_R2_CLK BIT(13) - #define U2P_R2_DATA_OUT_MASK GENMASK(17, 14) + #define U2P_R2_TESTDATA_IN_MASK GENMASK(7, 0) + #define U2P_R2_TESTADDR_MASK GENMASK(11, 8) + #define U2P_R2_TESTDATA_OUT_SEL BIT(12) + #define U2P_R2_TESTCLK BIT(13) + #define U2P_R2_TESTDATA_OUT_MASK GENMASK(17, 14) #define U2P_R2_ACA_PIN_RANGE_C BIT(18) #define U2P_R2_ACA_PIN_RANGE_B BIT(19) #define U2P_R2_ACA_PIN_RANGE_A BIT(20) @@ -99,6 +99,8 @@ struct phy_meson_gxl_usb2_priv { struct regmap *regmap; enum phy_mode mode; int is_enabled; + struct clk *clk; + struct reset_control *reset; }; static const struct regmap_config phy_meson_gxl_usb2_regmap_conf = { @@ -108,6 +110,31 @@ static const struct regmap_config phy_meson_gxl_usb2_regmap_conf = { .max_register = U2P_R3, }; +static int phy_meson_gxl_usb2_init(struct phy *phy) +{ + struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy); + int ret; + + ret = reset_control_reset(priv->reset); + if (ret) + return ret; + + ret = clk_prepare_enable(priv->clk); + if (ret) + return ret; + + return 0; +} + +static int phy_meson_gxl_usb2_exit(struct phy *phy) +{ + struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy); + + clk_disable_unprepare(priv->clk); + + return 0; +} + static int phy_meson_gxl_usb2_reset(struct phy *phy) { struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy); @@ -195,6 +222,8 @@ static int phy_meson_gxl_usb2_power_on(struct phy *phy) } static const struct phy_ops phy_meson_gxl_usb2_ops = { + .init = phy_meson_gxl_usb2_init, + .exit = phy_meson_gxl_usb2_exit, .power_on = phy_meson_gxl_usb2_power_on, .power_off = phy_meson_gxl_usb2_power_off, .set_mode = phy_meson_gxl_usb2_set_mode, @@ -210,6 +239,7 @@ static int phy_meson_gxl_usb2_probe(struct platform_device *pdev) struct phy_meson_gxl_usb2_priv *priv; struct phy *phy; void __iomem *base; + int ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -222,28 +252,34 @@ static int phy_meson_gxl_usb2_probe(struct platform_device *pdev) if (IS_ERR(base)) return PTR_ERR(base); - switch (of_usb_get_dr_mode_by_phy(dev->of_node, -1)) { - case USB_DR_MODE_PERIPHERAL: - priv->mode = PHY_MODE_USB_DEVICE; - break; - case USB_DR_MODE_OTG: - priv->mode = PHY_MODE_USB_OTG; - break; - case USB_DR_MODE_HOST: - default: - priv->mode = PHY_MODE_USB_HOST; - break; - } + /* start in host mode */ + priv->mode = PHY_MODE_USB_HOST; priv->regmap = devm_regmap_init_mmio(dev, base, &phy_meson_gxl_usb2_regmap_conf); if (IS_ERR(priv->regmap)) return PTR_ERR(priv->regmap); + priv->clk = devm_clk_get(dev, "phy"); + if (IS_ERR(priv->clk)) { + ret = PTR_ERR(priv->clk); + if (ret == -ENOENT) + priv->clk = NULL; + else + return ret; + } + + priv->reset = devm_reset_control_get_optional_shared(dev, "phy"); + if (IS_ERR(priv->reset)) + return PTR_ERR(priv->reset); + phy = devm_phy_create(dev, NULL, &phy_meson_gxl_usb2_ops); if (IS_ERR(phy)) { - dev_err(dev, "failed to create PHY\n"); - return PTR_ERR(phy); + ret = PTR_ERR(phy); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to create PHY\n"); + + return ret; } phy_set_drvdata(phy, priv); diff --git a/drivers/phy/amlogic/phy-meson-gxl-usb3.c b/drivers/phy/amlogic/phy-meson-gxl-usb3.c new file mode 100644 index 000000000000..d37d94ddf9c0 --- /dev/null +++ b/drivers/phy/amlogic/phy-meson-gxl-usb3.c @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Meson GXL USB3 PHY and OTG mode detection driver + * + * Copyright (C) 2018 Martin Blumenstingl <martin.blumenstingl@googlemail.com> + */ + +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/phy/phy.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/platform_device.h> + +#define USB_R0 0x00 + #define USB_R0_P30_FSEL_MASK GENMASK(5, 0) + #define USB_R0_P30_PHY_RESET BIT(6) + #define USB_R0_P30_TEST_POWERDOWN_HSP BIT(7) + #define USB_R0_P30_TEST_POWERDOWN_SSP BIT(8) + #define USB_R0_P30_ACJT_LEVEL_MASK GENMASK(13, 9) + #define USB_R0_P30_TX_BOOST_LEVEL_MASK GENMASK(16, 14) + #define USB_R0_P30_LANE0_TX2RX_LOOPBACK BIT(17) + #define USB_R0_P30_LANE0_EXT_PCLK_REQ BIT(18) + #define USB_R0_P30_PCS_RX_LOS_MASK_VAL_MASK GENMASK(28, 19) + #define USB_R0_U2D_SS_SCALEDOWN_MODE_MASK GENMASK(30, 29) + #define USB_R0_U2D_ACT BIT(31) + +#define USB_R1 0x04 + #define USB_R1_U3H_BIGENDIAN_GS BIT(0) + #define USB_R1_U3H_PME_ENABLE BIT(1) + #define USB_R1_U3H_HUB_PORT_OVERCURRENT_MASK GENMASK(6, 2) + #define USB_R1_U3H_HUB_PORT_PERM_ATTACH_MASK GENMASK(11, 7) + #define USB_R1_U3H_HOST_U2_PORT_DISABLE_MASK GENMASK(15, 12) + #define USB_R1_U3H_HOST_U3_PORT_DISABLE BIT(16) + #define USB_R1_U3H_HOST_PORT_POWER_CONTROL_PRESENT BIT(17) + #define USB_R1_U3H_HOST_MSI_ENABLE BIT(18) + #define USB_R1_U3H_FLADJ_30MHZ_REG_MASK GENMASK(24, 19) + #define USB_R1_P30_PCS_TX_SWING_FULL_MASK GENMASK(31, 25) + +#define USB_R2 0x08 + #define USB_R2_P30_CR_DATA_IN_MASK GENMASK(15, 0) + #define USB_R2_P30_CR_READ BIT(16) + #define USB_R2_P30_CR_WRITE BIT(17) + #define USB_R2_P30_CR_CAP_ADDR BIT(18) + #define USB_R2_P30_CR_CAP_DATA BIT(19) + #define USB_R2_P30_PCS_TX_DEEMPH_3P5DB_MASK GENMASK(25, 20) + #define USB_R2_P30_PCS_TX_DEEMPH_6DB_MASK GENMASK(31, 26) + +#define USB_R3 0x0c + #define USB_R3_P30_SSC_ENABLE BIT(0) + #define USB_R3_P30_SSC_RANGE_MASK GENMASK(3, 1) + #define USB_R3_P30_SSC_REF_CLK_SEL_MASK GENMASK(12, 4) + #define USB_R3_P30_REF_SSP_EN BIT(13) + #define USB_R3_P30_LOS_BIAS_MASK GENMASK(18, 16) + #define USB_R3_P30_LOS_LEVEL_MASK GENMASK(23, 19) + #define USB_R3_P30_MPLL_MULTIPLIER_MASK GENMASK(30, 24) + +#define USB_R4 0x10 + #define USB_R4_P21_PORT_RESET_0 BIT(0) + #define USB_R4_P21_SLEEP_M0 BIT(1) + #define USB_R4_MEM_PD_MASK GENMASK(3, 2) + #define USB_R4_P21_ONLY BIT(4) + +#define USB_R5 0x14 + #define USB_R5_ID_DIG_SYNC BIT(0) + #define USB_R5_ID_DIG_REG BIT(1) + #define USB_R5_ID_DIG_CFG_MASK GENMASK(3, 2) + #define USB_R5_ID_DIG_EN_0 BIT(4) + #define USB_R5_ID_DIG_EN_1 BIT(5) + #define USB_R5_ID_DIG_CURR BIT(6) + #define USB_R5_ID_DIG_IRQ BIT(7) + #define USB_R5_ID_DIG_TH_MASK GENMASK(15, 8) + #define USB_R5_ID_DIG_CNT_MASK GENMASK(23, 16) + +/* read-only register */ +#define USB_R6 0x18 + #define USB_R6_P30_CR_DATA_OUT_MASK GENMASK(15, 0) + #define USB_R6_P30_CR_ACK BIT(16) + +struct phy_meson_gxl_usb3_priv { + struct regmap *regmap; + enum phy_mode mode; + struct clk *clk_phy; + struct clk *clk_peripheral; + struct reset_control *reset; +}; + +static const struct regmap_config phy_meson_gxl_usb3_regmap_conf = { + .reg_bits = 8, + .val_bits = 32, + .reg_stride = 4, + .max_register = USB_R6, +}; + +static int phy_meson_gxl_usb3_power_on(struct phy *phy) +{ + struct phy_meson_gxl_usb3_priv *priv = phy_get_drvdata(phy); + + regmap_update_bits(priv->regmap, USB_R5, USB_R5_ID_DIG_EN_0, + USB_R5_ID_DIG_EN_0); + regmap_update_bits(priv->regmap, USB_R5, USB_R5_ID_DIG_EN_1, + USB_R5_ID_DIG_EN_1); + regmap_update_bits(priv->regmap, USB_R5, USB_R5_ID_DIG_TH_MASK, + FIELD_PREP(USB_R5_ID_DIG_TH_MASK, 0xff)); + + return 0; +} + +static int phy_meson_gxl_usb3_power_off(struct phy *phy) +{ + struct phy_meson_gxl_usb3_priv *priv = phy_get_drvdata(phy); + + regmap_update_bits(priv->regmap, USB_R5, USB_R5_ID_DIG_EN_0, 0); + regmap_update_bits(priv->regmap, USB_R5, USB_R5_ID_DIG_EN_1, 0); + + return 0; +} + +static int phy_meson_gxl_usb3_set_mode(struct phy *phy, enum phy_mode mode) +{ + struct phy_meson_gxl_usb3_priv *priv = phy_get_drvdata(phy); + + switch (mode) { + case PHY_MODE_USB_HOST: + regmap_update_bits(priv->regmap, USB_R0, USB_R0_U2D_ACT, 0); + regmap_update_bits(priv->regmap, USB_R4, USB_R4_P21_SLEEP_M0, + 0); + break; + + case PHY_MODE_USB_DEVICE: + regmap_update_bits(priv->regmap, USB_R0, USB_R0_U2D_ACT, + USB_R0_U2D_ACT); + regmap_update_bits(priv->regmap, USB_R4, USB_R4_P21_SLEEP_M0, + USB_R4_P21_SLEEP_M0); + break; + + default: + dev_err(&phy->dev, "unsupported PHY mode %d\n", mode); + return -EINVAL; + } + + priv->mode = mode; + + return 0; +} + +static int phy_meson_gxl_usb3_init(struct phy *phy) +{ + struct phy_meson_gxl_usb3_priv *priv = phy_get_drvdata(phy); + int ret; + + ret = reset_control_reset(priv->reset); + if (ret) + goto err; + + ret = clk_prepare_enable(priv->clk_phy); + if (ret) + goto err; + + ret = clk_prepare_enable(priv->clk_peripheral); + if (ret) + goto err_disable_clk_phy; + + ret = phy_meson_gxl_usb3_set_mode(phy, priv->mode); + if (ret) + goto err_disable_clk_peripheral; + + regmap_update_bits(priv->regmap, USB_R1, + USB_R1_U3H_FLADJ_30MHZ_REG_MASK, + FIELD_PREP(USB_R1_U3H_FLADJ_30MHZ_REG_MASK, 0x20)); + + return 0; + +err_disable_clk_peripheral: + clk_disable_unprepare(priv->clk_peripheral); +err_disable_clk_phy: + clk_disable_unprepare(priv->clk_phy); +err: + return ret; +} + +static int phy_meson_gxl_usb3_exit(struct phy *phy) +{ + struct phy_meson_gxl_usb3_priv *priv = phy_get_drvdata(phy); + + clk_disable_unprepare(priv->clk_peripheral); + clk_disable_unprepare(priv->clk_phy); + + return 0; +} + +static const struct phy_ops phy_meson_gxl_usb3_ops = { + .power_on = phy_meson_gxl_usb3_power_on, + .power_off = phy_meson_gxl_usb3_power_off, + .set_mode = phy_meson_gxl_usb3_set_mode, + .init = phy_meson_gxl_usb3_init, + .exit = phy_meson_gxl_usb3_exit, + .owner = THIS_MODULE, +}; + +static int phy_meson_gxl_usb3_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct phy_meson_gxl_usb3_priv *priv; + struct resource *res; + struct phy *phy; + struct phy_provider *phy_provider; + void __iomem *base; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->regmap = devm_regmap_init_mmio(dev, base, + &phy_meson_gxl_usb3_regmap_conf); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->clk_phy = devm_clk_get(dev, "phy"); + if (IS_ERR(priv->clk_phy)) + return PTR_ERR(priv->clk_phy); + + priv->clk_peripheral = devm_clk_get(dev, "peripheral"); + if (IS_ERR(priv->clk_peripheral)) + return PTR_ERR(priv->clk_peripheral); + + priv->reset = devm_reset_control_array_get_shared(dev); + if (IS_ERR(priv->reset)) + return PTR_ERR(priv->reset); + + /* + * default to host mode as hardware defaults and/or boot-loader + * behavior can result in this PHY starting up in device mode. this + * default and the initialization in phy_meson_gxl_usb3_init ensure + * that we reproducibly start in a known mode on all devices. + */ + priv->mode = PHY_MODE_USB_HOST; + + phy = devm_phy_create(dev, np, &phy_meson_gxl_usb3_ops); + if (IS_ERR(phy)) { + ret = PTR_ERR(phy); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to create PHY\n"); + + return ret; + } + + phy_set_drvdata(phy, priv); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id phy_meson_gxl_usb3_of_match[] = { + { .compatible = "amlogic,meson-gxl-usb3-phy", }, + { }, +}; +MODULE_DEVICE_TABLE(of, phy_meson_gxl_usb3_of_match); + +static struct platform_driver phy_meson_gxl_usb3_driver = { + .probe = phy_meson_gxl_usb3_probe, + .driver = { + .name = "phy-meson-gxl-usb3", + .of_match_table = phy_meson_gxl_usb3_of_match, + }, +}; +module_platform_driver(phy_meson_gxl_usb3_driver); + +MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>"); +MODULE_DESCRIPTION("Meson GXL USB3 PHY and OTG detection driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/hisilicon/Kconfig b/drivers/phy/hisilicon/Kconfig index 6164c4cd0f65..b40ee54a1a50 100644 --- a/drivers/phy/hisilicon/Kconfig +++ b/drivers/phy/hisilicon/Kconfig @@ -4,6 +4,7 @@ config PHY_HI6220_USB tristate "hi6220 USB PHY support" depends on (ARCH_HISI && ARM64) || COMPILE_TEST + depends on HAS_IOMEM select GENERIC_PHY select MFD_SYSCON help @@ -11,6 +12,25 @@ config PHY_HI6220_USB To compile this driver as a module, choose M here. +config PHY_HISTB_COMBPHY + tristate "HiSilicon STB SoCs COMBPHY support" + depends on (ARCH_HISI && ARM64) || COMPILE_TEST + select GENERIC_PHY + select MFD_SYSCON + help + Enable this to support the HISILICON STB SoCs COMBPHY. + If unsure, say N. + +config PHY_HISI_INNO_USB2 + tristate "HiSilicon INNO USB2 PHY support" + depends on (ARCH_HISI && ARM64) || COMPILE_TEST + select GENERIC_PHY + select MFD_SYSCON + help + Support for INNO USB2 PHY on HiSilicon SoCs. This Phy supports + USB 1.5Mb/s, USB 12Mb/s, USB 480Mb/s speeds. It supports one + USB host port to accept one USB device. + config PHY_HIX5HD2_SATA tristate "HIX5HD2 SATA PHY Driver" depends on ARCH_HIX5HD2 && OF && HAS_IOMEM diff --git a/drivers/phy/hisilicon/Makefile b/drivers/phy/hisilicon/Makefile index 541b348187a8..f662a4fe18d8 100644 --- a/drivers/phy/hisilicon/Makefile +++ b/drivers/phy/hisilicon/Makefile @@ -1,2 +1,4 @@ obj-$(CONFIG_PHY_HI6220_USB) += phy-hi6220-usb.o +obj-$(CONFIG_PHY_HISTB_COMBPHY) += phy-histb-combphy.o +obj-$(CONFIG_PHY_HISI_INNO_USB2) += phy-hisi-inno-usb2.o obj-$(CONFIG_PHY_HIX5HD2_SATA) += phy-hix5hd2-sata.o diff --git a/drivers/phy/hisilicon/phy-hisi-inno-usb2.c b/drivers/phy/hisilicon/phy-hisi-inno-usb2.c new file mode 100644 index 000000000000..524381249a2b --- /dev/null +++ b/drivers/phy/hisilicon/phy-hisi-inno-usb2.c @@ -0,0 +1,197 @@ +/* + * HiSilicon INNO USB2 PHY Driver. + * + * Copyright (c) 2016-2017 HiSilicon Technologies Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/phy/phy.h> +#include <linux/reset.h> + +#define INNO_PHY_PORT_NUM 2 +#define REF_CLK_STABLE_TIME 100 /* unit:us */ +#define UTMI_CLK_STABLE_TIME 200 /* unit:us */ +#define TEST_CLK_STABLE_TIME 2 /* unit:ms */ +#define PHY_CLK_STABLE_TIME 2 /* unit:ms */ +#define UTMI_RST_COMPLETE_TIME 2 /* unit:ms */ +#define POR_RST_COMPLETE_TIME 300 /* unit:us */ +#define PHY_TEST_DATA GENMASK(7, 0) +#define PHY_TEST_ADDR GENMASK(15, 8) +#define PHY_TEST_PORT GENMASK(18, 16) +#define PHY_TEST_WREN BIT(21) +#define PHY_TEST_CLK BIT(22) /* rising edge active */ +#define PHY_TEST_RST BIT(23) /* low active */ +#define PHY_CLK_ENABLE BIT(2) + +struct hisi_inno_phy_port { + struct reset_control *utmi_rst; + struct hisi_inno_phy_priv *priv; +}; + +struct hisi_inno_phy_priv { + void __iomem *mmio; + struct clk *ref_clk; + struct reset_control *por_rst; + struct hisi_inno_phy_port ports[INNO_PHY_PORT_NUM]; +}; + +static void hisi_inno_phy_write_reg(struct hisi_inno_phy_priv *priv, + u8 port, u32 addr, u32 data) +{ + void __iomem *reg = priv->mmio; + u32 val; + + val = (data & PHY_TEST_DATA) | + ((addr << 8) & PHY_TEST_ADDR) | + ((port << 16) & PHY_TEST_PORT) | + PHY_TEST_WREN | PHY_TEST_RST; + writel(val, reg); + + val |= PHY_TEST_CLK; + writel(val, reg); + + val &= ~PHY_TEST_CLK; + writel(val, reg); +} + +static void hisi_inno_phy_setup(struct hisi_inno_phy_priv *priv) +{ + /* The phy clk is controlled by the port0 register 0x06. */ + hisi_inno_phy_write_reg(priv, 0, 0x06, PHY_CLK_ENABLE); + msleep(PHY_CLK_STABLE_TIME); +} + +static int hisi_inno_phy_init(struct phy *phy) +{ + struct hisi_inno_phy_port *port = phy_get_drvdata(phy); + struct hisi_inno_phy_priv *priv = port->priv; + int ret; + + ret = clk_prepare_enable(priv->ref_clk); + if (ret) + return ret; + udelay(REF_CLK_STABLE_TIME); + + reset_control_deassert(priv->por_rst); + udelay(POR_RST_COMPLETE_TIME); + + /* Set up phy registers */ + hisi_inno_phy_setup(priv); + + reset_control_deassert(port->utmi_rst); + udelay(UTMI_RST_COMPLETE_TIME); + + return 0; +} + +static int hisi_inno_phy_exit(struct phy *phy) +{ + struct hisi_inno_phy_port *port = phy_get_drvdata(phy); + struct hisi_inno_phy_priv *priv = port->priv; + + reset_control_assert(port->utmi_rst); + reset_control_assert(priv->por_rst); + clk_disable_unprepare(priv->ref_clk); + + return 0; +} + +static const struct phy_ops hisi_inno_phy_ops = { + .init = hisi_inno_phy_init, + .exit = hisi_inno_phy_exit, + .owner = THIS_MODULE, +}; + +static int hisi_inno_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct hisi_inno_phy_priv *priv; + struct phy_provider *provider; + struct device_node *child; + struct resource *res; + int i = 0; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->mmio = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->mmio)) { + ret = PTR_ERR(priv->mmio); + return ret; + } + + priv->ref_clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->ref_clk)) + return PTR_ERR(priv->ref_clk); + + priv->por_rst = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(priv->por_rst)) + return PTR_ERR(priv->por_rst); + + for_each_child_of_node(np, child) { + struct reset_control *rst; + struct phy *phy; + + rst = of_reset_control_get_exclusive(child, NULL); + if (IS_ERR(rst)) + return PTR_ERR(rst); + priv->ports[i].utmi_rst = rst; + priv->ports[i].priv = priv; + + phy = devm_phy_create(dev, child, &hisi_inno_phy_ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + phy_set_bus_width(phy, 8); + phy_set_drvdata(phy, &priv->ports[i]); + i++; + + if (i > INNO_PHY_PORT_NUM) { + dev_warn(dev, "Support %d ports in maximum\n", i); + break; + } + } + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(provider); +} + +static const struct of_device_id hisi_inno_phy_of_match[] = { + { .compatible = "hisilicon,inno-usb2-phy", }, + { .compatible = "hisilicon,hi3798cv200-usb2-phy", }, + { }, +}; +MODULE_DEVICE_TABLE(of, hisi_inno_phy_of_match); + +static struct platform_driver hisi_inno_phy_driver = { + .probe = hisi_inno_phy_probe, + .driver = { + .name = "hisi-inno-phy", + .of_match_table = hisi_inno_phy_of_match, + } +}; +module_platform_driver(hisi_inno_phy_driver); + +MODULE_DESCRIPTION("HiSilicon INNO USB2 PHY Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/hisilicon/phy-histb-combphy.c b/drivers/phy/hisilicon/phy-histb-combphy.c new file mode 100644 index 000000000000..5777b3120017 --- /dev/null +++ b/drivers/phy/hisilicon/phy-histb-combphy.c @@ -0,0 +1,289 @@ +/* + * COMBPHY driver for HiSilicon STB SoCs + * + * Copyright (C) 2016-2017 HiSilicon Co., Ltd. http://www.hisilicon.com + * + * Authors: Jianguo Sun <sunjianguo1@huawei.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/phy/phy.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <dt-bindings/phy/phy.h> + +#define COMBPHY_MODE_PCIE 0 +#define COMBPHY_MODE_USB3 1 +#define COMBPHY_MODE_SATA 2 + +#define COMBPHY_CFG_REG 0x0 +#define COMBPHY_BYPASS_CODEC BIT(31) +#define COMBPHY_TEST_WRITE BIT(24) +#define COMBPHY_TEST_DATA_SHIFT 20 +#define COMBPHY_TEST_DATA_MASK GENMASK(23, 20) +#define COMBPHY_TEST_ADDR_SHIFT 12 +#define COMBPHY_TEST_ADDR_MASK GENMASK(16, 12) +#define COMBPHY_CLKREF_OUT_OEN BIT(0) + +struct histb_combphy_mode { + int fixed; + int select; + u32 reg; + u32 shift; + u32 mask; +}; + +struct histb_combphy_priv { + void __iomem *mmio; + struct regmap *syscon; + struct reset_control *por_rst; + struct clk *ref_clk; + struct phy *phy; + struct histb_combphy_mode mode; +}; + +static void nano_register_write(struct histb_combphy_priv *priv, + u32 addr, u32 data) +{ + void __iomem *reg = priv->mmio + COMBPHY_CFG_REG; + u32 val; + + /* Set up address and data for the write */ + val = readl(reg); + val &= ~COMBPHY_TEST_ADDR_MASK; + val |= addr << COMBPHY_TEST_ADDR_SHIFT; + val &= ~COMBPHY_TEST_DATA_MASK; + val |= data << COMBPHY_TEST_DATA_SHIFT; + writel(val, reg); + + /* Flip strobe control to trigger the write */ + val &= ~COMBPHY_TEST_WRITE; + writel(val, reg); + val |= COMBPHY_TEST_WRITE; + writel(val, reg); +} + +static int is_mode_fixed(struct histb_combphy_mode *mode) +{ + return (mode->fixed != PHY_NONE) ? true : false; +} + +static int histb_combphy_set_mode(struct histb_combphy_priv *priv) +{ + struct histb_combphy_mode *mode = &priv->mode; + struct regmap *syscon = priv->syscon; + u32 hw_sel; + + if (is_mode_fixed(mode)) + return 0; + + switch (mode->select) { + case PHY_TYPE_SATA: + hw_sel = COMBPHY_MODE_SATA; + break; + case PHY_TYPE_PCIE: + hw_sel = COMBPHY_MODE_PCIE; + break; + case PHY_TYPE_USB3: + hw_sel = COMBPHY_MODE_USB3; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(syscon, mode->reg, mode->mask, + hw_sel << mode->shift); +} + +static int histb_combphy_init(struct phy *phy) +{ + struct histb_combphy_priv *priv = phy_get_drvdata(phy); + u32 val; + int ret; + + ret = histb_combphy_set_mode(priv); + if (ret) + return ret; + + /* Clear bypass bit to enable encoding/decoding */ + val = readl(priv->mmio + COMBPHY_CFG_REG); + val &= ~COMBPHY_BYPASS_CODEC; + writel(val, priv->mmio + COMBPHY_CFG_REG); + + ret = clk_prepare_enable(priv->ref_clk); + if (ret) + return ret; + + reset_control_deassert(priv->por_rst); + + /* Enable EP clock */ + val = readl(priv->mmio + COMBPHY_CFG_REG); + val |= COMBPHY_CLKREF_OUT_OEN; + writel(val, priv->mmio + COMBPHY_CFG_REG); + + /* Need to wait for EP clock stable */ + mdelay(5); + + /* Configure nano phy registers as suggested by vendor */ + nano_register_write(priv, 0x1, 0x8); + nano_register_write(priv, 0xc, 0x9); + nano_register_write(priv, 0x1a, 0x4); + + return 0; +} + +static int histb_combphy_exit(struct phy *phy) +{ + struct histb_combphy_priv *priv = phy_get_drvdata(phy); + u32 val; + + /* Disable EP clock */ + val = readl(priv->mmio + COMBPHY_CFG_REG); + val &= ~COMBPHY_CLKREF_OUT_OEN; + writel(val, priv->mmio + COMBPHY_CFG_REG); + + reset_control_assert(priv->por_rst); + clk_disable_unprepare(priv->ref_clk); + + return 0; +} + +static const struct phy_ops histb_combphy_ops = { + .init = histb_combphy_init, + .exit = histb_combphy_exit, + .owner = THIS_MODULE, +}; + +static struct phy *histb_combphy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct histb_combphy_priv *priv = dev_get_drvdata(dev); + struct histb_combphy_mode *mode = &priv->mode; + + if (args->args_count < 1) { + dev_err(dev, "invalid number of arguments\n"); + return ERR_PTR(-EINVAL); + } + + mode->select = args->args[0]; + + if (mode->select < PHY_TYPE_SATA || mode->select > PHY_TYPE_USB3) { + dev_err(dev, "invalid phy mode select argument\n"); + return ERR_PTR(-EINVAL); + } + + if (is_mode_fixed(mode) && mode->select != mode->fixed) { + dev_err(dev, "mode select %d mismatch fixed phy mode %d\n", + mode->select, mode->fixed); + return ERR_PTR(-EINVAL); + } + + return priv->phy; +} + +static int histb_combphy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct histb_combphy_priv *priv; + struct device_node *np = dev->of_node; + struct histb_combphy_mode *mode; + struct resource *res; + u32 vals[3]; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->mmio = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->mmio)) { + ret = PTR_ERR(priv->mmio); + return ret; + } + + priv->syscon = syscon_node_to_regmap(np->parent); + if (IS_ERR(priv->syscon)) { + dev_err(dev, "failed to find peri_ctrl syscon regmap\n"); + return PTR_ERR(priv->syscon); + } + + mode = &priv->mode; + mode->fixed = PHY_NONE; + + ret = of_property_read_u32(np, "hisilicon,fixed-mode", &mode->fixed); + if (ret == 0) + dev_dbg(dev, "found fixed phy mode %d\n", mode->fixed); + + ret = of_property_read_u32_array(np, "hisilicon,mode-select-bits", + vals, ARRAY_SIZE(vals)); + if (ret == 0) { + if (is_mode_fixed(mode)) { + dev_err(dev, "found select bits for fixed mode phy\n"); + return -EINVAL; + } + + mode->reg = vals[0]; + mode->shift = vals[1]; + mode->mask = vals[2]; + dev_dbg(dev, "found mode select bits\n"); + } else { + if (!is_mode_fixed(mode)) { + dev_err(dev, "no valid select bits found for non-fixed phy\n"); + return -ENODEV; + } + } + + priv->ref_clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->ref_clk)) { + dev_err(dev, "failed to find ref clock\n"); + return PTR_ERR(priv->ref_clk); + } + + priv->por_rst = devm_reset_control_get(dev, NULL); + if (IS_ERR(priv->por_rst)) { + dev_err(dev, "failed to get poweron reset\n"); + return PTR_ERR(priv->por_rst); + } + + priv->phy = devm_phy_create(dev, NULL, &histb_combphy_ops); + if (IS_ERR(priv->phy)) { + dev_err(dev, "failed to create combphy\n"); + return PTR_ERR(priv->phy); + } + + dev_set_drvdata(dev, priv); + phy_set_drvdata(priv->phy, priv); + + phy_provider = devm_of_phy_provider_register(dev, histb_combphy_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id histb_combphy_of_match[] = { + { .compatible = "hisilicon,hi3798cv200-combphy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, histb_combphy_of_match); + +static struct platform_driver histb_combphy_driver = { + .probe = histb_combphy_probe, + .driver = { + .name = "combphy", + .of_match_table = histb_combphy_of_match, + }, +}; +module_platform_driver(histb_combphy_driver); + +MODULE_DESCRIPTION("HiSilicon STB COMBPHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/marvell/phy-berlin-usb.c b/drivers/phy/marvell/phy-berlin-usb.c index 2017751ede26..8f2b5cae360f 100644 --- a/drivers/phy/marvell/phy-berlin-usb.c +++ b/drivers/phy/marvell/phy-berlin-usb.c @@ -127,7 +127,7 @@ static int phy_berlin_usb_power_on(struct phy *phy) writel(V2I_VCO_RATIO(0x5) | R_ROTATE_0 | ANA_TEST_DC_CTRL(0x5), priv->base + USB_PHY_ANALOG); writel(PHASE_FREEZE_DLY_4_CL | ACK_LENGTH_16_CL | SQ_LENGTH_12 | - DISCON_THRESHOLD_260 | SQ_THRESHOLD(0xa) | LPF_COEF(0x2) | + DISCON_THRESHOLD_270 | SQ_THRESHOLD(0xa) | LPF_COEF(0x2) | INTPL_CUR_30, priv->base + USB_PHY_RX_CTRL); writel(TX_VDD12_13 | TX_OUT_AMP(0x3), priv->base + USB_PHY_TX_CTRL1); diff --git a/drivers/phy/mediatek/phy-mtk-tphy.c b/drivers/phy/mediatek/phy-mtk-tphy.c index 1e96d0740ef5..38c281b5abbb 100644 --- a/drivers/phy/mediatek/phy-mtk-tphy.c +++ b/drivers/phy/mediatek/phy-mtk-tphy.c @@ -306,6 +306,8 @@ struct mtk_tphy { const struct mtk_phy_pdata *pdata; struct mtk_phy_instance **phys; int nphys; + int src_ref_clk; /* MHZ, reference clock for slew rate calibrate */ + int src_coef; /* coefficient for slew rate calibrate */ }; static void hs_slew_rate_calibrate(struct mtk_tphy *tphy, @@ -360,16 +362,17 @@ static void hs_slew_rate_calibrate(struct mtk_tphy *tphy, writel(tmp, fmreg + U3P_U2FREQ_FMMONR1); if (fm_out) { - /* ( 1024 / FM_OUT ) x reference clock frequency x 0.028 */ - tmp = U3P_FM_DET_CYCLE_CNT * U3P_REF_CLK * U3P_SLEW_RATE_COEF; - tmp /= fm_out; + /* ( 1024 / FM_OUT ) x reference clock frequency x coef */ + tmp = tphy->src_ref_clk * tphy->src_coef; + tmp = (tmp * U3P_FM_DET_CYCLE_CNT) / fm_out; calibration_val = DIV_ROUND_CLOSEST(tmp, U3P_SR_COEF_DIVISOR); } else { /* if FM detection fail, set default value */ calibration_val = 4; } - dev_dbg(tphy->dev, "phy:%d, fm_out:%d, calib:%d\n", - instance->index, fm_out, calibration_val); + dev_dbg(tphy->dev, "phy:%d, fm_out:%d, calib:%d (clk:%d, coef:%d)\n", + instance->index, fm_out, calibration_val, + tphy->src_ref_clk, tphy->src_coef); /* set HS slew rate */ tmp = readl(com + U3P_USBPHYACR5); @@ -688,8 +691,7 @@ static void pcie_phy_instance_power_on(struct mtk_tphy *tphy, u32 tmp; tmp = readl(bank->chip + U3P_U3_CHIP_GPIO_CTLD); - tmp &= ~(P3C_FORCE_IP_SW_RST | P3C_MCU_BUS_CK_GATE_EN | - P3C_REG_IP_SW_RST); + tmp &= ~(P3C_FORCE_IP_SW_RST | P3C_REG_IP_SW_RST); writel(tmp, bank->chip + U3P_U3_CHIP_GPIO_CTLD); tmp = readl(bank->chip + U3P_U3_CHIP_GPIO_CTLE); @@ -1042,6 +1044,13 @@ static int mtk_tphy_probe(struct platform_device *pdev) tphy->u3phya_ref = NULL; } + tphy->src_ref_clk = U3P_REF_CLK; + tphy->src_coef = U3P_SLEW_RATE_COEF; + /* update parameters of slew rate calibrate if exist */ + device_property_read_u32(dev, "mediatek,src-ref-clk-mhz", + &tphy->src_ref_clk); + device_property_read_u32(dev, "mediatek,src-coef", &tphy->src_coef); + port = 0; for_each_child_of_node(np, child_np) { struct mtk_phy_instance *instance; diff --git a/drivers/phy/motorola/Kconfig b/drivers/phy/motorola/Kconfig index 6bb7d6bdf1bf..82651524ffb9 100644 --- a/drivers/phy/motorola/Kconfig +++ b/drivers/phy/motorola/Kconfig @@ -10,3 +10,11 @@ config PHY_CPCAP_USB help Enable this for USB to work on Motorola phones and tablets such as Droid 4. + +config PHY_MAPPHONE_MDM6600 + tristate "Motorola Mapphone MDM6600 modem USB PHY driver" + depends on OF && USB_SUPPORT + select GENERIC_PHY + help + Enable this for MDM6600 USB modem to work on Motorola phones + and tablets such as Droid 4. diff --git a/drivers/phy/motorola/Makefile b/drivers/phy/motorola/Makefile index b6cd618d671a..3514f985b355 100644 --- a/drivers/phy/motorola/Makefile +++ b/drivers/phy/motorola/Makefile @@ -3,3 +3,4 @@ # obj-$(CONFIG_PHY_CPCAP_USB) += phy-cpcap-usb.o +obj-$(CONFIG_PHY_MAPPHONE_MDM6600) += phy-mapphone-mdm6600.o diff --git a/drivers/phy/motorola/phy-mapphone-mdm6600.c b/drivers/phy/motorola/phy-mapphone-mdm6600.c new file mode 100644 index 000000000000..5439dd90d0dd --- /dev/null +++ b/drivers/phy/motorola/phy-mapphone-mdm6600.c @@ -0,0 +1,542 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Motorola Mapphone MDM6600 modem GPIO controlled USB PHY driver + * Copyright (C) 2018 Tony Lindgren <tony@atomide.com> + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <linux/gpio/consumer.h> +#include <linux/of_platform.h> +#include <linux/phy/phy.h> + +#define PHY_MDM6600_PHY_DELAY_MS 4000 /* PHY enable 2.2s to 3.5s */ +#define PHY_MDM6600_ENABLED_DELAY_MS 8000 /* 8s more total for MDM6600 */ + +enum phy_mdm6600_ctrl_lines { + PHY_MDM6600_ENABLE, /* USB PHY enable */ + PHY_MDM6600_POWER, /* Device power */ + PHY_MDM6600_RESET, /* Device reset */ + PHY_MDM6600_NR_CTRL_LINES, +}; + +enum phy_mdm6600_bootmode_lines { + PHY_MDM6600_MODE0, /* out USB mode0 and OOB wake */ + PHY_MDM6600_MODE1, /* out USB mode1, in OOB wake */ + PHY_MDM6600_NR_MODE_LINES, +}; + +enum phy_mdm6600_cmd_lines { + PHY_MDM6600_CMD0, + PHY_MDM6600_CMD1, + PHY_MDM6600_CMD2, + PHY_MDM6600_NR_CMD_LINES, +}; + +enum phy_mdm6600_status_lines { + PHY_MDM6600_STATUS0, + PHY_MDM6600_STATUS1, + PHY_MDM6600_STATUS2, + PHY_MDM6600_NR_STATUS_LINES, +}; + +/* + * MDM6600 command codes. These are based on Motorola Mapphone Linux + * kernel tree. + */ +enum phy_mdm6600_cmd { + PHY_MDM6600_CMD_BP_PANIC_ACK, + PHY_MDM6600_CMD_DATA_ONLY_BYPASS, /* Reroute USB to CPCAP PHY */ + PHY_MDM6600_CMD_FULL_BYPASS, /* Reroute USB to CPCAP PHY */ + PHY_MDM6600_CMD_NO_BYPASS, /* Request normal USB mode */ + PHY_MDM6600_CMD_BP_SHUTDOWN_REQ, /* Request device power off */ + PHY_MDM6600_CMD_BP_UNKNOWN_5, + PHY_MDM6600_CMD_BP_UNKNOWN_6, + PHY_MDM6600_CMD_UNDEFINED, +}; + +/* + * MDM6600 status codes. These are based on Motorola Mapphone Linux + * kernel tree. + */ +enum phy_mdm6600_status { + PHY_MDM6600_STATUS_PANIC, /* Seems to be really off */ + PHY_MDM6600_STATUS_PANIC_BUSY_WAIT, + PHY_MDM6600_STATUS_QC_DLOAD, + PHY_MDM6600_STATUS_RAM_DOWNLOADER, /* MDM6600 USB flashing mode */ + PHY_MDM6600_STATUS_PHONE_CODE_AWAKE, /* MDM6600 normal USB mode */ + PHY_MDM6600_STATUS_PHONE_CODE_ASLEEP, + PHY_MDM6600_STATUS_SHUTDOWN_ACK, + PHY_MDM6600_STATUS_UNDEFINED, +}; + +static const char * const +phy_mdm6600_status_name[] = { + "off", "busy", "qc_dl", "ram_dl", "awake", + "asleep", "shutdown", "undefined", +}; + +struct phy_mdm6600 { + struct device *dev; + struct phy *generic_phy; + struct phy_provider *phy_provider; + struct gpio_desc *ctrl_gpios[PHY_MDM6600_NR_CTRL_LINES]; + struct gpio_descs *mode_gpios; + struct gpio_descs *status_gpios; + struct gpio_descs *cmd_gpios; + struct delayed_work bootup_work; + struct delayed_work status_work; + struct completion ack; + bool enabled; /* mdm6600 phy enabled */ + bool running; /* mdm6600 boot done */ + int status; +}; + +static int phy_mdm6600_init(struct phy *x) +{ + struct phy_mdm6600 *ddata = phy_get_drvdata(x); + struct gpio_desc *enable_gpio = ddata->ctrl_gpios[PHY_MDM6600_ENABLE]; + + if (!ddata->enabled) + return -EPROBE_DEFER; + + gpiod_set_value_cansleep(enable_gpio, 0); + + return 0; +} + +static int phy_mdm6600_power_on(struct phy *x) +{ + struct phy_mdm6600 *ddata = phy_get_drvdata(x); + struct gpio_desc *enable_gpio = ddata->ctrl_gpios[PHY_MDM6600_ENABLE]; + + if (!ddata->enabled) + return -ENODEV; + + gpiod_set_value_cansleep(enable_gpio, 1); + + return 0; +} + +static int phy_mdm6600_power_off(struct phy *x) +{ + struct phy_mdm6600 *ddata = phy_get_drvdata(x); + struct gpio_desc *enable_gpio = ddata->ctrl_gpios[PHY_MDM6600_ENABLE]; + + if (!ddata->enabled) + return -ENODEV; + + gpiod_set_value_cansleep(enable_gpio, 0); + + return 0; +} + +static const struct phy_ops gpio_usb_ops = { + .init = phy_mdm6600_init, + .power_on = phy_mdm6600_power_on, + .power_off = phy_mdm6600_power_off, + .owner = THIS_MODULE, +}; + +/** + * phy_mdm6600_cmd() - send a command request to mdm6600 + * @ddata: device driver data + * + * Configures the three command request GPIOs to the specified value. + */ +static void phy_mdm6600_cmd(struct phy_mdm6600 *ddata, int val) +{ + int values[PHY_MDM6600_NR_CMD_LINES]; + int i; + + val &= (1 << PHY_MDM6600_NR_CMD_LINES) - 1; + for (i = 0; i < PHY_MDM6600_NR_CMD_LINES; i++) + values[i] = (val & BIT(i)) >> i; + + gpiod_set_array_value_cansleep(PHY_MDM6600_NR_CMD_LINES, + ddata->cmd_gpios->desc, values); +} + +/** + * phy_mdm6600_status() - read mdm6600 status lines + * @ddata: device driver data + */ +static void phy_mdm6600_status(struct work_struct *work) +{ + struct phy_mdm6600 *ddata; + struct device *dev; + int values[PHY_MDM6600_NR_STATUS_LINES]; + int error, i, val = 0; + + ddata = container_of(work, struct phy_mdm6600, status_work.work); + dev = ddata->dev; + + error = gpiod_get_array_value_cansleep(PHY_MDM6600_NR_CMD_LINES, + ddata->status_gpios->desc, + values); + if (error) + return; + + for (i = 0; i < PHY_MDM6600_NR_CMD_LINES; i++) { + val |= values[i] << i; + dev_dbg(ddata->dev, "XXX %s: i: %i values[i]: %i val: %i\n", + __func__, i, values[i], val); + } + ddata->status = val; + + dev_info(dev, "modem status: %i %s\n", + ddata->status, + phy_mdm6600_status_name[ddata->status & 7]); + complete(&ddata->ack); +} + +static irqreturn_t phy_mdm6600_irq_thread(int irq, void *data) +{ + struct phy_mdm6600 *ddata = data; + + schedule_delayed_work(&ddata->status_work, msecs_to_jiffies(10)); + + return IRQ_HANDLED; +} + +/** + * phy_mdm6600_wakeirq_thread - handle mode1 line OOB wake after booting + * @irq: interrupt + * @data: interrupt handler data + * + * GPIO mode1 is used initially as output to configure the USB boot + * mode for mdm6600. After booting it is used as input for OOB wake + * signal from mdm6600 to the SoC. Just use it for debug info only + * for now. + */ +static irqreturn_t phy_mdm6600_wakeirq_thread(int irq, void *data) +{ + struct phy_mdm6600 *ddata = data; + struct gpio_desc *mode_gpio1; + + mode_gpio1 = ddata->mode_gpios->desc[PHY_MDM6600_MODE1]; + dev_dbg(ddata->dev, "OOB wake on mode_gpio1: %i\n", + gpiod_get_value(mode_gpio1)); + + return IRQ_HANDLED; +} + +/** + * phy_mdm6600_init_irq() - initialize mdm6600 status IRQ lines + * @ddata: device driver data + */ +static void phy_mdm6600_init_irq(struct phy_mdm6600 *ddata) +{ + struct device *dev = ddata->dev; + int i, error, irq; + + for (i = PHY_MDM6600_STATUS0; + i <= PHY_MDM6600_STATUS2; i++) { + struct gpio_desc *gpio = ddata->status_gpios->desc[i]; + + irq = gpiod_to_irq(gpio); + if (irq <= 0) + continue; + + error = devm_request_threaded_irq(dev, irq, NULL, + phy_mdm6600_irq_thread, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "mdm6600", + ddata); + if (error) + dev_warn(dev, "no modem status irq%i: %i\n", + irq, error); + } +} + +struct phy_mdm6600_map { + const char *name; + int direction; +}; + +static const struct phy_mdm6600_map +phy_mdm6600_ctrl_gpio_map[PHY_MDM6600_NR_CTRL_LINES] = { + { "enable", GPIOD_OUT_LOW, }, /* low = phy disabled */ + { "power", GPIOD_OUT_LOW, }, /* low = off */ + { "reset", GPIOD_OUT_HIGH, }, /* high = reset */ +}; + +/** + * phy_mdm6600_init_lines() - initialize mdm6600 GPIO lines + * @ddata: device driver data + */ +static int phy_mdm6600_init_lines(struct phy_mdm6600 *ddata) +{ + struct device *dev = ddata->dev; + int i; + + /* MDM6600 control lines */ + for (i = 0; i < ARRAY_SIZE(phy_mdm6600_ctrl_gpio_map); i++) { + const struct phy_mdm6600_map *map = + &phy_mdm6600_ctrl_gpio_map[i]; + struct gpio_desc **gpio = &ddata->ctrl_gpios[i]; + + *gpio = devm_gpiod_get(dev, map->name, map->direction); + if (IS_ERR(*gpio)) { + dev_info(dev, "gpio %s error %li\n", + map->name, PTR_ERR(*gpio)); + return PTR_ERR(*gpio); + } + } + + /* MDM6600 USB start-up mode output lines */ + ddata->mode_gpios = devm_gpiod_get_array(dev, "motorola,mode", + GPIOD_OUT_LOW); + if (IS_ERR(ddata->mode_gpios)) + return PTR_ERR(ddata->mode_gpios); + + if (ddata->mode_gpios->ndescs != PHY_MDM6600_NR_MODE_LINES) + return -EINVAL; + + /* MDM6600 status input lines */ + ddata->status_gpios = devm_gpiod_get_array(dev, "motorola,status", + GPIOD_IN); + if (IS_ERR(ddata->status_gpios)) + return PTR_ERR(ddata->status_gpios); + + if (ddata->status_gpios->ndescs != PHY_MDM6600_NR_STATUS_LINES) + return -EINVAL; + + /* MDM6600 cmd output lines */ + ddata->cmd_gpios = devm_gpiod_get_array(dev, "motorola,cmd", + GPIOD_OUT_LOW); + if (IS_ERR(ddata->cmd_gpios)) + return PTR_ERR(ddata->cmd_gpios); + + if (ddata->cmd_gpios->ndescs != PHY_MDM6600_NR_CMD_LINES) + return -EINVAL; + + return 0; +} + +/** + * phy_mdm6600_device_power_on() - power on mdm6600 device + * @ddata: device driver data + * + * To get the integrated USB phy in MDM6600 takes some hoops. We must ensure + * the shared USB bootmode GPIOs are configured, then request modem start-up, + * reset and power-up.. And then we need to recycle the shared USB bootmode + * GPIOs as they are also used for Out of Band (OOB) wake for the USB and + * TS 27.010 serial mux. + */ +static int phy_mdm6600_device_power_on(struct phy_mdm6600 *ddata) +{ + struct gpio_desc *mode_gpio0, *mode_gpio1, *reset_gpio, *power_gpio; + int error = 0, wakeirq; + + mode_gpio0 = ddata->mode_gpios->desc[PHY_MDM6600_MODE0]; + mode_gpio1 = ddata->mode_gpios->desc[PHY_MDM6600_MODE1]; + reset_gpio = ddata->ctrl_gpios[PHY_MDM6600_RESET]; + power_gpio = ddata->ctrl_gpios[PHY_MDM6600_POWER]; + + /* + * Shared GPIOs must be low for normal USB mode. After booting + * they are used for OOB wake signaling. These can be also used + * to configure USB flashing mode later on based on a module + * parameter. + */ + gpiod_set_value_cansleep(mode_gpio0, 0); + gpiod_set_value_cansleep(mode_gpio1, 0); + + /* Request start-up mode */ + phy_mdm6600_cmd(ddata, PHY_MDM6600_CMD_NO_BYPASS); + + /* Request a reset first */ + gpiod_set_value_cansleep(reset_gpio, 0); + msleep(100); + + /* Toggle power GPIO to request mdm6600 to start */ + gpiod_set_value_cansleep(power_gpio, 1); + msleep(100); + gpiod_set_value_cansleep(power_gpio, 0); + + /* + * Looks like the USB PHY needs between 2.2 to 4 seconds. + * If we try to use it before that, we will get L3 errors + * from omap-usb-host trying to access the PHY. See also + * phy_mdm6600_init() for -EPROBE_DEFER. + */ + msleep(PHY_MDM6600_PHY_DELAY_MS); + ddata->enabled = true; + + /* Booting up the rest of MDM6600 will take total about 8 seconds */ + dev_info(ddata->dev, "Waiting for power up request to complete..\n"); + if (wait_for_completion_timeout(&ddata->ack, + msecs_to_jiffies(PHY_MDM6600_ENABLED_DELAY_MS))) { + if (ddata->status > PHY_MDM6600_STATUS_PANIC && + ddata->status < PHY_MDM6600_STATUS_SHUTDOWN_ACK) + dev_info(ddata->dev, "Powered up OK\n"); + } else { + ddata->enabled = false; + error = -ETIMEDOUT; + dev_err(ddata->dev, "Timed out powering up\n"); + } + + /* Reconfigure mode1 GPIO as input for OOB wake */ + gpiod_direction_input(mode_gpio1); + + wakeirq = gpiod_to_irq(mode_gpio1); + if (wakeirq <= 0) + return wakeirq; + + error = devm_request_threaded_irq(ddata->dev, wakeirq, NULL, + phy_mdm6600_wakeirq_thread, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "mdm6600-wake", + ddata); + if (error) + dev_warn(ddata->dev, "no modem wakeirq irq%i: %i\n", + wakeirq, error); + + ddata->running = true; + + return error; +} + +/** + * phy_mdm6600_device_power_off() - power off mdm6600 device + * @ddata: device driver data + */ +static void phy_mdm6600_device_power_off(struct phy_mdm6600 *ddata) +{ + struct gpio_desc *reset_gpio = + ddata->ctrl_gpios[PHY_MDM6600_RESET]; + + ddata->enabled = false; + phy_mdm6600_cmd(ddata, PHY_MDM6600_CMD_BP_SHUTDOWN_REQ); + msleep(100); + + gpiod_set_value_cansleep(reset_gpio, 1); + + dev_info(ddata->dev, "Waiting for power down request to complete.. "); + if (wait_for_completion_timeout(&ddata->ack, + msecs_to_jiffies(5000))) { + if (ddata->status == PHY_MDM6600_STATUS_PANIC) + dev_info(ddata->dev, "Powered down OK\n"); + } else { + dev_err(ddata->dev, "Timed out powering down\n"); + } +} + +static void phy_mdm6600_deferred_power_on(struct work_struct *work) +{ + struct phy_mdm6600 *ddata; + int error; + + ddata = container_of(work, struct phy_mdm6600, bootup_work.work); + + error = phy_mdm6600_device_power_on(ddata); + if (error) + dev_err(ddata->dev, "Device not functional\n"); +} + +static const struct of_device_id phy_mdm6600_id_table[] = { + { .compatible = "motorola,mapphone-mdm6600", }, + {}, +}; +MODULE_DEVICE_TABLE(of, phy_mdm6600_id_table); + +static int phy_mdm6600_probe(struct platform_device *pdev) +{ + struct phy_mdm6600 *ddata; + int error; + + ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + INIT_DELAYED_WORK(&ddata->bootup_work, + phy_mdm6600_deferred_power_on); + INIT_DELAYED_WORK(&ddata->status_work, phy_mdm6600_status); + init_completion(&ddata->ack); + + ddata->dev = &pdev->dev; + platform_set_drvdata(pdev, ddata); + + error = phy_mdm6600_init_lines(ddata); + if (error) + return error; + + phy_mdm6600_init_irq(ddata); + + ddata->generic_phy = devm_phy_create(ddata->dev, NULL, &gpio_usb_ops); + if (IS_ERR(ddata->generic_phy)) { + error = PTR_ERR(ddata->generic_phy); + goto cleanup; + } + + phy_set_drvdata(ddata->generic_phy, ddata); + + ddata->phy_provider = + devm_of_phy_provider_register(ddata->dev, + of_phy_simple_xlate); + if (IS_ERR(ddata->phy_provider)) { + error = PTR_ERR(ddata->phy_provider); + goto cleanup; + } + + schedule_delayed_work(&ddata->bootup_work, 0); + + /* + * See phy_mdm6600_device_power_on(). We should be able + * to remove this eventually when ohci-platform can deal + * with -EPROBE_DEFER. + */ + msleep(PHY_MDM6600_PHY_DELAY_MS + 500); + + return 0; + +cleanup: + phy_mdm6600_device_power_off(ddata); + return error; +} + +static int phy_mdm6600_remove(struct platform_device *pdev) +{ + struct phy_mdm6600 *ddata = platform_get_drvdata(pdev); + struct gpio_desc *reset_gpio = ddata->ctrl_gpios[PHY_MDM6600_RESET]; + + if (!ddata->running) + wait_for_completion_timeout(&ddata->ack, + msecs_to_jiffies(PHY_MDM6600_ENABLED_DELAY_MS)); + + gpiod_set_value_cansleep(reset_gpio, 1); + phy_mdm6600_device_power_off(ddata); + + cancel_delayed_work_sync(&ddata->bootup_work); + cancel_delayed_work_sync(&ddata->status_work); + + return 0; +} + +static struct platform_driver phy_mdm6600_driver = { + .probe = phy_mdm6600_probe, + .remove = phy_mdm6600_remove, + .driver = { + .name = "phy-mapphone-mdm6600", + .of_match_table = of_match_ptr(phy_mdm6600_id_table), + }, +}; + +module_platform_driver(phy_mdm6600_driver); + +MODULE_ALIAS("platform:gpio_usb"); +MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>"); +MODULE_DESCRIPTION("mdm6600 gpio usb phy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c index 8f6e8e28996d..09ac8afb97ac 100644 --- a/drivers/phy/phy-core.c +++ b/drivers/phy/phy-core.c @@ -351,6 +351,8 @@ int phy_set_mode(struct phy *phy, enum phy_mode mode) mutex_lock(&phy->mutex); ret = phy->ops->set_mode(phy, mode); + if (!ret) + phy->attrs.mode = mode; mutex_unlock(&phy->mutex); return ret; diff --git a/drivers/phy/phy-lpc18xx-usb-otg.c b/drivers/phy/phy-lpc18xx-usb-otg.c index 3b7a71eb5b7e..7de280a45421 100644 --- a/drivers/phy/phy-lpc18xx-usb-otg.c +++ b/drivers/phy/phy-lpc18xx-usb-otg.c @@ -60,8 +60,14 @@ static int lpc18xx_usb_otg_phy_power_on(struct phy *phy) return ret; /* The bit in CREG is cleared to enable the PHY */ - return regmap_update_bits(lpc->reg, LPC18XX_CREG_CREG0, + ret = regmap_update_bits(lpc->reg, LPC18XX_CREG_CREG0, LPC18XX_CREG_CREG0_USB0PHY, 0); + if (ret) { + clk_disable(lpc->clk); + return ret; + } + + return 0; } static int lpc18xx_usb_otg_phy_power_off(struct phy *phy) diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.c b/drivers/phy/qualcomm/phy-qcom-qmp.c index e17f0351ccc2..6470c5d61d1c 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp.c @@ -1,15 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2017, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * */ #include <linux/clk.h> @@ -31,124 +22,7 @@ #include <dt-bindings/phy/phy.h> -/* QMP PHY QSERDES COM registers */ -#define QSERDES_COM_BG_TIMER 0x00c -#define QSERDES_COM_SSC_EN_CENTER 0x010 -#define QSERDES_COM_SSC_ADJ_PER1 0x014 -#define QSERDES_COM_SSC_ADJ_PER2 0x018 -#define QSERDES_COM_SSC_PER1 0x01c -#define QSERDES_COM_SSC_PER2 0x020 -#define QSERDES_COM_SSC_STEP_SIZE1 0x024 -#define QSERDES_COM_SSC_STEP_SIZE2 0x028 -#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN 0x034 -#define QSERDES_COM_CLK_ENABLE1 0x038 -#define QSERDES_COM_SYS_CLK_CTRL 0x03c -#define QSERDES_COM_SYSCLK_BUF_ENABLE 0x040 -#define QSERDES_COM_PLL_IVCO 0x048 -#define QSERDES_COM_LOCK_CMP1_MODE0 0x04c -#define QSERDES_COM_LOCK_CMP2_MODE0 0x050 -#define QSERDES_COM_LOCK_CMP3_MODE0 0x054 -#define QSERDES_COM_LOCK_CMP1_MODE1 0x058 -#define QSERDES_COM_LOCK_CMP2_MODE1 0x05c -#define QSERDES_COM_LOCK_CMP3_MODE1 0x060 -#define QSERDES_COM_BG_TRIM 0x070 -#define QSERDES_COM_CLK_EP_DIV 0x074 -#define QSERDES_COM_CP_CTRL_MODE0 0x078 -#define QSERDES_COM_CP_CTRL_MODE1 0x07c -#define QSERDES_COM_PLL_RCTRL_MODE0 0x084 -#define QSERDES_COM_PLL_RCTRL_MODE1 0x088 -#define QSERDES_COM_PLL_CCTRL_MODE0 0x090 -#define QSERDES_COM_PLL_CCTRL_MODE1 0x094 -#define QSERDES_COM_BIAS_EN_CTRL_BY_PSM 0x0a8 -#define QSERDES_COM_SYSCLK_EN_SEL 0x0ac -#define QSERDES_COM_RESETSM_CNTRL 0x0b4 -#define QSERDES_COM_RESTRIM_CTRL 0x0bc -#define QSERDES_COM_RESCODE_DIV_NUM 0x0c4 -#define QSERDES_COM_LOCK_CMP_EN 0x0c8 -#define QSERDES_COM_LOCK_CMP_CFG 0x0cc -#define QSERDES_COM_DEC_START_MODE0 0x0d0 -#define QSERDES_COM_DEC_START_MODE1 0x0d4 -#define QSERDES_COM_DIV_FRAC_START1_MODE0 0x0dc -#define QSERDES_COM_DIV_FRAC_START2_MODE0 0x0e0 -#define QSERDES_COM_DIV_FRAC_START3_MODE0 0x0e4 -#define QSERDES_COM_DIV_FRAC_START1_MODE1 0x0e8 -#define QSERDES_COM_DIV_FRAC_START2_MODE1 0x0ec -#define QSERDES_COM_DIV_FRAC_START3_MODE1 0x0f0 -#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0 0x108 -#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0 0x10c -#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1 0x110 -#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1 0x114 -#define QSERDES_COM_VCO_TUNE_CTRL 0x124 -#define QSERDES_COM_VCO_TUNE_MAP 0x128 -#define QSERDES_COM_VCO_TUNE1_MODE0 0x12c -#define QSERDES_COM_VCO_TUNE2_MODE0 0x130 -#define QSERDES_COM_VCO_TUNE1_MODE1 0x134 -#define QSERDES_COM_VCO_TUNE2_MODE1 0x138 -#define QSERDES_COM_VCO_TUNE_TIMER1 0x144 -#define QSERDES_COM_VCO_TUNE_TIMER2 0x148 -#define QSERDES_COM_BG_CTRL 0x170 -#define QSERDES_COM_CLK_SELECT 0x174 -#define QSERDES_COM_HSCLK_SEL 0x178 -#define QSERDES_COM_CORECLK_DIV 0x184 -#define QSERDES_COM_CORE_CLK_EN 0x18c -#define QSERDES_COM_C_READY_STATUS 0x190 -#define QSERDES_COM_CMN_CONFIG 0x194 -#define QSERDES_COM_SVS_MODE_CLK_SEL 0x19c -#define QSERDES_COM_DEBUG_BUS0 0x1a0 -#define QSERDES_COM_DEBUG_BUS1 0x1a4 -#define QSERDES_COM_DEBUG_BUS2 0x1a8 -#define QSERDES_COM_DEBUG_BUS3 0x1ac -#define QSERDES_COM_DEBUG_BUS_SEL 0x1b0 -#define QSERDES_COM_CORECLK_DIV_MODE1 0x1bc - -/* QMP PHY TX registers */ -#define QSERDES_TX_RES_CODE_LANE_OFFSET 0x054 -#define QSERDES_TX_DEBUG_BUS_SEL 0x064 -#define QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN 0x068 -#define QSERDES_TX_LANE_MODE 0x094 -#define QSERDES_TX_RCV_DETECT_LVL_2 0x0ac - -/* QMP PHY RX registers */ -#define QSERDES_RX_UCDR_SO_GAIN_HALF 0x010 -#define QSERDES_RX_UCDR_SO_GAIN 0x01c -#define QSERDES_RX_UCDR_FASTLOCK_FO_GAIN 0x040 -#define QSERDES_RX_UCDR_SO_SATURATION_AND_ENABLE 0x048 -#define QSERDES_RX_RX_TERM_BW 0x090 -#define QSERDES_RX_RX_EQ_GAIN1_LSB 0x0c4 -#define QSERDES_RX_RX_EQ_GAIN1_MSB 0x0c8 -#define QSERDES_RX_RX_EQ_GAIN2_LSB 0x0cc -#define QSERDES_RX_RX_EQ_GAIN2_MSB 0x0d0 -#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2 0x0d8 -#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3 0x0dc -#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4 0x0e0 -#define QSERDES_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1 0x108 -#define QSERDES_RX_RX_OFFSET_ADAPTOR_CNTRL2 0x10c -#define QSERDES_RX_SIGDET_ENABLES 0x110 -#define QSERDES_RX_SIGDET_CNTRL 0x114 -#define QSERDES_RX_SIGDET_LVL 0x118 -#define QSERDES_RX_SIGDET_DEGLITCH_CNTRL 0x11c -#define QSERDES_RX_RX_BAND 0x120 -#define QSERDES_RX_RX_INTERFACE_MODE 0x12c - -/* QMP PHY PCS registers */ -#define QPHY_POWER_DOWN_CONTROL 0x04 -#define QPHY_TXDEEMPH_M6DB_V0 0x24 -#define QPHY_TXDEEMPH_M3P5DB_V0 0x28 -#define QPHY_ENDPOINT_REFCLK_DRIVE 0x54 -#define QPHY_RX_IDLE_DTCT_CNTRL 0x58 -#define QPHY_POWER_STATE_CONFIG1 0x60 -#define QPHY_POWER_STATE_CONFIG2 0x64 -#define QPHY_POWER_STATE_CONFIG4 0x6c -#define QPHY_LOCK_DETECT_CONFIG1 0x80 -#define QPHY_LOCK_DETECT_CONFIG2 0x84 -#define QPHY_LOCK_DETECT_CONFIG3 0x88 -#define QPHY_PWRUP_RESET_DLY_TIME_AUXCLK 0xa0 -#define QPHY_LP_WAKEUP_DLY_TIME_AUXCLK 0xa4 -#define QPHY_PLL_LOCK_CHK_DLY_TIME_AUXCLK_LSB 0x1A8 -#define QPHY_OSC_DTCT_ACTIONS 0x1AC -#define QPHY_RX_SIGDET_LVL 0x1D8 -#define QPHY_L1SS_WAKEUP_DLY_TIME_AUXCLK_LSB 0x1DC -#define QPHY_L1SS_WAKEUP_DLY_TIME_AUXCLK_MSB 0x1E0 +#include "phy-qcom-qmp.h" /* QPHY_SW_RESET bit */ #define SW_RESET BIT(0) @@ -164,6 +38,34 @@ /* QPHY_COM_PCS_READY_STATUS bit */ #define PCS_READY BIT(0) +/* QPHY_V3_DP_COM_RESET_OVRD_CTRL register bits */ +/* DP PHY soft reset */ +#define SW_DPPHY_RESET BIT(0) +/* mux to select DP PHY reset control, 0:HW control, 1: software reset */ +#define SW_DPPHY_RESET_MUX BIT(1) +/* USB3 PHY soft reset */ +#define SW_USB3PHY_RESET BIT(2) +/* mux to select USB3 PHY reset control, 0:HW control, 1: software reset */ +#define SW_USB3PHY_RESET_MUX BIT(3) + +/* QPHY_V3_DP_COM_PHY_MODE_CTRL register bits */ +#define USB3_MODE BIT(0) /* enables USB3 mode */ +#define DP_MODE BIT(1) /* enables DP mode */ + +/* QPHY_PCS_AUTONOMOUS_MODE_CTRL register bits */ +#define ARCVR_DTCT_EN BIT(0) +#define ALFPS_DTCT_EN BIT(1) +#define ARCVR_DTCT_EVENT_SEL BIT(4) + +/* QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR register bits */ +#define IRQ_CLEAR BIT(0) + +/* QPHY_PCS_LFPS_RXTERM_IRQ_STATUS register bits */ +#define RCVR_DETECT BIT(0) + +/* QPHY_V3_PCS_MISC_CLAMP_ENABLE register bits */ +#define CLAMP_EN BIT(0) /* enables i/o clamp_n */ + #define PHY_INIT_COMPLETE_TIMEOUT 1000 #define POWER_DOWN_DELAY_US_MIN 10 #define POWER_DOWN_DELAY_US_MAX 11 @@ -210,6 +112,9 @@ enum qphy_reg_layout { QPHY_SW_RESET, QPHY_START_CTRL, QPHY_PCS_READY_STATUS, + QPHY_PCS_AUTONOMOUS_MODE_CTRL, + QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR, + QPHY_PCS_LFPS_RXTERM_IRQ_STATUS, }; static const unsigned int pciephy_regs_layout[] = { @@ -237,6 +142,18 @@ static const unsigned int usb3phy_regs_layout[] = { [QPHY_SW_RESET] = 0x00, [QPHY_START_CTRL] = 0x08, [QPHY_PCS_READY_STATUS] = 0x17c, + [QPHY_PCS_AUTONOMOUS_MODE_CTRL] = 0x0d4, + [QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR] = 0x0d8, + [QPHY_PCS_LFPS_RXTERM_IRQ_STATUS] = 0x178, +}; + +static const unsigned int qmp_v3_usb3phy_regs_layout[] = { + [QPHY_SW_RESET] = 0x00, + [QPHY_START_CTRL] = 0x08, + [QPHY_PCS_READY_STATUS] = 0x174, + [QPHY_PCS_AUTONOMOUS_MODE_CTRL] = 0x0d8, + [QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR] = 0x0dc, + [QPHY_PCS_LFPS_RXTERM_IRQ_STATUS] = 0x170, }; static const struct qmp_phy_init_tbl msm8996_pcie_serdes_tbl[] = { @@ -467,6 +384,112 @@ static const struct qmp_phy_init_tbl ipq8074_pcie_pcs_tbl[] = { QMP_PHY_INIT_CFG_L(QPHY_START_CTRL, 0x3), }; +static const struct qmp_phy_init_tbl qmp_v3_usb3_serdes_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_IVCO, 0x07), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYSCLK_EN_SEL, 0x14), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_BIAS_EN_CLKBUFLR_EN, 0x08), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CLK_SELECT, 0x30), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYS_CLK_CTRL, 0x02), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_RESETSM_CNTRL2, 0x08), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CMN_CONFIG, 0x16), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SVS_MODE_CLK_SEL, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_HSCLK_SEL, 0x80), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_DEC_START_MODE0, 0x82), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START1_MODE0, 0xab), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START2_MODE0, 0xea), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START3_MODE0, 0x02), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CP_CTRL_MODE0, 0x06), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_RCTRL_MODE0, 0x16), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_CCTRL_MODE0, 0x36), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_INTEGLOOP_GAIN1_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_INTEGLOOP_GAIN0_MODE0, 0x3f), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE2_MODE0, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE1_MODE0, 0xc9), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CORECLK_DIV_MODE0, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP3_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP2_MODE0, 0x34), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP1_MODE0, 0x15), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP_EN, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CORE_CLK_EN, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP_CFG, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE_MAP, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYSCLK_BUF_ENABLE, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_EN_CENTER, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_PER1, 0x31), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_PER2, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_ADJ_PER1, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_ADJ_PER2, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_STEP_SIZE1, 0x85), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_STEP_SIZE2, 0x07), +}; + +static const struct qmp_phy_init_tbl qmp_v3_usb3_tx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V3_TX_HIGHZ_DRVR_EN, 0x10), + QMP_PHY_INIT_CFG(QSERDES_V3_TX_RCV_DETECT_LVL_2, 0x12), + QMP_PHY_INIT_CFG(QSERDES_V3_TX_LANE_MODE_1, 0x16), + QMP_PHY_INIT_CFG(QSERDES_V3_TX_RES_CODE_LANE_OFFSET_RX, 0x09), + QMP_PHY_INIT_CFG(QSERDES_V3_TX_RES_CODE_LANE_OFFSET_TX, 0x06), +}; + +static const struct qmp_phy_init_tbl qmp_v3_usb3_rx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_FASTLOCK_FO_GAIN, 0x0b), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0f), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL3, 0x4e), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL4, 0x18), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1, 0x77), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_OFFSET_ADAPTOR_CNTRL2, 0x80), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_CNTRL, 0x03), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_DEGLITCH_CNTRL, 0x16), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x75), +}; + +static const struct qmp_phy_init_tbl qmp_v3_usb3_pcs_tbl[] = { + /* FLL settings */ + QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNTRL2, 0x83), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNT_VAL_L, 0x09), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNT_VAL_H_TOL, 0xa2), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_MAN_CODE, 0x40), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNTRL1, 0x02), + + /* Lock Det settings */ + QMP_PHY_INIT_CFG(QPHY_V3_PCS_LOCK_DETECT_CONFIG1, 0xd1), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_LOCK_DETECT_CONFIG2, 0x1f), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_LOCK_DETECT_CONFIG3, 0x47), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_POWER_STATE_CONFIG2, 0x1b), + + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RX_SIGDET_LVL, 0xba), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_V0, 0x9f), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_V1, 0x9f), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_V2, 0xb7), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_V3, 0x4e), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_V4, 0x65), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_LS, 0x6b), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V0, 0x15), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V0, 0x0d), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V1, 0x15), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V1, 0x0d), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V2, 0x15), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V2, 0x0d), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V3, 0x15), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V3, 0x1d), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V4, 0x15), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V4, 0x0d), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_LS, 0x15), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_LS, 0x0d), + + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RATE_SLEW_CNTRL, 0x02), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_PWRUP_RESET_DLY_TIME_AUXCLK, 0x04), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TSYNC_RSYNC_TIME, 0x44), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_PWRUP_RESET_DLY_TIME_AUXCLK, 0x04), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_P1U2_L, 0xe7), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_P1U2_H, 0x03), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_U3_L, 0x40), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_U3_H, 0x00), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RXEQTRAINING_WAIT_TIME, 0x75), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_LFPS_TX_ECSTART_EQTLOCK, 0x86), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RXEQTRAINING_RUN_TIME, 0x13), +}; + /* struct qmp_phy_cfg - per-PHY initialization config */ struct qmp_phy_cfg { /* phy-type - PCIE/UFS/USB */ @@ -511,6 +534,12 @@ struct qmp_phy_cfg { /* power_down delay in usec */ int pwrdn_delay_min; int pwrdn_delay_max; + + /* true, if PHY has a separate DP_COM control block */ + bool has_phy_dp_com_ctrl; + /* Register offset of secondary tx/rx lanes for USB DP combo PHY */ + unsigned int tx_b_lane_offset; + unsigned int rx_b_lane_offset; }; /** @@ -520,6 +549,7 @@ struct qmp_phy_cfg { * @tx: iomapped memory space for lane's tx * @rx: iomapped memory space for lane's rx * @pcs: iomapped memory space for lane's pcs + * @pcs_misc: iomapped memory space for lane's pcs_misc * @pipe_clk: pipe lock * @index: lane index * @qmp: QMP phy to which this lane belongs @@ -530,6 +560,7 @@ struct qmp_phy { void __iomem *tx; void __iomem *rx; void __iomem *pcs; + void __iomem *pcs_misc; struct clk *pipe_clk; unsigned int index; struct qcom_qmp *qmp; @@ -541,6 +572,7 @@ struct qmp_phy { * * @dev: device * @serdes: iomapped memory space for phy's serdes + * @dp_com: iomapped memory space for phy's dp_com control block * * @clks: array of clocks required by phy * @resets: array of resets required by phy @@ -550,12 +582,15 @@ struct qmp_phy { * @phys: array of per-lane phy descriptors * @phy_mutex: mutex lock for PHY common block initialization * @init_count: phy common block initialization count + * @phy_initialized: indicate if PHY has been initialized + * @mode: current PHY mode */ struct qcom_qmp { struct device *dev; void __iomem *serdes; + void __iomem *dp_com; - struct clk **clks; + struct clk_bulk_data *clks; struct reset_control **resets; struct regulator_bulk_data *vregs; @@ -564,6 +599,8 @@ struct qcom_qmp { struct mutex phy_mutex; int init_count; + bool phy_initialized; + enum phy_mode mode; }; static inline void qphy_setbits(void __iomem *base, u32 offset, u32 val) @@ -595,6 +632,10 @@ static const char * const msm8996_phy_clk_l[] = { "aux", "cfg_ahb", "ref", }; +static const char * const qmp_v3_phy_clk_l[] = { + "aux", "cfg_ahb", "ref", "com_aux", +}; + /* list of resets */ static const char * const msm8996_pciephy_reset_l[] = { "phy", "common", "cfg", @@ -701,6 +742,38 @@ static const struct qmp_phy_cfg ipq8074_pciephy_cfg = { .pwrdn_delay_max = 1005, /* us */ }; +static const struct qmp_phy_cfg qmp_v3_usb3phy_cfg = { + .type = PHY_TYPE_USB3, + .nlanes = 1, + + .serdes_tbl = qmp_v3_usb3_serdes_tbl, + .serdes_tbl_num = ARRAY_SIZE(qmp_v3_usb3_serdes_tbl), + .tx_tbl = qmp_v3_usb3_tx_tbl, + .tx_tbl_num = ARRAY_SIZE(qmp_v3_usb3_tx_tbl), + .rx_tbl = qmp_v3_usb3_rx_tbl, + .rx_tbl_num = ARRAY_SIZE(qmp_v3_usb3_rx_tbl), + .pcs_tbl = qmp_v3_usb3_pcs_tbl, + .pcs_tbl_num = ARRAY_SIZE(qmp_v3_usb3_pcs_tbl), + .clk_list = qmp_v3_phy_clk_l, + .num_clks = ARRAY_SIZE(qmp_v3_phy_clk_l), + .reset_list = msm8996_usb3phy_reset_l, + .num_resets = ARRAY_SIZE(msm8996_usb3phy_reset_l), + .vreg_list = msm8996_phy_vreg_l, + .num_vregs = ARRAY_SIZE(msm8996_phy_vreg_l), + .regs = qmp_v3_usb3phy_regs_layout, + + .start_ctrl = SERDES_START | PCS_START, + .pwrdn_ctrl = SW_PWRDN, + .mask_pcs_ready = PHYSTATUS, + + .pwrdn_delay_min = POWER_DOWN_DELAY_US_MIN, + .pwrdn_delay_max = POWER_DOWN_DELAY_US_MAX, + + .has_phy_dp_com_ctrl = true, + .tx_b_lane_offset = 0x400, + .rx_b_lane_offset = 0x400, +}; + static void qcom_qmp_phy_configure(void __iomem *base, const unsigned int *regs, const struct qmp_phy_init_tbl tbl[], @@ -724,44 +797,20 @@ static int qcom_qmp_phy_poweron(struct phy *phy) { struct qmp_phy *qphy = phy_get_drvdata(phy); struct qcom_qmp *qmp = qphy->qmp; - int num = qmp->cfg->num_vregs; int ret; - dev_vdbg(&phy->dev, "Powering on QMP phy\n"); - - /* turn on regulator supplies */ - ret = regulator_bulk_enable(num, qmp->vregs); - if (ret) { - dev_err(qmp->dev, "failed to enable regulators, err=%d\n", ret); - return ret; - } - ret = clk_prepare_enable(qphy->pipe_clk); - if (ret) { + if (ret) dev_err(qmp->dev, "pipe_clk enable failed, err=%d\n", ret); - regulator_bulk_disable(num, qmp->vregs); - return ret; - } - return 0; -} - -static int qcom_qmp_phy_poweroff(struct phy *phy) -{ - struct qmp_phy *qphy = phy_get_drvdata(phy); - struct qcom_qmp *qmp = qphy->qmp; - - clk_disable_unprepare(qphy->pipe_clk); - - regulator_bulk_disable(qmp->cfg->num_vregs, qmp->vregs); - - return 0; + return ret; } static int qcom_qmp_phy_com_init(struct qcom_qmp *qmp) { const struct qmp_phy_cfg *cfg = qmp->cfg; void __iomem *serdes = qmp->serdes; + void __iomem *dp_com = qmp->dp_com; int ret, i; mutex_lock(&qmp->phy_mutex); @@ -770,7 +819,23 @@ static int qcom_qmp_phy_com_init(struct qcom_qmp *qmp) return 0; } + /* turn on regulator supplies */ + ret = regulator_bulk_enable(cfg->num_vregs, qmp->vregs); + if (ret) { + dev_err(qmp->dev, "failed to enable regulators, err=%d\n", ret); + goto err_reg_enable; + } + for (i = 0; i < cfg->num_resets; i++) { + ret = reset_control_assert(qmp->resets[i]); + if (ret) { + dev_err(qmp->dev, "%s reset assert failed\n", + cfg->reset_list[i]); + goto err_rst_assert; + } + } + + for (i = cfg->num_resets - 1; i >= 0; i--) { ret = reset_control_deassert(qmp->resets[i]); if (ret) { dev_err(qmp->dev, "%s reset deassert failed\n", @@ -779,10 +844,33 @@ static int qcom_qmp_phy_com_init(struct qcom_qmp *qmp) } } + ret = clk_bulk_prepare_enable(cfg->num_clks, qmp->clks); + if (ret) { + dev_err(qmp->dev, "failed to enable clks, err=%d\n", ret); + goto err_rst; + } + if (cfg->has_phy_com_ctrl) qphy_setbits(serdes, cfg->regs[QPHY_COM_POWER_DOWN_CONTROL], SW_PWRDN); + if (cfg->has_phy_dp_com_ctrl) { + qphy_setbits(dp_com, QPHY_V3_DP_COM_POWER_DOWN_CTRL, + SW_PWRDN); + /* override hardware control for reset of qmp phy */ + qphy_setbits(dp_com, QPHY_V3_DP_COM_RESET_OVRD_CTRL, + SW_DPPHY_RESET_MUX | SW_DPPHY_RESET | + SW_USB3PHY_RESET_MUX | SW_USB3PHY_RESET); + + qphy_setbits(dp_com, QPHY_V3_DP_COM_PHY_MODE_CTRL, + USB3_MODE | DP_MODE); + + /* bring both QMP USB and QMP DP PHYs PCS block out of reset */ + qphy_clrbits(dp_com, QPHY_V3_DP_COM_RESET_OVRD_CTRL, + SW_DPPHY_RESET_MUX | SW_DPPHY_RESET | + SW_USB3PHY_RESET_MUX | SW_USB3PHY_RESET); + } + /* Serdes configuration */ qcom_qmp_phy_configure(serdes, cfg->regs, cfg->serdes_tbl, cfg->serdes_tbl_num); @@ -803,7 +891,7 @@ static int qcom_qmp_phy_com_init(struct qcom_qmp *qmp) if (ret) { dev_err(qmp->dev, "phy common block init timed-out\n"); - goto err_rst; + goto err_com_init; } } @@ -811,9 +899,14 @@ static int qcom_qmp_phy_com_init(struct qcom_qmp *qmp) return 0; +err_com_init: + clk_bulk_disable_unprepare(cfg->num_clks, qmp->clks); err_rst: - while (--i >= 0) + while (++i < cfg->num_resets) reset_control_assert(qmp->resets[i]); +err_rst_assert: + regulator_bulk_disable(cfg->num_vregs, qmp->vregs); +err_reg_enable: mutex_unlock(&qmp->phy_mutex); return ret; @@ -843,6 +936,10 @@ static int qcom_qmp_phy_com_exit(struct qcom_qmp *qmp) while (--i >= 0) reset_control_assert(qmp->resets[i]); + clk_bulk_disable_unprepare(cfg->num_clks, qmp->clks); + + regulator_bulk_disable(cfg->num_vregs, qmp->vregs); + mutex_unlock(&qmp->phy_mutex); return 0; @@ -857,24 +954,16 @@ static int qcom_qmp_phy_init(struct phy *phy) void __iomem *tx = qphy->tx; void __iomem *rx = qphy->rx; void __iomem *pcs = qphy->pcs; + void __iomem *dp_com = qmp->dp_com; void __iomem *status; unsigned int mask, val; - int ret, i; + int ret; dev_vdbg(qmp->dev, "Initializing QMP phy\n"); - for (i = 0; i < qmp->cfg->num_clks; i++) { - ret = clk_prepare_enable(qmp->clks[i]); - if (ret) { - dev_err(qmp->dev, "failed to enable %s clk, err=%d\n", - qmp->cfg->clk_list[i], ret); - goto err_clk; - } - } - ret = qcom_qmp_phy_com_init(qmp); if (ret) - goto err_clk; + return ret; if (cfg->has_lane_rst) { ret = reset_control_deassert(qphy->lane_rst); @@ -887,7 +976,16 @@ static int qcom_qmp_phy_init(struct phy *phy) /* Tx, Rx, and PCS configurations */ qcom_qmp_phy_configure(tx, cfg->regs, cfg->tx_tbl, cfg->tx_tbl_num); + /* Configuration for other LANE for USB-DP combo PHY */ + if (cfg->has_phy_dp_com_ctrl) + qcom_qmp_phy_configure(tx + cfg->tx_b_lane_offset, cfg->regs, + cfg->tx_tbl, cfg->tx_tbl_num); + qcom_qmp_phy_configure(rx, cfg->regs, cfg->rx_tbl, cfg->rx_tbl_num); + if (cfg->has_phy_dp_com_ctrl) + qcom_qmp_phy_configure(rx + cfg->rx_b_lane_offset, cfg->regs, + cfg->rx_tbl, cfg->rx_tbl_num); + qcom_qmp_phy_configure(pcs, cfg->regs, cfg->pcs_tbl, cfg->pcs_tbl_num); /* @@ -899,11 +997,13 @@ static int qcom_qmp_phy_init(struct phy *phy) if (cfg->has_pwrdn_delay) usleep_range(cfg->pwrdn_delay_min, cfg->pwrdn_delay_max); - /* start SerDes and Phy-Coding-Sublayer */ - qphy_setbits(pcs, cfg->regs[QPHY_START_CTRL], cfg->start_ctrl); - /* Pull PHY out of reset state */ qphy_clrbits(pcs, cfg->regs[QPHY_SW_RESET], SW_RESET); + if (cfg->has_phy_dp_com_ctrl) + qphy_clrbits(dp_com, QPHY_V3_DP_COM_SW_RESET, SW_RESET); + + /* start SerDes and Phy-Coding-Sublayer */ + qphy_setbits(pcs, cfg->regs[QPHY_START_CTRL], cfg->start_ctrl); status = pcs + cfg->regs[QPHY_PCS_READY_STATUS]; mask = cfg->mask_pcs_ready; @@ -914,6 +1014,7 @@ static int qcom_qmp_phy_init(struct phy *phy) dev_err(qmp->dev, "phy initialization timed-out\n"); goto err_pcs_ready; } + qmp->phy_initialized = true; return ret; @@ -922,9 +1023,6 @@ err_pcs_ready: reset_control_assert(qphy->lane_rst); err_lane_rst: qcom_qmp_phy_com_exit(qmp); -err_clk: - while (--i >= 0) - clk_disable_unprepare(qmp->clks[i]); return ret; } @@ -934,7 +1032,8 @@ static int qcom_qmp_phy_exit(struct phy *phy) struct qmp_phy *qphy = phy_get_drvdata(phy); struct qcom_qmp *qmp = qphy->qmp; const struct qmp_phy_cfg *cfg = qmp->cfg; - int i = cfg->num_clks; + + clk_disable_unprepare(qphy->pipe_clk); /* PHY reset */ qphy_setbits(qphy->pcs, cfg->regs[QPHY_SW_RESET], SW_RESET); @@ -950,8 +1049,127 @@ static int qcom_qmp_phy_exit(struct phy *phy) qcom_qmp_phy_com_exit(qmp); - while (--i >= 0) - clk_disable_unprepare(qmp->clks[i]); + qmp->phy_initialized = false; + + return 0; +} + +static int qcom_qmp_phy_set_mode(struct phy *phy, enum phy_mode mode) +{ + struct qmp_phy *qphy = phy_get_drvdata(phy); + struct qcom_qmp *qmp = qphy->qmp; + + qmp->mode = mode; + + return 0; +} + +static void qcom_qmp_phy_enable_autonomous_mode(struct qmp_phy *qphy) +{ + struct qcom_qmp *qmp = qphy->qmp; + const struct qmp_phy_cfg *cfg = qmp->cfg; + void __iomem *pcs = qphy->pcs; + void __iomem *pcs_misc = qphy->pcs_misc; + u32 intr_mask; + + if (qmp->mode == PHY_MODE_USB_HOST_SS || + qmp->mode == PHY_MODE_USB_DEVICE_SS) + intr_mask = ARCVR_DTCT_EN | ALFPS_DTCT_EN; + else + intr_mask = ARCVR_DTCT_EN | ARCVR_DTCT_EVENT_SEL; + + /* Clear any pending interrupts status */ + qphy_setbits(pcs, cfg->regs[QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR], IRQ_CLEAR); + /* Writing 1 followed by 0 clears the interrupt */ + qphy_clrbits(pcs, cfg->regs[QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR], IRQ_CLEAR); + + qphy_clrbits(pcs, cfg->regs[QPHY_PCS_AUTONOMOUS_MODE_CTRL], + ARCVR_DTCT_EN | ALFPS_DTCT_EN | ARCVR_DTCT_EVENT_SEL); + + /* Enable required PHY autonomous mode interrupts */ + qphy_setbits(pcs, cfg->regs[QPHY_PCS_AUTONOMOUS_MODE_CTRL], intr_mask); + + /* Enable i/o clamp_n for autonomous mode */ + if (pcs_misc) + qphy_clrbits(pcs_misc, QPHY_V3_PCS_MISC_CLAMP_ENABLE, CLAMP_EN); +} + +static void qcom_qmp_phy_disable_autonomous_mode(struct qmp_phy *qphy) +{ + struct qcom_qmp *qmp = qphy->qmp; + const struct qmp_phy_cfg *cfg = qmp->cfg; + void __iomem *pcs = qphy->pcs; + void __iomem *pcs_misc = qphy->pcs_misc; + + /* Disable i/o clamp_n on resume for normal mode */ + if (pcs_misc) + qphy_setbits(pcs_misc, QPHY_V3_PCS_MISC_CLAMP_ENABLE, CLAMP_EN); + + qphy_clrbits(pcs, cfg->regs[QPHY_PCS_AUTONOMOUS_MODE_CTRL], + ARCVR_DTCT_EN | ARCVR_DTCT_EVENT_SEL | ALFPS_DTCT_EN); + + qphy_setbits(pcs, cfg->regs[QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR], IRQ_CLEAR); + /* Writing 1 followed by 0 clears the interrupt */ + qphy_clrbits(pcs, cfg->regs[QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR], IRQ_CLEAR); +} + +static int __maybe_unused qcom_qmp_phy_runtime_suspend(struct device *dev) +{ + struct qcom_qmp *qmp = dev_get_drvdata(dev); + struct qmp_phy *qphy = qmp->phys[0]; + const struct qmp_phy_cfg *cfg = qmp->cfg; + + dev_vdbg(dev, "Suspending QMP phy, mode:%d\n", qmp->mode); + + /* Supported only for USB3 PHY */ + if (cfg->type != PHY_TYPE_USB3) + return 0; + + if (!qmp->phy_initialized) { + dev_vdbg(dev, "PHY not initialized, bailing out\n"); + return 0; + } + + qcom_qmp_phy_enable_autonomous_mode(qphy); + + clk_disable_unprepare(qphy->pipe_clk); + clk_bulk_disable_unprepare(cfg->num_clks, qmp->clks); + + return 0; +} + +static int __maybe_unused qcom_qmp_phy_runtime_resume(struct device *dev) +{ + struct qcom_qmp *qmp = dev_get_drvdata(dev); + struct qmp_phy *qphy = qmp->phys[0]; + const struct qmp_phy_cfg *cfg = qmp->cfg; + int ret = 0; + + dev_vdbg(dev, "Resuming QMP phy, mode:%d\n", qmp->mode); + + /* Supported only for USB3 PHY */ + if (cfg->type != PHY_TYPE_USB3) + return 0; + + if (!qmp->phy_initialized) { + dev_vdbg(dev, "PHY not initialized, bailing out\n"); + return 0; + } + + ret = clk_bulk_prepare_enable(cfg->num_clks, qmp->clks); + if (ret) { + dev_err(qmp->dev, "failed to enable clks, err=%d\n", ret); + return ret; + } + + ret = clk_prepare_enable(qphy->pipe_clk); + if (ret) { + dev_err(dev, "pipe_clk enable failed, err=%d\n", ret); + clk_bulk_disable_unprepare(cfg->num_clks, qmp->clks); + return ret; + } + + qcom_qmp_phy_disable_autonomous_mode(qphy); return 0; } @@ -1000,29 +1218,17 @@ static int qcom_qmp_phy_reset_init(struct device *dev) static int qcom_qmp_phy_clk_init(struct device *dev) { struct qcom_qmp *qmp = dev_get_drvdata(dev); - int ret, i; + int num = qmp->cfg->num_clks; + int i; - qmp->clks = devm_kcalloc(dev, qmp->cfg->num_clks, - sizeof(*qmp->clks), GFP_KERNEL); + qmp->clks = devm_kcalloc(dev, num, sizeof(*qmp->clks), GFP_KERNEL); if (!qmp->clks) return -ENOMEM; - for (i = 0; i < qmp->cfg->num_clks; i++) { - struct clk *_clk; - const char *name = qmp->cfg->clk_list[i]; - - _clk = devm_clk_get(dev, name); - if (IS_ERR(_clk)) { - ret = PTR_ERR(_clk); - if (ret != -EPROBE_DEFER) - dev_err(dev, "failed to get %s clk, %d\n", - name, ret); - return ret; - } - qmp->clks[i] = _clk; - } + for (i = 0; i < num; i++) + qmp->clks[i].id = qmp->cfg->clk_list[i]; - return 0; + return devm_clk_bulk_get(dev, num, qmp->clks); } /* @@ -1078,7 +1284,7 @@ static const struct phy_ops qcom_qmp_phy_gen_ops = { .init = qcom_qmp_phy_init, .exit = qcom_qmp_phy_exit, .power_on = qcom_qmp_phy_poweron, - .power_off = qcom_qmp_phy_poweroff, + .set_mode = qcom_qmp_phy_set_mode, .owner = THIS_MODULE, }; @@ -1097,7 +1303,8 @@ int qcom_qmp_phy_create(struct device *dev, struct device_node *np, int id) /* * Get memory resources for each phy lane: - * Resources are indexed as: tx -> 0; rx -> 1; pcs -> 2. + * Resources are indexed as: tx -> 0; rx -> 1; pcs -> 2; and + * pcs_misc (optional) -> 3. */ qphy->tx = of_iomap(np, 0); if (!qphy->tx) @@ -1111,6 +1318,10 @@ int qcom_qmp_phy_create(struct device *dev, struct device_node *np, int id) if (!qphy->pcs) return -ENOMEM; + qphy->pcs_misc = of_iomap(np, 3); + if (!qphy->pcs_misc) + dev_vdbg(dev, "PHY pcs_misc-reg not used\n"); + /* * Get PHY's Pipe clock, if any. USB3 and PCIe are PIPE3 * based phys, so they essentially have pipe clock. So, @@ -1169,11 +1380,19 @@ static const struct of_device_id qcom_qmp_phy_of_match_table[] = { }, { .compatible = "qcom,ipq8074-qmp-pcie-phy", .data = &ipq8074_pciephy_cfg, + }, { + .compatible = "qcom,qmp-v3-usb3-phy", + .data = &qmp_v3_usb3phy_cfg, }, { }, }; MODULE_DEVICE_TABLE(of, qcom_qmp_phy_of_match_table); +static const struct dev_pm_ops qcom_qmp_phy_pm_ops = { + SET_RUNTIME_PM_OPS(qcom_qmp_phy_runtime_suspend, + qcom_qmp_phy_runtime_resume, NULL) +}; + static int qcom_qmp_phy_probe(struct platform_device *pdev) { struct qcom_qmp *qmp; @@ -1192,6 +1411,11 @@ static int qcom_qmp_phy_probe(struct platform_device *pdev) qmp->dev = dev; dev_set_drvdata(dev, qmp); + /* Get the specific init parameters of QMP phy */ + qmp->cfg = of_device_get_match_data(dev); + if (!qmp->cfg) + return -EINVAL; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(dev, res); if (IS_ERR(base)) @@ -1200,10 +1424,18 @@ static int qcom_qmp_phy_probe(struct platform_device *pdev) /* per PHY serdes; usually located at base address */ qmp->serdes = base; - mutex_init(&qmp->phy_mutex); + /* per PHY dp_com; if PHY has dp_com control block */ + if (qmp->cfg->has_phy_dp_com_ctrl) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "dp_com"); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); - /* Get the specific init parameters of QMP phy */ - qmp->cfg = of_device_get_match_data(dev); + qmp->dp_com = base; + } + + mutex_init(&qmp->phy_mutex); ret = qcom_qmp_phy_clk_init(dev); if (ret) @@ -1229,12 +1461,21 @@ static int qcom_qmp_phy_probe(struct platform_device *pdev) return -ENOMEM; id = 0; + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + /* + * Prevent runtime pm from being ON by default. Users can enable + * it using power/control in sysfs. + */ + pm_runtime_forbid(dev); + for_each_available_child_of_node(dev->of_node, child) { /* Create per-lane phy */ ret = qcom_qmp_phy_create(dev, child, id); if (ret) { dev_err(dev, "failed to create lane%d phy, %d\n", id, ret); + pm_runtime_disable(dev); return ret; } @@ -1246,6 +1487,7 @@ static int qcom_qmp_phy_probe(struct platform_device *pdev) if (ret) { dev_err(qmp->dev, "failed to register pipe clock source\n"); + pm_runtime_disable(dev); return ret; } id++; @@ -1254,6 +1496,8 @@ static int qcom_qmp_phy_probe(struct platform_device *pdev) phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); if (!IS_ERR(phy_provider)) dev_info(dev, "Registered Qcom-QMP phy\n"); + else + pm_runtime_disable(dev); return PTR_ERR_OR_ZERO(phy_provider); } @@ -1262,6 +1506,7 @@ static struct platform_driver qcom_qmp_phy_driver = { .probe = qcom_qmp_phy_probe, .driver = { .name = "qcom-qmp-phy", + .pm = &qcom_qmp_phy_pm_ops, .of_match_table = qcom_qmp_phy_of_match_table, }, }; diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.h b/drivers/phy/qualcomm/phy-qcom-qmp.h new file mode 100644 index 000000000000..d1c6905d0439 --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-qmp.h @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017, The Linux Foundation. All rights reserved. + */ + +#ifndef QCOM_PHY_QMP_H_ +#define QCOM_PHY_QMP_H_ + +/* Only for QMP V2 PHY - QSERDES COM registers */ +#define QSERDES_COM_BG_TIMER 0x00c +#define QSERDES_COM_SSC_EN_CENTER 0x010 +#define QSERDES_COM_SSC_ADJ_PER1 0x014 +#define QSERDES_COM_SSC_ADJ_PER2 0x018 +#define QSERDES_COM_SSC_PER1 0x01c +#define QSERDES_COM_SSC_PER2 0x020 +#define QSERDES_COM_SSC_STEP_SIZE1 0x024 +#define QSERDES_COM_SSC_STEP_SIZE2 0x028 +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN 0x034 +#define QSERDES_COM_CLK_ENABLE1 0x038 +#define QSERDES_COM_SYS_CLK_CTRL 0x03c +#define QSERDES_COM_SYSCLK_BUF_ENABLE 0x040 +#define QSERDES_COM_PLL_IVCO 0x048 +#define QSERDES_COM_LOCK_CMP1_MODE0 0x04c +#define QSERDES_COM_LOCK_CMP2_MODE0 0x050 +#define QSERDES_COM_LOCK_CMP3_MODE0 0x054 +#define QSERDES_COM_LOCK_CMP1_MODE1 0x058 +#define QSERDES_COM_LOCK_CMP2_MODE1 0x05c +#define QSERDES_COM_LOCK_CMP3_MODE1 0x060 +#define QSERDES_COM_BG_TRIM 0x070 +#define QSERDES_COM_CLK_EP_DIV 0x074 +#define QSERDES_COM_CP_CTRL_MODE0 0x078 +#define QSERDES_COM_CP_CTRL_MODE1 0x07c +#define QSERDES_COM_PLL_RCTRL_MODE0 0x084 +#define QSERDES_COM_PLL_RCTRL_MODE1 0x088 +#define QSERDES_COM_PLL_CCTRL_MODE0 0x090 +#define QSERDES_COM_PLL_CCTRL_MODE1 0x094 +#define QSERDES_COM_BIAS_EN_CTRL_BY_PSM 0x0a8 +#define QSERDES_COM_SYSCLK_EN_SEL 0x0ac +#define QSERDES_COM_RESETSM_CNTRL 0x0b4 +#define QSERDES_COM_RESTRIM_CTRL 0x0bc +#define QSERDES_COM_RESCODE_DIV_NUM 0x0c4 +#define QSERDES_COM_LOCK_CMP_EN 0x0c8 +#define QSERDES_COM_LOCK_CMP_CFG 0x0cc +#define QSERDES_COM_DEC_START_MODE0 0x0d0 +#define QSERDES_COM_DEC_START_MODE1 0x0d4 +#define QSERDES_COM_DIV_FRAC_START1_MODE0 0x0dc +#define QSERDES_COM_DIV_FRAC_START2_MODE0 0x0e0 +#define QSERDES_COM_DIV_FRAC_START3_MODE0 0x0e4 +#define QSERDES_COM_DIV_FRAC_START1_MODE1 0x0e8 +#define QSERDES_COM_DIV_FRAC_START2_MODE1 0x0ec +#define QSERDES_COM_DIV_FRAC_START3_MODE1 0x0f0 +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0 0x108 +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0 0x10c +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1 0x110 +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1 0x114 +#define QSERDES_COM_VCO_TUNE_CTRL 0x124 +#define QSERDES_COM_VCO_TUNE_MAP 0x128 +#define QSERDES_COM_VCO_TUNE1_MODE0 0x12c +#define QSERDES_COM_VCO_TUNE2_MODE0 0x130 +#define QSERDES_COM_VCO_TUNE1_MODE1 0x134 +#define QSERDES_COM_VCO_TUNE2_MODE1 0x138 +#define QSERDES_COM_VCO_TUNE_TIMER1 0x144 +#define QSERDES_COM_VCO_TUNE_TIMER2 0x148 +#define QSERDES_COM_BG_CTRL 0x170 +#define QSERDES_COM_CLK_SELECT 0x174 +#define QSERDES_COM_HSCLK_SEL 0x178 +#define QSERDES_COM_CORECLK_DIV 0x184 +#define QSERDES_COM_CORE_CLK_EN 0x18c +#define QSERDES_COM_C_READY_STATUS 0x190 +#define QSERDES_COM_CMN_CONFIG 0x194 +#define QSERDES_COM_SVS_MODE_CLK_SEL 0x19c +#define QSERDES_COM_DEBUG_BUS0 0x1a0 +#define QSERDES_COM_DEBUG_BUS1 0x1a4 +#define QSERDES_COM_DEBUG_BUS2 0x1a8 +#define QSERDES_COM_DEBUG_BUS3 0x1ac +#define QSERDES_COM_DEBUG_BUS_SEL 0x1b0 +#define QSERDES_COM_CORECLK_DIV_MODE1 0x1bc + +/* Only for QMP V2 PHY - TX registers */ +#define QSERDES_TX_RES_CODE_LANE_OFFSET 0x054 +#define QSERDES_TX_DEBUG_BUS_SEL 0x064 +#define QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN 0x068 +#define QSERDES_TX_LANE_MODE 0x094 +#define QSERDES_TX_RCV_DETECT_LVL_2 0x0ac + +/* Only for QMP V2 PHY - RX registers */ +#define QSERDES_RX_UCDR_SO_GAIN_HALF 0x010 +#define QSERDES_RX_UCDR_SO_GAIN 0x01c +#define QSERDES_RX_UCDR_FASTLOCK_FO_GAIN 0x040 +#define QSERDES_RX_UCDR_SO_SATURATION_AND_ENABLE 0x048 +#define QSERDES_RX_RX_TERM_BW 0x090 +#define QSERDES_RX_RX_EQ_GAIN1_LSB 0x0c4 +#define QSERDES_RX_RX_EQ_GAIN1_MSB 0x0c8 +#define QSERDES_RX_RX_EQ_GAIN2_LSB 0x0cc +#define QSERDES_RX_RX_EQ_GAIN2_MSB 0x0d0 +#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2 0x0d8 +#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3 0x0dc +#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4 0x0e0 +#define QSERDES_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1 0x108 +#define QSERDES_RX_RX_OFFSET_ADAPTOR_CNTRL2 0x10c +#define QSERDES_RX_SIGDET_ENABLES 0x110 +#define QSERDES_RX_SIGDET_CNTRL 0x114 +#define QSERDES_RX_SIGDET_LVL 0x118 +#define QSERDES_RX_SIGDET_DEGLITCH_CNTRL 0x11c +#define QSERDES_RX_RX_BAND 0x120 +#define QSERDES_RX_RX_INTERFACE_MODE 0x12c + +/* Only for QMP V2 PHY - PCS registers */ +#define QPHY_POWER_DOWN_CONTROL 0x04 +#define QPHY_TXDEEMPH_M6DB_V0 0x24 +#define QPHY_TXDEEMPH_M3P5DB_V0 0x28 +#define QPHY_ENDPOINT_REFCLK_DRIVE 0x54 +#define QPHY_RX_IDLE_DTCT_CNTRL 0x58 +#define QPHY_POWER_STATE_CONFIG1 0x60 +#define QPHY_POWER_STATE_CONFIG2 0x64 +#define QPHY_POWER_STATE_CONFIG4 0x6c +#define QPHY_LOCK_DETECT_CONFIG1 0x80 +#define QPHY_LOCK_DETECT_CONFIG2 0x84 +#define QPHY_LOCK_DETECT_CONFIG3 0x88 +#define QPHY_PWRUP_RESET_DLY_TIME_AUXCLK 0xa0 +#define QPHY_LP_WAKEUP_DLY_TIME_AUXCLK 0xa4 +#define QPHY_PLL_LOCK_CHK_DLY_TIME_AUXCLK_LSB 0x1A8 +#define QPHY_OSC_DTCT_ACTIONS 0x1AC +#define QPHY_RX_SIGDET_LVL 0x1D8 +#define QPHY_L1SS_WAKEUP_DLY_TIME_AUXCLK_LSB 0x1DC +#define QPHY_L1SS_WAKEUP_DLY_TIME_AUXCLK_MSB 0x1E0 + +/* Only for QMP V3 PHY - DP COM registers */ +#define QPHY_V3_DP_COM_PHY_MODE_CTRL 0x00 +#define QPHY_V3_DP_COM_SW_RESET 0x04 +#define QPHY_V3_DP_COM_POWER_DOWN_CTRL 0x08 +#define QPHY_V3_DP_COM_SWI_CTRL 0x0c +#define QPHY_V3_DP_COM_TYPEC_CTRL 0x10 +#define QPHY_V3_DP_COM_TYPEC_PWRDN_CTRL 0x14 +#define QPHY_V3_DP_COM_RESET_OVRD_CTRL 0x1c + +/* Only for QMP V3 PHY - QSERDES COM registers */ +#define QSERDES_V3_COM_BG_TIMER 0x00c +#define QSERDES_V3_COM_SSC_EN_CENTER 0x010 +#define QSERDES_V3_COM_SSC_ADJ_PER1 0x014 +#define QSERDES_V3_COM_SSC_ADJ_PER2 0x018 +#define QSERDES_V3_COM_SSC_PER1 0x01c +#define QSERDES_V3_COM_SSC_PER2 0x020 +#define QSERDES_V3_COM_SSC_STEP_SIZE1 0x024 +#define QSERDES_V3_COM_SSC_STEP_SIZE2 0x028 +#define QSERDES_V3_COM_BIAS_EN_CLKBUFLR_EN 0x034 +#define QSERDES_V3_COM_CLK_ENABLE1 0x038 +#define QSERDES_V3_COM_SYS_CLK_CTRL 0x03c +#define QSERDES_V3_COM_SYSCLK_BUF_ENABLE 0x040 +#define QSERDES_V3_COM_PLL_IVCO 0x048 +#define QSERDES_V3_COM_LOCK_CMP1_MODE0 0x098 +#define QSERDES_V3_COM_LOCK_CMP2_MODE0 0x09c +#define QSERDES_V3_COM_LOCK_CMP3_MODE0 0x0a0 +#define QSERDES_V3_COM_LOCK_CMP1_MODE1 0x0a4 +#define QSERDES_V3_COM_LOCK_CMP2_MODE1 0x0a8 +#define QSERDES_V3_COM_LOCK_CMP3_MODE1 0x0ac +#define QSERDES_V3_COM_CLK_EP_DIV 0x05c +#define QSERDES_V3_COM_CP_CTRL_MODE0 0x060 +#define QSERDES_V3_COM_CP_CTRL_MODE1 0x064 +#define QSERDES_V3_COM_PLL_RCTRL_MODE0 0x068 +#define QSERDES_V3_COM_PLL_RCTRL_MODE1 0x06c +#define QSERDES_V3_COM_PLL_CCTRL_MODE0 0x070 +#define QSERDES_V3_COM_PLL_CCTRL_MODE1 0x074 +#define QSERDES_V3_COM_SYSCLK_EN_SEL 0x080 +#define QSERDES_V3_COM_RESETSM_CNTRL 0x088 +#define QSERDES_V3_COM_RESETSM_CNTRL2 0x08c +#define QSERDES_V3_COM_LOCK_CMP_EN 0x090 +#define QSERDES_V3_COM_LOCK_CMP_CFG 0x094 +#define QSERDES_V3_COM_DEC_START_MODE0 0x0b0 +#define QSERDES_V3_COM_DEC_START_MODE1 0x0b4 +#define QSERDES_V3_COM_DIV_FRAC_START1_MODE0 0x0b8 +#define QSERDES_V3_COM_DIV_FRAC_START2_MODE0 0x0bc +#define QSERDES_V3_COM_DIV_FRAC_START3_MODE0 0x0c0 +#define QSERDES_V3_COM_DIV_FRAC_START1_MODE1 0x0c4 +#define QSERDES_V3_COM_DIV_FRAC_START2_MODE1 0x0c8 +#define QSERDES_V3_COM_DIV_FRAC_START3_MODE1 0x0cc +#define QSERDES_V3_COM_INTEGLOOP_GAIN0_MODE0 0x0d8 +#define QSERDES_V3_COM_INTEGLOOP_GAIN1_MODE0 0x0dc +#define QSERDES_V3_COM_INTEGLOOP_GAIN0_MODE1 0x0e0 +#define QSERDES_V3_COM_INTEGLOOP_GAIN1_MODE1 0x0e4 +#define QSERDES_V3_COM_VCO_TUNE_CTRL 0x0ec +#define QSERDES_V3_COM_VCO_TUNE_MAP 0x0f0 +#define QSERDES_V3_COM_VCO_TUNE1_MODE0 0x0f4 +#define QSERDES_V3_COM_VCO_TUNE2_MODE0 0x0f8 +#define QSERDES_V3_COM_VCO_TUNE1_MODE1 0x0fc +#define QSERDES_V3_COM_VCO_TUNE2_MODE1 0x100 +#define QSERDES_V3_COM_VCO_TUNE_TIMER1 0x11c +#define QSERDES_V3_COM_VCO_TUNE_TIMER2 0x120 +#define QSERDES_V3_COM_CLK_SELECT 0x138 +#define QSERDES_V3_COM_HSCLK_SEL 0x13c +#define QSERDES_V3_COM_CORECLK_DIV_MODE0 0x148 +#define QSERDES_V3_COM_CORECLK_DIV_MODE1 0x14c +#define QSERDES_V3_COM_CORE_CLK_EN 0x154 +#define QSERDES_V3_COM_C_READY_STATUS 0x158 +#define QSERDES_V3_COM_CMN_CONFIG 0x15c +#define QSERDES_V3_COM_SVS_MODE_CLK_SEL 0x164 +#define QSERDES_V3_COM_DEBUG_BUS0 0x168 +#define QSERDES_V3_COM_DEBUG_BUS1 0x16c +#define QSERDES_V3_COM_DEBUG_BUS2 0x170 +#define QSERDES_V3_COM_DEBUG_BUS3 0x174 +#define QSERDES_V3_COM_DEBUG_BUS_SEL 0x178 + +/* Only for QMP V3 PHY - TX registers */ +#define QSERDES_V3_TX_RES_CODE_LANE_OFFSET_TX 0x044 +#define QSERDES_V3_TX_RES_CODE_LANE_OFFSET_RX 0x048 +#define QSERDES_V3_TX_DEBUG_BUS_SEL 0x058 +#define QSERDES_V3_TX_HIGHZ_DRVR_EN 0x060 +#define QSERDES_V3_TX_LANE_MODE_1 0x08c +#define QSERDES_V3_TX_RCV_DETECT_LVL_2 0x0a4 + +/* Only for QMP V3 PHY - RX registers */ +#define QSERDES_V3_RX_UCDR_SO_GAIN_HALF 0x00c +#define QSERDES_V3_RX_UCDR_SO_GAIN 0x014 +#define QSERDES_V3_RX_UCDR_FASTLOCK_FO_GAIN 0x030 +#define QSERDES_V3_RX_UCDR_SO_SATURATION_AND_ENABLE 0x034 +#define QSERDES_V3_RX_RX_TERM_BW 0x07c +#define QSERDES_V3_RX_RX_EQ_GAIN2_LSB 0x0c8 +#define QSERDES_V3_RX_RX_EQ_GAIN2_MSB 0x0cc +#define QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL2 0x0d4 +#define QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL3 0x0d8 +#define QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL4 0x0dc +#define QSERDES_V3_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1 0x0f8 +#define QSERDES_V3_RX_RX_OFFSET_ADAPTOR_CNTRL2 0x0fc +#define QSERDES_V3_RX_SIGDET_ENABLES 0x100 +#define QSERDES_V3_RX_SIGDET_CNTRL 0x104 +#define QSERDES_V3_RX_SIGDET_LVL 0x108 +#define QSERDES_V3_RX_SIGDET_DEGLITCH_CNTRL 0x10c +#define QSERDES_V3_RX_RX_BAND 0x110 +#define QSERDES_V3_RX_RX_INTERFACE_MODE 0x11c + +/* Only for QMP V3 PHY - PCS registers */ +#define QPHY_V3_PCS_POWER_DOWN_CONTROL 0x004 +#define QPHY_V3_PCS_TXMGN_V0 0x00c +#define QPHY_V3_PCS_TXMGN_V1 0x010 +#define QPHY_V3_PCS_TXMGN_V2 0x014 +#define QPHY_V3_PCS_TXMGN_V3 0x018 +#define QPHY_V3_PCS_TXMGN_V4 0x01c +#define QPHY_V3_PCS_TXMGN_LS 0x020 +#define QPHY_V3_PCS_TXDEEMPH_M6DB_V0 0x024 +#define QPHY_V3_PCS_TXDEEMPH_M3P5DB_V0 0x028 +#define QPHY_V3_PCS_TXDEEMPH_M6DB_V1 0x02c +#define QPHY_V3_PCS_TXDEEMPH_M3P5DB_V1 0x030 +#define QPHY_V3_PCS_TXDEEMPH_M6DB_V2 0x034 +#define QPHY_V3_PCS_TXDEEMPH_M3P5DB_V2 0x038 +#define QPHY_V3_PCS_TXDEEMPH_M6DB_V3 0x03c +#define QPHY_V3_PCS_TXDEEMPH_M3P5DB_V3 0x040 +#define QPHY_V3_PCS_TXDEEMPH_M6DB_V4 0x044 +#define QPHY_V3_PCS_TXDEEMPH_M3P5DB_V4 0x048 +#define QPHY_V3_PCS_TXDEEMPH_M6DB_LS 0x04c +#define QPHY_V3_PCS_TXDEEMPH_M3P5DB_LS 0x050 +#define QPHY_V3_PCS_ENDPOINT_REFCLK_DRIVE 0x054 +#define QPHY_V3_PCS_RX_IDLE_DTCT_CNTRL 0x058 +#define QPHY_V3_PCS_RATE_SLEW_CNTRL 0x05c +#define QPHY_V3_PCS_POWER_STATE_CONFIG1 0x060 +#define QPHY_V3_PCS_POWER_STATE_CONFIG2 0x064 +#define QPHY_V3_PCS_POWER_STATE_CONFIG4 0x06c +#define QPHY_V3_PCS_RCVR_DTCT_DLY_P1U2_L 0x070 +#define QPHY_V3_PCS_RCVR_DTCT_DLY_P1U2_H 0x074 +#define QPHY_V3_PCS_RCVR_DTCT_DLY_U3_L 0x078 +#define QPHY_V3_PCS_RCVR_DTCT_DLY_U3_H 0x07c +#define QPHY_V3_PCS_LOCK_DETECT_CONFIG1 0x080 +#define QPHY_V3_PCS_LOCK_DETECT_CONFIG2 0x084 +#define QPHY_V3_PCS_LOCK_DETECT_CONFIG3 0x088 +#define QPHY_V3_PCS_TSYNC_RSYNC_TIME 0x08c +#define QPHY_V3_PCS_PWRUP_RESET_DLY_TIME_AUXCLK 0x0a0 +#define QPHY_V3_PCS_LP_WAKEUP_DLY_TIME_AUXCLK 0x0a4 +#define QPHY_V3_PCS_LFPS_TX_ECSTART_EQTLOCK 0x0b0 +#define QPHY_V3_PCS_RXEQTRAINING_WAIT_TIME 0x0b8 +#define QPHY_V3_PCS_RXEQTRAINING_RUN_TIME 0x0bc +#define QPHY_V3_PCS_FLL_CNTRL1 0x0c4 +#define QPHY_V3_PCS_FLL_CNTRL2 0x0c8 +#define QPHY_V3_PCS_FLL_CNT_VAL_L 0x0cc +#define QPHY_V3_PCS_FLL_CNT_VAL_H_TOL 0x0d0 +#define QPHY_V3_PCS_FLL_MAN_CODE 0x0d4 +#define QPHY_V3_PCS_RX_SIGDET_LVL 0x1d8 + +/* Only for QMP V3 PHY - PCS_MISC registers */ +#define QPHY_V3_PCS_MISC_CLAMP_ENABLE 0x0c + +#endif diff --git a/drivers/phy/qualcomm/phy-qcom-qusb2.c b/drivers/phy/qualcomm/phy-qcom-qusb2.c index 6c575244c0fb..94afeac1a19e 100644 --- a/drivers/phy/qualcomm/phy-qcom-qusb2.c +++ b/drivers/phy/qualcomm/phy-qcom-qusb2.c @@ -1,14 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2017, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include <linux/clk.h> @@ -37,28 +29,57 @@ #define QUSB2PHY_PLL_AUTOPGM_CTL1 0x1c #define QUSB2PHY_PLL_PWR_CTRL 0x18 -#define QUSB2PHY_PLL_STATUS 0x38 +/* QUSB2PHY_PLL_STATUS register bits */ #define PLL_LOCKED BIT(5) -#define QUSB2PHY_PORT_TUNE1 0x80 -#define QUSB2PHY_PORT_TUNE2 0x84 -#define QUSB2PHY_PORT_TUNE3 0x88 -#define QUSB2PHY_PORT_TUNE4 0x8c -#define QUSB2PHY_PORT_TUNE5 0x90 -#define QUSB2PHY_PORT_TEST2 0x9c +/* QUSB2PHY_PLL_COMMON_STATUS_ONE register bits */ +#define CORE_READY_STATUS BIT(0) -#define QUSB2PHY_PORT_POWERDOWN 0xb4 +/* QUSB2PHY_PORT_POWERDOWN register bits */ #define CLAMP_N_EN BIT(5) #define FREEZIO_N BIT(1) #define POWER_DOWN BIT(0) +/* QUSB2PHY_PWR_CTRL1 register bits */ +#define PWR_CTRL1_VREF_SUPPLY_TRIM BIT(5) +#define PWR_CTRL1_CLAMP_N_EN BIT(1) + #define QUSB2PHY_REFCLK_ENABLE BIT(0) #define PHY_CLK_SCHEME_SEL BIT(0) +/* QUSB2PHY_INTR_CTRL register bits */ +#define DMSE_INTR_HIGH_SEL BIT(4) +#define DPSE_INTR_HIGH_SEL BIT(3) +#define CHG_DET_INTR_EN BIT(2) +#define DMSE_INTR_EN BIT(1) +#define DPSE_INTR_EN BIT(0) + +/* QUSB2PHY_PLL_CORE_INPUT_OVERRIDE register bits */ +#define CORE_PLL_EN_FROM_RESET BIT(4) +#define CORE_RESET BIT(5) +#define CORE_RESET_MUX BIT(6) + +#define QUSB2PHY_PLL_ANALOG_CONTROLS_TWO 0x04 +#define QUSB2PHY_PLL_CLOCK_INVERTERS 0x18c +#define QUSB2PHY_PLL_CMODE 0x2c +#define QUSB2PHY_PLL_LOCK_DELAY 0x184 +#define QUSB2PHY_PLL_DIGITAL_TIMERS_TWO 0xb4 +#define QUSB2PHY_PLL_BIAS_CONTROL_1 0x194 +#define QUSB2PHY_PLL_BIAS_CONTROL_2 0x198 +#define QUSB2PHY_PWR_CTRL2 0x214 +#define QUSB2PHY_IMP_CTRL1 0x220 +#define QUSB2PHY_IMP_CTRL2 0x224 +#define QUSB2PHY_CHG_CTRL2 0x23c + struct qusb2_phy_init_tbl { unsigned int offset; unsigned int val; + /* + * register part of layout ? + * if yes, then offset gives index in the reg-layout + */ + int in_layout; }; #define QUSB2_PHY_INIT_CFG(o, v) \ @@ -67,30 +88,136 @@ struct qusb2_phy_init_tbl { .val = v, \ } +#define QUSB2_PHY_INIT_CFG_L(o, v) \ + { \ + .offset = o, \ + .val = v, \ + .in_layout = 1, \ + } + +/* set of registers with offsets different per-PHY */ +enum qusb2phy_reg_layout { + QUSB2PHY_PLL_CORE_INPUT_OVERRIDE, + QUSB2PHY_PLL_STATUS, + QUSB2PHY_PORT_TUNE1, + QUSB2PHY_PORT_TUNE2, + QUSB2PHY_PORT_TUNE3, + QUSB2PHY_PORT_TUNE4, + QUSB2PHY_PORT_TUNE5, + QUSB2PHY_PORT_TEST1, + QUSB2PHY_PORT_TEST2, + QUSB2PHY_PORT_POWERDOWN, + QUSB2PHY_INTR_CTRL, +}; + +static const unsigned int msm8996_regs_layout[] = { + [QUSB2PHY_PLL_STATUS] = 0x38, + [QUSB2PHY_PORT_TUNE1] = 0x80, + [QUSB2PHY_PORT_TUNE2] = 0x84, + [QUSB2PHY_PORT_TUNE3] = 0x88, + [QUSB2PHY_PORT_TUNE4] = 0x8c, + [QUSB2PHY_PORT_TUNE5] = 0x90, + [QUSB2PHY_PORT_TEST1] = 0xb8, + [QUSB2PHY_PORT_TEST2] = 0x9c, + [QUSB2PHY_PORT_POWERDOWN] = 0xb4, + [QUSB2PHY_INTR_CTRL] = 0xbc, +}; + static const struct qusb2_phy_init_tbl msm8996_init_tbl[] = { - QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE1, 0xf8), - QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE2, 0xb3), - QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE3, 0x83), - QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE4, 0xc0), + QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE1, 0xf8), + QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE2, 0xb3), + QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE3, 0x83), + QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE4, 0xc0), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_TUNE, 0x30), QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_USER_CTL1, 0x79), QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_USER_CTL2, 0x21), - QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TEST2, 0x14), + + QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TEST2, 0x14), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_AUTOPGM_CTL1, 0x9f), QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_PWR_CTRL, 0x00), }; +static const unsigned int qusb2_v2_regs_layout[] = { + [QUSB2PHY_PLL_CORE_INPUT_OVERRIDE] = 0xa8, + [QUSB2PHY_PLL_STATUS] = 0x1a0, + [QUSB2PHY_PORT_TUNE1] = 0x240, + [QUSB2PHY_PORT_TUNE2] = 0x244, + [QUSB2PHY_PORT_TUNE3] = 0x248, + [QUSB2PHY_PORT_TUNE4] = 0x24c, + [QUSB2PHY_PORT_TUNE5] = 0x250, + [QUSB2PHY_PORT_TEST1] = 0x254, + [QUSB2PHY_PORT_TEST2] = 0x258, + [QUSB2PHY_PORT_POWERDOWN] = 0x210, + [QUSB2PHY_INTR_CTRL] = 0x230, +}; + +static const struct qusb2_phy_init_tbl qusb2_v2_init_tbl[] = { + QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_ANALOG_CONTROLS_TWO, 0x03), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_CLOCK_INVERTERS, 0x7c), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_CMODE, 0x80), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_LOCK_DELAY, 0x0a), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_DIGITAL_TIMERS_TWO, 0x19), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_BIAS_CONTROL_1, 0x40), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_BIAS_CONTROL_2, 0x20), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PWR_CTRL2, 0x21), + QUSB2_PHY_INIT_CFG(QUSB2PHY_IMP_CTRL1, 0x0), + QUSB2_PHY_INIT_CFG(QUSB2PHY_IMP_CTRL2, 0x58), + + QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE1, 0x30), + QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE2, 0x29), + QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE3, 0xca), + QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE4, 0x04), + QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE5, 0x03), + + QUSB2_PHY_INIT_CFG(QUSB2PHY_CHG_CTRL2, 0x0), +}; + struct qusb2_phy_cfg { const struct qusb2_phy_init_tbl *tbl; /* number of entries in the table */ unsigned int tbl_num; /* offset to PHY_CLK_SCHEME register in TCSR map */ unsigned int clk_scheme_offset; + + /* array of registers with different offsets */ + const unsigned int *regs; + unsigned int mask_core_ready; + unsigned int disable_ctrl; + unsigned int autoresume_en; + + /* true if PHY has PLL_TEST register to select clk_scheme */ + bool has_pll_test; + + /* true if TUNE1 register must be updated by fused value, else TUNE2 */ + bool update_tune1_with_efuse; + + /* true if PHY has PLL_CORE_INPUT_OVERRIDE register to reset PLL */ + bool has_pll_override; }; static const struct qusb2_phy_cfg msm8996_phy_cfg = { - .tbl = msm8996_init_tbl, - .tbl_num = ARRAY_SIZE(msm8996_init_tbl), + .tbl = msm8996_init_tbl, + .tbl_num = ARRAY_SIZE(msm8996_init_tbl), + .regs = msm8996_regs_layout, + + .has_pll_test = true, + .disable_ctrl = (CLAMP_N_EN | FREEZIO_N | POWER_DOWN), + .mask_core_ready = PLL_LOCKED, + .autoresume_en = BIT(3), +}; + +static const struct qusb2_phy_cfg qusb2_v2_phy_cfg = { + .tbl = qusb2_v2_init_tbl, + .tbl_num = ARRAY_SIZE(qusb2_v2_init_tbl), + .regs = qusb2_v2_regs_layout, + + .disable_ctrl = (PWR_CTRL1_VREF_SUPPLY_TRIM | PWR_CTRL1_CLAMP_N_EN | + POWER_DOWN), + .mask_core_ready = CORE_READY_STATUS, + .has_pll_override = true, + .autoresume_en = BIT(0), }; static const char * const qusb2_phy_vreg_names[] = { @@ -116,6 +243,8 @@ static const char * const qusb2_phy_vreg_names[] = { * * @cfg: phy config data * @has_se_clk_scheme: indicate if PHY has single-ended ref clock scheme + * @phy_initialized: indicate if PHY has been initialized + * @mode: current PHY mode */ struct qusb2_phy { struct phy *phy; @@ -132,6 +261,8 @@ struct qusb2_phy { const struct qusb2_phy_cfg *cfg; bool has_se_clk_scheme; + bool phy_initialized; + enum phy_mode mode; }; static inline void qusb2_setbits(void __iomem *base, u32 offset, u32 val) @@ -160,26 +291,32 @@ static inline void qusb2_clrbits(void __iomem *base, u32 offset, u32 val) static inline void qcom_qusb2_phy_configure(void __iomem *base, + const unsigned int *regs, const struct qusb2_phy_init_tbl tbl[], int num) { int i; - for (i = 0; i < num; i++) - writel(tbl[i].val, base + tbl[i].offset); + for (i = 0; i < num; i++) { + if (tbl[i].in_layout) + writel(tbl[i].val, base + regs[tbl[i].offset]); + else + writel(tbl[i].val, base + tbl[i].offset); + } } /* * Fetches HS Tx tuning value from nvmem and sets the - * QUSB2PHY_PORT_TUNE2 register. + * QUSB2PHY_PORT_TUNE1/2 register. * For error case, skip setting the value and use the default value. */ static void qusb2_phy_set_tune2_param(struct qusb2_phy *qphy) { struct device *dev = &qphy->phy->dev; + const struct qusb2_phy_cfg *cfg = qphy->cfg; u8 *val; /* - * Read efuse register having TUNE2 parameter's high nibble. + * Read efuse register having TUNE2/1 parameter's high nibble. * If efuse register shows value as 0x0, or if we fail to find * a valid efuse register settings, then use default value * as 0xB for high nibble that we have already set while @@ -191,58 +328,169 @@ static void qusb2_phy_set_tune2_param(struct qusb2_phy *qphy) return; } - /* Fused TUNE2 value is the higher nibble only */ - qusb2_setbits(qphy->base, QUSB2PHY_PORT_TUNE2, val[0] << 0x4); + /* Fused TUNE1/2 value is the higher nibble only */ + if (cfg->update_tune1_with_efuse) + qusb2_setbits(qphy->base, cfg->regs[QUSB2PHY_PORT_TUNE1], + val[0] << 0x4); + else + qusb2_setbits(qphy->base, cfg->regs[QUSB2PHY_PORT_TUNE2], + val[0] << 0x4); + } -static int qusb2_phy_poweron(struct phy *phy) +static int qusb2_phy_set_mode(struct phy *phy, enum phy_mode mode) { struct qusb2_phy *qphy = phy_get_drvdata(phy); - int num = ARRAY_SIZE(qphy->vregs); + + qphy->mode = mode; + + return 0; +} + +static int __maybe_unused qusb2_phy_runtime_suspend(struct device *dev) +{ + struct qusb2_phy *qphy = dev_get_drvdata(dev); + const struct qusb2_phy_cfg *cfg = qphy->cfg; + u32 intr_mask; + + dev_vdbg(dev, "Suspending QUSB2 Phy, mode:%d\n", qphy->mode); + + if (!qphy->phy_initialized) { + dev_vdbg(dev, "PHY not initialized, bailing out\n"); + return 0; + } + + /* + * Enable DP/DM interrupts to detect line state changes based on current + * speed. In other words, enable the triggers _opposite_ of what the + * current D+/D- levels are e.g. if currently D+ high, D- low + * (HS 'J'/Suspend), configure the mask to trigger on D+ low OR D- high + */ + intr_mask = DPSE_INTR_EN | DMSE_INTR_EN; + switch (qphy->mode) { + case PHY_MODE_USB_HOST_HS: + case PHY_MODE_USB_HOST_FS: + case PHY_MODE_USB_DEVICE_HS: + case PHY_MODE_USB_DEVICE_FS: + intr_mask |= DMSE_INTR_HIGH_SEL; + break; + case PHY_MODE_USB_HOST_LS: + case PHY_MODE_USB_DEVICE_LS: + intr_mask |= DPSE_INTR_HIGH_SEL; + break; + default: + /* No device connected, enable both DP/DM high interrupt */ + intr_mask |= DMSE_INTR_HIGH_SEL; + intr_mask |= DPSE_INTR_HIGH_SEL; + break; + } + + writel(intr_mask, qphy->base + cfg->regs[QUSB2PHY_INTR_CTRL]); + + /* hold core PLL into reset */ + if (cfg->has_pll_override) { + qusb2_setbits(qphy->base, + cfg->regs[QUSB2PHY_PLL_CORE_INPUT_OVERRIDE], + CORE_PLL_EN_FROM_RESET | CORE_RESET | + CORE_RESET_MUX); + } + + /* enable phy auto-resume only if device is connected on bus */ + if (qphy->mode != PHY_MODE_INVALID) { + qusb2_setbits(qphy->base, cfg->regs[QUSB2PHY_PORT_TEST1], + cfg->autoresume_en); + /* Autoresume bit has to be toggled in order to enable it */ + qusb2_clrbits(qphy->base, cfg->regs[QUSB2PHY_PORT_TEST1], + cfg->autoresume_en); + } + + if (!qphy->has_se_clk_scheme) + clk_disable_unprepare(qphy->ref_clk); + + clk_disable_unprepare(qphy->cfg_ahb_clk); + clk_disable_unprepare(qphy->iface_clk); + + return 0; +} + +static int __maybe_unused qusb2_phy_runtime_resume(struct device *dev) +{ + struct qusb2_phy *qphy = dev_get_drvdata(dev); + const struct qusb2_phy_cfg *cfg = qphy->cfg; int ret; - dev_vdbg(&phy->dev, "%s(): Powering-on QUSB2 phy\n", __func__); + dev_vdbg(dev, "Resuming QUSB2 phy, mode:%d\n", qphy->mode); - /* turn on regulator supplies */ - ret = regulator_bulk_enable(num, qphy->vregs); - if (ret) - return ret; + if (!qphy->phy_initialized) { + dev_vdbg(dev, "PHY not initialized, bailing out\n"); + return 0; + } ret = clk_prepare_enable(qphy->iface_clk); if (ret) { - dev_err(&phy->dev, "failed to enable iface_clk, %d\n", ret); - regulator_bulk_disable(num, qphy->vregs); + dev_err(dev, "failed to enable iface_clk, %d\n", ret); return ret; } - return 0; -} + ret = clk_prepare_enable(qphy->cfg_ahb_clk); + if (ret) { + dev_err(dev, "failed to enable cfg ahb clock, %d\n", ret); + goto disable_iface_clk; + } -static int qusb2_phy_poweroff(struct phy *phy) -{ - struct qusb2_phy *qphy = phy_get_drvdata(phy); + if (!qphy->has_se_clk_scheme) { + clk_prepare_enable(qphy->ref_clk); + if (ret) { + dev_err(dev, "failed to enable ref clk, %d\n", ret); + goto disable_ahb_clk; + } + } - clk_disable_unprepare(qphy->iface_clk); + writel(0x0, qphy->base + cfg->regs[QUSB2PHY_INTR_CTRL]); - regulator_bulk_disable(ARRAY_SIZE(qphy->vregs), qphy->vregs); + /* bring core PLL out of reset */ + if (cfg->has_pll_override) { + qusb2_clrbits(qphy->base, + cfg->regs[QUSB2PHY_PLL_CORE_INPUT_OVERRIDE], + CORE_RESET | CORE_RESET_MUX); + } return 0; + +disable_ahb_clk: + clk_disable_unprepare(qphy->cfg_ahb_clk); +disable_iface_clk: + clk_disable_unprepare(qphy->iface_clk); + + return ret; } static int qusb2_phy_init(struct phy *phy) { struct qusb2_phy *qphy = phy_get_drvdata(phy); - unsigned int val; + const struct qusb2_phy_cfg *cfg = qphy->cfg; + unsigned int val = 0; unsigned int clk_scheme; int ret; dev_vdbg(&phy->dev, "%s(): Initializing QUSB2 phy\n", __func__); + /* turn on regulator supplies */ + ret = regulator_bulk_enable(ARRAY_SIZE(qphy->vregs), qphy->vregs); + if (ret) + return ret; + + ret = clk_prepare_enable(qphy->iface_clk); + if (ret) { + dev_err(&phy->dev, "failed to enable iface_clk, %d\n", ret); + goto poweroff_phy; + } + /* enable ahb interface clock to program phy */ ret = clk_prepare_enable(qphy->cfg_ahb_clk); if (ret) { dev_err(&phy->dev, "failed to enable cfg ahb clock, %d\n", ret); - return ret; + goto disable_iface_clk; } /* Perform phy reset */ @@ -262,20 +510,23 @@ static int qusb2_phy_init(struct phy *phy) } /* Disable the PHY */ - qusb2_setbits(qphy->base, QUSB2PHY_PORT_POWERDOWN, - CLAMP_N_EN | FREEZIO_N | POWER_DOWN); + qusb2_setbits(qphy->base, cfg->regs[QUSB2PHY_PORT_POWERDOWN], + qphy->cfg->disable_ctrl); - /* save reset value to override reference clock scheme later */ - val = readl(qphy->base + QUSB2PHY_PLL_TEST); + if (cfg->has_pll_test) { + /* save reset value to override reference clock scheme later */ + val = readl(qphy->base + QUSB2PHY_PLL_TEST); + } - qcom_qusb2_phy_configure(qphy->base, qphy->cfg->tbl, - qphy->cfg->tbl_num); + qcom_qusb2_phy_configure(qphy->base, cfg->regs, cfg->tbl, + cfg->tbl_num); /* Set efuse value for tuning the PHY */ qusb2_phy_set_tune2_param(qphy); /* Enable the PHY */ - qusb2_clrbits(qphy->base, QUSB2PHY_PORT_POWERDOWN, POWER_DOWN); + qusb2_clrbits(qphy->base, cfg->regs[QUSB2PHY_PORT_POWERDOWN], + POWER_DOWN); /* Required to get phy pll lock successfully */ usleep_range(150, 160); @@ -308,32 +559,37 @@ static int qusb2_phy_init(struct phy *phy) } if (!qphy->has_se_clk_scheme) { - val &= ~CLK_REF_SEL; ret = clk_prepare_enable(qphy->ref_clk); if (ret) { dev_err(&phy->dev, "failed to enable ref clk, %d\n", ret); goto assert_phy_reset; } - } else { - val |= CLK_REF_SEL; } - writel(val, qphy->base + QUSB2PHY_PLL_TEST); + if (cfg->has_pll_test) { + if (!qphy->has_se_clk_scheme) + val &= ~CLK_REF_SEL; + else + val |= CLK_REF_SEL; - /* ensure above write is through */ - readl(qphy->base + QUSB2PHY_PLL_TEST); + writel(val, qphy->base + QUSB2PHY_PLL_TEST); + + /* ensure above write is through */ + readl(qphy->base + QUSB2PHY_PLL_TEST); + } /* Required to get phy pll lock successfully */ usleep_range(100, 110); - val = readb(qphy->base + QUSB2PHY_PLL_STATUS); - if (!(val & PLL_LOCKED)) { + val = readb(qphy->base + cfg->regs[QUSB2PHY_PLL_STATUS]); + if (!(val & cfg->mask_core_ready)) { dev_err(&phy->dev, "QUSB2PHY pll lock failed: status reg = %x\n", val); ret = -EBUSY; goto disable_ref_clk; } + qphy->phy_initialized = true; return 0; @@ -344,6 +600,11 @@ assert_phy_reset: reset_control_assert(qphy->phy_reset); disable_ahb_clk: clk_disable_unprepare(qphy->cfg_ahb_clk); +disable_iface_clk: + clk_disable_unprepare(qphy->iface_clk); +poweroff_phy: + regulator_bulk_disable(ARRAY_SIZE(qphy->vregs), qphy->vregs); + return ret; } @@ -352,8 +613,8 @@ static int qusb2_phy_exit(struct phy *phy) struct qusb2_phy *qphy = phy_get_drvdata(phy); /* Disable the PHY */ - qusb2_setbits(qphy->base, QUSB2PHY_PORT_POWERDOWN, - CLAMP_N_EN | FREEZIO_N | POWER_DOWN); + qusb2_setbits(qphy->base, qphy->cfg->regs[QUSB2PHY_PORT_POWERDOWN], + qphy->cfg->disable_ctrl); if (!qphy->has_se_clk_scheme) clk_disable_unprepare(qphy->ref_clk); @@ -361,6 +622,11 @@ static int qusb2_phy_exit(struct phy *phy) reset_control_assert(qphy->phy_reset); clk_disable_unprepare(qphy->cfg_ahb_clk); + clk_disable_unprepare(qphy->iface_clk); + + regulator_bulk_disable(ARRAY_SIZE(qphy->vregs), qphy->vregs); + + qphy->phy_initialized = false; return 0; } @@ -368,8 +634,7 @@ static int qusb2_phy_exit(struct phy *phy) static const struct phy_ops qusb2_phy_gen_ops = { .init = qusb2_phy_init, .exit = qusb2_phy_exit, - .power_on = qusb2_phy_poweron, - .power_off = qusb2_phy_poweroff, + .set_mode = qusb2_phy_set_mode, .owner = THIS_MODULE, }; @@ -377,11 +642,19 @@ static const struct of_device_id qusb2_phy_of_match_table[] = { { .compatible = "qcom,msm8996-qusb2-phy", .data = &msm8996_phy_cfg, + }, { + .compatible = "qcom,qusb2-v2-phy", + .data = &qusb2_v2_phy_cfg, }, { }, }; MODULE_DEVICE_TABLE(of, qusb2_phy_of_match_table); +static const struct dev_pm_ops qusb2_phy_pm_ops = { + SET_RUNTIME_PM_OPS(qusb2_phy_runtime_suspend, + qusb2_phy_runtime_resume, NULL) +}; + static int qusb2_phy_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -459,11 +732,19 @@ static int qusb2_phy_probe(struct platform_device *pdev) qphy->cell = NULL; dev_dbg(dev, "failed to lookup tune2 hstx trim value\n"); } + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + /* + * Prevent runtime pm from being ON by default. Users can enable + * it using power/control in sysfs. + */ + pm_runtime_forbid(dev); generic_phy = devm_phy_create(dev, NULL, &qusb2_phy_gen_ops); if (IS_ERR(generic_phy)) { ret = PTR_ERR(generic_phy); dev_err(dev, "failed to create phy, %d\n", ret); + pm_runtime_disable(dev); return ret; } qphy->phy = generic_phy; @@ -474,6 +755,8 @@ static int qusb2_phy_probe(struct platform_device *pdev) phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); if (!IS_ERR(phy_provider)) dev_info(dev, "Registered Qcom-QUSB2 phy\n"); + else + pm_runtime_disable(dev); return PTR_ERR_OR_ZERO(phy_provider); } @@ -482,6 +765,7 @@ static struct platform_driver qusb2_phy_driver = { .probe = qusb2_phy_probe, .driver = { .name = "qcom-qusb2-phy", + .pm = &qusb2_phy_pm_ops, .of_match_table = qusb2_phy_of_match_table, }, }; diff --git a/drivers/phy/ralink/Kconfig b/drivers/phy/ralink/Kconfig index b17635b407bc..14fd219535ef 100644 --- a/drivers/phy/ralink/Kconfig +++ b/drivers/phy/ralink/Kconfig @@ -4,6 +4,7 @@ config PHY_RALINK_USB tristate "Ralink USB PHY driver" depends on RALINK || COMPILE_TEST + depends on HAS_IOMEM select GENERIC_PHY select MFD_SYSCON help diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c index 9c90e7d67e0a..fb8f05e39cf7 100644 --- a/drivers/phy/renesas/phy-rcar-gen3-usb2.c +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -397,6 +397,10 @@ static const struct of_device_id rcar_gen3_phy_usb2_match_table[] = { .data = (void *)RCAR_GEN3_PHY_HAS_DEDICATED_PINS, }, { + .compatible = "renesas,usb2-phy-r8a77965", + .data = (void *)RCAR_GEN3_PHY_HAS_DEDICATED_PINS, + }, + { .compatible = "renesas,rcar-gen3-usb2-phy", }, { } diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig index f5325b2b679e..0e15119ddfc6 100644 --- a/drivers/phy/rockchip/Kconfig +++ b/drivers/phy/rockchip/Kconfig @@ -29,6 +29,7 @@ config PHY_ROCKCHIP_INNO_USB2 config PHY_ROCKCHIP_PCIE tristate "Rockchip PCIe PHY Driver" depends on (ARCH_ROCKCHIP && OF) || COMPILE_TEST + depends on HAS_IOMEM select GENERIC_PHY select MFD_SYSCON help diff --git a/drivers/phy/rockchip/phy-rockchip-emmc.c b/drivers/phy/rockchip/phy-rockchip-emmc.c index f1b24f18e9b2..b237360f95f6 100644 --- a/drivers/phy/rockchip/phy-rockchip-emmc.c +++ b/drivers/phy/rockchip/phy-rockchip-emmc.c @@ -76,6 +76,13 @@ #define PHYCTRL_OTAPDLYSEL_MASK 0xf #define PHYCTRL_OTAPDLYSEL_SHIFT 0x7 +#define PHYCTRL_IS_CALDONE(x) \ + ((((x) >> PHYCTRL_CALDONE_SHIFT) & \ + PHYCTRL_CALDONE_MASK) == PHYCTRL_CALDONE_DONE) +#define PHYCTRL_IS_DLLRDY(x) \ + ((((x) >> PHYCTRL_DLLRDY_SHIFT) & \ + PHYCTRL_DLLRDY_MASK) == PHYCTRL_DLLRDY_DONE) + struct rockchip_emmc_phy { unsigned int reg_offset; struct regmap *reg_base; @@ -89,7 +96,7 @@ static int rockchip_emmc_phy_power(struct phy *phy, bool on_off) unsigned int dllrdy; unsigned int freqsel = PHYCTRL_FREQSEL_200M; unsigned long rate; - unsigned long timeout; + int ret; /* * Keep phyctrl_pdb and phyctrl_endll low to allow @@ -160,17 +167,19 @@ static int rockchip_emmc_phy_power(struct phy *phy, bool on_off) PHYCTRL_PDB_SHIFT)); /* - * According to the user manual, it asks driver to - * wait 5us for calpad busy trimming + * According to the user manual, it asks driver to wait 5us for + * calpad busy trimming. However it is documented that this value is + * PVT(A.K.A process,voltage and temperature) relevant, so some + * failure cases are found which indicates we should be more tolerant + * to calpad busy trimming. */ - udelay(5); - regmap_read(rk_phy->reg_base, - rk_phy->reg_offset + GRF_EMMCPHY_STATUS, - &caldone); - caldone = (caldone >> PHYCTRL_CALDONE_SHIFT) & PHYCTRL_CALDONE_MASK; - if (caldone != PHYCTRL_CALDONE_DONE) { - pr_err("rockchip_emmc_phy_power: caldone timeout.\n"); - return -ETIMEDOUT; + ret = regmap_read_poll_timeout(rk_phy->reg_base, + rk_phy->reg_offset + GRF_EMMCPHY_STATUS, + caldone, PHYCTRL_IS_CALDONE(caldone), + 0, 50); + if (ret) { + pr_err("%s: caldone failed, ret=%d\n", __func__, ret); + return ret; } /* Set the frequency of the DLL operation */ @@ -210,28 +219,15 @@ static int rockchip_emmc_phy_power(struct phy *phy, bool on_off) * NOTE: There appear to be corner cases where the DLL seems to take * extra long to lock for reasons that aren't understood. In some * extreme cases we've seen it take up to over 10ms (!). We'll be - * generous and give it 50ms. We still busy wait here because: - * - In most cases it should be super fast. - * - This is not called lots during normal operation so it shouldn't - * be a power or performance problem to busy wait. We expect it - * only at boot / resume. In both cases, eMMC is probably on the - * critical path so busy waiting a little extra time should be OK. + * generous and give it 50ms. */ - timeout = jiffies + msecs_to_jiffies(50); - do { - udelay(1); - - regmap_read(rk_phy->reg_base, - rk_phy->reg_offset + GRF_EMMCPHY_STATUS, - &dllrdy); - dllrdy = (dllrdy >> PHYCTRL_DLLRDY_SHIFT) & PHYCTRL_DLLRDY_MASK; - if (dllrdy == PHYCTRL_DLLRDY_DONE) - break; - } while (!time_after(jiffies, timeout)); - - if (dllrdy != PHYCTRL_DLLRDY_DONE) { - pr_err("rockchip_emmc_phy_power: dllrdy timeout.\n"); - return -ETIMEDOUT; + ret = regmap_read_poll_timeout(rk_phy->reg_base, + rk_phy->reg_offset + GRF_EMMCPHY_STATUS, + dllrdy, PHYCTRL_IS_DLLRDY(dllrdy), + 0, 50 * USEC_PER_MSEC); + if (ret) { + pr_err("%s: dllrdy failed. ret=%d\n", __func__, ret); + return ret; } return 0; diff --git a/drivers/phy/rockchip/phy-rockchip-typec.c b/drivers/phy/rockchip/phy-rockchip-typec.c index 7492c8978217..76a4b58ec771 100644 --- a/drivers/phy/rockchip/phy-rockchip-typec.c +++ b/drivers/phy/rockchip/phy-rockchip-typec.c @@ -355,11 +355,26 @@ struct usb3phy_reg { u32 write_enable; }; +/** + * struct rockchip_usb3phy_port_cfg: usb3-phy port configuration. + * @reg: the base address for usb3-phy config. + * @typec_conn_dir: the register of type-c connector direction. + * @usb3tousb2_en: the register of type-c force usb2 to usb2 enable. + * @external_psm: the register of type-c phy external psm clock. + * @pipe_status: the register of type-c phy pipe status. + * @usb3_host_disable: the register of type-c usb3 host disable. + * @usb3_host_port: the register of type-c usb3 host port. + * @uphy_dp_sel: the register of type-c phy DP select control. + */ struct rockchip_usb3phy_port_cfg { + unsigned int reg; struct usb3phy_reg typec_conn_dir; struct usb3phy_reg usb3tousb2_en; struct usb3phy_reg external_psm; struct usb3phy_reg pipe_status; + struct usb3phy_reg usb3_host_disable; + struct usb3phy_reg usb3_host_port; + struct usb3phy_reg uphy_dp_sel; }; struct rockchip_typec_phy { @@ -372,7 +387,7 @@ struct rockchip_typec_phy { struct reset_control *uphy_rst; struct reset_control *pipe_rst; struct reset_control *tcphy_rst; - struct rockchip_usb3phy_port_cfg port_cfgs; + const struct rockchip_usb3phy_port_cfg *port_cfgs; /* mutex to protect access to individual PHYs */ struct mutex lock; @@ -424,6 +439,30 @@ struct phy_reg dp_pll_cfg[] = { { 0x4, CMN_DIAG_PLL1_INCLK_CTRL }, }; +static const struct rockchip_usb3phy_port_cfg rk3399_usb3phy_port_cfgs[] = { + { + .reg = 0xff7c0000, + .typec_conn_dir = { 0xe580, 0, 16 }, + .usb3tousb2_en = { 0xe580, 3, 19 }, + .external_psm = { 0xe588, 14, 30 }, + .pipe_status = { 0xe5c0, 0, 0 }, + .usb3_host_disable = { 0x2434, 0, 16 }, + .usb3_host_port = { 0x2434, 12, 28 }, + .uphy_dp_sel = { 0x6268, 19, 19 }, + }, + { + .reg = 0xff800000, + .typec_conn_dir = { 0xe58c, 0, 16 }, + .usb3tousb2_en = { 0xe58c, 3, 19 }, + .external_psm = { 0xe594, 14, 30 }, + .pipe_status = { 0xe5c0, 16, 16 }, + .usb3_host_disable = { 0x2444, 0, 16 }, + .usb3_host_port = { 0x2444, 12, 28 }, + .uphy_dp_sel = { 0x6268, 3, 19 }, + }, + { /* sentinel */ } +}; + static void tcphy_cfg_24m(struct rockchip_typec_phy *tcphy) { u32 i, rdata; @@ -691,7 +730,7 @@ static void tcphy_dp_aux_calibration(struct rockchip_typec_phy *tcphy) static int tcphy_phy_init(struct rockchip_typec_phy *tcphy, u8 mode) { - struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs; + const struct rockchip_usb3phy_port_cfg *cfg = tcphy->port_cfgs; int ret, i; u32 val; @@ -782,6 +821,9 @@ static int tcphy_get_mode(struct rockchip_typec_phy *tcphy) u8 mode; int ret; + if (!edev) + return MODE_DFP_USB; + ufp = extcon_get_state(edev, EXTCON_USB); dp = extcon_get_state(edev, EXTCON_DISP_DP); @@ -818,10 +860,22 @@ static int tcphy_get_mode(struct rockchip_typec_phy *tcphy) return mode; } +static int tcphy_cfg_usb3_to_usb2_only(struct rockchip_typec_phy *tcphy, + bool value) +{ + const struct rockchip_usb3phy_port_cfg *cfg = tcphy->port_cfgs; + + property_enable(tcphy, &cfg->usb3tousb2_en, value); + property_enable(tcphy, &cfg->usb3_host_disable, value); + property_enable(tcphy, &cfg->usb3_host_port, !value); + + return 0; +} + static int rockchip_usb3_phy_power_on(struct phy *phy) { struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy); - struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs; + const struct rockchip_usb3phy_port_cfg *cfg = tcphy->port_cfgs; const struct usb3phy_reg *reg = &cfg->pipe_status; int timeout, new_mode, ret = 0; u32 val; @@ -835,8 +889,10 @@ static int rockchip_usb3_phy_power_on(struct phy *phy) } /* DP-only mode; fall back to USB2 */ - if (!(new_mode & (MODE_DFP_USB | MODE_UFP_USB))) + if (!(new_mode & (MODE_DFP_USB | MODE_UFP_USB))) { + tcphy_cfg_usb3_to_usb2_only(tcphy, true); goto unlock_ret; + } if (tcphy->mode == new_mode) goto unlock_ret; @@ -852,6 +908,9 @@ static int rockchip_usb3_phy_power_on(struct phy *phy) regmap_read(tcphy->grf_regs, reg->offset, &val); if (!(val & BIT(reg->enable_bit))) { tcphy->mode |= new_mode & (MODE_DFP_USB | MODE_UFP_USB); + + /* enable usb3 host */ + tcphy_cfg_usb3_to_usb2_only(tcphy, false); goto unlock_ret; } usleep_range(10, 20); @@ -872,6 +931,7 @@ static int rockchip_usb3_phy_power_off(struct phy *phy) struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy); mutex_lock(&tcphy->lock); + tcphy_cfg_usb3_to_usb2_only(tcphy, false); if (tcphy->mode == MODE_DISCONNECT) goto unlock; @@ -894,6 +954,7 @@ static const struct phy_ops rockchip_usb3_phy_ops = { static int rockchip_dp_phy_power_on(struct phy *phy) { struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy); + const struct rockchip_usb3phy_port_cfg *cfg = tcphy->port_cfgs; int new_mode, ret = 0; u32 val; @@ -926,6 +987,8 @@ static int rockchip_dp_phy_power_on(struct phy *phy) if (ret) goto unlock_ret; + property_enable(tcphy, &cfg->uphy_dp_sel, 1); + ret = readx_poll_timeout(readl, tcphy->base + DP_MODE_CTL, val, val & DP_MODE_A2, 1000, PHY_MODE_SET_TIMEOUT); @@ -984,51 +1047,9 @@ static const struct phy_ops rockchip_dp_phy_ops = { .owner = THIS_MODULE, }; -static int tcphy_get_param(struct device *dev, - struct usb3phy_reg *reg, - const char *name) -{ - u32 buffer[3]; - int ret; - - ret = of_property_read_u32_array(dev->of_node, name, buffer, 3); - if (ret) { - dev_err(dev, "Can not parse %s\n", name); - return ret; - } - - reg->offset = buffer[0]; - reg->enable_bit = buffer[1]; - reg->write_enable = buffer[2]; - return 0; -} - static int tcphy_parse_dt(struct rockchip_typec_phy *tcphy, struct device *dev) { - struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs; - int ret; - - ret = tcphy_get_param(dev, &cfg->typec_conn_dir, - "rockchip,typec-conn-dir"); - if (ret) - return ret; - - ret = tcphy_get_param(dev, &cfg->usb3tousb2_en, - "rockchip,usb3tousb2-en"); - if (ret) - return ret; - - ret = tcphy_get_param(dev, &cfg->external_psm, - "rockchip,external-psm"); - if (ret) - return ret; - - ret = tcphy_get_param(dev, &cfg->pipe_status, - "rockchip,pipe-status"); - if (ret) - return ret; - tcphy->grf_regs = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,grf"); if (IS_ERR(tcphy->grf_regs)) { @@ -1071,7 +1092,7 @@ static int tcphy_parse_dt(struct rockchip_typec_phy *tcphy, static void typec_phy_pre_init(struct rockchip_typec_phy *tcphy) { - struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs; + const struct rockchip_usb3phy_port_cfg *cfg = tcphy->port_cfgs; reset_control_assert(tcphy->tcphy_rst); reset_control_assert(tcphy->uphy_rst); @@ -1092,17 +1113,43 @@ static int rockchip_typec_phy_probe(struct platform_device *pdev) struct rockchip_typec_phy *tcphy; struct phy_provider *phy_provider; struct resource *res; - int ret; + const struct rockchip_usb3phy_port_cfg *phy_cfgs; + const struct of_device_id *match; + int index, ret; tcphy = devm_kzalloc(dev, sizeof(*tcphy), GFP_KERNEL); if (!tcphy) return -ENOMEM; + match = of_match_device(dev->driver->of_match_table, dev); + if (!match || !match->data) { + dev_err(dev, "phy configs are not assigned!\n"); + return -EINVAL; + } + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); tcphy->base = devm_ioremap_resource(dev, res); if (IS_ERR(tcphy->base)) return PTR_ERR(tcphy->base); + phy_cfgs = match->data; + /* find out a proper config which can be matched with dt. */ + index = 0; + while (phy_cfgs[index].reg) { + if (phy_cfgs[index].reg == res->start) { + tcphy->port_cfgs = &phy_cfgs[index]; + break; + } + + ++index; + } + + if (!tcphy->port_cfgs) { + dev_err(dev, "no phy-config can be matched with %s node\n", + np->name); + return -EINVAL; + } + ret = tcphy_parse_dt(tcphy, dev); if (ret) return ret; @@ -1115,9 +1162,13 @@ static int rockchip_typec_phy_probe(struct platform_device *pdev) tcphy->extcon = extcon_get_edev_by_phandle(dev, 0); if (IS_ERR(tcphy->extcon)) { - if (PTR_ERR(tcphy->extcon) != -EPROBE_DEFER) - dev_err(dev, "Invalid or missing extcon\n"); - return PTR_ERR(tcphy->extcon); + if (PTR_ERR(tcphy->extcon) == -ENODEV) { + tcphy->extcon = NULL; + } else { + if (PTR_ERR(tcphy->extcon) != -EPROBE_DEFER) + dev_err(dev, "Invalid or missing extcon\n"); + return PTR_ERR(tcphy->extcon); + } } pm_runtime_enable(dev); @@ -1162,8 +1213,11 @@ static int rockchip_typec_phy_remove(struct platform_device *pdev) } static const struct of_device_id rockchip_typec_phy_dt_ids[] = { - { .compatible = "rockchip,rk3399-typec-phy" }, - {} + { + .compatible = "rockchip,rk3399-typec-phy", + .data = &rk3399_usb3phy_port_cfgs + }, + { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, rockchip_typec_phy_dt_ids); diff --git a/drivers/phy/samsung/Kconfig b/drivers/phy/samsung/Kconfig index b7e0645a7bd9..2a5d33cb0e7e 100644 --- a/drivers/phy/samsung/Kconfig +++ b/drivers/phy/samsung/Kconfig @@ -49,7 +49,7 @@ config PHY_EXYNOS4210_USB2 config PHY_EXYNOS4X12_USB2 bool depends on PHY_SAMSUNG_USB2 - default SOC_EXYNOS3250 || SOC_EXYNOS4212 || SOC_EXYNOS4412 + default SOC_EXYNOS3250 || SOC_EXYNOS4412 config PHY_EXYNOS5250_USB2 bool diff --git a/drivers/phy/st/Kconfig b/drivers/phy/st/Kconfig index 0814d3f87ec6..609719bdfa50 100644 --- a/drivers/phy/st/Kconfig +++ b/drivers/phy/st/Kconfig @@ -31,3 +31,17 @@ config PHY_STIH407_USB help Enable this support to enable the picoPHY device used by USB2 and USB3 controllers on STMicroelectronics STiH407 SoC families. + +config PHY_STM32_USBPHYC + tristate "STMicroelectronics STM32 USB HS PHY Controller driver" + depends on ARCH_STM32 || COMPILE_TEST + select GENERIC_PHY + help + Enable this to support the High-Speed USB transceivers that are part + of some STMicroelectronics STM32 SoCs. + + This driver controls the entire USB PHY block: the USB PHY controller + (USBPHYC) and the two 8-bit wide UTMI+ interfaces. First interface is + used by an HS USB Host controller, and the second one is shared + between an HS USB OTG controller and an HS USB Host controller, + selected by a USB switch. diff --git a/drivers/phy/st/Makefile b/drivers/phy/st/Makefile index e2adfe2166d2..c0091ad1fd48 100644 --- a/drivers/phy/st/Makefile +++ b/drivers/phy/st/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_PHY_MIPHY28LP) += phy-miphy28lp.o obj-$(CONFIG_PHY_ST_SPEAR1310_MIPHY) += phy-spear1310-miphy.o obj-$(CONFIG_PHY_ST_SPEAR1340_MIPHY) += phy-spear1340-miphy.o obj-$(CONFIG_PHY_STIH407_USB) += phy-stih407-usb.o +obj-$(CONFIG_PHY_STM32_USBPHYC) += phy-stm32-usbphyc.o diff --git a/drivers/phy/st/phy-stm32-usbphyc.c b/drivers/phy/st/phy-stm32-usbphyc.c new file mode 100644 index 000000000000..bc4e78a19c91 --- /dev/null +++ b/drivers/phy/st/phy-stm32-usbphyc.c @@ -0,0 +1,461 @@ +// SPDX-Licence-Identifier: GPL-2.0 +/* + * STMicroelectronics STM32 USB PHY Controller driver + * + * Copyright (C) 2018 STMicroelectronics + * Author(s): Amelie Delaunay <amelie.delaunay@st.com>. + */ +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/phy/phy.h> +#include <linux/reset.h> + +#define STM32_USBPHYC_PLL 0x0 +#define STM32_USBPHYC_MISC 0x8 +#define STM32_USBPHYC_VERSION 0x3F4 + +/* STM32_USBPHYC_PLL bit fields */ +#define PLLNDIV GENMASK(6, 0) +#define PLLFRACIN GENMASK(25, 10) +#define PLLEN BIT(26) +#define PLLSTRB BIT(27) +#define PLLSTRBYP BIT(28) +#define PLLFRACCTL BIT(29) +#define PLLDITHEN0 BIT(30) +#define PLLDITHEN1 BIT(31) + +/* STM32_USBPHYC_MISC bit fields */ +#define SWITHOST BIT(0) + +/* STM32_USBPHYC_VERSION bit fields */ +#define MINREV GENMASK(3, 0) +#define MAJREV GENMASK(7, 4) + +static const char * const supplies_names[] = { + "vdda1v1", /* 1V1 */ + "vdda1v8", /* 1V8 */ +}; + +#define NUM_SUPPLIES ARRAY_SIZE(supplies_names) + +#define PLL_LOCK_TIME_US 100 +#define PLL_PWR_DOWN_TIME_US 5 +#define PLL_FVCO_MHZ 2880 +#define PLL_INFF_MIN_RATE_HZ 19200000 +#define PLL_INFF_MAX_RATE_HZ 38400000 +#define HZ_PER_MHZ 1000000L + +struct pll_params { + u8 ndiv; + u16 frac; +}; + +struct stm32_usbphyc_phy { + struct phy *phy; + struct stm32_usbphyc *usbphyc; + struct regulator_bulk_data supplies[NUM_SUPPLIES]; + u32 index; + bool active; +}; + +struct stm32_usbphyc { + struct device *dev; + void __iomem *base; + struct clk *clk; + struct reset_control *rst; + struct stm32_usbphyc_phy **phys; + int nphys; + int switch_setup; + bool pll_enabled; +}; + +static inline void stm32_usbphyc_set_bits(void __iomem *reg, u32 bits) +{ + writel_relaxed(readl_relaxed(reg) | bits, reg); +} + +static inline void stm32_usbphyc_clr_bits(void __iomem *reg, u32 bits) +{ + writel_relaxed(readl_relaxed(reg) & ~bits, reg); +} + +static void stm32_usbphyc_get_pll_params(u32 clk_rate, struct pll_params *pll_params) +{ + unsigned long long fvco, ndiv, frac; + + /* _ + * | FVCO = INFF*2*(NDIV + FRACT/2^16) when DITHER_DISABLE[1] = 1 + * | FVCO = 2880MHz + * < + * | NDIV = integer part of input bits to set the LDF + * |_FRACT = fractional part of input bits to set the LDF + * => PLLNDIV = integer part of (FVCO / (INFF*2)) + * => PLLFRACIN = fractional part of(FVCO / INFF*2) * 2^16 + * <=> PLLFRACIN = ((FVCO / (INFF*2)) - PLLNDIV) * 2^16 + */ + fvco = (unsigned long long)PLL_FVCO_MHZ * HZ_PER_MHZ; + + ndiv = fvco; + do_div(ndiv, (clk_rate * 2)); + pll_params->ndiv = (u8)ndiv; + + frac = fvco * (1 << 16); + do_div(frac, (clk_rate * 2)); + frac = frac - (ndiv * (1 << 16)); + pll_params->frac = (u16)frac; +} + +static int stm32_usbphyc_pll_init(struct stm32_usbphyc *usbphyc) +{ + struct pll_params pll_params; + u32 clk_rate = clk_get_rate(usbphyc->clk); + u32 ndiv, frac; + u32 usbphyc_pll; + + if ((clk_rate < PLL_INFF_MIN_RATE_HZ) || + (clk_rate > PLL_INFF_MAX_RATE_HZ)) { + dev_err(usbphyc->dev, "input clk freq (%dHz) out of range\n", + clk_rate); + return -EINVAL; + } + + stm32_usbphyc_get_pll_params(clk_rate, &pll_params); + ndiv = FIELD_PREP(PLLNDIV, pll_params.ndiv); + frac = FIELD_PREP(PLLFRACIN, pll_params.frac); + + usbphyc_pll = PLLDITHEN1 | PLLDITHEN0 | PLLSTRBYP | ndiv; + + if (pll_params.frac) + usbphyc_pll |= PLLFRACCTL | frac; + + writel_relaxed(usbphyc_pll, usbphyc->base + STM32_USBPHYC_PLL); + + dev_dbg(usbphyc->dev, "input clk freq=%dHz, ndiv=%lu, frac=%lu\n", + clk_rate, FIELD_GET(PLLNDIV, usbphyc_pll), + FIELD_GET(PLLFRACIN, usbphyc_pll)); + + return 0; +} + +static bool stm32_usbphyc_has_one_phy_active(struct stm32_usbphyc *usbphyc) +{ + int i; + + for (i = 0; i < usbphyc->nphys; i++) + if (usbphyc->phys[i]->active) + return true; + + return false; +} + +static int stm32_usbphyc_pll_enable(struct stm32_usbphyc *usbphyc) +{ + void __iomem *pll_reg = usbphyc->base + STM32_USBPHYC_PLL; + bool pllen = (readl_relaxed(pll_reg) & PLLEN); + int ret; + + /* Check if one phy port has already configured the pll */ + if (pllen && stm32_usbphyc_has_one_phy_active(usbphyc)) + return 0; + + if (pllen) { + stm32_usbphyc_clr_bits(pll_reg, PLLEN); + /* Wait for minimum width of powerdown pulse (ENABLE = Low) */ + udelay(PLL_PWR_DOWN_TIME_US); + } + + ret = stm32_usbphyc_pll_init(usbphyc); + if (ret) + return ret; + + stm32_usbphyc_set_bits(pll_reg, PLLEN); + + /* Wait for maximum lock time */ + udelay(PLL_LOCK_TIME_US); + + if (!(readl_relaxed(pll_reg) & PLLEN)) { + dev_err(usbphyc->dev, "PLLEN not set\n"); + return -EIO; + } + + return 0; +} + +static int stm32_usbphyc_pll_disable(struct stm32_usbphyc *usbphyc) +{ + void __iomem *pll_reg = usbphyc->base + STM32_USBPHYC_PLL; + + /* Check if other phy port active */ + if (stm32_usbphyc_has_one_phy_active(usbphyc)) + return 0; + + stm32_usbphyc_clr_bits(pll_reg, PLLEN); + /* Wait for minimum width of powerdown pulse (ENABLE = Low) */ + udelay(PLL_PWR_DOWN_TIME_US); + + if (readl_relaxed(pll_reg) & PLLEN) { + dev_err(usbphyc->dev, "PLL not reset\n"); + return -EIO; + } + + return 0; +} + +static int stm32_usbphyc_phy_init(struct phy *phy) +{ + struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy); + struct stm32_usbphyc *usbphyc = usbphyc_phy->usbphyc; + int ret; + + ret = stm32_usbphyc_pll_enable(usbphyc); + if (ret) + return ret; + + usbphyc_phy->active = true; + + return 0; +} + +static int stm32_usbphyc_phy_exit(struct phy *phy) +{ + struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy); + struct stm32_usbphyc *usbphyc = usbphyc_phy->usbphyc; + + usbphyc_phy->active = false; + + return stm32_usbphyc_pll_disable(usbphyc); +} + +static int stm32_usbphyc_phy_power_on(struct phy *phy) +{ + struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy); + + return regulator_bulk_enable(NUM_SUPPLIES, usbphyc_phy->supplies); +} + +static int stm32_usbphyc_phy_power_off(struct phy *phy) +{ + struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy); + + return regulator_bulk_disable(NUM_SUPPLIES, usbphyc_phy->supplies); +} + +static const struct phy_ops stm32_usbphyc_phy_ops = { + .init = stm32_usbphyc_phy_init, + .exit = stm32_usbphyc_phy_exit, + .power_on = stm32_usbphyc_phy_power_on, + .power_off = stm32_usbphyc_phy_power_off, + .owner = THIS_MODULE, +}; + +static void stm32_usbphyc_switch_setup(struct stm32_usbphyc *usbphyc, + u32 utmi_switch) +{ + if (!utmi_switch) + stm32_usbphyc_clr_bits(usbphyc->base + STM32_USBPHYC_MISC, + SWITHOST); + else + stm32_usbphyc_set_bits(usbphyc->base + STM32_USBPHYC_MISC, + SWITHOST); + usbphyc->switch_setup = utmi_switch; +} + +static struct phy *stm32_usbphyc_of_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct stm32_usbphyc *usbphyc = dev_get_drvdata(dev); + struct stm32_usbphyc_phy *usbphyc_phy = NULL; + struct device_node *phynode = args->np; + + int port = 0; + + for (port = 0; port < usbphyc->nphys; port++) { + if (phynode == usbphyc->phys[port]->phy->dev.of_node) { + usbphyc_phy = usbphyc->phys[port]; + break; + } + } + if (!usbphyc_phy) { + dev_err(dev, "failed to find phy\n"); + return ERR_PTR(-EINVAL); + } + + if (((usbphyc_phy->index == 0) && (args->args_count != 0)) || + ((usbphyc_phy->index == 1) && (args->args_count != 1))) { + dev_err(dev, "invalid number of cells for phy port%d\n", + usbphyc_phy->index); + return ERR_PTR(-EINVAL); + } + + /* Configure the UTMI switch for PHY port#2 */ + if (usbphyc_phy->index == 1) { + if (usbphyc->switch_setup < 0) { + stm32_usbphyc_switch_setup(usbphyc, args->args[0]); + } else { + if (args->args[0] != usbphyc->switch_setup) { + dev_err(dev, "phy port1 already used\n"); + return ERR_PTR(-EBUSY); + } + } + } + + return usbphyc_phy->phy; +} + +static int stm32_usbphyc_probe(struct platform_device *pdev) +{ + struct stm32_usbphyc *usbphyc; + struct device *dev = &pdev->dev; + struct device_node *child, *np = dev->of_node; + struct resource *res; + struct phy_provider *phy_provider; + u32 version; + int ret, port = 0; + + usbphyc = devm_kzalloc(dev, sizeof(*usbphyc), GFP_KERNEL); + if (!usbphyc) + return -ENOMEM; + usbphyc->dev = dev; + dev_set_drvdata(dev, usbphyc); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + usbphyc->base = devm_ioremap_resource(dev, res); + if (IS_ERR(usbphyc->base)) + return PTR_ERR(usbphyc->base); + + usbphyc->clk = devm_clk_get(dev, 0); + if (IS_ERR(usbphyc->clk)) { + ret = PTR_ERR(usbphyc->clk); + dev_err(dev, "clk get failed: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(usbphyc->clk); + if (ret) { + dev_err(dev, "clk enable failed: %d\n", ret); + return ret; + } + + usbphyc->rst = devm_reset_control_get(dev, 0); + if (!IS_ERR(usbphyc->rst)) { + reset_control_assert(usbphyc->rst); + udelay(2); + reset_control_deassert(usbphyc->rst); + } + + usbphyc->switch_setup = -EINVAL; + usbphyc->nphys = of_get_child_count(np); + usbphyc->phys = devm_kcalloc(dev, usbphyc->nphys, + sizeof(*usbphyc->phys), GFP_KERNEL); + if (!usbphyc->phys) { + ret = -ENOMEM; + goto clk_disable; + } + + for_each_child_of_node(np, child) { + struct stm32_usbphyc_phy *usbphyc_phy; + struct phy *phy; + u32 index; + int i; + + phy = devm_phy_create(dev, child, &stm32_usbphyc_phy_ops); + if (IS_ERR(phy)) { + ret = PTR_ERR(phy); + if (ret != -EPROBE_DEFER) + dev_err(dev, + "failed to create phy%d: %d\n", i, ret); + goto put_child; + } + + usbphyc_phy = devm_kzalloc(dev, sizeof(*usbphyc_phy), + GFP_KERNEL); + if (!usbphyc_phy) { + ret = -ENOMEM; + goto put_child; + } + + for (i = 0; i < NUM_SUPPLIES; i++) + usbphyc_phy->supplies[i].supply = supplies_names[i]; + + ret = devm_regulator_bulk_get(&phy->dev, NUM_SUPPLIES, + usbphyc_phy->supplies); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(&phy->dev, + "failed to get regulators: %d\n", ret); + goto put_child; + } + + ret = of_property_read_u32(child, "reg", &index); + if (ret || index > usbphyc->nphys) { + dev_err(&phy->dev, "invalid reg property: %d\n", ret); + goto put_child; + } + + usbphyc->phys[port] = usbphyc_phy; + phy_set_bus_width(phy, 8); + phy_set_drvdata(phy, usbphyc_phy); + + usbphyc->phys[port]->phy = phy; + usbphyc->phys[port]->usbphyc = usbphyc; + usbphyc->phys[port]->index = index; + usbphyc->phys[port]->active = false; + + port++; + } + + phy_provider = devm_of_phy_provider_register(dev, + stm32_usbphyc_of_xlate); + if (IS_ERR(phy_provider)) { + ret = PTR_ERR(phy_provider); + dev_err(dev, "failed to register phy provider: %d\n", ret); + goto clk_disable; + } + + version = readl_relaxed(usbphyc->base + STM32_USBPHYC_VERSION); + dev_info(dev, "registered rev:%lu.%lu\n", + FIELD_GET(MAJREV, version), FIELD_GET(MINREV, version)); + + return 0; + +put_child: + of_node_put(child); +clk_disable: + clk_disable_unprepare(usbphyc->clk); + + return ret; +} + +static int stm32_usbphyc_remove(struct platform_device *pdev) +{ + struct stm32_usbphyc *usbphyc = dev_get_drvdata(&pdev->dev); + + clk_disable_unprepare(usbphyc->clk); + + return 0; +} + +static const struct of_device_id stm32_usbphyc_of_match[] = { + { .compatible = "st,stm32mp1-usbphyc", }, + { }, +}; +MODULE_DEVICE_TABLE(of, stm32_usbphyc_of_match); + +static struct platform_driver stm32_usbphyc_driver = { + .probe = stm32_usbphyc_probe, + .remove = stm32_usbphyc_remove, + .driver = { + .of_match_table = stm32_usbphyc_of_match, + .name = "stm32-usbphyc", + } +}; +module_platform_driver(stm32_usbphyc_driver); + +MODULE_DESCRIPTION("STMicroelectronics STM32 USBPHYC driver"); +MODULE_AUTHOR("Amelie Delaunay <amelie.delaunay@st.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/tegra/xusb-tegra210.c b/drivers/phy/tegra/xusb-tegra210.c index 9d0689ebd28c..05bee32a3a4d 100644 --- a/drivers/phy/tegra/xusb-tegra210.c +++ b/drivers/phy/tegra/xusb-tegra210.c @@ -169,6 +169,7 @@ #define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_EN (1 << 0) #define XUSB_PADCTL_UPHY_PLL_P0_CTL4 0x36c +#define XUSB_PADCTL_UPHY_PLL_CTL4_XDIGCLK_EN (1 << 19) #define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_EN (1 << 15) #define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT 12 #define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_MASK 0x3 @@ -537,11 +538,8 @@ static int tegra210_sata_uphy_enable(struct tegra_xusb_padctl *padctl, bool usb) value |= (XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SATA_VAL << XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT); - /* XXX PLL0_XDIGCLK_EN */ - /* - value &= ~(1 << 19); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL4_XDIGCLK_EN; padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL4); - */ value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1); value &= ~((XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_MASK << diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c index 63e916d4d069..11aa5902a9ac 100644 --- a/drivers/phy/tegra/xusb.c +++ b/drivers/phy/tegra/xusb.c @@ -418,7 +418,7 @@ tegra_xusb_port_find_lane(struct tegra_xusb_port *port, { struct tegra_xusb_lane *lane, *match = ERR_PTR(-ENODEV); - for (map = map; map->type; map++) { + for (; map->type; map++) { if (port->index != map->port) continue; diff --git a/drivers/platform/x86/intel_cht_int33fe.c b/drivers/platform/x86/intel_cht_int33fe.c index 380ef7ec094f..39d4100c60a2 100644 --- a/drivers/platform/x86/intel_cht_int33fe.c +++ b/drivers/platform/x86/intel_cht_int33fe.c @@ -33,6 +33,8 @@ struct cht_int33fe_data { struct i2c_client *max17047; struct i2c_client *fusb302; struct i2c_client *pi3usb30532; + /* Contain a list-head must be per device */ + struct device_connection connections[3]; }; /* @@ -172,6 +174,20 @@ static int cht_int33fe_probe(struct i2c_client *client) return -EPROBE_DEFER; /* Wait for i2c-adapter to load */ } + data->connections[0].endpoint[0] = "i2c-fusb302"; + data->connections[0].endpoint[1] = "i2c-pi3usb30532"; + data->connections[0].id = "typec-switch"; + data->connections[1].endpoint[0] = "i2c-fusb302"; + data->connections[1].endpoint[1] = "i2c-pi3usb30532"; + data->connections[1].id = "typec-mux"; + data->connections[2].endpoint[0] = "i2c-fusb302"; + data->connections[2].endpoint[1] = "intel_xhci_usb_sw-role-switch"; + data->connections[2].id = "usb-role-switch"; + + device_connection_add(&data->connections[0]); + device_connection_add(&data->connections[1]); + device_connection_add(&data->connections[2]); + memset(&board_info, 0, sizeof(board_info)); strlcpy(board_info.type, "typec_fusb302", I2C_NAME_SIZE); board_info.dev_name = "fusb302"; @@ -201,6 +217,10 @@ out_unregister_max17047: if (data->max17047) i2c_unregister_device(data->max17047); + device_connection_remove(&data->connections[2]); + device_connection_remove(&data->connections[1]); + device_connection_remove(&data->connections[0]); + return -EPROBE_DEFER; /* Wait for the i2c-adapter to load */ } @@ -213,6 +233,10 @@ static int cht_int33fe_remove(struct i2c_client *i2c) if (data->max17047) i2c_unregister_device(data->max17047); + device_connection_remove(&data->connections[2]); + device_connection_remove(&data->connections[1]); + device_connection_remove(&data->connections[0]); + return 0; } diff --git a/drivers/staging/typec/tcpci.c b/drivers/staging/typec/tcpci.c index 9bd4412356c9..076d97eaff6f 100644 --- a/drivers/staging/typec/tcpci.c +++ b/drivers/staging/typec/tcpci.c @@ -21,7 +21,6 @@ struct tcpci { struct device *dev; - struct i2c_client *client; struct tcpm_port *port; @@ -30,6 +29,12 @@ struct tcpci { bool controls_vbus; struct tcpc_dev tcpc; + struct tcpci_data *data; +}; + +struct tcpci_chip { + struct tcpci *tcpci; + struct tcpci_data data; }; static inline struct tcpci *tcpc_to_tcpci(struct tcpc_dev *tcpc) @@ -37,8 +42,7 @@ static inline struct tcpci *tcpc_to_tcpci(struct tcpc_dev *tcpc) return container_of(tcpc, struct tcpci, tcpc); } -static int tcpci_read16(struct tcpci *tcpci, unsigned int reg, - u16 *val) +static int tcpci_read16(struct tcpci *tcpci, unsigned int reg, u16 *val) { return regmap_raw_read(tcpci->regmap, reg, val, sizeof(u16)); } @@ -98,9 +102,17 @@ static int tcpci_set_cc(struct tcpc_dev *tcpc, enum typec_cc_status cc) static int tcpci_start_drp_toggling(struct tcpc_dev *tcpc, enum typec_cc_status cc) { + int ret; struct tcpci *tcpci = tcpc_to_tcpci(tcpc); unsigned int reg = TCPC_ROLE_CTRL_DRP; + /* Handle vendor drp toggling */ + if (tcpci->data->start_drp_toggling) { + ret = tcpci->data->start_drp_toggling(tcpci, tcpci->data, cc); + if (ret < 0) + return ret; + } + switch (cc) { default: case TYPEC_CC_RP_DEF: @@ -117,7 +129,17 @@ static int tcpci_start_drp_toggling(struct tcpc_dev *tcpc, break; } - return regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg); + if (cc == TYPEC_CC_RD) + reg |= (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT) | + (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT); + else + reg |= (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) | + (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT); + ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg); + if (ret < 0) + return ret; + return regmap_write(tcpci->regmap, TCPC_COMMAND, + TCPC_CMD_LOOK4CONNECTION); } static enum typec_cc_status tcpci_to_typec_cc(unsigned int cc, bool sink) @@ -178,6 +200,13 @@ static int tcpci_set_vconn(struct tcpc_dev *tcpc, bool enable) struct tcpci *tcpci = tcpc_to_tcpci(tcpc); int ret; + /* Handle vendor set vconn */ + if (tcpci->data->set_vconn) { + ret = tcpci->data->set_vconn(tcpci, tcpci->data, enable); + if (ret < 0) + return ret; + } + ret = regmap_write(tcpci->regmap, TCPC_POWER_CTRL, enable ? TCPC_POWER_CTRL_VCONN_ENABLE : 0); if (ret < 0) @@ -323,6 +352,13 @@ static int tcpci_init(struct tcpc_dev *tcpc) if (time_after(jiffies, timeout)) return -ETIMEDOUT; + /* Handle vendor init */ + if (tcpci->data->init) { + ret = tcpci->data->init(tcpci, tcpci->data); + if (ret < 0) + return ret; + } + /* Clear all events */ ret = tcpci_write16(tcpci, TCPC_ALERT, 0xffff); if (ret < 0) @@ -344,9 +380,8 @@ static int tcpci_init(struct tcpc_dev *tcpc) return tcpci_write16(tcpci, TCPC_ALERT_MASK, reg); } -static irqreturn_t tcpci_irq(int irq, void *dev_id) +irqreturn_t tcpci_irq(struct tcpci *tcpci) { - struct tcpci *tcpci = dev_id; u16 status; tcpci_read16(tcpci, TCPC_ALERT, &status); @@ -412,6 +447,14 @@ static irqreturn_t tcpci_irq(int irq, void *dev_id) return IRQ_HANDLED; } +EXPORT_SYMBOL_GPL(tcpci_irq); + +static irqreturn_t _tcpci_irq(int irq, void *dev_id) +{ + struct tcpci_chip *chip = dev_id; + + return tcpci_irq(chip->tcpci); +} static const struct regmap_config tcpci_regmap_config = { .reg_bits = 8, @@ -435,22 +478,18 @@ static int tcpci_parse_config(struct tcpci *tcpci) return 0; } -static int tcpci_probe(struct i2c_client *client, - const struct i2c_device_id *i2c_id) +struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data) { struct tcpci *tcpci; int err; - tcpci = devm_kzalloc(&client->dev, sizeof(*tcpci), GFP_KERNEL); + tcpci = devm_kzalloc(dev, sizeof(*tcpci), GFP_KERNEL); if (!tcpci) - return -ENOMEM; + return ERR_PTR(-ENOMEM); - tcpci->client = client; - tcpci->dev = &client->dev; - i2c_set_clientdata(client, tcpci); - tcpci->regmap = devm_regmap_init_i2c(client, &tcpci_regmap_config); - if (IS_ERR(tcpci->regmap)) - return PTR_ERR(tcpci->regmap); + tcpci->dev = dev; + tcpci->data = data; + tcpci->regmap = data->regmap; tcpci->tcpc.init = tcpci_init; tcpci->tcpc.get_vbus = tcpci_get_vbus; @@ -467,27 +506,63 @@ static int tcpci_probe(struct i2c_client *client, err = tcpci_parse_config(tcpci); if (err < 0) - return err; + return ERR_PTR(err); - /* Disable chip interrupts */ - tcpci_write16(tcpci, TCPC_ALERT_MASK, 0); + tcpci->port = tcpm_register_port(tcpci->dev, &tcpci->tcpc); + if (PTR_ERR_OR_ZERO(tcpci->port)) + return ERR_CAST(tcpci->port); - err = devm_request_threaded_irq(tcpci->dev, client->irq, NULL, - tcpci_irq, + return tcpci; +} +EXPORT_SYMBOL_GPL(tcpci_register_port); + +void tcpci_unregister_port(struct tcpci *tcpci) +{ + tcpm_unregister_port(tcpci->port); +} +EXPORT_SYMBOL_GPL(tcpci_unregister_port); + +static int tcpci_probe(struct i2c_client *client, + const struct i2c_device_id *i2c_id) +{ + struct tcpci_chip *chip; + int err; + u16 val = 0; + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->data.regmap = devm_regmap_init_i2c(client, &tcpci_regmap_config); + if (IS_ERR(chip->data.regmap)) + return PTR_ERR(chip->data.regmap); + + /* Disable chip interrupts before requesting irq */ + err = regmap_raw_write(chip->data.regmap, TCPC_ALERT_MASK, &val, + sizeof(u16)); + if (err < 0) + return err; + + err = devm_request_threaded_irq(&client->dev, client->irq, NULL, + _tcpci_irq, IRQF_ONESHOT | IRQF_TRIGGER_LOW, - dev_name(tcpci->dev), tcpci); + dev_name(&client->dev), chip); if (err < 0) return err; - tcpci->port = tcpm_register_port(tcpci->dev, &tcpci->tcpc); - return PTR_ERR_OR_ZERO(tcpci->port); + chip->tcpci = tcpci_register_port(&client->dev, &chip->data); + if (PTR_ERR_OR_ZERO(chip->tcpci)) + return PTR_ERR(chip->tcpci); + + i2c_set_clientdata(client, chip); + return 0; } static int tcpci_remove(struct i2c_client *client) { - struct tcpci *tcpci = i2c_get_clientdata(client); + struct tcpci_chip *chip = i2c_get_clientdata(client); - tcpm_unregister_port(tcpci->port); + tcpci_unregister_port(chip->tcpci); return 0; } diff --git a/drivers/staging/typec/tcpci.h b/drivers/staging/typec/tcpci.h index fdfb06cc3b86..34c865f0dcf6 100644 --- a/drivers/staging/typec/tcpci.h +++ b/drivers/staging/typec/tcpci.h @@ -121,4 +121,18 @@ #define TCPC_VBUS_VOLTAGE_ALARM_HI_CFG 0x76 #define TCPC_VBUS_VOLTAGE_ALARM_LO_CFG 0x78 +struct tcpci; +struct tcpci_data { + struct regmap *regmap; + int (*init)(struct tcpci *tcpci, struct tcpci_data *data); + int (*set_vconn)(struct tcpci *tcpci, struct tcpci_data *data, + bool enable); + int (*start_drp_toggling)(struct tcpci *tcpci, struct tcpci_data *data, + enum typec_cc_status cc); +}; + +struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data); +void tcpci_unregister_port(struct tcpci *tcpci); +irqreturn_t tcpci_irq(struct tcpci *tcpci); + #endif /* __LINUX_USB_TCPCI_H */ diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 148f3ee70286..75f7fb151f71 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -171,6 +171,8 @@ source "drivers/usb/gadget/Kconfig" source "drivers/usb/typec/Kconfig" +source "drivers/usb/roles/Kconfig" + config USB_LED_TRIG bool "USB LED Triggers" depends on LEDS_CLASS && LEDS_TRIGGERS @@ -203,4 +205,7 @@ config USB_ULPI_BUS To compile this driver as a module, choose M here: the module will be called ulpi. +config USB_ROLE_SWITCH + tristate + endif # USB_SUPPORT diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index 060643a1b5c8..7d1b8c82b208 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -65,3 +65,5 @@ obj-$(CONFIG_USB_COMMON) += common/ obj-$(CONFIG_USBIP_CORE) += usbip/ obj-$(CONFIG_TYPEC) += typec/ + +obj-$(CONFIG_USB_ROLE_SWITCH) += roles/ diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 3b45c25f296e..e431c5aafe35 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -20,7 +20,6 @@ struct ci_hdrc_imx_platform_flag { unsigned int flags; - bool runtime_pm; }; static const struct ci_hdrc_imx_platform_flag imx23_usb_data = { @@ -29,7 +28,7 @@ static const struct ci_hdrc_imx_platform_flag imx23_usb_data = { }; static const struct ci_hdrc_imx_platform_flag imx27_usb_data = { - CI_HDRC_DISABLE_STREAMING, + .flags = CI_HDRC_DISABLE_STREAMING, }; static const struct ci_hdrc_imx_platform_flag imx28_usb_data = { @@ -84,6 +83,7 @@ struct ci_hdrc_imx_data { struct clk *clk; struct imx_usbmisc_data *usbmisc_data; bool supports_runtime_pm; + bool override_phy_control; bool in_lpm; /* SoC before i.mx6 (except imx23/imx28) needs three clks */ bool need_three_clks; @@ -255,6 +255,7 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) int ret; const struct of_device_id *of_id; const struct ci_hdrc_imx_platform_flag *imx_platform_flag; + struct device_node *np = pdev->dev.of_node; of_id = of_match_device(ci_hdrc_imx_dt_ids, &pdev->dev); if (!of_id) @@ -289,6 +290,14 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) } pdata.usb_phy = data->phy; + + if (of_device_is_compatible(np, "fsl,imx53-usb") && pdata.usb_phy && + of_usb_get_phy_mode(np) == USBPHY_INTERFACE_MODE_ULPI) { + pdata.flags |= CI_HDRC_OVERRIDE_PHY_CONTROL; + data->override_phy_control = true; + usb_phy_init(pdata.usb_phy); + } + pdata.flags |= imx_platform_flag->flags; if (pdata.flags & CI_HDRC_SUPPORTS_RUNTIME_PM) data->supports_runtime_pm = true; @@ -342,6 +351,8 @@ static int ci_hdrc_imx_remove(struct platform_device *pdev) pm_runtime_put_noidle(&pdev->dev); } ci_hdrc_remove_device(data->ci_pdev); + if (data->override_phy_control) + usb_phy_shutdown(data->phy); imx_disable_unprepare_clks(&pdev->dev); return 0; diff --git a/drivers/usb/chipidea/debug.c b/drivers/usb/chipidea/debug.c index c9e1a165ed82..ce648cb3ed94 100644 --- a/drivers/usb/chipidea/debug.c +++ b/drivers/usb/chipidea/debug.c @@ -45,18 +45,7 @@ static int ci_device_show(struct seq_file *s, void *data) return 0; } - -static int ci_device_open(struct inode *inode, struct file *file) -{ - return single_open(file, ci_device_show, inode->i_private); -} - -static const struct file_operations ci_device_fops = { - .open = ci_device_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(ci_device); /** * ci_port_test_show: reads port test mode @@ -156,18 +145,7 @@ static int ci_qheads_show(struct seq_file *s, void *data) return 0; } - -static int ci_qheads_open(struct inode *inode, struct file *file) -{ - return single_open(file, ci_qheads_show, inode->i_private); -} - -static const struct file_operations ci_qheads_fops = { - .open = ci_qheads_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(ci_qheads); /** * ci_requests_show: DMA contents of all requests currently queued (all endpts) @@ -204,18 +182,7 @@ static int ci_requests_show(struct seq_file *s, void *data) return 0; } - -static int ci_requests_open(struct inode *inode, struct file *file) -{ - return single_open(file, ci_requests_show, inode->i_private); -} - -static const struct file_operations ci_requests_fops = { - .open = ci_requests_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(ci_requests); static int ci_otg_show(struct seq_file *s, void *unused) { @@ -278,18 +245,7 @@ static int ci_otg_show(struct seq_file *s, void *unused) return 0; } - -static int ci_otg_open(struct inode *inode, struct file *file) -{ - return single_open(file, ci_otg_show, inode->i_private); -} - -static const struct file_operations ci_otg_fops = { - .open = ci_otg_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(ci_otg); static int ci_role_show(struct seq_file *s, void *data) { @@ -376,18 +332,7 @@ static int ci_registers_show(struct seq_file *s, void *unused) return 0; } - -static int ci_registers_open(struct inode *inode, struct file *file) -{ - return single_open(file, ci_registers_show, inode->i_private); -} - -static const struct file_operations ci_registers_fops = { - .open = ci_registers_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(ci_registers); /** * dbg_create_files: initializes the attribute interface diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c index 19d60ed7e41f..af45aa3222b5 100644 --- a/drivers/usb/chipidea/host.c +++ b/drivers/usb/chipidea/host.c @@ -124,10 +124,8 @@ static int host_start(struct ci_hdrc *ci) hcd->power_budget = ci->platdata->power_budget; hcd->tpl_support = ci->platdata->tpl_support; - if (ci->phy) - hcd->phy = ci->phy; - else - hcd->usb_phy = ci->usb_phy; + if (ci->phy || ci->usb_phy) + hcd->skip_phy_initialization = 1; ehci = hcd_to_ehci(hcd); ehci->caps = ci->hw_bank.cap; diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c index 8cdf0af156c6..34ad5bf8acd8 100644 --- a/drivers/usb/chipidea/usbmisc_imx.c +++ b/drivers/usb/chipidea/usbmisc_imx.c @@ -148,14 +148,21 @@ static int usbmisc_imx25_post(struct imx_usbmisc_data *data) if (data->index > 2) return -EINVAL; - if (data->evdo) { - spin_lock_irqsave(&usbmisc->lock, flags); - reg = usbmisc->base + MX25_USB_PHY_CTRL_OFFSET; - val = readl(reg); - writel(val | MX25_BM_EXTERNAL_VBUS_DIVIDER, reg); - spin_unlock_irqrestore(&usbmisc->lock, flags); - usleep_range(5000, 10000); /* needed to stabilize voltage */ - } + if (data->index) + return 0; + + spin_lock_irqsave(&usbmisc->lock, flags); + reg = usbmisc->base + MX25_USB_PHY_CTRL_OFFSET; + val = readl(reg); + + if (data->evdo) + val |= MX25_BM_EXTERNAL_VBUS_DIVIDER; + else + val &= ~MX25_BM_EXTERNAL_VBUS_DIVIDER; + + writel(val, reg); + spin_unlock_irqrestore(&usbmisc->lock, flags); + usleep_range(5000, 10000); /* needed to stabilize voltage */ return 0; } @@ -308,13 +315,12 @@ static int usbmisc_imx6q_set_wakeup val = readl(usbmisc->base + data->index * 4); if (enabled) { val |= wakeup_setting; - writel(val, usbmisc->base + data->index * 4); } else { if (val & MX6_BM_WAKEUP_INTR) pr_debug("wakeup int at ci_hdrc.%d\n", data->index); val &= ~wakeup_setting; - writel(val, usbmisc->base + data->index * 4); } + writel(val, usbmisc->base + data->index * 4); spin_unlock_irqrestore(&usbmisc->lock, flags); return ret; diff --git a/drivers/usb/common/Makefile b/drivers/usb/common/Makefile index 0a7c45e85481..fb4d5ef4165c 100644 --- a/drivers/usb/common/Makefile +++ b/drivers/usb/common/Makefile @@ -9,3 +9,4 @@ usb-common-$(CONFIG_USB_LED_TRIG) += led.o obj-$(CONFIG_USB_OTG_FSM) += usb-otg-fsm.o obj-$(CONFIG_USB_ULPI_BUS) += ulpi.o +obj-$(CONFIG_USB_ROLE_SWITCH) += roles.o diff --git a/drivers/usb/common/roles.c b/drivers/usb/common/roles.c new file mode 100644 index 000000000000..15cc76e22123 --- /dev/null +++ b/drivers/usb/common/roles.c @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Role Switch Support + * + * Copyright (C) 2018 Intel Corporation + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> + * Hans de Goede <hdegoede@redhat.com> + */ + +#include <linux/usb/role.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> + +static struct class *role_class; + +struct usb_role_switch { + struct device dev; + struct mutex lock; /* device lock*/ + enum usb_role role; + + /* From descriptor */ + struct device *usb2_port; + struct device *usb3_port; + struct device *udc; + usb_role_switch_set_t set; + usb_role_switch_get_t get; + bool allow_userspace_control; +}; + +#define to_role_switch(d) container_of(d, struct usb_role_switch, dev) + +/** + * usb_role_switch_set_role - Set USB role for a switch + * @sw: USB role switch + * @role: USB role to be switched to + * + * Set USB role @role for @sw. + */ +int usb_role_switch_set_role(struct usb_role_switch *sw, enum usb_role role) +{ + int ret; + + if (IS_ERR_OR_NULL(sw)) + return 0; + + mutex_lock(&sw->lock); + + ret = sw->set(sw->dev.parent, role); + if (!ret) + sw->role = role; + + mutex_unlock(&sw->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_role_switch_set_role); + +/** + * usb_role_switch_get_role - Get the USB role for a switch + * @sw: USB role switch + * + * Depending on the role-switch-driver this function returns either a cached + * value of the last set role, or reads back the actual value from the hardware. + */ +enum usb_role usb_role_switch_get_role(struct usb_role_switch *sw) +{ + enum usb_role role; + + if (IS_ERR_OR_NULL(sw)) + return USB_ROLE_NONE; + + mutex_lock(&sw->lock); + + if (sw->get) + role = sw->get(sw->dev.parent); + else + role = sw->role; + + mutex_unlock(&sw->lock); + + return role; +} +EXPORT_SYMBOL_GPL(usb_role_switch_get_role); + +static int __switch_match(struct device *dev, const void *name) +{ + return !strcmp((const char *)name, dev_name(dev)); +} + +static void *usb_role_switch_match(struct device_connection *con, int ep, + void *data) +{ + struct device *dev; + + dev = class_find_device(role_class, NULL, con->endpoint[ep], + __switch_match); + + return dev ? to_role_switch(dev) : ERR_PTR(-EPROBE_DEFER); +} + +/** + * usb_role_switch_get - Find USB role switch linked with the caller + * @dev: The caller device + * + * Finds and returns role switch linked with @dev. The reference count for the + * found switch is incremented. + */ +struct usb_role_switch *usb_role_switch_get(struct device *dev) +{ + return device_connection_find_match(dev, "usb-role-switch", NULL, + usb_role_switch_match); +} +EXPORT_SYMBOL_GPL(usb_role_switch_get); + +/** + * usb_role_switch_put - Release handle to a switch + * @sw: USB Role Switch + * + * Decrement reference count for @sw. + */ +void usb_role_switch_put(struct usb_role_switch *sw) +{ + if (!IS_ERR_OR_NULL(sw)) + put_device(&sw->dev); +} +EXPORT_SYMBOL_GPL(usb_role_switch_put); + +static umode_t +usb_role_switch_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + struct device *dev = container_of(kobj, typeof(*dev), kobj); + struct usb_role_switch *sw = to_role_switch(dev); + + if (sw->allow_userspace_control) + return attr->mode; + + return 0; +} + +static const char * const usb_roles[] = { + [USB_ROLE_NONE] = "none", + [USB_ROLE_HOST] = "host", + [USB_ROLE_DEVICE] = "device", +}; + +static ssize_t +role_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_role_switch *sw = to_role_switch(dev); + enum usb_role role = usb_role_switch_get_role(sw); + + return sprintf(buf, "%s\n", usb_roles[role]); +} + +static ssize_t role_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct usb_role_switch *sw = to_role_switch(dev); + int ret; + + ret = sysfs_match_string(usb_roles, buf); + if (ret < 0) { + bool res; + + /* Extra check if the user wants to disable the switch */ + ret = kstrtobool(buf, &res); + if (ret || res) + return -EINVAL; + } + + ret = usb_role_switch_set_role(sw, ret); + if (ret) + return ret; + + return size; +} +static DEVICE_ATTR_RW(role); + +static struct attribute *usb_role_switch_attrs[] = { + &dev_attr_role.attr, + NULL, +}; + +static const struct attribute_group usb_role_switch_group = { + .is_visible = usb_role_switch_is_visible, + .attrs = usb_role_switch_attrs, +}; + +static const struct attribute_group *usb_role_switch_groups[] = { + &usb_role_switch_group, + NULL, +}; + +static int +usb_role_switch_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + int ret; + + ret = add_uevent_var(env, "USB_ROLE_SWITCH=%s", dev_name(dev)); + if (ret) + dev_err(dev, "failed to add uevent USB_ROLE_SWITCH\n"); + + return ret; +} + +static void usb_role_switch_release(struct device *dev) +{ + struct usb_role_switch *sw = to_role_switch(dev); + + kfree(sw); +} + +static const struct device_type usb_role_dev_type = { + .name = "usb_role_switch", + .groups = usb_role_switch_groups, + .uevent = usb_role_switch_uevent, + .release = usb_role_switch_release, +}; + +/** + * usb_role_switch_register - Register USB Role Switch + * @parent: Parent device for the switch + * @desc: Description of the switch + * + * USB Role Switch is a device capable or choosing the role for USB connector. + * On platforms where the USB controller is dual-role capable, the controller + * driver will need to register the switch. On platforms where the USB host and + * USB device controllers behind the connector are separate, there will be a + * mux, and the driver for that mux will need to register the switch. + * + * Returns handle to a new role switch or ERR_PTR. The content of @desc is + * copied. + */ +struct usb_role_switch * +usb_role_switch_register(struct device *parent, + const struct usb_role_switch_desc *desc) +{ + struct usb_role_switch *sw; + int ret; + + if (!desc || !desc->set) + return ERR_PTR(-EINVAL); + + sw = kzalloc(sizeof(*sw), GFP_KERNEL); + if (!sw) + return ERR_PTR(-ENOMEM); + + mutex_init(&sw->lock); + + sw->allow_userspace_control = desc->allow_userspace_control; + sw->usb2_port = desc->usb2_port; + sw->usb3_port = desc->usb3_port; + sw->udc = desc->udc; + sw->set = desc->set; + sw->get = desc->get; + + sw->dev.parent = parent; + sw->dev.class = role_class; + sw->dev.type = &usb_role_dev_type; + dev_set_name(&sw->dev, "%s-role-switch", dev_name(parent)); + + ret = device_register(&sw->dev); + if (ret) { + put_device(&sw->dev); + return ERR_PTR(ret); + } + + /* TODO: Symlinks for the host port and the device controller. */ + + return sw; +} +EXPORT_SYMBOL_GPL(usb_role_switch_register); + +/** + * usb_role_switch_unregister - Unregsiter USB Role Switch + * @sw: USB Role Switch + * + * Unregister switch that was registered with usb_role_switch_register(). + */ +void usb_role_switch_unregister(struct usb_role_switch *sw) +{ + if (!IS_ERR_OR_NULL(sw)) + device_unregister(&sw->dev); +} +EXPORT_SYMBOL_GPL(usb_role_switch_unregister); + +static int __init usb_roles_init(void) +{ + role_class = class_create(THIS_MODULE, "usb_role"); + return PTR_ERR_OR_ZERO(role_class); +} +subsys_initcall(usb_roles_init); + +static void __exit usb_roles_exit(void) +{ + class_destroy(role_class); +} +module_exit(usb_roles_exit); + +MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>"); +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("USB Role Class"); diff --git a/drivers/usb/core/Makefile b/drivers/usb/core/Makefile index 92c9cefb4317..18e874b0441e 100644 --- a/drivers/usb/core/Makefile +++ b/drivers/usb/core/Makefile @@ -6,7 +6,7 @@ usbcore-y := usb.o hub.o hcd.o urb.o message.o driver.o usbcore-y += config.o file.o buffer.o sysfs.o endpoint.o usbcore-y += devio.o notify.o generic.o quirks.o devices.o -usbcore-y += port.o +usbcore-y += phy.o port.o usbcore-$(CONFIG_OF) += of.o usbcore-$(CONFIG_USB_PCI) += hcd-pci.o diff --git a/drivers/usb/core/generic.c b/drivers/usb/core/generic.c index 83c14dda6300..bc8242bc4564 100644 --- a/drivers/usb/core/generic.c +++ b/drivers/usb/core/generic.c @@ -210,8 +210,13 @@ static int generic_suspend(struct usb_device *udev, pm_message_t msg) if (!udev->parent) rc = hcd_bus_suspend(udev, msg); - /* Non-root devices don't need to do anything for FREEZE or PRETHAW */ - else if (msg.event == PM_EVENT_FREEZE || msg.event == PM_EVENT_PRETHAW) + /* + * Non-root USB2 devices don't need to do anything for FREEZE + * or PRETHAW. USB3 devices don't support global suspend and + * needs to be selectively suspended. + */ + else if ((msg.event == PM_EVENT_FREEZE || msg.event == PM_EVENT_PRETHAW) + && (udev->speed < USB_SPEED_SUPER)) rc = 0; else rc = usb_port_suspend(udev, msg); diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index fc32391a34d5..777036ae6367 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -37,6 +37,7 @@ #include <linux/usb/otg.h> #include "usb.h" +#include "phy.h" /*-------------------------------------------------------------------------*/ @@ -2260,6 +2261,9 @@ int hcd_bus_suspend(struct usb_device *rhdev, pm_message_t msg) usb_set_device_state(rhdev, USB_STATE_SUSPENDED); hcd->state = HC_STATE_SUSPENDED; + if (!PMSG_IS_AUTO(msg)) + usb_phy_roothub_power_off(hcd->phy_roothub); + /* Did we race with a root-hub wakeup event? */ if (rhdev->do_remote_wakeup) { char buffer[6]; @@ -2296,6 +2300,13 @@ int hcd_bus_resume(struct usb_device *rhdev, pm_message_t msg) dev_dbg(&rhdev->dev, "skipped %s of dead bus\n", "resume"); return 0; } + + if (!PMSG_IS_AUTO(msg)) { + status = usb_phy_roothub_power_on(hcd->phy_roothub); + if (status) + return status; + } + if (!hcd->driver->bus_resume) return -ENOENT; if (HCD_RH_RUNNING(hcd)) @@ -2333,6 +2344,7 @@ int hcd_bus_resume(struct usb_device *rhdev, pm_message_t msg) } } else { hcd->state = old_state; + usb_phy_roothub_power_off(hcd->phy_roothub); dev_dbg(&rhdev->dev, "bus %s fail, err %d\n", "resume", status); if (status != -ESHUTDOWN) @@ -2727,7 +2739,7 @@ int usb_add_hcd(struct usb_hcd *hcd, int retval; struct usb_device *rhdev; - if (IS_ENABLED(CONFIG_USB_PHY) && !hcd->usb_phy) { + if (IS_ENABLED(CONFIG_USB_PHY) && !hcd->skip_phy_initialization) { struct usb_phy *phy = usb_get_phy_dev(hcd->self.sysdev, 0); if (IS_ERR(phy)) { @@ -2745,28 +2757,16 @@ int usb_add_hcd(struct usb_hcd *hcd, } } - if (IS_ENABLED(CONFIG_GENERIC_PHY) && !hcd->phy) { - struct phy *phy = phy_get(hcd->self.sysdev, "usb"); - - if (IS_ERR(phy)) { - retval = PTR_ERR(phy); - if (retval == -EPROBE_DEFER) - goto err_phy; - } else { - retval = phy_init(phy); - if (retval) { - phy_put(phy); - goto err_phy; - } - retval = phy_power_on(phy); - if (retval) { - phy_exit(phy); - phy_put(phy); - goto err_phy; - } - hcd->phy = phy; - hcd->remove_phy = 1; + if (!hcd->skip_phy_initialization && usb_hcd_is_primary_hcd(hcd)) { + hcd->phy_roothub = usb_phy_roothub_init(hcd->self.sysdev); + if (IS_ERR(hcd->phy_roothub)) { + retval = PTR_ERR(hcd->phy_roothub); + goto err_phy_roothub_init; } + + retval = usb_phy_roothub_power_on(hcd->phy_roothub); + if (retval) + goto err_usb_phy_roothub_power_on; } dev_info(hcd->self.controller, "%s\n", hcd->product_desc); @@ -2933,13 +2933,10 @@ err_allocate_root_hub: err_register_bus: hcd_buffer_destroy(hcd); err_create_buf: - if (IS_ENABLED(CONFIG_GENERIC_PHY) && hcd->remove_phy && hcd->phy) { - phy_power_off(hcd->phy); - phy_exit(hcd->phy); - phy_put(hcd->phy); - hcd->phy = NULL; - } -err_phy: + usb_phy_roothub_power_off(hcd->phy_roothub); +err_usb_phy_roothub_power_on: + usb_phy_roothub_exit(hcd->phy_roothub); +err_phy_roothub_init: if (hcd->remove_phy && hcd->usb_phy) { usb_phy_shutdown(hcd->usb_phy); usb_put_phy(hcd->usb_phy); @@ -3017,12 +3014,9 @@ void usb_remove_hcd(struct usb_hcd *hcd) usb_deregister_bus(&hcd->self); hcd_buffer_destroy(hcd); - if (IS_ENABLED(CONFIG_GENERIC_PHY) && hcd->remove_phy && hcd->phy) { - phy_power_off(hcd->phy); - phy_exit(hcd->phy); - phy_put(hcd->phy); - hcd->phy = NULL; - } + usb_phy_roothub_power_off(hcd->phy_roothub); + usb_phy_roothub_exit(hcd->phy_roothub); + if (hcd->remove_phy && hcd->usb_phy) { usb_phy_shutdown(hcd->usb_phy); usb_put_phy(hcd->usb_phy); diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index c5c1f6cf3228..f6ea16e9f6bb 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2192,9 +2192,13 @@ static void show_string(struct usb_device *udev, char *id, char *string) static void announce_device(struct usb_device *udev) { - dev_info(&udev->dev, "New USB device found, idVendor=%04x, idProduct=%04x\n", + u16 bcdDevice = le16_to_cpu(udev->descriptor.bcdDevice); + + dev_info(&udev->dev, + "New USB device found, idVendor=%04x, idProduct=%04x, bcdDevice=%2x.%02x\n", le16_to_cpu(udev->descriptor.idVendor), - le16_to_cpu(udev->descriptor.idProduct)); + le16_to_cpu(udev->descriptor.idProduct), + bcdDevice >> 8, bcdDevice & 0xff); dev_info(&udev->dev, "New USB device strings: Mfr=%d, Product=%d, SerialNumber=%d\n", udev->descriptor.iManufacturer, @@ -3655,7 +3659,7 @@ static int hub_reset_resume(struct usb_interface *intf) */ void usb_root_hub_lost_power(struct usb_device *rhdev) { - dev_warn(&rhdev->dev, "root hub lost power or was reset\n"); + dev_notice(&rhdev->dev, "root hub lost power or was reset\n"); rhdev->reset_resume = 1; } EXPORT_SYMBOL_GPL(usb_root_hub_lost_power); @@ -5104,8 +5108,10 @@ static void port_event(struct usb_hub *hub, int port1) if (portchange & USB_PORT_STAT_C_OVERCURRENT) { u16 status = 0, unused; + port_dev->over_current_count++; - dev_dbg(&port_dev->dev, "over-current change\n"); + dev_dbg(&port_dev->dev, "over-current change #%u\n", + port_dev->over_current_count); usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_OVER_CURRENT); msleep(100); /* Cool down */ @@ -5505,21 +5511,15 @@ static int usb_reset_and_verify_device(struct usb_device *udev) if (udev->usb2_hw_lpm_enabled == 1) usb_set_usb2_hardware_lpm(udev, 0); - /* Disable LPM and LTM while we reset the device and reinstall the alt - * settings. Device-initiated LPM settings, and system exit latency - * settings are cleared when the device is reset, so we have to set - * them up again. + /* Disable LPM while we reset the device and reinstall the alt settings. + * Device-initiated LPM, and system exit latency settings are cleared + * when the device is reset, so we have to set them up again. */ ret = usb_unlocked_disable_lpm(udev); if (ret) { dev_err(&udev->dev, "%s Failed to disable LPM\n", __func__); goto re_enumerate_no_bos; } - ret = usb_disable_ltm(udev); - if (ret) { - dev_err(&udev->dev, "%s Failed to disable LTM\n", __func__); - goto re_enumerate_no_bos; - } bos = udev->bos; udev->bos = NULL; diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h index 2a700ccc868c..4dc769ee9c74 100644 --- a/drivers/usb/core/hub.h +++ b/drivers/usb/core/hub.h @@ -96,6 +96,7 @@ struct usb_port { enum usb_port_connect_type connect_type; usb_port_location_t location; struct mutex status_lock; + u32 over_current_count; u8 portnum; unsigned int is_superspeed:1; unsigned int usb3_lpm_u1_permit:1; diff --git a/drivers/usb/core/phy.c b/drivers/usb/core/phy.c new file mode 100644 index 000000000000..09b7c43c0ea4 --- /dev/null +++ b/drivers/usb/core/phy.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * A wrapper for multiple PHYs which passes all phy_* function calls to + * multiple (actual) PHY devices. This is comes handy when initializing + * all PHYs on a HCD and to keep them all in the same state. + * + * Copyright (C) 2018 Martin Blumenstingl <martin.blumenstingl@googlemail.com> + */ + +#include <linux/device.h> +#include <linux/list.h> +#include <linux/phy/phy.h> +#include <linux/of.h> + +#include "phy.h" + +struct usb_phy_roothub { + struct phy *phy; + struct list_head list; +}; + +static struct usb_phy_roothub *usb_phy_roothub_alloc(struct device *dev) +{ + struct usb_phy_roothub *roothub_entry; + + roothub_entry = devm_kzalloc(dev, sizeof(*roothub_entry), GFP_KERNEL); + if (!roothub_entry) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&roothub_entry->list); + + return roothub_entry; +} + +static int usb_phy_roothub_add_phy(struct device *dev, int index, + struct list_head *list) +{ + struct usb_phy_roothub *roothub_entry; + struct phy *phy = devm_of_phy_get_by_index(dev, dev->of_node, index); + + if (IS_ERR_OR_NULL(phy)) { + if (!phy || PTR_ERR(phy) == -ENODEV) + return 0; + else + return PTR_ERR(phy); + } + + roothub_entry = usb_phy_roothub_alloc(dev); + if (IS_ERR(roothub_entry)) + return PTR_ERR(roothub_entry); + + roothub_entry->phy = phy; + + list_add_tail(&roothub_entry->list, list); + + return 0; +} + +struct usb_phy_roothub *usb_phy_roothub_init(struct device *dev) +{ + struct usb_phy_roothub *phy_roothub; + struct usb_phy_roothub *roothub_entry; + struct list_head *head; + int i, num_phys, err; + + num_phys = of_count_phandle_with_args(dev->of_node, "phys", + "#phy-cells"); + if (num_phys <= 0) + return NULL; + + phy_roothub = usb_phy_roothub_alloc(dev); + if (IS_ERR(phy_roothub)) + return phy_roothub; + + for (i = 0; i < num_phys; i++) { + err = usb_phy_roothub_add_phy(dev, i, &phy_roothub->list); + if (err) + goto err_out; + } + + head = &phy_roothub->list; + + list_for_each_entry(roothub_entry, head, list) { + err = phy_init(roothub_entry->phy); + if (err) + goto err_exit_phys; + } + + return phy_roothub; + +err_exit_phys: + list_for_each_entry_continue_reverse(roothub_entry, head, list) + phy_exit(roothub_entry->phy); + +err_out: + return ERR_PTR(err); +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_init); + +int usb_phy_roothub_exit(struct usb_phy_roothub *phy_roothub) +{ + struct usb_phy_roothub *roothub_entry; + struct list_head *head; + int err, ret = 0; + + if (!phy_roothub) + return 0; + + head = &phy_roothub->list; + + list_for_each_entry(roothub_entry, head, list) { + err = phy_exit(roothub_entry->phy); + if (err) + ret = ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_exit); + +int usb_phy_roothub_power_on(struct usb_phy_roothub *phy_roothub) +{ + struct usb_phy_roothub *roothub_entry; + struct list_head *head; + int err; + + if (!phy_roothub) + return 0; + + head = &phy_roothub->list; + + list_for_each_entry(roothub_entry, head, list) { + err = phy_power_on(roothub_entry->phy); + if (err) + goto err_out; + } + + return 0; + +err_out: + list_for_each_entry_continue_reverse(roothub_entry, head, list) + phy_power_off(roothub_entry->phy); + + return err; +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_power_on); + +void usb_phy_roothub_power_off(struct usb_phy_roothub *phy_roothub) +{ + struct usb_phy_roothub *roothub_entry; + + if (!phy_roothub) + return; + + list_for_each_entry_reverse(roothub_entry, &phy_roothub->list, list) + phy_power_off(roothub_entry->phy); +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_power_off); diff --git a/drivers/usb/core/phy.h b/drivers/usb/core/phy.h new file mode 100644 index 000000000000..6fde59bfbff8 --- /dev/null +++ b/drivers/usb/core/phy.h @@ -0,0 +1,7 @@ +struct usb_phy_roothub; + +struct usb_phy_roothub *usb_phy_roothub_init(struct device *dev); +int usb_phy_roothub_exit(struct usb_phy_roothub *phy_roothub); + +int usb_phy_roothub_power_on(struct usb_phy_roothub *phy_roothub); +void usb_phy_roothub_power_off(struct usb_phy_roothub *phy_roothub); diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index 1a01e9ad3804..6979bde87d31 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -41,6 +41,15 @@ static ssize_t connect_type_show(struct device *dev, } static DEVICE_ATTR_RO(connect_type); +static ssize_t over_current_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_port *port_dev = to_usb_port(dev); + + return sprintf(buf, "%u\n", port_dev->over_current_count); +} +static DEVICE_ATTR_RO(over_current_count); + static ssize_t usb3_lpm_permit_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -109,6 +118,7 @@ static DEVICE_ATTR_RW(usb3_lpm_permit); static struct attribute *port_dev_attrs[] = { &dev_attr_connect_type.attr, + &dev_attr_over_current_count.attr, NULL, }; diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index 54b019e267c5..920f48a49a87 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -6,11 +6,157 @@ * Copyright (c) 2007 Greg Kroah-Hartman <gregkh@suse.de> */ +#include <linux/moduleparam.h> #include <linux/usb.h> #include <linux/usb/quirks.h> #include <linux/usb/hcd.h> #include "usb.h" +struct quirk_entry { + u16 vid; + u16 pid; + u32 flags; +}; + +static DEFINE_MUTEX(quirk_mutex); + +static struct quirk_entry *quirk_list; +static unsigned int quirk_count; + +static char quirks_param[128]; + +static int quirks_param_set(const char *val, const struct kernel_param *kp) +{ + char *p, *field; + u16 vid, pid; + u32 flags; + size_t i; + int err; + + err = param_set_copystring(val, kp); + if (err) + return err; + + mutex_lock(&quirk_mutex); + + if (!*val) { + quirk_count = 0; + kfree(quirk_list); + quirk_list = NULL; + goto unlock; + } + + for (quirk_count = 1, i = 0; val[i]; i++) + if (val[i] == ',') + quirk_count++; + + if (quirk_list) { + kfree(quirk_list); + quirk_list = NULL; + } + + quirk_list = kcalloc(quirk_count, sizeof(struct quirk_entry), + GFP_KERNEL); + if (!quirk_list) { + mutex_unlock(&quirk_mutex); + return -ENOMEM; + } + + for (i = 0, p = (char *)val; p && *p;) { + /* Each entry consists of VID:PID:flags */ + field = strsep(&p, ":"); + if (!field) + break; + + if (kstrtou16(field, 16, &vid)) + break; + + field = strsep(&p, ":"); + if (!field) + break; + + if (kstrtou16(field, 16, &pid)) + break; + + field = strsep(&p, ","); + if (!field || !*field) + break; + + /* Collect the flags */ + for (flags = 0; *field; field++) { + switch (*field) { + case 'a': + flags |= USB_QUIRK_STRING_FETCH_255; + break; + case 'b': + flags |= USB_QUIRK_RESET_RESUME; + break; + case 'c': + flags |= USB_QUIRK_NO_SET_INTF; + break; + case 'd': + flags |= USB_QUIRK_CONFIG_INTF_STRINGS; + break; + case 'e': + flags |= USB_QUIRK_RESET; + break; + case 'f': + flags |= USB_QUIRK_HONOR_BNUMINTERFACES; + break; + case 'g': + flags |= USB_QUIRK_DELAY_INIT; + break; + case 'h': + flags |= USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL; + break; + case 'i': + flags |= USB_QUIRK_DEVICE_QUALIFIER; + break; + case 'j': + flags |= USB_QUIRK_IGNORE_REMOTE_WAKEUP; + break; + case 'k': + flags |= USB_QUIRK_NO_LPM; + break; + case 'l': + flags |= USB_QUIRK_LINEAR_FRAME_INTR_BINTERVAL; + break; + case 'm': + flags |= USB_QUIRK_DISCONNECT_SUSPEND; + break; + case 'n': + flags |= USB_QUIRK_DELAY_CTRL_MSG; + break; + /* Ignore unrecognized flag characters */ + } + } + + quirk_list[i++] = (struct quirk_entry) + { .vid = vid, .pid = pid, .flags = flags }; + } + + if (i < quirk_count) + quirk_count = i; + +unlock: + mutex_unlock(&quirk_mutex); + + return 0; +} + +static const struct kernel_param_ops quirks_param_ops = { + .set = quirks_param_set, + .get = param_get_string, +}; + +static struct kparam_string quirks_param_string = { + .maxlen = sizeof(quirks_param), + .string = quirks_param, +}; + +module_param_cb(quirks, &quirks_param_ops, &quirks_param_string, 0644); +MODULE_PARM_DESC(quirks, "Add/modify USB quirks by specifying quirks=vendorID:productID:quirks"); + /* Lists of quirky USB devices, split in device quirks and interface quirks. * Device quirks are applied at the very beginning of the enumeration process, * right after reading the device descriptor. They can thus only match on device @@ -321,8 +467,8 @@ static int usb_amd_resume_quirk(struct usb_device *udev) return 0; } -static u32 __usb_detect_quirks(struct usb_device *udev, - const struct usb_device_id *id) +static u32 usb_detect_static_quirks(struct usb_device *udev, + const struct usb_device_id *id) { u32 quirks = 0; @@ -340,21 +486,43 @@ static u32 __usb_detect_quirks(struct usb_device *udev, return quirks; } +static u32 usb_detect_dynamic_quirks(struct usb_device *udev) +{ + u16 vid = le16_to_cpu(udev->descriptor.idVendor); + u16 pid = le16_to_cpu(udev->descriptor.idProduct); + int i, flags = 0; + + mutex_lock(&quirk_mutex); + + for (i = 0; i < quirk_count; i++) { + if (vid == quirk_list[i].vid && pid == quirk_list[i].pid) { + flags = quirk_list[i].flags; + break; + } + } + + mutex_unlock(&quirk_mutex); + + return flags; +} + /* * Detect any quirks the device has, and do any housekeeping for it if needed. */ void usb_detect_quirks(struct usb_device *udev) { - udev->quirks = __usb_detect_quirks(udev, usb_quirk_list); + udev->quirks = usb_detect_static_quirks(udev, usb_quirk_list); /* * Pixart-based mice would trigger remote wakeup issue on AMD * Yangtze chipset, so set them as RESET_RESUME flag. */ if (usb_amd_resume_quirk(udev)) - udev->quirks |= __usb_detect_quirks(udev, + udev->quirks |= usb_detect_static_quirks(udev, usb_amd_resume_quirk_list); + udev->quirks ^= usb_detect_dynamic_quirks(udev); + if (udev->quirks) dev_dbg(&udev->dev, "USB quirks for this device: %x\n", udev->quirks); @@ -373,7 +541,7 @@ void usb_detect_interface_quirks(struct usb_device *udev) { u32 quirks; - quirks = __usb_detect_quirks(udev, usb_interface_quirk_list); + quirks = usb_detect_static_quirks(udev, usb_interface_quirk_list); if (quirks == 0) return; @@ -381,3 +549,11 @@ void usb_detect_interface_quirks(struct usb_device *udev) quirks); udev->quirks |= quirks; } + +void usb_release_quirk_list(void) +{ + mutex_lock(&quirk_mutex); + kfree(quirk_list); + quirk_list = NULL; + mutex_unlock(&quirk_mutex); +} diff --git a/drivers/usb/core/urb.c b/drivers/usb/core/urb.c index 796c9b149728..f51750bcd152 100644 --- a/drivers/usb/core/urb.c +++ b/drivers/usb/core/urb.c @@ -433,6 +433,14 @@ int usb_submit_urb(struct urb *urb, gfp_t mem_flags) max *= mult; } + if (dev->speed == USB_SPEED_SUPER_PLUS && + USB_SS_SSP_ISOC_COMP(ep->ss_ep_comp.bmAttributes)) { + struct usb_ssp_isoc_ep_comp_descriptor *isoc_ep_comp; + + isoc_ep_comp = &ep->ssp_isoc_ep_comp; + max = le32_to_cpu(isoc_ep_comp->dwBytesPerInterval); + } + /* "high bandwidth" mode, 1-3 packets/uframe? */ if (dev->speed == USB_SPEED_HIGH) max *= usb_endpoint_maxp_mult(&ep->desc); diff --git a/drivers/usb/core/usb-acpi.c b/drivers/usb/core/usb-acpi.c index 84da17460568..e221861b3187 100644 --- a/drivers/usb/core/usb-acpi.c +++ b/drivers/usb/core/usb-acpi.c @@ -90,8 +90,8 @@ static enum usb_port_connect_type usb_acpi_get_connect_type(acpi_handle handle, acpi_status status; /* - * According to ACPI Spec 9.13. PLD indicates whether usb port is - * user visible and _UPC indicates whether it is connectable. If + * According to 9.14 in ACPI Spec 6.2. _PLD indicates whether usb port + * is user visible and _UPC indicates whether it is connectable. If * the port was visible and connectable, it could be freely connected * and disconnected with USB devices. If no visible and connectable, * a usb device is directly hard-wired to the port. If no visible and diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 2f5fbc56a9dd..0adb6345ff2e 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -1259,6 +1259,7 @@ static void __exit usb_exit(void) if (usb_disabled()) return; + usb_release_quirk_list(); usb_deregister_device_driver(&usb_generic_driver); usb_major_cleanup(); usb_deregister(&usbfs_driver); diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index 149cc7480971..546a2219454b 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -36,6 +36,7 @@ extern void usb_deauthorize_interface(struct usb_interface *); extern void usb_authorize_interface(struct usb_interface *); extern void usb_detect_quirks(struct usb_device *udev); extern void usb_detect_interface_quirks(struct usb_device *udev); +extern void usb_release_quirk_list(void); extern int usb_remove_device(struct usb_device *udev); extern int usb_get_device_descriptor(struct usb_device *dev, diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index 82a7d98c3436..18a0a1771289 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -64,10 +64,11 @@ * * @hsotg: Programming view of the DWC_otg controller */ -static int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg) +int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg) { struct dwc2_gregs_backup *gr; - int i; + + dev_dbg(hsotg->dev, "%s\n", __func__); /* Backup global regs */ gr = &hsotg->gr_backup; @@ -78,10 +79,11 @@ static int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg) gr->gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); gr->grxfsiz = dwc2_readl(hsotg->regs + GRXFSIZ); gr->gnptxfsiz = dwc2_readl(hsotg->regs + GNPTXFSIZ); - gr->hptxfsiz = dwc2_readl(hsotg->regs + HPTXFSIZ); gr->gdfifocfg = dwc2_readl(hsotg->regs + GDFIFOCFG); - for (i = 0; i < MAX_EPS_CHANNELS; i++) - gr->dtxfsiz[i] = dwc2_readl(hsotg->regs + DPTXFSIZN(i)); + gr->pcgcctl1 = dwc2_readl(hsotg->regs + PCGCCTL1); + gr->glpmcfg = dwc2_readl(hsotg->regs + GLPMCFG); + gr->gi2cctl = dwc2_readl(hsotg->regs + GI2CCTL); + gr->pcgcctl = dwc2_readl(hsotg->regs + PCGCTL); gr->valid = true; return 0; @@ -94,10 +96,9 @@ static int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg) * * @hsotg: Programming view of the DWC_otg controller */ -static int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg) +int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg) { struct dwc2_gregs_backup *gr; - int i; dev_dbg(hsotg->dev, "%s\n", __func__); @@ -117,26 +118,27 @@ static int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg) dwc2_writel(gr->gahbcfg, hsotg->regs + GAHBCFG); dwc2_writel(gr->grxfsiz, hsotg->regs + GRXFSIZ); dwc2_writel(gr->gnptxfsiz, hsotg->regs + GNPTXFSIZ); - dwc2_writel(gr->hptxfsiz, hsotg->regs + HPTXFSIZ); dwc2_writel(gr->gdfifocfg, hsotg->regs + GDFIFOCFG); - for (i = 0; i < MAX_EPS_CHANNELS; i++) - dwc2_writel(gr->dtxfsiz[i], hsotg->regs + DPTXFSIZN(i)); + dwc2_writel(gr->pcgcctl1, hsotg->regs + PCGCCTL1); + dwc2_writel(gr->glpmcfg, hsotg->regs + GLPMCFG); + dwc2_writel(gr->pcgcctl, hsotg->regs + PCGCTL); + dwc2_writel(gr->gi2cctl, hsotg->regs + GI2CCTL); return 0; } /** - * dwc2_exit_hibernation() - Exit controller from Partial Power Down. + * dwc2_exit_partial_power_down() - Exit controller from Partial Power Down. * * @hsotg: Programming view of the DWC_otg controller * @restore: Controller registers need to be restored */ -int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, bool restore) +int dwc2_exit_partial_power_down(struct dwc2_hsotg *hsotg, bool restore) { u32 pcgcctl; int ret = 0; - if (!hsotg->params.hibernation) + if (hsotg->params.power_down != DWC2_POWER_DOWN_PARAM_PARTIAL) return -ENOTSUPP; pcgcctl = dwc2_readl(hsotg->regs + PCGCTL); @@ -167,7 +169,7 @@ int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, bool restore) return ret; } } else { - ret = dwc2_restore_device_registers(hsotg); + ret = dwc2_restore_device_registers(hsotg, 0); if (ret) { dev_err(hsotg->dev, "%s: failed to restore device registers\n", __func__); @@ -180,16 +182,16 @@ int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, bool restore) } /** - * dwc2_enter_hibernation() - Put controller in Partial Power Down. + * dwc2_enter_partial_power_down() - Put controller in Partial Power Down. * * @hsotg: Programming view of the DWC_otg controller */ -int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg) +int dwc2_enter_partial_power_down(struct dwc2_hsotg *hsotg) { u32 pcgcctl; int ret = 0; - if (!hsotg->params.hibernation) + if (!hsotg->params.power_down) return -ENOTSUPP; /* Backup all registers */ @@ -218,7 +220,7 @@ int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg) /* * Clear any pending interrupts since dwc2 will not be able to - * clear them after entering hibernation. + * clear them after entering partial_power_down. */ dwc2_writel(0xffffffff, hsotg->regs + GINTSTS); @@ -240,6 +242,142 @@ int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg) } /** + * dwc2_restore_essential_regs() - Restore essiential regs of core. + * + * @hsotg: Programming view of the DWC_otg controller + * @rmode: Restore mode, enabled in case of remote-wakeup. + * @is_host: Host or device mode. + */ +static void dwc2_restore_essential_regs(struct dwc2_hsotg *hsotg, int rmode, + int is_host) +{ + u32 pcgcctl; + struct dwc2_gregs_backup *gr; + struct dwc2_dregs_backup *dr; + struct dwc2_hregs_backup *hr; + + gr = &hsotg->gr_backup; + dr = &hsotg->dr_backup; + hr = &hsotg->hr_backup; + + dev_dbg(hsotg->dev, "%s: restoring essential regs\n", __func__); + + /* Load restore values for [31:14] bits */ + pcgcctl = (gr->pcgcctl & 0xffffc000); + /* If High Speed */ + if (is_host) { + if (!(pcgcctl & PCGCTL_P2HD_PRT_SPD_MASK)) + pcgcctl |= BIT(17); + } else { + if (!(pcgcctl & PCGCTL_P2HD_DEV_ENUM_SPD_MASK)) + pcgcctl |= BIT(17); + } + dwc2_writel(pcgcctl, hsotg->regs + PCGCTL); + + /* Umnask global Interrupt in GAHBCFG and restore it */ + dwc2_writel(gr->gahbcfg | GAHBCFG_GLBL_INTR_EN, hsotg->regs + GAHBCFG); + + /* Clear all pending interupts */ + dwc2_writel(0xffffffff, hsotg->regs + GINTSTS); + + /* Unmask restore done interrupt */ + dwc2_writel(GINTSTS_RESTOREDONE, hsotg->regs + GINTMSK); + + /* Restore GUSBCFG and HCFG/DCFG */ + dwc2_writel(gr->gusbcfg, hsotg->regs + GUSBCFG); + + if (is_host) { + dwc2_writel(hr->hcfg, hsotg->regs + HCFG); + if (rmode) + pcgcctl |= PCGCTL_RESTOREMODE; + dwc2_writel(pcgcctl, hsotg->regs + PCGCTL); + udelay(10); + + pcgcctl |= PCGCTL_ESS_REG_RESTORED; + dwc2_writel(pcgcctl, hsotg->regs + PCGCTL); + udelay(10); + } else { + dwc2_writel(dr->dcfg, hsotg->regs + DCFG); + if (!rmode) + pcgcctl |= PCGCTL_RESTOREMODE | PCGCTL_RSTPDWNMODULE; + dwc2_writel(pcgcctl, hsotg->regs + PCGCTL); + udelay(10); + + pcgcctl |= PCGCTL_ESS_REG_RESTORED; + dwc2_writel(pcgcctl, hsotg->regs + PCGCTL); + udelay(10); + } +} + +/** + * dwc2_hib_restore_common() - Common part of restore routine. + * + * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: Remote-wakeup, enabled in case of remote-wakeup. + * @is_host: Host or device mode. + */ +void dwc2_hib_restore_common(struct dwc2_hsotg *hsotg, int rem_wakeup, + int is_host) +{ + u32 gpwrdn; + + /* Switch-on voltage to the core */ + gpwrdn = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn &= ~GPWRDN_PWRDNSWTCH; + dwc2_writel(gpwrdn, hsotg->regs + GPWRDN); + udelay(10); + + /* Reset core */ + gpwrdn = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn &= ~GPWRDN_PWRDNRSTN; + dwc2_writel(gpwrdn, hsotg->regs + GPWRDN); + udelay(10); + + /* Enable restore from PMU */ + gpwrdn = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn |= GPWRDN_RESTORE; + dwc2_writel(gpwrdn, hsotg->regs + GPWRDN); + udelay(10); + + /* Disable Power Down Clamp */ + gpwrdn = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn &= ~GPWRDN_PWRDNCLMP; + dwc2_writel(gpwrdn, hsotg->regs + GPWRDN); + udelay(50); + + if (!is_host && rem_wakeup) + udelay(70); + + /* Deassert reset core */ + gpwrdn = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn |= GPWRDN_PWRDNRSTN; + dwc2_writel(gpwrdn, hsotg->regs + GPWRDN); + udelay(10); + + /* Disable PMU interrupt */ + gpwrdn = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn &= ~GPWRDN_PMUINTSEL; + dwc2_writel(gpwrdn, hsotg->regs + GPWRDN); + udelay(10); + + /* Set Restore Essential Regs bit in PCGCCTL register */ + dwc2_restore_essential_regs(hsotg, rem_wakeup, is_host); + + /* + * Wait For Restore_done Interrupt. This mechanism of polling the + * interrupt is introduced to avoid any possible race conditions + */ + if (dwc2_hsotg_wait_bit_set(hsotg, GINTSTS, GINTSTS_RESTOREDONE, + 20000)) { + dev_dbg(hsotg->dev, + "%s: Restore Done wan't generated here\n", + __func__); + } else { + dev_dbg(hsotg->dev, "restore done generated here\n"); + } +} + +/** * dwc2_wait_for_mode() - Waits for the controller mode. * @hsotg: Programming view of the DWC_otg controller. * @host_mode: If true, waits for host mode, otherwise device mode. @@ -311,13 +449,50 @@ static bool dwc2_iddig_filter_enabled(struct dwc2_hsotg *hsotg) } /* + * dwc2_enter_hibernation() - Common function to enter hibernation. + * + * @hsotg: Programming view of the DWC_otg controller + * @is_host: True if core is in host mode. + * + * Return: 0 if successful, negative error code otherwise + */ +int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg, int is_host) +{ + if (hsotg->params.power_down != DWC2_POWER_DOWN_PARAM_HIBERNATION) + return -ENOTSUPP; + + if (is_host) + return dwc2_host_enter_hibernation(hsotg); + else + return dwc2_gadget_enter_hibernation(hsotg); +} + +/* + * dwc2_exit_hibernation() - Common function to exit from hibernation. + * + * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: Remote-wakeup, enabled in case of remote-wakeup. + * @reset: Enabled in case of restore with reset. + * @is_host: True if core is in host mode. + * + * Return: 0 if successful, negative error code otherwise + */ +int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup, + int reset, int is_host) +{ + if (is_host) + return dwc2_host_exit_hibernation(hsotg, rem_wakeup, reset); + else + return dwc2_gadget_exit_hibernation(hsotg, rem_wakeup, reset); +} + +/* * Do core a soft reset of the core. Be careful with this because it * resets all the internal state machines of the core. */ int dwc2_core_reset(struct dwc2_hsotg *hsotg, bool skip_wait) { u32 greset; - int count = 0; bool wait_for_host_mode = false; dev_vdbg(hsotg->dev, "%s()\n", __func__); @@ -346,29 +521,19 @@ int dwc2_core_reset(struct dwc2_hsotg *hsotg, bool skip_wait) greset = dwc2_readl(hsotg->regs + GRSTCTL); greset |= GRSTCTL_CSFTRST; dwc2_writel(greset, hsotg->regs + GRSTCTL); - do { - udelay(1); - greset = dwc2_readl(hsotg->regs + GRSTCTL); - if (++count > 50) { - dev_warn(hsotg->dev, - "%s() HANG! Soft Reset GRSTCTL=%0x\n", - __func__, greset); - return -EBUSY; - } - } while (greset & GRSTCTL_CSFTRST); + + if (dwc2_hsotg_wait_bit_clear(hsotg, GRSTCTL, GRSTCTL_CSFTRST, 50)) { + dev_warn(hsotg->dev, "%s: HANG! Soft Reset timeout GRSTCTL GRSTCTL_CSFTRST\n", + __func__); + return -EBUSY; + } /* Wait for AHB master IDLE state */ - count = 0; - do { - udelay(1); - greset = dwc2_readl(hsotg->regs + GRSTCTL); - if (++count > 50) { - dev_warn(hsotg->dev, - "%s() HANG! AHB Idle GRSTCTL=%0x\n", - __func__, greset); - return -EBUSY; - } - } while (!(greset & GRSTCTL_AHBIDLE)); + if (dwc2_hsotg_wait_bit_set(hsotg, GRSTCTL, GRSTCTL_AHBIDLE, 50)) { + dev_warn(hsotg->dev, "%s: HANG! AHB Idle timeout GRSTCTL GRSTCTL_AHBIDLE\n", + __func__); + return -EBUSY; + } if (wait_for_host_mode && !skip_wait) dwc2_wait_for_mode(hsotg, true); @@ -376,14 +541,14 @@ int dwc2_core_reset(struct dwc2_hsotg *hsotg, bool skip_wait) return 0; } -/* - * Force the mode of the controller. +/** + * dwc2_force_mode() - Force the mode of the controller. * * Forcing the mode is needed for two cases: * * 1) If the dr_mode is set to either HOST or PERIPHERAL we force the * controller to stay in a particular mode regardless of ID pin - * changes. We do this usually after a core reset. + * changes. We do this once during probe. * * 2) During probe we want to read reset values of the hw * configuration registers that are only available in either host or @@ -400,7 +565,7 @@ int dwc2_core_reset(struct dwc2_hsotg *hsotg, bool skip_wait) * the filter is configured and enabled. We poll the current mode of * the controller to account for this delay. */ -static bool dwc2_force_mode(struct dwc2_hsotg *hsotg, bool host) +void dwc2_force_mode(struct dwc2_hsotg *hsotg, bool host) { u32 gusbcfg; u32 set; @@ -412,17 +577,17 @@ static bool dwc2_force_mode(struct dwc2_hsotg *hsotg, bool host) * Force mode has no effect if the hardware is not OTG. */ if (!dwc2_hw_is_otg(hsotg)) - return false; + return; /* * If dr_mode is either peripheral or host only, there is no * need to ever force the mode to the opposite mode. */ if (WARN_ON(host && hsotg->dr_mode == USB_DR_MODE_PERIPHERAL)) - return false; + return; if (WARN_ON(!host && hsotg->dr_mode == USB_DR_MODE_HOST)) - return false; + return; gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); @@ -434,7 +599,7 @@ static bool dwc2_force_mode(struct dwc2_hsotg *hsotg, bool host) dwc2_writel(gusbcfg, hsotg->regs + GUSBCFG); dwc2_wait_for_mode(hsotg, host); - return true; + return; } /** @@ -446,10 +611,15 @@ static bool dwc2_force_mode(struct dwc2_hsotg *hsotg, bool host) * the force mode. We only need to call this once during probe if * dr_mode == OTG. */ -void dwc2_clear_force_mode(struct dwc2_hsotg *hsotg) +static void dwc2_clear_force_mode(struct dwc2_hsotg *hsotg) { u32 gusbcfg; + if (!dwc2_hw_is_otg(hsotg)) + return; + + dev_dbg(hsotg->dev, "Clearing force mode bits\n"); + gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); gusbcfg &= ~GUSBCFG_FORCEHOSTMODE; gusbcfg &= ~GUSBCFG_FORCEDEVMODE; @@ -464,16 +634,13 @@ void dwc2_clear_force_mode(struct dwc2_hsotg *hsotg) */ void dwc2_force_dr_mode(struct dwc2_hsotg *hsotg) { - bool ret; - switch (hsotg->dr_mode) { case USB_DR_MODE_HOST: - ret = dwc2_force_mode(hsotg, true); /* * NOTE: This is required for some rockchip soc based * platforms on their host-only dwc2. */ - if (!ret) + if (!dwc2_hw_is_otg(hsotg)) msleep(50); break; @@ -491,22 +658,17 @@ void dwc2_force_dr_mode(struct dwc2_hsotg *hsotg) } /* - * Do core a soft reset of the core. Be careful with this because it - * resets all the internal state machines of the core. - * - * Additionally this will apply force mode as per the hsotg->dr_mode - * parameter. + * dwc2_enable_acg - enable active clock gating feature */ -int dwc2_core_reset_and_force_dr_mode(struct dwc2_hsotg *hsotg) +void dwc2_enable_acg(struct dwc2_hsotg *hsotg) { - int retval; + if (hsotg->params.acg_enable) { + u32 pcgcctl1 = dwc2_readl(hsotg->regs + PCGCCTL1); - retval = dwc2_core_reset(hsotg, false); - if (retval) - return retval; - - dwc2_force_dr_mode(hsotg); - return 0; + dev_dbg(hsotg->dev, "Enabling Active Clock Gating\n"); + pcgcctl1 |= PCGCCTL1_GATEEN; + dwc2_writel(pcgcctl1, hsotg->regs + PCGCCTL1); + } } /** @@ -683,25 +845,21 @@ void dwc2_dump_global_registers(struct dwc2_hsotg *hsotg) void dwc2_flush_tx_fifo(struct dwc2_hsotg *hsotg, const int num) { u32 greset; - int count = 0; dev_vdbg(hsotg->dev, "Flush Tx FIFO %d\n", num); + /* Wait for AHB master IDLE state */ + if (dwc2_hsotg_wait_bit_set(hsotg, GRSTCTL, GRSTCTL_AHBIDLE, 10000)) + dev_warn(hsotg->dev, "%s: HANG! AHB Idle GRSCTL\n", + __func__); + greset = GRSTCTL_TXFFLSH; greset |= num << GRSTCTL_TXFNUM_SHIFT & GRSTCTL_TXFNUM_MASK; dwc2_writel(greset, hsotg->regs + GRSTCTL); - do { - greset = dwc2_readl(hsotg->regs + GRSTCTL); - if (++count > 10000) { - dev_warn(hsotg->dev, - "%s() HANG! GRSTCTL=%0x GNPTXSTS=0x%08x\n", - __func__, greset, - dwc2_readl(hsotg->regs + GNPTXSTS)); - break; - } - udelay(1); - } while (greset & GRSTCTL_TXFFLSH); + if (dwc2_hsotg_wait_bit_clear(hsotg, GRSTCTL, GRSTCTL_TXFFLSH, 10000)) + dev_warn(hsotg->dev, "%s: HANG! timeout GRSTCTL GRSTCTL_TXFFLSH\n", + __func__); /* Wait for at least 3 PHY Clocks */ udelay(1); @@ -715,43 +873,26 @@ void dwc2_flush_tx_fifo(struct dwc2_hsotg *hsotg, const int num) void dwc2_flush_rx_fifo(struct dwc2_hsotg *hsotg) { u32 greset; - int count = 0; dev_vdbg(hsotg->dev, "%s()\n", __func__); + /* Wait for AHB master IDLE state */ + if (dwc2_hsotg_wait_bit_set(hsotg, GRSTCTL, GRSTCTL_AHBIDLE, 10000)) + dev_warn(hsotg->dev, "%s: HANG! AHB Idle GRSCTL\n", + __func__); + greset = GRSTCTL_RXFFLSH; dwc2_writel(greset, hsotg->regs + GRSTCTL); - do { - greset = dwc2_readl(hsotg->regs + GRSTCTL); - if (++count > 10000) { - dev_warn(hsotg->dev, "%s() HANG! GRSTCTL=%0x\n", - __func__, greset); - break; - } - udelay(1); - } while (greset & GRSTCTL_RXFFLSH); + /* Wait for RxFIFO flush done */ + if (dwc2_hsotg_wait_bit_clear(hsotg, GRSTCTL, GRSTCTL_RXFFLSH, 10000)) + dev_warn(hsotg->dev, "%s: HANG! timeout GRSTCTL GRSTCTL_RXFFLSH\n", + __func__); /* Wait for at least 3 PHY Clocks */ udelay(1); } -/* - * Forces either host or device mode if the controller is not - * currently in that mode. - * - * Returns true if the mode was forced. - */ -bool dwc2_force_mode_if_needed(struct dwc2_hsotg *hsotg, bool host) -{ - if (host && dwc2_is_host_mode(hsotg)) - return false; - else if (!host && dwc2_is_device_mode(hsotg)) - return false; - - return dwc2_force_mode(hsotg, host); -} - bool dwc2_is_controller_alive(struct dwc2_hsotg *hsotg) { if (dwc2_readl(hsotg->regs + GSNPSID) == 0xffffffff) @@ -825,6 +966,52 @@ bool dwc2_hw_is_device(struct dwc2_hsotg *hsotg) (op_mode == GHWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE); } +/** + * dwc2_hsotg_wait_bit_set - Waits for bit to be set. + * @hsotg: Programming view of DWC_otg controller. + * @offset: Register's offset where bit/bits must be set. + * @mask: Mask of the bit/bits which must be set. + * @timeout: Timeout to wait. + * + * Return: 0 if bit/bits are set or -ETIMEDOUT in case of timeout. + */ +int dwc2_hsotg_wait_bit_set(struct dwc2_hsotg *hsotg, u32 offset, u32 mask, + u32 timeout) +{ + u32 i; + + for (i = 0; i < timeout; i++) { + if (dwc2_readl(hsotg->regs + offset) & mask) + return 0; + udelay(1); + } + + return -ETIMEDOUT; +} + +/** + * dwc2_hsotg_wait_bit_clear - Waits for bit to be clear. + * @hsotg: Programming view of DWC_otg controller. + * @offset: Register's offset where bit/bits must be set. + * @mask: Mask of the bit/bits which must be set. + * @timeout: Timeout to wait. + * + * Return: 0 if bit/bits are set or -ETIMEDOUT in case of timeout. + */ +int dwc2_hsotg_wait_bit_clear(struct dwc2_hsotg *hsotg, u32 offset, u32 mask, + u32 timeout) +{ + u32 i; + + for (i = 0; i < timeout; i++) { + if (!(dwc2_readl(hsotg->regs + offset) & mask)) + return 0; + udelay(1); + } + + return -ETIMEDOUT; +} + MODULE_DESCRIPTION("DESIGNWARE HS OTG Core"); MODULE_AUTHOR("Synopsys, Inc."); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index cd77af3b1565..d83be5651f87 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -217,7 +217,7 @@ struct dwc2_hsotg_ep { unsigned char dir_in; unsigned char index; unsigned char mc; - unsigned char interval; + u16 interval; unsigned int halted:1; unsigned int periodic:1; @@ -408,7 +408,7 @@ enum dwc2_ep0_state { * @ahbcfg: This field allows the default value of the GAHBCFG * register to be overridden * -1 - GAHBCFG value will be set to 0x06 - * (INCR4, default) + * (INCR, default) * all others - GAHBCFG value will be overridden with * this value * Not all bits can be controlled like this, the @@ -421,12 +421,26 @@ enum dwc2_ep0_state { * case. * 0 - No (default) * 1 - Yes - * @hibernation: Specifies whether the controller support hibernation. - * If hibernation is enabled, the controller will enter - * hibernation in both peripheral and host mode when + * @power_down: Specifies whether the controller support power_down. + * If power_down is enabled, the controller will enter + * power_down in both peripheral and host mode when * needed. * 0 - No (default) + * 1 - Partial power down + * 2 - Hibernation + * @lpm: Enable LPM support. + * 0 - No * 1 - Yes + * @lpm_clock_gating: Enable core PHY clock gating. + * 0 - No + * 1 - Yes + * @besl: Enable LPM Errata support. + * 0 - No + * 1 - Yes + * @hird_threshold_en: HIRD or HIRD Threshold enable. + * 0 - No + * 1 - Yes + * @hird_threshold: Value of BESL or HIRD Threshold. * @activate_stm_fs_transceiver: Activate internal transceiver using GGPIO * register. * 0 - Deactivate the transceiver (default) @@ -479,12 +493,23 @@ struct dwc2_core_params { bool enable_dynamic_fifo; bool en_multiple_tx_fifo; bool i2c_enable; + bool acg_enable; bool ulpi_fs_ls; bool ts_dline; bool reload_ctl; bool uframe_sched; bool external_id_pin_ctl; - bool hibernation; + + int power_down; +#define DWC2_POWER_DOWN_PARAM_NONE 0 +#define DWC2_POWER_DOWN_PARAM_PARTIAL 1 +#define DWC2_POWER_DOWN_PARAM_HIBERNATION 2 + + bool lpm; + bool lpm_clock_gating; + bool besl; + bool hird_threshold_en; + u8 hird_threshold; bool activate_stm_fs_transceiver; u16 max_packet_count; u32 max_transfer_size; @@ -560,6 +585,7 @@ struct dwc2_core_params { * 2 - FS pins shared with UTMI+ pins * 3 - FS pins shared with ULPI pins * @total_fifo_size: Total internal RAM for FIFOs (bytes) + * @hibernation Is hibernation enabled? * @utmi_phy_data_width UTMI+ PHY data width * 0 - 8 bits * 1 - 16 bits @@ -587,12 +613,15 @@ struct dwc2_hw_params { unsigned hs_phy_type:2; unsigned fs_phy_type:2; unsigned i2c_enable:1; + unsigned acg_enable:1; unsigned num_dev_ep:4; unsigned num_dev_in_eps : 4; unsigned num_dev_perio_in_ep:4; unsigned total_fifo_size:16; unsigned power_optimized:1; + unsigned hibernation:1; unsigned utmi_phy_data_width:2; + unsigned lpm_mode:1; u32 snpsid; u32 dev_ep_dirs; u32 g_tx_fifo_size[MAX_EPS_CHANNELS]; @@ -611,9 +640,8 @@ struct dwc2_hw_params { * @grxfsiz: Backup of GRXFSIZ register * @gnptxfsiz: Backup of GNPTXFSIZ register * @gi2cctl: Backup of GI2CCTL register - * @hptxfsiz: Backup of HPTXFSIZ register + * @glpmcfg: Backup of GLPMCFG register * @gdfifocfg: Backup of GDFIFOCFG register - * @dtxfsiz: Backup of DTXFSIZ registers for each endpoint * @gpwrdn: Backup of GPWRDN register */ struct dwc2_gregs_backup { @@ -624,10 +652,10 @@ struct dwc2_gregs_backup { u32 grxfsiz; u32 gnptxfsiz; u32 gi2cctl; - u32 hptxfsiz; + u32 glpmcfg; u32 pcgcctl; + u32 pcgcctl1; u32 gdfifocfg; - u32 dtxfsiz[MAX_EPS_CHANNELS]; u32 gpwrdn; bool valid; }; @@ -646,6 +674,7 @@ struct dwc2_gregs_backup { * @doepctl: Backup of DOEPCTL register * @doeptsiz: Backup of DOEPTSIZ register * @doepdma: Backup of DOEPDMA register + * @dtxfsiz: Backup of DTXFSIZ registers for each endpoint */ struct dwc2_dregs_backup { u32 dcfg; @@ -659,6 +688,7 @@ struct dwc2_dregs_backup { u32 doepctl[MAX_EPS_CHANNELS]; u32 doeptsiz[MAX_EPS_CHANNELS]; u32 doepdma[MAX_EPS_CHANNELS]; + u32 dtxfsiz[MAX_EPS_CHANNELS]; bool valid; }; @@ -670,6 +700,7 @@ struct dwc2_dregs_backup { * @hcintmsk: Backup of HCINTMSK register * @hptr0: Backup of HPTR0 register * @hfir: Backup of HFIR register + * @hptxfsiz: Backup of HPTXFSIZ register */ struct dwc2_hregs_backup { u32 hcfg; @@ -677,6 +708,7 @@ struct dwc2_hregs_backup { u32 hcintmsk[MAX_EPS_CHANNELS]; u32 hprt0; u32 hfir; + u32 hptxfsiz; bool valid; }; @@ -780,12 +812,14 @@ struct dwc2_hregs_backup { * @hcd_enabled Host mode sub-driver initialization indicator. * @gadget_enabled Peripheral mode sub-driver initialization indicator. * @ll_hw_enabled Status of low-level hardware resources. + * @hibernated: True if core is hibernated * @phy: The otg phy transceiver structure for phy control. * @uphy: The otg phy transceiver structure for old USB phy * control. * @plat: The platform specific configuration data. This can be * removed once all SoCs support usb transceiver. * @supplies: Definition of USB power supplies + * @vbus_supply: Regulator supplying vbus. * @phyif: PHY interface width * @lock: Spinlock that protects all the driver data structures * @priv: Stores a pointer to the struct usb_hcd @@ -897,6 +931,8 @@ struct dwc2_hregs_backup { * @ctrl_req: Request for EP0 control packets. * @ep0_state: EP0 control transfers state * @test_mode: USB test mode requested by the host + * @remote_wakeup_allowed: True if device is allowed to wake-up host by + * remote-wakeup signalling * @setup_desc_dma: EP0 setup stage desc chain DMA address * @setup_desc: EP0 setup stage desc chain pointer * @ctrl_in_desc_dma: EP0 IN data phase desc chain DMA address @@ -917,11 +953,13 @@ struct dwc2_hsotg { unsigned int hcd_enabled:1; unsigned int gadget_enabled:1; unsigned int ll_hw_enabled:1; + unsigned int hibernated:1; struct phy *phy; struct usb_phy *uphy; struct dwc2_hsotg_plat *plat; struct regulator_bulk_data supplies[DWC2_NUM_SUPPLIES]; + struct regulator *vbus_supply; u32 phyif; spinlock_t lock; @@ -947,6 +985,7 @@ struct dwc2_hsotg { /* DWC OTG HW Release versions */ #define DWC2_CORE_REV_2_71a 0x4f54271a +#define DWC2_CORE_REV_2_80a 0x4f54280a #define DWC2_CORE_REV_2_90a 0x4f54290a #define DWC2_CORE_REV_2_91a 0x4f54291a #define DWC2_CORE_REV_2_92a 0x4f54292a @@ -956,6 +995,11 @@ struct dwc2_hsotg { #define DWC2_FS_IOT_REV_1_00a 0x5531100a #define DWC2_HS_IOT_REV_1_00a 0x5532100a + /* DWC OTG HW Core ID */ +#define DWC2_OTG_ID 0x4f540000 +#define DWC2_FS_IOT_ID 0x55310000 +#define DWC2_HS_IOT_ID 0x55320000 + #if IS_ENABLED(CONFIG_USB_DWC2_HOST) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) union dwc2_hcd_internal_flags { u32 d32; @@ -1016,24 +1060,6 @@ struct dwc2_hsotg { struct kmem_cache *desc_gen_cache; struct kmem_cache *desc_hsisoc_cache; -#ifdef DEBUG - u32 frrem_samples; - u64 frrem_accum; - - u32 hfnum_7_samples_a; - u64 hfnum_7_frrem_accum_a; - u32 hfnum_0_samples_a; - u64 hfnum_0_frrem_accum_a; - u32 hfnum_other_samples_a; - u64 hfnum_other_frrem_accum_a; - - u32 hfnum_7_samples_b; - u64 hfnum_7_frrem_accum_b; - u32 hfnum_0_samples_b; - u64 hfnum_0_frrem_accum_b; - u32 hfnum_other_samples_b; - u64 hfnum_other_frrem_accum_b; -#endif #endif /* CONFIG_USB_DWC2_HOST || CONFIG_USB_DWC2_DUAL_ROLE */ #if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \ @@ -1062,6 +1088,7 @@ struct dwc2_hsotg { struct usb_gadget gadget; unsigned int enabled:1; unsigned int connected:1; + unsigned int remote_wakeup_allowed:1; struct dwc2_hsotg_ep *eps_in[MAX_EPS_CHANNELS]; struct dwc2_hsotg_ep *eps_out[MAX_EPS_CHANNELS]; #endif /* CONFIG_USB_DWC2_PERIPHERAL || CONFIG_USB_DWC2_DUAL_ROLE */ @@ -1106,12 +1133,13 @@ static inline bool dwc2_is_hs_iot(struct dwc2_hsotg *hsotg) * and the DWC_otg controller */ int dwc2_core_reset(struct dwc2_hsotg *hsotg, bool skip_wait); -int dwc2_core_reset_and_force_dr_mode(struct dwc2_hsotg *hsotg); -int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg); -int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, bool restore); +int dwc2_enter_partial_power_down(struct dwc2_hsotg *hsotg); +int dwc2_exit_partial_power_down(struct dwc2_hsotg *hsotg, bool restore); +int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg, int is_host); +int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup, + int reset, int is_host); -bool dwc2_force_mode_if_needed(struct dwc2_hsotg *hsotg, bool host); -void dwc2_clear_force_mode(struct dwc2_hsotg *hsotg); +void dwc2_force_mode(struct dwc2_hsotg *hsotg, bool host); void dwc2_force_dr_mode(struct dwc2_hsotg *hsotg); bool dwc2_is_controller_alive(struct dwc2_hsotg *hsotg); @@ -1128,6 +1156,13 @@ void dwc2_flush_rx_fifo(struct dwc2_hsotg *hsotg); void dwc2_enable_global_interrupts(struct dwc2_hsotg *hcd); void dwc2_disable_global_interrupts(struct dwc2_hsotg *hcd); +void dwc2_hib_restore_common(struct dwc2_hsotg *hsotg, int rem_wakeup, + int is_host); +int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg); +int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg); + +void dwc2_enable_acg(struct dwc2_hsotg *hsotg); + /* This function should be called on every hardware interrupt. */ irqreturn_t dwc2_handle_common_intr(int irq, void *dev); @@ -1137,6 +1172,11 @@ extern const struct of_device_id dwc2_of_match_table[]; int dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg); int dwc2_lowlevel_hw_disable(struct dwc2_hsotg *hsotg); +/* Common polling functions */ +int dwc2_hsotg_wait_bit_set(struct dwc2_hsotg *hs_otg, u32 reg, u32 bit, + u32 timeout); +int dwc2_hsotg_wait_bit_clear(struct dwc2_hsotg *hs_otg, u32 reg, u32 bit, + u32 timeout); /* Parameters */ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg); int dwc2_init_params(struct dwc2_hsotg *hsotg); @@ -1180,7 +1220,7 @@ void dwc2_dump_global_registers(struct dwc2_hsotg *hsotg); int dwc2_hsotg_remove(struct dwc2_hsotg *hsotg); int dwc2_hsotg_suspend(struct dwc2_hsotg *dwc2); int dwc2_hsotg_resume(struct dwc2_hsotg *dwc2); -int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq); +int dwc2_gadget_init(struct dwc2_hsotg *hsotg); void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2, bool reset); void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg); @@ -1188,10 +1228,14 @@ void dwc2_hsotg_disconnect(struct dwc2_hsotg *dwc2); int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, int testmode); #define dwc2_is_device_connected(hsotg) (hsotg->connected) int dwc2_backup_device_registers(struct dwc2_hsotg *hsotg); -int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg); +int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg, int remote_wakeup); +int dwc2_gadget_enter_hibernation(struct dwc2_hsotg *hsotg); +int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg, + int rem_wakeup, int reset); int dwc2_hsotg_tx_fifo_count(struct dwc2_hsotg *hsotg); int dwc2_hsotg_tx_fifo_total_depth(struct dwc2_hsotg *hsotg); int dwc2_hsotg_tx_fifo_average_depth(struct dwc2_hsotg *hsotg); +void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg); #else static inline int dwc2_hsotg_remove(struct dwc2_hsotg *dwc2) { return 0; } @@ -1199,7 +1243,7 @@ static inline int dwc2_hsotg_suspend(struct dwc2_hsotg *dwc2) { return 0; } static inline int dwc2_hsotg_resume(struct dwc2_hsotg *dwc2) { return 0; } -static inline int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) +static inline int dwc2_gadget_init(struct dwc2_hsotg *hsotg) { return 0; } static inline void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2, bool reset) {} @@ -1211,7 +1255,13 @@ static inline int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, #define dwc2_is_device_connected(hsotg) (0) static inline int dwc2_backup_device_registers(struct dwc2_hsotg *hsotg) { return 0; } -static inline int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg) +static inline int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg, + int remote_wakeup) +{ return 0; } +static inline int dwc2_gadget_enter_hibernation(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg, + int rem_wakeup, int reset) { return 0; } static inline int dwc2_hsotg_tx_fifo_count(struct dwc2_hsotg *hsotg) { return 0; } @@ -1219,6 +1269,7 @@ static inline int dwc2_hsotg_tx_fifo_total_depth(struct dwc2_hsotg *hsotg) { return 0; } static inline int dwc2_hsotg_tx_fifo_average_depth(struct dwc2_hsotg *hsotg) { return 0; } +static inline void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg) {} #endif #if IS_ENABLED(CONFIG_USB_DWC2_HOST) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) @@ -1227,8 +1278,12 @@ int dwc2_hcd_get_future_frame_number(struct dwc2_hsotg *hsotg, int us); void dwc2_hcd_connect(struct dwc2_hsotg *hsotg); void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg, bool force); void dwc2_hcd_start(struct dwc2_hsotg *hsotg); +int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup); int dwc2_backup_host_registers(struct dwc2_hsotg *hsotg); int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg); +int dwc2_host_enter_hibernation(struct dwc2_hsotg *hsotg); +int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, + int rem_wakeup, int reset); #else static inline int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg) { return 0; } @@ -1239,12 +1294,19 @@ static inline void dwc2_hcd_connect(struct dwc2_hsotg *hsotg) {} static inline void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg, bool force) {} static inline void dwc2_hcd_start(struct dwc2_hsotg *hsotg) {} static inline void dwc2_hcd_remove(struct dwc2_hsotg *hsotg) {} +static inline int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup) +{ return 0; } static inline int dwc2_hcd_init(struct dwc2_hsotg *hsotg) { return 0; } static inline int dwc2_backup_host_registers(struct dwc2_hsotg *hsotg) { return 0; } static inline int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg) { return 0; } +static inline int dwc2_host_enter_hibernation(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, + int rem_wakeup, int reset) +{ return 0; } #endif diff --git a/drivers/usb/dwc2/core_intr.c b/drivers/usb/dwc2/core_intr.c index ab3fa1630853..2982a155734d 100644 --- a/drivers/usb/dwc2/core_intr.c +++ b/drivers/usb/dwc2/core_intr.c @@ -321,10 +321,10 @@ static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg) if (dwc2_is_device_mode(hsotg)) { if (hsotg->lx_state == DWC2_L2) { - ret = dwc2_exit_hibernation(hsotg, true); + ret = dwc2_exit_partial_power_down(hsotg, true); if (ret && (ret != -ENOTSUPP)) dev_err(hsotg->dev, - "exit hibernation failed\n"); + "exit power_down failed\n"); } /* @@ -335,6 +335,57 @@ static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg) } } +/** + * dwc2_wakeup_from_lpm_l1 - Exit the device from LPM L1 state + * + * @hsotg: Programming view of DWC_otg controller + * + */ +static void dwc2_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg) +{ + u32 glpmcfg; + u32 i = 0; + + if (hsotg->lx_state != DWC2_L1) { + dev_err(hsotg->dev, "Core isn't in DWC2_L1 state\n"); + return; + } + + glpmcfg = dwc2_readl(hsotg->regs + GLPMCFG); + if (dwc2_is_device_mode(hsotg)) { + dev_dbg(hsotg->dev, "Exit from L1 state\n"); + glpmcfg &= ~GLPMCFG_ENBLSLPM; + glpmcfg &= ~GLPMCFG_HIRD_THRES_EN; + dwc2_writel(glpmcfg, hsotg->regs + GLPMCFG); + + do { + glpmcfg = dwc2_readl(hsotg->regs + GLPMCFG); + + if (!(glpmcfg & (GLPMCFG_COREL1RES_MASK | + GLPMCFG_L1RESUMEOK | GLPMCFG_SLPSTS))) + break; + + udelay(1); + } while (++i < 200); + + if (i == 200) { + dev_err(hsotg->dev, "Failed to exit L1 sleep state in 200us.\n"); + return; + } + dwc2_gadget_init_lpm(hsotg); + } else { + /* TODO */ + dev_err(hsotg->dev, "Host side LPM is not supported.\n"); + return; + } + + /* Change to L0 state */ + hsotg->lx_state = DWC2_L0; + + /* Inform gadget to exit from L1 */ + call_gadget(hsotg, resume); +} + /* * This interrupt indicates that the DWC_otg controller has detected a * resume or remote wakeup sequence. If the DWC_otg controller is in @@ -352,6 +403,11 @@ static void dwc2_handle_wakeup_detected_intr(struct dwc2_hsotg *hsotg) dev_dbg(hsotg->dev, "++Resume or Remote Wakeup Detected Interrupt++\n"); dev_dbg(hsotg->dev, "%s lxstate = %d\n", __func__, hsotg->lx_state); + if (hsotg->lx_state == DWC2_L1) { + dwc2_wakeup_from_lpm_l1(hsotg); + return; + } + if (dwc2_is_device_mode(hsotg)) { dev_dbg(hsotg->dev, "DSTS=0x%0x\n", dwc2_readl(hsotg->regs + DSTS)); @@ -361,16 +417,16 @@ static void dwc2_handle_wakeup_detected_intr(struct dwc2_hsotg *hsotg) /* Clear Remote Wakeup Signaling */ dctl &= ~DCTL_RMTWKUPSIG; dwc2_writel(dctl, hsotg->regs + DCTL); - ret = dwc2_exit_hibernation(hsotg, true); + ret = dwc2_exit_partial_power_down(hsotg, true); if (ret && (ret != -ENOTSUPP)) - dev_err(hsotg->dev, "exit hibernation failed\n"); + dev_err(hsotg->dev, "exit power_down failed\n"); call_gadget(hsotg, resume); } /* Change to L0 state */ hsotg->lx_state = DWC2_L0; } else { - if (hsotg->params.hibernation) + if (hsotg->params.power_down) return; if (hsotg->lx_state != DWC2_L1) { @@ -428,32 +484,44 @@ static void dwc2_handle_usb_suspend_intr(struct dwc2_hsotg *hsotg) * state is active */ dsts = dwc2_readl(hsotg->regs + DSTS); - dev_dbg(hsotg->dev, "DSTS=0x%0x\n", dsts); + dev_dbg(hsotg->dev, "%s: DSTS=0x%0x\n", __func__, dsts); dev_dbg(hsotg->dev, - "DSTS.Suspend Status=%d HWCFG4.Power Optimize=%d\n", + "DSTS.Suspend Status=%d HWCFG4.Power Optimize=%d HWCFG4.Hibernation=%d\n", !!(dsts & DSTS_SUSPSTS), - hsotg->hw_params.power_optimized); - if ((dsts & DSTS_SUSPSTS) && hsotg->hw_params.power_optimized) { - /* Ignore suspend request before enumeration */ - if (!dwc2_is_device_connected(hsotg)) { - dev_dbg(hsotg->dev, - "ignore suspend request before enumeration\n"); - return; + hsotg->hw_params.power_optimized, + hsotg->hw_params.hibernation); + + /* Ignore suspend request before enumeration */ + if (!dwc2_is_device_connected(hsotg)) { + dev_dbg(hsotg->dev, + "ignore suspend request before enumeration\n"); + return; + } + if (dsts & DSTS_SUSPSTS) { + if (hsotg->hw_params.power_optimized) { + ret = dwc2_enter_partial_power_down(hsotg); + if (ret) { + if (ret != -ENOTSUPP) + dev_err(hsotg->dev, + "%s: enter partial_power_down failed\n", + __func__); + goto skip_power_saving; + } + + udelay(100); + + /* Ask phy to be suspended */ + if (!IS_ERR_OR_NULL(hsotg->uphy)) + usb_phy_set_suspend(hsotg->uphy, true); } - ret = dwc2_enter_hibernation(hsotg); - if (ret) { - if (ret != -ENOTSUPP) + if (hsotg->hw_params.hibernation) { + ret = dwc2_enter_hibernation(hsotg, 0); + if (ret && ret != -ENOTSUPP) dev_err(hsotg->dev, - "enter hibernation failed\n"); - goto skip_power_saving; + "%s: enter hibernation failed\n", + __func__); } - - udelay(100); - - /* Ask phy to be suspended */ - if (!IS_ERR_OR_NULL(hsotg->uphy)) - usb_phy_set_suspend(hsotg->uphy, true); skip_power_saving: /* * Change to L2 (suspend) state before releasing @@ -479,10 +547,75 @@ skip_power_saving: } } +/** + * dwc2_handle_lpm_intr - GINTSTS_LPMTRANRCVD Interrupt handler + * + * @hsotg: Programming view of DWC_otg controller + * + */ +static void dwc2_handle_lpm_intr(struct dwc2_hsotg *hsotg) +{ + u32 glpmcfg; + u32 pcgcctl; + u32 hird; + u32 hird_thres; + u32 hird_thres_en; + u32 enslpm; + + /* Clear interrupt */ + dwc2_writel(GINTSTS_LPMTRANRCVD, hsotg->regs + GINTSTS); + + glpmcfg = dwc2_readl(hsotg->regs + GLPMCFG); + + if (!(glpmcfg & GLPMCFG_LPMCAP)) { + dev_err(hsotg->dev, "Unexpected LPM interrupt\n"); + return; + } + + hird = (glpmcfg & GLPMCFG_HIRD_MASK) >> GLPMCFG_HIRD_SHIFT; + hird_thres = (glpmcfg & GLPMCFG_HIRD_THRES_MASK & + ~GLPMCFG_HIRD_THRES_EN) >> GLPMCFG_HIRD_THRES_SHIFT; + hird_thres_en = glpmcfg & GLPMCFG_HIRD_THRES_EN; + enslpm = glpmcfg & GLPMCFG_ENBLSLPM; + + if (dwc2_is_device_mode(hsotg)) { + dev_dbg(hsotg->dev, "HIRD_THRES_EN = %d\n", hird_thres_en); + + if (hird_thres_en && hird >= hird_thres) { + dev_dbg(hsotg->dev, "L1 with utmi_l1_suspend_n\n"); + } else if (enslpm) { + dev_dbg(hsotg->dev, "L1 with utmi_sleep_n\n"); + } else { + dev_dbg(hsotg->dev, "Entering Sleep with L1 Gating\n"); + + pcgcctl = dwc2_readl(hsotg->regs + PCGCTL); + pcgcctl |= PCGCTL_ENBL_SLEEP_GATING; + dwc2_writel(pcgcctl, hsotg->regs + PCGCTL); + } + /** + * Examine prt_sleep_sts after TL1TokenTetry period max (10 us) + */ + udelay(10); + + glpmcfg = dwc2_readl(hsotg->regs + GLPMCFG); + + if (glpmcfg & GLPMCFG_SLPSTS) { + /* Save the current state */ + hsotg->lx_state = DWC2_L1; + dev_dbg(hsotg->dev, + "Core is in L1 sleep glpmcfg=%08x\n", glpmcfg); + + /* Inform gadget that we are in L1 state */ + call_gadget(hsotg, suspend); + } + } +} + #define GINTMSK_COMMON (GINTSTS_WKUPINT | GINTSTS_SESSREQINT | \ GINTSTS_CONIDSTSCHNG | GINTSTS_OTGINT | \ GINTSTS_MODEMIS | GINTSTS_DISCONNINT | \ - GINTSTS_USBSUSP | GINTSTS_PRTINT) + GINTSTS_USBSUSP | GINTSTS_PRTINT | \ + GINTSTS_LPMTRANRCVD) /* * This function returns the Core Interrupt register @@ -510,6 +643,116 @@ static u32 dwc2_read_common_intr(struct dwc2_hsotg *hsotg) } /* + * GPWRDN interrupt handler. + * + * The GPWRDN interrupts are those that occur in both Host and + * Device mode while core is in hibernated state. + */ +static void dwc2_handle_gpwrdn_intr(struct dwc2_hsotg *hsotg) +{ + u32 gpwrdn; + int linestate; + + gpwrdn = dwc2_readl(hsotg->regs + GPWRDN); + /* clear all interrupt */ + dwc2_writel(gpwrdn, hsotg->regs + GPWRDN); + linestate = (gpwrdn & GPWRDN_LINESTATE_MASK) >> GPWRDN_LINESTATE_SHIFT; + dev_dbg(hsotg->dev, + "%s: dwc2_handle_gpwrdwn_intr called gpwrdn= %08x\n", __func__, + gpwrdn); + + if ((gpwrdn & GPWRDN_DISCONN_DET) && + (gpwrdn & GPWRDN_DISCONN_DET_MSK) && !linestate) { + u32 gpwrdn_tmp; + + dev_dbg(hsotg->dev, "%s: GPWRDN_DISCONN_DET\n", __func__); + + /* Switch-on voltage to the core */ + gpwrdn_tmp = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn_tmp &= ~GPWRDN_PWRDNSWTCH; + dwc2_writel(gpwrdn_tmp, hsotg->regs + GPWRDN); + udelay(10); + + /* Reset core */ + gpwrdn_tmp = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn_tmp &= ~GPWRDN_PWRDNRSTN; + dwc2_writel(gpwrdn_tmp, hsotg->regs + GPWRDN); + udelay(10); + + /* Disable Power Down Clamp */ + gpwrdn_tmp = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn_tmp &= ~GPWRDN_PWRDNCLMP; + dwc2_writel(gpwrdn_tmp, hsotg->regs + GPWRDN); + udelay(10); + + /* Deassert reset core */ + gpwrdn_tmp = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn_tmp |= GPWRDN_PWRDNRSTN; + dwc2_writel(gpwrdn_tmp, hsotg->regs + GPWRDN); + udelay(10); + + /* Disable PMU interrupt */ + gpwrdn_tmp = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn_tmp &= ~GPWRDN_PMUINTSEL; + dwc2_writel(gpwrdn_tmp, hsotg->regs + GPWRDN); + + /* De-assert Wakeup Logic */ + gpwrdn_tmp = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn_tmp &= ~GPWRDN_PMUACTV; + dwc2_writel(gpwrdn_tmp, hsotg->regs + GPWRDN); + + hsotg->hibernated = 0; + + if (gpwrdn & GPWRDN_IDSTS) { + hsotg->op_state = OTG_STATE_B_PERIPHERAL; + dwc2_core_init(hsotg, false); + dwc2_enable_global_interrupts(hsotg); + dwc2_hsotg_core_init_disconnected(hsotg, false); + dwc2_hsotg_core_connect(hsotg); + } else { + hsotg->op_state = OTG_STATE_A_HOST; + + /* Initialize the Core for Host mode */ + dwc2_core_init(hsotg, false); + dwc2_enable_global_interrupts(hsotg); + dwc2_hcd_start(hsotg); + } + } + + if ((gpwrdn & GPWRDN_LNSTSCHG) && + (gpwrdn & GPWRDN_LNSTSCHG_MSK) && linestate) { + dev_dbg(hsotg->dev, "%s: GPWRDN_LNSTSCHG\n", __func__); + if (hsotg->hw_params.hibernation && + hsotg->hibernated) { + if (gpwrdn & GPWRDN_IDSTS) { + dwc2_exit_hibernation(hsotg, 0, 0, 0); + call_gadget(hsotg, resume); + } else { + dwc2_exit_hibernation(hsotg, 1, 0, 1); + } + } + } + if ((gpwrdn & GPWRDN_RST_DET) && (gpwrdn & GPWRDN_RST_DET_MSK)) { + dev_dbg(hsotg->dev, "%s: GPWRDN_RST_DET\n", __func__); + if (!linestate && (gpwrdn & GPWRDN_BSESSVLD)) + dwc2_exit_hibernation(hsotg, 0, 1, 0); + } + if ((gpwrdn & GPWRDN_STS_CHGINT) && + (gpwrdn & GPWRDN_STS_CHGINT_MSK) && linestate) { + dev_dbg(hsotg->dev, "%s: GPWRDN_STS_CHGINT\n", __func__); + if (hsotg->hw_params.hibernation && + hsotg->hibernated) { + if (gpwrdn & GPWRDN_IDSTS) { + dwc2_exit_hibernation(hsotg, 0, 0, 0); + call_gadget(hsotg, resume); + } else { + dwc2_exit_hibernation(hsotg, 1, 0, 1); + } + } + } +} + +/* * Common interrupt handler * * The common interrupts are those that occur in both Host and Device mode. @@ -539,6 +782,13 @@ irqreturn_t dwc2_handle_common_intr(int irq, void *dev) if (gintsts & ~GINTSTS_PRTINT) retval = IRQ_HANDLED; + /* In case of hibernated state gintsts must not work */ + if (hsotg->hibernated) { + dwc2_handle_gpwrdn_intr(hsotg); + retval = IRQ_HANDLED; + goto out; + } + if (gintsts & GINTSTS_MODEMIS) dwc2_handle_mode_mismatch_intr(hsotg); if (gintsts & GINTSTS_OTGINT) @@ -553,6 +803,8 @@ irqreturn_t dwc2_handle_common_intr(int irq, void *dev) dwc2_handle_wakeup_detected_intr(hsotg); if (gintsts & GINTSTS_USBSUSP) dwc2_handle_usb_suspend_intr(hsotg); + if (gintsts & GINTSTS_LPMTRANRCVD) + dwc2_handle_lpm_intr(hsotg); if (gintsts & GINTSTS_PRTINT) { /* diff --git a/drivers/usb/dwc2/debugfs.c b/drivers/usb/dwc2/debugfs.c index f4650a88be78..58c691f882a8 100644 --- a/drivers/usb/dwc2/debugfs.c +++ b/drivers/usb/dwc2/debugfs.c @@ -170,19 +170,7 @@ static int state_show(struct seq_file *seq, void *v) return 0; } - -static int state_open(struct inode *inode, struct file *file) -{ - return single_open(file, state_show, inode->i_private); -} - -static const struct file_operations state_fops = { - .owner = THIS_MODULE, - .open = state_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(state); /** * fifo_show - debugfs: show the fifo information @@ -219,19 +207,7 @@ static int fifo_show(struct seq_file *seq, void *v) return 0; } - -static int fifo_open(struct inode *inode, struct file *file) -{ - return single_open(file, fifo_show, inode->i_private); -} - -static const struct file_operations fifo_fops = { - .owner = THIS_MODULE, - .open = fifo_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(fifo); static const char *decode_direction(int is_in) { @@ -303,19 +279,7 @@ static int ep_show(struct seq_file *seq, void *v) return 0; } - -static int ep_open(struct inode *inode, struct file *file) -{ - return single_open(file, ep_show, inode->i_private); -} - -static const struct file_operations ep_fops = { - .owner = THIS_MODULE, - .open = ep_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(ep); /** * dwc2_hsotg_create_debug - create debugfs directory and files @@ -754,7 +718,12 @@ static int params_show(struct seq_file *seq, void *v) print_param_hex(seq, p, ahbcfg); print_param(seq, p, uframe_sched); print_param(seq, p, external_id_pin_ctl); - print_param(seq, p, hibernation); + print_param(seq, p, power_down); + print_param(seq, p, lpm); + print_param(seq, p, lpm_clock_gating); + print_param(seq, p, besl); + print_param(seq, p, hird_threshold_en); + print_param(seq, p, hird_threshold); print_param(seq, p, host_dma); print_param(seq, p, g_dma); print_param(seq, p, g_dma_desc); @@ -770,19 +739,7 @@ static int params_show(struct seq_file *seq, void *v) return 0; } - -static int params_open(struct inode *inode, struct file *file) -{ - return single_open(file, params_show, inode->i_private); -} - -static const struct file_operations params_fops = { - .owner = THIS_MODULE, - .open = params_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(params); static int hw_params_show(struct seq_file *seq, void *v) { @@ -817,19 +774,7 @@ static int hw_params_show(struct seq_file *seq, void *v) return 0; } - -static int hw_params_open(struct inode *inode, struct file *file) -{ - return single_open(file, hw_params_show, inode->i_private); -} - -static const struct file_operations hw_params_fops = { - .owner = THIS_MODULE, - .open = hw_params_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(hw_params); static int dr_mode_show(struct seq_file *seq, void *v) { @@ -840,19 +785,7 @@ static int dr_mode_show(struct seq_file *seq, void *v) seq_printf(seq, "%s\n", dr_mode); return 0; } - -static int dr_mode_open(struct inode *inode, struct file *file) -{ - return single_open(file, dr_mode_show, inode->i_private); -} - -static const struct file_operations dr_mode_fops = { - .owner = THIS_MODULE, - .open = dr_mode_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(dr_mode); int dwc2_debugfs_init(struct dwc2_hsotg *hsotg) { diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 5bcad1d869b5..6c32bf26e48e 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -47,12 +47,12 @@ static inline struct dwc2_hsotg *to_hsotg(struct usb_gadget *gadget) return container_of(gadget, struct dwc2_hsotg, gadget); } -static inline void __orr32(void __iomem *ptr, u32 val) +static inline void dwc2_set_bit(void __iomem *ptr, u32 val) { dwc2_writel(dwc2_readl(ptr) | val, ptr); } -static inline void __bic32(void __iomem *ptr, u32 val) +static inline void dwc2_clear_bit(void __iomem *ptr, u32 val) { dwc2_writel(dwc2_readl(ptr) & ~val, ptr); } @@ -116,10 +116,10 @@ static inline void dwc2_gadget_incr_frame_num(struct dwc2_hsotg_ep *hs_ep) { hs_ep->target_frame += hs_ep->interval; if (hs_ep->target_frame > DSTS_SOFFN_LIMIT) { - hs_ep->frame_overrun = 1; + hs_ep->frame_overrun = true; hs_ep->target_frame &= DSTS_SOFFN_LIMIT; } else { - hs_ep->frame_overrun = 0; + hs_ep->frame_overrun = false; } } @@ -252,6 +252,7 @@ static void dwc2_hsotg_init_fifo(struct dwc2_hsotg *hsotg) unsigned int ep; unsigned int addr; int timeout; + u32 val; u32 *txfsz = hsotg->params.g_tx_fifo_size; @@ -1296,8 +1297,8 @@ static int dwc2_hsotg_ep_queue(struct usb_ep *ep, struct usb_request *req, req->zero, req->short_not_ok); /* Prevent new request submission when controller is suspended */ - if (hs->lx_state == DWC2_L2) { - dev_dbg(hs->dev, "%s: don't submit request while suspended\n", + if (hs->lx_state != DWC2_L0) { + dev_dbg(hs->dev, "%s: submit request only in active state\n", __func__); return -EAGAIN; } @@ -1639,6 +1640,10 @@ static int dwc2_hsotg_process_req_feature(struct dwc2_hsotg *hsotg, switch (recip) { case USB_RECIP_DEVICE: switch (wValue) { + case USB_DEVICE_REMOTE_WAKEUP: + hsotg->remote_wakeup_allowed = 1; + break; + case USB_DEVICE_TEST_MODE: if ((wIndex & 0xff) != 0) return -EINVAL; @@ -2495,30 +2500,13 @@ bad_mps: */ static void dwc2_hsotg_txfifo_flush(struct dwc2_hsotg *hsotg, unsigned int idx) { - int timeout; - int val; - dwc2_writel(GRSTCTL_TXFNUM(idx) | GRSTCTL_TXFFLSH, hsotg->regs + GRSTCTL); /* wait until the fifo is flushed */ - timeout = 100; - - while (1) { - val = dwc2_readl(hsotg->regs + GRSTCTL); - - if ((val & (GRSTCTL_TXFFLSH)) == 0) - break; - - if (--timeout == 0) { - dev_err(hsotg->dev, - "%s: timeout flushing fifo (GRSTCTL=%08x)\n", - __func__, val); - break; - } - - udelay(1); - } + if (dwc2_hsotg_wait_bit_clear(hsotg, GRSTCTL, GRSTCTL_TXFFLSH, 100)) + dev_warn(hsotg->dev, "%s: timeout flushing fifo GRSTCTL_TXFFLSH\n", + __func__); } /** @@ -3253,7 +3241,7 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, dwc2_hsotg_init_fifo(hsotg); if (!is_usb_reset) - __orr32(hsotg->regs + DCTL, DCTL_SFTDISCON); + dwc2_set_bit(hsotg->regs + DCTL, DCTL_SFTDISCON); dcfg |= DCFG_EPMISCNT(1); @@ -3282,7 +3270,8 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, GINTSTS_GOUTNAKEFF | GINTSTS_GINNAKEFF | GINTSTS_USBRST | GINTSTS_RESETDET | GINTSTS_ENUMDONE | GINTSTS_OTGINT | - GINTSTS_USBSUSP | GINTSTS_WKUPINT; + GINTSTS_USBSUSP | GINTSTS_WKUPINT | + GINTSTS_LPMTRANRCVD; if (!using_desc_dma(hsotg)) intmsk |= GINTSTS_INCOMPL_SOIN | GINTSTS_INCOMPL_SOOUT; @@ -3294,12 +3283,12 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, if (using_dma(hsotg)) { dwc2_writel(GAHBCFG_GLBL_INTR_EN | GAHBCFG_DMA_EN | - (GAHBCFG_HBSTLEN_INCR4 << GAHBCFG_HBSTLEN_SHIFT), + hsotg->params.ahbcfg, hsotg->regs + GAHBCFG); /* Set DDMA mode support in the core if needed */ if (using_desc_dma(hsotg)) - __orr32(hsotg->regs + DCFG, DCFG_DESCDMA_EN); + dwc2_set_bit(hsotg->regs + DCFG, DCFG_DESCDMA_EN); } else { dwc2_writel(((hsotg->dedicated_fifos) ? @@ -3332,7 +3321,7 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, /* Enable BNA interrupt for DDMA */ if (using_desc_dma(hsotg)) - __orr32(hsotg->regs + DOEPMSK, DOEPMSK_BNAMSK); + dwc2_set_bit(hsotg->regs + DOEPMSK, DOEPMSK_BNAMSK); dwc2_writel(0, hsotg->regs + DAINTMSK); @@ -3356,9 +3345,9 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, dwc2_hsotg_ctrl_epint(hsotg, 0, 1, 1); if (!is_usb_reset) { - __orr32(hsotg->regs + DCTL, DCTL_PWRONPRGDONE); + dwc2_set_bit(hsotg->regs + DCTL, DCTL_PWRONPRGDONE); udelay(10); /* see openiboot */ - __bic32(hsotg->regs + DCTL, DCTL_PWRONPRGDONE); + dwc2_clear_bit(hsotg->regs + DCTL, DCTL_PWRONPRGDONE); } dev_dbg(hsotg->dev, "DCTL=0x%08x\n", dwc2_readl(hsotg->regs + DCTL)); @@ -3385,7 +3374,10 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, val = DCTL_CGOUTNAK | DCTL_CGNPINNAK; if (!is_usb_reset) val |= DCTL_SFTDISCON; - __orr32(hsotg->regs + DCTL, val); + dwc2_set_bit(hsotg->regs + DCTL, val); + + /* configure the core to support LPM */ + dwc2_gadget_init_lpm(hsotg); /* must be at-least 3ms to allow bus to see disconnect */ mdelay(3); @@ -3402,13 +3394,13 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, static void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg) { /* set the soft-disconnect bit */ - __orr32(hsotg->regs + DCTL, DCTL_SFTDISCON); + dwc2_set_bit(hsotg->regs + DCTL, DCTL_SFTDISCON); } void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg) { /* remove the soft-disconnect and let's go */ - __bic32(hsotg->regs + DCTL, DCTL_SFTDISCON); + dwc2_clear_bit(hsotg->regs + DCTL, DCTL_SFTDISCON); } /** @@ -3428,14 +3420,21 @@ static void dwc2_gadget_handle_incomplete_isoc_in(struct dwc2_hsotg *hsotg) { struct dwc2_hsotg_ep *hs_ep; u32 epctrl; + u32 daintmsk; u32 idx; dev_dbg(hsotg->dev, "Incomplete isoc in interrupt received:\n"); + daintmsk = dwc2_readl(hsotg->regs + DAINTMSK); + for (idx = 1; idx <= hsotg->num_of_eps; idx++) { hs_ep = hsotg->eps_in[idx]; + /* Proceed only unmasked ISOC EPs */ + if (!hs_ep->isochronous || (BIT(idx) & ~daintmsk)) + continue; + epctrl = dwc2_readl(hsotg->regs + DIEPCTL(idx)); - if ((epctrl & DXEPCTL_EPENA) && hs_ep->isochronous && + if ((epctrl & DXEPCTL_EPENA) && dwc2_gadget_target_frame_elapsed(hs_ep)) { epctrl |= DXEPCTL_SNAK; epctrl |= DXEPCTL_EPDIS; @@ -3464,16 +3463,24 @@ static void dwc2_gadget_handle_incomplete_isoc_out(struct dwc2_hsotg *hsotg) { u32 gintsts; u32 gintmsk; + u32 daintmsk; u32 epctrl; struct dwc2_hsotg_ep *hs_ep; int idx; dev_dbg(hsotg->dev, "%s: GINTSTS_INCOMPL_SOOUT\n", __func__); + daintmsk = dwc2_readl(hsotg->regs + DAINTMSK); + daintmsk >>= DAINT_OUTEP_SHIFT; + for (idx = 1; idx <= hsotg->num_of_eps; idx++) { hs_ep = hsotg->eps_out[idx]; + /* Proceed only unmasked ISOC EPs */ + if (!hs_ep->isochronous || (BIT(idx) & ~daintmsk)) + continue; + epctrl = dwc2_readl(hsotg->regs + DOEPCTL(idx)); - if ((epctrl & DXEPCTL_EPENA) && hs_ep->isochronous && + if ((epctrl & DXEPCTL_EPENA) && dwc2_gadget_target_frame_elapsed(hs_ep)) { /* Unmask GOUTNAKEFF interrupt */ gintmsk = dwc2_readl(hsotg->regs + GINTMSK); @@ -3481,8 +3488,10 @@ static void dwc2_gadget_handle_incomplete_isoc_out(struct dwc2_hsotg *hsotg) dwc2_writel(gintmsk, hsotg->regs + GINTMSK); gintsts = dwc2_readl(hsotg->regs + GINTSTS); - if (!(gintsts & GINTSTS_GOUTNAKEFF)) - __orr32(hsotg->regs + DCTL, DCTL_SGOUTNAK); + if (!(gintsts & GINTSTS_GOUTNAKEFF)) { + dwc2_set_bit(hsotg->regs + DCTL, DCTL_SGOUTNAK); + break; + } } } @@ -3522,7 +3531,7 @@ irq_retry: /* This event must be used only if controller is suspended */ if (hsotg->lx_state == DWC2_L2) { - dwc2_exit_hibernation(hsotg, true); + dwc2_exit_partial_power_down(hsotg, true); hsotg->lx_state = DWC2_L0; } } @@ -3541,7 +3550,7 @@ irq_retry: dwc2_hsotg_disconnect(hsotg); /* Reset device address to zero */ - __bic32(hsotg->regs + DCFG, DCFG_DEVADDR_MASK); + dwc2_clear_bit(hsotg->regs + DCFG, DCFG_DEVADDR_MASK); if (usb_status & GOTGCTL_BSESVLD && connected) dwc2_hsotg_core_init_disconnected(hsotg, true); @@ -3627,8 +3636,11 @@ irq_retry: u8 idx; u32 epctrl; u32 gintmsk; + u32 daintmsk; struct dwc2_hsotg_ep *hs_ep; + daintmsk = dwc2_readl(hsotg->regs + DAINTMSK); + daintmsk >>= DAINT_OUTEP_SHIFT; /* Mask this interrupt */ gintmsk = dwc2_readl(hsotg->regs + GINTMSK); gintmsk &= ~GINTSTS_GOUTNAKEFF; @@ -3637,9 +3649,13 @@ irq_retry: dev_dbg(hsotg->dev, "GOUTNakEff triggered\n"); for (idx = 1; idx <= hsotg->num_of_eps; idx++) { hs_ep = hsotg->eps_out[idx]; + /* Proceed only unmasked ISOC EPs */ + if (!hs_ep->isochronous || (BIT(idx) & ~daintmsk)) + continue; + epctrl = dwc2_readl(hsotg->regs + DOEPCTL(idx)); - if ((epctrl & DXEPCTL_EPENA) && hs_ep->isochronous) { + if (epctrl & DXEPCTL_EPENA) { epctrl |= DXEPCTL_SNAK; epctrl |= DXEPCTL_EPDIS; dwc2_writel(epctrl, hsotg->regs + DOEPCTL(idx)); @@ -3652,7 +3668,7 @@ irq_retry: if (gintsts & GINTSTS_GINNAKEFF) { dev_info(hsotg->dev, "GINNakEff triggered\n"); - __orr32(hsotg->regs + DCTL, DCTL_CGNPINNAK); + dwc2_set_bit(hsotg->regs + DCTL, DCTL_CGNPINNAK); dwc2_hsotg_dump(hsotg); } @@ -3676,20 +3692,6 @@ irq_retry: return IRQ_HANDLED; } -static int dwc2_hsotg_wait_bit_set(struct dwc2_hsotg *hs_otg, u32 reg, - u32 bit, u32 timeout) -{ - u32 i; - - for (i = 0; i < timeout; i++) { - if (dwc2_readl(hs_otg->regs + reg) & bit) - return 0; - udelay(1); - } - - return -ETIMEDOUT; -} - static void dwc2_hsotg_ep_stop_xfr(struct dwc2_hsotg *hsotg, struct dwc2_hsotg_ep *hs_ep) { @@ -3706,7 +3708,7 @@ static void dwc2_hsotg_ep_stop_xfr(struct dwc2_hsotg *hsotg, if (hs_ep->dir_in) { if (hsotg->dedicated_fifos || hs_ep->periodic) { - __orr32(hsotg->regs + epctrl_reg, DXEPCTL_SNAK); + dwc2_set_bit(hsotg->regs + epctrl_reg, DXEPCTL_SNAK); /* Wait for Nak effect */ if (dwc2_hsotg_wait_bit_set(hsotg, epint_reg, DXEPINT_INEPNAKEFF, 100)) @@ -3714,7 +3716,7 @@ static void dwc2_hsotg_ep_stop_xfr(struct dwc2_hsotg *hsotg, "%s: timeout DIEPINT.NAKEFF\n", __func__); } else { - __orr32(hsotg->regs + DCTL, DCTL_SGNPINNAK); + dwc2_set_bit(hsotg->regs + DCTL, DCTL_SGNPINNAK); /* Wait for Nak effect */ if (dwc2_hsotg_wait_bit_set(hsotg, GINTSTS, GINTSTS_GINNAKEFF, 100)) @@ -3724,7 +3726,7 @@ static void dwc2_hsotg_ep_stop_xfr(struct dwc2_hsotg *hsotg, } } else { if (!(dwc2_readl(hsotg->regs + GINTSTS) & GINTSTS_GOUTNAKEFF)) - __orr32(hsotg->regs + DCTL, DCTL_SGOUTNAK); + dwc2_set_bit(hsotg->regs + DCTL, DCTL_SGOUTNAK); /* Wait for global nak to take effect */ if (dwc2_hsotg_wait_bit_set(hsotg, GINTSTS, @@ -3734,7 +3736,7 @@ static void dwc2_hsotg_ep_stop_xfr(struct dwc2_hsotg *hsotg, } /* Disable ep */ - __orr32(hsotg->regs + epctrl_reg, DXEPCTL_EPDIS | DXEPCTL_SNAK); + dwc2_set_bit(hsotg->regs + epctrl_reg, DXEPCTL_EPDIS | DXEPCTL_SNAK); /* Wait for ep to be disabled */ if (dwc2_hsotg_wait_bit_set(hsotg, epint_reg, DXEPINT_EPDISBLD, 100)) @@ -3742,7 +3744,7 @@ static void dwc2_hsotg_ep_stop_xfr(struct dwc2_hsotg *hsotg, "%s: timeout DOEPCTL.EPDisable\n", __func__); /* Clear EPDISBLD interrupt */ - __orr32(hsotg->regs + epint_reg, DXEPINT_EPDISBLD); + dwc2_set_bit(hsotg->regs + epint_reg, DXEPINT_EPDISBLD); if (hs_ep->dir_in) { unsigned short fifo_index; @@ -3757,11 +3759,11 @@ static void dwc2_hsotg_ep_stop_xfr(struct dwc2_hsotg *hsotg, /* Clear Global In NP NAK in Shared FIFO for non periodic ep */ if (!hsotg->dedicated_fifos && !hs_ep->periodic) - __orr32(hsotg->regs + DCTL, DCTL_CGNPINNAK); + dwc2_set_bit(hsotg->regs + DCTL, DCTL_CGNPINNAK); } else { /* Remove global NAKs */ - __orr32(hsotg->regs + DCTL, DCTL_CGOUTNAK); + dwc2_set_bit(hsotg->regs + DCTL, DCTL_CGOUTNAK); } } @@ -4183,7 +4185,7 @@ static void dwc2_hsotg_init(struct dwc2_hsotg *hsotg) dwc2_writel(0, hsotg->regs + DAINTMSK); /* Be in disconnected state until gadget is registered */ - __orr32(hsotg->regs + DCTL, DCTL_SFTDISCON); + dwc2_set_bit(hsotg->regs + DCTL, DCTL_SFTDISCON); /* setup fifos */ @@ -4205,7 +4207,7 @@ static void dwc2_hsotg_init(struct dwc2_hsotg *hsotg) dwc2_writel(usbcfg, hsotg->regs + GUSBCFG); if (using_dma(hsotg)) - __orr32(hsotg->regs + GAHBCFG, GAHBCFG_DMA_EN); + dwc2_set_bit(hsotg->regs + GAHBCFG, GAHBCFG_DMA_EN); } /** @@ -4352,6 +4354,8 @@ static int dwc2_hsotg_pullup(struct usb_gadget *gadget, int is_on) if (is_on) { hsotg->enabled = 1; dwc2_hsotg_core_init_disconnected(hsotg, false); + /* Enable ACG feature in device mode,if supported */ + dwc2_enable_acg(hsotg); dwc2_hsotg_core_connect(hsotg); } else { dwc2_hsotg_core_disconnect(hsotg); @@ -4374,18 +4378,21 @@ static int dwc2_hsotg_vbus_session(struct usb_gadget *gadget, int is_active) spin_lock_irqsave(&hsotg->lock, flags); /* - * If controller is hibernated, it must exit from hibernation + * If controller is hibernated, it must exit from power_down * before being initialized / de-initialized */ if (hsotg->lx_state == DWC2_L2) - dwc2_exit_hibernation(hsotg, false); + dwc2_exit_partial_power_down(hsotg, false); if (is_active) { hsotg->op_state = OTG_STATE_B_PERIPHERAL; dwc2_hsotg_core_init_disconnected(hsotg, false); - if (hsotg->enabled) + if (hsotg->enabled) { + /* Enable ACG feature in device mode,if supported */ + dwc2_enable_acg(hsotg); dwc2_hsotg_core_connect(hsotg); + } } else { dwc2_hsotg_core_disconnect(hsotg); dwc2_hsotg_disconnect(hsotg); @@ -4606,9 +4613,8 @@ static void dwc2_hsotg_dump(struct dwc2_hsotg *hsotg) /** * dwc2_gadget_init - init function for gadget * @dwc2: The data structure for the DWC2 driver. - * @irq: The IRQ number for the controller. */ -int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) +int dwc2_gadget_init(struct dwc2_hsotg *hsotg) { struct device *dev = hsotg->dev; int epnum; @@ -4622,6 +4628,11 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) hsotg->gadget.max_speed = USB_SPEED_HIGH; hsotg->gadget.ops = &dwc2_hsotg_gadget_ops; hsotg->gadget.name = dev_name(dev); + hsotg->remote_wakeup_allowed = 0; + + if (hsotg->params.lpm) + hsotg->gadget.lpm_capable = true; + if (hsotg->dr_mode == USB_DR_MODE_OTG) hsotg->gadget.is_otg = 1; else if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) @@ -4649,8 +4660,8 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) return ret; } - ret = devm_request_irq(hsotg->dev, irq, dwc2_hsotg_irq, IRQF_SHARED, - dev_name(hsotg->dev), hsotg); + ret = devm_request_irq(hsotg->dev, hsotg->irq, dwc2_hsotg_irq, + IRQF_SHARED, dev_name(hsotg->dev), hsotg); if (ret < 0) { dev_err(dev, "cannot claim IRQ for gadget\n"); return ret; @@ -4751,8 +4762,11 @@ int dwc2_hsotg_resume(struct dwc2_hsotg *hsotg) spin_lock_irqsave(&hsotg->lock, flags); dwc2_hsotg_core_init_disconnected(hsotg, false); - if (hsotg->enabled) + if (hsotg->enabled) { + /* Enable ACG feature in device mode,if supported */ + dwc2_enable_acg(hsotg); dwc2_hsotg_core_connect(hsotg); + } spin_unlock_irqrestore(&hsotg->lock, flags); } @@ -4806,6 +4820,7 @@ int dwc2_backup_device_registers(struct dwc2_hsotg *hsotg) dr->doeptsiz[i] = dwc2_readl(hsotg->regs + DOEPTSIZ(i)); dr->doepdma[i] = dwc2_readl(hsotg->regs + DOEPDMA(i)); + dr->dtxfsiz[i] = dwc2_readl(hsotg->regs + DPTXFSIZN(i)); } dr->valid = true; return 0; @@ -4817,11 +4832,13 @@ int dwc2_backup_device_registers(struct dwc2_hsotg *hsotg) * if controller power were disabled. * * @hsotg: Programming view of the DWC_otg controller + * @remote_wakeup: Indicates whether resume is initiated by Device or Host. + * + * Return: 0 if successful, negative error code otherwise */ -int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg) +int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg, int remote_wakeup) { struct dwc2_dregs_backup *dr; - u32 dctl; int i; dev_dbg(hsotg->dev, "%s\n", __func__); @@ -4835,28 +4852,240 @@ int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg) } dr->valid = false; - dwc2_writel(dr->dcfg, hsotg->regs + DCFG); - dwc2_writel(dr->dctl, hsotg->regs + DCTL); + if (!remote_wakeup) + dwc2_writel(dr->dctl, hsotg->regs + DCTL); + dwc2_writel(dr->daintmsk, hsotg->regs + DAINTMSK); dwc2_writel(dr->diepmsk, hsotg->regs + DIEPMSK); dwc2_writel(dr->doepmsk, hsotg->regs + DOEPMSK); for (i = 0; i < hsotg->num_of_eps; i++) { /* Restore IN EPs */ - dwc2_writel(dr->diepctl[i], hsotg->regs + DIEPCTL(i)); dwc2_writel(dr->dieptsiz[i], hsotg->regs + DIEPTSIZ(i)); dwc2_writel(dr->diepdma[i], hsotg->regs + DIEPDMA(i)); - + dwc2_writel(dr->doeptsiz[i], hsotg->regs + DOEPTSIZ(i)); + /** WA for enabled EPx's IN in DDMA mode. On entering to + * hibernation wrong value read and saved from DIEPDMAx, + * as result BNA interrupt asserted on hibernation exit + * by restoring from saved area. + */ + if (hsotg->params.g_dma_desc && + (dr->diepctl[i] & DXEPCTL_EPENA)) + dr->diepdma[i] = hsotg->eps_in[i]->desc_list_dma; + dwc2_writel(dr->dtxfsiz[i], hsotg->regs + DPTXFSIZN(i)); + dwc2_writel(dr->diepctl[i], hsotg->regs + DIEPCTL(i)); /* Restore OUT EPs */ - dwc2_writel(dr->doepctl[i], hsotg->regs + DOEPCTL(i)); dwc2_writel(dr->doeptsiz[i], hsotg->regs + DOEPTSIZ(i)); + /* WA for enabled EPx's OUT in DDMA mode. On entering to + * hibernation wrong value read and saved from DOEPDMAx, + * as result BNA interrupt asserted on hibernation exit + * by restoring from saved area. + */ + if (hsotg->params.g_dma_desc && + (dr->doepctl[i] & DXEPCTL_EPENA)) + dr->doepdma[i] = hsotg->eps_out[i]->desc_list_dma; dwc2_writel(dr->doepdma[i], hsotg->regs + DOEPDMA(i)); + dwc2_writel(dr->doepctl[i], hsotg->regs + DOEPCTL(i)); } - /* Set the Power-On Programming done bit */ - dctl = dwc2_readl(hsotg->regs + DCTL); - dctl |= DCTL_PWRONPRGDONE; - dwc2_writel(dctl, hsotg->regs + DCTL); - return 0; } + +/** + * dwc2_gadget_init_lpm - Configure the core to support LPM in device mode + * + * @hsotg: Programming view of DWC_otg controller + * + */ +void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg) +{ + u32 val; + + if (!hsotg->params.lpm) + return; + + val = GLPMCFG_LPMCAP | GLPMCFG_APPL1RES; + val |= hsotg->params.hird_threshold_en ? GLPMCFG_HIRD_THRES_EN : 0; + val |= hsotg->params.lpm_clock_gating ? GLPMCFG_ENBLSLPM : 0; + val |= hsotg->params.hird_threshold << GLPMCFG_HIRD_THRES_SHIFT; + val |= hsotg->params.besl ? GLPMCFG_ENBESL : 0; + dwc2_writel(val, hsotg->regs + GLPMCFG); + dev_dbg(hsotg->dev, "GLPMCFG=0x%08x\n", dwc2_readl(hsotg->regs + + GLPMCFG)); +} + +/** + * dwc2_gadget_enter_hibernation() - Put controller in Hibernation. + * + * @hsotg: Programming view of the DWC_otg controller + * + * Return non-zero if failed to enter to hibernation. + */ +int dwc2_gadget_enter_hibernation(struct dwc2_hsotg *hsotg) +{ + u32 gpwrdn; + int ret = 0; + + /* Change to L2(suspend) state */ + hsotg->lx_state = DWC2_L2; + dev_dbg(hsotg->dev, "Start of hibernation completed\n"); + ret = dwc2_backup_global_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to backup global registers\n", + __func__); + return ret; + } + ret = dwc2_backup_device_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to backup device registers\n", + __func__); + return ret; + } + + gpwrdn = GPWRDN_PWRDNRSTN; + gpwrdn |= GPWRDN_PMUACTV; + dwc2_writel(gpwrdn, hsotg->regs + GPWRDN); + udelay(10); + + /* Set flag to indicate that we are in hibernation */ + hsotg->hibernated = 1; + + /* Enable interrupts from wake up logic */ + gpwrdn = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn |= GPWRDN_PMUINTSEL; + dwc2_writel(gpwrdn, hsotg->regs + GPWRDN); + udelay(10); + + /* Unmask device mode interrupts in GPWRDN */ + gpwrdn = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn |= GPWRDN_RST_DET_MSK; + gpwrdn |= GPWRDN_LNSTSCHG_MSK; + gpwrdn |= GPWRDN_STS_CHGINT_MSK; + dwc2_writel(gpwrdn, hsotg->regs + GPWRDN); + udelay(10); + + /* Enable Power Down Clamp */ + gpwrdn = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn |= GPWRDN_PWRDNCLMP; + dwc2_writel(gpwrdn, hsotg->regs + GPWRDN); + udelay(10); + + /* Switch off VDD */ + gpwrdn = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn |= GPWRDN_PWRDNSWTCH; + dwc2_writel(gpwrdn, hsotg->regs + GPWRDN); + udelay(10); + + /* Save gpwrdn register for further usage if stschng interrupt */ + hsotg->gr_backup.gpwrdn = dwc2_readl(hsotg->regs + GPWRDN); + dev_dbg(hsotg->dev, "Hibernation completed\n"); + + return ret; +} + +/** + * dwc2_gadget_exit_hibernation() + * This function is for exiting from Device mode hibernation by host initiated + * resume/reset and device initiated remote-wakeup. + * + * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: indicates whether resume is initiated by Device or Host. + * @param reset: indicates whether resume is initiated by Reset. + * + * Return non-zero if failed to exit from hibernation. + */ +int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg, + int rem_wakeup, int reset) +{ + u32 pcgcctl; + u32 gpwrdn; + u32 dctl; + int ret = 0; + struct dwc2_gregs_backup *gr; + struct dwc2_dregs_backup *dr; + + gr = &hsotg->gr_backup; + dr = &hsotg->dr_backup; + + if (!hsotg->hibernated) { + dev_dbg(hsotg->dev, "Already exited from Hibernation\n"); + return 1; + } + dev_dbg(hsotg->dev, + "%s: called with rem_wakeup = %d reset = %d\n", + __func__, rem_wakeup, reset); + + dwc2_hib_restore_common(hsotg, rem_wakeup, 0); + + if (!reset) { + /* Clear all pending interupts */ + dwc2_writel(0xffffffff, hsotg->regs + GINTSTS); + } + + /* De-assert Restore */ + gpwrdn = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn &= ~GPWRDN_RESTORE; + dwc2_writel(gpwrdn, hsotg->regs + GPWRDN); + udelay(10); + + if (!rem_wakeup) { + pcgcctl = dwc2_readl(hsotg->regs + PCGCTL); + pcgcctl &= ~PCGCTL_RSTPDWNMODULE; + dwc2_writel(pcgcctl, hsotg->regs + PCGCTL); + } + + /* Restore GUSBCFG, DCFG and DCTL */ + dwc2_writel(gr->gusbcfg, hsotg->regs + GUSBCFG); + dwc2_writel(dr->dcfg, hsotg->regs + DCFG); + dwc2_writel(dr->dctl, hsotg->regs + DCTL); + + /* De-assert Wakeup Logic */ + gpwrdn = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn &= ~GPWRDN_PMUACTV; + dwc2_writel(gpwrdn, hsotg->regs + GPWRDN); + + if (rem_wakeup) { + udelay(10); + /* Start Remote Wakeup Signaling */ + dwc2_writel(dr->dctl | DCTL_RMTWKUPSIG, hsotg->regs + DCTL); + } else { + udelay(50); + /* Set Device programming done bit */ + dctl = dwc2_readl(hsotg->regs + DCTL); + dctl |= DCTL_PWRONPRGDONE; + dwc2_writel(dctl, hsotg->regs + DCTL); + } + /* Wait for interrupts which must be cleared */ + mdelay(2); + /* Clear all pending interupts */ + dwc2_writel(0xffffffff, hsotg->regs + GINTSTS); + + /* Restore global registers */ + ret = dwc2_restore_global_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to restore registers\n", + __func__); + return ret; + } + + /* Restore device registers */ + ret = dwc2_restore_device_registers(hsotg, rem_wakeup); + if (ret) { + dev_err(hsotg->dev, "%s: failed to restore device registers\n", + __func__); + return ret; + } + + if (rem_wakeup) { + mdelay(10); + dctl = dwc2_readl(hsotg->regs + DCTL); + dctl &= ~DCTL_RMTWKUPSIG; + dwc2_writel(dctl, hsotg->regs + DCTL); + } + + hsotg->hibernated = 0; + hsotg->lx_state = DWC2_L0; + dev_dbg(hsotg->dev, "Hibernation recovery completes here\n"); + + return ret; +} diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index a5d72fcd1603..190f95964000 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -91,6 +91,9 @@ static void dwc2_enable_common_interrupts(struct dwc2_hsotg *hsotg) intmsk |= GINTSTS_WKUPINT | GINTSTS_USBSUSP | GINTSTS_SESSREQINT; + if (dwc2_is_device_mode(hsotg) && hsotg->params.lpm) + intmsk |= GINTSTS_LPMTRANRCVD; + dwc2_writel(intmsk, hsotg->regs + GINTMSK); } @@ -138,7 +141,7 @@ static int dwc2_fs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) dwc2_writel(usbcfg, hsotg->regs + GUSBCFG); /* Reset after a PHY select */ - retval = dwc2_core_reset_and_force_dr_mode(hsotg); + retval = dwc2_core_reset(hsotg, false); if (retval) { dev_err(hsotg->dev, @@ -236,7 +239,7 @@ static int dwc2_hs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) dwc2_writel(usbcfg, hsotg->regs + GUSBCFG); /* Reset after setting the PHY parameters */ - retval = dwc2_core_reset_and_force_dr_mode(hsotg); + retval = dwc2_core_reset(hsotg, false); if (retval) { dev_err(hsotg->dev, "%s: Reset failed, aborting", __func__); @@ -308,22 +311,10 @@ static int dwc2_gahbcfg_init(struct dwc2_hsotg *hsotg) break; } - dev_dbg(hsotg->dev, "host_dma:%d dma_desc_enable:%d\n", - hsotg->params.host_dma, - hsotg->params.dma_desc_enable); - - if (hsotg->params.host_dma) { - if (hsotg->params.dma_desc_enable) - dev_dbg(hsotg->dev, "Using Descriptor DMA mode\n"); - else - dev_dbg(hsotg->dev, "Using Buffer DMA mode\n"); - } else { - dev_dbg(hsotg->dev, "Using Slave mode\n"); - hsotg->params.dma_desc_enable = false; - } - if (hsotg->params.host_dma) ahbcfg |= GAHBCFG_DMA_EN; + else + hsotg->params.dma_desc_enable = false; dwc2_writel(ahbcfg, hsotg->regs + GAHBCFG); @@ -365,6 +356,23 @@ static void dwc2_gusbcfg_init(struct dwc2_hsotg *hsotg) dwc2_writel(usbcfg, hsotg->regs + GUSBCFG); } +static int dwc2_vbus_supply_init(struct dwc2_hsotg *hsotg) +{ + hsotg->vbus_supply = devm_regulator_get_optional(hsotg->dev, "vbus"); + if (IS_ERR(hsotg->vbus_supply)) + return 0; + + return regulator_enable(hsotg->vbus_supply); +} + +static int dwc2_vbus_supply_exit(struct dwc2_hsotg *hsotg) +{ + if (hsotg->vbus_supply) + return regulator_disable(hsotg->vbus_supply); + + return 0; +} + /** * dwc2_enable_host_interrupts() - Enables the Host mode interrupts * @@ -989,6 +997,24 @@ void dwc2_hc_halt(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan, if (dbg_hc(chan)) dev_vdbg(hsotg->dev, "%s()\n", __func__); + + /* + * In buffer DMA or external DMA mode channel can't be halted + * for non-split periodic channels. At the end of the next + * uframe/frame (in the worst case), the core generates a channel + * halted and disables the channel automatically. + */ + if ((hsotg->params.g_dma && !hsotg->params.g_dma_desc) || + hsotg->hw_params.arch == GHWCFG2_EXT_DMA_ARCH) { + if (!chan->do_split && + (chan->ep_type == USB_ENDPOINT_XFER_ISOC || + chan->ep_type == USB_ENDPOINT_XFER_INT)) { + dev_err(hsotg->dev, "%s() Channel can't be halted\n", + __func__); + return; + } + } + if (halt_status == DWC2_HC_XFER_NO_HALT_STATUS) dev_err(hsotg->dev, "!!! halt_status = %d !!!\n", halt_status); @@ -2232,7 +2258,7 @@ static int dwc2_hcd_endpoint_reset(struct dwc2_hsotg *hsotg, * @hsotg: Programming view of the DWC_otg controller * @initial_setup: If true then this is the first init for this instance. */ -static int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup) +int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup) { u32 usbcfg, otgctl; int retval; @@ -2261,7 +2287,7 @@ static int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup) * needed to in order to properly detect various parameters). */ if (!initial_setup) { - retval = dwc2_core_reset_and_force_dr_mode(hsotg); + retval = dwc2_core_reset(hsotg, false); if (retval) { dev_err(hsotg->dev, "%s(): Reset failed, aborting\n", __func__); @@ -2322,10 +2348,22 @@ static int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup) */ static void dwc2_core_host_init(struct dwc2_hsotg *hsotg) { - u32 hcfg, hfir, otgctl; + u32 hcfg, hfir, otgctl, usbcfg; dev_dbg(hsotg->dev, "%s(%p)\n", __func__, hsotg); + /* Set HS/FS Timeout Calibration to 7 (max available value). + * The number of PHY clocks that the application programs in + * this field is added to the high/full speed interpacket timeout + * duration in the core to account for any additional delays + * introduced by the PHY. This can be required, because the delay + * introduced by the PHY in generating the linestate condition + * can vary from one PHY to another. + */ + usbcfg = dwc2_readl(hsotg->regs + GUSBCFG); + usbcfg |= GUSBCFG_TOUTCAL(7); + dwc2_writel(usbcfg, hsotg->regs + GUSBCFG); + /* Restart the Phy Clock */ dwc2_writel(0, hsotg->regs + PCGCTL); @@ -2403,27 +2441,24 @@ static void dwc2_core_host_init(struct dwc2_hsotg *hsotg) /* Halt all channels to put them into a known state */ for (i = 0; i < num_channels; i++) { - int count = 0; - hcchar = dwc2_readl(hsotg->regs + HCCHAR(i)); hcchar |= HCCHAR_CHENA | HCCHAR_CHDIS; hcchar &= ~HCCHAR_EPDIR; dwc2_writel(hcchar, hsotg->regs + HCCHAR(i)); dev_dbg(hsotg->dev, "%s: Halt channel %d\n", __func__, i); - do { - hcchar = dwc2_readl(hsotg->regs + HCCHAR(i)); - if (++count > 1000) { - dev_err(hsotg->dev, - "Unable to clear enable on channel %d\n", - i); - break; - } - udelay(1); - } while (hcchar & HCCHAR_CHENA); + + if (dwc2_hsotg_wait_bit_clear(hsotg, HCCHAR(i), + HCCHAR_CHENA, 1000)) { + dev_warn(hsotg->dev, "Unable to clear enable on channel %d\n", + i); + } } } + /* Enable ACG feature in host mode, if supported */ + dwc2_enable_acg(hsotg); + /* Turn on the vbus power */ dev_dbg(hsotg->dev, "Init: Port Power? op_state=%d\n", hsotg->op_state); if (hsotg->op_state == OTG_STATE_A_HOST) { @@ -3257,6 +3292,7 @@ static void dwc2_conn_id_status_change(struct work_struct *work) /* B-Device connector (Device Mode) */ if (gotgctl & GOTGCTL_CONID_B) { + dwc2_vbus_supply_exit(hsotg); /* Wait for switch to device mode */ dev_dbg(hsotg->dev, "connId B\n"); if (hsotg->bus_suspended) { @@ -3290,6 +3326,8 @@ static void dwc2_conn_id_status_change(struct work_struct *work) spin_lock_irqsave(&hsotg->lock, flags); dwc2_hsotg_core_init_disconnected(hsotg, false); spin_unlock_irqrestore(&hsotg->lock, flags); + /* Enable ACG feature in device mode,if supported */ + dwc2_enable_acg(hsotg); dwc2_hsotg_core_connect(hsotg); } else { host: @@ -3377,10 +3415,10 @@ static void dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex) hsotg->bus_suspended = true; /* - * If hibernation is supported, Phy clock will be suspended + * If power_down is supported, Phy clock will be suspended * after registers are backuped. */ - if (!hsotg->params.hibernation) { + if (!hsotg->params.power_down) { /* Suspend the Phy Clock */ pcgctl = dwc2_readl(hsotg->regs + PCGCTL); pcgctl |= PCGCTL_STOPPCLK; @@ -3412,10 +3450,10 @@ static void dwc2_port_resume(struct dwc2_hsotg *hsotg) spin_lock_irqsave(&hsotg->lock, flags); /* - * If hibernation is supported, Phy clock is already resumed + * If power_down is supported, Phy clock is already resumed * after registers restore. */ - if (!hsotg->params.hibernation) { + if (!hsotg->params.power_down) { pcgctl = dwc2_readl(hsotg->regs + PCGCTL); pcgctl &= ~PCGCTL_STOPPCLK; dwc2_writel(pcgctl, hsotg->regs + PCGCTL); @@ -3486,8 +3524,12 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, dev_dbg(hsotg->dev, "ClearPortFeature USB_PORT_FEAT_SUSPEND\n"); - if (hsotg->bus_suspended) - dwc2_port_resume(hsotg); + if (hsotg->bus_suspended) { + if (hsotg->hibernated) + dwc2_exit_hibernation(hsotg, 0, 0, 1); + else + dwc2_port_resume(hsotg); + } break; case USB_PORT_FEAT_POWER: @@ -3695,7 +3737,10 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, "SetPortFeature - USB_PORT_FEAT_SUSPEND\n"); if (windex != hsotg->otg_port) goto error; - dwc2_port_suspend(hsotg, windex); + if (hsotg->params.power_down == 2) + dwc2_enter_hibernation(hsotg, 1); + else + dwc2_port_suspend(hsotg, windex); break; case USB_PORT_FEAT_POWER: @@ -3707,6 +3752,9 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, break; case USB_PORT_FEAT_RESET: + if (hsotg->params.power_down == 2 && + hsotg->hibernated) + dwc2_exit_hibernation(hsotg, 0, 1, 1); hprt0 = dwc2_read_hprt0(hsotg); dev_dbg(hsotg->dev, "SetPortFeature - USB_PORT_FEAT_RESET\n"); @@ -4002,7 +4050,6 @@ void dwc2_hcd_dump_state(struct dwc2_hsotg *hsotg) (p_tx_status & TXSTS_QSPCAVAIL_MASK) >> TXSTS_QSPCAVAIL_SHIFT); dev_dbg(hsotg->dev, " P Tx FIFO Space Avail: %d\n", (p_tx_status & TXSTS_FSPCAVAIL_MASK) >> TXSTS_FSPCAVAIL_SHIFT); - dwc2_hcd_dump_frrem(hsotg); dwc2_dump_global_registers(hsotg); dwc2_dump_host_registers(hsotg); dev_dbg(hsotg->dev, @@ -4011,75 +4058,6 @@ void dwc2_hcd_dump_state(struct dwc2_hsotg *hsotg) #endif } -/* - * NOTE: This function will be removed once the peripheral controller code - * is integrated and the driver is stable - */ -void dwc2_hcd_dump_frrem(struct dwc2_hsotg *hsotg) -{ -#ifdef DWC2_DUMP_FRREM - dev_dbg(hsotg->dev, "Frame remaining at SOF:\n"); - dev_dbg(hsotg->dev, " samples %u, accum %llu, avg %llu\n", - hsotg->frrem_samples, hsotg->frrem_accum, - hsotg->frrem_samples > 0 ? - hsotg->frrem_accum / hsotg->frrem_samples : 0); - dev_dbg(hsotg->dev, "\n"); - dev_dbg(hsotg->dev, "Frame remaining at start_transfer (uframe 7):\n"); - dev_dbg(hsotg->dev, " samples %u, accum %llu, avg %llu\n", - hsotg->hfnum_7_samples, - hsotg->hfnum_7_frrem_accum, - hsotg->hfnum_7_samples > 0 ? - hsotg->hfnum_7_frrem_accum / hsotg->hfnum_7_samples : 0); - dev_dbg(hsotg->dev, "Frame remaining at start_transfer (uframe 0):\n"); - dev_dbg(hsotg->dev, " samples %u, accum %llu, avg %llu\n", - hsotg->hfnum_0_samples, - hsotg->hfnum_0_frrem_accum, - hsotg->hfnum_0_samples > 0 ? - hsotg->hfnum_0_frrem_accum / hsotg->hfnum_0_samples : 0); - dev_dbg(hsotg->dev, "Frame remaining at start_transfer (uframe 1-6):\n"); - dev_dbg(hsotg->dev, " samples %u, accum %llu, avg %llu\n", - hsotg->hfnum_other_samples, - hsotg->hfnum_other_frrem_accum, - hsotg->hfnum_other_samples > 0 ? - hsotg->hfnum_other_frrem_accum / hsotg->hfnum_other_samples : - 0); - dev_dbg(hsotg->dev, "\n"); - dev_dbg(hsotg->dev, "Frame remaining at sample point A (uframe 7):\n"); - dev_dbg(hsotg->dev, " samples %u, accum %llu, avg %llu\n", - hsotg->hfnum_7_samples_a, hsotg->hfnum_7_frrem_accum_a, - hsotg->hfnum_7_samples_a > 0 ? - hsotg->hfnum_7_frrem_accum_a / hsotg->hfnum_7_samples_a : 0); - dev_dbg(hsotg->dev, "Frame remaining at sample point A (uframe 0):\n"); - dev_dbg(hsotg->dev, " samples %u, accum %llu, avg %llu\n", - hsotg->hfnum_0_samples_a, hsotg->hfnum_0_frrem_accum_a, - hsotg->hfnum_0_samples_a > 0 ? - hsotg->hfnum_0_frrem_accum_a / hsotg->hfnum_0_samples_a : 0); - dev_dbg(hsotg->dev, "Frame remaining at sample point A (uframe 1-6):\n"); - dev_dbg(hsotg->dev, " samples %u, accum %llu, avg %llu\n", - hsotg->hfnum_other_samples_a, hsotg->hfnum_other_frrem_accum_a, - hsotg->hfnum_other_samples_a > 0 ? - hsotg->hfnum_other_frrem_accum_a / hsotg->hfnum_other_samples_a - : 0); - dev_dbg(hsotg->dev, "\n"); - dev_dbg(hsotg->dev, "Frame remaining at sample point B (uframe 7):\n"); - dev_dbg(hsotg->dev, " samples %u, accum %llu, avg %llu\n", - hsotg->hfnum_7_samples_b, hsotg->hfnum_7_frrem_accum_b, - hsotg->hfnum_7_samples_b > 0 ? - hsotg->hfnum_7_frrem_accum_b / hsotg->hfnum_7_samples_b : 0); - dev_dbg(hsotg->dev, "Frame remaining at sample point B (uframe 0):\n"); - dev_dbg(hsotg->dev, " samples %u, accum %llu, avg %llu\n", - hsotg->hfnum_0_samples_b, hsotg->hfnum_0_frrem_accum_b, - (hsotg->hfnum_0_samples_b > 0) ? - hsotg->hfnum_0_frrem_accum_b / hsotg->hfnum_0_samples_b : 0); - dev_dbg(hsotg->dev, "Frame remaining at sample point B (uframe 1-6):\n"); - dev_dbg(hsotg->dev, " samples %u, accum %llu, avg %llu\n", - hsotg->hfnum_other_samples_b, hsotg->hfnum_other_frrem_accum_b, - (hsotg->hfnum_other_samples_b > 0) ? - hsotg->hfnum_other_frrem_accum_b / hsotg->hfnum_other_samples_b - : 0); -#endif -} - struct wrapper_priv_data { struct dwc2_hsotg *hsotg; }; @@ -4363,6 +4341,9 @@ static int _dwc2_hcd_start(struct usb_hcd *hcd) } spin_unlock_irqrestore(&hsotg->lock, flags); + + dwc2_vbus_supply_init(hsotg); + return 0; } @@ -4390,6 +4371,8 @@ static void _dwc2_hcd_stop(struct usb_hcd *hcd) clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); spin_unlock_irqrestore(&hsotg->lock, flags); + dwc2_vbus_supply_exit(hsotg); + usleep_range(1000, 3000); } @@ -4414,7 +4397,7 @@ static int _dwc2_hcd_suspend(struct usb_hcd *hcd) if (hsotg->op_state == OTG_STATE_B_PERIPHERAL) goto unlock; - if (!hsotg->params.hibernation) + if (hsotg->params.power_down != DWC2_POWER_DOWN_PARAM_PARTIAL) goto skip_power_saving; /* @@ -4426,14 +4409,15 @@ static int _dwc2_hcd_suspend(struct usb_hcd *hcd) hprt0 |= HPRT0_SUSP; hprt0 &= ~HPRT0_PWR; dwc2_writel(hprt0, hsotg->regs + HPRT0); + dwc2_vbus_supply_exit(hsotg); } - /* Enter hibernation */ - ret = dwc2_enter_hibernation(hsotg); + /* Enter partial_power_down */ + ret = dwc2_enter_partial_power_down(hsotg); if (ret) { if (ret != -ENOTSUPP) dev_err(hsotg->dev, - "enter hibernation failed\n"); + "enter partial_power_down failed\n"); goto skip_power_saving; } @@ -4444,7 +4428,7 @@ static int _dwc2_hcd_suspend(struct usb_hcd *hcd) spin_lock_irqsave(&hsotg->lock, flags); } - /* After entering hibernation, hardware is no more accessible */ + /* After entering partial_power_down, hardware is no more accessible */ clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); skip_power_saving: @@ -4469,7 +4453,7 @@ static int _dwc2_hcd_resume(struct usb_hcd *hcd) if (hsotg->lx_state != DWC2_L2) goto unlock; - if (!hsotg->params.hibernation) { + if (hsotg->params.power_down != DWC2_POWER_DOWN_PARAM_PARTIAL) { hsotg->lx_state = DWC2_L0; goto unlock; } @@ -4491,10 +4475,10 @@ static int _dwc2_hcd_resume(struct usb_hcd *hcd) spin_lock_irqsave(&hsotg->lock, flags); } - /* Exit hibernation */ - ret = dwc2_exit_hibernation(hsotg, true); + /* Exit partial_power_down */ + ret = dwc2_exit_partial_power_down(hsotg, true); if (ret && (ret != -ENOTSUPP)) - dev_err(hsotg->dev, "exit hibernation failed\n"); + dev_err(hsotg->dev, "exit partial_power_down failed\n"); hsotg->lx_state = DWC2_L0; @@ -4506,6 +4490,8 @@ static int _dwc2_hcd_resume(struct usb_hcd *hcd) spin_unlock_irqrestore(&hsotg->lock, flags); dwc2_port_resume(hsotg); } else { + dwc2_vbus_supply_init(hsotg); + /* Wait for controller to correctly update D+/D- level */ usleep_range(3000, 5000); @@ -5368,6 +5354,7 @@ int dwc2_backup_host_registers(struct dwc2_hsotg *hsotg) hr->hprt0 = dwc2_read_hprt0(hsotg); hr->hfir = dwc2_readl(hsotg->regs + HFIR); + hr->hptxfsiz = dwc2_readl(hsotg->regs + HPTXFSIZ); hr->valid = true; return 0; @@ -5404,7 +5391,231 @@ int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg) dwc2_writel(hr->hprt0, hsotg->regs + HPRT0); dwc2_writel(hr->hfir, hsotg->regs + HFIR); + dwc2_writel(hr->hptxfsiz, hsotg->regs + HPTXFSIZ); hsotg->frame_number = 0; return 0; } + +/** + * dwc2_host_enter_hibernation() - Put controller in Hibernation. + * + * @hsotg: Programming view of the DWC_otg controller + */ +int dwc2_host_enter_hibernation(struct dwc2_hsotg *hsotg) +{ + unsigned long flags; + int ret = 0; + u32 hprt0; + u32 pcgcctl; + u32 gusbcfg; + u32 gpwrdn; + + dev_dbg(hsotg->dev, "Preparing host for hibernation\n"); + ret = dwc2_backup_global_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to backup global registers\n", + __func__); + return ret; + } + ret = dwc2_backup_host_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to backup host registers\n", + __func__); + return ret; + } + + /* Enter USB Suspend Mode */ + hprt0 = dwc2_readl(hsotg->regs + HPRT0); + hprt0 |= HPRT0_SUSP; + hprt0 &= ~HPRT0_ENA; + dwc2_writel(hprt0, hsotg->regs + HPRT0); + + /* Wait for the HPRT0.PrtSusp register field to be set */ + if (dwc2_hsotg_wait_bit_set(hsotg, HPRT0, HPRT0_SUSP, 300)) + dev_warn(hsotg->dev, "Suspend wasn't generated\n"); + + /* + * We need to disable interrupts to prevent servicing of any IRQ + * during going to hibernation + */ + spin_lock_irqsave(&hsotg->lock, flags); + hsotg->lx_state = DWC2_L2; + + gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); + if (gusbcfg & GUSBCFG_ULPI_UTMI_SEL) { + /* ULPI interface */ + /* Suspend the Phy Clock */ + pcgcctl = dwc2_readl(hsotg->regs + PCGCTL); + pcgcctl |= PCGCTL_STOPPCLK; + dwc2_writel(pcgcctl, hsotg->regs + PCGCTL); + udelay(10); + + gpwrdn = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn |= GPWRDN_PMUACTV; + dwc2_writel(gpwrdn, hsotg->regs + GPWRDN); + udelay(10); + } else { + /* UTMI+ Interface */ + gpwrdn = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn |= GPWRDN_PMUACTV; + dwc2_writel(gpwrdn, hsotg->regs + GPWRDN); + udelay(10); + + pcgcctl = dwc2_readl(hsotg->regs + PCGCTL); + pcgcctl |= PCGCTL_STOPPCLK; + dwc2_writel(pcgcctl, hsotg->regs + PCGCTL); + udelay(10); + } + + /* Enable interrupts from wake up logic */ + gpwrdn = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn |= GPWRDN_PMUINTSEL; + dwc2_writel(gpwrdn, hsotg->regs + GPWRDN); + udelay(10); + + /* Unmask host mode interrupts in GPWRDN */ + gpwrdn = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn |= GPWRDN_DISCONN_DET_MSK; + gpwrdn |= GPWRDN_LNSTSCHG_MSK; + gpwrdn |= GPWRDN_STS_CHGINT_MSK; + dwc2_writel(gpwrdn, hsotg->regs + GPWRDN); + udelay(10); + + /* Enable Power Down Clamp */ + gpwrdn = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn |= GPWRDN_PWRDNCLMP; + dwc2_writel(gpwrdn, hsotg->regs + GPWRDN); + udelay(10); + + /* Switch off VDD */ + gpwrdn = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn |= GPWRDN_PWRDNSWTCH; + dwc2_writel(gpwrdn, hsotg->regs + GPWRDN); + + hsotg->hibernated = 1; + hsotg->bus_suspended = 1; + dev_dbg(hsotg->dev, "Host hibernation completed\n"); + spin_unlock_irqrestore(&hsotg->lock, flags); + return ret; +} + +/* + * dwc2_host_exit_hibernation() + * + * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: indicates whether resume is initiated by Device or Host. + * @param reset: indicates whether resume is initiated by Reset. + * + * Return: non-zero if failed to enter to hibernation. + * + * This function is for exiting from Host mode hibernation by + * Host Initiated Resume/Reset and Device Initiated Remote-Wakeup. + */ +int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup, + int reset) +{ + u32 gpwrdn; + u32 hprt0; + int ret = 0; + struct dwc2_gregs_backup *gr; + struct dwc2_hregs_backup *hr; + + gr = &hsotg->gr_backup; + hr = &hsotg->hr_backup; + + dev_dbg(hsotg->dev, + "%s: called with rem_wakeup = %d reset = %d\n", + __func__, rem_wakeup, reset); + + dwc2_hib_restore_common(hsotg, rem_wakeup, 1); + hsotg->hibernated = 0; + + /* + * This step is not described in functional spec but if not wait for + * this delay, mismatch interrupts occurred because just after restore + * core is in Device mode(gintsts.curmode == 0) + */ + mdelay(100); + + /* Clear all pending interupts */ + dwc2_writel(0xffffffff, hsotg->regs + GINTSTS); + + /* De-assert Restore */ + gpwrdn = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn &= ~GPWRDN_RESTORE; + dwc2_writel(gpwrdn, hsotg->regs + GPWRDN); + udelay(10); + + /* Restore GUSBCFG, HCFG */ + dwc2_writel(gr->gusbcfg, hsotg->regs + GUSBCFG); + dwc2_writel(hr->hcfg, hsotg->regs + HCFG); + + /* De-assert Wakeup Logic */ + gpwrdn = dwc2_readl(hsotg->regs + GPWRDN); + gpwrdn &= ~GPWRDN_PMUACTV; + dwc2_writel(gpwrdn, hsotg->regs + GPWRDN); + udelay(10); + + hprt0 = hr->hprt0; + hprt0 |= HPRT0_PWR; + hprt0 &= ~HPRT0_ENA; + hprt0 &= ~HPRT0_SUSP; + dwc2_writel(hprt0, hsotg->regs + HPRT0); + + hprt0 = hr->hprt0; + hprt0 |= HPRT0_PWR; + hprt0 &= ~HPRT0_ENA; + hprt0 &= ~HPRT0_SUSP; + + if (reset) { + hprt0 |= HPRT0_RST; + dwc2_writel(hprt0, hsotg->regs + HPRT0); + + /* Wait for Resume time and then program HPRT again */ + mdelay(60); + hprt0 &= ~HPRT0_RST; + dwc2_writel(hprt0, hsotg->regs + HPRT0); + } else { + hprt0 |= HPRT0_RES; + dwc2_writel(hprt0, hsotg->regs + HPRT0); + + /* Wait for Resume time and then program HPRT again */ + mdelay(100); + hprt0 &= ~HPRT0_RES; + dwc2_writel(hprt0, hsotg->regs + HPRT0); + } + /* Clear all interrupt status */ + hprt0 = dwc2_readl(hsotg->regs + HPRT0); + hprt0 |= HPRT0_CONNDET; + hprt0 |= HPRT0_ENACHG; + hprt0 &= ~HPRT0_ENA; + dwc2_writel(hprt0, hsotg->regs + HPRT0); + + hprt0 = dwc2_readl(hsotg->regs + HPRT0); + + /* Clear all pending interupts */ + dwc2_writel(0xffffffff, hsotg->regs + GINTSTS); + + /* Restore global registers */ + ret = dwc2_restore_global_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to restore registers\n", + __func__); + return ret; + } + + /* Restore host registers */ + ret = dwc2_restore_host_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to restore host registers\n", + __func__); + return ret; + } + + hsotg->hibernated = 0; + hsotg->bus_suspended = 0; + hsotg->lx_state = DWC2_L0; + dev_dbg(hsotg->dev, "Host hibernation restore complete\n"); + return ret; +} diff --git a/drivers/usb/dwc2/hcd.h b/drivers/usb/dwc2/hcd.h index ad60e46e66e1..96a9da5fb202 100644 --- a/drivers/usb/dwc2/hcd.h +++ b/drivers/usb/dwc2/hcd.h @@ -783,19 +783,6 @@ int dwc2_hcd_is_b_host(struct dwc2_hsotg *hsotg); */ void dwc2_hcd_dump_state(struct dwc2_hsotg *hsotg); -/** - * dwc2_hcd_dump_frrem() - Dumps the average frame remaining at SOF - * - * @hsotg: The DWC2 HCD - * - * This can be used to determine average interrupt latency. Frame remaining is - * also shown for start transfer and two additional sample points. - * - * NOTE: This function will be removed once the peripheral controller code - * is integrated and the driver is stable - */ -void dwc2_hcd_dump_frrem(struct dwc2_hsotg *hsotg); - /* URB interface */ /* Transfer flags */ @@ -813,47 +800,4 @@ int dwc2_host_get_speed(struct dwc2_hsotg *hsotg, void *context); void dwc2_host_complete(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd, int status); -#ifdef DEBUG -/* - * Macro to sample the remaining PHY clocks left in the current frame. This - * may be used during debugging to determine the average time it takes to - * execute sections of code. There are two possible sample points, "a" and - * "b", so the _letter_ argument must be one of these values. - * - * To dump the average sample times, read the "hcd_frrem" sysfs attribute. For - * example, "cat /sys/devices/lm0/hcd_frrem". - */ -#define dwc2_sample_frrem(_hcd_, _qh_, _letter_) \ -do { \ - struct hfnum_data _hfnum_; \ - struct dwc2_qtd *_qtd_; \ - \ - _qtd_ = list_entry((_qh_)->qtd_list.next, struct dwc2_qtd, \ - qtd_list_entry); \ - if (usb_pipeint(_qtd_->urb->pipe) && \ - (_qh_)->start_active_frame != 0 && !_qtd_->complete_split) { \ - _hfnum_.d32 = dwc2_readl((_hcd_)->regs + HFNUM); \ - switch (_hfnum_.b.frnum & 0x7) { \ - case 7: \ - (_hcd_)->hfnum_7_samples_##_letter_++; \ - (_hcd_)->hfnum_7_frrem_accum_##_letter_ += \ - _hfnum_.b.frrem; \ - break; \ - case 0: \ - (_hcd_)->hfnum_0_samples_##_letter_++; \ - (_hcd_)->hfnum_0_frrem_accum_##_letter_ += \ - _hfnum_.b.frrem; \ - break; \ - default: \ - (_hcd_)->hfnum_other_samples_##_letter_++; \ - (_hcd_)->hfnum_other_frrem_accum_##_letter_ += \ - _hfnum_.b.frrem; \ - break; \ - } \ - } \ -} while (0) -#else -#define dwc2_sample_frrem(_hcd_, _qh_, _letter_) do {} while (0) -#endif - #endif /* __DWC2_HCD_H__ */ diff --git a/drivers/usb/dwc2/hw.h b/drivers/usb/dwc2/hw.h index 2c906d8ee465..38391e48351f 100644 --- a/drivers/usb/dwc2/hw.h +++ b/drivers/usb/dwc2/hw.h @@ -231,6 +231,7 @@ #define GUID HSOTG_REG(0x003c) #define GSNPSID HSOTG_REG(0x0040) #define GHWCFG1 HSOTG_REG(0x0044) +#define GSNPSID_ID_MASK GENMASK(31, 16) #define GHWCFG2 HSOTG_REG(0x0048) #define GHWCFG2_OTG_ENABLE_IC_USB BIT(31) @@ -309,6 +310,7 @@ #define GHWCFG4_NUM_DEV_MODE_CTRL_EP_SHIFT 16 #define GHWCFG4_UTMI_PHY_DATA_WIDTH_MASK (0x3 << 14) #define GHWCFG4_UTMI_PHY_DATA_WIDTH_SHIFT 14 +#define GHWCFG4_ACG_SUPPORTED BIT(12) #define GHWCFG4_UTMI_PHY_DATA_WIDTH_8 0 #define GHWCFG4_UTMI_PHY_DATA_WIDTH_16 1 #define GHWCFG4_UTMI_PHY_DATA_WIDTH_8_OR_16 2 @@ -320,28 +322,30 @@ #define GHWCFG4_NUM_DEV_PERIO_IN_EP_SHIFT 0 #define GLPMCFG HSOTG_REG(0x0054) -#define GLPMCFG_INV_SEL_HSIC BIT(31) -#define GLPMCFG_HSIC_CONNECT BIT(30) -#define GLPMCFG_RETRY_COUNT_STS_MASK (0x7 << 25) -#define GLPMCFG_RETRY_COUNT_STS_SHIFT 25 -#define GLPMCFG_SEND_LPM BIT(24) -#define GLPMCFG_RETRY_COUNT_MASK (0x7 << 21) -#define GLPMCFG_RETRY_COUNT_SHIFT 21 -#define GLPMCFG_LPM_CHAN_INDEX_MASK (0xf << 17) -#define GLPMCFG_LPM_CHAN_INDEX_SHIFT 17 -#define GLPMCFG_SLEEP_STATE_RESUMEOK BIT(16) -#define GLPMCFG_PRT_SLEEP_STS BIT(15) -#define GLPMCFG_LPM_RESP_MASK (0x3 << 13) -#define GLPMCFG_LPM_RESP_SHIFT 13 +#define GLPMCFG_INVSELHSIC BIT(31) +#define GLPMCFG_HSICCON BIT(30) +#define GLPMCFG_RSTRSLPSTS BIT(29) +#define GLPMCFG_ENBESL BIT(28) +#define GLPMCFG_LPM_RETRYCNT_STS_MASK (0x7 << 25) +#define GLPMCFG_LPM_RETRYCNT_STS_SHIFT 25 +#define GLPMCFG_SNDLPM BIT(24) +#define GLPMCFG_RETRY_CNT_MASK (0x7 << 21) +#define GLPMCFG_RETRY_CNT_SHIFT 21 +#define GLPMCFG_LPM_CHNL_INDX_MASK (0xf << 17) +#define GLPMCFG_LPM_CHNL_INDX_SHIFT 17 +#define GLPMCFG_L1RESUMEOK BIT(16) +#define GLPMCFG_SLPSTS BIT(15) +#define GLPMCFG_COREL1RES_MASK (0x3 << 13) +#define GLPMCFG_COREL1RES_SHIFT 13 #define GLPMCFG_HIRD_THRES_MASK (0x1f << 8) #define GLPMCFG_HIRD_THRES_SHIFT 8 -#define GLPMCFG_HIRD_THRES_EN (0x10 << 8) -#define GLPMCFG_EN_UTMI_SLEEP BIT(7) -#define GLPMCFG_REM_WKUP_EN BIT(6) +#define GLPMCFG_HIRD_THRES_EN (0x10 << 8) +#define GLPMCFG_ENBLSLPM BIT(7) +#define GLPMCFG_BREMOTEWAKE BIT(6) #define GLPMCFG_HIRD_MASK (0xf << 2) #define GLPMCFG_HIRD_SHIFT 2 -#define GLPMCFG_APPL_RESP BIT(1) -#define GLPMCFG_LPM_CAP_EN BIT(0) +#define GLPMCFG_APPL1RES BIT(1) +#define GLPMCFG_LPMCAP BIT(0) #define GPWRDN HSOTG_REG(0x0058) #define GPWRDN_MULT_VAL_ID_BC_MASK (0x1f << 24) @@ -644,6 +648,10 @@ #define PCGCTL_GATEHCLK BIT(1) #define PCGCTL_STOPPCLK BIT(0) +#define PCGCCTL1 HSOTG_REG(0xe04) +#define PCGCCTL1_TIMER (0x3 << 1) +#define PCGCCTL1_GATEEN BIT(0) + #define EPFIFO(_a) HSOTG_REG(0x1000 + ((_a) * 0x1000)) /* Host Mode Registers */ diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c index c4a47496d2fb..f03e41879224 100644 --- a/drivers/usb/dwc2/params.c +++ b/drivers/usb/dwc2/params.c @@ -252,6 +252,20 @@ static void dwc2_set_param_tx_fifo_sizes(struct dwc2_hsotg *hsotg) p->g_tx_fifo_size[i] = depth_average; } +static void dwc2_set_param_power_down(struct dwc2_hsotg *hsotg) +{ + int val; + + if (hsotg->hw_params.hibernation) + val = 2; + else if (hsotg->hw_params.power_optimized) + val = 1; + else + val = 0; + + hsotg->params.power_down = val; +} + /** * dwc2_set_default_params() - Set all core parameters to their * auto-detected default values. @@ -266,21 +280,27 @@ static void dwc2_set_default_params(struct dwc2_hsotg *hsotg) dwc2_set_param_phy_type(hsotg); dwc2_set_param_speed(hsotg); dwc2_set_param_phy_utmi_width(hsotg); + dwc2_set_param_power_down(hsotg); p->phy_ulpi_ddr = false; p->phy_ulpi_ext_vbus = false; p->enable_dynamic_fifo = hw->enable_dynamic_fifo; p->en_multiple_tx_fifo = hw->en_multiple_tx_fifo; p->i2c_enable = hw->i2c_enable; + p->acg_enable = hw->acg_enable; p->ulpi_fs_ls = false; p->ts_dline = false; p->reload_ctl = (hw->snpsid >= DWC2_CORE_REV_2_92a); p->uframe_sched = true; p->external_id_pin_ctl = false; - p->hibernation = false; + p->lpm = true; + p->lpm_clock_gating = true; + p->besl = true; + p->hird_threshold_en = true; + p->hird_threshold = 4; p->max_packet_count = hw->max_packet_count; p->max_transfer_size = hw->max_transfer_size; - p->ahbcfg = GAHBCFG_HBSTLEN_INCR4 << GAHBCFG_HBSTLEN_SHIFT; + p->ahbcfg = GAHBCFG_HBSTLEN_INCR << GAHBCFG_HBSTLEN_SHIFT; if ((hsotg->dr_mode == USB_DR_MODE_HOST) || (hsotg->dr_mode == USB_DR_MODE_OTG)) { @@ -463,6 +483,38 @@ static void dwc2_check_param_phy_utmi_width(struct dwc2_hsotg *hsotg) dwc2_set_param_phy_utmi_width(hsotg); } +static void dwc2_check_param_power_down(struct dwc2_hsotg *hsotg) +{ + int param = hsotg->params.power_down; + + switch (param) { + case DWC2_POWER_DOWN_PARAM_NONE: + break; + case DWC2_POWER_DOWN_PARAM_PARTIAL: + if (hsotg->hw_params.power_optimized) + break; + dev_dbg(hsotg->dev, + "Partial power down isn't supported by HW\n"); + param = DWC2_POWER_DOWN_PARAM_NONE; + break; + case DWC2_POWER_DOWN_PARAM_HIBERNATION: + if (hsotg->hw_params.hibernation) + break; + dev_dbg(hsotg->dev, + "Hibernation isn't supported by HW\n"); + param = DWC2_POWER_DOWN_PARAM_NONE; + break; + default: + dev_err(hsotg->dev, + "%s: Invalid parameter power_down=%d\n", + __func__, param); + param = DWC2_POWER_DOWN_PARAM_NONE; + break; + } + + hsotg->params.power_down = param; +} + static void dwc2_check_param_tx_fifo_sizes(struct dwc2_hsotg *hsotg) { int fifo_count; @@ -523,10 +575,19 @@ static void dwc2_check_params(struct dwc2_hsotg *hsotg) dwc2_check_param_phy_type(hsotg); dwc2_check_param_speed(hsotg); dwc2_check_param_phy_utmi_width(hsotg); + dwc2_check_param_power_down(hsotg); CHECK_BOOL(enable_dynamic_fifo, hw->enable_dynamic_fifo); CHECK_BOOL(en_multiple_tx_fifo, hw->en_multiple_tx_fifo); CHECK_BOOL(i2c_enable, hw->i2c_enable); + CHECK_BOOL(acg_enable, hw->acg_enable); CHECK_BOOL(reload_ctl, (hsotg->hw_params.snpsid > DWC2_CORE_REV_2_92a)); + CHECK_BOOL(lpm, (hsotg->hw_params.snpsid >= DWC2_CORE_REV_2_80a)); + CHECK_BOOL(lpm, hw->lpm_mode); + CHECK_BOOL(lpm_clock_gating, hsotg->params.lpm); + CHECK_BOOL(besl, hsotg->params.lpm); + CHECK_BOOL(besl, (hsotg->hw_params.snpsid >= DWC2_CORE_REV_3_00a)); + CHECK_BOOL(hird_threshold_en, hsotg->params.lpm); + CHECK_RANGE(hird_threshold, 0, hsotg->params.besl ? 12 : 7, 0); CHECK_RANGE(max_packet_count, 15, hw->max_packet_count, hw->max_packet_count); @@ -579,19 +640,15 @@ static void dwc2_get_host_hwparams(struct dwc2_hsotg *hsotg) struct dwc2_hw_params *hw = &hsotg->hw_params; u32 gnptxfsiz; u32 hptxfsiz; - bool forced; if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) return; - forced = dwc2_force_mode_if_needed(hsotg, true); + dwc2_force_mode(hsotg, true); gnptxfsiz = dwc2_readl(hsotg->regs + GNPTXFSIZ); hptxfsiz = dwc2_readl(hsotg->regs + HPTXFSIZ); - if (forced) - dwc2_clear_force_mode(hsotg); - hw->host_nperio_tx_fifo_size = (gnptxfsiz & FIFOSIZE_DEPTH_MASK) >> FIFOSIZE_DEPTH_SHIFT; hw->host_perio_tx_fifo_size = (hptxfsiz & FIFOSIZE_DEPTH_MASK) >> @@ -606,14 +663,13 @@ static void dwc2_get_host_hwparams(struct dwc2_hsotg *hsotg) static void dwc2_get_dev_hwparams(struct dwc2_hsotg *hsotg) { struct dwc2_hw_params *hw = &hsotg->hw_params; - bool forced; u32 gnptxfsiz; int fifo, fifo_count; if (hsotg->dr_mode == USB_DR_MODE_HOST) return; - forced = dwc2_force_mode_if_needed(hsotg, false); + dwc2_force_mode(hsotg, false); gnptxfsiz = dwc2_readl(hsotg->regs + GNPTXFSIZ); @@ -625,9 +681,6 @@ static void dwc2_get_dev_hwparams(struct dwc2_hsotg *hsotg) FIFOSIZE_DEPTH_MASK) >> FIFOSIZE_DEPTH_SHIFT; } - if (forced) - dwc2_clear_force_mode(hsotg); - hw->dev_nperio_tx_fifo_size = (gnptxfsiz & FIFOSIZE_DEPTH_MASK) >> FIFOSIZE_DEPTH_SHIFT; } @@ -646,14 +699,13 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) /* * Attempt to ensure this device is really a DWC_otg Controller. * Read and verify the GSNPSID register contents. The value should be - * 0x45f42xxx or 0x45f43xxx, which corresponds to either "OT2" or "OT3", - * as in "OTG version 2.xx" or "OTG version 3.xx". + * 0x45f4xxxx, 0x5531xxxx or 0x5532xxxx */ + hw->snpsid = dwc2_readl(hsotg->regs + GSNPSID); - if ((hw->snpsid & 0xfffff000) != 0x4f542000 && - (hw->snpsid & 0xfffff000) != 0x4f543000 && - (hw->snpsid & 0xffff0000) != 0x55310000 && - (hw->snpsid & 0xffff0000) != 0x55320000) { + if ((hw->snpsid & GSNPSID_ID_MASK) != DWC2_OTG_ID && + (hw->snpsid & GSNPSID_ID_MASK) != DWC2_FS_IOT_ID && + (hw->snpsid & GSNPSID_ID_MASK) != DWC2_HS_IOT_ID) { dev_err(hsotg->dev, "Bad value for GSNPSID: 0x%08x\n", hw->snpsid); return -ENODEV; @@ -706,6 +758,7 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) hw->i2c_enable = !!(hwcfg3 & GHWCFG3_I2C); hw->total_fifo_size = (hwcfg3 & GHWCFG3_DFIFO_DEPTH_MASK) >> GHWCFG3_DFIFO_DEPTH_SHIFT; + hw->lpm_mode = !!(hwcfg3 & GHWCFG3_OTG_LPM_EN); /* hwcfg4 */ hw->en_multiple_tx_fifo = !!(hwcfg4 & GHWCFG4_DED_FIFO_EN); @@ -715,8 +768,10 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) GHWCFG4_NUM_IN_EPS_SHIFT; hw->dma_desc_enable = !!(hwcfg4 & GHWCFG4_DESC_DMA); hw->power_optimized = !!(hwcfg4 & GHWCFG4_POWER_OPTIMIZ); + hw->hibernation = !!(hwcfg4 & GHWCFG4_HIBER); hw->utmi_phy_data_width = (hwcfg4 & GHWCFG4_UTMI_PHY_DATA_WIDTH_MASK) >> GHWCFG4_UTMI_PHY_DATA_WIDTH_SHIFT; + hw->acg_enable = !!(hwcfg4 & GHWCFG4_ACG_SUPPORTED); /* fifo sizes */ hw->rx_fifo_size = (grxfsiz & GRXFSIZ_DEPTH_MASK) >> diff --git a/drivers/usb/dwc2/pci.c b/drivers/usb/dwc2/pci.c index 3ecc951a1aea..7f21747007f1 100644 --- a/drivers/usb/dwc2/pci.c +++ b/drivers/usb/dwc2/pci.c @@ -83,7 +83,6 @@ static void dwc2_pci_remove(struct pci_dev *pci) platform_device_unregister(glue->dwc2); usb_phy_generic_unregister(glue->phy); - kfree(glue); pci_set_drvdata(pci, NULL); } @@ -105,10 +104,17 @@ static int dwc2_pci_probe(struct pci_dev *pci, pci_set_master(pci); + phy = usb_phy_generic_register(); + if (IS_ERR(phy)) { + dev_err(dev, "error registering generic PHY (%ld)\n", + PTR_ERR(phy)); + return PTR_ERR(phy); + } + dwc2 = platform_device_alloc("dwc2", PLATFORM_DEVID_AUTO); if (!dwc2) { dev_err(dev, "couldn't allocate dwc2 device\n"); - return -ENOMEM; + goto err; } memset(res, 0x00, sizeof(struct resource) * ARRAY_SIZE(res)); @@ -125,32 +131,25 @@ static int dwc2_pci_probe(struct pci_dev *pci, ret = platform_device_add_resources(dwc2, res, ARRAY_SIZE(res)); if (ret) { dev_err(dev, "couldn't add resources to dwc2 device\n"); - return ret; + goto err; } dwc2->dev.parent = dev; - phy = usb_phy_generic_register(); - if (IS_ERR(phy)) { - dev_err(dev, "error registering generic PHY (%ld)\n", - PTR_ERR(phy)); - return PTR_ERR(phy); - } - ret = dwc2_pci_quirks(pci, dwc2); if (ret) goto err; + glue = devm_kzalloc(dev, sizeof(*glue), GFP_KERNEL); + if (!glue) + goto err; + ret = platform_device_add(dwc2); if (ret) { dev_err(dev, "failed to register dwc2 device\n"); goto err; } - glue = kzalloc(sizeof(*glue), GFP_KERNEL); - if (!glue) - return -ENOMEM; - glue->phy = phy; glue->dwc2 = dwc2; pci_set_drvdata(pci, glue); diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index 4703478f702f..4c0819554bcd 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -382,8 +382,10 @@ static int dwc2_driver_probe(struct platform_device *dev) if (!dev->dev.dma_mask) dev->dev.dma_mask = &dev->dev.coherent_dma_mask; retval = dma_set_coherent_mask(&dev->dev, DMA_BIT_MASK(32)); - if (retval) + if (retval) { + dev_err(&dev->dev, "can't set coherent DMA mask: %d\n", retval); return retval; + } res = platform_get_resource(dev, IORESOURCE_MEM, 0); hsotg->regs = devm_ioremap_resource(&dev->dev, res); @@ -425,13 +427,20 @@ static int dwc2_driver_probe(struct platform_device *dev) * Reset before dwc2_get_hwparams() then it could get power-on real * reset value form registers. */ - dwc2_core_reset_and_force_dr_mode(hsotg); + retval = dwc2_core_reset(hsotg, false); + if (retval) + goto error; /* Detect config values from hardware */ retval = dwc2_get_hwparams(hsotg); if (retval) goto error; + /* + * For OTG cores, set the force mode bits to reflect the value + * of dr_mode. Force mode bits should not be touched at any + * other time after this. + */ dwc2_force_dr_mode(hsotg); retval = dwc2_init_params(hsotg); @@ -439,7 +448,7 @@ static int dwc2_driver_probe(struct platform_device *dev) goto error; if (hsotg->dr_mode != USB_DR_MODE_HOST) { - retval = dwc2_gadget_init(hsotg, hsotg->irq); + retval = dwc2_gadget_init(hsotg); if (retval) goto error; hsotg->gadget_enabled = 1; @@ -456,6 +465,7 @@ static int dwc2_driver_probe(struct platform_device *dev) } platform_set_drvdata(dev, hsotg); + hsotg->hibernated = 0; dwc2_debugfs_init(hsotg); diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index 7ac725038f8d..025bc68094fc 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -6,7 +6,7 @@ obj-$(CONFIG_USB_DWC3) += dwc3.o dwc3-y := core.o -ifneq ($(CONFIG_FTRACE),) +ifneq ($(CONFIG_TRACING),) dwc3-y += trace.o endif diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index e94bf91cc58a..a15648d25e30 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -89,10 +89,7 @@ static int dwc3_get_dr_mode(struct dwc3 *dwc) return 0; } -static void dwc3_event_buffers_cleanup(struct dwc3 *dwc); -static int dwc3_event_buffers_setup(struct dwc3 *dwc); - -static void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode) +void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode) { u32 reg; @@ -110,13 +107,19 @@ static void __dwc3_set_mode(struct work_struct *work) unsigned long flags; int ret; + if (dwc->dr_mode != USB_DR_MODE_OTG) + return; + + if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_OTG) + dwc3_otg_update(dwc, 0); + if (!dwc->desired_dr_role) return; if (dwc->desired_dr_role == dwc->current_dr_role) return; - if (dwc->dr_mode != USB_DR_MODE_OTG) + if (dwc->desired_dr_role == DWC3_GCTL_PRTCAP_OTG && dwc->edev) return; switch (dwc->current_dr_role) { @@ -127,6 +130,13 @@ static void __dwc3_set_mode(struct work_struct *work) dwc3_gadget_exit(dwc); dwc3_event_buffers_cleanup(dwc); break; + case DWC3_GCTL_PRTCAP_OTG: + dwc3_otg_exit(dwc); + spin_lock_irqsave(&dwc->lock, flags); + dwc->desired_otg_role = DWC3_OTG_ROLE_IDLE; + spin_unlock_irqrestore(&dwc->lock, flags); + dwc3_otg_update(dwc, 1); + break; default: break; } @@ -162,9 +172,14 @@ static void __dwc3_set_mode(struct work_struct *work) if (ret) dev_err(dwc->dev, "failed to initialize peripheral\n"); break; + case DWC3_GCTL_PRTCAP_OTG: + dwc3_otg_init(dwc); + dwc3_otg_update(dwc, 0); + break; default: break; } + } void dwc3_set_mode(struct dwc3 *dwc, u32 mode) @@ -229,7 +244,7 @@ static int dwc3_core_soft_reset(struct dwc3 *dwc) do { reg = dwc3_readl(dwc->regs, DWC3_DCTL); if (!(reg & DWC3_DCTL_CSFTRST)) - return 0; + goto done; udelay(1); } while (--retries); @@ -238,6 +253,17 @@ static int dwc3_core_soft_reset(struct dwc3 *dwc) phy_exit(dwc->usb2_generic_phy); return -ETIMEDOUT; + +done: + /* + * For DWC_usb31 controller, once DWC3_DCTL_CSFTRST bit is cleared, + * we must wait at least 50ms before accessing the PHY domain + * (synchronization delay). DWC_usb31 programming guide section 1.3.2. + */ + if (dwc3_is_usb31(dwc)) + msleep(50); + + return 0; } /* @@ -348,7 +374,7 @@ static int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned length) * * Returns 0 on success otherwise negative errno. */ -static int dwc3_event_buffers_setup(struct dwc3 *dwc) +int dwc3_event_buffers_setup(struct dwc3 *dwc) { struct dwc3_event_buffer *evt; @@ -365,7 +391,7 @@ static int dwc3_event_buffers_setup(struct dwc3 *dwc) return 0; } -static void dwc3_event_buffers_cleanup(struct dwc3 *dwc) +void dwc3_event_buffers_cleanup(struct dwc3 *dwc) { struct dwc3_event_buffer *evt; @@ -846,6 +872,43 @@ static int dwc3_core_init(struct dwc3 *dwc) dwc3_writel(dwc->regs, DWC3_GUCTL1, reg); } + /* + * Must config both number of packets and max burst settings to enable + * RX and/or TX threshold. + */ + if (dwc3_is_usb31(dwc) && dwc->dr_mode == USB_DR_MODE_HOST) { + u8 rx_thr_num = dwc->rx_thr_num_pkt_prd; + u8 rx_maxburst = dwc->rx_max_burst_prd; + u8 tx_thr_num = dwc->tx_thr_num_pkt_prd; + u8 tx_maxburst = dwc->tx_max_burst_prd; + + if (rx_thr_num && rx_maxburst) { + reg = dwc3_readl(dwc->regs, DWC3_GRXTHRCFG); + reg |= DWC31_RXTHRNUMPKTSEL_PRD; + + reg &= ~DWC31_RXTHRNUMPKT_PRD(~0); + reg |= DWC31_RXTHRNUMPKT_PRD(rx_thr_num); + + reg &= ~DWC31_MAXRXBURSTSIZE_PRD(~0); + reg |= DWC31_MAXRXBURSTSIZE_PRD(rx_maxburst); + + dwc3_writel(dwc->regs, DWC3_GRXTHRCFG, reg); + } + + if (tx_thr_num && tx_maxburst) { + reg = dwc3_readl(dwc->regs, DWC3_GTXTHRCFG); + reg |= DWC31_TXTHRNUMPKTSEL_PRD; + + reg &= ~DWC31_TXTHRNUMPKT_PRD(~0); + reg |= DWC31_TXTHRNUMPKT_PRD(tx_thr_num); + + reg &= ~DWC31_MAXTXBURSTSIZE_PRD(~0); + reg |= DWC31_MAXTXBURSTSIZE_PRD(tx_maxburst); + + dwc3_writel(dwc->regs, DWC3_GTXTHRCFG, reg); + } + } + return 0; err4: @@ -1016,6 +1079,10 @@ static void dwc3_get_properties(struct dwc3 *dwc) u8 lpm_nyet_threshold; u8 tx_de_emphasis; u8 hird_threshold; + u8 rx_thr_num_pkt_prd; + u8 rx_max_burst_prd; + u8 tx_thr_num_pkt_prd; + u8 tx_max_burst_prd; /* default to highest possible threshold */ lpm_nyet_threshold = 0xff; @@ -1050,6 +1117,14 @@ static void dwc3_get_properties(struct dwc3 *dwc) &hird_threshold); dwc->usb3_lpm_capable = device_property_read_bool(dev, "snps,usb3_lpm_capable"); + device_property_read_u8(dev, "snps,rx-thr-num-pkt-prd", + &rx_thr_num_pkt_prd); + device_property_read_u8(dev, "snps,rx-max-burst-prd", + &rx_max_burst_prd); + device_property_read_u8(dev, "snps,tx-thr-num-pkt-prd", + &tx_thr_num_pkt_prd); + device_property_read_u8(dev, "snps,tx-max-burst-prd", + &tx_max_burst_prd); dwc->disable_scramble_quirk = device_property_read_bool(dev, "snps,disable_scramble_quirk"); @@ -1100,6 +1175,12 @@ static void dwc3_get_properties(struct dwc3 *dwc) dwc->hird_threshold = hird_threshold | (dwc->is_utmi_l1_suspend << 4); + dwc->rx_thr_num_pkt_prd = rx_thr_num_pkt_prd; + dwc->rx_max_burst_prd = rx_max_burst_prd; + + dwc->tx_thr_num_pkt_prd = tx_thr_num_pkt_prd; + dwc->tx_max_burst_prd = tx_max_burst_prd; + dwc->imod_interval = 0; } @@ -1326,6 +1407,20 @@ static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg) if (!PMSG_IS_AUTO(msg)) dwc3_core_exit(dwc); break; + case DWC3_GCTL_PRTCAP_OTG: + /* do nothing during runtime_suspend */ + if (PMSG_IS_AUTO(msg)) + break; + + if (dwc->current_otg_role == DWC3_OTG_ROLE_DEVICE) { + spin_lock_irqsave(&dwc->lock, flags); + dwc3_gadget_suspend(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + } + + dwc3_otg_exit(dwc); + dwc3_core_exit(dwc); + break; default: /* do nothing */ break; @@ -1345,6 +1440,7 @@ static int dwc3_resume_common(struct dwc3 *dwc, pm_message_t msg) if (ret) return ret; + dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE); spin_lock_irqsave(&dwc->lock, flags); dwc3_gadget_resume(dwc); spin_unlock_irqrestore(&dwc->lock, flags); @@ -1355,7 +1451,29 @@ static int dwc3_resume_common(struct dwc3 *dwc, pm_message_t msg) ret = dwc3_core_init(dwc); if (ret) return ret; + dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST); + } + break; + case DWC3_GCTL_PRTCAP_OTG: + /* nothing to do on runtime_resume */ + if (PMSG_IS_AUTO(msg)) + break; + + ret = dwc3_core_init(dwc); + if (ret) + return ret; + + dwc3_set_prtcap(dwc, dwc->current_dr_role); + + dwc3_otg_init(dwc); + if (dwc->current_otg_role == DWC3_OTG_ROLE_HOST) { + dwc3_otg_host_init(dwc); + } else if (dwc->current_otg_role == DWC3_OTG_ROLE_DEVICE) { + spin_lock_irqsave(&dwc->lock, flags); + dwc3_gadget_resume(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); } + break; default: /* do nothing */ diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 860d2bc184d1..4f3b43809917 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -58,6 +58,11 @@ #define DWC3_DEVICE_EVENT_CMD_CMPL 10 #define DWC3_DEVICE_EVENT_OVERFLOW 11 +/* Controller's role while using the OTG block */ +#define DWC3_OTG_ROLE_IDLE 0 +#define DWC3_OTG_ROLE_HOST 1 +#define DWC3_OTG_ROLE_DEVICE 2 + #define DWC3_GEVNTCOUNT_MASK 0xfffc #define DWC3_GEVNTCOUNT_EHB BIT(31) #define DWC3_GSNPSID_MASK 0xffff0000 @@ -100,6 +105,11 @@ #define DWC3_GHWPARAMS7 0xc15c #define DWC3_GDBGFIFOSPACE 0xc160 #define DWC3_GDBGLTSSM 0xc164 +#define DWC3_GDBGBMU 0xc16c +#define DWC3_GDBGLSPMUX 0xc170 +#define DWC3_GDBGLSP 0xc174 +#define DWC3_GDBGEPINFO0 0xc178 +#define DWC3_GDBGEPINFO1 0xc17c #define DWC3_GPRTBIMAP_HS0 0xc180 #define DWC3_GPRTBIMAP_HS1 0xc184 #define DWC3_GPRTBIMAP_FS0 0xc188 @@ -173,6 +183,26 @@ #define DWC3_GRXTHRCFG_RXPKTCNT(n) (((n) & 0xf) << 24) #define DWC3_GRXTHRCFG_PKTCNTSEL BIT(29) +/* Global RX Threshold Configuration Register for DWC_usb31 only */ +#define DWC31_GRXTHRCFG_MAXRXBURSTSIZE(n) (((n) & 0x1f) << 16) +#define DWC31_GRXTHRCFG_RXPKTCNT(n) (((n) & 0x1f) << 21) +#define DWC31_GRXTHRCFG_PKTCNTSEL BIT(26) +#define DWC31_RXTHRNUMPKTSEL_HS_PRD BIT(15) +#define DWC31_RXTHRNUMPKT_HS_PRD(n) (((n) & 0x3) << 13) +#define DWC31_RXTHRNUMPKTSEL_PRD BIT(10) +#define DWC31_RXTHRNUMPKT_PRD(n) (((n) & 0x1f) << 5) +#define DWC31_MAXRXBURSTSIZE_PRD(n) ((n) & 0x1f) + +/* Global TX Threshold Configuration Register for DWC_usb31 only */ +#define DWC31_GTXTHRCFG_MAXTXBURSTSIZE(n) (((n) & 0x1f) << 16) +#define DWC31_GTXTHRCFG_TXPKTCNT(n) (((n) & 0x1f) << 21) +#define DWC31_GTXTHRCFG_PKTCNTSEL BIT(26) +#define DWC31_TXTHRNUMPKTSEL_HS_PRD BIT(15) +#define DWC31_TXTHRNUMPKT_HS_PRD(n) (((n) & 0x3) << 13) +#define DWC31_TXTHRNUMPKTSEL_PRD BIT(10) +#define DWC31_TXTHRNUMPKT_PRD(n) (((n) & 0x1f) << 5) +#define DWC31_MAXTXBURSTSIZE_PRD(n) ((n) & 0x1f) + /* Global Configuration Register */ #define DWC3_GCTL_PWRDNSCALE(n) ((n) << 19) #define DWC3_GCTL_U2RSTECN BIT(16) @@ -201,6 +231,15 @@ #define DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS BIT(28) #define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW BIT(24) +/* Global Status Register */ +#define DWC3_GSTS_OTG_IP BIT(10) +#define DWC3_GSTS_BC_IP BIT(9) +#define DWC3_GSTS_ADP_IP BIT(8) +#define DWC3_GSTS_HOST_IP BIT(7) +#define DWC3_GSTS_DEVICE_IP BIT(6) +#define DWC3_GSTS_CSR_TIMEOUT BIT(5) +#define DWC3_GSTS_BUS_ERR_ADDR_VLD BIT(4) + /* Global USB2 PHY Configuration Register */ #define DWC3_GUSB2PHYCFG_PHYSOFTRST BIT(31) #define DWC3_GUSB2PHYCFG_U2_FREECLK_EXISTS BIT(30) @@ -241,6 +280,8 @@ #define DWC3_GUSB3PIPECTL_TX_DEEPH(n) ((n) << 1) /* Global TX Fifo Size Register */ +#define DWC31_GTXFIFOSIZ_TXFRAMNUM BIT(15) /* DWC_usb31 only */ +#define DWC31_GTXFIFOSIZ_TXFDEF(n) ((n) & 0x7fff) /* DWC_usb31 only */ #define DWC3_GTXFIFOSIZ_TXFDEF(n) ((n) & 0xffff) #define DWC3_GTXFIFOSIZ_TXFSTADDR(n) ((n) & 0xffff0000) @@ -286,6 +327,11 @@ #define DWC3_MAX_HIBER_SCRATCHBUFS 15 /* Global HWPARAMS6 Register */ +#define DWC3_GHWPARAMS6_BCSUPPORT BIT(14) +#define DWC3_GHWPARAMS6_OTG3SUPPORT BIT(13) +#define DWC3_GHWPARAMS6_ADPSUPPORT BIT(12) +#define DWC3_GHWPARAMS6_HNPSUPPORT BIT(11) +#define DWC3_GHWPARAMS6_SRPSUPPORT BIT(10) #define DWC3_GHWPARAMS6_EN_FPGA BIT(7) /* Global HWPARAMS7 Register */ @@ -467,6 +513,74 @@ #define DWC3_DEV_IMOD_INTERVAL_SHIFT 0 #define DWC3_DEV_IMOD_INTERVAL_MASK (0xffff << 0) +/* OTG Configuration Register */ +#define DWC3_OCFG_DISPWRCUTTOFF BIT(5) +#define DWC3_OCFG_HIBDISMASK BIT(4) +#define DWC3_OCFG_SFTRSTMASK BIT(3) +#define DWC3_OCFG_OTGVERSION BIT(2) +#define DWC3_OCFG_HNPCAP BIT(1) +#define DWC3_OCFG_SRPCAP BIT(0) + +/* OTG CTL Register */ +#define DWC3_OCTL_OTG3GOERR BIT(7) +#define DWC3_OCTL_PERIMODE BIT(6) +#define DWC3_OCTL_PRTPWRCTL BIT(5) +#define DWC3_OCTL_HNPREQ BIT(4) +#define DWC3_OCTL_SESREQ BIT(3) +#define DWC3_OCTL_TERMSELIDPULSE BIT(2) +#define DWC3_OCTL_DEVSETHNPEN BIT(1) +#define DWC3_OCTL_HSTSETHNPEN BIT(0) + +/* OTG Event Register */ +#define DWC3_OEVT_DEVICEMODE BIT(31) +#define DWC3_OEVT_XHCIRUNSTPSET BIT(27) +#define DWC3_OEVT_DEVRUNSTPSET BIT(26) +#define DWC3_OEVT_HIBENTRY BIT(25) +#define DWC3_OEVT_CONIDSTSCHNG BIT(24) +#define DWC3_OEVT_HRRCONFNOTIF BIT(23) +#define DWC3_OEVT_HRRINITNOTIF BIT(22) +#define DWC3_OEVT_ADEVIDLE BIT(21) +#define DWC3_OEVT_ADEVBHOSTEND BIT(20) +#define DWC3_OEVT_ADEVHOST BIT(19) +#define DWC3_OEVT_ADEVHNPCHNG BIT(18) +#define DWC3_OEVT_ADEVSRPDET BIT(17) +#define DWC3_OEVT_ADEVSESSENDDET BIT(16) +#define DWC3_OEVT_BDEVBHOSTEND BIT(11) +#define DWC3_OEVT_BDEVHNPCHNG BIT(10) +#define DWC3_OEVT_BDEVSESSVLDDET BIT(9) +#define DWC3_OEVT_BDEVVBUSCHNG BIT(8) +#define DWC3_OEVT_BSESSVLD BIT(3) +#define DWC3_OEVT_HSTNEGSTS BIT(2) +#define DWC3_OEVT_SESREQSTS BIT(1) +#define DWC3_OEVT_ERROR BIT(0) + +/* OTG Event Enable Register */ +#define DWC3_OEVTEN_XHCIRUNSTPSETEN BIT(27) +#define DWC3_OEVTEN_DEVRUNSTPSETEN BIT(26) +#define DWC3_OEVTEN_HIBENTRYEN BIT(25) +#define DWC3_OEVTEN_CONIDSTSCHNGEN BIT(24) +#define DWC3_OEVTEN_HRRCONFNOTIFEN BIT(23) +#define DWC3_OEVTEN_HRRINITNOTIFEN BIT(22) +#define DWC3_OEVTEN_ADEVIDLEEN BIT(21) +#define DWC3_OEVTEN_ADEVBHOSTENDEN BIT(20) +#define DWC3_OEVTEN_ADEVHOSTEN BIT(19) +#define DWC3_OEVTEN_ADEVHNPCHNGEN BIT(18) +#define DWC3_OEVTEN_ADEVSRPDETEN BIT(17) +#define DWC3_OEVTEN_ADEVSESSENDDETEN BIT(16) +#define DWC3_OEVTEN_BDEVBHOSTENDEN BIT(11) +#define DWC3_OEVTEN_BDEVHNPCHNGEN BIT(10) +#define DWC3_OEVTEN_BDEVSESSVLDDETEN BIT(9) +#define DWC3_OEVTEN_BDEVVBUSCHNGEN BIT(8) + +/* OTG Status Register */ +#define DWC3_OSTS_DEVRUNSTP BIT(13) +#define DWC3_OSTS_XHCIRUNSTP BIT(12) +#define DWC3_OSTS_PERIPHERALSTATE BIT(4) +#define DWC3_OSTS_XHCIPRTPOWER BIT(3) +#define DWC3_OSTS_BSESVLD BIT(2) +#define DWC3_OSTS_VBUSVLD BIT(1) +#define DWC3_OSTS_CONIDSTS BIT(0) + /* Structures */ struct dwc3_trb; @@ -781,6 +895,10 @@ struct dwc3_scratchpad_array { * @regs_size: address space size * @fladj: frame length adjustment * @irq_gadget: peripheral controller's IRQ number + * @otg_irq: IRQ number for OTG IRQs + * @current_otg_role: current role of operation while using the OTG block + * @desired_otg_role: desired role of operation while using the OTG block + * @otg_restart_host: flag that OTG controller needs to restart host * @nr_scratch: number of scratch buffers * @u1u2: only used on revisions <1.83a for workaround * @maximum_speed: maximum speed requested (mainly for testing purposes) @@ -816,6 +934,10 @@ struct dwc3_scratchpad_array { * @test_mode_nr: test feature selector * @lpm_nyet_threshold: LPM NYET response threshold * @hird_threshold: HIRD threshold + * @rx_thr_num_pkt_prd: periodic ESS receive packet count + * @rx_max_burst_prd: max periodic ESS receive burst size + * @tx_thr_num_pkt_prd: periodic ESS transmit packet count + * @tx_max_burst_prd: max periodic ESS transmit burst size * @hsphy_interface: "utmi" or "ulpi" * @connected: true when we're connected to a host, false otherwise * @delayed_status: true when gadget driver asks for delayed status @@ -914,6 +1036,10 @@ struct dwc3 { u32 fladj; u32 irq_gadget; + u32 otg_irq; + u32 current_otg_role; + u32 desired_otg_role; + bool otg_restart_host; u32 nr_scratch; u32 u1u2; u32 maximum_speed; @@ -979,6 +1105,10 @@ struct dwc3 { u8 test_mode_nr; u8 lpm_nyet_threshold; u8 hird_threshold; + u8 rx_thr_num_pkt_prd; + u8 rx_max_burst_prd; + u8 tx_thr_num_pkt_prd; + u8 tx_max_burst_prd; const char *hsphy_interface; @@ -1175,6 +1305,7 @@ struct dwc3_gadget_ep_cmd_params { #define DWC3_HAS_OTG BIT(3) /* prototypes */ +void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode); void dwc3_set_mode(struct dwc3 *dwc, u32 mode); u32 dwc3_core_fifo_space(struct dwc3_ep *dep, u8 type); @@ -1192,6 +1323,9 @@ static inline bool dwc3_is_usb31(struct dwc3 *dwc) bool dwc3_has_imod(struct dwc3 *dwc); +int dwc3_event_buffers_setup(struct dwc3 *dwc); +void dwc3_event_buffers_cleanup(struct dwc3 *dwc); + #if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) int dwc3_host_init(struct dwc3 *dwc); void dwc3_host_exit(struct dwc3 *dwc); @@ -1235,11 +1369,23 @@ static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc, #if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) int dwc3_drd_init(struct dwc3 *dwc); void dwc3_drd_exit(struct dwc3 *dwc); +void dwc3_otg_init(struct dwc3 *dwc); +void dwc3_otg_exit(struct dwc3 *dwc); +void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus); +void dwc3_otg_host_init(struct dwc3 *dwc); #else static inline int dwc3_drd_init(struct dwc3 *dwc) { return 0; } static inline void dwc3_drd_exit(struct dwc3 *dwc) { } +static inline void dwc3_otg_init(struct dwc3 *dwc) +{ } +static inline void dwc3_otg_exit(struct dwc3 *dwc) +{ } +static inline void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus) +{ } +static inline void dwc3_otg_host_init(struct dwc3 *dwc) +{ } #endif /* power management interface */ diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c index 00e65530c81e..2f07be1e1f31 100644 --- a/drivers/usb/dwc3/debugfs.c +++ b/drivers/usb/dwc3/debugfs.c @@ -81,6 +81,11 @@ static const struct debugfs_reg32 dwc3_regs[] = { dump_register(GHWPARAMS7), dump_register(GDBGFIFOSPACE), dump_register(GDBGLTSSM), + dump_register(GDBGBMU), + dump_register(GDBGLSPMUX), + dump_register(GDBGLSP), + dump_register(GDBGEPINFO0), + dump_register(GDBGEPINFO1), dump_register(GPRTBIMAP_HS0), dump_register(GPRTBIMAP_HS1), dump_register(GPRTBIMAP_FS0), @@ -487,8 +492,8 @@ static const struct file_operations dwc3_link_state_fops = { }; struct dwc3_ep_file_map { - char name[25]; - int (*show)(struct seq_file *s, void *unused); + const char name[25]; + const struct file_operations *const fops; }; static int dwc3_tx_fifo_queue_show(struct seq_file *s, void *unused) @@ -596,7 +601,7 @@ static int dwc3_event_queue_show(struct seq_file *s, void *unused) return 0; } -static int dwc3_ep_transfer_type_show(struct seq_file *s, void *unused) +static int dwc3_transfer_type_show(struct seq_file *s, void *unused) { struct dwc3_ep *dep = s->private; struct dwc3 *dwc = dep->dwc; @@ -632,7 +637,7 @@ out: return 0; } -static int dwc3_ep_trb_ring_show(struct seq_file *s, void *unused) +static int dwc3_trb_ring_show(struct seq_file *s, void *unused) { struct dwc3_ep *dep = s->private; struct dwc3 *dwc = dep->dwc; @@ -670,58 +675,39 @@ out: return 0; } -static struct dwc3_ep_file_map map[] = { - { "tx_fifo_queue", dwc3_tx_fifo_queue_show, }, - { "rx_fifo_queue", dwc3_rx_fifo_queue_show, }, - { "tx_request_queue", dwc3_tx_request_queue_show, }, - { "rx_request_queue", dwc3_rx_request_queue_show, }, - { "rx_info_queue", dwc3_rx_info_queue_show, }, - { "descriptor_fetch_queue", dwc3_descriptor_fetch_queue_show, }, - { "event_queue", dwc3_event_queue_show, }, - { "transfer_type", dwc3_ep_transfer_type_show, }, - { "trb_ring", dwc3_ep_trb_ring_show, }, +DEFINE_SHOW_ATTRIBUTE(dwc3_tx_fifo_queue); +DEFINE_SHOW_ATTRIBUTE(dwc3_rx_fifo_queue); +DEFINE_SHOW_ATTRIBUTE(dwc3_tx_request_queue); +DEFINE_SHOW_ATTRIBUTE(dwc3_rx_request_queue); +DEFINE_SHOW_ATTRIBUTE(dwc3_rx_info_queue); +DEFINE_SHOW_ATTRIBUTE(dwc3_descriptor_fetch_queue); +DEFINE_SHOW_ATTRIBUTE(dwc3_event_queue); +DEFINE_SHOW_ATTRIBUTE(dwc3_transfer_type); +DEFINE_SHOW_ATTRIBUTE(dwc3_trb_ring); + +static const struct dwc3_ep_file_map dwc3_ep_file_map[] = { + { "tx_fifo_queue", &dwc3_tx_fifo_queue_fops, }, + { "rx_fifo_queue", &dwc3_rx_fifo_queue_fops, }, + { "tx_request_queue", &dwc3_tx_request_queue_fops, }, + { "rx_request_queue", &dwc3_rx_request_queue_fops, }, + { "rx_info_queue", &dwc3_rx_info_queue_fops, }, + { "descriptor_fetch_queue", &dwc3_descriptor_fetch_queue_fops, }, + { "event_queue", &dwc3_event_queue_fops, }, + { "transfer_type", &dwc3_transfer_type_fops, }, + { "trb_ring", &dwc3_trb_ring_fops, }, }; -static int dwc3_endpoint_open(struct inode *inode, struct file *file) -{ - const char *file_name = file_dentry(file)->d_iname; - struct dwc3_ep_file_map *f_map; - int i; - - for (i = 0; i < ARRAY_SIZE(map); i++) { - f_map = &map[i]; - - if (strcmp(f_map->name, file_name) == 0) - break; - } - - return single_open(file, f_map->show, inode->i_private); -} - -static const struct file_operations dwc3_endpoint_fops = { - .open = dwc3_endpoint_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static void dwc3_debugfs_create_endpoint_file(struct dwc3_ep *dep, - struct dentry *parent, int type) -{ - struct dentry *file; - struct dwc3_ep_file_map *ep_file = &map[type]; - - file = debugfs_create_file(ep_file->name, S_IRUGO, parent, dep, - &dwc3_endpoint_fops); -} - static void dwc3_debugfs_create_endpoint_files(struct dwc3_ep *dep, struct dentry *parent) { int i; - for (i = 0; i < ARRAY_SIZE(map); i++) - dwc3_debugfs_create_endpoint_file(dep, parent, i); + for (i = 0; i < ARRAY_SIZE(dwc3_ep_file_map); i++) { + const struct file_operations *fops = dwc3_ep_file_map[i].fops; + const char *name = dwc3_ep_file_map[i].name; + + debugfs_create_file(name, S_IRUGO, parent, dep, fops); + } } static void dwc3_debugfs_create_endpoint_dir(struct dwc3_ep *dep, diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c index cc8ab9a8e9d2..1d8c557e97e0 100644 --- a/drivers/usb/dwc3/drd.c +++ b/drivers/usb/dwc3/drd.c @@ -8,22 +8,423 @@ */ #include <linux/extcon.h> +#include <linux/platform_device.h> #include "debug.h" #include "core.h" #include "gadget.h" -static void dwc3_drd_update(struct dwc3 *dwc) +static void dwc3_otg_disable_events(struct dwc3 *dwc, u32 disable_mask) +{ + u32 reg = dwc3_readl(dwc->regs, DWC3_OEVTEN); + + reg &= ~(disable_mask); + dwc3_writel(dwc->regs, DWC3_OEVTEN, reg); +} + +static void dwc3_otg_enable_events(struct dwc3 *dwc, u32 enable_mask) +{ + u32 reg = dwc3_readl(dwc->regs, DWC3_OEVTEN); + + reg |= (enable_mask); + dwc3_writel(dwc->regs, DWC3_OEVTEN, reg); +} + +static void dwc3_otg_clear_events(struct dwc3 *dwc) +{ + u32 reg = dwc3_readl(dwc->regs, DWC3_OEVT); + + dwc3_writel(dwc->regs, DWC3_OEVTEN, reg); +} + +#define DWC3_OTG_ALL_EVENTS (DWC3_OEVTEN_XHCIRUNSTPSETEN | \ + DWC3_OEVTEN_DEVRUNSTPSETEN | DWC3_OEVTEN_HIBENTRYEN | \ + DWC3_OEVTEN_CONIDSTSCHNGEN | DWC3_OEVTEN_HRRCONFNOTIFEN | \ + DWC3_OEVTEN_HRRINITNOTIFEN | DWC3_OEVTEN_ADEVIDLEEN | \ + DWC3_OEVTEN_ADEVBHOSTENDEN | DWC3_OEVTEN_ADEVHOSTEN | \ + DWC3_OEVTEN_ADEVHNPCHNGEN | DWC3_OEVTEN_ADEVSRPDETEN | \ + DWC3_OEVTEN_ADEVSESSENDDETEN | DWC3_OEVTEN_BDEVBHOSTENDEN | \ + DWC3_OEVTEN_BDEVHNPCHNGEN | DWC3_OEVTEN_BDEVSESSVLDDETEN | \ + DWC3_OEVTEN_BDEVVBUSCHNGEN) + +static irqreturn_t dwc3_otg_thread_irq(int irq, void *_dwc) { + struct dwc3 *dwc = _dwc; + + spin_lock(&dwc->lock); + if (dwc->otg_restart_host) { + dwc3_otg_host_init(dwc); + dwc->otg_restart_host = 0; + } + + spin_unlock(&dwc->lock); + + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG); + + return IRQ_HANDLED; +} + +static irqreturn_t dwc3_otg_irq(int irq, void *_dwc) +{ + u32 reg; + struct dwc3 *dwc = _dwc; + irqreturn_t ret = IRQ_NONE; + + reg = dwc3_readl(dwc->regs, DWC3_OEVT); + if (reg) { + /* ignore non OTG events, we can't disable them in OEVTEN */ + if (!(reg & DWC3_OTG_ALL_EVENTS)) { + dwc3_writel(dwc->regs, DWC3_OEVT, reg); + return IRQ_NONE; + } + + if (dwc->current_otg_role == DWC3_OTG_ROLE_HOST && + !(reg & DWC3_OEVT_DEVICEMODE)) + dwc->otg_restart_host = 1; + dwc3_writel(dwc->regs, DWC3_OEVT, reg); + ret = IRQ_WAKE_THREAD; + } + + return ret; +} + +static void dwc3_otgregs_init(struct dwc3 *dwc) +{ + u32 reg; + + /* + * Prevent host/device reset from resetting OTG core. + * If we don't do this then xhci_reset (USBCMD.HCRST) will reset + * the signal outputs sent to the PHY, the OTG FSM logic of the + * core and also the resets to the VBUS filters inside the core. + */ + reg = dwc3_readl(dwc->regs, DWC3_OCFG); + reg |= DWC3_OCFG_SFTRSTMASK; + dwc3_writel(dwc->regs, DWC3_OCFG, reg); + + /* Disable hibernation for simplicity */ + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + reg &= ~DWC3_GCTL_GBLHIBERNATIONEN; + dwc3_writel(dwc->regs, DWC3_GCTL, reg); + + /* + * Initialize OTG registers as per + * Figure 11-4 OTG Driver Overall Programming Flow + */ + /* OCFG.SRPCap = 0, OCFG.HNPCap = 0 */ + reg = dwc3_readl(dwc->regs, DWC3_OCFG); + reg &= ~(DWC3_OCFG_SRPCAP | DWC3_OCFG_HNPCAP); + dwc3_writel(dwc->regs, DWC3_OCFG, reg); + /* OEVT = FFFF */ + dwc3_otg_clear_events(dwc); + /* OEVTEN = 0 */ + dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS); + /* OEVTEN.ConIDStsChngEn = 1. Instead we enable all events */ + dwc3_otg_enable_events(dwc, DWC3_OTG_ALL_EVENTS); + /* + * OCTL.PeriMode = 1, OCTL.DevSetHNPEn = 0, OCTL.HstSetHNPEn = 0, + * OCTL.HNPReq = 0 + */ + reg = dwc3_readl(dwc->regs, DWC3_OCTL); + reg |= DWC3_OCTL_PERIMODE; + reg &= ~(DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HSTSETHNPEN | + DWC3_OCTL_HNPREQ); + dwc3_writel(dwc->regs, DWC3_OCTL, reg); +} + +static int dwc3_otg_get_irq(struct dwc3 *dwc) +{ + struct platform_device *dwc3_pdev = to_platform_device(dwc->dev); + int irq; + + irq = platform_get_irq_byname(dwc3_pdev, "otg"); + if (irq > 0) + goto out; + + if (irq == -EPROBE_DEFER) + goto out; + + irq = platform_get_irq_byname(dwc3_pdev, "dwc_usb3"); + if (irq > 0) + goto out; + + if (irq == -EPROBE_DEFER) + goto out; + + irq = platform_get_irq(dwc3_pdev, 0); + if (irq > 0) + goto out; + + if (irq != -EPROBE_DEFER) + dev_err(dwc->dev, "missing OTG IRQ\n"); + + if (!irq) + irq = -EINVAL; + +out: + return irq; +} + +void dwc3_otg_init(struct dwc3 *dwc) +{ + u32 reg; + + /* + * As per Figure 11-4 OTG Driver Overall Programming Flow, + * block "Initialize GCTL for OTG operation". + */ + /* GCTL.PrtCapDir=2'b11 */ + dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_OTG); + /* GUSB2PHYCFG0.SusPHY=0 */ + reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); + reg &= ~DWC3_GUSB2PHYCFG_SUSPHY; + dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); + + /* Initialize OTG registers */ + dwc3_otgregs_init(dwc); +} + +void dwc3_otg_exit(struct dwc3 *dwc) +{ + /* disable all OTG IRQs */ + dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS); + /* clear all events */ + dwc3_otg_clear_events(dwc); +} + +/* should be called before Host controller driver is started */ +void dwc3_otg_host_init(struct dwc3 *dwc) +{ + u32 reg; + + /* As per Figure 11-10 A-Device Flow Diagram */ + /* OCFG.HNPCap = 0, OCFG.SRPCap = 0. Already 0 */ + + /* + * OCTL.PeriMode=0, OCTL.TermSelDLPulse = 0, + * OCTL.DevSetHNPEn = 0, OCTL.HstSetHNPEn = 0 + */ + reg = dwc3_readl(dwc->regs, DWC3_OCTL); + reg &= ~(DWC3_OCTL_PERIMODE | DWC3_OCTL_TERMSELIDPULSE | + DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HSTSETHNPEN); + dwc3_writel(dwc->regs, DWC3_OCTL, reg); + + /* + * OCFG.DisPrtPwrCutoff = 0/1 + */ + reg = dwc3_readl(dwc->regs, DWC3_OCFG); + reg &= ~DWC3_OCFG_DISPWRCUTTOFF; + dwc3_writel(dwc->regs, DWC3_OCFG, reg); + + /* + * OCFG.SRPCap = 1, OCFG.HNPCap = GHWPARAMS6.HNP_CAP + * We don't want SRP/HNP for simple dual-role so leave + * these disabled. + */ + + /* + * OEVTEN.OTGADevHostEvntEn = 1 + * OEVTEN.OTGADevSessEndDetEvntEn = 1 + * We don't want HNP/role-swap so leave these disabled. + */ + + /* GUSB2PHYCFG.ULPIAutoRes = 1/0, GUSB2PHYCFG.SusPHY = 1 */ + if (!dwc->dis_u2_susphy_quirk) { + reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); + reg |= DWC3_GUSB2PHYCFG_SUSPHY; + dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); + } + + /* Set Port Power to enable VBUS: OCTL.PrtPwrCtl = 1 */ + reg = dwc3_readl(dwc->regs, DWC3_OCTL); + reg |= DWC3_OCTL_PRTPWRCTL; + dwc3_writel(dwc->regs, DWC3_OCTL, reg); +} + +/* should be called after Host controller driver is stopped */ +static void dwc3_otg_host_exit(struct dwc3 *dwc) +{ + u32 reg; + + /* + * Exit from A-device flow as per + * Figure 11-4 OTG Driver Overall Programming Flow + */ + + /* + * OEVTEN.OTGADevBHostEndEvntEn=0, OEVTEN.OTGADevHNPChngEvntEn=0 + * OEVTEN.OTGADevSessEndDetEvntEn=0, + * OEVTEN.OTGADevHostEvntEn = 0 + * But we don't disable any OTG events + */ + + /* OCTL.HstSetHNPEn = 0, OCTL.PrtPwrCtl=0 */ + reg = dwc3_readl(dwc->regs, DWC3_OCTL); + reg &= ~(DWC3_OCTL_HSTSETHNPEN | DWC3_OCTL_PRTPWRCTL); + dwc3_writel(dwc->regs, DWC3_OCTL, reg); +} + +/* should be called before the gadget controller driver is started */ +static void dwc3_otg_device_init(struct dwc3 *dwc) +{ + u32 reg; + + /* As per Figure 11-20 B-Device Flow Diagram */ + + /* + * OCFG.HNPCap = GHWPARAMS6.HNP_CAP, OCFG.SRPCap = 1 + * but we keep them 0 for simple dual-role operation. + */ + reg = dwc3_readl(dwc->regs, DWC3_OCFG); + /* OCFG.OTGSftRstMsk = 0/1 */ + reg |= DWC3_OCFG_SFTRSTMASK; + dwc3_writel(dwc->regs, DWC3_OCFG, reg); + /* + * OCTL.PeriMode = 1 + * OCTL.TermSelDLPulse = 0/1, OCTL.HNPReq = 0 + * OCTL.DevSetHNPEn = 0, OCTL.HstSetHNPEn = 0 + */ + reg = dwc3_readl(dwc->regs, DWC3_OCTL); + reg |= DWC3_OCTL_PERIMODE; + reg &= ~(DWC3_OCTL_TERMSELIDPULSE | DWC3_OCTL_HNPREQ | + DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HSTSETHNPEN); + dwc3_writel(dwc->regs, DWC3_OCTL, reg); + /* OEVTEN.OTGBDevSesVldDetEvntEn = 1 */ + dwc3_otg_enable_events(dwc, DWC3_OEVTEN_BDEVSESSVLDDETEN); + /* GUSB2PHYCFG.ULPIAutoRes = 0, GUSB2PHYCFG0.SusPHY = 1 */ + if (!dwc->dis_u2_susphy_quirk) { + reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); + reg |= DWC3_GUSB2PHYCFG_SUSPHY; + dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); + } + /* GCTL.GblHibernationEn = 0. Already 0. */ +} + +/* should be called after the gadget controller driver is stopped */ +static void dwc3_otg_device_exit(struct dwc3 *dwc) +{ + u32 reg; + + /* + * Exit from B-device flow as per + * Figure 11-4 OTG Driver Overall Programming Flow + */ + + /* + * OEVTEN.OTGBDevHNPChngEvntEn = 0 + * OEVTEN.OTGBDevVBusChngEvntEn = 0 + * OEVTEN.OTGBDevBHostEndEvntEn = 0 + */ + dwc3_otg_disable_events(dwc, DWC3_OEVTEN_BDEVHNPCHNGEN | + DWC3_OEVTEN_BDEVVBUSCHNGEN | + DWC3_OEVTEN_BDEVBHOSTENDEN); + + /* OCTL.DevSetHNPEn = 0, OCTL.HNPReq = 0, OCTL.PeriMode=1 */ + reg = dwc3_readl(dwc->regs, DWC3_OCTL); + reg &= ~(DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HNPREQ); + reg |= DWC3_OCTL_PERIMODE; + dwc3_writel(dwc->regs, DWC3_OCTL, reg); +} + +void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus) +{ + int ret; + u32 reg; int id; + unsigned long flags; - id = extcon_get_state(dwc->edev, EXTCON_USB_HOST); - if (id < 0) - id = 0; + if (dwc->dr_mode != USB_DR_MODE_OTG) + return; - dwc3_set_mode(dwc, id ? - DWC3_GCTL_PRTCAP_HOST : - DWC3_GCTL_PRTCAP_DEVICE); + /* don't do anything if debug user changed role to not OTG */ + if (dwc->current_dr_role != DWC3_GCTL_PRTCAP_OTG) + return; + + if (!ignore_idstatus) { + reg = dwc3_readl(dwc->regs, DWC3_OSTS); + id = !!(reg & DWC3_OSTS_CONIDSTS); + + dwc->desired_otg_role = id ? DWC3_OTG_ROLE_DEVICE : + DWC3_OTG_ROLE_HOST; + } + + if (dwc->desired_otg_role == dwc->current_otg_role) + return; + + switch (dwc->current_otg_role) { + case DWC3_OTG_ROLE_HOST: + dwc3_host_exit(dwc); + spin_lock_irqsave(&dwc->lock, flags); + dwc3_otg_host_exit(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + break; + case DWC3_OTG_ROLE_DEVICE: + dwc3_gadget_exit(dwc); + spin_lock_irqsave(&dwc->lock, flags); + dwc3_event_buffers_cleanup(dwc); + dwc3_otg_device_exit(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + break; + default: + break; + } + + spin_lock_irqsave(&dwc->lock, flags); + + dwc->current_otg_role = dwc->desired_otg_role; + + spin_unlock_irqrestore(&dwc->lock, flags); + + switch (dwc->desired_otg_role) { + case DWC3_OTG_ROLE_HOST: + spin_lock_irqsave(&dwc->lock, flags); + dwc3_otgregs_init(dwc); + dwc3_otg_host_init(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + ret = dwc3_host_init(dwc); + if (ret) { + dev_err(dwc->dev, "failed to initialize host\n"); + } else { + if (dwc->usb2_phy) + otg_set_vbus(dwc->usb2_phy->otg, true); + if (dwc->usb2_generic_phy) + phy_set_mode(dwc->usb2_generic_phy, + PHY_MODE_USB_HOST); + } + break; + case DWC3_OTG_ROLE_DEVICE: + spin_lock_irqsave(&dwc->lock, flags); + dwc3_otgregs_init(dwc); + dwc3_otg_device_init(dwc); + dwc3_event_buffers_setup(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + + if (dwc->usb2_phy) + otg_set_vbus(dwc->usb2_phy->otg, false); + if (dwc->usb2_generic_phy) + phy_set_mode(dwc->usb2_generic_phy, + PHY_MODE_USB_DEVICE); + ret = dwc3_gadget_init(dwc); + if (ret) + dev_err(dwc->dev, "failed to initialize peripheral\n"); + break; + default: + break; + } +} + +static void dwc3_drd_update(struct dwc3 *dwc) +{ + int id; + + if (dwc->edev) { + id = extcon_get_state(dwc->edev, EXTCON_USB_HOST); + if (id < 0) + id = 0; + dwc3_set_mode(dwc, id ? + DWC3_GCTL_PRTCAP_HOST : + DWC3_GCTL_PRTCAP_DEVICE); + } } static int dwc3_drd_notifier(struct notifier_block *nb, @@ -40,11 +441,11 @@ static int dwc3_drd_notifier(struct notifier_block *nb, int dwc3_drd_init(struct dwc3 *dwc) { - int ret; + int ret, irq; - if (dwc->dev->of_node) { - if (of_property_read_bool(dwc->dev->of_node, "extcon")) - dwc->edev = extcon_get_edev_by_phandle(dwc->dev, 0); + 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); @@ -56,19 +457,71 @@ int dwc3_drd_init(struct dwc3 *dwc) dev_err(dwc->dev, "couldn't register cable notifier\n"); return ret; } - } - dwc3_drd_update(dwc); + dwc3_drd_update(dwc); + } else { + dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_OTG); + dwc->current_dr_role = DWC3_GCTL_PRTCAP_OTG; + + /* use OTG block to get ID event */ + irq = dwc3_otg_get_irq(dwc); + if (irq < 0) + return irq; + + dwc->otg_irq = irq; + + /* disable all OTG IRQs */ + dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS); + /* clear all events */ + dwc3_otg_clear_events(dwc); + + ret = request_threaded_irq(dwc->otg_irq, dwc3_otg_irq, + dwc3_otg_thread_irq, + IRQF_SHARED, "dwc3-otg", dwc); + if (ret) { + dev_err(dwc->dev, "failed to request irq #%d --> %d\n", + dwc->otg_irq, ret); + ret = -ENODEV; + return ret; + } + + dwc3_otg_init(dwc); + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG); + } return 0; } void dwc3_drd_exit(struct dwc3 *dwc) { - extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST, - &dwc->edev_nb); + unsigned long flags; + + if (dwc->edev) + extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST, + &dwc->edev_nb); + + cancel_work_sync(&dwc->drd_work); + + /* debug user might have changed role, clean based on current role */ + switch (dwc->current_dr_role) { + case DWC3_GCTL_PRTCAP_HOST: + dwc3_host_exit(dwc); + break; + case DWC3_GCTL_PRTCAP_DEVICE: + dwc3_gadget_exit(dwc); + dwc3_event_buffers_cleanup(dwc); + break; + case DWC3_GCTL_PRTCAP_OTG: + dwc3_otg_exit(dwc); + spin_lock_irqsave(&dwc->lock, flags); + dwc->desired_otg_role = DWC3_OTG_ROLE_IDLE; + spin_unlock_irqrestore(&dwc->lock, flags); + dwc3_otg_update(dwc, 1); + break; + default: + break; + } - dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); - flush_work(&dwc->drd_work); - dwc3_gadget_exit(dwc); + if (!dwc->edev) + free_irq(dwc->otg_irq, dwc); } diff --git a/drivers/usb/dwc3/dwc3-of-simple.c b/drivers/usb/dwc3/dwc3-of-simple.c index e54c3622eb28..cb2ee96fd3e8 100644 --- a/drivers/usb/dwc3/dwc3-of-simple.c +++ b/drivers/usb/dwc3/dwc3-of-simple.c @@ -27,6 +27,7 @@ struct dwc3_of_simple { struct clk **clks; int num_clocks; struct reset_control *resets; + bool pulse_resets; }; static int dwc3_of_simple_clk_init(struct dwc3_of_simple *simple, int count) @@ -83,6 +84,7 @@ static int dwc3_of_simple_probe(struct platform_device *pdev) int ret; int i; + bool shared_resets = false; simple = devm_kzalloc(dev, sizeof(*simple), GFP_KERNEL); if (!simple) @@ -91,16 +93,28 @@ static int dwc3_of_simple_probe(struct platform_device *pdev) platform_set_drvdata(pdev, simple); simple->dev = dev; - simple->resets = of_reset_control_array_get_optional_exclusive(np); + if (of_device_is_compatible(np, "amlogic,meson-axg-dwc3") || + of_device_is_compatible(np, "amlogic,meson-gxl-dwc3")) { + shared_resets = true; + simple->pulse_resets = true; + } + + simple->resets = of_reset_control_array_get(np, shared_resets, true); if (IS_ERR(simple->resets)) { ret = PTR_ERR(simple->resets); dev_err(dev, "failed to get device resets, err=%d\n", ret); return ret; } - ret = reset_control_deassert(simple->resets); - if (ret) - goto err_resetc_put; + if (simple->pulse_resets) { + ret = reset_control_reset(simple->resets); + if (ret) + goto err_resetc_put; + } else { + ret = reset_control_deassert(simple->resets); + if (ret) + goto err_resetc_put; + } ret = dwc3_of_simple_clk_init(simple, of_count_phandle_with_args(np, "clocks", "#clock-cells")); @@ -124,7 +138,8 @@ static int dwc3_of_simple_probe(struct platform_device *pdev) return 0; err_resetc_assert: - reset_control_assert(simple->resets); + if (!simple->pulse_resets) + reset_control_assert(simple->resets); err_resetc_put: reset_control_put(simple->resets); @@ -145,7 +160,9 @@ static int dwc3_of_simple_remove(struct platform_device *pdev) } simple->num_clocks = 0; - reset_control_assert(simple->resets); + if (!simple->pulse_resets) + reset_control_assert(simple->resets); + reset_control_put(simple->resets); pm_runtime_put_sync(dev); @@ -196,6 +213,8 @@ static const struct of_device_id of_dwc3_simple_match[] = { { .compatible = "xlnx,zynqmp-dwc3" }, { .compatible = "cavium,octeon-7130-usb-uctl" }, { .compatible = "sprd,sc9860-dwc3" }, + { .compatible = "amlogic,meson-axg-dwc3" }, + { .compatible = "amlogic,meson-gxl-dwc3" }, { /* Sentinel */ } }; MODULE_DEVICE_TABLE(of, of_dwc3_simple_match); diff --git a/drivers/usb/dwc3/dwc3-pci.c b/drivers/usb/dwc3/dwc3-pci.c index 3ba11136ebf0..c961a94d136b 100644 --- a/drivers/usb/dwc3/dwc3-pci.c +++ b/drivers/usb/dwc3/dwc3-pci.c @@ -222,7 +222,7 @@ static int dwc3_pci_probe(struct pci_dev *pci, ret = platform_device_add_resources(dwc->dwc3, res, ARRAY_SIZE(res)); if (ret) { dev_err(dev, "couldn't add resources to dwc3 device\n"); - return ret; + goto err; } dwc->pci = pci; diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 18be31d5743a..5a991bca8ed7 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -814,7 +814,7 @@ out: static void dwc3_ep0_complete_data(struct dwc3 *dwc, const struct dwc3_event_depevt *event) { - struct dwc3_request *r = NULL; + struct dwc3_request *r; struct usb_request *ur; struct dwc3_trb *trb; struct dwc3_ep *ep0; diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 2bda4eb1e9ac..8796a5ee9bb9 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -166,18 +166,8 @@ static void dwc3_ep_inc_deq(struct dwc3_ep *dep) dwc3_ep_inc_trb(&dep->trb_dequeue); } -/** - * dwc3_gadget_giveback - call struct usb_request's ->complete callback - * @dep: The endpoint to whom the request belongs to - * @req: The request we're giving back - * @status: completion code for the request - * - * Must be called with controller's lock held and interrupts disabled. This - * function will unmap @req and call its ->complete() callback to notify upper - * layers that it has completed. - */ -void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, - int status) +void dwc3_gadget_del_and_unmap_request(struct dwc3_ep *dep, + struct dwc3_request *req, int status) { struct dwc3 *dwc = dep->dwc; @@ -190,18 +180,35 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, if (req->trb) usb_gadget_unmap_request_by_dev(dwc->sysdev, - &req->request, req->direction); + &req->request, req->direction); req->trb = NULL; - trace_dwc3_gadget_giveback(req); + if (dep->number > 1) + pm_runtime_put(dwc->dev); +} + +/** + * dwc3_gadget_giveback - call struct usb_request's ->complete callback + * @dep: The endpoint to whom the request belongs to + * @req: The request we're giving back + * @status: completion code for the request + * + * Must be called with controller's lock held and interrupts disabled. This + * function will unmap @req and call its ->complete() callback to notify upper + * layers that it has completed. + */ +void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, + int status) +{ + struct dwc3 *dwc = dep->dwc; + + dwc3_gadget_del_and_unmap_request(dep, req, status); + spin_unlock(&dwc->lock); usb_gadget_giveback_request(&dep->endpoint, &req->request); spin_lock(&dwc->lock); - - if (dep->number > 1) - pm_runtime_put(dwc->dev); } /** @@ -1227,7 +1234,7 @@ 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_giveback(dep, req, ret); + dwc3_gadget_del_and_unmap_request(dep, req, ret); return ret; } @@ -1858,7 +1865,11 @@ static int __dwc3_gadget_start(struct dwc3 *dwc) * bursts of data without going through any sort of endpoint throttling. */ reg = dwc3_readl(dwc->regs, DWC3_GRXTHRCFG); - reg &= ~DWC3_GRXTHRCFG_PKTCNTSEL; + if (dwc3_is_usb31(dwc)) + reg &= ~DWC31_GRXTHRCFG_PKTCNTSEL; + else + reg &= ~DWC3_GRXTHRCFG_PKTCNTSEL; + dwc3_writel(dwc->regs, DWC3_GRXTHRCFG, reg); dwc3_gadget_setup_nump(dwc); @@ -1950,6 +1961,7 @@ static int dwc3_gadget_stop(struct usb_gadget *g) struct dwc3 *dwc = gadget_to_dwc(g); unsigned long flags; int epnum; + u32 tmo_eps = 0; spin_lock_irqsave(&dwc->lock, flags); @@ -1960,6 +1972,7 @@ static int dwc3_gadget_stop(struct usb_gadget *g) for (epnum = 2; epnum < DWC3_ENDPOINTS_NUM; epnum++) { struct dwc3_ep *dep = dwc->eps[epnum]; + int ret; if (!dep) continue; @@ -1967,9 +1980,24 @@ static int dwc3_gadget_stop(struct usb_gadget *g) if (!(dep->flags & DWC3_EP_END_TRANSFER_PENDING)) continue; - wait_event_lock_irq(dep->wait_end_transfer, - !(dep->flags & DWC3_EP_END_TRANSFER_PENDING), - dwc->lock); + ret = wait_event_interruptible_lock_irq_timeout(dep->wait_end_transfer, + !(dep->flags & DWC3_EP_END_TRANSFER_PENDING), + dwc->lock, msecs_to_jiffies(5)); + + if (ret <= 0) { + /* Timed out or interrupted! There's nothing much + * we can do so we just log here and print which + * endpoints timed out at the end. + */ + tmo_eps |= 1 << epnum; + dep->flags &= DWC3_EP_END_TRANSFER_PENDING; + } + } + + if (tmo_eps) { + dev_err(dwc->dev, + "end transfer timed out on endpoints 0x%x [bitmap]\n", + tmo_eps); } out: @@ -2023,7 +2051,10 @@ static void dwc3_gadget_set_speed(struct usb_gadget *g, reg |= DWC3_DCFG_SUPERSPEED; break; case USB_SPEED_SUPER_PLUS: - reg |= DWC3_DCFG_SUPERSPEED_PLUS; + if (dwc3_is_usb31(dwc)) + reg |= DWC3_DCFG_SUPERSPEED_PLUS; + else + reg |= DWC3_DCFG_SUPERSPEED; break; default: dev_err(dwc->dev, "invalid speed (%d)\n", speed); @@ -2101,7 +2132,10 @@ static int dwc3_gadget_init_endpoints(struct dwc3 *dwc, u8 total) mdwidth /= 8; size = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(num)); - size = DWC3_GTXFIFOSIZ_TXFDEF(size); + if (dwc3_is_usb31(dwc)) + size = DWC31_GTXFIFOSIZ_TXFDEF(size); + else + size = DWC3_GTXFIFOSIZ_TXFDEF(size); /* FIFO Depth is in MDWDITH bytes. Multiply */ size *= mdwidth; diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 77c7ecca816a..63a7cb87514a 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -1422,11 +1422,12 @@ static int count_ext_compat(struct usb_configuration *c) return res; } -static void fill_ext_compat(struct usb_configuration *c, u8 *buf) +static int fill_ext_compat(struct usb_configuration *c, u8 *buf) { int i, count; count = 16; + buf += 16; for (i = 0; i < c->next_interface_id; ++i) { struct usb_function *f; int j; @@ -1449,10 +1450,12 @@ static void fill_ext_compat(struct usb_configuration *c, u8 *buf) buf += 23; } count += 24; - if (count >= 4096) - return; + if (count + 24 >= USB_COMP_EP0_OS_DESC_BUFSIZ) + return count; } } + + return count; } static int count_ext_prop(struct usb_configuration *c, int interface) @@ -1497,25 +1500,21 @@ static int fill_ext_prop(struct usb_configuration *c, int interface, u8 *buf) struct usb_os_desc *d; struct usb_os_desc_ext_prop *ext_prop; int j, count, n, ret; - u8 *start = buf; f = c->interface[interface]; + count = 10; /* header length */ + buf += 10; for (j = 0; j < f->os_desc_n; ++j) { if (interface != f->os_desc_table[j].if_id) continue; d = f->os_desc_table[j].os_desc; if (d) list_for_each_entry(ext_prop, &d->ext_prop, entry) { - /* 4kB minus header length */ - n = buf - start; - if (n >= 4086) - return 0; - - count = ext_prop->data_len + + n = ext_prop->data_len + ext_prop->name_len + 14; - if (count > 4086 - n) - return -EINVAL; - usb_ext_prop_put_size(buf, count); + if (count + n >= USB_COMP_EP0_OS_DESC_BUFSIZ) + return count; + usb_ext_prop_put_size(buf, n); usb_ext_prop_put_type(buf, ext_prop->type); ret = usb_ext_prop_put_name(buf, ext_prop->name, ext_prop->name_len); @@ -1541,11 +1540,12 @@ static int fill_ext_prop(struct usb_configuration *c, int interface, u8 *buf) default: return -EINVAL; } - buf += count; + buf += n; + count += n; } } - return 0; + return count; } /* @@ -1827,6 +1827,7 @@ unknown: req->complete = composite_setup_complete; buf = req->buf; os_desc_cfg = cdev->os_desc_config; + w_length = min_t(u16, w_length, USB_COMP_EP0_OS_DESC_BUFSIZ); memset(buf, 0, w_length); buf[5] = 0x01; switch (ctrl->bRequestType & USB_RECIP_MASK) { @@ -1834,24 +1835,16 @@ unknown: if (w_index != 0x4 || (w_value >> 8)) break; buf[6] = w_index; - if (w_length == 0x10) { - /* Number of ext compat interfaces */ - count = count_ext_compat(os_desc_cfg); - buf[8] = count; - count *= 24; /* 24 B/ext compat desc */ - count += 16; /* header */ - put_unaligned_le32(count, buf); - value = w_length; - } else { - /* "extended compatibility ID"s */ - count = count_ext_compat(os_desc_cfg); - buf[8] = count; - count *= 24; /* 24 B/ext compat desc */ - count += 16; /* header */ - put_unaligned_le32(count, buf); - buf += 16; - fill_ext_compat(os_desc_cfg, buf); - value = w_length; + /* Number of ext compat interfaces */ + count = count_ext_compat(os_desc_cfg); + buf[8] = count; + count *= 24; /* 24 B/ext compat desc */ + count += 16; /* header */ + put_unaligned_le32(count, buf); + value = w_length; + if (w_length > 0x10) { + value = fill_ext_compat(os_desc_cfg, buf); + value = min_t(u16, w_length, value); } break; case USB_RECIP_INTERFACE: @@ -1859,47 +1852,23 @@ unknown: break; interface = w_value & 0xFF; buf[6] = w_index; - if (w_length == 0x0A) { - count = count_ext_prop(os_desc_cfg, - interface); - put_unaligned_le16(count, buf + 8); - count = len_ext_prop(os_desc_cfg, - interface); - put_unaligned_le32(count, buf); - - value = w_length; - } else { - count = count_ext_prop(os_desc_cfg, - interface); - put_unaligned_le16(count, buf + 8); - count = len_ext_prop(os_desc_cfg, - interface); - put_unaligned_le32(count, buf); - buf += 10; + count = count_ext_prop(os_desc_cfg, + interface); + put_unaligned_le16(count, buf + 8); + count = len_ext_prop(os_desc_cfg, + interface); + put_unaligned_le32(count, buf); + value = w_length; + if (w_length > 0x0A) { value = fill_ext_prop(os_desc_cfg, interface, buf); - if (value < 0) - return value; - - value = w_length; + if (value >= 0) + value = min_t(u16, w_length, value); } break; } - if (value >= 0) { - req->length = value; - req->context = cdev; - req->zero = value < w_length; - value = composite_ep0_queue(cdev, req, - GFP_ATOMIC); - if (value < 0) { - DBG(cdev, "ep_queue --> %d\n", value); - req->status = 0; - composite_setup_complete(gadget->ep0, - req); - } - } - return value; + goto check_value; } VDBG(cdev, @@ -1973,6 +1942,7 @@ try_fun_setup: goto done; } +check_value: /* respond with data transfer before status phase? */ if (value >= 0 && value != USB_GADGET_DELAYED_STATUS) { req->length = value; @@ -2156,8 +2126,8 @@ int composite_os_desc_req_prepare(struct usb_composite_dev *cdev, goto end; } - /* OS feature descriptor length <= 4kB */ - cdev->os_desc_req->buf = kmalloc(4096, GFP_KERNEL); + cdev->os_desc_req->buf = kmalloc(USB_COMP_EP0_OS_DESC_BUFSIZ, + GFP_KERNEL); if (!cdev->os_desc_req->buf) { ret = -ENOMEM; usb_ep_free_request(ep0, cdev->os_desc_req); @@ -2172,6 +2142,7 @@ end: void composite_dev_cleanup(struct usb_composite_dev *cdev) { struct usb_gadget_string_container *uc, *tmp; + struct usb_ep *ep, *tmp_ep; list_for_each_entry_safe(uc, tmp, &cdev->gstrings, list) { list_del(&uc->list); @@ -2193,6 +2164,21 @@ void composite_dev_cleanup(struct usb_composite_dev *cdev) } cdev->next_string_id = 0; device_remove_file(&cdev->gadget->dev, &dev_attr_suspended); + + /* + * Some UDC backends have a dynamic EP allocation scheme. + * + * In that case, the dispose() callback is used to notify the + * backend that the EPs are no longer in use. + * + * Note: The UDC backend can remove the EP from the ep_list as + * a result, so we need to use the _safe list iterator. + */ + list_for_each_entry_safe(ep, tmp_ep, + &cdev->gadget->ep_list, ep_list) { + if (ep->ops->dispose) + ep->ops->dispose(ep); + } } static int composite_bind(struct usb_gadget *gadget, diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index d2428a9e8900..0294e4f18873 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -758,9 +758,13 @@ static void ffs_user_copy_worker(struct work_struct *work) bool kiocb_has_eventfd = io_data->kiocb->ki_flags & IOCB_EVENTFD; if (io_data->read && ret > 0) { + mm_segment_t oldfs = get_fs(); + + set_fs(USER_DS); use_mm(io_data->mm); ret = ffs_copy_to_iter(io_data->buf, ret, &io_data->data); unuse_mm(io_data->mm); + set_fs(oldfs); } io_data->kiocb->ki_complete(io_data->kiocb, ret, ret); @@ -3238,7 +3242,7 @@ static int ffs_func_setup(struct usb_function *f, __ffs_event_add(ffs, FUNCTIONFS_SETUP); spin_unlock_irqrestore(&ffs->ev.waitq.lock, flags); - return 0; + return USB_GADGET_DELAYED_STATUS; } static bool ffs_func_req_match(struct usb_function *f, diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c index 4eb96b91cc40..e8f35db42394 100644 --- a/drivers/usb/gadget/function/f_midi.c +++ b/drivers/usb/gadget/function/f_midi.c @@ -404,7 +404,8 @@ static int f_midi_set_alt(struct usb_function *f, unsigned intf, unsigned alt) if (err) { ERROR(midi, "%s: couldn't enqueue request: %d\n", midi->out_ep->name, err); - free_ep_req(midi->out_ep, req); + if (req->buf != NULL) + free_ep_req(midi->out_ep, req); return err; } } diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c index da81cf16b850..d78dbb73bde8 100644 --- a/drivers/usb/gadget/function/f_tcm.c +++ b/drivers/usb/gadget/function/f_tcm.c @@ -1094,7 +1094,7 @@ static int usbg_submit_command(struct f_uas *fu, struct command_iu *cmd_iu = cmdbuf; struct usbg_cmd *cmd; struct usbg_tpg *tpg = fu->tpg; - struct tcm_usbg_nexus *tv_nexus = tpg->tpg_nexus; + struct tcm_usbg_nexus *tv_nexus; u32 cmd_len; u16 scsi_tag; diff --git a/drivers/usb/gadget/legacy/mass_storage.c b/drivers/usb/gadget/legacy/mass_storage.c index ef3d25259b0e..fd5595ac5bf7 100644 --- a/drivers/usb/gadget/legacy/mass_storage.c +++ b/drivers/usb/gadget/legacy/mass_storage.c @@ -225,7 +225,7 @@ static int msg_unbind(struct usb_composite_dev *cdev) static struct usb_composite_driver msg_driver = { .name = "g_mass_storage", .dev = &msg_device_desc, - .max_speed = USB_SPEED_SUPER, + .max_speed = USB_SPEED_SUPER_PLUS, .needs_serial = 1, .strings = dev_strings, .bind = msg_bind, diff --git a/drivers/usb/gadget/u_f.h b/drivers/usb/gadget/u_f.h index c3fbef2bb5db..09f90447fed5 100644 --- a/drivers/usb/gadget/u_f.h +++ b/drivers/usb/gadget/u_f.h @@ -61,7 +61,9 @@ struct usb_request *alloc_ep_req(struct usb_ep *ep, size_t len); /* Frees a usb_request previously allocated by alloc_ep_req() */ static inline void free_ep_req(struct usb_ep *ep, struct usb_request *req) { + WARN_ON(req->buf == NULL); kfree(req->buf); + req->buf = NULL; usb_ep_free_request(ep, req); } diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c index 075eaaa8a408..27c16399c7e8 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.c +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -23,7 +23,8 @@ #include <linux/usb/atmel_usba_udc.h> #include <linux/delay.h> #include <linux/of.h> -#include <linux/of_gpio.h> +#include <linux/irq.h> +#include <linux/gpio/consumer.h> #include "atmel_usba_udc.h" #define USBA_VBUS_IRQFLAGS (IRQF_ONESHOT \ @@ -415,8 +416,8 @@ static inline void usba_int_enb_set(struct usba_udc *udc, u32 val) static int vbus_is_present(struct usba_udc *udc) { - if (gpio_is_valid(udc->vbus_pin)) - return gpio_get_value(udc->vbus_pin) ^ udc->vbus_pin_inverted; + if (udc->vbus_pin) + return gpiod_get_value(udc->vbus_pin) ^ udc->vbus_pin_inverted; /* No Vbus detection: Assume always present */ return 1; @@ -1975,8 +1976,8 @@ static int atmel_usba_start(struct usb_gadget *gadget, mutex_lock(&udc->vbus_mutex); - if (gpio_is_valid(udc->vbus_pin)) - enable_irq(gpio_to_irq(udc->vbus_pin)); + if (udc->vbus_pin) + enable_irq(gpiod_to_irq(udc->vbus_pin)); /* If Vbus is present, enable the controller and wait for reset */ udc->vbus_prev = vbus_is_present(udc); @@ -1990,8 +1991,8 @@ static int atmel_usba_start(struct usb_gadget *gadget, return 0; err: - if (gpio_is_valid(udc->vbus_pin)) - disable_irq(gpio_to_irq(udc->vbus_pin)); + if (udc->vbus_pin) + disable_irq(gpiod_to_irq(udc->vbus_pin)); mutex_unlock(&udc->vbus_mutex); @@ -2006,8 +2007,8 @@ static int atmel_usba_stop(struct usb_gadget *gadget) { struct usba_udc *udc = container_of(gadget, struct usba_udc, gadget); - if (gpio_is_valid(udc->vbus_pin)) - disable_irq(gpio_to_irq(udc->vbus_pin)); + if (udc->vbus_pin) + disable_irq(gpiod_to_irq(udc->vbus_pin)); if (fifo_mode == 0) udc->configured_ep = 1; @@ -2019,7 +2020,6 @@ static int atmel_usba_stop(struct usb_gadget *gadget) return 0; } -#ifdef CONFIG_OF static void at91sam9rl_toggle_bias(struct usba_udc *udc, int is_on) { regmap_update_bits(udc->pmc, AT91_CKGR_UCKR, AT91_PMC_BIASEN, @@ -2055,7 +2055,6 @@ static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev, { u32 val; const char *name; - enum of_gpio_flags flags; struct device_node *np = pdev->dev.of_node; const struct of_device_id *match; struct device_node *pp; @@ -2075,9 +2074,9 @@ static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev, udc->num_ep = 0; - udc->vbus_pin = of_get_named_gpio_flags(np, "atmel,vbus-gpio", 0, - &flags); - udc->vbus_pin_inverted = (flags & OF_GPIO_ACTIVE_LOW) ? 1 : 0; + udc->vbus_pin = devm_gpiod_get_optional(&pdev->dev, "atmel,vbus", + GPIOD_IN); + udc->vbus_pin_inverted = gpiod_is_active_low(udc->vbus_pin); if (fifo_mode == 0) { pp = NULL; @@ -2204,75 +2203,10 @@ static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev, err: return ERR_PTR(ret); } -#else -static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev, - struct usba_udc *udc) -{ - return ERR_PTR(-ENOSYS); -} -#endif - -static struct usba_ep * usba_udc_pdata(struct platform_device *pdev, - struct usba_udc *udc) -{ - struct usba_platform_data *pdata = dev_get_platdata(&pdev->dev); - struct usba_ep *eps; - int i; - - if (!pdata) - return ERR_PTR(-ENXIO); - - eps = devm_kzalloc(&pdev->dev, sizeof(struct usba_ep) * pdata->num_ep, - GFP_KERNEL); - if (!eps) - return ERR_PTR(-ENOMEM); - - udc->gadget.ep0 = &eps[0].ep; - - udc->vbus_pin = pdata->vbus_pin; - udc->vbus_pin_inverted = pdata->vbus_pin_inverted; - udc->num_ep = pdata->num_ep; - - INIT_LIST_HEAD(&eps[0].ep.ep_list); - - for (i = 0; i < pdata->num_ep; i++) { - struct usba_ep *ep = &eps[i]; - - ep->ep_regs = udc->regs + USBA_EPT_BASE(i); - ep->dma_regs = udc->regs + USBA_DMA_BASE(i); - ep->fifo = udc->fifo + USBA_FIFO_BASE(i); - ep->ep.ops = &usba_ep_ops; - ep->ep.name = pdata->ep[i].name; - ep->fifo_size = pdata->ep[i].fifo_size; - usb_ep_set_maxpacket_limit(&ep->ep, ep->fifo_size); - ep->udc = udc; - INIT_LIST_HEAD(&ep->queue); - ep->nr_banks = pdata->ep[i].nr_banks; - ep->index = pdata->ep[i].index; - ep->can_dma = pdata->ep[i].can_dma; - ep->can_isoc = pdata->ep[i].can_isoc; - - if (i == 0) { - ep->ep.caps.type_control = true; - } else { - ep->ep.caps.type_iso = ep->can_isoc; - ep->ep.caps.type_bulk = true; - ep->ep.caps.type_int = true; - } - - ep->ep.caps.dir_in = true; - ep->ep.caps.dir_out = true; - - if (i) - list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); - } - - return eps; -} static int usba_udc_probe(struct platform_device *pdev) { - struct resource *regs, *fifo; + struct resource *res; struct clk *pclk, *hclk; struct usba_udc *udc; int irq, ret, i; @@ -2284,10 +2218,18 @@ static int usba_udc_probe(struct platform_device *pdev) udc->gadget = usba_gadget_template; INIT_LIST_HEAD(&udc->gadget.ep_list); - regs = platform_get_resource(pdev, IORESOURCE_MEM, CTRL_IOMEM_ID); - fifo = platform_get_resource(pdev, IORESOURCE_MEM, FIFO_IOMEM_ID); - if (!regs || !fifo) - return -ENXIO; + res = platform_get_resource(pdev, IORESOURCE_MEM, CTRL_IOMEM_ID); + udc->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(udc->regs)) + return PTR_ERR(udc->regs); + dev_info(&pdev->dev, "MMIO registers at %pR mapped at %p\n", + res, udc->regs); + + res = platform_get_resource(pdev, IORESOURCE_MEM, FIFO_IOMEM_ID); + udc->fifo = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(udc->fifo)) + return PTR_ERR(udc->fifo); + dev_info(&pdev->dev, "FIFO at %pR mapped at %p\n", res, udc->fifo); irq = platform_get_irq(pdev, 0); if (irq < 0) @@ -2305,23 +2247,6 @@ static int usba_udc_probe(struct platform_device *pdev) udc->pdev = pdev; udc->pclk = pclk; udc->hclk = hclk; - udc->vbus_pin = -ENODEV; - - ret = -ENOMEM; - udc->regs = devm_ioremap(&pdev->dev, regs->start, resource_size(regs)); - if (!udc->regs) { - dev_err(&pdev->dev, "Unable to map I/O memory, aborting.\n"); - return ret; - } - dev_info(&pdev->dev, "MMIO registers at 0x%08lx mapped at %p\n", - (unsigned long)regs->start, udc->regs); - udc->fifo = devm_ioremap(&pdev->dev, fifo->start, resource_size(fifo)); - if (!udc->fifo) { - dev_err(&pdev->dev, "Unable to map FIFO, aborting.\n"); - return ret; - } - dev_info(&pdev->dev, "FIFO at 0x%08lx mapped at %p\n", - (unsigned long)fifo->start, udc->fifo); platform_set_drvdata(pdev, udc); @@ -2335,10 +2260,7 @@ static int usba_udc_probe(struct platform_device *pdev) usba_writel(udc, CTRL, USBA_DISABLE_MASK); clk_disable_unprepare(pclk); - if (pdev->dev.of_node) - udc->usba_ep = atmel_udc_of_init(pdev, udc); - else - udc->usba_ep = usba_udc_pdata(pdev, udc); + udc->usba_ep = atmel_udc_of_init(pdev, udc); toggle_bias(udc, 0); @@ -2354,24 +2276,18 @@ static int usba_udc_probe(struct platform_device *pdev) } udc->irq = irq; - if (gpio_is_valid(udc->vbus_pin)) { - if (!devm_gpio_request(&pdev->dev, udc->vbus_pin, "atmel_usba_udc")) { - irq_set_status_flags(gpio_to_irq(udc->vbus_pin), - IRQ_NOAUTOEN); - ret = devm_request_threaded_irq(&pdev->dev, - gpio_to_irq(udc->vbus_pin), NULL, + if (udc->vbus_pin) { + irq_set_status_flags(gpiod_to_irq(udc->vbus_pin), IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(&pdev->dev, + gpiod_to_irq(udc->vbus_pin), NULL, usba_vbus_irq_thread, USBA_VBUS_IRQFLAGS, "atmel_usba_udc", udc); if (ret) { - udc->vbus_pin = -ENODEV; + udc->vbus_pin = NULL; dev_warn(&udc->pdev->dev, "failed to request vbus irq; " "assuming always on\n"); } - } else { - /* gpio_request fail so use -EINVAL for gpio_is_valid */ - udc->vbus_pin = -EINVAL; - } } ret = usb_add_gadget_udc(&pdev->dev, &udc->gadget); @@ -2423,9 +2339,9 @@ static int usba_udc_suspend(struct device *dev) * Device may wake up. We stay clocked if we failed * to request vbus irq, assuming always on. */ - if (gpio_is_valid(udc->vbus_pin)) { + if (udc->vbus_pin) { usba_stop(udc); - enable_irq_wake(gpio_to_irq(udc->vbus_pin)); + enable_irq_wake(gpiod_to_irq(udc->vbus_pin)); } out: @@ -2441,8 +2357,8 @@ static int usba_udc_resume(struct device *dev) if (!udc->driver) return 0; - if (device_may_wakeup(dev) && gpio_is_valid(udc->vbus_pin)) - disable_irq_wake(gpio_to_irq(udc->vbus_pin)); + if (device_may_wakeup(dev) && udc->vbus_pin) + disable_irq_wake(gpiod_to_irq(udc->vbus_pin)); /* If Vbus is present, enable the controller and wait for reset */ mutex_lock(&udc->vbus_mutex); @@ -2462,7 +2378,7 @@ static struct platform_driver udc_driver = { .driver = { .name = "atmel_usba_udc", .pm = &usba_udc_pm_ops, - .of_match_table = of_match_ptr(atmel_udc_dt_ids), + .of_match_table = atmel_udc_dt_ids, }, }; diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.h b/drivers/usb/gadget/udc/atmel_usba_udc.h index 860a00a6fdd0..969ce8f3c3e2 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.h +++ b/drivers/usb/gadget/udc/atmel_usba_udc.h @@ -7,6 +7,8 @@ #ifndef __LINUX_USB_GADGET_USBA_UDC_H__ #define __LINUX_USB_GADGET_USBA_UDC_H__ +#include <linux/gpio/consumer.h> + /* USB register offsets */ #define USBA_CTRL 0x0000 #define USBA_FNUM 0x0004 @@ -323,7 +325,7 @@ struct usba_udc { struct platform_device *pdev; const struct usba_udc_errata *errata; int irq; - int vbus_pin; + struct gpio_desc *vbus_pin; int vbus_pin_inverted; int num_ep; int configured_ep; diff --git a/drivers/usb/gadget/udc/bcm63xx_udc.c b/drivers/usb/gadget/udc/bcm63xx_udc.c index 465ccd1104de..3a8df8601074 100644 --- a/drivers/usb/gadget/udc/bcm63xx_udc.c +++ b/drivers/usb/gadget/udc/bcm63xx_udc.c @@ -2158,6 +2158,7 @@ static int bcm63xx_usbd_dbg_show(struct seq_file *s, void *p) return 0; } +DEFINE_SHOW_ATTRIBUTE(bcm63xx_usbd_dbg); /* * bcm63xx_iudma_dbg_show - Show IUDMA status and descriptors. @@ -2238,33 +2239,7 @@ static int bcm63xx_iudma_dbg_show(struct seq_file *s, void *p) return 0; } - -static int bcm63xx_usbd_dbg_open(struct inode *inode, struct file *file) -{ - return single_open(file, bcm63xx_usbd_dbg_show, inode->i_private); -} - -static int bcm63xx_iudma_dbg_open(struct inode *inode, struct file *file) -{ - return single_open(file, bcm63xx_iudma_dbg_show, inode->i_private); -} - -static const struct file_operations usbd_dbg_fops = { - .owner = THIS_MODULE, - .open = bcm63xx_usbd_dbg_open, - .llseek = seq_lseek, - .read = seq_read, - .release = single_release, -}; - -static const struct file_operations iudma_dbg_fops = { - .owner = THIS_MODULE, - .open = bcm63xx_iudma_dbg_open, - .llseek = seq_lseek, - .read = seq_read, - .release = single_release, -}; - +DEFINE_SHOW_ATTRIBUTE(bcm63xx_iudma_dbg); /** * bcm63xx_udc_init_debugfs - Create debugfs entries. @@ -2282,11 +2257,11 @@ static void bcm63xx_udc_init_debugfs(struct bcm63xx_udc *udc) goto err_root; usbd = debugfs_create_file("usbd", 0400, root, udc, - &usbd_dbg_fops); + &bcm63xx_usbd_dbg_fops); if (!usbd) goto err_usbd; iudma = debugfs_create_file("iudma", 0400, root, udc, - &iudma_dbg_fops); + &bcm63xx_iudma_dbg_fops); if (!iudma) goto err_iudma; diff --git a/drivers/usb/gadget/udc/bdc/bdc_ep.c b/drivers/usb/gadget/udc/bdc/bdc_ep.c index f40d4c13cfa4..03149b9d7ea7 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_ep.c +++ b/drivers/usb/gadget/udc/bdc/bdc_ep.c @@ -151,7 +151,7 @@ static int ep_bd_list_alloc(struct bdc_ep *ep) if (!bd_table) goto fail; - bd_table->start_bd = dma_pool_alloc(bdc->bd_table_pool, + bd_table->start_bd = dma_pool_zalloc(bdc->bd_table_pool, GFP_ATOMIC, &dma); if (!bd_table->start_bd) { @@ -167,7 +167,6 @@ static int ep_bd_list_alloc(struct bdc_ep *ep) (unsigned long long)bd_table->dma, prev_table); ep->bd_list.bd_table_array[index] = bd_table; - memset(bd_table->start_bd, 0, bd_p_tab * sizeof(struct bdc_bd)); if (prev_table) chain_table(prev_table, bd_table, bd_p_tab); diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index 1f8b19d9cf97..842814bc0e4f 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -238,6 +238,9 @@ EXPORT_SYMBOL_GPL(usb_ep_free_request); * arranges to poll once per interval, and the gadget driver usually will * have queued some data to transfer at that time. * + * Note that @req's ->complete() callback must never be called from + * within usb_ep_queue() as that can create deadlock situations. + * * Returns zero, or a negative error code. Endpoints that are not enabled * report errors; errors will also be * reported when the usb peripheral is disconnected. @@ -1482,7 +1485,7 @@ ssize_t name##_show(struct device *dev, \ struct device_attribute *attr, char *buf) \ { \ struct usb_udc *udc = container_of(dev, struct usb_udc, dev); \ - return snprintf(buf, PAGE_SIZE, "%s\n", \ + return scnprintf(buf, PAGE_SIZE, "%s\n", \ usb_speed_string(udc->gadget->param)); \ } \ static DEVICE_ATTR_RO(name) @@ -1497,7 +1500,7 @@ ssize_t name##_show(struct device *dev, \ struct usb_udc *udc = container_of(dev, struct usb_udc, dev); \ struct usb_gadget *gadget = udc->gadget; \ \ - return snprintf(buf, PAGE_SIZE, "%d\n", gadget->name); \ + return scnprintf(buf, PAGE_SIZE, "%d\n", gadget->name); \ } \ static DEVICE_ATTR_RO(name) diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index e744d4b7bfed..baf72f95f0f1 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -2366,7 +2366,7 @@ static inline ssize_t show_urb(char *buf, size_t size, struct urb *urb) { int ep = usb_pipeendpoint(urb->pipe); - return snprintf(buf, size, + return scnprintf(buf, size, "urb/%p %s ep%d%s%s len %d/%d\n", urb, ({ char *s; diff --git a/drivers/usb/gadget/udc/goku_udc.h b/drivers/usb/gadget/udc/goku_udc.h index 26601bf4e7a9..70023d401079 100644 --- a/drivers/usb/gadget/udc/goku_udc.h +++ b/drivers/usb/gadget/udc/goku_udc.h @@ -25,7 +25,7 @@ struct goku_udc_regs { # define INT_EP1DATASET 0x00040 # define INT_EP2DATASET 0x00080 # define INT_EP3DATASET 0x00100 -#define INT_EPnNAK(n) (0x00100 < (n)) /* 0 < n < 4 */ +#define INT_EPnNAK(n) (0x00100 << (n)) /* 0 < n < 4 */ # define INT_EP1NAK 0x00200 # define INT_EP2NAK 0x00400 # define INT_EP3NAK 0x00800 diff --git a/drivers/usb/gadget/udc/gr_udc.c b/drivers/usb/gadget/udc/gr_udc.c index b3fb1bbdb854..ca83c15d8ea4 100644 --- a/drivers/usb/gadget/udc/gr_udc.c +++ b/drivers/usb/gadget/udc/gr_udc.c @@ -179,8 +179,7 @@ static void gr_seq_ep_show(struct seq_file *seq, struct gr_ep *ep) seq_puts(seq, "\n"); } - -static int gr_seq_show(struct seq_file *seq, void *v) +static int gr_dfs_show(struct seq_file *seq, void *v) { struct gr_udc *dev = seq->private; u32 control = gr_read32(&dev->regs->control); @@ -203,19 +202,7 @@ static int gr_seq_show(struct seq_file *seq, void *v) return 0; } - -static int gr_dfs_open(struct inode *inode, struct file *file) -{ - return single_open(file, gr_seq_show, inode->i_private); -} - -static const struct file_operations gr_dfs_fops = { - .owner = THIS_MODULE, - .open = gr_dfs_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(gr_dfs); static void gr_dfs_create(struct gr_udc *dev) { diff --git a/drivers/usb/gadget/udc/pxa25x_udc.c b/drivers/usb/gadget/udc/pxa25x_udc.c index 0e3f5faa000e..d4be53559f2e 100644 --- a/drivers/usb/gadget/udc/pxa25x_udc.c +++ b/drivers/usb/gadget/udc/pxa25x_udc.c @@ -1233,8 +1233,7 @@ static const struct usb_gadget_ops pxa25x_udc_ops = { #ifdef CONFIG_USB_GADGET_DEBUG_FS -static int -udc_seq_show(struct seq_file *m, void *_d) +static int udc_debug_show(struct seq_file *m, void *_d) { struct pxa25x_udc *dev = m->private; unsigned long flags; @@ -1335,25 +1334,12 @@ done: local_irq_restore(flags); return 0; } - -static int -udc_debugfs_open(struct inode *inode, struct file *file) -{ - return single_open(file, udc_seq_show, inode->i_private); -} - -static const struct file_operations debug_fops = { - .open = udc_debugfs_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .owner = THIS_MODULE, -}; +DEFINE_SHOW_ATTRIBUTE(udc_debug); #define create_debug_files(dev) \ do { \ dev->debugfs_udc = debugfs_create_file(dev->gadget.name, \ - S_IRUGO, NULL, dev, &debug_fops); \ + S_IRUGO, NULL, dev, &udc_debug_fops); \ } while (0) #define remove_debug_files(dev) debugfs_remove(dev->debugfs_udc) diff --git a/drivers/usb/gadget/udc/pxa27x_udc.c b/drivers/usb/gadget/udc/pxa27x_udc.c index fadcf2653c3d..a58242e901df 100644 --- a/drivers/usb/gadget/udc/pxa27x_udc.c +++ b/drivers/usb/gadget/udc/pxa27x_udc.c @@ -131,6 +131,7 @@ static int state_dbg_show(struct seq_file *s, void *p) return 0; } +DEFINE_SHOW_ATTRIBUTE(state_dbg); static int queues_dbg_show(struct seq_file *s, void *p) { @@ -163,6 +164,7 @@ static int queues_dbg_show(struct seq_file *s, void *p) return 0; } +DEFINE_SHOW_ATTRIBUTE(queues_dbg); static int eps_dbg_show(struct seq_file *s, void *p) { @@ -199,45 +201,7 @@ static int eps_dbg_show(struct seq_file *s, void *p) return 0; } - -static int eps_dbg_open(struct inode *inode, struct file *file) -{ - return single_open(file, eps_dbg_show, inode->i_private); -} - -static int queues_dbg_open(struct inode *inode, struct file *file) -{ - return single_open(file, queues_dbg_show, inode->i_private); -} - -static int state_dbg_open(struct inode *inode, struct file *file) -{ - return single_open(file, state_dbg_show, inode->i_private); -} - -static const struct file_operations state_dbg_fops = { - .owner = THIS_MODULE, - .open = state_dbg_open, - .llseek = seq_lseek, - .read = seq_read, - .release = single_release, -}; - -static const struct file_operations queues_dbg_fops = { - .owner = THIS_MODULE, - .open = queues_dbg_open, - .llseek = seq_lseek, - .read = seq_read, - .release = single_release, -}; - -static const struct file_operations eps_dbg_fops = { - .owner = THIS_MODULE, - .open = eps_dbg_open, - .llseek = seq_lseek, - .read = seq_read, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(eps_dbg); static void pxa_init_debugfs(struct pxa_udc *udc) { diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index b85822f0c874..5d958da8e1bc 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -293,19 +293,6 @@ config USB_CNS3XXX_EHCI It is needed for high-speed (480Mbit/sec) USB 2.0 device support. -config USB_EHCI_ATH79 - bool "EHCI support for AR7XXX/AR9XXX SoCs (DEPRECATED)" - depends on (SOC_AR71XX || SOC_AR724X || SOC_AR913X || SOC_AR933X) - select USB_EHCI_ROOT_HUB_TT - select USB_EHCI_HCD_PLATFORM - default y - ---help--- - This option is deprecated now and the driver was removed, use - USB_EHCI_HCD_PLATFORM instead. - - Enables support for the built-in EHCI controller present - on the Atheros AR7XXX/AR9XXX SoCs. - config USB_EHCI_HCD_PLATFORM tristate "Generic EHCI driver for a platform device" default n @@ -490,18 +477,6 @@ config USB_OHCI_HCD_DAVINCI controller. This driver cannot currently be a loadable module because it lacks a proper PHY abstraction. -config USB_OHCI_ATH79 - bool "USB OHCI support for the Atheros AR71XX/AR7240 SoCs (DEPRECATED)" - depends on (SOC_AR71XX || SOC_AR724X) - select USB_OHCI_HCD_PLATFORM - default y - help - This option is deprecated now and the driver was removed, use - USB_OHCI_HCD_PLATFORM instead. - - Enables support for the built-in OHCI controller present on the - Atheros AR71XX/AR7240 SoCs. - config USB_OHCI_HCD_PPC_OF_BE bool "OHCI support for OF platform bus (big endian)" depends on PPC diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 4ede4ce12366..8a8cffe0b445 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -11,7 +11,7 @@ fhci-y += fhci-mem.o fhci-tds.o fhci-sched.o fhci-$(CONFIG_FHCI_DEBUG) += fhci-dbg.o -xhci-hcd-y := xhci.o xhci-mem.o +xhci-hcd-y := xhci.o xhci-mem.o xhci-ext-caps.o xhci-hcd-y += xhci-ring.o xhci-hub.o xhci-dbg.o xhci-hcd-y += xhci-trace.o diff --git a/drivers/usb/host/ehci-fsl.c b/drivers/usb/host/ehci-fsl.c index c5094cb88cd5..0a9fd2022acf 100644 --- a/drivers/usb/host/ehci-fsl.c +++ b/drivers/usb/host/ehci-fsl.c @@ -155,6 +155,8 @@ static int fsl_ehci_drv_probe(struct platform_device *pdev) retval = -ENODEV; goto err2; } + + hcd->skip_phy_initialization = 1; } #endif return retval; diff --git a/drivers/usb/host/ehci-mem.c b/drivers/usb/host/ehci-mem.c index 21307d862af6..4c6c08b675b5 100644 --- a/drivers/usb/host/ehci-mem.c +++ b/drivers/usb/host/ehci-mem.c @@ -73,10 +73,9 @@ static struct ehci_qh *ehci_qh_alloc (struct ehci_hcd *ehci, gfp_t flags) if (!qh) goto done; qh->hw = (struct ehci_qh_hw *) - dma_pool_alloc(ehci->qh_pool, flags, &dma); + dma_pool_zalloc(ehci->qh_pool, flags, &dma); if (!qh->hw) goto fail; - memset(qh->hw, 0, sizeof *qh->hw); qh->qh_dma = dma; // INIT_LIST_HEAD (&qh->qh_list); INIT_LIST_HEAD (&qh->qtd_list); diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c index b065a960adc2..4c306fb6b069 100644 --- a/drivers/usb/host/ehci-platform.c +++ b/drivers/usb/host/ehci-platform.c @@ -27,7 +27,6 @@ #include <linux/io.h> #include <linux/module.h> #include <linux/of.h> -#include <linux/phy/phy.h> #include <linux/platform_device.h> #include <linux/reset.h> #include <linux/usb.h> @@ -44,8 +43,6 @@ struct ehci_platform_priv { struct clk *clks[EHCI_MAX_CLKS]; struct reset_control *rsts; - struct phy **phys; - int num_phys; bool reset_on_resume; }; @@ -80,7 +77,7 @@ static int ehci_platform_power_on(struct platform_device *dev) { struct usb_hcd *hcd = platform_get_drvdata(dev); struct ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); - int clk, ret, phy_num; + int clk, ret; for (clk = 0; clk < EHCI_MAX_CLKS && priv->clks[clk]; clk++) { ret = clk_prepare_enable(priv->clks[clk]); @@ -88,24 +85,8 @@ static int ehci_platform_power_on(struct platform_device *dev) goto err_disable_clks; } - for (phy_num = 0; phy_num < priv->num_phys; phy_num++) { - ret = phy_init(priv->phys[phy_num]); - if (ret) - goto err_exit_phy; - ret = phy_power_on(priv->phys[phy_num]); - if (ret) { - phy_exit(priv->phys[phy_num]); - goto err_exit_phy; - } - } - return 0; -err_exit_phy: - while (--phy_num >= 0) { - phy_power_off(priv->phys[phy_num]); - phy_exit(priv->phys[phy_num]); - } err_disable_clks: while (--clk >= 0) clk_disable_unprepare(priv->clks[clk]); @@ -117,12 +98,7 @@ static void ehci_platform_power_off(struct platform_device *dev) { struct usb_hcd *hcd = platform_get_drvdata(dev); struct ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); - int clk, phy_num; - - for (phy_num = 0; phy_num < priv->num_phys; phy_num++) { - phy_power_off(priv->phys[phy_num]); - phy_exit(priv->phys[phy_num]); - } + int clk; for (clk = EHCI_MAX_CLKS - 1; clk >= 0; clk--) if (priv->clks[clk]) @@ -149,7 +125,7 @@ static int ehci_platform_probe(struct platform_device *dev) struct usb_ehci_pdata *pdata = dev_get_platdata(&dev->dev); struct ehci_platform_priv *priv; struct ehci_hcd *ehci; - int err, irq, phy_num, clk = 0; + int err, irq, clk = 0; if (usb_disabled()) return -ENODEV; @@ -202,29 +178,6 @@ static int ehci_platform_probe(struct platform_device *dev) "has-transaction-translator")) hcd->has_tt = 1; - priv->num_phys = of_count_phandle_with_args(dev->dev.of_node, - "phys", "#phy-cells"); - - if (priv->num_phys > 0) { - priv->phys = devm_kcalloc(&dev->dev, priv->num_phys, - sizeof(struct phy *), GFP_KERNEL); - if (!priv->phys) - return -ENOMEM; - } else - priv->num_phys = 0; - - for (phy_num = 0; phy_num < priv->num_phys; phy_num++) { - priv->phys[phy_num] = devm_of_phy_get_by_index( - &dev->dev, dev->dev.of_node, phy_num); - if (IS_ERR(priv->phys[phy_num])) { - err = PTR_ERR(priv->phys[phy_num]); - goto err_put_hcd; - } else if (!hcd->phy) { - /* Avoiding phy_get() in usb_add_hcd() */ - hcd->phy = priv->phys[phy_num]; - } - } - for (clk = 0; clk < EHCI_MAX_CLKS; clk++) { priv->clks[clk] = of_clk_get(dev->dev.of_node, clk); if (IS_ERR(priv->clks[clk])) { @@ -306,7 +259,7 @@ err_reset: err_put_clks: while (--clk >= 0) clk_put(priv->clks[clk]); -err_put_hcd: + if (pdata == &ehci_platform_defaults) dev->dev.platform_data = NULL; diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c index e56db44708bc..28e2a338b481 100644 --- a/drivers/usb/host/ehci-sched.c +++ b/drivers/usb/host/ehci-sched.c @@ -1287,7 +1287,7 @@ itd_urb_transaction( } else { alloc_itd: spin_unlock_irqrestore(&ehci->lock, flags); - itd = dma_pool_alloc(ehci->itd_pool, mem_flags, + itd = dma_pool_zalloc(ehci->itd_pool, mem_flags, &itd_dma); spin_lock_irqsave(&ehci->lock, flags); if (!itd) { @@ -1297,7 +1297,6 @@ itd_urb_transaction( } } - memset(itd, 0, sizeof(*itd)); itd->itd_dma = itd_dma; itd->frame = NO_FRAME; list_add(&itd->itd_list, &sched->td_list); @@ -2081,7 +2080,7 @@ sitd_urb_transaction( } else { alloc_sitd: spin_unlock_irqrestore(&ehci->lock, flags); - sitd = dma_pool_alloc(ehci->sitd_pool, mem_flags, + sitd = dma_pool_zalloc(ehci->sitd_pool, mem_flags, &sitd_dma); spin_lock_irqsave(&ehci->lock, flags); if (!sitd) { @@ -2091,7 +2090,6 @@ sitd_urb_transaction( } } - memset(sitd, 0, sizeof(*sitd)); sitd->sitd_dma = sitd_dma; sitd->frame = NO_FRAME; list_add(&sitd->sitd_list, &iso_sched->td_list); diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c index c809f7d2f08f..a6f4389f7e88 100644 --- a/drivers/usb/host/ehci-tegra.c +++ b/drivers/usb/host/ehci-tegra.c @@ -461,6 +461,7 @@ static int tegra_ehci_probe(struct platform_device *pdev) goto cleanup_clk_en; } hcd->usb_phy = u_phy; + hcd->skip_phy_initialization = 1; tegra->needs_double_reset = of_property_read_bool(pdev->dev.of_node, "nvidia,needs-double-reset"); diff --git a/drivers/usb/host/fhci-dbg.c b/drivers/usb/host/fhci-dbg.c index fafa91189e45..ebf9bb219f75 100644 --- a/drivers/usb/host/fhci-dbg.c +++ b/drivers/usb/host/fhci-dbg.c @@ -55,6 +55,7 @@ static int fhci_dfs_regs_show(struct seq_file *s, void *v) return 0; } +DEFINE_SHOW_ATTRIBUTE(fhci_dfs_regs); static int fhci_dfs_irq_stat_show(struct seq_file *s, void *v) { @@ -75,30 +76,7 @@ static int fhci_dfs_irq_stat_show(struct seq_file *s, void *v) return 0; } - -static int fhci_dfs_regs_open(struct inode *inode, struct file *file) -{ - return single_open(file, fhci_dfs_regs_show, inode->i_private); -} - -static int fhci_dfs_irq_stat_open(struct inode *inode, struct file *file) -{ - return single_open(file, fhci_dfs_irq_stat_show, inode->i_private); -} - -static const struct file_operations fhci_dfs_regs_fops = { - .open = fhci_dfs_regs_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static const struct file_operations fhci_dfs_irq_stat_fops = { - .open = fhci_dfs_irq_stat_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(fhci_dfs_irq_stat); void fhci_dfs_create(struct fhci_hcd *fhci) { diff --git a/drivers/usb/host/imx21-dbg.c b/drivers/usb/host/imx21-dbg.c index b964f9a51d87..a213ed6f07b5 100644 --- a/drivers/usb/host/imx21-dbg.c +++ b/drivers/usb/host/imx21-dbg.c @@ -245,6 +245,7 @@ static int debug_status_show(struct seq_file *s, void *v) return 0; } +DEFINE_SHOW_ATTRIBUTE(debug_status); static int debug_dmem_show(struct seq_file *s, void *v) { @@ -266,6 +267,7 @@ static int debug_dmem_show(struct seq_file *s, void *v) return 0; } +DEFINE_SHOW_ATTRIBUTE(debug_dmem); static int debug_etd_show(struct seq_file *s, void *v) { @@ -334,6 +336,7 @@ static int debug_etd_show(struct seq_file *s, void *v) return 0; } +DEFINE_SHOW_ATTRIBUTE(debug_etd); static void debug_statistics_show_one(struct seq_file *s, const char *name, struct debug_stats *stats) @@ -368,6 +371,7 @@ static int debug_statistics_show(struct seq_file *s, void *v) return 0; } +DEFINE_SHOW_ATTRIBUTE(debug_statistics); static void debug_isoc_show_one(struct seq_file *s, const char *name, int index, struct debug_isoc_trace *trace) @@ -409,66 +413,7 @@ static int debug_isoc_show(struct seq_file *s, void *v) return 0; } - -static int debug_status_open(struct inode *inode, struct file *file) -{ - return single_open(file, debug_status_show, inode->i_private); -} - -static int debug_dmem_open(struct inode *inode, struct file *file) -{ - return single_open(file, debug_dmem_show, inode->i_private); -} - -static int debug_etd_open(struct inode *inode, struct file *file) -{ - return single_open(file, debug_etd_show, inode->i_private); -} - -static int debug_statistics_open(struct inode *inode, struct file *file) -{ - return single_open(file, debug_statistics_show, inode->i_private); -} - -static int debug_isoc_open(struct inode *inode, struct file *file) -{ - return single_open(file, debug_isoc_show, inode->i_private); -} - -static const struct file_operations debug_status_fops = { - .open = debug_status_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static const struct file_operations debug_dmem_fops = { - .open = debug_dmem_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static const struct file_operations debug_etd_fops = { - .open = debug_etd_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static const struct file_operations debug_statistics_fops = { - .open = debug_statistics_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static const struct file_operations debug_isoc_fops = { - .open = debug_isoc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(debug_isoc); static void create_debug_files(struct imx21 *imx21) { diff --git a/drivers/usb/host/isp116x-hcd.c b/drivers/usb/host/isp116x-hcd.c index 5f9234b9cf7b..4602ed801f0a 100644 --- a/drivers/usb/host/isp116x-hcd.c +++ b/drivers/usb/host/isp116x-hcd.c @@ -1168,7 +1168,7 @@ static void dump_int(struct seq_file *s, char *label, u32 mask) mask & HCINT_SF ? " sof" : "", mask & HCINT_SO ? " so" : ""); } -static int isp116x_show_dbg(struct seq_file *s, void *unused) +static int isp116x_debug_show(struct seq_file *s, void *unused) { struct isp116x *isp116x = s->private; @@ -1196,18 +1196,7 @@ static int isp116x_show_dbg(struct seq_file *s, void *unused) return 0; } - -static int isp116x_open_seq(struct inode *inode, struct file *file) -{ - return single_open(file, isp116x_show_dbg, inode->i_private); -} - -static const struct file_operations isp116x_debug_fops = { - .open = isp116x_open_seq, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(isp116x_debug); static int create_debug_file(struct isp116x *isp116x) { diff --git a/drivers/usb/host/ohci-omap.c b/drivers/usb/host/ohci-omap.c index 0201c49bc4fc..d8d35d456456 100644 --- a/drivers/usb/host/ohci-omap.c +++ b/drivers/usb/host/ohci-omap.c @@ -230,6 +230,7 @@ static int ohci_omap_reset(struct usb_hcd *hcd) } else { return -EPROBE_DEFER; } + hcd->skip_phy_initialization = 1; ohci->start_hnp = start_hnp; } #endif diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c index 1e6c954f4b3f..65a1c3fdc88c 100644 --- a/drivers/usb/host/ohci-platform.c +++ b/drivers/usb/host/ohci-platform.c @@ -21,7 +21,7 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/err.h> -#include <linux/phy/phy.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/reset.h> @@ -38,8 +38,6 @@ struct ohci_platform_priv { struct clk *clks[OHCI_MAX_CLKS]; struct reset_control *resets; - struct phy **phys; - int num_phys; }; static const char hcd_name[] = "ohci-platform"; @@ -48,7 +46,7 @@ static int ohci_platform_power_on(struct platform_device *dev) { struct usb_hcd *hcd = platform_get_drvdata(dev); struct ohci_platform_priv *priv = hcd_to_ohci_priv(hcd); - int clk, ret, phy_num; + int clk, ret; for (clk = 0; clk < OHCI_MAX_CLKS && priv->clks[clk]; clk++) { ret = clk_prepare_enable(priv->clks[clk]); @@ -56,24 +54,8 @@ static int ohci_platform_power_on(struct platform_device *dev) goto err_disable_clks; } - for (phy_num = 0; phy_num < priv->num_phys; phy_num++) { - ret = phy_init(priv->phys[phy_num]); - if (ret) - goto err_exit_phy; - ret = phy_power_on(priv->phys[phy_num]); - if (ret) { - phy_exit(priv->phys[phy_num]); - goto err_exit_phy; - } - } - return 0; -err_exit_phy: - while (--phy_num >= 0) { - phy_power_off(priv->phys[phy_num]); - phy_exit(priv->phys[phy_num]); - } err_disable_clks: while (--clk >= 0) clk_disable_unprepare(priv->clks[clk]); @@ -85,12 +67,7 @@ static void ohci_platform_power_off(struct platform_device *dev) { struct usb_hcd *hcd = platform_get_drvdata(dev); struct ohci_platform_priv *priv = hcd_to_ohci_priv(hcd); - int clk, phy_num; - - for (phy_num = 0; phy_num < priv->num_phys; phy_num++) { - phy_power_off(priv->phys[phy_num]); - phy_exit(priv->phys[phy_num]); - } + int clk; for (clk = OHCI_MAX_CLKS - 1; clk >= 0; clk--) if (priv->clks[clk]) @@ -117,7 +94,7 @@ static int ohci_platform_probe(struct platform_device *dev) struct usb_ohci_pdata *pdata = dev_get_platdata(&dev->dev); struct ohci_platform_priv *priv; struct ohci_hcd *ohci; - int err, irq, phy_num, clk = 0; + int err, irq, clk = 0; if (usb_disabled()) return -ENODEV; @@ -169,29 +146,6 @@ static int ohci_platform_probe(struct platform_device *dev) of_property_read_u32(dev->dev.of_node, "num-ports", &ohci->num_ports); - priv->num_phys = of_count_phandle_with_args(dev->dev.of_node, - "phys", "#phy-cells"); - - if (priv->num_phys > 0) { - priv->phys = devm_kcalloc(&dev->dev, priv->num_phys, - sizeof(struct phy *), GFP_KERNEL); - if (!priv->phys) - return -ENOMEM; - } else - priv->num_phys = 0; - - for (phy_num = 0; phy_num < priv->num_phys; phy_num++) { - priv->phys[phy_num] = devm_of_phy_get_by_index( - &dev->dev, dev->dev.of_node, phy_num); - if (IS_ERR(priv->phys[phy_num])) { - err = PTR_ERR(priv->phys[phy_num]); - goto err_put_hcd; - } else if (!hcd->phy) { - /* Avoiding phy_get() in usb_add_hcd() */ - hcd->phy = priv->phys[phy_num]; - } - } - for (clk = 0; clk < OHCI_MAX_CLKS; clk++) { priv->clks[clk] = of_clk_get(dev->dev.of_node, clk); if (IS_ERR(priv->clks[clk])) { @@ -277,7 +231,7 @@ err_reset: err_put_clks: while (--clk >= 0) clk_put(priv->clks[clk]); -err_put_hcd: + if (pdata == &ohci_platform_defaults) dev->dev.platform_data = NULL; diff --git a/drivers/usb/host/sl811-hcd.c b/drivers/usb/host/sl811-hcd.c index fa88a903fa2e..5b061e599948 100644 --- a/drivers/usb/host/sl811-hcd.c +++ b/drivers/usb/host/sl811-hcd.c @@ -1381,7 +1381,7 @@ static void dump_irq(struct seq_file *s, char *label, u8 mask) (mask & SL11H_INTMASK_DP) ? " dp" : ""); } -static int sl811h_show(struct seq_file *s, void *unused) +static int sl811h_debug_show(struct seq_file *s, void *unused) { struct sl811 *sl811 = s->private; struct sl811h_ep *ep; @@ -1491,25 +1491,14 @@ static int sl811h_show(struct seq_file *s, void *unused) return 0; } - -static int sl811h_open(struct inode *inode, struct file *file) -{ - return single_open(file, sl811h_show, inode->i_private); -} - -static const struct file_operations debug_ops = { - .open = sl811h_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(sl811h_debug); /* expect just one sl811 per system */ static void create_debug_file(struct sl811 *sl811) { sl811->debug_file = debugfs_create_file("sl811h", S_IRUGO, usb_debug_root, sl811, - &debug_ops); + &sl811h_debug_fops); } static void remove_debug_file(struct sl811 *sl811) diff --git a/drivers/usb/host/whci/debug.c b/drivers/usb/host/whci/debug.c index f154e5791bfd..8ddfe3f1f693 100644 --- a/drivers/usb/host/whci/debug.c +++ b/drivers/usb/host/whci/debug.c @@ -72,7 +72,7 @@ static void qset_print(struct seq_file *s, struct whc_qset *qset) } } -static int di_print(struct seq_file *s, void *p) +static int di_show(struct seq_file *s, void *p) { struct whc *whc = s->private; int d; @@ -91,8 +91,9 @@ static int di_print(struct seq_file *s, void *p) } return 0; } +DEFINE_SHOW_ATTRIBUTE(di); -static int asl_print(struct seq_file *s, void *p) +static int asl_show(struct seq_file *s, void *p) { struct whc *whc = s->private; struct whc_qset *qset; @@ -103,8 +104,9 @@ static int asl_print(struct seq_file *s, void *p) return 0; } +DEFINE_SHOW_ATTRIBUTE(asl); -static int pzl_print(struct seq_file *s, void *p) +static int pzl_show(struct seq_file *s, void *p) { struct whc *whc = s->private; struct whc_qset *qset; @@ -118,45 +120,7 @@ static int pzl_print(struct seq_file *s, void *p) } return 0; } - -static int di_open(struct inode *inode, struct file *file) -{ - return single_open(file, di_print, inode->i_private); -} - -static int asl_open(struct inode *inode, struct file *file) -{ - return single_open(file, asl_print, inode->i_private); -} - -static int pzl_open(struct inode *inode, struct file *file) -{ - return single_open(file, pzl_print, inode->i_private); -} - -static const struct file_operations di_fops = { - .open = di_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .owner = THIS_MODULE, -}; - -static const struct file_operations asl_fops = { - .open = asl_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .owner = THIS_MODULE, -}; - -static const struct file_operations pzl_fops = { - .open = pzl_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .owner = THIS_MODULE, -}; +DEFINE_SHOW_ATTRIBUTE(pzl); void whc_dbg_init(struct whc *whc) { diff --git a/drivers/usb/host/xhci-dbgtty.c b/drivers/usb/host/xhci-dbgtty.c index 75f0b92694ba..48779c44c361 100644 --- a/drivers/usb/host/xhci-dbgtty.c +++ b/drivers/usb/host/xhci-dbgtty.c @@ -447,9 +447,10 @@ int xhci_dbc_tty_register_device(struct xhci_hcd *xhci) xhci_dbc_tty_init_port(xhci, port); tty_dev = tty_port_register_device(&port->port, dbc_tty_driver, 0, NULL); - ret = IS_ERR_OR_NULL(tty_dev); - if (ret) + if (IS_ERR(tty_dev)) { + ret = PTR_ERR(tty_dev); goto register_fail; + } ret = kfifo_alloc(&port->write_fifo, DBC_WRITE_BUF_SIZE, GFP_KERNEL); if (ret) diff --git a/drivers/usb/host/xhci-ext-caps.c b/drivers/usb/host/xhci-ext-caps.c new file mode 100644 index 000000000000..399113f9fc5c --- /dev/null +++ b/drivers/usb/host/xhci-ext-caps.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * XHCI extended capability handling + * + * Copyright (c) 2017 Hans de Goede <hdegoede@redhat.com> + */ + +#include <linux/platform_device.h> +#include "xhci.h" + +#define USB_SW_DRV_NAME "intel_xhci_usb_sw" +#define USB_SW_RESOURCE_SIZE 0x400 + +static void xhci_intel_unregister_pdev(void *arg) +{ + platform_device_unregister(arg); +} + +static int xhci_create_intel_xhci_sw_pdev(struct xhci_hcd *xhci, u32 cap_offset) +{ + struct usb_hcd *hcd = xhci_to_hcd(xhci); + struct device *dev = hcd->self.controller; + struct platform_device *pdev; + struct resource res = { 0, }; + int ret; + + pdev = platform_device_alloc(USB_SW_DRV_NAME, PLATFORM_DEVID_NONE); + if (!pdev) { + xhci_err(xhci, "couldn't allocate %s platform device\n", + USB_SW_DRV_NAME); + return -ENOMEM; + } + + res.start = hcd->rsrc_start + cap_offset; + res.end = res.start + USB_SW_RESOURCE_SIZE - 1; + res.name = USB_SW_DRV_NAME; + res.flags = IORESOURCE_MEM; + + ret = platform_device_add_resources(pdev, &res, 1); + if (ret) { + dev_err(dev, "couldn't add resources to intel_xhci_usb_sw pdev\n"); + platform_device_put(pdev); + return ret; + } + + pdev->dev.parent = dev; + + ret = platform_device_add(pdev); + if (ret) { + dev_err(dev, "couldn't register intel_xhci_usb_sw pdev\n"); + platform_device_put(pdev); + return ret; + } + + ret = devm_add_action_or_reset(dev, xhci_intel_unregister_pdev, pdev); + if (ret) { + dev_err(dev, "couldn't add unregister action for intel_xhci_usb_sw pdev\n"); + return ret; + } + + return 0; +} + +int xhci_ext_cap_init(struct xhci_hcd *xhci) +{ + void __iomem *base = &xhci->cap_regs->hc_capbase; + u32 offset, val; + int ret; + + offset = xhci_find_next_ext_cap(base, 0, 0); + + while (offset) { + val = readl(base + offset); + + switch (XHCI_EXT_CAPS_ID(val)) { + case XHCI_EXT_CAPS_VENDOR_INTEL: + if (xhci->quirks & XHCI_INTEL_USB_ROLE_SW) { + ret = xhci_create_intel_xhci_sw_pdev(xhci, + offset); + if (ret) + return ret; + } + break; + } + offset = xhci_find_next_ext_cap(base, offset, 0); + } + + return 0; +} +EXPORT_SYMBOL_GPL(xhci_ext_cap_init); diff --git a/drivers/usb/host/xhci-ext-caps.h b/drivers/usb/host/xhci-ext-caps.h index bf7316e130d3..268328c20681 100644 --- a/drivers/usb/host/xhci-ext-caps.h +++ b/drivers/usb/host/xhci-ext-caps.h @@ -39,6 +39,8 @@ #define XHCI_EXT_CAPS_ROUTE 5 /* IDs 6-9 reserved */ #define XHCI_EXT_CAPS_DEBUG 10 +/* Vendor caps */ +#define XHCI_EXT_CAPS_VENDOR_INTEL 192 /* USB Legacy Support Capability - section 7.1.1 */ #define XHCI_HC_BIOS_OWNED (1 << 16) #define XHCI_HC_OS_OWNED (1 << 24) @@ -84,7 +86,8 @@ * @base PCI MMIO registers base address. * @start address at which to start looking, (0 or HCC_PARAMS to start at * beginning of list) - * @id Extended capability ID to search for. + * @id Extended capability ID to search for, or 0 for the next + * capability * * Returns the offset of the next matching extended capability structure. * Some capabilities can occur several times, e.g., the XHCI_EXT_CAPS_PROTOCOL, @@ -110,7 +113,7 @@ static inline int xhci_find_next_ext_cap(void __iomem *base, u32 start, int id) val = readl(base + offset); if (val == ~0) return 0; - if (XHCI_EXT_CAPS_ID(val) == id && offset != start) + if (offset != start && (id == 0 || XHCI_EXT_CAPS_ID(val) == id)) return offset; next = XHCI_EXT_CAPS_NEXT(val); diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 332420d10be9..e5ace8995b3b 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -913,6 +913,8 @@ void xhci_free_virt_device(struct xhci_hcd *xhci, int slot_id) if (dev->out_ctx) xhci_free_container_ctx(xhci, dev->out_ctx); + if (dev->udev && dev->udev->slot_id) + dev->udev->slot_id = 0; kfree(xhci->devs[slot_id]); xhci->devs[slot_id] = NULL; } diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c index b0ab4d5e2751..7334da9e9779 100644 --- a/drivers/usb/host/xhci-mtk.c +++ b/drivers/usb/host/xhci-mtk.c @@ -14,7 +14,6 @@ #include <linux/mfd/syscon.h> #include <linux/module.h> #include <linux/of.h> -#include <linux/phy/phy.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/regmap.h> @@ -352,62 +351,6 @@ static const struct xhci_driver_overrides xhci_mtk_overrides __initconst = { static struct hc_driver __read_mostly xhci_mtk_hc_driver; -static int xhci_mtk_phy_init(struct xhci_hcd_mtk *mtk) -{ - int i; - int ret; - - for (i = 0; i < mtk->num_phys; i++) { - ret = phy_init(mtk->phys[i]); - if (ret) - goto exit_phy; - } - return 0; - -exit_phy: - for (; i > 0; i--) - phy_exit(mtk->phys[i - 1]); - - return ret; -} - -static int xhci_mtk_phy_exit(struct xhci_hcd_mtk *mtk) -{ - int i; - - for (i = 0; i < mtk->num_phys; i++) - phy_exit(mtk->phys[i]); - - return 0; -} - -static int xhci_mtk_phy_power_on(struct xhci_hcd_mtk *mtk) -{ - int i; - int ret; - - for (i = 0; i < mtk->num_phys; i++) { - ret = phy_power_on(mtk->phys[i]); - if (ret) - goto power_off_phy; - } - return 0; - -power_off_phy: - for (; i > 0; i--) - phy_power_off(mtk->phys[i - 1]); - - return ret; -} - -static void xhci_mtk_phy_power_off(struct xhci_hcd_mtk *mtk) -{ - unsigned int i; - - for (i = 0; i < mtk->num_phys; i++) - phy_power_off(mtk->phys[i]); -} - static int xhci_mtk_ldos_enable(struct xhci_hcd_mtk *mtk) { int ret; @@ -488,8 +431,6 @@ static int xhci_mtk_probe(struct platform_device *pdev) struct xhci_hcd *xhci; struct resource *res; struct usb_hcd *hcd; - struct phy *phy; - int phy_num; int ret = -ENODEV; int irq; @@ -529,16 +470,6 @@ static int xhci_mtk_probe(struct platform_device *pdev) return ret; } - mtk->num_phys = of_count_phandle_with_args(node, - "phys", "#phy-cells"); - if (mtk->num_phys > 0) { - mtk->phys = devm_kcalloc(dev, mtk->num_phys, - sizeof(*mtk->phys), GFP_KERNEL); - if (!mtk->phys) - return -ENOMEM; - } else { - mtk->num_phys = 0; - } pm_runtime_enable(dev); pm_runtime_get_sync(dev); device_enable_async_suspend(dev); @@ -596,23 +527,6 @@ static int xhci_mtk_probe(struct platform_device *pdev) mtk->has_ippc = false; } - for (phy_num = 0; phy_num < mtk->num_phys; phy_num++) { - phy = devm_of_phy_get_by_index(dev, node, phy_num); - if (IS_ERR(phy)) { - ret = PTR_ERR(phy); - goto put_usb2_hcd; - } - mtk->phys[phy_num] = phy; - } - - ret = xhci_mtk_phy_init(mtk); - if (ret) - goto put_usb2_hcd; - - ret = xhci_mtk_phy_power_on(mtk); - if (ret) - goto exit_phys; - device_init_wakeup(dev, true); xhci = hcd_to_xhci(hcd); @@ -630,7 +544,7 @@ static int xhci_mtk_probe(struct platform_device *pdev) dev_name(dev), hcd); if (!xhci->shared_hcd) { ret = -ENOMEM; - goto power_off_phys; + goto disable_device_wakeup; } ret = usb_add_hcd(hcd, irq, IRQF_SHARED); @@ -653,13 +567,9 @@ put_usb3_hcd: xhci_mtk_sch_exit(mtk); usb_put_hcd(xhci->shared_hcd); -power_off_phys: - xhci_mtk_phy_power_off(mtk); +disable_device_wakeup: device_init_wakeup(dev, false); -exit_phys: - xhci_mtk_phy_exit(mtk); - put_usb2_hcd: usb_put_hcd(hcd); @@ -682,8 +592,6 @@ static int xhci_mtk_remove(struct platform_device *dev) struct xhci_hcd *xhci = hcd_to_xhci(hcd); usb_remove_hcd(xhci->shared_hcd); - xhci_mtk_phy_power_off(mtk); - xhci_mtk_phy_exit(mtk); device_init_wakeup(&dev->dev, false); usb_remove_hcd(hcd); @@ -718,7 +626,6 @@ static int __maybe_unused xhci_mtk_suspend(struct device *dev) del_timer_sync(&xhci->shared_hcd->rh_timer); xhci_mtk_host_disable(mtk); - xhci_mtk_phy_power_off(mtk); xhci_mtk_clks_disable(mtk); usb_wakeup_set(mtk, true); return 0; @@ -732,7 +639,6 @@ static int __maybe_unused xhci_mtk_resume(struct device *dev) usb_wakeup_set(mtk, false); xhci_mtk_clks_enable(mtk); - xhci_mtk_phy_power_on(mtk); xhci_mtk_host_enable(mtk); xhci_dbg(xhci, "%s: restart port polling\n", __func__); diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index d9f831b67e57..f17b7eab66cf 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -178,6 +178,7 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) if (pdev->vendor == PCI_VENDOR_ID_INTEL && pdev->device == PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI) { xhci->quirks |= XHCI_SSIC_PORT_UNUSED; + xhci->quirks |= XHCI_INTEL_USB_ROLE_SW; } if (pdev->vendor == PCI_VENDOR_ID_INTEL && (pdev->device == PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI || @@ -311,6 +312,10 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) goto dealloc_usb2_hcd; } + retval = xhci_ext_cap_init(xhci); + if (retval) + goto put_usb3_hcd; + retval = usb_add_hcd(xhci->shared_hcd, dev->irq, IRQF_SHARED); if (retval) diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 6652e2d5bd2e..df327dcc2bac 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -284,6 +284,7 @@ static int xhci_plat_probe(struct platform_device *pdev) ret = usb_phy_init(hcd->usb_phy); if (ret) goto put_usb3_hcd; + hcd->skip_phy_initialization = 1; } ret = usb_add_hcd(hcd, irq, IRQF_SHARED); diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index daa94c3aed80..91a1a824673d 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1436,7 +1436,8 @@ static void handle_cmd_completion(struct xhci_hcd *xhci, case TRB_STOP_RING: WARN_ON(slot_id != TRB_TO_SLOT_ID( le32_to_cpu(cmd_trb->generic.field[3]))); - xhci_handle_cmd_stop_ep(xhci, slot_id, cmd_trb, event); + if (!cmd->completion) + xhci_handle_cmd_stop_ep(xhci, slot_id, cmd_trb, event); break; case TRB_SET_DEQ: WARN_ON(slot_id != TRB_TO_SLOT_ID( @@ -1815,8 +1816,7 @@ struct xhci_segment *trb_in_td(struct xhci_hcd *xhci, static void xhci_cleanup_halted_endpoint(struct xhci_hcd *xhci, unsigned int slot_id, unsigned int ep_index, - unsigned int stream_id, - struct xhci_td *td, union xhci_trb *ep_trb, + unsigned int stream_id, struct xhci_td *td, enum xhci_ep_reset_type reset_type) { struct xhci_virt_ep *ep = &xhci->devs[slot_id]->eps[ep_index]; @@ -1829,9 +1829,10 @@ static void xhci_cleanup_halted_endpoint(struct xhci_hcd *xhci, xhci_queue_reset_ep(xhci, command, slot_id, ep_index, reset_type); - if (reset_type == EP_HARD_RESET) + if (reset_type == EP_HARD_RESET) { + ep->ep_state |= EP_HARD_CLEAR_TOGGLE; xhci_cleanup_stalled_ring(xhci, ep_index, stream_id, td); - + } xhci_ring_cmd_db(xhci); } @@ -1922,7 +1923,7 @@ static int xhci_td_cleanup(struct xhci_hcd *xhci, struct xhci_td *td, } static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, - union xhci_trb *ep_trb, struct xhci_transfer_event *event, + struct xhci_transfer_event *event, struct xhci_virt_ep *ep, int *status) { struct xhci_virt_device *xdev; @@ -1957,8 +1958,7 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, * The class driver clears the device side halt later. */ xhci_cleanup_halted_endpoint(xhci, slot_id, ep_index, - ep_ring->stream_id, td, ep_trb, - EP_HARD_RESET); + ep_ring->stream_id, td, EP_HARD_RESET); } else { /* Update ring dequeue pointer */ while (ep_ring->dequeue != td->last_trb) @@ -2083,7 +2083,7 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td, td->urb->actual_length = requested; finish_td: - return finish_td(xhci, td, ep_trb, event, ep, status); + return finish_td(xhci, td, event, ep, status); } /* @@ -2170,7 +2170,7 @@ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, td->urb->actual_length += frame->actual_length; - return finish_td(xhci, td, ep_trb, event, ep, status); + return finish_td(xhci, td, event, ep, status); } static int skip_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, @@ -2260,7 +2260,7 @@ finish_td: remaining); td->urb->actual_length = 0; } - return finish_td(xhci, td, ep_trb, event, ep, status); + return finish_td(xhci, td, event, ep, status); } /* @@ -2318,7 +2318,7 @@ static int handle_tx_event(struct xhci_hcd *xhci, case COMP_INVALID_STREAM_TYPE_ERROR: case COMP_INVALID_STREAM_ID_ERROR: xhci_cleanup_halted_endpoint(xhci, slot_id, ep_index, 0, - NULL, NULL, EP_SOFT_RESET); + NULL, EP_SOFT_RESET); goto cleanup; case COMP_RING_UNDERRUN: case COMP_RING_OVERRUN: @@ -2584,8 +2584,7 @@ static int handle_tx_event(struct xhci_hcd *xhci, xhci_cleanup_halted_endpoint(xhci, slot_id, ep_index, ep_ring->stream_id, - td, ep_trb, - EP_HARD_RESET); + td, EP_HARD_RESET); goto cleanup; } diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 5d37700ae4b0..9b27798ecce5 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1290,7 +1290,8 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag struct xhci_hcd *xhci = hcd_to_xhci(hcd); unsigned long flags; int ret = 0; - unsigned int slot_id, ep_index, ep_state; + unsigned int slot_id, ep_index; + unsigned int *ep_state; struct urb_priv *urb_priv; int num_tds; @@ -1300,6 +1301,7 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag slot_id = urb->dev->slot_id; ep_index = xhci_get_endpoint_index(&urb->ep->desc); + ep_state = &xhci->devs[slot_id]->eps[ep_index].ep_state; if (!HCD_HW_ACCESSIBLE(hcd)) { if (!in_interrupt()) @@ -1351,6 +1353,17 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag ret = -ESHUTDOWN; goto free_priv; } + if (*ep_state & (EP_GETTING_STREAMS | EP_GETTING_NO_STREAMS)) { + xhci_warn(xhci, "WARN: Can't enqueue URB, ep in streams transition state %x\n", + *ep_state); + ret = -EINVAL; + goto free_priv; + } + if (*ep_state & EP_SOFT_CLEAR_TOGGLE) { + xhci_warn(xhci, "Can't enqueue URB while manually clearing toggle\n"); + ret = -EINVAL; + goto free_priv; + } switch (usb_endpoint_type(&urb->ep->desc)) { @@ -1359,23 +1372,13 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag slot_id, ep_index); break; case USB_ENDPOINT_XFER_BULK: - ep_state = xhci->devs[slot_id]->eps[ep_index].ep_state; - if (ep_state & (EP_GETTING_STREAMS | EP_GETTING_NO_STREAMS)) { - xhci_warn(xhci, "WARN: Can't enqueue URB, ep in streams transition state %x\n", - ep_state); - ret = -EINVAL; - break; - } ret = xhci_queue_bulk_tx(xhci, GFP_ATOMIC, urb, slot_id, ep_index); break; - - case USB_ENDPOINT_XFER_INT: ret = xhci_queue_intr_tx(xhci, GFP_ATOMIC, urb, slot_id, ep_index); break; - case USB_ENDPOINT_XFER_ISOC: ret = xhci_queue_isoc_tx_prepare(xhci, GFP_ATOMIC, urb, slot_id, ep_index); @@ -2874,33 +2877,103 @@ void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci, unsigned int ep_index, } } -/* Called when clearing halted device. The core should have sent the control - * message to clear the device halt condition. The host side of the halt should - * already be cleared with a reset endpoint command issued when the STALL tx - * event was received. +/* + * Called after usb core issues a clear halt control message. + * The host side of the halt should already be cleared by a reset endpoint + * command issued when the STALL event was received. * - * Context: in_interrupt + * The reset endpoint command may only be issued to endpoints in the halted + * state. For software that wishes to reset the data toggle or sequence number + * of an endpoint that isn't in the halted state this function will issue a + * configure endpoint command with the Drop and Add bits set for the target + * endpoint. Refer to the additional note in xhci spcification section 4.6.8. */ static void xhci_endpoint_reset(struct usb_hcd *hcd, - struct usb_host_endpoint *ep) + struct usb_host_endpoint *host_ep) { struct xhci_hcd *xhci; + struct usb_device *udev; + struct xhci_virt_device *vdev; + struct xhci_virt_ep *ep; + struct xhci_input_control_ctx *ctrl_ctx; + struct xhci_command *stop_cmd, *cfg_cmd; + unsigned int ep_index; + unsigned long flags; + u32 ep_flag; xhci = hcd_to_xhci(hcd); + if (!host_ep->hcpriv) + return; + udev = (struct usb_device *) host_ep->hcpriv; + vdev = xhci->devs[udev->slot_id]; + ep_index = xhci_get_endpoint_index(&host_ep->desc); + ep = &vdev->eps[ep_index]; + + /* Bail out if toggle is already being cleared by a endpoint reset */ + if (ep->ep_state & EP_HARD_CLEAR_TOGGLE) { + ep->ep_state &= ~EP_HARD_CLEAR_TOGGLE; + return; + } + /* Only interrupt and bulk ep's use data toggle, USB2 spec 5.5.4-> */ + if (usb_endpoint_xfer_control(&host_ep->desc) || + usb_endpoint_xfer_isoc(&host_ep->desc)) + return; + + ep_flag = xhci_get_endpoint_flag(&host_ep->desc); + + if (ep_flag == SLOT_FLAG || ep_flag == EP0_FLAG) + return; + + stop_cmd = xhci_alloc_command(xhci, true, GFP_NOWAIT); + if (!stop_cmd) + return; + + cfg_cmd = xhci_alloc_command_with_ctx(xhci, true, GFP_NOWAIT); + if (!cfg_cmd) + goto cleanup; + + spin_lock_irqsave(&xhci->lock, flags); + + /* block queuing new trbs and ringing ep doorbell */ + ep->ep_state |= EP_SOFT_CLEAR_TOGGLE; /* - * We might need to implement the config ep cmd in xhci 4.8.1 note: - * The Reset Endpoint Command may only be issued to endpoints in the - * Halted state. If software wishes reset the Data Toggle or Sequence - * Number of an endpoint that isn't in the Halted state, then software - * may issue a Configure Endpoint Command with the Drop and Add bits set - * for the target endpoint. that is in the Stopped state. + * Make sure endpoint ring is empty before resetting the toggle/seq. + * Driver is required to synchronously cancel all transfer request. + * Stop the endpoint to force xHC to update the output context */ - /* For now just print debug to follow the situation */ - xhci_dbg(xhci, "Endpoint 0x%x ep reset callback called\n", - ep->desc.bEndpointAddress); + if (!list_empty(&ep->ring->td_list)) { + dev_err(&udev->dev, "EP not empty, refuse reset\n"); + spin_unlock_irqrestore(&xhci->lock, flags); + goto cleanup; + } + xhci_queue_stop_endpoint(xhci, stop_cmd, udev->slot_id, ep_index, 0); + xhci_ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + + wait_for_completion(stop_cmd->completion); + + spin_lock_irqsave(&xhci->lock, flags); + + /* config ep command clears toggle if add and drop ep flags are set */ + ctrl_ctx = xhci_get_input_control_ctx(cfg_cmd->in_ctx); + xhci_setup_input_ctx_for_config_ep(xhci, cfg_cmd->in_ctx, vdev->out_ctx, + ctrl_ctx, ep_flag, ep_flag); + xhci_endpoint_copy(xhci, cfg_cmd->in_ctx, vdev->out_ctx, ep_index); + + xhci_queue_configure_endpoint(xhci, cfg_cmd, cfg_cmd->in_ctx->dma, + udev->slot_id, false); + xhci_ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + + wait_for_completion(cfg_cmd->completion); + + ep->ep_state &= ~EP_SOFT_CLEAR_TOGGLE; + xhci_free_command(xhci, cfg_cmd); +cleanup: + xhci_free_command(xhci, stop_cmd); } static int xhci_check_streams_endpoint(struct xhci_hcd *xhci, @@ -4768,6 +4841,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) * quirks */ struct device *dev = hcd->self.sysdev; + unsigned int minor_rev; int retval; /* Accept arbitrarily long scatter-gather lists */ @@ -4795,12 +4869,19 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) */ hcd->has_tt = 1; } else { - /* Some 3.1 hosts return sbrn 0x30, can't rely on sbrn alone */ - if (xhci->sbrn == 0x31 || xhci->usb3_rhub.min_rev >= 1) { - xhci_info(xhci, "Host supports USB 3.1 Enhanced SuperSpeed\n"); + /* + * Some 3.1 hosts return sbrn 0x30, use xhci supported protocol + * minor revision instead of sbrn + */ + minor_rev = xhci->usb3_rhub.min_rev; + if (minor_rev) { hcd->speed = HCD_USB31; hcd->self.root_hub->speed = USB_SPEED_SUPER_PLUS; } + xhci_info(xhci, "Host supports USB 3.%x %s SuperSpeed\n", + minor_rev, + minor_rev ? "Enhanced" : ""); + /* xHCI private pointer was set in xhci_pci_probe for the second * registered roothub. */ diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 866e141d4972..05c909b04f14 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -922,6 +922,8 @@ struct xhci_virt_ep { #define EP_HAS_STREAMS (1 << 4) /* Transitioning the endpoint to not using streams, don't enqueue URBs */ #define EP_GETTING_NO_STREAMS (1 << 5) +#define EP_HARD_CLEAR_TOGGLE (1 << 6) +#define EP_SOFT_CLEAR_TOGGLE (1 << 7) /* ---- Related to URB cancellation ---- */ struct list_head cancelled_td_list; /* Watchdog timer for stop endpoint command to cancel URBs */ @@ -1827,6 +1829,7 @@ struct xhci_hcd { #define XHCI_ASMEDIA_MODIFY_FLOWCONTROL (1 << 28) #define XHCI_HW_LPM_DISABLE (1 << 29) #define XHCI_SUSPEND_DELAY (1 << 30) +#define XHCI_INTEL_USB_ROLE_SW (1 << 31) unsigned int num_active_eps; unsigned int limit_active_eps; @@ -2022,6 +2025,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks); void xhci_init_driver(struct hc_driver *drv, const struct xhci_driver_overrides *over); int xhci_disable_slot(struct xhci_hcd *xhci, u32 slot_id); +int xhci_ext_cap_init(struct xhci_hcd *xhci); int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup); int xhci_resume(struct xhci_hcd *xhci, bool hibernated); diff --git a/drivers/usb/isp1760/isp1760-udc.c b/drivers/usb/isp1760/isp1760-udc.c index bac4ef5d9512..1714b2258b54 100644 --- a/drivers/usb/isp1760/isp1760-udc.c +++ b/drivers/usb/isp1760/isp1760-udc.c @@ -1441,7 +1441,6 @@ int isp1760_udc_register(struct isp1760_device *isp, int irq, unsigned long irqflags) { struct isp1760_udc *udc = &isp->udc; - const char *devname; int ret; udc->irq = -1; @@ -1455,13 +1454,10 @@ int isp1760_udc_register(struct isp1760_device *isp, int irq, if (ret < 0) return ret; - devname = dev_name(isp->dev); - udc->irqname = kmalloc(strlen(devname) + 7, GFP_KERNEL); + udc->irqname = kasprintf(GFP_KERNEL, "%s (udc)", dev_name(isp->dev)); if (!udc->irqname) return -ENOMEM; - sprintf(udc->irqname, "%s (udc)", devname); - ret = request_irq(irq, isp1760_udc_irq, IRQF_SHARED | irqflags, udc->irqname, udc); if (ret < 0) diff --git a/drivers/usb/misc/adutux.c b/drivers/usb/misc/adutux.c index 4b8712733fc7..b3160afe0458 100644 --- a/drivers/usb/misc/adutux.c +++ b/drivers/usb/misc/adutux.c @@ -54,7 +54,7 @@ MODULE_DEVICE_TABLE(usb, device_table); /* we can have up to this number of device plugged in at once */ #define MAX_DEVICES 16 -#define COMMAND_TIMEOUT (2*HZ) /* 60 second timeout for a command */ +#define COMMAND_TIMEOUT (2*HZ) /* * The locking scheme is a vanilla 3-lock: @@ -132,6 +132,8 @@ static void adu_abort_transfers(struct adu_device *dev) spin_lock_irqsave(&dev->buflock, flags); if (!dev->out_urb_finished) { spin_unlock_irqrestore(&dev->buflock, flags); + wait_event_timeout(dev->write_wait, dev->out_urb_finished, + COMMAND_TIMEOUT); usb_kill_urb(dev->interrupt_out_urb); } else spin_unlock_irqrestore(&dev->buflock, flags); diff --git a/drivers/usb/misc/chaoskey.c b/drivers/usb/misc/chaoskey.c index 716cb515523e..cf5828ce927a 100644 --- a/drivers/usb/misc/chaoskey.c +++ b/drivers/usb/misc/chaoskey.c @@ -168,14 +168,10 @@ static int chaoskey_probe(struct usb_interface *interface, */ if (udev->product && udev->serial) { - dev->name = kmalloc(strlen(udev->product) + 1 + - strlen(udev->serial) + 1, GFP_KERNEL); + dev->name = kasprintf(GFP_KERNEL, "%s-%s", udev->product, + udev->serial); if (dev->name == NULL) goto out; - - strcpy(dev->name, udev->product); - strcat(dev->name, "-"); - strcat(dev->name, udev->serial); } dev->interface = interface; diff --git a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c index 90028ef541e3..9e1142b8b91b 100644 --- a/drivers/usb/misc/usbtest.c +++ b/drivers/usb/misc/usbtest.c @@ -2028,11 +2028,14 @@ test_queue(struct usbtest_dev *dev, struct usbtest_param_32 *param, unsigned i; unsigned long packets = 0; int status = 0; - struct urb *urbs[param->sglen]; + struct urb *urbs[MAX_SGLEN]; if (!param->sglen || param->iterations > UINT_MAX / param->sglen) return -EINVAL; + if (param->sglen > MAX_SGLEN) + return -EINVAL; + memset(&context, 0, sizeof(context)); context.count = param->iterations * param->sglen; context.dev = dev; diff --git a/drivers/usb/misc/uss720.c b/drivers/usb/misc/uss720.c index 263c97fec708..de9a502491c2 100644 --- a/drivers/usb/misc/uss720.c +++ b/drivers/usb/misc/uss720.c @@ -769,10 +769,15 @@ static void uss720_disconnect(struct usb_interface *intf) /* table of cables that work through this driver */ static const struct usb_device_id uss720_table[] = { { USB_DEVICE(0x047e, 0x1001) }, + { USB_DEVICE(0x04b8, 0x0002) }, + { USB_DEVICE(0x04b8, 0x0003) }, + { USB_DEVICE(0x050d, 0x0002) }, + { USB_DEVICE(0x050d, 0x1202) }, { USB_DEVICE(0x0557, 0x2001) }, + { USB_DEVICE(0x05ab, 0x0002) }, + { USB_DEVICE(0x06c6, 0x0100) }, { USB_DEVICE(0x0729, 0x1284) }, { USB_DEVICE(0x1293, 0x0002) }, - { USB_DEVICE(0x050d, 0x0002) }, { } /* Terminating entry */ }; diff --git a/drivers/usb/musb/musb_debugfs.c b/drivers/usb/musb/musb_debugfs.c index 7dac456f7ebc..e2050cac3eae 100644 --- a/drivers/usb/musb/musb_debugfs.c +++ b/drivers/usb/musb/musb_debugfs.c @@ -110,11 +110,7 @@ static int musb_regdump_show(struct seq_file *s, void *unused) pm_runtime_put_autosuspend(musb->controller); return 0; } - -static int musb_regdump_open(struct inode *inode, struct file *file) -{ - return single_open(file, musb_regdump_show, inode->i_private); -} +DEFINE_SHOW_ATTRIBUTE(musb_regdump); static int musb_test_mode_show(struct seq_file *s, void *unused) { @@ -159,13 +155,6 @@ static int musb_test_mode_show(struct seq_file *s, void *unused) return 0; } -static const struct file_operations musb_regdump_fops = { - .open = musb_regdump_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - static int musb_test_mode_open(struct inode *inode, struct file *file) { return single_open(file, musb_test_mode_show, inode->i_private); diff --git a/drivers/usb/musb/musb_gadget_ep0.c b/drivers/usb/musb/musb_gadget_ep0.c index 18da4873e52e..91a5027b5c1f 100644 --- a/drivers/usb/musb/musb_gadget_ep0.c +++ b/drivers/usb/musb/musb_gadget_ep0.c @@ -89,15 +89,19 @@ static int service_tx_status_request( } is_in = epnum & USB_DIR_IN; - if (is_in) { - epnum &= 0x0f; + epnum &= 0x0f; + if (epnum >= MUSB_C_NUM_EPS) { + handled = -EINVAL; + break; + } + + if (is_in) ep = &musb->endpoints[epnum].ep_in; - } else { + else ep = &musb->endpoints[epnum].ep_out; - } regs = musb->endpoints[epnum].regs; - if (epnum >= MUSB_C_NUM_EPS || !ep->desc) { + if (!ep->desc) { handled = -EINVAL; break; } diff --git a/drivers/usb/phy/phy-ab8500-usb.c b/drivers/usb/phy/phy-ab8500-usb.c index 87295313a10c..66143ab8c043 100644 --- a/drivers/usb/phy/phy-ab8500-usb.c +++ b/drivers/usb/phy/phy-ab8500-usb.c @@ -29,17 +29,12 @@ /* Bank AB8500_USB */ #define AB8500_USB_LINE_STAT_REG 0x80 #define AB8505_USB_LINE_STAT_REG 0x94 -#define AB8540_USB_LINK_STAT_REG 0x94 -#define AB9540_USB_LINK_STAT_REG 0x94 -#define AB8540_USB_OTG_CTL_REG 0x87 #define AB8500_USB_PHY_CTRL_REG 0x8A -#define AB8540_VBUS_CTRL_REG 0x82 /* Bank AB8500_DEVELOPMENT */ #define AB8500_BANK12_ACCESS 0x00 /* Bank AB8500_DEBUG */ -#define AB8540_DEBUG 0x32 #define AB8500_USB_PHY_TUNE1 0x05 #define AB8500_USB_PHY_TUNE2 0x06 #define AB8500_USB_PHY_TUNE3 0x07 @@ -53,10 +48,6 @@ #define AB8500_BIT_WD_CTRL_ENABLE (1 << 0) #define AB8500_BIT_WD_CTRL_KICK (1 << 1) #define AB8500_BIT_SOURCE2_VBUSDET (1 << 7) -#define AB8540_BIT_OTG_CTL_VBUS_VALID_ENA (1 << 0) -#define AB8540_BIT_OTG_CTL_ID_HOST_ENA (1 << 1) -#define AB8540_BIT_OTG_CTL_ID_DEV_ENA (1 << 5) -#define AB8540_BIT_VBUS_CTRL_CHARG_DET_ENA (1 << 0) #define AB8500_WD_KICK_DELAY_US 100 /* usec */ #define AB8500_WD_V11_DISABLE_DELAY_US 100 /* usec */ @@ -113,68 +104,6 @@ enum ab8505_usb_link_status { USB_LINK_MOTOROLA_FACTORY_CBL_PHY_EN_8505, }; -enum ab8540_usb_link_status { - USB_LINK_NOT_CONFIGURED_8540 = 0, - USB_LINK_STD_HOST_NC_8540, - USB_LINK_STD_HOST_C_NS_8540, - USB_LINK_STD_HOST_C_S_8540, - USB_LINK_CDP_8540, - USB_LINK_RESERVED0_8540, - USB_LINK_RESERVED1_8540, - USB_LINK_DEDICATED_CHG_8540, - USB_LINK_ACA_RID_A_8540, - USB_LINK_ACA_RID_B_8540, - USB_LINK_ACA_RID_C_NM_8540, - USB_LINK_RESERVED2_8540, - USB_LINK_RESERVED3_8540, - USB_LINK_HM_IDGND_8540, - USB_LINK_CHARGERPORT_NOT_OK_8540, - USB_LINK_CHARGER_DM_HIGH_8540, - USB_LINK_PHYEN_NO_VBUS_NO_IDGND_8540, - USB_LINK_STD_UPSTREAM_NO_IDGNG_VBUS_8540, - USB_LINK_STD_UPSTREAM_8540, - USB_LINK_CHARGER_SE1_8540, - USB_LINK_CARKIT_CHGR_1_8540, - USB_LINK_CARKIT_CHGR_2_8540, - USB_LINK_ACA_DOCK_CHGR_8540, - USB_LINK_SAMSUNG_BOOT_CBL_PHY_EN_8540, - USB_LINK_SAMSUNG_BOOT_CBL_PHY_DISB_8540, - USB_LINK_SAMSUNG_UART_CBL_PHY_EN_8540, - USB_LINK_SAMSUNG_UART_CBL_PHY_DISB_8540, - USB_LINK_MOTOROLA_FACTORY_CBL_PHY_EN_8540 -}; - -enum ab9540_usb_link_status { - USB_LINK_NOT_CONFIGURED_9540 = 0, - USB_LINK_STD_HOST_NC_9540, - USB_LINK_STD_HOST_C_NS_9540, - USB_LINK_STD_HOST_C_S_9540, - USB_LINK_CDP_9540, - USB_LINK_RESERVED0_9540, - USB_LINK_RESERVED1_9540, - USB_LINK_DEDICATED_CHG_9540, - USB_LINK_ACA_RID_A_9540, - USB_LINK_ACA_RID_B_9540, - USB_LINK_ACA_RID_C_NM_9540, - USB_LINK_RESERVED2_9540, - USB_LINK_RESERVED3_9540, - USB_LINK_HM_IDGND_9540, - USB_LINK_CHARGERPORT_NOT_OK_9540, - USB_LINK_CHARGER_DM_HIGH_9540, - USB_LINK_PHYEN_NO_VBUS_NO_IDGND_9540, - USB_LINK_STD_UPSTREAM_NO_IDGNG_VBUS_9540, - USB_LINK_STD_UPSTREAM_9540, - USB_LINK_CHARGER_SE1_9540, - USB_LINK_CARKIT_CHGR_1_9540, - USB_LINK_CARKIT_CHGR_2_9540, - USB_LINK_ACA_DOCK_CHGR_9540, - USB_LINK_SAMSUNG_BOOT_CBL_PHY_EN_9540, - USB_LINK_SAMSUNG_BOOT_CBL_PHY_DISB_9540, - USB_LINK_SAMSUNG_UART_CBL_PHY_EN_9540, - USB_LINK_SAMSUNG_UART_CBL_PHY_DISB_9540, - USB_LINK_MOTOROLA_FACTORY_CBL_PHY_EN_9540 -}; - enum ab8500_usb_mode { USB_IDLE = 0, USB_PERIPHERAL, @@ -192,10 +121,6 @@ enum ab8500_usb_mode { #define AB8500_USB_FLAG_USE_AB_IDDET (1 << 3) /* Enable setting regulators voltage */ #define AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE (1 << 4) -/* Enable the check_vbus_status workaround */ -#define AB8500_USB_FLAG_USE_CHECK_VBUS_STATUS (1 << 5) -/* Enable the vbus host workaround */ -#define AB8500_USB_FLAG_USE_VBUS_HOST_QUIRK (1 << 6) struct ab8500_usb { struct usb_phy phy; @@ -203,7 +128,6 @@ struct ab8500_usb { struct ab8500 *ab8500; unsigned vbus_draw; struct work_struct phy_dis_work; - struct work_struct vbus_event_work; enum ab8500_usb_mode mode; struct clk *sysclk; struct regulator *v_ape; @@ -342,15 +266,6 @@ static void ab8500_usb_phy_enable(struct ab8500_usb *ab, bool sel_host) abx500_mask_and_set_register_interruptible(ab->dev, AB8500_USB, AB8500_USB_PHY_CTRL_REG, bit, bit); - - if (ab->flags & AB8500_USB_FLAG_USE_VBUS_HOST_QUIRK) { - if (sel_host) - abx500_set_register_interruptible(ab->dev, - AB8500_USB, AB8540_USB_OTG_CTL_REG, - AB8540_BIT_OTG_CTL_VBUS_VALID_ENA | - AB8540_BIT_OTG_CTL_ID_HOST_ENA | - AB8540_BIT_OTG_CTL_ID_DEV_ENA); - } } static void ab8500_usb_phy_disable(struct ab8500_usb *ab, bool sel_host) @@ -395,263 +310,6 @@ static void ab8500_usb_phy_disable(struct ab8500_usb *ab, bool sel_host) #define ab8500_usb_peri_phy_en(ab) ab8500_usb_phy_enable(ab, false) #define ab8500_usb_peri_phy_dis(ab) ab8500_usb_phy_disable(ab, false) -static int ab9540_usb_link_status_update(struct ab8500_usb *ab, - enum ab9540_usb_link_status lsts) -{ - enum ux500_musb_vbus_id_status event = 0; - - dev_dbg(ab->dev, "ab9540_usb_link_status_update %d\n", lsts); - - if (ab->previous_link_status_state == USB_LINK_HM_IDGND_9540 && - (lsts == USB_LINK_STD_HOST_C_NS_9540 || - lsts == USB_LINK_STD_HOST_NC_9540)) - return 0; - - if (ab->previous_link_status_state == USB_LINK_ACA_RID_A_9540 && - (lsts == USB_LINK_STD_HOST_NC_9540)) - return 0; - - ab->previous_link_status_state = lsts; - - switch (lsts) { - case USB_LINK_ACA_RID_B_9540: - event = UX500_MUSB_RIDB; - case USB_LINK_NOT_CONFIGURED_9540: - case USB_LINK_RESERVED0_9540: - case USB_LINK_RESERVED1_9540: - case USB_LINK_RESERVED2_9540: - case USB_LINK_RESERVED3_9540: - if (ab->mode == USB_PERIPHERAL) - atomic_notifier_call_chain(&ab->phy.notifier, - UX500_MUSB_CLEAN, &ab->vbus_draw); - ab->mode = USB_IDLE; - ab->phy.otg->default_a = false; - ab->vbus_draw = 0; - if (event != UX500_MUSB_RIDB) - event = UX500_MUSB_NONE; - /* Fallback to default B_IDLE as nothing is connected. */ - ab->phy.otg->state = OTG_STATE_B_IDLE; - usb_phy_set_event(&ab->phy, USB_EVENT_NONE); - break; - - case USB_LINK_ACA_RID_C_NM_9540: - event = UX500_MUSB_RIDC; - case USB_LINK_STD_HOST_NC_9540: - case USB_LINK_STD_HOST_C_NS_9540: - case USB_LINK_STD_HOST_C_S_9540: - case USB_LINK_CDP_9540: - if (ab->mode == USB_HOST) { - ab->mode = USB_PERIPHERAL; - ab8500_usb_host_phy_dis(ab); - ab8500_usb_peri_phy_en(ab); - atomic_notifier_call_chain(&ab->phy.notifier, - UX500_MUSB_PREPARE, &ab->vbus_draw); - usb_phy_set_event(&ab->phy, USB_EVENT_ENUMERATED); - } - if (ab->mode == USB_IDLE) { - ab->mode = USB_PERIPHERAL; - ab8500_usb_peri_phy_en(ab); - atomic_notifier_call_chain(&ab->phy.notifier, - UX500_MUSB_PREPARE, &ab->vbus_draw); - usb_phy_set_event(&ab->phy, USB_EVENT_ENUMERATED); - } - if (event != UX500_MUSB_RIDC) - event = UX500_MUSB_VBUS; - break; - - case USB_LINK_ACA_RID_A_9540: - event = UX500_MUSB_RIDA; - case USB_LINK_HM_IDGND_9540: - case USB_LINK_STD_UPSTREAM_9540: - if (ab->mode == USB_PERIPHERAL) { - ab->mode = USB_HOST; - ab8500_usb_peri_phy_dis(ab); - ab8500_usb_host_phy_en(ab); - atomic_notifier_call_chain(&ab->phy.notifier, - UX500_MUSB_PREPARE, &ab->vbus_draw); - } - if (ab->mode == USB_IDLE) { - ab->mode = USB_HOST; - ab8500_usb_host_phy_en(ab); - atomic_notifier_call_chain(&ab->phy.notifier, - UX500_MUSB_PREPARE, &ab->vbus_draw); - } - ab->phy.otg->default_a = true; - if (event != UX500_MUSB_RIDA) - event = UX500_MUSB_ID; - - atomic_notifier_call_chain(&ab->phy.notifier, - event, &ab->vbus_draw); - break; - - case USB_LINK_DEDICATED_CHG_9540: - ab->mode = USB_DEDICATED_CHG; - event = UX500_MUSB_CHARGER; - atomic_notifier_call_chain(&ab->phy.notifier, - event, &ab->vbus_draw); - usb_phy_set_event(&ab->phy, USB_EVENT_CHARGER); - break; - - case USB_LINK_PHYEN_NO_VBUS_NO_IDGND_9540: - case USB_LINK_STD_UPSTREAM_NO_IDGNG_VBUS_9540: - if (!(is_ab9540_2p0_or_earlier(ab->ab8500))) { - event = UX500_MUSB_NONE; - if (ab->mode == USB_HOST) { - ab->phy.otg->default_a = false; - ab->vbus_draw = 0; - atomic_notifier_call_chain(&ab->phy.notifier, - event, &ab->vbus_draw); - ab8500_usb_host_phy_dis(ab); - ab->mode = USB_IDLE; - } - if (ab->mode == USB_PERIPHERAL) { - atomic_notifier_call_chain(&ab->phy.notifier, - event, &ab->vbus_draw); - ab8500_usb_peri_phy_dis(ab); - atomic_notifier_call_chain(&ab->phy.notifier, - UX500_MUSB_CLEAN, - &ab->vbus_draw); - ab->mode = USB_IDLE; - ab->phy.otg->default_a = false; - ab->vbus_draw = 0; - usb_phy_set_event(&ab->phy, USB_EVENT_NONE); - } - } - break; - - default: - break; - } - - return 0; -} - -static int ab8540_usb_link_status_update(struct ab8500_usb *ab, - enum ab8540_usb_link_status lsts) -{ - enum ux500_musb_vbus_id_status event = 0; - - dev_dbg(ab->dev, "ab8540_usb_link_status_update %d\n", lsts); - - if (ab->enabled_charging_detection) { - /* Disable USB Charger detection */ - abx500_mask_and_set_register_interruptible(ab->dev, - AB8500_USB, AB8540_VBUS_CTRL_REG, - AB8540_BIT_VBUS_CTRL_CHARG_DET_ENA, 0x00); - ab->enabled_charging_detection = false; - } - - /* - * Spurious link_status interrupts are seen in case of a - * disconnection of a device in IDGND and RIDA stage - */ - if (ab->previous_link_status_state == USB_LINK_HM_IDGND_8540 && - (lsts == USB_LINK_STD_HOST_C_NS_8540 || - lsts == USB_LINK_STD_HOST_NC_8540)) - return 0; - - if (ab->previous_link_status_state == USB_LINK_ACA_RID_A_8540 && - (lsts == USB_LINK_STD_HOST_NC_8540)) - return 0; - - ab->previous_link_status_state = lsts; - - switch (lsts) { - case USB_LINK_ACA_RID_B_8540: - event = UX500_MUSB_RIDB; - case USB_LINK_NOT_CONFIGURED_8540: - case USB_LINK_RESERVED0_8540: - case USB_LINK_RESERVED1_8540: - case USB_LINK_RESERVED2_8540: - case USB_LINK_RESERVED3_8540: - ab->mode = USB_IDLE; - ab->phy.otg->default_a = false; - ab->vbus_draw = 0; - if (event != UX500_MUSB_RIDB) - event = UX500_MUSB_NONE; - /* - * Fallback to default B_IDLE as nothing - * is connected - */ - ab->phy.otg->state = OTG_STATE_B_IDLE; - usb_phy_set_event(&ab->phy, USB_EVENT_NONE); - break; - - case USB_LINK_ACA_RID_C_NM_8540: - event = UX500_MUSB_RIDC; - case USB_LINK_STD_HOST_NC_8540: - case USB_LINK_STD_HOST_C_NS_8540: - case USB_LINK_STD_HOST_C_S_8540: - case USB_LINK_CDP_8540: - if (ab->mode == USB_IDLE) { - ab->mode = USB_PERIPHERAL; - ab8500_usb_peri_phy_en(ab); - atomic_notifier_call_chain(&ab->phy.notifier, - UX500_MUSB_PREPARE, &ab->vbus_draw); - usb_phy_set_event(&ab->phy, USB_EVENT_ENUMERATED); - } - if (event != UX500_MUSB_RIDC) - event = UX500_MUSB_VBUS; - break; - - case USB_LINK_ACA_RID_A_8540: - case USB_LINK_ACA_DOCK_CHGR_8540: - event = UX500_MUSB_RIDA; - case USB_LINK_HM_IDGND_8540: - case USB_LINK_STD_UPSTREAM_8540: - if (ab->mode == USB_IDLE) { - ab->mode = USB_HOST; - ab8500_usb_host_phy_en(ab); - atomic_notifier_call_chain(&ab->phy.notifier, - UX500_MUSB_PREPARE, &ab->vbus_draw); - } - ab->phy.otg->default_a = true; - if (event != UX500_MUSB_RIDA) - event = UX500_MUSB_ID; - atomic_notifier_call_chain(&ab->phy.notifier, - event, &ab->vbus_draw); - break; - - case USB_LINK_DEDICATED_CHG_8540: - ab->mode = USB_DEDICATED_CHG; - event = UX500_MUSB_CHARGER; - atomic_notifier_call_chain(&ab->phy.notifier, - event, &ab->vbus_draw); - usb_phy_set_event(&ab->phy, USB_EVENT_CHARGER); - break; - - case USB_LINK_PHYEN_NO_VBUS_NO_IDGND_8540: - case USB_LINK_STD_UPSTREAM_NO_IDGNG_VBUS_8540: - event = UX500_MUSB_NONE; - if (ab->mode == USB_HOST) { - ab->phy.otg->default_a = false; - ab->vbus_draw = 0; - atomic_notifier_call_chain(&ab->phy.notifier, - event, &ab->vbus_draw); - ab8500_usb_host_phy_dis(ab); - ab->mode = USB_IDLE; - } - if (ab->mode == USB_PERIPHERAL) { - atomic_notifier_call_chain(&ab->phy.notifier, - event, &ab->vbus_draw); - ab8500_usb_peri_phy_dis(ab); - atomic_notifier_call_chain(&ab->phy.notifier, - UX500_MUSB_CLEAN, &ab->vbus_draw); - ab->mode = USB_IDLE; - ab->phy.otg->default_a = false; - ab->vbus_draw = 0; - usb_phy_set_event(&ab->phy, USB_EVENT_NONE); - } - break; - - default: - event = UX500_MUSB_NONE; - break; - } - - return 0; -} - static int ab8505_usb_link_status_update(struct ab8500_usb *ab, enum ab8505_usb_link_status lsts) { @@ -858,20 +516,6 @@ static int abx500_usb_link_status_update(struct ab8500_usb *ab) AB8500_USB, AB8505_USB_LINE_STAT_REG, ®); lsts = (reg >> 3) & 0x1F; ret = ab8505_usb_link_status_update(ab, lsts); - } else if (is_ab8540(ab->ab8500)) { - enum ab8540_usb_link_status lsts; - - abx500_get_register_interruptible(ab->dev, - AB8500_USB, AB8540_USB_LINK_STAT_REG, ®); - lsts = (reg >> 3) & 0xFF; - ret = ab8540_usb_link_status_update(ab, lsts); - } else if (is_ab9540(ab->ab8500)) { - enum ab9540_usb_link_status lsts; - - abx500_get_register_interruptible(ab->dev, - AB8500_USB, AB9540_USB_LINK_STAT_REG, ®); - lsts = (reg >> 3) & 0xFF; - ret = ab9540_usb_link_status_update(ab, lsts); } return ret; @@ -889,7 +533,7 @@ static int abx500_usb_link_status_update(struct ab8500_usb *ab) static irqreturn_t ab8500_usb_disconnect_irq(int irq, void *data) { struct ab8500_usb *ab = (struct ab8500_usb *) data; - enum usb_phy_events event = UX500_MUSB_NONE; + enum usb_phy_events event = USB_EVENT_NONE; /* Link status will not be updated till phy is disabled. */ if (ab->mode == USB_HOST) { @@ -946,69 +590,6 @@ static void ab8500_usb_phy_disable_work(struct work_struct *work) ab8500_usb_peri_phy_dis(ab); } -/* Check if VBUS is set and linkstatus has not detected a cable. */ -static bool ab8500_usb_check_vbus_status(struct ab8500_usb *ab) -{ - u8 isource2; - u8 reg; - enum ab8540_usb_link_status lsts; - - abx500_get_register_interruptible(ab->dev, - AB8500_INTERRUPT, AB8500_IT_SOURCE2_REG, - &isource2); - - /* If Vbus is below 3.6V abort */ - if (!(isource2 & AB8500_BIT_SOURCE2_VBUSDET)) - return false; - - abx500_get_register_interruptible(ab->dev, - AB8500_USB, AB8540_USB_LINK_STAT_REG, - ®); - - lsts = (reg >> 3) & 0xFF; - - /* Check if linkstatus has detected a cable */ - if (lsts) - return false; - - return true; -} - -/* re-trigger charger detection again with watchdog re-kick. */ -static void ab8500_usb_vbus_turn_on_event_work(struct work_struct *work) -{ - struct ab8500_usb *ab = container_of(work, struct ab8500_usb, - vbus_event_work); - - if (ab->mode != USB_IDLE) - return; - - abx500_set_register_interruptible(ab->dev, - AB8500_SYS_CTRL2_BLOCK, AB8500_MAIN_WD_CTRL_REG, - AB8500_BIT_WD_CTRL_ENABLE); - - udelay(100); - - abx500_set_register_interruptible(ab->dev, - AB8500_SYS_CTRL2_BLOCK, AB8500_MAIN_WD_CTRL_REG, - AB8500_BIT_WD_CTRL_ENABLE | AB8500_BIT_WD_CTRL_KICK); - - udelay(100); - - /* Disable Main watchdog */ - abx500_set_register_interruptible(ab->dev, - AB8500_SYS_CTRL2_BLOCK, AB8500_MAIN_WD_CTRL_REG, - 0x0); - - /* Enable USB Charger detection */ - abx500_mask_and_set_register_interruptible(ab->dev, - AB8500_USB, AB8540_VBUS_CTRL_REG, - AB8540_BIT_VBUS_CTRL_CHARG_DET_ENA, - AB8540_BIT_VBUS_CTRL_CHARG_DET_ENA); - - ab->enabled_charging_detection = true; -} - static int ab8500_usb_set_suspend(struct usb_phy *x, int suspend) { /* TODO */ @@ -1256,66 +837,6 @@ static void ab8500_usb_set_ab8505_tuning_values(struct ab8500_usb *ab) err); } -static void ab8500_usb_set_ab8540_tuning_values(struct ab8500_usb *ab) -{ - int err; - - err = abx500_set_register_interruptible(ab->dev, - AB8540_DEBUG, AB8500_USB_PHY_TUNE1, 0xCC); - if (err < 0) - dev_err(ab->dev, "Failed to set PHY_TUNE1 register ret=%d\n", - err); - - err = abx500_set_register_interruptible(ab->dev, - AB8540_DEBUG, AB8500_USB_PHY_TUNE2, 0x60); - if (err < 0) - dev_err(ab->dev, "Failed to set PHY_TUNE2 register ret=%d\n", - err); - - err = abx500_set_register_interruptible(ab->dev, - AB8540_DEBUG, AB8500_USB_PHY_TUNE3, 0x90); - if (err < 0) - dev_err(ab->dev, "Failed to set PHY_TUNE3 register ret=%d\n", - err); -} - -static void ab8500_usb_set_ab9540_tuning_values(struct ab8500_usb *ab) -{ - int err; - - /* Enable the PBT/Bank 0x12 access */ - err = abx500_set_register_interruptible(ab->dev, - AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, 0x01); - if (err < 0) - dev_err(ab->dev, "Failed to enable bank12 access err=%d\n", - err); - - err = abx500_set_register_interruptible(ab->dev, - AB8500_DEBUG, AB8500_USB_PHY_TUNE1, 0xC8); - if (err < 0) - dev_err(ab->dev, "Failed to set PHY_TUNE1 register err=%d\n", - err); - - err = abx500_set_register_interruptible(ab->dev, - AB8500_DEBUG, AB8500_USB_PHY_TUNE2, 0x60); - if (err < 0) - dev_err(ab->dev, "Failed to set PHY_TUNE2 register err=%d\n", - err); - - err = abx500_set_register_interruptible(ab->dev, - AB8500_DEBUG, AB8500_USB_PHY_TUNE3, 0x80); - if (err < 0) - dev_err(ab->dev, "Failed to set PHY_TUNE3 register err=%d\n", - err); - - /* Switch to normal mode/disable Bank 0x12 access */ - err = abx500_set_register_interruptible(ab->dev, - AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, 0x00); - if (err < 0) - dev_err(ab->dev, "Failed to switch bank12 access err=%d\n", - err); -} - static int ab8500_usb_probe(struct platform_device *pdev) { struct ab8500_usb *ab; @@ -1362,17 +883,6 @@ static int ab8500_usb_probe(struct platform_device *pdev) AB8500_USB_FLAG_USE_ID_WAKEUP_IRQ | AB8500_USB_FLAG_USE_VBUS_DET_IRQ | AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE; - } else if (is_ab8540(ab->ab8500)) { - ab->flags |= AB8500_USB_FLAG_USE_LINK_STATUS_IRQ | - AB8500_USB_FLAG_USE_CHECK_VBUS_STATUS | - AB8500_USB_FLAG_USE_VBUS_HOST_QUIRK | - AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE; - } else if (is_ab9540(ab->ab8500)) { - ab->flags |= AB8500_USB_FLAG_USE_LINK_STATUS_IRQ | - AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE; - if (is_ab9540_2p0_or_earlier(ab->ab8500)) - ab->flags |= AB8500_USB_FLAG_USE_ID_WAKEUP_IRQ | - AB8500_USB_FLAG_USE_VBUS_DET_IRQ; } /* Disable regulator voltage setting for AB8500 <= v2.0 */ @@ -1384,8 +894,6 @@ static int ab8500_usb_probe(struct platform_device *pdev) /* all: Disable phy when called from set_host and set_peripheral */ INIT_WORK(&ab->phy_dis_work, ab8500_usb_phy_disable_work); - INIT_WORK(&ab->vbus_event_work, ab8500_usb_vbus_turn_on_event_work); - err = ab8500_usb_regulator_get(ab); if (err) return err; @@ -1412,12 +920,6 @@ static int ab8500_usb_probe(struct platform_device *pdev) else if (is_ab8505(ab->ab8500)) /* Phy tuning values for AB8505 */ ab8500_usb_set_ab8505_tuning_values(ab); - else if (is_ab8540(ab->ab8500)) - /* Phy tuning values for AB8540 */ - ab8500_usb_set_ab8540_tuning_values(ab); - else if (is_ab9540(ab->ab8500)) - /* Phy tuning values for AB9540 */ - ab8500_usb_set_ab9540_tuning_values(ab); /* Needed to enable ID detection. */ ab8500_usb_wd_workaround(ab); @@ -1428,11 +930,6 @@ static int ab8500_usb_probe(struct platform_device *pdev) */ ab8500_usb_restart_phy(ab); - if (ab->flags & AB8500_USB_FLAG_USE_CHECK_VBUS_STATUS) { - if (ab8500_usb_check_vbus_status(ab)) - schedule_work(&ab->vbus_event_work); - } - abx500_usb_link_status_update(ab); dev_info(&pdev->dev, "revision 0x%2x driver initialized\n", rev); @@ -1445,7 +942,6 @@ static int ab8500_usb_remove(struct platform_device *pdev) struct ab8500_usb *ab = platform_get_drvdata(pdev); cancel_work_sync(&ab->phy_dis_work); - cancel_work_sync(&ab->vbus_event_work); usb_remove_phy(&ab->phy); @@ -1459,8 +955,6 @@ static int ab8500_usb_remove(struct platform_device *pdev) static const struct platform_device_id ab8500_usb_devtype[] = { { .name = "ab8500-usb", }, - { .name = "ab8540-usb", }, - { .name = "ab9540-usb", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(platform, ab8500_usb_devtype); diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c index 74ba88297991..a53b89be5324 100644 --- a/drivers/usb/phy/phy-generic.c +++ b/drivers/usb/phy/phy-generic.c @@ -63,9 +63,9 @@ static void nop_reset(struct usb_phy_generic *nop) if (!nop->gpiod_reset) return; - gpiod_set_value(nop->gpiod_reset, 1); + gpiod_set_value_cansleep(nop->gpiod_reset, 1); usleep_range(10000, 20000); - gpiod_set_value(nop->gpiod_reset, 0); + gpiod_set_value_cansleep(nop->gpiod_reset, 0); } /* interface to regulator framework */ @@ -159,7 +159,7 @@ void usb_gen_phy_shutdown(struct usb_phy *phy) { struct usb_phy_generic *nop = dev_get_drvdata(phy->dev); - gpiod_set_value(nop->gpiod_reset, 1); + gpiod_set_value_cansleep(nop->gpiod_reset, 1); if (!IS_ERR(nop->clk)) clk_disable_unprepare(nop->clk); diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index fbec863350f6..e5aa24c1e4fd 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -578,7 +578,7 @@ static enum usb_charger_type mxs_charger_primary_detection(struct mxs_phy *x) * It must be called after DP is pulled up, which is used to * differentiate DCP and CDP. */ -enum usb_charger_type mxs_charger_secondary_detection(struct mxs_phy *x) +static enum usb_charger_type mxs_charger_secondary_detection(struct mxs_phy *x) { struct regmap *regmap = x->regmap_anatop; int val; diff --git a/drivers/usb/phy/phy-tegra-usb.c b/drivers/usb/phy/phy-tegra-usb.c index f668bfb708d3..0e8d23e51732 100644 --- a/drivers/usb/phy/phy-tegra-usb.c +++ b/drivers/usb/phy/phy-tegra-usb.c @@ -16,7 +16,7 @@ #include <linux/export.h> #include <linux/module.h> #include <linux/platform_device.h> -#include <linux/io.h> +#include <linux/iopoll.h> #include <linux/gpio.h> #include <linux/of.h> #include <linux/of_device.h> @@ -305,14 +305,10 @@ static int utmip_pad_power_off(struct tegra_usb_phy *phy) static int utmi_wait_register(void __iomem *reg, u32 mask, u32 result) { - unsigned long timeout = 2000; - do { - if ((readl(reg) & mask) == result) - return 0; - udelay(1); - timeout--; - } while (timeout); - return -1; + u32 tmp; + + return readl_poll_timeout(reg, tmp, (tmp & mask) == result, + 2000, 6000); } static void utmi_phy_clk_disable(struct tegra_usb_phy *phy) diff --git a/drivers/usb/roles/Kconfig b/drivers/usb/roles/Kconfig new file mode 100644 index 000000000000..f5a5e6f79f1b --- /dev/null +++ b/drivers/usb/roles/Kconfig @@ -0,0 +1,14 @@ +if USB_ROLE_SWITCH + +config USB_ROLES_INTEL_XHCI + tristate "Intel XHCI USB Role Switch" + depends on ACPI && X86 + help + Driver for the internal USB role switch for switching the USB data + lines between the xHCI host controller and the dwc3 gadget controller + found on various Intel SoCs. + + To compile the driver as a module, choose M here: the module will + be called intel-xhci-usb-role-switch. + +endif # USB_ROLE_SWITCH diff --git a/drivers/usb/roles/Makefile b/drivers/usb/roles/Makefile new file mode 100644 index 000000000000..e44b179ba275 --- /dev/null +++ b/drivers/usb/roles/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_USB_ROLES_INTEL_XHCI) += intel-xhci-usb-role-switch.o diff --git a/drivers/usb/roles/intel-xhci-usb-role-switch.c b/drivers/usb/roles/intel-xhci-usb-role-switch.c new file mode 100644 index 000000000000..de72eedb762e --- /dev/null +++ b/drivers/usb/roles/intel-xhci-usb-role-switch.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Intel XHCI (Cherry Trail, Broxton and others) USB OTG role switch driver + * + * Copyright (c) 2016-2017 Hans de Goede <hdegoede@redhat.com> + * + * Loosely based on android x86 kernel code which is: + * + * Copyright (C) 2014 Intel Corp. + * + * Author: Wu, Hao + */ + +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/usb/role.h> + +/* register definition */ +#define DUAL_ROLE_CFG0 0x68 +#define SW_VBUS_VALID BIT(24) +#define SW_IDPIN_EN BIT(21) +#define SW_IDPIN BIT(20) + +#define DUAL_ROLE_CFG1 0x6c +#define HOST_MODE BIT(29) + +#define DUAL_ROLE_CFG1_POLL_TIMEOUT 1000 + +#define DRV_NAME "intel_xhci_usb_sw" + +struct intel_xhci_usb_data { + struct usb_role_switch *role_sw; + void __iomem *base; +}; + +struct intel_xhci_acpi_match { + const char *hid; + int hrv; +}; + +/* + * ACPI IDs for PMICs which do not support separate data and power role + * detection (USB ACA detection for micro USB OTG), we allow userspace to + * change the role manually on these. + */ +static const struct intel_xhci_acpi_match allow_userspace_ctrl_ids[] = { + { "INT33F4", 3 }, /* X-Powers AXP288 PMIC */ +}; + +static int intel_xhci_usb_set_role(struct device *dev, enum usb_role role) +{ + struct intel_xhci_usb_data *data = dev_get_drvdata(dev); + unsigned long timeout; + acpi_status status; + u32 glk, val; + + /* + * On many CHT devices ACPI event (_AEI) handlers read / modify / + * write the cfg0 register, just like we do. Take the ACPI lock + * to avoid us racing with the AML code. + */ + status = acpi_acquire_global_lock(ACPI_WAIT_FOREVER, &glk); + if (ACPI_FAILURE(status) && status != AE_NOT_CONFIGURED) { + dev_err(dev, "Error could not acquire lock\n"); + return -EIO; + } + + /* Set idpin value as requested */ + val = readl(data->base + DUAL_ROLE_CFG0); + switch (role) { + case USB_ROLE_NONE: + val |= SW_IDPIN; + val &= ~SW_VBUS_VALID; + break; + case USB_ROLE_HOST: + val &= ~SW_IDPIN; + val &= ~SW_VBUS_VALID; + break; + case USB_ROLE_DEVICE: + val |= SW_IDPIN; + val |= SW_VBUS_VALID; + break; + } + val |= SW_IDPIN_EN; + + writel(val, data->base + DUAL_ROLE_CFG0); + + acpi_release_global_lock(glk); + + /* In most case it takes about 600ms to finish mode switching */ + timeout = jiffies + msecs_to_jiffies(DUAL_ROLE_CFG1_POLL_TIMEOUT); + + /* Polling on CFG1 register to confirm mode switch.*/ + do { + val = readl(data->base + DUAL_ROLE_CFG1); + if (!!(val & HOST_MODE) == (role == USB_ROLE_HOST)) + return 0; + + /* Interval for polling is set to about 5 - 10 ms */ + usleep_range(5000, 10000); + } while (time_before(jiffies, timeout)); + + dev_warn(dev, "Timeout waiting for role-switch\n"); + return -ETIMEDOUT; +} + +static enum usb_role intel_xhci_usb_get_role(struct device *dev) +{ + struct intel_xhci_usb_data *data = dev_get_drvdata(dev); + enum usb_role role; + u32 val; + + val = readl(data->base + DUAL_ROLE_CFG0); + + if (!(val & SW_IDPIN)) + role = USB_ROLE_HOST; + else if (val & SW_VBUS_VALID) + role = USB_ROLE_DEVICE; + else + role = USB_ROLE_NONE; + + return role; +} + +static struct usb_role_switch_desc sw_desc = { + .set = intel_xhci_usb_set_role, + .get = intel_xhci_usb_get_role, +}; + +static int intel_xhci_usb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct intel_xhci_usb_data *data; + struct resource *res; + int i; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + data->base = devm_ioremap_nocache(dev, res->start, resource_size(res)); + if (!data->base) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(allow_userspace_ctrl_ids); i++) + if (acpi_dev_present(allow_userspace_ctrl_ids[i].hid, "1", + allow_userspace_ctrl_ids[i].hrv)) + sw_desc.allow_userspace_control = true; + + platform_set_drvdata(pdev, data); + + data->role_sw = usb_role_switch_register(dev, &sw_desc); + if (IS_ERR(data->role_sw)) + return PTR_ERR(data->role_sw); + + return 0; +} + +static int intel_xhci_usb_remove(struct platform_device *pdev) +{ + struct intel_xhci_usb_data *data = platform_get_drvdata(pdev); + + usb_role_switch_unregister(data->role_sw); + return 0; +} + +static const struct platform_device_id intel_xhci_usb_table[] = { + { .name = DRV_NAME }, + {} +}; +MODULE_DEVICE_TABLE(platform, intel_xhci_usb_table); + +static struct platform_driver intel_xhci_usb_driver = { + .driver = { + .name = DRV_NAME, + }, + .id_table = intel_xhci_usb_table, + .probe = intel_xhci_usb_probe, + .remove = intel_xhci_usb_remove, +}; + +module_platform_driver(intel_xhci_usb_driver); + +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); +MODULE_DESCRIPTION("Intel XHCI USB role switch driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 06d502b3e913..de1e759dd512 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -155,6 +155,7 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(0x12B8, 0xEC62) }, /* Link G4+ ECU */ { USB_DEVICE(0x13AD, 0x9999) }, /* Baltech card reader */ { USB_DEVICE(0x1555, 0x0004) }, /* Owen AC4 USB-RS485 Converter */ + { USB_DEVICE(0x155A, 0x1006) }, /* ELDAT Easywave RX09 */ { USB_DEVICE(0x166A, 0x0201) }, /* Clipsal 5500PACA C-Bus Pascal Automation Controller */ { USB_DEVICE(0x166A, 0x0301) }, /* Clipsal 5800PC C-Bus Wireless PC Interface */ { USB_DEVICE(0x166A, 0x0303) }, /* Clipsal 5500PCU C-Bus USB interface */ diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index f58c4ff6b387..87202ad5a50d 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -769,6 +769,7 @@ static const struct usb_device_id id_table_combined[] = { .driver_info = (kernel_ulong_t)&ftdi_NDI_device_quirk }, { USB_DEVICE(TELLDUS_VID, TELLDUS_TELLSTICK_PID) }, { USB_DEVICE(NOVITUS_VID, NOVITUS_BONO_E_PID) }, + { USB_DEVICE(FTDI_VID, RTSYSTEMS_USB_VX8_PID) }, { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_S03_PID) }, { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_59_PID) }, { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_57A_PID) }, @@ -931,6 +932,7 @@ static const struct usb_device_id id_table_combined[] = { { USB_DEVICE(FTDI_VID, FTDI_SCIENCESCOPE_LS_LOGBOOK_PID) }, { USB_DEVICE(FTDI_VID, FTDI_SCIENCESCOPE_HS_LOGBOOK_PID) }, { USB_DEVICE(FTDI_VID, FTDI_CINTERION_MC55I_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_FHE_PID) }, { USB_DEVICE(FTDI_VID, FTDI_DOTEC_PID) }, { USB_DEVICE(QIHARDWARE_VID, MILKYMISTONE_JTAGSERIAL_PID), .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, diff --git a/drivers/usb/serial/ftdi_sio_ids.h b/drivers/usb/serial/ftdi_sio_ids.h index 8b4ecd2bd297..975d02666c5a 100644 --- a/drivers/usb/serial/ftdi_sio_ids.h +++ b/drivers/usb/serial/ftdi_sio_ids.h @@ -923,6 +923,9 @@ /* * RT Systems programming cables for various ham radios */ +/* This device uses the VID of FTDI */ +#define RTSYSTEMS_USB_VX8_PID 0x9e50 /* USB-VX8 USB to 7 pin modular plug for Yaesu VX-8 radio */ + #define RTSYSTEMS_VID 0x2100 /* Vendor ID */ #define RTSYSTEMS_USB_S03_PID 0x9001 /* RTS-03 USB to Serial Adapter */ #define RTSYSTEMS_USB_59_PID 0x9e50 /* USB-59 USB to 8 pin plug */ @@ -1442,6 +1445,12 @@ #define FTDI_CINTERION_MC55I_PID 0xA951 /* + * Product: FirmwareHubEmulator + * Manufacturer: Harman Becker Automotive Systems + */ +#define FTDI_FHE_PID 0xA9A0 + +/* * Product: Comet Caller ID decoder * Manufacturer: Crucible Technologies */ diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 2d8d9150da0c..c3f252283ab9 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -548,151 +548,15 @@ static void option_instat_callback(struct urb *urb); #define WETELECOM_PRODUCT_6802 0x6802 #define WETELECOM_PRODUCT_WMD300 0x6803 -struct option_blacklist_info { - /* bitmask of interface numbers blacklisted for send_setup */ - const unsigned long sendsetup; - /* bitmask of interface numbers that are reserved */ - const unsigned long reserved; -}; - -static const struct option_blacklist_info four_g_w14_blacklist = { - .sendsetup = BIT(0) | BIT(1), -}; - -static const struct option_blacklist_info four_g_w100_blacklist = { - .sendsetup = BIT(1) | BIT(2), - .reserved = BIT(3), -}; - -static const struct option_blacklist_info alcatel_x200_blacklist = { - .sendsetup = BIT(0) | BIT(1), - .reserved = BIT(4), -}; - -static const struct option_blacklist_info zte_0037_blacklist = { - .sendsetup = BIT(0) | BIT(1), -}; - -static const struct option_blacklist_info zte_k3765_z_blacklist = { - .sendsetup = BIT(0) | BIT(1) | BIT(2), - .reserved = BIT(4), -}; - -static const struct option_blacklist_info zte_ad3812_z_blacklist = { - .sendsetup = BIT(0) | BIT(1) | BIT(2), -}; - -static const struct option_blacklist_info zte_mc2718_z_blacklist = { - .sendsetup = BIT(1) | BIT(2) | BIT(3) | BIT(4), -}; - -static const struct option_blacklist_info zte_mc2716_z_blacklist = { - .sendsetup = BIT(1) | BIT(2) | BIT(3), -}; - -static const struct option_blacklist_info zte_me3620_mbim_blacklist = { - .reserved = BIT(2) | BIT(3) | BIT(4), -}; - -static const struct option_blacklist_info zte_me3620_xl_blacklist = { - .reserved = BIT(3) | BIT(4) | BIT(5), -}; - -static const struct option_blacklist_info zte_zm8620_x_blacklist = { - .reserved = BIT(3) | BIT(4) | BIT(5), -}; - -static const struct option_blacklist_info huawei_cdc12_blacklist = { - .reserved = BIT(1) | BIT(2), -}; - -static const struct option_blacklist_info net_intf0_blacklist = { - .reserved = BIT(0), -}; - -static const struct option_blacklist_info net_intf1_blacklist = { - .reserved = BIT(1), -}; - -static const struct option_blacklist_info net_intf2_blacklist = { - .reserved = BIT(2), -}; - -static const struct option_blacklist_info net_intf3_blacklist = { - .reserved = BIT(3), -}; - -static const struct option_blacklist_info net_intf4_blacklist = { - .reserved = BIT(4), -}; - -static const struct option_blacklist_info net_intf5_blacklist = { - .reserved = BIT(5), -}; - -static const struct option_blacklist_info net_intf6_blacklist = { - .reserved = BIT(6), -}; - -static const struct option_blacklist_info zte_mf626_blacklist = { - .sendsetup = BIT(0) | BIT(1), - .reserved = BIT(4), -}; - -static const struct option_blacklist_info zte_1255_blacklist = { - .reserved = BIT(3) | BIT(4), -}; - -static const struct option_blacklist_info simcom_sim7100e_blacklist = { - .reserved = BIT(5) | BIT(6), -}; - -static const struct option_blacklist_info telit_me910_blacklist = { - .sendsetup = BIT(0), - .reserved = BIT(1) | BIT(3), -}; - -static const struct option_blacklist_info telit_me910_dual_modem_blacklist = { - .sendsetup = BIT(0), - .reserved = BIT(3), -}; - -static const struct option_blacklist_info telit_le910_blacklist = { - .sendsetup = BIT(0), - .reserved = BIT(1) | BIT(2), -}; -static const struct option_blacklist_info telit_le920_blacklist = { - .sendsetup = BIT(0), - .reserved = BIT(1) | BIT(5), -}; +/* Device flags */ -static const struct option_blacklist_info telit_le920a4_blacklist_1 = { - .sendsetup = BIT(0), - .reserved = BIT(1), -}; +/* Interface does not support modem-control requests */ +#define NCTRL(ifnum) ((BIT(ifnum) & 0xff) << 8) -static const struct option_blacklist_info telit_le922_blacklist_usbcfg0 = { - .sendsetup = BIT(2), - .reserved = BIT(0) | BIT(1) | BIT(3), -}; +/* Interface is reserved */ +#define RSVD(ifnum) ((BIT(ifnum) & 0xff) << 0) -static const struct option_blacklist_info telit_le922_blacklist_usbcfg3 = { - .sendsetup = BIT(0), - .reserved = BIT(1) | BIT(2) | BIT(3), -}; - -static const struct option_blacklist_info cinterion_rmnet2_blacklist = { - .reserved = BIT(4) | BIT(5), -}; - -static const struct option_blacklist_info yuga_clm920_nc5_blacklist = { - .reserved = BIT(1) | BIT(4), -}; - -static const struct option_blacklist_info quectel_ep06_blacklist = { - .reserved = BIT(4) | BIT(5), -}; static const struct usb_device_id option_ids[] = { { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_COLT) }, @@ -726,26 +590,26 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE(QUANTA_VENDOR_ID, QUANTA_PRODUCT_GKE) }, { USB_DEVICE(QUANTA_VENDOR_ID, QUANTA_PRODUCT_GLE) }, { USB_DEVICE(QUANTA_VENDOR_ID, 0xea42), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x1c05, USB_CLASS_COMM, 0x02, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x1c1f, USB_CLASS_COMM, 0x02, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x1c23, USB_CLASS_COMM, 0x02, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E173, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t) &net_intf1_blacklist }, + .driver_info = RSVD(1) }, { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E173S6, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t) &net_intf1_blacklist }, + .driver_info = RSVD(1) }, { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1750, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t) &net_intf2_blacklist }, + .driver_info = RSVD(2) }, { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x1441, USB_CLASS_COMM, 0x02, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x1442, USB_CLASS_COMM, 0x02, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K4505, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t) &huawei_cdc12_blacklist }, + .driver_info = RSVD(1) | RSVD(2) }, { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K3765, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t) &huawei_cdc12_blacklist }, + .driver_info = RSVD(1) | RSVD(2) }, { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x14ac, 0xff, 0xff, 0xff), /* Huawei E1820 */ - .driver_info = (kernel_ulong_t) &net_intf1_blacklist }, + .driver_info = RSVD(1) }, { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K4605, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t) &huawei_cdc12_blacklist }, + .driver_info = RSVD(1) | RSVD(2) }, { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0xff, 0xff) }, { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x01) }, { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x02) }, @@ -1190,67 +1054,67 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE(KYOCERA_VENDOR_ID, KYOCERA_PRODUCT_KPC680) }, { USB_DEVICE(QUALCOMM_VENDOR_ID, 0x6000)}, /* ZTE AC8700 */ { USB_DEVICE_AND_INTERFACE_INFO(QUALCOMM_VENDOR_ID, 0x6001, 0xff, 0xff, 0xff), /* 4G LTE usb-modem U901 */ - .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + .driver_info = RSVD(3) }, { USB_DEVICE(QUALCOMM_VENDOR_ID, 0x6613)}, /* Onda H600/ZTE MF330 */ { USB_DEVICE(QUALCOMM_VENDOR_ID, 0x0023)}, /* ONYX 3G device */ { USB_DEVICE(QUALCOMM_VENDOR_ID, 0x9000)}, /* SIMCom SIM5218 */ /* Quectel products using Qualcomm vendor ID */ { USB_DEVICE(QUALCOMM_VENDOR_ID, QUECTEL_PRODUCT_UC15)}, { USB_DEVICE(QUALCOMM_VENDOR_ID, QUECTEL_PRODUCT_UC20), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, /* Yuga products use Qualcomm vendor ID */ { USB_DEVICE(QUALCOMM_VENDOR_ID, YUGA_PRODUCT_CLM920_NC5), - .driver_info = (kernel_ulong_t)&yuga_clm920_nc5_blacklist }, + .driver_info = RSVD(1) | RSVD(4) }, /* Quectel products using Quectel vendor ID */ { USB_DEVICE(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC21), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC25), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_BG96), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EP06), - .driver_info = (kernel_ulong_t)&quectel_ep06_blacklist }, + .driver_info = RSVD(4) | RSVD(5) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_6001) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CMU_300) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_6003), - .driver_info = (kernel_ulong_t)&net_intf0_blacklist }, + .driver_info = RSVD(0) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_6004) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_6005) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CGU_628A) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHE_628S), - .driver_info = (kernel_ulong_t)&net_intf0_blacklist }, + .driver_info = RSVD(0) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CMU_301), - .driver_info = (kernel_ulong_t)&net_intf0_blacklist }, + .driver_info = RSVD(0) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHU_628), - .driver_info = (kernel_ulong_t)&net_intf0_blacklist }, + .driver_info = RSVD(0) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHU_628S) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CDU_680) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CDU_685A) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHU_720S), - .driver_info = (kernel_ulong_t)&net_intf0_blacklist }, + .driver_info = RSVD(0) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7002), - .driver_info = (kernel_ulong_t)&net_intf0_blacklist }, + .driver_info = RSVD(0) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHU_629K), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7004), - .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + .driver_info = RSVD(3) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7005) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CGU_629), - .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + .driver_info = RSVD(5) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHU_629S), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHU_720I), - .driver_info = (kernel_ulong_t)&net_intf0_blacklist }, + .driver_info = RSVD(0) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7212), - .driver_info = (kernel_ulong_t)&net_intf0_blacklist }, + .driver_info = RSVD(0) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7213), - .driver_info = (kernel_ulong_t)&net_intf0_blacklist }, + .driver_info = RSVD(0) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7251), - .driver_info = (kernel_ulong_t)&net_intf1_blacklist }, + .driver_info = RSVD(1) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7252), - .driver_info = (kernel_ulong_t)&net_intf1_blacklist }, + .driver_info = RSVD(1) }, { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7253), - .driver_info = (kernel_ulong_t)&net_intf1_blacklist }, + .driver_info = RSVD(1) }, { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_UC864E) }, { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_UC864G) }, { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_CC864_DUAL) }, @@ -1258,38 +1122,38 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_DE910_DUAL) }, { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_UE910_V2) }, { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE922_USBCFG0), - .driver_info = (kernel_ulong_t)&telit_le922_blacklist_usbcfg0 }, + .driver_info = RSVD(0) | RSVD(1) | NCTRL(2) | RSVD(3) }, { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE922_USBCFG1), - .driver_info = (kernel_ulong_t)&telit_le910_blacklist }, + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) }, { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE922_USBCFG2), - .driver_info = (kernel_ulong_t)&telit_le922_blacklist_usbcfg3 }, + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) | RSVD(3) }, { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE922_USBCFG3), - .driver_info = (kernel_ulong_t)&telit_le922_blacklist_usbcfg3 }, + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) | RSVD(3) }, { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, TELIT_PRODUCT_LE922_USBCFG5, 0xff), - .driver_info = (kernel_ulong_t)&telit_le922_blacklist_usbcfg0 }, + .driver_info = RSVD(0) | RSVD(1) | NCTRL(2) | RSVD(3) }, { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_ME910), - .driver_info = (kernel_ulong_t)&telit_me910_blacklist }, + .driver_info = NCTRL(0) | RSVD(1) | RSVD(3) }, { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_ME910_DUAL_MODEM), - .driver_info = (kernel_ulong_t)&telit_me910_dual_modem_blacklist }, + .driver_info = NCTRL(0) | RSVD(3) }, { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE910), - .driver_info = (kernel_ulong_t)&telit_le910_blacklist }, + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) }, { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE910_USBCFG4), - .driver_info = (kernel_ulong_t)&telit_le922_blacklist_usbcfg3 }, + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) | RSVD(3) }, { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE920), - .driver_info = (kernel_ulong_t)&telit_le920_blacklist }, + .driver_info = NCTRL(0) | RSVD(1) | RSVD(5) }, { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE920A4_1207) }, { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE920A4_1208), - .driver_info = (kernel_ulong_t)&telit_le920a4_blacklist_1 }, + .driver_info = NCTRL(0) | RSVD(1) }, { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE920A4_1211), - .driver_info = (kernel_ulong_t)&telit_le922_blacklist_usbcfg3 }, + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) | RSVD(3) }, { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE920A4_1212), - .driver_info = (kernel_ulong_t)&telit_le920a4_blacklist_1 }, + .driver_info = NCTRL(0) | RSVD(1) }, { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, TELIT_PRODUCT_LE920A4_1213, 0xff) }, { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE920A4_1214), - .driver_info = (kernel_ulong_t)&telit_le922_blacklist_usbcfg3 }, + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) | RSVD(3) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MF622, 0xff, 0xff, 0xff) }, /* ZTE WCDMA products */ { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0002, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf1_blacklist }, + .driver_info = RSVD(1) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0003, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0004, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0005, 0xff, 0xff, 0xff) }, @@ -1305,58 +1169,58 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0010, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0011, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0012, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf1_blacklist }, + .driver_info = RSVD(1) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0013, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MF628, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0016, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0017, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + .driver_info = RSVD(3) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0018, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0019, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + .driver_info = RSVD(3) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0020, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0021, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0022, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0023, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0024, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0025, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf1_blacklist }, + .driver_info = RSVD(1) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0028, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0029, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0030, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MF626, 0xff, - 0xff, 0xff), .driver_info = (kernel_ulong_t)&zte_mf626_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MF626, 0xff, 0xff, 0xff), + .driver_info = NCTRL(0) | NCTRL(1) | RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0032, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0033, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0034, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0037, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&zte_0037_blacklist }, + .driver_info = NCTRL(0) | NCTRL(1) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0038, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0039, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0040, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0042, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0043, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0044, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0048, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0049, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + .driver_info = RSVD(5) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0050, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0051, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0052, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0054, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0055, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf1_blacklist }, + .driver_info = RSVD(1) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0056, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0057, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0058, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0061, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0062, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0063, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0064, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0065, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0066, 0xff, 0xff, 0xff) }, @@ -1381,26 +1245,26 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0096, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0097, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0104, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0105, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0106, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0108, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0113, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + .driver_info = RSVD(5) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0117, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0118, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + .driver_info = RSVD(5) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0121, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + .driver_info = RSVD(5) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0122, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0123, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0124, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + .driver_info = RSVD(5) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0125, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf6_blacklist }, + .driver_info = RSVD(6) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0126, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + .driver_info = RSVD(5) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0128, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0135, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0136, 0xff, 0xff, 0xff) }, @@ -1416,50 +1280,50 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0155, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0156, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0157, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + .driver_info = RSVD(5) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0158, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + .driver_info = RSVD(3) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0159, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0161, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0162, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0164, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0165, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0167, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0189, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0191, 0xff, 0xff, 0xff), /* ZTE EuFi890 */ - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0196, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0197, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0199, 0xff, 0xff, 0xff), /* ZTE MF820S */ - .driver_info = (kernel_ulong_t)&net_intf1_blacklist }, + .driver_info = RSVD(1) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0200, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0201, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0254, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0257, 0xff, 0xff, 0xff), /* ZTE MF821 */ - .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + .driver_info = RSVD(3) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0265, 0xff, 0xff, 0xff), /* ONDA MT8205 */ - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0284, 0xff, 0xff, 0xff), /* ZTE MF880 */ - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0317, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0326, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0330, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0395, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0412, 0xff, 0xff, 0xff), /* Telewell TW-LTE 4G */ - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0414, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0417, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1008, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1010, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1012, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1018, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1021, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf2_blacklist }, + .driver_info = RSVD(2) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1057, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1058, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1059, 0xff, 0xff, 0xff) }, @@ -1576,23 +1440,23 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1170, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1244, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1245, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1246, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1247, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1248, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1249, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1250, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1251, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1252, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1253, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1254, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1255, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&zte_1255_blacklist }, + .driver_info = RSVD(3) | RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1256, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1257, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1258, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1259, 0xff, 0xff, 0xff) }, @@ -1607,7 +1471,7 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1268, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1269, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1270, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + .driver_info = RSVD(5) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1271, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1272, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1273, 0xff, 0xff, 0xff) }, @@ -1643,17 +1507,17 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1303, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1333, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1401, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf2_blacklist }, + .driver_info = RSVD(2) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1402, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf2_blacklist }, + .driver_info = RSVD(2) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1424, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf2_blacklist }, + .driver_info = RSVD(2) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1425, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf2_blacklist }, + .driver_info = RSVD(2) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1426, 0xff, 0xff, 0xff), /* ZTE MF91 */ - .driver_info = (kernel_ulong_t)&net_intf2_blacklist }, + .driver_info = RSVD(2) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1428, 0xff, 0xff, 0xff), /* Telewell TW-LTE 4G v2 */ - .driver_info = (kernel_ulong_t)&net_intf2_blacklist }, + .driver_info = RSVD(2) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1533, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1534, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1535, 0xff, 0xff, 0xff) }, @@ -1671,8 +1535,8 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1596, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1598, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1600, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x2002, 0xff, - 0xff, 0xff), .driver_info = (kernel_ulong_t)&zte_k3765_z_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x2002, 0xff, 0xff, 0xff), + .driver_info = NCTRL(0) | NCTRL(1) | NCTRL(2) | RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x2003, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0014, 0xff, 0xff, 0xff) }, /* ZTE CDMA products */ @@ -1683,20 +1547,20 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0073, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0094, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0130, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf1_blacklist }, + .driver_info = RSVD(1) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0133, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + .driver_info = RSVD(3) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0141, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + .driver_info = RSVD(5) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0147, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0152, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0168, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0170, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0176, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + .driver_info = RSVD(3) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0178, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + .driver_info = RSVD(3) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff42, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff43, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff44, 0xff, 0xff, 0xff) }, @@ -1848,19 +1712,19 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_AC2726, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_AC8710T, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MC2718, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&zte_mc2718_z_blacklist }, + .driver_info = NCTRL(1) | NCTRL(2) | NCTRL(3) | NCTRL(4) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_AD3812, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&zte_ad3812_z_blacklist }, + .driver_info = NCTRL(0) | NCTRL(1) | NCTRL(2) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MC2716, 0xff, 0xff, 0xff), - .driver_info = (kernel_ulong_t)&zte_mc2716_z_blacklist }, + .driver_info = NCTRL(1) | NCTRL(2) | NCTRL(3) }, { USB_DEVICE(ZTE_VENDOR_ID, ZTE_PRODUCT_ME3620_L), - .driver_info = (kernel_ulong_t)&zte_me3620_xl_blacklist }, + .driver_info = RSVD(3) | RSVD(4) | RSVD(5) }, { USB_DEVICE(ZTE_VENDOR_ID, ZTE_PRODUCT_ME3620_MBIM), - .driver_info = (kernel_ulong_t)&zte_me3620_mbim_blacklist }, + .driver_info = RSVD(2) | RSVD(3) | RSVD(4) }, { USB_DEVICE(ZTE_VENDOR_ID, ZTE_PRODUCT_ME3620_X), - .driver_info = (kernel_ulong_t)&zte_me3620_xl_blacklist }, + .driver_info = RSVD(3) | RSVD(4) | RSVD(5) }, { USB_DEVICE(ZTE_VENDOR_ID, ZTE_PRODUCT_ZM8620_X), - .driver_info = (kernel_ulong_t)&zte_zm8620_x_blacklist }, + .driver_info = RSVD(3) | RSVD(4) | RSVD(5) }, { USB_VENDOR_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff, 0x02, 0x01) }, { USB_VENDOR_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff, 0x02, 0x05) }, { USB_VENDOR_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff, 0x86, 0x10) }, @@ -1880,37 +1744,34 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE(ALINK_VENDOR_ID, ALINK_PRODUCT_PH300) }, { USB_DEVICE_AND_INTERFACE_INFO(ALINK_VENDOR_ID, ALINK_PRODUCT_3GU, 0xff, 0xff, 0xff) }, { USB_DEVICE(ALINK_VENDOR_ID, SIMCOM_PRODUCT_SIM7100E), - .driver_info = (kernel_ulong_t)&simcom_sim7100e_blacklist }, + .driver_info = RSVD(5) | RSVD(6) }, { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_X060S_X200), - .driver_info = (kernel_ulong_t)&alcatel_x200_blacklist - }, + .driver_info = NCTRL(0) | NCTRL(1) | RSVD(4) }, { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_X220_X500D), - .driver_info = (kernel_ulong_t)&net_intf6_blacklist }, + .driver_info = RSVD(6) }, { USB_DEVICE(ALCATEL_VENDOR_ID, 0x0052), - .driver_info = (kernel_ulong_t)&net_intf6_blacklist }, + .driver_info = RSVD(6) }, { USB_DEVICE(ALCATEL_VENDOR_ID, 0x00b6), - .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + .driver_info = RSVD(3) }, { USB_DEVICE(ALCATEL_VENDOR_ID, 0x00b7), - .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + .driver_info = RSVD(5) }, { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_L100V), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_L800MA), - .driver_info = (kernel_ulong_t)&net_intf2_blacklist }, + .driver_info = RSVD(2) }, { USB_DEVICE(AIRPLUS_VENDOR_ID, AIRPLUS_PRODUCT_MCD650) }, { USB_DEVICE(TLAYTECH_VENDOR_ID, TLAYTECH_PRODUCT_TEU800) }, { USB_DEVICE(LONGCHEER_VENDOR_ID, FOUR_G_SYSTEMS_PRODUCT_W14), - .driver_info = (kernel_ulong_t)&four_g_w14_blacklist - }, + .driver_info = NCTRL(0) | NCTRL(1) }, { USB_DEVICE(LONGCHEER_VENDOR_ID, FOUR_G_SYSTEMS_PRODUCT_W100), - .driver_info = (kernel_ulong_t)&four_g_w100_blacklist - }, + .driver_info = NCTRL(1) | NCTRL(2) | RSVD(3) }, {USB_DEVICE(LONGCHEER_VENDOR_ID, FUJISOFT_PRODUCT_FS040U), - .driver_info = (kernel_ulong_t)&net_intf3_blacklist}, + .driver_info = RSVD(3)}, { USB_DEVICE_INTERFACE_CLASS(LONGCHEER_VENDOR_ID, SPEEDUP_PRODUCT_SU9800, 0xff) }, { USB_DEVICE_INTERFACE_CLASS(LONGCHEER_VENDOR_ID, 0x9801, 0xff), - .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + .driver_info = RSVD(3) }, { USB_DEVICE_INTERFACE_CLASS(LONGCHEER_VENDOR_ID, 0x9803, 0xff), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE(LONGCHEER_VENDOR_ID, ZOOM_PRODUCT_4597) }, { USB_DEVICE(LONGCHEER_VENDOR_ID, IBALL_3_5G_CONNECT) }, { USB_DEVICE(HAIER_VENDOR_ID, HAIER_PRODUCT_CE100) }, @@ -1936,14 +1797,14 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE(CINTERION_VENDOR_ID, CINTERION_PRODUCT_EU3_E) }, { USB_DEVICE(CINTERION_VENDOR_ID, CINTERION_PRODUCT_EU3_P) }, { USB_DEVICE(CINTERION_VENDOR_ID, CINTERION_PRODUCT_PH8), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_AHXX, 0xff) }, { USB_DEVICE(CINTERION_VENDOR_ID, CINTERION_PRODUCT_PLXX), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_PH8_2RMNET, 0xff), - .driver_info = (kernel_ulong_t)&cinterion_rmnet2_blacklist }, + .driver_info = RSVD(4) | RSVD(5) }, { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_PH8_AUDIO, 0xff), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_AHXX_2RMNET, 0xff) }, { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_AHXX_AUDIO, 0xff) }, { USB_DEVICE(CINTERION_VENDOR_ID, CINTERION_PRODUCT_HC28_MDM) }, @@ -1953,20 +1814,20 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE(SIEMENS_VENDOR_ID, CINTERION_PRODUCT_HC28_MDM) }, /* HC28 enumerates with Siemens or Cinterion VID depending on FW revision */ { USB_DEVICE(SIEMENS_VENDOR_ID, CINTERION_PRODUCT_HC28_MDMNET) }, { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD100), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD120), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD140), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD145) }, { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD155), - .driver_info = (kernel_ulong_t)&net_intf6_blacklist }, + .driver_info = RSVD(6) }, { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD200), - .driver_info = (kernel_ulong_t)&net_intf6_blacklist }, + .driver_info = RSVD(6) }, { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD160), - .driver_info = (kernel_ulong_t)&net_intf6_blacklist }, + .driver_info = RSVD(6) }, { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD500), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE(CELOT_VENDOR_ID, CELOT_PRODUCT_CT680M) }, /* CT-650 CDMA 450 1xEVDO modem */ { USB_DEVICE_AND_INTERFACE_INFO(SAMSUNG_VENDOR_ID, SAMSUNG_PRODUCT_GT_B3730, USB_CLASS_CDC_DATA, 0x00, 0x00) }, /* Samsung GT-B3730 LTE USB modem.*/ { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEM600) }, @@ -2043,9 +1904,9 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE(PETATEL_VENDOR_ID, PETATEL_PRODUCT_NP10T_600E) }, { USB_DEVICE_AND_INTERFACE_INFO(TPLINK_VENDOR_ID, TPLINK_PRODUCT_LTE, 0xff, 0x00, 0x00) }, /* TP-Link LTE Module */ { USB_DEVICE(TPLINK_VENDOR_ID, TPLINK_PRODUCT_MA180), - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE(TPLINK_VENDOR_ID, 0x9000), /* TP-Link MA260 */ - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE(CHANGHONG_VENDOR_ID, CHANGHONG_PRODUCT_CH690) }, { USB_DEVICE_INTERFACE_CLASS(0x2001, 0x7d01, 0xff) }, /* D-Link DWM-156 (variant) */ { USB_DEVICE_INTERFACE_CLASS(0x2001, 0x7d02, 0xff) }, @@ -2053,9 +1914,9 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_INTERFACE_CLASS(0x2001, 0x7d04, 0xff) }, /* D-Link DWM-158 */ { USB_DEVICE_INTERFACE_CLASS(0x2001, 0x7d0e, 0xff) }, /* D-Link DWM-157 C1 */ { USB_DEVICE_INTERFACE_CLASS(0x2001, 0x7e19, 0xff), /* D-Link DWM-221 B1 */ - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_INTERFACE_CLASS(0x2001, 0x7e35, 0xff), /* D-Link DWM-222 */ - .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + .driver_info = RSVD(4) }, { USB_DEVICE_AND_INTERFACE_INFO(0x07d1, 0x3e01, 0xff, 0xff, 0xff) }, /* D-Link DWM-152/C1 */ { USB_DEVICE_AND_INTERFACE_INFO(0x07d1, 0x3e02, 0xff, 0xff, 0xff) }, /* D-Link DWM-156/C1 */ { USB_DEVICE_AND_INTERFACE_INFO(0x07d1, 0x7e11, 0xff, 0xff, 0xff) }, /* D-Link DWM-156/A3 */ @@ -2114,11 +1975,10 @@ static int option_probe(struct usb_serial *serial, { struct usb_interface_descriptor *iface_desc = &serial->interface->cur_altsetting->desc; - struct usb_device_descriptor *dev_desc = &serial->dev->descriptor; - const struct option_blacklist_info *blacklist; + unsigned long device_flags = id->driver_info; /* Never bind to the CD-Rom emulation interface */ - if (iface_desc->bInterfaceClass == 0x08) + if (iface_desc->bInterfaceClass == USB_CLASS_MASS_STORAGE) return -ENODEV; /* @@ -2126,21 +1986,11 @@ static int option_probe(struct usb_serial *serial, * the same class/subclass/protocol as the serial interfaces. Look at * the Windows driver .INF files for reserved interface numbers. */ - blacklist = (void *)id->driver_info; - if (blacklist && test_bit(iface_desc->bInterfaceNumber, - &blacklist->reserved)) - return -ENODEV; - /* - * Don't bind network interface on Samsung GT-B3730, it is handled by - * a separate module. - */ - if (dev_desc->idVendor == cpu_to_le16(SAMSUNG_VENDOR_ID) && - dev_desc->idProduct == cpu_to_le16(SAMSUNG_PRODUCT_GT_B3730) && - iface_desc->bInterfaceClass != USB_CLASS_CDC_DATA) + if (device_flags & RSVD(iface_desc->bInterfaceNumber)) return -ENODEV; - /* Store the blacklist info so we can use it during attach. */ - usb_set_serial_data(serial, (void *)blacklist); + /* Store the device flags so we can use them during attach. */ + usb_set_serial_data(serial, (void *)device_flags); return 0; } @@ -2148,22 +1998,21 @@ static int option_probe(struct usb_serial *serial, static int option_attach(struct usb_serial *serial) { struct usb_interface_descriptor *iface_desc; - const struct option_blacklist_info *blacklist; struct usb_wwan_intf_private *data; + unsigned long device_flags; data = kzalloc(sizeof(struct usb_wwan_intf_private), GFP_KERNEL); if (!data) return -ENOMEM; - /* Retrieve blacklist info stored at probe. */ - blacklist = usb_get_serial_data(serial); + /* Retrieve device flags stored at probe. */ + device_flags = (unsigned long)usb_get_serial_data(serial); iface_desc = &serial->interface->cur_altsetting->desc; - if (!blacklist || !test_bit(iface_desc->bInterfaceNumber, - &blacklist->sendsetup)) { + if (!(device_flags & NCTRL(iface_desc->bInterfaceNumber))) data->use_send_setup = 1; - } + spin_lock_init(&data->susp_lock); usb_set_serial_data(serial, data); diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig index bcb2744c5977..030f88cb0c3f 100644 --- a/drivers/usb/typec/Kconfig +++ b/drivers/usb/typec/Kconfig @@ -48,6 +48,7 @@ if TYPEC config TYPEC_TCPM tristate "USB Type-C Port Controller Manager" depends on USB + select USB_ROLE_SWITCH help The Type-C Port Controller Manager provides a USB PD and USB Type-C state machine for use with Type-C Port Controllers. @@ -84,4 +85,6 @@ config TYPEC_TPS6598X If you choose to build this driver as a dynamically linked module, the module will be called tps6598x.ko. +source "drivers/usb/typec/mux/Kconfig" + endif # TYPEC diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile index bb3138a6eaac..1f599a6c30cc 100644 --- a/drivers/usb/typec/Makefile +++ b/drivers/usb/typec/Makefile @@ -1,7 +1,9 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_TYPEC) += typec.o +typec-y := class.o mux.o obj-$(CONFIG_TYPEC_TCPM) += tcpm.o obj-y += fusb302/ obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o obj-$(CONFIG_TYPEC_UCSI) += ucsi/ obj-$(CONFIG_TYPEC_TPS6598X) += tps6598x.o +obj-$(CONFIG_TYPEC) += mux/ diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/class.c index 735726ced602..53df10df2f9d 100644 --- a/drivers/usb/typec/typec.c +++ b/drivers/usb/typec/class.c @@ -11,6 +11,7 @@ #include <linux/mutex.h> #include <linux/slab.h> #include <linux/usb/typec.h> +#include <linux/usb/typec_mux.h> struct typec_mode { int index; @@ -70,6 +71,10 @@ struct typec_port { enum typec_port_type port_type; struct mutex port_type_lock; + enum typec_orientation orientation; + struct typec_switch *sw; + struct typec_mux *mux; + const struct typec_capability *cap; }; @@ -92,6 +97,7 @@ static const struct device_type typec_port_dev_type; static DEFINE_IDA(typec_index_ida); static struct class *typec_class; +/* ------------------------------------------------------------------------- */ /* Common attributes */ static const char * const typec_accessory_modes[] = { @@ -276,10 +282,10 @@ typec_altmode_roles_show(struct device *dev, struct device_attribute *attr, ssize_t ret; switch (mode->roles) { - case TYPEC_PORT_DFP: + case TYPEC_PORT_SRC: ret = sprintf(buf, "source\n"); break; - case TYPEC_PORT_UFP: + case TYPEC_PORT_SNK: ret = sprintf(buf, "sink\n"); break; case TYPEC_PORT_DRP: @@ -386,7 +392,7 @@ typec_register_altmode(struct device *parent, alt = kzalloc(sizeof(*alt), GFP_KERNEL); if (!alt) - return NULL; + return ERR_PTR(-ENOMEM); alt->svid = desc->svid; alt->n_modes = desc->n_modes; @@ -402,7 +408,7 @@ typec_register_altmode(struct device *parent, dev_err(parent, "failed to register alternate mode (%d)\n", ret); put_device(&alt->dev); - return NULL; + return ERR_PTR(ret); } return alt; @@ -417,7 +423,7 @@ typec_register_altmode(struct device *parent, */ void typec_unregister_altmode(struct typec_altmode *alt) { - if (alt) + if (!IS_ERR_OR_NULL(alt)) device_unregister(&alt->dev); } EXPORT_SYMBOL_GPL(typec_unregister_altmode); @@ -509,7 +515,7 @@ EXPORT_SYMBOL_GPL(typec_partner_register_altmode); * * Registers a device for USB Type-C Partner described in @desc. * - * Returns handle to the partner on success or NULL on failure. + * Returns handle to the partner on success or ERR_PTR on failure. */ struct typec_partner *typec_register_partner(struct typec_port *port, struct typec_partner_desc *desc) @@ -519,7 +525,7 @@ struct typec_partner *typec_register_partner(struct typec_port *port, partner = kzalloc(sizeof(*partner), GFP_KERNEL); if (!partner) - return NULL; + return ERR_PTR(-ENOMEM); partner->usb_pd = desc->usb_pd; partner->accessory = desc->accessory; @@ -542,7 +548,7 @@ struct typec_partner *typec_register_partner(struct typec_port *port, if (ret) { dev_err(&port->dev, "failed to register partner (%d)\n", ret); put_device(&partner->dev); - return NULL; + return ERR_PTR(ret); } return partner; @@ -557,7 +563,7 @@ EXPORT_SYMBOL_GPL(typec_register_partner); */ void typec_unregister_partner(struct typec_partner *partner) { - if (partner) + if (!IS_ERR_OR_NULL(partner)) device_unregister(&partner->dev); } EXPORT_SYMBOL_GPL(typec_unregister_partner); @@ -587,7 +593,7 @@ static const struct device_type typec_plug_dev_type = { * the plug lists in response to Discover Modes command need to be listed in an * array in @desc. * - * Returns handle to the alternate mode on success or NULL on failure. + * Returns handle to the alternate mode on success or ERR_PTR on failure. */ struct typec_altmode * typec_plug_register_altmode(struct typec_plug *plug, @@ -606,7 +612,7 @@ EXPORT_SYMBOL_GPL(typec_plug_register_altmode); * Cable Plug represents a plug with electronics in it that can response to USB * Power Delivery SOP Prime or SOP Double Prime packages. * - * Returns handle to the cable plug on success or NULL on failure. + * Returns handle to the cable plug on success or ERR_PTR on failure. */ struct typec_plug *typec_register_plug(struct typec_cable *cable, struct typec_plug_desc *desc) @@ -617,7 +623,7 @@ struct typec_plug *typec_register_plug(struct typec_cable *cable, plug = kzalloc(sizeof(*plug), GFP_KERNEL); if (!plug) - return NULL; + return ERR_PTR(-ENOMEM); sprintf(name, "plug%d", desc->index); @@ -631,7 +637,7 @@ struct typec_plug *typec_register_plug(struct typec_cable *cable, if (ret) { dev_err(&cable->dev, "failed to register plug (%d)\n", ret); put_device(&plug->dev); - return NULL; + return ERR_PTR(ret); } return plug; @@ -646,7 +652,7 @@ EXPORT_SYMBOL_GPL(typec_register_plug); */ void typec_unregister_plug(struct typec_plug *plug) { - if (plug) + if (!IS_ERR_OR_NULL(plug)) device_unregister(&plug->dev); } EXPORT_SYMBOL_GPL(typec_unregister_plug); @@ -724,7 +730,7 @@ EXPORT_SYMBOL_GPL(typec_cable_set_identity); * Registers a device for USB Type-C Cable described in @desc. The cable will be * parent for the optional cable plug devises. * - * Returns handle to the cable on success or NULL on failure. + * Returns handle to the cable on success or ERR_PTR on failure. */ struct typec_cable *typec_register_cable(struct typec_port *port, struct typec_cable_desc *desc) @@ -734,7 +740,7 @@ struct typec_cable *typec_register_cable(struct typec_port *port, cable = kzalloc(sizeof(*cable), GFP_KERNEL); if (!cable) - return NULL; + return ERR_PTR(-ENOMEM); cable->type = desc->type; cable->active = desc->active; @@ -757,7 +763,7 @@ struct typec_cable *typec_register_cable(struct typec_port *port, if (ret) { dev_err(&port->dev, "failed to register cable (%d)\n", ret); put_device(&cable->dev); - return NULL; + return ERR_PTR(ret); } return cable; @@ -772,7 +778,7 @@ EXPORT_SYMBOL_GPL(typec_register_cable); */ void typec_unregister_cable(struct typec_cable *cable) { - if (cable) + if (!IS_ERR_OR_NULL(cable)) device_unregister(&cable->dev); } EXPORT_SYMBOL_GPL(typec_unregister_cable); @@ -791,14 +797,14 @@ static const char * const typec_data_roles[] = { }; static const char * const typec_port_types[] = { - [TYPEC_PORT_DFP] = "source", - [TYPEC_PORT_UFP] = "sink", + [TYPEC_PORT_SRC] = "source", + [TYPEC_PORT_SNK] = "sink", [TYPEC_PORT_DRP] = "dual", }; static const char * const typec_port_types_drp[] = { - [TYPEC_PORT_DFP] = "dual [source] sink", - [TYPEC_PORT_UFP] = "dual source [sink]", + [TYPEC_PORT_SRC] = "dual [source] sink", + [TYPEC_PORT_SNK] = "dual source [sink]", [TYPEC_PORT_DRP] = "[dual] source sink", }; @@ -869,9 +875,7 @@ static ssize_t data_role_store(struct device *dev, return ret; mutex_lock(&port->port_type_lock); - if (port->port_type != TYPEC_PORT_DRP) { - dev_dbg(dev, "port type fixed at \"%s\"", - typec_port_types[port->port_type]); + if (port->cap->data != TYPEC_PORT_DRD) { ret = -EOPNOTSUPP; goto unlock_and_ret; } @@ -891,7 +895,7 @@ static ssize_t data_role_show(struct device *dev, { struct typec_port *port = to_typec_port(dev); - if (port->cap->type == TYPEC_PORT_DRP) + if (port->cap->data == TYPEC_PORT_DRD) return sprintf(buf, "%s\n", port->data_role == TYPEC_HOST ? "[host] device" : "host [device]"); @@ -1137,6 +1141,8 @@ static void typec_release(struct device *dev) struct typec_port *port = to_typec_port(dev); ida_simple_remove(&typec_index_ida, port->id); + typec_switch_put(port->sw); + typec_mux_put(port->mux); kfree(port); } @@ -1246,6 +1252,47 @@ void typec_set_pwr_opmode(struct typec_port *port, } EXPORT_SYMBOL_GPL(typec_set_pwr_opmode); +/* ------------------------------------------ */ +/* API for Multiplexer/DeMultiplexer Switches */ + +/** + * typec_set_orientation - Set USB Type-C cable plug orientation + * @port: USB Type-C Port + * @orientation: USB Type-C cable plug orientation + * + * Set cable plug orientation for @port. + */ +int typec_set_orientation(struct typec_port *port, + enum typec_orientation orientation) +{ + int ret; + + if (port->sw) { + ret = port->sw->set(port->sw, orientation); + if (ret) + return ret; + } + + port->orientation = orientation; + + return 0; +} +EXPORT_SYMBOL_GPL(typec_set_orientation); + +/** + * typec_set_mode - Set mode of operation for USB Type-C connector + * @port: USB Type-C port for the connector + * @mode: Operation mode for the connector + * + * Set mode @mode for @port. This function will configure the muxes needed to + * enter @mode. + */ +int typec_set_mode(struct typec_port *port, int mode) +{ + return port->mux ? port->mux->set(port->mux, mode) : 0; +} +EXPORT_SYMBOL_GPL(typec_set_mode); + /* --------------------------------------- */ /** @@ -1256,7 +1303,7 @@ EXPORT_SYMBOL_GPL(typec_set_pwr_opmode); * This routine is used to register an alternate mode that @port is capable of * supporting. * - * Returns handle to the alternate mode on success or NULL on failure. + * Returns handle to the alternate mode on success or ERR_PTR on failure. */ struct typec_altmode * typec_port_register_altmode(struct typec_port *port, @@ -1273,41 +1320,67 @@ EXPORT_SYMBOL_GPL(typec_port_register_altmode); * * Registers a device for USB Type-C Port described in @cap. * - * Returns handle to the port on success or NULL on failure. + * Returns handle to the port on success or ERR_PTR on failure. */ struct typec_port *typec_register_port(struct device *parent, const struct typec_capability *cap) { struct typec_port *port; - int role; int ret; int id; port = kzalloc(sizeof(*port), GFP_KERNEL); if (!port) - return NULL; + return ERR_PTR(-ENOMEM); id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL); if (id < 0) { kfree(port); - return NULL; + return ERR_PTR(id); } - if (cap->type == TYPEC_PORT_DFP) - role = TYPEC_SOURCE; - else if (cap->type == TYPEC_PORT_UFP) - role = TYPEC_SINK; - else - role = cap->prefer_role; + port->sw = typec_switch_get(cap->fwnode ? &port->dev : parent); + if (IS_ERR(port->sw)) { + ret = PTR_ERR(port->sw); + goto err_switch; + } - if (role == TYPEC_SOURCE) { - port->data_role = TYPEC_HOST; + port->mux = typec_mux_get(cap->fwnode ? &port->dev : parent); + if (IS_ERR(port->mux)) { + ret = PTR_ERR(port->mux); + goto err_mux; + } + + switch (cap->type) { + case TYPEC_PORT_SRC: port->pwr_role = TYPEC_SOURCE; port->vconn_role = TYPEC_SOURCE; - } else { - port->data_role = TYPEC_DEVICE; + break; + case TYPEC_PORT_SNK: port->pwr_role = TYPEC_SINK; port->vconn_role = TYPEC_SINK; + break; + case TYPEC_PORT_DRP: + if (cap->prefer_role != TYPEC_NO_PREFERRED_ROLE) + port->pwr_role = cap->prefer_role; + else + port->pwr_role = TYPEC_SINK; + break; + } + + switch (cap->data) { + case TYPEC_PORT_DFP: + port->data_role = TYPEC_HOST; + break; + case TYPEC_PORT_UFP: + port->data_role = TYPEC_DEVICE; + break; + case TYPEC_PORT_DRD: + if (cap->prefer_role == TYPEC_SOURCE) + port->data_role = TYPEC_HOST; + else + port->data_role = TYPEC_DEVICE; + break; } port->id = id; @@ -1326,10 +1399,19 @@ struct typec_port *typec_register_port(struct device *parent, if (ret) { dev_err(parent, "failed to register port (%d)\n", ret); put_device(&port->dev); - return NULL; + return ERR_PTR(ret); } return port; + +err_mux: + typec_switch_put(port->sw); + +err_switch: + ida_simple_remove(&typec_index_ida, port->id); + kfree(port); + + return ERR_PTR(ret); } EXPORT_SYMBOL_GPL(typec_register_port); @@ -1341,7 +1423,7 @@ EXPORT_SYMBOL_GPL(typec_register_port); */ void typec_unregister_port(struct typec_port *port) { - if (port) + if (!IS_ERR_OR_NULL(port)) device_unregister(&port->dev); } EXPORT_SYMBOL_GPL(typec_unregister_port); diff --git a/drivers/usb/typec/fusb302/fusb302.c b/drivers/usb/typec/fusb302/fusb302.c index dcd8ef085b30..703617129067 100644 --- a/drivers/usb/typec/fusb302/fusb302.c +++ b/drivers/usb/typec/fusb302/fusb302.c @@ -199,7 +199,7 @@ static void fusb302_log(struct fusb302_chip *chip, const char *fmt, ...) va_end(args); } -static int fusb302_seq_show(struct seq_file *s, void *v) +static int fusb302_debug_show(struct seq_file *s, void *v) { struct fusb302_chip *chip = (struct fusb302_chip *)s->private; int tail; @@ -216,18 +216,7 @@ static int fusb302_seq_show(struct seq_file *s, void *v) return 0; } - -static int fusb302_debug_open(struct inode *inode, struct file *file) -{ - return single_open(file, fusb302_seq_show, inode->i_private); -} - -static const struct file_operations fusb302_debug_operations = { - .open = fusb302_debug_open, - .llseek = seq_lseek, - .read = seq_read, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(fusb302_debug); static struct dentry *rootdir; @@ -242,7 +231,7 @@ static int fusb302_debugfs_init(struct fusb302_chip *chip) chip->dentry = debugfs_create_file(dev_name(chip->dev), S_IFREG | 0444, rootdir, - chip, &fusb302_debug_operations); + chip, &fusb302_debug_fops); return 0; } @@ -1230,6 +1219,7 @@ static const struct tcpc_config fusb302_tcpc_config = { .max_snk_mw = 15000, .operating_snk_mw = 2500, .type = TYPEC_PORT_DRP, + .data = TYPEC_PORT_DRD, .default_role = TYPEC_SINK, .alt_modes = NULL, }; @@ -1249,7 +1239,6 @@ static void init_tcpc_dev(struct tcpc_dev *fusb302_tcpc_dev) fusb302_tcpc_dev->set_roles = tcpm_set_roles; fusb302_tcpc_dev->start_drp_toggling = tcpm_start_drp_toggling; fusb302_tcpc_dev->pd_transmit = tcpm_pd_transmit; - fusb302_tcpc_dev->mux = NULL; } static const char * const cc_polarity_name[] = { diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c new file mode 100644 index 000000000000..f89093bd7185 --- /dev/null +++ b/drivers/usb/typec/mux.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * USB Type-C Multiplexer/DeMultiplexer Switch support + * + * Copyright (C) 2018 Intel Corporation + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> + * Hans de Goede <hdegoede@redhat.com> + */ + +#include <linux/device.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/usb/typec_mux.h> + +static DEFINE_MUTEX(switch_lock); +static DEFINE_MUTEX(mux_lock); +static LIST_HEAD(switch_list); +static LIST_HEAD(mux_list); + +static void *typec_switch_match(struct device_connection *con, int ep, + void *data) +{ + struct typec_switch *sw; + + list_for_each_entry(sw, &switch_list, entry) + if (!strcmp(con->endpoint[ep], dev_name(sw->dev))) + return sw; + + /* + * We only get called if a connection was found, tell the caller to + * wait for the switch to show up. + */ + return ERR_PTR(-EPROBE_DEFER); +} + +/** + * typec_switch_get - Find USB Type-C orientation switch + * @dev: The caller device + * + * Finds a switch linked with @dev. Returns a reference to the switch on + * success, NULL if no matching connection was found, or + * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch + * has not been enumerated yet. + */ +struct typec_switch *typec_switch_get(struct device *dev) +{ + struct typec_switch *sw; + + mutex_lock(&switch_lock); + sw = device_connection_find_match(dev, "typec-switch", NULL, + typec_switch_match); + if (!IS_ERR_OR_NULL(sw)) + get_device(sw->dev); + mutex_unlock(&switch_lock); + + return sw; +} +EXPORT_SYMBOL_GPL(typec_switch_get); + +/** + * typec_put_switch - Release USB Type-C orientation switch + * @sw: USB Type-C orientation switch + * + * Decrement reference count for @sw. + */ +void typec_switch_put(struct typec_switch *sw) +{ + if (!IS_ERR_OR_NULL(sw)) + put_device(sw->dev); +} +EXPORT_SYMBOL_GPL(typec_switch_put); + +/** + * typec_switch_register - Register USB Type-C orientation switch + * @sw: USB Type-C orientation switch + * + * This function registers a switch that can be used for routing the correct + * data pairs depending on the cable plug orientation from the USB Type-C + * connector to the USB controllers. USB Type-C plugs can be inserted + * right-side-up or upside-down. + */ +int typec_switch_register(struct typec_switch *sw) +{ + mutex_lock(&switch_lock); + list_add_tail(&sw->entry, &switch_list); + mutex_unlock(&switch_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(typec_switch_register); + +/** + * typec_switch_unregister - Unregister USB Type-C orientation switch + * @sw: USB Type-C orientation switch + * + * Unregister switch that was registered with typec_switch_register(). + */ +void typec_switch_unregister(struct typec_switch *sw) +{ + mutex_lock(&switch_lock); + list_del(&sw->entry); + mutex_unlock(&switch_lock); +} +EXPORT_SYMBOL_GPL(typec_switch_unregister); + +/* ------------------------------------------------------------------------- */ + +static void *typec_mux_match(struct device_connection *con, int ep, void *data) +{ + struct typec_mux *mux; + + list_for_each_entry(mux, &mux_list, entry) + if (!strcmp(con->endpoint[ep], dev_name(mux->dev))) + return mux; + + /* + * We only get called if a connection was found, tell the caller to + * wait for the switch to show up. + */ + return ERR_PTR(-EPROBE_DEFER); +} + +/** + * typec_mux_get - Find USB Type-C Multiplexer + * @dev: The caller device + * + * Finds a mux linked to the caller. This function is primarily meant for the + * Type-C drivers. Returns a reference to the mux on success, NULL if no + * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection + * was found but the mux has not been enumerated yet. + */ +struct typec_mux *typec_mux_get(struct device *dev) +{ + struct typec_mux *mux; + + mutex_lock(&mux_lock); + mux = device_connection_find_match(dev, "typec-mux", NULL, + typec_mux_match); + if (!IS_ERR_OR_NULL(mux)) + get_device(mux->dev); + mutex_unlock(&mux_lock); + + return mux; +} +EXPORT_SYMBOL_GPL(typec_mux_get); + +/** + * typec_mux_put - Release handle to a Multiplexer + * @mux: USB Type-C Connector Multiplexer/DeMultiplexer + * + * Decrements reference count for @mux. + */ +void typec_mux_put(struct typec_mux *mux) +{ + if (!IS_ERR_OR_NULL(mux)) + put_device(mux->dev); +} +EXPORT_SYMBOL_GPL(typec_mux_put); + +/** + * typec_mux_register - Register Multiplexer routing USB Type-C pins + * @mux: USB Type-C Connector Multiplexer/DeMultiplexer + * + * USB Type-C connectors can be used for alternate modes of operation besides + * USB when Accessory/Alternate Modes are supported. With some of those modes, + * the pins on the connector need to be reconfigured. This function registers + * multiplexer switches routing the pins on the connector. + */ +int typec_mux_register(struct typec_mux *mux) +{ + mutex_lock(&mux_lock); + list_add_tail(&mux->entry, &mux_list); + mutex_unlock(&mux_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(typec_mux_register); + +/** + * typec_mux_unregister - Unregister Multiplexer Switch + * @sw: USB Type-C Connector Multiplexer/DeMultiplexer + * + * Unregister mux that was registered with typec_mux_register(). + */ +void typec_mux_unregister(struct typec_mux *mux) +{ + mutex_lock(&mux_lock); + list_del(&mux->entry); + mutex_unlock(&mux_lock); +} +EXPORT_SYMBOL_GPL(typec_mux_unregister); diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig new file mode 100644 index 000000000000..9a954d2b8d8f --- /dev/null +++ b/drivers/usb/typec/mux/Kconfig @@ -0,0 +1,10 @@ +menu "USB Type-C Multiplexer/DeMultiplexer Switch support" + +config TYPEC_MUX_PI3USB30532 + tristate "Pericom PI3USB30532 Type-C cross switch driver" + depends on I2C + help + Say Y or M if your system has a Pericom PI3USB30532 Type-C cross + switch / mux chip found on some devices with a Type-C port. + +endmenu diff --git a/drivers/usb/typec/mux/Makefile b/drivers/usb/typec/mux/Makefile new file mode 100644 index 000000000000..1332e469b8a0 --- /dev/null +++ b/drivers/usb/typec/mux/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_TYPEC_MUX_PI3USB30532) += pi3usb30532.o diff --git a/drivers/usb/typec/mux/pi3usb30532.c b/drivers/usb/typec/mux/pi3usb30532.c new file mode 100644 index 000000000000..b0e88db60ecf --- /dev/null +++ b/drivers/usb/typec/mux/pi3usb30532.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Pericom PI3USB30532 Type-C cross switch / mux driver + * + * Copyright (c) 2017-2018 Hans de Goede <hdegoede@redhat.com> + */ + +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/usb/tcpm.h> +#include <linux/usb/typec_mux.h> + +#define PI3USB30532_CONF 0x00 + +#define PI3USB30532_CONF_OPEN 0x00 +#define PI3USB30532_CONF_SWAP 0x01 +#define PI3USB30532_CONF_4LANE_DP 0x02 +#define PI3USB30532_CONF_USB3 0x04 +#define PI3USB30532_CONF_USB3_AND_2LANE_DP 0x06 + +struct pi3usb30532 { + struct i2c_client *client; + struct mutex lock; /* protects the cached conf register */ + struct typec_switch sw; + struct typec_mux mux; + u8 conf; +}; + +static int pi3usb30532_set_conf(struct pi3usb30532 *pi, u8 new_conf) +{ + int ret = 0; + + if (pi->conf == new_conf) + return 0; + + ret = i2c_smbus_write_byte_data(pi->client, PI3USB30532_CONF, new_conf); + if (ret) { + dev_err(&pi->client->dev, "Error writing conf: %d\n", ret); + return ret; + } + + pi->conf = new_conf; + return 0; +} + +static int pi3usb30532_sw_set(struct typec_switch *sw, + enum typec_orientation orientation) +{ + struct pi3usb30532 *pi = container_of(sw, struct pi3usb30532, sw); + u8 new_conf; + int ret; + + mutex_lock(&pi->lock); + new_conf = pi->conf; + + switch (orientation) { + case TYPEC_ORIENTATION_NONE: + new_conf = PI3USB30532_CONF_OPEN; + break; + case TYPEC_ORIENTATION_NORMAL: + new_conf &= ~PI3USB30532_CONF_SWAP; + break; + case TYPEC_ORIENTATION_REVERSE: + new_conf |= PI3USB30532_CONF_SWAP; + break; + } + + ret = pi3usb30532_set_conf(pi, new_conf); + mutex_unlock(&pi->lock); + + return ret; +} + +static int pi3usb30532_mux_set(struct typec_mux *mux, int state) +{ + struct pi3usb30532 *pi = container_of(mux, struct pi3usb30532, mux); + u8 new_conf; + int ret; + + mutex_lock(&pi->lock); + new_conf = pi->conf; + + switch (state) { + case TYPEC_MUX_NONE: + new_conf = PI3USB30532_CONF_OPEN; + break; + case TYPEC_MUX_USB: + new_conf = (new_conf & PI3USB30532_CONF_SWAP) | + PI3USB30532_CONF_USB3; + break; + case TYPEC_MUX_DP: + new_conf = (new_conf & PI3USB30532_CONF_SWAP) | + PI3USB30532_CONF_4LANE_DP; + break; + case TYPEC_MUX_DOCK: + new_conf = (new_conf & PI3USB30532_CONF_SWAP) | + PI3USB30532_CONF_USB3_AND_2LANE_DP; + break; + } + + ret = pi3usb30532_set_conf(pi, new_conf); + mutex_unlock(&pi->lock); + + return ret; +} + +static int pi3usb30532_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct pi3usb30532 *pi; + int ret; + + pi = devm_kzalloc(dev, sizeof(*pi), GFP_KERNEL); + if (!pi) + return -ENOMEM; + + pi->client = client; + pi->sw.dev = dev; + pi->sw.set = pi3usb30532_sw_set; + pi->mux.dev = dev; + pi->mux.set = pi3usb30532_mux_set; + mutex_init(&pi->lock); + + ret = i2c_smbus_read_byte_data(client, PI3USB30532_CONF); + if (ret < 0) { + dev_err(dev, "Error reading config register %d\n", ret); + return ret; + } + pi->conf = ret; + + ret = typec_switch_register(&pi->sw); + if (ret) { + dev_err(dev, "Error registering typec switch: %d\n", ret); + return ret; + } + + ret = typec_mux_register(&pi->mux); + if (ret) { + typec_switch_unregister(&pi->sw); + dev_err(dev, "Error registering typec mux: %d\n", ret); + return ret; + } + + i2c_set_clientdata(client, pi); + return 0; +} + +static int pi3usb30532_remove(struct i2c_client *client) +{ + struct pi3usb30532 *pi = i2c_get_clientdata(client); + + typec_mux_unregister(&pi->mux); + typec_switch_unregister(&pi->sw); + return 0; +} + +static const struct i2c_device_id pi3usb30532_table[] = { + { "pi3usb30532" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pi3usb30532_table); + +static struct i2c_driver pi3usb30532_driver = { + .driver = { + .name = "pi3usb30532", + }, + .probe_new = pi3usb30532_probe, + .remove = pi3usb30532_remove, + .id_table = pi3usb30532_table, +}; + +module_i2c_driver(pi3usb30532_driver); + +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); +MODULE_DESCRIPTION("Pericom PI3USB30532 Type-C mux driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c index 8b637a4b474b..677d12138dbd 100644 --- a/drivers/usb/typec/tcpm.c +++ b/drivers/usb/typec/tcpm.c @@ -20,6 +20,7 @@ #include <linux/usb/pd.h> #include <linux/usb/pd_bdo.h> #include <linux/usb/pd_vdo.h> +#include <linux/usb/role.h> #include <linux/usb/tcpm.h> #include <linux/usb/typec.h> #include <linux/workqueue.h> @@ -176,6 +177,7 @@ struct tcpm_port { struct typec_port *typec_port; struct tcpc_dev *tcpc; + struct usb_role_switch *role_sw; enum typec_role vconn_role; enum typec_role pwr_role; @@ -345,7 +347,7 @@ static enum tcpm_state tcpm_default_state(struct tcpm_port *port) else if (port->tcpc->config->default_role == TYPEC_SINK) return SNK_UNATTACHED; /* Fall through to return SRC_UNATTACHED */ - } else if (port->port_type == TYPEC_PORT_UFP) { + } else if (port->port_type == TYPEC_PORT_SNK) { return SNK_UNATTACHED; } return SRC_UNATTACHED; @@ -503,7 +505,7 @@ static void tcpm_log_source_caps(struct tcpm_port *port) } } -static int tcpm_seq_show(struct seq_file *s, void *v) +static int tcpm_debug_show(struct seq_file *s, void *v) { struct tcpm_port *port = (struct tcpm_port *)s->private; int tail; @@ -520,18 +522,7 @@ static int tcpm_seq_show(struct seq_file *s, void *v) return 0; } - -static int tcpm_debug_open(struct inode *inode, struct file *file) -{ - return single_open(file, tcpm_seq_show, inode->i_private); -} - -static const struct file_operations tcpm_debug_operations = { - .open = tcpm_debug_open, - .llseek = seq_lseek, - .read = seq_read, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(tcpm_debug); static struct dentry *rootdir; @@ -547,7 +538,7 @@ static int tcpm_debugfs_init(struct tcpm_port *port) port->dentry = debugfs_create_file(dev_name(port->dev), S_IFREG | 0444, rootdir, - port, &tcpm_debug_operations); + port, &tcpm_debug_fops); return 0; } @@ -615,18 +606,25 @@ void tcpm_pd_transmit_complete(struct tcpm_port *port, EXPORT_SYMBOL_GPL(tcpm_pd_transmit_complete); static int tcpm_mux_set(struct tcpm_port *port, enum tcpc_mux_mode mode, - enum tcpc_usb_switch config) + enum usb_role usb_role, + enum typec_orientation orientation) { - int ret = 0; + int ret; - tcpm_log(port, "Requesting mux mode %d, config %d, polarity %d", - mode, config, port->polarity); + tcpm_log(port, "Requesting mux mode %d, usb-role %d, orientation %d", + mode, usb_role, orientation); - if (port->tcpc->mux) - ret = port->tcpc->mux->set(port->tcpc->mux, mode, config, - port->polarity); + ret = typec_set_orientation(port->typec_port, orientation); + if (ret) + return ret; - return ret; + if (port->role_sw) { + ret = usb_role_switch_set_role(port->role_sw, usb_role); + if (ret) + return ret; + } + + return typec_set_mode(port->typec_port, mode); } static int tcpm_set_polarity(struct tcpm_port *port, @@ -739,14 +737,21 @@ static int tcpm_set_attached_state(struct tcpm_port *port, bool attached) static int tcpm_set_roles(struct tcpm_port *port, bool attached, enum typec_role role, enum typec_data_role data) { + enum typec_orientation orientation; + enum usb_role usb_role; int ret; + if (port->polarity == TYPEC_POLARITY_CC1) + orientation = TYPEC_ORIENTATION_NORMAL; + else + orientation = TYPEC_ORIENTATION_REVERSE; + if (data == TYPEC_HOST) - ret = tcpm_mux_set(port, TYPEC_MUX_USB, - TCPC_USB_SWITCH_CONNECT); + usb_role = USB_ROLE_HOST; else - ret = tcpm_mux_set(port, TYPEC_MUX_NONE, - TCPC_USB_SWITCH_DISCONNECT); + usb_role = USB_ROLE_DEVICE; + + ret = tcpm_mux_set(port, TYPEC_MUX_USB, usb_role, orientation); if (ret < 0) return ret; @@ -1041,7 +1046,7 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt, break; case CMDT_RSP_ACK: /* silently drop message if we are not connected */ - if (!port->partner) + if (IS_ERR_OR_NULL(port->partner)) break; switch (cmd) { @@ -2039,7 +2044,8 @@ out_disable_vconn: out_disable_pd: port->tcpc->set_pd_rx(port->tcpc, false); out_disable_mux: - tcpm_mux_set(port, TYPEC_MUX_NONE, TCPC_USB_SWITCH_DISCONNECT); + tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE, + TYPEC_ORIENTATION_NONE); return ret; } @@ -2083,6 +2089,8 @@ static void tcpm_reset_port(struct tcpm_port *port) tcpm_init_vconn(port); tcpm_set_current_limit(port, 0, 0); tcpm_set_polarity(port, TYPEC_POLARITY_CC1); + tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE, + TYPEC_ORIENTATION_NONE); tcpm_set_attached_state(port, false); port->try_src_count = 0; port->try_snk_count = 0; @@ -2133,8 +2141,6 @@ static int tcpm_snk_attach(struct tcpm_port *port) static void tcpm_snk_detach(struct tcpm_port *port) { tcpm_detach(port); - - /* XXX: (Dis)connect SuperSpeed mux? */ } static int tcpm_acc_attach(struct tcpm_port *port) @@ -2190,7 +2196,7 @@ static inline enum tcpm_state unattached_state(struct tcpm_port *port) return SRC_UNATTACHED; else return SNK_UNATTACHED; - } else if (port->port_type == TYPEC_PORT_DFP) { + } else if (port->port_type == TYPEC_PORT_SRC) { return SRC_UNATTACHED; } @@ -3480,11 +3486,11 @@ static int tcpm_port_type_set(const struct typec_capability *cap, if (!port->connected) { tcpm_set_state(port, PORT_RESET, 0); - } else if (type == TYPEC_PORT_UFP) { + } else if (type == TYPEC_PORT_SNK) { if (!(port->pwr_role == TYPEC_SINK && port->data_role == TYPEC_DEVICE)) tcpm_set_state(port, PORT_RESET, 0); - } else if (type == TYPEC_PORT_DFP) { + } else if (type == TYPEC_PORT_SRC) { if (!(port->pwr_role == TYPEC_SOURCE && port->data_role == TYPEC_HOST)) tcpm_set_state(port, PORT_RESET, 0); @@ -3652,6 +3658,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) port->typec_caps.prefer_role = tcpc->config->default_role; port->typec_caps.type = tcpc->config->type; + port->typec_caps.data = tcpc->config->data; port->typec_caps.revision = 0x0120; /* Type-C spec release 1.2 */ port->typec_caps.pd_revision = 0x0200; /* USB-PD spec release 2.0 */ port->typec_caps.dr_set = tcpm_dr_set; @@ -3663,9 +3670,15 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) port->partner_desc.identity = &port->partner_ident; port->port_type = tcpc->config->type; + port->role_sw = usb_role_switch_get(port->dev); + if (IS_ERR(port->role_sw)) { + err = PTR_ERR(port->role_sw); + goto out_destroy_wq; + } + port->typec_port = typec_register_port(port->dev, &port->typec_caps); - if (!port->typec_port) { - err = -ENOMEM; + if (IS_ERR(port->typec_port)) { + err = PTR_ERR(port->typec_port); goto out_destroy_wq; } @@ -3674,15 +3687,17 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) i = 0; while (paltmode->svid && i < ARRAY_SIZE(port->port_altmode)) { - port->port_altmode[i] = - typec_port_register_altmode(port->typec_port, - paltmode); - if (!port->port_altmode[i]) { + struct typec_altmode *alt; + + alt = typec_port_register_altmode(port->typec_port, + paltmode); + if (IS_ERR(alt)) { tcpm_log(port, "%s: failed to register port alternate mode 0x%x", dev_name(dev), paltmode->svid); break; } + port->port_altmode[i] = alt; i++; paltmode++; } @@ -3696,6 +3711,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) return port; out_destroy_wq: + usb_role_switch_put(port->role_sw); destroy_workqueue(port->wq); return ERR_PTR(err); } diff --git a/drivers/usb/typec/tps6598x.c b/drivers/usb/typec/tps6598x.c index 2719f5d382f7..8b8406867c02 100644 --- a/drivers/usb/typec/tps6598x.c +++ b/drivers/usb/typec/tps6598x.c @@ -158,15 +158,15 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status) desc.identity = &tps->partner_identity; } - tps->partner = typec_register_partner(tps->port, &desc); - if (!tps->partner) - return -ENODEV; - typec_set_pwr_opmode(tps->port, mode); typec_set_pwr_role(tps->port, TPS_STATUS_PORTROLE(status)); typec_set_vconn_role(tps->port, TPS_STATUS_VCONN(status)); typec_set_data_role(tps->port, TPS_STATUS_DATAROLE(status)); + tps->partner = typec_register_partner(tps->port, &desc); + if (IS_ERR(tps->partner)) + return PTR_ERR(tps->partner); + if (desc.identity) typec_partner_set_identity(tps->partner); @@ -175,7 +175,8 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status) static void tps6598x_disconnect(struct tps6598x *tps, u32 status) { - typec_unregister_partner(tps->partner); + if (!IS_ERR(tps->partner)) + typec_unregister_partner(tps->partner); tps->partner = NULL; typec_set_pwr_opmode(tps->port, TYPEC_PWR_MODE_USB); typec_set_pwr_role(tps->port, TPS_STATUS_PORTROLE(status)); @@ -392,34 +393,42 @@ static int tps6598x_probe(struct i2c_client *client) if (ret < 0) return ret; + tps->typec_cap.revision = USB_TYPEC_REV_1_2; + tps->typec_cap.pd_revision = 0x200; + tps->typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; + tps->typec_cap.pr_set = tps6598x_pr_set; + tps->typec_cap.dr_set = tps6598x_dr_set; + switch (TPS_SYSCONF_PORTINFO(conf)) { case TPS_PORTINFO_SINK_ACCESSORY: case TPS_PORTINFO_SINK: - tps->typec_cap.type = TYPEC_PORT_UFP; + tps->typec_cap.type = TYPEC_PORT_SNK; + tps->typec_cap.data = TYPEC_PORT_UFP; break; case TPS_PORTINFO_DRP_UFP_DRD: case TPS_PORTINFO_DRP_DFP_DRD: - tps->typec_cap.dr_set = tps6598x_dr_set; - /* fall through */ + tps->typec_cap.type = TYPEC_PORT_DRP; + tps->typec_cap.data = TYPEC_PORT_DRD; + break; case TPS_PORTINFO_DRP_UFP: + tps->typec_cap.type = TYPEC_PORT_DRP; + tps->typec_cap.data = TYPEC_PORT_UFP; + break; case TPS_PORTINFO_DRP_DFP: - tps->typec_cap.pr_set = tps6598x_pr_set; tps->typec_cap.type = TYPEC_PORT_DRP; + tps->typec_cap.data = TYPEC_PORT_DFP; break; case TPS_PORTINFO_SOURCE: - tps->typec_cap.type = TYPEC_PORT_DFP; + tps->typec_cap.type = TYPEC_PORT_SRC; + tps->typec_cap.data = TYPEC_PORT_DFP; break; default: return -ENODEV; } - tps->typec_cap.revision = USB_TYPEC_REV_1_2; - tps->typec_cap.pd_revision = 0x200; - tps->typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; - tps->port = typec_register_port(&client->dev, &tps->typec_cap); - if (!tps->port) - return -ENODEV; + if (IS_ERR(tps->port)) + return PTR_ERR(tps->port); if (status & TPS_STATUS_PLUG_PRESENT) { ret = tps6598x_connect(tps, status); diff --git a/drivers/usb/typec/typec_wcove.c b/drivers/usb/typec/typec_wcove.c index 2e990e0d917d..19cca7f1b2c5 100644 --- a/drivers/usb/typec/typec_wcove.c +++ b/drivers/usb/typec/typec_wcove.c @@ -572,6 +572,7 @@ static struct tcpc_config wcove_typec_config = { .operating_snk_mw = 15000, .type = TYPEC_PORT_DRP, + .data = TYPEC_PORT_DRD, .default_role = TYPEC_SINK, }; diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 79046fe66426..bf0977fbd100 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -260,38 +260,45 @@ static void ucsi_pwr_opmode_change(struct ucsi_connector *con) static int ucsi_register_partner(struct ucsi_connector *con) { - struct typec_partner_desc partner; + struct typec_partner_desc desc; + struct typec_partner *partner; if (con->partner) return 0; - memset(&partner, 0, sizeof(partner)); + memset(&desc, 0, sizeof(desc)); switch (con->status.partner_type) { case UCSI_CONSTAT_PARTNER_TYPE_DEBUG: - partner.accessory = TYPEC_ACCESSORY_DEBUG; + desc.accessory = TYPEC_ACCESSORY_DEBUG; break; case UCSI_CONSTAT_PARTNER_TYPE_AUDIO: - partner.accessory = TYPEC_ACCESSORY_AUDIO; + desc.accessory = TYPEC_ACCESSORY_AUDIO; break; default: break; } - partner.usb_pd = con->status.pwr_op_mode == UCSI_CONSTAT_PWR_OPMODE_PD; + desc.usb_pd = con->status.pwr_op_mode == UCSI_CONSTAT_PWR_OPMODE_PD; - con->partner = typec_register_partner(con->port, &partner); - if (!con->partner) { - dev_err(con->ucsi->dev, "con%d: failed to register partner\n", - con->num); - return -ENODEV; + partner = typec_register_partner(con->port, &desc); + if (IS_ERR(partner)) { + dev_err(con->ucsi->dev, + "con%d: failed to register partner (%ld)\n", con->num, + PTR_ERR(partner)); + return PTR_ERR(partner); } + con->partner = partner; + return 0; } static void ucsi_unregister_partner(struct ucsi_connector *con) { + if (!con->partner) + return; + typec_unregister_partner(con->partner); con->partner = NULL; } @@ -585,11 +592,18 @@ static int ucsi_register_port(struct ucsi *ucsi, int index) return ret; if (con->cap.op_mode & UCSI_CONCAP_OPMODE_DRP) - cap->type = TYPEC_PORT_DRP; + cap->data = TYPEC_PORT_DRD; else if (con->cap.op_mode & UCSI_CONCAP_OPMODE_DFP) - cap->type = TYPEC_PORT_DFP; + cap->data = TYPEC_PORT_DFP; else if (con->cap.op_mode & UCSI_CONCAP_OPMODE_UFP) - cap->type = TYPEC_PORT_UFP; + cap->data = TYPEC_PORT_UFP; + + if (con->cap.provider && con->cap.consumer) + cap->type = TYPEC_PORT_DRP; + else if (con->cap.provider) + cap->type = TYPEC_PORT_SRC; + else if (con->cap.consumer) + cap->type = TYPEC_PORT_SNK; cap->revision = ucsi->cap.typec_version; cap->pd_revision = ucsi->cap.pd_version; @@ -606,8 +620,8 @@ static int ucsi_register_port(struct ucsi *ucsi, int index) /* Register the connector */ con->port = typec_register_port(ucsi->dev, cap); - if (!con->port) - return -ENODEV; + if (IS_ERR(con->port)) + return PTR_ERR(con->port); /* Get the status */ UCSI_CMD_GET_CONNECTOR_STATUS(ctrl, con->num); diff --git a/drivers/usb/usb-skeleton.c b/drivers/usb/usb-skeleton.c index 26ca0ec01fd5..c3ddd0f1f449 100644 --- a/drivers/usb/usb-skeleton.c +++ b/drivers/usb/usb-skeleton.c @@ -640,4 +640,4 @@ static struct usb_driver skel_driver = { module_usb_driver(skel_driver); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/usbip/Kconfig b/drivers/usb/usbip/Kconfig index eeefa29f8aa2..a20b65cb6678 100644 --- a/drivers/usb/usbip/Kconfig +++ b/drivers/usb/usbip/Kconfig @@ -27,7 +27,7 @@ config USBIP_VHCI_HCD config USBIP_VHCI_HC_PORTS int "Number of ports per USB/IP virtual host controller" - range 1 31 + range 1 15 default 8 depends on USBIP_VHCI_HCD ---help--- diff --git a/drivers/usb/wusbcore/crypto.c b/drivers/usb/wusbcore/crypto.c index 4c00be2d1993..aff50eb09ca9 100644 --- a/drivers/usb/wusbcore/crypto.c +++ b/drivers/usb/wusbcore/crypto.c @@ -202,7 +202,7 @@ static int wusb_ccm_mac(struct crypto_skcipher *tfm_cbc, struct scatterlist sg[4], sg_dst; void *dst_buf; size_t dst_size; - u8 iv[crypto_skcipher_ivsize(tfm_cbc)]; + u8 *iv; size_t zero_padding; /* @@ -224,7 +224,9 @@ static int wusb_ccm_mac(struct crypto_skcipher *tfm_cbc, if (!dst_buf) goto error_dst_buf; - memset(iv, 0, sizeof(iv)); + iv = kzalloc(crypto_skcipher_ivsize(tfm_cbc), GFP_KERNEL); + if (!iv) + goto error_iv; /* Setup B0 */ scratch->b0.flags = 0x59; /* Format B0 */ @@ -276,6 +278,8 @@ static int wusb_ccm_mac(struct crypto_skcipher *tfm_cbc, bytewise_xor(mic, &scratch->ax, iv, 8); result = 8; error_cbc_crypt: + kfree(iv); +error_iv: kfree(dst_buf); error_dst_buf: return result; diff --git a/drivers/usb/wusbcore/wa-nep.c b/drivers/usb/wusbcore/wa-nep.c index 9fdcb6b84abf..5f0656db5482 100644 --- a/drivers/usb/wusbcore/wa-nep.c +++ b/drivers/usb/wusbcore/wa-nep.c @@ -93,7 +93,6 @@ static void wa_notif_dispatch(struct work_struct *ws) goto out; /* screw it */ #endif atomic_dec(&wa->notifs_queued); /* Throttling ctl */ - dev = &wa->usb_iface->dev; size = nw->size; itr = nw->data; diff --git a/drivers/uwb/uwb-debug.c b/drivers/uwb/uwb-debug.c index 991374b13571..f1622bae13be 100644 --- a/drivers/uwb/uwb-debug.c +++ b/drivers/uwb/uwb-debug.c @@ -206,7 +206,7 @@ static const struct file_operations command_fops = { .owner = THIS_MODULE, }; -static int reservations_print(struct seq_file *s, void *p) +static int reservations_show(struct seq_file *s, void *p) { struct uwb_rc *rc = s->private; struct uwb_rsv *rsv; @@ -240,21 +240,9 @@ static int reservations_print(struct seq_file *s, void *p) return 0; } +DEFINE_SHOW_ATTRIBUTE(reservations); -static int reservations_open(struct inode *inode, struct file *file) -{ - return single_open(file, reservations_print, inode->i_private); -} - -static const struct file_operations reservations_fops = { - .open = reservations_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .owner = THIS_MODULE, -}; - -static int drp_avail_print(struct seq_file *s, void *p) +static int drp_avail_show(struct seq_file *s, void *p) { struct uwb_rc *rc = s->private; @@ -264,19 +252,7 @@ static int drp_avail_print(struct seq_file *s, void *p) return 0; } - -static int drp_avail_open(struct inode *inode, struct file *file) -{ - return single_open(file, drp_avail_print, inode->i_private); -} - -static const struct file_operations drp_avail_fops = { - .open = drp_avail_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .owner = THIS_MODULE, -}; +DEFINE_SHOW_ATTRIBUTE(drp_avail); static void uwb_dbg_channel_changed(struct uwb_pal *pal, int channel) { diff --git a/include/linux/device.h b/include/linux/device.h index abf952c82c6d..4b5a4925b780 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -730,6 +730,28 @@ struct device_dma_parameters { }; /** + * struct device_connection - Device Connection Descriptor + * @endpoint: The names of the two devices connected together + * @id: Unique identifier for the connection + * @list: List head, private, for internal use only + */ +struct device_connection { + const char *endpoint[2]; + const char *id; + struct list_head list; +}; + +void *device_connection_find_match(struct device *dev, const char *con_id, + void *data, + void *(*match)(struct device_connection *con, + int ep, void *data)); + +struct device *device_connection_find(struct device *dev, const char *con_id); + +void device_connection_add(struct device_connection *con); +void device_connection_remove(struct device_connection *con); + +/** * enum device_link_state - Device link states. * @DL_STATE_NONE: The presence of the drivers is not being tracked. * @DL_STATE_DORMANT: None of the supplier/consumer drivers is present. diff --git a/include/linux/phy/phy.h b/include/linux/phy/phy.h index 4f8423a948d5..c9d14eeee7f5 100644 --- a/include/linux/phy/phy.h +++ b/include/linux/phy/phy.h @@ -25,7 +25,15 @@ struct phy; enum phy_mode { PHY_MODE_INVALID, PHY_MODE_USB_HOST, + PHY_MODE_USB_HOST_LS, + PHY_MODE_USB_HOST_FS, + PHY_MODE_USB_HOST_HS, + PHY_MODE_USB_HOST_SS, PHY_MODE_USB_DEVICE, + PHY_MODE_USB_DEVICE_LS, + PHY_MODE_USB_DEVICE_FS, + PHY_MODE_USB_DEVICE_HS, + PHY_MODE_USB_DEVICE_SS, PHY_MODE_USB_OTG, PHY_MODE_SGMII, PHY_MODE_10GKR, @@ -61,6 +69,7 @@ struct phy_ops { */ struct phy_attrs { u32 bus_width; + enum phy_mode mode; }; /** @@ -72,7 +81,8 @@ struct phy_attrs { * @mutex: mutex to protect phy_ops * @init_count: used to protect when the PHY is used by multiple consumers * @power_count: used to protect when the PHY is used by multiple consumers - * @phy_attrs: used to specify PHY specific attributes + * @attrs: used to specify PHY specific attributes + * @pwr: power regulator associated with the phy */ struct phy { struct device dev; @@ -88,9 +98,10 @@ struct phy { /** * struct phy_provider - represents the phy provider * @dev: phy provider device + * @children: can be used to override the default (dev->of_node) child node * @owner: the module owner having of_xlate - * @of_xlate: function pointer to obtain phy instance from phy pointer * @list: to maintain a linked list of PHY providers + * @of_xlate: function pointer to obtain phy instance from phy pointer */ struct phy_provider { struct device *dev; @@ -101,6 +112,13 @@ struct phy_provider { struct of_phandle_args *args); }; +/** + * struct phy_lookup - PHY association in list of phys managed by the phy driver + * @node: list node + * @dev_id: the device of the association + * @con_id: connection ID string on device + * @phy: the phy of the association + */ struct phy_lookup { struct list_head node; const char *dev_id; @@ -144,6 +162,10 @@ int phy_exit(struct phy *phy); int phy_power_on(struct phy *phy); int phy_power_off(struct phy *phy); int phy_set_mode(struct phy *phy, enum phy_mode mode); +static inline enum phy_mode phy_get_mode(struct phy *phy) +{ + return phy->attrs.mode; +} int phy_reset(struct phy *phy); int phy_calibrate(struct phy *phy); static inline int phy_get_bus_width(struct phy *phy) @@ -260,6 +282,11 @@ static inline int phy_set_mode(struct phy *phy, enum phy_mode mode) return -ENOSYS; } +static inline enum phy_mode phy_get_mode(struct phy *phy) +{ + return PHY_MODE_INVALID; +} + static inline int phy_reset(struct phy *phy) { if (!phy) diff --git a/include/linux/usb/composite.h b/include/linux/usb/composite.h index cef0e44601f8..4b6b9283fa7b 100644 --- a/include/linux/usb/composite.h +++ b/include/linux/usb/composite.h @@ -54,6 +54,9 @@ /* big enough to hold our biggest descriptor */ #define USB_COMP_EP0_BUFSIZ 1024 +/* OS feature descriptor length <= 4kB */ +#define USB_COMP_EP0_OS_DESC_BUFSIZ 4096 + #define USB_MS_TO_HS_INTERVAL(x) (ilog2((x * 1000 / 125)) + 1) struct usb_configuration; diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index 66a5cff7ee14..e3424234b23a 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -129,6 +129,7 @@ struct usb_ep_ops { int (*enable) (struct usb_ep *ep, const struct usb_endpoint_descriptor *desc); int (*disable) (struct usb_ep *ep); + void (*dispose) (struct usb_ep *ep); struct usb_request *(*alloc_request) (struct usb_ep *ep, gfp_t gfp_flags); diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index 176900528822..aef50cb2ed1b 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -103,7 +103,7 @@ struct usb_hcd { * other external phys should be software-transparent */ struct usb_phy *usb_phy; - struct phy *phy; + struct usb_phy_roothub *phy_roothub; /* Flags that need to be manipulated atomically because they can * change while the host controller is running. Always use @@ -151,6 +151,12 @@ struct usb_hcd { unsigned msix_enabled:1; /* driver has MSI-X enabled? */ unsigned msi_enabled:1; /* driver has MSI enabled? */ unsigned remove_phy:1; /* auto-remove USB phy */ + /* + * do not manage the PHY state in the HCD core, instead let the driver + * handle this (for example if the PHY can only be turned on after a + * specific event) + */ + unsigned skip_phy_initialization:1; /* The next flag is a stopgap, to be removed when all the HCDs * support the new root-hub polling mechanism. */ diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h index b3d41d7409b3..ff359bdfdc7b 100644 --- a/include/linux/usb/pd.h +++ b/include/linux/usb/pd.h @@ -35,6 +35,13 @@ enum pd_ctrl_msg_type { PD_CTRL_WAIT = 12, PD_CTRL_SOFT_RESET = 13, /* 14-15 Reserved */ + PD_CTRL_NOT_SUPP = 16, + PD_CTRL_GET_SOURCE_CAP_EXT = 17, + PD_CTRL_GET_STATUS = 18, + PD_CTRL_FR_SWAP = 19, + PD_CTRL_GET_PPS_STATUS = 20, + PD_CTRL_GET_COUNTRY_CODES = 21, + /* 22-31 Reserved */ }; enum pd_data_msg_type { @@ -43,13 +50,39 @@ enum pd_data_msg_type { PD_DATA_REQUEST = 2, PD_DATA_BIST = 3, PD_DATA_SINK_CAP = 4, - /* 5-14 Reserved */ + PD_DATA_BATT_STATUS = 5, + PD_DATA_ALERT = 6, + PD_DATA_GET_COUNTRY_INFO = 7, + /* 8-14 Reserved */ PD_DATA_VENDOR_DEF = 15, + /* 16-31 Reserved */ +}; + +enum pd_ext_msg_type { + /* 0 Reserved */ + PD_EXT_SOURCE_CAP_EXT = 1, + PD_EXT_STATUS = 2, + PD_EXT_GET_BATT_CAP = 3, + PD_EXT_GET_BATT_STATUS = 4, + PD_EXT_BATT_CAP = 5, + PD_EXT_GET_MANUFACTURER_INFO = 6, + PD_EXT_MANUFACTURER_INFO = 7, + PD_EXT_SECURITY_REQUEST = 8, + PD_EXT_SECURITY_RESPONSE = 9, + PD_EXT_FW_UPDATE_REQUEST = 10, + PD_EXT_FW_UPDATE_RESPONSE = 11, + PD_EXT_PPS_STATUS = 12, + PD_EXT_COUNTRY_INFO = 13, + PD_EXT_COUNTRY_CODES = 14, + /* 15-31 Reserved */ }; #define PD_REV10 0x0 #define PD_REV20 0x1 +#define PD_REV30 0x2 +#define PD_MAX_REV PD_REV30 +#define PD_HEADER_EXT_HDR BIT(15) #define PD_HEADER_CNT_SHIFT 12 #define PD_HEADER_CNT_MASK 0x7 #define PD_HEADER_ID_SHIFT 9 @@ -59,18 +92,19 @@ enum pd_data_msg_type { #define PD_HEADER_REV_MASK 0x3 #define PD_HEADER_DATA_ROLE BIT(5) #define PD_HEADER_TYPE_SHIFT 0 -#define PD_HEADER_TYPE_MASK 0xf +#define PD_HEADER_TYPE_MASK 0x1f -#define PD_HEADER(type, pwr, data, id, cnt) \ +#define PD_HEADER(type, pwr, data, rev, id, cnt, ext_hdr) \ ((((type) & PD_HEADER_TYPE_MASK) << PD_HEADER_TYPE_SHIFT) | \ ((pwr) == TYPEC_SOURCE ? PD_HEADER_PWR_ROLE : 0) | \ ((data) == TYPEC_HOST ? PD_HEADER_DATA_ROLE : 0) | \ - (PD_REV20 << PD_HEADER_REV_SHIFT) | \ + (rev << PD_HEADER_REV_SHIFT) | \ (((id) & PD_HEADER_ID_MASK) << PD_HEADER_ID_SHIFT) | \ - (((cnt) & PD_HEADER_CNT_MASK) << PD_HEADER_CNT_SHIFT)) + (((cnt) & PD_HEADER_CNT_MASK) << PD_HEADER_CNT_SHIFT) | \ + ((ext_hdr) ? PD_HEADER_EXT_HDR : 0)) #define PD_HEADER_LE(type, pwr, data, id, cnt) \ - cpu_to_le16(PD_HEADER((type), (pwr), (data), (id), (cnt))) + cpu_to_le16(PD_HEADER((type), (pwr), (data), PD_REV20, (id), (cnt), (0))) static inline unsigned int pd_header_cnt(u16 header) { @@ -102,16 +136,75 @@ static inline unsigned int pd_header_msgid_le(__le16 header) return pd_header_msgid(le16_to_cpu(header)); } +static inline unsigned int pd_header_rev(u16 header) +{ + return (header >> PD_HEADER_REV_SHIFT) & PD_HEADER_REV_MASK; +} + +static inline unsigned int pd_header_rev_le(__le16 header) +{ + return pd_header_rev(le16_to_cpu(header)); +} + +#define PD_EXT_HDR_CHUNKED BIT(15) +#define PD_EXT_HDR_CHUNK_NUM_SHIFT 11 +#define PD_EXT_HDR_CHUNK_NUM_MASK 0xf +#define PD_EXT_HDR_REQ_CHUNK BIT(10) +#define PD_EXT_HDR_DATA_SIZE_SHIFT 0 +#define PD_EXT_HDR_DATA_SIZE_MASK 0x1ff + +#define PD_EXT_HDR(data_size, req_chunk, chunk_num, chunked) \ + ((((data_size) & PD_EXT_HDR_DATA_SIZE_MASK) << PD_EXT_HDR_DATA_SIZE_SHIFT) | \ + ((req_chunk) ? PD_EXT_HDR_REQ_CHUNK : 0) | \ + (((chunk_num) & PD_EXT_HDR_CHUNK_NUM_MASK) << PD_EXT_HDR_CHUNK_NUM_SHIFT) | \ + ((chunked) ? PD_EXT_HDR_CHUNKED : 0)) + +#define PD_EXT_HDR_LE(data_size, req_chunk, chunk_num, chunked) \ + cpu_to_le16(PD_EXT_HDR((data_size), (req_chunk), (chunk_num), (chunked))) + +static inline unsigned int pd_ext_header_chunk_num(u16 ext_header) +{ + return (ext_header >> PD_EXT_HDR_CHUNK_NUM_SHIFT) & + PD_EXT_HDR_CHUNK_NUM_MASK; +} + +static inline unsigned int pd_ext_header_data_size(u16 ext_header) +{ + return (ext_header >> PD_EXT_HDR_DATA_SIZE_SHIFT) & + PD_EXT_HDR_DATA_SIZE_MASK; +} + +static inline unsigned int pd_ext_header_data_size_le(__le16 ext_header) +{ + return pd_ext_header_data_size(le16_to_cpu(ext_header)); +} + #define PD_MAX_PAYLOAD 7 +#define PD_EXT_MAX_CHUNK_DATA 26 /** - * struct pd_message - PD message as seen on wire - * @header: PD message header - * @payload: PD message payload - */ + * struct pd_chunked_ext_message_data - PD chunked extended message data as + * seen on wire + * @header: PD extended message header + * @data: PD extended message data + */ +struct pd_chunked_ext_message_data { + __le16 header; + u8 data[PD_EXT_MAX_CHUNK_DATA]; +} __packed; + +/** + * struct pd_message - PD message as seen on wire + * @header: PD message header + * @payload: PD message payload + * @ext_msg: PD message chunked extended message data + */ struct pd_message { __le16 header; - __le32 payload[PD_MAX_PAYLOAD]; + union { + __le32 payload[PD_MAX_PAYLOAD]; + struct pd_chunked_ext_message_data ext_msg; + }; } __packed; /* PDO: Power Data Object */ @@ -121,6 +214,7 @@ enum pd_pdo_type { PDO_TYPE_FIXED = 0, PDO_TYPE_BATT = 1, PDO_TYPE_VAR = 2, + PDO_TYPE_APDO = 3, }; #define PDO_TYPE_SHIFT 30 @@ -174,6 +268,34 @@ enum pd_pdo_type { (PDO_TYPE(PDO_TYPE_VAR) | PDO_VAR_MIN_VOLT(min_mv) | \ PDO_VAR_MAX_VOLT(max_mv) | PDO_VAR_MAX_CURR(max_ma)) +enum pd_apdo_type { + APDO_TYPE_PPS = 0, +}; + +#define PDO_APDO_TYPE_SHIFT 28 /* Only valid value currently is 0x0 - PPS */ +#define PDO_APDO_TYPE_MASK 0x3 + +#define PDO_APDO_TYPE(t) ((t) << PDO_APDO_TYPE_SHIFT) + +#define PDO_PPS_APDO_MAX_VOLT_SHIFT 17 /* 100mV units */ +#define PDO_PPS_APDO_MIN_VOLT_SHIFT 8 /* 100mV units */ +#define PDO_PPS_APDO_MAX_CURR_SHIFT 0 /* 50mA units */ + +#define PDO_PPS_APDO_VOLT_MASK 0xff +#define PDO_PPS_APDO_CURR_MASK 0x7f + +#define PDO_PPS_APDO_MIN_VOLT(mv) \ + ((((mv) / 100) & PDO_PPS_APDO_VOLT_MASK) << PDO_PPS_APDO_MIN_VOLT_SHIFT) +#define PDO_PPS_APDO_MAX_VOLT(mv) \ + ((((mv) / 100) & PDO_PPS_APDO_VOLT_MASK) << PDO_PPS_APDO_MAX_VOLT_SHIFT) +#define PDO_PPS_APDO_MAX_CURR(ma) \ + ((((ma) / 50) & PDO_PPS_APDO_CURR_MASK) << PDO_PPS_APDO_MAX_CURR_SHIFT) + +#define PDO_PPS_APDO(min_mv, max_mv, max_ma) \ + (PDO_TYPE(PDO_TYPE_APDO) | PDO_APDO_TYPE(APDO_TYPE_PPS) | \ + PDO_PPS_APDO_MIN_VOLT(min_mv) | PDO_PPS_APDO_MAX_VOLT(max_mv) | \ + PDO_PPS_APDO_MAX_CURR(max_ma)) + static inline enum pd_pdo_type pdo_type(u32 pdo) { return (pdo >> PDO_TYPE_SHIFT) & PDO_TYPE_MASK; @@ -204,6 +326,29 @@ static inline unsigned int pdo_max_power(u32 pdo) return ((pdo >> PDO_BATT_MAX_PWR_SHIFT) & PDO_PWR_MASK) * 250; } +static inline enum pd_apdo_type pdo_apdo_type(u32 pdo) +{ + return (pdo >> PDO_APDO_TYPE_SHIFT) & PDO_APDO_TYPE_MASK; +} + +static inline unsigned int pdo_pps_apdo_min_voltage(u32 pdo) +{ + return ((pdo >> PDO_PPS_APDO_MIN_VOLT_SHIFT) & + PDO_PPS_APDO_VOLT_MASK) * 100; +} + +static inline unsigned int pdo_pps_apdo_max_voltage(u32 pdo) +{ + return ((pdo >> PDO_PPS_APDO_MAX_VOLT_SHIFT) & + PDO_PPS_APDO_VOLT_MASK) * 100; +} + +static inline unsigned int pdo_pps_apdo_max_current(u32 pdo) +{ + return ((pdo >> PDO_PPS_APDO_MAX_CURR_SHIFT) & + PDO_PPS_APDO_CURR_MASK) * 50; +} + /* RDO: Request Data Object */ #define RDO_OBJ_POS_SHIFT 28 #define RDO_OBJ_POS_MASK 0x7 @@ -237,6 +382,24 @@ static inline unsigned int pdo_max_power(u32 pdo) (RDO_OBJ(idx) | (flags) | \ RDO_BATT_OP_PWR(op_mw) | RDO_BATT_MAX_PWR(max_mw)) +#define RDO_PROG_VOLT_MASK 0x7ff +#define RDO_PROG_CURR_MASK 0x7f + +#define RDO_PROG_VOLT_SHIFT 9 +#define RDO_PROG_CURR_SHIFT 0 + +#define RDO_PROG_VOLT_MV_STEP 20 +#define RDO_PROG_CURR_MA_STEP 50 + +#define PDO_PROG_OUT_VOLT(mv) \ + ((((mv) / RDO_PROG_VOLT_MV_STEP) & RDO_PROG_VOLT_MASK) << RDO_PROG_VOLT_SHIFT) +#define PDO_PROG_OP_CURR(ma) \ + ((((ma) / RDO_PROG_CURR_MA_STEP) & RDO_PROG_CURR_MASK) << RDO_PROG_CURR_SHIFT) + +#define RDO_PROG(idx, out_mv, op_ma, flags) \ + (RDO_OBJ(idx) | (flags) | \ + PDO_PROG_OUT_VOLT(out_mv) | PDO_PROG_OP_CURR(op_ma)) + static inline unsigned int rdo_index(u32 rdo) { return (rdo >> RDO_OBJ_POS_SHIFT) & RDO_OBJ_POS_MASK; diff --git a/include/linux/usb/pd_ado.h b/include/linux/usb/pd_ado.h new file mode 100644 index 000000000000..9aa1cf31c93c --- /dev/null +++ b/include/linux/usb/pd_ado.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2017 Dialog Semiconductor + * + * Author: Adam Thomson <Adam.Thomson.Opensource@diasemi.com> + */ + +#ifndef __LINUX_USB_PD_ADO_H +#define __LINUX_USB_PD_ADO_H + +/* ADO : Alert Data Object */ +#define USB_PD_ADO_TYPE_SHIFT 24 +#define USB_PD_ADO_TYPE_MASK 0xff +#define USB_PD_ADO_FIXED_BATT_SHIFT 20 +#define USB_PD_ADO_FIXED_BATT_MASK 0xf +#define USB_PD_ADO_HOT_SWAP_BATT_SHIFT 16 +#define USB_PD_ADO_HOT_SWAP_BATT_MASK 0xf + +#define USB_PD_ADO_TYPE_BATT_STATUS_CHANGE BIT(1) +#define USB_PD_ADO_TYPE_OCP BIT(2) +#define USB_PD_ADO_TYPE_OTP BIT(3) +#define USB_PD_ADO_TYPE_OP_COND_CHANGE BIT(4) +#define USB_PD_ADO_TYPE_SRC_INPUT_CHANGE BIT(5) +#define USB_PD_ADO_TYPE_OVP BIT(6) + +static inline unsigned int usb_pd_ado_type(u32 ado) +{ + return (ado >> USB_PD_ADO_TYPE_SHIFT) & USB_PD_ADO_TYPE_MASK; +} + +static inline unsigned int usb_pd_ado_fixed_batt(u32 ado) +{ + return (ado >> USB_PD_ADO_FIXED_BATT_SHIFT) & + USB_PD_ADO_FIXED_BATT_MASK; +} + +static inline unsigned int usb_pd_ado_hot_swap_batt(u32 ado) +{ + return (ado >> USB_PD_ADO_HOT_SWAP_BATT_SHIFT) & + USB_PD_ADO_HOT_SWAP_BATT_MASK; +} +#endif /* __LINUX_USB_PD_ADO_H */ diff --git a/include/linux/usb/pd_ext_sdb.h b/include/linux/usb/pd_ext_sdb.h new file mode 100644 index 000000000000..0eb83ce19597 --- /dev/null +++ b/include/linux/usb/pd_ext_sdb.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2017 Dialog Semiconductor + * + * Author: Adam Thomson <Adam.Thomson.Opensource@diasemi.com> + */ + +#ifndef __LINUX_USB_PD_EXT_SDB_H +#define __LINUX_USB_PD_EXT_SDB_H + +/* SDB : Status Data Block */ +enum usb_pd_ext_sdb_fields { + USB_PD_EXT_SDB_INTERNAL_TEMP = 0, + USB_PD_EXT_SDB_PRESENT_INPUT, + USB_PD_EXT_SDB_PRESENT_BATT_INPUT, + USB_PD_EXT_SDB_EVENT_FLAGS, + USB_PD_EXT_SDB_TEMP_STATUS, + USB_PD_EXT_SDB_DATA_SIZE, +}; + +/* Event Flags */ +#define USB_PD_EXT_SDB_EVENT_OCP BIT(1) +#define USB_PD_EXT_SDB_EVENT_OTP BIT(2) +#define USB_PD_EXT_SDB_EVENT_OVP BIT(3) +#define USB_PD_EXT_SDB_EVENT_CF_CV_MODE BIT(4) + +#define USB_PD_EXT_SDB_PPS_EVENTS (USB_PD_EXT_SDB_EVENT_OCP | \ + USB_PD_EXT_SDB_EVENT_OTP | \ + USB_PD_EXT_SDB_EVENT_OVP) + +#endif /* __LINUX_USB_PD_EXT_SDB_H */ diff --git a/include/linux/usb/role.h b/include/linux/usb/role.h new file mode 100644 index 000000000000..edc51be4a77c --- /dev/null +++ b/include/linux/usb/role.h @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0 + +#ifndef __LINUX_USB_ROLE_H +#define __LINUX_USB_ROLE_H + +#include <linux/device.h> + +struct usb_role_switch; + +enum usb_role { + USB_ROLE_NONE, + USB_ROLE_HOST, + USB_ROLE_DEVICE, +}; + +typedef int (*usb_role_switch_set_t)(struct device *dev, enum usb_role role); +typedef enum usb_role (*usb_role_switch_get_t)(struct device *dev); + +/** + * struct usb_role_switch_desc - USB Role Switch Descriptor + * @usb2_port: Optional reference to the host controller port device (USB2) + * @usb3_port: Optional reference to the host controller port device (USB3) + * @udc: Optional reference to the peripheral controller device + * @set: Callback for setting the role + * @get: Callback for getting the role (optional) + * @allow_userspace_control: If true userspace may change the role through sysfs + * + * @usb2_port and @usb3_port will point to the USB host port and @udc to the USB + * device controller behind the USB connector with the role switch. If + * @usb2_port, @usb3_port and @udc are included in the description, the + * reference count for them should be incremented by the caller of + * usb_role_switch_register() before registering the switch. + */ +struct usb_role_switch_desc { + struct device *usb2_port; + struct device *usb3_port; + struct device *udc; + usb_role_switch_set_t set; + usb_role_switch_get_t get; + bool allow_userspace_control; +}; + +int usb_role_switch_set_role(struct usb_role_switch *sw, enum usb_role role); +enum usb_role usb_role_switch_get_role(struct usb_role_switch *sw); +struct usb_role_switch *usb_role_switch_get(struct device *dev); +void usb_role_switch_put(struct usb_role_switch *sw); + +struct usb_role_switch * +usb_role_switch_register(struct device *parent, + const struct usb_role_switch_desc *desc); +void usb_role_switch_unregister(struct usb_role_switch *sw); + +#endif /* __LINUX_USB_ROLE_H */ diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h index ca1c0b57f03f..f0d839daeaea 100644 --- a/include/linux/usb/tcpm.h +++ b/include/linux/usb/tcpm.h @@ -91,17 +91,13 @@ struct tcpc_config { unsigned int operating_snk_mw; enum typec_port_type type; + enum typec_port_data data; enum typec_role default_role; bool try_role_hw; /* try.{src,snk} implemented in hardware */ const struct typec_altmode_desc *alt_modes; }; -enum tcpc_usb_switch { - TCPC_USB_SWITCH_CONNECT, - TCPC_USB_SWITCH_DISCONNECT, -}; - /* Mux state attributes */ #define TCPC_MUX_USB_ENABLED BIT(0) /* USB enabled */ #define TCPC_MUX_DP_ENABLED BIT(1) /* DP enabled */ @@ -116,14 +112,6 @@ enum tcpc_mux_mode { TCPC_MUX_DP_ENABLED, }; -struct tcpc_mux_dev { - int (*set)(struct tcpc_mux_dev *dev, enum tcpc_mux_mode mux_mode, - enum tcpc_usb_switch usb_config, - enum typec_cc_polarity polarity); - bool dfp_only; - void *priv_data; -}; - /** * struct tcpc_dev - Port configuration and callback functions * @config: Pointer to port configuration @@ -175,7 +163,6 @@ struct tcpc_dev { int (*try_role)(struct tcpc_dev *dev, int role); int (*pd_transmit)(struct tcpc_dev *dev, enum tcpm_transmit_type type, const struct pd_message *msg); - struct tcpc_mux_dev *mux; }; struct tcpm_port; diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index 0d44ce6af08f..672b39bb0adc 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -22,9 +22,15 @@ struct typec_port; struct fwnode_handle; enum typec_port_type { + TYPEC_PORT_SRC, + TYPEC_PORT_SNK, + TYPEC_PORT_DRP, +}; + +enum typec_port_data { TYPEC_PORT_DFP, TYPEC_PORT_UFP, - TYPEC_PORT_DRP, + TYPEC_PORT_DRD, }; enum typec_plug_type { @@ -60,6 +66,12 @@ enum typec_accessory { #define TYPEC_MAX_ACCESSORY 3 +enum typec_orientation { + TYPEC_ORIENTATION_NONE, + TYPEC_ORIENTATION_NORMAL, + TYPEC_ORIENTATION_REVERSE, +}; + /* * struct usb_pd_identity - USB Power Delivery identity data * @id_header: ID Header VDO @@ -180,11 +192,14 @@ struct typec_partner_desc { /* * struct typec_capability - USB Type-C Port Capabilities - * @role: DFP (Host-only), UFP (Device-only) or DRP (Dual Role) + * @type: Supported power role of the port + * @data: Supported data role of the port * @revision: USB Type-C Specification release. Binary coded decimal * @pd_revision: USB Power Delivery Specification revision if supported - * @prefer_role: Initial role preference + * @prefer_role: Initial role preference (DRP ports). * @accessory: Supported Accessory Modes + * @sw: Cable plug orientation switch + * @mux: Multiplexer switch for Alternate/Accessory Modes * @fwnode: Optional fwnode of the port * @try_role: Set data role preference for DRP port * @dr_set: Set Data Role @@ -197,11 +212,14 @@ struct typec_partner_desc { */ struct typec_capability { enum typec_port_type type; + enum typec_port_data data; u16 revision; /* 0120H = "1.2" */ u16 pd_revision; /* 0300H = "3.0" */ int prefer_role; enum typec_accessory accessory[TYPEC_MAX_ACCESSORY]; + struct typec_switch *sw; + struct typec_mux *mux; struct fwnode_handle *fwnode; int (*try_role)(const struct typec_capability *, @@ -245,4 +263,8 @@ void typec_set_pwr_role(struct typec_port *port, enum typec_role role); void typec_set_vconn_role(struct typec_port *port, enum typec_role role); void typec_set_pwr_opmode(struct typec_port *port, enum typec_pwr_opmode mode); +int typec_set_orientation(struct typec_port *port, + enum typec_orientation orientation); +int typec_set_mode(struct typec_port *port, int mode); + #endif /* __LINUX_USB_TYPEC_H */ diff --git a/include/linux/usb/typec_mux.h b/include/linux/usb/typec_mux.h new file mode 100644 index 000000000000..12c1b057834b --- /dev/null +++ b/include/linux/usb/typec_mux.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0 + +#ifndef __USB_TYPEC_MUX +#define __USB_TYPEC_MUX + +#include <linux/list.h> +#include <linux/usb/typec.h> + +struct device; + +/** + * struct typec_switch - USB Type-C cable orientation switch + * @dev: Switch device + * @entry: List entry + * @set: Callback to the driver for setting the orientation + * + * USB Type-C pin flipper switch routing the correct data pairs from the + * connector to the USB controller depending on the orientation of the cable + * plug. + */ +struct typec_switch { + struct device *dev; + struct list_head entry; + + int (*set)(struct typec_switch *sw, enum typec_orientation orientation); +}; + +/** + * struct typec_switch - USB Type-C connector pin mux + * @dev: Mux device + * @entry: List entry + * @set: Callback to the driver for setting the state of the mux + * + * Pin Multiplexer/DeMultiplexer switch routing the USB Type-C connector pins to + * different components depending on the requested mode of operation. Used with + * Accessory/Alternate modes. + */ +struct typec_mux { + struct device *dev; + struct list_head entry; + + int (*set)(struct typec_mux *mux, int state); +}; + +struct typec_switch *typec_switch_get(struct device *dev); +void typec_switch_put(struct typec_switch *sw); +int typec_switch_register(struct typec_switch *sw); +void typec_switch_unregister(struct typec_switch *sw); + +struct typec_mux *typec_mux_get(struct device *dev); +void typec_mux_put(struct typec_mux *mux); +int typec_mux_register(struct typec_mux *mux); +void typec_mux_unregister(struct typec_mux *mux); + +#endif /* __USB_TYPEC_MUX */ diff --git a/tools/usb/usbip/libsrc/usbip_common.c b/tools/usb/usbip/libsrc/usbip_common.c index 001bb8e8f668..bb424638d75b 100644 --- a/tools/usb/usbip/libsrc/usbip_common.c +++ b/tools/usb/usbip/libsrc/usbip_common.c @@ -66,6 +66,29 @@ const char *usbip_speed_string(int num) return "Unknown Speed"; } +struct op_common_status_string { + int num; + char *desc; +}; + +static struct op_common_status_string op_common_status_strings[] = { + { ST_OK, "Request Completed Successfully" }, + { ST_NA, "Request Failed" }, + { ST_DEV_BUSY, "Device busy (exported)" }, + { ST_DEV_ERR, "Device in error state" }, + { ST_NODEV, "Device not found" }, + { ST_ERROR, "Unexpected response" }, + { 0, NULL} +}; + +const char *usbip_op_common_status_string(int status) +{ + for (int i = 0; op_common_status_strings[i].desc != NULL; i++) + if (op_common_status_strings[i].num == status) + return op_common_status_strings[i].desc; + + return "Unknown Op Common Status"; +} #define DBG_UDEV_INTEGER(name)\ dbg("%-20s = %x", to_string(name), (int) udev->name) diff --git a/tools/usb/usbip/libsrc/usbip_common.h b/tools/usb/usbip/libsrc/usbip_common.h index e45ec9d2fdbc..73a367a7fa10 100644 --- a/tools/usb/usbip/libsrc/usbip_common.h +++ b/tools/usb/usbip/libsrc/usbip_common.h @@ -43,6 +43,16 @@ #define SYSFS_PATH_MAX 256 #define SYSFS_BUS_ID_SIZE 32 +/* Defines for op_code status in server/client op_common PDUs */ +#define ST_OK 0x00 +#define ST_NA 0x01 + /* Device requested for import is not available */ +#define ST_DEV_BUSY 0x02 + /* Device requested for import is in error state */ +#define ST_DEV_ERR 0x03 +#define ST_NODEV 0x04 +#define ST_ERROR 0x05 + extern int usbip_use_syslog; extern int usbip_use_stderr; extern int usbip_use_debug ; @@ -130,6 +140,7 @@ int read_usb_interface(struct usbip_usb_device *udev, int i, const char *usbip_speed_string(int num); const char *usbip_status_string(int32_t status); +const char *usbip_op_common_status_string(int status); int usbip_names_init(char *); void usbip_names_free(void); diff --git a/tools/usb/usbip/libsrc/usbip_host_common.c b/tools/usb/usbip/libsrc/usbip_host_common.c index 6ff7b601f854..dc93fadbee96 100644 --- a/tools/usb/usbip/libsrc/usbip_host_common.c +++ b/tools/usb/usbip/libsrc/usbip_host_common.c @@ -234,14 +234,17 @@ int usbip_export_device(struct usbip_exported_device *edev, int sockfd) switch (edev->status) { case SDEV_ST_ERROR: dbg("status SDEV_ST_ERROR"); + ret = ST_DEV_ERR; break; case SDEV_ST_USED: dbg("status SDEV_ST_USED"); + ret = ST_DEV_BUSY; break; default: dbg("status unknown: 0x%x", edev->status); + ret = -1; } - return -1; + return ret; } /* only the first interface is true */ diff --git a/tools/usb/usbip/src/usbip_attach.c b/tools/usb/usbip/src/usbip_attach.c index 7f07b2d50f59..ba88728483ff 100644 --- a/tools/usb/usbip/src/usbip_attach.c +++ b/tools/usb/usbip/src/usbip_attach.c @@ -135,6 +135,7 @@ static int query_import_device(int sockfd, char *busid) struct op_import_request request; struct op_import_reply reply; uint16_t code = OP_REP_IMPORT; + int status; memset(&request, 0, sizeof(request)); memset(&reply, 0, sizeof(reply)); @@ -157,9 +158,10 @@ static int query_import_device(int sockfd, char *busid) } /* receive a reply */ - rc = usbip_net_recv_op_common(sockfd, &code); + rc = usbip_net_recv_op_common(sockfd, &code, &status); if (rc < 0) { - err("recv op_common"); + err("Attach Request for %s failed - %s\n", + busid, usbip_op_common_status_string(status)); return -1; } @@ -194,10 +196,8 @@ static int attach_device(char *host, char *busid) } rhport = query_import_device(sockfd, busid); - if (rhport < 0) { - err("query"); + if (rhport < 0) return -1; - } close(sockfd); diff --git a/tools/usb/usbip/src/usbip_list.c b/tools/usb/usbip/src/usbip_list.c index d65a9f444174..8d4ccf4b9480 100644 --- a/tools/usb/usbip/src/usbip_list.c +++ b/tools/usb/usbip/src/usbip_list.c @@ -62,6 +62,7 @@ static int get_exported_devices(char *host, int sockfd) struct usbip_usb_interface uintf; unsigned int i; int rc, j; + int status; rc = usbip_net_send_op_common(sockfd, OP_REQ_DEVLIST, 0); if (rc < 0) { @@ -69,9 +70,10 @@ static int get_exported_devices(char *host, int sockfd) return -1; } - rc = usbip_net_recv_op_common(sockfd, &code); + rc = usbip_net_recv_op_common(sockfd, &code, &status); if (rc < 0) { - dbg("usbip_net_recv_op_common failed"); + err("Exported Device List Request failed - %s\n", + usbip_op_common_status_string(status)); return -1; } diff --git a/tools/usb/usbip/src/usbip_network.c b/tools/usb/usbip/src/usbip_network.c index b4c37e76a6e0..8ffcd47d9638 100644 --- a/tools/usb/usbip/src/usbip_network.c +++ b/tools/usb/usbip/src/usbip_network.c @@ -163,7 +163,7 @@ int usbip_net_send_op_common(int sockfd, uint32_t code, uint32_t status) return 0; } -int usbip_net_recv_op_common(int sockfd, uint16_t *code) +int usbip_net_recv_op_common(int sockfd, uint16_t *code, int *status) { struct op_common op_common; int rc; @@ -179,8 +179,8 @@ int usbip_net_recv_op_common(int sockfd, uint16_t *code) PACK_OP_COMMON(0, &op_common); if (op_common.version != USBIP_VERSION) { - dbg("version mismatch: %d %d", op_common.version, - USBIP_VERSION); + err("USBIP Kernel and tool version mismatch: %d %d:", + op_common.version, USBIP_VERSION); goto err; } @@ -191,10 +191,14 @@ int usbip_net_recv_op_common(int sockfd, uint16_t *code) if (op_common.code != *code) { dbg("unexpected pdu %#0x for %#0x", op_common.code, *code); + /* return error status */ + *status = ST_ERROR; goto err; } } + *status = op_common.status; + if (op_common.status != ST_OK) { dbg("request failed at peer: %d", op_common.status); goto err; diff --git a/tools/usb/usbip/src/usbip_network.h b/tools/usb/usbip/src/usbip_network.h index 7032687621d3..555215eae43e 100644 --- a/tools/usb/usbip/src/usbip_network.h +++ b/tools/usb/usbip/src/usbip_network.h @@ -27,9 +27,7 @@ struct op_common { #define OP_REPLY (0x00 << 8) uint16_t code; - /* add more error code */ -#define ST_OK 0x00 -#define ST_NA 0x01 + /* status codes defined in usbip_common.h */ uint32_t status; /* op_code status (for reply) */ } __attribute__((packed)); @@ -176,7 +174,7 @@ void usbip_net_pack_usb_interface(int pack, struct usbip_usb_interface *uinf); ssize_t usbip_net_recv(int sockfd, void *buff, size_t bufflen); ssize_t usbip_net_send(int sockfd, void *buff, size_t bufflen); int usbip_net_send_op_common(int sockfd, uint32_t code, uint32_t status); -int usbip_net_recv_op_common(int sockfd, uint16_t *code); +int usbip_net_recv_op_common(int sockfd, uint16_t *code, int *status); int usbip_net_set_reuseaddr(int sockfd); int usbip_net_set_nodelay(int sockfd); int usbip_net_set_keepalive(int sockfd); diff --git a/tools/usb/usbip/src/usbipd.c b/tools/usb/usbip/src/usbipd.c index c6dad2a13c80..32864c52942d 100644 --- a/tools/usb/usbip/src/usbipd.c +++ b/tools/usb/usbip/src/usbipd.c @@ -107,7 +107,7 @@ static int recv_request_import(int sockfd) struct usbip_usb_device pdu_udev; struct list_head *i; int found = 0; - int error = 0; + int status = ST_OK; int rc; memset(&req, 0, sizeof(req)); @@ -133,22 +133,21 @@ static int recv_request_import(int sockfd) usbip_net_set_nodelay(sockfd); /* export device needs a TCP/IP socket descriptor */ - rc = usbip_export_device(edev, sockfd); - if (rc < 0) - error = 1; + status = usbip_export_device(edev, sockfd); + if (status < 0) + status = ST_NA; } else { info("requested device not found: %s", req.busid); - error = 1; + status = ST_NODEV; } - rc = usbip_net_send_op_common(sockfd, OP_REP_IMPORT, - (!error ? ST_OK : ST_NA)); + rc = usbip_net_send_op_common(sockfd, OP_REP_IMPORT, status); if (rc < 0) { dbg("usbip_net_send_op_common failed: %#0x", OP_REP_IMPORT); return -1; } - if (error) { + if (status) { dbg("import request busid %s: failed", req.busid); return -1; } @@ -176,10 +175,21 @@ static int send_reply_devlist(int connfd) struct list_head *j; int rc, i; + /* + * Exclude devices that are already exported to a client from + * the exportable device list to avoid: + * - import requests for devices that are exported only to + * fail the request. + * - revealing devices that are imported by a client to + * another client. + */ + reply.ndev = 0; /* number of exported devices */ list_for_each(j, &driver->edev_list) { - reply.ndev += 1; + edev = list_entry(j, struct usbip_exported_device, node); + if (edev->status != SDEV_ST_USED) + reply.ndev += 1; } info("exportable devices: %d", reply.ndev); @@ -198,6 +208,9 @@ static int send_reply_devlist(int connfd) list_for_each(j, &driver->edev_list) { edev = list_entry(j, struct usbip_exported_device, node); + if (edev->status == SDEV_ST_USED) + continue; + dump_usb_device(&edev->udev); memcpy(&pdu_udev, &edev->udev, sizeof(pdu_udev)); usbip_net_pack_usb_device(1, &pdu_udev); @@ -251,8 +264,9 @@ static int recv_pdu(int connfd) { uint16_t code = OP_UNSPEC; int ret; + int status; - ret = usbip_net_recv_op_common(connfd, &code); + ret = usbip_net_recv_op_common(connfd, &code, &status); if (ret < 0) { dbg("could not receive opcode: %#0x", code); return -1; |