diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2017-07-03 19:30:55 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2017-07-03 19:30:55 -0700 |
commit | 362f6729cbb1d6bbab59e069f19441b0622ff7ec (patch) | |
tree | 654070221092c34c97f48fbbdef7d5f02ffc7ba4 | |
parent | 4422d80ed7d4bdb2d6e9fb890c66c3d9250ba694 (diff) | |
parent | 6836796de4019944f4ba4c99a360e8250fd2e735 (diff) | |
download | linux-362f6729cbb1d6bbab59e069f19441b0622ff7ec.tar.gz linux-362f6729cbb1d6bbab59e069f19441b0622ff7ec.tar.bz2 linux-362f6729cbb1d6bbab59e069f19441b0622ff7ec.zip |
Merge tag 'usb-4.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
Pull USB/PHY updates from Greg KH:
"Here is the big patchset of USB and PHY driver updates for 4.13-rc1.
On the PHY side, they decided to move files around to "make things
easier" in their tree. Hopefully that wasn't a mistake, but in
linux-next testing, we haven't had any reported problems.
There's the usual set of gadget and xhci and musb updates in here as
well, along with a number of smaller updates for a raft of different
USB drivers. Full details in the shortlog, nothing really major.
All of these have been in linux-next for a while with no reported
issues"
* tag 'usb-4.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (173 commits)
Add USB quirk for HVR-950q to avoid intermittent device resets
USB hub_probe: rework ugly goto-into-compound-statement
usb: host: ohci-pxa27x: Handle return value of clk_prepare_enable
USB: serial: cp210x: add ID for CEL EM3588 USB ZigBee stick
usbip: Fix uninitialized variable bug in vhci
usb: core: read USB ports from DT in the usbport LED trigger driver
dt-bindings: leds: document new trigger-sources property
usb: typec: ucsi: Add ACPI driver
usb: typec: Add support for UCSI interface
usb: musb: compress return logic into one line
USB: serial: propagate late probe errors
USB: serial: refactor port endpoint setup
usb: musb: tusb6010_omap: Convert to DMAengine API
ARM: OMAP2+: DMA: Add slave map entries for 24xx external request lines
usb: musb: tusb6010: Handle DMA TX completion in DMA callback as well
usb: musb: tusb6010_omap: Allocate DMA channels upfront
usb: musb: tusb6010_omap: Create new struct for DMA data/parameters
usb: musb: tusb6010_omap: Use one musb_ep_select call in tusb_omap_dma_program
usb: musb: tusb6010: Add MUSB_G_NO_SKB_RESERVE to quirks
usb: musb: Add quirk to avoid skb reserve in gadget mode
...
245 files changed, 11039 insertions, 4437 deletions
diff --git a/Documentation/ABI/stable/sysfs-class-udc b/Documentation/ABI/stable/sysfs-class-udc index 85d3dac2e204..d1e2f3ec1fc9 100644 --- a/Documentation/ABI/stable/sysfs-class-udc +++ b/Documentation/ABI/stable/sysfs-class-udc @@ -55,14 +55,6 @@ Description: Indicates the maximum USB speed supported by this port. Users: -What: /sys/class/udc/<udc>/maximum_speed -Date: June 2011 -KernelVersion: 3.1 -Contact: Felipe Balbi <balbi@kernel.org> -Description: - Indicates the maximum USB speed supported by this port. -Users: - What: /sys/class/udc/<udc>/soft_connect Date: June 2011 KernelVersion: 3.1 @@ -91,3 +83,11 @@ Description: 'configured', and 'suspended'; however not all USB Device Controllers support reporting all states. Users: + +What: /sys/class/udc/<udc>/function +Date: June 2017 +KernelVersion: 4.13 +Contact: Felipe Balbi <balbi@kernel.org> +Description: + Prints out name of currently running USB Gadget Driver. +Users: diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac1 b/Documentation/ABI/testing/configfs-usb-gadget-uac1 index 8ba9a123316e..abfe447c848f 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-uac1 +++ b/Documentation/ABI/testing/configfs-usb-gadget-uac1 @@ -1,12 +1,14 @@ What: /config/usb-gadget/gadget/functions/uac1.name -Date: Sep 2014 -KernelVersion: 3.18 +Date: June 2017 +KernelVersion: 4.14 Description: The attributes: - audio_buf_size - audio buffer size - fn_cap - capture pcm device file name - fn_cntl - control device file name - fn_play - playback pcm device file name - req_buf_size - ISO OUT endpoint request buffer size - req_count - ISO OUT endpoint request count + c_chmask - capture channel mask + c_srate - capture sampling rate + c_ssize - capture sample size (bytes) + p_chmask - playback channel mask + p_srate - playback sampling rate + p_ssize - playback sample size (bytes) + req_number - the number of pre-allocated request + for both capture and playback diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac1_legacy b/Documentation/ABI/testing/configfs-usb-gadget-uac1_legacy new file mode 100644 index 000000000000..b2eaefd9bc49 --- /dev/null +++ b/Documentation/ABI/testing/configfs-usb-gadget-uac1_legacy @@ -0,0 +1,12 @@ +What: /config/usb-gadget/gadget/functions/uac1_legacy.name +Date: Sep 2014 +KernelVersion: 3.18 +Description: + The attributes: + + audio_buf_size - audio buffer size + fn_cap - capture pcm device file name + fn_cntl - control device file name + fn_play - playback pcm device file name + req_buf_size - ISO OUT endpoint request buffer size + req_count - ISO OUT endpoint request count diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec index d4a3d23eb09c..5be552e255e9 100644 --- a/Documentation/ABI/testing/sysfs-class-typec +++ b/Documentation/ABI/testing/sysfs-class-typec @@ -30,6 +30,21 @@ Description: Valid values: source, sink +What: /sys/class/typec/<port>/port_type +Date: May 2017 +Contact: Badhri Jagan Sridharan <Badhri@google.com> +Description: + Indicates the type of the port. This attribute can be used for + requesting a change in the port type. Port type change is + supported as a synchronous operation, so write(2) to the + attribute will not return until the operation has finished. + + Valid values: + - source (The port will behave as source only DFP port) + - sink (The port will behave as sink only UFP port) + - dual (The port will behave as dual-role-data and + dual-role-power port) + What: /sys/class/typec/<port>/vconn_source Date: April 2017 Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> diff --git a/Documentation/devicetree/bindings/leds/common.txt b/Documentation/devicetree/bindings/leds/common.txt index 24b656014089..1d4afe9644b6 100644 --- a/Documentation/devicetree/bindings/leds/common.txt +++ b/Documentation/devicetree/bindings/leds/common.txt @@ -1,4 +1,4 @@ -Common leds properties. +* Common leds properties. LED and flash LED devices provide the same basic functionality as current regulators, but extended with LED and flash LED specific features like @@ -49,6 +49,22 @@ Optional properties for child nodes: - panic-indicator : This property specifies that the LED should be used, if at all possible, as a panic indicator. +- trigger-sources : List of devices which should be used as a source triggering + this LED activity. Some LEDs can be related to a specific + device and should somehow indicate its state. E.g. USB 2.0 + LED may react to device(s) in a USB 2.0 port(s). + Another common example is switch or router with multiple + Ethernet ports each of them having its own LED assigned + (assuming they are not hardwired). In such cases this + property should contain phandle(s) of related source + device(s). + In many cases LED can be related to more than one device + (e.g. one USB LED vs. multiple USB ports). Each source + should be represented by a node in the device tree and be + referenced by a phandle and a set of phandle arguments. A + length of arguments should be specified by the + #trigger-source-cells property in the source node. + Required properties for flash LED child nodes: - flash-max-microamp : Maximum flash LED supply current in microamperes. - flash-max-timeout-us : Maximum timeout in microseconds after which the flash @@ -59,7 +75,17 @@ property can be omitted. For controllers that have no configurable timeout the flash-max-timeout-us property can be omitted. -Examples: +* Trigger source providers + +Each trigger source should be represented by a device tree node. It may be e.g. +a USB port or an Ethernet device. + +Required properties for trigger source: +- #trigger-source-cells : Number of cells in a source trigger. Typically 0 for + nodes of simple trigger sources (e.g. a specific USB + port). + +* Examples gpio-leds { compatible = "gpio-leds"; @@ -69,6 +95,11 @@ gpio-leds { linux,default-trigger = "heartbeat"; gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>; }; + + usb { + gpios = <&gpio0 1 GPIO_ACTIVE_HIGH>; + trigger-sources = <&ohci_port1>, <&ehci_port1>; + }; }; max77693-led { diff --git a/Documentation/devicetree/bindings/phy/bcm-ns-usb3-phy.txt b/Documentation/devicetree/bindings/phy/bcm-ns-usb3-phy.txt index 09aeba94538d..32f057260351 100644 --- a/Documentation/devicetree/bindings/phy/bcm-ns-usb3-phy.txt +++ b/Documentation/devicetree/bindings/phy/bcm-ns-usb3-phy.txt @@ -3,9 +3,10 @@ Driver for Broadcom Northstar USB 3.0 PHY Required properties: - compatible: one of: "brcm,ns-ax-usb3-phy", "brcm,ns-bx-usb3-phy". -- reg: register mappings for DMP (Device Management Plugin) and ChipCommon B - MMI. -- reg-names: "dmp" and "ccb-mii" +- reg: address of MDIO bus device +- usb3-dmp-syscon: phandle to syscon with DMP (Device Management Plugin) + registers +- #phy-cells: must be 0 Initialization of USB 3.0 PHY depends on Northstar version. There are currently three known series: Ax, Bx and Cx. @@ -15,9 +16,19 @@ Known B1: BCM4707 rev 6 Known C0: BCM47094 rev 0 Example: - usb3-phy { - compatible = "brcm,ns-ax-usb3-phy"; - reg = <0x18105000 0x1000>, <0x18003000 0x1000>; - reg-names = "dmp", "ccb-mii"; - #phy-cells = <0>; + mdio: mdio@0 { + reg = <0x0>; + #size-cells = <1>; + #address-cells = <0>; + + usb3-phy@10 { + compatible = "brcm,ns-ax-usb3-phy"; + reg = <0x10>; + usb3-dmp-syscon = <&usb3_dmp>; + #phy-cells = <0>; + }; + }; + + usb3_dmp: syscon@18105000 { + reg = <0x18105000 0x1000>; }; diff --git a/Documentation/devicetree/bindings/phy/brcm,ns2-drd-phy.txt b/Documentation/devicetree/bindings/phy/brcm,ns2-drd-phy.txt new file mode 100644 index 000000000000..04f063aa7883 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/brcm,ns2-drd-phy.txt @@ -0,0 +1,30 @@ +BROADCOM NORTHSTAR2 USB2 (DUAL ROLE DEVICE) PHY + +Required properties: + - compatible: brcm,ns2-drd-phy + - reg: offset and length of the NS2 PHY related registers. + - reg-names + The below registers must be provided. + icfg - for DRD ICFG configurations + rst-ctrl - for DRD IDM reset + crmu-ctrl - for CRMU core vdd, PHY and PHY PLL reset + usb2-strap - for port over current polarity reversal + - #phy-cells: Must be 0. No args required. + - vbus-gpios: vbus gpio binding + - id-gpios: id gpio binding + +Refer to phy/phy-bindings.txt for the generic PHY binding properties + +Example: + usbdrd_phy: phy@66000960 { + #phy-cells = <0>; + compatible = "brcm,ns2-drd-phy"; + reg = <0x66000960 0x24>, + <0x67012800 0x4>, + <0x6501d148 0x4>, + <0x664d0700 0x4>; + reg-names = "icfg", "rst-ctrl", + "crmu-ctrl", "usb2-strap"; + id-gpios = <&gpio_g 30 0>; + vbus-gpios = <&gpio_g 31 0>; + }; diff --git a/Documentation/devicetree/bindings/phy/brcm-sata-phy.txt b/Documentation/devicetree/bindings/phy/brcm-sata-phy.txt index 6ccce09d8bbf..97977cd29a98 100644 --- a/Documentation/devicetree/bindings/phy/brcm-sata-phy.txt +++ b/Documentation/devicetree/bindings/phy/brcm-sata-phy.txt @@ -7,12 +7,13 @@ Required properties: "brcm,iproc-ns2-sata-phy" "brcm,iproc-nsp-sata-phy" "brcm,phy-sata3" + "brcm,iproc-sr-sata-phy" - address-cells: should be 1 - size-cells: should be 0 - reg: register ranges for the PHY PCB interface - reg-names: should be "phy" and "phy-ctrl" The "phy-ctrl" registers are only required for - "brcm,iproc-ns2-sata-phy". + "brcm,iproc-ns2-sata-phy" and "brcm,iproc-sr-sata-phy". Sub-nodes: Each port's PHY should be represented as a sub-node. @@ -23,8 +24,8 @@ Sub-nodes required properties: Sub-nodes optional properties: - brcm,enable-ssc: use spread spectrum clocking (SSC) on this port - This property is not applicable for "brcm,iproc-ns2-sata-phy" and - "brcm,iproc-nsp-sata-phy". + This property is not applicable for "brcm,iproc-ns2-sata-phy", + "brcm,iproc-nsp-sata-phy" and "brcm,iproc-sr-sata-phy". Example: sata-phy@f0458100 { diff --git a/Documentation/devicetree/bindings/phy/meson-gxl-usb2-phy.txt b/Documentation/devicetree/bindings/phy/meson-gxl-usb2-phy.txt new file mode 100644 index 000000000000..a105494a0fc9 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/meson-gxl-usb2-phy.txt @@ -0,0 +1,17 @@ +* Amlogic Meson GXL and GXM USB2 PHY binding + +Required properties: +- compatible: Should be "amlogic,meson-gxl-usb2-phy" +- reg: The base address and length of the registers +- #phys-cells: must be 0 (see phy-bindings.txt in this directory) + +Optional properties: +- phy-supply: see phy-bindings.txt in this directory + + +Example: + usb2_phy0: phy@78000 { + compatible = "amlogic,meson-gxl-usb2-phy"; + #phy-cells = <0>; + reg = <0x0 0x78000 0x0 0x20>; + }; diff --git a/Documentation/devicetree/bindings/phy/meson8b-usb2-phy.txt b/Documentation/devicetree/bindings/phy/meson8b-usb2-phy.txt index 5fa73b9d20f5..d81d73aea608 100644 --- a/Documentation/devicetree/bindings/phy/meson8b-usb2-phy.txt +++ b/Documentation/devicetree/bindings/phy/meson8b-usb2-phy.txt @@ -1,7 +1,8 @@ -* Amlogic Meson8b and GXBB USB2 PHY +* Amlogic Meson8, Meson8b and GXBB USB2 PHY Required properties: - compatible: Depending on the platform this should be one of: + "amlogic,meson8-usb2-phy" "amlogic,meson8b-usb2-phy" "amlogic,meson-gxbb-usb2-phy" - reg: The base address and length of the registers diff --git a/Documentation/devicetree/bindings/phy/phy-cpcap-usb.txt b/Documentation/devicetree/bindings/phy/phy-cpcap-usb.txt new file mode 100644 index 000000000000..2eb9b2b69037 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/phy-cpcap-usb.txt @@ -0,0 +1,40 @@ +Motorola CPCAP PMIC USB PHY binding + +Required properties: +compatible: Shall be either "motorola,cpcap-usb-phy" or + "motorola,mapphone-cpcap-usb-phy" +#phy-cells: Shall be 0 +interrupts: CPCAP PMIC interrupts used by the USB PHY +interrupt-names: Interrupt names +io-channels: IIO ADC channels used by the USB PHY +io-channel-names: IIO ADC channel names +vusb-supply: Regulator for the PHY + +Optional properties: +pinctrl: Optional alternate pin modes for the PHY +pinctrl-names: Names for optional pin modes +mode-gpios: Optional GPIOs for configuring alternate modes + +Example: +cpcap_usb2_phy: phy { + compatible = "motorola,mapphone-cpcap-usb-phy"; + pinctrl-0 = <&usb_gpio_mux_sel1 &usb_gpio_mux_sel2>; + pinctrl-1 = <&usb_ulpi_pins>; + pinctrl-2 = <&usb_utmi_pins>; + pinctrl-3 = <&uart3_pins>; + pinctrl-names = "default", "ulpi", "utmi", "uart"; + #phy-cells = <0>; + interrupts-extended = < + &cpcap 15 0 &cpcap 14 0 &cpcap 28 0 &cpcap 19 0 + &cpcap 18 0 &cpcap 17 0 &cpcap 16 0 &cpcap 49 0 + &cpcap 48 1 + >; + interrupt-names = + "id_ground", "id_float", "se0conn", "vbusvld", + "sessvld", "sessend", "se1", "dm", "dp"; + mode-gpios = <&gpio2 28 GPIO_ACTIVE_HIGH + &gpio1 0 GPIO_ACTIVE_HIGH>; + io-channels = <&cpcap_adc 2>, <&cpcap_adc 7>; + io-channel-names = "vbus", "id"; + vusb-supply = <&vusb>; +}; diff --git a/Documentation/devicetree/bindings/phy/phy-rockchip-inno-usb2.txt b/Documentation/devicetree/bindings/phy/phy-rockchip-inno-usb2.txt index e71a8d23f4a8..84d59b0db8df 100644 --- a/Documentation/devicetree/bindings/phy/phy-rockchip-inno-usb2.txt +++ b/Documentation/devicetree/bindings/phy/phy-rockchip-inno-usb2.txt @@ -2,6 +2,7 @@ ROCKCHIP USB2.0 PHY WITH INNO IP BLOCK Required properties (phy (parent) node): - compatible : should be one of the listed compatibles: + * "rockchip,rk3228-usb2phy" * "rockchip,rk3328-usb2phy" * "rockchip,rk3366-usb2phy" * "rockchip,rk3399-usb2phy" diff --git a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb3.txt b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb3.txt new file mode 100644 index 000000000000..f94cea48f6b1 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb3.txt @@ -0,0 +1,46 @@ +* Renesas R-Car generation 3 USB 3.0 PHY + +This file provides information on what the device node for the R-Car generation +3 USB 3.0 PHY contains. +If you want to enable spread spectrum clock (ssc), you should use USB_EXTAL +instead of USB3_CLK. However, if you don't want to these features, you don't +need this driver. + +Required properties: +- compatible: "renesas,r8a7795-usb3-phy" if the device is a part of an R8A7795 + SoC. + "renesas,r8a7796-usb3-phy" if the device is a part of an R8A7796 + SoC. + "renesas,rcar-gen3-usb3-phy" for a generic 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: offset and length of the USB 3.0 PHY register block. +- clocks: A list of phandles and clock-specifier pairs. +- clock-names: Name of the clocks. + - The funcional clock must be "usb3-if". + - The usb3's external clock must be "usb3s_clk". + - The usb2's external clock must be "usb_extal". If you want to use the ssc, + the clock-frequency must not be 0. +- #phy-cells: see phy-bindings.txt in the same directory, must be <0>. + +Optional properties: +- renesas,ssc-range: Enable/disable spread spectrum clock (ssc) by using + the following values as u32: + - 0 (or the property doesn't exist): disable the ssc + - 4980: enable the ssc as -4980 ppm + - 4492: enable the ssc as -4492 ppm + - 4003: enable the ssc as -4003 ppm + +Example (R-Car H3): + + usb-phy@e65ee000 { + compatible = "renesas,r8a7795-usb3-phy", + "renesas,rcar-gen3-usb3-phy"; + reg = <0 0xe65ee000 0 0x90>; + clocks = <&cpg CPG_MOD 328>, <&usb3s0_clk>, <&usb_extal>; + clock-names = "usb3-if", "usb3s_clk", "usb_extal"; + }; diff --git a/Documentation/devicetree/bindings/usb/dwc3.txt b/Documentation/devicetree/bindings/usb/dwc3.txt index f658f394c2d3..52fb41046b34 100644 --- a/Documentation/devicetree/bindings/usb/dwc3.txt +++ b/Documentation/devicetree/bindings/usb/dwc3.txt @@ -45,6 +45,8 @@ Optional properties: a free-running PHY clock. - snps,dis-del-phy-power-chg-quirk: when set core will change PHY power from P0 to P1/P2/P3 without delay. + - snps,dis-tx-ipgap-linecheck-quirk: when set, disable u2mac linestate check + during HS transmit. - snps,is-utmi-l1-suspend: true when DWC3 asserts output signal utmi_l1_suspend_n, false when asserts utmi_sleep_n - snps,hird-threshold: HIRD threshold diff --git a/Documentation/devicetree/bindings/usb/iproc-udc.txt b/Documentation/devicetree/bindings/usb/iproc-udc.txt new file mode 100644 index 000000000000..272d7faf1a97 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/iproc-udc.txt @@ -0,0 +1,21 @@ +Broadcom IPROC USB Device controller. + +The device node is used for UDCs integrated into Broadcom's +iProc family (Northstar2, Cygnus) of SoCs'. The UDC is based +on Synopsys Designware Cores AHB Subsystem Device Controller +IP. + +Required properties: + - compatible: Add the compatibility strings for supported platforms. + For Broadcom NS2 platform, add "brcm,ns2-udc","brcm,iproc-udc". + For Broadcom Cygnus platform, add "brcm,cygnus-udc", "brcm,iproc-udc". + - reg: Offset and length of UDC register set + - interrupts: description of interrupt line + - phys: phandle to phy node. + +Example: + udc_dwc: usb@664e0000 { + compatible = "brcm,ns2-udc", "brcm,iproc-udc"; + reg = <0x664e0000 0x2000>; + interrupts = <GIC_SPI 424 IRQ_TYPE_LEVEL_HIGH>; + phys = <&usbdrd_phy>; diff --git a/Documentation/devicetree/bindings/usb/usb-ohci.txt b/Documentation/devicetree/bindings/usb/usb-ohci.txt index 9df456968596..e8766b08c93b 100644 --- a/Documentation/devicetree/bindings/usb/usb-ohci.txt +++ b/Documentation/devicetree/bindings/usb/usb-ohci.txt @@ -10,6 +10,7 @@ Optional properties: - big-endian-desc : boolean, set this for hcds with big-endian descriptors - big-endian : boolean, for hcds with big-endian-regs + big-endian-desc - no-big-frame-no : boolean, set if frame_no lives in bits [15:0] of HCCA +- remote-wakeup-connected: remote wakeup is wired on the platform - num-ports : u32, to override the detected port count - clocks : a list of phandle + clock specifier pairs - phys : phandle + phy specifier pair diff --git a/Documentation/driver-api/usb/dwc3.rst b/Documentation/driver-api/usb/dwc3.rst new file mode 100644 index 000000000000..c3dc84a50ce5 --- /dev/null +++ b/Documentation/driver-api/usb/dwc3.rst @@ -0,0 +1,712 @@ +=============================================================== +Synopsys DesignWare Core SuperSpeed USB 3.0 Controller +=============================================================== + +:Author: Felipe Balbi <felipe.balbi@linux.intel.com> +:Date: April 2017 + +Introduction +============ + +The *Synopsys DesignWare Core SuperSpeed USB 3.0 Controller* +(hereinafter referred to as *DWC3*) is a USB SuperSpeed compliant +controller which can be configured in one of 4 ways: + + 1. Peripheral-only configuration + 2. Host-only configuration + 3. Dual-Role configuration + 4. Hub configuration + +Linux currently supports several versions of this controller. In all +likelyhood, the version in your SoC is already supported. At the time +of this writing, known tested versions range from 2.02a to 3.10a. As a +rule of thumb, anything above 2.02a should work reliably well. + +Currently, we have many known users for this driver. In alphabetical +order: + + 1. Cavium + 2. Intel Corporation + 3. Qualcomm + 4. Rockchip + 5. ST + 6. Samsung + 7. Texas Instruments + 8. Xilinx + +Summary of Features +====================== + +For details about features supported by your version of DWC3, consult +your IP team and/or *Synopsys DesignWare Core SuperSpeed USB 3.0 +Controller Databook*. Following is a list of features supported by the +driver at the time of this writing: + + 1. Up to 16 bidirectional endpoints (including the control + pipe - ep0) + 2. Flexible endpoint configuration + 3. Simultaneous IN and OUT transfer support + 4. Scatter-list support + 5. Up to 256 TRBs [#trb]_ per endpoint + 6. Support for all transfer types (*Control*, *Bulk*, + *Interrupt*, and *Isochronous*) + 7. SuperSpeed Bulk Streams + 8. Link Power Management + 9. Trace Events for debugging + 10. DebugFS [#debugfs]_ interface + +These features have all been exercised with many of the **in-tree** +gadget drivers. We have verified both *ConfigFS* [#configfs]_ and +legacy gadget drivers. + +Driver Design +============== + +The DWC3 driver sits on the *drivers/usb/dwc3/* directory. All files +related to this driver are in this one directory. This makes it easy +for new-comers to read the code and understand how it behaves. + +Because of DWC3's configuration flexibility, the driver is a little +complex in some places but it should be rather straightforward to +understand. + +The biggest part of the driver refers to the Gadget API. + +Known Limitations +=================== + +Like any other HW, DWC3 has its own set of limitations. To avoid +constant questions about such problems, we decided to document them +here and have a single location to where we could point users. + +OUT Transfer Size Requirements +--------------------------------- + +According to Synopsys Databook, all OUT transfer TRBs [#trb]_ must +have their *size* field set to a value which is integer divisible by +the endpoint's *wMaxPacketSize*. This means that *e.g.* in order to +receive a Mass Storage *CBW* [#cbw]_, req->length must either be set +to a value that's divisible by *wMaxPacketSize* (1024 on SuperSpeed, +512 on HighSpeed, etc), or DWC3 driver must add a Chained TRB pointing +to a throw-away buffer for the remaining length. Without this, OUT +transfers will **NOT** start. + +Note that as of this writing, this won't be a problem because DWC3 is +fully capable of appending a chained TRB for the remaining length and +completely hide this detail from the gadget driver. It's still worth +mentioning because this seems to be the largest source of queries +about DWC3 and *non-working transfers*. + +TRB Ring Size Limitation +------------------------- + +We, currently, have a hard limit of 256 TRBs [#trb]_ per endpoint, +with the last TRB being a Link TRB [#link_trb]_ pointing back to the +first. This limit is arbitrary but it has the benefit of adding up to +exactly 4096 bytes, or 1 Page. + +DWC3 driver will try its best to cope with more than 255 requests and, +for the most part, it should work normally. However this is not +something that has been exercised very frequently. If you experience +any problems, see section **Reporting Bugs** below. + +Reporting Bugs +================ + +Whenever you encounter a problem with DWC3, first and foremost you +should make sure that: + + 1. You're running latest tag from `Linus' tree`_ + 2. You can reproduce the error without any out-of-tree changes + to DWC3 + 3. You have checked that it's not a fault on the host machine + +After all these are verified, then here's how to capture enough +information so we can be of any help to you. + +Required Information +--------------------- + +DWC3 relies exclusively on Trace Events for debugging. Everything is +exposed there, with some extra bits being exposed to DebugFS +[#debugfs]_. + +In order to capture DWC3's Trace Events you should run the following +commands **before** plugging the USB cable to a host machine: + +.. code-block:: sh + + # mkdir -p /d + # mkdir -p /t + # mount -t debugfs none /d + # mount -t tracefs none /t + # echo 81920 > /t/buffer_size_kb + # echo 1 > /t/events/dwc3/enable + +After this is done, you can connect your USB cable and reproduce the +problem. As soon as the fault is reproduced, make a copy of files +``trace`` and ``regdump``, like so: + +.. code-block:: sh + + # cp /t/trace /root/trace.txt + # cat /d/*dwc3*/regdump > /root/regdump.txt + +Make sure to compress ``trace.txt`` and ``regdump.txt`` in a tarball +and email it to `me`_ with `linux-usb`_ in Cc. If you want to be extra +sure that I'll help you, write your subject line in the following +format: + + **[BUG REPORT] usb: dwc3: Bug while doing XYZ** + +On the email body, make sure to detail what you doing, which gadget +driver you were using, how to reproduce the problem, what SoC you're +using, which OS (and its version) was running on the Host machine. + +With all this information, we should be able to understand what's +going on and be helpful to you. + +Debugging +=========== + +First and foremost a disclaimer:: + + DISCLAIMER: The information available on DebugFS and/or TraceFS can + change at any time at any Major Linux Kernel Release. If writing + scripts, do **NOT** assume information to be available in the + current format. + +With that out of the way, let's carry on. + +If you're willing to debug your own problem, you deserve a round of +applause :-) + +Anyway, there isn't much to say here other than Trace Events will be +really helpful in figuring out issues with DWC3. Also, access to +Synopsys Databook will be **really** valuable in this case. + +A USB Sniffer can be helpful at times but it's not entirely required, +there's a lot that can be understood without looking at the wire. + +Feel free to email `me`_ and Cc `linux-usb`_ if you need any help. + +``DebugFS`` +------------- + +``DebugFS`` is very good for gathering snapshots of what's going on +with DWC3 and/or any endpoint. + +On DWC3's ``DebugFS`` directory, you will find the following files and +directories: + +``ep[0..15]{in,out}/`` +``link_state`` +``regdump`` +``testmode`` + +``link_state`` +`````````````` + +When read, ``link_state`` will print out one of ``U0``, ``U1``, +``U2``, ``U3``, ``SS.Disabled``, ``RX.Detect``, ``SS.Inactive``, +``Polling``, ``Recovery``, ``Hot Reset``, ``Compliance``, +``Loopback``, ``Reset``, ``Resume`` or ``UNKNOWN link state``. + +This file can also be written to in order to force link to one of the +states above. + +``regdump`` +````````````` + +File name is self-explanatory. When read, ``regdump`` will print out a +register dump of DWC3. Note that this file can be grepped to find the +information you want. + +``testmode`` +`````````````` + +When read, ``testmode`` will print out a name of one of the specified +USB 2.0 Testmodes (``test_j``, ``test_k``, ``test_se0_nak``, +``test_packet``, ``test_force_enable``) or the string ``no test`` in +case no tests are currently being executed. + +In order to start any of these test modes, the same strings can be +written to the file and DWC3 will enter the requested test mode. + + +``ep[0..15]{in,out}`` +`````````````````````` + +For each endpoint we expose one directory following the naming +convention ``ep$num$dir`` *(ep0in, ep0out, ep1in, ...)*. Inside each +of these directories you will find the following files: + +``descriptor_fetch_queue`` +``event_queue`` +``rx_fifo_queue`` +``rx_info_queue`` +``rx_request_queue`` +``transfer_type`` +``trb_ring`` +``tx_fifo_queue`` +``tx_request_queue`` + +With access to Synopsys Databook, you can decode the information on +them. + +``transfer_type`` +~~~~~~~~~~~~~~~~~~ + +When read, ``transfer_type`` will print out one of ``control``, +``bulk``, ``interrupt`` or ``isochronous`` depending on what the +endpoint descriptor says. If the endpoint hasn't been enabled yet, it +will print ``--``. + +``trb_ring`` +~~~~~~~~~~~~~ + +When read, ``trb_ring`` will print out details about all TRBs on the +ring. It will also tell you where our enqueue and dequeue pointers are +located in the ring: + +.. code-block:: sh + + buffer_addr,size,type,ioc,isp_imi,csp,chn,lst,hwo + 000000002c754000,481,normal,1,0,1,0,0,0 + 000000002c75c000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c78c000,481,normal,1,0,1,0,0,0 + 000000002c754000,481,normal,1,0,1,0,0,0 + 000000002c75c000,481,normal,1,0,1,0,0,0 + 000000002c784000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c78c000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c784000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c78c000,481,normal,1,0,1,0,0,0 + 000000002c754000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c784000,481,normal,1,0,1,0,0,0 + 000000002c78c000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c78c000,481,normal,1,0,1,0,0,0 + 000000002c784000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c78c000,481,normal,1,0,1,0,0,0 + 000000002c754000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c78c000,481,normal,1,0,1,0,0,0 + 000000002c75c000,481,normal,1,0,1,0,0,0 + 000000002c78c000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c754000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c754000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c78c000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c754000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c75c000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c784000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c78c000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c754000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c75c000,512,normal,1,0,1,0,0,1 D + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 E + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 00000000381ab000,0,link,0,0,0,0,0,1 + + +Trace Events +------------- + +DWC3 also provides several trace events which help us gathering +information about the behavior of the driver during runtime. + +In order to use these events, you must enable ``CONFIG_FTRACE`` in +your kernel config. + +For details about how enable DWC3 events, see section **Reporting +Bugs**. + +The following subsections will give details about each Event Class and +each Event defined by DWC3. + +MMIO +``````` + +It is sometimes useful to look at every MMIO access when looking for +bugs. Because of that, DWC3 offers two Trace Events (one for +dwc3_readl() and one for dwc3_writel()). ``TP_printk`` follows:: + + TP_printk("addr %p value %08x", __entry->base + __entry->offset, + __entry->value) + +Interrupt Events +```````````````` + +Every IRQ event can be logged and decoded into a human readable +string. Because every event will be different, we don't give an +example other than the ``TP_printk`` format used:: + + TP_printk("event (%08x): %s", __entry->event, + dwc3_decode_event(__entry->event, __entry->ep0state)) + +Control Request +````````````````` + +Every USB Control Request can be logged to the trace buffer. The +output format is:: + + TP_printk("%s", dwc3_decode_ctrl(__entry->bRequestType, + __entry->bRequest, __entry->wValue, + __entry->wIndex, __entry->wLength) + ) + +Note that Standard Control Requests will be decoded into +human-readable strings with their respective arguments. Class and +Vendor requests will be printed out a sequence of 8 bytes in hex +format. + +Lifetime of a ``struct usb_request`` +``````````````````````````````````````` + +The entire lifetime of a ``struct usb_request`` can be tracked on the +trace buffer. We have one event for each of allocation, free, +queueing, dequeueing, and giveback. Output format is:: + + 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 + ) + +Generic Commands +```````````````````` + +We can log and decode every Generic Command with its completion +code. Format is:: + + TP_printk("cmd '%s' [%x] param %08x --> status: %s", + dwc3_gadget_generic_cmd_string(__entry->cmd), + __entry->cmd, __entry->param, + dwc3_gadget_generic_cmd_status_string(__entry->status) + ) + +Endpoint Commands +```````````````````` + +Endpoints commands can also be logged together with completion +code. Format is:: + + TP_printk("%s: cmd '%s' [%d] params %08x %08x %08x --> status: %s", + __get_str(name), dwc3_gadget_ep_cmd_string(__entry->cmd), + __entry->cmd, __entry->param0, + __entry->param1, __entry->param2, + dwc3_ep_cmd_status_string(__entry->cmd_status) + ) + +Lifetime of a ``TRB`` +`````````````````````` + +A ``TRB`` Lifetime is simple. We are either preparing a ``TRB`` or +completing it. With these two events, we can see how a ``TRB`` changes +over time. Format is:: + + TP_printk("%s: %d/%d trb %p buf %08x%08x size %s%d ctrl %08x (%c%c%c%c:%c%c:%s)", + __get_str(name), __entry->queued, __entry->allocated, + __entry->trb, __entry->bph, __entry->bpl, + ({char *s; + int pcm = ((__entry->size >> 24) & 3) + 1; + switch (__entry->type) { + case USB_ENDPOINT_XFER_INT: + case USB_ENDPOINT_XFER_ISOC: + switch (pcm) { + case 1: + s = "1x "; + break; + case 2: + s = "2x "; + break; + case 3: + s = "3x "; + break; + } + default: + s = ""; + } s; }), + DWC3_TRB_SIZE_LENGTH(__entry->size), __entry->ctrl, + __entry->ctrl & DWC3_TRB_CTRL_HWO ? 'H' : 'h', + __entry->ctrl & DWC3_TRB_CTRL_LST ? 'L' : 'l', + __entry->ctrl & DWC3_TRB_CTRL_CHN ? 'C' : 'c', + __entry->ctrl & DWC3_TRB_CTRL_CSP ? 'S' : 's', + __entry->ctrl & DWC3_TRB_CTRL_ISP_IMI ? 'S' : 's', + __entry->ctrl & DWC3_TRB_CTRL_IOC ? 'C' : 'c', + dwc3_trb_type_string(DWC3_TRBCTL_TYPE(__entry->ctrl)) + ) + +Lifetime of an Endpoint +``````````````````````` + +And endpoint's lifetime is summarized with enable and disable +operations, both of which can be traced. Format is:: + + TP_printk("%s: mps %d/%d streams %d burst %d ring %d/%d flags %c:%c%c%c%c%c:%c:%c", + __get_str(name), __entry->maxpacket, + __entry->maxpacket_limit, __entry->max_streams, + __entry->maxburst, __entry->trb_enqueue, + __entry->trb_dequeue, + __entry->flags & DWC3_EP_ENABLED ? 'E' : 'e', + __entry->flags & DWC3_EP_STALL ? 'S' : 's', + __entry->flags & DWC3_EP_WEDGE ? 'W' : 'w', + __entry->flags & DWC3_EP_BUSY ? 'B' : 'b', + __entry->flags & DWC3_EP_PENDING_REQUEST ? 'P' : 'p', + __entry->flags & DWC3_EP_MISSED_ISOC ? 'M' : 'm', + __entry->flags & DWC3_EP_END_TRANSFER_PENDING ? 'E' : 'e', + __entry->direction ? '<' : '>' + ) + + +Structures, Methods and Definitions +==================================== + +.. kernel-doc:: drivers/usb/dwc3/core.h + :doc: main data structures + :internal: + +.. kernel-doc:: drivers/usb/dwc3/gadget.h + :doc: gadget-only helpers + :internal: + +.. kernel-doc:: drivers/usb/dwc3/gadget.c + :doc: gadget-side implementation + :internal: + +.. kernel-doc:: drivers/usb/dwc3/core.c + :doc: core driver (probe, PM, etc) + :internal: + +.. [#trb] Transfer Request Block +.. [#link_trb] Transfer Request Block pointing to another Transfer + Request Block. +.. [#debugfs] The Debug File System +.. [#configfs] The Config File System +.. [#cbw] Command Block Wrapper +.. _Linus' tree: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/ +.. _me: felipe.balbi@linux.intel.com +.. _linux-usb: linux-usb@vger.kernel.org diff --git a/Documentation/driver-api/usb/index.rst b/Documentation/driver-api/usb/index.rst index 1bf64edc8c8a..8fe995a1ec94 100644 --- a/Documentation/driver-api/usb/index.rst +++ b/Documentation/driver-api/usb/index.rst @@ -16,7 +16,10 @@ Linux USB API persist error-codes writing_usb_driver + dwc3 writing_musb_glue_layer + typec + usb3-debug-port .. only:: subproject and html diff --git a/Documentation/usb/typec.rst b/Documentation/driver-api/usb/typec.rst index 8a7249f2ff04..8a7249f2ff04 100644 --- a/Documentation/usb/typec.rst +++ b/Documentation/driver-api/usb/typec.rst diff --git a/Documentation/usb/usb3-debug-port.rst b/Documentation/driver-api/usb/usb3-debug-port.rst index feb1a36a65b7..feb1a36a65b7 100644 --- a/Documentation/usb/usb3-debug-port.rst +++ b/Documentation/driver-api/usb/usb3-debug-port.rst diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index fb0cc4df1765..fbc397d17e98 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -16,10 +16,11 @@ provided by gadgets. 13. RNDIS function 14. SERIAL function 15. SOURCESINK function -16. UAC1 function +16. UAC1 function (legacy implementation) 17. UAC2 function 18. UVC function 19. PRINTER function +20. UAC1 function (new API) 1. ACM function @@ -589,15 +590,16 @@ device: run the gadget host: test-usb (tools/usb/testusb.c) -16. UAC1 function +16. UAC1 function (legacy implementation) ================= -The function is provided by usb_f_uac1.ko module. +The function is provided by usb_f_uac1_legacy.ko module. Function-specific configfs interface ------------------------------------ -The function name to use when creating the function directory is "uac1". +The function name to use when creating the function directory +is "uac1_legacy". The uac1 function provides these attributes in its function directory: audio_buf_size - audio buffer size @@ -772,3 +774,46 @@ host: More advanced testing can be done with the prn_example described in Documentation/usb/gadget-printer.txt. + + +20. UAC1 function (virtual ALSA card, using u_audio API) +================= + +The function is provided by usb_f_uac1.ko module. +It will create a virtual ALSA card and the audio streams are simply +sinked to and sourced from it. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "uac1". +The uac1 function provides these attributes in its function directory: + + c_chmask - capture channel mask + c_srate - capture sampling rate + c_ssize - capture sample size (bytes) + p_chmask - playback channel mask + p_srate - playback sampling rate + p_ssize - playback sample size (bytes) + req_number - the number of pre-allocated request for both capture + and playback + +The attributes have sane default values. + +Testing the UAC1 function +------------------------- + +device: run the gadget +host: aplay -l # should list our USB Audio Gadget + +This function does not require real hardware support, it just +sends a stream of audio data to/from the host. In order to +actually hear something at the device side, a command similar +to this must be used at the device side: + +$ arecord -f dat -t wav -D hw:2,0 | aplay -D hw:0,0 & + +e.g.: + +$ arecord -f dat -t wav -D hw:CARD=UAC1Gadget,DEV=0 | \ +aplay -D default:CARD=OdroidU3 diff --git a/MAINTAINERS b/MAINTAINERS index d357695ee4fe..f9c877f1ebf3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1843,8 +1843,8 @@ F: drivers/i2c/busses/i2c-st.c F: drivers/media/rc/st_rc.c F: drivers/media/platform/sti/c8sectpfe/ F: drivers/mmc/host/sdhci-st.c -F: drivers/phy/phy-miphy28lp.c -F: drivers/phy/phy-stih407-usb.c +F: drivers/phy/st/phy-miphy28lp.c +F: drivers/phy/st/phy-stih407-usb.c F: drivers/pinctrl/pinctrl-st.c F: drivers/remoteproc/st_remoteproc.c F: drivers/remoteproc/st_slim_rproc.c @@ -10840,11 +10840,11 @@ L: linux-iio@vger.kernel.org S: Supported F: drivers/iio/adc/rcar_gyro_adc.c -RENESAS USB2 PHY DRIVER +RENESAS USB PHY DRIVER M: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> L: linux-renesas-soc@vger.kernel.org S: Maintained -F: drivers/phy/phy-rcar-gen3-usb2.c +F: drivers/phy/renesas/phy-rcar-gen3-usb*.c RESET CONTROLLER FRAMEWORK M: Philipp Zabel <p.zabel@pengutronix.de> @@ -11246,12 +11246,12 @@ L: linux-kernel@vger.kernel.org S: Supported F: Documentation/devicetree/bindings/phy/samsung-phy.txt F: Documentation/phy/samsung-usb2.txt -F: drivers/phy/phy-exynos4210-usb2.c -F: drivers/phy/phy-exynos4x12-usb2.c -F: drivers/phy/phy-exynos5250-usb2.c -F: drivers/phy/phy-s5pv210-usb2.c -F: drivers/phy/phy-samsung-usb2.c -F: drivers/phy/phy-samsung-usb2.h +F: drivers/phy/samsung/phy-exynos4210-usb2.c +F: drivers/phy/samsung/phy-exynos4x12-usb2.c +F: drivers/phy/samsung/phy-exynos5250-usb2.c +F: drivers/phy/samsung/phy-s5pv210-usb2.c +F: drivers/phy/samsung/phy-samsung-usb2.c +F: drivers/phy/samsung/phy-samsung-usb2.h SERIAL DRIVERS M: Greg Kroah-Hartman <gregkh@linuxfoundation.org> diff --git a/arch/arm/mach-omap2/dma.c b/arch/arm/mach-omap2/dma.c index e58c13a9bea5..0b77a0176018 100644 --- a/arch/arm/mach-omap2/dma.c +++ b/arch/arm/mach-omap2/dma.c @@ -249,6 +249,24 @@ static const struct dma_slave_map omap24xx_sdma_map[] = { { "omap_uart.2", "rx", SDMA_FILTER_PARAM(54) }, { "omap_hsmmc.0", "tx", SDMA_FILTER_PARAM(61) }, { "omap_hsmmc.0", "rx", SDMA_FILTER_PARAM(62) }, + + /* external DMA requests when tusb6010 is used */ + { "musb-tusb", "dmareq0", SDMA_FILTER_PARAM(2) }, + { "musb-tusb", "dmareq1", SDMA_FILTER_PARAM(3) }, + { "musb-tusb", "dmareq2", SDMA_FILTER_PARAM(14) }, /* OMAP2420 only */ + { "musb-tusb", "dmareq3", SDMA_FILTER_PARAM(15) }, /* OMAP2420 only */ + { "musb-tusb", "dmareq4", SDMA_FILTER_PARAM(16) }, /* OMAP2420 only */ + { "musb-tusb", "dmareq5", SDMA_FILTER_PARAM(64) }, /* OMAP2420 only */ +}; + +static const struct dma_slave_map omap24xx_sdma_dt_map[] = { + /* external DMA requests when tusb6010 is used */ + { "musb-hdrc.1.auto", "dmareq0", SDMA_FILTER_PARAM(2) }, + { "musb-hdrc.1.auto", "dmareq1", SDMA_FILTER_PARAM(3) }, + { "musb-hdrc.1.auto", "dmareq2", SDMA_FILTER_PARAM(14) }, /* OMAP2420 only */ + { "musb-hdrc.1.auto", "dmareq3", SDMA_FILTER_PARAM(15) }, /* OMAP2420 only */ + { "musb-hdrc.1.auto", "dmareq4", SDMA_FILTER_PARAM(16) }, /* OMAP2420 only */ + { "musb-hdrc.1.auto", "dmareq5", SDMA_FILTER_PARAM(64) }, /* OMAP2420 only */ }; static const struct dma_slave_map omap3xxx_sdma_map[] = { @@ -346,6 +364,12 @@ static int __init omap2_system_dma_init_dev(struct omap_hwmod *oh, void *unused) __func__); return -ENODEV; } + } else { + if (soc_is_omap24xx()) { + /* DMA slave map for drivers not yet converted to DT */ + p.slave_map = omap24xx_sdma_dt_map; + p.slavecnt = ARRAY_SIZE(omap24xx_sdma_dt_map); + } } pdev = omap_device_build(name, 0, oh, &p, sizeof(p)); diff --git a/drivers/base/core.c b/drivers/base/core.c index bbecaf9293be..c3064ff09af5 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -2884,3 +2884,19 @@ void set_secondary_fwnode(struct device *dev, struct fwnode_handle *fwnode) else dev->fwnode = fwnode; } + +/** + * device_set_of_node_from_dev - reuse device-tree node of another device + * @dev: device whose device-tree node is being set + * @dev2: device whose device-tree node is being reused + * + * Takes another reference to the new device-tree node after first dropping + * any reference held to the old node. + */ +void device_set_of_node_from_dev(struct device *dev, const struct device *dev2) +{ + of_node_put(dev->of_node); + dev->of_node = of_node_get(dev2->of_node); + dev->of_node_reused = true; +} +EXPORT_SYMBOL_GPL(device_set_of_node_from_dev); diff --git a/drivers/base/pinctrl.c b/drivers/base/pinctrl.c index 5917b4b5fb99..eb929dd6ef1e 100644 --- a/drivers/base/pinctrl.c +++ b/drivers/base/pinctrl.c @@ -23,6 +23,9 @@ int pinctrl_bind_pins(struct device *dev) { int ret; + if (dev->of_node_reused) + return 0; + dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL); if (!dev->pins) return -ENOMEM; diff --git a/drivers/dma/omap-dma.c b/drivers/dma/omap-dma.c index daf479cce691..8c1665c8fe33 100644 --- a/drivers/dma/omap-dma.c +++ b/drivers/dma/omap-dma.c @@ -916,12 +916,6 @@ static struct dma_async_tx_descriptor *omap_dma_prep_slave_sg( return NULL; } - /* When the port_window is used, one frame must cover the window */ - if (port_window) { - burst = port_window; - port_window_bytes = port_window * es_bytes[es]; - } - /* Now allocate and setup the descriptor. */ d = kzalloc(sizeof(*d) + sglen * sizeof(d->sg[0]), GFP_ATOMIC); if (!d) @@ -931,6 +925,21 @@ static struct dma_async_tx_descriptor *omap_dma_prep_slave_sg( d->dev_addr = dev_addr; d->es = es; + /* When the port_window is used, one frame must cover the window */ + if (port_window) { + burst = port_window; + port_window_bytes = port_window * es_bytes[es]; + + d->ei = 1; + /* + * One frame covers the port_window and by configure + * the source frame index to be -1 * (port_window - 1) + * we instruct the sDMA that after a frame is processed + * it should move back to the start of the window. + */ + d->fi = -(port_window_bytes - 1); + } + d->ccr = c->ccr | CCR_SYNC_FRAME; if (dir == DMA_DEV_TO_MEM) { d->csdp = CSDP_DST_BURST_64 | CSDP_DST_PACKED; @@ -955,14 +964,6 @@ static struct dma_async_tx_descriptor *omap_dma_prep_slave_sg( d->ccr |= CCR_SRC_AMODE_POSTINC; if (port_window) { d->ccr |= CCR_DST_AMODE_DBLIDX; - d->ei = 1; - /* - * One frame covers the port_window and by configure - * the source frame index to be -1 * (port_window - 1) - * we instruct the sDMA that after a frame is processed - * it should move back to the start of the window. - */ - d->fi = -(port_window_bytes - 1); if (port_window_bytes >= 64) d->csdp |= CSDP_DST_BURST_64; @@ -1018,16 +1019,6 @@ static struct dma_async_tx_descriptor *omap_dma_prep_slave_sg( osg->addr = sg_dma_address(sgent); osg->en = en; osg->fn = sg_dma_len(sgent) / frame_bytes; - if (port_window && dir == DMA_DEV_TO_MEM) { - osg->ei = 1; - /* - * One frame covers the port_window and by configure - * the source frame index to be -1 * (port_window - 1) - * we instruct the sDMA that after a frame is processed - * it should move back to the start of the window. - */ - osg->fi = -(port_window_bytes - 1); - } if (d->using_ll) { osg->t2_desc = dma_pool_alloc(od->desc_pool, GFP_ATOMIC, diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index 32f2dc8e4702..6d50071f07d5 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -115,6 +115,7 @@ config EXTCON_PALMAS config EXTCON_QCOM_SPMI_MISC tristate "Qualcomm USB extcon support" + depends on ARCH_QCOM || COMPILE_TEST help Say Y here to enable SPMI PMIC based USB cable detection support on Qualcomm PMICs such as PM8941. diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index e2d78cd7030d..f84da4a17724 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -1271,9 +1271,7 @@ static int arizona_extcon_get_micd_configs(struct device *dev, goto out; nconfs /= entries_per_config; - - micd_configs = devm_kzalloc(dev, - nconfs * sizeof(struct arizona_micd_range), + micd_configs = devm_kcalloc(dev, nconfs, sizeof(*micd_configs), GFP_KERNEL); if (!micd_configs) { ret = -ENOMEM; diff --git a/drivers/extcon/extcon-intel-int3496.c b/drivers/extcon/extcon-intel-int3496.c index 9d17984bbbd4..d9f9afe45961 100644 --- a/drivers/extcon/extcon-intel-int3496.c +++ b/drivers/extcon/extcon-intel-int3496.c @@ -94,8 +94,7 @@ static int int3496_probe(struct platform_device *pdev) struct int3496_data *data; int ret; - ret = acpi_dev_add_driver_gpios(ACPI_COMPANION(dev), - acpi_int3496_default_gpios); + ret = devm_acpi_dev_add_driver_gpios(dev, acpi_int3496_default_gpios); if (ret) { dev_err(dev, "can't add GPIO ACPI mapping\n"); return ret; @@ -169,8 +168,6 @@ static int int3496_remove(struct platform_device *pdev) devm_free_irq(&pdev->dev, data->usb_id_irq, data); cancel_delayed_work_sync(&data->work); - acpi_dev_remove_driver_gpios(ACPI_COMPANION(&pdev->dev)); - return 0; } diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index f422a78ba342..8eccf7b14937 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -964,12 +964,12 @@ EXPORT_SYMBOL_GPL(extcon_unregister_notifier); /** * extcon_register_notifier_all() - Register a notifier block for all connectors - * @edev: the extcon device that has the external connecotr. + * @edev: the extcon device that has the external connector. * @nb: a notifier block to be registered. * - * This fucntion registers a notifier block in order to receive the state + * This function registers a notifier block in order to receive the state * change of all supported external connectors from extcon device. - * And The second parameter given to the callback of nb (val) is + * And the second parameter given to the callback of nb (val) is * the current state and third parameter is the edev pointer. * * Returns 0 if success or error number if fail @@ -1252,9 +1252,8 @@ int extcon_dev_register(struct extcon_dev *edev) } spin_lock_init(&edev->lock); - - edev->nh = devm_kzalloc(&edev->dev, - sizeof(*edev->nh) * edev->max_supported, GFP_KERNEL); + edev->nh = devm_kcalloc(&edev->dev, edev->max_supported, + sizeof(*edev->nh), GFP_KERNEL); if (!edev->nh) { ret = -ENOMEM; goto err_dev; diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index afaf7b643eeb..c1807d4a0079 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -15,73 +15,6 @@ config GENERIC_PHY phy users can obtain reference to the PHY. All the users of this framework should select this config. -config PHY_BCM_NS_USB2 - tristate "Broadcom Northstar USB 2.0 PHY Driver" - depends on ARCH_BCM_IPROC || COMPILE_TEST - depends on HAS_IOMEM && OF - select GENERIC_PHY - help - Enable this to support Broadcom USB 2.0 PHY connected to the USB - controller on Northstar family. - -config PHY_BCM_NS_USB3 - tristate "Broadcom Northstar USB 3.0 PHY Driver" - depends on ARCH_BCM_IPROC || COMPILE_TEST - depends on HAS_IOMEM && OF - select GENERIC_PHY - help - Enable this to support Broadcom USB 3.0 PHY connected to the USB - controller on Northstar family. - -config PHY_BERLIN_USB - tristate "Marvell Berlin USB PHY Driver" - depends on ARCH_BERLIN && RESET_CONTROLLER && HAS_IOMEM && OF - select GENERIC_PHY - help - Enable this to support the USB PHY on Marvell Berlin SoCs. - -config PHY_BERLIN_SATA - tristate "Marvell Berlin SATA PHY driver" - depends on ARCH_BERLIN && HAS_IOMEM && OF - select GENERIC_PHY - help - Enable this to support the SATA PHY on Marvell Berlin SoCs. - -config ARMADA375_USBCLUSTER_PHY - def_bool y - depends on MACH_ARMADA_375 || COMPILE_TEST - depends on OF && HAS_IOMEM - select GENERIC_PHY - -config PHY_DA8XX_USB - tristate "TI DA8xx USB PHY Driver" - depends on ARCH_DAVINCI_DA8XX - select GENERIC_PHY - select MFD_SYSCON - help - Enable this to support the USB PHY on DA8xx SoCs. - - This driver controls both the USB 1.1 PHY and the USB 2.0 PHY. - -config PHY_DM816X_USB - tristate "TI dm816x USB PHY driver" - depends on ARCH_OMAP2PLUS - depends on USB_SUPPORT - select GENERIC_PHY - select USB_PHY - help - Enable this for dm816x USB to work. - -config PHY_EXYNOS_MIPI_VIDEO - tristate "S5P/EXYNOS SoC series MIPI CSI-2/DSI PHY driver" - depends on HAS_IOMEM - depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST - select GENERIC_PHY - default y if ARCH_S5PV210 || ARCH_EXYNOS - help - Support for MIPI CSI-2 and MIPI DSI DPHY found on Samsung S5P - and EXYNOS SoCs. - config PHY_LPC18XX_USB_OTG tristate "NXP LPC18xx/43xx SoC USB OTG PHY driver" depends on OF && (ARCH_LPC18XX || COMPILE_TEST) @@ -93,146 +26,6 @@ config PHY_LPC18XX_USB_OTG This driver is need for USB0 support on LPC18xx/43xx and takes care of enabling and clock setup. -config PHY_PXA_28NM_HSIC - tristate "Marvell USB HSIC 28nm PHY Driver" - depends on HAS_IOMEM - select GENERIC_PHY - help - Enable this to support Marvell USB HSIC PHY driver for Marvell - SoC. This driver will do the PHY initialization and shutdown. - The PHY driver will be used by Marvell ehci driver. - - To compile this driver as a module, choose M here. - -config PHY_PXA_28NM_USB2 - tristate "Marvell USB 2.0 28nm PHY Driver" - depends on HAS_IOMEM - select GENERIC_PHY - help - Enable this to support Marvell USB 2.0 PHY driver for Marvell - SoC. This driver will do the PHY initialization and shutdown. - The PHY driver will be used by Marvell udc/ehci/otg driver. - - To compile this driver as a module, choose M here. - -config PHY_MVEBU_SATA - def_bool y - depends on ARCH_DOVE || MACH_DOVE || MACH_KIRKWOOD - depends on OF - select GENERIC_PHY - -config PHY_MIPHY28LP - tristate "STMicroelectronics MIPHY28LP PHY driver for STiH407" - depends on ARCH_STI - select GENERIC_PHY - help - Enable this to support the miphy transceiver (for SATA/PCIE/USB3) - that is part of STMicroelectronics STiH407 SoC. - -config PHY_RCAR_GEN2 - tristate "Renesas R-Car generation 2 USB PHY driver" - depends on ARCH_RENESAS - depends on GENERIC_PHY - 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 ARCH_RENESAS - depends on EXTCON - 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 - help - Enable this to add support for the PHY part present in the control - module. This driver has API to power on the USB2 PHY and to write to - the mailbox. The mailbox is present only in omap4 and the register to - power on the USB2 PHY is present in OMAP4 and OMAP5. OMAP5 has an - additional register to power on USB3 PHY/SATA PHY/PCIE PHY - (PIPE3 PHY). - -config OMAP_USB2 - tristate "OMAP USB2 PHY Driver" - depends on ARCH_OMAP2PLUS - depends on USB_SUPPORT - select GENERIC_PHY - select USB_PHY - select OMAP_CONTROL_PHY - depends on OMAP_OCP2SCP - help - Enable this to support the transceiver that is part of SOC. This - driver takes care of all the PHY functionality apart from comparator. - The USB OTG controller communicates with the comparator using this - driver. - -config TI_PIPE3 - tristate "TI PIPE3 PHY Driver" - depends on ARCH_OMAP2PLUS || COMPILE_TEST - select GENERIC_PHY - select OMAP_CONTROL_PHY - depends on OMAP_OCP2SCP - help - Enable this to support the PIPE3 PHY that is part of TI SOCs. This - driver takes care of all the PHY functionality apart from comparator. - This driver interacts with the "OMAP Control PHY Driver" to power - on/off the PHY. - -config TWL4030_USB - tristate "TWL4030 USB Transceiver Driver" - depends on TWL4030_CORE && REGULATOR_TWL4030 && USB_MUSB_OMAP2PLUS - depends on USB_SUPPORT - depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't 'y' - select GENERIC_PHY - select USB_PHY - help - Enable this to support the USB OTG transceiver on TWL4030 - family chips (including the TWL5030 and TPS659x0 devices). - This transceiver supports high and full speed devices plus, - in host mode, low speed. - -config PHY_EXYNOS_DP_VIDEO - tristate "EXYNOS SoC series Display Port PHY driver" - depends on OF - depends on ARCH_EXYNOS || COMPILE_TEST - default ARCH_EXYNOS - select GENERIC_PHY - help - Support for Display Port PHY found on Samsung EXYNOS SoCs. - -config BCM_KONA_USB2_PHY - tristate "Broadcom Kona USB2 PHY Driver" - depends on HAS_IOMEM - select GENERIC_PHY - help - Enable this to support the Broadcom Kona USB 2.0 PHY. - -config PHY_EXYNOS5250_SATA - tristate "Exynos5250 Sata SerDes/PHY driver" - depends on SOC_EXYNOS5250 - depends on HAS_IOMEM - depends on OF - select GENERIC_PHY - select I2C - select I2C_S3C2410 - select MFD_SYSCON - help - Enable this to support SATA SerDes/Phy found on Samsung's - Exynos5250 based SoCs.This SerDes/Phy supports SATA 1.5 Gb/s, - SATA 3.0 Gb/s, SATA 6.0 Gb/s speeds. It supports one SATA host - port to accept one SATA device. - -config PHY_HIX5HD2_SATA - tristate "HIX5HD2 SATA PHY Driver" - depends on ARCH_HIX5HD2 && OF && HAS_IOMEM - select GENERIC_PHY - select MFD_SYSCON - help - Support for SATA PHY on Hisilicon hix5hd2 Soc. - config PHY_MT65XX_USB3 tristate "Mediatek USB3.0 PHY Driver" depends on ARCH_MEDIATEK && OF @@ -241,104 +34,6 @@ config PHY_MT65XX_USB3 Say 'Y' here to add support for Mediatek USB3.0 PHY driver, it supports multiple usb2.0 and usb3.0 ports. -config PHY_HI6220_USB - tristate "hi6220 USB PHY support" - depends on (ARCH_HISI && ARM64) || COMPILE_TEST - 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 - depends on RESET_CONTROLLER - depends on EXTCON - depends on POWER_SUPPLY - depends on USB_SUPPORT - select GENERIC_PHY - select USB_COMMON - help - Enable this to support the transceiver that is part of Allwinner - sunxi SoCs. - - This driver controls the entire USB PHY block, both the USB OTG - parts, as well as the 2 regular USB 2 host PHYs. - -config PHY_SUN9I_USB - tristate "Allwinner sun9i SoC USB PHY driver" - depends on ARCH_SUNXI && HAS_IOMEM && OF - depends on RESET_CONTROLLER - depends on USB_SUPPORT - select USB_COMMON - select GENERIC_PHY - help - Enable this to support the transceiver that is part of Allwinner - sun9i SoCs. - - This driver controls each individual USB 2 host PHY. - -config PHY_SAMSUNG_USB2 - tristate "Samsung USB 2.0 PHY driver" - depends on HAS_IOMEM - depends on USB_EHCI_EXYNOS || USB_OHCI_EXYNOS || USB_DWC2 - select GENERIC_PHY - select MFD_SYSCON - default ARCH_EXYNOS - help - Enable this to support the Samsung USB 2.0 PHY driver for Samsung - SoCs. This driver provides the interface for USB 2.0 PHY. Support - for particular PHYs will be enabled based on the SoC type in addition - to this driver. - -config PHY_S5PV210_USB2 - bool "Support for S5PV210" - depends on PHY_SAMSUNG_USB2 - depends on ARCH_S5PV210 - help - Enable USB PHY support for S5PV210. This option requires that Samsung - USB 2.0 PHY driver is enabled and means that support for this - particular SoC is compiled in the driver. In case of S5PV210 two phys - are available - device and host. - -config PHY_EXYNOS4210_USB2 - bool - depends on PHY_SAMSUNG_USB2 - default CPU_EXYNOS4210 - -config PHY_EXYNOS4X12_USB2 - bool - depends on PHY_SAMSUNG_USB2 - default SOC_EXYNOS3250 || SOC_EXYNOS4212 || SOC_EXYNOS4412 - -config PHY_EXYNOS5250_USB2 - bool - depends on PHY_SAMSUNG_USB2 - default SOC_EXYNOS5250 || SOC_EXYNOS5420 - -config PHY_EXYNOS5_USBDRD - tristate "Exynos5 SoC series USB DRD PHY driver" - depends on ARCH_EXYNOS && OF - depends on HAS_IOMEM - depends on USB_DWC3_EXYNOS - select GENERIC_PHY - select MFD_SYSCON - default y - help - Enable USB DRD PHY support for Exynos 5 SoC series. - This driver provides PHY interface for USB 3.0 DRD controller - present on Exynos5 SoC series. - -config PHY_EXYNOS_PCIE - bool "Exynos PCIe PHY driver" - depends on OF && (ARCH_EXYNOS || COMPILE_TEST) - select GENERIC_PHY - help - Enable PCIe PHY support for Exynos SoC series. - This driver provides PHY interface for Exynos PCIe controller. - config PHY_PISTACHIO_USB tristate "IMG Pistachio USB2.0 PHY driver" depends on MACH_PISTACHIO @@ -346,83 +41,6 @@ config PHY_PISTACHIO_USB help Enable this to support the USB2.0 PHY on the IMG Pistachio SoC. -config PHY_QCOM_APQ8064_SATA - tristate "Qualcomm APQ8064 SATA SerDes/PHY driver" - depends on ARCH_QCOM - depends on HAS_IOMEM - depends on OF - select GENERIC_PHY - -config PHY_QCOM_IPQ806X_SATA - tristate "Qualcomm IPQ806x SATA SerDes/PHY driver" - depends on ARCH_QCOM - depends on HAS_IOMEM - depends on OF - select GENERIC_PHY - -config PHY_ROCKCHIP_USB - tristate "Rockchip USB2 PHY Driver" - depends on ARCH_ROCKCHIP && OF - select GENERIC_PHY - help - Enable this to support the Rockchip USB 2.0 PHY. - -config PHY_ROCKCHIP_INNO_USB2 - tristate "Rockchip INNO USB2PHY Driver" - depends on (ARCH_ROCKCHIP || COMPILE_TEST) && OF - depends on COMMON_CLK - depends on EXTCON - depends on USB_SUPPORT - select GENERIC_PHY - select USB_COMMON - help - Support for Rockchip USB2.0 PHY with Innosilicon IP block. - -config PHY_ROCKCHIP_EMMC - tristate "Rockchip EMMC PHY Driver" - depends on ARCH_ROCKCHIP && OF - select GENERIC_PHY - help - Enable this to support the Rockchip EMMC PHY. - -config PHY_ROCKCHIP_DP - tristate "Rockchip Display Port PHY Driver" - depends on ARCH_ROCKCHIP && OF - select GENERIC_PHY - help - Enable this to support the Rockchip Display Port PHY. - -config PHY_ROCKCHIP_PCIE - tristate "Rockchip PCIe PHY Driver" - depends on (ARCH_ROCKCHIP && OF) || COMPILE_TEST - select GENERIC_PHY - select MFD_SYSCON - help - Enable this to support the Rockchip PCIe PHY. - -config PHY_ROCKCHIP_TYPEC - tristate "Rockchip TYPEC PHY Driver" - depends on OF && (ARCH_ROCKCHIP || COMPILE_TEST) - select EXTCON - select GENERIC_PHY - select RESET_CONTROLLER - help - Enable this to support the Rockchip USB TYPEC PHY. - -config PHY_ST_SPEAR1310_MIPHY - tristate "ST SPEAR1310-MIPHY driver" - select GENERIC_PHY - depends on MACH_SPEAR1310 || COMPILE_TEST - help - Support for ST SPEAr1310 MIPHY which can be used for PCIe and SATA. - -config PHY_ST_SPEAR1340_MIPHY - tristate "ST SPEAR1340-MIPHY driver" - select GENERIC_PHY - depends on MACH_SPEAR1340 || COMPILE_TEST - help - Support for ST SPEAr1340 MIPHY which can be used for PCIe and SATA. - config PHY_XGENE tristate "APM X-Gene 15Gbps PHY support" depends on HAS_IOMEM && OF && (ARM64 || COMPILE_TEST) @@ -430,104 +48,18 @@ config PHY_XGENE help This option enables support for APM X-Gene SoC multi-purpose PHY. -config PHY_STIH407_USB - tristate "STMicroelectronics USB2 picoPHY driver for STiH407 family" - depends on RESET_CONTROLLER - depends on ARCH_STI || COMPILE_TEST - select GENERIC_PHY - help - Enable this support to enable the picoPHY device used by USB2 - and USB3 controllers on STMicroelectronics STiH407 SoC families. - -config PHY_QCOM_QMP - tristate "Qualcomm QMP PHY Driver" - depends on OF && COMMON_CLK && (ARCH_QCOM || COMPILE_TEST) - select GENERIC_PHY - help - Enable this to support the QMP PHY transceiver that is used - with controllers such as PCIe, UFS, and USB on Qualcomm chips. - -config PHY_QCOM_QUSB2 - tristate "Qualcomm QUSB2 PHY Driver" - depends on OF && (ARCH_QCOM || COMPILE_TEST) - depends on NVMEM || !NVMEM - select GENERIC_PHY - help - Enable this to support the HighSpeed QUSB2 PHY transceiver for USB - controllers on Qualcomm chips. This driver supports the high-speed - PHY which is usually paired with either the ChipIdea or Synopsys DWC3 - USB IPs on MSM SOCs. - -config PHY_QCOM_UFS - tristate "Qualcomm UFS PHY driver" - depends on OF && ARCH_QCOM - select GENERIC_PHY - help - Support for UFS PHY on QCOM chipsets. - -config PHY_QCOM_USB_HS - tristate "Qualcomm USB HS PHY module" - depends on USB_ULPI_BUS - depends on EXTCON || !EXTCON # if EXTCON=m, this cannot be built-in - select GENERIC_PHY - help - Support for the USB high-speed ULPI compliant phy on Qualcomm - chipsets. - -config PHY_QCOM_USB_HSIC - tristate "Qualcomm USB HSIC ULPI PHY module" - depends on USB_ULPI_BUS - select GENERIC_PHY - help - Support for the USB HSIC ULPI compliant PHY on QCOM chipsets. - -config PHY_TUSB1210 - tristate "TI TUSB1210 ULPI PHY module" - depends on USB_ULPI_BUS - select GENERIC_PHY - help - Support for TI TUSB1210 USB ULPI PHY. - -config PHY_BRCM_SATA - tristate "Broadcom SATA PHY driver" - depends on ARCH_BRCMSTB || ARCH_BCM_IPROC || BMIPS_GENERIC || COMPILE_TEST - depends on OF - select GENERIC_PHY - default ARCH_BCM_IPROC - help - Enable this to support the Broadcom SATA PHY. - If unsure, say N. - -config PHY_CYGNUS_PCIE - tristate "Broadcom Cygnus PCIe PHY driver" - depends on OF && (ARCH_BCM_CYGNUS || COMPILE_TEST) - select GENERIC_PHY - default ARCH_BCM_CYGNUS - help - Enable this to support the Broadcom Cygnus PCIe PHY. - If unsure, say N. - +source "drivers/phy/allwinner/Kconfig" +source "drivers/phy/amlogic/Kconfig" +source "drivers/phy/broadcom/Kconfig" +source "drivers/phy/hisilicon/Kconfig" +source "drivers/phy/marvell/Kconfig" +source "drivers/phy/motorola/Kconfig" +source "drivers/phy/qualcomm/Kconfig" +source "drivers/phy/renesas/Kconfig" +source "drivers/phy/rockchip/Kconfig" +source "drivers/phy/samsung/Kconfig" +source "drivers/phy/st/Kconfig" source "drivers/phy/tegra/Kconfig" - -config PHY_NS2_PCIE - tristate "Broadcom Northstar2 PCIe PHY driver" - depends on OF && MDIO_BUS_MUX_BCM_IPROC - select GENERIC_PHY - default ARCH_BCM_IPROC - help - Enable this to support the Broadcom Northstar2 PCIe PHY. - If unsure, say N. - -config PHY_MESON8B_USB2 - tristate "Meson8b and GXBB USB2 PHY driver" - default ARCH_MESON - depends on OF && (ARCH_MESON || COMPILE_TEST) - depends on USB_SUPPORT - select USB_COMMON - select GENERIC_PHY - help - Enable this to support the Meson USB2 PHYs found in Meson8b - and GXBB SoCs. - If unsure, say N. +source "drivers/phy/ti/Kconfig" endmenu diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index f8047b4639fa..f252201e0ec9 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -3,64 +3,21 @@ # obj-$(CONFIG_GENERIC_PHY) += phy-core.o -obj-$(CONFIG_PHY_BCM_NS_USB2) += phy-bcm-ns-usb2.o -obj-$(CONFIG_PHY_BCM_NS_USB3) += phy-bcm-ns-usb3.o -obj-$(CONFIG_PHY_BERLIN_USB) += phy-berlin-usb.o -obj-$(CONFIG_PHY_BERLIN_SATA) += phy-berlin-sata.o -obj-$(CONFIG_PHY_DA8XX_USB) += phy-da8xx-usb.o -obj-$(CONFIG_PHY_DM816X_USB) += phy-dm816x-usb.o -obj-$(CONFIG_ARMADA375_USBCLUSTER_PHY) += phy-armada375-usb2.o -obj-$(CONFIG_BCM_KONA_USB2_PHY) += phy-bcm-kona-usb2.o -obj-$(CONFIG_PHY_EXYNOS_DP_VIDEO) += phy-exynos-dp-video.o -obj-$(CONFIG_PHY_EXYNOS_MIPI_VIDEO) += phy-exynos-mipi-video.o obj-$(CONFIG_PHY_LPC18XX_USB_OTG) += phy-lpc18xx-usb-otg.o -obj-$(CONFIG_PHY_PXA_28NM_USB2) += phy-pxa-28nm-usb2.o -obj-$(CONFIG_PHY_PXA_28NM_HSIC) += phy-pxa-28nm-hsic.o -obj-$(CONFIG_PHY_MVEBU_SATA) += phy-mvebu-sata.o -obj-$(CONFIG_PHY_MIPHY28LP) += phy-miphy28lp.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 -obj-$(CONFIG_PHY_SAMSUNG_USB2) += phy-exynos-usb2.o -phy-exynos-usb2-y += phy-samsung-usb2.o -phy-exynos-usb2-$(CONFIG_PHY_EXYNOS4210_USB2) += phy-exynos4210-usb2.o -phy-exynos-usb2-$(CONFIG_PHY_EXYNOS4X12_USB2) += phy-exynos4x12-usb2.o -phy-exynos-usb2-$(CONFIG_PHY_EXYNOS5250_USB2) += phy-exynos5250-usb2.o -phy-exynos-usb2-$(CONFIG_PHY_S5PV210_USB2) += phy-s5pv210-usb2.o -obj-$(CONFIG_PHY_EXYNOS5_USBDRD) += phy-exynos5-usbdrd.o -obj-$(CONFIG_PHY_EXYNOS_PCIE) += phy-exynos-pcie.o -obj-$(CONFIG_PHY_QCOM_APQ8064_SATA) += phy-qcom-apq8064-sata.o -obj-$(CONFIG_PHY_ROCKCHIP_USB) += phy-rockchip-usb.o -obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB2) += phy-rockchip-inno-usb2.o -obj-$(CONFIG_PHY_ROCKCHIP_EMMC) += phy-rockchip-emmc.o -obj-$(CONFIG_PHY_ROCKCHIP_PCIE) += phy-rockchip-pcie.o -obj-$(CONFIG_PHY_ROCKCHIP_DP) += phy-rockchip-dp.o -obj-$(CONFIG_PHY_ROCKCHIP_TYPEC) += phy-rockchip-typec.o -obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o -obj-$(CONFIG_PHY_ST_SPEAR1310_MIPHY) += phy-spear1310-miphy.o -obj-$(CONFIG_PHY_ST_SPEAR1340_MIPHY) += phy-spear1340-miphy.o obj-$(CONFIG_PHY_XGENE) += phy-xgene.o -obj-$(CONFIG_PHY_STIH407_USB) += phy-stih407-usb.o -obj-$(CONFIG_PHY_QCOM_QMP) += phy-qcom-qmp.o -obj-$(CONFIG_PHY_QCOM_QUSB2) += phy-qcom-qusb2.o -obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs.o -obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-20nm.o -obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-14nm.o -obj-$(CONFIG_PHY_QCOM_USB_HS) += phy-qcom-usb-hs.o -obj-$(CONFIG_PHY_QCOM_USB_HSIC) += phy-qcom-usb-hsic.o -obj-$(CONFIG_PHY_TUSB1210) += phy-tusb1210.o -obj-$(CONFIG_PHY_BRCM_SATA) += phy-brcm-sata.o obj-$(CONFIG_PHY_PISTACHIO_USB) += phy-pistachio-usb.o -obj-$(CONFIG_PHY_CYGNUS_PCIE) += phy-bcm-cygnus-pcie.o -obj-$(CONFIG_ARCH_TEGRA) += tegra/ -obj-$(CONFIG_PHY_NS2_PCIE) += phy-bcm-ns2-pcie.o -obj-$(CONFIG_PHY_MESON8B_USB2) += phy-meson8b-usb2.o + +obj-$(CONFIG_ARCH_SUNXI) += allwinner/ +obj-$(CONFIG_ARCH_MESON) += amlogic/ +obj-$(CONFIG_ARCH_RENESAS) += renesas/ +obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/ +obj-$(CONFIG_ARCH_TEGRA) += tegra/ +obj-y += broadcom/ \ + hisilicon/ \ + marvell/ \ + motorola/ \ + qualcomm/ \ + samsung/ \ + st/ \ + ti/ diff --git a/drivers/phy/allwinner/Kconfig b/drivers/phy/allwinner/Kconfig new file mode 100644 index 000000000000..cdc1e745ba47 --- /dev/null +++ b/drivers/phy/allwinner/Kconfig @@ -0,0 +1,31 @@ +# +# Phy drivers for Allwinner platforms +# +config PHY_SUN4I_USB + tristate "Allwinner sunxi SoC USB PHY driver" + depends on ARCH_SUNXI && HAS_IOMEM && OF + depends on RESET_CONTROLLER + depends on EXTCON + depends on POWER_SUPPLY + depends on USB_SUPPORT + select GENERIC_PHY + select USB_COMMON + help + Enable this to support the transceiver that is part of Allwinner + sunxi SoCs. + + This driver controls the entire USB PHY block, both the USB OTG + parts, as well as the 2 regular USB 2 host PHYs. + +config PHY_SUN9I_USB + tristate "Allwinner sun9i SoC USB PHY driver" + depends on ARCH_SUNXI && HAS_IOMEM && OF + depends on RESET_CONTROLLER + depends on USB_SUPPORT + select USB_COMMON + select GENERIC_PHY + help + Enable this to support the transceiver that is part of Allwinner + sun9i SoCs. + + This driver controls each individual USB 2 host PHY. diff --git a/drivers/phy/allwinner/Makefile b/drivers/phy/allwinner/Makefile new file mode 100644 index 000000000000..8605529c01a1 --- /dev/null +++ b/drivers/phy/allwinner/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_PHY_SUN4I_USB) += phy-sun4i-usb.o +obj-$(CONFIG_PHY_SUN9I_USB) += phy-sun9i-usb.o diff --git a/drivers/phy/phy-sun4i-usb.c b/drivers/phy/allwinner/phy-sun4i-usb.c index bbf06cfe5898..bbf06cfe5898 100644 --- a/drivers/phy/phy-sun4i-usb.c +++ b/drivers/phy/allwinner/phy-sun4i-usb.c diff --git a/drivers/phy/phy-sun9i-usb.c b/drivers/phy/allwinner/phy-sun9i-usb.c index 28fce4bce638..28fce4bce638 100644 --- a/drivers/phy/phy-sun9i-usb.c +++ b/drivers/phy/allwinner/phy-sun9i-usb.c diff --git a/drivers/phy/amlogic/Kconfig b/drivers/phy/amlogic/Kconfig new file mode 100644 index 000000000000..cb8f4501652b --- /dev/null +++ b/drivers/phy/amlogic/Kconfig @@ -0,0 +1,27 @@ +# +# Phy drivers for Amlogic platforms +# +config PHY_MESON8B_USB2 + tristate "Meson8, Meson8b and GXBB USB2 PHY driver" + default ARCH_MESON + depends on OF && (ARCH_MESON || COMPILE_TEST) + depends on USB_SUPPORT + select USB_COMMON + select GENERIC_PHY + help + Enable this to support the Meson USB2 PHYs found in Meson8, + Meson8b and GXBB SoCs. + If unsure, say N. + +config PHY_MESON_GXL_USB2 + tristate "Meson GXL and GXM USB2 PHY drivers" + default ARCH_MESON + depends on OF && (ARCH_MESON || COMPILE_TEST) + depends on USB_SUPPORT + select USB_COMMON + select GENERIC_PHY + select REGMAP_MMIO + help + Enable this to support the Meson USB2 PHYs found in Meson + GXL and GXM SoCs. + If unsure, say N. diff --git a/drivers/phy/amlogic/Makefile b/drivers/phy/amlogic/Makefile new file mode 100644 index 000000000000..cfdc98715c30 --- /dev/null +++ b/drivers/phy/amlogic/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_PHY_MESON8B_USB2) += phy-meson8b-usb2.o +obj-$(CONFIG_PHY_MESON_GXL_USB2) += phy-meson-gxl-usb2.o diff --git a/drivers/phy/amlogic/phy-meson-gxl-usb2.c b/drivers/phy/amlogic/phy-meson-gxl-usb2.c new file mode 100644 index 000000000000..e90c4ee25dfe --- /dev/null +++ b/drivers/phy/amlogic/phy-meson-gxl-usb2.c @@ -0,0 +1,273 @@ +/* + * Meson GXL and GXM USB2 PHY driver + * + * Copyright (C) 2017 Martin Blumenstingl <martin.blumenstingl@googlemail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/usb/of.h> + +/* bits [31:27] are read-only */ +#define U2P_R0 0x0 + #define U2P_R0_BYPASS_SEL BIT(0) + #define U2P_R0_BYPASS_DM_EN BIT(1) + #define U2P_R0_BYPASS_DP_EN BIT(2) + #define U2P_R0_TXBITSTUFF_ENH BIT(3) + #define U2P_R0_TXBITSTUFF_EN BIT(4) + #define U2P_R0_DM_PULLDOWN BIT(5) + #define U2P_R0_DP_PULLDOWN BIT(6) + #define U2P_R0_DP_VBUS_VLD_EXT_SEL BIT(7) + #define U2P_R0_DP_VBUS_VLD_EXT BIT(8) + #define U2P_R0_ADP_PRB_EN BIT(9) + #define U2P_R0_ADP_DISCHARGE BIT(10) + #define U2P_R0_ADP_CHARGE BIT(11) + #define U2P_R0_DRV_VBUS BIT(12) + #define U2P_R0_ID_PULLUP BIT(13) + #define U2P_R0_LOOPBACK_EN_B BIT(14) + #define U2P_R0_OTG_DISABLE BIT(15) + #define U2P_R0_COMMON_ONN BIT(16) + #define U2P_R0_FSEL_MASK GENMASK(19, 17) + #define U2P_R0_REF_CLK_SEL_MASK GENMASK(21, 20) + #define U2P_R0_POWER_ON_RESET BIT(22) + #define U2P_R0_V_ATE_TEST_EN_B_MASK GENMASK(24, 23) + #define U2P_R0_ID_SET_ID_DQ BIT(25) + #define U2P_R0_ATE_RESET BIT(26) + #define U2P_R0_FSV_MINUS BIT(27) + #define U2P_R0_FSV_PLUS BIT(28) + #define U2P_R0_BYPASS_DM_DATA BIT(29) + #define U2P_R0_BYPASS_DP_DATA BIT(30) + +#define U2P_R1 0x4 + #define U2P_R1_BURN_IN_TEST BIT(0) + #define U2P_R1_ACA_ENABLE BIT(1) + #define U2P_R1_DCD_ENABLE BIT(2) + #define U2P_R1_VDAT_SRC_EN_B BIT(3) + #define U2P_R1_VDAT_DET_EN_B BIT(4) + #define U2P_R1_CHARGES_SEL BIT(5) + #define U2P_R1_TX_PREEMP_PULSE_TUNE BIT(6) + #define U2P_R1_TX_PREEMP_AMP_TUNE_MASK GENMASK(8, 7) + #define U2P_R1_TX_RES_TUNE_MASK GENMASK(10, 9) + #define U2P_R1_TX_RISE_TUNE_MASK GENMASK(12, 11) + #define U2P_R1_TX_VREF_TUNE_MASK GENMASK(16, 13) + #define U2P_R1_TX_FSLS_TUNE_MASK GENMASK(20, 17) + #define U2P_R1_TX_HSXV_TUNE_MASK GENMASK(22, 21) + #define U2P_R1_OTG_TUNE_MASK GENMASK(25, 23) + #define U2P_R1_SQRX_TUNE_MASK GENMASK(28, 26) + #define U2P_R1_COMP_DIS_TUNE_MASK GENMASK(31, 29) + +/* bits [31:14] are read-only */ +#define U2P_R2 0x8 + #define U2P_R2_DATA_IN_MASK GENMASK(3, 0) + #define U2P_R2_DATA_IN_EN_MASK GENMASK(7, 4) + #define U2P_R2_ADDR_MASK GENMASK(11, 8) + #define U2P_R2_DATA_OUT_SEL BIT(12) + #define U2P_R2_CLK BIT(13) + #define U2P_R2_DATA_OUT_MASK GENMASK(17, 14) + #define U2P_R2_ACA_PIN_RANGE_C BIT(18) + #define U2P_R2_ACA_PIN_RANGE_B BIT(19) + #define U2P_R2_ACA_PIN_RANGE_A BIT(20) + #define U2P_R2_ACA_PIN_GND BIT(21) + #define U2P_R2_ACA_PIN_FLOAT BIT(22) + #define U2P_R2_CHARGE_DETECT BIT(23) + #define U2P_R2_DEVICE_SESSION_VALID BIT(24) + #define U2P_R2_ADP_PROBE BIT(25) + #define U2P_R2_ADP_SENSE BIT(26) + #define U2P_R2_SESSION_END BIT(27) + #define U2P_R2_VBUS_VALID BIT(28) + #define U2P_R2_B_VALID BIT(29) + #define U2P_R2_A_VALID BIT(30) + #define U2P_R2_ID_DIG BIT(31) + +#define U2P_R3 0xc + +#define RESET_COMPLETE_TIME 500 + +struct phy_meson_gxl_usb2_priv { + struct regmap *regmap; + enum phy_mode mode; + int is_enabled; +}; + +static const struct regmap_config phy_meson_gxl_usb2_regmap_conf = { + .reg_bits = 8, + .val_bits = 32, + .reg_stride = 4, + .max_register = U2P_R3, +}; + +static int phy_meson_gxl_usb2_reset(struct phy *phy) +{ + struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy); + + if (priv->is_enabled) { + /* reset the PHY and wait until settings are stabilized */ + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_POWER_ON_RESET, + U2P_R0_POWER_ON_RESET); + udelay(RESET_COMPLETE_TIME); + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_POWER_ON_RESET, + 0); + udelay(RESET_COMPLETE_TIME); + } + + return 0; +} + +static int phy_meson_gxl_usb2_set_mode(struct phy *phy, enum phy_mode mode) +{ + struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy); + + switch (mode) { + case PHY_MODE_USB_HOST: + case PHY_MODE_USB_OTG: + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_DM_PULLDOWN, + U2P_R0_DM_PULLDOWN); + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_DP_PULLDOWN, + U2P_R0_DP_PULLDOWN); + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_ID_PULLUP, 0); + break; + + case PHY_MODE_USB_DEVICE: + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_DM_PULLDOWN, + 0); + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_DP_PULLDOWN, + 0); + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_ID_PULLUP, + U2P_R0_ID_PULLUP); + break; + + default: + return -EINVAL; + } + + phy_meson_gxl_usb2_reset(phy); + + priv->mode = mode; + + return 0; +} + +static int phy_meson_gxl_usb2_power_off(struct phy *phy) +{ + struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy); + + priv->is_enabled = 0; + + /* power off the PHY by putting it into reset mode */ + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_POWER_ON_RESET, + U2P_R0_POWER_ON_RESET); + + return 0; +} + +static int phy_meson_gxl_usb2_power_on(struct phy *phy) +{ + struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy); + int ret; + + priv->is_enabled = 1; + + /* power on the PHY by taking it out of reset mode */ + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_POWER_ON_RESET, 0); + + ret = phy_meson_gxl_usb2_set_mode(phy, priv->mode); + if (ret) { + phy_meson_gxl_usb2_power_off(phy); + + dev_err(&phy->dev, "Failed to initialize PHY with mode %d\n", + priv->mode); + return ret; + } + + return 0; +} + +static const struct phy_ops phy_meson_gxl_usb2_ops = { + .power_on = phy_meson_gxl_usb2_power_on, + .power_off = phy_meson_gxl_usb2_power_off, + .set_mode = phy_meson_gxl_usb2_set_mode, + .reset = phy_meson_gxl_usb2_reset, + .owner = THIS_MODULE, +}; + +static int phy_meson_gxl_usb2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phy_provider *phy_provider; + struct resource *res; + struct phy_meson_gxl_usb2_priv *priv; + struct phy *phy; + void __iomem *base; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + switch (of_usb_get_dr_mode_by_phy(dev->of_node, -1)) { + case USB_DR_MODE_PERIPHERAL: + priv->mode = PHY_MODE_USB_DEVICE; + break; + case USB_DR_MODE_OTG: + priv->mode = PHY_MODE_USB_OTG; + break; + case USB_DR_MODE_HOST: + default: + priv->mode = PHY_MODE_USB_HOST; + break; + } + + priv->regmap = devm_regmap_init_mmio(dev, base, + &phy_meson_gxl_usb2_regmap_conf); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + phy = devm_phy_create(dev, NULL, &phy_meson_gxl_usb2_ops); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create PHY\n"); + return PTR_ERR(phy); + } + + phy_set_drvdata(phy, priv); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id phy_meson_gxl_usb2_of_match[] = { + { .compatible = "amlogic,meson-gxl-usb2-phy", }, + { }, +}; +MODULE_DEVICE_TABLE(of, phy_meson_gxl_usb2_of_match); + +static struct platform_driver phy_meson_gxl_usb2_driver = { + .probe = phy_meson_gxl_usb2_probe, + .driver = { + .name = "phy-meson-gxl-usb2", + .of_match_table = phy_meson_gxl_usb2_of_match, + }, +}; +module_platform_driver(phy_meson_gxl_usb2_driver); + +MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>"); +MODULE_DESCRIPTION("Meson GXL and GXM USB2 PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-meson8b-usb2.c b/drivers/phy/amlogic/phy-meson8b-usb2.c index 30f56a6a411f..9c01b7e19b06 100644 --- a/drivers/phy/phy-meson8b-usb2.c +++ b/drivers/phy/amlogic/phy-meson8b-usb2.c @@ -1,5 +1,5 @@ /* - * Meson8b and GXBB USB2 PHY driver + * Meson8, Meson8b and GXBB USB2 PHY driver * * Copyright (C) 2016 Martin Blumenstingl <martin.blumenstingl@googlemail.com> * @@ -266,6 +266,7 @@ static int phy_meson8b_usb2_probe(struct platform_device *pdev) } static const struct of_device_id phy_meson8b_usb2_of_match[] = { + { .compatible = "amlogic,meson8-usb2-phy", }, { .compatible = "amlogic,meson8b-usb2-phy", }, { .compatible = "amlogic,meson-gxbb-usb2-phy", }, { }, @@ -282,5 +283,5 @@ static struct platform_driver phy_meson8b_usb2_driver = { module_platform_driver(phy_meson8b_usb2_driver); MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>"); -MODULE_DESCRIPTION("Meson8b and GXBB USB2 PHY driver"); +MODULE_DESCRIPTION("Meson8, Meson8b and GXBB USB2 PHY driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig new file mode 100644 index 000000000000..37371b89b14f --- /dev/null +++ b/drivers/phy/broadcom/Kconfig @@ -0,0 +1,69 @@ +# +# Phy drivers for Broadcom platforms +# +config PHY_CYGNUS_PCIE + tristate "Broadcom Cygnus PCIe PHY driver" + depends on OF && (ARCH_BCM_CYGNUS || COMPILE_TEST) + select GENERIC_PHY + default ARCH_BCM_CYGNUS + help + Enable this to support the Broadcom Cygnus PCIe PHY. + If unsure, say N. + +config BCM_KONA_USB2_PHY + tristate "Broadcom Kona USB2 PHY Driver" + depends on HAS_IOMEM + select GENERIC_PHY + help + Enable this to support the Broadcom Kona USB 2.0 PHY. + +config PHY_BCM_NS_USB2 + tristate "Broadcom Northstar USB 2.0 PHY Driver" + depends on ARCH_BCM_IPROC || COMPILE_TEST + depends on HAS_IOMEM && OF + select GENERIC_PHY + help + Enable this to support Broadcom USB 2.0 PHY connected to the USB + controller on Northstar family. + +config PHY_BCM_NS_USB3 + tristate "Broadcom Northstar USB 3.0 PHY Driver" + depends on ARCH_BCM_IPROC || COMPILE_TEST + depends on HAS_IOMEM && OF + select GENERIC_PHY + select MDIO_DEVICE + help + Enable this to support Broadcom USB 3.0 PHY connected to the USB + controller on Northstar family. + +config PHY_NS2_PCIE + tristate "Broadcom Northstar2 PCIe PHY driver" + depends on OF && MDIO_BUS_MUX_BCM_IPROC + select GENERIC_PHY + default ARCH_BCM_IPROC + help + Enable this to support the Broadcom Northstar2 PCIe PHY. + If unsure, say N. + +config PHY_NS2_USB_DRD + tristate "Broadcom Northstar2 USB DRD PHY support" + depends on OF && (ARCH_BCM_IPROC || COMPILE_TEST) + select GENERIC_PHY + select EXTCON + default ARCH_BCM_IPROC + help + Enable this to support the Broadcom Northstar2 USB DRD PHY. + This driver initializes the PHY in either HOST or DEVICE mode. + The host or device configuration is read from device tree. + + If unsure, say N. + +config PHY_BRCM_SATA + tristate "Broadcom SATA PHY driver" + depends on ARCH_BRCMSTB || ARCH_BCM_IPROC || BMIPS_GENERIC || COMPILE_TEST + depends on OF + select GENERIC_PHY + default ARCH_BCM_IPROC + help + Enable this to support the Broadcom SATA PHY. + If unsure, say N. diff --git a/drivers/phy/broadcom/Makefile b/drivers/phy/broadcom/Makefile new file mode 100644 index 000000000000..4eb82ec8d491 --- /dev/null +++ b/drivers/phy/broadcom/Makefile @@ -0,0 +1,7 @@ +obj-$(CONFIG_PHY_CYGNUS_PCIE) += phy-bcm-cygnus-pcie.o +obj-$(CONFIG_BCM_KONA_USB2_PHY) += phy-bcm-kona-usb2.o +obj-$(CONFIG_PHY_BCM_NS_USB2) += phy-bcm-ns-usb2.o +obj-$(CONFIG_PHY_BCM_NS_USB3) += phy-bcm-ns-usb3.o +obj-$(CONFIG_PHY_NS2_PCIE) += phy-bcm-ns2-pcie.o +obj-$(CONFIG_PHY_NS2_USB_DRD) += phy-bcm-ns2-usbdrd.o +obj-$(CONFIG_PHY_BRCM_SATA) += phy-brcm-sata.o diff --git a/drivers/phy/phy-bcm-cygnus-pcie.c b/drivers/phy/broadcom/phy-bcm-cygnus-pcie.c index 0f4ac5d63cff..0f4ac5d63cff 100644 --- a/drivers/phy/phy-bcm-cygnus-pcie.c +++ b/drivers/phy/broadcom/phy-bcm-cygnus-pcie.c diff --git a/drivers/phy/phy-bcm-kona-usb2.c b/drivers/phy/broadcom/phy-bcm-kona-usb2.c index 7b67fe49e30b..7b67fe49e30b 100644 --- a/drivers/phy/phy-bcm-kona-usb2.c +++ b/drivers/phy/broadcom/phy-bcm-kona-usb2.c diff --git a/drivers/phy/phy-bcm-ns-usb2.c b/drivers/phy/broadcom/phy-bcm-ns-usb2.c index 58dff80e9386..58dff80e9386 100644 --- a/drivers/phy/phy-bcm-ns-usb2.c +++ b/drivers/phy/broadcom/phy-bcm-ns-usb2.c diff --git a/drivers/phy/phy-bcm-ns-usb3.c b/drivers/phy/broadcom/phy-bcm-ns-usb3.c index 22b5e7047fa6..a53ae128eadf 100644 --- a/drivers/phy/phy-bcm-ns-usb3.c +++ b/drivers/phy/broadcom/phy-bcm-ns-usb3.c @@ -16,7 +16,9 @@ #include <linux/bcma/bcma.h> #include <linux/delay.h> #include <linux/err.h> +#include <linux/mdio.h> #include <linux/module.h> +#include <linux/of_address.h> #include <linux/of_platform.h> #include <linux/platform_device.h> #include <linux/phy/phy.h> @@ -52,7 +54,10 @@ struct bcm_ns_usb3 { enum bcm_ns_family family; void __iomem *dmp; void __iomem *ccb_mii; + struct mdio_device *mdiodev; struct phy *phy; + + int (*phy_write)(struct bcm_ns_usb3 *usb3, u16 reg, u16 value); }; static const struct of_device_id bcm_ns_usb3_id_table[] = { @@ -68,63 +73,16 @@ static const struct of_device_id bcm_ns_usb3_id_table[] = { }; MODULE_DEVICE_TABLE(of, bcm_ns_usb3_id_table); -static int bcm_ns_usb3_wait_reg(struct bcm_ns_usb3 *usb3, void __iomem *addr, - u32 mask, u32 value, unsigned long timeout) -{ - unsigned long deadline = jiffies + timeout; - u32 val; - - do { - val = readl(addr); - if ((val & mask) == value) - return 0; - cpu_relax(); - udelay(10); - } while (!time_after_eq(jiffies, deadline)); - - dev_err(usb3->dev, "Timeout waiting for register %p\n", addr); - - return -EBUSY; -} - -static inline int bcm_ns_usb3_mii_mng_wait_idle(struct bcm_ns_usb3 *usb3) -{ - return bcm_ns_usb3_wait_reg(usb3, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL, - 0x0100, 0x0000, - usecs_to_jiffies(BCM_NS_USB3_MII_MNG_TIMEOUT_US)); -} - static int bcm_ns_usb3_mdio_phy_write(struct bcm_ns_usb3 *usb3, u16 reg, u16 value) { - u32 tmp = 0; - int err; - - err = bcm_ns_usb3_mii_mng_wait_idle(usb3); - if (err < 0) { - dev_err(usb3->dev, "Couldn't write 0x%08x value\n", value); - return err; - } - - /* TODO: Use a proper MDIO bus layer */ - tmp |= 0x58020000; /* Magic value for MDIO PHY write */ - tmp |= reg << 18; - tmp |= value; - writel(tmp, usb3->ccb_mii + BCMA_CCB_MII_MNG_CMD_DATA); - - return 0; + return usb3->phy_write(usb3, reg, value); } static int bcm_ns_usb3_phy_init_ns_bx(struct bcm_ns_usb3 *usb3) { int err; - /* Enable MDIO. Setting MDCDIV as 26 */ - writel(0x0000009a, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL); - - /* Wait for MDIO? */ - udelay(2); - /* USB3 PLL Block */ err = bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, BCM_NS_USB3_PHY_PLL30_BLOCK); @@ -143,9 +101,6 @@ static int bcm_ns_usb3_phy_init_ns_bx(struct bcm_ns_usb3 *usb3) /* Deaaserting PLL Reset */ bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL1, 0x8000); - /* Waiting MII Mgt interface idle */ - bcm_ns_usb3_mii_mng_wait_idle(usb3); - /* Deasserting USB3 system reset */ writel(0, usb3->dmp + BCMA_RESET_CTL); @@ -169,9 +124,6 @@ static int bcm_ns_usb3_phy_init_ns_bx(struct bcm_ns_usb3 *usb3) /* Enabling SSC */ bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_TX_PMD_CONTROL1, 0x1003); - /* Waiting MII Mgt interface idle */ - bcm_ns_usb3_mii_mng_wait_idle(usb3); - return 0; } @@ -179,12 +131,6 @@ static int bcm_ns_usb3_phy_init_ns_ax(struct bcm_ns_usb3 *usb3) { int err; - /* Enable MDIO. Setting MDCDIV as 26 */ - writel(0x0000009a, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL); - - /* Wait for MDIO? */ - udelay(2); - /* PLL30 block */ err = bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, BCM_NS_USB3_PHY_PLL30_BLOCK); @@ -205,9 +151,6 @@ static int bcm_ns_usb3_phy_init_ns_ax(struct bcm_ns_usb3 *usb3) bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_TX_PMD_CONTROL1, 0x1003); - /* Waiting MII Mgt interface idle */ - bcm_ns_usb3_mii_mng_wait_idle(usb3); - /* Deasserting USB3 system reset */ writel(0, usb3->dmp + BCMA_RESET_CTL); @@ -242,6 +185,128 @@ static const struct phy_ops ops = { .owner = THIS_MODULE, }; +/************************************************** + * MDIO driver code + **************************************************/ + +static int bcm_ns_usb3_mdiodev_phy_write(struct bcm_ns_usb3 *usb3, u16 reg, + u16 value) +{ + struct mdio_device *mdiodev = usb3->mdiodev; + + return mdiobus_write(mdiodev->bus, mdiodev->addr, reg, value); +} + +static int bcm_ns_usb3_mdio_probe(struct mdio_device *mdiodev) +{ + struct device *dev = &mdiodev->dev; + const struct of_device_id *of_id; + struct phy_provider *phy_provider; + struct device_node *syscon_np; + struct bcm_ns_usb3 *usb3; + struct resource res; + int err; + + usb3 = devm_kzalloc(dev, sizeof(*usb3), GFP_KERNEL); + if (!usb3) + return -ENOMEM; + + usb3->dev = dev; + usb3->mdiodev = mdiodev; + + of_id = of_match_device(bcm_ns_usb3_id_table, dev); + if (!of_id) + return -EINVAL; + usb3->family = (enum bcm_ns_family)of_id->data; + + syscon_np = of_parse_phandle(dev->of_node, "usb3-dmp-syscon", 0); + err = of_address_to_resource(syscon_np, 0, &res); + of_node_put(syscon_np); + if (err) + return err; + + usb3->dmp = devm_ioremap_resource(dev, &res); + if (IS_ERR(usb3->dmp)) { + dev_err(dev, "Failed to map DMP regs\n"); + return PTR_ERR(usb3->dmp); + } + + usb3->phy_write = bcm_ns_usb3_mdiodev_phy_write; + + usb3->phy = devm_phy_create(dev, NULL, &ops); + if (IS_ERR(usb3->phy)) { + dev_err(dev, "Failed to create PHY\n"); + return PTR_ERR(usb3->phy); + } + + phy_set_drvdata(usb3->phy, usb3); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static struct mdio_driver bcm_ns_usb3_mdio_driver = { + .mdiodrv = { + .driver = { + .name = "bcm_ns_mdio_usb3", + .of_match_table = bcm_ns_usb3_id_table, + }, + }, + .probe = bcm_ns_usb3_mdio_probe, +}; + +/************************************************** + * Platform driver code + **************************************************/ + +static int bcm_ns_usb3_wait_reg(struct bcm_ns_usb3 *usb3, void __iomem *addr, + u32 mask, u32 value, unsigned long timeout) +{ + unsigned long deadline = jiffies + timeout; + u32 val; + + do { + val = readl(addr); + if ((val & mask) == value) + return 0; + cpu_relax(); + udelay(10); + } while (!time_after_eq(jiffies, deadline)); + + dev_err(usb3->dev, "Timeout waiting for register %p\n", addr); + + return -EBUSY; +} + +static inline int bcm_ns_usb3_mii_mng_wait_idle(struct bcm_ns_usb3 *usb3) +{ + return bcm_ns_usb3_wait_reg(usb3, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL, + 0x0100, 0x0000, + usecs_to_jiffies(BCM_NS_USB3_MII_MNG_TIMEOUT_US)); +} + +static int bcm_ns_usb3_platform_phy_write(struct bcm_ns_usb3 *usb3, u16 reg, + u16 value) +{ + u32 tmp = 0; + int err; + + err = bcm_ns_usb3_mii_mng_wait_idle(usb3); + if (err < 0) { + dev_err(usb3->dev, "Couldn't write 0x%08x value\n", value); + return err; + } + + /* TODO: Use a proper MDIO bus layer */ + tmp |= 0x58020000; /* Magic value for MDIO PHY write */ + tmp |= reg << 18; + tmp |= value; + writel(tmp, usb3->ccb_mii + BCMA_CCB_MII_MNG_CMD_DATA); + + return bcm_ns_usb3_mii_mng_wait_idle(usb3); +} + static int bcm_ns_usb3_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -275,6 +340,14 @@ static int bcm_ns_usb3_probe(struct platform_device *pdev) return PTR_ERR(usb3->ccb_mii); } + /* Enable MDIO. Setting MDCDIV as 26 */ + writel(0x0000009a, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL); + + /* Wait for MDIO? */ + udelay(2); + + usb3->phy_write = bcm_ns_usb3_platform_phy_write; + usb3->phy = devm_phy_create(dev, NULL, &ops); if (IS_ERR(usb3->phy)) { dev_err(dev, "Failed to create PHY\n"); @@ -298,6 +371,35 @@ static struct platform_driver bcm_ns_usb3_driver = { .of_match_table = bcm_ns_usb3_id_table, }, }; -module_platform_driver(bcm_ns_usb3_driver); + +static int __init bcm_ns_usb3_module_init(void) +{ + int err; + + /* + * For backward compatibility we register as MDIO and platform driver. + * After getting MDIO binding commonly used (e.g. switching all DT files + * to use it) we should deprecate the old binding and eventually drop + * support for it. + */ + + err = mdio_driver_register(&bcm_ns_usb3_mdio_driver); + if (err) + return err; + + err = platform_driver_register(&bcm_ns_usb3_driver); + if (err) + mdio_driver_unregister(&bcm_ns_usb3_mdio_driver); + + return err; +} +module_init(bcm_ns_usb3_module_init); + +static void __exit bcm_ns_usb3_module_exit(void) +{ + platform_driver_unregister(&bcm_ns_usb3_driver); + mdio_driver_unregister(&bcm_ns_usb3_mdio_driver); +} +module_exit(bcm_ns_usb3_module_exit) MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-bcm-ns2-pcie.c b/drivers/phy/broadcom/phy-bcm-ns2-pcie.c index 4c7d11d2b378..4c7d11d2b378 100644 --- a/drivers/phy/phy-bcm-ns2-pcie.c +++ b/drivers/phy/broadcom/phy-bcm-ns2-pcie.c diff --git a/drivers/phy/broadcom/phy-bcm-ns2-usbdrd.c b/drivers/phy/broadcom/phy-bcm-ns2-usbdrd.c new file mode 100644 index 000000000000..9ae59e223131 --- /dev/null +++ b/drivers/phy/broadcom/phy-bcm-ns2-usbdrd.c @@ -0,0 +1,437 @@ +/* + * Copyright (C) 2017 Broadcom + * + * 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. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/delay.h> +#include <linux/extcon.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/workqueue.h> + +#define ICFG_DRD_AFE 0x0 +#define ICFG_MISC_STAT 0x18 +#define ICFG_DRD_P0CTL 0x1C +#define ICFG_STRAP_CTRL 0x20 +#define ICFG_FSM_CTRL 0x24 + +#define ICFG_DEV_BIT BIT(2) +#define IDM_RST_BIT BIT(0) +#define AFE_CORERDY_VDDC BIT(18) +#define PHY_PLL_RESETB BIT(15) +#define PHY_RESETB BIT(14) +#define PHY_PLL_LOCK BIT(0) + +#define DRD_DEV_MODE BIT(20) +#define OHCI_OVRCUR_POL BIT(11) +#define ICFG_OFF_MODE BIT(6) +#define PLL_LOCK_RETRY 1000 + +#define EVT_DEVICE 0 +#define EVT_HOST 1 + +#define DRD_HOST_MODE (BIT(2) | BIT(3)) +#define DRD_DEVICE_MODE (BIT(4) | BIT(5)) +#define DRD_HOST_VAL 0x803 +#define DRD_DEV_VAL 0x807 +#define GPIO_DELAY 20 + +struct ns2_phy_data; +struct ns2_phy_driver { + void __iomem *icfgdrd_regs; + void __iomem *idmdrd_rst_ctrl; + void __iomem *crmu_usb2_ctrl; + void __iomem *usb2h_strap_reg; + struct ns2_phy_data *data; + struct extcon_dev *edev; + struct gpio_desc *vbus_gpiod; + struct gpio_desc *id_gpiod; + int id_irq; + int vbus_irq; + unsigned long debounce_jiffies; + struct delayed_work wq_extcon; +}; + +struct ns2_phy_data { + struct ns2_phy_driver *driver; + struct phy *phy; + int new_state; +}; + +static const unsigned int usb_extcon_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_NONE, +}; + +static inline int pll_lock_stat(u32 usb_reg, int reg_mask, + struct ns2_phy_driver *driver) +{ + int retry = PLL_LOCK_RETRY; + u32 val; + + do { + udelay(1); + val = readl(driver->icfgdrd_regs + usb_reg); + if (val & reg_mask) + return 0; + } while (--retry > 0); + + return -EBUSY; +} + +static int ns2_drd_phy_init(struct phy *phy) +{ + struct ns2_phy_data *data = phy_get_drvdata(phy); + struct ns2_phy_driver *driver = data->driver; + u32 val; + + val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL); + + if (data->new_state == EVT_HOST) { + val &= ~DRD_DEVICE_MODE; + val |= DRD_HOST_MODE; + } else { + val &= ~DRD_HOST_MODE; + val |= DRD_DEVICE_MODE; + } + writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL); + + return 0; +} + +static int ns2_drd_phy_poweroff(struct phy *phy) +{ + struct ns2_phy_data *data = phy_get_drvdata(phy); + struct ns2_phy_driver *driver = data->driver; + u32 val; + + val = readl(driver->crmu_usb2_ctrl); + val &= ~AFE_CORERDY_VDDC; + writel(val, driver->crmu_usb2_ctrl); + + val = readl(driver->crmu_usb2_ctrl); + val &= ~DRD_DEV_MODE; + writel(val, driver->crmu_usb2_ctrl); + + /* Disable Host and Device Mode */ + val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL); + val &= ~(DRD_HOST_MODE | DRD_DEVICE_MODE | ICFG_OFF_MODE); + writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL); + + return 0; +} + +static int ns2_drd_phy_poweron(struct phy *phy) +{ + struct ns2_phy_data *data = phy_get_drvdata(phy); + struct ns2_phy_driver *driver = data->driver; + u32 extcon_event = data->new_state; + int ret; + u32 val; + + if (extcon_event == EVT_DEVICE) { + writel(DRD_DEV_VAL, driver->icfgdrd_regs + ICFG_DRD_P0CTL); + + val = readl(driver->idmdrd_rst_ctrl); + val &= ~IDM_RST_BIT; + writel(val, driver->idmdrd_rst_ctrl); + + val = readl(driver->crmu_usb2_ctrl); + val |= (AFE_CORERDY_VDDC | DRD_DEV_MODE); + writel(val, driver->crmu_usb2_ctrl); + + /* Bring PHY and PHY_PLL out of Reset */ + val = readl(driver->crmu_usb2_ctrl); + val |= (PHY_PLL_RESETB | PHY_RESETB); + writel(val, driver->crmu_usb2_ctrl); + + ret = pll_lock_stat(ICFG_MISC_STAT, PHY_PLL_LOCK, driver); + if (ret < 0) { + dev_err(&phy->dev, "Phy PLL lock failed\n"); + return ret; + } + } else { + writel(DRD_HOST_VAL, driver->icfgdrd_regs + ICFG_DRD_P0CTL); + + val = readl(driver->crmu_usb2_ctrl); + val |= AFE_CORERDY_VDDC; + writel(val, driver->crmu_usb2_ctrl); + + ret = pll_lock_stat(ICFG_MISC_STAT, PHY_PLL_LOCK, driver); + if (ret < 0) { + dev_err(&phy->dev, "Phy PLL lock failed\n"); + return ret; + } + + val = readl(driver->idmdrd_rst_ctrl); + val &= ~IDM_RST_BIT; + writel(val, driver->idmdrd_rst_ctrl); + + /* port over current Polarity */ + val = readl(driver->usb2h_strap_reg); + val |= OHCI_OVRCUR_POL; + writel(val, driver->usb2h_strap_reg); + } + + return 0; +} + +static void connect_change(struct ns2_phy_driver *driver) +{ + u32 extcon_event; + u32 val; + + extcon_event = driver->data->new_state; + val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL); + + switch (extcon_event) { + case EVT_DEVICE: + val &= ~(DRD_HOST_MODE | DRD_DEVICE_MODE); + writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL); + + val = (val & ~DRD_HOST_MODE) | DRD_DEVICE_MODE; + writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL); + + val = readl(driver->icfgdrd_regs + ICFG_DRD_P0CTL); + val |= ICFG_DEV_BIT; + writel(val, driver->icfgdrd_regs + ICFG_DRD_P0CTL); + break; + + case EVT_HOST: + val &= ~(DRD_HOST_MODE | DRD_DEVICE_MODE); + writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL); + + val = (val & ~DRD_DEVICE_MODE) | DRD_HOST_MODE; + writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL); + + val = readl(driver->usb2h_strap_reg); + val |= OHCI_OVRCUR_POL; + writel(val, driver->usb2h_strap_reg); + + val = readl(driver->icfgdrd_regs + ICFG_DRD_P0CTL); + val &= ~ICFG_DEV_BIT; + writel(val, driver->icfgdrd_regs + ICFG_DRD_P0CTL); + break; + + default: + pr_err("Invalid extcon event\n"); + break; + } +} + +static void extcon_work(struct work_struct *work) +{ + struct ns2_phy_driver *driver; + int vbus; + int id; + + driver = container_of(to_delayed_work(work), + struct ns2_phy_driver, wq_extcon); + + id = gpiod_get_value_cansleep(driver->id_gpiod); + vbus = gpiod_get_value_cansleep(driver->vbus_gpiod); + + if (!id && vbus) { /* Host connected */ + extcon_set_cable_state_(driver->edev, EXTCON_USB_HOST, true); + pr_debug("Host cable connected\n"); + driver->data->new_state = EVT_HOST; + connect_change(driver); + } else if (id && !vbus) { /* Disconnected */ + extcon_set_cable_state_(driver->edev, EXTCON_USB_HOST, false); + extcon_set_cable_state_(driver->edev, EXTCON_USB, false); + pr_debug("Cable disconnected\n"); + } else if (id && vbus) { /* Device connected */ + extcon_set_cable_state_(driver->edev, EXTCON_USB, true); + pr_debug("Device cable connected\n"); + driver->data->new_state = EVT_DEVICE; + connect_change(driver); + } +} + +static irqreturn_t gpio_irq_handler(int irq, void *dev_id) +{ + struct ns2_phy_driver *driver = dev_id; + + queue_delayed_work(system_power_efficient_wq, &driver->wq_extcon, + driver->debounce_jiffies); + + return IRQ_HANDLED; +} + +static struct phy_ops ops = { + .init = ns2_drd_phy_init, + .power_on = ns2_drd_phy_poweron, + .power_off = ns2_drd_phy_poweroff, + .owner = THIS_MODULE, +}; + +static const struct of_device_id ns2_drd_phy_dt_ids[] = { + { .compatible = "brcm,ns2-drd-phy", }, + { } +}; +MODULE_DEVICE_TABLE(of, ns2_drd_phy_dt_ids); + +static int ns2_drd_phy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct ns2_phy_driver *driver; + struct ns2_phy_data *data; + struct resource *res; + int ret; + u32 val; + + driver = devm_kzalloc(dev, sizeof(struct ns2_phy_driver), + GFP_KERNEL); + if (!driver) + return -ENOMEM; + + driver->data = devm_kzalloc(dev, sizeof(struct ns2_phy_data), + GFP_KERNEL); + if (!driver->data) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "icfg"); + driver->icfgdrd_regs = devm_ioremap_resource(dev, res); + if (IS_ERR(driver->icfgdrd_regs)) + return PTR_ERR(driver->icfgdrd_regs); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rst-ctrl"); + driver->idmdrd_rst_ctrl = devm_ioremap_resource(dev, res); + if (IS_ERR(driver->idmdrd_rst_ctrl)) + return PTR_ERR(driver->idmdrd_rst_ctrl); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "crmu-ctrl"); + driver->crmu_usb2_ctrl = devm_ioremap_resource(dev, res); + if (IS_ERR(driver->crmu_usb2_ctrl)) + return PTR_ERR(driver->crmu_usb2_ctrl); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2-strap"); + driver->usb2h_strap_reg = devm_ioremap_resource(dev, res); + if (IS_ERR(driver->usb2h_strap_reg)) + return PTR_ERR(driver->usb2h_strap_reg); + + /* create extcon */ + driver->id_gpiod = devm_gpiod_get(&pdev->dev, "id", GPIOD_IN); + if (IS_ERR(driver->id_gpiod)) { + dev_err(dev, "failed to get ID GPIO\n"); + return PTR_ERR(driver->id_gpiod); + } + driver->vbus_gpiod = devm_gpiod_get(&pdev->dev, "vbus", GPIOD_IN); + if (IS_ERR(driver->vbus_gpiod)) { + dev_err(dev, "failed to get VBUS GPIO\n"); + return PTR_ERR(driver->vbus_gpiod); + } + + driver->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable); + if (IS_ERR(driver->edev)) { + dev_err(dev, "failed to allocate extcon device\n"); + return -ENOMEM; + } + + ret = devm_extcon_dev_register(dev, driver->edev); + if (ret < 0) { + dev_err(dev, "failed to register extcon device\n"); + return ret; + } + + ret = gpiod_set_debounce(driver->id_gpiod, GPIO_DELAY * 1000); + if (ret < 0) + driver->debounce_jiffies = msecs_to_jiffies(GPIO_DELAY); + + INIT_DELAYED_WORK(&driver->wq_extcon, extcon_work); + + driver->id_irq = gpiod_to_irq(driver->id_gpiod); + if (driver->id_irq < 0) { + dev_err(dev, "failed to get ID IRQ\n"); + return driver->id_irq; + } + + driver->vbus_irq = gpiod_to_irq(driver->vbus_gpiod); + if (driver->vbus_irq < 0) { + dev_err(dev, "failed to get ID IRQ\n"); + return driver->vbus_irq; + } + + ret = devm_request_irq(dev, driver->id_irq, gpio_irq_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "usb_id", driver); + if (ret < 0) { + dev_err(dev, "failed to request handler for ID IRQ\n"); + return ret; + } + + ret = devm_request_irq(dev, driver->vbus_irq, gpio_irq_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "usb_vbus", driver); + if (ret < 0) { + dev_err(dev, "failed to request handler for VBUS IRQ\n"); + return ret; + } + + dev_set_drvdata(dev, driver); + + /* Shutdown all ports. They can be powered up as required */ + val = readl(driver->crmu_usb2_ctrl); + val &= ~(AFE_CORERDY_VDDC | PHY_RESETB); + writel(val, driver->crmu_usb2_ctrl); + + data = driver->data; + data->phy = devm_phy_create(dev, dev->of_node, &ops); + if (IS_ERR(data->phy)) { + dev_err(dev, "Failed to create usb drd phy\n"); + return PTR_ERR(data->phy); + } + + data->driver = driver; + phy_set_drvdata(data->phy, data); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(phy_provider)) { + dev_err(dev, "Failed to register as phy provider\n"); + return PTR_ERR(phy_provider); + } + + platform_set_drvdata(pdev, driver); + + dev_info(dev, "Registered NS2 DRD Phy device\n"); + queue_delayed_work(system_power_efficient_wq, &driver->wq_extcon, + driver->debounce_jiffies); + + return 0; +} + +static struct platform_driver ns2_drd_phy_driver = { + .probe = ns2_drd_phy_probe, + .driver = { + .name = "bcm-ns2-usbphy", + .of_match_table = of_match_ptr(ns2_drd_phy_dt_ids), + }, +}; +module_platform_driver(ns2_drd_phy_driver); + +MODULE_ALIAS("platform:bcm-ns2-drd-phy"); +MODULE_AUTHOR("Broadcom"); +MODULE_DESCRIPTION("Broadcom NS2 USB2 PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-brcm-sata.c b/drivers/phy/broadcom/phy-brcm-sata.c index ccbc3d994998..e6544c8b1ace 100644 --- a/drivers/phy/phy-brcm-sata.c +++ b/drivers/phy/broadcom/phy-brcm-sata.c @@ -46,6 +46,7 @@ enum brcm_sata_phy_version { BRCM_SATA_PHY_STB_40NM, BRCM_SATA_PHY_IPROC_NS2, BRCM_SATA_PHY_IPROC_NSP, + BRCM_SATA_PHY_IPROC_SR, }; struct brcm_sata_port { @@ -81,12 +82,17 @@ enum sata_phy_regs { PLL_ACTRL2 = 0x8b, PLL_ACTRL2_SELDIV_MASK = 0x1f, PLL_ACTRL2_SELDIV_SHIFT = 9, + PLL_ACTRL6 = 0x86, PLL1_REG_BANK = 0x060, PLL1_ACTRL2 = 0x82, PLL1_ACTRL3 = 0x83, PLL1_ACTRL4 = 0x84, + TX_REG_BANK = 0x070, + TX_ACTRL0 = 0x80, + TX_ACTRL0_TXPOL_FLIP = BIT(6), + OOB_REG_BANK = 0x150, OOB1_REG_BANK = 0x160, OOB_CTRL1 = 0x80, @@ -347,6 +353,68 @@ static int brcm_nsp_sata_init(struct brcm_sata_port *port) return 0; } +/* SR PHY PLL0 registers */ +#define SR_PLL0_ACTRL6_MAGIC 0xa + +/* SR PHY PLL1 registers */ +#define SR_PLL1_ACTRL2_MAGIC 0x32 +#define SR_PLL1_ACTRL3_MAGIC 0x2 +#define SR_PLL1_ACTRL4_MAGIC 0x3e8 + +static int brcm_sr_sata_init(struct brcm_sata_port *port) +{ + struct brcm_sata_phy *priv = port->phy_priv; + struct device *dev = port->phy_priv->dev; + void __iomem *base = priv->phy_base; + unsigned int val, try; + + /* Configure PHY PLL register bank 1 */ + val = SR_PLL1_ACTRL2_MAGIC; + brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL2, 0x0, val); + val = SR_PLL1_ACTRL3_MAGIC; + brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL3, 0x0, val); + val = SR_PLL1_ACTRL4_MAGIC; + brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL4, 0x0, val); + + /* Configure PHY PLL register bank 0 */ + val = SR_PLL0_ACTRL6_MAGIC; + brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_ACTRL6, 0x0, val); + + /* Wait for PHY PLL lock by polling pll_lock bit */ + try = 50; + do { + val = brcm_sata_phy_rd(base, BLOCK0_REG_BANK, + BLOCK0_XGXSSTATUS); + if (val & BLOCK0_XGXSSTATUS_PLL_LOCK) + break; + msleep(20); + try--; + } while (try); + + if ((val & BLOCK0_XGXSSTATUS_PLL_LOCK) == 0) { + /* PLL did not lock; give up */ + dev_err(dev, "port%d PLL did not lock\n", port->portnum); + return -ETIMEDOUT; + } + + /* Invert Tx polarity */ + brcm_sata_phy_wr(base, TX_REG_BANK, TX_ACTRL0, + ~TX_ACTRL0_TXPOL_FLIP, TX_ACTRL0_TXPOL_FLIP); + + /* Configure OOB control to handle 100MHz reference clock */ + val = ((0xc << OOB_CTRL1_BURST_MAX_SHIFT) | + (0x4 << OOB_CTRL1_BURST_MIN_SHIFT) | + (0x8 << OOB_CTRL1_WAKE_IDLE_MAX_SHIFT) | + (0x3 << OOB_CTRL1_WAKE_IDLE_MIN_SHIFT)); + brcm_sata_phy_wr(base, OOB_REG_BANK, OOB_CTRL1, 0x0, val); + val = ((0x1b << OOB_CTRL2_RESET_IDLE_MAX_SHIFT) | + (0x2 << OOB_CTRL2_BURST_CNT_SHIFT) | + (0x9 << OOB_CTRL2_RESET_IDLE_MIN_SHIFT)); + brcm_sata_phy_wr(base, OOB_REG_BANK, OOB_CTRL2, 0x0, val); + + return 0; +} + static int brcm_sata_phy_init(struct phy *phy) { int rc; @@ -363,6 +431,9 @@ static int brcm_sata_phy_init(struct phy *phy) case BRCM_SATA_PHY_IPROC_NSP: rc = brcm_nsp_sata_init(port); break; + case BRCM_SATA_PHY_IPROC_SR: + rc = brcm_sr_sata_init(port); + break; default: rc = -ENODEV; } @@ -384,6 +455,8 @@ static const struct of_device_id brcm_sata_phy_of_match[] = { .data = (void *)BRCM_SATA_PHY_IPROC_NS2 }, { .compatible = "brcm,iproc-nsp-sata-phy", .data = (void *)BRCM_SATA_PHY_IPROC_NSP }, + { .compatible = "brcm,iproc-sr-sata-phy", + .data = (void *)BRCM_SATA_PHY_IPROC_SR }, {}, }; MODULE_DEVICE_TABLE(of, brcm_sata_phy_of_match); diff --git a/drivers/phy/hisilicon/Kconfig b/drivers/phy/hisilicon/Kconfig new file mode 100644 index 000000000000..6164c4cd0f65 --- /dev/null +++ b/drivers/phy/hisilicon/Kconfig @@ -0,0 +1,20 @@ +# +# Phy drivers for Hisilicon platforms +# +config PHY_HI6220_USB + tristate "hi6220 USB PHY support" + depends on (ARCH_HISI && ARM64) || COMPILE_TEST + 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_HIX5HD2_SATA + tristate "HIX5HD2 SATA PHY Driver" + depends on ARCH_HIX5HD2 && OF && HAS_IOMEM + select GENERIC_PHY + select MFD_SYSCON + help + Support for SATA PHY on Hisilicon hix5hd2 Soc. diff --git a/drivers/phy/hisilicon/Makefile b/drivers/phy/hisilicon/Makefile new file mode 100644 index 000000000000..541b348187a8 --- /dev/null +++ b/drivers/phy/hisilicon/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_PHY_HI6220_USB) += phy-hi6220-usb.o +obj-$(CONFIG_PHY_HIX5HD2_SATA) += phy-hix5hd2-sata.o diff --git a/drivers/phy/phy-hi6220-usb.c b/drivers/phy/hisilicon/phy-hi6220-usb.c index 398c1021deec..398c1021deec 100644 --- a/drivers/phy/phy-hi6220-usb.c +++ b/drivers/phy/hisilicon/phy-hi6220-usb.c diff --git a/drivers/phy/phy-hix5hd2-sata.c b/drivers/phy/hisilicon/phy-hix5hd2-sata.c index e5ab3aa78b9d..e5ab3aa78b9d 100644 --- a/drivers/phy/phy-hix5hd2-sata.c +++ b/drivers/phy/hisilicon/phy-hix5hd2-sata.c diff --git a/drivers/phy/marvell/Kconfig b/drivers/phy/marvell/Kconfig new file mode 100644 index 000000000000..048d8893bc2e --- /dev/null +++ b/drivers/phy/marvell/Kconfig @@ -0,0 +1,50 @@ +# +# Phy drivers for Marvell platforms +# +config ARMADA375_USBCLUSTER_PHY + def_bool y + depends on MACH_ARMADA_375 || COMPILE_TEST + depends on OF && HAS_IOMEM + select GENERIC_PHY + +config PHY_BERLIN_SATA + tristate "Marvell Berlin SATA PHY driver" + depends on ARCH_BERLIN && HAS_IOMEM && OF + select GENERIC_PHY + help + Enable this to support the SATA PHY on Marvell Berlin SoCs. + +config PHY_BERLIN_USB + tristate "Marvell Berlin USB PHY Driver" + depends on ARCH_BERLIN && RESET_CONTROLLER && HAS_IOMEM && OF + select GENERIC_PHY + help + Enable this to support the USB PHY on Marvell Berlin SoCs. + +config PHY_MVEBU_SATA + def_bool y + depends on ARCH_DOVE || MACH_DOVE || MACH_KIRKWOOD + depends on OF + select GENERIC_PHY + +config PHY_PXA_28NM_HSIC + tristate "Marvell USB HSIC 28nm PHY Driver" + depends on HAS_IOMEM + select GENERIC_PHY + help + Enable this to support Marvell USB HSIC PHY driver for Marvell + SoC. This driver will do the PHY initialization and shutdown. + The PHY driver will be used by Marvell ehci driver. + + To compile this driver as a module, choose M here. + +config PHY_PXA_28NM_USB2 + tristate "Marvell USB 2.0 28nm PHY Driver" + depends on HAS_IOMEM + select GENERIC_PHY + help + Enable this to support Marvell USB 2.0 PHY driver for Marvell + SoC. This driver will do the PHY initialization and shutdown. + The PHY driver will be used by Marvell udc/ehci/otg driver. + + To compile this driver as a module, choose M here. diff --git a/drivers/phy/marvell/Makefile b/drivers/phy/marvell/Makefile new file mode 100644 index 000000000000..3fc188f59118 --- /dev/null +++ b/drivers/phy/marvell/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_ARMADA375_USBCLUSTER_PHY) += phy-armada375-usb2.o +obj-$(CONFIG_PHY_BERLIN_SATA) += phy-berlin-sata.o +obj-$(CONFIG_PHY_BERLIN_USB) += phy-berlin-usb.o +obj-$(CONFIG_PHY_MVEBU_SATA) += phy-mvebu-sata.o +obj-$(CONFIG_PHY_PXA_28NM_HSIC) += phy-pxa-28nm-hsic.o +obj-$(CONFIG_PHY_PXA_28NM_USB2) += phy-pxa-28nm-usb2.o diff --git a/drivers/phy/phy-armada375-usb2.c b/drivers/phy/marvell/phy-armada375-usb2.c index 1a3db288c0a9..1a3db288c0a9 100644 --- a/drivers/phy/phy-armada375-usb2.c +++ b/drivers/phy/marvell/phy-armada375-usb2.c diff --git a/drivers/phy/phy-berlin-sata.c b/drivers/phy/marvell/phy-berlin-sata.c index 2c7a57f2d595..2c7a57f2d595 100644 --- a/drivers/phy/phy-berlin-sata.c +++ b/drivers/phy/marvell/phy-berlin-sata.c diff --git a/drivers/phy/phy-berlin-usb.c b/drivers/phy/marvell/phy-berlin-usb.c index 2017751ede26..2017751ede26 100644 --- a/drivers/phy/phy-berlin-usb.c +++ b/drivers/phy/marvell/phy-berlin-usb.c diff --git a/drivers/phy/phy-mvebu-sata.c b/drivers/phy/marvell/phy-mvebu-sata.c index 768ce92e81ce..768ce92e81ce 100644 --- a/drivers/phy/phy-mvebu-sata.c +++ b/drivers/phy/marvell/phy-mvebu-sata.c diff --git a/drivers/phy/phy-pxa-28nm-hsic.c b/drivers/phy/marvell/phy-pxa-28nm-hsic.c index 234aacf4db20..234aacf4db20 100644 --- a/drivers/phy/phy-pxa-28nm-hsic.c +++ b/drivers/phy/marvell/phy-pxa-28nm-hsic.c diff --git a/drivers/phy/phy-pxa-28nm-usb2.c b/drivers/phy/marvell/phy-pxa-28nm-usb2.c index 37e9c8ca4983..37e9c8ca4983 100644 --- a/drivers/phy/phy-pxa-28nm-usb2.c +++ b/drivers/phy/marvell/phy-pxa-28nm-usb2.c diff --git a/drivers/phy/motorola/Kconfig b/drivers/phy/motorola/Kconfig new file mode 100644 index 000000000000..6bb7d6bdf1bf --- /dev/null +++ b/drivers/phy/motorola/Kconfig @@ -0,0 +1,12 @@ +# +# Phy drivers for Motorola devices +# +config PHY_CPCAP_USB + tristate "CPCAP PMIC USB PHY driver" + depends on USB_SUPPORT && IIO + depends on USB_MUSB_HDRC || USB_MUSB_HDRC=n + select GENERIC_PHY + select USB_PHY + help + Enable this for USB to work on Motorola phones and tablets + such as Droid 4. diff --git a/drivers/phy/motorola/Makefile b/drivers/phy/motorola/Makefile new file mode 100644 index 000000000000..b6cd618d671a --- /dev/null +++ b/drivers/phy/motorola/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the phy drivers. +# + +obj-$(CONFIG_PHY_CPCAP_USB) += phy-cpcap-usb.o diff --git a/drivers/phy/motorola/phy-cpcap-usb.c b/drivers/phy/motorola/phy-cpcap-usb.c new file mode 100644 index 000000000000..9b63efa5ae4d --- /dev/null +++ b/drivers/phy/motorola/phy-cpcap-usb.c @@ -0,0 +1,676 @@ +/* + * Motorola CPCAP PMIC USB PHY driver + * Copyright (C) 2017 Tony Lindgren <tony@atomide.com> + * + * Some parts based on earlier Motorola Linux kernel tree code in + * board-mapphone-usb.c and cpcap-usb-det.c: + * Copyright (C) 2007 - 2011 Motorola, Inc. + * + * 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. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/atomic.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/iio/consumer.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#include <linux/gpio/consumer.h> +#include <linux/mfd/motorola-cpcap.h> +#include <linux/phy/omap_usb.h> +#include <linux/phy/phy.h> +#include <linux/regulator/consumer.h> +#include <linux/usb/musb.h> + +/* CPCAP_REG_USBC1 register bits */ +#define CPCAP_BIT_IDPULSE BIT(15) +#define CPCAP_BIT_ID100KPU BIT(14) +#define CPCAP_BIT_IDPUCNTRL BIT(13) +#define CPCAP_BIT_IDPU BIT(12) +#define CPCAP_BIT_IDPD BIT(11) +#define CPCAP_BIT_VBUSCHRGTMR3 BIT(10) +#define CPCAP_BIT_VBUSCHRGTMR2 BIT(9) +#define CPCAP_BIT_VBUSCHRGTMR1 BIT(8) +#define CPCAP_BIT_VBUSCHRGTMR0 BIT(7) +#define CPCAP_BIT_VBUSPU BIT(6) +#define CPCAP_BIT_VBUSPD BIT(5) +#define CPCAP_BIT_DMPD BIT(4) +#define CPCAP_BIT_DPPD BIT(3) +#define CPCAP_BIT_DM1K5PU BIT(2) +#define CPCAP_BIT_DP1K5PU BIT(1) +#define CPCAP_BIT_DP150KPU BIT(0) + +/* CPCAP_REG_USBC2 register bits */ +#define CPCAP_BIT_ZHSDRV1 BIT(15) +#define CPCAP_BIT_ZHSDRV0 BIT(14) +#define CPCAP_BIT_DPLLCLKREQ BIT(13) +#define CPCAP_BIT_SE0CONN BIT(12) +#define CPCAP_BIT_UARTTXTRI BIT(11) +#define CPCAP_BIT_UARTSWAP BIT(10) +#define CPCAP_BIT_UARTMUX1 BIT(9) +#define CPCAP_BIT_UARTMUX0 BIT(8) +#define CPCAP_BIT_ULPISTPLOW BIT(7) +#define CPCAP_BIT_TXENPOL BIT(6) +#define CPCAP_BIT_USBXCVREN BIT(5) +#define CPCAP_BIT_USBCNTRL BIT(4) +#define CPCAP_BIT_USBSUSPEND BIT(3) +#define CPCAP_BIT_EMUMODE2 BIT(2) +#define CPCAP_BIT_EMUMODE1 BIT(1) +#define CPCAP_BIT_EMUMODE0 BIT(0) + +/* CPCAP_REG_USBC3 register bits */ +#define CPCAP_BIT_SPARE_898_15 BIT(15) +#define CPCAP_BIT_IHSTX03 BIT(14) +#define CPCAP_BIT_IHSTX02 BIT(13) +#define CPCAP_BIT_IHSTX01 BIT(12) +#define CPCAP_BIT_IHSTX0 BIT(11) +#define CPCAP_BIT_IDPU_SPI BIT(10) +#define CPCAP_BIT_UNUSED_898_9 BIT(9) +#define CPCAP_BIT_VBUSSTBY_EN BIT(8) +#define CPCAP_BIT_VBUSEN_SPI BIT(7) +#define CPCAP_BIT_VBUSPU_SPI BIT(6) +#define CPCAP_BIT_VBUSPD_SPI BIT(5) +#define CPCAP_BIT_DMPD_SPI BIT(4) +#define CPCAP_BIT_DPPD_SPI BIT(3) +#define CPCAP_BIT_SUSPEND_SPI BIT(2) +#define CPCAP_BIT_PU_SPI BIT(1) +#define CPCAP_BIT_ULPI_SPI_SEL BIT(0) + +struct cpcap_usb_ints_state { + bool id_ground; + bool id_float; + bool chrg_det; + bool rvrs_chrg; + bool vbusov; + + bool chrg_se1b; + bool se0conn; + bool rvrs_mode; + bool chrgcurr1; + bool vbusvld; + bool sessvld; + bool sessend; + bool se1; + + bool battdetb; + bool dm; + bool dp; +}; + +enum cpcap_gpio_mode { + CPCAP_DM_DP, + CPCAP_MDM_RX_TX, + CPCAP_UNKNOWN, + CPCAP_OTG_DM_DP, +}; + +struct cpcap_phy_ddata { + struct regmap *reg; + struct device *dev; + struct clk *refclk; + struct usb_phy phy; + struct delayed_work detect_work; + struct pinctrl *pins; + struct pinctrl_state *pins_ulpi; + struct pinctrl_state *pins_utmi; + struct pinctrl_state *pins_uart; + struct gpio_desc *gpio[2]; + struct iio_channel *vbus; + struct iio_channel *id; + struct regulator *vusb; + atomic_t active; +}; + +static bool cpcap_usb_vbus_valid(struct cpcap_phy_ddata *ddata) +{ + int error, value = 0; + + error = iio_read_channel_processed(ddata->vbus, &value); + if (error >= 0) + return value > 3900 ? true : false; + + dev_err(ddata->dev, "error reading VBUS: %i\n", error); + + return false; +} + +static int cpcap_usb_phy_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + otg->host = host; + if (!host) + otg->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int cpcap_usb_phy_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + otg->gadget = gadget; + if (!gadget) + otg->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static const struct phy_ops ops = { + .owner = THIS_MODULE, +}; + +static int cpcap_phy_get_ints_state(struct cpcap_phy_ddata *ddata, + struct cpcap_usb_ints_state *s) +{ + int val, error; + + error = regmap_read(ddata->reg, CPCAP_REG_INTS1, &val); + if (error) + return error; + + s->id_ground = val & BIT(15); + s->id_float = val & BIT(14); + s->vbusov = val & BIT(11); + + error = regmap_read(ddata->reg, CPCAP_REG_INTS2, &val); + if (error) + return error; + + s->vbusvld = val & BIT(3); + s->sessvld = val & BIT(2); + s->sessend = val & BIT(1); + s->se1 = val & BIT(0); + + error = regmap_read(ddata->reg, CPCAP_REG_INTS4, &val); + if (error) + return error; + + s->dm = val & BIT(1); + s->dp = val & BIT(0); + + return 0; +} + +static int cpcap_usb_set_uart_mode(struct cpcap_phy_ddata *ddata); +static int cpcap_usb_set_usb_mode(struct cpcap_phy_ddata *ddata); + +static void cpcap_usb_detect(struct work_struct *work) +{ + struct cpcap_phy_ddata *ddata; + struct cpcap_usb_ints_state s; + bool vbus = false; + int error; + + ddata = container_of(work, struct cpcap_phy_ddata, detect_work.work); + + error = cpcap_phy_get_ints_state(ddata, &s); + if (error) + return; + + if (s.id_ground) { + dev_dbg(ddata->dev, "id ground, USB host mode\n"); + error = cpcap_usb_set_usb_mode(ddata); + if (error) + goto out_err; + + error = musb_mailbox(MUSB_ID_GROUND); + if (error) + goto out_err; + + error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3, + CPCAP_BIT_VBUSSTBY_EN, + CPCAP_BIT_VBUSSTBY_EN); + if (error) + goto out_err; + + return; + } + + error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3, + CPCAP_BIT_VBUSSTBY_EN, 0); + if (error) + goto out_err; + + vbus = cpcap_usb_vbus_valid(ddata); + + if (vbus) { + /* Are we connected to a docking station with vbus? */ + if (s.id_ground) { + dev_dbg(ddata->dev, "connected to a dock\n"); + + /* No VBUS needed with docks */ + error = cpcap_usb_set_usb_mode(ddata); + if (error) + goto out_err; + error = musb_mailbox(MUSB_ID_GROUND); + if (error) + goto out_err; + + return; + } + + /* Otherwise assume we're connected to a USB host */ + dev_dbg(ddata->dev, "connected to USB host\n"); + error = cpcap_usb_set_usb_mode(ddata); + if (error) + goto out_err; + error = musb_mailbox(MUSB_VBUS_VALID); + if (error) + goto out_err; + + return; + } + + /* Default to debug UART mode */ + error = cpcap_usb_set_uart_mode(ddata); + if (error) + goto out_err; + + error = musb_mailbox(MUSB_VBUS_OFF); + if (error) + goto out_err; + + dev_dbg(ddata->dev, "set UART mode\n"); + + return; + +out_err: + dev_err(ddata->dev, "error setting cable state: %i\n", error); +} + +static irqreturn_t cpcap_phy_irq_thread(int irq, void *data) +{ + struct cpcap_phy_ddata *ddata = data; + + if (!atomic_read(&ddata->active)) + return IRQ_NONE; + + schedule_delayed_work(&ddata->detect_work, msecs_to_jiffies(1)); + + return IRQ_HANDLED; +} + +static int cpcap_usb_init_irq(struct platform_device *pdev, + struct cpcap_phy_ddata *ddata, + const char *name) +{ + int irq, error; + + irq = platform_get_irq_byname(pdev, name); + if (!irq) + return -ENODEV; + + error = devm_request_threaded_irq(ddata->dev, irq, NULL, + cpcap_phy_irq_thread, + IRQF_SHARED, + name, ddata); + if (error) { + dev_err(ddata->dev, "could not get irq %s: %i\n", + name, error); + + return error; + } + + return 0; +} + +static const char * const cpcap_phy_irqs[] = { + /* REG_INT_0 */ + "id_ground", "id_float", + + /* REG_INT1 */ + "se0conn", "vbusvld", "sessvld", "sessend", "se1", + + /* REG_INT_3 */ + "dm", "dp", +}; + +static int cpcap_usb_init_interrupts(struct platform_device *pdev, + struct cpcap_phy_ddata *ddata) +{ + int i, error; + + for (i = 0; i < ARRAY_SIZE(cpcap_phy_irqs); i++) { + error = cpcap_usb_init_irq(pdev, ddata, cpcap_phy_irqs[i]); + if (error) + return error; + } + + return 0; +} + +/* + * Optional pins and modes. At least Motorola mapphone devices + * are using two GPIOs and dynamic pinctrl to multiplex PHY pins + * to UART, ULPI or UTMI mode. + */ + +static int cpcap_usb_gpio_set_mode(struct cpcap_phy_ddata *ddata, + enum cpcap_gpio_mode mode) +{ + if (!ddata->gpio[0] || !ddata->gpio[1]) + return 0; + + gpiod_set_value(ddata->gpio[0], mode & 1); + gpiod_set_value(ddata->gpio[1], mode >> 1); + + return 0; +} + +static int cpcap_usb_set_uart_mode(struct cpcap_phy_ddata *ddata) +{ + int error; + + error = cpcap_usb_gpio_set_mode(ddata, CPCAP_DM_DP); + if (error) + goto out_err; + + if (ddata->pins_uart) { + error = pinctrl_select_state(ddata->pins, ddata->pins_uart); + if (error) + goto out_err; + } + + error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC1, + CPCAP_BIT_VBUSPD, + CPCAP_BIT_VBUSPD); + if (error) + goto out_err; + + error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC2, + 0xffff, CPCAP_BIT_UARTMUX0 | + CPCAP_BIT_EMUMODE0); + if (error) + goto out_err; + + error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3, 0x7fff, + CPCAP_BIT_IDPU_SPI); + if (error) + goto out_err; + + return 0; + +out_err: + dev_err(ddata->dev, "%s failed with %i\n", __func__, error); + + return error; +} + +static int cpcap_usb_set_usb_mode(struct cpcap_phy_ddata *ddata) +{ + int error; + + error = cpcap_usb_gpio_set_mode(ddata, CPCAP_OTG_DM_DP); + if (error) + return error; + + if (ddata->pins_utmi) { + error = pinctrl_select_state(ddata->pins, ddata->pins_utmi); + if (error) { + dev_err(ddata->dev, "could not set usb mode: %i\n", + error); + + return error; + } + } + + error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC1, + CPCAP_BIT_VBUSPD, 0); + if (error) + goto out_err; + + error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC2, + CPCAP_BIT_USBXCVREN, + CPCAP_BIT_USBXCVREN); + if (error) + goto out_err; + + error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3, + CPCAP_BIT_PU_SPI | + CPCAP_BIT_DMPD_SPI | + CPCAP_BIT_DPPD_SPI | + CPCAP_BIT_SUSPEND_SPI | + CPCAP_BIT_ULPI_SPI_SEL, 0); + if (error) + goto out_err; + + error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC2, + CPCAP_BIT_USBXCVREN, + CPCAP_BIT_USBXCVREN); + if (error) + goto out_err; + + return 0; + +out_err: + dev_err(ddata->dev, "%s failed with %i\n", __func__, error); + + return error; +} + +static int cpcap_usb_init_optional_pins(struct cpcap_phy_ddata *ddata) +{ + ddata->pins = devm_pinctrl_get(ddata->dev); + if (IS_ERR(ddata->pins)) { + dev_info(ddata->dev, "default pins not configured: %ld\n", + PTR_ERR(ddata->pins)); + ddata->pins = NULL; + + return 0; + } + + ddata->pins_ulpi = pinctrl_lookup_state(ddata->pins, "ulpi"); + if (IS_ERR(ddata->pins_ulpi)) { + dev_info(ddata->dev, "ulpi pins not configured\n"); + ddata->pins_ulpi = NULL; + } + + ddata->pins_utmi = pinctrl_lookup_state(ddata->pins, "utmi"); + if (IS_ERR(ddata->pins_utmi)) { + dev_info(ddata->dev, "utmi pins not configured\n"); + ddata->pins_utmi = NULL; + } + + ddata->pins_uart = pinctrl_lookup_state(ddata->pins, "uart"); + if (IS_ERR(ddata->pins_uart)) { + dev_info(ddata->dev, "uart pins not configured\n"); + ddata->pins_uart = NULL; + } + + if (ddata->pins_uart) + return pinctrl_select_state(ddata->pins, ddata->pins_uart); + + return 0; +} + +static void cpcap_usb_init_optional_gpios(struct cpcap_phy_ddata *ddata) +{ + int i; + + for (i = 0; i < 2; i++) { + ddata->gpio[i] = devm_gpiod_get_index(ddata->dev, "mode", + i, GPIOD_OUT_HIGH); + if (IS_ERR(ddata->gpio[i])) { + dev_info(ddata->dev, "no mode change GPIO%i: %li\n", + i, PTR_ERR(ddata->gpio[i])); + ddata->gpio[i] = NULL; + } + } +} + +static int cpcap_usb_init_iio(struct cpcap_phy_ddata *ddata) +{ + enum iio_chan_type type; + int error; + + ddata->vbus = devm_iio_channel_get(ddata->dev, "vbus"); + if (IS_ERR(ddata->vbus)) { + error = PTR_ERR(ddata->vbus); + goto out_err; + } + + if (!ddata->vbus->indio_dev) { + error = -ENXIO; + goto out_err; + } + + error = iio_get_channel_type(ddata->vbus, &type); + if (error < 0) + goto out_err; + + if (type != IIO_VOLTAGE) { + error = -EINVAL; + goto out_err; + } + + return 0; + +out_err: + dev_err(ddata->dev, "could not initialize VBUS or ID IIO: %i\n", + error); + + return error; +} + +#ifdef CONFIG_OF +static const struct of_device_id cpcap_usb_phy_id_table[] = { + { + .compatible = "motorola,cpcap-usb-phy", + }, + { + .compatible = "motorola,mapphone-cpcap-usb-phy", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, cpcap_usb_phy_id_table); +#endif + +static int cpcap_usb_phy_probe(struct platform_device *pdev) +{ + struct cpcap_phy_ddata *ddata; + struct phy *generic_phy; + struct phy_provider *phy_provider; + struct usb_otg *otg; + const struct of_device_id *of_id; + int error; + + of_id = of_match_device(of_match_ptr(cpcap_usb_phy_id_table), + &pdev->dev); + if (!of_id) + return -EINVAL; + + ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + ddata->reg = dev_get_regmap(pdev->dev.parent, NULL); + if (!ddata->reg) + return -ENODEV; + + otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL); + if (!otg) + return -ENOMEM; + + ddata->dev = &pdev->dev; + ddata->phy.dev = ddata->dev; + ddata->phy.label = "cpcap_usb_phy"; + ddata->phy.otg = otg; + ddata->phy.type = USB_PHY_TYPE_USB2; + otg->set_host = cpcap_usb_phy_set_host; + otg->set_peripheral = cpcap_usb_phy_set_peripheral; + otg->usb_phy = &ddata->phy; + INIT_DELAYED_WORK(&ddata->detect_work, cpcap_usb_detect); + platform_set_drvdata(pdev, ddata); + + ddata->vusb = devm_regulator_get(&pdev->dev, "vusb"); + if (IS_ERR(ddata->vusb)) + return PTR_ERR(ddata->vusb); + + error = regulator_enable(ddata->vusb); + if (error) + return error; + + generic_phy = devm_phy_create(ddata->dev, NULL, &ops); + if (IS_ERR(generic_phy)) { + error = PTR_ERR(generic_phy); + return PTR_ERR(generic_phy); + } + + phy_set_drvdata(generic_phy, ddata); + + phy_provider = devm_of_phy_provider_register(ddata->dev, + of_phy_simple_xlate); + if (IS_ERR(phy_provider)) + return PTR_ERR(phy_provider); + + error = cpcap_usb_init_optional_pins(ddata); + if (error) + return error; + + cpcap_usb_init_optional_gpios(ddata); + + error = cpcap_usb_init_iio(ddata); + if (error) + return error; + + error = cpcap_usb_init_interrupts(pdev, ddata); + if (error) + return error; + + usb_add_phy_dev(&ddata->phy); + atomic_set(&ddata->active, 1); + schedule_delayed_work(&ddata->detect_work, msecs_to_jiffies(1)); + + return 0; +} + +static int cpcap_usb_phy_remove(struct platform_device *pdev) +{ + struct cpcap_phy_ddata *ddata = platform_get_drvdata(pdev); + int error; + + atomic_set(&ddata->active, 0); + error = cpcap_usb_set_uart_mode(ddata); + if (error) + dev_err(ddata->dev, "could not set UART mode\n"); + + error = musb_mailbox(MUSB_VBUS_OFF); + if (error) + dev_err(ddata->dev, "could not set mailbox\n"); + + usb_remove_phy(&ddata->phy); + cancel_delayed_work_sync(&ddata->detect_work); + clk_unprepare(ddata->refclk); + regulator_disable(ddata->vusb); + + return 0; +} + +static struct platform_driver cpcap_usb_phy_driver = { + .probe = cpcap_usb_phy_probe, + .remove = cpcap_usb_phy_remove, + .driver = { + .name = "cpcap-usb-phy", + .of_match_table = of_match_ptr(cpcap_usb_phy_id_table), + }, +}; + +module_platform_driver(cpcap_usb_phy_driver); + +MODULE_ALIAS("platform:cpcap_usb"); +MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>"); +MODULE_DESCRIPTION("CPCAP usb phy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig new file mode 100644 index 000000000000..7bfa64baf837 --- /dev/null +++ b/drivers/phy/qualcomm/Kconfig @@ -0,0 +1,58 @@ +# +# Phy drivers for Qualcomm platforms +# +config PHY_QCOM_APQ8064_SATA + tristate "Qualcomm APQ8064 SATA SerDes/PHY driver" + depends on ARCH_QCOM + depends on HAS_IOMEM + depends on OF + select GENERIC_PHY + +config PHY_QCOM_IPQ806X_SATA + tristate "Qualcomm IPQ806x SATA SerDes/PHY driver" + depends on ARCH_QCOM + depends on HAS_IOMEM + depends on OF + select GENERIC_PHY + +config PHY_QCOM_QMP + tristate "Qualcomm QMP PHY Driver" + depends on OF && COMMON_CLK && (ARCH_QCOM || COMPILE_TEST) + select GENERIC_PHY + help + Enable this to support the QMP PHY transceiver that is used + with controllers such as PCIe, UFS, and USB on Qualcomm chips. + +config PHY_QCOM_QUSB2 + tristate "Qualcomm QUSB2 PHY Driver" + depends on OF && (ARCH_QCOM || COMPILE_TEST) + depends on NVMEM || !NVMEM + select GENERIC_PHY + help + Enable this to support the HighSpeed QUSB2 PHY transceiver for USB + controllers on Qualcomm chips. This driver supports the high-speed + PHY which is usually paired with either the ChipIdea or Synopsys DWC3 + USB IPs on MSM SOCs. + +config PHY_QCOM_UFS + tristate "Qualcomm UFS PHY driver" + depends on OF && ARCH_QCOM + select GENERIC_PHY + help + Support for UFS PHY on QCOM chipsets. + +config PHY_QCOM_USB_HS + tristate "Qualcomm USB HS PHY module" + depends on USB_ULPI_BUS + depends on EXTCON || !EXTCON # if EXTCON=m, this cannot be built-in + select GENERIC_PHY + help + Support for the USB high-speed ULPI compliant phy on Qualcomm + chipsets. + +config PHY_QCOM_USB_HSIC + tristate "Qualcomm USB HSIC ULPI PHY module" + depends on USB_ULPI_BUS + select GENERIC_PHY + help + Support for the USB HSIC ULPI compliant PHY on QCOM chipsets. diff --git a/drivers/phy/qualcomm/Makefile b/drivers/phy/qualcomm/Makefile new file mode 100644 index 000000000000..2e183d7695fd --- /dev/null +++ b/drivers/phy/qualcomm/Makefile @@ -0,0 +1,9 @@ +obj-$(CONFIG_PHY_QCOM_APQ8064_SATA) += phy-qcom-apq8064-sata.o +obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o +obj-$(CONFIG_PHY_QCOM_QMP) += phy-qcom-qmp.o +obj-$(CONFIG_PHY_QCOM_QUSB2) += phy-qcom-qusb2.o +obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs.o +obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-14nm.o +obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-20nm.o +obj-$(CONFIG_PHY_QCOM_USB_HS) += phy-qcom-usb-hs.o +obj-$(CONFIG_PHY_QCOM_USB_HSIC) += phy-qcom-usb-hsic.o diff --git a/drivers/phy/phy-qcom-apq8064-sata.c b/drivers/phy/qualcomm/phy-qcom-apq8064-sata.c index 69ce2afac015..69ce2afac015 100644 --- a/drivers/phy/phy-qcom-apq8064-sata.c +++ b/drivers/phy/qualcomm/phy-qcom-apq8064-sata.c diff --git a/drivers/phy/phy-qcom-ipq806x-sata.c b/drivers/phy/qualcomm/phy-qcom-ipq806x-sata.c index 0ad127cc9298..0ad127cc9298 100644 --- a/drivers/phy/phy-qcom-ipq806x-sata.c +++ b/drivers/phy/qualcomm/phy-qcom-ipq806x-sata.c diff --git a/drivers/phy/phy-qcom-qmp.c b/drivers/phy/qualcomm/phy-qcom-qmp.c index 78ca62897784..78ca62897784 100644 --- a/drivers/phy/phy-qcom-qmp.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp.c diff --git a/drivers/phy/phy-qcom-qusb2.c b/drivers/phy/qualcomm/phy-qcom-qusb2.c index 6c575244c0fb..6c575244c0fb 100644 --- a/drivers/phy/phy-qcom-qusb2.c +++ b/drivers/phy/qualcomm/phy-qcom-qusb2.c diff --git a/drivers/phy/phy-qcom-ufs-i.h b/drivers/phy/qualcomm/phy-qcom-ufs-i.h index 13b02b7de30b..13b02b7de30b 100644 --- a/drivers/phy/phy-qcom-ufs-i.h +++ b/drivers/phy/qualcomm/phy-qcom-ufs-i.h diff --git a/drivers/phy/phy-qcom-ufs-qmp-14nm.c b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c index 12a1b498dc4b..12a1b498dc4b 100644 --- a/drivers/phy/phy-qcom-ufs-qmp-14nm.c +++ b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c diff --git a/drivers/phy/phy-qcom-ufs-qmp-14nm.h b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.h index 3aefdbacbcd0..3aefdbacbcd0 100644 --- a/drivers/phy/phy-qcom-ufs-qmp-14nm.h +++ b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.h diff --git a/drivers/phy/phy-qcom-ufs-qmp-20nm.c b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c index 4f68acb58b73..4f68acb58b73 100644 --- a/drivers/phy/phy-qcom-ufs-qmp-20nm.c +++ b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c diff --git a/drivers/phy/phy-qcom-ufs-qmp-20nm.h b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.h index 4f3076bb3d71..4f3076bb3d71 100644 --- a/drivers/phy/phy-qcom-ufs-qmp-20nm.h +++ b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.h diff --git a/drivers/phy/phy-qcom-ufs.c b/drivers/phy/qualcomm/phy-qcom-ufs.c index 43865ef340e2..43865ef340e2 100644 --- a/drivers/phy/phy-qcom-ufs.c +++ b/drivers/phy/qualcomm/phy-qcom-ufs.c diff --git a/drivers/phy/phy-qcom-usb-hs.c b/drivers/phy/qualcomm/phy-qcom-usb-hs.c index 94dfbfd739c3..4b20abc3ae2f 100644 --- a/drivers/phy/phy-qcom-usb-hs.c +++ b/drivers/phy/qualcomm/phy-qcom-usb-hs.c @@ -11,12 +11,11 @@ #include <linux/clk.h> #include <linux/regulator/consumer.h> #include <linux/of_device.h> +#include <linux/phy/phy.h> #include <linux/reset.h> #include <linux/extcon.h> #include <linux/notifier.h> -#include "ulpi_phy.h" - #define ULPI_PWR_CLK_MNG_REG 0x88 # define ULPI_PWR_OTG_COMP_DISABLE BIT(0) diff --git a/drivers/phy/phy-qcom-usb-hsic.c b/drivers/phy/qualcomm/phy-qcom-usb-hsic.c index 47690f9945b9..c110563a73cb 100644 --- a/drivers/phy/phy-qcom-usb-hsic.c +++ b/drivers/phy/qualcomm/phy-qcom-usb-hsic.c @@ -8,13 +8,12 @@ #include <linux/module.h> #include <linux/ulpi/driver.h> #include <linux/ulpi/regs.h> +#include <linux/phy/phy.h> #include <linux/pinctrl/consumer.h> #include <linux/pinctrl/pinctrl-state.h> #include <linux/delay.h> #include <linux/clk.h> -#include "ulpi_phy.h" - #define ULPI_HSIC_CFG 0x30 #define ULPI_HSIC_IO_CAL 0x33 diff --git a/drivers/phy/renesas/Kconfig b/drivers/phy/renesas/Kconfig new file mode 100644 index 000000000000..cb09245e9b4c --- /dev/null +++ b/drivers/phy/renesas/Kconfig @@ -0,0 +1,24 @@ +# +# Phy drivers for Renesas platforms +# +config PHY_RCAR_GEN2 + tristate "Renesas R-Car generation 2 USB PHY driver" + depends on ARCH_RENESAS + depends on GENERIC_PHY + 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 ARCH_RENESAS + depends on EXTCON + select GENERIC_PHY + help + Support for USB 2.0 PHY found on Renesas R-Car generation 3 SoCs. + +config PHY_RCAR_GEN3_USB3 + tristate "Renesas R-Car generation 3 USB 3.0 PHY driver" + depends on ARCH_RENESAS || COMPILE_TEST + select GENERIC_PHY + help + Support for USB 3.0 PHY found on Renesas R-Car generation 3 SoCs. diff --git a/drivers/phy/renesas/Makefile b/drivers/phy/renesas/Makefile new file mode 100644 index 000000000000..8b6025916a93 --- /dev/null +++ b/drivers/phy/renesas/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_PHY_RCAR_GEN2) += phy-rcar-gen2.o +obj-$(CONFIG_PHY_RCAR_GEN3_USB2) += phy-rcar-gen3-usb2.o +obj-$(CONFIG_PHY_RCAR_GEN3_USB3) += phy-rcar-gen3-usb3.o diff --git a/drivers/phy/phy-rcar-gen2.c b/drivers/phy/renesas/phy-rcar-gen2.c index 97d4dd6ea924..97d4dd6ea924 100644 --- a/drivers/phy/phy-rcar-gen2.c +++ b/drivers/phy/renesas/phy-rcar-gen2.c diff --git a/drivers/phy/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c index 54c34298a000..54c34298a000 100644 --- a/drivers/phy/phy-rcar-gen3-usb2.c +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb3.c b/drivers/phy/renesas/phy-rcar-gen3-usb3.c new file mode 100644 index 000000000000..88c83c9b8ff9 --- /dev/null +++ b/drivers/phy/renesas/phy-rcar-gen3-usb3.c @@ -0,0 +1,226 @@ +/* + * Renesas R-Car Gen3 for USB3.0 PHY driver + * + * Copyright (C) 2017 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. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> + +#define USB30_CLKSET0 0x034 +#define USB30_CLKSET1 0x036 +#define USB30_SSC_SET 0x038 +#define USB30_PHY_ENABLE 0x060 +#define USB30_VBUS_EN 0x064 + +/* USB30_CLKSET0 */ +#define CLKSET0_PRIVATE 0x05c0 +#define CLKSET0_USB30_FSEL_USB_EXTAL 0x0002 + +/* USB30_CLKSET1 */ +#define CLKSET1_USB30_PLL_MULTI_SHIFT 6 +#define CLKSET1_USB30_PLL_MULTI_USB_EXTAL (0x64 << \ + CLKSET1_USB30_PLL_MULTI_SHIFT) +#define CLKSET1_PHYRESET BIT(4) /* 1: reset */ +#define CLKSET1_REF_CLKDIV BIT(3) /* 1: USB_EXTAL */ +#define CLKSET1_PRIVATE_2_1 BIT(1) /* Write B'01 */ +#define CLKSET1_REF_CLK_SEL BIT(0) /* 1: USB3S0_CLK_P */ + +/* USB30_SSC_SET */ +#define SSC_SET_SSC_EN BIT(12) +#define SSC_SET_RANGE_SHIFT 9 +#define SSC_SET_RANGE_4980 (0x0 << SSC_SET_RANGE_SHIFT) +#define SSC_SET_RANGE_4492 (0x1 << SSC_SET_RANGE_SHIFT) +#define SSC_SET_RANGE_4003 (0x2 << SSC_SET_RANGE_SHIFT) + +/* USB30_PHY_ENABLE */ +#define PHY_ENABLE_RESET_EN BIT(4) + +/* USB30_VBUS_EN */ +#define VBUS_EN_VBUS_EN BIT(1) + +struct rcar_gen3_usb3 { + void __iomem *base; + struct phy *phy; + u32 ssc_range; + bool usb3s_clk; + bool usb_extal; +}; + +static void write_clkset1_for_usb_extal(struct rcar_gen3_usb3 *r, bool reset) +{ + u16 val = CLKSET1_USB30_PLL_MULTI_USB_EXTAL | + CLKSET1_REF_CLKDIV | CLKSET1_PRIVATE_2_1; + + if (reset) + val |= CLKSET1_PHYRESET; + + writew(val, r->base + USB30_CLKSET1); +} + +static void rcar_gen3_phy_usb3_enable_ssc(struct rcar_gen3_usb3 *r) +{ + u16 val = SSC_SET_SSC_EN; + + switch (r->ssc_range) { + case 4980: + val |= SSC_SET_RANGE_4980; + break; + case 4492: + val |= SSC_SET_RANGE_4492; + break; + case 4003: + val |= SSC_SET_RANGE_4003; + break; + default: + dev_err(&r->phy->dev, "%s: unsupported range (%x)\n", __func__, + r->ssc_range); + return; + } + + writew(val, r->base + USB30_SSC_SET); +} + +static void rcar_gen3_phy_usb3_select_usb_extal(struct rcar_gen3_usb3 *r) +{ + write_clkset1_for_usb_extal(r, false); + if (r->ssc_range) + rcar_gen3_phy_usb3_enable_ssc(r); + writew(CLKSET0_PRIVATE | CLKSET0_USB30_FSEL_USB_EXTAL, + r->base + USB30_CLKSET0); + writew(PHY_ENABLE_RESET_EN, r->base + USB30_PHY_ENABLE); + write_clkset1_for_usb_extal(r, true); + usleep_range(10, 20); + write_clkset1_for_usb_extal(r, false); +} + +static int rcar_gen3_phy_usb3_init(struct phy *p) +{ + struct rcar_gen3_usb3 *r = phy_get_drvdata(p); + + dev_vdbg(&r->phy->dev, "%s: enter (%d, %d, %d)\n", __func__, + r->usb3s_clk, r->usb_extal, r->ssc_range); + + if (!r->usb3s_clk && r->usb_extal) + rcar_gen3_phy_usb3_select_usb_extal(r); + + /* Enables VBUS detection anyway */ + writew(VBUS_EN_VBUS_EN, r->base + USB30_VBUS_EN); + + return 0; +} + +static const struct phy_ops rcar_gen3_phy_usb3_ops = { + .init = rcar_gen3_phy_usb3_init, + .owner = THIS_MODULE, +}; + +static const struct of_device_id rcar_gen3_phy_usb3_match_table[] = { + { .compatible = "renesas,rcar-gen3-usb3-phy" }, + { } +}; +MODULE_DEVICE_TABLE(of, rcar_gen3_phy_usb3_match_table); + +static int rcar_gen3_phy_usb3_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rcar_gen3_usb3 *r; + struct phy_provider *provider; + struct resource *res; + int ret = 0; + struct clk *clk; + + if (!dev->of_node) { + dev_err(dev, "This driver needs device tree\n"); + return -EINVAL; + } + + r = devm_kzalloc(dev, sizeof(*r), GFP_KERNEL); + if (!r) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + r->base = devm_ioremap_resource(dev, res); + if (IS_ERR(r->base)) + return PTR_ERR(r->base); + + clk = devm_clk_get(dev, "usb3s_clk"); + if (!IS_ERR(clk) && !clk_prepare_enable(clk)) { + r->usb3s_clk = !!clk_get_rate(clk); + clk_disable_unprepare(clk); + } + clk = devm_clk_get(dev, "usb_extal"); + if (!IS_ERR(clk) && !clk_prepare_enable(clk)) { + r->usb_extal = !!clk_get_rate(clk); + clk_disable_unprepare(clk); + } + + if (!r->usb3s_clk && !r->usb_extal) { + dev_err(dev, "This driver needs usb3s_clk and/or usb_extal\n"); + ret = -EINVAL; + goto error; + } + + /* + * devm_phy_create() will call pm_runtime_enable(&phy->dev); + * And then, phy-core will manage runtime pm for this device. + */ + pm_runtime_enable(dev); + + r->phy = devm_phy_create(dev, NULL, &rcar_gen3_phy_usb3_ops); + if (IS_ERR(r->phy)) { + dev_err(dev, "Failed to create USB3 PHY\n"); + ret = PTR_ERR(r->phy); + goto error; + } + + of_property_read_u32(dev->of_node, "renesas,ssc-range", &r->ssc_range); + + platform_set_drvdata(pdev, r); + phy_set_drvdata(r->phy, r); + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(provider)) { + dev_err(dev, "Failed to register PHY provider\n"); + ret = PTR_ERR(provider); + goto error; + } + + return 0; + +error: + pm_runtime_disable(dev); + + return ret; +} + +static int rcar_gen3_phy_usb3_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +}; + +static struct platform_driver rcar_gen3_phy_usb3_driver = { + .driver = { + .name = "phy_rcar_gen3_usb3", + .of_match_table = rcar_gen3_phy_usb3_match_table, + }, + .probe = rcar_gen3_phy_usb3_probe, + .remove = rcar_gen3_phy_usb3_remove, +}; +module_platform_driver(rcar_gen3_phy_usb3_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Renesas R-Car Gen3 USB 3.0 PHY"); +MODULE_AUTHOR("Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>"); diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig new file mode 100644 index 000000000000..f5325b2b679e --- /dev/null +++ b/drivers/phy/rockchip/Kconfig @@ -0,0 +1,51 @@ +# +# Phy drivers for Rockchip platforms +# +config PHY_ROCKCHIP_DP + tristate "Rockchip Display Port PHY Driver" + depends on ARCH_ROCKCHIP && OF + select GENERIC_PHY + help + Enable this to support the Rockchip Display Port PHY. + +config PHY_ROCKCHIP_EMMC + tristate "Rockchip EMMC PHY Driver" + depends on ARCH_ROCKCHIP && OF + select GENERIC_PHY + help + Enable this to support the Rockchip EMMC PHY. + +config PHY_ROCKCHIP_INNO_USB2 + tristate "Rockchip INNO USB2PHY Driver" + depends on (ARCH_ROCKCHIP || COMPILE_TEST) && OF + depends on COMMON_CLK + depends on EXTCON + depends on USB_SUPPORT + select GENERIC_PHY + select USB_COMMON + help + Support for Rockchip USB2.0 PHY with Innosilicon IP block. + +config PHY_ROCKCHIP_PCIE + tristate "Rockchip PCIe PHY Driver" + depends on (ARCH_ROCKCHIP && OF) || COMPILE_TEST + select GENERIC_PHY + select MFD_SYSCON + help + Enable this to support the Rockchip PCIe PHY. + +config PHY_ROCKCHIP_TYPEC + tristate "Rockchip TYPEC PHY Driver" + depends on OF && (ARCH_ROCKCHIP || COMPILE_TEST) + select EXTCON + select GENERIC_PHY + select RESET_CONTROLLER + help + Enable this to support the Rockchip USB TYPEC PHY. + +config PHY_ROCKCHIP_USB + tristate "Rockchip USB2 PHY Driver" + depends on ARCH_ROCKCHIP && OF + select GENERIC_PHY + help + Enable this to support the Rockchip USB 2.0 PHY. diff --git a/drivers/phy/rockchip/Makefile b/drivers/phy/rockchip/Makefile new file mode 100644 index 000000000000..bd0acdf38e0f --- /dev/null +++ b/drivers/phy/rockchip/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_PHY_ROCKCHIP_DP) += phy-rockchip-dp.o +obj-$(CONFIG_PHY_ROCKCHIP_EMMC) += phy-rockchip-emmc.o +obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB2) += phy-rockchip-inno-usb2.o +obj-$(CONFIG_PHY_ROCKCHIP_PCIE) += phy-rockchip-pcie.o +obj-$(CONFIG_PHY_ROCKCHIP_TYPEC) += phy-rockchip-typec.o +obj-$(CONFIG_PHY_ROCKCHIP_USB) += phy-rockchip-usb.o diff --git a/drivers/phy/phy-rockchip-dp.c b/drivers/phy/rockchip/phy-rockchip-dp.c index 8b267a746576..8b267a746576 100644 --- a/drivers/phy/phy-rockchip-dp.c +++ b/drivers/phy/rockchip/phy-rockchip-dp.c diff --git a/drivers/phy/phy-rockchip-emmc.c b/drivers/phy/rockchip/phy-rockchip-emmc.c index f1b24f18e9b2..f1b24f18e9b2 100644 --- a/drivers/phy/phy-rockchip-emmc.c +++ b/drivers/phy/rockchip/phy-rockchip-emmc.c diff --git a/drivers/phy/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c index 8efe78a49916..626883d9d176 100644 --- a/drivers/phy/phy-rockchip-inno-usb2.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c @@ -406,7 +406,8 @@ static int rockchip_usb2phy_init(struct phy *phy) mutex_lock(&rport->mutex); if (rport->port_id == USB2PHY_PORT_OTG) { - if (rport->mode != USB_DR_MODE_HOST) { + if (rport->mode != USB_DR_MODE_HOST && + rport->mode != USB_DR_MODE_UNKNOWN) { /* clear bvalid status and enable bvalid detect irq */ ret = property_enable(rphy, &rport->port_cfg->bvalid_det_clr, @@ -421,7 +422,7 @@ static int rockchip_usb2phy_init(struct phy *phy) goto out; schedule_delayed_work(&rport->otg_sm_work, - OTG_SCHEDULE_DELAY); + OTG_SCHEDULE_DELAY * 3); } else { /* If OTG works in host only mode, do nothing. */ dev_dbg(&rport->phy->dev, "mode %d\n", rport->mode); @@ -463,6 +464,9 @@ static int rockchip_usb2phy_power_on(struct phy *phy) if (ret) return ret; + /* waiting for the utmi_clk to become stable */ + usleep_range(1500, 2000); + rport->suspended = false; return 0; } @@ -493,7 +497,8 @@ static int rockchip_usb2phy_exit(struct phy *phy) struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); if (rport->port_id == USB2PHY_PORT_OTG && - rport->mode != USB_DR_MODE_HOST) { + rport->mode != USB_DR_MODE_HOST && + rport->mode != USB_DR_MODE_UNKNOWN) { cancel_delayed_work_sync(&rport->otg_sm_work); cancel_delayed_work_sync(&rport->chg_work); } else if (rport->port_id == USB2PHY_PORT_HOST) @@ -970,7 +975,8 @@ static int rockchip_usb2phy_otg_port_init(struct rockchip_usb2phy *rphy, mutex_init(&rport->mutex); rport->mode = of_usb_get_dr_mode_by_phy(child_np, -1); - if (rport->mode == USB_DR_MODE_HOST) { + if (rport->mode == USB_DR_MODE_HOST || + rport->mode == USB_DR_MODE_UNKNOWN) { ret = 0; goto out; } @@ -1138,6 +1144,65 @@ disable_clks: return ret; } +static const struct rockchip_usb2phy_cfg rk3228_phy_cfgs[] = { + { + .reg = 0x760, + .num_ports = 2, + .clkout_ctl = { 0x0768, 4, 4, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_OTG] = { + .phy_sus = { 0x0760, 15, 0, 0, 0x1d1 }, + .bvalid_det_en = { 0x0680, 3, 3, 0, 1 }, + .bvalid_det_st = { 0x0690, 3, 3, 0, 1 }, + .bvalid_det_clr = { 0x06a0, 3, 3, 0, 1 }, + .ls_det_en = { 0x0680, 2, 2, 0, 1 }, + .ls_det_st = { 0x0690, 2, 2, 0, 1 }, + .ls_det_clr = { 0x06a0, 2, 2, 0, 1 }, + .utmi_bvalid = { 0x0480, 4, 4, 0, 1 }, + .utmi_ls = { 0x0480, 3, 2, 0, 1 }, + }, + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0x0764, 15, 0, 0, 0x1d1 }, + .ls_det_en = { 0x0680, 4, 4, 0, 1 }, + .ls_det_st = { 0x0690, 4, 4, 0, 1 }, + .ls_det_clr = { 0x06a0, 4, 4, 0, 1 } + } + }, + .chg_det = { + .opmode = { 0x0760, 3, 0, 5, 1 }, + .cp_det = { 0x0884, 4, 4, 0, 1 }, + .dcp_det = { 0x0884, 3, 3, 0, 1 }, + .dp_det = { 0x0884, 5, 5, 0, 1 }, + .idm_sink_en = { 0x0768, 8, 8, 0, 1 }, + .idp_sink_en = { 0x0768, 7, 7, 0, 1 }, + .idp_src_en = { 0x0768, 9, 9, 0, 1 }, + .rdm_pdwn_en = { 0x0768, 10, 10, 0, 1 }, + .vdm_src_en = { 0x0768, 12, 12, 0, 1 }, + .vdp_src_en = { 0x0768, 11, 11, 0, 1 }, + }, + }, + { + .reg = 0x800, + .num_ports = 2, + .clkout_ctl = { 0x0808, 4, 4, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_OTG] = { + .phy_sus = { 0x800, 15, 0, 0, 0x1d1 }, + .ls_det_en = { 0x0684, 0, 0, 0, 1 }, + .ls_det_st = { 0x0694, 0, 0, 0, 1 }, + .ls_det_clr = { 0x06a4, 0, 0, 0, 1 } + }, + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0x804, 15, 0, 0, 0x1d1 }, + .ls_det_en = { 0x0684, 1, 1, 0, 1 }, + .ls_det_st = { 0x0694, 1, 1, 0, 1 }, + .ls_det_clr = { 0x06a4, 1, 1, 0, 1 } + } + }, + }, + { /* sentinel */ } +}; + static const struct rockchip_usb2phy_cfg rk3328_phy_cfgs[] = { { .reg = 0x100, @@ -1263,6 +1328,7 @@ static const struct rockchip_usb2phy_cfg rk3399_phy_cfgs[] = { }; static const struct of_device_id rockchip_usb2phy_dt_match[] = { + { .compatible = "rockchip,rk3228-usb2phy", .data = &rk3228_phy_cfgs }, { .compatible = "rockchip,rk3328-usb2phy", .data = &rk3328_phy_cfgs }, { .compatible = "rockchip,rk3366-usb2phy", .data = &rk3366_phy_cfgs }, { .compatible = "rockchip,rk3399-usb2phy", .data = &rk3399_phy_cfgs }, diff --git a/drivers/phy/phy-rockchip-pcie.c b/drivers/phy/rockchip/phy-rockchip-pcie.c index 6904633cad68..6904633cad68 100644 --- a/drivers/phy/phy-rockchip-pcie.c +++ b/drivers/phy/rockchip/phy-rockchip-pcie.c diff --git a/drivers/phy/phy-rockchip-typec.c b/drivers/phy/rockchip/phy-rockchip-typec.c index 7cfb0f8995de..7cfb0f8995de 100644 --- a/drivers/phy/phy-rockchip-typec.c +++ b/drivers/phy/rockchip/phy-rockchip-typec.c diff --git a/drivers/phy/phy-rockchip-usb.c b/drivers/phy/rockchip/phy-rockchip-usb.c index 3378eeb7a562..3378eeb7a562 100644 --- a/drivers/phy/phy-rockchip-usb.c +++ b/drivers/phy/rockchip/phy-rockchip-usb.c diff --git a/drivers/phy/samsung/Kconfig b/drivers/phy/samsung/Kconfig new file mode 100644 index 000000000000..b7e0645a7bd9 --- /dev/null +++ b/drivers/phy/samsung/Kconfig @@ -0,0 +1,95 @@ +# +# Phy drivers for Samsung platforms +# +config PHY_EXYNOS_DP_VIDEO + tristate "EXYNOS SoC series Display Port PHY driver" + depends on OF + depends on ARCH_EXYNOS || COMPILE_TEST + default ARCH_EXYNOS + select GENERIC_PHY + help + Support for Display Port PHY found on Samsung EXYNOS SoCs. + +config PHY_EXYNOS_MIPI_VIDEO + tristate "S5P/EXYNOS SoC series MIPI CSI-2/DSI PHY driver" + depends on HAS_IOMEM + depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST + select GENERIC_PHY + default y if ARCH_S5PV210 || ARCH_EXYNOS + help + Support for MIPI CSI-2 and MIPI DSI DPHY found on Samsung S5P + and EXYNOS SoCs. + +config PHY_EXYNOS_PCIE + bool "Exynos PCIe PHY driver" + depends on OF && (ARCH_EXYNOS || COMPILE_TEST) + select GENERIC_PHY + help + Enable PCIe PHY support for Exynos SoC series. + This driver provides PHY interface for Exynos PCIe controller. + +config PHY_SAMSUNG_USB2 + tristate "Samsung USB 2.0 PHY driver" + depends on HAS_IOMEM + depends on USB_EHCI_EXYNOS || USB_OHCI_EXYNOS || USB_DWC2 + select GENERIC_PHY + select MFD_SYSCON + default ARCH_EXYNOS + help + Enable this to support the Samsung USB 2.0 PHY driver for Samsung + SoCs. This driver provides the interface for USB 2.0 PHY. Support + for particular PHYs will be enabled based on the SoC type in addition + to this driver. + +config PHY_EXYNOS4210_USB2 + bool + depends on PHY_SAMSUNG_USB2 + default CPU_EXYNOS4210 + +config PHY_EXYNOS4X12_USB2 + bool + depends on PHY_SAMSUNG_USB2 + default SOC_EXYNOS3250 || SOC_EXYNOS4212 || SOC_EXYNOS4412 + +config PHY_EXYNOS5250_USB2 + bool + depends on PHY_SAMSUNG_USB2 + default SOC_EXYNOS5250 || SOC_EXYNOS5420 + +config PHY_S5PV210_USB2 + bool "Support for S5PV210" + depends on PHY_SAMSUNG_USB2 + depends on ARCH_S5PV210 + help + Enable USB PHY support for S5PV210. This option requires that Samsung + USB 2.0 PHY driver is enabled and means that support for this + particular SoC is compiled in the driver. In case of S5PV210 two phys + are available - device and host. + +config PHY_EXYNOS5_USBDRD + tristate "Exynos5 SoC series USB DRD PHY driver" + depends on ARCH_EXYNOS && OF + depends on HAS_IOMEM + depends on USB_DWC3_EXYNOS + select GENERIC_PHY + select MFD_SYSCON + default y + help + Enable USB DRD PHY support for Exynos 5 SoC series. + This driver provides PHY interface for USB 3.0 DRD controller + present on Exynos5 SoC series. + +config PHY_EXYNOS5250_SATA + tristate "Exynos5250 Sata SerDes/PHY driver" + depends on SOC_EXYNOS5250 + depends on HAS_IOMEM + depends on OF + select GENERIC_PHY + select I2C + select I2C_S3C2410 + select MFD_SYSCON + help + Enable this to support SATA SerDes/Phy found on Samsung's + Exynos5250 based SoCs.This SerDes/Phy supports SATA 1.5 Gb/s, + SATA 3.0 Gb/s, SATA 6.0 Gb/s speeds. It supports one SATA host + port to accept one SATA device. diff --git a/drivers/phy/samsung/Makefile b/drivers/phy/samsung/Makefile new file mode 100644 index 000000000000..20d7f2424772 --- /dev/null +++ b/drivers/phy/samsung/Makefile @@ -0,0 +1,11 @@ +obj-$(CONFIG_PHY_EXYNOS_DP_VIDEO) += phy-exynos-dp-video.o +obj-$(CONFIG_PHY_EXYNOS_MIPI_VIDEO) += phy-exynos-mipi-video.o +obj-$(CONFIG_PHY_EXYNOS_PCIE) += phy-exynos-pcie.o +obj-$(CONFIG_PHY_SAMSUNG_USB2) += phy-exynos-usb2.o +phy-exynos-usb2-y += phy-samsung-usb2.o +phy-exynos-usb2-$(CONFIG_PHY_EXYNOS4210_USB2) += phy-exynos4210-usb2.o +phy-exynos-usb2-$(CONFIG_PHY_EXYNOS4X12_USB2) += phy-exynos4x12-usb2.o +phy-exynos-usb2-$(CONFIG_PHY_EXYNOS5250_USB2) += phy-exynos5250-usb2.o +phy-exynos-usb2-$(CONFIG_PHY_S5PV210_USB2) += phy-s5pv210-usb2.o +obj-$(CONFIG_PHY_EXYNOS5_USBDRD) += phy-exynos5-usbdrd.o +obj-$(CONFIG_PHY_EXYNOS5250_SATA) += phy-exynos5250-sata.o diff --git a/drivers/phy/phy-exynos-dp-video.c b/drivers/phy/samsung/phy-exynos-dp-video.c index bb3279dbf88c..bb3279dbf88c 100644 --- a/drivers/phy/phy-exynos-dp-video.c +++ b/drivers/phy/samsung/phy-exynos-dp-video.c diff --git a/drivers/phy/phy-exynos-mipi-video.c b/drivers/phy/samsung/phy-exynos-mipi-video.c index c198886f80a3..c198886f80a3 100644 --- a/drivers/phy/phy-exynos-mipi-video.c +++ b/drivers/phy/samsung/phy-exynos-mipi-video.c diff --git a/drivers/phy/phy-exynos-pcie.c b/drivers/phy/samsung/phy-exynos-pcie.c index a89c12faff39..a89c12faff39 100644 --- a/drivers/phy/phy-exynos-pcie.c +++ b/drivers/phy/samsung/phy-exynos-pcie.c diff --git a/drivers/phy/phy-exynos4210-usb2.c b/drivers/phy/samsung/phy-exynos4210-usb2.c index 1f50e1004828..1f50e1004828 100644 --- a/drivers/phy/phy-exynos4210-usb2.c +++ b/drivers/phy/samsung/phy-exynos4210-usb2.c diff --git a/drivers/phy/phy-exynos4x12-usb2.c b/drivers/phy/samsung/phy-exynos4x12-usb2.c index 7f27a91acf87..7f27a91acf87 100644 --- a/drivers/phy/phy-exynos4x12-usb2.c +++ b/drivers/phy/samsung/phy-exynos4x12-usb2.c diff --git a/drivers/phy/phy-exynos5-usbdrd.c b/drivers/phy/samsung/phy-exynos5-usbdrd.c index 7c41daa2c625..7c41daa2c625 100644 --- a/drivers/phy/phy-exynos5-usbdrd.c +++ b/drivers/phy/samsung/phy-exynos5-usbdrd.c diff --git a/drivers/phy/phy-exynos5250-sata.c b/drivers/phy/samsung/phy-exynos5250-sata.c index 60e13afcd9b8..60e13afcd9b8 100644 --- a/drivers/phy/phy-exynos5250-sata.c +++ b/drivers/phy/samsung/phy-exynos5250-sata.c diff --git a/drivers/phy/phy-exynos5250-usb2.c b/drivers/phy/samsung/phy-exynos5250-usb2.c index aad806272305..aad806272305 100644 --- a/drivers/phy/phy-exynos5250-usb2.c +++ b/drivers/phy/samsung/phy-exynos5250-usb2.c diff --git a/drivers/phy/phy-s5pv210-usb2.c b/drivers/phy/samsung/phy-s5pv210-usb2.c index f6f72339bbc3..f6f72339bbc3 100644 --- a/drivers/phy/phy-s5pv210-usb2.c +++ b/drivers/phy/samsung/phy-s5pv210-usb2.c diff --git a/drivers/phy/phy-samsung-usb2.c b/drivers/phy/samsung/phy-samsung-usb2.c index 1d22d93b552d..1d22d93b552d 100644 --- a/drivers/phy/phy-samsung-usb2.c +++ b/drivers/phy/samsung/phy-samsung-usb2.c diff --git a/drivers/phy/phy-samsung-usb2.h b/drivers/phy/samsung/phy-samsung-usb2.h index 6563e7ca0ac4..6563e7ca0ac4 100644 --- a/drivers/phy/phy-samsung-usb2.h +++ b/drivers/phy/samsung/phy-samsung-usb2.h diff --git a/drivers/phy/st/Kconfig b/drivers/phy/st/Kconfig new file mode 100644 index 000000000000..0814d3f87ec6 --- /dev/null +++ b/drivers/phy/st/Kconfig @@ -0,0 +1,33 @@ +# +# Phy drivers for STMicro platforms +# +config PHY_MIPHY28LP + tristate "STMicroelectronics MIPHY28LP PHY driver for STiH407" + depends on ARCH_STI + select GENERIC_PHY + help + Enable this to support the miphy transceiver (for SATA/PCIE/USB3) + that is part of STMicroelectronics STiH407 SoC. + +config PHY_ST_SPEAR1310_MIPHY + tristate "ST SPEAR1310-MIPHY driver" + select GENERIC_PHY + depends on MACH_SPEAR1310 || COMPILE_TEST + help + Support for ST SPEAr1310 MIPHY which can be used for PCIe and SATA. + +config PHY_ST_SPEAR1340_MIPHY + tristate "ST SPEAR1340-MIPHY driver" + select GENERIC_PHY + depends on MACH_SPEAR1340 || COMPILE_TEST + help + Support for ST SPEAr1340 MIPHY which can be used for PCIe and SATA. + +config PHY_STIH407_USB + tristate "STMicroelectronics USB2 picoPHY driver for STiH407 family" + depends on RESET_CONTROLLER + depends on ARCH_STI || COMPILE_TEST + select GENERIC_PHY + help + Enable this support to enable the picoPHY device used by USB2 + and USB3 controllers on STMicroelectronics STiH407 SoC families. diff --git a/drivers/phy/st/Makefile b/drivers/phy/st/Makefile new file mode 100644 index 000000000000..e2adfe2166d2 --- /dev/null +++ b/drivers/phy/st/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_PHY_MIPHY28LP) += phy-miphy28lp.o +obj-$(CONFIG_PHY_ST_SPEAR1310_MIPHY) += phy-spear1310-miphy.o +obj-$(CONFIG_PHY_ST_SPEAR1340_MIPHY) += phy-spear1340-miphy.o +obj-$(CONFIG_PHY_STIH407_USB) += phy-stih407-usb.o diff --git a/drivers/phy/phy-miphy28lp.c b/drivers/phy/st/phy-miphy28lp.c index 213e2e15339c..213e2e15339c 100644 --- a/drivers/phy/phy-miphy28lp.c +++ b/drivers/phy/st/phy-miphy28lp.c diff --git a/drivers/phy/phy-spear1310-miphy.c b/drivers/phy/st/phy-spear1310-miphy.c index ed67e98e54ca..ed67e98e54ca 100644 --- a/drivers/phy/phy-spear1310-miphy.c +++ b/drivers/phy/st/phy-spear1310-miphy.c diff --git a/drivers/phy/phy-spear1340-miphy.c b/drivers/phy/st/phy-spear1340-miphy.c index 97280c0cf612..97280c0cf612 100644 --- a/drivers/phy/phy-spear1340-miphy.c +++ b/drivers/phy/st/phy-spear1340-miphy.c diff --git a/drivers/phy/phy-stih407-usb.c b/drivers/phy/st/phy-stih407-usb.c index b1f44ab669fb..b1f44ab669fb 100644 --- a/drivers/phy/phy-stih407-usb.c +++ b/drivers/phy/st/phy-stih407-usb.c diff --git a/drivers/phy/ti/Kconfig b/drivers/phy/ti/Kconfig new file mode 100644 index 000000000000..20503562666c --- /dev/null +++ b/drivers/phy/ti/Kconfig @@ -0,0 +1,78 @@ +# +# Phy drivers for TI platforms +# +config PHY_DA8XX_USB + tristate "TI DA8xx USB PHY Driver" + depends on ARCH_DAVINCI_DA8XX + select GENERIC_PHY + select MFD_SYSCON + help + Enable this to support the USB PHY on DA8xx SoCs. + + This driver controls both the USB 1.1 PHY and the USB 2.0 PHY. + +config PHY_DM816X_USB + tristate "TI dm816x USB PHY driver" + depends on ARCH_OMAP2PLUS + depends on USB_SUPPORT + select GENERIC_PHY + select USB_PHY + help + Enable this for dm816x USB to work. + +config OMAP_CONTROL_PHY + tristate "OMAP CONTROL PHY Driver" + depends on ARCH_OMAP2PLUS || COMPILE_TEST + help + Enable this to add support for the PHY part present in the control + module. This driver has API to power on the USB2 PHY and to write to + the mailbox. The mailbox is present only in omap4 and the register to + power on the USB2 PHY is present in OMAP4 and OMAP5. OMAP5 has an + additional register to power on USB3 PHY/SATA PHY/PCIE PHY + (PIPE3 PHY). + +config OMAP_USB2 + tristate "OMAP USB2 PHY Driver" + depends on ARCH_OMAP2PLUS + depends on USB_SUPPORT + select GENERIC_PHY + select USB_PHY + select OMAP_CONTROL_PHY + depends on OMAP_OCP2SCP + help + Enable this to support the transceiver that is part of SOC. This + driver takes care of all the PHY functionality apart from comparator. + The USB OTG controller communicates with the comparator using this + driver. + +config TI_PIPE3 + tristate "TI PIPE3 PHY Driver" + depends on ARCH_OMAP2PLUS || COMPILE_TEST + select GENERIC_PHY + select OMAP_CONTROL_PHY + depends on OMAP_OCP2SCP + help + Enable this to support the PIPE3 PHY that is part of TI SOCs. This + driver takes care of all the PHY functionality apart from comparator. + This driver interacts with the "OMAP Control PHY Driver" to power + on/off the PHY. + +config PHY_TUSB1210 + tristate "TI TUSB1210 ULPI PHY module" + depends on USB_ULPI_BUS + select GENERIC_PHY + help + Support for TI TUSB1210 USB ULPI PHY. + +config TWL4030_USB + tristate "TWL4030 USB Transceiver Driver" + depends on TWL4030_CORE && REGULATOR_TWL4030 && USB_MUSB_OMAP2PLUS + depends on USB_SUPPORT + depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't 'y' + select GENERIC_PHY + select USB_PHY + help + Enable this to support the USB OTG transceiver on TWL4030 + family chips (including the TWL5030 and TPS659x0 devices). + This transceiver supports high and full speed devices plus, + in host mode, low speed. diff --git a/drivers/phy/ti/Makefile b/drivers/phy/ti/Makefile new file mode 100644 index 000000000000..0cc3a1a557a3 --- /dev/null +++ b/drivers/phy/ti/Makefile @@ -0,0 +1,7 @@ +obj-$(CONFIG_PHY_DA8XX_USB) += phy-da8xx-usb.o +obj-$(CONFIG_PHY_DM816X_USB) += phy-dm816x-usb.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_PHY_TUSB1210) += phy-tusb1210.o +obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o diff --git a/drivers/phy/phy-da8xx-usb.c b/drivers/phy/ti/phy-da8xx-usb.c index 1b82bff6330f..1b82bff6330f 100644 --- a/drivers/phy/phy-da8xx-usb.c +++ b/drivers/phy/ti/phy-da8xx-usb.c diff --git a/drivers/phy/phy-dm816x-usb.c b/drivers/phy/ti/phy-dm816x-usb.c index cbcce7cf0028..cbcce7cf0028 100644 --- a/drivers/phy/phy-dm816x-usb.c +++ b/drivers/phy/ti/phy-dm816x-usb.c diff --git a/drivers/phy/phy-omap-control.c b/drivers/phy/ti/phy-omap-control.c index e9c41b3fa0ee..e9c41b3fa0ee 100644 --- a/drivers/phy/phy-omap-control.c +++ b/drivers/phy/ti/phy-omap-control.c diff --git a/drivers/phy/phy-omap-usb2.c b/drivers/phy/ti/phy-omap-usb2.c index fe909fd8144f..fe909fd8144f 100644 --- a/drivers/phy/phy-omap-usb2.c +++ b/drivers/phy/ti/phy-omap-usb2.c diff --git a/drivers/phy/phy-ti-pipe3.c b/drivers/phy/ti/phy-ti-pipe3.c index 9c84d32c6f60..9c84d32c6f60 100644 --- a/drivers/phy/phy-ti-pipe3.c +++ b/drivers/phy/ti/phy-ti-pipe3.c diff --git a/drivers/phy/phy-tusb1210.c b/drivers/phy/ti/phy-tusb1210.c index 4f6d5e71507d..b8ec39ac4dfc 100644 --- a/drivers/phy/phy-tusb1210.c +++ b/drivers/phy/ti/phy-tusb1210.c @@ -11,9 +11,9 @@ */ #include <linux/module.h> #include <linux/ulpi/driver.h> +#include <linux/ulpi/regs.h> #include <linux/gpio/consumer.h> - -#include "ulpi_phy.h" +#include <linux/phy/ulpi_phy.h> #define TUSB1210_VENDOR_SPECIFIC2 0x80 #define TUSB1210_VENDOR_SPECIFIC2_IHSTX_SHIFT 0 @@ -53,9 +53,43 @@ static int tusb1210_power_off(struct phy *phy) return 0; } +static int tusb1210_set_mode(struct phy *phy, enum phy_mode mode) +{ + struct tusb1210 *tusb = phy_get_drvdata(phy); + int ret; + + ret = ulpi_read(tusb->ulpi, ULPI_OTG_CTRL); + if (ret < 0) + return ret; + + switch (mode) { + case PHY_MODE_USB_HOST: + ret |= (ULPI_OTG_CTRL_DRVVBUS_EXT + | ULPI_OTG_CTRL_ID_PULLUP + | ULPI_OTG_CTRL_DP_PULLDOWN + | ULPI_OTG_CTRL_DM_PULLDOWN); + ulpi_write(tusb->ulpi, ULPI_OTG_CTRL, ret); + ret |= ULPI_OTG_CTRL_DRVVBUS; + break; + case PHY_MODE_USB_DEVICE: + ret &= ~(ULPI_OTG_CTRL_DRVVBUS + | ULPI_OTG_CTRL_DP_PULLDOWN + | ULPI_OTG_CTRL_DM_PULLDOWN); + ulpi_write(tusb->ulpi, ULPI_OTG_CTRL, ret); + ret &= ~ULPI_OTG_CTRL_DRVVBUS_EXT; + break; + default: + /* nothing */ + return 0; + } + + return ulpi_write(tusb->ulpi, ULPI_OTG_CTRL, ret); +} + static const struct phy_ops phy_ops = { .power_on = tusb1210_power_on, .power_off = tusb1210_power_off, + .set_mode = tusb1210_set_mode, .owner = THIS_MODULE, }; @@ -125,7 +159,8 @@ static void tusb1210_remove(struct ulpi *ulpi) #define TI_VENDOR_ID 0x0451 static const struct ulpi_device_id tusb1210_ulpi_id[] = { - { TI_VENDOR_ID, 0x1507, }, + { TI_VENDOR_ID, 0x1507, }, /* TUSB1210 */ + { TI_VENDOR_ID, 0x1508, }, /* TUSB1211 */ { }, }; MODULE_DEVICE_TABLE(ulpi, tusb1210_ulpi_id); diff --git a/drivers/phy/phy-twl4030-usb.c b/drivers/phy/ti/phy-twl4030-usb.c index 2990b3965460..2990b3965460 100644 --- a/drivers/phy/phy-twl4030-usb.c +++ b/drivers/phy/ti/phy-twl4030-usb.c diff --git a/drivers/thermal/max77620_thermal.c b/drivers/thermal/max77620_thermal.c index e9a1fe342760..159bbcee8821 100644 --- a/drivers/thermal/max77620_thermal.c +++ b/drivers/thermal/max77620_thermal.c @@ -104,8 +104,6 @@ static int max77620_thermal_probe(struct platform_device *pdev) return -EINVAL; } - pdev->dev.of_node = pdev->dev.parent->of_node; - mtherm->dev = &pdev->dev; mtherm->rmap = dev_get_regmap(pdev->dev.parent, NULL); if (!mtherm->rmap) { @@ -113,6 +111,12 @@ static int max77620_thermal_probe(struct platform_device *pdev) return -ENODEV; } + /* + * The reference taken to the parent's node which will be balanced on + * reprobe or on platform-device release. + */ + device_set_of_node_from_dev(&pdev->dev, pdev->dev.parent); + mtherm->tz_device = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, mtherm, &max77620_thermal_ops); if (IS_ERR(mtherm->tz_device)) { diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index fe4fe2440729..b17ed3a9a304 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -818,7 +818,7 @@ static inline void ci_role_destroy(struct ci_hdrc *ci) { ci_hdrc_gadget_destroy(ci); ci_hdrc_host_destroy(ci); - if (ci->is_otg) + if (ci->is_otg && ci->roles[CI_ROLE_GADGET]) ci_hdrc_otg_destroy(ci); } @@ -980,27 +980,35 @@ static int ci_hdrc_probe(struct platform_device *pdev) /* initialize role(s) before the interrupt is requested */ if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) { ret = ci_hdrc_host_init(ci); - if (ret) - dev_info(dev, "doesn't support host\n"); + if (ret) { + if (ret == -ENXIO) + dev_info(dev, "doesn't support host\n"); + else + goto deinit_phy; + } } if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) { ret = ci_hdrc_gadget_init(ci); - if (ret) - dev_info(dev, "doesn't support gadget\n"); + if (ret) { + if (ret == -ENXIO) + dev_info(dev, "doesn't support gadget\n"); + else + goto deinit_host; + } } if (!ci->roles[CI_ROLE_HOST] && !ci->roles[CI_ROLE_GADGET]) { dev_err(dev, "no supported roles\n"); ret = -ENODEV; - goto deinit_phy; + goto deinit_gadget; } if (ci->is_otg && ci->roles[CI_ROLE_GADGET]) { ret = ci_hdrc_otg_init(ci); if (ret) { dev_err(dev, "init otg fails, ret = %d\n", ret); - goto stop; + goto deinit_gadget; } } @@ -1070,7 +1078,12 @@ static int ci_hdrc_probe(struct platform_device *pdev) remove_debug: dbg_remove_files(ci); stop: - ci_role_destroy(ci); + if (ci->is_otg && ci->roles[CI_ROLE_GADGET]) + ci_hdrc_otg_destroy(ci); +deinit_gadget: + ci_hdrc_gadget_destroy(ci); +deinit_host: + ci_hdrc_host_destroy(ci); deinit_phy: ci_usb_phy_exit(ci); ulpi_exit: diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c index 93e24ce61a3a..949183ede16f 100644 --- a/drivers/usb/chipidea/otg_fsm.c +++ b/drivers/usb/chipidea/otg_fsm.c @@ -234,7 +234,7 @@ static void ci_otg_add_timer(struct ci_hdrc *ci, enum otg_fsm_timer t) ktime_set(timer_sec, timer_nsec)); ci->enabled_otg_timer_bits |= (1 << t); if ((ci->next_otg_timer == NUM_OTG_FSM_TIMERS) || - (ci->hr_timeouts[ci->next_otg_timer] > + ktime_after(ci->hr_timeouts[ci->next_otg_timer], ci->hr_timeouts[t])) { ci->next_otg_timer = t; hrtimer_start_range_ns(&ci->otg_fsm_hrtimer, @@ -269,7 +269,7 @@ static void ci_otg_del_timer(struct ci_hdrc *ci, enum otg_fsm_timer t) for_each_set_bit(cur_timer, &enabled_timer_bits, NUM_OTG_FSM_TIMERS) { if ((next_timer == NUM_OTG_FSM_TIMERS) || - (ci->hr_timeouts[next_timer] < + ktime_before(ci->hr_timeouts[next_timer], ci->hr_timeouts[cur_timer])) next_timer = cur_timer; } @@ -397,13 +397,13 @@ static enum hrtimer_restart ci_otg_hrtimer_func(struct hrtimer *t) now = ktime_get(); for_each_set_bit(cur_timer, &enabled_timer_bits, NUM_OTG_FSM_TIMERS) { - if (now >= ci->hr_timeouts[cur_timer]) { + if (ktime_compare(now, ci->hr_timeouts[cur_timer]) >= 0) { ci->enabled_otg_timer_bits &= ~(1 << cur_timer); if (otg_timer_handlers[cur_timer]) ret = otg_timer_handlers[cur_timer](ci); } else { if ((next_timer == NUM_OTG_FSM_TIMERS) || - (ci->hr_timeouts[cur_timer] < + ktime_before(ci->hr_timeouts[cur_timer], ci->hr_timeouts[next_timer])) next_timer = cur_timer; } diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index 08669fee6d7f..8f972247b1c1 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -361,17 +361,9 @@ static ssize_t wdm_write if (we < 0) return usb_translate_errors(we); - buf = kmalloc(count, GFP_KERNEL); - if (!buf) { - rv = -ENOMEM; - goto outnl; - } - - r = copy_from_user(buf, buffer, count); - if (r > 0) { - rv = -EFAULT; - goto out_free_mem; - } + buf = memdup_user(buffer, count); + if (IS_ERR(buf)) + return PTR_ERR(buf); /* concurrent writes and disconnect */ r = mutex_lock_interruptible(&desc->wlock); @@ -441,8 +433,7 @@ static ssize_t wdm_write usb_autopm_put_interface(desc->intf); mutex_unlock(&desc->wlock); -outnl: - return rv < 0 ? rv : count; + return count; out_free_mem_pm: usb_autopm_put_interface(desc->intf); diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 8e6ef671be9b..0e7d0e81a7cb 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -2537,6 +2537,9 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd, case USBDEVFS_DROP_PRIVILEGES: ret = proc_drop_privileges(ps, p); break; + case USBDEVFS_GET_SPEED: + ret = ps->dev->speed; + break; } done: diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 5dea98358c05..ab1bb3b538ac 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -26,6 +26,7 @@ #include <linux/module.h> #include <linux/version.h> #include <linux/kernel.h> +#include <linux/sched/task_stack.h> #include <linux/slab.h> #include <linux/completion.h> #include <linux/utsname.h> @@ -1076,7 +1077,6 @@ static void usb_deregister_bus (struct usb_bus *bus) static int register_root_hub(struct usb_hcd *hcd) { struct device *parent_dev = hcd->self.controller; - struct device *sysdev = hcd->self.sysdev; struct usb_device *usb_dev = hcd->self.root_hub; const int devnum = 1; int retval; @@ -1123,7 +1123,6 @@ static int register_root_hub(struct usb_hcd *hcd) /* Did the HC die before the root hub was registered? */ if (HCD_DEAD(hcd)) usb_hc_died (hcd); /* This time clean up */ - usb_dev->dev.of_node = sysdev->of_node; } mutex_unlock(&usb_bus_idr_lock); @@ -1523,6 +1522,14 @@ int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, if (hcd->self.uses_pio_for_control) return ret; if (IS_ENABLED(CONFIG_HAS_DMA) && hcd->self.uses_dma) { + if (is_vmalloc_addr(urb->setup_packet)) { + WARN_ONCE(1, "setup packet is not dma capable\n"); + return -EAGAIN; + } else if (object_is_on_stack(urb->setup_packet)) { + WARN_ONCE(1, "setup packet is on stack\n"); + return -EAGAIN; + } + urb->setup_dma = dma_map_single( hcd->self.sysdev, urb->setup_packet, @@ -1587,6 +1594,9 @@ int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, } else if (is_vmalloc_addr(urb->transfer_buffer)) { WARN_ONCE(1, "transfer buffer not dma capable\n"); ret = -EAGAIN; + } else if (object_is_on_stack(urb->transfer_buffer)) { + WARN_ONCE(1, "transfer buffer is on stack\n"); + ret = -EAGAIN; } else { urb->transfer_dma = dma_map_single( hcd->self.sysdev, diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index b8bb20d7acdb..6e6797d145dd 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -1661,10 +1661,28 @@ static void hub_disconnect(struct usb_interface *intf) kref_put(&hub->kref, hub_release); } +static bool hub_descriptor_is_sane(struct usb_host_interface *desc) +{ + /* Some hubs have a subclass of 1, which AFAICT according to the */ + /* specs is not defined, but it works */ + if (desc->desc.bInterfaceSubClass != 0 && + desc->desc.bInterfaceSubClass != 1) + return false; + + /* Multiple endpoints? What kind of mutant ninja-hub is this? */ + if (desc->desc.bNumEndpoints != 1) + return false; + + /* If the first endpoint is not interrupt IN, we'd better punt! */ + if (!usb_endpoint_is_int_in(&desc->endpoint[0].desc)) + return false; + + return true; +} + static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct usb_host_interface *desc; - struct usb_endpoint_descriptor *endpoint; struct usb_device *hdev; struct usb_hub *hub; @@ -1739,25 +1757,11 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) } #endif - /* Some hubs have a subclass of 1, which AFAICT according to the */ - /* specs is not defined, but it works */ - if ((desc->desc.bInterfaceSubClass != 0) && - (desc->desc.bInterfaceSubClass != 1)) { -descriptor_error: + if (!hub_descriptor_is_sane(desc)) { dev_err(&intf->dev, "bad descriptor, ignoring hub\n"); return -EIO; } - /* Multiple endpoints? What kind of mutant ninja-hub is this? */ - if (desc->desc.bNumEndpoints != 1) - goto descriptor_error; - - endpoint = &desc->endpoint[0].desc; - - /* If it's not an interrupt in endpoint, we'd better punt! */ - if (!usb_endpoint_is_int_in(endpoint)) - goto descriptor_error; - /* We found a hub */ dev_info(&intf->dev, "USB hub found\n"); @@ -1784,7 +1788,7 @@ descriptor_error: if (id->driver_info & HUB_QUIRK_CHECK_PORT_AUTOSUSPEND) hub->quirk_check_port_auto_suspend = 1; - if (hub_configure(hub, endpoint) >= 0) + if (hub_configure(hub, &desc->endpoint[0].desc) >= 0) return 0; hub_disconnect(intf); @@ -3155,12 +3159,6 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) if (PMSG_IS_AUTO(msg)) goto err_ltm; } - if (usb_unlocked_disable_lpm(udev)) { - dev_err(&udev->dev, "Failed to disable LPM before suspend\n."); - status = -ENOMEM; - if (PMSG_IS_AUTO(msg)) - goto err_lpm3; - } /* see 7.1.7.6 */ if (hub_is_superspeed(hub->hdev)) @@ -3187,9 +3185,7 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) if (status) { dev_dbg(&port_dev->dev, "can't suspend, status %d\n", status); - /* Try to enable USB3 LPM and LTM again */ - usb_unlocked_enable_lpm(udev); - err_lpm3: + /* Try to enable USB3 LTM again */ usb_enable_ltm(udev); err_ltm: /* Try to enable USB2 hardware LPM again */ @@ -3473,9 +3469,8 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) if (udev->usb2_hw_lpm_capable == 1) usb_set_usb2_hardware_lpm(udev, 1); - /* Try to enable USB3 LTM and LPM */ + /* Try to enable USB3 LTM */ usb_enable_ltm(udev); - usb_unlocked_enable_lpm(udev); } usb_unlock_port(port_dev); diff --git a/drivers/usb/core/ledtrig-usbport.c b/drivers/usb/core/ledtrig-usbport.c index 1713248ab15a..16c19a31dad1 100644 --- a/drivers/usb/core/ledtrig-usbport.c +++ b/drivers/usb/core/ledtrig-usbport.c @@ -11,8 +11,10 @@ #include <linux/device.h> #include <linux/leds.h> #include <linux/module.h> +#include <linux/of.h> #include <linux/slab.h> #include <linux/usb.h> +#include <linux/usb/of.h> struct usbport_trig_data { struct led_classdev *led_cdev; @@ -123,6 +125,57 @@ static const struct attribute_group ports_group = { * Adding & removing ports ***************************************/ +/** + * usbport_trig_port_observed - Check if port should be observed + */ +static bool usbport_trig_port_observed(struct usbport_trig_data *usbport_data, + struct usb_device *usb_dev, int port1) +{ + struct device *dev = usbport_data->led_cdev->dev; + struct device_node *led_np = dev->of_node; + struct of_phandle_args args; + struct device_node *port_np; + int count, i; + + if (!led_np) + return false; + + /* Get node of port being added */ + port_np = usb_of_get_child_node(usb_dev->dev.of_node, port1); + if (!port_np) + return false; + + /* Amount of trigger sources for this LED */ + count = of_count_phandle_with_args(led_np, "trigger-sources", + "#trigger-source-cells"); + if (count < 0) { + dev_warn(dev, "Failed to get trigger sources for %s\n", + led_np->full_name); + return false; + } + + /* Check list of sources for this specific port */ + for (i = 0; i < count; i++) { + int err; + + err = of_parse_phandle_with_args(led_np, "trigger-sources", + "#trigger-source-cells", i, + &args); + if (err) { + dev_err(dev, "Failed to get trigger source phandle at index %d: %d\n", + i, err); + continue; + } + + of_node_put(args.np); + + if (args.np == port_np) + return true; + } + + return false; +} + static int usbport_trig_add_port(struct usbport_trig_data *usbport_data, struct usb_device *usb_dev, const char *hub_name, int portnum) @@ -141,6 +194,8 @@ static int usbport_trig_add_port(struct usbport_trig_data *usbport_data, port->data = usbport_data; port->hub = usb_dev; port->portnum = portnum; + port->observed = usbport_trig_port_observed(usbport_data, usb_dev, + portnum); len = strlen(hub_name) + 8; port->port_name = kzalloc(len, GFP_KERNEL); @@ -255,6 +310,7 @@ static void usbport_trig_activate(struct led_classdev *led_cdev) if (err) goto err_free; usb_for_each_dev(usbport_data, usbport_trig_add_usb_dev_ports); + usbport_trig_update_count(usbport_data); /* Notifications */ usbport_data->nb.notifier_call = usbport_trig_notify, diff --git a/drivers/usb/core/of.c b/drivers/usb/core/of.c index d563cbcf76cf..3863bb1ce8c5 100644 --- a/drivers/usb/core/of.c +++ b/drivers/usb/core/of.c @@ -28,7 +28,8 @@ * * Find the node from device tree according to its port number. * - * Return: On success, a pointer to the device node, %NULL on failure. + * Return: A pointer to the node with incremented refcount if found, or + * %NULL otherwise. */ struct device_node *usb_of_get_child_node(struct device_node *parent, int portnum) diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index 96b21b0dac1e..3116edfcdc18 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -223,6 +223,10 @@ static const struct usb_device_id usb_quirk_list[] = { /* Blackmagic Design UltraStudio SDI */ { USB_DEVICE(0x1edb, 0xbd4f), .driver_info = USB_QUIRK_NO_LPM }, + /* Hauppauge HVR-950q */ + { USB_DEVICE(0x2040, 0x7200), .driver_info = + USB_QUIRK_CONFIG_INTF_STRINGS }, + /* INTEL VALUE SSD */ { USB_DEVICE(0x8086, 0xf1a5), .driver_info = USB_QUIRK_RESET_RESUME }, diff --git a/drivers/usb/core/usb-acpi.c b/drivers/usb/core/usb-acpi.c index 2776cfe64c09..ef9cf4a21afe 100644 --- a/drivers/usb/core/usb-acpi.c +++ b/drivers/usb/core/usb-acpi.c @@ -127,6 +127,22 @@ out: */ #define USB_ACPI_LOCATION_VALID (1 << 31) +static struct acpi_device *usb_acpi_find_port(struct acpi_device *parent, + int raw) +{ + struct acpi_device *adev; + + if (!parent) + return NULL; + + list_for_each_entry(adev, &parent->children, node) { + if (acpi_device_adr(adev) == raw) + return adev; + } + + return acpi_find_child_device(parent, raw, false); +} + static struct acpi_device *usb_acpi_find_companion(struct device *dev) { struct usb_device *udev; @@ -174,8 +190,10 @@ static struct acpi_device *usb_acpi_find_companion(struct device *dev) int raw; raw = usb_hcd_find_raw_port_number(hcd, port1); - adev = acpi_find_child_device(ACPI_COMPANION(&udev->dev), - raw, false); + + adev = usb_acpi_find_port(ACPI_COMPANION(&udev->dev), + raw); + if (!adev) return NULL; } else { @@ -186,7 +204,9 @@ static struct acpi_device *usb_acpi_find_companion(struct device *dev) return NULL; acpi_bus_get_device(parent_handle, &adev); - adev = acpi_find_child_device(adev, port1, false); + + adev = usb_acpi_find_port(adev, port1); + if (!adev) return NULL; } diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 28b053cacc90..17681d5638ac 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -416,6 +416,7 @@ static void usb_release_dev(struct device *dev) usb_destroy_configuration(udev); usb_release_bos_descriptor(udev); + of_node_put(dev->of_node); usb_put_hcd(hcd); kfree(udev->product); kfree(udev->manufacturer); @@ -614,6 +615,7 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent, dev->route = 0; dev->dev.parent = bus->controller; + device_set_of_node_from_dev(&dev->dev, bus->sysdev); dev_set_name(&dev->dev, "usb%d", bus->busnum); root_hub = 1; } else { diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 455d89a1cd6d..326b302fc440 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -151,11 +151,24 @@ static void __dwc3_set_mode(struct work_struct *work) switch (dwc->desired_dr_role) { case DWC3_GCTL_PRTCAP_HOST: ret = dwc3_host_init(dwc); - if (ret) + if (ret) { dev_err(dwc->dev, "failed to initialize host\n"); + } else { + if (dwc->usb2_phy) + otg_set_vbus(dwc->usb2_phy->otg, true); + if (dwc->usb2_generic_phy) + phy_set_mode(dwc->usb2_generic_phy, PHY_MODE_USB_HOST); + + } break; case DWC3_GCTL_PRTCAP_DEVICE: dwc3_event_buffers_setup(dwc); + + if (dwc->usb2_phy) + otg_set_vbus(dwc->usb2_phy->otg, false); + if (dwc->usb2_generic_phy) + phy_set_mode(dwc->usb2_generic_phy, PHY_MODE_USB_DEVICE); + ret = dwc3_gadget_init(dwc); if (ret) dev_err(dwc->dev, "failed to initialize peripheral\n"); @@ -721,6 +734,8 @@ static void dwc3_core_setup_global_control(struct dwc3 *dwc) dwc3_writel(dwc->regs, DWC3_GCTL, reg); } +static int dwc3_core_get_phy(struct dwc3 *dwc); + /** * dwc3_core_init - Low-level initialization of DWC3 Core * @dwc: Pointer to our controller context structure @@ -759,6 +774,10 @@ static int dwc3_core_init(struct dwc3 *dwc) if (ret) goto err0; + ret = dwc3_core_get_phy(dwc); + if (ret) + goto err0; + dwc3_core_setup_global_control(dwc); dwc3_core_num_eps(dwc); @@ -796,13 +815,19 @@ static int dwc3_core_init(struct dwc3 *dwc) dwc3_writel(dwc->regs, DWC3_GUCTL2, reg); } - /* - * Enable hardware control of sending remote wakeup in HS when - * the device is in the L1 state. - */ - if (dwc->revision >= DWC3_REVISION_290A) { + if (dwc->revision >= DWC3_REVISION_250A) { reg = dwc3_readl(dwc->regs, DWC3_GUCTL1); - reg |= DWC3_GUCTL1_DEV_L1_EXIT_BY_HW; + + /* + * Enable hardware control of sending remote wakeup + * in HS when the device is in the L1 state. + */ + if (dwc->revision >= DWC3_REVISION_290A) + reg |= DWC3_GUCTL1_DEV_L1_EXIT_BY_HW; + + if (dwc->dis_tx_ipgap_linecheck_quirk) + reg |= DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS; + dwc3_writel(dwc->regs, DWC3_GUCTL1, reg); } @@ -903,6 +928,12 @@ static int dwc3_core_init_mode(struct dwc3 *dwc) switch (dwc->dr_mode) { case USB_DR_MODE_PERIPHERAL: dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE); + + if (dwc->usb2_phy) + otg_set_vbus(dwc->usb2_phy->otg, false); + if (dwc->usb2_generic_phy) + phy_set_mode(dwc->usb2_generic_phy, PHY_MODE_USB_DEVICE); + ret = dwc3_gadget_init(dwc); if (ret) { if (ret != -EPROBE_DEFER) @@ -912,6 +943,12 @@ static int dwc3_core_init_mode(struct dwc3 *dwc) break; case USB_DR_MODE_HOST: dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST); + + if (dwc->usb2_phy) + otg_set_vbus(dwc->usb2_phy->otg, true); + if (dwc->usb2_generic_phy) + phy_set_mode(dwc->usb2_generic_phy, PHY_MODE_USB_HOST); + ret = dwc3_host_init(dwc); if (ret) { if (ret != -EPROBE_DEFER) @@ -1023,6 +1060,8 @@ static void dwc3_get_properties(struct dwc3 *dwc) "snps,dis-u2-freeclk-exists-quirk"); dwc->dis_del_phy_power_chg_quirk = device_property_read_bool(dev, "snps,dis-del-phy-power-chg-quirk"); + dwc->dis_tx_ipgap_linecheck_quirk = device_property_read_bool(dev, + "snps,dis-tx-ipgap-linecheck-quirk"); dwc->tx_de_emphasis_quirk = device_property_read_bool(dev, "snps,tx_de_emphasis_quirk"); @@ -1148,10 +1187,6 @@ static int dwc3_probe(struct platform_device *pdev) platform_set_drvdata(pdev, dwc); dwc3_cache_hwparams(dwc); - ret = dwc3_core_get_phy(dwc); - if (ret) - goto err0; - spin_lock_init(&dwc->lock); pm_runtime_set_active(dev); diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 981c77f5628e..ea910acb4bb0 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -1,4 +1,4 @@ -/** +/* * core.h - DesignWare USB3 DRD Core Header * * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com @@ -204,6 +204,7 @@ #define DWC3_GCTL_DSBLCLKGTNG BIT(0) /* Global User Control 1 Register */ +#define DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS BIT(28) #define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW BIT(24) /* Global USB2 PHY Configuration Register */ @@ -522,7 +523,6 @@ struct dwc3_event_buffer { * @trb_pool_dma: dma address of @trb_pool * @trb_enqueue: enqueue 'pointer' into TRB array * @trb_dequeue: dequeue 'pointer' into TRB array - * @desc: usb_endpoint_descriptor pointer * @dwc: pointer to DWC controller * @saved_state: ep state saved during hibernation * @flags: endpoint flags (wedged, stalled, ...) @@ -664,7 +664,7 @@ enum dwc3_link_state { * @bpl: DW0-3 * @bph: DW4-7 * @size: DW8-B - * @trl: DWC-F + * @ctrl: DWC-F */ struct dwc3_trb { u32 bpl; @@ -674,16 +674,16 @@ struct dwc3_trb { } __packed; /** - * dwc3_hwparams - copy of HWPARAMS registers - * @hwparams0 - GHWPARAMS0 - * @hwparams1 - GHWPARAMS1 - * @hwparams2 - GHWPARAMS2 - * @hwparams3 - GHWPARAMS3 - * @hwparams4 - GHWPARAMS4 - * @hwparams5 - GHWPARAMS5 - * @hwparams6 - GHWPARAMS6 - * @hwparams7 - GHWPARAMS7 - * @hwparams8 - GHWPARAMS8 + * struct dwc3_hwparams - copy of HWPARAMS registers + * @hwparams0: GHWPARAMS0 + * @hwparams1: GHWPARAMS1 + * @hwparams2: GHWPARAMS2 + * @hwparams3: GHWPARAMS3 + * @hwparams4: GHWPARAMS4 + * @hwparams5: GHWPARAMS5 + * @hwparams6: GHWPARAMS6 + * @hwparams7: GHWPARAMS7 + * @hwparams8: GHWPARAMS8 */ struct dwc3_hwparams { u32 hwparams0; @@ -730,7 +730,8 @@ struct dwc3_hwparams { * @unaligned: true for OUT endpoints with length not divisible by maxp * @direction: IN or OUT direction flag * @mapped: true when request has been dma-mapped - * @queued: true when request has been queued to HW + * @started: request is started + * @zero: wants a ZLP */ struct dwc3_request { struct usb_request request; @@ -761,17 +762,23 @@ struct dwc3_scratchpad_array { /** * struct dwc3 - representation of our controller - * @drd_work - workqueue used for role swapping + * @drd_work: workqueue used for role swapping * @ep0_trb: trb which is used for the ctrl_req + * @bounce: address of bounce buffer + * @scratchbuf: address of scratch buffer * @setup_buf: used while precessing STD USB requests - * @ep0_trb: dma address of ep0_trb + * @ep0_trb_addr: dma address of @ep0_trb + * @bounce_addr: dma address of @bounce * @ep0_usb_req: dummy req used while handling STD USB requests * @scratch_addr: dma address of scratchbuf * @ep0_in_setup: one control transfer is completed and enter setup phase * @lock: for synchronizing * @dev: pointer to our struct device + * @sysdev: pointer to the DMA-capable device * @xhci: pointer to our xHCI child - * @event_buffer_list: a list of event buffers + * @xhci_resources: struct resources for our @xhci child + * @ev_buf: struct dwc3_event_buffer pointer + * @eps: endpoint array * @gadget: device side representation of the peripheral controller * @gadget_driver: pointer to the gadget driver * @regs: base address for our registers @@ -795,8 +802,6 @@ struct dwc3_scratchpad_array { * @usb2_generic_phy: pointer to USB2 PHY * @usb3_generic_phy: pointer to USB3 PHY * @ulpi: pointer to ulpi interface - * @dcfg: saved contents of DCFG register - * @gctl: saved contents of GCTL register * @isoch_delay: wValue from Set Isochronous Delay request; * @u2sel: parameter from Set SEL request. * @u2pel: parameter from Set SEL request. @@ -830,7 +835,6 @@ struct dwc3_scratchpad_array { * @pending_events: true when we have pending IRQs to be handled * @pullups_connected: true when Run/Stop bit is set * @setup_packet_pending: true when there's a Setup Packet in FIFO. Workaround - * @start_config_issued: true when StartConfig command has been issued * @three_stage_setup: set if we perform a three phase setup * @usb3_lpm_capable: set if hadrware supports Link Power Management * @disable_scramble_quirk: set if we enable the disable scramble quirk @@ -845,11 +849,14 @@ struct dwc3_scratchpad_array { * @dis_u2_susphy_quirk: set if we disable usb2 suspend phy * @dis_enblslpm_quirk: set if we clear enblslpm in GUSB2PHYCFG, * disabling the suspend signal to the PHY. + * @dis_rxdet_inp3_quirk: set if we disable Rx.Detect in P3 * @dis_u2_freeclk_exists_quirk : set if we clear u2_freeclk_exists * in GUSB2PHYCFG, specify that USB2 PHY doesn't * provide a free-running PHY clock. * @dis_del_phy_power_chg_quirk: set if we disable delay phy power * change quirk. + * @dis_tx_ipgap_linecheck_quirk: set if we disable u2mac linestate + * check during HS transmit. * @tx_de_emphasis_quirk: set if we enable Tx de-emphasis quirk * @tx_de_emphasis: Tx de-emphasis value * 0 - -6dB de-emphasis @@ -1004,6 +1011,7 @@ struct dwc3 { unsigned dis_rxdet_inp3_quirk:1; unsigned dis_u2_freeclk_exists_quirk:1; unsigned dis_del_phy_power_chg_quirk:1; + unsigned dis_tx_ipgap_linecheck_quirk:1; unsigned tx_de_emphasis_quirk:1; unsigned tx_de_emphasis:2; diff --git a/drivers/usb/dwc3/debug.h b/drivers/usb/dwc3/debug.h index cb2d8d3f7f3d..5e9c070ec874 100644 --- a/drivers/usb/dwc3/debug.h +++ b/drivers/usb/dwc3/debug.h @@ -173,9 +173,8 @@ static inline const char *dwc3_ep0_state_string(enum dwc3_ep0_state state) * @event: the event code */ static inline const char * -dwc3_gadget_event_string(const struct dwc3_event_devt *event) +dwc3_gadget_event_string(char *str, const struct dwc3_event_devt *event) { - static char str[256]; enum dwc3_link_state state = event->event_info & DWC3_LINK_STATE_MASK; switch (event->type) { @@ -223,15 +222,249 @@ dwc3_gadget_event_string(const struct dwc3_event_devt *event) return str; } +static inline void dwc3_decode_get_status(__u8 t, __u16 i, __u16 l, char *str) +{ + switch (t & USB_RECIP_MASK) { + case USB_RECIP_INTERFACE: + sprintf(str, "Get Interface Status(Intf = %d, Length = %d)", + i, l); + break; + case USB_RECIP_ENDPOINT: + sprintf(str, "Get Endpoint Status(ep%d%s)", + i & ~USB_DIR_IN, + i & USB_DIR_IN ? "in" : "out"); + break; + } +} + +static inline void dwc3_decode_set_clear_feature(__u8 t, __u8 b, __u16 v, + __u16 i, char *str) +{ + switch (t & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + sprintf(str, "%s Device Feature(%s%s)", + b == USB_REQ_CLEAR_FEATURE ? "Clear" : "Set", + ({char *s; + switch (v) { + case USB_DEVICE_SELF_POWERED: + s = "Self Powered"; + break; + case USB_DEVICE_REMOTE_WAKEUP: + s = "Remote Wakeup"; + break; + case USB_DEVICE_TEST_MODE: + s = "Test Mode"; + break; + default: + s = "UNKNOWN"; + } s; }), + v == USB_DEVICE_TEST_MODE ? + ({ char *s; + switch (i) { + case TEST_J: + s = ": TEST_J"; + break; + case TEST_K: + s = ": TEST_K"; + break; + case TEST_SE0_NAK: + s = ": TEST_SE0_NAK"; + break; + case TEST_PACKET: + s = ": TEST_PACKET"; + break; + case TEST_FORCE_EN: + s = ": TEST_FORCE_EN"; + break; + default: + s = ": UNKNOWN"; + } s; }) : ""); + break; + case USB_RECIP_INTERFACE: + sprintf(str, "%s Interface Feature(%s)", + b == USB_REQ_CLEAR_FEATURE ? "Clear" : "Set", + v == USB_INTRF_FUNC_SUSPEND ? + "Function Suspend" : "UNKNOWN"); + break; + case USB_RECIP_ENDPOINT: + sprintf(str, "%s Endpoint Feature(%s ep%d%s)", + b == USB_REQ_CLEAR_FEATURE ? "Clear" : "Set", + v == USB_ENDPOINT_HALT ? "Halt" : "UNKNOWN", + i & ~USB_DIR_IN, + i & USB_DIR_IN ? "in" : "out"); + break; + } +} + +static inline void dwc3_decode_set_address(__u16 v, char *str) +{ + sprintf(str, "Set Address(Addr = %02x)", v); +} + +static inline void dwc3_decode_get_set_descriptor(__u8 t, __u8 b, __u16 v, + __u16 i, __u16 l, char *str) +{ + sprintf(str, "%s %s Descriptor(Index = %d, Length = %d)", + b == USB_REQ_GET_DESCRIPTOR ? "Get" : "Set", + ({ char *s; + switch (v >> 8) { + case USB_DT_DEVICE: + s = "Device"; + break; + case USB_DT_CONFIG: + s = "Configuration"; + break; + case USB_DT_STRING: + s = "String"; + break; + case USB_DT_INTERFACE: + s = "Interface"; + break; + case USB_DT_ENDPOINT: + s = "Endpoint"; + break; + case USB_DT_DEVICE_QUALIFIER: + s = "Device Qualifier"; + break; + case USB_DT_OTHER_SPEED_CONFIG: + s = "Other Speed Config"; + break; + case USB_DT_INTERFACE_POWER: + s = "Interface Power"; + break; + case USB_DT_OTG: + s = "OTG"; + break; + case USB_DT_DEBUG: + s = "Debug"; + break; + case USB_DT_INTERFACE_ASSOCIATION: + s = "Interface Association"; + break; + case USB_DT_BOS: + s = "BOS"; + break; + case USB_DT_DEVICE_CAPABILITY: + s = "Device Capability"; + break; + case USB_DT_PIPE_USAGE: + s = "Pipe Usage"; + break; + case USB_DT_SS_ENDPOINT_COMP: + s = "SS Endpoint Companion"; + break; + case USB_DT_SSP_ISOC_ENDPOINT_COMP: + s = "SSP Isochronous Endpoint Companion"; + break; + default: + s = "UNKNOWN"; + break; + } s; }), v & 0xff, l); +} + + +static inline void dwc3_decode_get_configuration(__u16 l, char *str) +{ + sprintf(str, "Get Configuration(Length = %d)", l); +} + +static inline void dwc3_decode_set_configuration(__u8 v, char *str) +{ + sprintf(str, "Set Configuration(Config = %d)", v); +} + +static inline void dwc3_decode_get_intf(__u16 i, __u16 l, char *str) +{ + sprintf(str, "Get Interface(Intf = %d, Length = %d)", i, l); +} + +static inline void dwc3_decode_set_intf(__u8 v, __u16 i, char *str) +{ + sprintf(str, "Set Interface(Intf = %d, Alt.Setting = %d)", i, v); +} + +static inline void dwc3_decode_synch_frame(__u16 i, __u16 l, char *str) +{ + sprintf(str, "Synch Frame(Endpoint = %d, Length = %d)", i, l); +} + +static inline void dwc3_decode_set_sel(__u16 l, char *str) +{ + sprintf(str, "Set SEL(Length = %d)", l); +} + +static inline void dwc3_decode_set_isoch_delay(__u8 v, char *str) +{ + sprintf(str, "Set Isochronous Delay(Delay = %d ns)", v); +} + +/** + * dwc3_decode_ctrl - returns a string represetion of ctrl request + */ +static inline const char *dwc3_decode_ctrl(char *str, __u8 bRequestType, + __u8 bRequest, __u16 wValue, __u16 wIndex, __u16 wLength) +{ + switch (bRequest) { + case USB_REQ_GET_STATUS: + dwc3_decode_get_status(bRequestType, wIndex, wLength, str); + break; + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + dwc3_decode_set_clear_feature(bRequestType, bRequest, wValue, + wIndex, str); + break; + case USB_REQ_SET_ADDRESS: + dwc3_decode_set_address(wValue, str); + break; + case USB_REQ_GET_DESCRIPTOR: + case USB_REQ_SET_DESCRIPTOR: + dwc3_decode_get_set_descriptor(bRequestType, bRequest, wValue, + wIndex, wLength, str); + break; + case USB_REQ_GET_CONFIGURATION: + dwc3_decode_get_configuration(wLength, str); + break; + case USB_REQ_SET_CONFIGURATION: + dwc3_decode_set_configuration(wValue, str); + break; + case USB_REQ_GET_INTERFACE: + dwc3_decode_get_intf(wIndex, wLength, str); + break; + case USB_REQ_SET_INTERFACE: + dwc3_decode_set_intf(wValue, wIndex, str); + break; + case USB_REQ_SYNCH_FRAME: + dwc3_decode_synch_frame(wIndex, wLength, str); + break; + case USB_REQ_SET_SEL: + dwc3_decode_set_sel(wLength, str); + break; + case USB_REQ_SET_ISOCH_DELAY: + dwc3_decode_set_isoch_delay(wValue, str); + break; + default: + sprintf(str, "%02x %02x %02x %02x %02x %02x %02x %02x", + bRequestType, bRequest, + cpu_to_le16(wValue) & 0xff, + cpu_to_le16(wValue) >> 8, + cpu_to_le16(wIndex) & 0xff, + cpu_to_le16(wIndex) >> 8, + cpu_to_le16(wLength) & 0xff, + cpu_to_le16(wLength) >> 8); + } + + return str; +} + /** * dwc3_ep_event_string - returns event name * @event: then event code */ static inline const char * -dwc3_ep_event_string(const struct dwc3_event_depevt *event, u32 ep0state) +dwc3_ep_event_string(char *str, const struct dwc3_event_depevt *event, + u32 ep0state) { u8 epnum = event->endpoint_number; - static char str[256]; size_t len; int status; int ret; @@ -332,14 +565,14 @@ static inline const char *dwc3_gadget_event_type_string(u8 event) } } -static inline const char *dwc3_decode_event(u32 event, u32 ep0state) +static inline const char *dwc3_decode_event(char *str, u32 event, u32 ep0state) { const union dwc3_event evt = (union dwc3_event) event; if (evt.type.is_devspec) - return dwc3_gadget_event_string(&evt.devt); + return dwc3_gadget_event_string(str, &evt.devt); else - return dwc3_ep_event_string(&evt.depevt, ep0state); + return dwc3_ep_event_string(str, &evt.depevt, ep0state); } static inline const char *dwc3_ep_cmd_status_string(int status) diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c index 7be963dd8e3b..4e09be80e59f 100644 --- a/drivers/usb/dwc3/debugfs.c +++ b/drivers/usb/dwc3/debugfs.c @@ -653,16 +653,13 @@ static int dwc3_ep_trb_ring_show(struct seq_file *s, void *unused) goto out; } - seq_printf(s, "enqueue pointer %d\n", dep->trb_enqueue); - seq_printf(s, "dequeue pointer %d\n", dep->trb_dequeue); - seq_printf(s, "\n--------------------------------------------------\n\n"); seq_printf(s, "buffer_addr,size,type,ioc,isp_imi,csp,chn,lst,hwo\n"); for (i = 0; i < DWC3_TRB_NUM; i++) { struct dwc3_trb *trb = &dep->trb_pool[i]; unsigned int type = DWC3_TRBCTL_TYPE(trb->ctrl); - seq_printf(s, "%08x%08x,%d,%s,%d,%d,%d,%d,%d,%d\n", + seq_printf(s, "%08x%08x,%d,%s,%d,%d,%d,%d,%d,%d %c%c\n", trb->bph, trb->bpl, trb->size, dwc3_trb_type_string(type), !!(trb->ctrl & DWC3_TRB_CTRL_IOC), @@ -670,7 +667,9 @@ static int dwc3_ep_trb_ring_show(struct seq_file *s, void *unused) !!(trb->ctrl & DWC3_TRB_CTRL_CSP), !!(trb->ctrl & DWC3_TRB_CTRL_CHN), !!(trb->ctrl & DWC3_TRB_CTRL_LST), - !!(trb->ctrl & DWC3_TRB_CTRL_HWO)); + !!(trb->ctrl & DWC3_TRB_CTRL_HWO), + dep->trb_enqueue == i ? 'E' : ' ', + dep->trb_dequeue == i ? 'D' : ' '); } out: diff --git a/drivers/usb/dwc3/dwc3-exynos.c b/drivers/usb/dwc3/dwc3-exynos.c index 98f74ff66120..e089df72f766 100644 --- a/drivers/usb/dwc3/dwc3-exynos.c +++ b/drivers/usb/dwc3/dwc3-exynos.c @@ -125,12 +125,16 @@ static int dwc3_exynos_probe(struct platform_device *pdev) dev_err(dev, "couldn't get clock\n"); return -EINVAL; } - clk_prepare_enable(exynos->clk); + ret = clk_prepare_enable(exynos->clk); + if (ret) + return ret; exynos->susp_clk = devm_clk_get(dev, "usbdrd30_susp_clk"); if (IS_ERR(exynos->susp_clk)) exynos->susp_clk = NULL; - clk_prepare_enable(exynos->susp_clk); + ret = clk_prepare_enable(exynos->susp_clk); + if (ret) + goto susp_clk_err; if (of_device_is_compatible(node, "samsung,exynos7-dwusb3")) { exynos->axius_clk = devm_clk_get(dev, "usbdrd30_axius_clk"); @@ -139,7 +143,9 @@ static int dwc3_exynos_probe(struct platform_device *pdev) ret = -ENODEV; goto axius_clk_err; } - clk_prepare_enable(exynos->axius_clk); + ret = clk_prepare_enable(exynos->axius_clk); + if (ret) + goto axius_clk_err; } else { exynos->axius_clk = NULL; } @@ -197,6 +203,7 @@ vdd33_err: clk_disable_unprepare(exynos->axius_clk); axius_clk_err: clk_disable_unprepare(exynos->susp_clk); +susp_clk_err: clk_disable_unprepare(exynos->clk); return ret; } diff --git a/drivers/usb/dwc3/dwc3-st.c b/drivers/usb/dwc3/dwc3-st.c index dfbf464eb88c..505676fd3ba4 100644 --- a/drivers/usb/dwc3/dwc3-st.c +++ b/drivers/usb/dwc3/dwc3-st.c @@ -230,7 +230,7 @@ static int st_dwc3_probe(struct platform_device *pdev) dwc3_data->syscfg_reg_off = res->start; - dev_vdbg(&pdev->dev, "glue-logic addr 0x%p, syscfg-reg offset 0x%x\n", + dev_vdbg(&pdev->dev, "glue-logic addr 0x%pK, syscfg-reg offset 0x%x\n", dwc3_data->glue_base, dwc3_data->syscfg_reg_off); dwc3_data->rstc_pwrdn = diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index a78c78e7a8c3..827e376bfa97 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -1,4 +1,4 @@ -/** +/* * ep0.c - DesignWare USB3 DRD Controller Endpoint 0 Handling * * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com @@ -319,10 +319,16 @@ static int dwc3_ep0_handle_status(struct dwc3 *dwc, { struct dwc3_ep *dep; u32 recip; + u32 value; u32 reg; u16 usb_status = 0; __le16 *response_pkt; + /* We don't support PTM_STATUS */ + value = le16_to_cpu(ctrl->wValue); + if (value != 0) + return -EINVAL; + recip = ctrl->bRequestType & USB_RECIP_MASK; switch (recip) { case USB_RECIP_DEVICE: diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index aea9a5b948b4..9e41605a276b 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1,4 +1,4 @@ -/** +/* * gadget.c - DesignWare USB3 DRD Controller Gadget Framework Link * * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com @@ -36,13 +36,12 @@ #include "io.h" /** - * dwc3_gadget_set_test_mode - Enables USB2 Test Modes + * dwc3_gadget_set_test_mode - enables usb2 test modes * @dwc: pointer to our context structure * @mode: the mode to set (J, K SE0 NAK, Force Enable) * - * Caller should take care of locking. This function will - * return 0 on success or -EINVAL if wrong Test Selector - * is passed + * Caller should take care of locking. This function will return 0 on + * success or -EINVAL if wrong Test Selector is passed. */ int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode) { @@ -69,7 +68,7 @@ int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode) } /** - * dwc3_gadget_get_link_state - Gets current state of USB Link + * dwc3_gadget_get_link_state - gets current state of usb link * @dwc: pointer to our context structure * * Caller should take care of locking. This function will @@ -85,7 +84,7 @@ int dwc3_gadget_get_link_state(struct dwc3 *dwc) } /** - * dwc3_gadget_set_link_state - Sets USB Link to a particular State + * dwc3_gadget_set_link_state - sets usb link to a particular state * @dwc: pointer to our context structure * @state: the state to put link into * @@ -143,8 +142,8 @@ int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state) } /** - * dwc3_ep_inc_trb() - Increment a TRB index. - * @index - Pointer to the TRB index to increment. + * dwc3_ep_inc_trb - increment a trb index. + * @index: Pointer to the TRB index to increment. * * The index should never point to the link TRB. After incrementing, * if it is point to the link TRB, wrap around to the beginning. The @@ -157,16 +156,34 @@ static void dwc3_ep_inc_trb(u8 *index) *index = 0; } +/** + * dwc3_ep_inc_enq - increment endpoint's enqueue pointer + * @dep: The endpoint whose enqueue pointer we're incrementing + */ static void dwc3_ep_inc_enq(struct dwc3_ep *dep) { dwc3_ep_inc_trb(&dep->trb_enqueue); } +/** + * dwc3_ep_inc_deq - increment endpoint's dequeue pointer + * @dep: The endpoint whose enqueue pointer we're incrementing + */ static void dwc3_ep_inc_deq(struct dwc3_ep *dep) { dwc3_ep_inc_trb(&dep->trb_dequeue); } +/** + * dwc3_gadget_giveback - call struct usb_request's ->complete callback + * @dep: The endpoint to whom the request belongs to + * @req: The request we're giving back + * @status: completion code for the request + * + * Must be called with controller's lock held and interrupts disabled. This + * function will unmap @req and call its ->complete() callback to notify upper + * layers that it has completed. + */ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, int status) { @@ -193,6 +210,15 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, pm_runtime_put(dwc->dev); } +/** + * dwc3_send_gadget_generic_command - issue a generic command for the controller + * @dwc: pointer to the controller context + * @cmd: the command to be issued + * @param: command parameter + * + * Caller should take care of locking. Issue @cmd with a given @param to @dwc + * and wait for its completion. + */ int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param) { u32 timeout = 500; @@ -225,6 +251,15 @@ int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param) static int __dwc3_gadget_wakeup(struct dwc3 *dwc); +/** + * dwc3_send_gadget_ep_cmd - issue an endpoint command + * @dep: the endpoint to which the command is going to be issued + * @cmd: the command to be issued + * @params: parameters to the command + * + * Caller should handle locking. This function will issue @cmd with given + * @params to @dep and wait for its completion. + */ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd, struct dwc3_gadget_ep_cmd_params *params) { @@ -422,36 +457,38 @@ static void dwc3_free_trb_pool(struct dwc3_ep *dep) static int dwc3_gadget_set_xfer_resource(struct dwc3 *dwc, struct dwc3_ep *dep); /** - * dwc3_gadget_start_config - Configure EP resources + * dwc3_gadget_start_config - configure ep resources * @dwc: pointer to our controller context structure * @dep: endpoint that is being enabled * - * The assignment of transfer resources cannot perfectly follow the - * data book due to the fact that the controller driver does not have - * all knowledge of the configuration in advance. It is given this - * information piecemeal by the composite gadget framework after every - * SET_CONFIGURATION and SET_INTERFACE. Trying to follow the databook - * programming model in this scenario can cause errors. For two - * reasons: + * Issue a %DWC3_DEPCMD_DEPSTARTCFG command to @dep. After the command's + * completion, it will set Transfer Resource for all available endpoints. * - * 1) The databook says to do DEPSTARTCFG for every SET_CONFIGURATION - * and SET_INTERFACE (8.1.5). This is incorrect in the scenario of - * multiple interfaces. + * The assignment of transfer resources cannot perfectly follow the data book + * due to the fact that the controller driver does not have all knowledge of the + * configuration in advance. It is given this information piecemeal by the + * composite gadget framework after every SET_CONFIGURATION and + * SET_INTERFACE. Trying to follow the databook programming model in this + * scenario can cause errors. For two reasons: * - * 2) The databook does not mention doing more DEPXFERCFG for new + * 1) The databook says to do %DWC3_DEPCMD_DEPSTARTCFG for every + * %USB_REQ_SET_CONFIGURATION and %USB_REQ_SET_INTERFACE (8.1.5). This is + * incorrect in the scenario of multiple interfaces. + * + * 2) The databook does not mention doing more %DWC3_DEPCMD_DEPXFERCFG for new * endpoint on alt setting (8.1.6). * * The following simplified method is used instead: * - * All hardware endpoints can be assigned a transfer resource and this - * setting will stay persistent until either a core reset or - * hibernation. So whenever we do a DEPSTARTCFG(0) we can go ahead and - * do DEPXFERCFG for every hardware endpoint as well. We are + * All hardware endpoints can be assigned a transfer resource and this setting + * will stay persistent until either a core reset or hibernation. So whenever we + * do a %DWC3_DEPCMD_DEPSTARTCFG(0) we can go ahead and do + * %DWC3_DEPCMD_DEPXFERCFG for every hardware endpoint as well. We are * guaranteed that there are as many transfer resources as endpoints. * - * This function is called for each endpoint when it is being enabled - * but is triggered only when called for EP0-out, which always happens - * first, and which should only happen in one of the above conditions. + * This function is called for each endpoint when it is being enabled but is + * triggered only when called for EP0-out, which always happens first, and which + * should only happen in one of the above conditions. */ static int dwc3_gadget_start_config(struct dwc3 *dwc, struct dwc3_ep *dep) { @@ -569,11 +606,13 @@ static int dwc3_gadget_set_xfer_resource(struct dwc3 *dwc, struct dwc3_ep *dep) } /** - * __dwc3_gadget_ep_enable - Initializes a HW endpoint + * __dwc3_gadget_ep_enable - initializes a hw endpoint * @dep: endpoint to be initialized - * @desc: USB Endpoint Descriptor + * @modify: if true, modify existing endpoint configuration + * @restore: if true, restore endpoint configuration from scratch buffer * - * Caller should take care of locking + * Caller should take care of locking. Execute all necessary commands to + * initialize a HW endpoint so it can be used by a gadget driver. */ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, bool modify, bool restore) @@ -685,11 +724,13 @@ static void dwc3_remove_requests(struct dwc3 *dwc, struct dwc3_ep *dep) } /** - * __dwc3_gadget_ep_disable - Disables a HW endpoint + * __dwc3_gadget_ep_disable - disables a hw endpoint * @dep: the endpoint to disable * - * This function also removes requests which are currently processed ny the - * hardware and those which are not yet scheduled. + * This function undoes what __dwc3_gadget_ep_enable did and also removes + * requests which are currently being processed by the hardware and those which + * are not yet scheduled. + * * Caller should take care of locking. */ static int __dwc3_gadget_ep_disable(struct dwc3_ep *dep) @@ -932,7 +973,7 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep, } /** - * dwc3_ep_prev_trb() - Returns the previous TRB in the ring + * dwc3_ep_prev_trb - returns the previous TRB in the ring * @dep: The endpoint with the TRB ring * @index: The index of the current TRB in the ring * @@ -953,7 +994,6 @@ static struct dwc3_trb *dwc3_ep_prev_trb(struct dwc3_ep *dep, u8 index) static u32 dwc3_calc_trbs_left(struct dwc3_ep *dep) { struct dwc3_trb *tmp; - struct dwc3 *dwc = dep->dwc; u8 trbs_left; /* @@ -965,8 +1005,7 @@ static u32 dwc3_calc_trbs_left(struct dwc3_ep *dep) */ if (dep->trb_enqueue == dep->trb_dequeue) { tmp = dwc3_ep_prev_trb(dep, dep->trb_enqueue); - if (dev_WARN_ONCE(dwc->dev, tmp->ctrl & DWC3_TRB_CTRL_HWO, - "%s No TRBS left\n", dep->name)) + if (tmp->ctrl & DWC3_TRB_CTRL_HWO) return 0; return DWC3_TRB_NUM - 1; @@ -1101,6 +1140,17 @@ static void dwc3_prepare_trbs(struct dwc3_ep *dep) } list_for_each_entry_safe(req, n, &dep->pending_list, list) { + struct dwc3 *dwc = dep->dwc; + int ret; + + ret = usb_gadget_map_request_by_dev(dwc->sysdev, &req->request, + dep->direction); + if (ret) + return; + + req->sg = req->request.sg; + req->num_pending_sgs = req->request.num_mapped_sgs; + if (req->num_pending_sgs > 0) dwc3_prepare_one_trb_sg(dep, req); else @@ -1207,7 +1257,7 @@ static void dwc3_gadget_start_isoc(struct dwc3 *dwc, static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) { struct dwc3 *dwc = dep->dwc; - int ret; + int ret = 0; if (!dep->endpoint.desc) { dev_err(dwc->dev, "%s: can't queue to disabled endpoint\n", @@ -1215,12 +1265,9 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) return -ESHUTDOWN; } - if (WARN(req->dep != dep, "request %p belongs to '%s'\n", - &req->request, req->dep->name)) { - dev_err(dwc->dev, "%s: request %p belongs to '%s'\n", - dep->name, &req->request, req->dep->name); + if (WARN(req->dep != dep, "request %pK belongs to '%s'\n", + &req->request, req->dep->name)) return -EINVAL; - } pm_runtime_get(dwc->dev); @@ -1231,14 +1278,6 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) trace_dwc3_ep_queue(req); - ret = usb_gadget_map_request_by_dev(dwc->sysdev, &req->request, - dep->direction); - if (ret) - return ret; - - req->sg = req->request.sg; - req->num_pending_sgs = req->request.num_mapped_sgs; - list_add_tail(&req->list, &dep->pending_list); /* @@ -1396,7 +1435,7 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep, } goto out1; } - dev_err(dwc->dev, "request %p was not queued to %s\n", + dev_err(dwc->dev, "request %pK was not queued to %s\n", request, ep->name); ret = -EINVAL; goto out0; @@ -1741,8 +1780,8 @@ static irqreturn_t dwc3_interrupt(int irq, void *_dwc); static irqreturn_t dwc3_thread_interrupt(int irq, void *_dwc); /** - * dwc3_gadget_setup_nump - Calculate and initialize NUMP field of DCFG - * dwc: pointer to our context structure + * dwc3_gadget_setup_nump - calculate and initialize NUMP field of %DWC3_DCFG + * @dwc: pointer to our context structure * * The following looks like complex but it's actually very simple. In order to * calculate the number of packets we can burst at once on OUT transfers, we're @@ -1798,49 +1837,6 @@ static int __dwc3_gadget_start(struct dwc3 *dwc) dwc3_writel(dwc->regs, DWC3_DEV_IMOD(0), 0); } - reg = dwc3_readl(dwc->regs, DWC3_DCFG); - reg &= ~(DWC3_DCFG_SPEED_MASK); - - /** - * WORKAROUND: DWC3 revision < 2.20a have an issue - * which would cause metastability state on Run/Stop - * bit if we try to force the IP to USB2-only mode. - * - * Because of that, we cannot configure the IP to any - * speed other than the SuperSpeed - * - * Refers to: - * - * STAR#9000525659: Clock Domain Crossing on DCTL in - * USB 2.0 Mode - */ - if (dwc->revision < DWC3_REVISION_220A) { - reg |= DWC3_DCFG_SUPERSPEED; - } else { - switch (dwc->maximum_speed) { - case USB_SPEED_LOW: - reg |= DWC3_DCFG_LOWSPEED; - break; - case USB_SPEED_FULL: - reg |= DWC3_DCFG_FULLSPEED; - break; - case USB_SPEED_HIGH: - reg |= DWC3_DCFG_HIGHSPEED; - break; - case USB_SPEED_SUPER_PLUS: - reg |= DWC3_DCFG_SUPERSPEED_PLUS; - break; - default: - dev_err(dwc->dev, "invalid dwc->maximum_speed (%d)\n", - dwc->maximum_speed); - /* fall through */ - case USB_SPEED_SUPER: - reg |= DWC3_DCFG_SUPERSPEED; - break; - } - } - dwc3_writel(dwc->regs, DWC3_DCFG, reg); - /* * We are telling dwc3 that we want to use DCFG.NUMP as ACK TP's NUMP * field instead of letting dwc3 itself calculate that automatically. @@ -1972,6 +1968,63 @@ out: return 0; } +static void dwc3_gadget_set_speed(struct usb_gadget *g, + enum usb_device_speed speed) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&dwc->lock, flags); + reg = dwc3_readl(dwc->regs, DWC3_DCFG); + reg &= ~(DWC3_DCFG_SPEED_MASK); + + /* + * WORKAROUND: DWC3 revision < 2.20a have an issue + * which would cause metastability state on Run/Stop + * bit if we try to force the IP to USB2-only mode. + * + * Because of that, we cannot configure the IP to any + * speed other than the SuperSpeed + * + * Refers to: + * + * STAR#9000525659: Clock Domain Crossing on DCTL in + * USB 2.0 Mode + */ + if (dwc->revision < DWC3_REVISION_220A) { + reg |= DWC3_DCFG_SUPERSPEED; + } else { + switch (speed) { + case USB_SPEED_LOW: + reg |= DWC3_DCFG_LOWSPEED; + break; + case USB_SPEED_FULL: + reg |= DWC3_DCFG_FULLSPEED; + break; + case USB_SPEED_HIGH: + reg |= DWC3_DCFG_HIGHSPEED; + break; + case USB_SPEED_SUPER: + reg |= DWC3_DCFG_SUPERSPEED; + break; + case USB_SPEED_SUPER_PLUS: + reg |= DWC3_DCFG_SUPERSPEED_PLUS; + break; + default: + dev_err(dwc->dev, "invalid speed (%d)\n", speed); + + if (dwc->revision & DWC3_REVISION_IS_DWC31) + reg |= DWC3_DCFG_SUPERSPEED_PLUS; + else + reg |= DWC3_DCFG_SUPERSPEED; + } + } + dwc3_writel(dwc->regs, DWC3_DCFG, reg); + + spin_unlock_irqrestore(&dwc->lock, flags); +} + static const struct usb_gadget_ops dwc3_gadget_ops = { .get_frame = dwc3_gadget_get_frame, .wakeup = dwc3_gadget_wakeup, @@ -1979,19 +2032,21 @@ static const struct usb_gadget_ops dwc3_gadget_ops = { .pullup = dwc3_gadget_pullup, .udc_start = dwc3_gadget_start, .udc_stop = dwc3_gadget_stop, + .udc_set_speed = dwc3_gadget_set_speed, }; /* -------------------------------------------------------------------------- */ -static int dwc3_gadget_init_endpoints(struct dwc3 *dwc, u8 num) +static int dwc3_gadget_init_endpoints(struct dwc3 *dwc, u8 total) { struct dwc3_ep *dep; u8 epnum; INIT_LIST_HEAD(&dwc->gadget.ep_list); - for (epnum = 0; epnum < num; epnum++) { + for (epnum = 0; epnum < total; epnum++) { bool direction = epnum & 1; + u8 num = epnum >> 1; dep = kzalloc(sizeof(*dep), GFP_KERNEL); if (!dep) @@ -2003,7 +2058,7 @@ static int dwc3_gadget_init_endpoints(struct dwc3 *dwc, u8 num) dep->regs = dwc->regs + DWC3_DEP_BASE(epnum); dwc->eps[epnum] = dep; - snprintf(dep->name, sizeof(dep->name), "ep%d%s", epnum >> 1, + snprintf(dep->name, sizeof(dep->name), "ep%u%s", num, direction ? "in" : "out"); dep->endpoint.name = dep->name; @@ -2015,39 +2070,39 @@ static int dwc3_gadget_init_endpoints(struct dwc3 *dwc, u8 num) spin_lock_init(&dep->lock); - if (epnum == 0 || epnum == 1) { + if (num == 0) { usb_ep_set_maxpacket_limit(&dep->endpoint, 512); dep->endpoint.maxburst = 1; dep->endpoint.ops = &dwc3_gadget_ep0_ops; - if (!epnum) + if (!direction) dwc->gadget.ep0 = &dep->endpoint; } else if (direction) { int mdwidth; + int kbytes; int size; int ret; - int num; mdwidth = DWC3_MDWIDTH(dwc->hwparams.hwparams0); /* MDWIDTH is represented in bits, we need it in bytes */ mdwidth /= 8; - size = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(epnum >> 1)); + size = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(num)); size = DWC3_GTXFIFOSIZ_TXFDEF(size); /* FIFO Depth is in MDWDITH bytes. Multiply */ size *= mdwidth; - num = size / 1024; - if (num == 0) - num = 1; + kbytes = size / 1024; + if (kbytes == 0) + kbytes = 1; /* - * FIFO sizes account an extra MDWIDTH * (num + 1) bytes for + * FIFO sizes account an extra MDWIDTH * (kbytes + 1) bytes for * internal overhead. We don't really know how these are used, * but documentation say it exists. */ - size -= mdwidth * (num + 1); - size /= num; + size -= mdwidth * (kbytes + 1); + size /= kbytes; usb_ep_set_maxpacket_limit(&dep->endpoint, size); @@ -2073,7 +2128,7 @@ static int dwc3_gadget_init_endpoints(struct dwc3 *dwc, u8 num) return ret; } - if (epnum == 0 || epnum == 1) { + if (num == 0) { dep->endpoint.caps.type_control = true; } else { dep->endpoint.caps.type_iso = true; @@ -2871,7 +2926,7 @@ static void dwc3_gadget_hibernation_interrupt(struct dwc3 *dwc, { unsigned int is_ss = evtinfo & BIT(4); - /** + /* * WORKAROUND: DWC3 revison 2.20a with hibernation support * have a known issue which can cause USB CV TD.9.23 to fail * randomly. @@ -2943,20 +2998,12 @@ static void dwc3_process_event_entry(struct dwc3 *dwc, { trace_dwc3_event(event->raw, dwc); - /* Endpoint IRQ, handle it and return early */ - if (event->type.is_devspec == 0) { - /* depevt */ - return dwc3_endpoint_interrupt(dwc, &event->depevt); - } - - switch (event->type.type) { - case DWC3_EVENT_TYPE_DEV: + if (!event->type.is_devspec) + dwc3_endpoint_interrupt(dwc, &event->depevt); + else if (event->type.type == DWC3_EVENT_TYPE_DEV) dwc3_gadget_interrupt(dwc, &event->devt); - break; - /* REVISIT what to do with Carkit and I2C events ? */ - default: + else dev_err(dwc->dev, "UNKNOWN IRQ type %d\n", event->raw); - } } static irqreturn_t dwc3_process_event_buf(struct dwc3_event_buffer *evt) @@ -3110,7 +3157,7 @@ out: } /** - * dwc3_gadget_init - Initializes gadget related registers + * dwc3_gadget_init - initializes gadget related registers * @dwc: pointer to our controller context structure * * Returns 0 on success otherwise negative errno. diff --git a/drivers/usb/dwc3/gadget.h b/drivers/usb/dwc3/gadget.h index e4602d0e515b..4a3227543255 100644 --- a/drivers/usb/dwc3/gadget.h +++ b/drivers/usb/dwc3/gadget.h @@ -1,4 +1,4 @@ -/** +/* * gadget.h - DesignWare USB3 DRD Gadget Header * * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com @@ -60,11 +60,25 @@ struct dwc3; #define to_dwc3_request(r) (container_of(r, struct dwc3_request, request)) +/** + * next_request - gets the next request on the given list + * @list: the request list to operate on + * + * Caller should take care of locking. This function return %NULL or the first + * request available on @list. + */ static inline struct dwc3_request *next_request(struct list_head *list) { return list_first_entry_or_null(list, struct dwc3_request, list); } +/** + * dwc3_gadget_move_started_request - move @req to the started_list + * @req: the request to be moved + * + * Caller should take care of locking. This function will move @req from its + * current list to the endpoint's started_list. + */ static inline void dwc3_gadget_move_started_request(struct dwc3_request *req) { struct dwc3_ep *dep = req->dep; @@ -87,10 +101,10 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol); /** * dwc3_gadget_ep_get_transfer_index - Gets transfer index from HW - * @dwc: DesignWare USB3 Pointer - * @number: DWC endpoint number + * @dep: dwc3 endpoint * - * Caller should take care of locking + * Caller should take care of locking. Returns the transfer resource + * index for a given endpoint. */ static inline u32 dwc3_gadget_ep_get_transfer_index(struct dwc3_ep *dep) { diff --git a/drivers/usb/dwc3/trace.h b/drivers/usb/dwc3/trace.h index f1bd444d22a3..6504b116da04 100644 --- a/drivers/usb/dwc3/trace.h +++ b/drivers/usb/dwc3/trace.h @@ -60,13 +60,15 @@ DECLARE_EVENT_CLASS(dwc3_log_event, TP_STRUCT__entry( __field(u32, event) __field(u32, ep0state) + __dynamic_array(char, str, DWC3_MSG_MAX) ), TP_fast_assign( __entry->event = event; __entry->ep0state = dwc->ep0state; ), TP_printk("event (%08x): %s", __entry->event, - dwc3_decode_event(__entry->event, __entry->ep0state)) + dwc3_decode_event(__get_str(str), __entry->event, + __entry->ep0state)) ); DEFINE_EVENT(dwc3_log_event, dwc3_event, @@ -83,6 +85,7 @@ DECLARE_EVENT_CLASS(dwc3_log_ctrl, __field(__u16, wValue) __field(__u16, wIndex) __field(__u16, wLength) + __dynamic_array(char, str, DWC3_MSG_MAX) ), TP_fast_assign( __entry->bRequestType = ctrl->bRequestType; @@ -91,10 +94,9 @@ DECLARE_EVENT_CLASS(dwc3_log_ctrl, __entry->wIndex = le16_to_cpu(ctrl->wIndex); __entry->wLength = le16_to_cpu(ctrl->wLength); ), - TP_printk("bRequestType %02x bRequest %02x wValue %04x wIndex %04x wLength %d", - __entry->bRequestType, __entry->bRequest, - __entry->wValue, __entry->wIndex, - __entry->wLength + TP_printk("%s", dwc3_decode_ctrl(__get_str(str), __entry->bRequestType, + __entry->bRequest, __entry->wValue, + __entry->wIndex, __entry->wLength) ) ); @@ -107,7 +109,7 @@ DECLARE_EVENT_CLASS(dwc3_log_request, TP_PROTO(struct dwc3_request *req), TP_ARGS(req), TP_STRUCT__entry( - __dynamic_array(char, name, DWC3_MSG_MAX) + __string(name, req->dep->name) __field(struct dwc3_request *, req) __field(unsigned, actual) __field(unsigned, length) @@ -117,7 +119,7 @@ DECLARE_EVENT_CLASS(dwc3_log_request, __field(int, no_interrupt) ), TP_fast_assign( - snprintf(__get_str(name), DWC3_MSG_MAX, "%s", req->dep->name); + __assign_str(name, req->dep->name); __entry->req = req; __entry->actual = req->request.actual; __entry->length = req->request.length; @@ -190,7 +192,7 @@ DECLARE_EVENT_CLASS(dwc3_log_gadget_ep_cmd, struct dwc3_gadget_ep_cmd_params *params, int cmd_status), TP_ARGS(dep, cmd, params, cmd_status), TP_STRUCT__entry( - __dynamic_array(char, name, DWC3_MSG_MAX) + __string(name, dep->name) __field(unsigned int, cmd) __field(u32, param0) __field(u32, param1) @@ -198,7 +200,7 @@ DECLARE_EVENT_CLASS(dwc3_log_gadget_ep_cmd, __field(int, cmd_status) ), TP_fast_assign( - snprintf(__get_str(name), DWC3_MSG_MAX, "%s", dep->name); + __assign_str(name, dep->name); __entry->cmd = cmd; __entry->param0 = params->param0; __entry->param1 = params->param1; @@ -223,7 +225,7 @@ DECLARE_EVENT_CLASS(dwc3_log_trb, TP_PROTO(struct dwc3_ep *dep, struct dwc3_trb *trb), TP_ARGS(dep, trb), TP_STRUCT__entry( - __dynamic_array(char, name, DWC3_MSG_MAX) + __string(name, dep->name) __field(struct dwc3_trb *, trb) __field(u32, allocated) __field(u32, queued) @@ -234,7 +236,7 @@ DECLARE_EVENT_CLASS(dwc3_log_trb, __field(u32, type) ), TP_fast_assign( - snprintf(__get_str(name), DWC3_MSG_MAX, "%s", dep->name); + __assign_str(name, dep->name); __entry->trb = trb; __entry->allocated = dep->allocated_requests; __entry->queued = dep->queued_requests; @@ -291,7 +293,7 @@ DECLARE_EVENT_CLASS(dwc3_log_ep, TP_PROTO(struct dwc3_ep *dep), TP_ARGS(dep), TP_STRUCT__entry( - __dynamic_array(char, name, DWC3_MSG_MAX) + __string(name, dep->name) __field(unsigned, maxpacket) __field(unsigned, maxpacket_limit) __field(unsigned, max_streams) @@ -302,7 +304,7 @@ DECLARE_EVENT_CLASS(dwc3_log_ep, __field(u8, trb_dequeue) ), TP_fast_assign( - snprintf(__get_str(name), DWC3_MSG_MAX, "%s", dep->name); + __assign_str(name, dep->name); __entry->maxpacket = dep->endpoint.maxpacket; __entry->maxpacket_limit = dep->endpoint.maxpacket_limit; __entry->max_streams = dep->endpoint.max_streams; diff --git a/drivers/usb/dwc3/ulpi.c b/drivers/usb/dwc3/ulpi.c index bd86f84f3790..e87ce8e9edee 100644 --- a/drivers/usb/dwc3/ulpi.c +++ b/drivers/usb/dwc3/ulpi.c @@ -41,6 +41,12 @@ static int dwc3_ulpi_read(struct device *dev, u8 addr) u32 reg; int ret; + reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); + if (reg & DWC3_GUSB2PHYCFG_SUSPHY) { + reg &= ~DWC3_GUSB2PHYCFG_SUSPHY; + dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); + } + reg = DWC3_GUSB2PHYACC_NEWREGREQ | DWC3_ULPI_ADDR(addr); dwc3_writel(dwc->regs, DWC3_GUSB2PHYACC(0), reg); @@ -58,6 +64,12 @@ static int dwc3_ulpi_write(struct device *dev, u8 addr, u8 val) struct dwc3 *dwc = dev_get_drvdata(dev); u32 reg; + reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); + if (reg & DWC3_GUSB2PHYCFG_SUSPHY) { + reg &= ~DWC3_GUSB2PHYCFG_SUSPHY; + dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); + } + reg = DWC3_GUSB2PHYACC_NEWREGREQ | DWC3_ULPI_ADDR(addr); reg |= DWC3_GUSB2PHYACC_WRITE | val; dwc3_writel(dwc->regs, DWC3_GUSB2PHYACC(0), reg); diff --git a/drivers/usb/early/xhci-dbc.c b/drivers/usb/early/xhci-dbc.c index 1268818e2263..12fe70beae69 100644 --- a/drivers/usb/early/xhci-dbc.c +++ b/drivers/usb/early/xhci-dbc.c @@ -32,7 +32,6 @@ static struct xdbc_state xdbc; static bool early_console_keep; -#define XDBC_TRACE #ifdef XDBC_TRACE #define xdbc_trace trace_printk #else diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index c164d6b788c3..35cc641d9f31 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -41,7 +41,7 @@ menuconfig USB_GADGET don't have this kind of hardware (except maybe inside Linux PDAs). For more information, see <http://www.linux-usb.org/gadget> and - the kernel DocBook documentation for this API. + the kernel documentation for this API. if USB_GADGET @@ -158,6 +158,9 @@ config USB_U_SERIAL config USB_U_ETHER tristate +config USB_U_AUDIO + tristate + config USB_F_SERIAL tristate @@ -191,6 +194,9 @@ config USB_F_FS config USB_F_UAC1 tristate +config USB_F_UAC1_LEGACY + tristate + config USB_F_UAC2 tristate @@ -368,12 +374,30 @@ config USB_CONFIGFS_F_UAC1 depends on SND select USB_LIBCOMPOSITE select SND_PCM + select USB_U_AUDIO select USB_F_UAC1 help This Audio function implements 1 AudioControl interface, 1 AudioStreaming Interface each for USB-OUT and USB-IN. - This driver requires a real Audio codec to be present - on the device. + This driver doesn't expect any real Audio codec to be present + on the device - the audio streams are simply sinked to and + sourced from a virtual ALSA sound card created. The user-space + application may choose to do whatever it wants with the data + received from the USB Host and choose to provide whatever it + wants as audio data to the USB Host. + +config USB_CONFIGFS_F_UAC1_LEGACY + bool "Audio Class 1.0 (legacy implementation)" + depends on USB_CONFIGFS + depends on SND + select USB_LIBCOMPOSITE + select SND_PCM + select USB_F_UAC1_LEGACY + help + This Audio function implements 1 AudioControl interface, + 1 AudioStreaming Interface each for USB-OUT and USB-IN. + This is a legacy driver and requires a real Audio codec + to be present on the device. config USB_CONFIGFS_F_UAC2 bool "Audio Class 2.0" @@ -381,6 +405,7 @@ config USB_CONFIGFS_F_UAC2 depends on SND select USB_LIBCOMPOSITE select SND_PCM + select USB_U_AUDIO select USB_F_UAC2 help This Audio function is compatible with USB Audio Class diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 45b554032332..dd74c99d6ce1 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -610,7 +610,6 @@ static int count_configs(struct usb_composite_dev *cdev, unsigned type) static int bos_desc(struct usb_composite_dev *cdev) { struct usb_ext_cap_descriptor *usb_ext; - struct usb_ss_cap_descriptor *ss_cap; struct usb_dcd_config_params dcd_config_params; struct usb_bos_descriptor *bos = cdev->req->buf; @@ -636,29 +635,35 @@ static int bos_desc(struct usb_composite_dev *cdev) * The Superspeed USB Capability descriptor shall be implemented by all * SuperSpeed devices. */ - ss_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength); - bos->bNumDeviceCaps++; - le16_add_cpu(&bos->wTotalLength, USB_DT_USB_SS_CAP_SIZE); - ss_cap->bLength = USB_DT_USB_SS_CAP_SIZE; - ss_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY; - ss_cap->bDevCapabilityType = USB_SS_CAP_TYPE; - ss_cap->bmAttributes = 0; /* LTM is not supported yet */ - ss_cap->wSpeedSupported = cpu_to_le16(USB_LOW_SPEED_OPERATION | - USB_FULL_SPEED_OPERATION | - USB_HIGH_SPEED_OPERATION | - USB_5GBPS_OPERATION); - ss_cap->bFunctionalitySupport = USB_LOW_SPEED_OPERATION; - - /* Get Controller configuration */ - if (cdev->gadget->ops->get_config_params) - cdev->gadget->ops->get_config_params(&dcd_config_params); - else { - dcd_config_params.bU1devExitLat = USB_DEFAULT_U1_DEV_EXIT_LAT; - dcd_config_params.bU2DevExitLat = - cpu_to_le16(USB_DEFAULT_U2_DEV_EXIT_LAT); + if (gadget_is_superspeed(cdev->gadget)) { + struct usb_ss_cap_descriptor *ss_cap; + + ss_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength); + bos->bNumDeviceCaps++; + le16_add_cpu(&bos->wTotalLength, USB_DT_USB_SS_CAP_SIZE); + ss_cap->bLength = USB_DT_USB_SS_CAP_SIZE; + ss_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY; + ss_cap->bDevCapabilityType = USB_SS_CAP_TYPE; + ss_cap->bmAttributes = 0; /* LTM is not supported yet */ + ss_cap->wSpeedSupported = cpu_to_le16(USB_LOW_SPEED_OPERATION | + USB_FULL_SPEED_OPERATION | + USB_HIGH_SPEED_OPERATION | + USB_5GBPS_OPERATION); + ss_cap->bFunctionalitySupport = USB_LOW_SPEED_OPERATION; + + /* Get Controller configuration */ + if (cdev->gadget->ops->get_config_params) { + cdev->gadget->ops->get_config_params( + &dcd_config_params); + } else { + dcd_config_params.bU1devExitLat = + USB_DEFAULT_U1_DEV_EXIT_LAT; + dcd_config_params.bU2DevExitLat = + cpu_to_le16(USB_DEFAULT_U2_DEV_EXIT_LAT); + } + ss_cap->bU1devExitLat = dcd_config_params.bU1devExitLat; + ss_cap->bU2DevExitLat = dcd_config_params.bU2DevExitLat; } - ss_cap->bU1devExitLat = dcd_config_params.bU1devExitLat; - ss_cap->bU2DevExitLat = dcd_config_params.bU2DevExitLat; /* The SuperSpeedPlus USB Device Capability descriptor */ if (gadget_is_superspeed_plus(cdev->gadget)) { @@ -1602,7 +1607,10 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) cdev->desc.bcdUSB = cpu_to_le16(0x0210); } } else { - cdev->desc.bcdUSB = cpu_to_le16(0x0200); + if (gadget->lpm_capable) + cdev->desc.bcdUSB = cpu_to_le16(0x0201); + else + cdev->desc.bcdUSB = cpu_to_le16(0x0200); } value = min(w_length, (u16) sizeof cdev->desc); @@ -1633,7 +1641,8 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) value = min(w_length, (u16) value); break; case USB_DT_BOS: - if (gadget_is_superspeed(gadget)) { + if (gadget_is_superspeed(gadget) || + gadget->lpm_capable) { value = bos_desc(cdev); value = min(w_length, (u16) value); } diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index cbff3b02840d..a22a892de7b7 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -738,7 +738,7 @@ static inline struct gadget_info *os_desc_item_to_gadget_info( static ssize_t os_desc_use_show(struct config_item *item, char *page) { - return sprintf(page, "%d", + return sprintf(page, "%d\n", os_desc_item_to_gadget_info(item)->use_os_desc); } @@ -762,7 +762,7 @@ static ssize_t os_desc_use_store(struct config_item *item, const char *page, static ssize_t os_desc_b_vendor_code_show(struct config_item *item, char *page) { - return sprintf(page, "%d", + return sprintf(page, "0x%02x\n", os_desc_item_to_gadget_info(item)->b_vendor_code); } @@ -787,9 +787,13 @@ static ssize_t os_desc_b_vendor_code_store(struct config_item *item, static ssize_t os_desc_qw_sign_show(struct config_item *item, char *page) { struct gadget_info *gi = os_desc_item_to_gadget_info(item); + int res; + + res = utf16s_to_utf8s((wchar_t *) gi->qw_sign, OS_STRING_QW_SIGN_LEN, + UTF16_LITTLE_ENDIAN, page, PAGE_SIZE - 1); + page[res++] = '\n'; - memcpy(page, gi->qw_sign, OS_STRING_QW_SIGN_LEN); - return OS_STRING_QW_SIGN_LEN; + return res; } static ssize_t os_desc_qw_sign_store(struct config_item *item, const char *page, @@ -900,7 +904,7 @@ static inline struct usb_os_desc_ext_prop static ssize_t ext_prop_type_show(struct config_item *item, char *page) { - return sprintf(page, "%d", to_usb_os_desc_ext_prop(item)->type); + return sprintf(page, "%d\n", to_usb_os_desc_ext_prop(item)->type); } static ssize_t ext_prop_type_store(struct config_item *item, diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile index cb8c225e8549..86e825269947 100644 --- a/drivers/usb/gadget/function/Makefile +++ b/drivers/usb/gadget/function/Makefile @@ -32,8 +32,11 @@ usb_f_mass_storage-y := f_mass_storage.o storage_common.o obj-$(CONFIG_USB_F_MASS_STORAGE)+= usb_f_mass_storage.o usb_f_fs-y := f_fs.o obj-$(CONFIG_USB_F_FS) += usb_f_fs.o -usb_f_uac1-y := f_uac1.o u_uac1.o +obj-$(CONFIG_USB_U_AUDIO) += u_audio.o +usb_f_uac1-y := f_uac1.o obj-$(CONFIG_USB_F_UAC1) += usb_f_uac1.o +usb_f_uac1_legacy-y := f_uac1_legacy.o u_uac1_legacy.o +obj-$(CONFIG_USB_F_UAC1_LEGACY) += usb_f_uac1_legacy.o usb_f_uac2-y := f_uac2.o obj-$(CONFIG_USB_F_UAC2) += usb_f_uac2.o usb_f_uvc-y := f_uvc.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_configfs.o diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 47dda3450abd..d21874b35cf6 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -127,7 +127,6 @@ struct ffs_ep { struct ffs_epfile { /* Protects ep->ep and ep->req. */ struct mutex mutex; - wait_queue_head_t wait; struct ffs_data *ffs; struct ffs_ep *ep; /* P: ffs->eps_lock */ @@ -889,7 +888,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) if (file->f_flags & O_NONBLOCK) return -EAGAIN; - ret = wait_event_interruptible(epfile->wait, (ep = epfile->ep)); + ret = wait_event_interruptible( + epfile->ffs->wait, (ep = epfile->ep)); if (ret) return -EINTR; } @@ -1189,6 +1189,7 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, unsigned long value) { struct ffs_epfile *epfile = file->private_data; + struct ffs_ep *ep; int ret; ENTER(); @@ -1196,50 +1197,65 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) return -ENODEV; + /* Wait for endpoint to be enabled */ + ep = epfile->ep; + if (!ep) { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + ret = wait_event_interruptible( + epfile->ffs->wait, (ep = epfile->ep)); + if (ret) + return -EINTR; + } + spin_lock_irq(&epfile->ffs->eps_lock); - if (likely(epfile->ep)) { - switch (code) { - case FUNCTIONFS_FIFO_STATUS: - ret = usb_ep_fifo_status(epfile->ep->ep); - break; - case FUNCTIONFS_FIFO_FLUSH: - usb_ep_fifo_flush(epfile->ep->ep); - ret = 0; - break; - case FUNCTIONFS_CLEAR_HALT: - ret = usb_ep_clear_halt(epfile->ep->ep); - break; - case FUNCTIONFS_ENDPOINT_REVMAP: - ret = epfile->ep->num; - break; - case FUNCTIONFS_ENDPOINT_DESC: - { - int desc_idx; - struct usb_endpoint_descriptor *desc; - switch (epfile->ffs->gadget->speed) { - case USB_SPEED_SUPER: - desc_idx = 2; - break; - case USB_SPEED_HIGH: - desc_idx = 1; - break; - default: - desc_idx = 0; - } - desc = epfile->ep->descs[desc_idx]; + /* In the meantime, endpoint got disabled or changed. */ + if (epfile->ep != ep) { + spin_unlock_irq(&epfile->ffs->eps_lock); + return -ESHUTDOWN; + } - spin_unlock_irq(&epfile->ffs->eps_lock); - ret = copy_to_user((void *)value, desc, desc->bLength); - if (ret) - ret = -EFAULT; - return ret; - } + switch (code) { + case FUNCTIONFS_FIFO_STATUS: + ret = usb_ep_fifo_status(epfile->ep->ep); + break; + case FUNCTIONFS_FIFO_FLUSH: + usb_ep_fifo_flush(epfile->ep->ep); + ret = 0; + break; + case FUNCTIONFS_CLEAR_HALT: + ret = usb_ep_clear_halt(epfile->ep->ep); + break; + case FUNCTIONFS_ENDPOINT_REVMAP: + ret = epfile->ep->num; + break; + case FUNCTIONFS_ENDPOINT_DESC: + { + int desc_idx; + struct usb_endpoint_descriptor *desc; + + switch (epfile->ffs->gadget->speed) { + case USB_SPEED_SUPER: + desc_idx = 2; + break; + case USB_SPEED_HIGH: + desc_idx = 1; + break; default: - ret = -ENOTTY; + desc_idx = 0; } - } else { - ret = -ENODEV; + desc = epfile->ep->descs[desc_idx]; + + spin_unlock_irq(&epfile->ffs->eps_lock); + ret = copy_to_user((void *)value, desc, desc->bLength); + if (ret) + ret = -EFAULT; + return ret; + } + default: + ret = -ENOTTY; } spin_unlock_irq(&epfile->ffs->eps_lock); @@ -1593,7 +1609,8 @@ static void ffs_data_put(struct ffs_data *ffs) pr_info("%s(): freeing\n", __func__); ffs_data_clear(ffs); BUG_ON(waitqueue_active(&ffs->ev.waitq) || - waitqueue_active(&ffs->ep0req_completion.wait)); + waitqueue_active(&ffs->ep0req_completion.wait) || + waitqueue_active(&ffs->wait)); kfree(ffs->dev_name); kfree(ffs); } @@ -1640,6 +1657,7 @@ static struct ffs_data *ffs_data_new(void) mutex_init(&ffs->mutex); spin_lock_init(&ffs->eps_lock); init_waitqueue_head(&ffs->ev.waitq); + init_waitqueue_head(&ffs->wait); init_completion(&ffs->ep0req_completion); /* XXX REVISIT need to update it in some places, or do we? */ @@ -1761,7 +1779,6 @@ static int ffs_epfiles_create(struct ffs_data *ffs) for (i = 1; i <= count; ++i, ++epfile) { epfile->ffs = ffs; mutex_init(&epfile->mutex); - init_waitqueue_head(&epfile->wait); if (ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR) sprintf(epfile->name, "ep%02x", ffs->eps_addrmap[i]); else @@ -1786,8 +1803,7 @@ static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count) ENTER(); for (; count; --count, ++epfile) { - BUG_ON(mutex_is_locked(&epfile->mutex) || - waitqueue_active(&epfile->wait)); + BUG_ON(mutex_is_locked(&epfile->mutex)); if (epfile->dentry) { d_delete(epfile->dentry); dput(epfile->dentry); @@ -1874,11 +1890,11 @@ static int ffs_func_eps_enable(struct ffs_function *func) break; } - wake_up(&epfile->wait); - ++ep; ++epfile; } + + wake_up_interruptible(&ffs->wait); spin_unlock_irqrestore(&func->ffs->eps_lock, flags); return ret; diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c index 74d57d6994da..e80b9c123a9d 100644 --- a/drivers/usb/gadget/function/f_mass_storage.c +++ b/drivers/usb/gadget/function/f_mass_storage.c @@ -260,12 +260,13 @@ struct fsg_common { struct usb_gadget *gadget; struct usb_composite_dev *cdev; struct fsg_dev *fsg, *new_fsg; + wait_queue_head_t io_wait; wait_queue_head_t fsg_wait; /* filesem protects: backing files in use */ struct rw_semaphore filesem; - /* lock protects: state, all the req_busy's */ + /* lock protects: state and thread_task */ spinlock_t lock; struct usb_ep *ep0; /* Copy of gadget->ep0 */ @@ -303,7 +304,6 @@ struct fsg_common { unsigned int running:1; unsigned int sysfs:1; - int thread_wakeup_needed; struct completion thread_notifier; struct task_struct *thread_task; @@ -355,7 +355,7 @@ typedef void (*fsg_routine_t)(struct fsg_dev *); static int exception_in_progress(struct fsg_common *common) { - return common->state > FSG_STATE_IDLE; + return common->state > FSG_STATE_NORMAL; } /* Make bulk-out requests be divisible by the maxpacket size */ @@ -393,20 +393,6 @@ static int fsg_set_halt(struct fsg_dev *fsg, struct usb_ep *ep) /* These routines may be called in process context or in_irq */ -/* Caller must hold fsg->lock */ -static void wakeup_thread(struct fsg_common *common) -{ - /* - * Ensure the reading of thread_wakeup_needed - * and the writing of bh->state are completed - */ - smp_mb(); - /* Tell the main thread that something has happened */ - common->thread_wakeup_needed = 1; - if (common->thread_task) - wake_up_process(common->thread_task); -} - static void raise_exception(struct fsg_common *common, enum fsg_state new_state) { unsigned long flags; @@ -460,13 +446,9 @@ static void bulk_in_complete(struct usb_ep *ep, struct usb_request *req) if (req->status == -ECONNRESET) /* Request was cancelled */ usb_ep_fifo_flush(ep); - /* Hold the lock while we update the request and buffer states */ - smp_wmb(); - spin_lock(&common->lock); - bh->inreq_busy = 0; - bh->state = BUF_STATE_EMPTY; - wakeup_thread(common); - spin_unlock(&common->lock); + /* Synchronize with the smp_load_acquire() in sleep_thread() */ + smp_store_release(&bh->state, BUF_STATE_EMPTY); + wake_up(&common->io_wait); } static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req) @@ -481,13 +463,9 @@ static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req) if (req->status == -ECONNRESET) /* Request was cancelled */ usb_ep_fifo_flush(ep); - /* Hold the lock while we update the request and buffer states */ - smp_wmb(); - spin_lock(&common->lock); - bh->outreq_busy = 0; - bh->state = BUF_STATE_FULL; - wakeup_thread(common); - spin_unlock(&common->lock); + /* Synchronize with the smp_load_acquire() in sleep_thread() */ + smp_store_release(&bh->state, BUF_STATE_FULL); + wake_up(&common->io_wait); } static int _fsg_common_get_max_lun(struct fsg_common *common) @@ -532,7 +510,7 @@ static int fsg_setup(struct usb_function *f, * and reinitialize our state. */ DBG(fsg, "bulk reset request\n"); - raise_exception(fsg->common, FSG_STATE_RESET); + raise_exception(fsg->common, FSG_STATE_PROTOCOL_RESET); return USB_GADGET_DELAYED_STATUS; case US_BULK_GET_MAX_LUN: @@ -563,43 +541,39 @@ static int fsg_setup(struct usb_function *f, /* All the following routines run in process context */ /* Use this for bulk or interrupt transfers, not ep0 */ -static void start_transfer(struct fsg_dev *fsg, struct usb_ep *ep, - struct usb_request *req, int *pbusy, - enum fsg_buffer_state *state) +static int start_transfer(struct fsg_dev *fsg, struct usb_ep *ep, + struct usb_request *req) { int rc; if (ep == fsg->bulk_in) dump_msg(fsg, "bulk-in", req->buf, req->length); - spin_lock_irq(&fsg->common->lock); - *pbusy = 1; - *state = BUF_STATE_BUSY; - spin_unlock_irq(&fsg->common->lock); - rc = usb_ep_queue(ep, req, GFP_KERNEL); - if (rc == 0) - return; /* All good, we're done */ - - *pbusy = 0; - *state = BUF_STATE_EMPTY; + if (rc) { - /* We can't do much more than wait for a reset */ + /* We can't do much more than wait for a reset */ + req->status = rc; - /* - * Note: currently the net2280 driver fails zero-length - * submissions if DMA is enabled. - */ - if (rc != -ESHUTDOWN && !(rc == -EOPNOTSUPP && req->length == 0)) - WARNING(fsg, "error in submission: %s --> %d\n", ep->name, rc); + /* + * Note: currently the net2280 driver fails zero-length + * submissions if DMA is enabled. + */ + if (rc != -ESHUTDOWN && + !(rc == -EOPNOTSUPP && req->length == 0)) + WARNING(fsg, "error in submission: %s --> %d\n", + ep->name, rc); + } + return rc; } static bool start_in_transfer(struct fsg_common *common, struct fsg_buffhd *bh) { if (!fsg_is_set(common)) return false; - start_transfer(common->fsg, common->fsg->bulk_in, - bh->inreq, &bh->inreq_busy, &bh->state); + bh->state = BUF_STATE_SENDING; + if (start_transfer(common->fsg, common->fsg->bulk_in, bh->inreq)) + bh->state = BUF_STATE_EMPTY; return true; } @@ -607,37 +581,31 @@ static bool start_out_transfer(struct fsg_common *common, struct fsg_buffhd *bh) { if (!fsg_is_set(common)) return false; - start_transfer(common->fsg, common->fsg->bulk_out, - bh->outreq, &bh->outreq_busy, &bh->state); + bh->state = BUF_STATE_RECEIVING; + if (start_transfer(common->fsg, common->fsg->bulk_out, bh->outreq)) + bh->state = BUF_STATE_FULL; return true; } -static int sleep_thread(struct fsg_common *common, bool can_freeze) +static int sleep_thread(struct fsg_common *common, bool can_freeze, + struct fsg_buffhd *bh) { - int rc = 0; - - /* Wait until a signal arrives or we are woken up */ - for (;;) { - if (can_freeze) - try_to_freeze(); - set_current_state(TASK_INTERRUPTIBLE); - if (signal_pending(current)) { - rc = -EINTR; - break; - } - if (common->thread_wakeup_needed) - break; - schedule(); - } - __set_current_state(TASK_RUNNING); - common->thread_wakeup_needed = 0; + int rc; - /* - * Ensure the writing of thread_wakeup_needed - * and the reading of bh->state are completed - */ - smp_mb(); - return rc; + /* Wait until a signal arrives or bh is no longer busy */ + if (can_freeze) + /* + * synchronize with the smp_store_release(&bh->state) in + * bulk_in_complete() or bulk_out_complete() + */ + rc = wait_event_freezable(common->io_wait, + bh && smp_load_acquire(&bh->state) >= + BUF_STATE_EMPTY); + else + rc = wait_event_interruptible(common->io_wait, + bh && smp_load_acquire(&bh->state) >= + BUF_STATE_EMPTY); + return rc ? -EINTR : 0; } @@ -697,11 +665,9 @@ static int do_read(struct fsg_common *common) /* Wait for the next buffer to become available */ bh = common->next_buffhd_to_fill; - while (bh->state != BUF_STATE_EMPTY) { - rc = sleep_thread(common, false); - if (rc) - return rc; - } + rc = sleep_thread(common, false, bh); + if (rc) + return rc; /* * If we were asked to read past the end of file, @@ -878,84 +844,80 @@ static int do_write(struct fsg_common *common) bh = common->next_buffhd_to_drain; if (bh->state == BUF_STATE_EMPTY && !get_some_more) break; /* We stopped early */ - if (bh->state == BUF_STATE_FULL) { - smp_rmb(); - common->next_buffhd_to_drain = bh->next; - bh->state = BUF_STATE_EMPTY; - - /* Did something go wrong with the transfer? */ - if (bh->outreq->status != 0) { - curlun->sense_data = SS_COMMUNICATION_FAILURE; - curlun->sense_data_info = + + /* Wait for the data to be received */ + rc = sleep_thread(common, false, bh); + if (rc) + return rc; + + common->next_buffhd_to_drain = bh->next; + bh->state = BUF_STATE_EMPTY; + + /* Did something go wrong with the transfer? */ + if (bh->outreq->status != 0) { + curlun->sense_data = SS_COMMUNICATION_FAILURE; + curlun->sense_data_info = file_offset >> curlun->blkbits; - curlun->info_valid = 1; - break; - } + curlun->info_valid = 1; + break; + } - amount = bh->outreq->actual; - if (curlun->file_length - file_offset < amount) { - LERROR(curlun, - "write %u @ %llu beyond end %llu\n", + amount = bh->outreq->actual; + if (curlun->file_length - file_offset < amount) { + LERROR(curlun, "write %u @ %llu beyond end %llu\n", amount, (unsigned long long)file_offset, (unsigned long long)curlun->file_length); - amount = curlun->file_length - file_offset; - } + amount = curlun->file_length - file_offset; + } - /* Don't accept excess data. The spec doesn't say - * what to do in this case. We'll ignore the error. - */ - amount = min(amount, bh->bulk_out_intended_length); - - /* Don't write a partial block */ - amount = round_down(amount, curlun->blksize); - if (amount == 0) - goto empty_write; - - /* Perform the write */ - file_offset_tmp = file_offset; - nwritten = vfs_write(curlun->filp, - (char __user *)bh->buf, - amount, &file_offset_tmp); - VLDBG(curlun, "file write %u @ %llu -> %d\n", amount, - (unsigned long long)file_offset, (int)nwritten); - if (signal_pending(current)) - return -EINTR; /* Interrupted! */ - - if (nwritten < 0) { - LDBG(curlun, "error in file write: %d\n", - (int)nwritten); - nwritten = 0; - } else if (nwritten < amount) { - LDBG(curlun, "partial file write: %d/%u\n", - (int)nwritten, amount); - nwritten = round_down(nwritten, curlun->blksize); - } - file_offset += nwritten; - amount_left_to_write -= nwritten; - common->residue -= nwritten; + /* + * Don't accept excess data. The spec doesn't say + * what to do in this case. We'll ignore the error. + */ + amount = min(amount, bh->bulk_out_intended_length); - /* If an error occurred, report it and its position */ - if (nwritten < amount) { - curlun->sense_data = SS_WRITE_ERROR; - curlun->sense_data_info = + /* Don't write a partial block */ + amount = round_down(amount, curlun->blksize); + if (amount == 0) + goto empty_write; + + /* Perform the write */ + file_offset_tmp = file_offset; + nwritten = vfs_write(curlun->filp, (char __user *)bh->buf, + amount, &file_offset_tmp); + VLDBG(curlun, "file write %u @ %llu -> %d\n", amount, + (unsigned long long)file_offset, (int)nwritten); + if (signal_pending(current)) + return -EINTR; /* Interrupted! */ + + if (nwritten < 0) { + LDBG(curlun, "error in file write: %d\n", + (int) nwritten); + nwritten = 0; + } else if (nwritten < amount) { + LDBG(curlun, "partial file write: %d/%u\n", + (int) nwritten, amount); + nwritten = round_down(nwritten, curlun->blksize); + } + file_offset += nwritten; + amount_left_to_write -= nwritten; + common->residue -= nwritten; + + /* If an error occurred, report it and its position */ + if (nwritten < amount) { + curlun->sense_data = SS_WRITE_ERROR; + curlun->sense_data_info = file_offset >> curlun->blkbits; - curlun->info_valid = 1; - break; - } + curlun->info_valid = 1; + break; + } empty_write: - /* Did the host decide to stop early? */ - if (bh->outreq->actual < bh->bulk_out_intended_length) { - common->short_packet_received = 1; - break; - } - continue; + /* Did the host decide to stop early? */ + if (bh->outreq->actual < bh->bulk_out_intended_length) { + common->short_packet_received = 1; + break; } - - /* Wait for something to happen */ - rc = sleep_thread(common, false); - if (rc) - return rc; } return -EIO; /* No default reply */ @@ -1480,7 +1442,7 @@ static int wedge_bulk_in_endpoint(struct fsg_dev *fsg) static int throw_away_data(struct fsg_common *common) { - struct fsg_buffhd *bh; + struct fsg_buffhd *bh, *bh2; u32 amount; int rc; @@ -1488,26 +1450,10 @@ static int throw_away_data(struct fsg_common *common) bh->state != BUF_STATE_EMPTY || common->usb_amount_left > 0; bh = common->next_buffhd_to_drain) { - /* Throw away the data in a filled buffer */ - if (bh->state == BUF_STATE_FULL) { - smp_rmb(); - bh->state = BUF_STATE_EMPTY; - common->next_buffhd_to_drain = bh->next; - - /* A short packet or an error ends everything */ - if (bh->outreq->actual < bh->bulk_out_intended_length || - bh->outreq->status != 0) { - raise_exception(common, - FSG_STATE_ABORT_BULK_OUT); - return -EINTR; - } - continue; - } - /* Try to submit another request if we need one */ - bh = common->next_buffhd_to_fill; - if (bh->state == BUF_STATE_EMPTY - && common->usb_amount_left > 0) { + bh2 = common->next_buffhd_to_fill; + if (bh2->state == BUF_STATE_EMPTY && + common->usb_amount_left > 0) { amount = min(common->usb_amount_left, FSG_BUFLEN); /* @@ -1515,19 +1461,30 @@ static int throw_away_data(struct fsg_common *common) * equal to the buffer size, which is divisible by * the bulk-out maxpacket size. */ - set_bulk_out_req_length(common, bh, amount); - if (!start_out_transfer(common, bh)) + set_bulk_out_req_length(common, bh2, amount); + if (!start_out_transfer(common, bh2)) /* Dunno what to do if common->fsg is NULL */ return -EIO; - common->next_buffhd_to_fill = bh->next; + common->next_buffhd_to_fill = bh2->next; common->usb_amount_left -= amount; continue; } - /* Otherwise wait for something to happen */ - rc = sleep_thread(common, true); + /* Wait for the data to be received */ + rc = sleep_thread(common, false, bh); if (rc) return rc; + + /* Throw away the data in a filled buffer */ + bh->state = BUF_STATE_EMPTY; + common->next_buffhd_to_drain = bh->next; + + /* A short packet or an error ends everything */ + if (bh->outreq->actual < bh->bulk_out_intended_length || + bh->outreq->status != 0) { + raise_exception(common, FSG_STATE_ABORT_BULK_OUT); + return -EINTR; + } } return 0; } @@ -1634,7 +1591,7 @@ static int finish_reply(struct fsg_common *common) return rc; } -static int send_status(struct fsg_common *common) +static void send_status(struct fsg_common *common) { struct fsg_lun *curlun = common->curlun; struct fsg_buffhd *bh; @@ -1645,11 +1602,9 @@ static int send_status(struct fsg_common *common) /* Wait for the next buffer to become available */ bh = common->next_buffhd_to_fill; - while (bh->state != BUF_STATE_EMPTY) { - rc = sleep_thread(common, true); - if (rc) - return rc; - } + rc = sleep_thread(common, false, bh); + if (rc) + return; if (curlun) { sd = curlun->sense_data; @@ -1683,10 +1638,10 @@ static int send_status(struct fsg_common *common) bh->inreq->zero = 0; if (!start_in_transfer(common, bh)) /* Don't know what to do if common->fsg is NULL */ - return -EIO; + return; common->next_buffhd_to_fill = bh->next; - return 0; + return; } @@ -1848,11 +1803,10 @@ static int do_scsi_command(struct fsg_common *common) /* Wait for the next buffer to become available for data or status */ bh = common->next_buffhd_to_fill; common->next_buffhd_to_drain = bh; - while (bh->state != BUF_STATE_EMPTY) { - rc = sleep_thread(common, true); - if (rc) - return rc; - } + rc = sleep_thread(common, false, bh); + if (rc) + return rc; + common->phase_error = 0; common->short_packet_received = 0; @@ -2195,11 +2149,9 @@ static int get_next_command(struct fsg_common *common) /* Wait for the next buffer to become available */ bh = common->next_buffhd_to_fill; - while (bh->state != BUF_STATE_EMPTY) { - rc = sleep_thread(common, true); - if (rc) - return rc; - } + rc = sleep_thread(common, true, bh); + if (rc) + return rc; /* Queue a request to read a Bulk-only CBW */ set_bulk_out_req_length(common, bh, US_BULK_CB_WRAP_LEN); @@ -2214,12 +2166,10 @@ static int get_next_command(struct fsg_common *common) */ /* Wait for the CBW to arrive */ - while (bh->state != BUF_STATE_FULL) { - rc = sleep_thread(common, true); - if (rc) - return rc; - } - smp_rmb(); + rc = sleep_thread(common, true, bh); + if (rc) + return rc; + rc = fsg_is_set(common) ? received_cbw(common->fsg, bh) : -EIO; bh->state = BUF_STATE_EMPTY; @@ -2371,9 +2321,11 @@ static void handle_exception(struct fsg_common *common) if (!sig) break; if (sig != SIGUSR1) { + spin_lock_irq(&common->lock); if (common->state < FSG_STATE_EXIT) DBG(common, "Main thread exiting on signal\n"); - raise_exception(common, FSG_STATE_EXIT); + common->state = FSG_STATE_EXIT; + spin_unlock_irq(&common->lock); } } @@ -2381,23 +2333,14 @@ static void handle_exception(struct fsg_common *common) if (likely(common->fsg)) { for (i = 0; i < common->fsg_num_buffers; ++i) { bh = &common->buffhds[i]; - if (bh->inreq_busy) + if (bh->state == BUF_STATE_SENDING) usb_ep_dequeue(common->fsg->bulk_in, bh->inreq); - if (bh->outreq_busy) + if (bh->state == BUF_STATE_RECEIVING) usb_ep_dequeue(common->fsg->bulk_out, bh->outreq); - } - /* Wait until everything is idle */ - for (;;) { - int num_active = 0; - for (i = 0; i < common->fsg_num_buffers; ++i) { - bh = &common->buffhds[i]; - num_active += bh->inreq_busy + bh->outreq_busy; - } - if (num_active == 0) - break; - if (sleep_thread(common, true)) + /* Wait for a transfer to become idle */ + if (sleep_thread(common, false, bh)) return; } @@ -2422,10 +2365,9 @@ static void handle_exception(struct fsg_common *common) common->next_buffhd_to_drain = &common->buffhds[0]; exception_req_tag = common->exception_req_tag; old_state = common->state; + common->state = FSG_STATE_NORMAL; - if (old_state == FSG_STATE_ABORT_BULK_OUT) - common->state = FSG_STATE_STATUS_PHASE; - else { + if (old_state != FSG_STATE_ABORT_BULK_OUT) { for (i = 0; i < ARRAY_SIZE(common->luns); ++i) { curlun = common->luns[i]; if (!curlun) @@ -2436,21 +2378,19 @@ static void handle_exception(struct fsg_common *common) curlun->sense_data_info = 0; curlun->info_valid = 0; } - common->state = FSG_STATE_IDLE; } spin_unlock_irq(&common->lock); /* Carry out any extra actions required for the exception */ switch (old_state) { + case FSG_STATE_NORMAL: + break; + case FSG_STATE_ABORT_BULK_OUT: send_status(common); - spin_lock_irq(&common->lock); - if (common->state == FSG_STATE_STATUS_PHASE) - common->state = FSG_STATE_IDLE; - spin_unlock_irq(&common->lock); break; - case FSG_STATE_RESET: + case FSG_STATE_PROTOCOL_RESET: /* * In case we were forced against our will to halt a * bulk endpoint, clear the halt now. (The SuperH UDC @@ -2483,19 +2423,13 @@ static void handle_exception(struct fsg_common *common) break; case FSG_STATE_EXIT: - case FSG_STATE_TERMINATED: do_set_interface(common, NULL); /* Free resources */ spin_lock_irq(&common->lock); common->state = FSG_STATE_TERMINATED; /* Stop the thread */ spin_unlock_irq(&common->lock); break; - case FSG_STATE_INTERFACE_CHANGE: - case FSG_STATE_DISCONNECT: - case FSG_STATE_COMMAND_PHASE: - case FSG_STATE_DATA_PHASE: - case FSG_STATE_STATUS_PHASE: - case FSG_STATE_IDLE: + case FSG_STATE_TERMINATED: break; } } @@ -2534,33 +2468,17 @@ static int fsg_main_thread(void *common_) } if (!common->running) { - sleep_thread(common, true); + sleep_thread(common, true, NULL); continue; } - if (get_next_command(common)) + if (get_next_command(common) || exception_in_progress(common)) continue; - - spin_lock_irq(&common->lock); - if (!exception_in_progress(common)) - common->state = FSG_STATE_DATA_PHASE; - spin_unlock_irq(&common->lock); - - if (do_scsi_command(common) || finish_reply(common)) + if (do_scsi_command(common) || exception_in_progress(common)) continue; - - spin_lock_irq(&common->lock); - if (!exception_in_progress(common)) - common->state = FSG_STATE_STATUS_PHASE; - spin_unlock_irq(&common->lock); - - if (send_status(common)) + if (finish_reply(common) || exception_in_progress(common)) continue; - - spin_lock_irq(&common->lock); - if (!exception_in_progress(common)) - common->state = FSG_STATE_IDLE; - spin_unlock_irq(&common->lock); + send_status(common); } spin_lock_irq(&common->lock); @@ -2680,6 +2598,7 @@ static struct fsg_common *fsg_common_setup(struct fsg_common *common) spin_lock_init(&common->lock); kref_init(&common->ref); init_completion(&common->thread_notifier); + init_waitqueue_head(&common->io_wait); init_waitqueue_head(&common->fsg_wait); common->state = FSG_STATE_TERMINATED; memset(common->luns, 0, sizeof(common->luns)); @@ -2981,7 +2900,6 @@ static void fsg_common_release(struct kref *ref) if (common->state != FSG_STATE_TERMINATED) { raise_exception(common, FSG_STATE_EXIT); wait_for_completion(&common->thread_notifier); - common->thread_task = NULL; } for (i = 0; i < ARRAY_SIZE(common->luns); ++i) { @@ -3030,11 +2948,11 @@ static int fsg_bind(struct usb_configuration *c, struct usb_function *f) } if (!common->thread_task) { - common->state = FSG_STATE_IDLE; + common->state = FSG_STATE_NORMAL; common->thread_task = kthread_create(fsg_main_thread, common, "file-storage"); if (IS_ERR(common->thread_task)) { - int ret = PTR_ERR(common->thread_task); + ret = PTR_ERR(common->thread_task); common->thread_task = NULL; common->state = FSG_STATE_TERMINATED; return ret; diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c index f2ac0cbc29a4..8656f84e17d9 100644 --- a/drivers/usb/gadget/function/f_uac1.c +++ b/drivers/usb/gadget/function/f_uac1.c @@ -1,24 +1,38 @@ /* - * f_audio.c -- USB Audio class function driver - * - * Copyright (C) 2008 Bryan Wu <cooloney@kernel.org> - * Copyright (C) 2008 Analog Devices, Inc + * f_uac1.c -- USB Audio Class 1.0 Function (using u_audio API) * - * Enter bugs at http://blackfin.uclinux.org/ + * Copyright (C) 2016 Ruslan Bilovol <ruslan.bilovol@gmail.com> * - * Licensed under the GPL-2 or later. + * This driver doesn't expect any real Audio codec to be present + * on the device - the audio streams are simply sinked to and + * sourced from a virtual ALSA sound card created. + * + * This file is based on f_uac1.c which is + * Copyright (C) 2008 Bryan Wu <cooloney@kernel.org> + * Copyright (C) 2008 Analog Devices, Inc + * + * 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 <linux/slab.h> -#include <linux/kernel.h> +#include <linux/usb/audio.h> #include <linux/module.h> -#include <linux/device.h> -#include <linux/atomic.h> +#include "u_audio.h" #include "u_uac1.h" -static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value); -static int generic_get_cmd(struct usb_audio_control *con, u8 cmd); +struct f_uac1 { + struct g_audio g_audio; + u8 ac_intf, as_in_intf, as_out_intf; + u8 ac_alt, as_in_alt, as_out_alt; /* needed for get_alt() */ +}; + +static inline struct f_uac1 *func_to_uac1(struct usb_function *f) +{ + return container_of(f, struct f_uac1, g_audio.func); +} /* * DESCRIPTORS ... most are static, but strings and full @@ -26,12 +40,17 @@ static int generic_get_cmd(struct usb_audio_control *con, u8 cmd); */ /* - * We have two interfaces- AudioControl and AudioStreaming - * TODO: only supcard playback currently + * We have three interfaces - one AudioControl and two AudioStreaming + * + * The driver implements a simple UAC_1 topology. + * USB-OUT -> IT_1 -> OT_2 -> ALSA_Capture + * ALSA_Playback -> IT_3 -> OT_4 -> USB-IN */ -#define F_AUDIO_AC_INTERFACE 0 -#define F_AUDIO_AS_INTERFACE 1 -#define F_AUDIO_NUM_INTERFACES 1 +#define F_AUDIO_AC_INTERFACE 0 +#define F_AUDIO_AS_OUT_INTERFACE 1 +#define F_AUDIO_AS_IN_INTERFACE 2 +/* Number of streaming interfaces */ +#define F_AUDIO_NUM_INTERFACES 2 /* B.3.1 Standard AC Interface Descriptor */ static struct usb_interface_descriptor ac_interface_desc = { @@ -46,89 +65,91 @@ static struct usb_interface_descriptor ac_interface_desc = { * The number of AudioStreaming and MIDIStreaming interfaces * in the Audio Interface Collection */ -DECLARE_UAC_AC_HEADER_DESCRIPTOR(1); +DECLARE_UAC_AC_HEADER_DESCRIPTOR(2); #define UAC_DT_AC_HEADER_LENGTH UAC_DT_AC_HEADER_SIZE(F_AUDIO_NUM_INTERFACES) -/* 1 input terminal, 1 output terminal and 1 feature unit */ -#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH + UAC_DT_INPUT_TERMINAL_SIZE \ - + UAC_DT_OUTPUT_TERMINAL_SIZE + UAC_DT_FEATURE_UNIT_SIZE(0)) +/* 2 input terminals and 2 output terminals */ +#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH \ + + 2*UAC_DT_INPUT_TERMINAL_SIZE + 2*UAC_DT_OUTPUT_TERMINAL_SIZE) /* B.3.2 Class-Specific AC Interface Descriptor */ -static struct uac1_ac_header_descriptor_1 ac_header_desc = { +static struct uac1_ac_header_descriptor_2 ac_header_desc = { .bLength = UAC_DT_AC_HEADER_LENGTH, .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubtype = UAC_HEADER, - .bcdADC = __constant_cpu_to_le16(0x0100), - .wTotalLength = __constant_cpu_to_le16(UAC_DT_TOTAL_LENGTH), + .bcdADC = cpu_to_le16(0x0100), + .wTotalLength = cpu_to_le16(UAC_DT_TOTAL_LENGTH), .bInCollection = F_AUDIO_NUM_INTERFACES, .baInterfaceNr = { - /* Interface number of the first AudioStream interface */ + /* Interface number of the AudioStream interfaces */ [0] = 1, + [1] = 2, } }; -#define INPUT_TERMINAL_ID 1 -static struct uac_input_terminal_descriptor input_terminal_desc = { +#define USB_OUT_IT_ID 1 +static struct uac_input_terminal_descriptor usb_out_it_desc = { .bLength = UAC_DT_INPUT_TERMINAL_SIZE, .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubtype = UAC_INPUT_TERMINAL, - .bTerminalID = INPUT_TERMINAL_ID, + .bTerminalID = USB_OUT_IT_ID, .wTerminalType = UAC_TERMINAL_STREAMING, .bAssocTerminal = 0, .wChannelConfig = 0x3, }; -DECLARE_UAC_FEATURE_UNIT_DESCRIPTOR(0); - -#define FEATURE_UNIT_ID 2 -static struct uac_feature_unit_descriptor_0 feature_unit_desc = { - .bLength = UAC_DT_FEATURE_UNIT_SIZE(0), +#define IO_OUT_OT_ID 2 +static struct uac1_output_terminal_descriptor io_out_ot_desc = { + .bLength = UAC_DT_OUTPUT_TERMINAL_SIZE, .bDescriptorType = USB_DT_CS_INTERFACE, - .bDescriptorSubtype = UAC_FEATURE_UNIT, - .bUnitID = FEATURE_UNIT_ID, - .bSourceID = INPUT_TERMINAL_ID, - .bControlSize = 2, - .bmaControls[0] = (UAC_FU_MUTE | UAC_FU_VOLUME), + .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, + .bTerminalID = IO_OUT_OT_ID, + .wTerminalType = UAC_OUTPUT_TERMINAL_SPEAKER, + .bAssocTerminal = 0, + .bSourceID = USB_OUT_IT_ID, }; -static struct usb_audio_control mute_control = { - .list = LIST_HEAD_INIT(mute_control.list), - .name = "Mute Control", - .type = UAC_FU_MUTE, - /* Todo: add real Mute control code */ - .set = generic_set_cmd, - .get = generic_get_cmd, +#define IO_IN_IT_ID 3 +static struct uac_input_terminal_descriptor io_in_it_desc = { + .bLength = UAC_DT_INPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_INPUT_TERMINAL, + .bTerminalID = IO_IN_IT_ID, + .wTerminalType = UAC_INPUT_TERMINAL_MICROPHONE, + .bAssocTerminal = 0, + .wChannelConfig = 0x3, }; -static struct usb_audio_control volume_control = { - .list = LIST_HEAD_INIT(volume_control.list), - .name = "Volume Control", - .type = UAC_FU_VOLUME, - /* Todo: add real Volume control code */ - .set = generic_set_cmd, - .get = generic_get_cmd, +#define USB_IN_OT_ID 4 +static struct uac1_output_terminal_descriptor usb_in_ot_desc = { + .bLength = UAC_DT_OUTPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, + .bTerminalID = USB_IN_OT_ID, + .wTerminalType = UAC_TERMINAL_STREAMING, + .bAssocTerminal = 0, + .bSourceID = IO_IN_IT_ID, }; -static struct usb_audio_control_selector feature_unit = { - .list = LIST_HEAD_INIT(feature_unit.list), - .id = FEATURE_UNIT_ID, - .name = "Mute & Volume Control", - .type = UAC_FEATURE_UNIT, - .desc = (struct usb_descriptor_header *)&feature_unit_desc, +/* B.4.1 Standard AS Interface Descriptor */ +static struct usb_interface_descriptor as_out_interface_alt_0_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, }; -#define OUTPUT_TERMINAL_ID 3 -static struct uac1_output_terminal_descriptor output_terminal_desc = { - .bLength = UAC_DT_OUTPUT_TERMINAL_SIZE, - .bDescriptorType = USB_DT_CS_INTERFACE, - .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, - .bTerminalID = OUTPUT_TERMINAL_ID, - .wTerminalType = UAC_OUTPUT_TERMINAL_SPEAKER, - .bAssocTerminal = FEATURE_UNIT_ID, - .bSourceID = FEATURE_UNIT_ID, +static struct usb_interface_descriptor as_out_interface_alt_1_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 1, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, }; -/* B.4.1 Standard AS Interface Descriptor */ -static struct usb_interface_descriptor as_interface_alt_0_desc = { +static struct usb_interface_descriptor as_in_interface_alt_0_desc = { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, .bAlternateSetting = 0, @@ -137,7 +158,7 @@ static struct usb_interface_descriptor as_interface_alt_0_desc = { .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, }; -static struct usb_interface_descriptor as_interface_alt_1_desc = { +static struct usb_interface_descriptor as_in_interface_alt_1_desc = { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, .bAlternateSetting = 1, @@ -147,18 +168,27 @@ static struct usb_interface_descriptor as_interface_alt_1_desc = { }; /* B.4.2 Class-Specific AS Interface Descriptor */ -static struct uac1_as_header_descriptor as_header_desc = { +static struct uac1_as_header_descriptor as_out_header_desc = { .bLength = UAC_DT_AS_HEADER_SIZE, .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubtype = UAC_AS_GENERAL, - .bTerminalLink = INPUT_TERMINAL_ID, + .bTerminalLink = USB_OUT_IT_ID, + .bDelay = 1, + .wFormatTag = UAC_FORMAT_TYPE_I_PCM, +}; + +static struct uac1_as_header_descriptor as_in_header_desc = { + .bLength = UAC_DT_AS_HEADER_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_AS_GENERAL, + .bTerminalLink = USB_IN_OT_ID, .bDelay = 1, .wFormatTag = UAC_FORMAT_TYPE_I_PCM, }; DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1); -static struct uac_format_type_i_discrete_descriptor_1 as_type_i_desc = { +static struct uac_format_type_i_discrete_descriptor_1 as_out_type_i_desc = { .bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1), .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubtype = UAC_FORMAT_TYPE, @@ -184,48 +214,97 @@ static struct uac_iso_endpoint_descriptor as_iso_out_desc = { .bLength = UAC_ISO_ENDPOINT_DESC_SIZE, .bDescriptorType = USB_DT_CS_ENDPOINT, .bDescriptorSubtype = UAC_EP_GENERAL, - .bmAttributes = 1, + .bmAttributes = 1, .bLockDelayUnits = 1, - .wLockDelay = __constant_cpu_to_le16(1), + .wLockDelay = cpu_to_le16(1), +}; + +static struct uac_format_type_i_discrete_descriptor_1 as_in_type_i_desc = { + .bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_FORMAT_TYPE, + .bFormatType = UAC_FORMAT_TYPE_I, + .bSubframeSize = 2, + .bBitResolution = 16, + .bSamFreqType = 1, +}; + +/* Standard ISO OUT Endpoint Descriptor */ +static struct usb_endpoint_descriptor as_in_ep_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_SYNC_ASYNC + | USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE), + .bInterval = 4, +}; + +/* Class-specific AS ISO OUT Endpoint Descriptor */ +static struct uac_iso_endpoint_descriptor as_iso_in_desc = { + .bLength = UAC_ISO_ENDPOINT_DESC_SIZE, + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubtype = UAC_EP_GENERAL, + .bmAttributes = 1, + .bLockDelayUnits = 0, + .wLockDelay = 0, }; static struct usb_descriptor_header *f_audio_desc[] = { (struct usb_descriptor_header *)&ac_interface_desc, (struct usb_descriptor_header *)&ac_header_desc, - (struct usb_descriptor_header *)&input_terminal_desc, - (struct usb_descriptor_header *)&output_terminal_desc, - (struct usb_descriptor_header *)&feature_unit_desc, + (struct usb_descriptor_header *)&usb_out_it_desc, + (struct usb_descriptor_header *)&io_out_ot_desc, + (struct usb_descriptor_header *)&io_in_it_desc, + (struct usb_descriptor_header *)&usb_in_ot_desc, - (struct usb_descriptor_header *)&as_interface_alt_0_desc, - (struct usb_descriptor_header *)&as_interface_alt_1_desc, - (struct usb_descriptor_header *)&as_header_desc, + (struct usb_descriptor_header *)&as_out_interface_alt_0_desc, + (struct usb_descriptor_header *)&as_out_interface_alt_1_desc, + (struct usb_descriptor_header *)&as_out_header_desc, - (struct usb_descriptor_header *)&as_type_i_desc, + (struct usb_descriptor_header *)&as_out_type_i_desc, (struct usb_descriptor_header *)&as_out_ep_desc, (struct usb_descriptor_header *)&as_iso_out_desc, + + (struct usb_descriptor_header *)&as_in_interface_alt_0_desc, + (struct usb_descriptor_header *)&as_in_interface_alt_1_desc, + (struct usb_descriptor_header *)&as_in_header_desc, + + (struct usb_descriptor_header *)&as_in_type_i_desc, + + (struct usb_descriptor_header *)&as_in_ep_desc, + (struct usb_descriptor_header *)&as_iso_in_desc, NULL, }; enum { STR_AC_IF, - STR_INPUT_TERMINAL, - STR_INPUT_TERMINAL_CH_NAMES, - STR_FEAT_DESC_0, - STR_OUTPUT_TERMINAL, - STR_AS_IF_ALT0, - STR_AS_IF_ALT1, + STR_USB_OUT_IT, + STR_USB_OUT_IT_CH_NAMES, + STR_IO_OUT_OT, + STR_IO_IN_IT, + STR_IO_IN_IT_CH_NAMES, + STR_USB_IN_OT, + STR_AS_OUT_IF_ALT0, + STR_AS_OUT_IF_ALT1, + STR_AS_IN_IF_ALT0, + STR_AS_IN_IF_ALT1, }; static struct usb_string strings_uac1[] = { [STR_AC_IF].s = "AC Interface", - [STR_INPUT_TERMINAL].s = "Input terminal", - [STR_INPUT_TERMINAL_CH_NAMES].s = "Channels", - [STR_FEAT_DESC_0].s = "Volume control & mute", - [STR_OUTPUT_TERMINAL].s = "Output terminal", - [STR_AS_IF_ALT0].s = "AS Interface", - [STR_AS_IF_ALT1].s = "AS Interface", + [STR_USB_OUT_IT].s = "Playback Input terminal", + [STR_USB_OUT_IT_CH_NAMES].s = "Playback Channels", + [STR_IO_OUT_OT].s = "Playback Output terminal", + [STR_IO_IN_IT].s = "Capture Input terminal", + [STR_IO_IN_IT_CH_NAMES].s = "Capture Channels", + [STR_USB_IN_OT].s = "Capture Output terminal", + [STR_AS_OUT_IF_ALT0].s = "Playback Inactive", + [STR_AS_OUT_IF_ALT1].s = "Playback Active", + [STR_AS_IN_IF_ALT0].s = "Capture Inactive", + [STR_AS_IN_IF_ALT1].s = "Capture Active", { }, }; @@ -243,216 +322,6 @@ static struct usb_gadget_strings *uac1_strings[] = { * This function is an ALSA sound card following USB Audio Class Spec 1.0. */ -/*-------------------------------------------------------------------------*/ -struct f_audio_buf { - u8 *buf; - int actual; - struct list_head list; -}; - -static struct f_audio_buf *f_audio_buffer_alloc(int buf_size) -{ - struct f_audio_buf *copy_buf; - - copy_buf = kzalloc(sizeof *copy_buf, GFP_ATOMIC); - if (!copy_buf) - return ERR_PTR(-ENOMEM); - - copy_buf->buf = kzalloc(buf_size, GFP_ATOMIC); - if (!copy_buf->buf) { - kfree(copy_buf); - return ERR_PTR(-ENOMEM); - } - - return copy_buf; -} - -static void f_audio_buffer_free(struct f_audio_buf *audio_buf) -{ - kfree(audio_buf->buf); - kfree(audio_buf); -} -/*-------------------------------------------------------------------------*/ - -struct f_audio { - struct gaudio card; - - /* endpoints handle full and/or high speeds */ - struct usb_ep *out_ep; - - spinlock_t lock; - struct f_audio_buf *copy_buf; - struct work_struct playback_work; - struct list_head play_queue; - - /* Control Set command */ - struct list_head cs; - u8 set_cmd; - struct usb_audio_control *set_con; -}; - -static inline struct f_audio *func_to_audio(struct usb_function *f) -{ - return container_of(f, struct f_audio, card.func); -} - -/*-------------------------------------------------------------------------*/ - -static void f_audio_playback_work(struct work_struct *data) -{ - struct f_audio *audio = container_of(data, struct f_audio, - playback_work); - struct f_audio_buf *play_buf; - - spin_lock_irq(&audio->lock); - if (list_empty(&audio->play_queue)) { - spin_unlock_irq(&audio->lock); - return; - } - play_buf = list_first_entry(&audio->play_queue, - struct f_audio_buf, list); - list_del(&play_buf->list); - spin_unlock_irq(&audio->lock); - - u_audio_playback(&audio->card, play_buf->buf, play_buf->actual); - f_audio_buffer_free(play_buf); -} - -static int f_audio_out_ep_complete(struct usb_ep *ep, struct usb_request *req) -{ - struct f_audio *audio = req->context; - struct usb_composite_dev *cdev = audio->card.func.config->cdev; - struct f_audio_buf *copy_buf = audio->copy_buf; - struct f_uac1_opts *opts; - int audio_buf_size; - int err; - - opts = container_of(audio->card.func.fi, struct f_uac1_opts, - func_inst); - audio_buf_size = opts->audio_buf_size; - - if (!copy_buf) - return -EINVAL; - - /* Copy buffer is full, add it to the play_queue */ - if (audio_buf_size - copy_buf->actual < req->actual) { - list_add_tail(©_buf->list, &audio->play_queue); - schedule_work(&audio->playback_work); - copy_buf = f_audio_buffer_alloc(audio_buf_size); - if (IS_ERR(copy_buf)) - return -ENOMEM; - } - - memcpy(copy_buf->buf + copy_buf->actual, req->buf, req->actual); - copy_buf->actual += req->actual; - audio->copy_buf = copy_buf; - - err = usb_ep_queue(ep, req, GFP_ATOMIC); - if (err) - ERROR(cdev, "%s queue req: %d\n", ep->name, err); - - return 0; - -} - -static void f_audio_complete(struct usb_ep *ep, struct usb_request *req) -{ - struct f_audio *audio = req->context; - int status = req->status; - u32 data = 0; - struct usb_ep *out_ep = audio->out_ep; - - switch (status) { - - case 0: /* normal completion? */ - if (ep == out_ep) - f_audio_out_ep_complete(ep, req); - else if (audio->set_con) { - memcpy(&data, req->buf, req->length); - audio->set_con->set(audio->set_con, audio->set_cmd, - le16_to_cpu(data)); - audio->set_con = NULL; - } - break; - default: - break; - } -} - -static int audio_set_intf_req(struct usb_function *f, - const struct usb_ctrlrequest *ctrl) -{ - struct f_audio *audio = func_to_audio(f); - struct usb_composite_dev *cdev = f->config->cdev; - struct usb_request *req = cdev->req; - u8 id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); - u16 len = le16_to_cpu(ctrl->wLength); - u16 w_value = le16_to_cpu(ctrl->wValue); - u8 con_sel = (w_value >> 8) & 0xFF; - u8 cmd = (ctrl->bRequest & 0x0F); - struct usb_audio_control_selector *cs; - struct usb_audio_control *con; - - DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n", - ctrl->bRequest, w_value, len, id); - - list_for_each_entry(cs, &audio->cs, list) { - if (cs->id == id) { - list_for_each_entry(con, &cs->control, list) { - if (con->type == con_sel) { - audio->set_con = con; - break; - } - } - break; - } - } - - audio->set_cmd = cmd; - req->context = audio; - req->complete = f_audio_complete; - - return len; -} - -static int audio_get_intf_req(struct usb_function *f, - const struct usb_ctrlrequest *ctrl) -{ - struct f_audio *audio = func_to_audio(f); - struct usb_composite_dev *cdev = f->config->cdev; - struct usb_request *req = cdev->req; - int value = -EOPNOTSUPP; - u8 id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); - u16 len = le16_to_cpu(ctrl->wLength); - u16 w_value = le16_to_cpu(ctrl->wValue); - u8 con_sel = (w_value >> 8) & 0xFF; - u8 cmd = (ctrl->bRequest & 0x0F); - struct usb_audio_control_selector *cs; - struct usb_audio_control *con; - - DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n", - ctrl->bRequest, w_value, len, id); - - list_for_each_entry(cs, &audio->cs, list) { - if (cs->id == id) { - list_for_each_entry(con, &cs->control, list) { - if (con->type == con_sel && con->get) { - value = con->get(con, cmd); - break; - } - } - break; - } - } - - req->context = audio; - req->complete = f_audio_complete; - len = min_t(size_t, sizeof(value), len); - memcpy(req->buf, &value, len); - - return len; -} - static int audio_set_endpoint_req(struct usb_function *f, const struct usb_ctrlrequest *ctrl) { @@ -531,14 +400,6 @@ f_audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) * activation uses set_alt(). */ switch (ctrl->bRequestType) { - case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE: - value = audio_set_intf_req(f, ctrl); - break; - - case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE: - value = audio_get_intf_req(f, ctrl); - break; - case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: value = audio_set_endpoint_req(f, ctrl); break; @@ -571,143 +432,158 @@ f_audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt) { - struct f_audio *audio = func_to_audio(f); struct usb_composite_dev *cdev = f->config->cdev; - struct usb_ep *out_ep = audio->out_ep; - struct usb_request *req; - struct f_uac1_opts *opts; - int req_buf_size, req_count, audio_buf_size; - int i = 0, err = 0; - - DBG(cdev, "intf %d, alt %d\n", intf, alt); + struct usb_gadget *gadget = cdev->gadget; + struct device *dev = &gadget->dev; + struct f_uac1 *uac1 = func_to_uac1(f); + int ret = 0; + + /* No i/f has more than 2 alt settings */ + if (alt > 1) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } - opts = container_of(f->fi, struct f_uac1_opts, func_inst); - req_buf_size = opts->req_buf_size; - req_count = opts->req_count; - audio_buf_size = opts->audio_buf_size; - - if (intf == 1) { - if (alt == 1) { - err = config_ep_by_speed(cdev->gadget, f, out_ep); - if (err) - return err; - - usb_ep_enable(out_ep); - audio->copy_buf = f_audio_buffer_alloc(audio_buf_size); - if (IS_ERR(audio->copy_buf)) - return -ENOMEM; - - /* - * allocate a bunch of read buffers - * and queue them all at once. - */ - for (i = 0; i < req_count && err == 0; i++) { - req = usb_ep_alloc_request(out_ep, GFP_ATOMIC); - if (req) { - req->buf = kzalloc(req_buf_size, - GFP_ATOMIC); - if (req->buf) { - req->length = req_buf_size; - req->context = audio; - req->complete = - f_audio_complete; - err = usb_ep_queue(out_ep, - req, GFP_ATOMIC); - if (err) - ERROR(cdev, - "%s queue req: %d\n", - out_ep->name, err); - } else - err = -ENOMEM; - } else - err = -ENOMEM; - } - - } else { - struct f_audio_buf *copy_buf = audio->copy_buf; - if (copy_buf) { - list_add_tail(©_buf->list, - &audio->play_queue); - schedule_work(&audio->playback_work); - } + if (intf == uac1->ac_intf) { + /* Control I/f has only 1 AltSetting - 0 */ + if (alt) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; } + return 0; } - return err; + if (intf == uac1->as_out_intf) { + uac1->as_out_alt = alt; + + if (alt) + ret = u_audio_start_capture(&uac1->g_audio); + else + u_audio_stop_capture(&uac1->g_audio); + } else if (intf == uac1->as_in_intf) { + uac1->as_in_alt = alt; + + if (alt) + ret = u_audio_start_playback(&uac1->g_audio); + else + u_audio_stop_playback(&uac1->g_audio); + } else { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } + + return ret; } -static void f_audio_disable(struct usb_function *f) +static int f_audio_get_alt(struct usb_function *f, unsigned intf) { - return; + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_gadget *gadget = cdev->gadget; + struct device *dev = &gadget->dev; + struct f_uac1 *uac1 = func_to_uac1(f); + + if (intf == uac1->ac_intf) + return uac1->ac_alt; + else if (intf == uac1->as_out_intf) + return uac1->as_out_alt; + else if (intf == uac1->as_in_intf) + return uac1->as_in_alt; + else + dev_err(dev, "%s:%d Invalid Interface %d!\n", + __func__, __LINE__, intf); + + return -EINVAL; } -/*-------------------------------------------------------------------------*/ -static void f_audio_build_desc(struct f_audio *audio) +static void f_audio_disable(struct usb_function *f) { - struct gaudio *card = &audio->card; - u8 *sam_freq; - int rate; + struct f_uac1 *uac1 = func_to_uac1(f); - /* Set channel numbers */ - input_terminal_desc.bNrChannels = u_audio_get_playback_channels(card); - as_type_i_desc.bNrChannels = u_audio_get_playback_channels(card); + uac1->as_out_alt = 0; + uac1->as_in_alt = 0; - /* Set sample rates */ - rate = u_audio_get_playback_rate(card); - sam_freq = as_type_i_desc.tSamFreq[0]; - memcpy(sam_freq, &rate, 3); - - /* Todo: Set Sample bits and other parameters */ - - return; + u_audio_stop_capture(&uac1->g_audio); } +/*-------------------------------------------------------------------------*/ + /* audio function driver setup/binding */ -static int -f_audio_bind(struct usb_configuration *c, struct usb_function *f) +static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) { - struct usb_composite_dev *cdev = c->cdev; - struct f_audio *audio = func_to_audio(f); - struct usb_string *us; - int status; - struct usb_ep *ep = NULL; - struct f_uac1_opts *audio_opts; + struct usb_composite_dev *cdev = c->cdev; + struct usb_gadget *gadget = cdev->gadget; + struct f_uac1 *uac1 = func_to_uac1(f); + struct g_audio *audio = func_to_g_audio(f); + struct f_uac1_opts *audio_opts; + struct usb_ep *ep = NULL; + struct usb_string *us; + u8 *sam_freq; + int rate; + int status; audio_opts = container_of(f->fi, struct f_uac1_opts, func_inst); - audio->card.gadget = c->cdev->gadget; - /* set up ASLA audio devices */ - if (!audio_opts->bound) { - status = gaudio_setup(&audio->card); - if (status < 0) - return status; - audio_opts->bound = true; - } + us = usb_gstrings_attach(cdev, uac1_strings, ARRAY_SIZE(strings_uac1)); if (IS_ERR(us)) return PTR_ERR(us); ac_interface_desc.iInterface = us[STR_AC_IF].id; - input_terminal_desc.iTerminal = us[STR_INPUT_TERMINAL].id; - input_terminal_desc.iChannelNames = us[STR_INPUT_TERMINAL_CH_NAMES].id; - feature_unit_desc.iFeature = us[STR_FEAT_DESC_0].id; - output_terminal_desc.iTerminal = us[STR_OUTPUT_TERMINAL].id; - as_interface_alt_0_desc.iInterface = us[STR_AS_IF_ALT0].id; - as_interface_alt_1_desc.iInterface = us[STR_AS_IF_ALT1].id; + usb_out_it_desc.iTerminal = us[STR_USB_OUT_IT].id; + usb_out_it_desc.iChannelNames = us[STR_USB_OUT_IT_CH_NAMES].id; + io_out_ot_desc.iTerminal = us[STR_IO_OUT_OT].id; + as_out_interface_alt_0_desc.iInterface = us[STR_AS_OUT_IF_ALT0].id; + as_out_interface_alt_1_desc.iInterface = us[STR_AS_OUT_IF_ALT1].id; + io_in_it_desc.iTerminal = us[STR_IO_IN_IT].id; + io_in_it_desc.iChannelNames = us[STR_IO_IN_IT_CH_NAMES].id; + usb_in_ot_desc.iTerminal = us[STR_USB_IN_OT].id; + as_in_interface_alt_0_desc.iInterface = us[STR_AS_IN_IF_ALT0].id; + as_in_interface_alt_1_desc.iInterface = us[STR_AS_IN_IF_ALT1].id; + /* Set channel numbers */ + usb_out_it_desc.bNrChannels = num_channels(audio_opts->c_chmask); + usb_out_it_desc.wChannelConfig = cpu_to_le16(audio_opts->c_chmask); + as_out_type_i_desc.bNrChannels = num_channels(audio_opts->c_chmask); + as_out_type_i_desc.bSubframeSize = audio_opts->c_ssize; + as_out_type_i_desc.bBitResolution = audio_opts->c_ssize * 8; + io_in_it_desc.bNrChannels = num_channels(audio_opts->p_chmask); + io_in_it_desc.wChannelConfig = cpu_to_le16(audio_opts->p_chmask); + as_in_type_i_desc.bNrChannels = num_channels(audio_opts->p_chmask); + as_in_type_i_desc.bSubframeSize = audio_opts->p_ssize; + as_in_type_i_desc.bBitResolution = audio_opts->p_ssize * 8; - f_audio_build_desc(audio); + /* Set sample rates */ + rate = audio_opts->c_srate; + sam_freq = as_out_type_i_desc.tSamFreq[0]; + memcpy(sam_freq, &rate, 3); + rate = audio_opts->p_srate; + sam_freq = as_in_type_i_desc.tSamFreq[0]; + memcpy(sam_freq, &rate, 3); /* allocate instance-specific interface IDs, and patch descriptors */ status = usb_interface_id(c, f); if (status < 0) goto fail; ac_interface_desc.bInterfaceNumber = status; + uac1->ac_intf = status; + uac1->ac_alt = 0; status = usb_interface_id(c, f); if (status < 0) goto fail; - as_interface_alt_0_desc.bInterfaceNumber = status; - as_interface_alt_1_desc.bInterfaceNumber = status; + as_out_interface_alt_0_desc.bInterfaceNumber = status; + as_out_interface_alt_1_desc.bInterfaceNumber = status; + uac1->as_out_intf = status; + uac1->as_out_alt = 0; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + as_in_interface_alt_0_desc.bInterfaceNumber = status; + as_in_interface_alt_1_desc.bInterfaceNumber = status; + uac1->as_in_intf = status; + uac1->as_in_alt = 0; + + audio->gadget = gadget; status = -ENODEV; @@ -718,52 +594,42 @@ f_audio_bind(struct usb_configuration *c, struct usb_function *f) audio->out_ep = ep; audio->out_ep->desc = &as_out_ep_desc; - status = -ENOMEM; + ep = usb_ep_autoconfig(cdev->gadget, &as_in_ep_desc); + if (!ep) + goto fail; + audio->in_ep = ep; + audio->in_ep->desc = &as_in_ep_desc; /* copy descriptors, and track endpoint copies */ status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL, NULL); if (status) goto fail; + + audio->out_ep_maxpsize = as_out_ep_desc.wMaxPacketSize; + audio->in_ep_maxpsize = as_in_ep_desc.wMaxPacketSize; + audio->params.c_chmask = audio_opts->c_chmask; + audio->params.c_srate = audio_opts->c_srate; + audio->params.c_ssize = audio_opts->c_ssize; + audio->params.p_chmask = audio_opts->p_chmask; + audio->params.p_srate = audio_opts->p_srate; + audio->params.p_ssize = audio_opts->p_ssize; + audio->params.req_number = audio_opts->req_number; + + status = g_audio_setup(audio, "UAC1_PCM", "UAC1_Gadget"); + if (status) + goto err_card_register; + return 0; +err_card_register: + usb_free_all_descriptors(f); fail: - gaudio_cleanup(&audio->card); return status; } /*-------------------------------------------------------------------------*/ -static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value) -{ - con->data[cmd] = value; - - return 0; -} - -static int generic_get_cmd(struct usb_audio_control *con, u8 cmd) -{ - return con->data[cmd]; -} - -/* Todo: add more control selecotor dynamically */ -static int control_selector_init(struct f_audio *audio) -{ - INIT_LIST_HEAD(&audio->cs); - list_add(&feature_unit.list, &audio->cs); - - INIT_LIST_HEAD(&feature_unit.control); - list_add(&mute_control.list, &feature_unit.control); - list_add(&volume_control.list, &feature_unit.control); - - volume_control.data[UAC__CUR] = 0xffc0; - volume_control.data[UAC__MIN] = 0xe3a0; - volume_control.data[UAC__MAX] = 0xfff0; - volume_control.data[UAC__RES] = 0x0030; - - return 0; -} - static inline struct f_uac1_opts *to_f_uac1_opts(struct config_item *item) { return container_of(to_config_group(item), struct f_uac1_opts, @@ -781,9 +647,10 @@ static struct configfs_item_operations f_uac1_item_ops = { .release = f_uac1_attr_release, }; -#define UAC1_INT_ATTRIBUTE(name) \ -static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \ - char *page) \ +#define UAC1_ATTRIBUTE(name) \ +static ssize_t f_uac1_opts_##name##_show( \ + struct config_item *item, \ + char *page) \ { \ struct f_uac1_opts *opts = to_f_uac1_opts(item); \ int result; \ @@ -795,7 +662,8 @@ static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \ return result; \ } \ \ -static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \ +static ssize_t f_uac1_opts_##name##_store( \ + struct config_item *item, \ const char *page, size_t len) \ { \ struct f_uac1_opts *opts = to_f_uac1_opts(item); \ @@ -822,64 +690,22 @@ end: \ \ CONFIGFS_ATTR(f_uac1_opts_, name) -UAC1_INT_ATTRIBUTE(req_buf_size); -UAC1_INT_ATTRIBUTE(req_count); -UAC1_INT_ATTRIBUTE(audio_buf_size); - -#define UAC1_STR_ATTRIBUTE(name) \ -static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \ - char *page) \ -{ \ - struct f_uac1_opts *opts = to_f_uac1_opts(item); \ - int result; \ - \ - mutex_lock(&opts->lock); \ - result = sprintf(page, "%s\n", opts->name); \ - mutex_unlock(&opts->lock); \ - \ - return result; \ -} \ - \ -static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \ - const char *page, size_t len) \ -{ \ - struct f_uac1_opts *opts = to_f_uac1_opts(item); \ - int ret = -EBUSY; \ - char *tmp; \ - \ - mutex_lock(&opts->lock); \ - if (opts->refcnt) \ - goto end; \ - \ - tmp = kstrndup(page, len, GFP_KERNEL); \ - if (tmp) { \ - ret = -ENOMEM; \ - goto end; \ - } \ - if (opts->name##_alloc) \ - kfree(opts->name); \ - opts->name##_alloc = true; \ - opts->name = tmp; \ - ret = len; \ - \ -end: \ - mutex_unlock(&opts->lock); \ - return ret; \ -} \ - \ -CONFIGFS_ATTR(f_uac1_opts_, name) - -UAC1_STR_ATTRIBUTE(fn_play); -UAC1_STR_ATTRIBUTE(fn_cap); -UAC1_STR_ATTRIBUTE(fn_cntl); +UAC1_ATTRIBUTE(c_chmask); +UAC1_ATTRIBUTE(c_srate); +UAC1_ATTRIBUTE(c_ssize); +UAC1_ATTRIBUTE(p_chmask); +UAC1_ATTRIBUTE(p_srate); +UAC1_ATTRIBUTE(p_ssize); +UAC1_ATTRIBUTE(req_number); static struct configfs_attribute *f_uac1_attrs[] = { - &f_uac1_opts_attr_req_buf_size, - &f_uac1_opts_attr_req_count, - &f_uac1_opts_attr_audio_buf_size, - &f_uac1_opts_attr_fn_play, - &f_uac1_opts_attr_fn_cap, - &f_uac1_opts_attr_fn_cntl, + &f_uac1_opts_attr_c_chmask, + &f_uac1_opts_attr_c_srate, + &f_uac1_opts_attr_c_ssize, + &f_uac1_opts_attr_p_chmask, + &f_uac1_opts_attr_p_srate, + &f_uac1_opts_attr_p_ssize, + &f_uac1_opts_attr_req_number, NULL, }; @@ -894,12 +720,6 @@ static void f_audio_free_inst(struct usb_function_instance *f) struct f_uac1_opts *opts; opts = container_of(f, struct f_uac1_opts, func_inst); - if (opts->fn_play_alloc) - kfree(opts->fn_play); - if (opts->fn_cap_alloc) - kfree(opts->fn_cap); - if (opts->fn_cntl_alloc) - kfree(opts->fn_cntl); kfree(opts); } @@ -917,21 +737,22 @@ static struct usb_function_instance *f_audio_alloc_inst(void) config_group_init_type_name(&opts->func_inst.group, "", &f_uac1_func_type); - opts->req_buf_size = UAC1_OUT_EP_MAX_PACKET_SIZE; - opts->req_count = UAC1_REQ_COUNT; - opts->audio_buf_size = UAC1_AUDIO_BUF_SIZE; - opts->fn_play = FILE_PCM_PLAYBACK; - opts->fn_cap = FILE_PCM_CAPTURE; - opts->fn_cntl = FILE_CONTROL; + opts->c_chmask = UAC1_DEF_CCHMASK; + opts->c_srate = UAC1_DEF_CSRATE; + opts->c_ssize = UAC1_DEF_CSSIZE; + opts->p_chmask = UAC1_DEF_PCHMASK; + opts->p_srate = UAC1_DEF_PSRATE; + opts->p_ssize = UAC1_DEF_PSSIZE; + opts->req_number = UAC1_DEF_REQ_NUM; return &opts->func_inst; } static void f_audio_free(struct usb_function *f) { - struct f_audio *audio = func_to_audio(f); + struct g_audio *audio; struct f_uac1_opts *opts; - gaudio_cleanup(&audio->card); + audio = func_to_g_audio(f); opts = container_of(f->fi, struct f_uac1_opts, func_inst); kfree(audio); mutex_lock(&opts->lock); @@ -941,42 +762,41 @@ static void f_audio_free(struct usb_function *f) static void f_audio_unbind(struct usb_configuration *c, struct usb_function *f) { + struct g_audio *audio = func_to_g_audio(f); + + g_audio_cleanup(audio); usb_free_all_descriptors(f); + + audio->gadget = NULL; } static struct usb_function *f_audio_alloc(struct usb_function_instance *fi) { - struct f_audio *audio; + struct f_uac1 *uac1; struct f_uac1_opts *opts; /* allocate and initialize one new instance */ - audio = kzalloc(sizeof(*audio), GFP_KERNEL); - if (!audio) + uac1 = kzalloc(sizeof(*uac1), GFP_KERNEL); + if (!uac1) return ERR_PTR(-ENOMEM); - audio->card.func.name = "g_audio"; - opts = container_of(fi, struct f_uac1_opts, func_inst); mutex_lock(&opts->lock); ++opts->refcnt; mutex_unlock(&opts->lock); - INIT_LIST_HEAD(&audio->play_queue); - spin_lock_init(&audio->lock); - - audio->card.func.bind = f_audio_bind; - audio->card.func.unbind = f_audio_unbind; - audio->card.func.set_alt = f_audio_set_alt; - audio->card.func.setup = f_audio_setup; - audio->card.func.disable = f_audio_disable; - audio->card.func.free_func = f_audio_free; - - control_selector_init(audio); - INIT_WORK(&audio->playback_work, f_audio_playback_work); + uac1->g_audio.func.name = "uac1_func"; + uac1->g_audio.func.bind = f_audio_bind; + uac1->g_audio.func.unbind = f_audio_unbind; + uac1->g_audio.func.set_alt = f_audio_set_alt; + uac1->g_audio.func.get_alt = f_audio_get_alt; + uac1->g_audio.func.setup = f_audio_setup; + uac1->g_audio.func.disable = f_audio_disable; + uac1->g_audio.func.free_func = f_audio_free; - return &audio->card.func; + return &uac1->g_audio.func; } DECLARE_USB_FUNCTION_INIT(uac1, f_audio_alloc_inst, f_audio_alloc); MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Bryan Wu"); +MODULE_AUTHOR("Ruslan Bilovol"); diff --git a/drivers/usb/gadget/function/f_uac1_legacy.c b/drivers/usb/gadget/function/f_uac1_legacy.c new file mode 100644 index 000000000000..5d229e72912e --- /dev/null +++ b/drivers/usb/gadget/function/f_uac1_legacy.c @@ -0,0 +1,1021 @@ +/* + * f_audio.c -- USB Audio class function driver + * + * Copyright (C) 2008 Bryan Wu <cooloney@kernel.org> + * Copyright (C) 2008 Analog Devices, Inc + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/atomic.h> + +#include "u_uac1_legacy.h" + +static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value); +static int generic_get_cmd(struct usb_audio_control *con, u8 cmd); + +/* + * DESCRIPTORS ... most are static, but strings and full + * configuration descriptors are built on demand. + */ + +/* + * We have two interfaces- AudioControl and AudioStreaming + * TODO: only supcard playback currently + */ +#define F_AUDIO_AC_INTERFACE 0 +#define F_AUDIO_AS_INTERFACE 1 +#define F_AUDIO_NUM_INTERFACES 1 + +/* B.3.1 Standard AC Interface Descriptor */ +static struct usb_interface_descriptor ac_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, +}; + +/* + * The number of AudioStreaming and MIDIStreaming interfaces + * in the Audio Interface Collection + */ +DECLARE_UAC_AC_HEADER_DESCRIPTOR(1); + +#define UAC_DT_AC_HEADER_LENGTH UAC_DT_AC_HEADER_SIZE(F_AUDIO_NUM_INTERFACES) +/* 1 input terminal, 1 output terminal and 1 feature unit */ +#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH + UAC_DT_INPUT_TERMINAL_SIZE \ + + UAC_DT_OUTPUT_TERMINAL_SIZE + UAC_DT_FEATURE_UNIT_SIZE(0)) +/* B.3.2 Class-Specific AC Interface Descriptor */ +static struct uac1_ac_header_descriptor_1 ac_header_desc = { + .bLength = UAC_DT_AC_HEADER_LENGTH, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_HEADER, + .bcdADC = __constant_cpu_to_le16(0x0100), + .wTotalLength = __constant_cpu_to_le16(UAC_DT_TOTAL_LENGTH), + .bInCollection = F_AUDIO_NUM_INTERFACES, + .baInterfaceNr = { + /* Interface number of the first AudioStream interface */ + [0] = 1, + } +}; + +#define INPUT_TERMINAL_ID 1 +static struct uac_input_terminal_descriptor input_terminal_desc = { + .bLength = UAC_DT_INPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_INPUT_TERMINAL, + .bTerminalID = INPUT_TERMINAL_ID, + .wTerminalType = UAC_TERMINAL_STREAMING, + .bAssocTerminal = 0, + .wChannelConfig = 0x3, +}; + +DECLARE_UAC_FEATURE_UNIT_DESCRIPTOR(0); + +#define FEATURE_UNIT_ID 2 +static struct uac_feature_unit_descriptor_0 feature_unit_desc = { + .bLength = UAC_DT_FEATURE_UNIT_SIZE(0), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_FEATURE_UNIT, + .bUnitID = FEATURE_UNIT_ID, + .bSourceID = INPUT_TERMINAL_ID, + .bControlSize = 2, + .bmaControls[0] = (UAC_FU_MUTE | UAC_FU_VOLUME), +}; + +static struct usb_audio_control mute_control = { + .list = LIST_HEAD_INIT(mute_control.list), + .name = "Mute Control", + .type = UAC_FU_MUTE, + /* Todo: add real Mute control code */ + .set = generic_set_cmd, + .get = generic_get_cmd, +}; + +static struct usb_audio_control volume_control = { + .list = LIST_HEAD_INIT(volume_control.list), + .name = "Volume Control", + .type = UAC_FU_VOLUME, + /* Todo: add real Volume control code */ + .set = generic_set_cmd, + .get = generic_get_cmd, +}; + +static struct usb_audio_control_selector feature_unit = { + .list = LIST_HEAD_INIT(feature_unit.list), + .id = FEATURE_UNIT_ID, + .name = "Mute & Volume Control", + .type = UAC_FEATURE_UNIT, + .desc = (struct usb_descriptor_header *)&feature_unit_desc, +}; + +#define OUTPUT_TERMINAL_ID 3 +static struct uac1_output_terminal_descriptor output_terminal_desc = { + .bLength = UAC_DT_OUTPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, + .bTerminalID = OUTPUT_TERMINAL_ID, + .wTerminalType = UAC_OUTPUT_TERMINAL_SPEAKER, + .bAssocTerminal = FEATURE_UNIT_ID, + .bSourceID = FEATURE_UNIT_ID, +}; + +/* B.4.1 Standard AS Interface Descriptor */ +static struct usb_interface_descriptor as_interface_alt_0_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, +}; + +static struct usb_interface_descriptor as_interface_alt_1_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 1, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, +}; + +/* B.4.2 Class-Specific AS Interface Descriptor */ +static struct uac1_as_header_descriptor as_header_desc = { + .bLength = UAC_DT_AS_HEADER_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_AS_GENERAL, + .bTerminalLink = INPUT_TERMINAL_ID, + .bDelay = 1, + .wFormatTag = UAC_FORMAT_TYPE_I_PCM, +}; + +DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1); + +static struct uac_format_type_i_discrete_descriptor_1 as_type_i_desc = { + .bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_FORMAT_TYPE, + .bFormatType = UAC_FORMAT_TYPE_I, + .bSubframeSize = 2, + .bBitResolution = 16, + .bSamFreqType = 1, +}; + +/* Standard ISO OUT Endpoint Descriptor */ +static struct usb_endpoint_descriptor as_out_ep_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_SYNC_ADAPTIVE + | USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE), + .bInterval = 4, +}; + +/* Class-specific AS ISO OUT Endpoint Descriptor */ +static struct uac_iso_endpoint_descriptor as_iso_out_desc = { + .bLength = UAC_ISO_ENDPOINT_DESC_SIZE, + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubtype = UAC_EP_GENERAL, + .bmAttributes = 1, + .bLockDelayUnits = 1, + .wLockDelay = __constant_cpu_to_le16(1), +}; + +static struct usb_descriptor_header *f_audio_desc[] = { + (struct usb_descriptor_header *)&ac_interface_desc, + (struct usb_descriptor_header *)&ac_header_desc, + + (struct usb_descriptor_header *)&input_terminal_desc, + (struct usb_descriptor_header *)&output_terminal_desc, + (struct usb_descriptor_header *)&feature_unit_desc, + + (struct usb_descriptor_header *)&as_interface_alt_0_desc, + (struct usb_descriptor_header *)&as_interface_alt_1_desc, + (struct usb_descriptor_header *)&as_header_desc, + + (struct usb_descriptor_header *)&as_type_i_desc, + + (struct usb_descriptor_header *)&as_out_ep_desc, + (struct usb_descriptor_header *)&as_iso_out_desc, + NULL, +}; + +enum { + STR_AC_IF, + STR_INPUT_TERMINAL, + STR_INPUT_TERMINAL_CH_NAMES, + STR_FEAT_DESC_0, + STR_OUTPUT_TERMINAL, + STR_AS_IF_ALT0, + STR_AS_IF_ALT1, +}; + +static struct usb_string strings_uac1[] = { + [STR_AC_IF].s = "AC Interface", + [STR_INPUT_TERMINAL].s = "Input terminal", + [STR_INPUT_TERMINAL_CH_NAMES].s = "Channels", + [STR_FEAT_DESC_0].s = "Volume control & mute", + [STR_OUTPUT_TERMINAL].s = "Output terminal", + [STR_AS_IF_ALT0].s = "AS Interface", + [STR_AS_IF_ALT1].s = "AS Interface", + { }, +}; + +static struct usb_gadget_strings str_uac1 = { + .language = 0x0409, /* en-us */ + .strings = strings_uac1, +}; + +static struct usb_gadget_strings *uac1_strings[] = { + &str_uac1, + NULL, +}; + +/* + * This function is an ALSA sound card following USB Audio Class Spec 1.0. + */ + +/*-------------------------------------------------------------------------*/ +struct f_audio_buf { + u8 *buf; + int actual; + struct list_head list; +}; + +static struct f_audio_buf *f_audio_buffer_alloc(int buf_size) +{ + struct f_audio_buf *copy_buf; + + copy_buf = kzalloc(sizeof *copy_buf, GFP_ATOMIC); + if (!copy_buf) + return ERR_PTR(-ENOMEM); + + copy_buf->buf = kzalloc(buf_size, GFP_ATOMIC); + if (!copy_buf->buf) { + kfree(copy_buf); + return ERR_PTR(-ENOMEM); + } + + return copy_buf; +} + +static void f_audio_buffer_free(struct f_audio_buf *audio_buf) +{ + kfree(audio_buf->buf); + kfree(audio_buf); +} +/*-------------------------------------------------------------------------*/ + +struct f_audio { + struct gaudio card; + + u8 ac_intf, ac_alt; + u8 as_intf, as_alt; + + /* endpoints handle full and/or high speeds */ + struct usb_ep *out_ep; + + spinlock_t lock; + struct f_audio_buf *copy_buf; + struct work_struct playback_work; + struct list_head play_queue; + + /* Control Set command */ + struct list_head cs; + u8 set_cmd; + struct usb_audio_control *set_con; +}; + +static inline struct f_audio *func_to_audio(struct usb_function *f) +{ + return container_of(f, struct f_audio, card.func); +} + +/*-------------------------------------------------------------------------*/ + +static void f_audio_playback_work(struct work_struct *data) +{ + struct f_audio *audio = container_of(data, struct f_audio, + playback_work); + struct f_audio_buf *play_buf; + + spin_lock_irq(&audio->lock); + if (list_empty(&audio->play_queue)) { + spin_unlock_irq(&audio->lock); + return; + } + play_buf = list_first_entry(&audio->play_queue, + struct f_audio_buf, list); + list_del(&play_buf->list); + spin_unlock_irq(&audio->lock); + + u_audio_playback(&audio->card, play_buf->buf, play_buf->actual); + f_audio_buffer_free(play_buf); +} + +static int f_audio_out_ep_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_audio *audio = req->context; + struct usb_composite_dev *cdev = audio->card.func.config->cdev; + struct f_audio_buf *copy_buf = audio->copy_buf; + struct f_uac1_legacy_opts *opts; + int audio_buf_size; + int err; + + opts = container_of(audio->card.func.fi, struct f_uac1_legacy_opts, + func_inst); + audio_buf_size = opts->audio_buf_size; + + if (!copy_buf) + return -EINVAL; + + /* Copy buffer is full, add it to the play_queue */ + if (audio_buf_size - copy_buf->actual < req->actual) { + list_add_tail(©_buf->list, &audio->play_queue); + schedule_work(&audio->playback_work); + copy_buf = f_audio_buffer_alloc(audio_buf_size); + if (IS_ERR(copy_buf)) + return -ENOMEM; + } + + memcpy(copy_buf->buf + copy_buf->actual, req->buf, req->actual); + copy_buf->actual += req->actual; + audio->copy_buf = copy_buf; + + err = usb_ep_queue(ep, req, GFP_ATOMIC); + if (err) + ERROR(cdev, "%s queue req: %d\n", ep->name, err); + + return 0; + +} + +static void f_audio_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_audio *audio = req->context; + int status = req->status; + u32 data = 0; + struct usb_ep *out_ep = audio->out_ep; + + switch (status) { + + case 0: /* normal completion? */ + if (ep == out_ep) + f_audio_out_ep_complete(ep, req); + else if (audio->set_con) { + memcpy(&data, req->buf, req->length); + audio->set_con->set(audio->set_con, audio->set_cmd, + le16_to_cpu(data)); + audio->set_con = NULL; + } + break; + default: + break; + } +} + +static int audio_set_intf_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct f_audio *audio = func_to_audio(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + u8 id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + u8 con_sel = (w_value >> 8) & 0xFF; + u8 cmd = (ctrl->bRequest & 0x0F); + struct usb_audio_control_selector *cs; + struct usb_audio_control *con; + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n", + ctrl->bRequest, w_value, len, id); + + list_for_each_entry(cs, &audio->cs, list) { + if (cs->id == id) { + list_for_each_entry(con, &cs->control, list) { + if (con->type == con_sel) { + audio->set_con = con; + break; + } + } + break; + } + } + + audio->set_cmd = cmd; + req->context = audio; + req->complete = f_audio_complete; + + return len; +} + +static int audio_get_intf_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct f_audio *audio = func_to_audio(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u8 id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + u8 con_sel = (w_value >> 8) & 0xFF; + u8 cmd = (ctrl->bRequest & 0x0F); + struct usb_audio_control_selector *cs; + struct usb_audio_control *con; + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n", + ctrl->bRequest, w_value, len, id); + + list_for_each_entry(cs, &audio->cs, list) { + if (cs->id == id) { + list_for_each_entry(con, &cs->control, list) { + if (con->type == con_sel && con->get) { + value = con->get(con, cmd); + break; + } + } + break; + } + } + + req->context = audio; + req->complete = f_audio_complete; + len = min_t(size_t, sizeof(value), len); + memcpy(req->buf, &value, len); + + return len; +} + +static int audio_set_endpoint_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + int value = -EOPNOTSUPP; + u16 ep = le16_to_cpu(ctrl->wIndex); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", + ctrl->bRequest, w_value, len, ep); + + switch (ctrl->bRequest) { + case UAC_SET_CUR: + value = len; + break; + + case UAC_SET_MIN: + break; + + case UAC_SET_MAX: + break; + + case UAC_SET_RES: + break; + + case UAC_SET_MEM: + break; + + default: + break; + } + + return value; +} + +static int audio_get_endpoint_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + int value = -EOPNOTSUPP; + u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", + ctrl->bRequest, w_value, len, ep); + + switch (ctrl->bRequest) { + case UAC_GET_CUR: + case UAC_GET_MIN: + case UAC_GET_MAX: + case UAC_GET_RES: + value = len; + break; + case UAC_GET_MEM: + break; + default: + break; + } + + return value; +} + +static int +f_audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + /* composite driver infrastructure handles everything; interface + * activation uses set_alt(). + */ + switch (ctrl->bRequestType) { + case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE: + value = audio_set_intf_req(f, ctrl); + break; + + case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE: + value = audio_get_intf_req(f, ctrl); + break; + + case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: + value = audio_set_endpoint_req(f, ctrl); + break; + + case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: + value = audio_get_endpoint_req(f, ctrl); + break; + + default: + ERROR(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + DBG(cdev, "audio req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "audio response on err %d\n", value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + +static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_audio *audio = func_to_audio(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_ep *out_ep = audio->out_ep; + struct usb_request *req; + struct f_uac1_legacy_opts *opts; + int req_buf_size, req_count, audio_buf_size; + int i = 0, err = 0; + + DBG(cdev, "intf %d, alt %d\n", intf, alt); + + opts = container_of(f->fi, struct f_uac1_legacy_opts, func_inst); + req_buf_size = opts->req_buf_size; + req_count = opts->req_count; + audio_buf_size = opts->audio_buf_size; + + /* No i/f has more than 2 alt settings */ + if (alt > 1) { + ERROR(cdev, "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } + + if (intf == audio->ac_intf) { + /* Control I/f has only 1 AltSetting - 0 */ + if (alt) { + ERROR(cdev, "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } + return 0; + } else if (intf == audio->as_intf) { + if (alt == 1) { + err = config_ep_by_speed(cdev->gadget, f, out_ep); + if (err) + return err; + + usb_ep_enable(out_ep); + audio->copy_buf = f_audio_buffer_alloc(audio_buf_size); + if (IS_ERR(audio->copy_buf)) + return -ENOMEM; + + /* + * allocate a bunch of read buffers + * and queue them all at once. + */ + for (i = 0; i < req_count && err == 0; i++) { + req = usb_ep_alloc_request(out_ep, GFP_ATOMIC); + if (req) { + req->buf = kzalloc(req_buf_size, + GFP_ATOMIC); + if (req->buf) { + req->length = req_buf_size; + req->context = audio; + req->complete = + f_audio_complete; + err = usb_ep_queue(out_ep, + req, GFP_ATOMIC); + if (err) + ERROR(cdev, + "%s queue req: %d\n", + out_ep->name, err); + } else + err = -ENOMEM; + } else + err = -ENOMEM; + } + + } else { + struct f_audio_buf *copy_buf = audio->copy_buf; + if (copy_buf) { + list_add_tail(©_buf->list, + &audio->play_queue); + schedule_work(&audio->playback_work); + } + } + audio->as_alt = alt; + } + + return err; +} + +static int f_audio_get_alt(struct usb_function *f, unsigned intf) +{ + struct f_audio *audio = func_to_audio(f); + struct usb_composite_dev *cdev = f->config->cdev; + + if (intf == audio->ac_intf) + return audio->ac_alt; + else if (intf == audio->as_intf) + return audio->as_alt; + else + ERROR(cdev, "%s:%d Invalid Interface %d!\n", + __func__, __LINE__, intf); + + return -EINVAL; +} + +static void f_audio_disable(struct usb_function *f) +{ + return; +} + +/*-------------------------------------------------------------------------*/ + +static void f_audio_build_desc(struct f_audio *audio) +{ + struct gaudio *card = &audio->card; + u8 *sam_freq; + int rate; + + /* Set channel numbers */ + input_terminal_desc.bNrChannels = u_audio_get_playback_channels(card); + as_type_i_desc.bNrChannels = u_audio_get_playback_channels(card); + + /* Set sample rates */ + rate = u_audio_get_playback_rate(card); + sam_freq = as_type_i_desc.tSamFreq[0]; + memcpy(sam_freq, &rate, 3); + + /* Todo: Set Sample bits and other parameters */ + + return; +} + +/* audio function driver setup/binding */ +static int +f_audio_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_audio *audio = func_to_audio(f); + struct usb_string *us; + int status; + struct usb_ep *ep = NULL; + struct f_uac1_legacy_opts *audio_opts; + + audio_opts = container_of(f->fi, struct f_uac1_legacy_opts, func_inst); + audio->card.gadget = c->cdev->gadget; + /* set up ASLA audio devices */ + if (!audio_opts->bound) { + status = gaudio_setup(&audio->card); + if (status < 0) + return status; + audio_opts->bound = true; + } + us = usb_gstrings_attach(cdev, uac1_strings, ARRAY_SIZE(strings_uac1)); + if (IS_ERR(us)) + return PTR_ERR(us); + ac_interface_desc.iInterface = us[STR_AC_IF].id; + input_terminal_desc.iTerminal = us[STR_INPUT_TERMINAL].id; + input_terminal_desc.iChannelNames = us[STR_INPUT_TERMINAL_CH_NAMES].id; + feature_unit_desc.iFeature = us[STR_FEAT_DESC_0].id; + output_terminal_desc.iTerminal = us[STR_OUTPUT_TERMINAL].id; + as_interface_alt_0_desc.iInterface = us[STR_AS_IF_ALT0].id; + as_interface_alt_1_desc.iInterface = us[STR_AS_IF_ALT1].id; + + + f_audio_build_desc(audio); + + /* allocate instance-specific interface IDs, and patch descriptors */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + ac_interface_desc.bInterfaceNumber = status; + audio->ac_intf = status; + audio->ac_alt = 0; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + as_interface_alt_0_desc.bInterfaceNumber = status; + as_interface_alt_1_desc.bInterfaceNumber = status; + audio->as_intf = status; + audio->as_alt = 0; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc); + if (!ep) + goto fail; + audio->out_ep = ep; + audio->out_ep->desc = &as_out_ep_desc; + + status = -ENOMEM; + + /* copy descriptors, and track endpoint copies */ + status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL, + NULL); + if (status) + goto fail; + return 0; + +fail: + gaudio_cleanup(&audio->card); + return status; +} + +/*-------------------------------------------------------------------------*/ + +static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value) +{ + con->data[cmd] = value; + + return 0; +} + +static int generic_get_cmd(struct usb_audio_control *con, u8 cmd) +{ + return con->data[cmd]; +} + +/* Todo: add more control selecotor dynamically */ +static int control_selector_init(struct f_audio *audio) +{ + INIT_LIST_HEAD(&audio->cs); + list_add(&feature_unit.list, &audio->cs); + + INIT_LIST_HEAD(&feature_unit.control); + list_add(&mute_control.list, &feature_unit.control); + list_add(&volume_control.list, &feature_unit.control); + + volume_control.data[UAC__CUR] = 0xffc0; + volume_control.data[UAC__MIN] = 0xe3a0; + volume_control.data[UAC__MAX] = 0xfff0; + volume_control.data[UAC__RES] = 0x0030; + + return 0; +} + +static inline +struct f_uac1_legacy_opts *to_f_uac1_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_uac1_legacy_opts, + func_inst.group); +} + +static void f_uac1_attr_release(struct config_item *item) +{ + struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static struct configfs_item_operations f_uac1_item_ops = { + .release = f_uac1_attr_release, +}; + +#define UAC1_INT_ATTRIBUTE(name) \ +static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%u\n", opts->name); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_uac1_legacy_opts *opts = to_f_uac1_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->name = num; \ + ret = len; \ + \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_uac1_opts_, name) + +UAC1_INT_ATTRIBUTE(req_buf_size); +UAC1_INT_ATTRIBUTE(req_count); +UAC1_INT_ATTRIBUTE(audio_buf_size); + +#define UAC1_STR_ATTRIBUTE(name) \ +static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%s\n", opts->name); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); \ + int ret = -EBUSY; \ + char *tmp; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) \ + goto end; \ + \ + tmp = kstrndup(page, len, GFP_KERNEL); \ + if (tmp) { \ + ret = -ENOMEM; \ + goto end; \ + } \ + if (opts->name##_alloc) \ + kfree(opts->name); \ + opts->name##_alloc = true; \ + opts->name = tmp; \ + ret = len; \ + \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_uac1_opts_, name) + +UAC1_STR_ATTRIBUTE(fn_play); +UAC1_STR_ATTRIBUTE(fn_cap); +UAC1_STR_ATTRIBUTE(fn_cntl); + +static struct configfs_attribute *f_uac1_attrs[] = { + &f_uac1_opts_attr_req_buf_size, + &f_uac1_opts_attr_req_count, + &f_uac1_opts_attr_audio_buf_size, + &f_uac1_opts_attr_fn_play, + &f_uac1_opts_attr_fn_cap, + &f_uac1_opts_attr_fn_cntl, + NULL, +}; + +static struct config_item_type f_uac1_func_type = { + .ct_item_ops = &f_uac1_item_ops, + .ct_attrs = f_uac1_attrs, + .ct_owner = THIS_MODULE, +}; + +static void f_audio_free_inst(struct usb_function_instance *f) +{ + struct f_uac1_legacy_opts *opts; + + opts = container_of(f, struct f_uac1_legacy_opts, func_inst); + if (opts->fn_play_alloc) + kfree(opts->fn_play); + if (opts->fn_cap_alloc) + kfree(opts->fn_cap); + if (opts->fn_cntl_alloc) + kfree(opts->fn_cntl); + kfree(opts); +} + +static struct usb_function_instance *f_audio_alloc_inst(void) +{ + struct f_uac1_legacy_opts *opts; + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return ERR_PTR(-ENOMEM); + + mutex_init(&opts->lock); + opts->func_inst.free_func_inst = f_audio_free_inst; + + config_group_init_type_name(&opts->func_inst.group, "", + &f_uac1_func_type); + + opts->req_buf_size = UAC1_OUT_EP_MAX_PACKET_SIZE; + opts->req_count = UAC1_REQ_COUNT; + opts->audio_buf_size = UAC1_AUDIO_BUF_SIZE; + opts->fn_play = FILE_PCM_PLAYBACK; + opts->fn_cap = FILE_PCM_CAPTURE; + opts->fn_cntl = FILE_CONTROL; + return &opts->func_inst; +} + +static void f_audio_free(struct usb_function *f) +{ + struct f_audio *audio = func_to_audio(f); + struct f_uac1_legacy_opts *opts; + + gaudio_cleanup(&audio->card); + opts = container_of(f->fi, struct f_uac1_legacy_opts, func_inst); + kfree(audio); + mutex_lock(&opts->lock); + --opts->refcnt; + mutex_unlock(&opts->lock); +} + +static void f_audio_unbind(struct usb_configuration *c, struct usb_function *f) +{ + usb_free_all_descriptors(f); +} + +static struct usb_function *f_audio_alloc(struct usb_function_instance *fi) +{ + struct f_audio *audio; + struct f_uac1_legacy_opts *opts; + + /* allocate and initialize one new instance */ + audio = kzalloc(sizeof(*audio), GFP_KERNEL); + if (!audio) + return ERR_PTR(-ENOMEM); + + audio->card.func.name = "g_audio"; + + opts = container_of(fi, struct f_uac1_legacy_opts, func_inst); + mutex_lock(&opts->lock); + ++opts->refcnt; + mutex_unlock(&opts->lock); + INIT_LIST_HEAD(&audio->play_queue); + spin_lock_init(&audio->lock); + + audio->card.func.bind = f_audio_bind; + audio->card.func.unbind = f_audio_unbind; + audio->card.func.set_alt = f_audio_set_alt; + audio->card.func.get_alt = f_audio_get_alt; + audio->card.func.setup = f_audio_setup; + audio->card.func.disable = f_audio_disable; + audio->card.func.free_func = f_audio_free; + + control_selector_init(audio); + + INIT_WORK(&audio->playback_work, f_audio_playback_work); + + return &audio->card.func; +} + +DECLARE_USB_FUNCTION_INIT(uac1_legacy, f_audio_alloc_inst, f_audio_alloc); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Bryan Wu"); diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c index f6a0d3a1311b..9082ce261e70 100644 --- a/drivers/usb/gadget/function/f_uac2.c +++ b/drivers/usb/gadget/function/f_uac2.c @@ -13,13 +13,9 @@ #include <linux/usb/audio.h> #include <linux/usb/audio-v2.h> -#include <linux/platform_device.h> #include <linux/module.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> - +#include "u_audio.h" #include "u_uac2.h" /* @@ -51,506 +47,23 @@ #define UNFLW_CTRL 8 #define OVFLW_CTRL 10 -static const char *uac2_name = "snd_uac2"; - -struct uac2_req { - struct uac2_rtd_params *pp; /* parent param */ - struct usb_request *req; -}; - -struct uac2_rtd_params { - struct snd_uac2_chip *uac2; /* parent chip */ - bool ep_enabled; /* if the ep is enabled */ - /* Size of the ring buffer */ - size_t dma_bytes; - unsigned char *dma_area; - - struct snd_pcm_substream *ss; - - /* Ring buffer */ - ssize_t hw_ptr; - - void *rbuf; - - size_t period_size; - - unsigned max_psize; - struct uac2_req *ureq; - - spinlock_t lock; -}; - -struct snd_uac2_chip { - struct platform_device pdev; - struct platform_driver pdrv; - - struct uac2_rtd_params p_prm; - struct uac2_rtd_params c_prm; - - struct snd_card *card; - struct snd_pcm *pcm; - - /* timekeeping for the playback endpoint */ - unsigned int p_interval; - unsigned int p_residue; - - /* pre-calculated values for playback iso completion */ - unsigned int p_pktsize; - unsigned int p_pktsize_residue; - unsigned int p_framesize; +struct f_uac2 { + struct g_audio g_audio; + u8 ac_intf, as_in_intf, as_out_intf; + u8 ac_alt, as_in_alt, as_out_alt; /* needed for get_alt() */ }; -#define BUFF_SIZE_MAX (PAGE_SIZE * 16) -#define PRD_SIZE_MAX PAGE_SIZE -#define MIN_PERIODS 4 - -static struct snd_pcm_hardware uac2_pcm_hardware = { - .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER - | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID - | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, - .rates = SNDRV_PCM_RATE_CONTINUOUS, - .periods_max = BUFF_SIZE_MAX / PRD_SIZE_MAX, - .buffer_bytes_max = BUFF_SIZE_MAX, - .period_bytes_max = PRD_SIZE_MAX, - .periods_min = MIN_PERIODS, -}; - -struct audio_dev { - u8 ac_intf, ac_alt; - u8 as_out_intf, as_out_alt; - u8 as_in_intf, as_in_alt; - - struct usb_ep *in_ep, *out_ep; - struct usb_function func; - - /* The ALSA Sound Card it represents on the USB-Client side */ - struct snd_uac2_chip uac2; -}; - -static inline -struct audio_dev *func_to_agdev(struct usb_function *f) +static inline struct f_uac2 *func_to_uac2(struct usb_function *f) { - return container_of(f, struct audio_dev, func); + return container_of(f, struct f_uac2, g_audio.func); } static inline -struct audio_dev *uac2_to_agdev(struct snd_uac2_chip *u) -{ - return container_of(u, struct audio_dev, uac2); -} - -static inline -struct snd_uac2_chip *pdev_to_uac2(struct platform_device *p) -{ - return container_of(p, struct snd_uac2_chip, pdev); -} - -static inline -struct f_uac2_opts *agdev_to_uac2_opts(struct audio_dev *agdev) +struct f_uac2_opts *g_audio_to_uac2_opts(struct g_audio *agdev) { return container_of(agdev->func.fi, struct f_uac2_opts, func_inst); } -static inline -uint num_channels(uint chanmask) -{ - uint num = 0; - - while (chanmask) { - num += (chanmask & 1); - chanmask >>= 1; - } - - return num; -} - -static void -agdev_iso_complete(struct usb_ep *ep, struct usb_request *req) -{ - unsigned pending; - unsigned long flags; - unsigned int hw_ptr; - bool update_alsa = false; - int status = req->status; - struct uac2_req *ur = req->context; - struct snd_pcm_substream *substream; - struct uac2_rtd_params *prm = ur->pp; - struct snd_uac2_chip *uac2 = prm->uac2; - - /* i/f shutting down */ - if (!prm->ep_enabled || req->status == -ESHUTDOWN) - return; - - /* - * We can't really do much about bad xfers. - * Afterall, the ISOCH xfers could fail legitimately. - */ - if (status) - pr_debug("%s: iso_complete status(%d) %d/%d\n", - __func__, status, req->actual, req->length); - - substream = prm->ss; - - /* Do nothing if ALSA isn't active */ - if (!substream) - goto exit; - - spin_lock_irqsave(&prm->lock, flags); - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - /* - * For each IN packet, take the quotient of the current data - * rate and the endpoint's interval as the base packet size. - * If there is a residue from this division, add it to the - * residue accumulator. - */ - req->length = uac2->p_pktsize; - uac2->p_residue += uac2->p_pktsize_residue; - - /* - * Whenever there are more bytes in the accumulator than we - * need to add one more sample frame, increase this packet's - * size and decrease the accumulator. - */ - if (uac2->p_residue / uac2->p_interval >= uac2->p_framesize) { - req->length += uac2->p_framesize; - uac2->p_residue -= uac2->p_framesize * - uac2->p_interval; - } - - req->actual = req->length; - } - - pending = prm->hw_ptr % prm->period_size; - pending += req->actual; - if (pending >= prm->period_size) - update_alsa = true; - - hw_ptr = prm->hw_ptr; - prm->hw_ptr = (prm->hw_ptr + req->actual) % prm->dma_bytes; - - spin_unlock_irqrestore(&prm->lock, flags); - - /* Pack USB load in ALSA ring buffer */ - pending = prm->dma_bytes - hw_ptr; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - if (unlikely(pending < req->actual)) { - memcpy(req->buf, prm->dma_area + hw_ptr, pending); - memcpy(req->buf + pending, prm->dma_area, - req->actual - pending); - } else { - memcpy(req->buf, prm->dma_area + hw_ptr, req->actual); - } - } else { - if (unlikely(pending < req->actual)) { - memcpy(prm->dma_area + hw_ptr, req->buf, pending); - memcpy(prm->dma_area, req->buf + pending, - req->actual - pending); - } else { - memcpy(prm->dma_area + hw_ptr, req->buf, req->actual); - } - } - -exit: - if (usb_ep_queue(ep, req, GFP_ATOMIC)) - dev_err(&uac2->pdev.dev, "%d Error!\n", __LINE__); - - if (update_alsa) - snd_pcm_period_elapsed(substream); - - return; -} - -static int -uac2_pcm_trigger(struct snd_pcm_substream *substream, int cmd) -{ - struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); - struct audio_dev *agdev = uac2_to_agdev(uac2); - struct f_uac2_opts *uac2_opts = agdev_to_uac2_opts(agdev); - struct uac2_rtd_params *prm; - unsigned long flags; - int err = 0; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - prm = &uac2->p_prm; - else - prm = &uac2->c_prm; - - spin_lock_irqsave(&prm->lock, flags); - - /* Reset */ - prm->hw_ptr = 0; - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_RESUME: - prm->ss = substream; - break; - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_SUSPEND: - prm->ss = NULL; - break; - default: - err = -EINVAL; - } - - spin_unlock_irqrestore(&prm->lock, flags); - - /* Clear buffer after Play stops */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !prm->ss) - memset(prm->rbuf, 0, prm->max_psize * uac2_opts->req_number); - - return err; -} - -static snd_pcm_uframes_t uac2_pcm_pointer(struct snd_pcm_substream *substream) -{ - struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); - struct uac2_rtd_params *prm; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - prm = &uac2->p_prm; - else - prm = &uac2->c_prm; - - return bytes_to_frames(substream->runtime, prm->hw_ptr); -} - -static int uac2_pcm_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *hw_params) -{ - struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); - struct uac2_rtd_params *prm; - int err; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - prm = &uac2->p_prm; - else - prm = &uac2->c_prm; - - err = snd_pcm_lib_malloc_pages(substream, - params_buffer_bytes(hw_params)); - if (err >= 0) { - prm->dma_bytes = substream->runtime->dma_bytes; - prm->dma_area = substream->runtime->dma_area; - prm->period_size = params_period_bytes(hw_params); - } - - return err; -} - -static int uac2_pcm_hw_free(struct snd_pcm_substream *substream) -{ - struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); - struct uac2_rtd_params *prm; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - prm = &uac2->p_prm; - else - prm = &uac2->c_prm; - - prm->dma_area = NULL; - prm->dma_bytes = 0; - prm->period_size = 0; - - return snd_pcm_lib_free_pages(substream); -} - -static int uac2_pcm_open(struct snd_pcm_substream *substream) -{ - struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); - struct snd_pcm_runtime *runtime = substream->runtime; - struct audio_dev *audio_dev; - struct f_uac2_opts *opts; - int p_ssize, c_ssize; - int p_srate, c_srate; - int p_chmask, c_chmask; - - audio_dev = uac2_to_agdev(uac2); - opts = container_of(audio_dev->func.fi, struct f_uac2_opts, func_inst); - p_ssize = opts->p_ssize; - c_ssize = opts->c_ssize; - p_srate = opts->p_srate; - c_srate = opts->c_srate; - p_chmask = opts->p_chmask; - c_chmask = opts->c_chmask; - uac2->p_residue = 0; - - runtime->hw = uac2_pcm_hardware; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - spin_lock_init(&uac2->p_prm.lock); - runtime->hw.rate_min = p_srate; - switch (p_ssize) { - case 3: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE; - break; - case 4: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; - break; - default: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; - break; - } - runtime->hw.channels_min = num_channels(p_chmask); - runtime->hw.period_bytes_min = 2 * uac2->p_prm.max_psize - / runtime->hw.periods_min; - } else { - spin_lock_init(&uac2->c_prm.lock); - runtime->hw.rate_min = c_srate; - switch (c_ssize) { - case 3: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE; - break; - case 4: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; - break; - default: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; - break; - } - runtime->hw.channels_min = num_channels(c_chmask); - runtime->hw.period_bytes_min = 2 * uac2->c_prm.max_psize - / runtime->hw.periods_min; - } - - runtime->hw.rate_max = runtime->hw.rate_min; - runtime->hw.channels_max = runtime->hw.channels_min; - - snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); - - return 0; -} - -/* ALSA cries without these function pointers */ -static int uac2_pcm_null(struct snd_pcm_substream *substream) -{ - return 0; -} - -static struct snd_pcm_ops uac2_pcm_ops = { - .open = uac2_pcm_open, - .close = uac2_pcm_null, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = uac2_pcm_hw_params, - .hw_free = uac2_pcm_hw_free, - .trigger = uac2_pcm_trigger, - .pointer = uac2_pcm_pointer, - .prepare = uac2_pcm_null, -}; - -static int snd_uac2_probe(struct platform_device *pdev) -{ - struct snd_uac2_chip *uac2 = pdev_to_uac2(pdev); - struct snd_card *card; - struct snd_pcm *pcm; - struct audio_dev *audio_dev; - struct f_uac2_opts *opts; - int err; - int p_chmask, c_chmask; - - audio_dev = uac2_to_agdev(uac2); - opts = container_of(audio_dev->func.fi, struct f_uac2_opts, func_inst); - p_chmask = opts->p_chmask; - c_chmask = opts->c_chmask; - - /* Choose any slot, with no id */ - err = snd_card_new(&pdev->dev, -1, NULL, THIS_MODULE, 0, &card); - if (err < 0) - return err; - - uac2->card = card; - - /* - * Create first PCM device - * Create a substream only for non-zero channel streams - */ - err = snd_pcm_new(uac2->card, "UAC2 PCM", 0, - p_chmask ? 1 : 0, c_chmask ? 1 : 0, &pcm); - if (err < 0) - goto snd_fail; - - strcpy(pcm->name, "UAC2 PCM"); - pcm->private_data = uac2; - - uac2->pcm = pcm; - - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac2_pcm_ops); - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac2_pcm_ops); - - strcpy(card->driver, "UAC2_Gadget"); - strcpy(card->shortname, "UAC2_Gadget"); - sprintf(card->longname, "UAC2_Gadget %i", pdev->id); - - snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, - snd_dma_continuous_data(GFP_KERNEL), 0, BUFF_SIZE_MAX); - - err = snd_card_register(card); - if (!err) { - platform_set_drvdata(pdev, card); - return 0; - } - -snd_fail: - snd_card_free(card); - - uac2->pcm = NULL; - uac2->card = NULL; - - return err; -} - -static int snd_uac2_remove(struct platform_device *pdev) -{ - struct snd_card *card = platform_get_drvdata(pdev); - - if (card) - return snd_card_free(card); - - return 0; -} - -static void snd_uac2_release(struct device *dev) -{ - dev_dbg(dev, "releasing '%s'\n", dev_name(dev)); -} - -static int alsa_uac2_init(struct audio_dev *agdev) -{ - struct snd_uac2_chip *uac2 = &agdev->uac2; - int err; - - uac2->pdrv.probe = snd_uac2_probe; - uac2->pdrv.remove = snd_uac2_remove; - uac2->pdrv.driver.name = uac2_name; - - uac2->pdev.id = 0; - uac2->pdev.name = uac2_name; - uac2->pdev.dev.release = snd_uac2_release; - - /* Register snd_uac2 driver */ - err = platform_driver_register(&uac2->pdrv); - if (err) - return err; - - /* Register snd_uac2 device */ - err = platform_device_register(&uac2->pdev); - if (err) - platform_driver_unregister(&uac2->pdrv); - - return err; -} - -static void alsa_uac2_exit(struct audio_dev *agdev) -{ - struct snd_uac2_chip *uac2 = &agdev->uac2; - - platform_driver_unregister(&uac2->pdrv); - platform_device_unregister(&uac2->pdev); -} - - /* --------- USB Function Interface ------------- */ enum { @@ -938,32 +451,6 @@ struct cntrl_range_lay3 { __u32 dRES; } __packed; -static inline void -free_ep(struct uac2_rtd_params *prm, struct usb_ep *ep) -{ - struct snd_uac2_chip *uac2 = prm->uac2; - struct audio_dev *agdev = uac2_to_agdev(uac2); - struct f_uac2_opts *uac2_opts = agdev_to_uac2_opts(agdev); - int i; - - if (!prm->ep_enabled) - return; - - prm->ep_enabled = false; - - for (i = 0; i < uac2_opts->req_number; i++) { - if (prm->ureq[i].req) { - usb_ep_dequeue(ep, prm->ureq[i].req); - usb_ep_free_request(ep, prm->ureq[i].req); - prm->ureq[i].req = NULL; - } - } - - if (usb_ep_disable(ep)) - dev_err(&uac2->pdev.dev, - "%s:%d Error!\n", __func__, __LINE__); -} - static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, struct usb_endpoint_descriptor *ep_desc, unsigned int factor, bool is_playback) @@ -990,12 +477,11 @@ static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, static int afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) { - struct audio_dev *agdev = func_to_agdev(fn); - struct snd_uac2_chip *uac2 = &agdev->uac2; + struct f_uac2 *uac2 = func_to_uac2(fn); + struct g_audio *agdev = func_to_g_audio(fn); struct usb_composite_dev *cdev = cfg->cdev; struct usb_gadget *gadget = cdev->gadget; - struct device *dev = &uac2->pdev.dev; - struct uac2_rtd_params *prm; + struct device *dev = &gadget->dev; struct f_uac2_opts *uac2_opts; struct usb_string *us; int ret; @@ -1042,8 +528,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) return ret; } std_ac_if_desc.bInterfaceNumber = ret; - agdev->ac_intf = ret; - agdev->ac_alt = 0; + uac2->ac_intf = ret; + uac2->ac_alt = 0; ret = usb_interface_id(cfg, fn); if (ret < 0) { @@ -1052,8 +538,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) } std_as_out_if0_desc.bInterfaceNumber = ret; std_as_out_if1_desc.bInterfaceNumber = ret; - agdev->as_out_intf = ret; - agdev->as_out_alt = 0; + uac2->as_out_intf = ret; + uac2->as_out_alt = 0; ret = usb_interface_id(cfg, fn); if (ret < 0) { @@ -1062,8 +548,14 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) } std_as_in_if0_desc.bInterfaceNumber = ret; std_as_in_if1_desc.bInterfaceNumber = ret; - agdev->as_in_intf = ret; - agdev->as_in_alt = 0; + uac2->as_in_intf = ret; + uac2->as_in_alt = 0; + + /* Calculate wMaxPacketSize according to audio bandwidth */ + set_ep_max_packet_size(uac2_opts, &fs_epin_desc, 1000, true); + set_ep_max_packet_size(uac2_opts, &fs_epout_desc, 1000, false); + set_ep_max_packet_size(uac2_opts, &hs_epin_desc, 8000, true); + set_ep_max_packet_size(uac2_opts, &hs_epout_desc, 8000, false); agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc); if (!agdev->out_ep) { @@ -1077,14 +569,10 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) return ret; } - uac2->p_prm.uac2 = uac2; - uac2->c_prm.uac2 = uac2; - - /* Calculate wMaxPacketSize according to audio bandwidth */ - set_ep_max_packet_size(uac2_opts, &fs_epin_desc, 1000, true); - set_ep_max_packet_size(uac2_opts, &fs_epout_desc, 1000, false); - set_ep_max_packet_size(uac2_opts, &hs_epin_desc, 8000, true); - set_ep_max_packet_size(uac2_opts, &hs_epout_desc, 8000, false); + agdev->in_ep_maxpsize = max(fs_epin_desc.wMaxPacketSize, + hs_epin_desc.wMaxPacketSize); + agdev->out_ep_maxpsize = max(fs_epout_desc.wMaxPacketSize, + hs_epout_desc.wMaxPacketSize); hs_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress; hs_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress; @@ -1094,48 +582,23 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) if (ret) return ret; - prm = &agdev->uac2.c_prm; - prm->max_psize = hs_epout_desc.wMaxPacketSize; - prm->ureq = kcalloc(uac2_opts->req_number, sizeof(struct uac2_req), - GFP_KERNEL); - if (!prm->ureq) { - ret = -ENOMEM; - goto err_free_descs; - } - prm->rbuf = kcalloc(uac2_opts->req_number, prm->max_psize, GFP_KERNEL); - if (!prm->rbuf) { - prm->max_psize = 0; - ret = -ENOMEM; - goto err_free_descs; - } - - prm = &agdev->uac2.p_prm; - prm->max_psize = hs_epin_desc.wMaxPacketSize; - prm->ureq = kcalloc(uac2_opts->req_number, sizeof(struct uac2_req), - GFP_KERNEL); - if (!prm->ureq) { - ret = -ENOMEM; - goto err_free_descs; - } - prm->rbuf = kcalloc(uac2_opts->req_number, prm->max_psize, GFP_KERNEL); - if (!prm->rbuf) { - prm->max_psize = 0; - ret = -ENOMEM; - goto err_no_memory; - } + agdev->gadget = gadget; - ret = alsa_uac2_init(agdev); + agdev->params.p_chmask = uac2_opts->p_chmask; + agdev->params.p_srate = uac2_opts->p_srate; + agdev->params.p_ssize = uac2_opts->p_ssize; + agdev->params.c_chmask = uac2_opts->c_chmask; + agdev->params.c_srate = uac2_opts->c_srate; + agdev->params.c_ssize = uac2_opts->c_ssize; + agdev->params.req_number = uac2_opts->req_number; + ret = g_audio_setup(agdev, "UAC2 PCM", "UAC2_Gadget"); if (ret) - goto err_no_memory; + goto err_free_descs; return 0; -err_no_memory: - kfree(agdev->uac2.p_prm.ureq); - kfree(agdev->uac2.c_prm.ureq); - kfree(agdev->uac2.p_prm.rbuf); - kfree(agdev->uac2.c_prm.rbuf); err_free_descs: usb_free_all_descriptors(fn); + agdev->gadget = NULL; return ret; } @@ -1143,15 +606,10 @@ static int afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt) { struct usb_composite_dev *cdev = fn->config->cdev; - struct audio_dev *agdev = func_to_agdev(fn); - struct f_uac2_opts *opts = agdev_to_uac2_opts(agdev); - struct snd_uac2_chip *uac2 = &agdev->uac2; + struct f_uac2 *uac2 = func_to_uac2(fn); struct usb_gadget *gadget = cdev->gadget; - struct device *dev = &uac2->pdev.dev; - struct usb_request *req; - struct usb_ep *ep; - struct uac2_rtd_params *prm; - int req_len, i; + struct device *dev = &gadget->dev; + int ret = 0; /* No i/f has more than 2 alt settings */ if (alt > 1) { @@ -1159,7 +617,7 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt) return -EINVAL; } - if (intf == agdev->ac_intf) { + if (intf == uac2->ac_intf) { /* Control I/f has only 1 AltSetting - 0 */ if (alt) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); @@ -1168,95 +626,42 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt) return 0; } - if (intf == agdev->as_out_intf) { - ep = agdev->out_ep; - prm = &uac2->c_prm; - config_ep_by_speed(gadget, fn, ep); - agdev->as_out_alt = alt; - req_len = prm->max_psize; - } else if (intf == agdev->as_in_intf) { - unsigned int factor, rate; - struct usb_endpoint_descriptor *ep_desc; - - ep = agdev->in_ep; - prm = &uac2->p_prm; - config_ep_by_speed(gadget, fn, ep); - agdev->as_in_alt = alt; - - /* pre-calculate the playback endpoint's interval */ - if (gadget->speed == USB_SPEED_FULL) { - ep_desc = &fs_epin_desc; - factor = 1000; - } else { - ep_desc = &hs_epin_desc; - factor = 8000; - } - - /* pre-compute some values for iso_complete() */ - uac2->p_framesize = opts->p_ssize * - num_channels(opts->p_chmask); - rate = opts->p_srate * uac2->p_framesize; - uac2->p_interval = factor / (1 << (ep_desc->bInterval - 1)); - uac2->p_pktsize = min_t(unsigned int, rate / uac2->p_interval, - prm->max_psize); + if (intf == uac2->as_out_intf) { + uac2->as_out_alt = alt; - if (uac2->p_pktsize < prm->max_psize) - uac2->p_pktsize_residue = rate % uac2->p_interval; + if (alt) + ret = u_audio_start_capture(&uac2->g_audio); else - uac2->p_pktsize_residue = 0; + u_audio_stop_capture(&uac2->g_audio); + } else if (intf == uac2->as_in_intf) { + uac2->as_in_alt = alt; - req_len = uac2->p_pktsize; - uac2->p_residue = 0; + if (alt) + ret = u_audio_start_playback(&uac2->g_audio); + else + u_audio_stop_playback(&uac2->g_audio); } else { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return -EINVAL; } - if (alt == 0) { - free_ep(prm, ep); - return 0; - } - - prm->ep_enabled = true; - usb_ep_enable(ep); - - for (i = 0; i < opts->req_number; i++) { - if (!prm->ureq[i].req) { - req = usb_ep_alloc_request(ep, GFP_ATOMIC); - if (req == NULL) - return -ENOMEM; - - prm->ureq[i].req = req; - prm->ureq[i].pp = prm; - - req->zero = 0; - req->context = &prm->ureq[i]; - req->length = req_len; - req->complete = agdev_iso_complete; - req->buf = prm->rbuf + i * prm->max_psize; - } - - if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC)) - dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); - } - - return 0; + return ret; } static int afunc_get_alt(struct usb_function *fn, unsigned intf) { - struct audio_dev *agdev = func_to_agdev(fn); - struct snd_uac2_chip *uac2 = &agdev->uac2; - - if (intf == agdev->ac_intf) - return agdev->ac_alt; - else if (intf == agdev->as_out_intf) - return agdev->as_out_alt; - else if (intf == agdev->as_in_intf) - return agdev->as_in_alt; + struct f_uac2 *uac2 = func_to_uac2(fn); + struct g_audio *agdev = func_to_g_audio(fn); + + if (intf == uac2->ac_intf) + return uac2->ac_alt; + else if (intf == uac2->as_out_intf) + return uac2->as_out_alt; + else if (intf == uac2->as_in_intf) + return uac2->as_in_alt; else - dev_err(&uac2->pdev.dev, + dev_err(&agdev->gadget->dev, "%s:%d Invalid Interface %d!\n", __func__, __LINE__, intf); @@ -1266,22 +671,19 @@ afunc_get_alt(struct usb_function *fn, unsigned intf) static void afunc_disable(struct usb_function *fn) { - struct audio_dev *agdev = func_to_agdev(fn); - struct snd_uac2_chip *uac2 = &agdev->uac2; - - free_ep(&uac2->p_prm, agdev->in_ep); - agdev->as_in_alt = 0; + struct f_uac2 *uac2 = func_to_uac2(fn); - free_ep(&uac2->c_prm, agdev->out_ep); - agdev->as_out_alt = 0; + uac2->as_in_alt = 0; + uac2->as_out_alt = 0; + u_audio_stop_capture(&uac2->g_audio); + u_audio_stop_playback(&uac2->g_audio); } static int in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) { struct usb_request *req = fn->config->cdev->req; - struct audio_dev *agdev = func_to_agdev(fn); - struct snd_uac2_chip *uac2 = &agdev->uac2; + struct g_audio *agdev = func_to_g_audio(fn); struct f_uac2_opts *opts; u16 w_length = le16_to_cpu(cr->wLength); u16 w_index = le16_to_cpu(cr->wIndex); @@ -1291,7 +693,7 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) int value = -EOPNOTSUPP; int p_srate, c_srate; - opts = agdev_to_uac2_opts(agdev); + opts = g_audio_to_uac2_opts(agdev); p_srate = opts->p_srate; c_srate = opts->c_srate; @@ -1310,7 +712,7 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) *(u8 *)req->buf = 1; value = min_t(unsigned, w_length, 1); } else { - dev_err(&uac2->pdev.dev, + dev_err(&agdev->gadget->dev, "%s:%d control_selector=%d TODO!\n", __func__, __LINE__, control_selector); } @@ -1322,8 +724,7 @@ static int in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr) { struct usb_request *req = fn->config->cdev->req; - struct audio_dev *agdev = func_to_agdev(fn); - struct snd_uac2_chip *uac2 = &agdev->uac2; + struct g_audio *agdev = func_to_g_audio(fn); struct f_uac2_opts *opts; u16 w_length = le16_to_cpu(cr->wLength); u16 w_index = le16_to_cpu(cr->wIndex); @@ -1334,7 +735,7 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr) int value = -EOPNOTSUPP; int p_srate, c_srate; - opts = agdev_to_uac2_opts(agdev); + opts = g_audio_to_uac2_opts(agdev); p_srate = opts->p_srate; c_srate = opts->c_srate; @@ -1353,7 +754,7 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr) value = min_t(unsigned, w_length, sizeof r); memcpy(req->buf, &r, value); } else { - dev_err(&uac2->pdev.dev, + dev_err(&agdev->gadget->dev, "%s:%d control_selector=%d TODO!\n", __func__, __LINE__, control_selector); } @@ -1388,13 +789,13 @@ out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) static int setup_rq_inf(struct usb_function *fn, const struct usb_ctrlrequest *cr) { - struct audio_dev *agdev = func_to_agdev(fn); - struct snd_uac2_chip *uac2 = &agdev->uac2; + struct f_uac2 *uac2 = func_to_uac2(fn); + struct g_audio *agdev = func_to_g_audio(fn); u16 w_index = le16_to_cpu(cr->wIndex); u8 intf = w_index & 0xff; - if (intf != agdev->ac_intf) { - dev_err(&uac2->pdev.dev, + if (intf != uac2->ac_intf) { + dev_err(&agdev->gadget->dev, "%s:%d Error!\n", __func__, __LINE__); return -EOPNOTSUPP; } @@ -1411,8 +812,7 @@ static int afunc_setup(struct usb_function *fn, const struct usb_ctrlrequest *cr) { struct usb_composite_dev *cdev = fn->config->cdev; - struct audio_dev *agdev = func_to_agdev(fn); - struct snd_uac2_chip *uac2 = &agdev->uac2; + struct g_audio *agdev = func_to_g_audio(fn); struct usb_request *req = cdev->req; u16 w_length = le16_to_cpu(cr->wLength); int value = -EOPNOTSUPP; @@ -1424,14 +824,15 @@ afunc_setup(struct usb_function *fn, const struct usb_ctrlrequest *cr) if ((cr->bRequestType & USB_RECIP_MASK) == USB_RECIP_INTERFACE) value = setup_rq_inf(fn, cr); else - dev_err(&uac2->pdev.dev, "%s:%d Error!\n", __func__, __LINE__); + dev_err(&agdev->gadget->dev, "%s:%d Error!\n", + __func__, __LINE__); if (value >= 0) { req->length = value; req->zero = value < w_length; value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); if (value < 0) { - dev_err(&uac2->pdev.dev, + dev_err(&agdev->gadget->dev, "%s:%d Error!\n", __func__, __LINE__); req->status = 0; } @@ -1557,10 +958,10 @@ static struct usb_function_instance *afunc_alloc_inst(void) static void afunc_free(struct usb_function *f) { - struct audio_dev *agdev; + struct g_audio *agdev; struct f_uac2_opts *opts; - agdev = func_to_agdev(f); + agdev = func_to_g_audio(f); opts = container_of(f->fi, struct f_uac2_opts, func_inst); kfree(agdev); mutex_lock(&opts->lock); @@ -1570,27 +971,21 @@ static void afunc_free(struct usb_function *f) static void afunc_unbind(struct usb_configuration *c, struct usb_function *f) { - struct audio_dev *agdev = func_to_agdev(f); - struct uac2_rtd_params *prm; + struct g_audio *agdev = func_to_g_audio(f); - alsa_uac2_exit(agdev); - - prm = &agdev->uac2.p_prm; - kfree(prm->rbuf); - - prm = &agdev->uac2.c_prm; - kfree(prm->rbuf); - kfree(prm->ureq); + g_audio_cleanup(agdev); usb_free_all_descriptors(f); + + agdev->gadget = NULL; } static struct usb_function *afunc_alloc(struct usb_function_instance *fi) { - struct audio_dev *agdev; + struct f_uac2 *uac2; struct f_uac2_opts *opts; - agdev = kzalloc(sizeof(*agdev), GFP_KERNEL); - if (agdev == NULL) + uac2 = kzalloc(sizeof(*uac2), GFP_KERNEL); + if (uac2 == NULL) return ERR_PTR(-ENOMEM); opts = container_of(fi, struct f_uac2_opts, func_inst); @@ -1598,16 +993,16 @@ static struct usb_function *afunc_alloc(struct usb_function_instance *fi) ++opts->refcnt; mutex_unlock(&opts->lock); - agdev->func.name = "uac2_func"; - agdev->func.bind = afunc_bind; - agdev->func.unbind = afunc_unbind; - agdev->func.set_alt = afunc_set_alt; - agdev->func.get_alt = afunc_get_alt; - agdev->func.disable = afunc_disable; - agdev->func.setup = afunc_setup; - agdev->func.free_func = afunc_free; + uac2->g_audio.func.name = "uac2_func"; + uac2->g_audio.func.bind = afunc_bind; + uac2->g_audio.func.unbind = afunc_unbind; + uac2->g_audio.func.set_alt = afunc_set_alt; + uac2->g_audio.func.get_alt = afunc_get_alt; + uac2->g_audio.func.disable = afunc_disable; + uac2->g_audio.func.setup = afunc_setup; + uac2->g_audio.func.free_func = afunc_free; - return &agdev->func; + return &uac2->g_audio.func; } DECLARE_USB_FUNCTION_INIT(uac2, afunc_alloc_inst, afunc_alloc); diff --git a/drivers/usb/gadget/function/storage_common.h b/drivers/usb/gadget/function/storage_common.h index e69848994cb4..e0814a960132 100644 --- a/drivers/usb/gadget/function/storage_common.h +++ b/drivers/usb/gadget/function/storage_common.h @@ -133,9 +133,10 @@ static inline bool fsg_lun_is_open(struct fsg_lun *curlun) #define FSG_MAX_LUNS 16 enum fsg_buffer_state { + BUF_STATE_SENDING = -2, + BUF_STATE_RECEIVING, BUF_STATE_EMPTY = 0, - BUF_STATE_FULL, - BUF_STATE_BUSY + BUF_STATE_FULL }; struct fsg_buffhd { @@ -151,23 +152,14 @@ struct fsg_buffhd { unsigned int bulk_out_intended_length; struct usb_request *inreq; - int inreq_busy; struct usb_request *outreq; - int outreq_busy; }; enum fsg_state { - /* This one isn't used anywhere */ - FSG_STATE_COMMAND_PHASE = -10, - FSG_STATE_DATA_PHASE, - FSG_STATE_STATUS_PHASE, - - FSG_STATE_IDLE = 0, + FSG_STATE_NORMAL, FSG_STATE_ABORT_BULK_OUT, - FSG_STATE_RESET, - FSG_STATE_INTERFACE_CHANGE, + FSG_STATE_PROTOCOL_RESET, FSG_STATE_CONFIG_CHANGE, - FSG_STATE_DISCONNECT, FSG_STATE_EXIT, FSG_STATE_TERMINATED }; diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c new file mode 100644 index 000000000000..5dd73b9e5172 --- /dev/null +++ b/drivers/usb/gadget/function/u_audio.c @@ -0,0 +1,662 @@ +/* + * u_audio.c -- interface to USB gadget "ALSA sound card" utilities + * + * Copyright (C) 2016 + * Author: Ruslan Bilovol <ruslan.bilovol@gmail.com> + * + * Sound card implementation was cut-and-pasted with changes + * from f_uac2.c and has: + * Copyright (C) 2011 + * Yadwinder Singh (yadi.brar01@gmail.com) + * Jaswinder Singh (jaswinder.singh@linaro.org) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> + +#include "u_audio.h" + +#define BUFF_SIZE_MAX (PAGE_SIZE * 16) +#define PRD_SIZE_MAX PAGE_SIZE +#define MIN_PERIODS 4 + +struct uac_req { + struct uac_rtd_params *pp; /* parent param */ + struct usb_request *req; +}; + +/* Runtime data params for one stream */ +struct uac_rtd_params { + struct snd_uac_chip *uac; /* parent chip */ + bool ep_enabled; /* if the ep is enabled */ + /* Size of the ring buffer */ + size_t dma_bytes; + unsigned char *dma_area; + + struct snd_pcm_substream *ss; + + /* Ring buffer */ + ssize_t hw_ptr; + + void *rbuf; + + size_t period_size; + + unsigned max_psize; /* MaxPacketSize of endpoint */ + struct uac_req *ureq; + + spinlock_t lock; +}; + +struct snd_uac_chip { + struct g_audio *audio_dev; + + struct uac_rtd_params p_prm; + struct uac_rtd_params c_prm; + + struct snd_card *card; + struct snd_pcm *pcm; + + /* timekeeping for the playback endpoint */ + unsigned int p_interval; + unsigned int p_residue; + + /* pre-calculated values for playback iso completion */ + unsigned int p_pktsize; + unsigned int p_pktsize_residue; + unsigned int p_framesize; +}; + +static struct snd_pcm_hardware uac_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER + | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID + | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .periods_max = BUFF_SIZE_MAX / PRD_SIZE_MAX, + .buffer_bytes_max = BUFF_SIZE_MAX, + .period_bytes_max = PRD_SIZE_MAX, + .periods_min = MIN_PERIODS, +}; + +static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req) +{ + unsigned pending; + unsigned long flags; + unsigned int hw_ptr; + bool update_alsa = false; + int status = req->status; + struct uac_req *ur = req->context; + struct snd_pcm_substream *substream; + struct uac_rtd_params *prm = ur->pp; + struct snd_uac_chip *uac = prm->uac; + + /* i/f shutting down */ + if (!prm->ep_enabled || req->status == -ESHUTDOWN) + return; + + /* + * We can't really do much about bad xfers. + * Afterall, the ISOCH xfers could fail legitimately. + */ + if (status) + pr_debug("%s: iso_complete status(%d) %d/%d\n", + __func__, status, req->actual, req->length); + + substream = prm->ss; + + /* Do nothing if ALSA isn't active */ + if (!substream) + goto exit; + + spin_lock_irqsave(&prm->lock, flags); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* + * For each IN packet, take the quotient of the current data + * rate and the endpoint's interval as the base packet size. + * If there is a residue from this division, add it to the + * residue accumulator. + */ + req->length = uac->p_pktsize; + uac->p_residue += uac->p_pktsize_residue; + + /* + * Whenever there are more bytes in the accumulator than we + * need to add one more sample frame, increase this packet's + * size and decrease the accumulator. + */ + if (uac->p_residue / uac->p_interval >= uac->p_framesize) { + req->length += uac->p_framesize; + uac->p_residue -= uac->p_framesize * + uac->p_interval; + } + + req->actual = req->length; + } + + pending = prm->hw_ptr % prm->period_size; + pending += req->actual; + if (pending >= prm->period_size) + update_alsa = true; + + hw_ptr = prm->hw_ptr; + prm->hw_ptr = (prm->hw_ptr + req->actual) % prm->dma_bytes; + + spin_unlock_irqrestore(&prm->lock, flags); + + /* Pack USB load in ALSA ring buffer */ + pending = prm->dma_bytes - hw_ptr; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (unlikely(pending < req->actual)) { + memcpy(req->buf, prm->dma_area + hw_ptr, pending); + memcpy(req->buf + pending, prm->dma_area, + req->actual - pending); + } else { + memcpy(req->buf, prm->dma_area + hw_ptr, req->actual); + } + } else { + if (unlikely(pending < req->actual)) { + memcpy(prm->dma_area + hw_ptr, req->buf, pending); + memcpy(prm->dma_area, req->buf + pending, + req->actual - pending); + } else { + memcpy(prm->dma_area + hw_ptr, req->buf, req->actual); + } + } + +exit: + if (usb_ep_queue(ep, req, GFP_ATOMIC)) + dev_err(uac->card->dev, "%d Error!\n", __LINE__); + + if (update_alsa) + snd_pcm_period_elapsed(substream); +} + +static int uac_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_uac_chip *uac = snd_pcm_substream_chip(substream); + struct uac_rtd_params *prm; + struct g_audio *audio_dev; + struct uac_params *params; + unsigned long flags; + int err = 0; + + audio_dev = uac->audio_dev; + params = &audio_dev->params; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prm = &uac->p_prm; + else + prm = &uac->c_prm; + + spin_lock_irqsave(&prm->lock, flags); + + /* Reset */ + prm->hw_ptr = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + prm->ss = substream; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + prm->ss = NULL; + break; + default: + err = -EINVAL; + } + + spin_unlock_irqrestore(&prm->lock, flags); + + /* Clear buffer after Play stops */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !prm->ss) + memset(prm->rbuf, 0, prm->max_psize * params->req_number); + + return err; +} + +static snd_pcm_uframes_t uac_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_uac_chip *uac = snd_pcm_substream_chip(substream); + struct uac_rtd_params *prm; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prm = &uac->p_prm; + else + prm = &uac->c_prm; + + return bytes_to_frames(substream->runtime, prm->hw_ptr); +} + +static int uac_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_uac_chip *uac = snd_pcm_substream_chip(substream); + struct uac_rtd_params *prm; + int err; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prm = &uac->p_prm; + else + prm = &uac->c_prm; + + err = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (err >= 0) { + prm->dma_bytes = substream->runtime->dma_bytes; + prm->dma_area = substream->runtime->dma_area; + prm->period_size = params_period_bytes(hw_params); + } + + return err; +} + +static int uac_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_uac_chip *uac = snd_pcm_substream_chip(substream); + struct uac_rtd_params *prm; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prm = &uac->p_prm; + else + prm = &uac->c_prm; + + prm->dma_area = NULL; + prm->dma_bytes = 0; + prm->period_size = 0; + + return snd_pcm_lib_free_pages(substream); +} + +static int uac_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_uac_chip *uac = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct g_audio *audio_dev; + struct uac_params *params; + int p_ssize, c_ssize; + int p_srate, c_srate; + int p_chmask, c_chmask; + + audio_dev = uac->audio_dev; + params = &audio_dev->params; + p_ssize = params->p_ssize; + c_ssize = params->c_ssize; + p_srate = params->p_srate; + c_srate = params->c_srate; + p_chmask = params->p_chmask; + c_chmask = params->c_chmask; + uac->p_residue = 0; + + runtime->hw = uac_pcm_hardware; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + spin_lock_init(&uac->p_prm.lock); + runtime->hw.rate_min = p_srate; + switch (p_ssize) { + case 3: + runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE; + break; + case 4: + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; + break; + default: + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; + break; + } + runtime->hw.channels_min = num_channels(p_chmask); + runtime->hw.period_bytes_min = 2 * uac->p_prm.max_psize + / runtime->hw.periods_min; + } else { + spin_lock_init(&uac->c_prm.lock); + runtime->hw.rate_min = c_srate; + switch (c_ssize) { + case 3: + runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE; + break; + case 4: + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; + break; + default: + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; + break; + } + runtime->hw.channels_min = num_channels(c_chmask); + runtime->hw.period_bytes_min = 2 * uac->c_prm.max_psize + / runtime->hw.periods_min; + } + + runtime->hw.rate_max = runtime->hw.rate_min; + runtime->hw.channels_max = runtime->hw.channels_min; + + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + + return 0; +} + +/* ALSA cries without these function pointers */ +static int uac_pcm_null(struct snd_pcm_substream *substream) +{ + return 0; +} + +static struct snd_pcm_ops uac_pcm_ops = { + .open = uac_pcm_open, + .close = uac_pcm_null, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = uac_pcm_hw_params, + .hw_free = uac_pcm_hw_free, + .trigger = uac_pcm_trigger, + .pointer = uac_pcm_pointer, + .prepare = uac_pcm_null, +}; + +static inline void free_ep(struct uac_rtd_params *prm, struct usb_ep *ep) +{ + struct snd_uac_chip *uac = prm->uac; + struct g_audio *audio_dev; + struct uac_params *params; + int i; + + if (!prm->ep_enabled) + return; + + prm->ep_enabled = false; + + audio_dev = uac->audio_dev; + params = &audio_dev->params; + + for (i = 0; i < params->req_number; i++) { + if (prm->ureq[i].req) { + usb_ep_dequeue(ep, prm->ureq[i].req); + usb_ep_free_request(ep, prm->ureq[i].req); + prm->ureq[i].req = NULL; + } + } + + if (usb_ep_disable(ep)) + dev_err(uac->card->dev, "%s:%d Error!\n", __func__, __LINE__); +} + + +int u_audio_start_capture(struct g_audio *audio_dev) +{ + struct snd_uac_chip *uac = audio_dev->uac; + struct usb_gadget *gadget = audio_dev->gadget; + struct device *dev = &gadget->dev; + struct usb_request *req; + struct usb_ep *ep; + struct uac_rtd_params *prm; + struct uac_params *params = &audio_dev->params; + int req_len, i; + + ep = audio_dev->out_ep; + prm = &uac->c_prm; + config_ep_by_speed(gadget, &audio_dev->func, ep); + req_len = prm->max_psize; + + prm->ep_enabled = true; + usb_ep_enable(ep); + + for (i = 0; i < params->req_number; i++) { + if (!prm->ureq[i].req) { + req = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (req == NULL) + return -ENOMEM; + + prm->ureq[i].req = req; + prm->ureq[i].pp = prm; + + req->zero = 0; + req->context = &prm->ureq[i]; + req->length = req_len; + req->complete = u_audio_iso_complete; + req->buf = prm->rbuf + i * prm->max_psize; + } + + if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC)) + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + } + + return 0; +} +EXPORT_SYMBOL_GPL(u_audio_start_capture); + +void u_audio_stop_capture(struct g_audio *audio_dev) +{ + struct snd_uac_chip *uac = audio_dev->uac; + + free_ep(&uac->c_prm, audio_dev->out_ep); +} +EXPORT_SYMBOL_GPL(u_audio_stop_capture); + +int u_audio_start_playback(struct g_audio *audio_dev) +{ + struct snd_uac_chip *uac = audio_dev->uac; + struct usb_gadget *gadget = audio_dev->gadget; + struct device *dev = &gadget->dev; + struct usb_request *req; + struct usb_ep *ep; + struct uac_rtd_params *prm; + struct uac_params *params = &audio_dev->params; + unsigned int factor, rate; + const struct usb_endpoint_descriptor *ep_desc; + int req_len, i; + + ep = audio_dev->in_ep; + prm = &uac->p_prm; + config_ep_by_speed(gadget, &audio_dev->func, ep); + + ep_desc = ep->desc; + + /* pre-calculate the playback endpoint's interval */ + if (gadget->speed == USB_SPEED_FULL) + factor = 1000; + else + factor = 8000; + + /* pre-compute some values for iso_complete() */ + uac->p_framesize = params->p_ssize * + num_channels(params->p_chmask); + rate = params->p_srate * uac->p_framesize; + uac->p_interval = factor / (1 << (ep_desc->bInterval - 1)); + uac->p_pktsize = min_t(unsigned int, rate / uac->p_interval, + prm->max_psize); + + if (uac->p_pktsize < prm->max_psize) + uac->p_pktsize_residue = rate % uac->p_interval; + else + uac->p_pktsize_residue = 0; + + req_len = uac->p_pktsize; + uac->p_residue = 0; + + prm->ep_enabled = true; + usb_ep_enable(ep); + + for (i = 0; i < params->req_number; i++) { + if (!prm->ureq[i].req) { + req = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (req == NULL) + return -ENOMEM; + + prm->ureq[i].req = req; + prm->ureq[i].pp = prm; + + req->zero = 0; + req->context = &prm->ureq[i]; + req->length = req_len; + req->complete = u_audio_iso_complete; + req->buf = prm->rbuf + i * prm->max_psize; + } + + if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC)) + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + } + + return 0; +} +EXPORT_SYMBOL_GPL(u_audio_start_playback); + +void u_audio_stop_playback(struct g_audio *audio_dev) +{ + struct snd_uac_chip *uac = audio_dev->uac; + + free_ep(&uac->p_prm, audio_dev->in_ep); +} +EXPORT_SYMBOL_GPL(u_audio_stop_playback); + +int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, + const char *card_name) +{ + struct snd_uac_chip *uac; + struct snd_card *card; + struct snd_pcm *pcm; + struct uac_params *params; + int p_chmask, c_chmask; + int err; + + if (!g_audio) + return -EINVAL; + + uac = kzalloc(sizeof(*uac), GFP_KERNEL); + if (!uac) + return -ENOMEM; + g_audio->uac = uac; + uac->audio_dev = g_audio; + + params = &g_audio->params; + p_chmask = params->p_chmask; + c_chmask = params->c_chmask; + + if (c_chmask) { + struct uac_rtd_params *prm = &uac->c_prm; + + uac->c_prm.uac = uac; + prm->max_psize = g_audio->out_ep_maxpsize; + + prm->ureq = kcalloc(params->req_number, sizeof(struct uac_req), + GFP_KERNEL); + if (!prm->ureq) { + err = -ENOMEM; + goto fail; + } + + prm->rbuf = kcalloc(params->req_number, prm->max_psize, + GFP_KERNEL); + if (!prm->rbuf) { + prm->max_psize = 0; + err = -ENOMEM; + goto fail; + } + } + + if (p_chmask) { + struct uac_rtd_params *prm = &uac->p_prm; + + uac->p_prm.uac = uac; + prm->max_psize = g_audio->in_ep_maxpsize; + + prm->ureq = kcalloc(params->req_number, sizeof(struct uac_req), + GFP_KERNEL); + if (!prm->ureq) { + err = -ENOMEM; + goto fail; + } + + prm->rbuf = kcalloc(params->req_number, prm->max_psize, + GFP_KERNEL); + if (!prm->rbuf) { + prm->max_psize = 0; + err = -ENOMEM; + goto fail; + } + } + + /* Choose any slot, with no id */ + err = snd_card_new(&g_audio->gadget->dev, + -1, NULL, THIS_MODULE, 0, &card); + if (err < 0) + goto fail; + + uac->card = card; + + /* + * Create first PCM device + * Create a substream only for non-zero channel streams + */ + err = snd_pcm_new(uac->card, pcm_name, 0, + p_chmask ? 1 : 0, c_chmask ? 1 : 0, &pcm); + if (err < 0) + goto snd_fail; + + strcpy(pcm->name, pcm_name); + pcm->private_data = uac; + uac->pcm = pcm; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac_pcm_ops); + + strcpy(card->driver, card_name); + strcpy(card->shortname, card_name); + sprintf(card->longname, "%s %i", card_name, card->dev->id); + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), 0, BUFF_SIZE_MAX); + + err = snd_card_register(card); + + if (!err) + return 0; + +snd_fail: + snd_card_free(card); +fail: + kfree(uac->p_prm.ureq); + kfree(uac->c_prm.ureq); + kfree(uac->p_prm.rbuf); + kfree(uac->c_prm.rbuf); + kfree(uac); + + return err; +} +EXPORT_SYMBOL_GPL(g_audio_setup); + +void g_audio_cleanup(struct g_audio *g_audio) +{ + struct snd_uac_chip *uac; + struct snd_card *card; + + if (!g_audio || !g_audio->uac) + return; + + uac = g_audio->uac; + card = uac->card; + if (card) + snd_card_free(card); + + kfree(uac->p_prm.ureq); + kfree(uac->c_prm.ureq); + kfree(uac->p_prm.rbuf); + kfree(uac->c_prm.rbuf); + kfree(uac); +} +EXPORT_SYMBOL_GPL(g_audio_cleanup); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("USB gadget \"ALSA sound card\" utilities"); +MODULE_AUTHOR("Ruslan Bilovol"); diff --git a/drivers/usb/gadget/function/u_audio.h b/drivers/usb/gadget/function/u_audio.h new file mode 100644 index 000000000000..07e13784cbb8 --- /dev/null +++ b/drivers/usb/gadget/function/u_audio.h @@ -0,0 +1,95 @@ +/* + * u_audio.h -- interface to USB gadget "ALSA sound card" utilities + * + * Copyright (C) 2016 + * Author: Ruslan Bilovol <ruslan.bilovol@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __U_AUDIO_H +#define __U_AUDIO_H + +#include <linux/usb/composite.h> + +struct uac_params { + /* playback */ + int p_chmask; /* channel mask */ + int p_srate; /* rate in Hz */ + int p_ssize; /* sample size */ + + /* capture */ + int c_chmask; /* channel mask */ + int c_srate; /* rate in Hz */ + int c_ssize; /* sample size */ + + int req_number; /* number of preallocated requests */ +}; + +struct g_audio { + struct usb_function func; + struct usb_gadget *gadget; + + struct usb_ep *in_ep; + struct usb_ep *out_ep; + + /* Max packet size for all in_ep possible speeds */ + unsigned int in_ep_maxpsize; + /* Max packet size for all out_ep possible speeds */ + unsigned int out_ep_maxpsize; + + /* The ALSA Sound Card it represents on the USB-Client side */ + struct snd_uac_chip *uac; + + struct uac_params params; +}; + +static inline struct g_audio *func_to_g_audio(struct usb_function *f) +{ + return container_of(f, struct g_audio, func); +} + +static inline uint num_channels(uint chanmask) +{ + uint num = 0; + + while (chanmask) { + num += (chanmask & 1); + chanmask >>= 1; + } + + return num; +} + +/* + * g_audio_setup - initialize one virtual ALSA sound card + * @g_audio: struct with filled params, in_ep_maxpsize, out_ep_maxpsize + * @pcm_name: the id string for a PCM instance of this sound card + * @card_name: name of this soundcard + * + * This sets up the single virtual ALSA sound card that may be exported by a + * gadget driver using this framework. + * + * Context: may sleep + * + * Returns zero on success, or a negative error on failure. + */ +int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, + const char *card_name); +void g_audio_cleanup(struct g_audio *g_audio); + +int u_audio_start_capture(struct g_audio *g_audio); +void u_audio_stop_capture(struct g_audio *g_audio); +int u_audio_start_playback(struct g_audio *g_audio); +void u_audio_stop_playback(struct g_audio *g_audio); + +#endif /* __U_AUDIO_H */ diff --git a/drivers/usb/gadget/function/u_fs.h b/drivers/usb/gadget/function/u_fs.h index 4378cc2fcac3..540f1c48c1a8 100644 --- a/drivers/usb/gadget/function/u_fs.h +++ b/drivers/usb/gadget/function/u_fs.h @@ -216,6 +216,9 @@ struct ffs_data { #define FFS_FL_CALL_CLOSED_CALLBACK 0 #define FFS_FL_BOUND 1 + /* For waking up blocked threads when function is enabled. */ + wait_queue_head_t wait; + /* Active function */ struct ffs_function *func; diff --git a/drivers/usb/gadget/function/u_uac1.h b/drivers/usb/gadget/function/u_uac1.h index 5c2ac8e8456d..6f188fd8633f 100644 --- a/drivers/usb/gadget/function/u_uac1.h +++ b/drivers/usb/gadget/function/u_uac1.h @@ -1,82 +1,41 @@ /* - * u_uac1.h -- interface to USB gadget "ALSA AUDIO" utilities + * u_uac1.h - Utility definitions for UAC1 function * - * Copyright (C) 2008 Bryan Wu <cooloney@kernel.org> - * Copyright (C) 2008 Analog Devices, Inc + * Copyright (C) 2016 Ruslan Bilovol <ruslan.bilovol@gmail.com> * - * Enter bugs at http://blackfin.uclinux.org/ - * - * Licensed under the GPL-2 or later. + * 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 __U_AUDIO_H -#define __U_AUDIO_H +#ifndef __U_UAC1_H +#define __U_UAC1_H -#include <linux/device.h> -#include <linux/err.h> -#include <linux/usb/audio.h> #include <linux/usb/composite.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> - -#define FILE_PCM_PLAYBACK "/dev/snd/pcmC0D0p" -#define FILE_PCM_CAPTURE "/dev/snd/pcmC0D0c" -#define FILE_CONTROL "/dev/snd/controlC0" - #define UAC1_OUT_EP_MAX_PACKET_SIZE 200 -#define UAC1_REQ_COUNT 256 -#define UAC1_AUDIO_BUF_SIZE 48000 - -/* - * This represents the USB side of an audio card device, managed by a USB - * function which provides control and stream interfaces. - */ - -struct gaudio_snd_dev { - struct gaudio *card; - struct file *filp; - struct snd_pcm_substream *substream; - int access; - int format; - int channels; - int rate; -}; - -struct gaudio { - struct usb_function func; - struct usb_gadget *gadget; +#define UAC1_DEF_CCHMASK 0x3 +#define UAC1_DEF_CSRATE 48000 +#define UAC1_DEF_CSSIZE 2 +#define UAC1_DEF_PCHMASK 0x3 +#define UAC1_DEF_PSRATE 48000 +#define UAC1_DEF_PSSIZE 2 +#define UAC1_DEF_REQ_NUM 2 - /* ALSA sound device interfaces */ - struct gaudio_snd_dev control; - struct gaudio_snd_dev playback; - struct gaudio_snd_dev capture; - - /* TODO */ -}; struct f_uac1_opts { struct usb_function_instance func_inst; - int req_buf_size; - int req_count; - int audio_buf_size; - char *fn_play; - char *fn_cap; - char *fn_cntl; + int c_chmask; + int c_srate; + int c_ssize; + int p_chmask; + int p_srate; + int p_ssize; + int req_number; unsigned bound:1; - unsigned fn_play_alloc:1; - unsigned fn_cap_alloc:1; - unsigned fn_cntl_alloc:1; + struct mutex lock; int refcnt; }; -int gaudio_setup(struct gaudio *card); -void gaudio_cleanup(struct gaudio *the_card); - -size_t u_audio_playback(struct gaudio *card, void *buf, size_t count); -int u_audio_get_playback_channels(struct gaudio *card); -int u_audio_get_playback_rate(struct gaudio *card); - -#endif /* __U_AUDIO_H */ +#endif /* __U_UAC1_H */ diff --git a/drivers/usb/gadget/function/u_uac1.c b/drivers/usb/gadget/function/u_uac1_legacy.c index c78c84138a28..8aa76b4dc117 100644 --- a/drivers/usb/gadget/function/u_uac1.c +++ b/drivers/usb/gadget/function/u_uac1_legacy.c @@ -18,7 +18,7 @@ #include <linux/random.h> #include <linux/syscalls.h> -#include "u_uac1.h" +#include "u_uac1_legacy.h" /* * This component encapsulates the ALSA devices for USB audio gadget @@ -205,10 +205,11 @@ static int gaudio_open_snd_dev(struct gaudio *card) { struct snd_pcm_file *pcm_file; struct gaudio_snd_dev *snd; - struct f_uac1_opts *opts; + struct f_uac1_legacy_opts *opts; char *fn_play, *fn_cap, *fn_cntl; - opts = container_of(card->func.fi, struct f_uac1_opts, func_inst); + opts = container_of(card->func.fi, struct f_uac1_legacy_opts, + func_inst); fn_play = opts->fn_play; fn_cap = opts->fn_cap; fn_cntl = opts->fn_cntl; diff --git a/drivers/usb/gadget/function/u_uac1_legacy.h b/drivers/usb/gadget/function/u_uac1_legacy.h new file mode 100644 index 000000000000..d715b1af56a4 --- /dev/null +++ b/drivers/usb/gadget/function/u_uac1_legacy.h @@ -0,0 +1,82 @@ +/* + * u_uac1.h -- interface to USB gadget "ALSA AUDIO" utilities + * + * Copyright (C) 2008 Bryan Wu <cooloney@kernel.org> + * Copyright (C) 2008 Analog Devices, Inc + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +#ifndef __U_UAC1_LEGACY_H +#define __U_UAC1_LEGACY_H + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/usb/audio.h> +#include <linux/usb/composite.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> + +#define FILE_PCM_PLAYBACK "/dev/snd/pcmC0D0p" +#define FILE_PCM_CAPTURE "/dev/snd/pcmC0D0c" +#define FILE_CONTROL "/dev/snd/controlC0" + +#define UAC1_OUT_EP_MAX_PACKET_SIZE 200 +#define UAC1_REQ_COUNT 256 +#define UAC1_AUDIO_BUF_SIZE 48000 + +/* + * This represents the USB side of an audio card device, managed by a USB + * function which provides control and stream interfaces. + */ + +struct gaudio_snd_dev { + struct gaudio *card; + struct file *filp; + struct snd_pcm_substream *substream; + int access; + int format; + int channels; + int rate; +}; + +struct gaudio { + struct usb_function func; + struct usb_gadget *gadget; + + /* ALSA sound device interfaces */ + struct gaudio_snd_dev control; + struct gaudio_snd_dev playback; + struct gaudio_snd_dev capture; + + /* TODO */ +}; + +struct f_uac1_legacy_opts { + struct usb_function_instance func_inst; + int req_buf_size; + int req_count; + int audio_buf_size; + char *fn_play; + char *fn_cap; + char *fn_cntl; + unsigned bound:1; + unsigned fn_play_alloc:1; + unsigned fn_cap_alloc:1; + unsigned fn_cntl_alloc:1; + struct mutex lock; + int refcnt; +}; + +int gaudio_setup(struct gaudio *card); +void gaudio_cleanup(struct gaudio *the_card); + +size_t u_audio_playback(struct gaudio *card, void *buf, size_t count); +int u_audio_get_playback_channels(struct gaudio *card); +int u_audio_get_playback_rate(struct gaudio *card); + +#endif /* __U_UAC1_LEGACY_H */ diff --git a/drivers/usb/gadget/legacy/Kconfig b/drivers/usb/gadget/legacy/Kconfig index 0b36878eb5fd..a12fb459dbd9 100644 --- a/drivers/usb/gadget/legacy/Kconfig +++ b/drivers/usb/gadget/legacy/Kconfig @@ -54,8 +54,10 @@ config USB_AUDIO depends on SND select USB_LIBCOMPOSITE select SND_PCM - select USB_F_UAC1 if GADGET_UAC1 + select USB_F_UAC1 if (GADGET_UAC1 && !GADGET_UAC1_LEGACY) + select USB_F_UAC1_LEGACY if (GADGET_UAC1 && GADGET_UAC1_LEGACY) select USB_F_UAC2 if !GADGET_UAC1 + select USB_U_AUDIO if (USB_F_UAC2 || USB_F_UAC1) help This Gadget Audio driver is compatible with USB Audio Class specification 2.0. It implements 1 AudioControl interface, @@ -73,10 +75,17 @@ config USB_AUDIO dynamically linked module called "g_audio". config GADGET_UAC1 - bool "UAC 1.0 (Legacy)" + bool "UAC 1.0" depends on USB_AUDIO help - If you instead want older UAC Spec-1.0 driver that also has audio + If you instead want older USB Audio Class specification 1.0 support + with similar driver capabilities. + +config GADGET_UAC1_LEGACY + bool "UAC 1.0 (Legacy)" + depends on GADGET_UAC1 + help + If you instead want legacy UAC Spec-1.0 driver that also has audio paths hardwired to the Audio codec chip on-board and doesn't work without one. diff --git a/drivers/usb/gadget/legacy/audio.c b/drivers/usb/gadget/legacy/audio.c index 8a39f42a4d56..1f5cdbe162df 100644 --- a/drivers/usb/gadget/legacy/audio.c +++ b/drivers/usb/gadget/legacy/audio.c @@ -53,8 +53,41 @@ static int c_ssize = UAC2_DEF_CSSIZE; module_param(c_ssize, uint, S_IRUGO); MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)"); #else +#ifndef CONFIG_GADGET_UAC1_LEGACY #include "u_uac1.h" +/* Playback(USB-IN) Default Stereo - Fl/Fr */ +static int p_chmask = UAC1_DEF_PCHMASK; +module_param(p_chmask, uint, S_IRUGO); +MODULE_PARM_DESC(p_chmask, "Playback Channel Mask"); + +/* Playback Default 48 KHz */ +static int p_srate = UAC1_DEF_PSRATE; +module_param(p_srate, uint, S_IRUGO); +MODULE_PARM_DESC(p_srate, "Playback Sampling Rate"); + +/* Playback Default 16bits/sample */ +static int p_ssize = UAC1_DEF_PSSIZE; +module_param(p_ssize, uint, S_IRUGO); +MODULE_PARM_DESC(p_ssize, "Playback Sample Size(bytes)"); + +/* Capture(USB-OUT) Default Stereo - Fl/Fr */ +static int c_chmask = UAC1_DEF_CCHMASK; +module_param(c_chmask, uint, S_IRUGO); +MODULE_PARM_DESC(c_chmask, "Capture Channel Mask"); + +/* Capture Default 48 KHz */ +static int c_srate = UAC1_DEF_CSRATE; +module_param(c_srate, uint, S_IRUGO); +MODULE_PARM_DESC(c_srate, "Capture Sampling Rate"); + +/* Capture Default 16bits/sample */ +static int c_ssize = UAC1_DEF_CSSIZE; +module_param(c_ssize, uint, S_IRUGO); +MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)"); +#else /* CONFIG_GADGET_UAC1_LEGACY */ +#include "u_uac1_legacy.h" + static char *fn_play = FILE_PCM_PLAYBACK; module_param(fn_play, charp, S_IRUGO); MODULE_PARM_DESC(fn_play, "Playback PCM device file name"); @@ -78,6 +111,7 @@ MODULE_PARM_DESC(req_count, "ISO OUT endpoint request count"); static int audio_buf_size = UAC1_AUDIO_BUF_SIZE; module_param(audio_buf_size, int, S_IRUGO); MODULE_PARM_DESC(audio_buf_size, "Audio buffer size"); +#endif /* CONFIG_GADGET_UAC1_LEGACY */ #endif /* string IDs are assigned dynamically */ @@ -125,7 +159,7 @@ static struct usb_device_descriptor device_desc = { /* .bcdUSB = DYNAMIC */ -#ifdef CONFIG_GADGET_UAC1 +#ifdef CONFIG_GADGET_UAC1_LEGACY .bDeviceClass = USB_CLASS_PER_INTERFACE, .bDeviceSubClass = 0, .bDeviceProtocol = 0, @@ -207,7 +241,11 @@ static int audio_bind(struct usb_composite_dev *cdev) #ifndef CONFIG_GADGET_UAC1 struct f_uac2_opts *uac2_opts; #else +#ifndef CONFIG_GADGET_UAC1_LEGACY struct f_uac1_opts *uac1_opts; +#else + struct f_uac1_legacy_opts *uac1_opts; +#endif #endif int status; @@ -216,7 +254,11 @@ static int audio_bind(struct usb_composite_dev *cdev) if (IS_ERR(fi_uac2)) return PTR_ERR(fi_uac2); #else +#ifndef CONFIG_GADGET_UAC1_LEGACY fi_uac1 = usb_get_function_instance("uac1"); +#else + fi_uac1 = usb_get_function_instance("uac1_legacy"); +#endif if (IS_ERR(fi_uac1)) return PTR_ERR(fi_uac1); #endif @@ -231,13 +273,24 @@ static int audio_bind(struct usb_composite_dev *cdev) uac2_opts->c_ssize = c_ssize; uac2_opts->req_number = UAC2_DEF_REQ_NUM; #else +#ifndef CONFIG_GADGET_UAC1_LEGACY uac1_opts = container_of(fi_uac1, struct f_uac1_opts, func_inst); + uac1_opts->p_chmask = p_chmask; + uac1_opts->p_srate = p_srate; + uac1_opts->p_ssize = p_ssize; + uac1_opts->c_chmask = c_chmask; + uac1_opts->c_srate = c_srate; + uac1_opts->c_ssize = c_ssize; + uac1_opts->req_number = UAC1_DEF_REQ_NUM; +#else /* CONFIG_GADGET_UAC1_LEGACY */ + uac1_opts = container_of(fi_uac1, struct f_uac1_legacy_opts, func_inst); uac1_opts->fn_play = fn_play; uac1_opts->fn_cap = fn_cap; uac1_opts->fn_cntl = fn_cntl; uac1_opts->req_buf_size = req_buf_size; uac1_opts->req_count = req_count; uac1_opts->audio_buf_size = audio_buf_size; +#endif /* CONFIG_GADGET_UAC1_LEGACY */ #endif status = usb_string_ids_tab(cdev, strings_dev); diff --git a/drivers/usb/gadget/legacy/mass_storage.c b/drivers/usb/gadget/legacy/mass_storage.c index 125974f32f50..e99ab57ee3e5 100644 --- a/drivers/usb/gadget/legacy/mass_storage.c +++ b/drivers/usb/gadget/legacy/mass_storage.c @@ -210,7 +210,6 @@ static int msg_bind(struct usb_composite_dev *cdev) usb_composite_overwrite_options(cdev, &coverwrite); dev_info(&cdev->gadget->dev, DRIVER_DESC ", version: " DRIVER_VERSION "\n"); - set_bit(0, &msg_registered); return 0; fail_otg_desc: @@ -257,7 +256,12 @@ MODULE_LICENSE("GPL"); static int __init msg_init(void) { - return usb_composite_probe(&msg_driver); + int ret; + + ret = usb_composite_probe(&msg_driver); + set_bit(0, &msg_registered); + + return ret; } module_init(msg_init); diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index 1c14c283cc47..9ffb11ec9ed9 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -55,7 +55,7 @@ config USB_LPC32XX config USB_ATMEL_USBA tristate "Atmel USBA" - depends on ((AVR32 && !OF) || ARCH_AT91) + depends on ARCH_AT91 help USBA is the integrated high-speed USB Device controller on the AT32AP700x, some AT91SAM9 and AT91CAP9 processors from Atmel. @@ -256,7 +256,7 @@ config USB_MV_U3D controller, which support super speed USB peripheral. config USB_SNP_CORE - depends on USB_AMD5536UDC + depends on (USB_AMD5536UDC || USB_SNP_UDC_PLAT) tristate help This enables core driver support for Synopsys USB 2.0 Device @@ -269,6 +269,20 @@ config USB_SNP_CORE This IP is different to the High Speed OTG IP that can be enabled by selecting USB_DWC2 or USB_DWC3 options. +config USB_SNP_UDC_PLAT + tristate "Synopsys USB 2.0 Device controller" + depends on (USB_GADGET && OF) + select USB_GADGET_DUALSPEED + select USB_SNP_CORE + default ARCH_BCM_IPROC + help + This adds Platform Device support for Synopsys Designware core + AHB subsystem USB2.0 Device Controller (UDC). + + This driver works with UDCs integrated into Broadcom's Northstar2 + and Cygnus SoCs. + + If unsure, say N. # # Controllers available in both integrated and discrete versions # diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile index 626e1f1c62da..ea9e1c7f1923 100644 --- a/drivers/usb/gadget/udc/Makefile +++ b/drivers/usb/gadget/udc/Makefile @@ -10,7 +10,7 @@ obj-$(CONFIG_USB_GADGET) += udc-core.o obj-$(CONFIG_USB_DUMMY_HCD) += dummy_hcd.o obj-$(CONFIG_USB_NET2272) += net2272.o obj-$(CONFIG_USB_NET2280) += net2280.o -obj-$(CONFIG_USB_SNP_CORE) += amd5536udc.o +obj-$(CONFIG_USB_SNP_CORE) += snps_udc_core.o obj-$(CONFIG_USB_AMD5536UDC) += amd5536udc_pci.o obj-$(CONFIG_USB_PXA25X) += pxa25x_udc.o obj-$(CONFIG_USB_PXA27X) += pxa27x_udc.o @@ -37,4 +37,5 @@ obj-$(CONFIG_USB_FOTG210_UDC) += fotg210-udc.o obj-$(CONFIG_USB_MV_U3D) += mv_u3d_core.o obj-$(CONFIG_USB_GR_UDC) += gr_udc.o obj-$(CONFIG_USB_GADGET_XILINX) += udc-xilinx.o +obj-$(CONFIG_USB_SNP_UDC_PLAT) += snps_udc_plat.o obj-$(CONFIG_USB_BDC_UDC) += bdc/ diff --git a/drivers/usb/gadget/udc/amd5536udc.h b/drivers/usb/gadget/udc/amd5536udc.h index fae49bf3833e..4fe22d432af2 100644 --- a/drivers/usb/gadget/udc/amd5536udc.h +++ b/drivers/usb/gadget/udc/amd5536udc.h @@ -16,6 +16,7 @@ /* debug control */ /* #define UDC_VERBOSE */ +#include <linux/extcon.h> #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> @@ -28,6 +29,9 @@ #define UDC_HSA0_REV 1 #define UDC_HSB1_REV 2 +/* Broadcom chip rev. */ +#define UDC_BCM_REV 10 + /* * SETUP usb commands * needed, because some SETUP's are handled in hw, but must be passed to @@ -112,6 +116,7 @@ #define UDC_DEVCTL_BRLEN_MASK 0x00ff0000 #define UDC_DEVCTL_BRLEN_OFS 16 +#define UDC_DEVCTL_SRX_FLUSH 14 #define UDC_DEVCTL_CSR_DONE 13 #define UDC_DEVCTL_DEVNAK 12 #define UDC_DEVCTL_SD 10 @@ -563,6 +568,16 @@ struct udc { u16 cur_config; u16 cur_intf; u16 cur_alt; + + /* for platform device and extcon support */ + struct device *dev; + struct phy *udc_phy; + struct extcon_dev *edev; + struct extcon_specific_cable_nb extcon_nb; + struct notifier_block nb; + struct delayed_work drd_work; + struct workqueue_struct *drd_wq; + u32 conn_type; }; #define to_amd5536_udc(g) (container_of((g), struct udc, gadget)) @@ -578,6 +593,7 @@ int udc_enable_dev_setup_interrupts(struct udc *dev); int udc_mask_unused_interrupts(struct udc *dev); irqreturn_t udc_irq(int irq, void *pdev); void gadget_release(struct device *pdev); +void empty_req_queue(struct udc_ep *ep); void udc_basic_init(struct udc *dev); void free_dma_pools(struct udc *dev); int init_dma_pools(struct udc *dev); @@ -639,7 +655,7 @@ MODULE_PARM_DESC(use_fullspeed, "true for fullspeed only"); /* debug macros ------------------------------------------------------------*/ -#define DBG(udc , args...) dev_dbg(&(udc)->pdev->dev, args) +#define DBG(udc , args...) dev_dbg(udc->dev, args) #ifdef UDC_VERBOSE #define VDBG DBG diff --git a/drivers/usb/gadget/udc/amd5536udc_pci.c b/drivers/usb/gadget/udc/amd5536udc_pci.c index 2a2d0a96fe24..57a13f080a79 100644 --- a/drivers/usb/gadget/udc/amd5536udc_pci.c +++ b/drivers/usb/gadget/udc/amd5536udc_pci.c @@ -168,6 +168,7 @@ static int udc_pci_probe( dev->phys_addr = resource; dev->irq = pdev->irq; dev->pdev = pdev; + dev->dev = &pdev->dev; /* general probing */ if (udc_probe(dev)) { diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c index 3ccc34176a5a..98d71400f8a1 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.c +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -152,7 +152,7 @@ static int regs_dbg_open(struct inode *inode, struct file *file) spin_lock_irq(&udc->lock); for (i = 0; i < inode->i_size / 4; i++) - data[i] = usba_io_readl(udc->regs + i * 4); + data[i] = readl_relaxed(udc->regs + i * 4); spin_unlock_irq(&udc->lock); file->private_data = data; @@ -1369,7 +1369,7 @@ static int handle_ep0_setup(struct usba_udc *udc, struct usba_ep *ep, if (crq->wLength != cpu_to_le16(sizeof(status))) goto stall; ep->state = DATA_STAGE_IN; - usba_io_writew(status, ep->fifo); + writew_relaxed(status, ep->fifo); usba_ep_writel(ep, SET_STA, USBA_TX_PK_RDY); break; } diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.h b/drivers/usb/gadget/udc/atmel_usba_udc.h index 9551b704bfd3..f8ebe0389bd4 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.h +++ b/drivers/usb/gadget/udc/atmel_usba_udc.h @@ -43,13 +43,8 @@ #define USBA_REMOTE_WAKE_UP (1 << 10) #define USBA_PULLD_DIS (1 << 11) -#if defined(CONFIG_AVR32) -#define USBA_ENABLE_MASK USBA_EN_USBA -#define USBA_DISABLE_MASK 0 -#elif defined(CONFIG_ARCH_AT91) #define USBA_ENABLE_MASK (USBA_EN_USBA | USBA_PULLD_DIS) #define USBA_DISABLE_MASK USBA_DETACH -#endif /* CONFIG_ARCH_AT91 */ /* Bitfields in FNUM */ #define USBA_MICRO_FRAME_NUM_OFFSET 0 @@ -191,28 +186,18 @@ | USBA_BF(name, value)) /* Register access macros */ -#ifdef CONFIG_AVR32 -#define usba_io_readl __raw_readl -#define usba_io_writel __raw_writel -#define usba_io_writew __raw_writew -#else -#define usba_io_readl readl_relaxed -#define usba_io_writel writel_relaxed -#define usba_io_writew writew_relaxed -#endif - #define usba_readl(udc, reg) \ - usba_io_readl((udc)->regs + USBA_##reg) + readl_relaxed((udc)->regs + USBA_##reg) #define usba_writel(udc, reg, value) \ - usba_io_writel((value), (udc)->regs + USBA_##reg) + writel_relaxed((value), (udc)->regs + USBA_##reg) #define usba_ep_readl(ep, reg) \ - usba_io_readl((ep)->ep_regs + USBA_EPT_##reg) + readl_relaxed((ep)->ep_regs + USBA_EPT_##reg) #define usba_ep_writel(ep, reg, value) \ - usba_io_writel((value), (ep)->ep_regs + USBA_EPT_##reg) + writel_relaxed((value), (ep)->ep_regs + USBA_EPT_##reg) #define usba_dma_readl(ep, reg) \ - usba_io_readl((ep)->dma_regs + USBA_DMA_##reg) + readl_relaxed((ep)->dma_regs + USBA_DMA_##reg) #define usba_dma_writel(ep, reg, value) \ - usba_io_writel((value), (ep)->dma_regs + USBA_DMA_##reg) + writel_relaxed((value), (ep)->dma_regs + USBA_DMA_##reg) /* Calculate base address for a given endpoint or DMA controller */ #define USBA_EPT_BASE(x) (0x100 + (x) * 0x20) diff --git a/drivers/usb/gadget/udc/bdc/bdc_core.c b/drivers/usb/gadget/udc/bdc/bdc_core.c index ccb9c213cc9f..e9bd8d4abca0 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_core.c +++ b/drivers/usb/gadget/udc/bdc/bdc_core.c @@ -475,7 +475,7 @@ static int bdc_probe(struct platform_device *pdev) bdc->dev = dev; dev_dbg(bdc->dev, "bdc->regs: %p irq=%d\n", bdc->regs, bdc->irq); - temp = bdc_readl(bdc->regs, BDC_BDCSC); + temp = bdc_readl(bdc->regs, BDC_BDCCAP1); if ((temp & BDC_P64) && !dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64))) { dev_dbg(bdc->dev, "Using 64-bit address\n"); diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index efce68e9a8e0..e6f04eee95c4 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -23,6 +23,7 @@ #include <linux/list.h> #include <linux/err.h> #include <linux/dma-mapping.h> +#include <linux/sched/task_stack.h> #include <linux/workqueue.h> #include <linux/usb/ch9.h> @@ -139,10 +140,8 @@ int usb_ep_disable(struct usb_ep *ep) goto out; ret = ep->ops->disable(ep); - if (ret) { - ret = ret; + if (ret) goto out; - } ep->enabled = false; @@ -798,6 +797,14 @@ int usb_gadget_map_request_by_dev(struct device *dev, req->num_mapped_sgs = mapped; } else { + if (is_vmalloc_addr(req->buf)) { + dev_err(dev, "buffer is not dma capable\n"); + return -EFAULT; + } else if (object_is_on_stack(req->buf)) { + dev_err(dev, "buffer is on stack\n"); + return -EFAULT; + } + req->dma = dma_map_single(dev, req->buf, req->length, is_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE); @@ -1058,6 +1065,23 @@ static inline void usb_gadget_udc_stop(struct usb_udc *udc) } /** + * usb_gadget_udc_set_speed - tells usb device controller speed supported by + * current driver + * @udc: The device we want to set maximum speed + * @speed: The maximum speed to allowed to run + * + * This call is issued by the UDC Class driver before calling + * usb_gadget_udc_start() in order to make sure that we don't try to + * connect on speeds the gadget driver doesn't support. + */ +static inline void usb_gadget_udc_set_speed(struct usb_udc *udc, + enum usb_device_speed speed) +{ + if (udc->gadget->ops->udc_set_speed) + udc->gadget->ops->udc_set_speed(udc->gadget, speed); +} + +/** * usb_udc_release - release the usb_udc struct * @dev: the dev member within usb_udc * @@ -1290,6 +1314,9 @@ static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *dri udc->dev.driver = &driver->driver; udc->gadget->dev.driver = &driver->driver; + if (driver->max_speed < udc->gadget->max_speed) + usb_gadget_udc_set_speed(udc, driver->max_speed); + ret = driver->bind(udc->gadget, driver); if (ret) goto err1; @@ -1442,6 +1469,18 @@ static ssize_t state_show(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RO(state); +static ssize_t function_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_udc *udc = container_of(dev, struct usb_udc, dev); + struct usb_gadget_driver *drv = udc->driver; + + if (!drv || !drv->function) + return 0; + return scnprintf(buf, PAGE_SIZE, "%s\n", drv->function); +} +static DEVICE_ATTR_RO(function); + #define USB_UDC_SPEED_ATTR(name, param) \ ssize_t name##_show(struct device *dev, \ struct device_attribute *attr, char *buf) \ @@ -1477,6 +1516,7 @@ static struct attribute *usb_udc_attrs[] = { &dev_attr_srp.attr, &dev_attr_soft_connect.attr, &dev_attr_state.attr, + &dev_attr_function.attr, &dev_attr_current_speed.attr, &dev_attr_maximum_speed.attr, diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index 7635fd7cc328..3c3760315910 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -881,22 +881,6 @@ static int dummy_pullup(struct usb_gadget *_gadget, int value) unsigned long flags; dum = gadget_dev_to_dummy(&_gadget->dev); - - if (value && dum->driver) { - if (mod_data.is_super_speed) - dum->gadget.speed = dum->driver->max_speed; - else if (mod_data.is_high_speed) - dum->gadget.speed = min_t(u8, USB_SPEED_HIGH, - dum->driver->max_speed); - else - dum->gadget.speed = USB_SPEED_FULL; - dummy_udc_update_ep0(dum); - - if (dum->gadget.speed < dum->driver->max_speed) - dev_dbg(udc_dev(dum), "This device can perform faster" - " if you connect it to a %s port...\n", - usb_speed_string(dum->driver->max_speed)); - } dum_hcd = gadget_to_dummy_hcd(_gadget); spin_lock_irqsave(&dum->lock, flags); @@ -908,6 +892,28 @@ static int dummy_pullup(struct usb_gadget *_gadget, int value) return 0; } +static void dummy_udc_set_speed(struct usb_gadget *_gadget, + enum usb_device_speed speed) +{ + struct dummy *dum; + + dum = gadget_dev_to_dummy(&_gadget->dev); + + if (mod_data.is_super_speed) + dum->gadget.speed = min_t(u8, USB_SPEED_SUPER, speed); + else if (mod_data.is_high_speed) + dum->gadget.speed = min_t(u8, USB_SPEED_HIGH, speed); + else + dum->gadget.speed = USB_SPEED_FULL; + + dummy_udc_update_ep0(dum); + + if (dum->gadget.speed < speed) + dev_dbg(udc_dev(dum), "This device can perform faster" + " if you connect it to a %s port...\n", + usb_speed_string(speed)); +} + static int dummy_udc_start(struct usb_gadget *g, struct usb_gadget_driver *driver); static int dummy_udc_stop(struct usb_gadget *g); @@ -919,6 +925,7 @@ static const struct usb_gadget_ops dummy_ops = { .pullup = dummy_pullup, .udc_start = dummy_udc_start, .udc_stop = dummy_udc_stop, + .udc_set_speed = dummy_udc_set_speed, }; /*-------------------------------------------------------------------------*/ diff --git a/drivers/usb/gadget/udc/mv_udc_core.c b/drivers/usb/gadget/udc/mv_udc_core.c index 76f56c5762f9..8a708d0a1042 100644 --- a/drivers/usb/gadget/udc/mv_udc_core.c +++ b/drivers/usb/gadget/udc/mv_udc_core.c @@ -960,9 +960,9 @@ static const struct usb_ep_ops mv_ep_ops = { .fifo_flush = mv_ep_fifo_flush, /* flush fifo */ }; -static void udc_clock_enable(struct mv_udc *udc) +static int udc_clock_enable(struct mv_udc *udc) { - clk_prepare_enable(udc->clk); + return clk_prepare_enable(udc->clk); } static void udc_clock_disable(struct mv_udc *udc) @@ -1070,7 +1070,10 @@ static int mv_udc_enable_internal(struct mv_udc *udc) return 0; dev_dbg(&udc->dev->dev, "enable udc\n"); - udc_clock_enable(udc); + retval = udc_clock_enable(udc); + if (retval) + return retval; + if (udc->pdata->phy_init) { retval = udc->pdata->phy_init(udc->phy_regs); if (retval) { diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index f2cbd7f8005e..f608c1f85e61 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -3566,7 +3566,6 @@ static void net2280_remove(struct pci_dev *pdev) BUG_ON(dev->driver); /* then clean up the resources we allocated during probe() */ - net2280_led_shutdown(dev); if (dev->requests) { int i; for (i = 1; i < 5; i++) { @@ -3581,8 +3580,10 @@ static void net2280_remove(struct pci_dev *pdev) free_irq(pdev->irq, dev); if (dev->quirks & PLX_PCIE) pci_disable_msi(pdev); - if (dev->regs) + if (dev->regs) { + net2280_led_shutdown(dev); iounmap(dev->regs); + } if (dev->region) release_mem_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); diff --git a/drivers/usb/gadget/udc/renesas_usb3.c b/drivers/usb/gadget/udc/renesas_usb3.c index cd4c88529721..d8278322d5ac 100644 --- a/drivers/usb/gadget/udc/renesas_usb3.c +++ b/drivers/usb/gadget/udc/renesas_usb3.c @@ -9,6 +9,7 @@ */ #include <linux/delay.h> +#include <linux/dma-mapping.h> #include <linux/err.h> #include <linux/extcon.h> #include <linux/interrupt.h> @@ -27,6 +28,8 @@ #define USB3_AXI_INT_ENA 0x00c #define USB3_DMA_INT_STA 0x010 #define USB3_DMA_INT_ENA 0x014 +#define USB3_DMA_CH0_CON(n) (0x030 + ((n) - 1) * 0x10) /* n = 1 to 4 */ +#define USB3_DMA_CH0_PRD_ADR(n) (0x034 + ((n) - 1) * 0x10) /* n = 1 to 4 */ #define USB3_USB_COM_CON 0x200 #define USB3_USB20_CON 0x204 #define USB3_USB30_CON 0x208 @@ -64,6 +67,22 @@ /* AXI_INT_ENA and AXI_INT_STA */ #define AXI_INT_DMAINT BIT(31) #define AXI_INT_EPCINT BIT(30) +/* PRD's n = from 1 to 4 */ +#define AXI_INT_PRDEN_CLR_STA_SHIFT(n) (16 + (n) - 1) +#define AXI_INT_PRDERR_STA_SHIFT(n) (0 + (n) - 1) +#define AXI_INT_PRDEN_CLR_STA(n) (1 << AXI_INT_PRDEN_CLR_STA_SHIFT(n)) +#define AXI_INT_PRDERR_STA(n) (1 << AXI_INT_PRDERR_STA_SHIFT(n)) + +/* DMA_INT_ENA and DMA_INT_STA */ +#define DMA_INT(n) BIT(n) + +/* DMA_CH0_CONn */ +#define DMA_CON_PIPE_DIR BIT(15) /* 1: In Transfer */ +#define DMA_CON_PIPE_NO_SHIFT 8 +#define DMA_CON_PIPE_NO_MASK GENMASK(12, DMA_CON_PIPE_NO_SHIFT) +#define DMA_COM_PIPE_NO(n) (((n) << DMA_CON_PIPE_NO_SHIFT) & \ + DMA_CON_PIPE_NO_MASK) +#define DMA_CON_PRD_EN BIT(0) /* LCLKSEL */ #define LCLKSEL_LSEL BIT(18) @@ -231,8 +250,50 @@ #define USB3_EP0_BUF_SIZE 8 #define USB3_MAX_NUM_PIPES 30 #define USB3_WAIT_US 3 +#define USB3_DMA_NUM_SETTING_AREA 4 +/* + * To avoid double-meaning of "0" (xferred 65536 bytes or received zlp if + * buffer size is 65536), this driver uses the maximum size per a entry is + * 32768 bytes. + */ +#define USB3_DMA_MAX_XFER_SIZE 32768 +#define USB3_DMA_PRD_SIZE 4096 struct renesas_usb3; + +/* Physical Region Descriptor Table */ +struct renesas_usb3_prd { + u32 word1; +#define USB3_PRD1_E BIT(30) /* the end of chain */ +#define USB3_PRD1_U BIT(29) /* completion of transfer */ +#define USB3_PRD1_D BIT(28) /* Error occurred */ +#define USB3_PRD1_INT BIT(27) /* Interrupt occurred */ +#define USB3_PRD1_LST BIT(26) /* Last Packet */ +#define USB3_PRD1_B_INC BIT(24) +#define USB3_PRD1_MPS_8 0 +#define USB3_PRD1_MPS_16 BIT(21) +#define USB3_PRD1_MPS_32 BIT(22) +#define USB3_PRD1_MPS_64 (BIT(22) | BIT(21)) +#define USB3_PRD1_MPS_512 BIT(23) +#define USB3_PRD1_MPS_1024 (BIT(23) | BIT(21)) +#define USB3_PRD1_MPS_RESERVED (BIT(23) | BIT(22) | BIT(21)) +#define USB3_PRD1_SIZE_MASK GENMASK(15, 0) + + u32 bap; +}; +#define USB3_DMA_NUM_PRD_ENTRIES (USB3_DMA_PRD_SIZE / \ + sizeof(struct renesas_usb3_prd)) +#define USB3_DMA_MAX_XFER_SIZE_ALL_PRDS (USB3_DMA_PRD_SIZE / \ + sizeof(struct renesas_usb3_prd) * \ + USB3_DMA_MAX_XFER_SIZE) + +struct renesas_usb3_dma { + struct renesas_usb3_prd *prd; + dma_addr_t prd_dma; + int num; /* Setting area number (from 1 to 4) */ + bool used; +}; + struct renesas_usb3_request { struct usb_request req; struct list_head queue; @@ -242,6 +303,7 @@ struct renesas_usb3_request { struct renesas_usb3_ep { struct usb_ep ep; struct renesas_usb3 *usb3; + struct renesas_usb3_dma *dma; int num; char ep_name[USB3_EP_NAME_SIZE]; struct list_head queue; @@ -270,6 +332,8 @@ struct renesas_usb3 { struct renesas_usb3_ep *usb3_ep; int num_usb3_eps; + struct renesas_usb3_dma dma[USB3_DMA_NUM_SETTING_AREA]; + spinlock_t lock; int disabled_count; @@ -298,8 +362,18 @@ struct renesas_usb3 { (i) < (usb3)->num_usb3_eps; \ (i)++, usb3_ep = usb3_get_ep(usb3, (i))) +#define usb3_get_dma(usb3, i) (&(usb3)->dma[i]) +#define usb3_for_each_dma(usb3, dma, i) \ + for ((i) = 0, dma = usb3_get_dma((usb3), (i)); \ + (i) < USB3_DMA_NUM_SETTING_AREA; \ + (i)++, dma = usb3_get_dma((usb3), (i))) + static const char udc_name[] = "renesas_usb3"; +static bool use_dma = 1; +module_param(use_dma, bool, 0644); +MODULE_PARM_DESC(use_dma, "use dedicated DMAC"); + static void usb3_write(struct renesas_usb3 *usb3, u32 data, u32 offs) { iowrite32(data, usb3->reg + offs); @@ -1059,6 +1133,273 @@ static void usb3_start_pipe0(struct renesas_usb3_ep *usb3_ep, usb3_p0_xfer(usb3_ep, usb3_req); } +static void usb3_enable_dma_pipen(struct renesas_usb3 *usb3) +{ + usb3_set_bit(usb3, PN_CON_DATAIF_EN, USB3_PN_CON); +} + +static void usb3_disable_dma_pipen(struct renesas_usb3 *usb3) +{ + usb3_clear_bit(usb3, PN_CON_DATAIF_EN, USB3_PN_CON); +} + +static void usb3_enable_dma_irq(struct renesas_usb3 *usb3, int num) +{ + usb3_set_bit(usb3, DMA_INT(num), USB3_DMA_INT_ENA); +} + +static void usb3_disable_dma_irq(struct renesas_usb3 *usb3, int num) +{ + usb3_clear_bit(usb3, DMA_INT(num), USB3_DMA_INT_ENA); +} + +static u32 usb3_dma_mps_to_prd_word1(struct renesas_usb3_ep *usb3_ep) +{ + switch (usb3_ep->ep.maxpacket) { + case 8: + return USB3_PRD1_MPS_8; + case 16: + return USB3_PRD1_MPS_16; + case 32: + return USB3_PRD1_MPS_32; + case 64: + return USB3_PRD1_MPS_64; + case 512: + return USB3_PRD1_MPS_512; + case 1024: + return USB3_PRD1_MPS_1024; + default: + return USB3_PRD1_MPS_RESERVED; + } +} + +static bool usb3_dma_get_setting_area(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_dma *dma; + int i; + bool ret = false; + + if (usb3_req->req.length > USB3_DMA_MAX_XFER_SIZE_ALL_PRDS) { + dev_dbg(usb3_to_dev(usb3), "%s: the length is too big (%d)\n", + __func__, usb3_req->req.length); + return false; + } + + /* The driver doesn't handle zero-length packet via dmac */ + if (!usb3_req->req.length) + return false; + + if (usb3_dma_mps_to_prd_word1(usb3_ep) == USB3_PRD1_MPS_RESERVED) + return false; + + usb3_for_each_dma(usb3, dma, i) { + if (dma->used) + continue; + + if (usb_gadget_map_request(&usb3->gadget, &usb3_req->req, + usb3_ep->dir_in) < 0) + break; + + dma->used = true; + usb3_ep->dma = dma; + ret = true; + break; + } + + return ret; +} + +static void usb3_dma_put_setting_area(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + int i; + struct renesas_usb3_dma *dma; + + usb3_for_each_dma(usb3, dma, i) { + if (usb3_ep->dma == dma) { + usb_gadget_unmap_request(&usb3->gadget, &usb3_req->req, + usb3_ep->dir_in); + dma->used = false; + usb3_ep->dma = NULL; + break; + } + } +} + +static void usb3_dma_fill_prd(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3_prd *cur_prd = usb3_ep->dma->prd; + u32 remain = usb3_req->req.length; + u32 dma = usb3_req->req.dma; + u32 len; + int i = 0; + + do { + len = min_t(u32, remain, USB3_DMA_MAX_XFER_SIZE) & + USB3_PRD1_SIZE_MASK; + cur_prd->word1 = usb3_dma_mps_to_prd_word1(usb3_ep) | + USB3_PRD1_B_INC | len; + cur_prd->bap = dma; + remain -= len; + dma += len; + if (!remain || (i + 1) < USB3_DMA_NUM_PRD_ENTRIES) + break; + + cur_prd++; + i++; + } while (1); + + cur_prd->word1 |= USB3_PRD1_E | USB3_PRD1_INT; + if (usb3_ep->dir_in) + cur_prd->word1 |= USB3_PRD1_LST; +} + +static void usb3_dma_kick_prd(struct renesas_usb3_ep *usb3_ep) +{ + struct renesas_usb3_dma *dma = usb3_ep->dma; + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + u32 dma_con = DMA_COM_PIPE_NO(usb3_ep->num) | DMA_CON_PRD_EN; + + if (usb3_ep->dir_in) + dma_con |= DMA_CON_PIPE_DIR; + + wmb(); /* prd entries should be in system memory here */ + + usb3_write(usb3, 1 << usb3_ep->num, USB3_DMA_INT_STA); + usb3_write(usb3, AXI_INT_PRDEN_CLR_STA(dma->num) | + AXI_INT_PRDERR_STA(dma->num), USB3_AXI_INT_STA); + + usb3_write(usb3, dma->prd_dma, USB3_DMA_CH0_PRD_ADR(dma->num)); + usb3_write(usb3, dma_con, USB3_DMA_CH0_CON(dma->num)); + usb3_enable_dma_irq(usb3, usb3_ep->num); +} + +static void usb3_dma_stop_prd(struct renesas_usb3_ep *usb3_ep) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + struct renesas_usb3_dma *dma = usb3_ep->dma; + + usb3_disable_dma_irq(usb3, usb3_ep->num); + usb3_write(usb3, 0, USB3_DMA_CH0_CON(dma->num)); +} + +static int usb3_dma_update_status(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3_prd *cur_prd = usb3_ep->dma->prd; + struct usb_request *req = &usb3_req->req; + u32 remain, len; + int i = 0; + int status = 0; + + rmb(); /* The controller updated prd entries */ + + do { + if (cur_prd->word1 & USB3_PRD1_D) + status = -EIO; + if (cur_prd->word1 & USB3_PRD1_E) + len = req->length % USB3_DMA_MAX_XFER_SIZE; + else + len = USB3_DMA_MAX_XFER_SIZE; + remain = cur_prd->word1 & USB3_PRD1_SIZE_MASK; + req->actual += len - remain; + + if (cur_prd->word1 & USB3_PRD1_E || + (i + 1) < USB3_DMA_NUM_PRD_ENTRIES) + break; + + cur_prd++; + i++; + } while (1); + + return status; +} + +static bool usb3_dma_try_start(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + + if (!use_dma) + return false; + + if (usb3_dma_get_setting_area(usb3_ep, usb3_req)) { + usb3_pn_stop(usb3); + usb3_enable_dma_pipen(usb3); + usb3_dma_fill_prd(usb3_ep, usb3_req); + usb3_dma_kick_prd(usb3_ep); + usb3_pn_start(usb3); + return true; + } + + return false; +} + +static int usb3_dma_try_stop(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + unsigned long flags; + int status = 0; + + spin_lock_irqsave(&usb3->lock, flags); + if (!usb3_ep->dma) + goto out; + + if (!usb3_pn_change(usb3, usb3_ep->num)) + usb3_disable_dma_pipen(usb3); + usb3_dma_stop_prd(usb3_ep); + status = usb3_dma_update_status(usb3_ep, usb3_req); + usb3_dma_put_setting_area(usb3_ep, usb3_req); + +out: + spin_unlock_irqrestore(&usb3->lock, flags); + return status; +} + +static int renesas_usb3_dma_free_prd(struct renesas_usb3 *usb3, + struct device *dev) +{ + int i; + struct renesas_usb3_dma *dma; + + usb3_for_each_dma(usb3, dma, i) { + if (dma->prd) { + dma_free_coherent(dev, USB3_DMA_MAX_XFER_SIZE, + dma->prd, dma->prd_dma); + dma->prd = NULL; + } + } + + return 0; +} + +static int renesas_usb3_dma_alloc_prd(struct renesas_usb3 *usb3, + struct device *dev) +{ + int i; + struct renesas_usb3_dma *dma; + + if (!use_dma) + return 0; + + usb3_for_each_dma(usb3, dma, i) { + dma->prd = dma_alloc_coherent(dev, USB3_DMA_PRD_SIZE, + &dma->prd_dma, GFP_KERNEL); + if (!dma->prd) { + renesas_usb3_dma_free_prd(usb3, dev); + return -ENOMEM; + } + dma->num = i + 1; + } + + return 0; +} + static void usb3_start_pipen(struct renesas_usb3_ep *usb3_ep, struct renesas_usb3_request *usb3_req) { @@ -1078,6 +1419,10 @@ static void usb3_start_pipen(struct renesas_usb3_ep *usb3_ep, goto out; usb3_ep->started = true; + + if (usb3_dma_try_start(usb3_ep, usb3_req)) + goto out; + usb3_pn_start(usb3); if (usb3_ep->dir_in) { @@ -1603,12 +1948,49 @@ static void usb3_irq_epc(struct renesas_usb3 *usb3) } } +static void usb3_irq_dma_int(struct renesas_usb3 *usb3, u32 dma_sta) +{ + struct renesas_usb3_ep *usb3_ep; + struct renesas_usb3_request *usb3_req; + int i, status; + + for (i = 0; i < usb3->num_usb3_eps; i++) { + if (!(dma_sta & DMA_INT(i))) + continue; + + usb3_ep = usb3_get_ep(usb3, i); + if (!(usb3_read(usb3, USB3_AXI_INT_STA) & + AXI_INT_PRDEN_CLR_STA(usb3_ep->dma->num))) + continue; + + usb3_req = usb3_get_request(usb3_ep); + status = usb3_dma_try_stop(usb3_ep, usb3_req); + usb3_request_done_pipen(usb3, usb3_ep, usb3_req, status); + } +} + +static void usb3_irq_dma(struct renesas_usb3 *usb3) +{ + u32 dma_sta = usb3_read(usb3, USB3_DMA_INT_STA); + + dma_sta &= usb3_read(usb3, USB3_DMA_INT_ENA); + if (dma_sta) { + usb3_write(usb3, dma_sta, USB3_DMA_INT_STA); + usb3_irq_dma_int(usb3, dma_sta); + } +} + 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_DMAINT) { + usb3_irq_dma(usb3); + ret = IRQ_HANDLED; + } + if (axi_int_sta & AXI_INT_EPCINT) { usb3_irq_epc(usb3); ret = IRQ_HANDLED; @@ -1708,6 +2090,7 @@ static int renesas_usb3_ep_disable(struct usb_ep *_ep) usb3_req = usb3_get_request(usb3_ep); if (!usb3_req) break; + usb3_dma_try_stop(usb3_ep, usb3_req); usb3_request_done(usb3_ep, usb3_req, -ESHUTDOWN); } while (1); @@ -1755,6 +2138,7 @@ static int renesas_usb3_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) dev_dbg(usb3_to_dev(usb3), "ep_dequeue: ep%2d, %u\n", usb3_ep->num, _req->length); + usb3_dma_try_stop(usb3_ep, usb3_req); usb3_request_done_pipen(usb3, usb3_ep, usb3_req, -ECONNRESET); return 0; @@ -1917,6 +2301,7 @@ static int renesas_usb3_remove(struct platform_device *pdev) device_remove_file(&pdev->dev, &dev_attr_role); usb_del_gadget_udc(&usb3->gadget); + renesas_usb3_dma_free_prd(usb3, &pdev->dev); __renesas_usb3_ep_free_request(usb3->ep0_req); @@ -2111,6 +2496,10 @@ static int renesas_usb3_probe(struct platform_device *pdev) if (!usb3->ep0_req) return -ENOMEM; + ret = renesas_usb3_dma_alloc_prd(usb3, &pdev->dev); + if (ret < 0) + goto err_alloc_prd; + ret = usb_add_gadget_udc(&pdev->dev, &usb3->gadget); if (ret < 0) goto err_add_udc; @@ -2129,6 +2518,9 @@ err_dev_create: usb_del_gadget_udc(&usb3->gadget); err_add_udc: + renesas_usb3_dma_free_prd(usb3, &pdev->dev); + +err_alloc_prd: __renesas_usb3_ep_free_request(usb3->ep0_req); return ret; diff --git a/drivers/usb/gadget/udc/amd5536udc.c b/drivers/usb/gadget/udc/snps_udc_core.c index 4ecd2f20ea48..38a165dbf924 100644 --- a/drivers/usb/gadget/udc/amd5536udc.c +++ b/drivers/usb/gadget/udc/snps_udc_core.c @@ -41,7 +41,6 @@ #include "amd5536udc.h" static void udc_tasklet_disconnect(unsigned long); -static void empty_req_queue(struct udc_ep *); static void udc_setup_endpoints(struct udc *dev); static void udc_soft_reset(struct udc *dev); static struct udc_request *udc_alloc_bna_dummy(struct udc_ep *ep); @@ -209,18 +208,18 @@ static void print_regs(struct udc *dev) if (use_dma && use_dma_ppb && !use_dma_ppb_du) { DBG(dev, "DMA mode = PPBNDU (packet per buffer " "WITHOUT desc. update)\n"); - dev_info(&dev->pdev->dev, "DMA mode (%s)\n", "PPBNDU"); + dev_info(dev->dev, "DMA mode (%s)\n", "PPBNDU"); } else if (use_dma && use_dma_ppb && use_dma_ppb_du) { DBG(dev, "DMA mode = PPBDU (packet per buffer " "WITH desc. update)\n"); - dev_info(&dev->pdev->dev, "DMA mode (%s)\n", "PPBDU"); + dev_info(dev->dev, "DMA mode (%s)\n", "PPBDU"); } if (use_dma && use_dma_bufferfill_mode) { DBG(dev, "DMA mode = BF (buffer fill mode)\n"); - dev_info(&dev->pdev->dev, "DMA mode (%s)\n", "BF"); + dev_info(dev->dev, "DMA mode (%s)\n", "BF"); } if (!use_dma) - dev_info(&dev->pdev->dev, "FIFO mode\n"); + dev_info(dev->dev, "FIFO mode\n"); DBG(dev, "-------------------------------------------------------\n"); } @@ -1244,7 +1243,7 @@ finished: } /* Empty request queue of an endpoint; caller holds spinlock */ -static void empty_req_queue(struct udc_ep *ep) +void empty_req_queue(struct udc_ep *ep) { struct udc_request *req; @@ -1256,6 +1255,7 @@ static void empty_req_queue(struct udc_ep *ep) complete_req(ep, req, -ESHUTDOWN); } } +EXPORT_SYMBOL_GPL(empty_req_queue); /* Dequeues a request packet, called by gadget driver */ static int udc_dequeue(struct usb_ep *usbep, struct usb_request *usbreq) @@ -1623,8 +1623,11 @@ static void udc_setup_endpoints(struct udc *dev) /* Bringup after Connect event, initial bringup to be ready for ep0 events */ static void usb_connect(struct udc *dev) { + /* Return if already connected */ + if (dev->connected) + return; - dev_info(&dev->pdev->dev, "USB Connect\n"); + dev_info(dev->dev, "USB Connect\n"); dev->connected = 1; @@ -1641,8 +1644,11 @@ static void usb_connect(struct udc *dev) */ static void usb_disconnect(struct udc *dev) { + /* Return if already disconnected */ + if (!dev->connected) + return; - dev_info(&dev->pdev->dev, "USB Disconnect\n"); + dev_info(dev->dev, "USB Disconnect\n"); dev->connected = 0; @@ -1715,11 +1721,15 @@ static void udc_soft_reset(struct udc *dev) /* device int. status reset */ writel(UDC_DEV_MSK_DISABLE, &dev->regs->irqsts); - spin_lock_irqsave(&udc_irq_spinlock, flags); - writel(AMD_BIT(UDC_DEVCFG_SOFTRESET), &dev->regs->cfg); - readl(&dev->regs->cfg); - spin_unlock_irqrestore(&udc_irq_spinlock, flags); - + /* Don't do this for Broadcom UDC since this is a reserved + * bit. + */ + if (dev->chiprev != UDC_BCM_REV) { + spin_lock_irqsave(&udc_irq_spinlock, flags); + writel(AMD_BIT(UDC_DEVCFG_SOFTRESET), &dev->regs->cfg); + readl(&dev->regs->cfg); + spin_unlock_irqrestore(&udc_irq_spinlock, flags); + } } /* RDE timer callback to set RDE bit */ @@ -2106,7 +2116,7 @@ static irqreturn_t udc_data_out_isr(struct udc *dev, int ep_ix) } /* HE event ? */ if (tmp & AMD_BIT(UDC_EPSTS_HE)) { - dev_err(&dev->pdev->dev, "HE ep%dout occurred\n", ep->num); + dev_err(dev->dev, "HE ep%dout occurred\n", ep->num); /* clear HE */ writel(tmp | AMD_BIT(UDC_EPSTS_HE), &ep->regs->sts); @@ -2305,7 +2315,7 @@ static irqreturn_t udc_data_in_isr(struct udc *dev, int ep_ix) if (use_dma) { /* BNA ? */ if (epsts & AMD_BIT(UDC_EPSTS_BNA)) { - dev_err(&dev->pdev->dev, + dev_err(dev->dev, "BNA ep%din occurred - DESPTR = %08lx\n", ep->num, (unsigned long) readl(&ep->regs->desptr)); @@ -2318,7 +2328,7 @@ static irqreturn_t udc_data_in_isr(struct udc *dev, int ep_ix) } /* HE event ? */ if (epsts & AMD_BIT(UDC_EPSTS_HE)) { - dev_err(&dev->pdev->dev, + dev_err(dev->dev, "HE ep%dn occurred - DESPTR = %08lx\n", ep->num, (unsigned long) readl(&ep->regs->desptr)); @@ -2956,7 +2966,7 @@ __acquires(dev->lock) /* link up all endpoints */ udc_setup_endpoints(dev); - dev_info(&dev->pdev->dev, "Connect: %s\n", + dev_info(dev->dev, "Connect: %s\n", usb_speed_string(dev->gadget.speed)); /* init ep 0 */ @@ -3097,7 +3107,7 @@ int init_dma_pools(struct udc *dev) } /* DMA setup */ - dev->data_requests = dma_pool_create("data_requests", NULL, + dev->data_requests = dma_pool_create("data_requests", dev->dev, sizeof(struct udc_data_dma), 0, 0); if (!dev->data_requests) { DBG(dev, "can't get request data pool\n"); @@ -3108,7 +3118,7 @@ int init_dma_pools(struct udc *dev) dev->ep[UDC_EP0IN_IX].dma = &dev->regs->ctl; /* dma desc for setup data */ - dev->stp_requests = dma_pool_create("setup requests", NULL, + dev->stp_requests = dma_pool_create("setup requests", dev->dev, sizeof(struct udc_stp_dma), 0, 0); if (!dev->stp_requests) { DBG(dev, "can't get stp request pool\n"); @@ -3168,24 +3178,30 @@ int udc_probe(struct udc *dev) /* init registers, interrupts, ... */ startup_registers(dev); - dev_info(&dev->pdev->dev, "%s\n", mod_desc); + dev_info(dev->dev, "%s\n", mod_desc); snprintf(tmp, sizeof(tmp), "%d", dev->irq); - dev_info(&dev->pdev->dev, - "irq %s, pci mem %08lx, chip rev %02x(Geode5536 %s)\n", - tmp, dev->phys_addr, dev->chiprev, - (dev->chiprev == UDC_HSA0_REV) ? "A0" : "B1"); - strcpy(tmp, UDC_DRIVER_VERSION_STRING); - if (dev->chiprev == UDC_HSA0_REV) { - dev_err(&dev->pdev->dev, "chip revision is A0; too old\n"); - retval = -ENODEV; - goto finished; + + /* Print this device info for AMD chips only*/ + if (dev->chiprev == UDC_HSA0_REV || + dev->chiprev == UDC_HSB1_REV) { + dev_info(dev->dev, "irq %s, pci mem %08lx, chip rev %02x(Geode5536 %s)\n", + tmp, dev->phys_addr, dev->chiprev, + (dev->chiprev == UDC_HSA0_REV) ? + "A0" : "B1"); + strcpy(tmp, UDC_DRIVER_VERSION_STRING); + if (dev->chiprev == UDC_HSA0_REV) { + dev_err(dev->dev, "chip revision is A0; too old\n"); + retval = -ENODEV; + goto finished; + } + dev_info(dev->dev, + "driver version: %s(for Geode5536 B1)\n", tmp); } - dev_info(&dev->pdev->dev, - "driver version: %s(for Geode5536 B1)\n", tmp); + udc = dev; - retval = usb_add_gadget_udc_release(&udc->pdev->dev, &dev->gadget, + retval = usb_add_gadget_udc_release(udc->dev, &dev->gadget, gadget_release); if (retval) goto finished; diff --git a/drivers/usb/gadget/udc/snps_udc_plat.c b/drivers/usb/gadget/udc/snps_udc_plat.c new file mode 100644 index 000000000000..2e11f19e07ae --- /dev/null +++ b/drivers/usb/gadget/udc/snps_udc_plat.c @@ -0,0 +1,344 @@ +/* + * snps_udc_plat.c - Synopsys UDC Platform Driver + * + * Copyright (C) 2016 Broadcom + * + * 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. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/extcon.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_gpio.h> +#include <linux/platform_device.h> +#include <linux/phy/phy.h> +#include <linux/module.h> +#include <linux/dmapool.h> +#include <linux/interrupt.h> +#include <linux/moduleparam.h> +#include "amd5536udc.h" + +/* description */ +#define UDC_MOD_DESCRIPTION "Synopsys UDC platform driver" + +void start_udc(struct udc *udc) +{ + if (udc->driver) { + dev_info(udc->dev, "Connecting...\n"); + udc_enable_dev_setup_interrupts(udc); + udc_basic_init(udc); + udc->connected = 1; + } +} + +void stop_udc(struct udc *udc) +{ + int tmp; + u32 reg; + + spin_lock(&udc->lock); + + /* Flush the receieve fifo */ + reg = readl(&udc->regs->ctl); + reg |= AMD_BIT(UDC_DEVCTL_SRX_FLUSH); + writel(reg, &udc->regs->ctl); + + reg = readl(&udc->regs->ctl); + reg &= ~(AMD_BIT(UDC_DEVCTL_SRX_FLUSH)); + writel(reg, &udc->regs->ctl); + dev_dbg(udc->dev, "ep rx queue flushed\n"); + + /* Mask interrupts. Required more so when the + * UDC is connected to a DRD phy. + */ + udc_mask_unused_interrupts(udc); + + /* Disconnect gadget driver */ + if (udc->driver) { + spin_unlock(&udc->lock); + udc->driver->disconnect(&udc->gadget); + spin_lock(&udc->lock); + + /* empty queues */ + for (tmp = 0; tmp < UDC_EP_NUM; tmp++) + empty_req_queue(&udc->ep[tmp]); + } + udc->connected = 0; + + spin_unlock(&udc->lock); + dev_info(udc->dev, "Device disconnected\n"); +} + +void udc_drd_work(struct work_struct *work) +{ + struct udc *udc; + + udc = container_of(to_delayed_work(work), + struct udc, drd_work); + + if (udc->conn_type) { + dev_dbg(udc->dev, "idle -> device\n"); + start_udc(udc); + } else { + dev_dbg(udc->dev, "device -> idle\n"); + stop_udc(udc); + } +} + +static int usbd_connect_notify(struct notifier_block *self, + unsigned long event, void *ptr) +{ + struct udc *udc = container_of(self, struct udc, nb); + + dev_dbg(udc->dev, "%s: event: %lu\n", __func__, event); + + udc->conn_type = event; + + schedule_delayed_work(&udc->drd_work, 0); + + return NOTIFY_OK; +} + +static int udc_plat_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct udc *udc; + int ret; + + udc = devm_kzalloc(dev, sizeof(*udc), GFP_KERNEL); + if (!udc) + return -ENOMEM; + + spin_lock_init(&udc->lock); + udc->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + udc->virt_addr = devm_ioremap_resource(dev, res); + if (IS_ERR(udc->regs)) + return PTR_ERR(udc->regs); + + /* udc csr registers base */ + udc->csr = udc->virt_addr + UDC_CSR_ADDR; + + /* dev registers base */ + udc->regs = udc->virt_addr + UDC_DEVCFG_ADDR; + + /* ep registers base */ + udc->ep_regs = udc->virt_addr + UDC_EPREGS_ADDR; + + /* fifo's base */ + udc->rxfifo = (u32 __iomem *)(udc->virt_addr + UDC_RXFIFO_ADDR); + udc->txfifo = (u32 __iomem *)(udc->virt_addr + UDC_TXFIFO_ADDR); + + udc->phys_addr = (unsigned long)res->start; + + udc->irq = irq_of_parse_and_map(dev->of_node, 0); + if (udc->irq <= 0) { + dev_err(dev, "Can't parse and map interrupt\n"); + return -EINVAL; + } + + udc->udc_phy = devm_of_phy_get_by_index(dev, dev->of_node, 0); + if (IS_ERR(udc->udc_phy)) { + dev_err(dev, "Failed to obtain phy from device tree\n"); + return PTR_ERR(udc->udc_phy); + } + + ret = phy_init(udc->udc_phy); + if (ret) { + dev_err(dev, "UDC phy init failed"); + return ret; + } + + ret = phy_power_on(udc->udc_phy); + if (ret) { + dev_err(dev, "UDC phy power on failed"); + phy_exit(udc->udc_phy); + return ret; + } + + /* Register for extcon if supported */ + if (of_get_property(dev->of_node, "extcon", NULL)) { + udc->edev = extcon_get_edev_by_phandle(dev, 0); + if (IS_ERR(udc->edev)) { + if (PTR_ERR(udc->edev) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_err(dev, "Invalid or missing extcon\n"); + ret = PTR_ERR(udc->edev); + goto exit_phy; + } + + udc->nb.notifier_call = usbd_connect_notify; + ret = extcon_register_notifier(udc->edev, EXTCON_USB, + &udc->nb); + if (ret < 0) { + dev_err(dev, "Can't register extcon device\n"); + goto exit_phy; + } + + ret = extcon_get_cable_state_(udc->edev, EXTCON_USB); + if (ret < 0) { + dev_err(dev, "Can't get cable state\n"); + goto exit_extcon; + } else if (ret) { + udc->conn_type = ret; + } + INIT_DELAYED_WORK(&udc->drd_work, udc_drd_work); + } + + /* init dma pools */ + if (use_dma) { + ret = init_dma_pools(udc); + if (ret != 0) + goto exit_extcon; + } + + ret = devm_request_irq(dev, udc->irq, udc_irq, IRQF_SHARED, + "snps-udc", udc); + if (ret < 0) { + dev_err(dev, "Request irq %d failed for UDC\n", udc->irq); + goto exit_dma; + } + + platform_set_drvdata(pdev, udc); + udc->chiprev = UDC_BCM_REV; + + if (udc_probe(udc)) { + ret = -ENODEV; + goto exit_dma; + } + dev_info(dev, "Synopsys UDC platform driver probe successful\n"); + + return 0; + +exit_dma: + if (use_dma) + free_dma_pools(udc); +exit_extcon: + if (udc->edev) + extcon_unregister_notifier(udc->edev, EXTCON_USB, &udc->nb); +exit_phy: + if (udc->udc_phy) { + phy_power_off(udc->udc_phy); + phy_exit(udc->udc_phy); + } + return ret; +} + +static int udc_plat_remove(struct platform_device *pdev) +{ + struct udc *dev; + + dev = platform_get_drvdata(pdev); + + usb_del_gadget_udc(&dev->gadget); + /* gadget driver must not be registered */ + if (WARN_ON(dev->driver)) + return 0; + + /* dma pool cleanup */ + free_dma_pools(dev); + + udc_remove(dev); + + platform_set_drvdata(pdev, NULL); + + if (dev->drd_wq) { + flush_workqueue(dev->drd_wq); + destroy_workqueue(dev->drd_wq); + } + + phy_power_off(dev->udc_phy); + phy_exit(dev->udc_phy); + extcon_unregister_notifier(dev->edev, EXTCON_USB, &dev->nb); + + dev_info(&pdev->dev, "Synopsys UDC platform driver removed\n"); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int udc_plat_suspend(struct device *dev) +{ + struct udc *udc; + + udc = dev_get_drvdata(dev); + stop_udc(udc); + + if (extcon_get_cable_state_(udc->edev, EXTCON_USB) > 0) { + dev_dbg(udc->dev, "device -> idle\n"); + stop_udc(udc); + } + phy_power_off(udc->udc_phy); + phy_exit(udc->udc_phy); + + return 0; +} + +static int udc_plat_resume(struct device *dev) +{ + struct udc *udc; + int ret; + + udc = dev_get_drvdata(dev); + + ret = phy_init(udc->udc_phy); + if (ret) { + dev_err(udc->dev, "UDC phy init failure"); + return ret; + } + + ret = phy_power_on(udc->udc_phy); + if (ret) { + dev_err(udc->dev, "UDC phy power on failure"); + phy_exit(udc->udc_phy); + return ret; + } + + if (extcon_get_cable_state_(udc->edev, EXTCON_USB) > 0) { + dev_dbg(udc->dev, "idle -> device\n"); + start_udc(udc); + } + + return 0; +} +static const struct dev_pm_ops udc_plat_pm_ops = { + .suspend = udc_plat_suspend, + .resume = udc_plat_resume, +}; +#endif + +#if defined(CONFIG_OF) +static const struct of_device_id of_udc_match[] = { + { .compatible = "brcm,ns2-udc", }, + { .compatible = "brcm,cygnus-udc", }, + { .compatible = "brcm,iproc-udc", }, + { } +}; +MODULE_DEVICE_TABLE(of, of_udc_match); +#endif + +static struct platform_driver udc_plat_driver = { + .probe = udc_plat_probe, + .remove = udc_plat_remove, + .driver = { + .name = "snps-udc-plat", + .of_match_table = of_match_ptr(of_udc_match), +#ifdef CONFIG_PM_SLEEP + .pm = &udc_plat_pm_ops, +#endif + }, +}; +module_platform_driver(udc_plat_driver); + +MODULE_DESCRIPTION(UDC_MOD_DESCRIPTION); +MODULE_AUTHOR("Broadcom"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/gadget/udc/udc-xilinx.c b/drivers/usb/gadget/udc/udc-xilinx.c index 588e2531b8b8..de207a90571e 100644 --- a/drivers/usb/gadget/udc/udc-xilinx.c +++ b/drivers/usb/gadget/udc/udc-xilinx.c @@ -1151,7 +1151,7 @@ static int xudc_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) break; } if (&req->usb_req != _req) { - spin_unlock_irqrestore(&ep->udc->lock, flags); + spin_unlock_irqrestore(&udc->lock, flags); return -EINVAL; } xudc_done(ep, req, -ECONNRESET); diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index ababb91d654a..fa5692dec832 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -473,8 +473,12 @@ config USB_OHCI_HCD_AT91 config USB_OHCI_HCD_OMAP3 tristate "OHCI support for OMAP3 and later chips" depends on (ARCH_OMAP3 || ARCH_OMAP4 || SOC_OMAP5) + select USB_OHCI_HCD_PLATFORM default y - ---help--- + help + This option is deprecated now and the driver was removed, use + USB_OHCI_HCD_PLATFORM instead. + Enables support for the on-chip OHCI controller on OMAP3 and later chips. @@ -627,7 +631,11 @@ config USB_UHCI_SUPPORT_NON_PCI_HC config USB_UHCI_PLATFORM bool - default y if ARCH_VT8500 + default y if (ARCH_VT8500 || ARCH_ASPEED) + +config USB_UHCI_ASPEED + bool + default y if ARCH_ASPEED config USB_UHCI_BIG_ENDIAN_MMIO bool diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index c77b0a38557b..cf2691fffcc0 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -52,7 +52,6 @@ obj-$(CONFIG_USB_OHCI_HCD_PCI) += ohci-pci.o obj-$(CONFIG_USB_OHCI_HCD_PLATFORM) += ohci-platform.o obj-$(CONFIG_USB_OHCI_EXYNOS) += ohci-exynos.o obj-$(CONFIG_USB_OHCI_HCD_OMAP1) += ohci-omap.o -obj-$(CONFIG_USB_OHCI_HCD_OMAP3) += ohci-omap3.o obj-$(CONFIG_USB_OHCI_HCD_SPEAR) += ohci-spear.o obj-$(CONFIG_USB_OHCI_HCD_STI) += ohci-st.o obj-$(CONFIG_USB_OHCI_HCD_AT91) += ohci-at91.o diff --git a/drivers/usb/host/ehci-exynos.c b/drivers/usb/host/ehci-exynos.c index 7a603f66a9bc..26b641100639 100644 --- a/drivers/usb/host/ehci-exynos.c +++ b/drivers/usb/host/ehci-exynos.c @@ -279,7 +279,9 @@ static int exynos_ehci_resume(struct device *dev) struct exynos_ehci_hcd *exynos_ehci = to_exynos_ehci(hcd); int ret; - clk_prepare_enable(exynos_ehci->clk); + ret = clk_prepare_enable(exynos_ehci->clk); + if (ret) + return ret; ret = exynos_ehci_phy_enable(dev); if (ret) { diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c index 980a6b3b2da2..6bc6304672bc 100644 --- a/drivers/usb/host/ehci-sched.c +++ b/drivers/usb/host/ehci-sched.c @@ -1105,7 +1105,7 @@ iso_stream_init( addr |= epnum << 8; addr |= dev->devnum; stream->ps.usecs = HS_USECS_ISO(maxp); - think_time = dev->tt ? dev->tt->think_time : 0; + think_time = dev->tt->think_time; stream->ps.tt_usecs = NS_TO_US(think_time + usb_calc_bus_time( dev->speed, is_input, 1, maxp)); hs_transfers = max(1u, (maxp + 187) / 188); diff --git a/drivers/usb/host/ehci-timer.c b/drivers/usb/host/ehci-timer.c index 3893b5bafd87..0b6cdb723192 100644 --- a/drivers/usb/host/ehci-timer.c +++ b/drivers/usb/host/ehci-timer.c @@ -424,7 +424,7 @@ static enum hrtimer_restart ehci_hrtimer_func(struct hrtimer *t) */ now = ktime_get(); for_each_set_bit(e, &events, EHCI_HRTIMER_NUM_EVENTS) { - if (now >= ehci->hr_timeouts[e]) + if (ktime_compare(now, ehci->hr_timeouts[e]) >= 0) event_handlers[e](ehci); else ehci_enable_event(ehci, e, false); diff --git a/drivers/usb/host/fotg210-hcd.c b/drivers/usb/host/fotg210-hcd.c index ced08dc229ad..457cc6525abd 100644 --- a/drivers/usb/host/fotg210-hcd.c +++ b/drivers/usb/host/fotg210-hcd.c @@ -1380,7 +1380,7 @@ static enum hrtimer_restart fotg210_hrtimer_func(struct hrtimer *t) */ now = ktime_get(); for_each_set_bit(e, &events, FOTG210_HRTIMER_NUM_EVENTS) { - if (now >= fotg210->hr_timeouts[e]) + if (ktime_compare(now, fotg210->hr_timeouts[e]) >= 0) event_handlers[e](fotg210); else fotg210_enable_event(fotg210, e, false); diff --git a/drivers/usb/host/ohci-omap3.c b/drivers/usb/host/ohci-omap3.c deleted file mode 100644 index ec15aebe8786..000000000000 --- a/drivers/usb/host/ohci-omap3.c +++ /dev/null @@ -1,211 +0,0 @@ -/* - * ohci-omap3.c - driver for OHCI on OMAP3 and later processors - * - * Bus Glue for OMAP3 USBHOST 3 port OHCI controller - * This controller is also used in later OMAPs and AM35x chips - * - * Copyright (C) 2007-2010 Texas Instruments, Inc. - * Author: Vikram Pandita <vikram.pandita@ti.com> - * Author: Anand Gadiyar <gadiyar@ti.com> - * Author: Keshava Munegowda <keshava_mgowda@ti.com> - * - * Based on ehci-omap.c and some other ohci glue layers - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * TODO (last updated Feb 27, 2011): - * - add kernel-doc - */ - -#include <linux/dma-mapping.h> -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/of.h> -#include <linux/usb/otg.h> -#include <linux/platform_device.h> -#include <linux/pm_runtime.h> -#include <linux/usb.h> -#include <linux/usb/hcd.h> - -#include "ohci.h" - -#define DRIVER_DESC "OHCI OMAP3 driver" - -static const char hcd_name[] = "ohci-omap3"; -static struct hc_driver __read_mostly ohci_omap3_hc_driver; - -/* - * configure so an HC device and id are always provided - * always called with process context; sleeping is OK - */ - -/** - * ohci_hcd_omap3_probe - initialize OMAP-based HCDs - * - * Allocates basic resources for this USB host controller, and - * then invokes the start() method for the HCD associated with it - * through the hotplug entry's driver_data. - */ -static int ohci_hcd_omap3_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct ohci_hcd *ohci; - struct usb_hcd *hcd = NULL; - void __iomem *regs = NULL; - struct resource *res; - int ret; - int irq; - - if (usb_disabled()) - return -ENODEV; - - if (!dev->parent) { - dev_err(dev, "Missing parent device\n"); - return -ENODEV; - } - - irq = platform_get_irq(pdev, 0); - if (irq < 0) { - dev_err(dev, "OHCI irq failed\n"); - return -ENODEV; - } - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(dev, "UHH OHCI get resource failed\n"); - return -ENOMEM; - } - - regs = ioremap(res->start, resource_size(res)); - if (!regs) { - dev_err(dev, "UHH OHCI ioremap failed\n"); - return -ENOMEM; - } - - /* - * Right now device-tree probed devices don't get dma_mask set. - * Since shared usb code relies on it, set it here for now. - * Once we have dma capability bindings this can go away. - */ - ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); - if (ret) - goto err_io; - - ret = -ENODEV; - hcd = usb_create_hcd(&ohci_omap3_hc_driver, dev, - dev_name(dev)); - if (!hcd) { - dev_err(dev, "usb_create_hcd failed\n"); - goto err_io; - } - - hcd->rsrc_start = res->start; - hcd->rsrc_len = resource_size(res); - hcd->regs = regs; - - pm_runtime_enable(dev); - pm_runtime_get_sync(dev); - - ohci = hcd_to_ohci(hcd); - /* - * RemoteWakeupConnected has to be set explicitly before - * calling ohci_run. The reset value of RWC is 0. - */ - ohci->hc_control = OHCI_CTRL_RWC; - - ret = usb_add_hcd(hcd, irq, 0); - if (ret) { - dev_dbg(dev, "failed to add hcd with err %d\n", ret); - goto err_add_hcd; - } - device_wakeup_enable(hcd->self.controller); - - return 0; - -err_add_hcd: - pm_runtime_put_sync(dev); - usb_put_hcd(hcd); - -err_io: - iounmap(regs); - - return ret; -} - -/* - * may be called without controller electrically present - * may be called with controller, bus, and devices active - */ - -/** - * ohci_hcd_omap3_remove - shutdown processing for OHCI HCDs - * @pdev: USB Host Controller being removed - * - * Reverses the effect of ohci_hcd_omap3_probe(), first invoking - * the HCD's stop() method. It is always called from a thread - * context, normally "rmmod", "apmd", or something similar. - */ -static int ohci_hcd_omap3_remove(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct usb_hcd *hcd = dev_get_drvdata(dev); - - iounmap(hcd->regs); - usb_remove_hcd(hcd); - pm_runtime_put_sync(dev); - pm_runtime_disable(dev); - usb_put_hcd(hcd); - return 0; -} - -static const struct of_device_id omap_ohci_dt_ids[] = { - { .compatible = "ti,ohci-omap3" }, - { } -}; - -MODULE_DEVICE_TABLE(of, omap_ohci_dt_ids); - -static struct platform_driver ohci_hcd_omap3_driver = { - .probe = ohci_hcd_omap3_probe, - .remove = ohci_hcd_omap3_remove, - .shutdown = usb_hcd_platform_shutdown, - .driver = { - .name = "ohci-omap3", - .of_match_table = omap_ohci_dt_ids, - }, -}; - -static int __init ohci_omap3_init(void) -{ - if (usb_disabled()) - return -ENODEV; - - pr_info("%s: " DRIVER_DESC "\n", hcd_name); - - ohci_init_driver(&ohci_omap3_hc_driver, NULL); - return platform_driver_register(&ohci_hcd_omap3_driver); -} -module_init(ohci_omap3_init); - -static void __exit ohci_omap3_cleanup(void) -{ - platform_driver_unregister(&ohci_hcd_omap3_driver); -} -module_exit(ohci_omap3_cleanup); - -MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_ALIAS("platform:ohci-omap3"); -MODULE_AUTHOR("Anand Gadiyar <gadiyar@ti.com>"); -MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c index 6368fce43197..61fe2b985070 100644 --- a/drivers/usb/host/ohci-platform.c +++ b/drivers/usb/host/ohci-platform.c @@ -24,6 +24,7 @@ #include <linux/err.h> #include <linux/phy/phy.h> #include <linux/platform_device.h> +#include <linux/pm_runtime.h> #include <linux/reset.h> #include <linux/usb/ohci_pdriver.h> #include <linux/usb.h> @@ -163,6 +164,10 @@ static int ohci_platform_probe(struct platform_device *dev) if (of_property_read_bool(dev->dev.of_node, "no-big-frame-no")) ohci->flags |= OHCI_QUIRK_FRAME_NO; + if (of_property_read_bool(dev->dev.of_node, + "remote-wakeup-connected")) + ohci->hc_control = OHCI_CTRL_RWC; + of_property_read_u32(dev->dev.of_node, "num-ports", &ohci->num_ports); @@ -242,6 +247,8 @@ static int ohci_platform_probe(struct platform_device *dev) } #endif + pm_runtime_set_active(&dev->dev); + pm_runtime_enable(&dev->dev); if (pdata->power_on) { err = pdata->power_on(dev); if (err < 0) @@ -271,6 +278,7 @@ err_power: if (pdata->power_off) pdata->power_off(dev); err_reset: + pm_runtime_disable(&dev->dev); while (--rst >= 0) reset_control_assert(priv->resets[rst]); err_put_clks: @@ -292,6 +300,7 @@ static int ohci_platform_remove(struct platform_device *dev) struct ohci_platform_priv *priv = hcd_to_ohci_priv(hcd); int clk, rst; + pm_runtime_get_sync(&dev->dev); usb_remove_hcd(hcd); if (pdata->power_off) @@ -305,6 +314,9 @@ static int ohci_platform_remove(struct platform_device *dev) usb_put_hcd(hcd); + pm_runtime_put_sync(&dev->dev); + pm_runtime_disable(&dev->dev); + if (pdata == &ohci_platform_defaults) dev->dev.platform_data = NULL; @@ -350,6 +362,7 @@ static int ohci_platform_resume(struct device *dev) static const struct of_device_id ohci_platform_ids[] = { { .compatible = "generic-ohci", }, { .compatible = "cavium,octeon-6335-ohci", }, + { .compatible = "ti,ohci-omap3", }, { } }; MODULE_DEVICE_TABLE(of, ohci_platform_ids); diff --git a/drivers/usb/host/ohci-pxa27x.c b/drivers/usb/host/ohci-pxa27x.c index 79efde8f21e0..21c010ffb03c 100644 --- a/drivers/usb/host/ohci-pxa27x.c +++ b/drivers/usb/host/ohci-pxa27x.c @@ -274,14 +274,16 @@ extern void pxa27x_clear_otgph(void); static int pxa27x_start_hc(struct pxa27x_ohci *pxa_ohci, struct device *dev) { - int retval = 0; + int retval; struct pxaohci_platform_data *inf; uint32_t uhchr; struct usb_hcd *hcd = dev_get_drvdata(dev); inf = dev_get_platdata(dev); - clk_prepare_enable(pxa_ohci->clk); + retval = clk_prepare_enable(pxa_ohci->clk); + if (retval) + return retval; pxa27x_reset_hc(pxa_ohci); @@ -296,8 +298,10 @@ static int pxa27x_start_hc(struct pxa27x_ohci *pxa_ohci, struct device *dev) if (inf->init) retval = inf->init(dev); - if (retval < 0) + if (retval < 0) { + clk_disable_unprepare(pxa_ohci->clk); return retval; + } if (cpu_is_pxa3xx()) pxa3xx_u2d_start_hc(&hcd->self); diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c index 94b150196d4f..c3267a78c94e 100644 --- a/drivers/usb/host/uhci-hcd.c +++ b/drivers/usb/host/uhci-hcd.c @@ -265,9 +265,13 @@ static void configure_hc(struct uhci_hcd *uhci) static int resume_detect_interrupts_are_broken(struct uhci_hcd *uhci) { - /* If we have to ignore overcurrent events then almost by definition - * we can't depend on resume-detect interrupts. */ - if (ignore_oc) + /* + * If we have to ignore overcurrent events then almost by definition + * we can't depend on resume-detect interrupts. + * + * Those interrupts also don't seem to work on ASpeed SoCs. + */ + if (ignore_oc || uhci_is_aspeed(uhci)) return 1; return uhci->resume_detect_interrupts_are_broken ? @@ -384,6 +388,13 @@ static void start_rh(struct uhci_hcd *uhci) { uhci->is_stopped = 0; + /* + * Clear stale status bits on Aspeed as we get a stale HCH + * which causes problems later on + */ + if (uhci_is_aspeed(uhci)) + uhci_writew(uhci, uhci_readw(uhci, USBSTS), USBSTS); + /* Mark it configured and running with a 64-byte max packet. * All interrupts are enabled, even though RESUME won't do anything. */ diff --git a/drivers/usb/host/uhci-hcd.h b/drivers/usb/host/uhci-hcd.h index 7fa318a3091d..91b22b2ea3aa 100644 --- a/drivers/usb/host/uhci-hcd.h +++ b/drivers/usb/host/uhci-hcd.h @@ -48,6 +48,8 @@ /* USB port status and control registers */ #define USBPORTSC1 16 #define USBPORTSC2 18 +#define USBPORTSC3 20 +#define USBPORTSC4 22 #define USBPORTSC_CCS 0x0001 /* Current Connect Status * ("device present") */ #define USBPORTSC_CSC 0x0002 /* Connect Status Change */ @@ -427,6 +429,7 @@ struct uhci_hcd { unsigned int wait_for_hp:1; /* Wait for HP port reset */ unsigned int big_endian_mmio:1; /* Big endian registers */ unsigned int big_endian_desc:1; /* Big endian descriptors */ + unsigned int is_aspeed:1; /* Aspeed impl. workarounds */ /* Support for port suspend/resume/reset */ unsigned long port_c_suspend; /* Bit-arrays of ports */ @@ -490,6 +493,12 @@ struct urb_priv { #define PCI_VENDOR_ID_GENESYS 0x17a0 #define PCI_DEVICE_ID_GL880S_UHCI 0x8083 +/* Aspeed SoC needs some quirks */ +static inline bool uhci_is_aspeed(const struct uhci_hcd *uhci) +{ + return IS_ENABLED(CONFIG_USB_UHCI_ASPEED) && uhci->is_aspeed; +} + /* * Functions used to access controller registers. The UCHI spec says that host * controller I/O registers are mapped into PCI I/O space. For non-PCI hosts @@ -545,10 +554,42 @@ static inline void uhci_writeb(const struct uhci_hcd *uhci, u8 val, int reg) #define uhci_big_endian_mmio(u) 0 #endif +static inline int uhci_aspeed_reg(unsigned int reg) +{ + switch (reg) { + case USBCMD: + return 00; + case USBSTS: + return 0x04; + case USBINTR: + return 0x08; + case USBFRNUM: + return 0x80; + case USBFLBASEADD: + return 0x0c; + case USBSOF: + return 0x84; + case USBPORTSC1: + return 0x88; + case USBPORTSC2: + return 0x8c; + case USBPORTSC3: + return 0x90; + case USBPORTSC4: + return 0x94; + default: + pr_warn("UHCI: Unsupported register 0x%02x on Aspeed\n", reg); + /* Return an unimplemented register */ + return 0x10; + } +} + static inline u32 uhci_readl(const struct uhci_hcd *uhci, int reg) { if (uhci_has_pci_registers(uhci)) return inl(uhci->io_addr + reg); + else if (uhci_is_aspeed(uhci)) + return readl(uhci->regs + uhci_aspeed_reg(reg)); #ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO else if (uhci_big_endian_mmio(uhci)) return readl_be(uhci->regs + reg); @@ -561,6 +602,8 @@ static inline void uhci_writel(const struct uhci_hcd *uhci, u32 val, int reg) { if (uhci_has_pci_registers(uhci)) outl(val, uhci->io_addr + reg); + else if (uhci_is_aspeed(uhci)) + writel(val, uhci->regs + uhci_aspeed_reg(reg)); #ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO else if (uhci_big_endian_mmio(uhci)) writel_be(val, uhci->regs + reg); @@ -573,6 +616,8 @@ static inline u16 uhci_readw(const struct uhci_hcd *uhci, int reg) { if (uhci_has_pci_registers(uhci)) return inw(uhci->io_addr + reg); + else if (uhci_is_aspeed(uhci)) + return readl(uhci->regs + uhci_aspeed_reg(reg)); #ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO else if (uhci_big_endian_mmio(uhci)) return readw_be(uhci->regs + reg); @@ -585,6 +630,8 @@ static inline void uhci_writew(const struct uhci_hcd *uhci, u16 val, int reg) { if (uhci_has_pci_registers(uhci)) outw(val, uhci->io_addr + reg); + else if (uhci_is_aspeed(uhci)) + writel(val, uhci->regs + uhci_aspeed_reg(reg)); #ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO else if (uhci_big_endian_mmio(uhci)) writew_be(val, uhci->regs + reg); @@ -597,6 +644,8 @@ static inline u8 uhci_readb(const struct uhci_hcd *uhci, int reg) { if (uhci_has_pci_registers(uhci)) return inb(uhci->io_addr + reg); + else if (uhci_is_aspeed(uhci)) + return readl(uhci->regs + uhci_aspeed_reg(reg)); #ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO else if (uhci_big_endian_mmio(uhci)) return readb_be(uhci->regs + reg); @@ -609,6 +658,8 @@ static inline void uhci_writeb(const struct uhci_hcd *uhci, u8 val, int reg) { if (uhci_has_pci_registers(uhci)) outb(val, uhci->io_addr + reg); + else if (uhci_is_aspeed(uhci)) + writel(val, uhci->regs + uhci_aspeed_reg(reg)); #ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO else if (uhci_big_endian_mmio(uhci)) writeb_be(val, uhci->regs + reg); diff --git a/drivers/usb/host/uhci-platform.c b/drivers/usb/host/uhci-platform.c index 32a6f3d8deec..1b4e086c33a0 100644 --- a/drivers/usb/host/uhci-platform.c +++ b/drivers/usb/host/uhci-platform.c @@ -15,7 +15,9 @@ static int uhci_platform_init(struct usb_hcd *hcd) { struct uhci_hcd *uhci = hcd_to_uhci(hcd); - uhci->rh_numports = uhci_count_ports(hcd); + /* Probe number of ports if not already provided by DT */ + if (!uhci->rh_numports) + uhci->rh_numports = uhci_count_ports(hcd); /* Set up pointers to to generic functions */ uhci->reset_hc = uhci_generic_reset_hc; @@ -63,6 +65,7 @@ static const struct hc_driver uhci_platform_hc_driver = { static int uhci_hcd_platform_probe(struct platform_device *pdev) { + struct device_node *np = pdev->dev.of_node; struct usb_hcd *hcd; struct uhci_hcd *uhci; struct resource *res; @@ -98,6 +101,23 @@ static int uhci_hcd_platform_probe(struct platform_device *pdev) uhci->regs = hcd->regs; + /* Grab some things from the device-tree */ + if (np) { + u32 num_ports; + + if (of_property_read_u32(np, "#ports", &num_ports) == 0) { + uhci->rh_numports = num_ports; + dev_info(&pdev->dev, + "Detected %d ports from device-tree\n", + num_ports); + } + if (of_device_is_compatible(np, "aspeed,ast2400-uhci") || + of_device_is_compatible(np, "aspeed,ast2500-uhci")) { + uhci->is_aspeed = 1; + dev_info(&pdev->dev, + "Enabled Aspeed implementation workarounds\n"); + } + } ret = usb_add_hcd(hcd, pdev->resource[1].start, IRQF_SHARED); if (ret) goto err_rmr; diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index fddf2731f798..2a82c927ded2 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -407,64 +407,17 @@ fail: return NULL; } -void xhci_free_or_cache_endpoint_ring(struct xhci_hcd *xhci, +void xhci_free_endpoint_ring(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, unsigned int ep_index) { - int rings_cached; - - rings_cached = virt_dev->num_rings_cached; - if (rings_cached < XHCI_MAX_RINGS_CACHED) { - virt_dev->ring_cache[rings_cached] = - virt_dev->eps[ep_index].ring; - virt_dev->num_rings_cached++; - xhci_dbg(xhci, "Cached old ring, " - "%d ring%s cached\n", - virt_dev->num_rings_cached, - (virt_dev->num_rings_cached > 1) ? "s" : ""); - } else { - xhci_ring_free(xhci, virt_dev->eps[ep_index].ring); - xhci_dbg(xhci, "Ring cache full (%d rings), " - "freeing ring\n", - virt_dev->num_rings_cached); - } + xhci_ring_free(xhci, virt_dev->eps[ep_index].ring); virt_dev->eps[ep_index].ring = NULL; } -/* Zero an endpoint ring (except for link TRBs) and move the enqueue and dequeue - * pointers to the beginning of the ring. - */ -static void xhci_reinit_cached_ring(struct xhci_hcd *xhci, - struct xhci_ring *ring, unsigned int cycle_state, - enum xhci_ring_type type) -{ - struct xhci_segment *seg = ring->first_seg; - int i; - - do { - memset(seg->trbs, 0, - sizeof(union xhci_trb)*TRBS_PER_SEGMENT); - if (cycle_state == 0) { - for (i = 0; i < TRBS_PER_SEGMENT; i++) - seg->trbs[i].link.control |= - cpu_to_le32(TRB_CYCLE); - } - /* All endpoint rings have link TRBs */ - xhci_link_segments(xhci, seg, seg->next, type); - seg = seg->next; - } while (seg != ring->first_seg); - ring->type = type; - xhci_initialize_ring_info(ring, cycle_state); - /* td list should be empty since all URBs have been cancelled, - * but just in case... - */ - INIT_LIST_HEAD(&ring->td_list); -} - /* * Expand an existing ring. - * Look for a cached ring or allocate a new ring which has same segment numbers - * and link the two rings. + * Allocate a new ring which has same segment numbers and link the two rings. */ int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring, unsigned int num_trbs, gfp_t flags) @@ -968,12 +921,6 @@ void xhci_free_virt_device(struct xhci_hcd *xhci, int slot_id) /* If necessary, update the number of active TTs on this root port */ xhci_update_tt_active_eps(xhci, dev, old_active_eps); - if (dev->ring_cache) { - for (i = 0; i < dev->num_rings_cached; i++) - xhci_ring_free(xhci, dev->ring_cache[i]); - kfree(dev->ring_cache); - } - if (dev->in_ctx) xhci_free_container_ctx(xhci, dev->in_ctx); if (dev->out_ctx) @@ -1062,14 +1009,6 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, if (!dev->eps[0].ring) goto fail; - /* Allocate pointers to the ring cache */ - dev->ring_cache = kzalloc( - sizeof(struct xhci_ring *)*XHCI_MAX_RINGS_CACHED, - flags); - if (!dev->ring_cache) - goto fail; - dev->num_rings_cached = 0; - dev->udev = udev; /* Point to output device context in dcbaa. */ @@ -1537,17 +1476,9 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, /* Set up the endpoint ring */ virt_dev->eps[ep_index].new_ring = xhci_ring_alloc(xhci, 2, 1, ring_type, max_packet, mem_flags); - if (!virt_dev->eps[ep_index].new_ring) { - /* Attempt to use the ring cache */ - if (virt_dev->num_rings_cached == 0) - return -ENOMEM; - virt_dev->num_rings_cached--; - virt_dev->eps[ep_index].new_ring = - virt_dev->ring_cache[virt_dev->num_rings_cached]; - virt_dev->ring_cache[virt_dev->num_rings_cached] = NULL; - xhci_reinit_cached_ring(xhci, virt_dev->eps[ep_index].new_ring, - 1, ring_type); - } + if (!virt_dev->eps[ep_index].new_ring) + return -ENOMEM; + virt_dev->eps[ep_index].skip = false; ep_ring = virt_dev->eps[ep_index].new_ring; diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 03f63f50afb6..c50c902d009e 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -480,10 +480,34 @@ struct xhci_ring *xhci_triad_to_transfer_ring(struct xhci_hcd *xhci, return NULL; } + +/* + * Get the hw dequeue pointer xHC stopped on, either directly from the + * endpoint context, or if streams are in use from the stream context. + * The returned hw_dequeue contains the lowest four bits with cycle state + * and possbile stream context type. + */ +static u64 xhci_get_hw_deq(struct xhci_hcd *xhci, struct xhci_virt_device *vdev, + unsigned int ep_index, unsigned int stream_id) +{ + struct xhci_ep_ctx *ep_ctx; + struct xhci_stream_ctx *st_ctx; + struct xhci_virt_ep *ep; + + ep = &vdev->eps[ep_index]; + + if (ep->ep_state & EP_HAS_STREAMS) { + st_ctx = &ep->stream_info->stream_ctx_array[stream_id]; + return le64_to_cpu(st_ctx->stream_ring); + } + ep_ctx = xhci_get_ep_ctx(xhci, vdev->out_ctx, ep_index); + return le64_to_cpu(ep_ctx->deq); +} + /* * Move the xHC's endpoint ring dequeue pointer past cur_td. * Record the new state of the xHC's endpoint ring dequeue segment, - * dequeue pointer, and new consumer cycle state in state. + * dequeue pointer, stream id, and new consumer cycle state in state. * Update our internal representation of the ring's dequeue pointer. * * We do this in three jumps: @@ -521,24 +545,15 @@ void xhci_find_new_dequeue_state(struct xhci_hcd *xhci, stream_id); return; } - /* Dig out the cycle state saved by the xHC during the stop ep cmd */ xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, "Finding endpoint context"); - /* 4.6.9 the css flag is written to the stream context for streams */ - if (ep->ep_state & EP_HAS_STREAMS) { - struct xhci_stream_ctx *ctx = - &ep->stream_info->stream_ctx_array[stream_id]; - hw_dequeue = le64_to_cpu(ctx->stream_ring); - } else { - struct xhci_ep_ctx *ep_ctx - = xhci_get_ep_ctx(xhci, dev->out_ctx, ep_index); - hw_dequeue = le64_to_cpu(ep_ctx->deq); - } + hw_dequeue = xhci_get_hw_deq(xhci, dev, ep_index, stream_id); new_seg = ep_ring->deq_seg; new_deq = ep_ring->dequeue; state->new_cycle_state = hw_dequeue & 0x1; + state->stream_id = stream_id; /* * We want to find the pointer, segment and cycle state of the new trb @@ -691,7 +706,7 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, struct xhci_td *last_unlinked_td; struct xhci_ep_ctx *ep_ctx; struct xhci_virt_device *vdev; - + u64 hw_deq; struct xhci_dequeue_state deq_state; if (unlikely(TRB_TO_SUSPEND_PORT(le32_to_cpu(trb->generic.field[3])))) { @@ -715,7 +730,6 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, if (list_empty(&ep->cancelled_td_list)) { xhci_stop_watchdog_timer_in_irq(xhci, ep); - ep->stopped_td = NULL; ring_doorbell_for_active_rings(xhci, slot_id, ep_index); return; } @@ -753,12 +767,19 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, * If we stopped on the TD we need to cancel, then we have to * move the xHC endpoint ring dequeue pointer past this TD. */ - if (cur_td == ep->stopped_td) + hw_deq = xhci_get_hw_deq(xhci, vdev, ep_index, + cur_td->urb->stream_id); + hw_deq &= ~0xf; + + if (trb_in_td(xhci, cur_td->start_seg, cur_td->first_trb, + cur_td->last_trb, hw_deq, false)) { xhci_find_new_dequeue_state(xhci, slot_id, ep_index, - cur_td->urb->stream_id, - cur_td, &deq_state); - else + cur_td->urb->stream_id, + cur_td, &deq_state); + } else { td_to_noop(xhci, ep_ring, cur_td, false); + } + remove_finished_td: /* * The event handler won't see a completion for this TD anymore, @@ -773,15 +794,13 @@ remove_finished_td: /* If necessary, queue a Set Transfer Ring Dequeue Pointer command */ if (deq_state.new_deq_ptr && deq_state.new_deq_seg) { xhci_queue_new_dequeue_state(xhci, slot_id, ep_index, - ep->stopped_td->urb->stream_id, &deq_state); + &deq_state); xhci_ring_cmd_db(xhci); } else { /* Otherwise ring the doorbell(s) to restart queued transfers */ ring_doorbell_for_active_rings(xhci, slot_id, ep_index); } - ep->stopped_td = NULL; - /* * Drop the lock and complete the URBs in the cancelled TD list. * New TDs to be cancelled might be added to the end of the list before @@ -1800,7 +1819,8 @@ struct xhci_segment *trb_in_td(struct xhci_hcd *xhci, static void xhci_cleanup_halted_endpoint(struct xhci_hcd *xhci, unsigned int slot_id, unsigned int ep_index, unsigned int stream_id, - struct xhci_td *td, union xhci_trb *ep_trb) + struct xhci_td *td, union xhci_trb *ep_trb, + enum xhci_ep_reset_type reset_type) { struct xhci_virt_ep *ep = &xhci->devs[slot_id]->eps[ep_index]; struct xhci_command *command; @@ -1809,12 +1829,11 @@ static void xhci_cleanup_halted_endpoint(struct xhci_hcd *xhci, return; ep->ep_state |= EP_HALTED; - ep->stopped_stream = stream_id; - xhci_queue_reset_ep(xhci, command, slot_id, ep_index); - xhci_cleanup_stalled_ring(xhci, ep_index, td); + xhci_queue_reset_ep(xhci, command, slot_id, ep_index, reset_type); - ep->stopped_stream = 0; + if (reset_type == EP_HARD_RESET) + xhci_cleanup_stalled_ring(xhci, ep_index, stream_id, td); xhci_ring_cmd_db(xhci); } @@ -1909,7 +1928,7 @@ static int xhci_td_cleanup(struct xhci_hcd *xhci, struct xhci_td *td, static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, union xhci_trb *ep_trb, struct xhci_transfer_event *event, - struct xhci_virt_ep *ep, int *status, bool skip) + struct xhci_virt_ep *ep, int *status) { struct xhci_virt_device *xdev; struct xhci_ep_ctx *ep_ctx; @@ -1925,9 +1944,6 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, ep_ctx = xhci_get_ep_ctx(xhci, xdev->out_ctx, ep_index); trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); - if (skip) - goto td_cleanup; - if (trb_comp_code == COMP_STOPPED_LENGTH_INVALID || trb_comp_code == COMP_STOPPED || trb_comp_code == COMP_STOPPED_SHORT_PACKET) { @@ -1935,7 +1951,6 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, * stopped TDs. A stopped TD may be restarted, so don't update * the ring dequeue pointer or take this TD off any lists yet. */ - ep->stopped_td = td; return 0; } if (trb_comp_code == COMP_STALL_ERROR || @@ -1947,7 +1962,8 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, * The class driver clears the device side halt later. */ xhci_cleanup_halted_endpoint(xhci, slot_id, ep_index, - ep_ring->stream_id, td, ep_trb); + ep_ring->stream_id, td, ep_trb, + EP_HARD_RESET); } else { /* Update ring dequeue pointer */ while (ep_ring->dequeue != td->last_trb) @@ -1955,7 +1971,6 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, inc_deq(xhci, ep_ring); } -td_cleanup: return xhci_td_cleanup(xhci, td, ep_ring, status); } @@ -2075,7 +2090,7 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td, td->urb->actual_length = requested; finish_td: - return finish_td(xhci, td, ep_trb, event, ep, status, false); + return finish_td(xhci, td, ep_trb, event, ep, status); } /* @@ -2162,7 +2177,7 @@ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, td->urb->actual_length += frame->actual_length; - return finish_td(xhci, td, ep_trb, event, ep, status, false); + return finish_td(xhci, td, ep_trb, event, ep, status); } static int skip_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, @@ -2190,7 +2205,7 @@ static int skip_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, inc_deq(xhci, ep_ring); inc_deq(xhci, ep_ring); - return finish_td(xhci, td, NULL, event, ep, status, true); + return xhci_td_cleanup(xhci, td, ep_ring, status); } /* @@ -2252,7 +2267,7 @@ finish_td: remaining); td->urb->actual_length = 0; } - return finish_td(xhci, td, ep_trb, event, ep, status, false); + return finish_td(xhci, td, ep_trb, event, ep, status); } /* @@ -2280,39 +2295,46 @@ static int handle_tx_event(struct xhci_hcd *xhci, bool handling_skipped_tds = false; slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags)); + ep_index = TRB_TO_EP_ID(le32_to_cpu(event->flags)) - 1; + trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); + ep_trb_dma = le64_to_cpu(event->buffer); + xdev = xhci->devs[slot_id]; if (!xdev) { xhci_err(xhci, "ERROR Transfer event pointed to bad slot %u\n", slot_id); - xhci_err(xhci, "@%016llx %08x %08x %08x %08x\n", - (unsigned long long) xhci_trb_virt_to_dma( - xhci->event_ring->deq_seg, - xhci->event_ring->dequeue), - lower_32_bits(le64_to_cpu(event->buffer)), - upper_32_bits(le64_to_cpu(event->buffer)), - le32_to_cpu(event->transfer_len), - le32_to_cpu(event->flags)); - return -ENODEV; - } - - /* Endpoint ID is 1 based, our index is zero based */ - ep_index = TRB_TO_EP_ID(le32_to_cpu(event->flags)) - 1; + goto err_out; + } + ep = &xdev->eps[ep_index]; - ep_ring = xhci_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer)); + ep_ring = xhci_dma_to_transfer_ring(ep, ep_trb_dma); ep_ctx = xhci_get_ep_ctx(xhci, xdev->out_ctx, ep_index); - if (!ep_ring || GET_EP_CTX_STATE(ep_ctx) == EP_STATE_DISABLED) { + + if (GET_EP_CTX_STATE(ep_ctx) == EP_STATE_DISABLED) { xhci_err(xhci, - "ERROR Transfer event for disabled endpoint slot %u ep %u or incorrect stream ring\n", + "ERROR Transfer event for disabled endpoint slot %u ep %u\n", slot_id, ep_index); - xhci_err(xhci, "@%016llx %08x %08x %08x %08x\n", - (unsigned long long) xhci_trb_virt_to_dma( - xhci->event_ring->deq_seg, - xhci->event_ring->dequeue), - lower_32_bits(le64_to_cpu(event->buffer)), - upper_32_bits(le64_to_cpu(event->buffer)), - le32_to_cpu(event->transfer_len), - le32_to_cpu(event->flags)); - return -ENODEV; + goto err_out; + } + + /* Some transfer events don't always point to a trb, see xhci 4.17.4 */ + if (!ep_ring) { + switch (trb_comp_code) { + case COMP_STALL_ERROR: + case COMP_USB_TRANSACTION_ERROR: + case COMP_INVALID_STREAM_TYPE_ERROR: + case COMP_INVALID_STREAM_ID_ERROR: + xhci_cleanup_halted_endpoint(xhci, slot_id, ep_index, 0, + NULL, NULL, EP_SOFT_RESET); + goto cleanup; + case COMP_RING_UNDERRUN: + case COMP_RING_OVERRUN: + goto cleanup; + default: + xhci_err(xhci, "ERROR Transfer event for unknown stream ring slot %u ep %u\n", + slot_id, ep_index); + goto err_out; + } } /* Count current td numbers if ep->skip is set */ @@ -2321,8 +2343,6 @@ static int handle_tx_event(struct xhci_hcd *xhci, td_num++; } - ep_trb_dma = le64_to_cpu(event->buffer); - trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); /* Look for common error cases */ switch (trb_comp_code) { /* Skip codes that require special handling depending on @@ -2339,6 +2359,7 @@ static int handle_tx_event(struct xhci_hcd *xhci, slot_id, ep_index); case COMP_SHORT_PACKET: break; + /* Completion codes for endpoint stopped state */ case COMP_STOPPED: xhci_dbg(xhci, "Stopped on Transfer TRB for slot %u ep %u\n", slot_id, ep_index); @@ -2353,18 +2374,13 @@ static int handle_tx_event(struct xhci_hcd *xhci, "Stopped with short packet transfer detected for slot %u ep %u\n", slot_id, ep_index); break; + /* Completion codes for endpoint halted state */ case COMP_STALL_ERROR: xhci_dbg(xhci, "Stalled endpoint for slot %u ep %u\n", slot_id, ep_index); ep->ep_state |= EP_HALTED; status = -EPIPE; break; - case COMP_TRB_ERROR: - xhci_warn(xhci, - "WARN: TRB error for slot %u ep %u on endpoint\n", - slot_id, ep_index); - status = -EILSEQ; - break; case COMP_SPLIT_TRANSACTION_ERROR: case COMP_USB_TRANSACTION_ERROR: xhci_dbg(xhci, "Transfer error for slot %u ep %u on endpoint\n", @@ -2376,6 +2392,14 @@ static int handle_tx_event(struct xhci_hcd *xhci, slot_id, ep_index); status = -EOVERFLOW; break; + /* Completion codes for endpoint error state */ + case COMP_TRB_ERROR: + xhci_warn(xhci, + "WARN: TRB error for slot %u ep %u on endpoint\n", + slot_id, ep_index); + status = -EILSEQ; + break; + /* completion codes not indicating endpoint state change */ case COMP_DATA_BUFFER_ERROR: xhci_warn(xhci, "WARN: HC couldn't access mem fast enough for slot %u ep %u\n", @@ -2413,12 +2437,6 @@ static int handle_tx_event(struct xhci_hcd *xhci, TRB_TO_SLOT_ID(le32_to_cpu(event->flags)), ep_index); goto cleanup; - case COMP_INCOMPATIBLE_DEVICE_ERROR: - xhci_warn(xhci, - "WARN: detect an incompatible device for slot %u ep %u", - slot_id, ep_index); - status = -EPROTO; - break; case COMP_MISSED_SERVICE_ERROR: /* * When encounter missed service error, one or more isoc tds @@ -2437,6 +2455,14 @@ static int handle_tx_event(struct xhci_hcd *xhci, "No Ping response error for slot %u ep %u, Skip one Isoc TD\n", slot_id, ep_index); goto cleanup; + + case COMP_INCOMPATIBLE_DEVICE_ERROR: + /* needs disable slot command to recover */ + xhci_warn(xhci, + "WARN: detect an incompatible device for slot %u ep %u", + slot_id, ep_index); + status = -EPROTO; + break; default: if (xhci_is_vendor_info_code(xhci, trb_comp_code)) { status = 0; @@ -2589,6 +2615,17 @@ cleanup: } while (handling_skipped_tds); return 0; + +err_out: + xhci_err(xhci, "@%016llx %08x %08x %08x %08x\n", + (unsigned long long) xhci_trb_virt_to_dma( + xhci->event_ring->deq_seg, + xhci->event_ring->dequeue), + lower_32_bits(le64_to_cpu(event->buffer)), + upper_32_bits(le64_to_cpu(event->buffer)), + le32_to_cpu(event->transfer_len), + le32_to_cpu(event->flags)); + return -ENODEV; } /* @@ -3963,13 +4000,12 @@ int xhci_queue_stop_endpoint(struct xhci_hcd *xhci, struct xhci_command *cmd, /* Set Transfer Ring Dequeue Pointer command */ void xhci_queue_new_dequeue_state(struct xhci_hcd *xhci, unsigned int slot_id, unsigned int ep_index, - unsigned int stream_id, struct xhci_dequeue_state *deq_state) { dma_addr_t addr; u32 trb_slot_id = SLOT_ID_FOR_TRB(slot_id); u32 trb_ep_index = EP_ID_FOR_TRB(ep_index); - u32 trb_stream_id = STREAM_ID_FOR_TRB(stream_id); + u32 trb_stream_id = STREAM_ID_FOR_TRB(deq_state->stream_id); u32 trb_sct = 0; u32 type = TRB_TYPE(TRB_SET_DEQ); struct xhci_virt_ep *ep; @@ -4007,7 +4043,7 @@ void xhci_queue_new_dequeue_state(struct xhci_hcd *xhci, ep->queued_deq_seg = deq_state->new_deq_seg; ep->queued_deq_ptr = deq_state->new_deq_ptr; - if (stream_id) + if (deq_state->stream_id) trb_sct = SCT_FOR_TRB(SCT_PRI_TR); ret = queue_command(xhci, cmd, lower_32_bits(addr) | trb_sct | deq_state->new_cycle_state, @@ -4027,12 +4063,16 @@ void xhci_queue_new_dequeue_state(struct xhci_hcd *xhci, } int xhci_queue_reset_ep(struct xhci_hcd *xhci, struct xhci_command *cmd, - int slot_id, unsigned int ep_index) + int slot_id, unsigned int ep_index, + enum xhci_ep_reset_type reset_type) { u32 trb_slot_id = SLOT_ID_FOR_TRB(slot_id); u32 trb_ep_index = EP_ID_FOR_TRB(ep_index); u32 type = TRB_TYPE(TRB_RESET_EP); + if (reset_type == EP_SOFT_RESET) + type |= TRB_TSP; + return queue_command(xhci, cmd, 0, 0, 0, trb_slot_id | trb_ep_index | type, false); } diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 30f47d92a610..56f85df013db 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1695,8 +1695,7 @@ static int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, 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); + xhci_free_endpoint_ring(xhci, virt_dev, ep_index); return ret; } } @@ -2720,23 +2719,23 @@ static int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) for (i = 1; i < 31; i++) { if ((le32_to_cpu(ctrl_ctx->drop_flags) & (1 << (i + 1))) && !(le32_to_cpu(ctrl_ctx->add_flags) & (1 << (i + 1)))) { - xhci_free_or_cache_endpoint_ring(xhci, virt_dev, i); + xhci_free_endpoint_ring(xhci, virt_dev, i); xhci_check_bw_drop_ep_streams(xhci, virt_dev, i); } } xhci_zero_in_ctx(xhci, virt_dev); /* * Install any rings for completely new endpoints or changed endpoints, - * and free or cache any old rings from changed endpoints. + * and free any old rings from changed endpoints. */ for (i = 1; i < 31; i++) { if (!virt_dev->eps[i].new_ring) continue; - /* Only cache or free the old ring if it exists. + /* Only free the old ring if it exists. * It may not if this is the first add of an endpoint. */ if (virt_dev->eps[i].ring) { - xhci_free_or_cache_endpoint_ring(xhci, virt_dev, i); + xhci_free_endpoint_ring(xhci, virt_dev, i); } xhci_check_bw_drop_ep_streams(xhci, virt_dev, i); virt_dev->eps[i].ring = virt_dev->eps[i].new_ring; @@ -2823,8 +2822,8 @@ static void xhci_setup_input_ctx_for_quirk(struct xhci_hcd *xhci, added_ctxs, added_ctxs); } -void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci, - unsigned int ep_index, struct xhci_td *td) +void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci, unsigned int ep_index, + unsigned int stream_id, struct xhci_td *td) { struct xhci_dequeue_state deq_state; struct xhci_virt_ep *ep; @@ -2837,7 +2836,7 @@ void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci, * or it will attempt to resend it on the next doorbell ring. */ xhci_find_new_dequeue_state(xhci, udev->slot_id, - ep_index, ep->stopped_stream, td, &deq_state); + ep_index, stream_id, td, &deq_state); if (!deq_state.new_deq_ptr || !deq_state.new_deq_seg) return; @@ -2849,7 +2848,7 @@ void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci, xhci_dbg_trace(xhci, trace_xhci_dbg_reset_ep, "Queueing new dequeue state"); xhci_queue_new_dequeue_state(xhci, udev->slot_id, - ep_index, ep->stopped_stream, &deq_state); + ep_index, &deq_state); } else { /* Better hope no one uses the input context between now and the * reset endpoint completion! @@ -3336,7 +3335,7 @@ void xhci_free_device_endpoint_resources(struct xhci_hcd *xhci, * * Wait for the Reset Device command to finish. Remove all structures * associated with the endpoints that were disabled. Clear the input device - * structure? Cache the rings? Reset the control endpoint 0 max packet size? + * structure? Reset the control endpoint 0 max packet size? * * If the virt_dev to be reset does not exist or does not match the udev, * it means the device is lost, possibly due to the xHC restore error and @@ -3466,7 +3465,7 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd, spin_unlock_irqrestore(&xhci->lock, flags); } - /* Everything but endpoint 0 is disabled, so free or cache the rings. */ + /* Everything but endpoint 0 is disabled, so free the rings. */ last_freed_endpoint = 1; for (i = 1; i < 31; i++) { struct xhci_virt_ep *ep = &virt_dev->eps[i]; @@ -3480,7 +3479,7 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd, } if (ep->ring) { - xhci_free_or_cache_endpoint_ring(xhci, virt_dev, i); + xhci_free_endpoint_ring(xhci, virt_dev, i); last_freed_endpoint = i; } if (!list_empty(&virt_dev->eps[i].bw_endpoint_list)) diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 73a28a986d5e..650a2d9d4aec 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -924,8 +924,6 @@ struct xhci_virt_ep { #define EP_GETTING_NO_STREAMS (1 << 5) /* ---- Related to URB cancellation ---- */ struct list_head cancelled_td_list; - struct xhci_td *stopped_td; - unsigned int stopped_stream; /* Watchdog timer for stop endpoint command to cancel URBs */ struct timer_list stop_cmd_timer; struct xhci_hcd *xhci; @@ -993,10 +991,6 @@ struct xhci_virt_device { struct xhci_container_ctx *out_ctx; /* Used for addressing devices and configuration changes */ struct xhci_container_ctx *in_ctx; - /* Rings saved to ensure old alt settings can be re-instated */ - struct xhci_ring **ring_cache; - int num_rings_cached; -#define XHCI_MAX_RINGS_CACHED 31 struct xhci_virt_ep eps[31]; u8 fake_port; u8 real_port; @@ -1210,6 +1204,11 @@ struct xhci_event_cmd { /* Stop Ring - Transfer State Preserve */ #define TRB_TSP (1<<9) +enum xhci_ep_reset_type { + EP_HARD_RESET, + EP_SOFT_RESET, +}; + /* Force Event */ #define TRB_TO_VF_INTR_TARGET(p) (((p) & (0x3ff << 22)) >> 22) #define TRB_TO_VF_ID(p) (((p) & (0xff << 16)) >> 16) @@ -1527,6 +1526,7 @@ struct xhci_dequeue_state { struct xhci_segment *new_deq_seg; union xhci_trb *new_deq_ptr; int new_cycle_state; + unsigned int stream_id; }; enum xhci_ring_type { @@ -1960,7 +1960,7 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring); int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring, unsigned int num_trbs, gfp_t flags); -void xhci_free_or_cache_endpoint_ring(struct xhci_hcd *xhci, +void xhci_free_endpoint_ring(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, unsigned int ep_index); struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci, @@ -2044,7 +2044,8 @@ int xhci_queue_configure_endpoint(struct xhci_hcd *xhci, int xhci_queue_evaluate_context(struct xhci_hcd *xhci, struct xhci_command *cmd, dma_addr_t in_ctx_ptr, u32 slot_id, bool command_must_succeed); int xhci_queue_reset_ep(struct xhci_hcd *xhci, struct xhci_command *cmd, - int slot_id, unsigned int ep_index); + int slot_id, unsigned int ep_index, + enum xhci_ep_reset_type reset_type); int xhci_queue_reset_device(struct xhci_hcd *xhci, struct xhci_command *cmd, u32 slot_id); void xhci_find_new_dequeue_state(struct xhci_hcd *xhci, @@ -2053,10 +2054,9 @@ void xhci_find_new_dequeue_state(struct xhci_hcd *xhci, struct xhci_dequeue_state *state); void xhci_queue_new_dequeue_state(struct xhci_hcd *xhci, unsigned int slot_id, unsigned int ep_index, - unsigned int stream_id, struct xhci_dequeue_state *deq_state); -void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci, - unsigned int ep_index, struct xhci_td *td); +void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci, unsigned int ep_index, + unsigned int stream_id, struct xhci_td *td); void xhci_stop_endpoint_command_watchdog(unsigned long arg); void xhci_handle_command_timeout(struct work_struct *work); diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 1d1d70d62a19..0f9f25db9163 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -275,29 +275,3 @@ config USB_CHAOSKEY To compile this driver as a module, choose M here: the module will be called chaoskey. - -config UCSI - tristate "USB Type-C Connector System Software Interface driver" - depends on ACPI - help - UCSI driver is meant to be used as a convenience tool for desktop and - server systems that are not equipped to handle USB in device mode. It - will always select USB host role for the USB Type-C ports on systems - that provide UCSI interface. - - USB Type-C Connector System Software Interface (UCSI) is a - specification for an interface that allows the Operating System to - control the USB Type-C ports on a system. Things the need controlling - include the USB Data Role (host or device), and when USB Power - Delivery is supported, the Power Role (source or sink). With USB - Type-C connectors, when two dual role capable devices are attached - together, the data role is selected randomly. Therefore it is - important to give the OS a way to select the role. Otherwise the user - would have to unplug and replug in order in order to attempt to swap - the data and power roles. - - The UCSI specification can be downloaded from: - http://www.intel.com/content/www/us/en/io/universal-serial-bus/usb-type-c-ucsi-spec.html - - To compile the driver as a module, choose M here: the module will be - called ucsi. diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index f6ac6c99a6e6..7fdb45fc976f 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -27,7 +27,6 @@ obj-$(CONFIG_USB_HUB_USB251XB) += usb251xb.o obj-$(CONFIG_USB_HSIC_USB3503) += usb3503.o obj-$(CONFIG_USB_HSIC_USB4604) += usb4604.o obj-$(CONFIG_USB_CHAOSKEY) += chaoskey.o -obj-$(CONFIG_UCSI) += ucsi.o obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/ obj-$(CONFIG_USB_LINK_LAYER_TEST) += lvstest.o diff --git a/drivers/usb/misc/iowarrior.c b/drivers/usb/misc/iowarrior.c index 83b05a287b0c..7ca4c7e0ea0d 100644 --- a/drivers/usb/misc/iowarrior.c +++ b/drivers/usb/misc/iowarrior.c @@ -368,14 +368,9 @@ static ssize_t iowarrior_write(struct file *file, case USB_DEVICE_ID_CODEMERCS_IOWPV2: case USB_DEVICE_ID_CODEMERCS_IOW40: /* IOW24 and IOW40 use a synchronous call */ - buf = kmalloc(count, GFP_KERNEL); - if (!buf) { - retval = -ENOMEM; - goto exit; - } - if (copy_from_user(buf, user_buffer, count)) { - retval = -EFAULT; - kfree(buf); + buf = memdup_user(user_buffer, count); + if (IS_ERR(buf)) { + retval = PTR_ERR(buf); goto exit; } retval = usb_set_report(dev->interface, 2, 0, buf, count); diff --git a/drivers/usb/misc/ucsi.c b/drivers/usb/misc/ucsi.c deleted file mode 100644 index 81251aaa20f9..000000000000 --- a/drivers/usb/misc/ucsi.c +++ /dev/null @@ -1,478 +0,0 @@ -/* - * USB Type-C Connector System Software Interface driver - * - * Copyright (C) 2016, Intel Corporation - * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include <linux/platform_device.h> -#include <linux/module.h> -#include <linux/delay.h> -#include <linux/acpi.h> - -#include "ucsi.h" - -/* Double the time defined by MIN_TIME_TO_RESPOND_WITH_BUSY */ -#define UCSI_TIMEOUT_MS 20 - -enum ucsi_status { - UCSI_IDLE = 0, - UCSI_BUSY, - UCSI_ERROR, -}; - -struct ucsi_connector { - int num; - struct ucsi *ucsi; - struct work_struct work; - struct ucsi_connector_capability cap; -}; - -struct ucsi { - struct device *dev; - struct ucsi_data __iomem *data; - - enum ucsi_status status; - struct completion complete; - struct ucsi_capability cap; - struct ucsi_connector *connector; - - /* device lock */ - spinlock_t dev_lock; - - /* PPM Communication lock */ - struct mutex ppm_lock; - - /* PPM communication flags */ - unsigned long flags; -#define EVENT_PENDING 0 -#define COMMAND_PENDING 1 -}; - -static int ucsi_acpi_cmd(struct ucsi *ucsi, struct ucsi_control *ctrl) -{ - guid_t guid = GUID_INIT(0x6f8398c2, 0x7ca4, 0x11e4, - 0xad, 0x36, 0x63, 0x10, 0x42, 0xb5, 0x00, 0x8f); - union acpi_object *obj; - - ucsi->data->ctrl.raw_cmd = ctrl->raw_cmd; - - obj = acpi_evaluate_dsm(ACPI_HANDLE(ucsi->dev), &guid, 1, 1, NULL); - if (!obj) { - dev_err(ucsi->dev, "%s: failed to evaluate _DSM\n", __func__); - return -EIO; - } - - ACPI_FREE(obj); - return 0; -} - -static void ucsi_acpi_notify(acpi_handle handle, u32 event, void *data) -{ - struct ucsi *ucsi = data; - struct ucsi_cci *cci; - - spin_lock(&ucsi->dev_lock); - - ucsi->status = UCSI_IDLE; - cci = &ucsi->data->cci; - - /* - * REVISIT: This is not documented behavior, but all known PPMs ACK - * asynchronous events by sending notification with cleared CCI. - */ - if (!ucsi->data->raw_cci) { - if (test_bit(EVENT_PENDING, &ucsi->flags)) - complete(&ucsi->complete); - else - dev_WARN(ucsi->dev, "spurious notification\n"); - goto out_unlock; - } - - if (test_bit(COMMAND_PENDING, &ucsi->flags)) { - if (cci->busy) { - ucsi->status = UCSI_BUSY; - complete(&ucsi->complete); - - goto out_unlock; - } else if (cci->ack_complete || cci->cmd_complete) { - /* Error Indication is only valid with commands */ - if (cci->error && cci->cmd_complete) - ucsi->status = UCSI_ERROR; - - ucsi->data->ctrl.raw_cmd = 0; - complete(&ucsi->complete); - } - } - - if (cci->connector_change) { - struct ucsi_connector *con; - - /* - * This is workaround for buggy PPMs that create asynchronous - * event notifications before OPM has enabled them. - */ - if (!ucsi->connector) - goto out_unlock; - - con = ucsi->connector + (cci->connector_change - 1); - - /* - * PPM will not clear the connector specific bit in Connector - * Change Indication field of CCI until the driver has ACK it, - * and the driver can not ACK it before it has been processed. - * The PPM will not generate new events before the first has - * been acknowledged, even if they are for an other connector. - * So only one event at a time. - */ - if (!test_and_set_bit(EVENT_PENDING, &ucsi->flags)) - schedule_work(&con->work); - } -out_unlock: - spin_unlock(&ucsi->dev_lock); -} - -static int ucsi_ack(struct ucsi *ucsi, u8 cmd) -{ - struct ucsi_control ctrl; - int ret; - - ctrl.cmd.cmd = UCSI_ACK_CC_CI; - ctrl.cmd.length = 0; - ctrl.cmd.data = cmd; - ret = ucsi_acpi_cmd(ucsi, &ctrl); - if (ret) - return ret; - - /* Waiting for ACK also with ACK CMD for now */ - ret = wait_for_completion_timeout(&ucsi->complete, - msecs_to_jiffies(UCSI_TIMEOUT_MS)); - if (!ret) - return -ETIMEDOUT; - return 0; -} - -static int ucsi_run_cmd(struct ucsi *ucsi, struct ucsi_control *ctrl, - void *data, size_t size) -{ - u16 err_value = 0; - int ret; - - set_bit(COMMAND_PENDING, &ucsi->flags); - - ret = ucsi_acpi_cmd(ucsi, ctrl); - if (ret) - goto err_clear_flag; - - ret = wait_for_completion_timeout(&ucsi->complete, - msecs_to_jiffies(UCSI_TIMEOUT_MS)); - if (!ret) { - ret = -ETIMEDOUT; - goto err_clear_flag; - } - - switch (ucsi->status) { - case UCSI_IDLE: - if (data) - memcpy(data, ucsi->data->message_in, size); - - ret = ucsi_ack(ucsi, UCSI_ACK_CMD); - break; - case UCSI_BUSY: - /* The caller decides whether to cancel or not */ - ret = -EBUSY; - goto err_clear_flag; - case UCSI_ERROR: - ret = ucsi_ack(ucsi, UCSI_ACK_CMD); - if (ret) - goto err_clear_flag; - - ctrl->cmd.cmd = UCSI_GET_ERROR_STATUS; - ctrl->cmd.length = 0; - ctrl->cmd.data = 0; - ret = ucsi_acpi_cmd(ucsi, ctrl); - if (ret) - goto err_clear_flag; - - ret = wait_for_completion_timeout(&ucsi->complete, - msecs_to_jiffies(UCSI_TIMEOUT_MS)); - if (!ret) { - ret = -ETIMEDOUT; - goto err_clear_flag; - } - - memcpy(&err_value, ucsi->data->message_in, sizeof(err_value)); - - /* Something has really gone wrong */ - if (WARN_ON(ucsi->status == UCSI_ERROR)) { - ret = -ENODEV; - goto err_clear_flag; - } - - ret = ucsi_ack(ucsi, UCSI_ACK_CMD); - if (ret) - goto err_clear_flag; - - switch (err_value) { - case UCSI_ERROR_INCOMPATIBLE_PARTNER: - ret = -EOPNOTSUPP; - break; - case UCSI_ERROR_CC_COMMUNICATION_ERR: - ret = -ECOMM; - break; - case UCSI_ERROR_CONTRACT_NEGOTIATION_FAIL: - ret = -EIO; - break; - case UCSI_ERROR_DEAD_BATTERY: - dev_warn(ucsi->dev, "Dead battery condition!\n"); - ret = -EPERM; - break; - /* The following mean a bug in this driver */ - case UCSI_ERROR_INVALID_CON_NUM: - case UCSI_ERROR_UNREGONIZED_CMD: - case UCSI_ERROR_INVALID_CMD_ARGUMENT: - default: - dev_warn(ucsi->dev, - "%s: possible UCSI driver bug - error %hu\n", - __func__, err_value); - ret = -EINVAL; - break; - } - break; - } - ctrl->raw_cmd = 0; -err_clear_flag: - clear_bit(COMMAND_PENDING, &ucsi->flags); - return ret; -} - -static void ucsi_connector_change(struct work_struct *work) -{ - struct ucsi_connector *con = container_of(work, struct ucsi_connector, - work); - struct ucsi_connector_status constat; - struct ucsi *ucsi = con->ucsi; - struct ucsi_control ctrl; - int ret; - - mutex_lock(&ucsi->ppm_lock); - - ctrl.cmd.cmd = UCSI_GET_CONNECTOR_STATUS; - ctrl.cmd.length = 0; - ctrl.cmd.data = con->num; - ret = ucsi_run_cmd(con->ucsi, &ctrl, &constat, sizeof(constat)); - if (ret) { - dev_err(ucsi->dev, "%s: failed to read connector status (%d)\n", - __func__, ret); - goto out_ack_event; - } - - /* Ignoring disconnections and Alternate Modes */ - if (!constat.connected || !(constat.change & - (UCSI_CONSTAT_PARTNER_CHANGE | UCSI_CONSTAT_CONNECT_CHANGE)) || - constat.partner_flags & UCSI_CONSTAT_PARTNER_FLAG_ALT_MODE) - goto out_ack_event; - - /* If the partner got USB Host role, attempting swap */ - if (constat.partner_type & UCSI_CONSTAT_PARTNER_TYPE_DFP) { - ctrl.uor.cmd = UCSI_SET_UOR; - ctrl.uor.con_num = con->num; - ctrl.uor.role = UCSI_UOR_ROLE_DFP; - - ret = ucsi_run_cmd(con->ucsi, &ctrl, NULL, 0); - if (ret) - dev_err(ucsi->dev, "%s: failed to swap role (%d)\n", - __func__, ret); - } -out_ack_event: - ucsi_ack(ucsi, UCSI_ACK_EVENT); - clear_bit(EVENT_PENDING, &ucsi->flags); - mutex_unlock(&ucsi->ppm_lock); -} - -static int ucsi_reset_ppm(struct ucsi *ucsi) -{ - int timeout = UCSI_TIMEOUT_MS; - struct ucsi_control ctrl; - int ret; - - memset(&ctrl, 0, sizeof(ctrl)); - ctrl.cmd.cmd = UCSI_PPM_RESET; - ret = ucsi_acpi_cmd(ucsi, &ctrl); - if (ret) - return ret; - - /* There is no quarantee the PPM will ever set the RESET_COMPLETE bit */ - while (!ucsi->data->cci.reset_complete && timeout--) - usleep_range(1000, 2000); - return 0; -} - -static int ucsi_init(struct ucsi *ucsi) -{ - struct ucsi_connector *con; - struct ucsi_control ctrl; - int ret; - int i; - - init_completion(&ucsi->complete); - spin_lock_init(&ucsi->dev_lock); - mutex_init(&ucsi->ppm_lock); - - /* Reset the PPM */ - ret = ucsi_reset_ppm(ucsi); - if (ret) - return ret; - - /* - * REVISIT: Executing second reset to WA an issue seen on some of the - * Broxton based platforms, where the first reset puts the PPM into a - * state where it's unable to recognise some of the commands. - */ - ret = ucsi_reset_ppm(ucsi); - if (ret) - return ret; - - mutex_lock(&ucsi->ppm_lock); - - /* Enable basic notifications */ - ctrl.cmd.cmd = UCSI_SET_NOTIFICATION_ENABLE; - ctrl.cmd.length = 0; - ctrl.cmd.data = UCSI_ENABLE_NTFY_CMD_COMPLETE | UCSI_ENABLE_NTFY_ERROR; - ret = ucsi_run_cmd(ucsi, &ctrl, NULL, 0); - if (ret) - goto err_reset; - - /* Get PPM capabilities */ - ctrl.cmd.cmd = UCSI_GET_CAPABILITY; - ret = ucsi_run_cmd(ucsi, &ctrl, &ucsi->cap, sizeof(ucsi->cap)); - if (ret) - goto err_reset; - - if (!ucsi->cap.num_connectors) { - ret = -ENODEV; - goto err_reset; - } - - ucsi->connector = devm_kcalloc(ucsi->dev, ucsi->cap.num_connectors, - sizeof(*ucsi->connector), GFP_KERNEL); - if (!ucsi->connector) { - ret = -ENOMEM; - goto err_reset; - } - - for (i = 1, con = ucsi->connector; i < ucsi->cap.num_connectors + 1; - i++, con++) { - /* Get connector capability */ - ctrl.cmd.cmd = UCSI_GET_CONNECTOR_CAPABILITY; - ctrl.cmd.data = i; - ret = ucsi_run_cmd(ucsi, &ctrl, &con->cap, sizeof(con->cap)); - if (ret) - goto err_reset; - - con->num = i; - con->ucsi = ucsi; - INIT_WORK(&con->work, ucsi_connector_change); - } - - /* Enable all notifications */ - ctrl.cmd.cmd = UCSI_SET_NOTIFICATION_ENABLE; - ctrl.cmd.data = UCSI_ENABLE_NTFY_ALL; - ret = ucsi_run_cmd(ucsi, &ctrl, NULL, 0); - if (ret < 0) - goto err_reset; - - mutex_unlock(&ucsi->ppm_lock); - return 0; -err_reset: - ucsi_reset_ppm(ucsi); - mutex_unlock(&ucsi->ppm_lock); - return ret; -} - -static int ucsi_acpi_probe(struct platform_device *pdev) -{ - struct resource *res; - acpi_status status; - struct ucsi *ucsi; - int ret; - - ucsi = devm_kzalloc(&pdev->dev, sizeof(*ucsi), GFP_KERNEL); - if (!ucsi) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(&pdev->dev, "missing memory resource\n"); - return -ENODEV; - } - - /* - * NOTE: ACPI has claimed the memory region as it's also an Operation - * Region. It's not possible to request it in the driver. - */ - ucsi->data = devm_ioremap(&pdev->dev, res->start, resource_size(res)); - if (!ucsi->data) - return -ENOMEM; - - ucsi->dev = &pdev->dev; - - status = acpi_install_notify_handler(ACPI_HANDLE(&pdev->dev), - ACPI_ALL_NOTIFY, - ucsi_acpi_notify, ucsi); - if (ACPI_FAILURE(status)) - return -ENODEV; - - ret = ucsi_init(ucsi); - if (ret) { - acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), - ACPI_ALL_NOTIFY, - ucsi_acpi_notify); - return ret; - } - - platform_set_drvdata(pdev, ucsi); - return 0; -} - -static int ucsi_acpi_remove(struct platform_device *pdev) -{ - struct ucsi *ucsi = platform_get_drvdata(pdev); - - acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), - ACPI_ALL_NOTIFY, ucsi_acpi_notify); - - /* Make sure there are no events in the middle of being processed */ - if (wait_on_bit_timeout(&ucsi->flags, EVENT_PENDING, - TASK_UNINTERRUPTIBLE, - msecs_to_jiffies(UCSI_TIMEOUT_MS))) - dev_WARN(ucsi->dev, "%s: Events still pending\n", __func__); - - ucsi_reset_ppm(ucsi); - return 0; -} - -static const struct acpi_device_id ucsi_acpi_match[] = { - { "PNP0CA0", 0 }, - { }, -}; -MODULE_DEVICE_TABLE(acpi, ucsi_acpi_match); - -static struct platform_driver ucsi_acpi_platform_driver = { - .driver = { - .name = "ucsi_acpi", - .acpi_match_table = ACPI_PTR(ucsi_acpi_match), - }, - .probe = ucsi_acpi_probe, - .remove = ucsi_acpi_remove, -}; - -module_platform_driver(ucsi_acpi_platform_driver); - -MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>"); -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("USB Type-C System Software Interface (UCSI) driver"); diff --git a/drivers/usb/misc/usbsevseg.c b/drivers/usb/misc/usbsevseg.c index a0ba5298160c..388fae6373db 100644 --- a/drivers/usb/misc/usbsevseg.c +++ b/drivers/usb/misc/usbsevseg.c @@ -33,7 +33,7 @@ static const struct usb_device_id id_table[] = { MODULE_DEVICE_TABLE(usb, id_table); /* the different text display modes the device is capable of */ -static char *display_textmodes[] = {"raw", "hex", "ascii", NULL}; +static const char *display_textmodes[] = {"raw", "hex", "ascii"}; struct usb_sevsegdev { struct usb_device *udev; @@ -280,7 +280,7 @@ static ssize_t show_attr_textmode(struct device *dev, buf[0] = 0; - for (i = 0; display_textmodes[i]; i++) { + for (i = 0; i < ARRAY_SIZE(display_textmodes); i++) { if (mydev->textmode == i) { strcat(buf, " ["); strcat(buf, display_textmodes[i]); @@ -304,15 +304,13 @@ static ssize_t set_attr_textmode(struct device *dev, struct usb_sevsegdev *mydev = usb_get_intfdata(intf); int i; - for (i = 0; display_textmodes[i]; i++) { - if (sysfs_streq(display_textmodes[i], buf)) { - mydev->textmode = i; - update_display_visual(mydev, GFP_KERNEL); - return count; - } - } + i = sysfs_match_string(display_textmodes, buf); + if (i < 0) + return i; - return -EINVAL; + mydev->textmode = i; + update_display_visual(mydev, GFP_KERNEL); + return count; } static DEVICE_ATTR(textmode, S_IRUGO | S_IWUSR, show_attr_textmode, set_attr_textmode); diff --git a/drivers/usb/mtu3/mtu3.h b/drivers/usb/mtu3/mtu3.h index aa6fd6a51221..7b6dc23d77e9 100644 --- a/drivers/usb/mtu3/mtu3.h +++ b/drivers/usb/mtu3/mtu3.h @@ -356,12 +356,8 @@ static inline struct mtu3_ep *to_mtu3_ep(struct usb_ep *ep) static inline struct mtu3_request *next_request(struct mtu3_ep *mep) { - struct list_head *queue = &mep->req_list; - - if (list_empty(queue)) - return NULL; - - return list_first_entry(queue, struct mtu3_request, list); + return list_first_entry_or_null(&mep->req_list, struct mtu3_request, + list); } static inline void mtu3_writel(void __iomem *base, u32 offset, u32 data) diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c index 42550c7db3e7..0d3ebb353e08 100644 --- a/drivers/usb/mtu3/mtu3_plat.c +++ b/drivers/usb/mtu3/mtu3_plat.c @@ -458,6 +458,7 @@ static int __maybe_unused mtu3_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct ssusb_mtk *ssusb = platform_get_drvdata(pdev); + int ret; dev_dbg(dev, "%s\n", __func__); @@ -465,12 +466,28 @@ static int __maybe_unused mtu3_resume(struct device *dev) return 0; ssusb_wakeup_disable(ssusb); - clk_prepare_enable(ssusb->sys_clk); - clk_prepare_enable(ssusb->ref_clk); - ssusb_phy_power_on(ssusb); + ret = clk_prepare_enable(ssusb->sys_clk); + if (ret) + goto err_sys_clk; + + ret = clk_prepare_enable(ssusb->ref_clk); + if (ret) + goto err_ref_clk; + + ret = ssusb_phy_power_on(ssusb); + if (ret) + goto err_power_on; + ssusb_host_enable(ssusb); return 0; + +err_power_on: + clk_disable_unprepare(ssusb->ref_clk); +err_ref_clk: + clk_disable_unprepare(ssusb->sys_clk); +err_sys_clk: + return ret; } static const struct dev_pm_ops mtu3_pm_ops = { diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index 870da18f5077..87cbd56cc761 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -2224,6 +2224,9 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl) musb->io.ep_select = musb_flat_ep_select; } + if (musb->io.quirks & MUSB_G_NO_SKB_RESERVE) + musb->g.quirk_avoids_skb_reserve = 1; + /* At least tusb6010 has its own offsets */ if (musb->ops->ep_offset) musb->io.ep_offset = musb->ops->ep_offset; diff --git a/drivers/usb/musb/musb_core.h b/drivers/usb/musb/musb_core.h index 3e98d4268a64..9f22c5b8ce37 100644 --- a/drivers/usb/musb/musb_core.h +++ b/drivers/usb/musb/musb_core.h @@ -172,6 +172,7 @@ struct musb_io; */ struct musb_platform_ops { +#define MUSB_G_NO_SKB_RESERVE BIT(9) #define MUSB_DA8XX BIT(8) #define MUSB_PRESERVE_SESSION BIT(7) #define MUSB_DMA_UX500 BIT(6) diff --git a/drivers/usb/musb/musb_cppi41.c b/drivers/usb/musb/musb_cppi41.c index e7c8b1b8bf22..ba255280a624 100644 --- a/drivers/usb/musb/musb_cppi41.c +++ b/drivers/usb/musb/musb_cppi41.c @@ -673,12 +673,15 @@ static int cppi41_dma_controller_start(struct cppi41_dma_controller *controller) musb_dma->status = MUSB_DMA_STATUS_FREE; musb_dma->max_len = SZ_4M; - dc = dma_request_slave_channel(dev->parent, str); - if (!dc) { - dev_err(dev, "Failed to request %s.\n", str); - ret = -EPROBE_DEFER; + dc = dma_request_chan(dev->parent, str); + if (IS_ERR(dc)) { + ret = PTR_ERR(dc); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to request %s: %d.\n", + str, ret); goto err; } + cppi41_channel->dc = dc; } return 0; diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c index dbe617a735d8..76decb8011eb 100644 --- a/drivers/usb/musb/musb_host.c +++ b/drivers/usb/musb/musb_host.c @@ -1540,7 +1540,7 @@ static int musb_rx_dma_iso_cppi41(struct dma_controller *dma, struct dma_channel *channel = hw_ep->rx_channel; void __iomem *epio = hw_ep->regs; dma_addr_t *buf; - u32 length, res; + u32 length; u16 val; buf = (void *)urb->iso_frame_desc[qh->iso_idx].offset + @@ -1552,10 +1552,8 @@ static int musb_rx_dma_iso_cppi41(struct dma_controller *dma, val |= MUSB_RXCSR_DMAENAB; musb_writew(hw_ep->regs, MUSB_RXCSR, val); - res = dma->channel_program(channel, qh->maxpacket, 0, + return dma->channel_program(channel, qh->maxpacket, 0, (u32)buf, length); - - return res; } #else static inline int musb_rx_dma_iso_cppi41(struct dma_controller *dma, diff --git a/drivers/usb/musb/tusb6010.c b/drivers/usb/musb/tusb6010.c index e85cc8e4e7a9..4eb640c54f2c 100644 --- a/drivers/usb/musb/tusb6010.c +++ b/drivers/usb/musb/tusb6010.c @@ -881,26 +881,14 @@ static irqreturn_t tusb_musb_interrupt(int irq, void *__hci) | TUSB_INT_SRC_ID_STATUS_CHNG)) idle_timeout = tusb_otg_ints(musb, int_src, tbase); - /* TX dma callback must be handled here, RX dma callback is - * handled in tusb_omap_dma_cb. + /* + * Just clear the DMA interrupt if it comes as the completion for both + * TX and RX is handled by the DMA callback in tusb6010_omap */ if ((int_src & TUSB_INT_SRC_TXRX_DMA_DONE)) { u32 dma_src = musb_readl(tbase, TUSB_DMA_INT_SRC); - u32 real_dma_src = musb_readl(tbase, TUSB_DMA_INT_MASK); dev_dbg(musb->controller, "DMA IRQ %08x\n", dma_src); - real_dma_src = ~real_dma_src & dma_src; - if (tusb_dma_omap(musb) && real_dma_src) { - int tx_source = (real_dma_src & 0xffff); - int i; - - for (i = 1; i <= 15; i++) { - if (tx_source & (1 << i)) { - dev_dbg(musb->controller, "completing ep%i %s\n", i, "tx"); - musb_dma_completion(musb, i, 1); - } - } - } musb_writel(tbase, TUSB_DMA_INT_CLEAR, dma_src); } @@ -1181,7 +1169,8 @@ static int tusb_musb_exit(struct musb *musb) } static const struct musb_platform_ops tusb_ops = { - .quirks = MUSB_DMA_TUSB_OMAP | MUSB_IN_TUSB, + .quirks = MUSB_DMA_TUSB_OMAP | MUSB_IN_TUSB | + MUSB_G_NO_SKB_RESERVE, .init = tusb_musb_init, .exit = tusb_musb_exit, diff --git a/drivers/usb/musb/tusb6010_omap.c b/drivers/usb/musb/tusb6010_omap.c index 7870b37e0ea5..e8060e49b0f4 100644 --- a/drivers/usb/musb/tusb6010_omap.c +++ b/drivers/usb/musb/tusb6010_omap.c @@ -15,7 +15,7 @@ #include <linux/platform_device.h> #include <linux/dma-mapping.h> #include <linux/slab.h> -#include <linux/omap-dma.h> +#include <linux/dmaengine.h> #include "musb_core.h" #include "tusb6010.h" @@ -24,12 +24,10 @@ #define MAX_DMAREQ 5 /* REVISIT: Really 6, but req5 not OK */ -#define OMAP24XX_DMA_EXT_DMAREQ0 2 -#define OMAP24XX_DMA_EXT_DMAREQ1 3 -#define OMAP242X_DMA_EXT_DMAREQ2 14 -#define OMAP242X_DMA_EXT_DMAREQ3 15 -#define OMAP242X_DMA_EXT_DMAREQ4 16 -#define OMAP242X_DMA_EXT_DMAREQ5 64 +struct tusb_dma_data { + s8 dmareq; + struct dma_chan *chan; +}; struct tusb_omap_dma_ch { struct musb *musb; @@ -39,9 +37,7 @@ struct tusb_omap_dma_ch { u8 tx; struct musb_hw_ep *hw_ep; - int ch; - s8 dmareq; - s8 sync_dev; + struct tusb_dma_data *dma_data; struct tusb_omap_dma *tusb_dma; @@ -58,9 +54,7 @@ struct tusb_omap_dma { struct dma_controller controller; void __iomem *tbase; - int ch; - s8 dmareq; - s8 sync_dev; + struct tusb_dma_data dma_pool[MAX_DMAREQ]; unsigned multichannel:1; }; @@ -103,7 +97,7 @@ static inline void tusb_omap_free_shared_dmareq(struct tusb_omap_dma_ch *chdat) * See also musb_dma_completion in plat_uds.c and musb_g_[tx|rx]() in * musb_gadget.c. */ -static void tusb_omap_dma_cb(int lch, u16 ch_status, void *data) +static void tusb_omap_dma_cb(void *data) { struct dma_channel *channel = (struct dma_channel *)data; struct tusb_omap_dma_ch *chdat = to_chdat(channel); @@ -114,21 +108,11 @@ static void tusb_omap_dma_cb(int lch, u16 ch_status, void *data) void __iomem *ep_conf = hw_ep->conf; void __iomem *mbase = musb->mregs; unsigned long remaining, flags, pio; - int ch; spin_lock_irqsave(&musb->lock, flags); - if (tusb_dma->multichannel) - ch = chdat->ch; - else - ch = tusb_dma->ch; - - if (ch_status != OMAP_DMA_BLOCK_IRQ) - printk(KERN_ERR "TUSB DMA error status: %i\n", ch_status); - - dev_dbg(musb->controller, "ep%i %s dma callback ch: %i status: %x\n", - chdat->epnum, chdat->tx ? "tx" : "rx", - ch, ch_status); + dev_dbg(musb->controller, "ep%i %s dma callback\n", + chdat->epnum, chdat->tx ? "tx" : "rx"); if (chdat->tx) remaining = musb_readl(ep_conf, TUSB_EP_TX_OFFSET); @@ -139,9 +123,8 @@ static void tusb_omap_dma_cb(int lch, u16 ch_status, void *data) /* HW issue #10: XFR_SIZE may get corrupt on DMA (both async & sync) */ if (unlikely(remaining > chdat->transfer_len)) { - dev_dbg(musb->controller, "Corrupt %s dma ch%i XFR_SIZE: 0x%08lx\n", - chdat->tx ? "tx" : "rx", chdat->ch, - remaining); + dev_dbg(musb->controller, "Corrupt %s XFR_SIZE: 0x%08lx\n", + chdat->tx ? "tx" : "rx", remaining); remaining = 0; } @@ -175,13 +158,7 @@ static void tusb_omap_dma_cb(int lch, u16 ch_status, void *data) channel->status = MUSB_DMA_STATUS_FREE; - /* Handle only RX callbacks here. TX callbacks must be handled based - * on the TUSB DMA status interrupt. - * REVISIT: Use both TUSB DMA status interrupt and OMAP DMA callback - * interrupt for RX and TX. - */ - if (!chdat->tx) - musb_dma_completion(musb, chdat->epnum, chdat->tx); + musb_dma_completion(musb, chdat->epnum, chdat->tx); /* We must terminate short tx transfers manually by setting TXPKTRDY. * REVISIT: This same problem may occur with other MUSB dma as well. @@ -214,15 +191,16 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, struct musb_hw_ep *hw_ep = chdat->hw_ep; void __iomem *mbase = musb->mregs; void __iomem *ep_conf = hw_ep->conf; - dma_addr_t fifo = hw_ep->fifo_sync; - struct omap_dma_channel_params dma_params; + dma_addr_t fifo_addr = hw_ep->fifo_sync; u32 dma_remaining; - int src_burst, dst_burst; u16 csr; u32 psize; - int ch; - s8 dmareq; - s8 sync_dev; + struct tusb_dma_data *dma_data; + struct dma_async_tx_descriptor *dma_desc; + struct dma_slave_config dma_cfg; + enum dma_transfer_direction dma_dir; + u32 port_window; + int ret; if (unlikely(dma_addr & 0x1) || (len < 32) || (len > packet_sz)) return false; @@ -248,9 +226,8 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, dma_remaining = TUSB_EP_CONFIG_XFR_SIZE(dma_remaining); if (dma_remaining) { - dev_dbg(musb->controller, "Busy %s dma ch%i, not using: %08x\n", - chdat->tx ? "tx" : "rx", chdat->ch, - dma_remaining); + dev_dbg(musb->controller, "Busy %s dma, not using: %08x\n", + chdat->tx ? "tx" : "rx", dma_remaining); return false; } @@ -261,27 +238,19 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, else chdat->transfer_packet_sz = packet_sz; - if (tusb_dma->multichannel) { - ch = chdat->ch; - dmareq = chdat->dmareq; - sync_dev = chdat->sync_dev; - } else { + dma_data = chdat->dma_data; + if (!tusb_dma->multichannel) { if (tusb_omap_use_shared_dmareq(chdat) != 0) { dev_dbg(musb->controller, "could not get dma for ep%i\n", chdat->epnum); return false; } - if (tusb_dma->ch < 0) { + if (dma_data->dmareq < 0) { /* REVISIT: This should get blocked earlier, happens * with MSC ErrorRecoveryTest */ WARN_ON(1); return false; } - - ch = tusb_dma->ch; - dmareq = tusb_dma->dmareq; - sync_dev = tusb_dma->sync_dev; - omap_set_dma_callback(ch, tusb_omap_dma_cb, channel); } chdat->packet_sz = packet_sz; @@ -291,92 +260,80 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, channel->status = MUSB_DMA_STATUS_BUSY; /* Since we're recycling dma areas, we need to clean or invalidate */ - if (chdat->tx) + if (chdat->tx) { + dma_dir = DMA_MEM_TO_DEV; dma_map_single(dev, phys_to_virt(dma_addr), len, DMA_TO_DEVICE); - else + } else { + dma_dir = DMA_DEV_TO_MEM; dma_map_single(dev, phys_to_virt(dma_addr), len, DMA_FROM_DEVICE); + } + + memset(&dma_cfg, 0, sizeof(dma_cfg)); /* Use 16-bit transfer if dma_addr is not 32-bit aligned */ if ((dma_addr & 0x3) == 0) { - dma_params.data_type = OMAP_DMA_DATA_TYPE_S32; - dma_params.elem_count = 8; /* Elements in frame */ + dma_cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + dma_cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + port_window = 8; } else { - dma_params.data_type = OMAP_DMA_DATA_TYPE_S16; - dma_params.elem_count = 16; /* Elements in frame */ - fifo = hw_ep->fifo_async; - } + dma_cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + dma_cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + port_window = 16; - dma_params.frame_count = chdat->transfer_len / 32; /* Burst sz frame */ + fifo_addr = hw_ep->fifo_async; + } - dev_dbg(musb->controller, "ep%i %s dma ch%i dma: %pad len: %u(%u) packet_sz: %i(%i)\n", - chdat->epnum, chdat->tx ? "tx" : "rx", - ch, &dma_addr, chdat->transfer_len, len, - chdat->transfer_packet_sz, packet_sz); + dev_dbg(musb->controller, + "ep%i %s dma: %pad len: %u(%u) packet_sz: %i(%i)\n", + chdat->epnum, chdat->tx ? "tx" : "rx", &dma_addr, + chdat->transfer_len, len, chdat->transfer_packet_sz, packet_sz); + + dma_cfg.src_addr = fifo_addr; + dma_cfg.dst_addr = fifo_addr; + dma_cfg.src_port_window_size = port_window; + dma_cfg.src_maxburst = port_window; + dma_cfg.dst_port_window_size = port_window; + dma_cfg.dst_maxburst = port_window; + + ret = dmaengine_slave_config(dma_data->chan, &dma_cfg); + if (ret) { + dev_err(musb->controller, "DMA slave config failed: %d\n", ret); + return false; + } - /* - * Prepare omap DMA for transfer - */ - if (chdat->tx) { - dma_params.src_amode = OMAP_DMA_AMODE_POST_INC; - dma_params.src_start = (unsigned long)dma_addr; - dma_params.src_ei = 0; - dma_params.src_fi = 0; - - dma_params.dst_amode = OMAP_DMA_AMODE_DOUBLE_IDX; - dma_params.dst_start = (unsigned long)fifo; - dma_params.dst_ei = 1; - dma_params.dst_fi = -31; /* Loop 32 byte window */ - - dma_params.trigger = sync_dev; - dma_params.sync_mode = OMAP_DMA_SYNC_FRAME; - dma_params.src_or_dst_synch = 0; /* Dest sync */ - - src_burst = OMAP_DMA_DATA_BURST_16; /* 16x32 read */ - dst_burst = OMAP_DMA_DATA_BURST_8; /* 8x32 write */ - } else { - dma_params.src_amode = OMAP_DMA_AMODE_DOUBLE_IDX; - dma_params.src_start = (unsigned long)fifo; - dma_params.src_ei = 1; - dma_params.src_fi = -31; /* Loop 32 byte window */ - - dma_params.dst_amode = OMAP_DMA_AMODE_POST_INC; - dma_params.dst_start = (unsigned long)dma_addr; - dma_params.dst_ei = 0; - dma_params.dst_fi = 0; - - dma_params.trigger = sync_dev; - dma_params.sync_mode = OMAP_DMA_SYNC_FRAME; - dma_params.src_or_dst_synch = 1; /* Source sync */ - - src_burst = OMAP_DMA_DATA_BURST_8; /* 8x32 read */ - dst_burst = OMAP_DMA_DATA_BURST_16; /* 16x32 write */ + dma_desc = dmaengine_prep_slave_single(dma_data->chan, dma_addr, + chdat->transfer_len, dma_dir, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!dma_desc) { + dev_err(musb->controller, "DMA prep_slave_single failed\n"); + return false; } - dev_dbg(musb->controller, "ep%i %s using %i-bit %s dma from 0x%08lx to 0x%08lx\n", + dma_desc->callback = tusb_omap_dma_cb; + dma_desc->callback_param = channel; + dmaengine_submit(dma_desc); + + dev_dbg(musb->controller, + "ep%i %s using %i-bit %s dma from %pad to %pad\n", chdat->epnum, chdat->tx ? "tx" : "rx", - (dma_params.data_type == OMAP_DMA_DATA_TYPE_S32) ? 32 : 16, + dma_cfg.src_addr_width * 8, ((dma_addr & 0x3) == 0) ? "sync" : "async", - dma_params.src_start, dma_params.dst_start); - - omap_set_dma_params(ch, &dma_params); - omap_set_dma_src_burst_mode(ch, src_burst); - omap_set_dma_dest_burst_mode(ch, dst_burst); - omap_set_dma_write_mode(ch, OMAP_DMA_WRITE_LAST_NON_POSTED); + (dma_dir == DMA_MEM_TO_DEV) ? &dma_addr : &fifo_addr, + (dma_dir == DMA_MEM_TO_DEV) ? &fifo_addr : &dma_addr); /* * Prepare MUSB for DMA transfer */ + musb_ep_select(mbase, chdat->epnum); if (chdat->tx) { - musb_ep_select(mbase, chdat->epnum); csr = musb_readw(hw_ep->regs, MUSB_TXCSR); csr |= (MUSB_TXCSR_AUTOSET | MUSB_TXCSR_DMAENAB | MUSB_TXCSR_DMAMODE | MUSB_TXCSR_MODE); csr &= ~MUSB_TXCSR_P_UNDERRUN; musb_writew(hw_ep->regs, MUSB_TXCSR, csr); } else { - musb_ep_select(mbase, chdat->epnum); csr = musb_readw(hw_ep->regs, MUSB_RXCSR); csr |= MUSB_RXCSR_DMAENAB; csr &= ~(MUSB_RXCSR_AUTOCLEAR | MUSB_RXCSR_DMAMODE); @@ -384,10 +341,8 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, csr | MUSB_RXCSR_P_WZC_BITS); } - /* - * Start DMA transfer - */ - omap_start_dma(ch); + /* Start DMA transfer */ + dma_async_issue_pending(dma_data->chan); if (chdat->tx) { /* Send transfer_packet_sz packets at a time */ @@ -415,18 +370,9 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, static int tusb_omap_dma_abort(struct dma_channel *channel) { struct tusb_omap_dma_ch *chdat = to_chdat(channel); - struct tusb_omap_dma *tusb_dma = chdat->tusb_dma; - if (!tusb_dma->multichannel) { - if (tusb_dma->ch >= 0) { - omap_stop_dma(tusb_dma->ch); - omap_free_dma(tusb_dma->ch); - tusb_dma->ch = -1; - } - - tusb_dma->dmareq = -1; - tusb_dma->sync_dev = -1; - } + if (chdat->dma_data) + dmaengine_terminate_all(chdat->dma_data->chan); channel->status = MUSB_DMA_STATUS_FREE; @@ -438,15 +384,6 @@ static inline int tusb_omap_dma_allocate_dmareq(struct tusb_omap_dma_ch *chdat) u32 reg = musb_readl(chdat->tbase, TUSB_DMA_EP_MAP); int i, dmareq_nr = -1; - const int sync_dev[6] = { - OMAP24XX_DMA_EXT_DMAREQ0, - OMAP24XX_DMA_EXT_DMAREQ1, - OMAP242X_DMA_EXT_DMAREQ2, - OMAP242X_DMA_EXT_DMAREQ3, - OMAP242X_DMA_EXT_DMAREQ4, - OMAP242X_DMA_EXT_DMAREQ5, - }; - for (i = 0; i < MAX_DMAREQ; i++) { int cur = (reg & (0xf << (i * 5))) >> (i * 5); if (cur == 0) { @@ -463,8 +400,7 @@ static inline int tusb_omap_dma_allocate_dmareq(struct tusb_omap_dma_ch *chdat) reg |= ((1 << 4) << (dmareq_nr * 5)); musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, reg); - chdat->dmareq = dmareq_nr; - chdat->sync_dev = sync_dev[chdat->dmareq]; + chdat->dma_data = &chdat->tusb_dma->dma_pool[dmareq_nr]; return 0; } @@ -473,15 +409,14 @@ static inline void tusb_omap_dma_free_dmareq(struct tusb_omap_dma_ch *chdat) { u32 reg; - if (!chdat || chdat->dmareq < 0) + if (!chdat || !chdat->dma_data || chdat->dma_data->dmareq < 0) return; reg = musb_readl(chdat->tbase, TUSB_DMA_EP_MAP); - reg &= ~(0x1f << (chdat->dmareq * 5)); + reg &= ~(0x1f << (chdat->dma_data->dmareq * 5)); musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, reg); - chdat->dmareq = -1; - chdat->sync_dev = -1; + chdat->dma_data = NULL; } static struct dma_channel *dma_channel_pool[MAX_DMAREQ]; @@ -492,24 +427,14 @@ tusb_omap_dma_allocate(struct dma_controller *c, u8 tx) { int ret, i; - const char *dev_name; struct tusb_omap_dma *tusb_dma; struct musb *musb; - void __iomem *tbase; struct dma_channel *channel = NULL; struct tusb_omap_dma_ch *chdat = NULL; - u32 reg; + struct tusb_dma_data *dma_data = NULL; tusb_dma = container_of(c, struct tusb_omap_dma, controller); musb = tusb_dma->controller.musb; - tbase = musb->ctrl_base; - - reg = musb_readl(tbase, TUSB_DMA_INT_MASK); - if (tx) - reg &= ~(1 << hw_ep->epnum); - else - reg &= ~(1 << (hw_ep->epnum + 15)); - musb_writel(tbase, TUSB_DMA_INT_MASK, reg); /* REVISIT: Why does dmareq5 not work? */ if (hw_ep->epnum == 0) { @@ -530,56 +455,38 @@ tusb_omap_dma_allocate(struct dma_controller *c, if (!channel) return NULL; - if (tx) { - chdat->tx = 1; - dev_name = "TUSB transmit"; - } else { - chdat->tx = 0; - dev_name = "TUSB receive"; - } - chdat->musb = tusb_dma->controller.musb; chdat->tbase = tusb_dma->tbase; chdat->hw_ep = hw_ep; chdat->epnum = hw_ep->epnum; - chdat->dmareq = -1; chdat->completed_len = 0; chdat->tusb_dma = tusb_dma; + if (tx) + chdat->tx = 1; + else + chdat->tx = 0; channel->max_len = 0x7fffffff; channel->desired_mode = 0; channel->actual_len = 0; - if (tusb_dma->multichannel) { - ret = tusb_omap_dma_allocate_dmareq(chdat); - if (ret != 0) - goto free_dmareq; - - ret = omap_request_dma(chdat->sync_dev, dev_name, - tusb_omap_dma_cb, channel, &chdat->ch); - if (ret != 0) - goto free_dmareq; - } else if (tusb_dma->ch == -1) { - tusb_dma->dmareq = 0; - tusb_dma->sync_dev = OMAP24XX_DMA_EXT_DMAREQ0; - - /* Callback data gets set later in the shared dmareq case */ - ret = omap_request_dma(tusb_dma->sync_dev, "TUSB shared", - tusb_omap_dma_cb, NULL, &tusb_dma->ch); - if (ret != 0) - goto free_dmareq; - - chdat->dmareq = -1; - chdat->ch = -1; + if (!chdat->dma_data) { + if (tusb_dma->multichannel) { + ret = tusb_omap_dma_allocate_dmareq(chdat); + if (ret != 0) + goto free_dmareq; + } else { + chdat->dma_data = &tusb_dma->dma_pool[0]; + } } - dev_dbg(musb->controller, "ep%i %s dma: %s dma%i dmareq%i sync%i\n", + dma_data = chdat->dma_data; + + dev_dbg(musb->controller, "ep%i %s dma: %s dmareq%i\n", chdat->epnum, chdat->tx ? "tx" : "rx", - chdat->ch >= 0 ? "dedicated" : "shared", - chdat->ch >= 0 ? chdat->ch : tusb_dma->ch, - chdat->dmareq >= 0 ? chdat->dmareq : tusb_dma->dmareq, - chdat->sync_dev >= 0 ? chdat->sync_dev : tusb_dma->sync_dev); + tusb_dma->multichannel ? "shared" : "dedicated", + dma_data->dmareq); return channel; @@ -596,35 +503,13 @@ static void tusb_omap_dma_release(struct dma_channel *channel) { struct tusb_omap_dma_ch *chdat = to_chdat(channel); struct musb *musb = chdat->musb; - void __iomem *tbase = musb->ctrl_base; - u32 reg; - - dev_dbg(musb->controller, "ep%i ch%i\n", chdat->epnum, chdat->ch); - - reg = musb_readl(tbase, TUSB_DMA_INT_MASK); - if (chdat->tx) - reg |= (1 << chdat->epnum); - else - reg |= (1 << (chdat->epnum + 15)); - musb_writel(tbase, TUSB_DMA_INT_MASK, reg); - reg = musb_readl(tbase, TUSB_DMA_INT_CLEAR); - if (chdat->tx) - reg |= (1 << chdat->epnum); - else - reg |= (1 << (chdat->epnum + 15)); - musb_writel(tbase, TUSB_DMA_INT_CLEAR, reg); + dev_dbg(musb->controller, "Release for ep%i\n", chdat->epnum); channel->status = MUSB_DMA_STATUS_UNKNOWN; - if (chdat->ch >= 0) { - omap_stop_dma(chdat->ch); - omap_free_dma(chdat->ch); - chdat->ch = -1; - } - - if (chdat->dmareq >= 0) - tusb_omap_dma_free_dmareq(chdat); + dmaengine_terminate_sync(chdat->dma_data->chan); + tusb_omap_dma_free_dmareq(chdat); channel = NULL; } @@ -641,15 +526,62 @@ void tusb_dma_controller_destroy(struct dma_controller *c) kfree(ch->private_data); kfree(ch); } - } - if (tusb_dma && !tusb_dma->multichannel && tusb_dma->ch >= 0) - omap_free_dma(tusb_dma->ch); + /* Free up the DMA channels */ + if (tusb_dma && tusb_dma->dma_pool[i].chan) + dma_release_channel(tusb_dma->dma_pool[i].chan); + } kfree(tusb_dma); } EXPORT_SYMBOL_GPL(tusb_dma_controller_destroy); +static int tusb_omap_allocate_dma_pool(struct tusb_omap_dma *tusb_dma) +{ + struct musb *musb = tusb_dma->controller.musb; + int i; + int ret = 0; + + for (i = 0; i < MAX_DMAREQ; i++) { + struct tusb_dma_data *dma_data = &tusb_dma->dma_pool[i]; + + /* + * Request DMA channels: + * - one channel in case of non multichannel mode + * - MAX_DMAREQ number of channels in multichannel mode + */ + if (i == 0 || tusb_dma->multichannel) { + char ch_name[8]; + + sprintf(ch_name, "dmareq%d", i); + dma_data->chan = dma_request_chan(musb->controller, + ch_name); + if (IS_ERR(dma_data->chan)) { + dev_err(musb->controller, + "Failed to request %s\n", ch_name); + ret = PTR_ERR(dma_data->chan); + goto dma_error; + } + + dma_data->dmareq = i; + } else { + dma_data->dmareq = -1; + } + } + + return 0; + +dma_error: + for (; i >= 0; i--) { + struct tusb_dma_data *dma_data = &tusb_dma->dma_pool[i]; + + if (dma_data->dmareq >= 0) + dma_release_channel(dma_data->chan); + } + + return ret; +} + struct dma_controller * tusb_dma_controller_create(struct musb *musb, void __iomem *base) { @@ -674,10 +606,6 @@ tusb_dma_controller_create(struct musb *musb, void __iomem *base) tusb_dma->controller.musb = musb; tusb_dma->tbase = musb->ctrl_base; - tusb_dma->ch = -1; - tusb_dma->dmareq = -1; - tusb_dma->sync_dev = -1; - tusb_dma->controller.channel_alloc = tusb_omap_dma_allocate; tusb_dma->controller.channel_release = tusb_omap_dma_release; tusb_dma->controller.channel_program = tusb_omap_dma_program; @@ -704,6 +632,9 @@ tusb_dma_controller_create(struct musb *musb, void __iomem *base) ch->private_data = chdat; } + if (tusb_omap_allocate_dma_pool(tusb_dma)) + goto cleanup; + return &tusb_dma->controller; cleanup: diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 3006f569c068..aff702c0eb9f 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -4,6 +4,7 @@ menu "USB Physical Layer drivers" config USB_PHY + select EXTCON def_bool n # @@ -109,7 +110,7 @@ config OMAP_OTG config TAHVO_USB tristate "Tahvo USB transceiver driver" - depends on MFD_RETU && EXTCON + depends on MFD_RETU depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't be 'y' select USB_PHY help @@ -141,7 +142,6 @@ config USB_MSM_OTG depends on (USB || USB_GADGET) && (ARCH_QCOM || COMPILE_TEST) depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't be 'y' depends on RESET_CONTROLLER - depends on EXTCON select USB_PHY help Enable this to support the USB OTG transceiver on Qualcomm chips. It @@ -155,7 +155,7 @@ config USB_MSM_OTG config USB_QCOM_8X16_PHY tristate "Qualcomm APQ8016/MSM8916 on-chip USB PHY controller support" depends on ARCH_QCOM || COMPILE_TEST - depends on RESET_CONTROLLER && EXTCON + depends on RESET_CONTROLLER select USB_PHY select USB_ULPI_VIEWPORT help diff --git a/drivers/usb/phy/phy-msm-usb.c b/drivers/usb/phy/phy-msm-usb.c index 93d9aaad2994..8fb86a5f458e 100644 --- a/drivers/usb/phy/phy-msm-usb.c +++ b/drivers/usb/phy/phy-msm-usb.c @@ -146,17 +146,6 @@ struct msm_otg_platform_data { }; /** - * struct msm_usb_cable - structure for exteternal connector cable - * state tracking - * @nb: hold event notification callback - * @conn: used for notification registration - */ -struct msm_usb_cable { - struct notifier_block nb; - struct extcon_dev *extcon; -}; - -/** * struct msm_otg: OTG driver data. Shared by HCD and DCD. * @otg: USB OTG Transceiver structure. * @pdata: otg device platform data. @@ -215,9 +204,6 @@ struct msm_otg { bool manual_pullup; - struct msm_usb_cable vbus; - struct msm_usb_cable id; - struct gpio_desc *switch_gpio; struct notifier_block reboot; }; @@ -1612,8 +1598,8 @@ MODULE_DEVICE_TABLE(of, msm_otg_dt_match); static int msm_otg_vbus_notifier(struct notifier_block *nb, unsigned long event, void *ptr) { - struct msm_usb_cable *vbus = container_of(nb, struct msm_usb_cable, nb); - struct msm_otg *motg = container_of(vbus, struct msm_otg, vbus); + struct usb_phy *usb_phy = container_of(nb, struct usb_phy, vbus_nb); + struct msm_otg *motg = container_of(usb_phy, struct msm_otg, phy); if (event) set_bit(B_SESS_VLD, &motg->inputs); @@ -1636,8 +1622,8 @@ static int msm_otg_vbus_notifier(struct notifier_block *nb, unsigned long event, static int msm_otg_id_notifier(struct notifier_block *nb, unsigned long event, void *ptr) { - struct msm_usb_cable *id = container_of(nb, struct msm_usb_cable, nb); - struct msm_otg *motg = container_of(id, struct msm_otg, id); + struct usb_phy *usb_phy = container_of(nb, struct usb_phy, id_nb); + struct msm_otg *motg = container_of(usb_phy, struct msm_otg, phy); if (event) clear_bit(ID, &motg->inputs); @@ -1652,7 +1638,6 @@ static int msm_otg_id_notifier(struct notifier_block *nb, unsigned long event, static int msm_otg_read_dt(struct platform_device *pdev, struct msm_otg *motg) { struct msm_otg_platform_data *pdata; - struct extcon_dev *ext_id, *ext_vbus; struct device_node *node = pdev->dev.of_node; struct property *prop; int len, ret, words; @@ -1708,54 +1693,6 @@ static int msm_otg_read_dt(struct platform_device *pdev, struct msm_otg *motg) if (IS_ERR(motg->switch_gpio)) return PTR_ERR(motg->switch_gpio); - ext_id = ERR_PTR(-ENODEV); - ext_vbus = ERR_PTR(-ENODEV); - if (of_property_read_bool(node, "extcon")) { - - /* Each one of them is not mandatory */ - ext_vbus = extcon_get_edev_by_phandle(&pdev->dev, 0); - if (IS_ERR(ext_vbus) && PTR_ERR(ext_vbus) != -ENODEV) - return PTR_ERR(ext_vbus); - - ext_id = extcon_get_edev_by_phandle(&pdev->dev, 1); - if (IS_ERR(ext_id) && PTR_ERR(ext_id) != -ENODEV) - return PTR_ERR(ext_id); - } - - if (!IS_ERR(ext_vbus)) { - motg->vbus.extcon = ext_vbus; - motg->vbus.nb.notifier_call = msm_otg_vbus_notifier; - ret = devm_extcon_register_notifier(&pdev->dev, ext_vbus, - EXTCON_USB, &motg->vbus.nb); - if (ret < 0) { - dev_err(&pdev->dev, "register VBUS notifier failed\n"); - return ret; - } - - ret = extcon_get_state(ext_vbus, EXTCON_USB); - if (ret) - set_bit(B_SESS_VLD, &motg->inputs); - else - clear_bit(B_SESS_VLD, &motg->inputs); - } - - if (!IS_ERR(ext_id)) { - motg->id.extcon = ext_id; - motg->id.nb.notifier_call = msm_otg_id_notifier; - ret = devm_extcon_register_notifier(&pdev->dev, ext_id, - EXTCON_USB_HOST, &motg->id.nb); - if (ret < 0) { - dev_err(&pdev->dev, "register ID notifier failed\n"); - return ret; - } - - ret = extcon_get_state(ext_id, EXTCON_USB_HOST); - if (ret) - clear_bit(ID, &motg->inputs); - else - set_bit(ID, &motg->inputs); - } - prop = of_find_property(node, "qcom,phy-init-sequence", &len); if (!prop || !len) return 0; @@ -1932,6 +1869,8 @@ static int msm_otg_probe(struct platform_device *pdev) phy->init = msm_phy_init; phy->notify_disconnect = msm_phy_notify_disconnect; phy->type = USB_PHY_TYPE_USB2; + phy->vbus_nb.notifier_call = msm_otg_vbus_notifier; + phy->id_nb.notifier_call = msm_otg_id_notifier; phy->io_ops = &msm_otg_io_ops; @@ -1947,6 +1886,18 @@ static int msm_otg_probe(struct platform_device *pdev) goto disable_ldo; } + ret = extcon_get_state(phy->edev, EXTCON_USB); + if (ret) + set_bit(B_SESS_VLD, &motg->inputs); + else + clear_bit(B_SESS_VLD, &motg->inputs); + + ret = extcon_get_state(phy->id_edev, EXTCON_USB_HOST); + if (ret) + clear_bit(ID, &motg->inputs); + else + set_bit(ID, &motg->inputs); + platform_set_drvdata(pdev, motg); device_init_wakeup(&pdev->dev, 1); diff --git a/drivers/usb/phy/phy-qcom-8x16-usb.c b/drivers/usb/phy/phy-qcom-8x16-usb.c index fdf686398772..b6a83a5cbad3 100644 --- a/drivers/usb/phy/phy-qcom-8x16-usb.c +++ b/drivers/usb/phy/phy-qcom-8x16-usb.c @@ -69,9 +69,6 @@ struct phy_8x16 { struct reset_control *phy_reset; - struct extcon_dev *vbus_edev; - struct notifier_block vbus_notify; - struct gpio_desc *switch_gpio; struct notifier_block reboot_notify; }; @@ -131,7 +128,8 @@ static int phy_8x16_vbus_off(struct phy_8x16 *qphy) static int phy_8x16_vbus_notify(struct notifier_block *nb, unsigned long event, void *ptr) { - struct phy_8x16 *qphy = container_of(nb, struct phy_8x16, vbus_notify); + struct usb_phy *usb_phy = container_of(nb, struct usb_phy, vbus_nb); + struct phy_8x16 *qphy = container_of(usb_phy, struct phy_8x16, phy); if (event) phy_8x16_vbus_on(qphy); @@ -187,7 +185,7 @@ static int phy_8x16_init(struct usb_phy *phy) val = ULPI_PWR_OTG_COMP_DISABLE; usb_phy_io_write(phy, val, ULPI_SET(ULPI_PWR_CLK_MNG_REG)); - state = extcon_get_state(qphy->vbus_edev, EXTCON_USB); + state = extcon_get_state(qphy->phy.edev, EXTCON_USB); if (state) phy_8x16_vbus_on(qphy); else @@ -289,15 +287,13 @@ static int phy_8x16_probe(struct platform_device *pdev) phy->io_priv = qphy->regs + HSPHY_ULPI_VIEWPORT; phy->io_ops = &ulpi_viewport_access_ops; phy->type = USB_PHY_TYPE_USB2; + phy->vbus_nb.notifier_call = phy_8x16_vbus_notify; + phy->id_nb.notifier_call = NULL; ret = phy_8x16_read_devicetree(qphy); if (ret < 0) return ret; - qphy->vbus_edev = extcon_get_edev_by_phandle(phy->dev, 0); - if (IS_ERR(qphy->vbus_edev)) - return PTR_ERR(qphy->vbus_edev); - ret = clk_set_rate(qphy->core_clk, INT_MAX); if (ret < 0) dev_dbg(phy->dev, "Can't boost core clock\n"); @@ -315,12 +311,6 @@ static int phy_8x16_probe(struct platform_device *pdev) if (WARN_ON(ret)) goto off_clks; - qphy->vbus_notify.notifier_call = phy_8x16_vbus_notify; - ret = devm_extcon_register_notifier(&pdev->dev, qphy->vbus_edev, - EXTCON_USB, &qphy->vbus_notify); - if (ret < 0) - goto off_power; - ret = usb_add_phy_dev(&qphy->phy); if (ret) goto off_power; diff --git a/drivers/usb/phy/phy.c b/drivers/usb/phy/phy.c index 98f75d2842b7..032f5afaad4b 100644 --- a/drivers/usb/phy/phy.c +++ b/drivers/usb/phy/phy.c @@ -100,6 +100,54 @@ static int devm_usb_phy_match(struct device *dev, void *res, void *match_data) return *phy == match_data; } +static int usb_add_extcon(struct usb_phy *x) +{ + int ret; + + if (of_property_read_bool(x->dev->of_node, "extcon")) { + x->edev = extcon_get_edev_by_phandle(x->dev, 0); + if (IS_ERR(x->edev)) + return PTR_ERR(x->edev); + + x->id_edev = extcon_get_edev_by_phandle(x->dev, 1); + if (IS_ERR(x->id_edev)) { + x->id_edev = NULL; + dev_info(x->dev, "No separate ID extcon device\n"); + } + + if (x->vbus_nb.notifier_call) { + ret = devm_extcon_register_notifier(x->dev, x->edev, + EXTCON_USB, + &x->vbus_nb); + if (ret < 0) { + dev_err(x->dev, + "register VBUS notifier failed\n"); + return ret; + } + } + + if (x->id_nb.notifier_call) { + struct extcon_dev *id_ext; + + if (x->id_edev) + id_ext = x->id_edev; + else + id_ext = x->edev; + + ret = devm_extcon_register_notifier(x->dev, id_ext, + EXTCON_USB_HOST, + &x->id_nb); + if (ret < 0) { + dev_err(x->dev, + "register ID notifier failed\n"); + return ret; + } + } + } + + return 0; +} + /** * devm_usb_get_phy - find the USB PHY * @dev - device that requests this phy @@ -388,6 +436,10 @@ int usb_add_phy(struct usb_phy *x, enum usb_phy_type type) return -EINVAL; } + ret = usb_add_extcon(x); + if (ret) + return ret; + ATOMIC_INIT_NOTIFIER_HEAD(&x->notifier); spin_lock_irqsave(&phy_lock, flags); @@ -422,12 +474,17 @@ int usb_add_phy_dev(struct usb_phy *x) { struct usb_phy_bind *phy_bind; unsigned long flags; + int ret; if (!x->dev) { dev_err(x->dev, "no device provided for PHY\n"); return -EINVAL; } + ret = usb_add_extcon(x); + if (ret) + return ret; + ATOMIC_INIT_NOTIFIER_HEAD(&x->notifier); spin_lock_irqsave(&phy_lock, flags); diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 0c55e7f64269..f64e914a8985 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -141,6 +141,7 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(0x10C4, 0x8977) }, /* CEL MeshWorks DevKit Device */ { USB_DEVICE(0x10C4, 0x8998) }, /* KCF Technologies PRN */ { USB_DEVICE(0x10C4, 0x8A2A) }, /* HubZ dual ZigBee and Z-Wave dongle */ + { USB_DEVICE(0x10C4, 0x8A5E) }, /* CEL EM3588 ZigBee USB Stick Long Range */ { USB_DEVICE(0x10C4, 0xEA60) }, /* Silicon Labs factory default */ { USB_DEVICE(0x10C4, 0xEA61) }, /* Silicon Labs factory default */ { USB_DEVICE(0x10C4, 0xEA70) }, /* Silicon Labs factory default */ diff --git a/drivers/usb/serial/qcserial.c b/drivers/usb/serial/qcserial.c index fd509ed6cf70..4ac137d070fb 100644 --- a/drivers/usb/serial/qcserial.c +++ b/drivers/usb/serial/qcserial.c @@ -454,6 +454,8 @@ static struct usb_serial_driver qcdevice = { .write = usb_wwan_write, .write_room = usb_wwan_write_room, .chars_in_buffer = usb_wwan_chars_in_buffer, + .tiocmget = usb_wwan_tiocmget, + .tiocmset = usb_wwan_tiocmset, .attach = qc_attach, .release = qc_release, .port_probe = usb_wwan_port_probe, diff --git a/drivers/usb/serial/upd78f0730.c b/drivers/usb/serial/upd78f0730.c index a028dd2310c9..6819a3486e5d 100644 --- a/drivers/usb/serial/upd78f0730.c +++ b/drivers/usb/serial/upd78f0730.c @@ -288,7 +288,7 @@ static void upd78f0730_dtr_rts(struct usb_serial_port *port, int on) static speed_t upd78f0730_get_baud_rate(struct tty_struct *tty) { const speed_t baud_rate = tty_get_baud_rate(tty); - const speed_t supported[] = { + static const speed_t supported[] = { 0, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 153600 }; int i; @@ -384,7 +384,7 @@ static void upd78f0730_set_termios(struct tty_struct *tty, static int upd78f0730_open(struct tty_struct *tty, struct usb_serial_port *port) { - struct upd78f0730_open_close request = { + static const struct upd78f0730_open_close request = { .opcode = UPD78F0730_CMD_OPEN_CLOSE, .state = UPD78F0730_PORT_OPEN }; @@ -402,7 +402,7 @@ static int upd78f0730_open(struct tty_struct *tty, struct usb_serial_port *port) static void upd78f0730_close(struct usb_serial_port *port) { - struct upd78f0730_open_close request = { + static const struct upd78f0730_open_close request = { .opcode = UPD78F0730_CMD_OPEN_CLOSE, .state = UPD78F0730_PORT_CLOSE }; diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index c7ca95f64edc..bb34f9f7eaf4 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -742,6 +742,124 @@ static void find_endpoints(struct usb_serial *serial, } } +static int setup_port_bulk_in(struct usb_serial_port *port, + struct usb_endpoint_descriptor *epd) +{ + struct usb_serial_driver *type = port->serial->type; + struct usb_device *udev = port->serial->dev; + int buffer_size; + int i; + + buffer_size = max_t(int, type->bulk_in_size, usb_endpoint_maxp(epd)); + port->bulk_in_size = buffer_size; + port->bulk_in_endpointAddress = epd->bEndpointAddress; + + for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i) { + set_bit(i, &port->read_urbs_free); + port->read_urbs[i] = usb_alloc_urb(0, GFP_KERNEL); + if (!port->read_urbs[i]) + return -ENOMEM; + port->bulk_in_buffers[i] = kmalloc(buffer_size, GFP_KERNEL); + if (!port->bulk_in_buffers[i]) + return -ENOMEM; + usb_fill_bulk_urb(port->read_urbs[i], udev, + usb_rcvbulkpipe(udev, epd->bEndpointAddress), + port->bulk_in_buffers[i], buffer_size, + type->read_bulk_callback, port); + } + + port->read_urb = port->read_urbs[0]; + port->bulk_in_buffer = port->bulk_in_buffers[0]; + + return 0; +} + +static int setup_port_bulk_out(struct usb_serial_port *port, + struct usb_endpoint_descriptor *epd) +{ + struct usb_serial_driver *type = port->serial->type; + struct usb_device *udev = port->serial->dev; + int buffer_size; + int i; + + if (kfifo_alloc(&port->write_fifo, PAGE_SIZE, GFP_KERNEL)) + return -ENOMEM; + if (type->bulk_out_size) + buffer_size = type->bulk_out_size; + else + buffer_size = usb_endpoint_maxp(epd); + port->bulk_out_size = buffer_size; + port->bulk_out_endpointAddress = epd->bEndpointAddress; + + for (i = 0; i < ARRAY_SIZE(port->write_urbs); ++i) { + set_bit(i, &port->write_urbs_free); + port->write_urbs[i] = usb_alloc_urb(0, GFP_KERNEL); + if (!port->write_urbs[i]) + return -ENOMEM; + port->bulk_out_buffers[i] = kmalloc(buffer_size, GFP_KERNEL); + if (!port->bulk_out_buffers[i]) + return -ENOMEM; + usb_fill_bulk_urb(port->write_urbs[i], udev, + usb_sndbulkpipe(udev, epd->bEndpointAddress), + port->bulk_out_buffers[i], buffer_size, + type->write_bulk_callback, port); + } + + port->write_urb = port->write_urbs[0]; + port->bulk_out_buffer = port->bulk_out_buffers[0]; + + return 0; +} + +static int setup_port_interrupt_in(struct usb_serial_port *port, + struct usb_endpoint_descriptor *epd) +{ + struct usb_serial_driver *type = port->serial->type; + struct usb_device *udev = port->serial->dev; + int buffer_size; + + port->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!port->interrupt_in_urb) + return -ENOMEM; + buffer_size = usb_endpoint_maxp(epd); + port->interrupt_in_endpointAddress = epd->bEndpointAddress; + port->interrupt_in_buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!port->interrupt_in_buffer) + return -ENOMEM; + usb_fill_int_urb(port->interrupt_in_urb, udev, + usb_rcvintpipe(udev, epd->bEndpointAddress), + port->interrupt_in_buffer, buffer_size, + type->read_int_callback, port, + epd->bInterval); + + return 0; +} + +static int setup_port_interrupt_out(struct usb_serial_port *port, + struct usb_endpoint_descriptor *epd) +{ + struct usb_serial_driver *type = port->serial->type; + struct usb_device *udev = port->serial->dev; + int buffer_size; + + port->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!port->interrupt_out_urb) + return -ENOMEM; + buffer_size = usb_endpoint_maxp(epd); + port->interrupt_out_size = buffer_size; + port->interrupt_out_endpointAddress = epd->bEndpointAddress; + port->interrupt_out_buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!port->interrupt_out_buffer) + return -ENOMEM; + usb_fill_int_urb(port->interrupt_out_urb, udev, + usb_sndintpipe(udev, epd->bEndpointAddress), + port->interrupt_out_buffer, buffer_size, + type->write_int_callback, port, + epd->bInterval); + + return 0; +} + static int usb_serial_probe(struct usb_interface *interface, const struct usb_device_id *id) { @@ -749,13 +867,10 @@ static int usb_serial_probe(struct usb_interface *interface, struct usb_device *dev = interface_to_usbdev(interface); struct usb_serial *serial = NULL; struct usb_serial_port *port; - struct usb_endpoint_descriptor *endpoint; struct usb_serial_endpoints *epds; struct usb_serial_driver *type = NULL; int retval; - int buffer_size; int i; - int j; int num_ports = 0; unsigned char max_endpoints; @@ -847,8 +962,10 @@ static int usb_serial_probe(struct usb_interface *interface, dev_dbg(ddev, "setting up %d port structure(s)\n", max_endpoints); for (i = 0; i < max_endpoints; ++i) { port = kzalloc(sizeof(struct usb_serial_port), GFP_KERNEL); - if (!port) - goto probe_error; + if (!port) { + retval = -ENOMEM; + goto err_free_epds; + } tty_port_init(&port->port); port->port.ops = &serial_port_ops; port->serial = serial; @@ -867,86 +984,24 @@ static int usb_serial_probe(struct usb_interface *interface, /* set up the endpoint information */ for (i = 0; i < epds->num_bulk_in; ++i) { - endpoint = epds->bulk_in[i]; - port = serial->port[i]; - buffer_size = max_t(int, serial->type->bulk_in_size, - usb_endpoint_maxp(endpoint)); - port->bulk_in_size = buffer_size; - port->bulk_in_endpointAddress = endpoint->bEndpointAddress; - - for (j = 0; j < ARRAY_SIZE(port->read_urbs); ++j) { - set_bit(j, &port->read_urbs_free); - port->read_urbs[j] = usb_alloc_urb(0, GFP_KERNEL); - if (!port->read_urbs[j]) - goto probe_error; - port->bulk_in_buffers[j] = kmalloc(buffer_size, - GFP_KERNEL); - if (!port->bulk_in_buffers[j]) - goto probe_error; - usb_fill_bulk_urb(port->read_urbs[j], dev, - usb_rcvbulkpipe(dev, - endpoint->bEndpointAddress), - port->bulk_in_buffers[j], buffer_size, - serial->type->read_bulk_callback, - port); - } - - port->read_urb = port->read_urbs[0]; - port->bulk_in_buffer = port->bulk_in_buffers[0]; + retval = setup_port_bulk_in(serial->port[i], epds->bulk_in[i]); + if (retval) + goto err_free_epds; } for (i = 0; i < epds->num_bulk_out; ++i) { - endpoint = epds->bulk_out[i]; - port = serial->port[i]; - if (kfifo_alloc(&port->write_fifo, PAGE_SIZE, GFP_KERNEL)) - goto probe_error; - buffer_size = serial->type->bulk_out_size; - if (!buffer_size) - buffer_size = usb_endpoint_maxp(endpoint); - port->bulk_out_size = buffer_size; - port->bulk_out_endpointAddress = endpoint->bEndpointAddress; - - for (j = 0; j < ARRAY_SIZE(port->write_urbs); ++j) { - set_bit(j, &port->write_urbs_free); - port->write_urbs[j] = usb_alloc_urb(0, GFP_KERNEL); - if (!port->write_urbs[j]) - goto probe_error; - port->bulk_out_buffers[j] = kmalloc(buffer_size, - GFP_KERNEL); - if (!port->bulk_out_buffers[j]) - goto probe_error; - usb_fill_bulk_urb(port->write_urbs[j], dev, - usb_sndbulkpipe(dev, - endpoint->bEndpointAddress), - port->bulk_out_buffers[j], buffer_size, - serial->type->write_bulk_callback, - port); - } - - port->write_urb = port->write_urbs[0]; - port->bulk_out_buffer = port->bulk_out_buffers[0]; + retval = setup_port_bulk_out(serial->port[i], + epds->bulk_out[i]); + if (retval) + goto err_free_epds; } if (serial->type->read_int_callback) { for (i = 0; i < epds->num_interrupt_in; ++i) { - endpoint = epds->interrupt_in[i]; - port = serial->port[i]; - port->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); - if (!port->interrupt_in_urb) - goto probe_error; - buffer_size = usb_endpoint_maxp(endpoint); - port->interrupt_in_endpointAddress = - endpoint->bEndpointAddress; - port->interrupt_in_buffer = kmalloc(buffer_size, - GFP_KERNEL); - if (!port->interrupt_in_buffer) - goto probe_error; - usb_fill_int_urb(port->interrupt_in_urb, dev, - usb_rcvintpipe(dev, - endpoint->bEndpointAddress), - port->interrupt_in_buffer, buffer_size, - serial->type->read_int_callback, port, - endpoint->bInterval); + retval = setup_port_interrupt_in(serial->port[i], + epds->interrupt_in[i]); + if (retval) + goto err_free_epds; } } else if (epds->num_interrupt_in) { dev_dbg(ddev, "The device claims to support interrupt in transfers, but read_int_callback is not defined\n"); @@ -954,25 +1009,10 @@ static int usb_serial_probe(struct usb_interface *interface, if (serial->type->write_int_callback) { for (i = 0; i < epds->num_interrupt_out; ++i) { - endpoint = epds->interrupt_out[i]; - port = serial->port[i]; - port->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); - if (!port->interrupt_out_urb) - goto probe_error; - buffer_size = usb_endpoint_maxp(endpoint); - port->interrupt_out_size = buffer_size; - port->interrupt_out_endpointAddress = - endpoint->bEndpointAddress; - port->interrupt_out_buffer = kmalloc(buffer_size, - GFP_KERNEL); - if (!port->interrupt_out_buffer) - goto probe_error; - usb_fill_int_urb(port->interrupt_out_urb, dev, - usb_sndintpipe(dev, - endpoint->bEndpointAddress), - port->interrupt_out_buffer, buffer_size, - serial->type->write_int_callback, port, - endpoint->bInterval); + retval = setup_port_interrupt_out(serial->port[i], + epds->interrupt_out[i]); + if (retval) + goto err_free_epds; } } else if (epds->num_interrupt_out) { dev_dbg(ddev, "The device claims to support interrupt out transfers, but write_int_callback is not defined\n"); @@ -984,7 +1024,7 @@ static int usb_serial_probe(struct usb_interface *interface, if (type->attach) { retval = type->attach(serial); if (retval < 0) - goto probe_error; + goto err_free_epds; serial->attached = 1; if (retval > 0) { /* quietly accept this device, but don't bind to a @@ -996,9 +1036,10 @@ static int usb_serial_probe(struct usb_interface *interface, serial->attached = 1; } - if (allocate_minors(serial, num_ports)) { + retval = allocate_minors(serial, num_ports); + if (retval) { dev_err(ddev, "No more free serial minor numbers\n"); - goto probe_error; + goto err_free_epds; } /* register all of the individual ports with the driver core */ @@ -1020,8 +1061,6 @@ exit: module_put(type->driver.owner); return 0; -probe_error: - retval = -EIO; err_free_epds: kfree(epds); err_put_serial: diff --git a/drivers/usb/storage/ene_ub6250.c b/drivers/usb/storage/ene_ub6250.c index 44af719194b2..28100374f7bd 100644 --- a/drivers/usb/storage/ene_ub6250.c +++ b/drivers/usb/storage/ene_ub6250.c @@ -95,12 +95,12 @@ static struct us_unusual_dev ene_ub6250_unusual_dev_list[] = { #define REG_HW_TRAP1 0xFF89 /* SRB Status */ -#define SS_SUCCESS 0x00 /* No Sense */ -#define SS_NOT_READY 0x02 -#define SS_MEDIUM_ERR 0x03 -#define SS_HW_ERR 0x04 -#define SS_ILLEGAL_REQUEST 0x05 -#define SS_UNIT_ATTENTION 0x06 +#define SS_SUCCESS 0x000000 /* No Sense */ +#define SS_NOT_READY 0x023A00 /* Medium not present */ +#define SS_MEDIUM_ERR 0x031100 /* Unrecovered read error */ +#define SS_HW_ERR 0x040800 /* Communication failure */ +#define SS_ILLEGAL_REQUEST 0x052000 /* Invalid command */ +#define SS_UNIT_ATTENTION 0x062900 /* Reset occurred */ /* ENE Load FW Pattern */ #define SD_INIT1_PATTERN 1 @@ -584,24 +584,26 @@ static int ene_send_scsi_cmd(struct us_data *us, u8 fDir, void *buf, int use_sg) return USB_STOR_TRANSPORT_GOOD; } -static int sd_scsi_test_unit_ready(struct us_data *us, struct scsi_cmnd *srb) +static int do_scsi_request_sense(struct us_data *us, struct scsi_cmnd *srb) { struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + unsigned char buf[18]; - if (info->SD_Status.Insert && info->SD_Status.Ready) - return USB_STOR_TRANSPORT_GOOD; - else { - ene_sd_init(us); - return USB_STOR_TRANSPORT_GOOD; - } + memset(buf, 0, 18); + buf[0] = 0x70; /* Current error */ + buf[2] = info->SrbStatus >> 16; /* Sense key */ + buf[7] = 10; /* Additional length */ + buf[12] = info->SrbStatus >> 8; /* ASC */ + buf[13] = info->SrbStatus; /* ASCQ */ + usb_stor_set_xfer_buf(buf, sizeof(buf), srb); return USB_STOR_TRANSPORT_GOOD; } -static int sd_scsi_inquiry(struct us_data *us, struct scsi_cmnd *srb) +static int do_scsi_inquiry(struct us_data *us, struct scsi_cmnd *srb) { unsigned char data_ptr[36] = { - 0x00, 0x80, 0x02, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x55, + 0x00, 0x00, 0x02, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x55, 0x53, 0x42, 0x32, 0x2E, 0x30, 0x20, 0x20, 0x43, 0x61, 0x72, 0x64, 0x52, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x31, 0x30, 0x30 }; @@ -610,6 +612,20 @@ static int sd_scsi_inquiry(struct us_data *us, struct scsi_cmnd *srb) return USB_STOR_TRANSPORT_GOOD; } +static int sd_scsi_test_unit_ready(struct us_data *us, struct scsi_cmnd *srb) +{ + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + if (info->SD_Status.Insert && info->SD_Status.Ready) + return USB_STOR_TRANSPORT_GOOD; + else { + ene_sd_init(us); + return USB_STOR_TRANSPORT_GOOD; + } + + return USB_STOR_TRANSPORT_GOOD; +} + static int sd_scsi_mode_sense(struct us_data *us, struct scsi_cmnd *srb) { struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; @@ -1455,19 +1471,6 @@ static int ms_scsi_test_unit_ready(struct us_data *us, struct scsi_cmnd *srb) return USB_STOR_TRANSPORT_GOOD; } -static int ms_scsi_inquiry(struct us_data *us, struct scsi_cmnd *srb) -{ - /* pr_info("MS_SCSI_Inquiry\n"); */ - unsigned char data_ptr[36] = { - 0x00, 0x80, 0x02, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x55, - 0x53, 0x42, 0x32, 0x2E, 0x30, 0x20, 0x20, 0x43, 0x61, - 0x72, 0x64, 0x52, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x31, 0x30, 0x30}; - - usb_stor_set_xfer_buf(data_ptr, 36, srb); - return USB_STOR_TRANSPORT_GOOD; -} - static int ms_scsi_mode_sense(struct us_data *us, struct scsi_cmnd *srb) { struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; @@ -1940,6 +1943,8 @@ static int ene_load_bincode(struct us_data *us, unsigned char flag) bcb->CDB[0] = 0xEF; result = ene_send_scsi_cmd(us, FDIR_WRITE, buf, 0); + if (us->srb != NULL) + scsi_set_resid(us->srb, 0); info->BIN_FLAG = flag; kfree(buf); @@ -2223,13 +2228,15 @@ static int sd_scsi_irp(struct us_data *us, struct scsi_cmnd *srb) int result; struct ene_ub6250_info *info = (struct ene_ub6250_info *)us->extra; - info->SrbStatus = SS_SUCCESS; switch (srb->cmnd[0]) { case TEST_UNIT_READY: result = sd_scsi_test_unit_ready(us, srb); break; /* 0x00 */ + case REQUEST_SENSE: + result = do_scsi_request_sense(us, srb); + break; /* 0x03 */ case INQUIRY: - result = sd_scsi_inquiry(us, srb); + result = do_scsi_inquiry(us, srb); break; /* 0x12 */ case MODE_SENSE: result = sd_scsi_mode_sense(us, srb); @@ -2253,6 +2260,8 @@ static int sd_scsi_irp(struct us_data *us, struct scsi_cmnd *srb) result = USB_STOR_TRANSPORT_FAILED; break; } + if (result == USB_STOR_TRANSPORT_GOOD) + info->SrbStatus = SS_SUCCESS; return result; } @@ -2263,13 +2272,16 @@ static int ms_scsi_irp(struct us_data *us, struct scsi_cmnd *srb) { int result; struct ene_ub6250_info *info = (struct ene_ub6250_info *)us->extra; - info->SrbStatus = SS_SUCCESS; + switch (srb->cmnd[0]) { case TEST_UNIT_READY: result = ms_scsi_test_unit_ready(us, srb); break; /* 0x00 */ + case REQUEST_SENSE: + result = do_scsi_request_sense(us, srb); + break; /* 0x03 */ case INQUIRY: - result = ms_scsi_inquiry(us, srb); + result = do_scsi_inquiry(us, srb); break; /* 0x12 */ case MODE_SENSE: result = ms_scsi_mode_sense(us, srb); @@ -2288,26 +2300,29 @@ static int ms_scsi_irp(struct us_data *us, struct scsi_cmnd *srb) result = USB_STOR_TRANSPORT_FAILED; break; } + if (result == USB_STOR_TRANSPORT_GOOD) + info->SrbStatus = SS_SUCCESS; return result; } static int ene_transport(struct scsi_cmnd *srb, struct us_data *us) { - int result = 0; + int result = USB_STOR_XFER_GOOD; struct ene_ub6250_info *info = (struct ene_ub6250_info *)(us->extra); /*US_DEBUG(usb_stor_show_command(us, srb)); */ scsi_set_resid(srb, 0); - if (unlikely(!(info->SD_Status.Ready || info->MS_Status.Ready))) { + if (unlikely(!(info->SD_Status.Ready || info->MS_Status.Ready))) result = ene_init(us); - } else { + if (result == USB_STOR_XFER_GOOD) { + result = USB_STOR_TRANSPORT_ERROR; if (info->SD_Status.Ready) result = sd_scsi_irp(us, srb); if (info->MS_Status.Ready) result = ms_scsi_irp(us, srb); } - return 0; + return result; } static struct scsi_host_template ene_ub6250_host_template; diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig index dfcfe459b7cf..bc1b7745f1d4 100644 --- a/drivers/usb/typec/Kconfig +++ b/drivers/usb/typec/Kconfig @@ -19,4 +19,6 @@ config TYPEC_WCOVE To compile this driver as module, choose M here: the module will be called typec_wcove +source "drivers/usb/typec/ucsi/Kconfig" + endmenu diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile index b9cb862221af..bc214f15f1b5 100644 --- a/drivers/usb/typec/Makefile +++ b/drivers/usb/typec/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_TYPEC) += typec.o obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o +obj-$(CONFIG_TYPEC_UCSI) += ucsi/ diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/typec.c index 89e540bb7ff3..24e355ba109d 100644 --- a/drivers/usb/typec/typec.c +++ b/drivers/usb/typec/typec.c @@ -11,6 +11,7 @@ #include <linux/device.h> #include <linux/module.h> +#include <linux/mutex.h> #include <linux/slab.h> #include <linux/usb/typec.h> @@ -69,6 +70,8 @@ struct typec_port { enum typec_role pwr_role; enum typec_role vconn_role; enum typec_pwr_opmode pwr_opmode; + enum typec_port_type port_type; + struct mutex port_type_lock; const struct typec_capability *cap; }; @@ -291,7 +294,7 @@ typec_altmode_roles_show(struct device *dev, struct device_attribute *attr, } static void typec_init_modes(struct typec_altmode *alt, - struct typec_mode_desc *desc, bool is_port) + const struct typec_mode_desc *desc, bool is_port) { int i; @@ -378,7 +381,8 @@ static const struct device_type typec_altmode_dev_type = { }; static struct typec_altmode * -typec_register_altmode(struct device *parent, struct typec_altmode_desc *desc) +typec_register_altmode(struct device *parent, + const struct typec_altmode_desc *desc) { struct typec_altmode *alt; int ret; @@ -495,7 +499,7 @@ EXPORT_SYMBOL_GPL(typec_partner_set_identity); */ struct typec_altmode * typec_partner_register_altmode(struct typec_partner *partner, - struct typec_altmode_desc *desc) + const struct typec_altmode_desc *desc) { return typec_register_altmode(&partner->dev, desc); } @@ -590,7 +594,7 @@ static const struct device_type typec_plug_dev_type = { */ struct typec_altmode * typec_plug_register_altmode(struct typec_plug *plug, - struct typec_altmode_desc *desc) + const struct typec_altmode_desc *desc) { return typec_register_altmode(&plug->dev, desc); } @@ -789,6 +793,18 @@ static const char * const typec_data_roles[] = { [TYPEC_HOST] = "host", }; +static const char * const typec_port_types[] = { + [TYPEC_PORT_DFP] = "source", + [TYPEC_PORT_UFP] = "sink", + [TYPEC_PORT_DRP] = "dual", +}; + +static const char * const typec_port_types_drp[] = { + [TYPEC_PORT_DFP] = "dual [source] sink", + [TYPEC_PORT_UFP] = "dual source [sink]", + [TYPEC_PORT_DRP] = "[dual] source sink", +}; + static ssize_t preferred_role_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) @@ -846,11 +862,6 @@ static ssize_t data_role_store(struct device *dev, struct typec_port *port = to_typec_port(dev); int ret; - if (port->cap->type != TYPEC_PORT_DRP) { - dev_dbg(dev, "data role swap only supported with DRP ports\n"); - return -EOPNOTSUPP; - } - if (!port->cap->dr_set) { dev_dbg(dev, "data role swapping not supported\n"); return -EOPNOTSUPP; @@ -860,11 +871,22 @@ static ssize_t data_role_store(struct device *dev, if (ret < 0) return ret; + mutex_lock(&port->port_type_lock); + if (port->port_type != TYPEC_PORT_DRP) { + dev_dbg(dev, "port type fixed at \"%s\"", + typec_port_types[port->port_type]); + ret = -EOPNOTSUPP; + goto unlock_and_ret; + } + ret = port->cap->dr_set(port->cap, ret); if (ret) - return ret; + goto unlock_and_ret; - return size; + ret = size; +unlock_and_ret: + mutex_unlock(&port->port_type_lock); + return ret; } static ssize_t data_role_show(struct device *dev, @@ -885,7 +907,7 @@ static ssize_t power_role_store(struct device *dev, const char *buf, size_t size) { struct typec_port *port = to_typec_port(dev); - int ret = size; + int ret; if (!port->cap->pd_revision) { dev_dbg(dev, "USB Power Delivery not supported\n"); @@ -906,11 +928,22 @@ static ssize_t power_role_store(struct device *dev, if (ret < 0) return ret; + mutex_lock(&port->port_type_lock); + if (port->port_type != TYPEC_PORT_DRP) { + dev_dbg(dev, "port type fixed at \"%s\"", + typec_port_types[port->port_type]); + ret = -EOPNOTSUPP; + goto unlock_and_ret; + } + ret = port->cap->pr_set(port->cap, ret); if (ret) - return ret; + goto unlock_and_ret; - return size; + ret = size; +unlock_and_ret: + mutex_unlock(&port->port_type_lock); + return ret; } static ssize_t power_role_show(struct device *dev, @@ -926,6 +959,57 @@ static ssize_t power_role_show(struct device *dev, } static DEVICE_ATTR_RW(power_role); +static ssize_t +port_type_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + int ret; + enum typec_port_type type; + + if (!port->cap->port_type_set || port->cap->type != TYPEC_PORT_DRP) { + dev_dbg(dev, "changing port type not supported\n"); + return -EOPNOTSUPP; + } + + ret = sysfs_match_string(typec_port_types, buf); + if (ret < 0) + return ret; + + type = ret; + mutex_lock(&port->port_type_lock); + + if (port->port_type == type) { + ret = size; + goto unlock_and_ret; + } + + ret = port->cap->port_type_set(port->cap, type); + if (ret) + goto unlock_and_ret; + + port->port_type = type; + ret = size; + +unlock_and_ret: + mutex_unlock(&port->port_type_lock); + return ret; +} + +static ssize_t +port_type_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + if (port->cap->type == TYPEC_PORT_DRP) + return sprintf(buf, "%s\n", + typec_port_types_drp[port->port_type]); + + return sprintf(buf, "[%s]\n", typec_port_types[port->cap->type]); +} +static DEVICE_ATTR_RW(port_type); + static const char * const typec_pwr_opmodes[] = { [TYPEC_PWR_MODE_USB] = "default", [TYPEC_PWR_MODE_1_5A] = "1.5A", @@ -1035,6 +1119,7 @@ static struct attribute *typec_attrs[] = { &dev_attr_usb_power_delivery_revision.attr, &dev_attr_usb_typec_revision.attr, &dev_attr_vconn_source.attr, + &dev_attr_port_type.attr, NULL, }; ATTRIBUTE_GROUPS(typec); @@ -1123,6 +1208,11 @@ void typec_set_vconn_role(struct typec_port *port, enum typec_role role) } EXPORT_SYMBOL_GPL(typec_set_vconn_role); +static int partner_match(struct device *dev, void *data) +{ + return is_typec_partner(dev); +} + /** * typec_set_pwr_opmode - Report changed power operation mode * @port: The USB Type-C Port where the mode was changed @@ -1136,12 +1226,26 @@ EXPORT_SYMBOL_GPL(typec_set_vconn_role); void typec_set_pwr_opmode(struct typec_port *port, enum typec_pwr_opmode opmode) { + struct device *partner_dev; + if (port->pwr_opmode == opmode) return; port->pwr_opmode = opmode; sysfs_notify(&port->dev.kobj, NULL, "power_operation_mode"); kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); + + partner_dev = device_find_child(&port->dev, NULL, partner_match); + if (partner_dev) { + struct typec_partner *partner = to_typec_partner(partner_dev); + + if (opmode == TYPEC_PWR_MODE_PD && !partner->usb_pd) { + partner->usb_pd = 1; + sysfs_notify(&partner_dev->kobj, NULL, + "supports_usb_power_delivery"); + } + put_device(partner_dev); + } } EXPORT_SYMBOL_GPL(typec_set_pwr_opmode); @@ -1159,7 +1263,7 @@ EXPORT_SYMBOL_GPL(typec_set_pwr_opmode); */ struct typec_altmode * typec_port_register_altmode(struct typec_port *port, - struct typec_altmode_desc *desc) + const struct typec_altmode_desc *desc) { return typec_register_altmode(&port->dev, desc); } @@ -1211,6 +1315,8 @@ struct typec_port *typec_register_port(struct device *parent, port->id = id; port->cap = cap; + port->port_type = cap->type; + mutex_init(&port->port_type_lock); port->prefer_role = cap->prefer_role; port->dev.class = typec_class; diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig new file mode 100644 index 000000000000..d0c31cee4720 --- /dev/null +++ b/drivers/usb/typec/ucsi/Kconfig @@ -0,0 +1,39 @@ +config TYPEC_UCSI + tristate "USB Type-C Connector System Software Interface driver" + depends on !CPU_BIG_ENDIAN + select TYPEC + help + USB Type-C Connector System Software Interface (UCSI) is a + specification for an interface that allows the operating system to + control the USB Type-C ports. On UCSI system the USB Type-C ports + function autonomously by default, but in order to get the status of + the ports and support basic operations like role swapping, the driver + is required. UCSI is available on most of the new Intel based systems + that are equipped with Embedded Controller and USB Type-C ports. + + UCSI specification does not define the interface method, so depending + on the platform, ACPI, PCI, I2C, etc. may be used. Therefore this + driver only provides the core part, and separate drivers are needed + for every supported interface method. + + The UCSI specification can be downloaded from: + http://www.intel.com/content/www/us/en/io/universal-serial-bus/usb-type-c-ucsi-spec.html + + To compile the driver as a module, choose M here: the module will be + called typec_ucsi. + +if TYPEC_UCSI + +config UCSI_ACPI + tristate "UCSI ACPI Interface Driver" + depends on ACPI + help + This driver enables UCSI support on platforms that expose UCSI + interface as ACPI device. On new Intel Atom based platforms starting + from Broxton SoCs and Core platforms stating from Skylake, UCSI is an + ACPI enumerated device. + + To compile the driver as a module, choose M here: the module will be + called ucsi_acpi + +endif diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile new file mode 100644 index 000000000000..8372fc22f9b3 --- /dev/null +++ b/drivers/usb/typec/ucsi/Makefile @@ -0,0 +1,9 @@ +CFLAGS_trace.o := -I$(src) + +obj-$(CONFIG_TYPEC_UCSI) += typec_ucsi.o + +typec_ucsi-y := ucsi.o + +typec_ucsi-$(CONFIG_FTRACE) += trace.o + +obj-$(CONFIG_UCSI_ACPI) += ucsi_acpi.o diff --git a/drivers/usb/typec/ucsi/debug.h b/drivers/usb/typec/ucsi/debug.h new file mode 100644 index 000000000000..e4d8fc763e6c --- /dev/null +++ b/drivers/usb/typec/ucsi/debug.h @@ -0,0 +1,64 @@ +#ifndef __UCSI_DEBUG_H +#define __UCSI_DEBUG_H + +#include "ucsi.h" + +static const char * const ucsi_cmd_strs[] = { + [0] = "Unknown command", + [UCSI_PPM_RESET] = "PPM_RESET", + [UCSI_CANCEL] = "CANCEL", + [UCSI_CONNECTOR_RESET] = "CONNECTOR_RESET", + [UCSI_ACK_CC_CI] = "ACK_CC_CI", + [UCSI_SET_NOTIFICATION_ENABLE] = "SET_NOTIFICATION_ENABLE", + [UCSI_GET_CAPABILITY] = "GET_CAPABILITY", + [UCSI_GET_CONNECTOR_CAPABILITY] = "GET_CONNECTOR_CAPABILITY", + [UCSI_SET_UOM] = "SET_UOM", + [UCSI_SET_UOR] = "SET_UOR", + [UCSI_SET_PDM] = "SET_PDM", + [UCSI_SET_PDR] = "SET_PDR", + [UCSI_GET_ALTERNATE_MODES] = "GET_ALTERNATE_MODES", + [UCSI_GET_CAM_SUPPORTED] = "GET_CAM_SUPPORTED", + [UCSI_GET_CURRENT_CAM] = "GET_CURRENT_CAM", + [UCSI_SET_NEW_CAM] = "SET_NEW_CAM", + [UCSI_GET_PDOS] = "GET_PDOS", + [UCSI_GET_CABLE_PROPERTY] = "GET_CABLE_PROPERTY", + [UCSI_GET_CONNECTOR_STATUS] = "GET_CONNECTOR_STATUS", + [UCSI_GET_ERROR_STATUS] = "GET_ERROR_STATUS", +}; + +static inline const char *ucsi_cmd_str(u64 raw_cmd) +{ + u8 cmd = raw_cmd & GENMASK(7, 0); + + return ucsi_cmd_strs[(cmd >= ARRAY_SIZE(ucsi_cmd_strs)) ? 0 : cmd]; +} + +static const char * const ucsi_ack_strs[] = { + [0] = "", + [UCSI_ACK_EVENT] = "event", + [UCSI_ACK_CMD] = "command", +}; + +static inline const char *ucsi_ack_str(u8 ack) +{ + return ucsi_ack_strs[(ack >= ARRAY_SIZE(ucsi_ack_strs)) ? 0 : ack]; +} + +static inline const char *ucsi_cci_str(u32 cci) +{ + if (cci & GENMASK(7, 0)) { + if (cci & BIT(29)) + return "Event pending (ACK completed)"; + if (cci & BIT(31)) + return "Event pending (command completed)"; + return "Connector Change"; + } + if (cci & BIT(29)) + return "ACK completed"; + if (cci & BIT(31)) + return "Command completed"; + + return ""; +} + +#endif /* __UCSI_DEBUG_H */ diff --git a/drivers/usb/typec/ucsi/trace.c b/drivers/usb/typec/ucsi/trace.c new file mode 100644 index 000000000000..006f65c72a34 --- /dev/null +++ b/drivers/usb/typec/ucsi/trace.c @@ -0,0 +1,2 @@ +#define CREATE_TRACE_POINTS +#include "trace.h" diff --git a/drivers/usb/typec/ucsi/trace.h b/drivers/usb/typec/ucsi/trace.h new file mode 100644 index 000000000000..98b404404834 --- /dev/null +++ b/drivers/usb/typec/ucsi/trace.h @@ -0,0 +1,143 @@ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM ucsi + +#if !defined(__UCSI_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define __UCSI_TRACE_H + +#include <linux/tracepoint.h> +#include "ucsi.h" +#include "debug.h" + +DECLARE_EVENT_CLASS(ucsi_log_ack, + TP_PROTO(u8 ack), + TP_ARGS(ack), + TP_STRUCT__entry( + __field(u8, ack) + ), + TP_fast_assign( + __entry->ack = ack; + ), + TP_printk("ACK %s", ucsi_ack_str(__entry->ack)) +); + +DEFINE_EVENT(ucsi_log_ack, ucsi_ack, + TP_PROTO(u8 ack), + TP_ARGS(ack) +); + +DECLARE_EVENT_CLASS(ucsi_log_control, + TP_PROTO(struct ucsi_control *ctrl), + TP_ARGS(ctrl), + TP_STRUCT__entry( + __field(u64, ctrl) + ), + TP_fast_assign( + __entry->ctrl = ctrl->raw_cmd; + ), + TP_printk("control=%08llx (%s)", __entry->ctrl, + ucsi_cmd_str(__entry->ctrl)) +); + +DEFINE_EVENT(ucsi_log_control, ucsi_command, + TP_PROTO(struct ucsi_control *ctrl), + TP_ARGS(ctrl) +); + +DECLARE_EVENT_CLASS(ucsi_log_command, + TP_PROTO(struct ucsi_control *ctrl, int ret), + TP_ARGS(ctrl, ret), + TP_STRUCT__entry( + __field(u64, ctrl) + __field(int, ret) + ), + TP_fast_assign( + __entry->ctrl = ctrl->raw_cmd; + __entry->ret = ret; + ), + TP_printk("%s -> %s (err=%d)", ucsi_cmd_str(__entry->ctrl), + __entry->ret < 0 ? "FAIL" : "OK", + __entry->ret < 0 ? __entry->ret : 0) +); + +DEFINE_EVENT(ucsi_log_command, ucsi_run_command, + TP_PROTO(struct ucsi_control *ctrl, int ret), + TP_ARGS(ctrl, ret) +); + +DEFINE_EVENT(ucsi_log_command, ucsi_reset_ppm, + TP_PROTO(struct ucsi_control *ctrl, int ret), + TP_ARGS(ctrl, ret) +); + +DECLARE_EVENT_CLASS(ucsi_log_cci, + TP_PROTO(u32 cci), + TP_ARGS(cci), + TP_STRUCT__entry( + __field(u32, cci) + ), + TP_fast_assign( + __entry->cci = cci; + ), + TP_printk("CCI=%08x %s", __entry->cci, ucsi_cci_str(__entry->cci)) +); + +DEFINE_EVENT(ucsi_log_cci, ucsi_notify, + TP_PROTO(u32 cci), + TP_ARGS(cci) +); + +DECLARE_EVENT_CLASS(ucsi_log_connector_status, + TP_PROTO(int port, struct ucsi_connector_status *status), + TP_ARGS(port, status), + TP_STRUCT__entry( + __field(int, port) + __field(u16, change) + __field(u8, opmode) + __field(u8, connected) + __field(u8, pwr_dir) + __field(u8, partner_flags) + __field(u8, partner_type) + __field(u32, request_data_obj) + __field(u8, bc_status) + ), + TP_fast_assign( + __entry->port = port - 1; + __entry->change = status->change; + __entry->opmode = status->pwr_op_mode; + __entry->connected = status->connected; + __entry->pwr_dir = status->pwr_dir; + __entry->partner_flags = status->partner_flags; + __entry->partner_type = status->partner_type; + __entry->request_data_obj = status->request_data_obj; + __entry->bc_status = status->bc_status; + ), + TP_printk("port%d status: change=%04x, opmode=%x, connected=%d, " + "sourcing=%d, partner_flags=%x, partner_type=%x, " + "request_data_obj=%08x, BC status=%x", __entry->port, + __entry->change, __entry->opmode, __entry->connected, + __entry->pwr_dir, __entry->partner_flags, __entry->partner_type, + __entry->request_data_obj, __entry->bc_status) +); + +DEFINE_EVENT(ucsi_log_connector_status, ucsi_connector_change, + TP_PROTO(int port, struct ucsi_connector_status *status), + TP_ARGS(port, status) +); + +DEFINE_EVENT(ucsi_log_connector_status, ucsi_register_port, + TP_PROTO(int port, struct ucsi_connector_status *status), + TP_ARGS(port, status) +); + +#endif /* __UCSI_TRACE_H */ + +/* This part must be outside protection */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace + +#include <trace/define_trace.h> diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c new file mode 100644 index 000000000000..714c5bcedf2b --- /dev/null +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -0,0 +1,790 @@ +/* + * USB Type-C Connector System Software Interface driver + * + * Copyright (C) 2017, Intel Corporation + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/completion.h> +#include <linux/property.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/usb/typec.h> + +#include "ucsi.h" +#include "trace.h" + +#define to_ucsi_connector(_cap_) container_of(_cap_, struct ucsi_connector, \ + typec_cap) + +/* + * UCSI_TIMEOUT_MS - PPM communication timeout + * + * Ideally we could use MIN_TIME_TO_RESPOND_WITH_BUSY (which is defined in UCSI + * specification) here as reference, but unfortunately we can't. It is very + * difficult to estimate the time it takes for the system to process the command + * before it is actually passed to the PPM. + */ +#define UCSI_TIMEOUT_MS 1000 + +/* + * UCSI_SWAP_TIMEOUT_MS - Timeout for role swap requests + * + * 5 seconds is close to the time it takes for CapsCounter to reach 0, so even + * if the PPM does not generate Connector Change events before that with + * partners that do not support USB Power Delivery, this should still work. + */ +#define UCSI_SWAP_TIMEOUT_MS 5000 + +enum ucsi_status { + UCSI_IDLE = 0, + UCSI_BUSY, + UCSI_ERROR, +}; + +struct ucsi_connector { + int num; + + struct ucsi *ucsi; + struct work_struct work; + struct completion complete; + + struct typec_port *port; + struct typec_partner *partner; + + struct typec_capability typec_cap; + + struct ucsi_connector_status status; + struct ucsi_connector_capability cap; +}; + +struct ucsi { + struct device *dev; + struct ucsi_ppm *ppm; + + enum ucsi_status status; + struct completion complete; + struct ucsi_capability cap; + struct ucsi_connector *connector; + + struct work_struct work; + + /* PPM Communication lock */ + struct mutex ppm_lock; + + /* PPM communication flags */ + unsigned long flags; +#define EVENT_PENDING 0 +#define COMMAND_PENDING 1 +#define ACK_PENDING 2 +}; + +static inline int ucsi_sync(struct ucsi *ucsi) +{ + if (ucsi->ppm && ucsi->ppm->sync) + return ucsi->ppm->sync(ucsi->ppm); + return 0; +} + +static int ucsi_command(struct ucsi *ucsi, struct ucsi_control *ctrl) +{ + int ret; + + trace_ucsi_command(ctrl); + + set_bit(COMMAND_PENDING, &ucsi->flags); + + ret = ucsi->ppm->cmd(ucsi->ppm, ctrl); + if (ret) + goto err_clear_flag; + + if (!wait_for_completion_timeout(&ucsi->complete, + msecs_to_jiffies(UCSI_TIMEOUT_MS))) { + dev_warn(ucsi->dev, "PPM NOT RESPONDING\n"); + ret = -ETIMEDOUT; + } + +err_clear_flag: + clear_bit(COMMAND_PENDING, &ucsi->flags); + + return ret; +} + +static int ucsi_ack(struct ucsi *ucsi, u8 ack) +{ + struct ucsi_control ctrl; + int ret; + + trace_ucsi_ack(ack); + + set_bit(ACK_PENDING, &ucsi->flags); + + UCSI_CMD_ACK(ctrl, ack); + ret = ucsi->ppm->cmd(ucsi->ppm, &ctrl); + if (ret) + goto out_clear_bit; + + /* Waiting for ACK with ACK CMD, but not with EVENT for now */ + if (ack == UCSI_ACK_EVENT) + goto out_clear_bit; + + if (!wait_for_completion_timeout(&ucsi->complete, + msecs_to_jiffies(UCSI_TIMEOUT_MS))) + ret = -ETIMEDOUT; + +out_clear_bit: + clear_bit(ACK_PENDING, &ucsi->flags); + + if (ret) + dev_err(ucsi->dev, "%s: failed\n", __func__); + + return ret; +} + +static int ucsi_run_command(struct ucsi *ucsi, struct ucsi_control *ctrl, + void *data, size_t size) +{ + struct ucsi_control _ctrl; + u8 data_length; + u16 error; + int ret; + + ret = ucsi_command(ucsi, ctrl); + if (ret) + goto err; + + switch (ucsi->status) { + case UCSI_IDLE: + ret = ucsi_sync(ucsi); + if (ret) + dev_warn(ucsi->dev, "%s: sync failed\n", __func__); + + if (data) + memcpy(data, ucsi->ppm->data->message_in, size); + + data_length = ucsi->ppm->data->cci.data_length; + + ret = ucsi_ack(ucsi, UCSI_ACK_CMD); + if (!ret) + ret = data_length; + break; + case UCSI_BUSY: + /* The caller decides whether to cancel or not */ + ret = -EBUSY; + break; + case UCSI_ERROR: + ret = ucsi_ack(ucsi, UCSI_ACK_CMD); + if (ret) + break; + + _ctrl.raw_cmd = 0; + _ctrl.cmd.cmd = UCSI_GET_ERROR_STATUS; + ret = ucsi_command(ucsi, &_ctrl); + if (ret) { + dev_err(ucsi->dev, "reading error failed!\n"); + break; + } + + memcpy(&error, ucsi->ppm->data->message_in, sizeof(error)); + + /* Something has really gone wrong */ + if (WARN_ON(ucsi->status == UCSI_ERROR)) { + ret = -ENODEV; + break; + } + + ret = ucsi_ack(ucsi, UCSI_ACK_CMD); + if (ret) + break; + + switch (error) { + case UCSI_ERROR_INCOMPATIBLE_PARTNER: + ret = -EOPNOTSUPP; + break; + case UCSI_ERROR_CC_COMMUNICATION_ERR: + ret = -ECOMM; + break; + case UCSI_ERROR_CONTRACT_NEGOTIATION_FAIL: + ret = -EPROTO; + break; + case UCSI_ERROR_DEAD_BATTERY: + dev_warn(ucsi->dev, "Dead battery condition!\n"); + ret = -EPERM; + break; + /* The following mean a bug in this driver */ + case UCSI_ERROR_INVALID_CON_NUM: + case UCSI_ERROR_UNREGONIZED_CMD: + case UCSI_ERROR_INVALID_CMD_ARGUMENT: + dev_warn(ucsi->dev, + "%s: possible UCSI driver bug - error 0x%x\n", + __func__, error); + ret = -EINVAL; + break; + default: + dev_warn(ucsi->dev, + "%s: error without status\n", __func__); + ret = -EIO; + break; + } + break; + } + +err: + trace_ucsi_run_command(ctrl, ret); + + return ret; +} + +/* -------------------------------------------------------------------------- */ + +static void ucsi_pwr_opmode_change(struct ucsi_connector *con) +{ + switch (con->status.pwr_op_mode) { + case UCSI_CONSTAT_PWR_OPMODE_PD: + typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_PD); + break; + case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: + typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_1_5A); + break; + case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: + typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_3_0A); + break; + default: + typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_USB); + break; + } +} + +static int ucsi_register_partner(struct ucsi_connector *con) +{ + struct typec_partner_desc partner; + + if (con->partner) + return 0; + + memset(&partner, 0, sizeof(partner)); + + switch (con->status.partner_type) { + case UCSI_CONSTAT_PARTNER_TYPE_DEBUG: + partner.accessory = TYPEC_ACCESSORY_DEBUG; + break; + case UCSI_CONSTAT_PARTNER_TYPE_AUDIO: + partner.accessory = TYPEC_ACCESSORY_AUDIO; + break; + default: + break; + } + + partner.usb_pd = con->status.pwr_op_mode == UCSI_CONSTAT_PWR_OPMODE_PD; + + con->partner = typec_register_partner(con->port, &partner); + if (!con->partner) { + dev_err(con->ucsi->dev, "con%d: failed to register partner\n", + con->num); + return -ENODEV; + } + + return 0; +} + +static void ucsi_unregister_partner(struct ucsi_connector *con) +{ + typec_unregister_partner(con->partner); + con->partner = NULL; +} + +static void ucsi_connector_change(struct work_struct *work) +{ + struct ucsi_connector *con = container_of(work, struct ucsi_connector, + work); + struct ucsi *ucsi = con->ucsi; + struct ucsi_control ctrl; + int ret; + + mutex_lock(&ucsi->ppm_lock); + + UCSI_CMD_GET_CONNECTOR_STATUS(ctrl, con->num); + ret = ucsi_run_command(ucsi, &ctrl, &con->status, sizeof(con->status)); + if (ret < 0) { + dev_err(ucsi->dev, "%s: GET_CONNECTOR_STATUS failed (%d)\n", + __func__, ret); + goto out_unlock; + } + + if (con->status.change & UCSI_CONSTAT_POWER_OPMODE_CHANGE) + ucsi_pwr_opmode_change(con); + + if (con->status.change & UCSI_CONSTAT_POWER_DIR_CHANGE) { + typec_set_pwr_role(con->port, con->status.pwr_dir); + + /* Complete pending power role swap */ + if (!completion_done(&con->complete)) + complete(&con->complete); + } + + if (con->status.change & UCSI_CONSTAT_PARTNER_CHANGE) { + switch (con->status.partner_type) { + case UCSI_CONSTAT_PARTNER_TYPE_UFP: + typec_set_data_role(con->port, TYPEC_HOST); + break; + case UCSI_CONSTAT_PARTNER_TYPE_DFP: + typec_set_data_role(con->port, TYPEC_DEVICE); + break; + default: + break; + } + + /* Complete pending data role swap */ + if (!completion_done(&con->complete)) + complete(&con->complete); + } + + if (con->status.change & UCSI_CONSTAT_CONNECT_CHANGE) { + if (con->status.connected) + ucsi_register_partner(con); + else + ucsi_unregister_partner(con); + } + + ret = ucsi_ack(ucsi, UCSI_ACK_EVENT); + if (ret) + dev_err(ucsi->dev, "%s: ACK failed (%d)", __func__, ret); + + trace_ucsi_connector_change(con->num, &con->status); + +out_unlock: + clear_bit(EVENT_PENDING, &ucsi->flags); + mutex_unlock(&ucsi->ppm_lock); +} + +/** + * ucsi_notify - PPM notification handler + * @ucsi: Source UCSI Interface for the notifications + * + * Handle notifications from PPM of @ucsi. + */ +void ucsi_notify(struct ucsi *ucsi) +{ + struct ucsi_cci *cci; + + /* There is no requirement to sync here, but no harm either. */ + ucsi_sync(ucsi); + + cci = &ucsi->ppm->data->cci; + + if (cci->error) + ucsi->status = UCSI_ERROR; + else if (cci->busy) + ucsi->status = UCSI_BUSY; + else + ucsi->status = UCSI_IDLE; + + if (cci->cmd_complete && test_bit(COMMAND_PENDING, &ucsi->flags)) { + complete(&ucsi->complete); + } else if (cci->ack_complete && test_bit(ACK_PENDING, &ucsi->flags)) { + complete(&ucsi->complete); + } else if (cci->connector_change) { + struct ucsi_connector *con; + + con = &ucsi->connector[cci->connector_change - 1]; + + if (!test_and_set_bit(EVENT_PENDING, &ucsi->flags)) + schedule_work(&con->work); + } + + trace_ucsi_notify(ucsi->ppm->data->raw_cci); +} +EXPORT_SYMBOL_GPL(ucsi_notify); + +/* -------------------------------------------------------------------------- */ + +static int ucsi_reset_connector(struct ucsi_connector *con, bool hard) +{ + struct ucsi_control ctrl; + + UCSI_CMD_CONNECTOR_RESET(ctrl, con, hard); + + return ucsi_run_command(con->ucsi, &ctrl, NULL, 0); +} + +static int ucsi_reset_ppm(struct ucsi *ucsi) +{ + struct ucsi_control ctrl; + unsigned long tmo; + int ret; + + ctrl.raw_cmd = 0; + ctrl.cmd.cmd = UCSI_PPM_RESET; + trace_ucsi_command(&ctrl); + ret = ucsi->ppm->cmd(ucsi->ppm, &ctrl); + if (ret) + goto err; + + tmo = jiffies + msecs_to_jiffies(UCSI_TIMEOUT_MS); + + do { + /* Here sync is critical. */ + ret = ucsi_sync(ucsi); + if (ret) + goto err; + + if (ucsi->ppm->data->cci.reset_complete) + break; + + /* If the PPM is still doing something else, reset it again. */ + if (ucsi->ppm->data->raw_cci) { + dev_warn_ratelimited(ucsi->dev, + "Failed to reset PPM! Trying again..\n"); + + trace_ucsi_command(&ctrl); + ret = ucsi->ppm->cmd(ucsi->ppm, &ctrl); + if (ret) + goto err; + } + + /* Letting the PPM settle down. */ + msleep(20); + + ret = -ETIMEDOUT; + } while (time_is_after_jiffies(tmo)); + +err: + trace_ucsi_reset_ppm(&ctrl, ret); + + return ret; +} + +static int ucsi_role_cmd(struct ucsi_connector *con, struct ucsi_control *ctrl) +{ + int ret; + + ret = ucsi_run_command(con->ucsi, ctrl, NULL, 0); + if (ret == -ETIMEDOUT) { + struct ucsi_control c; + + /* PPM most likely stopped responding. Resetting everything. */ + ucsi_reset_ppm(con->ucsi); + + UCSI_CMD_SET_NTFY_ENABLE(c, UCSI_ENABLE_NTFY_ALL); + ucsi_run_command(con->ucsi, &c, NULL, 0); + + ucsi_reset_connector(con, true); + } + + return ret; +} + +static int +ucsi_dr_swap(const struct typec_capability *cap, enum typec_data_role role) +{ + struct ucsi_connector *con = to_ucsi_connector(cap); + struct ucsi_control ctrl; + int ret = 0; + + if (!con->partner) + return -ENOTCONN; + + mutex_lock(&con->ucsi->ppm_lock); + + if ((con->status.partner_type == UCSI_CONSTAT_PARTNER_TYPE_DFP && + role == TYPEC_DEVICE) || + (con->status.partner_type == UCSI_CONSTAT_PARTNER_TYPE_UFP && + role == TYPEC_HOST)) + goto out_unlock; + + UCSI_CMD_SET_UOR(ctrl, con, role); + ret = ucsi_role_cmd(con, &ctrl); + if (ret < 0) + goto out_unlock; + + mutex_unlock(&con->ucsi->ppm_lock); + + if (!wait_for_completion_timeout(&con->complete, + msecs_to_jiffies(UCSI_SWAP_TIMEOUT_MS))) + return -ETIMEDOUT; + + return 0; + +out_unlock: + mutex_unlock(&con->ucsi->ppm_lock); + + return ret; +} + +static int +ucsi_pr_swap(const struct typec_capability *cap, enum typec_role role) +{ + struct ucsi_connector *con = to_ucsi_connector(cap); + struct ucsi_control ctrl; + int ret = 0; + + if (!con->partner) + return -ENOTCONN; + + mutex_lock(&con->ucsi->ppm_lock); + + if (con->status.pwr_dir == role) + goto out_unlock; + + UCSI_CMD_SET_PDR(ctrl, con, role); + ret = ucsi_role_cmd(con, &ctrl); + if (ret < 0) + goto out_unlock; + + mutex_unlock(&con->ucsi->ppm_lock); + + if (!wait_for_completion_timeout(&con->complete, + msecs_to_jiffies(UCSI_SWAP_TIMEOUT_MS))) + return -ETIMEDOUT; + + mutex_lock(&con->ucsi->ppm_lock); + + /* Something has gone wrong while swapping the role */ + if (con->status.pwr_op_mode != UCSI_CONSTAT_PWR_OPMODE_PD) { + ucsi_reset_connector(con, true); + ret = -EPROTO; + } + +out_unlock: + mutex_unlock(&con->ucsi->ppm_lock); + + return ret; +} + +static struct fwnode_handle *ucsi_find_fwnode(struct ucsi_connector *con) +{ + struct fwnode_handle *fwnode; + int i = 1; + + device_for_each_child_node(con->ucsi->dev, fwnode) + if (i++ == con->num) + return fwnode; + return NULL; +} + +static int ucsi_register_port(struct ucsi *ucsi, int index) +{ + struct ucsi_connector *con = &ucsi->connector[index]; + struct typec_capability *cap = &con->typec_cap; + enum typec_accessory *accessory = cap->accessory; + struct ucsi_control ctrl; + int ret; + + INIT_WORK(&con->work, ucsi_connector_change); + init_completion(&con->complete); + con->num = index + 1; + con->ucsi = ucsi; + + /* Get connector capability */ + UCSI_CMD_GET_CONNECTOR_CAPABILITY(ctrl, con->num); + ret = ucsi_run_command(ucsi, &ctrl, &con->cap, sizeof(con->cap)); + if (ret < 0) + return ret; + + if (con->cap.op_mode & UCSI_CONCAP_OPMODE_DRP) + cap->type = TYPEC_PORT_DRP; + else if (con->cap.op_mode & UCSI_CONCAP_OPMODE_DFP) + cap->type = TYPEC_PORT_DFP; + else if (con->cap.op_mode & UCSI_CONCAP_OPMODE_UFP) + cap->type = TYPEC_PORT_UFP; + + cap->revision = ucsi->cap.typec_version; + cap->pd_revision = ucsi->cap.pd_version; + cap->prefer_role = TYPEC_NO_PREFERRED_ROLE; + + if (con->cap.op_mode & UCSI_CONCAP_OPMODE_AUDIO_ACCESSORY) + *accessory++ = TYPEC_ACCESSORY_AUDIO; + if (con->cap.op_mode & UCSI_CONCAP_OPMODE_DEBUG_ACCESSORY) + *accessory = TYPEC_ACCESSORY_DEBUG; + + cap->fwnode = ucsi_find_fwnode(con); + cap->dr_set = ucsi_dr_swap; + cap->pr_set = ucsi_pr_swap; + + /* Register the connector */ + con->port = typec_register_port(ucsi->dev, cap); + if (!con->port) + return -ENODEV; + + /* Get the status */ + UCSI_CMD_GET_CONNECTOR_STATUS(ctrl, con->num); + ret = ucsi_run_command(ucsi, &ctrl, &con->status, sizeof(con->status)); + if (ret < 0) { + dev_err(ucsi->dev, "con%d: failed to get status\n", con->num); + return 0; + } + + ucsi_pwr_opmode_change(con); + typec_set_pwr_role(con->port, con->status.pwr_dir); + + switch (con->status.partner_type) { + case UCSI_CONSTAT_PARTNER_TYPE_UFP: + typec_set_data_role(con->port, TYPEC_HOST); + break; + case UCSI_CONSTAT_PARTNER_TYPE_DFP: + typec_set_data_role(con->port, TYPEC_DEVICE); + break; + default: + break; + } + + /* Check if there is already something connected */ + if (con->status.connected) + ucsi_register_partner(con); + + trace_ucsi_register_port(con->num, &con->status); + + return 0; +} + +static void ucsi_init(struct work_struct *work) +{ + struct ucsi *ucsi = container_of(work, struct ucsi, work); + struct ucsi_connector *con; + struct ucsi_control ctrl; + int ret; + int i; + + mutex_lock(&ucsi->ppm_lock); + + /* Reset the PPM */ + ret = ucsi_reset_ppm(ucsi); + if (ret) { + dev_err(ucsi->dev, "failed to reset PPM!\n"); + goto err; + } + + /* Enable basic notifications */ + UCSI_CMD_SET_NTFY_ENABLE(ctrl, UCSI_ENABLE_NTFY_CMD_COMPLETE | + UCSI_ENABLE_NTFY_ERROR); + ret = ucsi_run_command(ucsi, &ctrl, NULL, 0); + if (ret < 0) + goto err_reset; + + /* Get PPM capabilities */ + UCSI_CMD_GET_CAPABILITY(ctrl); + ret = ucsi_run_command(ucsi, &ctrl, &ucsi->cap, sizeof(ucsi->cap)); + if (ret < 0) + goto err_reset; + + if (!ucsi->cap.num_connectors) { + ret = -ENODEV; + goto err_reset; + } + + /* Allocate the connectors. Released in ucsi_unregister_ppm() */ + ucsi->connector = kcalloc(ucsi->cap.num_connectors + 1, + sizeof(*ucsi->connector), GFP_KERNEL); + if (!ucsi->connector) { + ret = -ENOMEM; + goto err_reset; + } + + /* Register all connectors */ + for (i = 0; i < ucsi->cap.num_connectors; i++) { + ret = ucsi_register_port(ucsi, i); + if (ret) + goto err_unregister; + } + + /* Enable all notifications */ + UCSI_CMD_SET_NTFY_ENABLE(ctrl, UCSI_ENABLE_NTFY_ALL); + ret = ucsi_run_command(ucsi, &ctrl, NULL, 0); + if (ret < 0) + goto err_unregister; + + mutex_unlock(&ucsi->ppm_lock); + + return; + +err_unregister: + for (con = ucsi->connector; con->port; con++) { + ucsi_unregister_partner(con); + typec_unregister_port(con->port); + con->port = NULL; + } + +err_reset: + ucsi_reset_ppm(ucsi); +err: + mutex_unlock(&ucsi->ppm_lock); + dev_err(ucsi->dev, "PPM init failed (%d)\n", ret); +} + +/** + * ucsi_register_ppm - Register UCSI PPM Interface + * @dev: Device interface to the PPM + * @ppm: The PPM interface + * + * Allocates UCSI instance, associates it with @ppm and returns it to the + * caller, and schedules initialization of the interface. + */ +struct ucsi *ucsi_register_ppm(struct device *dev, struct ucsi_ppm *ppm) +{ + struct ucsi *ucsi; + + ucsi = kzalloc(sizeof(*ucsi), GFP_KERNEL); + if (!ucsi) + return ERR_PTR(-ENOMEM); + + INIT_WORK(&ucsi->work, ucsi_init); + init_completion(&ucsi->complete); + mutex_init(&ucsi->ppm_lock); + + ucsi->dev = dev; + ucsi->ppm = ppm; + + /* + * Communication with the PPM takes a lot of time. It is not reasonable + * to initialize the driver here. Using a work for now. + */ + queue_work(system_long_wq, &ucsi->work); + + return ucsi; +} +EXPORT_SYMBOL_GPL(ucsi_register_ppm); + +/** + * ucsi_unregister_ppm - Unregister UCSI PPM Interface + * @ucsi: struct ucsi associated with the PPM + * + * Unregister UCSI PPM that was created with ucsi_register(). + */ +void ucsi_unregister_ppm(struct ucsi *ucsi) +{ + struct ucsi_control ctrl; + int i; + + /* Make sure that we are not in the middle of driver initialization */ + cancel_work_sync(&ucsi->work); + + mutex_lock(&ucsi->ppm_lock); + + /* Disable everything except command complete notification */ + UCSI_CMD_SET_NTFY_ENABLE(ctrl, UCSI_ENABLE_NTFY_CMD_COMPLETE) + ucsi_run_command(ucsi, &ctrl, NULL, 0); + + mutex_unlock(&ucsi->ppm_lock); + + for (i = 0; i < ucsi->cap.num_connectors; i++) { + cancel_work_sync(&ucsi->connector[i].work); + ucsi_unregister_partner(&ucsi->connector[i]); + typec_unregister_port(ucsi->connector[i].port); + } + + ucsi_reset_ppm(ucsi); + + kfree(ucsi->connector); + kfree(ucsi); +} +EXPORT_SYMBOL_GPL(ucsi_unregister_ppm); + +MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("USB Type-C Connector System Software Interface driver"); diff --git a/drivers/usb/misc/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h index 6dd11d1fe225..6b0d2f0918c6 100644 --- a/drivers/usb/misc/ucsi.h +++ b/drivers/usb/typec/ucsi/ucsi.h @@ -1,21 +1,25 @@ +#ifndef __DRIVER_USB_TYPEC_UCSI_H +#define __DRIVER_USB_TYPEC_UCSI_H + +#include <linux/bitops.h> #include <linux/types.h> /* -------------------------------------------------------------------------- */ /* Command Status and Connector Change Indication (CCI) data structure */ struct ucsi_cci { - unsigned int RESERVED1:1; - unsigned int connector_change:7; + u8:1; /* reserved */ + u8 connector_change:7; u8 data_length; - unsigned int RESERVED9:9; - unsigned int not_supported:1; - unsigned int cancel_complete:1; - unsigned int reset_complete:1; - unsigned int busy:1; - unsigned int ack_complete:1; - unsigned int error:1; - unsigned int cmd_complete:1; + u16:9; /* reserved */ + u16 not_supported:1; + u16 cancel_complete:1; + u16 reset_complete:1; + u16 busy:1; + u16 ack_complete:1; + u16 error:1; + u16 cmd_complete:1; } __packed; /* Default fields in CONTROL data structure */ @@ -25,16 +29,33 @@ struct ucsi_command { u64 data:48; } __packed; +/* ACK Command structure */ +struct ucsi_ack_cmd { + u8 cmd; + u8 length; + u8 cci_ack:1; + u8 cmd_ack:1; + u8:6; /* reserved */ +} __packed; + +/* Connector Reset Command structure */ +struct ucsi_con_rst { + u8 cmd; + u8 length; + u8 con_num:7; + u8 hard_reset:1; +} __packed; + /* Set USB Operation Mode Command structure */ struct ucsi_uor_cmd { u8 cmd; u8 length; - u64 con_num:7; - u64 role:3; + u16 con_num:7; + u16 role:3; #define UCSI_UOR_ROLE_DFP BIT(0) #define UCSI_UOR_ROLE_UFP BIT(1) #define UCSI_UOR_ROLE_DRP BIT(2) - u64 data:38; + u16:6; /* reserved */ } __packed; struct ucsi_control { @@ -42,20 +63,82 @@ struct ucsi_control { u64 raw_cmd; struct ucsi_command cmd; struct ucsi_uor_cmd uor; + struct ucsi_ack_cmd ack; + struct ucsi_con_rst con_rst; }; }; -struct ucsi_data { - u16 version; - u16 RESERVED; - union { - u32 raw_cci; - struct ucsi_cci cci; - }; - struct ucsi_control ctrl; - u32 message_in[4]; - u32 message_out[4]; -} __packed; +#define __UCSI_CMD(_ctrl_, _cmd_) \ +{ \ + (_ctrl_).raw_cmd = 0; \ + (_ctrl_).cmd.cmd = _cmd_; \ +} + +/* Helper for preparing ucsi_control for CONNECTOR_RESET command. */ +#define UCSI_CMD_CONNECTOR_RESET(_ctrl_, _con_, _hard_) \ +{ \ + __UCSI_CMD(_ctrl_, UCSI_CONNECTOR_RESET) \ + (_ctrl_).con_rst.con_num = (_con_)->num; \ + (_ctrl_).con_rst.hard_reset = _hard_; \ +} + +/* Helper for preparing ucsi_control for ACK_CC_CI command. */ +#define UCSI_CMD_ACK(_ctrl_, _ack_) \ +{ \ + __UCSI_CMD(_ctrl_, UCSI_ACK_CC_CI) \ + (_ctrl_).ack.cci_ack = ((_ack_) == UCSI_ACK_EVENT); \ + (_ctrl_).ack.cmd_ack = ((_ack_) == UCSI_ACK_CMD); \ +} + +/* Helper for preparing ucsi_control for SET_NOTIFY_ENABLE command. */ +#define UCSI_CMD_SET_NTFY_ENABLE(_ctrl_, _ntfys_) \ +{ \ + __UCSI_CMD(_ctrl_, UCSI_SET_NOTIFICATION_ENABLE) \ + (_ctrl_).cmd.data = _ntfys_; \ +} + +/* Helper for preparing ucsi_control for GET_CAPABILITY command. */ +#define UCSI_CMD_GET_CAPABILITY(_ctrl_) \ +{ \ + __UCSI_CMD(_ctrl_, UCSI_GET_CAPABILITY) \ +} + +/* Helper for preparing ucsi_control for GET_CONNECTOR_CAPABILITY command. */ +#define UCSI_CMD_GET_CONNECTOR_CAPABILITY(_ctrl_, _con_) \ +{ \ + __UCSI_CMD(_ctrl_, UCSI_GET_CONNECTOR_CAPABILITY) \ + (_ctrl_).cmd.data = _con_; \ +} + +/* Helper for preparing ucsi_control for GET_CONNECTOR_STATUS command. */ +#define UCSI_CMD_GET_CONNECTOR_STATUS(_ctrl_, _con_) \ +{ \ + __UCSI_CMD(_ctrl_, UCSI_GET_CONNECTOR_STATUS) \ + (_ctrl_).cmd.data = _con_; \ +} + +#define __UCSI_ROLE(_ctrl_, _cmd_, _con_num_) \ +{ \ + __UCSI_CMD(_ctrl_, _cmd_) \ + (_ctrl_).uor.con_num = _con_num_; \ + (_ctrl_).uor.role = UCSI_UOR_ROLE_DRP; \ +} + +/* Helper for preparing ucsi_control for SET_UOR command. */ +#define UCSI_CMD_SET_UOR(_ctrl_, _con_, _role_) \ +{ \ + __UCSI_ROLE(_ctrl_, UCSI_SET_UOR, (_con_)->num) \ + (_ctrl_).uor.role |= (_role_) == TYPEC_HOST ? UCSI_UOR_ROLE_DFP : \ + UCSI_UOR_ROLE_UFP; \ +} + +/* Helper for preparing ucsi_control for SET_PDR command. */ +#define UCSI_CMD_SET_PDR(_ctrl_, _con_, _role_) \ +{ \ + __UCSI_ROLE(_ctrl_, UCSI_SET_PDR, (_con_)->num) \ + (_ctrl_).uor.role |= (_role_) == TYPEC_SOURCE ? UCSI_UOR_ROLE_DFP : \ + UCSI_UOR_ROLE_UFP; \ +} /* Commands */ #define UCSI_PPM_RESET 0x01 @@ -67,12 +150,12 @@ struct ucsi_data { #define UCSI_GET_CONNECTOR_CAPABILITY 0x07 #define UCSI_SET_UOM 0x08 #define UCSI_SET_UOR 0x09 -#define UCSI_SET_PDM 0x0A -#define UCSI_SET_PDR 0x0B -#define UCSI_GET_ALTERNATE_MODES 0x0C -#define UCSI_GET_CAM_SUPPORTED 0x0D -#define UCSI_GET_CURRENT_CAM 0x0E -#define UCSI_SET_NEW_CAM 0x0F +#define UCSI_SET_PDM 0x0a +#define UCSI_SET_PDR 0x0b +#define UCSI_GET_ALTERNATE_MODES 0x0c +#define UCSI_GET_CAM_SUPPORTED 0x0d +#define UCSI_GET_CURRENT_CAM 0x0e +#define UCSI_SET_NEW_CAM 0x0f #define UCSI_GET_PDOS 0x10 #define UCSI_GET_CABLE_PROPERTY 0x11 #define UCSI_GET_CONNECTOR_STATUS 0x12 @@ -116,7 +199,7 @@ struct ucsi_capability { #define UCSI_CAP_ATTR_POWER_AC_SUPPLY BIT(8) #define UCSI_CAP_ATTR_POWER_OTHER BIT(10) #define UCSI_CAP_ATTR_POWER_VBUS BIT(14) - u8 num_connectors; + u32 num_connectors:8; u32 features:24; #define UCSI_CAP_SET_UOM BIT(0) #define UCSI_CAP_SET_PDM BIT(1) @@ -127,7 +210,7 @@ struct ucsi_capability { #define UCSI_CAP_EXT_SUPPLY_NOTIFICATIONS BIT(6) #define UCSI_CAP_PD_RESET BIT(7) u8 num_alt_modes; - u8 RESERVED; + u8 reserved; u16 bc_version; u16 pd_version; u16 typec_version; @@ -146,6 +229,12 @@ struct ucsi_connector_capability { #define UCSI_CONCAP_OPMODE_ALT_MODE BIT(7) u8 provider:1; u8 consumer:1; + u8:6; /* reserved */ +} __packed; + +struct ucsi_altmode { + u16 svid; + u32 mid; } __packed; /* Data structure filled by PPM in response to GET_CABLE_PROPERTY command. */ @@ -161,9 +250,9 @@ struct ucsi_cable_property { #define UCSI_CABLE_PROPERTY_PLUG_TYPE_C 2 #define UCSI_CABLE_PROPERTY_PLUG_OTHER 3 u8 mode_support:1; - u8 RESERVED_2:2; + u8:2; /* reserved */ u8 latency:4; - u8 RESERVED_4:4; + u8:4; /* reserved */ } __packed; /* Data structure filled by PPM in response to GET_CONNECTOR_STATUS command. */ @@ -185,7 +274,7 @@ struct ucsi_connector_status { #define UCSI_CONSTAT_PWR_OPMODE_DEFAULT 1 #define UCSI_CONSTAT_PWR_OPMODE_BC 2 #define UCSI_CONSTAT_PWR_OPMODE_PD 3 -#define UCSI_CONSTAT_PWR_OPMODE_TYPEC1_3 4 +#define UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5 4 #define UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0 5 u16 connected:1; u16 pwr_dir:1; @@ -195,7 +284,7 @@ struct ucsi_connector_status { u16 partner_type:3; #define UCSI_CONSTAT_PARTNER_TYPE_DFP 1 #define UCSI_CONSTAT_PARTNER_TYPE_UFP 2 -#define UCSI_CONSTAT_PARTNER_TYPE_CABLE_NO_UFP 3 /* Powered Cable */ +#define UCSI_CONSTAT_PARTNER_TYPE_CABLE 3 /* Powered Cable */ #define UCSI_CONSTAT_PARTNER_TYPE_CABLE_AND_UFP 4 /* Powered Cable */ #define UCSI_CONSTAT_PARTNER_TYPE_DEBUG 5 #define UCSI_CONSTAT_PARTNER_TYPE_AUDIO 6 @@ -208,8 +297,39 @@ struct ucsi_connector_status { u8 provider_cap_limit_reason:4; #define UCSI_CONSTAT_CAP_PWR_LOWERED 0 #define UCSI_CONSTAT_CAP_PWR_BUDGET_LIMIT 1 - u8 RESERVED:2; + u8:2; /* reserved */ } __packed; /* -------------------------------------------------------------------------- */ +struct ucsi; + +struct ucsi_data { + u16 version; + u16 reserved; + union { + u32 raw_cci; + struct ucsi_cci cci; + }; + struct ucsi_control ctrl; + u32 message_in[4]; + u32 message_out[4]; +} __packed; + +/* + * struct ucsi_ppm - Interface to UCSI Platform Policy Manager + * @data: memory location to the UCSI data structures + * @cmd: UCSI command execution routine + * @sync: Refresh UCSI mailbox (the data structures) + */ +struct ucsi_ppm { + struct ucsi_data *data; + int (*cmd)(struct ucsi_ppm *, struct ucsi_control *); + int (*sync)(struct ucsi_ppm *); +}; + +struct ucsi *ucsi_register_ppm(struct device *dev, struct ucsi_ppm *ppm); +void ucsi_unregister_ppm(struct ucsi *ucsi); +void ucsi_notify(struct ucsi *ucsi); + +#endif /* __DRIVER_USB_TYPEC_UCSI_H */ diff --git a/drivers/usb/typec/ucsi/ucsi_acpi.c b/drivers/usb/typec/ucsi/ucsi_acpi.c new file mode 100644 index 000000000000..cabd47612b0a --- /dev/null +++ b/drivers/usb/typec/ucsi/ucsi_acpi.c @@ -0,0 +1,158 @@ +/* + * UCSI ACPI driver + * + * Copyright (C) 2017, Intel Corporation + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/acpi.h> + +#include "ucsi.h" + +#define UCSI_DSM_UUID "6f8398c2-7ca4-11e4-ad36-631042b5008f" +#define UCSI_DSM_FUNC_WRITE 1 +#define UCSI_DSM_FUNC_READ 2 + +struct ucsi_acpi { + struct device *dev; + struct ucsi *ucsi; + struct ucsi_ppm ppm; + guid_t guid; +}; + +static int ucsi_acpi_dsm(struct ucsi_acpi *ua, int func) +{ + union acpi_object *obj; + + obj = acpi_evaluate_dsm(ACPI_HANDLE(ua->dev), &ua->guid, 1, func, + NULL); + if (!obj) { + dev_err(ua->dev, "%s: failed to evaluate _DSM %d\n", + __func__, func); + return -EIO; + } + + ACPI_FREE(obj); + return 0; +} + +static int ucsi_acpi_cmd(struct ucsi_ppm *ppm, struct ucsi_control *ctrl) +{ + struct ucsi_acpi *ua = container_of(ppm, struct ucsi_acpi, ppm); + + ppm->data->ctrl.raw_cmd = ctrl->raw_cmd; + + return ucsi_acpi_dsm(ua, UCSI_DSM_FUNC_WRITE); +} + +static int ucsi_acpi_sync(struct ucsi_ppm *ppm) +{ + struct ucsi_acpi *ua = container_of(ppm, struct ucsi_acpi, ppm); + + return ucsi_acpi_dsm(ua, UCSI_DSM_FUNC_READ); +} + +static void ucsi_acpi_notify(acpi_handle handle, u32 event, void *data) +{ + struct ucsi_acpi *ua = data; + + ucsi_notify(ua->ucsi); +} + +static int ucsi_acpi_probe(struct platform_device *pdev) +{ + struct ucsi_acpi *ua; + struct resource *res; + acpi_status status; + int ret; + + ua = devm_kzalloc(&pdev->dev, sizeof(*ua), GFP_KERNEL); + if (!ua) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "missing memory resource\n"); + return -ENODEV; + } + + /* + * NOTE: The memory region for the data structures is used also in an + * operation region, which means ACPI has already reserved it. Therefore + * it can not be requested here, and we can not use + * devm_ioremap_resource(). + */ + ua->ppm.data = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!ua->ppm.data) + return -ENOMEM; + + if (!ua->ppm.data->version) + return -ENODEV; + + ret = guid_parse(UCSI_DSM_UUID, &ua->guid); + if (ret) + return ret; + + ua->ppm.cmd = ucsi_acpi_cmd; + ua->ppm.sync = ucsi_acpi_sync; + ua->dev = &pdev->dev; + + status = acpi_install_notify_handler(ACPI_HANDLE(&pdev->dev), + ACPI_DEVICE_NOTIFY, + ucsi_acpi_notify, ua); + if (ACPI_FAILURE(status)) { + dev_err(&pdev->dev, "failed to install notify handler\n"); + return -ENODEV; + } + + ua->ucsi = ucsi_register_ppm(&pdev->dev, &ua->ppm); + if (IS_ERR(ua->ucsi)) { + acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), + ACPI_DEVICE_NOTIFY, + ucsi_acpi_notify); + return PTR_ERR(ua->ucsi); + } + + platform_set_drvdata(pdev, ua); + + return 0; +} + +static int ucsi_acpi_remove(struct platform_device *pdev) +{ + struct ucsi_acpi *ua = platform_get_drvdata(pdev); + + ucsi_unregister_ppm(ua->ucsi); + + acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), ACPI_DEVICE_NOTIFY, + ucsi_acpi_notify); + + return 0; +} + +static const struct acpi_device_id ucsi_acpi_match[] = { + { "PNP0CA0", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, ucsi_acpi_match); + +static struct platform_driver ucsi_acpi_platform_driver = { + .driver = { + .name = "ucsi_acpi", + .acpi_match_table = ACPI_PTR(ucsi_acpi_match), + }, + .probe = ucsi_acpi_probe, + .remove = ucsi_acpi_remove, +}; + +module_platform_driver(ucsi_acpi_platform_driver); + +MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("UCSI ACPI driver"); diff --git a/drivers/usb/usbip/stub_main.c b/drivers/usb/usbip/stub_main.c index 44ab43fc4fcc..af10f7b131a4 100644 --- a/drivers/usb/usbip/stub_main.c +++ b/drivers/usb/usbip/stub_main.c @@ -262,7 +262,11 @@ void stub_device_cleanup_urbs(struct stub_device *sdev) kmem_cache_free(stub_priv_cache, priv); kfree(urb->transfer_buffer); + urb->transfer_buffer = NULL; + kfree(urb->setup_packet); + urb->setup_packet = NULL; + usb_free_urb(urb); } } diff --git a/drivers/usb/usbip/stub_tx.c b/drivers/usb/usbip/stub_tx.c index 6b1e8c3f0e4b..be50cef645d8 100644 --- a/drivers/usb/usbip/stub_tx.c +++ b/drivers/usb/usbip/stub_tx.c @@ -28,7 +28,11 @@ static void stub_free_priv_and_urb(struct stub_priv *priv) struct urb *urb = priv->urb; kfree(urb->setup_packet); + urb->setup_packet = NULL; + kfree(urb->transfer_buffer); + urb->transfer_buffer = NULL; + list_del(&priv->list); kmem_cache_free(stub_priv_cache, priv); usb_free_urb(urb); diff --git a/drivers/usb/usbip/vhci.h b/drivers/usb/usbip/vhci.h index 88b71c4e068f..5cfb59e98e44 100644 --- a/drivers/usb/usbip/vhci.h +++ b/drivers/usb/usbip/vhci.h @@ -72,6 +72,11 @@ struct vhci_unlink { unsigned long unlink_seqnum; }; +enum hub_speed { + HUB_SPEED_HIGH = 0, + HUB_SPEED_SUPER, +}; + /* Number of supported ports. Value has an upperbound of USB_MAXCHILDREN */ #ifdef CONFIG_USBIP_VHCI_HC_PORTS #define VHCI_HC_PORTS CONFIG_USBIP_VHCI_HC_PORTS @@ -79,6 +84,9 @@ struct vhci_unlink { #define VHCI_HC_PORTS 8 #endif +/* Each VHCI has 2 hubs (USB2 and USB3), each has VHCI_HC_PORTS ports */ +#define VHCI_PORTS (VHCI_HC_PORTS*2) + #ifdef CONFIG_USBIP_VHCI_NR_HCS #define VHCI_NR_HCS CONFIG_USBIP_VHCI_NR_HCS #else @@ -87,10 +95,19 @@ struct vhci_unlink { #define MAX_STATUS_NAME 16 -/* for usb_bus.hcpriv */ -struct vhci_hcd { +struct vhci { spinlock_t lock; + struct platform_device *pdev; + + struct vhci_hcd *vhci_hcd_hs; + struct vhci_hcd *vhci_hcd_ss; +}; + +/* for usb_hcd.hcd_priv[0] */ +struct vhci_hcd { + struct vhci *vhci; + u32 port_status[VHCI_HC_PORTS]; unsigned resuming:1; @@ -107,7 +124,7 @@ struct vhci_hcd { }; extern int vhci_num_controllers; -extern struct platform_device **vhci_pdevs; +extern struct vhci *vhcis; extern struct attribute_group vhci_attr_group; /* vhci_hcd.c */ @@ -131,10 +148,10 @@ static inline __u32 port_to_rhport(__u32 port) static inline int port_to_pdev_nr(__u32 port) { - return port / VHCI_HC_PORTS; + return port / VHCI_PORTS; } -static inline struct vhci_hcd *hcd_to_vhci(struct usb_hcd *hcd) +static inline struct vhci_hcd *hcd_to_vhci_hcd(struct usb_hcd *hcd) { return (struct vhci_hcd *) (hcd->hcd_priv); } @@ -149,15 +166,14 @@ static inline const char *hcd_name(struct usb_hcd *hcd) return (hcd)->self.bus_name; } -static inline struct usb_hcd *vhci_to_hcd(struct vhci_hcd *vhci) +static inline struct usb_hcd *vhci_hcd_to_hcd(struct vhci_hcd *vhci_hcd) { - return container_of((void *) vhci, struct usb_hcd, hcd_priv); + return container_of((void *) vhci_hcd, struct usb_hcd, hcd_priv); } -static inline struct vhci_hcd *vdev_to_vhci(struct vhci_device *vdev) +static inline struct vhci_hcd *vdev_to_vhci_hcd(struct vhci_device *vdev) { - return container_of( - (void *)(vdev - vdev->rhport), struct vhci_hcd, vdev); + return container_of((void *)(vdev - vdev->rhport), struct vhci_hcd, vdev); } #endif /* __USBIP_VHCI_H */ diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c index 0585078638db..2c4b2fd40406 100644 --- a/drivers/usb/usbip/vhci_hcd.c +++ b/drivers/usb/usbip/vhci_hcd.c @@ -58,8 +58,7 @@ static const char driver_name[] = "vhci_hcd"; static const char driver_desc[] = "USB/IP Virtual Host Controller"; int vhci_num_controllers = VHCI_NR_HCS; - -struct platform_device **vhci_pdevs; +struct vhci *vhcis; static const char * const bit_desc[] = { "CONNECTION", /*0*/ @@ -67,7 +66,7 @@ static const char * const bit_desc[] = { "SUSPEND", /*2*/ "OVER_CURRENT", /*3*/ "RESET", /*4*/ - "R5", /*5*/ + "L1", /*5*/ "R6", /*6*/ "R7", /*7*/ "POWER", /*8*/ @@ -83,7 +82,7 @@ static const char * const bit_desc[] = { "C_SUSPEND", /*18*/ "C_OVER_CURRENT", /*19*/ "C_RESET", /*20*/ - "R21", /*21*/ + "C_L1", /*21*/ "R22", /*22*/ "R23", /*23*/ "R24", /*24*/ @@ -96,10 +95,49 @@ static const char * const bit_desc[] = { "R31", /*31*/ }; -static void dump_port_status_diff(u32 prev_status, u32 new_status) +static const char * const bit_desc_ss[] = { + "CONNECTION", /*0*/ + "ENABLE", /*1*/ + "SUSPEND", /*2*/ + "OVER_CURRENT", /*3*/ + "RESET", /*4*/ + "L1", /*5*/ + "R6", /*6*/ + "R7", /*7*/ + "R8", /*8*/ + "POWER", /*9*/ + "HIGHSPEED", /*10*/ + "PORT_TEST", /*11*/ + "INDICATOR", /*12*/ + "R13", /*13*/ + "R14", /*14*/ + "R15", /*15*/ + "C_CONNECTION", /*16*/ + "C_ENABLE", /*17*/ + "C_SUSPEND", /*18*/ + "C_OVER_CURRENT", /*19*/ + "C_RESET", /*20*/ + "C_BH_RESET", /*21*/ + "C_LINK_STATE", /*22*/ + "C_CONFIG_ERROR", /*23*/ + "R24", /*24*/ + "R25", /*25*/ + "R26", /*26*/ + "R27", /*27*/ + "R28", /*28*/ + "R29", /*29*/ + "R30", /*30*/ + "R31", /*31*/ +}; + +static void dump_port_status_diff(u32 prev_status, u32 new_status, bool usb3) { int i = 0; u32 bit = 1; + const char * const *desc = bit_desc; + + if (usb3) + desc = bit_desc_ss; pr_debug("status prev -> new: %08x -> %08x\n", prev_status, new_status); while (bit) { @@ -114,8 +152,12 @@ static void dump_port_status_diff(u32 prev_status, u32 new_status) else change = ' '; - if (prev || new) - pr_debug(" %c%s\n", change, bit_desc[i]); + if (prev || new) { + pr_debug(" %c%s\n", change, desc[i]); + + if (bit == 1) /* USB_PORT_STAT_CONNECTION */ + pr_debug(" %c%s\n", change, "USB_PORT_STAT_SPEED_5GBPS"); + } bit <<= 1; i++; } @@ -124,7 +166,8 @@ static void dump_port_status_diff(u32 prev_status, u32 new_status) void rh_port_connect(struct vhci_device *vdev, enum usb_device_speed speed) { - struct vhci_hcd *vhci = vdev_to_vhci(vdev); + struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); + struct vhci *vhci = vhci_hcd->vhci; int rhport = vdev->rhport; u32 status; unsigned long flags; @@ -133,7 +176,7 @@ void rh_port_connect(struct vhci_device *vdev, enum usb_device_speed speed) spin_lock_irqsave(&vhci->lock, flags); - status = vhci->port_status[rhport]; + status = vhci_hcd->port_status[rhport]; status |= USB_PORT_STAT_CONNECTION | (1 << USB_PORT_FEAT_C_CONNECTION); @@ -148,16 +191,17 @@ void rh_port_connect(struct vhci_device *vdev, enum usb_device_speed speed) break; } - vhci->port_status[rhport] = status; + vhci_hcd->port_status[rhport] = status; spin_unlock_irqrestore(&vhci->lock, flags); - usb_hcd_poll_rh_status(vhci_to_hcd(vhci)); + usb_hcd_poll_rh_status(vhci_hcd_to_hcd(vhci_hcd)); } static void rh_port_disconnect(struct vhci_device *vdev) { - struct vhci_hcd *vhci = vdev_to_vhci(vdev); + struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); + struct vhci *vhci = vhci_hcd->vhci; int rhport = vdev->rhport; u32 status; unsigned long flags; @@ -166,15 +210,15 @@ static void rh_port_disconnect(struct vhci_device *vdev) spin_lock_irqsave(&vhci->lock, flags); - status = vhci->port_status[rhport]; + status = vhci_hcd->port_status[rhport]; status &= ~USB_PORT_STAT_CONNECTION; status |= (1 << USB_PORT_FEAT_C_CONNECTION); - vhci->port_status[rhport] = status; + vhci_hcd->port_status[rhport] = status; spin_unlock_irqrestore(&vhci->lock, flags); - usb_hcd_poll_rh_status(vhci_to_hcd(vhci)); + usb_hcd_poll_rh_status(vhci_hcd_to_hcd(vhci_hcd)); } #define PORT_C_MASK \ @@ -197,17 +241,15 @@ static void rh_port_disconnect(struct vhci_device *vdev) */ static int vhci_hub_status(struct usb_hcd *hcd, char *buf) { - struct vhci_hcd *vhci; - int retval; + struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd); + struct vhci *vhci = vhci_hcd->vhci; + int retval = DIV_ROUND_UP(VHCI_HC_PORTS + 1, 8); int rhport; int changed = 0; unsigned long flags; - retval = DIV_ROUND_UP(VHCI_HC_PORTS + 1, 8); memset(buf, 0, retval); - vhci = hcd_to_vhci(hcd); - spin_lock_irqsave(&vhci->lock, flags); if (!HCD_HW_ACCESSIBLE(hcd)) { usbip_dbg_vhci_rh("hw accessible flag not on?\n"); @@ -216,7 +258,7 @@ static int vhci_hub_status(struct usb_hcd *hcd, char *buf) /* check pseudo status register for each port */ for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) { - if ((vhci->port_status[rhport] & PORT_C_MASK)) { + if ((vhci_hcd->port_status[rhport] & PORT_C_MASK)) { /* The status of a port has been changed, */ usbip_dbg_vhci_rh("port %d status changed\n", rhport); @@ -233,6 +275,40 @@ done: return changed ? retval : 0; } +/* usb 3.0 root hub device descriptor */ +static struct { + struct usb_bos_descriptor bos; + struct usb_ss_cap_descriptor ss_cap; +} __packed usb3_bos_desc = { + + .bos = { + .bLength = USB_DT_BOS_SIZE, + .bDescriptorType = USB_DT_BOS, + .wTotalLength = cpu_to_le16(sizeof(usb3_bos_desc)), + .bNumDeviceCaps = 1, + }, + .ss_cap = { + .bLength = USB_DT_USB_SS_CAP_SIZE, + .bDescriptorType = USB_DT_DEVICE_CAPABILITY, + .bDevCapabilityType = USB_SS_CAP_TYPE, + .wSpeedSupported = cpu_to_le16(USB_5GBPS_OPERATION), + .bFunctionalitySupport = ilog2(USB_5GBPS_OPERATION), + }, +}; + +static inline void +ss_hub_descriptor(struct usb_hub_descriptor *desc) +{ + memset(desc, 0, sizeof *desc); + desc->bDescriptorType = USB_DT_SS_HUB; + desc->bDescLength = 12; + desc->wHubCharacteristics = cpu_to_le16( + HUB_CHAR_INDV_PORT_LPSM | HUB_CHAR_COMMON_OCPM); + desc->bNbrPorts = VHCI_HC_PORTS; + desc->u.ss.bHubHdrDecLat = 0x04; /* Worst case: 0.4 micro sec*/ + desc->u.ss.DeviceRemovable = 0xffff; +} + static inline void hub_descriptor(struct usb_hub_descriptor *desc) { int width; @@ -253,7 +329,8 @@ static inline void hub_descriptor(struct usb_hub_descriptor *desc) static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, char *buf, u16 wLength) { - struct vhci_hcd *dum; + struct vhci_hcd *vhci_hcd; + struct vhci *vhci; int retval = 0; int rhport; unsigned long flags; @@ -265,21 +342,24 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, /* * NOTE: - * wIndex shows the port number and begins from 1. + * wIndex (bits 0-7) shows the port number and begins from 1? */ + wIndex = ((__u8)(wIndex & 0x00ff)); usbip_dbg_vhci_rh("typeReq %x wValue %x wIndex %x\n", typeReq, wValue, wIndex); + if (wIndex > VHCI_HC_PORTS) pr_err("invalid port number %d\n", wIndex); - rhport = ((__u8)(wIndex & 0x00ff)) - 1; + rhport = wIndex - 1; - dum = hcd_to_vhci(hcd); + vhci_hcd = hcd_to_vhci_hcd(hcd); + vhci = vhci_hcd->vhci; - spin_lock_irqsave(&dum->lock, flags); + spin_lock_irqsave(&vhci->lock, flags); /* store old status and compare now and old later */ if (usbip_dbg_flag_vhci_rh) { - memcpy(prev_port_status, dum->port_status, + memcpy(prev_port_status, vhci_hcd->port_status, sizeof(prev_port_status)); } @@ -290,45 +370,56 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, case ClearPortFeature: switch (wValue) { case USB_PORT_FEAT_SUSPEND: - if (dum->port_status[rhport] & USB_PORT_STAT_SUSPEND) { + if (hcd->speed == HCD_USB3) { + pr_err(" ClearPortFeature: USB_PORT_FEAT_SUSPEND req not " + "supported for USB 3.0 roothub\n"); + goto error; + } + usbip_dbg_vhci_rh( + " ClearPortFeature: USB_PORT_FEAT_SUSPEND\n"); + if (vhci_hcd->port_status[rhport] & USB_PORT_STAT_SUSPEND) { /* 20msec signaling */ - dum->resuming = 1; - dum->re_timeout = - jiffies + msecs_to_jiffies(20); + vhci_hcd->resuming = 1; + vhci_hcd->re_timeout = jiffies + msecs_to_jiffies(20); } break; case USB_PORT_FEAT_POWER: usbip_dbg_vhci_rh( " ClearPortFeature: USB_PORT_FEAT_POWER\n"); - dum->port_status[rhport] = 0; - dum->resuming = 0; - break; - case USB_PORT_FEAT_C_RESET: - usbip_dbg_vhci_rh( - " ClearPortFeature: USB_PORT_FEAT_C_RESET\n"); - switch (dum->vdev[rhport].speed) { - case USB_SPEED_HIGH: - dum->port_status[rhport] |= - USB_PORT_STAT_HIGH_SPEED; - break; - case USB_SPEED_LOW: - dum->port_status[rhport] |= - USB_PORT_STAT_LOW_SPEED; - break; - default: - break; - } + if (hcd->speed == HCD_USB3) + vhci_hcd->port_status[rhport] &= ~USB_SS_PORT_STAT_POWER; + else + vhci_hcd->port_status[rhport] &= ~USB_PORT_STAT_POWER; break; default: usbip_dbg_vhci_rh(" ClearPortFeature: default %x\n", wValue); - dum->port_status[rhport] &= ~(1 << wValue); + vhci_hcd->port_status[rhport] &= ~(1 << wValue); break; } break; case GetHubDescriptor: usbip_dbg_vhci_rh(" GetHubDescriptor\n"); - hub_descriptor((struct usb_hub_descriptor *) buf); + if (hcd->speed == HCD_USB3 && + (wLength < USB_DT_SS_HUB_SIZE || + wValue != (USB_DT_SS_HUB << 8))) { + pr_err("Wrong hub descriptor type for USB 3.0 roothub.\n"); + goto error; + } + if (hcd->speed == HCD_USB3) + ss_hub_descriptor((struct usb_hub_descriptor *) buf); + else + hub_descriptor((struct usb_hub_descriptor *) buf); + break; + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + if (hcd->speed != HCD_USB3) + goto error; + + if ((wValue >> 8) != USB_DT_BOS) + goto error; + + memcpy(buf, &usb3_bos_desc, sizeof(usb3_bos_desc)); + retval = sizeof(usb3_bos_desc); break; case GetHubStatus: usbip_dbg_vhci_rh(" GetHubStatus\n"); @@ -336,7 +427,7 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, break; case GetPortStatus: usbip_dbg_vhci_rh(" GetPortStatus port %x\n", wIndex); - if (wIndex > VHCI_HC_PORTS || wIndex < 1) { + if (wIndex < 1) { pr_err("invalid port number %d\n", wIndex); retval = -EPIPE; } @@ -346,36 +437,48 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, /* whoever resets or resumes must GetPortStatus to * complete it!! */ - if (dum->resuming && time_after(jiffies, dum->re_timeout)) { - dum->port_status[rhport] |= - (1 << USB_PORT_FEAT_C_SUSPEND); - dum->port_status[rhport] &= - ~(1 << USB_PORT_FEAT_SUSPEND); - dum->resuming = 0; - dum->re_timeout = 0; + if (vhci_hcd->resuming && time_after(jiffies, vhci_hcd->re_timeout)) { + vhci_hcd->port_status[rhport] |= (1 << USB_PORT_FEAT_C_SUSPEND); + vhci_hcd->port_status[rhport] &= ~(1 << USB_PORT_FEAT_SUSPEND); + vhci_hcd->resuming = 0; + vhci_hcd->re_timeout = 0; } - if ((dum->port_status[rhport] & (1 << USB_PORT_FEAT_RESET)) != - 0 && time_after(jiffies, dum->re_timeout)) { - dum->port_status[rhport] |= - (1 << USB_PORT_FEAT_C_RESET); - dum->port_status[rhport] &= - ~(1 << USB_PORT_FEAT_RESET); - dum->re_timeout = 0; + if ((vhci_hcd->port_status[rhport] & (1 << USB_PORT_FEAT_RESET)) != + 0 && time_after(jiffies, vhci_hcd->re_timeout)) { + vhci_hcd->port_status[rhport] |= (1 << USB_PORT_FEAT_C_RESET); + vhci_hcd->port_status[rhport] &= ~(1 << USB_PORT_FEAT_RESET); + vhci_hcd->re_timeout = 0; - if (dum->vdev[rhport].ud.status == + if (vhci_hcd->vdev[rhport].ud.status == VDEV_ST_NOTASSIGNED) { usbip_dbg_vhci_rh( " enable rhport %d (status %u)\n", rhport, - dum->vdev[rhport].ud.status); - dum->port_status[rhport] |= + vhci_hcd->vdev[rhport].ud.status); + vhci_hcd->port_status[rhport] |= USB_PORT_STAT_ENABLE; } + + if (hcd->speed < HCD_USB3) { + switch (vhci_hcd->vdev[rhport].speed) { + case USB_SPEED_HIGH: + vhci_hcd->port_status[rhport] |= + USB_PORT_STAT_HIGH_SPEED; + break; + case USB_SPEED_LOW: + vhci_hcd->port_status[rhport] |= + USB_PORT_STAT_LOW_SPEED; + break; + default: + pr_err("vhci_device speed not set\n"); + break; + } + } } - ((__le16 *) buf)[0] = cpu_to_le16(dum->port_status[rhport]); + ((__le16 *) buf)[0] = cpu_to_le16(vhci_hcd->port_status[rhport]); ((__le16 *) buf)[1] = - cpu_to_le16(dum->port_status[rhport] >> 16); + cpu_to_le16(vhci_hcd->port_status[rhport] >> 16); usbip_dbg_vhci_rh(" GetPortStatus bye %x %x\n", ((u16 *)buf)[0], ((u16 *)buf)[1]); @@ -386,36 +489,119 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, break; case SetPortFeature: switch (wValue) { + case USB_PORT_FEAT_LINK_STATE: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_LINK_STATE\n"); + if (hcd->speed != HCD_USB3) { + pr_err("USB_PORT_FEAT_LINK_STATE req not " + "supported for USB 2.0 roothub\n"); + goto error; + } + /* + * Since this is dummy we don't have an actual link so + * there is nothing to do for the SET_LINK_STATE cmd + */ + break; + case USB_PORT_FEAT_U1_TIMEOUT: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_U1_TIMEOUT\n"); + case USB_PORT_FEAT_U2_TIMEOUT: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_U2_TIMEOUT\n"); + /* TODO: add suspend/resume support! */ + if (hcd->speed != HCD_USB3) { + pr_err("USB_PORT_FEAT_U1/2_TIMEOUT req not " + "supported for USB 2.0 roothub\n"); + goto error; + } + break; case USB_PORT_FEAT_SUSPEND: usbip_dbg_vhci_rh( " SetPortFeature: USB_PORT_FEAT_SUSPEND\n"); + /* Applicable only for USB2.0 hub */ + if (hcd->speed == HCD_USB3) { + pr_err("USB_PORT_FEAT_SUSPEND req not " + "supported for USB 3.0 roothub\n"); + goto error; + } + + vhci_hcd->port_status[rhport] |= USB_PORT_STAT_SUSPEND; break; + case USB_PORT_FEAT_POWER: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_POWER\n"); + if (hcd->speed == HCD_USB3) + vhci_hcd->port_status[rhport] |= USB_SS_PORT_STAT_POWER; + else + vhci_hcd->port_status[rhport] |= USB_PORT_STAT_POWER; + break; + case USB_PORT_FEAT_BH_PORT_RESET: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_BH_PORT_RESET\n"); + /* Applicable only for USB3.0 hub */ + if (hcd->speed != HCD_USB3) { + pr_err("USB_PORT_FEAT_BH_PORT_RESET req not " + "supported for USB 2.0 roothub\n"); + goto error; + } + /* FALLS THROUGH */ case USB_PORT_FEAT_RESET: usbip_dbg_vhci_rh( " SetPortFeature: USB_PORT_FEAT_RESET\n"); - /* if it's already running, disconnect first */ - if (dum->port_status[rhport] & USB_PORT_STAT_ENABLE) { - dum->port_status[rhport] &= - ~(USB_PORT_STAT_ENABLE | - USB_PORT_STAT_LOW_SPEED | - USB_PORT_STAT_HIGH_SPEED); - /* FIXME test that code path! */ + /* if it's already enabled, disable */ + if (hcd->speed == HCD_USB3) { + vhci_hcd->port_status[rhport] = 0; + vhci_hcd->port_status[rhport] = + (USB_SS_PORT_STAT_POWER | + USB_PORT_STAT_CONNECTION | + USB_PORT_STAT_RESET); + } else if (vhci_hcd->port_status[rhport] & USB_PORT_STAT_ENABLE) { + vhci_hcd->port_status[rhport] &= ~(USB_PORT_STAT_ENABLE + | USB_PORT_STAT_LOW_SPEED + | USB_PORT_STAT_HIGH_SPEED); } + /* 50msec reset signaling */ - dum->re_timeout = jiffies + msecs_to_jiffies(50); + vhci_hcd->re_timeout = jiffies + msecs_to_jiffies(50); - /* FALLTHROUGH */ + /* FALLS THROUGH */ default: usbip_dbg_vhci_rh(" SetPortFeature: default %d\n", wValue); - dum->port_status[rhport] |= (1 << wValue); - break; + if (hcd->speed == HCD_USB3) { + if ((vhci_hcd->port_status[rhport] & + USB_SS_PORT_STAT_POWER) != 0) { + vhci_hcd->port_status[rhport] |= (1 << wValue); + } + } else + if ((vhci_hcd->port_status[rhport] & + USB_PORT_STAT_POWER) != 0) { + vhci_hcd->port_status[rhport] |= (1 << wValue); + } + } + break; + case GetPortErrorCount: + usbip_dbg_vhci_rh(" GetPortErrorCount\n"); + if (hcd->speed != HCD_USB3) { + pr_err("GetPortErrorCount req not " + "supported for USB 2.0 roothub\n"); + goto error; + } + /* We'll always return 0 since this is a dummy hub */ + *(__le32 *) buf = cpu_to_le32(0); + break; + case SetHubDepth: + usbip_dbg_vhci_rh(" SetHubDepth\n"); + if (hcd->speed != HCD_USB3) { + pr_err("SetHubDepth req not supported for " + "USB 2.0 roothub\n"); + goto error; } break; - default: - pr_err("default: no such request\n"); - + pr_err("default hub control req: %04x v%04x i%04x l%d\n", + typeReq, wValue, wIndex, wLength); +error: /* "protocol stall" on error */ retval = -EPIPE; } @@ -425,12 +611,16 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, /* Only dump valid port status */ if (rhport >= 0) { dump_port_status_diff(prev_port_status[rhport], - dum->port_status[rhport]); + vhci_hcd->port_status[rhport], + hcd->speed == HCD_USB3); } } usbip_dbg_vhci_rh(" bye\n"); - spin_unlock_irqrestore(&dum->lock, flags); + spin_unlock_irqrestore(&vhci->lock, flags); + + if ((vhci_hcd->port_status[rhport] & PORT_C_MASK) != 0) + usb_hcd_poll_rh_status(hcd); return retval; } @@ -438,14 +628,14 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, static void vhci_tx_urb(struct urb *urb, struct vhci_device *vdev) { struct vhci_priv *priv; - struct vhci_hcd *vhci; + struct vhci_hcd *vhci_hcd; unsigned long flags; if (!vdev) { pr_err("could not get virtual device"); return; } - vhci = vdev_to_vhci(vdev); + vhci_hcd = vdev_to_vhci_hcd(vdev); priv = kzalloc(sizeof(struct vhci_priv), GFP_ATOMIC); if (!priv) { @@ -455,7 +645,7 @@ static void vhci_tx_urb(struct urb *urb, struct vhci_device *vdev) spin_lock_irqsave(&vdev->priv_lock, flags); - priv->seqnum = atomic_inc_return(&vhci->seqnum); + priv->seqnum = atomic_inc_return(&vhci_hcd->seqnum); if (priv->seqnum == 0xffff) dev_info(&urb->dev->dev, "seqnum max\n"); @@ -470,10 +660,10 @@ static void vhci_tx_urb(struct urb *urb, struct vhci_device *vdev) spin_unlock_irqrestore(&vdev->priv_lock, flags); } -static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, - gfp_t mem_flags) +static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) { - struct vhci_hcd *vhci = hcd_to_vhci(hcd); + struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd); + struct vhci *vhci = vhci_hcd->vhci; struct device *dev = &urb->dev->dev; u8 portnum = urb->dev->portnum; int ret = 0; @@ -487,7 +677,7 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, pr_err("invalid port number %d\n", portnum); return -ENODEV; } - vdev = &vhci->vdev[portnum-1]; + vdev = &vhci_hcd->vdev[portnum-1]; /* patch to usb_sg_init() is in 2.5.60 */ BUG_ON(!urb->transfer_buffer && urb->transfer_buffer_length); @@ -640,7 +830,8 @@ no_need_unlink: */ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) { - struct vhci_hcd *vhci = hcd_to_vhci(hcd); + struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd); + struct vhci *vhci = vhci_hcd->vhci; struct vhci_priv *priv; struct vhci_device *vdev; unsigned long flags; @@ -691,7 +882,7 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) usb_hcd_unlink_urb_from_ep(hcd, urb); spin_unlock_irqrestore(&vhci->lock, flags); - usb_hcd_giveback_urb(vhci_to_hcd(vhci), urb, urb->status); + usb_hcd_giveback_urb(hcd, urb, urb->status); spin_lock_irqsave(&vhci->lock, flags); } else { @@ -709,7 +900,7 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) return -ENOMEM; } - unlink->seqnum = atomic_inc_return(&vhci->seqnum); + unlink->seqnum = atomic_inc_return(&vhci_hcd->seqnum); if (unlink->seqnum == 0xffff) pr_info("seqnum max\n"); @@ -733,8 +924,9 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) static void vhci_device_unlink_cleanup(struct vhci_device *vdev) { - struct vhci_hcd *vhci = vdev_to_vhci(vdev); - struct usb_hcd *hcd = vhci_to_hcd(vhci); + struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); + struct usb_hcd *hcd = vhci_hcd_to_hcd(vhci_hcd); + struct vhci *vhci = vhci_hcd->vhci; struct vhci_unlink *unlink, *tmp; unsigned long flags; @@ -846,7 +1038,6 @@ static void vhci_shutdown_connection(struct usbip_device *ud) pr_info("disconnect device\n"); } - static void vhci_device_reset(struct usbip_device *ud) { struct vhci_device *vdev = container_of(ud, struct vhci_device, ud); @@ -918,29 +1109,58 @@ static int hcd_name_to_id(const char *name) return val; } +static int vhci_setup(struct usb_hcd *hcd) +{ + struct vhci *vhci = *((void **)dev_get_platdata(hcd->self.controller)); + hcd->self.sg_tablesize = ~0; + if (usb_hcd_is_primary_hcd(hcd)) { + vhci->vhci_hcd_hs = hcd_to_vhci_hcd(hcd); + vhci->vhci_hcd_hs->vhci = vhci; + /* + * Mark the first roothub as being USB 2.0. + * The USB 3.0 roothub will be registered later by + * vhci_hcd_probe() + */ + hcd->speed = HCD_USB2; + hcd->self.root_hub->speed = USB_SPEED_HIGH; + } else { + vhci->vhci_hcd_ss = hcd_to_vhci_hcd(hcd); + vhci->vhci_hcd_ss->vhci = vhci; + hcd->speed = HCD_USB3; + hcd->self.root_hub->speed = USB_SPEED_SUPER; + } + return 0; +} + static int vhci_start(struct usb_hcd *hcd) { - struct vhci_hcd *vhci = hcd_to_vhci(hcd); + struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd); int id, rhport; - int err = 0; + int err; usbip_dbg_vhci_hc("enter vhci_start\n"); + if (usb_hcd_is_primary_hcd(hcd)) + spin_lock_init(&vhci_hcd->vhci->lock); + /* initialize private data of usb_hcd */ for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) { - struct vhci_device *vdev = &vhci->vdev[rhport]; + struct vhci_device *vdev = &vhci_hcd->vdev[rhport]; vhci_device_init(vdev); vdev->rhport = rhport; } - atomic_set(&vhci->seqnum, 0); - spin_lock_init(&vhci->lock); + atomic_set(&vhci_hcd->seqnum, 0); hcd->power_budget = 0; /* no limit */ hcd->uses_new_polling = 1; +#ifdef CONFIG_USB_OTG + hcd->self.otg_port = 1; +#endif + id = hcd_name_to_id(hcd_name(hcd)); if (id < 0) { pr_err("invalid vhci name %s\n", hcd_name(hcd)); @@ -948,7 +1168,7 @@ static int vhci_start(struct usb_hcd *hcd) } /* vhci_hcd is now ready to be controlled through sysfs */ - if (id == 0) { + if (id == 0 && usb_hcd_is_primary_hcd(hcd)) { err = vhci_init_attr_group(); if (err) { pr_err("init attr group\n"); @@ -968,21 +1188,21 @@ static int vhci_start(struct usb_hcd *hcd) static void vhci_stop(struct usb_hcd *hcd) { - struct vhci_hcd *vhci = hcd_to_vhci(hcd); + struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd); int id, rhport; usbip_dbg_vhci_hc("stop VHCI controller\n"); /* 1. remove the userland interface of vhci_hcd */ id = hcd_name_to_id(hcd_name(hcd)); - if (id == 0) { + if (id == 0 && usb_hcd_is_primary_hcd(hcd)) { sysfs_remove_group(&hcd_dev(hcd)->kobj, &vhci_attr_group); vhci_finish_attr_group(); } /* 2. shutdown all the ports of vhci_hcd */ for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) { - struct vhci_device *vdev = &vhci->vdev[rhport]; + struct vhci_device *vdev = &vhci_hcd->vdev[rhport]; usbip_event_add(&vdev->ud, VDEV_EVENT_REMOVED); usbip_stop_eh(&vdev->ud); @@ -1000,7 +1220,7 @@ static int vhci_get_frame_number(struct usb_hcd *hcd) /* FIXME: suspend/resume */ static int vhci_bus_suspend(struct usb_hcd *hcd) { - struct vhci_hcd *vhci = hcd_to_vhci(hcd); + struct vhci *vhci = *((void **)dev_get_platdata(hcd->self.controller)); unsigned long flags; dev_dbg(&hcd->self.root_hub->dev, "%s\n", __func__); @@ -1014,7 +1234,7 @@ static int vhci_bus_suspend(struct usb_hcd *hcd) static int vhci_bus_resume(struct usb_hcd *hcd) { - struct vhci_hcd *vhci = hcd_to_vhci(hcd); + struct vhci *vhci = *((void **)dev_get_platdata(hcd->self.controller)); int rc = 0; unsigned long flags; @@ -1036,13 +1256,32 @@ static int vhci_bus_resume(struct usb_hcd *hcd) #define vhci_bus_resume NULL #endif +/* Change a group of bulk endpoints to support multiple stream IDs */ +static int vhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint **eps, unsigned int num_eps, + unsigned int num_streams, gfp_t mem_flags) +{ + dev_dbg(&hcd->self.root_hub->dev, "vhci_alloc_streams not implemented\n"); + return 0; +} + +/* Reverts a group of bulk endpoints back to not using stream IDs. */ +static int vhci_free_streams(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint **eps, unsigned int num_eps, + gfp_t mem_flags) +{ + dev_dbg(&hcd->self.root_hub->dev, "vhci_free_streams not implemented\n"); + return 0; +} + static struct hc_driver vhci_hc_driver = { .description = driver_name, .product_desc = driver_desc, .hcd_priv_size = sizeof(struct vhci_hcd), - .flags = HCD_USB2, + .flags = HCD_USB3 | HCD_SHARED, + .reset = vhci_setup, .start = vhci_start, .stop = vhci_stop, @@ -1055,11 +1294,16 @@ static struct hc_driver vhci_hc_driver = { .hub_control = vhci_hub_control, .bus_suspend = vhci_bus_suspend, .bus_resume = vhci_bus_resume, + + .alloc_streams = vhci_alloc_streams, + .free_streams = vhci_free_streams, }; static int vhci_hcd_probe(struct platform_device *pdev) { - struct usb_hcd *hcd; + struct vhci *vhci = *((void **)dev_get_platdata(&pdev->dev)); + struct usb_hcd *hcd_hs; + struct usb_hcd *hcd_ss; int ret; usbip_dbg_vhci_hc("name %s id %d\n", pdev->name, pdev->id); @@ -1068,43 +1312,68 @@ static int vhci_hcd_probe(struct platform_device *pdev) * Allocate and initialize hcd. * Our private data is also allocated automatically. */ - hcd = usb_create_hcd(&vhci_hc_driver, &pdev->dev, dev_name(&pdev->dev)); - if (!hcd) { - pr_err("create hcd failed\n"); + hcd_hs = usb_create_hcd(&vhci_hc_driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd_hs) { + pr_err("create primary hcd failed\n"); return -ENOMEM; } - hcd->has_tt = 1; + hcd_hs->has_tt = 1; /* * Finish generic HCD structure initialization and register. * Call the driver's reset() and start() routines. */ - ret = usb_add_hcd(hcd, 0, 0); + ret = usb_add_hcd(hcd_hs, 0, 0); if (ret != 0) { - pr_err("usb_add_hcd failed %d\n", ret); - usb_put_hcd(hcd); - return ret; + pr_err("usb_add_hcd hs failed %d\n", ret); + goto put_usb2_hcd; + } + + hcd_ss = usb_create_shared_hcd(&vhci_hc_driver, &pdev->dev, + dev_name(&pdev->dev), hcd_hs); + if (!hcd_ss) { + ret = -ENOMEM; + pr_err("create shared hcd failed\n"); + goto remove_usb2_hcd; + } + + ret = usb_add_hcd(hcd_ss, 0, 0); + if (ret) { + pr_err("usb_add_hcd ss failed %d\n", ret); + goto put_usb3_hcd; } usbip_dbg_vhci_hc("bye\n"); return 0; + +put_usb3_hcd: + usb_put_hcd(hcd_ss); +remove_usb2_hcd: + usb_remove_hcd(hcd_hs); +put_usb2_hcd: + usb_put_hcd(hcd_hs); + vhci->vhci_hcd_hs = NULL; + vhci->vhci_hcd_ss = NULL; + return ret; } static int vhci_hcd_remove(struct platform_device *pdev) { - struct usb_hcd *hcd; - - hcd = platform_get_drvdata(pdev); - if (!hcd) - return 0; + struct vhci *vhci = *((void **)dev_get_platdata(&pdev->dev)); /* * Disconnects the root hub, * then reverses the effects of usb_add_hcd(), * invoking the HCD's stop() methods. */ - usb_remove_hcd(hcd); - usb_put_hcd(hcd); + usb_remove_hcd(vhci_hcd_to_hcd(vhci->vhci_hcd_ss)); + usb_put_hcd(vhci_hcd_to_hcd(vhci->vhci_hcd_ss)); + + usb_remove_hcd(vhci_hcd_to_hcd(vhci->vhci_hcd_hs)); + usb_put_hcd(vhci_hcd_to_hcd(vhci->vhci_hcd_hs)); + + vhci->vhci_hcd_hs = NULL; + vhci->vhci_hcd_ss = NULL; return 0; } @@ -1115,23 +1384,32 @@ static int vhci_hcd_remove(struct platform_device *pdev) static int vhci_hcd_suspend(struct platform_device *pdev, pm_message_t state) { struct usb_hcd *hcd; - struct vhci_hcd *vhci; + struct vhci *vhci; int rhport; int connected = 0; int ret = 0; unsigned long flags; + dev_dbg(&pdev->dev, "%s\n", __func__); + hcd = platform_get_drvdata(pdev); if (!hcd) return 0; - vhci = hcd_to_vhci(hcd); + + vhci = *((void **)dev_get_platdata(hcd->self.controller)); spin_lock_irqsave(&vhci->lock, flags); - for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) - if (vhci->port_status[rhport] & USB_PORT_STAT_CONNECTION) + for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) { + if (vhci->vhci_hcd_hs->port_status[rhport] & + USB_PORT_STAT_CONNECTION) connected += 1; + if (vhci->vhci_hcd_ss->port_status[rhport] & + USB_PORT_STAT_CONNECTION) + connected += 1; + } + spin_unlock_irqrestore(&vhci->lock, flags); if (connected > 0) { @@ -1179,34 +1457,16 @@ static struct platform_driver vhci_driver = { }, }; -static int add_platform_device(int id) -{ - struct platform_device *pdev; - int dev_nr; - - if (id == 0) - dev_nr = -1; - else - dev_nr = id; - - pdev = platform_device_register_simple(driver_name, dev_nr, NULL, 0); - if (IS_ERR(pdev)) - return PTR_ERR(pdev); - - *(vhci_pdevs + id) = pdev; - return 0; -} - static void del_platform_devices(void) { struct platform_device *pdev; int i; for (i = 0; i < vhci_num_controllers; i++) { - pdev = *(vhci_pdevs + i); + pdev = vhcis[i].pdev; if (pdev != NULL) platform_device_unregister(pdev); - *(vhci_pdevs + i) = NULL; + vhcis[i].pdev = NULL; } sysfs_remove_link(&platform_bus.kobj, driver_name); } @@ -1221,28 +1481,51 @@ static int __init vhci_hcd_init(void) if (vhci_num_controllers < 1) vhci_num_controllers = 1; - vhci_pdevs = kcalloc(vhci_num_controllers, sizeof(void *), GFP_KERNEL); - if (vhci_pdevs == NULL) + vhcis = kcalloc(vhci_num_controllers, sizeof(struct vhci), GFP_KERNEL); + if (vhcis == NULL) return -ENOMEM; + for (i = 0; i < vhci_num_controllers; i++) { + vhcis[i].pdev = platform_device_alloc(driver_name, i); + if (!vhcis[i].pdev) { + i--; + while (i >= 0) + platform_device_put(vhcis[i--].pdev); + ret = -ENOMEM; + goto err_device_alloc; + } + } + for (i = 0; i < vhci_num_controllers; i++) { + void *vhci = &vhcis[i]; + ret = platform_device_add_data(vhcis[i].pdev, &vhci, sizeof(void *)); + if (ret) + goto err_driver_register; + } + ret = platform_driver_register(&vhci_driver); if (ret) goto err_driver_register; for (i = 0; i < vhci_num_controllers; i++) { - ret = add_platform_device(i); - if (ret) - goto err_platform_device_register; + ret = platform_device_add(vhcis[i].pdev); + if (ret < 0) { + i--; + while (i >= 0) + platform_device_del(vhcis[i--].pdev); + goto err_add_hcd; + } } pr_info(DRIVER_DESC " v" USBIP_VERSION "\n"); return ret; -err_platform_device_register: - del_platform_devices(); +err_add_hcd: platform_driver_unregister(&vhci_driver); err_driver_register: - kfree(vhci_pdevs); + for (i = 0; i < vhci_num_controllers; i++) + platform_device_put(vhcis[i].pdev); +err_device_alloc: + kfree(vhcis); return ret; } @@ -1250,7 +1533,7 @@ static void __exit vhci_hcd_exit(void) { del_platform_devices(); platform_driver_unregister(&vhci_driver); - kfree(vhci_pdevs); + kfree(vhcis); } module_init(vhci_hcd_init); diff --git a/drivers/usb/usbip/vhci_rx.c b/drivers/usb/usbip/vhci_rx.c index fc2d319e2360..ef2f2d5ca6b2 100644 --- a/drivers/usb/usbip/vhci_rx.c +++ b/drivers/usb/usbip/vhci_rx.c @@ -70,7 +70,8 @@ struct urb *pickup_urb_and_free_priv(struct vhci_device *vdev, __u32 seqnum) static void vhci_recv_ret_submit(struct vhci_device *vdev, struct usbip_header *pdu) { - struct vhci_hcd *vhci = vdev_to_vhci(vdev); + struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); + struct vhci *vhci = vhci_hcd->vhci; struct usbip_device *ud = &vdev->ud; struct urb *urb; unsigned long flags; @@ -82,7 +83,7 @@ static void vhci_recv_ret_submit(struct vhci_device *vdev, if (!urb) { pr_err("cannot find a urb of seqnum %u\n", pdu->base.seqnum); pr_info("max seqnum %d\n", - atomic_read(&vhci->seqnum)); + atomic_read(&vhci_hcd->seqnum)); usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); return; } @@ -107,10 +108,10 @@ static void vhci_recv_ret_submit(struct vhci_device *vdev, usbip_dbg_vhci_rx("now giveback urb %p\n", urb); spin_lock_irqsave(&vhci->lock, flags); - usb_hcd_unlink_urb_from_ep(vhci_to_hcd(vhci), urb); + usb_hcd_unlink_urb_from_ep(vhci_hcd_to_hcd(vhci_hcd), urb); spin_unlock_irqrestore(&vhci->lock, flags); - usb_hcd_giveback_urb(vhci_to_hcd(vhci), urb, urb->status); + usb_hcd_giveback_urb(vhci_hcd_to_hcd(vhci_hcd), urb, urb->status); usbip_dbg_vhci_rx("Leave\n"); } @@ -143,7 +144,8 @@ static struct vhci_unlink *dequeue_pending_unlink(struct vhci_device *vdev, static void vhci_recv_ret_unlink(struct vhci_device *vdev, struct usbip_header *pdu) { - struct vhci_hcd *vhci = vdev_to_vhci(vdev); + struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); + struct vhci *vhci = vhci_hcd->vhci; struct vhci_unlink *unlink; struct urb *urb; unsigned long flags; @@ -177,10 +179,10 @@ static void vhci_recv_ret_unlink(struct vhci_device *vdev, pr_info("urb->status %d\n", urb->status); spin_lock_irqsave(&vhci->lock, flags); - usb_hcd_unlink_urb_from_ep(vhci_to_hcd(vhci), urb); + usb_hcd_unlink_urb_from_ep(vhci_hcd_to_hcd(vhci_hcd), urb); spin_unlock_irqrestore(&vhci->lock, flags); - usb_hcd_giveback_urb(vhci_to_hcd(vhci), urb, urb->status); + usb_hcd_giveback_urb(vhci_hcd_to_hcd(vhci_hcd), urb, urb->status); } kfree(unlink); diff --git a/drivers/usb/usbip/vhci_sysfs.c b/drivers/usb/usbip/vhci_sysfs.c index b96e5b189269..5778b640ba9c 100644 --- a/drivers/usb/usbip/vhci_sysfs.c +++ b/drivers/usb/usbip/vhci_sysfs.c @@ -29,13 +29,51 @@ /* TODO: refine locking ?*/ +/* + * output example: + * hub port sta spd dev socket local_busid + * hs 0000 004 000 00000000 c5a7bb80 1-2.3 + * ................................................ + * ss 0008 004 000 00000000 d8cee980 2-3.4 + * ................................................ + * + * IP address can be retrieved from a socket pointer address by looking + * up /proc/net/{tcp,tcp6}. Also, a userland program may remember a + * port number and its peer IP address. + */ +static void port_show_vhci(char **out, int hub, int port, struct vhci_device *vdev) +{ + if (hub == HUB_SPEED_HIGH) + *out += sprintf(*out, "hs %04u %03u ", + port, vdev->ud.status); + else /* hub == HUB_SPEED_SUPER */ + *out += sprintf(*out, "ss %04u %03u ", + port, vdev->ud.status); + + if (vdev->ud.status == VDEV_ST_USED) { + *out += sprintf(*out, "%03u %08x ", + vdev->speed, vdev->devid); + *out += sprintf(*out, "%16p %s", + vdev->ud.tcp_socket, + dev_name(&vdev->udev->dev)); + + } else { + *out += sprintf(*out, "000 00000000 "); + *out += sprintf(*out, "0000000000000000 0-0"); + } + + *out += sprintf(*out, "\n"); +} + /* Sysfs entry to show port status */ static ssize_t status_show_vhci(int pdev_nr, char *out) { - struct platform_device *pdev = *(vhci_pdevs + pdev_nr); - struct vhci_hcd *vhci; + struct platform_device *pdev = vhcis[pdev_nr].pdev; + struct vhci *vhci; + struct usb_hcd *hcd; + struct vhci_hcd *vhci_hcd; char *s = out; - int i = 0; + int i; unsigned long flags; if (!pdev || !out) { @@ -43,41 +81,27 @@ static ssize_t status_show_vhci(int pdev_nr, char *out) return 0; } - vhci = hcd_to_vhci(platform_get_drvdata(pdev)); + hcd = platform_get_drvdata(pdev); + vhci_hcd = hcd_to_vhci_hcd(hcd); + vhci = vhci_hcd->vhci; spin_lock_irqsave(&vhci->lock, flags); - /* - * output example: - * port sta spd dev socket local_busid - * 0000 004 000 00000000 c5a7bb80 1-2.3 - * 0001 004 000 00000000 d8cee980 2-3.4 - * - * IP address can be retrieved from a socket pointer address by looking - * up /proc/net/{tcp,tcp6}. Also, a userland program may remember a - * port number and its peer IP address. - */ for (i = 0; i < VHCI_HC_PORTS; i++) { - struct vhci_device *vdev = &vhci->vdev[i]; + struct vhci_device *vdev = &vhci->vhci_hcd_hs->vdev[i]; spin_lock(&vdev->ud.lock); - out += sprintf(out, "%04u %03u ", - (pdev_nr * VHCI_HC_PORTS) + i, - vdev->ud.status); - - if (vdev->ud.status == VDEV_ST_USED) { - out += sprintf(out, "%03u %08x ", - vdev->speed, vdev->devid); - out += sprintf(out, "%16p %s", - vdev->ud.tcp_socket, - dev_name(&vdev->udev->dev)); - - } else { - out += sprintf(out, "000 00000000 "); - out += sprintf(out, "0000000000000000 0-0"); - } + port_show_vhci(&out, HUB_SPEED_HIGH, + pdev_nr * VHCI_PORTS + i, vdev); + spin_unlock(&vdev->ud.lock); + } - out += sprintf(out, "\n"); + for (i = 0; i < VHCI_HC_PORTS; i++) { + struct vhci_device *vdev = &vhci->vhci_hcd_ss->vdev[i]; + + spin_lock(&vdev->ud.lock); + port_show_vhci(&out, HUB_SPEED_SUPER, + pdev_nr * VHCI_PORTS + VHCI_HC_PORTS + i, vdev); spin_unlock(&vdev->ud.lock); } @@ -92,8 +116,16 @@ static ssize_t status_show_not_ready(int pdev_nr, char *out) int i = 0; for (i = 0; i < VHCI_HC_PORTS; i++) { - out += sprintf(out, "%04u %03u ", - (pdev_nr * VHCI_HC_PORTS) + i, + out += sprintf(out, "hs %04u %03u ", + (pdev_nr * VHCI_PORTS) + i, + VDEV_ST_NOTASSIGNED); + out += sprintf(out, "000 00000000 0000000000000000 0-0"); + out += sprintf(out, "\n"); + } + + for (i = 0; i < VHCI_HC_PORTS; i++) { + out += sprintf(out, "ss %04u %03u ", + (pdev_nr * VHCI_PORTS) + VHCI_HC_PORTS + i, VDEV_ST_NOTASSIGNED); out += sprintf(out, "000 00000000 0000000000000000 0-0"); out += sprintf(out, "\n"); @@ -125,7 +157,7 @@ static ssize_t status_show(struct device *dev, int pdev_nr; out += sprintf(out, - "port sta spd dev socket local_busid\n"); + "hub port sta spd dev socket local_busid\n"); pdev_nr = status_name_to_id(attr->attr.name); if (pdev_nr < 0) @@ -141,15 +173,19 @@ static ssize_t nports_show(struct device *dev, struct device_attribute *attr, { char *s = out; - out += sprintf(out, "%d\n", VHCI_HC_PORTS * vhci_num_controllers); + /* + * Half the ports are for SPEED_HIGH and half for SPEED_SUPER, thus the * 2. + */ + out += sprintf(out, "%d\n", VHCI_PORTS * vhci_num_controllers); return out - s; } static DEVICE_ATTR_RO(nports); /* Sysfs entry to shutdown a virtual connection */ -static int vhci_port_disconnect(struct vhci_hcd *vhci, __u32 rhport) +static int vhci_port_disconnect(struct vhci_hcd *vhci_hcd, __u32 rhport) { - struct vhci_device *vdev = &vhci->vdev[rhport]; + struct vhci_device *vdev = &vhci_hcd->vdev[rhport]; + struct vhci *vhci = vhci_hcd->vhci; unsigned long flags; usbip_dbg_vhci_sysfs("enter\n"); @@ -195,6 +231,7 @@ static ssize_t store_detach(struct device *dev, struct device_attribute *attr, { __u32 port = 0, pdev_nr = 0, rhport = 0; struct usb_hcd *hcd; + struct vhci_hcd *vhci_hcd; int ret; if (kstrtoint(buf, 10, &port) < 0) @@ -206,13 +243,20 @@ static ssize_t store_detach(struct device *dev, struct device_attribute *attr, if (!valid_port(pdev_nr, rhport)) return -EINVAL; - hcd = platform_get_drvdata(*(vhci_pdevs + pdev_nr)); + hcd = platform_get_drvdata(vhcis[pdev_nr].pdev); if (hcd == NULL) { dev_err(dev, "port is not ready %u\n", port); return -EAGAIN; } - ret = vhci_port_disconnect(hcd_to_vhci(hcd), rhport); + usbip_dbg_vhci_sysfs("rhport %d\n", rhport); + + if ((port / VHCI_HC_PORTS) % 2) + vhci_hcd = hcd_to_vhci_hcd(hcd)->vhci->vhci_hcd_ss; + else + vhci_hcd = hcd_to_vhci_hcd(hcd)->vhci->vhci_hcd_hs; + + ret = vhci_port_disconnect(vhci_hcd, rhport); if (ret < 0) return -EINVAL; @@ -233,6 +277,7 @@ static int valid_args(__u32 pdev_nr, __u32 rhport, enum usb_device_speed speed) case USB_SPEED_FULL: case USB_SPEED_HIGH: case USB_SPEED_WIRELESS: + case USB_SPEED_SUPER: break; default: pr_err("Failed attach request for unsupported USB speed: %s\n", @@ -262,8 +307,9 @@ static ssize_t store_attach(struct device *dev, struct device_attribute *attr, int sockfd = 0; __u32 port = 0, pdev_nr = 0, rhport = 0, devid = 0, speed = 0; struct usb_hcd *hcd; - struct vhci_hcd *vhci; + struct vhci_hcd *vhci_hcd; struct vhci_device *vdev; + struct vhci *vhci; int err; unsigned long flags; @@ -287,13 +333,19 @@ static ssize_t store_attach(struct device *dev, struct device_attribute *attr, if (!valid_args(pdev_nr, rhport, speed)) return -EINVAL; - hcd = platform_get_drvdata(*(vhci_pdevs + pdev_nr)); + hcd = platform_get_drvdata(vhcis[pdev_nr].pdev); if (hcd == NULL) { dev_err(dev, "port %d is not ready\n", port); return -EAGAIN; } - vhci = hcd_to_vhci(hcd); - vdev = &vhci->vdev[rhport]; + + vhci_hcd = hcd_to_vhci_hcd(hcd); + vhci = vhci_hcd->vhci; + + if (speed == USB_SPEED_SUPER) + vdev = &vhci->vhci_hcd_ss->vdev[rhport]; + else + vdev = &vhci->vhci_hcd_hs->vdev[rhport]; /* Extract socket from fd. */ socket = sockfd_lookup(sockfd, &err); diff --git a/drivers/uwb/i1480/dfu/phy.c b/drivers/uwb/i1480/dfu/phy.c index 3b1a87de8e63..1ac8526bb689 100644 --- a/drivers/uwb/i1480/dfu/phy.c +++ b/drivers/uwb/i1480/dfu/phy.c @@ -126,6 +126,7 @@ int i1480_mpi_read(struct i1480 *i1480, u8 *data, u16 srcaddr, size_t size) dev_err(i1480->dev, "MPI-READ: command execution failed: %d\n", reply->bResultCode); result = -EIO; + goto out; } for (cnt = 0; cnt < size; cnt++) { if (reply->data[cnt].page != (srcaddr + cnt) >> 8) diff --git a/include/linux/device.h b/include/linux/device.h index 9ef518af5515..60ab00b13095 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -879,6 +879,8 @@ struct dev_links_info { * * @offline_disabled: If set, the device is permanently online. * @offline: Set after successful invocation of bus type's .offline(). + * @of_node_reused: Set if the device-tree node is shared with an ancestor + * device. * * At the lowest level, every device in a Linux system is represented by an * instance of struct device. The device structure contains the information @@ -966,6 +968,7 @@ struct device { bool offline_disabled:1; bool offline:1; + bool of_node_reused:1; }; static inline struct device *kobj_to_dev(struct kobject *kobj) @@ -1144,6 +1147,7 @@ extern int device_offline(struct device *dev); extern int device_online(struct device *dev); extern void set_primary_fwnode(struct device *dev, struct fwnode_handle *fwnode); extern void set_secondary_fwnode(struct device *dev, struct fwnode_handle *fwnode); +void device_set_of_node_from_dev(struct device *dev, const struct device *dev2); static inline int dev_num_vf(struct device *dev) { diff --git a/drivers/phy/ulpi_phy.h b/include/linux/phy/ulpi_phy.h index f2ebe490a4bc..f2ebe490a4bc 100644 --- a/drivers/phy/ulpi_phy.h +++ b/include/linux/phy/ulpi_phy.h diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index fbc22a39e7bc..1a4a4bacfae6 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -304,6 +304,7 @@ struct usb_gadget_ops { int (*udc_start)(struct usb_gadget *, struct usb_gadget_driver *); int (*udc_stop)(struct usb_gadget *); + void (*udc_set_speed)(struct usb_gadget *, enum usb_device_speed); struct usb_ep *(*match_ep)(struct usb_gadget *, struct usb_endpoint_descriptor *, struct usb_ss_ep_comp_descriptor *); @@ -352,6 +353,8 @@ struct usb_gadget_ops { * @deactivated: True if gadget is deactivated - in deactivated state it cannot * be connected. * @connected: True if gadget is connected. + * @lpm_capable: If the gadget max_speed is FULL or HIGH, this flag + * indicates that it supports LPM as per the LPM ECN & errata. * * Gadgets have a mostly-portable "gadget driver" implementing device * functions, handling all usb configurations and interfaces. Gadget @@ -404,6 +407,7 @@ struct usb_gadget { unsigned is_selfpowered:1; unsigned deactivated:1; unsigned connected:1; + unsigned lpm_capable:1; }; #define work_to_gadget(w) (container_of((w), struct usb_gadget, work)) diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index 50398b69ca44..a1f03ebfde47 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -565,9 +565,9 @@ extern void usb_ep0_reinit(struct usb_device *); ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8) #define EndpointRequest \ - ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8) + ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_ENDPOINT)<<8) #define EndpointOutRequest \ - ((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8) + ((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_ENDPOINT)<<8) /* class requests from the USB 2.0 hub spec, table 11-15 */ #define HUB_CLASS_REQ(dir, type, request) ((((dir) | (type)) << 8) | (request)) diff --git a/include/linux/usb/phy.h b/include/linux/usb/phy.h index 31a8068c42a5..299245105610 100644 --- a/include/linux/usb/phy.h +++ b/include/linux/usb/phy.h @@ -9,6 +9,7 @@ #ifndef __LINUX_USB_PHY_H #define __LINUX_USB_PHY_H +#include <linux/extcon.h> #include <linux/notifier.h> #include <linux/usb.h> @@ -85,6 +86,12 @@ struct usb_phy { struct usb_phy_io_ops *io_ops; void __iomem *io_priv; + /* to support extcon device */ + struct extcon_dev *edev; + struct extcon_dev *id_edev; + struct notifier_block vbus_nb; + struct notifier_block id_nb; + /* for notification of usb_phy_events */ struct atomic_notifier_head notifier; diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index ec78204964ab..ffe7487886ca 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -117,13 +117,13 @@ struct typec_altmode_desc { struct typec_altmode *typec_partner_register_altmode(struct typec_partner *partner, - struct typec_altmode_desc *desc); + const struct typec_altmode_desc *desc); struct typec_altmode *typec_plug_register_altmode(struct typec_plug *plug, - struct typec_altmode_desc *desc); + const struct typec_altmode_desc *desc); struct typec_altmode *typec_port_register_altmode(struct typec_port *port, - struct typec_altmode_desc *desc); + const struct typec_altmode_desc *desc); void typec_unregister_altmode(struct typec_altmode *altmode); struct typec_port *typec_altmode2port(struct typec_altmode *alt); @@ -190,6 +190,7 @@ struct typec_partner_desc { * @pr_set: Set Power Role * @vconn_set: Set VCONN Role * @activate_mode: Enter/exit given Alternate Mode + * @port_type_set: Set port type * * Static capabilities of a single USB Type-C port. */ @@ -214,6 +215,9 @@ struct typec_capability { int (*activate_mode)(const struct typec_capability *, int mode, int activate); + int (*port_type_set)(const struct typec_capability *, + enum typec_port_type); + }; /* Specific to try_role(). Indicates the user want's to clear the preference. */ diff --git a/include/uapi/linux/usb/functionfs.h b/include/uapi/linux/usb/functionfs.h index 062606f02309..f913d08ab7bb 100644 --- a/include/uapi/linux/usb/functionfs.h +++ b/include/uapi/linux/usb/functionfs.h @@ -275,13 +275,14 @@ struct usb_functionfs_event { #define FUNCTIONFS_INTERFACE_REVMAP _IO('g', 128) /* - * Returns real bEndpointAddress of an endpoint. If function is not - * active returns -ENODEV. + * Returns real bEndpointAddress of an endpoint. If endpoint shuts down + * during the call, returns -ESHUTDOWN. */ #define FUNCTIONFS_ENDPOINT_REVMAP _IO('g', 129) /* - * Returns endpoint descriptor. If function is not active returns -ENODEV. + * Returns endpoint descriptor. If endpoint shuts down during the call, + * returns -ESHUTDOWN. */ #define FUNCTIONFS_ENDPOINT_DESC _IOR('g', 130, \ struct usb_endpoint_descriptor) diff --git a/include/uapi/linux/usbdevice_fs.h b/include/uapi/linux/usbdevice_fs.h index a8653a6f40df..0bbfd4abd2e3 100644 --- a/include/uapi/linux/usbdevice_fs.h +++ b/include/uapi/linux/usbdevice_fs.h @@ -156,6 +156,11 @@ struct usbdevfs_streams { unsigned char eps[0]; }; +/* + * USB_SPEED_* values returned by USBDEVFS_GET_SPEED are defined in + * linux/usb/ch9.h + */ + #define USBDEVFS_CONTROL _IOWR('U', 0, struct usbdevfs_ctrltransfer) #define USBDEVFS_CONTROL32 _IOWR('U', 0, struct usbdevfs_ctrltransfer32) #define USBDEVFS_BULK _IOWR('U', 2, struct usbdevfs_bulktransfer) @@ -190,5 +195,6 @@ struct usbdevfs_streams { #define USBDEVFS_ALLOC_STREAMS _IOR('U', 28, struct usbdevfs_streams) #define USBDEVFS_FREE_STREAMS _IOR('U', 29, struct usbdevfs_streams) #define USBDEVFS_DROP_PRIVILEGES _IOW('U', 30, __u32) +#define USBDEVFS_GET_SPEED _IO('U', 31) #endif /* _UAPI_LINUX_USBDEVICE_FS_H */ diff --git a/tools/usb/testusb.c b/tools/usb/testusb.c index 0692d99b6d8f..2d89b5f686b1 100644 --- a/tools/usb/testusb.c +++ b/tools/usb/testusb.c @@ -387,15 +387,17 @@ int main (int argc, char **argv) /* pick defaults that works with all speeds, without short packets. * * Best per-frame data rates: - * high speed, bulk 512 * 13 * 8 = 53248 - * interrupt 1024 * 3 * 8 = 24576 - * full speed, bulk/intr 64 * 19 = 1216 - * interrupt 64 * 1 = 64 - * low speed, interrupt 8 * 1 = 8 + * super speed,bulk 1024 * 16 * 8 = 131072 + * interrupt 1024 * 3 * 8 = 24576 + * high speed, bulk 512 * 13 * 8 = 53248 + * interrupt 1024 * 3 * 8 = 24576 + * full speed, bulk/intr 64 * 19 = 1216 + * interrupt 64 * 1 = 64 + * low speed, interrupt 8 * 1 = 8 */ param.iterations = 1000; param.length = 1024; - param.vary = 512; + param.vary = 1024; param.sglen = 32; /* for easy use when hotplugging */ @@ -457,7 +459,7 @@ usage: "\t-c iterations default 1000\n" "\t-s transfer length default 1024\n" "\t-g sglen default 32\n" - "\t-v vary default 512\n", + "\t-v vary default 1024\n", argv[0]); return 1; } diff --git a/tools/usb/usbip/libsrc/vhci_driver.c b/tools/usb/usbip/libsrc/vhci_driver.c index f659c146cdc8..9bd2cd71645d 100644 --- a/tools/usb/usbip/libsrc/vhci_driver.c +++ b/tools/usb/usbip/libsrc/vhci_driver.c @@ -7,6 +7,7 @@ #include <limits.h> #include <netdb.h> #include <libudev.h> +#include <dirent.h> #include "sysfs_utils.h" #undef PROGNAME @@ -35,18 +36,11 @@ err: return NULL; } - - static int parse_status(const char *value) { int ret = 0; char *c; - - for (int i = 0; i < vhci_driver->nports; i++) - memset(&vhci_driver->idev[i], 0, sizeof(vhci_driver->idev[i])); - - /* skip a header line */ c = strchr(value, '\n'); if (!c) @@ -57,9 +51,11 @@ static int parse_status(const char *value) int port, status, speed, devid; unsigned long socket; char lbusid[SYSFS_BUS_ID_SIZE]; + struct usbip_imported_device *idev; + char hub[3]; - ret = sscanf(c, "%d %d %d %x %lx %31s\n", - &port, &status, &speed, + ret = sscanf(c, "%2s %d %d %d %x %lx %31s\n", + hub, &port, &status, &speed, &devid, &socket, lbusid); if (ret < 5) { @@ -67,34 +63,36 @@ static int parse_status(const char *value) BUG(); } - dbg("port %d status %d speed %d devid %x", - port, status, speed, devid); + dbg("hub %s port %d status %d speed %d devid %x", + hub, port, status, speed, devid); dbg("socket %lx lbusid %s", socket, lbusid); - /* if a device is connected, look at it */ - { - struct usbip_imported_device *idev = &vhci_driver->idev[port]; + idev = &vhci_driver->idev[port]; + memset(idev, 0, sizeof(*idev)); + + if (strncmp("hs", hub, 2) == 0) + idev->hub = HUB_SPEED_HIGH; + else /* strncmp("ss", hub, 2) == 0 */ + idev->hub = HUB_SPEED_SUPER; - idev->port = port; - idev->status = status; + idev->port = port; + idev->status = status; - idev->devid = devid; + idev->devid = devid; - idev->busnum = (devid >> 16); - idev->devnum = (devid & 0x0000ffff); + idev->busnum = (devid >> 16); + idev->devnum = (devid & 0x0000ffff); - if (idev->status != VDEV_ST_NULL - && idev->status != VDEV_ST_NOTASSIGNED) { - idev = imported_device_init(idev, lbusid); - if (!idev) { - dbg("imported_device_init failed"); - return -1; - } + if (idev->status != VDEV_ST_NULL + && idev->status != VDEV_ST_NOTASSIGNED) { + idev = imported_device_init(idev, lbusid); + if (!idev) { + dbg("imported_device_init failed"); + return -1; } } - /* go to the next line */ c = strchr(c, '\n'); if (!c) @@ -107,18 +105,33 @@ static int parse_status(const char *value) return 0; } +#define MAX_STATUS_NAME 16 + static int refresh_imported_device_list(void) { const char *attr_status; + char status[MAX_STATUS_NAME+1] = "status"; + int i, ret; - attr_status = udev_device_get_sysattr_value(vhci_driver->hc_device, - "status"); - if (!attr_status) { - err("udev_device_get_sysattr_value failed"); - return -1; + for (i = 0; i < vhci_driver->ncontrollers; i++) { + if (i > 0) + snprintf(status, sizeof(status), "status.%d", i); + + attr_status = udev_device_get_sysattr_value(vhci_driver->hc_device, + status); + if (!attr_status) { + err("udev_device_get_sysattr_value failed"); + return -1; + } + + dbg("controller %d", i); + + ret = parse_status(attr_status); + if (ret != 0) + return ret; } - return parse_status(attr_status); + return 0; } static int get_nports(void) @@ -134,6 +147,33 @@ static int get_nports(void) return (int)strtoul(attr_nports, NULL, 10); } +static int vhci_hcd_filter(const struct dirent *dirent) +{ + return strcmp(dirent->d_name, "vhci_hcd") >= 0; +} + +static int get_ncontrollers(void) +{ + struct dirent **namelist; + struct udev_device *platform; + int n; + + platform = udev_device_get_parent(vhci_driver->hc_device); + if (platform == NULL) + return -1; + + n = scandir(udev_device_get_syspath(platform), &namelist, vhci_hcd_filter, NULL); + if (n < 0) + err("scandir failed"); + else { + for (int i = 0; i < n; i++) + free(namelist[i]); + free(namelist); + } + + return n; +} + /* * Read the given port's record. * @@ -213,16 +253,31 @@ int usbip_vhci_driver_open(void) vhci_driver->hc_device = udev_device_new_from_subsystem_sysname(udev_context, USBIP_VHCI_BUS_TYPE, - USBIP_VHCI_DRV_NAME); + USBIP_VHCI_DEVICE_NAME); if (!vhci_driver->hc_device) { err("udev_device_new_from_subsystem_sysname failed"); goto err; } vhci_driver->nports = get_nports(); - dbg("available ports: %d", vhci_driver->nports); + if (vhci_driver->nports <= 0) { + err("no available ports"); + goto err; + } else if (vhci_driver->nports > MAXNPORT) { + err("port number exceeds %d", MAXNPORT); + goto err; + } + + vhci_driver->ncontrollers = get_ncontrollers(); + dbg("available controllers: %d", vhci_driver->ncontrollers); + + if (vhci_driver->ncontrollers <=0) { + err("no available usb controllers"); + goto err; + } + if (refresh_imported_device_list()) goto err; @@ -270,11 +325,15 @@ err: } -int usbip_vhci_get_free_port(void) +int usbip_vhci_get_free_port(uint32_t speed) { for (int i = 0; i < vhci_driver->nports; i++) { + if (speed == USB_SPEED_SUPER && + vhci_driver->idev[i].hub != HUB_SPEED_SUPER) + continue; + if (vhci_driver->idev[i].status == VDEV_ST_NULL) - return i; + return vhci_driver->idev[i].port; } return -1; diff --git a/tools/usb/usbip/libsrc/vhci_driver.h b/tools/usb/usbip/libsrc/vhci_driver.h index fa2316cf2cac..4898d3bafb10 100644 --- a/tools/usb/usbip/libsrc/vhci_driver.h +++ b/tools/usb/usbip/libsrc/vhci_driver.h @@ -11,9 +11,16 @@ #include "usbip_common.h" #define USBIP_VHCI_BUS_TYPE "platform" +#define USBIP_VHCI_DEVICE_NAME "vhci_hcd.0" #define MAXNPORT 128 +enum hub_speed { + HUB_SPEED_HIGH = 0, + HUB_SPEED_SUPER, +}; + struct usbip_imported_device { + enum hub_speed hub; uint8_t port; uint32_t status; @@ -31,6 +38,7 @@ struct usbip_vhci_driver { /* /sys/devices/platform/vhci_hcd */ struct udev_device *hc_device; + int ncontrollers; int nports; struct usbip_imported_device idev[MAXNPORT]; }; @@ -44,7 +52,7 @@ void usbip_vhci_driver_close(void); int usbip_vhci_refresh_device_list(void); -int usbip_vhci_get_free_port(void); +int usbip_vhci_get_free_port(uint32_t speed); int usbip_vhci_attach_device2(uint8_t port, int sockfd, uint32_t devid, uint32_t speed); diff --git a/tools/usb/usbip/src/usbip_attach.c b/tools/usb/usbip/src/usbip_attach.c index 70a6b507fb62..6e89768ffe30 100644 --- a/tools/usb/usbip/src/usbip_attach.c +++ b/tools/usb/usbip/src/usbip_attach.c @@ -94,6 +94,7 @@ static int import_device(int sockfd, struct usbip_usb_device *udev) { int rc; int port; + uint32_t speed = udev->speed; rc = usbip_vhci_driver_open(); if (rc < 0) { @@ -101,13 +102,15 @@ static int import_device(int sockfd, struct usbip_usb_device *udev) return -1; } - port = usbip_vhci_get_free_port(); + port = usbip_vhci_get_free_port(speed); if (port < 0) { err("no free port"); usbip_vhci_driver_close(); return -1; } + dbg("got free port %d", port); + rc = usbip_vhci_attach_device(port, sockfd, udev->busnum, udev->devnum, udev->speed); if (rc < 0) { |