USB / Thunderbolt driver changes for 6.3-rc1

Here is the big set of USB and Thunderbolt driver changes for 6.3-rc1.
 
 Nothing major in here, just lots of good development, including:
   - Thunderbolt additions for new device support and features
   - xhci driver updates and cleanups
   - USB gadget media driver updates (includes media core changes that
     were acked by the v4l2 maintainers)
   - lots of other USB gadget driver updates for new features
   - dwc3 driver updates and fixes
   - minor debugfs leak fixes
   - typec driver updates and additions
   - dt-bindings conversions to yaml
   - other small bugfixes and driver updates
 
 All have been in linux-next for a while with no reported issues.
 
 Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
 -----BEGIN PGP SIGNATURE-----
 
 iG0EABECAC0WIQT0tgzFv3jCIUoxPcsxR9QN2y37KQUCY/ivpQ8cZ3JlZ0Brcm9h
 aC5jb20ACgkQMUfUDdst+ymkdQCeOS6N613eggYrXwnbjJhxMQDtKAcAmweK6kXh
 3o1IKOYqIMOx5E7zxn6W
 =7ajf
 -----END PGP SIGNATURE-----

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

Pull USB / Thunderbolt driver updates from Greg KH:
 "Here is the big set of USB and Thunderbolt driver changes for 6.3-rc1.

  Nothing major in here, just lots of good development, including:

   - Thunderbolt additions for new device support and features

   - xhci driver updates and cleanups

   - USB gadget media driver updates (includes media core changes that
     were acked by the v4l2 maintainers)

   - lots of other USB gadget driver updates for new features

   - dwc3 driver updates and fixes

   - minor debugfs leak fixes

   - typec driver updates and additions

   - dt-bindings conversions to yaml

   - other small bugfixes and driver updates

  All have been in linux-next for a while with no reported issues"

* tag 'usb-6.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (237 commits)
  usb: dwc3: xilinx: Remove unused of_gpio,h
  usb: typec: pd: Add higher capability sysfs for sink PDO
  usb: typec: pd: Remove usb_suspend_supported sysfs from sink PDO
  usb: dwc3: pci: add support for the Intel Meteor Lake-M
  usb: gadget: u_ether: Don't warn in gether_setup_name_default()
  usb: gadget: u_ether: Convert prints to device prints
  usb: gadget: u_serial: Add null pointer check in gserial_resume
  usb: gadget: uvc: fix missing mutex_unlock() if kstrtou8() fails
  xhci: host: potential NULL dereference in xhci_generic_plat_probe()
  dt-bindings: usb: amlogic,meson-g12a-usb-ctrl: make G12A usb3-phy0 optional
  usb: host: fsl-mph-dr-of: reuse device_set_of_node_from_dev
  of: device: Do not ignore error code in of_device_uevent_modalias
  of: device: Ignore modalias of reused nodes
  usb: gadget: configfs: Fix set but not used variable warning
  usb: gadget: uvc: Use custom strings if available
  usb: gadget: uvc: Allow linking function to string descs
  usb: gadget: uvc: Pick up custom string descriptor IDs
  usb: gadget: uvc: Allow linking XUs to string descriptors
  usb: gadget: configfs: Attach arbitrary strings to cdev
  usb: gadget: configfs: Support arbitrary string descriptors
  ...
This commit is contained in:
Linus Torvalds 2023-02-24 12:07:00 -08:00
commit 72bffe7e1e
199 changed files with 11854 additions and 2215 deletions

View File

@ -143,3 +143,16 @@ Description:
qw_sign an identifier to be reported as "OS String"
proper
============= ===============================================
What: /config/usb-gadget/gadget/webusb
Date: Dec 2022
KernelVersion: 6.3
Description:
This group contains "WebUSB" extension handling attributes.
============= ===============================================
use flag turning "WebUSB" support on/off
bcdVersion bcd WebUSB specification version number
bVendorCode one-byte value used for custom per-device
landingPage UTF-8 encoded URL of the device's landing page
============= ===============================================

View File

@ -15,12 +15,14 @@ Date: Dec 2014
KernelVersion: 4.0
Description: Control descriptors
All attributes read only:
All attributes read only except enable_interrupt_ep:
================ =============================
=================== =============================
bInterfaceNumber USB interface number for this
streaming interface
================ =============================
enable_interrupt_ep flag to enable the interrupt
endpoint for the VC interface
=================== =============================
What: /config/usb-gadget/gadget/functions/uvc.name/control/class
Date: Dec 2014
@ -52,7 +54,7 @@ Date: Dec 2014
KernelVersion: 4.0
Description: Default output terminal descriptors
All attributes read only:
All attributes read only except bSourceID:
============== =============================================
iTerminal index of string descriptor
@ -111,6 +113,34 @@ Description: Default processing unit descriptors
bUnitID a non-zero id of this unit
=============== ========================================
What: /config/usb-gadget/gadget/functions/uvc.name/control/extensions
Date: Nov 2022
KernelVersion: 6.1
Description: Extension unit descriptors
What: /config/usb-gadget/gadget/functions/uvc.name/control/extensions/name
Date: Nov 2022
KernelVersion: 6.1
Description: Extension Unit (XU) Descriptor
bLength, bUnitID and iExtension are read-only. All others are
read-write.
================= ========================================
bLength size of the descriptor in bytes
bUnitID non-zero ID of this unit
guidExtensionCode Vendor-specific code identifying the XU
bNumControls number of controls in this XU
bNrInPins number of input pins for this unit
baSourceID list of the IDs of the units or terminals
to which this XU is connected
bControlSize size of the bmControls field in bytes
bmControls list of bitmaps detailing which vendor
specific controls are supported
iExtension index of a string descriptor that describes
this extension unit
================= ========================================
What: /config/usb-gadget/gadget/functions/uvc.name/control/header
Date: Dec 2014
KernelVersion: 4.0
@ -165,7 +195,24 @@ Date: Dec 2014
KernelVersion: 4.0
Description: Default color matching descriptors
All attributes read only:
All attributes read/write:
======================== ======================================
bMatrixCoefficients matrix used to compute luma and
chroma values from the color primaries
bTransferCharacteristics optoelectronic transfer
characteristic of the source picture,
also called the gamma function
bColorPrimaries color primaries and the reference
white
======================== ======================================
What: /config/usb-gadget/gadget/functions/uvc.name/streaming/color_matching/name
Date: Dec 2022
KernelVersion: 6.3
Description: Additional color matching descriptors
All attributes read/write:
======================== ======================================
bMatrixCoefficients matrix used to compute luma and

View File

@ -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>

View File

@ -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 HPDs logical state is high (HPD_High) as defined
by VESA DisplayPort Alt Mode on USB Type-C Standard.
- 0 when HPDs logical state is low (HPD_Low) as defined by
VESA DisplayPort Alt Mode on USB Type-C Standard.

View File

@ -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>;

View File

@ -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.

View File

@ -13,10 +13,12 @@ maintainers:
properties:
compatible:
oneOf:
- const: allwinner,sun4i-a10-musb
- const: allwinner,sun6i-a31-musb
- const: allwinner,sun8i-a33-musb
- const: allwinner,sun8i-h3-musb
- enum:
- allwinner,sun4i-a10-musb
- allwinner,sun6i-a31-musb
- allwinner,sun8i-a33-musb
- allwinner,sun8i-h3-musb
- allwinner,suniv-f1c100s-musb
- items:
- enum:
- allwinner,sun8i-a83t-musb

View File

@ -108,6 +108,7 @@ allOf:
then:
properties:
phy-names:
minItems: 2
items:
- const: usb2-phy0 # USB2 PHY0 if USBHOST_A port is used
- const: usb2-phy1 # USB2 PHY1 if USBOTG_B port is used

View File

@ -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

View File

@ -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"

View File

@ -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>;
};
};
};
};
};
};

View File

@ -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>;
};

View File

@ -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>;
};

View File

@ -5,7 +5,7 @@
$id: http://devicetree.org/schemas/usb/faraday,fotg210.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Faraday Technology FOTG210 HS OTG USB 2.0 controller
title: Faraday Technology FOTG200 series HS OTG USB 2.0 controller
maintainers:
- Linus Walleij <linus.walleij@linaro.org>
@ -17,10 +17,11 @@ allOf:
properties:
compatible:
oneOf:
- const: faraday,fotg200
- const: faraday,fotg210
- items:
- const: cortina,gemini-usb
- const: faraday,fotg210
- const: faraday,fotg200
reg:
maxItems: 1
@ -66,7 +67,7 @@ examples:
#include <dt-bindings/clock/cortina,gemini-clock.h>
#include <dt-bindings/reset/cortina,gemini-reset.h>
usb0: usb@68000000 {
compatible = "cortina,gemini-usb", "faraday,fotg210";
compatible = "cortina,gemini-usb", "faraday,fotg200";
reg = <0x68000000 0x1000>;
interrupts = <10 IRQ_TYPE_LEVEL_HIGH>;
resets = <&syscon GEMINI_RESET_USB0>;

View File

@ -51,7 +51,7 @@ examples:
#address-cells = <1>;
#size-cells = <0>;
fsa4480@42 {
typec-mux@42 {
compatible = "fcs,fsa4480";
reg = <0x42>;

View File

@ -71,6 +71,9 @@ properties:
description:
Power pad (PWR) polarity is active low.
power-domains:
maxItems: 1
# Required child node:
patternProperties:
@ -87,12 +90,14 @@ required:
- clocks
- clock-names
- interrupts
- power-domains
additionalProperties: false
examples:
- |
#include <dt-bindings/clock/imx8mp-clock.h>
#include <dt-bindings/power/imx8mp-power.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
usb3_0: usb@32f10100 {
compatible = "fsl,imx8mp-dwc3";
@ -102,6 +107,7 @@ examples:
<&clk IMX8MP_CLK_USB_ROOT>;
clock-names = "hsio", "suspend";
interrupts = <GIC_SPI 148 IRQ_TYPE_LEVEL_HIGH>;
power-domains = <&hsio_blk_ctrl IMX8MP_HSIOBLK_PD_USB>;
#address-cells = <1>;
#size-cells = <1>;
dma-ranges = <0x40000000 0x40000000 0xc0000000>;

View File

@ -74,6 +74,11 @@ properties:
- const: usb-ehci
- enum:
- generic-ehci
- marvell,armada-3700-ehci
- marvell,orion-ehci
- nuvoton,npcm750-ehci
- nuvoton,npcm845-ehci
- ti,ehci-omap
- usb-ehci
reg:

View File

@ -6,9 +6,6 @@ $schema: http://devicetree.org/meta-schemas/core.yaml#
title: USB OHCI Controller
allOf:
- $ref: "usb-hcd.yaml"
maintainers:
- Greg Kroah-Hartman <gregkh@linuxfoundation.org>
@ -49,7 +46,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
@ -119,11 +125,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:

View File

@ -16,6 +16,7 @@ properties:
compatible:
enum:
- usb5e3,608
- usb5e3,610
reg: true

View File

@ -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>;
};
};
};
...

View File

@ -35,6 +35,7 @@ properties:
- mediatek,mt8188-xhci
- mediatek,mt8192-xhci
- mediatek,mt8195-xhci
- mediatek,mt8365-xhci
- const: mediatek,mtk-xhci
reg:

View File

@ -28,6 +28,7 @@ properties:
- mediatek,mt8188-mtu3
- mediatek,mt8192-mtu3
- mediatek,mt8195-mtu3
- mediatek,mt8365-mtu3
- const: mediatek,mtu3
reg:

View File

@ -1,20 +0,0 @@
Nuvoton NPCM7XX SoC USB controllers:
-----------------------------
EHCI:
-----
Required properties:
- compatible: should be one of
"nuvoton,npcm750-ehci"
"nuvoton,npcm845-ehci"
- interrupts: Should contain the EHCI interrupt
- reg: Physical address and length of the register set for the device
Example:
ehci1: usb@f0806000 {
compatible = "nuvoton,npcm750-ehci";
reg = <0xf0806000 0x1000>;
interrupts = <0 61 4>;
};

View File

@ -22,6 +22,7 @@ properties:
- nvidia,tegra210-xudc # For Tegra210
- nvidia,tegra186-xudc # For Tegra186
- nvidia,tegra194-xudc # For Tegra194
- nvidia,tegra234-xudc # For Tegra234
reg:
minItems: 2
@ -112,6 +113,8 @@ properties:
hvdd-usb-supply:
description: USB controller power supply. Must supply 3.3 V.
dma-coherent: true
required:
- compatible
- reg
@ -153,6 +156,7 @@ allOf:
enum:
- nvidia,tegra186-xudc
- nvidia,tegra194-xudc
- nvidia,tegra234-xudc
then:
properties:
reg:
@ -164,6 +168,17 @@ allOf:
clock-names:
maxItems: 4
- if:
properties:
compatible:
contains:
enum:
- nvidia,tegra194-xudc
- nvidia,tegra234-xudc
then:
required:
- dma-coherent
additionalProperties: false
examples:

View File

@ -0,0 +1,159 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/usb/nvidia,tegra234-xusb.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: NVIDIA Tegra234 xHCI controller
maintainers:
- Thierry Reding <thierry.reding@gmail.com>
- Jon Hunter <jonathanh@nvidia.com>
description: |
The Tegra xHCI controller supports both USB2 and USB3 interfaces exposed by
the Tegra XUSB pad controller. The xHCI controller controls up to eight
ports; there are four USB 2.0 ports and four USB 3.2 Gen1 x1 ports.
properties:
compatible:
const: nvidia,tegra234-xusb
reg:
items:
- description: xHCI host registers
- description: XUSB FPCI registers
- description: XUSB bar2 registers
reg-names:
items:
- const: hcd
- const: fpci
- const: bar2
interrupts:
items:
- description: xHCI host interrupt
- description: mailbox interrupt
clocks:
items:
- description: XUSB host clock
- description: XUSB Falcon source clock
- description: XUSB SuperSpeed clock
- description: XUSB SuperSpeed source clock
- description: XUSB HighSpeed clock source
- description: XUSB FullSpeed clock source
- description: USB PLL
- description: reference clock
- description: I/O PLL
clock-names:
items:
- const: xusb_host
- const: xusb_falcon_src
- const: xusb_ss
- const: xusb_ss_src
- const: xusb_hs_src
- const: xusb_fs_src
- const: pll_u_480m
- const: clk_m
- const: pll_e
interconnects:
items:
- description: read client
- description: write client
interconnect-names:
items:
- const: dma-mem # read
- const: write
iommus:
maxItems: 1
nvidia,xusb-padctl:
$ref: /schemas/types.yaml#/definitions/phandle
description: phandle to the XUSB pad controller that is used to configure
the USB pads used by the XHCI controller
phys:
minItems: 1
maxItems: 8
phy-names:
minItems: 1
maxItems: 8
items:
enum:
- usb2-0
- usb2-1
- usb2-2
- usb2-3
- usb3-0
- usb3-1
- usb3-2
- usb3-3
power-domains:
items:
- description: XUSBC power domain (for Host and USB 2.0)
- description: XUSBA power domain (for SuperSpeed)
power-domain-names:
items:
- const: xusb_host
- const: xusb_ss
dma-coherent: true
allOf:
- $ref: usb-xhci.yaml
unevaluatedProperties: false
examples:
- |
#include <dt-bindings/clock/tegra234-clock.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/memory/tegra234-mc.h>
#include <dt-bindings/power/tegra234-powergate.h>
usb@3610000 {
compatible = "nvidia,tegra234-xusb";
reg = <0x03610000 0x40000>,
<0x03600000 0x10000>,
<0x03650000 0x10000>;
reg-names = "hcd", "fpci", "bar2";
interrupts = <GIC_SPI 163 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 164 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&bpmp TEGRA234_CLK_XUSB_CORE_HOST>,
<&bpmp TEGRA234_CLK_XUSB_FALCON>,
<&bpmp TEGRA234_CLK_XUSB_CORE_SS>,
<&bpmp TEGRA234_CLK_XUSB_SS>,
<&bpmp TEGRA234_CLK_CLK_M>,
<&bpmp TEGRA234_CLK_XUSB_FS>,
<&bpmp TEGRA234_CLK_UTMIP_PLL>,
<&bpmp TEGRA234_CLK_CLK_M>,
<&bpmp TEGRA234_CLK_PLLE>;
clock-names = "xusb_host", "xusb_falcon_src",
"xusb_ss", "xusb_ss_src", "xusb_hs_src",
"xusb_fs_src", "pll_u_480m", "clk_m",
"pll_e";
interconnects = <&mc TEGRA234_MEMORY_CLIENT_XUSB_HOSTR &emc>,
<&mc TEGRA234_MEMORY_CLIENT_XUSB_HOSTW &emc>;
interconnect-names = "dma-mem", "write";
iommus = <&smmu_niso1 TEGRA234_SID_XUSB_HOST>;
power-domains = <&bpmp TEGRA234_POWER_DOMAIN_XUSBC>,
<&bpmp TEGRA234_POWER_DOMAIN_XUSBA>;
power-domain-names = "xusb_host", "xusb_ss";
nvidia,xusb-padctl = <&xusb_padctl>;
phys = <&pad_lanes_usb2_0>;
phy-names = "usb2-0";
};

View File

@ -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>;
};

View File

@ -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>;
};

View File

@ -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;

View File

@ -58,6 +58,9 @@ properties:
description: specifies a phandle to PM domain provider node
maxItems: 1
required-opps:
maxItems: 1
clocks:
description: |
Several clocks are used, depending on the variant. Typical ones are::

View File

@ -0,0 +1,68 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/usb/renesas,rzn1-usbf.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Renesas RZ/N1 SoCs USBF (USB Function) controller
description: |
The Renesas USBF controller is an USB2.0 device
controller (UDC).
maintainers:
- Herve Codina <herve.codina@bootlin.com>
properties:
compatible:
items:
- enum:
- renesas,r9a06g032-usbf
- const: renesas,rzn1-usbf
reg:
maxItems: 1
clocks:
items:
- description: Internal bus clock (AHB) for Function
- description: Internal bus clock (AHB) for Power Management
clock-names:
items:
- const: hclkf
- const: hclkpm
power-domains:
maxItems: 1
interrupts:
items:
- description: The USBF EPC interrupt
- description: The USBF AHB-EPC interrupt
required:
- compatible
- reg
- clocks
- clock-names
- power-domains
- interrupts
additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/clock/r9a06g032-sysctrl.h>
usb@4001e000 {
compatible = "renesas,r9a06g032-usbf", "renesas,rzn1-usbf";
reg = <0x4001e000 0x2000>;
interrupts = <GIC_SPI 77 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 78 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&sysctrl R9A06G032_HCLK_USBF>,
<&sysctrl R9A06G032_HCLK_USBPM>;
clock-names = "hclkf", "hclkpm";
power-domains = <&sysctrl>;
};

View File

@ -0,0 +1,129 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/usb/renesas,rzv2m-usb3drd.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Renesas RZ/V2M USB 3.1 DRD controller
maintainers:
- Biju Das <biju.das.jz@bp.renesas.com>
description: |
The RZ/V2{M, MA} USB3.1 DRD module supports the following functions
* Role swapping function by the ID pin of the Micro-AB receptacle
* Battery Charging Specification Revision 1.2
properties:
compatible:
items:
- enum:
- renesas,r9a09g011-usb3drd # RZ/V2M
- renesas,r9a09g055-usb3drd # RZ/V2MA
- const: renesas,rzv2m-usb3drd
reg:
maxItems: 1
interrupts:
items:
- description: Dual Role Device (DRD)
- description: Battery Charging
- description: Global Purpose Input
interrupt-names:
items:
- const: drd
- const: bc
- const: gpi
clocks:
items:
- description: Peripheral AXI clock
- description: APB clock
clock-names:
items:
- const: axi
- const: reg
power-domains:
maxItems: 1
resets:
maxItems: 1
ranges: true
'#address-cells':
enum: [ 1, 2 ]
'#size-cells':
enum: [ 1, 2 ]
patternProperties:
"^usb3peri@[0-9a-f]+$":
type: object
$ref: /schemas/usb/renesas,usb3-peri.yaml
"^usb@[0-9a-f]+$":
type: object
$ref: renesas,usb-xhci.yaml#
required:
- compatible
- reg
- interrupts
- interrupt-names
- clocks
- clock-names
- power-domains
- resets
additionalProperties: false
examples:
- |
#include <dt-bindings/clock/r9a09g011-cpg.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
usb3drd: usb@85070400 {
compatible = "renesas,r9a09g011-usb3drd", "renesas,rzv2m-usb3drd";
reg = <0x85070400 0x100>;
interrupts = <GIC_SPI 242 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 243 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 244 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "drd", "bc", "gpi";
clocks = <&cpg CPG_MOD R9A09G011_USB_ACLK_P>,
<&cpg CPG_MOD R9A09G011_USB_PCLK>;
clock-names = "axi", "reg";
power-domains = <&cpg>;
resets = <&cpg R9A09G011_USB_DRD_RESET>;
ranges;
#address-cells = <1>;
#size-cells = <1>;
usb3host: usb@85060000 {
compatible = "renesas,r9a09g011-xhci",
"renesas,rzv2m-xhci";
reg = <0x85060000 0x2000>;
interrupts = <GIC_SPI 245 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cpg CPG_MOD R9A09G011_USB_ACLK_H>,
<&cpg CPG_MOD R9A09G011_USB_PCLK>;
clock-names = "axi", "reg";
power-domains = <&cpg>;
resets = <&cpg R9A09G011_USB_ARESETN_H>;
};
usb3peri: usb3peri@85070000 {
compatible = "renesas,r9a09g011-usb3-peri",
"renesas,rzv2m-usb3-peri";
reg = <0x85070000 0x400>;
interrupts = <GIC_SPI 246 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cpg CPG_MOD R9A09G011_USB_ACLK_P>,
<&cpg CPG_MOD R9A09G011_USB_PCLK>;
clock-names = "axi", "reg";
power-domains = <&cpg>;
resets = <&cpg R9A09G011_USB_ARESETN_P>;
};
};

View File

@ -10,9 +10,6 @@ maintainers:
- Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
- Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
allOf:
- $ref: "usb-xhci.yaml"
properties:
compatible:
oneOf:
@ -37,6 +34,11 @@ properties:
- renesas,xhci-r8a77965 # R-Car M3-N
- renesas,xhci-r8a77990 # R-Car E3
- const: renesas,rcar-gen3-xhci # R-Car Gen3 and RZ/G2
- items:
- enum:
- renesas,r9a09g011-xhci # RZ/V2M
- renesas,r9a09g055-xhci # RZ/V2MA
- const: renesas,rzv2m-xhci # RZ/{V2M, V2MA}
reg:
maxItems: 1
@ -45,7 +47,16 @@ properties:
maxItems: 1
clocks:
maxItems: 1
minItems: 1
items:
- description: Main clock for host
- description: Register access clock
clock-names:
minItems: 1
items:
- const: axi
- const: reg
phys:
maxItems: 1
@ -68,6 +79,28 @@ required:
- power-domains
- resets
allOf:
- $ref: usb-xhci.yaml
- if:
properties:
compatible:
contains:
enum:
- renesas,rzv2m-xhci
then:
properties:
clocks:
minItems: 2
clock-names:
minItems: 2
required:
- clock-names
else:
properties:
clocks:
maxItems: 1
unevaluatedProperties: false
examples:

View File

@ -28,26 +28,14 @@ properties:
- items:
- enum:
- renesas,r9a09g011-usb3-peri # RZ/V2M
- renesas,r9a09g055-usb3-peri # RZ/V2MA
- const: renesas,rzv2m-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
@ -58,7 +46,7 @@ properties:
clock-names:
minItems: 1
items:
- const: aclk
- const: axi
- const: reg
phys:
@ -71,15 +59,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
@ -127,25 +107,13 @@ allOf:
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

View File

@ -29,7 +29,6 @@ select:
contains:
enum:
- rockchip,rk3328-dwc3
- rockchip,rk3399-dwc3
- rockchip,rk3568-dwc3
required:
- compatible
@ -39,7 +38,6 @@ properties:
items:
- enum:
- rockchip,rk3328-dwc3
- rockchip,rk3399-dwc3
- rockchip,rk3568-dwc3
- const: snps,dwc3
@ -90,7 +88,7 @@ required:
examples:
- |
#include <dt-bindings/clock/rk3399-cru.h>
#include <dt-bindings/clock/rk3328-cru.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
bus {
@ -98,11 +96,11 @@ examples:
#size-cells = <2>;
usbdrd3_0: usb@fe800000 {
compatible = "rockchip,rk3399-dwc3", "snps,dwc3";
compatible = "rockchip,rk3328-dwc3", "snps,dwc3";
reg = <0x0 0xfe800000 0x0 0x100000>;
interrupts = <GIC_SPI 105 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru SCLK_USB3OTG0_REF>, <&cru SCLK_USB3OTG0_SUSPEND>,
<&cru ACLK_USB3OTG0>, <&cru ACLK_USB3_GRF>;
clocks = <&cru SCLK_USB3OTG_REF>, <&cru SCLK_USB3OTG_SUSPEND>,
<&cru ACLK_USB3OTG>;
clock-names = "ref_clk", "suspend_clk",
"bus_clk", "grf_clk";
dr_mode = "otg";

View File

@ -0,0 +1,115 @@
# SPDX-License-Identifier: GPL-2.0
%YAML 1.2
---
$id: http://devicetree.org/schemas/usb/rockchip,rk3399-dwc3.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Rockchip RK3399 SuperSpeed DWC3 USB SoC controller
maintainers:
- Heiko Stuebner <heiko@sntech.de>
properties:
compatible:
const: rockchip,rk3399-dwc3
'#address-cells':
const: 2
'#size-cells':
const: 2
ranges: true
clocks:
items:
- description:
Controller reference clock, must to be 24 MHz
- description:
Controller suspend clock, must to be 24 MHz or 32 KHz
- description:
Master/Core clock, must to be >= 62.5 MHz for SS
operation and >= 30MHz for HS operation
- description:
USB3 aclk peri
- description:
USB3 aclk
- description:
Controller grf clock
clock-names:
items:
- const: ref_clk
- const: suspend_clk
- const: bus_clk
- const: aclk_usb3_rksoc_axi_perf
- const: aclk_usb3
- const: grf_clk
resets:
maxItems: 1
reset-names:
const: usb3-otg
patternProperties:
'^usb@':
$ref: snps,dwc3.yaml#
additionalProperties: false
required:
- compatible
- '#address-cells'
- '#size-cells'
- ranges
- clocks
- clock-names
- resets
- reset-names
examples:
- |
#include <dt-bindings/clock/rk3399-cru.h>
#include <dt-bindings/power/rk3399-power.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
bus {
#address-cells = <2>;
#size-cells = <2>;
usb {
compatible = "rockchip,rk3399-dwc3";
#address-cells = <2>;
#size-cells = <2>;
ranges;
clocks = <&cru SCLK_USB3OTG0_REF>, <&cru SCLK_USB3OTG0_SUSPEND>,
<&cru ACLK_USB3OTG0>, <&cru ACLK_USB3_RKSOC_AXI_PERF>,
<&cru ACLK_USB3>, <&cru ACLK_USB3_GRF>;
clock-names = "ref_clk", "suspend_clk",
"bus_clk", "aclk_usb3_rksoc_axi_perf",
"aclk_usb3", "grf_clk";
resets = <&cru SRST_A_USB3_OTG0>;
reset-names = "usb3-otg";
usb@fe800000 {
compatible = "snps,dwc3";
reg = <0x0 0xfe800000 0x0 0x100000>;
interrupts = <GIC_SPI 105 IRQ_TYPE_LEVEL_HIGH 0>;
clocks = <&cru SCLK_USB3OTG0_REF>, <&cru ACLK_USB3OTG0>,
<&cru SCLK_USB3OTG0_SUSPEND>;
clock-names = "ref", "bus_early", "suspend";
dr_mode = "otg";
phys = <&u2phy0_otg>, <&tcphy0_usb3>;
phy-names = "usb2-phy", "usb3-phy";
phy_type = "utmi_wide";
snps,dis_enblslpm_quirk;
snps,dis-u2-freeclk-exists-quirk;
snps,dis_u2_susphy_quirk;
snps,dis-del-phy-power-chg-quirk;
snps,dis-tx-ipgap-linecheck-quirk;
power-domains = <&power RK3399_PD_USB3>;
};
};
};
...

View File

@ -108,19 +108,19 @@ examples:
#include <dt-bindings/clock/exynos5420.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
usb {
usb@12000000 {
compatible = "samsung,exynos5250-dwusb3";
#address-cells = <1>;
#size-cells = <1>;
ranges;
ranges = <0x0 0x12000000 0x10000>;
clocks = <&clock CLK_USBD300>;
clock-names = "usbdrd30";
vdd33-supply = <&ldo9_reg>;
vdd10-supply = <&ldo11_reg>;
usb@12000000 {
usb@0 {
compatible = "snps,dwc3";
reg = <0x12000000 0x10000>;
reg = <0x0 0x10000>;
interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;
phys = <&usbdrd_phy0 0>, <&usbdrd_phy0 1>;
phy-names = "usb2-phy", "usb3-phy";

View File

@ -91,6 +91,16 @@ properties:
- usb2-phy
- usb3-phy
power-domains:
description:
The DWC3 has 2 power-domains. The power management unit (PMU) and
everything else. The PMU is typically always powered and may not have an
entry.
minItems: 1
items:
- description: Core
- description: Power management unit
resets:
minItems: 1

View File

@ -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>;
};

View File

@ -46,7 +46,6 @@ properties:
required:
- compatible
- reg
- interrupts
additionalProperties: false

View File

@ -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>;

View File

@ -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

View File

@ -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:

View File

@ -0,0 +1,71 @@
# SPDX-License-Identifier: GPL-2.0-only or BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/usb/vialab,vl817.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Via labs VL817 USB 3.1 hub controller
maintainers:
- Anand Moon <linux.amoon@gmail.com>
allOf:
- $ref: usb-device.yaml#
properties:
compatible:
enum:
- usb2109,2817
- usb2109,817
reg: true
reset-gpios:
maxItems: 1
description:
GPIO controlling the RESET# pin.
vdd-supply:
description:
phandle to the regulator that provides power to the hub.
peer-hub:
$ref: /schemas/types.yaml#/definitions/phandle
description:
phandle to the peer hub on the controller.
required:
- compatible
- reg
- reset-gpios
- vdd-supply
- peer-hub
additionalProperties: false
examples:
- |
#include <dt-bindings/gpio/gpio.h>
usb {
#address-cells = <1>;
#size-cells = <0>;
/* 2.0 hub on port 1 */
hub_2_0: hub@1 {
compatible = "usb2109,2817";
reg = <1>;
vdd-supply = <&vcc_5v>;
peer-hub = <&hub_3_0>;
reset-gpios = <&gpio 20 GPIO_ACTIVE_LOW>;
};
/* 3.1 hub on port 4 */
hub_3_0: hub@2 {
compatible = "usb2109,817";
reg = <2>;
vdd-supply = <&vcc_5v>;
peer-hub = <&hub_2_0>;
reset-gpios = <&gpio 20 GPIO_ACTIVE_LOW>;
};
};

View File

@ -1422,6 +1422,8 @@ patternProperties:
description: Vertexcom Technologies, Inc.
"^via,.*":
description: VIA Technologies, Inc.
"^vialab,.*":
description: VIA Labs, Inc.
"^vicor,.*":
description: Vicor Corporation
"^videostrong,.*":

View File

@ -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;

View File

@ -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

View File

@ -90,6 +90,16 @@ Then the strings can be specified::
$ echo <manufacturer> > strings/0x409/manufacturer
$ echo <product> > strings/0x409/product
Further custom string descriptors can be created as directories within the
language's directory, with the string text being written to the "s" attribute
within the string's directory:
$ mkdir strings/0x409/xu.0
$ echo <string text> > strings/0x409/xu.0/s
Where function drivers support it, functions may allow symlinks to these custom
string descriptors to associate those strings with class descriptors.
2. Creating the configurations
------------------------------

View File

@ -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
=============

View File

@ -17811,6 +17811,14 @@ S: Maintained
F: Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml
F: drivers/rtc/rtc-rzn1.c
RENESAS RZ/N1 USBF CONTROLLER DRIVER
M: Herve Codina <herve.codina@bootlin.com>
L: linux-renesas-soc@vger.kernel.org
L: linux-usb@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/usb/renesas,rzn1-usbf.yaml
F: drivers/usb/gadget/udc/renesas_usbf.c
RENESAS R-CAR GEN3 & RZ/N1 NAND CONTROLLER DRIVER
M: Miquel Raynal <miquel.raynal@bootlin.com>
L: linux-mtd@lists.infradead.org

View File

@ -281,19 +281,6 @@
"J7 Header Pin 6", "J7 Header Pin 5",
"J7 Header Pin 7", "HDMI_CEC",
"SYS_LED", "", "";
/*
* WARNING: The USB Hub on the Odroid-C1/C1+ needs a reset signal
* to be turned high in order to be detected by the USB Controller.
* This signal should be handled by a USB specific power sequence
* in order to reset the Hub when USB bus is powered down.
*/
usb-hub {
gpio-hog;
gpios = <GPIOAO_4 GPIO_ACTIVE_HIGH>;
output-high;
line-name = "usb-hub-reset";
};
};
&ir_receiver {
@ -381,5 +368,16 @@
};
&usb1 {
dr_mode = "host";
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
hub@1 {
/* Genesys Logic GL852G usb hub */
compatible = "usb5e3,610";
reg = <1>;
vdd-supply = <&p5v0>;
reset-gpio = <&gpio_ao GPIOAO_4 GPIO_ACTIVE_LOW>;
};
};

View File

@ -250,21 +250,6 @@
};
};
&gpio_ao {
/*
* WARNING: The USB Hub on the Odroid-C2 needs a reset signal
* to be turned high in order to be detected by the USB Controller
* This signal should be handled by a USB specific power sequence
* in order to reset the Hub when USB bus is powered down.
*/
hog-0 {
gpio-hog;
gpios = <GPIOAO_4 GPIO_ACTIVE_HIGH>;
output-high;
line-name = "usb-hub-reset";
};
};
&hdmi_tx {
status = "okay";
pinctrl-0 = <&hdmi_hpd_pins>, <&hdmi_i2c_pins>;
@ -414,5 +399,16 @@
};
&usb1 {
dr_mode = "host";
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
hub@1 {
/* Genesys Logic GL852G USB 2.0 hub */
compatible = "usb5e3,610";
reg = <1>;
vdd-supply = <&p5v0>;
reset-gpio = <&gpio_ao GPIOAO_4 GPIO_ACTIVE_LOW>;
};
};

View File

@ -26,20 +26,30 @@
sound {
model = "ODROID-C4";
};
};
&gpio {
/*
* WARNING: The USB Hub on the Odroid-C4 needs a reset signal
* to be turned high in order to be detected by the USB Controller
* This signal should be handled by a USB specific power sequence
* in order to reset the Hub when USB bus is powered down.
*/
hog-0 {
gpio-hog;
gpios = <GPIOH_4 GPIO_ACTIVE_HIGH>;
output-high;
line-name = "usb-hub-reset";
/* USB hub supports both USB 2.0 and USB 3.0 root hub */
usb-hub {
dr_mode = "host";
#address-cells = <1>;
#size-cells = <0>;
/* 2.0 hub on port 1 */
hub_2_0: hub@1 {
compatible = "usb2109,2817";
reg = <1>;
peer-hub = <&hub_3_0>;
reset-gpios = <&gpio GPIOH_4 GPIO_ACTIVE_LOW>;
vdd-supply = <&vcc_5v>;
};
/* 3.1 hub on port 4 */
hub_3_0: hub@2 {
compatible = "usb2109,817";
reg = <2>;
peer-hub = <&hub_2_0>;
reset-gpios = <&gpio GPIOH_4 GPIO_ACTIVE_LOW>;
vdd-supply = <&vcc_5v>;
};
};
};

View File

@ -938,6 +938,7 @@ CONFIG_USB_SERIAL_FTDI_SIO=m
CONFIG_USB_SERIAL_OPTION=m
CONFIG_USB_QCOM_EUD=m
CONFIG_USB_HSIC_USB3503=y
CONFIG_USB_ONBOARD_HUB=m
CONFIG_NOP_USB_XCEIV=y
CONFIG_USB_GADGET=y
CONFIG_USB_RENESAS_USBHS_UDC=m
@ -960,6 +961,8 @@ CONFIG_TYPEC_TCPCI=m
CONFIG_TYPEC_FUSB302=m
CONFIG_TYPEC_TPS6598X=m
CONFIG_TYPEC_HD3SS3220=m
CONFIG_TYPEC_UCSI=m
CONFIG_UCSI_CCG=m
CONFIG_TYPEC_MUX_GPIO_SBU=m
CONFIG_MMC=y
CONFIG_MMC_BLOCK_MINORS=32

View File

@ -25,6 +25,8 @@
#include <linux/spinlock.h>
#include <dt-bindings/clock/r9a06g032-sysctrl.h>
#define R9A06G032_SYSCTRL_USB 0x00
#define R9A06G032_SYSCTRL_USB_H2MODE (1<<1)
#define R9A06G032_SYSCTRL_DMAMUX 0xA0
struct r9a06g032_gate {
@ -918,6 +920,29 @@ static void r9a06g032_clocks_del_clk_provider(void *data)
of_clk_del_provider(data);
}
static void __init r9a06g032_init_h2mode(struct r9a06g032_priv *clocks)
{
struct device_node *usbf_np = NULL;
u32 usb;
while ((usbf_np = of_find_compatible_node(usbf_np, NULL,
"renesas,rzn1-usbf"))) {
if (of_device_is_available(usbf_np))
break;
}
usb = readl(clocks->reg + R9A06G032_SYSCTRL_USB);
if (usbf_np) {
/* 1 host and 1 device mode */
usb &= ~R9A06G032_SYSCTRL_USB_H2MODE;
of_node_put(usbf_np);
} else {
/* 2 hosts mode */
usb |= R9A06G032_SYSCTRL_USB_H2MODE;
}
writel(usb, clocks->reg + R9A06G032_SYSCTRL_USB);
}
static int __init r9a06g032_clocks_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@ -947,6 +972,9 @@ static int __init r9a06g032_clocks_probe(struct platform_device *pdev)
clocks->reg = of_iomap(np, 0);
if (WARN_ON(!clocks->reg))
return -ENOMEM;
r9a06g032_init_h2mode(clocks);
for (i = 0; i < ARRAY_SIZE(r9a06g032_clocks); ++i) {
const struct r9a06g032_clkdesc *d = &r9a06g032_clocks[i];
const char *parent_name = d->source ?

View File

@ -4150,17 +4150,6 @@ void intel_execlists_show_requests(struct intel_engine_cs *engine,
spin_unlock_irqrestore(&sched_engine->lock, flags);
}
static unsigned long list_count(struct list_head *list)
{
struct list_head *pos;
unsigned long count = 0;
list_for_each(pos, list)
count++;
return count;
}
void intel_execlists_dump_active_requests(struct intel_engine_cs *engine,
struct i915_request *hung_rq,
struct drm_printer *m)
@ -4171,8 +4160,8 @@ void intel_execlists_dump_active_requests(struct intel_engine_cs *engine,
intel_engine_dump_active_requests(&engine->sched_engine->requests, hung_rq, m);
drm_printf(m, "\tOn hold?: %lu\n",
list_count(&engine->sched_engine->hold));
drm_printf(m, "\tOn hold?: %zu\n",
list_count_nodes(&engine->sched_engine->hold));
spin_unlock_irqrestore(&engine->sched_engine->lock, flags);
}

View File

@ -259,8 +259,8 @@ static const struct pci_device_id gpu_i2c_ids[] = {
MODULE_DEVICE_TABLE(pci, gpu_i2c_ids);
static const struct property_entry ccgx_props[] = {
/* Use FW built for NVIDIA (nv) only */
PROPERTY_ENTRY_U16("ccgx,firmware-build", ('n' << 8) | 'v'),
/* Use FW built for NVIDIA GPU only */
PROPERTY_ENTRY_STRING("firmware-name", "nvidia,gpu"),
{ }
};

View File

@ -14,6 +14,9 @@ config TTPCI_EEPROM
tristate
depends on I2C
config UVC_COMMON
tristate
config VIDEO_CX2341X
tristate

View File

@ -5,5 +5,6 @@ obj-y += b2c2/ siano/ v4l2-tpg/ videobuf2/
# (e. g. LC_ALL=C sort Makefile)
obj-$(CONFIG_CYPRESS_FIRMWARE) += cypress_firmware.o
obj-$(CONFIG_TTPCI_EEPROM) += ttpci-eeprom.o
obj-$(CONFIG_UVC_COMMON) += uvc.o
obj-$(CONFIG_VIDEO_CX2341X) += cx2341x.o
obj-$(CONFIG_VIDEO_TVEEPROM) += tveeprom.o

183
drivers/media/common/uvc.c Normal file
View File

@ -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");

View File

@ -3,6 +3,7 @@ config USB_VIDEO_CLASS
tristate "USB Video Class (UVC)"
depends on VIDEO_DEV
select VIDEOBUF2_VMALLOC
select UVC_COMMON
help
Support for the USB Video Class (UVC). Currently only video
input devices, such as webcams, are supported.

View File

@ -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);
}

View File

@ -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;
@ -251,14 +252,10 @@ static int uvc_parse_format(struct uvc_device *dev,
fmtdesc = uvc_format_by_guid(&buffer[5]);
if (fmtdesc != NULL) {
strscpy(format->name, fmtdesc->name,
sizeof(format->name));
format->fcc = fmtdesc->fcc;
} else {
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;
}
@ -270,8 +267,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 +307,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 +322,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 +330,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 +356,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];
@ -732,6 +713,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 +795,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 +882,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 +900,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 +1006,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 +1046,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 +1067,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 +1096,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 +1123,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 +1156,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 +1840,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 +1857,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 +2193,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 +2341,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 +2365,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 +2474,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 +2969,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 +2996,24 @@ 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 },
/* 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,

View File

@ -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)

View File

@ -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);
}

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -256,7 +256,7 @@ static ssize_t of_device_get_modalias(struct device *dev, char *str, ssize_t len
ssize_t csize;
ssize_t tsize;
if ((!dev) || (!dev->of_node))
if ((!dev) || (!dev->of_node) || dev->of_node_reused)
return -ENODEV;
/* Name & Type */
@ -376,7 +376,7 @@ int of_device_uevent_modalias(struct device *dev, struct kobj_uevent_env *env)
{
int sl;
if ((!dev) || (!dev->of_node))
if ((!dev) || (!dev->of_node) || dev->of_node_reused)
return -ENODEV;
/* Devicetree modalias is tricky, we add it in 2 steps */
@ -385,6 +385,8 @@ int of_device_uevent_modalias(struct device *dev, struct kobj_uevent_env *env)
sl = of_device_get_modalias(dev, &env->buf[env->buflen-1],
sizeof(env->buf) - env->buflen);
if (sl < 0)
return sl;
if (sl >= (sizeof(env->buf) - env->buflen))
return -ENOMEM;
env->buflen += sl;

View File

@ -7,4 +7,5 @@ phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_132_SOC) += xusb-tegra124.o
phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_210_SOC) += xusb-tegra210.o
phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_186_SOC) += xusb-tegra186.o
phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_194_SOC) += xusb-tegra186.o
phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_234_SOC) += xusb-tegra186.o
obj-$(CONFIG_PHY_TEGRA194_P2U) += phy-tegra194-p2u.o

View File

@ -89,6 +89,11 @@
#define USB2_TRK_START_TIMER(x) (((x) & 0x7f) << 12)
#define USB2_TRK_DONE_RESET_TIMER(x) (((x) & 0x7f) << 19)
#define USB2_PD_TRK BIT(26)
#define USB2_TRK_COMPLETED BIT(31)
#define XUSB_PADCTL_USB2_BIAS_PAD_CTL2 0x28c
#define USB2_TRK_HW_MODE BIT(0)
#define CYA_TRK_CODE_UPDATE_ON_IDLE BIT(31)
#define XUSB_PADCTL_HSIC_PADX_CTL0(x) (0x300 + (x) * 0x20)
#define HSIC_PD_TX_DATA0 BIT(1)
@ -609,6 +614,32 @@ static void tegra186_utmi_bias_pad_power_on(struct tegra_xusb_padctl *padctl)
value &= ~USB2_PD_TRK;
padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
if (padctl->soc->poll_trk_completed) {
err = padctl_readl_poll(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1,
USB2_TRK_COMPLETED, USB2_TRK_COMPLETED, 100);
if (err) {
/* The failure with polling on trk complete will not
* cause the failure of powering on the bias pad.
*/
dev_warn(dev, "failed to poll USB2 trk completed: %d\n", err);
}
value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
value |= USB2_TRK_COMPLETED;
padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
} else {
udelay(100);
}
if (padctl->soc->trk_hw_mode) {
value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL2);
value |= USB2_TRK_HW_MODE;
value &= ~CYA_TRK_CODE_UPDATE_ON_IDLE;
padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL2);
} else {
clk_disable_unprepare(priv->usb2_trk_clk);
}
mutex_unlock(&padctl->lock);
}
@ -633,7 +664,12 @@ static void tegra186_utmi_bias_pad_power_off(struct tegra_xusb_padctl *padctl)
value |= USB2_PD_TRK;
padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
clk_disable_unprepare(priv->usb2_trk_clk);
if (padctl->soc->trk_hw_mode) {
value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL2);
value &= ~USB2_TRK_HW_MODE;
padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL2);
clk_disable_unprepare(priv->usb2_trk_clk);
}
mutex_unlock(&padctl->lock);
}
@ -1557,7 +1593,8 @@ const struct tegra_xusb_padctl_soc tegra186_xusb_padctl_soc = {
EXPORT_SYMBOL_GPL(tegra186_xusb_padctl_soc);
#endif
#if IS_ENABLED(CONFIG_ARCH_TEGRA_194_SOC)
#if IS_ENABLED(CONFIG_ARCH_TEGRA_194_SOC) || \
IS_ENABLED(CONFIG_ARCH_TEGRA_234_SOC)
static const char * const tegra194_xusb_padctl_supply_names[] = {
"avdd-usb",
"vclamp-usb",
@ -1613,8 +1650,31 @@ const struct tegra_xusb_padctl_soc tegra194_xusb_padctl_soc = {
.supply_names = tegra194_xusb_padctl_supply_names,
.num_supplies = ARRAY_SIZE(tegra194_xusb_padctl_supply_names),
.supports_gen2 = true,
.poll_trk_completed = true,
};
EXPORT_SYMBOL_GPL(tegra194_xusb_padctl_soc);
const struct tegra_xusb_padctl_soc tegra234_xusb_padctl_soc = {
.num_pads = ARRAY_SIZE(tegra194_pads),
.pads = tegra194_pads,
.ports = {
.usb2 = {
.ops = &tegra186_usb2_port_ops,
.count = 4,
},
.usb3 = {
.ops = &tegra186_usb3_port_ops,
.count = 4,
},
},
.ops = &tegra186_xusb_padctl_ops,
.supply_names = tegra194_xusb_padctl_supply_names,
.num_supplies = ARRAY_SIZE(tegra194_xusb_padctl_supply_names),
.supports_gen2 = true,
.poll_trk_completed = true,
.trk_hw_mode = true,
};
EXPORT_SYMBOL_GPL(tegra234_xusb_padctl_soc);
#endif
MODULE_AUTHOR("JC Kuo <jckuo@nvidia.com>");

View File

@ -71,6 +71,12 @@ static const struct of_device_id tegra_xusb_padctl_of_match[] = {
.compatible = "nvidia,tegra194-xusb-padctl",
.data = &tegra194_xusb_padctl_soc,
},
#endif
#if defined(CONFIG_ARCH_TEGRA_234_SOC)
{
.compatible = "nvidia,tegra234-xusb-padctl",
.data = &tegra234_xusb_padctl_soc,
},
#endif
{ }
};

View File

@ -8,6 +8,7 @@
#define __PHY_TEGRA_XUSB_H
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/mutex.h>
#include <linux/workqueue.h>
@ -431,6 +432,8 @@ struct tegra_xusb_padctl_soc {
unsigned int num_supplies;
bool supports_gen2;
bool need_fake_usb3_port;
bool poll_trk_completed;
bool trk_hw_mode;
};
struct tegra_xusb_padctl {
@ -473,6 +476,23 @@ static inline u32 padctl_readl(struct tegra_xusb_padctl *padctl,
return value;
}
static inline u32 padctl_readl_poll(struct tegra_xusb_padctl *padctl,
unsigned long offset, u32 val, u32 mask,
int us)
{
u32 regval;
int err;
err = readl_poll_timeout(padctl->regs + offset, regval,
(regval & mask) == val, 1, us);
if (err) {
dev_err(padctl->dev, "%08lx poll timeout > %08x\n", offset,
regval);
}
return err;
}
struct tegra_xusb_lane *tegra_xusb_find_lane(struct tegra_xusb_padctl *padctl,
const char *name,
unsigned int index);
@ -489,5 +509,8 @@ extern const struct tegra_xusb_padctl_soc tegra186_xusb_padctl_soc;
#if defined(CONFIG_ARCH_TEGRA_194_SOC)
extern const struct tegra_xusb_padctl_soc tegra194_xusb_padctl_soc;
#endif
#if defined(CONFIG_ARCH_TEGRA_234_SOC)
extern const struct tegra_xusb_padctl_soc tegra234_xusb_padctl_soc;
#endif
#endif /* __PHY_TEGRA_XUSB_H */

View File

@ -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

View File

@ -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);
}

View File

@ -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,

View File

@ -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;

View File

@ -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");

View File

@ -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");

View File

@ -223,6 +223,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 +264,9 @@ 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
*
* In USB4 terminology this structure represents an adapter (protocol or
* lane adapter).
@ -272,6 +292,8 @@ 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;
};
/**
@ -1047,7 +1069,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);
@ -1238,6 +1260,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;

View File

@ -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

View File

@ -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

View File

@ -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), \
@ -339,9 +345,10 @@ static bool tb_dp_is_usb4(const struct tb_switch *sw)
return tb_switch_is_usb4(sw) || tb_switch_is_titan_ridge(sw);
}
static int tb_dp_cm_handshake(struct tb_port *in, struct tb_port *out)
static int tb_dp_cm_handshake(struct tb_port *in, struct tb_port *out,
int timeout_msec)
{
int timeout = 10;
ktime_t timeout = ktime_add_ms(ktime_get(), timeout_msec);
u32 val;
int ret;
@ -368,8 +375,8 @@ static int tb_dp_cm_handshake(struct tb_port *in, struct tb_port *out)
return ret;
if (!(val & DP_STATUS_CTRL_CMHS))
return 0;
usleep_range(10, 100);
} while (timeout--);
usleep_range(100, 150);
} while (ktime_before(ktime_get(), timeout));
return -ETIMEDOUT;
}
@ -519,7 +526,7 @@ static int tb_dp_xchg_caps(struct tb_tunnel *tunnel)
* Perform connection manager handshake between IN and OUT ports
* before capabilities exchange can take place.
*/
ret = tb_dp_cm_handshake(in, out);
ret = tb_dp_cm_handshake(in, out, 1500);
if (ret)
return ret;
@ -597,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;
@ -634,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;
@ -798,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;
@ -887,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;
@ -1713,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

View File

@ -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);

View File

@ -2186,3 +2186,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;
}

View File

@ -378,7 +378,7 @@ int cdnsp_ep_enqueue(struct cdnsp_ep *pep, struct cdnsp_request *preq)
ret = cdnsp_queue_bulk_tx(pdev, preq);
break;
case USB_ENDPOINT_XFER_ISOC:
ret = cdnsp_queue_isoc_tx_prepare(pdev, preq);
ret = cdnsp_queue_isoc_tx(pdev, preq);
}
if (ret)

View File

@ -1532,8 +1532,8 @@ void cdnsp_queue_stop_endpoint(struct cdnsp_device *pdev,
unsigned int ep_index);
int cdnsp_queue_ctrl_tx(struct cdnsp_device *pdev, struct cdnsp_request *preq);
int cdnsp_queue_bulk_tx(struct cdnsp_device *pdev, struct cdnsp_request *preq);
int cdnsp_queue_isoc_tx_prepare(struct cdnsp_device *pdev,
struct cdnsp_request *preq);
int cdnsp_queue_isoc_tx(struct cdnsp_device *pdev,
struct cdnsp_request *preq);
void cdnsp_queue_configure_endpoint(struct cdnsp_device *pdev,
dma_addr_t in_ctx_ptr);
void cdnsp_queue_reset_ep(struct cdnsp_device *pdev, unsigned int ep_index);

View File

@ -1333,6 +1333,20 @@ static int cdnsp_handle_tx_event(struct cdnsp_device *pdev,
ep_ring->dequeue, td->last_trb,
ep_trb_dma);
desc = td->preq->pep->endpoint.desc;
if (ep_seg) {
ep_trb = &ep_seg->trbs[(ep_trb_dma - ep_seg->dma)
/ sizeof(*ep_trb)];
trace_cdnsp_handle_transfer(ep_ring,
(struct cdnsp_generic_trb *)ep_trb);
if (pep->skip && usb_endpoint_xfer_isoc(desc) &&
td->last_trb != ep_trb)
return -EAGAIN;
}
/*
* Skip the Force Stopped Event. The event_trb(ep_trb_dma)
* of FSE is not in the current TD pointed by ep_ring->dequeue
@ -1347,7 +1361,6 @@ static int cdnsp_handle_tx_event(struct cdnsp_device *pdev,
goto cleanup;
}
desc = td->preq->pep->endpoint.desc;
if (!ep_seg) {
if (!pep->skip || !usb_endpoint_xfer_isoc(desc)) {
/* Something is busted, give up! */
@ -1374,12 +1387,6 @@ static int cdnsp_handle_tx_event(struct cdnsp_device *pdev,
goto cleanup;
}
ep_trb = &ep_seg->trbs[(ep_trb_dma - ep_seg->dma)
/ sizeof(*ep_trb)];
trace_cdnsp_handle_transfer(ep_ring,
(struct cdnsp_generic_trb *)ep_trb);
if (cdnsp_trb_is_noop(ep_trb))
goto cleanup;
@ -1726,11 +1733,6 @@ static unsigned int count_sg_trbs_needed(struct cdnsp_request *preq)
return num_trbs;
}
static unsigned int count_isoc_trbs_needed(struct cdnsp_request *preq)
{
return cdnsp_count_trbs(preq->request.dma, preq->request.length);
}
static void cdnsp_check_trb_math(struct cdnsp_request *preq, int running_total)
{
if (running_total != preq->request.length)
@ -2192,28 +2194,48 @@ static unsigned int
}
/* Queue function isoc transfer */
static int cdnsp_queue_isoc_tx(struct cdnsp_device *pdev,
struct cdnsp_request *preq)
int cdnsp_queue_isoc_tx(struct cdnsp_device *pdev,
struct cdnsp_request *preq)
{
int trb_buff_len, td_len, td_remain_len, ret;
unsigned int trb_buff_len, td_len, td_remain_len, block_len;
unsigned int burst_count, last_burst_pkt;
unsigned int total_pkt_count, max_pkt;
struct cdnsp_generic_trb *start_trb;
struct scatterlist *sg = NULL;
bool more_trbs_coming = true;
struct cdnsp_ring *ep_ring;
unsigned int num_sgs = 0;
int running_total = 0;
u32 field, length_field;
u64 addr, send_addr;
int start_cycle;
int trbs_per_td;
u64 addr;
int i;
int i, sent_len, ret;
ep_ring = preq->pep->ring;
td_len = preq->request.length;
if (preq->request.num_sgs) {
num_sgs = preq->request.num_sgs;
sg = preq->request.sg;
addr = (u64)sg_dma_address(sg);
block_len = sg_dma_len(sg);
trbs_per_td = count_sg_trbs_needed(preq);
} else {
addr = (u64)preq->request.dma;
block_len = td_len;
trbs_per_td = count_trbs_needed(preq);
}
ret = cdnsp_prepare_transfer(pdev, preq, trbs_per_td);
if (ret)
return ret;
start_trb = &ep_ring->enqueue->generic;
start_cycle = ep_ring->cycle_state;
td_len = preq->request.length;
addr = (u64)preq->request.dma;
td_remain_len = td_len;
send_addr = addr;
max_pkt = usb_endpoint_maxp(preq->pep->endpoint.desc);
total_pkt_count = DIV_ROUND_UP(td_len, max_pkt);
@ -2225,11 +2247,6 @@ static int cdnsp_queue_isoc_tx(struct cdnsp_device *pdev,
burst_count = cdnsp_get_burst_count(pdev, preq, total_pkt_count);
last_burst_pkt = cdnsp_get_last_burst_packet_count(pdev, preq,
total_pkt_count);
trbs_per_td = count_isoc_trbs_needed(preq);
ret = cdnsp_prepare_transfer(pdev, preq, trbs_per_td);
if (ret)
goto cleanup;
/*
* Set isoc specific data for the first TRB in a TD.
@ -2248,6 +2265,7 @@ static int cdnsp_queue_isoc_tx(struct cdnsp_device *pdev,
/* Calculate TRB length. */
trb_buff_len = TRB_BUFF_LEN_UP_TO_BOUNDARY(addr);
trb_buff_len = min(trb_buff_len, block_len);
if (trb_buff_len > td_remain_len)
trb_buff_len = td_remain_len;
@ -2256,7 +2274,8 @@ static int cdnsp_queue_isoc_tx(struct cdnsp_device *pdev,
trb_buff_len, td_len, preq,
more_trbs_coming, 0);
length_field = TRB_LEN(trb_buff_len) | TRB_INTR_TARGET(0);
length_field = TRB_LEN(trb_buff_len) | TRB_TD_SIZE(remainder) |
TRB_INTR_TARGET(0);
/* Only first TRB is isoc, overwrite otherwise. */
if (i) {
@ -2281,12 +2300,27 @@ static int cdnsp_queue_isoc_tx(struct cdnsp_device *pdev,
}
cdnsp_queue_trb(pdev, ep_ring, more_trbs_coming,
lower_32_bits(addr), upper_32_bits(addr),
lower_32_bits(send_addr), upper_32_bits(send_addr),
length_field, field);
running_total += trb_buff_len;
addr += trb_buff_len;
td_remain_len -= trb_buff_len;
sent_len = trb_buff_len;
while (sg && sent_len >= block_len) {
/* New sg entry */
--num_sgs;
sent_len -= block_len;
if (num_sgs != 0) {
sg = sg_next(sg);
block_len = sg_dma_len(sg);
addr = (u64)sg_dma_address(sg);
addr += sent_len;
}
}
block_len -= sent_len;
send_addr = addr;
}
/* Check TD length */
@ -2324,30 +2358,6 @@ cleanup:
return ret;
}
int cdnsp_queue_isoc_tx_prepare(struct cdnsp_device *pdev,
struct cdnsp_request *preq)
{
struct cdnsp_ring *ep_ring;
u32 ep_state;
int num_trbs;
int ret;
ep_ring = preq->pep->ring;
ep_state = GET_EP_CTX_STATE(preq->pep->out_ctx);
num_trbs = count_isoc_trbs_needed(preq);
/*
* Check the ring to guarantee there is enough room for the whole
* request. Do not insert any td of the USB Request to the ring if the
* check failed.
*/
ret = cdnsp_prepare_ring(pdev, ep_ring, ep_state, num_trbs, GFP_ATOMIC);
if (ret)
return ret;
return cdnsp_queue_isoc_tx(pdev, preq);
}
/**** Command Ring Operations ****/
/*
* Generic function for queuing a command TRB on the command ring.

View File

@ -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;
}
}
}

View File

@ -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);
}

View File

@ -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,

View File

@ -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);

View File

@ -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

View File

@ -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;
}

View File

@ -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);
}
/*

View File

@ -1117,6 +1117,7 @@ struct dwc3_scratchpad_array {
* address.
* @num_ep_resized: carries the current number endpoints which have had its tx
* fifo resized.
* @debug_root: root debugfs directory for this device to put its files in.
*/
struct dwc3 {
struct work_struct drd_work;
@ -1332,6 +1333,7 @@ struct dwc3 {
int max_cfg_eps;
int last_fifo_depth;
int num_ep_resized;
struct dentry *debug_root;
};
#define INCRX_BURST_MODE 0

View File

@ -414,11 +414,14 @@ static inline const char *dwc3_gadget_generic_cmd_status_string(int status)
#ifdef CONFIG_DEBUG_FS
extern void dwc3_debugfs_create_endpoint_dir(struct dwc3_ep *dep);
extern void dwc3_debugfs_remove_endpoint_dir(struct dwc3_ep *dep);
extern void dwc3_debugfs_init(struct dwc3 *d);
extern void dwc3_debugfs_exit(struct dwc3 *d);
#else
static inline void dwc3_debugfs_create_endpoint_dir(struct dwc3_ep *dep)
{ }
static inline void dwc3_debugfs_remove_endpoint_dir(struct dwc3_ep *dep)
{ }
static inline void dwc3_debugfs_init(struct dwc3 *d)
{ }
static inline void dwc3_debugfs_exit(struct dwc3 *d)

View File

@ -873,27 +873,23 @@ static const struct dwc3_ep_file_map dwc3_ep_file_map[] = {
{ "GDBGEPINFO", &dwc3_ep_info_register_fops, },
};
static void dwc3_debugfs_create_endpoint_files(struct dwc3_ep *dep,
struct dentry *parent)
void dwc3_debugfs_create_endpoint_dir(struct dwc3_ep *dep)
{
struct dentry *dir;
int i;
dir = debugfs_create_dir(dep->name, dep->dwc->debug_root);
for (i = 0; i < ARRAY_SIZE(dwc3_ep_file_map); i++) {
const struct file_operations *fops = dwc3_ep_file_map[i].fops;
const char *name = dwc3_ep_file_map[i].name;
debugfs_create_file(name, 0444, parent, dep, fops);
debugfs_create_file(name, 0444, dir, dep, fops);
}
}
void dwc3_debugfs_create_endpoint_dir(struct dwc3_ep *dep)
void dwc3_debugfs_remove_endpoint_dir(struct dwc3_ep *dep)
{
struct dentry *dir;
struct dentry *root;
root = debugfs_lookup(dev_name(dep->dwc->dev), usb_debug_root);
dir = debugfs_create_dir(dep->name, root);
dwc3_debugfs_create_endpoint_files(dep, dir);
debugfs_lookup_and_remove(dep->name, dep->dwc->debug_root);
}
void dwc3_debugfs_init(struct dwc3 *dwc)
@ -911,6 +907,7 @@ void dwc3_debugfs_init(struct dwc3 *dwc)
dwc->regset->base = dwc->regs - DWC3_GLOBALS_REGS_START;
root = debugfs_create_dir(dev_name(dwc->dev), usb_debug_root);
dwc->debug_root = root;
debugfs_create_regset32("regdump", 0444, root, dwc->regset);
debugfs_create_file("lsp_dump", 0644, root, dwc, &dwc3_lsp_fops);
@ -929,6 +926,6 @@ void dwc3_debugfs_init(struct dwc3 *dwc)
void dwc3_debugfs_exit(struct dwc3 *dwc)
{
debugfs_remove(debugfs_lookup(dev_name(dwc->dev), usb_debug_root));
debugfs_lookup_and_remove(dev_name(dwc->dev), usb_debug_root);
kfree(dwc->regset);
}

View File

@ -47,6 +47,7 @@
#define PCI_DEVICE_ID_INTEL_ADLS 0x7ae1
#define PCI_DEVICE_ID_INTEL_RPL 0xa70e
#define PCI_DEVICE_ID_INTEL_RPLS 0x7a61
#define PCI_DEVICE_ID_INTEL_MTLM 0x7eb1
#define PCI_DEVICE_ID_INTEL_MTLP 0x7ec1
#define PCI_DEVICE_ID_INTEL_MTL 0x7e7e
#define PCI_DEVICE_ID_INTEL_TGL 0x9a15
@ -467,6 +468,9 @@ static const struct pci_device_id dwc3_pci_id_table[] = {
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPLS),
(kernel_ulong_t) &dwc3_pci_intel_swnode, },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTLM),
(kernel_ulong_t) &dwc3_pci_intel_swnode, },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTLP),
(kernel_ulong_t) &dwc3_pci_intel_swnode, },

View File

@ -14,7 +14,6 @@
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/gpio/consumer.h>
#include <linux/of_gpio.h>
#include <linux/of_platform.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>

View File

@ -3194,9 +3194,7 @@ static void dwc3_gadget_free_endpoints(struct dwc3 *dwc)
list_del(&dep->endpoint.ep_list);
}
debugfs_remove_recursive(debugfs_lookup(dep->name,
debugfs_lookup(dev_name(dep->dwc->dev),
usb_debug_root)));
dwc3_debugfs_remove_endpoint_dir(dep);
kfree(dep);
}
}

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