diff --git a/Documentation/ABI/testing/configfs-usb-gadget-sourcesink b/Documentation/ABI/testing/configfs-usb-gadget-sourcesink index bc7ff731aa0c..f56335af2d88 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-sourcesink +++ b/Documentation/ABI/testing/configfs-usb-gadget-sourcesink @@ -10,3 +10,5 @@ Description: isoc_mult - 0..2 (hs/ss only) isoc_maxburst - 0..15 (ss only) buflen - buffer length + bulk_qlen - depth of queue for bulk + iso_qlen - depth of queue for iso diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index 3a4abfc44f5e..0bd731cbb50c 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -134,19 +134,21 @@ Description: enabled for the device. Developer can write y/Y/1 or n/N/0 to the file to enable/disable the feature. -What: /sys/bus/usb/devices/.../power/usb3_hardware_lpm -Date: June 2015 +What: /sys/bus/usb/devices/.../power/usb3_hardware_lpm_u1 + /sys/bus/usb/devices/.../power/usb3_hardware_lpm_u2 +Date: November 2015 Contact: Kevin Strasser + Lu Baolu Description: If CONFIG_PM is set and a USB 3.0 lpm-capable device is plugged in to a xHCI host which supports link PM, it will check if U1 and U2 exit latencies have been set in the BOS descriptor; if - the check is is passed and the host supports USB3 hardware LPM, + the check is passed and the host supports USB3 hardware LPM, USB3 hardware LPM will be enabled for the device and the USB - device directory will contain a file named - power/usb3_hardware_lpm. The file holds a string value (enable - or disable) indicating whether or not USB3 hardware LPM is - enabled for the device. + device directory will contain two files named + power/usb3_hardware_lpm_u1 and power/usb3_hardware_lpm_u2. These + files hold a string value (enable or disable) indicating whether + or not USB3 hardware LPM U1 or U2 is enabled for the device. What: /sys/bus/usb/devices/.../removable Date: February 2012 @@ -187,6 +189,17 @@ 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/usb3_lpm_permit +Date: November 2015 +Contact: Lu Baolu +Description: + Some USB3.0 devices are not friendly to USB3 LPM. usb3_lpm_permit + attribute allows enabling/disabling usb3 lpm of a port. It takes + effect both before and after a usb device is enumerated. Supported + values are "0" if both u1 and u2 are NOT permitted, "u1" if only u1 + is permitted, "u2" if only u2 is permitted, "u1_u2" if both u1 and + u2 are permitted. + What: /sys/bus/usb/devices/.../power/usb2_lpm_l1_timeout Date: May 2013 Contact: Mathias Nyman diff --git a/Documentation/devicetree/bindings/phy/brcm,brcmstb-sata-phy.txt b/Documentation/devicetree/bindings/phy/brcm,brcmstb-sata-phy.txt index 7f81ef90146a..d87ab7c127b8 100644 --- a/Documentation/devicetree/bindings/phy/brcm,brcmstb-sata-phy.txt +++ b/Documentation/devicetree/bindings/phy/brcm,brcmstb-sata-phy.txt @@ -2,6 +2,7 @@ Required properties: - compatible: should be one or more of + "brcm,bcm7425-sata-phy" "brcm,bcm7445-sata-phy" "brcm,phy-sata3" - address-cells: should be 1 diff --git a/Documentation/devicetree/bindings/phy/phy-hi6220-usb.txt b/Documentation/devicetree/bindings/phy/phy-hi6220-usb.txt new file mode 100644 index 000000000000..f17a56e2152f --- /dev/null +++ b/Documentation/devicetree/bindings/phy/phy-hi6220-usb.txt @@ -0,0 +1,16 @@ +Hisilicon hi6220 usb PHY +----------------------- + +Required properties: +- compatible: should be "hisilicon,hi6220-usb-phy" +- #phy-cells: must be 0 +- hisilicon,peripheral-syscon: phandle of syscon used to control phy. +Refer to phy/phy-bindings.txt for the generic PHY binding properties + +Example: + usb_phy: usbphy { + compatible = "hisilicon,hi6220-usb-phy"; + #phy-cells = <0>; + phy-supply = <&fixed_5v_hub>; + hisilicon,peripheral-syscon = <&sys_ctrl>; + }; diff --git a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt new file mode 100644 index 000000000000..2390e4e9c84c --- /dev/null +++ b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt @@ -0,0 +1,39 @@ +* Renesas R-Car generation 3 USB 2.0 PHY + +This file provides information on what the device node for the R-Car generation +3 USB 2.0 PHY contains. + +Required properties: +- compatible: "renesas,usb2-phy-r8a7795" if the device is a part of an R8A7795 + SoC. +- reg: offset and length of the partial USB 2.0 Host register block. +- reg-names: must be "usb2_host". +- clocks: clock phandle and specifier pair(s). +- #phy-cells: see phy-bindings.txt in the same directory, must be <0>. + +Optional properties: +To use a USB channel where USB 2.0 Host and HSUSB (USB 2.0 Peripheral) are +combined, the device tree node should set HSUSB properties to reg and reg-names +properties. This is because HSUSB has registers to select USB 2.0 host or +peripheral at that channel: +- reg: offset and length of the partial HSUSB register block. +- reg-names: must be "hsusb". +- interrupts: interrupt specifier for the PHY. + +Example (R-Car H3): + + usb-phy@ee080200 { + compatible = "renesas,usb2-phy-r8a7795"; + reg = <0 0xee080200 0 0x700>, <0 0xe6590100 0 0x100>; + reg-names = "usb2_host", "hsusb"; + interrupts = ; + clocks = <&mstp7_clks R8A7795_CLK_EHCI0>, + <&mstp7_clks R8A7795_CLK_HSUSB>; + }; + + usb-phy@ee0a0200 { + compatible = "renesas,usb2-phy-r8a7795"; + reg = <0 0xee0a0200 0 0x700>; + reg-names = "usb2_host"; + clocks = <&mstp7_clks R8A7795_CLK_EHCI0>; + }; diff --git a/Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt b/Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt index 826454ac43bb..68498d560354 100644 --- a/Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt +++ b/Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt @@ -1,7 +1,10 @@ ROCKCHIP USB2 PHY Required properties: - - compatible: rockchip,rk3288-usb-phy + - compatible: matching the soc type, one of + "rockchip,rk3066a-usb-phy" + "rockchip,rk3188-usb-phy" + "rockchip,rk3288-usb-phy" - rockchip,grf : phandle to the syscon managing the "general register files" - #address-cells: should be 1 @@ -21,6 +24,7 @@ required properties: Optional Properties: - clocks : phandle + clock specifier for the phy clocks - clock-names: string, clock name, must be "phyclk" +- #clock-cells: for users of the phy-pll, should be 0 Example: diff --git a/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt b/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt index 0cebf7454517..95736d77fbb7 100644 --- a/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt +++ b/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt @@ -9,6 +9,7 @@ Required properties: * allwinner,sun7i-a20-usb-phy * allwinner,sun8i-a23-usb-phy * allwinner,sun8i-a33-usb-phy + * allwinner,sun8i-h3-usb-phy - reg : a list of offset + length pairs - reg-names : * "phy_ctrl" diff --git a/Documentation/devicetree/bindings/phy/ti-phy.txt b/Documentation/devicetree/bindings/phy/ti-phy.txt index 9cf9446eaf2e..a3b394587874 100644 --- a/Documentation/devicetree/bindings/phy/ti-phy.txt +++ b/Documentation/devicetree/bindings/phy/ti-phy.txt @@ -31,6 +31,8 @@ OMAP USB2 PHY Required properties: - compatible: Should be "ti,omap-usb2" + Should be "ti,dra7x-usb2-phy2" for the 2nd instance of USB2 PHY + in DRA7x - reg : Address and length of the register set for the device. - #phy-cells: determine the number of cells that should be given in the phandle while referencing this phy. @@ -40,10 +42,14 @@ Required properties: * "wkupclk" - wakeup clock. * "refclk" - reference clock (optional). -Optional properties: +Deprecated properties: - ctrl-module : phandle of the control module used by PHY driver to power on the PHY. +Recommended properies: +- syscon-phy-power : phandle/offset pair. Phandle to the system control + module and the register offset to power on/off the PHY. + This is usually a subnode of ocp2scp to which it is connected. usb2phy@4a0ad080 { @@ -77,14 +83,22 @@ Required properties: * "div-clk" - apll clock Optional properties: - - ctrl-module : phandle of the control module used by PHY driver to power on - the PHY. - id: If there are multiple instance of the same type, in order to differentiate between each instance "id" can be used (e.g., multi-lane PCIe PHY). If "id" is not provided, it is set to default value of '1'. - syscon-pllreset: Handle to system control region that contains the CTRL_CORE_SMA_SW_0 register and register offset to the CTRL_CORE_SMA_SW_0 register that contains the SATA_PLL_SOFT_RESET bit. Only valid for sata_phy. + - syscon-pcs : phandle/offset pair. Phandle to the system control module and the + register offset to write the PCS delay value. + +Deprecated properties: + - ctrl-module : phandle of the control module used by PHY driver to power on + the PHY. + +Recommended properies: + - syscon-phy-power : phandle/offset pair. Phandle to the system control + module and the register offset to power on/off the PHY. This is usually a subnode of ocp2scp to which it is connected. diff --git a/Documentation/devicetree/bindings/usb/dwc2.txt b/Documentation/devicetree/bindings/usb/dwc2.txt index fd132cbee70e..221368207ca4 100644 --- a/Documentation/devicetree/bindings/usb/dwc2.txt +++ b/Documentation/devicetree/bindings/usb/dwc2.txt @@ -4,6 +4,7 @@ Platform DesignWare HS OTG USB 2.0 controller Required properties: - compatible : One of: - brcm,bcm2835-usb: The DWC2 USB controller instance in the BCM2835 SoC. + - hisilicon,hi6220-usb: The DWC2 USB controller instance in the hi6220 SoC. - rockchip,rk3066-usb: The DWC2 USB controller instance in the rk3066 Soc; - "rockchip,rk3188-usb", "rockchip,rk3066-usb", "snps,dwc2": for rk3188 Soc; - "rockchip,rk3288-usb", "rockchip,rk3066-usb", "snps,dwc2": for rk3288 Soc; diff --git a/Documentation/devicetree/bindings/usb/dwc3-xilinx.txt b/Documentation/devicetree/bindings/usb/dwc3-xilinx.txt new file mode 100644 index 000000000000..30361b32a460 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/dwc3-xilinx.txt @@ -0,0 +1,33 @@ +Xilinx SuperSpeed DWC3 USB SoC controller + +Required properties: +- compatible: Should contain "xlnx,zynqmp-dwc3" +- clocks: A list of phandles for the clocks listed in clock-names +- clock-names: Should contain the following: + "bus_clk" Master/Core clock, have to be >= 125 MHz for SS + operation and >= 60MHz for HS operation + + "ref_clk" Clock source to core during PHY power down + +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. + +Example device node: + + usb@0 { + #address-cells = <0x2>; + #size-cells = <0x1>; + status = "okay"; + compatible = "xlnx,zynqmp-dwc3"; + clock-names = "bus_clk" "ref_clk"; + clocks = <&clk125>, <&clk125>; + ranges; + + dwc3@fe200000 { + compatible = "snps,dwc3"; + reg = <0x0 0xfe200000 0x40000>; + interrupts = <0x0 0x41 0x4>; + dr_mode = "host"; + }; + }; diff --git a/Documentation/devicetree/bindings/usb/mt8173-xhci.txt b/Documentation/devicetree/bindings/usb/mt8173-xhci.txt new file mode 100644 index 000000000000..b3a7ffa48852 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/mt8173-xhci.txt @@ -0,0 +1,51 @@ +MT8173 xHCI + +The device node for Mediatek SOC USB3.0 host controller + +Required properties: + - compatible : should contain "mediatek,mt8173-xhci" + - reg : specifies physical base address and size of the registers, + the first one for MAC, the second for IPPC + - interrupts : interrupt used by the controller + - power-domains : a phandle to USB power domain node to control USB's + mtcmos + - vusb33-supply : regulator of USB avdd3.3v + + - clocks : a list of phandle + clock-specifier pairs, one for each + entry in clock-names + - clock-names : must contain + "sys_ck": for clock of xHCI MAC + "wakeup_deb_p0": for USB wakeup debounce clock of port0 + "wakeup_deb_p1": for USB wakeup debounce clock of port1 + + - phys : a list of phandle + phy specifier pairs + +Optional properties: + - mediatek,wakeup-src : 1: ip sleep wakeup mode; 2: line state wakeup + mode; + - mediatek,syscon-wakeup : phandle to syscon used to access USB wakeup + control register, it depends on "mediatek,wakeup-src". + - vbus-supply : reference to the VBUS regulator; + - usb3-lpm-capable : supports USB3.0 LPM + +Example: +usb30: usb@11270000 { + compatible = "mediatek,mt8173-xhci"; + reg = <0 0x11270000 0 0x1000>, + <0 0x11280700 0 0x0100>; + interrupts = ; + power-domains = <&scpsys MT8173_POWER_DOMAIN_USB>; + clocks = <&topckgen CLK_TOP_USB30_SEL>, + <&pericfg CLK_PERI_USB0>, + <&pericfg CLK_PERI_USB1>; + clock-names = "sys_ck", + "wakeup_deb_p0", + "wakeup_deb_p1"; + phys = <&phy_port0 PHY_TYPE_USB3>, + <&phy_port1 PHY_TYPE_USB2>; + vusb33-supply = <&mt6397_vusb_reg>; + vbus-supply = <&usb_p1_vbus>; + usb3-lpm-capable; + mediatek,syscon-wakeup = <&pericfg>; + mediatek,wakeup-src = <1>; +}; diff --git a/Documentation/devicetree/bindings/usb/renesas_usb3.txt b/Documentation/devicetree/bindings/usb/renesas_usb3.txt new file mode 100644 index 000000000000..8d52766f07b9 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/renesas_usb3.txt @@ -0,0 +1,23 @@ +Renesas Electronics USB3.0 Peripheral driver + +Required properties: + - compatible: Must contain one of the following: + - "renesas,r8a7795-usb3-peri" + - reg: Base address and length of the register for the USB3.0 Peripheral + - interrupts: Interrupt specifier for the USB3.0 Peripheral + - clocks: clock phandle and specifier pair + +Example: + usb3_peri0: usb@ee020000 { + compatible = "renesas,r8a7795-usb3-peri"; + reg = <0 0xee020000 0 0x400>; + interrupts = ; + clocks = <&cpg CPG_MOD 328>; + }; + + usb3_peri1: usb@ee060000 { + compatible = "renesas,r8a7795-usb3-peri"; + reg = <0 0xee060000 0 0x400>; + interrupts = ; + clocks = <&cpg CPG_MOD 327>; + }; diff --git a/Documentation/devicetree/bindings/usb/renesas_usbhs.txt b/Documentation/devicetree/bindings/usb/renesas_usbhs.txt index 7d48f63db44e..b6040563e51a 100644 --- a/Documentation/devicetree/bindings/usb/renesas_usbhs.txt +++ b/Documentation/devicetree/bindings/usb/renesas_usbhs.txt @@ -1,11 +1,21 @@ Renesas Electronics USBHS driver Required properties: - - compatible: Must contain one of the following: - - "renesas,usbhs-r8a7790" - - "renesas,usbhs-r8a7791" - - "renesas,usbhs-r8a7794" - - "renesas,usbhs-r8a7795" + - compatible: Must contain one or more of the following: + + - "renesas,usbhs-r8a7790" for r8a7790 (R-Car H2) compatible device + - "renesas,usbhs-r8a7791" for r8a7791 (R-Car M2-W) compatible device + - "renesas,usbhs-r8a7792" for r8a7792 (R-Car V2H) compatible device + - "renesas,usbhs-r8a7793" for r8a7793 (R-Car M2-N) compatible device + - "renesas,usbhs-r8a7794" for r8a7794 (R-Car E2) compatible device + - "renesas,usbhs-r8a7795" for r8a7795 (R-Car H3) compatible device + - "renesas,rcar-gen2-usbhs" for R-Car Gen2 compatible device + - "renesas,rcar-gen3-usbhs" for R-Car Gen3 compatible device + + When compatible with the generic version, nodes must list the + SoC-specific version corresponding to the platform first followed + by the generic version. + - reg: Base address and length of the register for the USBHS - interrupts: Interrupt specifier for the USBHS - clocks: A list of phandle + clock specifier pairs @@ -22,7 +32,7 @@ Optional properties: Example: usbhs: usb@e6590000 { - compatible = "renesas,usbhs-r8a7790"; + compatible = "renesas,usbhs-r8a7790", "renesas,rcar-gen2-usbhs"; reg = <0 0xe6590000 0 0x100>; interrupts = <0 107 IRQ_TYPE_LEVEL_HIGH>; clocks = <&mstp7_clks R8A7790_CLK_HSUSB>; diff --git a/Documentation/devicetree/bindings/usb/usb-xhci.txt b/Documentation/devicetree/bindings/usb/usb-xhci.txt index 86f67f0886bc..082573289f1e 100644 --- a/Documentation/devicetree/bindings/usb/usb-xhci.txt +++ b/Documentation/devicetree/bindings/usb/usb-xhci.txt @@ -3,8 +3,8 @@ USB xHCI controllers Required properties: - compatible: should be one of "generic-xhci", "marvell,armada-375-xhci", "marvell,armada-380-xhci", - "renesas,xhci-r8a7790", "renesas,xhci-r8a7791" (deprecated: - "xhci-platform"). + "renesas,xhci-r8a7790", "renesas,xhci-r8a7791", "renesas,xhci-r8a7793", + "renesas,xhci-r8a7795" (deprecated: "xhci-platform"). - reg: should contain address and length of the standard XHCI register set for the device. - interrupts: one XHCI interrupt should be described here. diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index 1a8169ba29e6..8b9fc655ddc7 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -2584,8 +2584,6 @@ bytes respectively. Such letter suffixes can also be entirely omitted. notsc [BUGS=X86-32] Disable Time Stamp Counter - nousb [USB] Disable the USB subsystem - nowatchdog [KNL] Disable both lockup detectors, i.e. soft-lockup and NMI watchdog (hard-lockup). @@ -3900,6 +3898,10 @@ bytes respectively. Such letter suffixes can also be entirely omitted. usbcore.usbfs_snoop= [USB] Set to log all usbfs traffic (default 0 = off). + usbcore.usbfs_snoop_max= + [USB] Maximum number of bytes to snoop in each URB + (default = 65536). + usbcore.blinkenlights= [USB] Set to cycle leds on hubs (default 0 = off). @@ -3920,6 +3922,8 @@ bytes respectively. Such letter suffixes can also be entirely omitted. USB_REQ_GET_DESCRIPTOR request in milliseconds (default 5000 = 5.0 seconds). + usbcore.nousb [USB] Disable the USB subsystem + usbhid.mousepoll= [USBHID] The interval which mice are to be polled at. diff --git a/Documentation/usb/chipidea.txt b/Documentation/usb/chipidea.txt index 3f848c1f2940..05f735a1b5a5 100644 --- a/Documentation/usb/chipidea.txt +++ b/Documentation/usb/chipidea.txt @@ -7,8 +7,8 @@ with 2 Freescale i.MX6Q sabre SD boards. --------------------------------------- Select CONFIG_USB_OTG_FSM, rebuild kernel Image and modules. If you want to check some internal variables for otg fsm, -select CONFIG_USB_CHIPIDEA_DEBUG, there are 2 files which -can show otg fsm variables and some controller registers value: +mount debugfs, there are 2 files which can show otg fsm +variables and some controller registers value: cat /sys/kernel/debug/ci_hdrc.0/otg cat /sys/kernel/debug/ci_hdrc.0/registers diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index b24d3ef89166..581960574889 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -434,7 +434,7 @@ On host: serialc -v -p -i -a1 -s1024 \ where seriald and serialc are Felipe's utilities found here: -https://git.gitorious.org/usb/usb-tools.git master +https://github.com/felipebalbi/usb-tools.git master 12. PHONET function =================== @@ -579,6 +579,8 @@ The SOURCESINK function provides these attributes in its function directory: isoc_mult - 0..2 (hs/ss only) isoc_maxburst - 0..15 (ss only) bulk_buflen - buffer length + bulk_qlen - depth of queue for bulk + iso_qlen - depth of queue for iso Testing the SOURCESINK function ------------------------------- diff --git a/Documentation/usb/power-management.txt b/Documentation/usb/power-management.txt index 4a15c90bc11d..0a94ffe17ab6 100644 --- a/Documentation/usb/power-management.txt +++ b/Documentation/usb/power-management.txt @@ -537,17 +537,18 @@ relevant attribute files are usb2_hardware_lpm and usb3_hardware_lpm. can write y/Y/1 or n/N/0 to the file to enable/disable USB2 hardware LPM manually. This is for test purpose mainly. - power/usb3_hardware_lpm + power/usb3_hardware_lpm_u1 + power/usb3_hardware_lpm_u2 When a USB 3.0 lpm-capable device is plugged in to a xHCI host which supports link PM, it will check if U1 and U2 exit latencies have been set in the BOS descriptor; if the check is is passed and the host supports USB3 hardware LPM, USB3 hardware LPM will be - enabled for the device and this file will be created. - The file holds a string value (enable or disable) - indicating whether or not USB3 hardware LPM is - enabled for the device. + enabled for the device and these files will be created. + The files hold a string value (enable or disable) + indicating whether or not USB3 hardware LPM U1 or U2 + is enabled for the device. USB Port Power Control ---------------------- diff --git a/MAINTAINERS b/MAINTAINERS index ded96408efe6..795846fada25 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2767,7 +2767,7 @@ S: Maintained F: Documentation/zh_CN/ CHIPIDEA USB HIGH SPEED DUAL ROLE CONTROLLER -M: Peter Chen +M: Peter Chen T: git git://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/usb.git L: linux-usb@vger.kernel.org S: Maintained @@ -8992,6 +8992,12 @@ L: linux-sh@vger.kernel.org F: drivers/net/ethernet/renesas/ F: include/linux/sh_eth.h +RENESAS USB2 PHY DRIVER +M: Yoshihiro Shimoda +L: linux-sh@vger.kernel.org +S: Maintained +F: drivers/phy/phy-rcar-gen3-usb2.c + RESET CONTROLLER FRAMEWORK M: Philipp Zabel S: Maintained @@ -11188,7 +11194,7 @@ F: Documentation/usb/ohci.txt F: drivers/usb/host/ohci* USB OTG FSM (Finite State Machine) -M: Peter Chen +M: Peter Chen T: git git://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/usb.git L: linux-usb@vger.kernel.org S: Maintained diff --git a/arch/arm64/boot/dts/mediatek/mt8173-evb.dts b/arch/arm64/boot/dts/mediatek/mt8173-evb.dts index 811cb760ba49..9b1482a1d007 100644 --- a/arch/arm64/boot/dts/mediatek/mt8173-evb.dts +++ b/arch/arm64/boot/dts/mediatek/mt8173-evb.dts @@ -13,6 +13,7 @@ */ /dts-v1/; +#include #include "mt8173.dtsi" / { @@ -32,6 +33,15 @@ }; chosen { }; + + usb_p1_vbus: regulator@0 { + compatible = "regulator-fixed"; + regulator-name = "usb_vbus"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + gpio = <&pio 130 GPIO_ACTIVE_HIGH>; + enable-active-high; + }; }; &i2c1 { @@ -408,3 +418,9 @@ &uart0 { status = "okay"; }; + +&usb30 { + vusb33-supply = <&mt6397_vusb_reg>; + vbus-supply = <&usb_p1_vbus>; + mediatek,wakeup-src = <1>; +}; diff --git a/arch/arm64/boot/dts/mediatek/mt8173.dtsi b/arch/arm64/boot/dts/mediatek/mt8173.dtsi index 4dd5f93d0303..c1fd2757f8e4 100644 --- a/arch/arm64/boot/dts/mediatek/mt8173.dtsi +++ b/arch/arm64/boot/dts/mediatek/mt8173.dtsi @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include "mt8173-pinfunc.h" @@ -510,6 +511,47 @@ status = "disabled"; }; + usb30: usb@11270000 { + compatible = "mediatek,mt8173-xhci"; + reg = <0 0x11270000 0 0x1000>, + <0 0x11280700 0 0x0100>; + interrupts = ; + power-domains = <&scpsys MT8173_POWER_DOMAIN_USB>; + clocks = <&topckgen CLK_TOP_USB30_SEL>, + <&pericfg CLK_PERI_USB0>, + <&pericfg CLK_PERI_USB1>; + clock-names = "sys_ck", + "wakeup_deb_p0", + "wakeup_deb_p1"; + phys = <&phy_port0 PHY_TYPE_USB3>, + <&phy_port1 PHY_TYPE_USB2>; + mediatek,syscon-wakeup = <&pericfg>; + status = "okay"; + }; + + u3phy: usb-phy@11290000 { + compatible = "mediatek,mt8173-u3phy"; + reg = <0 0x11290000 0 0x800>; + clocks = <&apmixedsys CLK_APMIXED_REF2USB_TX>; + clock-names = "u3phya_ref"; + #address-cells = <2>; + #size-cells = <2>; + ranges; + status = "okay"; + + phy_port0: port@11290800 { + reg = <0 0x11290800 0 0x800>; + #phy-cells = <1>; + status = "okay"; + }; + + phy_port1: port@11291000 { + reg = <0 0x11291000 0 0x800>; + #phy-cells = <1>; + status = "okay"; + }; + }; + mmsys: clock-controller@14000000 { compatible = "mediatek,mt8173-mmsys", "syscon"; reg = <0 0x14000000 0 0x1000>; diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 03cb3ea2d2c0..e7e117d5dbbe 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -118,6 +118,13 @@ config PHY_RCAR_GEN2 help Support for USB PHY found on Renesas R-Car generation 2 SoCs. +config PHY_RCAR_GEN3_USB2 + tristate "Renesas R-Car generation 3 USB 2.0 PHY driver" + depends on OF && ARCH_SHMOBILE + select GENERIC_PHY + help + Support for USB 2.0 PHY found on Renesas R-Car generation 3 SoCs. + config OMAP_CONTROL_PHY tristate "OMAP CONTROL PHY Driver" depends on ARCH_OMAP2PLUS || COMPILE_TEST @@ -215,6 +222,15 @@ config PHY_MT65XX_USB3 for mt65xx SoCs. it supports two usb2.0 ports and one usb3.0 port. +config PHY_HI6220_USB + tristate "hi6220 USB PHY support" + select GENERIC_PHY + select MFD_SYSCON + help + Enable this to support the HISILICON HI6220 USB PHY. + + To compile this driver as a module, choose M here. + config PHY_SUN4I_USB tristate "Allwinner sunxi SoC USB PHY driver" depends on ARCH_SUNXI && HAS_IOMEM && OF @@ -374,11 +390,11 @@ config PHY_TUSB1210 config PHY_BRCMSTB_SATA tristate "Broadcom STB SATA PHY driver" - depends on ARCH_BRCMSTB + depends on ARCH_BRCMSTB || BMIPS_GENERIC depends on OF select GENERIC_PHY help - Enable this to support the SATA3 PHY on 28nm Broadcom STB SoCs. + Enable this to support the SATA3 PHY on 28nm or 40nm Broadcom STB SoCs. Likely useful only with CONFIG_SATA_BRCMSTB enabled. config PHY_CYGNUS_PCIE diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 075db1a81aa5..c80f09df3bb8 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -17,12 +17,14 @@ obj-$(CONFIG_PHY_MVEBU_SATA) += phy-mvebu-sata.o obj-$(CONFIG_PHY_MIPHY28LP) += phy-miphy28lp.o obj-$(CONFIG_PHY_MIPHY365X) += phy-miphy365x.o obj-$(CONFIG_PHY_RCAR_GEN2) += phy-rcar-gen2.o +obj-$(CONFIG_PHY_RCAR_GEN3_USB2) += phy-rcar-gen3-usb2.o obj-$(CONFIG_OMAP_CONTROL_PHY) += phy-omap-control.o obj-$(CONFIG_OMAP_USB2) += phy-omap-usb2.o obj-$(CONFIG_TI_PIPE3) += phy-ti-pipe3.o obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o obj-$(CONFIG_PHY_EXYNOS5250_SATA) += phy-exynos5250-sata.o obj-$(CONFIG_PHY_HIX5HD2_SATA) += phy-hix5hd2-sata.o +obj-$(CONFIG_PHY_HI6220_USB) += phy-hi6220-usb.o obj-$(CONFIG_PHY_MT65XX_USB3) += phy-mt65xx-usb3.o obj-$(CONFIG_PHY_SUN4I_USB) += phy-sun4i-usb.o obj-$(CONFIG_PHY_SUN9I_USB) += phy-sun9i-usb.o diff --git a/drivers/phy/phy-berlin-usb.c b/drivers/phy/phy-berlin-usb.c index 797ba17c404f..2017751ede26 100644 --- a/drivers/phy/phy-berlin-usb.c +++ b/drivers/phy/phy-berlin-usb.c @@ -9,11 +9,9 @@ * warranty of any kind, whether express or implied. */ -#include #include #include #include -#include #include #include #include @@ -195,7 +193,6 @@ static int phy_berlin_usb_probe(struct platform_device *pdev) return PTR_ERR(phy); } - platform_set_drvdata(pdev, priv); phy_set_drvdata(phy, priv); phy_provider = diff --git a/drivers/phy/phy-brcmstb-sata.c b/drivers/phy/phy-brcmstb-sata.c index cd9dba820566..a23172ff40e3 100644 --- a/drivers/phy/phy-brcmstb-sata.c +++ b/drivers/phy/phy-brcmstb-sata.c @@ -26,13 +26,21 @@ #define SATA_MDIO_BANK_OFFSET 0x23c #define SATA_MDIO_REG_OFFSET(ofs) ((ofs) * 4) -#define SATA_MDIO_REG_SPACE_SIZE 0x1000 -#define SATA_MDIO_REG_LENGTH 0x1f00 #define MAX_PORTS 2 /* Register offset between PHYs in PCB space */ -#define SATA_MDIO_REG_SPACE_SIZE 0x1000 +#define SATA_MDIO_REG_28NM_SPACE_SIZE 0x1000 + +/* The older SATA PHY registers duplicated per port registers within the map, + * rather than having a separate map per port. + */ +#define SATA_MDIO_REG_40NM_SPACE_SIZE 0x10 + +enum brcm_sata_phy_version { + BRCM_SATA_PHY_28NM, + BRCM_SATA_PHY_40NM, +}; struct brcm_sata_port { int portnum; @@ -44,11 +52,12 @@ struct brcm_sata_port { struct brcm_sata_phy { struct device *dev; void __iomem *phy_base; + enum brcm_sata_phy_version version; struct brcm_sata_port phys[MAX_PORTS]; }; -enum sata_mdio_phy_regs_28nm { +enum sata_mdio_phy_regs { PLL_REG_BANK_0 = 0x50, PLL_REG_BANK_0_PLLCONTROL_0 = 0x81, @@ -66,8 +75,16 @@ enum sata_mdio_phy_regs_28nm { static inline void __iomem *brcm_sata_phy_base(struct brcm_sata_port *port) { struct brcm_sata_phy *priv = port->phy_priv; + u32 offset = 0; - return priv->phy_base + (port->portnum * SATA_MDIO_REG_SPACE_SIZE); + if (priv->version == BRCM_SATA_PHY_28NM) + offset = SATA_MDIO_REG_28NM_SPACE_SIZE; + else if (priv->version == BRCM_SATA_PHY_40NM) + offset = SATA_MDIO_REG_40NM_SPACE_SIZE; + else + dev_err(priv->dev, "invalid phy version\n"); + + return priv->phy_base + (port->portnum * offset); } static void brcm_sata_mdio_wr(void __iomem *addr, u32 bank, u32 ofs, @@ -86,7 +103,7 @@ static void brcm_sata_mdio_wr(void __iomem *addr, u32 bank, u32 ofs, #define FMAX_VAL_DEFAULT 0x3df #define FMAX_VAL_SSC 0x83 -static void brcm_sata_cfg_ssc_28nm(struct brcm_sata_port *port) +static void brcm_sata_cfg_ssc(struct brcm_sata_port *port) { void __iomem *base = brcm_sata_phy_base(port); struct brcm_sata_phy *priv = port->phy_priv; @@ -117,18 +134,21 @@ static int brcm_sata_phy_init(struct phy *phy) { struct brcm_sata_port *port = phy_get_drvdata(phy); - brcm_sata_cfg_ssc_28nm(port); + brcm_sata_cfg_ssc(port); return 0; } -static const struct phy_ops phy_ops_28nm = { +static const struct phy_ops phy_ops = { .init = brcm_sata_phy_init, .owner = THIS_MODULE, }; static const struct of_device_id brcm_sata_phy_of_match[] = { - { .compatible = "brcm,bcm7445-sata-phy" }, + { .compatible = "brcm,bcm7445-sata-phy", + .data = (void *)BRCM_SATA_PHY_28NM }, + { .compatible = "brcm,bcm7425-sata-phy", + .data = (void *)BRCM_SATA_PHY_40NM }, {}, }; MODULE_DEVICE_TABLE(of, brcm_sata_phy_of_match); @@ -137,6 +157,7 @@ static int brcm_sata_phy_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *dn = dev->of_node, *child; + const struct of_device_id *of_id; struct brcm_sata_phy *priv; struct resource *res; struct phy_provider *provider; @@ -156,6 +177,12 @@ static int brcm_sata_phy_probe(struct platform_device *pdev) if (IS_ERR(priv->phy_base)) return PTR_ERR(priv->phy_base); + of_id = of_match_node(brcm_sata_phy_of_match, dn); + if (of_id) + priv->version = (enum brcm_sata_phy_version)of_id->data; + else + priv->version = BRCM_SATA_PHY_28NM; + for_each_available_child_of_node(dn, child) { unsigned int id; struct brcm_sata_port *port; @@ -181,7 +208,7 @@ static int brcm_sata_phy_probe(struct platform_device *pdev) port = &priv->phys[id]; port->portnum = id; port->phy_priv = priv; - port->phy = devm_phy_create(dev, child, &phy_ops_28nm); + port->phy = devm_phy_create(dev, child, &phy_ops); port->ssc_en = of_property_read_bool(child, "brcm,enable-ssc"); if (IS_ERR(port->phy)) { dev_err(dev, "failed to create PHY\n"); diff --git a/drivers/phy/phy-hi6220-usb.c b/drivers/phy/phy-hi6220-usb.c new file mode 100644 index 000000000000..b2141cbd4cf6 --- /dev/null +++ b/drivers/phy/phy-hi6220-usb.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2015 Linaro Ltd. + * Copyright (c) 2015 Hisilicon Limited. + * + * 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. + */ + +#include +#include +#include +#include +#include + +#define SC_PERIPH_CTRL4 0x00c + +#define CTRL4_PICO_SIDDQ BIT(6) +#define CTRL4_PICO_OGDISABLE BIT(8) +#define CTRL4_PICO_VBUSVLDEXT BIT(10) +#define CTRL4_PICO_VBUSVLDEXTSEL BIT(11) +#define CTRL4_OTG_PHY_SEL BIT(21) + +#define SC_PERIPH_CTRL5 0x010 + +#define CTRL5_USBOTG_RES_SEL BIT(3) +#define CTRL5_PICOPHY_ACAENB BIT(4) +#define CTRL5_PICOPHY_BC_MODE BIT(5) +#define CTRL5_PICOPHY_CHRGSEL BIT(6) +#define CTRL5_PICOPHY_VDATSRCEND BIT(7) +#define CTRL5_PICOPHY_VDATDETENB BIT(8) +#define CTRL5_PICOPHY_DCDENB BIT(9) +#define CTRL5_PICOPHY_IDDIG BIT(10) + +#define SC_PERIPH_CTRL8 0x018 +#define SC_PERIPH_RSTEN0 0x300 +#define SC_PERIPH_RSTDIS0 0x304 + +#define RST0_USBOTG_BUS BIT(4) +#define RST0_POR_PICOPHY BIT(5) +#define RST0_USBOTG BIT(6) +#define RST0_USBOTG_32K BIT(7) + +#define EYE_PATTERN_PARA 0x7053348c + +struct hi6220_priv { + struct regmap *reg; + struct device *dev; +}; + +static void hi6220_phy_init(struct hi6220_priv *priv) +{ + struct regmap *reg = priv->reg; + u32 val, mask; + + val = RST0_USBOTG_BUS | RST0_POR_PICOPHY | + RST0_USBOTG | RST0_USBOTG_32K; + mask = val; + regmap_update_bits(reg, SC_PERIPH_RSTEN0, mask, val); + regmap_update_bits(reg, SC_PERIPH_RSTDIS0, mask, val); +} + +static int hi6220_phy_setup(struct hi6220_priv *priv, bool on) +{ + struct regmap *reg = priv->reg; + u32 val, mask; + int ret; + + if (on) { + val = CTRL5_USBOTG_RES_SEL | CTRL5_PICOPHY_ACAENB; + mask = val | CTRL5_PICOPHY_BC_MODE; + ret = regmap_update_bits(reg, SC_PERIPH_CTRL5, mask, val); + if (ret) + goto out; + + val = CTRL4_PICO_VBUSVLDEXT | CTRL4_PICO_VBUSVLDEXTSEL | + CTRL4_OTG_PHY_SEL; + mask = val | CTRL4_PICO_SIDDQ | CTRL4_PICO_OGDISABLE; + ret = regmap_update_bits(reg, SC_PERIPH_CTRL4, mask, val); + if (ret) + goto out; + + ret = regmap_write(reg, SC_PERIPH_CTRL8, EYE_PATTERN_PARA); + if (ret) + goto out; + } else { + val = CTRL4_PICO_SIDDQ; + mask = val; + ret = regmap_update_bits(reg, SC_PERIPH_CTRL4, mask, val); + if (ret) + goto out; + } + + return 0; +out: + dev_err(priv->dev, "failed to setup phy ret: %d\n", ret); + return ret; +} + +static int hi6220_phy_start(struct phy *phy) +{ + struct hi6220_priv *priv = phy_get_drvdata(phy); + + return hi6220_phy_setup(priv, true); +} + +static int hi6220_phy_exit(struct phy *phy) +{ + struct hi6220_priv *priv = phy_get_drvdata(phy); + + return hi6220_phy_setup(priv, false); +} + +static struct phy_ops hi6220_phy_ops = { + .init = hi6220_phy_start, + .exit = hi6220_phy_exit, + .owner = THIS_MODULE, +}; + +static int hi6220_phy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct phy *phy; + struct hi6220_priv *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->reg = syscon_regmap_lookup_by_phandle(dev->of_node, + "hisilicon,peripheral-syscon"); + if (IS_ERR(priv->reg)) { + dev_err(dev, "no hisilicon,peripheral-syscon\n"); + return PTR_ERR(priv->reg); + } + + hi6220_phy_init(priv); + + phy = devm_phy_create(dev, NULL, &hi6220_phy_ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + 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 hi6220_phy_of_match[] = { + {.compatible = "hisilicon,hi6220-usb-phy",}, + { }, +}; +MODULE_DEVICE_TABLE(of, hi6220_phy_of_match); + +static struct platform_driver hi6220_phy_driver = { + .probe = hi6220_phy_probe, + .driver = { + .name = "hi6220-usb-phy", + .of_match_table = hi6220_phy_of_match, + } +}; +module_platform_driver(hi6220_phy_driver); + +MODULE_DESCRIPTION("HISILICON HI6220 USB PHY driver"); +MODULE_ALIAS("platform:hi6220-usb-phy"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/phy-mt65xx-usb3.c b/drivers/phy/phy-mt65xx-usb3.c index e427c3b788ff..c0e7b4b0cf5c 100644 --- a/drivers/phy/phy-mt65xx-usb3.c +++ b/drivers/phy/phy-mt65xx-usb3.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -27,6 +28,7 @@ * relative to USB3_SIF2_BASE base address */ #define SSUSB_SIFSLV_SPLLC 0x0000 +#define SSUSB_SIFSLV_U2FREQ 0x0100 /* offsets of sub-segment in each port registers */ #define SSUSB_SIFSLV_U2PHY_COM_BASE 0x0000 @@ -41,6 +43,7 @@ #define PA2_RG_SIF_U2PLL_FORCE_EN BIT(18) #define U3P_USBPHYACR5 (SSUSB_SIFSLV_U2PHY_COM_BASE + 0x0014) +#define PA5_RG_U2_HSTX_SRCAL_EN BIT(15) #define PA5_RG_U2_HSTX_SRCTRL GENMASK(14, 12) #define PA5_RG_U2_HSTX_SRCTRL_VAL(x) ((0x7 & (x)) << 12) #define PA5_RG_U2_HS_100U_U3_EN BIT(11) @@ -49,6 +52,8 @@ #define PA6_RG_U2_ISO_EN BIT(31) #define PA6_RG_U2_BC11_SW_EN BIT(23) #define PA6_RG_U2_OTG_VBUSCMP_EN BIT(20) +#define PA6_RG_U2_SQTH GENMASK(3, 0) +#define PA6_RG_U2_SQTH_VAL(x) (0xf & (x)) #define U3P_U2PHYACR4 (SSUSB_SIFSLV_U2PHY_COM_BASE + 0x0020) #define P2C_RG_USB20_GPIO_CTL BIT(9) @@ -111,6 +116,24 @@ #define XC3_RG_U3_XTAL_RX_PWD BIT(9) #define XC3_RG_U3_FRC_XTAL_RX_PWD BIT(8) +#define U3P_U2FREQ_FMCR0 (SSUSB_SIFSLV_U2FREQ + 0x00) +#define P2F_RG_MONCLK_SEL GENMASK(27, 26) +#define P2F_RG_MONCLK_SEL_VAL(x) ((0x3 & (x)) << 26) +#define P2F_RG_FREQDET_EN BIT(24) +#define P2F_RG_CYCLECNT GENMASK(23, 0) +#define P2F_RG_CYCLECNT_VAL(x) ((P2F_RG_CYCLECNT) & (x)) + +#define U3P_U2FREQ_VALUE (SSUSB_SIFSLV_U2FREQ + 0x0c) + +#define U3P_U2FREQ_FMMONR1 (SSUSB_SIFSLV_U2FREQ + 0x10) +#define P2F_USB_FM_VALID BIT(0) +#define P2F_RG_FRCK_EN BIT(8) + +#define U3P_REF_CLK 26 /* MHZ */ +#define U3P_SLEW_RATE_COEF 28 +#define U3P_SR_COEF_DIVISOR 1000 +#define U3P_FM_DET_CYCLE_CNT 1024 + struct mt65xx_phy_instance { struct phy *phy; void __iomem *port_base; @@ -126,6 +149,77 @@ struct mt65xx_u3phy { int nphys; }; +static void hs_slew_rate_calibrate(struct mt65xx_u3phy *u3phy, + struct mt65xx_phy_instance *instance) +{ + void __iomem *sif_base = u3phy->sif_base; + int calibration_val; + int fm_out; + u32 tmp; + + /* enable USB ring oscillator */ + tmp = readl(instance->port_base + U3P_USBPHYACR5); + tmp |= PA5_RG_U2_HSTX_SRCAL_EN; + writel(tmp, instance->port_base + U3P_USBPHYACR5); + udelay(1); + + /*enable free run clock */ + tmp = readl(sif_base + U3P_U2FREQ_FMMONR1); + tmp |= P2F_RG_FRCK_EN; + writel(tmp, sif_base + U3P_U2FREQ_FMMONR1); + + /* set cycle count as 1024, and select u2 channel */ + tmp = readl(sif_base + U3P_U2FREQ_FMCR0); + tmp &= ~(P2F_RG_CYCLECNT | P2F_RG_MONCLK_SEL); + tmp |= P2F_RG_CYCLECNT_VAL(U3P_FM_DET_CYCLE_CNT); + tmp |= P2F_RG_MONCLK_SEL_VAL(instance->index); + writel(tmp, sif_base + U3P_U2FREQ_FMCR0); + + /* enable frequency meter */ + tmp = readl(sif_base + U3P_U2FREQ_FMCR0); + tmp |= P2F_RG_FREQDET_EN; + writel(tmp, sif_base + U3P_U2FREQ_FMCR0); + + /* ignore return value */ + readl_poll_timeout(sif_base + U3P_U2FREQ_FMMONR1, tmp, + (tmp & P2F_USB_FM_VALID), 10, 200); + + fm_out = readl(sif_base + U3P_U2FREQ_VALUE); + + /* disable frequency meter */ + tmp = readl(sif_base + U3P_U2FREQ_FMCR0); + tmp &= ~P2F_RG_FREQDET_EN; + writel(tmp, sif_base + U3P_U2FREQ_FMCR0); + + /*disable free run clock */ + tmp = readl(sif_base + U3P_U2FREQ_FMMONR1); + tmp &= ~P2F_RG_FRCK_EN; + writel(tmp, sif_base + 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; + calibration_val = DIV_ROUND_CLOSEST(tmp, U3P_SR_COEF_DIVISOR); + } else { + /* if FM detection fail, set default value */ + calibration_val = 4; + } + dev_dbg(u3phy->dev, "phy:%d, fm_out:%d, calib:%d\n", + instance->index, fm_out, calibration_val); + + /* set HS slew rate */ + tmp = readl(instance->port_base + U3P_USBPHYACR5); + tmp &= ~PA5_RG_U2_HSTX_SRCTRL; + tmp |= PA5_RG_U2_HSTX_SRCTRL_VAL(calibration_val); + writel(tmp, instance->port_base + U3P_USBPHYACR5); + + /* disable USB ring oscillator */ + tmp = readl(instance->port_base + U3P_USBPHYACR5); + tmp &= ~PA5_RG_U2_HSTX_SRCAL_EN; + writel(tmp, instance->port_base + U3P_USBPHYACR5); +} + static void phy_instance_init(struct mt65xx_u3phy *u3phy, struct mt65xx_phy_instance *instance) { @@ -165,9 +259,10 @@ static void phy_instance_init(struct mt65xx_u3phy *u3phy, writel(tmp, port_base + U3P_U2PHYDTM0); } - /* DP/DM BC1.1 path Disable */ tmp = readl(port_base + U3P_USBPHYACR6); - tmp &= ~PA6_RG_U2_BC11_SW_EN; + tmp &= ~PA6_RG_U2_BC11_SW_EN; /* DP/DM BC1.1 path Disable */ + tmp &= ~PA6_RG_U2_SQTH; + tmp |= PA6_RG_U2_SQTH_VAL(2); writel(tmp, port_base + U3P_USBPHYACR6); tmp = readl(port_base + U3P_U3PHYA_DA_REG0); @@ -223,9 +318,9 @@ static void phy_instance_power_on(struct mt65xx_u3phy *u3phy, tmp |= XC3_RG_U3_XTAL_RX_PWD | XC3_RG_U3_FRC_XTAL_RX_PWD; writel(tmp, u3phy->sif_base + U3P_XTALCTL3); - /* [mt8173]disable Change 100uA current from SSUSB */ + /* [mt8173]switch 100uA current to SSUSB */ tmp = readl(port_base + U3P_USBPHYACR5); - tmp &= ~PA5_RG_U2_HS_100U_U3_EN; + tmp |= PA5_RG_U2_HS_100U_U3_EN; writel(tmp, port_base + U3P_USBPHYACR5); } @@ -270,7 +365,7 @@ static void phy_instance_power_off(struct mt65xx_u3phy *u3phy, writel(tmp, port_base + U3P_USBPHYACR6); if (!index) { - /* (also disable)Change 100uA current switch to USB2.0 */ + /* switch 100uA current back to USB2.0 */ tmp = readl(port_base + U3P_USBPHYACR5); tmp &= ~PA5_RG_U2_HS_100U_U3_EN; writel(tmp, port_base + U3P_USBPHYACR5); @@ -340,6 +435,7 @@ static int mt65xx_phy_power_on(struct phy *phy) struct mt65xx_u3phy *u3phy = dev_get_drvdata(phy->dev.parent); phy_instance_power_on(u3phy, instance); + hs_slew_rate_calibrate(u3phy, instance); return 0; } diff --git a/drivers/phy/phy-omap-usb2.c b/drivers/phy/phy-omap-usb2.c index 0fe80589ffbe..c134989052f5 100644 --- a/drivers/phy/phy-omap-usb2.c +++ b/drivers/phy/phy-omap-usb2.c @@ -29,6 +29,8 @@ #include #include #include +#include +#include #include #define USB2PHY_DISCON_BYP_LATCH (1 << 31) @@ -97,22 +99,38 @@ static int omap_usb_set_peripheral(struct usb_otg *otg, return 0; } +static int omap_usb_phy_power(struct omap_usb *phy, int on) +{ + u32 val; + int ret; + + if (!phy->syscon_phy_power) { + omap_control_phy_power(phy->control_dev, on); + return 0; + } + + if (on) + val = phy->power_on; + else + val = phy->power_off; + + ret = regmap_update_bits(phy->syscon_phy_power, phy->power_reg, + phy->mask, val); + return ret; +} + static int omap_usb_power_off(struct phy *x) { struct omap_usb *phy = phy_get_drvdata(x); - omap_control_phy_power(phy->control_dev, 0); - - return 0; + return omap_usb_phy_power(phy, false); } static int omap_usb_power_on(struct phy *x) { struct omap_usb *phy = phy_get_drvdata(x); - omap_control_phy_power(phy->control_dev, 1); - - return 0; + return omap_usb_phy_power(phy, true); } static int omap_usb_init(struct phy *x) @@ -147,21 +165,38 @@ static const struct phy_ops ops = { static const struct usb_phy_data omap_usb2_data = { .label = "omap_usb2", .flags = OMAP_USB2_HAS_START_SRP | OMAP_USB2_HAS_SET_VBUS, + .mask = OMAP_DEV_PHY_PD, + .power_off = OMAP_DEV_PHY_PD, }; static const struct usb_phy_data omap5_usb2_data = { .label = "omap5_usb2", .flags = 0, + .mask = OMAP_DEV_PHY_PD, + .power_off = OMAP_DEV_PHY_PD, }; static const struct usb_phy_data dra7x_usb2_data = { .label = "dra7x_usb2", .flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT, + .mask = OMAP_DEV_PHY_PD, + .power_off = OMAP_DEV_PHY_PD, +}; + +static const struct usb_phy_data dra7x_usb2_phy2_data = { + .label = "dra7x_usb2_phy2", + .flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT, + .mask = OMAP_USB2_PHY_PD, + .power_off = OMAP_USB2_PHY_PD, }; static const struct usb_phy_data am437x_usb2_data = { .label = "am437x_usb2", .flags = 0, + .mask = AM437X_USB2_PHY_PD | AM437X_USB2_OTG_PD | + AM437X_USB2_OTGVDET_EN | AM437X_USB2_OTGSESSEND_EN, + .power_on = AM437X_USB2_OTGVDET_EN | AM437X_USB2_OTGSESSEND_EN, + .power_off = AM437X_USB2_PHY_PD | AM437X_USB2_OTG_PD, }; static const struct of_device_id omap_usb2_id_table[] = { @@ -177,6 +212,10 @@ static const struct of_device_id omap_usb2_id_table[] = { .compatible = "ti,dra7x-usb2", .data = &dra7x_usb2_data, }, + { + .compatible = "ti,dra7x-usb2-phy2", + .data = &dra7x_usb2_phy2_data, + }, { .compatible = "ti,am437x-usb2", .data = &am437x_usb2_data, @@ -219,6 +258,9 @@ static int omap_usb2_probe(struct platform_device *pdev) phy->phy.label = phy_data->label; phy->phy.otg = otg; phy->phy.type = USB_PHY_TYPE_USB2; + phy->mask = phy_data->mask; + phy->power_on = phy_data->power_on; + phy->power_off = phy_data->power_off; if (phy_data->flags & OMAP_USB2_CALIBRATE_FALSE_DISCONNECT) { res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -228,20 +270,35 @@ static int omap_usb2_probe(struct platform_device *pdev) phy->flags |= OMAP_USB2_CALIBRATE_FALSE_DISCONNECT; } - control_node = of_parse_phandle(node, "ctrl-module", 0); - if (!control_node) { - dev_err(&pdev->dev, "Failed to get control device phandle\n"); - return -EINVAL; - } + phy->syscon_phy_power = syscon_regmap_lookup_by_phandle(node, + "syscon-phy-power"); + if (IS_ERR(phy->syscon_phy_power)) { + dev_dbg(&pdev->dev, + "can't get syscon-phy-power, using control device\n"); + phy->syscon_phy_power = NULL; - control_pdev = of_find_device_by_node(control_node); - if (!control_pdev) { - dev_err(&pdev->dev, "Failed to get control device\n"); - return -EINVAL; - } + control_node = of_parse_phandle(node, "ctrl-module", 0); + if (!control_node) { + dev_err(&pdev->dev, + "Failed to get control device phandle\n"); + return -EINVAL; + } - phy->control_dev = &control_pdev->dev; - omap_control_phy_power(phy->control_dev, 0); + control_pdev = of_find_device_by_node(control_node); + if (!control_pdev) { + dev_err(&pdev->dev, "Failed to get control device\n"); + return -EINVAL; + } + phy->control_dev = &control_pdev->dev; + } else { + if (of_property_read_u32_index(node, + "syscon-phy-power", 1, + &phy->power_reg)) { + dev_err(&pdev->dev, + "couldn't get power reg. offset\n"); + return -EINVAL; + } + } otg->set_host = omap_usb_set_host; otg->set_peripheral = omap_usb_set_peripheral; @@ -261,6 +318,7 @@ static int omap_usb2_probe(struct platform_device *pdev) } phy_set_drvdata(generic_phy, phy); + omap_usb_power_off(generic_phy); phy_provider = devm_of_phy_provider_register(phy->dev, of_phy_simple_xlate); diff --git a/drivers/phy/phy-rcar-gen3-usb2.c b/drivers/phy/phy-rcar-gen3-usb2.c new file mode 100644 index 000000000000..ef332ef4abc7 --- /dev/null +++ b/drivers/phy/phy-rcar-gen3-usb2.c @@ -0,0 +1,378 @@ +/* + * Renesas R-Car Gen3 for USB2.0 PHY driver + * + * Copyright (C) 2015 Renesas Electronics Corporation + * + * This is based on the phy-rcar-gen2 driver: + * Copyright (C) 2014 Renesas Solutions Corp. + * Copyright (C) 2014 Cogent Embedded, Inc. + * + * 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 +#include +#include +#include +#include +#include +#include + +/******* USB2.0 Host registers (original offset is +0x200) *******/ +#define USB2_INT_ENABLE 0x000 +#define USB2_USBCTR 0x00c +#define USB2_SPD_RSM_TIMSET 0x10c +#define USB2_OC_TIMSET 0x110 +#define USB2_COMMCTRL 0x600 +#define USB2_OBINTSTA 0x604 +#define USB2_OBINTEN 0x608 +#define USB2_VBCTRL 0x60c +#define USB2_LINECTRL1 0x610 +#define USB2_ADPCTRL 0x630 + +/* INT_ENABLE */ +#define USB2_INT_ENABLE_UCOM_INTEN BIT(3) +#define USB2_INT_ENABLE_USBH_INTB_EN BIT(2) +#define USB2_INT_ENABLE_USBH_INTA_EN BIT(1) +#define USB2_INT_ENABLE_INIT (USB2_INT_ENABLE_UCOM_INTEN | \ + USB2_INT_ENABLE_USBH_INTB_EN | \ + USB2_INT_ENABLE_USBH_INTA_EN) + +/* USBCTR */ +#define USB2_USBCTR_DIRPD BIT(2) +#define USB2_USBCTR_PLL_RST BIT(1) + +/* SPD_RSM_TIMSET */ +#define USB2_SPD_RSM_TIMSET_INIT 0x014e029b + +/* OC_TIMSET */ +#define USB2_OC_TIMSET_INIT 0x000209ab + +/* COMMCTRL */ +#define USB2_COMMCTRL_OTG_PERI BIT(31) /* 1 = Peripheral mode */ + +/* OBINTSTA and OBINTEN */ +#define USB2_OBINT_SESSVLDCHG BIT(12) +#define USB2_OBINT_IDDIGCHG BIT(11) +#define USB2_OBINT_BITS (USB2_OBINT_SESSVLDCHG | \ + USB2_OBINT_IDDIGCHG) + +/* VBCTRL */ +#define USB2_VBCTRL_DRVVBUSSEL BIT(8) + +/* LINECTRL1 */ +#define USB2_LINECTRL1_DPRPD_EN BIT(19) +#define USB2_LINECTRL1_DP_RPD BIT(18) +#define USB2_LINECTRL1_DMRPD_EN BIT(17) +#define USB2_LINECTRL1_DM_RPD BIT(16) + +/* ADPCTRL */ +#define USB2_ADPCTRL_OTGSESSVLD BIT(20) +#define USB2_ADPCTRL_IDDIG BIT(19) +#define USB2_ADPCTRL_IDPULLUP BIT(5) /* 1 = ID sampling is enabled */ +#define USB2_ADPCTRL_DRVVBUS BIT(4) + +/******* HSUSB registers (original offset is +0x100) *******/ +#define HSUSB_LPSTS 0x02 +#define HSUSB_UGCTRL2 0x84 + +/* Low Power Status register (LPSTS) */ +#define HSUSB_LPSTS_SUSPM 0x4000 + +/* USB General control register 2 (UGCTRL2) */ +#define HSUSB_UGCTRL2_MASK 0x00000031 /* bit[31:6] should be 0 */ +#define HSUSB_UGCTRL2_USB0SEL 0x00000030 +#define HSUSB_UGCTRL2_USB0SEL_HOST 0x00000010 +#define HSUSB_UGCTRL2_USB0SEL_HS_USB 0x00000020 +#define HSUSB_UGCTRL2_USB0SEL_OTG 0x00000030 + +struct rcar_gen3_data { + void __iomem *base; + struct clk *clk; +}; + +struct rcar_gen3_chan { + struct rcar_gen3_data usb2; + struct rcar_gen3_data hsusb; + struct phy *phy; +}; + +static void rcar_gen3_set_host_mode(struct rcar_gen3_chan *ch, int host) +{ + void __iomem *usb2_base = ch->usb2.base; + u32 val = readl(usb2_base + USB2_COMMCTRL); + + dev_vdbg(&ch->phy->dev, "%s: %08x, %d\n", __func__, val, host); + if (host) + val &= ~USB2_COMMCTRL_OTG_PERI; + else + val |= USB2_COMMCTRL_OTG_PERI; + writel(val, usb2_base + USB2_COMMCTRL); +} + +static void rcar_gen3_set_linectrl(struct rcar_gen3_chan *ch, int dp, int dm) +{ + void __iomem *usb2_base = ch->usb2.base; + u32 val = readl(usb2_base + USB2_LINECTRL1); + + dev_vdbg(&ch->phy->dev, "%s: %08x, %d, %d\n", __func__, val, dp, dm); + val &= ~(USB2_LINECTRL1_DP_RPD | USB2_LINECTRL1_DM_RPD); + if (dp) + val |= USB2_LINECTRL1_DP_RPD; + if (dm) + val |= USB2_LINECTRL1_DM_RPD; + writel(val, usb2_base + USB2_LINECTRL1); +} + +static void rcar_gen3_enable_vbus_ctrl(struct rcar_gen3_chan *ch, int vbus) +{ + void __iomem *usb2_base = ch->usb2.base; + u32 val = readl(usb2_base + USB2_ADPCTRL); + + dev_vdbg(&ch->phy->dev, "%s: %08x, %d\n", __func__, val, vbus); + if (vbus) + val |= USB2_ADPCTRL_DRVVBUS; + else + val &= ~USB2_ADPCTRL_DRVVBUS; + writel(val, usb2_base + USB2_ADPCTRL); +} + +static void rcar_gen3_init_for_host(struct rcar_gen3_chan *ch) +{ + rcar_gen3_set_linectrl(ch, 1, 1); + rcar_gen3_set_host_mode(ch, 1); + rcar_gen3_enable_vbus_ctrl(ch, 1); +} + +static void rcar_gen3_init_for_peri(struct rcar_gen3_chan *ch) +{ + rcar_gen3_set_linectrl(ch, 0, 1); + rcar_gen3_set_host_mode(ch, 0); + rcar_gen3_enable_vbus_ctrl(ch, 0); +} + +static bool rcar_gen3_check_vbus(struct rcar_gen3_chan *ch) +{ + return !!(readl(ch->usb2.base + USB2_ADPCTRL) & + USB2_ADPCTRL_OTGSESSVLD); +} + +static bool rcar_gen3_check_id(struct rcar_gen3_chan *ch) +{ + return !!(readl(ch->usb2.base + USB2_ADPCTRL) & USB2_ADPCTRL_IDDIG); +} + +static void rcar_gen3_device_recognition(struct rcar_gen3_chan *ch) +{ + bool is_host = true; + + /* B-device? */ + if (rcar_gen3_check_id(ch) && rcar_gen3_check_vbus(ch)) + is_host = false; + + if (is_host) + rcar_gen3_init_for_host(ch); + else + rcar_gen3_init_for_peri(ch); +} + +static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch) +{ + void __iomem *usb2_base = ch->usb2.base; + u32 val; + + val = readl(usb2_base + USB2_VBCTRL); + writel(val | USB2_VBCTRL_DRVVBUSSEL, usb2_base + USB2_VBCTRL); + writel(USB2_OBINT_BITS, usb2_base + USB2_OBINTSTA); + val = readl(usb2_base + USB2_OBINTEN); + writel(val | USB2_OBINT_BITS, usb2_base + USB2_OBINTEN); + val = readl(usb2_base + USB2_ADPCTRL); + writel(val | USB2_ADPCTRL_IDPULLUP, usb2_base + USB2_ADPCTRL); + val = readl(usb2_base + USB2_LINECTRL1); + rcar_gen3_set_linectrl(ch, 0, 0); + writel(val | USB2_LINECTRL1_DPRPD_EN | USB2_LINECTRL1_DMRPD_EN, + usb2_base + USB2_LINECTRL1); + + rcar_gen3_device_recognition(ch); +} + +static int rcar_gen3_phy_usb2_init(struct phy *p) +{ + struct rcar_gen3_chan *channel = phy_get_drvdata(p); + void __iomem *usb2_base = channel->usb2.base; + void __iomem *hsusb_base = channel->hsusb.base; + u32 val; + + /* Initialize USB2 part */ + writel(USB2_INT_ENABLE_INIT, usb2_base + USB2_INT_ENABLE); + writel(USB2_SPD_RSM_TIMSET_INIT, usb2_base + USB2_SPD_RSM_TIMSET); + writel(USB2_OC_TIMSET_INIT, usb2_base + USB2_OC_TIMSET); + + /* Initialize HSUSB part */ + if (hsusb_base) { + val = readl(hsusb_base + HSUSB_UGCTRL2); + val = (val & ~HSUSB_UGCTRL2_USB0SEL) | + HSUSB_UGCTRL2_USB0SEL_OTG; + writel(val & HSUSB_UGCTRL2_MASK, hsusb_base + HSUSB_UGCTRL2); + + /* Initialize otg part */ + rcar_gen3_init_otg(channel); + } + + return 0; +} + +static int rcar_gen3_phy_usb2_exit(struct phy *p) +{ + struct rcar_gen3_chan *channel = phy_get_drvdata(p); + + writel(0, channel->usb2.base + USB2_INT_ENABLE); + + return 0; +} + +static int rcar_gen3_phy_usb2_power_on(struct phy *p) +{ + struct rcar_gen3_chan *channel = phy_get_drvdata(p); + void __iomem *usb2_base = channel->usb2.base; + void __iomem *hsusb_base = channel->hsusb.base; + u32 val; + + val = readl(usb2_base + USB2_USBCTR); + val |= USB2_USBCTR_PLL_RST; + writel(val, usb2_base + USB2_USBCTR); + val &= ~USB2_USBCTR_PLL_RST; + writel(val, usb2_base + USB2_USBCTR); + + /* + * TODO: To reduce power consuming, this driver should set the SUSPM + * after the PHY detects ID pin as peripheral. + */ + if (hsusb_base) { + /* Power on HSUSB PHY */ + val = readw(hsusb_base + HSUSB_LPSTS); + val |= HSUSB_LPSTS_SUSPM; + writew(val, hsusb_base + HSUSB_LPSTS); + } + + return 0; +} + +static int rcar_gen3_phy_usb2_power_off(struct phy *p) +{ + struct rcar_gen3_chan *channel = phy_get_drvdata(p); + void __iomem *hsusb_base = channel->hsusb.base; + u32 val; + + if (hsusb_base) { + /* Power off HSUSB PHY */ + val = readw(hsusb_base + HSUSB_LPSTS); + val &= ~HSUSB_LPSTS_SUSPM; + writew(val, hsusb_base + HSUSB_LPSTS); + } + + return 0; +} + +static struct phy_ops rcar_gen3_phy_usb2_ops = { + .init = rcar_gen3_phy_usb2_init, + .exit = rcar_gen3_phy_usb2_exit, + .power_on = rcar_gen3_phy_usb2_power_on, + .power_off = rcar_gen3_phy_usb2_power_off, + .owner = THIS_MODULE, +}; + +static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch) +{ + struct rcar_gen3_chan *ch = _ch; + void __iomem *usb2_base = ch->usb2.base; + u32 status = readl(usb2_base + USB2_OBINTSTA); + irqreturn_t ret = IRQ_NONE; + + if (status & USB2_OBINT_BITS) { + dev_vdbg(&ch->phy->dev, "%s: %08x\n", __func__, status); + writel(USB2_OBINT_BITS, usb2_base + USB2_OBINTSTA); + rcar_gen3_device_recognition(ch); + ret = IRQ_HANDLED; + } + + return ret; +} + +static const struct of_device_id rcar_gen3_phy_usb2_match_table[] = { + { .compatible = "renesas,usb2-phy-r8a7795" }, + { } +}; +MODULE_DEVICE_TABLE(of, rcar_gen3_phy_usb2_match_table); + +static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rcar_gen3_chan *channel; + struct phy_provider *provider; + struct resource *res; + + if (!dev->of_node) { + dev_err(dev, "This driver needs device tree\n"); + return -EINVAL; + } + + channel = devm_kzalloc(dev, sizeof(*channel), GFP_KERNEL); + if (!channel) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2_host"); + channel->usb2.base = devm_ioremap_resource(dev, res); + if (IS_ERR(channel->usb2.base)) + return PTR_ERR(channel->usb2.base); + + /* "hsusb" memory resource is optional */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hsusb"); + + /* To avoid error message by devm_ioremap_resource() */ + if (res) { + int irq; + + channel->hsusb.base = devm_ioremap_resource(dev, res); + if (IS_ERR(channel->hsusb.base)) + channel->hsusb.base = NULL; + /* call request_irq for OTG */ + irq = platform_get_irq(pdev, 0); + if (irq >= 0) + irq = devm_request_irq(dev, irq, rcar_gen3_phy_usb2_irq, + IRQF_SHARED, dev_name(dev), + channel); + if (irq < 0) + dev_err(dev, "No irq handler (%d)\n", irq); + } + + /* devm_phy_create() will call pm_runtime_enable(dev); */ + channel->phy = devm_phy_create(dev, NULL, &rcar_gen3_phy_usb2_ops); + if (IS_ERR(channel->phy)) { + dev_err(dev, "Failed to create USB2 PHY\n"); + return PTR_ERR(channel->phy); + } + + phy_set_drvdata(channel->phy, channel); + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(provider)) + dev_err(dev, "Failed to register PHY provider\n"); + + return PTR_ERR_OR_ZERO(provider); +} + +static struct platform_driver rcar_gen3_phy_usb2_driver = { + .driver = { + .name = "phy_rcar_gen3_usb2", + .of_match_table = rcar_gen3_phy_usb2_match_table, + }, + .probe = rcar_gen3_phy_usb2_probe, +}; +module_platform_driver(rcar_gen3_phy_usb2_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Renesas R-Car Gen3 USB 2.0 PHY"); +MODULE_AUTHOR("Yoshihiro Shimoda "); diff --git a/drivers/phy/phy-rockchip-usb.c b/drivers/phy/phy-rockchip-usb.c index 62c43c435194..33a80eba1cb4 100644 --- a/drivers/phy/phy-rockchip-usb.c +++ b/drivers/phy/phy-rockchip-usb.c @@ -15,12 +15,14 @@ */ #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -36,31 +38,91 @@ #define SIDDQ_ON BIT(13) #define SIDDQ_OFF (0 << 13) +struct rockchip_usb_phys { + int reg; + const char *pll_name; +}; + +struct rockchip_usb_phy_pdata { + struct rockchip_usb_phys *phys; +}; + +struct rockchip_usb_phy_base { + struct device *dev; + struct regmap *reg_base; + const struct rockchip_usb_phy_pdata *pdata; +}; + struct rockchip_usb_phy { + struct rockchip_usb_phy_base *base; + struct device_node *np; unsigned int reg_offset; - struct regmap *reg_base; struct clk *clk; + struct clk *clk480m; + struct clk_hw clk480m_hw; struct phy *phy; }; static int rockchip_usb_phy_power(struct rockchip_usb_phy *phy, bool siddq) { - return regmap_write(phy->reg_base, phy->reg_offset, + return regmap_write(phy->base->reg_base, phy->reg_offset, SIDDQ_WRITE_ENA | (siddq ? SIDDQ_ON : SIDDQ_OFF)); } +static unsigned long rockchip_usb_phy480m_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return 480000000; +} + +static void rockchip_usb_phy480m_disable(struct clk_hw *hw) +{ + struct rockchip_usb_phy *phy = container_of(hw, + struct rockchip_usb_phy, + clk480m_hw); + + /* Power down usb phy analog blocks by set siddq 1 */ + rockchip_usb_phy_power(phy, 1); +} + +static int rockchip_usb_phy480m_enable(struct clk_hw *hw) +{ + struct rockchip_usb_phy *phy = container_of(hw, + struct rockchip_usb_phy, + clk480m_hw); + + /* Power up usb phy analog blocks by set siddq 0 */ + return rockchip_usb_phy_power(phy, 0); +} + +static int rockchip_usb_phy480m_is_enabled(struct clk_hw *hw) +{ + struct rockchip_usb_phy *phy = container_of(hw, + struct rockchip_usb_phy, + clk480m_hw); + int ret; + u32 val; + + ret = regmap_read(phy->base->reg_base, phy->reg_offset, &val); + if (ret < 0) + return ret; + + return (val & SIDDQ_ON) ? 0 : 1; +} + +static const struct clk_ops rockchip_usb_phy480m_ops = { + .enable = rockchip_usb_phy480m_enable, + .disable = rockchip_usb_phy480m_disable, + .is_enabled = rockchip_usb_phy480m_is_enabled, + .recalc_rate = rockchip_usb_phy480m_recalc_rate, +}; + static int rockchip_usb_phy_power_off(struct phy *_phy) { struct rockchip_usb_phy *phy = phy_get_drvdata(_phy); - int ret = 0; - /* Power down usb phy analog blocks by set siddq 1 */ - ret = rockchip_usb_phy_power(phy, 1); - if (ret) - return ret; - - clk_disable_unprepare(phy->clk); + clk_disable_unprepare(phy->clk480m); return 0; } @@ -68,20 +130,8 @@ static int rockchip_usb_phy_power_off(struct phy *_phy) static int rockchip_usb_phy_power_on(struct phy *_phy) { struct rockchip_usb_phy *phy = phy_get_drvdata(_phy); - int ret = 0; - ret = clk_prepare_enable(phy->clk); - if (ret) - return ret; - - /* Power up usb phy analog blocks by set siddq 0 */ - ret = rockchip_usb_phy_power(phy, 0); - if (ret) { - clk_disable_unprepare(phy->clk); - return ret; - } - - return 0; + return clk_prepare_enable(phy->clk480m); } static const struct phy_ops ops = { @@ -90,66 +140,179 @@ static const struct phy_ops ops = { .owner = THIS_MODULE, }; +static void rockchip_usb_phy_action(void *data) +{ + struct rockchip_usb_phy *rk_phy = data; + + of_clk_del_provider(rk_phy->np); + clk_unregister(rk_phy->clk480m); + + if (rk_phy->clk) + clk_put(rk_phy->clk); +} + +static int rockchip_usb_phy_init(struct rockchip_usb_phy_base *base, + struct device_node *child) +{ + struct rockchip_usb_phy *rk_phy; + unsigned int reg_offset; + const char *clk_name; + struct clk_init_data init; + int err, i; + + rk_phy = devm_kzalloc(base->dev, sizeof(*rk_phy), GFP_KERNEL); + if (!rk_phy) + return -ENOMEM; + + rk_phy->base = base; + rk_phy->np = child; + + if (of_property_read_u32(child, "reg", ®_offset)) { + dev_err(base->dev, "missing reg property in node %s\n", + child->name); + return -EINVAL; + } + + rk_phy->reg_offset = reg_offset; + + rk_phy->clk = of_clk_get_by_name(child, "phyclk"); + if (IS_ERR(rk_phy->clk)) + rk_phy->clk = NULL; + + i = 0; + init.name = NULL; + while (base->pdata->phys[i].reg) { + if (base->pdata->phys[i].reg == reg_offset) { + init.name = base->pdata->phys[i].pll_name; + break; + } + i++; + } + + if (!init.name) { + dev_err(base->dev, "phy data not found\n"); + return -EINVAL; + } + + if (rk_phy->clk) { + clk_name = __clk_get_name(rk_phy->clk); + init.flags = 0; + init.parent_names = &clk_name; + init.num_parents = 1; + } else { + init.flags = CLK_IS_ROOT; + init.parent_names = NULL; + init.num_parents = 0; + } + + init.ops = &rockchip_usb_phy480m_ops; + rk_phy->clk480m_hw.init = &init; + + rk_phy->clk480m = clk_register(base->dev, &rk_phy->clk480m_hw); + if (IS_ERR(rk_phy->clk480m)) { + err = PTR_ERR(rk_phy->clk480m); + goto err_clk; + } + + err = of_clk_add_provider(child, of_clk_src_simple_get, + rk_phy->clk480m); + if (err < 0) + goto err_clk_prov; + + err = devm_add_action(base->dev, rockchip_usb_phy_action, rk_phy); + if (err) + goto err_devm_action; + + rk_phy->phy = devm_phy_create(base->dev, child, &ops); + if (IS_ERR(rk_phy->phy)) { + dev_err(base->dev, "failed to create PHY\n"); + return PTR_ERR(rk_phy->phy); + } + phy_set_drvdata(rk_phy->phy, rk_phy); + + /* only power up usb phy when it use, so disable it when init*/ + return rockchip_usb_phy_power(rk_phy, 1); + +err_devm_action: + of_clk_del_provider(child); +err_clk_prov: + clk_unregister(rk_phy->clk480m); +err_clk: + if (rk_phy->clk) + clk_put(rk_phy->clk); + return err; +} + +static const struct rockchip_usb_phy_pdata rk3066a_pdata = { + .phys = (struct rockchip_usb_phys[]){ + { .reg = 0x17c, .pll_name = "sclk_otgphy0_480m" }, + { .reg = 0x188, .pll_name = "sclk_otgphy1_480m" }, + { /* sentinel */ } + }, +}; + +static const struct rockchip_usb_phy_pdata rk3188_pdata = { + .phys = (struct rockchip_usb_phys[]){ + { .reg = 0x10c, .pll_name = "sclk_otgphy0_480m" }, + { .reg = 0x11c, .pll_name = "sclk_otgphy1_480m" }, + { /* sentinel */ } + }, +}; + +static const struct rockchip_usb_phy_pdata rk3288_pdata = { + .phys = (struct rockchip_usb_phys[]){ + { .reg = 0x320, .pll_name = "sclk_otgphy0_480m" }, + { .reg = 0x334, .pll_name = "sclk_otgphy1_480m" }, + { .reg = 0x348, .pll_name = "sclk_otgphy2_480m" }, + { /* sentinel */ } + }, +}; + static int rockchip_usb_phy_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct rockchip_usb_phy *rk_phy; + struct rockchip_usb_phy_base *phy_base; struct phy_provider *phy_provider; + const struct of_device_id *match; struct device_node *child; - struct regmap *grf; - unsigned int reg_offset; int err; - grf = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,grf"); - if (IS_ERR(grf)) { + phy_base = devm_kzalloc(dev, sizeof(*phy_base), GFP_KERNEL); + if (!phy_base) + return -ENOMEM; + + match = of_match_device(dev->driver->of_match_table, dev); + if (!match || !match->data) { + dev_err(dev, "missing phy data\n"); + return -EINVAL; + } + + phy_base->pdata = match->data; + + phy_base->dev = dev; + phy_base->reg_base = syscon_regmap_lookup_by_phandle(dev->of_node, + "rockchip,grf"); + if (IS_ERR(phy_base->reg_base)) { dev_err(&pdev->dev, "Missing rockchip,grf property\n"); - return PTR_ERR(grf); + return PTR_ERR(phy_base->reg_base); } for_each_available_child_of_node(dev->of_node, child) { - rk_phy = devm_kzalloc(dev, sizeof(*rk_phy), GFP_KERNEL); - if (!rk_phy) { - err = -ENOMEM; - goto put_child; + err = rockchip_usb_phy_init(phy_base, child); + if (err) { + of_node_put(child); + return err; } - - if (of_property_read_u32(child, "reg", ®_offset)) { - dev_err(dev, "missing reg property in node %s\n", - child->name); - err = -EINVAL; - goto put_child; - } - - rk_phy->reg_offset = reg_offset; - rk_phy->reg_base = grf; - - rk_phy->clk = of_clk_get_by_name(child, "phyclk"); - if (IS_ERR(rk_phy->clk)) - rk_phy->clk = NULL; - - rk_phy->phy = devm_phy_create(dev, child, &ops); - if (IS_ERR(rk_phy->phy)) { - dev_err(dev, "failed to create PHY\n"); - err = PTR_ERR(rk_phy->phy); - goto put_child; - } - phy_set_drvdata(rk_phy->phy, rk_phy); - - /* only power up usb phy when it use, so disable it when init*/ - err = rockchip_usb_phy_power(rk_phy, 1); - if (err) - goto put_child; } phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); return PTR_ERR_OR_ZERO(phy_provider); -put_child: - of_node_put(child); - return err; } static const struct of_device_id rockchip_usb_phy_dt_ids[] = { - { .compatible = "rockchip,rk3288-usb-phy" }, + { .compatible = "rockchip,rk3066a-usb-phy", .data = &rk3066a_pdata }, + { .compatible = "rockchip,rk3188-usb-phy", .data = &rk3188_pdata }, + { .compatible = "rockchip,rk3288-usb-phy", .data = &rk3288_pdata }, {} }; diff --git a/drivers/phy/phy-sun4i-usb.c b/drivers/phy/phy-sun4i-usb.c index b12964b70625..bae54f7a1f48 100644 --- a/drivers/phy/phy-sun4i-usb.c +++ b/drivers/phy/phy-sun4i-usb.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,9 @@ #define REG_PHYBIST 0x08 #define REG_PHYTUNE 0x0c #define REG_PHYCTL_A33 0x10 +#define REG_PHY_UNK_H3 0x20 + +#define REG_PMU_UNK_H3 0x10 #define PHYCTL_DATA BIT(7) @@ -79,7 +83,7 @@ #define PHY_DISCON_TH_SEL 0x2a #define PHY_SQUELCH_DETECT 0x3c -#define MAX_PHYS 3 +#define MAX_PHYS 4 /* * Note do not raise the debounce time, we must report Vusb high within 100ms @@ -88,12 +92,24 @@ #define DEBOUNCE_TIME msecs_to_jiffies(50) #define POLL_TIME msecs_to_jiffies(250) +enum sun4i_usb_phy_type { + sun4i_a10_phy, + sun8i_a33_phy, + sun8i_h3_phy, +}; + +struct sun4i_usb_phy_cfg { + int num_phys; + enum sun4i_usb_phy_type type; + u32 disc_thresh; + u8 phyctl_offset; + bool dedicated_clocks; +}; + struct sun4i_usb_phy_data { void __iomem *base; + const struct sun4i_usb_phy_cfg *cfg; struct mutex mutex; - int num_phys; - u32 disc_thresh; - bool has_a33_phyctl; struct sun4i_usb_phy { struct phy *phy; void __iomem *pmu; @@ -159,17 +175,14 @@ static void sun4i_usb_phy_write(struct sun4i_usb_phy *phy, u32 addr, u32 data, { struct sun4i_usb_phy_data *phy_data = to_sun4i_usb_phy_data(phy); u32 temp, usbc_bit = BIT(phy->index * 2); - void *phyctl; + void *phyctl = phy_data->base + phy_data->cfg->phyctl_offset; int i; mutex_lock(&phy_data->mutex); - if (phy_data->has_a33_phyctl) { - phyctl = phy_data->base + REG_PHYCTL_A33; + if (phy_data->cfg->type == sun8i_a33_phy) { /* A33 needs us to set phyctl to 0 explicitly */ writel(0, phyctl); - } else { - phyctl = phy_data->base + REG_PHYCTL_A10; } for (i = 0; i < len; i++) { @@ -230,6 +243,7 @@ static int sun4i_usb_phy_init(struct phy *_phy) struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy); int ret; + u32 val; ret = clk_prepare_enable(phy->clk); if (ret) @@ -241,15 +255,26 @@ static int sun4i_usb_phy_init(struct phy *_phy) return ret; } - /* Enable USB 45 Ohm resistor calibration */ - if (phy->index == 0) - sun4i_usb_phy_write(phy, PHY_RES45_CAL_EN, 0x01, 1); + if (data->cfg->type == sun8i_h3_phy) { + if (phy->index == 0) { + val = readl(data->base + REG_PHY_UNK_H3); + writel(val & ~1, data->base + REG_PHY_UNK_H3); + } - /* Adjust PHY's magnitude and rate */ - sun4i_usb_phy_write(phy, PHY_TX_AMPLITUDE_TUNE, 0x14, 5); + val = readl(phy->pmu + REG_PMU_UNK_H3); + writel(val & ~2, phy->pmu + REG_PMU_UNK_H3); + } else { + /* Enable USB 45 Ohm resistor calibration */ + if (phy->index == 0) + sun4i_usb_phy_write(phy, PHY_RES45_CAL_EN, 0x01, 1); - /* Disconnect threshold adjustment */ - sun4i_usb_phy_write(phy, PHY_DISCON_TH_SEL, data->disc_thresh, 2); + /* Adjust PHY's magnitude and rate */ + sun4i_usb_phy_write(phy, PHY_TX_AMPLITUDE_TUNE, 0x14, 5); + + /* Disconnect threshold adjustment */ + sun4i_usb_phy_write(phy, PHY_DISCON_TH_SEL, + data->cfg->disc_thresh, 2); + } sun4i_usb_phy_passby(phy, 1); @@ -476,7 +501,7 @@ static struct phy *sun4i_usb_phy_xlate(struct device *dev, { struct sun4i_usb_phy_data *data = dev_get_drvdata(dev); - if (args->args[0] >= data->num_phys) + if (args->args[0] >= data->cfg->num_phys) return ERR_PTR(-ENODEV); return data->phys[args->args[0]].phy; @@ -511,7 +536,6 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; struct phy_provider *phy_provider; - bool dedicated_clocks; struct resource *res; int i, ret; @@ -522,29 +546,9 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev) mutex_init(&data->mutex); INIT_DELAYED_WORK(&data->detect, sun4i_usb_phy0_id_vbus_det_scan); dev_set_drvdata(dev, data); - - if (of_device_is_compatible(np, "allwinner,sun5i-a13-usb-phy") || - of_device_is_compatible(np, "allwinner,sun8i-a23-usb-phy") || - of_device_is_compatible(np, "allwinner,sun8i-a33-usb-phy")) - data->num_phys = 2; - else - data->num_phys = 3; - - if (of_device_is_compatible(np, "allwinner,sun5i-a13-usb-phy") || - of_device_is_compatible(np, "allwinner,sun7i-a20-usb-phy")) - data->disc_thresh = 2; - else - data->disc_thresh = 3; - - if (of_device_is_compatible(np, "allwinner,sun6i-a31-usb-phy") || - of_device_is_compatible(np, "allwinner,sun8i-a23-usb-phy") || - of_device_is_compatible(np, "allwinner,sun8i-a33-usb-phy")) - dedicated_clocks = true; - else - dedicated_clocks = false; - - if (of_device_is_compatible(np, "allwinner,sun8i-a33-usb-phy")) - data->has_a33_phyctl = true; + data->cfg = of_device_get_match_data(dev); + if (!data->cfg) + return -EINVAL; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_ctrl"); data->base = devm_ioremap_resource(dev, res); @@ -590,7 +594,7 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev) } } - for (i = 0; i < data->num_phys; i++) { + for (i = 0; i < data->cfg->num_phys; i++) { struct sun4i_usb_phy *phy = data->phys + i; char name[16]; @@ -602,7 +606,7 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev) phy->vbus = NULL; } - if (dedicated_clocks) + if (data->cfg->dedicated_clocks) snprintf(name, sizeof(name), "usb%d_phy", i); else strlcpy(name, "usb_phy", sizeof(name)); @@ -689,13 +693,69 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev) return 0; } +static const struct sun4i_usb_phy_cfg sun4i_a10_cfg = { + .num_phys = 3, + .type = sun4i_a10_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A10, + .dedicated_clocks = false, +}; + +static const struct sun4i_usb_phy_cfg sun5i_a13_cfg = { + .num_phys = 2, + .type = sun4i_a10_phy, + .disc_thresh = 2, + .phyctl_offset = REG_PHYCTL_A10, + .dedicated_clocks = false, +}; + +static const struct sun4i_usb_phy_cfg sun6i_a31_cfg = { + .num_phys = 3, + .type = sun4i_a10_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A10, + .dedicated_clocks = true, +}; + +static const struct sun4i_usb_phy_cfg sun7i_a20_cfg = { + .num_phys = 3, + .type = sun4i_a10_phy, + .disc_thresh = 2, + .phyctl_offset = REG_PHYCTL_A10, + .dedicated_clocks = false, +}; + +static const struct sun4i_usb_phy_cfg sun8i_a23_cfg = { + .num_phys = 2, + .type = sun4i_a10_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A10, + .dedicated_clocks = true, +}; + +static const struct sun4i_usb_phy_cfg sun8i_a33_cfg = { + .num_phys = 2, + .type = sun8i_a33_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A33, + .dedicated_clocks = true, +}; + +static const struct sun4i_usb_phy_cfg sun8i_h3_cfg = { + .num_phys = 4, + .type = sun8i_h3_phy, + .disc_thresh = 3, + .dedicated_clocks = true, +}; + static const struct of_device_id sun4i_usb_phy_of_match[] = { - { .compatible = "allwinner,sun4i-a10-usb-phy" }, - { .compatible = "allwinner,sun5i-a13-usb-phy" }, - { .compatible = "allwinner,sun6i-a31-usb-phy" }, - { .compatible = "allwinner,sun7i-a20-usb-phy" }, - { .compatible = "allwinner,sun8i-a23-usb-phy" }, - { .compatible = "allwinner,sun8i-a33-usb-phy" }, + { .compatible = "allwinner,sun4i-a10-usb-phy", .data = &sun4i_a10_cfg }, + { .compatible = "allwinner,sun5i-a13-usb-phy", .data = &sun5i_a13_cfg }, + { .compatible = "allwinner,sun6i-a31-usb-phy", .data = &sun6i_a31_cfg }, + { .compatible = "allwinner,sun7i-a20-usb-phy", .data = &sun7i_a20_cfg }, + { .compatible = "allwinner,sun8i-a23-usb-phy", .data = &sun8i_a23_cfg }, + { .compatible = "allwinner,sun8i-a33-usb-phy", .data = &sun8i_a33_cfg }, + { .compatible = "allwinner,sun8i-h3-usb-phy", .data = &sun8i_h3_cfg }, { }, }; MODULE_DEVICE_TABLE(of, sun4i_usb_phy_of_match); diff --git a/drivers/phy/phy-ti-pipe3.c b/drivers/phy/phy-ti-pipe3.c index 93bc1120af12..0a477d24cf76 100644 --- a/drivers/phy/phy-ti-pipe3.c +++ b/drivers/phy/phy-ti-pipe3.c @@ -56,6 +56,18 @@ #define SATA_PLL_SOFT_RESET BIT(18) +#define PIPE3_PHY_PWRCTL_CLK_CMD_MASK 0x003FC000 +#define PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT 14 + +#define PIPE3_PHY_PWRCTL_CLK_FREQ_MASK 0xFFC00000 +#define PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT 22 + +#define PIPE3_PHY_TX_RX_POWERON 0x3 +#define PIPE3_PHY_TX_RX_POWEROFF 0x0 + +#define PCIE_PCS_MASK 0xFF0000 +#define PCIE_PCS_DELAY_COUNT_SHIFT 0x10 + /* * This is an Empirical value that works, need to confirm the actual * value required for the PIPE3PHY_PLL_CONFIGURATION2.PLL_IDLE status @@ -86,8 +98,12 @@ struct ti_pipe3 { struct clk *refclk; struct clk *div_clk; struct pipe3_dpll_map *dpll_map; + struct regmap *phy_power_syscon; /* ctrl. reg. acces */ + struct regmap *pcs_syscon; /* ctrl. reg. acces */ struct regmap *dpll_reset_syscon; /* ctrl. reg. acces */ unsigned int dpll_reset_reg; /* reg. index within syscon */ + unsigned int power_reg; /* power reg. index within syscon */ + unsigned int pcie_pcs_reg; /* pcs reg. index in syscon */ bool sata_refclk_enabled; }; @@ -144,20 +160,49 @@ static void ti_pipe3_disable_clocks(struct ti_pipe3 *phy); static int ti_pipe3_power_off(struct phy *x) { + u32 val; + int ret; struct ti_pipe3 *phy = phy_get_drvdata(x); - omap_control_phy_power(phy->control_dev, 0); + if (!phy->phy_power_syscon) { + omap_control_phy_power(phy->control_dev, 0); + return 0; + } - return 0; + val = PIPE3_PHY_TX_RX_POWEROFF << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT; + + ret = regmap_update_bits(phy->phy_power_syscon, phy->power_reg, + PIPE3_PHY_PWRCTL_CLK_CMD_MASK, val); + return ret; } static int ti_pipe3_power_on(struct phy *x) { + u32 val; + u32 mask; + int ret; + unsigned long rate; struct ti_pipe3 *phy = phy_get_drvdata(x); - omap_control_phy_power(phy->control_dev, 1); + if (!phy->phy_power_syscon) { + omap_control_phy_power(phy->control_dev, 1); + return 0; + } - return 0; + rate = clk_get_rate(phy->sys_clk); + if (!rate) { + dev_err(phy->dev, "Invalid clock rate\n"); + return -EINVAL; + } + rate = rate / 1000000; + mask = OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK | + OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_MASK; + val = PIPE3_PHY_TX_RX_POWERON << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT; + val |= rate << OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT; + + ret = regmap_update_bits(phy->phy_power_syscon, phy->power_reg, + mask, val); + return ret; } static int ti_pipe3_dpll_wait_lock(struct ti_pipe3 *phy) @@ -229,8 +274,15 @@ static int ti_pipe3_init(struct phy *x) * 18-1804. */ if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-pcie")) { - omap_control_pcie_pcs(phy->control_dev, 0x96); - return 0; + if (!phy->pcs_syscon) { + omap_control_pcie_pcs(phy->control_dev, 0x96); + return 0; + } + + val = 0x96 << OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT; + ret = regmap_update_bits(phy->pcs_syscon, phy->pcie_pcs_reg, + PCIE_PCS_MASK, val); + return ret; } /* Bring it out of IDLE if it is IDLE */ @@ -308,51 +360,15 @@ static const struct phy_ops ops = { static const struct of_device_id ti_pipe3_id_table[]; -static int ti_pipe3_probe(struct platform_device *pdev) +static int ti_pipe3_get_clk(struct ti_pipe3 *phy) { - struct ti_pipe3 *phy; - struct phy *generic_phy; - struct phy_provider *phy_provider; - struct resource *res; - struct device_node *node = pdev->dev.of_node; - struct device_node *control_node; - struct platform_device *control_pdev; - const struct of_device_id *match; struct clk *clk; + struct device *dev = phy->dev; + struct device_node *node = dev->of_node; - phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL); - if (!phy) - return -ENOMEM; - - phy->dev = &pdev->dev; - - if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie")) { - match = of_match_device(ti_pipe3_id_table, &pdev->dev); - if (!match) - return -EINVAL; - - phy->dpll_map = (struct pipe3_dpll_map *)match->data; - if (!phy->dpll_map) { - dev_err(&pdev->dev, "no DPLL data\n"); - return -EINVAL; - } - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - "pll_ctrl"); - phy->pll_ctrl_base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(phy->pll_ctrl_base)) - return PTR_ERR(phy->pll_ctrl_base); - - phy->sys_clk = devm_clk_get(phy->dev, "sysclk"); - if (IS_ERR(phy->sys_clk)) { - dev_err(&pdev->dev, "unable to get sysclk\n"); - return -EINVAL; - } - } - - phy->refclk = devm_clk_get(phy->dev, "refclk"); + phy->refclk = devm_clk_get(dev, "refclk"); if (IS_ERR(phy->refclk)) { - dev_err(&pdev->dev, "unable to get refclk\n"); + dev_err(dev, "unable to get refclk\n"); /* older DTBs have missing refclk in SATA PHY * so don't bail out in case of SATA PHY. */ @@ -361,80 +377,194 @@ static int ti_pipe3_probe(struct platform_device *pdev) } if (!of_device_is_compatible(node, "ti,phy-pipe3-sata")) { - phy->wkupclk = devm_clk_get(phy->dev, "wkupclk"); + phy->wkupclk = devm_clk_get(dev, "wkupclk"); if (IS_ERR(phy->wkupclk)) { - dev_err(&pdev->dev, "unable to get wkupclk\n"); + dev_err(dev, "unable to get wkupclk\n"); return PTR_ERR(phy->wkupclk); } } else { phy->wkupclk = ERR_PTR(-ENODEV); - phy->dpll_reset_syscon = syscon_regmap_lookup_by_phandle(node, - "syscon-pllreset"); - if (IS_ERR(phy->dpll_reset_syscon)) { - dev_info(&pdev->dev, - "can't get syscon-pllreset, sata dpll won't idle\n"); - phy->dpll_reset_syscon = NULL; - } else { - if (of_property_read_u32_index(node, - "syscon-pllreset", 1, - &phy->dpll_reset_reg)) { - dev_err(&pdev->dev, - "couldn't get pllreset reg. offset\n"); - return -EINVAL; - } + } + + if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie") || + phy->phy_power_syscon) { + phy->sys_clk = devm_clk_get(dev, "sysclk"); + if (IS_ERR(phy->sys_clk)) { + dev_err(dev, "unable to get sysclk\n"); + return -EINVAL; } } if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) { - - clk = devm_clk_get(phy->dev, "dpll_ref"); + clk = devm_clk_get(dev, "dpll_ref"); if (IS_ERR(clk)) { - dev_err(&pdev->dev, "unable to get dpll ref clk\n"); + dev_err(dev, "unable to get dpll ref clk\n"); return PTR_ERR(clk); } clk_set_rate(clk, 1500000000); - clk = devm_clk_get(phy->dev, "dpll_ref_m2"); + clk = devm_clk_get(dev, "dpll_ref_m2"); if (IS_ERR(clk)) { - dev_err(&pdev->dev, "unable to get dpll ref m2 clk\n"); + dev_err(dev, "unable to get dpll ref m2 clk\n"); return PTR_ERR(clk); } clk_set_rate(clk, 100000000); - clk = devm_clk_get(phy->dev, "phy-div"); + clk = devm_clk_get(dev, "phy-div"); if (IS_ERR(clk)) { - dev_err(&pdev->dev, "unable to get phy-div clk\n"); + dev_err(dev, "unable to get phy-div clk\n"); return PTR_ERR(clk); } clk_set_rate(clk, 100000000); - phy->div_clk = devm_clk_get(phy->dev, "div-clk"); + phy->div_clk = devm_clk_get(dev, "div-clk"); if (IS_ERR(phy->div_clk)) { - dev_err(&pdev->dev, "unable to get div-clk\n"); + dev_err(dev, "unable to get div-clk\n"); return PTR_ERR(phy->div_clk); } } else { phy->div_clk = ERR_PTR(-ENODEV); } - control_node = of_parse_phandle(node, "ctrl-module", 0); - if (!control_node) { - dev_err(&pdev->dev, "Failed to get control device phandle\n"); + return 0; +} + +static int ti_pipe3_get_sysctrl(struct ti_pipe3 *phy) +{ + struct device *dev = phy->dev; + struct device_node *node = dev->of_node; + struct device_node *control_node; + struct platform_device *control_pdev; + + phy->phy_power_syscon = syscon_regmap_lookup_by_phandle(node, + "syscon-phy-power"); + if (IS_ERR(phy->phy_power_syscon)) { + dev_dbg(dev, + "can't get syscon-phy-power, using control device\n"); + phy->phy_power_syscon = NULL; + } else { + if (of_property_read_u32_index(node, + "syscon-phy-power", 1, + &phy->power_reg)) { + dev_err(dev, "couldn't get power reg. offset\n"); + return -EINVAL; + } + } + + if (!phy->phy_power_syscon) { + control_node = of_parse_phandle(node, "ctrl-module", 0); + if (!control_node) { + dev_err(dev, "Failed to get control device phandle\n"); + return -EINVAL; + } + + control_pdev = of_find_device_by_node(control_node); + if (!control_pdev) { + dev_err(dev, "Failed to get control device\n"); + return -EINVAL; + } + + phy->control_dev = &control_pdev->dev; + } + + if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) { + phy->pcs_syscon = syscon_regmap_lookup_by_phandle(node, + "syscon-pcs"); + if (IS_ERR(phy->pcs_syscon)) { + dev_dbg(dev, + "can't get syscon-pcs, using omap control\n"); + phy->pcs_syscon = NULL; + } else { + if (of_property_read_u32_index(node, + "syscon-pcs", 1, + &phy->pcie_pcs_reg)) { + dev_err(dev, + "couldn't get pcie pcs reg. offset\n"); + return -EINVAL; + } + } + } + + if (of_device_is_compatible(node, "ti,phy-pipe3-sata")) { + phy->dpll_reset_syscon = syscon_regmap_lookup_by_phandle(node, + "syscon-pllreset"); + if (IS_ERR(phy->dpll_reset_syscon)) { + dev_info(dev, + "can't get syscon-pllreset, sata dpll won't idle\n"); + phy->dpll_reset_syscon = NULL; + } else { + if (of_property_read_u32_index(node, + "syscon-pllreset", 1, + &phy->dpll_reset_reg)) { + dev_err(dev, + "couldn't get pllreset reg. offset\n"); + return -EINVAL; + } + } + } + + return 0; +} + +static int ti_pipe3_get_pll_base(struct ti_pipe3 *phy) +{ + struct resource *res; + const struct of_device_id *match; + struct device *dev = phy->dev; + struct device_node *node = dev->of_node; + struct platform_device *pdev = to_platform_device(dev); + + if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) + return 0; + + match = of_match_device(ti_pipe3_id_table, dev); + if (!match) + return -EINVAL; + + phy->dpll_map = (struct pipe3_dpll_map *)match->data; + if (!phy->dpll_map) { + dev_err(dev, "no DPLL data\n"); return -EINVAL; } - control_pdev = of_find_device_by_node(control_node); - if (!control_pdev) { - dev_err(&pdev->dev, "Failed to get control device\n"); - return -EINVAL; - } + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "pll_ctrl"); + phy->pll_ctrl_base = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->pll_ctrl_base)) + return PTR_ERR(phy->pll_ctrl_base); - phy->control_dev = &control_pdev->dev; + return 0; +} - omap_control_phy_power(phy->control_dev, 0); +static int ti_pipe3_probe(struct platform_device *pdev) +{ + struct ti_pipe3 *phy; + struct phy *generic_phy; + struct phy_provider *phy_provider; + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + int ret; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + phy->dev = dev; + + ret = ti_pipe3_get_pll_base(phy); + if (ret) + return ret; + + ret = ti_pipe3_get_sysctrl(phy); + if (ret) + return ret; + + ret = ti_pipe3_get_clk(phy); + if (ret) + return ret; platform_set_drvdata(pdev, phy); - pm_runtime_enable(phy->dev); + pm_runtime_enable(dev); /* * Prevent auto-disable of refclk for SATA PHY due to Errata i783 @@ -446,13 +576,15 @@ static int ti_pipe3_probe(struct platform_device *pdev) } } - generic_phy = devm_phy_create(phy->dev, NULL, &ops); + generic_phy = devm_phy_create(dev, NULL, &ops); if (IS_ERR(generic_phy)) return PTR_ERR(generic_phy); phy_set_drvdata(generic_phy, phy); - phy_provider = devm_of_phy_provider_register(phy->dev, - of_phy_simple_xlate); + + ti_pipe3_power_off(generic_phy); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); if (IS_ERR(phy_provider)) return PTR_ERR(phy_provider); diff --git a/drivers/phy/phy-twl4030-usb.c b/drivers/phy/phy-twl4030-usb.c index 3a707dd14238..4a3fc6e59f8e 100644 --- a/drivers/phy/phy-twl4030-usb.c +++ b/drivers/phy/phy-twl4030-usb.c @@ -34,7 +34,7 @@ #include #include #include -#include +#include #include #include #include @@ -148,10 +148,10 @@ * If VBUS is valid or ID is ground, then we know a * cable is present and we need to be runtime-enabled */ -static inline bool cable_present(enum omap_musb_vbus_id_status stat) +static inline bool cable_present(enum musb_vbus_id_status stat) { - return stat == OMAP_MUSB_VBUS_VALID || - stat == OMAP_MUSB_ID_GROUND; + return stat == MUSB_VBUS_VALID || + stat == MUSB_ID_GROUND; } struct twl4030_usb { @@ -170,7 +170,7 @@ struct twl4030_usb { enum twl4030_usb_mode usb_mode; int irq; - enum omap_musb_vbus_id_status linkstat; + enum musb_vbus_id_status linkstat; bool vbus_supplied; struct delayed_work id_workaround_work; @@ -276,11 +276,11 @@ static bool twl4030_is_driving_vbus(struct twl4030_usb *twl) return (ret & (ULPI_OTG_DRVVBUS | ULPI_OTG_CHRGVBUS)) ? true : false; } -static enum omap_musb_vbus_id_status +static enum musb_vbus_id_status twl4030_usb_linkstat(struct twl4030_usb *twl) { int status; - enum omap_musb_vbus_id_status linkstat = OMAP_MUSB_UNKNOWN; + enum musb_vbus_id_status linkstat = MUSB_UNKNOWN; twl->vbus_supplied = false; @@ -306,14 +306,14 @@ static enum omap_musb_vbus_id_status } if (status & BIT(2)) - linkstat = OMAP_MUSB_ID_GROUND; + linkstat = MUSB_ID_GROUND; else if (status & BIT(7)) - linkstat = OMAP_MUSB_VBUS_VALID; + linkstat = MUSB_VBUS_VALID; else - linkstat = OMAP_MUSB_VBUS_OFF; + linkstat = MUSB_VBUS_OFF; } else { - if (twl->linkstat != OMAP_MUSB_UNKNOWN) - linkstat = OMAP_MUSB_VBUS_OFF; + if (twl->linkstat != MUSB_UNKNOWN) + linkstat = MUSB_VBUS_OFF; } dev_dbg(twl->dev, "HW_CONDITIONS 0x%02x/%d; link %d\n", @@ -535,7 +535,7 @@ static DEVICE_ATTR(vbus, 0444, twl4030_usb_vbus_show, NULL); static irqreturn_t twl4030_usb_irq(int irq, void *_twl) { struct twl4030_usb *twl = _twl; - enum omap_musb_vbus_id_status status; + enum musb_vbus_id_status status; bool status_changed = false; status = twl4030_usb_linkstat(twl); @@ -567,11 +567,11 @@ static irqreturn_t twl4030_usb_irq(int irq, void *_twl) pm_runtime_mark_last_busy(twl->dev); pm_runtime_put_autosuspend(twl->dev); } - omap_musb_mailbox(status); + musb_mailbox(status); } /* don't schedule during sleep - irq works right then */ - if (status == OMAP_MUSB_ID_GROUND && pm_runtime_active(twl->dev)) { + if (status == MUSB_ID_GROUND && pm_runtime_active(twl->dev)) { cancel_delayed_work(&twl->id_workaround_work); schedule_delayed_work(&twl->id_workaround_work, HZ); } @@ -670,7 +670,7 @@ static int twl4030_usb_probe(struct platform_device *pdev) twl->dev = &pdev->dev; twl->irq = platform_get_irq(pdev, 0); twl->vbus_supplied = false; - twl->linkstat = OMAP_MUSB_UNKNOWN; + twl->linkstat = MUSB_UNKNOWN; twl->phy.dev = twl->dev; twl->phy.label = "twl4030"; diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig index 5619b8ca3bf3..3644a3500b70 100644 --- a/drivers/usb/chipidea/Kconfig +++ b/drivers/usb/chipidea/Kconfig @@ -37,9 +37,4 @@ config USB_CHIPIDEA_HOST Say Y here to enable host controller functionality of the ChipIdea driver. -config USB_CHIPIDEA_DEBUG - bool "ChipIdea driver debug" - help - Say Y here to enable debugging output of the ChipIdea driver. - endif diff --git a/drivers/usb/chipidea/Makefile b/drivers/usb/chipidea/Makefile index 4decb12f2578..518e445476c3 100644 --- a/drivers/usb/chipidea/Makefile +++ b/drivers/usb/chipidea/Makefile @@ -1,11 +1,8 @@ -ccflags-$(CONFIG_USB_CHIPIDEA_DEBUG) := -DDEBUG - obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc.o -ci_hdrc-y := core.o otg.o +ci_hdrc-y := core.o otg.o debug.o ci_hdrc-$(CONFIG_USB_CHIPIDEA_UDC) += udc.o ci_hdrc-$(CONFIG_USB_CHIPIDEA_HOST) += host.o -ci_hdrc-$(CONFIG_USB_CHIPIDEA_DEBUG) += debug.o ci_hdrc-$(CONFIG_USB_OTG_FSM) += otg_fsm.o # Glue/Bridge layers go here diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index 41d7cf6d63ba..cd414559040f 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -433,4 +433,7 @@ int hw_wait_reg(struct ci_hdrc *ci, enum ci_hw_regs reg, u32 mask, void ci_platform_configure(struct ci_hdrc *ci); +int dbg_create_files(struct ci_hdrc *ci); + +void dbg_remove_files(struct ci_hdrc *ci); #endif /* __DRIVERS_USB_CHIPIDEA_CI_H */ diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 5a048b7b92e8..f14f4ab47ebb 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -345,6 +345,11 @@ static int ci_hdrc_imx_remove(struct platform_device *pdev) return 0; } +static void ci_hdrc_imx_shutdown(struct platform_device *pdev) +{ + ci_hdrc_imx_remove(pdev); +} + #ifdef CONFIG_PM static int imx_controller_suspend(struct device *dev) { @@ -462,6 +467,7 @@ static const struct dev_pm_ops ci_hdrc_imx_pm_ops = { static struct platform_driver ci_hdrc_imx_driver = { .probe = ci_hdrc_imx_probe, .remove = ci_hdrc_imx_remove, + .shutdown = ci_hdrc_imx_shutdown, .driver = { .name = "imx_usb", .of_match_table = ci_hdrc_imx_dt_ids, diff --git a/drivers/usb/chipidea/ci_hdrc_msm.c b/drivers/usb/chipidea/ci_hdrc_msm.c index d79ecc08a1be..3889809fd0c4 100644 --- a/drivers/usb/chipidea/ci_hdrc_msm.c +++ b/drivers/usb/chipidea/ci_hdrc_msm.c @@ -25,7 +25,8 @@ static void ci_hdrc_msm_notify_event(struct ci_hdrc *ci, unsigned event) case CI_HDRC_CONTROLLER_RESET_EVENT: dev_dbg(dev, "CI_HDRC_CONTROLLER_RESET_EVENT received\n"); writel(0, USB_AHBBURST); - writel(0, USB_AHBMODE); + /* use AHB transactor, allow posted data writes */ + writel(0x8, USB_AHBMODE); usb_phy_init(ci->usb_phy); break; case CI_HDRC_CONTROLLER_STOPPED_EVENT: diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 965d0e240dcb..7404064b9bbc 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -23,7 +23,6 @@ * - BUS: bus glue code, bus abstraction layer * * Compile Options - * - CONFIG_USB_CHIPIDEA_DEBUG: enable debug facilities * - STALL_IN: non-empty bulk-in pipes cannot be halted * if defined mass storage compliance succeeds but with warnings * => case 4: Hi > Dn @@ -71,7 +70,6 @@ #include "udc.h" #include "bits.h" #include "host.h" -#include "debug.h" #include "otg.h" #include "otg_fsm.h" @@ -688,52 +686,39 @@ static int ci_get_platdata(struct device *dev, if (usb_get_maximum_speed(dev) == USB_SPEED_FULL) platdata->flags |= CI_HDRC_FORCE_FULLSPEED; - if (of_find_property(dev->of_node, "phy-clkgate-delay-us", NULL)) - of_property_read_u32(dev->of_node, "phy-clkgate-delay-us", + of_property_read_u32(dev->of_node, "phy-clkgate-delay-us", &platdata->phy_clkgate_delay_us); platdata->itc_setting = 1; - if (of_find_property(dev->of_node, "itc-setting", NULL)) { - ret = of_property_read_u32(dev->of_node, "itc-setting", - &platdata->itc_setting); - if (ret) { - dev_err(dev, - "failed to get itc-setting\n"); - return ret; - } - } - if (of_find_property(dev->of_node, "ahb-burst-config", NULL)) { - ret = of_property_read_u32(dev->of_node, "ahb-burst-config", - &platdata->ahb_burst_config); - if (ret) { - dev_err(dev, - "failed to get ahb-burst-config\n"); - return ret; - } + of_property_read_u32(dev->of_node, "itc-setting", + &platdata->itc_setting); + + ret = of_property_read_u32(dev->of_node, "ahb-burst-config", + &platdata->ahb_burst_config); + if (!ret) { platdata->flags |= CI_HDRC_OVERRIDE_AHB_BURST; + } else if (ret != -EINVAL) { + dev_err(dev, "failed to get ahb-burst-config\n"); + return ret; } - if (of_find_property(dev->of_node, "tx-burst-size-dword", NULL)) { - ret = of_property_read_u32(dev->of_node, "tx-burst-size-dword", - &platdata->tx_burst_size); - if (ret) { - dev_err(dev, - "failed to get tx-burst-size-dword\n"); - return ret; - } + ret = of_property_read_u32(dev->of_node, "tx-burst-size-dword", + &platdata->tx_burst_size); + if (!ret) { platdata->flags |= CI_HDRC_OVERRIDE_TX_BURST; + } else if (ret != -EINVAL) { + dev_err(dev, "failed to get tx-burst-size-dword\n"); + return ret; } - if (of_find_property(dev->of_node, "rx-burst-size-dword", NULL)) { - ret = of_property_read_u32(dev->of_node, "rx-burst-size-dword", - &platdata->rx_burst_size); - if (ret) { - dev_err(dev, - "failed to get rx-burst-size-dword\n"); - return ret; - } + ret = of_property_read_u32(dev->of_node, "rx-burst-size-dword", + &platdata->rx_burst_size); + if (!ret) { platdata->flags |= CI_HDRC_OVERRIDE_RX_BURST; + } else if (ret != -EINVAL) { + dev_err(dev, "failed to get rx-burst-size-dword\n"); + return ret; } ext_id = ERR_PTR(-ENODEV); diff --git a/drivers/usb/chipidea/debug.c b/drivers/usb/chipidea/debug.c index 58c8485a0715..a4f7db2e18dd 100644 --- a/drivers/usb/chipidea/debug.c +++ b/drivers/usb/chipidea/debug.c @@ -15,7 +15,6 @@ #include "ci.h" #include "udc.h" #include "bits.h" -#include "debug.h" #include "otg.h" /** diff --git a/drivers/usb/chipidea/debug.h b/drivers/usb/chipidea/debug.h deleted file mode 100644 index e16478c4a943..000000000000 --- a/drivers/usb/chipidea/debug.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * debug.h - ChipIdea USB driver debug interfaces - * - * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. - * - * Author: David Lopo - * - * 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. - */ - -#ifndef __DRIVERS_USB_CHIPIDEA_DEBUG_H -#define __DRIVERS_USB_CHIPIDEA_DEBUG_H - -#ifdef CONFIG_USB_CHIPIDEA_DEBUG -int dbg_create_files(struct ci_hdrc *ci); -void dbg_remove_files(struct ci_hdrc *ci); -#else -static inline int dbg_create_files(struct ci_hdrc *ci) -{ - return 0; -} - -static inline void dbg_remove_files(struct ci_hdrc *ci) -{ -} -#endif - -#endif /* __DRIVERS_USB_CHIPIDEA_DEBUG_H */ diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c index 3d24304405b3..053bac9d983c 100644 --- a/drivers/usb/chipidea/host.c +++ b/drivers/usb/chipidea/host.c @@ -190,6 +190,8 @@ static void host_stop(struct ci_hdrc *ci) (ci->platdata->flags & CI_HDRC_TURN_VBUS_EARLY_ON)) regulator_disable(ci->platdata->reg_vbus); } + ci->hcd = NULL; + ci->otg.host = NULL; } diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c index 00ab59d45da1..ba90dc66703d 100644 --- a/drivers/usb/chipidea/otg_fsm.c +++ b/drivers/usb/chipidea/otg_fsm.c @@ -485,20 +485,30 @@ static void ci_otg_loc_conn(struct otg_fsm *fsm, int on) /* * Generate SOF by host. - * This is controlled through suspend/resume the port. * In host mode, controller will automatically send SOF. * Suspend will block the data on the port. + * + * This is controlled through usbcore by usb autosuspend, + * so the usb device class driver need support autosuspend, + * otherwise the bus suspend will not happen. */ static void ci_otg_loc_sof(struct otg_fsm *fsm, int on) { - struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); + struct usb_device *udev; - if (on) - hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_FPR, - PORTSC_FPR); - else - hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_SUSP, - PORTSC_SUSP); + if (!fsm->otg->host) + return; + + udev = usb_hub_find_child(fsm->otg->host->root_hub, 1); + if (!udev) + return; + + if (on) { + usb_disable_autosuspend(udev); + } else { + pm_runtime_set_autosuspend_delay(&udev->dev, 0); + usb_enable_autosuspend(udev); + } } /* diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index 391a1225b0ba..3eafa2c9a2ba 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -26,7 +26,6 @@ #include "ci.h" #include "udc.h" #include "bits.h" -#include "debug.h" #include "otg.h" #include "otg_fsm.h" @@ -349,14 +348,13 @@ static int add_td_to_list(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq, if (node == NULL) return -ENOMEM; - node->ptr = dma_pool_alloc(hwep->td_pool, GFP_ATOMIC, + node->ptr = dma_pool_zalloc(hwep->td_pool, GFP_ATOMIC, &node->dma); if (node->ptr == NULL) { kfree(node); return -ENOMEM; } - memset(node->ptr, 0, sizeof(struct ci_hw_td)); node->ptr->token = cpu_to_le32(length << __ffs(TD_TOTAL_BYTES)); node->ptr->token &= cpu_to_le32(TD_TOTAL_BYTES); node->ptr->token |= cpu_to_le32(TD_STATUS_ACTIVE); @@ -404,9 +402,9 @@ static inline u8 _usb_addr(struct ci_hw_ep *ep) } /** - * _hardware_queue: configures a request at hardware level - * @gadget: gadget + * _hardware_enqueue: configures a request at hardware level * @hwep: endpoint + * @hwreq: request * * This function returns an error code */ @@ -435,19 +433,28 @@ static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) if (hwreq->req.dma % PAGE_SIZE) pages--; - if (rest == 0) - add_td_to_list(hwep, hwreq, 0); + if (rest == 0) { + ret = add_td_to_list(hwep, hwreq, 0); + if (ret < 0) + goto done; + } while (rest > 0) { unsigned count = min(hwreq->req.length - hwreq->req.actual, (unsigned)(pages * CI_HDRC_PAGE_SIZE)); - add_td_to_list(hwep, hwreq, count); + ret = add_td_to_list(hwep, hwreq, count); + if (ret < 0) + goto done; + rest -= count; } if (hwreq->req.zero && hwreq->req.length && hwep->dir == TX - && (hwreq->req.length % hwep->ep.maxpacket == 0)) - add_td_to_list(hwep, hwreq, 0); + && (hwreq->req.length % hwep->ep.maxpacket == 0)) { + ret = add_td_to_list(hwep, hwreq, 0); + if (ret < 0) + goto done; + } firstnode = list_first_entry(&hwreq->tds, struct td_node, td); @@ -788,8 +795,12 @@ static void isr_get_status_complete(struct usb_ep *ep, struct usb_request *req) /** * _ep_queue: queues (submits) an I/O request to an endpoint + * @ep: endpoint + * @req: request + * @gfp_flags: GFP flags (not used) * * Caller must hold lock + * This function returns an error code */ static int _ep_queue(struct usb_ep *ep, struct usb_request *req, gfp_t __maybe_unused gfp_flags) diff --git a/drivers/usb/common/common.c b/drivers/usb/common/common.c index 673d53038ed2..e6ec125e4485 100644 --- a/drivers/usb/common/common.c +++ b/drivers/usb/common/common.c @@ -17,6 +17,7 @@ #include #include #include +#include const char *usb_otg_state_string(enum usb_otg_state state) { @@ -106,24 +107,71 @@ static const char *const usb_dr_modes[] = { [USB_DR_MODE_OTG] = "otg", }; +static enum usb_dr_mode usb_get_dr_mode_from_string(const char *str) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(usb_dr_modes); i++) + if (!strcmp(usb_dr_modes[i], str)) + return i; + + return USB_DR_MODE_UNKNOWN; +} + enum usb_dr_mode usb_get_dr_mode(struct device *dev) { const char *dr_mode; - int err, i; + int err; err = device_property_read_string(dev, "dr_mode", &dr_mode); if (err < 0) return USB_DR_MODE_UNKNOWN; - for (i = 0; i < ARRAY_SIZE(usb_dr_modes); i++) - if (!strcmp(dr_mode, usb_dr_modes[i])) - return i; - - return USB_DR_MODE_UNKNOWN; + return usb_get_dr_mode_from_string(dr_mode); } EXPORT_SYMBOL_GPL(usb_get_dr_mode); #ifdef CONFIG_OF +/** + * of_usb_get_dr_mode_by_phy - Get dual role mode for the controller device + * which is associated with the given phy device_node + * @np: Pointer to the given phy device_node + * + * In dts a usb controller associates with phy devices. The function gets + * the string from property 'dr_mode' of the controller associated with the + * given phy device node, and returns the correspondig enum usb_dr_mode. + */ +enum usb_dr_mode of_usb_get_dr_mode_by_phy(struct device_node *phy_np) +{ + struct device_node *controller = NULL; + struct device_node *phy; + const char *dr_mode; + int index; + int err; + + do { + controller = of_find_node_with_property(controller, "phys"); + index = 0; + do { + phy = of_parse_phandle(controller, "phys", index); + of_node_put(phy); + if (phy == phy_np) + goto finish; + index++; + } while (phy); + } while (controller); + +finish: + err = of_property_read_string(controller, "dr_mode", &dr_mode); + of_node_put(controller); + + if (err < 0) + return USB_DR_MODE_UNKNOWN; + + return usb_get_dr_mode_from_string(dr_mode); +} +EXPORT_SYMBOL_GPL(of_usb_get_dr_mode_by_phy); + /** * of_usb_host_tpl_support - to get if Targeted Peripheral List is supported * for given targeted hosts (non-PC hosts) diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index dbc3e143453a..59e7a3369084 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -100,6 +100,11 @@ static bool usbfs_snoop; module_param(usbfs_snoop, bool, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(usbfs_snoop, "true to log all usbfs traffic"); +static unsigned usbfs_snoop_max = 65536; +module_param(usbfs_snoop_max, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(usbfs_snoop_max, + "maximum number of bytes to print while snooping"); + #define snoop(dev, format, arg...) \ do { \ if (usbfs_snoop) \ @@ -368,6 +373,7 @@ static void snoop_urb(struct usb_device *udev, ep, t, d, length, timeout_or_status); } + data_len = min(data_len, usbfs_snoop_max); if (data && data_len > 0) { print_hex_dump(KERN_DEBUG, "data: ", DUMP_PREFIX_NONE, 32, 1, data, data_len, 1); @@ -378,7 +384,8 @@ static void snoop_urb_data(struct urb *urb, unsigned len) { int i, size; - if (!usbfs_snoop) + len = min(len, usbfs_snoop_max); + if (!usbfs_snoop || len == 0) return; if (urb->num_sgs == 0) { @@ -1685,8 +1692,12 @@ static struct async *reap_as(struct usb_dev_state *ps) static int proc_reapurb(struct usb_dev_state *ps, void __user *arg) { struct async *as = reap_as(ps); + if (as) { - int retval = processcompl(as, (void __user * __user *)arg); + int retval; + + snoop(&ps->dev->dev, "reap %p\n", as->userurb); + retval = processcompl(as, (void __user * __user *)arg); free_async(as); return retval; } @@ -1702,6 +1713,7 @@ static int proc_reapurbnonblock(struct usb_dev_state *ps, void __user *arg) as = async_getcompleted(ps); if (as) { + snoop(&ps->dev->dev, "reap %p\n", as->userurb); retval = processcompl(as, (void __user * __user *)arg); free_async(as); } else { @@ -1828,8 +1840,12 @@ static int processcompl_compat(struct async *as, void __user * __user *arg) static int proc_reapurb_compat(struct usb_dev_state *ps, void __user *arg) { struct async *as = reap_as(ps); + if (as) { - int retval = processcompl_compat(as, (void __user * __user *)arg); + int retval; + + snoop(&ps->dev->dev, "reap %p\n", as->userurb); + retval = processcompl_compat(as, (void __user * __user *)arg); free_async(as); return retval; } @@ -1845,6 +1861,7 @@ static int proc_reapurbnonblock_compat(struct usb_dev_state *ps, void __user *ar as = async_getcompleted(ps); if (as) { + snoop(&ps->dev->dev, "reap %p\n", as->userurb); retval = processcompl_compat(as, (void __user * __user *)arg); free_async(as); } else { @@ -2249,7 +2266,7 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd, #endif case USBDEVFS_DISCARDURB: - snoop(&dev->dev, "%s: DISCARDURB\n", __func__); + snoop(&dev->dev, "%s: DISCARDURB %p\n", __func__, p); ret = proc_unlinkurb(ps, p); break; diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 1c102d60cd9f..df0e3b92533a 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -3000,7 +3000,7 @@ EXPORT_SYMBOL_GPL(usb_hcd_platform_shutdown); #if defined(CONFIG_USB_MON) || defined(CONFIG_USB_MON_MODULE) -struct usb_mon_operations *mon_ops; +const struct usb_mon_operations *mon_ops; /* * The registration is unlocked. @@ -3010,7 +3010,7 @@ struct usb_mon_operations *mon_ops; * symbols from usbcore, usbcore gets referenced and cannot be unloaded first. */ -int usb_mon_register (struct usb_mon_operations *ops) +int usb_mon_register(const struct usb_mon_operations *ops) { if (mon_ops) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index ddbf32d599cb..51b436918f78 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -3324,7 +3324,7 @@ static int finish_port_resume(struct usb_device *udev) /* * There are some SS USB devices which take longer time for link training. * XHCI specs 4.19.4 says that when Link training is successful, port - * sets CSC bit to 1. So if SW reads port status before successful link + * sets CCS bit to 1. So if SW reads port status before successful link * training, then it will not find device to be present. * USB Analyzer log with such buggy devices show that in some cases * device switch on the RX termination after long delay of host enabling @@ -3335,14 +3335,17 @@ static int finish_port_resume(struct usb_device *udev) * routine implements a 2000 ms timeout for link training. If in a case * link trains before timeout, loop will exit earlier. * + * There are also some 2.0 hard drive based devices and 3.0 thumb + * drives that, when plugged into a 2.0 only port, take a long + * time to set CCS after VBUS enable. + * * FIXME: If a device was connected before suspend, but was removed * while system was asleep, then the loop in the following routine will * only exit at timeout. * - * This routine should only be called when persist is enabled for a SS - * device. + * This routine should only be called when persist is enabled. */ -static int wait_for_ss_port_enable(struct usb_device *udev, +static int wait_for_connected(struct usb_device *udev, struct usb_hub *hub, int *port1, u16 *portchange, u16 *portstatus) { @@ -3355,6 +3358,7 @@ static int wait_for_ss_port_enable(struct usb_device *udev, delay_ms += 20; status = hub_port_status(hub, *port1, portstatus, portchange); } + dev_dbg(&udev->dev, "Waited %dms for CONNECT\n", delay_ms); return status; } @@ -3454,8 +3458,8 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) } } - if (udev->persist_enabled && hub_is_superspeed(hub->hdev)) - status = wait_for_ss_port_enable(udev, hub, &port1, &portchange, + if (udev->persist_enabled) + status = wait_for_connected(udev, hub, &port1, &portchange, &portstatus); status = check_port_resume_type(udev, @@ -3895,17 +3899,30 @@ static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev, return; } - if (usb_set_lpm_timeout(udev, state, timeout)) + if (usb_set_lpm_timeout(udev, state, timeout)) { /* If we can't set the parent hub U1/U2 timeout, * device-initiated LPM won't be allowed either, so let the xHCI * host know that this link state won't be enabled. */ hcd->driver->disable_usb3_lpm_timeout(hcd, udev, state); + } else { + /* Only a configured device will accept the Set Feature + * U1/U2_ENABLE + */ + if (udev->actconfig) + usb_set_device_initiated_lpm(udev, state, true); - /* Only a configured device will accept the Set Feature U1/U2_ENABLE */ - else if (udev->actconfig) - usb_set_device_initiated_lpm(udev, state, true); - + /* As soon as usb_set_lpm_timeout(timeout) returns 0, the + * hub-initiated LPM is enabled. Thus, LPM is enabled no + * matter the result of usb_set_device_initiated_lpm(). + * The only difference is whether device is able to initiate + * LPM. + */ + if (state == USB3_LPM_U1) + udev->usb3_lpm_u1_enabled = 1; + else if (state == USB3_LPM_U2) + udev->usb3_lpm_u2_enabled = 1; + } } /* @@ -3945,6 +3962,18 @@ static int usb_disable_link_state(struct usb_hcd *hcd, struct usb_device *udev, dev_warn(&udev->dev, "Could not disable xHCI %s timeout, " "bus schedule bandwidth may be impacted.\n", usb3_lpm_names[state]); + + /* As soon as usb_set_lpm_timeout(0) return 0, hub initiated LPM + * is disabled. Hub will disallows link to enter U1/U2 as well, + * even device is initiating LPM. Hence LPM is disabled if hub LPM + * timeout set to 0, no matter device-initiated LPM is disabled or + * not. + */ + if (state == USB3_LPM_U1) + udev->usb3_lpm_u1_enabled = 0; + else if (state == USB3_LPM_U2) + udev->usb3_lpm_u2_enabled = 0; + return 0; } @@ -3979,8 +4008,6 @@ int usb_disable_lpm(struct usb_device *udev) if (usb_disable_link_state(hcd, udev, USB3_LPM_U2)) goto enable_lpm; - udev->usb3_lpm_enabled = 0; - return 0; enable_lpm: @@ -4017,6 +4044,8 @@ EXPORT_SYMBOL_GPL(usb_unlocked_disable_lpm); void usb_enable_lpm(struct usb_device *udev) { struct usb_hcd *hcd; + struct usb_hub *hub; + struct usb_port *port_dev; if (!udev || !udev->parent || udev->speed != USB_SPEED_SUPER || @@ -4036,10 +4065,17 @@ void usb_enable_lpm(struct usb_device *udev) if (udev->lpm_disable_count > 0) return; - usb_enable_link_state(hcd, udev, USB3_LPM_U1); - usb_enable_link_state(hcd, udev, USB3_LPM_U2); + hub = usb_hub_to_struct_hub(udev->parent); + if (!hub) + return; - udev->usb3_lpm_enabled = 1; + port_dev = hub->ports[udev->portnum - 1]; + + if (port_dev->usb3_lpm_u1_permit) + usb_enable_link_state(hcd, udev, USB3_LPM_U1); + + if (port_dev->usb3_lpm_u2_permit) + usb_enable_link_state(hcd, udev, USB3_LPM_U2); } EXPORT_SYMBOL_GPL(usb_enable_lpm); diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h index 688817fb3246..45d070dd1d03 100644 --- a/drivers/usb/core/hub.h +++ b/drivers/usb/core/hub.h @@ -92,6 +92,8 @@ struct usb_hub { * @status_lock: synchronize port_event() vs usb_port_{suspend|resume} * @portnum: port index num based one * @is_superspeed cache super-speed status + * @usb3_lpm_u1_permit: whether USB3 U1 LPM is permitted. + * @usb3_lpm_u2_permit: whether USB3 U2 LPM is permitted. */ struct usb_port { struct usb_device *child; @@ -104,6 +106,8 @@ struct usb_port { struct mutex status_lock; u8 portnum; unsigned int is_superspeed:1; + unsigned int usb3_lpm_u1_permit:1; + unsigned int usb3_lpm_u2_permit:1; }; #define to_usb_port(_dev) \ @@ -155,4 +159,3 @@ static inline int hub_port_debounce_be_stable(struct usb_hub *hub, { return hub_port_debounce(hub, port1, false); } - diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index 5487fe308f01..460c855be0d0 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -50,6 +50,72 @@ static ssize_t connect_type_show(struct device *dev, } static DEVICE_ATTR_RO(connect_type); +static ssize_t usb3_lpm_permit_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_port *port_dev = to_usb_port(dev); + const char *p; + + if (port_dev->usb3_lpm_u1_permit) { + if (port_dev->usb3_lpm_u2_permit) + p = "u1_u2"; + else + p = "u1"; + } else { + if (port_dev->usb3_lpm_u2_permit) + p = "u2"; + else + p = "0"; + } + + return sprintf(buf, "%s\n", p); +} + +static ssize_t usb3_lpm_permit_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_port *port_dev = to_usb_port(dev); + struct usb_device *udev = port_dev->child; + struct usb_hcd *hcd; + + if (!strncmp(buf, "u1_u2", 5)) { + port_dev->usb3_lpm_u1_permit = 1; + port_dev->usb3_lpm_u2_permit = 1; + + } else if (!strncmp(buf, "u1", 2)) { + port_dev->usb3_lpm_u1_permit = 1; + port_dev->usb3_lpm_u2_permit = 0; + + } else if (!strncmp(buf, "u2", 2)) { + port_dev->usb3_lpm_u1_permit = 0; + port_dev->usb3_lpm_u2_permit = 1; + + } else if (!strncmp(buf, "0", 1)) { + port_dev->usb3_lpm_u1_permit = 0; + port_dev->usb3_lpm_u2_permit = 0; + } else + return -EINVAL; + + /* If device is connected to the port, disable or enable lpm + * to make new u1 u2 setting take effect immediately. + */ + if (udev) { + hcd = bus_to_hcd(udev->bus); + if (!hcd) + return -EINVAL; + usb_lock_device(udev); + mutex_lock(hcd->bandwidth_mutex); + if (!usb_disable_lpm(udev)) + usb_enable_lpm(udev); + mutex_unlock(hcd->bandwidth_mutex); + usb_unlock_device(udev); + } + + return count; +} +static DEVICE_ATTR_RW(usb3_lpm_permit); + static struct attribute *port_dev_attrs[] = { &dev_attr_connect_type.attr, NULL, @@ -64,6 +130,21 @@ static const struct attribute_group *port_dev_group[] = { NULL, }; +static struct attribute *port_dev_usb3_attrs[] = { + &dev_attr_usb3_lpm_permit.attr, + NULL, +}; + +static struct attribute_group port_dev_usb3_attr_grp = { + .attrs = port_dev_usb3_attrs, +}; + +static const struct attribute_group *port_dev_usb3_group[] = { + &port_dev_attr_grp, + &port_dev_usb3_attr_grp, + NULL, +}; + static void usb_port_device_release(struct device *dev) { struct usb_port *port_dev = to_usb_port(dev); @@ -401,6 +482,7 @@ static void find_and_link_peer(struct usb_hub *hub, int port1) int usb_hub_create_port_device(struct usb_hub *hub, int port1) { struct usb_port *port_dev; + struct usb_device *hdev = hub->hdev; int retval; port_dev = kzalloc(sizeof(*port_dev), GFP_KERNEL); @@ -417,7 +499,12 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1) port_dev->portnum = port1; set_bit(port1, hub->power_bits); port_dev->dev.parent = hub->intfdev; - port_dev->dev.groups = port_dev_group; + if (hub_is_superspeed(hdev)) { + port_dev->usb3_lpm_u1_permit = 1; + port_dev->usb3_lpm_u2_permit = 1; + port_dev->dev.groups = port_dev_usb3_group; + } else + port_dev->dev.groups = port_dev_group; port_dev->dev.type = &usb_port_device_type; port_dev->dev.driver = &usb_port_driver; if (hub_is_superspeed(hub->hdev)) diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index d9ec2de6c4cf..65b6e6b84043 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -531,7 +531,7 @@ static ssize_t usb2_lpm_besl_store(struct device *dev, } static DEVICE_ATTR_RW(usb2_lpm_besl); -static ssize_t usb3_hardware_lpm_show(struct device *dev, +static ssize_t usb3_hardware_lpm_u1_show(struct device *dev, struct device_attribute *attr, char *buf) { struct usb_device *udev = to_usb_device(dev); @@ -539,7 +539,7 @@ static ssize_t usb3_hardware_lpm_show(struct device *dev, usb_lock_device(udev); - if (udev->usb3_lpm_enabled) + if (udev->usb3_lpm_u1_enabled) p = "enabled"; else p = "disabled"; @@ -548,7 +548,26 @@ static ssize_t usb3_hardware_lpm_show(struct device *dev, return sprintf(buf, "%s\n", p); } -static DEVICE_ATTR_RO(usb3_hardware_lpm); +static DEVICE_ATTR_RO(usb3_hardware_lpm_u1); + +static ssize_t usb3_hardware_lpm_u2_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_device *udev = to_usb_device(dev); + const char *p; + + usb_lock_device(udev); + + if (udev->usb3_lpm_u2_enabled) + p = "enabled"; + else + p = "disabled"; + + usb_unlock_device(udev); + + return sprintf(buf, "%s\n", p); +} +static DEVICE_ATTR_RO(usb3_hardware_lpm_u2); static struct attribute *usb2_hardware_lpm_attr[] = { &dev_attr_usb2_hardware_lpm.attr, @@ -562,7 +581,8 @@ static struct attribute_group usb2_hardware_lpm_attr_group = { }; static struct attribute *usb3_hardware_lpm_attr[] = { - &dev_attr_usb3_hardware_lpm.attr, + &dev_attr_usb3_hardware_lpm_u1.attr, + &dev_attr_usb3_hardware_lpm_u2.attr, NULL, }; static struct attribute_group usb3_hardware_lpm_attr_group = { @@ -592,7 +612,8 @@ static int add_power_attributes(struct device *dev) if (udev->usb2_hw_lpm_capable == 1) rc = sysfs_merge_group(&dev->kobj, &usb2_hardware_lpm_attr_group); - if (udev->lpm_capable == 1) + if (udev->speed == USB_SPEED_SUPER && + udev->lpm_capable == 1) rc = sysfs_merge_group(&dev->kobj, &usb3_hardware_lpm_attr_group); } diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index f8bbd0b6d9fe..77e4c9bc0ab1 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -49,12 +49,7 @@ const char *usbcore_name = "usbcore"; static bool nousb; /* Disable USB when built into kernel image */ -/* To disable USB, kernel command line is 'nousb' not 'usbcore.nousb' */ -#ifdef MODULE module_param(nousb, bool, 0444); -#else -core_param(nousb, nousb, bool, 0444); -#endif /* * for external read access to diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index ef73e498e98f..39a0fa8a4c0a 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -481,32 +481,19 @@ static void dwc2_init_fs_ls_pclk_sel(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. */ -static int dwc2_core_reset(struct dwc2_hsotg *hsotg) +int dwc2_core_reset(struct dwc2_hsotg *hsotg) { u32 greset; int count = 0; - u32 gusbcfg; dev_vdbg(hsotg->dev, "%s()\n", __func__); - /* Wait for AHB master IDLE state */ - do { - usleep_range(20000, 40000); - 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)); - /* Core Soft Reset */ - count = 0; + greset = dwc2_readl(hsotg->regs + GRSTCTL); greset |= GRSTCTL_CSFTRST; dwc2_writel(greset, hsotg->regs + GRSTCTL); do { - usleep_range(20000, 40000); + udelay(1); greset = dwc2_readl(hsotg->regs + GRSTCTL); if (++count > 50) { dev_warn(hsotg->dev, @@ -516,29 +503,146 @@ static int dwc2_core_reset(struct dwc2_hsotg *hsotg) } } while (greset & GRSTCTL_CSFTRST); - if (hsotg->dr_mode == USB_DR_MODE_HOST) { - gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); - gusbcfg &= ~GUSBCFG_FORCEDEVMODE; - gusbcfg |= GUSBCFG_FORCEHOSTMODE; - dwc2_writel(gusbcfg, hsotg->regs + GUSBCFG); - } else if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) { - gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); - gusbcfg &= ~GUSBCFG_FORCEHOSTMODE; - gusbcfg |= GUSBCFG_FORCEDEVMODE; - dwc2_writel(gusbcfg, hsotg->regs + GUSBCFG); - } else if (hsotg->dr_mode == USB_DR_MODE_OTG) { - gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); - gusbcfg &= ~GUSBCFG_FORCEHOSTMODE; - gusbcfg &= ~GUSBCFG_FORCEDEVMODE; - dwc2_writel(gusbcfg, hsotg->regs + GUSBCFG); - } + /* 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)); + + return 0; +} + +/* + * 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. + * + * 2) During probe we want to read reset values of the hw + * configuration registers that are only available in either host or + * device mode. We may need to force the mode if the current mode does + * not allow us to access the register in the mode that we want. + * + * In either case it only makes sense to force the mode if the + * controller hardware is OTG capable. + * + * Checks are done in this function to determine whether doing a force + * would be valid or not. + * + * If a force is done, it requires a 25ms delay to take effect. + * + * Returns true if the mode was forced. + */ +static bool dwc2_force_mode(struct dwc2_hsotg *hsotg, bool host) +{ + u32 gusbcfg; + u32 set; + u32 clear; + + dev_dbg(hsotg->dev, "Forcing mode to %s\n", host ? "host" : "device"); + + /* + * Force mode has no effect if the hardware is not OTG. + */ + if (!dwc2_hw_is_otg(hsotg)) + return false; + + /* + * 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; + + if (WARN_ON(!host && hsotg->dr_mode == USB_DR_MODE_HOST)) + return false; + + gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); + + set = host ? GUSBCFG_FORCEHOSTMODE : GUSBCFG_FORCEDEVMODE; + clear = host ? GUSBCFG_FORCEDEVMODE : GUSBCFG_FORCEHOSTMODE; + + /* + * If the force mode bit is already set, don't set it. + */ + if ((gusbcfg & set) && !(gusbcfg & clear)) + return false; + + gusbcfg &= ~clear; + gusbcfg |= set; + dwc2_writel(gusbcfg, hsotg->regs + GUSBCFG); + + msleep(25); + return true; +} + +/* + * Clears the force mode bits. + */ +static void dwc2_clear_force_mode(struct dwc2_hsotg *hsotg) +{ + u32 gusbcfg; + + gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); + gusbcfg &= ~GUSBCFG_FORCEHOSTMODE; + gusbcfg &= ~GUSBCFG_FORCEDEVMODE; + dwc2_writel(gusbcfg, hsotg->regs + GUSBCFG); /* * NOTE: This long sleep is _very_ important, otherwise the core will * not stay in host mode after a connector ID change! */ - usleep_range(150000, 200000); + msleep(25); +} +/* + * Sets or clears force mode based on the dr_mode parameter. + */ +void dwc2_force_dr_mode(struct dwc2_hsotg *hsotg) +{ + switch (hsotg->dr_mode) { + case USB_DR_MODE_HOST: + dwc2_force_mode(hsotg, true); + break; + case USB_DR_MODE_PERIPHERAL: + dwc2_force_mode(hsotg, false); + break; + case USB_DR_MODE_OTG: + dwc2_clear_force_mode(hsotg); + break; + default: + dev_warn(hsotg->dev, "%s() Invalid dr_mode=%d\n", + __func__, hsotg->dr_mode); + break; + } +} + +/* + * 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. + */ +int dwc2_core_reset_and_force_dr_mode(struct dwc2_hsotg *hsotg) +{ + int retval; + + retval = dwc2_core_reset(hsotg); + if (retval) + return retval; + + dwc2_force_dr_mode(hsotg); return 0; } @@ -553,16 +657,20 @@ static int dwc2_fs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) */ if (select_phy) { dev_dbg(hsotg->dev, "FS PHY selected\n"); - usbcfg = dwc2_readl(hsotg->regs + GUSBCFG); - usbcfg |= GUSBCFG_PHYSEL; - dwc2_writel(usbcfg, hsotg->regs + GUSBCFG); - /* Reset after a PHY select */ - retval = dwc2_core_reset(hsotg); - if (retval) { - dev_err(hsotg->dev, "%s() Reset failed, aborting", - __func__); - return retval; + usbcfg = dwc2_readl(hsotg->regs + GUSBCFG); + if (!(usbcfg & GUSBCFG_PHYSEL)) { + usbcfg |= GUSBCFG_PHYSEL; + dwc2_writel(usbcfg, hsotg->regs + GUSBCFG); + + /* Reset after a PHY select */ + retval = dwc2_core_reset_and_force_dr_mode(hsotg); + + if (retval) { + dev_err(hsotg->dev, + "%s: Reset failed, aborting", __func__); + return retval; + } } } @@ -597,13 +705,13 @@ static int dwc2_fs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) static int dwc2_hs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) { - u32 usbcfg; + u32 usbcfg, usbcfg_old; int retval = 0; if (!select_phy) return 0; - usbcfg = dwc2_readl(hsotg->regs + GUSBCFG); + usbcfg = usbcfg_old = dwc2_readl(hsotg->regs + GUSBCFG); /* * HS PHY parameters. These parameters are preserved during soft reset @@ -631,14 +739,16 @@ static int dwc2_hs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) break; } - dwc2_writel(usbcfg, hsotg->regs + GUSBCFG); + if (usbcfg != usbcfg_old) { + dwc2_writel(usbcfg, hsotg->regs + GUSBCFG); - /* Reset after setting the PHY parameters */ - retval = dwc2_core_reset(hsotg); - if (retval) { - dev_err(hsotg->dev, "%s() Reset failed, aborting", - __func__); - return retval; + /* Reset after setting the PHY parameters */ + retval = dwc2_core_reset_and_force_dr_mode(hsotg); + if (retval) { + dev_err(hsotg->dev, + "%s: Reset failed, aborting", __func__); + return retval; + } } return retval; @@ -765,11 +875,10 @@ static void dwc2_gusbcfg_init(struct dwc2_hsotg *hsotg) * dwc2_core_init() - Initializes the DWC_otg controller registers and * prepares the core for device mode or host mode operation * - * @hsotg: Programming view of the DWC_otg controller - * @select_phy: If true then also set the Phy type - * @irq: If >= 0, the irq to register + * @hsotg: Programming view of the DWC_otg controller + * @initial_setup: If true then this is the first init for this instance. */ -int dwc2_core_init(struct dwc2_hsotg *hsotg, bool select_phy, int irq) +int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup) { u32 usbcfg, otgctl; int retval; @@ -791,18 +900,26 @@ int dwc2_core_init(struct dwc2_hsotg *hsotg, bool select_phy, int irq) dwc2_writel(usbcfg, hsotg->regs + GUSBCFG); - /* Reset the Controller */ - retval = dwc2_core_reset(hsotg); - if (retval) { - dev_err(hsotg->dev, "%s(): Reset failed, aborting\n", - __func__); - return retval; + /* + * Reset the Controller + * + * We only need to reset the controller if this is a re-init. + * For the first init we know for sure that earlier code reset us (it + * needed to in order to properly detect various parameters). + */ + if (!initial_setup) { + retval = dwc2_core_reset_and_force_dr_mode(hsotg); + if (retval) { + dev_err(hsotg->dev, "%s(): Reset failed, aborting\n", + __func__); + return retval; + } } /* * This needs to happen in FS mode before any other programming occurs */ - retval = dwc2_phy_init(hsotg, select_phy); + retval = dwc2_phy_init(hsotg, initial_setup); if (retval) return retval; @@ -1707,6 +1824,7 @@ void dwc2_hc_start_transfer(struct dwc2_hsotg *hsotg, u32 hcchar; u32 hctsiz = 0; u16 num_packets; + u32 ec_mc; if (dbg_hc(chan)) dev_vdbg(hsotg->dev, "%s()\n", __func__); @@ -1743,6 +1861,13 @@ void dwc2_hc_start_transfer(struct dwc2_hsotg *hsotg, hctsiz |= chan->xfer_len << TSIZ_XFERSIZE_SHIFT & TSIZ_XFERSIZE_MASK; + + /* For split set ec_mc for immediate retries */ + if (chan->ep_type == USB_ENDPOINT_XFER_INT || + chan->ep_type == USB_ENDPOINT_XFER_ISOC) + ec_mc = 3; + else + ec_mc = 1; } else { if (dbg_hc(chan)) dev_vdbg(hsotg->dev, "no split\n"); @@ -1805,6 +1930,9 @@ void dwc2_hc_start_transfer(struct dwc2_hsotg *hsotg, hctsiz |= chan->xfer_len << TSIZ_XFERSIZE_SHIFT & TSIZ_XFERSIZE_MASK; + + /* The ec_mc gets the multi_count for non-split */ + ec_mc = chan->multi_count; } chan->start_pkt_count = num_packets; @@ -1855,8 +1983,7 @@ void dwc2_hc_start_transfer(struct dwc2_hsotg *hsotg, hcchar = dwc2_readl(hsotg->regs + HCCHAR(chan->hc_num)); hcchar &= ~HCCHAR_MULTICNT_MASK; - hcchar |= chan->multi_count << HCCHAR_MULTICNT_SHIFT & - HCCHAR_MULTICNT_MASK; + hcchar |= (ec_mc << HCCHAR_MULTICNT_SHIFT) & HCCHAR_MULTICNT_MASK; dwc2_hc_set_even_odd_frame(hsotg, chan, &hcchar); if (hcchar & HCCHAR_CHDIS) @@ -1905,7 +2032,6 @@ void dwc2_hc_start_transfer_ddma(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan) { u32 hcchar; - u32 hc_dma; u32 hctsiz = 0; if (chan->do_ping) @@ -1934,14 +2060,14 @@ void dwc2_hc_start_transfer_ddma(struct dwc2_hsotg *hsotg, dwc2_writel(hctsiz, hsotg->regs + HCTSIZ(chan->hc_num)); - hc_dma = (u32)chan->desc_list_addr & HCDMA_DMA_ADDR_MASK; + dma_sync_single_for_device(hsotg->dev, chan->desc_list_addr, + chan->desc_list_sz, DMA_TO_DEVICE); + + dwc2_writel(chan->desc_list_addr, hsotg->regs + HCDMA(chan->hc_num)); - /* Always start from first descriptor */ - hc_dma &= ~HCDMA_CTD_MASK; - dwc2_writel(hc_dma, hsotg->regs + HCDMA(chan->hc_num)); if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "Wrote %08x to HCDMA(%d)\n", - hc_dma, chan->hc_num); + dev_vdbg(hsotg->dev, "Wrote %pad to HCDMA(%d)\n", + &chan->desc_list_addr, chan->hc_num); hcchar = dwc2_readl(hsotg->regs + HCCHAR(chan->hc_num)); hcchar &= ~HCCHAR_MULTICNT_MASK; @@ -2485,6 +2611,29 @@ void dwc2_set_param_dma_desc_enable(struct dwc2_hsotg *hsotg, int val) hsotg->core_params->dma_desc_enable = val; } +void dwc2_set_param_dma_desc_fs_enable(struct dwc2_hsotg *hsotg, int val) +{ + int valid = 1; + + if (val > 0 && (hsotg->core_params->dma_enable <= 0 || + !hsotg->hw_params.dma_desc_enable)) + valid = 0; + if (val < 0) + valid = 0; + + if (!valid) { + if (val >= 0) + dev_err(hsotg->dev, + "%d invalid for dma_desc_fs_enable parameter. Check HW configuration.\n", + val); + val = (hsotg->core_params->dma_enable > 0 && + hsotg->hw_params.dma_desc_enable); + } + + hsotg->core_params->dma_desc_fs_enable = val; + dev_dbg(hsotg->dev, "Setting dma_desc_fs_enable to %d\n", val); +} + void dwc2_set_param_host_support_fs_ls_low_power(struct dwc2_hsotg *hsotg, int val) { @@ -3016,6 +3165,7 @@ void dwc2_set_parameters(struct dwc2_hsotg *hsotg, dwc2_set_param_otg_cap(hsotg, params->otg_cap); dwc2_set_param_dma_enable(hsotg, params->dma_enable); dwc2_set_param_dma_desc_enable(hsotg, params->dma_desc_enable); + dwc2_set_param_dma_desc_fs_enable(hsotg, params->dma_desc_fs_enable); dwc2_set_param_host_support_fs_ls_low_power(hsotg, params->host_support_fs_ls_low_power); dwc2_set_param_enable_dynamic_fifo(hsotg, @@ -3052,17 +3202,93 @@ void dwc2_set_parameters(struct dwc2_hsotg *hsotg, dwc2_set_param_hibernation(hsotg, params->hibernation); } +/* + * Forces either host or device mode if the controller is not + * currently in that mode. + * + * Returns true if the mode was forced. + */ +static 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); +} + +/* + * Gets host hardware parameters. Forces host mode if not currently in + * host mode. Should be called immediately after a core soft reset in + * order to get the reset values. + */ +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); + + gnptxfsiz = dwc2_readl(hsotg->regs + GNPTXFSIZ); + hptxfsiz = dwc2_readl(hsotg->regs + HPTXFSIZ); + dev_dbg(hsotg->dev, "gnptxfsiz=%08x\n", gnptxfsiz); + dev_dbg(hsotg->dev, "hptxfsiz=%08x\n", 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) >> + FIFOSIZE_DEPTH_SHIFT; +} + +/* + * Gets device hardware parameters. Forces device mode if not + * currently in device mode. Should be called immediately after a core + * soft reset in order to get the reset values. + */ +static void dwc2_get_dev_hwparams(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hw_params *hw = &hsotg->hw_params; + bool forced; + u32 gnptxfsiz; + + if (hsotg->dr_mode == USB_DR_MODE_HOST) + return; + + forced = dwc2_force_mode_if_needed(hsotg, false); + + gnptxfsiz = dwc2_readl(hsotg->regs + GNPTXFSIZ); + dev_dbg(hsotg->dev, "gnptxfsiz=%08x\n", gnptxfsiz); + + if (forced) + dwc2_clear_force_mode(hsotg); + + hw->dev_nperio_tx_fifo_size = (gnptxfsiz & FIFOSIZE_DEPTH_MASK) >> + FIFOSIZE_DEPTH_SHIFT; +} + /** * During device initialization, read various hardware configuration * registers and interpret the contents. + * + * This should be called during driver probe. It will perform a core + * soft reset in order to get the reset values of the parameters. */ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) { struct dwc2_hw_params *hw = &hsotg->hw_params; unsigned width; u32 hwcfg1, hwcfg2, hwcfg3, hwcfg4; - u32 hptxfsiz, grxfsiz, gnptxfsiz; - u32 gusbcfg; + u32 grxfsiz; + int retval; /* * Attempt to ensure this device is really a DWC_otg Controller. @@ -3082,6 +3308,10 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) hw->snpsid >> 12 & 0xf, hw->snpsid >> 8 & 0xf, hw->snpsid >> 4 & 0xf, hw->snpsid & 0xf, hw->snpsid); + retval = dwc2_core_reset(hsotg); + if (retval) + return retval; + hwcfg1 = dwc2_readl(hsotg->regs + GHWCFG1); hwcfg2 = dwc2_readl(hsotg->regs + GHWCFG2); hwcfg3 = dwc2_readl(hsotg->regs + GHWCFG3); @@ -3094,20 +3324,16 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) dev_dbg(hsotg->dev, "hwcfg4=%08x\n", hwcfg4); dev_dbg(hsotg->dev, "grxfsiz=%08x\n", grxfsiz); - /* Force host mode to get HPTXFSIZ / GNPTXFSIZ exact power on value */ - gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); - gusbcfg |= GUSBCFG_FORCEHOSTMODE; - dwc2_writel(gusbcfg, hsotg->regs + GUSBCFG); - usleep_range(100000, 150000); + /* + * Host specific hardware parameters. Reading these parameters + * requires the controller to be in host mode. The mode will + * be forced, if necessary, to read these values. + */ + dwc2_get_host_hwparams(hsotg); + dwc2_get_dev_hwparams(hsotg); - gnptxfsiz = dwc2_readl(hsotg->regs + GNPTXFSIZ); - hptxfsiz = dwc2_readl(hsotg->regs + HPTXFSIZ); - dev_dbg(hsotg->dev, "gnptxfsiz=%08x\n", gnptxfsiz); - dev_dbg(hsotg->dev, "hptxfsiz=%08x\n", hptxfsiz); - gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); - gusbcfg &= ~GUSBCFG_FORCEHOSTMODE; - dwc2_writel(gusbcfg, hsotg->regs + GUSBCFG); - usleep_range(100000, 150000); + /* hwcfg1 */ + hw->dev_ep_dirs = hwcfg1; /* hwcfg2 */ hw->op_mode = (hwcfg2 & GHWCFG2_OP_MODE_MASK) >> @@ -3163,10 +3389,6 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) /* fifo sizes */ hw->host_rx_fifo_size = (grxfsiz & GRXFSIZ_DEPTH_MASK) >> GRXFSIZ_DEPTH_SHIFT; - hw->host_nperio_tx_fifo_size = (gnptxfsiz & FIFOSIZE_DEPTH_MASK) >> - FIFOSIZE_DEPTH_SHIFT; - hw->host_perio_tx_fifo_size = (hptxfsiz & FIFOSIZE_DEPTH_MASK) >> - FIFOSIZE_DEPTH_SHIFT; dev_dbg(hsotg->dev, "Detected values from hardware:\n"); dev_dbg(hsotg->dev, " op_mode=%d\n", @@ -3275,6 +3497,43 @@ void dwc2_disable_global_interrupts(struct dwc2_hsotg *hsotg) dwc2_writel(ahbcfg, hsotg->regs + GAHBCFG); } +/* Returns the controller's GHWCFG2.OTG_MODE. */ +unsigned dwc2_op_mode(struct dwc2_hsotg *hsotg) +{ + u32 ghwcfg2 = dwc2_readl(hsotg->regs + GHWCFG2); + + return (ghwcfg2 & GHWCFG2_OP_MODE_MASK) >> + GHWCFG2_OP_MODE_SHIFT; +} + +/* Returns true if the controller is capable of DRD. */ +bool dwc2_hw_is_otg(struct dwc2_hsotg *hsotg) +{ + unsigned op_mode = dwc2_op_mode(hsotg); + + return (op_mode == GHWCFG2_OP_MODE_HNP_SRP_CAPABLE) || + (op_mode == GHWCFG2_OP_MODE_SRP_ONLY_CAPABLE) || + (op_mode == GHWCFG2_OP_MODE_NO_HNP_SRP_CAPABLE); +} + +/* Returns true if the controller is host-only. */ +bool dwc2_hw_is_host(struct dwc2_hsotg *hsotg) +{ + unsigned op_mode = dwc2_op_mode(hsotg); + + return (op_mode == GHWCFG2_OP_MODE_SRP_CAPABLE_HOST) || + (op_mode == GHWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST); +} + +/* Returns true if the controller is device-only. */ +bool dwc2_hw_is_device(struct dwc2_hsotg *hsotg) +{ + unsigned op_mode = dwc2_op_mode(hsotg); + + return (op_mode == GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE) || + (op_mode == GHWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE); +} + 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 a66d3cb62b65..7fb6434f4639 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -246,6 +246,13 @@ enum dwc2_ep0_state { * value for this if none is specified. * 0 - Address DMA * 1 - Descriptor DMA (default, if available) + * @dma_desc_fs_enable: When DMA mode is enabled, specifies whether to use + * address DMA mode or descriptor DMA mode for accessing + * the data FIFOs in Full Speed mode only. The driver + * will automatically detect the value for this if none is + * specified. + * 0 - Address DMA + * 1 - Descriptor DMA in FS (default, if available) * @speed: Specifies the maximum speed of operation in host and * device mode. The actual speed depends on the speed of * the attached device and the value of phy_type. @@ -375,6 +382,7 @@ struct dwc2_core_params { int otg_ver; int dma_enable; int dma_desc_enable; + int dma_desc_fs_enable; int speed; int enable_dynamic_fifo; int en_multiple_tx_fifo; @@ -451,15 +459,18 @@ struct dwc2_core_params { * 1 - 16 bits * 2 - 8 or 16 bits * @snpsid: Value from SNPSID register + * @dev_ep_dirs: Direction of device endpoints (GHWCFG1) */ struct dwc2_hw_params { unsigned op_mode:3; unsigned arch:2; unsigned dma_desc_enable:1; + unsigned dma_desc_fs_enable:1; unsigned enable_dynamic_fifo:1; unsigned en_multiple_tx_fifo:1; unsigned host_rx_fifo_size:16; unsigned host_nperio_tx_fifo_size:16; + unsigned dev_nperio_tx_fifo_size:16; unsigned host_perio_tx_fifo_size:16; unsigned nperio_tx_q_depth:3; unsigned host_perio_tx_q_depth:3; @@ -476,6 +487,7 @@ struct dwc2_hw_params { unsigned power_optimized:1; unsigned utmi_phy_data_width:2; u32 snpsid; + u32 dev_ep_dirs; }; /* Size of control and EP0 buffers */ @@ -676,6 +688,9 @@ struct dwc2_hregs_backup { * @otg_port: OTG port number * @frame_list: Frame list * @frame_list_dma: Frame list DMA address + * @frame_list_sz: Frame list size + * @desc_gen_cache: Kmem cache for generic descriptors + * @desc_hsisoc_cache: Kmem cache for hs isochronous descriptors * * These are for peripheral mode: * @@ -770,6 +785,7 @@ struct dwc2_hsotg { u16 frame_number; u16 periodic_qh_count; bool bus_suspended; + bool new_connection; #ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS #define FRAME_NUM_ARRAY_SIZE 1000 @@ -794,6 +810,9 @@ struct dwc2_hsotg { u8 otg_port; u32 *frame_list; dma_addr_t frame_list_dma; + u32 frame_list_sz; + struct kmem_cache *desc_gen_cache; + struct kmem_cache *desc_hsisoc_cache; #ifdef DEBUG u32 frrem_samples; @@ -864,10 +883,14 @@ enum dwc2_halt_status { * The following functions support initialization of the core driver component * and the DWC_otg controller */ +extern int dwc2_core_reset(struct dwc2_hsotg *hsotg); +extern int dwc2_core_reset_and_force_dr_mode(struct dwc2_hsotg *hsotg); extern void dwc2_core_host_init(struct dwc2_hsotg *hsotg); extern int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg); extern int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, bool restore); +void dwc2_force_dr_mode(struct dwc2_hsotg *hsotg); + /* * Host core Functions. * The following functions support managing the DWC_otg controller in host @@ -901,7 +924,7 @@ extern void dwc2_read_packet(struct dwc2_hsotg *hsotg, u8 *dest, u16 bytes); extern void dwc2_flush_tx_fifo(struct dwc2_hsotg *hsotg, const int num); extern void dwc2_flush_rx_fifo(struct dwc2_hsotg *hsotg); -extern int dwc2_core_init(struct dwc2_hsotg *hsotg, bool select_phy, int irq); +extern int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup); extern void dwc2_enable_global_interrupts(struct dwc2_hsotg *hcd); extern void dwc2_disable_global_interrupts(struct dwc2_hsotg *hcd); @@ -941,6 +964,16 @@ extern void dwc2_set_param_dma_enable(struct dwc2_hsotg *hsotg, int val); */ extern void dwc2_set_param_dma_desc_enable(struct dwc2_hsotg *hsotg, int val); +/* + * When DMA mode is enabled specifies whether to use + * address DMA or DMA Descritor mode with full speed devices + * for accessing the data FIFOs in host mode. + * 0 - address DMA + * 1 - FS DMA Descriptor(default, if available) + */ +extern void dwc2_set_param_dma_desc_fs_enable(struct dwc2_hsotg *hsotg, + int val); + /* * Specifies the maximum speed of operation in host and device mode. * The actual speed depends on the speed of the attached device and @@ -1109,6 +1142,31 @@ extern int dwc2_get_hwparams(struct dwc2_hsotg *hsotg); extern int dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg); extern int dwc2_lowlevel_hw_disable(struct dwc2_hsotg *hsotg); +/* + * The following functions check the controller's OTG operation mode + * capability (GHWCFG2.OTG_MODE). + * + * These functions can be used before the internal hsotg->hw_params + * are read in and cached so they always read directly from the + * GHWCFG2 register. + */ +unsigned dwc2_op_mode(struct dwc2_hsotg *hsotg); +bool dwc2_hw_is_otg(struct dwc2_hsotg *hsotg); +bool dwc2_hw_is_host(struct dwc2_hsotg *hsotg); +bool dwc2_hw_is_device(struct dwc2_hsotg *hsotg); + +/* + * Returns the mode of operation, host or device + */ +static inline int dwc2_is_host_mode(struct dwc2_hsotg *hsotg) +{ + return (dwc2_readl(hsotg->regs + GINTSTS) & GINTSTS_CURMODE_HOST) != 0; +} +static inline int dwc2_is_device_mode(struct dwc2_hsotg *hsotg) +{ + return (dwc2_readl(hsotg->regs + GINTSTS) & GINTSTS_CURMODE_HOST) == 0; +} + /* * Dump core registers and SPRAM */ @@ -1154,12 +1212,14 @@ static inline int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, #if IS_ENABLED(CONFIG_USB_DWC2_HOST) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) extern int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg); -extern void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg); +extern void dwc2_hcd_connect(struct dwc2_hsotg *hsotg); +extern void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg, bool force); extern void dwc2_hcd_start(struct dwc2_hsotg *hsotg); #else static inline int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg) { return 0; } -static inline void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg) {} +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_hcd_init(struct dwc2_hsotg *hsotg, int irq) diff --git a/drivers/usb/dwc2/core_intr.c b/drivers/usb/dwc2/core_intr.c index 27daa42788b1..d85c5c9f96c1 100644 --- a/drivers/usb/dwc2/core_intr.c +++ b/drivers/usb/dwc2/core_intr.c @@ -86,9 +86,6 @@ static void dwc2_handle_usb_port_intr(struct dwc2_hsotg *hsotg) hprt0 &= ~HPRT0_ENA; dwc2_writel(hprt0, hsotg->regs + HPRT0); } - - /* Clear interrupt */ - dwc2_writel(GINTSTS_PRTINT, hsotg->regs + GINTSTS); } /** @@ -98,11 +95,11 @@ static void dwc2_handle_usb_port_intr(struct dwc2_hsotg *hsotg) */ static void dwc2_handle_mode_mismatch_intr(struct dwc2_hsotg *hsotg) { - dev_warn(hsotg->dev, "Mode Mismatch Interrupt: currently in %s mode\n", - dwc2_is_host_mode(hsotg) ? "Host" : "Device"); - /* Clear interrupt */ dwc2_writel(GINTSTS_MODEMIS, hsotg->regs + GINTSTS); + + dev_warn(hsotg->dev, "Mode Mismatch Interrupt: currently in %s mode\n", + dwc2_is_host_mode(hsotg) ? "Host" : "Device"); } /** @@ -239,7 +236,7 @@ static void dwc2_handle_otg_intr(struct dwc2_hsotg *hsotg) dev_dbg(hsotg->dev, "a_suspend->a_peripheral (%d)\n", hsotg->op_state); spin_unlock(&hsotg->lock); - dwc2_hcd_disconnect(hsotg); + dwc2_hcd_disconnect(hsotg, false); spin_lock(&hsotg->lock); hsotg->op_state = OTG_STATE_A_PERIPHERAL; } else { @@ -276,9 +273,13 @@ static void dwc2_handle_otg_intr(struct dwc2_hsotg *hsotg) */ static void dwc2_handle_conn_id_status_change_intr(struct dwc2_hsotg *hsotg) { - u32 gintmsk = dwc2_readl(hsotg->regs + GINTMSK); + u32 gintmsk; + + /* Clear interrupt */ + dwc2_writel(GINTSTS_CONIDSTSCHNG, hsotg->regs + GINTSTS); /* Need to disable SOF interrupt immediately */ + gintmsk = dwc2_readl(hsotg->regs + GINTMSK); gintmsk &= ~GINTSTS_SOF; dwc2_writel(gintmsk, hsotg->regs + GINTMSK); @@ -295,9 +296,6 @@ static void dwc2_handle_conn_id_status_change_intr(struct dwc2_hsotg *hsotg) queue_work(hsotg->wq_otg, &hsotg->wf_otg); spin_lock(&hsotg->lock); } - - /* Clear interrupt */ - dwc2_writel(GINTSTS_CONIDSTSCHNG, hsotg->regs + GINTSTS); } /** @@ -315,12 +313,12 @@ static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg) { int ret; - dev_dbg(hsotg->dev, "Session request interrupt - lx_state=%d\n", - hsotg->lx_state); - /* Clear interrupt */ dwc2_writel(GINTSTS_SESSREQINT, hsotg->regs + GINTSTS); + dev_dbg(hsotg->dev, "Session request interrupt - lx_state=%d\n", + hsotg->lx_state); + if (dwc2_is_device_mode(hsotg)) { if (hsotg->lx_state == DWC2_L2) { ret = dwc2_exit_hibernation(hsotg, true); @@ -347,6 +345,10 @@ static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg) static void dwc2_handle_wakeup_detected_intr(struct dwc2_hsotg *hsotg) { int ret; + + /* Clear interrupt */ + dwc2_writel(GINTSTS_WKUPINT, hsotg->regs + GINTSTS); + dev_dbg(hsotg->dev, "++Resume or Remote Wakeup Detected Interrupt++\n"); dev_dbg(hsotg->dev, "%s lxstate = %d\n", __func__, hsotg->lx_state); @@ -368,10 +370,9 @@ static void dwc2_handle_wakeup_detected_intr(struct dwc2_hsotg *hsotg) /* Change to L0 state */ hsotg->lx_state = DWC2_L0; } else { - if (hsotg->core_params->hibernation) { - dwc2_writel(GINTSTS_WKUPINT, hsotg->regs + GINTSTS); + if (hsotg->core_params->hibernation) return; - } + if (hsotg->lx_state != DWC2_L1) { u32 pcgcctl = dwc2_readl(hsotg->regs + PCGCTL); @@ -385,9 +386,6 @@ static void dwc2_handle_wakeup_detected_intr(struct dwc2_hsotg *hsotg) hsotg->lx_state = DWC2_L0; } } - - /* Clear interrupt */ - dwc2_writel(GINTSTS_WKUPINT, hsotg->regs + GINTSTS); } /* @@ -396,14 +394,14 @@ static void dwc2_handle_wakeup_detected_intr(struct dwc2_hsotg *hsotg) */ static void dwc2_handle_disconnect_intr(struct dwc2_hsotg *hsotg) { + dwc2_writel(GINTSTS_DISCONNINT, hsotg->regs + GINTSTS); + dev_dbg(hsotg->dev, "++Disconnect Detected Interrupt++ (%s) %s\n", dwc2_is_host_mode(hsotg) ? "Host" : "Device", dwc2_op_state_str(hsotg)); if (hsotg->op_state == OTG_STATE_A_HOST) - dwc2_hcd_disconnect(hsotg); - - dwc2_writel(GINTSTS_DISCONNINT, hsotg->regs + GINTSTS); + dwc2_hcd_disconnect(hsotg, false); } /* @@ -419,6 +417,9 @@ static void dwc2_handle_usb_suspend_intr(struct dwc2_hsotg *hsotg) u32 dsts; int ret; + /* Clear interrupt */ + dwc2_writel(GINTSTS_USBSUSP, hsotg->regs + GINTSTS); + dev_dbg(hsotg->dev, "USB SUSPEND\n"); if (dwc2_is_device_mode(hsotg)) { @@ -437,7 +438,7 @@ static void dwc2_handle_usb_suspend_intr(struct dwc2_hsotg *hsotg) if (!dwc2_is_device_connected(hsotg)) { dev_dbg(hsotg->dev, "ignore suspend request before enumeration\n"); - goto clear_int; + return; } ret = dwc2_enter_hibernation(hsotg); @@ -476,10 +477,6 @@ skip_power_saving: hsotg->op_state = OTG_STATE_A_HOST; } } - -clear_int: - /* Clear interrupt */ - dwc2_writel(GINTSTS_USBSUSP, hsotg->regs + GINTSTS); } #define GINTMSK_COMMON (GINTSTS_WKUPINT | GINTSTS_SESSREQINT | \ diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 0abf73c91beb..422ab7da4eb5 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2095,7 +2095,7 @@ static void dwc2_hsotg_irq_enumdone(struct dwc2_hsotg *hsotg) */ /* catch both EnumSpd_FS and EnumSpd_FS48 */ - switch (dsts & DSTS_ENUMSPD_MASK) { + switch ((dsts & DSTS_ENUMSPD_MASK) >> DSTS_ENUMSPD_SHIFT) { case DSTS_ENUMSPD_FS: case DSTS_ENUMSPD_FS48: hsotg->gadget.speed = USB_SPEED_FULL; @@ -2243,54 +2243,6 @@ static void dwc2_hsotg_irq_fifoempty(struct dwc2_hsotg *hsotg, bool periodic) GINTSTS_PTXFEMP | \ GINTSTS_RXFLVL) -/** - * dwc2_hsotg_corereset - issue softreset to the core - * @hsotg: The device state - * - * Issue a soft reset to the core, and await the core finishing it. - */ -static int dwc2_hsotg_corereset(struct dwc2_hsotg *hsotg) -{ - int timeout; - u32 grstctl; - - dev_dbg(hsotg->dev, "resetting core\n"); - - /* issue soft reset */ - dwc2_writel(GRSTCTL_CSFTRST, hsotg->regs + GRSTCTL); - - timeout = 10000; - do { - grstctl = dwc2_readl(hsotg->regs + GRSTCTL); - } while ((grstctl & GRSTCTL_CSFTRST) && timeout-- > 0); - - if (grstctl & GRSTCTL_CSFTRST) { - dev_err(hsotg->dev, "Failed to get CSftRst asserted\n"); - return -EINVAL; - } - - timeout = 10000; - - while (1) { - u32 grstctl = dwc2_readl(hsotg->regs + GRSTCTL); - - if (timeout-- < 0) { - dev_info(hsotg->dev, - "%s: reset failed, GRSTCTL=%08x\n", - __func__, grstctl); - return -ETIMEDOUT; - } - - if (!(grstctl & GRSTCTL_AHBIDLE)) - continue; - - break; /* reset done */ - } - - dev_dbg(hsotg->dev, "reset successful\n"); - return 0; -} - /** * dwc2_hsotg_core_init - issue softreset to the core * @hsotg: The device state @@ -2307,7 +2259,7 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, kill_all_requests(hsotg, hsotg->eps_out[0], -ECONNRESET); if (!is_usb_reset) - if (dwc2_hsotg_corereset(hsotg)) + if (dwc2_core_reset(hsotg)) return; /* @@ -2585,7 +2537,7 @@ irq_retry: if (gintsts & GINTSTS_GOUTNAKEFF) { dev_info(hsotg->dev, "GOUTNakEff triggered\n"); - dwc2_writel(DCTL_CGOUTNAK, hsotg->regs + DCTL); + __orr32(hsotg->regs + DCTL, DCTL_CGOUTNAK); dwc2_hsotg_dump(hsotg); } @@ -2593,7 +2545,7 @@ irq_retry: if (gintsts & GINTSTS_GINNAKEFF) { dev_info(hsotg->dev, "GINNakEff triggered\n"); - dwc2_writel(DCTL_CGNPINNAK, hsotg->regs + DCTL); + __orr32(hsotg->regs + DCTL, DCTL_CGNPINNAK); dwc2_hsotg_dump(hsotg); } @@ -2911,15 +2863,15 @@ static void dwc2_hsotg_ep_stop_xfr(struct dwc2_hsotg *hsotg, "%s: timeout DIEPINT.NAKEFF\n", __func__); } else { /* Clear any pending nak effect interrupt */ - dwc2_writel(GINTSTS_GINNAKEFF, hsotg->regs + GINTSTS); + dwc2_writel(GINTSTS_GOUTNAKEFF, hsotg->regs + GINTSTS); - __orr32(hsotg->regs + DCTL, DCTL_SGNPINNAK); + __orr32(hsotg->regs + DCTL, DCTL_SGOUTNAK); /* Wait for global nak to take effect */ if (dwc2_hsotg_wait_bit_set(hsotg, GINTSTS, - GINTSTS_GINNAKEFF, 100)) + GINTSTS_GOUTNAKEFF, 100)) dev_warn(hsotg->dev, - "%s: timeout GINTSTS.GINNAKEFF\n", __func__); + "%s: timeout GINTSTS.GOUTNAKEFF\n", __func__); } /* Disable ep */ @@ -2944,7 +2896,7 @@ static void dwc2_hsotg_ep_stop_xfr(struct dwc2_hsotg *hsotg, /* TODO: Flush shared tx fifo */ } else { /* Remove global NAKs */ - __bic32(hsotg->regs + DCTL, DCTL_SGNPINNAK); + __bic32(hsotg->regs + DCTL, DCTL_SGOUTNAK); } } @@ -3403,8 +3355,8 @@ static int dwc2_hsotg_hw_cfg(struct dwc2_hsotg *hsotg) /* check hardware configuration */ - cfg = dwc2_readl(hsotg->regs + GHWCFG2); - hsotg->num_of_eps = (cfg >> GHWCFG2_NUM_DEV_EP_SHIFT) & 0xF; + hsotg->num_of_eps = hsotg->hw_params.num_dev_ep; + /* Add ep0 */ hsotg->num_of_eps++; @@ -3415,7 +3367,7 @@ static int dwc2_hsotg_hw_cfg(struct dwc2_hsotg *hsotg) /* Same dwc2_hsotg_ep is used in both directions for ep0 */ hsotg->eps_out[0] = hsotg->eps_in[0]; - cfg = dwc2_readl(hsotg->regs + GHWCFG1); + cfg = hsotg->hw_params.dev_ep_dirs; for (i = 1, cfg >>= 2; i < hsotg->num_of_eps; i++, cfg >>= 2) { ep_type = cfg & 3; /* Direction in or both */ @@ -3434,11 +3386,8 @@ static int dwc2_hsotg_hw_cfg(struct dwc2_hsotg *hsotg) } } - cfg = dwc2_readl(hsotg->regs + GHWCFG3); - hsotg->fifo_mem = (cfg >> GHWCFG3_DFIFO_DEPTH_SHIFT); - - cfg = dwc2_readl(hsotg->regs + GHWCFG4); - hsotg->dedicated_fifos = (cfg >> GHWCFG4_DED_FIFO_SHIFT) & 1; + hsotg->fifo_mem = hsotg->hw_params.total_fifo_size; + hsotg->dedicated_fifos = hsotg->hw_params.en_multiple_tx_fifo; dev_info(hsotg->dev, "EPs: %d, %s fifos, %d entries in SPRAM\n", hsotg->num_of_eps, @@ -3563,6 +3512,17 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) memcpy(&hsotg->g_tx_fifo_sz[1], p_tx_fifo, sizeof(p_tx_fifo)); /* Device tree specific probe */ dwc2_hsotg_of_probe(hsotg); + + /* Check against largest possible value. */ + if (hsotg->g_np_g_tx_fifo_sz > + hsotg->hw_params.dev_nperio_tx_fifo_size) { + dev_warn(dev, "Specified GNPTXFDEP=%d > %d\n", + hsotg->g_np_g_tx_fifo_sz, + hsotg->hw_params.dev_nperio_tx_fifo_size); + hsotg->g_np_g_tx_fifo_sz = + hsotg->hw_params.dev_nperio_tx_fifo_size; + } + /* Dump fifo information */ dev_dbg(dev, "NonPeriodic TXFIFO size: %d\n", hsotg->g_np_g_tx_fifo_sz); @@ -3579,31 +3539,12 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) else if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) hsotg->op_state = OTG_STATE_B_PERIPHERAL; - /* - * Force Device mode before initialization. - * This allows correctly configuring fifo for device mode. - */ - __bic32(hsotg->regs + GUSBCFG, GUSBCFG_FORCEHOSTMODE); - __orr32(hsotg->regs + GUSBCFG, GUSBCFG_FORCEDEVMODE); - - /* - * According to Synopsys databook, this sleep is needed for the force - * device mode to take effect. - */ - msleep(25); - - dwc2_hsotg_corereset(hsotg); ret = dwc2_hsotg_hw_cfg(hsotg); if (ret) { dev_err(hsotg->dev, "Hardware configuration failed: %d\n", ret); return ret; } - dwc2_hsotg_init(hsotg); - - /* Switch back to default configuration */ - __bic32(hsotg->regs + GUSBCFG, GUSBCFG_FORCEDEVMODE); - hsotg->ctrl_buff = devm_kzalloc(hsotg->dev, DWC2_CTRL_BUFF_SIZE, GFP_KERNEL); if (!hsotg->ctrl_buff) { diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 571c21727ff9..8847c72e55f6 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -268,15 +268,33 @@ static void dwc2_hcd_cleanup_channels(struct dwc2_hsotg *hsotg) } /** - * dwc2_hcd_disconnect() - Handles disconnect of the HCD + * dwc2_hcd_connect() - Handles connect of the HCD * * @hsotg: Pointer to struct dwc2_hsotg * * Must be called with interrupt disabled and spinlock held */ -void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg) +void dwc2_hcd_connect(struct dwc2_hsotg *hsotg) +{ + if (hsotg->lx_state != DWC2_L0) + usb_hcd_resume_root_hub(hsotg->priv); + + hsotg->flags.b.port_connect_status_change = 1; + hsotg->flags.b.port_connect_status = 1; +} + +/** + * dwc2_hcd_disconnect() - Handles disconnect of the HCD + * + * @hsotg: Pointer to struct dwc2_hsotg + * @force: If true, we won't try to reconnect even if we see device connected. + * + * Must be called with interrupt disabled and spinlock held + */ +void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg, bool force) { u32 intr; + u32 hprt0; /* Set status flags for the hub driver */ hsotg->flags.b.port_connect_status_change = 1; @@ -315,6 +333,24 @@ void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg) dwc2_hcd_cleanup_channels(hsotg); dwc2_host_disconnect(hsotg); + + /* + * Add an extra check here to see if we're actually connected but + * we don't have a detection interrupt pending. This can happen if: + * 1. hardware sees connect + * 2. hardware sees disconnect + * 3. hardware sees connect + * 4. dwc2_port_intr() - clears connect interrupt + * 5. dwc2_handle_common_intr() - calls here + * + * Without the extra check here we will end calling disconnect + * and won't get any future interrupts to handle the connect. + */ + if (!force) { + hprt0 = dwc2_readl(hsotg->regs + HPRT0); + if (!(hprt0 & HPRT0_CONNDET) && (hprt0 & HPRT0_CONNSTS)) + dwc2_hcd_connect(hsotg); + } } /** @@ -881,8 +917,10 @@ static int dwc2_assign_and_init_hc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) */ chan->multi_count = dwc2_hb_mult(qh->maxp); - if (hsotg->core_params->dma_desc_enable > 0) + if (hsotg->core_params->dma_desc_enable > 0) { chan->desc_list_addr = qh->desc_list_dma; + chan->desc_list_sz = qh->desc_list_sz; + } dwc2_hc_init(hsotg, chan); chan->qh = qh; @@ -1382,7 +1420,7 @@ static void dwc2_conn_id_status_change(struct work_struct *work) dev_err(hsotg->dev, "Connection id status change timed out\n"); hsotg->op_state = OTG_STATE_B_PERIPHERAL; - dwc2_core_init(hsotg, false, -1); + dwc2_core_init(hsotg, false); dwc2_enable_global_interrupts(hsotg); spin_lock_irqsave(&hsotg->lock, flags); dwc2_hsotg_core_init_disconnected(hsotg, false); @@ -1405,7 +1443,7 @@ static void dwc2_conn_id_status_change(struct work_struct *work) hsotg->op_state = OTG_STATE_A_HOST; /* Initialize the Core for Host mode */ - dwc2_core_init(hsotg, false, -1); + dwc2_core_init(hsotg, false); dwc2_enable_global_interrupts(hsotg); dwc2_hcd_start(hsotg); } @@ -1734,6 +1772,28 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, port_status |= USB_PORT_STAT_TEST; /* USB_PORT_FEAT_INDICATOR unsupported always 0 */ + if (hsotg->core_params->dma_desc_fs_enable) { + /* + * Enable descriptor DMA only if a full speed + * device is connected. + */ + if (hsotg->new_connection && + ((port_status & + (USB_PORT_STAT_CONNECTION | + USB_PORT_STAT_HIGH_SPEED | + USB_PORT_STAT_LOW_SPEED)) == + USB_PORT_STAT_CONNECTION)) { + u32 hcfg; + + dev_info(hsotg->dev, "Enabling descriptor DMA mode\n"); + hsotg->core_params->dma_desc_enable = 1; + hcfg = dwc2_readl(hsotg->regs + HCFG); + hcfg |= HCFG_DESCDMA; + dwc2_writel(hcfg, hsotg->regs + HCFG); + hsotg->new_connection = false; + } + } + dev_vdbg(hsotg->dev, "port_status=%08x\n", port_status); *(__le32 *)buf = cpu_to_le32(port_status); break; @@ -2298,13 +2358,19 @@ static void dwc2_hcd_reset_func(struct work_struct *work) { struct dwc2_hsotg *hsotg = container_of(work, struct dwc2_hsotg, reset_work.work); + unsigned long flags; u32 hprt0; dev_dbg(hsotg->dev, "USB RESET function called\n"); + + spin_lock_irqsave(&hsotg->lock, flags); + hprt0 = dwc2_read_hprt0(hsotg); hprt0 &= ~HPRT0_RST; dwc2_writel(hprt0, hsotg->regs + HPRT0); hsotg->flags.b.port_reset_change = 1; + + spin_unlock_irqrestore(&hsotg->lock, flags); } /* @@ -2366,7 +2432,7 @@ static void _dwc2_hcd_stop(struct usb_hcd *hcd) spin_lock_irqsave(&hsotg->lock, flags); /* Ensure hcd is disconnected */ - dwc2_hcd_disconnect(hsotg); + dwc2_hcd_disconnect(hsotg, true); dwc2_hcd_stop(hsotg); hsotg->lx_state = DWC2_L3; hcd->state = HC_STATE_HALT; @@ -3054,7 +3120,7 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg, int irq) dwc2_disable_global_interrupts(hsotg); /* Initialize the DWC_otg core, and select the Phy type */ - retval = dwc2_core_init(hsotg, true, irq); + retval = dwc2_core_init(hsotg, true); if (retval) goto error2; @@ -3122,6 +3188,47 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg, int irq) if (!hsotg->status_buf) goto error3; + /* + * Create kmem caches to handle descriptor buffers in descriptor + * DMA mode. + * Alignment must be set to 512 bytes. + */ + if (hsotg->core_params->dma_desc_enable || + hsotg->core_params->dma_desc_fs_enable) { + hsotg->desc_gen_cache = kmem_cache_create("dwc2-gen-desc", + sizeof(struct dwc2_hcd_dma_desc) * + MAX_DMA_DESC_NUM_GENERIC, 512, SLAB_CACHE_DMA, + NULL); + if (!hsotg->desc_gen_cache) { + dev_err(hsotg->dev, + "unable to create dwc2 generic desc cache\n"); + + /* + * Disable descriptor dma mode since it will not be + * usable. + */ + hsotg->core_params->dma_desc_enable = 0; + hsotg->core_params->dma_desc_fs_enable = 0; + } + + hsotg->desc_hsisoc_cache = kmem_cache_create("dwc2-hsisoc-desc", + sizeof(struct dwc2_hcd_dma_desc) * + MAX_DMA_DESC_NUM_HS_ISOC, 512, 0, NULL); + if (!hsotg->desc_hsisoc_cache) { + dev_err(hsotg->dev, + "unable to create dwc2 hs isoc desc cache\n"); + + kmem_cache_destroy(hsotg->desc_gen_cache); + + /* + * Disable descriptor dma mode since it will not be + * usable. + */ + hsotg->core_params->dma_desc_enable = 0; + hsotg->core_params->dma_desc_fs_enable = 0; + } + } + hsotg->otg_port = 1; hsotg->frame_list = NULL; hsotg->frame_list_dma = 0; @@ -3145,7 +3252,7 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg, int irq) */ retval = usb_add_hcd(hcd, irq, IRQF_SHARED); if (retval < 0) - goto error3; + goto error4; device_wakeup_enable(hcd->self.controller); @@ -3155,6 +3262,9 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg, int irq) return 0; +error4: + kmem_cache_destroy(hsotg->desc_gen_cache); + kmem_cache_destroy(hsotg->desc_hsisoc_cache); error3: dwc2_hcd_release(hsotg); error2: @@ -3195,6 +3305,10 @@ void dwc2_hcd_remove(struct dwc2_hsotg *hsotg) usb_remove_hcd(hcd); hsotg->priv = NULL; + + kmem_cache_destroy(hsotg->desc_gen_cache); + kmem_cache_destroy(hsotg->desc_hsisoc_cache); + dwc2_hcd_release(hsotg); usb_put_hcd(hcd); diff --git a/drivers/usb/dwc2/hcd.h b/drivers/usb/dwc2/hcd.h index f105bada2fd1..8f0a29cefdf7 100644 --- a/drivers/usb/dwc2/hcd.h +++ b/drivers/usb/dwc2/hcd.h @@ -107,6 +107,7 @@ struct dwc2_qh; * @qh: QH for the transfer being processed by this channel * @hc_list_entry: For linking to list of host channels * @desc_list_addr: Current QH's descriptor list DMA address + * @desc_list_sz: Current QH's descriptor list size * * This structure represents the state of a single host channel when acting in * host mode. It contains the data items needed to transfer packets to an @@ -159,6 +160,7 @@ struct dwc2_host_chan { struct dwc2_qh *qh; struct list_head hc_list_entry; dma_addr_t desc_list_addr; + u32 desc_list_sz; }; struct dwc2_hcd_pipe_info { @@ -251,6 +253,7 @@ enum dwc2_transaction_type { * schedule * @desc_list: List of transfer descriptors * @desc_list_dma: Physical address of desc_list + * @desc_list_sz: Size of descriptors list * @n_bytes: Xfer Bytes array. Each element corresponds to a transfer * descriptor and indicates original XferSize value for the * descriptor @@ -284,6 +287,7 @@ struct dwc2_qh { struct list_head qh_list_entry; struct dwc2_hcd_dma_desc *desc_list; dma_addr_t desc_list_dma; + u32 desc_list_sz; u32 *n_bytes; unsigned tt_buffer_dirty:1; }; @@ -340,6 +344,8 @@ struct dwc2_qtd { u8 isoc_split_pos; u16 isoc_frame_index; u16 isoc_split_offset; + u16 isoc_td_last; + u16 isoc_td_first; u32 ssplit_out_xfer_count; u8 error_count; u8 n_desc; @@ -377,18 +383,6 @@ static inline void disable_hc_int(struct dwc2_hsotg *hsotg, int chnum, u32 intr) dwc2_writel(mask, hsotg->regs + HCINTMSK(chnum)); } -/* - * Returns the mode of operation, host or device - */ -static inline int dwc2_is_host_mode(struct dwc2_hsotg *hsotg) -{ - return (dwc2_readl(hsotg->regs + GINTSTS) & GINTSTS_CURMODE_HOST) != 0; -} -static inline int dwc2_is_device_mode(struct dwc2_hsotg *hsotg) -{ - return (dwc2_readl(hsotg->regs + GINTSTS) & GINTSTS_CURMODE_HOST) == 0; -} - /* * Reads HPRT0 in preparation to modify. It keeps the WC bits 0 so that if they * are read as 1, they won't clear when written back. @@ -534,6 +528,19 @@ static inline bool dbg_perio(void) { return false; } /* Packet size for any kind of endpoint descriptor */ #define dwc2_max_packet(wmaxpacketsize) ((wmaxpacketsize) & 0x07ff) +/* + * Returns true if frame1 index is greater than frame2 index. The comparison + * is done modulo FRLISTEN_64_SIZE. This accounts for the rollover of the + * frame number when the max index frame number is reached. + */ +static inline bool dwc2_frame_idx_num_gt(u16 fr_idx1, u16 fr_idx2) +{ + u16 diff = fr_idx1 - fr_idx2; + u16 sign = diff & (FRLISTEN_64_SIZE >> 1); + + return diff && !sign; +} + /* * Returns true if frame1 is less than or equal to frame2. The comparison is * done modulo HFNUM_MAX_FRNUM. This accounts for the rollover of the diff --git a/drivers/usb/dwc2/hcd_ddma.c b/drivers/usb/dwc2/hcd_ddma.c index 78993aba9335..36606fc33c0d 100644 --- a/drivers/usb/dwc2/hcd_ddma.c +++ b/drivers/usb/dwc2/hcd_ddma.c @@ -87,22 +87,31 @@ static u16 dwc2_frame_incr_val(struct dwc2_qh *qh) static int dwc2_desc_list_alloc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, gfp_t flags) { - qh->desc_list = dma_alloc_coherent(hsotg->dev, - sizeof(struct dwc2_hcd_dma_desc) * - dwc2_max_desc_num(qh), &qh->desc_list_dma, - flags); + struct kmem_cache *desc_cache; + if (qh->ep_type == USB_ENDPOINT_XFER_ISOC + && qh->dev_speed == USB_SPEED_HIGH) + desc_cache = hsotg->desc_hsisoc_cache; + else + desc_cache = hsotg->desc_gen_cache; + + qh->desc_list_sz = sizeof(struct dwc2_hcd_dma_desc) * + dwc2_max_desc_num(qh); + + qh->desc_list = kmem_cache_zalloc(desc_cache, flags | GFP_DMA); if (!qh->desc_list) return -ENOMEM; - memset(qh->desc_list, 0, - sizeof(struct dwc2_hcd_dma_desc) * dwc2_max_desc_num(qh)); + qh->desc_list_dma = dma_map_single(hsotg->dev, qh->desc_list, + qh->desc_list_sz, + DMA_TO_DEVICE); qh->n_bytes = kzalloc(sizeof(u32) * dwc2_max_desc_num(qh), flags); if (!qh->n_bytes) { - dma_free_coherent(hsotg->dev, sizeof(struct dwc2_hcd_dma_desc) - * dwc2_max_desc_num(qh), qh->desc_list, - qh->desc_list_dma); + dma_unmap_single(hsotg->dev, qh->desc_list_dma, + qh->desc_list_sz, + DMA_FROM_DEVICE); + kfree(qh->desc_list); qh->desc_list = NULL; return -ENOMEM; } @@ -112,10 +121,18 @@ static int dwc2_desc_list_alloc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, static void dwc2_desc_list_free(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) { + struct kmem_cache *desc_cache; + + if (qh->ep_type == USB_ENDPOINT_XFER_ISOC + && qh->dev_speed == USB_SPEED_HIGH) + desc_cache = hsotg->desc_hsisoc_cache; + else + desc_cache = hsotg->desc_gen_cache; + if (qh->desc_list) { - dma_free_coherent(hsotg->dev, sizeof(struct dwc2_hcd_dma_desc) - * dwc2_max_desc_num(qh), qh->desc_list, - qh->desc_list_dma); + dma_unmap_single(hsotg->dev, qh->desc_list_dma, + qh->desc_list_sz, DMA_FROM_DEVICE); + kmem_cache_free(desc_cache, qh->desc_list); qh->desc_list = NULL; } @@ -128,21 +145,20 @@ static int dwc2_frame_list_alloc(struct dwc2_hsotg *hsotg, gfp_t mem_flags) if (hsotg->frame_list) return 0; - hsotg->frame_list = dma_alloc_coherent(hsotg->dev, - 4 * FRLISTEN_64_SIZE, - &hsotg->frame_list_dma, - mem_flags); + hsotg->frame_list_sz = 4 * FRLISTEN_64_SIZE; + hsotg->frame_list = kzalloc(hsotg->frame_list_sz, GFP_ATOMIC | GFP_DMA); if (!hsotg->frame_list) return -ENOMEM; - memset(hsotg->frame_list, 0, 4 * FRLISTEN_64_SIZE); + hsotg->frame_list_dma = dma_map_single(hsotg->dev, hsotg->frame_list, + hsotg->frame_list_sz, + DMA_TO_DEVICE); + return 0; } static void dwc2_frame_list_free(struct dwc2_hsotg *hsotg) { - u32 *frame_list; - dma_addr_t frame_list_dma; unsigned long flags; spin_lock_irqsave(&hsotg->lock, flags); @@ -152,14 +168,14 @@ static void dwc2_frame_list_free(struct dwc2_hsotg *hsotg) return; } - frame_list = hsotg->frame_list; - frame_list_dma = hsotg->frame_list_dma; + dma_unmap_single(hsotg->dev, hsotg->frame_list_dma, + hsotg->frame_list_sz, DMA_FROM_DEVICE); + + kfree(hsotg->frame_list); hsotg->frame_list = NULL; spin_unlock_irqrestore(&hsotg->lock, flags); - dma_free_coherent(hsotg->dev, 4 * FRLISTEN_64_SIZE, frame_list, - frame_list_dma); } static void dwc2_per_sched_enable(struct dwc2_hsotg *hsotg, u32 fr_list_en) @@ -249,6 +265,15 @@ static void dwc2_update_frame_list(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, j = (j + inc) & (FRLISTEN_64_SIZE - 1); } while (j != i); + /* + * Sync frame list since controller will access it if periodic + * channel is currently enabled. + */ + dma_sync_single_for_device(hsotg->dev, + hsotg->frame_list_dma, + hsotg->frame_list_sz, + DMA_TO_DEVICE); + if (!enable) return; @@ -278,6 +303,7 @@ static void dwc2_release_channel_ddma(struct dwc2_hsotg *hsotg, hsotg->non_periodic_channels--; } else { dwc2_update_frame_list(hsotg, qh, 0); + hsotg->available_host_channels++; } /* @@ -360,6 +386,8 @@ err0: */ void dwc2_hcd_qh_free_ddma(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) { + unsigned long flags; + dwc2_desc_list_free(hsotg, qh); /* @@ -369,8 +397,10 @@ void dwc2_hcd_qh_free_ddma(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) * when it comes here from endpoint disable routine * channel remains assigned. */ + spin_lock_irqsave(&hsotg->lock, flags); if (qh->channel) dwc2_release_channel_ddma(hsotg, qh); + spin_unlock_irqrestore(&hsotg->lock, flags); if ((qh->ep_type == USB_ENDPOINT_XFER_ISOC || qh->ep_type == USB_ENDPOINT_XFER_INT) && @@ -524,14 +554,23 @@ static void dwc2_fill_host_isoc_dma_desc(struct dwc2_hsotg *hsotg, dma_desc->status = qh->n_bytes[idx] << HOST_DMA_ISOC_NBYTES_SHIFT & HOST_DMA_ISOC_NBYTES_MASK; + /* Set active bit */ + dma_desc->status |= HOST_DMA_A; + + qh->ntd++; + qtd->isoc_frame_index_last++; + #ifdef ISOC_URB_GIVEBACK_ASAP /* Set IOC for each descriptor corresponding to last frame of URB */ if (qtd->isoc_frame_index_last == qtd->urb->packet_count) dma_desc->status |= HOST_DMA_IOC; #endif - qh->ntd++; - qtd->isoc_frame_index_last++; + dma_sync_single_for_device(hsotg->dev, + qh->desc_list_dma + + (idx * sizeof(struct dwc2_hcd_dma_desc)), + sizeof(struct dwc2_hcd_dma_desc), + DMA_TO_DEVICE); } static void dwc2_init_isoc_dma_desc(struct dwc2_hsotg *hsotg, @@ -539,11 +578,32 @@ static void dwc2_init_isoc_dma_desc(struct dwc2_hsotg *hsotg, { struct dwc2_qtd *qtd; u32 max_xfer_size; - u16 idx, inc, n_desc, ntd_max = 0; + u16 idx, inc, n_desc = 0, ntd_max = 0; + u16 cur_idx; + u16 next_idx; idx = qh->td_last; inc = qh->interval; - n_desc = 0; + hsotg->frame_number = dwc2_hcd_get_frame_number(hsotg); + cur_idx = dwc2_frame_list_idx(hsotg->frame_number); + next_idx = dwc2_desclist_idx_inc(qh->td_last, inc, qh->dev_speed); + + /* + * Ensure current frame number didn't overstep last scheduled + * descriptor. If it happens, the only way to recover is to move + * qh->td_last to current frame number + 1. + * So that next isoc descriptor will be scheduled on frame number + 1 + * and not on a past frame. + */ + if (dwc2_frame_idx_num_gt(cur_idx, next_idx) || (cur_idx == next_idx)) { + if (inc < 32) { + dev_vdbg(hsotg->dev, + "current frame number overstep last descriptor\n"); + qh->td_last = dwc2_desclist_idx_inc(cur_idx, inc, + qh->dev_speed); + idx = qh->td_last; + } + } if (qh->interval) { ntd_max = (dwc2_max_desc_num(qh) + qh->interval - 1) / @@ -556,15 +616,20 @@ static void dwc2_init_isoc_dma_desc(struct dwc2_hsotg *hsotg, MAX_ISOC_XFER_SIZE_HS : MAX_ISOC_XFER_SIZE_FS; list_for_each_entry(qtd, &qh->qtd_list, qtd_list_entry) { + if (qtd->in_process && + qtd->isoc_frame_index_last == + qtd->urb->packet_count) + continue; + + qtd->isoc_td_first = idx; while (qh->ntd < ntd_max && qtd->isoc_frame_index_last < qtd->urb->packet_count) { - if (n_desc > 1) - qh->desc_list[n_desc - 1].status |= HOST_DMA_A; dwc2_fill_host_isoc_dma_desc(hsotg, qtd, qh, max_xfer_size, idx); idx = dwc2_desclist_idx_inc(idx, inc, qh->dev_speed); n_desc++; } + qtd->isoc_td_last = idx; qtd->in_process = 1; } @@ -575,6 +640,11 @@ static void dwc2_init_isoc_dma_desc(struct dwc2_hsotg *hsotg, if (qh->ntd == ntd_max) { idx = dwc2_desclist_idx_dec(qh->td_last, inc, qh->dev_speed); qh->desc_list[idx].status |= HOST_DMA_IOC; + dma_sync_single_for_device(hsotg->dev, + qh->desc_list_dma + (idx * + sizeof(struct dwc2_hcd_dma_desc)), + sizeof(struct dwc2_hcd_dma_desc), + DMA_TO_DEVICE); } #else /* @@ -604,13 +674,12 @@ static void dwc2_init_isoc_dma_desc(struct dwc2_hsotg *hsotg, idx = dwc2_desclist_idx_dec(qh->td_last, inc, qh->dev_speed); qh->desc_list[idx].status |= HOST_DMA_IOC; + dma_sync_single_for_device(hsotg->dev, + qh->desc_list_dma + + (idx * sizeof(struct dwc2_hcd_dma_desc)), + sizeof(struct dwc2_hcd_dma_desc), + DMA_TO_DEVICE); #endif - - if (n_desc) { - qh->desc_list[n_desc - 1].status |= HOST_DMA_A; - if (n_desc > 1) - qh->desc_list[0].status |= HOST_DMA_A; - } } static void dwc2_fill_host_dma_desc(struct dwc2_hsotg *hsotg, @@ -647,6 +716,12 @@ static void dwc2_fill_host_dma_desc(struct dwc2_hsotg *hsotg, dma_desc->buf = (u32)chan->xfer_dma; + dma_sync_single_for_device(hsotg->dev, + qh->desc_list_dma + + (n_desc * sizeof(struct dwc2_hcd_dma_desc)), + sizeof(struct dwc2_hcd_dma_desc), + DMA_TO_DEVICE); + /* * Last (or only) descriptor of IN transfer with actual size less * than MaxPacket @@ -697,6 +772,12 @@ static void dwc2_init_non_isoc_dma_desc(struct dwc2_hsotg *hsotg, "set A bit in desc %d (%p)\n", n_desc - 1, &qh->desc_list[n_desc - 1]); + dma_sync_single_for_device(hsotg->dev, + qh->desc_list_dma + + ((n_desc - 1) * + sizeof(struct dwc2_hcd_dma_desc)), + sizeof(struct dwc2_hcd_dma_desc), + DMA_TO_DEVICE); } dwc2_fill_host_dma_desc(hsotg, chan, qtd, qh, n_desc); dev_vdbg(hsotg->dev, @@ -722,10 +803,19 @@ static void dwc2_init_non_isoc_dma_desc(struct dwc2_hsotg *hsotg, HOST_DMA_IOC | HOST_DMA_EOL | HOST_DMA_A; dev_vdbg(hsotg->dev, "set IOC/EOL/A bits in desc %d (%p)\n", n_desc - 1, &qh->desc_list[n_desc - 1]); + dma_sync_single_for_device(hsotg->dev, + qh->desc_list_dma + (n_desc - 1) * + sizeof(struct dwc2_hcd_dma_desc), + sizeof(struct dwc2_hcd_dma_desc), + DMA_TO_DEVICE); if (n_desc > 1) { qh->desc_list[0].status |= HOST_DMA_A; dev_vdbg(hsotg->dev, "set A bit in desc 0 (%p)\n", &qh->desc_list[0]); + dma_sync_single_for_device(hsotg->dev, + qh->desc_list_dma, + sizeof(struct dwc2_hcd_dma_desc), + DMA_TO_DEVICE); } chan->ntd = n_desc; } @@ -800,7 +890,7 @@ static int dwc2_cmpl_host_isoc_dma_desc(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd, struct dwc2_qh *qh, u16 idx) { - struct dwc2_hcd_dma_desc *dma_desc = &qh->desc_list[idx]; + struct dwc2_hcd_dma_desc *dma_desc; struct dwc2_hcd_iso_packet_desc *frame_desc; u16 remain = 0; int rc = 0; @@ -808,6 +898,13 @@ static int dwc2_cmpl_host_isoc_dma_desc(struct dwc2_hsotg *hsotg, if (!qtd->urb) return -EINVAL; + dma_sync_single_for_cpu(hsotg->dev, qh->desc_list_dma + (idx * + sizeof(struct dwc2_hcd_dma_desc)), + sizeof(struct dwc2_hcd_dma_desc), + DMA_FROM_DEVICE); + + dma_desc = &qh->desc_list[idx]; + frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index_last]; dma_desc->buf = (u32)(qtd->urb->dma + frame_desc->offset); if (chan->ep_is_in) @@ -911,17 +1008,51 @@ static void dwc2_complete_isoc_xfer_ddma(struct dwc2_hsotg *hsotg, list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list, qtd_list_entry) { if (!qtd->in_process) break; + + /* + * Ensure idx corresponds to descriptor where first urb of this + * qtd was added. In fact, during isoc desc init, dwc2 may skip + * an index if current frame number is already over this index. + */ + if (idx != qtd->isoc_td_first) { + dev_vdbg(hsotg->dev, + "try to complete %d instead of %d\n", + idx, qtd->isoc_td_first); + idx = qtd->isoc_td_first; + } + do { + struct dwc2_qtd *qtd_next; + u16 cur_idx; + rc = dwc2_cmpl_host_isoc_dma_desc(hsotg, chan, qtd, qh, idx); if (rc < 0) return; idx = dwc2_desclist_idx_inc(idx, qh->interval, chan->speed); - if (rc == DWC2_CMPL_STOP) - goto stop_scan; + if (!rc) + continue; + if (rc == DWC2_CMPL_DONE) break; + + /* rc == DWC2_CMPL_STOP */ + + if (qh->interval >= 32) + goto stop_scan; + + qh->td_first = idx; + cur_idx = dwc2_frame_list_idx(hsotg->frame_number); + qtd_next = list_first_entry(&qh->qtd_list, + struct dwc2_qtd, + qtd_list_entry); + if (dwc2_frame_idx_num_gt(cur_idx, + qtd_next->isoc_td_last)) + break; + + goto stop_scan; + } while (idx != qh->td_first); } @@ -1029,6 +1160,12 @@ static int dwc2_process_non_isoc_desc(struct dwc2_hsotg *hsotg, if (!urb) return -EINVAL; + dma_sync_single_for_cpu(hsotg->dev, + qh->desc_list_dma + (desc_num * + sizeof(struct dwc2_hcd_dma_desc)), + sizeof(struct dwc2_hcd_dma_desc), + DMA_FROM_DEVICE); + dma_desc = &qh->desc_list[desc_num]; n_bytes = qh->n_bytes[desc_num]; dev_vdbg(hsotg->dev, @@ -1037,7 +1174,10 @@ static int dwc2_process_non_isoc_desc(struct dwc2_hsotg *hsotg, failed = dwc2_update_non_isoc_urb_state_ddma(hsotg, chan, qtd, dma_desc, halt_status, n_bytes, xfer_done); - if (failed || (*xfer_done && urb->status != -EINPROGRESS)) { + if (*xfer_done && urb->status != -EINPROGRESS) + failed = 1; + + if (failed) { dwc2_host_complete(hsotg, qtd, urb->status); dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); dev_vdbg(hsotg->dev, "failed=%1x xfer_done=%1x status=%08x\n", @@ -1165,6 +1305,21 @@ void dwc2_hcd_complete_xfer_ddma(struct dwc2_hsotg *hsotg, /* Release the channel if halted or session completed */ if (halt_status != DWC2_HC_XFER_COMPLETE || list_empty(&qh->qtd_list)) { + struct dwc2_qtd *qtd, *qtd_tmp; + + /* + * Kill all remainings QTDs since channel has been + * halted. + */ + list_for_each_entry_safe(qtd, qtd_tmp, + &qh->qtd_list, + qtd_list_entry) { + dwc2_host_complete(hsotg, qtd, + -ECONNRESET); + dwc2_hcd_qtd_unlink_and_free(hsotg, + qtd, qh); + } + /* Halt the channel if session completed */ if (halt_status == DWC2_HC_XFER_COMPLETE) dwc2_hc_halt(hsotg, chan, halt_status); @@ -1174,7 +1329,12 @@ void dwc2_hcd_complete_xfer_ddma(struct dwc2_hsotg *hsotg, /* Keep in assigned schedule to continue transfer */ list_move(&qh->qh_list_entry, &hsotg->periodic_sched_assigned); - continue_isoc_xfer = 1; + /* + * If channel has been halted during giveback of urb + * then prevent any new scheduling. + */ + if (!chan->halt_status) + continue_isoc_xfer = 1; } /* * Todo: Consider the case when period exceeds FrameList size. diff --git a/drivers/usb/dwc2/hcd_intr.c b/drivers/usb/dwc2/hcd_intr.c index bda0b21b850f..f8253803a050 100644 --- a/drivers/usb/dwc2/hcd_intr.c +++ b/drivers/usb/dwc2/hcd_intr.c @@ -122,6 +122,9 @@ static void dwc2_sof_intr(struct dwc2_hsotg *hsotg) struct dwc2_qh *qh; enum dwc2_transaction_type tr_type; + /* Clear interrupt */ + dwc2_writel(GINTSTS_SOF, hsotg->regs + GINTSTS); + #ifdef DEBUG_SOF dev_vdbg(hsotg->dev, "--Start of Frame Interrupt--\n"); #endif @@ -146,9 +149,6 @@ static void dwc2_sof_intr(struct dwc2_hsotg *hsotg) tr_type = dwc2_hcd_select_transactions(hsotg); if (tr_type != DWC2_TRANSACTION_NONE) dwc2_hcd_queue_transactions(hsotg, tr_type); - - /* Clear interrupt */ - dwc2_writel(GINTSTS_SOF, hsotg->regs + GINTSTS); } /* @@ -312,6 +312,7 @@ static void dwc2_hprt0_enable(struct dwc2_hsotg *hsotg, u32 hprt0, if (do_reset) { *hprt0_modify |= HPRT0_RST; + dwc2_writel(*hprt0_modify, hsotg->regs + HPRT0); queue_delayed_work(hsotg->wq_otg, &hsotg->reset_work, msecs_to_jiffies(60)); } else { @@ -347,15 +348,12 @@ static void dwc2_port_intr(struct dwc2_hsotg *hsotg) * Set flag and clear if detected */ if (hprt0 & HPRT0_CONNDET) { + dwc2_writel(hprt0_modify | HPRT0_CONNDET, hsotg->regs + HPRT0); + dev_vdbg(hsotg->dev, "--Port Interrupt HPRT0=0x%08x Port Connect Detected--\n", hprt0); - if (hsotg->lx_state != DWC2_L0) - usb_hcd_resume_root_hub(hsotg->priv); - - hsotg->flags.b.port_connect_status_change = 1; - hsotg->flags.b.port_connect_status = 1; - hprt0_modify |= HPRT0_CONNDET; + dwc2_hcd_connect(hsotg); /* * The Hub driver asserts a reset when it sees port connect @@ -368,27 +366,36 @@ static void dwc2_port_intr(struct dwc2_hsotg *hsotg) * Clear if detected - Set internal flag if disabled */ if (hprt0 & HPRT0_ENACHG) { + dwc2_writel(hprt0_modify | HPRT0_ENACHG, hsotg->regs + HPRT0); dev_vdbg(hsotg->dev, " --Port Interrupt HPRT0=0x%08x Port Enable Changed (now %d)--\n", hprt0, !!(hprt0 & HPRT0_ENA)); - hprt0_modify |= HPRT0_ENACHG; - if (hprt0 & HPRT0_ENA) + if (hprt0 & HPRT0_ENA) { + hsotg->new_connection = true; dwc2_hprt0_enable(hsotg, hprt0, &hprt0_modify); - else + } else { hsotg->flags.b.port_enable_change = 1; + if (hsotg->core_params->dma_desc_fs_enable) { + u32 hcfg; + + hsotg->core_params->dma_desc_enable = 0; + hsotg->new_connection = false; + hcfg = dwc2_readl(hsotg->regs + HCFG); + hcfg &= ~HCFG_DESCDMA; + dwc2_writel(hcfg, hsotg->regs + HCFG); + } + } } /* Overcurrent Change Interrupt */ if (hprt0 & HPRT0_OVRCURRCHG) { + dwc2_writel(hprt0_modify | HPRT0_OVRCURRCHG, + hsotg->regs + HPRT0); dev_vdbg(hsotg->dev, " --Port Interrupt HPRT0=0x%08x Port Overcurrent Changed--\n", hprt0); hsotg->flags.b.port_over_current_change = 1; - hprt0_modify |= HPRT0_OVRCURRCHG; } - - /* Clear Port Interrupts */ - dwc2_writel(hprt0_modify, hsotg->regs + HPRT0); } /* diff --git a/drivers/usb/dwc2/hcd_queue.c b/drivers/usb/dwc2/hcd_queue.c index 7d8d06cfe3c1..27d402f680a3 100644 --- a/drivers/usb/dwc2/hcd_queue.c +++ b/drivers/usb/dwc2/hcd_queue.c @@ -232,7 +232,7 @@ struct dwc2_qh *dwc2_hcd_qh_create(struct dwc2_hsotg *hsotg, */ void dwc2_hcd_qh_free(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) { - if (hsotg->core_params->dma_desc_enable > 0) { + if (qh->desc_list) { dwc2_hcd_qh_free_ddma(hsotg, qh); } else { /* kfree(NULL) is safe */ diff --git a/drivers/usb/dwc2/hw.h b/drivers/usb/dwc2/hw.h index 553f24606c43..281b57b36ab4 100644 --- a/drivers/usb/dwc2/hw.h +++ b/drivers/usb/dwc2/hw.h @@ -769,10 +769,6 @@ #define TSIZ_XFERSIZE_SHIFT 0 #define HCDMA(_ch) HSOTG_REG(0x0514 + 0x20 * (_ch)) -#define HCDMA_DMA_ADDR_MASK (0x1fffff << 11) -#define HCDMA_DMA_ADDR_SHIFT 11 -#define HCDMA_CTD_MASK (0xff << 3) -#define HCDMA_CTD_SHIFT 3 #define HCDMAB(_ch) HSOTG_REG(0x051c + 0x20 * (_ch)) diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index 39c1cbf0e75d..510f787434b3 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -54,11 +54,44 @@ static const char dwc2_driver_name[] = "dwc2"; +static const struct dwc2_core_params params_hi6220 = { + .otg_cap = 2, /* No HNP/SRP capable */ + .otg_ver = 0, /* 1.3 */ + .dma_enable = 1, + .dma_desc_enable = 0, + .dma_desc_fs_enable = 0, + .speed = 0, /* High Speed */ + .enable_dynamic_fifo = 1, + .en_multiple_tx_fifo = 1, + .host_rx_fifo_size = 512, + .host_nperio_tx_fifo_size = 512, + .host_perio_tx_fifo_size = 512, + .max_transfer_size = 65535, + .max_packet_count = 511, + .host_channels = 16, + .phy_type = 1, /* UTMI */ + .phy_utmi_width = 8, + .phy_ulpi_ddr = 0, /* Single */ + .phy_ulpi_ext_vbus = 0, + .i2c_enable = 0, + .ulpi_fs_ls = 0, + .host_support_fs_ls_low_power = 0, + .host_ls_low_power_phy_clk = 0, /* 48 MHz */ + .ts_dline = 0, + .reload_ctl = 0, + .ahbcfg = GAHBCFG_HBSTLEN_INCR16 << + GAHBCFG_HBSTLEN_SHIFT, + .uframe_sched = 0, + .external_id_pin_ctl = -1, + .hibernation = -1, +}; + static const struct dwc2_core_params params_bcm2835 = { .otg_cap = 0, /* HNP/SRP capable */ .otg_ver = 0, /* 1.3 */ .dma_enable = 1, .dma_desc_enable = 0, + .dma_desc_fs_enable = 0, .speed = 0, /* High Speed */ .enable_dynamic_fifo = 1, .en_multiple_tx_fifo = 1, @@ -89,6 +122,7 @@ static const struct dwc2_core_params params_rk3066 = { .otg_ver = -1, .dma_enable = -1, .dma_desc_enable = 0, + .dma_desc_fs_enable = 0, .speed = -1, .enable_dynamic_fifo = 1, .en_multiple_tx_fifo = -1, @@ -115,6 +149,71 @@ static const struct dwc2_core_params params_rk3066 = { .hibernation = -1, }; +/* + * Check the dr_mode against the module configuration and hardware + * capabilities. + * + * The hardware, module, and dr_mode, can each be set to host, device, + * or otg. Check that all these values are compatible and adjust the + * value of dr_mode if possible. + * + * actual + * HW MOD dr_mode dr_mode + * ------------------------------ + * HST HST any : HST + * HST DEV any : --- + * HST OTG any : HST + * + * DEV HST any : --- + * DEV DEV any : DEV + * DEV OTG any : DEV + * + * OTG HST any : HST + * OTG DEV any : DEV + * OTG OTG any : dr_mode + */ +static int dwc2_get_dr_mode(struct dwc2_hsotg *hsotg) +{ + enum usb_dr_mode mode; + + hsotg->dr_mode = usb_get_dr_mode(hsotg->dev); + if (hsotg->dr_mode == USB_DR_MODE_UNKNOWN) + hsotg->dr_mode = USB_DR_MODE_OTG; + + mode = hsotg->dr_mode; + + if (dwc2_hw_is_device(hsotg)) { + if (IS_ENABLED(CONFIG_USB_DWC2_HOST)) { + dev_err(hsotg->dev, + "Controller does not support host mode.\n"); + return -EINVAL; + } + mode = USB_DR_MODE_PERIPHERAL; + } else if (dwc2_hw_is_host(hsotg)) { + if (IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL)) { + dev_err(hsotg->dev, + "Controller does not support device mode.\n"); + return -EINVAL; + } + mode = USB_DR_MODE_HOST; + } else { + if (IS_ENABLED(CONFIG_USB_DWC2_HOST)) + mode = USB_DR_MODE_HOST; + else if (IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL)) + mode = USB_DR_MODE_PERIPHERAL; + } + + if (mode != hsotg->dr_mode) { + dev_warn(hsotg->dev, + "Configuration mismatch. dr_mode forced to %s\n", + mode == USB_DR_MODE_HOST ? "host" : "device"); + + hsotg->dr_mode = mode; + } + + return 0; +} + static int __dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg) { struct platform_device *pdev = to_platform_device(hsotg->dev); @@ -306,8 +405,28 @@ static int dwc2_driver_remove(struct platform_device *dev) return 0; } +/** + * dwc2_driver_shutdown() - Called on device shutdown + * + * @dev: Platform device + * + * In specific conditions (involving usb hubs) dwc2 devices can create a + * lot of interrupts, even to the point of overwhelming devices running + * at low frequencies. Some devices need to do special clock handling + * at shutdown-time which may bring the system clock below the threshold + * of being able to handle the dwc2 interrupts. Disabling dwc2-irqs + * prevents reboots/poweroffs from getting stuck in such cases. + */ +static void dwc2_driver_shutdown(struct platform_device *dev) +{ + struct dwc2_hsotg *hsotg = platform_get_drvdata(dev); + + disable_irq(hsotg->irq); +} + static const struct of_device_id dwc2_of_match_table[] = { { .compatible = "brcm,bcm2835-usb", .data = ¶ms_bcm2835 }, + { .compatible = "hisilicon,hi6220-usb", .data = ¶ms_hi6220 }, { .compatible = "rockchip,rk3066-usb", .data = ¶ms_rk3066 }, { .compatible = "snps,dwc2", .data = NULL }, { .compatible = "samsung,s3c6400-hsotg", .data = NULL}, @@ -335,7 +454,6 @@ static int dwc2_driver_probe(struct platform_device *dev) struct dwc2_hsotg *hsotg; struct resource *res; int retval; - int irq; match = of_match_device(dwc2_of_match_table, &dev->dev); if (match && match->data) { @@ -348,8 +466,10 @@ static int dwc2_driver_probe(struct platform_device *dev) /* * Disable descriptor dma mode by default as the HW can support * it, but does not support it for SPLIT transactions. + * Disable it for FS devices as well. */ defparams.dma_desc_enable = 0; + defparams.dma_desc_fs_enable = 0; } hsotg = devm_kzalloc(&dev->dev, sizeof(*hsotg), GFP_KERNEL); @@ -375,19 +495,6 @@ static int dwc2_driver_probe(struct platform_device *dev) dev_dbg(&dev->dev, "mapped PA %08lx to VA %p\n", (unsigned long)res->start, hsotg->regs); - hsotg->dr_mode = usb_get_dr_mode(&dev->dev); - if (IS_ENABLED(CONFIG_USB_DWC2_HOST) && - hsotg->dr_mode != USB_DR_MODE_HOST) { - hsotg->dr_mode = USB_DR_MODE_HOST; - dev_warn(hsotg->dev, - "Configuration mismatch. Forcing host mode\n"); - } else if (IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) && - hsotg->dr_mode != USB_DR_MODE_PERIPHERAL) { - hsotg->dr_mode = USB_DR_MODE_PERIPHERAL; - dev_warn(hsotg->dev, - "Configuration mismatch. Forcing peripheral mode\n"); - } - retval = dwc2_lowlevel_hw_init(hsotg); if (retval) return retval; @@ -401,15 +508,15 @@ static int dwc2_driver_probe(struct platform_device *dev) dwc2_set_all_params(hsotg->core_params, -1); - irq = platform_get_irq(dev, 0); - if (irq < 0) { + hsotg->irq = platform_get_irq(dev, 0); + if (hsotg->irq < 0) { dev_err(&dev->dev, "missing IRQ resource\n"); - return irq; + return hsotg->irq; } dev_dbg(hsotg->dev, "registering common handler for irq%d\n", - irq); - retval = devm_request_irq(hsotg->dev, irq, + hsotg->irq); + retval = devm_request_irq(hsotg->dev, hsotg->irq, dwc2_handle_common_intr, IRQF_SHARED, dev_name(hsotg->dev), hsotg); if (retval) @@ -419,7 +526,11 @@ static int dwc2_driver_probe(struct platform_device *dev) if (retval) return retval; - /* Detect config values from hardware */ + retval = dwc2_get_dr_mode(hsotg); + if (retval) + return retval; + + /* Reset the controller and detect hardware config values */ retval = dwc2_get_hwparams(hsotg); if (retval) goto error; @@ -427,15 +538,17 @@ static int dwc2_driver_probe(struct platform_device *dev) /* Validate parameter values */ dwc2_set_parameters(hsotg, params); + dwc2_force_dr_mode(hsotg); + if (hsotg->dr_mode != USB_DR_MODE_HOST) { - retval = dwc2_gadget_init(hsotg, irq); + retval = dwc2_gadget_init(hsotg, hsotg->irq); if (retval) goto error; hsotg->gadget_enabled = 1; } if (hsotg->dr_mode != USB_DR_MODE_PERIPHERAL) { - retval = dwc2_hcd_init(hsotg, irq); + retval = dwc2_hcd_init(hsotg, hsotg->irq); if (retval) { if (hsotg->gadget_enabled) dwc2_hsotg_remove(hsotg); @@ -502,6 +615,7 @@ static struct platform_driver dwc2_platform_driver = { }, .probe = dwc2_driver_probe, .remove = dwc2_driver_remove, + .shutdown = dwc2_driver_shutdown, }; module_platform_driver(dwc2_platform_driver); diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index 5a42c4590402..a64ce1c94d6d 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -87,6 +87,15 @@ config USB_DWC3_KEYSTONE Support of USB2/3 functionality in TI Keystone2 platforms. Say 'Y' or 'M' here if you have one such device +config USB_DWC3_OF_SIMPLE + tristate "Generic OF Simple Glue Layer" + depends on OF && COMMON_CLK + default USB_DWC3 + help + Support USB2/3 functionality in simple SoC integrations. + Currently supports Xilinx and Qualcomm DWC USB3 IP. + Say 'Y' or 'M' if you have one such device. + config USB_DWC3_ST tristate "STMicroelectronics Platforms" depends on ARCH_STI && OF @@ -96,12 +105,4 @@ config USB_DWC3_ST inside (i.e. STiH407). Say 'Y' or 'M' if you have one such device. -config USB_DWC3_QCOM - tristate "Qualcomm Platforms" - depends on ARCH_QCOM || COMPILE_TEST - default USB_DWC3 - help - Recent Qualcomm SoCs ship with one DesignWare Core USB3 IP inside, - say 'Y' or 'M' if you have one such device. - endif diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index acc951d46c27..22420e17d68b 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -37,5 +37,5 @@ obj-$(CONFIG_USB_DWC3_OMAP) += dwc3-omap.o obj-$(CONFIG_USB_DWC3_EXYNOS) += dwc3-exynos.o obj-$(CONFIG_USB_DWC3_PCI) += dwc3-pci.o obj-$(CONFIG_USB_DWC3_KEYSTONE) += dwc3-keystone.o -obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom.o +obj-$(CONFIG_USB_DWC3_OF_SIMPLE) += dwc3-of-simple.o obj-$(CONFIG_USB_DWC3_ST) += dwc3-st.o diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 22b4797383cd..de5e01f41bc2 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -272,7 +272,8 @@ static int dwc3_event_buffers_setup(struct dwc3 *dwc) for (n = 0; n < dwc->num_event_buffers; n++) { evt = dwc->ev_buffs[n]; - dev_dbg(dwc->dev, "Event buf %p dma %08llx length %d\n", + dwc3_trace(trace_dwc3_core, + "Event buf %p dma %08llx length %d\n", evt->buf, (unsigned long long) evt->dma, evt->length); @@ -608,12 +609,13 @@ static int dwc3_core_init(struct dwc3 *dwc) reg |= DWC3_GCTL_GBLHIBERNATIONEN; break; default: - dev_dbg(dwc->dev, "No power optimization available\n"); + dwc3_trace(trace_dwc3_core, "No power optimization available\n"); } /* check if current dwc3 is on simulation board */ if (dwc->hwparams.hwparams6 & DWC3_GHWPARAMS6_EN_FPGA) { - dev_dbg(dwc->dev, "it is on FPGA board\n"); + dwc3_trace(trace_dwc3_core, + "running on FPGA platform\n"); dwc->is_fpga = true; } diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 36f1cb74588c..29130682e547 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -37,6 +37,7 @@ #define DWC3_MSG_MAX 500 /* Global constants */ +#define DWC3_ZLP_BUF_SIZE 1024 /* size of a superspeed bulk */ #define DWC3_EP0_BOUNCE_SIZE 512 #define DWC3_ENDPOINTS_NUM 32 #define DWC3_XHCI_RESOURCES_NUM 2 @@ -647,6 +648,7 @@ struct dwc3_scratchpad_array { * @ctrl_req: usb control request which is used for ep0 * @ep0_trb: trb which is used for the ctrl_req * @ep0_bounce: bounce buffer for ep0 + * @zlp_buf: used when request->zero is set * @setup_buf: used while precessing STD USB requests * @ctrl_req_addr: dma address of ctrl_req * @ep0_trb: dma address of ep0_trb @@ -734,6 +736,7 @@ struct dwc3 { struct usb_ctrlrequest *ctrl_req; struct dwc3_trb *ep0_trb; void *ep0_bounce; + void *zlp_buf; void *scratchbuf; u8 *setup_buf; dma_addr_t ctrl_req_addr; diff --git a/drivers/usb/dwc3/dwc3-of-simple.c b/drivers/usb/dwc3/dwc3-of-simple.c new file mode 100644 index 000000000000..9c9f74155066 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-of-simple.c @@ -0,0 +1,180 @@ +/** + * dwc3-of-simple.c - OF glue layer for simple integrations + * + * Copyright (c) 2015 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Felipe Balbi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License 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. + * + * This is a combination of the old dwc3-qcom.c by Ivan T. Ivanov + * and the original patch adding support for Xilinx' SoC + * by Subbaraya Sundeep Bhatta + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct dwc3_of_simple { + struct device *dev; + struct clk **clks; + int num_clocks; +}; + +static int dwc3_of_simple_probe(struct platform_device *pdev) +{ + struct dwc3_of_simple *simple; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + + int ret; + int i; + + simple = devm_kzalloc(dev, sizeof(*simple), GFP_KERNEL); + if (!simple) + return -ENOMEM; + + ret = of_clk_get_parent_count(np); + if (ret < 0) + return ret; + + simple->num_clocks = ret; + + simple->clks = devm_kcalloc(dev, simple->num_clocks, + sizeof(struct clk *), GFP_KERNEL); + if (!simple->clks) + return -ENOMEM; + + simple->dev = dev; + + for (i = 0; i < simple->num_clocks; i++) { + struct clk *clk; + + clk = of_clk_get(np, i); + if (IS_ERR(clk)) { + while (--i >= 0) + clk_put(simple->clks[i]); + return PTR_ERR(clk); + } + + ret = clk_prepare_enable(clk); + if (ret < 0) { + while (--i >= 0) { + clk_disable_unprepare(simple->clks[i]); + clk_put(simple->clks[i]); + } + clk_put(clk); + + return ret; + } + + simple->clks[i] = clk; + } + + ret = of_platform_populate(np, NULL, NULL, dev); + if (ret) { + for (i = 0; i < simple->num_clocks; i++) { + clk_disable_unprepare(simple->clks[i]); + clk_put(simple->clks[i]); + } + + return ret; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + + return 0; +} + +static int dwc3_of_simple_remove(struct platform_device *pdev) +{ + struct dwc3_of_simple *simple = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + int i; + + for (i = 0; i < simple->num_clocks; i++) { + clk_unprepare(simple->clks[i]); + clk_put(simple->clks[i]); + } + + of_platform_depopulate(dev); + + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + + return 0; +} + +#ifdef CONFIG_PM +static int dwc3_of_simple_runtime_suspend(struct device *dev) +{ + struct dwc3_of_simple *simple = dev_get_drvdata(dev); + int i; + + for (i = 0; i < simple->num_clocks; i++) + clk_disable(simple->clks[i]); + + return 0; +} + +static int dwc3_of_simple_runtime_resume(struct device *dev) +{ + struct dwc3_of_simple *simple = dev_get_drvdata(dev); + int ret; + int i; + + for (i = 0; i < simple->num_clocks; i++) { + ret = clk_enable(simple->clks[i]); + if (ret < 0) { + while (--i >= 0) + clk_disable(simple->clks[i]); + return ret; + } + } + + return 0; +} +#endif + +static const struct dev_pm_ops dwc3_of_simple_dev_pm_ops = { + SET_RUNTIME_PM_OPS(dwc3_of_simple_runtime_suspend, + dwc3_of_simple_runtime_resume, NULL) +}; + +static const struct of_device_id of_dwc3_simple_match[] = { + { .compatible = "qcom,dwc3" }, + { .compatible = "xlnx,zynqmp-dwc3" }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, of_dwc3_simple_match); + +static struct platform_driver dwc3_of_simple_driver = { + .probe = dwc3_of_simple_probe, + .remove = dwc3_of_simple_remove, + .driver = { + .name = "dwc3-of-simple", + .of_match_table = of_dwc3_simple_match, + }, +}; + +module_platform_driver(dwc3_of_simple_driver); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare USB3 OF Simple Glue Layer"); +MODULE_AUTHOR("Felipe Balbi "); diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c deleted file mode 100644 index 088026048f49..000000000000 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ /dev/null @@ -1,130 +0,0 @@ -/* Copyright (c) 2013-2014, 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 -#include -#include -#include -#include -#include -#include - -struct dwc3_qcom { - struct device *dev; - - struct clk *core_clk; - struct clk *iface_clk; - struct clk *sleep_clk; -}; - -static int dwc3_qcom_probe(struct platform_device *pdev) -{ - struct device_node *node = pdev->dev.of_node; - struct dwc3_qcom *qdwc; - int ret; - - qdwc = devm_kzalloc(&pdev->dev, sizeof(*qdwc), GFP_KERNEL); - if (!qdwc) - return -ENOMEM; - - platform_set_drvdata(pdev, qdwc); - - qdwc->dev = &pdev->dev; - - qdwc->core_clk = devm_clk_get(qdwc->dev, "core"); - if (IS_ERR(qdwc->core_clk)) { - dev_err(qdwc->dev, "failed to get core clock\n"); - return PTR_ERR(qdwc->core_clk); - } - - qdwc->iface_clk = devm_clk_get(qdwc->dev, "iface"); - if (IS_ERR(qdwc->iface_clk)) { - dev_info(qdwc->dev, "failed to get optional iface clock\n"); - qdwc->iface_clk = NULL; - } - - qdwc->sleep_clk = devm_clk_get(qdwc->dev, "sleep"); - if (IS_ERR(qdwc->sleep_clk)) { - dev_info(qdwc->dev, "failed to get optional sleep clock\n"); - qdwc->sleep_clk = NULL; - } - - ret = clk_prepare_enable(qdwc->core_clk); - if (ret) { - dev_err(qdwc->dev, "failed to enable core clock\n"); - goto err_core; - } - - ret = clk_prepare_enable(qdwc->iface_clk); - if (ret) { - dev_err(qdwc->dev, "failed to enable optional iface clock\n"); - goto err_iface; - } - - ret = clk_prepare_enable(qdwc->sleep_clk); - if (ret) { - dev_err(qdwc->dev, "failed to enable optional sleep clock\n"); - goto err_sleep; - } - - ret = of_platform_populate(node, NULL, NULL, qdwc->dev); - if (ret) { - dev_err(qdwc->dev, "failed to register core - %d\n", ret); - goto err_clks; - } - - return 0; - -err_clks: - clk_disable_unprepare(qdwc->sleep_clk); -err_sleep: - clk_disable_unprepare(qdwc->iface_clk); -err_iface: - clk_disable_unprepare(qdwc->core_clk); -err_core: - return ret; -} - -static int dwc3_qcom_remove(struct platform_device *pdev) -{ - struct dwc3_qcom *qdwc = platform_get_drvdata(pdev); - - of_platform_depopulate(&pdev->dev); - - clk_disable_unprepare(qdwc->sleep_clk); - clk_disable_unprepare(qdwc->iface_clk); - clk_disable_unprepare(qdwc->core_clk); - - return 0; -} - -static const struct of_device_id of_dwc3_match[] = { - { .compatible = "qcom,dwc3" }, - { /* Sentinel */ } -}; -MODULE_DEVICE_TABLE(of, of_dwc3_match); - -static struct platform_driver dwc3_qcom_driver = { - .probe = dwc3_qcom_probe, - .remove = dwc3_qcom_remove, - .driver = { - .name = "qcom-dwc3", - .of_match_table = of_dwc3_match, - }, -}; - -module_platform_driver(dwc3_qcom_driver); - -MODULE_ALIAS("platform:qcom-dwc3"); -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("DesignWare USB3 QCOM Glue Layer"); -MODULE_AUTHOR("Ivan T. Ivanov "); diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 5320e939e090..3a9354abcb68 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -817,6 +817,8 @@ static void dwc3_ep0_complete_data(struct dwc3 *dwc, status = DWC3_TRB_SIZE_TRBSTS(trb->size); if (status == DWC3_TRBSTS_SETUP_PENDING) { + dwc->setup_packet_pending = true; + dwc3_trace(trace_dwc3_ep0, "Setup Pending received"); if (r) @@ -916,8 +918,10 @@ static void dwc3_ep0_complete_status(struct dwc3 *dwc, } status = DWC3_TRB_SIZE_TRBSTS(trb->size); - if (status == DWC3_TRBSTS_SETUP_PENDING) + if (status == DWC3_TRBSTS_SETUP_PENDING) { + dwc->setup_packet_pending = true; dwc3_trace(trace_dwc3_ep0, "Setup Pending received"); + } dwc->ep0state = EP0_SETUP_PHASE; dwc3_ep0_out_start(dwc); @@ -971,7 +975,7 @@ static void __dwc3_ep0_do_control_data(struct dwc3 *dwc, ret = usb_gadget_map_request(&dwc->gadget, &req->request, dep->number); if (ret) { - dev_dbg(dwc->dev, "failed to map request\n"); + dwc3_trace(trace_dwc3_ep0, "failed to map request\n"); return; } @@ -999,7 +1003,7 @@ static void __dwc3_ep0_do_control_data(struct dwc3 *dwc, ret = usb_gadget_map_request(&dwc->gadget, &req->request, dep->number); if (ret) { - dev_dbg(dwc->dev, "failed to map request\n"); + dwc3_trace(trace_dwc3_ep0, "failed to map request\n"); return; } @@ -1063,8 +1067,6 @@ static void dwc3_ep0_end_control_data(struct dwc3 *dwc, struct dwc3_ep *dep) static void dwc3_ep0_xfernotready(struct dwc3 *dwc, const struct dwc3_event_depevt *event) { - dwc->setup_packet_pending = true; - switch (event->status) { case DEPEVT_STATUS_CONTROL_DATA: dwc3_trace(trace_dwc3_ep0, "Control Data"); diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index a58376fd65fe..af023a81a0b0 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -265,9 +265,6 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, usb_gadget_unmap_request(&dwc->gadget, &req->request, req->direction); - dev_dbg(dwc->dev, "request %p from %s completed %d/%d ===> %d\n", - req, dep->name, req->request.actual, - req->request.length, status); trace_dwc3_gadget_giveback(req); spin_unlock(&dwc->lock); @@ -664,11 +661,10 @@ static int dwc3_gadget_ep_enable(struct usb_ep *ep, dep = to_dwc3_ep(ep); dwc = dep->dwc; - if (dep->flags & DWC3_EP_ENABLED) { - dev_WARN_ONCE(dwc->dev, true, "%s is already enabled\n", - dep->name); + if (dev_WARN_ONCE(dwc->dev, dep->flags & DWC3_EP_ENABLED, + "%s is already enabled\n", + dep->name)) return 0; - } spin_lock_irqsave(&dwc->lock, flags); ret = __dwc3_gadget_ep_enable(dep, desc, ep->comp_desc, false, false); @@ -692,11 +688,10 @@ static int dwc3_gadget_ep_disable(struct usb_ep *ep) dep = to_dwc3_ep(ep); dwc = dep->dwc; - if (!(dep->flags & DWC3_EP_ENABLED)) { - dev_WARN_ONCE(dwc->dev, true, "%s is already disabled\n", - dep->name); + if (dev_WARN_ONCE(dwc->dev, !(dep->flags & DWC3_EP_ENABLED), + "%s is already disabled\n", + dep->name)) return 0; - } spin_lock_irqsave(&dwc->lock, flags); ret = __dwc3_gadget_ep_disable(dep); @@ -985,8 +980,6 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep, u16 cmd_param, cmd |= DWC3_DEPCMD_PARAM(cmd_param); ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, cmd, ¶ms); if (ret < 0) { - dev_dbg(dwc->dev, "failed to send STARTTRANSFER command\n"); - /* * FIXME we need to iterate over the list of requests * here and stop, unmap, free and del each of the linked @@ -1044,6 +1037,20 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) struct dwc3 *dwc = dep->dwc; int ret; + if (!dep->endpoint.desc) { + dwc3_trace(trace_dwc3_gadget, + "trying to queue request %p to disabled %s\n", + &req->request, dep->endpoint.name); + return -ESHUTDOWN; + } + + if (WARN(req->dep != dep, "request %p belongs to '%s'\n", + &req->request, req->dep->name)) { + dwc3_trace(trace_dwc3_gadget, "request %p belongs to '%s'\n", + &req->request, req->dep->name); + return -EINVAL; + } + req->request.actual = 0; req->request.status = -EINPROGRESS; req->direction = dep->direction; @@ -1141,7 +1148,8 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) out: if (ret && ret != -EBUSY) - dev_dbg(dwc->dev, "%s: failed to kick transfers\n", + dwc3_trace(trace_dwc3_gadget, + "%s: failed to kick transfers\n", dep->name); if (ret == -EBUSY) ret = 0; @@ -1149,6 +1157,32 @@ out: return ret; } +static void __dwc3_gadget_ep_zlp_complete(struct usb_ep *ep, + struct usb_request *request) +{ + dwc3_gadget_ep_free_request(ep, request); +} + +static int __dwc3_gadget_ep_queue_zlp(struct dwc3 *dwc, struct dwc3_ep *dep) +{ + struct dwc3_request *req; + struct usb_request *request; + struct usb_ep *ep = &dep->endpoint; + + dwc3_trace(trace_dwc3_gadget, "queueing ZLP\n"); + request = dwc3_gadget_ep_alloc_request(ep, GFP_ATOMIC); + if (!request) + return -ENOMEM; + + request->length = 0; + request->buf = dwc->zlp_buf; + request->complete = __dwc3_gadget_ep_zlp_complete; + + req = to_dwc3_request(request); + + return __dwc3_gadget_ep_queue(dep, req); +} + static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, gfp_t gfp_flags) { @@ -1161,22 +1195,18 @@ static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, int ret; spin_lock_irqsave(&dwc->lock, flags); - if (!dep->endpoint.desc) { - dev_dbg(dwc->dev, "trying to queue request %p to disabled %s\n", - request, ep->name); - ret = -ESHUTDOWN; - goto out; - } - - if (WARN(req->dep != dep, "request %p belongs to '%s'\n", - request, req->dep->name)) { - ret = -EINVAL; - goto out; - } - ret = __dwc3_gadget_ep_queue(dep, req); -out: + /* + * Okay, here's the thing, if gadget driver has requested for a ZLP by + * setting request->zero, instead of doing magic, we will just queue an + * extra usb_request ourselves so that it gets handled the same way as + * any other request. + */ + if (ret == 0 && request->zero && request->length && + (request->length % ep->maxpacket == 0)) + ret = __dwc3_gadget_ep_queue_zlp(dwc, dep); + spin_unlock_irqrestore(&dwc->lock, flags); return ret; @@ -1246,7 +1276,8 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol) if (!protocol && ((dep->direction && dep->flags & DWC3_EP_BUSY) || (!list_empty(&dep->req_queued) || !list_empty(&dep->request_list)))) { - dev_dbg(dwc->dev, "%s: pending request, cannot halt\n", + dwc3_trace(trace_dwc3_gadget, + "%s: pending request, cannot halt\n", dep->name); return -EAGAIN; } @@ -1373,7 +1404,7 @@ static int dwc3_gadget_wakeup(struct usb_gadget *g) speed = reg & DWC3_DSTS_CONNECTSPD; if (speed == DWC3_DSTS_SUPERSPEED) { - dev_dbg(dwc->dev, "no wakeup on SuperSpeed\n"); + dwc3_trace(trace_dwc3_gadget, "no wakeup on SuperSpeed\n"); ret = -EINVAL; goto out; } @@ -1385,8 +1416,9 @@ static int dwc3_gadget_wakeup(struct usb_gadget *g) case DWC3_LINK_STATE_U3: /* in HS, means SUSPEND */ break; default: - dev_dbg(dwc->dev, "can't wakeup from link state %d\n", - link_state); + dwc3_trace(trace_dwc3_gadget, + "can't wakeup from '%s'\n", + dwc3_gadget_link_string(link_state)); ret = -EINVAL; goto out; } @@ -1825,7 +1857,8 @@ static int __dwc3_cleanup_done_trbs(struct dwc3 *dwc, struct dwc3_ep *dep, if (count) { trb_status = DWC3_TRB_SIZE_TRBSTS(trb->size); if (trb_status == DWC3_TRBSTS_MISSED_ISOC) { - dev_dbg(dwc->dev, "incomplete IN transfer %s\n", + dwc3_trace(trace_dwc3_gadget, + "%s: incomplete IN transfer\n", dep->name); /* * If missed isoc occurred and there is @@ -1887,10 +1920,9 @@ static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep, do { req = next_request(&dep->req_queued); - if (!req) { - WARN_ON_ONCE(1); + if (WARN_ON_ONCE(!req)) return 1; - } + i = 0; do { slot = req->start_slot + i; @@ -2004,7 +2036,8 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, dep->resource_index = 0; if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { - dev_dbg(dwc->dev, "%s is an Isochronous endpoint\n", + dwc3_trace(trace_dwc3_gadget, + "%s is an Isochronous endpoint\n", dep->name); return; } @@ -2031,7 +2064,8 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, if (!ret || ret == -EBUSY) return; - dev_dbg(dwc->dev, "%s: failed to kick transfers\n", + dwc3_trace(trace_dwc3_gadget, + "%s: failed to kick transfers\n", dep->name); } @@ -2053,11 +2087,12 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, case DEPEVT_STREAMEVT_NOTFOUND: /* FALLTHROUGH */ default: - dev_dbg(dwc->dev, "Couldn't find suitable stream\n"); + dwc3_trace(trace_dwc3_gadget, + "unable to find suitable stream\n"); } break; case DWC3_DEPEVT_RXTXFIFOEVT: - dev_dbg(dwc->dev, "%s FIFO Overrun\n", dep->name); + dwc3_trace(trace_dwc3_gadget, "%s FIFO Overrun\n", dep->name); break; case DWC3_DEPEVT_EPCMDCMPLT: dwc3_trace(trace_dwc3_gadget, "Endpoint Command Complete"); @@ -2230,8 +2265,8 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) * * Our suggested workaround is to follow the Disconnect * Event steps here, instead, based on a setup_packet_pending - * flag. Such flag gets set whenever we have a XferNotReady - * event on EP0 and gets cleared on XferComplete for the + * flag. Such flag gets set whenever we have a SETUP_PENDING + * status for EP0 TRBs and gets cleared on XferComplete for the * same endpoint. * * Refers to: @@ -2744,6 +2779,12 @@ int dwc3_gadget_init(struct dwc3 *dwc) goto err3; } + dwc->zlp_buf = kzalloc(DWC3_ZLP_BUF_SIZE, GFP_KERNEL); + if (!dwc->zlp_buf) { + ret = -ENOMEM; + goto err4; + } + dwc->gadget.ops = &dwc3_gadget_ops; dwc->gadget.speed = USB_SPEED_UNKNOWN; dwc->gadget.sg_supported = true; @@ -2785,16 +2826,19 @@ int dwc3_gadget_init(struct dwc3 *dwc) ret = dwc3_gadget_init_endpoints(dwc); if (ret) - goto err4; + goto err5; ret = usb_add_gadget_udc(dwc->dev, &dwc->gadget); if (ret) { dev_err(dwc->dev, "failed to register udc\n"); - goto err4; + goto err5; } return 0; +err5: + kfree(dwc->zlp_buf); + err4: dwc3_gadget_free_endpoints(dwc); dma_free_coherent(dwc->dev, DWC3_EP0_BOUNCE_SIZE, @@ -2827,6 +2871,7 @@ void dwc3_gadget_exit(struct dwc3 *dwc) dwc->ep0_bounce, dwc->ep0_bounce_addr); kfree(dwc->setup_buf); + kfree(dwc->zlp_buf); dma_free_coherent(dwc->dev, sizeof(*dwc->ep0_trb), dwc->ep0_trb, dwc->ep0_trb_addr); diff --git a/drivers/usb/dwc3/trace.h b/drivers/usb/dwc3/trace.h index 9c10669ab91f..3ac7252f4427 100644 --- a/drivers/usb/dwc3/trace.h +++ b/drivers/usb/dwc3/trace.h @@ -117,6 +117,9 @@ DECLARE_EVENT_CLASS(dwc3_log_request, __field(unsigned, actual) __field(unsigned, length) __field(int, status) + __field(int, zero) + __field(int, short_not_ok) + __field(int, no_interrupt) ), TP_fast_assign( snprintf(__get_str(name), DWC3_MSG_MAX, "%s", req->dep->name); @@ -124,9 +127,15 @@ DECLARE_EVENT_CLASS(dwc3_log_request, __entry->actual = req->request.actual; __entry->length = req->request.length; __entry->status = req->request.status; + __entry->zero = req->request.zero; + __entry->short_not_ok = req->request.short_not_ok; + __entry->no_interrupt = req->request.no_interrupt; ), - TP_printk("%s: req %p length %u/%u ==> %d", + TP_printk("%s: req %p length %u/%u %s%s%s ==> %d", __get_str(name), __entry->req, __entry->actual, __entry->length, + __entry->zero ? "Z" : "z", + __entry->short_not_ok ? "S" : "s", + __entry->no_interrupt ? "i" : "I", __entry->status ) ); diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 33834aa09ed4..be5aab9c13f2 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -127,6 +127,12 @@ config USB_GADGET_STORAGE_NUM_BUFFERS a module parameter as well. If unsure, say 2. +config U_SERIAL_CONSOLE + bool "Serial gadget console support" + depends on USB_G_SERIAL + help + It supports the serial gadget can be used as a console. + source "drivers/usb/gadget/udc/Kconfig" # diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index 163d305e1200..590c44989e5e 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -56,7 +56,6 @@ struct gadget_info { struct list_head string_list; struct list_head available_func; - const char *udc_name; struct usb_composite_driver composite; struct usb_composite_dev cdev; bool use_os_desc; @@ -233,21 +232,23 @@ static ssize_t gadget_dev_desc_bcdUSB_store(struct config_item *item, static ssize_t gadget_dev_desc_UDC_show(struct config_item *item, char *page) { - return sprintf(page, "%s\n", to_gadget_info(item)->udc_name ?: ""); + char *udc_name = to_gadget_info(item)->composite.gadget_driver.udc_name; + + return sprintf(page, "%s\n", udc_name ?: ""); } static int unregister_gadget(struct gadget_info *gi) { int ret; - if (!gi->udc_name) + if (!gi->composite.gadget_driver.udc_name) return -ENODEV; ret = usb_gadget_unregister_driver(&gi->composite.gadget_driver); if (ret) return ret; - kfree(gi->udc_name); - gi->udc_name = NULL; + kfree(gi->composite.gadget_driver.udc_name); + gi->composite.gadget_driver.udc_name = NULL; return 0; } @@ -271,14 +272,16 @@ static ssize_t gadget_dev_desc_UDC_store(struct config_item *item, if (ret) goto err; } else { - if (gi->udc_name) { + if (gi->composite.gadget_driver.udc_name) { ret = -EBUSY; goto err; } - ret = usb_udc_attach_driver(name, &gi->composite.gadget_driver); - if (ret) + gi->composite.gadget_driver.udc_name = name; + ret = usb_gadget_probe_driver(&gi->composite.gadget_driver); + if (ret) { + gi->composite.gadget_driver.udc_name = NULL; goto err; - gi->udc_name = name; + } } mutex_unlock(&gi->lock); return len; @@ -427,9 +430,9 @@ static int config_usb_cfg_unlink( * remove the function. */ mutex_lock(&gi->lock); - if (gi->udc_name) + if (gi->composite.gadget_driver.udc_name) unregister_gadget(gi); - WARN_ON(gi->udc_name); + WARN_ON(gi->composite.gadget_driver.udc_name); list_for_each_entry(f, &cfg->func_list, list) { if (f->fi == fi) { @@ -873,10 +876,10 @@ static int os_desc_unlink(struct config_item *os_desc_ci, struct usb_composite_dev *cdev = &gi->cdev; mutex_lock(&gi->lock); - if (gi->udc_name) + if (gi->composite.gadget_driver.udc_name) unregister_gadget(gi); cdev->os_desc_config = NULL; - WARN_ON(gi->udc_name); + WARN_ON(gi->composite.gadget_driver.udc_name); mutex_unlock(&gi->lock); return 0; } diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c index 898a570319f1..fb1fe96d3215 100644 --- a/drivers/usb/gadget/function/f_midi.c +++ b/drivers/usb/gadget/function/f_midi.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -75,6 +76,7 @@ struct f_midi { struct usb_ep *in_ep, *out_ep; struct snd_card *card; struct snd_rawmidi *rmidi; + u8 ms_id; struct snd_rawmidi_substream *in_substream[MAX_PORTS]; struct snd_rawmidi_substream *out_substream[MAX_PORTS]; @@ -87,6 +89,9 @@ struct f_midi { int index; char *id; unsigned int buflen, qlen; + /* This fifo is used as a buffer ring for pre-allocated IN usb_requests */ + DECLARE_KFIFO_PTR(in_req_fifo, struct usb_request *); + unsigned int in_last_port; }; static inline struct f_midi *func_to_midi(struct usb_function *f) @@ -94,7 +99,7 @@ static inline struct f_midi *func_to_midi(struct usb_function *f) return container_of(f, struct f_midi, func); } -static void f_midi_transmit(struct f_midi *midi, struct usb_request *req); +static void f_midi_transmit(struct f_midi *midi); DECLARE_UAC_AC_HEADER_DESCRIPTOR(1); DECLARE_USB_MIDI_OUT_JACK_DESCRIPTOR(1); @@ -201,12 +206,6 @@ static inline struct usb_request *midi_alloc_ep_req(struct usb_ep *ep, return alloc_ep_req(ep, length, length); } -static void free_ep_req(struct usb_ep *ep, struct usb_request *req) -{ - kfree(req->buf); - usb_ep_free_request(ep, req); -} - static const uint8_t f_midi_cin_length[] = { 0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1 }; @@ -258,7 +257,8 @@ f_midi_complete(struct usb_ep *ep, struct usb_request *req) } else if (ep == midi->in_ep) { /* Our transmit completed. See if there's more to go. * f_midi_transmit eats req, don't queue it again. */ - f_midi_transmit(midi, req); + req->length = 0; + f_midi_transmit(midi); return; } break; @@ -269,10 +269,12 @@ f_midi_complete(struct usb_ep *ep, struct usb_request *req) case -ESHUTDOWN: /* disconnect from host */ VDBG(cdev, "%s gone (%d), %d/%d\n", ep->name, status, req->actual, req->length); - if (ep == midi->out_ep) + if (ep == midi->out_ep) { f_midi_handle_out_data(ep, req); - - free_ep_req(ep, req); + /* We don't need to free IN requests because it's handled + * by the midi->in_req_fifo. */ + free_ep_req(ep, req); + } return; case -EOVERFLOW: /* buffer overrun on read means that @@ -324,12 +326,11 @@ static int f_midi_start_ep(struct f_midi *midi, static int f_midi_set_alt(struct usb_function *f, unsigned intf, unsigned alt) { struct f_midi *midi = func_to_midi(f); - struct usb_composite_dev *cdev = f->config->cdev; unsigned i; int err; - /* For Control Device interface we do nothing */ - if (intf == 0) + /* we only set alt for MIDIStreaming interface */ + if (intf != midi->ms_id) return 0; err = f_midi_start_ep(midi, f, midi->in_ep); @@ -340,24 +341,20 @@ static int f_midi_set_alt(struct usb_function *f, unsigned intf, unsigned alt) if (err) return err; - usb_ep_disable(midi->out_ep); + /* pre-allocate write usb requests to use on f_midi_transmit. */ + while (kfifo_avail(&midi->in_req_fifo)) { + struct usb_request *req = + midi_alloc_ep_req(midi->in_ep, midi->buflen); - err = config_ep_by_speed(midi->gadget, f, midi->out_ep); - if (err) { - ERROR(cdev, "can't configure %s: %d\n", - midi->out_ep->name, err); - return err; + if (req == NULL) + return -ENOMEM; + + req->length = 0; + req->complete = f_midi_complete; + + kfifo_put(&midi->in_req_fifo, req); } - err = usb_ep_enable(midi->out_ep); - if (err) { - ERROR(cdev, "can't start %s: %d\n", - midi->out_ep->name, err); - return err; - } - - midi->out_ep->driver_data = midi; - /* allocate a bunch of read buffers and queue them all at once. */ for (i = 0; i < midi->qlen && err == 0; i++) { struct usb_request *req = @@ -368,9 +365,10 @@ static int f_midi_set_alt(struct usb_function *f, unsigned intf, unsigned alt) req->complete = f_midi_complete; err = usb_ep_queue(midi->out_ep, req, GFP_ATOMIC); if (err) { - ERROR(midi, "%s queue req: %d\n", + ERROR(midi, "%s: couldn't enqueue request: %d\n", midi->out_ep->name, err); free_ep_req(midi->out_ep, req); + return err; } } @@ -381,6 +379,7 @@ static void f_midi_disable(struct usb_function *f) { struct f_midi *midi = func_to_midi(f); struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = NULL; DBG(cdev, "disable\n"); @@ -390,6 +389,10 @@ static void f_midi_disable(struct usb_function *f) */ usb_ep_disable(midi->in_ep); usb_ep_disable(midi->out_ep); + + /* release IN requests */ + while (kfifo_get(&midi->in_req_fifo, &req)) + free_ep_req(midi->in_ep, req); } static int f_midi_snd_free(struct snd_device *device) @@ -511,57 +514,113 @@ static void f_midi_transmit_byte(struct usb_request *req, } } -static void f_midi_transmit(struct f_midi *midi, struct usb_request *req) +static void f_midi_drop_out_substreams(struct f_midi *midi) { - struct usb_ep *ep = midi->in_ep; - int i; - - if (!ep) - return; - - if (!req) - req = midi_alloc_ep_req(ep, midi->buflen); - - if (!req) { - ERROR(midi, "%s: alloc_ep_request failed\n", __func__); - return; - } - req->length = 0; - req->complete = f_midi_complete; + unsigned int i; for (i = 0; i < MAX_PORTS; i++) { struct gmidi_in_port *port = midi->in_port[i]; struct snd_rawmidi_substream *substream = midi->in_substream[i]; - if (!port || !port->active || !substream) + if (!port) + break; + + if (!port->active || !substream) continue; - while (req->length + 3 < midi->buflen) { - uint8_t b; - if (snd_rawmidi_transmit(substream, &b, 1) != 1) { - port->active = 0; + snd_rawmidi_drop_output(substream); + } +} + +static void f_midi_transmit(struct f_midi *midi) +{ + struct usb_ep *ep = midi->in_ep; + bool active; + + /* We only care about USB requests if IN endpoint is enabled */ + if (!ep || !ep->enabled) + goto drop_out; + + do { + struct usb_request *req = NULL; + unsigned int len, i; + + active = false; + + /* We peek the request in order to reuse it if it fails + * to enqueue on its endpoint */ + len = kfifo_peek(&midi->in_req_fifo, &req); + if (len != 1) { + ERROR(midi, "%s: Couldn't get usb request\n", __func__); + goto drop_out; + } + + /* If buffer overrun, then we ignore this transmission. + * IMPORTANT: This will cause the user-space rawmidi device to block until a) usb + * requests have been completed or b) snd_rawmidi_write() times out. */ + if (req->length > 0) + return; + + for (i = midi->in_last_port; i < MAX_PORTS; i++) { + struct gmidi_in_port *port = midi->in_port[i]; + struct snd_rawmidi_substream *substream = midi->in_substream[i]; + + if (!port) { + /* Reset counter when we reach the last available port */ + midi->in_last_port = 0; + break; + } + + if (!port->active || !substream) + continue; + + while (req->length + 3 < midi->buflen) { + uint8_t b; + + if (snd_rawmidi_transmit(substream, &b, 1) != 1) { + port->active = 0; + break; + } + f_midi_transmit_byte(req, port, b); + } + + active = !!port->active; + /* Check if last port is still active, which means that + * there is still data on that substream but this current + * request run out of space. */ + if (active) { + midi->in_last_port = i; + /* There is no need to re-iterate though midi ports. */ break; } - f_midi_transmit_byte(req, port, b); } - } - if (req->length > 0 && ep->enabled) { - int err; + if (req->length > 0) { + int err; - err = usb_ep_queue(ep, req, GFP_ATOMIC); - if (err < 0) - ERROR(midi, "%s queue req: %d\n", - midi->in_ep->name, err); - } else { - free_ep_req(ep, req); - } + err = usb_ep_queue(ep, req, GFP_ATOMIC); + if (err < 0) { + ERROR(midi, "%s failed to queue req: %d\n", + midi->in_ep->name, err); + req->length = 0; /* Re-use request next time. */ + } else { + /* Upon success, put request at the back of the queue. */ + kfifo_skip(&midi->in_req_fifo); + kfifo_put(&midi->in_req_fifo, req); + } + } + } while (active); + + return; + +drop_out: + f_midi_drop_out_substreams(midi); } static void f_midi_in_tasklet(unsigned long data) { struct f_midi *midi = (struct f_midi *) data; - f_midi_transmit(midi, NULL); + f_midi_transmit(midi); } static int f_midi_in_open(struct snd_rawmidi_substream *substream) @@ -687,6 +746,7 @@ static int f_midi_register_card(struct f_midi *midi) goto fail; } midi->rmidi = rmidi; + midi->in_last_port = 0; strcpy(rmidi->name, card->shortname); rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | @@ -755,6 +815,7 @@ static int f_midi_bind(struct usb_configuration *c, struct usb_function *f) goto fail; ms_interface_desc.bInterfaceNumber = status; ac_header_desc.baInterfaceNr[0] = status; + midi->ms_id = status; status = -ENODEV; @@ -1075,6 +1136,7 @@ static void f_midi_free(struct usb_function *f) mutex_lock(&opts->lock); for (i = opts->in_ports - 1; i >= 0; --i) kfree(midi->in_port[i]); + kfifo_free(&midi->in_req_fifo); kfree(midi); --opts->refcnt; mutex_unlock(&opts->lock); @@ -1148,6 +1210,12 @@ static struct usb_function *f_midi_alloc(struct usb_function_instance *fi) midi->index = opts->index; midi->buflen = opts->buflen; midi->qlen = opts->qlen; + midi->in_last_port = 0; + + status = kfifo_alloc(&midi->in_req_fifo, midi->qlen, GFP_KERNEL); + if (status) + goto setup_fail; + ++opts->refcnt; mutex_unlock(&opts->lock); diff --git a/drivers/usb/gadget/function/f_sourcesink.c b/drivers/usb/gadget/function/f_sourcesink.c index 9f3ced62d916..242ba5caffe5 100644 --- a/drivers/usb/gadget/function/f_sourcesink.c +++ b/drivers/usb/gadget/function/f_sourcesink.c @@ -34,13 +34,6 @@ * plus two that support control-OUT tests. If the optional "autoresume" * mode is enabled, it provides good functional coverage for the "USBCV" * test harness from USB-IF. - * - * Note that because this doesn't queue more than one request at a time, - * some other function must be used to test queueing logic. The network - * link (g_ether) is the best overall option for that, since its TX and RX - * queues are relatively independent, will receive a range of packet sizes, - * and can often be made to run out completely. Those issues are important - * when stress testing peripheral controller drivers. */ struct f_sourcesink { struct usb_function function; @@ -57,6 +50,8 @@ struct f_sourcesink { unsigned isoc_mult; unsigned isoc_maxburst; unsigned buflen; + unsigned bulk_qlen; + unsigned iso_qlen; }; static inline struct f_sourcesink *func_to_ss(struct usb_function *f) @@ -303,12 +298,6 @@ static inline struct usb_request *ss_alloc_ep_req(struct usb_ep *ep, int len) return alloc_ep_req(ep, len, ss->buflen); } -void free_ep_req(struct usb_ep *ep, struct usb_request *req) -{ - kfree(req->buf); - usb_ep_free_request(ep, req); -} - static void disable_ep(struct usb_composite_dev *cdev, struct usb_ep *ep) { int value; @@ -595,31 +584,33 @@ static int source_sink_start_ep(struct f_sourcesink *ss, bool is_in, { struct usb_ep *ep; struct usb_request *req; - int i, size, status; + int i, size, qlen, status = 0; - for (i = 0; i < 8; i++) { - if (is_iso) { - switch (speed) { - case USB_SPEED_SUPER: - size = ss->isoc_maxpacket * - (ss->isoc_mult + 1) * - (ss->isoc_maxburst + 1); - break; - case USB_SPEED_HIGH: - size = ss->isoc_maxpacket * (ss->isoc_mult + 1); - break; - default: - size = ss->isoc_maxpacket > 1023 ? - 1023 : ss->isoc_maxpacket; - break; - } - ep = is_in ? ss->iso_in_ep : ss->iso_out_ep; - req = ss_alloc_ep_req(ep, size); - } else { - ep = is_in ? ss->in_ep : ss->out_ep; - req = ss_alloc_ep_req(ep, 0); + if (is_iso) { + switch (speed) { + case USB_SPEED_SUPER: + size = ss->isoc_maxpacket * + (ss->isoc_mult + 1) * + (ss->isoc_maxburst + 1); + break; + case USB_SPEED_HIGH: + size = ss->isoc_maxpacket * (ss->isoc_mult + 1); + break; + default: + size = ss->isoc_maxpacket > 1023 ? + 1023 : ss->isoc_maxpacket; + break; } + ep = is_in ? ss->iso_in_ep : ss->iso_out_ep; + qlen = ss->iso_qlen; + } else { + ep = is_in ? ss->in_ep : ss->out_ep; + qlen = ss->bulk_qlen; + size = 0; + } + for (i = 0; i < qlen; i++) { + req = ss_alloc_ep_req(ep, size); if (!req) return -ENOMEM; @@ -638,10 +629,8 @@ static int source_sink_start_ep(struct f_sourcesink *ss, bool is_in, is_iso ? "ISO-" : "", is_in ? "IN" : "OUT", ep->name, status); free_ep_req(ep, req); + return status; } - - if (!is_iso) - break; } return status; @@ -869,6 +858,8 @@ static struct usb_function *source_sink_alloc_func( ss->isoc_mult = ss_opts->isoc_mult; ss->isoc_maxburst = ss_opts->isoc_maxburst; ss->buflen = ss_opts->bulk_buflen; + ss->bulk_qlen = ss_opts->bulk_qlen; + ss->iso_qlen = ss_opts->iso_qlen; ss->function.name = "source/sink"; ss->function.bind = sourcesink_bind; @@ -1153,6 +1144,82 @@ end: CONFIGFS_ATTR(f_ss_opts_, bulk_buflen); +static ssize_t f_ss_opts_bulk_qlen_show(struct config_item *item, char *page) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%u\n", opts->bulk_qlen); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_ss_opts_bulk_qlen_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int ret; + u32 num; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = kstrtou32(page, 0, &num); + if (ret) + goto end; + + opts->bulk_qlen = num; + ret = len; +end: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(f_ss_opts_, bulk_qlen); + +static ssize_t f_ss_opts_iso_qlen_show(struct config_item *item, char *page) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%u\n", opts->iso_qlen); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_ss_opts_iso_qlen_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int ret; + u32 num; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = kstrtou32(page, 0, &num); + if (ret) + goto end; + + opts->iso_qlen = num; + ret = len; +end: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(f_ss_opts_, iso_qlen); + static struct configfs_attribute *ss_attrs[] = { &f_ss_opts_attr_pattern, &f_ss_opts_attr_isoc_interval, @@ -1160,6 +1227,8 @@ static struct configfs_attribute *ss_attrs[] = { &f_ss_opts_attr_isoc_mult, &f_ss_opts_attr_isoc_maxburst, &f_ss_opts_attr_bulk_buflen, + &f_ss_opts_attr_bulk_qlen, + &f_ss_opts_attr_iso_qlen, NULL, }; @@ -1189,6 +1258,8 @@ static struct usb_function_instance *source_sink_alloc_inst(void) ss_opts->isoc_interval = GZERO_ISOC_INTERVAL; ss_opts->isoc_maxpacket = GZERO_ISOC_MAXPACKET; ss_opts->bulk_buflen = GZERO_BULK_BUFLEN; + ss_opts->bulk_qlen = GZERO_SS_BULK_QLEN; + ss_opts->iso_qlen = GZERO_SS_ISO_QLEN; config_group_init_type_name(&ss_opts->func_inst.group, "", &ss_func_type); diff --git a/drivers/usb/gadget/function/g_zero.h b/drivers/usb/gadget/function/g_zero.h index 15f180904f8a..492924d0d599 100644 --- a/drivers/usb/gadget/function/g_zero.h +++ b/drivers/usb/gadget/function/g_zero.h @@ -10,6 +10,8 @@ #define GZERO_QLEN 32 #define GZERO_ISOC_INTERVAL 4 #define GZERO_ISOC_MAXPACKET 1024 +#define GZERO_SS_BULK_QLEN 1 +#define GZERO_SS_ISO_QLEN 8 struct usb_zero_options { unsigned pattern; @@ -19,6 +21,8 @@ struct usb_zero_options { unsigned isoc_maxburst; unsigned bulk_buflen; unsigned qlen; + unsigned ss_bulk_qlen; + unsigned ss_iso_qlen; }; struct f_ss_opts { @@ -29,6 +33,8 @@ struct f_ss_opts { unsigned isoc_mult; unsigned isoc_maxburst; unsigned bulk_buflen; + unsigned bulk_qlen; + unsigned iso_qlen; /* * Read/write access to configfs attributes is handled by configfs. @@ -59,7 +65,6 @@ void lb_modexit(void); int lb_modinit(void); /* common utilities */ -void free_ep_req(struct usb_ep *ep, struct usb_request *req); void disable_endpoints(struct usb_composite_dev *cdev, struct usb_ep *in, struct usb_ep *out, struct usb_ep *iso_in, struct usb_ep *iso_out); diff --git a/drivers/usb/gadget/function/u_ether.c b/drivers/usb/gadget/function/u_ether.c index 6554322af2c1..637809e3bd0d 100644 --- a/drivers/usb/gadget/function/u_ether.c +++ b/drivers/usb/gadget/function/u_ether.c @@ -143,21 +143,11 @@ static inline int qlen(struct usb_gadget *gadget, unsigned qmult) static int ueth_change_mtu(struct net_device *net, int new_mtu) { - struct eth_dev *dev = netdev_priv(net); - unsigned long flags; - int status = 0; + if (new_mtu <= ETH_HLEN || new_mtu > GETHER_MAX_ETH_FRAME_LEN) + return -ERANGE; + net->mtu = new_mtu; - /* don't change MTU on "live" link (peer won't know) */ - spin_lock_irqsave(&dev->lock, flags); - if (dev->port_usb) - status = -EBUSY; - else if (new_mtu <= ETH_HLEN || new_mtu > GETHER_MAX_ETH_FRAME_LEN) - status = -ERANGE; - else - net->mtu = new_mtu; - spin_unlock_irqrestore(&dev->lock, flags); - - return status; + return 0; } static void eth_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *p) diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c index f7771d86ad6c..6af145f2a99d 100644 --- a/drivers/usb/gadget/function/u_serial.c +++ b/drivers/usb/gadget/function/u_serial.c @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include "u_serial.h" @@ -79,6 +81,7 @@ */ #define QUEUE_SIZE 16 #define WRITE_BUF_SIZE 8192 /* TX only */ +#define GS_CONSOLE_BUF_SIZE 8192 /* circular buffer */ struct gs_buf { @@ -88,6 +91,17 @@ struct gs_buf { char *buf_put; }; +/* console info */ +struct gscons_info { + struct gs_port *port; + struct task_struct *console_thread; + struct gs_buf con_buf; + /* protect the buf and busy flag */ + spinlock_t con_lock; + int req_busy; + struct usb_request *console_req; +}; + /* * The port structure holds info for each port, one for each minor number * (and thus for each /dev/ node). @@ -1023,6 +1037,246 @@ static const struct tty_operations gs_tty_ops = { static struct tty_driver *gs_tty_driver; +#ifdef CONFIG_U_SERIAL_CONSOLE + +static struct gscons_info gscons_info; +static struct console gserial_cons; + +static struct usb_request *gs_request_new(struct usb_ep *ep) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (!req) + return NULL; + + req->buf = kmalloc(ep->maxpacket, GFP_ATOMIC); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void gs_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (!req) + return; + + kfree(req->buf); + usb_ep_free_request(ep, req); +} + +static void gs_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct gscons_info *info = &gscons_info; + + switch (req->status) { + default: + pr_warn("%s: unexpected %s status %d\n", + __func__, ep->name, req->status); + case 0: + /* normal completion */ + spin_lock(&info->con_lock); + info->req_busy = 0; + spin_unlock(&info->con_lock); + + wake_up_process(info->console_thread); + break; + case -ESHUTDOWN: + /* disconnect */ + pr_vdebug("%s: %s shutdown\n", __func__, ep->name); + break; + } +} + +static int gs_console_connect(int port_num) +{ + struct gscons_info *info = &gscons_info; + struct gs_port *port; + struct usb_ep *ep; + + if (port_num != gserial_cons.index) { + pr_err("%s: port num [%d] is not support console\n", + __func__, port_num); + return -ENXIO; + } + + port = ports[port_num].port; + ep = port->port_usb->in; + if (!info->console_req) { + info->console_req = gs_request_new(ep); + if (!info->console_req) + return -ENOMEM; + info->console_req->complete = gs_complete_out; + } + + info->port = port; + spin_lock(&info->con_lock); + info->req_busy = 0; + spin_unlock(&info->con_lock); + pr_vdebug("port[%d] console connect!\n", port_num); + return 0; +} + +static void gs_console_disconnect(struct usb_ep *ep) +{ + struct gscons_info *info = &gscons_info; + struct usb_request *req = info->console_req; + + gs_request_free(req, ep); + info->console_req = NULL; +} + +static int gs_console_thread(void *data) +{ + struct gscons_info *info = &gscons_info; + struct gs_port *port; + struct usb_request *req; + struct usb_ep *ep; + int xfer, ret, count, size; + + do { + port = info->port; + set_current_state(TASK_INTERRUPTIBLE); + if (!port || !port->port_usb + || !port->port_usb->in || !info->console_req) + goto sched; + + req = info->console_req; + ep = port->port_usb->in; + + spin_lock_irq(&info->con_lock); + count = gs_buf_data_avail(&info->con_buf); + size = ep->maxpacket; + + if (count > 0 && !info->req_busy) { + set_current_state(TASK_RUNNING); + if (count < size) + size = count; + + xfer = gs_buf_get(&info->con_buf, req->buf, size); + req->length = xfer; + + spin_unlock(&info->con_lock); + ret = usb_ep_queue(ep, req, GFP_ATOMIC); + spin_lock(&info->con_lock); + if (ret < 0) + info->req_busy = 0; + else + info->req_busy = 1; + + spin_unlock_irq(&info->con_lock); + } else { + spin_unlock_irq(&info->con_lock); +sched: + if (kthread_should_stop()) { + set_current_state(TASK_RUNNING); + break; + } + schedule(); + } + } while (1); + + return 0; +} + +static int gs_console_setup(struct console *co, char *options) +{ + struct gscons_info *info = &gscons_info; + int status; + + info->port = NULL; + info->console_req = NULL; + info->req_busy = 0; + spin_lock_init(&info->con_lock); + + status = gs_buf_alloc(&info->con_buf, GS_CONSOLE_BUF_SIZE); + if (status) { + pr_err("%s: allocate console buffer failed\n", __func__); + return status; + } + + info->console_thread = kthread_create(gs_console_thread, + co, "gs_console"); + if (IS_ERR(info->console_thread)) { + pr_err("%s: cannot create console thread\n", __func__); + gs_buf_free(&info->con_buf); + return PTR_ERR(info->console_thread); + } + wake_up_process(info->console_thread); + + return 0; +} + +static void gs_console_write(struct console *co, + const char *buf, unsigned count) +{ + struct gscons_info *info = &gscons_info; + unsigned long flags; + + spin_lock_irqsave(&info->con_lock, flags); + gs_buf_put(&info->con_buf, buf, count); + spin_unlock_irqrestore(&info->con_lock, flags); + + wake_up_process(info->console_thread); +} + +static struct tty_driver *gs_console_device(struct console *co, int *index) +{ + struct tty_driver **p = (struct tty_driver **)co->data; + + if (!*p) + return NULL; + + *index = co->index; + return *p; +} + +static struct console gserial_cons = { + .name = "ttyGS", + .write = gs_console_write, + .device = gs_console_device, + .setup = gs_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &gs_tty_driver, +}; + +static void gserial_console_init(void) +{ + register_console(&gserial_cons); +} + +static void gserial_console_exit(void) +{ + struct gscons_info *info = &gscons_info; + + unregister_console(&gserial_cons); + kthread_stop(info->console_thread); + gs_buf_free(&info->con_buf); +} + +#else + +static int gs_console_connect(int port_num) +{ + return 0; +} + +static void gs_console_disconnect(struct usb_ep *ep) +{ +} + +static void gserial_console_init(void) +{ +} + +static void gserial_console_exit(void) +{ +} + +#endif + static int gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding) { @@ -1096,6 +1350,7 @@ void gserial_free_line(unsigned char port_num) gserial_free_port(port); tty_unregister_device(gs_tty_driver, port_num); + gserial_console_exit(); } EXPORT_SYMBOL_GPL(gserial_free_line); @@ -1138,6 +1393,7 @@ int gserial_alloc_line(unsigned char *line_num) goto err; } *line_num = port_num; + gserial_console_init(); err: return ret; } @@ -1219,6 +1475,7 @@ int gserial_connect(struct gserial *gser, u8 port_num) gser->disconnect(gser); } + status = gs_console_connect(port_num); spin_unlock_irqrestore(&port->port_lock, flags); return status; @@ -1277,6 +1534,7 @@ void gserial_disconnect(struct gserial *gser) port->read_allocated = port->read_started = port->write_allocated = port->write_started = 0; + gs_console_disconnect(gser->in); spin_unlock_irqrestore(&port->port_lock, flags); } EXPORT_SYMBOL_GPL(gserial_disconnect); diff --git a/drivers/usb/gadget/legacy/acm_ms.c b/drivers/usb/gadget/legacy/acm_ms.c index 4b158e2d1e57..c16089efc322 100644 --- a/drivers/usb/gadget/legacy/acm_ms.c +++ b/drivers/usb/gadget/legacy/acm_ms.c @@ -40,7 +40,7 @@ static struct usb_device_descriptor device_desc = { .bLength = sizeof device_desc, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_MISC /* 0xEF */, .bDeviceSubClass = 2, diff --git a/drivers/usb/gadget/legacy/audio.c b/drivers/usb/gadget/legacy/audio.c index 685cf3b4b78f..5d7b3c6a422b 100644 --- a/drivers/usb/gadget/legacy/audio.c +++ b/drivers/usb/gadget/legacy/audio.c @@ -123,7 +123,7 @@ static struct usb_device_descriptor device_desc = { .bLength = sizeof device_desc, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x200), + /* .bcdUSB = DYNAMIC */ #ifdef CONFIG_GADGET_UAC1 .bDeviceClass = USB_CLASS_PER_INTERFACE, diff --git a/drivers/usb/gadget/legacy/cdc2.c b/drivers/usb/gadget/legacy/cdc2.c index ecd8c8d62f2e..51c08682de84 100644 --- a/drivers/usb/gadget/legacy/cdc2.c +++ b/drivers/usb/gadget/legacy/cdc2.c @@ -43,7 +43,7 @@ static struct usb_device_descriptor device_desc = { .bLength = sizeof device_desc, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_COMM, .bDeviceSubClass = 0, diff --git a/drivers/usb/gadget/legacy/ether.c b/drivers/usb/gadget/legacy/ether.c index 31e9160223e9..25a2c2e48592 100644 --- a/drivers/usb/gadget/legacy/ether.c +++ b/drivers/usb/gadget/legacy/ether.c @@ -151,7 +151,7 @@ static struct usb_device_descriptor device_desc = { .bLength = sizeof device_desc, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16 (0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_COMM, .bDeviceSubClass = 0, diff --git a/drivers/usb/gadget/legacy/g_ffs.c b/drivers/usb/gadget/legacy/g_ffs.c index 320a81b2baa6..f85639ef8a8f 100644 --- a/drivers/usb/gadget/legacy/g_ffs.c +++ b/drivers/usb/gadget/legacy/g_ffs.c @@ -69,7 +69,7 @@ static struct usb_device_descriptor gfs_dev_desc = { .bLength = sizeof gfs_dev_desc, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_PER_INTERFACE, .idVendor = cpu_to_le16(GFS_VENDOR_ID), diff --git a/drivers/usb/gadget/legacy/gmidi.c b/drivers/usb/gadget/legacy/gmidi.c index 8a18348ae86e..fc2ac150f5ff 100644 --- a/drivers/usb/gadget/legacy/gmidi.c +++ b/drivers/usb/gadget/legacy/gmidi.c @@ -21,19 +21,12 @@ /* #define VERBOSE_DEBUG */ #include -#include #include -#include -#include #include -#include -#include #include #include -#include -#include #include "u_midi.h" @@ -42,7 +35,6 @@ MODULE_AUTHOR("Ben Williamson"); MODULE_LICENSE("GPL v2"); -static const char shortname[] = "g_midi"; static const char longname[] = "MIDI Gadget"; USB_GADGET_COMPOSITE_OPTIONS(); @@ -61,7 +53,7 @@ MODULE_PARM_DESC(buflen, "MIDI buffer length"); static unsigned int qlen = 32; module_param(qlen, uint, S_IRUGO); -MODULE_PARM_DESC(qlen, "USB read request queue length"); +MODULE_PARM_DESC(qlen, "USB read and write request queue length"); static unsigned int in_ports = 1; module_param(in_ports, uint, S_IRUGO); @@ -86,7 +78,7 @@ MODULE_PARM_DESC(out_ports, "Number of MIDI output ports"); static struct usb_device_descriptor device_desc = { .bLength = USB_DT_DEVICE_SIZE, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_PER_INTERFACE, .idVendor = cpu_to_le16(DRIVER_VENDOR_NUM), .idProduct = cpu_to_le16(DRIVER_PRODUCT_NUM), diff --git a/drivers/usb/gadget/legacy/hid.c b/drivers/usb/gadget/legacy/hid.c index 7e5d2c48476e..a71a884f79fc 100644 --- a/drivers/usb/gadget/legacy/hid.c +++ b/drivers/usb/gadget/legacy/hid.c @@ -47,7 +47,7 @@ static struct usb_device_descriptor device_desc = { .bLength = sizeof device_desc, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ /* .bDeviceClass = USB_CLASS_COMM, */ /* .bDeviceSubClass = 0, */ diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c index f454c7af489c..365afd7e14f8 100644 --- a/drivers/usb/gadget/legacy/inode.c +++ b/drivers/usb/gadget/legacy/inode.c @@ -1137,10 +1137,9 @@ ep0_write (struct file *fd, const char __user *buf, size_t len, loff_t *ptr) dev->gadget->ep0, dev->req, GFP_KERNEL); } + spin_lock_irq(&dev->lock); if (retval < 0) { - spin_lock_irq (&dev->lock); clean_req (dev->gadget->ep0, dev->req); - spin_unlock_irq (&dev->lock); } else retval = len; diff --git a/drivers/usb/gadget/legacy/mass_storage.c b/drivers/usb/gadget/legacy/mass_storage.c index bda3c519110f..e61af53c7d2b 100644 --- a/drivers/usb/gadget/legacy/mass_storage.c +++ b/drivers/usb/gadget/legacy/mass_storage.c @@ -55,7 +55,7 @@ static struct usb_device_descriptor msg_device_desc = { .bLength = sizeof msg_device_desc, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_PER_INTERFACE, /* Vendor and product id can be overridden by module parameters. */ diff --git a/drivers/usb/gadget/legacy/multi.c b/drivers/usb/gadget/legacy/multi.c index 4fe794ddcd49..229d704a620b 100644 --- a/drivers/usb/gadget/legacy/multi.c +++ b/drivers/usb/gadget/legacy/multi.c @@ -67,7 +67,7 @@ static struct usb_device_descriptor device_desc = { .bLength = sizeof device_desc, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_MISC /* 0xEF */, .bDeviceSubClass = 2, diff --git a/drivers/usb/gadget/legacy/ncm.c b/drivers/usb/gadget/legacy/ncm.c index 2bae4381332d..0aba68253e3d 100644 --- a/drivers/usb/gadget/legacy/ncm.c +++ b/drivers/usb/gadget/legacy/ncm.c @@ -49,7 +49,7 @@ static struct usb_device_descriptor device_desc = { .bLength = sizeof device_desc, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16 (0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_COMM, .bDeviceSubClass = 0, diff --git a/drivers/usb/gadget/legacy/nokia.c b/drivers/usb/gadget/legacy/nokia.c index 8b3f6fb1825d..09975046c694 100644 --- a/drivers/usb/gadget/legacy/nokia.c +++ b/drivers/usb/gadget/legacy/nokia.c @@ -89,7 +89,7 @@ static struct usb_gadget_strings *dev_strings[] = { static struct usb_device_descriptor device_desc = { .bLength = USB_DT_DEVICE_SIZE, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_COMM, .idVendor = cpu_to_le16(NOKIA_VENDOR_ID), .idProduct = cpu_to_le16(NOKIA_PRODUCT_ID), diff --git a/drivers/usb/gadget/legacy/printer.c b/drivers/usb/gadget/legacy/printer.c index a22d30a4def1..6f969a86175c 100644 --- a/drivers/usb/gadget/legacy/printer.c +++ b/drivers/usb/gadget/legacy/printer.c @@ -71,7 +71,7 @@ static struct usb_function *f_printer; static struct usb_device_descriptor device_desc = { .bLength = sizeof device_desc, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_PER_INTERFACE, .bDeviceSubClass = 0, .bDeviceProtocol = 0, diff --git a/drivers/usb/gadget/legacy/serial.c b/drivers/usb/gadget/legacy/serial.c index c5d42e0347a9..9d89adce756d 100644 --- a/drivers/usb/gadget/legacy/serial.c +++ b/drivers/usb/gadget/legacy/serial.c @@ -65,7 +65,7 @@ static struct usb_gadget_strings *dev_strings[] = { static struct usb_device_descriptor device_desc = { .bLength = USB_DT_DEVICE_SIZE, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ /* .bDeviceClass = f(use_acm) */ .bDeviceSubClass = 0, .bDeviceProtocol = 0, diff --git a/drivers/usb/gadget/legacy/tcm_usb_gadget.c b/drivers/usb/gadget/legacy/tcm_usb_gadget.c index 22e56158d585..7857fa411636 100644 --- a/drivers/usb/gadget/legacy/tcm_usb_gadget.c +++ b/drivers/usb/gadget/legacy/tcm_usb_gadget.c @@ -1974,7 +1974,7 @@ static struct usb_descriptor_header *uasp_ss_function_desc[] = { static struct usb_device_descriptor usbg_device_desc = { .bLength = sizeof(usbg_device_desc), .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_PER_INTERFACE, .idVendor = cpu_to_le16(UAS_VENDOR_ID), .idProduct = cpu_to_le16(UAS_PRODUCT_ID), diff --git a/drivers/usb/gadget/legacy/webcam.c b/drivers/usb/gadget/legacy/webcam.c index 72c976bf3530..f9661cd627c8 100644 --- a/drivers/usb/gadget/legacy/webcam.c +++ b/drivers/usb/gadget/legacy/webcam.c @@ -77,7 +77,7 @@ static struct usb_function *f_uvc; static struct usb_device_descriptor webcam_device_descriptor = { .bLength = USB_DT_DEVICE_SIZE, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_MISC, .bDeviceSubClass = 0x02, .bDeviceProtocol = 0x01, diff --git a/drivers/usb/gadget/legacy/zero.c b/drivers/usb/gadget/legacy/zero.c index 37a410056fed..d02e2ce73ea5 100644 --- a/drivers/usb/gadget/legacy/zero.c +++ b/drivers/usb/gadget/legacy/zero.c @@ -68,6 +68,8 @@ static struct usb_zero_options gzero_options = { .isoc_maxpacket = GZERO_ISOC_MAXPACKET, .bulk_buflen = GZERO_BULK_BUFLEN, .qlen = GZERO_QLEN, + .ss_bulk_qlen = GZERO_SS_BULK_QLEN, + .ss_iso_qlen = GZERO_SS_ISO_QLEN, }; /*-------------------------------------------------------------------------*/ @@ -113,7 +115,7 @@ static struct usb_device_descriptor device_desc = { .bLength = sizeof device_desc, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_VENDOR_SPEC, .idVendor = cpu_to_le16(DRIVER_VENDOR_NUM), @@ -255,6 +257,14 @@ static struct usb_function_instance *func_inst_lb; module_param_named(qlen, gzero_options.qlen, uint, S_IRUGO|S_IWUSR); MODULE_PARM_DESC(qlen, "depth of loopback queue"); +module_param_named(ss_bulk_qlen, gzero_options.ss_bulk_qlen, uint, + S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(bulk_qlen, "depth of sourcesink queue for bulk transfer"); + +module_param_named(ss_iso_qlen, gzero_options.ss_iso_qlen, uint, + S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(iso_qlen, "depth of sourcesink queue for iso transfer"); + static int zero_bind(struct usb_composite_dev *cdev) { struct f_ss_opts *ss_opts; @@ -285,6 +295,8 @@ static int zero_bind(struct usb_composite_dev *cdev) ss_opts->isoc_mult = gzero_options.isoc_mult; ss_opts->isoc_maxburst = gzero_options.isoc_maxburst; ss_opts->bulk_buflen = gzero_options.bulk_buflen; + ss_opts->bulk_qlen = gzero_options.ss_bulk_qlen; + ss_opts->iso_qlen = gzero_options.ss_iso_qlen; func_ss = usb_get_function(func_inst_ss); if (IS_ERR(func_ss)) { diff --git a/drivers/usb/gadget/u_f.c b/drivers/usb/gadget/u_f.c index c6276f0268ae..4bc7eea8bfc8 100644 --- a/drivers/usb/gadget/u_f.c +++ b/drivers/usb/gadget/u_f.c @@ -11,7 +11,6 @@ * published by the Free Software Foundation. */ -#include #include "u_f.h" struct usb_request *alloc_ep_req(struct usb_ep *ep, int len, int default_len) diff --git a/drivers/usb/gadget/u_f.h b/drivers/usb/gadget/u_f.h index 1d5f0eb68552..4247cc098a89 100644 --- a/drivers/usb/gadget/u_f.h +++ b/drivers/usb/gadget/u_f.h @@ -16,6 +16,8 @@ #ifndef __U_F_H__ #define __U_F_H__ +#include + /* Variable Length Array Macros **********************************************/ #define vla_group(groupname) size_t groupname##__next = 0 #define vla_group_size(groupname) groupname##__next @@ -45,8 +47,12 @@ struct usb_ep; struct usb_request; +/* Requests allocated via alloc_ep_req() must be freed by free_ep_req(). */ struct usb_request *alloc_ep_req(struct usb_ep *ep, int len, int default_len); +static inline void free_ep_req(struct usb_ep *ep, struct usb_request *req) +{ + kfree(req->buf); + usb_ep_free_request(ep, req); +} #endif /* __U_F_H__ */ - - diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index cdbff54e07ac..753c29bd11ad 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -174,6 +174,17 @@ config USB_RENESAS_USBHS_UDC dynamically linked module called "renesas_usbhs" and force all gadget drivers to also be dynamically linked. +config USB_RENESAS_USB3 + tristate 'Renesas USB3.0 Peripheral controller' + depends on ARCH_SHMOBILE || COMPILE_TEST + help + Renesas USB3.0 Peripheral controller is a USB peripheral controller + that supports super, high, and full speed USB 3.0 data transfers. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "renesas_usb3" and force all + gadget drivers to also be dynamically linked. + config USB_PXA27X tristate "PXA 27x" help diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile index fba2049bf985..dfee53446319 100644 --- a/drivers/usb/gadget/udc/Makefile +++ b/drivers/usb/gadget/udc/Makefile @@ -19,6 +19,7 @@ fsl_usb2_udc-y := fsl_udc_core.o fsl_usb2_udc-$(CONFIG_ARCH_MXC) += fsl_mxc_udc.o obj-$(CONFIG_USB_M66592) += m66592-udc.o obj-$(CONFIG_USB_R8A66597) += r8a66597-udc.o +obj-$(CONFIG_USB_RENESAS_USB3) += renesas_usb3.o obj-$(CONFIG_USB_FSL_QE) += fsl_qe_udc.o obj-$(CONFIG_USB_S3C_HSUDC) += s3c-hsudc.o obj-$(CONFIG_USB_LPC32XX) += lpc32xx_udc.o diff --git a/drivers/usb/gadget/udc/bcm63xx_udc.c b/drivers/usb/gadget/udc/bcm63xx_udc.c index 8cbb00325824..f5fccb3e4152 100644 --- a/drivers/usb/gadget/udc/bcm63xx_udc.c +++ b/drivers/usb/gadget/udc/bcm63xx_udc.c @@ -1083,7 +1083,7 @@ static int bcm63xx_ep_disable(struct usb_ep *ep) struct bcm63xx_ep *bep = our_ep(ep); struct bcm63xx_udc *udc = bep->udc; struct iudma_ch *iudma = bep->iudma; - struct list_head *pos, *n; + struct bcm63xx_req *breq, *n; unsigned long flags; if (!ep || !ep->desc) @@ -1099,10 +1099,7 @@ static int bcm63xx_ep_disable(struct usb_ep *ep) iudma_reset_channel(udc, iudma); if (!list_empty(&bep->queue)) { - list_for_each_safe(pos, n, &bep->queue) { - struct bcm63xx_req *breq = - list_entry(pos, struct bcm63xx_req, queue); - + list_for_each_entry_safe(breq, n, &bep->queue, queue) { usb_gadget_unmap_request(&udc->gadget, &breq->req, iudma->is_tx); list_del(&breq->queue); diff --git a/drivers/usb/gadget/udc/gr_udc.c b/drivers/usb/gadget/udc/gr_udc.c index b9429bc42511..39b7136d31d9 100644 --- a/drivers/usb/gadget/udc/gr_udc.c +++ b/drivers/usb/gadget/udc/gr_udc.c @@ -253,13 +253,12 @@ static struct gr_dma_desc *gr_alloc_dma_desc(struct gr_ep *ep, gfp_t gfp_flags) dma_addr_t paddr; struct gr_dma_desc *dma_desc; - dma_desc = dma_pool_alloc(ep->dev->desc_pool, gfp_flags, &paddr); + dma_desc = dma_pool_zalloc(ep->dev->desc_pool, gfp_flags, &paddr); if (!dma_desc) { dev_err(ep->dev->dev, "Could not allocate from DMA pool\n"); return NULL; } - memset(dma_desc, 0, sizeof(*dma_desc)); dma_desc->paddr = paddr; return dma_desc; diff --git a/drivers/usb/gadget/udc/lpc32xx_udc.c b/drivers/usb/gadget/udc/lpc32xx_udc.c index 00b5006baf15..79fe6b77ee44 100644 --- a/drivers/usb/gadget/udc/lpc32xx_udc.c +++ b/drivers/usb/gadget/udc/lpc32xx_udc.c @@ -28,42 +28,29 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include +#include #include #include -#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include -#include -#include -#include -#include - -#include -#include -#include #ifdef CONFIG_USB_GADGET_DEBUG_FILES #include #include #endif +#include +#include + /* * USB device configuration structure */ diff --git a/drivers/usb/gadget/udc/renesas_usb3.c b/drivers/usb/gadget/udc/renesas_usb3.c new file mode 100644 index 000000000000..93a3bec81df7 --- /dev/null +++ b/drivers/usb/gadget/udc/renesas_usb3.c @@ -0,0 +1,1975 @@ +/* + * Renesas USB3.0 Peripheral driver (USB gadget) + * + * Copyright (C) 2015 Renesas Electronics Corporation + * + * 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; version 2 of the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* register definitions */ +#define USB3_AXI_INT_STA 0x008 +#define USB3_AXI_INT_ENA 0x00c +#define USB3_DMA_INT_STA 0x010 +#define USB3_DMA_INT_ENA 0x014 +#define USB3_USB_COM_CON 0x200 +#define USB3_USB20_CON 0x204 +#define USB3_USB30_CON 0x208 +#define USB3_USB_STA 0x210 +#define USB3_DRD_CON 0x218 +#define USB3_USB_INT_STA_1 0x220 +#define USB3_USB_INT_STA_2 0x224 +#define USB3_USB_INT_ENA_1 0x228 +#define USB3_USB_INT_ENA_2 0x22c +#define USB3_STUP_DAT_0 0x230 +#define USB3_STUP_DAT_1 0x234 +#define USB3_P0_MOD 0x280 +#define USB3_P0_CON 0x288 +#define USB3_P0_STA 0x28c +#define USB3_P0_INT_STA 0x290 +#define USB3_P0_INT_ENA 0x294 +#define USB3_P0_LNG 0x2a0 +#define USB3_P0_READ 0x2a4 +#define USB3_P0_WRITE 0x2a8 +#define USB3_PIPE_COM 0x2b0 +#define USB3_PN_MOD 0x2c0 +#define USB3_PN_RAMMAP 0x2c4 +#define USB3_PN_CON 0x2c8 +#define USB3_PN_STA 0x2cc +#define USB3_PN_INT_STA 0x2d0 +#define USB3_PN_INT_ENA 0x2d4 +#define USB3_PN_LNG 0x2e0 +#define USB3_PN_READ 0x2e4 +#define USB3_PN_WRITE 0x2e8 +#define USB3_SSIFCMD 0x340 + +/* AXI_INT_ENA and AXI_INT_STA */ +#define AXI_INT_DMAINT BIT(31) +#define AXI_INT_EPCINT BIT(30) + +/* LCLKSEL */ +#define LCLKSEL_LSEL BIT(18) + +/* USB_COM_CON */ +#define USB_COM_CON_CONF BIT(24) +#define USB_COM_CON_SPD_MODE BIT(17) +#define USB_COM_CON_EP0_EN BIT(16) +#define USB_COM_CON_DEV_ADDR_SHIFT 8 +#define USB_COM_CON_DEV_ADDR_MASK GENMASK(14, USB_COM_CON_DEV_ADDR_SHIFT) +#define USB_COM_CON_DEV_ADDR(n) (((n) << USB_COM_CON_DEV_ADDR_SHIFT) & \ + USB_COM_CON_DEV_ADDR_MASK) +#define USB_COM_CON_RX_DETECTION BIT(1) +#define USB_COM_CON_PIPE_CLR BIT(0) + +/* USB20_CON */ +#define USB20_CON_B2_PUE BIT(31) +#define USB20_CON_B2_SUSPEND BIT(24) +#define USB20_CON_B2_CONNECT BIT(17) +#define USB20_CON_B2_TSTMOD_SHIFT 8 +#define USB20_CON_B2_TSTMOD_MASK GENMASK(10, USB20_CON_B2_TSTMOD_SHIFT) +#define USB20_CON_B2_TSTMOD(n) (((n) << USB20_CON_B2_TSTMOD_SHIFT) & \ + USB20_CON_B2_TSTMOD_MASK) +#define USB20_CON_B2_TSTMOD_EN BIT(0) + +/* USB30_CON */ +#define USB30_CON_POW_SEL_SHIFT 24 +#define USB30_CON_POW_SEL_MASK GENMASK(26, USB30_CON_POW_SEL_SHIFT) +#define USB30_CON_POW_SEL_IN_U3 BIT(26) +#define USB30_CON_POW_SEL_IN_DISCON 0 +#define USB30_CON_POW_SEL_P2_TO_P0 BIT(25) +#define USB30_CON_POW_SEL_P0_TO_P3 BIT(24) +#define USB30_CON_POW_SEL_P0_TO_P2 0 +#define USB30_CON_B3_PLLWAKE BIT(23) +#define USB30_CON_B3_CONNECT BIT(17) +#define USB30_CON_B3_HOTRST_CMP BIT(1) + +/* USB_STA */ +#define USB_STA_SPEED_MASK (BIT(2) | BIT(1)) +#define USB_STA_SPEED_HS BIT(2) +#define USB_STA_SPEED_FS BIT(1) +#define USB_STA_SPEED_SS 0 +#define USB_STA_VBUS_STA BIT(0) + +/* DRD_CON */ +#define DRD_CON_PERI_CON BIT(24) + +/* USB_INT_ENA_1 and USB_INT_STA_1 */ +#define USB_INT_1_B3_PLLWKUP BIT(31) +#define USB_INT_1_B3_LUPSUCS BIT(30) +#define USB_INT_1_B3_DISABLE BIT(27) +#define USB_INT_1_B3_WRMRST BIT(21) +#define USB_INT_1_B3_HOTRST BIT(20) +#define USB_INT_1_B2_USBRST BIT(12) +#define USB_INT_1_B2_L1SPND BIT(11) +#define USB_INT_1_B2_SPND BIT(9) +#define USB_INT_1_B2_RSUM BIT(8) +#define USB_INT_1_SPEED BIT(1) +#define USB_INT_1_VBUS_CNG BIT(0) + +/* USB_INT_ENA_2 and USB_INT_STA_2 */ +#define USB_INT_2_PIPE(n) BIT(n) + +/* P0_MOD */ +#define P0_MOD_DIR BIT(6) + +/* P0_CON and PN_CON */ +#define PX_CON_BYTE_EN_MASK (BIT(10) | BIT(9)) +#define PX_CON_BYTE_EN_SHIFT 9 +#define PX_CON_BYTE_EN_BYTES(n) (((n) << PX_CON_BYTE_EN_SHIFT) & \ + PX_CON_BYTE_EN_MASK) +#define PX_CON_SEND BIT(8) + +/* P0_CON */ +#define P0_CON_ST_RES_MASK (BIT(27) | BIT(26)) +#define P0_CON_ST_RES_FORCE_STALL BIT(27) +#define P0_CON_ST_RES_NORMAL BIT(26) +#define P0_CON_ST_RES_FORCE_NRDY 0 +#define P0_CON_OT_RES_MASK (BIT(25) | BIT(24)) +#define P0_CON_OT_RES_FORCE_STALL BIT(25) +#define P0_CON_OT_RES_NORMAL BIT(24) +#define P0_CON_OT_RES_FORCE_NRDY 0 +#define P0_CON_IN_RES_MASK (BIT(17) | BIT(16)) +#define P0_CON_IN_RES_FORCE_STALL BIT(17) +#define P0_CON_IN_RES_NORMAL BIT(16) +#define P0_CON_IN_RES_FORCE_NRDY 0 +#define P0_CON_RES_WEN BIT(7) +#define P0_CON_BCLR BIT(1) + +/* P0_STA and PN_STA */ +#define PX_STA_BUFSTS BIT(0) + +/* P0_INT_ENA and P0_INT_STA */ +#define P0_INT_STSED BIT(18) +#define P0_INT_STSST BIT(17) +#define P0_INT_SETUP BIT(16) +#define P0_INT_RCVNL BIT(8) +#define P0_INT_ERDY BIT(7) +#define P0_INT_FLOW BIT(6) +#define P0_INT_STALL BIT(2) +#define P0_INT_NRDY BIT(1) +#define P0_INT_BFRDY BIT(0) +#define P0_INT_ALL_BITS (P0_INT_STSED | P0_INT_SETUP | P0_INT_BFRDY) + +/* PN_MOD */ +#define PN_MOD_DIR BIT(6) +#define PN_MOD_TYPE_SHIFT 4 +#define PN_MOD_TYPE_MASK GENMASK(5, PN_MOD_TYPE_SHIFT) +#define PN_MOD_TYPE(n) (((n) << PN_MOD_TYPE_SHIFT) & \ + PN_MOD_TYPE_MASK) +#define PN_MOD_EPNUM_MASK GENMASK(3, 0) +#define PN_MOD_EPNUM(n) ((n) & PN_MOD_EPNUM_MASK) + +/* PN_RAMMAP */ +#define PN_RAMMAP_RAMAREA_SHIFT 29 +#define PN_RAMMAP_RAMAREA_MASK GENMASK(31, PN_RAMMAP_RAMAREA_SHIFT) +#define PN_RAMMAP_RAMAREA_16KB BIT(31) +#define PN_RAMMAP_RAMAREA_8KB (BIT(30) | BIT(29)) +#define PN_RAMMAP_RAMAREA_4KB BIT(30) +#define PN_RAMMAP_RAMAREA_2KB BIT(29) +#define PN_RAMMAP_RAMAREA_1KB 0 +#define PN_RAMMAP_MPKT_SHIFT 16 +#define PN_RAMMAP_MPKT_MASK GENMASK(26, PN_RAMMAP_MPKT_SHIFT) +#define PN_RAMMAP_MPKT(n) (((n) << PN_RAMMAP_MPKT_SHIFT) & \ + PN_RAMMAP_MPKT_MASK) +#define PN_RAMMAP_RAMIF_SHIFT 14 +#define PN_RAMMAP_RAMIF_MASK GENMASK(15, PN_RAMMAP_RAMIF_SHIFT) +#define PN_RAMMAP_RAMIF(n) (((n) << PN_RAMMAP_RAMIF_SHIFT) & \ + PN_RAMMAP_RAMIF_MASK) +#define PN_RAMMAP_BASEAD_MASK GENMASK(13, 0) +#define PN_RAMMAP_BASEAD(offs) (((offs) >> 3) & PN_RAMMAP_BASEAD_MASK) +#define PN_RAMMAP_DATA(area, ramif, basead) ((PN_RAMMAP_##area) | \ + (PN_RAMMAP_RAMIF(ramif)) | \ + (PN_RAMMAP_BASEAD(basead))) + +/* PN_CON */ +#define PN_CON_EN BIT(31) +#define PN_CON_DATAIF_EN BIT(30) +#define PN_CON_RES_MASK (BIT(17) | BIT(16)) +#define PN_CON_RES_FORCE_STALL BIT(17) +#define PN_CON_RES_NORMAL BIT(16) +#define PN_CON_RES_FORCE_NRDY 0 +#define PN_CON_LAST BIT(11) +#define PN_CON_RES_WEN BIT(7) +#define PN_CON_CLR BIT(0) + +/* PN_INT_STA and PN_INT_ENA */ +#define PN_INT_LSTTR BIT(4) +#define PN_INT_BFRDY BIT(0) + +/* USB3_SSIFCMD */ +#define SSIFCMD_URES_U2 BIT(9) +#define SSIFCMD_URES_U1 BIT(8) +#define SSIFCMD_UDIR_U2 BIT(7) +#define SSIFCMD_UDIR_U1 BIT(6) +#define SSIFCMD_UREQ_U2 BIT(5) +#define SSIFCMD_UREQ_U1 BIT(4) + +#define USB3_EP0_SS_MAX_PACKET_SIZE 512 +#define USB3_EP0_HSFS_MAX_PACKET_SIZE 64 +#define USB3_EP0_BUF_SIZE 8 +#define USB3_MAX_NUM_PIPES 30 +#define USB3_WAIT_US 3 + +struct renesas_usb3; +struct renesas_usb3_request { + struct usb_request req; + struct list_head queue; +}; + +#define USB3_EP_NAME_SIZE 8 +struct renesas_usb3_ep { + struct usb_ep ep; + struct renesas_usb3 *usb3; + int num; + char ep_name[USB3_EP_NAME_SIZE]; + struct list_head queue; + u32 rammap_val; + bool dir_in; + bool halt; + bool wedge; + bool started; +}; + +struct renesas_usb3_priv { + int ramsize_per_ramif; /* unit = bytes */ + int num_ramif; + int ramsize_per_pipe; /* unit = bytes */ + bool workaround_for_vbus; /* if true, don't check vbus signal */ +}; + +struct renesas_usb3 { + void __iomem *reg; + + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + + struct renesas_usb3_ep *usb3_ep; + int num_usb3_eps; + + spinlock_t lock; + int disabled_count; + + struct usb_request *ep0_req; + u16 test_mode; + u8 ep0_buf[USB3_EP0_BUF_SIZE]; + bool softconnect; + bool workaround_for_vbus; +}; + +#define gadget_to_renesas_usb3(_gadget) \ + container_of(_gadget, struct renesas_usb3, gadget) +#define renesas_usb3_to_gadget(renesas_usb3) (&renesas_usb3->gadget) +#define usb3_to_dev(_usb3) (_usb3->gadget.dev.parent) + +#define usb_ep_to_usb3_ep(_ep) container_of(_ep, struct renesas_usb3_ep, ep) +#define usb3_ep_to_usb3(_usb3_ep) (_usb3_ep->usb3) +#define usb_req_to_usb3_req(_req) container_of(_req, \ + struct renesas_usb3_request, req) + +#define usb3_get_ep(usb3, n) ((usb3)->usb3_ep + (n)) +#define usb3_for_each_ep(usb3_ep, usb3, i) \ + for ((i) = 0, usb3_ep = usb3_get_ep(usb3, (i)); \ + (i) < (usb3)->num_usb3_eps; \ + (i)++, usb3_ep = usb3_get_ep(usb3, (i))) + +static const char udc_name[] = "renesas_usb3"; + +static void usb3_write(struct renesas_usb3 *usb3, u32 data, u32 offs) +{ + iowrite32(data, usb3->reg + offs); +} + +static u32 usb3_read(struct renesas_usb3 *usb3, u32 offs) +{ + return ioread32(usb3->reg + offs); +} + +static void usb3_set_bit(struct renesas_usb3 *usb3, u32 bits, u32 offs) +{ + u32 val = usb3_read(usb3, offs); + + val |= bits; + usb3_write(usb3, val, offs); +} + +static void usb3_clear_bit(struct renesas_usb3 *usb3, u32 bits, u32 offs) +{ + u32 val = usb3_read(usb3, offs); + + val &= ~bits; + usb3_write(usb3, val, offs); +} + +static int usb3_wait(struct renesas_usb3 *usb3, u32 reg, u32 mask, + u32 expected) +{ + int i; + + for (i = 0; i < USB3_WAIT_US; i++) { + if ((usb3_read(usb3, reg) & mask) == expected) + return 0; + udelay(1); + } + + dev_dbg(usb3_to_dev(usb3), "%s: timed out (%8x, %08x, %08x)\n", + __func__, reg, mask, expected); + + return -EBUSY; +} + +static void usb3_enable_irq_1(struct renesas_usb3 *usb3, u32 bits) +{ + usb3_set_bit(usb3, bits, USB3_USB_INT_ENA_1); +} + +static void usb3_disable_irq_1(struct renesas_usb3 *usb3, u32 bits) +{ + usb3_clear_bit(usb3, bits, USB3_USB_INT_ENA_1); +} + +static void usb3_enable_pipe_irq(struct renesas_usb3 *usb3, int num) +{ + usb3_set_bit(usb3, USB_INT_2_PIPE(num), USB3_USB_INT_ENA_2); +} + +static void usb3_disable_pipe_irq(struct renesas_usb3 *usb3, int num) +{ + usb3_clear_bit(usb3, USB_INT_2_PIPE(num), USB3_USB_INT_ENA_2); +} + +static void usb3_init_axi_bridge(struct renesas_usb3 *usb3) +{ + /* Set AXI_INT */ + usb3_write(usb3, ~0, USB3_DMA_INT_STA); + usb3_write(usb3, 0, USB3_DMA_INT_ENA); + usb3_set_bit(usb3, AXI_INT_DMAINT | AXI_INT_EPCINT, USB3_AXI_INT_ENA); +} + +static void usb3_init_epc_registers(struct renesas_usb3 *usb3) +{ + /* FIXME: How to change host / peripheral mode as well? */ + usb3_set_bit(usb3, DRD_CON_PERI_CON, USB3_DRD_CON); + + usb3_write(usb3, ~0, USB3_USB_INT_STA_1); + usb3_enable_irq_1(usb3, USB_INT_1_VBUS_CNG); +} + +static bool usb3_wakeup_usb2_phy(struct renesas_usb3 *usb3) +{ + if (!(usb3_read(usb3, USB3_USB20_CON) & USB20_CON_B2_SUSPEND)) + return true; /* already waked it up */ + + usb3_clear_bit(usb3, USB20_CON_B2_SUSPEND, USB3_USB20_CON); + usb3_enable_irq_1(usb3, USB_INT_1_B2_RSUM); + + return false; +} + +static void usb3_usb2_pullup(struct renesas_usb3 *usb3, int pullup) +{ + u32 bits = USB20_CON_B2_PUE | USB20_CON_B2_CONNECT; + + if (usb3->softconnect && pullup) + usb3_set_bit(usb3, bits, USB3_USB20_CON); + else + usb3_clear_bit(usb3, bits, USB3_USB20_CON); +} + +static void usb3_set_test_mode(struct renesas_usb3 *usb3) +{ + u32 val = usb3_read(usb3, USB3_USB20_CON); + + val &= ~USB20_CON_B2_TSTMOD_MASK; + val |= USB20_CON_B2_TSTMOD(usb3->test_mode); + usb3_write(usb3, val | USB20_CON_B2_TSTMOD_EN, USB3_USB20_CON); + if (!usb3->test_mode) + usb3_clear_bit(usb3, USB20_CON_B2_TSTMOD_EN, USB3_USB20_CON); +} + +static void usb3_start_usb2_connection(struct renesas_usb3 *usb3) +{ + usb3->disabled_count++; + usb3_set_bit(usb3, USB_COM_CON_EP0_EN, USB3_USB_COM_CON); + usb3_set_bit(usb3, USB_COM_CON_SPD_MODE, USB3_USB_COM_CON); + usb3_usb2_pullup(usb3, 1); +} + +static int usb3_is_usb3_phy_in_u3(struct renesas_usb3 *usb3) +{ + return usb3_read(usb3, USB3_USB30_CON) & USB30_CON_POW_SEL_IN_U3; +} + +static bool usb3_wakeup_usb3_phy(struct renesas_usb3 *usb3) +{ + if (!usb3_is_usb3_phy_in_u3(usb3)) + return true; /* already waked it up */ + + usb3_set_bit(usb3, USB30_CON_B3_PLLWAKE, USB3_USB30_CON); + usb3_enable_irq_1(usb3, USB_INT_1_B3_PLLWKUP); + + return false; +} + +static u16 usb3_feature_get_un_enabled(struct renesas_usb3 *usb3) +{ + u32 mask_u2 = SSIFCMD_UDIR_U2 | SSIFCMD_UREQ_U2; + u32 mask_u1 = SSIFCMD_UDIR_U1 | SSIFCMD_UREQ_U1; + u32 val = usb3_read(usb3, USB3_SSIFCMD); + u16 ret = 0; + + /* Enables {U2,U1} if the bits of UDIR and UREQ are set to 0 */ + if (!(val & mask_u2)) + ret |= 1 << USB_DEV_STAT_U2_ENABLED; + if (!(val & mask_u1)) + ret |= 1 << USB_DEV_STAT_U1_ENABLED; + + return ret; +} + +static void usb3_feature_u2_enable(struct renesas_usb3 *usb3, bool enable) +{ + u32 bits = SSIFCMD_UDIR_U2 | SSIFCMD_UREQ_U2; + + /* Enables U2 if the bits of UDIR and UREQ are set to 0 */ + if (enable) + usb3_clear_bit(usb3, bits, USB3_SSIFCMD); + else + usb3_set_bit(usb3, bits, USB3_SSIFCMD); +} + +static void usb3_feature_u1_enable(struct renesas_usb3 *usb3, bool enable) +{ + u32 bits = SSIFCMD_UDIR_U1 | SSIFCMD_UREQ_U1; + + /* Enables U1 if the bits of UDIR and UREQ are set to 0 */ + if (enable) + usb3_clear_bit(usb3, bits, USB3_SSIFCMD); + else + usb3_set_bit(usb3, bits, USB3_SSIFCMD); +} + +static void usb3_start_operation_for_usb3(struct renesas_usb3 *usb3) +{ + usb3_set_bit(usb3, USB_COM_CON_EP0_EN, USB3_USB_COM_CON); + usb3_clear_bit(usb3, USB_COM_CON_SPD_MODE, USB3_USB_COM_CON); + usb3_set_bit(usb3, USB30_CON_B3_CONNECT, USB3_USB30_CON); +} + +static void usb3_start_usb3_connection(struct renesas_usb3 *usb3) +{ + usb3_start_operation_for_usb3(usb3); + usb3_set_bit(usb3, USB_COM_CON_RX_DETECTION, USB3_USB_COM_CON); + + usb3_enable_irq_1(usb3, USB_INT_1_B3_LUPSUCS | USB_INT_1_B3_DISABLE | + USB_INT_1_SPEED); +} + +static void usb3_stop_usb3_connection(struct renesas_usb3 *usb3) +{ + usb3_clear_bit(usb3, USB30_CON_B3_CONNECT, USB3_USB30_CON); +} + +static void usb3_transition_to_default_state(struct renesas_usb3 *usb3, + bool is_usb3) +{ + usb3_set_bit(usb3, USB_INT_2_PIPE(0), USB3_USB_INT_ENA_2); + usb3_write(usb3, P0_INT_ALL_BITS, USB3_P0_INT_STA); + usb3_set_bit(usb3, P0_INT_ALL_BITS, USB3_P0_INT_ENA); + + if (is_usb3) + usb3_enable_irq_1(usb3, USB_INT_1_B3_WRMRST | + USB_INT_1_B3_HOTRST); + else + usb3_enable_irq_1(usb3, USB_INT_1_B2_SPND | + USB_INT_1_B2_L1SPND | USB_INT_1_B2_USBRST); +} + +static void usb3_connect(struct renesas_usb3 *usb3) +{ + if (usb3_wakeup_usb3_phy(usb3)) + usb3_start_usb3_connection(usb3); +} + +static void usb3_reset_epc(struct renesas_usb3 *usb3) +{ + usb3_clear_bit(usb3, USB_COM_CON_CONF, USB3_USB_COM_CON); + usb3_clear_bit(usb3, USB_COM_CON_EP0_EN, USB3_USB_COM_CON); + usb3_set_bit(usb3, USB_COM_CON_PIPE_CLR, USB3_USB_COM_CON); + usb3->test_mode = 0; + usb3_set_test_mode(usb3); +} + +static void usb3_disconnect(struct renesas_usb3 *usb3) +{ + usb3->disabled_count = 0; + usb3_usb2_pullup(usb3, 0); + usb3_clear_bit(usb3, USB30_CON_B3_CONNECT, USB3_USB30_CON); + usb3_reset_epc(usb3); + + if (usb3->driver) + usb3->driver->disconnect(&usb3->gadget); +} + +static void usb3_check_vbus(struct renesas_usb3 *usb3) +{ + if (usb3->workaround_for_vbus) { + usb3_connect(usb3); + } else { + if (usb3_read(usb3, USB3_USB_STA) & USB_STA_VBUS_STA) + usb3_connect(usb3); + else + usb3_disconnect(usb3); + } +} + +static void renesas_usb3_init_controller(struct renesas_usb3 *usb3) +{ + usb3_init_axi_bridge(usb3); + usb3_init_epc_registers(usb3); + + usb3_check_vbus(usb3); +} + +static void renesas_usb3_stop_controller(struct renesas_usb3 *usb3) +{ + usb3_disconnect(usb3); + usb3_write(usb3, 0, USB3_P0_INT_ENA); + usb3_write(usb3, 0, USB3_PN_INT_ENA); + usb3_write(usb3, 0, USB3_USB_INT_ENA_1); + usb3_write(usb3, 0, USB3_USB_INT_ENA_2); + usb3_write(usb3, 0, USB3_AXI_INT_ENA); +} + +static void usb3_irq_epc_int_1_pll_wakeup(struct renesas_usb3 *usb3) +{ + usb3_disable_irq_1(usb3, USB_INT_1_B3_PLLWKUP); + usb3_clear_bit(usb3, USB30_CON_B3_PLLWAKE, USB3_USB30_CON); + usb3_start_usb3_connection(usb3); +} + +static void usb3_irq_epc_int_1_linkup_success(struct renesas_usb3 *usb3) +{ + usb3_transition_to_default_state(usb3, true); +} + +static void usb3_irq_epc_int_1_resume(struct renesas_usb3 *usb3) +{ + usb3_disable_irq_1(usb3, USB_INT_1_B2_RSUM); + usb3_start_usb2_connection(usb3); + usb3_transition_to_default_state(usb3, false); +} + +static void usb3_irq_epc_int_1_disable(struct renesas_usb3 *usb3) +{ + usb3_stop_usb3_connection(usb3); + if (usb3_wakeup_usb2_phy(usb3)) + usb3_irq_epc_int_1_resume(usb3); +} + +static void usb3_irq_epc_int_1_bus_reset(struct renesas_usb3 *usb3) +{ + usb3_reset_epc(usb3); + if (usb3->disabled_count < 3) + usb3_start_usb3_connection(usb3); + else + usb3_start_usb2_connection(usb3); +} + +static void usb3_irq_epc_int_1_vbus_change(struct renesas_usb3 *usb3) +{ + usb3_check_vbus(usb3); +} + +static void usb3_irq_epc_int_1_hot_reset(struct renesas_usb3 *usb3) +{ + usb3_reset_epc(usb3); + usb3_set_bit(usb3, USB_COM_CON_EP0_EN, USB3_USB_COM_CON); + + /* This bit shall be set within 12ms from the start of HotReset */ + usb3_set_bit(usb3, USB30_CON_B3_HOTRST_CMP, USB3_USB30_CON); +} + +static void usb3_irq_epc_int_1_warm_reset(struct renesas_usb3 *usb3) +{ + usb3_reset_epc(usb3); + usb3_set_bit(usb3, USB_COM_CON_EP0_EN, USB3_USB_COM_CON); + + usb3_start_operation_for_usb3(usb3); + usb3_enable_irq_1(usb3, USB_INT_1_SPEED); +} + +static void usb3_irq_epc_int_1_speed(struct renesas_usb3 *usb3) +{ + u32 speed = usb3_read(usb3, USB3_USB_STA) & USB_STA_SPEED_MASK; + + switch (speed) { + case USB_STA_SPEED_SS: + usb3->gadget.speed = USB_SPEED_SUPER; + break; + case USB_STA_SPEED_HS: + usb3->gadget.speed = USB_SPEED_HIGH; + break; + case USB_STA_SPEED_FS: + usb3->gadget.speed = USB_SPEED_FULL; + break; + default: + usb3->gadget.speed = USB_SPEED_UNKNOWN; + break; + } +} + +static void usb3_irq_epc_int_1(struct renesas_usb3 *usb3, u32 int_sta_1) +{ + if (int_sta_1 & USB_INT_1_B3_PLLWKUP) + usb3_irq_epc_int_1_pll_wakeup(usb3); + + if (int_sta_1 & USB_INT_1_B3_LUPSUCS) + usb3_irq_epc_int_1_linkup_success(usb3); + + if (int_sta_1 & USB_INT_1_B3_HOTRST) + usb3_irq_epc_int_1_hot_reset(usb3); + + if (int_sta_1 & USB_INT_1_B3_WRMRST) + usb3_irq_epc_int_1_warm_reset(usb3); + + if (int_sta_1 & USB_INT_1_B3_DISABLE) + usb3_irq_epc_int_1_disable(usb3); + + if (int_sta_1 & USB_INT_1_B2_USBRST) + usb3_irq_epc_int_1_bus_reset(usb3); + + if (int_sta_1 & USB_INT_1_B2_RSUM) + usb3_irq_epc_int_1_resume(usb3); + + if (int_sta_1 & USB_INT_1_SPEED) + usb3_irq_epc_int_1_speed(usb3); + + if (int_sta_1 & USB_INT_1_VBUS_CNG) + usb3_irq_epc_int_1_vbus_change(usb3); +} + +static struct renesas_usb3_request *__usb3_get_request(struct renesas_usb3_ep + *usb3_ep) +{ + return list_first_entry_or_null(&usb3_ep->queue, + struct renesas_usb3_request, queue); +} + +static struct renesas_usb3_request *usb3_get_request(struct renesas_usb3_ep + *usb3_ep) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + struct renesas_usb3_request *usb3_req; + unsigned long flags; + + spin_lock_irqsave(&usb3->lock, flags); + usb3_req = __usb3_get_request(usb3_ep); + spin_unlock_irqrestore(&usb3->lock, flags); + + return usb3_req; +} + +static void usb3_request_done(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req, int status) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + unsigned long flags; + + dev_dbg(usb3_to_dev(usb3), "giveback: ep%2d, %u, %u, %d\n", + usb3_ep->num, usb3_req->req.length, usb3_req->req.actual, + status); + usb3_req->req.status = status; + spin_lock_irqsave(&usb3->lock, flags); + usb3_ep->started = false; + list_del_init(&usb3_req->queue); + spin_unlock_irqrestore(&usb3->lock, flags); + usb_gadget_giveback_request(&usb3_ep->ep, &usb3_req->req); +} + +static void usb3_irq_epc_pipe0_status_end(struct renesas_usb3 *usb3) +{ + struct renesas_usb3_ep *usb3_ep = usb3_get_ep(usb3, 0); + struct renesas_usb3_request *usb3_req = usb3_get_request(usb3_ep); + + if (usb3_req) + usb3_request_done(usb3_ep, usb3_req, 0); + if (usb3->test_mode) + usb3_set_test_mode(usb3); +} + +static void usb3_get_setup_data(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl) +{ + struct renesas_usb3_ep *usb3_ep = usb3_get_ep(usb3, 0); + u32 *data = (u32 *)ctrl; + + *data++ = usb3_read(usb3, USB3_STUP_DAT_0); + *data = usb3_read(usb3, USB3_STUP_DAT_1); + + /* update this driver's flag */ + usb3_ep->dir_in = !!(ctrl->bRequestType & USB_DIR_IN); +} + +static void usb3_set_p0_con_update_res(struct renesas_usb3 *usb3, u32 res) +{ + u32 val = usb3_read(usb3, USB3_P0_CON); + + val &= ~(P0_CON_ST_RES_MASK | P0_CON_OT_RES_MASK | P0_CON_IN_RES_MASK); + val |= res | P0_CON_RES_WEN; + usb3_write(usb3, val, USB3_P0_CON); +} + +static void usb3_set_p0_con_for_ctrl_read_data(struct renesas_usb3 *usb3) +{ + usb3_set_p0_con_update_res(usb3, P0_CON_ST_RES_FORCE_NRDY | + P0_CON_OT_RES_FORCE_STALL | + P0_CON_IN_RES_NORMAL); +} + +static void usb3_set_p0_con_for_ctrl_read_status(struct renesas_usb3 *usb3) +{ + usb3_set_p0_con_update_res(usb3, P0_CON_ST_RES_NORMAL | + P0_CON_OT_RES_FORCE_STALL | + P0_CON_IN_RES_NORMAL); +} + +static void usb3_set_p0_con_for_ctrl_write_data(struct renesas_usb3 *usb3) +{ + usb3_set_p0_con_update_res(usb3, P0_CON_ST_RES_FORCE_NRDY | + P0_CON_OT_RES_NORMAL | + P0_CON_IN_RES_FORCE_STALL); +} + +static void usb3_set_p0_con_for_ctrl_write_status(struct renesas_usb3 *usb3) +{ + usb3_set_p0_con_update_res(usb3, P0_CON_ST_RES_NORMAL | + P0_CON_OT_RES_NORMAL | + P0_CON_IN_RES_FORCE_STALL); +} + +static void usb3_set_p0_con_for_no_data(struct renesas_usb3 *usb3) +{ + usb3_set_p0_con_update_res(usb3, P0_CON_ST_RES_NORMAL | + P0_CON_OT_RES_FORCE_STALL | + P0_CON_IN_RES_FORCE_STALL); +} + +static void usb3_set_p0_con_stall(struct renesas_usb3 *usb3) +{ + usb3_set_p0_con_update_res(usb3, P0_CON_ST_RES_FORCE_STALL | + P0_CON_OT_RES_FORCE_STALL | + P0_CON_IN_RES_FORCE_STALL); +} + +static void usb3_set_p0_con_stop(struct renesas_usb3 *usb3) +{ + usb3_set_p0_con_update_res(usb3, P0_CON_ST_RES_FORCE_NRDY | + P0_CON_OT_RES_FORCE_NRDY | + P0_CON_IN_RES_FORCE_NRDY); +} + +static int usb3_pn_change(struct renesas_usb3 *usb3, int num) +{ + if (num == 0 || num > usb3->num_usb3_eps) + return -ENXIO; + + usb3_write(usb3, num, USB3_PIPE_COM); + + return 0; +} + +static void usb3_set_pn_con_update_res(struct renesas_usb3 *usb3, u32 res) +{ + u32 val = usb3_read(usb3, USB3_PN_CON); + + val &= ~PN_CON_RES_MASK; + val |= res & PN_CON_RES_MASK; + val |= PN_CON_RES_WEN; + usb3_write(usb3, val, USB3_PN_CON); +} + +static void usb3_pn_start(struct renesas_usb3 *usb3) +{ + usb3_set_pn_con_update_res(usb3, PN_CON_RES_NORMAL); +} + +static void usb3_pn_stop(struct renesas_usb3 *usb3) +{ + usb3_set_pn_con_update_res(usb3, PN_CON_RES_FORCE_NRDY); +} + +static void usb3_pn_stall(struct renesas_usb3 *usb3) +{ + usb3_set_pn_con_update_res(usb3, PN_CON_RES_FORCE_STALL); +} + +static int usb3_pn_con_clear(struct renesas_usb3 *usb3) +{ + usb3_set_bit(usb3, PN_CON_CLR, USB3_PN_CON); + + return usb3_wait(usb3, USB3_PN_CON, PN_CON_CLR, 0); +} + +static bool usb3_is_transfer_complete(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct usb_request *req = &usb3_req->req; + + if ((!req->zero && req->actual == req->length) || + (req->actual % usb3_ep->ep.maxpacket) || (req->length == 0)) + return true; + else + return false; +} + +static int usb3_wait_pipe_status(struct renesas_usb3_ep *usb3_ep, u32 mask) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + u32 sta_reg = usb3_ep->num ? USB3_PN_STA : USB3_P0_STA; + + return usb3_wait(usb3, sta_reg, mask, mask); +} + +static void usb3_set_px_con_send(struct renesas_usb3_ep *usb3_ep, int bytes, + bool last) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + u32 con_reg = usb3_ep->num ? USB3_PN_CON : USB3_P0_CON; + u32 val = usb3_read(usb3, con_reg); + + val |= PX_CON_SEND | PX_CON_BYTE_EN_BYTES(bytes); + val |= (usb3_ep->num && last) ? PN_CON_LAST : 0; + usb3_write(usb3, val, con_reg); +} + +static int usb3_write_pipe(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req, + u32 fifo_reg) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + int i; + int len = min_t(unsigned, usb3_req->req.length - usb3_req->req.actual, + usb3_ep->ep.maxpacket); + u8 *buf = usb3_req->req.buf + usb3_req->req.actual; + u32 tmp = 0; + bool is_last; + + if (usb3_wait_pipe_status(usb3_ep, PX_STA_BUFSTS) < 0) + return -EBUSY; + + /* Update gadget driver parameter */ + usb3_req->req.actual += len; + + /* Write data to the register */ + if (len >= 4) { + iowrite32_rep(usb3->reg + fifo_reg, buf, len / 4); + buf += (len / 4) * 4; + len %= 4; /* update len to use usb3_set_pX_con_send() */ + } + + if (len) { + for (i = 0; i < len; i++) + tmp |= buf[i] << (8 * i); + usb3_write(usb3, tmp, fifo_reg); + } + + is_last = usb3_is_transfer_complete(usb3_ep, usb3_req); + /* Send the data */ + usb3_set_px_con_send(usb3_ep, len, is_last); + + return is_last ? 0 : -EAGAIN; +} + +static u32 usb3_get_received_length(struct renesas_usb3_ep *usb3_ep) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + u32 lng_reg = usb3_ep->num ? USB3_PN_LNG : USB3_P0_LNG; + + return usb3_read(usb3, lng_reg); +} + +static int usb3_read_pipe(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req, u32 fifo_reg) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + int i; + int len = min_t(unsigned, usb3_req->req.length - usb3_req->req.actual, + usb3_get_received_length(usb3_ep)); + u8 *buf = usb3_req->req.buf + usb3_req->req.actual; + u32 tmp = 0; + + if (!len) + return 0; + + /* Update gadget driver parameter */ + usb3_req->req.actual += len; + + /* Read data from the register */ + if (len >= 4) { + ioread32_rep(usb3->reg + fifo_reg, buf, len / 4); + buf += (len / 4) * 4; + len %= 4; + } + + if (len) { + tmp = usb3_read(usb3, fifo_reg); + for (i = 0; i < len; i++) + buf[i] = (tmp >> (8 * i)) & 0xff; + } + + return usb3_is_transfer_complete(usb3_ep, usb3_req) ? 0 : -EAGAIN; +} + +static void usb3_set_status_stage(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + + if (usb3_ep->dir_in) { + usb3_set_p0_con_for_ctrl_read_status(usb3); + } else { + if (!usb3_req->req.length) + usb3_set_p0_con_for_no_data(usb3); + else + usb3_set_p0_con_for_ctrl_write_status(usb3); + } +} + +static void usb3_p0_xfer(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + int ret = -EAGAIN; + + if (usb3_ep->dir_in) + ret = usb3_write_pipe(usb3_ep, usb3_req, USB3_P0_WRITE); + else + ret = usb3_read_pipe(usb3_ep, usb3_req, USB3_P0_READ); + + if (!ret) + usb3_set_status_stage(usb3_ep, usb3_req); +} + +static void usb3_start_pipe0(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + + if (usb3_ep->started) + return; + + usb3_ep->started = true; + + if (usb3_ep->dir_in) { + usb3_set_bit(usb3, P0_MOD_DIR, USB3_P0_MOD); + usb3_set_p0_con_for_ctrl_read_data(usb3); + } else { + usb3_clear_bit(usb3, P0_MOD_DIR, USB3_P0_MOD); + usb3_set_p0_con_for_ctrl_write_data(usb3); + } + + usb3_p0_xfer(usb3_ep, usb3_req); +} + +static void usb3_start_pipen(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + struct renesas_usb3_request *usb3_req_first = usb3_get_request(usb3_ep); + unsigned long flags; + int ret = -EAGAIN; + u32 enable_bits = 0; + + if (usb3_ep->halt || usb3_ep->started) + return; + if (usb3_req != usb3_req_first) + return; + + spin_lock_irqsave(&usb3->lock, flags); + if (usb3_pn_change(usb3, usb3_ep->num) < 0) + goto out; + + usb3_ep->started = true; + usb3_pn_start(usb3); + + if (usb3_ep->dir_in) { + ret = usb3_write_pipe(usb3_ep, usb3_req, USB3_PN_WRITE); + enable_bits |= PN_INT_LSTTR; + } + + if (ret < 0) + enable_bits |= PN_INT_BFRDY; + + if (enable_bits) { + usb3_set_bit(usb3, enable_bits, USB3_PN_INT_ENA); + usb3_enable_pipe_irq(usb3, usb3_ep->num); + } +out: + spin_unlock_irqrestore(&usb3->lock, flags); +} + +static int renesas_usb3_ep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct renesas_usb3_ep *usb3_ep = usb_ep_to_usb3_ep(_ep); + struct renesas_usb3_request *usb3_req = usb_req_to_usb3_req(_req); + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + unsigned long flags; + + dev_dbg(usb3_to_dev(usb3), "ep_queue: ep%2d, %u\n", usb3_ep->num, + _req->length); + + _req->status = -EINPROGRESS; + _req->actual = 0; + spin_lock_irqsave(&usb3->lock, flags); + list_add_tail(&usb3_req->queue, &usb3_ep->queue); + spin_unlock_irqrestore(&usb3->lock, flags); + + if (!usb3_ep->num) + usb3_start_pipe0(usb3_ep, usb3_req); + else + usb3_start_pipen(usb3_ep, usb3_req); + + return 0; +} + +static void usb3_set_device_address(struct renesas_usb3 *usb3, u16 addr) +{ + /* DEV_ADDR bit field is cleared by WarmReset, HotReset and BusReset */ + usb3_set_bit(usb3, USB_COM_CON_DEV_ADDR(addr), USB3_USB_COM_CON); +} + +static bool usb3_std_req_set_address(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl) +{ + if (ctrl->wValue >= 128) + return true; /* stall */ + + usb3_set_device_address(usb3, ctrl->wValue); + usb3_set_p0_con_for_no_data(usb3); + + return false; +} + +static void usb3_pipe0_internal_xfer(struct renesas_usb3 *usb3, + void *tx_data, size_t len, + void (*complete)(struct usb_ep *ep, + struct usb_request *req)) +{ + struct renesas_usb3_ep *usb3_ep = usb3_get_ep(usb3, 0); + + if (tx_data) + memcpy(usb3->ep0_buf, tx_data, + min_t(size_t, len, USB3_EP0_BUF_SIZE)); + + usb3->ep0_req->buf = &usb3->ep0_buf; + usb3->ep0_req->length = len; + usb3->ep0_req->complete = complete; + renesas_usb3_ep_queue(&usb3_ep->ep, usb3->ep0_req, GFP_ATOMIC); +} + +static void usb3_pipe0_get_status_completion(struct usb_ep *ep, + struct usb_request *req) +{ +} + +static bool usb3_std_req_get_status(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl) +{ + bool stall = false; + struct renesas_usb3_ep *usb3_ep; + int num; + u16 status = 0; + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + if (usb3->gadget.is_selfpowered) + status |= 1 << USB_DEVICE_SELF_POWERED; + if (usb3->gadget.speed == USB_SPEED_SUPER) + status |= usb3_feature_get_un_enabled(usb3); + break; + case USB_RECIP_INTERFACE: + break; + case USB_RECIP_ENDPOINT: + num = le16_to_cpu(ctrl->wIndex) & USB_ENDPOINT_NUMBER_MASK; + usb3_ep = usb3_get_ep(usb3, num); + if (usb3_ep->halt) + status |= 1 << USB_ENDPOINT_HALT; + break; + default: + stall = true; + break; + } + + if (!stall) { + status = cpu_to_le16(status); + dev_dbg(usb3_to_dev(usb3), "get_status: req = %p\n", + usb_req_to_usb3_req(usb3->ep0_req)); + usb3_pipe0_internal_xfer(usb3, &status, sizeof(status), + usb3_pipe0_get_status_completion); + } + + return stall; +} + +static bool usb3_std_req_feature_device(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl, bool set) +{ + bool stall = true; + u16 w_value = le16_to_cpu(ctrl->wValue); + + switch (w_value) { + case USB_DEVICE_TEST_MODE: + if (!set) + break; + usb3->test_mode = le16_to_cpu(ctrl->wIndex) >> 8; + stall = false; + break; + case USB_DEVICE_U1_ENABLE: + case USB_DEVICE_U2_ENABLE: + if (usb3->gadget.speed != USB_SPEED_SUPER) + break; + if (w_value == USB_DEVICE_U1_ENABLE) + usb3_feature_u1_enable(usb3, set); + if (w_value == USB_DEVICE_U2_ENABLE) + usb3_feature_u2_enable(usb3, set); + stall = false; + break; + default: + break; + } + + return stall; +} + +static int usb3_set_halt_p0(struct renesas_usb3_ep *usb3_ep, bool halt) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + + if (unlikely(usb3_ep->num)) + return -EINVAL; + + usb3_ep->halt = halt; + if (halt) + usb3_set_p0_con_stall(usb3); + else + usb3_set_p0_con_stop(usb3); + + return 0; +} + +static int usb3_set_halt_pn(struct renesas_usb3_ep *usb3_ep, bool halt, + bool is_clear_feature) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + unsigned long flags; + + spin_lock_irqsave(&usb3->lock, flags); + if (!usb3_pn_change(usb3, usb3_ep->num)) { + usb3_ep->halt = halt; + if (halt) { + usb3_pn_stall(usb3); + } else if (!is_clear_feature || !usb3_ep->wedge) { + usb3_pn_con_clear(usb3); + usb3_set_bit(usb3, PN_CON_EN, USB3_PN_CON); + usb3_pn_stop(usb3); + } + } + spin_unlock_irqrestore(&usb3->lock, flags); + + return 0; +} + +static int usb3_set_halt(struct renesas_usb3_ep *usb3_ep, bool halt, + bool is_clear_feature) +{ + int ret = 0; + + if (halt && usb3_ep->started) + return -EAGAIN; + + if (usb3_ep->num) + ret = usb3_set_halt_pn(usb3_ep, halt, is_clear_feature); + else + ret = usb3_set_halt_p0(usb3_ep, halt); + + return ret; +} + +static bool usb3_std_req_feature_endpoint(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl, + bool set) +{ + int num = le16_to_cpu(ctrl->wIndex) & USB_ENDPOINT_NUMBER_MASK; + struct renesas_usb3_ep *usb3_ep; + struct renesas_usb3_request *usb3_req; + + if (le16_to_cpu(ctrl->wValue) != USB_ENDPOINT_HALT) + return true; /* stall */ + + usb3_ep = usb3_get_ep(usb3, num); + usb3_set_halt(usb3_ep, set, true); + + /* Restarts a queue if clear feature */ + if (!set) { + usb3_ep->started = false; + usb3_req = usb3_get_request(usb3_ep); + if (usb3_req) + usb3_start_pipen(usb3_ep, usb3_req); + } + + return false; +} + +static bool usb3_std_req_feature(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl, bool set) +{ + bool stall = false; + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + stall = usb3_std_req_feature_device(usb3, ctrl, set); + break; + case USB_RECIP_INTERFACE: + break; + case USB_RECIP_ENDPOINT: + stall = usb3_std_req_feature_endpoint(usb3, ctrl, set); + break; + default: + stall = true; + break; + } + + if (!stall) + usb3_set_p0_con_for_no_data(usb3); + + return stall; +} + +static void usb3_pipe0_set_sel_completion(struct usb_ep *ep, + struct usb_request *req) +{ + /* TODO */ +} + +static bool usb3_std_req_set_sel(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl) +{ + u16 w_length = le16_to_cpu(ctrl->wLength); + + if (w_length != 6) + return true; /* stall */ + + dev_dbg(usb3_to_dev(usb3), "set_sel: req = %p\n", + usb_req_to_usb3_req(usb3->ep0_req)); + usb3_pipe0_internal_xfer(usb3, NULL, 6, usb3_pipe0_set_sel_completion); + + return false; +} + +static bool usb3_std_req_set_configuration(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl) +{ + if (ctrl->wValue > 0) + usb3_set_bit(usb3, USB_COM_CON_CONF, USB3_USB_COM_CON); + else + usb3_clear_bit(usb3, USB_COM_CON_CONF, USB3_USB_COM_CON); + + return false; +} + +/** + * usb3_handle_standard_request - handle some standard requests + * @usb3: the renesas_usb3 pointer + * @ctrl: a pointer of setup data + * + * Returns true if this function handled a standard request + */ +static bool usb3_handle_standard_request(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl) +{ + bool ret = false; + bool stall = false; + + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (ctrl->bRequest) { + case USB_REQ_SET_ADDRESS: + stall = usb3_std_req_set_address(usb3, ctrl); + ret = true; + break; + case USB_REQ_GET_STATUS: + stall = usb3_std_req_get_status(usb3, ctrl); + ret = true; + break; + case USB_REQ_CLEAR_FEATURE: + stall = usb3_std_req_feature(usb3, ctrl, false); + ret = true; + break; + case USB_REQ_SET_FEATURE: + stall = usb3_std_req_feature(usb3, ctrl, true); + ret = true; + break; + case USB_REQ_SET_SEL: + stall = usb3_std_req_set_sel(usb3, ctrl); + ret = true; + break; + case USB_REQ_SET_ISOCH_DELAY: + /* This hardware doesn't support Isochronous xfer */ + stall = true; + ret = true; + break; + case USB_REQ_SET_CONFIGURATION: + usb3_std_req_set_configuration(usb3, ctrl); + break; + default: + break; + } + } + + if (stall) + usb3_set_p0_con_stall(usb3); + + return ret; +} + +static int usb3_p0_con_clear_buffer(struct renesas_usb3 *usb3) +{ + usb3_set_bit(usb3, P0_CON_BCLR, USB3_P0_CON); + + return usb3_wait(usb3, USB3_P0_CON, P0_CON_BCLR, 0); +} + +static void usb3_irq_epc_pipe0_setup(struct renesas_usb3 *usb3) +{ + struct usb_ctrlrequest ctrl; + struct renesas_usb3_ep *usb3_ep = usb3_get_ep(usb3, 0); + + /* Call giveback function if previous transfer is not completed */ + if (usb3_ep->started) + usb3_request_done(usb3_ep, usb3_get_request(usb3_ep), + -ECONNRESET); + + usb3_p0_con_clear_buffer(usb3); + usb3_get_setup_data(usb3, &ctrl); + if (!usb3_handle_standard_request(usb3, &ctrl)) + if (usb3->driver->setup(&usb3->gadget, &ctrl) < 0) + usb3_set_p0_con_stall(usb3); +} + +static void usb3_irq_epc_pipe0_bfrdy(struct renesas_usb3 *usb3) +{ + struct renesas_usb3_ep *usb3_ep = usb3_get_ep(usb3, 0); + struct renesas_usb3_request *usb3_req = usb3_get_request(usb3_ep); + + if (!usb3_req) + return; + + usb3_p0_xfer(usb3_ep, usb3_req); +} + +static void usb3_irq_epc_pipe0(struct renesas_usb3 *usb3) +{ + u32 p0_int_sta = usb3_read(usb3, USB3_P0_INT_STA); + + p0_int_sta &= usb3_read(usb3, USB3_P0_INT_ENA); + usb3_write(usb3, p0_int_sta, USB3_P0_INT_STA); + if (p0_int_sta & P0_INT_STSED) + usb3_irq_epc_pipe0_status_end(usb3); + if (p0_int_sta & P0_INT_SETUP) + usb3_irq_epc_pipe0_setup(usb3); + if (p0_int_sta & P0_INT_BFRDY) + usb3_irq_epc_pipe0_bfrdy(usb3); +} + +static void usb3_request_done_pipen(struct renesas_usb3 *usb3, + struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req, + int status) +{ + usb3_pn_stop(usb3); + usb3_disable_pipe_irq(usb3, usb3_ep->num); + usb3_request_done(usb3_ep, usb3_req, status); + + /* get next usb3_req */ + usb3_req = usb3_get_request(usb3_ep); + if (usb3_req) + usb3_start_pipen(usb3_ep, usb3_req); +} + +static void usb3_irq_epc_pipen_lsttr(struct renesas_usb3 *usb3, int num) +{ + struct renesas_usb3_ep *usb3_ep = usb3_get_ep(usb3, num); + struct renesas_usb3_request *usb3_req = usb3_get_request(usb3_ep); + + if (!usb3_req) + return; + + if (usb3_ep->dir_in) { + dev_dbg(usb3_to_dev(usb3), "%s: len = %u, actual = %u\n", + __func__, usb3_req->req.length, usb3_req->req.actual); + usb3_request_done_pipen(usb3, usb3_ep, usb3_req, 0); + } +} + +static void usb3_irq_epc_pipen_bfrdy(struct renesas_usb3 *usb3, int num) +{ + struct renesas_usb3_ep *usb3_ep = usb3_get_ep(usb3, num); + struct renesas_usb3_request *usb3_req = usb3_get_request(usb3_ep); + + if (!usb3_req) + return; + + if (usb3_ep->dir_in) { + /* Do not stop the IN pipe here to detect LSTTR interrupt */ + if (!usb3_write_pipe(usb3_ep, usb3_req, USB3_PN_WRITE)) + usb3_clear_bit(usb3, PN_INT_BFRDY, USB3_PN_INT_ENA); + } else { + if (!usb3_read_pipe(usb3_ep, usb3_req, USB3_PN_READ)) + usb3_request_done_pipen(usb3, usb3_ep, usb3_req, 0); + } +} + +static void usb3_irq_epc_pipen(struct renesas_usb3 *usb3, int num) +{ + u32 pn_int_sta; + + if (usb3_pn_change(usb3, num) < 0) + return; + + pn_int_sta = usb3_read(usb3, USB3_PN_INT_STA); + pn_int_sta &= usb3_read(usb3, USB3_PN_INT_ENA); + usb3_write(usb3, pn_int_sta, USB3_PN_INT_STA); + if (pn_int_sta & PN_INT_LSTTR) + usb3_irq_epc_pipen_lsttr(usb3, num); + if (pn_int_sta & PN_INT_BFRDY) + usb3_irq_epc_pipen_bfrdy(usb3, num); +} + +static void usb3_irq_epc_int_2(struct renesas_usb3 *usb3, u32 int_sta_2) +{ + int i; + + for (i = 0; i < usb3->num_usb3_eps; i++) { + if (int_sta_2 & USB_INT_2_PIPE(i)) { + if (!i) + usb3_irq_epc_pipe0(usb3); + else + usb3_irq_epc_pipen(usb3, i); + } + } +} + +static void usb3_irq_epc(struct renesas_usb3 *usb3) +{ + u32 int_sta_1 = usb3_read(usb3, USB3_USB_INT_STA_1); + u32 int_sta_2 = usb3_read(usb3, USB3_USB_INT_STA_2); + + int_sta_1 &= usb3_read(usb3, USB3_USB_INT_ENA_1); + if (int_sta_1) { + usb3_write(usb3, int_sta_1, USB3_USB_INT_STA_1); + usb3_irq_epc_int_1(usb3, int_sta_1); + } + + int_sta_2 &= usb3_read(usb3, USB3_USB_INT_ENA_2); + if (int_sta_2) + usb3_irq_epc_int_2(usb3, int_sta_2); +} + +static irqreturn_t renesas_usb3_irq(int irq, void *_usb3) +{ + struct renesas_usb3 *usb3 = _usb3; + irqreturn_t ret = IRQ_NONE; + u32 axi_int_sta = usb3_read(usb3, USB3_AXI_INT_STA); + + if (axi_int_sta & AXI_INT_EPCINT) { + usb3_irq_epc(usb3); + ret = IRQ_HANDLED; + } + + return ret; +} + +static void usb3_write_pn_mod(struct renesas_usb3_ep *usb3_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + u32 val = 0; + + val |= usb3_ep->dir_in ? PN_MOD_DIR : 0; + val |= PN_MOD_TYPE(usb_endpoint_type(desc)); + val |= PN_MOD_EPNUM(usb_endpoint_num(desc)); + usb3_write(usb3, val, USB3_PN_MOD); +} + +static u32 usb3_calc_ramarea(int ram_size) +{ + WARN_ON(ram_size > SZ_16K); + + if (ram_size <= SZ_1K) + return PN_RAMMAP_RAMAREA_1KB; + else if (ram_size <= SZ_2K) + return PN_RAMMAP_RAMAREA_2KB; + else if (ram_size <= SZ_4K) + return PN_RAMMAP_RAMAREA_4KB; + else if (ram_size <= SZ_8K) + return PN_RAMMAP_RAMAREA_8KB; + else + return PN_RAMMAP_RAMAREA_16KB; +} + +static u32 usb3_calc_rammap_val(struct renesas_usb3_ep *usb3_ep, + const struct usb_endpoint_descriptor *desc) +{ + return usb3_ep->rammap_val | PN_RAMMAP_MPKT(usb_endpoint_maxp(desc)); +} + +static int usb3_enable_pipe_n(struct renesas_usb3_ep *usb3_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + unsigned long flags; + + usb3_ep->dir_in = usb_endpoint_dir_in(desc); + + spin_lock_irqsave(&usb3->lock, flags); + if (!usb3_pn_change(usb3, usb3_ep->num)) { + usb3_write_pn_mod(usb3_ep, desc); + usb3_write(usb3, usb3_calc_rammap_val(usb3_ep, desc), + USB3_PN_RAMMAP); + usb3_pn_con_clear(usb3); + usb3_set_bit(usb3, PN_CON_EN, USB3_PN_CON); + } + spin_unlock_irqrestore(&usb3->lock, flags); + + return 0; +} + +static int usb3_disable_pipe_n(struct renesas_usb3_ep *usb3_ep) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + unsigned long flags; + + usb3_ep->halt = false; + + spin_lock_irqsave(&usb3->lock, flags); + if (!usb3_pn_change(usb3, usb3_ep->num)) { + usb3_write(usb3, 0, USB3_PN_RAMMAP); + usb3_clear_bit(usb3, PN_CON_EN, USB3_PN_CON); + } + spin_unlock_irqrestore(&usb3->lock, flags); + + return 0; +} + +/*------- usb_ep_ops -----------------------------------------------------*/ +static int renesas_usb3_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct renesas_usb3_ep *usb3_ep = usb_ep_to_usb3_ep(_ep); + + return usb3_enable_pipe_n(usb3_ep, desc); +} + +static int renesas_usb3_ep_disable(struct usb_ep *_ep) +{ + struct renesas_usb3_ep *usb3_ep = usb_ep_to_usb3_ep(_ep); + struct renesas_usb3_request *usb3_req; + + do { + usb3_req = usb3_get_request(usb3_ep); + if (!usb3_req) + break; + usb3_request_done(usb3_ep, usb3_req, -ESHUTDOWN); + } while (1); + + return usb3_disable_pipe_n(usb3_ep); +} + +static struct usb_request *__renesas_usb3_ep_alloc_request(gfp_t gfp_flags) +{ + struct renesas_usb3_request *usb3_req; + + usb3_req = kzalloc(sizeof(struct renesas_usb3_request), gfp_flags); + if (!usb3_req) + return NULL; + + INIT_LIST_HEAD(&usb3_req->queue); + + return &usb3_req->req; +} + +static void __renesas_usb3_ep_free_request(struct usb_request *_req) +{ + struct renesas_usb3_request *usb3_req = usb_req_to_usb3_req(_req); + + kfree(usb3_req); +} + +static struct usb_request *renesas_usb3_ep_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + return __renesas_usb3_ep_alloc_request(gfp_flags); +} + +static void renesas_usb3_ep_free_request(struct usb_ep *_ep, + struct usb_request *_req) +{ + __renesas_usb3_ep_free_request(_req); +} + +static int renesas_usb3_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct renesas_usb3_ep *usb3_ep = usb_ep_to_usb3_ep(_ep); + struct renesas_usb3_request *usb3_req = usb_req_to_usb3_req(_req); + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + + dev_dbg(usb3_to_dev(usb3), "ep_dequeue: ep%2d, %u\n", usb3_ep->num, + _req->length); + + usb3_request_done_pipen(usb3, usb3_ep, usb3_req, -ECONNRESET); + + return 0; +} + +static int renesas_usb3_ep_set_halt(struct usb_ep *_ep, int value) +{ + return usb3_set_halt(usb_ep_to_usb3_ep(_ep), !!value, false); +} + +static int renesas_usb3_ep_set_wedge(struct usb_ep *_ep) +{ + struct renesas_usb3_ep *usb3_ep = usb_ep_to_usb3_ep(_ep); + + usb3_ep->wedge = true; + return usb3_set_halt(usb3_ep, true, false); +} + +static void renesas_usb3_ep_fifo_flush(struct usb_ep *_ep) +{ + struct renesas_usb3_ep *usb3_ep = usb_ep_to_usb3_ep(_ep); + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + unsigned long flags; + + if (usb3_ep->num) { + spin_lock_irqsave(&usb3->lock, flags); + if (!usb3_pn_change(usb3, usb3_ep->num)) { + usb3_pn_con_clear(usb3); + usb3_set_bit(usb3, PN_CON_EN, USB3_PN_CON); + } + spin_unlock_irqrestore(&usb3->lock, flags); + } else { + usb3_p0_con_clear_buffer(usb3); + } +} + +static struct usb_ep_ops renesas_usb3_ep_ops = { + .enable = renesas_usb3_ep_enable, + .disable = renesas_usb3_ep_disable, + + .alloc_request = renesas_usb3_ep_alloc_request, + .free_request = renesas_usb3_ep_free_request, + + .queue = renesas_usb3_ep_queue, + .dequeue = renesas_usb3_ep_dequeue, + + .set_halt = renesas_usb3_ep_set_halt, + .set_wedge = renesas_usb3_ep_set_wedge, + .fifo_flush = renesas_usb3_ep_fifo_flush, +}; + +/*------- usb_gadget_ops -------------------------------------------------*/ +static int renesas_usb3_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct renesas_usb3 *usb3; + + if (!driver || driver->max_speed < USB_SPEED_FULL || + !driver->setup) + return -EINVAL; + + usb3 = gadget_to_renesas_usb3(gadget); + + /* hook up the driver */ + usb3->driver = driver; + + renesas_usb3_init_controller(usb3); + + return 0; +} + +static int renesas_usb3_stop(struct usb_gadget *gadget) +{ + struct renesas_usb3 *usb3 = gadget_to_renesas_usb3(gadget); + unsigned long flags; + + spin_lock_irqsave(&usb3->lock, flags); + usb3->softconnect = false; + usb3->gadget.speed = USB_SPEED_UNKNOWN; + usb3->driver = NULL; + renesas_usb3_stop_controller(usb3); + spin_unlock_irqrestore(&usb3->lock, flags); + + return 0; +} + +static int renesas_usb3_get_frame(struct usb_gadget *_gadget) +{ + return -EOPNOTSUPP; +} + +static int renesas_usb3_pullup(struct usb_gadget *gadget, int is_on) +{ + struct renesas_usb3 *usb3 = gadget_to_renesas_usb3(gadget); + + usb3->softconnect = !!is_on; + + return 0; +} + +static int renesas_usb3_set_selfpowered(struct usb_gadget *gadget, int is_self) +{ + gadget->is_selfpowered = !!is_self; + + return 0; +} + +static const struct usb_gadget_ops renesas_usb3_gadget_ops = { + .get_frame = renesas_usb3_get_frame, + .udc_start = renesas_usb3_start, + .udc_stop = renesas_usb3_stop, + .pullup = renesas_usb3_pullup, + .set_selfpowered = renesas_usb3_set_selfpowered, +}; + +/*------- platform_driver ------------------------------------------------*/ +static int renesas_usb3_remove(struct platform_device *pdev) +{ + struct renesas_usb3 *usb3 = platform_get_drvdata(pdev); + + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + usb_del_gadget_udc(&usb3->gadget); + + __renesas_usb3_ep_free_request(usb3->ep0_req); + + return 0; +} + +static int renesas_usb3_init_ep(struct renesas_usb3 *usb3, struct device *dev, + const struct renesas_usb3_priv *priv) +{ + struct renesas_usb3_ep *usb3_ep; + int i; + + /* calculate num_usb3_eps from renesas_usb3_priv */ + usb3->num_usb3_eps = priv->ramsize_per_ramif * priv->num_ramif * 2 / + priv->ramsize_per_pipe + 1; + + if (usb3->num_usb3_eps > USB3_MAX_NUM_PIPES) + usb3->num_usb3_eps = USB3_MAX_NUM_PIPES; + + usb3->usb3_ep = devm_kzalloc(dev, sizeof(*usb3_ep) * usb3->num_usb3_eps, + GFP_KERNEL); + if (!usb3->usb3_ep) + return -ENOMEM; + + dev_dbg(dev, "%s: num_usb3_eps = %d\n", __func__, usb3->num_usb3_eps); + /* + * This driver prepares pipes as the followings: + * - odd pipes = IN pipe + * - even pipes = OUT pipe (except pipe 0) + */ + usb3_for_each_ep(usb3_ep, usb3, i) { + snprintf(usb3_ep->ep_name, sizeof(usb3_ep->ep_name), "ep%d", i); + usb3_ep->usb3 = usb3; + usb3_ep->num = i; + usb3_ep->ep.name = usb3_ep->ep_name; + usb3_ep->ep.ops = &renesas_usb3_ep_ops; + INIT_LIST_HEAD(&usb3_ep->queue); + INIT_LIST_HEAD(&usb3_ep->ep.ep_list); + if (!i) { + /* for control pipe */ + usb3->gadget.ep0 = &usb3_ep->ep; + usb_ep_set_maxpacket_limit(&usb3_ep->ep, + USB3_EP0_HSFS_MAX_PACKET_SIZE); + usb3_ep->ep.caps.type_control = true; + usb3_ep->ep.caps.dir_in = true; + usb3_ep->ep.caps.dir_out = true; + continue; + } + + /* for bulk or interrupt pipe */ + usb_ep_set_maxpacket_limit(&usb3_ep->ep, ~0); + list_add_tail(&usb3_ep->ep.ep_list, &usb3->gadget.ep_list); + usb3_ep->ep.caps.type_bulk = true; + usb3_ep->ep.caps.type_int = true; + if (i & 1) + usb3_ep->ep.caps.dir_in = true; + else + usb3_ep->ep.caps.dir_out = true; + } + + return 0; +} + +static void renesas_usb3_init_ram(struct renesas_usb3 *usb3, struct device *dev, + const struct renesas_usb3_priv *priv) +{ + struct renesas_usb3_ep *usb3_ep; + int i; + u32 ramif[2], basead[2]; /* index 0 = for IN pipes */ + u32 *cur_ramif, *cur_basead; + u32 val; + + memset(ramif, 0, sizeof(ramif)); + memset(basead, 0, sizeof(basead)); + + /* + * This driver prepares pipes as the followings: + * - all pipes = the same size as "ramsize_per_pipe" + * Please refer to the "Method of Specifying RAM Mapping" + */ + usb3_for_each_ep(usb3_ep, usb3, i) { + if (!i) + continue; /* out of scope if ep num = 0 */ + if (usb3_ep->ep.caps.dir_in) { + cur_ramif = &ramif[0]; + cur_basead = &basead[0]; + } else { + cur_ramif = &ramif[1]; + cur_basead = &basead[1]; + } + + if (*cur_basead > priv->ramsize_per_ramif) + continue; /* out of memory for IN or OUT pipe */ + + /* calculate rammap_val */ + val = PN_RAMMAP_RAMIF(*cur_ramif); + val |= usb3_calc_ramarea(priv->ramsize_per_pipe); + val |= PN_RAMMAP_BASEAD(*cur_basead); + usb3_ep->rammap_val = val; + + dev_dbg(dev, "ep%2d: val = %08x, ramif = %d, base = %x\n", + i, val, *cur_ramif, *cur_basead); + + /* update current ramif */ + if (*cur_ramif + 1 == priv->num_ramif) { + *cur_ramif = 0; + *cur_basead += priv->ramsize_per_pipe; + } else { + (*cur_ramif)++; + } + } +} + +static const struct renesas_usb3_priv renesas_usb3_priv_r8a7795 = { + .ramsize_per_ramif = SZ_16K, + .num_ramif = 2, + .ramsize_per_pipe = SZ_4K, + .workaround_for_vbus = true, +}; + +static const struct of_device_id usb3_of_match[] = { + { + .compatible = "renesas,r8a7795-usb3-peri", + .data = &renesas_usb3_priv_r8a7795, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, usb3_of_match); + +static int renesas_usb3_probe(struct platform_device *pdev) +{ + struct renesas_usb3 *usb3; + struct resource *res; + const struct of_device_id *match; + int irq, ret; + const struct renesas_usb3_priv *priv; + + match = of_match_node(usb3_of_match, pdev->dev.of_node); + if (!match) + return -ENODEV; + priv = match->data; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return -ENODEV; + + usb3 = devm_kzalloc(&pdev->dev, sizeof(*usb3), GFP_KERNEL); + if (!usb3) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + usb3->reg = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(usb3->reg)) + return PTR_ERR(usb3->reg); + + platform_set_drvdata(pdev, usb3); + spin_lock_init(&usb3->lock); + + usb3->gadget.ops = &renesas_usb3_gadget_ops; + usb3->gadget.name = udc_name; + usb3->gadget.max_speed = USB_SPEED_SUPER; + INIT_LIST_HEAD(&usb3->gadget.ep_list); + ret = renesas_usb3_init_ep(usb3, &pdev->dev, priv); + if (ret < 0) + return ret; + renesas_usb3_init_ram(usb3, &pdev->dev, priv); + + ret = devm_request_irq(&pdev->dev, irq, renesas_usb3_irq, 0, + dev_name(&pdev->dev), usb3); + if (ret < 0) + return ret; + + /* for ep0 handling */ + usb3->ep0_req = __renesas_usb3_ep_alloc_request(GFP_KERNEL); + if (!usb3->ep0_req) + return -ENOMEM; + + ret = usb_add_gadget_udc(&pdev->dev, &usb3->gadget); + if (ret < 0) + goto err_add_udc; + + usb3->workaround_for_vbus = priv->workaround_for_vbus; + + pm_runtime_enable(&pdev->dev); + pm_runtime_get_sync(&pdev->dev); + + dev_info(&pdev->dev, "probed\n"); + + return 0; + +err_add_udc: + __renesas_usb3_ep_free_request(usb3->ep0_req); + + return ret; +} + +static struct platform_driver renesas_usb3_driver = { + .probe = renesas_usb3_probe, + .remove = renesas_usb3_remove, + .driver = { + .name = (char *)udc_name, + .of_match_table = of_match_ptr(usb3_of_match), + }, +}; +module_platform_driver(renesas_usb3_driver); + +MODULE_DESCRIPTION("Renesas USB3.0 Peripheral driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Yoshihiro Shimoda "); +MODULE_ALIAS("platform:renesas_usb3"); diff --git a/drivers/usb/gadget/udc/s3c-hsudc.c b/drivers/usb/gadget/udc/s3c-hsudc.c index e9def42ce50d..82a9e2a3bedc 100644 --- a/drivers/usb/gadget/udc/s3c-hsudc.c +++ b/drivers/usb/gadget/udc/s3c-hsudc.c @@ -569,7 +569,7 @@ static int s3c_hsudc_handle_reqfeat(struct s3c_hsudc *hsudc, hsep = &hsudc->ep[ep_num]; switch (le16_to_cpu(ctrl->wValue)) { case USB_ENDPOINT_HALT: - if (set || (!set && !hsep->wedge)) + if (set || !hsep->wedge) s3c_hsudc_set_halt(&hsep->ep, set); return 0; } diff --git a/drivers/usb/gadget/udc/udc-core.c b/drivers/usb/gadget/udc/udc-core.c index f660afba715d..fd73a3ea07c2 100644 --- a/drivers/usb/gadget/udc/udc-core.c +++ b/drivers/usb/gadget/udc/udc-core.c @@ -51,8 +51,12 @@ struct usb_udc { static struct class *udc_class; static LIST_HEAD(udc_list); +static LIST_HEAD(gadget_driver_pending_list); static DEFINE_MUTEX(udc_lock); +static int udc_bind_to_driver(struct usb_udc *udc, + struct usb_gadget_driver *driver); + /* ------------------------------------------------------------------------- */ #ifdef CONFIG_HAS_DMA @@ -356,6 +360,7 @@ int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget, void (*release)(struct device *dev)) { struct usb_udc *udc; + struct usb_gadget_driver *driver; int ret = -ENOMEM; udc = kzalloc(sizeof(*udc), GFP_KERNEL); @@ -403,6 +408,18 @@ int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget, usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED); udc->vbus = true; + /* pick up one of pending gadget drivers */ + list_for_each_entry(driver, &gadget_driver_pending_list, pending) { + if (!driver->udc_name || strcmp(driver->udc_name, + dev_name(&udc->dev)) == 0) { + ret = udc_bind_to_driver(udc, driver); + if (ret) + goto err4; + list_del(&driver->pending); + break; + } + } + mutex_unlock(&udc_lock); return 0; @@ -473,10 +490,14 @@ void usb_del_gadget_udc(struct usb_gadget *gadget) mutex_lock(&udc_lock); list_del(&udc->list); - mutex_unlock(&udc_lock); - if (udc->driver) + if (udc->driver) { + struct usb_gadget_driver *driver = udc->driver; + usb_gadget_remove_driver(udc); + list_add(&driver->pending, &gadget_driver_pending_list); + } + mutex_unlock(&udc_lock); kobject_uevent(&udc->dev.kobj, KOBJ_REMOVE); flush_work(&gadget->work); @@ -520,50 +541,36 @@ err1: return ret; } -int usb_udc_attach_driver(const char *name, struct usb_gadget_driver *driver) -{ - struct usb_udc *udc = NULL; - int ret = -ENODEV; - - mutex_lock(&udc_lock); - list_for_each_entry(udc, &udc_list, list) { - ret = strcmp(name, dev_name(&udc->dev)); - if (!ret) - break; - } - if (ret) { - ret = -ENODEV; - goto out; - } - if (udc->driver) { - ret = -EBUSY; - goto out; - } - ret = udc_bind_to_driver(udc, driver); -out: - mutex_unlock(&udc_lock); - return ret; -} -EXPORT_SYMBOL_GPL(usb_udc_attach_driver); - int usb_gadget_probe_driver(struct usb_gadget_driver *driver) { struct usb_udc *udc = NULL; - int ret; + int ret = -ENODEV; if (!driver || !driver->bind || !driver->setup) return -EINVAL; mutex_lock(&udc_lock); - list_for_each_entry(udc, &udc_list, list) { - /* For now we take the first one */ - if (!udc->driver) + if (driver->udc_name) { + list_for_each_entry(udc, &udc_list, list) { + ret = strcmp(driver->udc_name, dev_name(&udc->dev)); + if (!ret) + break; + } + if (!ret && !udc->driver) goto found; + } else { + list_for_each_entry(udc, &udc_list, list) { + /* For now we take the first one */ + if (!udc->driver) + goto found; + } } - pr_debug("couldn't find an available UDC\n"); + list_add_tail(&driver->pending, &gadget_driver_pending_list); + pr_info("udc-core: couldn't find an available UDC - added [%s] to list of pending drivers\n", + driver->function); mutex_unlock(&udc_lock); - return -ENODEV; + return 0; found: ret = udc_bind_to_driver(udc, driver); mutex_unlock(&udc_lock); @@ -589,6 +596,10 @@ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) break; } + if (ret) { + list_del(&driver->pending); + ret = 0; + } mutex_unlock(&udc_lock); return ret; } diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 3bb08870148f..daa563ff1fa0 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -41,6 +41,15 @@ config USB_XHCI_PLATFORM If unsure, say N. +config USB_XHCI_MTK + tristate "xHCI support for Mediatek MT65xx" + select MFD_SYSCON + depends on ARCH_MEDIATEK || COMPILE_TEST + ---help--- + Say 'Y' to enable the support for the xHCI host controller + found in Mediatek MT65xx SoCs. + If unsure, say N. + config USB_XHCI_MVEBU tristate "xHCI support for Marvell Armada 375/38x" select USB_XHCI_PLATFORM diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index e7558abc994d..65a06b4382bf 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -13,6 +13,9 @@ fhci-$(CONFIG_FHCI_DEBUG) += fhci-dbg.o xhci-hcd-y := xhci.o xhci-mem.o xhci-hcd-y += xhci-ring.o xhci-hub.o xhci-dbg.o xhci-hcd-y += xhci-trace.o +ifneq ($(CONFIG_USB_XHCI_MTK), ) + xhci-hcd-y += xhci-mtk-sch.o +endif xhci-plat-hcd-y := xhci-plat.o ifneq ($(CONFIG_USB_XHCI_MVEBU), ) @@ -64,6 +67,7 @@ obj-$(CONFIG_USB_FHCI_HCD) += fhci.o obj-$(CONFIG_USB_XHCI_HCD) += xhci-hcd.o obj-$(CONFIG_USB_XHCI_PCI) += xhci-pci.o obj-$(CONFIG_USB_XHCI_PLATFORM) += xhci-plat-hcd.o +obj-$(CONFIG_USB_XHCI_MTK) += xhci-mtk.o obj-$(CONFIG_USB_SL811_HCD) += sl811-hcd.o obj-$(CONFIG_USB_SL811_CS) += sl811_cs.o obj-$(CONFIG_USB_U132_HCD) += u132-hcd.o diff --git a/drivers/usb/host/bcma-hcd.c b/drivers/usb/host/bcma-hcd.c index 5398e3d42822..291aaa2baed8 100644 --- a/drivers/usb/host/bcma-hcd.c +++ b/drivers/usb/host/bcma-hcd.c @@ -21,6 +21,7 @@ */ #include #include +#include #include #include #include @@ -36,6 +37,7 @@ MODULE_LICENSE("GPL"); struct bcma_hcd_device { struct platform_device *ehci_dev; struct platform_device *ohci_dev; + struct gpio_desc *gpio_desc; }; /* Wait for bitmask in a register to get set or cleared. @@ -228,19 +230,12 @@ static void bcma_hcd_init_chip_arm(struct bcma_device *dev) static void bcma_hci_platform_power_gpio(struct bcma_device *dev, bool val) { - int gpio; + struct bcma_hcd_device *usb_dev = bcma_get_drvdata(dev); - gpio = of_get_named_gpio(dev->dev.of_node, "vcc-gpio", 0); - if (!gpio_is_valid(gpio)) + if (IS_ERR_OR_NULL(usb_dev->gpio_desc)) return; - if (val) { - gpio_request(gpio, "bcma-hcd-gpio"); - gpio_set_value(gpio, 1); - } else { - gpio_set_value(gpio, 0); - gpio_free(gpio); - } + gpiod_set_value(usb_dev->gpio_desc, val); } static const struct usb_ehci_pdata ehci_pdata = { @@ -314,7 +309,11 @@ static int bcma_hcd_probe(struct bcma_device *dev) if (!usb_dev) return -ENOMEM; - bcma_hci_platform_power_gpio(dev, true); + if (dev->dev.of_node) + usb_dev->gpio_desc = devm_get_gpiod_from_child(&dev->dev, "vcc", + &dev->dev.of_node->fwnode); + if (!IS_ERR_OR_NULL(usb_dev->gpio_desc)) + gpiod_direction_output(usb_dev->gpio_desc, 1); switch (dev->id.id) { case BCMA_CORE_NS_USB20: diff --git a/drivers/usb/host/ehci-dbg.c b/drivers/usb/host/ehci-dbg.c index b26b96e25a13..b7d623f1523c 100644 --- a/drivers/usb/host/ehci-dbg.c +++ b/drivers/usb/host/ehci-dbg.c @@ -436,7 +436,8 @@ static void qh_lines ( scratch = hc32_to_cpup(ehci, &hw->hw_info1); hw_curr = (mark == '*') ? hc32_to_cpup(ehci, &hw->hw_current) : 0; temp = scnprintf (next, size, - "qh/%p dev%d %cs ep%d %08x %08x (%08x%c %s nak%d)", + "qh/%p dev%d %cs ep%d %08x %08x (%08x%c %s nak%d)" + " [cur %08x next %08x buf[0] %08x]", qh, scratch & 0x007f, speed_char (scratch), (scratch >> 8) & 0x000f, @@ -444,7 +445,10 @@ static void qh_lines ( hc32_to_cpup(ehci, &hw->hw_token), mark, (cpu_to_hc32(ehci, QTD_TOGGLE) & hw->hw_token) ? "data1" : "data0", - (hc32_to_cpup(ehci, &hw->hw_alt_next) >> 1) & 0x0f); + (hc32_to_cpup(ehci, &hw->hw_alt_next) >> 1) & 0x0f, + hc32_to_cpup(ehci, &hw->hw_current), + hc32_to_cpup(ehci, &hw->hw_qtd_next), + hc32_to_cpup(ehci, &hw->hw_buf[0])); size -= temp; next += temp; @@ -464,7 +468,8 @@ static void qh_lines ( mark = '/'; } temp = snprintf (next, size, - "\n\t%p%c%s len=%d %08x urb %p", + "\n\t%p%c%s len=%d %08x urb %p" + " [td %08x buf[0] %08x]", td, mark, ({ char *tmp; switch ((scratch>>8)&0x03) { case 0: tmp = "out"; break; @@ -474,7 +479,9 @@ static void qh_lines ( } tmp;}), (scratch >> 16) & 0x7fff, scratch, - td->urb); + td->urb, + (u32) td->qtd_dma, + hc32_to_cpup(ehci, &td->hw_buf[0])); if (size < temp) temp = size; size -= temp; diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 48c92bf78bd0..14178bbf0694 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -98,7 +98,7 @@ module_param (park, uint, S_IRUGO); MODULE_PARM_DESC (park, "park setting; 1-3 back-to-back async packets"); /* for flakey hardware, ignore overcurrent indicators */ -static bool ignore_oc = 0; +static bool ignore_oc; module_param (ignore_oc, bool, S_IRUGO); MODULE_PARM_DESC (ignore_oc, "ignore bogus hardware overcurrent indications"); diff --git a/drivers/usb/host/ehci-mem.c b/drivers/usb/host/ehci-mem.c index b6205fac3567..4de43011df23 100644 --- a/drivers/usb/host/ehci-mem.c +++ b/drivers/usb/host/ehci-mem.c @@ -128,21 +128,13 @@ static void ehci_mem_cleanup (struct ehci_hcd *ehci) ehci->dummy = NULL; /* DMA consistent memory and pools */ - if (ehci->qtd_pool) - dma_pool_destroy (ehci->qtd_pool); + dma_pool_destroy(ehci->qtd_pool); ehci->qtd_pool = NULL; - - if (ehci->qh_pool) { - dma_pool_destroy (ehci->qh_pool); - ehci->qh_pool = NULL; - } - - if (ehci->itd_pool) - dma_pool_destroy (ehci->itd_pool); + dma_pool_destroy(ehci->qh_pool); + ehci->qh_pool = NULL; + dma_pool_destroy(ehci->itd_pool); ehci->itd_pool = NULL; - - if (ehci->sitd_pool) - dma_pool_destroy (ehci->sitd_pool); + dma_pool_destroy(ehci->sitd_pool); ehci->sitd_pool = NULL; if (ehci->periodic) diff --git a/drivers/usb/host/ehci-msm.c b/drivers/usb/host/ehci-msm.c index c4f84c81de01..c23e2858c815 100644 --- a/drivers/usb/host/ehci-msm.c +++ b/drivers/usb/host/ehci-msm.c @@ -57,8 +57,8 @@ static int ehci_msm_reset(struct usb_hcd *hcd) /* bursts of unspecified length. */ writel(0, USB_AHBBURST); - /* Use the AHB transactor */ - writel(0, USB_AHBMODE); + /* Use the AHB transactor, allow posted data writes */ + writel(0x8, USB_AHBMODE); /* Disable streaming mode and select host mode */ writel(0x13, USB_USBMODE); diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index 54f5332f814d..aad0777240d3 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -132,10 +132,14 @@ qh_refresh (struct ehci_hcd *ehci, struct ehci_qh *qh) * qtd is updated in qh_completions(). Update the QH * overlay here. */ - if (qh->hw->hw_token & ACTIVE_BIT(ehci)) + if (qh->hw->hw_token & ACTIVE_BIT(ehci)) { qh->hw->hw_qtd_next = qtd->hw_next; - else + if (qh->should_be_inactive) + ehci_warn(ehci, "qh %p should be inactive!\n", qh); + } else { qh_update(ehci, qh, qtd); + } + qh->should_be_inactive = 0; } /*-------------------------------------------------------------------------*/ @@ -438,6 +442,7 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh) (hw->hw_token & ACTIVE_BIT(ehci))) { token = hc32_to_cpu(ehci, hw->hw_token); hw->hw_token &= ~ACTIVE_BIT(ehci); + qh->should_be_inactive = 1; /* An unlink may leave an incomplete * async transaction in the TT buffer. diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 46f62e41bcde..ec61aedb0067 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -439,6 +439,7 @@ struct ehci_qh { unsigned dequeue_during_giveback:1; unsigned exception:1; /* got a fault, or an unlink was requested */ + unsigned should_be_inactive:1; }; /*-------------------------------------------------------------------------*/ diff --git a/drivers/usb/host/fhci-tds.c b/drivers/usb/host/fhci-tds.c index 1498061f0aea..f82ad5df1b0d 100644 --- a/drivers/usb/host/fhci-tds.c +++ b/drivers/usb/host/fhci-tds.c @@ -85,7 +85,7 @@ static struct usb_td __iomem *next_bd(struct usb_td __iomem *base, void fhci_push_dummy_bd(struct endpoint *ep) { - if (ep->already_pushed_dummy_bd == false) { + if (!ep->already_pushed_dummy_bd) { u16 td_status = in_be16(&ep->empty_td->status); out_be32(&ep->empty_td->buf_ptr, DUMMY_BD_BUFFER); diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 760cb57e954e..04dcedfdebf8 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -99,13 +99,13 @@ static void io_watchdog_func(unsigned long _ohci); /* Some boards misreport power switching/overcurrent */ -static bool distrust_firmware = 1; +static bool distrust_firmware = true; module_param (distrust_firmware, bool, 0); MODULE_PARM_DESC (distrust_firmware, "true to distrust firmware power/overcurrent setup"); /* Some boards leave IR set wrongly, since they fail BIOS/SMM handshakes */ -static bool no_handshake = 0; +static bool no_handshake; module_param (no_handshake, bool, 0); MODULE_PARM_DESC (no_handshake, "true (not default) disables BIOS handshake"); diff --git a/drivers/usb/host/ohci-pxa27x.c b/drivers/usb/host/ohci-pxa27x.c index ba1bec7db026..e8c006e7a960 100644 --- a/drivers/usb/host/ohci-pxa27x.c +++ b/drivers/usb/host/ohci-pxa27x.c @@ -365,19 +365,19 @@ static int ohci_pxa_of_init(struct platform_device *pdev) if (!pdata) return -ENOMEM; - if (of_get_property(np, "marvell,enable-port1", NULL)) + if (of_property_read_bool(np, "marvell,enable-port1")) pdata->flags |= ENABLE_PORT1; - if (of_get_property(np, "marvell,enable-port2", NULL)) + if (of_property_read_bool(np, "marvell,enable-port2")) pdata->flags |= ENABLE_PORT2; - if (of_get_property(np, "marvell,enable-port3", NULL)) + if (of_property_read_bool(np, "marvell,enable-port3")) pdata->flags |= ENABLE_PORT3; - if (of_get_property(np, "marvell,port-sense-low", NULL)) + if (of_property_read_bool(np, "marvell,port-sense-low")) pdata->flags |= POWER_SENSE_LOW; - if (of_get_property(np, "marvell,power-control-low", NULL)) + if (of_property_read_bool(np, "marvell,power-control-low")) pdata->flags |= POWER_CONTROL_LOW; - if (of_get_property(np, "marvell,no-oc-protection", NULL)) + if (of_property_read_bool(np, "marvell,no-oc-protection")) pdata->flags |= NO_OC_PROTECTION; - if (of_get_property(np, "marvell,oc-mode-perport", NULL)) + if (of_property_read_bool(np, "marvell,oc-mode-perport")) pdata->flags |= OC_MODE_PERPORT; if (!of_property_read_u32(np, "marvell,power-on-delay", &tmp)) pdata->power_on_delay = tmp; diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c index 1f139d82cee0..bc74aca8a54c 100644 --- a/drivers/usb/host/oxu210hp-hcd.c +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -394,8 +394,7 @@ static void ehci_quiesce(struct oxu_hcd *oxu) u32 temp; #ifdef DEBUG - if (!HC_IS_RUNNING(oxu_to_hcd(oxu)->state)) - BUG(); + BUG_ON(!HC_IS_RUNNING(oxu_to_hcd(oxu)->state)); #endif /* wait for any schedule enables/disables to take effect */ @@ -1709,9 +1708,8 @@ static void start_unlink_async(struct oxu_hcd *oxu, struct ehci_qh *qh) #ifdef DEBUG assert_spin_locked(&oxu->lock); - if (oxu->reclaim || (qh->qh_state != QH_STATE_LINKED - && qh->qh_state != QH_STATE_UNLINK_WAIT)) - BUG(); + BUG_ON(oxu->reclaim || (qh->qh_state != QH_STATE_LINKED + && qh->qh_state != QH_STATE_UNLINK_WAIT)); #endif /* stop async schedule right now? */ diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c index f9400564cb72..26cb8c861e6e 100644 --- a/drivers/usb/host/pci-quirks.c +++ b/drivers/usb/host/pci-quirks.c @@ -984,24 +984,17 @@ static void quirk_usb_handoff_xhci(struct pci_dev *pdev) * Find the Legacy Support Capability register - * this is optional for xHCI host controllers. */ - ext_cap_offset = xhci_find_next_cap_offset(base, XHCI_HCC_PARAMS_OFFSET); - do { - if ((ext_cap_offset + sizeof(val)) > len) { - /* We're reading garbage from the controller */ - dev_warn(&pdev->dev, - "xHCI controller failing to respond"); - return; - } + ext_cap_offset = xhci_find_next_ext_cap(base, 0, XHCI_EXT_CAPS_LEGACY); - if (!ext_cap_offset) - /* We've reached the end of the extended capabilities */ - goto hc_init; + if (!ext_cap_offset) + goto hc_init; - val = readl(base + ext_cap_offset); - if (XHCI_EXT_CAPS_ID(val) == XHCI_EXT_CAPS_LEGACY) - break; - ext_cap_offset = xhci_find_next_cap_offset(base, ext_cap_offset); - } while (1); + if ((ext_cap_offset + sizeof(val)) > len) { + /* We're reading garbage from the controller */ + dev_warn(&pdev->dev, "xHCI controller failing to respond"); + return; + } + val = readl(base + ext_cap_offset); /* If the BIOS owns the HC, signal that the OS wants it, and wait */ if (val & XHCI_HC_BIOS_OWNED) { diff --git a/drivers/usb/host/u132-hcd.c b/drivers/usb/host/u132-hcd.c index 692ccc69345e..05c85c7baf84 100644 --- a/drivers/usb/host/u132-hcd.c +++ b/drivers/usb/host/u132-hcd.c @@ -73,7 +73,7 @@ MODULE_LICENSE("GPL"); #define INT_MODULE_PARM(n, v) static int n = v;module_param(n, int, 0444) INT_MODULE_PARM(testing, 0); /* Some boards misreport power switching/overcurrent*/ -static bool distrust_firmware = 1; +static bool distrust_firmware = true; module_param(distrust_firmware, bool, 0); MODULE_PARM_DESC(distrust_firmware, "true to distrust firmware power/overcurren" "t setup"); diff --git a/drivers/usb/host/uhci-q.c b/drivers/usb/host/uhci-q.c index da6f56d996ce..c17ea1589b83 100644 --- a/drivers/usb/host/uhci-q.c +++ b/drivers/usb/host/uhci-q.c @@ -248,11 +248,10 @@ static struct uhci_qh *uhci_alloc_qh(struct uhci_hcd *uhci, dma_addr_t dma_handle; struct uhci_qh *qh; - qh = dma_pool_alloc(uhci->qh_pool, GFP_ATOMIC, &dma_handle); + qh = dma_pool_zalloc(uhci->qh_pool, GFP_ATOMIC, &dma_handle); if (!qh) return NULL; - memset(qh, 0, sizeof(*qh)); qh->dma_handle = dma_handle; qh->element = UHCI_PTR_TERM(uhci); diff --git a/drivers/usb/host/whci/qset.c b/drivers/usb/host/whci/qset.c index 9f1c0538b211..1a8e960d073b 100644 --- a/drivers/usb/host/whci/qset.c +++ b/drivers/usb/host/whci/qset.c @@ -30,10 +30,9 @@ struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags) struct whc_qset *qset; dma_addr_t dma; - qset = dma_pool_alloc(whc->qset_pool, mem_flags, &dma); + qset = dma_pool_zalloc(whc->qset_pool, mem_flags, &dma); if (qset == NULL) return NULL; - memset(qset, 0, sizeof(struct whc_qset)); qset->qset_dma = dma; qset->whc = whc; @@ -400,7 +399,7 @@ static void urb_dequeue_work(struct work_struct *work) struct whc *whc = qset->whc; unsigned long flags; - if (wurb->is_async == true) + if (wurb->is_async) asl_update(whc, WUSBCMD_ASYNC_UPDATED | WUSBCMD_ASYNC_SYNCED_DB | WUSBCMD_ASYNC_QSET_RM); diff --git a/drivers/usb/host/xhci-ext-caps.h b/drivers/usb/host/xhci-ext-caps.h index 9fe3225e6c61..04ce6b156b35 100644 --- a/drivers/usb/host/xhci-ext-caps.h +++ b/drivers/usb/host/xhci-ext-caps.h @@ -90,67 +90,40 @@ #include -/** - * Return the next extended capability pointer register. - * - * @base PCI register base address. - * - * @ext_offset Offset of the 32-bit register that contains the extended - * capabilites pointer. If searching for the first extended capability, pass - * in XHCI_HCC_PARAMS_OFFSET. If searching for the next extended capability, - * pass in the offset of the current extended capability register. - * - * Returns 0 if there is no next extended capability register or returns the register offset - * from the PCI registers base address. - */ -static inline int xhci_find_next_cap_offset(void __iomem *base, int ext_offset) -{ - u32 next; - - next = readl(base + ext_offset); - - if (ext_offset == XHCI_HCC_PARAMS_OFFSET) { - /* Find the first extended capability */ - next = XHCI_HCC_EXT_CAPS(next); - ext_offset = 0; - } else { - /* Find the next extended capability */ - next = XHCI_EXT_CAPS_NEXT(next); - } - - if (!next) - return 0; - /* - * Address calculation from offset of extended capabilities - * (or HCCPARAMS) register - see section 5.3.6 and section 7. - */ - return ext_offset + (next << 2); -} - /** * Find the offset of the extended capabilities with capability ID id. * - * @base PCI MMIO registers base address. - * @ext_offset Offset from base of the first extended capability to look at, - * or the address of HCCPARAMS. - * @id Extended capability ID to search for. + * @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. * - * This uses an arbitrary limit of XHCI_MAX_EXT_CAPS extended capabilities - * to make sure that the list doesn't contain a loop. + * Returns the offset of the next matching extended capability structure. + * Some capabilities can occur several times, e.g., the XHCI_EXT_CAPS_PROTOCOL, + * and this provides a way to find them all. */ -static inline int xhci_find_ext_cap_by_id(void __iomem *base, int ext_offset, int id) + +static inline int xhci_find_next_ext_cap(void __iomem *base, u32 start, int id) { u32 val; - int limit = XHCI_MAX_EXT_CAPS; + u32 next; + u32 offset; + + offset = start; + if (!start || start == XHCI_HCC_PARAMS_OFFSET) { + val = readl(base + XHCI_HCC_PARAMS_OFFSET); + offset = XHCI_HCC_EXT_CAPS(val) << 2; + if (!offset) + return 0; + }; + do { + val = readl(base + offset); + if (XHCI_EXT_CAPS_ID(val) == id && offset != start) + return offset; + + next = XHCI_EXT_CAPS_NEXT(val); + offset += next << 2; + } while (next); - while (ext_offset && limit > 0) { - val = readl(base + ext_offset); - if (XHCI_EXT_CAPS_ID(val) == id) - break; - ext_offset = xhci_find_next_cap_offset(base, ext_offset); - limit--; - } - if (limit > 0) - return ext_offset; return 0; } diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index f980c239eded..b30b4ce294d3 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -855,7 +855,7 @@ static u32 xhci_get_port_status(struct usb_hcd *hcd, xhci_hub_report_usb2_link_state(&status, raw_port_status); } if (bus_state->port_c_suspend & (1 << wIndex)) - status |= 1 << USB_PORT_FEAT_C_SUSPEND; + status |= USB_PORT_STAT_C_SUSPEND << 16; return status; } diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index c48cbe731356..5cd080e0a685 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -47,13 +47,12 @@ static struct xhci_segment *xhci_segment_alloc(struct xhci_hcd *xhci, if (!seg) return NULL; - seg->trbs = dma_pool_alloc(xhci->segment_pool, flags, &dma); + seg->trbs = dma_pool_zalloc(xhci->segment_pool, flags, &dma); if (!seg->trbs) { kfree(seg); return NULL; } - memset(seg->trbs, 0, TRB_SEGMENT_SIZE); /* If the cycle state is 0, set the cycle bit to 1 for all the TRBs */ if (cycle_state == 0) { for (i = 0; i < TRBS_PER_SEGMENT; i++) @@ -517,12 +516,11 @@ static struct xhci_container_ctx *xhci_alloc_container_ctx(struct xhci_hcd *xhci if (type == XHCI_CTX_TYPE_INPUT) ctx->size += CTX_SIZE(xhci->hcc_params); - ctx->bytes = dma_pool_alloc(xhci->device_pool, flags, &ctx->dma); + ctx->bytes = dma_pool_zalloc(xhci->device_pool, flags, &ctx->dma); if (!ctx->bytes) { kfree(ctx); return NULL; } - memset(ctx->bytes, 0, ctx->size); return ctx; } @@ -1245,7 +1243,7 @@ static unsigned int xhci_microframes_to_exponent(struct usb_device *udev, interval = fls(desc_interval) - 1; interval = clamp_val(interval, min_exponent, max_exponent); if ((1 << interval) != desc_interval) - dev_warn(&udev->dev, + dev_dbg(&udev->dev, "ep %#x - rounding interval to %d microframes, ep desc says %d microframes\n", ep->desc.bEndpointAddress, 1 << interval, @@ -2064,17 +2062,19 @@ static void xhci_set_hc_event_deq(struct xhci_hcd *xhci) } static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, - __le32 __iomem *addr, u8 major_revision, int max_caps) + __le32 __iomem *addr, int max_caps) { u32 temp, port_offset, port_count; int i; + u8 major_revision; struct xhci_hub *rhub; temp = readl(addr); + major_revision = XHCI_EXT_PORT_MAJOR(temp); - if (XHCI_EXT_PORT_MAJOR(temp) == 0x03) { + if (major_revision == 0x03) { rhub = &xhci->usb3_rhub; - } else if (XHCI_EXT_PORT_MAJOR(temp) <= 0x02) { + } else if (major_revision <= 0x02) { rhub = &xhci->usb2_rhub; } else { xhci_warn(xhci, "Ignoring unknown port speed, " @@ -2190,19 +2190,12 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, */ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags) { - __le32 __iomem *addr, *tmp_addr; - u32 offset, tmp_offset; + void __iomem *base; + u32 offset; unsigned int num_ports; int i, j, port_index; int cap_count = 0; - - addr = &xhci->cap_regs->hcc_params; - offset = XHCI_HCC_EXT_CAPS(readl(addr)); - if (offset == 0) { - xhci_err(xhci, "No Extended Capability registers, " - "unable to set up roothub.\n"); - return -ENODEV; - } + u32 cap_start; num_ports = HCS_MAX_PORTS(xhci->hcs_params1); xhci->port_array = kzalloc(sizeof(*xhci->port_array)*num_ports, flags); @@ -2220,48 +2213,34 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags) for (j = 0; j < XHCI_MAX_INTERVAL; j++) INIT_LIST_HEAD(&bw_table->interval_bw[j].endpoints); } + base = &xhci->cap_regs->hc_capbase; - /* - * For whatever reason, the first capability offset is from the - * capability register base, not from the HCCPARAMS register. - * See section 5.3.6 for offset calculation. - */ - addr = &xhci->cap_regs->hc_capbase + offset; - - tmp_addr = addr; - tmp_offset = offset; + cap_start = xhci_find_next_ext_cap(base, 0, XHCI_EXT_CAPS_PROTOCOL); + if (!cap_start) { + xhci_err(xhci, "No Extended Capability registers, unable to set up roothub\n"); + return -ENODEV; + } + offset = cap_start; /* count extended protocol capability entries for later caching */ - do { - u32 cap_id; - cap_id = readl(tmp_addr); - if (XHCI_EXT_CAPS_ID(cap_id) == XHCI_EXT_CAPS_PROTOCOL) - cap_count++; - tmp_offset = XHCI_EXT_CAPS_NEXT(cap_id); - tmp_addr += tmp_offset; - } while (tmp_offset); + while (offset) { + cap_count++; + offset = xhci_find_next_ext_cap(base, offset, + XHCI_EXT_CAPS_PROTOCOL); + } xhci->ext_caps = kzalloc(sizeof(*xhci->ext_caps) * cap_count, flags); if (!xhci->ext_caps) return -ENOMEM; - while (1) { - u32 cap_id; + offset = cap_start; - cap_id = readl(addr); - if (XHCI_EXT_CAPS_ID(cap_id) == XHCI_EXT_CAPS_PROTOCOL) - xhci_add_in_port(xhci, num_ports, addr, - (u8) XHCI_EXT_PORT_MAJOR(cap_id), - cap_count); - offset = XHCI_EXT_CAPS_NEXT(cap_id); - if (!offset || (xhci->num_usb2_ports + xhci->num_usb3_ports) - == num_ports) + while (offset) { + xhci_add_in_port(xhci, num_ports, base + offset, cap_count); + if (xhci->num_usb2_ports + xhci->num_usb3_ports == num_ports) break; - /* - * Once you're into the Extended Capabilities, the offset is - * always relative to the register holding the offset. - */ - addr += offset; + offset = xhci_find_next_ext_cap(base, offset, + XHCI_EXT_CAPS_PROTOCOL); } if (xhci->num_usb2_ports == 0 && xhci->num_usb3_ports == 0) { diff --git a/drivers/usb/host/xhci-mtk-sch.c b/drivers/usb/host/xhci-mtk-sch.c new file mode 100644 index 000000000000..c30de7c39f44 --- /dev/null +++ b/drivers/usb/host/xhci-mtk-sch.c @@ -0,0 +1,415 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * Author: + * Zhigang.Wei + * Chunfeng.Yun + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 +#include +#include + +#include "xhci.h" +#include "xhci-mtk.h" + +#define SS_BW_BOUNDARY 51000 +/* table 5-5. High-speed Isoc Transaction Limits in usb_20 spec */ +#define HS_BW_BOUNDARY 6144 +/* usb2 spec section11.18.1: at most 188 FS bytes per microframe */ +#define FS_PAYLOAD_MAX 188 + +/* mtk scheduler bitmasks */ +#define EP_BPKTS(p) ((p) & 0x3f) +#define EP_BCSCOUNT(p) (((p) & 0x7) << 8) +#define EP_BBM(p) ((p) << 11) +#define EP_BOFFSET(p) ((p) & 0x3fff) +#define EP_BREPEAT(p) (((p) & 0x7fff) << 16) + +static int is_fs_or_ls(enum usb_device_speed speed) +{ + return speed == USB_SPEED_FULL || speed == USB_SPEED_LOW; +} + +/* +* get the index of bandwidth domains array which @ep belongs to. +* +* the bandwidth domain array is saved to @sch_array of struct xhci_hcd_mtk, +* each HS root port is treated as a single bandwidth domain, +* but each SS root port is treated as two bandwidth domains, one for IN eps, +* one for OUT eps. +* @real_port value is defined as follow according to xHCI spec: +* 1 for SSport0, ..., N+1 for SSportN, N+2 for HSport0, N+3 for HSport1, etc +* so the bandwidth domain array is organized as follow for simplification: +* SSport0-OUT, SSport0-IN, ..., SSportX-OUT, SSportX-IN, HSport0, ..., HSportY +*/ +static int get_bw_index(struct xhci_hcd *xhci, struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + struct xhci_virt_device *virt_dev; + int bw_index; + + virt_dev = xhci->devs[udev->slot_id]; + + if (udev->speed == USB_SPEED_SUPER) { + if (usb_endpoint_dir_out(&ep->desc)) + bw_index = (virt_dev->real_port - 1) * 2; + else + bw_index = (virt_dev->real_port - 1) * 2 + 1; + } else { + /* add one more for each SS port */ + bw_index = virt_dev->real_port + xhci->num_usb3_ports - 1; + } + + return bw_index; +} + +static void setup_sch_info(struct usb_device *udev, + struct xhci_ep_ctx *ep_ctx, struct mu3h_sch_ep_info *sch_ep) +{ + u32 ep_type; + u32 ep_interval; + u32 max_packet_size; + u32 max_burst; + u32 mult; + u32 esit_pkts; + + ep_type = CTX_TO_EP_TYPE(le32_to_cpu(ep_ctx->ep_info2)); + ep_interval = CTX_TO_EP_INTERVAL(le32_to_cpu(ep_ctx->ep_info)); + max_packet_size = MAX_PACKET_DECODED(le32_to_cpu(ep_ctx->ep_info2)); + max_burst = CTX_TO_MAX_BURST(le32_to_cpu(ep_ctx->ep_info2)); + mult = CTX_TO_EP_MULT(le32_to_cpu(ep_ctx->ep_info)); + + sch_ep->esit = 1 << ep_interval; + sch_ep->offset = 0; + sch_ep->burst_mode = 0; + + if (udev->speed == USB_SPEED_HIGH) { + sch_ep->cs_count = 0; + + /* + * usb_20 spec section5.9 + * a single microframe is enough for HS synchromous endpoints + * in a interval + */ + sch_ep->num_budget_microframes = 1; + sch_ep->repeat = 0; + + /* + * xHCI spec section6.2.3.4 + * @max_burst is the number of additional transactions + * opportunities per microframe + */ + sch_ep->pkts = max_burst + 1; + sch_ep->bw_cost_per_microframe = max_packet_size * sch_ep->pkts; + } else if (udev->speed == USB_SPEED_SUPER) { + /* usb3_r1 spec section4.4.7 & 4.4.8 */ + sch_ep->cs_count = 0; + esit_pkts = (mult + 1) * (max_burst + 1); + if (ep_type == INT_IN_EP || ep_type == INT_OUT_EP) { + sch_ep->pkts = esit_pkts; + sch_ep->num_budget_microframes = 1; + sch_ep->repeat = 0; + } + + if (ep_type == ISOC_IN_EP || ep_type == ISOC_OUT_EP) { + if (esit_pkts <= sch_ep->esit) + sch_ep->pkts = 1; + else + sch_ep->pkts = roundup_pow_of_two(esit_pkts) + / sch_ep->esit; + + sch_ep->num_budget_microframes = + DIV_ROUND_UP(esit_pkts, sch_ep->pkts); + + if (sch_ep->num_budget_microframes > 1) + sch_ep->repeat = 1; + else + sch_ep->repeat = 0; + } + sch_ep->bw_cost_per_microframe = max_packet_size * sch_ep->pkts; + } else if (is_fs_or_ls(udev->speed)) { + + /* + * usb_20 spec section11.18.4 + * assume worst cases + */ + sch_ep->repeat = 0; + sch_ep->pkts = 1; /* at most one packet for each microframe */ + if (ep_type == INT_IN_EP || ep_type == INT_OUT_EP) { + sch_ep->cs_count = 3; /* at most need 3 CS*/ + /* one for SS and one for budgeted transaction */ + sch_ep->num_budget_microframes = sch_ep->cs_count + 2; + sch_ep->bw_cost_per_microframe = max_packet_size; + } + if (ep_type == ISOC_OUT_EP) { + + /* + * the best case FS budget assumes that 188 FS bytes + * occur in each microframe + */ + sch_ep->num_budget_microframes = DIV_ROUND_UP( + max_packet_size, FS_PAYLOAD_MAX); + sch_ep->bw_cost_per_microframe = FS_PAYLOAD_MAX; + sch_ep->cs_count = sch_ep->num_budget_microframes; + } + if (ep_type == ISOC_IN_EP) { + /* at most need additional two CS. */ + sch_ep->cs_count = DIV_ROUND_UP( + max_packet_size, FS_PAYLOAD_MAX) + 2; + sch_ep->num_budget_microframes = sch_ep->cs_count + 2; + sch_ep->bw_cost_per_microframe = FS_PAYLOAD_MAX; + } + } +} + +/* Get maximum bandwidth when we schedule at offset slot. */ +static u32 get_max_bw(struct mu3h_sch_bw_info *sch_bw, + struct mu3h_sch_ep_info *sch_ep, u32 offset) +{ + u32 num_esit; + u32 max_bw = 0; + int i; + int j; + + num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit; + for (i = 0; i < num_esit; i++) { + u32 base = offset + i * sch_ep->esit; + + for (j = 0; j < sch_ep->num_budget_microframes; j++) { + if (sch_bw->bus_bw[base + j] > max_bw) + max_bw = sch_bw->bus_bw[base + j]; + } + } + return max_bw; +} + +static void update_bus_bw(struct mu3h_sch_bw_info *sch_bw, + struct mu3h_sch_ep_info *sch_ep, int bw_cost) +{ + u32 num_esit; + u32 base; + int i; + int j; + + num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit; + for (i = 0; i < num_esit; i++) { + base = sch_ep->offset + i * sch_ep->esit; + for (j = 0; j < sch_ep->num_budget_microframes; j++) + sch_bw->bus_bw[base + j] += bw_cost; + } +} + +static int check_sch_bw(struct usb_device *udev, + struct mu3h_sch_bw_info *sch_bw, struct mu3h_sch_ep_info *sch_ep) +{ + u32 offset; + u32 esit; + u32 num_budget_microframes; + u32 min_bw; + u32 min_index; + u32 worst_bw; + u32 bw_boundary; + + if (sch_ep->esit > XHCI_MTK_MAX_ESIT) + sch_ep->esit = XHCI_MTK_MAX_ESIT; + + esit = sch_ep->esit; + num_budget_microframes = sch_ep->num_budget_microframes; + + /* + * Search through all possible schedule microframes. + * and find a microframe where its worst bandwidth is minimum. + */ + min_bw = ~0; + min_index = 0; + for (offset = 0; offset < esit; offset++) { + if ((offset + num_budget_microframes) > sch_ep->esit) + break; + + /* + * usb_20 spec section11.18: + * must never schedule Start-Split in Y6 + */ + if (is_fs_or_ls(udev->speed) && (offset % 8 == 6)) + continue; + + worst_bw = get_max_bw(sch_bw, sch_ep, offset); + if (min_bw > worst_bw) { + min_bw = worst_bw; + min_index = offset; + } + if (min_bw == 0) + break; + } + sch_ep->offset = min_index; + + bw_boundary = (udev->speed == USB_SPEED_SUPER) + ? SS_BW_BOUNDARY : HS_BW_BOUNDARY; + + /* check bandwidth */ + if (min_bw + sch_ep->bw_cost_per_microframe > bw_boundary) + return -ERANGE; + + /* update bus bandwidth info */ + update_bus_bw(sch_bw, sch_ep, sch_ep->bw_cost_per_microframe); + + return 0; +} + +static bool need_bw_sch(struct usb_host_endpoint *ep, + enum usb_device_speed speed, int has_tt) +{ + /* only for periodic endpoints */ + if (usb_endpoint_xfer_control(&ep->desc) + || usb_endpoint_xfer_bulk(&ep->desc)) + return false; + + /* + * for LS & FS periodic endpoints which its device don't attach + * to TT are also ignored, root-hub will schedule them directly + */ + if (is_fs_or_ls(speed) && !has_tt) + return false; + + return true; +} + +int xhci_mtk_sch_init(struct xhci_hcd_mtk *mtk) +{ + struct mu3h_sch_bw_info *sch_array; + int num_usb_bus; + int i; + + /* ss IN and OUT are separated */ + num_usb_bus = mtk->num_u3_ports * 2 + mtk->num_u2_ports; + + sch_array = kcalloc(num_usb_bus, sizeof(*sch_array), GFP_KERNEL); + if (sch_array == NULL) + return -ENOMEM; + + for (i = 0; i < num_usb_bus; i++) + INIT_LIST_HEAD(&sch_array[i].bw_ep_list); + + mtk->sch_array = sch_array; + + return 0; +} +EXPORT_SYMBOL_GPL(xhci_mtk_sch_init); + +void xhci_mtk_sch_exit(struct xhci_hcd_mtk *mtk) +{ + kfree(mtk->sch_array); +} +EXPORT_SYMBOL_GPL(xhci_mtk_sch_exit); + +int xhci_mtk_add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd); + struct xhci_hcd *xhci; + struct xhci_ep_ctx *ep_ctx; + struct xhci_slot_ctx *slot_ctx; + struct xhci_virt_device *virt_dev; + struct mu3h_sch_bw_info *sch_bw; + struct mu3h_sch_ep_info *sch_ep; + struct mu3h_sch_bw_info *sch_array; + unsigned int ep_index; + int bw_index; + int ret = 0; + + xhci = hcd_to_xhci(hcd); + virt_dev = xhci->devs[udev->slot_id]; + ep_index = xhci_get_endpoint_index(&ep->desc); + slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->in_ctx); + ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index); + sch_array = mtk->sch_array; + + xhci_dbg(xhci, "%s() type:%d, speed:%d, mpkt:%d, dir:%d, ep:%p\n", + __func__, usb_endpoint_type(&ep->desc), udev->speed, + GET_MAX_PACKET(usb_endpoint_maxp(&ep->desc)), + usb_endpoint_dir_in(&ep->desc), ep); + + if (!need_bw_sch(ep, udev->speed, slot_ctx->tt_info & TT_SLOT)) + return 0; + + bw_index = get_bw_index(xhci, udev, ep); + sch_bw = &sch_array[bw_index]; + + sch_ep = kzalloc(sizeof(struct mu3h_sch_ep_info), GFP_NOIO); + if (!sch_ep) + return -ENOMEM; + + setup_sch_info(udev, ep_ctx, sch_ep); + + ret = check_sch_bw(udev, sch_bw, sch_ep); + if (ret) { + xhci_err(xhci, "Not enough bandwidth!\n"); + kfree(sch_ep); + return -ENOSPC; + } + + list_add_tail(&sch_ep->endpoint, &sch_bw->bw_ep_list); + sch_ep->ep = ep; + + ep_ctx->reserved[0] |= cpu_to_le32(EP_BPKTS(sch_ep->pkts) + | EP_BCSCOUNT(sch_ep->cs_count) | EP_BBM(sch_ep->burst_mode)); + ep_ctx->reserved[1] |= cpu_to_le32(EP_BOFFSET(sch_ep->offset) + | EP_BREPEAT(sch_ep->repeat)); + + xhci_dbg(xhci, " PKTS:%x, CSCOUNT:%x, BM:%x, OFFSET:%x, REPEAT:%x\n", + sch_ep->pkts, sch_ep->cs_count, sch_ep->burst_mode, + sch_ep->offset, sch_ep->repeat); + + return 0; +} +EXPORT_SYMBOL_GPL(xhci_mtk_add_ep_quirk); + +void xhci_mtk_drop_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd); + struct xhci_hcd *xhci; + struct xhci_slot_ctx *slot_ctx; + struct xhci_virt_device *virt_dev; + struct mu3h_sch_bw_info *sch_array; + struct mu3h_sch_bw_info *sch_bw; + struct mu3h_sch_ep_info *sch_ep; + int bw_index; + + xhci = hcd_to_xhci(hcd); + virt_dev = xhci->devs[udev->slot_id]; + slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->in_ctx); + sch_array = mtk->sch_array; + + xhci_dbg(xhci, "%s() type:%d, speed:%d, mpks:%d, dir:%d, ep:%p\n", + __func__, usb_endpoint_type(&ep->desc), udev->speed, + GET_MAX_PACKET(usb_endpoint_maxp(&ep->desc)), + usb_endpoint_dir_in(&ep->desc), ep); + + if (!need_bw_sch(ep, udev->speed, slot_ctx->tt_info & TT_SLOT)) + return; + + bw_index = get_bw_index(xhci, udev, ep); + sch_bw = &sch_array[bw_index]; + + list_for_each_entry(sch_ep, &sch_bw->bw_ep_list, endpoint) { + if (sch_ep->ep == ep) { + update_bus_bw(sch_bw, sch_ep, + -sch_ep->bw_cost_per_microframe); + list_del(&sch_ep->endpoint); + kfree(sch_ep); + break; + } + } +} +EXPORT_SYMBOL_GPL(xhci_mtk_drop_ep_quirk); diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c new file mode 100644 index 000000000000..c9ab6a44c34a --- /dev/null +++ b/drivers/usb/host/xhci-mtk.c @@ -0,0 +1,763 @@ +/* + * MediaTek xHCI Host Controller Driver + * + * Copyright (c) 2015 MediaTek Inc. + * Author: + * Chunfeng Yun + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xhci.h" +#include "xhci-mtk.h" + +/* ip_pw_ctrl0 register */ +#define CTRL0_IP_SW_RST BIT(0) + +/* ip_pw_ctrl1 register */ +#define CTRL1_IP_HOST_PDN BIT(0) + +/* ip_pw_ctrl2 register */ +#define CTRL2_IP_DEV_PDN BIT(0) + +/* ip_pw_sts1 register */ +#define STS1_IP_SLEEP_STS BIT(30) +#define STS1_XHCI_RST BIT(11) +#define STS1_SYS125_RST BIT(10) +#define STS1_REF_RST BIT(8) +#define STS1_SYSPLL_STABLE BIT(0) + +/* ip_xhci_cap register */ +#define CAP_U3_PORT_NUM(p) ((p) & 0xff) +#define CAP_U2_PORT_NUM(p) (((p) >> 8) & 0xff) + +/* u3_ctrl_p register */ +#define CTRL_U3_PORT_HOST_SEL BIT(2) +#define CTRL_U3_PORT_PDN BIT(1) +#define CTRL_U3_PORT_DIS BIT(0) + +/* u2_ctrl_p register */ +#define CTRL_U2_PORT_HOST_SEL BIT(2) +#define CTRL_U2_PORT_PDN BIT(1) +#define CTRL_U2_PORT_DIS BIT(0) + +/* u2_phy_pll register */ +#define CTRL_U2_FORCE_PLL_STB BIT(28) + +#define PERI_WK_CTRL0 0x400 +#define UWK_CTR0_0P_LS_PE BIT(8) /* posedge */ +#define UWK_CTR0_0P_LS_NE BIT(7) /* negedge for 0p linestate*/ +#define UWK_CTL1_1P_LS_C(x) (((x) & 0xf) << 1) +#define UWK_CTL1_1P_LS_E BIT(0) + +#define PERI_WK_CTRL1 0x404 +#define UWK_CTL1_IS_C(x) (((x) & 0xf) << 26) +#define UWK_CTL1_IS_E BIT(25) +#define UWK_CTL1_0P_LS_C(x) (((x) & 0xf) << 21) +#define UWK_CTL1_0P_LS_E BIT(20) +#define UWK_CTL1_IDDIG_C(x) (((x) & 0xf) << 11) /* cycle debounce */ +#define UWK_CTL1_IDDIG_E BIT(10) /* enable debounce */ +#define UWK_CTL1_IDDIG_P BIT(9) /* polarity */ +#define UWK_CTL1_0P_LS_P BIT(7) +#define UWK_CTL1_IS_P BIT(6) /* polarity for ip sleep */ + +enum ssusb_wakeup_src { + SSUSB_WK_IP_SLEEP = 1, + SSUSB_WK_LINE_STATE = 2, +}; + +static int xhci_mtk_host_enable(struct xhci_hcd_mtk *mtk) +{ + struct mu3c_ippc_regs __iomem *ippc = mtk->ippc_regs; + u32 value, check_val; + int ret; + int i; + + /* power on host ip */ + value = readl(&ippc->ip_pw_ctr1); + value &= ~CTRL1_IP_HOST_PDN; + writel(value, &ippc->ip_pw_ctr1); + + /* power on and enable all u3 ports */ + for (i = 0; i < mtk->num_u3_ports; i++) { + value = readl(&ippc->u3_ctrl_p[i]); + value &= ~(CTRL_U3_PORT_PDN | CTRL_U3_PORT_DIS); + value |= CTRL_U3_PORT_HOST_SEL; + writel(value, &ippc->u3_ctrl_p[i]); + } + + /* power on and enable all u2 ports */ + for (i = 0; i < mtk->num_u2_ports; i++) { + value = readl(&ippc->u2_ctrl_p[i]); + value &= ~(CTRL_U2_PORT_PDN | CTRL_U2_PORT_DIS); + value |= CTRL_U2_PORT_HOST_SEL; + writel(value, &ippc->u2_ctrl_p[i]); + } + + /* + * wait for clocks to be stable, and clock domains reset to + * be inactive after power on and enable ports + */ + check_val = STS1_SYSPLL_STABLE | STS1_REF_RST | + STS1_SYS125_RST | STS1_XHCI_RST; + + ret = readl_poll_timeout(&ippc->ip_pw_sts1, value, + (check_val == (value & check_val)), 100, 20000); + if (ret) { + dev_err(mtk->dev, "clocks are not stable (0x%x)\n", value); + return ret; + } + + return 0; +} + +static int xhci_mtk_host_disable(struct xhci_hcd_mtk *mtk) +{ + struct mu3c_ippc_regs __iomem *ippc = mtk->ippc_regs; + u32 value; + int ret; + int i; + + /* power down all u3 ports */ + for (i = 0; i < mtk->num_u3_ports; i++) { + value = readl(&ippc->u3_ctrl_p[i]); + value |= CTRL_U3_PORT_PDN; + writel(value, &ippc->u3_ctrl_p[i]); + } + + /* power down all u2 ports */ + for (i = 0; i < mtk->num_u2_ports; i++) { + value = readl(&ippc->u2_ctrl_p[i]); + value |= CTRL_U2_PORT_PDN; + writel(value, &ippc->u2_ctrl_p[i]); + } + + /* power down host ip */ + value = readl(&ippc->ip_pw_ctr1); + value |= CTRL1_IP_HOST_PDN; + writel(value, &ippc->ip_pw_ctr1); + + /* wait for host ip to sleep */ + ret = readl_poll_timeout(&ippc->ip_pw_sts1, value, + (value & STS1_IP_SLEEP_STS), 100, 100000); + if (ret) { + dev_err(mtk->dev, "ip sleep failed!!!\n"); + return ret; + } + return 0; +} + +static int xhci_mtk_ssusb_config(struct xhci_hcd_mtk *mtk) +{ + struct mu3c_ippc_regs __iomem *ippc = mtk->ippc_regs; + u32 value; + + /* reset whole ip */ + value = readl(&ippc->ip_pw_ctr0); + value |= CTRL0_IP_SW_RST; + writel(value, &ippc->ip_pw_ctr0); + udelay(1); + value = readl(&ippc->ip_pw_ctr0); + value &= ~CTRL0_IP_SW_RST; + writel(value, &ippc->ip_pw_ctr0); + + /* + * device ip is default power-on in fact + * power down device ip, otherwise ip-sleep will fail + */ + value = readl(&ippc->ip_pw_ctr2); + value |= CTRL2_IP_DEV_PDN; + writel(value, &ippc->ip_pw_ctr2); + + value = readl(&ippc->ip_xhci_cap); + mtk->num_u3_ports = CAP_U3_PORT_NUM(value); + mtk->num_u2_ports = CAP_U2_PORT_NUM(value); + dev_dbg(mtk->dev, "%s u2p:%d, u3p:%d\n", __func__, + mtk->num_u2_ports, mtk->num_u3_ports); + + return xhci_mtk_host_enable(mtk); +} + +static int xhci_mtk_clks_enable(struct xhci_hcd_mtk *mtk) +{ + int ret; + + ret = clk_prepare_enable(mtk->sys_clk); + if (ret) { + dev_err(mtk->dev, "failed to enable sys_clk\n"); + goto sys_clk_err; + } + + if (mtk->wakeup_src) { + ret = clk_prepare_enable(mtk->wk_deb_p0); + if (ret) { + dev_err(mtk->dev, "failed to enable wk_deb_p0\n"); + goto usb_p0_err; + } + + ret = clk_prepare_enable(mtk->wk_deb_p1); + if (ret) { + dev_err(mtk->dev, "failed to enable wk_deb_p1\n"); + goto usb_p1_err; + } + } + return 0; + +usb_p1_err: + clk_disable_unprepare(mtk->wk_deb_p0); +usb_p0_err: + clk_disable_unprepare(mtk->sys_clk); +sys_clk_err: + return -EINVAL; +} + +static void xhci_mtk_clks_disable(struct xhci_hcd_mtk *mtk) +{ + if (mtk->wakeup_src) { + clk_disable_unprepare(mtk->wk_deb_p1); + clk_disable_unprepare(mtk->wk_deb_p0); + } + clk_disable_unprepare(mtk->sys_clk); +} + +/* only clocks can be turn off for ip-sleep wakeup mode */ +static void usb_wakeup_ip_sleep_en(struct xhci_hcd_mtk *mtk) +{ + u32 tmp; + struct regmap *pericfg = mtk->pericfg; + + regmap_read(pericfg, PERI_WK_CTRL1, &tmp); + tmp &= ~UWK_CTL1_IS_P; + tmp &= ~(UWK_CTL1_IS_C(0xf)); + tmp |= UWK_CTL1_IS_C(0x8); + regmap_write(pericfg, PERI_WK_CTRL1, tmp); + regmap_write(pericfg, PERI_WK_CTRL1, tmp | UWK_CTL1_IS_E); + + regmap_read(pericfg, PERI_WK_CTRL1, &tmp); + dev_dbg(mtk->dev, "%s(): WK_CTRL1[P6,E25,C26:29]=%#x\n", + __func__, tmp); +} + +static void usb_wakeup_ip_sleep_dis(struct xhci_hcd_mtk *mtk) +{ + u32 tmp; + + regmap_read(mtk->pericfg, PERI_WK_CTRL1, &tmp); + tmp &= ~UWK_CTL1_IS_E; + regmap_write(mtk->pericfg, PERI_WK_CTRL1, tmp); +} + +/* +* for line-state wakeup mode, phy's power should not power-down +* and only support cable plug in/out +*/ +static void usb_wakeup_line_state_en(struct xhci_hcd_mtk *mtk) +{ + u32 tmp; + struct regmap *pericfg = mtk->pericfg; + + /* line-state of u2-port0 */ + regmap_read(pericfg, PERI_WK_CTRL1, &tmp); + tmp &= ~UWK_CTL1_0P_LS_P; + tmp &= ~(UWK_CTL1_0P_LS_C(0xf)); + tmp |= UWK_CTL1_0P_LS_C(0x8); + regmap_write(pericfg, PERI_WK_CTRL1, tmp); + regmap_read(pericfg, PERI_WK_CTRL1, &tmp); + regmap_write(pericfg, PERI_WK_CTRL1, tmp | UWK_CTL1_0P_LS_E); + + /* line-state of u2-port1 */ + regmap_read(pericfg, PERI_WK_CTRL0, &tmp); + tmp &= ~(UWK_CTL1_1P_LS_C(0xf)); + tmp |= UWK_CTL1_1P_LS_C(0x8); + regmap_write(pericfg, PERI_WK_CTRL0, tmp); + regmap_write(pericfg, PERI_WK_CTRL0, tmp | UWK_CTL1_1P_LS_E); +} + +static void usb_wakeup_line_state_dis(struct xhci_hcd_mtk *mtk) +{ + u32 tmp; + struct regmap *pericfg = mtk->pericfg; + + /* line-state of u2-port0 */ + regmap_read(pericfg, PERI_WK_CTRL1, &tmp); + tmp &= ~UWK_CTL1_0P_LS_E; + regmap_write(pericfg, PERI_WK_CTRL1, tmp); + + /* line-state of u2-port1 */ + regmap_read(pericfg, PERI_WK_CTRL0, &tmp); + tmp &= ~UWK_CTL1_1P_LS_E; + regmap_write(pericfg, PERI_WK_CTRL0, tmp); +} + +static void usb_wakeup_enable(struct xhci_hcd_mtk *mtk) +{ + if (mtk->wakeup_src == SSUSB_WK_IP_SLEEP) + usb_wakeup_ip_sleep_en(mtk); + else if (mtk->wakeup_src == SSUSB_WK_LINE_STATE) + usb_wakeup_line_state_en(mtk); +} + +static void usb_wakeup_disable(struct xhci_hcd_mtk *mtk) +{ + if (mtk->wakeup_src == SSUSB_WK_IP_SLEEP) + usb_wakeup_ip_sleep_dis(mtk); + else if (mtk->wakeup_src == SSUSB_WK_LINE_STATE) + usb_wakeup_line_state_dis(mtk); +} + +static int usb_wakeup_of_property_parse(struct xhci_hcd_mtk *mtk, + struct device_node *dn) +{ + struct device *dev = mtk->dev; + + /* + * wakeup function is optional, so it is not an error if this property + * does not exist, and in such case, no need to get relative + * properties anymore. + */ + of_property_read_u32(dn, "mediatek,wakeup-src", &mtk->wakeup_src); + if (!mtk->wakeup_src) + return 0; + + mtk->wk_deb_p0 = devm_clk_get(dev, "wakeup_deb_p0"); + if (IS_ERR(mtk->wk_deb_p0)) { + dev_err(dev, "fail to get wakeup_deb_p0\n"); + return PTR_ERR(mtk->wk_deb_p0); + } + + mtk->wk_deb_p1 = devm_clk_get(dev, "wakeup_deb_p1"); + if (IS_ERR(mtk->wk_deb_p1)) { + dev_err(dev, "fail to get wakeup_deb_p1\n"); + return PTR_ERR(mtk->wk_deb_p1); + } + + mtk->pericfg = syscon_regmap_lookup_by_phandle(dn, + "mediatek,syscon-wakeup"); + if (IS_ERR(mtk->pericfg)) { + dev_err(dev, "fail to get pericfg regs\n"); + return PTR_ERR(mtk->pericfg); + } + + return 0; +} + +static int xhci_mtk_setup(struct usb_hcd *hcd); +static const struct xhci_driver_overrides xhci_mtk_overrides __initconst = { + .extra_priv_size = sizeof(struct xhci_hcd), + .reset = xhci_mtk_setup, +}; + +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; + + ret = regulator_enable(mtk->vbus); + if (ret) { + dev_err(mtk->dev, "failed to enable vbus\n"); + return ret; + } + + ret = regulator_enable(mtk->vusb33); + if (ret) { + dev_err(mtk->dev, "failed to enable vusb33\n"); + regulator_disable(mtk->vbus); + return ret; + } + return 0; +} + +static void xhci_mtk_ldos_disable(struct xhci_hcd_mtk *mtk) +{ + regulator_disable(mtk->vbus); + regulator_disable(mtk->vusb33); +} + +static void xhci_mtk_quirks(struct device *dev, struct xhci_hcd *xhci) +{ + struct usb_hcd *hcd = xhci_to_hcd(xhci); + struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd); + + /* + * As of now platform drivers don't provide MSI support so we ensure + * here that the generic code does not try to make a pci_dev from our + * dev struct in order to setup MSI + */ + xhci->quirks |= XHCI_PLAT; + xhci->quirks |= XHCI_MTK_HOST; + /* + * MTK host controller gives a spurious successful event after a + * short transfer. Ignore it. + */ + xhci->quirks |= XHCI_SPURIOUS_SUCCESS; + if (mtk->lpm_support) + xhci->quirks |= XHCI_LPM_SUPPORT; +} + +/* called during probe() after chip reset completes */ +static int xhci_mtk_setup(struct usb_hcd *hcd) +{ + struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd); + int ret; + + if (usb_hcd_is_primary_hcd(hcd)) { + ret = xhci_mtk_ssusb_config(mtk); + if (ret) + return ret; + ret = xhci_mtk_sch_init(mtk); + if (ret) + return ret; + } + + return xhci_gen_setup(hcd, xhci_mtk_quirks); +} + +static int xhci_mtk_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct xhci_hcd_mtk *mtk; + const struct hc_driver *driver; + struct xhci_hcd *xhci; + struct resource *res; + struct usb_hcd *hcd; + struct phy *phy; + int phy_num; + int ret = -ENODEV; + int irq; + + if (usb_disabled()) + return -ENODEV; + + driver = &xhci_mtk_hc_driver; + mtk = devm_kzalloc(dev, sizeof(*mtk), GFP_KERNEL); + if (!mtk) + return -ENOMEM; + + mtk->dev = dev; + mtk->vbus = devm_regulator_get(dev, "vbus"); + if (IS_ERR(mtk->vbus)) { + dev_err(dev, "fail to get vbus\n"); + return PTR_ERR(mtk->vbus); + } + + mtk->vusb33 = devm_regulator_get(dev, "vusb33"); + if (IS_ERR(mtk->vusb33)) { + dev_err(dev, "fail to get vusb33\n"); + return PTR_ERR(mtk->vusb33); + } + + mtk->sys_clk = devm_clk_get(dev, "sys_ck"); + if (IS_ERR(mtk->sys_clk)) { + dev_err(dev, "fail to get sys_ck\n"); + return PTR_ERR(mtk->sys_clk); + } + + mtk->lpm_support = of_property_read_bool(node, "usb3-lpm-capable"); + + ret = usb_wakeup_of_property_parse(mtk, node); + if (ret) + 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); + + ret = xhci_mtk_ldos_enable(mtk); + if (ret) + goto disable_pm; + + ret = xhci_mtk_clks_enable(mtk); + if (ret) + goto disable_ldos; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + goto disable_clk; + + /* Initialize dma_mask and coherent_dma_mask to 32-bits */ + ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32)); + if (ret) + goto disable_clk; + + if (!dev->dma_mask) + dev->dma_mask = &dev->coherent_dma_mask; + else + dma_set_mask(dev, DMA_BIT_MASK(32)); + + hcd = usb_create_hcd(driver, dev, dev_name(dev)); + if (!hcd) { + ret = -ENOMEM; + goto disable_clk; + } + + /* + * USB 2.0 roothub is stored in the platform_device. + * Swap it with mtk HCD. + */ + mtk->hcd = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, mtk); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hcd->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(hcd->regs)) { + ret = PTR_ERR(hcd->regs); + goto put_usb2_hcd; + } + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + mtk->ippc_regs = devm_ioremap_resource(dev, res); + if (IS_ERR(mtk->ippc_regs)) { + ret = PTR_ERR(mtk->ippc_regs); + goto put_usb2_hcd; + } + + 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); + xhci->main_hcd = hcd; + xhci->shared_hcd = usb_create_shared_hcd(driver, dev, + dev_name(dev), hcd); + if (!xhci->shared_hcd) { + ret = -ENOMEM; + goto power_off_phys; + } + + if (HCC_MAX_PSA(xhci->hcc_params) >= 4) + xhci->shared_hcd->can_do_streams = 1; + + ret = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (ret) + goto put_usb3_hcd; + + ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED); + if (ret) + goto dealloc_usb2_hcd; + + return 0; + +dealloc_usb2_hcd: + usb_remove_hcd(hcd); + +put_usb3_hcd: + xhci_mtk_sch_exit(mtk); + usb_put_hcd(xhci->shared_hcd); + +power_off_phys: + xhci_mtk_phy_power_off(mtk); + device_init_wakeup(dev, false); + +exit_phys: + xhci_mtk_phy_exit(mtk); + +put_usb2_hcd: + usb_put_hcd(hcd); + +disable_clk: + xhci_mtk_clks_disable(mtk); + +disable_ldos: + xhci_mtk_ldos_disable(mtk); + +disable_pm: + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + return ret; +} + +static int xhci_mtk_remove(struct platform_device *dev) +{ + struct xhci_hcd_mtk *mtk = platform_get_drvdata(dev); + struct usb_hcd *hcd = mtk->hcd; + 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); + usb_put_hcd(xhci->shared_hcd); + usb_put_hcd(hcd); + xhci_mtk_sch_exit(mtk); + xhci_mtk_clks_disable(mtk); + xhci_mtk_ldos_disable(mtk); + pm_runtime_put_sync(&dev->dev); + pm_runtime_disable(&dev->dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int xhci_mtk_suspend(struct device *dev) +{ + struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev); + + xhci_mtk_host_disable(mtk); + xhci_mtk_phy_power_off(mtk); + xhci_mtk_clks_disable(mtk); + usb_wakeup_enable(mtk); + return 0; +} + +static int xhci_mtk_resume(struct device *dev) +{ + struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev); + + usb_wakeup_disable(mtk); + xhci_mtk_clks_enable(mtk); + xhci_mtk_phy_power_on(mtk); + xhci_mtk_host_enable(mtk); + return 0; +} + +static const struct dev_pm_ops xhci_mtk_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(xhci_mtk_suspend, xhci_mtk_resume) +}; +#define DEV_PM_OPS (&xhci_mtk_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif /* CONFIG_PM */ + +#ifdef CONFIG_OF +static const struct of_device_id mtk_xhci_of_match[] = { + { .compatible = "mediatek,mt8173-xhci"}, + { }, +}; +MODULE_DEVICE_TABLE(of, mtk_xhci_of_match); +#endif + +static struct platform_driver mtk_xhci_driver = { + .probe = xhci_mtk_probe, + .remove = xhci_mtk_remove, + .driver = { + .name = "xhci-mtk", + .pm = DEV_PM_OPS, + .of_match_table = of_match_ptr(mtk_xhci_of_match), + }, +}; +MODULE_ALIAS("platform:xhci-mtk"); + +static int __init xhci_mtk_init(void) +{ + xhci_init_driver(&xhci_mtk_hc_driver, &xhci_mtk_overrides); + return platform_driver_register(&mtk_xhci_driver); +} +module_init(xhci_mtk_init); + +static void __exit xhci_mtk_exit(void) +{ + platform_driver_unregister(&mtk_xhci_driver); +} +module_exit(xhci_mtk_exit); + +MODULE_AUTHOR("Chunfeng Yun "); +MODULE_DESCRIPTION("MediaTek xHCI Host Controller Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/xhci-mtk.h b/drivers/usb/host/xhci-mtk.h new file mode 100644 index 000000000000..7da677c79ea8 --- /dev/null +++ b/drivers/usb/host/xhci-mtk.h @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * Author: + * Zhigang.Wei + * Chunfeng.Yun + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef _XHCI_MTK_H_ +#define _XHCI_MTK_H_ + +#include "xhci.h" + +/** + * To simplify scheduler algorithm, set a upper limit for ESIT, + * if a synchromous ep's ESIT is larger than @XHCI_MTK_MAX_ESIT, + * round down to the limit value, that means allocating more + * bandwidth to it. + */ +#define XHCI_MTK_MAX_ESIT 64 + +/** + * struct mu3h_sch_bw_info: schedule information for bandwidth domain + * + * @bus_bw: array to keep track of bandwidth already used at each uframes + * @bw_ep_list: eps in the bandwidth domain + * + * treat a HS root port as a bandwidth domain, but treat a SS root port as + * two bandwidth domains, one for IN eps and another for OUT eps. + */ +struct mu3h_sch_bw_info { + u32 bus_bw[XHCI_MTK_MAX_ESIT]; + struct list_head bw_ep_list; +}; + +/** + * struct mu3h_sch_ep_info: schedule information for endpoint + * + * @esit: unit is 125us, equal to 2 << Interval field in ep-context + * @num_budget_microframes: number of continuous uframes + * (@repeat==1) scheduled within the interval + * @bw_cost_per_microframe: bandwidth cost per microframe + * @endpoint: linked into bandwidth domain which it belongs to + * @ep: address of usb_host_endpoint struct + * @offset: which uframe of the interval that transfer should be + * scheduled first time within the interval + * @repeat: the time gap between two uframes that transfers are + * scheduled within a interval. in the simple algorithm, only + * assign 0 or 1 to it; 0 means using only one uframe in a + * interval, and 1 means using @num_budget_microframes + * continuous uframes + * @pkts: number of packets to be transferred in the scheduled uframes + * @cs_count: number of CS that host will trigger + * @burst_mode: burst mode for scheduling. 0: normal burst mode, + * distribute the bMaxBurst+1 packets for a single burst + * according to @pkts and @repeat, repeate the burst multiple + * times; 1: distribute the (bMaxBurst+1)*(Mult+1) packets + * according to @pkts and @repeat. normal mode is used by + * default + */ +struct mu3h_sch_ep_info { + u32 esit; + u32 num_budget_microframes; + u32 bw_cost_per_microframe; + struct list_head endpoint; + void *ep; + /* + * mtk xHCI scheduling information put into reserved DWs + * in ep context + */ + u32 offset; + u32 repeat; + u32 pkts; + u32 cs_count; + u32 burst_mode; +}; + +#define MU3C_U3_PORT_MAX 4 +#define MU3C_U2_PORT_MAX 5 + +/** + * struct mu3c_ippc_regs: MTK ssusb ip port control registers + * @ip_pw_ctr0~3: ip power and clock control registers + * @ip_pw_sts1~2: ip power and clock status registers + * @ip_xhci_cap: ip xHCI capability register + * @u3_ctrl_p[x]: ip usb3 port x control register, only low 4bytes are used + * @u2_ctrl_p[x]: ip usb2 port x control register, only low 4bytes are used + * @u2_phy_pll: usb2 phy pll control register + */ +struct mu3c_ippc_regs { + __le32 ip_pw_ctr0; + __le32 ip_pw_ctr1; + __le32 ip_pw_ctr2; + __le32 ip_pw_ctr3; + __le32 ip_pw_sts1; + __le32 ip_pw_sts2; + __le32 reserved0[3]; + __le32 ip_xhci_cap; + __le32 reserved1[2]; + __le64 u3_ctrl_p[MU3C_U3_PORT_MAX]; + __le64 u2_ctrl_p[MU3C_U2_PORT_MAX]; + __le32 reserved2; + __le32 u2_phy_pll; + __le32 reserved3[33]; /* 0x80 ~ 0xff */ +}; + +struct xhci_hcd_mtk { + struct device *dev; + struct usb_hcd *hcd; + struct mu3h_sch_bw_info *sch_array; + struct mu3c_ippc_regs __iomem *ippc_regs; + int num_u2_ports; + int num_u3_ports; + struct regulator *vusb33; + struct regulator *vbus; + struct clk *sys_clk; /* sys and mac clock */ + struct clk *wk_deb_p0; /* port0's wakeup debounce clock */ + struct clk *wk_deb_p1; + struct regmap *pericfg; + struct phy **phys; + int num_phys; + int wakeup_src; + bool lpm_support; +}; + +static inline struct xhci_hcd_mtk *hcd_to_mtk(struct usb_hcd *hcd) +{ + return dev_get_drvdata(hcd->self.controller); +} + +#if IS_ENABLED(CONFIG_USB_XHCI_MTK) +int xhci_mtk_sch_init(struct xhci_hcd_mtk *mtk); +void xhci_mtk_sch_exit(struct xhci_hcd_mtk *mtk); +int xhci_mtk_add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep); +void xhci_mtk_drop_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep); + +#else +static inline int xhci_mtk_add_ep_quirk(struct usb_hcd *hcd, + struct usb_device *udev, struct usb_host_endpoint *ep) +{ + return 0; +} + +static inline void xhci_mtk_drop_ep_quirk(struct usb_hcd *hcd, + struct usb_device *udev, struct usb_host_endpoint *ep) +{ +} + +#endif + +#endif /* _XHCI_MTK_H_ */ diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index c62109091d12..58c43ed7ff3b 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -53,7 +53,6 @@ static struct hc_driver __read_mostly xhci_pci_hc_driver; static int xhci_pci_setup(struct usb_hcd *hcd); static const struct xhci_driver_overrides xhci_pci_overrides __initconst = { - .extra_priv_size = sizeof(struct xhci_hcd), .reset = xhci_pci_setup, }; diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 05647e6753cd..770b6b088797 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -22,6 +22,7 @@ #include #include "xhci.h" +#include "xhci-plat.h" #include "xhci-mvebu.h" #include "xhci-rcar.h" @@ -31,7 +32,7 @@ static int xhci_plat_setup(struct usb_hcd *hcd); static int xhci_plat_start(struct usb_hcd *hcd); static const struct xhci_driver_overrides xhci_plat_overrides __initconst = { - .extra_priv_size = sizeof(struct xhci_hcd), + .extra_priv_size = sizeof(struct xhci_plat_priv), .reset = xhci_plat_setup, .start = xhci_plat_start, }; @@ -49,11 +50,10 @@ static void xhci_plat_quirks(struct device *dev, struct xhci_hcd *xhci) /* called during probe() after chip reset completes */ static int xhci_plat_setup(struct usb_hcd *hcd) { - struct device_node *of_node = hcd->self.controller->of_node; int ret; - if (of_device_is_compatible(of_node, "renesas,xhci-r8a7790") || - of_device_is_compatible(of_node, "renesas,xhci-r8a7791")) { + if (xhci_plat_type_is(hcd, XHCI_PLAT_TYPE_RENESAS_RCAR_GEN2) || + xhci_plat_type_is(hcd, XHCI_PLAT_TYPE_RENESAS_RCAR_GEN3)) { ret = xhci_rcar_init_quirk(hcd); if (ret) return ret; @@ -64,19 +64,62 @@ static int xhci_plat_setup(struct usb_hcd *hcd) static int xhci_plat_start(struct usb_hcd *hcd) { - struct device_node *of_node = hcd->self.controller->of_node; - - if (of_device_is_compatible(of_node, "renesas,xhci-r8a7790") || - of_device_is_compatible(of_node, "renesas,xhci-r8a7791")) + if (xhci_plat_type_is(hcd, XHCI_PLAT_TYPE_RENESAS_RCAR_GEN2) || + xhci_plat_type_is(hcd, XHCI_PLAT_TYPE_RENESAS_RCAR_GEN3)) xhci_rcar_start(hcd); return xhci_run(hcd); } +#ifdef CONFIG_OF +static const struct xhci_plat_priv xhci_plat_marvell_armada = { + .type = XHCI_PLAT_TYPE_MARVELL_ARMADA, +}; + +static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen2 = { + .type = XHCI_PLAT_TYPE_RENESAS_RCAR_GEN2, + .firmware_name = XHCI_RCAR_FIRMWARE_NAME_V1, +}; + +static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen3 = { + .type = XHCI_PLAT_TYPE_RENESAS_RCAR_GEN3, + .firmware_name = XHCI_RCAR_FIRMWARE_NAME_V2, +}; + +static const struct of_device_id usb_xhci_of_match[] = { + { + .compatible = "generic-xhci", + }, { + .compatible = "xhci-platform", + }, { + .compatible = "marvell,armada-375-xhci", + .data = &xhci_plat_marvell_armada, + }, { + .compatible = "marvell,armada-380-xhci", + .data = &xhci_plat_marvell_armada, + }, { + .compatible = "renesas,xhci-r8a7790", + .data = &xhci_plat_renesas_rcar_gen2, + }, { + .compatible = "renesas,xhci-r8a7791", + .data = &xhci_plat_renesas_rcar_gen2, + }, { + .compatible = "renesas,xhci-r8a7793", + .data = &xhci_plat_renesas_rcar_gen2, + }, { + .compatible = "renesas,xhci-r8a7795", + .data = &xhci_plat_renesas_rcar_gen3, + }, { + }, +}; +MODULE_DEVICE_TABLE(of, usb_xhci_of_match); +#endif + static int xhci_plat_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; struct usb_xhci_pdata *pdata = dev_get_platdata(&pdev->dev); + const struct of_device_id *match; const struct hc_driver *driver; struct xhci_hcd *xhci; struct resource *res; @@ -134,10 +177,17 @@ static int xhci_plat_probe(struct platform_device *pdev) goto put_hcd; } - if (of_device_is_compatible(pdev->dev.of_node, - "marvell,armada-375-xhci") || - of_device_is_compatible(pdev->dev.of_node, - "marvell,armada-380-xhci")) { + xhci = hcd_to_xhci(hcd); + match = of_match_node(usb_xhci_of_match, node); + if (match) { + const struct xhci_plat_priv *priv_match = match->data; + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + + /* Just copy data for now */ + *priv = *priv_match; + } + + if (xhci_plat_type_is(hcd, XHCI_PLAT_TYPE_MARVELL_ARMADA)) { ret = xhci_mvebu_mbus_init_quirk(pdev); if (ret) goto disable_clk; @@ -145,7 +195,6 @@ static int xhci_plat_probe(struct platform_device *pdev) device_wakeup_enable(hcd->self.controller); - xhci = hcd_to_xhci(hcd); xhci->clk = clk; xhci->main_hcd = hcd; xhci->shared_hcd = usb_create_shared_hcd(driver, &pdev->dev, @@ -256,19 +305,6 @@ static const struct dev_pm_ops xhci_plat_pm_ops = { #define DEV_PM_OPS NULL #endif /* CONFIG_PM */ -#ifdef CONFIG_OF -static const struct of_device_id usb_xhci_of_match[] = { - { .compatible = "generic-xhci" }, - { .compatible = "xhci-platform" }, - { .compatible = "marvell,armada-375-xhci"}, - { .compatible = "marvell,armada-380-xhci"}, - { .compatible = "renesas,xhci-r8a7790"}, - { .compatible = "renesas,xhci-r8a7791"}, - { }, -}; -MODULE_DEVICE_TABLE(of, usb_xhci_of_match); -#endif - static const struct acpi_device_id usb_xhci_acpi_match[] = { /* XHCI-compliant USB Controller */ { "PNP0D10", }, diff --git a/drivers/usb/host/xhci-plat.h b/drivers/usb/host/xhci-plat.h new file mode 100644 index 000000000000..5a2e2e3936c4 --- /dev/null +++ b/drivers/usb/host/xhci-plat.h @@ -0,0 +1,39 @@ +/* + * xhci-plat.h - xHCI host controller driver platform Bus Glue. + * + * Copyright (C) 2015 Renesas Electronics Corporation + * + * 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. + */ + +#ifndef _XHCI_PLAT_H +#define _XHCI_PLAT_H + +#include "xhci.h" /* for hcd_to_xhci() */ + +enum xhci_plat_type { + XHCI_PLAT_TYPE_MARVELL_ARMADA, + XHCI_PLAT_TYPE_RENESAS_RCAR_GEN2, + XHCI_PLAT_TYPE_RENESAS_RCAR_GEN3, +}; + +struct xhci_plat_priv { + enum xhci_plat_type type; + const char *firmware_name; +}; + +#define hcd_to_xhci_priv(h) ((struct xhci_plat_priv *)hcd_to_xhci(h)->priv) + +static inline bool xhci_plat_type_is(struct usb_hcd *hcd, + enum xhci_plat_type type) +{ + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + + if (priv && priv->type == type) + return true; + else + return false; +} +#endif /* _XHCI_PLAT_H */ diff --git a/drivers/usb/host/xhci-rcar.c b/drivers/usb/host/xhci-rcar.c index ff0d1b44ea58..623100e9385e 100644 --- a/drivers/usb/host/xhci-rcar.c +++ b/drivers/usb/host/xhci-rcar.c @@ -14,10 +14,17 @@ #include #include "xhci.h" +#include "xhci-plat.h" #include "xhci-rcar.h" -#define FIRMWARE_NAME "r8a779x_usb3_v1.dlmem" -MODULE_FIRMWARE(FIRMWARE_NAME); +/* +* - The V2 firmware is possible to use on R-Car Gen2. However, the V2 causes +* performance degradation. So, this driver continues to use the V1 if R-Car +* Gen2. +* - The V1 firmware is impossible to use on R-Car Gen3. +*/ +MODULE_FIRMWARE(XHCI_RCAR_FIRMWARE_NAME_V1); +MODULE_FIRMWARE(XHCI_RCAR_FIRMWARE_NAME_V2); /*** Register Offset ***/ #define RCAR_USB3_INT_ENA 0x224 /* Interrupt Enable */ @@ -56,6 +63,19 @@ MODULE_FIRMWARE(FIRMWARE_NAME); #define RCAR_USB3_RX_POL_VAL BIT(21) #define RCAR_USB3_TX_POL_VAL BIT(4) +static void xhci_rcar_start_gen2(struct usb_hcd *hcd) +{ + /* LCLK Select */ + writel(RCAR_USB3_LCLK_ENA_VAL, hcd->regs + RCAR_USB3_LCLK); + /* USB3.0 Configuration */ + writel(RCAR_USB3_CONF1_VAL, hcd->regs + RCAR_USB3_CONF1); + writel(RCAR_USB3_CONF2_VAL, hcd->regs + RCAR_USB3_CONF2); + writel(RCAR_USB3_CONF3_VAL, hcd->regs + RCAR_USB3_CONF3); + /* USB3.0 Polarity */ + writel(RCAR_USB3_RX_POL_VAL, hcd->regs + RCAR_USB3_RX_POL); + writel(RCAR_USB3_TX_POL_VAL, hcd->regs + RCAR_USB3_TX_POL); +} + void xhci_rcar_start(struct usb_hcd *hcd) { u32 temp; @@ -65,27 +85,23 @@ void xhci_rcar_start(struct usb_hcd *hcd) temp = readl(hcd->regs + RCAR_USB3_INT_ENA); temp |= RCAR_USB3_INT_ENA_VAL; writel(temp, hcd->regs + RCAR_USB3_INT_ENA); - /* LCLK Select */ - writel(RCAR_USB3_LCLK_ENA_VAL, hcd->regs + RCAR_USB3_LCLK); - /* USB3.0 Configuration */ - writel(RCAR_USB3_CONF1_VAL, hcd->regs + RCAR_USB3_CONF1); - writel(RCAR_USB3_CONF2_VAL, hcd->regs + RCAR_USB3_CONF2); - writel(RCAR_USB3_CONF3_VAL, hcd->regs + RCAR_USB3_CONF3); - /* USB3.0 Polarity */ - writel(RCAR_USB3_RX_POL_VAL, hcd->regs + RCAR_USB3_RX_POL); - writel(RCAR_USB3_TX_POL_VAL, hcd->regs + RCAR_USB3_TX_POL); + if (xhci_plat_type_is(hcd, XHCI_PLAT_TYPE_RENESAS_RCAR_GEN2)) + xhci_rcar_start_gen2(hcd); } } -static int xhci_rcar_download_firmware(struct device *dev, void __iomem *regs) +static int xhci_rcar_download_firmware(struct usb_hcd *hcd) { + struct device *dev = hcd->self.controller; + void __iomem *regs = hcd->regs; + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); const struct firmware *fw; int retval, index, j, time; int timeout = 10000; u32 data, val, temp; /* request R-Car USB3.0 firmware */ - retval = request_firmware(&fw, FIRMWARE_NAME, dev); + retval = request_firmware(&fw, priv->firmware_name, dev); if (retval) return retval; @@ -144,5 +160,5 @@ int xhci_rcar_init_quirk(struct usb_hcd *hcd) if (!hcd->regs) return 0; - return xhci_rcar_download_firmware(hcd->self.controller, hcd->regs); + return xhci_rcar_download_firmware(hcd); } diff --git a/drivers/usb/host/xhci-rcar.h b/drivers/usb/host/xhci-rcar.h index 58501256715d..2941a25cfe98 100644 --- a/drivers/usb/host/xhci-rcar.h +++ b/drivers/usb/host/xhci-rcar.h @@ -11,6 +11,9 @@ #ifndef _XHCI_RCAR_H #define _XHCI_RCAR_H +#define XHCI_RCAR_FIRMWARE_NAME_V1 "r8a779x_usb3_v1.dlmem" +#define XHCI_RCAR_FIRMWARE_NAME_V2 "r8a779x_usb3_v2.dlmem" + #if IS_ENABLED(CONFIG_USB_XHCI_RCAR) void xhci_rcar_start(struct usb_hcd *hcd); int xhci_rcar_init_quirk(struct usb_hcd *hcd); diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index eeaa6c6bd540..f1c21c40b4a6 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -68,6 +68,7 @@ #include #include "xhci.h" #include "xhci-trace.h" +#include "xhci-mtk.h" /* * Returns zero if the TRB isn't in this segment, otherwise it returns the DMA @@ -3075,17 +3076,22 @@ static u32 xhci_td_remainder(struct xhci_hcd *xhci, int transferred, { u32 maxp, total_packet_count; - if (xhci->hci_version < 0x100) + /* MTK xHCI is mostly 0.97 but contains some features from 1.0 */ + if (xhci->hci_version < 0x100 && !(xhci->quirks & XHCI_MTK_HOST)) return ((td_total_len - transferred) >> 10); - maxp = GET_MAX_PACKET(usb_endpoint_maxp(&urb->ep->desc)); - total_packet_count = DIV_ROUND_UP(td_total_len, maxp); - /* One TRB with a zero-length data packet. */ if (num_trbs_left == 0 || (transferred == 0 && trb_buff_len == 0) || trb_buff_len == td_total_len) return 0; + /* for MTK xHCI, TD size doesn't include this TRB */ + if (xhci->quirks & XHCI_MTK_HOST) + trb_buff_len = 0; + + maxp = GET_MAX_PACKET(usb_endpoint_maxp(&urb->ep->desc)); + total_packet_count = DIV_ROUND_UP(td_total_len, maxp); + /* Queueing functions don't count the current TRB into transferred */ return (total_packet_count - ((transferred + trb_buff_len) / maxp)); } @@ -3473,7 +3479,7 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, field |= 0x1; /* xHCI 1.0/1.1 6.4.1.2.1: Transfer Type field */ - if (xhci->hci_version >= 0x100) { + if ((xhci->hci_version >= 0x100) || (xhci->quirks & XHCI_MTK_HOST)) { if (urb->transfer_buffer_length > 0) { if (setup->bRequestType & USB_DIR_IN) field |= TRB_TX_TYPE(TRB_DATA_IN); diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 3f912705dcef..26a44c0e969e 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -31,6 +31,7 @@ #include "xhci.h" #include "xhci-trace.h" +#include "xhci-mtk.h" #define DRIVER_AUTHOR "Sarah Sharp" #define DRIVER_DESC "'eXtensible' Host Controller (xHC) Driver" @@ -634,7 +635,11 @@ int xhci_run(struct usb_hcd *hcd) "// Set the interrupt modulation register"); temp = readl(&xhci->ir_set->irq_control); temp &= ~ER_IRQ_INTERVAL_MASK; - temp |= (u32) 160; + /* + * the increment interval is 8 times as much as that defined + * in xHCI spec on MTK's controller + */ + temp |= (u32) ((xhci->quirks & XHCI_MTK_HOST) ? 20 : 160); writel(temp, &xhci->ir_set->irq_control); /* Set the HCD state before we enable the irqs */ @@ -1698,6 +1703,9 @@ int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, xhci_endpoint_zero(xhci, xhci->devs[udev->slot_id], ep); + if (xhci->quirks & XHCI_MTK_HOST) + xhci_mtk_drop_ep_quirk(hcd, udev, ep); + xhci_dbg(xhci, "drop ep 0x%x, slot id %d, new drop flags = %#x, new add flags = %#x\n", (unsigned int) ep->desc.bEndpointAddress, udev->slot_id, @@ -1793,6 +1801,15 @@ int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, return -ENOMEM; } + if (xhci->quirks & XHCI_MTK_HOST) { + ret = xhci_mtk_add_ep_quirk(hcd, udev, ep); + if (ret < 0) { + xhci_free_or_cache_endpoint_ring(xhci, + virt_dev, ep_index); + return ret; + } + } + ctrl_ctx->add_flags |= cpu_to_le32(added_ctxs); new_add_flags = le32_to_cpu(ctrl_ctx->add_flags); @@ -4960,7 +4977,7 @@ EXPORT_SYMBOL_GPL(xhci_gen_setup); static const struct hc_driver xhci_hc_driver = { .description = "xhci-hcd", .product_desc = "xHCI Host Controller", - .hcd_priv_size = sizeof(struct xhci_hcd *), + .hcd_priv_size = sizeof(struct xhci_hcd), /* * generic hardware linkage @@ -5059,6 +5076,10 @@ static int __init xhci_hcd_init(void) BUILD_BUG_ON(sizeof(struct xhci_intr_reg) != 8*32/8); /* xhci_run_regs has eight fields and embeds 128 xhci_intr_regs */ BUILD_BUG_ON(sizeof(struct xhci_run_regs) != (8+8*128)*32/8); + + if (usb_disabled()) + return -ENODEV; + return 0; } diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 0b9451250e33..9be7348872ba 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1630,6 +1630,7 @@ struct xhci_hcd { /* For controllers with a broken beyond repair streams implementation */ #define XHCI_BROKEN_STREAMS (1 << 19) #define XHCI_PME_STUCK_QUIRK (1 << 20) +#define XHCI_MTK_HOST (1 << 21) unsigned int num_active_eps; unsigned int limit_active_eps; /* There are two roothubs to keep track of bus suspend info for */ @@ -1656,6 +1657,9 @@ struct xhci_hcd { u32 port_status_u0; /* Compliance Mode Timer Triggered every 2 seconds */ #define COMP_MODE_RCVRY_MSECS 2000 + + /* platform-specific data -- must come last */ + unsigned long priv[0] __aligned(sizeof(s64)); }; /* Platform specific overrides to generic XHCI hc_driver ops */ diff --git a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c index 637f3f7cfce8..92fdb6e9faff 100644 --- a/drivers/usb/misc/usbtest.c +++ b/drivers/usb/misc/usbtest.c @@ -22,18 +22,42 @@ static void complicated_callback(struct urb *urb); /*-------------------------------------------------------------------------*/ /* FIXME make these public somewhere; usbdevfs.h? */ -struct usbtest_param { + +/* Parameter for usbtest driver. */ +struct usbtest_param_32 { /* inputs */ - unsigned test_num; /* 0..(TEST_CASES-1) */ - unsigned iterations; - unsigned length; - unsigned vary; - unsigned sglen; + __u32 test_num; /* 0..(TEST_CASES-1) */ + __u32 iterations; + __u32 length; + __u32 vary; + __u32 sglen; /* outputs */ - struct timeval duration; + __s32 duration_sec; + __s32 duration_usec; }; -#define USBTEST_REQUEST _IOWR('U', 100, struct usbtest_param) + +/* + * Compat parameter to the usbtest driver. + * This supports older user space binaries compiled with 64 bit compiler. + */ +struct usbtest_param_64 { + /* inputs */ + __u32 test_num; /* 0..(TEST_CASES-1) */ + __u32 iterations; + __u32 length; + __u32 vary; + __u32 sglen; + + /* outputs */ + __s64 duration_sec; + __s64 duration_usec; +}; + +/* IOCTL interface to the driver. */ +#define USBTEST_REQUEST_32 _IOWR('U', 100, struct usbtest_param_32) +/* COMPAT IOCTL interface to the driver. */ +#define USBTEST_REQUEST_64 _IOWR('U', 100, struct usbtest_param_64) /*-------------------------------------------------------------------------*/ @@ -1030,7 +1054,7 @@ struct ctrl_ctx { unsigned pending; int status; struct urb **urb; - struct usbtest_param *param; + struct usbtest_param_32 *param; int last; }; @@ -1155,7 +1179,7 @@ error: } static int -test_ctrl_queue(struct usbtest_dev *dev, struct usbtest_param *param) +test_ctrl_queue(struct usbtest_dev *dev, struct usbtest_param_32 *param) { struct usb_device *udev = testdev_to_usbdev(dev); struct urb **urb; @@ -1849,7 +1873,7 @@ static void complicated_callback(struct urb *urb) goto done; default: dev_err(&ctx->dev->intf->dev, - "iso resubmit err %d\n", + "resubmit err %d\n", status); /* FALLTHROUGH */ case -ENODEV: /* disconnected */ @@ -1863,7 +1887,7 @@ static void complicated_callback(struct urb *urb) if (ctx->pending == 0) { if (ctx->errors) dev_err(&ctx->dev->intf->dev, - "iso test, %lu errors out of %lu\n", + "during the test, %lu errors out of %lu\n", ctx->errors, ctx->packet_count); complete(&ctx->done); } @@ -1930,7 +1954,7 @@ static struct urb *iso_alloc_urb( } static int -test_queue(struct usbtest_dev *dev, struct usbtest_param *param, +test_queue(struct usbtest_dev *dev, struct usbtest_param_32 *param, int pipe, struct usb_endpoint_descriptor *desc, unsigned offset) { struct transfer_context context; @@ -2049,81 +2073,20 @@ static int test_unaligned_bulk( return retval; } -/*-------------------------------------------------------------------------*/ - -/* We only have this one interface to user space, through usbfs. - * User mode code can scan usbfs to find N different devices (maybe on - * different busses) to use when testing, and allocate one thread per - * test. So discovery is simplified, and we have no device naming issues. - * - * Don't use these only as stress/load tests. Use them along with with - * other USB bus activity: plugging, unplugging, mousing, mp3 playback, - * video capture, and so on. Run different tests at different times, in - * different sequences. Nothing here should interact with other devices, - * except indirectly by consuming USB bandwidth and CPU resources for test - * threads and request completion. But the only way to know that for sure - * is to test when HC queues are in use by many devices. - * - * WARNING: Because usbfs grabs udev->dev.sem before calling this ioctl(), - * it locks out usbcore in certain code paths. Notably, if you disconnect - * the device-under-test, hub_wq will wait block forever waiting for the - * ioctl to complete ... so that usb_disconnect() can abort the pending - * urbs and then call usbtest_disconnect(). To abort a test, you're best - * off just killing the userspace task and waiting for it to exit. - */ - +/* Run tests. */ static int -usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf) +usbtest_do_ioctl(struct usb_interface *intf, struct usbtest_param_32 *param) { struct usbtest_dev *dev = usb_get_intfdata(intf); struct usb_device *udev = testdev_to_usbdev(dev); - struct usbtest_param *param = buf; - int retval = -EOPNOTSUPP; struct urb *urb; struct scatterlist *sg; struct usb_sg_request req; - struct timeval start; unsigned i; - - /* FIXME USBDEVFS_CONNECTINFO doesn't say how fast the device is. */ - - pattern = mod_pattern; - - if (code != USBTEST_REQUEST) - return -EOPNOTSUPP; + int retval = -EOPNOTSUPP; if (param->iterations <= 0) return -EINVAL; - - if (param->sglen > MAX_SGLEN) - return -EINVAL; - - if (mutex_lock_interruptible(&dev->lock)) - return -ERESTARTSYS; - - /* FIXME: What if a system sleep starts while a test is running? */ - - /* some devices, like ez-usb default devices, need a non-default - * altsetting to have any active endpoints. some tests change - * altsettings; force a default so most tests don't need to check. - */ - if (dev->info->alt >= 0) { - int res; - - if (intf->altsetting->desc.bInterfaceNumber) { - mutex_unlock(&dev->lock); - return -ENODEV; - } - res = set_altsetting(dev, dev->info->alt); - if (res) { - dev_err(&intf->dev, - "set altsetting to %d failed, %d\n", - dev->info->alt, res); - mutex_unlock(&dev->lock); - return res; - } - } - /* * Just a bunch of test cases that every HCD is expected to handle. * @@ -2133,7 +2096,6 @@ usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf) * FIXME add more tests! cancel requests, verify the data, control * queueing, concurrent read+write threads, and so on. */ - do_gettimeofday(&start); switch (param->test_num) { case 0: @@ -2548,13 +2510,116 @@ usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf) dev->in_pipe, NULL, 0); break; } - do_gettimeofday(¶m->duration); - param->duration.tv_sec -= start.tv_sec; - param->duration.tv_usec -= start.tv_usec; - if (param->duration.tv_usec < 0) { - param->duration.tv_usec += 1000 * 1000; - param->duration.tv_sec -= 1; + return retval; +} + +/*-------------------------------------------------------------------------*/ + +/* We only have this one interface to user space, through usbfs. + * User mode code can scan usbfs to find N different devices (maybe on + * different busses) to use when testing, and allocate one thread per + * test. So discovery is simplified, and we have no device naming issues. + * + * Don't use these only as stress/load tests. Use them along with with + * other USB bus activity: plugging, unplugging, mousing, mp3 playback, + * video capture, and so on. Run different tests at different times, in + * different sequences. Nothing here should interact with other devices, + * except indirectly by consuming USB bandwidth and CPU resources for test + * threads and request completion. But the only way to know that for sure + * is to test when HC queues are in use by many devices. + * + * WARNING: Because usbfs grabs udev->dev.sem before calling this ioctl(), + * it locks out usbcore in certain code paths. Notably, if you disconnect + * the device-under-test, hub_wq will wait block forever waiting for the + * ioctl to complete ... so that usb_disconnect() can abort the pending + * urbs and then call usbtest_disconnect(). To abort a test, you're best + * off just killing the userspace task and waiting for it to exit. + */ + +static int +usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf) +{ + + struct usbtest_dev *dev = usb_get_intfdata(intf); + struct usbtest_param_64 *param_64 = buf; + struct usbtest_param_32 temp; + struct usbtest_param_32 *param_32 = buf; + struct timespec64 start; + struct timespec64 end; + struct timespec64 duration; + int retval = -EOPNOTSUPP; + + /* FIXME USBDEVFS_CONNECTINFO doesn't say how fast the device is. */ + + pattern = mod_pattern; + + if (mutex_lock_interruptible(&dev->lock)) + return -ERESTARTSYS; + + /* FIXME: What if a system sleep starts while a test is running? */ + + /* some devices, like ez-usb default devices, need a non-default + * altsetting to have any active endpoints. some tests change + * altsettings; force a default so most tests don't need to check. + */ + if (dev->info->alt >= 0) { + if (intf->altsetting->desc.bInterfaceNumber) { + retval = -ENODEV; + goto free_mutex; + } + retval = set_altsetting(dev, dev->info->alt); + if (retval) { + dev_err(&intf->dev, + "set altsetting to %d failed, %d\n", + dev->info->alt, retval); + goto free_mutex; + } } + + switch (code) { + case USBTEST_REQUEST_64: + temp.test_num = param_64->test_num; + temp.iterations = param_64->iterations; + temp.length = param_64->length; + temp.sglen = param_64->sglen; + temp.vary = param_64->vary; + param_32 = &temp; + break; + + case USBTEST_REQUEST_32: + break; + + default: + retval = -EOPNOTSUPP; + goto free_mutex; + } + + ktime_get_ts64(&start); + + retval = usbtest_do_ioctl(intf, param_32); + if (retval) + goto free_mutex; + + ktime_get_ts64(&end); + + duration = timespec64_sub(end, start); + + temp.duration_sec = duration.tv_sec; + temp.duration_usec = duration.tv_nsec/NSEC_PER_USEC; + + switch (code) { + case USBTEST_REQUEST_32: + param_32->duration_sec = temp.duration_sec; + param_32->duration_usec = temp.duration_usec; + break; + + case USBTEST_REQUEST_64: + param_64->duration_sec = temp.duration_sec; + param_64->duration_usec = temp.duration_usec; + break; + } + +free_mutex: mutex_unlock(&dev->lock); return retval; } diff --git a/drivers/usb/mon/mon_bin.c b/drivers/usb/mon/mon_bin.c index 3598f1a62673..1a874a1f3890 100644 --- a/drivers/usb/mon/mon_bin.c +++ b/drivers/usb/mon/mon_bin.c @@ -18,6 +18,7 @@ #include #include #include +#include #include @@ -92,8 +93,8 @@ struct mon_bin_hdr { unsigned short busnum; /* Bus number */ char flag_setup; char flag_data; - s64 ts_sec; /* gettimeofday */ - s32 ts_usec; /* gettimeofday */ + s64 ts_sec; /* getnstimeofday64 */ + s32 ts_usec; /* getnstimeofday64 */ int status; unsigned int len_urb; /* Length of data (submitted or actual) */ unsigned int len_cap; /* Delivered length */ @@ -483,7 +484,7 @@ static void mon_bin_event(struct mon_reader_bin *rp, struct urb *urb, char ev_type, int status) { const struct usb_endpoint_descriptor *epd = &urb->ep->desc; - struct timeval ts; + struct timespec64 ts; unsigned long flags; unsigned int urb_length; unsigned int offset; @@ -494,7 +495,7 @@ static void mon_bin_event(struct mon_reader_bin *rp, struct urb *urb, struct mon_bin_hdr *ep; char data_tag = 0; - do_gettimeofday(&ts); + getnstimeofday64(&ts); spin_lock_irqsave(&rp->b_lock, flags); @@ -568,7 +569,7 @@ static void mon_bin_event(struct mon_reader_bin *rp, struct urb *urb, ep->busnum = urb->dev->bus->busnum; ep->id = (unsigned long) urb; ep->ts_sec = ts.tv_sec; - ep->ts_usec = ts.tv_usec; + ep->ts_usec = ts.tv_nsec / NSEC_PER_USEC; ep->status = status; ep->len_urb = urb_length; ep->len_cap = length + lendesc; @@ -629,12 +630,12 @@ static void mon_bin_complete(void *data, struct urb *urb, int status) static void mon_bin_error(void *data, struct urb *urb, int error) { struct mon_reader_bin *rp = data; - struct timeval ts; + struct timespec64 ts; unsigned long flags; unsigned int offset; struct mon_bin_hdr *ep; - do_gettimeofday(&ts); + getnstimeofday64(&ts); spin_lock_irqsave(&rp->b_lock, flags); @@ -656,7 +657,7 @@ static void mon_bin_error(void *data, struct urb *urb, int error) ep->busnum = urb->dev->bus->busnum; ep->id = (unsigned long) urb; ep->ts_sec = ts.tv_sec; - ep->ts_usec = ts.tv_usec; + ep->ts_usec = ts.tv_nsec / NSEC_PER_USEC; ep->status = error; ep->flag_setup = '-'; diff --git a/drivers/usb/mon/mon_main.c b/drivers/usb/mon/mon_main.c index f7c292f4891e..fec3f1128fdc 100644 --- a/drivers/usb/mon/mon_main.c +++ b/drivers/usb/mon/mon_main.c @@ -241,7 +241,7 @@ static struct notifier_block mon_nb = { /* * Ops */ -static struct usb_mon_operations mon_ops_0 = { +static const struct usb_mon_operations mon_ops_0 = { .urb_submit = mon_submit, .urb_submit_error = mon_submit_error, .urb_complete = mon_complete, diff --git a/drivers/usb/mon/mon_text.c b/drivers/usb/mon/mon_text.c index ad408251d955..e59334b09c41 100644 --- a/drivers/usb/mon/mon_text.c +++ b/drivers/usb/mon/mon_text.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -176,12 +177,12 @@ static inline char mon_text_get_data(struct mon_event_text *ep, struct urb *urb, static inline unsigned int mon_get_timestamp(void) { - struct timeval tval; + struct timespec64 now; unsigned int stamp; - do_gettimeofday(&tval); - stamp = tval.tv_sec & 0xFFF; /* 2^32 = 4294967296. Limit to 4096s. */ - stamp = stamp * 1000000 + tval.tv_usec; + ktime_get_ts64(&now); + stamp = now.tv_sec & 0xFFF; /* 2^32 = 4294967296. Limit to 4096s. */ + stamp = stamp * USEC_PER_SEC + now.tv_nsec / NSEC_PER_USEC; return stamp; } @@ -386,7 +387,8 @@ static ssize_t mon_text_read_t(struct file *file, char __user *buf, struct mon_event_text *ep; struct mon_text_ptr ptr; - if (IS_ERR(ep = mon_text_read_wait(rp, file))) + ep = mon_text_read_wait(rp, file); + if (IS_ERR(ep)) return PTR_ERR(ep); mutex_lock(&rp->printf_lock); ptr.cnt = 0; @@ -413,7 +415,8 @@ static ssize_t mon_text_read_u(struct file *file, char __user *buf, struct mon_event_text *ep; struct mon_text_ptr ptr; - if (IS_ERR(ep = mon_text_read_wait(rp, file))) + ep = mon_text_read_wait(rp, file); + if (IS_ERR(ep)) return PTR_ERR(ep); mutex_lock(&rp->printf_lock); ptr.cnt = 0; diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index ee9ff7028b92..c3791a01ab31 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -1360,8 +1360,7 @@ static int ep_config_from_table(struct musb *musb) break; } - printk(KERN_DEBUG "%s: setup fifo_mode %d\n", - musb_driver_name, fifo_mode); + pr_debug("%s: setup fifo_mode %d\n", musb_driver_name, fifo_mode); done: @@ -1390,7 +1389,7 @@ done: musb->nr_endpoints = max(epn, musb->nr_endpoints); } - printk(KERN_DEBUG "%s: %d/%d max ep, %d/%d memory\n", + pr_debug("%s: %d/%d max ep, %d/%d memory\n", musb_driver_name, n + 1, musb->config->num_eps * 2 - 1, offset, (1 << (musb->config->ram_bits + 2))); @@ -1491,8 +1490,7 @@ static int musb_core_init(u16 musb_type, struct musb *musb) if (reg & MUSB_CONFIGDATA_SOFTCONE) strcat(aInfo, ", SoftConn"); - printk(KERN_DEBUG "%s: ConfigData=0x%02x (%s)\n", - musb_driver_name, reg, aInfo); + pr_debug("%s: ConfigData=0x%02x (%s)\n", musb_driver_name, reg, aInfo); aDate[0] = 0; if (MUSB_CONTROLLER_MHDRC == musb_type) { @@ -1502,9 +1500,8 @@ static int musb_core_init(u16 musb_type, struct musb *musb) musb->is_multipoint = 0; type = ""; #ifndef CONFIG_USB_OTG_BLACKLIST_HUB - printk(KERN_ERR - "%s: kernel must blacklist external hubs\n", - musb_driver_name); + pr_err("%s: kernel must blacklist external hubs\n", + musb_driver_name); #endif } @@ -1513,8 +1510,8 @@ static int musb_core_init(u16 musb_type, struct musb *musb) snprintf(aRevision, 32, "%d.%d%s", MUSB_HWVERS_MAJOR(musb->hwvers), MUSB_HWVERS_MINOR(musb->hwvers), (musb->hwvers & MUSB_HWVERS_RC) ? "RC" : ""); - printk(KERN_DEBUG "%s: %sHDRC RTL version %s %s\n", - musb_driver_name, type, aRevision, aDate); + pr_debug("%s: %sHDRC RTL version %s %s\n", + musb_driver_name, type, aRevision, aDate); /* configure ep0 */ musb_configure_ep0(musb); @@ -1705,6 +1702,23 @@ EXPORT_SYMBOL_GPL(musb_dma_completion); #define use_dma 0 #endif +static void (*musb_phy_callback)(enum musb_vbus_id_status status); + +/* + * musb_mailbox - optional phy notifier function + * @status phy state change + * + * Optionally gets called from the USB PHY. Note that the USB PHY must be + * disabled at the point the phy_callback is registered or unregistered. + */ +void musb_mailbox(enum musb_vbus_id_status status) +{ + if (musb_phy_callback) + musb_phy_callback(status); + +}; +EXPORT_SYMBOL_GPL(musb_mailbox); + /*-------------------------------------------------------------------------*/ static ssize_t @@ -2117,8 +2131,15 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl) musb->xceiv->io_ops = &musb_ulpi_access; } + if (musb->ops->phy_callback) + musb_phy_callback = musb->ops->phy_callback; + pm_runtime_get_sync(musb->controller); + status = usb_phy_init(musb->xceiv); + if (status < 0) + goto err_usb_phy_init; + if (use_dma && dev->dma_mask) { musb->dma_controller = musb_dma_controller_create(musb, musb->mregs); @@ -2239,7 +2260,11 @@ fail3: cancel_delayed_work_sync(&musb->deassert_reset_work); if (musb->dma_controller) musb_dma_controller_destroy(musb->dma_controller); + fail2_5: + usb_phy_shutdown(musb->xceiv); + +err_usb_phy_init: pm_runtime_put_sync(musb->controller); fail2: @@ -2295,10 +2320,13 @@ static int musb_remove(struct platform_device *pdev) */ musb_exit_debugfs(musb); musb_shutdown(pdev); + musb_phy_callback = NULL; if (musb->dma_controller) musb_dma_controller_destroy(musb->dma_controller); + usb_phy_shutdown(musb->xceiv); + cancel_work_sync(&musb->irq_work); cancel_delayed_work_sync(&musb->finish_resume_work); cancel_delayed_work_sync(&musb->deassert_reset_work); diff --git a/drivers/usb/musb/musb_core.h b/drivers/usb/musb/musb_core.h index 2337d7a7d62d..fd215fb45fd4 100644 --- a/drivers/usb/musb/musb_core.h +++ b/drivers/usb/musb/musb_core.h @@ -168,6 +168,7 @@ struct musb_io; * @adjust_channel_params: pre check for standard dma channel_program func * @pre_root_reset_end: called before the root usb port reset flag gets cleared * @post_root_reset_end: called after the root usb port reset flag gets cleared + * @phy_callback: optional callback function for the phy to call */ struct musb_platform_ops { @@ -214,6 +215,7 @@ struct musb_platform_ops { dma_addr_t *dma_addr, u32 *len); void (*pre_root_reset_end)(struct musb *musb); void (*post_root_reset_end)(struct musb *musb); + void (*phy_callback)(enum musb_vbus_id_status status); }; /* diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c index 67ad630c86c9..87bd578799a8 100644 --- a/drivers/usb/musb/musb_gadget.c +++ b/drivers/usb/musb/musb_gadget.c @@ -353,9 +353,8 @@ static void txstate(struct musb *musb, struct musb_request *req) * 1 >0 Yes(FS bulk) */ if (!musb_ep->hb_mult || - (musb_ep->hb_mult && - can_bulk_split(musb, - musb_ep->type))) + can_bulk_split(musb, + musb_ep->type)) csr |= MUSB_TXCSR_AUTOSET; } csr &= ~MUSB_TXCSR_P_UNDERRUN; diff --git a/drivers/usb/musb/omap2430.c b/drivers/usb/musb/omap2430.c index 1bd9232ff76f..c84e0322c108 100644 --- a/drivers/usb/musb/omap2430.c +++ b/drivers/usb/musb/omap2430.c @@ -36,7 +36,7 @@ #include #include #include -#include +#include #include #include @@ -46,7 +46,7 @@ struct omap2430_glue { struct device *dev; struct platform_device *musb; - enum omap_musb_vbus_id_status status; + enum musb_vbus_id_status status; struct work_struct omap_musb_mailbox_work; struct device *control_otghs; }; @@ -234,7 +234,7 @@ static inline void omap2430_low_level_init(struct musb *musb) musb_writel(musb->mregs, OTG_FORCESTDBY, l); } -void omap_musb_mailbox(enum omap_musb_vbus_id_status status) +static void omap2430_musb_mailbox(enum musb_vbus_id_status status) { struct omap2430_glue *glue = _glue; @@ -251,7 +251,6 @@ void omap_musb_mailbox(enum omap_musb_vbus_id_status status) schedule_work(&glue->omap_musb_mailbox_work); } -EXPORT_SYMBOL_GPL(omap_musb_mailbox); static void omap_musb_set_mailbox(struct omap2430_glue *glue) { @@ -262,7 +261,7 @@ static void omap_musb_set_mailbox(struct omap2430_glue *glue) struct usb_otg *otg = musb->xceiv->otg; switch (glue->status) { - case OMAP_MUSB_ID_GROUND: + case MUSB_ID_GROUND: dev_dbg(dev, "ID GND\n"); otg->default_a = true; @@ -276,7 +275,7 @@ static void omap_musb_set_mailbox(struct omap2430_glue *glue) } break; - case OMAP_MUSB_VBUS_VALID: + case MUSB_VBUS_VALID: dev_dbg(dev, "VBUS Connect\n"); otg->default_a = false; @@ -287,8 +286,8 @@ static void omap_musb_set_mailbox(struct omap2430_glue *glue) omap_control_usb_set_mode(glue->control_otghs, USB_MODE_DEVICE); break; - case OMAP_MUSB_ID_FLOAT: - case OMAP_MUSB_VBUS_OFF: + case MUSB_ID_FLOAT: + case MUSB_VBUS_OFF: dev_dbg(dev, "VBUS Disconnect\n"); musb->xceiv->last_event = USB_EVENT_NONE; @@ -430,7 +429,7 @@ static int omap2430_musb_init(struct musb *musb) setup_timer(&musb_idle_timer, musb_do_idle, (unsigned long) musb); - if (glue->status != OMAP_MUSB_UNKNOWN) + if (glue->status != MUSB_UNKNOWN) omap_musb_set_mailbox(glue); phy_init(musb->phy); @@ -455,7 +454,7 @@ static void omap2430_musb_enable(struct musb *musb) switch (glue->status) { - case OMAP_MUSB_ID_GROUND: + case MUSB_ID_GROUND: omap_control_usb_set_mode(glue->control_otghs, USB_MODE_HOST); if (data->interface_type != MUSB_INTERFACE_UTMI) break; @@ -474,7 +473,7 @@ static void omap2430_musb_enable(struct musb *musb) } break; - case OMAP_MUSB_VBUS_VALID: + case MUSB_VBUS_VALID: omap_control_usb_set_mode(glue->control_otghs, USB_MODE_DEVICE); break; @@ -488,7 +487,7 @@ static void omap2430_musb_disable(struct musb *musb) struct device *dev = musb->controller; struct omap2430_glue *glue = dev_get_drvdata(dev->parent); - if (glue->status != OMAP_MUSB_UNKNOWN) + if (glue->status != MUSB_UNKNOWN) omap_control_usb_set_mode(glue->control_otghs, USB_MODE_DISCONNECT); } @@ -520,6 +519,8 @@ static const struct musb_platform_ops omap2430_ops = { .enable = omap2430_musb_enable, .disable = omap2430_musb_disable, + + .phy_callback = omap2430_musb_mailbox, }; static u64 omap2430_dmamask = DMA_BIT_MASK(32); @@ -551,7 +552,7 @@ static int omap2430_probe(struct platform_device *pdev) glue->dev = &pdev->dev; glue->musb = musb; - glue->status = OMAP_MUSB_UNKNOWN; + glue->status = MUSB_UNKNOWN; glue->control_otghs = ERR_PTR(-ENODEV); if (np) { @@ -663,8 +664,11 @@ static int omap2430_remove(struct platform_device *pdev) { struct omap2430_glue *glue = platform_get_drvdata(pdev); + pm_runtime_get_sync(glue->dev); cancel_work_sync(&glue->omap_musb_mailbox_work); platform_device_unregister(glue->musb); + pm_runtime_put_sync(glue->dev); + pm_runtime_disable(glue->dev); return 0; } diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 22e8ecb6bfbd..c6904742e2aa 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -66,6 +66,7 @@ config AM335X_PHY_USB select USB_PHY select AM335X_CONTROL_USB select NOP_USB_XCEIV + select USB_COMMON help This driver provides PHY support for that phy which part for the AM335x SoC. @@ -186,19 +187,6 @@ config USB_MXS_PHY MXS Phy is used by some of the i.MX SoCs, for example imx23/28/6x. -config USB_RCAR_PHY - tristate "Renesas R-Car USB PHY support" - depends on USB || USB_GADGET - depends on ARCH_R8A7778 || ARCH_R8A7779 || COMPILE_TEST - select USB_PHY - help - Say Y here to add support for the Renesas R-Car USB common PHY driver. - This chip is typically used as USB PHY for USB host, gadget. - This driver supports R8A7778 and R8A7779. - - To compile this driver as a module, choose M here: the - module will be called phy-rcar-usb. - config USB_ULPI bool "Generic ULPI Transceiver Driver" depends on ARM || ARM64 diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile index 19c0dccbb116..b433e5d89be4 100644 --- a/drivers/usb/phy/Makefile +++ b/drivers/usb/phy/Makefile @@ -23,7 +23,6 @@ obj-$(CONFIG_USB_MSM_OTG) += phy-msm-usb.o obj-$(CONFIG_USB_QCOM_8X16_PHY) += phy-qcom-8x16-usb.o obj-$(CONFIG_USB_MV_OTG) += phy-mv-usb.o obj-$(CONFIG_USB_MXS_PHY) += phy-mxs-usb.o -obj-$(CONFIG_USB_RCAR_PHY) += phy-rcar-usb.o obj-$(CONFIG_USB_ULPI) += phy-ulpi.o obj-$(CONFIG_USB_ULPI_VIEWPORT) += phy-ulpi-viewport.o obj-$(CONFIG_KEYSTONE_USB_PHY) += phy-keystone.o diff --git a/drivers/usb/phy/phy-am335x-control.c b/drivers/usb/phy/phy-am335x-control.c index 7b3035ff9434..42a1afe36a90 100644 --- a/drivers/usb/phy/phy-am335x-control.c +++ b/drivers/usb/phy/phy-am335x-control.c @@ -4,7 +4,8 @@ #include #include #include -#include "am35x-phy-control.h" +#include +#include "phy-am335x-control.h" struct am335x_control_usb { struct device *dev; @@ -58,7 +59,8 @@ static void am335x_phy_wkup(struct phy_control *phy_ctrl, u32 id, bool on) spin_unlock(&usb_ctrl->lock); } -static void am335x_phy_power(struct phy_control *phy_ctrl, u32 id, bool on) +static void am335x_phy_power(struct phy_control *phy_ctrl, u32 id, + enum usb_dr_mode dr_mode, bool on) { struct am335x_control_usb *usb_ctrl; u32 val; @@ -80,8 +82,14 @@ static void am335x_phy_power(struct phy_control *phy_ctrl, u32 id, bool on) val = readl(usb_ctrl->phy_reg + reg); if (on) { - val &= ~(USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN); - val |= USBPHY_OTGVDET_EN | USBPHY_OTGSESSEND_EN; + if (dr_mode == USB_DR_MODE_HOST) { + val &= ~(USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN | + USBPHY_OTGVDET_EN); + val |= USBPHY_OTGSESSEND_EN; + } else { + val &= ~(USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN); + val |= USBPHY_OTGVDET_EN | USBPHY_OTGSESSEND_EN; + } } else { val |= USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN; } diff --git a/drivers/usb/phy/am35x-phy-control.h b/drivers/usb/phy/phy-am335x-control.h similarity index 68% rename from drivers/usb/phy/am35x-phy-control.h rename to drivers/usb/phy/phy-am335x-control.h index b96594d1962c..e86b3165d69d 100644 --- a/drivers/usb/phy/am35x-phy-control.h +++ b/drivers/usb/phy/phy-am335x-control.h @@ -2,13 +2,15 @@ #define _AM335x_PHY_CONTROL_H_ struct phy_control { - void (*phy_power)(struct phy_control *phy_ctrl, u32 id, bool on); + void (*phy_power)(struct phy_control *phy_ctrl, u32 id, + enum usb_dr_mode dr_mode, bool on); void (*phy_wkup)(struct phy_control *phy_ctrl, u32 id, bool on); }; -static inline void phy_ctrl_power(struct phy_control *phy_ctrl, u32 id, bool on) +static inline void phy_ctrl_power(struct phy_control *phy_ctrl, u32 id, + enum usb_dr_mode dr_mode, bool on) { - phy_ctrl->phy_power(phy_ctrl, id, on); + phy_ctrl->phy_power(phy_ctrl, id, dr_mode, on); } static inline void phy_ctrl_wkup(struct phy_control *phy_ctrl, u32 id, bool on) diff --git a/drivers/usb/phy/phy-am335x.c b/drivers/usb/phy/phy-am335x.c index 90b67a4ca221..39b424f7f629 100644 --- a/drivers/usb/phy/phy-am335x.c +++ b/drivers/usb/phy/phy-am335x.c @@ -8,21 +8,23 @@ #include #include #include +#include -#include "am35x-phy-control.h" +#include "phy-am335x-control.h" #include "phy-generic.h" struct am335x_phy { struct usb_phy_generic usb_phy_gen; struct phy_control *phy_ctrl; int id; + enum usb_dr_mode dr_mode; }; static int am335x_init(struct usb_phy *phy) { struct am335x_phy *am_phy = dev_get_drvdata(phy->dev); - phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, true); + phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, am_phy->dr_mode, true); return 0; } @@ -30,7 +32,7 @@ static void am335x_shutdown(struct usb_phy *phy) { struct am335x_phy *am_phy = dev_get_drvdata(phy->dev); - phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, false); + phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, am_phy->dr_mode, false); } static int am335x_phy_probe(struct platform_device *pdev) @@ -46,12 +48,15 @@ static int am335x_phy_probe(struct platform_device *pdev) am_phy->phy_ctrl = am335x_get_phy_control(dev); if (!am_phy->phy_ctrl) return -EPROBE_DEFER; + am_phy->id = of_alias_get_id(pdev->dev.of_node, "phy"); if (am_phy->id < 0) { dev_err(&pdev->dev, "Missing PHY id: %d\n", am_phy->id); return am_phy->id; } + am_phy->dr_mode = of_usb_get_dr_mode_by_phy(pdev->dev.of_node); + ret = usb_phy_gen_create_phy(dev, &am_phy->usb_phy_gen, NULL); if (ret) return ret; @@ -75,7 +80,7 @@ static int am335x_phy_probe(struct platform_device *pdev) */ device_set_wakeup_enable(dev, false); - phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, false); + phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, am_phy->dr_mode, false); return 0; } @@ -105,7 +110,7 @@ static int am335x_phy_suspend(struct device *dev) if (device_may_wakeup(dev)) phy_ctrl_wkup(am_phy->phy_ctrl, am_phy->id, true); - phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, false); + phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, am_phy->dr_mode, false); return 0; } @@ -115,7 +120,7 @@ static int am335x_phy_resume(struct device *dev) struct platform_device *pdev = to_platform_device(dev); struct am335x_phy *am_phy = platform_get_drvdata(pdev); - phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, true); + phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, am_phy->dr_mode, true); if (device_may_wakeup(dev)) phy_ctrl_wkup(am_phy->phy_ctrl, am_phy->id, false); diff --git a/drivers/usb/phy/phy-rcar-usb.c b/drivers/usb/phy/phy-rcar-usb.c deleted file mode 100644 index 1e09b8377885..000000000000 --- a/drivers/usb/phy/phy-rcar-usb.c +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Renesas R-Car USB phy driver - * - * Copyright (C) 2012-2013 Renesas Solutions Corp. - * Kuninori Morimoto - * Copyright (C) 2013 Cogent Embedded, Inc. - * - * 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 -#include -#include -#include -#include -#include -#include - -/* REGS block */ -#define USBPCTRL0 0x00 -#define USBPCTRL1 0x04 -#define USBST 0x08 -#define USBEH0 0x0C -#define USBOH0 0x1C -#define USBCTL0 0x58 - -/* High-speed signal quality characteristic control registers (R8A7778 only) */ -#define HSQCTL1 0x24 -#define HSQCTL2 0x28 - -/* USBPCTRL0 */ -#define OVC2 (1 << 10) /* (R8A7779 only) */ - /* Switches the OVC input pin for port 2: */ - /* 1: USB_OVC2, 0: OVC2 */ -#define OVC1_VBUS1 (1 << 9) /* Switches the OVC input pin for port 1: */ - /* 1: USB_OVC1, 0: OVC1/VBUS1 */ - /* Function mode: set to 0 */ -#define OVC0 (1 << 8) /* Switches the OVC input pin for port 0: */ - /* 1: USB_OVC0 pin, 0: OVC0 */ -#define OVC2_ACT (1 << 6) /* (R8A7779 only) */ - /* Host mode: OVC2 polarity: */ - /* 1: active-high, 0: active-low */ -#define PENC (1 << 4) /* Function mode: output level of PENC1 pin: */ - /* 1: high, 0: low */ -#define OVC0_ACT (1 << 3) /* Host mode: OVC0 polarity: */ - /* 1: active-high, 0: active-low */ -#define OVC1_ACT (1 << 1) /* Host mode: OVC1 polarity: */ - /* 1: active-high, 0: active-low */ - /* Function mode: be sure to set to 1 */ -#define PORT1 (1 << 0) /* Selects port 1 mode: */ - /* 1: function, 0: host */ -/* USBPCTRL1 */ -#define PHY_RST (1 << 2) -#define PLL_ENB (1 << 1) -#define PHY_ENB (1 << 0) - -/* USBST */ -#define ST_ACT (1 << 31) -#define ST_PLL (1 << 30) - -struct rcar_usb_phy_priv { - struct usb_phy phy; - spinlock_t lock; - - void __iomem *reg0; - void __iomem *reg1; - int counter; -}; - -#define usb_phy_to_priv(p) container_of(p, struct rcar_usb_phy_priv, phy) - - -/* - * USB initial/install operation. - * - * This function setup USB phy. - * The used value and setting order came from - * [USB :: Initial setting] on datasheet. - */ -static int rcar_usb_phy_init(struct usb_phy *phy) -{ - struct rcar_usb_phy_priv *priv = usb_phy_to_priv(phy); - struct device *dev = phy->dev; - struct rcar_phy_platform_data *pdata = dev_get_platdata(dev); - void __iomem *reg0 = priv->reg0; - void __iomem *reg1 = priv->reg1; - static const u8 ovcn_act[] = { OVC0_ACT, OVC1_ACT, OVC2_ACT }; - int i; - u32 val; - unsigned long flags; - - spin_lock_irqsave(&priv->lock, flags); - if (priv->counter++ == 0) { - - /* - * USB phy start-up - */ - - /* (1) USB-PHY standby release */ - iowrite32(PHY_ENB, (reg0 + USBPCTRL1)); - - /* (2) start USB-PHY internal PLL */ - iowrite32(PHY_ENB | PLL_ENB, (reg0 + USBPCTRL1)); - - /* (3) set USB-PHY in accord with the conditions of usage */ - if (reg1) { - u32 hsqctl1 = pdata->ferrite_bead ? 0x41 : 0; - u32 hsqctl2 = pdata->ferrite_bead ? 0x0d : 7; - - iowrite32(hsqctl1, reg1 + HSQCTL1); - iowrite32(hsqctl2, reg1 + HSQCTL2); - } - - /* (4) USB module status check */ - for (i = 0; i < 1024; i++) { - udelay(10); - val = ioread32(reg0 + USBST); - if (val == (ST_ACT | ST_PLL)) - break; - } - - if (val != (ST_ACT | ST_PLL)) { - dev_err(dev, "USB phy not ready\n"); - goto phy_init_end; - } - - /* (5) USB-PHY reset clear */ - iowrite32(PHY_ENB | PLL_ENB | PHY_RST, (reg0 + USBPCTRL1)); - - /* Board specific port settings */ - val = 0; - if (pdata->port1_func) - val |= PORT1; - if (pdata->penc1) - val |= PENC; - for (i = 0; i < 3; i++) { - /* OVCn bits follow each other in the right order */ - if (pdata->ovc_pin[i].select_3_3v) - val |= OVC0 << i; - /* OVCn_ACT bits are spaced by irregular intervals */ - if (pdata->ovc_pin[i].active_high) - val |= ovcn_act[i]; - } - iowrite32(val, (reg0 + USBPCTRL0)); - - /* - * Bus alignment settings - */ - - /* (1) EHCI bus alignment (little endian) */ - iowrite32(0x00000000, (reg0 + USBEH0)); - - /* (1) OHCI bus alignment (little endian) */ - iowrite32(0x00000000, (reg0 + USBOH0)); - } - -phy_init_end: - spin_unlock_irqrestore(&priv->lock, flags); - - return 0; -} - -static void rcar_usb_phy_shutdown(struct usb_phy *phy) -{ - struct rcar_usb_phy_priv *priv = usb_phy_to_priv(phy); - void __iomem *reg0 = priv->reg0; - unsigned long flags; - - spin_lock_irqsave(&priv->lock, flags); - - if (priv->counter-- == 1) /* last user */ - iowrite32(0x00000000, (reg0 + USBPCTRL1)); - - spin_unlock_irqrestore(&priv->lock, flags); -} - -static int rcar_usb_phy_probe(struct platform_device *pdev) -{ - struct rcar_usb_phy_priv *priv; - struct resource *res0, *res1; - struct device *dev = &pdev->dev; - void __iomem *reg0, *reg1 = NULL; - int ret; - - if (!dev_get_platdata(&pdev->dev)) { - dev_err(dev, "No platform data\n"); - return -EINVAL; - } - - res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0); - reg0 = devm_ioremap_resource(dev, res0); - if (IS_ERR(reg0)) - return PTR_ERR(reg0); - - res1 = platform_get_resource(pdev, IORESOURCE_MEM, 1); - reg1 = devm_ioremap_resource(dev, res1); - if (IS_ERR(reg1)) - return PTR_ERR(reg1); - - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->reg0 = reg0; - priv->reg1 = reg1; - priv->counter = 0; - priv->phy.dev = dev; - priv->phy.label = dev_name(dev); - priv->phy.init = rcar_usb_phy_init; - priv->phy.shutdown = rcar_usb_phy_shutdown; - spin_lock_init(&priv->lock); - - ret = usb_add_phy(&priv->phy, USB_PHY_TYPE_USB2); - if (ret < 0) { - dev_err(dev, "usb phy addition error\n"); - return ret; - } - - platform_set_drvdata(pdev, priv); - - return ret; -} - -static int rcar_usb_phy_remove(struct platform_device *pdev) -{ - struct rcar_usb_phy_priv *priv = platform_get_drvdata(pdev); - - usb_remove_phy(&priv->phy); - - return 0; -} - -static struct platform_driver rcar_usb_phy_driver = { - .driver = { - .name = "rcar_usb_phy", - }, - .probe = rcar_usb_phy_probe, - .remove = rcar_usb_phy_remove, -}; - -module_platform_driver(rcar_usb_phy_driver); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Renesas R-Car USB phy"); -MODULE_AUTHOR("Kuninori Morimoto "); diff --git a/drivers/usb/phy/phy-twl6030-usb.c b/drivers/usb/phy/phy-twl6030-usb.c index 12741856a75c..014dbbd72132 100644 --- a/drivers/usb/phy/phy-twl6030-usb.c +++ b/drivers/usb/phy/phy-twl6030-usb.c @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #include #include @@ -102,7 +102,7 @@ struct twl6030_usb { int irq1; int irq2; - enum omap_musb_vbus_id_status linkstat; + enum musb_vbus_id_status linkstat; u8 asleep; bool vbus_enable; const char *regulator; @@ -189,13 +189,13 @@ static ssize_t twl6030_usb_vbus_show(struct device *dev, spin_lock_irqsave(&twl->lock, flags); switch (twl->linkstat) { - case OMAP_MUSB_VBUS_VALID: + case MUSB_VBUS_VALID: ret = snprintf(buf, PAGE_SIZE, "vbus\n"); break; - case OMAP_MUSB_ID_GROUND: + case MUSB_ID_GROUND: ret = snprintf(buf, PAGE_SIZE, "id\n"); break; - case OMAP_MUSB_VBUS_OFF: + case MUSB_VBUS_OFF: ret = snprintf(buf, PAGE_SIZE, "none\n"); break; default: @@ -210,7 +210,7 @@ static DEVICE_ATTR(vbus, 0444, twl6030_usb_vbus_show, NULL); static irqreturn_t twl6030_usb_irq(int irq, void *_twl) { struct twl6030_usb *twl = _twl; - enum omap_musb_vbus_id_status status = OMAP_MUSB_UNKNOWN; + enum musb_vbus_id_status status = MUSB_UNKNOWN; u8 vbus_state, hw_state; int ret; @@ -225,14 +225,14 @@ static irqreturn_t twl6030_usb_irq(int irq, void *_twl) dev_err(twl->dev, "Failed to enable usb3v3\n"); twl->asleep = 1; - status = OMAP_MUSB_VBUS_VALID; + status = MUSB_VBUS_VALID; twl->linkstat = status; - omap_musb_mailbox(status); + musb_mailbox(status); } else { - if (twl->linkstat != OMAP_MUSB_UNKNOWN) { - status = OMAP_MUSB_VBUS_OFF; + if (twl->linkstat != MUSB_UNKNOWN) { + status = MUSB_VBUS_OFF; twl->linkstat = status; - omap_musb_mailbox(status); + musb_mailbox(status); if (twl->asleep) { regulator_disable(twl->usb3v3); twl->asleep = 0; @@ -248,7 +248,7 @@ static irqreturn_t twl6030_usb_irq(int irq, void *_twl) static irqreturn_t twl6030_usbotg_irq(int irq, void *_twl) { struct twl6030_usb *twl = _twl; - enum omap_musb_vbus_id_status status = OMAP_MUSB_UNKNOWN; + enum musb_vbus_id_status status = MUSB_UNKNOWN; u8 hw_state; int ret; @@ -262,9 +262,9 @@ static irqreturn_t twl6030_usbotg_irq(int irq, void *_twl) twl->asleep = 1; twl6030_writeb(twl, TWL_MODULE_USB, 0x1, USB_ID_INT_EN_HI_CLR); twl6030_writeb(twl, TWL_MODULE_USB, 0x10, USB_ID_INT_EN_HI_SET); - status = OMAP_MUSB_ID_GROUND; + status = MUSB_ID_GROUND; twl->linkstat = status; - omap_musb_mailbox(status); + musb_mailbox(status); } else { twl6030_writeb(twl, TWL_MODULE_USB, 0x10, USB_ID_INT_EN_HI_CLR); twl6030_writeb(twl, TWL_MODULE_USB, 0x1, USB_ID_INT_EN_HI_SET); @@ -334,7 +334,7 @@ static int twl6030_usb_probe(struct platform_device *pdev) twl->dev = &pdev->dev; twl->irq1 = platform_get_irq(pdev, 0); twl->irq2 = platform_get_irq(pdev, 1); - twl->linkstat = OMAP_MUSB_UNKNOWN; + twl->linkstat = MUSB_UNKNOWN; twl->comparator.set_vbus = twl6030_set_vbus; twl->comparator.start_srp = twl6030_start_srp; diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index d82fa36c3465..5af9ca5d54ab 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -302,37 +302,37 @@ static void usbhsc_set_buswait(struct usbhs_priv *priv) */ /* commonly used on old SH-Mobile SoCs */ -static u32 usbhsc_default_pipe_type[] = { - USB_ENDPOINT_XFER_CONTROL, - USB_ENDPOINT_XFER_ISOC, - USB_ENDPOINT_XFER_ISOC, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_INT, - USB_ENDPOINT_XFER_INT, - USB_ENDPOINT_XFER_INT, - USB_ENDPOINT_XFER_INT, +static struct renesas_usbhs_driver_pipe_config usbhsc_default_pipe[] = { + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_CONTROL, 64, 0x00, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_ISOC, 1024, 0x08, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_ISOC, 1024, 0x18, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x28, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x38, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x48, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x04, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x05, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x06, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x07, false), }; /* commonly used on newer SH-Mobile and R-Car SoCs */ -static u32 usbhsc_new_pipe_type[] = { - USB_ENDPOINT_XFER_CONTROL, - USB_ENDPOINT_XFER_ISOC, - USB_ENDPOINT_XFER_ISOC, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_INT, - USB_ENDPOINT_XFER_INT, - USB_ENDPOINT_XFER_INT, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, +static struct renesas_usbhs_driver_pipe_config usbhsc_new_pipe[] = { + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_CONTROL, 64, 0x00, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_ISOC, 1024, 0x08, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_ISOC, 1024, 0x28, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x48, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x58, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x68, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x04, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x05, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x06, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x78, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x88, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x98, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0xa8, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0xb8, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0xc8, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0xd8, true), }; /* @@ -481,6 +481,15 @@ static const struct of_device_id usbhs_of_match[] = { .compatible = "renesas,usbhs-r8a7795", .data = (void *)USBHS_TYPE_RCAR_GEN2, }, + { + .compatible = "renesas,rcar-gen2-usbhs", + .data = (void *)USBHS_TYPE_RCAR_GEN2, + }, + { + /* Gen3 is compatible with Gen2 */ + .compatible = "renesas,rcar-gen3-usbhs", + .data = (void *)USBHS_TYPE_RCAR_GEN2, + }, { }, }; MODULE_DEVICE_TABLE(of, usbhs_of_match); @@ -564,10 +573,9 @@ static int usbhs_probe(struct platform_device *pdev) switch (priv->dparam.type) { case USBHS_TYPE_RCAR_GEN2: priv->pfunc = usbhs_rcar2_ops; - if (!priv->dparam.pipe_type) { - priv->dparam.pipe_type = usbhsc_new_pipe_type; - priv->dparam.pipe_size = - ARRAY_SIZE(usbhsc_new_pipe_type); + if (!priv->dparam.pipe_configs) { + priv->dparam.pipe_configs = usbhsc_new_pipe; + priv->dparam.pipe_size = ARRAY_SIZE(usbhsc_new_pipe); } break; default: @@ -586,9 +594,9 @@ static int usbhs_probe(struct platform_device *pdev) dfunc->notify_hotplug = usbhsc_drvcllbck_notify_hotplug; /* set default param if platform doesn't have */ - if (!priv->dparam.pipe_type) { - priv->dparam.pipe_type = usbhsc_default_pipe_type; - priv->dparam.pipe_size = ARRAY_SIZE(usbhsc_default_pipe_type); + if (!priv->dparam.pipe_configs) { + priv->dparam.pipe_configs = usbhsc_default_pipe; + priv->dparam.pipe_size = ARRAY_SIZE(usbhsc_default_pipe); } if (!priv->dparam.pio_dma_border) priv->dparam.pio_dma_border = 64; /* 64byte */ diff --git a/drivers/usb/renesas_usbhs/mod_gadget.c b/drivers/usb/renesas_usbhs/mod_gadget.c index 8f7a78e70975..657f9672ceba 100644 --- a/drivers/usb/renesas_usbhs/mod_gadget.c +++ b/drivers/usb/renesas_usbhs/mod_gadget.c @@ -1042,6 +1042,8 @@ int usbhs_mod_gadget_probe(struct usbhs_priv *priv) struct usbhsg_gpriv *gpriv; struct usbhsg_uep *uep; struct device *dev = usbhs_priv_to_dev(priv); + struct renesas_usbhs_driver_pipe_config *pipe_configs = + usbhs_get_dparam(priv, pipe_configs); int pipe_size = usbhs_get_dparam(priv, pipe_size); int i; int ret; @@ -1111,13 +1113,16 @@ int usbhs_mod_gadget_probe(struct usbhs_priv *priv) gpriv->gadget.ep0 = &uep->ep; usb_ep_set_maxpacket_limit(&uep->ep, 64); uep->ep.caps.type_control = true; - } - /* init normal pipe */ - else { - usb_ep_set_maxpacket_limit(&uep->ep, 512); - uep->ep.caps.type_iso = true; - uep->ep.caps.type_bulk = true; - uep->ep.caps.type_int = true; + } else { + /* init normal pipe */ + if (pipe_configs[i].type == USB_ENDPOINT_XFER_ISOC) + uep->ep.caps.type_iso = true; + if (pipe_configs[i].type == USB_ENDPOINT_XFER_BULK) + uep->ep.caps.type_bulk = true; + if (pipe_configs[i].type == USB_ENDPOINT_XFER_INT) + uep->ep.caps.type_int = true; + usb_ep_set_maxpacket_limit(&uep->ep, + pipe_configs[i].bufsize); list_add_tail(&uep->ep.ep_list, &gpriv->gadget.ep_list); } uep->ep.caps.dir_in = true; diff --git a/drivers/usb/renesas_usbhs/mod_host.c b/drivers/usb/renesas_usbhs/mod_host.c index bd050359926c..1a8e4c45c4c5 100644 --- a/drivers/usb/renesas_usbhs/mod_host.c +++ b/drivers/usb/renesas_usbhs/mod_host.c @@ -1414,7 +1414,8 @@ static void usbhsh_pipe_init_for_host(struct usbhs_priv *priv) { struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv); struct usbhs_pipe *pipe; - u32 *pipe_type = usbhs_get_dparam(priv, pipe_type); + struct renesas_usbhs_driver_pipe_config *pipe_configs = + usbhs_get_dparam(priv, pipe_configs); int pipe_size = usbhs_get_dparam(priv, pipe_size); int old_type, dir_in, i; @@ -1442,15 +1443,15 @@ static void usbhsh_pipe_init_for_host(struct usbhs_priv *priv) * USB_ENDPOINT_XFER_BULK -> dir in * ... */ - dir_in = (pipe_type[i] == old_type); - old_type = pipe_type[i]; + dir_in = (pipe_configs[i].type == old_type); + old_type = pipe_configs[i].type; - if (USB_ENDPOINT_XFER_CONTROL == pipe_type[i]) { + if (USB_ENDPOINT_XFER_CONTROL == pipe_configs[i].type) { pipe = usbhs_dcp_malloc(priv); usbhsh_hpriv_to_dcp(hpriv) = pipe; } else { pipe = usbhs_pipe_malloc(priv, - pipe_type[i], + pipe_configs[i].type, dir_in); } diff --git a/drivers/usb/renesas_usbhs/pipe.c b/drivers/usb/renesas_usbhs/pipe.c index 4f9c3356127a..0e95d2925dc5 100644 --- a/drivers/usb/renesas_usbhs/pipe.c +++ b/drivers/usb/renesas_usbhs/pipe.c @@ -44,6 +44,15 @@ char *usbhs_pipe_name(struct usbhs_pipe *pipe) return usbhsp_pipe_name[usbhs_pipe_type(pipe)]; } +static struct renesas_usbhs_driver_pipe_config +*usbhsp_get_pipe_config(struct usbhs_priv *priv, int pipe_num) +{ + struct renesas_usbhs_driver_pipe_config *pipe_configs = + usbhs_get_dparam(priv, pipe_configs); + + return &pipe_configs[pipe_num]; +} + /* * DCPCTR/PIPEnCTR functions */ @@ -384,18 +393,6 @@ void usbhs_pipe_set_trans_count_if_bulk(struct usbhs_pipe *pipe, int len) /* * pipe setup */ -static int usbhsp_possible_double_buffer(struct usbhs_pipe *pipe) -{ - /* - * only ISO / BULK pipe can use double buffer - */ - if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK) || - usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_ISOC)) - return 1; - - return 0; -} - static u16 usbhsp_setup_pipecfg(struct usbhs_pipe *pipe, int is_host, int dir_in) @@ -412,7 +409,6 @@ static u16 usbhsp_setup_pipecfg(struct usbhs_pipe *pipe, [USB_ENDPOINT_XFER_INT] = TYPE_INT, [USB_ENDPOINT_XFER_ISOC] = TYPE_ISO, }; - int is_double = usbhsp_possible_double_buffer(pipe); if (usbhs_pipe_is_dcp(pipe)) return -EINVAL; @@ -434,10 +430,7 @@ static u16 usbhsp_setup_pipecfg(struct usbhs_pipe *pipe, usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK)) bfre = 0; /* FIXME */ - /* DBLB */ - if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_ISOC) || - usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK)) - dblb = (is_double) ? DBLB : 0; + /* DBLB: see usbhs_pipe_config_update() */ /* CNTMD */ if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK)) @@ -473,13 +466,13 @@ static u16 usbhsp_setup_pipecfg(struct usbhs_pipe *pipe, static u16 usbhsp_setup_pipebuff(struct usbhs_pipe *pipe) { struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); - struct usbhs_pipe_info *info = usbhs_priv_to_pipeinfo(priv); struct device *dev = usbhs_priv_to_dev(priv); int pipe_num = usbhs_pipe_number(pipe); - int is_double = usbhsp_possible_double_buffer(pipe); u16 buff_size; u16 bufnmb; u16 bufnmb_cnt; + struct renesas_usbhs_driver_pipe_config *pipe_config = + usbhsp_get_pipe_config(priv, pipe_num); /* * PIPEBUF @@ -489,56 +482,13 @@ static u16 usbhsp_setup_pipebuff(struct usbhs_pipe *pipe) * - "Features" - "Pipe configuration" * - "Operation" - "FIFO Buffer Memory" * - "Operation" - "Pipe Control" - * - * ex) if pipe6 - pipe9 are USB_ENDPOINT_XFER_INT (SH7724) - * - * BUFNMB: PIPE - * 0: pipe0 (DCP 256byte) - * 1: - - * 2: - - * 3: - - * 4: pipe6 (INT 64byte) - * 5: pipe7 (INT 64byte) - * 6: pipe8 (INT 64byte) - * 7: pipe9 (INT 64byte) - * 8 - xx: free (for BULK, ISOC) */ - - /* - * FIXME - * - * it doesn't have good buffer allocator - * - * DCP : 256 byte - * BULK: 512 byte - * INT : 64 byte - * ISOC: 512 byte - */ - if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_CONTROL)) - buff_size = 256; - else if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_INT)) - buff_size = 64; - else - buff_size = 512; + buff_size = pipe_config->bufsize; + bufnmb = pipe_config->bufnum; /* change buff_size to register value */ bufnmb_cnt = (buff_size / 64) - 1; - /* BUFNMB has been reserved for INT pipe - * see above */ - if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_INT)) { - bufnmb = pipe_num - 2; - } else { - bufnmb = info->bufnmb_last; - info->bufnmb_last += bufnmb_cnt + 1; - - /* - * double buffer - */ - if (is_double) - info->bufnmb_last += bufnmb_cnt + 1; - } - dev_dbg(dev, "pipe : %d : buff_size 0x%x: bufnmb 0x%x\n", pipe_num, buff_size, bufnmb); @@ -549,8 +499,13 @@ static u16 usbhsp_setup_pipebuff(struct usbhs_pipe *pipe) void usbhs_pipe_config_update(struct usbhs_pipe *pipe, u16 devsel, u16 epnum, u16 maxp) { + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + int pipe_num = usbhs_pipe_number(pipe); + struct renesas_usbhs_driver_pipe_config *pipe_config = + usbhsp_get_pipe_config(priv, pipe_num); + u16 dblb = pipe_config->double_buf ? DBLB : 0; + if (devsel > 0xA) { - struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); struct device *dev = usbhs_priv_to_dev(priv); dev_err(dev, "devsel error %d\n", devsel); @@ -568,7 +523,7 @@ void usbhs_pipe_config_update(struct usbhs_pipe *pipe, u16 devsel, maxp); if (!usbhs_pipe_is_dcp(pipe)) - usbhsp_pipe_cfg_set(pipe, 0x000F, epnum); + usbhsp_pipe_cfg_set(pipe, 0x000F | DBLB, epnum | dblb); } /* @@ -708,23 +663,7 @@ void usbhs_pipe_init(struct usbhs_priv *priv, struct usbhs_pipe *pipe; int i; - /* - * FIXME - * - * driver needs good allocator. - * - * find first free buffer area (BULK, ISOC) - * (DCP, INT area is fixed) - * - * buffer number 0 - 3 have been reserved for DCP - * see - * usbhsp_to_bufnmb - */ - info->bufnmb_last = 4; usbhs_for_each_pipe_with_dcp(pipe, priv, i) { - if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_INT)) - info->bufnmb_last++; - usbhsp_flags_init(pipe); pipe->fifo = NULL; pipe->mod_private = NULL; @@ -851,12 +790,13 @@ int usbhs_pipe_probe(struct usbhs_priv *priv) struct usbhs_pipe_info *info = usbhs_priv_to_pipeinfo(priv); struct usbhs_pipe *pipe; struct device *dev = usbhs_priv_to_dev(priv); - u32 *pipe_type = usbhs_get_dparam(priv, pipe_type); + struct renesas_usbhs_driver_pipe_config *pipe_configs = + usbhs_get_dparam(priv, pipe_configs); int pipe_size = usbhs_get_dparam(priv, pipe_size); int i; /* This driver expects 1st pipe is DCP */ - if (pipe_type[0] != USB_ENDPOINT_XFER_CONTROL) { + if (pipe_configs[0].type != USB_ENDPOINT_XFER_CONTROL) { dev_err(dev, "1st PIPE is not DCP\n"); return -EINVAL; } @@ -876,10 +816,10 @@ int usbhs_pipe_probe(struct usbhs_priv *priv) pipe->priv = priv; usbhs_pipe_type(pipe) = - pipe_type[i] & USB_ENDPOINT_XFERTYPE_MASK; + pipe_configs[i].type & USB_ENDPOINT_XFERTYPE_MASK; dev_dbg(dev, "pipe %x\t: %s\n", - i, usbhsp_pipe_name[pipe_type[i]]); + i, usbhsp_pipe_name[pipe_configs[i].type]); } return 0; diff --git a/drivers/usb/renesas_usbhs/pipe.h b/drivers/usb/renesas_usbhs/pipe.h index b0bc7b603016..3212ab51e844 100644 --- a/drivers/usb/renesas_usbhs/pipe.h +++ b/drivers/usb/renesas_usbhs/pipe.h @@ -46,7 +46,6 @@ struct usbhs_pipe { struct usbhs_pipe_info { struct usbhs_pipe *pipe; int size; /* array size of "pipe" */ - int bufnmb_last; /* FIXME : driver needs good allocator */ int (*dma_map_ctrl)(struct usbhs_pkt *pkt, int map); }; diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig index 56ecb8b5115d..f612dda9c977 100644 --- a/drivers/usb/serial/Kconfig +++ b/drivers/usb/serial/Kconfig @@ -475,6 +475,22 @@ config USB_SERIAL_MOS7840 To compile this driver as a module, choose M here: the module will be called mos7840. If unsure, choose N. +config USB_SERIAL_MXUPORT11 + tristate "USB Moxa UPORT 11x0 Serial Driver" + ---help--- + Say Y here if you want to use a MOXA UPort 11x0 Serial hub. + + This driver supports: + + - UPort 1110 : 1 port RS-232 USB to Serial Hub. + - UPort 1130 : 1 port RS-422/485 USB to Serial Hub. + - UPort 1130I : 1 port RS-422/485 USB to Serial Hub with Isolation. + - UPort 1150 : 1 port RS-232/422/485 USB to Serial Hub. + - UPort 1150I : 1 port RS-232/422/485 USB to Serial Hub with Isolation. + + To compile this driver as a module, choose M here: the + module will be called mxu11x0. + config USB_SERIAL_MXUPORT tristate "USB Moxa UPORT Serial Driver" ---help--- diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile index 349d9df0895f..f3fa5e53702d 100644 --- a/drivers/usb/serial/Makefile +++ b/drivers/usb/serial/Makefile @@ -38,6 +38,7 @@ obj-$(CONFIG_USB_SERIAL_METRO) += metro-usb.o obj-$(CONFIG_USB_SERIAL_MOS7720) += mos7720.o obj-$(CONFIG_USB_SERIAL_MOS7840) += mos7840.o obj-$(CONFIG_USB_SERIAL_MXUPORT) += mxuport.o +obj-$(CONFIG_USB_SERIAL_MXUPORT11) += mxu11x0.o obj-$(CONFIG_USB_SERIAL_NAVMAN) += navman.o obj-$(CONFIG_USB_SERIAL_OMNINET) += omninet.o obj-$(CONFIG_USB_SERIAL_OPTICON) += opticon.o diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 7d4f51a32e66..9b90ad747d87 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -38,13 +38,14 @@ static void cp210x_change_speed(struct tty_struct *, struct usb_serial_port *, struct ktermios *); static void cp210x_set_termios(struct tty_struct *, struct usb_serial_port *, struct ktermios*); +static bool cp210x_tx_empty(struct usb_serial_port *port); static int cp210x_tiocmget(struct tty_struct *); static int cp210x_tiocmset(struct tty_struct *, unsigned int, unsigned int); static int cp210x_tiocmset_port(struct usb_serial_port *port, unsigned int, unsigned int); static void cp210x_break_ctl(struct tty_struct *, int); -static int cp210x_startup(struct usb_serial *); -static void cp210x_release(struct usb_serial *); +static int cp210x_port_probe(struct usb_serial_port *); +static int cp210x_port_remove(struct usb_serial_port *); static void cp210x_dtr_rts(struct usb_serial_port *p, int on); static const struct usb_device_id id_table[] = { @@ -160,6 +161,7 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(0x17F4, 0xAAAA) }, /* Wavesense Jazz blood glucose meter */ { USB_DEVICE(0x1843, 0x0200) }, /* Vaisala USB Instrument Cable */ { USB_DEVICE(0x18EF, 0xE00F) }, /* ELV USB-I2C-Interface */ + { USB_DEVICE(0x18EF, 0xE025) }, /* ELV Marble Sound Board 1 */ { USB_DEVICE(0x1ADB, 0x0001) }, /* Schweitzer Engineering C662 Cable */ { USB_DEVICE(0x1B1C, 0x1C00) }, /* Corsair USB Dongle */ { USB_DEVICE(0x1BA4, 0x0002) }, /* Silicon Labs 358x factory default */ @@ -196,8 +198,9 @@ static const struct usb_device_id id_table[] = { MODULE_DEVICE_TABLE(usb, id_table); -struct cp210x_serial_private { +struct cp210x_port_private { __u8 bInterfaceNumber; + bool has_swapped_line_ctl; }; static struct usb_serial_driver cp210x_device = { @@ -213,10 +216,11 @@ static struct usb_serial_driver cp210x_device = { .close = cp210x_close, .break_ctl = cp210x_break_ctl, .set_termios = cp210x_set_termios, + .tx_empty = cp210x_tx_empty, .tiocmget = cp210x_tiocmget, .tiocmset = cp210x_tiocmset, - .attach = cp210x_startup, - .release = cp210x_release, + .port_probe = cp210x_port_probe, + .port_remove = cp210x_port_remove, .dtr_rts = cp210x_dtr_rts }; @@ -299,6 +303,25 @@ static struct usb_serial_driver * const serial_drivers[] = { #define CONTROL_WRITE_DTR 0x0100 #define CONTROL_WRITE_RTS 0x0200 +/* CP210X_GET_COMM_STATUS returns these 0x13 bytes */ +struct cp210x_comm_status { + __le32 ulErrors; + __le32 ulHoldReasons; + __le32 ulAmountInInQueue; + __le32 ulAmountInOutQueue; + u8 bEofReceived; + u8 bWaitForImmediate; + u8 bReserved; +} __packed; + +/* + * CP210X_PURGE - 16 bits passed in wValue of USB request. + * SiLabs app note AN571 gives a strange description of the 4 bits: + * bit 0 or bit 2 clears the transmit queue and 1 or 3 receive. + * writing 1 to all, however, purges cp2108 well enough to avoid the hang. + */ +#define PURGE_ALL 0x000f + /* * cp210x_get_config * Reads from the CP210x configuration registers @@ -310,7 +333,7 @@ static int cp210x_get_config(struct usb_serial_port *port, u8 request, unsigned int *data, int size) { struct usb_serial *serial = port->serial; - struct cp210x_serial_private *spriv = usb_get_serial_data(serial); + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); __le32 *buf; int result, i, length; @@ -324,7 +347,7 @@ static int cp210x_get_config(struct usb_serial_port *port, u8 request, /* Issue the request, attempting to read 'size' bytes */ result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), request, REQTYPE_INTERFACE_TO_HOST, 0x0000, - spriv->bInterfaceNumber, buf, size, + port_priv->bInterfaceNumber, buf, size, USB_CTRL_GET_TIMEOUT); /* Convert data into an array of integers */ @@ -355,7 +378,7 @@ static int cp210x_set_config(struct usb_serial_port *port, u8 request, unsigned int *data, int size) { struct usb_serial *serial = port->serial; - struct cp210x_serial_private *spriv = usb_get_serial_data(serial); + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); __le32 *buf; int result, i, length; @@ -374,13 +397,13 @@ static int cp210x_set_config(struct usb_serial_port *port, u8 request, result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), request, REQTYPE_HOST_TO_INTERFACE, 0x0000, - spriv->bInterfaceNumber, buf, size, + port_priv->bInterfaceNumber, buf, size, USB_CTRL_SET_TIMEOUT); } else { result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), request, REQTYPE_HOST_TO_INTERFACE, data[0], - spriv->bInterfaceNumber, NULL, 0, + port_priv->bInterfaceNumber, NULL, 0, USB_CTRL_SET_TIMEOUT); } @@ -409,6 +432,60 @@ static inline int cp210x_set_config_single(struct usb_serial_port *port, return cp210x_set_config(port, request, &data, 2); } +/* + * Detect CP2108 GET_LINE_CTL bug and activate workaround. + * Write a known good value 0x800, read it back. + * If it comes back swapped the bug is detected. + * Preserve the original register value. + */ +static int cp210x_detect_swapped_line_ctl(struct usb_serial_port *port) +{ + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + unsigned int line_ctl_save; + unsigned int line_ctl_test; + int err; + + err = cp210x_get_config(port, CP210X_GET_LINE_CTL, &line_ctl_save, 2); + if (err) + return err; + + line_ctl_test = 0x800; + err = cp210x_set_config(port, CP210X_SET_LINE_CTL, &line_ctl_test, 2); + if (err) + return err; + + err = cp210x_get_config(port, CP210X_GET_LINE_CTL, &line_ctl_test, 2); + if (err) + return err; + + if (line_ctl_test == 8) { + port_priv->has_swapped_line_ctl = true; + line_ctl_save = swab16((u16)line_ctl_save); + } + + return cp210x_set_config(port, CP210X_SET_LINE_CTL, &line_ctl_save, 2); +} + +/* + * Must always be called instead of cp210x_get_config(CP210X_GET_LINE_CTL) + * to workaround cp2108 bug and get correct value. + */ +static int cp210x_get_line_ctl(struct usb_serial_port *port, unsigned int *ctl) +{ + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + int err; + + err = cp210x_get_config(port, CP210X_GET_LINE_CTL, ctl, 2); + if (err) + return err; + + /* Workaround swapped bytes in 16-bit value from CP210X_GET_LINE_CTL */ + if (port_priv->has_swapped_line_ctl) + *ctl = swab16((u16)(*ctl)); + + return 0; +} + /* * cp210x_quantise_baudrate * Quantises the baud rate as per AN205 Table 1 @@ -474,10 +551,62 @@ static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *port) static void cp210x_close(struct usb_serial_port *port) { + unsigned int purge_ctl; + usb_serial_generic_close(port); + + /* Clear both queues; cp2108 needs this to avoid an occasional hang */ + purge_ctl = PURGE_ALL; + cp210x_set_config(port, CP210X_PURGE, &purge_ctl, 2); + cp210x_set_config_single(port, CP210X_IFC_ENABLE, UART_DISABLE); } +/* + * Read how many bytes are waiting in the TX queue. + */ +static int cp210x_get_tx_queue_byte_count(struct usb_serial_port *port, + u32 *count) +{ + struct usb_serial *serial = port->serial; + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + struct cp210x_comm_status *sts; + int result; + + sts = kmalloc(sizeof(*sts), GFP_KERNEL); + if (!sts) + return -ENOMEM; + + result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + CP210X_GET_COMM_STATUS, REQTYPE_INTERFACE_TO_HOST, + 0, port_priv->bInterfaceNumber, sts, sizeof(*sts), + USB_CTRL_GET_TIMEOUT); + if (result == sizeof(*sts)) { + *count = le32_to_cpu(sts->ulAmountInOutQueue); + result = 0; + } else { + dev_err(&port->dev, "failed to get comm status: %d\n", result); + if (result >= 0) + result = -EPROTO; + } + + kfree(sts); + + return result; +} + +static bool cp210x_tx_empty(struct usb_serial_port *port) +{ + int err; + u32 count; + + err = cp210x_get_tx_queue_byte_count(port, &count); + if (err) + return true; + + return !count; +} + /* * cp210x_get_termios * Reads the baud rate, data bits, parity, stop bits and flow control mode @@ -519,7 +648,7 @@ static void cp210x_get_termios_port(struct usb_serial_port *port, cflag = *cflagp; - cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2); + cp210x_get_line_ctl(port, &bits); cflag &= ~CSIZE; switch (bits & BITS_DATA_MASK) { case BITS_DATA_5: @@ -687,7 +816,7 @@ static void cp210x_set_termios(struct tty_struct *tty, /* If the number of data bits is to be updated */ if ((cflag & CSIZE) != (old_cflag & CSIZE)) { - cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2); + cp210x_get_line_ctl(port, &bits); bits &= ~BITS_DATA_MASK; switch (cflag & CSIZE) { case CS5: @@ -721,7 +850,7 @@ static void cp210x_set_termios(struct tty_struct *tty, if ((cflag & (PARENB|PARODD|CMSPAR)) != (old_cflag & (PARENB|PARODD|CMSPAR))) { - cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2); + cp210x_get_line_ctl(port, &bits); bits &= ~BITS_PARITY_MASK; if (cflag & PARENB) { if (cflag & CMSPAR) { @@ -747,7 +876,7 @@ static void cp210x_set_termios(struct tty_struct *tty, } if ((cflag & CSTOPB) != (old_cflag & CSTOPB)) { - cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2); + cp210x_get_line_ctl(port, &bits); bits &= ~BITS_STOP_MASK; if (cflag & CSTOPB) { bits |= BITS_STOP_2; @@ -862,29 +991,39 @@ static void cp210x_break_ctl(struct tty_struct *tty, int break_state) cp210x_set_config(port, CP210X_SET_BREAK, &state, 2); } -static int cp210x_startup(struct usb_serial *serial) +static int cp210x_port_probe(struct usb_serial_port *port) { + struct usb_serial *serial = port->serial; struct usb_host_interface *cur_altsetting; - struct cp210x_serial_private *spriv; + struct cp210x_port_private *port_priv; + int ret; - spriv = kzalloc(sizeof(*spriv), GFP_KERNEL); - if (!spriv) + port_priv = kzalloc(sizeof(*port_priv), GFP_KERNEL); + if (!port_priv) return -ENOMEM; cur_altsetting = serial->interface->cur_altsetting; - spriv->bInterfaceNumber = cur_altsetting->desc.bInterfaceNumber; + port_priv->bInterfaceNumber = cur_altsetting->desc.bInterfaceNumber; - usb_set_serial_data(serial, spriv); + usb_set_serial_port_data(port, port_priv); + + ret = cp210x_detect_swapped_line_ctl(port); + if (ret) { + kfree(port_priv); + return ret; + } return 0; } -static void cp210x_release(struct usb_serial *serial) +static int cp210x_port_remove(struct usb_serial_port *port) { - struct cp210x_serial_private *spriv; + struct cp210x_port_private *port_priv; - spriv = usb_get_serial_data(serial); - kfree(spriv); + port_priv = usb_get_serial_port_data(port); + kfree(port_priv); + + return 0; } module_usb_serial_driver(serial_drivers, id_table); diff --git a/drivers/usb/serial/io_edgeport.c b/drivers/usb/serial/io_edgeport.c index c0866971db2b..f49327d20ee8 100644 --- a/drivers/usb/serial/io_edgeport.c +++ b/drivers/usb/serial/io_edgeport.c @@ -1046,9 +1046,8 @@ static void edge_close(struct usb_serial_port *port) edge_port->closePending = true; - if ((!edge_serial->is_epic) || - ((edge_serial->is_epic) && - (edge_serial->epic_descriptor.Supports.IOSPChase))) { + if (!edge_serial->is_epic || + edge_serial->epic_descriptor.Supports.IOSPChase) { /* flush and chase */ edge_port->chaseResponsePending = true; @@ -1061,9 +1060,8 @@ static void edge_close(struct usb_serial_port *port) edge_port->chaseResponsePending = false; } - if ((!edge_serial->is_epic) || - ((edge_serial->is_epic) && - (edge_serial->epic_descriptor.Supports.IOSPClose))) { + if (!edge_serial->is_epic || + edge_serial->epic_descriptor.Supports.IOSPClose) { /* close the port */ dev_dbg(&port->dev, "%s - Sending IOSP_CMD_CLOSE_PORT\n", __func__); send_iosp_ext_cmd(edge_port, IOSP_CMD_CLOSE_PORT, 0); @@ -1612,9 +1610,8 @@ static void edge_break(struct tty_struct *tty, int break_state) struct edgeport_serial *edge_serial = usb_get_serial_data(port->serial); int status; - if ((!edge_serial->is_epic) || - ((edge_serial->is_epic) && - (edge_serial->epic_descriptor.Supports.IOSPChase))) { + if (!edge_serial->is_epic || + edge_serial->epic_descriptor.Supports.IOSPChase) { /* flush and chase */ edge_port->chaseResponsePending = true; @@ -1628,9 +1625,8 @@ static void edge_break(struct tty_struct *tty, int break_state) } } - if ((!edge_serial->is_epic) || - ((edge_serial->is_epic) && - (edge_serial->epic_descriptor.Supports.IOSPSetClrBreak))) { + if (!edge_serial->is_epic || + edge_serial->epic_descriptor.Supports.IOSPSetClrBreak) { if (break_state == -1) { dev_dbg(&port->dev, "%s - Sending IOSP_CMD_SET_BREAK\n", __func__); status = send_iosp_ext_cmd(edge_port, @@ -2465,9 +2461,8 @@ static void change_port_settings(struct tty_struct *tty, unsigned char stop_char = STOP_CHAR(tty); unsigned char start_char = START_CHAR(tty); - if ((!edge_serial->is_epic) || - ((edge_serial->is_epic) && - (edge_serial->epic_descriptor.Supports.IOSPSetXChar))) { + if (!edge_serial->is_epic || + edge_serial->epic_descriptor.Supports.IOSPSetXChar) { send_iosp_ext_cmd(edge_port, IOSP_CMD_SET_XON_CHAR, start_char); send_iosp_ext_cmd(edge_port, @@ -2494,13 +2489,11 @@ static void change_port_settings(struct tty_struct *tty, } /* Set flow control to the configured value */ - if ((!edge_serial->is_epic) || - ((edge_serial->is_epic) && - (edge_serial->epic_descriptor.Supports.IOSPSetRxFlow))) + if (!edge_serial->is_epic || + edge_serial->epic_descriptor.Supports.IOSPSetRxFlow) send_iosp_ext_cmd(edge_port, IOSP_CMD_SET_RX_FLOW, rxFlow); - if ((!edge_serial->is_epic) || - ((edge_serial->is_epic) && - (edge_serial->epic_descriptor.Supports.IOSPSetTxFlow))) + if (!edge_serial->is_epic || + edge_serial->epic_descriptor.Supports.IOSPSetTxFlow) send_iosp_ext_cmd(edge_port, IOSP_CMD_SET_TX_FLOW, txFlow); diff --git a/drivers/usb/serial/mos7840.c b/drivers/usb/serial/mos7840.c index 8ac9b55f05af..2c69bfcdacc6 100644 --- a/drivers/usb/serial/mos7840.c +++ b/drivers/usb/serial/mos7840.c @@ -635,7 +635,7 @@ static void mos7840_interrupt_callback(struct urb *urb) * Byte 4 IIR Port 4 (port.number is 3) * Byte 5 FIFO status for both */ - if (length && length > 5) { + if (length > 5) { dev_dbg(&urb->dev->dev, "%s", "Wrong data !!!\n"); return; } diff --git a/drivers/usb/serial/mxu11x0.c b/drivers/usb/serial/mxu11x0.c new file mode 100644 index 000000000000..e3c3f57c2d82 --- /dev/null +++ b/drivers/usb/serial/mxu11x0.c @@ -0,0 +1,986 @@ +/* + * USB Moxa UPORT 11x0 Serial Driver + * + * Copyright (C) 2007 MOXA Technologies Co., Ltd. + * Copyright (C) 2015 Mathieu Othacehe + * + * 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. + * + * + * Supports the following Moxa USB to serial converters: + * UPort 1110, 1 port RS-232 USB to Serial Hub. + * UPort 1130, 1 port RS-422/485 USB to Serial Hub. + * UPort 1130I, 1 port RS-422/485 USB to Serial Hub with isolation + * protection. + * UPort 1150, 1 port RS-232/422/485 USB to Serial Hub. + * UPort 1150I, 1 port RS-232/422/485 USB to Serial Hub with isolation + * protection. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Vendor and product ids */ +#define MXU1_VENDOR_ID 0x110a +#define MXU1_1110_PRODUCT_ID 0x1110 +#define MXU1_1130_PRODUCT_ID 0x1130 +#define MXU1_1150_PRODUCT_ID 0x1150 +#define MXU1_1151_PRODUCT_ID 0x1151 +#define MXU1_1131_PRODUCT_ID 0x1131 + +/* Commands */ +#define MXU1_GET_VERSION 0x01 +#define MXU1_GET_PORT_STATUS 0x02 +#define MXU1_GET_PORT_DEV_INFO 0x03 +#define MXU1_GET_CONFIG 0x04 +#define MXU1_SET_CONFIG 0x05 +#define MXU1_OPEN_PORT 0x06 +#define MXU1_CLOSE_PORT 0x07 +#define MXU1_START_PORT 0x08 +#define MXU1_STOP_PORT 0x09 +#define MXU1_TEST_PORT 0x0A +#define MXU1_PURGE_PORT 0x0B +#define MXU1_RESET_EXT_DEVICE 0x0C +#define MXU1_GET_OUTQUEUE 0x0D +#define MXU1_WRITE_DATA 0x80 +#define MXU1_READ_DATA 0x81 +#define MXU1_REQ_TYPE_CLASS 0x82 + +/* Module identifiers */ +#define MXU1_I2C_PORT 0x01 +#define MXU1_IEEE1284_PORT 0x02 +#define MXU1_UART1_PORT 0x03 +#define MXU1_UART2_PORT 0x04 +#define MXU1_RAM_PORT 0x05 + +/* Modem status */ +#define MXU1_MSR_DELTA_CTS 0x01 +#define MXU1_MSR_DELTA_DSR 0x02 +#define MXU1_MSR_DELTA_RI 0x04 +#define MXU1_MSR_DELTA_CD 0x08 +#define MXU1_MSR_CTS 0x10 +#define MXU1_MSR_DSR 0x20 +#define MXU1_MSR_RI 0x40 +#define MXU1_MSR_CD 0x80 +#define MXU1_MSR_DELTA_MASK 0x0F +#define MXU1_MSR_MASK 0xF0 + +/* Line status */ +#define MXU1_LSR_OVERRUN_ERROR 0x01 +#define MXU1_LSR_PARITY_ERROR 0x02 +#define MXU1_LSR_FRAMING_ERROR 0x04 +#define MXU1_LSR_BREAK 0x08 +#define MXU1_LSR_ERROR 0x0F +#define MXU1_LSR_RX_FULL 0x10 +#define MXU1_LSR_TX_EMPTY 0x20 + +/* Modem control */ +#define MXU1_MCR_LOOP 0x04 +#define MXU1_MCR_DTR 0x10 +#define MXU1_MCR_RTS 0x20 + +/* Mask settings */ +#define MXU1_UART_ENABLE_RTS_IN 0x0001 +#define MXU1_UART_DISABLE_RTS 0x0002 +#define MXU1_UART_ENABLE_PARITY_CHECKING 0x0008 +#define MXU1_UART_ENABLE_DSR_OUT 0x0010 +#define MXU1_UART_ENABLE_CTS_OUT 0x0020 +#define MXU1_UART_ENABLE_X_OUT 0x0040 +#define MXU1_UART_ENABLE_XA_OUT 0x0080 +#define MXU1_UART_ENABLE_X_IN 0x0100 +#define MXU1_UART_ENABLE_DTR_IN 0x0800 +#define MXU1_UART_DISABLE_DTR 0x1000 +#define MXU1_UART_ENABLE_MS_INTS 0x2000 +#define MXU1_UART_ENABLE_AUTO_START_DMA 0x4000 +#define MXU1_UART_SEND_BREAK_SIGNAL 0x8000 + +/* Parity */ +#define MXU1_UART_NO_PARITY 0x00 +#define MXU1_UART_ODD_PARITY 0x01 +#define MXU1_UART_EVEN_PARITY 0x02 +#define MXU1_UART_MARK_PARITY 0x03 +#define MXU1_UART_SPACE_PARITY 0x04 + +/* Stop bits */ +#define MXU1_UART_1_STOP_BITS 0x00 +#define MXU1_UART_1_5_STOP_BITS 0x01 +#define MXU1_UART_2_STOP_BITS 0x02 + +/* Bits per character */ +#define MXU1_UART_5_DATA_BITS 0x00 +#define MXU1_UART_6_DATA_BITS 0x01 +#define MXU1_UART_7_DATA_BITS 0x02 +#define MXU1_UART_8_DATA_BITS 0x03 + +/* Operation modes */ +#define MXU1_UART_232 0x00 +#define MXU1_UART_485_RECEIVER_DISABLED 0x01 +#define MXU1_UART_485_RECEIVER_ENABLED 0x02 + +/* Pipe transfer mode and timeout */ +#define MXU1_PIPE_MODE_CONTINUOUS 0x01 +#define MXU1_PIPE_MODE_MASK 0x03 +#define MXU1_PIPE_TIMEOUT_MASK 0x7C +#define MXU1_PIPE_TIMEOUT_ENABLE 0x80 + +/* Config struct */ +struct mxu1_uart_config { + __be16 wBaudRate; + __be16 wFlags; + u8 bDataBits; + u8 bParity; + u8 bStopBits; + char cXon; + char cXoff; + u8 bUartMode; +} __packed; + +/* Purge modes */ +#define MXU1_PURGE_OUTPUT 0x00 +#define MXU1_PURGE_INPUT 0x80 + +/* Read/Write data */ +#define MXU1_RW_DATA_ADDR_SFR 0x10 +#define MXU1_RW_DATA_ADDR_IDATA 0x20 +#define MXU1_RW_DATA_ADDR_XDATA 0x30 +#define MXU1_RW_DATA_ADDR_CODE 0x40 +#define MXU1_RW_DATA_ADDR_GPIO 0x50 +#define MXU1_RW_DATA_ADDR_I2C 0x60 +#define MXU1_RW_DATA_ADDR_FLASH 0x70 +#define MXU1_RW_DATA_ADDR_DSP 0x80 + +#define MXU1_RW_DATA_UNSPECIFIED 0x00 +#define MXU1_RW_DATA_BYTE 0x01 +#define MXU1_RW_DATA_WORD 0x02 +#define MXU1_RW_DATA_DOUBLE_WORD 0x04 + +struct mxu1_write_data_bytes { + u8 bAddrType; + u8 bDataType; + u8 bDataCounter; + __be16 wBaseAddrHi; + __be16 wBaseAddrLo; + u8 bData[0]; +} __packed; + +/* Interrupt codes */ +#define MXU1_CODE_HARDWARE_ERROR 0xFF +#define MXU1_CODE_DATA_ERROR 0x03 +#define MXU1_CODE_MODEM_STATUS 0x04 + +static inline int mxu1_get_func_from_code(unsigned char code) +{ + return code & 0x0f; +} + +/* Download firmware max packet size */ +#define MXU1_DOWNLOAD_MAX_PACKET_SIZE 64 + +/* Firmware image header */ +struct mxu1_firmware_header { + __le16 wLength; + u8 bCheckSum; +} __packed; + +#define MXU1_UART_BASE_ADDR 0xFFA0 +#define MXU1_UART_OFFSET_MCR 0x0004 + +#define MXU1_BAUD_BASE 923077 + +#define MXU1_TRANSFER_TIMEOUT 2 +#define MXU1_DOWNLOAD_TIMEOUT 1000 +#define MXU1_DEFAULT_CLOSING_WAIT 4000 /* in .01 secs */ + +struct mxu1_port { + u8 msr; + u8 mcr; + u8 uart_mode; + spinlock_t spinlock; /* Protects msr */ + struct mutex mutex; /* Protects mcr */ + bool send_break; +}; + +struct mxu1_device { + u16 mxd_model; +}; + +static const struct usb_device_id mxu1_idtable[] = { + { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1110_PRODUCT_ID) }, + { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1130_PRODUCT_ID) }, + { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1150_PRODUCT_ID) }, + { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1151_PRODUCT_ID) }, + { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1131_PRODUCT_ID) }, + { } +}; + +MODULE_DEVICE_TABLE(usb, mxu1_idtable); + +/* Write the given buffer out to the control pipe. */ +static int mxu1_send_ctrl_data_urb(struct usb_serial *serial, + u8 request, + u16 value, u16 index, + void *data, size_t size) +{ + int status; + + status = usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + request, + (USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE), value, index, + data, size, + USB_CTRL_SET_TIMEOUT); + if (status < 0) { + dev_err(&serial->interface->dev, + "%s - usb_control_msg failed: %d\n", + __func__, status); + return status; + } + + if (status != size) { + dev_err(&serial->interface->dev, + "%s - short write (%d / %zd)\n", + __func__, status, size); + return -EIO; + } + + return 0; +} + +/* Send a vendor request without any data */ +static int mxu1_send_ctrl_urb(struct usb_serial *serial, + u8 request, u16 value, u16 index) +{ + return mxu1_send_ctrl_data_urb(serial, request, value, index, + NULL, 0); +} + +static int mxu1_download_firmware(struct usb_serial *serial, + const struct firmware *fw_p) +{ + int status = 0; + int buffer_size; + int pos; + int len; + int done; + u8 cs = 0; + u8 *buffer; + struct usb_device *dev = serial->dev; + struct mxu1_firmware_header *header; + unsigned int pipe; + + pipe = usb_sndbulkpipe(dev, serial->port[0]->bulk_out_endpointAddress); + + buffer_size = fw_p->size + sizeof(*header); + buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + memcpy(buffer, fw_p->data, fw_p->size); + memset(buffer + fw_p->size, 0xff, buffer_size - fw_p->size); + + for (pos = sizeof(*header); pos < buffer_size; pos++) + cs = (u8)(cs + buffer[pos]); + + header = (struct mxu1_firmware_header *)buffer; + header->wLength = cpu_to_le16(buffer_size - sizeof(*header)); + header->bCheckSum = cs; + + dev_dbg(&dev->dev, "%s - downloading firmware\n", __func__); + + for (pos = 0; pos < buffer_size; pos += done) { + len = min(buffer_size - pos, MXU1_DOWNLOAD_MAX_PACKET_SIZE); + + status = usb_bulk_msg(dev, pipe, buffer + pos, len, &done, + MXU1_DOWNLOAD_TIMEOUT); + if (status) + break; + } + + kfree(buffer); + + if (status) { + dev_err(&dev->dev, "failed to download firmware: %d\n", status); + return status; + } + + msleep_interruptible(100); + usb_reset_device(dev); + + dev_dbg(&dev->dev, "%s - download successful\n", __func__); + + return 0; +} + +static int mxu1_port_probe(struct usb_serial_port *port) +{ + struct mxu1_port *mxport; + struct mxu1_device *mxdev; + + if (!port->interrupt_in_urb) { + dev_err(&port->dev, "no interrupt urb\n"); + return -ENODEV; + } + + mxport = kzalloc(sizeof(struct mxu1_port), GFP_KERNEL); + if (!mxport) + return -ENOMEM; + + spin_lock_init(&mxport->spinlock); + mutex_init(&mxport->mutex); + + mxdev = usb_get_serial_data(port->serial); + + switch (mxdev->mxd_model) { + case MXU1_1110_PRODUCT_ID: + case MXU1_1150_PRODUCT_ID: + case MXU1_1151_PRODUCT_ID: + mxport->uart_mode = MXU1_UART_232; + break; + case MXU1_1130_PRODUCT_ID: + case MXU1_1131_PRODUCT_ID: + mxport->uart_mode = MXU1_UART_485_RECEIVER_DISABLED; + break; + } + + usb_set_serial_port_data(port, mxport); + + port->port.closing_wait = + msecs_to_jiffies(MXU1_DEFAULT_CLOSING_WAIT * 10); + port->port.drain_delay = 1; + + return 0; +} + +static int mxu1_startup(struct usb_serial *serial) +{ + struct mxu1_device *mxdev; + struct usb_device *dev = serial->dev; + struct usb_host_interface *cur_altsetting; + char fw_name[32]; + const struct firmware *fw_p = NULL; + int err; + + dev_dbg(&serial->interface->dev, "%s - product 0x%04X, num configurations %d, configuration value %d\n", + __func__, le16_to_cpu(dev->descriptor.idProduct), + dev->descriptor.bNumConfigurations, + dev->actconfig->desc.bConfigurationValue); + + /* create device structure */ + mxdev = kzalloc(sizeof(struct mxu1_device), GFP_KERNEL); + if (!mxdev) + return -ENOMEM; + + usb_set_serial_data(serial, mxdev); + + mxdev->mxd_model = le16_to_cpu(dev->descriptor.idProduct); + + cur_altsetting = serial->interface->cur_altsetting; + + /* if we have only 1 configuration, download firmware */ + if (cur_altsetting->desc.bNumEndpoints == 1) { + + snprintf(fw_name, + sizeof(fw_name), + "moxa/moxa-%04x.fw", + mxdev->mxd_model); + + err = request_firmware(&fw_p, fw_name, &serial->interface->dev); + if (err) { + dev_err(&serial->interface->dev, "failed to request firmware: %d\n", + err); + goto err_free_mxdev; + } + + err = mxu1_download_firmware(serial, fw_p); + if (err) + goto err_release_firmware; + + /* device is being reset */ + err = -ENODEV; + goto err_release_firmware; + } + + return 0; + +err_release_firmware: + release_firmware(fw_p); +err_free_mxdev: + kfree(mxdev); + + return err; +} + +static int mxu1_write_byte(struct usb_serial_port *port, u32 addr, + u8 mask, u8 byte) +{ + int status; + size_t size; + struct mxu1_write_data_bytes *data; + + dev_dbg(&port->dev, "%s - addr 0x%08X, mask 0x%02X, byte 0x%02X\n", + __func__, addr, mask, byte); + + size = sizeof(struct mxu1_write_data_bytes) + 2; + data = kzalloc(size, GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->bAddrType = MXU1_RW_DATA_ADDR_XDATA; + data->bDataType = MXU1_RW_DATA_BYTE; + data->bDataCounter = 1; + data->wBaseAddrHi = cpu_to_be16(addr >> 16); + data->wBaseAddrLo = cpu_to_be16(addr); + data->bData[0] = mask; + data->bData[1] = byte; + + status = mxu1_send_ctrl_data_urb(port->serial, MXU1_WRITE_DATA, 0, + MXU1_RAM_PORT, data, size); + if (status < 0) + dev_err(&port->dev, "%s - failed: %d\n", __func__, status); + + kfree(data); + + return status; +} + +static int mxu1_set_mcr(struct usb_serial_port *port, unsigned int mcr) +{ + int status; + + status = mxu1_write_byte(port, + MXU1_UART_BASE_ADDR + MXU1_UART_OFFSET_MCR, + MXU1_MCR_RTS | MXU1_MCR_DTR | MXU1_MCR_LOOP, + mcr); + return status; +} + +static void mxu1_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + struct ktermios *old_termios) +{ + struct mxu1_port *mxport = usb_get_serial_port_data(port); + struct mxu1_uart_config *config; + tcflag_t cflag, iflag; + speed_t baud; + int status; + unsigned int mcr; + + cflag = tty->termios.c_cflag; + iflag = tty->termios.c_iflag; + + if (old_termios && + !tty_termios_hw_change(&tty->termios, old_termios) && + tty->termios.c_iflag == old_termios->c_iflag) { + dev_dbg(&port->dev, "%s - nothing to change\n", __func__); + return; + } + + dev_dbg(&port->dev, + "%s - cflag 0x%08x, iflag 0x%08x\n", __func__, cflag, iflag); + + if (old_termios) { + dev_dbg(&port->dev, "%s - old cflag 0x%08x, old iflag 0x%08x\n", + __func__, + old_termios->c_cflag, + old_termios->c_iflag); + } + + config = kzalloc(sizeof(*config), GFP_KERNEL); + if (!config) + return; + + /* these flags must be set */ + config->wFlags |= MXU1_UART_ENABLE_MS_INTS; + config->wFlags |= MXU1_UART_ENABLE_AUTO_START_DMA; + if (mxport->send_break) + config->wFlags |= MXU1_UART_SEND_BREAK_SIGNAL; + config->bUartMode = mxport->uart_mode; + + switch (C_CSIZE(tty)) { + case CS5: + config->bDataBits = MXU1_UART_5_DATA_BITS; + break; + case CS6: + config->bDataBits = MXU1_UART_6_DATA_BITS; + break; + case CS7: + config->bDataBits = MXU1_UART_7_DATA_BITS; + break; + default: + case CS8: + config->bDataBits = MXU1_UART_8_DATA_BITS; + break; + } + + if (C_PARENB(tty)) { + config->wFlags |= MXU1_UART_ENABLE_PARITY_CHECKING; + if (C_CMSPAR(tty)) { + if (C_PARODD(tty)) + config->bParity = MXU1_UART_MARK_PARITY; + else + config->bParity = MXU1_UART_SPACE_PARITY; + } else { + if (C_PARODD(tty)) + config->bParity = MXU1_UART_ODD_PARITY; + else + config->bParity = MXU1_UART_EVEN_PARITY; + } + } else { + config->bParity = MXU1_UART_NO_PARITY; + } + + if (C_CSTOPB(tty)) + config->bStopBits = MXU1_UART_2_STOP_BITS; + else + config->bStopBits = MXU1_UART_1_STOP_BITS; + + if (C_CRTSCTS(tty)) { + /* RTS flow control must be off to drop RTS for baud rate B0 */ + if (C_BAUD(tty) != B0) + config->wFlags |= MXU1_UART_ENABLE_RTS_IN; + config->wFlags |= MXU1_UART_ENABLE_CTS_OUT; + } + + if (I_IXOFF(tty) || I_IXON(tty)) { + config->cXon = START_CHAR(tty); + config->cXoff = STOP_CHAR(tty); + + if (I_IXOFF(tty)) + config->wFlags |= MXU1_UART_ENABLE_X_IN; + + if (I_IXON(tty)) + config->wFlags |= MXU1_UART_ENABLE_X_OUT; + } + + baud = tty_get_baud_rate(tty); + if (!baud) + baud = 9600; + config->wBaudRate = MXU1_BAUD_BASE / baud; + + dev_dbg(&port->dev, "%s - BaudRate=%d, wBaudRate=%d, wFlags=0x%04X, bDataBits=%d, bParity=%d, bStopBits=%d, cXon=%d, cXoff=%d, bUartMode=%d\n", + __func__, baud, config->wBaudRate, config->wFlags, + config->bDataBits, config->bParity, config->bStopBits, + config->cXon, config->cXoff, config->bUartMode); + + cpu_to_be16s(&config->wBaudRate); + cpu_to_be16s(&config->wFlags); + + status = mxu1_send_ctrl_data_urb(port->serial, MXU1_SET_CONFIG, 0, + MXU1_UART1_PORT, config, + sizeof(*config)); + if (status) + dev_err(&port->dev, "cannot set config: %d\n", status); + + mutex_lock(&mxport->mutex); + mcr = mxport->mcr; + + if (C_BAUD(tty) == B0) + mcr &= ~(MXU1_MCR_DTR | MXU1_MCR_RTS); + else if (old_termios && (old_termios->c_cflag & CBAUD) == B0) + mcr |= MXU1_MCR_DTR | MXU1_MCR_RTS; + + status = mxu1_set_mcr(port, mcr); + if (status) + dev_err(&port->dev, "cannot set modem control: %d\n", status); + else + mxport->mcr = mcr; + + mutex_unlock(&mxport->mutex); + + kfree(config); +} + +static int mxu1_get_serial_info(struct usb_serial_port *port, + struct serial_struct __user *ret_arg) +{ + struct serial_struct ret_serial; + unsigned cwait; + + if (!ret_arg) + return -EFAULT; + + cwait = port->port.closing_wait; + if (cwait != ASYNC_CLOSING_WAIT_NONE) + cwait = jiffies_to_msecs(cwait) / 10; + + memset(&ret_serial, 0, sizeof(ret_serial)); + + ret_serial.type = PORT_16550A; + ret_serial.line = port->minor; + ret_serial.port = 0; + ret_serial.xmit_fifo_size = port->bulk_out_size; + ret_serial.baud_base = MXU1_BAUD_BASE; + ret_serial.close_delay = 5*HZ; + ret_serial.closing_wait = cwait; + + if (copy_to_user(ret_arg, &ret_serial, sizeof(*ret_arg))) + return -EFAULT; + + return 0; +} + + +static int mxu1_set_serial_info(struct usb_serial_port *port, + struct serial_struct __user *new_arg) +{ + struct serial_struct new_serial; + unsigned cwait; + + if (copy_from_user(&new_serial, new_arg, sizeof(new_serial))) + return -EFAULT; + + cwait = new_serial.closing_wait; + if (cwait != ASYNC_CLOSING_WAIT_NONE) + cwait = msecs_to_jiffies(10 * new_serial.closing_wait); + + port->port.closing_wait = cwait; + + return 0; +} + +static int mxu1_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + + switch (cmd) { + case TIOCGSERIAL: + return mxu1_get_serial_info(port, + (struct serial_struct __user *)arg); + case TIOCSSERIAL: + return mxu1_set_serial_info(port, + (struct serial_struct __user *)arg); + } + + return -ENOIOCTLCMD; +} + +static int mxu1_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct mxu1_port *mxport = usb_get_serial_port_data(port); + unsigned int result; + unsigned int msr; + unsigned int mcr; + unsigned long flags; + + mutex_lock(&mxport->mutex); + spin_lock_irqsave(&mxport->spinlock, flags); + + msr = mxport->msr; + mcr = mxport->mcr; + + spin_unlock_irqrestore(&mxport->spinlock, flags); + mutex_unlock(&mxport->mutex); + + result = ((mcr & MXU1_MCR_DTR) ? TIOCM_DTR : 0) | + ((mcr & MXU1_MCR_RTS) ? TIOCM_RTS : 0) | + ((mcr & MXU1_MCR_LOOP) ? TIOCM_LOOP : 0) | + ((msr & MXU1_MSR_CTS) ? TIOCM_CTS : 0) | + ((msr & MXU1_MSR_CD) ? TIOCM_CAR : 0) | + ((msr & MXU1_MSR_RI) ? TIOCM_RI : 0) | + ((msr & MXU1_MSR_DSR) ? TIOCM_DSR : 0); + + dev_dbg(&port->dev, "%s - 0x%04X\n", __func__, result); + + return result; +} + +static int mxu1_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct mxu1_port *mxport = usb_get_serial_port_data(port); + int err; + unsigned int mcr; + + mutex_lock(&mxport->mutex); + mcr = mxport->mcr; + + if (set & TIOCM_RTS) + mcr |= MXU1_MCR_RTS; + if (set & TIOCM_DTR) + mcr |= MXU1_MCR_DTR; + if (set & TIOCM_LOOP) + mcr |= MXU1_MCR_LOOP; + + if (clear & TIOCM_RTS) + mcr &= ~MXU1_MCR_RTS; + if (clear & TIOCM_DTR) + mcr &= ~MXU1_MCR_DTR; + if (clear & TIOCM_LOOP) + mcr &= ~MXU1_MCR_LOOP; + + err = mxu1_set_mcr(port, mcr); + if (!err) + mxport->mcr = mcr; + + mutex_unlock(&mxport->mutex); + + return err; +} + +static void mxu1_break(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + struct mxu1_port *mxport = usb_get_serial_port_data(port); + + if (break_state == -1) + mxport->send_break = true; + else + mxport->send_break = false; + + mxu1_set_termios(tty, port, NULL); +} + +static int mxu1_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct mxu1_port *mxport = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + int status; + u16 open_settings; + + open_settings = (MXU1_PIPE_MODE_CONTINUOUS | + MXU1_PIPE_TIMEOUT_ENABLE | + (MXU1_TRANSFER_TIMEOUT << 2)); + + mxport->msr = 0; + + status = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (status) { + dev_err(&port->dev, "failed to submit interrupt urb: %d\n", + status); + return status; + } + + if (tty) + mxu1_set_termios(tty, port, NULL); + + status = mxu1_send_ctrl_urb(serial, MXU1_OPEN_PORT, + open_settings, MXU1_UART1_PORT); + if (status) { + dev_err(&port->dev, "cannot send open command: %d\n", status); + goto unlink_int_urb; + } + + status = mxu1_send_ctrl_urb(serial, MXU1_START_PORT, + 0, MXU1_UART1_PORT); + if (status) { + dev_err(&port->dev, "cannot send start command: %d\n", status); + goto unlink_int_urb; + } + + status = mxu1_send_ctrl_urb(serial, MXU1_PURGE_PORT, + MXU1_PURGE_INPUT, MXU1_UART1_PORT); + if (status) { + dev_err(&port->dev, "cannot clear input buffers: %d\n", + status); + + goto unlink_int_urb; + } + + status = mxu1_send_ctrl_urb(serial, MXU1_PURGE_PORT, + MXU1_PURGE_OUTPUT, MXU1_UART1_PORT); + if (status) { + dev_err(&port->dev, "cannot clear output buffers: %d\n", + status); + + goto unlink_int_urb; + } + + /* + * reset the data toggle on the bulk endpoints to work around bug in + * host controllers where things get out of sync some times + */ + usb_clear_halt(serial->dev, port->write_urb->pipe); + usb_clear_halt(serial->dev, port->read_urb->pipe); + + if (tty) + mxu1_set_termios(tty, port, NULL); + + status = mxu1_send_ctrl_urb(serial, MXU1_OPEN_PORT, + open_settings, MXU1_UART1_PORT); + if (status) { + dev_err(&port->dev, "cannot send open command: %d\n", status); + goto unlink_int_urb; + } + + status = mxu1_send_ctrl_urb(serial, MXU1_START_PORT, + 0, MXU1_UART1_PORT); + if (status) { + dev_err(&port->dev, "cannot send start command: %d\n", status); + goto unlink_int_urb; + } + + status = usb_serial_generic_open(tty, port); + if (status) + goto unlink_int_urb; + + return 0; + +unlink_int_urb: + usb_kill_urb(port->interrupt_in_urb); + + return status; +} + +static void mxu1_close(struct usb_serial_port *port) +{ + int status; + + usb_serial_generic_close(port); + usb_kill_urb(port->interrupt_in_urb); + + status = mxu1_send_ctrl_urb(port->serial, MXU1_CLOSE_PORT, + 0, MXU1_UART1_PORT); + if (status) { + dev_err(&port->dev, "failed to send close port command: %d\n", + status); + } +} + +static void mxu1_handle_new_msr(struct usb_serial_port *port, u8 msr) +{ + struct mxu1_port *mxport = usb_get_serial_port_data(port); + struct async_icount *icount; + unsigned long flags; + + dev_dbg(&port->dev, "%s - msr 0x%02X\n", __func__, msr); + + spin_lock_irqsave(&mxport->spinlock, flags); + mxport->msr = msr & MXU1_MSR_MASK; + spin_unlock_irqrestore(&mxport->spinlock, flags); + + if (msr & MXU1_MSR_DELTA_MASK) { + icount = &port->icount; + if (msr & MXU1_MSR_DELTA_CTS) + icount->cts++; + if (msr & MXU1_MSR_DELTA_DSR) + icount->dsr++; + if (msr & MXU1_MSR_DELTA_CD) + icount->dcd++; + if (msr & MXU1_MSR_DELTA_RI) + icount->rng++; + + wake_up_interruptible(&port->port.delta_msr_wait); + } +} + +static void mxu1_interrupt_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + unsigned char *data = urb->transfer_buffer; + int length = urb->actual_length; + int function; + int status; + u8 msr; + + switch (urb->status) { + case 0: + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + dev_dbg(&port->dev, "%s - urb shutting down: %d\n", + __func__, urb->status); + return; + default: + dev_dbg(&port->dev, "%s - nonzero urb status: %d\n", + __func__, urb->status); + goto exit; + } + + if (length != 2) { + dev_dbg(&port->dev, "%s - bad packet size: %d\n", + __func__, length); + goto exit; + } + + if (data[0] == MXU1_CODE_HARDWARE_ERROR) { + dev_err(&port->dev, "hardware error: %d\n", data[1]); + goto exit; + } + + function = mxu1_get_func_from_code(data[0]); + + dev_dbg(&port->dev, "%s - function %d, data 0x%02X\n", + __func__, function, data[1]); + + switch (function) { + case MXU1_CODE_DATA_ERROR: + dev_dbg(&port->dev, "%s - DATA ERROR, data 0x%02X\n", + __func__, data[1]); + break; + + case MXU1_CODE_MODEM_STATUS: + msr = data[1]; + mxu1_handle_new_msr(port, msr); + break; + + default: + dev_err(&port->dev, "unknown interrupt code: 0x%02X\n", + data[1]); + break; + } + +exit: + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) { + dev_err(&port->dev, "resubmit interrupt urb failed: %d\n", + status); + } +} + +static struct usb_serial_driver mxu11x0_device = { + .driver = { + .owner = THIS_MODULE, + .name = "mxu11x0", + }, + .description = "MOXA UPort 11x0", + .id_table = mxu1_idtable, + .num_ports = 1, + .port_probe = mxu1_port_probe, + .attach = mxu1_startup, + .open = mxu1_open, + .close = mxu1_close, + .ioctl = mxu1_ioctl, + .set_termios = mxu1_set_termios, + .tiocmget = mxu1_tiocmget, + .tiocmset = mxu1_tiocmset, + .tiocmiwait = usb_serial_generic_tiocmiwait, + .get_icount = usb_serial_generic_get_icount, + .break_ctl = mxu1_break, + .read_int_callback = mxu1_interrupt_callback, +}; + +static struct usb_serial_driver *const serial_drivers[] = { + &mxu11x0_device, NULL +}; + +module_usb_serial_driver(serial_drivers, mxu1_idtable); + +MODULE_AUTHOR("Mathieu Othacehe "); +MODULE_DESCRIPTION("MOXA UPort 11x0 USB to Serial Hub Driver"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("moxa/moxa-1110.fw"); +MODULE_FIRMWARE("moxa/moxa-1130.fw"); +MODULE_FIRMWARE("moxa/moxa-1131.fw"); +MODULE_FIRMWARE("moxa/moxa-1150.fw"); +MODULE_FIRMWARE("moxa/moxa-1151.fw"); diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 5c66d3f7a6d0..9ff9404f99d7 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -49,18 +49,18 @@ struct uas_dev_info { }; enum { - SUBMIT_STATUS_URB = (1 << 1), - ALLOC_DATA_IN_URB = (1 << 2), - SUBMIT_DATA_IN_URB = (1 << 3), - ALLOC_DATA_OUT_URB = (1 << 4), - SUBMIT_DATA_OUT_URB = (1 << 5), - ALLOC_CMD_URB = (1 << 6), - SUBMIT_CMD_URB = (1 << 7), - COMMAND_INFLIGHT = (1 << 8), - DATA_IN_URB_INFLIGHT = (1 << 9), - DATA_OUT_URB_INFLIGHT = (1 << 10), - COMMAND_ABORTED = (1 << 11), - IS_IN_WORK_LIST = (1 << 12), + SUBMIT_STATUS_URB = BIT(1), + ALLOC_DATA_IN_URB = BIT(2), + SUBMIT_DATA_IN_URB = BIT(3), + ALLOC_DATA_OUT_URB = BIT(4), + SUBMIT_DATA_OUT_URB = BIT(5), + ALLOC_CMD_URB = BIT(6), + SUBMIT_CMD_URB = BIT(7), + COMMAND_INFLIGHT = BIT(8), + DATA_IN_URB_INFLIGHT = BIT(9), + DATA_OUT_URB_INFLIGHT = BIT(10), + COMMAND_ABORTED = BIT(11), + IS_IN_WORK_LIST = BIT(12), }; /* Overrides scsi_pointer */ @@ -74,7 +74,7 @@ struct uas_cmd_info { /* I hate forward declarations, but I actually have a loop */ static int uas_submit_urbs(struct scsi_cmnd *cmnd, - struct uas_dev_info *devinfo, gfp_t gfp); + struct uas_dev_info *devinfo); static void uas_do_work(struct work_struct *work); static int uas_try_complete(struct scsi_cmnd *cmnd, const char *caller); static void uas_free_streams(struct uas_dev_info *devinfo); @@ -105,7 +105,7 @@ static void uas_do_work(struct work_struct *work) if (!(cmdinfo->state & IS_IN_WORK_LIST)) continue; - err = uas_submit_urbs(cmnd, cmnd->device->hostdata, GFP_ATOMIC); + err = uas_submit_urbs(cmnd, cmnd->device->hostdata); if (!err) cmdinfo->state &= ~IS_IN_WORK_LIST; else @@ -240,7 +240,7 @@ static void uas_xfer_data(struct urb *urb, struct scsi_cmnd *cmnd, int err; cmdinfo->state |= direction | SUBMIT_STATUS_URB; - err = uas_submit_urbs(cmnd, cmnd->device->hostdata, GFP_ATOMIC); + err = uas_submit_urbs(cmnd, cmnd->device->hostdata); if (err) { uas_add_work(cmdinfo); } @@ -512,7 +512,7 @@ static struct urb *uas_submit_sense_urb(struct scsi_cmnd *cmnd, gfp_t gfp) } static int uas_submit_urbs(struct scsi_cmnd *cmnd, - struct uas_dev_info *devinfo, gfp_t gfp) + struct uas_dev_info *devinfo) { struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; struct urb *urb; @@ -520,14 +520,14 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, lockdep_assert_held(&devinfo->lock); if (cmdinfo->state & SUBMIT_STATUS_URB) { - urb = uas_submit_sense_urb(cmnd, gfp); + urb = uas_submit_sense_urb(cmnd, GFP_ATOMIC); if (!urb) return SCSI_MLQUEUE_DEVICE_BUSY; cmdinfo->state &= ~SUBMIT_STATUS_URB; } if (cmdinfo->state & ALLOC_DATA_IN_URB) { - cmdinfo->data_in_urb = uas_alloc_data_urb(devinfo, gfp, + cmdinfo->data_in_urb = uas_alloc_data_urb(devinfo, GFP_ATOMIC, cmnd, DMA_FROM_DEVICE); if (!cmdinfo->data_in_urb) return SCSI_MLQUEUE_DEVICE_BUSY; @@ -536,7 +536,7 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, if (cmdinfo->state & SUBMIT_DATA_IN_URB) { usb_anchor_urb(cmdinfo->data_in_urb, &devinfo->data_urbs); - err = usb_submit_urb(cmdinfo->data_in_urb, gfp); + err = usb_submit_urb(cmdinfo->data_in_urb, GFP_ATOMIC); if (err) { usb_unanchor_urb(cmdinfo->data_in_urb); uas_log_cmd_state(cmnd, "data in submit err", err); @@ -547,7 +547,7 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, } if (cmdinfo->state & ALLOC_DATA_OUT_URB) { - cmdinfo->data_out_urb = uas_alloc_data_urb(devinfo, gfp, + cmdinfo->data_out_urb = uas_alloc_data_urb(devinfo, GFP_ATOMIC, cmnd, DMA_TO_DEVICE); if (!cmdinfo->data_out_urb) return SCSI_MLQUEUE_DEVICE_BUSY; @@ -556,7 +556,7 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, if (cmdinfo->state & SUBMIT_DATA_OUT_URB) { usb_anchor_urb(cmdinfo->data_out_urb, &devinfo->data_urbs); - err = usb_submit_urb(cmdinfo->data_out_urb, gfp); + err = usb_submit_urb(cmdinfo->data_out_urb, GFP_ATOMIC); if (err) { usb_unanchor_urb(cmdinfo->data_out_urb); uas_log_cmd_state(cmnd, "data out submit err", err); @@ -567,7 +567,7 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, } if (cmdinfo->state & ALLOC_CMD_URB) { - cmdinfo->cmd_urb = uas_alloc_cmd_urb(devinfo, gfp, cmnd); + cmdinfo->cmd_urb = uas_alloc_cmd_urb(devinfo, GFP_ATOMIC, cmnd); if (!cmdinfo->cmd_urb) return SCSI_MLQUEUE_DEVICE_BUSY; cmdinfo->state &= ~ALLOC_CMD_URB; @@ -575,7 +575,7 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, if (cmdinfo->state & SUBMIT_CMD_URB) { usb_anchor_urb(cmdinfo->cmd_urb, &devinfo->cmd_urbs); - err = usb_submit_urb(cmdinfo->cmd_urb, gfp); + err = usb_submit_urb(cmdinfo->cmd_urb, GFP_ATOMIC); if (err) { usb_unanchor_urb(cmdinfo->cmd_urb); uas_log_cmd_state(cmnd, "cmd submit err", err); @@ -653,7 +653,7 @@ static int uas_queuecommand_lck(struct scsi_cmnd *cmnd, if (!devinfo->use_streams) cmdinfo->state &= ~(SUBMIT_DATA_IN_URB | SUBMIT_DATA_OUT_URB); - err = uas_submit_urbs(cmnd, devinfo, GFP_ATOMIC); + err = uas_submit_urbs(cmnd, devinfo); if (err) { /* If we did nothing, give up now */ if (cmdinfo->state & SUBMIT_STATUS_URB) { diff --git a/drivers/uwb/uwbd.c b/drivers/uwb/uwbd.c index bdcb13cc1d54..01c20a260a8b 100644 --- a/drivers/uwb/uwbd.c +++ b/drivers/uwb/uwbd.c @@ -279,7 +279,6 @@ static int uwbd(void *param) HZ); if (should_stop) break; - try_to_freeze(); spin_lock_irqsave(&rc->uwbd.event_list_lock, flags); if (!list_empty(&rc->uwbd.event_list)) { diff --git a/include/linux/phy/omap_usb.h b/include/linux/phy/omap_usb.h index dc2c541a619b..2e5fb870efa9 100644 --- a/include/linux/phy/omap_usb.h +++ b/include/linux/phy/omap_usb.h @@ -30,6 +30,12 @@ struct usb_dpll_params { u32 mf; }; +enum omap_usb_phy_type { + TYPE_USB2, /* USB2_PHY, power down in CONTROL_DEV_CONF */ + TYPE_DRA7USB2, /* USB2 PHY, power and power_aux e.g. DRA7 */ + TYPE_AM437USB2, /* USB2 PHY, power e.g. AM437x */ +}; + struct omap_usb { struct usb_phy phy; struct phy_companion *comparator; @@ -40,11 +46,20 @@ struct omap_usb { struct clk *wkupclk; struct clk *optclk; u8 flags; + enum omap_usb_phy_type type; + struct regmap *syscon_phy_power; /* ctrl. reg. acces */ + unsigned int power_reg; /* power reg. index within syscon */ + u32 mask; + u32 power_on; + u32 power_off; }; struct usb_phy_data { const char *label; u8 flags; + u32 mask; + u32 power_on; + u32 power_off; }; /* Driver Flags */ @@ -52,6 +67,14 @@ struct usb_phy_data { #define OMAP_USB2_HAS_SET_VBUS (1 << 1) #define OMAP_USB2_CALIBRATE_FALSE_DISCONNECT (1 << 2) +#define OMAP_DEV_PHY_PD BIT(0) +#define OMAP_USB2_PHY_PD BIT(28) + +#define AM437X_USB2_PHY_PD BIT(0) +#define AM437X_USB2_OTG_PD BIT(1) +#define AM437X_USB2_OTGVDET_EN BIT(19) +#define AM437X_USB2_OTGSESSEND_EN BIT(20) + #define phy_to_omapusb(x) container_of((x), struct omap_usb, phy) #if defined(CONFIG_OMAP_USB2) || defined(CONFIG_OMAP_USB2_MODULE) diff --git a/include/linux/platform_data/usb-rcar-phy.h b/include/linux/platform_data/usb-rcar-phy.h deleted file mode 100644 index 8ec6964a32a5..000000000000 --- a/include/linux/platform_data/usb-rcar-phy.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2013 Renesas Solutions Corp. - * Copyright (C) 2013 Cogent Embedded, Inc. - * - * 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. - */ - -#ifndef __USB_RCAR_PHY_H -#define __USB_RCAR_PHY_H - -#include - -struct rcar_phy_platform_data { - bool ferrite_bead:1; /* (R8A7778 only) */ - - bool port1_func:1; /* true: port 1 used by function, false: host */ - unsigned penc1:1; /* Output of the PENC1 pin in function mode */ - struct { /* Overcurrent pin control for ports 0..2 */ - bool select_3_3v:1; /* true: USB_OVCn pin, false: OVCn pin */ - /* Set to false on port 1 in function mode */ - bool active_high:1; /* true: active high, false: active low */ - /* Set to true on port 1 in function mode */ - } ovc_pin[3]; /* (R8A7778 only has 2 ports) */ -}; - -#endif /* __USB_RCAR_PHY_H */ diff --git a/include/linux/usb.h b/include/linux/usb.h index b9a28074210f..89533ba38691 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -510,7 +510,8 @@ struct usb3_lpm_parameters { * @usb2_hw_lpm_besl_capable: device can perform USB2 hardware BESL LPM * @usb2_hw_lpm_enabled: USB2 hardware LPM is enabled * @usb2_hw_lpm_allowed: Userspace allows USB 2.0 LPM to be enabled - * @usb3_lpm_enabled: USB3 hardware LPM enabled + * @usb3_lpm_u1_enabled: USB3 hardware U1 LPM enabled + * @usb3_lpm_u2_enabled: USB3 hardware U2 LPM enabled * @string_langid: language ID for strings * @product: iProduct string, if present (static) * @manufacturer: iManufacturer string, if present (static) @@ -583,7 +584,8 @@ struct usb_device { unsigned usb2_hw_lpm_besl_capable:1; unsigned usb2_hw_lpm_enabled:1; unsigned usb2_hw_lpm_allowed:1; - unsigned usb3_lpm_enabled:1; + unsigned usb3_lpm_u1_enabled:1; + unsigned usb3_lpm_u2_enabled:1; int string_langid; /* static strings from the device */ diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index 3d583a10b926..d82d0068872b 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -402,6 +402,9 @@ static inline void usb_ep_free_request(struct usb_ep *ep, static inline int usb_ep_queue(struct usb_ep *ep, struct usb_request *req, gfp_t gfp_flags) { + if (WARN_ON_ONCE(!ep->enabled && ep->address)) + return -ESHUTDOWN; + return ep->ops->queue(ep, req, gfp_flags); } @@ -1012,6 +1015,9 @@ static inline int usb_gadget_activate(struct usb_gadget *gadget) * @reset: Invoked on USB bus reset. It is mandatory for all gadget drivers * and should be called in_interrupt. * @driver: Driver model state for this driver. + * @udc_name: A name of UDC this driver should be bound to. If udc_name is NULL, + * this driver will be bound to any available UDC. + * @pending: UDC core private data used for deferred probe of this driver. * * Devices are disabled till a gadget driver successfully bind()s, which * means the driver will handle setup() requests needed to enumerate (and @@ -1072,6 +1078,9 @@ struct usb_gadget_driver { /* FIXME support safe rmmod */ struct device_driver driver; + + char *udc_name; + struct list_head pending; }; @@ -1117,8 +1126,6 @@ extern int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget, void (*release)(struct device *dev)); extern int usb_add_gadget_udc(struct device *parent, struct usb_gadget *gadget); extern void usb_del_gadget_udc(struct usb_gadget *gadget); -extern int usb_udc_attach_driver(const char *name, - struct usb_gadget_driver *driver); /*-------------------------------------------------------------------------*/ diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index f89c24bd53a4..4dcf8446dbcd 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -660,7 +660,7 @@ struct usb_mon_operations { /* void (*urb_unlink)(struct usb_bus *bus, struct urb *urb); */ }; -extern struct usb_mon_operations *mon_ops; +extern const struct usb_mon_operations *mon_ops; static inline void usbmon_urb_submit(struct usb_bus *bus, struct urb *urb) { @@ -682,7 +682,7 @@ static inline void usbmon_urb_complete(struct usb_bus *bus, struct urb *urb, (*mon_ops->urb_complete)(bus, urb, status); } -int usb_mon_register(struct usb_mon_operations *ops); +int usb_mon_register(const struct usb_mon_operations *ops); void usb_mon_deregister(void); #else diff --git a/include/linux/usb/musb-omap.h b/include/linux/usb/musb-omap.h deleted file mode 100644 index 7774c5986f07..000000000000 --- a/include/linux/usb/musb-omap.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2011-2012 by Texas Instruments - * - * The Inventra Controller Driver for Linux 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. - */ - -#ifndef __MUSB_OMAP_H__ -#define __MUSB_OMAP_H__ - -enum omap_musb_vbus_id_status { - OMAP_MUSB_UNKNOWN = 0, - OMAP_MUSB_ID_GROUND, - OMAP_MUSB_ID_FLOAT, - OMAP_MUSB_VBUS_VALID, - OMAP_MUSB_VBUS_OFF, -}; - -#if (defined(CONFIG_USB_MUSB_OMAP2PLUS) || \ - defined(CONFIG_USB_MUSB_OMAP2PLUS_MODULE)) -void omap_musb_mailbox(enum omap_musb_vbus_id_status status); -#else -static inline void omap_musb_mailbox(enum omap_musb_vbus_id_status status) -{ -} -#endif - -#endif /* __MUSB_OMAP_H__ */ diff --git a/include/linux/usb/musb.h b/include/linux/usb/musb.h index fa6dc132bd1b..96ddfb7ab018 100644 --- a/include/linux/usb/musb.h +++ b/include/linux/usb/musb.h @@ -133,6 +133,21 @@ struct musb_hdrc_platform_data { const void *platform_ops; }; +enum musb_vbus_id_status { + MUSB_UNKNOWN = 0, + MUSB_ID_GROUND, + MUSB_ID_FLOAT, + MUSB_VBUS_VALID, + MUSB_VBUS_OFF, +}; + +#if IS_ENABLED(CONFIG_USB_MUSB_HDRC) +void musb_mailbox(enum musb_vbus_id_status status); +#else +static inline void musb_mailbox(enum musb_vbus_id_status status) +{ +} +#endif /* TUSB 6010 support */ diff --git a/include/linux/usb/of.h b/include/linux/usb/of.h index c3fe9e48ce27..974bce93aa28 100644 --- a/include/linux/usb/of.h +++ b/include/linux/usb/of.h @@ -12,10 +12,16 @@ #include #if IS_ENABLED(CONFIG_OF) +enum usb_dr_mode of_usb_get_dr_mode_by_phy(struct device_node *phy_np); bool of_usb_host_tpl_support(struct device_node *np); int of_usb_update_otg_caps(struct device_node *np, struct usb_otg_caps *otg_caps); #else +static inline enum usb_dr_mode +of_usb_get_dr_mode_by_phy(struct device_node *phy_np) +{ + return USB_DR_MODE_UNKNOWN; +} static inline bool of_usb_host_tpl_support(struct device_node *np) { return false; diff --git a/include/linux/usb/renesas_usbhs.h b/include/linux/usb/renesas_usbhs.h index bfb74723f151..4db191fe8c2c 100644 --- a/include/linux/usb/renesas_usbhs.h +++ b/include/linux/usb/renesas_usbhs.h @@ -105,12 +105,26 @@ struct renesas_usbhs_platform_callback { * some register needs USB chip specific parameters. * This struct show it to driver */ + +struct renesas_usbhs_driver_pipe_config { + u8 type; /* USB_ENDPOINT_XFER_xxx */ + u16 bufsize; + u8 bufnum; + bool double_buf; +}; +#define RENESAS_USBHS_PIPE(_type, _size, _num, _double_buf) { \ + .type = (_type), \ + .bufsize = (_size), \ + .bufnum = (_num), \ + .double_buf = (_double_buf), \ + } + struct renesas_usbhs_driver_param { /* * pipe settings */ - u32 *pipe_type; /* array of USB_ENDPOINT_XFER_xxx (from ep0) */ - int pipe_size; /* pipe_type array size */ + struct renesas_usbhs_driver_pipe_config *pipe_configs; + int pipe_size; /* pipe_configs array size */ /* * option: