Merge: Update RHEL9.3 USB And Thunderbolt to linux_v6.3
MR: https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-9/-/merge_requests/2781 ## BUGZILLA Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2212495 ## UPSTREAM STATUS Upstream Status: Patches have been accepted on kernel/git/torvalds/linux.git ## CONFLICTS Minor nonfunctional conflicts on patches 0005, 0047, 0052, 0065, 0074 and 0087. Revert of unsupported device tree bindings documentation on patch 0136. ## OMITTED FIXES ~~~ Omitted-fix: 3180d827c807 ("usb: gadget: uvc: don't put item still in use") Omitted-fix: 181babf7b4e5 ("usb: gadget: uvc: also use try_format in set_format") ~~~ ## BUILD INFORMATION Build Info: https://brewweb.engineering.redhat.com/brew/taskinfo?taskID=53739516 ## TESTING Functional testing: Q&A Smoke testing: Basic testing with kernel and kernel-debug on a Intel Meteor Lake Beaker system with USB3.2 / TB4. ## VERSION UPDATES AND/OR OBSERVATIONS ~~~ Patch 0007: The thunderbolt-net driver changed its name to thunderbolt_net and moved into a specific folder. Patch 0140: On v2, I have added commit <6f489a966fbe> ("media: usb: siano: Fix warning due to null work_func_t function pointer") Patch 0141: On v3, I have added commit <c4af8e3fecd0> ("thunderbolt: Clear registers properly when auto clear isn't in use") Patch 0142: On v4, I have added commit <9f9666e65359> ("thunderbolt: Mask ring interrupt on Intel hardware as well") ~~~ ## DESCRIPTION This rebases supported USB and Thunderbolt drivers to upstream kernel v6.3. By design, changes on this rebase are limited to supported usb and thunderbolt drivers. Changes which happen to touch the drivers but are tree-wide are selectively pulled in, when relevant. Signed-off-by: Desnes Nunes <desnesn@redhat.com> Approved-by: David Arcari <darcari@redhat.com> Approved-by: Tony Camuso <tcamuso@redhat.com> Signed-off-by: Jan Stancek <jstancek@redhat.com>
This commit is contained in:
commit
d0c1da3b58
|
@ -69,7 +69,7 @@ Description:
|
|||
This file contains boolean value that tells does the device
|
||||
support both source and sink power roles.
|
||||
|
||||
What: /sys/class/usb_power_delivery/.../<capability>/1:fixed_supply/usb_suspend_supported
|
||||
What: /sys/class/usb_power_delivery/.../source-capabilities/1:fixed_supply/usb_suspend_supported
|
||||
Date: May 2022
|
||||
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
|
||||
Description:
|
||||
|
@ -78,6 +78,15 @@ Description:
|
|||
will follow the USB 2.0 and USB 3.2 rules for suspend and
|
||||
resume.
|
||||
|
||||
What: /sys/class/usb_power_delivery/.../sink-capabilities/1:fixed_supply/higher_capability
|
||||
Date: February 2023
|
||||
Contact: Saranya Gopal <saranya.gopal@linux.intel.com>
|
||||
Description:
|
||||
This file shows the value of the Higher capability bit in
|
||||
vsafe5V Fixed Supply Object. If the bit is set, then the sink
|
||||
needs more than vsafe5V(eg. 12 V) to provide full functionality.
|
||||
Valid values: 0, 1
|
||||
|
||||
What: /sys/class/usb_power_delivery/.../<capability>/1:fixed_supply/unconstrained_power
|
||||
Date: May 2022
|
||||
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
|
||||
|
|
|
@ -47,3 +47,18 @@ Description:
|
|||
USB SuperSpeed protocol. From user perspective pin assignments C
|
||||
and E are equal, where all channels on the connector are used
|
||||
for carrying DisplayPort protocol (allowing higher resolutions).
|
||||
|
||||
What: /sys/bus/typec/devices/.../displayport/hpd
|
||||
Date: Dec 2022
|
||||
Contact: Badhri Jagan Sridharan <badhri@google.com>
|
||||
Description:
|
||||
VESA DisplayPort Alt Mode on USB Type-C Standard defines how
|
||||
HotPlugDetect(HPD) shall be supported on the USB-C connector when
|
||||
operating in DisplayPort Alt Mode. This is a read only node which
|
||||
reflects the current state of HPD.
|
||||
|
||||
Valid values:
|
||||
- 1: when HPD’s logical state is high (HPD_High) as defined
|
||||
by VESA DisplayPort Alt Mode on USB Type-C Standard.
|
||||
- 0 when HPD’s logical state is low (HPD_Low) as defined by
|
||||
VESA DisplayPort Alt Mode on USB Type-C Standard.
|
||||
|
|
|
@ -64,8 +64,8 @@ Required properties if child node exists:
|
|||
Properties for children:
|
||||
|
||||
The OMAP HS USB Host subsystem contains EHCI and OHCI controllers.
|
||||
See Documentation/devicetree/bindings/usb/ehci-omap.txt and
|
||||
Documentation/devicetree/bindings/usb/ohci-omap3.txt.
|
||||
See Documentation/devicetree/bindings/usb/generic-ehci.yaml and
|
||||
Documentation/devicetree/bindings/usb/generic-ohci.yaml.
|
||||
|
||||
Example for OMAP4:
|
||||
|
||||
|
@ -78,14 +78,14 @@ usbhshost: usbhshost@4a064000 {
|
|||
ranges;
|
||||
|
||||
usbhsohci: ohci@4a064800 {
|
||||
compatible = "ti,ohci-omap3", "usb-ohci";
|
||||
compatible = "ti,ohci-omap3";
|
||||
reg = <0x4a064800 0x400>;
|
||||
interrupt-parent = <&gic>;
|
||||
interrupts = <0 76 0x4>;
|
||||
};
|
||||
|
||||
usbhsehci: ehci@4a064c00 {
|
||||
compatible = "ti,ehci-omap", "usb-ehci";
|
||||
compatible = "ti,ehci-omap";
|
||||
reg = <0x4a064c00 0x400>;
|
||||
interrupt-parent = <&gic>;
|
||||
interrupts = <0 77 0x4>;
|
||||
|
|
|
@ -97,16 +97,6 @@ Nintendo Wii device tree
|
|||
- reg : should contain the EXI registers location and length
|
||||
- interrupts : should contain the EXI interrupt
|
||||
|
||||
1.g) The Open Host Controller Interface (OHCI) nodes
|
||||
|
||||
Represent the USB 1.x Open Host Controller Interfaces.
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : should be "nintendo,hollywood-usb-ohci","usb-ohci"
|
||||
- reg : should contain the OHCI registers location and length
|
||||
- interrupts : should contain the OHCI interrupt
|
||||
|
||||
1.h) The Enhanced Host Controller Interface (EHCI) node
|
||||
|
||||
Represents the USB 2.0 Enhanced Host Controller Interface.
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
* Broadcom USB controllers
|
||||
|
||||
Required properties:
|
||||
- compatible: "brcm,bcm3384-ohci", "brcm,bcm3384-ehci"
|
||||
|
||||
These currently use the generic-ohci and generic-ehci drivers. On some
|
||||
systems, special handling may be needed in the following cases:
|
||||
|
||||
- Restoring state after systemwide power save modes
|
||||
- Sharing PHYs with the USBD (UDC) hardware
|
||||
- Figuring out which controllers are disabled on ASIC bondout variants
|
|
@ -11,6 +11,7 @@ Required properties:
|
|||
"fsl,imx6ul-usb"
|
||||
"fsl,imx7d-usb"
|
||||
"fsl,imx7ulp-usb"
|
||||
"fsl,imx8mm-usb"
|
||||
"lsi,zevio-usb"
|
||||
"qcom,ci-hdrc"
|
||||
"chipidea,usb2"
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/usb/cypress,cypd4226.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Cypress cypd4226 Type-C Controller
|
||||
|
||||
maintainers:
|
||||
- Wayne Chang <waynec@nvidia.com>
|
||||
|
||||
description:
|
||||
The Cypress cypd4226 is a dual Type-C controller that is controlled
|
||||
via an I2C interface.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: cypress,cypd4226
|
||||
|
||||
'#address-cells':
|
||||
const: 1
|
||||
|
||||
'#size-cells':
|
||||
const: 0
|
||||
|
||||
reg:
|
||||
const: 0x08
|
||||
|
||||
interrupts:
|
||||
items:
|
||||
- description: cypd4226 host interrupt
|
||||
|
||||
firmware-name:
|
||||
enum:
|
||||
- nvidia,gpu
|
||||
- nvidia,jetson-agx-xavier
|
||||
description: |
|
||||
The name of the CCGx firmware built for product series.
|
||||
should be set one of following:
|
||||
- "nvidia,gpu" for the NVIDIA RTX product series
|
||||
- "nvidia,jetson-agx-xavier" for the NVIDIA Jetson product series
|
||||
|
||||
patternProperties:
|
||||
'^connector@[01]$':
|
||||
$ref: /schemas/connector/usb-connector.yaml#
|
||||
unevaluatedProperties: false
|
||||
properties:
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
|
||||
anyOf:
|
||||
- required:
|
||||
- connector@0
|
||||
- required:
|
||||
- connector@1
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/tegra194-gpio.h>
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
#interrupt-cells = <2>;
|
||||
|
||||
typec@8 {
|
||||
compatible = "cypress,cypd4226";
|
||||
reg = <0x08>;
|
||||
interrupt-parent = <&gpio_aon>;
|
||||
interrupts = <TEGRA194_AON_GPIO(BB, 2) IRQ_TYPE_LEVEL_LOW>;
|
||||
firmware-name = "nvidia,jetson-agx-xavier";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
connector@0 {
|
||||
compatible = "usb-c-connector";
|
||||
reg = <0>;
|
||||
label = "USB-C";
|
||||
data-role = "dual";
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
endpoint {
|
||||
remote-endpoint = <&usb_role_switch0>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
|
@ -1,31 +0,0 @@
|
|||
OMAP HS USB EHCI controller
|
||||
|
||||
This device is usually the child of the omap-usb-host
|
||||
Documentation/devicetree/bindings/mfd/omap-usb-host.txt
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible: should be "ti,ehci-omap"
|
||||
- reg: should contain one register range i.e. start and length
|
||||
- interrupts: description of the interrupt line
|
||||
|
||||
Optional properties:
|
||||
|
||||
- phys: list of phandles to PHY nodes.
|
||||
This property is required if at least one of the ports are in
|
||||
PHY mode i.e. OMAP_EHCI_PORT_MODE_PHY
|
||||
|
||||
To specify the port mode, see
|
||||
Documentation/devicetree/bindings/mfd/omap-usb-host.txt
|
||||
|
||||
Example for OMAP4:
|
||||
|
||||
usbhsehci: ehci@4a064c00 {
|
||||
compatible = "ti,ehci-omap";
|
||||
reg = <0x4a064c00 0x400>;
|
||||
interrupts = <0 77 0x4>;
|
||||
};
|
||||
|
||||
&usbhsehci {
|
||||
phys = <&hsusb1_phy 0 &hsusb3_phy>;
|
||||
};
|
|
@ -1,22 +0,0 @@
|
|||
* EHCI controller, Orion Marvell variants
|
||||
|
||||
Required properties:
|
||||
- compatible: must be one of the following
|
||||
"marvell,orion-ehci"
|
||||
"marvell,armada-3700-ehci"
|
||||
- reg: physical base address of the controller and length of memory mapped
|
||||
region.
|
||||
- interrupts: The EHCI interrupt
|
||||
|
||||
Optional properties:
|
||||
- clocks: reference to the clock
|
||||
- phys: reference to the USB PHY
|
||||
- phy-names: name of the USB PHY, should be "usb"
|
||||
|
||||
Example:
|
||||
|
||||
ehci@50000 {
|
||||
compatible = "marvell,orion-ehci";
|
||||
reg = <0x50000 0x1000>;
|
||||
interrupts = <19>;
|
||||
};
|
|
@ -51,7 +51,7 @@ examples:
|
|||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
fsa4480@42 {
|
||||
typec-mux@42 {
|
||||
compatible = "fcs,fsa4480";
|
||||
reg = <0x42>;
|
||||
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
Fairchild FUSB302 Type-C Port controllers
|
||||
|
||||
Required properties :
|
||||
- compatible : "fcs,fusb302"
|
||||
- reg : I2C slave address
|
||||
- interrupts : Interrupt specifier
|
||||
|
||||
Required sub-node:
|
||||
- connector : The "usb-c-connector" attached to the FUSB302 IC. The bindings
|
||||
of the connector node are specified in:
|
||||
|
||||
Documentation/devicetree/bindings/connector/usb-connector.yaml
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
fusb302: typec-portc@54 {
|
||||
compatible = "fcs,fusb302";
|
||||
reg = <0x54>;
|
||||
interrupt-parent = <&nmi_intc>;
|
||||
interrupts = <0 IRQ_TYPE_LEVEL_LOW>;
|
||||
|
||||
usb_con: connector {
|
||||
compatible = "usb-c-connector";
|
||||
label = "USB-C";
|
||||
power-role = "dual";
|
||||
try-power-role = "sink";
|
||||
source-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)>;
|
||||
sink-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)
|
||||
PDO_VAR(3000, 12000, 3000)
|
||||
PDO_PPS_APDO(3000, 11000, 3000)>;
|
||||
op-sink-microwatt = <10000000>;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,67 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/usb/fcs,fusb302.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Fairchild FUSB302 Type-C Port controller
|
||||
|
||||
maintainers:
|
||||
- Rob Herring <robh@kernel.org>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: fcs,fusb302
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
vbus-supply:
|
||||
description: VBUS power supply
|
||||
|
||||
connector:
|
||||
type: object
|
||||
$ref: /schemas/connector/usb-connector.yaml#
|
||||
unevaluatedProperties: false
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
- vbus-supply
|
||||
- connector
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
#include <dt-bindings/usb/pd.h>
|
||||
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
typec-portc@54 {
|
||||
compatible = "fcs,fusb302";
|
||||
reg = <0x54>;
|
||||
interrupt-parent = <&nmi_intc>;
|
||||
interrupts = <0 IRQ_TYPE_LEVEL_LOW>;
|
||||
vbus-supply = <&vbus_typec>;
|
||||
|
||||
connector {
|
||||
compatible = "usb-c-connector";
|
||||
label = "USB-C";
|
||||
power-role = "dual";
|
||||
try-power-role = "sink";
|
||||
source-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)>;
|
||||
sink-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)
|
||||
PDO_VAR(3000, 12000, 3000)
|
||||
PDO_PPS_APDO(3000, 11000, 3000)>;
|
||||
op-sink-microwatt = <10000000>;
|
||||
};
|
||||
};
|
||||
};
|
|
@ -73,6 +73,9 @@ properties:
|
|||
- const: usb-ehci
|
||||
- enum:
|
||||
- generic-ehci
|
||||
- marvell,armada-3700-ehci
|
||||
- marvell,orion-ehci
|
||||
- ti,ehci-omap
|
||||
- usb-ehci
|
||||
|
||||
reg:
|
||||
|
|
|
@ -6,9 +6,6 @@ $schema: http://devicetree.org/meta-schemas/core.yaml#
|
|||
|
||||
title: USB OHCI Controller Device Tree Bindings
|
||||
|
||||
allOf:
|
||||
- $ref: "usb-hcd.yaml"
|
||||
|
||||
maintainers:
|
||||
- Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
||||
|
||||
|
@ -48,7 +45,16 @@ properties:
|
|||
- ingenic,jz4740-ohci
|
||||
- snps,hsdk-v1.0-ohci
|
||||
- const: generic-ohci
|
||||
- const: generic-ohci
|
||||
- enum:
|
||||
- generic-ohci
|
||||
- ti,ohci-omap3
|
||||
- items:
|
||||
- enum:
|
||||
- cavium,octeon-6335-ohci
|
||||
- nintendo,hollywood-usb-ohci
|
||||
- nxp,ohci-nxp
|
||||
- st,spear600-ohci
|
||||
- const: usb-ohci
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
@ -118,11 +124,29 @@ properties:
|
|||
- host
|
||||
- otg
|
||||
|
||||
transceiver:
|
||||
$ref: /schemas/types.yaml#/definitions/phandle
|
||||
description:
|
||||
The associated ISP1301 device. Necessary for the UDC controller for
|
||||
connecting to the USB physical layer.
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
|
||||
allOf:
|
||||
- $ref: usb-hcd.yaml
|
||||
- if:
|
||||
not:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: nxp,ohci-nxp
|
||||
then:
|
||||
properties:
|
||||
transceiver: false
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: "http://devicetree.org/schemas/usb/gpio-sbu-mux.yaml#"
|
||||
$schema: "http://devicetree.org/meta-schemas/core.yaml#"
|
||||
|
||||
title: GPIO-based SBU mux
|
||||
|
||||
maintainers:
|
||||
- Bjorn Andersson <andersson@kernel.org>
|
||||
|
||||
description:
|
||||
In USB Type-C applications the SBU lines needs to be connected, disconnected
|
||||
and swapped depending on the altmode and orientation. This binding describes
|
||||
a family of hardware solutions which switches between these modes using GPIO
|
||||
signals.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
items:
|
||||
- enum:
|
||||
- onnn,fsusb43l10x
|
||||
- pericom,pi3usb102
|
||||
- const: gpio-sbu-mux
|
||||
|
||||
enable-gpios:
|
||||
description: Switch enable GPIO
|
||||
|
||||
select-gpios:
|
||||
description: Orientation select
|
||||
|
||||
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
|
||||
|
||||
port:
|
||||
$ref: /schemas/graph.yaml#/properties/port
|
||||
description:
|
||||
A port node to link the SBU mux to a TypeC controller for the purpose of
|
||||
handling altmode muxing and orientation switching.
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- enable-gpios
|
||||
- select-gpios
|
||||
- mode-switch
|
||||
- orientation-switch
|
||||
- port
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
tcpm {
|
||||
connector {
|
||||
compatible = "usb-c-connector";
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
tcpm_hs_out: endpoint {
|
||||
remote-endpoint = <&usb_hs_phy_in>;
|
||||
};
|
||||
};
|
||||
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
tcpm_ss_out: endpoint {
|
||||
remote-endpoint = <&usb_ss_phy_in>;
|
||||
};
|
||||
};
|
||||
|
||||
port@2 {
|
||||
reg = <2>;
|
||||
tcpm_sbu_out: endpoint {
|
||||
remote-endpoint = <&sbu_mux_in>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
sbu-mux {
|
||||
compatible = "pericom,pi3usb102", "gpio-sbu-mux";
|
||||
|
||||
enable-gpios = <&tlmm 101 GPIO_ACTIVE_LOW>;
|
||||
select-gpios = <&tlmm 164 GPIO_ACTIVE_HIGH>;
|
||||
|
||||
mode-switch;
|
||||
orientation-switch;
|
||||
|
||||
port {
|
||||
sbu_mux_in: endpoint {
|
||||
remote-endpoint = <&tcpm_sbu_out>;
|
||||
};
|
||||
};
|
||||
};
|
||||
...
|
|
@ -1,24 +0,0 @@
|
|||
* OHCI controller, NXP ohci-nxp variant
|
||||
|
||||
Required properties:
|
||||
- compatible: must be "nxp,ohci-nxp"
|
||||
- reg: physical base address of the controller and length of memory mapped
|
||||
region.
|
||||
- interrupts: The OHCI interrupt
|
||||
- transceiver: phandle of the associated ISP1301 device - this is necessary for
|
||||
the UDC controller for connecting to the USB physical layer
|
||||
|
||||
Example (LPC32xx):
|
||||
|
||||
isp1301: usb-transceiver@2c {
|
||||
compatible = "nxp,isp1301";
|
||||
reg = <0x2c>;
|
||||
};
|
||||
|
||||
ohci@31020000 {
|
||||
compatible = "nxp,ohci-nxp";
|
||||
reg = <0x31020000 0x300>;
|
||||
interrupt-parent = <&mic>;
|
||||
interrupts = <0x3b 0>;
|
||||
transceiver = <&isp1301>;
|
||||
};
|
|
@ -1,15 +0,0 @@
|
|||
OMAP HS USB OHCI controller (OMAP3 and later)
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible: should be "ti,ohci-omap3"
|
||||
- reg: should contain one register range i.e. start and length
|
||||
- interrupts: description of the interrupt line
|
||||
|
||||
Example for OMAP4:
|
||||
|
||||
usbhsohci: ohci@4a064800 {
|
||||
compatible = "ti,ohci-omap3";
|
||||
reg = <0x4a064800 0x400>;
|
||||
interrupts = <0 76 0x4>;
|
||||
};
|
|
@ -22,7 +22,7 @@ Optional properties:
|
|||
Example:
|
||||
|
||||
usb0: ohci@4c000000 {
|
||||
compatible = "marvell,pxa-ohci", "usb-ohci";
|
||||
compatible = "marvell,pxa-ohci";
|
||||
reg = <0x4c000000 0x100000>;
|
||||
interrupts = <18>;
|
||||
marvell,enable-port1;
|
||||
|
|
|
@ -11,55 +11,27 @@ maintainers:
|
|||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- items:
|
||||
- enum:
|
||||
- renesas,r8a774a1-usb3-peri # RZ/G2M
|
||||
- renesas,r8a774b1-usb3-peri # RZ/G2N
|
||||
- renesas,r8a774c0-usb3-peri # RZ/G2E
|
||||
- renesas,r8a774e1-usb3-peri # RZ/G2H
|
||||
- renesas,r8a7795-usb3-peri # R-Car H3
|
||||
- renesas,r8a7796-usb3-peri # R-Car M3-W
|
||||
- renesas,r8a77961-usb3-peri # R-Car M3-W+
|
||||
- renesas,r8a77965-usb3-peri # R-Car M3-N
|
||||
- renesas,r8a77990-usb3-peri # R-Car E3
|
||||
- const: renesas,rcar-gen3-usb3-peri
|
||||
|
||||
- items:
|
||||
- enum:
|
||||
- renesas,r9a09g011-usb3-peri # RZ/V2M
|
||||
- const: renesas,rzv2m-usb3-peri
|
||||
items:
|
||||
- enum:
|
||||
- renesas,r8a774a1-usb3-peri # RZ/G2M
|
||||
- renesas,r8a774b1-usb3-peri # RZ/G2N
|
||||
- renesas,r8a774c0-usb3-peri # RZ/G2E
|
||||
- renesas,r8a774e1-usb3-peri # RZ/G2H
|
||||
- renesas,r8a7795-usb3-peri # R-Car H3
|
||||
- renesas,r8a7796-usb3-peri # R-Car M3-W
|
||||
- renesas,r8a77961-usb3-peri # R-Car M3-W+
|
||||
- renesas,r8a77965-usb3-peri # R-Car M3-N
|
||||
- renesas,r8a77990-usb3-peri # R-Car E3
|
||||
- const: renesas,rcar-gen3-usb3-peri
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
minItems: 1
|
||||
items:
|
||||
- description: Combined interrupt for DMA, SYS and ERR
|
||||
- description: Dual Role Device (DRD)
|
||||
- description: Battery Charging
|
||||
- description: Global Purpose Input
|
||||
|
||||
interrupt-names:
|
||||
minItems: 1
|
||||
items:
|
||||
- const: all_p
|
||||
- const: drd
|
||||
- const: bc
|
||||
- const: gpi
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
minItems: 1
|
||||
items:
|
||||
- description: Main clock
|
||||
- description: Register access clock
|
||||
|
||||
clock-names:
|
||||
minItems: 1
|
||||
items:
|
||||
- const: aclk
|
||||
- const: reg
|
||||
maxItems: 1
|
||||
|
||||
phys:
|
||||
maxItems: 1
|
||||
|
@ -71,15 +43,7 @@ properties:
|
|||
maxItems: 1
|
||||
|
||||
resets:
|
||||
minItems: 1
|
||||
items:
|
||||
- description: Peripheral reset
|
||||
- description: DRD reset
|
||||
|
||||
reset-names:
|
||||
items:
|
||||
- const: aresetn_p
|
||||
- const: drd_reset
|
||||
maxItems: 1
|
||||
|
||||
usb-role-switch:
|
||||
$ref: /schemas/types.yaml#/definitions/flag
|
||||
|
@ -114,39 +78,6 @@ required:
|
|||
- interrupts
|
||||
- clocks
|
||||
|
||||
allOf:
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- renesas,rzv2m-usb3-peri
|
||||
then:
|
||||
properties:
|
||||
clocks:
|
||||
minItems: 2
|
||||
clock-names:
|
||||
minItems: 2
|
||||
interrupts:
|
||||
minItems: 4
|
||||
interrupt-names:
|
||||
minItems: 4
|
||||
resets:
|
||||
minItems: 2
|
||||
required:
|
||||
- clock-names
|
||||
- interrupt-names
|
||||
- resets
|
||||
- reset-names
|
||||
else:
|
||||
properties:
|
||||
clocks:
|
||||
maxItems: 1
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
resets:
|
||||
maxItems: 1
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
ST SPEAr SoC USB controllers:
|
||||
-----------------------------
|
||||
|
||||
EHCI:
|
||||
-----
|
||||
|
||||
Required properties:
|
||||
- compatible: "st,spear600-ehci"
|
||||
- interrupts: Should contain the EHCI interrupt
|
||||
|
||||
Example:
|
||||
|
||||
ehci@e1800000 {
|
||||
compatible = "st,spear600-ehci", "usb-ehci";
|
||||
reg = <0xe1800000 0x1000>;
|
||||
interrupt-parent = <&vic1>;
|
||||
interrupts = <27>;
|
||||
};
|
||||
|
||||
|
||||
OHCI:
|
||||
-----
|
||||
|
||||
Required properties:
|
||||
- compatible: "st,spear600-ohci"
|
||||
- interrupts: Should contain the OHCI interrupt
|
||||
|
||||
Example:
|
||||
|
||||
ohci@e1900000 {
|
||||
compatible = "st,spear600-ohci", "usb-ohci";
|
||||
reg = <0xe1800000 0x1000>;
|
||||
interrupt-parent = <&vic1>;
|
||||
interrupts = <26>;
|
||||
};
|
|
@ -23,6 +23,8 @@ properties:
|
|||
reg:
|
||||
maxItems: 1
|
||||
|
||||
wakeup-source: true
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
|
@ -48,6 +50,7 @@ examples:
|
|||
tps6598x: tps6598x@38 {
|
||||
compatible = "ti,tps6598x";
|
||||
reg = <0x38>;
|
||||
wakeup-source;
|
||||
|
||||
interrupt-parent = <&msmgpio>;
|
||||
interrupts = <107 IRQ_TYPE_LEVEL_LOW>;
|
||||
|
|
|
@ -35,7 +35,7 @@ properties:
|
|||
maxItems: 1
|
||||
|
||||
vbus-regulator:
|
||||
description: Should specifiy the regulator supplying current drawn from
|
||||
description: Should specify the regulator supplying current drawn from
|
||||
the VBus line.
|
||||
$ref: /schemas/types.yaml#/definitions/phandle
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ Required properties:
|
|||
"fsl,imx6sx-usbmisc" for imx6sx
|
||||
"fsl,imx7d-usbmisc" for imx7d
|
||||
"fsl,imx7ulp-usbmisc" for imx7ulp
|
||||
"fsl,imx8mm-usbmisc" for imx8mm
|
||||
- reg: Should contain registers location and length
|
||||
|
||||
Examples:
|
||||
|
|
|
@ -35,10 +35,10 @@ which can show otg fsm variables and some controller registers value::
|
|||
1) Power up 2 Freescale i.MX6Q sabre SD boards with gadget class driver loaded
|
||||
(e.g. g_mass_storage).
|
||||
|
||||
2) Connect 2 boards with usb cable with one end is micro A plug, the other end
|
||||
2) Connect 2 boards with usb cable: one end is micro A plug, the other end
|
||||
is micro B plug.
|
||||
|
||||
The A-device(with micro A plug inserted) should enumerate B-device.
|
||||
The A-device (with micro A plug inserted) should enumerate B-device.
|
||||
|
||||
3) Role switch
|
||||
|
||||
|
@ -54,18 +54,19 @@ which can show otg fsm variables and some controller registers value::
|
|||
|
||||
echo 0 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req
|
||||
|
||||
or, by introducing HNP polling, B-Host can know when A-peripheral wish
|
||||
to be host role, so this role switch also can be trigged in A-peripheral
|
||||
side by answering the polling from B-Host, this can be done on A-device::
|
||||
or, by introducing HNP polling, B-Host can know when A-peripheral wishes to
|
||||
be in the host role, so this role switch also can be triggered in
|
||||
A-peripheral side by answering the polling from B-Host. This can be done on
|
||||
A-device::
|
||||
|
||||
echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_req
|
||||
|
||||
A-device should switch back to host and enumerate B-device.
|
||||
|
||||
5) Remove B-device(unplug micro B plug) and insert again in 10 seconds,
|
||||
5) Remove B-device (unplug micro B plug) and insert again in 10 seconds;
|
||||
A-device should enumerate B-device again.
|
||||
|
||||
6) Remove B-device(unplug micro B plug) and insert again after 10 seconds,
|
||||
6) Remove B-device (unplug micro B plug) and insert again after 10 seconds;
|
||||
A-device should NOT enumerate B-device.
|
||||
|
||||
if A-device wants to use bus:
|
||||
|
@ -105,7 +106,7 @@ July 27, 2012 Revision 2.0 version 1.1a"
|
|||
2. How to enable USB as system wakeup source
|
||||
--------------------------------------------
|
||||
Below is the example for how to enable USB as system wakeup source
|
||||
at imx6 platform.
|
||||
on an imx6 platform.
|
||||
|
||||
2.1 Enable core's wakeup::
|
||||
|
||||
|
@ -128,6 +129,6 @@ at imx6 platform.
|
|||
echo enabled > /sys/bus/usb/devices/1-1/power/wakeup
|
||||
|
||||
If the system has only one usb port, and you want usb wakeup at this port, you
|
||||
can use below script to enable usb wakeup::
|
||||
can use the below script to enable usb wakeup::
|
||||
|
||||
for i in $(find /sys -name wakeup | grep usb);do echo enabled > $i;done;
|
||||
|
|
|
@ -813,7 +813,7 @@ the user must provide the following:
|
|||
================== ====================================================
|
||||
|
||||
Each frame description contains frame interval specification, and each
|
||||
such specification consists of a number of lines with an inverval value
|
||||
such specification consists of a number of lines with an interval value
|
||||
in each line. The rules stated above are best illustrated with an example::
|
||||
|
||||
# mkdir functions/uvc.usb0/control/header/h
|
||||
|
|
|
@ -150,7 +150,7 @@ Module parameters
|
|||
- bcdDevice -- USB Device version (BCD) (16 bit integer)
|
||||
- iManufacturer -- USB Manufacturer string (string)
|
||||
- iProduct -- USB Product string (string)
|
||||
- iSerialNumber -- SerialNumber string (sting)
|
||||
- iSerialNumber -- SerialNumber string (string)
|
||||
|
||||
sysfs entries
|
||||
=============
|
||||
|
|
|
@ -18986,7 +18986,7 @@ M: Mika Westerberg <mika.westerberg@linux.intel.com>
|
|||
M: Yehezkel Bernat <YehezkelShB@gmail.com>
|
||||
L: netdev@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/net/thunderbolt.c
|
||||
F: drivers/net/thunderbolt/
|
||||
|
||||
THUNDERX GPIO DRIVER
|
||||
M: Robert Richter <rric@kernel.org>
|
||||
|
|
|
@ -6,6 +6,9 @@ config MEDIA_COMMON_OPTIONS
|
|||
comment "common driver options"
|
||||
depends on MEDIA_COMMON_OPTIONS
|
||||
|
||||
config UVC_COMMON
|
||||
tristate
|
||||
|
||||
config VIDEO_CX2341X
|
||||
tristate
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
obj-y += b2c2/ saa7146/ siano/ v4l2-tpg/ videobuf2/
|
||||
obj-$(CONFIG_UVC_COMMON) += uvc.o
|
||||
obj-$(CONFIG_VIDEO_CX2341X) += cx2341x.o
|
||||
obj-$(CONFIG_VIDEO_TVEEPROM) += tveeprom.o
|
||||
obj-$(CONFIG_CYPRESS_FIRMWARE) += cypress_firmware.o
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/usb/uvc.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
* Video formats
|
||||
*/
|
||||
|
||||
static const struct uvc_format_desc uvc_fmts[] = {
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_YUY2,
|
||||
.fcc = V4L2_PIX_FMT_YUYV,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_YUY2_ISIGHT,
|
||||
.fcc = V4L2_PIX_FMT_YUYV,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_NV12,
|
||||
.fcc = V4L2_PIX_FMT_NV12,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_MJPEG,
|
||||
.fcc = V4L2_PIX_FMT_MJPEG,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_YV12,
|
||||
.fcc = V4L2_PIX_FMT_YVU420,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_I420,
|
||||
.fcc = V4L2_PIX_FMT_YUV420,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_M420,
|
||||
.fcc = V4L2_PIX_FMT_M420,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_UYVY,
|
||||
.fcc = V4L2_PIX_FMT_UYVY,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_Y800,
|
||||
.fcc = V4L2_PIX_FMT_GREY,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_Y8,
|
||||
.fcc = V4L2_PIX_FMT_GREY,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_D3DFMT_L8,
|
||||
.fcc = V4L2_PIX_FMT_GREY,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_KSMEDIA_L8_IR,
|
||||
.fcc = V4L2_PIX_FMT_GREY,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_Y10,
|
||||
.fcc = V4L2_PIX_FMT_Y10,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_Y12,
|
||||
.fcc = V4L2_PIX_FMT_Y12,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_Y16,
|
||||
.fcc = V4L2_PIX_FMT_Y16,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_BY8,
|
||||
.fcc = V4L2_PIX_FMT_SBGGR8,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_BA81,
|
||||
.fcc = V4L2_PIX_FMT_SBGGR8,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_GBRG,
|
||||
.fcc = V4L2_PIX_FMT_SGBRG8,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_GRBG,
|
||||
.fcc = V4L2_PIX_FMT_SGRBG8,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_RGGB,
|
||||
.fcc = V4L2_PIX_FMT_SRGGB8,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_RGBP,
|
||||
.fcc = V4L2_PIX_FMT_RGB565,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_BGR3,
|
||||
.fcc = V4L2_PIX_FMT_BGR24,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_BGR4,
|
||||
.fcc = V4L2_PIX_FMT_XBGR32,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_H264,
|
||||
.fcc = V4L2_PIX_FMT_H264,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_H265,
|
||||
.fcc = V4L2_PIX_FMT_HEVC,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_Y8I,
|
||||
.fcc = V4L2_PIX_FMT_Y8I,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_Y12I,
|
||||
.fcc = V4L2_PIX_FMT_Y12I,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_Z16,
|
||||
.fcc = V4L2_PIX_FMT_Z16,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_RW10,
|
||||
.fcc = V4L2_PIX_FMT_SRGGB10P,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_BG16,
|
||||
.fcc = V4L2_PIX_FMT_SBGGR16,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_GB16,
|
||||
.fcc = V4L2_PIX_FMT_SGBRG16,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_RG16,
|
||||
.fcc = V4L2_PIX_FMT_SRGGB16,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_GR16,
|
||||
.fcc = V4L2_PIX_FMT_SGRBG16,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_INVZ,
|
||||
.fcc = V4L2_PIX_FMT_Z16,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_INVI,
|
||||
.fcc = V4L2_PIX_FMT_Y10,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_INZI,
|
||||
.fcc = V4L2_PIX_FMT_INZI,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_CNF4,
|
||||
.fcc = V4L2_PIX_FMT_CNF4,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_HEVC,
|
||||
.fcc = V4L2_PIX_FMT_HEVC,
|
||||
},
|
||||
};
|
||||
|
||||
const struct uvc_format_desc *uvc_format_by_guid(const u8 guid[16])
|
||||
{
|
||||
unsigned int len = ARRAY_SIZE(uvc_fmts);
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < len; ++i) {
|
||||
if (memcmp(guid, uvc_fmts[i].guid, 16) == 0)
|
||||
return &uvc_fmts[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uvc_format_by_guid);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
|
@ -1165,7 +1165,7 @@ static int af9015_rc_query(struct dvb_usb_device *d)
|
|||
/* If any of these are non-zero, assume invalid data */
|
||||
if (buf[1] || buf[2] || buf[3]) {
|
||||
dev_dbg(&intf->dev, "invalid data\n");
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check for repeat of previous code */
|
||||
|
@ -1174,7 +1174,7 @@ static int af9015_rc_query(struct dvb_usb_device *d)
|
|||
dev_dbg(&intf->dev, "key repeated\n");
|
||||
rc_repeat(d->rc_dev);
|
||||
state->rc_repeat = buf[6];
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Only process key if canary killed */
|
||||
|
|
|
@ -179,6 +179,8 @@ static void smsusb_stop_streaming(struct smsusb_device_t *dev)
|
|||
|
||||
for (i = 0; i < MAX_URBS; i++) {
|
||||
usb_kill_urb(&dev->surbs[i].urb);
|
||||
if (dev->surbs[i].wq.func)
|
||||
cancel_work_sync(&dev->surbs[i].wq);
|
||||
|
||||
if (dev->surbs[i].cb) {
|
||||
smscore_putbuffer(dev->coredev, dev->surbs[i].cb);
|
||||
|
|
|
@ -3,6 +3,7 @@ config USB_VIDEO_CLASS
|
|||
tristate "USB Video Class (UVC)"
|
||||
depends on VIDEO_V4L2
|
||||
select VIDEOBUF2_VMALLOC
|
||||
select UVC_COMMON
|
||||
help
|
||||
Support for the USB Video Class (UVC). Currently only video
|
||||
input devices, such as webcams, are supported.
|
||||
|
|
|
@ -6,19 +6,21 @@
|
|||
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
|
||||
*/
|
||||
|
||||
#include <asm/barrier.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/usb/uvc.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <media/v4l2-ctrls.h>
|
||||
#include <media/v4l2-uvc.h>
|
||||
|
||||
#include "uvcvideo.h"
|
||||
|
||||
|
@ -363,19 +365,45 @@ static const u32 uvc_control_classes[] = {
|
|||
V4L2_CID_USER_CLASS,
|
||||
};
|
||||
|
||||
static const struct uvc_menu_info power_line_frequency_controls[] = {
|
||||
{ 0, "Disabled" },
|
||||
{ 1, "50 Hz" },
|
||||
{ 2, "60 Hz" },
|
||||
{ 3, "Auto" },
|
||||
};
|
||||
static const int exposure_auto_mapping[] = { 2, 1, 4, 8 };
|
||||
|
||||
static const struct uvc_menu_info exposure_auto_controls[] = {
|
||||
{ 2, "Auto Mode" },
|
||||
{ 1, "Manual Mode" },
|
||||
{ 4, "Shutter Priority Mode" },
|
||||
{ 8, "Aperture Priority Mode" },
|
||||
};
|
||||
/*
|
||||
* This function translates the V4L2 menu index @idx, as exposed to userspace as
|
||||
* the V4L2 control value, to the corresponding UVC control value used by the
|
||||
* device. The custom menu_mapping in the control @mapping is used when
|
||||
* available, otherwise the function assumes that the V4L2 and UVC values are
|
||||
* identical.
|
||||
*
|
||||
* For controls of type UVC_CTRL_DATA_TYPE_BITMASK, the UVC control value is
|
||||
* expressed as a bitmask and is thus guaranteed to have a single bit set.
|
||||
*
|
||||
* The function returns -EINVAL if the V4L2 menu index @idx isn't valid for the
|
||||
* control, which includes all controls whose type isn't UVC_CTRL_DATA_TYPE_ENUM
|
||||
* or UVC_CTRL_DATA_TYPE_BITMASK.
|
||||
*/
|
||||
static int uvc_mapping_get_menu_value(const struct uvc_control_mapping *mapping,
|
||||
u32 idx)
|
||||
{
|
||||
if (!test_bit(idx, &mapping->menu_mask))
|
||||
return -EINVAL;
|
||||
|
||||
if (mapping->menu_mapping)
|
||||
return mapping->menu_mapping[idx];
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
static const char *
|
||||
uvc_mapping_get_menu_name(const struct uvc_control_mapping *mapping, u32 idx)
|
||||
{
|
||||
if (!test_bit(idx, &mapping->menu_mask))
|
||||
return NULL;
|
||||
|
||||
if (mapping->menu_names)
|
||||
return mapping->menu_names[idx];
|
||||
|
||||
return v4l2_ctrl_get_menu(mapping->id)[idx];
|
||||
}
|
||||
|
||||
static s32 uvc_ctrl_get_zoom(struct uvc_control_mapping *mapping,
|
||||
u8 query, const u8 *data)
|
||||
|
@ -524,8 +552,9 @@ static const struct uvc_control_mapping uvc_ctrl_mappings[] = {
|
|||
.offset = 0,
|
||||
.v4l2_type = V4L2_CTRL_TYPE_MENU,
|
||||
.data_type = UVC_CTRL_DATA_TYPE_BITMASK,
|
||||
.menu_info = exposure_auto_controls,
|
||||
.menu_count = ARRAY_SIZE(exposure_auto_controls),
|
||||
.menu_mapping = exposure_auto_mapping,
|
||||
.menu_mask = GENMASK(V4L2_EXPOSURE_APERTURE_PRIORITY,
|
||||
V4L2_EXPOSURE_AUTO),
|
||||
.slave_ids = { V4L2_CID_EXPOSURE_ABSOLUTE, },
|
||||
},
|
||||
{
|
||||
|
@ -721,32 +750,50 @@ static const struct uvc_control_mapping uvc_ctrl_mappings[] = {
|
|||
},
|
||||
};
|
||||
|
||||
static const struct uvc_control_mapping uvc_ctrl_mappings_uvc11[] = {
|
||||
{
|
||||
.id = V4L2_CID_POWER_LINE_FREQUENCY,
|
||||
.entity = UVC_GUID_UVC_PROCESSING,
|
||||
.selector = UVC_PU_POWER_LINE_FREQUENCY_CONTROL,
|
||||
.size = 2,
|
||||
.offset = 0,
|
||||
.v4l2_type = V4L2_CTRL_TYPE_MENU,
|
||||
.data_type = UVC_CTRL_DATA_TYPE_ENUM,
|
||||
.menu_info = power_line_frequency_controls,
|
||||
.menu_count = ARRAY_SIZE(power_line_frequency_controls) - 1,
|
||||
},
|
||||
const struct uvc_control_mapping uvc_ctrl_power_line_mapping_limited = {
|
||||
.id = V4L2_CID_POWER_LINE_FREQUENCY,
|
||||
.entity = UVC_GUID_UVC_PROCESSING,
|
||||
.selector = UVC_PU_POWER_LINE_FREQUENCY_CONTROL,
|
||||
.size = 2,
|
||||
.offset = 0,
|
||||
.v4l2_type = V4L2_CTRL_TYPE_MENU,
|
||||
.data_type = UVC_CTRL_DATA_TYPE_ENUM,
|
||||
.menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_60HZ,
|
||||
V4L2_CID_POWER_LINE_FREQUENCY_50HZ),
|
||||
};
|
||||
|
||||
static const struct uvc_control_mapping uvc_ctrl_mappings_uvc15[] = {
|
||||
{
|
||||
.id = V4L2_CID_POWER_LINE_FREQUENCY,
|
||||
.entity = UVC_GUID_UVC_PROCESSING,
|
||||
.selector = UVC_PU_POWER_LINE_FREQUENCY_CONTROL,
|
||||
.size = 2,
|
||||
.offset = 0,
|
||||
.v4l2_type = V4L2_CTRL_TYPE_MENU,
|
||||
.data_type = UVC_CTRL_DATA_TYPE_ENUM,
|
||||
.menu_info = power_line_frequency_controls,
|
||||
.menu_count = ARRAY_SIZE(power_line_frequency_controls),
|
||||
},
|
||||
const struct uvc_control_mapping uvc_ctrl_power_line_mapping_uvc11 = {
|
||||
.id = V4L2_CID_POWER_LINE_FREQUENCY,
|
||||
.entity = UVC_GUID_UVC_PROCESSING,
|
||||
.selector = UVC_PU_POWER_LINE_FREQUENCY_CONTROL,
|
||||
.size = 2,
|
||||
.offset = 0,
|
||||
.v4l2_type = V4L2_CTRL_TYPE_MENU,
|
||||
.data_type = UVC_CTRL_DATA_TYPE_ENUM,
|
||||
.menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_60HZ,
|
||||
V4L2_CID_POWER_LINE_FREQUENCY_DISABLED),
|
||||
};
|
||||
|
||||
static const struct uvc_control_mapping *uvc_ctrl_mappings_uvc11[] = {
|
||||
&uvc_ctrl_power_line_mapping_uvc11,
|
||||
NULL, /* Sentinel */
|
||||
};
|
||||
|
||||
static const struct uvc_control_mapping uvc_ctrl_power_line_mapping_uvc15 = {
|
||||
.id = V4L2_CID_POWER_LINE_FREQUENCY,
|
||||
.entity = UVC_GUID_UVC_PROCESSING,
|
||||
.selector = UVC_PU_POWER_LINE_FREQUENCY_CONTROL,
|
||||
.size = 2,
|
||||
.offset = 0,
|
||||
.v4l2_type = V4L2_CTRL_TYPE_MENU,
|
||||
.data_type = UVC_CTRL_DATA_TYPE_ENUM,
|
||||
.menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_AUTO,
|
||||
V4L2_CID_POWER_LINE_FREQUENCY_DISABLED),
|
||||
};
|
||||
|
||||
static const struct uvc_control_mapping *uvc_ctrl_mappings_uvc15[] = {
|
||||
&uvc_ctrl_power_line_mapping_uvc15,
|
||||
NULL, /* Sentinel */
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
|
@ -972,11 +1019,17 @@ static s32 __uvc_ctrl_get_value(struct uvc_control_mapping *mapping,
|
|||
s32 value = mapping->get(mapping, UVC_GET_CUR, data);
|
||||
|
||||
if (mapping->v4l2_type == V4L2_CTRL_TYPE_MENU) {
|
||||
const struct uvc_menu_info *menu = mapping->menu_info;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < mapping->menu_count; ++i, ++menu) {
|
||||
if (menu->value == value) {
|
||||
for (i = 0; BIT(i) <= mapping->menu_mask; ++i) {
|
||||
u32 menu_value;
|
||||
|
||||
if (!test_bit(i, &mapping->menu_mask))
|
||||
continue;
|
||||
|
||||
menu_value = uvc_mapping_get_menu_value(mapping, i);
|
||||
|
||||
if (menu_value == value) {
|
||||
value = i;
|
||||
break;
|
||||
}
|
||||
|
@ -1085,11 +1138,28 @@ static int uvc_query_v4l2_class(struct uvc_video_chain *chain, u32 req_id,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if control @v4l2_id can be accessed by the given control @ioctl
|
||||
* (VIDIOC_G_EXT_CTRLS, VIDIOC_TRY_EXT_CTRLS or VIDIOC_S_EXT_CTRLS).
|
||||
*
|
||||
* For set operations on slave controls, check if the master's value is set to
|
||||
* manual, either in the others controls set in the same ioctl call, or from
|
||||
* the master's current value. This catches VIDIOC_S_EXT_CTRLS calls that set
|
||||
* both the master and slave control, such as for instance setting
|
||||
* auto_exposure=1, exposure_time_absolute=251.
|
||||
*/
|
||||
int uvc_ctrl_is_accessible(struct uvc_video_chain *chain, u32 v4l2_id,
|
||||
bool read)
|
||||
const struct v4l2_ext_controls *ctrls,
|
||||
unsigned long ioctl)
|
||||
{
|
||||
struct uvc_control_mapping *master_map = NULL;
|
||||
struct uvc_control *master_ctrl = NULL;
|
||||
struct uvc_control_mapping *mapping;
|
||||
struct uvc_control *ctrl;
|
||||
bool read = ioctl == VIDIOC_G_EXT_CTRLS;
|
||||
s32 val;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
if (__uvc_query_v4l2_class(chain, v4l2_id, 0) >= 0)
|
||||
return -EACCES;
|
||||
|
@ -1104,6 +1174,29 @@ int uvc_ctrl_is_accessible(struct uvc_video_chain *chain, u32 v4l2_id,
|
|||
if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR) && !read)
|
||||
return -EACCES;
|
||||
|
||||
if (ioctl != VIDIOC_S_EXT_CTRLS || !mapping->master_id)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Iterate backwards in cases where the master control is accessed
|
||||
* multiple times in the same ioctl. We want the last value.
|
||||
*/
|
||||
for (i = ctrls->count - 1; i >= 0; i--) {
|
||||
if (ctrls->controls[i].id == mapping->master_id)
|
||||
return ctrls->controls[i].value ==
|
||||
mapping->master_manual ? 0 : -EACCES;
|
||||
}
|
||||
|
||||
__uvc_find_control(ctrl->entity, mapping->master_id, &master_map,
|
||||
&master_ctrl, 0);
|
||||
|
||||
if (!master_ctrl || !(master_ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR))
|
||||
return 0;
|
||||
|
||||
ret = __uvc_ctrl_get(chain, master_ctrl, master_map, &val);
|
||||
if (ret >= 0 && val != mapping->master_manual)
|
||||
return -EACCES;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1121,6 +1214,25 @@ static const char *uvc_map_get_name(const struct uvc_control_mapping *map)
|
|||
return "Unknown Control";
|
||||
}
|
||||
|
||||
static u32 uvc_get_ctrl_bitmap(struct uvc_control *ctrl,
|
||||
struct uvc_control_mapping *mapping)
|
||||
{
|
||||
/*
|
||||
* Some controls, like CT_AE_MODE_CONTROL, use GET_RES to represent
|
||||
* the number of bits supported. Those controls do not list GET_MAX
|
||||
* as supported.
|
||||
*/
|
||||
if (ctrl->info.flags & UVC_CTRL_FLAG_GET_RES)
|
||||
return mapping->get(mapping, UVC_GET_RES,
|
||||
uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES));
|
||||
|
||||
if (ctrl->info.flags & UVC_CTRL_FLAG_GET_MAX)
|
||||
return mapping->get(mapping, UVC_GET_MAX,
|
||||
uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MAX));
|
||||
|
||||
return ~0;
|
||||
}
|
||||
|
||||
static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
|
||||
struct uvc_control *ctrl,
|
||||
struct uvc_control_mapping *mapping,
|
||||
|
@ -1128,7 +1240,6 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
|
|||
{
|
||||
struct uvc_control_mapping *master_map = NULL;
|
||||
struct uvc_control *master_ctrl = NULL;
|
||||
const struct uvc_menu_info *menu;
|
||||
unsigned int i;
|
||||
|
||||
memset(v4l2_ctrl, 0, sizeof(*v4l2_ctrl));
|
||||
|
@ -1169,13 +1280,19 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
|
|||
|
||||
switch (mapping->v4l2_type) {
|
||||
case V4L2_CTRL_TYPE_MENU:
|
||||
v4l2_ctrl->minimum = 0;
|
||||
v4l2_ctrl->maximum = mapping->menu_count - 1;
|
||||
v4l2_ctrl->minimum = ffs(mapping->menu_mask) - 1;
|
||||
v4l2_ctrl->maximum = fls(mapping->menu_mask) - 1;
|
||||
v4l2_ctrl->step = 1;
|
||||
|
||||
menu = mapping->menu_info;
|
||||
for (i = 0; i < mapping->menu_count; ++i, ++menu) {
|
||||
if (menu->value == v4l2_ctrl->default_value) {
|
||||
for (i = 0; BIT(i) <= mapping->menu_mask; ++i) {
|
||||
u32 menu_value;
|
||||
|
||||
if (!test_bit(i, &mapping->menu_mask))
|
||||
continue;
|
||||
|
||||
menu_value = uvc_mapping_get_menu_value(mapping, i);
|
||||
|
||||
if (menu_value == v4l2_ctrl->default_value) {
|
||||
v4l2_ctrl->default_value = i;
|
||||
break;
|
||||
}
|
||||
|
@ -1195,6 +1312,12 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
|
|||
v4l2_ctrl->step = 0;
|
||||
return 0;
|
||||
|
||||
case V4L2_CTRL_TYPE_BITMASK:
|
||||
v4l2_ctrl->minimum = 0;
|
||||
v4l2_ctrl->maximum = uvc_get_ctrl_bitmap(ctrl, mapping);
|
||||
v4l2_ctrl->step = 0;
|
||||
return 0;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -1268,11 +1391,11 @@ done:
|
|||
int uvc_query_v4l2_menu(struct uvc_video_chain *chain,
|
||||
struct v4l2_querymenu *query_menu)
|
||||
{
|
||||
const struct uvc_menu_info *menu_info;
|
||||
struct uvc_control_mapping *mapping;
|
||||
struct uvc_control *ctrl;
|
||||
u32 index = query_menu->index;
|
||||
u32 id = query_menu->id;
|
||||
const char *name;
|
||||
int ret;
|
||||
|
||||
memset(query_menu, 0, sizeof(*query_menu));
|
||||
|
@ -1289,16 +1412,13 @@ int uvc_query_v4l2_menu(struct uvc_video_chain *chain,
|
|||
goto done;
|
||||
}
|
||||
|
||||
if (query_menu->index >= mapping->menu_count) {
|
||||
if (!test_bit(query_menu->index, &mapping->menu_mask)) {
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
menu_info = &mapping->menu_info[query_menu->index];
|
||||
|
||||
if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK &&
|
||||
(ctrl->info.flags & UVC_CTRL_FLAG_GET_RES)) {
|
||||
s32 bitmap;
|
||||
if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK) {
|
||||
int mask;
|
||||
|
||||
if (!ctrl->cached) {
|
||||
ret = uvc_ctrl_populate_cache(chain, ctrl);
|
||||
|
@ -1306,15 +1426,25 @@ int uvc_query_v4l2_menu(struct uvc_video_chain *chain,
|
|||
goto done;
|
||||
}
|
||||
|
||||
bitmap = mapping->get(mapping, UVC_GET_RES,
|
||||
uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES));
|
||||
if (!(bitmap & menu_info->value)) {
|
||||
mask = uvc_mapping_get_menu_value(mapping, query_menu->index);
|
||||
if (mask < 0) {
|
||||
ret = mask;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!(uvc_get_ctrl_bitmap(ctrl, mapping) & mask)) {
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
strscpy(query_menu->name, menu_info->name, sizeof(query_menu->name));
|
||||
name = uvc_mapping_get_menu_name(mapping, query_menu->index);
|
||||
if (!name) {
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
strscpy(query_menu->name, name, sizeof(query_menu->name));
|
||||
|
||||
done:
|
||||
mutex_unlock(&chain->ctrl_mutex);
|
||||
|
@ -1442,6 +1572,10 @@ static void uvc_ctrl_status_event_work(struct work_struct *work)
|
|||
|
||||
uvc_ctrl_status_event(w->chain, w->ctrl, w->data);
|
||||
|
||||
/* The barrier is needed to synchronize with uvc_status_stop(). */
|
||||
if (smp_load_acquire(&dev->flush_status))
|
||||
return;
|
||||
|
||||
/* Resubmit the URB. */
|
||||
w->urb->interval = dev->int_ep->desc.bInterval;
|
||||
ret = usb_submit_urb(w->urb, GFP_KERNEL);
|
||||
|
@ -1791,31 +1925,44 @@ int uvc_ctrl_set(struct uvc_fh *handle,
|
|||
value = xctrl->value;
|
||||
break;
|
||||
|
||||
case V4L2_CTRL_TYPE_BITMASK:
|
||||
if (!ctrl->cached) {
|
||||
ret = uvc_ctrl_populate_cache(chain, ctrl);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
xctrl->value &= uvc_get_ctrl_bitmap(ctrl, mapping);
|
||||
value = xctrl->value;
|
||||
break;
|
||||
|
||||
case V4L2_CTRL_TYPE_BOOLEAN:
|
||||
xctrl->value = clamp(xctrl->value, 0, 1);
|
||||
value = xctrl->value;
|
||||
break;
|
||||
|
||||
case V4L2_CTRL_TYPE_MENU:
|
||||
if (xctrl->value < 0 || xctrl->value >= mapping->menu_count)
|
||||
if (xctrl->value < (ffs(mapping->menu_mask) - 1) ||
|
||||
xctrl->value > (fls(mapping->menu_mask) - 1))
|
||||
return -ERANGE;
|
||||
value = mapping->menu_info[xctrl->value].value;
|
||||
|
||||
if (!test_bit(xctrl->value, &mapping->menu_mask))
|
||||
return -EINVAL;
|
||||
|
||||
value = uvc_mapping_get_menu_value(mapping, xctrl->value);
|
||||
|
||||
/*
|
||||
* Valid menu indices are reported by the GET_RES request for
|
||||
* UVC controls that support it.
|
||||
*/
|
||||
if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK &&
|
||||
(ctrl->info.flags & UVC_CTRL_FLAG_GET_RES)) {
|
||||
if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK) {
|
||||
if (!ctrl->cached) {
|
||||
ret = uvc_ctrl_populate_cache(chain, ctrl);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
step = mapping->get(mapping, UVC_GET_RES,
|
||||
uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES));
|
||||
if (!(step & value))
|
||||
if (!(uvc_get_ctrl_bitmap(ctrl, mapping) & value))
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
@ -2218,31 +2365,42 @@ static int __uvc_ctrl_add_mapping(struct uvc_video_chain *chain,
|
|||
unsigned int i;
|
||||
|
||||
/*
|
||||
* Most mappings come from static kernel data and need to be duplicated.
|
||||
* Most mappings come from static kernel data, and need to be duplicated.
|
||||
* Mappings that come from userspace will be unnecessarily duplicated,
|
||||
* this could be optimized.
|
||||
*/
|
||||
map = kmemdup(mapping, sizeof(*mapping), GFP_KERNEL);
|
||||
if (map == NULL)
|
||||
if (!map)
|
||||
return -ENOMEM;
|
||||
|
||||
map->name = NULL;
|
||||
map->menu_names = NULL;
|
||||
map->menu_mapping = NULL;
|
||||
|
||||
/* For UVCIOC_CTRL_MAP custom control */
|
||||
if (mapping->name) {
|
||||
map->name = kstrdup(mapping->name, GFP_KERNEL);
|
||||
if (!map->name) {
|
||||
kfree(map);
|
||||
return -ENOMEM;
|
||||
}
|
||||
if (!map->name)
|
||||
goto err_nomem;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&map->ev_subs);
|
||||
|
||||
size = sizeof(*mapping->menu_info) * mapping->menu_count;
|
||||
map->menu_info = kmemdup(mapping->menu_info, size, GFP_KERNEL);
|
||||
if (map->menu_info == NULL) {
|
||||
kfree(map->name);
|
||||
kfree(map);
|
||||
return -ENOMEM;
|
||||
if (mapping->menu_mapping && mapping->menu_mask) {
|
||||
size = sizeof(mapping->menu_mapping[0])
|
||||
* fls(mapping->menu_mask);
|
||||
map->menu_mapping = kmemdup(mapping->menu_mapping, size,
|
||||
GFP_KERNEL);
|
||||
if (!map->menu_mapping)
|
||||
goto err_nomem;
|
||||
}
|
||||
if (mapping->menu_names && mapping->menu_mask) {
|
||||
size = sizeof(mapping->menu_names[0])
|
||||
* fls(mapping->menu_mask);
|
||||
map->menu_names = kmemdup(mapping->menu_names, size,
|
||||
GFP_KERNEL);
|
||||
if (!map->menu_names)
|
||||
goto err_nomem;
|
||||
}
|
||||
|
||||
if (map->get == NULL)
|
||||
|
@ -2264,6 +2422,13 @@ static int __uvc_ctrl_add_mapping(struct uvc_video_chain *chain,
|
|||
ctrl->info.selector);
|
||||
|
||||
return 0;
|
||||
|
||||
err_nomem:
|
||||
kfree(map->menu_names);
|
||||
kfree(map->menu_mapping);
|
||||
kfree(map->name);
|
||||
kfree(map);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
int uvc_ctrl_add_mapping(struct uvc_video_chain *chain,
|
||||
|
@ -2421,8 +2586,7 @@ static void uvc_ctrl_prune_entity(struct uvc_device *dev,
|
|||
static void uvc_ctrl_init_ctrl(struct uvc_video_chain *chain,
|
||||
struct uvc_control *ctrl)
|
||||
{
|
||||
const struct uvc_control_mapping *mappings;
|
||||
unsigned int num_mappings;
|
||||
const struct uvc_control_mapping **mappings;
|
||||
unsigned int i;
|
||||
|
||||
/*
|
||||
|
@ -2489,16 +2653,11 @@ static void uvc_ctrl_init_ctrl(struct uvc_video_chain *chain,
|
|||
}
|
||||
|
||||
/* Finally process version-specific mappings. */
|
||||
if (chain->dev->uvc_version < 0x0150) {
|
||||
mappings = uvc_ctrl_mappings_uvc11;
|
||||
num_mappings = ARRAY_SIZE(uvc_ctrl_mappings_uvc11);
|
||||
} else {
|
||||
mappings = uvc_ctrl_mappings_uvc15;
|
||||
num_mappings = ARRAY_SIZE(uvc_ctrl_mappings_uvc15);
|
||||
}
|
||||
mappings = chain->dev->uvc_version < 0x0150
|
||||
? uvc_ctrl_mappings_uvc11 : uvc_ctrl_mappings_uvc15;
|
||||
|
||||
for (i = 0; i < num_mappings; ++i) {
|
||||
const struct uvc_control_mapping *mapping = &mappings[i];
|
||||
for (i = 0; mappings[i]; ++i) {
|
||||
const struct uvc_control_mapping *mapping = mappings[i];
|
||||
|
||||
if (uvc_entity_match_guid(ctrl->entity, mapping->entity) &&
|
||||
ctrl->info.selector == mapping->selector)
|
||||
|
@ -2591,7 +2750,8 @@ static void uvc_ctrl_cleanup_mappings(struct uvc_device *dev,
|
|||
|
||||
list_for_each_entry_safe(mapping, nm, &ctrl->info.mappings, list) {
|
||||
list_del(&mapping->list);
|
||||
kfree(mapping->menu_info);
|
||||
kfree(mapping->menu_names);
|
||||
kfree(mapping->menu_mapping);
|
||||
kfree(mapping->name);
|
||||
kfree(mapping);
|
||||
}
|
||||
|
|
|
@ -7,12 +7,14 @@
|
|||
*/
|
||||
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/usb/uvc.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/wait.h>
|
||||
|
@ -20,7 +22,6 @@
|
|||
|
||||
#include <media/v4l2-common.h>
|
||||
#include <media/v4l2-ioctl.h>
|
||||
#include <media/v4l2-uvc.h>
|
||||
|
||||
#include "uvcvideo.h"
|
||||
|
||||
|
@ -224,7 +225,7 @@ static int uvc_parse_format(struct uvc_device *dev,
|
|||
{
|
||||
struct usb_interface *intf = streaming->intf;
|
||||
struct usb_host_interface *alts = intf->cur_altsetting;
|
||||
struct uvc_format_desc *fmtdesc;
|
||||
const struct uvc_format_desc *fmtdesc;
|
||||
struct uvc_frame *frame;
|
||||
const unsigned char *start = buffer;
|
||||
unsigned int width_multiplier = 1;
|
||||
|
@ -250,18 +251,17 @@ static int uvc_parse_format(struct uvc_device *dev,
|
|||
/* Find the format descriptor from its GUID. */
|
||||
fmtdesc = uvc_format_by_guid(&buffer[5]);
|
||||
|
||||
if (fmtdesc != NULL) {
|
||||
strscpy(format->name, fmtdesc->name,
|
||||
sizeof(format->name));
|
||||
format->fcc = fmtdesc->fcc;
|
||||
} else {
|
||||
if (!fmtdesc) {
|
||||
/*
|
||||
* Unknown video formats are not fatal errors, the
|
||||
* caller will skip this descriptor.
|
||||
*/
|
||||
dev_info(&streaming->intf->dev,
|
||||
"Unknown video format %pUl\n", &buffer[5]);
|
||||
snprintf(format->name, sizeof(format->name), "%pUl\n",
|
||||
&buffer[5]);
|
||||
format->fcc = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
format->fcc = fmtdesc->fcc;
|
||||
format->bpp = buffer[21];
|
||||
|
||||
/*
|
||||
|
@ -270,8 +270,6 @@ static int uvc_parse_format(struct uvc_device *dev,
|
|||
*/
|
||||
if (dev->quirks & UVC_QUIRK_FORCE_Y8) {
|
||||
if (format->fcc == V4L2_PIX_FMT_YUYV) {
|
||||
strscpy(format->name, "Greyscale 8-bit (Y8 )",
|
||||
sizeof(format->name));
|
||||
format->fcc = V4L2_PIX_FMT_GREY;
|
||||
format->bpp = 8;
|
||||
width_multiplier = 2;
|
||||
|
@ -312,7 +310,6 @@ static int uvc_parse_format(struct uvc_device *dev,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
strscpy(format->name, "MJPEG", sizeof(format->name));
|
||||
format->fcc = V4L2_PIX_FMT_MJPEG;
|
||||
format->flags = UVC_FMT_FLAG_COMPRESSED;
|
||||
format->bpp = 0;
|
||||
|
@ -328,17 +325,7 @@ static int uvc_parse_format(struct uvc_device *dev,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (buffer[8] & 0x7f) {
|
||||
case 0:
|
||||
strscpy(format->name, "SD-DV", sizeof(format->name));
|
||||
break;
|
||||
case 1:
|
||||
strscpy(format->name, "SDL-DV", sizeof(format->name));
|
||||
break;
|
||||
case 2:
|
||||
strscpy(format->name, "HD-DV", sizeof(format->name));
|
||||
break;
|
||||
default:
|
||||
if ((buffer[8] & 0x7f) > 2) {
|
||||
uvc_dbg(dev, DESCR,
|
||||
"device %d videostreaming interface %d: unknown DV format %u\n",
|
||||
dev->udev->devnum,
|
||||
|
@ -346,9 +333,6 @@ static int uvc_parse_format(struct uvc_device *dev,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
strlcat(format->name, buffer[8] & (1 << 7) ? " 60Hz" : " 50Hz",
|
||||
sizeof(format->name));
|
||||
|
||||
format->fcc = V4L2_PIX_FMT_DV;
|
||||
format->flags = UVC_FMT_FLAG_COMPRESSED | UVC_FMT_FLAG_STREAM;
|
||||
format->bpp = 0;
|
||||
|
@ -375,7 +359,7 @@ static int uvc_parse_format(struct uvc_device *dev,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
uvc_dbg(dev, DESCR, "Found format %s\n", format->name);
|
||||
uvc_dbg(dev, DESCR, "Found format %p4cc", &format->fcc);
|
||||
|
||||
buflen -= buffer[0];
|
||||
buffer += buffer[0];
|
||||
|
@ -694,7 +678,7 @@ static int uvc_parse_streaming(struct uvc_device *dev,
|
|||
interval = (u32 *)&frame[nframes];
|
||||
|
||||
streaming->format = format;
|
||||
streaming->nformats = nformats;
|
||||
streaming->nformats = 0;
|
||||
|
||||
/* Parse the format descriptors. */
|
||||
while (buflen > 2 && buffer[1] == USB_DT_CS_INTERFACE) {
|
||||
|
@ -708,7 +692,10 @@ static int uvc_parse_streaming(struct uvc_device *dev,
|
|||
&interval, buffer, buflen);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
if (!ret)
|
||||
break;
|
||||
|
||||
streaming->nformats++;
|
||||
frame += format->nframes;
|
||||
format++;
|
||||
|
||||
|
@ -732,6 +719,7 @@ static int uvc_parse_streaming(struct uvc_device *dev,
|
|||
/* Parse the alternate settings to find the maximum bandwidth. */
|
||||
for (i = 0; i < intf->num_altsetting; ++i) {
|
||||
struct usb_host_endpoint *ep;
|
||||
|
||||
alts = &intf->altsetting[i];
|
||||
ep = uvc_find_endpoint(alts,
|
||||
streaming->header.bEndpointAddress);
|
||||
|
@ -813,6 +801,27 @@ static struct uvc_entity *uvc_alloc_entity(u16 type, u16 id,
|
|||
return entity;
|
||||
}
|
||||
|
||||
static void uvc_entity_set_name(struct uvc_device *dev, struct uvc_entity *entity,
|
||||
const char *type_name, u8 string_id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* First attempt to read the entity name from the device. If the entity
|
||||
* has no associated string, or if reading the string fails (most
|
||||
* likely due to a buggy firmware), fall back to default names based on
|
||||
* the entity type.
|
||||
*/
|
||||
if (string_id) {
|
||||
ret = usb_string(dev->udev, string_id, entity->name,
|
||||
sizeof(entity->name));
|
||||
if (!ret)
|
||||
return;
|
||||
}
|
||||
|
||||
sprintf(entity->name, "%s %u", type_name, entity->id);
|
||||
}
|
||||
|
||||
/* Parse vendor-specific extensions. */
|
||||
static int uvc_parse_vendor_control(struct uvc_device *dev,
|
||||
const unsigned char *buffer, int buflen)
|
||||
|
@ -879,11 +888,7 @@ static int uvc_parse_vendor_control(struct uvc_device *dev,
|
|||
+ n;
|
||||
memcpy(unit->extension.bmControls, &buffer[23+p], 2*n);
|
||||
|
||||
if (buffer[24+p+2*n] != 0)
|
||||
usb_string(udev, buffer[24+p+2*n], unit->name,
|
||||
sizeof(unit->name));
|
||||
else
|
||||
sprintf(unit->name, "Extension %u", buffer[3]);
|
||||
uvc_entity_set_name(dev, unit, "Extension", buffer[24+p+2*n]);
|
||||
|
||||
list_add_tail(&unit->list, &dev->entities);
|
||||
handled = 1;
|
||||
|
@ -901,6 +906,7 @@ static int uvc_parse_standard_control(struct uvc_device *dev,
|
|||
struct usb_interface *intf;
|
||||
struct usb_host_interface *alts = dev->intf->cur_altsetting;
|
||||
unsigned int i, n, p, len;
|
||||
const char *type_name;
|
||||
u16 type;
|
||||
|
||||
switch (buffer[2]) {
|
||||
|
@ -1006,15 +1012,14 @@ static int uvc_parse_standard_control(struct uvc_device *dev,
|
|||
memcpy(term->media.bmTransportModes, &buffer[10+n], p);
|
||||
}
|
||||
|
||||
if (buffer[7] != 0)
|
||||
usb_string(udev, buffer[7], term->name,
|
||||
sizeof(term->name));
|
||||
else if (UVC_ENTITY_TYPE(term) == UVC_ITT_CAMERA)
|
||||
sprintf(term->name, "Camera %u", buffer[3]);
|
||||
if (UVC_ENTITY_TYPE(term) == UVC_ITT_CAMERA)
|
||||
type_name = "Camera";
|
||||
else if (UVC_ENTITY_TYPE(term) == UVC_ITT_MEDIA_TRANSPORT_INPUT)
|
||||
sprintf(term->name, "Media %u", buffer[3]);
|
||||
type_name = "Media";
|
||||
else
|
||||
sprintf(term->name, "Input %u", buffer[3]);
|
||||
type_name = "Input";
|
||||
|
||||
uvc_entity_set_name(dev, term, type_name, buffer[7]);
|
||||
|
||||
list_add_tail(&term->list, &dev->entities);
|
||||
break;
|
||||
|
@ -1047,11 +1052,7 @@ static int uvc_parse_standard_control(struct uvc_device *dev,
|
|||
|
||||
memcpy(term->baSourceID, &buffer[7], 1);
|
||||
|
||||
if (buffer[8] != 0)
|
||||
usb_string(udev, buffer[8], term->name,
|
||||
sizeof(term->name));
|
||||
else
|
||||
sprintf(term->name, "Output %u", buffer[3]);
|
||||
uvc_entity_set_name(dev, term, "Output", buffer[8]);
|
||||
|
||||
list_add_tail(&term->list, &dev->entities);
|
||||
break;
|
||||
|
@ -1072,11 +1073,7 @@ static int uvc_parse_standard_control(struct uvc_device *dev,
|
|||
|
||||
memcpy(unit->baSourceID, &buffer[5], p);
|
||||
|
||||
if (buffer[5+p] != 0)
|
||||
usb_string(udev, buffer[5+p], unit->name,
|
||||
sizeof(unit->name));
|
||||
else
|
||||
sprintf(unit->name, "Selector %u", buffer[3]);
|
||||
uvc_entity_set_name(dev, unit, "Selector", buffer[5+p]);
|
||||
|
||||
list_add_tail(&unit->list, &dev->entities);
|
||||
break;
|
||||
|
@ -1105,11 +1102,7 @@ static int uvc_parse_standard_control(struct uvc_device *dev,
|
|||
if (dev->uvc_version >= 0x0110)
|
||||
unit->processing.bmVideoStandards = buffer[9+n];
|
||||
|
||||
if (buffer[8+n] != 0)
|
||||
usb_string(udev, buffer[8+n], unit->name,
|
||||
sizeof(unit->name));
|
||||
else
|
||||
sprintf(unit->name, "Processing %u", buffer[3]);
|
||||
uvc_entity_set_name(dev, unit, "Processing", buffer[8+n]);
|
||||
|
||||
list_add_tail(&unit->list, &dev->entities);
|
||||
break;
|
||||
|
@ -1136,11 +1129,7 @@ static int uvc_parse_standard_control(struct uvc_device *dev,
|
|||
unit->extension.bmControls = (u8 *)unit + sizeof(*unit);
|
||||
memcpy(unit->extension.bmControls, &buffer[23+p], n);
|
||||
|
||||
if (buffer[23+p+n] != 0)
|
||||
usb_string(udev, buffer[23+p+n], unit->name,
|
||||
sizeof(unit->name));
|
||||
else
|
||||
sprintf(unit->name, "Extension %u", buffer[3]);
|
||||
uvc_entity_set_name(dev, unit, "Extension", buffer[23+p+n]);
|
||||
|
||||
list_add_tail(&unit->list, &dev->entities);
|
||||
break;
|
||||
|
@ -1173,7 +1162,8 @@ static int uvc_parse_control(struct uvc_device *dev)
|
|||
buffer[1] != USB_DT_CS_INTERFACE)
|
||||
goto next_descriptor;
|
||||
|
||||
if ((ret = uvc_parse_standard_control(dev, buffer, buflen)) < 0)
|
||||
ret = uvc_parse_standard_control(dev, buffer, buflen);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
next_descriptor:
|
||||
|
@ -1856,12 +1846,14 @@ static void uvc_delete(struct kref *kref)
|
|||
|
||||
list_for_each_safe(p, n, &dev->chains) {
|
||||
struct uvc_video_chain *chain;
|
||||
|
||||
chain = list_entry(p, struct uvc_video_chain, list);
|
||||
kfree(chain);
|
||||
}
|
||||
|
||||
list_for_each_safe(p, n, &dev->entities) {
|
||||
struct uvc_entity *entity;
|
||||
|
||||
entity = list_entry(p, struct uvc_entity, list);
|
||||
#ifdef CONFIG_MEDIA_CONTROLLER
|
||||
uvc_mc_cleanup_entity(entity);
|
||||
|
@ -1871,6 +1863,7 @@ static void uvc_delete(struct kref *kref)
|
|||
|
||||
list_for_each_safe(p, n, &dev->streams) {
|
||||
struct uvc_streaming *streaming;
|
||||
|
||||
streaming = list_entry(p, struct uvc_streaming, list);
|
||||
usb_driver_release_interface(&uvc_driver.driver,
|
||||
streaming->intf);
|
||||
|
@ -2206,7 +2199,8 @@ static int uvc_probe(struct usb_interface *intf,
|
|||
usb_set_intfdata(intf, dev);
|
||||
|
||||
/* Initialize the interrupt URB. */
|
||||
if ((ret = uvc_status_init(dev)) < 0) {
|
||||
ret = uvc_status_init(dev);
|
||||
if (ret < 0) {
|
||||
dev_info(&dev->udev->dev,
|
||||
"Unable to initialize the status endpoint (%d), status interrupt will not be supported.\n",
|
||||
ret);
|
||||
|
@ -2353,40 +2347,23 @@ static int uvc_clock_param_set(const char *val, const struct kernel_param *kp)
|
|||
}
|
||||
|
||||
module_param_call(clock, uvc_clock_param_set, uvc_clock_param_get,
|
||||
&uvc_clock_param, S_IRUGO|S_IWUSR);
|
||||
&uvc_clock_param, 0644);
|
||||
MODULE_PARM_DESC(clock, "Video buffers timestamp clock");
|
||||
module_param_named(hwtimestamps, uvc_hw_timestamps_param, uint, S_IRUGO|S_IWUSR);
|
||||
module_param_named(hwtimestamps, uvc_hw_timestamps_param, uint, 0644);
|
||||
MODULE_PARM_DESC(hwtimestamps, "Use hardware timestamps");
|
||||
module_param_named(nodrop, uvc_no_drop_param, uint, S_IRUGO|S_IWUSR);
|
||||
module_param_named(nodrop, uvc_no_drop_param, uint, 0644);
|
||||
MODULE_PARM_DESC(nodrop, "Don't drop incomplete frames");
|
||||
module_param_named(quirks, uvc_quirks_param, uint, S_IRUGO|S_IWUSR);
|
||||
module_param_named(quirks, uvc_quirks_param, uint, 0644);
|
||||
MODULE_PARM_DESC(quirks, "Forced device quirks");
|
||||
module_param_named(trace, uvc_dbg_param, uint, S_IRUGO|S_IWUSR);
|
||||
module_param_named(trace, uvc_dbg_param, uint, 0644);
|
||||
MODULE_PARM_DESC(trace, "Trace level bitmask");
|
||||
module_param_named(timeout, uvc_timeout_param, uint, S_IRUGO|S_IWUSR);
|
||||
module_param_named(timeout, uvc_timeout_param, uint, 0644);
|
||||
MODULE_PARM_DESC(timeout, "Streaming control requests timeout");
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
* Driver initialization and cleanup
|
||||
*/
|
||||
|
||||
static const struct uvc_menu_info power_line_frequency_controls_limited[] = {
|
||||
{ 1, "50 Hz" },
|
||||
{ 2, "60 Hz" },
|
||||
};
|
||||
|
||||
static const struct uvc_control_mapping uvc_ctrl_power_line_mapping_limited = {
|
||||
.id = V4L2_CID_POWER_LINE_FREQUENCY,
|
||||
.entity = UVC_GUID_UVC_PROCESSING,
|
||||
.selector = UVC_PU_POWER_LINE_FREQUENCY_CONTROL,
|
||||
.size = 2,
|
||||
.offset = 0,
|
||||
.v4l2_type = V4L2_CTRL_TYPE_MENU,
|
||||
.data_type = UVC_CTRL_DATA_TYPE_ENUM,
|
||||
.menu_info = power_line_frequency_controls_limited,
|
||||
.menu_count = ARRAY_SIZE(power_line_frequency_controls_limited),
|
||||
};
|
||||
|
||||
static const struct uvc_device_info uvc_ctrl_power_line_limited = {
|
||||
.mappings = (const struct uvc_control_mapping *[]) {
|
||||
&uvc_ctrl_power_line_mapping_limited,
|
||||
|
@ -2394,6 +2371,13 @@ static const struct uvc_device_info uvc_ctrl_power_line_limited = {
|
|||
},
|
||||
};
|
||||
|
||||
static const struct uvc_device_info uvc_ctrl_power_line_uvc11 = {
|
||||
.mappings = (const struct uvc_control_mapping *[]) {
|
||||
&uvc_ctrl_power_line_mapping_uvc11,
|
||||
NULL, /* Sentinel */
|
||||
},
|
||||
};
|
||||
|
||||
static const struct uvc_device_info uvc_quirk_probe_minmax = {
|
||||
.quirks = UVC_QUIRK_PROBE_MINMAX,
|
||||
};
|
||||
|
@ -2496,6 +2480,24 @@ static const struct usb_device_id uvc_ids[] = {
|
|||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = 0,
|
||||
.driver_info = (kernel_ulong_t)&uvc_quirk_probe_minmax },
|
||||
/* Logitech, Webcam C910 */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
.idVendor = 0x046d,
|
||||
.idProduct = 0x0821,
|
||||
.bInterfaceClass = USB_CLASS_VIDEO,
|
||||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = 0,
|
||||
.driver_info = UVC_INFO_QUIRK(UVC_QUIRK_WAKE_AUTOSUSPEND)},
|
||||
/* Logitech, Webcam B910 */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
.idVendor = 0x046d,
|
||||
.idProduct = 0x0823,
|
||||
.bInterfaceClass = USB_CLASS_VIDEO,
|
||||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = 0,
|
||||
.driver_info = UVC_INFO_QUIRK(UVC_QUIRK_WAKE_AUTOSUSPEND)},
|
||||
/* Logitech Quickcam Fusion */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
|
@ -2973,6 +2975,15 @@ static const struct usb_device_id uvc_ids[] = {
|
|||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = 0,
|
||||
.driver_info = UVC_INFO_QUIRK(UVC_QUIRK_FORCE_BPP) },
|
||||
/* Lenovo Integrated Camera */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
.idVendor = 0x30c9,
|
||||
.idProduct = 0x0093,
|
||||
.bInterfaceClass = USB_CLASS_VIDEO,
|
||||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = UVC_PC_PROTOCOL_15,
|
||||
.driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_uvc11 },
|
||||
/* Sonix Technology USB 2.0 Camera */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
|
@ -2991,6 +3002,15 @@ static const struct usb_device_id uvc_ids[] = {
|
|||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = 0,
|
||||
.driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited },
|
||||
/* Acer EasyCamera */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
.idVendor = 0x5986,
|
||||
.idProduct = 0x1180,
|
||||
.bInterfaceClass = USB_CLASS_VIDEO,
|
||||
.bInterfaceSubClass = 1,
|
||||
.bInterfaceProtocol = 0,
|
||||
.driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited },
|
||||
/* Intel RealSense D4M */
|
||||
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
|
|
|
@ -37,7 +37,7 @@ static int uvc_mc_create_links(struct uvc_video_chain *chain,
|
|||
continue;
|
||||
|
||||
remote = uvc_entity_by_id(chain->dev, entity->baSourceID[i]);
|
||||
if (remote == NULL)
|
||||
if (remote == NULL || remote->num_pads == 0)
|
||||
return -EINVAL;
|
||||
|
||||
source = (UVC_ENTITY_TYPE(remote) == UVC_TT_STREAMING)
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
|
||||
*/
|
||||
|
||||
#include <asm/barrier.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/slab.h>
|
||||
|
@ -18,11 +19,34 @@
|
|||
* Input device
|
||||
*/
|
||||
#ifdef CONFIG_USB_VIDEO_CLASS_INPUT_EVDEV
|
||||
|
||||
static bool uvc_input_has_button(struct uvc_device *dev)
|
||||
{
|
||||
struct uvc_streaming *stream;
|
||||
|
||||
/*
|
||||
* The device has button events if both bTriggerSupport and
|
||||
* bTriggerUsage are one. Otherwise the camera button does not
|
||||
* exist or is handled automatically by the camera without host
|
||||
* driver or client application intervention.
|
||||
*/
|
||||
list_for_each_entry(stream, &dev->streams, list) {
|
||||
if (stream->header.bTriggerSupport == 1 &&
|
||||
stream->header.bTriggerUsage == 1)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int uvc_input_init(struct uvc_device *dev)
|
||||
{
|
||||
struct input_dev *input;
|
||||
int ret;
|
||||
|
||||
if (!uvc_input_has_button(dev))
|
||||
return 0;
|
||||
|
||||
input = input_allocate_device();
|
||||
if (input == NULL)
|
||||
return -ENOMEM;
|
||||
|
@ -73,38 +97,23 @@ static void uvc_input_report_key(struct uvc_device *dev, unsigned int code,
|
|||
/* --------------------------------------------------------------------------
|
||||
* Status interrupt endpoint
|
||||
*/
|
||||
struct uvc_streaming_status {
|
||||
u8 bStatusType;
|
||||
u8 bOriginator;
|
||||
u8 bEvent;
|
||||
u8 bValue[];
|
||||
} __packed;
|
||||
|
||||
struct uvc_control_status {
|
||||
u8 bStatusType;
|
||||
u8 bOriginator;
|
||||
u8 bEvent;
|
||||
u8 bSelector;
|
||||
u8 bAttribute;
|
||||
u8 bValue[];
|
||||
} __packed;
|
||||
|
||||
static void uvc_event_streaming(struct uvc_device *dev,
|
||||
struct uvc_streaming_status *status, int len)
|
||||
struct uvc_status *status, int len)
|
||||
{
|
||||
if (len < 3) {
|
||||
if (len <= offsetof(struct uvc_status, bEvent)) {
|
||||
uvc_dbg(dev, STATUS,
|
||||
"Invalid streaming status event received\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (status->bEvent == 0) {
|
||||
if (len < 4)
|
||||
if (len <= offsetof(struct uvc_status, streaming))
|
||||
return;
|
||||
|
||||
uvc_dbg(dev, STATUS, "Button (intf %u) %s len %d\n",
|
||||
status->bOriginator,
|
||||
status->bValue[0] ? "pressed" : "released", len);
|
||||
uvc_input_report_key(dev, KEY_CAMERA, status->bValue[0]);
|
||||
status->streaming.button ? "pressed" : "released", len);
|
||||
uvc_input_report_key(dev, KEY_CAMERA, status->streaming.button);
|
||||
} else {
|
||||
uvc_dbg(dev, STATUS, "Stream %u error event %02x len %d\n",
|
||||
status->bOriginator, status->bEvent, len);
|
||||
|
@ -131,7 +140,7 @@ static struct uvc_control *uvc_event_entity_find_ctrl(struct uvc_entity *entity,
|
|||
}
|
||||
|
||||
static struct uvc_control *uvc_event_find_ctrl(struct uvc_device *dev,
|
||||
const struct uvc_control_status *status,
|
||||
const struct uvc_status *status,
|
||||
struct uvc_video_chain **chain)
|
||||
{
|
||||
list_for_each_entry((*chain), &dev->chains, list) {
|
||||
|
@ -143,7 +152,7 @@ static struct uvc_control *uvc_event_find_ctrl(struct uvc_device *dev,
|
|||
continue;
|
||||
|
||||
ctrl = uvc_event_entity_find_ctrl(entity,
|
||||
status->bSelector);
|
||||
status->control.bSelector);
|
||||
if (ctrl)
|
||||
return ctrl;
|
||||
}
|
||||
|
@ -153,7 +162,7 @@ static struct uvc_control *uvc_event_find_ctrl(struct uvc_device *dev,
|
|||
}
|
||||
|
||||
static bool uvc_event_control(struct urb *urb,
|
||||
const struct uvc_control_status *status, int len)
|
||||
const struct uvc_status *status, int len)
|
||||
{
|
||||
static const char *attrs[] = { "value", "info", "failure", "min", "max" };
|
||||
struct uvc_device *dev = urb->context;
|
||||
|
@ -161,24 +170,24 @@ static bool uvc_event_control(struct urb *urb,
|
|||
struct uvc_control *ctrl;
|
||||
|
||||
if (len < 6 || status->bEvent != 0 ||
|
||||
status->bAttribute >= ARRAY_SIZE(attrs)) {
|
||||
status->control.bAttribute >= ARRAY_SIZE(attrs)) {
|
||||
uvc_dbg(dev, STATUS, "Invalid control status event received\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
uvc_dbg(dev, STATUS, "Control %u/%u %s change len %d\n",
|
||||
status->bOriginator, status->bSelector,
|
||||
attrs[status->bAttribute], len);
|
||||
status->bOriginator, status->control.bSelector,
|
||||
attrs[status->control.bAttribute], len);
|
||||
|
||||
/* Find the control. */
|
||||
ctrl = uvc_event_find_ctrl(dev, status, &chain);
|
||||
if (!ctrl)
|
||||
return false;
|
||||
|
||||
switch (status->bAttribute) {
|
||||
switch (status->control.bAttribute) {
|
||||
case UVC_CTRL_VALUE_CHANGE:
|
||||
return uvc_ctrl_status_event_async(urb, chain, ctrl,
|
||||
status->bValue);
|
||||
status->control.bValue);
|
||||
|
||||
case UVC_CTRL_INFO_CHANGE:
|
||||
case UVC_CTRL_FAILURE_CHANGE:
|
||||
|
@ -214,28 +223,22 @@ static void uvc_status_complete(struct urb *urb)
|
|||
|
||||
len = urb->actual_length;
|
||||
if (len > 0) {
|
||||
switch (dev->status[0] & 0x0f) {
|
||||
switch (dev->status->bStatusType & 0x0f) {
|
||||
case UVC_STATUS_TYPE_CONTROL: {
|
||||
struct uvc_control_status *status =
|
||||
(struct uvc_control_status *)dev->status;
|
||||
|
||||
if (uvc_event_control(urb, status, len))
|
||||
if (uvc_event_control(urb, dev->status, len))
|
||||
/* The URB will be resubmitted in work context. */
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
case UVC_STATUS_TYPE_STREAMING: {
|
||||
struct uvc_streaming_status *status =
|
||||
(struct uvc_streaming_status *)dev->status;
|
||||
|
||||
uvc_event_streaming(dev, status, len);
|
||||
uvc_event_streaming(dev, dev->status, len);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
uvc_dbg(dev, STATUS, "Unknown status event type %u\n",
|
||||
dev->status[0]);
|
||||
dev->status->bStatusType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -259,12 +262,12 @@ int uvc_status_init(struct uvc_device *dev)
|
|||
|
||||
uvc_input_init(dev);
|
||||
|
||||
dev->status = kzalloc(UVC_MAX_STATUS_SIZE, GFP_KERNEL);
|
||||
if (dev->status == NULL)
|
||||
dev->status = kzalloc(sizeof(*dev->status), GFP_KERNEL);
|
||||
if (!dev->status)
|
||||
return -ENOMEM;
|
||||
|
||||
dev->int_urb = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (dev->int_urb == NULL) {
|
||||
if (!dev->int_urb) {
|
||||
kfree(dev->status);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
@ -281,7 +284,7 @@ int uvc_status_init(struct uvc_device *dev)
|
|||
interval = fls(interval) - 1;
|
||||
|
||||
usb_fill_int_urb(dev->int_urb, dev->udev, pipe,
|
||||
dev->status, UVC_MAX_STATUS_SIZE, uvc_status_complete,
|
||||
dev->status, sizeof(*dev->status), uvc_status_complete,
|
||||
dev, interval);
|
||||
|
||||
return 0;
|
||||
|
@ -309,5 +312,41 @@ int uvc_status_start(struct uvc_device *dev, gfp_t flags)
|
|||
|
||||
void uvc_status_stop(struct uvc_device *dev)
|
||||
{
|
||||
struct uvc_ctrl_work *w = &dev->async_ctrl;
|
||||
|
||||
/*
|
||||
* Prevent the asynchronous control handler from requeing the URB. The
|
||||
* barrier is needed so the flush_status change is visible to other
|
||||
* CPUs running the asynchronous handler before usb_kill_urb() is
|
||||
* called below.
|
||||
*/
|
||||
smp_store_release(&dev->flush_status, true);
|
||||
|
||||
/*
|
||||
* Cancel any pending asynchronous work. If any status event was queued,
|
||||
* process it synchronously.
|
||||
*/
|
||||
if (cancel_work_sync(&w->work))
|
||||
uvc_ctrl_status_event(w->chain, w->ctrl, w->data);
|
||||
|
||||
/* Kill the urb. */
|
||||
usb_kill_urb(dev->int_urb);
|
||||
|
||||
/*
|
||||
* The URB completion handler may have queued asynchronous work. This
|
||||
* won't resubmit the URB as flush_status is set, but it needs to be
|
||||
* cancelled before returning or it could then race with a future
|
||||
* uvc_status_start() call.
|
||||
*/
|
||||
if (cancel_work_sync(&w->work))
|
||||
uvc_ctrl_status_event(w->chain, w->ctrl, w->data);
|
||||
|
||||
/*
|
||||
* From this point, there are no events on the queue and the status URB
|
||||
* is dead. No events will be queued until uvc_status_start() is called.
|
||||
* The barrier is needed to make sure that flush_status is visible to
|
||||
* uvc_ctrl_status_event_work() when uvc_status_start() will be called
|
||||
* again.
|
||||
*/
|
||||
smp_store_release(&dev->flush_status, false);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
|
||||
*/
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/compat.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
|
@ -25,14 +26,84 @@
|
|||
|
||||
#include "uvcvideo.h"
|
||||
|
||||
static int uvc_control_add_xu_mapping(struct uvc_video_chain *chain,
|
||||
struct uvc_control_mapping *map,
|
||||
const struct uvc_xu_control_mapping *xmap)
|
||||
{
|
||||
unsigned int i;
|
||||
size_t size;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Prevent excessive memory consumption, as well as integer
|
||||
* overflows.
|
||||
*/
|
||||
if (xmap->menu_count == 0 ||
|
||||
xmap->menu_count > UVC_MAX_CONTROL_MENU_ENTRIES)
|
||||
return -EINVAL;
|
||||
|
||||
map->menu_names = NULL;
|
||||
map->menu_mapping = NULL;
|
||||
|
||||
map->menu_mask = BIT_MASK(xmap->menu_count);
|
||||
|
||||
size = xmap->menu_count * sizeof(*map->menu_mapping);
|
||||
map->menu_mapping = kzalloc(size, GFP_KERNEL);
|
||||
if (!map->menu_mapping) {
|
||||
ret = -ENOMEM;
|
||||
goto done;
|
||||
}
|
||||
|
||||
for (i = 0; i < xmap->menu_count ; i++) {
|
||||
if (copy_from_user((u32 *)&map->menu_mapping[i],
|
||||
&xmap->menu_info[i].value,
|
||||
sizeof(map->menu_mapping[i]))) {
|
||||
ret = -EACCES;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Always use the standard naming if available, otherwise copy the
|
||||
* names supplied by userspace.
|
||||
*/
|
||||
if (!v4l2_ctrl_get_menu(map->id)) {
|
||||
size = xmap->menu_count * sizeof(map->menu_names[0]);
|
||||
map->menu_names = kzalloc(size, GFP_KERNEL);
|
||||
if (!map->menu_names) {
|
||||
ret = -ENOMEM;
|
||||
goto done;
|
||||
}
|
||||
|
||||
for (i = 0; i < xmap->menu_count ; i++) {
|
||||
/* sizeof(names[i]) - 1: to take care of \0 */
|
||||
if (copy_from_user((char *)map->menu_names[i],
|
||||
xmap->menu_info[i].name,
|
||||
sizeof(map->menu_names[i]) - 1)) {
|
||||
ret = -EACCES;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret = uvc_ctrl_add_mapping(chain, map);
|
||||
|
||||
done:
|
||||
kfree(map->menu_names);
|
||||
map->menu_names = NULL;
|
||||
kfree(map->menu_mapping);
|
||||
map->menu_mapping = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
* UVC ioctls
|
||||
*/
|
||||
static int uvc_ioctl_ctrl_map(struct uvc_video_chain *chain,
|
||||
struct uvc_xu_control_mapping *xmap)
|
||||
static int uvc_ioctl_xu_ctrl_map(struct uvc_video_chain *chain,
|
||||
struct uvc_xu_control_mapping *xmap)
|
||||
{
|
||||
struct uvc_control_mapping *map;
|
||||
unsigned int size;
|
||||
int ret;
|
||||
|
||||
map = kzalloc(sizeof(*map), GFP_KERNEL);
|
||||
|
@ -60,39 +131,20 @@ static int uvc_ioctl_ctrl_map(struct uvc_video_chain *chain,
|
|||
case V4L2_CTRL_TYPE_INTEGER:
|
||||
case V4L2_CTRL_TYPE_BOOLEAN:
|
||||
case V4L2_CTRL_TYPE_BUTTON:
|
||||
ret = uvc_ctrl_add_mapping(chain, map);
|
||||
break;
|
||||
|
||||
case V4L2_CTRL_TYPE_MENU:
|
||||
/*
|
||||
* Prevent excessive memory consumption, as well as integer
|
||||
* overflows.
|
||||
*/
|
||||
if (xmap->menu_count == 0 ||
|
||||
xmap->menu_count > UVC_MAX_CONTROL_MENU_ENTRIES) {
|
||||
ret = -EINVAL;
|
||||
goto free_map;
|
||||
}
|
||||
|
||||
size = xmap->menu_count * sizeof(*map->menu_info);
|
||||
map->menu_info = memdup_user(xmap->menu_info, size);
|
||||
if (IS_ERR(map->menu_info)) {
|
||||
ret = PTR_ERR(map->menu_info);
|
||||
goto free_map;
|
||||
}
|
||||
|
||||
map->menu_count = xmap->menu_count;
|
||||
ret = uvc_control_add_xu_mapping(chain, map, xmap);
|
||||
break;
|
||||
|
||||
default:
|
||||
uvc_dbg(chain->dev, CONTROL,
|
||||
"Unsupported V4L2 control type %u\n", xmap->v4l2_type);
|
||||
ret = -ENOTTY;
|
||||
goto free_map;
|
||||
break;
|
||||
}
|
||||
|
||||
ret = uvc_ctrl_add_mapping(chain, map);
|
||||
|
||||
kfree(map->menu_info);
|
||||
free_map:
|
||||
kfree(map);
|
||||
|
||||
|
@ -660,8 +712,6 @@ static int uvc_ioctl_enum_fmt(struct uvc_streaming *stream,
|
|||
fmt->flags = 0;
|
||||
if (format->flags & UVC_FMT_FLAG_COMPRESSED)
|
||||
fmt->flags |= V4L2_FMT_FLAG_COMPRESSED;
|
||||
strscpy(fmt->description, format->name, sizeof(fmt->description));
|
||||
fmt->description[sizeof(fmt->description) - 1] = 0;
|
||||
fmt->pixelformat = format->fcc;
|
||||
return 0;
|
||||
}
|
||||
|
@ -1020,8 +1070,7 @@ static int uvc_ctrl_check_access(struct uvc_video_chain *chain,
|
|||
int ret = 0;
|
||||
|
||||
for (i = 0; i < ctrls->count; ++ctrl, ++i) {
|
||||
ret = uvc_ctrl_is_accessible(chain, ctrl->id,
|
||||
ioctl == VIDIOC_G_EXT_CTRLS);
|
||||
ret = uvc_ctrl_is_accessible(chain, ctrl->id, ctrls, ioctl);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
@ -1316,7 +1365,7 @@ static long uvc_ioctl_default(struct file *file, void *fh, bool valid_prio,
|
|||
switch (cmd) {
|
||||
/* Dynamic controls. */
|
||||
case UVCIOC_CTRL_MAP:
|
||||
return uvc_ioctl_ctrl_map(chain, arg);
|
||||
return uvc_ioctl_xu_ctrl_map(chain, arg);
|
||||
|
||||
case UVCIOC_CTRL_QUERY:
|
||||
return uvc_xu_ctrl_query(chain, arg);
|
||||
|
@ -1429,7 +1478,7 @@ static long uvc_v4l2_compat_ioctl32(struct file *file,
|
|||
ret = uvc_v4l2_get_xu_mapping(&karg.xmap, up);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = uvc_ioctl_ctrl_map(handle->chain, &karg.xmap);
|
||||
ret = uvc_ioctl_xu_ctrl_map(handle->chain, &karg.xmap);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = uvc_v4l2_put_xu_mapping(&karg.xmap, up);
|
||||
|
|
|
@ -79,13 +79,14 @@ int uvc_query_ctrl(struct uvc_device *dev, u8 query, u8 unit,
|
|||
if (likely(ret == size))
|
||||
return 0;
|
||||
|
||||
dev_err(&dev->udev->dev,
|
||||
"Failed to query (%s) UVC control %u on unit %u: %d (exp. %u).\n",
|
||||
uvc_query_name(query), cs, unit, ret, size);
|
||||
|
||||
if (ret != -EPIPE)
|
||||
return ret;
|
||||
if (ret != -EPIPE) {
|
||||
dev_err(&dev->udev->dev,
|
||||
"Failed to query (%s) UVC control %u on unit %u: %d (exp. %u).\n",
|
||||
uvc_query_name(query), cs, unit, ret, size);
|
||||
return ret < 0 ? ret : -EPIPE;
|
||||
}
|
||||
|
||||
/* Reuse data[0] to request the error code. */
|
||||
tmp = *(u8 *)data;
|
||||
|
||||
ret = __uvc_query_ctrl(dev, UVC_GET_CUR, 0, intfnum,
|
||||
|
@ -107,7 +108,7 @@ int uvc_query_ctrl(struct uvc_device *dev, u8 query, u8 unit,
|
|||
case 1: /* Not ready */
|
||||
return -EBUSY;
|
||||
case 2: /* Wrong state */
|
||||
return -EILSEQ;
|
||||
return -EACCES;
|
||||
case 3: /* Power */
|
||||
return -EREMOTE;
|
||||
case 4: /* Out of range */
|
||||
|
@ -129,12 +130,13 @@ int uvc_query_ctrl(struct uvc_device *dev, u8 query, u8 unit,
|
|||
return -EPIPE;
|
||||
}
|
||||
|
||||
static const struct usb_device_id elgato_cam_link_4k = {
|
||||
USB_DEVICE(0x0fd9, 0x0066)
|
||||
};
|
||||
|
||||
static void uvc_fixup_video_ctrl(struct uvc_streaming *stream,
|
||||
struct uvc_streaming_control *ctrl)
|
||||
{
|
||||
static const struct usb_device_id elgato_cam_link_4k = {
|
||||
USB_DEVICE(0x0fd9, 0x0066)
|
||||
};
|
||||
struct uvc_format *format = NULL;
|
||||
struct uvc_frame *frame = NULL;
|
||||
unsigned int i;
|
||||
|
@ -297,7 +299,7 @@ static int uvc_get_video_ctrl(struct uvc_streaming *stream,
|
|||
dev_err(&stream->intf->dev,
|
||||
"Failed to query (%u) UVC %s control : %d (exp. %u).\n",
|
||||
query, probe ? "probe" : "commit", ret, size);
|
||||
ret = -EIO;
|
||||
ret = (ret == -EPROTO) ? -EPROTO : -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
@ -516,7 +518,9 @@ uvc_video_clock_decode(struct uvc_streaming *stream, struct uvc_buffer *buf,
|
|||
|
||||
/*
|
||||
* To limit the amount of data, drop SCRs with an SOF identical to the
|
||||
* previous one.
|
||||
* previous one. This filtering is also needed to support UVC 1.5, where
|
||||
* all the data packets of the same frame contains the same SOF. In that
|
||||
* case only the first one will match the host_sof.
|
||||
*/
|
||||
dev_sof = get_unaligned_le16(&data[header_size - 2]);
|
||||
if (dev_sof == stream->clock.last_sof)
|
||||
|
@ -1352,7 +1356,9 @@ static void uvc_video_decode_meta(struct uvc_streaming *stream,
|
|||
if (has_scr)
|
||||
memcpy(stream->clock.last_scr, scr, 6);
|
||||
|
||||
memcpy(&meta->length, mem, length);
|
||||
meta->length = mem[0];
|
||||
meta->flags = mem[1];
|
||||
memcpy(meta->buf, &mem[2], length - 2);
|
||||
meta_buf->bytesused += length + sizeof(meta->ns) + sizeof(meta->sof);
|
||||
|
||||
uvc_dbg(stream->dev, FRAME,
|
||||
|
@ -1965,6 +1971,17 @@ static int uvc_video_start_transfer(struct uvc_streaming *stream,
|
|||
"Selecting alternate setting %u (%u B/frame bandwidth)\n",
|
||||
altsetting, best_psize);
|
||||
|
||||
/*
|
||||
* Some devices, namely the Logitech C910 and B910, are unable
|
||||
* to recover from a USB autosuspend, unless the alternate
|
||||
* setting of the streaming interface is toggled.
|
||||
*/
|
||||
if (stream->dev->quirks & UVC_QUIRK_WAKE_AUTOSUSPEND) {
|
||||
usb_set_interface(stream->dev->udev, intfnum,
|
||||
altsetting);
|
||||
usb_set_interface(stream->dev->udev, intfnum, 0);
|
||||
}
|
||||
|
||||
ret = usb_set_interface(stream->dev->udev, intfnum, altsetting);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
@ -2121,6 +2138,21 @@ int uvc_video_init(struct uvc_streaming *stream)
|
|||
* request on the probe control, as required by the UVC specification.
|
||||
*/
|
||||
ret = uvc_get_video_ctrl(stream, probe, 1, UVC_GET_CUR);
|
||||
|
||||
/*
|
||||
* Elgato Cam Link 4k can be in a stalled state if the resolution of
|
||||
* the external source has changed while the firmware initializes.
|
||||
* Once in this state, the device is useless until it receives a
|
||||
* USB reset. It has even been observed that the stalled state will
|
||||
* continue even after unplugging the device.
|
||||
*/
|
||||
if (ret == -EPROTO &&
|
||||
usb_match_one_id(stream->dev->intf, &elgato_cam_link_4k)) {
|
||||
dev_err(&stream->intf->dev, "Elgato Cam Link 4K firmware crash detected\n");
|
||||
dev_err(&stream->intf->dev, "Resetting the device, unplug and replug to recover\n");
|
||||
usb_reset_device(stream->dev->udev);
|
||||
}
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
|
|
@ -51,8 +51,6 @@
|
|||
#define UVC_URBS 5
|
||||
/* Maximum number of packets per URB. */
|
||||
#define UVC_MAX_PACKETS 32
|
||||
/* Maximum status buffer size in bytes of interrupt URB. */
|
||||
#define UVC_MAX_STATUS_SIZE 16
|
||||
|
||||
#define UVC_CTRL_CONTROL_TIMEOUT 5000
|
||||
#define UVC_CTRL_STREAMING_TIMEOUT 5000
|
||||
|
@ -74,6 +72,7 @@
|
|||
#define UVC_QUIRK_RESTORE_CTRLS_ON_INIT 0x00000400
|
||||
#define UVC_QUIRK_FORCE_Y8 0x00000800
|
||||
#define UVC_QUIRK_FORCE_BPP 0x00001000
|
||||
#define UVC_QUIRK_WAKE_AUTOSUSPEND 0x00002000
|
||||
|
||||
/* Format flags */
|
||||
#define UVC_FMT_FLAG_COMPRESSED 0x00000001
|
||||
|
@ -116,8 +115,9 @@ struct uvc_control_mapping {
|
|||
enum v4l2_ctrl_type v4l2_type;
|
||||
u32 data_type;
|
||||
|
||||
const struct uvc_menu_info *menu_info;
|
||||
u32 menu_count;
|
||||
const u32 *menu_mapping;
|
||||
const char (*menu_names)[UVC_MENU_NAME_LEN];
|
||||
unsigned long menu_mask;
|
||||
|
||||
u32 master_id;
|
||||
s32 master_manual;
|
||||
|
@ -264,8 +264,6 @@ struct uvc_format {
|
|||
u32 fcc;
|
||||
u32 flags;
|
||||
|
||||
char name[32];
|
||||
|
||||
unsigned int nframes;
|
||||
struct uvc_frame *frame;
|
||||
};
|
||||
|
@ -527,6 +525,26 @@ struct uvc_device_info {
|
|||
const struct uvc_control_mapping **mappings;
|
||||
};
|
||||
|
||||
struct uvc_status_streaming {
|
||||
u8 button;
|
||||
} __packed;
|
||||
|
||||
struct uvc_status_control {
|
||||
u8 bSelector;
|
||||
u8 bAttribute;
|
||||
u8 bValue[11];
|
||||
} __packed;
|
||||
|
||||
struct uvc_status {
|
||||
u8 bStatusType;
|
||||
u8 bOriginator;
|
||||
u8 bEvent;
|
||||
union {
|
||||
struct uvc_status_control control;
|
||||
struct uvc_status_streaming streaming;
|
||||
};
|
||||
} __packed;
|
||||
|
||||
struct uvc_device {
|
||||
struct usb_device *udev;
|
||||
struct usb_interface *intf;
|
||||
|
@ -559,7 +577,9 @@ struct uvc_device {
|
|||
/* Status Interrupt Endpoint */
|
||||
struct usb_host_endpoint *int_ep;
|
||||
struct urb *int_urb;
|
||||
u8 *status;
|
||||
struct uvc_status *status;
|
||||
bool flush_status;
|
||||
|
||||
struct input_dev *input;
|
||||
char input_phys[64];
|
||||
|
||||
|
@ -728,6 +748,8 @@ int uvc_status_start(struct uvc_device *dev, gfp_t flags);
|
|||
void uvc_status_stop(struct uvc_device *dev);
|
||||
|
||||
/* Controls */
|
||||
extern const struct uvc_control_mapping uvc_ctrl_power_line_mapping_limited;
|
||||
extern const struct uvc_control_mapping uvc_ctrl_power_line_mapping_uvc11;
|
||||
extern const struct v4l2_subscribed_event_ops uvc_ctrl_sub_ev_ops;
|
||||
|
||||
int uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
|
||||
|
@ -761,7 +783,8 @@ static inline int uvc_ctrl_rollback(struct uvc_fh *handle)
|
|||
int uvc_ctrl_get(struct uvc_video_chain *chain, struct v4l2_ext_control *xctrl);
|
||||
int uvc_ctrl_set(struct uvc_fh *handle, struct v4l2_ext_control *xctrl);
|
||||
int uvc_ctrl_is_accessible(struct uvc_video_chain *chain, u32 v4l2_id,
|
||||
bool read);
|
||||
const struct v4l2_ext_controls *ctrls,
|
||||
unsigned long ioctl);
|
||||
|
||||
int uvc_xu_ctrl_query(struct uvc_video_chain *chain,
|
||||
struct uvc_xu_control_query *xqry);
|
||||
|
|
|
@ -568,18 +568,7 @@ config FUJITSU_ES
|
|||
This driver provides support for Extended Socket network device
|
||||
on Extended Partitioning of FUJITSU PRIMEQUEST 2000 E2 series.
|
||||
|
||||
config USB4_NET
|
||||
tristate "Networking over USB4 and Thunderbolt cables"
|
||||
depends on USB4 && INET
|
||||
help
|
||||
Select this if you want to create network between two computers
|
||||
over a USB4 and Thunderbolt cables. The driver supports Apple
|
||||
ThunderboltIP protocol and allows communication with any host
|
||||
supporting the same protocol including Windows and macOS.
|
||||
|
||||
To compile this driver a module, choose M here. The module will be
|
||||
called thunderbolt-net.
|
||||
|
||||
source "drivers/net/thunderbolt/Kconfig"
|
||||
source "drivers/net/hyperv/Kconfig"
|
||||
|
||||
config NETDEVSIM
|
||||
|
|
|
@ -81,8 +81,6 @@ obj-$(CONFIG_HYPERV_NET) += hyperv/
|
|||
obj-$(CONFIG_NTB_NETDEV) += ntb_netdev.o
|
||||
|
||||
obj-$(CONFIG_FUJITSU_ES) += fjes/
|
||||
|
||||
thunderbolt-net-y += thunderbolt.o
|
||||
obj-$(CONFIG_USB4_NET) += thunderbolt-net.o
|
||||
obj-$(CONFIG_USB4_NET) += thunderbolt/
|
||||
obj-$(CONFIG_NETDEVSIM) += netdevsim/
|
||||
obj-$(CONFIG_NET_FAILOVER) += net_failover.o
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config USB4_NET
|
||||
tristate "Networking over USB4 and Thunderbolt cables"
|
||||
depends on USB4 && INET
|
||||
help
|
||||
Select this if you want to create network between two computers
|
||||
over a USB4 and Thunderbolt cables. The driver supports Apple
|
||||
ThunderboltIP protocol and allows communication with any host
|
||||
supporting the same protocol including Windows and macOS.
|
||||
|
||||
To compile this driver a module, choose M here. The module will be
|
||||
called thunderbolt_net.
|
|
@ -0,0 +1,6 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
obj-$(CONFIG_USB4_NET) := thunderbolt_net.o
|
||||
thunderbolt_net-objs := main.o trace.o
|
||||
|
||||
# Tracepoints need to know where to find trace.h
|
||||
CFLAGS_trace.o := -I$(src)
|
|
@ -23,6 +23,8 @@
|
|||
|
||||
#include <net/ip6_checksum.h>
|
||||
|
||||
#include "trace.h"
|
||||
|
||||
/* Protocol timeouts in ms */
|
||||
#define TBNET_LOGIN_DELAY 4500
|
||||
#define TBNET_LOGIN_TIMEOUT 500
|
||||
|
@ -305,6 +307,8 @@ static int tbnet_logout_request(struct tbnet *net)
|
|||
|
||||
static void start_login(struct tbnet *net)
|
||||
{
|
||||
netdev_dbg(net->dev, "login started\n");
|
||||
|
||||
mutex_lock(&net->connection_lock);
|
||||
net->login_sent = false;
|
||||
net->login_received = false;
|
||||
|
@ -318,6 +322,8 @@ static void stop_login(struct tbnet *net)
|
|||
{
|
||||
cancel_delayed_work_sync(&net->login_work);
|
||||
cancel_work_sync(&net->connected_work);
|
||||
|
||||
netdev_dbg(net->dev, "login stopped\n");
|
||||
}
|
||||
|
||||
static inline unsigned int tbnet_frame_size(const struct tbnet_frame *tf)
|
||||
|
@ -349,6 +355,8 @@ static void tbnet_free_buffers(struct tbnet_ring *ring)
|
|||
size = TBNET_RX_PAGE_SIZE;
|
||||
}
|
||||
|
||||
trace_tbnet_free_frame(i, tf->page, tf->frame.buffer_phy, dir);
|
||||
|
||||
if (tf->frame.buffer_phy)
|
||||
dma_unmap_page(dma_dev, tf->frame.buffer_phy, size,
|
||||
dir);
|
||||
|
@ -374,6 +382,8 @@ static void tbnet_tear_down(struct tbnet *net, bool send_logout)
|
|||
int ret, retries = TBNET_LOGOUT_RETRIES;
|
||||
|
||||
while (send_logout && retries-- > 0) {
|
||||
netdev_dbg(net->dev, "sending logout request %u\n",
|
||||
retries);
|
||||
ret = tbnet_logout_request(net);
|
||||
if (ret != -ETIMEDOUT)
|
||||
break;
|
||||
|
@ -400,6 +410,8 @@ static void tbnet_tear_down(struct tbnet *net, bool send_logout)
|
|||
net->login_sent = false;
|
||||
net->login_received = false;
|
||||
|
||||
netdev_dbg(net->dev, "network traffic stopped\n");
|
||||
|
||||
mutex_unlock(&net->connection_lock);
|
||||
}
|
||||
|
||||
|
@ -431,12 +443,15 @@ static int tbnet_handle_packet(const void *buf, size_t size, void *data)
|
|||
|
||||
switch (pkg->hdr.type) {
|
||||
case TBIP_LOGIN:
|
||||
netdev_dbg(net->dev, "remote login request received\n");
|
||||
if (!netif_running(net->dev))
|
||||
break;
|
||||
|
||||
ret = tbnet_login_response(net, route, sequence,
|
||||
pkg->hdr.command_id);
|
||||
if (!ret) {
|
||||
netdev_dbg(net->dev, "remote login response sent\n");
|
||||
|
||||
mutex_lock(&net->connection_lock);
|
||||
net->login_received = true;
|
||||
net->remote_transmit_path = pkg->transmit_path;
|
||||
|
@ -458,9 +473,12 @@ static int tbnet_handle_packet(const void *buf, size_t size, void *data)
|
|||
break;
|
||||
|
||||
case TBIP_LOGOUT:
|
||||
netdev_dbg(net->dev, "remote logout request received\n");
|
||||
ret = tbnet_logout_response(net, route, sequence, command_id);
|
||||
if (!ret)
|
||||
if (!ret) {
|
||||
netdev_dbg(net->dev, "remote logout response sent\n");
|
||||
queue_work(system_long_wq, &net->disconnect_work);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -512,6 +530,9 @@ static int tbnet_alloc_rx_buffers(struct tbnet *net, unsigned int nbuffers)
|
|||
tf->frame.buffer_phy = dma_addr;
|
||||
tf->dev = net->dev;
|
||||
|
||||
trace_tbnet_alloc_rx_frame(index, tf->page, dma_addr,
|
||||
DMA_FROM_DEVICE);
|
||||
|
||||
tb_ring_rx(ring->ring, &tf->frame);
|
||||
|
||||
ring->prod++;
|
||||
|
@ -588,6 +609,8 @@ static int tbnet_alloc_tx_buffers(struct tbnet *net)
|
|||
tf->frame.callback = tbnet_tx_callback;
|
||||
tf->frame.sof = TBIP_PDF_FRAME_START;
|
||||
tf->frame.eof = TBIP_PDF_FRAME_END;
|
||||
|
||||
trace_tbnet_alloc_tx_frame(i, tf->page, dma_addr, DMA_TO_DEVICE);
|
||||
}
|
||||
|
||||
ring->cons = 0;
|
||||
|
@ -612,6 +635,8 @@ static void tbnet_connected_work(struct work_struct *work)
|
|||
if (!connected)
|
||||
return;
|
||||
|
||||
netdev_dbg(net->dev, "login successful, enabling paths\n");
|
||||
|
||||
ret = tb_xdomain_alloc_in_hopid(net->xd, net->remote_transmit_path);
|
||||
if (ret != net->remote_transmit_path) {
|
||||
netdev_err(net->dev, "failed to allocate Rx HopID\n");
|
||||
|
@ -647,6 +672,8 @@ static void tbnet_connected_work(struct work_struct *work)
|
|||
|
||||
netif_carrier_on(net->dev);
|
||||
netif_start_queue(net->dev);
|
||||
|
||||
netdev_dbg(net->dev, "network traffic started\n");
|
||||
return;
|
||||
|
||||
err_free_tx_buffers:
|
||||
|
@ -668,8 +695,13 @@ static void tbnet_login_work(struct work_struct *work)
|
|||
if (netif_carrier_ok(net->dev))
|
||||
return;
|
||||
|
||||
netdev_dbg(net->dev, "sending login request, retries=%u\n",
|
||||
net->login_retries);
|
||||
|
||||
ret = tbnet_login_request(net, net->login_retries % 4);
|
||||
if (ret) {
|
||||
netdev_dbg(net->dev, "sending login request failed, ret=%d\n",
|
||||
ret);
|
||||
if (net->login_retries++ < TBNET_LOGIN_RETRIES) {
|
||||
queue_delayed_work(system_long_wq, &net->login_work,
|
||||
delay);
|
||||
|
@ -677,6 +709,8 @@ static void tbnet_login_work(struct work_struct *work)
|
|||
netdev_info(net->dev, "ThunderboltIP login timed out\n");
|
||||
}
|
||||
} else {
|
||||
netdev_dbg(net->dev, "received login reply\n");
|
||||
|
||||
net->login_retries = 0;
|
||||
|
||||
mutex_lock(&net->connection_lock);
|
||||
|
@ -807,12 +841,16 @@ static int tbnet_poll(struct napi_struct *napi, int budget)
|
|||
|
||||
hdr = page_address(page);
|
||||
if (!tbnet_check_frame(net, tf, hdr)) {
|
||||
trace_tbnet_invalid_rx_ip_frame(hdr->frame_size,
|
||||
hdr->frame_id, hdr->frame_index, hdr->frame_count);
|
||||
__free_pages(page, TBNET_RX_PAGE_ORDER);
|
||||
dev_kfree_skb_any(net->skb);
|
||||
net->skb = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
trace_tbnet_rx_ip_frame(hdr->frame_size, hdr->frame_id,
|
||||
hdr->frame_index, hdr->frame_count);
|
||||
frame_size = le32_to_cpu(hdr->frame_size);
|
||||
|
||||
skb = net->skb;
|
||||
|
@ -846,6 +884,7 @@ static int tbnet_poll(struct napi_struct *napi, int budget)
|
|||
|
||||
if (last) {
|
||||
skb->protocol = eth_type_trans(skb, net->dev);
|
||||
trace_tbnet_rx_skb(skb);
|
||||
napi_gro_receive(&net->napi, skb);
|
||||
net->skb = NULL;
|
||||
}
|
||||
|
@ -965,6 +1004,8 @@ static bool tbnet_xmit_csum_and_map(struct tbnet *net, struct sk_buff *skb,
|
|||
for (i = 0; i < frame_count; i++) {
|
||||
hdr = page_address(frames[i]->page);
|
||||
hdr->frame_count = cpu_to_le32(frame_count);
|
||||
trace_tbnet_tx_ip_frame(hdr->frame_size, hdr->frame_id,
|
||||
hdr->frame_index, hdr->frame_count);
|
||||
dma_sync_single_for_device(dma_dev,
|
||||
frames[i]->frame.buffer_phy,
|
||||
tbnet_frame_size(frames[i]), DMA_TO_DEVICE);
|
||||
|
@ -1029,6 +1070,8 @@ static bool tbnet_xmit_csum_and_map(struct tbnet *net, struct sk_buff *skb,
|
|||
len = le32_to_cpu(hdr->frame_size) - offset;
|
||||
wsum = csum_partial(dest, len, wsum);
|
||||
hdr->frame_count = cpu_to_le32(frame_count);
|
||||
trace_tbnet_tx_ip_frame(hdr->frame_size, hdr->frame_id,
|
||||
hdr->frame_index, hdr->frame_count);
|
||||
|
||||
offset = 0;
|
||||
}
|
||||
|
@ -1071,6 +1114,8 @@ static netdev_tx_t tbnet_start_xmit(struct sk_buff *skb,
|
|||
bool unmap = false;
|
||||
void *dest;
|
||||
|
||||
trace_tbnet_tx_skb(skb);
|
||||
|
||||
nframes = DIV_ROUND_UP(data_len, TBNET_MAX_PAYLOAD_SIZE);
|
||||
if (tbnet_available_buffers(&net->tx_ring) < nframes) {
|
||||
netif_stop_queue(net->dev);
|
||||
|
@ -1177,6 +1222,7 @@ static netdev_tx_t tbnet_start_xmit(struct sk_buff *skb,
|
|||
net->stats.tx_packets++;
|
||||
net->stats.tx_bytes += skb->len;
|
||||
|
||||
trace_tbnet_consume_skb(skb);
|
||||
dev_consume_skb_any(skb);
|
||||
|
||||
return NETDEV_TX_OK;
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Tracepoints for Thunderbolt/USB4 networking driver
|
||||
*
|
||||
* Copyright (C) 2023, Intel Corporation
|
||||
* Author: Mika Westerberg <mika.westerberg@linux.intel.com>
|
||||
*/
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include "trace.h"
|
|
@ -0,0 +1,141 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Tracepoints for Thunderbolt/USB4 networking driver
|
||||
*
|
||||
* Copyright (C) 2023, Intel Corporation
|
||||
* Author: Mika Westerberg <mika.westerberg@linux.intel.com>
|
||||
*/
|
||||
|
||||
#undef TRACE_SYSTEM
|
||||
#define TRACE_SYSTEM thunderbolt_net
|
||||
|
||||
#if !defined(__TRACE_THUNDERBOLT_NET_H) || defined(TRACE_HEADER_MULTI_READ)
|
||||
#define __TRACE_THUNDERBOLT_NET_H
|
||||
|
||||
#include <linux/dma-direction.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/tracepoint.h>
|
||||
|
||||
#define DMA_DATA_DIRECTION_NAMES \
|
||||
{ DMA_BIDIRECTIONAL, "DMA_BIDIRECTIONAL" }, \
|
||||
{ DMA_TO_DEVICE, "DMA_TO_DEVICE" }, \
|
||||
{ DMA_FROM_DEVICE, "DMA_FROM_DEVICE" }, \
|
||||
{ DMA_NONE, "DMA_NONE" }
|
||||
|
||||
DECLARE_EVENT_CLASS(tbnet_frame,
|
||||
TP_PROTO(unsigned int index, const void *page, dma_addr_t phys,
|
||||
enum dma_data_direction dir),
|
||||
TP_ARGS(index, page, phys, dir),
|
||||
TP_STRUCT__entry(
|
||||
__field(unsigned int, index)
|
||||
__field(const void *, page)
|
||||
__field(dma_addr_t, phys)
|
||||
__field(enum dma_data_direction, dir)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->index = index;
|
||||
__entry->page = page;
|
||||
__entry->phys = phys;
|
||||
__entry->dir = dir;
|
||||
),
|
||||
TP_printk("index=%u page=%p phys=%pad dir=%s",
|
||||
__entry->index, __entry->page, &__entry->phys,
|
||||
__print_symbolic(__entry->dir, DMA_DATA_DIRECTION_NAMES))
|
||||
);
|
||||
|
||||
DEFINE_EVENT(tbnet_frame, tbnet_alloc_rx_frame,
|
||||
TP_PROTO(unsigned int index, const void *page, dma_addr_t phys,
|
||||
enum dma_data_direction dir),
|
||||
TP_ARGS(index, page, phys, dir)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(tbnet_frame, tbnet_alloc_tx_frame,
|
||||
TP_PROTO(unsigned int index, const void *page, dma_addr_t phys,
|
||||
enum dma_data_direction dir),
|
||||
TP_ARGS(index, page, phys, dir)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(tbnet_frame, tbnet_free_frame,
|
||||
TP_PROTO(unsigned int index, const void *page, dma_addr_t phys,
|
||||
enum dma_data_direction dir),
|
||||
TP_ARGS(index, page, phys, dir)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(tbnet_ip_frame,
|
||||
TP_PROTO(__le32 size, __le16 id, __le16 index, __le32 count),
|
||||
TP_ARGS(size, id, index, count),
|
||||
TP_STRUCT__entry(
|
||||
__field(u32, size)
|
||||
__field(u16, id)
|
||||
__field(u16, index)
|
||||
__field(u32, count)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->size = le32_to_cpu(size);
|
||||
__entry->id = le16_to_cpu(id);
|
||||
__entry->index = le16_to_cpu(index);
|
||||
__entry->count = le32_to_cpu(count);
|
||||
),
|
||||
TP_printk("id=%u size=%u index=%u count=%u",
|
||||
__entry->id, __entry->size, __entry->index, __entry->count)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(tbnet_ip_frame, tbnet_rx_ip_frame,
|
||||
TP_PROTO(__le32 size, __le16 id, __le16 index, __le32 count),
|
||||
TP_ARGS(size, id, index, count)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(tbnet_ip_frame, tbnet_invalid_rx_ip_frame,
|
||||
TP_PROTO(__le32 size, __le16 id, __le16 index, __le32 count),
|
||||
TP_ARGS(size, id, index, count)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(tbnet_ip_frame, tbnet_tx_ip_frame,
|
||||
TP_PROTO(__le32 size, __le16 id, __le16 index, __le32 count),
|
||||
TP_ARGS(size, id, index, count)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(tbnet_skb,
|
||||
TP_PROTO(const struct sk_buff *skb),
|
||||
TP_ARGS(skb),
|
||||
TP_STRUCT__entry(
|
||||
__field(const void *, addr)
|
||||
__field(unsigned int, len)
|
||||
__field(unsigned int, data_len)
|
||||
__field(unsigned int, nr_frags)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->addr = skb;
|
||||
__entry->len = skb->len;
|
||||
__entry->data_len = skb->data_len;
|
||||
__entry->nr_frags = skb_shinfo(skb)->nr_frags;
|
||||
),
|
||||
TP_printk("skb=%p len=%u data_len=%u nr_frags=%u",
|
||||
__entry->addr, __entry->len, __entry->data_len,
|
||||
__entry->nr_frags)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(tbnet_skb, tbnet_rx_skb,
|
||||
TP_PROTO(const struct sk_buff *skb),
|
||||
TP_ARGS(skb)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(tbnet_skb, tbnet_tx_skb,
|
||||
TP_PROTO(const struct sk_buff *skb),
|
||||
TP_ARGS(skb)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(tbnet_skb, tbnet_consume_skb,
|
||||
TP_PROTO(const struct sk_buff *skb),
|
||||
TP_ARGS(skb)
|
||||
);
|
||||
|
||||
#endif /* _TRACE_THUNDERBOLT_NET_H */
|
||||
|
||||
#undef TRACE_INCLUDE_PATH
|
||||
#define TRACE_INCLUDE_PATH .
|
||||
|
||||
#undef TRACE_INCLUDE_FILE
|
||||
#define TRACE_INCLUDE_FILE trace
|
||||
|
||||
#include <trace/define_trace.h>
|
|
@ -36,16 +36,13 @@ static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data,
|
|||
* We need to do this because the xHCI driver might not yet be
|
||||
* bound so the USB3 SuperSpeed ports are not yet created.
|
||||
*/
|
||||
dev = acpi_get_first_physical_node(adev);
|
||||
while (!dev) {
|
||||
adev = acpi_dev_parent(adev);
|
||||
if (!adev)
|
||||
break;
|
||||
do {
|
||||
dev = acpi_get_first_physical_node(adev);
|
||||
}
|
||||
if (dev)
|
||||
break;
|
||||
|
||||
if (!dev)
|
||||
goto out_put;
|
||||
adev = acpi_dev_parent(adev);
|
||||
} while (adev);
|
||||
|
||||
/*
|
||||
* Check that the device is PCIe. This is because USB3
|
||||
|
|
|
@ -230,7 +230,6 @@ static int check_config_address(struct tb_cfg_address addr,
|
|||
static struct tb_cfg_result decode_error(const struct ctl_pkg *response)
|
||||
{
|
||||
struct cfg_error_pkg *pkg = response->buffer;
|
||||
struct tb_ctl *ctl = response->ctl;
|
||||
struct tb_cfg_result res = { 0 };
|
||||
res.response_route = tb_cfg_get_route(&pkg->header);
|
||||
res.response_port = 0;
|
||||
|
@ -239,13 +238,6 @@ static struct tb_cfg_result decode_error(const struct ctl_pkg *response)
|
|||
if (res.err)
|
||||
return res;
|
||||
|
||||
if (pkg->zero1)
|
||||
tb_ctl_warn(ctl, "pkg->zero1 is %#x\n", pkg->zero1);
|
||||
if (pkg->zero2)
|
||||
tb_ctl_warn(ctl, "pkg->zero2 is %#x\n", pkg->zero2);
|
||||
if (pkg->zero3)
|
||||
tb_ctl_warn(ctl, "pkg->zero3 is %#x\n", pkg->zero3);
|
||||
|
||||
res.err = 1;
|
||||
res.tb_error = pkg->error;
|
||||
res.response_port = pkg->port;
|
||||
|
@ -416,6 +408,7 @@ static int tb_async_error(const struct ctl_pkg *pkg)
|
|||
case TB_CFG_ERROR_LINK_ERROR:
|
||||
case TB_CFG_ERROR_HEC_ERROR_DETECTED:
|
||||
case TB_CFG_ERROR_FLOW_CONTROL_ERROR:
|
||||
case TB_CFG_ERROR_DP_BW:
|
||||
return true;
|
||||
|
||||
default:
|
||||
|
@ -735,6 +728,47 @@ void tb_ctl_stop(struct tb_ctl *ctl)
|
|||
|
||||
/* public interface, commands */
|
||||
|
||||
/**
|
||||
* tb_cfg_ack_notification() - Ack notification
|
||||
* @ctl: Control channel to use
|
||||
* @route: Router that originated the event
|
||||
* @error: Pointer to the notification package
|
||||
*
|
||||
* Call this as response for non-plug notification to ack it. Returns
|
||||
* %0 on success or an error code on failure.
|
||||
*/
|
||||
int tb_cfg_ack_notification(struct tb_ctl *ctl, u64 route,
|
||||
const struct cfg_error_pkg *error)
|
||||
{
|
||||
struct cfg_ack_pkg pkg = {
|
||||
.header = tb_cfg_make_header(route),
|
||||
};
|
||||
const char *name;
|
||||
|
||||
switch (error->error) {
|
||||
case TB_CFG_ERROR_LINK_ERROR:
|
||||
name = "link error";
|
||||
break;
|
||||
case TB_CFG_ERROR_HEC_ERROR_DETECTED:
|
||||
name = "HEC error";
|
||||
break;
|
||||
case TB_CFG_ERROR_FLOW_CONTROL_ERROR:
|
||||
name = "flow control error";
|
||||
break;
|
||||
case TB_CFG_ERROR_DP_BW:
|
||||
name = "DP_BW";
|
||||
break;
|
||||
default:
|
||||
name = "unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
tb_ctl_dbg(ctl, "acking %s (%#x) notification on %llx\n", name,
|
||||
error->error, route);
|
||||
|
||||
return tb_ctl_tx(ctl, &pkg, sizeof(pkg), TB_CFG_PKG_NOTIFY_ACK);
|
||||
}
|
||||
|
||||
/**
|
||||
* tb_cfg_ack_plug() - Ack hot plug/unplug event
|
||||
* @ctl: Control channel to use
|
||||
|
@ -754,7 +788,7 @@ int tb_cfg_ack_plug(struct tb_ctl *ctl, u64 route, u32 port, bool unplug)
|
|||
.pg = unplug ? TB_CFG_ERROR_PG_HOT_UNPLUG
|
||||
: TB_CFG_ERROR_PG_HOT_PLUG,
|
||||
};
|
||||
tb_ctl_dbg(ctl, "acking hot %splug event on %llx:%x\n",
|
||||
tb_ctl_dbg(ctl, "acking hot %splug event on %llx:%u\n",
|
||||
unplug ? "un" : "", route, port);
|
||||
return tb_ctl_tx(ctl, &pkg, sizeof(pkg), TB_CFG_PKG_ERROR);
|
||||
}
|
||||
|
|
|
@ -122,6 +122,8 @@ static inline struct tb_cfg_header tb_cfg_make_header(u64 route)
|
|||
return header;
|
||||
}
|
||||
|
||||
int tb_cfg_ack_notification(struct tb_ctl *ctl, u64 route,
|
||||
const struct cfg_error_pkg *error);
|
||||
int tb_cfg_ack_plug(struct tb_ctl *ctl, u64 route, u32 port, bool unplug);
|
||||
struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route);
|
||||
struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer,
|
||||
|
|
|
@ -942,7 +942,8 @@ static void margining_port_remove(struct tb_port *port)
|
|||
|
||||
snprintf(dir_name, sizeof(dir_name), "port%d", port->port);
|
||||
parent = debugfs_lookup(dir_name, port->sw->debugfs_dir);
|
||||
debugfs_remove_recursive(debugfs_lookup("margining", parent));
|
||||
if (parent)
|
||||
debugfs_remove_recursive(debugfs_lookup("margining", parent));
|
||||
|
||||
kfree(port->usb4->margining);
|
||||
port->usb4->margining = NULL;
|
||||
|
@ -967,19 +968,18 @@ static void margining_switch_init(struct tb_switch *sw)
|
|||
|
||||
static void margining_switch_remove(struct tb_switch *sw)
|
||||
{
|
||||
struct tb_port *upstream, *downstream;
|
||||
struct tb_switch *parent_sw;
|
||||
struct tb_port *downstream;
|
||||
u64 route = tb_route(sw);
|
||||
|
||||
if (!route)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Upstream is removed with the router itself but we need to
|
||||
* remove the downstream port margining directory.
|
||||
*/
|
||||
upstream = tb_upstream_port(sw);
|
||||
parent_sw = tb_switch_parent(sw);
|
||||
downstream = tb_port_at(route, parent_sw);
|
||||
|
||||
margining_port_remove(upstream);
|
||||
margining_port_remove(downstream);
|
||||
}
|
||||
|
||||
|
@ -1159,7 +1159,10 @@ static void port_cap_show(struct tb_port *port, struct seq_file *s,
|
|||
if (tb_port_is_pcie_down(port) || tb_port_is_pcie_up(port)) {
|
||||
length = PORT_CAP_PCIE_LEN;
|
||||
} else if (tb_port_is_dpin(port) || tb_port_is_dpout(port)) {
|
||||
length = PORT_CAP_DP_LEN;
|
||||
if (usb4_dp_port_bw_mode_supported(port))
|
||||
length = PORT_CAP_DP_LEN + 1;
|
||||
else
|
||||
length = PORT_CAP_DP_LEN;
|
||||
} else if (tb_port_is_usb3_down(port) ||
|
||||
tb_port_is_usb3_up(port)) {
|
||||
length = PORT_CAP_USB3_LEN;
|
||||
|
|
|
@ -54,6 +54,26 @@ static int ring_interrupt_index(const struct tb_ring *ring)
|
|||
return bit;
|
||||
}
|
||||
|
||||
static void nhi_mask_interrupt(struct tb_nhi *nhi, int mask, int ring)
|
||||
{
|
||||
if (nhi->quirks & QUIRK_AUTO_CLEAR_INT) {
|
||||
u32 val;
|
||||
|
||||
val = ioread32(nhi->iobase + REG_RING_INTERRUPT_BASE + ring);
|
||||
iowrite32(val & ~mask, nhi->iobase + REG_RING_INTERRUPT_BASE + ring);
|
||||
} else {
|
||||
iowrite32(mask, nhi->iobase + REG_RING_INTERRUPT_MASK_CLEAR_BASE + ring);
|
||||
}
|
||||
}
|
||||
|
||||
static void nhi_clear_interrupt(struct tb_nhi *nhi, int ring)
|
||||
{
|
||||
if (nhi->quirks & QUIRK_AUTO_CLEAR_INT)
|
||||
ioread32(nhi->iobase + REG_RING_NOTIFY_BASE + ring);
|
||||
else
|
||||
iowrite32(~0, nhi->iobase + REG_RING_INT_CLEAR + ring);
|
||||
}
|
||||
|
||||
/*
|
||||
* ring_interrupt_active() - activate/deactivate interrupts for a single ring
|
||||
*
|
||||
|
@ -61,8 +81,8 @@ static int ring_interrupt_index(const struct tb_ring *ring)
|
|||
*/
|
||||
static void ring_interrupt_active(struct tb_ring *ring, bool active)
|
||||
{
|
||||
int reg = REG_RING_INTERRUPT_BASE +
|
||||
ring_interrupt_index(ring) / 32 * 4;
|
||||
int index = ring_interrupt_index(ring) / 32 * 4;
|
||||
int reg = REG_RING_INTERRUPT_BASE + index;
|
||||
int interrupt_bit = ring_interrupt_index(ring) & 31;
|
||||
int mask = 1 << interrupt_bit;
|
||||
u32 old, new;
|
||||
|
@ -123,7 +143,11 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active)
|
|||
"interrupt for %s %d is already %s\n",
|
||||
RING_TYPE(ring), ring->hop,
|
||||
active ? "enabled" : "disabled");
|
||||
iowrite32(new, ring->nhi->iobase + reg);
|
||||
|
||||
if (active)
|
||||
iowrite32(new, ring->nhi->iobase + reg);
|
||||
else
|
||||
nhi_mask_interrupt(ring->nhi, mask, index);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -136,11 +160,11 @@ static void nhi_disable_interrupts(struct tb_nhi *nhi)
|
|||
int i = 0;
|
||||
/* disable interrupts */
|
||||
for (i = 0; i < RING_INTERRUPT_REG_COUNT(nhi); i++)
|
||||
iowrite32(0, nhi->iobase + REG_RING_INTERRUPT_BASE + 4 * i);
|
||||
nhi_mask_interrupt(nhi, ~0, 4 * i);
|
||||
|
||||
/* clear interrupt status bits */
|
||||
for (i = 0; i < RING_NOTIFY_REG_COUNT(nhi); i++)
|
||||
ioread32(nhi->iobase + REG_RING_NOTIFY_BASE + 4 * i);
|
||||
nhi_clear_interrupt(nhi, 4 * i);
|
||||
}
|
||||
|
||||
/* ring helper methods */
|
||||
|
|
|
@ -93,6 +93,8 @@ struct ring_desc {
|
|||
#define REG_RING_INTERRUPT_BASE 0x38200
|
||||
#define RING_INTERRUPT_REG_COUNT(nhi) ((31 + 2 * nhi->hop_count) / 32)
|
||||
|
||||
#define REG_RING_INTERRUPT_MASK_CLEAR_BASE 0x38208
|
||||
|
||||
#define REG_INT_THROTTLING_RATE 0x38c00
|
||||
|
||||
/* Interrupt Vector Allocation */
|
||||
|
|
|
@ -20,6 +20,25 @@ static void quirk_dp_credit_allocation(struct tb_switch *sw)
|
|||
}
|
||||
}
|
||||
|
||||
static void quirk_clx_disable(struct tb_switch *sw)
|
||||
{
|
||||
sw->quirks |= QUIRK_NO_CLX;
|
||||
tb_sw_dbg(sw, "disabling CL states\n");
|
||||
}
|
||||
|
||||
static void quirk_usb3_maximum_bandwidth(struct tb_switch *sw)
|
||||
{
|
||||
struct tb_port *port;
|
||||
|
||||
tb_switch_for_each_port(sw, port) {
|
||||
if (!tb_port_is_usb3_down(port))
|
||||
continue;
|
||||
port->max_bw = 16376;
|
||||
tb_port_dbg(port, "USB3 maximum bandwidth limited to %u Mb/s\n",
|
||||
port->max_bw);
|
||||
}
|
||||
}
|
||||
|
||||
struct tb_quirk {
|
||||
u16 hw_vendor_id;
|
||||
u16 hw_device_id;
|
||||
|
@ -37,6 +56,31 @@ static const struct tb_quirk tb_quirks[] = {
|
|||
* DP buffers.
|
||||
*/
|
||||
{ 0x8087, 0x0b26, 0x0000, 0x0000, quirk_dp_credit_allocation },
|
||||
/*
|
||||
* Limit the maximum USB3 bandwidth for the following Intel USB4
|
||||
* host routers due to a hardware issue.
|
||||
*/
|
||||
{ 0x8087, PCI_DEVICE_ID_INTEL_ADL_NHI0, 0x0000, 0x0000,
|
||||
quirk_usb3_maximum_bandwidth },
|
||||
{ 0x8087, PCI_DEVICE_ID_INTEL_ADL_NHI1, 0x0000, 0x0000,
|
||||
quirk_usb3_maximum_bandwidth },
|
||||
{ 0x8087, PCI_DEVICE_ID_INTEL_RPL_NHI0, 0x0000, 0x0000,
|
||||
quirk_usb3_maximum_bandwidth },
|
||||
{ 0x8087, PCI_DEVICE_ID_INTEL_RPL_NHI1, 0x0000, 0x0000,
|
||||
quirk_usb3_maximum_bandwidth },
|
||||
{ 0x8087, PCI_DEVICE_ID_INTEL_MTL_M_NHI0, 0x0000, 0x0000,
|
||||
quirk_usb3_maximum_bandwidth },
|
||||
{ 0x8087, PCI_DEVICE_ID_INTEL_MTL_P_NHI0, 0x0000, 0x0000,
|
||||
quirk_usb3_maximum_bandwidth },
|
||||
{ 0x8087, PCI_DEVICE_ID_INTEL_MTL_P_NHI1, 0x0000, 0x0000,
|
||||
quirk_usb3_maximum_bandwidth },
|
||||
/*
|
||||
* CLx is not supported on AMD USB4 Yellow Carp and Pink Sardine platforms.
|
||||
*/
|
||||
{ 0x0438, 0x0208, 0x0000, 0x0000, quirk_clx_disable },
|
||||
{ 0x0438, 0x0209, 0x0000, 0x0000, quirk_clx_disable },
|
||||
{ 0x0438, 0x020a, 0x0000, 0x0000, quirk_clx_disable },
|
||||
{ 0x0438, 0x020b, 0x0000, 0x0000, quirk_clx_disable },
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -187,6 +187,22 @@ static ssize_t nvm_authenticate_show(struct device *dev,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static void tb_retimer_set_inbound_sbtx(struct tb_port *port)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 1; i <= TB_MAX_RETIMER_INDEX; i++)
|
||||
usb4_port_retimer_set_inbound_sbtx(port, i);
|
||||
}
|
||||
|
||||
static void tb_retimer_unset_inbound_sbtx(struct tb_port *port)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = TB_MAX_RETIMER_INDEX; i >= 1; i--)
|
||||
usb4_port_retimer_unset_inbound_sbtx(port, i);
|
||||
}
|
||||
|
||||
static ssize_t nvm_authenticate_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
|
@ -213,6 +229,7 @@ static ssize_t nvm_authenticate_store(struct device *dev,
|
|||
rt->auth_status = 0;
|
||||
|
||||
if (val) {
|
||||
tb_retimer_set_inbound_sbtx(rt->port);
|
||||
if (val == AUTHENTICATE_ONLY) {
|
||||
ret = tb_retimer_nvm_authenticate(rt, true);
|
||||
} else {
|
||||
|
@ -232,6 +249,7 @@ static ssize_t nvm_authenticate_store(struct device *dev,
|
|||
}
|
||||
|
||||
exit_unlock:
|
||||
tb_retimer_unset_inbound_sbtx(rt->port);
|
||||
mutex_unlock(&rt->tb->lock);
|
||||
exit_rpm:
|
||||
pm_runtime_mark_last_busy(&rt->dev);
|
||||
|
@ -440,8 +458,7 @@ int tb_retimer_scan(struct tb_port *port, bool add)
|
|||
* Enable sideband channel for each retimer. We can do this
|
||||
* regardless whether there is device connected or not.
|
||||
*/
|
||||
for (i = 1; i <= TB_MAX_RETIMER_INDEX; i++)
|
||||
usb4_port_retimer_set_inbound_sbtx(port, i);
|
||||
tb_retimer_set_inbound_sbtx(port);
|
||||
|
||||
/*
|
||||
* Before doing anything else, read the authentication status.
|
||||
|
@ -464,6 +481,8 @@ int tb_retimer_scan(struct tb_port *port, bool add)
|
|||
break;
|
||||
}
|
||||
|
||||
tb_retimer_unset_inbound_sbtx(port);
|
||||
|
||||
if (!last_idx)
|
||||
return 0;
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ enum usb4_sb_opcode {
|
|||
USB4_SB_OPCODE_ROUTER_OFFLINE = 0x4e45534c, /* "LSEN" */
|
||||
USB4_SB_OPCODE_ENUMERATE_RETIMERS = 0x4d554e45, /* "ENUM" */
|
||||
USB4_SB_OPCODE_SET_INBOUND_SBTX = 0x5055534c, /* "LSUP" */
|
||||
USB4_SB_OPCODE_UNSET_INBOUND_SBTX = 0x50555355, /* "USUP" */
|
||||
USB4_SB_OPCODE_QUERY_LAST_RETIMER = 0x5453414c, /* "LAST" */
|
||||
USB4_SB_OPCODE_GET_NVM_SECTOR_SIZE = 0x53534e47, /* "GNSS" */
|
||||
USB4_SB_OPCODE_NVM_SET_OFFSET = 0x53504f42, /* "BOPS" */
|
||||
|
|
|
@ -513,36 +513,44 @@ int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged)
|
|||
|
||||
while (retries--) {
|
||||
state = tb_port_state(port);
|
||||
if (state < 0)
|
||||
return state;
|
||||
if (state == TB_PORT_DISABLED) {
|
||||
switch (state) {
|
||||
case TB_PORT_DISABLED:
|
||||
tb_port_dbg(port, "is disabled (state: 0)\n");
|
||||
return 0;
|
||||
}
|
||||
if (state == TB_PORT_UNPLUGGED) {
|
||||
|
||||
case TB_PORT_UNPLUGGED:
|
||||
if (wait_if_unplugged) {
|
||||
/* used during resume */
|
||||
tb_port_dbg(port,
|
||||
"is unplugged (state: 7), retrying...\n");
|
||||
msleep(100);
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
tb_port_dbg(port, "is unplugged (state: 7)\n");
|
||||
return 0;
|
||||
}
|
||||
if (state == TB_PORT_UP) {
|
||||
tb_port_dbg(port, "is connected, link is up (state: 2)\n");
|
||||
|
||||
case TB_PORT_UP:
|
||||
case TB_PORT_TX_CL0S:
|
||||
case TB_PORT_RX_CL0S:
|
||||
case TB_PORT_CL1:
|
||||
case TB_PORT_CL2:
|
||||
tb_port_dbg(port, "is connected, link is up (state: %d)\n", state);
|
||||
return 1;
|
||||
|
||||
default:
|
||||
if (state < 0)
|
||||
return state;
|
||||
|
||||
/*
|
||||
* After plug-in the state is TB_PORT_CONNECTING. Give it some
|
||||
* time.
|
||||
*/
|
||||
tb_port_dbg(port,
|
||||
"is connected, link is not up (state: %d), retrying...\n",
|
||||
state);
|
||||
msleep(100);
|
||||
}
|
||||
|
||||
/*
|
||||
* After plug-in the state is TB_PORT_CONNECTING. Give it some
|
||||
* time.
|
||||
*/
|
||||
tb_port_dbg(port,
|
||||
"is connected, link is not up (state: %d), retrying...\n",
|
||||
state);
|
||||
msleep(100);
|
||||
}
|
||||
tb_port_warn(port,
|
||||
"failed to reach state TB_PORT_UP. Ignoring port...\n");
|
||||
|
@ -2960,8 +2968,6 @@ int tb_switch_add(struct tb_switch *sw)
|
|||
dev_warn(&sw->dev, "reading DROM failed: %d\n", ret);
|
||||
tb_sw_dbg(sw, "uid: %#llx\n", sw->uid);
|
||||
|
||||
tb_check_quirks(sw);
|
||||
|
||||
ret = tb_switch_set_uuid(sw);
|
||||
if (ret) {
|
||||
dev_err(&sw->dev, "failed to set UUID\n");
|
||||
|
@ -2980,6 +2986,8 @@ int tb_switch_add(struct tb_switch *sw)
|
|||
}
|
||||
}
|
||||
|
||||
tb_check_quirks(sw);
|
||||
|
||||
tb_switch_default_link_ports(sw);
|
||||
|
||||
ret = tb_switch_update_link_attributes(sw);
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
#include "tb_regs.h"
|
||||
#include "tunnel.h"
|
||||
|
||||
#define TB_TIMEOUT 100 /* ms */
|
||||
#define TB_TIMEOUT 100 /* ms */
|
||||
#define MAX_GROUPS 7 /* max Group_ID is 7 */
|
||||
|
||||
/**
|
||||
* struct tb_cm - Simple Thunderbolt connection manager
|
||||
|
@ -28,12 +29,14 @@
|
|||
* after cfg has been paused.
|
||||
* @remove_work: Work used to remove any unplugged routers after
|
||||
* runtime resume
|
||||
* @groups: Bandwidth groups used in this domain.
|
||||
*/
|
||||
struct tb_cm {
|
||||
struct list_head tunnel_list;
|
||||
struct list_head dp_resources;
|
||||
bool hotplug_active;
|
||||
struct delayed_work remove_work;
|
||||
struct tb_bandwidth_group groups[MAX_GROUPS];
|
||||
};
|
||||
|
||||
static inline struct tb *tcm_to_tb(struct tb_cm *tcm)
|
||||
|
@ -49,6 +52,112 @@ struct tb_hotplug_event {
|
|||
bool unplug;
|
||||
};
|
||||
|
||||
static void tb_init_bandwidth_groups(struct tb_cm *tcm)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(tcm->groups); i++) {
|
||||
struct tb_bandwidth_group *group = &tcm->groups[i];
|
||||
|
||||
group->tb = tcm_to_tb(tcm);
|
||||
group->index = i + 1;
|
||||
INIT_LIST_HEAD(&group->ports);
|
||||
}
|
||||
}
|
||||
|
||||
static void tb_bandwidth_group_attach_port(struct tb_bandwidth_group *group,
|
||||
struct tb_port *in)
|
||||
{
|
||||
if (!group || WARN_ON(in->group))
|
||||
return;
|
||||
|
||||
in->group = group;
|
||||
list_add_tail(&in->group_list, &group->ports);
|
||||
|
||||
tb_port_dbg(in, "attached to bandwidth group %d\n", group->index);
|
||||
}
|
||||
|
||||
static struct tb_bandwidth_group *tb_find_free_bandwidth_group(struct tb_cm *tcm)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(tcm->groups); i++) {
|
||||
struct tb_bandwidth_group *group = &tcm->groups[i];
|
||||
|
||||
if (list_empty(&group->ports))
|
||||
return group;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct tb_bandwidth_group *
|
||||
tb_attach_bandwidth_group(struct tb_cm *tcm, struct tb_port *in,
|
||||
struct tb_port *out)
|
||||
{
|
||||
struct tb_bandwidth_group *group;
|
||||
struct tb_tunnel *tunnel;
|
||||
|
||||
/*
|
||||
* Find all DP tunnels that go through all the same USB4 links
|
||||
* as this one. Because we always setup tunnels the same way we
|
||||
* can just check for the routers at both ends of the tunnels
|
||||
* and if they are the same we have a match.
|
||||
*/
|
||||
list_for_each_entry(tunnel, &tcm->tunnel_list, list) {
|
||||
if (!tb_tunnel_is_dp(tunnel))
|
||||
continue;
|
||||
|
||||
if (tunnel->src_port->sw == in->sw &&
|
||||
tunnel->dst_port->sw == out->sw) {
|
||||
group = tunnel->src_port->group;
|
||||
if (group) {
|
||||
tb_bandwidth_group_attach_port(group, in);
|
||||
return group;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Pick up next available group then */
|
||||
group = tb_find_free_bandwidth_group(tcm);
|
||||
if (group)
|
||||
tb_bandwidth_group_attach_port(group, in);
|
||||
else
|
||||
tb_port_warn(in, "no available bandwidth groups\n");
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
static void tb_discover_bandwidth_group(struct tb_cm *tcm, struct tb_port *in,
|
||||
struct tb_port *out)
|
||||
{
|
||||
if (usb4_dp_port_bw_mode_enabled(in)) {
|
||||
int index, i;
|
||||
|
||||
index = usb4_dp_port_group_id(in);
|
||||
for (i = 0; i < ARRAY_SIZE(tcm->groups); i++) {
|
||||
if (tcm->groups[i].index == index) {
|
||||
tb_bandwidth_group_attach_port(&tcm->groups[i], in);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tb_attach_bandwidth_group(tcm, in, out);
|
||||
}
|
||||
|
||||
static void tb_detach_bandwidth_group(struct tb_port *in)
|
||||
{
|
||||
struct tb_bandwidth_group *group = in->group;
|
||||
|
||||
if (group) {
|
||||
in->group = NULL;
|
||||
list_del_init(&in->group_list);
|
||||
|
||||
tb_port_dbg(in, "detached from bandwidth group %d\n", group->index);
|
||||
}
|
||||
}
|
||||
|
||||
static void tb_handle_hotplug(struct work_struct *work);
|
||||
|
||||
static void tb_queue_hotplug(struct tb *tb, u64 route, u8 port, bool unplug)
|
||||
|
@ -193,9 +302,14 @@ static void tb_discover_tunnels(struct tb *tb)
|
|||
parent = tb_switch_parent(parent);
|
||||
}
|
||||
} else if (tb_tunnel_is_dp(tunnel)) {
|
||||
struct tb_port *in = tunnel->src_port;
|
||||
struct tb_port *out = tunnel->dst_port;
|
||||
|
||||
/* Keep the domain from powering down */
|
||||
pm_runtime_get_sync(&tunnel->src_port->sw->dev);
|
||||
pm_runtime_get_sync(&tunnel->dst_port->sw->dev);
|
||||
pm_runtime_get_sync(&in->sw->dev);
|
||||
pm_runtime_get_sync(&out->sw->dev);
|
||||
|
||||
tb_discover_bandwidth_group(tcm, in, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -350,10 +464,13 @@ static int tb_available_bandwidth(struct tb *tb, struct tb_port *src_port,
|
|||
struct tb_tunnel *tunnel;
|
||||
struct tb_port *port;
|
||||
|
||||
tb_port_dbg(dst_port, "calculating available bandwidth\n");
|
||||
tb_dbg(tb, "calculating available bandwidth between %llx:%u <-> %llx:%u\n",
|
||||
tb_route(src_port->sw), src_port->port, tb_route(dst_port->sw),
|
||||
dst_port->port);
|
||||
|
||||
tunnel = tb_find_first_usb3_tunnel(tb, src_port, dst_port);
|
||||
if (tunnel) {
|
||||
if (tunnel && tunnel->src_port != src_port &&
|
||||
tunnel->dst_port != dst_port) {
|
||||
ret = tb_tunnel_consumed_bandwidth(tunnel, &usb3_consumed_up,
|
||||
&usb3_consumed_down);
|
||||
if (ret)
|
||||
|
@ -387,7 +504,8 @@ static int tb_available_bandwidth(struct tb *tb, struct tb_port *src_port,
|
|||
up_bw -= up_bw / 10;
|
||||
down_bw = up_bw;
|
||||
|
||||
tb_port_dbg(port, "link total bandwidth %d Mb/s\n", up_bw);
|
||||
tb_port_dbg(port, "link total bandwidth %d/%d Mb/s\n", up_bw,
|
||||
down_bw);
|
||||
|
||||
/*
|
||||
* Find all DP tunnels that cross the port and reduce
|
||||
|
@ -396,12 +514,24 @@ static int tb_available_bandwidth(struct tb *tb, struct tb_port *src_port,
|
|||
list_for_each_entry(tunnel, &tcm->tunnel_list, list) {
|
||||
int dp_consumed_up, dp_consumed_down;
|
||||
|
||||
if (tb_tunnel_is_invalid(tunnel))
|
||||
continue;
|
||||
|
||||
if (!tb_tunnel_is_dp(tunnel))
|
||||
continue;
|
||||
|
||||
if (!tb_tunnel_port_on_path(tunnel, port))
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Ignore the DP tunnel between src_port and
|
||||
* dst_port because it is the same tunnel and we
|
||||
* may be re-calculating estimated bandwidth.
|
||||
*/
|
||||
if (tunnel->src_port == src_port &&
|
||||
tunnel->dst_port == dst_port)
|
||||
continue;
|
||||
|
||||
ret = tb_tunnel_consumed_bandwidth(tunnel,
|
||||
&dp_consumed_up,
|
||||
&dp_consumed_down);
|
||||
|
@ -762,6 +892,7 @@ static void tb_deactivate_and_free_tunnel(struct tb_tunnel *tunnel)
|
|||
|
||||
switch (tunnel->type) {
|
||||
case TB_TUNNEL_DP:
|
||||
tb_detach_bandwidth_group(src_port);
|
||||
/*
|
||||
* In case of DP tunnel make sure the DP IN resource is
|
||||
* deallocated properly.
|
||||
|
@ -879,6 +1010,99 @@ out:
|
|||
return tb_find_unused_port(sw, TB_TYPE_PCIE_DOWN);
|
||||
}
|
||||
|
||||
static void
|
||||
tb_recalc_estimated_bandwidth_for_group(struct tb_bandwidth_group *group)
|
||||
{
|
||||
struct tb_tunnel *first_tunnel;
|
||||
struct tb *tb = group->tb;
|
||||
struct tb_port *in;
|
||||
int ret;
|
||||
|
||||
tb_dbg(tb, "re-calculating bandwidth estimation for group %u\n",
|
||||
group->index);
|
||||
|
||||
first_tunnel = NULL;
|
||||
list_for_each_entry(in, &group->ports, group_list) {
|
||||
int estimated_bw, estimated_up, estimated_down;
|
||||
struct tb_tunnel *tunnel;
|
||||
struct tb_port *out;
|
||||
|
||||
if (!usb4_dp_port_bw_mode_enabled(in))
|
||||
continue;
|
||||
|
||||
tunnel = tb_find_tunnel(tb, TB_TUNNEL_DP, in, NULL);
|
||||
if (WARN_ON(!tunnel))
|
||||
break;
|
||||
|
||||
if (!first_tunnel) {
|
||||
/*
|
||||
* Since USB3 bandwidth is shared by all DP
|
||||
* tunnels under the host router USB4 port, even
|
||||
* if they do not begin from the host router, we
|
||||
* can release USB3 bandwidth just once and not
|
||||
* for each tunnel separately.
|
||||
*/
|
||||
first_tunnel = tunnel;
|
||||
ret = tb_release_unused_usb3_bandwidth(tb,
|
||||
first_tunnel->src_port, first_tunnel->dst_port);
|
||||
if (ret) {
|
||||
tb_port_warn(in,
|
||||
"failed to release unused bandwidth\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
out = tunnel->dst_port;
|
||||
ret = tb_available_bandwidth(tb, in, out, &estimated_up,
|
||||
&estimated_down);
|
||||
if (ret) {
|
||||
tb_port_warn(in,
|
||||
"failed to re-calculate estimated bandwidth\n");
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Estimated bandwidth includes:
|
||||
* - already allocated bandwidth for the DP tunnel
|
||||
* - available bandwidth along the path
|
||||
* - bandwidth allocated for USB 3.x but not used.
|
||||
*/
|
||||
tb_port_dbg(in, "re-calculated estimated bandwidth %u/%u Mb/s\n",
|
||||
estimated_up, estimated_down);
|
||||
|
||||
if (in->sw->config.depth < out->sw->config.depth)
|
||||
estimated_bw = estimated_down;
|
||||
else
|
||||
estimated_bw = estimated_up;
|
||||
|
||||
if (usb4_dp_port_set_estimated_bw(in, estimated_bw))
|
||||
tb_port_warn(in, "failed to update estimated bandwidth\n");
|
||||
}
|
||||
|
||||
if (first_tunnel)
|
||||
tb_reclaim_usb3_bandwidth(tb, first_tunnel->src_port,
|
||||
first_tunnel->dst_port);
|
||||
|
||||
tb_dbg(tb, "bandwidth estimation for group %u done\n", group->index);
|
||||
}
|
||||
|
||||
static void tb_recalc_estimated_bandwidth(struct tb *tb)
|
||||
{
|
||||
struct tb_cm *tcm = tb_priv(tb);
|
||||
int i;
|
||||
|
||||
tb_dbg(tb, "bandwidth consumption changed, re-calculating estimated bandwidth\n");
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(tcm->groups); i++) {
|
||||
struct tb_bandwidth_group *group = &tcm->groups[i];
|
||||
|
||||
if (!list_empty(&group->ports))
|
||||
tb_recalc_estimated_bandwidth_for_group(group);
|
||||
}
|
||||
|
||||
tb_dbg(tb, "bandwidth re-calculation done\n");
|
||||
}
|
||||
|
||||
static struct tb_port *tb_find_dp_out(struct tb *tb, struct tb_port *in)
|
||||
{
|
||||
struct tb_port *host_port, *port;
|
||||
|
@ -892,7 +1116,7 @@ static struct tb_port *tb_find_dp_out(struct tb *tb, struct tb_port *in)
|
|||
continue;
|
||||
|
||||
if (tb_port_is_enabled(port)) {
|
||||
tb_port_dbg(port, "in use\n");
|
||||
tb_port_dbg(port, "DP OUT in use\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -941,7 +1165,7 @@ static void tb_tunnel_dp(struct tb *tb)
|
|||
continue;
|
||||
|
||||
if (tb_port_is_enabled(port)) {
|
||||
tb_port_dbg(port, "in use\n");
|
||||
tb_port_dbg(port, "DP IN in use\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -993,17 +1217,19 @@ static void tb_tunnel_dp(struct tb *tb)
|
|||
goto err_rpm_put;
|
||||
}
|
||||
|
||||
if (!tb_attach_bandwidth_group(tcm, in, out))
|
||||
goto err_dealloc_dp;
|
||||
|
||||
/* Make all unused USB3 bandwidth available for the new DP tunnel */
|
||||
ret = tb_release_unused_usb3_bandwidth(tb, in, out);
|
||||
if (ret) {
|
||||
tb_warn(tb, "failed to release unused bandwidth\n");
|
||||
goto err_dealloc_dp;
|
||||
goto err_detach_group;
|
||||
}
|
||||
|
||||
ret = tb_available_bandwidth(tb, in, out, &available_up,
|
||||
&available_down);
|
||||
ret = tb_available_bandwidth(tb, in, out, &available_up, &available_down);
|
||||
if (ret)
|
||||
goto err_reclaim;
|
||||
goto err_reclaim_usb;
|
||||
|
||||
tb_dbg(tb, "available bandwidth for new DP tunnel %u/%u Mb/s\n",
|
||||
available_up, available_down);
|
||||
|
@ -1012,7 +1238,7 @@ static void tb_tunnel_dp(struct tb *tb)
|
|||
available_down);
|
||||
if (!tunnel) {
|
||||
tb_port_dbg(out, "could not allocate DP tunnel\n");
|
||||
goto err_reclaim;
|
||||
goto err_reclaim_usb;
|
||||
}
|
||||
|
||||
if (tb_tunnel_activate(tunnel)) {
|
||||
|
@ -1022,6 +1248,10 @@ static void tb_tunnel_dp(struct tb *tb)
|
|||
|
||||
list_add_tail(&tunnel->list, &tcm->tunnel_list);
|
||||
tb_reclaim_usb3_bandwidth(tb, in, out);
|
||||
|
||||
/* Update the domain with the new bandwidth estimation */
|
||||
tb_recalc_estimated_bandwidth(tb);
|
||||
|
||||
/*
|
||||
* In case of DP tunnel exists, change host router's 1st children
|
||||
* TMU mode to HiFi for CL0s to work.
|
||||
|
@ -1032,8 +1262,10 @@ static void tb_tunnel_dp(struct tb *tb)
|
|||
|
||||
err_free:
|
||||
tb_tunnel_free(tunnel);
|
||||
err_reclaim:
|
||||
err_reclaim_usb:
|
||||
tb_reclaim_usb3_bandwidth(tb, in, out);
|
||||
err_detach_group:
|
||||
tb_detach_bandwidth_group(in);
|
||||
err_dealloc_dp:
|
||||
tb_switch_dealloc_dp_resource(in->sw, in);
|
||||
err_rpm_put:
|
||||
|
@ -1066,6 +1298,7 @@ static void tb_dp_resource_unavailable(struct tb *tb, struct tb_port *port)
|
|||
* See if there is another DP OUT port that can be used for
|
||||
* to create another tunnel.
|
||||
*/
|
||||
tb_recalc_estimated_bandwidth(tb);
|
||||
tb_tunnel_dp(tb);
|
||||
}
|
||||
|
||||
|
@ -1313,6 +1546,7 @@ static void tb_handle_hotplug(struct work_struct *work)
|
|||
if (port->dual_link_port)
|
||||
port->dual_link_port->remote = NULL;
|
||||
/* Maybe we can create another DP tunnel */
|
||||
tb_recalc_estimated_bandwidth(tb);
|
||||
tb_tunnel_dp(tb);
|
||||
} else if (port->xdomain) {
|
||||
struct tb_xdomain *xd = tb_xdomain_get(port->xdomain);
|
||||
|
@ -1370,6 +1604,239 @@ out:
|
|||
kfree(ev);
|
||||
}
|
||||
|
||||
static int tb_alloc_dp_bandwidth(struct tb_tunnel *tunnel, int *requested_up,
|
||||
int *requested_down)
|
||||
{
|
||||
int allocated_up, allocated_down, available_up, available_down, ret;
|
||||
int requested_up_corrected, requested_down_corrected, granularity;
|
||||
int max_up, max_down, max_up_rounded, max_down_rounded;
|
||||
struct tb *tb = tunnel->tb;
|
||||
struct tb_port *in, *out;
|
||||
|
||||
ret = tb_tunnel_allocated_bandwidth(tunnel, &allocated_up, &allocated_down);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
in = tunnel->src_port;
|
||||
out = tunnel->dst_port;
|
||||
|
||||
tb_port_dbg(in, "bandwidth allocated currently %d/%d Mb/s\n",
|
||||
allocated_up, allocated_down);
|
||||
|
||||
/*
|
||||
* If we get rounded up request from graphics side, say HBR2 x 4
|
||||
* that is 17500 instead of 17280 (this is because of the
|
||||
* granularity), we allow it too. Here the graphics has already
|
||||
* negotiated with the DPRX the maximum possible rates (which is
|
||||
* 17280 in this case).
|
||||
*
|
||||
* Since the link cannot go higher than 17280 we use that in our
|
||||
* calculations but the DP IN adapter Allocated BW write must be
|
||||
* the same value (17500) otherwise the adapter will mark it as
|
||||
* failed for graphics.
|
||||
*/
|
||||
ret = tb_tunnel_maximum_bandwidth(tunnel, &max_up, &max_down);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = usb4_dp_port_granularity(in);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
granularity = ret;
|
||||
|
||||
max_up_rounded = roundup(max_up, granularity);
|
||||
max_down_rounded = roundup(max_down, granularity);
|
||||
|
||||
/*
|
||||
* This will "fix" the request down to the maximum supported
|
||||
* rate * lanes if it is at the maximum rounded up level.
|
||||
*/
|
||||
requested_up_corrected = *requested_up;
|
||||
if (requested_up_corrected == max_up_rounded)
|
||||
requested_up_corrected = max_up;
|
||||
else if (requested_up_corrected < 0)
|
||||
requested_up_corrected = 0;
|
||||
requested_down_corrected = *requested_down;
|
||||
if (requested_down_corrected == max_down_rounded)
|
||||
requested_down_corrected = max_down;
|
||||
else if (requested_down_corrected < 0)
|
||||
requested_down_corrected = 0;
|
||||
|
||||
tb_port_dbg(in, "corrected bandwidth request %d/%d Mb/s\n",
|
||||
requested_up_corrected, requested_down_corrected);
|
||||
|
||||
if ((*requested_up >= 0 && requested_up_corrected > max_up_rounded) ||
|
||||
(*requested_down >= 0 && requested_down_corrected > max_down_rounded)) {
|
||||
tb_port_dbg(in, "bandwidth request too high (%d/%d Mb/s > %d/%d Mb/s)\n",
|
||||
requested_up_corrected, requested_down_corrected,
|
||||
max_up_rounded, max_down_rounded);
|
||||
return -ENOBUFS;
|
||||
}
|
||||
|
||||
if ((*requested_up >= 0 && requested_up_corrected <= allocated_up) ||
|
||||
(*requested_down >= 0 && requested_down_corrected <= allocated_down)) {
|
||||
/*
|
||||
* If requested bandwidth is less or equal than what is
|
||||
* currently allocated to that tunnel we simply change
|
||||
* the reservation of the tunnel. Since all the tunnels
|
||||
* going out from the same USB4 port are in the same
|
||||
* group the released bandwidth will be taken into
|
||||
* account for the other tunnels automatically below.
|
||||
*/
|
||||
return tb_tunnel_alloc_bandwidth(tunnel, requested_up,
|
||||
requested_down);
|
||||
}
|
||||
|
||||
/*
|
||||
* More bandwidth is requested. Release all the potential
|
||||
* bandwidth from USB3 first.
|
||||
*/
|
||||
ret = tb_release_unused_usb3_bandwidth(tb, in, out);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Then go over all tunnels that cross the same USB4 ports (they
|
||||
* are also in the same group but we use the same function here
|
||||
* that we use with the normal bandwidth allocation).
|
||||
*/
|
||||
ret = tb_available_bandwidth(tb, in, out, &available_up, &available_down);
|
||||
if (ret)
|
||||
goto reclaim;
|
||||
|
||||
tb_port_dbg(in, "bandwidth available for allocation %d/%d Mb/s\n",
|
||||
available_up, available_down);
|
||||
|
||||
if ((*requested_up >= 0 && available_up >= requested_up_corrected) ||
|
||||
(*requested_down >= 0 && available_down >= requested_down_corrected)) {
|
||||
ret = tb_tunnel_alloc_bandwidth(tunnel, requested_up,
|
||||
requested_down);
|
||||
} else {
|
||||
ret = -ENOBUFS;
|
||||
}
|
||||
|
||||
reclaim:
|
||||
tb_reclaim_usb3_bandwidth(tb, in, out);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void tb_handle_dp_bandwidth_request(struct work_struct *work)
|
||||
{
|
||||
struct tb_hotplug_event *ev = container_of(work, typeof(*ev), work);
|
||||
int requested_bw, requested_up, requested_down, ret;
|
||||
struct tb_port *in, *out;
|
||||
struct tb_tunnel *tunnel;
|
||||
struct tb *tb = ev->tb;
|
||||
struct tb_cm *tcm = tb_priv(tb);
|
||||
struct tb_switch *sw;
|
||||
|
||||
pm_runtime_get_sync(&tb->dev);
|
||||
|
||||
mutex_lock(&tb->lock);
|
||||
if (!tcm->hotplug_active)
|
||||
goto unlock;
|
||||
|
||||
sw = tb_switch_find_by_route(tb, ev->route);
|
||||
if (!sw) {
|
||||
tb_warn(tb, "bandwidth request from non-existent router %llx\n",
|
||||
ev->route);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
in = &sw->ports[ev->port];
|
||||
if (!tb_port_is_dpin(in)) {
|
||||
tb_port_warn(in, "bandwidth request to non-DP IN adapter\n");
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
tb_port_dbg(in, "handling bandwidth allocation request\n");
|
||||
|
||||
if (!usb4_dp_port_bw_mode_enabled(in)) {
|
||||
tb_port_warn(in, "bandwidth allocation mode not enabled\n");
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
ret = usb4_dp_port_requested_bw(in);
|
||||
if (ret < 0) {
|
||||
if (ret == -ENODATA)
|
||||
tb_port_dbg(in, "no bandwidth request active\n");
|
||||
else
|
||||
tb_port_warn(in, "failed to read requested bandwidth\n");
|
||||
goto unlock;
|
||||
}
|
||||
requested_bw = ret;
|
||||
|
||||
tb_port_dbg(in, "requested bandwidth %d Mb/s\n", requested_bw);
|
||||
|
||||
tunnel = tb_find_tunnel(tb, TB_TUNNEL_DP, in, NULL);
|
||||
if (!tunnel) {
|
||||
tb_port_warn(in, "failed to find tunnel\n");
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
out = tunnel->dst_port;
|
||||
|
||||
if (in->sw->config.depth < out->sw->config.depth) {
|
||||
requested_up = -1;
|
||||
requested_down = requested_bw;
|
||||
} else {
|
||||
requested_up = requested_bw;
|
||||
requested_down = -1;
|
||||
}
|
||||
|
||||
ret = tb_alloc_dp_bandwidth(tunnel, &requested_up, &requested_down);
|
||||
if (ret) {
|
||||
if (ret == -ENOBUFS)
|
||||
tb_port_warn(in, "not enough bandwidth available\n");
|
||||
else
|
||||
tb_port_warn(in, "failed to change bandwidth allocation\n");
|
||||
} else {
|
||||
tb_port_dbg(in, "bandwidth allocation changed to %d/%d Mb/s\n",
|
||||
requested_up, requested_down);
|
||||
|
||||
/* Update other clients about the allocation change */
|
||||
tb_recalc_estimated_bandwidth(tb);
|
||||
}
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&tb->lock);
|
||||
|
||||
pm_runtime_mark_last_busy(&tb->dev);
|
||||
pm_runtime_put_autosuspend(&tb->dev);
|
||||
}
|
||||
|
||||
static void tb_queue_dp_bandwidth_request(struct tb *tb, u64 route, u8 port)
|
||||
{
|
||||
struct tb_hotplug_event *ev;
|
||||
|
||||
ev = kmalloc(sizeof(*ev), GFP_KERNEL);
|
||||
if (!ev)
|
||||
return;
|
||||
|
||||
ev->tb = tb;
|
||||
ev->route = route;
|
||||
ev->port = port;
|
||||
INIT_WORK(&ev->work, tb_handle_dp_bandwidth_request);
|
||||
queue_work(tb->wq, &ev->work);
|
||||
}
|
||||
|
||||
static void tb_handle_notification(struct tb *tb, u64 route,
|
||||
const struct cfg_error_pkg *error)
|
||||
{
|
||||
if (tb_cfg_ack_notification(tb->ctl, route, error))
|
||||
tb_warn(tb, "could not ack notification on %llx\n", route);
|
||||
|
||||
switch (error->error) {
|
||||
case TB_CFG_ERROR_DP_BW:
|
||||
tb_queue_dp_bandwidth_request(tb, route, error->port);
|
||||
break;
|
||||
|
||||
default:
|
||||
/* Ack is enough */
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* tb_schedule_hotplug_handler() - callback function for the control channel
|
||||
*
|
||||
|
@ -1379,15 +1846,19 @@ static void tb_handle_event(struct tb *tb, enum tb_cfg_pkg_type type,
|
|||
const void *buf, size_t size)
|
||||
{
|
||||
const struct cfg_event_pkg *pkg = buf;
|
||||
u64 route;
|
||||
u64 route = tb_cfg_get_route(&pkg->header);
|
||||
|
||||
if (type != TB_CFG_PKG_EVENT) {
|
||||
switch (type) {
|
||||
case TB_CFG_PKG_ERROR:
|
||||
tb_handle_notification(tb, route, (const struct cfg_error_pkg *)buf);
|
||||
return;
|
||||
case TB_CFG_PKG_EVENT:
|
||||
break;
|
||||
default:
|
||||
tb_warn(tb, "unexpected event %#x, ignoring\n", type);
|
||||
return;
|
||||
}
|
||||
|
||||
route = tb_cfg_get_route(&pkg->header);
|
||||
|
||||
if (tb_cfg_ack_plug(tb->ctl, route, pkg->port, pkg->unplug)) {
|
||||
tb_warn(tb, "could not ack plug event on %llx:%x\n", route,
|
||||
pkg->port);
|
||||
|
@ -1817,6 +2288,7 @@ struct tb *tb_probe(struct tb_nhi *nhi)
|
|||
INIT_LIST_HEAD(&tcm->tunnel_list);
|
||||
INIT_LIST_HEAD(&tcm->dp_resources);
|
||||
INIT_DELAYED_WORK(&tcm->remove_work, tb_remove_work);
|
||||
tb_init_bandwidth_groups(tcm);
|
||||
|
||||
tb_dbg(tb, "using software connection manager\n");
|
||||
|
||||
|
|
|
@ -23,6 +23,11 @@
|
|||
#define NVM_MAX_SIZE SZ_512K
|
||||
#define NVM_DATA_DWORDS 16
|
||||
|
||||
/* Keep link controller awake during update */
|
||||
#define QUIRK_FORCE_POWER_LINK_CONTROLLER BIT(0)
|
||||
/* Disable CLx if not supported */
|
||||
#define QUIRK_NO_CLX BIT(1)
|
||||
|
||||
/**
|
||||
* struct tb_nvm - Structure holding NVM information
|
||||
* @dev: Owner of the NVM
|
||||
|
@ -223,6 +228,23 @@ struct tb_switch {
|
|||
enum tb_clx clx;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct tb_bandwidth_group - Bandwidth management group
|
||||
* @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
|
||||
*
|
||||
* Any tunnel that requires isochronous bandwidth (that's DP for now) is
|
||||
* attached to a bandwidth group. All tunnels going through the same
|
||||
* USB4 links share the same group and can dynamically distribute the
|
||||
* bandwidth within the group.
|
||||
*/
|
||||
struct tb_bandwidth_group {
|
||||
struct tb *tb;
|
||||
int index;
|
||||
struct list_head ports;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct tb_port - a thunderbolt port, part of a tb_switch
|
||||
* @config: Cached port configuration read from registers
|
||||
|
@ -247,6 +269,11 @@ struct tb_switch {
|
|||
* @ctl_credits: Buffers reserved for control path
|
||||
* @dma_credits: Number of credits allocated for DMA tunneling for all
|
||||
* DMA paths through this port.
|
||||
* @group: Bandwidth allocation group the adapter is assigned to. Only
|
||||
* used for DP IN adapters for now.
|
||||
* @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.
|
||||
*
|
||||
* In USB4 terminology this structure represents an adapter (protocol or
|
||||
* lane adapter).
|
||||
|
@ -272,6 +299,9 @@ struct tb_port {
|
|||
unsigned int total_credits;
|
||||
unsigned int ctl_credits;
|
||||
unsigned int dma_credits;
|
||||
struct tb_bandwidth_group *group;
|
||||
struct list_head group_list;
|
||||
unsigned int max_bw;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -997,6 +1027,9 @@ static inline bool tb_switch_is_clx_enabled(const struct tb_switch *sw,
|
|||
*/
|
||||
static inline bool tb_switch_is_clx_supported(const struct tb_switch *sw)
|
||||
{
|
||||
if (sw->quirks & QUIRK_NO_CLX)
|
||||
return false;
|
||||
|
||||
return tb_switch_is_usb4(sw) || tb_switch_is_titan_ridge(sw);
|
||||
}
|
||||
|
||||
|
@ -1047,7 +1080,7 @@ void tb_port_lane_bonding_disable(struct tb_port *port);
|
|||
int tb_port_wait_for_link_width(struct tb_port *port, int width,
|
||||
int timeout_msec);
|
||||
int tb_port_update_credits(struct tb_port *port);
|
||||
bool tb_port_is_clx_enabled(struct tb_port *port, enum tb_clx clx);
|
||||
bool tb_port_is_clx_enabled(struct tb_port *port, unsigned int clx);
|
||||
|
||||
int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec);
|
||||
int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap);
|
||||
|
@ -1212,6 +1245,7 @@ int usb4_port_sw_margin(struct tb_port *port, unsigned int lanes, bool timing,
|
|||
int usb4_port_sw_margin_errors(struct tb_port *port, u32 *errors);
|
||||
|
||||
int usb4_port_retimer_set_inbound_sbtx(struct tb_port *port, u8 index);
|
||||
int usb4_port_retimer_unset_inbound_sbtx(struct tb_port *port, u8 index);
|
||||
int usb4_port_retimer_read(struct tb_port *port, u8 index, u8 reg, void *buf,
|
||||
u8 size);
|
||||
int usb4_port_retimer_write(struct tb_port *port, u8 index, u8 reg,
|
||||
|
@ -1238,6 +1272,21 @@ int usb4_usb3_port_allocate_bandwidth(struct tb_port *port, int *upstream_bw,
|
|||
int usb4_usb3_port_release_bandwidth(struct tb_port *port, int *upstream_bw,
|
||||
int *downstream_bw);
|
||||
|
||||
int usb4_dp_port_set_cm_id(struct tb_port *port, int cm_id);
|
||||
bool usb4_dp_port_bw_mode_supported(struct tb_port *port);
|
||||
bool usb4_dp_port_bw_mode_enabled(struct tb_port *port);
|
||||
int usb4_dp_port_set_cm_bw_mode_supported(struct tb_port *port, bool supported);
|
||||
int usb4_dp_port_group_id(struct tb_port *port);
|
||||
int usb4_dp_port_set_group_id(struct tb_port *port, int group_id);
|
||||
int usb4_dp_port_nrd(struct tb_port *port, int *rate, int *lanes);
|
||||
int usb4_dp_port_set_nrd(struct tb_port *port, int rate, int lanes);
|
||||
int usb4_dp_port_granularity(struct tb_port *port);
|
||||
int usb4_dp_port_set_granularity(struct tb_port *port, int granularity);
|
||||
int usb4_dp_port_set_estimated_bw(struct tb_port *port, int bw);
|
||||
int usb4_dp_port_allocated_bw(struct tb_port *port);
|
||||
int usb4_dp_port_allocate_bw(struct tb_port *port, int bw);
|
||||
int usb4_dp_port_requested_bw(struct tb_port *port);
|
||||
|
||||
static inline bool tb_is_usb4_port_device(const struct device *dev)
|
||||
{
|
||||
return dev->type == &usb4_port_device_type;
|
||||
|
@ -1254,9 +1303,6 @@ struct usb4_port *usb4_port_device_add(struct tb_port *port);
|
|||
void usb4_port_device_remove(struct usb4_port *usb4);
|
||||
int usb4_port_device_resume(struct usb4_port *usb4);
|
||||
|
||||
/* Keep link controller awake during update */
|
||||
#define QUIRK_FORCE_POWER_LINK_CONTROLLER BIT(0)
|
||||
|
||||
void tb_check_quirks(struct tb_switch *sw);
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
|
|
|
@ -29,6 +29,7 @@ enum tb_cfg_error {
|
|||
TB_CFG_ERROR_HEC_ERROR_DETECTED = 12,
|
||||
TB_CFG_ERROR_FLOW_CONTROL_ERROR = 13,
|
||||
TB_CFG_ERROR_LOCK = 15,
|
||||
TB_CFG_ERROR_DP_BW = 32,
|
||||
};
|
||||
|
||||
/* common header */
|
||||
|
@ -64,14 +65,16 @@ struct cfg_write_pkg {
|
|||
/* TB_CFG_PKG_ERROR */
|
||||
struct cfg_error_pkg {
|
||||
struct tb_cfg_header header;
|
||||
enum tb_cfg_error error:4;
|
||||
u32 zero1:4;
|
||||
enum tb_cfg_error error:8;
|
||||
u32 port:6;
|
||||
u32 zero2:2; /* Both should be zero, still they are different fields. */
|
||||
u32 zero3:14;
|
||||
u32 reserved:16;
|
||||
u32 pg:2;
|
||||
} __packed;
|
||||
|
||||
struct cfg_ack_pkg {
|
||||
struct tb_cfg_header header;
|
||||
};
|
||||
|
||||
#define TB_CFG_ERROR_PG_HOT_PLUG 0x2
|
||||
#define TB_CFG_ERROR_PG_HOT_UNPLUG 0x3
|
||||
|
||||
|
|
|
@ -50,6 +50,10 @@ enum tb_port_state {
|
|||
TB_PORT_DISABLED = 0, /* tb_cap_phy.disable == 1 */
|
||||
TB_PORT_CONNECTING = 1, /* retry */
|
||||
TB_PORT_UP = 2,
|
||||
TB_PORT_TX_CL0S = 3,
|
||||
TB_PORT_RX_CL0S = 4,
|
||||
TB_PORT_CL1 = 5,
|
||||
TB_PORT_CL2 = 6,
|
||||
TB_PORT_UNPLUGGED = 7,
|
||||
};
|
||||
|
||||
|
@ -381,15 +385,42 @@ struct tb_regs_port_header {
|
|||
#define ADP_DP_CS_1_AUX_RX_HOPID_MASK GENMASK(21, 11)
|
||||
#define ADP_DP_CS_1_AUX_RX_HOPID_SHIFT 11
|
||||
#define ADP_DP_CS_2 0x02
|
||||
#define ADP_DP_CS_2_NRD_MLC_MASK GENMASK(2, 0)
|
||||
#define ADP_DP_CS_2_HDP BIT(6)
|
||||
#define ADP_DP_CS_2_NRD_MLR_MASK GENMASK(9, 7)
|
||||
#define ADP_DP_CS_2_NRD_MLR_SHIFT 7
|
||||
#define ADP_DP_CS_2_CA BIT(10)
|
||||
#define ADP_DP_CS_2_GR_MASK GENMASK(12, 11)
|
||||
#define ADP_DP_CS_2_GR_SHIFT 11
|
||||
#define ADP_DP_CS_2_GR_0_25G 0x0
|
||||
#define ADP_DP_CS_2_GR_0_5G 0x1
|
||||
#define ADP_DP_CS_2_GR_1G 0x2
|
||||
#define ADP_DP_CS_2_GROUP_ID_MASK GENMASK(15, 13)
|
||||
#define ADP_DP_CS_2_GROUP_ID_SHIFT 13
|
||||
#define ADP_DP_CS_2_CM_ID_MASK GENMASK(19, 16)
|
||||
#define ADP_DP_CS_2_CM_ID_SHIFT 16
|
||||
#define ADP_DP_CS_2_CMMS BIT(20)
|
||||
#define ADP_DP_CS_2_ESTIMATED_BW_MASK GENMASK(31, 24)
|
||||
#define ADP_DP_CS_2_ESTIMATED_BW_SHIFT 24
|
||||
#define ADP_DP_CS_3 0x03
|
||||
#define ADP_DP_CS_3_HDPC BIT(9)
|
||||
#define DP_LOCAL_CAP 0x04
|
||||
#define DP_REMOTE_CAP 0x05
|
||||
/* For DP IN adapter */
|
||||
#define DP_STATUS 0x06
|
||||
#define DP_STATUS_ALLOCATED_BW_MASK GENMASK(31, 24)
|
||||
#define DP_STATUS_ALLOCATED_BW_SHIFT 24
|
||||
/* For DP OUT adapter */
|
||||
#define DP_STATUS_CTRL 0x06
|
||||
#define DP_STATUS_CTRL_CMHS BIT(25)
|
||||
#define DP_STATUS_CTRL_UF BIT(26)
|
||||
#define DP_COMMON_CAP 0x07
|
||||
/* Only if DP IN supports BW allocation mode */
|
||||
#define ADP_DP_CS_8 0x08
|
||||
#define ADP_DP_CS_8_REQUESTED_BW_MASK GENMASK(7, 0)
|
||||
#define ADP_DP_CS_8_DPME BIT(30)
|
||||
#define ADP_DP_CS_8_DR BIT(31)
|
||||
|
||||
/*
|
||||
* DP_COMMON_CAP offsets work also for DP_LOCAL_CAP and DP_REMOTE_CAP
|
||||
* with exception of DPRX done.
|
||||
|
@ -406,7 +437,12 @@ struct tb_regs_port_header {
|
|||
#define DP_COMMON_CAP_2_LANES 0x1
|
||||
#define DP_COMMON_CAP_4_LANES 0x2
|
||||
#define DP_COMMON_CAP_LTTPR_NS BIT(27)
|
||||
#define DP_COMMON_CAP_BW_MODE BIT(28)
|
||||
#define DP_COMMON_CAP_DPRX_DONE BIT(31)
|
||||
/* Only present if DP IN supports BW allocation mode */
|
||||
#define ADP_DP_CS_8 0x08
|
||||
#define ADP_DP_CS_8_DPME BIT(30)
|
||||
#define ADP_DP_CS_8_DR BIT(31)
|
||||
|
||||
/* PCIe adapter registers */
|
||||
#define ADP_PCIE_CS_0 0x00
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <linux/delay.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/ktime.h>
|
||||
|
||||
#include "tunnel.h"
|
||||
#include "tb.h"
|
||||
|
@ -44,12 +45,17 @@
|
|||
/* Minimum number of credits for DMA path */
|
||||
#define TB_MIN_DMA_CREDITS 1U
|
||||
|
||||
static bool bw_alloc_mode = true;
|
||||
module_param(bw_alloc_mode, bool, 0444);
|
||||
MODULE_PARM_DESC(bw_alloc_mode,
|
||||
"enable bandwidth allocation mode if supported (default: true)");
|
||||
|
||||
static const char * const tb_tunnel_names[] = { "PCI", "DP", "DMA", "USB3" };
|
||||
|
||||
#define __TB_TUNNEL_PRINT(level, tunnel, fmt, arg...) \
|
||||
do { \
|
||||
struct tb_tunnel *__tunnel = (tunnel); \
|
||||
level(__tunnel->tb, "%llx:%x <-> %llx:%x (%s): " fmt, \
|
||||
level(__tunnel->tb, "%llx:%u <-> %llx:%u (%s): " fmt, \
|
||||
tb_route(__tunnel->src_port->sw), \
|
||||
__tunnel->src_port->port, \
|
||||
tb_route(__tunnel->dst_port->sw), \
|
||||
|
@ -598,6 +604,133 @@ static int tb_dp_xchg_caps(struct tb_tunnel *tunnel)
|
|||
in->cap_adap + DP_REMOTE_CAP, 1);
|
||||
}
|
||||
|
||||
static int tb_dp_bw_alloc_mode_enable(struct tb_tunnel *tunnel)
|
||||
{
|
||||
int ret, estimated_bw, granularity, tmp;
|
||||
struct tb_port *out = tunnel->dst_port;
|
||||
struct tb_port *in = tunnel->src_port;
|
||||
u32 out_dp_cap, out_rate, out_lanes;
|
||||
u32 in_dp_cap, in_rate, in_lanes;
|
||||
u32 rate, lanes;
|
||||
|
||||
if (!bw_alloc_mode)
|
||||
return 0;
|
||||
|
||||
ret = usb4_dp_port_set_cm_bw_mode_supported(in, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = usb4_dp_port_set_group_id(in, in->group->index);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Get the non-reduced rate and lanes based on the lowest
|
||||
* capability of both adapters.
|
||||
*/
|
||||
ret = tb_port_read(in, &in_dp_cap, TB_CFG_PORT,
|
||||
in->cap_adap + DP_LOCAL_CAP, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = tb_port_read(out, &out_dp_cap, TB_CFG_PORT,
|
||||
out->cap_adap + DP_LOCAL_CAP, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
in_rate = tb_dp_cap_get_rate(in_dp_cap);
|
||||
in_lanes = tb_dp_cap_get_lanes(in_dp_cap);
|
||||
out_rate = tb_dp_cap_get_rate(out_dp_cap);
|
||||
out_lanes = tb_dp_cap_get_lanes(out_dp_cap);
|
||||
|
||||
rate = min(in_rate, out_rate);
|
||||
lanes = min(in_lanes, out_lanes);
|
||||
tmp = tb_dp_bandwidth(rate, lanes);
|
||||
|
||||
tb_port_dbg(in, "non-reduced bandwidth %u Mb/s x%u = %u Mb/s\n", rate,
|
||||
lanes, tmp);
|
||||
|
||||
ret = usb4_dp_port_set_nrd(in, rate, lanes);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (granularity = 250; tmp / granularity > 255 && granularity <= 1000;
|
||||
granularity *= 2)
|
||||
;
|
||||
|
||||
tb_port_dbg(in, "granularity %d Mb/s\n", granularity);
|
||||
|
||||
/*
|
||||
* Returns -EINVAL if granularity above is outside of the
|
||||
* accepted ranges.
|
||||
*/
|
||||
ret = usb4_dp_port_set_granularity(in, granularity);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Bandwidth estimation is pretty much what we have in
|
||||
* max_up/down fields. For discovery we just read what the
|
||||
* estimation was set to.
|
||||
*/
|
||||
if (in->sw->config.depth < out->sw->config.depth)
|
||||
estimated_bw = tunnel->max_down;
|
||||
else
|
||||
estimated_bw = tunnel->max_up;
|
||||
|
||||
tb_port_dbg(in, "estimated bandwidth %d Mb/s\n", estimated_bw);
|
||||
|
||||
ret = usb4_dp_port_set_estimated_bw(in, estimated_bw);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Initial allocation should be 0 according the spec */
|
||||
ret = usb4_dp_port_allocate_bw(in, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
tb_port_dbg(in, "bandwidth allocation mode enabled\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tb_dp_init(struct tb_tunnel *tunnel)
|
||||
{
|
||||
struct tb_port *in = tunnel->src_port;
|
||||
struct tb_switch *sw = in->sw;
|
||||
struct tb *tb = in->sw->tb;
|
||||
int ret;
|
||||
|
||||
ret = tb_dp_xchg_caps(tunnel);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!tb_switch_is_usb4(sw))
|
||||
return 0;
|
||||
|
||||
if (!usb4_dp_port_bw_mode_supported(in))
|
||||
return 0;
|
||||
|
||||
tb_port_dbg(in, "bandwidth allocation mode supported\n");
|
||||
|
||||
ret = usb4_dp_port_set_cm_id(in, tb->index);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return tb_dp_bw_alloc_mode_enable(tunnel);
|
||||
}
|
||||
|
||||
static void tb_dp_deinit(struct tb_tunnel *tunnel)
|
||||
{
|
||||
struct tb_port *in = tunnel->src_port;
|
||||
|
||||
if (!usb4_dp_port_bw_mode_supported(in))
|
||||
return;
|
||||
if (usb4_dp_port_bw_mode_enabled(in)) {
|
||||
usb4_dp_port_set_cm_bw_mode_supported(in, false);
|
||||
tb_port_dbg(in, "bandwidth allocation mode disabled\n");
|
||||
}
|
||||
}
|
||||
|
||||
static int tb_dp_activate(struct tb_tunnel *tunnel, bool active)
|
||||
{
|
||||
int ret;
|
||||
|
@ -635,49 +768,275 @@ static int tb_dp_activate(struct tb_tunnel *tunnel, bool active)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* max_bw is rounded up to next granularity */
|
||||
static int tb_dp_nrd_bandwidth(struct tb_tunnel *tunnel, int *max_bw)
|
||||
{
|
||||
struct tb_port *in = tunnel->src_port;
|
||||
int ret, rate, lanes, nrd_bw;
|
||||
|
||||
ret = usb4_dp_port_nrd(in, &rate, &lanes);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
nrd_bw = tb_dp_bandwidth(rate, lanes);
|
||||
|
||||
if (max_bw) {
|
||||
ret = usb4_dp_port_granularity(in);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*max_bw = roundup(nrd_bw, ret);
|
||||
}
|
||||
|
||||
return nrd_bw;
|
||||
}
|
||||
|
||||
static int tb_dp_bw_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;
|
||||
|
||||
if (!usb4_dp_port_bw_mode_enabled(in))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (!tunnel->bw_mode)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
/* Read what was allocated previously if any */
|
||||
ret = usb4_dp_port_allocated_bw(in);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
allocated_bw = ret;
|
||||
|
||||
ret = tb_dp_nrd_bandwidth(tunnel, &max_bw);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (allocated_bw == max_bw)
|
||||
allocated_bw = ret;
|
||||
|
||||
tb_port_dbg(in, "consumed bandwidth through allocation mode %d Mb/s\n",
|
||||
allocated_bw);
|
||||
|
||||
if (in->sw->config.depth < out->sw->config.depth) {
|
||||
*consumed_up = 0;
|
||||
*consumed_down = allocated_bw;
|
||||
} else {
|
||||
*consumed_up = allocated_bw;
|
||||
*consumed_down = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/*
|
||||
* If we have already set the allocated bandwidth then use that.
|
||||
* Otherwise we read it from the DPRX.
|
||||
*/
|
||||
if (usb4_dp_port_bw_mode_enabled(in) && tunnel->bw_mode) {
|
||||
int ret, allocated_bw, max_bw;
|
||||
|
||||
ret = usb4_dp_port_allocated_bw(in);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
allocated_bw = ret;
|
||||
|
||||
ret = tb_dp_nrd_bandwidth(tunnel, &max_bw);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (allocated_bw == max_bw)
|
||||
allocated_bw = ret;
|
||||
|
||||
if (in->sw->config.depth < out->sw->config.depth) {
|
||||
*allocated_up = 0;
|
||||
*allocated_down = allocated_bw;
|
||||
} else {
|
||||
*allocated_up = allocated_bw;
|
||||
*allocated_down = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return tunnel->consumed_bandwidth(tunnel, allocated_up,
|
||||
allocated_down);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (!usb4_dp_port_bw_mode_enabled(in))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ret = tb_dp_nrd_bandwidth(tunnel, &max_bw);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (in->sw->config.depth < out->sw->config.depth) {
|
||||
tmp = min(*alloc_down, max_bw);
|
||||
ret = usb4_dp_port_allocate_bw(in, tmp);
|
||||
if (ret)
|
||||
return ret;
|
||||
*alloc_down = tmp;
|
||||
*alloc_up = 0;
|
||||
} else {
|
||||
tmp = min(*alloc_up, max_bw);
|
||||
ret = usb4_dp_port_allocate_bw(in, tmp);
|
||||
if (ret)
|
||||
return ret;
|
||||
*alloc_down = 0;
|
||||
*alloc_up = tmp;
|
||||
}
|
||||
|
||||
/* Now we can use BW mode registers to figure out the bandwidth */
|
||||
/* TODO: need to handle discovery too */
|
||||
tunnel->bw_mode = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tb_dp_read_dprx(struct tb_tunnel *tunnel, u32 *rate, u32 *lanes,
|
||||
int timeout_msec)
|
||||
{
|
||||
ktime_t timeout = ktime_add_ms(ktime_get(), timeout_msec);
|
||||
struct tb_port *in = tunnel->src_port;
|
||||
|
||||
/*
|
||||
* Wait for DPRX done. Normally it should be already set for
|
||||
* active tunnel.
|
||||
*/
|
||||
do {
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
ret = tb_port_read(in, &val, TB_CFG_PORT,
|
||||
in->cap_adap + DP_COMMON_CAP, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (val & DP_COMMON_CAP_DPRX_DONE) {
|
||||
*rate = tb_dp_cap_get_rate(val);
|
||||
*lanes = tb_dp_cap_get_lanes(val);
|
||||
|
||||
tb_port_dbg(in, "consumed bandwidth through DPRX %d Mb/s\n",
|
||||
tb_dp_bandwidth(*rate, *lanes));
|
||||
return 0;
|
||||
}
|
||||
usleep_range(100, 150);
|
||||
} while (ktime_before(ktime_get(), timeout));
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
/* Read cap from tunnel DP IN */
|
||||
static int tb_dp_read_cap(struct tb_tunnel *tunnel, unsigned int cap, u32 *rate,
|
||||
u32 *lanes)
|
||||
{
|
||||
struct tb_port *in = tunnel->src_port;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
switch (cap) {
|
||||
case DP_LOCAL_CAP:
|
||||
case DP_REMOTE_CAP:
|
||||
break;
|
||||
|
||||
default:
|
||||
tb_tunnel_WARN(tunnel, "invalid capability index %#x\n", cap);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read from the copied remote cap so that we take into account
|
||||
* if capabilities were reduced during exchange.
|
||||
*/
|
||||
ret = tb_port_read(in, &val, TB_CFG_PORT, in->cap_adap + cap, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*rate = tb_dp_cap_get_rate(val);
|
||||
*lanes = tb_dp_cap_get_lanes(val);
|
||||
|
||||
tb_port_dbg(in, "bandwidth from %#x capability %d Mb/s\n", cap,
|
||||
tb_dp_bandwidth(*rate, *lanes));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tb_dp_maximum_bandwidth(struct tb_tunnel *tunnel, int *max_up,
|
||||
int *max_down)
|
||||
{
|
||||
struct tb_port *in = tunnel->src_port;
|
||||
u32 rate, lanes;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* DP IN adapter DP_LOCAL_CAP gets updated to the lowest AUX read
|
||||
* parameter values so this so we can use this to determine the
|
||||
* maximum possible bandwidth over this link.
|
||||
*/
|
||||
ret = tb_dp_read_cap(tunnel, DP_LOCAL_CAP, &rate, &lanes);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (in->sw->config.depth < tunnel->dst_port->sw->config.depth) {
|
||||
*max_up = 0;
|
||||
*max_down = tb_dp_bandwidth(rate, lanes);
|
||||
} else {
|
||||
*max_up = tb_dp_bandwidth(rate, lanes);
|
||||
*max_down = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
u32 val, rate = 0, lanes = 0;
|
||||
u32 rate = 0, lanes = 0;
|
||||
int ret;
|
||||
|
||||
if (tb_dp_is_usb4(sw)) {
|
||||
int timeout = 20;
|
||||
|
||||
/*
|
||||
* Wait for DPRX done. Normally it should be already set
|
||||
* for active tunnel.
|
||||
* On USB4 routers check if the bandwidth allocation
|
||||
* mode is enabled first and then read the bandwidth
|
||||
* through those registers.
|
||||
*/
|
||||
do {
|
||||
ret = tb_port_read(in, &val, TB_CFG_PORT,
|
||||
in->cap_adap + DP_COMMON_CAP, 1);
|
||||
ret = tb_dp_bw_mode_consumed_bandwidth(tunnel, consumed_up,
|
||||
consumed_down);
|
||||
if (ret < 0) {
|
||||
if (ret != -EOPNOTSUPP)
|
||||
return ret;
|
||||
} else if (!ret) {
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
ret = tb_dp_read_dprx(tunnel, &rate, &lanes, 150);
|
||||
if (ret) {
|
||||
if (ret == -ETIMEDOUT)
|
||||
ret = tb_dp_read_cap(tunnel, DP_REMOTE_CAP,
|
||||
&rate, &lanes);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (val & DP_COMMON_CAP_DPRX_DONE) {
|
||||
rate = tb_dp_cap_get_rate(val);
|
||||
lanes = tb_dp_cap_get_lanes(val);
|
||||
break;
|
||||
}
|
||||
msleep(250);
|
||||
} while (timeout--);
|
||||
|
||||
if (!timeout)
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
} else if (sw->generation >= 2) {
|
||||
/*
|
||||
* Read from the copied remote cap so that we take into
|
||||
* account if capabilities were reduced during exchange.
|
||||
*/
|
||||
ret = tb_port_read(in, &val, TB_CFG_PORT,
|
||||
in->cap_adap + DP_REMOTE_CAP, 1);
|
||||
ret = tb_dp_read_cap(tunnel, DP_REMOTE_CAP, &rate, &lanes);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
rate = tb_dp_cap_get_rate(val);
|
||||
lanes = tb_dp_cap_get_lanes(val);
|
||||
} else {
|
||||
/* No bandwidth management for legacy devices */
|
||||
*consumed_up = 0;
|
||||
|
@ -799,8 +1158,12 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in,
|
|||
if (!tunnel)
|
||||
return NULL;
|
||||
|
||||
tunnel->init = tb_dp_xchg_caps;
|
||||
tunnel->init = tb_dp_init;
|
||||
tunnel->deinit = tb_dp_deinit;
|
||||
tunnel->activate = tb_dp_activate;
|
||||
tunnel->maximum_bandwidth = tb_dp_maximum_bandwidth;
|
||||
tunnel->allocated_bandwidth = tb_dp_allocated_bandwidth;
|
||||
tunnel->alloc_bandwidth = tb_dp_alloc_bandwidth;
|
||||
tunnel->consumed_bandwidth = tb_dp_consumed_bandwidth;
|
||||
tunnel->src_port = in;
|
||||
|
||||
|
@ -888,8 +1251,12 @@ struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in,
|
|||
if (!tunnel)
|
||||
return NULL;
|
||||
|
||||
tunnel->init = tb_dp_xchg_caps;
|
||||
tunnel->init = tb_dp_init;
|
||||
tunnel->deinit = tb_dp_deinit;
|
||||
tunnel->activate = tb_dp_activate;
|
||||
tunnel->maximum_bandwidth = tb_dp_maximum_bandwidth;
|
||||
tunnel->allocated_bandwidth = tb_dp_allocated_bandwidth;
|
||||
tunnel->alloc_bandwidth = tb_dp_alloc_bandwidth;
|
||||
tunnel->consumed_bandwidth = tb_dp_consumed_bandwidth;
|
||||
tunnel->src_port = in;
|
||||
tunnel->dst_port = out;
|
||||
|
@ -1714,6 +2081,72 @@ static bool tb_tunnel_is_active(const struct tb_tunnel *tunnel)
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* tb_tunnel_maximum_bandwidth() - Return maximum possible bandwidth
|
||||
* @tunnel: Tunnel to check
|
||||
* @max_up: Maximum upstream bandwidth in Mb/s
|
||||
* @max_down: Maximum downstream bandwidth in Mb/s
|
||||
*
|
||||
* Returns maximum possible bandwidth this tunnel can go if not limited
|
||||
* by other bandwidth clients. If the tunnel does not support this
|
||||
* returns %-EOPNOTSUPP.
|
||||
*/
|
||||
int tb_tunnel_maximum_bandwidth(struct tb_tunnel *tunnel, int *max_up,
|
||||
int *max_down)
|
||||
{
|
||||
if (!tb_tunnel_is_active(tunnel))
|
||||
return -EINVAL;
|
||||
|
||||
if (tunnel->maximum_bandwidth)
|
||||
return tunnel->maximum_bandwidth(tunnel, max_up, max_down);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
/**
|
||||
* tb_tunnel_allocated_bandwidth() - Return bandwidth allocated for the tunnel
|
||||
* @tunnel: Tunnel to check
|
||||
* @allocated_up: Currently allocated upstream bandwidth in Mb/s is stored here
|
||||
* @allocated_down: Currently allocated downstream bandwidth in Mb/s is
|
||||
* stored here
|
||||
*
|
||||
* Returns the bandwidth allocated for the tunnel. This may be higher
|
||||
* than what the tunnel actually consumes.
|
||||
*/
|
||||
int tb_tunnel_allocated_bandwidth(struct tb_tunnel *tunnel, int *allocated_up,
|
||||
int *allocated_down)
|
||||
{
|
||||
if (!tb_tunnel_is_active(tunnel))
|
||||
return -EINVAL;
|
||||
|
||||
if (tunnel->allocated_bandwidth)
|
||||
return tunnel->allocated_bandwidth(tunnel, allocated_up,
|
||||
allocated_down);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
/**
|
||||
* tb_tunnel_alloc_bandwidth() - Change tunnel bandwidth allocation
|
||||
* @tunnel: Tunnel whose bandwidth allocation to change
|
||||
* @alloc_up: New upstream bandwidth in Mb/s
|
||||
* @alloc_down: New downstream bandwidth in Mb/s
|
||||
*
|
||||
* Tries to change tunnel bandwidth allocation. If succeeds returns %0
|
||||
* and updates @alloc_up and @alloc_down to that was actually allocated
|
||||
* (it may not be the same as passed originally). Returns negative errno
|
||||
* in case of failure.
|
||||
*/
|
||||
int tb_tunnel_alloc_bandwidth(struct tb_tunnel *tunnel, int *alloc_up,
|
||||
int *alloc_down)
|
||||
{
|
||||
if (!tb_tunnel_is_active(tunnel))
|
||||
return -EINVAL;
|
||||
|
||||
if (tunnel->alloc_bandwidth)
|
||||
return tunnel->alloc_bandwidth(tunnel, alloc_up, alloc_down);
|
||||
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
/**
|
||||
* tb_tunnel_consumed_bandwidth() - Return bandwidth consumed by the tunnel
|
||||
* @tunnel: Tunnel to check
|
||||
|
|
|
@ -29,6 +29,9 @@ enum tb_tunnel_type {
|
|||
* @init: Optional tunnel specific initialization
|
||||
* @deinit: Optional tunnel specific de-initialization
|
||||
* @activate: Optional tunnel specific activation/deactivation
|
||||
* @maximum_bandwidth: Returns maximum possible bandwidth for this tunnel
|
||||
* @allocated_bandwidth: Return how much bandwidth is allocated for the tunnel
|
||||
* @alloc_bandwidth: Change tunnel bandwidth allocation
|
||||
* @consumed_bandwidth: Return how much bandwidth the tunnel consumes
|
||||
* @release_unused_bandwidth: Release all unused bandwidth
|
||||
* @reclaim_available_bandwidth: Reclaim back available bandwidth
|
||||
|
@ -40,6 +43,8 @@ enum tb_tunnel_type {
|
|||
* Only set if the bandwidth needs to be limited.
|
||||
* @allocated_up: Allocated upstream bandwidth (only for USB3)
|
||||
* @allocated_down: Allocated downstream bandwidth (only for USB3)
|
||||
* @bw_mode: DP bandwidth allocation mode registers can be used to
|
||||
* determine consumed and allocated bandwidth
|
||||
*/
|
||||
struct tb_tunnel {
|
||||
struct tb *tb;
|
||||
|
@ -50,6 +55,12 @@ struct tb_tunnel {
|
|||
int (*init)(struct tb_tunnel *tunnel);
|
||||
void (*deinit)(struct tb_tunnel *tunnel);
|
||||
int (*activate)(struct tb_tunnel *tunnel, bool activate);
|
||||
int (*maximum_bandwidth)(struct tb_tunnel *tunnel, int *max_up,
|
||||
int *max_down);
|
||||
int (*allocated_bandwidth)(struct tb_tunnel *tunnel, int *allocated_up,
|
||||
int *allocated_down);
|
||||
int (*alloc_bandwidth)(struct tb_tunnel *tunnel, int *alloc_up,
|
||||
int *alloc_down);
|
||||
int (*consumed_bandwidth)(struct tb_tunnel *tunnel, int *consumed_up,
|
||||
int *consumed_down);
|
||||
int (*release_unused_bandwidth)(struct tb_tunnel *tunnel);
|
||||
|
@ -62,6 +73,7 @@ struct tb_tunnel {
|
|||
int max_down;
|
||||
int allocated_up;
|
||||
int allocated_down;
|
||||
bool bw_mode;
|
||||
};
|
||||
|
||||
struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down,
|
||||
|
@ -92,6 +104,12 @@ void tb_tunnel_deactivate(struct tb_tunnel *tunnel);
|
|||
bool tb_tunnel_is_invalid(struct tb_tunnel *tunnel);
|
||||
bool tb_tunnel_port_on_path(const struct tb_tunnel *tunnel,
|
||||
const struct tb_port *port);
|
||||
int tb_tunnel_maximum_bandwidth(struct tb_tunnel *tunnel, int *max_up,
|
||||
int *max_down);
|
||||
int tb_tunnel_allocated_bandwidth(struct tb_tunnel *tunnel, int *allocated_up,
|
||||
int *allocated_down);
|
||||
int tb_tunnel_alloc_bandwidth(struct tb_tunnel *tunnel, int *alloc_up,
|
||||
int *alloc_down);
|
||||
int tb_tunnel_consumed_bandwidth(struct tb_tunnel *tunnel, int *consumed_up,
|
||||
int *consumed_down);
|
||||
int tb_tunnel_release_unused_bandwidth(struct tb_tunnel *tunnel);
|
||||
|
|
|
@ -1578,6 +1578,20 @@ int usb4_port_retimer_set_inbound_sbtx(struct tb_port *port, u8 index)
|
|||
500);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_port_retimer_unset_inbound_sbtx() - Disable sideband channel transactions
|
||||
* @port: USB4 port
|
||||
* @index: Retimer index
|
||||
*
|
||||
* Disables sideband channel transations on SBTX. The reverse of
|
||||
* usb4_port_retimer_set_inbound_sbtx().
|
||||
*/
|
||||
int usb4_port_retimer_unset_inbound_sbtx(struct tb_port *port, u8 index)
|
||||
{
|
||||
return usb4_port_retimer_op(port, index,
|
||||
USB4_SB_OPCODE_UNSET_INBOUND_SBTX, 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_port_retimer_read() - Read from retimer sideband registers
|
||||
* @port: USB4 port
|
||||
|
@ -1868,6 +1882,15 @@ int usb4_port_retimer_nvm_read(struct tb_port *port, u8 index,
|
|||
usb4_port_retimer_nvm_read_block, &info);
|
||||
}
|
||||
|
||||
static inline unsigned int
|
||||
usb4_usb3_port_max_bandwidth(const struct tb_port *port, unsigned int bw)
|
||||
{
|
||||
/* Take the possible bandwidth limitation into account */
|
||||
if (port->max_bw)
|
||||
return min(bw, port->max_bw);
|
||||
return bw;
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_usb3_port_max_link_rate() - Maximum support USB3 link rate
|
||||
* @port: USB3 adapter port
|
||||
|
@ -1889,7 +1912,9 @@ int usb4_usb3_port_max_link_rate(struct tb_port *port)
|
|||
return ret;
|
||||
|
||||
lr = (val & ADP_USB3_CS_4_MSLR_MASK) >> ADP_USB3_CS_4_MSLR_SHIFT;
|
||||
return lr == ADP_USB3_CS_4_MSLR_20G ? 20000 : 10000;
|
||||
ret = lr == ADP_USB3_CS_4_MSLR_20G ? 20000 : 10000;
|
||||
|
||||
return usb4_usb3_port_max_bandwidth(port, ret);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1916,7 +1941,9 @@ int usb4_usb3_port_actual_link_rate(struct tb_port *port)
|
|||
return 0;
|
||||
|
||||
lr = val & ADP_USB3_CS_4_ALR_MASK;
|
||||
return lr == ADP_USB3_CS_4_ALR_20G ? 20000 : 10000;
|
||||
ret = lr == ADP_USB3_CS_4_ALR_20G ? 20000 : 10000;
|
||||
|
||||
return usb4_usb3_port_max_bandwidth(port, ret);
|
||||
}
|
||||
|
||||
static int usb4_usb3_port_cm_request(struct tb_port *port, bool request)
|
||||
|
@ -2067,18 +2094,30 @@ static int usb4_usb3_port_write_allocated_bandwidth(struct tb_port *port,
|
|||
int downstream_bw)
|
||||
{
|
||||
u32 val, ubw, dbw, scale;
|
||||
int ret;
|
||||
int ret, max_bw;
|
||||
|
||||
/* Read the used scale, hardware default is 0 */
|
||||
ret = tb_port_read(port, &scale, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_USB3_CS_3, 1);
|
||||
/* Figure out suitable scale */
|
||||
scale = 0;
|
||||
max_bw = max(upstream_bw, downstream_bw);
|
||||
while (scale < 64) {
|
||||
if (mbps_to_usb3_bw(max_bw, scale) < 4096)
|
||||
break;
|
||||
scale++;
|
||||
}
|
||||
|
||||
if (WARN_ON(scale >= 64))
|
||||
return -EINVAL;
|
||||
|
||||
ret = tb_port_write(port, &scale, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_USB3_CS_3, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
scale &= ADP_USB3_CS_3_SCALE_MASK;
|
||||
ubw = mbps_to_usb3_bw(upstream_bw, scale);
|
||||
dbw = mbps_to_usb3_bw(downstream_bw, scale);
|
||||
|
||||
tb_port_dbg(port, "scaled bandwidth %u/%u, scale %u\n", ubw, dbw, scale);
|
||||
|
||||
ret = tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_USB3_CS_2, 1);
|
||||
if (ret)
|
||||
|
@ -2186,3 +2225,575 @@ err_request:
|
|||
usb4_usb3_port_clear_cm_request(port);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool is_usb4_dpin(const struct tb_port *port)
|
||||
{
|
||||
if (!tb_port_is_dpin(port))
|
||||
return false;
|
||||
if (!tb_switch_is_usb4(port->sw))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_dp_port_set_cm_id() - Assign CM ID to the DP IN adapter
|
||||
* @port: DP IN adapter
|
||||
* @cm_id: CM ID to assign
|
||||
*
|
||||
* Sets CM ID for the @port. Returns %0 on success and negative errno
|
||||
* otherwise. Speficially returns %-EOPNOTSUPP if the @port does not
|
||||
* support this.
|
||||
*/
|
||||
int usb4_dp_port_set_cm_id(struct tb_port *port, int cm_id)
|
||||
{
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (!is_usb4_dpin(port))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ret = tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_DP_CS_2, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val &= ~ADP_DP_CS_2_CM_ID_MASK;
|
||||
val |= cm_id << ADP_DP_CS_2_CM_ID_SHIFT;
|
||||
|
||||
return tb_port_write(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_DP_CS_2, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_dp_port_bw_mode_supported() - Is the bandwidth allocation mode supported
|
||||
* @port: DP IN adapter to check
|
||||
*
|
||||
* Can be called to any DP IN adapter. Returns true if the adapter
|
||||
* supports USB4 bandwidth allocation mode, false otherwise.
|
||||
*/
|
||||
bool usb4_dp_port_bw_mode_supported(struct tb_port *port)
|
||||
{
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
if (!is_usb4_dpin(port))
|
||||
return false;
|
||||
|
||||
ret = tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + DP_LOCAL_CAP, 1);
|
||||
if (ret)
|
||||
return false;
|
||||
|
||||
return !!(val & DP_COMMON_CAP_BW_MODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_dp_port_bw_mode_enabled() - Is the bandwidth allocation mode enabled
|
||||
* @port: DP IN adapter to check
|
||||
*
|
||||
* Can be called to any DP IN adapter. Returns true if the bandwidth
|
||||
* allocation mode has been enabled, false otherwise.
|
||||
*/
|
||||
bool usb4_dp_port_bw_mode_enabled(struct tb_port *port)
|
||||
{
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
if (!is_usb4_dpin(port))
|
||||
return false;
|
||||
|
||||
ret = tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_DP_CS_8, 1);
|
||||
if (ret)
|
||||
return false;
|
||||
|
||||
return !!(val & ADP_DP_CS_8_DPME);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_dp_port_set_cm_bw_mode_supported() - Set/clear CM support for bandwidth allocation mode
|
||||
* @port: DP IN adapter
|
||||
* @supported: Does the CM support bandwidth allocation mode
|
||||
*
|
||||
* Can be called to any DP IN adapter. Sets or clears the CM support bit
|
||||
* of the DP IN adapter. Returns %0 in success and negative errno
|
||||
* otherwise. Specifically returns %-OPNOTSUPP if the passed in adapter
|
||||
* does not support this.
|
||||
*/
|
||||
int usb4_dp_port_set_cm_bw_mode_supported(struct tb_port *port, bool supported)
|
||||
{
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (!is_usb4_dpin(port))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ret = tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_DP_CS_2, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (supported)
|
||||
val |= ADP_DP_CS_2_CMMS;
|
||||
else
|
||||
val &= ~ADP_DP_CS_2_CMMS;
|
||||
|
||||
return tb_port_write(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_DP_CS_2, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_dp_port_group_id() - Return Group ID assigned for the adapter
|
||||
* @port: DP IN adapter
|
||||
*
|
||||
* Reads bandwidth allocation Group ID from the DP IN adapter and
|
||||
* returns it. If the adapter does not support setting Group_ID
|
||||
* %-EOPNOTSUPP is returned.
|
||||
*/
|
||||
int usb4_dp_port_group_id(struct tb_port *port)
|
||||
{
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (!is_usb4_dpin(port))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ret = tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_DP_CS_2, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return (val & ADP_DP_CS_2_GROUP_ID_MASK) >> ADP_DP_CS_2_GROUP_ID_SHIFT;
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_dp_port_set_group_id() - Set adapter Group ID
|
||||
* @port: DP IN adapter
|
||||
* @group_id: Group ID for the adapter
|
||||
*
|
||||
* Sets bandwidth allocation mode Group ID for the DP IN adapter.
|
||||
* Returns %0 in case of success and negative errno otherwise.
|
||||
* Specifically returns %-EOPNOTSUPP if the adapter does not support
|
||||
* this.
|
||||
*/
|
||||
int usb4_dp_port_set_group_id(struct tb_port *port, int group_id)
|
||||
{
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (!is_usb4_dpin(port))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ret = tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_DP_CS_2, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val &= ~ADP_DP_CS_2_GROUP_ID_MASK;
|
||||
val |= group_id << ADP_DP_CS_2_GROUP_ID_SHIFT;
|
||||
|
||||
return tb_port_write(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_DP_CS_2, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_dp_port_nrd() - Read non-reduced rate and lanes
|
||||
* @port: DP IN adapter
|
||||
* @rate: Non-reduced rate in Mb/s is placed here
|
||||
* @lanes: Non-reduced lanes are placed here
|
||||
*
|
||||
* Reads the non-reduced rate and lanes from the DP IN adapter. Returns
|
||||
* %0 in success and negative errno otherwise. Specifically returns
|
||||
* %-EOPNOTSUPP if the adapter does not support this.
|
||||
*/
|
||||
int usb4_dp_port_nrd(struct tb_port *port, int *rate, int *lanes)
|
||||
{
|
||||
u32 val, tmp;
|
||||
int ret;
|
||||
|
||||
if (!is_usb4_dpin(port))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ret = tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_DP_CS_2, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
tmp = (val & ADP_DP_CS_2_NRD_MLR_MASK) >> ADP_DP_CS_2_NRD_MLR_SHIFT;
|
||||
switch (tmp) {
|
||||
case DP_COMMON_CAP_RATE_RBR:
|
||||
*rate = 1620;
|
||||
break;
|
||||
case DP_COMMON_CAP_RATE_HBR:
|
||||
*rate = 2700;
|
||||
break;
|
||||
case DP_COMMON_CAP_RATE_HBR2:
|
||||
*rate = 5400;
|
||||
break;
|
||||
case DP_COMMON_CAP_RATE_HBR3:
|
||||
*rate = 8100;
|
||||
break;
|
||||
}
|
||||
|
||||
tmp = val & ADP_DP_CS_2_NRD_MLC_MASK;
|
||||
switch (tmp) {
|
||||
case DP_COMMON_CAP_1_LANE:
|
||||
*lanes = 1;
|
||||
break;
|
||||
case DP_COMMON_CAP_2_LANES:
|
||||
*lanes = 2;
|
||||
break;
|
||||
case DP_COMMON_CAP_4_LANES:
|
||||
*lanes = 4;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_dp_port_set_nrd() - Set non-reduced rate and lanes
|
||||
* @port: DP IN adapter
|
||||
* @rate: Non-reduced rate in Mb/s
|
||||
* @lanes: Non-reduced lanes
|
||||
*
|
||||
* Before the capabilities reduction this function can be used to set
|
||||
* the non-reduced values for the DP IN adapter. Returns %0 in success
|
||||
* and negative errno otherwise. If the adapter does not support this
|
||||
* %-EOPNOTSUPP is returned.
|
||||
*/
|
||||
int usb4_dp_port_set_nrd(struct tb_port *port, int rate, int lanes)
|
||||
{
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (!is_usb4_dpin(port))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ret = tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_DP_CS_2, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val &= ~ADP_DP_CS_2_NRD_MLR_MASK;
|
||||
|
||||
switch (rate) {
|
||||
case 1620:
|
||||
break;
|
||||
case 2700:
|
||||
val |= (DP_COMMON_CAP_RATE_HBR << ADP_DP_CS_2_NRD_MLR_SHIFT)
|
||||
& ADP_DP_CS_2_NRD_MLR_MASK;
|
||||
break;
|
||||
case 5400:
|
||||
val |= (DP_COMMON_CAP_RATE_HBR2 << ADP_DP_CS_2_NRD_MLR_SHIFT)
|
||||
& ADP_DP_CS_2_NRD_MLR_MASK;
|
||||
break;
|
||||
case 8100:
|
||||
val |= (DP_COMMON_CAP_RATE_HBR3 << ADP_DP_CS_2_NRD_MLR_SHIFT)
|
||||
& ADP_DP_CS_2_NRD_MLR_MASK;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
val &= ~ADP_DP_CS_2_NRD_MLC_MASK;
|
||||
|
||||
switch (lanes) {
|
||||
case 1:
|
||||
break;
|
||||
case 2:
|
||||
val |= DP_COMMON_CAP_2_LANES;
|
||||
break;
|
||||
case 4:
|
||||
val |= DP_COMMON_CAP_4_LANES;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return tb_port_write(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_DP_CS_2, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_dp_port_granularity() - Return granularity for the bandwidth values
|
||||
* @port: DP IN adapter
|
||||
*
|
||||
* Reads the programmed granularity from @port. If the DP IN adapter does
|
||||
* not support bandwidth allocation mode returns %-EOPNOTSUPP and negative
|
||||
* errno in other error cases.
|
||||
*/
|
||||
int usb4_dp_port_granularity(struct tb_port *port)
|
||||
{
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (!is_usb4_dpin(port))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ret = tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_DP_CS_2, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val &= ADP_DP_CS_2_GR_MASK;
|
||||
val >>= ADP_DP_CS_2_GR_SHIFT;
|
||||
|
||||
switch (val) {
|
||||
case ADP_DP_CS_2_GR_0_25G:
|
||||
return 250;
|
||||
case ADP_DP_CS_2_GR_0_5G:
|
||||
return 500;
|
||||
case ADP_DP_CS_2_GR_1G:
|
||||
return 1000;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_dp_port_set_granularity() - Set granularity for the bandwidth values
|
||||
* @port: DP IN adapter
|
||||
* @granularity: Granularity in Mb/s. Supported values: 1000, 500 and 250.
|
||||
*
|
||||
* Sets the granularity used with the estimated, allocated and requested
|
||||
* bandwidth. Returns %0 in success and negative errno otherwise. If the
|
||||
* adapter does not support this %-EOPNOTSUPP is returned.
|
||||
*/
|
||||
int usb4_dp_port_set_granularity(struct tb_port *port, int granularity)
|
||||
{
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (!is_usb4_dpin(port))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ret = tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_DP_CS_2, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val &= ~ADP_DP_CS_2_GR_MASK;
|
||||
|
||||
switch (granularity) {
|
||||
case 250:
|
||||
val |= ADP_DP_CS_2_GR_0_25G << ADP_DP_CS_2_GR_SHIFT;
|
||||
break;
|
||||
case 500:
|
||||
val |= ADP_DP_CS_2_GR_0_5G << ADP_DP_CS_2_GR_SHIFT;
|
||||
break;
|
||||
case 1000:
|
||||
val |= ADP_DP_CS_2_GR_1G << ADP_DP_CS_2_GR_SHIFT;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return tb_port_write(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_DP_CS_2, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_dp_port_set_estimated_bw() - Set estimated bandwidth
|
||||
* @port: DP IN adapter
|
||||
* @bw: Estimated bandwidth in Mb/s.
|
||||
*
|
||||
* Sets the estimated bandwidth to @bw. Set the granularity by calling
|
||||
* usb4_dp_port_set_granularity() before calling this. The @bw is round
|
||||
* down to the closest granularity multiplier. Returns %0 in success
|
||||
* and negative errno otherwise. Specifically returns %-EOPNOTSUPP if
|
||||
* the adapter does not support this.
|
||||
*/
|
||||
int usb4_dp_port_set_estimated_bw(struct tb_port *port, int bw)
|
||||
{
|
||||
u32 val, granularity;
|
||||
int ret;
|
||||
|
||||
if (!is_usb4_dpin(port))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ret = usb4_dp_port_granularity(port);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
granularity = ret;
|
||||
|
||||
ret = tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_DP_CS_2, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val &= ~ADP_DP_CS_2_ESTIMATED_BW_MASK;
|
||||
val |= (bw / granularity) << ADP_DP_CS_2_ESTIMATED_BW_SHIFT;
|
||||
|
||||
return tb_port_write(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_DP_CS_2, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_dp_port_allocated_bw() - Return allocated bandwidth
|
||||
* @port: DP IN adapter
|
||||
*
|
||||
* Reads and returns allocated bandwidth for @port in Mb/s (taking into
|
||||
* account the programmed granularity). Returns negative errno in case
|
||||
* of error.
|
||||
*/
|
||||
int usb4_dp_port_allocated_bw(struct tb_port *port)
|
||||
{
|
||||
u32 val, granularity;
|
||||
int ret;
|
||||
|
||||
if (!is_usb4_dpin(port))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ret = usb4_dp_port_granularity(port);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
granularity = ret;
|
||||
|
||||
ret = tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + DP_STATUS, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val &= DP_STATUS_ALLOCATED_BW_MASK;
|
||||
val >>= DP_STATUS_ALLOCATED_BW_SHIFT;
|
||||
|
||||
return val * granularity;
|
||||
}
|
||||
|
||||
static int __usb4_dp_port_set_cm_ack(struct tb_port *port, bool ack)
|
||||
{
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
ret = tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_DP_CS_2, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (ack)
|
||||
val |= ADP_DP_CS_2_CA;
|
||||
else
|
||||
val &= ~ADP_DP_CS_2_CA;
|
||||
|
||||
return tb_port_write(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_DP_CS_2, 1);
|
||||
}
|
||||
|
||||
static inline int usb4_dp_port_set_cm_ack(struct tb_port *port)
|
||||
{
|
||||
return __usb4_dp_port_set_cm_ack(port, true);
|
||||
}
|
||||
|
||||
static int usb4_dp_port_wait_and_clear_cm_ack(struct tb_port *port,
|
||||
int timeout_msec)
|
||||
{
|
||||
ktime_t end;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
ret = __usb4_dp_port_set_cm_ack(port, false);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
end = ktime_add_ms(ktime_get(), timeout_msec);
|
||||
do {
|
||||
ret = tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_DP_CS_8, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!(val & ADP_DP_CS_8_DR))
|
||||
break;
|
||||
|
||||
usleep_range(50, 100);
|
||||
} while (ktime_before(ktime_get(), end));
|
||||
|
||||
if (val & ADP_DP_CS_8_DR)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
ret = tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_DP_CS_2, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val &= ~ADP_DP_CS_2_CA;
|
||||
return tb_port_write(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_DP_CS_2, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_dp_port_allocate_bw() - Set allocated bandwidth
|
||||
* @port: DP IN adapter
|
||||
* @bw: New allocated bandwidth in Mb/s
|
||||
*
|
||||
* Communicates the new allocated bandwidth with the DPCD (graphics
|
||||
* driver). Takes into account the programmed granularity. Returns %0 in
|
||||
* success and negative errno in case of error.
|
||||
*/
|
||||
int usb4_dp_port_allocate_bw(struct tb_port *port, int bw)
|
||||
{
|
||||
u32 val, granularity;
|
||||
int ret;
|
||||
|
||||
if (!is_usb4_dpin(port))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ret = usb4_dp_port_granularity(port);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
granularity = ret;
|
||||
|
||||
ret = tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + DP_STATUS, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val &= ~DP_STATUS_ALLOCATED_BW_MASK;
|
||||
val |= (bw / granularity) << DP_STATUS_ALLOCATED_BW_SHIFT;
|
||||
|
||||
ret = tb_port_write(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + DP_STATUS, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = usb4_dp_port_set_cm_ack(port);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return usb4_dp_port_wait_and_clear_cm_ack(port, 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_dp_port_requested_bw() - Read requested bandwidth
|
||||
* @port: DP IN adapter
|
||||
*
|
||||
* Reads the DPCD (graphics driver) requested bandwidth and returns it
|
||||
* in Mb/s. Takes the programmed granularity into account. In case of
|
||||
* error returns negative errno. Specifically returns %-EOPNOTSUPP if
|
||||
* the adapter does not support bandwidth allocation mode, and %ENODATA
|
||||
* if there is no active bandwidth request from the graphics driver.
|
||||
*/
|
||||
int usb4_dp_port_requested_bw(struct tb_port *port)
|
||||
{
|
||||
u32 val, granularity;
|
||||
int ret;
|
||||
|
||||
if (!is_usb4_dpin(port))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ret = usb4_dp_port_granularity(port);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
granularity = ret;
|
||||
|
||||
ret = tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_adap + ADP_DP_CS_8, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!(val & ADP_DP_CS_8_DR))
|
||||
return -ENODATA;
|
||||
|
||||
return (val & ADP_DP_CS_8_REQUESTED_BW_MASK) * granularity;
|
||||
}
|
||||
|
|
|
@ -208,6 +208,7 @@ struct hw_bank {
|
|||
* @in_lpm: if the core in low power mode
|
||||
* @wakeup_int: if wakeup interrupt occur
|
||||
* @rev: The revision number for controller
|
||||
* @mutex: protect code from concorrent running when doing role switch
|
||||
*/
|
||||
struct ci_hdrc {
|
||||
struct device *dev;
|
||||
|
@ -260,6 +261,7 @@ struct ci_hdrc {
|
|||
bool in_lpm;
|
||||
bool wakeup_int;
|
||||
enum ci_revision rev;
|
||||
struct mutex mutex;
|
||||
};
|
||||
|
||||
static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci)
|
||||
|
|
|
@ -413,15 +413,19 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
|
|||
data->phy = devm_usb_get_phy_by_phandle(dev, "fsl,usbphy", 0);
|
||||
if (IS_ERR(data->phy)) {
|
||||
ret = PTR_ERR(data->phy);
|
||||
if (ret != -ENODEV)
|
||||
if (ret != -ENODEV) {
|
||||
dev_err_probe(dev, ret, "Failed to parse fsl,usbphy\n");
|
||||
goto err_clk;
|
||||
}
|
||||
data->phy = devm_usb_get_phy_by_phandle(dev, "phys", 0);
|
||||
if (IS_ERR(data->phy)) {
|
||||
ret = PTR_ERR(data->phy);
|
||||
if (ret == -ENODEV)
|
||||
if (ret == -ENODEV) {
|
||||
data->phy = NULL;
|
||||
else
|
||||
} else {
|
||||
dev_err_probe(dev, ret, "Failed to parse phys\n");
|
||||
goto err_clk;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -984,9 +984,16 @@ static ssize_t role_store(struct device *dev,
|
|||
strlen(ci->roles[role]->name)))
|
||||
break;
|
||||
|
||||
if (role == CI_ROLE_END || role == ci->role)
|
||||
if (role == CI_ROLE_END)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&ci->mutex);
|
||||
|
||||
if (role == ci->role) {
|
||||
mutex_unlock(&ci->mutex);
|
||||
return n;
|
||||
}
|
||||
|
||||
pm_runtime_get_sync(dev);
|
||||
disable_irq(ci->irq);
|
||||
ci_role_stop(ci);
|
||||
|
@ -995,6 +1002,7 @@ static ssize_t role_store(struct device *dev,
|
|||
ci_handle_vbus_change(ci);
|
||||
enable_irq(ci->irq);
|
||||
pm_runtime_put_sync(dev);
|
||||
mutex_unlock(&ci->mutex);
|
||||
|
||||
return (ret == 0) ? n : ret;
|
||||
}
|
||||
|
@ -1030,6 +1038,7 @@ static int ci_hdrc_probe(struct platform_device *pdev)
|
|||
return -ENOMEM;
|
||||
|
||||
spin_lock_init(&ci->lock);
|
||||
mutex_init(&ci->mutex);
|
||||
ci->dev = dev;
|
||||
ci->platdata = dev_get_platdata(dev);
|
||||
ci->imx28_write_fix = !!(ci->platdata->flags &
|
||||
|
|
|
@ -364,5 +364,5 @@ void dbg_create_files(struct ci_hdrc *ci)
|
|||
*/
|
||||
void dbg_remove_files(struct ci_hdrc *ci)
|
||||
{
|
||||
debugfs_remove(debugfs_lookup(dev_name(ci->dev), usb_debug_root));
|
||||
debugfs_lookup_and_remove(dev_name(ci->dev), usb_debug_root);
|
||||
}
|
||||
|
|
|
@ -167,8 +167,10 @@ static int hw_wait_vbus_lower_bsv(struct ci_hdrc *ci)
|
|||
|
||||
void ci_handle_id_switch(struct ci_hdrc *ci)
|
||||
{
|
||||
enum ci_role role = ci_otg_role(ci);
|
||||
enum ci_role role;
|
||||
|
||||
mutex_lock(&ci->mutex);
|
||||
role = ci_otg_role(ci);
|
||||
if (role != ci->role) {
|
||||
dev_dbg(ci->dev, "switching from %s to %s\n",
|
||||
ci_role(ci)->name, ci->roles[role]->name);
|
||||
|
@ -198,6 +200,7 @@ void ci_handle_id_switch(struct ci_hdrc *ci)
|
|||
if (role == CI_ROLE_GADGET)
|
||||
ci_handle_vbus_change(ci);
|
||||
}
|
||||
mutex_unlock(&ci->mutex);
|
||||
}
|
||||
/**
|
||||
* ci_otg_work - perform otg (vbus/id) event handle
|
||||
|
|
|
@ -1263,14 +1263,8 @@ static int usbmisc_imx_probe(struct platform_device *pdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int usbmisc_imx_remove(struct platform_device *pdev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver usbmisc_imx_driver = {
|
||||
.probe = usbmisc_imx_probe,
|
||||
.remove = usbmisc_imx_remove,
|
||||
.driver = {
|
||||
.name = "usbmisc_imx",
|
||||
.of_match_table = usbmisc_imx_dt_ids,
|
||||
|
|
|
@ -271,7 +271,7 @@ static int ulpi_regs_show(struct seq_file *seq, void *data)
|
|||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(ulpi_regs);
|
||||
|
||||
#define ULPI_ROOT debugfs_lookup(KBUILD_MODNAME, NULL)
|
||||
static struct dentry *ulpi_root;
|
||||
|
||||
static int ulpi_register(struct device *dev, struct ulpi *ulpi)
|
||||
{
|
||||
|
@ -301,7 +301,7 @@ static int ulpi_register(struct device *dev, struct ulpi *ulpi)
|
|||
return ret;
|
||||
}
|
||||
|
||||
root = debugfs_create_dir(dev_name(dev), ULPI_ROOT);
|
||||
root = debugfs_create_dir(dev_name(dev), ulpi_root);
|
||||
debugfs_create_file("regs", 0444, root, ulpi, &ulpi_regs_fops);
|
||||
|
||||
dev_dbg(&ulpi->dev, "registered ULPI PHY: vendor %04x, product %04x\n",
|
||||
|
@ -349,8 +349,7 @@ EXPORT_SYMBOL_GPL(ulpi_register_interface);
|
|||
*/
|
||||
void ulpi_unregister_interface(struct ulpi *ulpi)
|
||||
{
|
||||
debugfs_remove_recursive(debugfs_lookup(dev_name(&ulpi->dev),
|
||||
ULPI_ROOT));
|
||||
debugfs_lookup_and_remove(dev_name(&ulpi->dev), ulpi_root);
|
||||
device_unregister(&ulpi->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ulpi_unregister_interface);
|
||||
|
@ -360,12 +359,11 @@ EXPORT_SYMBOL_GPL(ulpi_unregister_interface);
|
|||
static int __init ulpi_init(void)
|
||||
{
|
||||
int ret;
|
||||
struct dentry *root;
|
||||
|
||||
root = debugfs_create_dir(KBUILD_MODNAME, NULL);
|
||||
ulpi_root = debugfs_create_dir(KBUILD_MODNAME, NULL);
|
||||
ret = bus_register(&ulpi_bus);
|
||||
if (ret)
|
||||
debugfs_remove(root);
|
||||
debugfs_remove(ulpi_root);
|
||||
return ret;
|
||||
}
|
||||
subsys_initcall(ulpi_init);
|
||||
|
@ -373,7 +371,7 @@ subsys_initcall(ulpi_init);
|
|||
static void __exit ulpi_exit(void)
|
||||
{
|
||||
bus_unregister(&ulpi_bus);
|
||||
debugfs_remove_recursive(ULPI_ROOT);
|
||||
debugfs_remove(ulpi_root);
|
||||
}
|
||||
module_exit(ulpi_exit);
|
||||
|
||||
|
|
|
@ -2389,9 +2389,8 @@ static int usb_enumerate_device_otg(struct usb_device *udev)
|
|||
* usb_enumerate_device - Read device configs/intfs/otg (usbcore-internal)
|
||||
* @udev: newly addressed device (in ADDRESS state)
|
||||
*
|
||||
* This is only called by usb_new_device() and usb_authorize_device()
|
||||
* and FIXME -- all comments that apply to them apply here wrt to
|
||||
* environment.
|
||||
* This is only called by usb_new_device() -- all comments that apply there
|
||||
* apply here wrt to environment.
|
||||
*
|
||||
* If the device is WUSB and not authorized, we don't attempt to read
|
||||
* the string descriptors, as they will be errored out by the device
|
||||
|
|
|
@ -869,11 +869,7 @@ read_descriptors(struct file *filp, struct kobject *kobj,
|
|||
size_t srclen, n;
|
||||
int cfgno;
|
||||
void *src;
|
||||
int retval;
|
||||
|
||||
retval = usb_lock_device_interruptible(udev);
|
||||
if (retval < 0)
|
||||
return -EINTR;
|
||||
/* The binary attribute begins with the device descriptor.
|
||||
* Following that are the raw descriptor entries for all the
|
||||
* configurations (config plus subsidiary descriptors).
|
||||
|
@ -898,7 +894,6 @@ read_descriptors(struct file *filp, struct kobject *kobj,
|
|||
off -= srclen;
|
||||
}
|
||||
}
|
||||
usb_unlock_device(udev);
|
||||
return count - nleft;
|
||||
}
|
||||
|
||||
|
|
|
@ -998,7 +998,7 @@ static void usb_debugfs_init(void)
|
|||
|
||||
static void usb_debugfs_cleanup(void)
|
||||
{
|
||||
debugfs_remove(debugfs_lookup("devices", usb_debug_root));
|
||||
debugfs_lookup_and_remove("devices", usb_debug_root);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -499,8 +499,7 @@ static int xdbc_bulk_transfer(void *data, int size, bool read)
|
|||
addr = xdbc.in_dma;
|
||||
xdbc.flags |= XDBC_FLAGS_IN_PROCESS;
|
||||
} else {
|
||||
memset(xdbc.out_buf, 0, XDBC_MAX_PACKET);
|
||||
memcpy(xdbc.out_buf, data, size);
|
||||
memcpy_and_pad(xdbc.out_buf, XDBC_MAX_PACKET, data, size, 0);
|
||||
addr = xdbc.out_dma;
|
||||
xdbc.flags |= XDBC_FLAGS_OUT_PROCESS;
|
||||
}
|
||||
|
@ -874,13 +873,14 @@ retry:
|
|||
|
||||
static void early_xdbc_write(struct console *con, const char *str, u32 n)
|
||||
{
|
||||
static char buf[XDBC_MAX_PACKET];
|
||||
/* static variables are zeroed, so buf is always NULL terminated */
|
||||
static char buf[XDBC_MAX_PACKET + 1];
|
||||
int chunk, ret;
|
||||
int use_cr = 0;
|
||||
|
||||
if (!xdbc.xdbc_reg)
|
||||
return;
|
||||
memset(buf, 0, XDBC_MAX_PACKET);
|
||||
|
||||
while (n > 0) {
|
||||
for (chunk = 0; chunk < XDBC_MAX_PACKET && n > 0; str++, chunk++, n--) {
|
||||
|
||||
|
|
|
@ -203,6 +203,7 @@ config USB_F_UAC2
|
|||
|
||||
config USB_F_UVC
|
||||
tristate
|
||||
select UVC_COMMON
|
||||
|
||||
config USB_F_MIDI
|
||||
tristate
|
||||
|
|
|
@ -888,6 +888,7 @@ static void uvc_free(struct usb_function *f)
|
|||
struct uvc_device *uvc = to_uvc(f);
|
||||
struct f_uvc_opts *opts = container_of(f->fi, struct f_uvc_opts,
|
||||
func_inst);
|
||||
config_item_put(&uvc->header->item);
|
||||
--opts->refcnt;
|
||||
kfree(uvc);
|
||||
}
|
||||
|
@ -941,6 +942,7 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi)
|
|||
struct uvc_device *uvc;
|
||||
struct f_uvc_opts *opts;
|
||||
struct uvc_descriptor_header **strm_cls;
|
||||
struct config_item *streaming, *header, *h;
|
||||
|
||||
uvc = kzalloc(sizeof(*uvc), GFP_KERNEL);
|
||||
if (uvc == NULL)
|
||||
|
@ -973,6 +975,29 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi)
|
|||
uvc->desc.fs_streaming = opts->fs_streaming;
|
||||
uvc->desc.hs_streaming = opts->hs_streaming;
|
||||
uvc->desc.ss_streaming = opts->ss_streaming;
|
||||
|
||||
streaming = config_group_find_item(&opts->func_inst.group, "streaming");
|
||||
if (!streaming)
|
||||
goto err_config;
|
||||
|
||||
header = config_group_find_item(to_config_group(streaming), "header");
|
||||
config_item_put(streaming);
|
||||
if (!header)
|
||||
goto err_config;
|
||||
|
||||
h = config_group_find_item(to_config_group(header), "h");
|
||||
config_item_put(header);
|
||||
if (!h)
|
||||
goto err_config;
|
||||
|
||||
uvc->header = to_uvcg_streaming_header(h);
|
||||
config_item_put(h);
|
||||
if (!uvc->header->linked) {
|
||||
mutex_unlock(&opts->lock);
|
||||
kfree(uvc);
|
||||
return ERR_PTR(-EBUSY);
|
||||
}
|
||||
|
||||
++opts->refcnt;
|
||||
mutex_unlock(&opts->lock);
|
||||
|
||||
|
@ -988,6 +1013,11 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi)
|
|||
uvc->func.bind_deactivated = true;
|
||||
|
||||
return &uvc->func;
|
||||
|
||||
err_config:
|
||||
mutex_unlock(&opts->lock);
|
||||
kfree(uvc);
|
||||
return ERR_PTR(-ENOENT);
|
||||
}
|
||||
|
||||
DECLARE_USB_FUNCTION_INIT(uvc, uvc_alloc_inst, uvc_alloc);
|
||||
|
|
|
@ -133,6 +133,8 @@ struct uvc_device {
|
|||
bool func_connected;
|
||||
wait_queue_head_t func_connected_queue;
|
||||
|
||||
struct uvcg_streaming_header *header;
|
||||
|
||||
/* Descriptors */
|
||||
struct {
|
||||
const struct uvc_descriptor_header * const *fs_control;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/usb/g_uvc.h>
|
||||
#include <linux/usb/uvc.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/wait.h>
|
||||
|
@ -24,6 +25,154 @@
|
|||
#include "uvc_queue.h"
|
||||
#include "uvc_video.h"
|
||||
#include "uvc_v4l2.h"
|
||||
#include "uvc_configfs.h"
|
||||
|
||||
static const struct uvc_format_desc *to_uvc_format(struct uvcg_format *uformat)
|
||||
{
|
||||
char guid[16] = UVC_GUID_FORMAT_MJPEG;
|
||||
const struct uvc_format_desc *format;
|
||||
struct uvcg_uncompressed *unc;
|
||||
|
||||
if (uformat->type == UVCG_UNCOMPRESSED) {
|
||||
unc = to_uvcg_uncompressed(&uformat->group.cg_item);
|
||||
if (!unc)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
memcpy(guid, unc->desc.guidFormat, sizeof(guid));
|
||||
}
|
||||
|
||||
format = uvc_format_by_guid(guid);
|
||||
if (!format)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
static int uvc_v4l2_get_bytesperline(struct uvcg_format *uformat,
|
||||
struct uvcg_frame *uframe)
|
||||
{
|
||||
struct uvcg_uncompressed *u;
|
||||
|
||||
if (uformat->type == UVCG_UNCOMPRESSED) {
|
||||
u = to_uvcg_uncompressed(&uformat->group.cg_item);
|
||||
if (!u)
|
||||
return 0;
|
||||
|
||||
return u->desc.bBitsPerPixel * uframe->frame.w_width / 8;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uvc_get_frame_size(struct uvcg_format *uformat,
|
||||
struct uvcg_frame *uframe)
|
||||
{
|
||||
unsigned int bpl = uvc_v4l2_get_bytesperline(uformat, uframe);
|
||||
|
||||
return bpl ? bpl * uframe->frame.w_height :
|
||||
uframe->frame.dw_max_video_frame_buffer_size;
|
||||
}
|
||||
|
||||
static struct uvcg_format *find_format_by_index(struct uvc_device *uvc, int index)
|
||||
{
|
||||
struct uvcg_format_ptr *format;
|
||||
struct uvcg_format *uformat = NULL;
|
||||
int i = 1;
|
||||
|
||||
list_for_each_entry(format, &uvc->header->formats, entry) {
|
||||
if (index == i) {
|
||||
uformat = format->fmt;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return uformat;
|
||||
}
|
||||
|
||||
static struct uvcg_frame *find_frame_by_index(struct uvc_device *uvc,
|
||||
struct uvcg_format *uformat,
|
||||
int index)
|
||||
{
|
||||
struct uvcg_format_ptr *format;
|
||||
struct uvcg_frame_ptr *frame;
|
||||
struct uvcg_frame *uframe = NULL;
|
||||
|
||||
list_for_each_entry(format, &uvc->header->formats, entry) {
|
||||
if (format->fmt->type != uformat->type)
|
||||
continue;
|
||||
list_for_each_entry(frame, &format->fmt->frames, entry) {
|
||||
if (index == frame->frm->frame.b_frame_index) {
|
||||
uframe = frame->frm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return uframe;
|
||||
}
|
||||
|
||||
static struct uvcg_format *find_format_by_pix(struct uvc_device *uvc,
|
||||
u32 pixelformat)
|
||||
{
|
||||
struct uvcg_format_ptr *format;
|
||||
struct uvcg_format *uformat = NULL;
|
||||
|
||||
list_for_each_entry(format, &uvc->header->formats, entry) {
|
||||
const struct uvc_format_desc *fmtdesc = to_uvc_format(format->fmt);
|
||||
|
||||
if (fmtdesc->fcc == pixelformat) {
|
||||
uformat = format->fmt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return uformat;
|
||||
}
|
||||
|
||||
static struct uvcg_frame *find_closest_frame_by_size(struct uvc_device *uvc,
|
||||
struct uvcg_format *uformat,
|
||||
u16 rw, u16 rh)
|
||||
{
|
||||
struct uvc_video *video = &uvc->video;
|
||||
struct uvcg_format_ptr *format;
|
||||
struct uvcg_frame_ptr *frame;
|
||||
struct uvcg_frame *uframe = NULL;
|
||||
unsigned int d, maxd;
|
||||
|
||||
/* Find the closest image size. The distance between image sizes is
|
||||
* the size in pixels of the non-overlapping regions between the
|
||||
* requested size and the frame-specified size.
|
||||
*/
|
||||
maxd = (unsigned int)-1;
|
||||
|
||||
list_for_each_entry(format, &uvc->header->formats, entry) {
|
||||
if (format->fmt->type != uformat->type)
|
||||
continue;
|
||||
|
||||
list_for_each_entry(frame, &format->fmt->frames, entry) {
|
||||
u16 w, h;
|
||||
|
||||
w = frame->frm->frame.w_width;
|
||||
h = frame->frm->frame.w_height;
|
||||
|
||||
d = min(w, rw) * min(h, rh);
|
||||
d = w*h + rw*rh - 2*d;
|
||||
if (d < maxd) {
|
||||
maxd = d;
|
||||
uframe = frame->frm;
|
||||
}
|
||||
|
||||
if (maxd == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!uframe)
|
||||
uvcg_dbg(&video->uvc->func, "Unsupported size %ux%u\n", rw, rh);
|
||||
|
||||
return uframe;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Requests handling
|
||||
|
@ -134,6 +283,133 @@ uvc_v4l2_set_format(struct file *file, void *fh, struct v4l2_format *fmt)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
uvc_v4l2_try_format(struct file *file, void *fh, struct v4l2_format *fmt)
|
||||
{
|
||||
struct video_device *vdev = video_devdata(file);
|
||||
struct uvc_device *uvc = video_get_drvdata(vdev);
|
||||
struct uvc_video *video = &uvc->video;
|
||||
struct uvcg_format *uformat;
|
||||
struct uvcg_frame *uframe;
|
||||
u8 *fcc;
|
||||
|
||||
if (fmt->type != video->queue.queue.type)
|
||||
return -EINVAL;
|
||||
|
||||
fcc = (u8 *)&fmt->fmt.pix.pixelformat;
|
||||
uvcg_dbg(&uvc->func, "Trying format 0x%08x (%c%c%c%c): %ux%u\n",
|
||||
fmt->fmt.pix.pixelformat,
|
||||
fcc[0], fcc[1], fcc[2], fcc[3],
|
||||
fmt->fmt.pix.width, fmt->fmt.pix.height);
|
||||
|
||||
uformat = find_format_by_pix(uvc, fmt->fmt.pix.pixelformat);
|
||||
if (!uformat)
|
||||
return -EINVAL;
|
||||
|
||||
uframe = find_closest_frame_by_size(uvc, uformat,
|
||||
fmt->fmt.pix.width, fmt->fmt.pix.height);
|
||||
if (!uframe)
|
||||
return -EINVAL;
|
||||
|
||||
fmt->fmt.pix.width = uframe->frame.w_width;
|
||||
fmt->fmt.pix.height = uframe->frame.w_height;
|
||||
fmt->fmt.pix.field = V4L2_FIELD_NONE;
|
||||
fmt->fmt.pix.bytesperline = uvc_v4l2_get_bytesperline(uformat, uframe);
|
||||
fmt->fmt.pix.sizeimage = uvc_get_frame_size(uformat, uframe);
|
||||
fmt->fmt.pix.pixelformat = to_uvc_format(uformat)->fcc;
|
||||
fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
|
||||
fmt->fmt.pix.priv = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
uvc_v4l2_enum_frameintervals(struct file *file, void *fh,
|
||||
struct v4l2_frmivalenum *fival)
|
||||
{
|
||||
struct video_device *vdev = video_devdata(file);
|
||||
struct uvc_device *uvc = video_get_drvdata(vdev);
|
||||
struct uvcg_format *uformat = NULL;
|
||||
struct uvcg_frame *uframe = NULL;
|
||||
struct uvcg_frame_ptr *frame;
|
||||
|
||||
uformat = find_format_by_pix(uvc, fival->pixel_format);
|
||||
if (!uformat)
|
||||
return -EINVAL;
|
||||
|
||||
list_for_each_entry(frame, &uformat->frames, entry) {
|
||||
if (frame->frm->frame.w_width == fival->width &&
|
||||
frame->frm->frame.w_height == fival->height) {
|
||||
uframe = frame->frm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!uframe)
|
||||
return -EINVAL;
|
||||
|
||||
if (fival->index >= uframe->frame.b_frame_interval_type)
|
||||
return -EINVAL;
|
||||
|
||||
fival->discrete.numerator =
|
||||
uframe->dw_frame_interval[fival->index];
|
||||
|
||||
/* TODO: handle V4L2_FRMIVAL_TYPE_STEPWISE */
|
||||
fival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
|
||||
fival->discrete.denominator = 10000000;
|
||||
v4l2_simplify_fraction(&fival->discrete.numerator,
|
||||
&fival->discrete.denominator, 8, 333);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
uvc_v4l2_enum_framesizes(struct file *file, void *fh,
|
||||
struct v4l2_frmsizeenum *fsize)
|
||||
{
|
||||
struct video_device *vdev = video_devdata(file);
|
||||
struct uvc_device *uvc = video_get_drvdata(vdev);
|
||||
struct uvcg_format *uformat = NULL;
|
||||
struct uvcg_frame *uframe = NULL;
|
||||
|
||||
uformat = find_format_by_pix(uvc, fsize->pixel_format);
|
||||
if (!uformat)
|
||||
return -EINVAL;
|
||||
|
||||
if (fsize->index >= uformat->num_frames)
|
||||
return -EINVAL;
|
||||
|
||||
uframe = find_frame_by_index(uvc, uformat, fsize->index + 1);
|
||||
if (!uframe)
|
||||
return -EINVAL;
|
||||
|
||||
fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
|
||||
fsize->discrete.width = uframe->frame.w_width;
|
||||
fsize->discrete.height = uframe->frame.w_height;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
uvc_v4l2_enum_format(struct file *file, void *fh, struct v4l2_fmtdesc *f)
|
||||
{
|
||||
struct video_device *vdev = video_devdata(file);
|
||||
struct uvc_device *uvc = video_get_drvdata(vdev);
|
||||
const struct uvc_format_desc *fmtdesc;
|
||||
struct uvcg_format *uformat;
|
||||
|
||||
if (f->index >= uvc->header->num_fmt)
|
||||
return -EINVAL;
|
||||
|
||||
uformat = find_format_by_index(uvc, f->index + 1);
|
||||
if (!uformat)
|
||||
return -EINVAL;
|
||||
|
||||
fmtdesc = to_uvc_format(uformat);
|
||||
f->pixelformat = fmtdesc->fcc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
uvc_v4l2_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *b)
|
||||
{
|
||||
|
@ -298,8 +574,12 @@ uvc_v4l2_ioctl_default(struct file *file, void *fh, bool valid_prio,
|
|||
|
||||
const struct v4l2_ioctl_ops uvc_v4l2_ioctl_ops = {
|
||||
.vidioc_querycap = uvc_v4l2_querycap,
|
||||
.vidioc_try_fmt_vid_out = uvc_v4l2_try_format,
|
||||
.vidioc_g_fmt_vid_out = uvc_v4l2_get_format,
|
||||
.vidioc_s_fmt_vid_out = uvc_v4l2_set_format,
|
||||
.vidioc_enum_frameintervals = uvc_v4l2_enum_frameintervals,
|
||||
.vidioc_enum_framesizes = uvc_v4l2_enum_framesizes,
|
||||
.vidioc_enum_fmt_vid_out = uvc_v4l2_enum_format,
|
||||
.vidioc_reqbufs = uvc_v4l2_reqbufs,
|
||||
.vidioc_querybuf = uvc_v4l2_querybuf,
|
||||
.vidioc_qbuf = uvc_v4l2_qbuf,
|
||||
|
|
|
@ -796,21 +796,16 @@ static int tegra_xudc_get_phy_index(struct tegra_xudc *xudc,
|
|||
return -1;
|
||||
}
|
||||
|
||||
static int tegra_xudc_vbus_notify(struct notifier_block *nb,
|
||||
unsigned long action, void *data)
|
||||
static void tegra_xudc_update_data_role(struct tegra_xudc *xudc,
|
||||
struct usb_phy *usbphy)
|
||||
{
|
||||
struct tegra_xudc *xudc = container_of(nb, struct tegra_xudc,
|
||||
vbus_nb);
|
||||
struct usb_phy *usbphy = (struct usb_phy *)data;
|
||||
int phy_index;
|
||||
|
||||
dev_dbg(xudc->dev, "%s(): event is %d\n", __func__, usbphy->last_event);
|
||||
|
||||
if ((xudc->device_mode && usbphy->last_event == USB_EVENT_VBUS) ||
|
||||
(!xudc->device_mode && usbphy->last_event != USB_EVENT_VBUS)) {
|
||||
dev_dbg(xudc->dev, "Same role(%d) received. Ignore",
|
||||
xudc->device_mode);
|
||||
return NOTIFY_OK;
|
||||
return;
|
||||
}
|
||||
|
||||
xudc->device_mode = (usbphy->last_event == USB_EVENT_VBUS) ? true :
|
||||
|
@ -826,6 +821,18 @@ static int tegra_xudc_vbus_notify(struct notifier_block *nb,
|
|||
xudc->curr_usbphy = usbphy;
|
||||
schedule_work(&xudc->usb_role_sw_work);
|
||||
}
|
||||
}
|
||||
|
||||
static int tegra_xudc_vbus_notify(struct notifier_block *nb,
|
||||
unsigned long action, void *data)
|
||||
{
|
||||
struct tegra_xudc *xudc = container_of(nb, struct tegra_xudc,
|
||||
vbus_nb);
|
||||
struct usb_phy *usbphy = (struct usb_phy *)data;
|
||||
|
||||
dev_dbg(xudc->dev, "%s(): event is %d\n", __func__, usbphy->last_event);
|
||||
|
||||
tegra_xudc_update_data_role(xudc, usbphy);
|
||||
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
@ -3521,7 +3528,7 @@ static int tegra_xudc_phy_get(struct tegra_xudc *xudc)
|
|||
/* Get usb-phy, if utmi phy is available */
|
||||
xudc->usbphy[i] = devm_usb_get_phy_by_node(xudc->dev,
|
||||
xudc->utmi_phy[i]->dev.of_node,
|
||||
&xudc->vbus_nb);
|
||||
NULL);
|
||||
if (IS_ERR(xudc->usbphy[i])) {
|
||||
err = PTR_ERR(xudc->usbphy[i]);
|
||||
dev_err_probe(xudc->dev, err,
|
||||
|
@ -3873,6 +3880,14 @@ static int tegra_xudc_probe(struct platform_device *pdev)
|
|||
goto free_eps;
|
||||
}
|
||||
|
||||
for (i = 0; i < xudc->soc->num_phys; i++) {
|
||||
if (!xudc->usbphy[i])
|
||||
continue;
|
||||
|
||||
usb_register_notifier(xudc->usbphy[i], &xudc->vbus_nb);
|
||||
tegra_xudc_update_data_role(xudc, xudc->usbphy[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
free_eps:
|
||||
|
|
|
@ -53,7 +53,6 @@ config USB_XHCI_PCI_RENESAS
|
|||
|
||||
config USB_XHCI_PLATFORM
|
||||
tristate "Generic xHCI driver for a platform device"
|
||||
select USB_XHCI_RCAR if ARCH_RENESAS
|
||||
help
|
||||
Adds an xHCI host driver for a generic platform device, which
|
||||
provides a memory space and an irq.
|
||||
|
@ -91,6 +90,7 @@ config USB_XHCI_RCAR
|
|||
tristate "xHCI support for Renesas R-Car SoCs"
|
||||
depends on USB_XHCI_PLATFORM
|
||||
depends on ARCH_RENESAS || COMPILE_TEST
|
||||
default ARCH_RENESAS
|
||||
help
|
||||
Say 'Y' to enable the support for the xHCI host controller
|
||||
found in Renesas R-Car ARM SoCs.
|
||||
|
@ -549,17 +549,6 @@ config USB_OHCI_HCD_SSB
|
|||
|
||||
If unsure, say N.
|
||||
|
||||
config USB_OHCI_SH
|
||||
bool "OHCI support for SuperH USB controller (DEPRECATED)"
|
||||
depends on SUPERH || COMPILE_TEST
|
||||
select USB_OHCI_HCD_PLATFORM
|
||||
help
|
||||
This option is deprecated now and the driver was removed, use
|
||||
USB_OHCI_HCD_PLATFORM instead.
|
||||
|
||||
Enables support for the on-chip OHCI controller on the SuperH.
|
||||
If you use the PCI OHCI controller, this option is not necessary.
|
||||
|
||||
config USB_OHCI_EXYNOS
|
||||
tristate "OHCI support for Samsung S5P/Exynos SoC Series"
|
||||
depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST
|
||||
|
|
|
@ -25,14 +25,13 @@ xhci-plat-hcd-y := xhci-plat.o
|
|||
ifneq ($(CONFIG_USB_XHCI_MVEBU), )
|
||||
xhci-plat-hcd-y += xhci-mvebu.o
|
||||
endif
|
||||
ifneq ($(CONFIG_USB_XHCI_RCAR), )
|
||||
xhci-plat-hcd-y += xhci-rcar.o
|
||||
endif
|
||||
|
||||
ifneq ($(CONFIG_DEBUG_FS),)
|
||||
xhci-hcd-y += xhci-debugfs.o
|
||||
endif
|
||||
|
||||
xhci-rcar-hcd-y += xhci-rcar.o
|
||||
|
||||
obj-$(CONFIG_USB_PCI) += pci-quirks.o
|
||||
|
||||
obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o
|
||||
|
@ -71,6 +70,7 @@ obj-$(CONFIG_USB_XHCI_PCI) += xhci-pci.o
|
|||
obj-$(CONFIG_USB_XHCI_PCI_RENESAS) += xhci-pci-renesas.o
|
||||
obj-$(CONFIG_USB_XHCI_PLATFORM) += xhci-plat-hcd.o
|
||||
obj-$(CONFIG_USB_XHCI_HISTB) += xhci-histb.o
|
||||
obj-$(CONFIG_USB_XHCI_RCAR) += xhci-rcar-hcd.o
|
||||
obj-$(CONFIG_USB_XHCI_MTK) += xhci-mtk-hcd.o
|
||||
obj-$(CONFIG_USB_XHCI_TEGRA) += xhci-tegra.o
|
||||
obj-$(CONFIG_USB_SL811_HCD) += sl811-hcd.o
|
||||
|
|
|
@ -712,7 +712,7 @@ static struct platform_driver ehci_fsl_driver = {
|
|||
.remove = fsl_ehci_drv_remove,
|
||||
.shutdown = usb_hcd_platform_shutdown,
|
||||
.driver = {
|
||||
.name = "fsl-ehci",
|
||||
.name = DRV_NAME,
|
||||
.pm = EHCI_FSL_PM_OPS,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1264,11 +1264,6 @@ MODULE_LICENSE ("GPL");
|
|||
#define SM501_OHCI_DRIVER ohci_hcd_sm501_driver
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_MFD_TC6393XB
|
||||
#include "ohci-tmio.c"
|
||||
#define TMIO_OHCI_DRIVER ohci_hcd_tmio_driver
|
||||
#endif
|
||||
|
||||
static int __init ohci_hcd_mod_init(void)
|
||||
{
|
||||
int retval = 0;
|
||||
|
@ -1306,19 +1301,9 @@ static int __init ohci_hcd_mod_init(void)
|
|||
goto error_sm501;
|
||||
#endif
|
||||
|
||||
#ifdef TMIO_OHCI_DRIVER
|
||||
retval = platform_driver_register(&TMIO_OHCI_DRIVER);
|
||||
if (retval < 0)
|
||||
goto error_tmio;
|
||||
#endif
|
||||
|
||||
return retval;
|
||||
|
||||
/* Error path */
|
||||
#ifdef TMIO_OHCI_DRIVER
|
||||
platform_driver_unregister(&TMIO_OHCI_DRIVER);
|
||||
error_tmio:
|
||||
#endif
|
||||
#ifdef SM501_OHCI_DRIVER
|
||||
platform_driver_unregister(&SM501_OHCI_DRIVER);
|
||||
error_sm501:
|
||||
|
@ -1345,9 +1330,6 @@ module_init(ohci_hcd_mod_init);
|
|||
|
||||
static void __exit ohci_hcd_mod_exit(void)
|
||||
{
|
||||
#ifdef TMIO_OHCI_DRIVER
|
||||
platform_driver_unregister(&TMIO_OHCI_DRIVER);
|
||||
#endif
|
||||
#ifdef SM501_OHCI_DRIVER
|
||||
platform_driver_unregister(&SM501_OHCI_DRIVER);
|
||||
#endif
|
||||
|
|
|
@ -67,8 +67,6 @@ static void omap_ohci_clock_power(struct ohci_omap_priv *priv, int on)
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_USB_OTG
|
||||
|
||||
static void start_hnp(struct ohci_hcd *ohci)
|
||||
{
|
||||
struct usb_hcd *hcd = ohci_to_hcd(ohci);
|
||||
|
@ -87,8 +85,6 @@ static void start_hnp(struct ohci_hcd *ohci)
|
|||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int ohci_omap_reset(struct usb_hcd *hcd)
|
||||
|
@ -115,8 +111,7 @@ static int ohci_omap_reset(struct usb_hcd *hcd)
|
|||
if (config->ocpi_enable)
|
||||
config->ocpi_enable();
|
||||
|
||||
#ifdef CONFIG_USB_OTG
|
||||
if (need_transceiver) {
|
||||
if (IS_ENABLED(CONFIG_USB_OTG) && need_transceiver) {
|
||||
hcd->usb_phy = usb_get_phy(USB_PHY_TYPE_USB2);
|
||||
if (!IS_ERR_OR_NULL(hcd->usb_phy)) {
|
||||
int status = otg_set_host(hcd->usb_phy->otg,
|
||||
|
@ -133,7 +128,6 @@ static int ohci_omap_reset(struct usb_hcd *hcd)
|
|||
hcd->skip_phy_initialization = 1;
|
||||
ohci->start_hnp = start_hnp;
|
||||
}
|
||||
#endif
|
||||
|
||||
omap_ohci_clock_power(priv, 1);
|
||||
|
||||
|
|
|
@ -1,364 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* OHCI HCD(Host Controller Driver) for USB.
|
||||
*
|
||||
*(C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
|
||||
*(C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net>
|
||||
*(C) Copyright 2002 Hewlett-Packard Company
|
||||
*
|
||||
* Bus glue for Toshiba Mobile IO(TMIO) Controller's OHCI core
|
||||
* (C) Copyright 2005 Chris Humbert <mahadri-usb@drigon.com>
|
||||
* (C) Copyright 2007, 2008 Dmitry Baryshkov <dbaryshkov@gmail.com>
|
||||
*
|
||||
* This is known to work with the following variants:
|
||||
* TC6393XB revision 3 (32kB SRAM)
|
||||
*
|
||||
* The TMIO's OHCI core DMAs through a small internal buffer that
|
||||
* is directly addressable by the CPU.
|
||||
*
|
||||
* Written from sparse documentation from Toshiba and Sharp's driver
|
||||
* for the 2.4 kernel,
|
||||
* usb-ohci-tc6393.c(C) Copyright 2004 Lineo Solutions, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/tmio.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* USB Host Controller Configuration Register
|
||||
*/
|
||||
#define CCR_REVID 0x08 /* b Revision ID */
|
||||
#define CCR_BASE 0x10 /* l USB Control Register Base Address Low */
|
||||
#define CCR_ILME 0x40 /* b Internal Local Memory Enable */
|
||||
#define CCR_PM 0x4c /* w Power Management */
|
||||
#define CCR_INTC 0x50 /* b INT Control */
|
||||
#define CCR_LMW1L 0x54 /* w Local Memory Window 1 LMADRS Low */
|
||||
#define CCR_LMW1H 0x56 /* w Local Memory Window 1 LMADRS High */
|
||||
#define CCR_LMW1BL 0x58 /* w Local Memory Window 1 Base Address Low */
|
||||
#define CCR_LMW1BH 0x5A /* w Local Memory Window 1 Base Address High */
|
||||
#define CCR_LMW2L 0x5C /* w Local Memory Window 2 LMADRS Low */
|
||||
#define CCR_LMW2H 0x5E /* w Local Memory Window 2 LMADRS High */
|
||||
#define CCR_LMW2BL 0x60 /* w Local Memory Window 2 Base Address Low */
|
||||
#define CCR_LMW2BH 0x62 /* w Local Memory Window 2 Base Address High */
|
||||
#define CCR_MISC 0xFC /* b MISC */
|
||||
|
||||
#define CCR_PM_GKEN 0x0001
|
||||
#define CCR_PM_CKRNEN 0x0002
|
||||
#define CCR_PM_USBPW1 0x0004
|
||||
#define CCR_PM_USBPW2 0x0008
|
||||
#define CCR_PM_USBPW3 0x0010
|
||||
#define CCR_PM_PMEE 0x0100
|
||||
#define CCR_PM_PMES 0x8000
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
struct tmio_hcd {
|
||||
void __iomem *ccr;
|
||||
spinlock_t lock; /* protects RMW cycles */
|
||||
};
|
||||
|
||||
#define hcd_to_tmio(hcd) ((struct tmio_hcd *)(hcd_to_ohci(hcd) + 1))
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void tmio_write_pm(struct platform_device *dev)
|
||||
{
|
||||
struct usb_hcd *hcd = platform_get_drvdata(dev);
|
||||
struct tmio_hcd *tmio = hcd_to_tmio(hcd);
|
||||
u16 pm;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&tmio->lock, flags);
|
||||
|
||||
pm = CCR_PM_GKEN | CCR_PM_CKRNEN |
|
||||
CCR_PM_PMEE | CCR_PM_PMES;
|
||||
|
||||
tmio_iowrite16(pm, tmio->ccr + CCR_PM);
|
||||
spin_unlock_irqrestore(&tmio->lock, flags);
|
||||
}
|
||||
|
||||
static void tmio_stop_hc(struct platform_device *dev)
|
||||
{
|
||||
struct usb_hcd *hcd = platform_get_drvdata(dev);
|
||||
struct ohci_hcd *ohci = hcd_to_ohci(hcd);
|
||||
struct tmio_hcd *tmio = hcd_to_tmio(hcd);
|
||||
u16 pm;
|
||||
|
||||
pm = CCR_PM_GKEN | CCR_PM_CKRNEN;
|
||||
switch (ohci->num_ports) {
|
||||
default:
|
||||
dev_err(&dev->dev, "Unsupported amount of ports: %d\n", ohci->num_ports);
|
||||
fallthrough;
|
||||
case 3:
|
||||
pm |= CCR_PM_USBPW3;
|
||||
fallthrough;
|
||||
case 2:
|
||||
pm |= CCR_PM_USBPW2;
|
||||
fallthrough;
|
||||
case 1:
|
||||
pm |= CCR_PM_USBPW1;
|
||||
}
|
||||
tmio_iowrite8(0, tmio->ccr + CCR_INTC);
|
||||
tmio_iowrite8(0, tmio->ccr + CCR_ILME);
|
||||
tmio_iowrite16(0, tmio->ccr + CCR_BASE);
|
||||
tmio_iowrite16(0, tmio->ccr + CCR_BASE + 2);
|
||||
tmio_iowrite16(pm, tmio->ccr + CCR_PM);
|
||||
}
|
||||
|
||||
static void tmio_start_hc(struct platform_device *dev)
|
||||
{
|
||||
struct usb_hcd *hcd = platform_get_drvdata(dev);
|
||||
struct tmio_hcd *tmio = hcd_to_tmio(hcd);
|
||||
unsigned long base = hcd->rsrc_start;
|
||||
|
||||
tmio_write_pm(dev);
|
||||
tmio_iowrite16(base, tmio->ccr + CCR_BASE);
|
||||
tmio_iowrite16(base >> 16, tmio->ccr + CCR_BASE + 2);
|
||||
tmio_iowrite8(1, tmio->ccr + CCR_ILME);
|
||||
tmio_iowrite8(2, tmio->ccr + CCR_INTC);
|
||||
|
||||
dev_info(&dev->dev, "revision %d @ 0x%08llx, irq %d\n",
|
||||
tmio_ioread8(tmio->ccr + CCR_REVID),
|
||||
(u64) hcd->rsrc_start, hcd->irq);
|
||||
}
|
||||
|
||||
static int ohci_tmio_start(struct usb_hcd *hcd)
|
||||
{
|
||||
struct ohci_hcd *ohci = hcd_to_ohci(hcd);
|
||||
int ret;
|
||||
|
||||
if ((ret = ohci_init(ohci)) < 0)
|
||||
return ret;
|
||||
|
||||
if ((ret = ohci_run(ohci)) < 0) {
|
||||
dev_err(hcd->self.controller, "can't start %s\n",
|
||||
hcd->self.bus_name);
|
||||
ohci_stop(hcd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hc_driver ohci_tmio_hc_driver = {
|
||||
.description = hcd_name,
|
||||
.product_desc = "TMIO OHCI USB Host Controller",
|
||||
.hcd_priv_size = sizeof(struct ohci_hcd) + sizeof (struct tmio_hcd),
|
||||
|
||||
/* generic hardware linkage */
|
||||
.irq = ohci_irq,
|
||||
.flags = HCD_USB11 | HCD_MEMORY,
|
||||
|
||||
/* basic lifecycle operations */
|
||||
.start = ohci_tmio_start,
|
||||
.stop = ohci_stop,
|
||||
.shutdown = ohci_shutdown,
|
||||
|
||||
/* managing i/o requests and associated device resources */
|
||||
.urb_enqueue = ohci_urb_enqueue,
|
||||
.urb_dequeue = ohci_urb_dequeue,
|
||||
.endpoint_disable = ohci_endpoint_disable,
|
||||
|
||||
/* scheduling support */
|
||||
.get_frame_number = ohci_get_frame,
|
||||
|
||||
/* root hub support */
|
||||
.hub_status_data = ohci_hub_status_data,
|
||||
.hub_control = ohci_hub_control,
|
||||
#ifdef CONFIG_PM
|
||||
.bus_suspend = ohci_bus_suspend,
|
||||
.bus_resume = ohci_bus_resume,
|
||||
#endif
|
||||
.start_port_reset = ohci_start_port_reset,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
static struct platform_driver ohci_hcd_tmio_driver;
|
||||
|
||||
static int ohci_hcd_tmio_drv_probe(struct platform_device *dev)
|
||||
{
|
||||
const struct mfd_cell *cell = mfd_get_cell(dev);
|
||||
struct resource *regs = platform_get_resource(dev, IORESOURCE_MEM, 0);
|
||||
struct resource *config = platform_get_resource(dev, IORESOURCE_MEM, 1);
|
||||
struct resource *sram = platform_get_resource(dev, IORESOURCE_MEM, 2);
|
||||
int irq = platform_get_irq(dev, 0);
|
||||
struct tmio_hcd *tmio;
|
||||
struct ohci_hcd *ohci;
|
||||
struct usb_hcd *hcd;
|
||||
int ret;
|
||||
|
||||
if (usb_disabled())
|
||||
return -ENODEV;
|
||||
|
||||
if (!cell || !regs || !config || !sram)
|
||||
return -EINVAL;
|
||||
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
|
||||
hcd = usb_create_hcd(&ohci_tmio_hc_driver, &dev->dev, dev_name(&dev->dev));
|
||||
if (!hcd) {
|
||||
ret = -ENOMEM;
|
||||
goto err_usb_create_hcd;
|
||||
}
|
||||
|
||||
hcd->rsrc_start = regs->start;
|
||||
hcd->rsrc_len = resource_size(regs);
|
||||
|
||||
tmio = hcd_to_tmio(hcd);
|
||||
|
||||
spin_lock_init(&tmio->lock);
|
||||
|
||||
tmio->ccr = ioremap(config->start, resource_size(config));
|
||||
if (!tmio->ccr) {
|
||||
ret = -ENOMEM;
|
||||
goto err_ioremap_ccr;
|
||||
}
|
||||
|
||||
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
|
||||
if (!hcd->regs) {
|
||||
ret = -ENOMEM;
|
||||
goto err_ioremap_regs;
|
||||
}
|
||||
|
||||
if (cell->enable) {
|
||||
ret = cell->enable(dev);
|
||||
if (ret)
|
||||
goto err_enable;
|
||||
}
|
||||
|
||||
tmio_start_hc(dev);
|
||||
ohci = hcd_to_ohci(hcd);
|
||||
ohci_hcd_init(ohci);
|
||||
|
||||
ret = usb_hcd_setup_local_mem(hcd, sram->start, sram->start,
|
||||
resource_size(sram));
|
||||
if (ret < 0)
|
||||
goto err_enable;
|
||||
|
||||
ret = usb_add_hcd(hcd, irq, 0);
|
||||
if (ret)
|
||||
goto err_add_hcd;
|
||||
|
||||
device_wakeup_enable(hcd->self.controller);
|
||||
if (ret == 0)
|
||||
return ret;
|
||||
|
||||
usb_remove_hcd(hcd);
|
||||
|
||||
err_add_hcd:
|
||||
tmio_stop_hc(dev);
|
||||
if (cell->disable)
|
||||
cell->disable(dev);
|
||||
err_enable:
|
||||
iounmap(hcd->regs);
|
||||
err_ioremap_regs:
|
||||
iounmap(tmio->ccr);
|
||||
err_ioremap_ccr:
|
||||
usb_put_hcd(hcd);
|
||||
err_usb_create_hcd:
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ohci_hcd_tmio_drv_remove(struct platform_device *dev)
|
||||
{
|
||||
struct usb_hcd *hcd = platform_get_drvdata(dev);
|
||||
struct tmio_hcd *tmio = hcd_to_tmio(hcd);
|
||||
const struct mfd_cell *cell = mfd_get_cell(dev);
|
||||
|
||||
usb_remove_hcd(hcd);
|
||||
tmio_stop_hc(dev);
|
||||
if (cell->disable)
|
||||
cell->disable(dev);
|
||||
iounmap(hcd->regs);
|
||||
iounmap(tmio->ccr);
|
||||
usb_put_hcd(hcd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int ohci_hcd_tmio_drv_suspend(struct platform_device *dev, pm_message_t state)
|
||||
{
|
||||
const struct mfd_cell *cell = mfd_get_cell(dev);
|
||||
struct usb_hcd *hcd = platform_get_drvdata(dev);
|
||||
struct ohci_hcd *ohci = hcd_to_ohci(hcd);
|
||||
struct tmio_hcd *tmio = hcd_to_tmio(hcd);
|
||||
unsigned long flags;
|
||||
u8 misc;
|
||||
int ret;
|
||||
|
||||
if (time_before(jiffies, ohci->next_statechange))
|
||||
msleep(5);
|
||||
ohci->next_statechange = jiffies;
|
||||
|
||||
spin_lock_irqsave(&tmio->lock, flags);
|
||||
|
||||
misc = tmio_ioread8(tmio->ccr + CCR_MISC);
|
||||
misc |= 1 << 3; /* USSUSP */
|
||||
tmio_iowrite8(misc, tmio->ccr + CCR_MISC);
|
||||
|
||||
spin_unlock_irqrestore(&tmio->lock, flags);
|
||||
|
||||
if (cell->suspend) {
|
||||
ret = cell->suspend(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ohci_hcd_tmio_drv_resume(struct platform_device *dev)
|
||||
{
|
||||
const struct mfd_cell *cell = mfd_get_cell(dev);
|
||||
struct usb_hcd *hcd = platform_get_drvdata(dev);
|
||||
struct ohci_hcd *ohci = hcd_to_ohci(hcd);
|
||||
struct tmio_hcd *tmio = hcd_to_tmio(hcd);
|
||||
unsigned long flags;
|
||||
u8 misc;
|
||||
int ret;
|
||||
|
||||
if (time_before(jiffies, ohci->next_statechange))
|
||||
msleep(5);
|
||||
ohci->next_statechange = jiffies;
|
||||
|
||||
if (cell->resume) {
|
||||
ret = cell->resume(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
tmio_start_hc(dev);
|
||||
|
||||
spin_lock_irqsave(&tmio->lock, flags);
|
||||
|
||||
misc = tmio_ioread8(tmio->ccr + CCR_MISC);
|
||||
misc &= ~(1 << 3); /* USSUSP */
|
||||
tmio_iowrite8(misc, tmio->ccr + CCR_MISC);
|
||||
|
||||
spin_unlock_irqrestore(&tmio->lock, flags);
|
||||
|
||||
ohci_resume(hcd, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define ohci_hcd_tmio_drv_suspend NULL
|
||||
#define ohci_hcd_tmio_drv_resume NULL
|
||||
#endif
|
||||
|
||||
static struct platform_driver ohci_hcd_tmio_driver = {
|
||||
.probe = ohci_hcd_tmio_drv_probe,
|
||||
.remove = ohci_hcd_tmio_drv_remove,
|
||||
.shutdown = usb_hcd_platform_shutdown,
|
||||
.suspend = ohci_hcd_tmio_drv_suspend,
|
||||
.resume = ohci_hcd_tmio_drv_resume,
|
||||
.driver = {
|
||||
.name = "tmio-ohci",
|
||||
},
|
||||
};
|
|
@ -536,8 +536,8 @@ static void release_uhci(struct uhci_hcd *uhci)
|
|||
uhci->is_initialized = 0;
|
||||
spin_unlock_irq(&uhci->lock);
|
||||
|
||||
debugfs_remove(debugfs_lookup(uhci_to_hcd(uhci)->self.bus_name,
|
||||
uhci_debugfs_root));
|
||||
debugfs_lookup_and_remove(uhci_to_hcd(uhci)->self.bus_name,
|
||||
uhci_debugfs_root);
|
||||
|
||||
for (i = 0; i < UHCI_NUM_SKELQH; i++)
|
||||
uhci_free_qh(uhci, uhci->skelqh[i]);
|
||||
|
@ -700,7 +700,7 @@ err_alloc_frame_cpu:
|
|||
uhci->frame, uhci->frame_dma_handle);
|
||||
|
||||
err_alloc_frame:
|
||||
debugfs_remove(debugfs_lookup(hcd->self.bus_name, uhci_debugfs_root));
|
||||
debugfs_lookup_and_remove(hcd->self.bus_name, uhci_debugfs_root);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
|
|
@ -692,7 +692,7 @@ void xhci_debugfs_init(struct xhci_hcd *xhci)
|
|||
"command-ring",
|
||||
xhci->debugfs_root);
|
||||
|
||||
xhci_debugfs_create_ring_dir(xhci, &xhci->event_ring,
|
||||
xhci_debugfs_create_ring_dir(xhci, &xhci->interrupter->event_ring,
|
||||
"event-ring",
|
||||
xhci->debugfs_root);
|
||||
|
||||
|
|
|
@ -578,13 +578,16 @@ void xhci_ring_device(struct xhci_hcd *xhci, int slot_id)
|
|||
return;
|
||||
}
|
||||
|
||||
static void xhci_disable_port(struct usb_hcd *hcd, struct xhci_hcd *xhci,
|
||||
u16 wIndex, __le32 __iomem *addr, u32 port_status)
|
||||
static void xhci_disable_port(struct xhci_hcd *xhci, struct xhci_port *port)
|
||||
{
|
||||
struct usb_hcd *hcd;
|
||||
u32 portsc;
|
||||
|
||||
hcd = port->rhub->hcd;
|
||||
|
||||
/* Don't allow the USB core to disable SuperSpeed ports. */
|
||||
if (hcd->speed >= HCD_USB3) {
|
||||
xhci_dbg(xhci, "Ignoring request to disable "
|
||||
"SuperSpeed port.\n");
|
||||
xhci_dbg(xhci, "Ignoring request to disable SuperSpeed port.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -594,11 +597,15 @@ static void xhci_disable_port(struct usb_hcd *hcd, struct xhci_hcd *xhci,
|
|||
return;
|
||||
}
|
||||
|
||||
portsc = readl(port->addr);
|
||||
portsc = xhci_port_state_to_neutral(portsc);
|
||||
|
||||
/* Write 1 to disable the port */
|
||||
writel(port_status | PORT_PE, addr);
|
||||
port_status = readl(addr);
|
||||
writel(portsc | PORT_PE, port->addr);
|
||||
|
||||
portsc = readl(port->addr);
|
||||
xhci_dbg(xhci, "disable port %d-%d, portsc: 0x%x\n",
|
||||
hcd->self.busnum, wIndex + 1, port_status);
|
||||
hcd->self.busnum, port->hcd_portnum + 1, portsc);
|
||||
}
|
||||
|
||||
static void xhci_clear_port_change_bit(struct xhci_hcd *xhci, u16 wValue,
|
||||
|
@ -666,20 +673,18 @@ struct xhci_hub *xhci_get_rhub(struct usb_hcd *hcd)
|
|||
* It will release and re-aquire the lock while calling ACPI
|
||||
* method.
|
||||
*/
|
||||
static void xhci_set_port_power(struct xhci_hcd *xhci, struct usb_hcd *hcd,
|
||||
u16 index, bool on, unsigned long *flags)
|
||||
static void xhci_set_port_power(struct xhci_hcd *xhci, struct xhci_port *port,
|
||||
bool on, unsigned long *flags)
|
||||
__must_hold(&xhci->lock)
|
||||
{
|
||||
struct xhci_hub *rhub;
|
||||
struct xhci_port *port;
|
||||
struct usb_hcd *hcd;
|
||||
u32 temp;
|
||||
|
||||
rhub = xhci_get_rhub(hcd);
|
||||
port = rhub->ports[index];
|
||||
hcd = port->rhub->hcd;
|
||||
temp = readl(port->addr);
|
||||
|
||||
xhci_dbg(xhci, "set port power %d-%d %s, portsc: 0x%x\n",
|
||||
hcd->self.busnum, index + 1, on ? "ON" : "OFF", temp);
|
||||
hcd->self.busnum, port->hcd_portnum + 1, on ? "ON" : "OFF", temp);
|
||||
|
||||
temp = xhci_port_state_to_neutral(temp);
|
||||
|
||||
|
@ -694,10 +699,10 @@ static void xhci_set_port_power(struct xhci_hcd *xhci, struct usb_hcd *hcd,
|
|||
|
||||
spin_unlock_irqrestore(&xhci->lock, *flags);
|
||||
temp = usb_acpi_power_manageable(hcd->self.root_hub,
|
||||
index);
|
||||
port->hcd_portnum);
|
||||
if (temp)
|
||||
usb_acpi_set_power_state(hcd->self.root_hub,
|
||||
index, on);
|
||||
port->hcd_portnum, on);
|
||||
spin_lock_irqsave(&xhci->lock, *flags);
|
||||
}
|
||||
|
||||
|
@ -721,7 +726,6 @@ static int xhci_enter_test_mode(struct xhci_hcd *xhci,
|
|||
u16 test_mode, u16 wIndex, unsigned long *flags)
|
||||
__must_hold(&xhci->lock)
|
||||
{
|
||||
struct usb_hcd *usb3_hcd = xhci_get_usb3_hcd(xhci);
|
||||
int i, retval;
|
||||
|
||||
/* Disable all Device Slots */
|
||||
|
@ -742,10 +746,10 @@ static int xhci_enter_test_mode(struct xhci_hcd *xhci,
|
|||
xhci_dbg(xhci, "Disable all port (PP = 0)\n");
|
||||
/* Power off USB3 ports*/
|
||||
for (i = 0; i < xhci->usb3_rhub.num_ports; i++)
|
||||
xhci_set_port_power(xhci, usb3_hcd, i, false, flags);
|
||||
xhci_set_port_power(xhci, xhci->usb3_rhub.ports[i], false, flags);
|
||||
/* Power off USB2 ports*/
|
||||
for (i = 0; i < xhci->usb2_rhub.num_ports; i++)
|
||||
xhci_set_port_power(xhci, xhci->main_hcd, i, false, flags);
|
||||
xhci_set_port_power(xhci, xhci->usb2_rhub.ports[i], false, flags);
|
||||
/* Stop the controller */
|
||||
xhci_dbg(xhci, "Stop controller\n");
|
||||
retval = xhci_halt(xhci);
|
||||
|
@ -920,7 +924,7 @@ static void xhci_del_comp_mod_timer(struct xhci_hcd *xhci, u32 status,
|
|||
}
|
||||
|
||||
static int xhci_handle_usb2_port_link_resume(struct xhci_port *port,
|
||||
u32 *status, u32 portsc,
|
||||
u32 portsc,
|
||||
unsigned long *flags)
|
||||
{
|
||||
struct xhci_bus_state *bus_state;
|
||||
|
@ -935,11 +939,10 @@ static int xhci_handle_usb2_port_link_resume(struct xhci_port *port,
|
|||
wIndex = port->hcd_portnum;
|
||||
|
||||
if ((portsc & PORT_RESET) || !(portsc & PORT_PE)) {
|
||||
*status = 0xffffffff;
|
||||
return -EINVAL;
|
||||
}
|
||||
/* did port event handler already start resume timing? */
|
||||
if (!bus_state->resume_done[wIndex]) {
|
||||
if (!port->resume_timestamp) {
|
||||
/* If not, maybe we are in a host initated resume? */
|
||||
if (test_bit(wIndex, &bus_state->resuming_ports)) {
|
||||
/* Host initated resume doesn't time the resume
|
||||
|
@ -956,28 +959,29 @@ static int xhci_handle_usb2_port_link_resume(struct xhci_port *port,
|
|||
msecs_to_jiffies(USB_RESUME_TIMEOUT);
|
||||
|
||||
set_bit(wIndex, &bus_state->resuming_ports);
|
||||
bus_state->resume_done[wIndex] = timeout;
|
||||
port->resume_timestamp = timeout;
|
||||
mod_timer(&hcd->rh_timer, timeout);
|
||||
usb_hcd_start_port_resume(&hcd->self, wIndex);
|
||||
}
|
||||
/* Has resume been signalled for USB_RESUME_TIME yet? */
|
||||
} else if (time_after_eq(jiffies, bus_state->resume_done[wIndex])) {
|
||||
} else if (time_after_eq(jiffies, port->resume_timestamp)) {
|
||||
int time_left;
|
||||
|
||||
xhci_dbg(xhci, "resume USB2 port %d-%d\n",
|
||||
hcd->self.busnum, wIndex + 1);
|
||||
|
||||
bus_state->resume_done[wIndex] = 0;
|
||||
port->resume_timestamp = 0;
|
||||
clear_bit(wIndex, &bus_state->resuming_ports);
|
||||
|
||||
set_bit(wIndex, &bus_state->rexit_ports);
|
||||
reinit_completion(&port->rexit_done);
|
||||
port->rexit_active = true;
|
||||
|
||||
xhci_test_and_clear_bit(xhci, port, PORT_PLC);
|
||||
xhci_set_link_state(xhci, port, XDEV_U0);
|
||||
|
||||
spin_unlock_irqrestore(&xhci->lock, *flags);
|
||||
time_left = wait_for_completion_timeout(
|
||||
&bus_state->rexit_done[wIndex],
|
||||
&port->rexit_done,
|
||||
msecs_to_jiffies(XHCI_MAX_REXIT_TIMEOUT_MS));
|
||||
spin_lock_irqsave(&xhci->lock, *flags);
|
||||
|
||||
|
@ -986,7 +990,6 @@ static int xhci_handle_usb2_port_link_resume(struct xhci_port *port,
|
|||
wIndex + 1);
|
||||
if (!slot_id) {
|
||||
xhci_dbg(xhci, "slot_id is zero\n");
|
||||
*status = 0xffffffff;
|
||||
return -ENODEV;
|
||||
}
|
||||
xhci_ring_device(xhci, slot_id);
|
||||
|
@ -995,22 +998,19 @@ static int xhci_handle_usb2_port_link_resume(struct xhci_port *port,
|
|||
|
||||
xhci_warn(xhci, "Port resume timed out, port %d-%d: 0x%x\n",
|
||||
hcd->self.busnum, wIndex + 1, port_status);
|
||||
*status |= USB_PORT_STAT_SUSPEND;
|
||||
clear_bit(wIndex, &bus_state->rexit_ports);
|
||||
/*
|
||||
* keep rexit_active set if U0 transition failed so we
|
||||
* know to report PORT_STAT_SUSPEND status back to
|
||||
* usbcore. It will be cleared later once the port is
|
||||
* out of RESUME/U3 state
|
||||
*/
|
||||
}
|
||||
|
||||
usb_hcd_end_port_resume(&hcd->self, wIndex);
|
||||
bus_state->port_c_suspend |= 1 << wIndex;
|
||||
bus_state->suspended_ports &= ~(1 << wIndex);
|
||||
} else {
|
||||
/*
|
||||
* The resume has been signaling for less than
|
||||
* USB_RESUME_TIME. Report the port status as SUSPEND,
|
||||
* let the usbcore check port status again and clear
|
||||
* resume signaling later.
|
||||
*/
|
||||
*status |= USB_PORT_STAT_SUSPEND;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1087,7 +1087,7 @@ static void xhci_get_usb2_port_status(struct xhci_port *port, u32 *status,
|
|||
struct xhci_bus_state *bus_state;
|
||||
u32 link_state;
|
||||
u32 portnum;
|
||||
int ret;
|
||||
int err;
|
||||
|
||||
bus_state = &port->rhub->bus_state;
|
||||
link_state = portsc & PORT_PLS_MASK;
|
||||
|
@ -1103,23 +1103,35 @@ static void xhci_get_usb2_port_status(struct xhci_port *port, u32 *status,
|
|||
if (link_state == XDEV_U2)
|
||||
*status |= USB_PORT_STAT_L1;
|
||||
if (link_state == XDEV_U0) {
|
||||
if (bus_state->resume_done[portnum])
|
||||
usb_hcd_end_port_resume(&port->rhub->hcd->self,
|
||||
portnum);
|
||||
bus_state->resume_done[portnum] = 0;
|
||||
clear_bit(portnum, &bus_state->resuming_ports);
|
||||
if (bus_state->suspended_ports & (1 << portnum)) {
|
||||
bus_state->suspended_ports &= ~(1 << portnum);
|
||||
bus_state->port_c_suspend |= 1 << portnum;
|
||||
}
|
||||
}
|
||||
if (link_state == XDEV_RESUME) {
|
||||
ret = xhci_handle_usb2_port_link_resume(port, status,
|
||||
portsc, flags);
|
||||
if (ret)
|
||||
return;
|
||||
err = xhci_handle_usb2_port_link_resume(port, portsc,
|
||||
flags);
|
||||
if (err < 0)
|
||||
*status = 0xffffffff;
|
||||
else if (port->resume_timestamp || port->rexit_active)
|
||||
*status |= USB_PORT_STAT_SUSPEND;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Clear usb2 resume signalling variables if port is no longer suspended
|
||||
* or resuming. Port either resumed to U0/U1/U2, disconnected, or in a
|
||||
* error state. Resume related variables should be cleared in all those cases.
|
||||
*/
|
||||
if (link_state != XDEV_U3 && link_state != XDEV_RESUME) {
|
||||
if (port->resume_timestamp ||
|
||||
test_bit(portnum, &bus_state->resuming_ports)) {
|
||||
port->resume_timestamp = 0;
|
||||
clear_bit(portnum, &bus_state->resuming_ports);
|
||||
usb_hcd_end_port_resume(&port->rhub->hcd->self, portnum);
|
||||
}
|
||||
port->rexit_active = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1174,18 +1186,6 @@ static u32 xhci_get_port_status(struct usb_hcd *hcd,
|
|||
else
|
||||
xhci_get_usb2_port_status(port, &status, raw_port_status,
|
||||
flags);
|
||||
/*
|
||||
* Clear stale usb2 resume signalling variables in case port changed
|
||||
* state during resume signalling. For example on error
|
||||
*/
|
||||
if ((bus_state->resume_done[wIndex] ||
|
||||
test_bit(wIndex, &bus_state->resuming_ports)) &&
|
||||
(raw_port_status & PORT_PLS_MASK) != XDEV_U3 &&
|
||||
(raw_port_status & PORT_PLS_MASK) != XDEV_RESUME) {
|
||||
bus_state->resume_done[wIndex] = 0;
|
||||
clear_bit(wIndex, &bus_state->resuming_ports);
|
||||
usb_hcd_end_port_resume(&hcd->self, wIndex);
|
||||
}
|
||||
|
||||
if (bus_state->port_c_suspend & (1 << wIndex))
|
||||
status |= USB_PORT_STAT_C_SUSPEND << 16;
|
||||
|
@ -1209,11 +1209,14 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
|||
u16 test_mode = 0;
|
||||
struct xhci_hub *rhub;
|
||||
struct xhci_port **ports;
|
||||
struct xhci_port *port;
|
||||
int portnum1;
|
||||
|
||||
rhub = xhci_get_rhub(hcd);
|
||||
ports = rhub->ports;
|
||||
max_ports = rhub->num_ports;
|
||||
bus_state = &rhub->bus_state;
|
||||
portnum1 = wIndex & 0xff;
|
||||
|
||||
spin_lock_irqsave(&xhci->lock, flags);
|
||||
switch (typeReq) {
|
||||
|
@ -1247,10 +1250,12 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
|||
spin_unlock_irqrestore(&xhci->lock, flags);
|
||||
return retval;
|
||||
case GetPortStatus:
|
||||
if (!wIndex || wIndex > max_ports)
|
||||
if (!portnum1 || portnum1 > max_ports)
|
||||
goto error;
|
||||
|
||||
wIndex--;
|
||||
temp = readl(ports[wIndex]->addr);
|
||||
port = ports[portnum1 - 1];
|
||||
temp = readl(port->addr);
|
||||
if (temp == ~(u32)0) {
|
||||
xhci_hc_died(xhci);
|
||||
retval = -ENODEV;
|
||||
|
@ -1263,7 +1268,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
|||
goto error;
|
||||
|
||||
xhci_dbg(xhci, "Get port status %d-%d read: 0x%x, return 0x%x",
|
||||
hcd->self.busnum, wIndex + 1, temp, status);
|
||||
hcd->self.busnum, portnum1, temp, status);
|
||||
|
||||
put_unaligned(cpu_to_le32(status), (__le32 *) buf);
|
||||
/* if USB 3.1 extended port status return additional 4 bytes */
|
||||
|
@ -1275,7 +1280,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
|||
retval = -EINVAL;
|
||||
break;
|
||||
}
|
||||
port_li = readl(ports[wIndex]->addr + PORTLI);
|
||||
port_li = readl(port->addr + PORTLI);
|
||||
status = xhci_get_ext_port_status(temp, port_li);
|
||||
put_unaligned_le32(status, &buf[4]);
|
||||
}
|
||||
|
@ -1289,11 +1294,14 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
|||
test_mode = (wIndex & 0xff00) >> 8;
|
||||
/* The MSB of wIndex is the U1/U2 timeout */
|
||||
timeout = (wIndex & 0xff00) >> 8;
|
||||
|
||||
wIndex &= 0xff;
|
||||
if (!wIndex || wIndex > max_ports)
|
||||
if (!portnum1 || portnum1 > max_ports)
|
||||
goto error;
|
||||
|
||||
port = ports[portnum1 - 1];
|
||||
wIndex--;
|
||||
temp = readl(ports[wIndex]->addr);
|
||||
temp = readl(port->addr);
|
||||
if (temp == ~(u32)0) {
|
||||
xhci_hc_died(xhci);
|
||||
retval = -ENODEV;
|
||||
|
@ -1303,11 +1311,10 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
|||
/* FIXME: What new port features do we need to support? */
|
||||
switch (wValue) {
|
||||
case USB_PORT_FEAT_SUSPEND:
|
||||
temp = readl(ports[wIndex]->addr);
|
||||
temp = readl(port->addr);
|
||||
if ((temp & PORT_PLS_MASK) != XDEV_U0) {
|
||||
/* Resume the port to U0 first */
|
||||
xhci_set_link_state(xhci, ports[wIndex],
|
||||
XDEV_U0);
|
||||
xhci_set_link_state(xhci, port, XDEV_U0);
|
||||
spin_unlock_irqrestore(&xhci->lock, flags);
|
||||
msleep(10);
|
||||
spin_lock_irqsave(&xhci->lock, flags);
|
||||
|
@ -1316,16 +1323,16 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
|||
* a port unless the port reports that it is in the
|
||||
* enabled (PED = ‘1’,PLS < ‘3’) state.
|
||||
*/
|
||||
temp = readl(ports[wIndex]->addr);
|
||||
temp = readl(port->addr);
|
||||
if ((temp & PORT_PE) == 0 || (temp & PORT_RESET)
|
||||
|| (temp & PORT_PLS_MASK) >= XDEV_U3) {
|
||||
xhci_warn(xhci, "USB core suspending port %d-%d not in U0/U1/U2\n",
|
||||
hcd->self.busnum, wIndex + 1);
|
||||
hcd->self.busnum, portnum1);
|
||||
goto error;
|
||||
}
|
||||
|
||||
slot_id = xhci_find_slot_id_by_port(hcd, xhci,
|
||||
wIndex + 1);
|
||||
portnum1);
|
||||
if (!slot_id) {
|
||||
xhci_warn(xhci, "slot_id is zero\n");
|
||||
goto error;
|
||||
|
@ -1335,21 +1342,21 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
|||
xhci_stop_device(xhci, slot_id, 1);
|
||||
spin_lock_irqsave(&xhci->lock, flags);
|
||||
|
||||
xhci_set_link_state(xhci, ports[wIndex], XDEV_U3);
|
||||
xhci_set_link_state(xhci, port, XDEV_U3);
|
||||
|
||||
spin_unlock_irqrestore(&xhci->lock, flags);
|
||||
msleep(10); /* wait device to enter */
|
||||
spin_lock_irqsave(&xhci->lock, flags);
|
||||
|
||||
temp = readl(ports[wIndex]->addr);
|
||||
temp = readl(port->addr);
|
||||
bus_state->suspended_ports |= 1 << wIndex;
|
||||
break;
|
||||
case USB_PORT_FEAT_LINK_STATE:
|
||||
temp = readl(ports[wIndex]->addr);
|
||||
temp = readl(port->addr);
|
||||
/* Disable port */
|
||||
if (link_state == USB_SS_PORT_LS_SS_DISABLED) {
|
||||
xhci_dbg(xhci, "Disable port %d-%d\n",
|
||||
hcd->self.busnum, wIndex + 1);
|
||||
hcd->self.busnum, portnum1);
|
||||
temp = xhci_port_state_to_neutral(temp);
|
||||
/*
|
||||
* Clear all change bits, so that we get a new
|
||||
|
@ -1358,18 +1365,17 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
|||
temp |= PORT_CSC | PORT_PEC | PORT_WRC |
|
||||
PORT_OCC | PORT_RC | PORT_PLC |
|
||||
PORT_CEC;
|
||||
writel(temp | PORT_PE, ports[wIndex]->addr);
|
||||
temp = readl(ports[wIndex]->addr);
|
||||
writel(temp | PORT_PE, port->addr);
|
||||
temp = readl(port->addr);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Put link in RxDetect (enable port) */
|
||||
if (link_state == USB_SS_PORT_LS_RX_DETECT) {
|
||||
xhci_dbg(xhci, "Enable port %d-%d\n",
|
||||
hcd->self.busnum, wIndex + 1);
|
||||
xhci_set_link_state(xhci, ports[wIndex],
|
||||
link_state);
|
||||
temp = readl(ports[wIndex]->addr);
|
||||
hcd->self.busnum, portnum1);
|
||||
xhci_set_link_state(xhci, port, link_state);
|
||||
temp = readl(port->addr);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1399,11 +1405,10 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
|||
}
|
||||
|
||||
xhci_dbg(xhci, "Enable compliance mode transition for port %d-%d\n",
|
||||
hcd->self.busnum, wIndex + 1);
|
||||
xhci_set_link_state(xhci, ports[wIndex],
|
||||
link_state);
|
||||
hcd->self.busnum, portnum1);
|
||||
xhci_set_link_state(xhci, port, link_state);
|
||||
|
||||
temp = readl(ports[wIndex]->addr);
|
||||
temp = readl(port->addr);
|
||||
break;
|
||||
}
|
||||
/* Port must be enabled */
|
||||
|
@ -1414,8 +1419,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
|||
/* Can't set port link state above '3' (U3) */
|
||||
if (link_state > USB_SS_PORT_LS_U3) {
|
||||
xhci_warn(xhci, "Cannot set port %d-%d link state %d\n",
|
||||
hcd->self.busnum, wIndex + 1,
|
||||
link_state);
|
||||
hcd->self.busnum, portnum1, link_state);
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
@ -1437,30 +1441,29 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
|||
pls == XDEV_RESUME ||
|
||||
pls == XDEV_RECOVERY) {
|
||||
wait_u0 = true;
|
||||
reinit_completion(&bus_state->u3exit_done[wIndex]);
|
||||
reinit_completion(&port->u3exit_done);
|
||||
}
|
||||
if (pls <= XDEV_U3) /* U1, U2, U3 */
|
||||
xhci_set_link_state(xhci, ports[wIndex],
|
||||
USB_SS_PORT_LS_U0);
|
||||
xhci_set_link_state(xhci, port, USB_SS_PORT_LS_U0);
|
||||
if (!wait_u0) {
|
||||
if (pls > XDEV_U3)
|
||||
goto error;
|
||||
break;
|
||||
}
|
||||
spin_unlock_irqrestore(&xhci->lock, flags);
|
||||
if (!wait_for_completion_timeout(&bus_state->u3exit_done[wIndex],
|
||||
if (!wait_for_completion_timeout(&port->u3exit_done,
|
||||
msecs_to_jiffies(500)))
|
||||
xhci_dbg(xhci, "missing U0 port change event for port %d-%d\n",
|
||||
hcd->self.busnum, wIndex + 1);
|
||||
hcd->self.busnum, portnum1);
|
||||
spin_lock_irqsave(&xhci->lock, flags);
|
||||
temp = readl(ports[wIndex]->addr);
|
||||
temp = readl(port->addr);
|
||||
break;
|
||||
}
|
||||
|
||||
if (link_state == USB_SS_PORT_LS_U3) {
|
||||
int retries = 16;
|
||||
slot_id = xhci_find_slot_id_by_port(hcd, xhci,
|
||||
wIndex + 1);
|
||||
portnum1);
|
||||
if (slot_id) {
|
||||
/* unlock to execute stop endpoint
|
||||
* commands */
|
||||
|
@ -1469,16 +1472,16 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
|||
xhci_stop_device(xhci, slot_id, 1);
|
||||
spin_lock_irqsave(&xhci->lock, flags);
|
||||
}
|
||||
xhci_set_link_state(xhci, ports[wIndex], USB_SS_PORT_LS_U3);
|
||||
xhci_set_link_state(xhci, port, USB_SS_PORT_LS_U3);
|
||||
spin_unlock_irqrestore(&xhci->lock, flags);
|
||||
while (retries--) {
|
||||
usleep_range(4000, 8000);
|
||||
temp = readl(ports[wIndex]->addr);
|
||||
temp = readl(port->addr);
|
||||
if ((temp & PORT_PLS_MASK) == XDEV_U3)
|
||||
break;
|
||||
}
|
||||
spin_lock_irqsave(&xhci->lock, flags);
|
||||
temp = readl(ports[wIndex]->addr);
|
||||
temp = readl(port->addr);
|
||||
bus_state->suspended_ports |= 1 << wIndex;
|
||||
}
|
||||
break;
|
||||
|
@ -1489,43 +1492,42 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
|||
* However, hub_wq will ignore the roothub events until
|
||||
* the roothub is registered.
|
||||
*/
|
||||
xhci_set_port_power(xhci, hcd, wIndex, true, &flags);
|
||||
xhci_set_port_power(xhci, port, true, &flags);
|
||||
break;
|
||||
case USB_PORT_FEAT_RESET:
|
||||
temp = (temp | PORT_RESET);
|
||||
writel(temp, ports[wIndex]->addr);
|
||||
writel(temp, port->addr);
|
||||
|
||||
temp = readl(ports[wIndex]->addr);
|
||||
temp = readl(port->addr);
|
||||
xhci_dbg(xhci, "set port reset, actual port %d-%d status = 0x%x\n",
|
||||
hcd->self.busnum, wIndex + 1, temp);
|
||||
hcd->self.busnum, portnum1, temp);
|
||||
break;
|
||||
case USB_PORT_FEAT_REMOTE_WAKE_MASK:
|
||||
xhci_set_remote_wake_mask(xhci, ports[wIndex],
|
||||
wake_mask);
|
||||
temp = readl(ports[wIndex]->addr);
|
||||
xhci_set_remote_wake_mask(xhci, port, wake_mask);
|
||||
temp = readl(port->addr);
|
||||
xhci_dbg(xhci, "set port remote wake mask, actual port %d-%d status = 0x%x\n",
|
||||
hcd->self.busnum, wIndex + 1, temp);
|
||||
hcd->self.busnum, portnum1, temp);
|
||||
break;
|
||||
case USB_PORT_FEAT_BH_PORT_RESET:
|
||||
temp |= PORT_WR;
|
||||
writel(temp, ports[wIndex]->addr);
|
||||
temp = readl(ports[wIndex]->addr);
|
||||
writel(temp, port->addr);
|
||||
temp = readl(port->addr);
|
||||
break;
|
||||
case USB_PORT_FEAT_U1_TIMEOUT:
|
||||
if (hcd->speed < HCD_USB3)
|
||||
goto error;
|
||||
temp = readl(ports[wIndex]->addr + PORTPMSC);
|
||||
temp = readl(port->addr + PORTPMSC);
|
||||
temp &= ~PORT_U1_TIMEOUT_MASK;
|
||||
temp |= PORT_U1_TIMEOUT(timeout);
|
||||
writel(temp, ports[wIndex]->addr + PORTPMSC);
|
||||
writel(temp, port->addr + PORTPMSC);
|
||||
break;
|
||||
case USB_PORT_FEAT_U2_TIMEOUT:
|
||||
if (hcd->speed < HCD_USB3)
|
||||
goto error;
|
||||
temp = readl(ports[wIndex]->addr + PORTPMSC);
|
||||
temp = readl(port->addr + PORTPMSC);
|
||||
temp &= ~PORT_U2_TIMEOUT_MASK;
|
||||
temp |= PORT_U2_TIMEOUT(timeout);
|
||||
writel(temp, ports[wIndex]->addr + PORTPMSC);
|
||||
writel(temp, port->addr + PORTPMSC);
|
||||
break;
|
||||
case USB_PORT_FEAT_TEST:
|
||||
/* 4.19.6 Port Test Modes (USB2 Test Mode) */
|
||||
|
@ -1541,13 +1543,16 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
|||
goto error;
|
||||
}
|
||||
/* unblock any posted writes */
|
||||
temp = readl(ports[wIndex]->addr);
|
||||
temp = readl(port->addr);
|
||||
break;
|
||||
case ClearPortFeature:
|
||||
if (!wIndex || wIndex > max_ports)
|
||||
if (!portnum1 || portnum1 > max_ports)
|
||||
goto error;
|
||||
|
||||
port = ports[portnum1 - 1];
|
||||
|
||||
wIndex--;
|
||||
temp = readl(ports[wIndex]->addr);
|
||||
temp = readl(port->addr);
|
||||
if (temp == ~(u32)0) {
|
||||
xhci_hc_died(xhci);
|
||||
retval = -ENODEV;
|
||||
|
@ -1557,7 +1562,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
|||
temp = xhci_port_state_to_neutral(temp);
|
||||
switch (wValue) {
|
||||
case USB_PORT_FEAT_SUSPEND:
|
||||
temp = readl(ports[wIndex]->addr);
|
||||
temp = readl(port->addr);
|
||||
xhci_dbg(xhci, "clear USB_PORT_FEAT_SUSPEND\n");
|
||||
xhci_dbg(xhci, "PORTSC %04x\n", temp);
|
||||
if (temp & PORT_RESET)
|
||||
|
@ -1568,20 +1573,18 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
|||
|
||||
set_bit(wIndex, &bus_state->resuming_ports);
|
||||
usb_hcd_start_port_resume(&hcd->self, wIndex);
|
||||
xhci_set_link_state(xhci, ports[wIndex],
|
||||
XDEV_RESUME);
|
||||
xhci_set_link_state(xhci, port, XDEV_RESUME);
|
||||
spin_unlock_irqrestore(&xhci->lock, flags);
|
||||
msleep(USB_RESUME_TIMEOUT);
|
||||
spin_lock_irqsave(&xhci->lock, flags);
|
||||
xhci_set_link_state(xhci, ports[wIndex],
|
||||
XDEV_U0);
|
||||
xhci_set_link_state(xhci, port, XDEV_U0);
|
||||
clear_bit(wIndex, &bus_state->resuming_ports);
|
||||
usb_hcd_end_port_resume(&hcd->self, wIndex);
|
||||
}
|
||||
bus_state->port_c_suspend |= 1 << wIndex;
|
||||
|
||||
slot_id = xhci_find_slot_id_by_port(hcd, xhci,
|
||||
wIndex + 1);
|
||||
portnum1);
|
||||
if (!slot_id) {
|
||||
xhci_dbg(xhci, "slot_id is zero\n");
|
||||
goto error;
|
||||
|
@ -1599,14 +1602,13 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
|||
case USB_PORT_FEAT_C_PORT_LINK_STATE:
|
||||
case USB_PORT_FEAT_C_PORT_CONFIG_ERROR:
|
||||
xhci_clear_port_change_bit(xhci, wValue, wIndex,
|
||||
ports[wIndex]->addr, temp);
|
||||
port->addr, temp);
|
||||
break;
|
||||
case USB_PORT_FEAT_ENABLE:
|
||||
xhci_disable_port(hcd, xhci, wIndex,
|
||||
ports[wIndex]->addr, temp);
|
||||
xhci_disable_port(xhci, port);
|
||||
break;
|
||||
case USB_PORT_FEAT_POWER:
|
||||
xhci_set_port_power(xhci, hcd, wIndex, false, &flags);
|
||||
xhci_set_port_power(xhci, port, false, &flags);
|
||||
break;
|
||||
case USB_PORT_FEAT_TEST:
|
||||
retval = xhci_exit_test_mode(xhci);
|
||||
|
@ -1688,8 +1690,8 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf)
|
|||
|
||||
if ((temp & mask) != 0 ||
|
||||
(bus_state->port_c_suspend & 1 << i) ||
|
||||
(bus_state->resume_done[i] && time_after_eq(
|
||||
jiffies, bus_state->resume_done[i]))) {
|
||||
(ports[i]->resume_timestamp && time_after_eq(
|
||||
jiffies, ports[i]->resume_timestamp))) {
|
||||
buf[(i + 1) / 8] |= 1 << (i + 1) % 8;
|
||||
status = 1;
|
||||
}
|
||||
|
|
|
@ -1819,17 +1819,43 @@ int xhci_alloc_erst(struct xhci_hcd *xhci,
|
|||
return 0;
|
||||
}
|
||||
|
||||
void xhci_free_erst(struct xhci_hcd *xhci, struct xhci_erst *erst)
|
||||
static void
|
||||
xhci_free_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
|
||||
{
|
||||
size_t size;
|
||||
struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
|
||||
size_t erst_size;
|
||||
u64 tmp64;
|
||||
u32 tmp;
|
||||
|
||||
size = sizeof(struct xhci_erst_entry) * (erst->num_entries);
|
||||
if (erst->entries)
|
||||
dma_free_coherent(dev, size,
|
||||
erst->entries,
|
||||
erst->erst_dma_addr);
|
||||
erst->entries = NULL;
|
||||
if (!ir)
|
||||
return;
|
||||
|
||||
erst_size = sizeof(struct xhci_erst_entry) * (ir->erst.num_entries);
|
||||
if (ir->erst.entries)
|
||||
dma_free_coherent(dev, erst_size,
|
||||
ir->erst.entries,
|
||||
ir->erst.erst_dma_addr);
|
||||
ir->erst.entries = NULL;
|
||||
|
||||
/*
|
||||
* Clean out interrupter registers except ERSTBA. Clearing either the
|
||||
* low or high 32 bits of ERSTBA immediately causes the controller to
|
||||
* dereference the partially cleared 64 bit address, causing IOMMU error.
|
||||
*/
|
||||
tmp = readl(&ir->ir_set->erst_size);
|
||||
tmp &= ERST_SIZE_MASK;
|
||||
writel(tmp, &ir->ir_set->erst_size);
|
||||
|
||||
tmp64 = xhci_read_64(xhci, &ir->ir_set->erst_dequeue);
|
||||
tmp64 &= (u64) ERST_PTR_MASK;
|
||||
xhci_write_64(xhci, tmp64, &ir->ir_set->erst_dequeue);
|
||||
|
||||
/* free interrrupter event ring */
|
||||
if (ir->event_ring)
|
||||
xhci_ring_free(xhci, ir->event_ring);
|
||||
ir->event_ring = NULL;
|
||||
|
||||
kfree(ir);
|
||||
}
|
||||
|
||||
void xhci_mem_cleanup(struct xhci_hcd *xhci)
|
||||
|
@ -1839,12 +1865,9 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci)
|
|||
|
||||
cancel_delayed_work_sync(&xhci->cmd_timer);
|
||||
|
||||
xhci_free_erst(xhci, &xhci->erst);
|
||||
|
||||
if (xhci->event_ring)
|
||||
xhci_ring_free(xhci, xhci->event_ring);
|
||||
xhci->event_ring = NULL;
|
||||
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed event ring");
|
||||
xhci_free_interrupter(xhci, xhci->interrupter);
|
||||
xhci->interrupter = NULL;
|
||||
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed primary event ring");
|
||||
|
||||
if (xhci->cmd_ring)
|
||||
xhci_ring_free(xhci, xhci->cmd_ring);
|
||||
|
@ -1929,176 +1952,18 @@ no_bw:
|
|||
xhci->usb3_rhub.bus_state.bus_suspended = 0;
|
||||
}
|
||||
|
||||
static int xhci_test_trb_in_td(struct xhci_hcd *xhci,
|
||||
struct xhci_segment *input_seg,
|
||||
union xhci_trb *start_trb,
|
||||
union xhci_trb *end_trb,
|
||||
dma_addr_t input_dma,
|
||||
struct xhci_segment *result_seg,
|
||||
char *test_name, int test_number)
|
||||
{
|
||||
unsigned long long start_dma;
|
||||
unsigned long long end_dma;
|
||||
struct xhci_segment *seg;
|
||||
|
||||
start_dma = xhci_trb_virt_to_dma(input_seg, start_trb);
|
||||
end_dma = xhci_trb_virt_to_dma(input_seg, end_trb);
|
||||
|
||||
seg = trb_in_td(xhci, input_seg, start_trb, end_trb, input_dma, false);
|
||||
if (seg != result_seg) {
|
||||
xhci_warn(xhci, "WARN: %s TRB math test %d failed!\n",
|
||||
test_name, test_number);
|
||||
xhci_warn(xhci, "Tested TRB math w/ seg %p and "
|
||||
"input DMA 0x%llx\n",
|
||||
input_seg,
|
||||
(unsigned long long) input_dma);
|
||||
xhci_warn(xhci, "starting TRB %p (0x%llx DMA), "
|
||||
"ending TRB %p (0x%llx DMA)\n",
|
||||
start_trb, start_dma,
|
||||
end_trb, end_dma);
|
||||
xhci_warn(xhci, "Expected seg %p, got seg %p\n",
|
||||
result_seg, seg);
|
||||
trb_in_td(xhci, input_seg, start_trb, end_trb, input_dma,
|
||||
true);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* TRB math checks for xhci_trb_in_td(), using the command and event rings. */
|
||||
static int xhci_check_trb_in_td_math(struct xhci_hcd *xhci)
|
||||
{
|
||||
struct {
|
||||
dma_addr_t input_dma;
|
||||
struct xhci_segment *result_seg;
|
||||
} simple_test_vector [] = {
|
||||
/* A zeroed DMA field should fail */
|
||||
{ 0, NULL },
|
||||
/* One TRB before the ring start should fail */
|
||||
{ xhci->event_ring->first_seg->dma - 16, NULL },
|
||||
/* One byte before the ring start should fail */
|
||||
{ xhci->event_ring->first_seg->dma - 1, NULL },
|
||||
/* Starting TRB should succeed */
|
||||
{ xhci->event_ring->first_seg->dma, xhci->event_ring->first_seg },
|
||||
/* Ending TRB should succeed */
|
||||
{ xhci->event_ring->first_seg->dma + (TRBS_PER_SEGMENT - 1)*16,
|
||||
xhci->event_ring->first_seg },
|
||||
/* One byte after the ring end should fail */
|
||||
{ xhci->event_ring->first_seg->dma + (TRBS_PER_SEGMENT - 1)*16 + 1, NULL },
|
||||
/* One TRB after the ring end should fail */
|
||||
{ xhci->event_ring->first_seg->dma + (TRBS_PER_SEGMENT)*16, NULL },
|
||||
/* An address of all ones should fail */
|
||||
{ (dma_addr_t) (~0), NULL },
|
||||
};
|
||||
struct {
|
||||
struct xhci_segment *input_seg;
|
||||
union xhci_trb *start_trb;
|
||||
union xhci_trb *end_trb;
|
||||
dma_addr_t input_dma;
|
||||
struct xhci_segment *result_seg;
|
||||
} complex_test_vector [] = {
|
||||
/* Test feeding a valid DMA address from a different ring */
|
||||
{ .input_seg = xhci->event_ring->first_seg,
|
||||
.start_trb = xhci->event_ring->first_seg->trbs,
|
||||
.end_trb = &xhci->event_ring->first_seg->trbs[TRBS_PER_SEGMENT - 1],
|
||||
.input_dma = xhci->cmd_ring->first_seg->dma,
|
||||
.result_seg = NULL,
|
||||
},
|
||||
/* Test feeding a valid end TRB from a different ring */
|
||||
{ .input_seg = xhci->event_ring->first_seg,
|
||||
.start_trb = xhci->event_ring->first_seg->trbs,
|
||||
.end_trb = &xhci->cmd_ring->first_seg->trbs[TRBS_PER_SEGMENT - 1],
|
||||
.input_dma = xhci->cmd_ring->first_seg->dma,
|
||||
.result_seg = NULL,
|
||||
},
|
||||
/* Test feeding a valid start and end TRB from a different ring */
|
||||
{ .input_seg = xhci->event_ring->first_seg,
|
||||
.start_trb = xhci->cmd_ring->first_seg->trbs,
|
||||
.end_trb = &xhci->cmd_ring->first_seg->trbs[TRBS_PER_SEGMENT - 1],
|
||||
.input_dma = xhci->cmd_ring->first_seg->dma,
|
||||
.result_seg = NULL,
|
||||
},
|
||||
/* TRB in this ring, but after this TD */
|
||||
{ .input_seg = xhci->event_ring->first_seg,
|
||||
.start_trb = &xhci->event_ring->first_seg->trbs[0],
|
||||
.end_trb = &xhci->event_ring->first_seg->trbs[3],
|
||||
.input_dma = xhci->event_ring->first_seg->dma + 4*16,
|
||||
.result_seg = NULL,
|
||||
},
|
||||
/* TRB in this ring, but before this TD */
|
||||
{ .input_seg = xhci->event_ring->first_seg,
|
||||
.start_trb = &xhci->event_ring->first_seg->trbs[3],
|
||||
.end_trb = &xhci->event_ring->first_seg->trbs[6],
|
||||
.input_dma = xhci->event_ring->first_seg->dma + 2*16,
|
||||
.result_seg = NULL,
|
||||
},
|
||||
/* TRB in this ring, but after this wrapped TD */
|
||||
{ .input_seg = xhci->event_ring->first_seg,
|
||||
.start_trb = &xhci->event_ring->first_seg->trbs[TRBS_PER_SEGMENT - 3],
|
||||
.end_trb = &xhci->event_ring->first_seg->trbs[1],
|
||||
.input_dma = xhci->event_ring->first_seg->dma + 2*16,
|
||||
.result_seg = NULL,
|
||||
},
|
||||
/* TRB in this ring, but before this wrapped TD */
|
||||
{ .input_seg = xhci->event_ring->first_seg,
|
||||
.start_trb = &xhci->event_ring->first_seg->trbs[TRBS_PER_SEGMENT - 3],
|
||||
.end_trb = &xhci->event_ring->first_seg->trbs[1],
|
||||
.input_dma = xhci->event_ring->first_seg->dma + (TRBS_PER_SEGMENT - 4)*16,
|
||||
.result_seg = NULL,
|
||||
},
|
||||
/* TRB not in this ring, and we have a wrapped TD */
|
||||
{ .input_seg = xhci->event_ring->first_seg,
|
||||
.start_trb = &xhci->event_ring->first_seg->trbs[TRBS_PER_SEGMENT - 3],
|
||||
.end_trb = &xhci->event_ring->first_seg->trbs[1],
|
||||
.input_dma = xhci->cmd_ring->first_seg->dma + 2*16,
|
||||
.result_seg = NULL,
|
||||
},
|
||||
};
|
||||
|
||||
unsigned int num_tests;
|
||||
int i, ret;
|
||||
|
||||
num_tests = ARRAY_SIZE(simple_test_vector);
|
||||
for (i = 0; i < num_tests; i++) {
|
||||
ret = xhci_test_trb_in_td(xhci,
|
||||
xhci->event_ring->first_seg,
|
||||
xhci->event_ring->first_seg->trbs,
|
||||
&xhci->event_ring->first_seg->trbs[TRBS_PER_SEGMENT - 1],
|
||||
simple_test_vector[i].input_dma,
|
||||
simple_test_vector[i].result_seg,
|
||||
"Simple", i);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
num_tests = ARRAY_SIZE(complex_test_vector);
|
||||
for (i = 0; i < num_tests; i++) {
|
||||
ret = xhci_test_trb_in_td(xhci,
|
||||
complex_test_vector[i].input_seg,
|
||||
complex_test_vector[i].start_trb,
|
||||
complex_test_vector[i].end_trb,
|
||||
complex_test_vector[i].input_dma,
|
||||
complex_test_vector[i].result_seg,
|
||||
"Complex", i);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
xhci_dbg(xhci, "TRB math tests passed.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void xhci_set_hc_event_deq(struct xhci_hcd *xhci)
|
||||
static void xhci_set_hc_event_deq(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
|
||||
{
|
||||
u64 temp;
|
||||
dma_addr_t deq;
|
||||
|
||||
deq = xhci_trb_virt_to_dma(xhci->event_ring->deq_seg,
|
||||
xhci->event_ring->dequeue);
|
||||
deq = xhci_trb_virt_to_dma(ir->event_ring->deq_seg,
|
||||
ir->event_ring->dequeue);
|
||||
if (!deq)
|
||||
xhci_warn(xhci, "WARN something wrong with SW event ring "
|
||||
"dequeue ptr.\n");
|
||||
/* Update HC event ring dequeue pointer */
|
||||
temp = xhci_read_64(xhci, &xhci->ir_set->erst_dequeue);
|
||||
temp = xhci_read_64(xhci, &ir->ir_set->erst_dequeue);
|
||||
temp &= ERST_PTR_MASK;
|
||||
/* Don't clear the EHB bit (which is RW1C) because
|
||||
* there might be more events to service.
|
||||
|
@ -2108,7 +1973,7 @@ static void xhci_set_hc_event_deq(struct xhci_hcd *xhci)
|
|||
"// Write event ring dequeue pointer, "
|
||||
"preserving EHB bit");
|
||||
xhci_write_64(xhci, ((u64) deq & (u64) ~ERST_PTR_MASK) | temp,
|
||||
&xhci->ir_set->erst_dequeue);
|
||||
&ir->ir_set->erst_dequeue);
|
||||
}
|
||||
|
||||
static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports,
|
||||
|
@ -2289,6 +2154,9 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags)
|
|||
xhci->hw_ports[i].addr = &xhci->op_regs->port_status_base +
|
||||
NUM_PORT_REGS * i;
|
||||
xhci->hw_ports[i].hw_portnum = i;
|
||||
|
||||
init_completion(&xhci->hw_ports[i].rexit_done);
|
||||
init_completion(&xhci->hw_ports[i].u3exit_done);
|
||||
}
|
||||
|
||||
xhci->rh_bw = kcalloc_node(num_ports, sizeof(*xhci->rh_bw), flags,
|
||||
|
@ -2375,6 +2243,68 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct xhci_interrupter *
|
||||
xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int intr_num, gfp_t flags)
|
||||
{
|
||||
struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
|
||||
struct xhci_interrupter *ir;
|
||||
u64 erst_base;
|
||||
u32 erst_size;
|
||||
int ret;
|
||||
|
||||
if (intr_num > xhci->max_interrupters) {
|
||||
xhci_warn(xhci, "Can't allocate interrupter %d, max interrupters %d\n",
|
||||
intr_num, xhci->max_interrupters);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (xhci->interrupter) {
|
||||
xhci_warn(xhci, "Can't allocate already set up interrupter %d\n", intr_num);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ir = kzalloc_node(sizeof(*ir), flags, dev_to_node(dev));
|
||||
if (!ir)
|
||||
return NULL;
|
||||
|
||||
ir->ir_set = &xhci->run_regs->ir_set[intr_num];
|
||||
ir->event_ring = xhci_ring_alloc(xhci, ERST_NUM_SEGS, 1, TYPE_EVENT,
|
||||
0, flags);
|
||||
if (!ir->event_ring) {
|
||||
xhci_warn(xhci, "Failed to allocate interrupter %d event ring\n", intr_num);
|
||||
goto fail_ir;
|
||||
}
|
||||
|
||||
ret = xhci_alloc_erst(xhci, ir->event_ring, &ir->erst, flags);
|
||||
if (ret) {
|
||||
xhci_warn(xhci, "Failed to allocate interrupter %d erst\n", intr_num);
|
||||
goto fail_ev;
|
||||
|
||||
}
|
||||
/* set ERST count with the number of entries in the segment table */
|
||||
erst_size = readl(&ir->ir_set->erst_size);
|
||||
erst_size &= ERST_SIZE_MASK;
|
||||
erst_size |= ERST_NUM_SEGS;
|
||||
writel(erst_size, &ir->ir_set->erst_size);
|
||||
|
||||
erst_base = xhci_read_64(xhci, &ir->ir_set->erst_base);
|
||||
erst_base &= ERST_PTR_MASK;
|
||||
erst_base |= (ir->erst.erst_dma_addr & (u64) ~ERST_PTR_MASK);
|
||||
xhci_write_64(xhci, erst_base, &ir->ir_set->erst_base);
|
||||
|
||||
/* Set the event ring dequeue address of this interrupter */
|
||||
xhci_set_hc_event_deq(xhci, ir);
|
||||
|
||||
return ir;
|
||||
|
||||
fail_ev:
|
||||
xhci_ring_free(xhci, ir->event_ring);
|
||||
fail_ir:
|
||||
kfree(ir);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
|
||||
{
|
||||
dma_addr_t dma;
|
||||
|
@ -2382,7 +2312,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
|
|||
unsigned int val, val2;
|
||||
u64 val_64;
|
||||
u32 page_size, temp;
|
||||
int i, ret;
|
||||
int i;
|
||||
|
||||
INIT_LIST_HEAD(&xhci->cmd_list);
|
||||
|
||||
|
@ -2495,48 +2425,13 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
|
|||
" from cap regs base addr", val);
|
||||
xhci->dba = (void __iomem *) xhci->cap_regs + val;
|
||||
/* Set ir_set to interrupt register set 0 */
|
||||
xhci->ir_set = &xhci->run_regs->ir_set[0];
|
||||
|
||||
/*
|
||||
* Event ring setup: Allocate a normal ring, but also setup
|
||||
* the event ring segment table (ERST). Section 4.9.3.
|
||||
*/
|
||||
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "// Allocating event ring");
|
||||
xhci->event_ring = xhci_ring_alloc(xhci, ERST_NUM_SEGS, 1, TYPE_EVENT,
|
||||
0, flags);
|
||||
if (!xhci->event_ring)
|
||||
/* allocate and set up primary interrupter with an event ring. */
|
||||
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
|
||||
"Allocating primary event ring");
|
||||
xhci->interrupter = xhci_alloc_interrupter(xhci, 0, flags);
|
||||
if (!xhci->interrupter)
|
||||
goto fail;
|
||||
if (xhci_check_trb_in_td_math(xhci) < 0)
|
||||
goto fail;
|
||||
|
||||
ret = xhci_alloc_erst(xhci, xhci->event_ring, &xhci->erst, flags);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
/* set ERST count with the number of entries in the segment table */
|
||||
val = readl(&xhci->ir_set->erst_size);
|
||||
val &= ERST_SIZE_MASK;
|
||||
val |= ERST_NUM_SEGS;
|
||||
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
|
||||
"// Write ERST size = %i to ir_set 0 (some bits preserved)",
|
||||
val);
|
||||
writel(val, &xhci->ir_set->erst_size);
|
||||
|
||||
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
|
||||
"// Set ERST entries to point to event ring.");
|
||||
/* set the segment table base address */
|
||||
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
|
||||
"// Set ERST base address for ir_set 0 = 0x%llx",
|
||||
(unsigned long long)xhci->erst.erst_dma_addr);
|
||||
val_64 = xhci_read_64(xhci, &xhci->ir_set->erst_base);
|
||||
val_64 &= ERST_PTR_MASK;
|
||||
val_64 |= (xhci->erst.erst_dma_addr & (u64) ~ERST_PTR_MASK);
|
||||
xhci_write_64(xhci, val_64, &xhci->ir_set->erst_base);
|
||||
|
||||
/* Set the event ring dequeue address */
|
||||
xhci_set_hc_event_deq(xhci);
|
||||
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
|
||||
"Wrote ERST address to ir_set 0.");
|
||||
|
||||
xhci->isoc_bei_interval = AVOID_BEI_INTERVAL_MAX;
|
||||
|
||||
|
@ -2547,13 +2442,6 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
|
|||
*/
|
||||
for (i = 0; i < MAX_HC_SLOTS; i++)
|
||||
xhci->devs[i] = NULL;
|
||||
for (i = 0; i < USB_MAXCHILDREN; i++) {
|
||||
xhci->usb2_rhub.bus_state.resume_done[i] = 0;
|
||||
xhci->usb3_rhub.bus_state.resume_done[i] = 0;
|
||||
/* Only the USB 2.0 completions will ever be used. */
|
||||
init_completion(&xhci->usb2_rhub.bus_state.rexit_done[i]);
|
||||
init_completion(&xhci->usb3_rhub.bus_state.u3exit_done[i]);
|
||||
}
|
||||
|
||||
if (scratchpad_alloc(xhci, flags))
|
||||
goto fail;
|
||||
|
|
|
@ -32,7 +32,7 @@ static void xhci_mvebu_mbus_config(void __iomem *base,
|
|||
|
||||
/* Program each DRAM CS in a seperate window */
|
||||
for (win = 0; win < dram->num_cs; win++) {
|
||||
const struct mbus_dram_window *cs = dram->cs + win;
|
||||
const struct mbus_dram_window *cs = &dram->cs[win];
|
||||
|
||||
writel(((cs->size - 1) & 0xffff0000) | (cs->mbus_attr << 8) |
|
||||
(dram->mbus_dram_target_id << 4) | 1,
|
||||
|
|
|
@ -771,12 +771,11 @@ static struct pci_driver xhci_pci_driver = {
|
|||
/* suspend and resume implemented later */
|
||||
|
||||
.shutdown = usb_hcd_pci_shutdown,
|
||||
.driver = {
|
||||
#ifdef CONFIG_PM
|
||||
.pm = &usb_hcd_pci_pm_ops,
|
||||
#endif
|
||||
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
||||
.driver = {
|
||||
.pm = &usb_hcd_pci_pm_ops
|
||||
},
|
||||
#endif
|
||||
};
|
||||
|
||||
static int __init xhci_pci_init(void)
|
||||
|
|
|
@ -19,11 +19,11 @@
|
|||
#include <linux/slab.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/usb/of.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#include "xhci.h"
|
||||
#include "xhci-plat.h"
|
||||
#include "xhci-mvebu.h"
|
||||
#include "xhci-rcar.h"
|
||||
|
||||
static struct hc_driver __read_mostly xhci_plat_hc_driver;
|
||||
|
||||
|
@ -114,14 +114,6 @@ static const struct xhci_plat_priv xhci_plat_marvell_armada3700 = {
|
|||
.init_quirk = xhci_mvebu_a3700_init_quirk,
|
||||
};
|
||||
|
||||
static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen2 = {
|
||||
SET_XHCI_PLAT_PRIV_FOR_RCAR(XHCI_RCAR_FIRMWARE_NAME_V1)
|
||||
};
|
||||
|
||||
static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen3 = {
|
||||
SET_XHCI_PLAT_PRIV_FOR_RCAR(XHCI_RCAR_FIRMWARE_NAME_V3)
|
||||
};
|
||||
|
||||
static const struct xhci_plat_priv xhci_plat_brcm = {
|
||||
.quirks = XHCI_RESET_ON_RESUME | XHCI_SUSPEND_RESUME_CLKS,
|
||||
};
|
||||
|
@ -140,27 +132,6 @@ static const struct of_device_id usb_xhci_of_match[] = {
|
|||
}, {
|
||||
.compatible = "marvell,armada3700-xhci",
|
||||
.data = &xhci_plat_marvell_armada3700,
|
||||
}, {
|
||||
.compatible = "renesas,xhci-r8a7790",
|
||||
.data = &xhci_plat_renesas_rcar_gen2,
|
||||
}, {
|
||||
.compatible = "renesas,xhci-r8a7791",
|
||||
.data = &xhci_plat_renesas_rcar_gen2,
|
||||
}, {
|
||||
.compatible = "renesas,xhci-r8a7793",
|
||||
.data = &xhci_plat_renesas_rcar_gen2,
|
||||
}, {
|
||||
.compatible = "renesas,xhci-r8a7795",
|
||||
.data = &xhci_plat_renesas_rcar_gen3,
|
||||
}, {
|
||||
.compatible = "renesas,xhci-r8a7796",
|
||||
.data = &xhci_plat_renesas_rcar_gen3,
|
||||
}, {
|
||||
.compatible = "renesas,rcar-gen2-xhci",
|
||||
.data = &xhci_plat_renesas_rcar_gen2,
|
||||
}, {
|
||||
.compatible = "renesas,rcar-gen3-xhci",
|
||||
.data = &xhci_plat_renesas_rcar_gen3,
|
||||
}, {
|
||||
.compatible = "brcm,xhci-brcm-v2",
|
||||
.data = &xhci_plat_brcm,
|
||||
|
@ -173,11 +144,10 @@ static const struct of_device_id usb_xhci_of_match[] = {
|
|||
MODULE_DEVICE_TABLE(of, usb_xhci_of_match);
|
||||
#endif
|
||||
|
||||
static int xhci_plat_probe(struct platform_device *pdev)
|
||||
int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, const struct xhci_plat_priv *priv_match)
|
||||
{
|
||||
const struct xhci_plat_priv *priv_match;
|
||||
const struct hc_driver *driver;
|
||||
struct device *sysdev, *tmpdev;
|
||||
struct device *tmpdev;
|
||||
struct xhci_hcd *xhci;
|
||||
struct resource *res;
|
||||
struct usb_hcd *hcd, *usb3_hcd;
|
||||
|
@ -195,31 +165,10 @@ static int xhci_plat_probe(struct platform_device *pdev)
|
|||
if (irq < 0)
|
||||
return irq;
|
||||
|
||||
/*
|
||||
* sysdev must point to a device that is known to the system firmware
|
||||
* or PCI hardware. We handle these three cases here:
|
||||
* 1. xhci_plat comes from firmware
|
||||
* 2. xhci_plat is child of a device from firmware (dwc3-plat)
|
||||
* 3. xhci_plat is grandchild of a pci device (dwc3-pci)
|
||||
*/
|
||||
for (sysdev = &pdev->dev; sysdev; sysdev = sysdev->parent) {
|
||||
if (is_of_node(sysdev->fwnode) ||
|
||||
is_acpi_device_node(sysdev->fwnode))
|
||||
break;
|
||||
#ifdef CONFIG_PCI
|
||||
else if (sysdev->bus == &pci_bus_type)
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!sysdev)
|
||||
sysdev = &pdev->dev;
|
||||
|
||||
if (WARN_ON(!sysdev->dma_mask))
|
||||
/* Platform did not initialize dma_mask */
|
||||
ret = dma_coerce_mask_and_coherent(sysdev, DMA_BIT_MASK(64));
|
||||
else
|
||||
ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(64));
|
||||
ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(64));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
@ -257,25 +206,30 @@ static int xhci_plat_probe(struct platform_device *pdev)
|
|||
goto put_hcd;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(xhci->reg_clk);
|
||||
if (ret)
|
||||
goto put_hcd;
|
||||
|
||||
xhci->clk = devm_clk_get_optional(&pdev->dev, NULL);
|
||||
if (IS_ERR(xhci->clk)) {
|
||||
ret = PTR_ERR(xhci->clk);
|
||||
goto disable_reg_clk;
|
||||
goto put_hcd;
|
||||
}
|
||||
|
||||
xhci->reset = devm_reset_control_array_get_optional_shared(&pdev->dev);
|
||||
if (IS_ERR(xhci->reset)) {
|
||||
ret = PTR_ERR(xhci->reset);
|
||||
goto put_hcd;
|
||||
}
|
||||
|
||||
ret = reset_control_deassert(xhci->reset);
|
||||
if (ret)
|
||||
goto put_hcd;
|
||||
|
||||
ret = clk_prepare_enable(xhci->reg_clk);
|
||||
if (ret)
|
||||
goto err_reset;
|
||||
|
||||
ret = clk_prepare_enable(xhci->clk);
|
||||
if (ret)
|
||||
goto disable_reg_clk;
|
||||
|
||||
if (pdev->dev.of_node)
|
||||
priv_match = of_device_get_match_data(&pdev->dev);
|
||||
else
|
||||
priv_match = dev_get_platdata(&pdev->dev);
|
||||
|
||||
if (priv_match) {
|
||||
priv = hcd_to_xhci_priv(hcd);
|
||||
/* Just copy data for now */
|
||||
|
@ -377,6 +331,9 @@ disable_clk:
|
|||
disable_reg_clk:
|
||||
clk_disable_unprepare(xhci->reg_clk);
|
||||
|
||||
err_reset:
|
||||
reset_control_assert(xhci->reset);
|
||||
|
||||
put_hcd:
|
||||
usb_put_hcd(hcd);
|
||||
|
||||
|
@ -386,8 +343,50 @@ disable_runtime:
|
|||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(xhci_plat_probe);
|
||||
|
||||
static int xhci_plat_remove(struct platform_device *dev)
|
||||
static int xhci_generic_plat_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct xhci_plat_priv *priv_match;
|
||||
struct device *sysdev;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* sysdev must point to a device that is known to the system firmware
|
||||
* or PCI hardware. We handle these three cases here:
|
||||
* 1. xhci_plat comes from firmware
|
||||
* 2. xhci_plat is child of a device from firmware (dwc3-plat)
|
||||
* 3. xhci_plat is grandchild of a pci device (dwc3-pci)
|
||||
*/
|
||||
for (sysdev = &pdev->dev; sysdev; sysdev = sysdev->parent) {
|
||||
if (is_of_node(sysdev->fwnode) ||
|
||||
is_acpi_device_node(sysdev->fwnode))
|
||||
break;
|
||||
#ifdef CONFIG_PCI
|
||||
else if (sysdev->bus == &pci_bus_type)
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!sysdev)
|
||||
sysdev = &pdev->dev;
|
||||
|
||||
if (WARN_ON(!sysdev->dma_mask)) {
|
||||
/* Platform did not initialize dma_mask */
|
||||
ret = dma_coerce_mask_and_coherent(sysdev, DMA_BIT_MASK(64));
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (pdev->dev.of_node)
|
||||
priv_match = of_device_get_match_data(&pdev->dev);
|
||||
else
|
||||
priv_match = dev_get_platdata(&pdev->dev);
|
||||
|
||||
return xhci_plat_probe(pdev, sysdev, priv_match);
|
||||
}
|
||||
|
||||
int xhci_plat_remove(struct platform_device *dev)
|
||||
{
|
||||
struct usb_hcd *hcd = platform_get_drvdata(dev);
|
||||
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
|
||||
|
@ -412,6 +411,7 @@ static int xhci_plat_remove(struct platform_device *dev)
|
|||
|
||||
clk_disable_unprepare(clk);
|
||||
clk_disable_unprepare(reg_clk);
|
||||
reset_control_assert(xhci->reset);
|
||||
usb_put_hcd(hcd);
|
||||
|
||||
pm_runtime_disable(&dev->dev);
|
||||
|
@ -420,6 +420,7 @@ static int xhci_plat_remove(struct platform_device *dev)
|
|||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(xhci_plat_remove);
|
||||
|
||||
static int __maybe_unused xhci_plat_suspend(struct device *dev)
|
||||
{
|
||||
|
@ -496,13 +497,14 @@ static int __maybe_unused xhci_plat_runtime_resume(struct device *dev)
|
|||
return xhci_resume(xhci, 0);
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops xhci_plat_pm_ops = {
|
||||
const struct dev_pm_ops xhci_plat_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(xhci_plat_suspend, xhci_plat_resume)
|
||||
|
||||
SET_RUNTIME_PM_OPS(xhci_plat_runtime_suspend,
|
||||
xhci_plat_runtime_resume,
|
||||
NULL)
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(xhci_plat_pm_ops);
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
static const struct acpi_device_id usb_xhci_acpi_match[] = {
|
||||
|
@ -513,8 +515,8 @@ static const struct acpi_device_id usb_xhci_acpi_match[] = {
|
|||
MODULE_DEVICE_TABLE(acpi, usb_xhci_acpi_match);
|
||||
#endif
|
||||
|
||||
static struct platform_driver usb_xhci_driver = {
|
||||
.probe = xhci_plat_probe,
|
||||
static struct platform_driver usb_generic_xhci_driver = {
|
||||
.probe = xhci_generic_plat_probe,
|
||||
.remove = xhci_plat_remove,
|
||||
.shutdown = usb_hcd_platform_shutdown,
|
||||
.driver = {
|
||||
|
@ -529,13 +531,13 @@ MODULE_ALIAS("platform:xhci-hcd");
|
|||
static int __init xhci_plat_init(void)
|
||||
{
|
||||
xhci_init_driver(&xhci_plat_hc_driver, &xhci_plat_overrides);
|
||||
return platform_driver_register(&usb_xhci_driver);
|
||||
return platform_driver_register(&usb_generic_xhci_driver);
|
||||
}
|
||||
module_init(xhci_plat_init);
|
||||
|
||||
static void __exit xhci_plat_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&usb_xhci_driver);
|
||||
platform_driver_unregister(&usb_generic_xhci_driver);
|
||||
}
|
||||
module_exit(xhci_plat_exit);
|
||||
|
||||
|
|
|
@ -21,4 +21,11 @@ struct xhci_plat_priv {
|
|||
|
||||
#define hcd_to_xhci_priv(h) ((struct xhci_plat_priv *)hcd_to_xhci(h)->priv)
|
||||
#define xhci_to_priv(x) ((struct xhci_plat_priv *)(x)->priv)
|
||||
|
||||
int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev,
|
||||
const struct xhci_plat_priv *priv_match);
|
||||
|
||||
int xhci_plat_remove(struct platform_device *dev);
|
||||
extern const struct dev_pm_ops xhci_plat_pm_ops;
|
||||
|
||||
#endif /* _XHCI_PLAT_H */
|
||||
|
|
|
@ -10,12 +10,16 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/usb/phy.h>
|
||||
#include <linux/sys_soc.h>
|
||||
|
||||
#include "xhci.h"
|
||||
#include "xhci-plat.h"
|
||||
#include "xhci-rcar.h"
|
||||
|
||||
#define XHCI_RCAR_FIRMWARE_NAME_V1 "r8a779x_usb3_v1.dlmem"
|
||||
#define XHCI_RCAR_FIRMWARE_NAME_V2 "r8a779x_usb3_v2.dlmem"
|
||||
#define XHCI_RCAR_FIRMWARE_NAME_V3 "r8a779x_usb3_v3.dlmem"
|
||||
|
||||
/*
|
||||
* - The V3 firmware is for almost all R-Car Gen3 (except r8a7795 ES1.x)
|
||||
|
@ -108,7 +112,7 @@ static int xhci_rcar_is_gen2(struct device *dev)
|
|||
of_device_is_compatible(node, "renesas,rcar-gen2-xhci");
|
||||
}
|
||||
|
||||
void xhci_rcar_start(struct usb_hcd *hcd)
|
||||
static void xhci_rcar_start(struct usb_hcd *hcd)
|
||||
{
|
||||
u32 temp;
|
||||
|
||||
|
@ -203,7 +207,7 @@ static bool xhci_rcar_wait_for_pll_active(struct usb_hcd *hcd)
|
|||
}
|
||||
|
||||
/* This function needs to initialize a "phy" of usb before */
|
||||
int xhci_rcar_init_quirk(struct usb_hcd *hcd)
|
||||
static int xhci_rcar_init_quirk(struct usb_hcd *hcd)
|
||||
{
|
||||
/* If hcd->regs is NULL, we don't just call the following function */
|
||||
if (!hcd->regs)
|
||||
|
@ -215,7 +219,7 @@ int xhci_rcar_init_quirk(struct usb_hcd *hcd)
|
|||
return xhci_rcar_download_firmware(hcd);
|
||||
}
|
||||
|
||||
int xhci_rcar_resume_quirk(struct usb_hcd *hcd)
|
||||
static int xhci_rcar_resume_quirk(struct usb_hcd *hcd)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -225,3 +229,82 @@ int xhci_rcar_resume_quirk(struct usb_hcd *hcd)
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* On R-Car Gen2 and Gen3, the AC64 bit (bit 0) of HCCPARAMS1 is set
|
||||
* to 1. However, these SoCs don't support 64-bit address memory
|
||||
* pointers. So, this driver clears the AC64 bit of xhci->hcc_params
|
||||
* to call dma_set_coherent_mask(dev, DMA_BIT_MASK(32)) in
|
||||
* xhci_gen_setup() by using the XHCI_NO_64BIT_SUPPORT quirk.
|
||||
*
|
||||
* And, since the firmware/internal CPU control the USBSTS.STS_HALT
|
||||
* and the process speed is down when the roothub port enters U3,
|
||||
* long delay for the handshake of STS_HALT is neeed in xhci_suspend()
|
||||
* by using the XHCI_SLOW_SUSPEND quirk.
|
||||
*/
|
||||
#define SET_XHCI_PLAT_PRIV_FOR_RCAR(firmware) \
|
||||
.firmware_name = firmware, \
|
||||
.quirks = XHCI_NO_64BIT_SUPPORT | XHCI_TRUST_TX_LENGTH | \
|
||||
XHCI_SLOW_SUSPEND, \
|
||||
.init_quirk = xhci_rcar_init_quirk, \
|
||||
.plat_start = xhci_rcar_start, \
|
||||
.resume_quirk = xhci_rcar_resume_quirk,
|
||||
|
||||
static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen2 = {
|
||||
SET_XHCI_PLAT_PRIV_FOR_RCAR(XHCI_RCAR_FIRMWARE_NAME_V1)
|
||||
};
|
||||
|
||||
static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen3 = {
|
||||
SET_XHCI_PLAT_PRIV_FOR_RCAR(XHCI_RCAR_FIRMWARE_NAME_V3)
|
||||
};
|
||||
|
||||
static const struct of_device_id usb_xhci_of_match[] = {
|
||||
{
|
||||
.compatible = "renesas,xhci-r8a7790",
|
||||
.data = &xhci_plat_renesas_rcar_gen2,
|
||||
}, {
|
||||
.compatible = "renesas,xhci-r8a7791",
|
||||
.data = &xhci_plat_renesas_rcar_gen2,
|
||||
}, {
|
||||
.compatible = "renesas,xhci-r8a7793",
|
||||
.data = &xhci_plat_renesas_rcar_gen2,
|
||||
}, {
|
||||
.compatible = "renesas,xhci-r8a7795",
|
||||
.data = &xhci_plat_renesas_rcar_gen3,
|
||||
}, {
|
||||
.compatible = "renesas,xhci-r8a7796",
|
||||
.data = &xhci_plat_renesas_rcar_gen3,
|
||||
}, {
|
||||
.compatible = "renesas,rcar-gen2-xhci",
|
||||
.data = &xhci_plat_renesas_rcar_gen2,
|
||||
}, {
|
||||
.compatible = "renesas,rcar-gen3-xhci",
|
||||
.data = &xhci_plat_renesas_rcar_gen3,
|
||||
},
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, usb_xhci_of_match);
|
||||
|
||||
static int xhci_renesas_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct xhci_plat_priv *priv_match;
|
||||
|
||||
priv_match = of_device_get_match_data(&pdev->dev);
|
||||
|
||||
return xhci_plat_probe(pdev, NULL, priv_match);
|
||||
}
|
||||
|
||||
static struct platform_driver usb_xhci_renesas_driver = {
|
||||
.probe = xhci_renesas_probe,
|
||||
.remove = xhci_plat_remove,
|
||||
.shutdown = usb_hcd_platform_shutdown,
|
||||
.driver = {
|
||||
.name = "xhci-renesas-hcd",
|
||||
.pm = &xhci_plat_pm_ops,
|
||||
.of_match_table = of_match_ptr(usb_xhci_of_match),
|
||||
},
|
||||
};
|
||||
module_platform_driver(usb_xhci_renesas_driver);
|
||||
|
||||
MODULE_DESCRIPTION("xHCI Platform Host Controller Driver for Renesas R-Car");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* drivers/usb/host/xhci-rcar.h
|
||||
*
|
||||
* Copyright (C) 2014 Renesas Electronics Corporation
|
||||
*/
|
||||
|
||||
#ifndef _XHCI_RCAR_H
|
||||
#define _XHCI_RCAR_H
|
||||
|
||||
#define XHCI_RCAR_FIRMWARE_NAME_V1 "r8a779x_usb3_v1.dlmem"
|
||||
#define XHCI_RCAR_FIRMWARE_NAME_V2 "r8a779x_usb3_v2.dlmem"
|
||||
#define XHCI_RCAR_FIRMWARE_NAME_V3 "r8a779x_usb3_v3.dlmem"
|
||||
|
||||
#if IS_ENABLED(CONFIG_USB_XHCI_RCAR)
|
||||
void xhci_rcar_start(struct usb_hcd *hcd);
|
||||
int xhci_rcar_init_quirk(struct usb_hcd *hcd);
|
||||
int xhci_rcar_resume_quirk(struct usb_hcd *hcd);
|
||||
#else
|
||||
static inline void xhci_rcar_start(struct usb_hcd *hcd)
|
||||
{
|
||||
}
|
||||
|
||||
static inline int xhci_rcar_init_quirk(struct usb_hcd *hcd)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int xhci_rcar_resume_quirk(struct usb_hcd *hcd)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* On R-Car Gen2 and Gen3, the AC64 bit (bit 0) of HCCPARAMS1 is set
|
||||
* to 1. However, these SoCs don't support 64-bit address memory
|
||||
* pointers. So, this driver clears the AC64 bit of xhci->hcc_params
|
||||
* to call dma_set_coherent_mask(dev, DMA_BIT_MASK(32)) in
|
||||
* xhci_gen_setup() by using the XHCI_NO_64BIT_SUPPORT quirk.
|
||||
*
|
||||
* And, since the firmware/internal CPU control the USBSTS.STS_HALT
|
||||
* and the process speed is down when the roothub port enters U3,
|
||||
* long delay for the handshake of STS_HALT is neeed in xhci_suspend()
|
||||
* by using the XHCI_SLOW_SUSPEND quirk.
|
||||
*/
|
||||
#define SET_XHCI_PLAT_PRIV_FOR_RCAR(firmware) \
|
||||
.firmware_name = firmware, \
|
||||
.quirks = XHCI_NO_64BIT_SUPPORT | XHCI_TRUST_TX_LENGTH | \
|
||||
XHCI_SLOW_SUSPEND, \
|
||||
.init_quirk = xhci_rcar_init_quirk, \
|
||||
.plat_start = xhci_rcar_start, \
|
||||
.resume_quirk = xhci_rcar_resume_quirk,
|
||||
|
||||
#endif /* _XHCI_RCAR_H */
|
|
@ -1833,7 +1833,8 @@ static void xhci_cavium_reset_phy_quirk(struct xhci_hcd *xhci)
|
|||
}
|
||||
|
||||
static void handle_port_status(struct xhci_hcd *xhci,
|
||||
union xhci_trb *event)
|
||||
struct xhci_interrupter *ir,
|
||||
union xhci_trb *event)
|
||||
{
|
||||
struct usb_hcd *hcd;
|
||||
u32 port_id;
|
||||
|
@ -1856,7 +1857,7 @@ static void handle_port_status(struct xhci_hcd *xhci,
|
|||
if ((port_id <= 0) || (port_id > max_ports)) {
|
||||
xhci_warn(xhci, "Port change event with invalid port ID %d\n",
|
||||
port_id);
|
||||
inc_deq(xhci, xhci->event_ring);
|
||||
inc_deq(xhci, ir->event_ring);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1923,7 +1924,7 @@ static void handle_port_status(struct xhci_hcd *xhci,
|
|||
goto cleanup;
|
||||
} else if (!test_bit(hcd_portnum, &bus_state->resuming_ports)) {
|
||||
xhci_dbg(xhci, "resume HS port %d\n", port_id);
|
||||
bus_state->resume_done[hcd_portnum] = jiffies +
|
||||
port->resume_timestamp = jiffies +
|
||||
msecs_to_jiffies(USB_RESUME_TIMEOUT);
|
||||
set_bit(hcd_portnum, &bus_state->resuming_ports);
|
||||
/* Do the rest in GetPortStatus after resume time delay.
|
||||
|
@ -1932,7 +1933,7 @@ static void handle_port_status(struct xhci_hcd *xhci,
|
|||
*/
|
||||
set_bit(HCD_FLAG_POLL_RH, &hcd->flags);
|
||||
mod_timer(&hcd->rh_timer,
|
||||
bus_state->resume_done[hcd_portnum]);
|
||||
port->resume_timestamp);
|
||||
usb_hcd_start_port_resume(&hcd->self, hcd_portnum);
|
||||
bogus_port_status = true;
|
||||
}
|
||||
|
@ -1944,7 +1945,7 @@ static void handle_port_status(struct xhci_hcd *xhci,
|
|||
(portsc & PORT_PLS_MASK) == XDEV_U1 ||
|
||||
(portsc & PORT_PLS_MASK) == XDEV_U2)) {
|
||||
xhci_dbg(xhci, "resume SS port %d finished\n", port_id);
|
||||
complete(&bus_state->u3exit_done[hcd_portnum]);
|
||||
complete(&port->u3exit_done);
|
||||
/* We've just brought the device into U0/1/2 through either the
|
||||
* Resume state after a device remote wakeup, or through the
|
||||
* U3Exit state after a host-initiated resume. If it's a device
|
||||
|
@ -1969,10 +1970,9 @@ static void handle_port_status(struct xhci_hcd *xhci,
|
|||
* RExit to a disconnect state). If so, let the driver know it's
|
||||
* out of the RExit state.
|
||||
*/
|
||||
if (!DEV_SUPERSPEED_ANY(portsc) && hcd->speed < HCD_USB3 &&
|
||||
test_and_clear_bit(hcd_portnum,
|
||||
&bus_state->rexit_ports)) {
|
||||
complete(&bus_state->rexit_done[hcd_portnum]);
|
||||
if (hcd->speed < HCD_USB3 && port->rexit_active) {
|
||||
complete(&port->rexit_done);
|
||||
port->rexit_active = false;
|
||||
bogus_port_status = true;
|
||||
goto cleanup;
|
||||
}
|
||||
|
@ -1986,7 +1986,7 @@ static void handle_port_status(struct xhci_hcd *xhci,
|
|||
|
||||
cleanup:
|
||||
/* Update event ring dequeue pointer before dropping the lock */
|
||||
inc_deq(xhci, xhci->event_ring);
|
||||
inc_deq(xhci, ir->event_ring);
|
||||
|
||||
/* Don't make the USB core poll the roothub if we got a bad port status
|
||||
* change event. Besides, at that point we can't tell which roothub
|
||||
|
@ -2519,7 +2519,8 @@ finish_td:
|
|||
* At this point, the host controller is probably hosed and should be reset.
|
||||
*/
|
||||
static int handle_tx_event(struct xhci_hcd *xhci,
|
||||
struct xhci_transfer_event *event)
|
||||
struct xhci_interrupter *ir,
|
||||
struct xhci_transfer_event *event)
|
||||
{
|
||||
struct xhci_virt_ep *ep;
|
||||
struct xhci_ring *ep_ring;
|
||||
|
@ -2531,7 +2532,6 @@ static int handle_tx_event(struct xhci_hcd *xhci,
|
|||
union xhci_trb *ep_trb;
|
||||
int status = -EINPROGRESS;
|
||||
struct xhci_ep_ctx *ep_ctx;
|
||||
struct list_head *tmp;
|
||||
u32 trb_comp_code;
|
||||
int td_num = 0;
|
||||
bool handling_skipped_tds = false;
|
||||
|
@ -2585,10 +2585,8 @@ static int handle_tx_event(struct xhci_hcd *xhci,
|
|||
}
|
||||
|
||||
/* Count current td numbers if ep->skip is set */
|
||||
if (ep->skip) {
|
||||
list_for_each(tmp, &ep_ring->td_list)
|
||||
td_num++;
|
||||
}
|
||||
if (ep->skip)
|
||||
td_num += list_count_nodes(&ep_ring->td_list);
|
||||
|
||||
/* Look for common error cases */
|
||||
switch (trb_comp_code) {
|
||||
|
@ -2871,7 +2869,7 @@ cleanup:
|
|||
* processing missed tds.
|
||||
*/
|
||||
if (!handling_skipped_tds)
|
||||
inc_deq(xhci, xhci->event_ring);
|
||||
inc_deq(xhci, ir->event_ring);
|
||||
|
||||
/*
|
||||
* If ep->skip is set, it means there are missed tds on the
|
||||
|
@ -2886,8 +2884,8 @@ cleanup:
|
|||
err_out:
|
||||
xhci_err(xhci, "@%016llx %08x %08x %08x %08x\n",
|
||||
(unsigned long long) xhci_trb_virt_to_dma(
|
||||
xhci->event_ring->deq_seg,
|
||||
xhci->event_ring->dequeue),
|
||||
ir->event_ring->deq_seg,
|
||||
ir->event_ring->dequeue),
|
||||
lower_32_bits(le64_to_cpu(event->buffer)),
|
||||
upper_32_bits(le64_to_cpu(event->buffer)),
|
||||
le32_to_cpu(event->transfer_len),
|
||||
|
@ -2901,7 +2899,7 @@ err_out:
|
|||
* Returns >0 for "possibly more events to process" (caller should call again),
|
||||
* otherwise 0 if done. In future, <0 returns should indicate error code.
|
||||
*/
|
||||
static int xhci_handle_event(struct xhci_hcd *xhci)
|
||||
static int xhci_handle_event(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
|
||||
{
|
||||
union xhci_trb *event;
|
||||
int update_ptrs = 1;
|
||||
|
@ -2909,18 +2907,18 @@ static int xhci_handle_event(struct xhci_hcd *xhci)
|
|||
int ret;
|
||||
|
||||
/* Event ring hasn't been allocated yet. */
|
||||
if (!xhci->event_ring || !xhci->event_ring->dequeue) {
|
||||
xhci_err(xhci, "ERROR event ring not ready\n");
|
||||
if (!ir || !ir->event_ring || !ir->event_ring->dequeue) {
|
||||
xhci_err(xhci, "ERROR interrupter not ready\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
event = xhci->event_ring->dequeue;
|
||||
event = ir->event_ring->dequeue;
|
||||
/* Does the HC or OS own the TRB? */
|
||||
if ((le32_to_cpu(event->event_cmd.flags) & TRB_CYCLE) !=
|
||||
xhci->event_ring->cycle_state)
|
||||
ir->event_ring->cycle_state)
|
||||
return 0;
|
||||
|
||||
trace_xhci_handle_event(xhci->event_ring, &event->generic);
|
||||
trace_xhci_handle_event(ir->event_ring, &event->generic);
|
||||
|
||||
/*
|
||||
* Barrier between reading the TRB_CYCLE (valid) flag above and any
|
||||
|
@ -2935,11 +2933,11 @@ static int xhci_handle_event(struct xhci_hcd *xhci)
|
|||
handle_cmd_completion(xhci, &event->event_cmd);
|
||||
break;
|
||||
case TRB_PORT_STATUS:
|
||||
handle_port_status(xhci, event);
|
||||
handle_port_status(xhci, ir, event);
|
||||
update_ptrs = 0;
|
||||
break;
|
||||
case TRB_TRANSFER:
|
||||
ret = handle_tx_event(xhci, &event->trans_event);
|
||||
ret = handle_tx_event(xhci, ir, &event->trans_event);
|
||||
if (ret >= 0)
|
||||
update_ptrs = 0;
|
||||
break;
|
||||
|
@ -2963,7 +2961,7 @@ static int xhci_handle_event(struct xhci_hcd *xhci)
|
|||
|
||||
if (update_ptrs)
|
||||
/* Update SW event ring dequeue pointer */
|
||||
inc_deq(xhci, xhci->event_ring);
|
||||
inc_deq(xhci, ir->event_ring);
|
||||
|
||||
/* Are there more items on the event ring? Caller will call us again to
|
||||
* check.
|
||||
|
@ -2977,16 +2975,17 @@ static int xhci_handle_event(struct xhci_hcd *xhci)
|
|||
* - To avoid "Event Ring Full Error" condition
|
||||
*/
|
||||
static void xhci_update_erst_dequeue(struct xhci_hcd *xhci,
|
||||
union xhci_trb *event_ring_deq)
|
||||
struct xhci_interrupter *ir,
|
||||
union xhci_trb *event_ring_deq)
|
||||
{
|
||||
u64 temp_64;
|
||||
dma_addr_t deq;
|
||||
|
||||
temp_64 = xhci_read_64(xhci, &xhci->ir_set->erst_dequeue);
|
||||
temp_64 = xhci_read_64(xhci, &ir->ir_set->erst_dequeue);
|
||||
/* If necessary, update the HW's version of the event ring deq ptr. */
|
||||
if (event_ring_deq != xhci->event_ring->dequeue) {
|
||||
deq = xhci_trb_virt_to_dma(xhci->event_ring->deq_seg,
|
||||
xhci->event_ring->dequeue);
|
||||
if (event_ring_deq != ir->event_ring->dequeue) {
|
||||
deq = xhci_trb_virt_to_dma(ir->event_ring->deq_seg,
|
||||
ir->event_ring->dequeue);
|
||||
if (deq == 0)
|
||||
xhci_warn(xhci, "WARN something wrong with SW event ring dequeue ptr\n");
|
||||
/*
|
||||
|
@ -3004,7 +3003,7 @@ static void xhci_update_erst_dequeue(struct xhci_hcd *xhci,
|
|||
|
||||
/* Clear the event handler busy flag (RW1C) */
|
||||
temp_64 |= ERST_EHB;
|
||||
xhci_write_64(xhci, temp_64, &xhci->ir_set->erst_dequeue);
|
||||
xhci_write_64(xhci, temp_64, &ir->ir_set->erst_dequeue);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -3016,6 +3015,7 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd)
|
|||
{
|
||||
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
|
||||
union xhci_trb *event_ring_deq;
|
||||
struct xhci_interrupter *ir;
|
||||
irqreturn_t ret = IRQ_NONE;
|
||||
u64 temp_64;
|
||||
u32 status;
|
||||
|
@ -3053,11 +3053,13 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd)
|
|||
status |= STS_EINT;
|
||||
writel(status, &xhci->op_regs->status);
|
||||
|
||||
/* This is the handler of the primary interrupter */
|
||||
ir = xhci->interrupter;
|
||||
if (!hcd->msi_enabled) {
|
||||
u32 irq_pending;
|
||||
irq_pending = readl(&xhci->ir_set->irq_pending);
|
||||
irq_pending = readl(&ir->ir_set->irq_pending);
|
||||
irq_pending |= IMAN_IP;
|
||||
writel(irq_pending, &xhci->ir_set->irq_pending);
|
||||
writel(irq_pending, &ir->ir_set->irq_pending);
|
||||
}
|
||||
|
||||
if (xhci->xhc_state & XHCI_STATE_DYING ||
|
||||
|
@ -3067,22 +3069,22 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd)
|
|||
/* Clear the event handler busy flag (RW1C);
|
||||
* the event ring should be empty.
|
||||
*/
|
||||
temp_64 = xhci_read_64(xhci, &xhci->ir_set->erst_dequeue);
|
||||
temp_64 = xhci_read_64(xhci, &ir->ir_set->erst_dequeue);
|
||||
xhci_write_64(xhci, temp_64 | ERST_EHB,
|
||||
&xhci->ir_set->erst_dequeue);
|
||||
&ir->ir_set->erst_dequeue);
|
||||
ret = IRQ_HANDLED;
|
||||
goto out;
|
||||
}
|
||||
|
||||
event_ring_deq = xhci->event_ring->dequeue;
|
||||
event_ring_deq = ir->event_ring->dequeue;
|
||||
/* FIXME this should be a delayed service routine
|
||||
* that clears the EHB.
|
||||
*/
|
||||
while (xhci_handle_event(xhci) > 0) {
|
||||
while (xhci_handle_event(xhci, ir) > 0) {
|
||||
if (event_loop++ < TRBS_PER_SEGMENT / 2)
|
||||
continue;
|
||||
xhci_update_erst_dequeue(xhci, event_ring_deq);
|
||||
event_ring_deq = xhci->event_ring->dequeue;
|
||||
xhci_update_erst_dequeue(xhci, ir, event_ring_deq);
|
||||
event_ring_deq = ir->event_ring->dequeue;
|
||||
|
||||
/* ring is half-full, force isoc trbs to interrupt more often */
|
||||
if (xhci->isoc_bei_interval > AVOID_BEI_INTERVAL_MIN)
|
||||
|
@ -3091,7 +3093,7 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd)
|
|||
event_loop = 0;
|
||||
}
|
||||
|
||||
xhci_update_erst_dequeue(xhci, event_ring_deq);
|
||||
xhci_update_erst_dequeue(xhci, ir, event_ring_deq);
|
||||
ret = IRQ_HANDLED;
|
||||
|
||||
out:
|
||||
|
|
|
@ -1360,6 +1360,9 @@ static void tegra_xhci_id_work(struct work_struct *work)
|
|||
|
||||
mutex_unlock(&tegra->lock);
|
||||
|
||||
tegra->otg_usb3_port = tegra_xusb_padctl_get_usb3_companion(tegra->padctl,
|
||||
tegra->otg_usb2_port);
|
||||
|
||||
if (tegra->host_mode) {
|
||||
/* switch to host mode */
|
||||
if (tegra->otg_usb3_port >= 0) {
|
||||
|
@ -1474,9 +1477,6 @@ static int tegra_xhci_id_notify(struct notifier_block *nb,
|
|||
}
|
||||
|
||||
tegra->otg_usb2_port = tegra_xusb_get_usb2_port(tegra, usbphy);
|
||||
tegra->otg_usb3_port = tegra_xusb_padctl_get_usb3_companion(
|
||||
tegra->padctl,
|
||||
tegra->otg_usb2_port);
|
||||
|
||||
tegra->host_mode = (usbphy->last_event == USB_EVENT_ID) ? true : false;
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
*/
|
||||
|
||||
#include <linux/pci.h>
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/log2.h>
|
||||
|
@ -228,6 +229,7 @@ int xhci_reset(struct xhci_hcd *xhci, u64 timeout_us)
|
|||
static void xhci_zero_64b_regs(struct xhci_hcd *xhci)
|
||||
{
|
||||
struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
|
||||
struct iommu_domain *domain;
|
||||
int err, i;
|
||||
u64 val;
|
||||
u32 intrs;
|
||||
|
@ -246,7 +248,9 @@ static void xhci_zero_64b_regs(struct xhci_hcd *xhci)
|
|||
* an iommu. Doing anything when there is no iommu is definitely
|
||||
* unsafe...
|
||||
*/
|
||||
if (!(xhci->quirks & XHCI_ZERO_64B_REGS) || !device_iommu_mapped(dev))
|
||||
domain = iommu_get_domain_for_dev(dev);
|
||||
if (!(xhci->quirks & XHCI_ZERO_64B_REGS) || !domain ||
|
||||
domain->type == IOMMU_DOMAIN_IDENTITY)
|
||||
return;
|
||||
|
||||
xhci_info(xhci, "Zeroing 64bit base registers, expecting fault\n");
|
||||
|
@ -292,6 +296,32 @@ static void xhci_zero_64b_regs(struct xhci_hcd *xhci)
|
|||
xhci_info(xhci, "Fault detected\n");
|
||||
}
|
||||
|
||||
static int xhci_enable_interrupter(struct xhci_interrupter *ir)
|
||||
{
|
||||
u32 iman;
|
||||
|
||||
if (!ir || !ir->ir_set)
|
||||
return -EINVAL;
|
||||
|
||||
iman = readl(&ir->ir_set->irq_pending);
|
||||
writel(ER_IRQ_ENABLE(iman), &ir->ir_set->irq_pending);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int xhci_disable_interrupter(struct xhci_interrupter *ir)
|
||||
{
|
||||
u32 iman;
|
||||
|
||||
if (!ir || !ir->ir_set)
|
||||
return -EINVAL;
|
||||
|
||||
iman = readl(&ir->ir_set->irq_pending);
|
||||
writel(ER_IRQ_DISABLE(iman), &ir->ir_set->irq_pending);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_USB_PCI
|
||||
/*
|
||||
* Set up MSI
|
||||
|
@ -610,9 +640,9 @@ static int xhci_init(struct usb_hcd *hcd)
|
|||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
static int xhci_run_finished(struct xhci_hcd *xhci)
|
||||
{
|
||||
struct xhci_interrupter *ir = xhci->interrupter;
|
||||
unsigned long flags;
|
||||
u32 temp;
|
||||
|
||||
|
@ -628,8 +658,7 @@ static int xhci_run_finished(struct xhci_hcd *xhci)
|
|||
writel(temp, &xhci->op_regs->command);
|
||||
|
||||
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Enable primary interrupter");
|
||||
temp = readl(&xhci->ir_set->irq_pending);
|
||||
writel(ER_IRQ_ENABLE(temp), &xhci->ir_set->irq_pending);
|
||||
xhci_enable_interrupter(ir);
|
||||
|
||||
if (xhci_start(xhci)) {
|
||||
xhci_halt(xhci);
|
||||
|
@ -665,7 +694,7 @@ int xhci_run(struct usb_hcd *hcd)
|
|||
u64 temp_64;
|
||||
int ret;
|
||||
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
|
||||
|
||||
struct xhci_interrupter *ir = xhci->interrupter;
|
||||
/* Start the xHCI host controller running only after the USB 2.0 roothub
|
||||
* is setup.
|
||||
*/
|
||||
|
@ -680,17 +709,17 @@ int xhci_run(struct usb_hcd *hcd)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
temp_64 = xhci_read_64(xhci, &xhci->ir_set->erst_dequeue);
|
||||
temp_64 = xhci_read_64(xhci, &ir->ir_set->erst_dequeue);
|
||||
temp_64 &= ~ERST_PTR_MASK;
|
||||
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
|
||||
"ERST deq = 64'h%0lx", (long unsigned int) temp_64);
|
||||
|
||||
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
|
||||
"// Set the interrupt modulation register");
|
||||
temp = readl(&xhci->ir_set->irq_control);
|
||||
temp = readl(&ir->ir_set->irq_control);
|
||||
temp &= ~ER_IRQ_INTERVAL_MASK;
|
||||
temp |= (xhci->imod_interval / 250) & ER_IRQ_INTERVAL_MASK;
|
||||
writel(temp, &xhci->ir_set->irq_control);
|
||||
writel(temp, &ir->ir_set->irq_control);
|
||||
|
||||
if (xhci->quirks & XHCI_NEC_HOST) {
|
||||
struct xhci_command *command;
|
||||
|
@ -733,6 +762,7 @@ static void xhci_stop(struct usb_hcd *hcd)
|
|||
{
|
||||
u32 temp;
|
||||
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
|
||||
struct xhci_interrupter *ir = xhci->interrupter;
|
||||
|
||||
mutex_lock(&xhci->mutex);
|
||||
|
||||
|
@ -769,8 +799,7 @@ static void xhci_stop(struct usb_hcd *hcd)
|
|||
"// Disabling event ring interrupts");
|
||||
temp = readl(&xhci->op_regs->status);
|
||||
writel((temp & ~0x1fff) | STS_EINT, &xhci->op_regs->status);
|
||||
temp = readl(&xhci->ir_set->irq_pending);
|
||||
writel(ER_IRQ_DISABLE(temp), &xhci->ir_set->irq_pending);
|
||||
xhci_disable_interrupter(ir);
|
||||
|
||||
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "cleaning up memory");
|
||||
xhci_mem_cleanup(xhci);
|
||||
|
@ -832,28 +861,36 @@ EXPORT_SYMBOL_GPL(xhci_shutdown);
|
|||
#ifdef CONFIG_PM
|
||||
static void xhci_save_registers(struct xhci_hcd *xhci)
|
||||
{
|
||||
struct xhci_interrupter *ir = xhci->interrupter;
|
||||
|
||||
xhci->s3.command = readl(&xhci->op_regs->command);
|
||||
xhci->s3.dev_nt = readl(&xhci->op_regs->dev_notification);
|
||||
xhci->s3.dcbaa_ptr = xhci_read_64(xhci, &xhci->op_regs->dcbaa_ptr);
|
||||
xhci->s3.config_reg = readl(&xhci->op_regs->config_reg);
|
||||
xhci->s3.erst_size = readl(&xhci->ir_set->erst_size);
|
||||
xhci->s3.erst_base = xhci_read_64(xhci, &xhci->ir_set->erst_base);
|
||||
xhci->s3.erst_dequeue = xhci_read_64(xhci, &xhci->ir_set->erst_dequeue);
|
||||
xhci->s3.irq_pending = readl(&xhci->ir_set->irq_pending);
|
||||
xhci->s3.irq_control = readl(&xhci->ir_set->irq_control);
|
||||
|
||||
if (!ir)
|
||||
return;
|
||||
|
||||
ir->s3_erst_size = readl(&ir->ir_set->erst_size);
|
||||
ir->s3_erst_base = xhci_read_64(xhci, &ir->ir_set->erst_base);
|
||||
ir->s3_erst_dequeue = xhci_read_64(xhci, &ir->ir_set->erst_dequeue);
|
||||
ir->s3_irq_pending = readl(&ir->ir_set->irq_pending);
|
||||
ir->s3_irq_control = readl(&ir->ir_set->irq_control);
|
||||
}
|
||||
|
||||
static void xhci_restore_registers(struct xhci_hcd *xhci)
|
||||
{
|
||||
struct xhci_interrupter *ir = xhci->interrupter;
|
||||
|
||||
writel(xhci->s3.command, &xhci->op_regs->command);
|
||||
writel(xhci->s3.dev_nt, &xhci->op_regs->dev_notification);
|
||||
xhci_write_64(xhci, xhci->s3.dcbaa_ptr, &xhci->op_regs->dcbaa_ptr);
|
||||
writel(xhci->s3.config_reg, &xhci->op_regs->config_reg);
|
||||
writel(xhci->s3.erst_size, &xhci->ir_set->erst_size);
|
||||
xhci_write_64(xhci, xhci->s3.erst_base, &xhci->ir_set->erst_base);
|
||||
xhci_write_64(xhci, xhci->s3.erst_dequeue, &xhci->ir_set->erst_dequeue);
|
||||
writel(xhci->s3.irq_pending, &xhci->ir_set->irq_pending);
|
||||
writel(xhci->s3.irq_control, &xhci->ir_set->irq_control);
|
||||
writel(ir->s3_erst_size, &ir->ir_set->erst_size);
|
||||
xhci_write_64(xhci, ir->s3_erst_base, &ir->ir_set->erst_base);
|
||||
xhci_write_64(xhci, ir->s3_erst_dequeue, &ir->ir_set->erst_dequeue);
|
||||
writel(ir->s3_irq_pending, &ir->ir_set->irq_pending);
|
||||
writel(ir->s3_irq_control, &ir->ir_set->irq_control);
|
||||
}
|
||||
|
||||
static void xhci_set_cmd_ring_deq(struct xhci_hcd *xhci)
|
||||
|
@ -1218,8 +1255,7 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated)
|
|||
xhci_dbg(xhci, "// Disabling event ring interrupts\n");
|
||||
temp = readl(&xhci->op_regs->status);
|
||||
writel((temp & ~0x1fff) | STS_EINT, &xhci->op_regs->status);
|
||||
temp = readl(&xhci->ir_set->irq_pending);
|
||||
writel(ER_IRQ_DISABLE(temp), &xhci->ir_set->irq_pending);
|
||||
xhci_disable_interrupter(xhci->interrupter);
|
||||
|
||||
xhci_dbg(xhci, "cleaning up memory\n");
|
||||
xhci_mem_cleanup(xhci);
|
||||
|
@ -4406,6 +4442,7 @@ static int __maybe_unused xhci_change_max_exit_latency(struct xhci_hcd *xhci,
|
|||
|
||||
if (!virt_dev || max_exit_latency == virt_dev->current_mel) {
|
||||
spin_unlock_irqrestore(&xhci->lock, flags);
|
||||
xhci_free_command(xhci, command);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -5334,6 +5371,11 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks)
|
|||
if (xhci->hci_version > 0x100)
|
||||
xhci->hcc_params2 = readl(&xhci->cap_regs->hcc_params2);
|
||||
|
||||
/* xhci-plat or xhci-pci might have set max_interrupters already */
|
||||
if ((!xhci->max_interrupters) ||
|
||||
xhci->max_interrupters > HCS_MAX_INTRS(xhci->hcs_params1))
|
||||
xhci->max_interrupters = HCS_MAX_INTRS(xhci->hcs_params1);
|
||||
|
||||
xhci->quirks |= quirks;
|
||||
|
||||
get_quirks(dev, xhci);
|
||||
|
|
|
@ -513,6 +513,9 @@ struct xhci_intr_reg {
|
|||
/* Preserve bits 16:31 of erst_size */
|
||||
#define ERST_SIZE_MASK (0xffff << 16)
|
||||
|
||||
/* erst_base bitmasks */
|
||||
#define ERST_BASE_RSVDP (0x3f)
|
||||
|
||||
/* erst_dequeue bitmasks */
|
||||
/* Dequeue ERST Segment Index (DESI) - Segment number (or alias)
|
||||
* where the current dequeue pointer lies. This is an optional HW hint.
|
||||
|
@ -1684,11 +1687,6 @@ struct s3_save {
|
|||
u32 dev_nt;
|
||||
u64 dcbaa_ptr;
|
||||
u32 config_reg;
|
||||
u32 irq_pending;
|
||||
u32 irq_control;
|
||||
u32 erst_size;
|
||||
u64 erst_base;
|
||||
u64 erst_dequeue;
|
||||
};
|
||||
|
||||
/* Use for lpm */
|
||||
|
@ -1706,16 +1704,22 @@ struct xhci_bus_state {
|
|||
u32 port_c_suspend;
|
||||
u32 suspended_ports;
|
||||
u32 port_remote_wakeup;
|
||||
unsigned long resume_done[USB_MAXCHILDREN];
|
||||
/* which ports have started to resume */
|
||||
unsigned long resuming_ports;
|
||||
/* Which ports are waiting on RExit to U0 transition. */
|
||||
unsigned long rexit_ports;
|
||||
struct completion rexit_done[USB_MAXCHILDREN];
|
||||
struct completion u3exit_done[USB_MAXCHILDREN];
|
||||
};
|
||||
|
||||
|
||||
struct xhci_interrupter {
|
||||
struct xhci_ring *event_ring;
|
||||
struct xhci_erst erst;
|
||||
struct xhci_intr_reg __iomem *ir_set;
|
||||
unsigned int intr_num;
|
||||
/* For interrupter registers save and restore over suspend/resume */
|
||||
u32 s3_irq_pending;
|
||||
u32 s3_irq_control;
|
||||
u32 s3_erst_size;
|
||||
u64 s3_erst_base;
|
||||
u64 s3_erst_dequeue;
|
||||
};
|
||||
/*
|
||||
* It can take up to 20 ms to transition from RExit to U0 on the
|
||||
* Intel Lynx Point LP xHCI host.
|
||||
|
@ -1736,6 +1740,10 @@ struct xhci_port {
|
|||
struct xhci_hub *rhub;
|
||||
struct xhci_port_cap *port_cap;
|
||||
unsigned int lpm_incapable:1;
|
||||
unsigned long resume_timestamp;
|
||||
bool rexit_active;
|
||||
struct completion rexit_done;
|
||||
struct completion u3exit_done;
|
||||
};
|
||||
|
||||
struct xhci_hub {
|
||||
|
@ -1758,8 +1766,6 @@ struct xhci_hcd {
|
|||
struct xhci_op_regs __iomem *op_regs;
|
||||
struct xhci_run_regs __iomem *run_regs;
|
||||
struct xhci_doorbell_array __iomem *dba;
|
||||
/* Our HCD's current interrupter register set */
|
||||
struct xhci_intr_reg __iomem *ir_set;
|
||||
|
||||
/* Cached register copies of read-only HC data */
|
||||
__u32 hcs_params1;
|
||||
|
@ -1774,7 +1780,7 @@ struct xhci_hcd {
|
|||
u8 sbrn;
|
||||
u16 hci_version;
|
||||
u8 max_slots;
|
||||
u8 max_interrupters;
|
||||
u16 max_interrupters;
|
||||
u8 max_ports;
|
||||
u8 isoc_threshold;
|
||||
/* imod_interval in ns (I * 250ns) */
|
||||
|
@ -1794,6 +1800,7 @@ struct xhci_hcd {
|
|||
struct reset_control *reset;
|
||||
/* data structures */
|
||||
struct xhci_device_context_array *dcbaa;
|
||||
struct xhci_interrupter *interrupter;
|
||||
struct xhci_ring *cmd_ring;
|
||||
unsigned int cmd_ring_state;
|
||||
#define CMD_RING_STATE_RUNNING (1 << 0)
|
||||
|
@ -1804,8 +1811,7 @@ struct xhci_hcd {
|
|||
struct delayed_work cmd_timer;
|
||||
struct completion cmd_ring_stop_completion;
|
||||
struct xhci_command *current_cmd;
|
||||
struct xhci_ring *event_ring;
|
||||
struct xhci_erst erst;
|
||||
|
||||
/* Scratchpad */
|
||||
struct xhci_scratchpad *scratchpad;
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue