USB/Thunderbolt changes for 6.9-rc1

Here is the big set of USB and Thunderbolt changes for 6.9-rc1.  Lots of
 tiny changes and forward progress to support new hardware and better
 support for existing devices.  Included in here are:
   - Thunderbolt (i.e. USB4) updates for newer hardware and uses as more
     people start to use the hardware
   - default USB authentication mode Kconfig and documentation update to
     make it more obvious what is going on
   - USB typec updates and enhancements
   - usual dwc3 driver updates
   - usual xhci driver updates
   - function USB (i.e. gadget) driver updates and additions
   - new device ids for lots of drivers
   - loads of other small updates, full details in the shortlog
 
 All of these, including a "last minute regression fix" have been in
 linux-next with no reported issues.
 
 Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
 -----BEGIN PGP SIGNATURE-----
 
 iG0EABECAC0WIQT0tgzFv3jCIUoxPcsxR9QN2y37KQUCZfwpzA8cZ3JlZ0Brcm9h
 aC5jb20ACgkQMUfUDdst+ymS9QCdEuF6KJFLOrDrGS4NbZNSUPIVF6oAn350r4NX
 CMZah37Dfr1VDCOOV4gQ
 =HACL
 -----END PGP SIGNATURE-----

Merge tag 'usb-6.9-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb

Pull USB / Thunderbolt updates from Greg KH:
 "Here is the big set of USB and Thunderbolt changes for 6.9-rc1. Lots
  of tiny changes and forward progress to support new hardware and
  better support for existing devices. Included in here are:

   - Thunderbolt (i.e. USB4) updates for newer hardware and uses as more
     people start to use the hardware

   - default USB authentication mode Kconfig and documentation update to
     make it more obvious what is going on

   - USB typec updates and enhancements

   - usual dwc3 driver updates

   - usual xhci driver updates

   - function USB (i.e. gadget) driver updates and additions

   - new device ids for lots of drivers

   - loads of other small updates, full details in the shortlog

  All of these, including a "last minute regression fix" have been in
  linux-next with no reported issues"

* tag 'usb-6.9-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (185 commits)
  usb: usb-acpi: Fix oops due to freeing uninitialized pld pointer
  usb: gadget: net2272: Use irqflags in the call to net2272_probe_fin
  usb: gadget: tegra-xudc: Fix USB3 PHY retrieval logic
  phy: tegra: xusb: Add API to retrieve the port number of phy
  USB: gadget: pxa27x_udc: Remove unused of_gpio.h
  usb: gadget/snps_udc_plat: Remove unused of_gpio.h
  usb: ohci-pxa27x: Remove unused of_gpio.h
  usb: sl811-hcd: only defined function checkdone if QUIRK2 is defined
  usb: Clarify expected behavior of dev_bin_attrs_are_visible()
  xhci: Allow RPM on the USB controller (1022:43f7) by default
  usb: isp1760: remove SLAB_MEM_SPREAD flag usage
  usb: misc: onboard_hub: use pointer consistently in the probe function
  usb: gadget: fsl: Increase size of name buffer for endpoints
  usb: gadget: fsl: Add of device table to enable module autoloading
  usb: typec: tcpm: add support to set tcpc connector orientatition
  usb: typec: tcpci: add generic tcpci fallback compatible
  dt-bindings: usb: typec-tcpci: add tcpci fallback binding
  usb: gadget: fsl-udc: Replace custom log wrappers by dev_{err,warn,dbg,vdbg}
  usb: core: Set connect_type of ports based on DT node
  dt-bindings: usb: Add downstream facing ports to realtek binding
  ...
This commit is contained in:
Linus Torvalds 2024-03-21 12:35:20 -07:00
commit e09bf86f3d
178 changed files with 9091 additions and 2447 deletions

View File

@ -4,6 +4,14 @@ KernelVersion: 3.13
Description: The purpose of this directory is to create and remove it.
A corresponding USB function instance is created/removed.
There are no attributes here.
All parameters are set through FunctionFS.
All attributes are read only:
============= ============================================
ready 1 if the function is ready to be used, E.G.
if userspace has written descriptors and
strings to ep0, so the gadget can be
enabled - 0 otherwise.
============= ============================================
All other parameters are set through FunctionFS.

View File

@ -442,6 +442,16 @@ What: /sys/bus/usb/devices/usbX/descriptors
Description:
Contains the interface descriptors, in binary.
What: /sys/bus/usb/devices/usbX/bos_descriptors
Date: March 2024
Contact: Elbert Mai <code@elbertmai.com>
Description:
Binary file containing the cached binary device object store (BOS)
of the device. This consists of the BOS descriptor followed by the
set of device capability descriptors. All descriptors read from
this file are in bus-endian format. Note that the kernel will not
request the BOS from a device if its bcdUSB is less than 0x0201.
What: /sys/bus/usb/devices/usbX/idProduct
Description:
Product ID, in hexadecimal.

View File

@ -19,3 +19,9 @@ Description:
- none
- host
- device
What: /sys/class/usb_role/<switch>/connector
Date: Feb 2024
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
Optional symlink to the USB Type-C connector.

View File

@ -26,6 +26,7 @@ properties:
- enum:
- qcom,pm4125-vbus-reg
- qcom,pm6150-vbus-reg
- qcom,pmi632-vbus-reg
- const: qcom,pm8150b-vbus-reg
reg:

View File

@ -23,6 +23,7 @@ properties:
oneOf:
- items:
- enum:
- qcom,qcm6490-pmic-glink
- qcom,sc8180x-pmic-glink
- qcom,sc8280xp-pmic-glink
- qcom,sm8350-pmic-glink

View File

@ -0,0 +1,55 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/sound/qcom,q6usb.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Qualcomm ASoC DPCM USB backend DAI
maintainers:
- Wesley Cheng <quic_wcheng@quicinc.com>
description:
The USB port is a supported AFE path on the Q6 DSP. This ASoC DPCM
backend DAI will communicate the required settings to initialize the
XHCI host controller properly for enabling the offloaded audio stream.
Parameters defined under this node will carry settings, which will be
passed along during the QMI stream enable request and configuration of
the XHCI host controller.
allOf:
- $ref: dai-common.yaml#
properties:
compatible:
enum:
- qcom,q6usb
iommus:
maxItems: 1
"#sound-dai-cells":
const: 1
qcom,usb-audio-intr-idx:
description:
Desired XHCI interrupter number to use. Depending on the audio DSP
on the platform, it will operate on a specific XHCI interrupter.
$ref: /schemas/types.yaml#/definitions/uint16
maximum: 8
required:
- compatible
- "#sound-dai-cells"
- qcom,usb-audio-intr-idx
additionalProperties: false
examples:
- |
dais {
compatible = "qcom,q6usb";
#sound-dai-cells = <1>;
iommus = <&apps_smmu 0x180f 0x0>;
qcom,usb-audio-intr-idx = /bits/ 16 <2>;
};

View File

@ -23,24 +23,11 @@ properties:
connector:
type: object
$ref: ../connector/usb-connector.yaml
unevaluatedProperties: false
description:
Properties for usb c connector.
properties:
compatible:
const: usb-c-connector
power-role: true
data-role: true
try-power-role: true
required:
- compatible
required:
- compatible
- reg

View File

@ -313,7 +313,7 @@ properties:
usb-phy:
description: phandle for the PHY device. Use "phys" instead.
$ref: /schemas/types.yaml#/definitions/phandle
maxItems: 1
deprecated: true
fsl,usbphy:

View File

@ -27,13 +27,8 @@ properties:
vcc-supply:
description: power supply (2.7V-5.5V)
mode-switch:
description: Flag the port as possible handle of altmode switching
type: boolean
orientation-switch:
description: Flag the port as possible handler of orientation switching
type: boolean
mode-switch: true
orientation-switch: true
port:
$ref: /schemas/graph.yaml#/$defs/port-base
@ -79,6 +74,9 @@ required:
- reg
- port
allOf:
- $ref: usb-switch.yaml#
additionalProperties: false
examples:

View File

@ -77,6 +77,7 @@ properties:
- const: usb-ehci
- enum:
- generic-ehci
- marvell,ac5-ehci
- marvell,armada-3700-ehci
- marvell,orion-ehci
- nuvoton,npcm750-ehci

View File

@ -33,13 +33,8 @@ properties:
vcc-supply:
description: power supply
mode-switch:
description: Flag the port as possible handle of altmode switching
type: boolean
orientation-switch:
description: Flag the port as possible handler of orientation switching
type: boolean
mode-switch: true
orientation-switch: true
port:
$ref: /schemas/graph.yaml#/properties/port
@ -54,6 +49,9 @@ required:
- orientation-switch
- port
allOf:
- $ref: usb-switch.yaml#
additionalProperties: false
examples:

View File

@ -0,0 +1,99 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/usb/hisilicon,hi3798mv200-dwc3.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: HiSilicon Hi3798MV200 DWC3 USB SoC controller
maintainers:
- Yang Xiwen <forbidden405@foxmail.com>
properties:
compatible:
const: hisilicon,hi3798mv200-dwc3
'#address-cells':
const: 1
'#size-cells':
const: 1
ranges: true
clocks:
items:
- description: Controller bus clock
- description: Controller suspend clock
- description: Controller reference clock
- description: Controller gm clock
- description: Controller gs clock
- description: Controller utmi clock
- description: Controller pipe clock
clock-names:
items:
- const: bus
- const: suspend
- const: ref
- const: gm
- const: gs
- const: utmi
- const: pipe
resets:
maxItems: 1
reset-names:
const: soft
patternProperties:
'^usb@[0-9a-f]+$':
$ref: snps,dwc3.yaml#
required:
- compatible
- ranges
- '#address-cells'
- '#size-cells'
- clocks
- clock-names
- resets
- reset-names
additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/arm-gic.h>
usb {
compatible = "hisilicon,hi3798mv200-dwc3";
ranges;
#address-cells = <1>;
#size-cells = <1>;
clocks = <&clk_bus>,
<&clk_suspend>,
<&clk_ref>,
<&clk_gm>,
<&clk_gs>,
<&clk_utmi>,
<&clk_pipe>;
clock-names = "bus", "suspend", "ref", "gm", "gs", "utmi", "pipe";
resets = <&crg 0xb0 12>;
reset-names = "soft";
usb@98a0000 {
compatible = "snps,dwc3";
reg = <0x98a0000 0x10000>;
interrupts = <GIC_SPI 69 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk_bus>,
<&clk_suspend>,
<&clk_ref>;
clock-names = "bus_early", "suspend", "ref";
phys = <&usb2_phy1_port2>, <&combphy0 0>;
phy-names = "usb2-phy", "usb3-phy";
maximum-speed = "super-speed";
dr_mode = "host";
};
};

View File

@ -0,0 +1,72 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/usb/ite,it5205.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: ITE IT5202 Type-C USB Alternate Mode Passive MUX
maintainers:
- AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
- Tianping Fang <tianping.fang@mediatek.com>
properties:
compatible:
const: ite,it5205
reg:
maxItems: 1
vcc-supply:
description: Power supply for VCC pin (3.3V)
mode-switch:
description: Flag the port as possible handle of altmode switching
type: boolean
orientation-switch:
description: Flag the port as possible handler of orientation switching
type: boolean
ite,ovp-enable:
description: Enable Over Voltage Protection functionality
type: boolean
port:
$ref: /schemas/graph.yaml#/properties/port
description:
A port node to link the IT5205 to a TypeC controller for the purpose of
handling altmode muxing and orientation switching.
required:
- compatible
- reg
- orientation-switch
- port
additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/irq.h>
i2c2 {
#address-cells = <1>;
#size-cells = <0>;
typec-mux@48 {
compatible = "ite,it5205";
reg = <0x48>;
mode-switch;
orientation-switch;
vcc-supply = <&mt6359_vibr_ldo_reg>;
port {
it5205_usbss_sbu: endpoint {
remote-endpoint = <&typec_controller>;
};
};
};
};
...

View File

@ -185,7 +185,10 @@ properties:
2 - used by mt2712 etc, revision 2 with following IPM rule;
101 - used by mt8183, specific 1.01;
102 - used by mt8192, specific 1.02;
enum: [1, 2, 101, 102]
103 - used by mt8195, IP0, specific 1.03;
105 - used by mt8195, IP2, specific 1.05;
106 - used by mt8195, IP3, specific 1.06;
enum: [1, 2, 101, 102, 103, 105, 106]
mediatek,u3p-dis-msk:
$ref: /schemas/types.yaml#/definitions/uint32

View File

@ -72,8 +72,6 @@ allOf:
i2c-bus: false
else:
$ref: /schemas/usb/usb-device.yaml
required:
- peer-hub
additionalProperties: false

View File

@ -20,13 +20,8 @@ properties:
vdd18-supply:
description: Power supply for VDD18 pin
retimer-switch:
description: Flag the port as possible handle of SuperSpeed signals retiming
type: boolean
orientation-switch:
description: Flag the port as possible handler of orientation switching
type: boolean
orientation-switch: true
retimer-switch: true
ports:
$ref: /schemas/graph.yaml#/properties/ports
@ -49,6 +44,9 @@ required:
- compatible
- reg
allOf:
- $ref: usb-switch.yaml#
additionalProperties: false
examples:

View File

@ -11,7 +11,9 @@ maintainers:
properties:
compatible:
const: nxp,ptn5110
items:
- const: nxp,ptn5110
- const: tcpci
reg:
maxItems: 1
@ -41,7 +43,7 @@ examples:
#size-cells = <0>;
tcpci@50 {
compatible = "nxp,ptn5110";
compatible = "nxp,ptn5110", "tcpci";
reg = <0x50>;
interrupt-parent = <&gpio3>;
interrupts = <3 IRQ_TYPE_LEVEL_LOW>;

View File

@ -21,14 +21,8 @@ properties:
description: power supply (1.8V)
enable-gpios: true
retimer-switch:
description: Flag the port as possible handle of SuperSpeed signals retiming
type: boolean
orientation-switch:
description: Flag the port as possible handler of orientation switching
type: boolean
orientation-switch: true
retimer-switch: true
ports:
$ref: /schemas/graph.yaml#/properties/ports
@ -95,6 +89,9 @@ required:
- compatible
- reg
allOf:
- $ref: usb-switch.yaml#
additionalProperties: false
examples:

View File

@ -102,7 +102,7 @@ properties:
description: |
Different types of interrupts are used based on HS PHY used on target:
- pwr_event: Used for wakeup based on other power events.
- hs_phY_irq: Apart from DP/DM/QUSB2 PHY interrupts, there is
- hs_phy_irq: Apart from DP/DM/QUSB2 PHY interrupts, there is
hs_phy_irq which is not triggered by default and its
functionality is mutually exclusive to that of
{dp/dm}_hs_phy_irq and qusb2_phy_irq.

View File

@ -14,8 +14,19 @@ description:
properties:
compatible:
enum:
- qcom,pm8150b-typec
oneOf:
- enum:
- qcom,pmi632-typec
- qcom,pm8150b-typec
- items:
- enum:
- qcom,pm6150-typec
- const: qcom,pm8150b-typec
- items:
- enum:
- qcom,pm4125-typec
- const: qcom,pmi632-typec
connector:
type: object
@ -24,9 +35,11 @@ properties:
reg:
description: Type-C port and pdphy SPMI register base offsets
minItems: 1
maxItems: 2
interrupts:
minItems: 8
items:
- description: Type-C CC attach notification, VBUS error, tCCDebounce done
- description: Type-C VCONN powered
@ -46,6 +59,7 @@ properties:
- description: Power Domain Fast Role Swap event
interrupt-names:
minItems: 8
items:
- const: or-rid-detect-change
- const: vpd-detect
@ -81,7 +95,33 @@ required:
- interrupts
- interrupt-names
- vdd-vbus-supply
- vdd-pdphy-supply
allOf:
- if:
properties:
compatible:
contains:
enum:
- qcom,pmi632-typec
then:
properties:
reg:
maxItems: 1
interrupts:
maxItems: 8
interrupt-names:
maxItems: 8
vdd-pdphy-supply: false
else:
properties:
reg:
maxItems: 2
interrupts:
minItems: 16
interrupt-names:
maxItems: 16
required:
- vdd-pdphy-supply
additionalProperties: false

View File

@ -35,13 +35,8 @@ properties:
vdd-supply:
description: USBSS VDD power supply
mode-switch:
description: Flag the port as possible handle of altmode switching
type: boolean
orientation-switch:
description: Flag the port as possible handler of orientation switching
type: boolean
mode-switch: true
orientation-switch: true
ports:
$ref: /schemas/graph.yaml#/properties/ports
@ -63,6 +58,9 @@ required:
- reg
- ports
allOf:
- $ref: usb-switch.yaml#
additionalProperties: false
examples:

View File

@ -21,6 +21,12 @@ properties:
reg: true
'#address-cells':
const: 1
'#size-cells':
const: 0
vdd-supply:
description:
phandle to the regulator that provides power to the hub.
@ -30,6 +36,36 @@ properties:
description:
phandle to the peer hub on the controller.
ports:
$ref: /schemas/graph.yaml#/properties/ports
properties:
port@1:
$ref: /schemas/graph.yaml#/properties/port
description:
1st downstream facing USB port
port@2:
$ref: /schemas/graph.yaml#/properties/port
description:
2nd downstream facing USB port
port@3:
$ref: /schemas/graph.yaml#/properties/port
description:
3rd downstream facing USB port
port@4:
$ref: /schemas/graph.yaml#/properties/port
description:
4th downstream facing USB port
patternProperties:
'^.*@[1-4]$':
description: The hard wired USB devices
type: object
$ref: /schemas/usb/usb-device.yaml
required:
- peer-hub
- compatible
@ -50,6 +86,13 @@ examples:
reg = <1>;
vdd-supply = <&pp3300_hub>;
peer-hub = <&hub_3_0>;
#address-cells = <1>;
#size-cells = <0>;
/* USB 2.0 device on port 2 */
device@2 {
compatible = "usb123,4567";
reg = <2>;
};
};
/* 3.0 hub on port 2 */
@ -58,5 +101,17 @@ examples:
reg = <2>;
vdd-supply = <&pp3300_hub>;
peer-hub = <&hub_2_0>;
ports {
#address-cells = <1>;
#size-cells = <0>;
/* Type-A connector on port 4 */
port@4 {
reg = <4>;
endpoint {
remote-endpoint = <&usb_a0_ss>;
};
};
};
};
};

View File

@ -14,7 +14,10 @@ properties:
const: ti,am62-usb
reg:
maxItems: 1
minItems: 1
items:
- description: USB CFG register space
- description: USB PHY2 register space
ranges: true
@ -82,7 +85,8 @@ examples:
usbss1: usb@f910000 {
compatible = "ti,am62-usb";
reg = <0x00 0x0f910000 0x00 0x800>;
reg = <0x00 0x0f910000 0x00 0x800>,
<0x00 0x0f918000 0x00 0x400>;
clocks = <&k3_clks 162 3>;
clock-names = "ref";
ti,syscon-phy-pll-refclk = <&wkup_conf 0x4018>;

View File

@ -0,0 +1,69 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/usb/ti,usb8020b.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: TI USB8020B USB 3.0 hub controller
maintainers:
- Macpaul Lin <macpaul.lin@mediatek.com>
allOf:
- $ref: usb-device.yaml#
properties:
compatible:
enum:
- usb451,8025
- usb451,8027
reg: true
reset-gpios:
items:
- description: GPIO specifier for GRST# pin.
vdd-supply:
description:
VDD power supply to the hub
peer-hub:
$ref: /schemas/types.yaml#/definitions/phandle
description:
phandle to the peer hub on the controller.
required:
- compatible
- reg
- peer-hub
additionalProperties: false
examples:
- |
#include <dt-bindings/gpio/gpio.h>
usb {
dr_mode = "host";
#address-cells = <1>;
#size-cells = <0>;
/* 2.0 hub on port 1 */
hub_2_0: hub@1 {
compatible = "usb451,8027";
reg = <1>;
peer-hub = <&hub_3_0>;
reset-gpios = <&pio 7 GPIO_ACTIVE_HIGH>;
vdd-supply = <&usb_hub_fixed_3v3>;
};
/* 3.0 hub on port 2 */
hub_3_0: hub@2 {
compatible = "usb451,8025";
reg = <2>;
peer-hub = <&hub_2_0>;
reset-gpios = <&pio 7 GPIO_ACTIVE_HIGH>;
vdd-supply = <&usb_hub_fixed_3v3>;
};
};

View File

@ -37,10 +37,11 @@ properties:
description: Should specify the GPIO detecting a VBus insertion
maxItems: 1
vbus-regulator:
description: Should specify the regulator supplying current drawn from
the VBus line.
$ref: /schemas/types.yaml#/definitions/phandle
vbus-supply:
description: regulator supplying VBUS. It will be enabled and disabled
dynamically in OTG mode. If the regulator is controlled by a
GPIO line, this should be modeled as a regulator-fixed and
referenced by this supply.
wakeup-source:
description:
@ -65,7 +66,7 @@ examples:
vcc-supply = <&hsusb1_vcc_regulator>;
reset-gpios = <&gpio1 7 GPIO_ACTIVE_LOW>;
vbus-detect-gpio = <&gpio2 13 GPIO_ACTIVE_HIGH>;
vbus-regulator = <&vbus_regulator>;
vbus-supply = <&vbus_regulator>;
#phy-cells = <0>;
};

View File

@ -0,0 +1,67 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/usb/usb-switch.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: USB Orientation and Mode Switches Common Properties
maintainers:
- Greg Kroah-Hartman <gregkh@linuxfoundation.org>
description:
Common properties for devices handling USB mode and orientation switching.
properties:
mode-switch:
description: Possible handler of altmode switching
type: boolean
orientation-switch:
description: Possible handler of orientation switching
type: boolean
retimer-switch:
description: Possible handler of SuperSpeed signals retiming
type: boolean
port:
$ref: /schemas/graph.yaml#/properties/port
description:
A port node to link the device to a TypeC controller for the purpose of
handling altmode muxing and orientation switching.
ports:
$ref: /schemas/graph.yaml#/properties/ports
properties:
port@0:
$ref: /schemas/graph.yaml#/properties/port
description:
Super Speed (SS) Output endpoint to the Type-C connector
port@1:
$ref: /schemas/graph.yaml#/$defs/port-base
description:
Super Speed (SS) Input endpoint from the Super-Speed PHY
unevaluatedProperties: false
properties:
endpoint:
$ref: /schemas/graph.yaml#/$defs/endpoint-base
unevaluatedProperties: false
properties:
data-lanes:
$ref: /schemas/types.yaml#/definitions/uint32-array
minItems: 1
maxItems: 8
uniqueItems: true
items:
maximum: 8
oneOf:
- required:
- port
- required:
- ports
additionalProperties: true

View File

@ -25,6 +25,8 @@ properties:
usb-phy:
$ref: /schemas/types.yaml#/definitions/phandle-array
items:
maxItems: 1
description:
List of all the USB PHYs on this HCD to be accepted by the legacy USB
Physical Layer subsystem.

View File

@ -99,8 +99,10 @@ The disconnect() callback
This callback is a signal to break any connection with an interface.
You are not allowed any IO to a device after returning from this
callback. You also may not do any other operation that may interfere
with another driver bound the interface, eg. a power management
operation.
with another driver bound to the interface, eg. a power management
operation. Outstanding operations on the device must be completed or
aborted before this callback may return.
If you are called due to a physical disconnection, all your URBs will be
killed by usbcore. Note that in this case disconnect will be called some
time after the physical disconnection. Thus your driver must be prepared

View File

@ -2,6 +2,9 @@
How FunctionFS works
====================
Overview
========
From kernel point of view it is just a composite function with some
unique behaviour. It may be added to an USB configuration only after
the user space driver has registered by writing descriptors and
@ -66,3 +69,36 @@ have been written to their ep0's.
Conversely, the gadget is unregistered after the first USB function
closes its endpoints.
DMABUF interface
================
FunctionFS additionally supports a DMABUF based interface, where the
userspace can attach DMABUF objects (externally created) to an endpoint,
and subsequently use them for data transfers.
A userspace application can then use this interface to share DMABUF
objects between several interfaces, allowing it to transfer data in a
zero-copy fashion, for instance between IIO and the USB stack.
As part of this interface, three new IOCTLs have been added. These three
IOCTLs have to be performed on a data endpoint (ie. not ep0). They are:
``FUNCTIONFS_DMABUF_ATTACH(int)``
Attach the DMABUF object, identified by its file descriptor, to the
data endpoint. Returns zero on success, and a negative errno value
on error.
``FUNCTIONFS_DMABUF_DETACH(int)``
Detach the given DMABUF object, identified by its file descriptor,
from the data endpoint. Returns zero on success, and a negative
errno value on error. Note that closing the endpoint's file
descriptor will automatically detach all attached DMABUFs.
``FUNCTIONFS_DMABUF_TRANSFER(struct usb_ffs_dmabuf_transfer_req *)``
Enqueue the previously attached DMABUF to the transfer queue.
The argument is a structure that packs the DMABUF's file descriptor,
the size in bytes to transfer (which should generally correspond to
the size of the DMABUF), and a 'flags' field which is unused
for now. Returns zero on success, and a negative errno value on
error.

View File

@ -206,6 +206,14 @@ the standard procedure for using FunctionFS (mount it, run the userspace
process which implements the function proper). The gadget should be enabled
by writing a suitable string to usb_gadget/<gadget>/UDC.
The FFS function provides just one attribute in its function directory:
ready
The attribute is read-only and signals if the function is ready (1) to be
used, E.G. if userspace has written descriptors and strings to ep0, so
the gadget can be enabled.
Testing the FFS function
------------------------

View File

@ -63,6 +63,52 @@
};
};
pm6150_vbus: usb-vbus-regulator@1100 {
compatible = "qcom,pm6150-vbus-reg,
qcom,pm8150b-vbus-reg";
reg = <0x1100>;
status = "disabled";
};
pm6150_typec: typec@1500 {
compatible = "qcom,pm6150-typec,
qcom,pm8150b-typec";
reg = <0x1500>, <0x1700>;
interrupts = <0x0 0x15 0x00 IRQ_TYPE_EDGE_RISING>,
<0x0 0x15 0x01 IRQ_TYPE_EDGE_BOTH>,
<0x0 0x15 0x02 IRQ_TYPE_EDGE_RISING>,
<0x0 0x15 0x03 IRQ_TYPE_EDGE_BOTH>,
<0x0 0x15 0x04 IRQ_TYPE_EDGE_RISING>,
<0x0 0x15 0x05 IRQ_TYPE_EDGE_RISING>,
<0x0 0x15 0x06 IRQ_TYPE_EDGE_BOTH>,
<0x0 0x15 0x07 IRQ_TYPE_EDGE_RISING>,
<0x0 0x17 0x00 IRQ_TYPE_EDGE_RISING>,
<0x0 0x17 0x01 IRQ_TYPE_EDGE_RISING>,
<0x0 0x17 0x02 IRQ_TYPE_EDGE_RISING>,
<0x0 0x17 0x03 IRQ_TYPE_EDGE_RISING>,
<0x0 0x17 0x04 IRQ_TYPE_EDGE_RISING>,
<0x0 0x17 0x05 IRQ_TYPE_EDGE_RISING>,
<0x0 0x17 0x06 IRQ_TYPE_EDGE_RISING>,
<0x0 0x17 0x07 IRQ_TYPE_EDGE_RISING>;
interrupt-names = "or-rid-detect-change",
"vpd-detect",
"cc-state-change",
"vconn-oc",
"vbus-change",
"attach-detach",
"legacy-cable-detect",
"try-snk-src-detect",
"sig-tx",
"sig-rx",
"msg-tx",
"msg-rx",
"msg-tx-failed",
"msg-tx-discarded",
"msg-rx-discarded",
"fr-swap";
status = "disabled";
};
pm6150_temp: temp-alarm@2400 {
compatible = "qcom,spmi-temp-alarm";
reg = <0x2400>;

View File

@ -126,7 +126,7 @@
interrupts = <93 2>;
};
EHCI0: ehci@30010000000 {
EHCI0: usb@30010000000 {
compatible = "ibm,476gtr-ehci", "generic-ehci";
reg = <0x300 0x10000000 0x0 0x10000>;
interrupt-parent = <&MPIC>;
@ -140,14 +140,14 @@
interrupt-parent = <&MPIC>;
};
OHCI0: ohci@30010010000 {
OHCI0: usb@30010010000 {
compatible = "ibm,476gtr-ohci", "generic-ohci";
reg = <0x300 0x10010000 0x0 0x10000>;
interrupt-parent = <&MPIC>;
interrupts = <89 1>;
};
OHCI1: ohci@30010020000 {
OHCI1: usb@30010020000 {
compatible = "ibm,476gtr-ohci", "generic-ohci";
reg = <0x300 0x10020000 0x0 0x10000>;
interrupt-parent = <&MPIC>;

View File

@ -87,6 +87,7 @@ source "drivers/phy/motorola/Kconfig"
source "drivers/phy/mscc/Kconfig"
source "drivers/phy/qualcomm/Kconfig"
source "drivers/phy/ralink/Kconfig"
source "drivers/phy/realtek/Kconfig"
source "drivers/phy/renesas/Kconfig"
source "drivers/phy/rockchip/Kconfig"
source "drivers/phy/samsung/Kconfig"

View File

@ -26,6 +26,7 @@ obj-y += allwinner/ \
mscc/ \
qualcomm/ \
ralink/ \
realtek/ \
renesas/ \
rockchip/ \
samsung/ \

View File

@ -489,6 +489,53 @@ int phy_calibrate(struct phy *phy)
}
EXPORT_SYMBOL_GPL(phy_calibrate);
/**
* phy_notify_connect() - phy connect notification
* @phy: the phy returned by phy_get()
* @port: the port index for connect
*
* If the phy needs to get connection status, the callback can be used.
* Returns: %0 if successful, a negative error code otherwise
*/
int phy_notify_connect(struct phy *phy, int port)
{
int ret;
if (!phy || !phy->ops->connect)
return 0;
mutex_lock(&phy->mutex);
ret = phy->ops->connect(phy, port);
mutex_unlock(&phy->mutex);
return ret;
}
EXPORT_SYMBOL_GPL(phy_notify_connect);
/**
* phy_notify_disconnect() - phy disconnect notification
* @phy: the phy returned by phy_get()
* @port: the port index for disconnect
*
* If the phy needs to get connection status, the callback can be used.
*
* Returns: %0 if successful, a negative error code otherwise
*/
int phy_notify_disconnect(struct phy *phy, int port)
{
int ret;
if (!phy || !phy->ops->disconnect)
return 0;
mutex_lock(&phy->mutex);
ret = phy->ops->disconnect(phy, port);
mutex_unlock(&phy->mutex);
return ret;
}
EXPORT_SYMBOL_GPL(phy_notify_disconnect);
/**
* phy_configure() - Changes the phy parameters
* @phy: the phy returned by phy_get()

View File

@ -0,0 +1,32 @@
# SPDX-License-Identifier: GPL-2.0
#
# Phy drivers for Realtek platforms
#
if ARCH_REALTEK || COMPILE_TEST
config PHY_RTK_RTD_USB2PHY
tristate "Realtek RTD USB2 PHY Transceiver Driver"
depends on USB_SUPPORT
select GENERIC_PHY
select USB_PHY
select USB_COMMON
help
Enable this to support Realtek SoC USB2 phy transceiver.
The DHC (digital home center) RTD series SoCs used the Synopsys
DWC3 USB IP. This driver will do the PHY initialization
of the parameters.
config PHY_RTK_RTD_USB3PHY
tristate "Realtek RTD USB3 PHY Transceiver Driver"
depends on USB_SUPPORT
select GENERIC_PHY
select USB_PHY
select USB_COMMON
help
Enable this to support Realtek SoC USB3 phy transceiver.
The DHC (digital home center) RTD series SoCs used the Synopsys
DWC3 USB IP. This driver will do the PHY initialization
of the parameters.
endif # ARCH_REALTEK || COMPILE_TEST

View File

@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_PHY_RTK_RTD_USB2PHY) += phy-rtk-usb2.o
obj-$(CONFIG_PHY_RTK_RTD_USB3PHY) += phy-rtk-usb3.o

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,748 @@
// SPDX-License-Identifier: GPL-2.0
/*
* phy-rtk-usb3.c RTK usb3.0 phy driver
*
* copyright (c) 2023 realtek semiconductor corporation
*
*/
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#include <linux/debugfs.h>
#include <linux/nvmem-consumer.h>
#include <linux/regmap.h>
#include <linux/sys_soc.h>
#include <linux/mfd/syscon.h>
#include <linux/phy/phy.h>
#include <linux/usb.h>
#define USB_MDIO_CTRL_PHY_BUSY BIT(7)
#define USB_MDIO_CTRL_PHY_WRITE BIT(0)
#define USB_MDIO_CTRL_PHY_ADDR_SHIFT 8
#define USB_MDIO_CTRL_PHY_DATA_SHIFT 16
#define MAX_USB_PHY_DATA_SIZE 0x30
#define PHY_ADDR_0X09 0x09
#define PHY_ADDR_0X0B 0x0b
#define PHY_ADDR_0X0D 0x0d
#define PHY_ADDR_0X10 0x10
#define PHY_ADDR_0X1F 0x1f
#define PHY_ADDR_0X20 0x20
#define PHY_ADDR_0X21 0x21
#define PHY_ADDR_0X30 0x30
#define REG_0X09_FORCE_CALIBRATION BIT(9)
#define REG_0X0B_RX_OFFSET_RANGE_MASK 0xc
#define REG_0X0D_RX_DEBUG_TEST_EN BIT(6)
#define REG_0X10_DEBUG_MODE_SETTING 0x3c0
#define REG_0X10_DEBUG_MODE_SETTING_MASK 0x3f8
#define REG_0X1F_RX_OFFSET_CODE_MASK 0x1e
#define USB_U3_TX_LFPS_SWING_TRIM_SHIFT 4
#define USB_U3_TX_LFPS_SWING_TRIM_MASK 0xf
#define AMPLITUDE_CONTROL_COARSE_MASK 0xff
#define AMPLITUDE_CONTROL_FINE_MASK 0xffff
#define AMPLITUDE_CONTROL_COARSE_DEFAULT 0xff
#define AMPLITUDE_CONTROL_FINE_DEFAULT 0xffff
#define PHY_ADDR_MAP_ARRAY_INDEX(addr) (addr)
#define ARRAY_INDEX_MAP_PHY_ADDR(index) (index)
struct phy_reg {
void __iomem *reg_mdio_ctl;
};
struct phy_data {
u8 addr;
u16 data;
};
struct phy_cfg {
int param_size;
struct phy_data param[MAX_USB_PHY_DATA_SIZE];
bool check_efuse;
bool do_toggle;
bool do_toggle_once;
bool use_default_parameter;
bool check_rx_front_end_offset;
};
struct phy_parameter {
struct phy_reg phy_reg;
/* Get from efuse */
u8 efuse_usb_u3_tx_lfps_swing_trim;
/* Get from dts */
u32 amplitude_control_coarse;
u32 amplitude_control_fine;
};
struct rtk_phy {
struct device *dev;
struct phy_cfg *phy_cfg;
int num_phy;
struct phy_parameter *phy_parameter;
struct dentry *debug_dir;
};
#define PHY_IO_TIMEOUT_USEC (50000)
#define PHY_IO_DELAY_US (100)
static inline int utmi_wait_register(void __iomem *reg, u32 mask, u32 result)
{
int ret;
unsigned int val;
ret = read_poll_timeout(readl, val, ((val & mask) == result),
PHY_IO_DELAY_US, PHY_IO_TIMEOUT_USEC, false, reg);
if (ret) {
pr_err("%s can't program USB phy\n", __func__);
return -ETIMEDOUT;
}
return 0;
}
static int rtk_phy3_wait_vbusy(struct phy_reg *phy_reg)
{
return utmi_wait_register(phy_reg->reg_mdio_ctl, USB_MDIO_CTRL_PHY_BUSY, 0);
}
static u16 rtk_phy_read(struct phy_reg *phy_reg, char addr)
{
unsigned int tmp;
u32 value;
tmp = (addr << USB_MDIO_CTRL_PHY_ADDR_SHIFT);
writel(tmp, phy_reg->reg_mdio_ctl);
rtk_phy3_wait_vbusy(phy_reg);
value = readl(phy_reg->reg_mdio_ctl);
value = value >> USB_MDIO_CTRL_PHY_DATA_SHIFT;
return (u16)value;
}
static int rtk_phy_write(struct phy_reg *phy_reg, char addr, u16 data)
{
unsigned int val;
val = USB_MDIO_CTRL_PHY_WRITE |
(addr << USB_MDIO_CTRL_PHY_ADDR_SHIFT) |
(data << USB_MDIO_CTRL_PHY_DATA_SHIFT);
writel(val, phy_reg->reg_mdio_ctl);
rtk_phy3_wait_vbusy(phy_reg);
return 0;
}
static void do_rtk_usb3_phy_toggle(struct rtk_phy *rtk_phy, int index, bool connect)
{
struct phy_cfg *phy_cfg = rtk_phy->phy_cfg;
struct phy_reg *phy_reg;
struct phy_parameter *phy_parameter;
struct phy_data *phy_data;
u8 addr;
u16 data;
int i;
phy_parameter = &((struct phy_parameter *)rtk_phy->phy_parameter)[index];
phy_reg = &phy_parameter->phy_reg;
if (!phy_cfg->do_toggle)
return;
i = PHY_ADDR_MAP_ARRAY_INDEX(PHY_ADDR_0X09);
phy_data = phy_cfg->param + i;
addr = phy_data->addr;
data = phy_data->data;
if (!addr && !data) {
addr = PHY_ADDR_0X09;
data = rtk_phy_read(phy_reg, addr);
phy_data->addr = addr;
phy_data->data = data;
}
rtk_phy_write(phy_reg, addr, data & (~REG_0X09_FORCE_CALIBRATION));
mdelay(1);
rtk_phy_write(phy_reg, addr, data | REG_0X09_FORCE_CALIBRATION);
}
static int do_rtk_phy_init(struct rtk_phy *rtk_phy, int index)
{
struct phy_cfg *phy_cfg;
struct phy_reg *phy_reg;
struct phy_parameter *phy_parameter;
int i = 0;
phy_cfg = rtk_phy->phy_cfg;
phy_parameter = &((struct phy_parameter *)rtk_phy->phy_parameter)[index];
phy_reg = &phy_parameter->phy_reg;
if (phy_cfg->use_default_parameter)
goto do_toggle;
for (i = 0; i < phy_cfg->param_size; i++) {
struct phy_data *phy_data = phy_cfg->param + i;
u8 addr = phy_data->addr;
u16 data = phy_data->data;
if (!addr && !data)
continue;
rtk_phy_write(phy_reg, addr, data);
}
do_toggle:
if (phy_cfg->do_toggle_once)
phy_cfg->do_toggle = true;
do_rtk_usb3_phy_toggle(rtk_phy, index, false);
if (phy_cfg->do_toggle_once) {
u16 check_value = 0;
int count = 10;
u16 value_0x0d, value_0x10;
/* Enable Debug mode by set 0x0D and 0x10 */
value_0x0d = rtk_phy_read(phy_reg, PHY_ADDR_0X0D);
value_0x10 = rtk_phy_read(phy_reg, PHY_ADDR_0X10);
rtk_phy_write(phy_reg, PHY_ADDR_0X0D,
value_0x0d | REG_0X0D_RX_DEBUG_TEST_EN);
rtk_phy_write(phy_reg, PHY_ADDR_0X10,
(value_0x10 & ~REG_0X10_DEBUG_MODE_SETTING_MASK) |
REG_0X10_DEBUG_MODE_SETTING);
check_value = rtk_phy_read(phy_reg, PHY_ADDR_0X30);
while (!(check_value & BIT(15))) {
check_value = rtk_phy_read(phy_reg, PHY_ADDR_0X30);
mdelay(1);
if (count-- < 0)
break;
}
if (!(check_value & BIT(15)))
dev_info(rtk_phy->dev, "toggle fail addr=0x%02x, data=0x%04x\n",
PHY_ADDR_0X30, check_value);
/* Disable Debug mode by set 0x0D and 0x10 to default*/
rtk_phy_write(phy_reg, PHY_ADDR_0X0D, value_0x0d);
rtk_phy_write(phy_reg, PHY_ADDR_0X10, value_0x10);
phy_cfg->do_toggle = false;
}
if (phy_cfg->check_rx_front_end_offset) {
u16 rx_offset_code, rx_offset_range;
u16 code_mask = REG_0X1F_RX_OFFSET_CODE_MASK;
u16 range_mask = REG_0X0B_RX_OFFSET_RANGE_MASK;
bool do_update = false;
rx_offset_code = rtk_phy_read(phy_reg, PHY_ADDR_0X1F);
if (((rx_offset_code & code_mask) == 0x0) ||
((rx_offset_code & code_mask) == code_mask))
do_update = true;
rx_offset_range = rtk_phy_read(phy_reg, PHY_ADDR_0X0B);
if (((rx_offset_range & range_mask) == range_mask) && do_update) {
dev_warn(rtk_phy->dev, "Don't update rx_offset_range (rx_offset_code=0x%x, rx_offset_range=0x%x)\n",
rx_offset_code, rx_offset_range);
do_update = false;
}
if (do_update) {
u16 tmp1, tmp2;
tmp1 = rx_offset_range & (~range_mask);
tmp2 = rx_offset_range & range_mask;
tmp2 += (1 << 2);
rx_offset_range = tmp1 | (tmp2 & range_mask);
rtk_phy_write(phy_reg, PHY_ADDR_0X0B, rx_offset_range);
goto do_toggle;
}
}
return 0;
}
static int rtk_phy_init(struct phy *phy)
{
struct rtk_phy *rtk_phy = phy_get_drvdata(phy);
int ret = 0;
int i;
unsigned long phy_init_time = jiffies;
for (i = 0; i < rtk_phy->num_phy; i++)
ret = do_rtk_phy_init(rtk_phy, i);
dev_dbg(rtk_phy->dev, "Initialized RTK USB 3.0 PHY (take %dms)\n",
jiffies_to_msecs(jiffies - phy_init_time));
return ret;
}
static int rtk_phy_exit(struct phy *phy)
{
return 0;
}
static void rtk_phy_toggle(struct rtk_phy *rtk_phy, bool connect, int port)
{
int index = port;
if (index > rtk_phy->num_phy) {
dev_err(rtk_phy->dev, "%s: The port=%d is not in usb phy (num_phy=%d)\n",
__func__, index, rtk_phy->num_phy);
return;
}
do_rtk_usb3_phy_toggle(rtk_phy, index, connect);
}
static int rtk_phy_connect(struct phy *phy, int port)
{
struct rtk_phy *rtk_phy = phy_get_drvdata(phy);
dev_dbg(rtk_phy->dev, "%s port=%d\n", __func__, port);
rtk_phy_toggle(rtk_phy, true, port);
return 0;
}
static int rtk_phy_disconnect(struct phy *phy, int port)
{
struct rtk_phy *rtk_phy = phy_get_drvdata(phy);
dev_dbg(rtk_phy->dev, "%s port=%d\n", __func__, port);
rtk_phy_toggle(rtk_phy, false, port);
return 0;
}
static const struct phy_ops ops = {
.init = rtk_phy_init,
.exit = rtk_phy_exit,
.connect = rtk_phy_connect,
.disconnect = rtk_phy_disconnect,
.owner = THIS_MODULE,
};
#ifdef CONFIG_DEBUG_FS
static struct dentry *create_phy_debug_root(void)
{
struct dentry *phy_debug_root;
phy_debug_root = debugfs_lookup("phy", usb_debug_root);
if (!phy_debug_root)
phy_debug_root = debugfs_create_dir("phy", usb_debug_root);
return phy_debug_root;
}
static int rtk_usb3_parameter_show(struct seq_file *s, void *unused)
{
struct rtk_phy *rtk_phy = s->private;
struct phy_cfg *phy_cfg;
int i, index;
phy_cfg = rtk_phy->phy_cfg;
seq_puts(s, "Property:\n");
seq_printf(s, " check_efuse: %s\n",
phy_cfg->check_efuse ? "Enable" : "Disable");
seq_printf(s, " do_toggle: %s\n",
phy_cfg->do_toggle ? "Enable" : "Disable");
seq_printf(s, " do_toggle_once: %s\n",
phy_cfg->do_toggle_once ? "Enable" : "Disable");
seq_printf(s, " use_default_parameter: %s\n",
phy_cfg->use_default_parameter ? "Enable" : "Disable");
for (index = 0; index < rtk_phy->num_phy; index++) {
struct phy_reg *phy_reg;
struct phy_parameter *phy_parameter;
phy_parameter = &((struct phy_parameter *)rtk_phy->phy_parameter)[index];
phy_reg = &phy_parameter->phy_reg;
seq_printf(s, "PHY %d:\n", index);
for (i = 0; i < phy_cfg->param_size; i++) {
struct phy_data *phy_data = phy_cfg->param + i;
u8 addr = ARRAY_INDEX_MAP_PHY_ADDR(i);
u16 data = phy_data->data;
if (!phy_data->addr && !data)
seq_printf(s, " addr = 0x%02x, data = none ==> read value = 0x%04x\n",
addr, rtk_phy_read(phy_reg, addr));
else
seq_printf(s, " addr = 0x%02x, data = 0x%04x ==> read value = 0x%04x\n",
addr, data, rtk_phy_read(phy_reg, addr));
}
seq_puts(s, "PHY Property:\n");
seq_printf(s, " efuse_usb_u3_tx_lfps_swing_trim: 0x%x\n",
(int)phy_parameter->efuse_usb_u3_tx_lfps_swing_trim);
seq_printf(s, " amplitude_control_coarse: 0x%x\n",
(int)phy_parameter->amplitude_control_coarse);
seq_printf(s, " amplitude_control_fine: 0x%x\n",
(int)phy_parameter->amplitude_control_fine);
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(rtk_usb3_parameter);
static inline void create_debug_files(struct rtk_phy *rtk_phy)
{
struct dentry *phy_debug_root = NULL;
phy_debug_root = create_phy_debug_root();
if (!phy_debug_root)
return;
rtk_phy->debug_dir = debugfs_create_dir(dev_name(rtk_phy->dev), phy_debug_root);
debugfs_create_file("parameter", 0444, rtk_phy->debug_dir, rtk_phy,
&rtk_usb3_parameter_fops);
}
static inline void remove_debug_files(struct rtk_phy *rtk_phy)
{
debugfs_remove_recursive(rtk_phy->debug_dir);
}
#else
static inline void create_debug_files(struct rtk_phy *rtk_phy) { }
static inline void remove_debug_files(struct rtk_phy *rtk_phy) { }
#endif /* CONFIG_DEBUG_FS */
static int get_phy_data_by_efuse(struct rtk_phy *rtk_phy,
struct phy_parameter *phy_parameter, int index)
{
struct phy_cfg *phy_cfg = rtk_phy->phy_cfg;
u8 value = 0;
struct nvmem_cell *cell;
if (!phy_cfg->check_efuse)
goto out;
cell = nvmem_cell_get(rtk_phy->dev, "usb_u3_tx_lfps_swing_trim");
if (IS_ERR(cell)) {
dev_dbg(rtk_phy->dev, "%s no usb_u3_tx_lfps_swing_trim: %ld\n",
__func__, PTR_ERR(cell));
} else {
unsigned char *buf;
size_t buf_size;
buf = nvmem_cell_read(cell, &buf_size);
if (!IS_ERR(buf)) {
value = buf[0] & USB_U3_TX_LFPS_SWING_TRIM_MASK;
kfree(buf);
}
nvmem_cell_put(cell);
}
if (value > 0 && value < 0x8)
phy_parameter->efuse_usb_u3_tx_lfps_swing_trim = 0x8;
else
phy_parameter->efuse_usb_u3_tx_lfps_swing_trim = (u8)value;
out:
return 0;
}
static void update_amplitude_control_value(struct rtk_phy *rtk_phy,
struct phy_parameter *phy_parameter)
{
struct phy_cfg *phy_cfg;
struct phy_reg *phy_reg;
phy_reg = &phy_parameter->phy_reg;
phy_cfg = rtk_phy->phy_cfg;
if (phy_parameter->amplitude_control_coarse != AMPLITUDE_CONTROL_COARSE_DEFAULT) {
u16 val_mask = AMPLITUDE_CONTROL_COARSE_MASK;
u16 data;
if (!phy_cfg->param[PHY_ADDR_0X20].addr && !phy_cfg->param[PHY_ADDR_0X20].data) {
phy_cfg->param[PHY_ADDR_0X20].addr = PHY_ADDR_0X20;
data = rtk_phy_read(phy_reg, PHY_ADDR_0X20);
} else {
data = phy_cfg->param[PHY_ADDR_0X20].data;
}
data &= (~val_mask);
data |= (phy_parameter->amplitude_control_coarse & val_mask);
phy_cfg->param[PHY_ADDR_0X20].data = data;
}
if (phy_parameter->efuse_usb_u3_tx_lfps_swing_trim) {
u8 efuse_val = phy_parameter->efuse_usb_u3_tx_lfps_swing_trim;
u16 val_mask = USB_U3_TX_LFPS_SWING_TRIM_MASK;
int val_shift = USB_U3_TX_LFPS_SWING_TRIM_SHIFT;
u16 data;
if (!phy_cfg->param[PHY_ADDR_0X20].addr && !phy_cfg->param[PHY_ADDR_0X20].data) {
phy_cfg->param[PHY_ADDR_0X20].addr = PHY_ADDR_0X20;
data = rtk_phy_read(phy_reg, PHY_ADDR_0X20);
} else {
data = phy_cfg->param[PHY_ADDR_0X20].data;
}
data &= ~(val_mask << val_shift);
data |= ((efuse_val & val_mask) << val_shift);
phy_cfg->param[PHY_ADDR_0X20].data = data;
}
if (phy_parameter->amplitude_control_fine != AMPLITUDE_CONTROL_FINE_DEFAULT) {
u16 val_mask = AMPLITUDE_CONTROL_FINE_MASK;
if (!phy_cfg->param[PHY_ADDR_0X21].addr && !phy_cfg->param[PHY_ADDR_0X21].data)
phy_cfg->param[PHY_ADDR_0X21].addr = PHY_ADDR_0X21;
phy_cfg->param[PHY_ADDR_0X21].data =
phy_parameter->amplitude_control_fine & val_mask;
}
}
static int parse_phy_data(struct rtk_phy *rtk_phy)
{
struct device *dev = rtk_phy->dev;
struct phy_parameter *phy_parameter;
int ret = 0;
int index;
rtk_phy->phy_parameter = devm_kzalloc(dev, sizeof(struct phy_parameter) *
rtk_phy->num_phy, GFP_KERNEL);
if (!rtk_phy->phy_parameter)
return -ENOMEM;
for (index = 0; index < rtk_phy->num_phy; index++) {
phy_parameter = &((struct phy_parameter *)rtk_phy->phy_parameter)[index];
phy_parameter->phy_reg.reg_mdio_ctl = of_iomap(dev->of_node, 0) + index;
/* Amplitude control address 0x20 bit 0 to bit 7 */
if (of_property_read_u32(dev->of_node, "realtek,amplitude-control-coarse-tuning",
&phy_parameter->amplitude_control_coarse))
phy_parameter->amplitude_control_coarse = AMPLITUDE_CONTROL_COARSE_DEFAULT;
/* Amplitude control address 0x21 bit 0 to bit 16 */
if (of_property_read_u32(dev->of_node, "realtek,amplitude-control-fine-tuning",
&phy_parameter->amplitude_control_fine))
phy_parameter->amplitude_control_fine = AMPLITUDE_CONTROL_FINE_DEFAULT;
get_phy_data_by_efuse(rtk_phy, phy_parameter, index);
update_amplitude_control_value(rtk_phy, phy_parameter);
}
return ret;
}
static int rtk_usb3phy_probe(struct platform_device *pdev)
{
struct rtk_phy *rtk_phy;
struct device *dev = &pdev->dev;
struct phy *generic_phy;
struct phy_provider *phy_provider;
const struct phy_cfg *phy_cfg;
int ret;
phy_cfg = of_device_get_match_data(dev);
if (!phy_cfg) {
dev_err(dev, "phy config are not assigned!\n");
return -EINVAL;
}
rtk_phy = devm_kzalloc(dev, sizeof(*rtk_phy), GFP_KERNEL);
if (!rtk_phy)
return -ENOMEM;
rtk_phy->dev = &pdev->dev;
rtk_phy->phy_cfg = devm_kzalloc(dev, sizeof(*phy_cfg), GFP_KERNEL);
memcpy(rtk_phy->phy_cfg, phy_cfg, sizeof(*phy_cfg));
rtk_phy->num_phy = 1;
ret = parse_phy_data(rtk_phy);
if (ret)
goto err;
platform_set_drvdata(pdev, rtk_phy);
generic_phy = devm_phy_create(rtk_phy->dev, NULL, &ops);
if (IS_ERR(generic_phy))
return PTR_ERR(generic_phy);
phy_set_drvdata(generic_phy, rtk_phy);
phy_provider = devm_of_phy_provider_register(rtk_phy->dev, of_phy_simple_xlate);
if (IS_ERR(phy_provider))
return PTR_ERR(phy_provider);
create_debug_files(rtk_phy);
err:
return ret;
}
static void rtk_usb3phy_remove(struct platform_device *pdev)
{
struct rtk_phy *rtk_phy = platform_get_drvdata(pdev);
remove_debug_files(rtk_phy);
}
static const struct phy_cfg rtd1295_phy_cfg = {
.param_size = MAX_USB_PHY_DATA_SIZE,
.param = { [0] = {0x01, 0x4008}, [1] = {0x01, 0xe046},
[2] = {0x02, 0x6046}, [3] = {0x03, 0x2779},
[4] = {0x04, 0x72f5}, [5] = {0x05, 0x2ad3},
[6] = {0x06, 0x000e}, [7] = {0x07, 0x2e00},
[8] = {0x08, 0x3591}, [9] = {0x09, 0x525c},
[10] = {0x0a, 0xa600}, [11] = {0x0b, 0xa904},
[12] = {0x0c, 0xc000}, [13] = {0x0d, 0xef1c},
[14] = {0x0e, 0x2000}, [15] = {0x0f, 0x0000},
[16] = {0x10, 0x000c}, [17] = {0x11, 0x4c00},
[18] = {0x12, 0xfc00}, [19] = {0x13, 0x0c81},
[20] = {0x14, 0xde01}, [21] = {0x15, 0x0000},
[22] = {0x16, 0x0000}, [23] = {0x17, 0x0000},
[24] = {0x18, 0x0000}, [25] = {0x19, 0x4004},
[26] = {0x1a, 0x1260}, [27] = {0x1b, 0xff00},
[28] = {0x1c, 0xcb00}, [29] = {0x1d, 0xa03f},
[30] = {0x1e, 0xc2e0}, [31] = {0x1f, 0x2807},
[32] = {0x20, 0x947a}, [33] = {0x21, 0x88aa},
[34] = {0x22, 0x0057}, [35] = {0x23, 0xab66},
[36] = {0x24, 0x0800}, [37] = {0x25, 0x0000},
[38] = {0x26, 0x040a}, [39] = {0x27, 0x01d6},
[40] = {0x28, 0xf8c2}, [41] = {0x29, 0x3080},
[42] = {0x2a, 0x3082}, [43] = {0x2b, 0x2078},
[44] = {0x2c, 0xffff}, [45] = {0x2d, 0xffff},
[46] = {0x2e, 0x0000}, [47] = {0x2f, 0x0040}, },
.check_efuse = false,
.do_toggle = true,
.do_toggle_once = false,
.use_default_parameter = false,
.check_rx_front_end_offset = false,
};
static const struct phy_cfg rtd1619_phy_cfg = {
.param_size = MAX_USB_PHY_DATA_SIZE,
.param = { [8] = {0x08, 0x3591},
[38] = {0x26, 0x840b},
[40] = {0x28, 0xf842}, },
.check_efuse = false,
.do_toggle = true,
.do_toggle_once = false,
.use_default_parameter = false,
.check_rx_front_end_offset = false,
};
static const struct phy_cfg rtd1319_phy_cfg = {
.param_size = MAX_USB_PHY_DATA_SIZE,
.param = { [1] = {0x01, 0xac86},
[6] = {0x06, 0x0003},
[9] = {0x09, 0x924c},
[10] = {0x0a, 0xa608},
[11] = {0x0b, 0xb905},
[14] = {0x0e, 0x2010},
[32] = {0x20, 0x705a},
[33] = {0x21, 0xf645},
[34] = {0x22, 0x0013},
[35] = {0x23, 0xcb66},
[41] = {0x29, 0xff00}, },
.check_efuse = true,
.do_toggle = true,
.do_toggle_once = false,
.use_default_parameter = false,
.check_rx_front_end_offset = false,
};
static const struct phy_cfg rtd1619b_phy_cfg = {
.param_size = MAX_USB_PHY_DATA_SIZE,
.param = { [1] = {0x01, 0xac8c},
[6] = {0x06, 0x0017},
[9] = {0x09, 0x724c},
[10] = {0x0a, 0xb610},
[11] = {0x0b, 0xb90d},
[13] = {0x0d, 0xef2a},
[15] = {0x0f, 0x9050},
[16] = {0x10, 0x000c},
[32] = {0x20, 0x70ff},
[34] = {0x22, 0x0013},
[35] = {0x23, 0xdb66},
[38] = {0x26, 0x8609},
[41] = {0x29, 0xff13},
[42] = {0x2a, 0x3070}, },
.check_efuse = true,
.do_toggle = false,
.do_toggle_once = true,
.use_default_parameter = false,
.check_rx_front_end_offset = false,
};
static const struct phy_cfg rtd1319d_phy_cfg = {
.param_size = MAX_USB_PHY_DATA_SIZE,
.param = { [1] = {0x01, 0xac89},
[4] = {0x04, 0xf2f5},
[6] = {0x06, 0x0017},
[9] = {0x09, 0x424c},
[10] = {0x0a, 0x9610},
[11] = {0x0b, 0x9901},
[12] = {0x0c, 0xf000},
[13] = {0x0d, 0xef2a},
[14] = {0x0e, 0x1000},
[15] = {0x0f, 0x9050},
[32] = {0x20, 0x7077},
[35] = {0x23, 0x0b62},
[37] = {0x25, 0x10ec},
[42] = {0x2a, 0x3070}, },
.check_efuse = true,
.do_toggle = false,
.do_toggle_once = true,
.use_default_parameter = false,
.check_rx_front_end_offset = true,
};
static const struct of_device_id usbphy_rtk_dt_match[] = {
{ .compatible = "realtek,rtd1295-usb3phy", .data = &rtd1295_phy_cfg },
{ .compatible = "realtek,rtd1319-usb3phy", .data = &rtd1319_phy_cfg },
{ .compatible = "realtek,rtd1319d-usb3phy", .data = &rtd1319d_phy_cfg },
{ .compatible = "realtek,rtd1619-usb3phy", .data = &rtd1619_phy_cfg },
{ .compatible = "realtek,rtd1619b-usb3phy", .data = &rtd1619b_phy_cfg },
{},
};
MODULE_DEVICE_TABLE(of, usbphy_rtk_dt_match);
static struct platform_driver rtk_usb3phy_driver = {
.probe = rtk_usb3phy_probe,
.remove_new = rtk_usb3phy_remove,
.driver = {
.name = "rtk-usb3phy",
.of_match_table = usbphy_rtk_dt_match,
},
};
module_platform_driver(rtk_usb3phy_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Stanley Chang <stanley_chang@realtek.com>");
MODULE_DESCRIPTION("Realtek usb 3.0 phy driver");

View File

@ -1531,6 +1531,19 @@ int tegra_xusb_padctl_get_usb3_companion(struct tegra_xusb_padctl *padctl,
}
EXPORT_SYMBOL_GPL(tegra_xusb_padctl_get_usb3_companion);
int tegra_xusb_padctl_get_port_number(struct phy *phy)
{
struct tegra_xusb_lane *lane;
if (!phy)
return -ENODEV;
lane = phy_get_drvdata(phy);
return lane->index;
}
EXPORT_SYMBOL_GPL(tegra_xusb_padctl_get_port_number);
MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
MODULE_DESCRIPTION("Tegra XUSB Pad Controller driver");
MODULE_LICENSE("GPL v2");

View File

@ -24,6 +24,23 @@
#define DP_PORT_VDO (DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D)) | \
DP_CAP_DFP_D | DP_CAP_RECEPTACLE)
static void cros_typec_role_switch_quirk(struct fwnode_handle *fwnode)
{
#ifdef CONFIG_ACPI
struct fwnode_handle *switch_fwnode;
/* Supply the USB role switch with the correct pld_crc if it's missing. */
switch_fwnode = fwnode_find_reference(fwnode, "usb-role-switch", 0);
if (!IS_ERR_OR_NULL(switch_fwnode)) {
struct acpi_device *adev = to_acpi_device_node(switch_fwnode);
if (adev && !adev->pld_crc)
adev->pld_crc = to_acpi_device_node(fwnode)->pld_crc;
fwnode_handle_put(switch_fwnode);
}
#endif
}
static int cros_typec_parse_port_props(struct typec_capability *cap,
struct fwnode_handle *fwnode,
struct device *dev)
@ -66,6 +83,8 @@ static int cros_typec_parse_port_props(struct typec_capability *cap,
cap->prefer_role = ret;
}
cros_typec_role_switch_quirk(fwnode);
cap->fwnode = fwnode;
return 0;

View File

@ -1,4 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
ccflags-y := -I$(src)
obj-${CONFIG_USB4} := thunderbolt.o
thunderbolt-objs := nhi.o nhi_ops.o ctl.o tb.o switch.o cap.o path.o tunnel.o eeprom.o
thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o lc.o tmu.o usb4.o

View File

@ -15,6 +15,8 @@
#include "ctl.h"
#define CREATE_TRACE_POINTS
#include "trace.h"
#define TB_CTL_RX_PKG_COUNT 10
#define TB_CTL_RETRIES 4
@ -32,6 +34,7 @@
* @timeout_msec: Default timeout for non-raw control messages
* @callback: Callback called when hotplug message is received
* @callback_data: Data passed to @callback
* @index: Domain number. This will be output with the trace record.
*/
struct tb_ctl {
struct tb_nhi *nhi;
@ -47,6 +50,8 @@ struct tb_ctl {
int timeout_msec;
event_cb callback;
void *callback_data;
int index;
};
@ -369,6 +374,9 @@ static int tb_ctl_tx(struct tb_ctl *ctl, const void *data, size_t len,
pkg->frame.size = len + 4;
pkg->frame.sof = type;
pkg->frame.eof = type;
trace_tb_tx(ctl->index, type, data, len);
cpu_to_be32_array(pkg->buffer, data, len / 4);
*(__be32 *) (pkg->buffer + len) = tb_crc(pkg->buffer, len);
@ -384,6 +392,7 @@ static int tb_ctl_tx(struct tb_ctl *ctl, const void *data, size_t len,
static bool tb_ctl_handle_event(struct tb_ctl *ctl, enum tb_cfg_pkg_type type,
struct ctl_pkg *pkg, size_t size)
{
trace_tb_event(ctl->index, type, pkg->buffer, size);
return ctl->callback(ctl->callback_data, type, pkg->buffer, size);
}
@ -489,6 +498,9 @@ static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame,
* triggered from messing with the active requests.
*/
req = tb_cfg_request_find(pkg->ctl, pkg);
trace_tb_rx(pkg->ctl->index, frame->eof, pkg->buffer, frame->size, !req);
if (req) {
if (req->copy(req, pkg))
schedule_work(&req->work);
@ -614,6 +626,7 @@ struct tb_cfg_result tb_cfg_request_sync(struct tb_ctl *ctl,
/**
* tb_ctl_alloc() - allocate a control channel
* @nhi: Pointer to NHI
* @index: Domain number
* @timeout_msec: Default timeout used with non-raw control messages
* @cb: Callback called for plug events
* @cb_data: Data passed to @cb
@ -622,14 +635,16 @@ struct tb_cfg_result tb_cfg_request_sync(struct tb_ctl *ctl,
*
* Return: Returns a pointer on success or NULL on failure.
*/
struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, int timeout_msec, event_cb cb,
void *cb_data)
struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, int index, int timeout_msec,
event_cb cb, void *cb_data)
{
int i;
struct tb_ctl *ctl = kzalloc(sizeof(*ctl), GFP_KERNEL);
if (!ctl)
return NULL;
ctl->nhi = nhi;
ctl->index = index;
ctl->timeout_msec = timeout_msec;
ctl->callback = cb;
ctl->callback_data = cb_data;

View File

@ -21,8 +21,8 @@ struct tb_ctl;
typedef bool (*event_cb)(void *data, enum tb_cfg_pkg_type type,
const void *buf, size_t size);
struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, int timeout_msec, event_cb cb,
void *cb_data);
struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, int index, int timeout_msec,
event_cb cb, void *cb_data);
void tb_ctl_start(struct tb_ctl *ctl);
void tb_ctl_stop(struct tb_ctl *ctl);
void tb_ctl_free(struct tb_ctl *ctl);

View File

@ -321,12 +321,12 @@ static void tb_domain_release(struct device *dev)
tb_ctl_free(tb->ctl);
destroy_workqueue(tb->wq);
ida_simple_remove(&tb_domain_ida, tb->index);
ida_free(&tb_domain_ida, tb->index);
mutex_destroy(&tb->lock);
kfree(tb);
}
struct device_type tb_domain_type = {
const struct device_type tb_domain_type = {
.name = "thunderbolt_domain",
.release = tb_domain_release,
};
@ -389,7 +389,7 @@ struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize
tb->nhi = nhi;
mutex_init(&tb->lock);
tb->index = ida_simple_get(&tb_domain_ida, 0, 0, GFP_KERNEL);
tb->index = ida_alloc(&tb_domain_ida, GFP_KERNEL);
if (tb->index < 0)
goto err_free;
@ -397,7 +397,7 @@ struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize
if (!tb->wq)
goto err_remove_ida;
tb->ctl = tb_ctl_alloc(nhi, timeout_msec, tb_domain_event_cb, tb);
tb->ctl = tb_ctl_alloc(nhi, tb->index, timeout_msec, tb_domain_event_cb, tb);
if (!tb->ctl)
goto err_destroy_wq;
@ -413,7 +413,7 @@ struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize
err_destroy_wq:
destroy_workqueue(tb->wq);
err_remove_ida:
ida_simple_remove(&tb_domain_ida, tb->index);
ida_free(&tb_domain_ida, tb->index);
err_free:
kfree(tb);
@ -423,6 +423,7 @@ err_free:
/**
* tb_domain_add() - Add domain to the system
* @tb: Domain to add
* @reset: Issue reset to the host router
*
* Starts the domain and adds it to the system. Hotplugging devices will
* work after this has been returned successfully. In order to remove
@ -431,7 +432,7 @@ err_free:
*
* Return: %0 in case of success and negative errno in case of error
*/
int tb_domain_add(struct tb *tb)
int tb_domain_add(struct tb *tb, bool reset)
{
int ret;
@ -460,7 +461,7 @@ int tb_domain_add(struct tb *tb)
/* Start the domain */
if (tb->cm_ops->start) {
ret = tb->cm_ops->start(tb);
ret = tb->cm_ops->start(tb, reset);
if (ret)
goto err_domain_del;
}
@ -505,6 +506,10 @@ void tb_domain_remove(struct tb *tb)
mutex_unlock(&tb->lock);
flush_workqueue(tb->wq);
if (tb->cm_ops->deinit)
tb->cm_ops->deinit(tb);
device_unregister(&tb->dev);
}

View File

@ -2144,7 +2144,7 @@ static int icm_runtime_resume(struct tb *tb)
return 0;
}
static int icm_start(struct tb *tb)
static int icm_start(struct tb *tb, bool not_used)
{
struct icm *icm = tb_priv(tb);
int ret;

View File

@ -6,6 +6,8 @@
* Author: Mika Westerberg <mika.westerberg@linux.intel.com>
*/
#include <linux/delay.h>
#include "tb.h"
/**
@ -45,6 +47,49 @@ static int find_port_lc_cap(struct tb_port *port)
return sw->cap_lc + start + phys * size;
}
/**
* tb_lc_reset_port() - Trigger downstream port reset through LC
* @port: Port that is reset
*
* Triggers downstream port reset through link controller registers.
* Returns %0 in case of success negative errno otherwise. Only supports
* non-USB4 routers with link controller (that's Thunderbolt 2 and
* Thunderbolt 3).
*/
int tb_lc_reset_port(struct tb_port *port)
{
struct tb_switch *sw = port->sw;
int cap, ret;
u32 mode;
if (sw->generation < 2)
return -EINVAL;
cap = find_port_lc_cap(port);
if (cap < 0)
return cap;
ret = tb_sw_read(sw, &mode, TB_CFG_SWITCH, cap + TB_LC_PORT_MODE, 1);
if (ret)
return ret;
mode |= TB_LC_PORT_MODE_DPR;
ret = tb_sw_write(sw, &mode, TB_CFG_SWITCH, cap + TB_LC_PORT_MODE, 1);
if (ret)
return ret;
fsleep(10000);
ret = tb_sw_read(sw, &mode, TB_CFG_SWITCH, cap + TB_LC_PORT_MODE, 1);
if (ret)
return ret;
mode &= ~TB_LC_PORT_MODE_DPR;
return tb_sw_write(sw, &mode, TB_CFG_SWITCH, cap + TB_LC_PORT_MODE, 1);
}
static int tb_lc_set_port_configured(struct tb_port *port, bool configured)
{
bool upstream = tb_is_upstream_port(port);

View File

@ -48,7 +48,7 @@
static bool host_reset = true;
module_param(host_reset, bool, 0444);
MODULE_PARM_DESC(host_reset, "reset USBv2 host router (default: true)");
MODULE_PARM_DESC(host_reset, "reset USB4 host router (default: true)");
static int ring_interrupt_index(const struct tb_ring *ring)
{
@ -465,7 +465,7 @@ static int ring_request_msix(struct tb_ring *ring, bool no_suspend)
if (!nhi->pdev->msix_enabled)
return 0;
ret = ida_simple_get(&nhi->msix_ida, 0, MSIX_MAX_VECS, GFP_KERNEL);
ret = ida_alloc_max(&nhi->msix_ida, MSIX_MAX_VECS - 1, GFP_KERNEL);
if (ret < 0)
return ret;
@ -485,7 +485,7 @@ static int ring_request_msix(struct tb_ring *ring, bool no_suspend)
return 0;
err_ida_remove:
ida_simple_remove(&nhi->msix_ida, ring->vector);
ida_free(&nhi->msix_ida, ring->vector);
return ret;
}
@ -496,7 +496,7 @@ static void ring_release_msix(struct tb_ring *ring)
return;
free_irq(ring->irq, ring);
ida_simple_remove(&ring->nhi->msix_ida, ring->vector);
ida_free(&ring->nhi->msix_ida, ring->vector);
ring->vector = 0;
ring->irq = 0;
}
@ -1364,7 +1364,6 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
nhi_check_quirks(nhi);
nhi_check_iommu(nhi);
nhi_reset(nhi);
res = nhi_init_msi(nhi);
@ -1392,7 +1391,7 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
dev_dbg(dev, "NHI initialized, starting thunderbolt\n");
res = tb_domain_add(tb);
res = tb_domain_add(tb, host_reset);
if (res) {
/*
* At this point the RX/TX rings might already have been

View File

@ -330,7 +330,7 @@ struct tb_nvm *tb_nvm_alloc(struct device *dev)
if (!nvm)
return ERR_PTR(-ENOMEM);
ret = ida_simple_get(&nvm_ida, 0, 0, GFP_KERNEL);
ret = ida_alloc(&nvm_ida, GFP_KERNEL);
if (ret < 0) {
kfree(nvm);
return ERR_PTR(ret);
@ -528,7 +528,7 @@ void tb_nvm_free(struct tb_nvm *nvm)
nvmem_unregister(nvm->non_active);
nvmem_unregister(nvm->active);
vfree(nvm->buf);
ida_simple_remove(&nvm_ida, nvm->id);
ida_free(&nvm_ida, nvm->id);
}
kfree(nvm);
}

View File

@ -446,6 +446,19 @@ static int __tb_path_deactivate_hop(struct tb_port *port, int hop_index,
return -ETIMEDOUT;
}
/**
* tb_path_deactivate_hop() - Deactivate one path in path config space
* @port: Lane or protocol adapter
* @hop_index: HopID of the path to be cleared
*
* This deactivates or clears a single path config space entry at
* @hop_index. Returns %0 in success and negative errno otherwise.
*/
int tb_path_deactivate_hop(struct tb_port *port, int hop_index)
{
return __tb_path_deactivate_hop(port, hop_index, true);
}
static void __tb_path_deactivate_hops(struct tb_path *path, int first_hop)
{
int i, res;

View File

@ -43,6 +43,12 @@ static void quirk_usb3_maximum_bandwidth(struct tb_switch *sw)
}
}
static void quirk_block_rpm_in_redrive(struct tb_switch *sw)
{
sw->quirks |= QUIRK_KEEP_POWER_IN_DP_REDRIVE;
tb_sw_dbg(sw, "preventing runtime PM in DP redrive mode\n");
}
struct tb_quirk {
u16 hw_vendor_id;
u16 hw_device_id;
@ -86,6 +92,14 @@ static const struct tb_quirk tb_quirks[] = {
quirk_usb3_maximum_bandwidth },
{ 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_40G_BRIDGE, 0x0000, 0x0000,
quirk_usb3_maximum_bandwidth },
/*
* Block Runtime PM in DP redrive mode for Intel Barlow Ridge host
* controllers.
*/
{ 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_80G_NHI, 0x0000, 0x0000,
quirk_block_rpm_in_redrive },
{ 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_40G_NHI, 0x0000, 0x0000,
quirk_block_rpm_in_redrive },
/*
* CLx is not supported on AMD USB4 Yellow Carp and Pink Sardine platforms.
*/

View File

@ -356,7 +356,7 @@ static void tb_retimer_release(struct device *dev)
kfree(rt);
}
struct device_type tb_retimer_type = {
const struct device_type tb_retimer_type = {
.name = "thunderbolt_retimer",
.groups = retimer_groups,
.release = tb_retimer_release,

View File

@ -676,6 +676,13 @@ int tb_port_disable(struct tb_port *port)
return __tb_port_enable(port, false);
}
static int tb_port_reset(struct tb_port *port)
{
if (tb_switch_is_usb4(port->sw))
return port->cap_usb4 ? usb4_port_reset(port) : 0;
return tb_lc_reset_port(port);
}
/*
* tb_init_port() - initialize a port
*
@ -771,7 +778,7 @@ static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid,
if (max_hopid < 0 || max_hopid > port_max_hopid)
max_hopid = port_max_hopid;
return ida_simple_get(ida, min_hopid, max_hopid + 1, GFP_KERNEL);
return ida_alloc_range(ida, min_hopid, max_hopid, GFP_KERNEL);
}
/**
@ -809,7 +816,7 @@ int tb_port_alloc_out_hopid(struct tb_port *port, int min_hopid, int max_hopid)
*/
void tb_port_release_in_hopid(struct tb_port *port, int hopid)
{
ida_simple_remove(&port->in_hopids, hopid);
ida_free(&port->in_hopids, hopid);
}
/**
@ -819,7 +826,7 @@ void tb_port_release_in_hopid(struct tb_port *port, int hopid)
*/
void tb_port_release_out_hopid(struct tb_port *port, int hopid)
{
ida_simple_remove(&port->out_hopids, hopid);
ida_free(&port->out_hopids, hopid);
}
static inline bool tb_switch_is_reachable(const struct tb_switch *parent,
@ -1120,7 +1127,7 @@ int tb_port_lane_bonding_enable(struct tb_port *port)
ret = tb_port_set_link_width(port->dual_link_port,
TB_LINK_WIDTH_DUAL);
if (ret)
goto err_lane0;
goto err_lane1;
}
/*
@ -1534,29 +1541,124 @@ static void tb_dump_switch(const struct tb *tb, const struct tb_switch *sw)
regs->__unknown1, regs->__unknown4);
}
static int tb_switch_reset_host(struct tb_switch *sw)
{
if (sw->generation > 1) {
struct tb_port *port;
tb_switch_for_each_port(sw, port) {
int i, ret;
/*
* For lane adapters we issue downstream port
* reset and clear up path config spaces.
*
* For protocol adapters we disable the path and
* clear path config space one by one (from 8 to
* Max Input HopID of the adapter).
*/
if (tb_port_is_null(port) && !tb_is_upstream_port(port)) {
ret = tb_port_reset(port);
if (ret)
return ret;
} else if (tb_port_is_usb3_down(port) ||
tb_port_is_usb3_up(port)) {
tb_usb3_port_enable(port, false);
} else if (tb_port_is_dpin(port) ||
tb_port_is_dpout(port)) {
tb_dp_port_enable(port, false);
} else if (tb_port_is_pcie_down(port) ||
tb_port_is_pcie_up(port)) {
tb_pci_port_enable(port, false);
} else {
continue;
}
/* Cleanup path config space of protocol adapter */
for (i = TB_PATH_MIN_HOPID;
i <= port->config.max_in_hop_id; i++) {
ret = tb_path_deactivate_hop(port, i);
if (ret)
return ret;
}
}
} else {
struct tb_cfg_result res;
/* Thunderbolt 1 uses the "reset" config space packet */
res.err = tb_sw_write(sw, ((u32 *) &sw->config) + 2,
TB_CFG_SWITCH, 2, 2);
if (res.err)
return res.err;
res = tb_cfg_reset(sw->tb->ctl, tb_route(sw));
if (res.err > 0)
return -EIO;
else if (res.err < 0)
return res.err;
}
return 0;
}
static int tb_switch_reset_device(struct tb_switch *sw)
{
return tb_port_reset(tb_switch_downstream_port(sw));
}
static bool tb_switch_enumerated(struct tb_switch *sw)
{
u32 val;
int ret;
/*
* Read directly from the hardware because we use this also
* during system sleep where sw->config.enabled is already set
* by us.
*/
ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_3, 1);
if (ret)
return false;
return !!(val & ROUTER_CS_3_V);
}
/**
* tb_switch_reset() - reconfigure route, enable and send TB_CFG_PKG_RESET
* @sw: Switch to reset
* tb_switch_reset() - Perform reset to the router
* @sw: Router to reset
*
* Return: Returns 0 on success or an error code on failure.
* Issues reset to the router @sw. Can be used for any router. For host
* routers, resets all the downstream ports and cleans up path config
* spaces accordingly. For device routers issues downstream port reset
* through the parent router, so as side effect there will be unplug
* soon after this is finished.
*
* If the router is not enumerated does nothing.
*
* Returns %0 on success or negative errno in case of failure.
*/
int tb_switch_reset(struct tb_switch *sw)
{
struct tb_cfg_result res;
int ret;
if (sw->generation > 1)
/*
* We cannot access the port config spaces unless the router is
* already enumerated. If the router is not enumerated it is
* equal to being reset so we can skip that here.
*/
if (!tb_switch_enumerated(sw))
return 0;
tb_sw_dbg(sw, "resetting switch\n");
tb_sw_dbg(sw, "resetting\n");
res.err = tb_sw_write(sw, ((u32 *) &sw->config) + 2,
TB_CFG_SWITCH, 2, 2);
if (res.err)
return res.err;
res = tb_cfg_reset(sw->tb->ctl, tb_route(sw));
if (res.err > 0)
return -EIO;
return res.err;
if (tb_route(sw))
ret = tb_switch_reset_device(sw);
else
ret = tb_switch_reset_host(sw);
if (ret)
tb_sw_warn(sw, "failed to reset\n");
return ret;
}
/**
@ -2228,7 +2330,7 @@ static const struct dev_pm_ops tb_switch_pm_ops = {
NULL)
};
struct device_type tb_switch_type = {
const struct device_type tb_switch_type = {
.name = "thunderbolt_device",
.release = tb_switch_release,
.uevent = tb_switch_uevent,

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,8 @@
#define QUIRK_FORCE_POWER_LINK_CONTROLLER BIT(0)
/* Disable CLx if not supported */
#define QUIRK_NO_CLX BIT(1)
/* Need to keep power on while USB4 port is in redrive mode */
#define QUIRK_KEEP_POWER_IN_DP_REDRIVE BIT(2)
/**
* struct tb_nvm - Structure holding NVM information
@ -217,6 +219,11 @@ struct tb_switch {
* @tb: Pointer to the domain the group belongs to
* @index: Index of the group (aka Group_ID). Valid values %1-%7
* @ports: DP IN adapters belonging to this group are linked here
* @reserved: Bandwidth released by one tunnel in the group, available
* to others. This is reported as part of estimated_bw for
* the group.
* @release_work: Worker to release the @reserved if it is not used by
* any of the tunnels.
*
* Any tunnel that requires isochronous bandwidth (that's DP for now) is
* attached to a bandwidth group. All tunnels going through the same
@ -227,6 +234,8 @@ struct tb_bandwidth_group {
struct tb *tb;
int index;
struct list_head ports;
int reserved;
struct delayed_work release_work;
};
/**
@ -258,6 +267,7 @@ struct tb_bandwidth_group {
* @group_list: The adapter is linked to the group's list of ports through this
* @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.
*
* In USB4 terminology this structure represents an adapter (protocol or
* lane adapter).
@ -286,6 +296,7 @@ struct tb_port {
struct tb_bandwidth_group *group;
struct list_head group_list;
unsigned int max_bw;
bool redrive;
};
/**
@ -452,6 +463,8 @@ struct tb_path {
* ICM to send driver ready message to the firmware.
* @start: Starts the domain
* @stop: Stops the domain
* @deinit: Perform any cleanup after the domain is stopped but before
* it is unregistered. Called without @tb->lock taken. Optional.
* @suspend_noirq: Connection manager specific suspend_noirq
* @resume_noirq: Connection manager specific resume_noirq
* @suspend: Connection manager specific suspend
@ -483,8 +496,9 @@ struct tb_path {
*/
struct tb_cm_ops {
int (*driver_ready)(struct tb *tb);
int (*start)(struct tb *tb);
int (*start)(struct tb *tb, bool reset);
void (*stop)(struct tb *tb);
void (*deinit)(struct tb *tb);
int (*suspend_noirq)(struct tb *tb);
int (*resume_noirq)(struct tb *tb);
int (*suspend)(struct tb *tb);
@ -735,10 +749,10 @@ static inline int tb_port_write(struct tb_port *port, const void *buffer,
struct tb *icm_probe(struct tb_nhi *nhi);
struct tb *tb_probe(struct tb_nhi *nhi);
extern struct device_type tb_domain_type;
extern struct device_type tb_retimer_type;
extern struct device_type tb_switch_type;
extern struct device_type usb4_port_device_type;
extern const struct device_type tb_domain_type;
extern const struct device_type tb_retimer_type;
extern const struct device_type tb_switch_type;
extern const struct device_type usb4_port_device_type;
int tb_domain_init(void);
void tb_domain_exit(void);
@ -746,7 +760,7 @@ int tb_xdomain_init(void);
void tb_xdomain_exit(void);
struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize);
int tb_domain_add(struct tb *tb);
int tb_domain_add(struct tb *tb, bool reset);
void tb_domain_remove(struct tb *tb);
int tb_domain_suspend_noirq(struct tb *tb);
int tb_domain_resume_noirq(struct tb *tb);
@ -1150,6 +1164,7 @@ struct tb_path *tb_path_alloc(struct tb *tb, struct tb_port *src, int src_hopid,
void tb_path_free(struct tb_path *path);
int tb_path_activate(struct tb_path *path);
void tb_path_deactivate(struct tb_path *path);
int tb_path_deactivate_hop(struct tb_port *port, int hop_index);
bool tb_path_is_invalid(struct tb_path *path);
bool tb_path_port_on_path(const struct tb_path *path,
const struct tb_port *port);
@ -1169,6 +1184,7 @@ int tb_drom_read(struct tb_switch *sw);
int tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid);
int tb_lc_read_uuid(struct tb_switch *sw, u32 *uuid);
int tb_lc_reset_port(struct tb_port *port);
int tb_lc_configure_port(struct tb_port *port);
void tb_lc_unconfigure_port(struct tb_port *port);
int tb_lc_configure_xdomain(struct tb_port *port);
@ -1301,6 +1317,7 @@ void usb4_switch_remove_ports(struct tb_switch *sw);
int usb4_port_unlock(struct tb_port *port);
int usb4_port_hotplug_enable(struct tb_port *port);
int usb4_port_reset(struct tb_port *port);
int usb4_port_configure(struct tb_port *port);
void usb4_port_unconfigure(struct tb_port *port);
int usb4_port_configure_xdomain(struct tb_port *port, struct tb_xdomain *xd);

View File

@ -194,6 +194,8 @@ struct tb_regs_switch_header {
#define USB4_VERSION_MAJOR_MASK GENMASK(7, 5)
#define ROUTER_CS_1 0x01
#define ROUTER_CS_3 0x03
#define ROUTER_CS_3_V BIT(31)
#define ROUTER_CS_4 0x04
/* Used with the router cmuv field */
#define ROUTER_CS_4_CMUV_V1 0x10
@ -389,6 +391,7 @@ struct tb_regs_port_header {
#define PORT_CS_18_CSA BIT(22)
#define PORT_CS_18_TIP BIT(24)
#define PORT_CS_19 0x13
#define PORT_CS_19_DPR BIT(0)
#define PORT_CS_19_PC BIT(3)
#define PORT_CS_19_PID BIT(4)
#define PORT_CS_19_WOC BIT(16)
@ -584,6 +587,9 @@ struct tb_regs_hop {
#define TB_LC_POWER 0x740
/* Link controller registers */
#define TB_LC_PORT_MODE 0x26
#define TB_LC_PORT_MODE_DPR BIT(0)
#define TB_LC_CS_42 0x2a
#define TB_LC_CS_42_USB_PLUGGED BIT(31)

188
drivers/thunderbolt/trace.h Normal file
View File

@ -0,0 +1,188 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Thunderbolt tracing support
*
* Copyright (C) 2024, Intel Corporation
* Author: Mika Westerberg <mika.westerberg@linux.intel.com>
* Gil Fine <gil.fine@intel.com>
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM thunderbolt
#if !defined(TB_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ)
#define TB_TRACE_H_
#include <linux/trace_seq.h>
#include <linux/tracepoint.h>
#include "tb_msgs.h"
#define tb_cfg_type_name(type) { type, #type }
#define show_type_name(val) \
__print_symbolic(val, \
tb_cfg_type_name(TB_CFG_PKG_READ), \
tb_cfg_type_name(TB_CFG_PKG_WRITE), \
tb_cfg_type_name(TB_CFG_PKG_ERROR), \
tb_cfg_type_name(TB_CFG_PKG_NOTIFY_ACK), \
tb_cfg_type_name(TB_CFG_PKG_EVENT), \
tb_cfg_type_name(TB_CFG_PKG_XDOMAIN_REQ), \
tb_cfg_type_name(TB_CFG_PKG_XDOMAIN_RESP), \
tb_cfg_type_name(TB_CFG_PKG_OVERRIDE), \
tb_cfg_type_name(TB_CFG_PKG_RESET), \
tb_cfg_type_name(TB_CFG_PKG_ICM_EVENT), \
tb_cfg_type_name(TB_CFG_PKG_ICM_CMD), \
tb_cfg_type_name(TB_CFG_PKG_ICM_RESP))
#ifndef TB_TRACE_HELPERS
#define TB_TRACE_HELPERS
static inline const char *show_data_read_write(struct trace_seq *p,
const u32 *data)
{
const struct cfg_read_pkg *msg = (const struct cfg_read_pkg *)data;
const char *ret = trace_seq_buffer_ptr(p);
trace_seq_printf(p, "offset=%#x, len=%u, port=%d, config=%#x, seq=%d, ",
msg->addr.offset, msg->addr.length, msg->addr.port,
msg->addr.space, msg->addr.seq);
return ret;
}
static inline const char *show_data_error(struct trace_seq *p, const u32 *data)
{
const struct cfg_error_pkg *msg = (const struct cfg_error_pkg *)data;
const char *ret = trace_seq_buffer_ptr(p);
trace_seq_printf(p, "error=%#x, port=%d, plug=%#x, ", msg->error,
msg->port, msg->pg);
return ret;
}
static inline const char *show_data_event(struct trace_seq *p, const u32 *data)
{
const struct cfg_event_pkg *msg = (const struct cfg_event_pkg *)data;
const char *ret = trace_seq_buffer_ptr(p);
trace_seq_printf(p, "port=%d, unplug=%#x, ", msg->port, msg->unplug);
return ret;
}
static inline const char *show_route(struct trace_seq *p, const u32 *data)
{
const struct tb_cfg_header *header = (const struct tb_cfg_header *)data;
const char *ret = trace_seq_buffer_ptr(p);
trace_seq_printf(p, "route=%llx, ", tb_cfg_get_route(header));
return ret;
}
static inline const char *show_data(struct trace_seq *p, u8 type,
const u32 *data, u32 length)
{
const char *ret = trace_seq_buffer_ptr(p);
const char *prefix = "";
int i;
show_route(p, data);
switch (type) {
case TB_CFG_PKG_READ:
case TB_CFG_PKG_WRITE:
show_data_read_write(p, data);
break;
case TB_CFG_PKG_ERROR:
show_data_error(p, data);
break;
case TB_CFG_PKG_EVENT:
show_data_event(p, data);
break;
default:
break;
}
trace_seq_printf(p, "data=[");
for (i = 0; i < length; i++) {
trace_seq_printf(p, "%s0x%08x", prefix, data[i]);
prefix = ", ";
}
trace_seq_printf(p, "]");
trace_seq_putc(p, 0);
return ret;
}
#endif
DECLARE_EVENT_CLASS(tb_raw,
TP_PROTO(int index, u8 type, const void *data, size_t size),
TP_ARGS(index, type, data, size),
TP_STRUCT__entry(
__field(int, index)
__field(u8, type)
__field(size_t, size)
__dynamic_array(u32, data, size / 4)
),
TP_fast_assign(
__entry->index = index;
__entry->type = type;
__entry->size = size / 4;
memcpy(__get_dynamic_array(data), data, size);
),
TP_printk("type=%s, size=%zd, domain=%d, %s",
show_type_name(__entry->type), __entry->size, __entry->index,
show_data(p, __entry->type, __get_dynamic_array(data),
__entry->size)
)
);
DEFINE_EVENT(tb_raw, tb_tx,
TP_PROTO(int index, u8 type, const void *data, size_t size),
TP_ARGS(index, type, data, size)
);
DEFINE_EVENT(tb_raw, tb_event,
TP_PROTO(int index, u8 type, const void *data, size_t size),
TP_ARGS(index, type, data, size)
);
TRACE_EVENT(tb_rx,
TP_PROTO(int index, u8 type, const void *data, size_t size, bool dropped),
TP_ARGS(index, type, data, size, dropped),
TP_STRUCT__entry(
__field(int, index)
__field(u8, type)
__field(size_t, size)
__dynamic_array(u32, data, size / 4)
__field(bool, dropped)
),
TP_fast_assign(
__entry->index = index;
__entry->type = type;
__entry->size = size / 4;
memcpy(__get_dynamic_array(data), data, size);
__entry->dropped = dropped;
),
TP_printk("type=%s, dropped=%u, size=%zd, domain=%d, %s",
show_type_name(__entry->type), __entry->dropped,
__entry->size, __entry->index,
show_data(p, __entry->type, __get_dynamic_array(data),
__entry->size)
)
);
#endif /* TB_TRACE_H_ */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE trace
/* This part must be outside protection */
#include <trace/define_trace.h>

View File

@ -706,7 +706,7 @@ static int tb_dp_xchg_caps(struct tb_tunnel *tunnel)
"DP OUT maximum supported bandwidth %u Mb/s x%u = %u Mb/s\n",
out_rate, out_lanes, bw);
if (tb_port_path_direction_downstream(in, out))
if (tb_tunnel_direction_downstream(tunnel))
max_bw = tunnel->max_down;
else
max_bw = tunnel->max_up;
@ -831,7 +831,7 @@ static int tb_dp_bandwidth_alloc_mode_enable(struct tb_tunnel *tunnel)
* max_up/down fields. For discovery we just read what the
* estimation was set to.
*/
if (tb_port_path_direction_downstream(in, out))
if (tb_tunnel_direction_downstream(tunnel))
estimated_bw = tunnel->max_down;
else
estimated_bw = tunnel->max_up;
@ -926,12 +926,18 @@ static int tb_dp_activate(struct tb_tunnel *tunnel, bool active)
return 0;
}
/* max_bw is rounded up to next granularity */
/**
* tb_dp_bandwidth_mode_maximum_bandwidth() - Maximum possible bandwidth
* @tunnel: DP tunnel to check
* @max_bw_rounded: Maximum bandwidth in Mb/s rounded up to the next granularity
*
* Returns maximum possible bandwidth for this tunnel in Mb/s.
*/
static int tb_dp_bandwidth_mode_maximum_bandwidth(struct tb_tunnel *tunnel,
int *max_bw)
int *max_bw_rounded)
{
struct tb_port *in = tunnel->src_port;
int ret, rate, lanes, nrd_bw;
int ret, rate, lanes, max_bw;
u32 cap;
/*
@ -947,41 +953,26 @@ static int tb_dp_bandwidth_mode_maximum_bandwidth(struct tb_tunnel *tunnel,
return ret;
rate = tb_dp_cap_get_rate_ext(cap);
if (tb_dp_is_uhbr_rate(rate)) {
/*
* When UHBR is used there is no reduction in lanes so
* we can use this directly.
*/
lanes = tb_dp_cap_get_lanes(cap);
} else {
/*
* If there is no UHBR supported then check the
* non-reduced rate and lanes.
*/
ret = usb4_dp_port_nrd(in, &rate, &lanes);
if (ret)
return ret;
}
lanes = tb_dp_cap_get_lanes(cap);
nrd_bw = tb_dp_bandwidth(rate, lanes);
max_bw = tb_dp_bandwidth(rate, lanes);
if (max_bw) {
if (max_bw_rounded) {
ret = usb4_dp_port_granularity(in);
if (ret < 0)
return ret;
*max_bw = roundup(nrd_bw, ret);
*max_bw_rounded = roundup(max_bw, ret);
}
return nrd_bw;
return max_bw;
}
static int tb_dp_bandwidth_mode_consumed_bandwidth(struct tb_tunnel *tunnel,
int *consumed_up,
int *consumed_down)
{
struct tb_port *out = tunnel->dst_port;
struct tb_port *in = tunnel->src_port;
int ret, allocated_bw, max_bw;
int ret, allocated_bw, max_bw_rounded;
if (!usb4_dp_port_bandwidth_mode_enabled(in))
return -EOPNOTSUPP;
@ -995,13 +986,13 @@ static int tb_dp_bandwidth_mode_consumed_bandwidth(struct tb_tunnel *tunnel,
return ret;
allocated_bw = ret;
ret = tb_dp_bandwidth_mode_maximum_bandwidth(tunnel, &max_bw);
ret = tb_dp_bandwidth_mode_maximum_bandwidth(tunnel, &max_bw_rounded);
if (ret < 0)
return ret;
if (allocated_bw == max_bw)
if (allocated_bw == max_bw_rounded)
allocated_bw = ret;
if (tb_port_path_direction_downstream(in, out)) {
if (tb_tunnel_direction_downstream(tunnel)) {
*consumed_up = 0;
*consumed_down = allocated_bw;
} else {
@ -1015,7 +1006,6 @@ static int tb_dp_bandwidth_mode_consumed_bandwidth(struct tb_tunnel *tunnel,
static int tb_dp_allocated_bandwidth(struct tb_tunnel *tunnel, int *allocated_up,
int *allocated_down)
{
struct tb_port *out = tunnel->dst_port;
struct tb_port *in = tunnel->src_port;
/*
@ -1023,20 +1013,21 @@ static int tb_dp_allocated_bandwidth(struct tb_tunnel *tunnel, int *allocated_up
* Otherwise we read it from the DPRX.
*/
if (usb4_dp_port_bandwidth_mode_enabled(in) && tunnel->bw_mode) {
int ret, allocated_bw, max_bw;
int ret, allocated_bw, max_bw_rounded;
ret = usb4_dp_port_allocated_bandwidth(in);
if (ret < 0)
return ret;
allocated_bw = ret;
ret = tb_dp_bandwidth_mode_maximum_bandwidth(tunnel, &max_bw);
ret = tb_dp_bandwidth_mode_maximum_bandwidth(tunnel,
&max_bw_rounded);
if (ret < 0)
return ret;
if (allocated_bw == max_bw)
if (allocated_bw == max_bw_rounded)
allocated_bw = ret;
if (tb_port_path_direction_downstream(in, out)) {
if (tb_tunnel_direction_downstream(tunnel)) {
*allocated_up = 0;
*allocated_down = allocated_bw;
} else {
@ -1053,26 +1044,25 @@ static int tb_dp_allocated_bandwidth(struct tb_tunnel *tunnel, int *allocated_up
static int tb_dp_alloc_bandwidth(struct tb_tunnel *tunnel, int *alloc_up,
int *alloc_down)
{
struct tb_port *out = tunnel->dst_port;
struct tb_port *in = tunnel->src_port;
int max_bw, ret, tmp;
int max_bw_rounded, ret, tmp;
if (!usb4_dp_port_bandwidth_mode_enabled(in))
return -EOPNOTSUPP;
ret = tb_dp_bandwidth_mode_maximum_bandwidth(tunnel, &max_bw);
ret = tb_dp_bandwidth_mode_maximum_bandwidth(tunnel, &max_bw_rounded);
if (ret < 0)
return ret;
if (tb_port_path_direction_downstream(in, out)) {
tmp = min(*alloc_down, max_bw);
if (tb_tunnel_direction_downstream(tunnel)) {
tmp = min(*alloc_down, max_bw_rounded);
ret = usb4_dp_port_allocate_bandwidth(in, tmp);
if (ret)
return ret;
*alloc_down = tmp;
*alloc_up = 0;
} else {
tmp = min(*alloc_up, max_bw);
tmp = min(*alloc_up, max_bw_rounded);
ret = usb4_dp_port_allocate_bandwidth(in, tmp);
if (ret)
return ret;
@ -1150,17 +1140,16 @@ static int tb_dp_read_cap(struct tb_tunnel *tunnel, unsigned int cap, u32 *rate,
static int tb_dp_maximum_bandwidth(struct tb_tunnel *tunnel, int *max_up,
int *max_down)
{
struct tb_port *in = tunnel->src_port;
int ret;
if (!usb4_dp_port_bandwidth_mode_enabled(in))
if (!usb4_dp_port_bandwidth_mode_enabled(tunnel->src_port))
return -EOPNOTSUPP;
ret = tb_dp_bandwidth_mode_maximum_bandwidth(tunnel, NULL);
if (ret < 0)
return ret;
if (tb_port_path_direction_downstream(in, tunnel->dst_port)) {
if (tb_tunnel_direction_downstream(tunnel)) {
*max_up = 0;
*max_down = ret;
} else {
@ -1174,8 +1163,7 @@ static int tb_dp_maximum_bandwidth(struct tb_tunnel *tunnel, int *max_up,
static int tb_dp_consumed_bandwidth(struct tb_tunnel *tunnel, int *consumed_up,
int *consumed_down)
{
struct tb_port *in = tunnel->src_port;
const struct tb_switch *sw = in->sw;
const struct tb_switch *sw = tunnel->src_port->sw;
u32 rate = 0, lanes = 0;
int ret;
@ -1196,17 +1184,13 @@ static int tb_dp_consumed_bandwidth(struct tb_tunnel *tunnel, int *consumed_up,
/*
* Then see if the DPRX negotiation is ready and if yes
* return that bandwidth (it may be smaller than the
* reduced one). Otherwise return the remote (possibly
* reduced) caps.
* reduced one). According to VESA spec, the DPRX
* negotiation shall compete in 5 seconds after tunnel
* established. We give it 100ms extra just in case.
*/
ret = tb_dp_wait_dprx(tunnel, 150);
if (ret) {
if (ret == -ETIMEDOUT)
ret = tb_dp_read_cap(tunnel, DP_REMOTE_CAP,
&rate, &lanes);
if (ret)
return ret;
}
ret = tb_dp_wait_dprx(tunnel, 5100);
if (ret)
return ret;
ret = tb_dp_read_cap(tunnel, DP_COMMON_CAP, &rate, &lanes);
if (ret)
return ret;
@ -1221,7 +1205,7 @@ static int tb_dp_consumed_bandwidth(struct tb_tunnel *tunnel, int *consumed_up,
return 0;
}
if (tb_port_path_direction_downstream(in, tunnel->dst_port)) {
if (tb_tunnel_direction_downstream(tunnel)) {
*consumed_up = 0;
*consumed_down = tb_dp_bandwidth(rate, lanes);
} else {

View File

@ -139,6 +139,12 @@ static inline bool tb_tunnel_is_usb3(const struct tb_tunnel *tunnel)
return tunnel->type == TB_TUNNEL_USB3;
}
static inline bool tb_tunnel_direction_downstream(const struct tb_tunnel *tunnel)
{
return tb_port_path_direction_downstream(tunnel->src_port,
tunnel->dst_port);
}
const char *tb_tunnel_type_name(const struct tb_tunnel *tunnel);
#define __TB_TUNNEL_PRINT(level, tunnel, fmt, arg...) \

View File

@ -1113,6 +1113,45 @@ int usb4_port_hotplug_enable(struct tb_port *port)
return tb_port_write(port, &val, TB_CFG_PORT, ADP_CS_5, 1);
}
/**
* usb4_port_reset() - Issue downstream port reset
* @port: USB4 port to reset
*
* Issues downstream port reset to @port.
*/
int usb4_port_reset(struct tb_port *port)
{
int ret;
u32 val;
if (!port->cap_usb4)
return -EINVAL;
ret = tb_port_read(port, &val, TB_CFG_PORT,
port->cap_usb4 + PORT_CS_19, 1);
if (ret)
return ret;
val |= PORT_CS_19_DPR;
ret = tb_port_write(port, &val, TB_CFG_PORT,
port->cap_usb4 + PORT_CS_19, 1);
if (ret)
return ret;
fsleep(10000);
ret = tb_port_read(port, &val, TB_CFG_PORT,
port->cap_usb4 + PORT_CS_19, 1);
if (ret)
return ret;
val &= ~PORT_CS_19_DPR;
return tb_port_write(port, &val, TB_CFG_PORT,
port->cap_usb4 + PORT_CS_19, 1);
}
static int usb4_port_set_configured(struct tb_port *port, bool configured)
{
int ret;
@ -2819,8 +2858,10 @@ static int usb4_dp_port_wait_and_clear_cm_ack(struct tb_port *port,
usleep_range(50, 100);
} while (ktime_before(ktime_get(), end));
if (val & ADP_DP_CS_8_DR)
if (val & ADP_DP_CS_8_DR) {
tb_port_warn(port, "timeout waiting for DPTX request to clear\n");
return -ETIMEDOUT;
}
ret = tb_port_read(port, &val, TB_CFG_PORT,
port->cap_adap + ADP_DP_CS_2, 1);

View File

@ -243,7 +243,7 @@ static void usb4_port_device_release(struct device *dev)
kfree(usb4);
}
struct device_type usb4_port_device_type = {
const struct device_type usb4_port_device_type = {
.name = "usb4_port",
.groups = usb4_port_device_groups,
.release = usb4_port_device_release,

View File

@ -997,12 +997,12 @@ static void tb_service_release(struct device *dev)
struct tb_xdomain *xd = tb_service_parent(svc);
tb_service_debugfs_remove(svc);
ida_simple_remove(&xd->service_ids, svc->id);
ida_free(&xd->service_ids, svc->id);
kfree(svc->key);
kfree(svc);
}
struct device_type tb_service_type = {
const struct device_type tb_service_type = {
.name = "thunderbolt_service",
.groups = tb_service_attr_groups,
.uevent = tb_service_uevent,
@ -1099,7 +1099,7 @@ static void enumerate_services(struct tb_xdomain *xd)
break;
}
id = ida_simple_get(&xd->service_ids, 0, 0, GFP_KERNEL);
id = ida_alloc(&xd->service_ids, GFP_KERNEL);
if (id < 0) {
kfree(svc->key);
kfree(svc);
@ -1791,13 +1791,13 @@ static ssize_t rx_lanes_show(struct device *dev, struct device_attribute *attr,
switch (xd->link_width) {
case TB_LINK_WIDTH_SINGLE:
case TB_LINK_WIDTH_ASYM_RX:
case TB_LINK_WIDTH_ASYM_TX:
width = 1;
break;
case TB_LINK_WIDTH_DUAL:
width = 2;
break;
case TB_LINK_WIDTH_ASYM_TX:
case TB_LINK_WIDTH_ASYM_RX:
width = 3;
break;
default:
@ -1817,13 +1817,13 @@ static ssize_t tx_lanes_show(struct device *dev, struct device_attribute *attr,
switch (xd->link_width) {
case TB_LINK_WIDTH_SINGLE:
case TB_LINK_WIDTH_ASYM_TX:
case TB_LINK_WIDTH_ASYM_RX:
width = 1;
break;
case TB_LINK_WIDTH_DUAL:
width = 2;
break;
case TB_LINK_WIDTH_ASYM_RX:
case TB_LINK_WIDTH_ASYM_TX:
width = 3;
break;
default:
@ -1893,7 +1893,7 @@ static const struct dev_pm_ops tb_xdomain_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(tb_xdomain_suspend, tb_xdomain_resume)
};
struct device_type tb_xdomain_type = {
const struct device_type tb_xdomain_type = {
.name = "thunderbolt_xdomain",
.release = tb_xdomain_release,
.pm = &tb_xdomain_pm_ops,

View File

@ -435,7 +435,7 @@ int cdns_drd_init(struct cdns *cdns)
writel(1, &cdns->otg_v1_regs->simulate);
cdns->version = CDNS3_CONTROLLER_V1;
} else {
dev_err(cdns->dev, "not supporte DID=0x%08x\n", state);
dev_err(cdns->dev, "not supported DID=0x%08x\n", state);
return -EINVAL;
}

View File

@ -116,3 +116,30 @@ config USB_AUTOSUSPEND_DELAY
The default value Linux has always had is 2 seconds. Change
this value if you want a different delay and cannot modify
the command line or module parameter.
config USB_DEFAULT_AUTHORIZATION_MODE
int "Default authorization mode for USB devices"
range 0 2
default 1
depends on USB
help
Select the default USB device authorization mode. Can be overridden
with usbcore.authorized_default command line or module parameter.
This option allows you to choose whether USB devices that are
connected to the system can be used by default, or if they are
locked down.
With value 0 all connected USB devices with the exception of root
hub require user space authorization before they can be used.
With value 1 (default) no user space authorization is required to
use connected USB devices.
With value 2 all connected USB devices with exception of internal
USB devices require user space authorization before they can be
used. Note that in this mode the differentiation between internal
and external USB devices relies on ACPI, and on systems without
ACPI selecting value 2 is analogous to selecting value 0.
If unsure, keep the default value.

View File

@ -1710,9 +1710,7 @@ int usb_autoresume_device(struct usb_device *udev)
{
int status;
status = pm_runtime_get_sync(&udev->dev);
if (status < 0)
pm_runtime_put_sync(&udev->dev);
status = pm_runtime_resume_and_get(&udev->dev);
dev_vdbg(&udev->dev, "%s: cnt %d -> %d\n",
__func__, atomic_read(&udev->dev.power.usage_count),
status);
@ -1818,9 +1816,7 @@ int usb_autopm_get_interface(struct usb_interface *intf)
{
int status;
status = pm_runtime_get_sync(&intf->dev);
if (status < 0)
pm_runtime_put_sync(&intf->dev);
status = pm_runtime_resume_and_get(&intf->dev);
dev_vdbg(&intf->dev, "%s: cnt %d -> %d\n",
__func__, atomic_read(&intf->dev.power.usage_count),
status);

View File

@ -141,7 +141,7 @@ static void ep_device_release(struct device *dev)
kfree(ep_dev);
}
struct device_type usb_ep_device_type = {
const struct device_type usb_ep_device_type = {
.name = "usb_endpoint",
.release = ep_device_release,
};

View File

@ -357,12 +357,10 @@ static const u8 ss_rh_config_descriptor[] = {
#define USB_AUTHORIZE_ALL 1
#define USB_AUTHORIZE_INTERNAL 2
static int authorized_default = USB_AUTHORIZE_WIRED;
static int authorized_default = CONFIG_USB_DEFAULT_AUTHORIZATION_MODE;
module_param(authorized_default, int, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(authorized_default,
"Default USB device authorization: 0 is not authorized, 1 is "
"authorized, 2 is authorized for internal devices, -1 is "
"authorized (default, same as 1)");
"Default USB device authorization: 0 is not authorized, 1 is authorized (default), 2 is authorized for internal devices, -1 is authorized (same as 1)");
/*-------------------------------------------------------------------------*/
/**
@ -2795,10 +2793,16 @@ int usb_add_hcd(struct usb_hcd *hcd,
struct usb_device *rhdev;
struct usb_hcd *shared_hcd;
if (!hcd->skip_phy_initialization && usb_hcd_is_primary_hcd(hcd)) {
hcd->phy_roothub = usb_phy_roothub_alloc(hcd->self.sysdev);
if (IS_ERR(hcd->phy_roothub))
return PTR_ERR(hcd->phy_roothub);
if (!hcd->skip_phy_initialization) {
if (usb_hcd_is_primary_hcd(hcd)) {
hcd->phy_roothub = usb_phy_roothub_alloc(hcd->self.sysdev);
if (IS_ERR(hcd->phy_roothub))
return PTR_ERR(hcd->phy_roothub);
} else {
hcd->phy_roothub = usb_phy_roothub_alloc_usb3_phy(hcd->self.sysdev);
if (IS_ERR(hcd->phy_roothub))
return PTR_ERR(hcd->phy_roothub);
}
retval = usb_phy_roothub_init(hcd->phy_roothub);
if (retval)

View File

@ -37,6 +37,7 @@
#include <asm/byteorder.h>
#include "hub.h"
#include "phy.h"
#include "otg_productlist.h"
#define USB_VENDOR_GENESYS_LOGIC 0x05e3
@ -634,6 +635,34 @@ static int hub_ext_port_status(struct usb_hub *hub, int port1, int type,
ret = 0;
}
mutex_unlock(&hub->status_mutex);
/*
* There is no need to lock status_mutex here, because status_mutex
* protects hub->status, and the phy driver only checks the port
* status without changing the status.
*/
if (!ret) {
struct usb_device *hdev = hub->hdev;
/*
* Only roothub will be notified of connection changes,
* since the USB PHY only cares about changes at the next
* level.
*/
if (is_root_hub(hdev)) {
struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
bool connect;
bool connect_change;
connect_change = *change & USB_PORT_STAT_C_CONNECTION;
connect = *status & USB_PORT_STAT_CONNECTION;
if (connect_change && connect)
usb_phy_roothub_notify_connect(hcd->phy_roothub, port1 - 1);
else if (connect_change)
usb_phy_roothub_notify_disconnect(hcd->phy_roothub, port1 - 1);
}
}
return ret;
}

View File

@ -1198,6 +1198,8 @@ EXPORT_SYMBOL_GPL(usb_get_status);
* same status code used to report a true stall.
*
* This call is synchronous, and may not be used in an interrupt context.
* If a thread in your driver uses this call, make sure your disconnect()
* method can wait for it to complete.
*
* Return: Zero on success, or else the status code returned by the
* underlying usb_control_msg() call.
@ -1516,7 +1518,8 @@ void usb_enable_interface(struct usb_device *dev,
* This call is synchronous, and may not be used in an interrupt context.
* Also, drivers must not change altsettings while urbs are scheduled for
* endpoints in that interface; all such urbs must first be completed
* (perhaps forced by unlinking).
* (perhaps forced by unlinking). If a thread in your driver uses this call,
* make sure your disconnect() method can wait for it to complete.
*
* Return: Zero on success, or else the status code returned by the
* underlying usb_control_msg() call.
@ -1849,7 +1852,7 @@ static int usb_if_uevent(const struct device *dev, struct kobj_uevent_env *env)
return 0;
}
struct device_type usb_if_device_type = {
const struct device_type usb_if_device_type = {
.name = "usb_interface",
.release = usb_release_interface,
.uevent = usb_if_uevent,

View File

@ -8,6 +8,7 @@
*/
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/usb/of.h>
/**
@ -75,6 +76,76 @@ bool usb_of_has_combined_node(struct usb_device *udev)
}
EXPORT_SYMBOL_GPL(usb_of_has_combined_node);
static bool usb_of_has_devices_or_graph(const struct usb_device *hub)
{
const struct device_node *np = hub->dev.of_node;
struct device_node *child;
if (of_graph_is_present(np))
return true;
for_each_child_of_node(np, child)
if (of_property_present(child, "reg"))
return true;
return false;
}
/**
* usb_of_get_connect_type() - get a USB hub's port connect_type
* @hub: hub to which port is for @port1
* @port1: one-based index of port
*
* Get the connect_type of @port1 based on the device node for @hub. If the
* port is described in the OF graph, the connect_type is "hotplug". If the
* @hub has a child device has with a 'reg' property equal to @port1 the
* connect_type is "hard-wired". If there isn't an OF graph or child node at
* all then the connect_type is "unknown". Otherwise, the port is considered
* "unused" because it isn't described at all.
*
* Return: A connect_type for @port1 based on the device node for @hub.
*/
enum usb_port_connect_type usb_of_get_connect_type(struct usb_device *hub, int port1)
{
struct device_node *np, *child, *ep, *remote_np;
enum usb_port_connect_type connect_type;
/* Only set connect_type if binding has ports/hardwired devices. */
if (!usb_of_has_devices_or_graph(hub))
return USB_PORT_CONNECT_TYPE_UNKNOWN;
/* Assume port is unused if there's a graph or a child node. */
connect_type = USB_PORT_NOT_USED;
np = hub->dev.of_node;
/*
* Hotplug ports are connected to an available remote node, e.g.
* usb-a-connector compatible node, in the OF graph.
*/
if (of_graph_is_present(np)) {
ep = of_graph_get_endpoint_by_regs(np, port1, -1);
if (ep) {
remote_np = of_graph_get_remote_port_parent(ep);
of_node_put(ep);
if (of_device_is_available(remote_np))
connect_type = USB_PORT_CONNECT_TYPE_HOT_PLUG;
of_node_put(remote_np);
}
}
/*
* Hard-wired ports are child nodes with a reg property corresponding
* to the port number, i.e. a usb device.
*/
child = usb_of_get_device_node(hub, port1);
if (of_device_is_available(child))
connect_type = USB_PORT_CONNECT_TYPE_HARD_WIRED;
of_node_put(child);
return connect_type;
}
EXPORT_SYMBOL_GPL(usb_of_get_connect_type);
/**
* usb_of_get_interface_node() - get a USB interface node
* @udev: USB device of interface

View File

@ -19,6 +19,30 @@ struct usb_phy_roothub {
struct list_head list;
};
/* Allocate the roothub_entry by specific name of phy */
static int usb_phy_roothub_add_phy_by_name(struct device *dev, const char *name,
struct list_head *list)
{
struct usb_phy_roothub *roothub_entry;
struct phy *phy;
phy = devm_of_phy_get(dev, dev->of_node, name);
if (IS_ERR(phy))
return PTR_ERR(phy);
roothub_entry = devm_kzalloc(dev, sizeof(*roothub_entry), GFP_KERNEL);
if (!roothub_entry)
return -ENOMEM;
INIT_LIST_HEAD(&roothub_entry->list);
roothub_entry->phy = phy;
list_add_tail(&roothub_entry->list, list);
return 0;
}
static int usb_phy_roothub_add_phy(struct device *dev, int index,
struct list_head *list)
{
@ -65,6 +89,9 @@ struct usb_phy_roothub *usb_phy_roothub_alloc(struct device *dev)
INIT_LIST_HEAD(&phy_roothub->list);
if (!usb_phy_roothub_add_phy_by_name(dev, "usb2-phy", &phy_roothub->list))
return phy_roothub;
for (i = 0; i < num_phys; i++) {
err = usb_phy_roothub_add_phy(dev, i, &phy_roothub->list);
if (err)
@ -75,6 +102,41 @@ struct usb_phy_roothub *usb_phy_roothub_alloc(struct device *dev)
}
EXPORT_SYMBOL_GPL(usb_phy_roothub_alloc);
/**
* usb_phy_roothub_alloc_usb3_phy - alloc the roothub
* @dev: the device of the host controller
*
* Allocate the usb phy roothub if the host use a generic usb3-phy.
*
* Return: On success, a pointer to the usb_phy_roothub. Otherwise,
* %NULL if no use usb3 phy or %-ENOMEM if out of memory.
*/
struct usb_phy_roothub *usb_phy_roothub_alloc_usb3_phy(struct device *dev)
{
struct usb_phy_roothub *phy_roothub;
int num_phys;
if (!IS_ENABLED(CONFIG_GENERIC_PHY))
return NULL;
num_phys = of_count_phandle_with_args(dev->of_node, "phys",
"#phy-cells");
if (num_phys <= 0)
return NULL;
phy_roothub = devm_kzalloc(dev, sizeof(*phy_roothub), GFP_KERNEL);
if (!phy_roothub)
return ERR_PTR(-ENOMEM);
INIT_LIST_HEAD(&phy_roothub->list);
if (!usb_phy_roothub_add_phy_by_name(dev, "usb3-phy", &phy_roothub->list))
return phy_roothub;
return NULL;
}
EXPORT_SYMBOL_GPL(usb_phy_roothub_alloc_usb3_phy);
int usb_phy_roothub_init(struct usb_phy_roothub *phy_roothub)
{
struct usb_phy_roothub *roothub_entry;
@ -172,6 +234,64 @@ int usb_phy_roothub_calibrate(struct usb_phy_roothub *phy_roothub)
}
EXPORT_SYMBOL_GPL(usb_phy_roothub_calibrate);
/**
* usb_phy_roothub_notify_connect() - connect notification
* @phy_roothub: the phy of roothub, if the host use a generic phy.
* @port: the port index for connect
*
* If the phy needs to get connection status, the callback can be used.
* Returns: %0 if successful, a negative error code otherwise
*/
int usb_phy_roothub_notify_connect(struct usb_phy_roothub *phy_roothub, int port)
{
struct usb_phy_roothub *roothub_entry;
struct list_head *head;
int err;
if (!phy_roothub)
return 0;
head = &phy_roothub->list;
list_for_each_entry(roothub_entry, head, list) {
err = phy_notify_connect(roothub_entry->phy, port);
if (err)
return err;
}
return 0;
}
EXPORT_SYMBOL_GPL(usb_phy_roothub_notify_connect);
/**
* usb_phy_roothub_notify_disconnect() - disconnect notification
* @phy_roothub: the phy of roothub, if the host use a generic phy.
* @port: the port index for disconnect
*
* If the phy needs to get connection status, the callback can be used.
* Returns: %0 if successful, a negative error code otherwise
*/
int usb_phy_roothub_notify_disconnect(struct usb_phy_roothub *phy_roothub, int port)
{
struct usb_phy_roothub *roothub_entry;
struct list_head *head;
int err;
if (!phy_roothub)
return 0;
head = &phy_roothub->list;
list_for_each_entry(roothub_entry, head, list) {
err = phy_notify_disconnect(roothub_entry->phy, port);
if (err)
return err;
}
return 0;
}
EXPORT_SYMBOL_GPL(usb_phy_roothub_notify_disconnect);
int usb_phy_roothub_power_on(struct usb_phy_roothub *phy_roothub)
{
struct usb_phy_roothub *roothub_entry;

View File

@ -12,6 +12,7 @@ struct device;
struct usb_phy_roothub;
struct usb_phy_roothub *usb_phy_roothub_alloc(struct device *dev);
struct usb_phy_roothub *usb_phy_roothub_alloc_usb3_phy(struct device *dev);
int usb_phy_roothub_init(struct usb_phy_roothub *phy_roothub);
int usb_phy_roothub_exit(struct usb_phy_roothub *phy_roothub);
@ -19,6 +20,8 @@ int usb_phy_roothub_exit(struct usb_phy_roothub *phy_roothub);
int usb_phy_roothub_set_mode(struct usb_phy_roothub *phy_roothub,
enum phy_mode mode);
int usb_phy_roothub_calibrate(struct usb_phy_roothub *phy_roothub);
int usb_phy_roothub_notify_connect(struct usb_phy_roothub *phy_roothub, int port);
int usb_phy_roothub_notify_disconnect(struct usb_phy_roothub *phy_roothub, int port);
int usb_phy_roothub_power_on(struct usb_phy_roothub *phy_roothub);
void usb_phy_roothub_power_off(struct usb_phy_roothub *phy_roothub);

View File

@ -11,6 +11,7 @@
#include <linux/slab.h>
#include <linux/pm_qos.h>
#include <linux/component.h>
#include <linux/usb/of.h>
#include "hub.h"
@ -429,7 +430,7 @@ static const struct dev_pm_ops usb_port_pm_ops = {
#endif
};
struct device_type usb_port_device_type = {
const struct device_type usb_port_device_type = {
.name = "usb_port",
.release = usb_port_device_release,
.pm = &usb_port_pm_ops,
@ -709,6 +710,7 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
return -ENOMEM;
}
port_dev->connect_type = usb_of_get_connect_type(hdev, port1);
hub->ports[port1 - 1] = port_dev;
port_dev->portnum = port1;
set_bit(port1, hub->power_bits);

View File

@ -273,9 +273,10 @@ static ssize_t avoid_reset_quirk_store(struct device *dev,
const char *buf, size_t count)
{
struct usb_device *udev = to_usb_device(dev);
int val, rc;
bool val;
int rc;
if (sscanf(buf, "%d", &val) != 1 || val < 0 || val > 1)
if (kstrtobool(buf, &val) != 0)
return -EINVAL;
rc = usb_lock_device_interruptible(udev);
if (rc < 0)
@ -322,13 +323,14 @@ static ssize_t persist_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct usb_device *udev = to_usb_device(dev);
int value, rc;
bool value;
int rc;
/* Hubs are always enabled for USB_PERSIST */
if (udev->descriptor.bDeviceClass == USB_CLASS_HUB)
return -EPERM;
if (sscanf(buf, "%d", &value) != 1)
if (kstrtobool(buf, &value) != 0)
return -EINVAL;
rc = usb_lock_device_interruptible(udev);
@ -739,14 +741,14 @@ static ssize_t authorized_store(struct device *dev,
{
ssize_t result;
struct usb_device *usb_dev = to_usb_device(dev);
unsigned val;
result = sscanf(buf, "%u\n", &val);
if (result != 1)
bool val;
if (kstrtobool(buf, &val) != 0)
result = -EINVAL;
else if (val == 0)
result = usb_deauthorize_device(usb_dev);
else
else if (val)
result = usb_authorize_device(usb_dev);
else
result = usb_deauthorize_device(usb_dev);
return result < 0 ? result : size;
}
static DEVICE_ATTR_IGNORE_LOCKDEP(authorized, S_IRUGO | S_IWUSR,
@ -847,16 +849,10 @@ static const struct attribute_group dev_string_attr_grp = {
.is_visible = dev_string_attrs_are_visible,
};
const struct attribute_group *usb_device_groups[] = {
&dev_attr_grp,
&dev_string_attr_grp,
NULL
};
/* Binary descriptors */
static ssize_t
read_descriptors(struct file *filp, struct kobject *kobj,
descriptors_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr,
char *buf, loff_t off, size_t count)
{
@ -878,7 +874,7 @@ read_descriptors(struct file *filp, struct kobject *kobj,
srclen = sizeof(struct usb_device_descriptor);
} else {
src = udev->rawdescriptors[cfgno];
srclen = __le16_to_cpu(udev->config[cfgno].desc.
srclen = le16_to_cpu(udev->config[cfgno].desc.
wTotalLength);
}
if (off < srclen) {
@ -893,11 +889,69 @@ read_descriptors(struct file *filp, struct kobject *kobj,
}
return count - nleft;
}
static BIN_ATTR_RO(descriptors, 18 + 65535); /* dev descr + max-size raw descriptor */
static struct bin_attribute dev_bin_attr_descriptors = {
.attr = {.name = "descriptors", .mode = 0444},
.read = read_descriptors,
.size = 18 + 65535, /* dev descr + max-size raw descriptor */
static ssize_t
bos_descriptors_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr,
char *buf, loff_t off, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct usb_device *udev = to_usb_device(dev);
struct usb_host_bos *bos = udev->bos;
struct usb_bos_descriptor *desc;
size_t desclen, n = 0;
if (bos) {
desc = bos->desc;
desclen = le16_to_cpu(desc->wTotalLength);
if (off < desclen) {
n = min(count, desclen - (size_t) off);
memcpy(buf, (void *) desc + off, n);
}
}
return n;
}
static BIN_ATTR_RO(bos_descriptors, 65535); /* max-size BOS */
/* When modifying this list, be sure to modify dev_bin_attrs_are_visible()
* accordingly.
*/
static struct bin_attribute *dev_bin_attrs[] = {
&bin_attr_descriptors,
&bin_attr_bos_descriptors,
NULL
};
static umode_t dev_bin_attrs_are_visible(struct kobject *kobj,
struct bin_attribute *a, int n)
{
struct device *dev = kobj_to_dev(kobj);
struct usb_device *udev = to_usb_device(dev);
/*
* There's no need to check if the descriptors attribute should
* be visible because all devices have a device descriptor. The
* bos_descriptors attribute should be visible if and only if
* the device has a BOS, so check if it exists here.
*/
if (a == &bin_attr_bos_descriptors) {
if (udev->bos == NULL)
return 0;
}
return a->attr.mode;
}
static const struct attribute_group dev_bin_attr_grp = {
.bin_attrs = dev_bin_attrs,
.is_bin_visible = dev_bin_attrs_are_visible,
};
const struct attribute_group *usb_device_groups[] = {
&dev_attr_grp,
&dev_string_attr_grp,
&dev_bin_attr_grp,
NULL
};
/*
@ -1015,10 +1069,6 @@ int usb_create_sysfs_dev_files(struct usb_device *udev)
struct device *dev = &udev->dev;
int retval;
retval = device_create_bin_file(dev, &dev_bin_attr_descriptors);
if (retval)
goto error;
retval = add_persist_attributes(dev);
if (retval)
goto error;
@ -1048,7 +1098,6 @@ void usb_remove_sysfs_dev_files(struct usb_device *udev)
remove_power_attributes(dev);
remove_persist_attributes(dev);
device_remove_bin_file(dev, &dev_bin_attr_descriptors);
}
/* Interface Association Descriptor fields */

View File

@ -142,12 +142,19 @@ int usb_acpi_set_power_state(struct usb_device *hdev, int index, bool enable)
}
EXPORT_SYMBOL_GPL(usb_acpi_set_power_state);
static enum usb_port_connect_type usb_acpi_get_connect_type(acpi_handle handle,
struct acpi_pld_info *pld)
/*
* Private to usb-acpi, all the core needs to know is that
* port_dev->location is non-zero when it has been set by the firmware.
*/
#define USB_ACPI_LOCATION_VALID (1 << 31)
static void
usb_acpi_get_connect_type(struct usb_port *port_dev, acpi_handle *handle)
{
enum usb_port_connect_type connect_type = USB_PORT_CONNECT_TYPE_UNKNOWN;
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *upc = NULL;
struct acpi_pld_info *pld = NULL;
acpi_status status;
/*
@ -158,6 +165,12 @@ static enum usb_port_connect_type usb_acpi_get_connect_type(acpi_handle handle,
* a usb device is directly hard-wired to the port. If no visible and
* no connectable, the port would be not used.
*/
status = acpi_get_physical_device_location(handle, &pld);
if (ACPI_SUCCESS(status) && pld)
port_dev->location = USB_ACPI_LOCATION_VALID |
pld->group_token << 8 | pld->group_position;
status = acpi_evaluate_object(handle, "_UPC", NULL, &buffer);
if (ACPI_FAILURE(status))
goto out;
@ -166,25 +179,22 @@ static enum usb_port_connect_type usb_acpi_get_connect_type(acpi_handle handle,
if (!upc || (upc->type != ACPI_TYPE_PACKAGE) || upc->package.count != 4)
goto out;
/* UPC states port is connectable */
if (upc->package.elements[0].integer.value)
if (pld->user_visible)
if (!pld)
; /* keep connect_type as unknown */
else if (pld->user_visible)
connect_type = USB_PORT_CONNECT_TYPE_HOT_PLUG;
else
connect_type = USB_PORT_CONNECT_TYPE_HARD_WIRED;
else if (!pld->user_visible)
else
connect_type = USB_PORT_NOT_USED;
out:
port_dev->connect_type = connect_type;
kfree(upc);
return connect_type;
ACPI_FREE(pld);
}
/*
* Private to usb-acpi, all the core needs to know is that
* port_dev->location is non-zero when it has been set by the firmware.
*/
#define USB_ACPI_LOCATION_VALID (1 << 31)
static struct acpi_device *
usb_acpi_get_companion_for_port(struct usb_port *port_dev)
{
@ -222,22 +232,12 @@ static struct acpi_device *
usb_acpi_find_companion_for_port(struct usb_port *port_dev)
{
struct acpi_device *adev;
struct acpi_pld_info *pld;
acpi_handle *handle;
acpi_status status;
adev = usb_acpi_get_companion_for_port(port_dev);
if (!adev)
return NULL;
handle = adev->handle;
status = acpi_get_physical_device_location(handle, &pld);
if (ACPI_SUCCESS(status) && pld) {
port_dev->location = USB_ACPI_LOCATION_VALID
| pld->group_token << 8 | pld->group_position;
port_dev->connect_type = usb_acpi_get_connect_type(handle, pld);
ACPI_FREE(pld);
}
usb_acpi_get_connect_type(port_dev, adev->handle);
return adev;
}

View File

@ -592,7 +592,7 @@ static char *usb_devnode(const struct device *dev,
usb_dev->bus->busnum, usb_dev->devnum);
}
struct device_type usb_device_type = {
const struct device_type usb_device_type = {
.name = "usb_device",
.release = usb_release_dev,
.uevent = usb_dev_uevent,

View File

@ -144,10 +144,10 @@ static inline int usb_disable_usb2_hardware_lpm(struct usb_device *udev)
extern const struct class usbmisc_class;
extern const struct bus_type usb_bus_type;
extern struct mutex usb_port_peer_mutex;
extern struct device_type usb_device_type;
extern struct device_type usb_if_device_type;
extern struct device_type usb_ep_device_type;
extern struct device_type usb_port_device_type;
extern const struct device_type usb_device_type;
extern const struct device_type usb_if_device_type;
extern const struct device_type usb_ep_device_type;
extern const struct device_type usb_port_device_type;
extern struct usb_device_driver usb_generic_driver;
static inline int is_usb_device(const struct device *dev)

View File

@ -131,7 +131,7 @@ config USB_DWC3_QCOM
tristate "Qualcomm Platform"
depends on ARCH_QCOM || COMPILE_TEST
depends on EXTCON || !EXTCON
depends on (OF || ACPI)
depends on OF
default USB_DWC3
help
Some Qualcomm SoCs use DesignWare Core IP for USB2/3

View File

@ -755,6 +755,7 @@ struct dwc3_ep {
#define DWC3_EP_PENDING_CLEAR_STALL BIT(11)
#define DWC3_EP_TXFIFO_RESIZED BIT(12)
#define DWC3_EP_DELAY_STOP BIT(13)
#define DWC3_EP_RESOURCE_ALLOCATED BIT(14)
/* This last one is specific to EP0 */
#define DWC3_EP0_DIR_IN BIT(31)
@ -1257,6 +1258,7 @@ struct dwc3 {
#define DWC31_REVISION_170A 0x3137302a
#define DWC31_REVISION_180A 0x3138302a
#define DWC31_REVISION_190A 0x3139302a
#define DWC31_REVISION_200A 0x3230302a
#define DWC32_REVISION_ANY 0x0
#define DWC32_REVISION_100A 0x3130302a

View File

@ -97,9 +97,15 @@
#define USBSS_VBUS_STAT_SESSVALID BIT(2)
#define USBSS_VBUS_STAT_VBUSVALID BIT(0)
/* Mask for PHY PLL REFCLK */
/* USB_PHY_CTRL register bits in CTRL_MMR */
#define PHY_CORE_VOLTAGE_MASK BIT(31)
#define PHY_PLL_REFCLK_MASK GENMASK(3, 0)
/* USB PHY2 register offsets */
#define USB_PHY_PLL_REG12 0x130
#define USB_PHY_PLL_LDO_REF_EN BIT(5)
#define USB_PHY_PLL_LDO_REF_EN_EN BIT(4)
#define DWC3_AM62_AUTOSUSPEND_DELAY 100
struct dwc3_am62 {
@ -162,6 +168,13 @@ static int phy_syscon_pll_refclk(struct dwc3_am62 *am62)
am62->offset = args.args[0];
/* Core voltage. PHY_CORE_VOLTAGE bit Recommended to be 0 always */
ret = regmap_update_bits(am62->syscon, am62->offset, PHY_CORE_VOLTAGE_MASK, 0);
if (ret) {
dev_err(dev, "failed to set phy core voltage\n");
return ret;
}
ret = regmap_update_bits(am62->syscon, am62->offset, PHY_PLL_REFCLK_MASK, am62->rate_code);
if (ret) {
dev_err(dev, "failed to set phy pll reference clock rate\n");
@ -176,8 +189,9 @@ static int dwc3_ti_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct device_node *node = pdev->dev.of_node;
struct dwc3_am62 *am62;
int i, ret;
unsigned long rate;
void __iomem *phy;
int i, ret;
u32 reg;
am62 = devm_kzalloc(dev, sizeof(*am62), GFP_KERNEL);
@ -219,6 +233,17 @@ static int dwc3_ti_probe(struct platform_device *pdev)
if (ret)
return ret;
/* Workaround Errata i2409 */
phy = devm_platform_ioremap_resource(pdev, 1);
if (IS_ERR(phy)) {
dev_err(dev, "can't map PHY IOMEM resource. Won't apply i2409 fix.\n");
phy = NULL;
} else {
reg = readl(phy + USB_PHY_PLL_REG12);
reg |= USB_PHY_PLL_LDO_REF_EN | USB_PHY_PLL_LDO_REF_EN_EN;
writel(reg, phy + USB_PHY_PLL_REG12);
}
/* VBUS divider select */
am62->vbus_divider = device_property_read_bool(dev, "ti,vbus-divider");
reg = dwc3_ti_readl(am62, USBSS_PHY_CONFIG);
@ -267,21 +292,15 @@ err_pm_disable:
return ret;
}
static int dwc3_ti_remove_core(struct device *dev, void *c)
{
struct platform_device *pdev = to_platform_device(dev);
platform_device_unregister(pdev);
return 0;
}
static void dwc3_ti_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct dwc3_am62 *am62 = platform_get_drvdata(pdev);
u32 reg;
device_for_each_child(dev, NULL, dwc3_ti_remove_core);
pm_runtime_get_sync(dev);
device_init_wakeup(dev, false);
of_platform_depopulate(dev);
/* Clear mode valid bit */
reg = dwc3_ti_readl(am62, USBSS_MODE_CONTROL);
@ -289,7 +308,6 @@ static void dwc3_ti_remove(struct platform_device *pdev)
dwc3_ti_writel(am62, USBSS_MODE_CONTROL, reg);
pm_runtime_put_sync(dev);
clk_disable_unprepare(am62->usb2_refclk);
pm_runtime_disable(dev);
pm_runtime_set_suspended(dev);
}

View File

@ -52,8 +52,7 @@ static int dwc3_of_simple_probe(struct platform_device *pdev)
if (of_device_is_compatible(np, "rockchip,rk3399-dwc3"))
simple->need_reset = true;
simple->resets = of_reset_control_array_get(np, false, true,
true);
simple->resets = of_reset_control_array_get_optional_exclusive(np);
if (IS_ERR(simple->resets)) {
ret = PTR_ERR(simple->resets);
dev_err(dev, "failed to get device resets, err=%d\n", ret);
@ -173,6 +172,7 @@ static const struct of_device_id of_dwc3_simple_match[] = {
{ .compatible = "sprd,sc9860-dwc3" },
{ .compatible = "allwinner,sun50i-h6-dwc3" },
{ .compatible = "hisilicon,hi3670-dwc3" },
{ .compatible = "hisilicon,hi3798mv200-dwc3" },
{ .compatible = "intel,keembay-dwc3" },
{ /* Sentinel */ }
};

View File

@ -4,7 +4,6 @@
* Inspired by dwc3-of-simple.c
*/
#include <linux/acpi.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/clk.h>
@ -53,22 +52,10 @@
#define APPS_USB_AVG_BW 0
#define APPS_USB_PEAK_BW MBps_to_icc(40)
struct dwc3_acpi_pdata {
u32 qscratch_base_offset;
u32 qscratch_base_size;
u32 dwc3_core_base_size;
int qusb2_phy_irq_index;
int dp_hs_phy_irq_index;
int dm_hs_phy_irq_index;
int ss_phy_irq_index;
bool is_urs;
};
struct dwc3_qcom {
struct device *dev;
void __iomem *qscratch_base;
struct platform_device *dwc3;
struct platform_device *urs_usb;
struct clk **clks;
int num_clocks;
struct reset_control *resets;
@ -84,8 +71,6 @@ struct dwc3_qcom {
struct notifier_block vbus_nb;
struct notifier_block host_nb;
const struct dwc3_acpi_pdata *acpi_pdata;
enum usb_dr_mode mode;
bool is_suspended;
bool pm_suspended;
@ -248,9 +233,6 @@ static int dwc3_qcom_interconnect_init(struct dwc3_qcom *qcom)
struct device *dev = qcom->dev;
int ret;
if (has_acpi_companion(dev))
return 0;
qcom->icc_path_ddr = of_icc_get(dev, "usb-ddr");
if (IS_ERR(qcom->icc_path_ddr)) {
return dev_err_probe(dev, PTR_ERR(qcom->icc_path_ddr),
@ -519,31 +501,13 @@ static void dwc3_qcom_select_utmi_clk(struct dwc3_qcom *qcom)
PIPE_UTMI_CLK_DIS);
}
static int dwc3_qcom_get_irq(struct platform_device *pdev,
const char *name, int num)
{
struct dwc3_qcom *qcom = platform_get_drvdata(pdev);
struct platform_device *pdev_irq = qcom->urs_usb ? qcom->urs_usb : pdev;
struct device_node *np = pdev->dev.of_node;
int ret;
if (np)
ret = platform_get_irq_byname_optional(pdev_irq, name);
else
ret = platform_get_irq_optional(pdev_irq, num);
return ret;
}
static int dwc3_qcom_setup_irq(struct platform_device *pdev)
{
struct dwc3_qcom *qcom = platform_get_drvdata(pdev);
const struct dwc3_acpi_pdata *pdata = qcom->acpi_pdata;
int irq;
int ret;
irq = dwc3_qcom_get_irq(pdev, "qusb2_phy",
pdata ? pdata->qusb2_phy_irq_index : -1);
irq = platform_get_irq_byname_optional(pdev, "qusb2_phy");
if (irq > 0) {
/* Keep wakeup interrupts disabled until suspend */
ret = devm_request_threaded_irq(qcom->dev, irq, NULL,
@ -557,8 +521,7 @@ static int dwc3_qcom_setup_irq(struct platform_device *pdev)
qcom->qusb2_phy_irq = irq;
}
irq = dwc3_qcom_get_irq(pdev, "dp_hs_phy_irq",
pdata ? pdata->dp_hs_phy_irq_index : -1);
irq = platform_get_irq_byname_optional(pdev, "dp_hs_phy_irq");
if (irq > 0) {
ret = devm_request_threaded_irq(qcom->dev, irq, NULL,
qcom_dwc3_resume_irq,
@ -571,8 +534,7 @@ static int dwc3_qcom_setup_irq(struct platform_device *pdev)
qcom->dp_hs_phy_irq = irq;
}
irq = dwc3_qcom_get_irq(pdev, "dm_hs_phy_irq",
pdata ? pdata->dm_hs_phy_irq_index : -1);
irq = platform_get_irq_byname_optional(pdev, "dm_hs_phy_irq");
if (irq > 0) {
ret = devm_request_threaded_irq(qcom->dev, irq, NULL,
qcom_dwc3_resume_irq,
@ -585,8 +547,7 @@ static int dwc3_qcom_setup_irq(struct platform_device *pdev)
qcom->dm_hs_phy_irq = irq;
}
irq = dwc3_qcom_get_irq(pdev, "ss_phy_irq",
pdata ? pdata->ss_phy_irq_index : -1);
irq = platform_get_irq_byname_optional(pdev, "ss_phy_irq");
if (irq > 0) {
ret = devm_request_threaded_irq(qcom->dev, irq, NULL,
qcom_dwc3_resume_irq,
@ -649,88 +610,6 @@ static int dwc3_qcom_clk_init(struct dwc3_qcom *qcom, int count)
return 0;
}
static const struct property_entry dwc3_qcom_acpi_properties[] = {
PROPERTY_ENTRY_STRING("dr_mode", "host"),
{}
};
static const struct software_node dwc3_qcom_swnode = {
.properties = dwc3_qcom_acpi_properties,
};
static int dwc3_qcom_acpi_register_core(struct platform_device *pdev)
{
struct dwc3_qcom *qcom = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
struct resource *res, *child_res = NULL;
struct platform_device *pdev_irq = qcom->urs_usb ? qcom->urs_usb :
pdev;
int irq;
int ret;
qcom->dwc3 = platform_device_alloc("dwc3", PLATFORM_DEVID_AUTO);
if (!qcom->dwc3)
return -ENOMEM;
qcom->dwc3->dev.parent = dev;
qcom->dwc3->dev.type = dev->type;
qcom->dwc3->dev.dma_mask = dev->dma_mask;
qcom->dwc3->dev.dma_parms = dev->dma_parms;
qcom->dwc3->dev.coherent_dma_mask = dev->coherent_dma_mask;
child_res = kcalloc(2, sizeof(*child_res), GFP_KERNEL);
if (!child_res) {
platform_device_put(qcom->dwc3);
return -ENOMEM;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "failed to get memory resource\n");
ret = -ENODEV;
goto out;
}
child_res[0].flags = res->flags;
child_res[0].start = res->start;
child_res[0].end = child_res[0].start +
qcom->acpi_pdata->dwc3_core_base_size;
irq = platform_get_irq(pdev_irq, 0);
if (irq < 0) {
ret = irq;
goto out;
}
child_res[1].flags = IORESOURCE_IRQ;
child_res[1].start = child_res[1].end = irq;
ret = platform_device_add_resources(qcom->dwc3, child_res, 2);
if (ret) {
dev_err(&pdev->dev, "failed to add resources\n");
goto out;
}
ret = device_add_software_node(&qcom->dwc3->dev, &dwc3_qcom_swnode);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add properties\n");
goto out;
}
ret = platform_device_add(qcom->dwc3);
if (ret) {
dev_err(&pdev->dev, "failed to add device\n");
device_remove_software_node(&qcom->dwc3->dev);
goto out;
}
kfree(child_res);
return 0;
out:
platform_device_put(qcom->dwc3);
kfree(child_res);
return ret;
}
static int dwc3_qcom_of_register_core(struct platform_device *pdev)
{
struct dwc3_qcom *qcom = platform_get_drvdata(pdev);
@ -763,57 +642,12 @@ node_put:
return ret;
}
static struct platform_device *dwc3_qcom_create_urs_usb_platdev(struct device *dev)
{
struct platform_device *urs_usb = NULL;
struct fwnode_handle *fwh;
struct acpi_device *adev;
char name[8];
int ret;
int id;
/* Figure out device id */
ret = sscanf(fwnode_get_name(dev->fwnode), "URS%d", &id);
if (!ret)
return NULL;
/* Find the child using name */
snprintf(name, sizeof(name), "USB%d", id);
fwh = fwnode_get_named_child_node(dev->fwnode, name);
if (!fwh)
return NULL;
adev = to_acpi_device_node(fwh);
if (!adev)
goto err_put_handle;
urs_usb = acpi_create_platform_device(adev, NULL);
if (IS_ERR_OR_NULL(urs_usb))
goto err_put_handle;
return urs_usb;
err_put_handle:
fwnode_handle_put(fwh);
return urs_usb;
}
static void dwc3_qcom_destroy_urs_usb_platdev(struct platform_device *urs_usb)
{
struct fwnode_handle *fwh = urs_usb->dev.fwnode;
platform_device_unregister(urs_usb);
fwnode_handle_put(fwh);
}
static int dwc3_qcom_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device *dev = &pdev->dev;
struct dwc3_qcom *qcom;
struct resource *res, *parent_res = NULL;
struct resource local_res;
struct resource *res;
int ret, i;
bool ignore_pipe_clk;
bool wakeup_source;
@ -825,14 +659,6 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, qcom);
qcom->dev = &pdev->dev;
if (has_acpi_companion(dev)) {
qcom->acpi_pdata = acpi_device_get_match_data(dev);
if (!qcom->acpi_pdata) {
dev_err(&pdev->dev, "no supporting ACPI device data\n");
return -EINVAL;
}
}
qcom->resets = devm_reset_control_array_get_optional_exclusive(dev);
if (IS_ERR(qcom->resets)) {
return dev_err_probe(&pdev->dev, PTR_ERR(qcom->resets),
@ -861,40 +687,16 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (np) {
parent_res = res;
} else {
memcpy(&local_res, res, sizeof(struct resource));
parent_res = &local_res;
parent_res->start = res->start +
qcom->acpi_pdata->qscratch_base_offset;
parent_res->end = parent_res->start +
qcom->acpi_pdata->qscratch_base_size;
if (qcom->acpi_pdata->is_urs) {
qcom->urs_usb = dwc3_qcom_create_urs_usb_platdev(dev);
if (IS_ERR_OR_NULL(qcom->urs_usb)) {
dev_err(dev, "failed to create URS USB platdev\n");
if (!qcom->urs_usb)
ret = -ENODEV;
else
ret = PTR_ERR(qcom->urs_usb);
goto clk_disable;
}
}
}
qcom->qscratch_base = devm_ioremap_resource(dev, parent_res);
qcom->qscratch_base = devm_ioremap_resource(dev, res);
if (IS_ERR(qcom->qscratch_base)) {
ret = PTR_ERR(qcom->qscratch_base);
goto free_urs;
goto clk_disable;
}
ret = dwc3_qcom_setup_irq(pdev);
if (ret) {
dev_err(dev, "failed to setup IRQs, err=%d\n", ret);
goto free_urs;
goto clk_disable;
}
/*
@ -906,14 +708,10 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
if (ignore_pipe_clk)
dwc3_qcom_select_utmi_clk(qcom);
if (np)
ret = dwc3_qcom_of_register_core(pdev);
else
ret = dwc3_qcom_acpi_register_core(pdev);
ret = dwc3_qcom_of_register_core(pdev);
if (ret) {
dev_err(dev, "failed to register DWC3 Core, err=%d\n", ret);
goto free_urs;
goto clk_disable;
}
ret = dwc3_qcom_interconnect_init(qcom);
@ -945,16 +743,8 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
interconnect_exit:
dwc3_qcom_interconnect_exit(qcom);
depopulate:
if (np) {
of_platform_depopulate(&pdev->dev);
} else {
device_remove_software_node(&qcom->dwc3->dev);
platform_device_del(qcom->dwc3);
}
of_platform_depopulate(&pdev->dev);
platform_device_put(qcom->dwc3);
free_urs:
if (qcom->urs_usb)
dwc3_qcom_destroy_urs_usb_platdev(qcom->urs_usb);
clk_disable:
for (i = qcom->num_clocks - 1; i >= 0; i--) {
clk_disable_unprepare(qcom->clks[i]);
@ -969,21 +759,12 @@ reset_assert:
static void dwc3_qcom_remove(struct platform_device *pdev)
{
struct dwc3_qcom *qcom = platform_get_drvdata(pdev);
struct device_node *np = pdev->dev.of_node;
struct device *dev = &pdev->dev;
int i;
if (np) {
of_platform_depopulate(&pdev->dev);
} else {
device_remove_software_node(&qcom->dwc3->dev);
platform_device_del(qcom->dwc3);
}
of_platform_depopulate(&pdev->dev);
platform_device_put(qcom->dwc3);
if (qcom->urs_usb)
dwc3_qcom_destroy_urs_usb_platdev(qcom->urs_usb);
for (i = qcom->num_clocks - 1; i >= 0; i--) {
clk_disable_unprepare(qcom->clks[i]);
clk_put(qcom->clks[i]);
@ -1053,38 +834,6 @@ static const struct of_device_id dwc3_qcom_of_match[] = {
};
MODULE_DEVICE_TABLE(of, dwc3_qcom_of_match);
#ifdef CONFIG_ACPI
static const struct dwc3_acpi_pdata sdm845_acpi_pdata = {
.qscratch_base_offset = SDM845_QSCRATCH_BASE_OFFSET,
.qscratch_base_size = SDM845_QSCRATCH_SIZE,
.dwc3_core_base_size = SDM845_DWC3_CORE_SIZE,
.qusb2_phy_irq_index = 1,
.dp_hs_phy_irq_index = 4,
.dm_hs_phy_irq_index = 3,
.ss_phy_irq_index = 2
};
static const struct dwc3_acpi_pdata sdm845_acpi_urs_pdata = {
.qscratch_base_offset = SDM845_QSCRATCH_BASE_OFFSET,
.qscratch_base_size = SDM845_QSCRATCH_SIZE,
.dwc3_core_base_size = SDM845_DWC3_CORE_SIZE,
.qusb2_phy_irq_index = 1,
.dp_hs_phy_irq_index = 4,
.dm_hs_phy_irq_index = 3,
.ss_phy_irq_index = 2,
.is_urs = true,
};
static const struct acpi_device_id dwc3_qcom_acpi_match[] = {
{ "QCOM2430", (unsigned long)&sdm845_acpi_pdata },
{ "QCOM0304", (unsigned long)&sdm845_acpi_urs_pdata },
{ "QCOM0497", (unsigned long)&sdm845_acpi_urs_pdata },
{ "QCOM04A6", (unsigned long)&sdm845_acpi_pdata },
{ },
};
MODULE_DEVICE_TABLE(acpi, dwc3_qcom_acpi_match);
#endif
static struct platform_driver dwc3_qcom_driver = {
.probe = dwc3_qcom_probe,
.remove_new = dwc3_qcom_remove,
@ -1092,7 +841,6 @@ static struct platform_driver dwc3_qcom_driver = {
.name = "dwc3-qcom",
.pm = &dwc3_qcom_dev_pm_ops,
.of_match_table = dwc3_qcom_of_match,
.acpi_match_table = ACPI_PTR(dwc3_qcom_acpi_match),
},
};

View File

@ -646,6 +646,7 @@ static int dwc3_ep0_set_config(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl)
return -EINVAL;
case USB_STATE_ADDRESS:
dwc3_gadget_start_config(dwc, 2);
dwc3_gadget_clear_tx_fifos(dwc);
ret = dwc3_ep0_delegate_req(dwc, ctrl);

View File

@ -519,77 +519,56 @@ static void dwc3_free_trb_pool(struct dwc3_ep *dep)
static int dwc3_gadget_set_xfer_resource(struct dwc3_ep *dep)
{
struct dwc3_gadget_ep_cmd_params params;
int ret;
if (dep->flags & DWC3_EP_RESOURCE_ALLOCATED)
return 0;
memset(&params, 0x00, sizeof(params));
params.param0 = DWC3_DEPXFERCFG_NUM_XFER_RES(1);
return dwc3_send_gadget_ep_cmd(dep, DWC3_DEPCMD_SETTRANSFRESOURCE,
ret = dwc3_send_gadget_ep_cmd(dep, DWC3_DEPCMD_SETTRANSFRESOURCE,
&params);
if (ret)
return ret;
dep->flags |= DWC3_EP_RESOURCE_ALLOCATED;
return 0;
}
/**
* dwc3_gadget_start_config - configure ep resources
* @dep: endpoint that is being enabled
* dwc3_gadget_start_config - reset endpoint resources
* @dwc: pointer to the DWC3 context
* @resource_index: DEPSTARTCFG.XferRscIdx value (must be 0 or 2)
*
* Issue a %DWC3_DEPCMD_DEPSTARTCFG command to @dep. After the command's
* completion, it will set Transfer Resource for all available endpoints.
* Set resource_index=0 to reset all endpoints' resources allocation. Do this as
* part of the power-on/soft-reset initialization.
*
* The assignment of transfer resources cannot perfectly follow the data book
* due to the fact that the controller driver does not have all knowledge of the
* configuration in advance. It is given this information piecemeal by the
* composite gadget framework after every SET_CONFIGURATION and
* SET_INTERFACE. Trying to follow the databook programming model in this
* scenario can cause errors. For two reasons:
*
* 1) The databook says to do %DWC3_DEPCMD_DEPSTARTCFG for every
* %USB_REQ_SET_CONFIGURATION and %USB_REQ_SET_INTERFACE (8.1.5). This is
* incorrect in the scenario of multiple interfaces.
*
* 2) The databook does not mention doing more %DWC3_DEPCMD_DEPXFERCFG for new
* endpoint on alt setting (8.1.6).
*
* The following simplified method is used instead:
*
* All hardware endpoints can be assigned a transfer resource and this setting
* will stay persistent until either a core reset or hibernation. So whenever we
* do a %DWC3_DEPCMD_DEPSTARTCFG(0) we can go ahead and do
* %DWC3_DEPCMD_DEPXFERCFG for every hardware endpoint as well. We are
* guaranteed that there are as many transfer resources as endpoints.
*
* This function is called for each endpoint when it is being enabled but is
* triggered only when called for EP0-out, which always happens first, and which
* should only happen in one of the above conditions.
* Set resource_index=2 to reset only non-control endpoints' resources. Do this
* on receiving the SET_CONFIGURATION request or hibernation resume.
*/
static int dwc3_gadget_start_config(struct dwc3_ep *dep)
int dwc3_gadget_start_config(struct dwc3 *dwc, unsigned int resource_index)
{
struct dwc3_gadget_ep_cmd_params params;
struct dwc3 *dwc;
u32 cmd;
int i;
int ret;
if (dep->number)
return 0;
if (resource_index != 0 && resource_index != 2)
return -EINVAL;
memset(&params, 0x00, sizeof(params));
cmd = DWC3_DEPCMD_DEPSTARTCFG;
dwc = dep->dwc;
cmd |= DWC3_DEPCMD_PARAM(resource_index);
ret = dwc3_send_gadget_ep_cmd(dep, cmd, &params);
ret = dwc3_send_gadget_ep_cmd(dwc->eps[0], cmd, &params);
if (ret)
return ret;
for (i = 0; i < DWC3_ENDPOINTS_NUM; i++) {
struct dwc3_ep *dep = dwc->eps[i];
if (!dep)
continue;
ret = dwc3_gadget_set_xfer_resource(dep);
if (ret)
return ret;
}
/* Reset resource allocation flags */
for (i = resource_index; i < dwc->num_eps && dwc->eps[i]; i++)
dwc->eps[i]->flags &= ~DWC3_EP_RESOURCE_ALLOCATED;
return 0;
}
@ -884,16 +863,18 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, unsigned int action)
ret = dwc3_gadget_resize_tx_fifos(dep);
if (ret)
return ret;
ret = dwc3_gadget_start_config(dep);
if (ret)
return ret;
}
ret = dwc3_gadget_set_ep_config(dep, action);
if (ret)
return ret;
if (!(dep->flags & DWC3_EP_RESOURCE_ALLOCATED)) {
ret = dwc3_gadget_set_xfer_resource(dep);
if (ret)
return ret;
}
if (!(dep->flags & DWC3_EP_ENABLED)) {
struct dwc3_trb *trb_st_hw;
struct dwc3_trb *trb_link;
@ -1047,7 +1028,7 @@ static int __dwc3_gadget_ep_disable(struct dwc3_ep *dep)
dep->stream_capable = false;
dep->type = 0;
mask = DWC3_EP_TXFIFO_RESIZED;
mask = DWC3_EP_TXFIFO_RESIZED | DWC3_EP_RESOURCE_ALLOCATED;
/*
* dwc3_remove_requests() can exit early if DWC3 EP delayed stop is
* set. Do not clear DEP flags, so that the end transfer command will
@ -2913,6 +2894,12 @@ static int __dwc3_gadget_start(struct dwc3 *dwc)
/* Start with SuperSpeed Default */
dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512);
ret = dwc3_gadget_start_config(dwc, 0);
if (ret) {
dev_err(dwc->dev, "failed to config endpoints\n");
return ret;
}
dep = dwc->eps[0];
dep->flags = 0;
ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT);
@ -3428,7 +3415,7 @@ static int dwc3_gadget_ep_reclaim_trb_sg(struct dwc3_ep *dep,
struct dwc3_request *req, const struct dwc3_event_depevt *event,
int status)
{
struct dwc3_trb *trb = &dep->trb_pool[dep->trb_dequeue];
struct dwc3_trb *trb;
struct scatterlist *sg = req->sg;
struct scatterlist *s;
unsigned int num_queued = req->num_queued_sgs;

View File

@ -119,6 +119,7 @@ int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request,
int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol);
void dwc3_ep0_send_delayed_status(struct dwc3 *dwc);
void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force, bool interrupt);
int dwc3_gadget_start_config(struct dwc3 *dwc, unsigned int resource_index);
/**
* dwc3_gadget_ep_get_transfer_index - Gets transfer index from HW

View File

@ -11,8 +11,52 @@
#include <linux/of.h>
#include <linux/platform_device.h>
#include "../host/xhci-port.h"
#include "../host/xhci-ext-caps.h"
#include "../host/xhci-caps.h"
#include "core.h"
#define XHCI_HCSPARAMS1 0x4
#define XHCI_PORTSC_BASE 0x400
/**
* dwc3_power_off_all_roothub_ports - Power off all Root hub ports
* @dwc: Pointer to our controller context structure
*/
static void dwc3_power_off_all_roothub_ports(struct dwc3 *dwc)
{
void __iomem *xhci_regs;
u32 op_regs_base;
int port_num;
u32 offset;
u32 reg;
int i;
/* xhci regs is not mapped yet, do it temperary here */
if (dwc->xhci_resources[0].start) {
xhci_regs = ioremap(dwc->xhci_resources[0].start, DWC3_XHCI_REGS_END);
if (!xhci_regs) {
dev_err(dwc->dev, "Failed to ioremap xhci_regs\n");
return;
}
op_regs_base = HC_LENGTH(readl(xhci_regs));
reg = readl(xhci_regs + XHCI_HCSPARAMS1);
port_num = HCS_MAX_PORTS(reg);
for (i = 1; i <= port_num; i++) {
offset = op_regs_base + XHCI_PORTSC_BASE + 0x10 * (i - 1);
reg = readl(xhci_regs + offset);
reg &= ~PORT_POWER;
writel(reg, xhci_regs + offset);
}
iounmap(xhci_regs);
} else {
dev_err(dwc->dev, "xhci base reg invalid\n");
}
}
static void dwc3_host_fill_xhci_irq_res(struct dwc3 *dwc,
int irq, char *name)
{
@ -66,6 +110,12 @@ int dwc3_host_init(struct dwc3 *dwc)
int ret, irq;
int prop_idx = 0;
/*
* Some platforms need to power off all Root hub ports immediately after DWC3 set to host
* mode to avoid VBUS glitch happen when xhci get reset later.
*/
dwc3_power_off_all_roothub_ports(dwc);
irq = dwc3_host_get_irq(dwc);
if (irq < 0)
return irq;

View File

@ -190,6 +190,7 @@ config USB_F_MASS_STORAGE
tristate
config USB_F_FS
select DMA_SHARED_BUFFER
tristate
config USB_F_UAC1

View File

@ -15,6 +15,9 @@
/* #define VERBOSE_DEBUG */
#include <linux/blkdev.h>
#include <linux/dma-buf.h>
#include <linux/dma-fence.h>
#include <linux/dma-resv.h>
#include <linux/pagemap.h>
#include <linux/export.h>
#include <linux/fs_parser.h>
@ -43,6 +46,8 @@
#define FUNCTIONFS_MAGIC 0xa647361 /* Chosen by a honest dice roll ;) */
MODULE_IMPORT_NS(DMA_BUF);
/* Reference counter handling */
static void ffs_data_get(struct ffs_data *ffs);
static void ffs_data_put(struct ffs_data *ffs);
@ -124,6 +129,25 @@ struct ffs_ep {
u8 num;
};
struct ffs_dmabuf_priv {
struct list_head entry;
struct kref ref;
struct ffs_data *ffs;
struct dma_buf_attachment *attach;
struct sg_table *sgt;
enum dma_data_direction dir;
spinlock_t lock;
u64 context;
struct usb_request *req; /* P: ffs->eps_lock */
struct usb_ep *ep; /* P: ffs->eps_lock */
};
struct ffs_dma_fence {
struct dma_fence base;
struct ffs_dmabuf_priv *priv;
struct work_struct work;
};
struct ffs_epfile {
/* Protects ep->ep and ep->req. */
struct mutex mutex;
@ -197,6 +221,11 @@ struct ffs_epfile {
unsigned char isoc; /* P: ffs->eps_lock */
unsigned char _pad;
/* Protects dmabufs */
struct mutex dmabufs_mutex;
struct list_head dmabufs; /* P: dmabufs_mutex */
atomic_t seqno;
};
struct ffs_buffer {
@ -934,6 +963,27 @@ static ssize_t __ffs_epfile_read_data(struct ffs_epfile *epfile,
return ret;
}
static struct ffs_ep *ffs_epfile_wait_ep(struct file *file)
{
struct ffs_epfile *epfile = file->private_data;
struct ffs_ep *ep;
int ret;
/* Wait for endpoint to be enabled */
ep = epfile->ep;
if (!ep) {
if (file->f_flags & O_NONBLOCK)
return ERR_PTR(-EAGAIN);
ret = wait_event_interruptible(
epfile->ffs->wait, (ep = epfile->ep));
if (ret)
return ERR_PTR(-EINTR);
}
return ep;
}
static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
{
struct ffs_epfile *epfile = file->private_data;
@ -947,17 +997,9 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
if (WARN_ON(epfile->ffs->state != FFS_ACTIVE))
return -ENODEV;
/* Wait for endpoint to be enabled */
ep = epfile->ep;
if (!ep) {
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
ret = wait_event_interruptible(
epfile->ffs->wait, (ep = epfile->ep));
if (ret)
return -EINTR;
}
ep = ffs_epfile_wait_ep(file);
if (IS_ERR(ep))
return PTR_ERR(ep);
/* Do we halt? */
halt = (!io_data->read == !epfile->in);
@ -1258,10 +1300,58 @@ static ssize_t ffs_epfile_read_iter(struct kiocb *kiocb, struct iov_iter *to)
return res;
}
static void ffs_dmabuf_release(struct kref *ref)
{
struct ffs_dmabuf_priv *priv = container_of(ref, struct ffs_dmabuf_priv, ref);
struct dma_buf_attachment *attach = priv->attach;
struct dma_buf *dmabuf = attach->dmabuf;
pr_vdebug("FFS DMABUF release\n");
dma_resv_lock(dmabuf->resv, NULL);
dma_buf_unmap_attachment(attach, priv->sgt, priv->dir);
dma_resv_unlock(dmabuf->resv);
dma_buf_detach(attach->dmabuf, attach);
dma_buf_put(dmabuf);
kfree(priv);
}
static void ffs_dmabuf_get(struct dma_buf_attachment *attach)
{
struct ffs_dmabuf_priv *priv = attach->importer_priv;
kref_get(&priv->ref);
}
static void ffs_dmabuf_put(struct dma_buf_attachment *attach)
{
struct ffs_dmabuf_priv *priv = attach->importer_priv;
kref_put(&priv->ref, ffs_dmabuf_release);
}
static int
ffs_epfile_release(struct inode *inode, struct file *file)
{
struct ffs_epfile *epfile = inode->i_private;
struct ffs_dmabuf_priv *priv, *tmp;
struct ffs_data *ffs = epfile->ffs;
mutex_lock(&epfile->dmabufs_mutex);
/* Close all attached DMABUFs */
list_for_each_entry_safe(priv, tmp, &epfile->dmabufs, entry) {
/* Cancel any pending transfer */
spin_lock_irq(&ffs->eps_lock);
if (priv->ep && priv->req)
usb_ep_dequeue(priv->ep, priv->req);
spin_unlock_irq(&ffs->eps_lock);
list_del(&priv->entry);
ffs_dmabuf_put(priv->attach);
}
mutex_unlock(&epfile->dmabufs_mutex);
__ffs_epfile_read_buffer_free(epfile);
ffs_data_closed(epfile->ffs);
@ -1269,6 +1359,356 @@ ffs_epfile_release(struct inode *inode, struct file *file)
return 0;
}
static void ffs_dmabuf_cleanup(struct work_struct *work)
{
struct ffs_dma_fence *dma_fence =
container_of(work, struct ffs_dma_fence, work);
struct ffs_dmabuf_priv *priv = dma_fence->priv;
struct dma_buf_attachment *attach = priv->attach;
struct dma_fence *fence = &dma_fence->base;
ffs_dmabuf_put(attach);
dma_fence_put(fence);
}
static void ffs_dmabuf_signal_done(struct ffs_dma_fence *dma_fence, int ret)
{
struct ffs_dmabuf_priv *priv = dma_fence->priv;
struct dma_fence *fence = &dma_fence->base;
bool cookie = dma_fence_begin_signalling();
dma_fence_get(fence);
fence->error = ret;
dma_fence_signal(fence);
dma_fence_end_signalling(cookie);
/*
* The fence will be unref'd in ffs_dmabuf_cleanup.
* It can't be done here, as the unref functions might try to lock
* the resv object, which would deadlock.
*/
INIT_WORK(&dma_fence->work, ffs_dmabuf_cleanup);
queue_work(priv->ffs->io_completion_wq, &dma_fence->work);
}
static void ffs_epfile_dmabuf_io_complete(struct usb_ep *ep,
struct usb_request *req)
{
pr_vdebug("FFS: DMABUF transfer complete, status=%d\n", req->status);
ffs_dmabuf_signal_done(req->context, req->status);
usb_ep_free_request(ep, req);
}
static const char *ffs_dmabuf_get_driver_name(struct dma_fence *fence)
{
return "functionfs";
}
static const char *ffs_dmabuf_get_timeline_name(struct dma_fence *fence)
{
return "";
}
static void ffs_dmabuf_fence_release(struct dma_fence *fence)
{
struct ffs_dma_fence *dma_fence =
container_of(fence, struct ffs_dma_fence, base);
kfree(dma_fence);
}
static const struct dma_fence_ops ffs_dmabuf_fence_ops = {
.get_driver_name = ffs_dmabuf_get_driver_name,
.get_timeline_name = ffs_dmabuf_get_timeline_name,
.release = ffs_dmabuf_fence_release,
};
static int ffs_dma_resv_lock(struct dma_buf *dmabuf, bool nonblock)
{
if (!nonblock)
return dma_resv_lock_interruptible(dmabuf->resv, NULL);
if (!dma_resv_trylock(dmabuf->resv))
return -EBUSY;
return 0;
}
static struct dma_buf_attachment *
ffs_dmabuf_find_attachment(struct ffs_epfile *epfile, struct dma_buf *dmabuf)
{
struct device *dev = epfile->ffs->gadget->dev.parent;
struct dma_buf_attachment *attach = NULL;
struct ffs_dmabuf_priv *priv;
mutex_lock(&epfile->dmabufs_mutex);
list_for_each_entry(priv, &epfile->dmabufs, entry) {
if (priv->attach->dev == dev
&& priv->attach->dmabuf == dmabuf) {
attach = priv->attach;
break;
}
}
if (attach)
ffs_dmabuf_get(attach);
mutex_unlock(&epfile->dmabufs_mutex);
return attach ?: ERR_PTR(-EPERM);
}
static int ffs_dmabuf_attach(struct file *file, int fd)
{
bool nonblock = file->f_flags & O_NONBLOCK;
struct ffs_epfile *epfile = file->private_data;
struct usb_gadget *gadget = epfile->ffs->gadget;
struct dma_buf_attachment *attach;
struct ffs_dmabuf_priv *priv;
enum dma_data_direction dir;
struct sg_table *sg_table;
struct dma_buf *dmabuf;
int err;
if (!gadget || !gadget->sg_supported)
return -EPERM;
dmabuf = dma_buf_get(fd);
if (IS_ERR(dmabuf))
return PTR_ERR(dmabuf);
attach = dma_buf_attach(dmabuf, gadget->dev.parent);
if (IS_ERR(attach)) {
err = PTR_ERR(attach);
goto err_dmabuf_put;
}
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) {
err = -ENOMEM;
goto err_dmabuf_detach;
}
dir = epfile->in ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
err = ffs_dma_resv_lock(dmabuf, nonblock);
if (err)
goto err_free_priv;
sg_table = dma_buf_map_attachment(attach, dir);
dma_resv_unlock(dmabuf->resv);
if (IS_ERR(sg_table)) {
err = PTR_ERR(sg_table);
goto err_free_priv;
}
attach->importer_priv = priv;
priv->sgt = sg_table;
priv->dir = dir;
priv->ffs = epfile->ffs;
priv->attach = attach;
spin_lock_init(&priv->lock);
kref_init(&priv->ref);
priv->context = dma_fence_context_alloc(1);
mutex_lock(&epfile->dmabufs_mutex);
list_add(&priv->entry, &epfile->dmabufs);
mutex_unlock(&epfile->dmabufs_mutex);
return 0;
err_free_priv:
kfree(priv);
err_dmabuf_detach:
dma_buf_detach(dmabuf, attach);
err_dmabuf_put:
dma_buf_put(dmabuf);
return err;
}
static int ffs_dmabuf_detach(struct file *file, int fd)
{
struct ffs_epfile *epfile = file->private_data;
struct ffs_data *ffs = epfile->ffs;
struct device *dev = ffs->gadget->dev.parent;
struct ffs_dmabuf_priv *priv, *tmp;
struct dma_buf *dmabuf;
int ret = -EPERM;
dmabuf = dma_buf_get(fd);
if (IS_ERR(dmabuf))
return PTR_ERR(dmabuf);
mutex_lock(&epfile->dmabufs_mutex);
list_for_each_entry_safe(priv, tmp, &epfile->dmabufs, entry) {
if (priv->attach->dev == dev
&& priv->attach->dmabuf == dmabuf) {
/* Cancel any pending transfer */
spin_lock_irq(&ffs->eps_lock);
if (priv->ep && priv->req)
usb_ep_dequeue(priv->ep, priv->req);
spin_unlock_irq(&ffs->eps_lock);
list_del(&priv->entry);
/* Unref the reference from ffs_dmabuf_attach() */
ffs_dmabuf_put(priv->attach);
ret = 0;
break;
}
}
mutex_unlock(&epfile->dmabufs_mutex);
dma_buf_put(dmabuf);
return ret;
}
static int ffs_dmabuf_transfer(struct file *file,
const struct usb_ffs_dmabuf_transfer_req *req)
{
bool nonblock = file->f_flags & O_NONBLOCK;
struct ffs_epfile *epfile = file->private_data;
struct dma_buf_attachment *attach;
struct ffs_dmabuf_priv *priv;
struct ffs_dma_fence *fence;
struct usb_request *usb_req;
struct dma_buf *dmabuf;
struct ffs_ep *ep;
bool cookie;
u32 seqno;
int ret;
if (req->flags & ~USB_FFS_DMABUF_TRANSFER_MASK)
return -EINVAL;
dmabuf = dma_buf_get(req->fd);
if (IS_ERR(dmabuf))
return PTR_ERR(dmabuf);
if (req->length > dmabuf->size || req->length == 0) {
ret = -EINVAL;
goto err_dmabuf_put;
}
attach = ffs_dmabuf_find_attachment(epfile, dmabuf);
if (IS_ERR(attach)) {
ret = PTR_ERR(attach);
goto err_dmabuf_put;
}
priv = attach->importer_priv;
ep = ffs_epfile_wait_ep(file);
if (IS_ERR(ep)) {
ret = PTR_ERR(ep);
goto err_attachment_put;
}
ret = ffs_dma_resv_lock(dmabuf, nonblock);
if (ret)
goto err_attachment_put;
/* Make sure we don't have writers */
if (!dma_resv_test_signaled(dmabuf->resv, DMA_RESV_USAGE_WRITE)) {
pr_vdebug("FFS WRITE fence is not signaled\n");
ret = -EBUSY;
goto err_resv_unlock;
}
/* If we're writing to the DMABUF, make sure we don't have readers */
if (epfile->in &&
!dma_resv_test_signaled(dmabuf->resv, DMA_RESV_USAGE_READ)) {
pr_vdebug("FFS READ fence is not signaled\n");
ret = -EBUSY;
goto err_resv_unlock;
}
ret = dma_resv_reserve_fences(dmabuf->resv, 1);
if (ret)
goto err_resv_unlock;
fence = kmalloc(sizeof(*fence), GFP_KERNEL);
if (!fence) {
ret = -ENOMEM;
goto err_resv_unlock;
}
fence->priv = priv;
spin_lock_irq(&epfile->ffs->eps_lock);
/* In the meantime, endpoint got disabled or changed. */
if (epfile->ep != ep) {
ret = -ESHUTDOWN;
goto err_fence_put;
}
usb_req = usb_ep_alloc_request(ep->ep, GFP_ATOMIC);
if (!usb_req) {
ret = -ENOMEM;
goto err_fence_put;
}
/*
* usb_ep_queue() guarantees that all transfers are processed in the
* order they are enqueued, so we can use a simple incrementing
* sequence number for the dma_fence.
*/
seqno = atomic_add_return(1, &epfile->seqno);
dma_fence_init(&fence->base, &ffs_dmabuf_fence_ops,
&priv->lock, priv->context, seqno);
dma_resv_add_fence(dmabuf->resv, &fence->base,
dma_resv_usage_rw(epfile->in));
dma_resv_unlock(dmabuf->resv);
/* Now that the dma_fence is in place, queue the transfer. */
usb_req->length = req->length;
usb_req->buf = NULL;
usb_req->sg = priv->sgt->sgl;
usb_req->num_sgs = sg_nents_for_len(priv->sgt->sgl, req->length);
usb_req->sg_was_mapped = true;
usb_req->context = fence;
usb_req->complete = ffs_epfile_dmabuf_io_complete;
cookie = dma_fence_begin_signalling();
ret = usb_ep_queue(ep->ep, usb_req, GFP_ATOMIC);
dma_fence_end_signalling(cookie);
if (!ret) {
priv->req = usb_req;
priv->ep = ep->ep;
} else {
pr_warn("FFS: Failed to queue DMABUF: %d\n", ret);
ffs_dmabuf_signal_done(fence, ret);
usb_ep_free_request(ep->ep, usb_req);
}
spin_unlock_irq(&epfile->ffs->eps_lock);
dma_buf_put(dmabuf);
return ret;
err_fence_put:
spin_unlock_irq(&epfile->ffs->eps_lock);
dma_fence_put(&fence->base);
err_resv_unlock:
dma_resv_unlock(dmabuf->resv);
err_attachment_put:
ffs_dmabuf_put(attach);
err_dmabuf_put:
dma_buf_put(dmabuf);
return ret;
}
static long ffs_epfile_ioctl(struct file *file, unsigned code,
unsigned long value)
{
@ -1279,17 +1719,48 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code,
if (WARN_ON(epfile->ffs->state != FFS_ACTIVE))
return -ENODEV;
/* Wait for endpoint to be enabled */
ep = epfile->ep;
if (!ep) {
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
switch (code) {
case FUNCTIONFS_DMABUF_ATTACH:
{
int fd;
ret = wait_event_interruptible(
epfile->ffs->wait, (ep = epfile->ep));
if (ret)
return -EINTR;
if (copy_from_user(&fd, (void __user *)value, sizeof(fd))) {
ret = -EFAULT;
break;
}
return ffs_dmabuf_attach(file, fd);
}
case FUNCTIONFS_DMABUF_DETACH:
{
int fd;
if (copy_from_user(&fd, (void __user *)value, sizeof(fd))) {
ret = -EFAULT;
break;
}
return ffs_dmabuf_detach(file, fd);
}
case FUNCTIONFS_DMABUF_TRANSFER:
{
struct usb_ffs_dmabuf_transfer_req req;
if (copy_from_user(&req, (void __user *)value, sizeof(req))) {
ret = -EFAULT;
break;
}
return ffs_dmabuf_transfer(file, &req);
}
default:
break;
}
/* Wait for endpoint to be enabled */
ep = ffs_epfile_wait_ep(file);
if (IS_ERR(ep))
return PTR_ERR(ep);
spin_lock_irq(&epfile->ffs->eps_lock);
@ -1863,6 +2334,8 @@ static int ffs_epfiles_create(struct ffs_data *ffs)
for (i = 1; i <= count; ++i, ++epfile) {
epfile->ffs = ffs;
mutex_init(&epfile->mutex);
mutex_init(&epfile->dmabufs_mutex);
INIT_LIST_HEAD(&epfile->dmabufs);
if (ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR)
sprintf(epfile->name, "ep%02x", ffs->eps_addrmap[i]);
else
@ -3445,6 +3918,25 @@ static inline struct f_fs_opts *to_ffs_opts(struct config_item *item)
func_inst.group);
}
static ssize_t f_fs_opts_ready_show(struct config_item *item, char *page)
{
struct f_fs_opts *opts = to_ffs_opts(item);
int ready;
ffs_dev_lock();
ready = opts->dev->desc_ready;
ffs_dev_unlock();
return sprintf(page, "%d\n", ready);
}
CONFIGFS_ATTR_RO(f_fs_opts_, ready);
static struct configfs_attribute *ffs_attrs[] = {
&f_fs_opts_attr_ready,
NULL,
};
static void ffs_attr_release(struct config_item *item)
{
struct f_fs_opts *opts = to_ffs_opts(item);
@ -3458,6 +3950,7 @@ static struct configfs_item_operations ffs_item_ops = {
static const struct config_item_type ffs_func_type = {
.ct_item_ops = &ffs_item_ops,
.ct_attrs = ffs_attrs,
.ct_owner = THIS_MODULE,
};

View File

@ -718,7 +718,7 @@ static const struct net_device_ops eth_netdev_ops = {
.ndo_validate_addr = eth_validate_addr,
};
static struct device_type gadget_type = {
static const struct device_type gadget_type = {
.name = "gadget",
};

View File

@ -35,6 +35,9 @@ uvc_video_encode_header(struct uvc_video *video, struct uvc_buffer *buf,
data[1] = UVC_STREAM_EOH | video->fid;
if (video->queue.flags & UVC_QUEUE_DROP_INCOMPLETE)
data[1] |= UVC_STREAM_ERR;
if (video->queue.buf_used == 0 && ts.tv_sec) {
/* dwClockFrequency is 48 MHz */
u32 pts = ((u64)ts.tv_sec * USEC_PER_SEC + ts.tv_nsec / NSEC_PER_USEC) * 48;
@ -370,6 +373,7 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
struct uvc_video *video = ureq->video;
struct uvc_video_queue *queue = &video->queue;
struct uvc_buffer *last_buf;
struct usb_request *to_queue = req;
unsigned long flags;
bool is_bulk = video->max_payload_size;
int ret = 0;
@ -397,7 +401,8 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
case -EXDEV:
uvcg_dbg(&video->uvc->func, "VS request missed xfer.\n");
queue->flags |= UVC_QUEUE_DROP_INCOMPLETE;
if (req->length != 0)
queue->flags |= UVC_QUEUE_DROP_INCOMPLETE;
break;
case -ESHUTDOWN: /* disconnect from host. */
@ -425,59 +430,59 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
* we're still streaming before queueing the usb_request
* back to req_free
*/
if (video->is_enabled) {
/*
* Here we check whether any request is available in the ready
* list. If it is, queue it to the ep and add the current
* usb_request to the req_free list - for video_pump to fill in.
* Otherwise, just use the current usb_request to queue a 0
* length request to the ep. Since we always add to the req_free
* list if we dequeue from the ready list, there will never
* be a situation where the req_free list is completely out of
* requests and cannot recover.
*/
struct usb_request *to_queue = req;
to_queue->length = 0;
if (!list_empty(&video->req_ready)) {
to_queue = list_first_entry(&video->req_ready,
struct usb_request, list);
list_del(&to_queue->list);
list_add_tail(&req->list, &video->req_free);
/*
* Queue work to the wq as well since it is possible that a
* buffer may not have been completely encoded with the set of
* in-flight usb requests for whih the complete callbacks are
* firing.
* In that case, if we do not queue work to the worker thread,
* the buffer will never be marked as complete - and therefore
* not be returned to userpsace. As a result,
* dequeue -> queue -> dequeue flow of uvc buffers will not
* happen.
*/
queue_work(video->async_wq, &video->pump);
}
/*
* Queue to the endpoint. The actual queueing to ep will
* only happen on one thread - the async_wq for bulk endpoints
* and this thread for isoc endpoints.
*/
ret = uvcg_video_usb_req_queue(video, to_queue, !is_bulk);
if (ret < 0) {
/*
* Endpoint error, but the stream is still enabled.
* Put request back in req_free for it to be cleaned
* up later.
*/
list_add_tail(&to_queue->list, &video->req_free);
}
} else {
if (!video->is_enabled) {
uvc_video_free_request(ureq, ep);
ret = 0;
}
spin_unlock_irqrestore(&video->req_lock, flags);
if (ret < 0)
spin_unlock_irqrestore(&video->req_lock, flags);
uvcg_queue_cancel(queue, 0);
return;
}
/*
* Here we check whether any request is available in the ready
* list. If it is, queue it to the ep and add the current
* usb_request to the req_free list - for video_pump to fill in.
* Otherwise, just use the current usb_request to queue a 0
* length request to the ep. Since we always add to the req_free
* list if we dequeue from the ready list, there will never
* be a situation where the req_free list is completely out of
* requests and cannot recover.
*/
to_queue->length = 0;
if (!list_empty(&video->req_ready)) {
to_queue = list_first_entry(&video->req_ready,
struct usb_request, list);
list_del(&to_queue->list);
list_add_tail(&req->list, &video->req_free);
/*
* Queue work to the wq as well since it is possible that a
* buffer may not have been completely encoded with the set of
* in-flight usb requests for whih the complete callbacks are
* firing.
* In that case, if we do not queue work to the worker thread,
* the buffer will never be marked as complete - and therefore
* not be returned to userpsace. As a result,
* dequeue -> queue -> dequeue flow of uvc buffers will not
* happen.
*/
queue_work(video->async_wq, &video->pump);
}
/*
* Queue to the endpoint. The actual queueing to ep will
* only happen on one thread - the async_wq for bulk endpoints
* and this thread for isoc endpoints.
*/
ret = uvcg_video_usb_req_queue(video, to_queue, !is_bulk);
if (ret < 0) {
/*
* Endpoint error, but the stream is still enabled.
* Put request back in req_free for it to be cleaned
* up later.
*/
list_add_tail(&to_queue->list, &video->req_free);
}
spin_unlock_irqrestore(&video->req_lock, flags);
}
static int
@ -594,10 +599,7 @@ static void uvcg_video_pump(struct work_struct *work)
*/
spin_lock_irqsave(&queue->irqlock, flags);
buf = uvcg_queue_head(queue);
if (buf != NULL) {
video->encode(req, video, buf);
} else {
if (!buf) {
/*
* Either the queue has been disconnected or no video buffer
* available for bulk transfer. Either way, stop processing
@ -607,6 +609,8 @@ static void uvcg_video_pump(struct work_struct *work)
break;
}
video->encode(req, video, buf);
spin_unlock_irqrestore(&queue->irqlock, flags);
spin_lock_irqsave(&video->req_lock, flags);
@ -623,14 +627,7 @@ static void uvcg_video_pump(struct work_struct *work)
uvcg_queue_cancel(queue, 0);
break;
}
/* The request is owned by the endpoint / ready list. */
req = NULL;
}
if (!req)
return;
spin_lock_irqsave(&video->req_lock, flags);
if (video->is_enabled)
list_add_tail(&req->list, &video->req_free);

View File

@ -903,6 +903,11 @@ int usb_gadget_map_request_by_dev(struct device *dev,
if (req->length == 0)
return 0;
if (req->sg_was_mapped) {
req->num_mapped_sgs = req->num_sgs;
return 0;
}
if (req->num_sgs) {
int mapped;
@ -948,7 +953,7 @@ EXPORT_SYMBOL_GPL(usb_gadget_map_request);
void usb_gadget_unmap_request_by_dev(struct device *dev,
struct usb_request *req, int is_in)
{
if (req->length == 0)
if (req->length == 0 || req->sg_was_mapped)
return;
if (req->num_mapped_sgs) {

View File

@ -13,7 +13,7 @@
* code from Dave Liu and Shlomi Gridish.
*/
#undef VERBOSE
#define pr_fmt(x) "udc: " x
#include <linux/module.h>
#include <linux/kernel.h>
@ -183,9 +183,9 @@ __acquires(ep->udc->lock)
usb_gadget_unmap_request(&ep->udc->gadget, &req->req, ep_is_in(ep));
if (status && (status != -ESHUTDOWN))
VDBG("complete %s req %p stat %d len %u/%u",
ep->ep.name, &req->req, status,
req->req.actual, req->req.length);
dev_vdbg(&udc->gadget.dev, "complete %s req %p stat %d len %u/%u\n",
ep->ep.name, &req->req, status,
req->req.actual, req->req.length);
ep->stopped = 1;
@ -285,7 +285,7 @@ static int dr_controller_setup(struct fsl_udc *udc)
timeout = jiffies + FSL_UDC_RESET_TIMEOUT;
while (fsl_readl(&dr_regs->usbcmd) & USB_CMD_CTRL_RESET) {
if (time_after(jiffies, timeout)) {
ERR("udc reset timeout!\n");
dev_err(&udc->gadget.dev, "udc reset timeout!\n");
return -ETIMEDOUT;
}
cpu_relax();
@ -308,9 +308,10 @@ static int dr_controller_setup(struct fsl_udc *udc)
tmp &= USB_EP_LIST_ADDRESS_MASK;
fsl_writel(tmp, &dr_regs->endpointlistaddr);
VDBG("vir[qh_base] is %p phy[qh_base] is 0x%8x reg is 0x%8x",
udc->ep_qh, (int)tmp,
fsl_readl(&dr_regs->endpointlistaddr));
dev_vdbg(&udc->gadget.dev,
"vir[qh_base] is %p phy[qh_base] is 0x%8x reg is 0x%8x\n",
udc->ep_qh, (int)tmp,
fsl_readl(&dr_regs->endpointlistaddr));
max_no_of_ep = (0x0000001F & fsl_readl(&dr_regs->dccparams));
for (ep_num = 1; ep_num < max_no_of_ep; ep_num++) {
@ -498,7 +499,7 @@ static void struct_ep_qh_setup(struct fsl_udc *udc, unsigned char ep_num,
tmp = max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS;
break;
default:
VDBG("error ep type is %d", ep_type);
dev_vdbg(&udc->gadget.dev, "error ep type is %d\n", ep_type);
return;
}
if (zlt)
@ -611,10 +612,10 @@ static int fsl_ep_enable(struct usb_ep *_ep,
spin_unlock_irqrestore(&udc->lock, flags);
retval = 0;
VDBG("enabled %s (ep%d%s) maxpacket %d",ep->ep.name,
ep->ep.desc->bEndpointAddress & 0x0f,
(desc->bEndpointAddress & USB_DIR_IN)
? "in" : "out", max);
dev_vdbg(&udc->gadget.dev, "enabled %s (ep%d%s) maxpacket %d\n",
ep->ep.name, ep->ep.desc->bEndpointAddress & 0x0f,
(desc->bEndpointAddress & USB_DIR_IN) ? "in" : "out",
max);
en_done:
return retval;
}
@ -633,7 +634,10 @@ static int fsl_ep_disable(struct usb_ep *_ep)
ep = container_of(_ep, struct fsl_ep, ep);
if (!_ep || !ep->ep.desc) {
VDBG("%s not enabled", _ep ? ep->ep.name : NULL);
/*
* dev_vdbg(&udc->gadget.dev, "%s not enabled\n",
* _ep ? ep->ep.name : NULL);
*/
return -EINVAL;
}
@ -659,7 +663,7 @@ static int fsl_ep_disable(struct usb_ep *_ep)
ep->stopped = 1;
spin_unlock_irqrestore(&udc->lock, flags);
VDBG("disabled %s OK", _ep->name);
dev_vdbg(&udc->gadget.dev, "disabled %s OK\n", _ep->name);
return 0;
}
@ -719,8 +723,8 @@ static void fsl_queue_td(struct fsl_ep *ep, struct fsl_req *req)
{
u32 temp, bitmask, tmp_stat;
/* VDBG("QH addr Register 0x%8x", dr_regs->endpointlistaddr);
VDBG("ep_qh[%d] addr is 0x%8x", i, (u32)&(ep->udc->ep_qh[i])); */
/* dev_vdbg(&udc->gadget.dev, "QH addr Register 0x%8x\n", dr_regs->endpointlistaddr);
dev_vdbg(&udc->gadget.dev, "ep_qh[%d] addr is 0x%8x\n", i, (u32)&(ep->udc->ep_qh[i])); */
bitmask = ep_is_in(ep)
? (1 << (ep_index(ep) + 16))
@ -808,7 +812,7 @@ static struct ep_td_struct *fsl_build_dtd(struct fsl_req *req, unsigned *length,
*is_last = 0;
if ((*is_last) == 0)
VDBG("multi-dtd request!");
dev_vdbg(&udc_controller->gadget.dev, "multi-dtd request!\n");
/* Fill in the transfer size; set active bit */
swap_temp = ((*length << DTD_LENGTH_BIT_POS) | DTD_STATUS_ACTIVE);
@ -820,7 +824,7 @@ static struct ep_td_struct *fsl_build_dtd(struct fsl_req *req, unsigned *length,
mb();
VDBG("length = %d address= 0x%x", *length, (int)*dma);
dev_vdbg(&udc_controller->gadget.dev, "length = %d address= 0x%x\n", *length, (int)*dma);
return dtd;
}
@ -871,11 +875,11 @@ fsl_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags)
/* catch various bogus parameters */
if (!_req || !req->req.complete || !req->req.buf
|| !list_empty(&req->queue)) {
VDBG("%s, bad params", __func__);
dev_vdbg(&udc->gadget.dev, "%s, bad params\n", __func__);
return -EINVAL;
}
if (unlikely(!_ep || !ep->ep.desc)) {
VDBG("%s, bad ep", __func__);
dev_vdbg(&udc->gadget.dev, "%s, bad ep\n", __func__);
return -EINVAL;
}
if (usb_endpoint_xfer_isoc(ep->ep.desc)) {
@ -1036,8 +1040,8 @@ static int fsl_ep_set_halt(struct usb_ep *_ep, int value)
udc->ep0_dir = 0;
}
out:
VDBG(" %s %s halt stat %d", ep->ep.name,
value ? "set" : "clear", status);
dev_vdbg(&udc->gadget.dev, "%s %s halt stat %d\n", ep->ep.name,
value ? "set" : "clear", status);
return status;
}
@ -1105,7 +1109,8 @@ static void fsl_ep_fifo_flush(struct usb_ep *_ep)
/* Wait until flush complete */
while (fsl_readl(&dr_regs->endptflush)) {
if (time_after(jiffies, timeout)) {
ERR("ep flush timeout\n");
dev_err(&udc_controller->gadget.dev,
"ep flush timeout\n");
return;
}
cpu_relax();
@ -1177,7 +1182,7 @@ static int fsl_vbus_session(struct usb_gadget *gadget, int is_active)
udc = container_of(gadget, struct fsl_udc, gadget);
spin_lock_irqsave(&udc->lock, flags);
VDBG("VBUS %s", is_active ? "on" : "off");
dev_vdbg(&gadget->dev, "VBUS %s\n", is_active ? "on" : "off");
udc->vbus_active = (is_active != 0);
if (can_pullup(udc))
fsl_writel((fsl_readl(&dr_regs->usbcmd) | USB_CMD_RUN_STOP),
@ -1543,7 +1548,7 @@ static void ep0_req_complete(struct fsl_udc *udc, struct fsl_ep *ep0,
udc->ep0_state = WAIT_FOR_SETUP;
break;
case WAIT_FOR_SETUP:
ERR("Unexpected ep0 packets\n");
dev_err(&udc->gadget.dev, "Unexpected ep0 packets\n");
break;
default:
ep0stall(udc);
@ -1612,7 +1617,7 @@ static int process_ep_req(struct fsl_udc *udc, int pipe,
errors = hc32_to_cpu(curr_td->size_ioc_sts);
if (errors & DTD_ERROR_MASK) {
if (errors & DTD_STATUS_HALTED) {
ERR("dTD error %08x QH=%d\n", errors, pipe);
dev_err(&udc->gadget.dev, "dTD error %08x QH=%d\n", errors, pipe);
/* Clear the errors and Halt condition */
tmp = hc32_to_cpu(curr_qh->size_ioc_int_sts);
tmp &= ~errors;
@ -1623,32 +1628,35 @@ static int process_ep_req(struct fsl_udc *udc, int pipe,
break;
}
if (errors & DTD_STATUS_DATA_BUFF_ERR) {
VDBG("Transfer overflow");
dev_vdbg(&udc->gadget.dev, "Transfer overflow\n");
status = -EPROTO;
break;
} else if (errors & DTD_STATUS_TRANSACTION_ERR) {
VDBG("ISO error");
dev_vdbg(&udc->gadget.dev, "ISO error\n");
status = -EILSEQ;
break;
} else
ERR("Unknown error has occurred (0x%x)!\n",
dev_err(&udc->gadget.dev,
"Unknown error has occurred (0x%x)!\n",
errors);
} else if (hc32_to_cpu(curr_td->size_ioc_sts)
& DTD_STATUS_ACTIVE) {
VDBG("Request not complete");
dev_vdbg(&udc->gadget.dev, "Request not complete\n");
status = REQ_UNCOMPLETE;
return status;
} else if (remaining_length) {
if (direction) {
VDBG("Transmit dTD remaining length not zero");
dev_vdbg(&udc->gadget.dev,
"Transmit dTD remaining length not zero\n");
status = -EPROTO;
break;
} else {
break;
}
} else {
VDBG("dTD transmitted successful");
dev_vdbg(&udc->gadget.dev,
"dTD transmitted successful\n");
}
if (j != curr_req->dtd_count - 1)
@ -1691,7 +1699,7 @@ static void dtd_complete_irq(struct fsl_udc *udc)
/* If the ep is configured */
if (!curr_ep->ep.name) {
WARNING("Invalid EP?");
dev_warn(&udc->gadget.dev, "Invalid EP?\n");
continue;
}
@ -1700,8 +1708,9 @@ static void dtd_complete_irq(struct fsl_udc *udc)
queue) {
status = process_ep_req(udc, i, curr_req);
VDBG("status of process_ep_req= %d, ep = %d",
status, ep_num);
dev_vdbg(&udc->gadget.dev,
"status of process_ep_req= %d, ep = %d\n",
status, ep_num);
if (status == REQ_UNCOMPLETE)
break;
/* write back status to req */
@ -1820,7 +1829,7 @@ static void reset_irq(struct fsl_udc *udc)
while (fsl_readl(&dr_regs->endpointprime)) {
/* Wait until all endptprime bits cleared */
if (time_after(jiffies, timeout)) {
ERR("Timeout for reset\n");
dev_err(&udc->gadget.dev, "Timeout for reset\n");
break;
}
cpu_relax();
@ -1830,7 +1839,7 @@ static void reset_irq(struct fsl_udc *udc)
fsl_writel(0xffffffff, &dr_regs->endptflush);
if (fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_RESET) {
VDBG("Bus reset");
dev_vdbg(&udc->gadget.dev, "Bus reset\n");
/* Bus is reseting */
udc->bus_reset = 1;
/* Reset all the queues, include XD, dTD, EP queue
@ -1838,7 +1847,7 @@ static void reset_irq(struct fsl_udc *udc)
reset_queues(udc, true);
udc->usb_state = USB_STATE_DEFAULT;
} else {
VDBG("Controller reset");
dev_vdbg(&udc->gadget.dev, "Controller reset\n");
/* initialize usb hw reg except for regs for EP, not
* touch usbintr reg */
dr_controller_setup(udc);
@ -1872,7 +1881,7 @@ static irqreturn_t fsl_udc_irq(int irq, void *_udc)
/* Clear notification bits */
fsl_writel(irq_src, &dr_regs->usbsts);
/* VDBG("irq_src [0x%8x]", irq_src); */
/* dev_vdbg(&udc->gadget.dev, "irq_src [0x%8x]", irq_src); */
/* Need to resume? */
if (udc->usb_state == USB_STATE_SUSPENDED)
@ -1881,7 +1890,7 @@ static irqreturn_t fsl_udc_irq(int irq, void *_udc)
/* USB Interrupt */
if (irq_src & USB_STS_INT) {
VDBG("Packet int");
dev_vdbg(&udc->gadget.dev, "Packet int\n");
/* Setup package, we only support ep0 as control ep */
if (fsl_readl(&dr_regs->endptsetupstat) & EP_SETUP_STATUS_EP0) {
tripwire_handler(udc, 0,
@ -1910,7 +1919,7 @@ static irqreturn_t fsl_udc_irq(int irq, void *_udc)
/* Reset Received */
if (irq_src & USB_STS_RESET) {
VDBG("reset int");
dev_vdbg(&udc->gadget.dev, "reset int\n");
reset_irq(udc);
status = IRQ_HANDLED;
}
@ -1922,7 +1931,7 @@ static irqreturn_t fsl_udc_irq(int irq, void *_udc)
}
if (irq_src & (USB_STS_ERR | USB_STS_SYS_ERR)) {
VDBG("Error IRQ %x", irq_src);
dev_vdbg(&udc->gadget.dev, "Error IRQ %x\n", irq_src);
}
spin_unlock_irqrestore(&udc->lock, flags);
@ -1958,7 +1967,7 @@ static int fsl_udc_start(struct usb_gadget *g,
udc_controller->transceiver->otg,
&udc_controller->gadget);
if (retval < 0) {
ERR("can't bind to transceiver\n");
dev_err(&udc_controller->gadget.dev, "can't bind to transceiver\n");
udc_controller->driver = NULL;
return retval;
}
@ -2243,7 +2252,7 @@ static int struct_udc_setup(struct fsl_udc *udc,
udc->eps = kcalloc(udc->max_ep, sizeof(struct fsl_ep), GFP_KERNEL);
if (!udc->eps) {
ERR("kmalloc udc endpoint status failed\n");
dev_err(&udc->gadget.dev, "kmalloc udc endpoint status failed\n");
goto eps_alloc_failed;
}
@ -2258,7 +2267,7 @@ static int struct_udc_setup(struct fsl_udc *udc,
udc->ep_qh = dma_alloc_coherent(&pdev->dev, size,
&udc->ep_qh_dma, GFP_KERNEL);
if (!udc->ep_qh) {
ERR("malloc QHs for udc failed\n");
dev_err(&udc->gadget.dev, "malloc QHs for udc failed\n");
goto ep_queue_alloc_failed;
}
@ -2269,14 +2278,14 @@ static int struct_udc_setup(struct fsl_udc *udc,
udc->status_req = container_of(fsl_alloc_request(NULL, GFP_KERNEL),
struct fsl_req, req);
if (!udc->status_req) {
ERR("kzalloc for udc status request failed\n");
dev_err(&udc->gadget.dev, "kzalloc for udc status request failed\n");
goto udc_status_alloc_failed;
}
/* allocate a small amount of memory to get valid address */
udc->status_req->req.buf = kmalloc(8, GFP_KERNEL);
if (!udc->status_req->req.buf) {
ERR("kzalloc for udc request buffer failed\n");
dev_err(&udc->gadget.dev, "kzalloc for udc request buffer failed\n");
goto udc_req_buf_alloc_failed;
}
@ -2373,7 +2382,7 @@ static int fsl_udc_probe(struct platform_device *pdev)
if (pdata->operating_mode == FSL_USB2_DR_OTG) {
udc_controller->transceiver = usb_get_phy(USB_PHY_TYPE_USB2);
if (IS_ERR_OR_NULL(udc_controller->transceiver)) {
ERR("Can't find OTG driver!\n");
dev_err(&udc_controller->gadget.dev, "Can't find OTG driver!\n");
ret = -ENODEV;
goto err_kfree;
}
@ -2389,7 +2398,7 @@ static int fsl_udc_probe(struct platform_device *pdev)
if (pdata->operating_mode == FSL_USB2_DR_DEVICE) {
if (!request_mem_region(res->start, resource_size(res),
driver_name)) {
ERR("request mem region for %s failed\n", pdev->name);
dev_err(&udc_controller->gadget.dev, "request mem region for %s failed\n", pdev->name);
ret = -EBUSY;
goto err_kfree;
}
@ -2420,7 +2429,7 @@ static int fsl_udc_probe(struct platform_device *pdev)
/* Read Device Controller Capability Parameters register */
dccparams = fsl_readl(&dr_regs->dccparams);
if (!(dccparams & DCCPARAMS_DC)) {
ERR("This SOC doesn't support device role\n");
dev_err(&udc_controller->gadget.dev, "This SOC doesn't support device role\n");
ret = -ENODEV;
goto err_exit;
}
@ -2438,14 +2447,14 @@ static int fsl_udc_probe(struct platform_device *pdev)
ret = request_irq(udc_controller->irq, fsl_udc_irq, IRQF_SHARED,
driver_name, udc_controller);
if (ret != 0) {
ERR("cannot request irq %d err %d\n",
dev_err(&udc_controller->gadget.dev, "cannot request irq %d err %d\n",
udc_controller->irq, ret);
goto err_exit;
}
/* Initialize the udc structure including QH member and other member */
if (struct_udc_setup(udc_controller, pdev)) {
ERR("Can't initialize udc data structure\n");
dev_err(&udc_controller->gadget.dev, "Can't initialize udc data structure\n");
ret = -ENOMEM;
goto err_free_irq;
}
@ -2486,7 +2495,7 @@ static int fsl_udc_probe(struct platform_device *pdev)
/* setup the udc->eps[] for non-control endpoints and link
* to gadget.ep_list */
for (i = 1; i < (int)(udc_controller->max_ep / 2); i++) {
char name[14];
char name[16];
sprintf(name, "ep%dout", i);
struct_ep_setup(udc_controller, i * 2, name, 1);
@ -2666,6 +2675,15 @@ static const struct platform_device_id fsl_udc_devtype[] = {
}
};
MODULE_DEVICE_TABLE(platform, fsl_udc_devtype);
static const struct of_device_id fsl_udc_dt_ids[] = {
{ .compatible = "fsl-usb2-dr" },
{ .compatible = "fsl-usb2-mph" },
{ .compatible = "fsl,mpc5121-usb2-dr" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fsl_udc_dt_ids);
static struct platform_driver udc_driver = {
.probe = fsl_udc_probe,
.remove_new = fsl_udc_remove,
@ -2675,6 +2693,7 @@ static struct platform_driver udc_driver = {
.resume = fsl_udc_resume,
.driver = {
.name = driver_name,
.of_match_table = fsl_udc_dt_ids,
/* udc suspend/resume called from OTG driver */
.suspend = fsl_udc_otg_suspend,
.resume = fsl_udc_otg_resume,

View File

@ -508,53 +508,6 @@ struct fsl_udc {
/*-------------------------------------------------------------------------*/
#ifdef DEBUG
#define DBG(fmt, args...) printk(KERN_DEBUG "[%s] " fmt "\n", \
__func__, ## args)
#else
#define DBG(fmt, args...) do{}while(0)
#endif
#if 0
static void dump_msg(const char *label, const u8 * buf, unsigned int length)
{
unsigned int start, num, i;
char line[52], *p;
if (length >= 512)
return;
DBG("%s, length %u:\n", label, length);
start = 0;
while (length > 0) {
num = min(length, 16u);
p = line;
for (i = 0; i < num; ++i) {
if (i == 8)
*p++ = ' ';
sprintf(p, " %02x", buf[i]);
p += 3;
}
*p = 0;
printk(KERN_DEBUG "%6x: %s\n", start, line);
buf += num;
start += num;
length -= num;
}
}
#endif
#ifdef VERBOSE
#define VDBG DBG
#else
#define VDBG(stuff...) do{}while(0)
#endif
#define ERR(stuff...) pr_err("udc: " stuff)
#define WARNING(stuff...) pr_warn("udc: " stuff)
#define INFO(stuff...) pr_info("udc: " stuff)
/*-------------------------------------------------------------------------*/
/* ### Add board specific defines here
*/

View File

@ -2650,7 +2650,7 @@ net2272_plat_probe(struct platform_device *pdev)
goto err_req;
}
ret = net2272_probe_fin(dev, IRQF_TRIGGER_LOW);
ret = net2272_probe_fin(dev, irqflags);
if (ret)
goto err_io;

View File

@ -24,7 +24,6 @@
#include <linux/byteorder/generic.h>
#include <linux/platform_data/pxa2xx_udc.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/usb.h>
#include <linux/usb/ch9.h>

View File

@ -8,7 +8,6 @@
#include <linux/extcon.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
#include <linux/module.h>

View File

@ -3491,8 +3491,8 @@ static void tegra_xudc_device_params_init(struct tegra_xudc *xudc)
static int tegra_xudc_phy_get(struct tegra_xudc *xudc)
{
int err = 0, usb3;
unsigned int i;
int err = 0, usb3_companion_port;
unsigned int i, j;
xudc->utmi_phy = devm_kcalloc(xudc->dev, xudc->soc->num_phys,
sizeof(*xudc->utmi_phy), GFP_KERNEL);
@ -3520,7 +3520,7 @@ static int tegra_xudc_phy_get(struct tegra_xudc *xudc)
if (IS_ERR(xudc->utmi_phy[i])) {
err = PTR_ERR(xudc->utmi_phy[i]);
dev_err_probe(xudc->dev, err,
"failed to get usb2-%d PHY\n", i);
"failed to get PHY for phy-name usb2-%d\n", i);
goto clean_up;
} else if (xudc->utmi_phy[i]) {
/* Get usb-phy, if utmi phy is available */
@ -3539,19 +3539,30 @@ static int tegra_xudc_phy_get(struct tegra_xudc *xudc)
}
/* Get USB3 phy */
usb3 = tegra_xusb_padctl_get_usb3_companion(xudc->padctl, i);
if (usb3 < 0)
usb3_companion_port = tegra_xusb_padctl_get_usb3_companion(xudc->padctl, i);
if (usb3_companion_port < 0)
continue;
snprintf(phy_name, sizeof(phy_name), "usb3-%d", usb3);
xudc->usb3_phy[i] = devm_phy_optional_get(xudc->dev, phy_name);
if (IS_ERR(xudc->usb3_phy[i])) {
err = PTR_ERR(xudc->usb3_phy[i]);
dev_err_probe(xudc->dev, err,
"failed to get usb3-%d PHY\n", usb3);
goto clean_up;
} else if (xudc->usb3_phy[i])
dev_dbg(xudc->dev, "usb3-%d PHY registered", usb3);
for (j = 0; j < xudc->soc->num_phys; j++) {
snprintf(phy_name, sizeof(phy_name), "usb3-%d", j);
xudc->usb3_phy[i] = devm_phy_optional_get(xudc->dev, phy_name);
if (IS_ERR(xudc->usb3_phy[i])) {
err = PTR_ERR(xudc->usb3_phy[i]);
dev_err_probe(xudc->dev, err,
"failed to get PHY for phy-name usb3-%d\n", j);
goto clean_up;
} else if (xudc->usb3_phy[i]) {
int usb2_port =
tegra_xusb_padctl_get_port_number(xudc->utmi_phy[i]);
int usb3_port =
tegra_xusb_padctl_get_port_number(xudc->usb3_phy[i]);
if (usb3_port == usb3_companion_port) {
dev_dbg(xudc->dev, "USB2 port %d is paired with USB3 port %d for device mode port %d\n",
usb2_port, usb3_port, i);
break;
}
}
}
}
return err;

View File

@ -65,6 +65,15 @@ struct orion_ehci_hcd {
static struct hc_driver __read_mostly ehci_orion_hc_driver;
/*
* Legacy DMA mask is 32 bit.
* AC5 has the DDR starting at 8GB, hence it requires
* a larger (34-bit) DMA mask, in order for DMA allocations
* to succeed:
*/
static const u64 dma_mask_orion = DMA_BIT_MASK(32);
static const u64 dma_mask_ac5 = DMA_BIT_MASK(34);
/*
* Implement Orion USB controller specification guidelines
*/
@ -211,6 +220,7 @@ static int ehci_orion_drv_probe(struct platform_device *pdev)
int irq, err;
enum orion_ehci_phy_ver phy_version;
struct orion_ehci_hcd *priv;
u64 *dma_mask_ptr;
if (usb_disabled())
return -ENODEV;
@ -228,7 +238,8 @@ static int ehci_orion_drv_probe(struct platform_device *pdev)
* set. Since shared usb code relies on it, set it here for
* now. Once we have dma capability bindings this can go away.
*/
err = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
dma_mask_ptr = (u64 *)of_device_get_match_data(&pdev->dev);
err = dma_coerce_mask_and_coherent(&pdev->dev, *dma_mask_ptr);
if (err)
goto err;
@ -332,8 +343,9 @@ static void ehci_orion_drv_remove(struct platform_device *pdev)
}
static const struct of_device_id ehci_orion_dt_ids[] = {
{ .compatible = "marvell,orion-ehci", },
{ .compatible = "marvell,armada-3700-ehci", },
{ .compatible = "marvell,orion-ehci", .data = &dma_mask_orion},
{ .compatible = "marvell,armada-3700-ehci", .data = &dma_mask_orion},
{ .compatible = "marvell,ac5-ehci", .data = &dma_mask_ac5},
{},
};
MODULE_DEVICE_TABLE(of, ehci_orion_dt_ids);

View File

@ -27,7 +27,6 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/platform_data/usb-ohci-pxa27x.h>
#include <linux/platform_data/pxa2xx_udc.h>
#include <linux/platform_device.h>

View File

@ -585,6 +585,7 @@ done(struct sl811 *sl811, struct sl811h_ep *ep, u8 bank)
finish_request(sl811, ep, urb, urbstat);
}
#ifdef QUIRK2
static inline u8 checkdone(struct sl811 *sl811)
{
u8 ctl;
@ -616,6 +617,7 @@ static inline u8 checkdone(struct sl811 *sl811)
#endif
return irqstat;
}
#endif
static irqreturn_t sl811h_irq(struct usb_hcd *hcd)
{

Some files were not shown because too many files have changed in this diff Show More