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:
commit
e09bf86f3d
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -26,6 +26,7 @@ properties:
|
||||
- enum:
|
||||
- qcom,pm4125-vbus-reg
|
||||
- qcom,pm6150-vbus-reg
|
||||
- qcom,pmi632-vbus-reg
|
||||
- const: qcom,pm8150b-vbus-reg
|
||||
|
||||
reg:
|
||||
|
@ -23,6 +23,7 @@ properties:
|
||||
oneOf:
|
||||
- items:
|
||||
- enum:
|
||||
- qcom,qcm6490-pmic-glink
|
||||
- qcom,sc8180x-pmic-glink
|
||||
- qcom,sc8280xp-pmic-glink
|
||||
- qcom,sm8350-pmic-glink
|
||||
|
55
Documentation/devicetree/bindings/sound/qcom,q6usb.yaml
Normal file
55
Documentation/devicetree/bindings/sound/qcom,q6usb.yaml
Normal 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>;
|
||||
};
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -77,6 +77,7 @@ properties:
|
||||
- const: usb-ehci
|
||||
- enum:
|
||||
- generic-ehci
|
||||
- marvell,ac5-ehci
|
||||
- marvell,armada-3700-ehci
|
||||
- marvell,orion-ehci
|
||||
- nuvoton,npcm750-ehci
|
||||
|
@ -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:
|
||||
|
@ -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";
|
||||
};
|
||||
};
|
72
Documentation/devicetree/bindings/usb/ite,it5205.yaml
Normal file
72
Documentation/devicetree/bindings/usb/ite,it5205.yaml
Normal 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>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
...
|
@ -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
|
||||
|
@ -72,8 +72,6 @@ allOf:
|
||||
i2c-bus: false
|
||||
else:
|
||||
$ref: /schemas/usb/usb-device.yaml
|
||||
required:
|
||||
- peer-hub
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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>;
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -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>;
|
||||
|
69
Documentation/devicetree/bindings/usb/ti,usb8020b.yaml
Normal file
69
Documentation/devicetree/bindings/usb/ti,usb8020b.yaml
Normal 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>;
|
||||
};
|
||||
};
|
@ -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>;
|
||||
};
|
||||
|
||||
|
67
Documentation/devicetree/bindings/usb/usb-switch.yaml
Normal file
67
Documentation/devicetree/bindings/usb/usb-switch.yaml
Normal 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
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
------------------------
|
||||
|
||||
|
@ -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>;
|
||||
|
@ -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>;
|
||||
|
@ -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"
|
||||
|
@ -26,6 +26,7 @@ obj-y += allwinner/ \
|
||||
mscc/ \
|
||||
qualcomm/ \
|
||||
ralink/ \
|
||||
realtek/ \
|
||||
renesas/ \
|
||||
rockchip/ \
|
||||
samsung/ \
|
||||
|
@ -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()
|
||||
|
32
drivers/phy/realtek/Kconfig
Normal file
32
drivers/phy/realtek/Kconfig
Normal 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
|
3
drivers/phy/realtek/Makefile
Normal file
3
drivers/phy/realtek/Makefile
Normal 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
|
1312
drivers/phy/realtek/phy-rtk-usb2.c
Normal file
1312
drivers/phy/realtek/phy-rtk-usb2.c
Normal file
File diff suppressed because it is too large
Load Diff
748
drivers/phy/realtek/phy-rtk-usb3.c
Normal file
748
drivers/phy/realtek/phy-rtk-usb3.c
Normal 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");
|
@ -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");
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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,
|
||||
|
@ -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
@ -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);
|
||||
|
@ -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
188
drivers/thunderbolt/trace.h
Normal 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>
|
@ -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 {
|
||||
|
@ -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...) \
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 */ }
|
||||
};
|
||||
|
@ -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),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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(¶ms, 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,
|
||||
¶ms);
|
||||
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(¶ms, 0x00, sizeof(params));
|
||||
cmd = DWC3_DEPCMD_DEPSTARTCFG;
|
||||
dwc = dep->dwc;
|
||||
cmd |= DWC3_DEPCMD_PARAM(resource_index);
|
||||
|
||||
ret = dwc3_send_gadget_ep_cmd(dep, cmd, ¶ms);
|
||||
ret = dwc3_send_gadget_ep_cmd(dwc->eps[0], cmd, ¶ms);
|
||||
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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -190,6 +190,7 @@ config USB_F_MASS_STORAGE
|
||||
tristate
|
||||
|
||||
config USB_F_FS
|
||||
select DMA_SHARED_BUFFER
|
||||
tristate
|
||||
|
||||
config USB_F_UAC1
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
@ -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",
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
*/
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user