mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2024-12-29 01:02:08 +00:00
USB / Thunderbolt (USB4) changes for 6.13-rc1
Here is the big set of USB and Thunderbolt changes for 6.13-rc1. Overall, a pretty slow development cycle, the majority of the work going into the debugfs interface for the thunderbolt (i.e. USB4) code, to help with debugging the myrad ways that hardware vendors get their interfaces messed up. Other than that, here's the highlights: - thunderbolt changes and additions to debugfs interfaces - lots of device tree updates for new and old hardware - UVC configfs gadget updates and new apis for features - xhci driver updates and fixes - dwc3 driver updates and fixes - typec driver updates and fixes - lots of other small updates and fixes, full details in the shortlog All of these have been in linux-next for a while with no reported problems. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- iG0EABECAC0WIQT0tgzFv3jCIUoxPcsxR9QN2y37KQUCZ0lBqA8cZ3JlZ0Brcm9h aC5jb20ACgkQMUfUDdst+ynTXQCfSs0ldBqZoINU/22q8BUg7ybb+pcAoL5EbbEm b2igfp6YIEWAtUkactmO =gwwq -----END PGP SIGNATURE----- Merge tag 'usb-6.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb Pull USB / Thunderbolt updates from Greg KH: "Here is the big set of USB and Thunderbolt changes for 6.13-rc1. Overall, a pretty slow development cycle, the majority of the work going into the debugfs interface for the thunderbolt (i.e. USB4) code, to help with debugging the myrad ways that hardware vendors get their interfaces messed up. Other than that, here's the highlights: - thunderbolt changes and additions to debugfs interfaces - lots of device tree updates for new and old hardware - UVC configfs gadget updates and new apis for features - xhci driver updates and fixes - dwc3 driver updates and fixes - typec driver updates and fixes - lots of other small updates and fixes, full details in the shortlog All of these have been in linux-next for a while with no reported problems" * tag 'usb-6.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (148 commits) usb: typec: tcpm: Add support for sink-bc12-completion-time-ms DT property dt-bindings: usb: maxim,max33359: add usage of sink bc12 time property dt-bindings: connector: Add time property for Sink BC12 detection completion usb: dwc3: gadget: Remove dwc3_request->needs_extra_trb usb: dwc3: gadget: Cleanup SG handling usb: dwc3: gadget: Fix looping of queued SG entries usb: dwc3: gadget: Fix checking for number of TRBs left usb: dwc3: ep0: Don't clear ep0 DWC3_EP_TRANSFER_STARTED Revert "usb: gadget: composite: fix OS descriptors w_value logic" usb: ehci-spear: fix call balance of sehci clk handling routines USB: make to_usb_device_driver() use container_of_const() USB: make to_usb_driver() use container_of_const() USB: properly lock dynamic id list when showing an id USB: make single lock for all usb dynamic id lists drivers/usb/storage: refactor min with min_t drivers/usb/serial: refactor min with min_t drivers/usb/musb: refactor min/max with min_t/max_t drivers/usb/mon: refactor min with min_t drivers/usb/misc: refactor min with min_t drivers/usb/host: refactor min/max with min_t/max_t ...
This commit is contained in:
commit
e33a6d83e1
@ -342,6 +342,70 @@ Description: Specific uncompressed frame descriptors
|
||||
support
|
||||
========================= =====================================
|
||||
|
||||
What: /config/usb-gadget/gadget/functions/uvc.name/streaming/framebased
|
||||
Date: Sept 2024
|
||||
KernelVersion: 5.15
|
||||
Description: Framebased format descriptors
|
||||
|
||||
What: /config/usb-gadget/gadget/functions/uvc.name/streaming/framebased/name
|
||||
Date: Sept 2024
|
||||
KernelVersion: 5.15
|
||||
Description: Specific framebased format descriptors
|
||||
|
||||
================== =======================================
|
||||
bFormatIndex unique id for this format descriptor;
|
||||
only defined after parent header is
|
||||
linked into the streaming class;
|
||||
read-only
|
||||
bmaControls this format's data for bmaControls in
|
||||
the streaming header
|
||||
bmInterlaceFlags specifies interlace information,
|
||||
read-only
|
||||
bAspectRatioY the X dimension of the picture aspect
|
||||
ratio, read-only
|
||||
bAspectRatioX the Y dimension of the picture aspect
|
||||
ratio, read-only
|
||||
bDefaultFrameIndex optimum frame index for this stream
|
||||
bBitsPerPixel number of bits per pixel used to
|
||||
specify color in the decoded video
|
||||
frame
|
||||
guidFormat globally unique id used to identify
|
||||
stream-encoding format
|
||||
================== =======================================
|
||||
|
||||
What: /config/usb-gadget/gadget/functions/uvc.name/streaming/framebased/name/name
|
||||
Date: Sept 2024
|
||||
KernelVersion: 5.15
|
||||
Description: Specific framebased frame descriptors
|
||||
|
||||
========================= =====================================
|
||||
bFrameIndex unique id for this framedescriptor;
|
||||
only defined after parent format is
|
||||
linked into the streaming header;
|
||||
read-only
|
||||
dwFrameInterval indicates how frame interval can be
|
||||
programmed; a number of values
|
||||
separated by newline can be specified
|
||||
dwDefaultFrameInterval the frame interval the device would
|
||||
like to use as default
|
||||
dwBytesPerLine Specifies the number of bytes per line
|
||||
of video for packed fixed frame size
|
||||
formats, allowing the receiver to
|
||||
perform stride alignment of the video.
|
||||
If the bVariableSize value (above) is
|
||||
TRUE (1), or if the format does not
|
||||
permit such alignment, this value shall
|
||||
be set to zero (0).
|
||||
dwMaxBitRate the maximum bit rate at the shortest
|
||||
frame interval in bps
|
||||
dwMinBitRate the minimum bit rate at the longest
|
||||
frame interval in bps
|
||||
wHeight height of decoded bitmap frame in px
|
||||
wWidth width of decoded bitmam frame in px
|
||||
bmCapabilities still image support, fixed frame-rate
|
||||
support
|
||||
========================= =====================================
|
||||
|
||||
What: /config/usb-gadget/gadget/functions/uvc.name/streaming/header
|
||||
Date: Dec 2014
|
||||
KernelVersion: 4.0
|
||||
|
@ -149,6 +149,19 @@ Description:
|
||||
advertise to the partner. The currently used capabilities are in
|
||||
brackets. Selection happens by writing to the file.
|
||||
|
||||
What: /sys/class/typec/<port>/usb_capability
|
||||
Date: November 2024
|
||||
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
|
||||
Description: Lists the supported USB Modes. The default USB mode that is used
|
||||
next time with the Enter_USB Message is in brackets. The default
|
||||
mode can be changed by writing to the file when supported by the
|
||||
driver.
|
||||
|
||||
Valid values:
|
||||
- usb2 (USB 2.0)
|
||||
- usb3 (USB 3.2)
|
||||
- usb4 (USB4)
|
||||
|
||||
USB Type-C partner devices (eg. /sys/class/typec/port0-partner/)
|
||||
|
||||
What: /sys/class/typec/<port>-partner/accessory_mode
|
||||
@ -220,6 +233,20 @@ Description:
|
||||
directory exists, it will have an attribute file for every VDO
|
||||
in Discover Identity command result.
|
||||
|
||||
What: /sys/class/typec/<port>-partner/usb_mode
|
||||
Date: November 2024
|
||||
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
|
||||
Description: The USB Modes that the partner device supports. The active mode
|
||||
is displayed in brackets. The active USB mode can be changed by
|
||||
writing to this file when the port driver is able to send Data
|
||||
Reset Message to the partner. That requires USB Power Delivery
|
||||
contract between the partner and the port.
|
||||
|
||||
Valid values:
|
||||
- usb2 (USB 2.0)
|
||||
- usb3 (USB 3.2)
|
||||
- usb4 (USB4)
|
||||
|
||||
USB Type-C cable devices (eg. /sys/class/typec/port0-cable/)
|
||||
|
||||
Note: Electronically Marked Cables will have a device also for one cable plug
|
||||
|
@ -253,6 +253,46 @@ properties:
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
sink-wait-cap-time-ms:
|
||||
description: Represents the max time in ms that USB Type-C port (in sink
|
||||
role) should wait for the port partner (source role) to send source caps.
|
||||
SinkWaitCap timer starts when port in sink role attaches to the source.
|
||||
This timer will stop when sink receives PD source cap advertisement before
|
||||
timeout in which case it'll move to capability negotiation stage. A
|
||||
timeout leads to a hard reset message by the port.
|
||||
minimum: 310
|
||||
maximum: 620
|
||||
default: 310
|
||||
|
||||
ps-source-off-time-ms:
|
||||
description: Represents the max time in ms that a DRP in source role should
|
||||
take to turn off power after the PsSourceOff timer starts. PsSourceOff
|
||||
timer starts when a sink's PHY layer receives EOP of the GoodCRC message
|
||||
(corresponding to an Accept message sent in response to a PR_Swap or a
|
||||
FR_Swap request). This timer stops when last bit of GoodCRC EOP
|
||||
corresponding to the received PS_RDY message is transmitted by the PHY
|
||||
layer. A timeout shall lead to error recovery in the type-c port.
|
||||
minimum: 750
|
||||
maximum: 920
|
||||
default: 920
|
||||
|
||||
cc-debounce-time-ms:
|
||||
description: Represents the max time in ms that a port shall wait to
|
||||
determine if it's attached to a partner.
|
||||
minimum: 100
|
||||
maximum: 200
|
||||
default: 200
|
||||
|
||||
sink-bc12-completion-time-ms:
|
||||
description: Represents the max time in ms that a port in sink role takes
|
||||
to complete Battery Charger (BC1.2) Detection. BC1.2 detection is a
|
||||
hardware mechanism, which in some TCPC implementations, can run in
|
||||
parallel once the Type-C connection state machine reaches the "potential
|
||||
connect as sink" state. In TCPCs where this causes delays to respond to
|
||||
the incoming PD messages, sink-bc12-completion-time-ms is used to delay
|
||||
PD negotiation till BC1.2 detection completes.
|
||||
default: 0
|
||||
|
||||
dependencies:
|
||||
sink-vdos-v1: [ sink-vdos ]
|
||||
sink-vdos: [ sink-vdos-v1 ]
|
||||
@ -380,7 +420,7 @@ examples:
|
||||
};
|
||||
|
||||
# USB-C connector attached to a typec port controller(ptn5110), which has
|
||||
# power delivery support and enables drp.
|
||||
# power delivery support, explicitly defines time properties and enables drp.
|
||||
- |
|
||||
#include <dt-bindings/usb/pd.h>
|
||||
typec: ptn5110 {
|
||||
@ -393,6 +433,10 @@ examples:
|
||||
sink-pdos = <PDO_FIXED(5000, 2000, PDO_FIXED_USB_COMM)
|
||||
PDO_VAR(5000, 12000, 2000)>;
|
||||
op-sink-microwatt = <10000000>;
|
||||
sink-wait-cap-time-ms = <465>;
|
||||
ps-source-off-time-ms = <835>;
|
||||
cc-debounce-time-ms = <101>;
|
||||
sink-bc12-completion-time-ms = <500>;
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -11,12 +11,17 @@ maintainers:
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- fsl,imx8mq-usb-phy
|
||||
- fsl,imx8mp-usb-phy
|
||||
oneOf:
|
||||
- enum:
|
||||
- fsl,imx8mq-usb-phy
|
||||
- fsl,imx8mp-usb-phy
|
||||
- items:
|
||||
- const: fsl,imx95-usb-phy
|
||||
- const: fsl,imx8mp-usb-phy
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
|
||||
"#phy-cells":
|
||||
const: 0
|
||||
@ -89,7 +94,34 @@ required:
|
||||
- clocks
|
||||
- clock-names
|
||||
|
||||
additionalProperties: false
|
||||
allOf:
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- fsl,imx95-usb-phy
|
||||
then:
|
||||
properties:
|
||||
reg:
|
||||
items:
|
||||
- description: USB PHY Control range
|
||||
- description: USB PHY TCA Block range
|
||||
else:
|
||||
properties:
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- fsl,imx95-usb-phy
|
||||
then:
|
||||
$ref: /schemas/usb/usb-switch.yaml#
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
|
@ -18,6 +18,7 @@ properties:
|
||||
enum:
|
||||
- qcom,msm8998-qmp-usb3-phy
|
||||
- qcom,qcm2290-qmp-usb3-phy
|
||||
- qcom,qcs615-qmp-usb3-phy
|
||||
- qcom,sdm660-qmp-usb3-phy
|
||||
- qcom,sm6115-qmp-usb3-phy
|
||||
|
||||
@ -96,6 +97,7 @@ allOf:
|
||||
contains:
|
||||
enum:
|
||||
- qcom,msm8998-qmp-usb3-phy
|
||||
- qcom,qcs615-qmp-usb3-phy
|
||||
- qcom,sdm660-qmp-usb3-phy
|
||||
then:
|
||||
properties:
|
||||
|
@ -25,6 +25,7 @@ properties:
|
||||
- qcom,msm8996-qusb2-phy
|
||||
- qcom,msm8998-qusb2-phy
|
||||
- qcom,qcm2290-qusb2-phy
|
||||
- qcom,qcs615-qusb2-phy
|
||||
- qcom,sdm660-qusb2-phy
|
||||
- qcom,sm4250-qusb2-phy
|
||||
- qcom,sm6115-qusb2-phy
|
||||
|
@ -25,6 +25,7 @@ properties:
|
||||
- allwinner,sun20i-d1-musb
|
||||
- allwinner,sun50i-a100-musb
|
||||
- allwinner,sun50i-h6-musb
|
||||
- allwinner,sun55i-a523-musb
|
||||
- const: allwinner,sun8i-a33-musb
|
||||
- items:
|
||||
- const: allwinner,sun50i-h616-musb
|
||||
|
@ -61,18 +61,15 @@ 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>;
|
||||
interrupts = <2 IRQ_TYPE_LEVEL_LOW>;
|
||||
firmware-name = "nvidia,jetson-agx-xavier";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
@ -12,7 +12,11 @@ maintainers:
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: fsl,imx8mp-dwc3
|
||||
oneOf:
|
||||
- items:
|
||||
- const: fsl,imx95-dwc3
|
||||
- const: fsl,imx8mp-dwc3
|
||||
- const: fsl,imx8mp-dwc3
|
||||
|
||||
reg:
|
||||
items:
|
||||
|
@ -32,6 +32,7 @@ properties:
|
||||
- allwinner,sun50i-a64-ehci
|
||||
- allwinner,sun50i-h6-ehci
|
||||
- allwinner,sun50i-h616-ehci
|
||||
- allwinner,sun55i-a523-ehci
|
||||
- allwinner,sun5i-a13-ehci
|
||||
- allwinner,sun6i-a31-ehci
|
||||
- allwinner,sun7i-a20-ehci
|
||||
|
@ -19,6 +19,7 @@ properties:
|
||||
- allwinner,sun50i-a64-ohci
|
||||
- allwinner,sun50i-h6-ohci
|
||||
- allwinner,sun50i-h616-ohci
|
||||
- allwinner,sun55i-a523-ohci
|
||||
- allwinner,sun5i-a13-ohci
|
||||
- allwinner,sun6i-a31-ohci
|
||||
- allwinner,sun7i-a20-ohci
|
||||
|
@ -62,7 +62,14 @@ allOf:
|
||||
peer-hub: true
|
||||
vdd-supply: true
|
||||
|
||||
additionalProperties: false
|
||||
patternProperties:
|
||||
"^.*@[0-9a-f]{1,2}$":
|
||||
description: The hard wired USB devices
|
||||
type: object
|
||||
$ref: /schemas/usb/usb-device.yaml
|
||||
additionalProperties: true
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
|
@ -69,6 +69,7 @@ examples:
|
||||
PDO_FIXED_DATA_SWAP |
|
||||
PDO_FIXED_DUAL_ROLE)
|
||||
PDO_FIXED(9000, 2000, 0)>;
|
||||
sink-bc12-completion-time-ms = <500>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -14,8 +14,11 @@ maintainers:
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- microchip,mpfs-musb
|
||||
oneOf:
|
||||
- items:
|
||||
- const: microchip,pic64gx-musb
|
||||
- const: microchip,mpfs-musb
|
||||
- const: microchip,mpfs-musb
|
||||
|
||||
dr_mode: true
|
||||
|
||||
|
@ -29,6 +29,7 @@ properties:
|
||||
- qcom,qcs8300-dwc3
|
||||
- qcom,qdu1000-dwc3
|
||||
- qcom,sa8775p-dwc3
|
||||
- qcom,sar2130p-dwc3
|
||||
- qcom,sc7180-dwc3
|
||||
- qcom,sc7280-dwc3
|
||||
- qcom,sc8180x-dwc3
|
||||
@ -340,6 +341,7 @@ allOf:
|
||||
contains:
|
||||
enum:
|
||||
- qcom,qcm2290-dwc3
|
||||
- qcom,sar2130p-dwc3
|
||||
- qcom,sc8180x-dwc3
|
||||
- qcom,sc8180x-dwc3-mp
|
||||
- qcom,sm6115-dwc3
|
||||
|
@ -76,6 +76,10 @@ properties:
|
||||
Integer to use BUSWAIT register.
|
||||
|
||||
renesas,enable-gpio:
|
||||
deprecated: true
|
||||
maxItems: 1
|
||||
|
||||
renesas,enable-gpios:
|
||||
maxItems: 1
|
||||
description: |
|
||||
gpio specifier to check GPIO determining if USB function should be
|
||||
|
@ -27,6 +27,7 @@ select:
|
||||
enum:
|
||||
- rockchip,rk3328-dwc3
|
||||
- rockchip,rk3568-dwc3
|
||||
- rockchip,rk3576-dwc3
|
||||
- rockchip,rk3588-dwc3
|
||||
required:
|
||||
- compatible
|
||||
@ -37,6 +38,7 @@ properties:
|
||||
- enum:
|
||||
- rockchip,rk3328-dwc3
|
||||
- rockchip,rk3568-dwc3
|
||||
- rockchip,rk3576-dwc3
|
||||
- rockchip,rk3588-dwc3
|
||||
- const: snps,dwc3
|
||||
|
||||
@ -113,7 +115,9 @@ allOf:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: rockchip,rk3568-dwc3
|
||||
enum:
|
||||
- rockchip,rk3568-dwc3
|
||||
- rockchip,rk3576-dwc3
|
||||
then:
|
||||
properties:
|
||||
clocks:
|
||||
|
49
Documentation/devicetree/bindings/usb/ti,tusb1046.yaml
Normal file
49
Documentation/devicetree/bindings/usb/ti,tusb1046.yaml
Normal file
@ -0,0 +1,49 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/usb/ti,tusb1046.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Texas Instruments TUSB1046-DCI Type-C crosspoint switch
|
||||
|
||||
maintainers:
|
||||
- Romain Gantois <romain.gantois@bootlin.com>
|
||||
|
||||
allOf:
|
||||
- $ref: usb-switch.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: ti,tusb1046
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- port
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
typec-mux@44 {
|
||||
compatible = "ti,tusb1046";
|
||||
reg = <0x44>;
|
||||
|
||||
mode-switch;
|
||||
orientation-switch;
|
||||
|
||||
port {
|
||||
endpoint {
|
||||
remote-endpoint = <&typec_controller>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
...
|
55
Documentation/devicetree/bindings/usb/ti,tusb73x0-pci.yaml
Normal file
55
Documentation/devicetree/bindings/usb/ti,tusb73x0-pci.yaml
Normal file
@ -0,0 +1,55 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/usb/ti,tusb73x0-pci.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: TUSB73x0 USB 3.0 xHCI Host Controller (PCIe)
|
||||
|
||||
maintainers:
|
||||
- Francesco Dolcini <francesco.dolcini@toradex.com>
|
||||
|
||||
description:
|
||||
TUSB73x0 USB 3.0 xHCI Host Controller via PCIe x1 Gen2 interface.
|
||||
The TUSB7320 supports up to two downstream ports, the TUSB7340 supports up
|
||||
to four downstream ports, both variants share the same PCI device ID.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: pci104c,8241
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
ti,pwron-active-high:
|
||||
$ref: /schemas/types.yaml#/definitions/flag
|
||||
description:
|
||||
Configure the polarity of the PWRONx# signals. When this is present, the
|
||||
PWRONx# pins are active high and their internal pull-down resistors are
|
||||
disabled. When this is absent, the PWRONx# pins are active low (default)
|
||||
and their internal pull-down resistors are enabled.
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
allOf:
|
||||
- $ref: usb-xhci.yaml
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
pcie@0 {
|
||||
reg = <0x0 0x1000>;
|
||||
ranges = <0x02000000 0x0 0x100000 0x10000000 0x0 0x0>;
|
||||
#address-cells = <3>;
|
||||
#size-cells = <2>;
|
||||
device_type = "pci";
|
||||
|
||||
usb@0 {
|
||||
compatible = "pci104c,8241";
|
||||
reg = <0x0 0x0 0x0 0x0 0x0>;
|
||||
ti,pwron-active-high;
|
||||
};
|
||||
};
|
@ -24352,6 +24352,13 @@ L: linux-usb@vger.kernel.org
|
||||
S: Orphan
|
||||
F: drivers/usb/typec/tcpm/
|
||||
|
||||
USB TYPEC TUSB1046 MUX DRIVER
|
||||
M: Romain Gantois <romain.gantois@bootlin.com>
|
||||
L: linux-usb@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/usb/ti,tusb1046.yaml
|
||||
F: drivers/usb/typec/mux/tusb1046.c
|
||||
|
||||
USB UHCI DRIVER
|
||||
M: Alan Stern <stern@rowland.harvard.edu>
|
||||
L: linux-usb@vger.kernel.org
|
||||
|
@ -1023,6 +1023,8 @@ static int rtk_usb2phy_probe(struct platform_device *pdev)
|
||||
|
||||
rtk_phy->dev = &pdev->dev;
|
||||
rtk_phy->phy_cfg = devm_kzalloc(dev, sizeof(*phy_cfg), GFP_KERNEL);
|
||||
if (!rtk_phy->phy_cfg)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(rtk_phy->phy_cfg, phy_cfg, sizeof(*phy_cfg));
|
||||
|
||||
|
@ -577,6 +577,8 @@ static int rtk_usb3phy_probe(struct platform_device *pdev)
|
||||
|
||||
rtk_phy->dev = &pdev->dev;
|
||||
rtk_phy->phy_cfg = devm_kzalloc(dev, sizeof(*phy_cfg), GFP_KERNEL);
|
||||
if (!rtk_phy->phy_cfg)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(rtk_phy->phy_cfg, phy_cfg, sizeof(*phy_cfg));
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
* Mika Westerberg <mika.westerberg@linux.intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/delay.h>
|
||||
@ -43,6 +44,24 @@
|
||||
#define MAX_DWELL_TIME 500 /* ms */
|
||||
#define DWELL_SAMPLE_INTERVAL 10
|
||||
|
||||
enum usb4_margin_cap_voltage_indp {
|
||||
USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_2_3_MIN,
|
||||
USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_2_3_HL,
|
||||
USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_2_3_BOTH,
|
||||
USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_4_MIN,
|
||||
USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_4_BOTH,
|
||||
USB4_MARGIN_CAP_VOLTAGE_INDP_UNKNOWN,
|
||||
};
|
||||
|
||||
enum usb4_margin_cap_time_indp {
|
||||
USB4_MARGIN_CAP_TIME_INDP_GEN_2_3_MIN,
|
||||
USB4_MARGIN_CAP_TIME_INDP_GEN_2_3_LR,
|
||||
USB4_MARGIN_CAP_TIME_INDP_GEN_2_3_BOTH,
|
||||
USB4_MARGIN_CAP_TIME_INDP_GEN_4_MIN,
|
||||
USB4_MARGIN_CAP_TIME_INDP_GEN_4_BOTH,
|
||||
USB4_MARGIN_CAP_TIME_INDP_UNKNOWN,
|
||||
};
|
||||
|
||||
/* Sideband registers and their sizes as defined in the USB4 spec */
|
||||
struct sb_reg {
|
||||
unsigned int reg;
|
||||
@ -395,6 +414,8 @@ static ssize_t retimer_sb_regs_write(struct file *file,
|
||||
* @target: Sideband target
|
||||
* @index: Retimer index if taget is %USB4_SB_TARGET_RETIMER
|
||||
* @dev: Pointer to the device that is the target (USB4 port or retimer)
|
||||
* @gen: Link generation
|
||||
* @asym_rx: %true% if @port supports asymmetric link with 3 Rx
|
||||
* @caps: Port lane margining capabilities
|
||||
* @results: Last lane margining results
|
||||
* @lanes: %0, %1 or %7 (all)
|
||||
@ -416,15 +437,19 @@ static ssize_t retimer_sb_regs_write(struct file *file,
|
||||
* @time: %true if time margining is used instead of voltage
|
||||
* @right_high: %false if left/low margin test is performed, %true if
|
||||
* right/high
|
||||
* @upper_eye: %false if the lower PAM3 eye is used, %true if the upper
|
||||
* eye is used
|
||||
*/
|
||||
struct tb_margining {
|
||||
struct tb_port *port;
|
||||
enum usb4_sb_target target;
|
||||
u8 index;
|
||||
struct device *dev;
|
||||
u32 caps[2];
|
||||
u32 results[2];
|
||||
unsigned int lanes;
|
||||
unsigned int gen;
|
||||
bool asym_rx;
|
||||
u32 caps[3];
|
||||
u32 results[3];
|
||||
enum usb4_margining_lane lanes;
|
||||
unsigned int min_ber_level;
|
||||
unsigned int max_ber_level;
|
||||
unsigned int ber_level;
|
||||
@ -441,6 +466,7 @@ struct tb_margining {
|
||||
bool software;
|
||||
bool time;
|
||||
bool right_high;
|
||||
bool upper_eye;
|
||||
};
|
||||
|
||||
static int margining_modify_error_counter(struct tb_margining *margining,
|
||||
@ -463,35 +489,75 @@ static int margining_modify_error_counter(struct tb_margining *margining,
|
||||
|
||||
static bool supports_software(const struct tb_margining *margining)
|
||||
{
|
||||
return margining->caps[0] & USB4_MARGIN_CAP_0_MODES_SW;
|
||||
if (margining->gen < 4)
|
||||
return margining->caps[0] & USB4_MARGIN_CAP_0_MODES_SW;
|
||||
return margining->caps[2] & USB4_MARGIN_CAP_2_MODES_SW;
|
||||
}
|
||||
|
||||
static bool supports_hardware(const struct tb_margining *margining)
|
||||
{
|
||||
return margining->caps[0] & USB4_MARGIN_CAP_0_MODES_HW;
|
||||
if (margining->gen < 4)
|
||||
return margining->caps[0] & USB4_MARGIN_CAP_0_MODES_HW;
|
||||
return margining->caps[2] & USB4_MARGIN_CAP_2_MODES_HW;
|
||||
}
|
||||
|
||||
static bool both_lanes(const struct tb_margining *margining)
|
||||
static bool all_lanes(const struct tb_margining *margining)
|
||||
{
|
||||
return margining->caps[0] & USB4_MARGIN_CAP_0_2_LANES;
|
||||
return margining->caps[0] & USB4_MARGIN_CAP_0_ALL_LANES;
|
||||
}
|
||||
|
||||
static unsigned int
|
||||
static enum usb4_margin_cap_voltage_indp
|
||||
independent_voltage_margins(const struct tb_margining *margining)
|
||||
{
|
||||
return FIELD_GET(USB4_MARGIN_CAP_0_VOLTAGE_INDP_MASK, margining->caps[0]);
|
||||
if (margining->gen < 4) {
|
||||
switch (FIELD_GET(USB4_MARGIN_CAP_0_VOLTAGE_INDP_MASK, margining->caps[0])) {
|
||||
case USB4_MARGIN_CAP_0_VOLTAGE_MIN:
|
||||
return USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_2_3_MIN;
|
||||
case USB4_MARGIN_CAP_0_VOLTAGE_HL:
|
||||
return USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_2_3_HL;
|
||||
case USB4_MARGIN_CAP_1_TIME_BOTH:
|
||||
return USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_2_3_BOTH;
|
||||
}
|
||||
} else {
|
||||
switch (FIELD_GET(USB4_MARGIN_CAP_2_VOLTAGE_INDP_MASK, margining->caps[2])) {
|
||||
case USB4_MARGIN_CAP_2_VOLTAGE_MIN:
|
||||
return USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_4_MIN;
|
||||
case USB4_MARGIN_CAP_2_VOLTAGE_BOTH:
|
||||
return USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_4_BOTH;
|
||||
}
|
||||
}
|
||||
return USB4_MARGIN_CAP_VOLTAGE_INDP_UNKNOWN;
|
||||
}
|
||||
|
||||
static bool supports_time(const struct tb_margining *margining)
|
||||
{
|
||||
return margining->caps[0] & USB4_MARGIN_CAP_0_TIME;
|
||||
if (margining->gen < 4)
|
||||
return margining->caps[0] & USB4_MARGIN_CAP_0_TIME;
|
||||
return margining->caps[2] & USB4_MARGIN_CAP_2_TIME;
|
||||
}
|
||||
|
||||
/* Only applicable if supports_time() returns true */
|
||||
static unsigned int
|
||||
static enum usb4_margin_cap_time_indp
|
||||
independent_time_margins(const struct tb_margining *margining)
|
||||
{
|
||||
return FIELD_GET(USB4_MARGIN_CAP_1_TIME_INDP_MASK, margining->caps[1]);
|
||||
if (margining->gen < 4) {
|
||||
switch (FIELD_GET(USB4_MARGIN_CAP_1_TIME_INDP_MASK, margining->caps[1])) {
|
||||
case USB4_MARGIN_CAP_1_TIME_MIN:
|
||||
return USB4_MARGIN_CAP_TIME_INDP_GEN_2_3_MIN;
|
||||
case USB4_MARGIN_CAP_1_TIME_LR:
|
||||
return USB4_MARGIN_CAP_TIME_INDP_GEN_2_3_LR;
|
||||
case USB4_MARGIN_CAP_1_TIME_BOTH:
|
||||
return USB4_MARGIN_CAP_TIME_INDP_GEN_2_3_BOTH;
|
||||
}
|
||||
} else {
|
||||
switch (FIELD_GET(USB4_MARGIN_CAP_2_TIME_INDP_MASK, margining->caps[2])) {
|
||||
case USB4_MARGIN_CAP_2_TIME_MIN:
|
||||
return USB4_MARGIN_CAP_TIME_INDP_GEN_4_MIN;
|
||||
case USB4_MARGIN_CAP_2_TIME_BOTH:
|
||||
return USB4_MARGIN_CAP_TIME_INDP_GEN_4_BOTH;
|
||||
}
|
||||
}
|
||||
return USB4_MARGIN_CAP_TIME_INDP_UNKNOWN;
|
||||
}
|
||||
|
||||
static bool
|
||||
@ -570,16 +636,14 @@ static int margining_caps_show(struct seq_file *s, void *not_used)
|
||||
{
|
||||
struct tb_margining *margining = s->private;
|
||||
struct tb *tb = margining->port->sw->tb;
|
||||
u32 cap0, cap1;
|
||||
int ret = 0;
|
||||
|
||||
if (mutex_lock_interruptible(&tb->lock))
|
||||
return -ERESTARTSYS;
|
||||
|
||||
/* Dump the raw caps first */
|
||||
cap0 = margining->caps[0];
|
||||
seq_printf(s, "0x%08x\n", cap0);
|
||||
cap1 = margining->caps[1];
|
||||
seq_printf(s, "0x%08x\n", cap1);
|
||||
for (int i = 0; i < ARRAY_SIZE(margining->caps); i++)
|
||||
seq_printf(s, "0x%08x\n", margining->caps[i]);
|
||||
|
||||
seq_printf(s, "# software margining: %s\n",
|
||||
supports_software(margining) ? "yes" : "no");
|
||||
@ -593,8 +657,8 @@ static int margining_caps_show(struct seq_file *s, void *not_used)
|
||||
seq_puts(s, "# hardware margining: no\n");
|
||||
}
|
||||
|
||||
seq_printf(s, "# both lanes simultaneously: %s\n",
|
||||
both_lanes(margining) ? "yes" : "no");
|
||||
seq_printf(s, "# all lanes simultaneously: %s\n",
|
||||
str_yes_no(all_lanes(margining)));
|
||||
seq_printf(s, "# voltage margin steps: %u\n",
|
||||
margining->voltage_steps);
|
||||
seq_printf(s, "# maximum voltage offset: %u mV\n",
|
||||
@ -609,32 +673,54 @@ static int margining_caps_show(struct seq_file *s, void *not_used)
|
||||
}
|
||||
|
||||
switch (independent_voltage_margins(margining)) {
|
||||
case USB4_MARGIN_CAP_0_VOLTAGE_MIN:
|
||||
case USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_2_3_MIN:
|
||||
seq_puts(s, "# returns minimum between high and low voltage margins\n");
|
||||
break;
|
||||
case USB4_MARGIN_CAP_0_VOLTAGE_HL:
|
||||
case USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_2_3_HL:
|
||||
seq_puts(s, "# returns high or low voltage margin\n");
|
||||
break;
|
||||
case USB4_MARGIN_CAP_0_VOLTAGE_BOTH:
|
||||
case USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_2_3_BOTH:
|
||||
seq_puts(s, "# returns both high and low margins\n");
|
||||
break;
|
||||
case USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_4_MIN:
|
||||
seq_puts(s, "# returns minimum between high and low voltage margins in both lower and upper eye\n");
|
||||
break;
|
||||
case USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_4_BOTH:
|
||||
seq_puts(s, "# returns both high and low margins of both upper and lower eye\n");
|
||||
break;
|
||||
case USB4_MARGIN_CAP_VOLTAGE_INDP_UNKNOWN:
|
||||
tb_port_warn(margining->port,
|
||||
"failed to parse independent voltage margining capabilities\n");
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (supports_time(margining)) {
|
||||
seq_puts(s, "# time margining: yes\n");
|
||||
seq_printf(s, "# time margining is destructive: %s\n",
|
||||
cap1 & USB4_MARGIN_CAP_1_TIME_DESTR ? "yes" : "no");
|
||||
str_yes_no(margining->caps[1] & USB4_MARGIN_CAP_1_TIME_DESTR));
|
||||
|
||||
switch (independent_time_margins(margining)) {
|
||||
case USB4_MARGIN_CAP_1_TIME_MIN:
|
||||
case USB4_MARGIN_CAP_TIME_INDP_GEN_2_3_MIN:
|
||||
seq_puts(s, "# returns minimum between left and right time margins\n");
|
||||
break;
|
||||
case USB4_MARGIN_CAP_1_TIME_LR:
|
||||
case USB4_MARGIN_CAP_TIME_INDP_GEN_2_3_LR:
|
||||
seq_puts(s, "# returns left or right margin\n");
|
||||
break;
|
||||
case USB4_MARGIN_CAP_1_TIME_BOTH:
|
||||
case USB4_MARGIN_CAP_TIME_INDP_GEN_2_3_BOTH:
|
||||
seq_puts(s, "# returns both left and right margins\n");
|
||||
break;
|
||||
case USB4_MARGIN_CAP_TIME_INDP_GEN_4_MIN:
|
||||
seq_puts(s, "# returns minimum between left and right time margins in both lower and upper eye\n");
|
||||
break;
|
||||
case USB4_MARGIN_CAP_TIME_INDP_GEN_4_BOTH:
|
||||
seq_puts(s, "# returns both left and right margins of both upper and lower eye\n");
|
||||
break;
|
||||
case USB4_MARGIN_CAP_TIME_INDP_UNKNOWN:
|
||||
tb_port_warn(margining->port,
|
||||
"failed to parse independent time margining capabilities\n");
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
seq_printf(s, "# time margin steps: %u\n",
|
||||
@ -645,19 +731,43 @@ static int margining_caps_show(struct seq_file *s, void *not_used)
|
||||
seq_puts(s, "# time margining: no\n");
|
||||
}
|
||||
|
||||
out:
|
||||
mutex_unlock(&tb->lock);
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
DEBUGFS_ATTR_RO(margining_caps);
|
||||
|
||||
static const struct {
|
||||
enum usb4_margining_lane lane;
|
||||
const char *name;
|
||||
} lane_names[] = {
|
||||
{
|
||||
.lane = USB4_MARGINING_LANE_RX0,
|
||||
.name = "0",
|
||||
},
|
||||
{
|
||||
.lane = USB4_MARGINING_LANE_RX1,
|
||||
.name = "1",
|
||||
},
|
||||
{
|
||||
.lane = USB4_MARGINING_LANE_RX2,
|
||||
.name = "2",
|
||||
},
|
||||
{
|
||||
.lane = USB4_MARGINING_LANE_ALL,
|
||||
.name = "all",
|
||||
},
|
||||
};
|
||||
|
||||
static ssize_t
|
||||
margining_lanes_write(struct file *file, const char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct seq_file *s = file->private_data;
|
||||
struct tb_margining *margining = s->private;
|
||||
struct tb *tb = margining->port->sw->tb;
|
||||
int ret = 0;
|
||||
struct tb_port *port = margining->port;
|
||||
struct tb *tb = port->sw->tb;
|
||||
int lane = -1;
|
||||
char *buf;
|
||||
|
||||
buf = validate_and_copy_from_user(user_buf, &count);
|
||||
@ -666,57 +776,60 @@ margining_lanes_write(struct file *file, const char __user *user_buf,
|
||||
|
||||
buf[count - 1] = '\0';
|
||||
|
||||
if (mutex_lock_interruptible(&tb->lock)) {
|
||||
ret = -ERESTARTSYS;
|
||||
goto out_free;
|
||||
for (int i = 0; i < ARRAY_SIZE(lane_names); i++) {
|
||||
if (!strcmp(buf, lane_names[i].name)) {
|
||||
lane = lane_names[i].lane;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!strcmp(buf, "0")) {
|
||||
margining->lanes = 0;
|
||||
} else if (!strcmp(buf, "1")) {
|
||||
margining->lanes = 1;
|
||||
} else if (!strcmp(buf, "all")) {
|
||||
/* Needs to be supported */
|
||||
if (both_lanes(margining))
|
||||
margining->lanes = 7;
|
||||
else
|
||||
ret = -EINVAL;
|
||||
} else {
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
mutex_unlock(&tb->lock);
|
||||
|
||||
out_free:
|
||||
free_page((unsigned long)buf);
|
||||
return ret < 0 ? ret : count;
|
||||
|
||||
if (lane == -1)
|
||||
return -EINVAL;
|
||||
|
||||
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) {
|
||||
if (lane == USB4_MARGINING_LANE_ALL && !all_lanes(margining))
|
||||
return -EINVAL;
|
||||
/*
|
||||
* Enabling on RX2 requires that it is supported by the
|
||||
* USB4 port.
|
||||
*/
|
||||
if (lane == USB4_MARGINING_LANE_RX2 && !margining->asym_rx)
|
||||
return -EINVAL;
|
||||
|
||||
margining->lanes = lane;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static int margining_lanes_show(struct seq_file *s, void *not_used)
|
||||
{
|
||||
struct tb_margining *margining = s->private;
|
||||
struct tb *tb = margining->port->sw->tb;
|
||||
unsigned int lanes;
|
||||
struct tb_port *port = margining->port;
|
||||
struct tb *tb = port->sw->tb;
|
||||
|
||||
if (mutex_lock_interruptible(&tb->lock))
|
||||
return -ERESTARTSYS;
|
||||
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) {
|
||||
for (int i = 0; i < ARRAY_SIZE(lane_names); i++) {
|
||||
if (lane_names[i].lane == USB4_MARGINING_LANE_ALL &&
|
||||
!all_lanes(margining))
|
||||
continue;
|
||||
if (lane_names[i].lane == USB4_MARGINING_LANE_RX2 &&
|
||||
!margining->asym_rx)
|
||||
continue;
|
||||
|
||||
lanes = margining->lanes;
|
||||
if (both_lanes(margining)) {
|
||||
if (!lanes)
|
||||
seq_puts(s, "[0] 1 all\n");
|
||||
else if (lanes == 1)
|
||||
seq_puts(s, "0 [1] all\n");
|
||||
else
|
||||
seq_puts(s, "0 1 [all]\n");
|
||||
} else {
|
||||
if (!lanes)
|
||||
seq_puts(s, "[0] 1\n");
|
||||
else
|
||||
seq_puts(s, "0 [1]\n");
|
||||
if (i != 0)
|
||||
seq_putc(s, ' ');
|
||||
|
||||
if (lane_names[i].lane == margining->lanes)
|
||||
seq_printf(s, "[%s]", lane_names[i].name);
|
||||
else
|
||||
seq_printf(s, "%s", lane_names[i].name);
|
||||
}
|
||||
seq_puts(s, "\n");
|
||||
}
|
||||
|
||||
mutex_unlock(&tb->lock);
|
||||
return 0;
|
||||
}
|
||||
DEBUGFS_ATTR_RW(margining_lanes);
|
||||
@ -1004,13 +1117,16 @@ static int margining_run_sw(struct tb_margining *margining,
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
if (margining->lanes == USB4_MARGIN_SW_LANE_0)
|
||||
if (margining->lanes == USB4_MARGINING_LANE_RX0)
|
||||
errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_0_MASK,
|
||||
margining->results[1]);
|
||||
else if (margining->lanes == USB4_MARGIN_SW_LANE_1)
|
||||
else if (margining->lanes == USB4_MARGINING_LANE_RX1)
|
||||
errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_1_MASK,
|
||||
margining->results[1]);
|
||||
else if (margining->lanes == USB4_MARGIN_SW_ALL_LANES)
|
||||
else if (margining->lanes == USB4_MARGINING_LANE_RX2)
|
||||
errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_2_MASK,
|
||||
margining->results[1]);
|
||||
else if (margining->lanes == USB4_MARGINING_LANE_ALL)
|
||||
errors = margining->results[1];
|
||||
|
||||
/* Any errors stop the test */
|
||||
@ -1030,6 +1146,31 @@ static int margining_run_sw(struct tb_margining *margining,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int validate_margining(struct tb_margining *margining)
|
||||
{
|
||||
/*
|
||||
* For running on RX2 the link must be asymmetric with 3
|
||||
* receivers. Because this is can change dynamically, check it
|
||||
* here before we start the margining and report back error if
|
||||
* expectations are not met.
|
||||
*/
|
||||
if (margining->lanes == USB4_MARGINING_LANE_RX2) {
|
||||
int ret;
|
||||
|
||||
ret = tb_port_get_link_width(margining->port);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret != TB_LINK_WIDTH_ASYM_RX) {
|
||||
tb_port_warn(margining->port, "link is %s expected %s",
|
||||
tb_width_name(ret),
|
||||
tb_width_name(TB_LINK_WIDTH_ASYM_RX));
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int margining_run_write(void *data, u64 val)
|
||||
{
|
||||
struct tb_margining *margining = data;
|
||||
@ -1050,6 +1191,10 @@ static int margining_run_write(void *data, u64 val)
|
||||
goto out_rpm_put;
|
||||
}
|
||||
|
||||
ret = validate_margining(margining);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
|
||||
if (tb_is_upstream_port(port))
|
||||
down_sw = sw;
|
||||
else if (port->remote)
|
||||
@ -1080,6 +1225,7 @@ static int margining_run_write(void *data, u64 val)
|
||||
.time = margining->time,
|
||||
.voltage_time_offset = margining->voltage_time_offset,
|
||||
.right_high = margining->right_high,
|
||||
.upper_eye = margining->upper_eye,
|
||||
.optional_voltage_offset_range = margining->optional_voltage_offset_range,
|
||||
};
|
||||
|
||||
@ -1095,6 +1241,7 @@ static int margining_run_write(void *data, u64 val)
|
||||
.lanes = margining->lanes,
|
||||
.time = margining->time,
|
||||
.right_high = margining->right_high,
|
||||
.upper_eye = margining->upper_eye,
|
||||
.optional_voltage_offset_range = margining->optional_voltage_offset_range,
|
||||
};
|
||||
|
||||
@ -1104,7 +1251,7 @@ static int margining_run_write(void *data, u64 val)
|
||||
margining->lanes);
|
||||
|
||||
ret = usb4_port_hw_margin(port, margining->target, margining->index, ¶ms,
|
||||
margining->results);
|
||||
margining->results, ARRAY_SIZE(margining->results));
|
||||
}
|
||||
|
||||
if (down_sw)
|
||||
@ -1132,13 +1279,12 @@ static ssize_t margining_results_write(struct file *file,
|
||||
return -ERESTARTSYS;
|
||||
|
||||
/* Just clear the results */
|
||||
margining->results[0] = 0;
|
||||
margining->results[1] = 0;
|
||||
memset(margining->results, 0, sizeof(margining->results));
|
||||
|
||||
if (margining->software) {
|
||||
/* Clear the error counters */
|
||||
margining_modify_error_counter(margining,
|
||||
USB4_MARGIN_SW_ALL_LANES,
|
||||
USB4_MARGINING_LANE_ALL,
|
||||
USB4_MARGIN_SW_ERROR_COUNTER_CLEAR);
|
||||
}
|
||||
|
||||
@ -1151,10 +1297,10 @@ static void voltage_margin_show(struct seq_file *s,
|
||||
{
|
||||
unsigned int tmp, voltage;
|
||||
|
||||
tmp = FIELD_GET(USB4_MARGIN_HW_RES_1_MARGIN_MASK, val);
|
||||
tmp = FIELD_GET(USB4_MARGIN_HW_RES_MARGIN_MASK, val);
|
||||
voltage = tmp * margining->max_voltage_offset / margining->voltage_steps;
|
||||
seq_printf(s, "%u mV (%u)", voltage, tmp);
|
||||
if (val & USB4_MARGIN_HW_RES_1_EXCEEDS)
|
||||
if (val & USB4_MARGIN_HW_RES_EXCEEDS)
|
||||
seq_puts(s, " exceeds maximum");
|
||||
seq_puts(s, "\n");
|
||||
if (margining->optional_voltage_offset_range)
|
||||
@ -1166,14 +1312,55 @@ static void time_margin_show(struct seq_file *s,
|
||||
{
|
||||
unsigned int tmp, interval;
|
||||
|
||||
tmp = FIELD_GET(USB4_MARGIN_HW_RES_1_MARGIN_MASK, val);
|
||||
tmp = FIELD_GET(USB4_MARGIN_HW_RES_MARGIN_MASK, val);
|
||||
interval = tmp * margining->max_time_offset / margining->time_steps;
|
||||
seq_printf(s, "%u mUI (%u)", interval, tmp);
|
||||
if (val & USB4_MARGIN_HW_RES_1_EXCEEDS)
|
||||
if (val & USB4_MARGIN_HW_RES_EXCEEDS)
|
||||
seq_puts(s, " exceeds maximum");
|
||||
seq_puts(s, "\n");
|
||||
}
|
||||
|
||||
static u8 margining_hw_result_val(const u32 *results,
|
||||
enum usb4_margining_lane lane,
|
||||
bool right_high)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
if (lane == USB4_MARGINING_LANE_RX0)
|
||||
val = results[1];
|
||||
else if (lane == USB4_MARGINING_LANE_RX1)
|
||||
val = results[1] >> USB4_MARGIN_HW_RES_LANE_SHIFT;
|
||||
else if (lane == USB4_MARGINING_LANE_RX2)
|
||||
val = results[2];
|
||||
else
|
||||
val = 0;
|
||||
|
||||
return right_high ? val : val >> USB4_MARGIN_HW_RES_LL_SHIFT;
|
||||
}
|
||||
|
||||
static void margining_hw_result_format(struct seq_file *s,
|
||||
const struct tb_margining *margining,
|
||||
enum usb4_margining_lane lane)
|
||||
{
|
||||
u8 val;
|
||||
|
||||
if (margining->time) {
|
||||
val = margining_hw_result_val(margining->results, lane, true);
|
||||
seq_printf(s, "# lane %u right time margin: ", lane);
|
||||
time_margin_show(s, margining, val);
|
||||
val = margining_hw_result_val(margining->results, lane, false);
|
||||
seq_printf(s, "# lane %u left time margin: ", lane);
|
||||
time_margin_show(s, margining, val);
|
||||
} else {
|
||||
val = margining_hw_result_val(margining->results, lane, true);
|
||||
seq_printf(s, "# lane %u high voltage margin: ", lane);
|
||||
voltage_margin_show(s, margining, val);
|
||||
val = margining_hw_result_val(margining->results, lane, false);
|
||||
seq_printf(s, "# lane %u low voltage margin: ", lane);
|
||||
voltage_margin_show(s, margining, val);
|
||||
}
|
||||
}
|
||||
|
||||
static int margining_results_show(struct seq_file *s, void *not_used)
|
||||
{
|
||||
struct tb_margining *margining = s->private;
|
||||
@ -1186,69 +1373,46 @@ static int margining_results_show(struct seq_file *s, void *not_used)
|
||||
seq_printf(s, "0x%08x\n", margining->results[0]);
|
||||
/* Only the hardware margining has two result dwords */
|
||||
if (!margining->software) {
|
||||
unsigned int val;
|
||||
for (int i = 1; i < ARRAY_SIZE(margining->results); i++)
|
||||
seq_printf(s, "0x%08x\n", margining->results[i]);
|
||||
|
||||
seq_printf(s, "0x%08x\n", margining->results[1]);
|
||||
|
||||
if (margining->time) {
|
||||
if (!margining->lanes || margining->lanes == 7) {
|
||||
val = margining->results[1];
|
||||
seq_puts(s, "# lane 0 right time margin: ");
|
||||
time_margin_show(s, margining, val);
|
||||
val = margining->results[1] >>
|
||||
USB4_MARGIN_HW_RES_1_L0_LL_MARGIN_SHIFT;
|
||||
seq_puts(s, "# lane 0 left time margin: ");
|
||||
time_margin_show(s, margining, val);
|
||||
}
|
||||
if (margining->lanes == 1 || margining->lanes == 7) {
|
||||
val = margining->results[1] >>
|
||||
USB4_MARGIN_HW_RES_1_L1_RH_MARGIN_SHIFT;
|
||||
seq_puts(s, "# lane 1 right time margin: ");
|
||||
time_margin_show(s, margining, val);
|
||||
val = margining->results[1] >>
|
||||
USB4_MARGIN_HW_RES_1_L1_LL_MARGIN_SHIFT;
|
||||
seq_puts(s, "# lane 1 left time margin: ");
|
||||
time_margin_show(s, margining, val);
|
||||
}
|
||||
if (margining->lanes == USB4_MARGINING_LANE_ALL) {
|
||||
margining_hw_result_format(s, margining,
|
||||
USB4_MARGINING_LANE_RX0);
|
||||
margining_hw_result_format(s, margining,
|
||||
USB4_MARGINING_LANE_RX1);
|
||||
if (margining->asym_rx)
|
||||
margining_hw_result_format(s, margining,
|
||||
USB4_MARGINING_LANE_RX2);
|
||||
} else {
|
||||
if (!margining->lanes || margining->lanes == 7) {
|
||||
val = margining->results[1];
|
||||
seq_puts(s, "# lane 0 high voltage margin: ");
|
||||
voltage_margin_show(s, margining, val);
|
||||
val = margining->results[1] >>
|
||||
USB4_MARGIN_HW_RES_1_L0_LL_MARGIN_SHIFT;
|
||||
seq_puts(s, "# lane 0 low voltage margin: ");
|
||||
voltage_margin_show(s, margining, val);
|
||||
}
|
||||
if (margining->lanes == 1 || margining->lanes == 7) {
|
||||
val = margining->results[1] >>
|
||||
USB4_MARGIN_HW_RES_1_L1_RH_MARGIN_SHIFT;
|
||||
seq_puts(s, "# lane 1 high voltage margin: ");
|
||||
voltage_margin_show(s, margining, val);
|
||||
val = margining->results[1] >>
|
||||
USB4_MARGIN_HW_RES_1_L1_LL_MARGIN_SHIFT;
|
||||
seq_puts(s, "# lane 1 low voltage margin: ");
|
||||
voltage_margin_show(s, margining, val);
|
||||
}
|
||||
margining_hw_result_format(s, margining,
|
||||
margining->lanes);
|
||||
}
|
||||
} else {
|
||||
u32 lane_errors, result;
|
||||
|
||||
seq_printf(s, "0x%08x\n", margining->results[1]);
|
||||
result = FIELD_GET(USB4_MARGIN_SW_LANES_MASK, margining->results[0]);
|
||||
|
||||
if (result == USB4_MARGIN_SW_LANE_0 ||
|
||||
result == USB4_MARGIN_SW_ALL_LANES) {
|
||||
result = FIELD_GET(USB4_MARGIN_SW_LANES_MASK, margining->results[0]);
|
||||
if (result == USB4_MARGINING_LANE_RX0 ||
|
||||
result == USB4_MARGINING_LANE_ALL) {
|
||||
lane_errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_0_MASK,
|
||||
margining->results[1]);
|
||||
seq_printf(s, "# lane 0 errors: %u\n", lane_errors);
|
||||
}
|
||||
if (result == USB4_MARGIN_SW_LANE_1 ||
|
||||
result == USB4_MARGIN_SW_ALL_LANES) {
|
||||
if (result == USB4_MARGINING_LANE_RX1 ||
|
||||
result == USB4_MARGINING_LANE_ALL) {
|
||||
lane_errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_1_MASK,
|
||||
margining->results[1]);
|
||||
seq_printf(s, "# lane 1 errors: %u\n", lane_errors);
|
||||
}
|
||||
if (margining->asym_rx &&
|
||||
(result == USB4_MARGINING_LANE_RX2 ||
|
||||
result == USB4_MARGINING_LANE_ALL)) {
|
||||
lane_errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_2_MASK,
|
||||
margining->results[1]);
|
||||
seq_printf(s, "# lane 2 errors: %u\n", lane_errors);
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&tb->lock);
|
||||
@ -1382,6 +1546,55 @@ static int margining_margin_show(struct seq_file *s, void *not_used)
|
||||
}
|
||||
DEBUGFS_ATTR_RW(margining_margin);
|
||||
|
||||
static ssize_t margining_eye_write(struct file *file,
|
||||
const char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct seq_file *s = file->private_data;
|
||||
struct tb_port *port = s->private;
|
||||
struct usb4_port *usb4 = port->usb4;
|
||||
struct tb *tb = port->sw->tb;
|
||||
int ret = 0;
|
||||
char *buf;
|
||||
|
||||
buf = validate_and_copy_from_user(user_buf, &count);
|
||||
if (IS_ERR(buf))
|
||||
return PTR_ERR(buf);
|
||||
|
||||
buf[count - 1] = '\0';
|
||||
|
||||
scoped_cond_guard(mutex_intr, ret = -ERESTARTSYS, &tb->lock) {
|
||||
if (!strcmp(buf, "lower"))
|
||||
usb4->margining->upper_eye = false;
|
||||
else if (!strcmp(buf, "upper"))
|
||||
usb4->margining->upper_eye = true;
|
||||
else
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
free_page((unsigned long)buf);
|
||||
return ret ? ret : count;
|
||||
}
|
||||
|
||||
static int margining_eye_show(struct seq_file *s, void *not_used)
|
||||
{
|
||||
struct tb_port *port = s->private;
|
||||
struct usb4_port *usb4 = port->usb4;
|
||||
struct tb *tb = port->sw->tb;
|
||||
|
||||
scoped_guard(mutex_intr, &tb->lock) {
|
||||
if (usb4->margining->upper_eye)
|
||||
seq_puts(s, "lower [upper]\n");
|
||||
else
|
||||
seq_puts(s, "[lower] upper\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -ERESTARTSYS;
|
||||
}
|
||||
DEBUGFS_ATTR_RW(margining_eye);
|
||||
|
||||
static struct tb_margining *margining_alloc(struct tb_port *port,
|
||||
struct device *dev,
|
||||
enum usb4_sb_target target,
|
||||
@ -1392,6 +1605,12 @@ static struct tb_margining *margining_alloc(struct tb_port *port,
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
ret = tb_port_get_link_generation(port);
|
||||
if (ret < 0) {
|
||||
tb_port_warn(port, "failed to read link generation\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
margining = kzalloc(sizeof(*margining), GFP_KERNEL);
|
||||
if (!margining)
|
||||
return NULL;
|
||||
@ -1400,8 +1619,11 @@ static struct tb_margining *margining_alloc(struct tb_port *port,
|
||||
margining->target = target;
|
||||
margining->index = index;
|
||||
margining->dev = dev;
|
||||
margining->gen = ret;
|
||||
margining->asym_rx = tb_port_width_supported(port, TB_LINK_WIDTH_ASYM_RX);
|
||||
|
||||
ret = usb4_port_margining_caps(port, target, index, margining->caps);
|
||||
ret = usb4_port_margining_caps(port, target, index, margining->caps,
|
||||
ARRAY_SIZE(margining->caps));
|
||||
if (ret) {
|
||||
kfree(margining);
|
||||
return NULL;
|
||||
@ -1411,10 +1633,17 @@ static struct tb_margining *margining_alloc(struct tb_port *port,
|
||||
if (supports_software(margining))
|
||||
margining->software = true;
|
||||
|
||||
val = FIELD_GET(USB4_MARGIN_CAP_0_VOLTAGE_STEPS_MASK, margining->caps[0]);
|
||||
margining->voltage_steps = val;
|
||||
val = FIELD_GET(USB4_MARGIN_CAP_0_MAX_VOLTAGE_OFFSET_MASK, margining->caps[0]);
|
||||
margining->max_voltage_offset = 74 + val * 2;
|
||||
if (margining->gen < 4) {
|
||||
val = FIELD_GET(USB4_MARGIN_CAP_0_VOLTAGE_STEPS_MASK, margining->caps[0]);
|
||||
margining->voltage_steps = val;
|
||||
val = FIELD_GET(USB4_MARGIN_CAP_0_MAX_VOLTAGE_OFFSET_MASK, margining->caps[0]);
|
||||
margining->max_voltage_offset = 74 + val * 2;
|
||||
} else {
|
||||
val = FIELD_GET(USB4_MARGIN_CAP_2_VOLTAGE_STEPS_MASK, margining->caps[2]);
|
||||
margining->voltage_steps = val;
|
||||
val = FIELD_GET(USB4_MARGIN_CAP_2_MAX_VOLTAGE_OFFSET_MASK, margining->caps[2]);
|
||||
margining->max_voltage_offset = 74 + val * 2;
|
||||
}
|
||||
|
||||
if (supports_optional_voltage_offset_range(margining)) {
|
||||
val = FIELD_GET(USB4_MARGIN_CAP_0_VOLT_STEPS_OPT_MASK,
|
||||
@ -1456,11 +1685,10 @@ static struct tb_margining *margining_alloc(struct tb_port *port,
|
||||
debugfs_create_file("results", 0600, dir, margining,
|
||||
&margining_results_fops);
|
||||
debugfs_create_file("test", 0600, dir, margining, &margining_test_fops);
|
||||
if (independent_voltage_margins(margining) == USB4_MARGIN_CAP_0_VOLTAGE_HL ||
|
||||
if (independent_voltage_margins(margining) == USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_2_3_HL ||
|
||||
(supports_time(margining) &&
|
||||
independent_time_margins(margining) == USB4_MARGIN_CAP_1_TIME_LR))
|
||||
debugfs_create_file("margin", 0600, dir, margining,
|
||||
&margining_margin_fops);
|
||||
independent_time_margins(margining) == USB4_MARGIN_CAP_TIME_INDP_GEN_2_3_LR))
|
||||
debugfs_create_file("margin", 0600, dir, margining, &margining_margin_fops);
|
||||
|
||||
margining->error_counter = USB4_MARGIN_SW_ERROR_COUNTER_CLEAR;
|
||||
margining->dwell_time = MIN_DWELL_TIME;
|
||||
@ -1477,6 +1705,10 @@ static struct tb_margining *margining_alloc(struct tb_port *port,
|
||||
debugfs_create_file("dwell_time", DEBUGFS_MODE, dir, margining,
|
||||
&margining_dwell_time_fops);
|
||||
}
|
||||
|
||||
if (margining->gen >= 4)
|
||||
debugfs_create_file("eye", 0600, dir, port, &margining_eye_fops);
|
||||
|
||||
return margining;
|
||||
}
|
||||
|
||||
|
@ -1340,18 +1340,18 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
||||
if (res)
|
||||
return dev_err_probe(dev, res, "cannot enable PCI device, aborting\n");
|
||||
|
||||
res = pcim_iomap_regions(pdev, 1 << 0, "thunderbolt");
|
||||
if (res)
|
||||
return dev_err_probe(dev, res, "cannot obtain PCI resources, aborting\n");
|
||||
|
||||
nhi = devm_kzalloc(&pdev->dev, sizeof(*nhi), GFP_KERNEL);
|
||||
if (!nhi)
|
||||
return -ENOMEM;
|
||||
|
||||
nhi->pdev = pdev;
|
||||
nhi->ops = (const struct tb_nhi_ops *)id->driver_data;
|
||||
/* cannot fail - table is allocated in pcim_iomap_regions */
|
||||
nhi->iobase = pcim_iomap_table(pdev)[0];
|
||||
|
||||
nhi->iobase = pcim_iomap_region(pdev, 0, "thunderbolt");
|
||||
res = PTR_ERR_OR_ZERO(nhi->iobase);
|
||||
if (res)
|
||||
return dev_err_probe(dev, res, "cannot obtain PCI resources, aborting\n");
|
||||
|
||||
nhi->hop_count = ioread32(nhi->iobase + REG_CAPS) & 0x3ff;
|
||||
dev_dbg(dev, "total paths: %d\n", nhi->hop_count);
|
||||
|
||||
|
@ -49,7 +49,7 @@ enum usb4_sb_opcode {
|
||||
/* USB4_SB_OPCODE_READ_LANE_MARGINING_CAP */
|
||||
#define USB4_MARGIN_CAP_0_MODES_HW BIT(0)
|
||||
#define USB4_MARGIN_CAP_0_MODES_SW BIT(1)
|
||||
#define USB4_MARGIN_CAP_0_2_LANES BIT(2)
|
||||
#define USB4_MARGIN_CAP_0_ALL_LANES BIT(2)
|
||||
#define USB4_MARGIN_CAP_0_VOLTAGE_INDP_MASK GENMASK(4, 3)
|
||||
#define USB4_MARGIN_CAP_0_VOLTAGE_MIN 0x0
|
||||
#define USB4_MARGIN_CAP_0_VOLTAGE_HL 0x1
|
||||
@ -69,34 +69,44 @@ enum usb4_sb_opcode {
|
||||
#define USB4_MARGIN_CAP_1_TIME_OFFSET_MASK GENMASK(20, 16)
|
||||
#define USB4_MARGIN_CAP_1_MIN_BER_MASK GENMASK(25, 21)
|
||||
#define USB4_MARGIN_CAP_1_MAX_BER_MASK GENMASK(30, 26)
|
||||
#define USB4_MARGIN_CAP_2_MODES_HW BIT(0)
|
||||
#define USB4_MARGIN_CAP_2_MODES_SW BIT(1)
|
||||
#define USB4_MARGIN_CAP_2_TIME BIT(2)
|
||||
#define USB4_MARGIN_CAP_2_MAX_VOLTAGE_OFFSET_MASK GENMASK(8, 3)
|
||||
#define USB4_MARGIN_CAP_2_VOLTAGE_STEPS_MASK GENMASK(15, 9)
|
||||
#define USB4_MARGIN_CAP_2_VOLTAGE_INDP_MASK GENMASK(17, 16)
|
||||
#define USB4_MARGIN_CAP_2_VOLTAGE_MIN 0x0
|
||||
#define USB4_MARGIN_CAP_2_VOLTAGE_BOTH 0x1
|
||||
#define USB4_MARGIN_CAP_2_TIME_INDP_MASK GENMASK(19, 18)
|
||||
#define USB4_MARGIN_CAP_2_TIME_MIN 0x0
|
||||
#define USB4_MARGIN_CAP_2_TIME_BOTH 0x1
|
||||
|
||||
/* USB4_SB_OPCODE_RUN_HW_LANE_MARGINING */
|
||||
#define USB4_MARGIN_HW_TIME BIT(3)
|
||||
#define USB4_MARGIN_HW_RH BIT(4)
|
||||
#define USB4_MARGIN_HW_RHU BIT(4)
|
||||
#define USB4_MARGIN_HW_BER_MASK GENMASK(9, 5)
|
||||
#define USB4_MARGIN_HW_BER_SHIFT 5
|
||||
#define USB4_MARGIN_HW_OPT_VOLTAGE BIT(10)
|
||||
|
||||
/* Applicable to all margin values */
|
||||
#define USB4_MARGIN_HW_RES_1_MARGIN_MASK GENMASK(6, 0)
|
||||
#define USB4_MARGIN_HW_RES_1_EXCEEDS BIT(7)
|
||||
/* Different lane margin shifts */
|
||||
#define USB4_MARGIN_HW_RES_1_L0_LL_MARGIN_SHIFT 8
|
||||
#define USB4_MARGIN_HW_RES_1_L1_RH_MARGIN_SHIFT 16
|
||||
#define USB4_MARGIN_HW_RES_1_L1_LL_MARGIN_SHIFT 24
|
||||
#define USB4_MARGIN_HW_RES_MARGIN_MASK GENMASK(6, 0)
|
||||
#define USB4_MARGIN_HW_RES_EXCEEDS BIT(7)
|
||||
|
||||
/* Shifts for parsing the lane results */
|
||||
#define USB4_MARGIN_HW_RES_LANE_SHIFT 16
|
||||
#define USB4_MARGIN_HW_RES_LL_SHIFT 8
|
||||
|
||||
/* USB4_SB_OPCODE_RUN_SW_LANE_MARGINING */
|
||||
#define USB4_MARGIN_SW_LANES_MASK GENMASK(2, 0)
|
||||
#define USB4_MARGIN_SW_LANE_0 0x0
|
||||
#define USB4_MARGIN_SW_LANE_1 0x1
|
||||
#define USB4_MARGIN_SW_ALL_LANES 0x7
|
||||
#define USB4_MARGIN_SW_TIME BIT(3)
|
||||
#define USB4_MARGIN_SW_RH BIT(4)
|
||||
#define USB4_MARGIN_SW_OPT_VOLTAGE BIT(5)
|
||||
#define USB4_MARGIN_SW_VT_MASK GENMASK(12, 6)
|
||||
#define USB4_MARGIN_SW_COUNTER_MASK GENMASK(14, 13)
|
||||
#define USB4_MARGIN_SW_UPPER_EYE BIT(15)
|
||||
|
||||
#define USB4_MARGIN_SW_ERR_COUNTER_LANE_0_MASK GENMASK(3, 0)
|
||||
#define USB4_MARGIN_SW_ERR_COUNTER_LANE_1_MASK GENMASK(7, 4)
|
||||
#define USB4_MARGIN_SW_ERR_COUNTER_LANE_2_MASK GENMASK(11, 8)
|
||||
|
||||
#endif
|
||||
|
@ -1367,11 +1367,18 @@ enum usb4_margin_sw_error_counter {
|
||||
USB4_MARGIN_SW_ERROR_COUNTER_STOP,
|
||||
};
|
||||
|
||||
enum usb4_margining_lane {
|
||||
USB4_MARGINING_LANE_RX0 = 0,
|
||||
USB4_MARGINING_LANE_RX1 = 1,
|
||||
USB4_MARGINING_LANE_RX2 = 2,
|
||||
USB4_MARGINING_LANE_ALL = 7,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct usb4_port_margining_params - USB4 margining parameters
|
||||
* @error_counter: Error counter operation for software margining
|
||||
* @ber_level: Current BER level contour value
|
||||
* @lanes: %0, %1 or %7 (all)
|
||||
* @lanes: Lanes to enable for the margining operation
|
||||
* @voltage_time_offset: Offset for voltage / time for software margining
|
||||
* @optional_voltage_offset_range: Enable optional extended voltage range
|
||||
* @right_high: %false if left/low margin test is performed, %true if right/high
|
||||
@ -1380,18 +1387,19 @@ enum usb4_margin_sw_error_counter {
|
||||
struct usb4_port_margining_params {
|
||||
enum usb4_margin_sw_error_counter error_counter;
|
||||
u32 ber_level;
|
||||
u32 lanes;
|
||||
enum usb4_margining_lane lanes;
|
||||
u32 voltage_time_offset;
|
||||
bool optional_voltage_offset_range;
|
||||
bool right_high;
|
||||
bool upper_eye;
|
||||
bool time;
|
||||
};
|
||||
|
||||
int usb4_port_margining_caps(struct tb_port *port, enum usb4_sb_target target,
|
||||
u8 index, u32 *caps);
|
||||
u8 index, u32 *caps, size_t ncaps);
|
||||
int usb4_port_hw_margin(struct tb_port *port, enum usb4_sb_target target,
|
||||
u8 index, const struct usb4_port_margining_params *params,
|
||||
u32 *results);
|
||||
u32 *results, size_t nresults);
|
||||
int usb4_port_sw_margin(struct tb_port *port, enum usb4_sb_target target,
|
||||
u8 index, const struct usb4_port_margining_params *params,
|
||||
u32 *results);
|
||||
|
@ -1631,11 +1631,12 @@ int usb4_port_asym_start(struct tb_port *port)
|
||||
* @target: Sideband target
|
||||
* @index: Retimer index if taget is %USB4_SB_TARGET_RETIMER
|
||||
* @caps: Array with at least two elements to hold the results
|
||||
* @ncaps: Number of elements in the caps array
|
||||
*
|
||||
* Reads the USB4 port lane margining capabilities into @caps.
|
||||
*/
|
||||
int usb4_port_margining_caps(struct tb_port *port, enum usb4_sb_target target,
|
||||
u8 index, u32 *caps)
|
||||
u8 index, u32 *caps, size_t ncaps)
|
||||
{
|
||||
int ret;
|
||||
|
||||
@ -1645,7 +1646,7 @@ int usb4_port_margining_caps(struct tb_port *port, enum usb4_sb_target target,
|
||||
return ret;
|
||||
|
||||
return usb4_port_sb_read(port, target, index, USB4_SB_DATA, caps,
|
||||
sizeof(*caps) * 2);
|
||||
sizeof(*caps) * ncaps);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1654,14 +1655,15 @@ int usb4_port_margining_caps(struct tb_port *port, enum usb4_sb_target target,
|
||||
* @target: Sideband target
|
||||
* @index: Retimer index if taget is %USB4_SB_TARGET_RETIMER
|
||||
* @params: Parameters for USB4 hardware margining
|
||||
* @results: Array with at least two elements to hold the results
|
||||
* @results: Array to hold the results
|
||||
* @nresults: Number of elements in the results array
|
||||
*
|
||||
* Runs hardware lane margining on USB4 port and returns the result in
|
||||
* @results.
|
||||
*/
|
||||
int usb4_port_hw_margin(struct tb_port *port, enum usb4_sb_target target,
|
||||
u8 index, const struct usb4_port_margining_params *params,
|
||||
u32 *results)
|
||||
u32 *results, size_t nresults)
|
||||
{
|
||||
u32 val;
|
||||
int ret;
|
||||
@ -1672,8 +1674,8 @@ int usb4_port_hw_margin(struct tb_port *port, enum usb4_sb_target target,
|
||||
val = params->lanes;
|
||||
if (params->time)
|
||||
val |= USB4_MARGIN_HW_TIME;
|
||||
if (params->right_high)
|
||||
val |= USB4_MARGIN_HW_RH;
|
||||
if (params->right_high || params->upper_eye)
|
||||
val |= USB4_MARGIN_HW_RHU;
|
||||
if (params->ber_level)
|
||||
val |= FIELD_PREP(USB4_MARGIN_HW_BER_MASK, params->ber_level);
|
||||
if (params->optional_voltage_offset_range)
|
||||
@ -1690,7 +1692,7 @@ int usb4_port_hw_margin(struct tb_port *port, enum usb4_sb_target target,
|
||||
return ret;
|
||||
|
||||
return usb4_port_sb_read(port, target, index, USB4_SB_DATA, results,
|
||||
sizeof(*results) * 2);
|
||||
sizeof(*results) * nresults);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1722,6 +1724,8 @@ int usb4_port_sw_margin(struct tb_port *port, enum usb4_sb_target target,
|
||||
val |= USB4_MARGIN_SW_OPT_VOLTAGE;
|
||||
if (params->right_high)
|
||||
val |= USB4_MARGIN_SW_RH;
|
||||
if (params->upper_eye)
|
||||
val |= USB4_MARGIN_SW_UPPER_EYE;
|
||||
val |= FIELD_PREP(USB4_MARGIN_SW_COUNTER_MASK, params->error_counter);
|
||||
val |= FIELD_PREP(USB4_MARGIN_SW_VT_MASK, params->voltage_time_offset);
|
||||
|
||||
|
@ -808,7 +808,7 @@ static int check_dsp_e4(const u8 *dsp, int len)
|
||||
if (l > len)
|
||||
return 1;
|
||||
|
||||
/* zero is zero regardless endianes */
|
||||
/* zero is zero regardless endianness */
|
||||
} while (blockidx->NotLastBlock);
|
||||
}
|
||||
|
||||
@ -1276,7 +1276,7 @@ static void uea_set_bulk_timeout(struct uea_softc *sc, u32 dsrate)
|
||||
sc->stats.phy.dsrate == dsrate)
|
||||
return;
|
||||
|
||||
/* Original timming (1Mbit/s) from ADI (used in windows driver) */
|
||||
/* Original timing (1Mbit/s) from ADI (used in windows driver) */
|
||||
timeout = (dsrate <= 1024*1024) ? 0 : 1;
|
||||
ret = uea_request(sc, UEA_SET_TIMEOUT, timeout, 0, NULL);
|
||||
uea_info(INS_TO_USBDEV(sc), "setting new timeout %d%s\n",
|
||||
@ -1972,7 +1972,7 @@ static void uea_dispatch_cmv_e1(struct uea_softc *sc, struct intr_pkt *intr)
|
||||
if (cmv->bDirection != E1_MODEMTOHOST)
|
||||
goto bad1;
|
||||
|
||||
/* FIXME : ADI930 reply wrong preambule (func = 2, sub = 2) to
|
||||
/* FIXME : ADI930 reply wrong preamble (func = 2, sub = 2) to
|
||||
* the first MEMACCESS cmv. Ignore it...
|
||||
*/
|
||||
if (cmv->bFunction != dsc->function) {
|
||||
|
@ -1158,7 +1158,7 @@ int usbatm_usb_probe(struct usb_interface *intf, const struct usb_device_id *id,
|
||||
if (i >= num_rcv_urbs)
|
||||
list_add_tail(&urb->urb_list, &channel->list);
|
||||
|
||||
vdbg(&intf->dev, "%s: alloced buffer 0x%p buf size %u urb 0x%p",
|
||||
vdbg(&intf->dev, "%s: allocated buffer 0x%p buf size %u urb 0x%p",
|
||||
__func__, urb->transfer_buffer, urb->transfer_buffer_length, urb);
|
||||
}
|
||||
|
||||
|
@ -201,7 +201,7 @@ static void c67x00_drv_remove(struct platform_device *pdev)
|
||||
|
||||
static struct platform_driver c67x00_driver = {
|
||||
.probe = c67x00_drv_probe,
|
||||
.remove_new = c67x00_drv_remove,
|
||||
.remove = c67x00_drv_remove,
|
||||
.driver = {
|
||||
.name = "c67x00",
|
||||
},
|
||||
|
@ -422,7 +422,7 @@ MODULE_DEVICE_TABLE(of, cdns_imx_of_match);
|
||||
|
||||
static struct platform_driver cdns_imx_driver = {
|
||||
.probe = cdns_imx_probe,
|
||||
.remove_new = cdns_imx_remove,
|
||||
.remove = cdns_imx_remove,
|
||||
.driver = {
|
||||
.name = "cdns3-imx",
|
||||
.of_match_table = cdns_imx_of_match,
|
||||
|
@ -37,8 +37,6 @@ struct cdns3_wrap {
|
||||
#define PCI_DRIVER_NAME "cdns3-pci-usbss"
|
||||
#define PLAT_DRIVER_NAME "cdns-usb3"
|
||||
|
||||
#define PCI_DEVICE_ID_CDNS_USB3 0x0100
|
||||
|
||||
static struct pci_dev *cdns3_get_second_fun(struct pci_dev *pdev)
|
||||
{
|
||||
struct pci_dev *func;
|
||||
@ -189,7 +187,7 @@ static void cdns3_pci_remove(struct pci_dev *pdev)
|
||||
}
|
||||
|
||||
static const struct pci_device_id cdns3_pci_ids[] = {
|
||||
{ PCI_VDEVICE(CDNS, PCI_DEVICE_ID_CDNS_USB3) },
|
||||
{ PCI_VDEVICE(CDNS, PCI_DEVICE_ID_CDNS_USBSS) },
|
||||
{ 0, }
|
||||
};
|
||||
|
||||
|
@ -327,7 +327,7 @@ MODULE_DEVICE_TABLE(of, of_cdns3_match);
|
||||
|
||||
static struct platform_driver cdns3_driver = {
|
||||
.probe = cdns3_plat_probe,
|
||||
.remove_new = cdns3_plat_remove,
|
||||
.remove = cdns3_plat_remove,
|
||||
.driver = {
|
||||
.name = "cdns-usb3",
|
||||
.of_match_table = of_match_ptr(of_cdns3_match),
|
||||
|
@ -230,7 +230,7 @@ MODULE_DEVICE_TABLE(of, cdns_starfive_of_match);
|
||||
|
||||
static struct platform_driver cdns_starfive_driver = {
|
||||
.probe = cdns_starfive_probe,
|
||||
.remove_new = cdns_starfive_remove,
|
||||
.remove = cdns_starfive_remove,
|
||||
.driver = {
|
||||
.name = "cdns3-starfive",
|
||||
.of_match_table = cdns_starfive_of_match,
|
||||
|
@ -233,7 +233,7 @@ MODULE_DEVICE_TABLE(of, cdns_ti_of_match);
|
||||
|
||||
static struct platform_driver cdns_ti_driver = {
|
||||
.probe = cdns_ti_probe,
|
||||
.remove_new = cdns_ti_remove,
|
||||
.remove = cdns_ti_remove,
|
||||
.driver = {
|
||||
.name = "cdns3-ti",
|
||||
.of_match_table = cdns_ti_of_match,
|
||||
|
@ -28,12 +28,6 @@
|
||||
#define PCI_DRIVER_NAME "cdns-pci-usbssp"
|
||||
#define PLAT_DRIVER_NAME "cdns-usbssp"
|
||||
|
||||
#define PCI_DEVICE_ID_CDNS_USB3 0x0100
|
||||
#define PCI_DEVICE_ID_CDNS_UDC 0x0200
|
||||
|
||||
#define PCI_CLASS_SERIAL_USB_CDNS_USB3 (PCI_CLASS_SERIAL_USB << 8 | 0x80)
|
||||
#define PCI_CLASS_SERIAL_USB_CDNS_UDC PCI_CLASS_SERIAL_USB_DEVICE
|
||||
|
||||
static struct pci_dev *cdnsp_get_second_fun(struct pci_dev *pdev)
|
||||
{
|
||||
/*
|
||||
@ -41,10 +35,10 @@ static struct pci_dev *cdnsp_get_second_fun(struct pci_dev *pdev)
|
||||
* Platform has two function. The fist keeps resources for
|
||||
* Host/Device while the secon keeps resources for DRD/OTG.
|
||||
*/
|
||||
if (pdev->device == PCI_DEVICE_ID_CDNS_UDC)
|
||||
return pci_get_device(pdev->vendor, PCI_DEVICE_ID_CDNS_USB3, NULL);
|
||||
if (pdev->device == PCI_DEVICE_ID_CDNS_USB3)
|
||||
return pci_get_device(pdev->vendor, PCI_DEVICE_ID_CDNS_UDC, NULL);
|
||||
if (pdev->device == PCI_DEVICE_ID_CDNS_USBSSP)
|
||||
return pci_get_device(pdev->vendor, PCI_DEVICE_ID_CDNS_USBSS, NULL);
|
||||
if (pdev->device == PCI_DEVICE_ID_CDNS_USBSS)
|
||||
return pci_get_device(pdev->vendor, PCI_DEVICE_ID_CDNS_USBSSP, NULL);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@ -221,12 +215,12 @@ static const struct dev_pm_ops cdnsp_pci_pm_ops = {
|
||||
};
|
||||
|
||||
static const struct pci_device_id cdnsp_pci_ids[] = {
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_UDC),
|
||||
.class = PCI_CLASS_SERIAL_USB_CDNS_UDC },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_UDC),
|
||||
.class = PCI_CLASS_SERIAL_USB_CDNS_USB3 },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USB3),
|
||||
.class = PCI_CLASS_SERIAL_USB_CDNS_USB3 },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USBSSP),
|
||||
.class = PCI_CLASS_SERIAL_USB_DEVICE },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USBSSP),
|
||||
.class = PCI_CLASS_SERIAL_USB_CDNS },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USBSS),
|
||||
.class = PCI_CLASS_SERIAL_USB_CDNS },
|
||||
{ 0, }
|
||||
};
|
||||
|
||||
|
@ -25,6 +25,7 @@
|
||||
#define TD_PAGE_COUNT 5
|
||||
#define CI_HDRC_PAGE_SIZE 4096ul /* page size for TD's */
|
||||
#define ENDPT_MAX 32
|
||||
#define CI_MAX_REQ_SIZE (4 * CI_HDRC_PAGE_SIZE)
|
||||
#define CI_MAX_BUF_SIZE (TD_PAGE_COUNT * CI_HDRC_PAGE_SIZE)
|
||||
|
||||
/******************************************************************************
|
||||
@ -260,6 +261,7 @@ struct ci_hdrc {
|
||||
bool b_sess_valid_event;
|
||||
bool imx28_write_fix;
|
||||
bool has_portsc_pec_bug;
|
||||
bool has_short_pkt_limit;
|
||||
bool supports_runtime_pm;
|
||||
bool in_lpm;
|
||||
bool wakeup_int;
|
||||
|
@ -342,6 +342,7 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
|
||||
struct ci_hdrc_platform_data pdata = {
|
||||
.name = dev_name(&pdev->dev),
|
||||
.capoffset = DEF_CAPOFFSET,
|
||||
.flags = CI_HDRC_HAS_SHORT_PKT_LIMIT,
|
||||
.notify_event = ci_hdrc_imx_notify_event,
|
||||
};
|
||||
int ret;
|
||||
@ -675,7 +676,7 @@ static const struct dev_pm_ops ci_hdrc_imx_pm_ops = {
|
||||
};
|
||||
static struct platform_driver ci_hdrc_imx_driver = {
|
||||
.probe = ci_hdrc_imx_probe,
|
||||
.remove_new = ci_hdrc_imx_remove,
|
||||
.remove = ci_hdrc_imx_remove,
|
||||
.shutdown = ci_hdrc_imx_shutdown,
|
||||
.driver = {
|
||||
.name = "imx_usb",
|
||||
|
@ -292,7 +292,7 @@ MODULE_DEVICE_TABLE(of, msm_ci_dt_match);
|
||||
|
||||
static struct platform_driver ci_hdrc_msm_driver = {
|
||||
.probe = ci_hdrc_msm_probe,
|
||||
.remove_new = ci_hdrc_msm_remove,
|
||||
.remove = ci_hdrc_msm_remove,
|
||||
.driver = {
|
||||
.name = "msm_hsusb",
|
||||
.of_match_table = msm_ci_dt_match,
|
||||
|
@ -98,7 +98,7 @@ MODULE_DEVICE_TABLE(of, npcm_udc_dt_match);
|
||||
|
||||
static struct platform_driver npcm_udc_driver = {
|
||||
.probe = npcm_udc_probe,
|
||||
.remove_new = npcm_udc_remove,
|
||||
.remove = npcm_udc_remove,
|
||||
.driver = {
|
||||
.name = "npcm_udc",
|
||||
.of_match_table = npcm_udc_dt_match,
|
||||
|
@ -406,7 +406,7 @@ static struct platform_driver tegra_usb_driver = {
|
||||
.pm = pm_ptr(&tegra_usb_pm),
|
||||
},
|
||||
.probe = tegra_usb_probe,
|
||||
.remove_new = tegra_usb_remove,
|
||||
.remove = tegra_usb_remove,
|
||||
};
|
||||
module_platform_driver(tegra_usb_driver);
|
||||
|
||||
|
@ -116,7 +116,7 @@ static void ci_hdrc_usb2_remove(struct platform_device *pdev)
|
||||
|
||||
static struct platform_driver ci_hdrc_usb2_driver = {
|
||||
.probe = ci_hdrc_usb2_probe,
|
||||
.remove_new = ci_hdrc_usb2_remove,
|
||||
.remove = ci_hdrc_usb2_remove,
|
||||
.driver = {
|
||||
.name = "chipidea-usb2",
|
||||
.of_match_table = ci_hdrc_usb2_of_match,
|
||||
|
@ -765,7 +765,7 @@ static int ci_get_platdata(struct device *dev,
|
||||
|
||||
ext_id = ERR_PTR(-ENODEV);
|
||||
ext_vbus = ERR_PTR(-ENODEV);
|
||||
if (of_property_read_bool(dev->of_node, "extcon")) {
|
||||
if (of_property_present(dev->of_node, "extcon")) {
|
||||
/* Each one of them is not mandatory */
|
||||
ext_vbus = extcon_get_edev_by_phandle(dev, 0);
|
||||
if (IS_ERR(ext_vbus) && PTR_ERR(ext_vbus) != -ENODEV)
|
||||
@ -1076,6 +1076,8 @@ static int ci_hdrc_probe(struct platform_device *pdev)
|
||||
CI_HDRC_SUPPORTS_RUNTIME_PM);
|
||||
ci->has_portsc_pec_bug = !!(ci->platdata->flags &
|
||||
CI_HDRC_HAS_PORTSC_PEC_MISSED);
|
||||
ci->has_short_pkt_limit = !!(ci->platdata->flags &
|
||||
CI_HDRC_HAS_SHORT_PKT_LIMIT);
|
||||
platform_set_drvdata(pdev, ci);
|
||||
|
||||
ret = hw_device_init(ci, base);
|
||||
@ -1495,7 +1497,7 @@ static const struct dev_pm_ops ci_pm_ops = {
|
||||
|
||||
static struct platform_driver ci_hdrc_driver = {
|
||||
.probe = ci_hdrc_probe,
|
||||
.remove_new = ci_hdrc_remove,
|
||||
.remove = ci_hdrc_remove,
|
||||
.driver = {
|
||||
.name = "ci_hdrc",
|
||||
.pm = &ci_pm_ops,
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dmapool.h>
|
||||
#include <linux/dma-direct.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/irqreturn.h>
|
||||
#include <linux/kernel.h>
|
||||
@ -540,6 +541,126 @@ static int prepare_td_for_sg(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify if the scatterlist is valid by iterating each sg entry.
|
||||
* Return invalid sg entry index which is less than num_sgs.
|
||||
*/
|
||||
static int sglist_get_invalid_entry(struct device *dma_dev, u8 dir,
|
||||
struct usb_request *req)
|
||||
{
|
||||
int i;
|
||||
struct scatterlist *s = req->sg;
|
||||
|
||||
if (req->num_sgs == 1)
|
||||
return 1;
|
||||
|
||||
dir = dir ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
|
||||
|
||||
for (i = 0; i < req->num_sgs; i++, s = sg_next(s)) {
|
||||
/* Only small sg (generally last sg) may be bounced. If
|
||||
* that happens. we can't ensure the addr is page-aligned
|
||||
* after dma map.
|
||||
*/
|
||||
if (dma_kmalloc_needs_bounce(dma_dev, s->length, dir))
|
||||
break;
|
||||
|
||||
/* Make sure each sg start address (except first sg) is
|
||||
* page-aligned and end address (except last sg) is also
|
||||
* page-aligned.
|
||||
*/
|
||||
if (i == 0) {
|
||||
if (!IS_ALIGNED(s->offset + s->length,
|
||||
CI_HDRC_PAGE_SIZE))
|
||||
break;
|
||||
} else {
|
||||
if (s->offset)
|
||||
break;
|
||||
if (!sg_is_last(s) && !IS_ALIGNED(s->length,
|
||||
CI_HDRC_PAGE_SIZE))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static int sglist_do_bounce(struct ci_hw_req *hwreq, int index,
|
||||
bool copy, unsigned int *bounced)
|
||||
{
|
||||
void *buf;
|
||||
int i, ret, nents, num_sgs;
|
||||
unsigned int rest, rounded;
|
||||
struct scatterlist *sg, *src, *dst;
|
||||
|
||||
nents = index + 1;
|
||||
ret = sg_alloc_table(&hwreq->sgt, nents, GFP_KERNEL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
sg = src = hwreq->req.sg;
|
||||
num_sgs = hwreq->req.num_sgs;
|
||||
rest = hwreq->req.length;
|
||||
dst = hwreq->sgt.sgl;
|
||||
|
||||
for (i = 0; i < index; i++) {
|
||||
memcpy(dst, src, sizeof(*src));
|
||||
rest -= src->length;
|
||||
src = sg_next(src);
|
||||
dst = sg_next(dst);
|
||||
}
|
||||
|
||||
/* create one bounce buffer */
|
||||
rounded = round_up(rest, CI_HDRC_PAGE_SIZE);
|
||||
buf = kmalloc(rounded, GFP_KERNEL);
|
||||
if (!buf) {
|
||||
sg_free_table(&hwreq->sgt);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
sg_set_buf(dst, buf, rounded);
|
||||
|
||||
hwreq->req.sg = hwreq->sgt.sgl;
|
||||
hwreq->req.num_sgs = nents;
|
||||
hwreq->sgt.sgl = sg;
|
||||
hwreq->sgt.nents = num_sgs;
|
||||
|
||||
if (copy)
|
||||
sg_copy_to_buffer(src, num_sgs - index, buf, rest);
|
||||
|
||||
*bounced = rest;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sglist_do_debounce(struct ci_hw_req *hwreq, bool copy)
|
||||
{
|
||||
void *buf;
|
||||
int i, nents, num_sgs;
|
||||
struct scatterlist *sg, *src, *dst;
|
||||
|
||||
sg = hwreq->req.sg;
|
||||
num_sgs = hwreq->req.num_sgs;
|
||||
src = sg_last(sg, num_sgs);
|
||||
buf = sg_virt(src);
|
||||
|
||||
if (copy) {
|
||||
dst = hwreq->sgt.sgl;
|
||||
for (i = 0; i < num_sgs - 1; i++)
|
||||
dst = sg_next(dst);
|
||||
|
||||
nents = hwreq->sgt.nents - num_sgs + 1;
|
||||
sg_copy_from_buffer(dst, nents, buf, sg_dma_len(src));
|
||||
}
|
||||
|
||||
hwreq->req.sg = hwreq->sgt.sgl;
|
||||
hwreq->req.num_sgs = hwreq->sgt.nents;
|
||||
hwreq->sgt.sgl = sg;
|
||||
hwreq->sgt.nents = num_sgs;
|
||||
|
||||
kfree(buf);
|
||||
sg_free_table(&hwreq->sgt);
|
||||
}
|
||||
|
||||
/**
|
||||
* _hardware_enqueue: configures a request at hardware level
|
||||
* @hwep: endpoint
|
||||
@ -552,6 +673,8 @@ static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
|
||||
struct ci_hdrc *ci = hwep->ci;
|
||||
int ret = 0;
|
||||
struct td_node *firstnode, *lastnode;
|
||||
unsigned int bounced_size;
|
||||
struct scatterlist *sg;
|
||||
|
||||
/* don't queue twice */
|
||||
if (hwreq->req.status == -EALREADY)
|
||||
@ -559,11 +682,29 @@ static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
|
||||
|
||||
hwreq->req.status = -EALREADY;
|
||||
|
||||
if (hwreq->req.num_sgs && hwreq->req.length &&
|
||||
ci->has_short_pkt_limit) {
|
||||
ret = sglist_get_invalid_entry(ci->dev->parent, hwep->dir,
|
||||
&hwreq->req);
|
||||
if (ret < hwreq->req.num_sgs) {
|
||||
ret = sglist_do_bounce(hwreq, ret, hwep->dir == TX,
|
||||
&bounced_size);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = usb_gadget_map_request_by_dev(ci->dev->parent,
|
||||
&hwreq->req, hwep->dir);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (hwreq->sgt.sgl) {
|
||||
/* We've mapped a bigger buffer, now recover the actual size */
|
||||
sg = sg_last(hwreq->req.sg, hwreq->req.num_sgs);
|
||||
sg_dma_len(sg) = min(sg_dma_len(sg), bounced_size);
|
||||
}
|
||||
|
||||
if (hwreq->req.num_mapped_sgs)
|
||||
ret = prepare_td_for_sg(hwep, hwreq);
|
||||
else
|
||||
@ -612,10 +753,17 @@ static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
|
||||
do {
|
||||
hw_write(ci, OP_USBCMD, USBCMD_ATDTW, USBCMD_ATDTW);
|
||||
tmp_stat = hw_read(ci, OP_ENDPTSTAT, BIT(n));
|
||||
} while (!hw_read(ci, OP_USBCMD, USBCMD_ATDTW));
|
||||
} while (!hw_read(ci, OP_USBCMD, USBCMD_ATDTW) && tmp_stat);
|
||||
hw_write(ci, OP_USBCMD, USBCMD_ATDTW, 0);
|
||||
if (tmp_stat)
|
||||
goto done;
|
||||
|
||||
/* OP_ENDPTSTAT will be clear by HW when the endpoint met
|
||||
* err. This dTD don't push to dQH if current dTD point is
|
||||
* not the last one in previous request.
|
||||
*/
|
||||
if (hwep->qh.ptr->curr != cpu_to_le32(prevlastnode->dma))
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* QH configuration */
|
||||
@ -676,6 +824,7 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
|
||||
unsigned remaining_length;
|
||||
unsigned actual = hwreq->req.length;
|
||||
struct ci_hdrc *ci = hwep->ci;
|
||||
bool is_isoc = hwep->type == USB_ENDPOINT_XFER_ISOC;
|
||||
|
||||
if (hwreq->req.status != -EALREADY)
|
||||
return -EINVAL;
|
||||
@ -689,7 +838,7 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
|
||||
int n = hw_ep_bit(hwep->num, hwep->dir);
|
||||
|
||||
if (ci->rev == CI_REVISION_24 ||
|
||||
ci->rev == CI_REVISION_22)
|
||||
ci->rev == CI_REVISION_22 || is_isoc)
|
||||
if (!hw_read(ci, OP_ENDPTSTAT, BIT(n)))
|
||||
reprime_dtd(ci, hwep, node);
|
||||
hwreq->req.status = -EALREADY;
|
||||
@ -708,11 +857,15 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
|
||||
hwreq->req.status = -EPROTO;
|
||||
break;
|
||||
} else if ((TD_STATUS_TR_ERR & hwreq->req.status)) {
|
||||
hwreq->req.status = -EILSEQ;
|
||||
break;
|
||||
if (is_isoc) {
|
||||
hwreq->req.status = 0;
|
||||
} else {
|
||||
hwreq->req.status = -EILSEQ;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (remaining_length) {
|
||||
if (remaining_length && !is_isoc) {
|
||||
if (hwep->dir == TX) {
|
||||
hwreq->req.status = -EPROTO;
|
||||
break;
|
||||
@ -733,6 +886,10 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
|
||||
usb_gadget_unmap_request_by_dev(hwep->ci->dev->parent,
|
||||
&hwreq->req, hwep->dir);
|
||||
|
||||
/* sglist bounced */
|
||||
if (hwreq->sgt.sgl)
|
||||
sglist_do_debounce(hwreq, hwep->dir == RX);
|
||||
|
||||
hwreq->req.actual += actual;
|
||||
|
||||
if (hwreq->req.status)
|
||||
@ -960,6 +1117,12 @@ static int _ep_queue(struct usb_ep *ep, struct usb_request *req,
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
if (ci->has_short_pkt_limit &&
|
||||
hwreq->req.length > CI_MAX_REQ_SIZE) {
|
||||
dev_err(hwep->ci->dev, "request length too big (max 16KB)\n");
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
/* first nuke then test link, e.g. previous status has not sent */
|
||||
if (!list_empty(&hwreq->queue)) {
|
||||
dev_err(hwep->ci->dev, "request already in queue\n");
|
||||
@ -1574,6 +1737,9 @@ static int ep_dequeue(struct usb_ep *ep, struct usb_request *req)
|
||||
|
||||
usb_gadget_unmap_request(&hwep->ci->gadget, req, hwep->dir);
|
||||
|
||||
if (hwreq->sgt.sgl)
|
||||
sglist_do_debounce(hwreq, false);
|
||||
|
||||
req->status = -ECONNRESET;
|
||||
|
||||
if (hwreq->req.complete != NULL) {
|
||||
@ -2063,7 +2229,7 @@ static irqreturn_t udc_irq(struct ci_hdrc *ci)
|
||||
}
|
||||
}
|
||||
|
||||
if (USBi_UI & intr)
|
||||
if ((USBi_UI | USBi_UEI) & intr)
|
||||
isr_tr_complete_handler(ci);
|
||||
|
||||
if ((USBi_SLI & intr) && !(ci->suspended)) {
|
||||
|
@ -69,11 +69,13 @@ struct td_node {
|
||||
* @req: request structure for gadget drivers
|
||||
* @queue: link to QH list
|
||||
* @tds: link to TD list
|
||||
* @sgt: hold original sglist when bounce sglist
|
||||
*/
|
||||
struct ci_hw_req {
|
||||
struct usb_request req;
|
||||
struct list_head queue;
|
||||
struct list_head tds;
|
||||
struct sg_table sgt;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_USB_CHIPIDEA_UDC
|
||||
|
@ -1285,6 +1285,10 @@ static const struct of_device_id usbmisc_imx_dt_ids[] = {
|
||||
.compatible = "fsl,imx7ulp-usbmisc",
|
||||
.data = &imx7ulp_usbmisc_ops,
|
||||
},
|
||||
{
|
||||
.compatible = "fsl,imx8ulp-usbmisc",
|
||||
.data = &imx7ulp_usbmisc_ops,
|
||||
},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, usbmisc_imx_dt_ids);
|
||||
|
@ -415,6 +415,9 @@ EXPORT_SYMBOL_GPL(usb_of_get_companion_dev);
|
||||
struct dentry *usb_debug_root;
|
||||
EXPORT_SYMBOL_GPL(usb_debug_root);
|
||||
|
||||
DEFINE_MUTEX(usb_dynids_lock);
|
||||
EXPORT_SYMBOL_GPL(usb_dynids_lock);
|
||||
|
||||
static int __init usb_common_init(void)
|
||||
{
|
||||
usb_debug_root = debugfs_create_dir("usb", NULL);
|
||||
|
@ -340,7 +340,7 @@ MODULE_DEVICE_TABLE(of, usb_conn_dt_match);
|
||||
|
||||
static struct platform_driver usb_conn_driver = {
|
||||
.probe = usb_conn_probe,
|
||||
.remove_new = usb_conn_remove,
|
||||
.remove = usb_conn_remove,
|
||||
.driver = {
|
||||
.name = "usb-conn-gpio",
|
||||
.pm = &usb_conn_pm_ops,
|
||||
|
@ -924,7 +924,7 @@ int usb_get_configuration(struct usb_device *dev)
|
||||
result = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
length = max((int) le16_to_cpu(desc->wTotalLength),
|
||||
length = max_t(int, le16_to_cpu(desc->wTotalLength),
|
||||
USB_DT_CONFIG_SIZE);
|
||||
|
||||
/* Now that we know the length, get the whole thing */
|
||||
|
@ -238,6 +238,9 @@ static int usbdev_mmap(struct file *file, struct vm_area_struct *vma)
|
||||
dma_addr_t dma_handle = DMA_MAPPING_ERROR;
|
||||
int ret;
|
||||
|
||||
if (!(file->f_mode & FMODE_WRITE))
|
||||
return -EPERM;
|
||||
|
||||
ret = usbfs_increase_memory_usage(size + sizeof(struct usb_memory));
|
||||
if (ret)
|
||||
goto error;
|
||||
@ -1295,7 +1298,7 @@ static int do_proc_bulk(struct usb_dev_state *ps,
|
||||
return ret;
|
||||
|
||||
len1 = bulk->len;
|
||||
if (len1 < 0 || len1 >= (INT_MAX - sizeof(struct urb)))
|
||||
if (len1 >= (INT_MAX - sizeof(struct urb)))
|
||||
return -EINVAL;
|
||||
|
||||
if (bulk->ep & USB_DIR_IN)
|
||||
|
@ -95,9 +95,9 @@ ssize_t usb_store_new_id(struct usb_dynids *dynids,
|
||||
}
|
||||
}
|
||||
|
||||
spin_lock(&dynids->lock);
|
||||
mutex_lock(&usb_dynids_lock);
|
||||
list_add_tail(&dynid->node, &dynids->list);
|
||||
spin_unlock(&dynids->lock);
|
||||
mutex_unlock(&usb_dynids_lock);
|
||||
|
||||
retval = driver_attach(driver);
|
||||
|
||||
@ -116,6 +116,7 @@ ssize_t usb_show_dynids(struct usb_dynids *dynids, char *buf)
|
||||
struct usb_dynid *dynid;
|
||||
size_t count = 0;
|
||||
|
||||
guard(mutex)(&usb_dynids_lock);
|
||||
list_for_each_entry(dynid, &dynids->list, node)
|
||||
if (dynid->id.bInterfaceClass != 0)
|
||||
count += scnprintf(&buf[count], PAGE_SIZE - count, "%04x %04x %02x\n",
|
||||
@ -160,7 +161,7 @@ static ssize_t remove_id_store(struct device_driver *driver, const char *buf,
|
||||
if (fields < 2)
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock(&usb_driver->dynids.lock);
|
||||
guard(mutex)(&usb_dynids_lock);
|
||||
list_for_each_entry_safe(dynid, n, &usb_driver->dynids.list, node) {
|
||||
struct usb_device_id *id = &dynid->id;
|
||||
|
||||
@ -171,7 +172,6 @@ static ssize_t remove_id_store(struct device_driver *driver, const char *buf,
|
||||
break;
|
||||
}
|
||||
}
|
||||
spin_unlock(&usb_driver->dynids.lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
@ -220,27 +220,24 @@ static void usb_free_dynids(struct usb_driver *usb_drv)
|
||||
{
|
||||
struct usb_dynid *dynid, *n;
|
||||
|
||||
spin_lock(&usb_drv->dynids.lock);
|
||||
guard(mutex)(&usb_dynids_lock);
|
||||
list_for_each_entry_safe(dynid, n, &usb_drv->dynids.list, node) {
|
||||
list_del(&dynid->node);
|
||||
kfree(dynid);
|
||||
}
|
||||
spin_unlock(&usb_drv->dynids.lock);
|
||||
}
|
||||
|
||||
static const struct usb_device_id *usb_match_dynamic_id(struct usb_interface *intf,
|
||||
struct usb_driver *drv)
|
||||
const struct usb_driver *drv)
|
||||
{
|
||||
struct usb_dynid *dynid;
|
||||
|
||||
spin_lock(&drv->dynids.lock);
|
||||
guard(mutex)(&usb_dynids_lock);
|
||||
list_for_each_entry(dynid, &drv->dynids.list, node) {
|
||||
if (usb_match_one_id(intf, &dynid->id)) {
|
||||
spin_unlock(&drv->dynids.lock);
|
||||
return &dynid->id;
|
||||
}
|
||||
}
|
||||
spin_unlock(&drv->dynids.lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -853,7 +850,7 @@ const struct usb_device_id *usb_device_match_id(struct usb_device *udev,
|
||||
EXPORT_SYMBOL_GPL(usb_device_match_id);
|
||||
|
||||
bool usb_driver_applicable(struct usb_device *udev,
|
||||
struct usb_device_driver *udrv)
|
||||
const struct usb_device_driver *udrv)
|
||||
{
|
||||
if (udrv->id_table && udrv->match)
|
||||
return usb_device_match_id(udev, udrv->id_table) != NULL &&
|
||||
@ -873,7 +870,7 @@ static int usb_device_match(struct device *dev, const struct device_driver *drv)
|
||||
/* devices and interfaces are handled separately */
|
||||
if (is_usb_device(dev)) {
|
||||
struct usb_device *udev;
|
||||
struct usb_device_driver *udrv;
|
||||
const struct usb_device_driver *udrv;
|
||||
|
||||
/* interface drivers never match devices */
|
||||
if (!is_usb_device_driver(drv))
|
||||
@ -893,7 +890,7 @@ static int usb_device_match(struct device *dev, const struct device_driver *drv)
|
||||
|
||||
} else if (is_usb_interface(dev)) {
|
||||
struct usb_interface *intf;
|
||||
struct usb_driver *usb_drv;
|
||||
const struct usb_driver *usb_drv;
|
||||
const struct usb_device_id *id;
|
||||
|
||||
/* device drivers never match interfaces */
|
||||
@ -1076,7 +1073,6 @@ int usb_register_driver(struct usb_driver *new_driver, struct module *owner,
|
||||
new_driver->driver.owner = owner;
|
||||
new_driver->driver.mod_name = mod_name;
|
||||
new_driver->driver.dev_groups = new_driver->dev_groups;
|
||||
spin_lock_init(&new_driver->dynids.lock);
|
||||
INIT_LIST_HEAD(&new_driver->dynids.list);
|
||||
|
||||
retval = driver_register(&new_driver->driver);
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/usb.h>
|
||||
#include "usb.h"
|
||||
|
||||
@ -39,7 +40,7 @@ static ssize_t field##_show(struct device *dev, \
|
||||
char *buf) \
|
||||
{ \
|
||||
struct ep_device *ep = to_ep_device(dev); \
|
||||
return sprintf(buf, format_string, ep->desc->field); \
|
||||
return sysfs_emit(buf, format_string, ep->desc->field); \
|
||||
} \
|
||||
static DEVICE_ATTR_RO(field)
|
||||
|
||||
@ -52,7 +53,7 @@ static ssize_t wMaxPacketSize_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct ep_device *ep = to_ep_device(dev);
|
||||
return sprintf(buf, "%04x\n", usb_endpoint_maxp(ep->desc));
|
||||
return sysfs_emit(buf, "%04x\n", usb_endpoint_maxp(ep->desc));
|
||||
}
|
||||
static DEVICE_ATTR_RO(wMaxPacketSize);
|
||||
|
||||
@ -76,7 +77,7 @@ static ssize_t type_show(struct device *dev, struct device_attribute *attr,
|
||||
type = "Interrupt";
|
||||
break;
|
||||
}
|
||||
return sprintf(buf, "%s\n", type);
|
||||
return sysfs_emit(buf, "%s\n", type);
|
||||
}
|
||||
static DEVICE_ATTR_RO(type);
|
||||
|
||||
@ -95,7 +96,7 @@ static ssize_t interval_show(struct device *dev, struct device_attribute *attr,
|
||||
interval /= 1000;
|
||||
}
|
||||
|
||||
return sprintf(buf, "%d%cs\n", interval, unit);
|
||||
return sysfs_emit(buf, "%d%cs\n", interval, unit);
|
||||
}
|
||||
static DEVICE_ATTR_RO(interval);
|
||||
|
||||
@ -111,7 +112,7 @@ static ssize_t direction_show(struct device *dev, struct device_attribute *attr,
|
||||
direction = "in";
|
||||
else
|
||||
direction = "out";
|
||||
return sprintf(buf, "%s\n", direction);
|
||||
return sysfs_emit(buf, "%s\n", direction);
|
||||
}
|
||||
static DEVICE_ATTR_RO(direction);
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/usb/of.h>
|
||||
|
||||
@ -87,7 +88,7 @@ static ssize_t usbport_trig_port_show(struct device *dev,
|
||||
struct usbport_trig_port,
|
||||
attr);
|
||||
|
||||
return sprintf(buf, "%d\n", port->observed) + 1;
|
||||
return sysfs_emit(buf, "%d\n", port->observed) + 1;
|
||||
}
|
||||
|
||||
static ssize_t usbport_trig_port_store(struct device *dev,
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
#include <linux/kstrtox.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/pm_qos.h>
|
||||
#include <linux/component.h>
|
||||
#include <linux/usb/of.h>
|
||||
@ -166,7 +167,7 @@ static ssize_t location_show(struct device *dev,
|
||||
{
|
||||
struct usb_port *port_dev = to_usb_port(dev);
|
||||
|
||||
return sprintf(buf, "0x%08x\n", port_dev->location);
|
||||
return sysfs_emit(buf, "0x%08x\n", port_dev->location);
|
||||
}
|
||||
static DEVICE_ATTR_RO(location);
|
||||
|
||||
@ -191,7 +192,7 @@ static ssize_t connect_type_show(struct device *dev,
|
||||
break;
|
||||
}
|
||||
|
||||
return sprintf(buf, "%s\n", result);
|
||||
return sysfs_emit(buf, "%s\n", result);
|
||||
}
|
||||
static DEVICE_ATTR_RO(connect_type);
|
||||
|
||||
@ -210,7 +211,7 @@ static ssize_t over_current_count_show(struct device *dev,
|
||||
{
|
||||
struct usb_port *port_dev = to_usb_port(dev);
|
||||
|
||||
return sprintf(buf, "%u\n", port_dev->over_current_count);
|
||||
return sysfs_emit(buf, "%u\n", port_dev->over_current_count);
|
||||
}
|
||||
static DEVICE_ATTR_RO(over_current_count);
|
||||
|
||||
@ -219,7 +220,7 @@ static ssize_t quirks_show(struct device *dev,
|
||||
{
|
||||
struct usb_port *port_dev = to_usb_port(dev);
|
||||
|
||||
return sprintf(buf, "%08x\n", port_dev->quirks);
|
||||
return sysfs_emit(buf, "%08x\n", port_dev->quirks);
|
||||
}
|
||||
|
||||
static ssize_t quirks_store(struct device *dev, struct device_attribute *attr,
|
||||
@ -254,7 +255,7 @@ static ssize_t usb3_lpm_permit_show(struct device *dev,
|
||||
p = "0";
|
||||
}
|
||||
|
||||
return sprintf(buf, "%s\n", p);
|
||||
return sysfs_emit(buf, "%s\n", p);
|
||||
}
|
||||
|
||||
static ssize_t usb3_lpm_permit_store(struct device *dev,
|
||||
|
@ -75,7 +75,7 @@ extern int usb_match_device(struct usb_device *dev,
|
||||
extern const struct usb_device_id *usb_device_match_id(struct usb_device *udev,
|
||||
const struct usb_device_id *id);
|
||||
extern bool usb_driver_applicable(struct usb_device *udev,
|
||||
struct usb_device_driver *udrv);
|
||||
const struct usb_device_driver *udrv);
|
||||
extern void usb_forced_unbind_intf(struct usb_interface *intf);
|
||||
extern void usb_unbind_and_rebind_marked_interfaces(struct usb_device *udev);
|
||||
|
||||
|
@ -756,7 +756,7 @@ static struct platform_driver dwc2_platform_driver = {
|
||||
.pm = &dwc2_dev_pm_ops,
|
||||
},
|
||||
.probe = dwc2_driver_probe,
|
||||
.remove_new = dwc2_driver_remove,
|
||||
.remove = dwc2_driver_remove,
|
||||
.shutdown = dwc2_driver_shutdown,
|
||||
};
|
||||
|
||||
|
@ -1409,7 +1409,7 @@ static int dwc3_core_init(struct dwc3 *dwc)
|
||||
|
||||
/*
|
||||
* When configured in HOST mode, after issuing U3/L2 exit controller
|
||||
* fails to send proper CRC checksum in CRC5 feild. Because of this
|
||||
* fails to send proper CRC checksum in CRC5 field. Because of this
|
||||
* behaviour Transaction Error is generated, resulting in reset and
|
||||
* re-enumeration of usb device attached. All the termsel, xcvrsel,
|
||||
* opmode becomes 0 during end of resume. Enabling bit 10 of GUCTL1
|
||||
@ -1470,9 +1470,13 @@ static int dwc3_core_init(struct dwc3 *dwc)
|
||||
if (hw_mode != DWC3_GHWPARAMS0_MODE_GADGET &&
|
||||
(DWC3_IP_IS(DWC31)) &&
|
||||
dwc->maximum_speed == USB_SPEED_SUPER) {
|
||||
reg = dwc3_readl(dwc->regs, DWC3_LLUCTL);
|
||||
reg |= DWC3_LLUCTL_FORCE_GEN1;
|
||||
dwc3_writel(dwc->regs, DWC3_LLUCTL, reg);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < dwc->num_usb3_ports; i++) {
|
||||
reg = dwc3_readl(dwc->regs, DWC3_LLUCTL(i));
|
||||
reg |= DWC3_LLUCTL_FORCE_GEN1;
|
||||
dwc3_writel(dwc->regs, DWC3_LLUCTL(i), reg);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -1941,7 +1945,7 @@ static struct extcon_dev *dwc3_get_extcon(struct dwc3 *dwc)
|
||||
struct extcon_dev *edev = NULL;
|
||||
const char *name;
|
||||
|
||||
if (device_property_read_bool(dev, "extcon"))
|
||||
if (device_property_present(dev, "extcon"))
|
||||
return extcon_get_edev_by_phandle(dev, 0);
|
||||
|
||||
/*
|
||||
@ -2651,7 +2655,7 @@ MODULE_DEVICE_TABLE(acpi, dwc3_acpi_match);
|
||||
|
||||
static struct platform_driver dwc3_driver = {
|
||||
.probe = dwc3_probe,
|
||||
.remove_new = dwc3_remove,
|
||||
.remove = dwc3_remove,
|
||||
.driver = {
|
||||
.name = "dwc3",
|
||||
.of_match_table = of_match_ptr(of_dwc3_match),
|
||||
|
@ -81,7 +81,7 @@
|
||||
#define DWC3_GSNPSREV_MASK 0xffff
|
||||
#define DWC3_GSNPS_ID(p) (((p) & DWC3_GSNPSID_MASK) >> 16)
|
||||
|
||||
/* DWC3 registers memory space boundries */
|
||||
/* DWC3 registers memory space boundaries */
|
||||
#define DWC3_XHCI_REGS_START 0x0
|
||||
#define DWC3_XHCI_REGS_END 0x7fff
|
||||
#define DWC3_GLOBALS_REGS_START 0xc100
|
||||
@ -179,7 +179,7 @@
|
||||
#define DWC3_OEVTEN 0xcc0C
|
||||
#define DWC3_OSTS 0xcc10
|
||||
|
||||
#define DWC3_LLUCTL 0xd024
|
||||
#define DWC3_LLUCTL(n) (0xd024 + ((n) * 0x80))
|
||||
|
||||
/* Bit fields */
|
||||
|
||||
@ -915,6 +915,7 @@ struct dwc3_hwparams {
|
||||
#define DWC3_MODE(n) ((n) & 0x7)
|
||||
|
||||
/* HWPARAMS1 */
|
||||
#define DWC3_SPRAM_TYPE(n) (((n) >> 23) & 1)
|
||||
#define DWC3_NUM_INT(n) (((n) & (0x3f << 15)) >> 15)
|
||||
|
||||
/* HWPARAMS3 */
|
||||
@ -925,6 +926,9 @@ struct dwc3_hwparams {
|
||||
#define DWC3_NUM_IN_EPS(p) (((p)->hwparams3 & \
|
||||
(DWC3_NUM_IN_EPS_MASK)) >> 18)
|
||||
|
||||
/* HWPARAMS6 */
|
||||
#define DWC3_RAM0_DEPTH(n) (((n) & (0xffff0000)) >> 16)
|
||||
|
||||
/* HWPARAMS7 */
|
||||
#define DWC3_RAM1_DEPTH(n) ((n) & 0xffff)
|
||||
|
||||
@ -937,18 +941,14 @@ struct dwc3_hwparams {
|
||||
* @request: struct usb_request to be transferred
|
||||
* @list: a list_head used for request queueing
|
||||
* @dep: struct dwc3_ep owning this request
|
||||
* @sg: pointer to first incomplete sg
|
||||
* @start_sg: pointer to the sg which should be queued next
|
||||
* @num_pending_sgs: counter to pending sgs
|
||||
* @num_queued_sgs: counter to the number of sgs which already got queued
|
||||
* @remaining: amount of data remaining
|
||||
* @status: internal dwc3 request status tracking
|
||||
* @epnum: endpoint number to which this request refers
|
||||
* @trb: pointer to struct dwc3_trb
|
||||
* @trb_dma: DMA address of @trb
|
||||
* @num_trbs: number of TRBs used by this request
|
||||
* @needs_extra_trb: true when request needs one extra TRB (either due to ZLP
|
||||
* or unaligned OUT)
|
||||
* @direction: IN or OUT direction flag
|
||||
* @mapped: true when request has been dma-mapped
|
||||
*/
|
||||
@ -960,7 +960,6 @@ struct dwc3_request {
|
||||
struct scatterlist *start_sg;
|
||||
|
||||
unsigned int num_pending_sgs;
|
||||
unsigned int num_queued_sgs;
|
||||
unsigned int remaining;
|
||||
|
||||
unsigned int status;
|
||||
@ -978,7 +977,6 @@ struct dwc3_request {
|
||||
|
||||
unsigned int num_trbs;
|
||||
|
||||
unsigned int needs_extra_trb:1;
|
||||
unsigned int direction:1;
|
||||
unsigned int mapped:1;
|
||||
};
|
||||
|
@ -377,7 +377,7 @@ MODULE_DEVICE_TABLE(of, dwc3_ti_of_match);
|
||||
|
||||
static struct platform_driver dwc3_ti_driver = {
|
||||
.probe = dwc3_ti_probe,
|
||||
.remove_new = dwc3_ti_remove,
|
||||
.remove = dwc3_ti_remove,
|
||||
.driver = {
|
||||
.name = "dwc3-am62",
|
||||
.pm = DEV_PM_OPS,
|
||||
|
@ -243,7 +243,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(dwc3_exynos_dev_pm_ops,
|
||||
|
||||
static struct platform_driver dwc3_exynos_driver = {
|
||||
.probe = dwc3_exynos_probe,
|
||||
.remove_new = dwc3_exynos_remove,
|
||||
.remove = dwc3_exynos_remove,
|
||||
.driver = {
|
||||
.name = "exynos-dwc3",
|
||||
.of_match_table = exynos_dwc3_match,
|
||||
|
@ -400,7 +400,7 @@ MODULE_DEVICE_TABLE(of, dwc3_imx8mp_of_match);
|
||||
|
||||
static struct platform_driver dwc3_imx8mp_driver = {
|
||||
.probe = dwc3_imx8mp_probe,
|
||||
.remove_new = dwc3_imx8mp_remove,
|
||||
.remove = dwc3_imx8mp_remove,
|
||||
.driver = {
|
||||
.name = "imx8mp-dwc3",
|
||||
.pm = pm_ptr(&dwc3_imx8mp_dev_pm_ops),
|
||||
|
@ -208,7 +208,7 @@ MODULE_DEVICE_TABLE(of, kdwc3_of_match);
|
||||
|
||||
static struct platform_driver kdwc3_driver = {
|
||||
.probe = kdwc3_probe,
|
||||
.remove_new = kdwc3_remove,
|
||||
.remove = kdwc3_remove,
|
||||
.driver = {
|
||||
.name = "keystone-dwc3",
|
||||
.of_match_table = kdwc3_of_match,
|
||||
|
@ -968,7 +968,7 @@ MODULE_DEVICE_TABLE(of, dwc3_meson_g12a_match);
|
||||
|
||||
static struct platform_driver dwc3_meson_g12a_driver = {
|
||||
.probe = dwc3_meson_g12a_probe,
|
||||
.remove_new = dwc3_meson_g12a_remove,
|
||||
.remove = dwc3_meson_g12a_remove,
|
||||
.driver = {
|
||||
.name = "dwc3-meson-g12a",
|
||||
.of_match_table = dwc3_meson_g12a_match,
|
||||
|
@ -520,7 +520,7 @@ MODULE_DEVICE_TABLE(of, dwc3_octeon_of_match);
|
||||
|
||||
static struct platform_driver dwc3_octeon_driver = {
|
||||
.probe = dwc3_octeon_probe,
|
||||
.remove_new = dwc3_octeon_remove,
|
||||
.remove = dwc3_octeon_remove,
|
||||
.driver = {
|
||||
.name = "dwc3-octeon",
|
||||
.of_match_table = dwc3_octeon_of_match,
|
||||
|
@ -180,7 +180,7 @@ MODULE_DEVICE_TABLE(of, of_dwc3_simple_match);
|
||||
|
||||
static struct platform_driver dwc3_of_simple_driver = {
|
||||
.probe = dwc3_of_simple_probe,
|
||||
.remove_new = dwc3_of_simple_remove,
|
||||
.remove = dwc3_of_simple_remove,
|
||||
.shutdown = dwc3_of_simple_shutdown,
|
||||
.driver = {
|
||||
.name = "dwc3-of-simple",
|
||||
|
@ -416,7 +416,7 @@ static int dwc3_omap_extcon_register(struct dwc3_omap *omap)
|
||||
struct device_node *node = omap->dev->of_node;
|
||||
struct extcon_dev *edev;
|
||||
|
||||
if (of_property_read_bool(node, "extcon")) {
|
||||
if (of_property_present(node, "extcon")) {
|
||||
edev = extcon_get_edev_by_phandle(omap->dev, 0);
|
||||
if (IS_ERR(edev)) {
|
||||
dev_vdbg(omap->dev, "couldn't get extcon device\n");
|
||||
@ -611,7 +611,7 @@ static const struct dev_pm_ops dwc3_omap_dev_pm_ops = {
|
||||
|
||||
static struct platform_driver dwc3_omap_driver = {
|
||||
.probe = dwc3_omap_probe,
|
||||
.remove_new = dwc3_omap_remove,
|
||||
.remove = dwc3_omap_remove,
|
||||
.driver = {
|
||||
.name = "omap-dwc3",
|
||||
.of_match_table = of_dwc3_match,
|
||||
|
@ -161,7 +161,7 @@ static int dwc3_qcom_register_extcon(struct dwc3_qcom *qcom)
|
||||
struct extcon_dev *host_edev;
|
||||
int ret;
|
||||
|
||||
if (!of_property_read_bool(dev->of_node, "extcon"))
|
||||
if (!of_property_present(dev->of_node, "extcon"))
|
||||
return 0;
|
||||
|
||||
qcom->edev = extcon_get_edev_by_phandle(dev, 0);
|
||||
@ -921,7 +921,7 @@ MODULE_DEVICE_TABLE(of, dwc3_qcom_of_match);
|
||||
|
||||
static struct platform_driver dwc3_qcom_driver = {
|
||||
.probe = dwc3_qcom_probe,
|
||||
.remove_new = dwc3_qcom_remove,
|
||||
.remove = dwc3_qcom_remove,
|
||||
.driver = {
|
||||
.name = "dwc3-qcom",
|
||||
.pm = &dwc3_qcom_dev_pm_ops,
|
||||
|
@ -441,7 +441,7 @@ static const struct dev_pm_ops dwc3_rtk_dev_pm_ops = {
|
||||
|
||||
static struct platform_driver dwc3_rtk_driver = {
|
||||
.probe = dwc3_rtk_probe,
|
||||
.remove_new = dwc3_rtk_remove,
|
||||
.remove = dwc3_rtk_remove,
|
||||
.driver = {
|
||||
.name = "rtk-dwc3",
|
||||
.of_match_table = rtk_dwc3_match,
|
||||
|
@ -356,7 +356,7 @@ MODULE_DEVICE_TABLE(of, st_dwc3_match);
|
||||
|
||||
static struct platform_driver st_dwc3_driver = {
|
||||
.probe = st_dwc3_probe,
|
||||
.remove_new = st_dwc3_remove,
|
||||
.remove = st_dwc3_remove,
|
||||
.driver = {
|
||||
.name = "usb-st-dwc3",
|
||||
.of_match_table = st_dwc3_match,
|
||||
|
@ -420,7 +420,7 @@ static const struct dev_pm_ops dwc3_xlnx_dev_pm_ops = {
|
||||
|
||||
static struct platform_driver dwc3_xlnx_driver = {
|
||||
.probe = dwc3_xlnx_probe,
|
||||
.remove_new = dwc3_xlnx_remove,
|
||||
.remove = dwc3_xlnx_remove,
|
||||
.driver = {
|
||||
.name = "dwc3-xilinx",
|
||||
.of_match_table = dwc3_xlnx_of_match,
|
||||
|
@ -145,7 +145,7 @@ static int __dwc3_gadget_ep0_queue(struct dwc3_ep *dep,
|
||||
* Unfortunately we have uncovered a limitation wrt the Data Phase.
|
||||
*
|
||||
* Section 9.4 says we can wait for the XferNotReady(DATA) event to
|
||||
* come before issueing Start Transfer command, but if we do, we will
|
||||
* come before issuing Start Transfer command, but if we do, we will
|
||||
* miss situations where the host starts another SETUP phase instead of
|
||||
* the DATA phase. Such cases happen at least on TD.7.6 of the Link
|
||||
* Layer Compliance Suite.
|
||||
@ -232,7 +232,7 @@ void dwc3_ep0_stall_and_restart(struct dwc3 *dwc)
|
||||
/* stall is always issued on EP0 */
|
||||
dep = dwc->eps[0];
|
||||
__dwc3_gadget_ep_set_halt(dep, 1, false);
|
||||
dep->flags &= DWC3_EP_RESOURCE_ALLOCATED;
|
||||
dep->flags &= DWC3_EP_RESOURCE_ALLOCATED | DWC3_EP_TRANSFER_STARTED;
|
||||
dep->flags |= DWC3_EP_ENABLED;
|
||||
dwc->delayed_status = false;
|
||||
|
||||
|
@ -197,7 +197,6 @@ static void dwc3_gadget_del_and_unmap_request(struct dwc3_ep *dep,
|
||||
|
||||
list_del(&req->list);
|
||||
req->remaining = 0;
|
||||
req->needs_extra_trb = false;
|
||||
req->num_trbs = 0;
|
||||
|
||||
if (req->request.status == -EINPROGRESS)
|
||||
@ -687,6 +686,44 @@ static int dwc3_gadget_calc_tx_fifo_size(struct dwc3 *dwc, int mult)
|
||||
return fifo_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* dwc3_gadget_calc_ram_depth - calculates the ram depth for txfifo
|
||||
* @dwc: pointer to the DWC3 context
|
||||
*/
|
||||
static int dwc3_gadget_calc_ram_depth(struct dwc3 *dwc)
|
||||
{
|
||||
int ram_depth;
|
||||
int fifo_0_start;
|
||||
bool is_single_port_ram;
|
||||
|
||||
/* Check supporting RAM type by HW */
|
||||
is_single_port_ram = DWC3_SPRAM_TYPE(dwc->hwparams.hwparams1);
|
||||
|
||||
/*
|
||||
* If a single port RAM is utilized, then allocate TxFIFOs from
|
||||
* RAM0. otherwise, allocate them from RAM1.
|
||||
*/
|
||||
ram_depth = is_single_port_ram ? DWC3_RAM0_DEPTH(dwc->hwparams.hwparams6) :
|
||||
DWC3_RAM1_DEPTH(dwc->hwparams.hwparams7);
|
||||
|
||||
/*
|
||||
* In a single port RAM configuration, the available RAM is shared
|
||||
* between the RX and TX FIFOs. This means that the txfifo can begin
|
||||
* at a non-zero address.
|
||||
*/
|
||||
if (is_single_port_ram) {
|
||||
u32 reg;
|
||||
|
||||
/* Check if TXFIFOs start at non-zero addr */
|
||||
reg = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(0));
|
||||
fifo_0_start = DWC3_GTXFIFOSIZ_TXFSTADDR(reg);
|
||||
|
||||
ram_depth -= (fifo_0_start >> 16);
|
||||
}
|
||||
|
||||
return ram_depth;
|
||||
}
|
||||
|
||||
/**
|
||||
* dwc3_gadget_clear_tx_fifos - Clears txfifo allocation
|
||||
* @dwc: pointer to the DWC3 context
|
||||
@ -753,7 +790,7 @@ static int dwc3_gadget_resize_tx_fifos(struct dwc3_ep *dep)
|
||||
{
|
||||
struct dwc3 *dwc = dep->dwc;
|
||||
int fifo_0_start;
|
||||
int ram1_depth;
|
||||
int ram_depth;
|
||||
int fifo_size;
|
||||
int min_depth;
|
||||
int num_in_ep;
|
||||
@ -773,17 +810,32 @@ static int dwc3_gadget_resize_tx_fifos(struct dwc3_ep *dep)
|
||||
if (dep->flags & DWC3_EP_TXFIFO_RESIZED)
|
||||
return 0;
|
||||
|
||||
ram1_depth = DWC3_RAM1_DEPTH(dwc->hwparams.hwparams7);
|
||||
ram_depth = dwc3_gadget_calc_ram_depth(dwc);
|
||||
|
||||
if ((dep->endpoint.maxburst > 1 &&
|
||||
usb_endpoint_xfer_bulk(dep->endpoint.desc)) ||
|
||||
usb_endpoint_xfer_isoc(dep->endpoint.desc))
|
||||
num_fifos = 3;
|
||||
|
||||
if (dep->endpoint.maxburst > 6 &&
|
||||
(usb_endpoint_xfer_bulk(dep->endpoint.desc) ||
|
||||
usb_endpoint_xfer_isoc(dep->endpoint.desc)) && DWC3_IP_IS(DWC31))
|
||||
num_fifos = dwc->tx_fifo_resize_max_num;
|
||||
switch (dwc->gadget->speed) {
|
||||
case USB_SPEED_SUPER_PLUS:
|
||||
case USB_SPEED_SUPER:
|
||||
if (usb_endpoint_xfer_bulk(dep->endpoint.desc) ||
|
||||
usb_endpoint_xfer_isoc(dep->endpoint.desc))
|
||||
num_fifos = min_t(unsigned int,
|
||||
dep->endpoint.maxburst,
|
||||
dwc->tx_fifo_resize_max_num);
|
||||
break;
|
||||
case USB_SPEED_HIGH:
|
||||
if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) {
|
||||
num_fifos = min_t(unsigned int,
|
||||
usb_endpoint_maxp_mult(dep->endpoint.desc) + 1,
|
||||
dwc->tx_fifo_resize_max_num);
|
||||
break;
|
||||
}
|
||||
fallthrough;
|
||||
case USB_SPEED_FULL:
|
||||
if (usb_endpoint_xfer_bulk(dep->endpoint.desc))
|
||||
num_fifos = 2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* FIFO size for a single buffer */
|
||||
fifo = dwc3_gadget_calc_tx_fifo_size(dwc, 1);
|
||||
@ -794,7 +846,7 @@ static int dwc3_gadget_resize_tx_fifos(struct dwc3_ep *dep)
|
||||
|
||||
/* Reserve at least one FIFO for the number of IN EPs */
|
||||
min_depth = num_in_ep * (fifo + 1);
|
||||
remaining = ram1_depth - min_depth - dwc->last_fifo_depth;
|
||||
remaining = ram_depth - min_depth - dwc->last_fifo_depth;
|
||||
remaining = max_t(int, 0, remaining);
|
||||
/*
|
||||
* We've already reserved 1 FIFO per EP, so check what we can fit in
|
||||
@ -820,9 +872,9 @@ static int dwc3_gadget_resize_tx_fifos(struct dwc3_ep *dep)
|
||||
dwc->last_fifo_depth += DWC31_GTXFIFOSIZ_TXFDEP(fifo_size);
|
||||
|
||||
/* Check fifo size allocation doesn't exceed available RAM size. */
|
||||
if (dwc->last_fifo_depth >= ram1_depth) {
|
||||
if (dwc->last_fifo_depth >= ram_depth) {
|
||||
dev_err(dwc->dev, "Fifosize(%d) > RAM size(%d) %s depth:%d\n",
|
||||
dwc->last_fifo_depth, ram1_depth,
|
||||
dwc->last_fifo_depth, ram_depth,
|
||||
dep->endpoint.name, fifo_size);
|
||||
if (DWC3_IP_IS(DWC3))
|
||||
fifo_size = DWC3_GTXFIFOSIZ_TXFDEP(fifo_size);
|
||||
@ -1177,11 +1229,14 @@ static u32 dwc3_calc_trbs_left(struct dwc3_ep *dep)
|
||||
* pending to be processed by the driver.
|
||||
*/
|
||||
if (dep->trb_enqueue == dep->trb_dequeue) {
|
||||
struct dwc3_request *req;
|
||||
|
||||
/*
|
||||
* If there is any request remained in the started_list at
|
||||
* this point, that means there is no TRB available.
|
||||
* If there is any request remained in the started_list with
|
||||
* active TRBs at this point, then there is no TRB available.
|
||||
*/
|
||||
if (!list_empty(&dep->started_list))
|
||||
req = next_request(&dep->started_list);
|
||||
if (req && req->num_trbs)
|
||||
return 0;
|
||||
|
||||
return DWC3_TRB_NUM - 1;
|
||||
@ -1384,6 +1439,7 @@ static int dwc3_prepare_last_sg(struct dwc3_ep *dep,
|
||||
unsigned int maxp = usb_endpoint_maxp(dep->endpoint.desc);
|
||||
unsigned int rem = req->request.length % maxp;
|
||||
unsigned int num_trbs = 1;
|
||||
bool needs_extra_trb;
|
||||
|
||||
if (dwc3_needs_extra_trb(dep, req))
|
||||
num_trbs++;
|
||||
@ -1391,15 +1447,15 @@ static int dwc3_prepare_last_sg(struct dwc3_ep *dep,
|
||||
if (dwc3_calc_trbs_left(dep) < num_trbs)
|
||||
return 0;
|
||||
|
||||
req->needs_extra_trb = num_trbs > 1;
|
||||
needs_extra_trb = num_trbs > 1;
|
||||
|
||||
/* Prepare a normal TRB */
|
||||
if (req->direction || req->request.length)
|
||||
dwc3_prepare_one_trb(dep, req, entry_length,
|
||||
req->needs_extra_trb, node, false, false);
|
||||
needs_extra_trb, node, false, false);
|
||||
|
||||
/* Prepare extra TRBs for ZLP and MPS OUT transfer alignment */
|
||||
if ((!req->direction && !req->request.length) || req->needs_extra_trb)
|
||||
if ((!req->direction && !req->request.length) || needs_extra_trb)
|
||||
dwc3_prepare_one_trb(dep, req,
|
||||
req->direction ? 0 : maxp - rem,
|
||||
false, 1, true, false);
|
||||
@ -1414,8 +1470,8 @@ static int dwc3_prepare_trbs_sg(struct dwc3_ep *dep,
|
||||
struct scatterlist *s;
|
||||
int i;
|
||||
unsigned int length = req->request.length;
|
||||
unsigned int remaining = req->request.num_mapped_sgs
|
||||
- req->num_queued_sgs;
|
||||
unsigned int remaining = req->num_pending_sgs;
|
||||
unsigned int num_queued_sgs = req->request.num_mapped_sgs - remaining;
|
||||
unsigned int num_trbs = req->num_trbs;
|
||||
bool needs_extra_trb = dwc3_needs_extra_trb(dep, req);
|
||||
|
||||
@ -1423,7 +1479,7 @@ static int dwc3_prepare_trbs_sg(struct dwc3_ep *dep,
|
||||
* If we resume preparing the request, then get the remaining length of
|
||||
* the request and resume where we left off.
|
||||
*/
|
||||
for_each_sg(req->request.sg, s, req->num_queued_sgs, i)
|
||||
for_each_sg(req->request.sg, s, num_queued_sgs, i)
|
||||
length -= sg_dma_len(s);
|
||||
|
||||
for_each_sg(sg, s, remaining, i) {
|
||||
@ -1488,7 +1544,6 @@ static int dwc3_prepare_trbs_sg(struct dwc3_ep *dep,
|
||||
if (!last_sg)
|
||||
req->start_sg = sg_next(s);
|
||||
|
||||
req->num_queued_sgs++;
|
||||
req->num_pending_sgs--;
|
||||
|
||||
/*
|
||||
@ -1569,9 +1624,7 @@ static int dwc3_prepare_trbs(struct dwc3_ep *dep)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
req->sg = req->request.sg;
|
||||
req->start_sg = req->sg;
|
||||
req->num_queued_sgs = 0;
|
||||
req->start_sg = req->request.sg;
|
||||
req->num_pending_sgs = req->request.num_mapped_sgs;
|
||||
|
||||
if (req->num_pending_sgs > 0) {
|
||||
@ -3075,7 +3128,7 @@ static int dwc3_gadget_check_config(struct usb_gadget *g)
|
||||
struct dwc3 *dwc = gadget_to_dwc(g);
|
||||
struct usb_ep *ep;
|
||||
int fifo_size = 0;
|
||||
int ram1_depth;
|
||||
int ram_depth;
|
||||
int ep_num = 0;
|
||||
|
||||
if (!dwc->do_fifo_resize)
|
||||
@ -3098,8 +3151,8 @@ static int dwc3_gadget_check_config(struct usb_gadget *g)
|
||||
fifo_size += dwc->max_cfg_eps;
|
||||
|
||||
/* Check if we can fit a single fifo per endpoint */
|
||||
ram1_depth = DWC3_RAM1_DEPTH(dwc->hwparams.hwparams7);
|
||||
if (fifo_size > ram1_depth)
|
||||
ram_depth = dwc3_gadget_calc_ram_depth(dwc);
|
||||
if (fifo_size > ram_depth)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
@ -3416,20 +3469,16 @@ static int dwc3_gadget_ep_reclaim_trb_sg(struct dwc3_ep *dep,
|
||||
int status)
|
||||
{
|
||||
struct dwc3_trb *trb;
|
||||
struct scatterlist *sg = req->sg;
|
||||
struct scatterlist *s;
|
||||
unsigned int num_queued = req->num_queued_sgs;
|
||||
unsigned int num_completed_trbs = req->num_trbs;
|
||||
unsigned int i;
|
||||
int ret = 0;
|
||||
|
||||
for_each_sg(sg, s, num_queued, i) {
|
||||
for (i = 0; i < num_completed_trbs; i++) {
|
||||
trb = &dep->trb_pool[dep->trb_dequeue];
|
||||
|
||||
req->sg = sg_next(s);
|
||||
req->num_queued_sgs--;
|
||||
|
||||
ret = dwc3_gadget_ep_reclaim_completed_trb(dep, req,
|
||||
trb, event, status, true);
|
||||
trb, event, status,
|
||||
!!(trb->ctrl & DWC3_TRB_CTRL_CHN));
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
@ -3437,19 +3486,9 @@ static int dwc3_gadget_ep_reclaim_trb_sg(struct dwc3_ep *dep,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dwc3_gadget_ep_reclaim_trb_linear(struct dwc3_ep *dep,
|
||||
struct dwc3_request *req, const struct dwc3_event_depevt *event,
|
||||
int status)
|
||||
{
|
||||
struct dwc3_trb *trb = &dep->trb_pool[dep->trb_dequeue];
|
||||
|
||||
return dwc3_gadget_ep_reclaim_completed_trb(dep, req, trb,
|
||||
event, status, false);
|
||||
}
|
||||
|
||||
static bool dwc3_gadget_ep_request_completed(struct dwc3_request *req)
|
||||
{
|
||||
return req->num_pending_sgs == 0 && req->num_queued_sgs == 0;
|
||||
return req->num_pending_sgs == 0 && req->num_trbs == 0;
|
||||
}
|
||||
|
||||
static int dwc3_gadget_ep_cleanup_completed_request(struct dwc3_ep *dep,
|
||||
@ -3459,24 +3498,13 @@ static int dwc3_gadget_ep_cleanup_completed_request(struct dwc3_ep *dep,
|
||||
int request_status;
|
||||
int ret;
|
||||
|
||||
if (req->request.num_mapped_sgs)
|
||||
ret = dwc3_gadget_ep_reclaim_trb_sg(dep, req, event,
|
||||
status);
|
||||
else
|
||||
ret = dwc3_gadget_ep_reclaim_trb_linear(dep, req, event,
|
||||
status);
|
||||
ret = dwc3_gadget_ep_reclaim_trb_sg(dep, req, event, status);
|
||||
|
||||
req->request.actual = req->request.length - req->remaining;
|
||||
|
||||
if (!dwc3_gadget_ep_request_completed(req))
|
||||
goto out;
|
||||
|
||||
if (req->needs_extra_trb) {
|
||||
ret = dwc3_gadget_ep_reclaim_trb_linear(dep, req, event,
|
||||
status);
|
||||
req->needs_extra_trb = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* The event status only reflects the status of the TRB with IOC set.
|
||||
* For the requests that don't set interrupt on completion, the driver
|
||||
|
@ -35,7 +35,7 @@ static void dwc3_power_off_all_roothub_ports(struct dwc3 *dwc)
|
||||
u32 reg;
|
||||
int i;
|
||||
|
||||
/* xhci regs is not mapped yet, do it temperary here */
|
||||
/* xhci regs are not mapped yet, do it temporarily here */
|
||||
if (dwc->xhci_resources[0].start) {
|
||||
xhci_regs = ioremap(dwc->xhci_resources[0].start, DWC3_XHCI_REGS_END);
|
||||
if (!xhci_regs) {
|
||||
|
@ -195,7 +195,7 @@ static struct platform_driver fotg210_driver = {
|
||||
.of_match_table = of_match_ptr(fotg210_of_match),
|
||||
},
|
||||
.probe = fotg210_probe,
|
||||
.remove_new = fotg210_remove,
|
||||
.remove = fotg210_remove,
|
||||
};
|
||||
|
||||
static int __init fotg210_init(void)
|
||||
|
@ -1844,7 +1844,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
|
||||
cdev->desc.bcdUSB = cpu_to_le16(0x0200);
|
||||
}
|
||||
|
||||
value = min(w_length, (u16) sizeof cdev->desc);
|
||||
value = min_t(u16, w_length, sizeof(cdev->desc));
|
||||
memcpy(req->buf, &cdev->desc, value);
|
||||
break;
|
||||
case USB_DT_DEVICE_QUALIFIER:
|
||||
@ -1863,19 +1863,19 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
|
||||
case USB_DT_CONFIG:
|
||||
value = config_desc(cdev, w_value);
|
||||
if (value >= 0)
|
||||
value = min(w_length, (u16) value);
|
||||
value = min_t(u16, w_length, value);
|
||||
break;
|
||||
case USB_DT_STRING:
|
||||
value = get_string(cdev, req->buf,
|
||||
w_index, w_value & 0xff);
|
||||
if (value >= 0)
|
||||
value = min(w_length, (u16) value);
|
||||
value = min_t(u16, w_length, value);
|
||||
break;
|
||||
case USB_DT_BOS:
|
||||
if (gadget_is_superspeed(gadget) ||
|
||||
gadget->lpm_capable || cdev->use_webusb) {
|
||||
value = bos_desc(cdev);
|
||||
value = min(w_length, (u16) value);
|
||||
value = min_t(u16, w_length, value);
|
||||
}
|
||||
break;
|
||||
case USB_DT_OTG:
|
||||
@ -1930,7 +1930,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
|
||||
*(u8 *)req->buf = cdev->config->bConfigurationValue;
|
||||
else
|
||||
*(u8 *)req->buf = 0;
|
||||
value = min(w_length, (u16) 1);
|
||||
value = min_t(u16, w_length, 1);
|
||||
break;
|
||||
|
||||
/* function drivers must handle get/set altsetting */
|
||||
@ -1976,7 +1976,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
|
||||
if (value < 0)
|
||||
break;
|
||||
*((u8 *)req->buf) = value;
|
||||
value = min(w_length, (u16) 1);
|
||||
value = min_t(u16, w_length, 1);
|
||||
break;
|
||||
case USB_REQ_GET_STATUS:
|
||||
if (gadget_is_otg(gadget) && gadget->hnp_polling_support &&
|
||||
@ -2111,8 +2111,20 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
|
||||
memset(buf, 0, w_length);
|
||||
buf[5] = 0x01;
|
||||
switch (ctrl->bRequestType & USB_RECIP_MASK) {
|
||||
/*
|
||||
* The Microsoft CompatID OS Descriptor Spec(w_index = 0x4) and
|
||||
* Extended Prop OS Desc Spec(w_index = 0x5) state that the
|
||||
* HighByte of wValue is the InterfaceNumber and the LowByte is
|
||||
* the PageNumber. This high/low byte ordering is incorrectly
|
||||
* documented in the Spec. USB analyzer output on the below
|
||||
* request packets show the high/low byte inverted i.e LowByte
|
||||
* is the InterfaceNumber and the HighByte is the PageNumber.
|
||||
* Since we dont support >64KB CompatID/ExtendedProp descriptors,
|
||||
* PageNumber is set to 0. Hence verify that the HighByte is 0
|
||||
* for below two cases.
|
||||
*/
|
||||
case USB_RECIP_DEVICE:
|
||||
if (w_index != 0x4 || (w_value & 0xff))
|
||||
if (w_index != 0x4 || (w_value >> 8))
|
||||
break;
|
||||
buf[6] = w_index;
|
||||
/* Number of ext compat interfaces */
|
||||
@ -2128,9 +2140,9 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
|
||||
}
|
||||
break;
|
||||
case USB_RECIP_INTERFACE:
|
||||
if (w_index != 0x5 || (w_value & 0xff))
|
||||
if (w_index != 0x5 || (w_value >> 8))
|
||||
break;
|
||||
interface = w_value >> 8;
|
||||
interface = w_value & 0xFF;
|
||||
if (interface >= MAX_CONFIG_INTERFACES ||
|
||||
!os_desc_cfg->interface[interface])
|
||||
break;
|
||||
|
@ -57,11 +57,11 @@ EXPORT_SYMBOL_GPL(usb_descriptor_fillbuf);
|
||||
* usb_gadget_config_buf - builts a complete configuration descriptor
|
||||
* @config: Header for the descriptor, including characteristics such
|
||||
* as power requirements and number of interfaces.
|
||||
* @desc: Null-terminated vector of pointers to the descriptors (interface,
|
||||
* endpoint, etc) defining all functions in this device configuration.
|
||||
* @buf: Buffer for the resulting configuration descriptor.
|
||||
* @length: Length of buffer. If this is not big enough to hold the
|
||||
* entire configuration descriptor, an error code will be returned.
|
||||
* @desc: Null-terminated vector of pointers to the descriptors (interface,
|
||||
* endpoint, etc) defining all functions in this device configuration.
|
||||
*
|
||||
* This copies descriptors into the response buffer, building a descriptor
|
||||
* for that configuration. It returns the buffer length or a negative
|
||||
|
@ -1184,7 +1184,7 @@ static ssize_t os_desc_qw_sign_store(struct config_item *item, const char *page,
|
||||
struct gadget_info *gi = os_desc_item_to_gadget_info(item);
|
||||
int res, l;
|
||||
|
||||
l = min((int)len, OS_STRING_QW_SIGN_LEN >> 1);
|
||||
l = min_t(int, len, OS_STRING_QW_SIGN_LEN >> 1);
|
||||
if (page[l - 1] == '\n')
|
||||
--l;
|
||||
|
||||
|
@ -41,6 +41,10 @@ obj-$(CONFIG_USB_F_UAC1_LEGACY) += usb_f_uac1_legacy.o
|
||||
usb_f_uac2-y := f_uac2.o
|
||||
obj-$(CONFIG_USB_F_UAC2) += usb_f_uac2.o
|
||||
usb_f_uvc-y := f_uvc.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_configfs.o
|
||||
ifneq ($(CONFIG_TRACING),)
|
||||
CFLAGS_uvc_trace.o := -I$(src)
|
||||
usb_f_uvc-y += uvc_trace.o
|
||||
endif
|
||||
obj-$(CONFIG_USB_F_UVC) += usb_f_uvc.o
|
||||
usb_f_midi-y := f_midi.o
|
||||
obj-$(CONFIG_USB_F_MIDI) += usb_f_midi.o
|
||||
|
@ -456,7 +456,7 @@ static ssize_t ffs_ep0_write(struct file *file, const char __user *buf,
|
||||
}
|
||||
|
||||
/* FFS_SETUP_PENDING and not stall */
|
||||
len = min(len, (size_t)le16_to_cpu(ffs->ev.setup.wLength));
|
||||
len = min_t(size_t, len, le16_to_cpu(ffs->ev.setup.wLength));
|
||||
|
||||
spin_unlock_irq(&ffs->ev.waitq.lock);
|
||||
|
||||
@ -590,7 +590,7 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf,
|
||||
|
||||
/* unlocks spinlock */
|
||||
return __ffs_ep0_read_events(ffs, buf,
|
||||
min(n, (size_t)ffs->ev.count));
|
||||
min_t(size_t, n, ffs->ev.count));
|
||||
|
||||
case FFS_SETUP_PENDING:
|
||||
if (ffs->ev.setup.bRequestType & USB_DIR_IN) {
|
||||
@ -599,7 +599,7 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf,
|
||||
goto done_mutex;
|
||||
}
|
||||
|
||||
len = min(len, (size_t)le16_to_cpu(ffs->ev.setup.wLength));
|
||||
len = min_t(size_t, len, le16_to_cpu(ffs->ev.setup.wLength));
|
||||
|
||||
spin_unlock_irq(&ffs->ev.waitq.lock);
|
||||
|
||||
|
@ -500,7 +500,7 @@ static int fsg_setup(struct usb_function *f,
|
||||
*(u8 *)req->buf = _fsg_common_get_max_lun(fsg->common);
|
||||
|
||||
/* Respond with data/status */
|
||||
req->length = min((u16)1, w_length);
|
||||
req->length = min_t(u16, 1, w_length);
|
||||
return ep0_queue(fsg->common);
|
||||
}
|
||||
|
||||
@ -655,7 +655,7 @@ static int do_read(struct fsg_common *common)
|
||||
* And don't try to read past the end of the file.
|
||||
*/
|
||||
amount = min(amount_left, FSG_BUFLEN);
|
||||
amount = min((loff_t)amount,
|
||||
amount = min_t(loff_t, amount,
|
||||
curlun->file_length - file_offset);
|
||||
|
||||
/* Wait for the next buffer to become available */
|
||||
@ -1005,7 +1005,7 @@ static int do_verify(struct fsg_common *common)
|
||||
* And don't try to read past the end of the file.
|
||||
*/
|
||||
amount = min(amount_left, FSG_BUFLEN);
|
||||
amount = min((loff_t)amount,
|
||||
amount = min_t(loff_t, amount,
|
||||
curlun->file_length - file_offset);
|
||||
if (amount == 0) {
|
||||
curlun->sense_data =
|
||||
@ -2167,7 +2167,7 @@ static int do_scsi_command(struct fsg_common *common)
|
||||
if (reply == -EINVAL)
|
||||
reply = 0; /* Error reply length */
|
||||
if (reply >= 0 && common->data_dir == DATA_DIR_TO_HOST) {
|
||||
reply = min((u32)reply, common->data_size_from_cmnd);
|
||||
reply = min_t(u32, reply, common->data_size_from_cmnd);
|
||||
bh->inreq->length = reply;
|
||||
bh->state = BUF_STATE_FULL;
|
||||
common->residue -= reply;
|
||||
|
@ -819,9 +819,9 @@ static int f_midi_register_card(struct f_midi *midi)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
strcpy(card->driver, f_midi_longname);
|
||||
strcpy(card->longname, f_midi_longname);
|
||||
strcpy(card->shortname, f_midi_shortname);
|
||||
strscpy(card->driver, f_midi_longname);
|
||||
strscpy(card->longname, f_midi_longname);
|
||||
strscpy(card->shortname, f_midi_shortname);
|
||||
|
||||
/* Set up rawmidi */
|
||||
snd_component_add(card, "MIDI");
|
||||
@ -833,7 +833,7 @@ static int f_midi_register_card(struct f_midi *midi)
|
||||
}
|
||||
midi->rmidi = rmidi;
|
||||
midi->in_last_port = 0;
|
||||
strcpy(rmidi->name, card->shortname);
|
||||
strscpy(rmidi->name, card->shortname);
|
||||
rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
|
||||
SNDRV_RAWMIDI_INFO_INPUT |
|
||||
SNDRV_RAWMIDI_INFO_DUPLEX;
|
||||
|
@ -1285,10 +1285,8 @@ static int f_midi2_set_alt(struct usb_function *fn, unsigned int intf,
|
||||
|
||||
if (alt == 0)
|
||||
op_mode = MIDI_OP_MODE_MIDI1;
|
||||
else if (alt == 1)
|
||||
op_mode = MIDI_OP_MODE_MIDI2;
|
||||
else
|
||||
op_mode = MIDI_OP_MODE_UNSET;
|
||||
op_mode = MIDI_OP_MODE_MIDI2;
|
||||
|
||||
if (midi2->operation_mode == op_mode)
|
||||
return 0;
|
||||
|
@ -465,7 +465,7 @@ uvc_register_video(struct uvc_device *uvc)
|
||||
memcpy(mem, desc, (desc)->bLength); \
|
||||
*(dst)++ = mem; \
|
||||
mem += (desc)->bLength; \
|
||||
} while (0);
|
||||
} while (0)
|
||||
|
||||
#define UVC_COPY_DESCRIPTORS(mem, dst, src) \
|
||||
do { \
|
||||
@ -991,6 +991,8 @@ static void uvc_function_unbind(struct usb_configuration *c,
|
||||
|
||||
uvcg_info(f, "%s()\n", __func__);
|
||||
|
||||
kthread_cancel_work_sync(&video->hw_submit);
|
||||
|
||||
if (video->async_wq)
|
||||
destroy_workqueue(video->async_wq);
|
||||
|
||||
|
@ -71,6 +71,11 @@ extern unsigned int uvc_gadget_trace_param;
|
||||
|
||||
#define UVCG_REQUEST_HEADER_LEN 12
|
||||
|
||||
#define UVCG_REQ_MAX_INT_COUNT 16
|
||||
#define UVCG_REQ_MAX_ZERO_COUNT (2 * UVCG_REQ_MAX_INT_COUNT)
|
||||
|
||||
#define UVCG_STREAMING_MIN_BUFFERS 2
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
* Structures
|
||||
*/
|
||||
@ -91,16 +96,24 @@ struct uvc_video {
|
||||
struct work_struct pump;
|
||||
struct workqueue_struct *async_wq;
|
||||
|
||||
struct kthread_worker *kworker;
|
||||
struct kthread_work hw_submit;
|
||||
|
||||
atomic_t queued;
|
||||
|
||||
/* Frame parameters */
|
||||
u8 bpp;
|
||||
u32 fcc;
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
unsigned int imagesize;
|
||||
unsigned int interval;
|
||||
struct mutex mutex; /* protects frame parameters */
|
||||
|
||||
unsigned int uvc_num_requests;
|
||||
|
||||
unsigned int reqs_per_frame;
|
||||
|
||||
/* Requests */
|
||||
bool is_enabled; /* tracks whether video stream is enabled */
|
||||
unsigned int req_size;
|
||||
|
@ -1566,11 +1566,13 @@ static const struct uvcg_config_group_type uvcg_control_grp_type = {
|
||||
/* -----------------------------------------------------------------------------
|
||||
* streaming/uncompressed
|
||||
* streaming/mjpeg
|
||||
* streaming/framebased
|
||||
*/
|
||||
|
||||
static const char * const uvcg_format_names[] = {
|
||||
"uncompressed",
|
||||
"mjpeg",
|
||||
"framebased",
|
||||
};
|
||||
|
||||
static struct uvcg_color_matching *
|
||||
@ -1777,6 +1779,9 @@ static int uvcg_streaming_header_allow_link(struct config_item *src,
|
||||
target_fmt = container_of(to_config_group(target), struct uvcg_format,
|
||||
group);
|
||||
|
||||
if (!target_fmt)
|
||||
goto out;
|
||||
|
||||
uvcg_format_set_indices(to_config_group(target));
|
||||
|
||||
format_ptr = kzalloc(sizeof(*format_ptr), GFP_KERNEL);
|
||||
@ -1816,6 +1821,9 @@ static void uvcg_streaming_header_drop_link(struct config_item *src,
|
||||
target_fmt = container_of(to_config_group(target), struct uvcg_format,
|
||||
group);
|
||||
|
||||
if (!target_fmt)
|
||||
goto out;
|
||||
|
||||
list_for_each_entry_safe(format_ptr, tmp, &src_hdr->formats, entry)
|
||||
if (format_ptr->fmt == target_fmt) {
|
||||
list_del(&format_ptr->entry);
|
||||
@ -1826,6 +1834,7 @@ static void uvcg_streaming_header_drop_link(struct config_item *src,
|
||||
|
||||
--target_fmt->linked;
|
||||
|
||||
out:
|
||||
mutex_unlock(&opts->lock);
|
||||
mutex_unlock(su_mutex);
|
||||
}
|
||||
@ -2022,6 +2031,7 @@ UVCG_FRAME_ATTR(dw_min_bit_rate, dwMinBitRate, 32);
|
||||
UVCG_FRAME_ATTR(dw_max_bit_rate, dwMaxBitRate, 32);
|
||||
UVCG_FRAME_ATTR(dw_max_video_frame_buffer_size, dwMaxVideoFrameBufferSize, 32);
|
||||
UVCG_FRAME_ATTR(dw_default_frame_interval, dwDefaultFrameInterval, 32);
|
||||
UVCG_FRAME_ATTR(dw_bytes_perline, dwBytesPerLine, 32);
|
||||
|
||||
#undef UVCG_FRAME_ATTR
|
||||
|
||||
@ -2035,7 +2045,7 @@ static ssize_t uvcg_frame_dw_frame_interval_show(struct config_item *item,
|
||||
int result, i;
|
||||
char *pg = page;
|
||||
|
||||
mutex_lock(su_mutex); /* for navigating configfs hierarchy */
|
||||
mutex_lock(su_mutex); /* for navigating configfs hierarchy */
|
||||
|
||||
opts_item = frm->item.ci_parent->ci_parent->ci_parent->ci_parent;
|
||||
opts = to_f_uvc_opts(opts_item);
|
||||
@ -2105,7 +2115,7 @@ static ssize_t uvcg_frame_dw_frame_interval_store(struct config_item *item,
|
||||
|
||||
UVC_ATTR(uvcg_frame_, dw_frame_interval, dwFrameInterval);
|
||||
|
||||
static struct configfs_attribute *uvcg_frame_attrs[] = {
|
||||
static struct configfs_attribute *uvcg_frame_attrs1[] = {
|
||||
&uvcg_frame_attr_b_frame_index,
|
||||
&uvcg_frame_attr_bm_capabilities,
|
||||
&uvcg_frame_attr_w_width,
|
||||
@ -2118,12 +2128,31 @@ static struct configfs_attribute *uvcg_frame_attrs[] = {
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct config_item_type uvcg_frame_type = {
|
||||
static struct configfs_attribute *uvcg_frame_attrs2[] = {
|
||||
&uvcg_frame_attr_b_frame_index,
|
||||
&uvcg_frame_attr_bm_capabilities,
|
||||
&uvcg_frame_attr_w_width,
|
||||
&uvcg_frame_attr_w_height,
|
||||
&uvcg_frame_attr_dw_min_bit_rate,
|
||||
&uvcg_frame_attr_dw_max_bit_rate,
|
||||
&uvcg_frame_attr_dw_default_frame_interval,
|
||||
&uvcg_frame_attr_dw_frame_interval,
|
||||
&uvcg_frame_attr_dw_bytes_perline,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct config_item_type uvcg_frame_type1 = {
|
||||
.ct_item_ops = &uvcg_config_item_ops,
|
||||
.ct_attrs = uvcg_frame_attrs,
|
||||
.ct_attrs = uvcg_frame_attrs1,
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static const struct config_item_type uvcg_frame_type2 = {
|
||||
.ct_item_ops = &uvcg_config_item_ops,
|
||||
.ct_attrs = uvcg_frame_attrs2,
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static struct config_item *uvcg_frame_make(struct config_group *group,
|
||||
const char *name)
|
||||
{
|
||||
@ -2145,6 +2174,7 @@ static struct config_item *uvcg_frame_make(struct config_group *group,
|
||||
h->frame.dw_max_bit_rate = 55296000;
|
||||
h->frame.dw_max_video_frame_buffer_size = 460800;
|
||||
h->frame.dw_default_frame_interval = 666666;
|
||||
h->frame.dw_bytes_perline = 0;
|
||||
|
||||
opts_item = group->cg_item.ci_parent->ci_parent->ci_parent;
|
||||
opts = to_f_uvc_opts(opts_item);
|
||||
@ -2157,6 +2187,9 @@ static struct config_item *uvcg_frame_make(struct config_group *group,
|
||||
} else if (fmt->type == UVCG_MJPEG) {
|
||||
h->frame.b_descriptor_subtype = UVC_VS_FRAME_MJPEG;
|
||||
h->fmt_type = UVCG_MJPEG;
|
||||
} else if (fmt->type == UVCG_FRAMEBASED) {
|
||||
h->frame.b_descriptor_subtype = UVC_VS_FRAME_FRAME_BASED;
|
||||
h->fmt_type = UVCG_FRAMEBASED;
|
||||
} else {
|
||||
mutex_unlock(&opts->lock);
|
||||
kfree(h);
|
||||
@ -2175,7 +2208,10 @@ static struct config_item *uvcg_frame_make(struct config_group *group,
|
||||
++fmt->num_frames;
|
||||
mutex_unlock(&opts->lock);
|
||||
|
||||
config_item_init_type_name(&h->item, name, &uvcg_frame_type);
|
||||
if (fmt->type == UVCG_FRAMEBASED)
|
||||
config_item_init_type_name(&h->item, name, &uvcg_frame_type2);
|
||||
else
|
||||
config_item_init_type_name(&h->item, name, &uvcg_frame_type1);
|
||||
|
||||
return &h->item;
|
||||
}
|
||||
@ -2215,9 +2251,6 @@ static void uvcg_format_set_indices(struct config_group *fmt)
|
||||
list_for_each_entry(ci, &fmt->cg_children, ci_entry) {
|
||||
struct uvcg_frame *frm;
|
||||
|
||||
if (ci->ci_type != &uvcg_frame_type)
|
||||
continue;
|
||||
|
||||
frm = to_uvcg_frame(ci);
|
||||
frm->frame.b_frame_index = i++;
|
||||
}
|
||||
@ -2677,6 +2710,251 @@ static const struct uvcg_config_group_type uvcg_mjpeg_grp_type = {
|
||||
.name = "mjpeg",
|
||||
};
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* streaming/framebased/<NAME>
|
||||
*/
|
||||
|
||||
static struct configfs_group_operations uvcg_framebased_group_ops = {
|
||||
.make_item = uvcg_frame_make,
|
||||
.drop_item = uvcg_frame_drop,
|
||||
};
|
||||
|
||||
#define UVCG_FRAMEBASED_ATTR_RO(cname, aname, bits) \
|
||||
static ssize_t uvcg_framebased_##cname##_show(struct config_item *item, \
|
||||
char *page) \
|
||||
{ \
|
||||
struct uvcg_framebased *u = to_uvcg_framebased(item); \
|
||||
struct f_uvc_opts *opts; \
|
||||
struct config_item *opts_item; \
|
||||
struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \
|
||||
int result; \
|
||||
\
|
||||
mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \
|
||||
\
|
||||
opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\
|
||||
opts = to_f_uvc_opts(opts_item); \
|
||||
\
|
||||
mutex_lock(&opts->lock); \
|
||||
result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\
|
||||
mutex_unlock(&opts->lock); \
|
||||
\
|
||||
mutex_unlock(su_mutex); \
|
||||
return result; \
|
||||
} \
|
||||
\
|
||||
UVC_ATTR_RO(uvcg_framebased_, cname, aname)
|
||||
|
||||
#define UVCG_FRAMEBASED_ATTR(cname, aname, bits) \
|
||||
static ssize_t uvcg_framebased_##cname##_show(struct config_item *item, \
|
||||
char *page) \
|
||||
{ \
|
||||
struct uvcg_framebased *u = to_uvcg_framebased(item); \
|
||||
struct f_uvc_opts *opts; \
|
||||
struct config_item *opts_item; \
|
||||
struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \
|
||||
int result; \
|
||||
\
|
||||
mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \
|
||||
\
|
||||
opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\
|
||||
opts = to_f_uvc_opts(opts_item); \
|
||||
\
|
||||
mutex_lock(&opts->lock); \
|
||||
result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\
|
||||
mutex_unlock(&opts->lock); \
|
||||
\
|
||||
mutex_unlock(su_mutex); \
|
||||
return result; \
|
||||
} \
|
||||
\
|
||||
static ssize_t \
|
||||
uvcg_framebased_##cname##_store(struct config_item *item, \
|
||||
const char *page, size_t len) \
|
||||
{ \
|
||||
struct uvcg_framebased *u = to_uvcg_framebased(item); \
|
||||
struct f_uvc_opts *opts; \
|
||||
struct config_item *opts_item; \
|
||||
struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \
|
||||
int ret; \
|
||||
u8 num; \
|
||||
\
|
||||
mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \
|
||||
\
|
||||
opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\
|
||||
opts = to_f_uvc_opts(opts_item); \
|
||||
\
|
||||
mutex_lock(&opts->lock); \
|
||||
if (u->fmt.linked || opts->refcnt) { \
|
||||
ret = -EBUSY; \
|
||||
goto end; \
|
||||
} \
|
||||
\
|
||||
ret = kstrtou8(page, 0, &num); \
|
||||
if (ret) \
|
||||
goto end; \
|
||||
\
|
||||
if (num > 255) { \
|
||||
ret = -EINVAL; \
|
||||
goto end; \
|
||||
} \
|
||||
u->desc.aname = num; \
|
||||
ret = len; \
|
||||
end: \
|
||||
mutex_unlock(&opts->lock); \
|
||||
mutex_unlock(su_mutex); \
|
||||
return ret; \
|
||||
} \
|
||||
\
|
||||
UVC_ATTR(uvcg_framebased_, cname, aname)
|
||||
|
||||
UVCG_FRAMEBASED_ATTR_RO(b_format_index, bFormatIndex, 8);
|
||||
UVCG_FRAMEBASED_ATTR_RO(b_bits_per_pixel, bBitsPerPixel, 8);
|
||||
UVCG_FRAMEBASED_ATTR(b_default_frame_index, bDefaultFrameIndex, 8);
|
||||
UVCG_FRAMEBASED_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, 8);
|
||||
UVCG_FRAMEBASED_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, 8);
|
||||
UVCG_FRAMEBASED_ATTR_RO(bm_interface_flags, bmInterfaceFlags, 8);
|
||||
|
||||
#undef UVCG_FRAMEBASED_ATTR
|
||||
#undef UVCG_FRAMEBASED_ATTR_RO
|
||||
|
||||
static ssize_t uvcg_framebased_guid_format_show(struct config_item *item,
|
||||
char *page)
|
||||
{
|
||||
struct uvcg_framebased *ch = to_uvcg_framebased(item);
|
||||
struct f_uvc_opts *opts;
|
||||
struct config_item *opts_item;
|
||||
struct mutex *su_mutex = &ch->fmt.group.cg_subsys->su_mutex;
|
||||
|
||||
mutex_lock(su_mutex); /* for navigating configfs hierarchy */
|
||||
|
||||
opts_item = ch->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;
|
||||
opts = to_f_uvc_opts(opts_item);
|
||||
|
||||
mutex_lock(&opts->lock);
|
||||
memcpy(page, ch->desc.guidFormat, sizeof(ch->desc.guidFormat));
|
||||
mutex_unlock(&opts->lock);
|
||||
|
||||
mutex_unlock(su_mutex);
|
||||
|
||||
return sizeof(ch->desc.guidFormat);
|
||||
}
|
||||
|
||||
static ssize_t uvcg_framebased_guid_format_store(struct config_item *item,
|
||||
const char *page, size_t len)
|
||||
{
|
||||
struct uvcg_framebased *ch = to_uvcg_framebased(item);
|
||||
struct f_uvc_opts *opts;
|
||||
struct config_item *opts_item;
|
||||
struct mutex *su_mutex = &ch->fmt.group.cg_subsys->su_mutex;
|
||||
int ret;
|
||||
|
||||
mutex_lock(su_mutex); /* for navigating configfs hierarchy */
|
||||
|
||||
opts_item = ch->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;
|
||||
opts = to_f_uvc_opts(opts_item);
|
||||
|
||||
mutex_lock(&opts->lock);
|
||||
if (ch->fmt.linked || opts->refcnt) {
|
||||
ret = -EBUSY;
|
||||
goto end;
|
||||
}
|
||||
|
||||
memcpy(ch->desc.guidFormat, page,
|
||||
min(sizeof(ch->desc.guidFormat), len));
|
||||
ret = sizeof(ch->desc.guidFormat);
|
||||
|
||||
end:
|
||||
mutex_unlock(&opts->lock);
|
||||
mutex_unlock(su_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
UVC_ATTR(uvcg_framebased_, guid_format, guidFormat);
|
||||
|
||||
static inline ssize_t
|
||||
uvcg_framebased_bma_controls_show(struct config_item *item, char *page)
|
||||
{
|
||||
struct uvcg_framebased *u = to_uvcg_framebased(item);
|
||||
|
||||
return uvcg_format_bma_controls_show(&u->fmt, page);
|
||||
}
|
||||
|
||||
static inline ssize_t
|
||||
uvcg_framebased_bma_controls_store(struct config_item *item,
|
||||
const char *page, size_t len)
|
||||
{
|
||||
struct uvcg_framebased *u = to_uvcg_framebased(item);
|
||||
|
||||
return uvcg_format_bma_controls_store(&u->fmt, page, len);
|
||||
}
|
||||
|
||||
UVC_ATTR(uvcg_framebased_, bma_controls, bmaControls);
|
||||
|
||||
static struct configfs_attribute *uvcg_framebased_attrs[] = {
|
||||
&uvcg_framebased_attr_b_format_index,
|
||||
&uvcg_framebased_attr_b_default_frame_index,
|
||||
&uvcg_framebased_attr_b_bits_per_pixel,
|
||||
&uvcg_framebased_attr_b_aspect_ratio_x,
|
||||
&uvcg_framebased_attr_b_aspect_ratio_y,
|
||||
&uvcg_framebased_attr_bm_interface_flags,
|
||||
&uvcg_framebased_attr_bma_controls,
|
||||
&uvcg_framebased_attr_guid_format,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct config_item_type uvcg_framebased_type = {
|
||||
.ct_item_ops = &uvcg_config_item_ops,
|
||||
.ct_group_ops = &uvcg_framebased_group_ops,
|
||||
.ct_attrs = uvcg_framebased_attrs,
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static struct config_group *uvcg_framebased_make(struct config_group *group,
|
||||
const char *name)
|
||||
{
|
||||
static char guid[] = { /*Declear frame based as H264 format*/
|
||||
'H', '2', '6', '4', 0x00, 0x00, 0x10, 0x00,
|
||||
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
|
||||
};
|
||||
struct uvcg_framebased *h;
|
||||
|
||||
h = kzalloc(sizeof(*h), GFP_KERNEL);
|
||||
if (!h)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
h->desc.bLength = UVC_DT_FORMAT_FRAMEBASED_SIZE;
|
||||
h->desc.bDescriptorType = USB_DT_CS_INTERFACE;
|
||||
h->desc.bDescriptorSubType = UVC_VS_FORMAT_FRAME_BASED;
|
||||
memcpy(h->desc.guidFormat, guid, sizeof(guid));
|
||||
h->desc.bBitsPerPixel = 0;
|
||||
h->desc.bDefaultFrameIndex = 1;
|
||||
h->desc.bAspectRatioX = 0;
|
||||
h->desc.bAspectRatioY = 0;
|
||||
h->desc.bmInterfaceFlags = 0;
|
||||
h->desc.bCopyProtect = 0;
|
||||
h->desc.bVariableSize = 1;
|
||||
|
||||
INIT_LIST_HEAD(&h->fmt.frames);
|
||||
h->fmt.type = UVCG_FRAMEBASED;
|
||||
config_group_init_type_name(&h->fmt.group, name,
|
||||
&uvcg_framebased_type);
|
||||
|
||||
return &h->fmt.group;
|
||||
}
|
||||
|
||||
static struct configfs_group_operations uvcg_framebased_grp_ops = {
|
||||
.make_group = uvcg_framebased_make,
|
||||
};
|
||||
|
||||
static const struct uvcg_config_group_type uvcg_framebased_grp_type = {
|
||||
.type = {
|
||||
.ct_item_ops = &uvcg_config_item_ops,
|
||||
.ct_group_ops = &uvcg_framebased_grp_ops,
|
||||
.ct_owner = THIS_MODULE,
|
||||
},
|
||||
.name = "framebased",
|
||||
};
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* streaming/color_matching/default
|
||||
*/
|
||||
@ -2912,6 +3190,7 @@ static int __uvcg_iter_strm_cls(struct uvcg_streaming_header *h,
|
||||
if (ret)
|
||||
return ret;
|
||||
grp = &f->fmt->group;
|
||||
j = 0;
|
||||
list_for_each_entry(item, &grp->cg_children, ci_entry) {
|
||||
frm = to_uvcg_frame(item);
|
||||
ret = fun(frm, priv2, priv3, j++, UVCG_FRAME);
|
||||
@ -2965,6 +3244,11 @@ static int __uvcg_cnt_strm(void *priv1, void *priv2, void *priv3, int n,
|
||||
container_of(fmt, struct uvcg_mjpeg, fmt);
|
||||
|
||||
*size += sizeof(m->desc);
|
||||
} else if (fmt->type == UVCG_FRAMEBASED) {
|
||||
struct uvcg_framebased *f =
|
||||
container_of(fmt, struct uvcg_framebased, fmt);
|
||||
|
||||
*size += sizeof(f->desc);
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
@ -2975,6 +3259,11 @@ static int __uvcg_cnt_strm(void *priv1, void *priv2, void *priv3, int n,
|
||||
int sz = sizeof(frm->dw_frame_interval);
|
||||
|
||||
*size += sizeof(frm->frame);
|
||||
/*
|
||||
* framebased has duplicate member with uncompressed and
|
||||
* mjpeg, so minus it
|
||||
*/
|
||||
*size -= sizeof(u32);
|
||||
*size += frm->frame.b_frame_interval_type * sz;
|
||||
}
|
||||
break;
|
||||
@ -2991,6 +3280,27 @@ static int __uvcg_cnt_strm(void *priv1, void *priv2, void *priv3, int n,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __uvcg_copy_framebased_desc(void *dest, struct uvcg_frame *frm,
|
||||
int sz)
|
||||
{
|
||||
struct uvc_frame_framebased *desc = dest;
|
||||
|
||||
desc->bLength = frm->frame.b_length;
|
||||
desc->bDescriptorType = frm->frame.b_descriptor_type;
|
||||
desc->bDescriptorSubType = frm->frame.b_descriptor_subtype;
|
||||
desc->bFrameIndex = frm->frame.b_frame_index;
|
||||
desc->bmCapabilities = frm->frame.bm_capabilities;
|
||||
desc->wWidth = frm->frame.w_width;
|
||||
desc->wHeight = frm->frame.w_height;
|
||||
desc->dwMinBitRate = frm->frame.dw_min_bit_rate;
|
||||
desc->dwMaxBitRate = frm->frame.dw_max_bit_rate;
|
||||
desc->dwDefaultFrameInterval = frm->frame.dw_default_frame_interval;
|
||||
desc->bFrameIntervalType = frm->frame.b_frame_interval_type;
|
||||
desc->dwBytesPerLine = frm->frame.dw_bytes_perline;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fill an array of streaming descriptors.
|
||||
*
|
||||
@ -3045,6 +3355,15 @@ static int __uvcg_fill_strm(void *priv1, void *priv2, void *priv3, int n,
|
||||
m->desc.bNumFrameDescriptors = fmt->num_frames;
|
||||
memcpy(*dest, &m->desc, sizeof(m->desc));
|
||||
*dest += sizeof(m->desc);
|
||||
} else if (fmt->type == UVCG_FRAMEBASED) {
|
||||
struct uvcg_framebased *f =
|
||||
container_of(fmt, struct uvcg_framebased,
|
||||
fmt);
|
||||
|
||||
f->desc.bFormatIndex = n + 1;
|
||||
f->desc.bNumFrameDescriptors = fmt->num_frames;
|
||||
memcpy(*dest, &f->desc, sizeof(f->desc));
|
||||
*dest += sizeof(f->desc);
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
@ -3054,8 +3373,11 @@ static int __uvcg_fill_strm(void *priv1, void *priv2, void *priv3, int n,
|
||||
struct uvcg_frame *frm = priv1;
|
||||
struct uvc_descriptor_header *h = *dest;
|
||||
|
||||
sz = sizeof(frm->frame);
|
||||
memcpy(*dest, &frm->frame, sz);
|
||||
sz = sizeof(frm->frame) - 4;
|
||||
if (frm->fmt_type != UVCG_FRAMEBASED)
|
||||
memcpy(*dest, &frm->frame, sz);
|
||||
else
|
||||
__uvcg_copy_framebased_desc(*dest, frm, sz);
|
||||
*dest += sz;
|
||||
sz = frm->frame.b_frame_interval_type *
|
||||
sizeof(*frm->dw_frame_interval);
|
||||
@ -3066,7 +3388,10 @@ static int __uvcg_fill_strm(void *priv1, void *priv2, void *priv3, int n,
|
||||
frm->frame.b_frame_interval_type);
|
||||
else if (frm->fmt_type == UVCG_MJPEG)
|
||||
h->bLength = UVC_DT_FRAME_MJPEG_SIZE(
|
||||
frm->frame.b_frame_interval_type);
|
||||
frm->frame.b_frame_interval_type);
|
||||
else if (frm->fmt_type == UVCG_FRAMEBASED)
|
||||
h->bLength = UVC_DT_FRAME_FRAMEBASED_SIZE(
|
||||
frm->frame.b_frame_interval_type);
|
||||
}
|
||||
break;
|
||||
case UVCG_COLOR_MATCHING: {
|
||||
@ -3285,6 +3610,7 @@ static const struct uvcg_config_group_type uvcg_streaming_grp_type = {
|
||||
&uvcg_streaming_header_grp_type,
|
||||
&uvcg_uncompressed_grp_type,
|
||||
&uvcg_mjpeg_grp_type,
|
||||
&uvcg_framebased_grp_type,
|
||||
&uvcg_color_matching_grp_type,
|
||||
&uvcg_streaming_class_grp_type,
|
||||
NULL,
|
||||
|
@ -49,6 +49,7 @@ container_of(group_ptr, struct uvcg_color_matching, group)
|
||||
enum uvcg_format_type {
|
||||
UVCG_UNCOMPRESSED = 0,
|
||||
UVCG_MJPEG,
|
||||
UVCG_FRAMEBASED,
|
||||
};
|
||||
|
||||
struct uvcg_format {
|
||||
@ -105,6 +106,7 @@ struct uvcg_frame {
|
||||
u32 dw_max_video_frame_buffer_size;
|
||||
u32 dw_default_frame_interval;
|
||||
u8 b_frame_interval_type;
|
||||
u32 dw_bytes_perline;
|
||||
} __attribute__((packed)) frame;
|
||||
u32 *dw_frame_interval;
|
||||
};
|
||||
@ -142,6 +144,20 @@ static inline struct uvcg_mjpeg *to_uvcg_mjpeg(struct config_item *item)
|
||||
return container_of(to_uvcg_format(item), struct uvcg_mjpeg, fmt);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* streaming/framebased/<NAME>
|
||||
*/
|
||||
|
||||
struct uvcg_framebased {
|
||||
struct uvcg_format fmt;
|
||||
struct uvc_format_framebased desc;
|
||||
};
|
||||
|
||||
static inline struct uvcg_framebased *to_uvcg_framebased(struct config_item *item)
|
||||
{
|
||||
return container_of(to_uvcg_format(item), struct uvcg_framebased, fmt);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* control/extensions/<NAME>
|
||||
*/
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <media/videobuf2-vmalloc.h>
|
||||
|
||||
#include "uvc.h"
|
||||
#include "uvc_video.h"
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
* Video buffers queue management.
|
||||
@ -44,33 +45,23 @@ static int uvc_queue_setup(struct vb2_queue *vq,
|
||||
{
|
||||
struct uvc_video_queue *queue = vb2_get_drv_priv(vq);
|
||||
struct uvc_video *video = container_of(queue, struct uvc_video, queue);
|
||||
unsigned int req_size;
|
||||
unsigned int nreq;
|
||||
|
||||
if (*nbuffers > UVC_MAX_VIDEO_BUFFERS)
|
||||
*nbuffers = UVC_MAX_VIDEO_BUFFERS;
|
||||
if (*nbuffers < UVCG_STREAMING_MIN_BUFFERS)
|
||||
*nbuffers = UVCG_STREAMING_MIN_BUFFERS;
|
||||
|
||||
*nplanes = 1;
|
||||
|
||||
sizes[0] = video->imagesize;
|
||||
|
||||
req_size = video->ep->maxpacket
|
||||
* max_t(unsigned int, video->ep->maxburst, 1)
|
||||
* (video->ep->mult);
|
||||
|
||||
/* We divide by two, to increase the chance to run
|
||||
* into fewer requests for smaller framesizes.
|
||||
*/
|
||||
nreq = DIV_ROUND_UP(DIV_ROUND_UP(sizes[0], 2), req_size);
|
||||
nreq = clamp(nreq, 4U, 64U);
|
||||
video->uvc_num_requests = nreq;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uvc_buffer_prepare(struct vb2_buffer *vb)
|
||||
{
|
||||
struct uvc_video_queue *queue = vb2_get_drv_priv(vb->vb2_queue);
|
||||
struct uvc_video *video = container_of(queue, struct uvc_video, queue);
|
||||
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
|
||||
struct uvc_buffer *buf = container_of(vbuf, struct uvc_buffer, buf);
|
||||
|
||||
@ -91,10 +82,15 @@ static int uvc_buffer_prepare(struct vb2_buffer *vb)
|
||||
buf->mem = vb2_plane_vaddr(vb, 0);
|
||||
}
|
||||
buf->length = vb2_plane_size(vb, 0);
|
||||
if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
||||
if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
|
||||
buf->bytesused = 0;
|
||||
else
|
||||
} else {
|
||||
buf->bytesused = vb2_get_plane_payload(vb, 0);
|
||||
buf->req_payload_size =
|
||||
DIV_ROUND_UP(buf->bytesused +
|
||||
(video->reqs_per_frame * UVCG_REQUEST_HEADER_LEN),
|
||||
video->reqs_per_frame);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -39,6 +39,8 @@ struct uvc_buffer {
|
||||
unsigned int offset;
|
||||
unsigned int length;
|
||||
unsigned int bytesused;
|
||||
/* req_payload_size: only used with isoc */
|
||||
unsigned int req_payload_size;
|
||||
};
|
||||
|
||||
#define UVC_QUEUE_DISCONNECTED (1 << 0)
|
||||
|
11
drivers/usb/gadget/function/uvc_trace.c
Normal file
11
drivers/usb/gadget/function/uvc_trace.c
Normal file
@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* trace.c - USB UVC Gadget Trace Support
|
||||
*
|
||||
* Copyright (C) 2024 Pengutronix e.K.
|
||||
*
|
||||
* Author: Michael Grzeschik <m.grzeschik@pengutronix.de>
|
||||
*/
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include "uvc_trace.h"
|
60
drivers/usb/gadget/function/uvc_trace.h
Normal file
60
drivers/usb/gadget/function/uvc_trace.h
Normal file
@ -0,0 +1,60 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* trace.h - USB UVC Gadget Trace Support
|
||||
*
|
||||
* Copyright (C) 2024 Pengutronix e.K.
|
||||
*
|
||||
* Author: Michael Grzeschik <m.grzeschik@pengutronix.de>
|
||||
*/
|
||||
|
||||
#undef TRACE_SYSTEM
|
||||
#define TRACE_SYSTEM uvcg
|
||||
|
||||
#if !defined(__UVCG_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
|
||||
#define __UVCG_TRACE_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/tracepoint.h>
|
||||
#include <linux/usb/gadget.h>
|
||||
#include <asm/byteorder.h>
|
||||
|
||||
DECLARE_EVENT_CLASS(uvcg_video_req,
|
||||
TP_PROTO(struct usb_request *req, u32 queued),
|
||||
TP_ARGS(req, queued),
|
||||
TP_STRUCT__entry(
|
||||
__field(struct usb_request *, req)
|
||||
__field(u32, length)
|
||||
__field(u32, queued)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->req = req;
|
||||
__entry->length = req->length;
|
||||
__entry->queued = queued;
|
||||
),
|
||||
TP_printk("req %p length %u queued %u",
|
||||
__entry->req,
|
||||
__entry->length,
|
||||
__entry->queued)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(uvcg_video_req, uvcg_video_complete,
|
||||
TP_PROTO(struct usb_request *req, u32 queued),
|
||||
TP_ARGS(req, queued)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(uvcg_video_req, uvcg_video_queue,
|
||||
TP_PROTO(struct usb_request *req, u32 queued),
|
||||
TP_ARGS(req, queued)
|
||||
);
|
||||
|
||||
#endif /* __UVCG_TRACE_H */
|
||||
|
||||
/* this part has to be here */
|
||||
|
||||
#undef TRACE_INCLUDE_PATH
|
||||
#define TRACE_INCLUDE_PATH .
|
||||
|
||||
#undef TRACE_INCLUDE_FILE
|
||||
#define TRACE_INCLUDE_FILE uvc_trace
|
||||
|
||||
#include <trace/define_trace.h>
|
@ -31,13 +31,22 @@ static const struct uvc_format_desc *to_uvc_format(struct uvcg_format *uformat)
|
||||
{
|
||||
char guid[16] = UVC_GUID_FORMAT_MJPEG;
|
||||
const struct uvc_format_desc *format;
|
||||
struct uvcg_uncompressed *unc;
|
||||
|
||||
if (uformat->type == UVCG_UNCOMPRESSED) {
|
||||
struct uvcg_uncompressed *unc;
|
||||
|
||||
unc = to_uvcg_uncompressed(&uformat->group.cg_item);
|
||||
if (!unc)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
memcpy(guid, unc->desc.guidFormat, sizeof(guid));
|
||||
} else if (uformat->type == UVCG_FRAMEBASED) {
|
||||
struct uvcg_framebased *unc;
|
||||
|
||||
unc = to_uvcg_framebased(&uformat->group.cg_item);
|
||||
if (!unc)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
memcpy(guid, unc->desc.guidFormat, sizeof(guid));
|
||||
}
|
||||
|
||||
@ -314,6 +323,56 @@ uvc_v4l2_set_format(struct file *file, void *fh, struct v4l2_format *fmt)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int uvc_v4l2_g_parm(struct file *file, void *fh,
|
||||
struct v4l2_streamparm *parm)
|
||||
{
|
||||
struct video_device *vdev = video_devdata(file);
|
||||
struct uvc_device *uvc = video_get_drvdata(vdev);
|
||||
struct uvc_video *video = &uvc->video;
|
||||
struct v4l2_fract timeperframe;
|
||||
|
||||
if (!V4L2_TYPE_IS_OUTPUT(parm->type))
|
||||
return -EINVAL;
|
||||
|
||||
/* Return the actual frame period. */
|
||||
timeperframe.numerator = video->interval;
|
||||
timeperframe.denominator = 10000000;
|
||||
v4l2_simplify_fraction(&timeperframe.numerator,
|
||||
&timeperframe.denominator, 8, 333);
|
||||
|
||||
uvcg_dbg(&uvc->func, "Getting frame interval of %u/%u (%u)\n",
|
||||
timeperframe.numerator, timeperframe.denominator,
|
||||
video->interval);
|
||||
|
||||
parm->parm.output.timeperframe = timeperframe;
|
||||
parm->parm.output.capability = V4L2_CAP_TIMEPERFRAME;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uvc_v4l2_s_parm(struct file *file, void *fh,
|
||||
struct v4l2_streamparm *parm)
|
||||
{
|
||||
struct video_device *vdev = video_devdata(file);
|
||||
struct uvc_device *uvc = video_get_drvdata(vdev);
|
||||
struct uvc_video *video = &uvc->video;
|
||||
struct v4l2_fract timeperframe;
|
||||
|
||||
if (!V4L2_TYPE_IS_OUTPUT(parm->type))
|
||||
return -EINVAL;
|
||||
|
||||
timeperframe = parm->parm.output.timeperframe;
|
||||
|
||||
video->interval = v4l2_fraction_to_interval(timeperframe.numerator,
|
||||
timeperframe.denominator);
|
||||
|
||||
uvcg_dbg(&uvc->func, "Setting frame interval to %u/%u (%u)\n",
|
||||
timeperframe.numerator, timeperframe.denominator,
|
||||
video->interval);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
uvc_v4l2_enum_frameintervals(struct file *file, void *fh,
|
||||
struct v4l2_frmivalenum *fival)
|
||||
@ -496,6 +555,9 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (uvc->state != UVC_STATE_STREAMING)
|
||||
return 0;
|
||||
|
||||
uvc->state = UVC_STATE_CONNECTED;
|
||||
uvc_function_setup_continue(uvc, 1);
|
||||
return 0;
|
||||
@ -587,6 +649,8 @@ const struct v4l2_ioctl_ops uvc_v4l2_ioctl_ops = {
|
||||
.vidioc_dqbuf = uvc_v4l2_dqbuf,
|
||||
.vidioc_streamon = uvc_v4l2_streamon,
|
||||
.vidioc_streamoff = uvc_v4l2_streamoff,
|
||||
.vidioc_s_parm = uvc_v4l2_s_parm,
|
||||
.vidioc_g_parm = uvc_v4l2_g_parm,
|
||||
.vidioc_subscribe_event = uvc_v4l2_subscribe_event,
|
||||
.vidioc_unsubscribe_event = uvc_v4l2_unsubscribe_event,
|
||||
.vidioc_default = uvc_v4l2_ioctl_default,
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "uvc.h"
|
||||
#include "uvc_queue.h"
|
||||
#include "uvc_video.h"
|
||||
#include "uvc_trace.h"
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Video codecs
|
||||
@ -78,7 +79,7 @@ uvc_video_encode_data(struct uvc_video *video, struct uvc_buffer *buf,
|
||||
|
||||
/* Copy video data to the USB buffer. */
|
||||
mem = buf->mem + queue->buf_used;
|
||||
nbytes = min((unsigned int)len, buf->bytesused - queue->buf_used);
|
||||
nbytes = min_t(unsigned int, len, buf->bytesused - queue->buf_used);
|
||||
|
||||
memcpy(data, mem, nbytes);
|
||||
queue->buf_used += nbytes;
|
||||
@ -104,7 +105,7 @@ uvc_video_encode_bulk(struct usb_request *req, struct uvc_video *video,
|
||||
}
|
||||
|
||||
/* Process video data. */
|
||||
len = min((int)(video->max_payload_size - video->payload_size), len);
|
||||
len = min_t(int, video->max_payload_size - video->payload_size, len);
|
||||
ret = uvc_video_encode_data(video, buf, mem, len);
|
||||
|
||||
video->payload_size += ret;
|
||||
@ -136,7 +137,7 @@ uvc_video_encode_isoc_sg(struct usb_request *req, struct uvc_video *video,
|
||||
unsigned int pending = buf->bytesused - video->queue.buf_used;
|
||||
struct uvc_request *ureq = req->context;
|
||||
struct scatterlist *sg, *iter;
|
||||
unsigned int len = video->req_size;
|
||||
unsigned int len = buf->req_payload_size;
|
||||
unsigned int sg_left, part = 0;
|
||||
unsigned int i;
|
||||
int header_len;
|
||||
@ -146,15 +147,15 @@ uvc_video_encode_isoc_sg(struct usb_request *req, struct uvc_video *video,
|
||||
|
||||
/* Init the header. */
|
||||
header_len = uvc_video_encode_header(video, buf, ureq->header,
|
||||
video->req_size);
|
||||
buf->req_payload_size);
|
||||
sg_set_buf(sg, ureq->header, header_len);
|
||||
len -= header_len;
|
||||
|
||||
if (pending <= len)
|
||||
len = pending;
|
||||
|
||||
req->length = (len == pending) ?
|
||||
len + header_len : video->req_size;
|
||||
req->length = (len == pending) ? len + header_len :
|
||||
buf->req_payload_size;
|
||||
|
||||
/* Init the pending sgs with payload */
|
||||
sg = sg_next(sg);
|
||||
@ -202,7 +203,7 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
|
||||
{
|
||||
void *mem = req->buf;
|
||||
struct uvc_request *ureq = req->context;
|
||||
int len = video->req_size;
|
||||
int len = buf->req_payload_size;
|
||||
int ret;
|
||||
|
||||
/* Add the header. */
|
||||
@ -214,7 +215,7 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
|
||||
ret = uvc_video_encode_data(video, buf, mem, len);
|
||||
len -= ret;
|
||||
|
||||
req->length = video->req_size - len;
|
||||
req->length = buf->req_payload_size - len;
|
||||
|
||||
if (buf->bytesused == video->queue.buf_used ||
|
||||
video->queue.flags & UVC_QUEUE_DROP_INCOMPLETE) {
|
||||
@ -269,6 +270,10 @@ static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
|
||||
}
|
||||
}
|
||||
|
||||
atomic_inc(&video->queued);
|
||||
|
||||
trace_uvcg_video_queue(req, atomic_read(&video->queued));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -304,7 +309,7 @@ static int uvcg_video_usb_req_queue(struct uvc_video *video,
|
||||
*/
|
||||
if (list_empty(&video->req_free) || ureq->last_buf ||
|
||||
!(video->req_int_count %
|
||||
DIV_ROUND_UP(video->uvc_num_requests, 4))) {
|
||||
min(DIV_ROUND_UP(video->uvc_num_requests, 4), UVCG_REQ_MAX_INT_COUNT))) {
|
||||
video->req_int_count = 0;
|
||||
req->no_interrupt = 0;
|
||||
} else {
|
||||
@ -322,50 +327,6 @@ static int uvcg_video_usb_req_queue(struct uvc_video *video,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Must only be called from uvcg_video_enable - since after that we only want to
|
||||
* queue requests to the endpoint from the uvc_video_complete complete handler.
|
||||
* This function is needed in order to 'kick start' the flow of requests from
|
||||
* gadget driver to the usb controller.
|
||||
*/
|
||||
static void uvc_video_ep_queue_initial_requests(struct uvc_video *video)
|
||||
{
|
||||
struct usb_request *req = NULL;
|
||||
unsigned long flags = 0;
|
||||
unsigned int count = 0;
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* We only queue half of the free list since we still want to have
|
||||
* some free usb_requests in the free list for the video_pump async_wq
|
||||
* thread to encode uvc buffers into. Otherwise we could get into a
|
||||
* situation where the free list does not have any usb requests to
|
||||
* encode into - we always end up queueing 0 length requests to the
|
||||
* end point.
|
||||
*/
|
||||
unsigned int half_list_size = video->uvc_num_requests / 2;
|
||||
|
||||
spin_lock_irqsave(&video->req_lock, flags);
|
||||
/*
|
||||
* Take these requests off the free list and queue them all to the
|
||||
* endpoint. Since we queue 0 length requests with the req_lock held,
|
||||
* there isn't any 'data' race involved here with the complete handler.
|
||||
*/
|
||||
while (count < half_list_size) {
|
||||
req = list_first_entry(&video->req_free, struct usb_request,
|
||||
list);
|
||||
list_del(&req->list);
|
||||
req->length = 0;
|
||||
ret = uvcg_video_ep_queue(video, req);
|
||||
if (ret < 0) {
|
||||
uvcg_queue_cancel(&video->queue, 0);
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
spin_unlock_irqrestore(&video->req_lock, flags);
|
||||
}
|
||||
|
||||
static void
|
||||
uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
@ -373,12 +334,10 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
struct uvc_video *video = ureq->video;
|
||||
struct uvc_video_queue *queue = &video->queue;
|
||||
struct uvc_buffer *last_buf;
|
||||
struct usb_request *to_queue = req;
|
||||
unsigned long flags;
|
||||
bool is_bulk = video->max_payload_size;
|
||||
int ret = 0;
|
||||
|
||||
spin_lock_irqsave(&video->req_lock, flags);
|
||||
atomic_dec(&video->queued);
|
||||
if (!video->is_enabled) {
|
||||
/*
|
||||
* When is_enabled is false, uvcg_video_disable() ensures
|
||||
@ -438,51 +397,87 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
return;
|
||||
}
|
||||
|
||||
list_add_tail(&req->list, &video->req_free);
|
||||
/*
|
||||
* Here we check whether any request is available in the ready
|
||||
* list. If it is, queue it to the ep and add the current
|
||||
* usb_request to the req_free list - for video_pump to fill in.
|
||||
* Otherwise, just use the current usb_request to queue a 0
|
||||
* length request to the ep. Since we always add to the req_free
|
||||
* list if we dequeue from the ready list, there will never
|
||||
* be a situation where the req_free list is completely out of
|
||||
* requests and cannot recover.
|
||||
* Queue work to the wq as well since it is possible that a
|
||||
* buffer may not have been completely encoded with the set of
|
||||
* in-flight usb requests for whih the complete callbacks are
|
||||
* firing.
|
||||
* In that case, if we do not queue work to the worker thread,
|
||||
* the buffer will never be marked as complete - and therefore
|
||||
* not be returned to userpsace. As a result,
|
||||
* dequeue -> queue -> dequeue flow of uvc buffers will not
|
||||
* happen. Since there are is a new free request wake up the pump.
|
||||
*/
|
||||
to_queue->length = 0;
|
||||
if (!list_empty(&video->req_ready)) {
|
||||
to_queue = list_first_entry(&video->req_ready,
|
||||
struct usb_request, list);
|
||||
list_del(&to_queue->list);
|
||||
list_add_tail(&req->list, &video->req_free);
|
||||
/*
|
||||
* Queue work to the wq as well since it is possible that a
|
||||
* buffer may not have been completely encoded with the set of
|
||||
* in-flight usb requests for whih the complete callbacks are
|
||||
* firing.
|
||||
* In that case, if we do not queue work to the worker thread,
|
||||
* the buffer will never be marked as complete - and therefore
|
||||
* not be returned to userpsace. As a result,
|
||||
* dequeue -> queue -> dequeue flow of uvc buffers will not
|
||||
* happen.
|
||||
*/
|
||||
queue_work(video->async_wq, &video->pump);
|
||||
}
|
||||
/*
|
||||
* Queue to the endpoint. The actual queueing to ep will
|
||||
* only happen on one thread - the async_wq for bulk endpoints
|
||||
* and this thread for isoc endpoints.
|
||||
*/
|
||||
ret = uvcg_video_usb_req_queue(video, to_queue, !is_bulk);
|
||||
if (ret < 0) {
|
||||
/*
|
||||
* Endpoint error, but the stream is still enabled.
|
||||
* Put request back in req_free for it to be cleaned
|
||||
* up later.
|
||||
*/
|
||||
list_add_tail(&to_queue->list, &video->req_free);
|
||||
}
|
||||
queue_work(video->async_wq, &video->pump);
|
||||
|
||||
trace_uvcg_video_complete(req, atomic_read(&video->queued));
|
||||
|
||||
spin_unlock_irqrestore(&video->req_lock, flags);
|
||||
|
||||
kthread_queue_work(video->kworker, &video->hw_submit);
|
||||
}
|
||||
|
||||
static void uvcg_video_hw_submit(struct kthread_work *work)
|
||||
{
|
||||
struct uvc_video *video = container_of(work, struct uvc_video, hw_submit);
|
||||
bool is_bulk = video->max_payload_size;
|
||||
unsigned long flags;
|
||||
struct usb_request *req;
|
||||
int ret = 0;
|
||||
|
||||
while (true) {
|
||||
if (!video->ep->enabled)
|
||||
return;
|
||||
spin_lock_irqsave(&video->req_lock, flags);
|
||||
/*
|
||||
* Here we check whether any request is available in the ready
|
||||
* list. If it is, queue it to the ep and add the current
|
||||
* usb_request to the req_free list - for video_pump to fill in.
|
||||
* Otherwise, just use the current usb_request to queue a 0
|
||||
* length request to the ep. Since we always add to the req_free
|
||||
* list if we dequeue from the ready list, there will never
|
||||
* be a situation where the req_free list is completely out of
|
||||
* requests and cannot recover.
|
||||
*/
|
||||
if (!list_empty(&video->req_ready)) {
|
||||
req = list_first_entry(&video->req_ready,
|
||||
struct usb_request, list);
|
||||
} else {
|
||||
if (list_empty(&video->req_free) ||
|
||||
(atomic_read(&video->queued) > UVCG_REQ_MAX_ZERO_COUNT)) {
|
||||
spin_unlock_irqrestore(&video->req_lock, flags);
|
||||
|
||||
return;
|
||||
}
|
||||
req = list_first_entry(&video->req_free, struct usb_request,
|
||||
list);
|
||||
req->length = 0;
|
||||
}
|
||||
list_del(&req->list);
|
||||
|
||||
/*
|
||||
* Queue to the endpoint. The actual queueing to ep will
|
||||
* only happen on one thread - the async_wq for bulk endpoints
|
||||
* and this thread for isoc endpoints.
|
||||
*/
|
||||
ret = uvcg_video_usb_req_queue(video, req, !is_bulk);
|
||||
if (ret < 0) {
|
||||
/*
|
||||
* Endpoint error, but the stream is still enabled.
|
||||
* Put request back in req_free for it to be cleaned
|
||||
* up later.
|
||||
*/
|
||||
list_add_tail(&req->list, &video->req_free);
|
||||
/*
|
||||
* There is a new free request - wake up the pump.
|
||||
*/
|
||||
queue_work(video->async_wq, &video->pump);
|
||||
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&video->req_lock, flags);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
@ -496,23 +491,71 @@ uvc_video_free_requests(struct uvc_video *video)
|
||||
INIT_LIST_HEAD(&video->ureqs);
|
||||
INIT_LIST_HEAD(&video->req_free);
|
||||
INIT_LIST_HEAD(&video->req_ready);
|
||||
video->req_size = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
uvc_video_prep_requests(struct uvc_video *video)
|
||||
{
|
||||
struct uvc_device *uvc = container_of(video, struct uvc_device, video);
|
||||
struct usb_composite_dev *cdev = uvc->func.config->cdev;
|
||||
unsigned int interval_duration = video->ep->desc->bInterval * 1250;
|
||||
unsigned int max_req_size, req_size, header_size;
|
||||
unsigned int nreq;
|
||||
|
||||
max_req_size = video->ep->maxpacket
|
||||
* max_t(unsigned int, video->ep->maxburst, 1)
|
||||
* (video->ep->mult);
|
||||
|
||||
if (!usb_endpoint_xfer_isoc(video->ep->desc)) {
|
||||
video->req_size = max_req_size;
|
||||
video->reqs_per_frame = video->uvc_num_requests =
|
||||
DIV_ROUND_UP(video->imagesize, max_req_size);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (cdev->gadget->speed < USB_SPEED_HIGH)
|
||||
interval_duration = video->ep->desc->bInterval * 10000;
|
||||
|
||||
nreq = DIV_ROUND_UP(video->interval, interval_duration);
|
||||
|
||||
header_size = nreq * UVCG_REQUEST_HEADER_LEN;
|
||||
|
||||
req_size = DIV_ROUND_UP(video->imagesize + header_size, nreq);
|
||||
|
||||
if (req_size > max_req_size) {
|
||||
/* The prepared interval length and expected buffer size
|
||||
* is not possible to stream with the currently configured
|
||||
* isoc bandwidth. Fallback to the maximum.
|
||||
*/
|
||||
req_size = max_req_size;
|
||||
}
|
||||
video->req_size = req_size;
|
||||
|
||||
/* We need to compensate the amount of requests to be
|
||||
* allocated with the maximum amount of zero length requests.
|
||||
* Since it is possible that hw_submit will initially
|
||||
* enqueue some zero length requests and we then will not be
|
||||
* able to fully encode one frame.
|
||||
*/
|
||||
video->uvc_num_requests = nreq + UVCG_REQ_MAX_ZERO_COUNT;
|
||||
video->reqs_per_frame = nreq;
|
||||
}
|
||||
|
||||
static int
|
||||
uvc_video_alloc_requests(struct uvc_video *video)
|
||||
{
|
||||
struct uvc_request *ureq;
|
||||
unsigned int req_size;
|
||||
unsigned int i;
|
||||
int ret = -ENOMEM;
|
||||
|
||||
BUG_ON(video->req_size);
|
||||
|
||||
req_size = video->ep->maxpacket
|
||||
* max_t(unsigned int, video->ep->maxburst, 1)
|
||||
* (video->ep->mult);
|
||||
/*
|
||||
* calculate in uvc_video_prep_requests
|
||||
* - video->uvc_num_requests
|
||||
* - video->req_size
|
||||
*/
|
||||
uvc_video_prep_requests(video);
|
||||
|
||||
for (i = 0; i < video->uvc_num_requests; i++) {
|
||||
ureq = kzalloc(sizeof(struct uvc_request), GFP_KERNEL);
|
||||
@ -523,7 +566,7 @@ uvc_video_alloc_requests(struct uvc_video *video)
|
||||
|
||||
list_add_tail(&ureq->list, &video->ureqs);
|
||||
|
||||
ureq->req_buffer = kmalloc(req_size, GFP_KERNEL);
|
||||
ureq->req_buffer = kmalloc(video->req_size, GFP_KERNEL);
|
||||
if (ureq->req_buffer == NULL)
|
||||
goto error;
|
||||
|
||||
@ -541,12 +584,10 @@ uvc_video_alloc_requests(struct uvc_video *video)
|
||||
list_add_tail(&ureq->req->list, &video->req_free);
|
||||
/* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
|
||||
sg_alloc_table(&ureq->sgt,
|
||||
DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN,
|
||||
DIV_ROUND_UP(video->req_size - UVCG_REQUEST_HEADER_LEN,
|
||||
PAGE_SIZE) + 2, GFP_KERNEL);
|
||||
}
|
||||
|
||||
video->req_size = req_size;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
@ -699,7 +740,6 @@ uvcg_video_disable(struct uvc_video *video)
|
||||
INIT_LIST_HEAD(&video->ureqs);
|
||||
INIT_LIST_HEAD(&video->req_free);
|
||||
INIT_LIST_HEAD(&video->req_ready);
|
||||
video->req_size = 0;
|
||||
spin_unlock_irqrestore(&video->req_lock, flags);
|
||||
|
||||
/*
|
||||
@ -752,7 +792,9 @@ int uvcg_video_enable(struct uvc_video *video)
|
||||
|
||||
video->req_int_count = 0;
|
||||
|
||||
uvc_video_ep_queue_initial_requests(video);
|
||||
atomic_set(&video->queued, 0);
|
||||
|
||||
kthread_queue_work(video->kworker, &video->hw_submit);
|
||||
queue_work(video->async_wq, &video->pump);
|
||||
|
||||
return ret;
|
||||
@ -775,12 +817,24 @@ int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
|
||||
if (!video->async_wq)
|
||||
return -EINVAL;
|
||||
|
||||
/* Allocate a kthread for asynchronous hw submit handler. */
|
||||
video->kworker = kthread_create_worker(0, "UVCG");
|
||||
if (IS_ERR(video->kworker)) {
|
||||
uvcg_err(&video->uvc->func, "failed to create UVCG kworker\n");
|
||||
return PTR_ERR(video->kworker);
|
||||
}
|
||||
|
||||
kthread_init_work(&video->hw_submit, uvcg_video_hw_submit);
|
||||
|
||||
sched_set_fifo(video->kworker->task);
|
||||
|
||||
video->uvc = uvc;
|
||||
video->fcc = V4L2_PIX_FMT_YUYV;
|
||||
video->bpp = 16;
|
||||
video->width = 320;
|
||||
video->height = 240;
|
||||
video->imagesize = 320 * 240 * 2;
|
||||
video->interval = 666666;
|
||||
|
||||
/* Initialize the video buffers queue. */
|
||||
uvcg_queue_init(&video->queue, uvc->v4l2_dev.dev->parent,
|
||||
|
@ -261,7 +261,7 @@ static struct usb_composite_driver hidg_driver = {
|
||||
};
|
||||
|
||||
static struct platform_driver hidg_plat_driver = {
|
||||
.remove_new = hidg_plat_driver_remove,
|
||||
.remove = hidg_plat_driver_remove,
|
||||
.driver = {
|
||||
.name = "hidg",
|
||||
},
|
||||
|
@ -782,7 +782,7 @@ static int raw_ioctl_ep0_read(struct raw_dev *dev, unsigned long value)
|
||||
if (ret < 0)
|
||||
goto free;
|
||||
|
||||
length = min(io.length, (unsigned int)ret);
|
||||
length = min_t(unsigned int, io.length, ret);
|
||||
if (copy_to_user((void __user *)(value + sizeof(io)), data, length))
|
||||
ret = -EFAULT;
|
||||
else
|
||||
@ -1168,7 +1168,7 @@ static int raw_ioctl_ep_read(struct raw_dev *dev, unsigned long value)
|
||||
if (ret < 0)
|
||||
goto free;
|
||||
|
||||
length = min(io.length, (unsigned int)ret);
|
||||
length = min_t(unsigned int, io.length, ret);
|
||||
if (copy_to_user((void __user *)(value + sizeof(io)), data, length))
|
||||
ret = -EFAULT;
|
||||
else
|
||||
|
@ -428,7 +428,7 @@ MODULE_DEVICE_TABLE(of, ast_vhub_dt_ids);
|
||||
|
||||
static struct platform_driver ast_vhub_driver = {
|
||||
.probe = ast_vhub_probe,
|
||||
.remove_new = ast_vhub_remove,
|
||||
.remove = ast_vhub_remove,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.of_match_table = ast_vhub_dt_ids,
|
||||
|
@ -156,7 +156,7 @@
|
||||
#define AST_EP_DMA_DESC_PID_DATA1 (2 << 14)
|
||||
#define AST_EP_DMA_DESC_PID_MDATA (3 << 14)
|
||||
#define EP_DESC1_IN_LEN(x) ((x) & 0x1fff)
|
||||
#define AST_EP_DMA_DESC_MAX_LEN (7680) /* Max packet length for trasmit in 1 desc */
|
||||
#define AST_EP_DMA_DESC_MAX_LEN (7680) /* Max packet length for transmit in 1 desc */
|
||||
|
||||
struct ast_udc_request {
|
||||
struct usb_request req;
|
||||
@ -1590,7 +1590,7 @@ MODULE_DEVICE_TABLE(of, ast_udc_of_dt_ids);
|
||||
|
||||
static struct platform_driver ast_udc_driver = {
|
||||
.probe = ast_udc_probe,
|
||||
.remove_new = ast_udc_remove,
|
||||
.remove = ast_udc_remove,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.of_match_table = ast_udc_of_dt_ids,
|
||||
|
@ -2002,7 +2002,7 @@ static int at91udc_resume(struct platform_device *pdev)
|
||||
|
||||
static struct platform_driver at91_udc_driver = {
|
||||
.probe = at91udc_probe,
|
||||
.remove_new = at91udc_remove,
|
||||
.remove = at91udc_remove,
|
||||
.shutdown = at91udc_shutdown,
|
||||
.suspend = at91udc_suspend,
|
||||
.resume = at91udc_resume,
|
||||
|
@ -2444,7 +2444,7 @@ static SIMPLE_DEV_PM_OPS(usba_udc_pm_ops, usba_udc_suspend, usba_udc_resume);
|
||||
|
||||
static struct platform_driver udc_driver = {
|
||||
.probe = usba_udc_probe,
|
||||
.remove_new = usba_udc_remove,
|
||||
.remove = usba_udc_remove,
|
||||
.driver = {
|
||||
.name = "atmel_usba_udc",
|
||||
.pm = &usba_udc_pm_ops,
|
||||
|
@ -2367,7 +2367,7 @@ static void bcm63xx_udc_remove(struct platform_device *pdev)
|
||||
|
||||
static struct platform_driver bcm63xx_udc_driver = {
|
||||
.probe = bcm63xx_udc_probe,
|
||||
.remove_new = bcm63xx_udc_remove,
|
||||
.remove = bcm63xx_udc_remove,
|
||||
.driver = {
|
||||
.name = DRV_MODULE_NAME,
|
||||
},
|
||||
|
@ -648,7 +648,7 @@ static struct platform_driver bdc_driver = {
|
||||
.of_match_table = bdc_of_match,
|
||||
},
|
||||
.probe = bdc_probe,
|
||||
.remove_new = bdc_remove,
|
||||
.remove = bdc_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(bdc_driver);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user