mirror of
https://github.com/torvalds/linux.git
synced 2026-11-06 21:58:01 +01:00
USB / Thunderbolt changes for 7.4-rc1
Here is the big set of USB and Thunderbolt changes for 7.4-rc1. Included in here are: - thunderbolt driver updates and fixes - lots of dt schema conversions - usb gadget driver fixes (and a new maintainer!) - usb core fixups with some interactions with the driver core - usb serial updates and fixes for scan-found "issues" - xhci driver updates - typec driver updates - dwc3 driver updates - lots of usb gadget driver fixes for syzbot found "problems" - lots of other small USB driver fixes for randomly reported problems. All of these have been in linux-next with no reported issues. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- iG0EABECAC0WIQT0tgzFv3jCIUoxPcsxR9QN2y37KQUCatoSZA8cZ3JlZ0Brcm9h aC5jb20ACgkQMUfUDdst+ylC5ACgyKlacs/i8gEGMvrRxDMTFDvuO3gAoMDvwPsy Vf+NM23wH/Ptg3DJkynD =Y981 -----END PGP SIGNATURE----- Merge tag 'usb-7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb Pull USB / Thunderbolt update from Greg KH: "Here is the big set of USB and Thunderbolt changes for 7.4-rc1. Included in here are: - thunderbolt driver updates and fixes - lots of dt schema conversions - usb gadget driver fixes (and a new maintainer!) - usb core fixups with some interactions with the driver core - usb serial updates and fixes for scan-found "issues" - xhci driver updates - typec driver updates - dwc3 driver updates - lots of usb gadget driver fixes for syzbot found "problems" - lots of other small USB driver fixes for randomly reported problems. All of these have been in linux-next with no reported issues" * tag 'usb-7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (158 commits) usb: gadget: dummy_hcd: prevent unbind via sysfs usb: gadget: dummy_hcd: add tick_us module parameter for timer-rate tuning usb: gadget: dummy_hcd: set no_sg_constraint only on the SuperSpeed bus usb: gadget: dummy_hcd: fix SuperSpeed ep0 maxpacket usb: gadget: fix refcount underflow in gadgetfs_bind() error path USB: core: document purpose of interface parent reference MAINTAINERS: add Neill Kapron as USB Gadget maintainer USB: Documentation: Update description of completion callback context USB: serial: mxuport: support RS485 mode configuration USB: serial: mxuport: handle SEND_NEXT transmit flow control USB: serial: add generic write control callbacks USB: serial: mxuport: add MUX50U-based device support USB: serial: mxuport: clean up firmware version handling USB: serial: mxuport: clean up product ID definitions and device table USB: serial: sierra: fix urb leak on write completion usb: typec: ucsi_ccg: Add retry mechanism in ccg_read usb: host: xhci-tegra: Add Tegra264 XHCI support usb: host: xhci-tegra: Skip MBOX MSG_ENABLED on Tegra264 dt-bindings: usb: Add Tegra264 XUSB host support usb: xhci-pci: Add TUSB73x0 definitions ...
This commit is contained in:
commit
168151cdb0
109
Documentation/devicetree/bindings/dma/ti/ti,cppi41.yaml
Normal file
109
Documentation/devicetree/bindings/dma/ti/ti,cppi41.yaml
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/dma/ti/ti,cppi41.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: TI CPPI41 DMA Controller
|
||||
|
||||
maintainers:
|
||||
- Sebastian Andrzej Siewior <bigeasy@linutronix.de>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- ti,am3359-cppi41
|
||||
- ti,da830-cppi41
|
||||
|
||||
reg:
|
||||
minItems: 3
|
||||
maxItems: 4
|
||||
|
||||
reg-names:
|
||||
minItems: 3
|
||||
maxItems: 4
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
interrupt-names:
|
||||
const: glue
|
||||
|
||||
"#dma-cells":
|
||||
const: 2
|
||||
|
||||
dma-channels:
|
||||
description: Number of DMA channels supported.
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
|
||||
dma-requests:
|
||||
description: Number of DMA requests supported.
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
|
||||
power-domains:
|
||||
maxItems: 1
|
||||
|
||||
"#dma-channels":
|
||||
description: Deprecated, use dma-channels instead.
|
||||
deprecated: true
|
||||
|
||||
"#dma-requests":
|
||||
description: Deprecated, use dma-requests instead.
|
||||
deprecated: true
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- reg-names
|
||||
- "#dma-cells"
|
||||
- dma-channels
|
||||
|
||||
allOf:
|
||||
- $ref: /schemas/dma/dma-controller.yaml#
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: ti,am3359-cppi41
|
||||
then:
|
||||
properties:
|
||||
reg:
|
||||
minItems: 4
|
||||
reg-names:
|
||||
items:
|
||||
- const: glue
|
||||
- const: controller
|
||||
- const: scheduler
|
||||
- const: queuemgr
|
||||
power-domains: false
|
||||
required:
|
||||
- interrupt-names
|
||||
- interrupts
|
||||
- dma-requests
|
||||
else:
|
||||
properties:
|
||||
reg:
|
||||
maxItems: 3
|
||||
reg-names:
|
||||
items:
|
||||
- const: controller
|
||||
- const: scheduler
|
||||
- const: queuemgr
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
dma-controller@7402000 {
|
||||
compatible = "ti,am3359-cppi41";
|
||||
reg = <0x47400000 0x1000>,
|
||||
<0x47402000 0x1000>,
|
||||
<0x47403000 0x1000>,
|
||||
<0x47404000 0x4000>;
|
||||
reg-names = "glue", "controller", "scheduler", "queuemgr";
|
||||
interrupts = <17>;
|
||||
interrupt-names = "glue";
|
||||
#dma-cells = <2>;
|
||||
dma-channels = <30>;
|
||||
dma-requests = <256>;
|
||||
};
|
||||
49
Documentation/devicetree/bindings/phy/ti,am335x-usb-phy.yaml
Normal file
49
Documentation/devicetree/bindings/phy/ti,am335x-usb-phy.yaml
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/phy/ti,am335x-usb-phy.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: TI AM335x USB PHY
|
||||
|
||||
maintainers:
|
||||
- Sebastian Andrzej Siewior <bigeasy@linutronix.de>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: ti,am335x-usb-phy
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
reg-names:
|
||||
const: phy
|
||||
|
||||
ti,ctrl_mod:
|
||||
$ref: /schemas/types.yaml#/definitions/phandle
|
||||
description: Reference to the reset module node.
|
||||
|
||||
"#phy-cells":
|
||||
const: 0
|
||||
|
||||
vcc-supply:
|
||||
description: Phandle to the regulator that provides power to the PHY.
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- reg-names
|
||||
- ti,ctrl_mod
|
||||
- "#phy-cells"
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
usb-phy@47401300 {
|
||||
compatible = "ti,am335x-usb-phy";
|
||||
reg = <0x47401300 0x100>;
|
||||
reg-names = "phy";
|
||||
ti,ctrl_mod = <&ctrl_mod>;
|
||||
#phy-cells = <0>;
|
||||
};
|
||||
|
|
@ -1,200 +0,0 @@
|
|||
AM33xx MUSB
|
||||
~~~~~~~~~~~~~~~
|
||||
- compatible: ti,am33xx-usb
|
||||
- reg: offset and length of the usbss register sets
|
||||
- ti,hwmods : must be "usb_otg_hs"
|
||||
|
||||
The glue layer contains multiple child nodes. It is required to have
|
||||
at least a control module node, USB node and a PHY node. The second USB
|
||||
node and its PHY node are optional. The DMA node is also optional.
|
||||
|
||||
Reset module
|
||||
~~~~~~~~~~~~
|
||||
- compatible: ti,am335x-usb-ctrl-module
|
||||
- reg: offset and length of the "USB control registers" in the "Control
|
||||
Module" block. A second offset and length for the USB wake up control
|
||||
in the same memory block.
|
||||
- reg-names: "phy_ctrl" for the "USB control registers" and "wakeup" for
|
||||
the USB wake up control register.
|
||||
|
||||
USB PHY
|
||||
~~~~~~~
|
||||
compatible: ti,am335x-usb-phy
|
||||
reg: offset and length of the "USB PHY" register space
|
||||
ti,ctrl_mod: reference to the "reset module" node
|
||||
reg-names: phy
|
||||
The PHY should have a "phy" alias numbered properly in the alias
|
||||
node.
|
||||
|
||||
USB
|
||||
~~~
|
||||
- compatible: ti,musb-am33xx
|
||||
- reg: offset and length of "USB Controller Registers", and offset and
|
||||
length of "USB Core" register space.
|
||||
- reg-names: control for the ""USB Controller Registers" and "mc" for
|
||||
"USB Core" register space
|
||||
- interrupts: USB interrupt number
|
||||
- interrupt-names: mc
|
||||
- dr_mode: Should be one of "host", "peripheral" or "otg".
|
||||
- mentor,multipoint: Should be "1" indicating the musb controller supports
|
||||
multipoint. This is a MUSB configuration-specific setting.
|
||||
- mentor,num-eps: Specifies the number of endpoints. This is also a
|
||||
MUSB configuration-specific setting. Should be set to "16"
|
||||
- mentor,ram-bits: Specifies the ram address size. Should be set to "12"
|
||||
- mentor,power: Should be "500". This signifies the controller can supply up to
|
||||
500mA when operating in host mode.
|
||||
- phys: reference to the USB phy
|
||||
- dmas: specifies the dma channels
|
||||
- dma-names: specifies the names of the channels. Use "rxN" for receive
|
||||
and "txN" for transmit endpoints. N specifies the endpoint number.
|
||||
|
||||
The controller should have an "usb" alias numbered properly in the alias
|
||||
node.
|
||||
|
||||
DMA
|
||||
~~~
|
||||
- compatible: ti,am3359-cppi41
|
||||
- reg: offset and length of the following register spaces: USBSS, USB
|
||||
CPPI DMA Controller, USB CPPI DMA Scheduler, USB Queue Manager
|
||||
- reg-names: glue, controller, scheduler, queuemgr
|
||||
- #dma-cells: should be set to 2. The first number represents the
|
||||
endpoint number (0 … 14 for endpoints 1 … 15 on instance 0 and 15 … 29
|
||||
for endpoints 1 … 15 on instance 1). The second number is 0 for RX and
|
||||
1 for TX transfers.
|
||||
- dma-channels: should be set to 30 representing the 15 endpoints for
|
||||
each USB instance.
|
||||
- #dma-channels: deprecated
|
||||
|
||||
Example:
|
||||
~~~~~~~~
|
||||
The following example contains all the nodes as used on am335x-evm:
|
||||
|
||||
aliases {
|
||||
usb0 = &usb0;
|
||||
usb1 = &usb1;
|
||||
phy0 = &usb0_phy;
|
||||
phy1 = &usb1_phy;
|
||||
};
|
||||
|
||||
usb: usb@47400000 {
|
||||
compatible = "ti,am33xx-usb";
|
||||
reg = <0x47400000 0x1000>;
|
||||
ranges;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
ti,hwmods = "usb_otg_hs";
|
||||
|
||||
ctrl_mod: control@44e10000 {
|
||||
compatible = "ti,am335x-usb-ctrl-module";
|
||||
reg = <0x44e10620 0x10
|
||||
0x44e10648 0x4>;
|
||||
reg-names = "phy_ctrl", "wakeup";
|
||||
};
|
||||
|
||||
usb0_phy: usb-phy@47401300 {
|
||||
compatible = "ti,am335x-usb-phy";
|
||||
reg = <0x47401300 0x100>;
|
||||
reg-names = "phy";
|
||||
ti,ctrl_mod = <&ctrl_mod>;
|
||||
#phy-cells = <0>;
|
||||
};
|
||||
|
||||
usb0: usb@47401000 {
|
||||
compatible = "ti,musb-am33xx";
|
||||
reg = <0x47401400 0x400
|
||||
0x47401000 0x200>;
|
||||
reg-names = "mc", "control";
|
||||
|
||||
interrupts = <18>;
|
||||
interrupt-names = "mc";
|
||||
dr_mode = "otg"
|
||||
mentor,multipoint = <1>;
|
||||
mentor,num-eps = <16>;
|
||||
mentor,ram-bits = <12>;
|
||||
mentor,power = <500>;
|
||||
phys = <&usb0_phy>;
|
||||
|
||||
dmas = <&cppi41dma 0 0 &cppi41dma 1 0
|
||||
&cppi41dma 2 0 &cppi41dma 3 0
|
||||
&cppi41dma 4 0 &cppi41dma 5 0
|
||||
&cppi41dma 6 0 &cppi41dma 7 0
|
||||
&cppi41dma 8 0 &cppi41dma 9 0
|
||||
&cppi41dma 10 0 &cppi41dma 11 0
|
||||
&cppi41dma 12 0 &cppi41dma 13 0
|
||||
&cppi41dma 14 0 &cppi41dma 0 1
|
||||
&cppi41dma 1 1 &cppi41dma 2 1
|
||||
&cppi41dma 3 1 &cppi41dma 4 1
|
||||
&cppi41dma 5 1 &cppi41dma 6 1
|
||||
&cppi41dma 7 1 &cppi41dma 8 1
|
||||
&cppi41dma 9 1 &cppi41dma 10 1
|
||||
&cppi41dma 11 1 &cppi41dma 12 1
|
||||
&cppi41dma 13 1 &cppi41dma 14 1>;
|
||||
dma-names =
|
||||
"rx1", "rx2", "rx3", "rx4", "rx5", "rx6", "rx7",
|
||||
"rx8", "rx9", "rx10", "rx11", "rx12", "rx13",
|
||||
"rx14", "rx15",
|
||||
"tx1", "tx2", "tx3", "tx4", "tx5", "tx6", "tx7",
|
||||
"tx8", "tx9", "tx10", "tx11", "tx12", "tx13",
|
||||
"tx14", "tx15";
|
||||
};
|
||||
|
||||
usb1_phy: usb-phy@47401b00 {
|
||||
compatible = "ti,am335x-usb-phy";
|
||||
reg = <0x47401b00 0x100>;
|
||||
reg-names = "phy";
|
||||
ti,ctrl_mod = <&ctrl_mod>;
|
||||
#phy-cells = <0>;
|
||||
};
|
||||
|
||||
usb1: usb@47401800 {
|
||||
compatible = "ti,musb-am33xx";
|
||||
reg = <0x47401c00 0x400
|
||||
0x47401800 0x200>;
|
||||
reg-names = "mc", "control";
|
||||
interrupts = <19>;
|
||||
interrupt-names = "mc";
|
||||
dr_mode = "host"
|
||||
mentor,multipoint = <1>;
|
||||
mentor,num-eps = <16>;
|
||||
mentor,ram-bits = <12>;
|
||||
mentor,power = <500>;
|
||||
phys = <&usb1_phy>;
|
||||
|
||||
dmas = <&cppi41dma 15 0 &cppi41dma 16 0
|
||||
&cppi41dma 17 0 &cppi41dma 18 0
|
||||
&cppi41dma 19 0 &cppi41dma 20 0
|
||||
&cppi41dma 21 0 &cppi41dma 22 0
|
||||
&cppi41dma 23 0 &cppi41dma 24 0
|
||||
&cppi41dma 25 0 &cppi41dma 26 0
|
||||
&cppi41dma 27 0 &cppi41dma 28 0
|
||||
&cppi41dma 29 0 &cppi41dma 15 1
|
||||
&cppi41dma 16 1 &cppi41dma 17 1
|
||||
&cppi41dma 18 1 &cppi41dma 19 1
|
||||
&cppi41dma 20 1 &cppi41dma 21 1
|
||||
&cppi41dma 22 1 &cppi41dma 23 1
|
||||
&cppi41dma 24 1 &cppi41dma 25 1
|
||||
&cppi41dma 26 1 &cppi41dma 27 1
|
||||
&cppi41dma 28 1 &cppi41dma 29 1>;
|
||||
dma-names =
|
||||
"rx1", "rx2", "rx3", "rx4", "rx5", "rx6", "rx7",
|
||||
"rx8", "rx9", "rx10", "rx11", "rx12", "rx13",
|
||||
"rx14", "rx15",
|
||||
"tx1", "tx2", "tx3", "tx4", "tx5", "tx6", "tx7",
|
||||
"tx8", "tx9", "tx10", "tx11", "tx12", "tx13",
|
||||
"tx14", "tx15";
|
||||
};
|
||||
|
||||
cppi41dma: dma-controller@7402000 {
|
||||
compatible = "ti,am3359-cppi41";
|
||||
reg = <0x47400000 0x1000
|
||||
0x47402000 0x1000
|
||||
0x47403000 0x1000
|
||||
0x47404000 0x4000>;
|
||||
reg-names = "glue", "controller", "scheduler", "queuemgr";
|
||||
interrupts = <17>;
|
||||
interrupt-names = "glue";
|
||||
#dma-cells = <2>;
|
||||
dma-channels = <30>;
|
||||
dma-requests = <256>;
|
||||
};
|
||||
};
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
TI DA8xx MUSB
|
||||
~~~~~~~~~~~~~
|
||||
For DA8xx/OMAP-L1x/AM17xx/AM18xx platforms.
|
||||
|
||||
Required properties:
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
- compatible : Should be set to "ti,da830-musb".
|
||||
|
||||
- reg: Offset and length of the USB controller register set.
|
||||
|
||||
- interrupts: The USB interrupt number.
|
||||
|
||||
- interrupt-names: Should be set to "mc".
|
||||
|
||||
- dr_mode: The USB operation mode. Should be one of "host", "peripheral" or "otg".
|
||||
|
||||
- phys: Phandle for the PHY device
|
||||
|
||||
- phy-names: Should be "usb-phy"
|
||||
|
||||
- dmas: specifies the dma channels
|
||||
|
||||
- dma-names: specifies the names of the channels. Use "rxN" for receive
|
||||
and "txN" for transmit endpoints. N specifies the endpoint number.
|
||||
|
||||
Optional properties:
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
- vbus-supply: Phandle to a regulator providing the USB bus power.
|
||||
|
||||
DMA
|
||||
~~~
|
||||
- compatible: ti,da830-cppi41
|
||||
- reg: offset and length of the following register spaces: CPPI DMA Controller,
|
||||
CPPI DMA Scheduler, Queue Manager
|
||||
- reg-names: "controller", "scheduler", "queuemgr"
|
||||
- #dma-cells: should be set to 2. The first number represents the
|
||||
channel number (0 … 3 for endpoints 1 … 4).
|
||||
The second number is 0 for RX and 1 for TX transfers.
|
||||
- dma-channels: should be set to 4 representing the 4 endpoints.
|
||||
- #dma-channels: deprecated
|
||||
|
||||
Example:
|
||||
usb_phy: usb-phy {
|
||||
compatible = "ti,da830-usb-phy";
|
||||
#phy-cells = <0>;
|
||||
};
|
||||
usb0: usb@200000 {
|
||||
compatible = "ti,da830-musb";
|
||||
reg = <0x00200000 0x1000>;
|
||||
ranges;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
interrupts = <58>;
|
||||
interrupt-names = "mc";
|
||||
|
||||
dr_mode = "host";
|
||||
vbus-supply = <&usb_vbus>;
|
||||
phys = <&usb_phy 0>;
|
||||
phy-names = "usb-phy";
|
||||
|
||||
dmas = <&cppi41dma 0 0 &cppi41dma 1 0
|
||||
&cppi41dma 2 0 &cppi41dma 3 0
|
||||
&cppi41dma 0 1 &cppi41dma 1 1
|
||||
&cppi41dma 2 1 &cppi41dma 3 1>;
|
||||
dma-names =
|
||||
"rx1", "rx2", "rx3", "rx4",
|
||||
"tx1", "tx2", "tx3", "tx4";
|
||||
|
||||
|
||||
cppi41dma: dma-controller@201000 {
|
||||
compatible = "ti,da830-cppi41";
|
||||
reg = <0x201000 0x1000
|
||||
0x202000 0x1000
|
||||
0x204000 0x4000>;
|
||||
reg-names = "controller", "scheduler", "queuemgr";
|
||||
interrupts = <58>;
|
||||
#dma-cells = <2>;
|
||||
dma-channels = <4>;
|
||||
};
|
||||
|
||||
};
|
||||
|
|
@ -55,6 +55,10 @@ properties:
|
|||
- rockchip,rk3588-ehci
|
||||
- snps,hsdk-v1.0-ehci
|
||||
- socionext,uniphier-ehci
|
||||
- st,stm32mp13-ehci
|
||||
- st,stm32mp15-ehci
|
||||
- st,stm32mp23-ehci
|
||||
- st,stm32mp25-ehci
|
||||
- const: generic-ehci
|
||||
- items:
|
||||
- enum:
|
||||
|
|
@ -80,6 +84,9 @@ properties:
|
|||
minItems: 1
|
||||
maxItems: 2
|
||||
|
||||
access-controllers:
|
||||
maxItems: 1
|
||||
|
||||
dma-coherent: true
|
||||
|
||||
interrupts:
|
||||
|
|
@ -193,6 +200,23 @@ allOf:
|
|||
else:
|
||||
properties:
|
||||
clock-names: false
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- st,stm32mp23-ehci
|
||||
- st,stm32mp25-ehci
|
||||
then:
|
||||
properties:
|
||||
clocks:
|
||||
items:
|
||||
- description: PHY clock
|
||||
- description: Bus clock
|
||||
|
||||
required:
|
||||
- access-controllers
|
||||
- clocks
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,10 @@ properties:
|
|||
- ingenic,jz4740-ohci
|
||||
- rockchip,rk3588-ohci
|
||||
- snps,hsdk-v1.0-ohci
|
||||
- st,stm32mp13-ohci
|
||||
- st,stm32mp15-ohci
|
||||
- st,stm32mp23-ohci
|
||||
- st,stm32mp25-ohci
|
||||
- const: generic-ohci
|
||||
- enum:
|
||||
- generic-ohci
|
||||
|
|
@ -65,6 +69,9 @@ properties:
|
|||
reg:
|
||||
maxItems: 1
|
||||
|
||||
access-controllers:
|
||||
maxItems: 1
|
||||
|
||||
dma-coherent: true
|
||||
|
||||
interrupts:
|
||||
|
|
@ -213,6 +220,23 @@ allOf:
|
|||
clocks:
|
||||
minItems: 1
|
||||
maxItems: 3
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- st,stm32mp23-ohci
|
||||
- st,stm32mp25-ohci
|
||||
then:
|
||||
properties:
|
||||
clocks:
|
||||
items:
|
||||
- description: PHY clock
|
||||
- description: Bus clock
|
||||
|
||||
required:
|
||||
- access-controllers
|
||||
- clocks
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ properties:
|
|||
- enum:
|
||||
- marvell,armada-375-xhci
|
||||
- marvell,armada-380-xhci
|
||||
- description: Armada 37xx/8k SoCs
|
||||
items:
|
||||
- items:
|
||||
- enum:
|
||||
- axiado,ax3005-xhci
|
||||
- marvell,armada3700-xhci
|
||||
- marvell,armada-8k-xhci
|
||||
- const: generic-xhci
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ properties:
|
|||
- nxp,cbdtu02043
|
||||
- onnn,fsusb42
|
||||
- onnn,fsusb43l10x
|
||||
- onnn,fusb340
|
||||
- pericom,pi3usb102
|
||||
- ti,tmuxhs4212
|
||||
- const: gpio-sbu-mux
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ description: |
|
|||
|
||||
properties:
|
||||
compatible:
|
||||
const: nvidia,tegra234-xusb
|
||||
enum:
|
||||
- nvidia,tegra234-xusb
|
||||
- nvidia,tegra264-xusb
|
||||
|
||||
reg:
|
||||
items:
|
||||
|
|
@ -43,6 +45,7 @@ properties:
|
|||
- description: USB wake event 4
|
||||
- description: USB wake event 5
|
||||
- description: USB wake event 6
|
||||
- description: USB wake event 7
|
||||
description: |
|
||||
The first two interrupts are required for the USB host controller. The
|
||||
remaining USB wake event interrupts are optional. Each USB wake event is
|
||||
|
|
@ -50,10 +53,9 @@ properties:
|
|||
platform. The USB host controller can function even if no wake-up events
|
||||
are defined. The USB wake event interrupts are handled by the Tegra PMC;
|
||||
hence, the interrupt controller for these is the PMC and the interrupt
|
||||
IDs correspond to the PMC wake event IDs. A complete list of wake event
|
||||
IDs is provided below, and this information is also present in the Tegra
|
||||
TRM document.
|
||||
IDs correspond to the PMC wake event IDs.
|
||||
|
||||
Tegra234 PMC wake events:
|
||||
PMC wake-up 76 for USB3 port 0 wakeup
|
||||
PMC wake-up 77 for USB3 port 1 wakeup
|
||||
PMC wake-up 78 for USB3 port 2 and port 3 wakeup
|
||||
|
|
@ -62,6 +64,16 @@ properties:
|
|||
PMC wake-up 81 for USB2 port 2 wakeup
|
||||
PMC wake-up 82 for USB2 port 3 wakeup
|
||||
|
||||
Tegra264 PMC wake events:
|
||||
PMC wake-up 79 for USB3 port 0 wakeup
|
||||
PMC wake-up 80 for USB3 port 1 wakeup
|
||||
PMC wake-up 81 for USB3 port 2 wakeup
|
||||
PMC wake-up 82 for USB3 port 3 wakeup
|
||||
PMC wake-up 83 for USB2 port 0 wakeup
|
||||
PMC wake-up 84 for USB2 port 1 wakeup
|
||||
PMC wake-up 85 for USB2 port 2 wakeup
|
||||
PMC wake-up 86 for USB2 port 3 wakeup
|
||||
|
||||
clocks:
|
||||
items:
|
||||
- description: XUSB host clock
|
||||
|
|
@ -97,7 +109,13 @@ properties:
|
|||
- const: write
|
||||
|
||||
iommus:
|
||||
maxItems: 1
|
||||
minItems: 1
|
||||
items:
|
||||
- description: XUSB host stream ID
|
||||
- description: XUSB host stream ID for virtual function 1
|
||||
- description: XUSB host stream ID for virtual function 2
|
||||
- description: XUSB host stream ID for virtual function 3
|
||||
- description: XUSB host stream ID for virtual function 4
|
||||
|
||||
nvidia,xusb-padctl:
|
||||
$ref: /schemas/types.yaml#/definitions/phandle
|
||||
|
|
@ -137,6 +155,30 @@ properties:
|
|||
allOf:
|
||||
- $ref: usb-xhci.yaml
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: nvidia,tegra234-xusb
|
||||
then:
|
||||
properties:
|
||||
interrupts:
|
||||
maxItems: 9
|
||||
iommus:
|
||||
maxItems: 1
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: nvidia,tegra264-xusb
|
||||
then:
|
||||
properties:
|
||||
interrupts:
|
||||
maxItems: 10
|
||||
iommus:
|
||||
minItems: 5
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
DA8XX USB OHCI controller
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible: Should be "ti,da830-ohci"
|
||||
- reg: Should contain one register range i.e. start and length
|
||||
- interrupts: Description of the interrupt line
|
||||
- phys: Phandle for the PHY device
|
||||
- phy-names: Should be "usb-phy"
|
||||
|
||||
Optional properties:
|
||||
- vbus-supply: phandle of regulator that controls vbus power / over-current
|
||||
|
||||
Example:
|
||||
|
||||
ohci: usb@225000 {
|
||||
compatible = "ti,da830-ohci";
|
||||
reg = <0x225000 0x1000>;
|
||||
interrupts = <59>;
|
||||
phys = <&usb_phy 1>;
|
||||
phy-names = "usb-phy";
|
||||
vbus-supply = <®_usb_ohci>;
|
||||
};
|
||||
|
|
@ -41,9 +41,11 @@ properties:
|
|||
- qcom,maili-dwc3
|
||||
- qcom,milos-dwc3
|
||||
- qcom,msm8953-dwc3
|
||||
- qcom,msm8974-dwc3
|
||||
- qcom,msm8994-dwc3
|
||||
- qcom,msm8996-dwc3
|
||||
- qcom,msm8998-dwc3
|
||||
- qcom,nord-dwc3
|
||||
- qcom,qcm2290-dwc3
|
||||
- qcom,qcs404-dwc3
|
||||
- qcom,qcs615-dwc3
|
||||
|
|
@ -270,6 +272,7 @@ allOf:
|
|||
contains:
|
||||
enum:
|
||||
- qcom,ipq8074-dwc3
|
||||
- qcom,msm8974-dwc3
|
||||
- qcom,qdu1000-dwc3
|
||||
then:
|
||||
properties:
|
||||
|
|
@ -361,6 +364,7 @@ allOf:
|
|||
enum:
|
||||
- qcom,eliza-dwc3
|
||||
- qcom,milos-dwc3
|
||||
- qcom,nord-dwc3
|
||||
- qcom,qcm2290-dwc3
|
||||
- qcom,qcs615-dwc3
|
||||
- qcom,sar2130p-dwc3
|
||||
|
|
@ -455,6 +459,23 @@ allOf:
|
|||
- const: qusb2_phy
|
||||
- const: ss_phy_irq
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- qcom,msm8974-dwc3
|
||||
then:
|
||||
properties:
|
||||
interrupts:
|
||||
minItems: 3
|
||||
maxItems: 3
|
||||
interrupt-names:
|
||||
items:
|
||||
- const: dwc_usb3
|
||||
- const: pwr_event
|
||||
- const: hs_phy_irq
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
|
|
@ -554,6 +575,7 @@ allOf:
|
|||
- qcom,ipq8064-dwc3
|
||||
- qcom,kaanapali-dwc3
|
||||
- qcom,maili-dwc3
|
||||
- qcom,nord-dwc3
|
||||
- qcom,qcs615-dwc3
|
||||
- qcom,qcs8300-dwc3
|
||||
- qcom,qdu1000-dwc3
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/usb/ti,am335x-usb-ctrl-module.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: TI AM335x USB Control Module
|
||||
|
||||
maintainers:
|
||||
- Sebastian Andrzej Siewior <bigeasy@linutronix.de>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: ti,am335x-usb-ctrl-module
|
||||
|
||||
reg:
|
||||
maxItems: 2
|
||||
|
||||
reg-names:
|
||||
items:
|
||||
- const: phy_ctrl
|
||||
- const: wakeup
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- reg-names
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
control@44e10000 {
|
||||
compatible = "ti,am335x-usb-ctrl-module";
|
||||
reg = <0x44e10620 0x10>,
|
||||
<0x44e10648 0x4>;
|
||||
reg-names = "phy_ctrl", "wakeup";
|
||||
};
|
||||
103
Documentation/devicetree/bindings/usb/ti,am33xx-usb.yaml
Normal file
103
Documentation/devicetree/bindings/usb/ti,am33xx-usb.yaml
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/usb/ti,am33xx-usb.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: TI AM33xx USB Wrapper
|
||||
|
||||
description:
|
||||
The glue layer contains multiple child nodes. It is required to have at least
|
||||
a USB node and a PHY node. The second USB node and its PHY node are optional.
|
||||
The DMA node is also optional.
|
||||
|
||||
maintainers:
|
||||
- Sebastian Andrzej Siewior <bigeasy@linutronix.de>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: ti,am33xx-usb
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
ranges: true
|
||||
|
||||
"#address-cells":
|
||||
const: 1
|
||||
|
||||
"#size-cells":
|
||||
const: 1
|
||||
|
||||
ti,hwmods:
|
||||
$ref: /schemas/types.yaml#/definitions/string
|
||||
description: Name of the hwmod associated with the USB module.
|
||||
const: usb_otg_hs
|
||||
deprecated: true
|
||||
|
||||
patternProperties:
|
||||
"^(usb-)?phy@[0-9a-f]+$":
|
||||
$ref: /schemas/phy/ti,am335x-usb-phy.yaml#
|
||||
|
||||
"^usb@[0-9a-f]+$":
|
||||
$ref: /schemas/usb/ti,musb-am33xx.yaml#
|
||||
|
||||
"^dma-controller@[0-9a-f]+$":
|
||||
$ref: /schemas/dma/ti/ti,cppi41.yaml#
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- ranges
|
||||
- "#address-cells"
|
||||
- "#size-cells"
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
usb@47400000 {
|
||||
compatible = "ti,am33xx-usb";
|
||||
reg = <0x47400000 0x1000>;
|
||||
ranges;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
ti,hwmods = "usb_otg_hs";
|
||||
|
||||
usb-phy@47401300 {
|
||||
compatible = "ti,am335x-usb-phy";
|
||||
reg = <0x47401300 0x100>;
|
||||
reg-names = "phy";
|
||||
ti,ctrl_mod = <&ctrl_mod>;
|
||||
#phy-cells = <0>;
|
||||
};
|
||||
|
||||
usb@47401000 {
|
||||
compatible = "ti,musb-am33xx";
|
||||
reg = <0x47401400 0x400>,
|
||||
<0x47401000 0x200>;
|
||||
reg-names = "mc", "control";
|
||||
interrupts = <18>;
|
||||
interrupt-names = "mc";
|
||||
dr_mode = "otg";
|
||||
mentor,multipoint = <1>;
|
||||
mentor,num-eps = <16>;
|
||||
mentor,ram-bits = <12>;
|
||||
mentor,power = <500>;
|
||||
phys = <&usb0_phy>;
|
||||
};
|
||||
|
||||
dma-controller@7402000 {
|
||||
compatible = "ti,am3359-cppi41";
|
||||
reg = <0x47400000 0x1000>,
|
||||
<0x47402000 0x1000>,
|
||||
<0x47403000 0x1000>,
|
||||
<0x47404000 0x4000>;
|
||||
reg-names = "glue", "controller", "scheduler", "queuemgr";
|
||||
interrupts = <17>;
|
||||
interrupt-names = "glue";
|
||||
#dma-cells = <2>;
|
||||
dma-channels = <30>;
|
||||
dma-requests = <256>;
|
||||
};
|
||||
};
|
||||
113
Documentation/devicetree/bindings/usb/ti,da830-musb.yaml
Normal file
113
Documentation/devicetree/bindings/usb/ti,da830-musb.yaml
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/usb/ti,da830-musb.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: TI DA8xx MUSB
|
||||
|
||||
maintainers:
|
||||
- Bin Liu <b-liu@ti.com>
|
||||
|
||||
allOf:
|
||||
- $ref: /schemas/usb/usb-drd.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: ti,da830-musb
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
interrupt-names:
|
||||
const: mc
|
||||
|
||||
phys:
|
||||
maxItems: 1
|
||||
|
||||
phy-names:
|
||||
const: usb-phy
|
||||
|
||||
dmas:
|
||||
minItems: 1
|
||||
maxItems: 8
|
||||
|
||||
dma-names:
|
||||
minItems: 1
|
||||
maxItems: 8
|
||||
items:
|
||||
pattern: "^(rx|tx)[1-4]$"
|
||||
|
||||
vbus-supply:
|
||||
description: Phandle to a regulator providing the USB bus power.
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
|
||||
clock-ranges: true
|
||||
|
||||
ranges: true
|
||||
|
||||
"#address-cells":
|
||||
const: 1
|
||||
|
||||
"#size-cells":
|
||||
const: 1
|
||||
|
||||
patternProperties:
|
||||
"^dma-controller@[0-9a-f]+$":
|
||||
$ref: /schemas/dma/ti/ti,cppi41.yaml#
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
- interrupt-names
|
||||
- phys
|
||||
- phy-names
|
||||
- ranges
|
||||
- "#address-cells"
|
||||
- "#size-cells"
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
usb@200000 {
|
||||
compatible = "ti,da830-musb";
|
||||
reg = <0x00200000 0x1000>;
|
||||
ranges;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
interrupts = <58>;
|
||||
interrupt-names = "mc";
|
||||
|
||||
dr_mode = "host";
|
||||
vbus-supply = <&usb_vbus>;
|
||||
phys = <&usb_phy 0>;
|
||||
phy-names = "usb-phy";
|
||||
|
||||
clocks = <&psc1 1>;
|
||||
clock-ranges;
|
||||
|
||||
dmas = <&cppi41dma 0 0>, <&cppi41dma 1 0>,
|
||||
<&cppi41dma 2 0>, <&cppi41dma 3 0>,
|
||||
<&cppi41dma 0 1>, <&cppi41dma 1 1>,
|
||||
<&cppi41dma 2 1>, <&cppi41dma 3 1>;
|
||||
dma-names = "rx1", "rx2", "rx3", "rx4",
|
||||
"tx1", "tx2", "tx3", "tx4";
|
||||
|
||||
dma-controller@201000 {
|
||||
compatible = "ti,da830-cppi41";
|
||||
reg = <0x201000 0x1000>,
|
||||
<0x202000 0x1000>,
|
||||
<0x204000 0x4000>;
|
||||
reg-names = "controller", "scheduler", "queuemgr";
|
||||
interrupts = <58>;
|
||||
#dma-cells = <2>;
|
||||
dma-channels = <4>;
|
||||
};
|
||||
};
|
||||
61
Documentation/devicetree/bindings/usb/ti,da830-ohci.yaml
Normal file
61
Documentation/devicetree/bindings/usb/ti,da830-ohci.yaml
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/usb/ti,da830-ohci.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: DA8XX USB OHCI controller
|
||||
|
||||
maintainers:
|
||||
- Axel Haslam <ahaslam@baylibre.com>
|
||||
|
||||
allOf:
|
||||
- $ref: usb-hcd.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: ti,da830-ohci
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
phys:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
|
||||
phy-names:
|
||||
const: usb-phy
|
||||
|
||||
vbus-supply:
|
||||
description:
|
||||
phandle of regulator that controls vbus power / over-current
|
||||
|
||||
oc-gpios:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
- phys
|
||||
- phy-names
|
||||
- clocks
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
usb@225000 {
|
||||
compatible = "ti,da830-ohci";
|
||||
reg = <0x225000 0x1000>;
|
||||
interrupts = <59>;
|
||||
phys = <&usb_phy 1>;
|
||||
phy-names = "usb-phy";
|
||||
clocks = <&psc1 2>;
|
||||
vbus-supply = <®_usb_ohci>;
|
||||
};
|
||||
|
|
@ -25,6 +25,15 @@ properties:
|
|||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
vcc33-supply:
|
||||
description: 3.3V supply (VCC33 pin), powering the SuperSpeed 2:1 MUX.
|
||||
|
||||
vdd5-supply:
|
||||
description:
|
||||
5V supply (VDD5 pin), powering the CC controller and sourcing VCONN.
|
||||
VDD5 has to be stable for at least tVDD5V_PG (2ms) before VCC33 starts
|
||||
ramping up, unless ENn_CC is held high while both rails ramp up.
|
||||
|
||||
id-gpios:
|
||||
description:
|
||||
An input gpio for USB ID pin. Upon detecting a UFP device, HD3SS3220
|
||||
|
|
@ -68,6 +77,8 @@ examples:
|
|||
reg = <0x47>;
|
||||
interrupt-parent = <&gpio6>;
|
||||
interrupts = <3>;
|
||||
vcc33-supply = <&vcc3v3_control>;
|
||||
vdd5-supply = <&vcc5v0_device_s0>;
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
|
|
|
|||
113
Documentation/devicetree/bindings/usb/ti,musb-am33xx.yaml
Normal file
113
Documentation/devicetree/bindings/usb/ti,musb-am33xx.yaml
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/usb/ti,musb-am33xx.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: TI AM33xx/DM816 MUSB
|
||||
|
||||
maintainers:
|
||||
- Ravi B <ravibabu@ti.com>
|
||||
- Ajay Kumar Gupta <ajay.gupta@ti.com>
|
||||
|
||||
allOf:
|
||||
- $ref: /schemas/usb/usb-drd.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- ti,musb-am33xx
|
||||
- ti,musb-dm816
|
||||
|
||||
reg:
|
||||
maxItems: 2
|
||||
|
||||
reg-names:
|
||||
items:
|
||||
- const: mc
|
||||
- const: control
|
||||
|
||||
interrupts:
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
|
||||
interrupt-names:
|
||||
minItems: 1
|
||||
items:
|
||||
- const: mc
|
||||
- const: vbus
|
||||
|
||||
mentor,multipoint:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description: Indicates the musb controller supports multipoint.
|
||||
enum: [0, 1]
|
||||
|
||||
mentor,num-eps:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description: Specifies the number of endpoints.
|
||||
const: 16
|
||||
|
||||
mentor,ram-bits:
|
||||
description: Specifies the ram address size.
|
||||
const: 12
|
||||
|
||||
mentor,power:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description: Controller supply capacity (in mA) when operating in host mode.
|
||||
const: 500
|
||||
|
||||
phys:
|
||||
maxItems: 1
|
||||
|
||||
dmas:
|
||||
minItems: 1
|
||||
maxItems: 30
|
||||
|
||||
dma-names:
|
||||
minItems: 1
|
||||
maxItems: 30
|
||||
items:
|
||||
pattern: "^(rx|tx)([1-9]|1[0-5])$"
|
||||
|
||||
phy-names:
|
||||
const: usb2-phy
|
||||
|
||||
interface-type:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description: Describes the type of interface between the controller and the PHY.
|
||||
enum: [ 0, 1 ]
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- reg-names
|
||||
- interrupts
|
||||
- interrupt-names
|
||||
- dr_mode
|
||||
- mentor,multipoint
|
||||
- mentor,num-eps
|
||||
- mentor,ram-bits
|
||||
- mentor,power
|
||||
- phys
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
usb@47401000 {
|
||||
compatible = "ti,musb-am33xx";
|
||||
reg = <0x47401400 0x400>,
|
||||
<0x47401000 0x200>;
|
||||
reg-names = "mc", "control";
|
||||
interrupts = <18>;
|
||||
interrupt-names = "mc";
|
||||
dr_mode = "otg";
|
||||
mentor,multipoint = <1>;
|
||||
mentor,num-eps = <16>;
|
||||
mentor,ram-bits = <12>;
|
||||
mentor,power = <500>;
|
||||
phys = <&usb0_phy>;
|
||||
dmas = <&cppi41dma 0 0 &cppi41dma 1 0>;
|
||||
dma-names = "rx1", "tx1";
|
||||
};
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ Supported chips:
|
|||
|
||||
Prefix: 'prom21_xhci'
|
||||
|
||||
PCI IDs: 1022:43fc, 1022:43fd
|
||||
PCI IDs: 1022:43f7, 1022:43f9, 1022:43fa, 1022:43fc, 1022:43fd
|
||||
|
||||
Author:
|
||||
|
||||
|
|
|
|||
|
|
@ -361,7 +361,7 @@ Then, execute a loop similar to the one written in pseudo-code below::
|
|||
ioctl(fd, MON_IOCX_MFETCH, &fetch); // Process errors, too
|
||||
nflush = fetch.nfetch; // This many packets to flush when done
|
||||
for (i = 0; i < nflush; i++) {
|
||||
hdr = (struct ubsmon_packet *) &mmap_area[vec[i]];
|
||||
hdr = (struct usbmon_packet *) &mmap_area[vec[i]];
|
||||
if (hdr->type == '@') // Filler packet
|
||||
continue;
|
||||
caddr_t data = &mmap_area[vec[i]] + 64;
|
||||
|
|
|
|||
22
MAINTAINERS
22
MAINTAINERS
|
|
@ -28514,6 +28514,28 @@ S: Maintained
|
|||
F: Documentation/usb/ehci.rst
|
||||
F: drivers/usb/host/ehci*
|
||||
|
||||
USB GADGET/PERIPHERAL SUBSYSTEM
|
||||
M: Neill Kapron <nkapron@google.com>
|
||||
L: linux-usb@vger.kernel.org
|
||||
S: Supported
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git
|
||||
F: Documentation/ABI/testing/configfs-usb-gadget*
|
||||
F: Documentation/ABI/testing/sysfs-devices-platform-_UDC_-gadget
|
||||
F: Documentation/driver-api/usb/gadget.rst
|
||||
F: Documentation/usb/functionfs*
|
||||
F: Documentation/usb/gadget*
|
||||
F: Documentation/usb/mass-storage.rst
|
||||
F: drivers/usb/gadget/
|
||||
F: include/linux/usb/composite.h
|
||||
F: include/linux/usb/func_utils.h
|
||||
F: include/linux/usb/functionfs.h
|
||||
F: include/linux/usb/g_hid.h
|
||||
F: include/linux/usb/gadget*
|
||||
F: include/uapi/linux/usb/functionfs.h
|
||||
F: include/uapi/linux/usb/g_*.h
|
||||
F: include/uapi/linux/usb/gadgetfs.h
|
||||
F: tools/usb/ffs-*
|
||||
|
||||
USB HID/HIDBP DRIVERS (USB KEYBOARDS, MICE, REMOTE CONTROLS, ...)
|
||||
M: Jiri Kosina <jikos@kernel.org>
|
||||
M: Benjamin Tissoires <bentiss@kernel.org>
|
||||
|
|
|
|||
|
|
@ -1128,7 +1128,7 @@ crc1: crc@58009000 {
|
|||
};
|
||||
|
||||
usbh_ohci: usb@5800c000 {
|
||||
compatible = "generic-ohci";
|
||||
compatible = "st,stm32mp13-ohci", "generic-ohci";
|
||||
reg = <0x5800c000 0x1000>;
|
||||
clocks = <&usbphyc>, <&rcc USBH>;
|
||||
resets = <&rcc USBH_R>;
|
||||
|
|
@ -1137,7 +1137,7 @@ usbh_ohci: usb@5800c000 {
|
|||
};
|
||||
|
||||
usbh_ehci: usb@5800d000 {
|
||||
compatible = "generic-ehci";
|
||||
compatible = "st,stm32mp13-ehci", "generic-ehci";
|
||||
reg = <0x5800d000 0x1000>;
|
||||
clocks = <&usbphyc>, <&rcc USBH>;
|
||||
resets = <&rcc USBH_R>;
|
||||
|
|
|
|||
|
|
@ -504,7 +504,7 @@ crc1: crc@58009000 {
|
|||
};
|
||||
|
||||
usbh_ohci: usb@5800c000 {
|
||||
compatible = "generic-ohci";
|
||||
compatible = "st,stm32mp15-ohci", "generic-ohci";
|
||||
reg = <0x5800c000 0x1000>;
|
||||
clocks = <&usbphyc>, <&rcc USBH>;
|
||||
resets = <&rcc USBH_R>;
|
||||
|
|
@ -515,7 +515,7 @@ usbh_ohci: usb@5800c000 {
|
|||
};
|
||||
|
||||
usbh_ehci: usb@5800d000 {
|
||||
compatible = "generic-ehci";
|
||||
compatible = "st,stm32mp15-ehci", "generic-ehci";
|
||||
reg = <0x5800d000 0x1000>;
|
||||
clocks = <&usbphyc>, <&rcc USBH>;
|
||||
resets = <&rcc USBH_R>;
|
||||
|
|
|
|||
|
|
@ -877,6 +877,48 @@ stmmac_axi_config_1: stmmac-axi-config {
|
|||
snps,wr_osr_lmt = <0x7>;
|
||||
};
|
||||
};
|
||||
|
||||
usbh_ohci: usb@482e0000 {
|
||||
compatible = "st,stm32mp23-ohci", "generic-ohci";
|
||||
reg = <0x482e0000 0x1000>;
|
||||
access-controllers = <&rifsc 63>;
|
||||
clocks = <&usb2_phy1 1>, <&rcc CK_BUS_USB2OHCI>;
|
||||
interrupts = <GIC_SPI 140 IRQ_TYPE_LEVEL_HIGH>;
|
||||
phys = <&usb2_phy1>;
|
||||
phy-names = "usb";
|
||||
resets = <&rcc USBH_R>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
usbh_ehci: usb@482f0000 {
|
||||
compatible = "st,stm32mp23-ehci", "generic-ehci";
|
||||
reg = <0x482f0000 0x1000>;
|
||||
access-controllers = <&rifsc 63>;
|
||||
clocks = <&usb2_phy1 0>, <&rcc CK_BUS_USB2EHCI>;
|
||||
companion = <&usbh_ohci>;
|
||||
interrupts = <GIC_SPI 139 IRQ_TYPE_LEVEL_HIGH>;
|
||||
phys = <&usb2_phy1>;
|
||||
phy-names = "usb";
|
||||
resets = <&rcc USBH_R>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
usb3dr: usb@48300000 {
|
||||
compatible = "st,stm32mp25-dwc3";
|
||||
reg = <0x48300000 0x100000>;
|
||||
access-controllers = <&rifsc 66>;
|
||||
clocks = <&rcc CK_KER_USB2PHY2>,
|
||||
<&rcc CK_BUS_USB3DR>,
|
||||
<&rcc CK_KER_USB2PHY2>;
|
||||
clock-names = "ref", "bus_early", "suspend";
|
||||
interrupts = <GIC_SPI 228 IRQ_TYPE_LEVEL_HIGH>;
|
||||
maximum-speed = "high-speed";
|
||||
phys = <&usb2_phy2>;
|
||||
phy-names = "usb2-phy";
|
||||
resets = <&rcc USB3DR_R>;
|
||||
st,syscfg = <&syscfg 0x4800>;
|
||||
status = "disabled";
|
||||
};
|
||||
};
|
||||
|
||||
bsec: efuse@44000000 {
|
||||
|
|
@ -1080,6 +1122,28 @@ syscfg: syscon@44230000 {
|
|||
compatible = "st,stm32mp23-syscfg", "syscon";
|
||||
reg = <0x44230000 0x10000>;
|
||||
#clock-cells = <0>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
usb2_phy1: phy@2400 {
|
||||
compatible = "st,stm32mp25-usb2phy1";
|
||||
reg = <0x2400 0x24>;
|
||||
#phy-cells = <0>;
|
||||
#clock-cells = <1>;
|
||||
clocks = <&rcc CK_KER_USB2PHY1>;
|
||||
resets = <&rcc USB2PHY1_R>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
usb2_phy2: phy@2800 {
|
||||
compatible = "st,stm32mp25-usb2phy2";
|
||||
reg = <0x2800 0x10>;
|
||||
#phy-cells = <0>;
|
||||
#clock-cells = <1>;
|
||||
clocks = <&rcc CK_KER_USB2PHY2EN>;
|
||||
resets = <&rcc USB2PHY2_R>;
|
||||
status = "disabled";
|
||||
};
|
||||
};
|
||||
|
||||
pinctrl: pinctrl@44240000 {
|
||||
|
|
|
|||
|
|
@ -1762,6 +1762,47 @@ stmmac_axi_config_1: stmmac-axi-config {
|
|||
};
|
||||
};
|
||||
|
||||
usbh_ohci: usb@482e0000 {
|
||||
compatible = "st,stm32mp25-ohci", "generic-ohci";
|
||||
reg = <0x482e0000 0x1000>;
|
||||
access-controllers = <&rifsc 63>;
|
||||
clocks = <&usb2_phy1 1>, <&rcc CK_BUS_USB2OHCI>;
|
||||
interrupts = <GIC_SPI 140 IRQ_TYPE_LEVEL_HIGH>;
|
||||
phys = <&usb2_phy1>;
|
||||
phy-names = "usb";
|
||||
resets = <&rcc USBH_R>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
usbh_ehci: usb@482f0000 {
|
||||
compatible = "st,stm32mp25-ehci", "generic-ehci";
|
||||
reg = <0x482f0000 0x1000>;
|
||||
access-controllers = <&rifsc 63>;
|
||||
clocks = <&usb2_phy1 0>, <&rcc CK_BUS_USB2EHCI>;
|
||||
companion = <&usbh_ohci>;
|
||||
interrupts = <GIC_SPI 139 IRQ_TYPE_LEVEL_HIGH>;
|
||||
phys = <&usb2_phy1>;
|
||||
phy-names = "usb";
|
||||
resets = <&rcc USBH_R>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
usb3dr: usb@48300000 {
|
||||
compatible = "st,stm32mp25-dwc3";
|
||||
reg = <0x48300000 0x100000>;
|
||||
access-controllers = <&rifsc 66>;
|
||||
clocks = <&rcc CK_KER_USB2PHY2>,
|
||||
<&rcc CK_BUS_USB3DR>,
|
||||
<&rcc CK_KER_USB2PHY2>;
|
||||
clock-names = "ref", "bus_early", "suspend";
|
||||
interrupts = <GIC_SPI 228 IRQ_TYPE_LEVEL_HIGH>;
|
||||
phys = <&usb2_phy2>, <&combophy PHY_TYPE_USB3>;
|
||||
phy-names = "usb2-phy", "usb3-phy";
|
||||
resets = <&rcc USB3DR_R>;
|
||||
st,syscfg = <&syscfg 0x4800>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
pcie_ep: pcie-ep@48400000 {
|
||||
compatible = "st,stm32mp25-pcie-ep";
|
||||
reg = <0x48400000 0x100000>,
|
||||
|
|
@ -2036,6 +2077,28 @@ syscfg: syscon@44230000 {
|
|||
compatible = "st,stm32mp25-syscfg", "syscon";
|
||||
reg = <0x44230000 0x10000>;
|
||||
#clock-cells = <0>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
usb2_phy1: phy@2400 {
|
||||
compatible = "st,stm32mp25-usb2phy1";
|
||||
reg = <0x2400 0x24>;
|
||||
#phy-cells = <0>;
|
||||
#clock-cells = <1>;
|
||||
clocks = <&rcc CK_KER_USB2PHY1>;
|
||||
resets = <&rcc USB2PHY1_R>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
usb2_phy2: phy@2800 {
|
||||
compatible = "st,stm32mp25-usb2phy2";
|
||||
reg = <0x2800 0x10>;
|
||||
#phy-cells = <0>;
|
||||
#clock-cells = <1>;
|
||||
clocks = <&rcc CK_KER_USB2PHY2EN>;
|
||||
resets = <&rcc USB2PHY2_R>;
|
||||
status = "disabled";
|
||||
};
|
||||
};
|
||||
|
||||
pinctrl: pinctrl@44240000 {
|
||||
|
|
|
|||
|
|
@ -540,11 +540,12 @@ static int tbnet_alloc_rx_buffers(struct tbnet *net, unsigned int nbuffers)
|
|||
trace_tbnet_alloc_rx_frame(index, tf->page, dma_addr,
|
||||
DMA_FROM_DEVICE);
|
||||
|
||||
tb_ring_rx(ring->ring, &tf->frame);
|
||||
tb_ring_rx_more(ring->ring, &tf->frame);
|
||||
|
||||
ring->prod++;
|
||||
}
|
||||
|
||||
tb_ring_notify(ring->ring);
|
||||
return 0;
|
||||
|
||||
err_free:
|
||||
|
|
@ -1243,7 +1244,8 @@ static netdev_tx_t tbnet_start_xmit(struct sk_buff *skb,
|
|||
goto err_drop;
|
||||
|
||||
for (i = 0; i < frame_index + 1; i++)
|
||||
tb_ring_tx(net->tx_ring.ring, &frames[i]->frame);
|
||||
tb_ring_tx_more(net->tx_ring.ring, &frames[i]->frame);
|
||||
tb_ring_notify(net->tx_ring.ring);
|
||||
|
||||
if (net->svc->prtcstns & TBNET_MATCH_FRAGS_ID)
|
||||
atomic_inc(&net->frame_id);
|
||||
|
|
|
|||
|
|
@ -10474,7 +10474,7 @@ static int __init rtl8152_driver_init(void)
|
|||
{
|
||||
int ret;
|
||||
|
||||
ret = usb_register_device_driver(&rtl8152_cfgselector_driver, THIS_MODULE);
|
||||
ret = usb_register_device_driver(&rtl8152_cfgselector_driver);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,9 @@
|
|||
|
||||
#define COUNTER_SET_LEN 3
|
||||
|
||||
#define HW_MARGINING_DWORDS 3
|
||||
#define SW_MARGINING_DWORDS 4
|
||||
|
||||
/*
|
||||
* USB4 spec doesn't specify dwell range, the range of 100 ms to 500 ms
|
||||
* probed to give good results.
|
||||
|
|
@ -494,7 +497,7 @@ struct tb_margining {
|
|||
unsigned int gen;
|
||||
bool asym_rx;
|
||||
u32 caps[3];
|
||||
u32 results[3];
|
||||
u32 results[5];
|
||||
enum usb4_margining_lane lanes;
|
||||
unsigned int min_ber_level;
|
||||
unsigned int max_ber_level;
|
||||
|
|
@ -612,6 +615,11 @@ supports_optional_voltage_offset_range(const struct tb_margining *margining)
|
|||
return margining->caps[0] & USB4_MARGIN_CAP_0_OPT_VOLTAGE_SUPPORT;
|
||||
}
|
||||
|
||||
static bool supports_extended_error_counter(const struct tb_margining *margining)
|
||||
{
|
||||
return margining->caps[0] & USB4_MARGIN_CAP_0_EXT_ERR_COUNTER;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
margining_ber_level_write(struct file *file, const char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
|
|
@ -701,6 +709,8 @@ static int margining_caps_show(struct seq_file *s, void *not_used)
|
|||
ber_level_show(s, margining->max_ber_level);
|
||||
} else {
|
||||
seq_puts(s, "# hardware margining: no\n");
|
||||
seq_printf(s, "# extended error counter: %s\n",
|
||||
str_yes_no(supports_extended_error_counter(margining)));
|
||||
}
|
||||
|
||||
seq_printf(s, "# all lanes simultaneously: %s\n",
|
||||
|
|
@ -1150,36 +1160,91 @@ static int margining_mode_show(struct seq_file *s, void *not_used)
|
|||
}
|
||||
DEBUGFS_ATTR_RW(margining_mode);
|
||||
|
||||
static u32 margining_get_lane_error(const u32 *results, unsigned int lane, bool extended)
|
||||
{
|
||||
u32 error_count;
|
||||
|
||||
switch (lane) {
|
||||
case USB4_MARGINING_LANE_RX0:
|
||||
error_count = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_0_MASK, results[0]);
|
||||
break;
|
||||
case USB4_MARGINING_LANE_RX1:
|
||||
error_count = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_1_MASK, results[0]);
|
||||
break;
|
||||
case USB4_MARGINING_LANE_RX2:
|
||||
error_count = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_2_MASK, results[0]);
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (extended)
|
||||
error_count |= FIELD_PREP(USB4_MARGIN_SW_EXT_ERR_COUNTER_MASK, results[1 + lane]);
|
||||
|
||||
return error_count;
|
||||
}
|
||||
|
||||
static int margining_run_sw(struct tb_margining *margining,
|
||||
struct usb4_port_margining_params *params)
|
||||
{
|
||||
bool extended_err = supports_extended_error_counter(margining);
|
||||
u32 nsamples = margining->dwell_time / DWELL_SAMPLE_INTERVAL;
|
||||
int ret, i;
|
||||
u32 dwords;
|
||||
|
||||
if (params->error_counter != USB4_MARGIN_SW_ERROR_COUNTER_START)
|
||||
goto out_stop_or_clear;
|
||||
|
||||
if (extended_err)
|
||||
dwords = SW_MARGINING_DWORDS;
|
||||
else
|
||||
dwords = 1;
|
||||
|
||||
ret = usb4_port_sw_margin(margining->port, margining->target, margining->index,
|
||||
params, margining->results);
|
||||
if (ret)
|
||||
goto out_stop;
|
||||
return ret;
|
||||
|
||||
for (i = 0; i <= nsamples; i++) {
|
||||
u32 errors = 0;
|
||||
|
||||
ret = usb4_port_sw_margin_errors(margining->port, margining->target,
|
||||
margining->index, &margining->results[1]);
|
||||
if (ret)
|
||||
margining->index, &margining->results[1],
|
||||
dwords);
|
||||
if (ret) {
|
||||
tb_port_warn(margining->port, "failed to read margining error counters\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (margining->lanes == USB4_MARGINING_LANE_RX0)
|
||||
errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_0_MASK,
|
||||
margining->results[1]);
|
||||
else if (margining->lanes == USB4_MARGINING_LANE_RX1)
|
||||
errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_1_MASK,
|
||||
margining->results[1]);
|
||||
else if (margining->lanes == USB4_MARGINING_LANE_RX2)
|
||||
errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_2_MASK,
|
||||
margining->results[1]);
|
||||
else if (margining->lanes == USB4_MARGINING_LANE_ALL)
|
||||
errors = margining->results[1];
|
||||
if (margining->lanes == USB4_MARGINING_LANE_ALL) {
|
||||
if (extended_err) {
|
||||
/* Extended counters with all lanes: check each active lane */
|
||||
int width = tb_port_get_link_width(margining->port);
|
||||
unsigned int lane, max_lane = 2;
|
||||
|
||||
if (width == TB_LINK_WIDTH_ASYM_TX) {
|
||||
max_lane = 1;
|
||||
} else if ((width == TB_LINK_WIDTH_ASYM_RX) && margining->asym_rx) {
|
||||
max_lane = 3;
|
||||
} else if (width < 0) {
|
||||
tb_port_warn(margining->port, "failed to read link width\n");
|
||||
break;
|
||||
}
|
||||
|
||||
for (lane = 0; lane < max_lane; lane++) {
|
||||
errors = margining_get_lane_error(&margining->results[1],
|
||||
lane, extended_err);
|
||||
if (errors)
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
/* Standard counters: all lane errors fit in results[1]*/
|
||||
errors = margining->results[1];
|
||||
}
|
||||
} else {
|
||||
errors = margining_get_lane_error(&margining->results[1],
|
||||
margining->lanes, extended_err);
|
||||
}
|
||||
|
||||
/* Any errors stop the test */
|
||||
if (errors)
|
||||
|
|
@ -1187,15 +1252,15 @@ static int margining_run_sw(struct tb_margining *margining,
|
|||
|
||||
fsleep(DWELL_SAMPLE_INTERVAL * USEC_PER_MSEC);
|
||||
}
|
||||
params->error_counter = USB4_MARGIN_SW_ERROR_COUNTER_STOP;
|
||||
|
||||
out_stop:
|
||||
out_stop_or_clear:
|
||||
/*
|
||||
* Stop the counters but don't clear them to allow the
|
||||
* Stop the counters or clear them as per the
|
||||
* different error counter configurations.
|
||||
*/
|
||||
margining_modify_error_counter(margining, margining->lanes,
|
||||
USB4_MARGIN_SW_ERROR_COUNTER_STOP);
|
||||
return ret;
|
||||
return margining_modify_error_counter(margining, margining->lanes,
|
||||
params->error_counter);
|
||||
}
|
||||
|
||||
static int validate_margining(struct tb_margining *margining)
|
||||
|
|
@ -1272,7 +1337,7 @@ static int margining_run_write(void *data, u64 val)
|
|||
|
||||
if (margining->software) {
|
||||
struct usb4_port_margining_params params = {
|
||||
.error_counter = USB4_MARGIN_SW_ERROR_COUNTER_CLEAR,
|
||||
.error_counter = margining->error_counter,
|
||||
.lanes = margining->lanes,
|
||||
.time = margining->time,
|
||||
.voltage_time_offset = margining->voltage_time_offset,
|
||||
|
|
@ -1303,7 +1368,7 @@ static int margining_run_write(void *data, u64 val)
|
|||
margining->lanes);
|
||||
|
||||
ret = usb4_port_hw_margin(port, margining->target, margining->index, ¶ms,
|
||||
margining->results, ARRAY_SIZE(margining->results));
|
||||
margining->results, HW_MARGINING_DWORDS);
|
||||
}
|
||||
|
||||
if (down_sw)
|
||||
|
|
@ -1425,7 +1490,7 @@ static int margining_results_show(struct seq_file *s, void *not_used)
|
|||
seq_printf(s, "0x%08x\n", margining->results[0]);
|
||||
/* Only the hardware margining has two result dwords */
|
||||
if (!margining->software) {
|
||||
for (int i = 1; i < ARRAY_SIZE(margining->results); i++)
|
||||
for (int i = 1; i < HW_MARGINING_DWORDS; i++)
|
||||
seq_printf(s, "0x%08x\n", margining->results[i]);
|
||||
|
||||
if (margining->lanes == USB4_MARGINING_LANE_ALL) {
|
||||
|
|
@ -1444,18 +1509,30 @@ static int margining_results_show(struct seq_file *s, void *not_used)
|
|||
u32 lane_errors, result;
|
||||
|
||||
seq_printf(s, "0x%08x\n", margining->results[1]);
|
||||
if (supports_extended_error_counter(margining)) {
|
||||
seq_printf(s, "0x%08x\n", margining->results[2]);
|
||||
seq_printf(s, "0x%08x\n", margining->results[3]);
|
||||
seq_printf(s, "0x%08x\n", margining->results[4]);
|
||||
}
|
||||
|
||||
|
||||
result = FIELD_GET(USB4_MARGIN_SW_LANES_MASK, margining->results[0]);
|
||||
if (result == USB4_MARGINING_LANE_RX0 ||
|
||||
result == USB4_MARGINING_LANE_ALL) {
|
||||
lane_errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_0_MASK,
|
||||
margining->results[1]);
|
||||
if (supports_extended_error_counter(margining))
|
||||
lane_errors |= FIELD_PREP(USB4_MARGIN_SW_EXT_ERR_COUNTER_MASK,
|
||||
margining->results[2]);
|
||||
seq_printf(s, "# lane 0 errors: %u\n", lane_errors);
|
||||
}
|
||||
if (result == USB4_MARGINING_LANE_RX1 ||
|
||||
result == USB4_MARGINING_LANE_ALL) {
|
||||
lane_errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_1_MASK,
|
||||
margining->results[1]);
|
||||
if (supports_extended_error_counter(margining))
|
||||
lane_errors |= FIELD_PREP(USB4_MARGIN_SW_EXT_ERR_COUNTER_MASK,
|
||||
margining->results[3]);
|
||||
seq_printf(s, "# lane 1 errors: %u\n", lane_errors);
|
||||
}
|
||||
if (margining->asym_rx &&
|
||||
|
|
@ -1463,6 +1540,9 @@ static int margining_results_show(struct seq_file *s, void *not_used)
|
|||
result == USB4_MARGINING_LANE_ALL)) {
|
||||
lane_errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_2_MASK,
|
||||
margining->results[1]);
|
||||
if (supports_extended_error_counter(margining))
|
||||
lane_errors |= FIELD_PREP(USB4_MARGIN_SW_EXT_ERR_COUNTER_MASK,
|
||||
margining->results[4]);
|
||||
seq_printf(s, "# lane 2 errors: %u\n", lane_errors);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -215,11 +215,6 @@ static void dma_test_stop_rings(struct dma_test *dt)
|
|||
{
|
||||
int ret;
|
||||
|
||||
if (dt->rx_ring)
|
||||
tb_ring_stop(dt->rx_ring);
|
||||
if (dt->tx_ring)
|
||||
tb_ring_stop(dt->tx_ring);
|
||||
|
||||
ret = tb_xdomain_disable_paths(dt->xd, dt->tx_hopid,
|
||||
dt->tx_ring ? dt->tx_ring->hop : -1,
|
||||
dt->rx_hopid,
|
||||
|
|
@ -227,6 +222,11 @@ static void dma_test_stop_rings(struct dma_test *dt)
|
|||
if (ret)
|
||||
dev_warn(&dt->svc->dev, "failed to disable DMA paths\n");
|
||||
|
||||
if (dt->rx_ring)
|
||||
tb_ring_stop(dt->rx_ring);
|
||||
if (dt->tx_ring)
|
||||
tb_ring_stop(dt->tx_ring);
|
||||
|
||||
dma_test_free_rings(dt);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -324,7 +324,7 @@ int tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid)
|
|||
}
|
||||
|
||||
static int tb_drom_parse_entry_generic(struct tb_switch *sw,
|
||||
struct tb_drom_entry_header *header)
|
||||
const struct tb_drom_entry_header *header)
|
||||
{
|
||||
const struct tb_drom_entry_generic *entry =
|
||||
(const struct tb_drom_entry_generic *)header;
|
||||
|
|
@ -360,7 +360,7 @@ static int tb_drom_parse_entry_generic(struct tb_switch *sw,
|
|||
}
|
||||
|
||||
static int tb_drom_parse_entry_port(struct tb_switch *sw,
|
||||
struct tb_drom_entry_header *header)
|
||||
const struct tb_drom_entry_header *header)
|
||||
{
|
||||
struct tb_port *port;
|
||||
int res;
|
||||
|
|
@ -386,11 +386,13 @@ static int tb_drom_parse_entry_port(struct tb_switch *sw,
|
|||
type &= 0xffffff;
|
||||
|
||||
if (type == TB_TYPE_PORT) {
|
||||
struct tb_drom_entry_port *entry = (void *) header;
|
||||
const struct tb_drom_entry_port *entry =
|
||||
(const struct tb_drom_entry_port *)header;
|
||||
|
||||
if (header->len != sizeof(*entry)) {
|
||||
tb_sw_warn(sw,
|
||||
"port entry has size %#x (expected %#zx)\n",
|
||||
header->len, sizeof(struct tb_drom_entry_port));
|
||||
header->len, sizeof(*entry));
|
||||
return -EIO;
|
||||
}
|
||||
port->link_nr = entry->link_nr;
|
||||
|
|
@ -421,9 +423,16 @@ static int tb_drom_parse_entries(struct tb_switch *sw, size_t header_size)
|
|||
int res;
|
||||
|
||||
while (pos < drom_size) {
|
||||
struct tb_drom_entry_header *entry = (void *) (sw->drom + pos);
|
||||
if (pos + 1 == drom_size || pos + entry->len > drom_size
|
||||
|| !entry->len) {
|
||||
const struct tb_drom_entry_header *entry;
|
||||
|
||||
if (drom_size - pos < sizeof(*entry)) {
|
||||
tb_sw_warn(sw, "DROM buffer overrun\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
entry = (const struct tb_drom_entry_header *)(sw->drom + pos);
|
||||
if (entry->len < sizeof(*entry) ||
|
||||
entry->len > drom_size - pos) {
|
||||
tb_sw_warn(sw, "DROM buffer overrun\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2072,7 +2072,7 @@ static int complete_rpm(struct device *dev, void *data)
|
|||
{
|
||||
struct tb_switch *sw = tb_to_switch(dev);
|
||||
|
||||
if (sw)
|
||||
if (sw && sw->tb == data)
|
||||
complete(&sw->rpm_complete);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -2088,8 +2088,8 @@ static void remove_unplugged_switch(struct tb_switch *sw)
|
|||
* tb_switch_remove() calls pm_runtime_get_sync() that then waits
|
||||
* for it.
|
||||
*/
|
||||
complete_rpm(&sw->dev, NULL);
|
||||
bus_for_each_dev(&tb_bus_type, &sw->dev, NULL, complete_rpm);
|
||||
complete_rpm(&sw->dev, sw->tb);
|
||||
bus_for_each_dev(&tb_bus_type, &sw->dev, sw->tb, complete_rpm);
|
||||
tb_switch_remove(sw);
|
||||
|
||||
pm_runtime_mark_last_busy(parent);
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ static bool host_reset = true;
|
|||
module_param(host_reset, bool, 0444);
|
||||
MODULE_PARM_DESC(host_reset, "reset USB4 host router (default: true)");
|
||||
|
||||
/* Returns absolute bit number of the ring in the interrupt registers */
|
||||
static int ring_interrupt_index(const struct tb_ring *ring)
|
||||
{
|
||||
int bit = ring->hop;
|
||||
|
|
@ -49,24 +50,41 @@ static int ring_interrupt_index(const struct tb_ring *ring)
|
|||
return bit;
|
||||
}
|
||||
|
||||
static void nhi_mask_interrupt(struct tb_nhi *nhi, int mask, int ring)
|
||||
static void nhi_mask_interrupt(struct tb_nhi *nhi, u32 mask, int reg_index)
|
||||
{
|
||||
if (nhi->quirks & QUIRK_AUTO_CLEAR_INT) {
|
||||
u32 val;
|
||||
int offset = reg_index * 4;
|
||||
u32 val;
|
||||
|
||||
val = ioread32(nhi->iobase + REG_RING_INTERRUPT_BASE + ring);
|
||||
iowrite32(val & ~mask, nhi->iobase + REG_RING_INTERRUPT_BASE + ring);
|
||||
} else {
|
||||
iowrite32(mask, nhi->iobase + REG_RING_INTERRUPT_MASK_CLEAR_BASE + ring);
|
||||
}
|
||||
/* Use shadow copy instead of reading the register */
|
||||
val = nhi->interrupt_mask[reg_index] & ~mask;
|
||||
nhi->interrupt_mask[reg_index] = val;
|
||||
|
||||
if (nhi->quirks & QUIRK_AUTO_CLEAR_INT)
|
||||
iowrite32(val, nhi->iobase + REG_RING_INTERRUPT_BASE + offset);
|
||||
else
|
||||
iowrite32(mask, nhi->iobase + REG_RING_INTERRUPT_MASK_CLEAR_BASE + offset);
|
||||
}
|
||||
|
||||
static void nhi_clear_interrupt(struct tb_nhi *nhi, int ring)
|
||||
static void nhi_unmask_interrupt(struct tb_nhi *nhi, u32 mask, int reg_index)
|
||||
{
|
||||
int offset = reg_index * 4;
|
||||
u32 val;
|
||||
|
||||
/* Use shadow copy instead of reading the register */
|
||||
val = nhi->interrupt_mask[reg_index] | mask;
|
||||
nhi->interrupt_mask[reg_index] = val;
|
||||
|
||||
iowrite32(val, nhi->iobase + REG_RING_INTERRUPT_BASE + offset);
|
||||
}
|
||||
|
||||
static void nhi_clear_interrupt(struct tb_nhi *nhi, int reg_index)
|
||||
{
|
||||
int offset = reg_index * 4;
|
||||
|
||||
if (nhi->quirks & QUIRK_AUTO_CLEAR_INT)
|
||||
ioread32(nhi->iobase + REG_RING_NOTIFY_BASE + ring);
|
||||
ioread32(nhi->iobase + REG_RING_NOTIFY_BASE + offset);
|
||||
else
|
||||
iowrite32(~0, nhi->iobase + REG_RING_INT_CLEAR + ring);
|
||||
iowrite32(~0, nhi->iobase + REG_RING_INT_CLEAR + offset);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -76,22 +94,17 @@ static void nhi_clear_interrupt(struct tb_nhi *nhi, int ring)
|
|||
*/
|
||||
static void ring_interrupt_active(struct tb_ring *ring, bool active)
|
||||
{
|
||||
int index = ring_interrupt_index(ring) / 32 * 4;
|
||||
int reg = REG_RING_INTERRUPT_BASE + index;
|
||||
int interrupt_bit = ring_interrupt_index(ring) & 31;
|
||||
int mask = 1 << interrupt_bit;
|
||||
int interrupt_index = ring_interrupt_index(ring);
|
||||
int reg_index = interrupt_index / 32;
|
||||
int reg = REG_RING_INTERRUPT_BASE + reg_index * 4;
|
||||
int interrupt_bit = interrupt_index % 32;
|
||||
u32 mask = BIT(interrupt_bit);
|
||||
u32 old, new;
|
||||
|
||||
if (ring->irq > 0) {
|
||||
u32 step, shift, ivr, misc, itr;
|
||||
void __iomem *ivr_base;
|
||||
int auto_clear_bit;
|
||||
int index;
|
||||
|
||||
if (ring->is_tx)
|
||||
index = ring->hop;
|
||||
else
|
||||
index = ring->hop + ring->nhi->hop_count;
|
||||
|
||||
/*
|
||||
* Intel routers support a bit that isn't part of
|
||||
|
|
@ -114,8 +127,8 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active)
|
|||
ring->nhi->iobase + REG_DMA_MISC);
|
||||
|
||||
ivr_base = ring->nhi->iobase + REG_INT_VEC_ALLOC_BASE;
|
||||
step = index / REG_INT_VEC_ALLOC_REGS * REG_INT_VEC_ALLOC_BITS;
|
||||
shift = index % REG_INT_VEC_ALLOC_REGS * REG_INT_VEC_ALLOC_BITS;
|
||||
step = interrupt_index / REG_INT_VEC_ALLOC_REGS * REG_INT_VEC_ALLOC_BITS;
|
||||
shift = interrupt_index % REG_INT_VEC_ALLOC_REGS * REG_INT_VEC_ALLOC_BITS;
|
||||
ivr = ioread32(ivr_base + step);
|
||||
ivr &= ~(REG_INT_VEC_ALLOC_MASK << shift);
|
||||
if (active)
|
||||
|
|
@ -129,7 +142,7 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active)
|
|||
ring->vector * 4);
|
||||
}
|
||||
|
||||
old = ioread32(ring->nhi->iobase + reg);
|
||||
old = ring->nhi->interrupt_mask[reg_index];
|
||||
if (active)
|
||||
new = old | mask;
|
||||
else
|
||||
|
|
@ -139,15 +152,24 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active)
|
|||
"%s interrupt at register %#x bit %d (%#x -> %#x)\n",
|
||||
active ? "enabling" : "disabling", reg, interrupt_bit, old, new);
|
||||
|
||||
if (new == old)
|
||||
dev_WARN(ring->nhi->dev, "interrupt for %s %d is already %s\n",
|
||||
RING_TYPE(ring), ring->hop,
|
||||
str_enabled_disabled(active));
|
||||
if (new == old) {
|
||||
/*
|
||||
* Rings that are polled mask the interrupt while the
|
||||
* completions are being advanced (see __ring_interrupt())
|
||||
* so for those it can already be disabled by the time
|
||||
* the ring is stopped.
|
||||
*/
|
||||
if (active || !ring->start_poll)
|
||||
dev_WARN(ring->nhi->dev,
|
||||
"interrupt for %s %d is already %s\n",
|
||||
RING_TYPE(ring), ring->hop,
|
||||
str_enabled_disabled(active));
|
||||
}
|
||||
|
||||
if (active)
|
||||
iowrite32(new, ring->nhi->iobase + reg);
|
||||
nhi_unmask_interrupt(ring->nhi, mask, reg_index);
|
||||
else
|
||||
nhi_mask_interrupt(ring->nhi, mask, index);
|
||||
nhi_mask_interrupt(ring->nhi, mask, reg_index);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -160,11 +182,11 @@ void nhi_disable_interrupts(struct tb_nhi *nhi)
|
|||
int i = 0;
|
||||
/* disable interrupts */
|
||||
for (i = 0; i < RING_INTERRUPT_REG_COUNT(nhi); i++)
|
||||
nhi_mask_interrupt(nhi, ~0, 4 * i);
|
||||
nhi_mask_interrupt(nhi, ~0, i);
|
||||
|
||||
/* clear interrupt status bits */
|
||||
for (i = 0; i < RING_NOTIFY_REG_COUNT(nhi); i++)
|
||||
nhi_clear_interrupt(nhi, 4 * i);
|
||||
nhi_clear_interrupt(nhi, i);
|
||||
}
|
||||
|
||||
/* ring helper methods */
|
||||
|
|
@ -227,12 +249,37 @@ static bool ring_empty(struct tb_ring *ring)
|
|||
return ring->head == ring->tail;
|
||||
}
|
||||
|
||||
static void __ring_notify(struct tb_ring *ring)
|
||||
{
|
||||
lockdep_assert_held(&ring->lock);
|
||||
|
||||
if (ring->notify_pending) {
|
||||
/*
|
||||
* The doorbell carries the absolute index of the head
|
||||
* so a single write covers all the descriptors posted
|
||||
* since the previous one.
|
||||
*/
|
||||
if (ring->is_tx)
|
||||
ring_iowrite_prod(ring, ring->head);
|
||||
else
|
||||
ring_iowrite_cons(ring, ring->head);
|
||||
}
|
||||
ring->notify_pending = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* ring_write_descriptors() - post frames from ring->queue to the controller
|
||||
* @ring: Ring to post the frames to
|
||||
* @notify: Notify the controller about the posted descriptors
|
||||
*
|
||||
* Unless @notify is %true the controller is not notified about the posted
|
||||
* descriptors and the caller is expected to call tb_ring_notify() once it
|
||||
* is done queuing frames. This allows batching of frames before
|
||||
* updating the producer/consumer indices.
|
||||
*
|
||||
* ring->lock is held.
|
||||
*/
|
||||
static void ring_write_descriptors(struct tb_ring *ring)
|
||||
static void ring_write_descriptors(struct tb_ring *ring, bool notify)
|
||||
{
|
||||
struct ring_frame *frame, *n;
|
||||
struct ring_desc *descriptor;
|
||||
|
|
@ -256,11 +303,11 @@ static void ring_write_descriptors(struct tb_ring *ring)
|
|||
descriptor->sof = frame->sof;
|
||||
}
|
||||
ring->head = (ring->head + 1) % ring->size;
|
||||
if (ring->is_tx)
|
||||
ring_iowrite_prod(ring, ring->head);
|
||||
else
|
||||
ring_iowrite_cons(ring, ring->head);
|
||||
ring->notify_pending = true;
|
||||
}
|
||||
|
||||
if (notify)
|
||||
__ring_notify(ring);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -305,7 +352,7 @@ static void ring_work(struct work_struct *work)
|
|||
}
|
||||
ring->tail = (ring->tail + 1) % ring->size;
|
||||
}
|
||||
ring_write_descriptors(ring);
|
||||
ring_write_descriptors(ring, true);
|
||||
|
||||
invoke_callback:
|
||||
/* allow callbacks to schedule new work */
|
||||
|
|
@ -324,7 +371,7 @@ static void ring_work(struct work_struct *work)
|
|||
wake_up(&ring->wait);
|
||||
}
|
||||
|
||||
int __tb_ring_enqueue(struct tb_ring *ring, struct ring_frame *frame)
|
||||
int __tb_ring_enqueue(struct tb_ring *ring, struct ring_frame *frame, bool more)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
|
@ -332,7 +379,7 @@ int __tb_ring_enqueue(struct tb_ring *ring, struct ring_frame *frame)
|
|||
spin_lock_irqsave(&ring->lock, flags);
|
||||
if (ring->running) {
|
||||
list_add_tail(&frame->list, &ring->queue);
|
||||
ring_write_descriptors(ring);
|
||||
ring_write_descriptors(ring, !more);
|
||||
} else {
|
||||
ret = -ESHUTDOWN;
|
||||
}
|
||||
|
|
@ -341,6 +388,22 @@ int __tb_ring_enqueue(struct tb_ring *ring, struct ring_frame *frame)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(__tb_ring_enqueue);
|
||||
|
||||
/**
|
||||
* tb_ring_notify() - Notify the controller about the queued frames
|
||||
* @ring: Ring to notify
|
||||
*
|
||||
* Notifies the controller about frames that were enqueued using
|
||||
* tb_ring_tx_more() or tb_ring_rx_more(). Does nothing if there are no
|
||||
* such frames pending.
|
||||
*/
|
||||
void tb_ring_notify(struct tb_ring *ring)
|
||||
{
|
||||
guard(spinlock_irqsave)(&ring->lock);
|
||||
if (ring->running)
|
||||
__ring_notify(ring);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tb_ring_notify);
|
||||
|
||||
/**
|
||||
* tb_ring_poll() - Poll one completed frame from the ring
|
||||
* @ring: Ring to poll
|
||||
|
|
@ -377,6 +440,13 @@ struct ring_frame *tb_ring_poll(struct tb_ring *ring)
|
|||
}
|
||||
|
||||
ring->tail = (ring->tail + 1) % ring->size;
|
||||
|
||||
/*
|
||||
* There is one more slot available so we can push next
|
||||
* descriptor to the ring. This is needed when the ring
|
||||
* is created with %RING_FLAG_NO_INTERRUPT.
|
||||
*/
|
||||
ring_write_descriptors(ring, true);
|
||||
}
|
||||
|
||||
unlock:
|
||||
|
|
@ -385,19 +455,35 @@ struct ring_frame *tb_ring_poll(struct tb_ring *ring)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(tb_ring_poll);
|
||||
|
||||
/**
|
||||
* tb_ring_poll_pending() - Does the ring have completed frame
|
||||
* @ring: Ring to check
|
||||
*
|
||||
* Can be used to check whether there is a completed frame in the ring
|
||||
* that next call to tb_ring_poll() returns.
|
||||
*
|
||||
* Return: %true if a completed frame is waiting, %false otherwise.
|
||||
*/
|
||||
bool tb_ring_poll_pending(struct tb_ring *ring)
|
||||
{
|
||||
guard(spinlock_irqsave)(&ring->lock);
|
||||
|
||||
if (!ring->running || ring_empty(ring))
|
||||
return false;
|
||||
return !!(ring->descriptors[ring->tail].flags & RING_DESC_COMPLETED);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tb_ring_poll_pending);
|
||||
|
||||
static void __ring_interrupt_mask(struct tb_ring *ring, bool mask)
|
||||
{
|
||||
int idx = ring_interrupt_index(ring);
|
||||
int reg = REG_RING_INTERRUPT_BASE + idx / 32 * 4;
|
||||
int bit = idx % 32;
|
||||
u32 val;
|
||||
int interrupt_index = ring_interrupt_index(ring);
|
||||
int reg_index = interrupt_index / 32;
|
||||
int interrupt_bit = interrupt_index % 32;
|
||||
|
||||
val = ioread32(ring->nhi->iobase + reg);
|
||||
if (mask)
|
||||
val &= ~BIT(bit);
|
||||
nhi_mask_interrupt(ring->nhi, BIT(interrupt_bit), reg_index);
|
||||
else
|
||||
val |= BIT(bit);
|
||||
iowrite32(val, ring->nhi->iobase + reg);
|
||||
nhi_unmask_interrupt(ring->nhi, BIT(interrupt_bit), reg_index);
|
||||
}
|
||||
|
||||
/* Both @nhi->lock and @ring->lock should be held */
|
||||
|
|
@ -788,6 +874,7 @@ void tb_ring_stop(struct tb_ring *ring)
|
|||
ring_iowrite32desc(ring, 0, 12);
|
||||
ring->head = 0;
|
||||
ring->tail = 0;
|
||||
ring->notify_pending = false;
|
||||
ring->running = false;
|
||||
|
||||
err:
|
||||
|
|
@ -1182,24 +1269,40 @@ static void nhi_reset(struct tb_nhi *nhi)
|
|||
|
||||
static struct tb *nhi_select_cm(struct tb_nhi *nhi)
|
||||
{
|
||||
bool linked = false;
|
||||
struct tb *tb;
|
||||
|
||||
/*
|
||||
* USB4 case is simple. If we got control of any of the
|
||||
* capabilities, we use software CM.
|
||||
*/
|
||||
if (tb_acpi_is_native())
|
||||
return tb_probe(nhi);
|
||||
if (!tb_acpi_is_native()) {
|
||||
/*
|
||||
* Either firmware based CM is running (we did not get
|
||||
* control from the firmware) or this is pre-USB4 PC so
|
||||
* try first firmware CM and then fallback to software CM.
|
||||
*/
|
||||
tb = icm_probe(nhi);
|
||||
if (tb)
|
||||
return tb;
|
||||
}
|
||||
|
||||
/*
|
||||
* Either firmware based CM is running (we did not get control
|
||||
* from the firmware) or this is pre-USB4 PC so try first
|
||||
* firmware CM and then fallback to software CM.
|
||||
*/
|
||||
tb = icm_probe(nhi);
|
||||
tb = tb_probe(nhi);
|
||||
if (!tb)
|
||||
tb = tb_probe(nhi);
|
||||
return NULL;
|
||||
|
||||
if (nhi->ops->add_links)
|
||||
linked = nhi->ops->add_links(nhi);
|
||||
if (!linked)
|
||||
linked = tb_acpi_add_links(nhi);
|
||||
/*
|
||||
* Device links are needed to make sure we establish tunnels
|
||||
* before protocol stacks are resumed so complain here if we
|
||||
* found them missing.
|
||||
*/
|
||||
if (!linked)
|
||||
dev_warn(nhi->dev,
|
||||
"device links to tunneled native ports are missing!\n");
|
||||
return tb;
|
||||
}
|
||||
|
||||
|
|
@ -1222,7 +1325,10 @@ int nhi_probe(struct tb_nhi *nhi)
|
|||
sizeof(*nhi->tx_rings), GFP_KERNEL);
|
||||
nhi->rx_rings = devm_kcalloc(dev, nhi->hop_count,
|
||||
sizeof(*nhi->rx_rings), GFP_KERNEL);
|
||||
if (!nhi->tx_rings || !nhi->rx_rings)
|
||||
nhi->interrupt_mask = devm_kcalloc(dev, RING_INTERRUPT_REG_COUNT(nhi),
|
||||
sizeof(*nhi->interrupt_mask),
|
||||
GFP_KERNEL);
|
||||
if (!nhi->tx_rings || !nhi->rx_rings || !nhi->interrupt_mask)
|
||||
return -ENOMEM;
|
||||
|
||||
nhi_reset(nhi);
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ extern const struct dev_pm_ops nhi_pm_ops;
|
|||
/**
|
||||
* struct tb_nhi_ops - NHI specific optional operations
|
||||
* @init: NHI specific initialization
|
||||
* @add_links: Setup device links for tunneling native protocols
|
||||
* @suspend_noirq: NHI specific suspend_noirq hook
|
||||
* @resume_noirq: NHI specific resume_noirq hook
|
||||
* @runtime_suspend: NHI specific runtime_suspend hook
|
||||
|
|
@ -55,6 +56,7 @@ extern const struct dev_pm_ops nhi_pm_ops;
|
|||
*/
|
||||
struct tb_nhi_ops {
|
||||
int (*init)(struct tb_nhi *nhi);
|
||||
bool (*add_links)(struct tb_nhi *nhi);
|
||||
int (*suspend_noirq)(struct tb_nhi *nhi, bool wakeup);
|
||||
int (*resume_noirq)(struct tb_nhi *nhi);
|
||||
int (*runtime_suspend)(struct tb_nhi *nhi);
|
||||
|
|
|
|||
|
|
@ -398,7 +398,7 @@ static int __tb_path_deactivate_hop(struct tb_port *port, int hop_index,
|
|||
return ret;
|
||||
|
||||
/* Wait until it is drained */
|
||||
timeout = ktime_add_ms(ktime_get(), 500);
|
||||
timeout = ktime_add_ms(ktime_get(), port->pp_timeout_msec);
|
||||
do {
|
||||
ret = tb_port_read(port, &hop, TB_CFG_HOPS, 2 * hop_index, 2);
|
||||
if (ret)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
#include <linux/property.h>
|
||||
#include <linux/string_helpers.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <linux/platform_data/x86/apple.h>
|
||||
|
||||
#include "nhi.h"
|
||||
#include "nhi_regs.h"
|
||||
|
|
@ -108,6 +109,82 @@ static void nhi_pci_check_iommu(struct tb_nhi_pci *nhi_pci)
|
|||
str_enabled_disabled(port_ok));
|
||||
}
|
||||
|
||||
static bool add_link(struct tb_nhi *nhi, struct pci_dev *pdev)
|
||||
{
|
||||
const struct device_link *link;
|
||||
|
||||
link = device_link_add(&pdev->dev, nhi->dev,
|
||||
DL_FLAG_AUTOREMOVE_SUPPLIER |
|
||||
DL_FLAG_PM_RUNTIME);
|
||||
if (!link) {
|
||||
dev_warn(nhi->dev, "device link creation from %s failed\n",
|
||||
dev_name(&pdev->dev));
|
||||
return false;
|
||||
}
|
||||
|
||||
dev_dbg(nhi->dev, "created link from %s\n", dev_name(&pdev->dev));
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* During suspend the Thunderbolt controller is reset and all PCIe
|
||||
* tunnels are lost. The NHI driver will try to reestablish all tunnels
|
||||
* during resume. This adds device links between the tunneled PCIe
|
||||
* downstream ports and the NHI so that the device core will make sure
|
||||
* NHI is resumed first before the rest.
|
||||
*/
|
||||
static bool nhi_pci_add_links(struct tb_nhi *nhi)
|
||||
{
|
||||
struct pci_dev *nhi_pdev = to_pci_dev(nhi->dev);
|
||||
struct pci_dev *upstream, *pdev;
|
||||
bool ret;
|
||||
|
||||
if (!x86_apple_machine)
|
||||
return false;
|
||||
|
||||
switch (nhi_pdev->device) {
|
||||
case PCI_DEVICE_ID_INTEL_LIGHT_RIDGE:
|
||||
case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C:
|
||||
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI:
|
||||
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI:
|
||||
case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_NHI:
|
||||
case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_NHI:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
upstream = pci_upstream_bridge(nhi_pdev);
|
||||
while (upstream) {
|
||||
if (!pci_is_pcie(upstream))
|
||||
return false;
|
||||
if (pci_pcie_type(upstream) == PCI_EXP_TYPE_UPSTREAM)
|
||||
break;
|
||||
upstream = pci_upstream_bridge(upstream);
|
||||
}
|
||||
|
||||
if (!upstream)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* For each hotplug downstream port, create add device link back
|
||||
* to NHI so that PCIe tunnels can be re-established after
|
||||
* sleep.
|
||||
*/
|
||||
ret = false;
|
||||
for_each_pci_bridge(pdev, upstream->subordinate) {
|
||||
if (!pci_is_pcie(pdev))
|
||||
continue;
|
||||
if (pci_pcie_type(pdev) != PCI_EXP_TYPE_DOWNSTREAM ||
|
||||
!pdev->is_pciehp)
|
||||
continue;
|
||||
|
||||
ret |= add_link(nhi, pdev);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int nhi_pci_init_msi(struct tb_nhi *nhi)
|
||||
{
|
||||
struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi);
|
||||
|
|
@ -251,6 +328,7 @@ static bool nhi_pci_is_present(struct tb_nhi *nhi)
|
|||
}
|
||||
|
||||
static const struct tb_nhi_ops pci_nhi_default_ops = {
|
||||
.add_links = nhi_pci_add_links,
|
||||
.pre_nvm_auth = nhi_pci_start_dma_port,
|
||||
.post_nvm_auth = nhi_pci_complete_dma_port,
|
||||
.request_ring_irq = nhi_pci_ring_request_msix,
|
||||
|
|
@ -421,6 +499,40 @@ static int icl_nhi_resume(struct tb_nhi *nhi)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static bool icl_nhi_add_links(struct tb_nhi *nhi)
|
||||
{
|
||||
struct pci_dev *nhi_pdev = to_pci_dev(nhi->dev);
|
||||
struct pci_bus *bus = nhi_pdev->bus;
|
||||
struct pci_dev *pdev;
|
||||
bool ret;
|
||||
|
||||
if (!x86_apple_machine)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* On integrated controllers the tunneled PCIe root ports are
|
||||
* directly under the host bridge.
|
||||
*/
|
||||
if (!pci_is_root_bus(bus))
|
||||
return false;
|
||||
|
||||
ret = false;
|
||||
for_each_pci_bridge(pdev, bus) {
|
||||
switch (nhi_pdev->device) {
|
||||
case PCI_DEVICE_ID_INTEL_ICL_NHI0:
|
||||
if (pdev->device == 0x8a1d || pdev->device == 0x8a1f)
|
||||
ret |= add_link(nhi, pdev);
|
||||
break;
|
||||
case PCI_DEVICE_ID_INTEL_ICL_NHI1:
|
||||
if (pdev->device == 0x8a21 || pdev->device == 0x8a23)
|
||||
ret |= add_link(nhi, pdev);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void icl_nhi_shutdown(struct tb_nhi *nhi)
|
||||
{
|
||||
nhi_pci_release_irq(nhi);
|
||||
|
|
@ -430,6 +542,7 @@ static void icl_nhi_shutdown(struct tb_nhi *nhi)
|
|||
|
||||
static const struct tb_nhi_ops icl_nhi_ops = {
|
||||
.init = icl_nhi_resume,
|
||||
.add_links = icl_nhi_add_links,
|
||||
.suspend_noirq = icl_nhi_suspend_noirq,
|
||||
.resume_noirq = icl_nhi_resume,
|
||||
.runtime_suspend = icl_nhi_suspend,
|
||||
|
|
|
|||
|
|
@ -52,6 +52,19 @@ static void quirk_block_rpm_in_redrive(struct tb_switch *sw)
|
|||
tb_sw_dbg(sw, "preventing runtime PM in DP redrive mode\n");
|
||||
}
|
||||
|
||||
static void quirk_stuck_pending(struct tb_switch *sw)
|
||||
{
|
||||
struct tb_port *port;
|
||||
|
||||
tb_switch_for_each_port(sw, port) {
|
||||
if (!tb_port_is_nhi(port))
|
||||
continue;
|
||||
|
||||
port->pp_timeout_msec = 0;
|
||||
tb_port_dbg(port, "pending bit does not clear, reading it once\n");
|
||||
}
|
||||
}
|
||||
|
||||
struct tb_quirk {
|
||||
u16 hw_vendor_id;
|
||||
u16 hw_device_id;
|
||||
|
|
@ -117,6 +130,11 @@ static const struct tb_quirk tb_quirks[] = {
|
|||
{ 0x0438, 0x0209, 0x0000, 0x0000, quirk_clx_disable },
|
||||
{ 0x0438, 0x020a, 0x0000, 0x0000, quirk_clx_disable },
|
||||
{ 0x0438, 0x020b, 0x0000, 0x0000, quirk_clx_disable },
|
||||
/*
|
||||
* ASMedia ASM4242 never clears the Pending Packets bit of its host
|
||||
* interface adapter.
|
||||
*/
|
||||
{ 0x174c, 0x2428, 0x0000, 0x0000, quirk_stuck_pending },
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ enum usb4_sb_opcode {
|
|||
#define USB4_MARGIN_CAP_0_MAX_VOLTAGE_OFFSET_MASK GENMASK(18, 13)
|
||||
#define USB4_MARGIN_CAP_0_OPT_VOLTAGE_SUPPORT BIT(19)
|
||||
#define USB4_MARGIN_CAP_0_VOLT_STEPS_OPT_MASK GENMASK(26, 20)
|
||||
#define USB4_MARGIN_CAP_0_EXT_ERR_COUNTER BIT(28)
|
||||
#define USB4_MARGIN_CAP_1_MAX_VOLT_OFS_OPT_MASK GENMASK(7, 0)
|
||||
#define USB4_MARGIN_CAP_1_TIME_DESTR BIT(8)
|
||||
#define USB4_MARGIN_CAP_1_TIME_INDP_MASK GENMASK(10, 9)
|
||||
|
|
@ -108,5 +109,6 @@ enum usb4_sb_opcode {
|
|||
#define USB4_MARGIN_SW_ERR_COUNTER_LANE_0_MASK GENMASK(3, 0)
|
||||
#define USB4_MARGIN_SW_ERR_COUNTER_LANE_1_MASK GENMASK(7, 4)
|
||||
#define USB4_MARGIN_SW_ERR_COUNTER_LANE_2_MASK GENMASK(11, 8)
|
||||
#define USB4_MARGIN_SW_EXT_ERR_COUNTER_MASK GENMASK(15, 4)
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -131,14 +131,20 @@ struct tbstream_ring {
|
|||
* @ring_size: Size of the rings
|
||||
* @throttling: Interrupt throttling rate in ns
|
||||
* @busy_poll: Instead of interrupts, busy poll the rings
|
||||
* @rx_pending: Receive ring has completions that need to be advanced
|
||||
* @users: Number of times @cdev has been opened
|
||||
* @closed: CLOSE packet was received
|
||||
* @close_received: CLOSE packet appeared in the RX ring
|
||||
* @closed: CLOSE packet was handled in the read side
|
||||
* @removed: Userspace removed the ConfigFS group underneath.
|
||||
* @wait: Waitqueue for open, read and write
|
||||
* @lock: Lock protecting this structure
|
||||
* @tx_ring: Transmit ring
|
||||
* @rx_ring: Receive ring
|
||||
* @list: Stream devices are linked through this
|
||||
*
|
||||
* @close_received is used on write side to notify the writer that the
|
||||
* other side sent CLOSE. @closed on the other hand is used on the read
|
||||
* side to make read(2) return EOF to the caller.
|
||||
*/
|
||||
struct tbstream_dev {
|
||||
struct config_group group;
|
||||
|
|
@ -151,7 +157,9 @@ struct tbstream_dev {
|
|||
unsigned int ring_size;
|
||||
unsigned int throttling;
|
||||
bool busy_poll;
|
||||
bool rx_pending;
|
||||
int users;
|
||||
bool close_received;
|
||||
bool closed;
|
||||
bool removed;
|
||||
wait_queue_head_t wait;
|
||||
|
|
@ -278,6 +286,14 @@ static inline bool tbstream_ring_available(const struct tbstream_ring *ring)
|
|||
return ring->prod > ring->cons;
|
||||
}
|
||||
|
||||
static void tbstream_ring_poll(struct tbstream_ring *ring)
|
||||
{
|
||||
struct ring_frame *frame;
|
||||
|
||||
while ((frame = tb_ring_poll(ring->ring)))
|
||||
frame->callback(ring->ring, frame, false);
|
||||
}
|
||||
|
||||
static inline struct tb_xdomain *tbstream_dev_xdomain(struct tbstream_dev *sdev)
|
||||
{
|
||||
if (sdev->stream)
|
||||
|
|
@ -338,9 +354,19 @@ static inline bool tbstream_dev_removed(const struct tbstream_dev *sdev)
|
|||
return sdev->removed;
|
||||
}
|
||||
|
||||
static inline bool tbstream_dev_close_received(const struct tbstream_dev *sdev)
|
||||
{
|
||||
return READ_ONCE(sdev->close_received);
|
||||
}
|
||||
|
||||
static inline bool tbstream_dev_closed(const struct tbstream_dev *sdev)
|
||||
{
|
||||
return sdev->closed;
|
||||
return READ_ONCE(sdev->closed);
|
||||
}
|
||||
|
||||
static inline bool tbstream_dev_rx_pending(const struct tbstream_dev *sdev)
|
||||
{
|
||||
return READ_ONCE(sdev->rx_pending);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -349,6 +375,7 @@ tbstream_dev_rx_callback(struct tb_ring *ring, struct ring_frame *frame,
|
|||
{
|
||||
struct tbstream_frame *sf = container_of(frame, typeof(*sf), frame);
|
||||
struct tbstream_dev *sdev = sf->sdev;
|
||||
__poll_t mask;
|
||||
|
||||
if (canceled)
|
||||
return;
|
||||
|
|
@ -356,12 +383,17 @@ tbstream_dev_rx_callback(struct tb_ring *ring, struct ring_frame *frame,
|
|||
sf->completed = true;
|
||||
sdev->rx_ring.prod++;
|
||||
|
||||
if (sf->frame.flags & RING_DESC_CRC_ERROR)
|
||||
pr_warn("RX CRC error\n");
|
||||
else if (sf->frame.flags & RING_DESC_BUFFER_OVERRUN)
|
||||
pr_warn("RX buffer overrun\n");
|
||||
else
|
||||
wake_up_interruptible_poll(&sdev->wait, EPOLLIN | EPOLLRDNORM);
|
||||
mask = EPOLLIN | EPOLLRDNORM;
|
||||
/*
|
||||
* The CLOSE packet does not have a payload so the flags do not
|
||||
* matter. The read_iter() deals with the flags.
|
||||
*/
|
||||
if (sf->frame.eof == TBSTREAM_CLOSE) {
|
||||
WRITE_ONCE(sdev->close_received, true);
|
||||
mask |= EPOLLHUP;
|
||||
}
|
||||
|
||||
wake_up_interruptible_poll(&sdev->wait, mask);
|
||||
}
|
||||
|
||||
static struct tbstream_frame *
|
||||
|
|
@ -538,38 +570,27 @@ tbstream_dev_send_data(struct tbstream_dev *sdev, struct iov_iter *from,
|
|||
return tb_ring_tx(sdev->tx_ring.ring, &sf->frame);
|
||||
}
|
||||
|
||||
static void
|
||||
tbstream_dev_poll_ring(struct tbstream_dev *sdev, struct tbstream_ring *ring)
|
||||
{
|
||||
struct ring_frame *frame;
|
||||
|
||||
if (!sdev->busy_poll)
|
||||
return;
|
||||
|
||||
while ((frame = tb_ring_poll(ring->ring)))
|
||||
frame->callback(ring->ring, frame, false);
|
||||
}
|
||||
|
||||
static int tbstream_dev_send_close(struct tbstream_dev *sdev)
|
||||
{
|
||||
struct tbstream_frame *sf;
|
||||
ktime_t timeout;
|
||||
|
||||
if (sdev->busy_poll) {
|
||||
/*
|
||||
* Wait for the ring to have available slots before we send the
|
||||
* CLOSE packet.
|
||||
*/
|
||||
timeout = ktime_add_ms(ktime_get(), 500);
|
||||
do {
|
||||
if (tbstream_ring_available(&sdev->tx_ring))
|
||||
break;
|
||||
/*
|
||||
* When busy polling it's the write(2) path that
|
||||
* advances the completions so it is possible that the
|
||||
* ring is full at this point. Advance the ring here so
|
||||
* that there is room for the CLOSE packet to be sent.
|
||||
* For busy polling we need to advance the ring here as
|
||||
* well to make the slots available.
|
||||
*/
|
||||
ktime_t timeout = ktime_add_ms(ktime_get(), 500);
|
||||
|
||||
do {
|
||||
if (tbstream_ring_available(&sdev->tx_ring))
|
||||
break;
|
||||
tbstream_dev_poll_ring(sdev, &sdev->tx_ring);
|
||||
fsleep(15);
|
||||
} while (ktime_before(ktime_get(), timeout));
|
||||
}
|
||||
if (sdev->busy_poll)
|
||||
tbstream_ring_poll(&sdev->tx_ring);
|
||||
fsleep(15);
|
||||
} while (ktime_before(ktime_get(), timeout));
|
||||
|
||||
sf = tbstream_dev_alloc_tx(sdev, TBSTREAM_CLOSE, NULL, SZ_256);
|
||||
if (IS_ERR(sf))
|
||||
|
|
@ -577,16 +598,45 @@ static int tbstream_dev_send_close(struct tbstream_dev *sdev)
|
|||
return tb_ring_tx(sdev->tx_ring.ring, &sf->frame);
|
||||
}
|
||||
|
||||
static void tbstream_dev_start_poll(void *data)
|
||||
{
|
||||
struct tbstream_dev *sdev = data;
|
||||
|
||||
WRITE_ONCE(sdev->rx_pending, true);
|
||||
wake_up_interruptible_poll(&sdev->wait, EPOLLIN | EPOLLRDNORM);
|
||||
}
|
||||
|
||||
/* sdev->lock must be held */
|
||||
static void tbstream_dev_advance_rx(struct tbstream_dev *sdev)
|
||||
{
|
||||
/*
|
||||
* Clear before running the completions so that an interrupt
|
||||
* that arrives while we are doing that is not missed.
|
||||
*/
|
||||
WRITE_ONCE(sdev->rx_pending, false);
|
||||
tbstream_ring_poll(&sdev->rx_ring);
|
||||
}
|
||||
|
||||
/* sdev->lock must be held */
|
||||
static void tbstream_dev_complete_rx(struct tbstream_dev *sdev)
|
||||
{
|
||||
if (!sdev->busy_poll)
|
||||
tb_ring_poll_complete(sdev->rx_ring.ring);
|
||||
}
|
||||
|
||||
static int tbstream_dev_start(struct tbstream_dev *sdev)
|
||||
{
|
||||
struct tb_xdomain *xd = tbstream_dev_xdomain(sdev);
|
||||
unsigned int flags = RING_FLAG_FRAME | RING_FLAG_E2E;
|
||||
void (*start_poll)(void *) = NULL;
|
||||
u16 sof_mask, eof_mask;
|
||||
struct tb_ring *ring;
|
||||
int ret, e2e_tx_hop;
|
||||
|
||||
if (sdev->busy_poll)
|
||||
flags |= RING_FLAG_NO_INTERRUPT;
|
||||
else
|
||||
start_poll = tbstream_dev_start_poll;
|
||||
|
||||
ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size, flags);
|
||||
if (!ring)
|
||||
|
|
@ -602,7 +652,8 @@ static int tbstream_dev_start(struct tbstream_dev *sdev)
|
|||
eof_mask = BIT(TBSTREAM_DATA) | BIT(TBSTREAM_CLOSE);
|
||||
|
||||
ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size, flags,
|
||||
e2e_tx_hop, sof_mask, eof_mask, NULL, NULL);
|
||||
e2e_tx_hop, sof_mask, eof_mask, start_poll,
|
||||
sdev);
|
||||
if (!ring) {
|
||||
ret = -ENOMEM;
|
||||
goto err_free_tx_buffers;
|
||||
|
|
@ -619,6 +670,8 @@ static int tbstream_dev_start(struct tbstream_dev *sdev)
|
|||
tb_ring_throttling(sdev->tx_ring.ring, sdev->throttling);
|
||||
tb_ring_throttling(sdev->rx_ring.ring, sdev->throttling);
|
||||
|
||||
sdev->rx_pending = false;
|
||||
|
||||
tb_ring_start(sdev->tx_ring.ring);
|
||||
tb_ring_start(sdev->rx_ring.ring);
|
||||
|
||||
|
|
@ -665,19 +718,16 @@ static void tbstream_dev_stop(struct tbstream_dev *sdev)
|
|||
do {
|
||||
if (tbstream_dev_tx_drained(sdev))
|
||||
break;
|
||||
tbstream_dev_poll_ring(sdev, &sdev->tx_ring);
|
||||
tbstream_ring_poll(&sdev->tx_ring);
|
||||
fsleep(15);
|
||||
} while (ktime_before(ktime_get(), timeout));
|
||||
|
||||
tb_ring_stop(sdev->tx_ring.ring);
|
||||
tb_ring_stop(sdev->rx_ring.ring);
|
||||
} else {
|
||||
tb_ring_flush(sdev->tx_ring.ring, 500);
|
||||
tb_ring_stop(sdev->tx_ring.ring);
|
||||
tb_ring_flush(sdev->rx_ring.ring, 500);
|
||||
tb_ring_stop(sdev->rx_ring.ring);
|
||||
}
|
||||
|
||||
tb_ring_stop(sdev->tx_ring.ring);
|
||||
tb_ring_stop(sdev->rx_ring.ring);
|
||||
|
||||
xd = tbstream_dev_xdomain(sdev);
|
||||
if (xd) {
|
||||
tb_xdomain_disable_paths(xd, sdev->out_hopid,
|
||||
|
|
@ -707,6 +757,45 @@ static int tbstream_dev_lock(struct tbstream_dev *sdev, bool nowait)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* Must not be called with @sdev->lock held */
|
||||
static int tbstream_dev_busy_poll_wait(struct tbstream_dev *sdev,
|
||||
struct tbstream_ring *ring)
|
||||
{
|
||||
for (;;) {
|
||||
if (signal_pending(current))
|
||||
return -ERESTARTSYS;
|
||||
if (tb_ring_poll_pending(ring->ring))
|
||||
return 0;
|
||||
/*
|
||||
* For TX ring we need to check the RX side too because
|
||||
* it might have received CLOSE packet.
|
||||
*/
|
||||
if (ring == &sdev->tx_ring &&
|
||||
tb_ring_poll_pending(sdev->rx_ring.ring))
|
||||
return 0;
|
||||
if (tbstream_dev_valid(sdev) != 0 ||
|
||||
tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev))
|
||||
return 0;
|
||||
cond_resched();
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
tbstream_dev_has_event(struct tbstream_dev *sdev, struct tbstream_ring *ring)
|
||||
{
|
||||
if (tbstream_dev_valid(sdev) != 0)
|
||||
return true;
|
||||
if (tbstream_dev_closed(sdev))
|
||||
return true;
|
||||
if (tbstream_dev_removed(sdev))
|
||||
return true;
|
||||
if (tbstream_dev_rx_pending(sdev))
|
||||
return true;
|
||||
if (tbstream_ring_available(ring))
|
||||
return true;
|
||||
return tb_ring_poll_pending(sdev->rx_ring.ring);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to)
|
||||
{
|
||||
|
|
@ -725,8 +814,8 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to)
|
|||
return ret;
|
||||
|
||||
for (;;) {
|
||||
/* When busy polling, advance any completions manually */
|
||||
tbstream_dev_poll_ring(sdev, &sdev->rx_ring);
|
||||
/* Advance RX completions */
|
||||
tbstream_dev_advance_rx(sdev);
|
||||
|
||||
ret = tbstream_dev_valid(sdev);
|
||||
if (ret) {
|
||||
|
|
@ -742,21 +831,20 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to)
|
|||
if (tbstream_ring_available(&sdev->rx_ring))
|
||||
break;
|
||||
|
||||
/* Polled all we could. Re-enable the interrupt now. */
|
||||
tbstream_dev_complete_rx(sdev);
|
||||
mutex_unlock(&sdev->lock);
|
||||
|
||||
if (nowait)
|
||||
return -EAGAIN;
|
||||
|
||||
if (sdev->busy_poll) {
|
||||
if (signal_pending(current))
|
||||
return -ERESTARTSYS;
|
||||
cond_resched();
|
||||
ret = tbstream_dev_busy_poll_wait(sdev, &sdev->rx_ring);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else {
|
||||
ret = wait_event_interruptible(sdev->wait,
|
||||
tbstream_ring_available(&sdev->rx_ring) ||
|
||||
tbstream_dev_valid(sdev) != 0 ||
|
||||
tbstream_dev_closed(sdev) ||
|
||||
tbstream_dev_removed(sdev));
|
||||
tbstream_dev_has_event(sdev, &sdev->rx_ring));
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -783,7 +871,20 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to)
|
|||
if (sf->frame.eof == TBSTREAM_CLOSE) {
|
||||
if (!nbytes) {
|
||||
tbstream_dev_consume_rx(sdev);
|
||||
sdev->closed = true;
|
||||
WRITE_ONCE(sdev->closed, true);
|
||||
}
|
||||
break;
|
||||
} else if (sf->frame.flags &
|
||||
(RING_DESC_CRC_ERROR | RING_DESC_BUFFER_OVERRUN)) {
|
||||
/*
|
||||
* If something was already read return that now
|
||||
* and next read will report the error.
|
||||
*/
|
||||
if (!nbytes) {
|
||||
pr_warn("corrupted frame received, flags %#x\n",
|
||||
sf->frame.flags);
|
||||
tbstream_dev_consume_rx(sdev);
|
||||
ret = -EIO;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -819,6 +920,25 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to)
|
|||
return nbytes;
|
||||
}
|
||||
|
||||
static void tbstream_dev_advance_both(struct tbstream_dev *sdev)
|
||||
{
|
||||
/*
|
||||
* When busy polling, advance TX completions manually.
|
||||
*
|
||||
* We also need to advance the RX side in both modes to be able
|
||||
* to receive CLOSE packet from the other peer if there is no
|
||||
* reader.
|
||||
*/
|
||||
if (sdev->busy_poll) {
|
||||
tbstream_ring_poll(&sdev->tx_ring);
|
||||
tbstream_dev_advance_rx(sdev);
|
||||
} else if (tbstream_dev_rx_pending(sdev) ||
|
||||
tb_ring_poll_pending(sdev->rx_ring.ring)) {
|
||||
tbstream_dev_advance_rx(sdev);
|
||||
tbstream_dev_complete_rx(sdev);
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from)
|
||||
{
|
||||
|
|
@ -837,7 +957,8 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from)
|
|||
return ret;
|
||||
|
||||
for (;;) {
|
||||
tbstream_dev_poll_ring(sdev, &sdev->tx_ring);
|
||||
/* Advance TX (and RX) completions */
|
||||
tbstream_dev_advance_both(sdev);
|
||||
|
||||
ret = tbstream_dev_valid(sdev);
|
||||
if (ret) {
|
||||
|
|
@ -845,7 +966,12 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from)
|
|||
return ret;
|
||||
}
|
||||
|
||||
if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) {
|
||||
if (tbstream_dev_close_received(sdev)) {
|
||||
mutex_unlock(&sdev->lock);
|
||||
return -EPIPE;
|
||||
}
|
||||
|
||||
if (tbstream_dev_removed(sdev)) {
|
||||
mutex_unlock(&sdev->lock);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
|
@ -859,15 +985,13 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from)
|
|||
return -EAGAIN;
|
||||
|
||||
if (sdev->busy_poll) {
|
||||
if (signal_pending(current))
|
||||
return -ERESTARTSYS;
|
||||
cond_resched();
|
||||
ret = tbstream_dev_busy_poll_wait(sdev, &sdev->tx_ring);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else {
|
||||
ret = wait_event_interruptible(sdev->wait,
|
||||
tbstream_ring_available(&sdev->tx_ring) ||
|
||||
tbstream_dev_valid(sdev) != 0 ||
|
||||
tbstream_dev_closed(sdev) ||
|
||||
tbstream_dev_removed(sdev));
|
||||
tbstream_dev_has_event(sdev, &sdev->tx_ring) ||
|
||||
tbstream_dev_close_received(sdev));
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -917,14 +1041,28 @@ tbstream_dev_fops_poll(struct file *file, struct poll_table_struct *wait)
|
|||
|
||||
poll_wait(file, &sdev->wait, wait);
|
||||
guard(mutex)(&sdev->lock);
|
||||
if (tbstream_dev_valid(sdev) != 0) {
|
||||
mask |= EPOLLHUP | EPOLLERR;
|
||||
if (tbstream_dev_valid(sdev) != 0)
|
||||
return EPOLLHUP | EPOLLERR;
|
||||
|
||||
/*
|
||||
* The RX completions are only advanced from here and from
|
||||
* read(2) so do that now, otherwise we would never report
|
||||
* anything to be available.
|
||||
*/
|
||||
tbstream_dev_advance_rx(sdev);
|
||||
|
||||
if (tbstream_dev_close_received(sdev))
|
||||
mask |= EPOLLHUP;
|
||||
if (tbstream_ring_available(&sdev->tx_ring))
|
||||
mask |= EPOLLOUT | EPOLLWRNORM;
|
||||
if (tbstream_ring_available(&sdev->rx_ring)) {
|
||||
mask |= EPOLLIN | EPOLLRDNORM;
|
||||
} else {
|
||||
if (tbstream_ring_available(&sdev->tx_ring))
|
||||
mask |= EPOLLOUT | EPOLLWRNORM;
|
||||
if (tbstream_ring_available(&sdev->rx_ring))
|
||||
tbstream_dev_complete_rx(sdev);
|
||||
if (tb_ring_poll_pending(sdev->rx_ring.ring))
|
||||
mask |= EPOLLIN | EPOLLRDNORM;
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
|
|
@ -974,6 +1112,8 @@ static int tbstream_dev_fops_open(struct inode *inode, struct file *file)
|
|||
sdev->users--;
|
||||
goto err_unlock;
|
||||
}
|
||||
|
||||
sdev->close_received = false;
|
||||
sdev->closed = false;
|
||||
}
|
||||
|
||||
|
|
@ -998,11 +1138,18 @@ static int tbstream_dev_fops_release(struct inode *inode, struct file *file)
|
|||
mutex_lock(&sdev->lock);
|
||||
if (--sdev->users == 0) {
|
||||
/*
|
||||
* Send CLOSE tunneled packet to notify the other end
|
||||
* that we are closing the file. We do this twice if the
|
||||
* first one fails.
|
||||
* Advance now in case there is CLOSE waiting in the RX
|
||||
* ring.
|
||||
*/
|
||||
tbstream_dev_send_close(sdev);
|
||||
tbstream_dev_advance_both(sdev);
|
||||
/*
|
||||
* Send CLOSE tunneled packet to notify the other end
|
||||
* that we are closing the file, if it is not closed
|
||||
* already.
|
||||
*/
|
||||
if (!tbstream_dev_close_received(sdev) &&
|
||||
tbstream_dev_valid(sdev) == 0)
|
||||
tbstream_dev_send_close(sdev);
|
||||
tbstream_dev_stop(sdev);
|
||||
}
|
||||
mutex_unlock(&sdev->lock);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@
|
|||
|
||||
#include "tb.h"
|
||||
|
||||
/* How long a hop is given to drain when a path is deactivated */
|
||||
#define TB_PORT_PENDING_TIMEOUT 500 /* ms */
|
||||
|
||||
/* Switch NVM support */
|
||||
|
||||
struct nvm_auth_status {
|
||||
|
|
@ -712,6 +715,7 @@ static int tb_init_port(struct tb_port *port)
|
|||
int cap;
|
||||
|
||||
INIT_LIST_HEAD(&port->list);
|
||||
port->pp_timeout_msec = TB_PORT_PENDING_TIMEOUT;
|
||||
|
||||
/* Control adapter does not have configuration space */
|
||||
if (!port->port)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
#include <linux/errno.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/platform_data/x86/apple.h>
|
||||
#include <linux/stringify.h>
|
||||
|
||||
#include "tb.h"
|
||||
|
|
@ -3335,75 +3334,6 @@ static const struct tb_cm_ops tb_cm_ops = {
|
|||
.disconnect_xdomain_paths = tb_disconnect_xdomain_paths,
|
||||
};
|
||||
|
||||
/*
|
||||
* During suspend the Thunderbolt controller is reset and all PCIe
|
||||
* tunnels are lost. The NHI driver will try to reestablish all tunnels
|
||||
* during resume. This adds device links between the tunneled PCIe
|
||||
* downstream ports and the NHI so that the device core will make sure
|
||||
* NHI is resumed first before the rest.
|
||||
*/
|
||||
static bool tb_apple_add_links(struct tb_nhi *nhi)
|
||||
{
|
||||
struct pci_dev *nhi_pdev = to_pci_dev(nhi->dev);
|
||||
struct pci_dev *upstream, *pdev;
|
||||
bool ret;
|
||||
|
||||
if (!x86_apple_machine)
|
||||
return false;
|
||||
|
||||
switch (nhi_pdev->device) {
|
||||
case PCI_DEVICE_ID_INTEL_LIGHT_RIDGE:
|
||||
case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C:
|
||||
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI:
|
||||
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
upstream = pci_upstream_bridge(nhi_pdev);
|
||||
while (upstream) {
|
||||
if (!pci_is_pcie(upstream))
|
||||
return false;
|
||||
if (pci_pcie_type(upstream) == PCI_EXP_TYPE_UPSTREAM)
|
||||
break;
|
||||
upstream = pci_upstream_bridge(upstream);
|
||||
}
|
||||
|
||||
if (!upstream)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* For each hotplug downstream port, create add device link
|
||||
* back to NHI so that PCIe tunnels can be re-established after
|
||||
* sleep.
|
||||
*/
|
||||
ret = false;
|
||||
for_each_pci_bridge(pdev, upstream->subordinate) {
|
||||
const struct device_link *link;
|
||||
|
||||
if (!pci_is_pcie(pdev))
|
||||
continue;
|
||||
if (pci_pcie_type(pdev) != PCI_EXP_TYPE_DOWNSTREAM ||
|
||||
!pdev->is_pciehp)
|
||||
continue;
|
||||
|
||||
link = device_link_add(&pdev->dev, nhi->dev,
|
||||
DL_FLAG_AUTOREMOVE_SUPPLIER |
|
||||
DL_FLAG_PM_RUNTIME);
|
||||
if (link) {
|
||||
dev_dbg(nhi->dev, "created link from %s\n",
|
||||
dev_name(&pdev->dev));
|
||||
ret = true;
|
||||
} else {
|
||||
dev_warn(nhi->dev, "device link creation from %s failed\n",
|
||||
dev_name(&pdev->dev));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct tb *tb_probe(struct tb_nhi *nhi)
|
||||
{
|
||||
struct tb_cm *tcm;
|
||||
|
|
@ -3428,13 +3358,5 @@ struct tb *tb_probe(struct tb_nhi *nhi)
|
|||
|
||||
tb_dbg(tb, "using software connection manager\n");
|
||||
|
||||
/*
|
||||
* Device links are needed to make sure we establish tunnels
|
||||
* before the PCIe/USB stack is resumed so complain here if we
|
||||
* found them missing.
|
||||
*/
|
||||
if (!tb_apple_add_links(nhi) && !tb_acpi_add_links(nhi))
|
||||
tb_warn(tb, "device links to tunneled native ports are missing!\n");
|
||||
|
||||
return tb;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -273,6 +273,8 @@ struct tb_bandwidth_group {
|
|||
* @max_bw: Maximum possible bandwidth through this adapter if set to
|
||||
* non-zero.
|
||||
* @redrive: For DP IN, if true the adapter is in redrive mode.
|
||||
* @pp_timeout_msec: How long a hop of this adapter is given to drain when a
|
||||
* path is deactivated. %0 means a single read.
|
||||
*
|
||||
* In USB4 terminology this structure represents an adapter (protocol or
|
||||
* lane adapter).
|
||||
|
|
@ -302,6 +304,7 @@ struct tb_port {
|
|||
struct list_head group_list;
|
||||
unsigned int max_bw;
|
||||
bool redrive;
|
||||
unsigned int pp_timeout_msec;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1439,7 +1442,7 @@ int usb4_port_sw_margin(struct tb_port *port, enum usb4_sb_target target,
|
|||
u8 index, const struct usb4_port_margining_params *params,
|
||||
u32 *results);
|
||||
int usb4_port_sw_margin_errors(struct tb_port *port, enum usb4_sb_target target,
|
||||
u8 index, u32 *errors);
|
||||
u8 index, u32 *errors, size_t dwords);
|
||||
|
||||
int usb4_port_retimer_set_inbound_sbtx(struct tb_port *port, u8 index);
|
||||
int usb4_port_retimer_unset_inbound_sbtx(struct tb_port *port, u8 index);
|
||||
|
|
|
|||
|
|
@ -1826,23 +1826,27 @@ int usb4_port_sw_margin(struct tb_port *port, enum usb4_sb_target target,
|
|||
* @target: Sideband target
|
||||
* @index: Retimer index if target is %USB4_SB_TARGET_RETIMER
|
||||
* @errors: Error metadata is copied here.
|
||||
* @dwords: Number of dwords to read error counter values
|
||||
*
|
||||
* This reads back the software margining error counters from the port.
|
||||
*
|
||||
* Return: %0 on success, negative errno otherwise.
|
||||
*/
|
||||
int usb4_port_sw_margin_errors(struct tb_port *port, enum usb4_sb_target target,
|
||||
u8 index, u32 *errors)
|
||||
u8 index, u32 *errors, size_t dwords)
|
||||
{
|
||||
int ret;
|
||||
u8 reg;
|
||||
|
||||
ret = usb4_port_sb_op(port, target, index,
|
||||
USB4_SB_OPCODE_READ_SW_MARGIN_ERR, 150);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return usb4_port_sb_read(port, target, index, USB4_SB_METADATA, errors,
|
||||
sizeof(*errors));
|
||||
reg = (dwords > 1) ? USB4_SB_DATA : USB4_SB_METADATA;
|
||||
|
||||
return usb4_port_sb_read(port, target, index, reg, errors,
|
||||
sizeof(*errors) * dwords);
|
||||
}
|
||||
|
||||
static inline int usb4_port_retimer_op(struct tb_port *port, u8 index,
|
||||
|
|
|
|||
|
|
@ -449,10 +449,8 @@ int cdns_init(struct cdns *cdns)
|
|||
IRQF_SHARED,
|
||||
dev_name(cdns->dev), cdns);
|
||||
|
||||
if (ret) {
|
||||
dev_err(cdns->dev, "couldn't register wakeup irq handler\n");
|
||||
if (ret)
|
||||
goto role_switch_unregister;
|
||||
}
|
||||
}
|
||||
|
||||
ret = cdns_drd_init(cdns);
|
||||
|
|
|
|||
|
|
@ -509,10 +509,8 @@ int cdns_drd_init(struct cdns *cdns)
|
|||
cdns_drd_thread_irq,
|
||||
IRQF_SHARED,
|
||||
dev_name(cdns->dev), cdns);
|
||||
if (ret) {
|
||||
dev_err(cdns->dev, "couldn't get otg_irq\n");
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
state = readl(&cdns->otg_regs->sts);
|
||||
if (OTGSTS_OTG_NRDY(state)) {
|
||||
|
|
|
|||
|
|
@ -525,11 +525,15 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
|
|||
data->supports_runtime_pm = true;
|
||||
|
||||
data->wakeup_irq = platform_get_irq_optional(pdev, 1);
|
||||
if (data->wakeup_irq < 0 && data->wakeup_irq != -ENXIO) {
|
||||
ret = data->wakeup_irq;
|
||||
goto phy_shutdown;
|
||||
}
|
||||
if (data->wakeup_irq > 0) {
|
||||
irq_name = devm_kasprintf(dev, GFP_KERNEL, "%s:wakeup", pdata.name);
|
||||
if (!irq_name) {
|
||||
ret = dev_err_probe(dev, -ENOMEM, "failed to create irq_name\n");
|
||||
goto err_clk;
|
||||
goto phy_shutdown;
|
||||
}
|
||||
|
||||
ret = devm_request_threaded_irq(dev, data->wakeup_irq,
|
||||
|
|
@ -537,7 +541,7 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
|
|||
IRQF_ONESHOT | IRQF_NO_AUTOEN,
|
||||
irq_name, data);
|
||||
if (ret)
|
||||
goto err_clk;
|
||||
goto phy_shutdown;
|
||||
}
|
||||
|
||||
ret = imx_usbmisc_init(data->usbmisc_data);
|
||||
|
|
|
|||
|
|
@ -705,7 +705,7 @@ static void ci_otg_fsm_event(struct ci_hdrc *ci)
|
|||
if ((intr_sts & USBi_PCI) && !port_conn) {
|
||||
fsm->b_conn = 0;
|
||||
|
||||
/* if gadget driver is binded */
|
||||
/* if gadget driver is bound */
|
||||
if (ci->driver) {
|
||||
/* A device to be peripheral mode */
|
||||
ci->gadget.is_a_peripheral = 1;
|
||||
|
|
|
|||
|
|
@ -1862,7 +1862,7 @@ static int ci_udc_get_frame(struct usb_gadget *_gadget)
|
|||
}
|
||||
|
||||
/*
|
||||
* ci_hdrc_gadget_connect: caller makes sure gadget driver is binded
|
||||
* ci_hdrc_gadget_connect: caller makes sure gadget driver is bound
|
||||
*/
|
||||
static void ci_hdrc_gadget_connect(struct usb_gadget *_gadget, int is_active)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -782,6 +782,7 @@ static void acm_port_shutdown(struct tty_port *port)
|
|||
urb = usb_get_from_anchor(&acm->delayed);
|
||||
if (!urb)
|
||||
break;
|
||||
usb_put_urb(urb);
|
||||
wb = urb->context;
|
||||
wb->use = false;
|
||||
usb_autopm_put_interface_async(acm->control);
|
||||
|
|
@ -1710,7 +1711,7 @@ static int acm_resume(struct usb_interface *intf)
|
|||
urb = usb_get_from_anchor(&acm->delayed);
|
||||
if (!urb)
|
||||
break;
|
||||
|
||||
usb_put_urb(urb);
|
||||
acm_start_wb(acm, urb->context);
|
||||
}
|
||||
|
||||
|
|
@ -1818,6 +1819,9 @@ static const struct usb_device_id acm_ids[] = {
|
|||
{ USB_DEVICE(0x11ca, 0x0201), /* VeriFone Mx870 Gadget Serial */
|
||||
.driver_info = SINGLE_RX_URB,
|
||||
},
|
||||
{ USB_DEVICE(0x11ca, 0x0219), /* VeriFone Vx820 Gadget Serial */
|
||||
.driver_info = SINGLE_RX_URB,
|
||||
},
|
||||
{ USB_DEVICE(0x1901, 0x0006), /* GE Healthcare Patient Monitor UI Controller */
|
||||
.driver_info = DISABLE_ECHO, /* DISABLE ECHO in termios flag */
|
||||
},
|
||||
|
|
|
|||
|
|
@ -147,15 +147,17 @@ static const struct device_type ulpi_dev_type = {
|
|||
* __ulpi_register_driver - register a driver with the ULPI bus
|
||||
* @drv: driver being registered
|
||||
* @module: ends up being THIS_MODULE
|
||||
* @mod_name: ends up being KBUILD_MODNAME
|
||||
*
|
||||
* Registers a driver with the ULPI bus.
|
||||
*/
|
||||
int __ulpi_register_driver(struct ulpi_driver *drv, struct module *module)
|
||||
int __ulpi_register_driver(struct ulpi_driver *drv, struct module *module, const char *mod_name)
|
||||
{
|
||||
if (!drv->probe)
|
||||
return -EINVAL;
|
||||
|
||||
drv->driver.owner = module;
|
||||
drv->driver.mod_name = mod_name;
|
||||
drv->driver.bus = &ulpi_bus;
|
||||
|
||||
return driver_register(&drv->driver);
|
||||
|
|
|
|||
|
|
@ -251,10 +251,8 @@ static int usb_conn_probe(struct platform_device *pdev)
|
|||
ret = devm_request_threaded_irq(dev, info->id_irq, NULL,
|
||||
usb_conn_isr, USB_CONN_IRQF,
|
||||
pdev->name, info);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to request ID IRQ\n");
|
||||
if (ret < 0)
|
||||
goto put_role_sw;
|
||||
}
|
||||
}
|
||||
|
||||
if (info->vbus_gpiod) {
|
||||
|
|
@ -268,10 +266,8 @@ static int usb_conn_probe(struct platform_device *pdev)
|
|||
ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL,
|
||||
usb_conn_isr, USB_CONN_IRQF,
|
||||
pdev->name, info);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to request VBUS IRQ\n");
|
||||
if (ret < 0)
|
||||
goto put_role_sw;
|
||||
}
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, info);
|
||||
|
|
|
|||
|
|
@ -410,7 +410,6 @@ static int usb_probe_interface(struct device *dev)
|
|||
return error;
|
||||
|
||||
err:
|
||||
usb_set_intfdata(intf, NULL);
|
||||
intf->needs_remote_wakeup = 0;
|
||||
intf->condition = USB_INTERFACE_UNBOUND;
|
||||
|
||||
|
|
@ -497,7 +496,6 @@ static int usb_unbind_interface(struct device *dev)
|
|||
} else {
|
||||
intf->needs_altsetting0 = 1;
|
||||
}
|
||||
usb_set_intfdata(intf, NULL);
|
||||
|
||||
intf->condition = USB_INTERFACE_UNBOUND;
|
||||
intf->needs_remote_wakeup = 0;
|
||||
|
|
@ -644,6 +642,7 @@ void usb_driver_release_interface(struct usb_driver *driver,
|
|||
} else {
|
||||
device_lock(dev);
|
||||
usb_unbind_interface(dev);
|
||||
dev_set_drvdata(dev, NULL);
|
||||
dev->driver = NULL;
|
||||
device_unlock(dev);
|
||||
}
|
||||
|
|
@ -984,9 +983,10 @@ bool is_usb_device_driver(const struct device_driver *drv)
|
|||
}
|
||||
|
||||
/**
|
||||
* usb_register_device_driver - register a USB device (not interface) driver
|
||||
* __usb_register_device_driver - register a USB device (not interface) driver
|
||||
* @new_udriver: USB operations for the device driver
|
||||
* @owner: module owner of this driver.
|
||||
* @mod_name: module name string
|
||||
*
|
||||
* Registers a USB device driver with the USB core. The list of
|
||||
* unattached devices will be rescanned whenever a new driver is
|
||||
|
|
@ -994,8 +994,8 @@ bool is_usb_device_driver(const struct device_driver *drv)
|
|||
*
|
||||
* Return: A negative error code on failure and 0 on success.
|
||||
*/
|
||||
int usb_register_device_driver(struct usb_device_driver *new_udriver,
|
||||
struct module *owner)
|
||||
int __usb_register_device_driver(struct usb_device_driver *new_udriver,
|
||||
struct module *owner, const char *mod_name)
|
||||
{
|
||||
int retval = 0;
|
||||
|
||||
|
|
@ -1007,6 +1007,7 @@ int usb_register_device_driver(struct usb_device_driver *new_udriver,
|
|||
new_udriver->driver.probe = usb_probe_device;
|
||||
new_udriver->driver.remove = usb_unbind_device;
|
||||
new_udriver->driver.owner = owner;
|
||||
new_udriver->driver.mod_name = mod_name;
|
||||
new_udriver->driver.dev_groups = new_udriver->dev_groups;
|
||||
|
||||
retval = driver_register(&new_udriver->driver);
|
||||
|
|
@ -1027,7 +1028,7 @@ int usb_register_device_driver(struct usb_device_driver *new_udriver,
|
|||
|
||||
return retval;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(usb_register_device_driver);
|
||||
EXPORT_SYMBOL_GPL(__usb_register_device_driver);
|
||||
|
||||
/**
|
||||
* usb_deregister_device_driver - unregister a USB device (not interface) driver
|
||||
|
|
|
|||
|
|
@ -2416,7 +2416,7 @@ static void announce_device_ids(struct usb_device *udev)
|
|||
u16 bcdDevice = le16_to_cpu(udev->descriptor.bcdDevice);
|
||||
|
||||
dev_info(&udev->dev,
|
||||
"New USB device found, idVendor=%04x, idProduct=%04x, bcdDevice=%2x.%02x\n",
|
||||
"New USB device found: idVendor=%04x, idProduct=%04x, bcdDevice=%x.%02x\n",
|
||||
le16_to_cpu(udev->descriptor.idVendor),
|
||||
le16_to_cpu(udev->descriptor.idProduct),
|
||||
bcdDevice >> 8, bcdDevice & 0xff);
|
||||
|
|
|
|||
|
|
@ -2208,6 +2208,11 @@ int usb_set_configuration(struct usb_device *dev, int configuration)
|
|||
pm_runtime_no_callbacks(&intf->dev);
|
||||
dev_set_name(&intf->dev, "%d-%s:%d.%d", dev->bus->busnum,
|
||||
dev->devpath, configuration, ifnum);
|
||||
/*
|
||||
* Make sure the interface does not outlive its parent (e.g.
|
||||
* as needed for usb_queue_reset_device() and
|
||||
* usb_if_device_type->need_parent_lock).
|
||||
*/
|
||||
usb_get_dev(dev);
|
||||
}
|
||||
kfree(new_interfaces);
|
||||
|
|
|
|||
|
|
@ -612,6 +612,9 @@ static const struct usb_device_id usb_quirk_list[] = {
|
|||
/* ezcap401 - BOS descriptor fetch hangs at SuperSpeed Plus */
|
||||
{ USB_DEVICE(0x32ed, 0x0401), .driver_info = USB_QUIRK_NO_BOS },
|
||||
|
||||
/* GameSir G7 SE */
|
||||
{ USB_DEVICE(0x3537, 0x10a0), .driver_info = USB_QUIRK_WINDOWS_CONFIG_REQ_SIZE },
|
||||
|
||||
/* DELL USB GEN2 */
|
||||
{ USB_DEVICE(0x413c, 0xb062), .driver_info = USB_QUIRK_NO_LPM | USB_QUIRK_RESET_RESUME },
|
||||
|
||||
|
|
|
|||
|
|
@ -254,6 +254,14 @@ EXPORT_SYMBOL_GPL(usb_urb_ep_type_check);
|
|||
* request. The completion handler may then immediately free or reuse that
|
||||
* URB.
|
||||
*
|
||||
* The USB core makes no guarantees at all about the context in which
|
||||
* the complete() callback is invoked. It may be in process context,
|
||||
* interrupt context, BH context, or anything else. Also, local interrupts
|
||||
* may be enabled or disabled. To deal with this, the callback routine
|
||||
* must always use the _irqsave and _irqrestore variants of things like
|
||||
* spin_lock(), and it must always use GFP_ATOMIC for its memory
|
||||
* allocation flags.
|
||||
*
|
||||
* With few exceptions, USB device drivers should never access URB fields
|
||||
* provided by usbcore or the HCD until its complete() is called.
|
||||
* The exceptions relate to periodic transfer scheduling. For both
|
||||
|
|
@ -951,8 +959,8 @@ EXPORT_SYMBOL_GPL(usb_wait_anchor_empty_timeout);
|
|||
* usb_get_from_anchor - get an anchor's oldest urb
|
||||
* @anchor: the anchor whose urb you want
|
||||
*
|
||||
* This will take the oldest urb from an anchor,
|
||||
* unanchor and return it
|
||||
* This will take the oldest urb from an anchor, unanchor and return it with
|
||||
* a reference held.
|
||||
*
|
||||
* Return: The oldest urb from @anchor, or %NULL if @anchor has no
|
||||
* urbs associated with it.
|
||||
|
|
|
|||
|
|
@ -1241,7 +1241,7 @@ static int __init usb_init(void)
|
|||
retval = usb_hub_init();
|
||||
if (retval)
|
||||
goto hub_init_failed;
|
||||
retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
|
||||
retval = usb_register_device_driver(&usb_generic_driver);
|
||||
if (!retval)
|
||||
goto out;
|
||||
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ static void dwc2_restore_essential_regs(struct dwc2_hsotg *hsotg, int rmode,
|
|||
/* Umnask global Interrupt in GAHBCFG and restore it */
|
||||
dwc2_writel(hsotg, gr->gahbcfg | GAHBCFG_GLBL_INTR_EN, GAHBCFG);
|
||||
|
||||
/* Clear all pending interupts */
|
||||
/* Clear all pending interrupts */
|
||||
dwc2_writel(hsotg, 0xffffffff, GINTSTS);
|
||||
|
||||
/* Unmask restore done interrupt */
|
||||
|
|
|
|||
|
|
@ -334,7 +334,7 @@ enum dwc2_ep0_state {
|
|||
* (default when phy_type is Full Speed)
|
||||
* @oc_disable: Flag to disable overcurrent condition.
|
||||
* 0 - Allow overcurrent condition to get detected
|
||||
* 1 - Disable overcurrent condtion to get detected
|
||||
* 1 - Disable overcurrent condition to get detected
|
||||
* @ts_dline: Enable Term Select Dline pulsing
|
||||
* 0 - No (default)
|
||||
* 1 - Yes
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include <linux/spinlock.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#include "core.h"
|
||||
|
|
@ -787,8 +788,7 @@ int dwc2_debugfs_init(struct dwc2_hsotg *hsotg)
|
|||
/* Add gadget debugfs nodes */
|
||||
dwc2_hsotg_create_debug(hsotg);
|
||||
|
||||
hsotg->regset = devm_kzalloc(hsotg->dev, sizeof(*hsotg->regset),
|
||||
GFP_KERNEL);
|
||||
hsotg->regset = kzalloc_obj(*hsotg->regset, GFP_KERNEL);
|
||||
if (!hsotg->regset) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
|
|
@ -810,4 +810,6 @@ void dwc2_debugfs_exit(struct dwc2_hsotg *hsotg)
|
|||
{
|
||||
debugfs_remove_recursive(hsotg->debug_root);
|
||||
hsotg->debug_root = NULL;
|
||||
kfree(hsotg->regset);
|
||||
hsotg->regset = NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -658,7 +658,7 @@ static int dwc2_hsotg_write_fifo(struct dwc2_hsotg *hsotg,
|
|||
}
|
||||
|
||||
/**
|
||||
* get_ep_limit - get the maximum data legnth for this endpoint
|
||||
* get_ep_limit - get the maximum data length for this endpoint
|
||||
* @hs_ep: The endpoint
|
||||
*
|
||||
* Return the maximum data that can be queued in one go on a given endpoint
|
||||
|
|
@ -1780,6 +1780,7 @@ static int dwc2_hsotg_process_req_feature(struct dwc2_hsotg *hsotg,
|
|||
struct dwc2_hsotg_ep *ep;
|
||||
int ret;
|
||||
bool halted;
|
||||
u32 otgctl;
|
||||
u32 recip;
|
||||
u32 wValue;
|
||||
u32 wIndex;
|
||||
|
|
@ -1809,6 +1810,36 @@ static int dwc2_hsotg_process_req_feature(struct dwc2_hsotg *hsotg,
|
|||
|
||||
hsotg->test_mode = wIndex >> 8;
|
||||
break;
|
||||
case USB_DEVICE_B_HNP_ENABLE:
|
||||
if (!hsotg->params.otg_caps.hnp_support)
|
||||
return -ENOENT;
|
||||
if (!set)
|
||||
return -EINVAL;
|
||||
|
||||
otgctl = dwc2_readl(hsotg, GOTGCTL);
|
||||
otgctl |= GOTGCTL_DEVHNPEN;
|
||||
dwc2_writel(hsotg, otgctl, GOTGCTL);
|
||||
hsotg->gadget.b_hnp_enable = 1;
|
||||
dev_dbg(hsotg->dev, "HNP enabled\n");
|
||||
break;
|
||||
case USB_DEVICE_A_HNP_SUPPORT:
|
||||
if (!hsotg->params.otg_caps.hnp_support)
|
||||
return -ENOENT;
|
||||
if (!set)
|
||||
return -EINVAL;
|
||||
|
||||
hsotg->gadget.a_hnp_support = 1;
|
||||
dev_dbg(hsotg->dev, "a_hnp_support set\n");
|
||||
break;
|
||||
case USB_DEVICE_A_ALT_HNP_SUPPORT:
|
||||
if (!hsotg->params.otg_caps.hnp_support)
|
||||
return -ENOENT;
|
||||
if (!set)
|
||||
return -EINVAL;
|
||||
|
||||
hsotg->gadget.a_alt_hnp_support = 1;
|
||||
dev_dbg(hsotg->dev, "a_alt_hnp_support set\n");
|
||||
break;
|
||||
default:
|
||||
return -ENOENT;
|
||||
}
|
||||
|
|
@ -2267,6 +2298,7 @@ static void dwc2_hsotg_rx_data(struct dwc2_hsotg *hsotg, int ep_idx, int size)
|
|||
int to_read;
|
||||
int max_req;
|
||||
int read_ptr;
|
||||
u32 drain;
|
||||
|
||||
if (!hs_req) {
|
||||
u32 epctl = dwc2_readl(hsotg, DOEPCTL(ep_idx));
|
||||
|
|
@ -2286,30 +2318,46 @@ static void dwc2_hsotg_rx_data(struct dwc2_hsotg *hsotg, int ep_idx, int size)
|
|||
to_read = size;
|
||||
read_ptr = hs_req->req.actual;
|
||||
max_req = hs_req->req.length - read_ptr;
|
||||
drain = 0;
|
||||
|
||||
dev_dbg(hsotg->dev, "%s: read %d/%d, done %d/%d\n",
|
||||
__func__, to_read, max_req, read_ptr, hs_req->req.length);
|
||||
|
||||
if (to_read > max_req) {
|
||||
/*
|
||||
* more data appeared than we where willing
|
||||
* to deal with in this request.
|
||||
* More data appeared than we were willing to deal with in
|
||||
* this request. Keep only what fits in the request buffer
|
||||
* and discard the rest from the FIFO, instead of overwriting
|
||||
* bytes past the request buffer (a 64-byte packet into a
|
||||
* request with one byte left used to over-write 63 bytes past
|
||||
* its end).
|
||||
*/
|
||||
|
||||
/* currently we don't deal this */
|
||||
WARN_ON_ONCE(1);
|
||||
dev_dbg(hsotg->dev, "%s: packet %d > request space %d, dropping %d\n",
|
||||
__func__, to_read, max_req, to_read - max_req);
|
||||
drain = DIV_ROUND_UP(to_read - max_req, 4);
|
||||
to_read = max_req;
|
||||
}
|
||||
|
||||
hs_ep->total_data += to_read;
|
||||
hs_req->req.actual += to_read;
|
||||
to_read = DIV_ROUND_UP(to_read, 4);
|
||||
|
||||
/*
|
||||
* note, we might over-write the buffer end by 3 bytes depending on
|
||||
* alignment of the data.
|
||||
* Copy word-at-a-time so the request buffer is exactly filled and
|
||||
* never over-run by the 4-byte rounding of the previous FIFO bulk
|
||||
* read (which could also emit up to 3 bytes past the buffer end).
|
||||
*/
|
||||
dwc2_readl_rep(hsotg, EPFIFO(ep_idx),
|
||||
hs_req->req.buf + read_ptr, to_read);
|
||||
while (to_read > 0) {
|
||||
u32 word = dwc2_readl(hsotg, EPFIFO(ep_idx));
|
||||
unsigned int chunk = min_t(unsigned int, to_read, 4);
|
||||
|
||||
memcpy(hs_req->req.buf + read_ptr, &word, chunk);
|
||||
read_ptr += chunk;
|
||||
to_read -= chunk;
|
||||
}
|
||||
|
||||
/* drain the discarded bytes so the next FIFO event is a fresh packet */
|
||||
while (drain-- > 0)
|
||||
(void)dwc2_readl(hsotg, EPFIFO(ep_idx));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -3322,6 +3370,13 @@ void dwc2_hsotg_disconnect(struct dwc2_hsotg *hsotg)
|
|||
hsotg->connected = 0;
|
||||
hsotg->test_mode = 0;
|
||||
|
||||
if (hsotg->params.otg_caps.hnp_support) {
|
||||
hsotg->gadget.b_hnp_enable = 0;
|
||||
hsotg->gadget.a_hnp_support = 0;
|
||||
hsotg->gadget.a_alt_hnp_support = 0;
|
||||
dwc2_clear_bit(hsotg, GOTGCTL, GOTGCTL_DEVHNPEN);
|
||||
}
|
||||
|
||||
/* all endpoints should be shutdown */
|
||||
for (ep = 0; ep < hsotg->num_of_eps; ep++) {
|
||||
if (hsotg->eps_in[ep])
|
||||
|
|
@ -3416,9 +3471,18 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg,
|
|||
usbcfg &= ~GUSBCFG_TOUTCAL_MASK;
|
||||
usbcfg |= GUSBCFG_TOUTCAL(7);
|
||||
|
||||
/* remove the HNP/SRP and set the PHY */
|
||||
/*
|
||||
* Configure HNP/SRP capability from params (same idea as
|
||||
* dwc2_gusbcfg_init() for host). Unconditionally clearing these
|
||||
* bits leaves an HNP-capable OTG gadget unable to negotiate.
|
||||
*/
|
||||
usbcfg &= ~(GUSBCFG_SRPCAP | GUSBCFG_HNPCAP);
|
||||
dwc2_writel(hsotg, usbcfg, GUSBCFG);
|
||||
if (hsotg->params.otg_caps.hnp_support &&
|
||||
hsotg->params.otg_caps.srp_support)
|
||||
usbcfg |= GUSBCFG_HNPCAP;
|
||||
if (hsotg->params.otg_caps.srp_support)
|
||||
usbcfg |= GUSBCFG_SRPCAP;
|
||||
dwc2_writel(hsotg, usbcfg, GUSBCFG);
|
||||
|
||||
dwc2_phy_init(hsotg, true);
|
||||
|
||||
|
|
@ -5053,10 +5117,8 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg)
|
|||
|
||||
ret = devm_request_irq(hsotg->dev, hsotg->irq, dwc2_hsotg_irq,
|
||||
IRQF_SHARED, dev_name(hsotg->dev), hsotg);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "cannot claim IRQ for gadget\n");
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* hsotg->num_of_eps holds number of EPs other than ep0 */
|
||||
|
||||
|
|
@ -5501,7 +5563,7 @@ int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg,
|
|||
dwc2_hib_restore_common(hsotg, rem_wakeup, 0);
|
||||
|
||||
if (!reset) {
|
||||
/* Clear all pending interupts */
|
||||
/* Clear all pending interrupts */
|
||||
dwc2_writel(hsotg, 0xffffffff, GINTSTS);
|
||||
}
|
||||
|
||||
|
|
@ -5550,7 +5612,7 @@ int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg,
|
|||
}
|
||||
/* Wait for interrupts which must be cleared */
|
||||
mdelay(2);
|
||||
/* Clear all pending interupts */
|
||||
/* Clear all pending interrupts */
|
||||
dwc2_writel(hsotg, 0xffffffff, GINTSTS);
|
||||
|
||||
/* Restore global registers */
|
||||
|
|
|
|||
|
|
@ -5655,7 +5655,7 @@ int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup,
|
|||
*/
|
||||
mdelay(100);
|
||||
|
||||
/* Clear all pending interupts */
|
||||
/* Clear all pending interrupts */
|
||||
dwc2_writel(hsotg, 0xffffffff, GINTSTS);
|
||||
|
||||
/* De-assert Restore */
|
||||
|
|
@ -5725,7 +5725,7 @@ int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup,
|
|||
|
||||
hprt0 = dwc2_readl(hsotg, HPRT0);
|
||||
|
||||
/* Clear all pending interupts */
|
||||
/* Clear all pending interrupts */
|
||||
dwc2_writel(hsotg, 0xffffffff, GINTSTS);
|
||||
|
||||
/* Restore global registers */
|
||||
|
|
|
|||
|
|
@ -963,7 +963,7 @@ static void dwc2_complete_isoc_xfer_ddma(struct dwc2_hsotg *hsotg,
|
|||
* whether some of the descriptors (frames) succeeded or not.
|
||||
* Pass error code to completion routine as well, to update
|
||||
* urb->status, some of class drivers might use it to stop
|
||||
* queing transfer requests.
|
||||
* queuing transfer requests.
|
||||
*/
|
||||
int err = halt_status == DWC2_HC_XFER_AHB_ERR ?
|
||||
-EIO : -EOVERFLOW;
|
||||
|
|
|
|||
|
|
@ -367,14 +367,10 @@ static ssize_t dwc3_lsp_write(struct file *file, const char __user *ubuf,
|
|||
struct seq_file *s = file->private_data;
|
||||
struct dwc3 *dwc = s->private;
|
||||
unsigned long flags;
|
||||
char buf[32] = { 0 };
|
||||
u32 sel;
|
||||
int ret;
|
||||
|
||||
if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
|
||||
return -EFAULT;
|
||||
|
||||
ret = kstrtouint(buf, 0, &sel);
|
||||
ret = kstrtou32_from_user(ubuf, count, 0, &sel);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
|
|
|||
|
|
@ -235,10 +235,8 @@ static int dwc3_google_request_irq(struct dwc3_google *google, struct platform_d
|
|||
dwc3_google_resume_irq,
|
||||
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
|
||||
req_name, google);
|
||||
if (ret < 0) {
|
||||
dev_err(google->dev, "failed to request irq %s\n", req_name);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return irq;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ static int dwc3_imx_probe(struct platform_device *pdev)
|
|||
IRQF_ONESHOT | IRQF_NO_AUTOEN,
|
||||
irq_name, dwc_imx);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to request IRQ #%d\n", irq);
|
||||
return ret;
|
||||
|
||||
ret = device_add_software_node(dev, &dwc3_imx_swnode);
|
||||
if (ret)
|
||||
|
|
|
|||
|
|
@ -273,10 +273,8 @@ static int dwc3_imx8mp_probe(struct platform_device *pdev)
|
|||
|
||||
err = devm_request_threaded_irq(dev, irq, NULL, dwc3_imx8mp_interrupt,
|
||||
IRQF_ONESHOT, dev_name(dev), dwc3_imx);
|
||||
if (err) {
|
||||
dev_err(dev, "failed to request IRQ #%d --> %d\n", irq, err);
|
||||
if (err)
|
||||
goto put_dwc3;
|
||||
}
|
||||
|
||||
device_set_wakeup_capable(dev, true);
|
||||
pm_runtime_put(dev);
|
||||
|
|
|
|||
|
|
@ -144,11 +144,8 @@ static int kdwc3_probe(struct platform_device *pdev)
|
|||
|
||||
error = devm_request_irq(dev, irq, dwc3_keystone_interrupt, IRQF_SHARED,
|
||||
dev_name(dev), kdwc);
|
||||
if (error) {
|
||||
dev_err(dev, "failed to request IRQ #%d --> %d\n",
|
||||
irq, error);
|
||||
if (error)
|
||||
goto err_irq;
|
||||
}
|
||||
|
||||
kdwc3_enable_irqs(kdwc);
|
||||
|
||||
|
|
|
|||
|
|
@ -518,11 +518,8 @@ static int dwc3_omap_probe(struct platform_device *pdev)
|
|||
ret = devm_request_threaded_irq(dev, omap->irq, dwc3_omap_interrupt,
|
||||
dwc3_omap_interrupt_thread, IRQF_SHARED,
|
||||
"dwc3-omap", omap);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to request IRQ #%d --> %d\n",
|
||||
omap->irq, ret);
|
||||
if (ret)
|
||||
goto err2;
|
||||
}
|
||||
dwc3_omap_enable_irqs(omap);
|
||||
return 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,11 @@ if USB_FOTG210
|
|||
config USB_FOTG210_HCD
|
||||
bool "Faraday FOTG210 USB Host Controller support"
|
||||
depends on USB=y || USB=USB_FOTG210
|
||||
depends on USB_EHCI_HCD=y || USB_EHCI_HCD=USB_FOTG210
|
||||
select USB_EHCI_DEVIANT_PORT_STATUS_REG
|
||||
select USB_EHCI_PORT_RESET_HOOKS
|
||||
select USB_EHCI_PORT_SPEED_HOOK
|
||||
select USB_EHCI_ROOT_HUB_TT
|
||||
help
|
||||
Faraday FOTG210 is an OTG controller which can be configured as
|
||||
an USB2.0 host. It is designed to meet USB2.0 EHCI specification
|
||||
|
|
|
|||
|
|
@ -1,37 +1,50 @@
|
|||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Central probing code for the FOTG210 dual role driver
|
||||
* We register one driver for the hardware and then we decide
|
||||
* whether to proceed with probing the host or the peripheral
|
||||
* driver.
|
||||
* Central probing code for the FOTG210 dual-role controller.
|
||||
*
|
||||
* The role is selected once at probe time. The driver does not attempt to
|
||||
* switch between the host and peripheral blocks while it is running.
|
||||
*/
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/string_choices.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/usb/hcd.h>
|
||||
#include <linux/usb/otg.h>
|
||||
|
||||
#include "fotg210.h"
|
||||
|
||||
/* Role Register 0x80 */
|
||||
#define FOTG210_RR 0x80
|
||||
#define FOTG210_RR_ID BIT(21) /* 1 = B-device, 0 = A-device */
|
||||
#define FOTG210_RR_CROLE BIT(20) /* 1 = device, 0 = host */
|
||||
/* OTG control/status register. */
|
||||
#define FOTG210_OTGCSR 0x80
|
||||
#define FOTG210_OTGCSR_ID BIT(21)
|
||||
#define FOTG210_OTGCSR_CROLE BIT(20)
|
||||
#define FOTG210_OTGCSR_A_VBUS_VLD BIT(19)
|
||||
#define FOTG210_OTGCSR_A_BUS_DROP BIT(5)
|
||||
#define FOTG210_OTGCSR_A_BUS_REQ BIT(4)
|
||||
#define FOTG210_OTGCSR_B_HNP_EN BIT(1)
|
||||
|
||||
/*
|
||||
* Gemini-specific initialization function, only executed on the
|
||||
* Gemini SoC using the global misc control register.
|
||||
*
|
||||
* The gemini USB blocks are connected to either Mini-A (host mode) or
|
||||
* Mini-B (peripheral mode) plugs. There is no role switch support on the
|
||||
* Gemini SoC, just either-or.
|
||||
*/
|
||||
/* OTG interrupt status and enable registers. */
|
||||
#define FOTG210_OTGISR 0x84
|
||||
#define FOTG210_OTGIEN 0x88
|
||||
|
||||
/* Global interrupt mask register; set bits mask the corresponding source. */
|
||||
#define FOTG210_GINTM 0xc4
|
||||
#define FOTG210_GINTM_INT_POLARITY BIT(3)
|
||||
#define FOTG210_GINTM_MHC_INT BIT(2)
|
||||
#define FOTG210_GINTM_MOTG_INT BIT(1)
|
||||
#define FOTG210_GINTM_MDEV_INT BIT(0)
|
||||
|
||||
/* Gemini global miscellaneous-control register. */
|
||||
#define GEMINI_GLOBAL_MISC_CTRL 0x30
|
||||
#define GEMINI_MISC_USB0_WAKEUP BIT(14)
|
||||
#define GEMINI_MISC_USB1_WAKEUP BIT(15)
|
||||
|
|
@ -40,94 +53,133 @@
|
|||
#define GEMINI_MISC_USB0_MINI_B BIT(29)
|
||||
#define GEMINI_MISC_USB1_MINI_B BIT(30)
|
||||
|
||||
static int fotg210_gemini_init(struct fotg210 *fotg, struct resource *res,
|
||||
enum usb_dr_mode mode)
|
||||
static int fotg210_gemini_init(struct fotg210 *fotg)
|
||||
{
|
||||
struct device *dev = fotg->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
struct regmap *map;
|
||||
bool wakeup;
|
||||
bool wakeup = of_property_read_bool(np, "wakeup-source");
|
||||
u32 mask, val;
|
||||
int ret;
|
||||
|
||||
map = syscon_regmap_lookup_by_phandle(np, "syscon");
|
||||
if (IS_ERR(map))
|
||||
return dev_err_probe(dev, PTR_ERR(map), "no syscon\n");
|
||||
fotg->map = map;
|
||||
wakeup = of_property_read_bool(np, "wakeup-source");
|
||||
fotg->map = syscon_regmap_lookup_by_phandle(np, "syscon");
|
||||
if (IS_ERR(fotg->map))
|
||||
return dev_err_probe(dev, PTR_ERR(fotg->map), "no syscon\n");
|
||||
|
||||
/*
|
||||
* Figure out if this is USB0 or USB1 by simply checking the
|
||||
* physical base address.
|
||||
*/
|
||||
mask = 0;
|
||||
if (res->start == 0x69000000) {
|
||||
if (fotg->res->start == 0x69000000) {
|
||||
fotg->port = GEMINI_PORT_1;
|
||||
mask = GEMINI_MISC_USB1_VBUS_ON | GEMINI_MISC_USB1_MINI_B |
|
||||
GEMINI_MISC_USB1_WAKEUP;
|
||||
if (mode == USB_DR_MODE_HOST)
|
||||
val = GEMINI_MISC_USB1_VBUS_ON;
|
||||
else
|
||||
val = GEMINI_MISC_USB1_MINI_B;
|
||||
GEMINI_MISC_USB1_WAKEUP;
|
||||
val = fotg->mode == USB_DR_MODE_HOST ?
|
||||
GEMINI_MISC_USB1_VBUS_ON : GEMINI_MISC_USB1_MINI_B;
|
||||
if (wakeup)
|
||||
val |= GEMINI_MISC_USB1_WAKEUP;
|
||||
} else {
|
||||
fotg->port = GEMINI_PORT_0;
|
||||
mask = GEMINI_MISC_USB0_VBUS_ON | GEMINI_MISC_USB0_MINI_B |
|
||||
GEMINI_MISC_USB0_WAKEUP;
|
||||
if (mode == USB_DR_MODE_HOST)
|
||||
val = GEMINI_MISC_USB0_VBUS_ON;
|
||||
else
|
||||
val = GEMINI_MISC_USB0_MINI_B;
|
||||
GEMINI_MISC_USB0_WAKEUP;
|
||||
val = fotg->mode == USB_DR_MODE_HOST ?
|
||||
GEMINI_MISC_USB0_VBUS_ON : GEMINI_MISC_USB0_MINI_B;
|
||||
if (wakeup)
|
||||
val |= GEMINI_MISC_USB0_WAKEUP;
|
||||
}
|
||||
|
||||
ret = regmap_update_bits(map, GEMINI_GLOBAL_MISC_CTRL, mask, val);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to initialize Gemini PHY\n");
|
||||
return ret;
|
||||
}
|
||||
ret = regmap_update_bits(fotg->map, GEMINI_GLOBAL_MISC_CTRL,
|
||||
mask, val);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to initialize Gemini PHY\n");
|
||||
|
||||
dev_info(dev, "initialized Gemini PHY in %s mode\n",
|
||||
(mode == USB_DR_MODE_HOST) ? "host" : "gadget");
|
||||
fotg->mode == USB_DR_MODE_HOST ? "host" : "gadget");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* fotg210_vbus() - Called by gadget driver to enable/disable VBUS
|
||||
* @fotg: pointer to a private fotg210 object
|
||||
* @enable: true to enable VBUS, false to disable VBUS
|
||||
* fotg210_vbus() - enable or disable the A-device VBUS supply
|
||||
* @fotg: controller state
|
||||
* @enable: whether to drive VBUS
|
||||
*/
|
||||
void fotg210_vbus(struct fotg210 *fotg, bool enable)
|
||||
{
|
||||
u32 mask;
|
||||
u32 mask = 0;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
val = readl(fotg->base + FOTG210_OTGCSR);
|
||||
if (enable) {
|
||||
val &= ~FOTG210_OTGCSR_A_BUS_DROP;
|
||||
val |= FOTG210_OTGCSR_A_BUS_REQ;
|
||||
} else {
|
||||
val &= ~FOTG210_OTGCSR_A_BUS_REQ;
|
||||
val |= FOTG210_OTGCSR_A_BUS_DROP;
|
||||
}
|
||||
writel(val, fotg->base + FOTG210_OTGCSR);
|
||||
|
||||
switch (fotg->port) {
|
||||
case GEMINI_PORT_0:
|
||||
mask = GEMINI_MISC_USB0_VBUS_ON;
|
||||
val = enable ? GEMINI_MISC_USB0_VBUS_ON : 0;
|
||||
break;
|
||||
case GEMINI_PORT_1:
|
||||
mask = GEMINI_MISC_USB1_VBUS_ON;
|
||||
val = enable ? GEMINI_MISC_USB1_VBUS_ON : 0;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
case GEMINI_PORT_NONE:
|
||||
break;
|
||||
}
|
||||
ret = regmap_update_bits(fotg->map, GEMINI_GLOBAL_MISC_CTRL, mask, val);
|
||||
|
||||
if (mask) {
|
||||
ret = regmap_update_bits(fotg->map, GEMINI_GLOBAL_MISC_CTRL,
|
||||
mask, enable ? mask : 0);
|
||||
if (ret) {
|
||||
dev_err(fotg->dev, "failed to %s VBUS\n",
|
||||
str_enable_disable(enable));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ret = readl_poll_timeout(fotg->base + FOTG210_OTGCSR, val,
|
||||
enable == !!(val & FOTG210_OTGCSR_A_VBUS_VLD),
|
||||
1000, 500000);
|
||||
if (ret)
|
||||
dev_err(fotg->dev, "failed to %s VBUS\n",
|
||||
str_enable_disable(enable));
|
||||
dev_info(fotg->dev, "%s: %s VBUS\n", __func__, str_enable_disable(enable));
|
||||
dev_warn(fotg->dev, "timeout waiting for VBUS to %s\n",
|
||||
str_enable_disable(enable));
|
||||
}
|
||||
|
||||
static void fotg210_init_host(struct fotg210 *fotg)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* This driver keeps a fixed A-host role and does not negotiate HNP. */
|
||||
val = readl(fotg->base + FOTG210_OTGCSR);
|
||||
val &= ~FOTG210_OTGCSR_B_HNP_EN;
|
||||
writel(val, fotg->base + FOTG210_OTGCSR);
|
||||
|
||||
/* Mask peripheral and OTG sources, enable the host source, active high. */
|
||||
writel(FOTG210_GINTM_MDEV_INT | FOTG210_GINTM_MOTG_INT |
|
||||
FOTG210_GINTM_INT_POLARITY, fotg->base + FOTG210_GINTM);
|
||||
writel(0, fotg->base + FOTG210_OTGIEN);
|
||||
writel(readl(fotg->base + FOTG210_OTGISR),
|
||||
fotg->base + FOTG210_OTGISR);
|
||||
|
||||
/* Vendor trees cycle the A bus before starting the host controller. */
|
||||
fotg210_vbus(fotg, false);
|
||||
usleep_range(10000, 12000);
|
||||
fotg210_vbus(fotg, true);
|
||||
usleep_range(10000, 12000);
|
||||
}
|
||||
|
||||
static void fotg210_init_peripheral(struct fotg210 *fotg)
|
||||
{
|
||||
/* Mask host and OTG sources, enable the device source, active high. */
|
||||
writel(FOTG210_GINTM_MHC_INT | FOTG210_GINTM_MOTG_INT |
|
||||
FOTG210_GINTM_INT_POLARITY, fotg->base + FOTG210_GINTM);
|
||||
writel(0, fotg->base + FOTG210_OTGIEN);
|
||||
writel(readl(fotg->base + FOTG210_OTGISR),
|
||||
fotg->base + FOTG210_OTGISR);
|
||||
}
|
||||
|
||||
static int fotg210_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
enum usb_dr_mode mode;
|
||||
struct reset_control *rst;
|
||||
struct fotg210 *fotg;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
|
@ -141,62 +193,111 @@ static int fotg210_probe(struct platform_device *pdev)
|
|||
if (IS_ERR(fotg->base))
|
||||
return PTR_ERR(fotg->base);
|
||||
|
||||
fotg->pclk = devm_clk_get_optional_enabled(dev, "PCLK");
|
||||
fotg->pclk = devm_clk_get_enabled(dev, "PCLK");
|
||||
if (IS_ERR(fotg->pclk))
|
||||
return PTR_ERR(fotg->pclk);
|
||||
return dev_err_probe(dev, PTR_ERR(fotg->pclk),
|
||||
"failed to enable PCLK\n");
|
||||
|
||||
mode = usb_get_dr_mode(dev);
|
||||
rst = devm_reset_control_get_optional_exclusive(dev, NULL);
|
||||
if (IS_ERR(rst))
|
||||
return dev_err_probe(dev, PTR_ERR(rst),
|
||||
"failed to get reset\n");
|
||||
ret = reset_control_reset(rst);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"failed to reset controller\n");
|
||||
|
||||
val = readl(fotg->base + FOTG210_OTGCSR);
|
||||
fotg->mode = usb_get_dr_mode(dev);
|
||||
/* Keep the historical fixed-host default for old device trees. */
|
||||
if (fotg->mode == USB_DR_MODE_UNKNOWN)
|
||||
fotg->mode = USB_DR_MODE_HOST;
|
||||
if (fotg->mode != USB_DR_MODE_HOST &&
|
||||
fotg->mode != USB_DR_MODE_PERIPHERAL)
|
||||
return dev_err_probe(dev, -EINVAL,
|
||||
"dr_mode must select host or peripheral\n");
|
||||
|
||||
if (of_device_is_compatible(dev->of_node, "cortina,gemini-usb")) {
|
||||
ret = fotg210_gemini_init(fotg, fotg->res, mode);
|
||||
fotg->is_fotg210 = true;
|
||||
ret = fotg210_gemini_init(fotg);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else if (of_device_is_compatible(dev->of_node, "faraday,fotg210")) {
|
||||
fotg->is_fotg210 = true;
|
||||
}
|
||||
|
||||
val = readl(fotg->base + FOTG210_RR);
|
||||
if (mode == USB_DR_MODE_PERIPHERAL) {
|
||||
if (!(val & FOTG210_RR_CROLE))
|
||||
dev_err(dev, "block not in device role\n");
|
||||
ret = fotg210_udc_probe(pdev, fotg);
|
||||
} else {
|
||||
if (val & FOTG210_RR_CROLE)
|
||||
dev_err(dev, "block not in host role\n");
|
||||
ret = fotg210_hcd_probe(pdev, fotg);
|
||||
if (fotg->mode == USB_DR_MODE_PERIPHERAL) {
|
||||
if (!(val & FOTG210_OTGCSR_CROLE))
|
||||
dev_warn(dev, "controller does not report device role\n");
|
||||
if (!(val & FOTG210_OTGCSR_ID))
|
||||
dev_warn(dev, "controller does not report B-device role\n");
|
||||
fotg210_init_peripheral(fotg);
|
||||
return fotg210_udc_probe(pdev, fotg);
|
||||
}
|
||||
|
||||
if (val & FOTG210_OTGCSR_CROLE)
|
||||
dev_warn(dev, "controller does not report host role\n");
|
||||
if (val & FOTG210_OTGCSR_ID)
|
||||
dev_warn(dev, "controller does not report A-device role\n");
|
||||
fotg210_init_host(fotg);
|
||||
ret = fotg210_hcd_probe(pdev, fotg);
|
||||
if (ret)
|
||||
fotg210_vbus(fotg, false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void fotg210_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
enum usb_dr_mode mode;
|
||||
|
||||
mode = usb_get_dr_mode(dev);
|
||||
|
||||
if (mode == USB_DR_MODE_PERIPHERAL)
|
||||
/* The subdrivers own drvdata, so recover the fixed role from firmware. */
|
||||
if (usb_get_dr_mode(&pdev->dev) == USB_DR_MODE_PERIPHERAL)
|
||||
fotg210_udc_remove(pdev);
|
||||
else
|
||||
fotg210_hcd_remove(pdev);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static void fotg210_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
if (IS_ENABLED(CONFIG_USB_FOTG210_HCD) &&
|
||||
usb_get_dr_mode(&pdev->dev) != USB_DR_MODE_PERIPHERAL)
|
||||
usb_hcd_platform_shutdown(pdev);
|
||||
}
|
||||
|
||||
static int fotg210_suspend(struct device *dev)
|
||||
{
|
||||
if (usb_get_dr_mode(dev) == USB_DR_MODE_PERIPHERAL)
|
||||
return 0;
|
||||
|
||||
return fotg210_hcd_suspend(dev);
|
||||
}
|
||||
|
||||
static int fotg210_resume(struct device *dev)
|
||||
{
|
||||
if (usb_get_dr_mode(dev) == USB_DR_MODE_PERIPHERAL)
|
||||
return 0;
|
||||
|
||||
return fotg210_hcd_resume(dev);
|
||||
}
|
||||
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(fotg210_pm_ops, fotg210_suspend,
|
||||
fotg210_resume);
|
||||
|
||||
static const struct of_device_id fotg210_of_match[] = {
|
||||
{ .compatible = "faraday,fotg200" },
|
||||
{ .compatible = "faraday,fotg210" },
|
||||
/* TODO: can we also handle FUSB220? */
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, fotg210_of_match);
|
||||
#endif
|
||||
|
||||
static struct platform_driver fotg210_driver = {
|
||||
.driver = {
|
||||
.name = "fotg210",
|
||||
.of_match_table = of_match_ptr(fotg210_of_match),
|
||||
.name = "fotg210",
|
||||
.of_match_table = fotg210_of_match,
|
||||
.pm = pm_sleep_ptr(&fotg210_pm_ops),
|
||||
},
|
||||
.probe = fotg210_probe,
|
||||
.probe = fotg210_probe,
|
||||
.remove = fotg210_remove,
|
||||
.shutdown = fotg210_shutdown,
|
||||
};
|
||||
|
||||
static int __init fotg210_init(void)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,689 +0,0 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef __LINUX_FOTG210_H
|
||||
#define __LINUX_FOTG210_H
|
||||
|
||||
#include <linux/usb/ehci-dbgp.h>
|
||||
|
||||
/* definitions used for the EHCI driver */
|
||||
|
||||
/*
|
||||
* __hc32 and __hc16 are "Host Controller" types, they may be equivalent to
|
||||
* __leXX (normally) or __beXX (given FOTG210_BIG_ENDIAN_DESC), depending on
|
||||
* the host controller implementation.
|
||||
*
|
||||
* To facilitate the strongest possible byte-order checking from "sparse"
|
||||
* and so on, we use __leXX unless that's not practical.
|
||||
*/
|
||||
#define __hc32 __le32
|
||||
#define __hc16 __le16
|
||||
|
||||
/* statistics can be kept for tuning/monitoring */
|
||||
struct fotg210_stats {
|
||||
/* irq usage */
|
||||
unsigned long normal;
|
||||
unsigned long error;
|
||||
unsigned long iaa;
|
||||
unsigned long lost_iaa;
|
||||
|
||||
/* termination of urbs from core */
|
||||
unsigned long complete;
|
||||
unsigned long unlink;
|
||||
};
|
||||
|
||||
/* fotg210_hcd->lock guards shared data against other CPUs:
|
||||
* fotg210_hcd: async, unlink, periodic (and shadow), ...
|
||||
* usb_host_endpoint: hcpriv
|
||||
* fotg210_qh: qh_next, qtd_list
|
||||
* fotg210_qtd: qtd_list
|
||||
*
|
||||
* Also, hold this lock when talking to HC registers or
|
||||
* when updating hw_* fields in shared qh/qtd/... structures.
|
||||
*/
|
||||
|
||||
#define FOTG210_MAX_ROOT_PORTS 1 /* see HCS_N_PORTS */
|
||||
|
||||
/*
|
||||
* fotg210_rh_state values of FOTG210_RH_RUNNING or above mean that the
|
||||
* controller may be doing DMA. Lower values mean there's no DMA.
|
||||
*/
|
||||
enum fotg210_rh_state {
|
||||
FOTG210_RH_HALTED,
|
||||
FOTG210_RH_SUSPENDED,
|
||||
FOTG210_RH_RUNNING,
|
||||
FOTG210_RH_STOPPING
|
||||
};
|
||||
|
||||
/*
|
||||
* Timer events, ordered by increasing delay length.
|
||||
* Always update event_delays_ns[] and event_handlers[] (defined in
|
||||
* ehci-timer.c) in parallel with this list.
|
||||
*/
|
||||
enum fotg210_hrtimer_event {
|
||||
FOTG210_HRTIMER_POLL_ASS, /* Poll for async schedule off */
|
||||
FOTG210_HRTIMER_POLL_PSS, /* Poll for periodic schedule off */
|
||||
FOTG210_HRTIMER_POLL_DEAD, /* Wait for dead controller to stop */
|
||||
FOTG210_HRTIMER_UNLINK_INTR, /* Wait for interrupt QH unlink */
|
||||
FOTG210_HRTIMER_FREE_ITDS, /* Wait for unused iTDs and siTDs */
|
||||
FOTG210_HRTIMER_ASYNC_UNLINKS, /* Unlink empty async QHs */
|
||||
FOTG210_HRTIMER_IAA_WATCHDOG, /* Handle lost IAA interrupts */
|
||||
FOTG210_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */
|
||||
FOTG210_HRTIMER_DISABLE_ASYNC, /* Wait to disable async sched */
|
||||
FOTG210_HRTIMER_IO_WATCHDOG, /* Check for missing IRQs */
|
||||
FOTG210_HRTIMER_NUM_EVENTS /* Must come last */
|
||||
};
|
||||
#define FOTG210_HRTIMER_NO_EVENT 99
|
||||
|
||||
struct fotg210_hcd { /* one per controller */
|
||||
/* timing support */
|
||||
enum fotg210_hrtimer_event next_hrtimer_event;
|
||||
unsigned enabled_hrtimer_events;
|
||||
ktime_t hr_timeouts[FOTG210_HRTIMER_NUM_EVENTS];
|
||||
struct hrtimer hrtimer;
|
||||
|
||||
int PSS_poll_count;
|
||||
int ASS_poll_count;
|
||||
int died_poll_count;
|
||||
|
||||
/* glue to PCI and HCD framework */
|
||||
struct fotg210_caps __iomem *caps;
|
||||
struct fotg210_regs __iomem *regs;
|
||||
struct ehci_dbg_port __iomem *debug;
|
||||
|
||||
__u32 hcs_params; /* cached register copy */
|
||||
spinlock_t lock;
|
||||
enum fotg210_rh_state rh_state;
|
||||
|
||||
/* general schedule support */
|
||||
bool scanning:1;
|
||||
bool need_rescan:1;
|
||||
bool intr_unlinking:1;
|
||||
bool async_unlinking:1;
|
||||
bool shutdown:1;
|
||||
struct fotg210_qh *qh_scan_next;
|
||||
|
||||
/* async schedule support */
|
||||
struct fotg210_qh *async;
|
||||
struct fotg210_qh *dummy; /* For AMD quirk use */
|
||||
struct fotg210_qh *async_unlink;
|
||||
struct fotg210_qh *async_unlink_last;
|
||||
struct fotg210_qh *async_iaa;
|
||||
unsigned async_unlink_cycle;
|
||||
unsigned async_count; /* async activity count */
|
||||
|
||||
/* periodic schedule support */
|
||||
#define DEFAULT_I_TDPS 1024 /* some HCs can do less */
|
||||
unsigned periodic_size;
|
||||
__hc32 *periodic; /* hw periodic table */
|
||||
dma_addr_t periodic_dma;
|
||||
struct list_head intr_qh_list;
|
||||
unsigned i_thresh; /* uframes HC might cache */
|
||||
|
||||
union fotg210_shadow *pshadow; /* mirror hw periodic table */
|
||||
struct fotg210_qh *intr_unlink;
|
||||
struct fotg210_qh *intr_unlink_last;
|
||||
unsigned intr_unlink_cycle;
|
||||
unsigned now_frame; /* frame from HC hardware */
|
||||
unsigned next_frame; /* scan periodic, start here */
|
||||
unsigned intr_count; /* intr activity count */
|
||||
unsigned isoc_count; /* isoc activity count */
|
||||
unsigned periodic_count; /* periodic activity count */
|
||||
/* max periodic time per uframe */
|
||||
unsigned uframe_periodic_max;
|
||||
|
||||
|
||||
/* list of itds completed while now_frame was still active */
|
||||
struct list_head cached_itd_list;
|
||||
struct fotg210_itd *last_itd_to_free;
|
||||
|
||||
/* per root hub port */
|
||||
unsigned long reset_done[FOTG210_MAX_ROOT_PORTS];
|
||||
|
||||
/* bit vectors (one bit per port)
|
||||
* which ports were already suspended at the start of a bus suspend
|
||||
*/
|
||||
unsigned long bus_suspended;
|
||||
|
||||
/* which ports are edicated to the companion controller */
|
||||
unsigned long companion_ports;
|
||||
|
||||
/* which ports are owned by the companion during a bus suspend */
|
||||
unsigned long owned_ports;
|
||||
|
||||
/* which ports have the change-suspend feature turned on */
|
||||
unsigned long port_c_suspend;
|
||||
|
||||
/* which ports are suspended */
|
||||
unsigned long suspended_ports;
|
||||
|
||||
/* which ports have started to resume */
|
||||
unsigned long resuming_ports;
|
||||
|
||||
/* per-HC memory pools (could be per-bus, but ...) */
|
||||
struct dma_pool *qh_pool; /* qh per active urb */
|
||||
struct dma_pool *qtd_pool; /* one or more per qh */
|
||||
struct dma_pool *itd_pool; /* itd per iso urb */
|
||||
|
||||
unsigned random_frame;
|
||||
unsigned long next_statechange;
|
||||
ktime_t last_periodic_enable;
|
||||
u32 command;
|
||||
|
||||
/* SILICON QUIRKS */
|
||||
unsigned need_io_watchdog:1;
|
||||
unsigned fs_i_thresh:1; /* Intel iso scheduling */
|
||||
|
||||
u8 sbrn; /* packed release number */
|
||||
|
||||
/* irq statistics */
|
||||
#ifdef FOTG210_STATS
|
||||
struct fotg210_stats stats;
|
||||
# define INCR(x) ((x)++)
|
||||
#else
|
||||
# define INCR(x) do {} while (0)
|
||||
#endif
|
||||
|
||||
struct fotg210 *fotg; /* Overarching FOTG210 device */
|
||||
/* silicon clock */
|
||||
struct clk *pclk;
|
||||
};
|
||||
|
||||
/* convert between an HCD pointer and the corresponding FOTG210_HCD */
|
||||
static inline struct fotg210_hcd *hcd_to_fotg210(struct usb_hcd *hcd)
|
||||
{
|
||||
return (struct fotg210_hcd *)(hcd->hcd_priv);
|
||||
}
|
||||
static inline struct usb_hcd *fotg210_to_hcd(struct fotg210_hcd *fotg210)
|
||||
{
|
||||
return container_of((void *) fotg210, struct usb_hcd, hcd_priv);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* EHCI register interface, corresponds to EHCI Revision 0.95 specification */
|
||||
|
||||
/* Section 2.2 Host Controller Capability Registers */
|
||||
struct fotg210_caps {
|
||||
/* these fields are specified as 8 and 16 bit registers,
|
||||
* but some hosts can't perform 8 or 16 bit PCI accesses.
|
||||
* some hosts treat caplength and hciversion as parts of a 32-bit
|
||||
* register, others treat them as two separate registers, this
|
||||
* affects the memory map for big endian controllers.
|
||||
*/
|
||||
u32 hc_capbase;
|
||||
#define HC_LENGTH(fotg210, p) (0x00ff&((p) >> /* bits 7:0 / offset 00h */ \
|
||||
(fotg210_big_endian_capbase(fotg210) ? 24 : 0)))
|
||||
#define HC_VERSION(fotg210, p) (0xffff&((p) >> /* bits 31:16 / offset 02h */ \
|
||||
(fotg210_big_endian_capbase(fotg210) ? 0 : 16)))
|
||||
u32 hcs_params; /* HCSPARAMS - offset 0x4 */
|
||||
#define HCS_N_PORTS(p) (((p)>>0)&0xf) /* bits 3:0, ports on HC */
|
||||
|
||||
u32 hcc_params; /* HCCPARAMS - offset 0x8 */
|
||||
#define HCC_CANPARK(p) ((p)&(1 << 2)) /* true: can park on async qh */
|
||||
#define HCC_PGM_FRAMELISTLEN(p) ((p)&(1 << 1)) /* true: periodic_size changes*/
|
||||
u8 portroute[8]; /* nibbles for routing - offset 0xC */
|
||||
};
|
||||
|
||||
|
||||
/* Section 2.3 Host Controller Operational Registers */
|
||||
struct fotg210_regs {
|
||||
|
||||
/* USBCMD: offset 0x00 */
|
||||
u32 command;
|
||||
|
||||
/* EHCI 1.1 addendum */
|
||||
/* 23:16 is r/w intr rate, in microframes; default "8" == 1/msec */
|
||||
#define CMD_PARK (1<<11) /* enable "park" on async qh */
|
||||
#define CMD_PARK_CNT(c) (((c)>>8)&3) /* how many transfers to park for */
|
||||
#define CMD_IAAD (1<<6) /* "doorbell" interrupt async advance */
|
||||
#define CMD_ASE (1<<5) /* async schedule enable */
|
||||
#define CMD_PSE (1<<4) /* periodic schedule enable */
|
||||
/* 3:2 is periodic frame list size */
|
||||
#define CMD_RESET (1<<1) /* reset HC not bus */
|
||||
#define CMD_RUN (1<<0) /* start/stop HC */
|
||||
|
||||
/* USBSTS: offset 0x04 */
|
||||
u32 status;
|
||||
#define STS_ASS (1<<15) /* Async Schedule Status */
|
||||
#define STS_PSS (1<<14) /* Periodic Schedule Status */
|
||||
#define STS_RECL (1<<13) /* Reclamation */
|
||||
#define STS_HALT (1<<12) /* Not running (any reason) */
|
||||
/* some bits reserved */
|
||||
/* these STS_* flags are also intr_enable bits (USBINTR) */
|
||||
#define STS_IAA (1<<5) /* Interrupted on async advance */
|
||||
#define STS_FATAL (1<<4) /* such as some PCI access errors */
|
||||
#define STS_FLR (1<<3) /* frame list rolled over */
|
||||
#define STS_PCD (1<<2) /* port change detect */
|
||||
#define STS_ERR (1<<1) /* "error" completion (overflow, ...) */
|
||||
#define STS_INT (1<<0) /* "normal" completion (short, ...) */
|
||||
|
||||
/* USBINTR: offset 0x08 */
|
||||
u32 intr_enable;
|
||||
|
||||
/* FRINDEX: offset 0x0C */
|
||||
u32 frame_index; /* current microframe number */
|
||||
/* CTRLDSSEGMENT: offset 0x10 */
|
||||
u32 segment; /* address bits 63:32 if needed */
|
||||
/* PERIODICLISTBASE: offset 0x14 */
|
||||
u32 frame_list; /* points to periodic list */
|
||||
/* ASYNCLISTADDR: offset 0x18 */
|
||||
u32 async_next; /* address of next async queue head */
|
||||
|
||||
u32 reserved1;
|
||||
/* PORTSC: offset 0x20 */
|
||||
u32 port_status;
|
||||
/* 31:23 reserved */
|
||||
#define PORT_USB11(x) (((x)&(3<<10)) == (1<<10)) /* USB 1.1 device */
|
||||
#define PORT_RESET (1<<8) /* reset port */
|
||||
#define PORT_SUSPEND (1<<7) /* suspend port */
|
||||
#define PORT_RESUME (1<<6) /* resume it */
|
||||
#define PORT_PEC (1<<3) /* port enable change */
|
||||
#define PORT_PE (1<<2) /* port enable */
|
||||
#define PORT_CSC (1<<1) /* connect status change */
|
||||
#define PORT_CONNECT (1<<0) /* device connected */
|
||||
#define PORT_RWC_BITS (PORT_CSC | PORT_PEC)
|
||||
u32 reserved2[19];
|
||||
|
||||
/* OTGCSR: offet 0x70 */
|
||||
u32 otgcsr;
|
||||
#define OTGCSR_HOST_SPD_TYP (3 << 22)
|
||||
#define OTGCSR_A_BUS_DROP (1 << 5)
|
||||
#define OTGCSR_A_BUS_REQ (1 << 4)
|
||||
|
||||
/* OTGISR: offset 0x74 */
|
||||
u32 otgisr;
|
||||
#define OTGISR_OVC (1 << 10)
|
||||
|
||||
u32 reserved3[15];
|
||||
|
||||
/* GMIR: offset 0xB4 */
|
||||
u32 gmir;
|
||||
#define GMIR_INT_POLARITY (1 << 3) /*Active High*/
|
||||
#define GMIR_MHC_INT (1 << 2)
|
||||
#define GMIR_MOTG_INT (1 << 1)
|
||||
#define GMIR_MDEV_INT (1 << 0)
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#define QTD_NEXT(fotg210, dma) cpu_to_hc32(fotg210, (u32)dma)
|
||||
|
||||
/*
|
||||
* EHCI Specification 0.95 Section 3.5
|
||||
* QTD: describe data transfer components (buffer, direction, ...)
|
||||
* See Fig 3-6 "Queue Element Transfer Descriptor Block Diagram".
|
||||
*
|
||||
* These are associated only with "QH" (Queue Head) structures,
|
||||
* used with control, bulk, and interrupt transfers.
|
||||
*/
|
||||
struct fotg210_qtd {
|
||||
/* first part defined by EHCI spec */
|
||||
__hc32 hw_next; /* see EHCI 3.5.1 */
|
||||
__hc32 hw_alt_next; /* see EHCI 3.5.2 */
|
||||
__hc32 hw_token; /* see EHCI 3.5.3 */
|
||||
#define QTD_TOGGLE (1 << 31) /* data toggle */
|
||||
#define QTD_LENGTH(tok) (((tok)>>16) & 0x7fff)
|
||||
#define QTD_IOC (1 << 15) /* interrupt on complete */
|
||||
#define QTD_CERR(tok) (((tok)>>10) & 0x3)
|
||||
#define QTD_PID(tok) (((tok)>>8) & 0x3)
|
||||
#define QTD_STS_ACTIVE (1 << 7) /* HC may execute this */
|
||||
#define QTD_STS_HALT (1 << 6) /* halted on error */
|
||||
#define QTD_STS_DBE (1 << 5) /* data buffer error (in HC) */
|
||||
#define QTD_STS_BABBLE (1 << 4) /* device was babbling (qtd halted) */
|
||||
#define QTD_STS_XACT (1 << 3) /* device gave illegal response */
|
||||
#define QTD_STS_MMF (1 << 2) /* incomplete split transaction */
|
||||
#define QTD_STS_STS (1 << 1) /* split transaction state */
|
||||
#define QTD_STS_PING (1 << 0) /* issue PING? */
|
||||
|
||||
#define ACTIVE_BIT(fotg210) cpu_to_hc32(fotg210, QTD_STS_ACTIVE)
|
||||
#define HALT_BIT(fotg210) cpu_to_hc32(fotg210, QTD_STS_HALT)
|
||||
#define STATUS_BIT(fotg210) cpu_to_hc32(fotg210, QTD_STS_STS)
|
||||
|
||||
__hc32 hw_buf[5]; /* see EHCI 3.5.4 */
|
||||
__hc32 hw_buf_hi[5]; /* Appendix B */
|
||||
|
||||
/* the rest is HCD-private */
|
||||
dma_addr_t qtd_dma; /* qtd address */
|
||||
struct list_head qtd_list; /* sw qtd list */
|
||||
struct urb *urb; /* qtd's urb */
|
||||
size_t length; /* length of buffer */
|
||||
} __aligned(32);
|
||||
|
||||
/* mask NakCnt+T in qh->hw_alt_next */
|
||||
#define QTD_MASK(fotg210) cpu_to_hc32(fotg210, ~0x1f)
|
||||
|
||||
#define IS_SHORT_READ(token) (QTD_LENGTH(token) != 0 && QTD_PID(token) == 1)
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* type tag from {qh,itd,fstn}->hw_next */
|
||||
#define Q_NEXT_TYPE(fotg210, dma) ((dma) & cpu_to_hc32(fotg210, 3 << 1))
|
||||
|
||||
/*
|
||||
* Now the following defines are not converted using the
|
||||
* cpu_to_le32() macro anymore, since we have to support
|
||||
* "dynamic" switching between be and le support, so that the driver
|
||||
* can be used on one system with SoC EHCI controller using big-endian
|
||||
* descriptors as well as a normal little-endian PCI EHCI controller.
|
||||
*/
|
||||
/* values for that type tag */
|
||||
#define Q_TYPE_ITD (0 << 1)
|
||||
#define Q_TYPE_QH (1 << 1)
|
||||
#define Q_TYPE_SITD (2 << 1)
|
||||
#define Q_TYPE_FSTN (3 << 1)
|
||||
|
||||
/* next async queue entry, or pointer to interrupt/periodic QH */
|
||||
#define QH_NEXT(fotg210, dma) \
|
||||
(cpu_to_hc32(fotg210, (((u32)dma)&~0x01f)|Q_TYPE_QH))
|
||||
|
||||
/* for periodic/async schedules and qtd lists, mark end of list */
|
||||
#define FOTG210_LIST_END(fotg210) \
|
||||
cpu_to_hc32(fotg210, 1) /* "null pointer" to hw */
|
||||
|
||||
/*
|
||||
* Entries in periodic shadow table are pointers to one of four kinds
|
||||
* of data structure. That's dictated by the hardware; a type tag is
|
||||
* encoded in the low bits of the hardware's periodic schedule. Use
|
||||
* Q_NEXT_TYPE to get the tag.
|
||||
*
|
||||
* For entries in the async schedule, the type tag always says "qh".
|
||||
*/
|
||||
union fotg210_shadow {
|
||||
struct fotg210_qh *qh; /* Q_TYPE_QH */
|
||||
struct fotg210_itd *itd; /* Q_TYPE_ITD */
|
||||
struct fotg210_fstn *fstn; /* Q_TYPE_FSTN */
|
||||
__hc32 *hw_next; /* (all types) */
|
||||
void *ptr;
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* EHCI Specification 0.95 Section 3.6
|
||||
* QH: describes control/bulk/interrupt endpoints
|
||||
* See Fig 3-7 "Queue Head Structure Layout".
|
||||
*
|
||||
* These appear in both the async and (for interrupt) periodic schedules.
|
||||
*/
|
||||
|
||||
/* first part defined by EHCI spec */
|
||||
struct fotg210_qh_hw {
|
||||
__hc32 hw_next; /* see EHCI 3.6.1 */
|
||||
__hc32 hw_info1; /* see EHCI 3.6.2 */
|
||||
#define QH_CONTROL_EP (1 << 27) /* FS/LS control endpoint */
|
||||
#define QH_HEAD (1 << 15) /* Head of async reclamation list */
|
||||
#define QH_TOGGLE_CTL (1 << 14) /* Data toggle control */
|
||||
#define QH_HIGH_SPEED (2 << 12) /* Endpoint speed */
|
||||
#define QH_LOW_SPEED (1 << 12)
|
||||
#define QH_FULL_SPEED (0 << 12)
|
||||
#define QH_INACTIVATE (1 << 7) /* Inactivate on next transaction */
|
||||
__hc32 hw_info2; /* see EHCI 3.6.2 */
|
||||
#define QH_SMASK 0x000000ff
|
||||
#define QH_CMASK 0x0000ff00
|
||||
#define QH_HUBADDR 0x007f0000
|
||||
#define QH_HUBPORT 0x3f800000
|
||||
#define QH_MULT 0xc0000000
|
||||
__hc32 hw_current; /* qtd list - see EHCI 3.6.4 */
|
||||
|
||||
/* qtd overlay (hardware parts of a struct fotg210_qtd) */
|
||||
__hc32 hw_qtd_next;
|
||||
__hc32 hw_alt_next;
|
||||
__hc32 hw_token;
|
||||
__hc32 hw_buf[5];
|
||||
__hc32 hw_buf_hi[5];
|
||||
} __aligned(32);
|
||||
|
||||
struct fotg210_qh {
|
||||
struct fotg210_qh_hw *hw; /* Must come first */
|
||||
/* the rest is HCD-private */
|
||||
dma_addr_t qh_dma; /* address of qh */
|
||||
union fotg210_shadow qh_next; /* ptr to qh; or periodic */
|
||||
struct list_head qtd_list; /* sw qtd list */
|
||||
struct list_head intr_node; /* list of intr QHs */
|
||||
struct fotg210_qtd *dummy;
|
||||
struct fotg210_qh *unlink_next; /* next on unlink list */
|
||||
|
||||
unsigned unlink_cycle;
|
||||
|
||||
u8 needs_rescan; /* Dequeue during giveback */
|
||||
u8 qh_state;
|
||||
#define QH_STATE_LINKED 1 /* HC sees this */
|
||||
#define QH_STATE_UNLINK 2 /* HC may still see this */
|
||||
#define QH_STATE_IDLE 3 /* HC doesn't see this */
|
||||
#define QH_STATE_UNLINK_WAIT 4 /* LINKED and on unlink q */
|
||||
#define QH_STATE_COMPLETING 5 /* don't touch token.HALT */
|
||||
|
||||
u8 xacterrs; /* XactErr retry counter */
|
||||
#define QH_XACTERR_MAX 32 /* XactErr retry limit */
|
||||
|
||||
/* periodic schedule info */
|
||||
u8 usecs; /* intr bandwidth */
|
||||
u8 gap_uf; /* uframes split/csplit gap */
|
||||
u8 c_usecs; /* ... split completion bw */
|
||||
u16 tt_usecs; /* tt downstream bandwidth */
|
||||
unsigned short period; /* polling interval */
|
||||
unsigned short start; /* where polling starts */
|
||||
#define NO_FRAME ((unsigned short)~0) /* pick new start */
|
||||
|
||||
struct usb_device *dev; /* access to TT */
|
||||
unsigned is_out:1; /* bulk or intr OUT */
|
||||
unsigned clearing_tt:1; /* Clear-TT-Buf in progress */
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* description of one iso transaction (up to 3 KB data if highspeed) */
|
||||
struct fotg210_iso_packet {
|
||||
/* These will be copied to iTD when scheduling */
|
||||
u64 bufp; /* itd->hw_bufp{,_hi}[pg] |= */
|
||||
__hc32 transaction; /* itd->hw_transaction[i] |= */
|
||||
u8 cross; /* buf crosses pages */
|
||||
/* for full speed OUT splits */
|
||||
u32 buf1;
|
||||
};
|
||||
|
||||
/* temporary schedule data for packets from iso urbs (both speeds)
|
||||
* each packet is one logical usb transaction to the device (not TT),
|
||||
* beginning at stream->next_uframe
|
||||
*/
|
||||
struct fotg210_iso_sched {
|
||||
struct list_head td_list;
|
||||
unsigned span;
|
||||
struct fotg210_iso_packet packet[];
|
||||
};
|
||||
|
||||
/*
|
||||
* fotg210_iso_stream - groups all (s)itds for this endpoint.
|
||||
* acts like a qh would, if EHCI had them for ISO.
|
||||
*/
|
||||
struct fotg210_iso_stream {
|
||||
/* first field matches fotg210_hq, but is NULL */
|
||||
struct fotg210_qh_hw *hw;
|
||||
|
||||
u8 bEndpointAddress;
|
||||
u8 highspeed;
|
||||
struct list_head td_list; /* queued itds */
|
||||
struct list_head free_list; /* list of unused itds */
|
||||
struct usb_device *udev;
|
||||
struct usb_host_endpoint *ep;
|
||||
|
||||
/* output of (re)scheduling */
|
||||
int next_uframe;
|
||||
__hc32 splits;
|
||||
|
||||
/* the rest is derived from the endpoint descriptor,
|
||||
* trusting urb->interval == f(epdesc->bInterval) and
|
||||
* including the extra info for hw_bufp[0..2]
|
||||
*/
|
||||
u8 usecs, c_usecs;
|
||||
u16 interval;
|
||||
u16 tt_usecs;
|
||||
u16 maxp;
|
||||
u16 raw_mask;
|
||||
unsigned bandwidth;
|
||||
|
||||
/* This is used to initialize iTD's hw_bufp fields */
|
||||
__hc32 buf0;
|
||||
__hc32 buf1;
|
||||
__hc32 buf2;
|
||||
|
||||
/* this is used to initialize sITD's tt info */
|
||||
__hc32 address;
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* EHCI Specification 0.95 Section 3.3
|
||||
* Fig 3-4 "Isochronous Transaction Descriptor (iTD)"
|
||||
*
|
||||
* Schedule records for high speed iso xfers
|
||||
*/
|
||||
struct fotg210_itd {
|
||||
/* first part defined by EHCI spec */
|
||||
__hc32 hw_next; /* see EHCI 3.3.1 */
|
||||
__hc32 hw_transaction[8]; /* see EHCI 3.3.2 */
|
||||
#define FOTG210_ISOC_ACTIVE (1<<31) /* activate transfer this slot */
|
||||
#define FOTG210_ISOC_BUF_ERR (1<<30) /* Data buffer error */
|
||||
#define FOTG210_ISOC_BABBLE (1<<29) /* babble detected */
|
||||
#define FOTG210_ISOC_XACTERR (1<<28) /* XactErr - transaction error */
|
||||
#define FOTG210_ITD_LENGTH(tok) (((tok)>>16) & 0x0fff)
|
||||
#define FOTG210_ITD_IOC (1 << 15) /* interrupt on complete */
|
||||
|
||||
#define ITD_ACTIVE(fotg210) cpu_to_hc32(fotg210, FOTG210_ISOC_ACTIVE)
|
||||
|
||||
__hc32 hw_bufp[7]; /* see EHCI 3.3.3 */
|
||||
__hc32 hw_bufp_hi[7]; /* Appendix B */
|
||||
|
||||
/* the rest is HCD-private */
|
||||
dma_addr_t itd_dma; /* for this itd */
|
||||
union fotg210_shadow itd_next; /* ptr to periodic q entry */
|
||||
|
||||
struct urb *urb;
|
||||
struct fotg210_iso_stream *stream; /* endpoint's queue */
|
||||
struct list_head itd_list; /* list of stream's itds */
|
||||
|
||||
/* any/all hw_transactions here may be used by that urb */
|
||||
unsigned frame; /* where scheduled */
|
||||
unsigned pg;
|
||||
unsigned index[8]; /* in urb->iso_frame_desc */
|
||||
} __aligned(32);
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* EHCI Specification 0.96 Section 3.7
|
||||
* Periodic Frame Span Traversal Node (FSTN)
|
||||
*
|
||||
* Manages split interrupt transactions (using TT) that span frame boundaries
|
||||
* into uframes 0/1; see 4.12.2.2. In those uframes, a "save place" FSTN
|
||||
* makes the HC jump (back) to a QH to scan for fs/ls QH completions until
|
||||
* it hits a "restore" FSTN; then it returns to finish other uframe 0/1 work.
|
||||
*/
|
||||
struct fotg210_fstn {
|
||||
__hc32 hw_next; /* any periodic q entry */
|
||||
__hc32 hw_prev; /* qh or FOTG210_LIST_END */
|
||||
|
||||
/* the rest is HCD-private */
|
||||
dma_addr_t fstn_dma;
|
||||
union fotg210_shadow fstn_next; /* ptr to periodic q entry */
|
||||
} __aligned(32);
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* Prepare the PORTSC wakeup flags during controller suspend/resume */
|
||||
|
||||
#define fotg210_prepare_ports_for_controller_suspend(fotg210, do_wakeup) \
|
||||
fotg210_adjust_port_wakeup_flags(fotg210, true, do_wakeup)
|
||||
|
||||
#define fotg210_prepare_ports_for_controller_resume(fotg210) \
|
||||
fotg210_adjust_port_wakeup_flags(fotg210, false, false)
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* Some EHCI controllers have a Transaction Translator built into the
|
||||
* root hub. This is a non-standard feature. Each controller will need
|
||||
* to add code to the following inline functions, and call them as
|
||||
* needed (mostly in root hub code).
|
||||
*/
|
||||
|
||||
static inline unsigned int
|
||||
fotg210_get_speed(struct fotg210_hcd *fotg210, unsigned int portsc)
|
||||
{
|
||||
return (readl(&fotg210->regs->otgcsr)
|
||||
& OTGCSR_HOST_SPD_TYP) >> 22;
|
||||
}
|
||||
|
||||
/* Returns the speed of a device attached to a port on the root hub. */
|
||||
static inline unsigned int
|
||||
fotg210_port_speed(struct fotg210_hcd *fotg210, unsigned int portsc)
|
||||
{
|
||||
switch (fotg210_get_speed(fotg210, portsc)) {
|
||||
case 0:
|
||||
return 0;
|
||||
case 1:
|
||||
return USB_PORT_STAT_LOW_SPEED;
|
||||
case 2:
|
||||
default:
|
||||
return USB_PORT_STAT_HIGH_SPEED;
|
||||
}
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#define fotg210_has_fsl_portno_bug(e) (0)
|
||||
|
||||
/*
|
||||
* While most USB host controllers implement their registers in
|
||||
* little-endian format, a minority (celleb companion chip) implement
|
||||
* them in big endian format.
|
||||
*
|
||||
* This attempts to support either format at compile time without a
|
||||
* runtime penalty, or both formats with the additional overhead
|
||||
* of checking a flag bit.
|
||||
*
|
||||
*/
|
||||
|
||||
#define fotg210_big_endian_mmio(e) 0
|
||||
#define fotg210_big_endian_capbase(e) 0
|
||||
|
||||
static inline unsigned int fotg210_readl(const struct fotg210_hcd *fotg210,
|
||||
__u32 __iomem *regs)
|
||||
{
|
||||
return readl(regs);
|
||||
}
|
||||
|
||||
static inline void fotg210_writel(const struct fotg210_hcd *fotg210,
|
||||
const unsigned int val, __u32 __iomem *regs)
|
||||
{
|
||||
writel(val, regs);
|
||||
}
|
||||
|
||||
/* cpu to fotg210 */
|
||||
static inline __hc32 cpu_to_hc32(const struct fotg210_hcd *fotg210, const u32 x)
|
||||
{
|
||||
return cpu_to_le32(x);
|
||||
}
|
||||
|
||||
/* fotg210 to cpu */
|
||||
static inline u32 hc32_to_cpu(const struct fotg210_hcd *fotg210, const __hc32 x)
|
||||
{
|
||||
return le32_to_cpu(x);
|
||||
}
|
||||
|
||||
static inline u32 hc32_to_cpup(const struct fotg210_hcd *fotg210,
|
||||
const __hc32 *x)
|
||||
{
|
||||
return le32_to_cpup(x);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static inline unsigned fotg210_read_frame_index(struct fotg210_hcd *fotg210)
|
||||
{
|
||||
return fotg210_readl(fotg210, &fotg210->regs->frame_index);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#endif /* __LINUX_FOTG210_H */
|
||||
|
|
@ -124,6 +124,7 @@ static void fotg210_set_fifo_dir(struct fotg210_ep *ep, u32 epnum, u32 dir_in)
|
|||
u32 val;
|
||||
|
||||
val = ioread32(fotg210->reg + FOTG210_FIFOMAP);
|
||||
val &= ~FIFOMAP_NA(epnum - 1);
|
||||
val |= (dir_in ? FIFOMAP_DIRIN(epnum - 1) : FIFOMAP_DIROUT(epnum - 1));
|
||||
iowrite32(val, fotg210->reg + FOTG210_FIFOMAP);
|
||||
}
|
||||
|
|
@ -134,6 +135,7 @@ static void fotg210_set_tfrtype(struct fotg210_ep *ep, u32 epnum, u32 type)
|
|||
u32 val;
|
||||
|
||||
val = ioread32(fotg210->reg + FOTG210_FIFOCF);
|
||||
val &= ~FIFOCF_TYPE(3, epnum - 1);
|
||||
val |= FIFOCF_TYPE(type, epnum - 1);
|
||||
iowrite32(val, fotg210->reg + FOTG210_FIFOCF);
|
||||
}
|
||||
|
|
@ -147,6 +149,7 @@ static void fotg210_set_mps(struct fotg210_ep *ep, u32 epnum, u32 mps,
|
|||
FOTG210_OUTEPMPSR(epnum);
|
||||
|
||||
val = ioread32(fotg210->reg + offset);
|
||||
val &= ~INOUTEPMPSR_MPS(~0);
|
||||
val |= INOUTEPMPSR_MPS(mps);
|
||||
iowrite32(val, fotg210->reg + offset);
|
||||
}
|
||||
|
|
@ -209,12 +212,13 @@ static int fotg210_ep_release(struct fotg210_ep *ep)
|
|||
{
|
||||
if (!ep->epnum)
|
||||
return 0;
|
||||
|
||||
fotg210_reset_tseq(ep->fotg210, ep->epnum);
|
||||
|
||||
ep->epnum = 0;
|
||||
ep->stall = 0;
|
||||
ep->wedged = 0;
|
||||
|
||||
fotg210_reset_tseq(ep->fotg210, ep->epnum);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -338,6 +342,7 @@ static void fotg210_start_dma(struct fotg210_ep *ep,
|
|||
struct fotg210_request *req)
|
||||
{
|
||||
struct device *dev = &ep->fotg210->gadget.dev;
|
||||
enum dma_data_direction direction;
|
||||
dma_addr_t d;
|
||||
u8 *buffer;
|
||||
u32 length;
|
||||
|
|
@ -361,8 +366,8 @@ static void fotg210_start_dma(struct fotg210_ep *ep,
|
|||
length = req->req.length - req->req.actual;
|
||||
}
|
||||
|
||||
d = dma_map_single(dev, buffer, length,
|
||||
ep->dir_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
|
||||
direction = ep->dir_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
|
||||
d = dma_map_single(dev, buffer, length, direction);
|
||||
|
||||
if (dma_mapping_error(dev, d)) {
|
||||
pr_err("dma_mapping_error\n");
|
||||
|
|
@ -379,7 +384,7 @@ static void fotg210_start_dma(struct fotg210_ep *ep,
|
|||
/* update actual transfer length */
|
||||
req->req.actual += length;
|
||||
|
||||
dma_unmap_single(dev, d, length, DMA_TO_DEVICE);
|
||||
dma_unmap_single(dev, d, length, direction);
|
||||
}
|
||||
|
||||
static void fotg210_ep0_queue(struct fotg210_ep *ep,
|
||||
|
|
@ -445,7 +450,7 @@ static int fotg210_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req)
|
|||
req = container_of(_req, struct fotg210_request, req);
|
||||
|
||||
spin_lock_irqsave(&ep->fotg210->lock, flags);
|
||||
if (!list_empty(&ep->queue))
|
||||
if (!list_empty(&req->queue))
|
||||
fotg210_done(ep, req, -ECONNRESET);
|
||||
spin_unlock_irqrestore(&ep->fotg210->lock, flags);
|
||||
|
||||
|
|
@ -661,7 +666,7 @@ static void fotg210_set_feature(struct fotg210_udc *fotg210,
|
|||
case USB_RECIP_ENDPOINT: {
|
||||
u8 epnum;
|
||||
epnum = le16_to_cpu(ctrl->wIndex) & USB_ENDPOINT_NUMBER_MASK;
|
||||
if (epnum)
|
||||
if (epnum && epnum < FOTG210_MAX_NUM_EP)
|
||||
fotg210_set_epnstall(fotg210->ep[epnum]);
|
||||
else
|
||||
fotg210_set_cxstall(fotg210);
|
||||
|
|
@ -677,8 +682,8 @@ static void fotg210_set_feature(struct fotg210_udc *fotg210,
|
|||
static void fotg210_clear_feature(struct fotg210_udc *fotg210,
|
||||
struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
struct fotg210_ep *ep =
|
||||
fotg210->ep[ctrl->wIndex & USB_ENDPOINT_NUMBER_MASK];
|
||||
u8 epnum = le16_to_cpu(ctrl->wIndex) & USB_ENDPOINT_NUMBER_MASK;
|
||||
struct fotg210_ep *ep;
|
||||
|
||||
switch (ctrl->bRequestType & USB_RECIP_MASK) {
|
||||
case USB_RECIP_DEVICE:
|
||||
|
|
@ -688,7 +693,12 @@ static void fotg210_clear_feature(struct fotg210_udc *fotg210,
|
|||
fotg210_set_cxdone(fotg210);
|
||||
break;
|
||||
case USB_RECIP_ENDPOINT:
|
||||
if (ctrl->wIndex & USB_ENDPOINT_NUMBER_MASK) {
|
||||
if (epnum >= FOTG210_MAX_NUM_EP) {
|
||||
fotg210_request_error(fotg210);
|
||||
break;
|
||||
}
|
||||
if (epnum) {
|
||||
ep = fotg210->ep[epnum];
|
||||
if (ep->wedged) {
|
||||
fotg210_set_cxdone(fotg210);
|
||||
break;
|
||||
|
|
@ -744,8 +754,8 @@ static void fotg210_get_status(struct fotg210_udc *fotg210,
|
|||
fotg210->ep0_data = cpu_to_le16(0);
|
||||
break;
|
||||
case USB_RECIP_ENDPOINT:
|
||||
epnum = ctrl->wIndex & USB_ENDPOINT_NUMBER_MASK;
|
||||
if (epnum)
|
||||
epnum = le16_to_cpu(ctrl->wIndex) & USB_ENDPOINT_NUMBER_MASK;
|
||||
if (epnum && epnum < FOTG210_MAX_NUM_EP)
|
||||
fotg210->ep0_data =
|
||||
cpu_to_le16(fotg210_is_epnstall(fotg210->ep[epnum])
|
||||
<< USB_ENDPOINT_HALT);
|
||||
|
|
@ -886,6 +896,8 @@ static irqreturn_t fotg210_irq(int irq, void *_fotg210)
|
|||
u32 int_msk = ioread32(fotg210->reg + FOTG210_DMIGR);
|
||||
|
||||
int_grp &= ~int_msk;
|
||||
if (!int_grp)
|
||||
return IRQ_NONE;
|
||||
|
||||
spin_lock(&fotg210->lock);
|
||||
|
||||
|
|
@ -1002,6 +1014,14 @@ static void fotg210_disable_unplug(struct fotg210_udc *fotg210)
|
|||
iowrite32(reg, fotg210->reg + FOTG210_PHYTMSR);
|
||||
}
|
||||
|
||||
static void fotg210_enable_unplug(struct fotg210_udc *fotg210)
|
||||
{
|
||||
u32 reg = ioread32(fotg210->reg + FOTG210_PHYTMSR);
|
||||
|
||||
reg |= PHYTMSR_UNPLUG;
|
||||
iowrite32(reg, fotg210->reg + FOTG210_PHYTMSR);
|
||||
}
|
||||
|
||||
static int fotg210_udc_start(struct usb_gadget *g,
|
||||
struct usb_gadget_driver *driver)
|
||||
{
|
||||
|
|
@ -1009,20 +1029,23 @@ static int fotg210_udc_start(struct usb_gadget *g,
|
|||
u32 value;
|
||||
int ret;
|
||||
|
||||
/* hook up the driver */
|
||||
/* Hook up the driver before enabling device interrupts. */
|
||||
fotg210->driver = driver;
|
||||
fotg210->gadget.dev.of_node = fotg210->dev->of_node;
|
||||
fotg210->gadget.speed = USB_SPEED_UNKNOWN;
|
||||
|
||||
dev_info(fotg210->dev, "bound driver %s\n", driver->driver.name);
|
||||
|
||||
if (!IS_ERR_OR_NULL(fotg210->phy)) {
|
||||
ret = otg_set_peripheral(fotg210->phy->otg,
|
||||
&fotg210->gadget);
|
||||
if (ret)
|
||||
dev_err(fotg210->dev, "can't bind to phy\n");
|
||||
if (ret) {
|
||||
fotg210->driver = NULL;
|
||||
return dev_err_probe(fotg210->dev, ret,
|
||||
"can't bind to PHY\n");
|
||||
}
|
||||
}
|
||||
|
||||
dev_info(fotg210->dev, "bound driver %s\n", driver->driver.name);
|
||||
|
||||
/* chip enable */
|
||||
value = ioread32(fotg210->reg + FOTG210_DMCR);
|
||||
value |= DMCR_CHIP_EN;
|
||||
|
|
@ -1076,20 +1099,23 @@ static void fotg210_init(struct fotg210_udc *fotg210)
|
|||
static int fotg210_udc_stop(struct usb_gadget *g)
|
||||
{
|
||||
struct fotg210_udc *fotg210 = gadget_to_fotg210(g);
|
||||
unsigned long flags;
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
||||
if (!IS_ERR_OR_NULL(fotg210->phy))
|
||||
return otg_set_peripheral(fotg210->phy->otg, NULL);
|
||||
ret = otg_set_peripheral(fotg210->phy->otg, NULL);
|
||||
|
||||
/* fotg210_init() sleeps, so it must run outside the spinlock. */
|
||||
fotg210_init(fotg210);
|
||||
|
||||
spin_lock_irqsave(&fotg210->lock, flags);
|
||||
|
||||
fotg210_init(fotg210);
|
||||
fotg210->driver = NULL;
|
||||
fotg210->gadget.speed = USB_SPEED_UNKNOWN;
|
||||
|
||||
spin_unlock_irqrestore(&fotg210->lock, flags);
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1103,8 +1129,12 @@ static int fotg210_vbus_session(struct usb_gadget *g, int is_active)
|
|||
{
|
||||
struct fotg210_udc *fotg210 = gadget_to_fotg210(g);
|
||||
|
||||
/* Call down to core integration layer to drive or disable VBUS */
|
||||
fotg210_vbus(fotg210->fotg, is_active);
|
||||
/* A peripheral must never source VBUS; only control its pull-up. */
|
||||
if (is_active)
|
||||
fotg210_disable_unplug(fotg210);
|
||||
else
|
||||
fotg210_enable_unplug(fotg210);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -1144,28 +1174,24 @@ static int fotg210_phy_event(struct notifier_block *nb, unsigned long action,
|
|||
}
|
||||
}
|
||||
|
||||
static struct notifier_block fotg210_phy_notifier = {
|
||||
.notifier_call = fotg210_phy_event,
|
||||
};
|
||||
|
||||
int fotg210_udc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct fotg210_udc *fotg210 = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
usb_del_gadget_udc(&fotg210->gadget);
|
||||
if (!IS_ERR_OR_NULL(fotg210->phy)) {
|
||||
usb_unregister_notifier(fotg210->phy, &fotg210_phy_notifier);
|
||||
usb_put_phy(fotg210->phy);
|
||||
}
|
||||
iounmap(fotg210->reg);
|
||||
if (!IS_ERR_OR_NULL(fotg210->phy))
|
||||
usb_unregister_notifier(fotg210->phy,
|
||||
&fotg210->phy_notifier);
|
||||
free_irq(platform_get_irq(pdev, 0), fotg210);
|
||||
usb_phy_shutdown(fotg210->phy);
|
||||
|
||||
fotg210_ep_free_request(&fotg210->ep[0]->ep, fotg210->ep0_req);
|
||||
for (i = 0; i < FOTG210_MAX_NUM_EP; i++)
|
||||
kfree(fotg210->ep[i]);
|
||||
|
||||
kfree(fotg210);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1188,7 +1214,6 @@ int fotg210_udc_probe(struct platform_device *pdev, struct fotg210 *fotg)
|
|||
return -ENOMEM;
|
||||
|
||||
fotg210->dev = dev;
|
||||
fotg210->fotg = fotg;
|
||||
|
||||
fotg210->phy = devm_usb_get_phy_by_phandle(dev, "usb-phy", 0);
|
||||
if (IS_ERR(fotg210->phy)) {
|
||||
|
|
@ -1216,8 +1241,6 @@ int fotg210_udc_probe(struct platform_device *pdev, struct fotg210 *fotg)
|
|||
|
||||
spin_lock_init(&fotg210->lock);
|
||||
|
||||
platform_set_drvdata(pdev, fotg210);
|
||||
|
||||
fotg210->gadget.ops = &fotg210_gadget_ops;
|
||||
|
||||
fotg210->gadget.max_speed = USB_SPEED_HIGH;
|
||||
|
|
@ -1267,38 +1290,45 @@ int fotg210_udc_probe(struct platform_device *pdev, struct fotg210 *fotg)
|
|||
|
||||
fotg210_disable_unplug(fotg210);
|
||||
|
||||
ret = request_irq(irq, fotg210_irq, IRQF_SHARED,
|
||||
udc_name, fotg210);
|
||||
ret = request_irq(irq, fotg210_irq, 0, udc_name, fotg210);
|
||||
if (ret < 0) {
|
||||
dev_err_probe(dev, ret, "request_irq error\n");
|
||||
goto err_req;
|
||||
}
|
||||
|
||||
if (!IS_ERR_OR_NULL(fotg210->phy))
|
||||
usb_register_notifier(fotg210->phy, &fotg210_phy_notifier);
|
||||
if (!IS_ERR_OR_NULL(fotg210->phy)) {
|
||||
fotg210->phy_notifier.notifier_call = fotg210_phy_event;
|
||||
ret = usb_register_notifier(fotg210->phy,
|
||||
&fotg210->phy_notifier);
|
||||
if (ret)
|
||||
goto err_notifier;
|
||||
}
|
||||
|
||||
ret = usb_add_gadget_udc(dev, &fotg210->gadget);
|
||||
if (ret)
|
||||
goto err_add_udc;
|
||||
|
||||
platform_set_drvdata(pdev, fotg210);
|
||||
|
||||
dev_info(dev, "version %s\n", DRIVER_VERSION);
|
||||
|
||||
return 0;
|
||||
|
||||
err_add_udc:
|
||||
if (!IS_ERR_OR_NULL(fotg210->phy))
|
||||
usb_unregister_notifier(fotg210->phy, &fotg210_phy_notifier);
|
||||
usb_unregister_notifier(fotg210->phy,
|
||||
&fotg210->phy_notifier);
|
||||
err_notifier:
|
||||
free_irq(irq, fotg210);
|
||||
|
||||
err_req:
|
||||
fotg210_ep_free_request(&fotg210->ep[0]->ep, fotg210->ep0_req);
|
||||
|
||||
err_map:
|
||||
iounmap(fotg210->reg);
|
||||
|
||||
err_alloc:
|
||||
for (i = 0; i < FOTG210_MAX_NUM_EP; i++)
|
||||
kfree(fotg210->ep[i]);
|
||||
usb_phy_shutdown(fotg210->phy);
|
||||
|
||||
err_free:
|
||||
kfree(fotg210);
|
||||
|
|
|
|||
|
|
@ -237,8 +237,8 @@ struct fotg210_udc {
|
|||
unsigned long irq_trigger;
|
||||
|
||||
struct device *dev;
|
||||
struct fotg210 *fotg;
|
||||
struct usb_phy *phy;
|
||||
struct notifier_block phy_notifier;
|
||||
struct usb_gadget gadget;
|
||||
struct usb_gadget_driver *driver;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
#ifndef __FOTG210_H
|
||||
#define __FOTG210_H
|
||||
|
||||
#include <linux/usb/otg.h>
|
||||
|
||||
enum gemini_port {
|
||||
GEMINI_PORT_NONE = 0,
|
||||
GEMINI_PORT_0,
|
||||
|
|
@ -15,6 +17,8 @@ struct fotg210 {
|
|||
struct clk *pclk;
|
||||
struct regmap *map;
|
||||
enum gemini_port port;
|
||||
enum usb_dr_mode mode;
|
||||
bool is_fotg210;
|
||||
};
|
||||
|
||||
void fotg210_vbus(struct fotg210 *fotg, bool enable);
|
||||
|
|
@ -22,22 +26,37 @@ void fotg210_vbus(struct fotg210 *fotg, bool enable);
|
|||
#ifdef CONFIG_USB_FOTG210_HCD
|
||||
int fotg210_hcd_probe(struct platform_device *pdev, struct fotg210 *fotg);
|
||||
int fotg210_hcd_remove(struct platform_device *pdev);
|
||||
int fotg210_hcd_suspend(struct device *dev);
|
||||
int fotg210_hcd_resume(struct device *dev);
|
||||
int fotg210_hcd_init(void);
|
||||
void fotg210_hcd_cleanup(void);
|
||||
#else
|
||||
static inline int fotg210_hcd_probe(struct platform_device *pdev,
|
||||
struct fotg210 *fotg)
|
||||
{
|
||||
return 0;
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static inline int fotg210_hcd_remove(struct platform_device *pdev)
|
||||
{
|
||||
return 0;
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static inline int fotg210_hcd_suspend(struct device *dev)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static inline int fotg210_hcd_resume(struct device *dev)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static inline int fotg210_hcd_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void fotg210_hcd_cleanup(void)
|
||||
{
|
||||
}
|
||||
|
|
@ -50,8 +69,9 @@ int fotg210_udc_remove(struct platform_device *pdev);
|
|||
static inline int fotg210_udc_probe(struct platform_device *pdev,
|
||||
struct fotg210 *fotg)
|
||||
{
|
||||
return 0;
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static inline int fotg210_udc_remove(struct platform_device *pdev)
|
||||
{
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -2740,7 +2740,7 @@ void usb_composite_setup_continue(struct usb_composite_dev *cdev)
|
|||
spin_lock_irqsave(&cdev->lock, flags);
|
||||
|
||||
if (cdev->delayed_status == 0) {
|
||||
WARN(cdev, "%s: Unexpected call\n", __func__);
|
||||
DBG(cdev, "%s: Ignoring obsolete call\n", __func__);
|
||||
|
||||
} else if (--cdev->delayed_status == 0) {
|
||||
DBG(cdev, "%s: Completing delayed status\n", __func__);
|
||||
|
|
|
|||
|
|
@ -463,7 +463,12 @@ static int fsg_setup(struct usb_function *f,
|
|||
u16 w_value = le16_to_cpu(ctrl->wValue);
|
||||
u16 w_length = le16_to_cpu(ctrl->wLength);
|
||||
|
||||
if (!fsg_is_set(fsg->common))
|
||||
/*
|
||||
* fsg_is_set() warns when common->fsg is NULL, but that can happen
|
||||
* normally while a configuration change installs or removes the active
|
||||
* function.
|
||||
*/
|
||||
if (!fsg->common->fsg)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
++fsg->common->ep0_req_tag; /* Record arrival of a new request */
|
||||
|
|
|
|||
|
|
@ -236,6 +236,9 @@ static void put_ep (struct ep_data *data)
|
|||
static const char *CHIP;
|
||||
static DEFINE_MUTEX(sb_mutex); /* Serialize superblock operations */
|
||||
|
||||
static DEFINE_SPINLOCK(aio_lock); /* Protect aio cancellation info */
|
||||
static struct workqueue_struct *gadgetfs_wq;
|
||||
|
||||
/*----------------------------------------------------------------------*/
|
||||
|
||||
/* NOTE: don't use dev_printk calls before binding to the gadget
|
||||
|
|
@ -433,40 +436,105 @@ static long ep_ioctl(struct file *fd, unsigned code, unsigned long value)
|
|||
|
||||
/* ASYNCHRONOUS ENDPOINT I/O OPERATIONS (bulk/intr/iso) */
|
||||
|
||||
enum aio_req_state {
|
||||
AIO_SUBMITTING,
|
||||
AIO_RUNNING,
|
||||
AIO_COMPLETED,
|
||||
AIO_GIVEN_BACK,
|
||||
};
|
||||
|
||||
enum aio_cancel_state {
|
||||
AIO_NOT_CANCELLED,
|
||||
AIO_UNLINKING,
|
||||
AIO_UNLINK_DONE,
|
||||
};
|
||||
|
||||
struct kiocb_priv {
|
||||
struct usb_request *req;
|
||||
struct ep_data *epdata;
|
||||
struct usb_ep *ep;
|
||||
struct kiocb *iocb;
|
||||
struct mm_struct *mm;
|
||||
struct work_struct work;
|
||||
struct work_struct copy_work;
|
||||
struct work_struct unlink_work;
|
||||
void *buf;
|
||||
struct iov_iter to;
|
||||
const void *to_free;
|
||||
unsigned actual;
|
||||
enum aio_req_state req_state;
|
||||
enum aio_cancel_state cancel_state;
|
||||
bool is_read;
|
||||
};
|
||||
|
||||
static void ep_unlink_worker(struct work_struct *work)
|
||||
{
|
||||
struct kiocb_priv *priv;
|
||||
struct usb_request *req;
|
||||
struct ep_data *epdata;
|
||||
struct usb_ep *ep;
|
||||
enum aio_req_state req_state;
|
||||
|
||||
priv = container_of(work, struct kiocb_priv, unlink_work);
|
||||
req = priv->req;
|
||||
epdata = priv->epdata;
|
||||
ep = priv->ep;
|
||||
|
||||
usb_ep_dequeue(ep, req);
|
||||
|
||||
spin_lock_irq(&aio_lock);
|
||||
req_state = priv->req_state;
|
||||
priv->cancel_state = AIO_UNLINK_DONE;
|
||||
spin_unlock_irq(&aio_lock);
|
||||
|
||||
/*
|
||||
* req and epdata are freed after unlinking and completion are both done.
|
||||
* priv is freed after unlinking and giveback are both done.
|
||||
*/
|
||||
if (req_state >= AIO_COMPLETED) {
|
||||
/* priv may have been freed already, depending on req_state */
|
||||
usb_ep_free_request(ep, req);
|
||||
put_ep(epdata);
|
||||
if (req_state == AIO_GIVEN_BACK)
|
||||
kfree(priv);
|
||||
}
|
||||
}
|
||||
|
||||
static int ep_aio_cancel(struct kiocb *iocb)
|
||||
{
|
||||
struct kiocb_priv *priv = iocb->private;
|
||||
struct ep_data *epdata;
|
||||
int value;
|
||||
struct kiocb_priv *priv;
|
||||
unsigned long flags;
|
||||
|
||||
local_irq_disable();
|
||||
epdata = priv->epdata;
|
||||
// spin_lock(&epdata->dev->lock);
|
||||
if (likely(epdata && epdata->ep && priv->req))
|
||||
value = usb_ep_dequeue (epdata->ep, priv->req);
|
||||
else
|
||||
value = -EINVAL;
|
||||
// spin_unlock(&epdata->dev->lock);
|
||||
local_irq_enable();
|
||||
spin_lock_irqsave(&aio_lock, flags);
|
||||
priv = iocb->private;
|
||||
if (!priv || priv->cancel_state != AIO_NOT_CANCELLED) {
|
||||
spin_unlock_irqrestore(&aio_lock, flags);
|
||||
return -EINVAL; /* Already completed or cancelled */
|
||||
}
|
||||
if (priv->req_state == AIO_SUBMITTING) {
|
||||
priv->cancel_state = AIO_UNLINK_DONE;
|
||||
spin_unlock_irqrestore(&aio_lock, flags);
|
||||
return 0; /* ep_aio() will call us again if needed */
|
||||
}
|
||||
priv->cancel_state = AIO_UNLINKING;
|
||||
|
||||
return value;
|
||||
/*
|
||||
* We are called with the aio core holding iocb's context lock.
|
||||
* usb_ep_dequeue() is allowed to run synchronously, calling the
|
||||
* completion handler ep_aio_complete() before it returns.
|
||||
* But ep_aio_complete() may call iocb->io_complete(), which
|
||||
* tries to acquire the context lock, leading to deadlock.
|
||||
* For this reason, do the dequeue operation in a work routine.
|
||||
*/
|
||||
INIT_WORK(&priv->unlink_work, ep_unlink_worker);
|
||||
queue_work(gadgetfs_wq, &priv->unlink_work);
|
||||
|
||||
spin_unlock_irqrestore(&aio_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ep_user_copy_worker(struct work_struct *work)
|
||||
{
|
||||
struct kiocb_priv *priv = container_of(work, struct kiocb_priv, work);
|
||||
struct kiocb_priv *priv = container_of(work, struct kiocb_priv, copy_work);
|
||||
struct mm_struct *mm = priv->mm;
|
||||
struct kiocb *iocb = priv->iocb;
|
||||
size_t ret;
|
||||
|
|
@ -488,47 +556,63 @@ static void ep_user_copy_worker(struct work_struct *work)
|
|||
|
||||
kfree(priv->buf);
|
||||
kfree(priv->to_free);
|
||||
kfree(priv);
|
||||
|
||||
spin_lock_irq(&aio_lock);
|
||||
priv->req_state = AIO_GIVEN_BACK;
|
||||
if (priv->cancel_state != AIO_UNLINKING)
|
||||
kfree(priv);
|
||||
spin_unlock_irq(&aio_lock);
|
||||
}
|
||||
|
||||
static void ep_aio_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
struct kiocb *iocb = req->context;
|
||||
struct kiocb_priv *priv = iocb->private;
|
||||
struct ep_data *epdata = priv->epdata;
|
||||
enum aio_req_state new_req_state;
|
||||
enum aio_cancel_state cancel_state;
|
||||
|
||||
/* lock against disconnect (and ideally, cancel) */
|
||||
spin_lock(&epdata->dev->lock);
|
||||
priv->req = NULL;
|
||||
priv->epdata = NULL;
|
||||
/* Prevent future cancellation */
|
||||
spin_lock(&aio_lock);
|
||||
iocb->private = NULL;
|
||||
spin_unlock(&aio_lock);
|
||||
|
||||
/* if this was a write or a read returning no data then we
|
||||
* don't need to copy anything to userspace, so we can
|
||||
* complete the aio request immediately.
|
||||
*/
|
||||
if (priv->to_free == NULL || unlikely(req->actual == 0)) {
|
||||
if (!priv->is_read || unlikely(req->actual == 0)) {
|
||||
mmdrop(priv->mm);
|
||||
kfree(req->buf);
|
||||
kfree(priv->to_free);
|
||||
kfree(priv);
|
||||
iocb->private = NULL;
|
||||
iocb->ki_complete(iocb,
|
||||
req->actual ? req->actual : (long)req->status);
|
||||
new_req_state = AIO_GIVEN_BACK;
|
||||
} else {
|
||||
/* ep_copy_to_user() won't report both; we hide some faults */
|
||||
if (unlikely(0 != req->status))
|
||||
DBG(epdata->dev, "%s fault %d len %d\n",
|
||||
DBG(priv->epdata->dev, "%s fault %d len %d\n",
|
||||
ep->name, req->status, req->actual);
|
||||
|
||||
priv->buf = req->buf;
|
||||
priv->actual = req->actual;
|
||||
INIT_WORK(&priv->work, ep_user_copy_worker);
|
||||
schedule_work(&priv->work);
|
||||
new_req_state = AIO_COMPLETED;
|
||||
}
|
||||
|
||||
usb_ep_free_request(ep, req);
|
||||
spin_unlock(&epdata->dev->lock);
|
||||
put_ep(epdata);
|
||||
spin_lock(&aio_lock);
|
||||
priv->req_state = new_req_state;
|
||||
cancel_state = priv->cancel_state;
|
||||
spin_unlock(&aio_lock);
|
||||
|
||||
if (new_req_state == AIO_COMPLETED) {
|
||||
INIT_WORK(&priv->copy_work, ep_user_copy_worker);
|
||||
queue_work(gadgetfs_wq, &priv->copy_work);
|
||||
}
|
||||
if (cancel_state != AIO_UNLINKING) {
|
||||
usb_ep_free_request(ep, req);
|
||||
put_ep(priv->epdata);
|
||||
if (new_req_state == AIO_GIVEN_BACK)
|
||||
kfree(priv);
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t ep_aio(struct kiocb *iocb,
|
||||
|
|
@ -539,11 +623,12 @@ static ssize_t ep_aio(struct kiocb *iocb,
|
|||
{
|
||||
struct usb_request *req;
|
||||
ssize_t value;
|
||||
struct usb_ep *ep;
|
||||
bool need_unlink = false;
|
||||
|
||||
iocb->private = priv;
|
||||
priv->iocb = iocb;
|
||||
|
||||
kiocb_set_cancel_fn(iocb, ep_aio_cancel);
|
||||
get_ep(epdata);
|
||||
priv->epdata = epdata;
|
||||
priv->actual = 0;
|
||||
|
|
@ -555,10 +640,12 @@ static ssize_t ep_aio(struct kiocb *iocb,
|
|||
*/
|
||||
spin_lock_irq(&epdata->dev->lock);
|
||||
value = -ENODEV;
|
||||
if (unlikely(epdata->ep == NULL))
|
||||
ep = epdata->ep;
|
||||
if (unlikely(ep == NULL))
|
||||
goto fail;
|
||||
priv->ep = ep;
|
||||
|
||||
req = usb_ep_alloc_request(epdata->ep, GFP_ATOMIC);
|
||||
req = usb_ep_alloc_request(ep, GFP_ATOMIC);
|
||||
value = -ENOMEM;
|
||||
if (unlikely(!req))
|
||||
goto fail;
|
||||
|
|
@ -568,12 +655,45 @@ static ssize_t ep_aio(struct kiocb *iocb,
|
|||
req->length = len;
|
||||
req->complete = ep_aio_complete;
|
||||
req->context = iocb;
|
||||
value = usb_ep_queue(epdata->ep, req, GFP_ATOMIC);
|
||||
|
||||
priv->req_state = AIO_SUBMITTING;
|
||||
priv->cancel_state = AIO_NOT_CANCELLED;
|
||||
|
||||
/* Not allowed to manipulate the aio context while holding dev->lock */
|
||||
++epdata->dev->udc_usage;
|
||||
spin_unlock_irq(&epdata->dev->lock);
|
||||
|
||||
kiocb_set_cancel_fn(iocb, ep_aio_cancel);
|
||||
value = usb_ep_queue(ep, req, GFP_KERNEL);
|
||||
|
||||
spin_lock_irq(&epdata->dev->lock);
|
||||
--epdata->dev->udc_usage;
|
||||
|
||||
if (unlikely(0 != value)) {
|
||||
usb_ep_free_request(epdata->ep, req);
|
||||
spin_lock(&aio_lock);
|
||||
iocb->private = NULL;
|
||||
spin_unlock(&aio_lock);
|
||||
|
||||
usb_ep_free_request(ep, req);
|
||||
goto fail;
|
||||
}
|
||||
spin_unlock_irq(&epdata->dev->lock);
|
||||
|
||||
spin_lock_irq(&aio_lock);
|
||||
if (iocb->private != NULL) {
|
||||
priv->req_state = AIO_RUNNING;
|
||||
|
||||
/* Cancelled before or just after submission? */
|
||||
if (priv->cancel_state == AIO_UNLINK_DONE) {
|
||||
priv->cancel_state = AIO_NOT_CANCELLED;
|
||||
need_unlink = true;
|
||||
}
|
||||
} /* Otherwise already completed */
|
||||
spin_unlock_irq(&aio_lock);
|
||||
|
||||
if (need_unlink) /* Redo cancel that was too early */
|
||||
ep_aio_cancel(iocb);
|
||||
|
||||
return -EIOCBQUEUED;
|
||||
|
||||
fail:
|
||||
|
|
@ -627,6 +747,7 @@ ep_read_iter(struct kiocb *iocb, struct iov_iter *to)
|
|||
value = -ENOMEM;
|
||||
if (!priv)
|
||||
goto fail;
|
||||
priv->is_read = true;
|
||||
priv->to_free = dup_iter(&priv->to, to, GFP_KERNEL);
|
||||
if (!iter_is_ubuf(&priv->to) && !priv->to_free) {
|
||||
kfree(priv);
|
||||
|
|
@ -1692,6 +1813,8 @@ static int gadgetfs_bind(struct usb_gadget *gadget,
|
|||
dev->gadget = gadget;
|
||||
gadget->ep0->driver_data = dev;
|
||||
|
||||
get_dev (dev);
|
||||
|
||||
/* preallocate control response and buffer */
|
||||
dev->req = usb_ep_alloc_request (gadget->ep0, GFP_KERNEL);
|
||||
if (!dev->req)
|
||||
|
|
@ -1706,7 +1829,6 @@ static int gadgetfs_bind(struct usb_gadget *gadget,
|
|||
spin_lock_irq(&dev->lock);
|
||||
dev->state = STATE_DEV_UNCONNECTED;
|
||||
spin_unlock_irq(&dev->lock);
|
||||
get_dev (dev);
|
||||
return 0;
|
||||
|
||||
enomem:
|
||||
|
|
@ -2135,10 +2257,17 @@ static int __init gadgetfs_init (void)
|
|||
{
|
||||
int status;
|
||||
|
||||
gadgetfs_wq = alloc_workqueue("gadgetfs", WQ_PERCPU, 0);
|
||||
if (!gadgetfs_wq)
|
||||
return -ENOMEM;
|
||||
|
||||
status = register_filesystem (&gadgetfs_type);
|
||||
if (status == 0)
|
||||
pr_info ("%s: %s, version " DRIVER_VERSION "\n",
|
||||
shortname, driver_desc);
|
||||
if (status) {
|
||||
destroy_workqueue(gadgetfs_wq);
|
||||
return status;
|
||||
}
|
||||
|
||||
pr_info("%s: %s, version " DRIVER_VERSION "\n", shortname, driver_desc);
|
||||
return status;
|
||||
}
|
||||
module_init (gadgetfs_init);
|
||||
|
|
@ -2147,6 +2276,7 @@ static void __exit gadgetfs_cleanup (void)
|
|||
{
|
||||
pr_debug ("unregister %s\n", shortname);
|
||||
unregister_filesystem (&gadgetfs_type);
|
||||
destroy_workqueue(gadgetfs_wq);
|
||||
}
|
||||
module_exit (gadgetfs_cleanup);
|
||||
|
||||
|
|
|
|||
|
|
@ -383,10 +383,8 @@ static int ast_vhub_probe(struct platform_device *pdev)
|
|||
}
|
||||
rc = devm_request_irq(&pdev->dev, vhub->irq, ast_vhub_irq, 0,
|
||||
KBUILD_MODNAME, vhub);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "Failed to request interrupt\n");
|
||||
if (rc)
|
||||
goto err;
|
||||
}
|
||||
|
||||
dma_mask_ptr = (u64 *)of_device_get_match_data(&pdev->dev);
|
||||
if (dma_mask_ptr) {
|
||||
|
|
|
|||
|
|
@ -592,8 +592,6 @@ int ast_vhub_init_dev(struct ast_vhub *vhub, unsigned int idx)
|
|||
else
|
||||
d->gadget.max_speed = USB_SPEED_HIGH;
|
||||
d->gadget.speed = USB_SPEED_UNKNOWN;
|
||||
d->gadget.dev.of_node = vhub->pdev->dev.of_node;
|
||||
dev_set_of_node_reused(&d->gadget.dev);
|
||||
|
||||
rc = usb_add_gadget_udc(d->port_dev, &d->gadget);
|
||||
if (rc != 0)
|
||||
|
|
|
|||
|
|
@ -1299,7 +1299,6 @@ static int ast_udc_start(struct usb_gadget *gadget,
|
|||
|
||||
UDC_DBG(udc, "\n");
|
||||
udc->driver = driver;
|
||||
udc->gadget.dev.of_node = udc->pdev->dev.of_node;
|
||||
|
||||
for (i = 0; i < AST_UDC_NUM_ENDPOINTS; i++) {
|
||||
ep = &udc->ep[i];
|
||||
|
|
@ -1568,10 +1567,8 @@ static int ast_udc_probe(struct platform_device *pdev)
|
|||
|
||||
rc = devm_request_irq(&pdev->dev, udc->irq, ast_udc_isr, 0,
|
||||
KBUILD_MODNAME, udc);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "Failed to request interrupt\n");
|
||||
if (rc)
|
||||
goto err_cleanup;
|
||||
}
|
||||
|
||||
rc = usb_add_gadget_udc(&pdev->dev, &udc->gadget);
|
||||
if (rc) {
|
||||
|
|
|
|||
|
|
@ -1558,7 +1558,6 @@ static int at91_start(struct usb_gadget *gadget,
|
|||
|
||||
udc = container_of(gadget, struct at91_udc, gadget);
|
||||
udc->driver = driver;
|
||||
udc->gadget.dev.of_node = udc->pdev->dev.of_node;
|
||||
udc->enabled = 1;
|
||||
udc->gadget.is_selfpowered = 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -2332,11 +2332,8 @@ static int usba_udc_probe(struct platform_device *pdev)
|
|||
|
||||
ret = devm_request_irq(&pdev->dev, irq, usba_udc_irq, 0,
|
||||
"atmel_usba_udc", udc);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Cannot request irq %d (error %d)\n",
|
||||
irq, ret);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
udc->irq = irq;
|
||||
|
||||
if (udc->vbus_pin) {
|
||||
|
|
|
|||
|
|
@ -1830,7 +1830,6 @@ static int bcm63xx_udc_start(struct usb_gadget *gadget,
|
|||
bcm63xx_select_phy_mode(udc, true);
|
||||
|
||||
udc->driver = driver;
|
||||
udc->gadget.dev.of_node = udc->dev->of_node;
|
||||
|
||||
spin_unlock_irqrestore(&udc->lock, flags);
|
||||
|
||||
|
|
|
|||
|
|
@ -142,13 +142,14 @@ int usb_ep_enable(struct usb_ep *ep)
|
|||
EXPORT_SYMBOL_GPL(usb_ep_enable);
|
||||
|
||||
/**
|
||||
* usb_ep_disable - endpoint is no longer usable
|
||||
* usb_ep_disable - unconfigure an endpoint, making it no longer usable
|
||||
* @ep:the endpoint being unconfigured. may not be the endpoint named "ep0".
|
||||
*
|
||||
* no other task may be using this endpoint when this is called.
|
||||
* any pending and uncompleted requests will complete with status
|
||||
* Any pending and uncompleted requests will complete with status
|
||||
* indicating disconnect (-ESHUTDOWN) before this call returns.
|
||||
* gadget drivers must call usb_ep_enable() again before queueing
|
||||
* (However, this call will not wait for requests whose completion
|
||||
* handlers have already started running but not yet returned.)
|
||||
* Gadget drivers must call usb_ep_enable() again before queuing
|
||||
* requests to the endpoint.
|
||||
*
|
||||
* This routine may be called in an atomic (interrupt) context.
|
||||
|
|
|
|||
|
|
@ -51,8 +51,6 @@
|
|||
#define POWER_BUDGET 500 /* in mA; use 8 for low-power port testing */
|
||||
#define POWER_BUDGET_3 900 /* in mA */
|
||||
|
||||
#define DUMMY_TIMER_INT_NSECS 125000 /* 1 microframe */
|
||||
|
||||
static const char driver_name[] = "dummy_hcd";
|
||||
static const char driver_desc[] = "USB Host+Gadget Emulator";
|
||||
|
||||
|
|
@ -66,6 +64,31 @@ struct dummy_hcd_module_parameters {
|
|||
bool is_super_speed;
|
||||
bool is_high_speed;
|
||||
unsigned int num;
|
||||
unsigned int tick_us;
|
||||
};
|
||||
|
||||
/*
|
||||
* Length in microseconds of an emulated microframe. The default (125,
|
||||
* one high-speed microframe) preserves the historical timer rate; larger
|
||||
* values slow the emulated controller down and reduce its interrupt-rate
|
||||
* pressure on the host platform. Values below 125 are rejected.
|
||||
*/
|
||||
static int tick_us_set(const char *val,
|
||||
const struct kernel_param *kp)
|
||||
{
|
||||
unsigned int v;
|
||||
int ret = kstrtouint(val, 0, &v);
|
||||
|
||||
if (ret < 0 || v < 125) {
|
||||
pr_err("dummy_hcd: tick_us must be >= 125\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
return param_set_uint(val, kp);
|
||||
}
|
||||
|
||||
static const struct kernel_param_ops tick_us_ops = {
|
||||
.set = tick_us_set,
|
||||
.get = param_get_uint,
|
||||
};
|
||||
|
||||
static struct dummy_hcd_module_parameters mod_data = {
|
||||
|
|
@ -79,6 +102,9 @@ module_param_named(is_high_speed, mod_data.is_high_speed, bool, S_IRUGO);
|
|||
MODULE_PARM_DESC(is_high_speed, "true to simulate HighSpeed connection");
|
||||
module_param_named(num, mod_data.num, uint, S_IRUGO);
|
||||
MODULE_PARM_DESC(num, "number of emulated controllers");
|
||||
module_param_cb(tick_us, &tick_us_ops, &mod_data.tick_us, 0444);
|
||||
MODULE_PARM_DESC(tick_us,
|
||||
"Length in microseconds of an emulated (micro)frame (default 125 for high/super-speed emulation, 1000 for full-speed); larger values reduce the interrupt-rate pressure on the host platform");
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* gadget side driver data structures */
|
||||
|
|
@ -244,6 +270,7 @@ struct dummy_hcd {
|
|||
struct dummy *dum;
|
||||
enum dummy_rh_state rh_state;
|
||||
struct hrtimer timer;
|
||||
ktime_t timer_interval; /* emulated microframe length */
|
||||
u32 port_status;
|
||||
u32 old_status;
|
||||
unsigned long re_timeout;
|
||||
|
|
@ -918,7 +945,7 @@ static int dummy_set_selfpowered(struct usb_gadget *_gadget, int value)
|
|||
static void dummy_udc_update_ep0(struct dummy *dum)
|
||||
{
|
||||
if (dum->gadget.speed == USB_SPEED_SUPER)
|
||||
dum->ep[0].ep.maxpacket = 9;
|
||||
dum->ep[0].ep.maxpacket = 512;
|
||||
else
|
||||
dum->ep[0].ep.maxpacket = 64;
|
||||
}
|
||||
|
|
@ -1184,6 +1211,7 @@ static struct platform_driver dummy_udc_driver = {
|
|||
.resume = dummy_udc_resume,
|
||||
.driver = {
|
||||
.name = gadget_name,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -1333,7 +1361,7 @@ static int dummy_urb_enqueue(
|
|||
/* kick the scheduler, it'll do the rest */
|
||||
if (!dum_hcd->timer_pending) {
|
||||
dum_hcd->timer_pending = 1;
|
||||
hrtimer_start(&dum_hcd->timer, ns_to_ktime(DUMMY_TIMER_INT_NSECS),
|
||||
hrtimer_start(&dum_hcd->timer, dum_hcd->timer_interval,
|
||||
HRTIMER_MODE_REL_SOFT);
|
||||
}
|
||||
|
||||
|
|
@ -2033,7 +2061,7 @@ static enum hrtimer_restart dummy_timer(struct hrtimer *t)
|
|||
dum_hcd->rh_state == DUMMY_RH_RUNNING) {
|
||||
/* want a 1 msec delay here */
|
||||
dum_hcd->timer_pending = 1;
|
||||
hrtimer_start(&dum_hcd->timer, ns_to_ktime(DUMMY_TIMER_INT_NSECS),
|
||||
hrtimer_start(&dum_hcd->timer, dum_hcd->timer_interval,
|
||||
HRTIMER_MODE_REL_SOFT);
|
||||
}
|
||||
|
||||
|
|
@ -2513,6 +2541,7 @@ static DEVICE_ATTR_RO(urbs);
|
|||
static int dummy_start_ss(struct dummy_hcd *dum_hcd)
|
||||
{
|
||||
hrtimer_setup(&dum_hcd->timer, dummy_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT);
|
||||
dum_hcd->timer_interval = ns_to_ktime((u64)mod_data.tick_us * NSEC_PER_USEC);
|
||||
dum_hcd->rh_state = DUMMY_RH_RUNNING;
|
||||
dum_hcd->stream_en_ep = 0;
|
||||
INIT_LIST_HEAD(&dum_hcd->urbp_list);
|
||||
|
|
@ -2542,6 +2571,7 @@ static int dummy_start(struct usb_hcd *hcd)
|
|||
|
||||
spin_lock_init(&dum_hcd->dum->lock);
|
||||
hrtimer_setup(&dum_hcd->timer, dummy_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT);
|
||||
dum_hcd->timer_interval = ns_to_ktime((u64)mod_data.tick_us * NSEC_PER_USEC);
|
||||
dum_hcd->rh_state = DUMMY_RH_RUNNING;
|
||||
|
||||
INIT_LIST_HEAD(&dum_hcd->urbp_list);
|
||||
|
|
@ -2596,6 +2626,15 @@ static int dummy_setup(struct usb_hcd *hcd)
|
|||
dum->ss_hcd->dum = dum;
|
||||
hcd->speed = HCD_USB3;
|
||||
hcd->self.root_hub->speed = USB_SPEED_SUPER;
|
||||
|
||||
/*
|
||||
* xHCI-class SuperSpeed controllers have no maxpacket
|
||||
* alignment requirement for non-final SG segments. Only
|
||||
* relax the constraint on the SS bus; the HS bus keeps
|
||||
* emulating the alignment requirement of EHCI-class
|
||||
* controllers.
|
||||
*/
|
||||
hcd->self.no_sg_constraint = 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -2806,6 +2845,7 @@ static struct platform_driver dummy_hcd_driver = {
|
|||
.resume = dummy_hcd_resume,
|
||||
.driver = {
|
||||
.name = driver_name,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -2832,6 +2872,17 @@ static int __init dummy_hcd_init(void)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* The emulated microframe length defaults to one high-speed
|
||||
* microframe (125 us). When neither is_high_speed nor
|
||||
* is_super_speed is set the emulation is full-speed, where the
|
||||
* natural scheduling unit is the 1-ms frame, so default to
|
||||
* 1000 us instead.
|
||||
*/
|
||||
if (!mod_data.tick_us)
|
||||
mod_data.tick_us = (mod_data.is_super_speed ||
|
||||
mod_data.is_high_speed) ? 125 : 1000;
|
||||
|
||||
for (i = 0; i < mod_data.num; i++) {
|
||||
the_hcd_pdev[i] = platform_device_alloc(driver_name, i);
|
||||
if (!the_hcd_pdev[i]) {
|
||||
|
|
|
|||
|
|
@ -2465,9 +2465,6 @@ static int fsl_udc_probe(struct platform_device *pdev)
|
|||
udc_controller->gadget.speed = USB_SPEED_UNKNOWN;
|
||||
udc_controller->gadget.name = driver_name;
|
||||
|
||||
/* Setup gadget.dev and register with kernel */
|
||||
udc_controller->gadget.dev.of_node = pdev->dev.of_node;
|
||||
|
||||
if (!IS_ERR_OR_NULL(udc_controller->transceiver))
|
||||
udc_controller->gadget.is_otg = 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -2889,7 +2889,6 @@ static int lpc32xx_start(struct usb_gadget *gadget,
|
|||
}
|
||||
|
||||
udc->driver = driver;
|
||||
udc->gadget.dev.of_node = udc->dev->of_node;
|
||||
udc->enabled = 1;
|
||||
udc->gadget.is_selfpowered = 1;
|
||||
udc->vbus = 0;
|
||||
|
|
|
|||
|
|
@ -1894,10 +1894,8 @@ static int r8a66597_probe(struct platform_device *pdev)
|
|||
|
||||
ret = devm_request_irq(dev, irq, r8a66597_irq, IRQF_SHARED,
|
||||
udc_name, r8a66597);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "request_irq error (%d)\n", ret);
|
||||
if (ret < 0)
|
||||
goto clean_up2;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&r8a66597->gadget.ep_list);
|
||||
r8a66597->gadget.ep0 = &r8a66597->ep[0].ep;
|
||||
|
|
|
|||
|
|
@ -3335,19 +3335,15 @@ static int usbf_probe(struct platform_device *pdev)
|
|||
if (irq < 0)
|
||||
return irq;
|
||||
ret = devm_request_irq(dev, irq, usbf_epc_irq, 0, "usbf-epc", udc);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot request irq %d err %d\n", irq, ret);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
irq = platform_get_irq(pdev, 1);
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
ret = devm_request_irq(dev, irq, usbf_ahb_epc_irq, 0, "usbf-ahb-epc", udc);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot request irq %d err %d\n", irq, ret);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
usbf_reg_bitset(udc, USBF_REG_AHBMCTR, USBF_SYS_WBURST_TYPE);
|
||||
|
||||
|
|
|
|||
|
|
@ -192,10 +192,8 @@ static int udc_plat_probe(struct platform_device *pdev)
|
|||
|
||||
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);
|
||||
if (ret < 0)
|
||||
goto exit_dma;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, udc);
|
||||
udc->chiprev = UDC_BCM_REV;
|
||||
|
|
|
|||
|
|
@ -3803,11 +3803,8 @@ static int tegra_xudc_probe(struct platform_device *pdev)
|
|||
|
||||
err = devm_request_irq(&pdev->dev, xudc->irq, tegra_xudc_irq, 0,
|
||||
dev_name(&pdev->dev), xudc);
|
||||
if (err < 0) {
|
||||
dev_err(xudc->dev, "failed to claim IRQ#%u: %d\n", xudc->irq,
|
||||
err);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
xudc->clks = devm_kcalloc(&pdev->dev, xudc->soc->num_clks, sizeof(*xudc->clks),
|
||||
GFP_KERNEL);
|
||||
|
|
|
|||
|
|
@ -211,6 +211,19 @@ config USB_EHCI_PCI
|
|||
depends on USB_PCI
|
||||
default y
|
||||
|
||||
config USB_EHCI_DEVIANT_PORT_STATUS_REG
|
||||
bool
|
||||
# Used for hosts with a deviant port status register location
|
||||
|
||||
config USB_EHCI_PORT_RESET_HOOKS
|
||||
bool
|
||||
# Used for hosts with controller-specific port reset sequencing
|
||||
|
||||
config USB_EHCI_PORT_SPEED_HOOK
|
||||
bool
|
||||
depends on USB_EHCI_ROOT_HUB_TT
|
||||
# Used for hosts with controller-specific port speed reporting
|
||||
|
||||
config XPS_USB_HCD_XILINX
|
||||
bool "Use Xilinx usb host EHCI controller core"
|
||||
depends on (PPC32 || MICROBLAZE)
|
||||
|
|
|
|||
|
|
@ -869,8 +869,7 @@ static ssize_t fill_registers_buffer(struct debug_buffer *buf)
|
|||
|
||||
for (i = 1; i <= HCS_N_PORTS(ehci->hcs_params); i++) {
|
||||
temp = dbg_port_buf(scratch, sizeof(scratch), label, i,
|
||||
ehci_readl(ehci,
|
||||
&ehci->regs->port_status[i - 1]));
|
||||
ehci_readl(ehci, ehci_portsc(ehci, i - 1)));
|
||||
temp = scnprintf(next, size, fmt, temp, scratch);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
|
|
|||
|
|
@ -187,7 +187,8 @@ static int ehci_halt (struct ehci_hcd *ehci)
|
|||
/* disable any irqs left enabled by previous code */
|
||||
ehci_writel(ehci, 0, &ehci->regs->intr_enable);
|
||||
|
||||
if (ehci_is_TDI(ehci) && !tdi_in_host_mode(ehci)) {
|
||||
if (ehci_is_TDI(ehci) && !ehci->no_tdi_mode &&
|
||||
!tdi_in_host_mode(ehci)) {
|
||||
spin_unlock_irq(&ehci->lock);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -254,7 +255,7 @@ int ehci_reset(struct ehci_hcd *ehci)
|
|||
if (retval)
|
||||
return retval;
|
||||
|
||||
if (ehci_is_TDI(ehci))
|
||||
if (ehci_is_TDI(ehci) && !ehci->no_tdi_mode)
|
||||
tdi_reset (ehci);
|
||||
|
||||
if (ehci->debug)
|
||||
|
|
@ -325,7 +326,7 @@ static void ehci_turn_off_all_ports(struct ehci_hcd *ehci)
|
|||
ehci_port_power(ehci, port, false);
|
||||
spin_lock_irq(&ehci->lock);
|
||||
ehci_writel(ehci, PORT_RWC_BITS,
|
||||
&ehci->regs->port_status[port]);
|
||||
ehci_portsc(ehci, port));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -342,10 +343,12 @@ static void ehci_silence_controller(struct ehci_hcd *ehci)
|
|||
ehci_turn_off_all_ports(ehci);
|
||||
|
||||
/* make BIOS/etc use companion controller during reboot */
|
||||
ehci_writel(ehci, 0, &ehci->regs->configured_flag);
|
||||
if (!ehci->no_configured_flag) {
|
||||
ehci_writel(ehci, 0, &ehci->regs->configured_flag);
|
||||
|
||||
/* unblock posted writes */
|
||||
ehci_readl(ehci, &ehci->regs->configured_flag);
|
||||
/* unblock posted writes */
|
||||
ehci_readl(ehci, &ehci->regs->configured_flag);
|
||||
}
|
||||
spin_unlock_irq(&ehci->lock);
|
||||
}
|
||||
|
||||
|
|
@ -629,7 +632,8 @@ static int ehci_run (struct usb_hcd *hcd)
|
|||
*/
|
||||
down_write(&ehci_cf_port_reset_rwsem);
|
||||
ehci->rh_state = EHCI_RH_RUNNING;
|
||||
ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag);
|
||||
if (!ehci->no_configured_flag)
|
||||
ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag);
|
||||
|
||||
/* Wait until HC become operational */
|
||||
ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */
|
||||
|
|
@ -811,8 +815,7 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd)
|
|||
/* leverage per-port change bits feature */
|
||||
if (!(ppcd & (1 << i)))
|
||||
continue;
|
||||
pstatus = ehci_readl(ehci,
|
||||
&ehci->regs->port_status[i]);
|
||||
pstatus = ehci_readl(ehci, ehci_portsc(ehci, i));
|
||||
|
||||
if (pstatus & PORT_OWNER)
|
||||
continue;
|
||||
|
|
@ -910,6 +913,8 @@ static int ehci_urb_enqueue (
|
|||
case PIPE_ISOCHRONOUS:
|
||||
if (urb->dev->speed == USB_SPEED_HIGH)
|
||||
return itd_submit (ehci, urb, mem_flags);
|
||||
else if (ehci->no_fsls_isoc)
|
||||
return -EOPNOTSUPP;
|
||||
else
|
||||
return sitd_submit (ehci, urb, mem_flags);
|
||||
}
|
||||
|
|
@ -1109,7 +1114,7 @@ static void ehci_remove_device(struct usb_hcd *hcd, struct usb_device *udev)
|
|||
/* Clear wakeup signal locked in zhaoxin platform when device plug in. */
|
||||
static void ehci_zx_wakeup_clear(struct ehci_hcd *ehci)
|
||||
{
|
||||
u32 __iomem *reg = &ehci->regs->port_status[4];
|
||||
u32 __iomem *reg = ehci_portsc(ehci, 4);
|
||||
u32 t1 = ehci_readl(ehci, reg);
|
||||
|
||||
t1 &= (u32)~0xf0000;
|
||||
|
|
@ -1185,7 +1190,8 @@ int ehci_resume(struct usb_hcd *hcd, bool force_reset)
|
|||
* then we maintained suspend power.
|
||||
* Just undo the effect of ehci_suspend().
|
||||
*/
|
||||
if (ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF &&
|
||||
if ((ehci->no_configured_flag ||
|
||||
ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF) &&
|
||||
!force_reset) {
|
||||
int mask = INTR_MASK;
|
||||
|
||||
|
|
@ -1217,7 +1223,8 @@ int ehci_resume(struct usb_hcd *hcd, bool force_reset)
|
|||
goto skip;
|
||||
|
||||
ehci_writel(ehci, ehci->command, &ehci->regs->command);
|
||||
ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag);
|
||||
if (!ehci->no_configured_flag)
|
||||
ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag);
|
||||
ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */
|
||||
|
||||
ehci->rh_state = EHCI_RH_SUSPENDED;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,44 @@
|
|||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* Some EHCI controllers have a Transaction Translator built into the
|
||||
* root hub. This is a non-standard feature. Each controller will need
|
||||
* to add code to the following function, and call it as needed.
|
||||
*/
|
||||
|
||||
/*
|
||||
* In a bunch of EHCI implementations with transaction translators,
|
||||
* the port speed can be found in the reserved bits in position 26 and
|
||||
* 27. Implementations with the HOSTPC register will have this in
|
||||
* bits 25 and 26 of the HOSTPC registers.
|
||||
*/
|
||||
#define PORTSC_SPEED_BITS(a) (((a) >> 26) & 3)
|
||||
#define HOSTPC_SPEED_BITS(a) (((a) >> 25) & 3)
|
||||
|
||||
/* Returns the speed of a device attached to a port on the root hub. */
|
||||
static unsigned int ehci_port_speed(struct ehci_hcd *ehci,
|
||||
unsigned int port, unsigned int speed)
|
||||
{
|
||||
#ifdef CONFIG_USB_EHCI_PORT_SPEED_HOOK
|
||||
if (ehci->get_port_speed)
|
||||
return ehci->get_port_speed(ehci, port);
|
||||
#endif
|
||||
|
||||
if (!IS_ENABLED(CONFIG_USB_EHCI_ROOT_HUB_TT) || !ehci_is_TDI(ehci))
|
||||
return USB_PORT_STAT_HIGH_SPEED;
|
||||
|
||||
switch (speed) {
|
||||
case 0:
|
||||
return 0;
|
||||
case 1:
|
||||
return USB_PORT_STAT_LOW_SPEED;
|
||||
case 2:
|
||||
default:
|
||||
return USB_PORT_STAT_HIGH_SPEED;
|
||||
}
|
||||
}
|
||||
|
||||
#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E)
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
|
@ -55,7 +93,7 @@ static void ehci_handover_companion_ports(struct ehci_hcd *ehci)
|
|||
port = HCS_N_PORTS(ehci->hcs_params);
|
||||
while (port--) {
|
||||
if (test_bit(port, &ehci->owned_ports)) {
|
||||
reg = &ehci->regs->port_status[port];
|
||||
reg = ehci_portsc(ehci, port);
|
||||
status = ehci_readl(ehci, reg) & ~PORT_RWC_BITS;
|
||||
if (!(status & PORT_POWER))
|
||||
ehci_port_power(ehci, port, true);
|
||||
|
|
@ -69,7 +107,7 @@ static void ehci_handover_companion_ports(struct ehci_hcd *ehci)
|
|||
port = HCS_N_PORTS(ehci->hcs_params);
|
||||
while (port--) {
|
||||
if (test_bit(port, &ehci->owned_ports)) {
|
||||
reg = &ehci->regs->port_status[port];
|
||||
reg = ehci_portsc(ehci, port);
|
||||
status = ehci_readl(ehci, reg) & ~PORT_RWC_BITS;
|
||||
|
||||
/* Port already owned by companion? */
|
||||
|
|
@ -106,7 +144,7 @@ static void ehci_handover_companion_ports(struct ehci_hcd *ehci)
|
|||
* but if something went wrong the port must not
|
||||
* remain enabled.
|
||||
*/
|
||||
reg = &ehci->regs->port_status[port];
|
||||
reg = ehci_portsc(ehci, port);
|
||||
status = ehci_readl(ehci, reg) & ~PORT_RWC_BITS;
|
||||
if (status & PORT_OWNER)
|
||||
ehci_writel(ehci, status | PORT_CSC, reg);
|
||||
|
|
@ -137,7 +175,7 @@ static int ehci_port_change(struct ehci_hcd *ehci)
|
|||
*/
|
||||
|
||||
while (i--)
|
||||
if (ehci_readl(ehci, &ehci->regs->port_status[i]) & PORT_CSC)
|
||||
if (ehci_readl(ehci, ehci_portsc(ehci, i)) & PORT_CSC)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
|
|
@ -175,7 +213,7 @@ void ehci_adjust_port_wakeup_flags(struct ehci_hcd *ehci,
|
|||
|
||||
port = HCS_N_PORTS(ehci->hcs_params);
|
||||
while (port--) {
|
||||
u32 __iomem *reg = &ehci->regs->port_status[port];
|
||||
u32 __iomem *reg = ehci_portsc(ehci, port);
|
||||
u32 t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS;
|
||||
u32 t2 = t1 & ~PORT_WAKE_BITS;
|
||||
|
||||
|
|
@ -254,7 +292,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd)
|
|||
fs_idle_delay = false;
|
||||
port = HCS_N_PORTS(ehci->hcs_params);
|
||||
while (port--) {
|
||||
u32 __iomem *reg = &ehci->regs->port_status [port];
|
||||
u32 __iomem *reg = ehci_portsc(ehci, port);
|
||||
u32 t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS;
|
||||
u32 t2 = t1 & ~PORT_WAKE_BITS;
|
||||
|
||||
|
|
@ -287,8 +325,9 @@ static int ehci_bus_suspend (struct usb_hcd *hcd)
|
|||
* sake, add a delay if we need one.
|
||||
*/
|
||||
if ((t2 & PORT_WKDISC_E) &&
|
||||
ehci_port_speed(ehci, t2) ==
|
||||
USB_PORT_STAT_HIGH_SPEED)
|
||||
ehci_port_speed(ehci, port,
|
||||
PORTSC_SPEED_BITS(t2)) ==
|
||||
USB_PORT_STAT_HIGH_SPEED)
|
||||
fs_idle_delay = true;
|
||||
ehci_writel(ehci, t2, reg);
|
||||
changed = 1;
|
||||
|
|
@ -426,7 +465,7 @@ static int ehci_bus_resume (struct usb_hcd *hcd)
|
|||
*/
|
||||
i = HCS_N_PORTS(ehci->hcs_params);
|
||||
while (i--) {
|
||||
temp = ehci_readl(ehci, &ehci->regs->port_status[i]);
|
||||
temp = ehci_readl(ehci, ehci_portsc(ehci, i));
|
||||
if ((temp & PORT_PE) &&
|
||||
!(temp & (PORT_SUSPEND | PORT_RESUME))) {
|
||||
ehci_dbg(ehci, "Port status(0x%x) is wrong\n", temp);
|
||||
|
|
@ -463,14 +502,14 @@ static int ehci_bus_resume (struct usb_hcd *hcd)
|
|||
/* manually resume the ports we suspended during bus_suspend() */
|
||||
i = HCS_N_PORTS (ehci->hcs_params);
|
||||
while (i--) {
|
||||
temp = ehci_readl(ehci, &ehci->regs->port_status [i]);
|
||||
temp = ehci_readl(ehci, ehci_portsc(ehci, i));
|
||||
temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS);
|
||||
if (test_bit(i, &ehci->bus_suspended) &&
|
||||
(temp & PORT_SUSPEND)) {
|
||||
temp |= PORT_RESUME;
|
||||
set_bit(i, &resume_needed);
|
||||
}
|
||||
ehci_writel(ehci, temp, &ehci->regs->port_status [i]);
|
||||
ehci_writel(ehci, temp, ehci_portsc(ehci, i));
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -487,10 +526,10 @@ static int ehci_bus_resume (struct usb_hcd *hcd)
|
|||
|
||||
i = HCS_N_PORTS (ehci->hcs_params);
|
||||
while (i--) {
|
||||
temp = ehci_readl(ehci, &ehci->regs->port_status [i]);
|
||||
temp = ehci_readl(ehci, ehci_portsc(ehci, i));
|
||||
if (test_bit(i, &resume_needed)) {
|
||||
temp &= ~(PORT_RWC_BITS | PORT_SUSPEND | PORT_RESUME);
|
||||
ehci_writel(ehci, temp, &ehci->regs->port_status [i]);
|
||||
ehci_writel(ehci, temp, ehci_portsc(ehci, i));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -540,7 +579,7 @@ static void set_owner(struct ehci_hcd *ehci, int portnum, int new_owner)
|
|||
u32 port_status;
|
||||
int try;
|
||||
|
||||
status_reg = &ehci->regs->port_status[portnum];
|
||||
status_reg = ehci_portsc(ehci, portnum);
|
||||
|
||||
/*
|
||||
* The controller won't set the OWNER bit if the port is
|
||||
|
|
@ -661,7 +700,7 @@ ehci_hub_status_data (struct usb_hcd *hcd, char *buf)
|
|||
for (i = 0; i < ports; i++) {
|
||||
/* leverage per-port change bits feature */
|
||||
if (ppcd & (1 << i))
|
||||
temp = ehci_readl(ehci, &ehci->regs->port_status[i]);
|
||||
temp = ehci_readl(ehci, ehci_portsc(ehci, i));
|
||||
else
|
||||
temp = 0;
|
||||
|
||||
|
|
@ -753,7 +792,7 @@ int ehci_hub_control(
|
|||
temp = (wIndex - 1) & 0xff;
|
||||
if (temp >= HCS_N_PORTS_MAX)
|
||||
temp = 0;
|
||||
status_reg = &ehci->regs->port_status[temp];
|
||||
status_reg = ehci_portsc(ehci, temp);
|
||||
hostpc_reg = &ehci->regs->hostpc[temp];
|
||||
|
||||
/*
|
||||
|
|
@ -963,6 +1002,13 @@ int ehci_hub_control(
|
|||
/* see what we found out */
|
||||
temp = check_reset_complete (ehci, wIndex, status_reg,
|
||||
ehci_readl(ehci, status_reg));
|
||||
#ifdef CONFIG_USB_EHCI_PORT_RESET_HOOKS
|
||||
if (ehci->post_port_reset) {
|
||||
retval = ehci->post_port_reset(ehci, wIndex);
|
||||
if (retval)
|
||||
goto error_exit;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* transfer dedicated ports to the companion hc */
|
||||
|
|
@ -983,12 +1029,14 @@ int ehci_hub_control(
|
|||
|
||||
if (temp & PORT_CONNECT) {
|
||||
status |= USB_PORT_STAT_CONNECTION;
|
||||
// status may be from integrated TT
|
||||
if (ehci->has_hostpc) {
|
||||
temp1 = ehci_readl(ehci, hostpc_reg);
|
||||
status |= ehci_port_speed(ehci, temp1);
|
||||
} else
|
||||
status |= ehci_port_speed(ehci, temp);
|
||||
status |= ehci_port_speed(ehci, wIndex,
|
||||
HOSTPC_SPEED_BITS(temp1));
|
||||
} else {
|
||||
status |= ehci_port_speed(ehci, wIndex,
|
||||
PORTSC_SPEED_BITS(temp));
|
||||
}
|
||||
}
|
||||
if (temp & PORT_PE)
|
||||
status |= USB_PORT_STAT_ENABLE;
|
||||
|
|
@ -1121,7 +1169,16 @@ int ehci_hub_control(
|
|||
*/
|
||||
if (ehci_has_fsl_hs_errata(ehci))
|
||||
temp |= (1 << PORTSC_FSL_PFSC);
|
||||
|
||||
#ifdef CONFIG_USB_EHCI_PORT_RESET_HOOKS
|
||||
if (ehci->pre_port_reset) {
|
||||
retval = ehci->pre_port_reset(ehci, wIndex);
|
||||
if (retval)
|
||||
goto error_exit;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
ehci_writel(ehci, temp, status_reg);
|
||||
break;
|
||||
|
||||
|
|
@ -1149,8 +1206,7 @@ int ehci_hub_control(
|
|||
|
||||
/* Put all enabled ports into suspend */
|
||||
while (ports--) {
|
||||
u32 __iomem *sreg =
|
||||
&ehci->regs->port_status[ports];
|
||||
u32 __iomem *sreg = ehci_portsc(ehci, ports);
|
||||
|
||||
temp = ehci_readl(ehci, sreg) & ~PORT_RWC_BITS;
|
||||
if (temp & PORT_PE)
|
||||
|
|
@ -1200,14 +1256,14 @@ static int ehci_port_handed_over(struct usb_hcd *hcd, int portnum)
|
|||
|
||||
if (ehci_is_TDI(ehci))
|
||||
return 0;
|
||||
reg = &ehci->regs->port_status[portnum - 1];
|
||||
reg = ehci_portsc(ehci, portnum - 1);
|
||||
return ehci_readl(ehci, reg) & PORT_OWNER;
|
||||
}
|
||||
|
||||
static int ehci_port_power(struct ehci_hcd *ehci, int portnum, bool enable)
|
||||
{
|
||||
struct usb_hcd *hcd = ehci_to_hcd(ehci);
|
||||
u32 __iomem *status_reg = &ehci->regs->port_status[portnum];
|
||||
u32 __iomem *status_reg = ehci_portsc(ehci, portnum);
|
||||
u32 temp = ehci_readl(ehci, status_reg) & ~PORT_RWC_BITS;
|
||||
|
||||
if (enable)
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@
|
|||
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
|
||||
static const struct hc_driver ehci_ppc_of_hc_driver = {
|
||||
|
|
@ -96,7 +94,9 @@ static int ehci_hcd_ppc_of_probe(struct platform_device *op)
|
|||
struct device_node *dn = op->dev.of_node;
|
||||
struct usb_hcd *hcd;
|
||||
struct ehci_hcd *ehci = NULL;
|
||||
struct resource res;
|
||||
struct resource *res;
|
||||
struct resource ohci_res;
|
||||
void __iomem *regs;
|
||||
int irq;
|
||||
int rv;
|
||||
|
||||
|
|
@ -107,39 +107,30 @@ static int ehci_hcd_ppc_of_probe(struct platform_device *op)
|
|||
|
||||
dev_dbg(&op->dev, "initializing PPC-OF USB Controller\n");
|
||||
|
||||
rv = of_address_to_resource(dn, 0, &res);
|
||||
if (rv)
|
||||
return rv;
|
||||
regs = devm_platform_get_and_ioremap_resource(op, 0, &res);
|
||||
if (IS_ERR(regs))
|
||||
return PTR_ERR(regs);
|
||||
|
||||
irq = platform_get_irq(op, 0);
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
|
||||
hcd = usb_create_hcd(&ehci_ppc_of_hc_driver, &op->dev, "PPC-OF USB");
|
||||
if (!hcd)
|
||||
return -ENOMEM;
|
||||
|
||||
hcd->rsrc_start = res.start;
|
||||
hcd->rsrc_len = resource_size(&res);
|
||||
|
||||
irq = irq_of_parse_and_map(dn, 0);
|
||||
if (!irq) {
|
||||
dev_err(&op->dev, "%s: irq_of_parse_and_map failed\n",
|
||||
__FILE__);
|
||||
rv = -EBUSY;
|
||||
goto err_irq;
|
||||
}
|
||||
|
||||
hcd->regs = devm_ioremap_resource(&op->dev, &res);
|
||||
if (IS_ERR(hcd->regs)) {
|
||||
rv = PTR_ERR(hcd->regs);
|
||||
goto err_ioremap;
|
||||
}
|
||||
hcd->regs = regs;
|
||||
hcd->rsrc_start = res->start;
|
||||
hcd->rsrc_len = resource_size(res);
|
||||
|
||||
ehci = hcd_to_ehci(hcd);
|
||||
np = of_find_compatible_node(NULL, NULL, "ibm,usb-ohci-440epx");
|
||||
if (np != NULL) {
|
||||
/* claim we really affected by usb23 erratum */
|
||||
if (!of_address_to_resource(np, 0, &res))
|
||||
if (!of_address_to_resource(np, 0, &ohci_res))
|
||||
ehci->ohci_hcctrl_reg =
|
||||
devm_ioremap(&op->dev,
|
||||
res.start + OHCI_HCCTRL_OFFSET,
|
||||
ohci_res.start + OHCI_HCCTRL_OFFSET,
|
||||
OHCI_HCCTRL_LEN);
|
||||
else
|
||||
pr_debug("%s: no ohci offset in fdt\n", __FILE__);
|
||||
|
|
@ -170,14 +161,12 @@ static int ehci_hcd_ppc_of_probe(struct platform_device *op)
|
|||
|
||||
rv = usb_add_hcd(hcd, irq, 0);
|
||||
if (rv)
|
||||
goto err_ioremap;
|
||||
goto err;
|
||||
|
||||
device_wakeup_enable(hcd->self.controller);
|
||||
return 0;
|
||||
|
||||
err_ioremap:
|
||||
irq_dispose_mapping(irq);
|
||||
err_irq:
|
||||
err:
|
||||
usb_put_hcd(hcd);
|
||||
|
||||
return rv;
|
||||
|
|
@ -196,8 +185,6 @@ static void ehci_hcd_ppc_of_remove(struct platform_device *op)
|
|||
|
||||
usb_remove_hcd(hcd);
|
||||
|
||||
irq_dispose_mapping(hcd->irq);
|
||||
|
||||
/* use request_mem_region to test if the ohci driver is loaded. if so
|
||||
* ensure the ohci core is operational.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -199,7 +199,8 @@ static void ehci_handle_controller_death(struct ehci_hcd *ehci)
|
|||
|
||||
/* Clean up the mess */
|
||||
ehci->rh_state = EHCI_RH_HALTED;
|
||||
ehci_writel(ehci, 0, &ehci->regs->configured_flag);
|
||||
if (!ehci->no_configured_flag)
|
||||
ehci_writel(ehci, 0, &ehci->regs->configured_flag);
|
||||
ehci_writel(ehci, 0, &ehci->regs->intr_enable);
|
||||
ehci_work(ehci);
|
||||
end_unlink_async(ehci);
|
||||
|
|
|
|||
|
|
@ -123,6 +123,19 @@ struct ehci_hcd { /* one per controller */
|
|||
struct ehci_caps __iomem *caps;
|
||||
struct ehci_regs __iomem *regs;
|
||||
struct ehci_dbg_port __iomem *debug;
|
||||
#ifdef CONFIG_USB_EHCI_DEVIANT_PORT_STATUS_REG
|
||||
u32 __iomem *port_status;
|
||||
#endif
|
||||
#ifdef CONFIG_USB_EHCI_PORT_SPEED_HOOK
|
||||
unsigned int (*get_port_speed)(struct ehci_hcd *ehci,
|
||||
unsigned int port);
|
||||
#endif
|
||||
#ifdef CONFIG_USB_EHCI_PORT_RESET_HOOKS
|
||||
int (*pre_port_reset)(struct ehci_hcd *ehci,
|
||||
unsigned int port);
|
||||
int (*post_port_reset)(struct ehci_hcd *ehci,
|
||||
unsigned int port);
|
||||
#endif
|
||||
|
||||
__u32 hcs_params; /* cached register copy */
|
||||
spinlock_t lock;
|
||||
|
|
@ -222,6 +235,9 @@ struct ehci_hcd { /* one per controller */
|
|||
unsigned spurious_oc:1;
|
||||
unsigned is_aspeed:1;
|
||||
unsigned zx_wakeup_clear_needed:1;
|
||||
unsigned no_configured_flag:1;
|
||||
unsigned no_tdi_mode:1;
|
||||
unsigned no_fsls_isoc:1;
|
||||
|
||||
/* required for usb32 quirk */
|
||||
#define OHCI_CTRL_HCFS (3 << 6)
|
||||
|
|
@ -278,6 +294,19 @@ static inline struct usb_hcd *ehci_to_hcd(struct ehci_hcd *ehci)
|
|||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static inline u32 __iomem *ehci_portsc(struct ehci_hcd *ehci,
|
||||
unsigned int port)
|
||||
{
|
||||
#ifdef CONFIG_USB_EHCI_DEVIANT_PORT_STATUS_REG
|
||||
if (ehci->port_status)
|
||||
return ehci->port_status + port;
|
||||
#endif
|
||||
|
||||
return &ehci->regs->port_status[port];
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#define QTD_NEXT(ehci, dma) cpu_to_hc32(ehci, (u32)dma)
|
||||
|
||||
/*
|
||||
|
|
@ -651,38 +680,12 @@ struct ehci_tt {
|
|||
|
||||
#ifdef CONFIG_USB_EHCI_ROOT_HUB_TT
|
||||
|
||||
/*
|
||||
* Some EHCI controllers have a Transaction Translator built into the
|
||||
* root hub. This is a non-standard feature. Each controller will need
|
||||
* to add code to the following inline functions, and call them as
|
||||
* needed (mostly in root hub code).
|
||||
*/
|
||||
|
||||
#define ehci_is_TDI(e) (ehci_to_hcd(e)->has_tt)
|
||||
|
||||
/* Returns the speed of a device attached to a port on the root hub. */
|
||||
static inline unsigned int
|
||||
ehci_port_speed(struct ehci_hcd *ehci, unsigned int portsc)
|
||||
{
|
||||
if (ehci_is_TDI(ehci)) {
|
||||
switch ((portsc >> (ehci->has_hostpc ? 25 : 26)) & 3) {
|
||||
case 0:
|
||||
return 0;
|
||||
case 1:
|
||||
return USB_PORT_STAT_LOW_SPEED;
|
||||
case 2:
|
||||
default:
|
||||
return USB_PORT_STAT_HIGH_SPEED;
|
||||
}
|
||||
}
|
||||
return USB_PORT_STAT_HIGH_SPEED;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define ehci_is_TDI(e) (0)
|
||||
|
||||
#define ehci_port_speed(ehci, portsc) USB_PORT_STAT_HIGH_SPEED
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
|
|
|||
|
|
@ -708,6 +708,7 @@ static int cvmx_usb_initialize(struct device *dev,
|
|||
int channel;
|
||||
int divisor;
|
||||
int retries = 0;
|
||||
int status;
|
||||
union cvmx_usbcx_hcfg usbcx_hcfg;
|
||||
union cvmx_usbnx_clk_ctl usbn_clk_ctl;
|
||||
union cvmx_usbcx_gintsts usbc_gintsts;
|
||||
|
|
@ -872,7 +873,8 @@ static int cvmx_usb_initialize(struct device *dev,
|
|||
if (usbcx_gahbcfg.u32 == 0xffffffff || usbcx_gahbcfg.u32 == 0) {
|
||||
dev_err(dev, "USB core is not responding (GHWCFG3=0x%08x)\n",
|
||||
usbcx_gahbcfg.u32);
|
||||
return -ENODEV;
|
||||
status = -ENODEV;
|
||||
goto err_shutdown;
|
||||
}
|
||||
|
||||
usbcx_gahbcfg.u32 = 0;
|
||||
|
|
@ -961,14 +963,27 @@ static int cvmx_usb_initialize(struct device *dev,
|
|||
dev_dbg(dev, "gintsts after reset: 0x%x\n", (int)usbc_gintsts.u32);
|
||||
if (!usbc_gintsts.s.disconnint && !usbc_gintsts.s.prtint)
|
||||
return 0;
|
||||
if (retries++ >= 5)
|
||||
return -EAGAIN;
|
||||
if (retries++ >= 5) {
|
||||
status = -EAGAIN;
|
||||
goto err_shutdown;
|
||||
}
|
||||
dev_info(dev, "controller reset failed (gintsts=0x%x) - retrying\n",
|
||||
(int)usbc_gintsts.u32);
|
||||
msleep(50);
|
||||
cvmx_usb_shutdown(usb);
|
||||
msleep(50);
|
||||
goto retry;
|
||||
|
||||
err_shutdown:
|
||||
/*
|
||||
* Steps 2b-2e above selected the reference clock, enabled
|
||||
* USBN_CLK_CTL and took the PHY out of reset, and the caller only
|
||||
* frees the HCD, so hand the block back before giving up. The pipe
|
||||
* lists cvmx_usb_shutdown() checks are initialised by the caller
|
||||
* before it gets here, so it cannot refuse with -EBUSY.
|
||||
*/
|
||||
cvmx_usb_shutdown(usb);
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1923,6 +1938,7 @@ static void cvmx_usb_schedule(struct octeon_hcd *usb, int is_sof)
|
|||
struct cvmx_usb_pipe *pipe;
|
||||
int need_sof;
|
||||
u64 min_due;
|
||||
bool due_now;
|
||||
enum cvmx_usb_transfer ttype;
|
||||
|
||||
if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) {
|
||||
|
|
@ -1964,15 +1980,32 @@ static void cvmx_usb_schedule(struct octeon_hcd *usb, int is_sof)
|
|||
*/
|
||||
need_sof = 0;
|
||||
min_due = ~0ull;
|
||||
due_now = false;
|
||||
for (ttype = CVMX_USB_TRANSFER_CONTROL;
|
||||
ttype <= CVMX_USB_TRANSFER_INTERRUPT; ttype++) {
|
||||
list_for_each_entry(pipe, &usb->active_pipes[ttype], node) {
|
||||
/*
|
||||
* Do not sleep past a pipe that is only retried from a
|
||||
* SOF: a periodic one that is due but was not started
|
||||
* (no idle channel, closed split window, another pipe's
|
||||
* split active), or any pipe waiting for the frame its
|
||||
* split complete may go out in. Other control and bulk
|
||||
* pipes are left out: their next_tx_frame is not updated
|
||||
* on completion, so they always look due.
|
||||
*/
|
||||
if (pipe->split_sc_frame != -1 ||
|
||||
(pipe->next_tx_frame <= usb->frame_number &&
|
||||
(ttype == CVMX_USB_TRANSFER_ISOCHRONOUS ||
|
||||
ttype == CVMX_USB_TRANSFER_INTERRUPT)))
|
||||
due_now = true;
|
||||
if (pipe->next_tx_frame > usb->frame_number &&
|
||||
pipe->next_tx_frame < min_due)
|
||||
min_due = pipe->next_tx_frame;
|
||||
}
|
||||
}
|
||||
if (min_due != ~0ull) {
|
||||
if (due_now) {
|
||||
need_sof = 1;
|
||||
} else if (min_due != ~0ull) {
|
||||
u64 delta = min_due - usb->frame_number;
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ ohci_dump_status (struct ohci_hcd *controller, char **next, unsigned *size)
|
|||
\
|
||||
(temp & RH_PS_PES) ? " PES" : "", \
|
||||
(temp & RH_PS_CCS) ? " CCS" : "" \
|
||||
);
|
||||
)
|
||||
|
||||
|
||||
static void
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user