-What: /sys/bus/thunderbolt/devices/<xdomain>/rx_speed
-Date: Feb 2021
-KernelVersion: 5.11
-Contact: Isaac Hazan <isaac.hazan@intel.com>
-Description: This attribute reports the XDomain RX speed per lane.
- All RX lanes run at the same speed.
-
-What: /sys/bus/thunderbolt/devices/<xdomain>/rx_lanes
-Date: Feb 2021
-KernelVersion: 5.11
-Contact: Isaac Hazan <isaac.hazan@intel.com>
-Description: This attribute reports the number of RX lanes the XDomain
- is using simultaneously through its upstream port.
-
-What: /sys/bus/thunderbolt/devices/<xdomain>/tx_speed
-Date: Feb 2021
-KernelVersion: 5.11
-Contact: Isaac Hazan <isaac.hazan@intel.com>
-Description: This attribute reports the XDomain TX speed per lane.
- All TX lanes run at the same speed.
-
-What: /sys/bus/thunderbolt/devices/<xdomain>/tx_lanes
-Date: Feb 2021
-KernelVersion: 5.11
-Contact: Isaac Hazan <isaac.hazan@intel.com>
-Description: This attribute reports number of TX lanes the XDomain
- is using simultaneously through its upstream port.
-
What: /sys/bus/thunderbolt/devices/.../domainX/boot_acl
Date: Jun 2018
KernelVersion: 4.17
Description: This attribute contains name of this device extracted from
the device DROM.
+What: /sys/bus/thunderbolt/devices/.../maxhopid
+Date: Jul 2021
+KernelVersion: 5.13
+Contact: Mika Westerberg <mika.westerberg@linux.intel.com>
+Description: Only set for XDomains. The maximum HopID the other host
+ supports as its input HopID.
+
What: /sys/bus/thunderbolt/devices/.../rx_speed
Date: Jan 2020
KernelVersion: 5.5
$ref: /schemas/types.yaml#/definitions/uint32
enum: [1, 2, 3]
+ slow-charger-loop:
+ description: Allows PMIC charger loops which are slow(i.e. cannot meet the 15ms deadline) to
+ still comply to pSnkStby i.e Maximum power that can be consumed by sink while in Sink Standby
+ state as defined in 7.4.2 Sink Electrical Parameters of USB Power Delivery Specification
+ Revision 3.0, Version 1.2. When the property is set, the port requests pSnkStby(2.5W -
+ 5V@500mA) upon entering SNK_DISCOVERY(instead of 3A or the 1.5A, Rp current advertised, during
+ SNK_DISCOVERY) and the actual currrent limit after reception of PS_Ready for PD link or during
+ SNK_READY for non-pd link.
+ type: boolean
+
required:
- compatible
Xilinx SuperSpeed DWC3 USB SoC controller
Required properties:
-- compatible: Should contain "xlnx,zynqmp-dwc3"
+- compatible: May contain "xlnx,zynqmp-dwc3" or "xlnx,versal-dwc3"
+- reg: Base address and length of the register control block
- clocks: A list of phandles for the clocks listed in clock-names
- clock-names: Should contain the following:
"bus_clk" Master/Core clock, have to be >= 125 MHz for SS
operation and >= 60MHz for HS operation
"ref_clk" Clock source to core during PHY power down
+- resets: A list of phandles for resets listed in reset-names
+- reset-names:
+ "usb_crst" USB core reset
+ "usb_hibrst" USB hibernation reset
+ "usb_apbrst" USB APB reset
Required child node:
A child node must exist to represent the core DWC3 IP block. The name of
the node is not important. The content of the node is defined in dwc3.txt.
+Optional properties for snps,dwc3:
+- dma-coherent: Enable this flag if CCI is enabled in design. Adding this
+ flag configures Global SoC bus Configuration Register and
+ Xilinx USB 3.0 IP - USB coherency register to enable CCI.
+- interrupt-names: Should contain the following:
+ "dwc_usb3" USB gadget mode interrupts
+ "otg" USB OTG mode interrupts
+ "hiber" USB hibernation interrupts
+
Example device node:
usb@0 {
#address-cells = <0x2>;
#size-cells = <0x1>;
compatible = "xlnx,zynqmp-dwc3";
+ reg = <0x0 0xff9d0000 0x0 0x100>;
clock-names = "bus_clk", "ref_clk";
clocks = <&clk125>, <&clk125>;
+ resets = <&zynqmp_reset ZYNQMP_RESET_USB1_CORERESET>,
+ <&zynqmp_reset ZYNQMP_RESET_USB1_HIBERRESET>,
+ <&zynqmp_reset ZYNQMP_RESET_USB1_APB>;
+ reset-names = "usb_crst", "usb_hibrst", "usb_apbrst";
ranges;
dwc3@fe200000 {
compatible = "snps,dwc3";
reg = <0x0 0xfe200000 0x40000>;
- interrupts = <0x0 0x41 0x4>;
+ interrupt-names = "dwc_usb3", "otg", "hiber";
+ interrupts = <0 65 4>, <0 69 4>, <0 75 4>;
+ phys = <&psgtr 2 PHY_TYPE_USB3 0 2>;
+ phy-names = "usb3-phy";
dr_mode = "host";
+ dma-coherent;
};
};
# Required child node:
patternProperties:
- "^dwc3@[0-9a-f]+$":
- type: object
- description:
- A child node must exist to represent the core DWC3 IP block
- The content of the node is defined in dwc3.txt.
+ "^usb@[0-9a-f]+$":
+ $ref: snps,dwc3.yaml#
required:
- compatible
dma-ranges = <0x40000000 0x40000000 0xc0000000>;
ranges;
- dwc3@38100000 {
+ usb@38100000 {
compatible = "snps,dwc3";
reg = <0x38100000 0x10000>;
clocks = <&clk IMX8MP_CLK_HSIO_AXI>,
description:
Set this flag to force EHCI reset after resume.
+ spurious-oc:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Set this flag to indicate that the hardware sometimes turns on
+ the OC bit when an over-current isn't actually present.
+
companion:
$ref: /schemas/types.yaml#/definitions/phandle
description:
- mediatek,mt7629-xhci
- mediatek,mt8173-xhci
- mediatek,mt8183-xhci
+ - mediatek,mt8192-xhci
- const: mediatek,mtk-xhci
reg:
- const: ippc # optional, only needed for case 1.
interrupts:
- maxItems: 1
+ description:
+ use "interrupts-extended" when the interrupts are connected to the
+ separate interrupt controllers
+ minItems: 1
+ items:
+ - description: xHCI host controller interrupt
+ - description: optional, wakeup interrupt used to support runtime PM
+
+ interrupt-names:
+ items:
+ - const: host
+ - const: wakeup
power-domains:
description: A phandle to USB power domain node to control USB's MTCMOS
vbus-supply:
description: Regulator of USB VBUS5v
- usb3-lpm-capable:
- description: supports USB3.0 LPM
- type: boolean
+ usb3-lpm-capable: true
+
+ usb2-lpm-disable: true
imod-interval-ns:
description:
- description:
The second cell represents the register base address of the glue
layer in syscon
- - description:
+ - description: |
The third cell represents the hardware version of the glue layer,
- 1 is used by mt8173 etc, 2 is used by mt2712 etc
- enum: [1, 2]
+ 1 - used by mt8173 etc, revision 1 without following IPM rule;
+ 2 - used by mt2712 etc, revision 2 following IPM rule;
+ 101 - used by mt8183, specific 1.01;
+ 102 - used by mt8192, specific 1.02;
+ enum: [1, 2, 101, 102]
mediatek,u3p-dis-msk:
$ref: /schemas/types.yaml#/definitions/uint32
- mediatek,mt2712-mtu3
- mediatek,mt8173-mtu3
- mediatek,mt8183-mtu3
+ - mediatek,mt8192-mtu3
- const: mediatek,mtu3
reg:
Any connector to the data bus of this controller should be modelled
using the OF graph bindings specified, if the "usb-role-switch"
property is used. See graph.txt
- type: object
+ $ref: /schemas/graph.yaml#/properties/port
enable-manual-drd:
$ref: /schemas/types.yaml#/definitions/flag
- description:
The second cell represents the register base address of the glue
layer in syscon
- - description:
+ - description: |
The third cell represents the hardware version of the glue layer,
- 1 is used by mt8173 etc, 2 is used by mt2712 etc
- enum: [1, 2]
+ 1 - used by mt8173 etc, revision 1 without following IPM rule;
+ 2 - used by mt2712 etc, revision 2 with following IPM rule;
+ 101 - used by mt8183, specific 1.01;
+ 102 - used by mt8192, specific 1.02;
+ enum: [1, 2, 101, 102]
mediatek,u3p-dis-msk:
$ref: /schemas/types.yaml#/definitions/uint32
- qcom,msm8996-dwc3
- qcom,msm8998-dwc3
- qcom,sc7180-dwc3
+ - qcom,sc7280-dwc3
- qcom,sdm845-dwc3
- qcom,sdx55-dwc3
- qcom,sm8150-dwc3
minItems: 1
snps,usb2-lpm-disable:
- description: Indicate if we don't want to enable USB2 HW LPM
+ description: Indicate if we don't want to enable USB2 HW LPM for host
+ mode.
type: boolean
snps,usb3_lpm_capable:
description: Determines if platform is USB3 LPM capable
type: boolean
+ snps,usb2-gadget-lpm-disable:
+ description: Indicate if we don't want to enable USB2 HW LPM for gadget
+ mode.
+ type: boolean
+
snps,dis-start-transfer-quirk:
description:
When set, disable isoc START TRANSFER command failure SW work-around
additionalProperties: true
examples:
- #hub connected to port 1
- #device connected to port 2
- #device connected to port 3
+ # hub connected to port 1
+ # device connected to port 2
+ # device connected to port 3
# interface 0 of configuration 1
# interface 0 of configuration 2
- |
+++ /dev/null
-USB NOP PHY
-
-Required properties:
-- compatible: should be usb-nop-xceiv
-- #phy-cells: Must be 0
-
-Optional properties:
-- clocks: phandle to the PHY clock. Use as per Documentation/devicetree
- /bindings/clock/clock-bindings.txt
- This property is required if clock-frequency is specified.
-
-- clock-names: Should be "main_clk"
-
-- clock-frequency: the clock frequency (in Hz) that the PHY clock must
- be configured to.
-
-- vcc-supply: phandle to the regulator that provides power to the PHY.
-
-- reset-gpios: Should specify the GPIO for reset.
-
-- vbus-detect-gpio: should specify the GPIO detecting a VBus insertion
- (see Documentation/devicetree/bindings/gpio/gpio.txt)
-- vbus-regulator : should specifiy the regulator supplying current drawn from
- the VBus line (see Documentation/devicetree/bindings/regulator/regulator.txt).
-
-Example:
-
- hsusb1_phy {
- compatible = "usb-nop-xceiv";
- clock-frequency = <19200000>;
- clocks = <&osc 0>;
- clock-names = "main_clk";
- vcc-supply = <&hsusb1_vcc_regulator>;
- reset-gpios = <&gpio1 7 GPIO_ACTIVE_LOW>;
- vbus-detect-gpio = <&gpio2 13 GPIO_ACTIVE_HIGH>;
- vbus-regulator = <&vbus_regulator>;
- #phy-cells = <0>;
- };
-
-hsusb1_phy is a NOP USB PHY device that gets its clock from an oscillator
-and expects that clock to be configured to 19.2MHz by the NOP PHY driver.
-hsusb1_vcc_regulator provides power to the PHY and GPIO 7 controls RESET.
-GPIO 13 detects VBus insertion, and accordingly notifies the vbus-regulator.
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/usb-nop-xceiv.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: USB NOP PHY
+
+maintainers:
+ - Rob Herring <robh@kernel.org>
+
+properties:
+ compatible:
+ const: usb-nop-xceiv
+
+ clocks:
+ maxItems: 1
+
+ clock-names:
+ const: main_clk
+
+ clock-frequency: true
+
+ '#phy-cells':
+ const: 0
+
+ vcc-supply:
+ description: phandle to the regulator that provides power to the PHY.
+
+ reset-gpios:
+ maxItems: 1
+
+ vbus-detect-gpio:
+ description: Should specify the GPIO detecting a VBus insertion
+ maxItems: 1
+
+ vbus-regulator:
+ description: Should specifiy the regulator supplying current drawn from
+ the VBus line.
+ $ref: /schemas/types.yaml#/definitions/phandle
+
+required:
+ - compatible
+ - '#phy-cells'
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ hsusb1_phy {
+ compatible = "usb-nop-xceiv";
+ clock-frequency = <19200000>;
+ clocks = <&osc 0>;
+ clock-names = "main_clk";
+ vcc-supply = <&hsusb1_vcc_regulator>;
+ reset-gpios = <&gpio1 7 GPIO_ACTIVE_LOW>;
+ vbus-detect-gpio = <&gpio2 13 GPIO_ACTIVE_HIGH>;
+ vbus-regulator = <&vbus_regulator>;
+ #phy-cells = <0>;
+ };
+
+...
USB-Standard Types
==================
-In ``<linux/usb/ch9.h>`` you will find the USB data types defined in
-chapter 9 of the USB specification. These data types are used throughout
-USB, and in APIs including this host side API, gadget APIs, usb character
-devices and debugfs interfaces.
+In ``drivers/usb/common/common.c`` and ``drivers/usb/common/debug.c`` you
+will find the USB data types defined in chapter 9 of the USB specification.
+These data types are used throughout USB, and in APIs including this host
+side API, gadget APIs, usb character devices and debugfs interfaces.
-.. kernel-doc:: include/linux/usb/ch9.h
- :internal:
+.. kernel-doc:: drivers/usb/common/common.c
+ :export:
-.. _usb_header:
+.. kernel-doc:: drivers/usb/common/debug.c
+ :export:
Host-Side Data Types and Macros
===============================
USB/IP protocol
===============
-PRELIMINARY DRAFT, MAY CONTAIN MISTAKES!
-28 Jun 2011
+Architecture
+============
The USB/IP protocol follows a server/client architecture. The server exports the
-USB devices and the clients imports them. The device driver for the exported
+USB devices and the clients import them. The device driver for the exported
USB device runs on the client machine.
The client may ask for the list of the exported USB devices. To get the list the
-client opens a TCP/IP connection towards the server, and sends an OP_REQ_DEVLIST
+client opens a TCP/IP connection to the server, and sends an OP_REQ_DEVLIST
packet on top of the TCP/IP connection (so the actual OP_REQ_DEVLIST may be sent
in one or more pieces at the low level transport layer). The server sends back
the OP_REP_DEVLIST packet which lists the exported USB devices. Finally the
| |
Once the client knows the list of exported USB devices it may decide to use one
-of them. First the client opens a TCP/IP connection towards the server and
+of them. First the client opens a TCP/IP connection to the server and
sends an OP_REQ_IMPORT packet. The server replies with OP_REP_IMPORT. If the
import was successful the TCP/IP connection remains open and will be used
to transfer the URB traffic between the client and the server. The client may
| <---------------------------------------------- |
| . |
| : |
+
+For UNLINK, note that after a successful USBIP_RET_UNLINK, the unlinked URB
+submission would not have a corresponding USBIP_RET_SUBMIT (this is explained in
+function stub_recv_cmd_unlink of drivers/usb/usbip/stub_rx.c).
+
+::
+
+ virtual host controller usb host
+ "client" "server"
+ (imports USB devices) (exports USB devices)
+ | |
+ | USBIP_CMD_SUBMIT(seqnum = p) |
+ | ----------------------------------------------> |
| |
| USBIP_CMD_UNLINK |
+ | (seqnum = p+1, unlink_seqnum = p) |
| ----------------------------------------------> |
| |
| USBIP_RET_UNLINK |
+ | (seqnum = p+1, status = -ECONNRESET) |
+ | <---------------------------------------------- |
+ | |
+ | Note: No USBIP_RET_SUBMIT(seqnum = p) |
+ | <--X---X---X---X---X---X---X---X---X---X---X--- |
+ | . |
+ | : |
+ | |
+ | USBIP_CMD_SUBMIT(seqnum = q) |
+ | ----------------------------------------------> |
+ | |
+ | USBIP_RET_SUBMIT(seqnum = q) |
+ | <---------------------------------------------- |
+ | |
+ | USBIP_CMD_UNLINK |
+ | (seqnum = q+1, unlink_seqnum = q) |
+ | ----------------------------------------------> |
+ | |
+ | USBIP_RET_UNLINK |
+ | (seqnum = q+1, status = 0) |
| <---------------------------------------------- |
| |
The fields are in network (big endian) byte order meaning that the most significant
byte (MSB) is stored at the lowest address.
+Protocol Version
+================
+
+The documented USBIP version is v1.1.1. The binary representation of this
+version in message headers is 0x0111.
+
+This is defined in tools/usb/usbip/configure.ac
+
+Message Format
+==============
OP_REQ_DEVLIST:
Retrieve the list of exported USB devices.
+-----------+--------+------------+---------------------------------------------------+
| Offset | Length | Value | Description |
+===========+========+============+===================================================+
-| 0 | 2 | 0x0100 | Binary-coded decimal USBIP version number: v1.0.0 |
+| 0 | 2 | | USBIP version |
+-----------+--------+------------+---------------------------------------------------+
| 2 | 2 | 0x8005 | Command code: Retrieve the list of exported USB |
| | | | devices. |
+-----------+--------+------------+---------------------------------------------------+
| Offset | Length | Value | Description |
+===========+========+============+===================================================+
-| 0 | 2 | 0x0100 | Binary-coded decimal USBIP version number: v1.0.0.|
+| 0 | 2 | | USBIP version |
+-----------+--------+------------+---------------------------------------------------+
| 2 | 2 | 0x0005 | Reply code: The list of exported USB devices. |
+-----------+--------+------------+---------------------------------------------------+
| 0x143 | 1 | | bNumInterfaces |
+-----------+--------+------------+---------------------------------------------------+
| 0x144 | | m_0 | From now on each interface is described, all |
-| | | | together bNumInterfaces times, with the |
-| | | | the following 4 fields: |
+| | | | together bNumInterfaces times, with the following |
+| | | | 4 fields: |
+-----------+--------+------------+---------------------------------------------------+
| | 1 | | bInterfaceClass |
+-----------+--------+------------+---------------------------------------------------+
| 0x147 | 1 | | padding byte for alignment, shall be set to zero |
+-----------+--------+------------+---------------------------------------------------+
| 0xC + | | | The second exported USB device starts at i=1 |
-| i*0x138 + | | | with the busid field. |
+| i*0x138 + | | | with the path field. |
| m_(i-1)*4 | | | |
+-----------+--------+------------+---------------------------------------------------+
+-----------+--------+------------+---------------------------------------------------+
| Offset | Length | Value | Description |
+===========+========+============+===================================================+
-| 0 | 2 | 0x0100 | Binary-coded decimal USBIP version number: v1.0.0 |
+| 0 | 2 | | USBIP version |
+-----------+--------+------------+---------------------------------------------------+
| 2 | 2 | 0x8003 | Command code: import a remote USB device. |
+-----------+--------+------------+---------------------------------------------------+
+-----------+--------+------------+---------------------------------------------------+
| Offset | Length | Value | Description |
+===========+========+============+===================================================+
-| 0 | 2 | 0x0100 | Binary-coded decimal USBIP version number: v1.0.0 |
+| 0 | 2 | | USBIP version |
+-----------+--------+------------+---------------------------------------------------+
| 2 | 2 | 0x0003 | Reply code: Reply to import. |
+-----------+--------+------------+---------------------------------------------------+
| 0x13E | 1 | | bNumInterfaces |
+-----------+--------+------------+---------------------------------------------------+
-USBIP_CMD_SUBMIT:
- Submit an URB
+The following four commands have a common basic header called
+'usbip_header_basic', and their headers, called 'usbip_header' (before
+transfer_buffer payload), have the same length, therefore paddings are needed.
-+-----------+--------+------------+---------------------------------------------------+
-| Offset | Length | Value | Description |
-+===========+========+============+===================================================+
-| 0 | 4 | 0x00000001 | command: Submit an URB |
-+-----------+--------+------------+---------------------------------------------------+
-| 4 | 4 | | seqnum: the sequence number of the URB to submit |
-+-----------+--------+------------+---------------------------------------------------+
-| 8 | 4 | | devid |
-+-----------+--------+------------+---------------------------------------------------+
-| 0xC | 4 | | direction: |
-| | | | |
-| | | | - 0: USBIP_DIR_OUT |
-| | | | - 1: USBIP_DIR_IN |
-+-----------+--------+------------+---------------------------------------------------+
-| 0x10 | 4 | | ep: endpoint number, possible values are: 0...15 |
-+-----------+--------+------------+---------------------------------------------------+
-| 0x14 | 4 | | transfer_flags: possible values depend on the |
-| | | | URB transfer type, see below |
-+-----------+--------+------------+---------------------------------------------------+
-| 0x18 | 4 | | transfer_buffer_length |
-+-----------+--------+------------+---------------------------------------------------+
-| 0x1C | 4 | | start_frame: specify the selected frame to |
-| | | | transmit an ISO frame, ignored if URB_ISO_ASAP |
-| | | | is specified at transfer_flags |
-+-----------+--------+------------+---------------------------------------------------+
-| 0x20 | 4 | | number_of_packets: number of ISO packets |
-+-----------+--------+------------+---------------------------------------------------+
-| 0x24 | 4 | | interval: maximum time for the request on the |
-| | | | server-side host controller |
-+-----------+--------+------------+---------------------------------------------------+
-| 0x28 | 8 | | setup: data bytes for USB setup, filled with |
-| | | | zeros if not used |
-+-----------+--------+------------+---------------------------------------------------+
-| 0x30 | | | URB data. For ISO transfers the padding between |
-| | | | each ISO packets is not transmitted. |
-+-----------+--------+------------+---------------------------------------------------+
+usbip_header_basic:
++-----------+--------+---------------------------------------------------+
+| Offset | Length | Description |
++===========+========+===================================================+
+| 0 | 4 | command |
++-----------+--------+---------------------------------------------------+
+| 4 | 4 | seqnum: sequential number that identifies requests|
+| | | and corresponding responses; |
+| | | incremented per connection |
++-----------+--------+---------------------------------------------------+
+| 8 | 4 | devid: specifies a remote USB device uniquely |
+| | | instead of busnum and devnum; |
+| | | for client (request), this value is |
+| | | ((busnum << 16) | devnum); |
+| | | for server (response), this shall be set to 0 |
++-----------+--------+---------------------------------------------------+
+| 0xC | 4 | direction: |
+| | | |
+| | | - 0: USBIP_DIR_OUT |
+| | | - 1: USBIP_DIR_IN |
+| | | |
+| | | only used by client, for server this shall be 0 |
++-----------+--------+---------------------------------------------------+
+| 0x10 | 4 | ep: endpoint number |
+| | | only used by client, for server this shall be 0; |
+| | | for UNLINK, this shall be 0 |
++-----------+--------+---------------------------------------------------+
- +-------------------------+------------+---------+-----------+----------+-------------+
- | Allowed transfer_flags | value | control | interrupt | bulk | isochronous |
- +=========================+============+=========+===========+==========+=============+
- | URB_SHORT_NOT_OK | 0x00000001 | only in | only in | only in | no |
- +-------------------------+------------+---------+-----------+----------+-------------+
- | URB_ISO_ASAP | 0x00000002 | no | no | no | yes |
- +-------------------------+------------+---------+-----------+----------+-------------+
- | URB_NO_TRANSFER_DMA_MAP | 0x00000004 | yes | yes | yes | yes |
- +-------------------------+------------+---------+-----------+----------+-------------+
- | URB_ZERO_PACKET | 0x00000040 | no | no | only out | no |
- +-------------------------+------------+---------+-----------+----------+-------------+
- | URB_NO_INTERRUPT | 0x00000080 | yes | yes | yes | yes |
- +-------------------------+------------+---------+-----------+----------+-------------+
- | URB_FREE_BUFFER | 0x00000100 | yes | yes | yes | yes |
- +-------------------------+------------+---------+-----------+----------+-------------+
- | URB_DIR_MASK | 0x00000200 | yes | yes | yes | yes |
- +-------------------------+------------+---------+-----------+----------+-------------+
+USBIP_CMD_SUBMIT:
+ Submit an URB
++-----------+--------+---------------------------------------------------+
+| Offset | Length | Description |
++===========+========+===================================================+
+| 0 | 20 | usbip_header_basic, 'command' shall be 0x00000001 |
++-----------+--------+---------------------------------------------------+
+| 0x14 | 4 | transfer_flags: possible values depend on the |
+| | | URB transfer_flags (refer to URB doc in |
+| | | Documentation/driver-api/usb/URB.rst) |
+| | | but with URB_NO_TRANSFER_DMA_MAP masked. Refer to |
+| | | function usbip_pack_cmd_submit and function |
+| | | tweak_transfer_flags in drivers/usb/usbip/ |
+| | | usbip_common.c. The following fields may also ref |
+| | | to function usbip_pack_cmd_submit and URB doc |
++-----------+--------+---------------------------------------------------+
+| 0x18 | 4 | transfer_buffer_length: |
+| | | use URB transfer_buffer_length |
++-----------+--------+---------------------------------------------------+
+| 0x1C | 4 | start_frame: use URB start_frame; |
+| | | initial frame for ISO transfer; |
+| | | shall be set to 0 if not ISO transfer |
++-----------+--------+---------------------------------------------------+
+| 0x20 | 4 | number_of_packets: number of ISO packets; |
+| | | shall be set to 0xffffffff if not ISO transfer |
++-----------+--------+---------------------------------------------------+
+| 0x24 | 4 | interval: maximum time for the request on the |
+| | | server-side host controller |
++-----------+--------+---------------------------------------------------+
+| 0x28 | 8 | setup: data bytes for USB setup, filled with |
+| | | zeros if not used. |
++-----------+--------+---------------------------------------------------+
+| 0x30 | n | transfer_buffer. |
+| | | If direction is USBIP_DIR_OUT then n equals |
+| | | transfer_buffer_length; otherwise n equals 0. |
+| | | For ISO transfers the padding between each ISO |
+| | | packets is not transmitted. |
++-----------+--------+---------------------------------------------------+
+| 0x30+n | m | iso_packet_descriptor |
++-----------+--------+---------------------------------------------------+
USBIP_RET_SUBMIT:
Reply for submitting an URB
-+-----------+--------+------------+---------------------------------------------------+
-| Offset | Length | Value | Description |
-+===========+========+============+===================================================+
-| 0 | 4 | 0x00000003 | command |
-+-----------+--------+------------+---------------------------------------------------+
-| 4 | 4 | | seqnum: URB sequence number |
-+-----------+--------+------------+---------------------------------------------------+
-| 8 | 4 | | devid |
-+-----------+--------+------------+---------------------------------------------------+
-| 0xC | 4 | | direction: |
-| | | | |
-| | | | - 0: USBIP_DIR_OUT |
-| | | | - 1: USBIP_DIR_IN |
-+-----------+--------+------------+---------------------------------------------------+
-| 0x10 | 4 | | ep: endpoint number |
-+-----------+--------+------------+---------------------------------------------------+
-| 0x14 | 4 | | status: zero for successful URB transaction, |
-| | | | otherwise some kind of error happened. |
-+-----------+--------+------------+---------------------------------------------------+
-| 0x18 | 4 | n | actual_length: number of URB data bytes |
-+-----------+--------+------------+---------------------------------------------------+
-| 0x1C | 4 | | start_frame: for an ISO frame the actually |
-| | | | selected frame for transmit. |
-+-----------+--------+------------+---------------------------------------------------+
-| 0x20 | 4 | | number_of_packets |
-+-----------+--------+------------+---------------------------------------------------+
-| 0x24 | 4 | | error_count |
-+-----------+--------+------------+---------------------------------------------------+
-| 0x28 | 8 | | setup: data bytes for USB setup, filled with |
-| | | | zeros if not used |
-+-----------+--------+------------+---------------------------------------------------+
-| 0x30 | n | | URB data bytes. For ISO transfers the padding |
-| | | | between each ISO packets is not transmitted. |
-+-----------+--------+------------+---------------------------------------------------+
++-----------+--------+---------------------------------------------------+
+| Offset | Length | Description |
++===========+========+===================================================+
+| 0 | 20 | usbip_header_basic, 'command' shall be 0x00000003 |
++-----------+--------+---------------------------------------------------+
+| 0x14 | 4 | status: zero for successful URB transaction, |
+| | | otherwise some kind of error happened. |
++-----------+--------+---------------------------------------------------+
+| 0x18 | 4 | actual_length: number of URB data bytes; |
+| | | use URB actual_length |
++-----------+--------+---------------------------------------------------+
+| 0x1C | 4 | start_frame: use URB start_frame; |
+| | | initial frame for ISO transfer; |
+| | | shall be set to 0 if not ISO transfer |
++-----------+--------+---------------------------------------------------+
+| 0x20 | 4 | number_of_packets: number of ISO packets; |
+| | | shall be set to 0xffffffff if not ISO transfer |
++-----------+--------+---------------------------------------------------+
+| 0x24 | 4 | error_count |
++-----------+--------+---------------------------------------------------+
+| 0x28 | 8 | padding, shall be set to 0 |
++-----------+--------+---------------------------------------------------+
+| 0x30 | n | transfer_buffer. |
+| | | If direction is USBIP_DIR_IN then n equals |
+| | | actual_length; otherwise n equals 0. |
+| | | For ISO transfers the padding between each ISO |
+| | | packets is not transmitted. |
++-----------+--------+---------------------------------------------------+
+| 0x30+n | m | iso_packet_descriptor |
++-----------+--------+---------------------------------------------------+
USBIP_CMD_UNLINK:
Unlink an URB
-+-----------+--------+------------+---------------------------------------------------+
-| Offset | Length | Value | Description |
-+===========+========+============+===================================================+
-| 0 | 4 | 0x00000002 | command: URB unlink command |
-+-----------+--------+------------+---------------------------------------------------+
-| 4 | 4 | | seqnum: URB sequence number to unlink: |
-| | | | |
-| | | | FIXME: |
-| | | | is this so? |
-+-----------+--------+------------+---------------------------------------------------+
-| 8 | 4 | | devid |
-+-----------+--------+------------+---------------------------------------------------+
-| 0xC | 4 | | direction: |
-| | | | |
-| | | | - 0: USBIP_DIR_OUT |
-| | | | - 1: USBIP_DIR_IN |
-+-----------+--------+------------+---------------------------------------------------+
-| 0x10 | 4 | | ep: endpoint number: zero |
-+-----------+--------+------------+---------------------------------------------------+
-| 0x14 | 4 | | seqnum: the URB sequence number given previously |
-| | | | at USBIP_CMD_SUBMIT.seqnum field |
-+-----------+--------+------------+---------------------------------------------------+
-| 0x30 | n | | URB data bytes. For ISO transfers the padding |
-| | | | between each ISO packets is not transmitted. |
-+-----------+--------+------------+---------------------------------------------------+
++-----------+--------+---------------------------------------------------+
+| Offset | Length | Description |
++===========+========+===================================================+
+| 0 | 20 | usbip_header_basic, 'command' shall be 0x00000002 |
++-----------+--------+---------------------------------------------------+
+| 0x14 | 4 | unlink_seqnum, of the SUBMIT request to unlink |
++-----------+--------+---------------------------------------------------+
+| 0x18 | 24 | padding, shall be set to 0 |
++-----------+--------+---------------------------------------------------+
USBIP_RET_UNLINK:
Reply for URB unlink
-+-----------+--------+------------+---------------------------------------------------+
-| Offset | Length | Value | Description |
-+===========+========+============+===================================================+
-| 0 | 4 | 0x00000004 | command: reply for the URB unlink command |
-+-----------+--------+------------+---------------------------------------------------+
-| 4 | 4 | | seqnum: the unlinked URB sequence number |
-+-----------+--------+------------+---------------------------------------------------+
-| 8 | 4 | | devid |
-+-----------+--------+------------+---------------------------------------------------+
-| 0xC | 4 | | direction: |
-| | | | |
-| | | | - 0: USBIP_DIR_OUT |
-| | | | - 1: USBIP_DIR_IN |
-+-----------+--------+------------+---------------------------------------------------+
-| 0x10 | 4 | | ep: endpoint number |
-+-----------+--------+------------+---------------------------------------------------+
-| 0x14 | 4 | | status: This is the value contained in the |
-| | | | urb->status in the URB completition handler. |
-| | | | |
-| | | | FIXME: |
-| | | | a better explanation needed. |
-+-----------+--------+------------+---------------------------------------------------+
-| 0x30 | n | | URB data bytes. For ISO transfers the padding |
-| | | | between each ISO packets is not transmitted. |
-+-----------+--------+------------+---------------------------------------------------+
++-----------+--------+---------------------------------------------------+
+| Offset | Length | Description |
++===========+========+===================================================+
+| 0 | 20 | usbip_header_basic, 'command' shall be 0x00000004 |
++-----------+--------+---------------------------------------------------+
+| 0x14 | 4 | status: This is similar to the status of |
+| | | USBIP_RET_SUBMIT (share the same memory offset). |
+| | | When UNLINK is successful, status is -ECONNRESET; |
+| | | when USBIP_CMD_UNLINK is after USBIP_RET_SUBMIT |
+| | | status is 0 |
++-----------+--------+---------------------------------------------------+
+| 0x18 | 24 | padding, shall be set to 0 |
++-----------+--------+---------------------------------------------------+
+
+EXAMPLE
+=======
+
+ The following data is captured from wire with Human Interface Devices (HID)
+ payload
+
+::
+
+ CmdIntrIN: 00000001 00000d05 0001000f 00000001 00000001 00000200 00000040 ffffffff 00000000 00000004 00000000 00000000
+ CmdIntrOUT: 00000001 00000d06 0001000f 00000000 00000001 00000000 00000040 ffffffff 00000000 00000004 00000000 00000000
+ ffffffff860008a784ce5ae212376300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ RetIntrOut: 00000003 00000d06 00000000 00000000 00000000 00000000 00000040 ffffffff 00000000 00000000 00000000 00000000
+ RetIntrIn: 00000003 00000d05 00000000 00000000 00000000 00000000 00000040 ffffffff 00000000 00000000 00000000 00000000
+ ffffffff860011a784ce5ae2123763612891b1020100000400000000000000000000000000000000000000000000000000000000000000000000000000000000
CONFIG_USB_XHCI_TEGRA=m
CONFIG_USB_EHCI_HCD=y
CONFIG_USB_EHCI_HCD_STI=y
-CONFIG_USB_EHCI_TEGRA=y
CONFIG_USB_EHCI_EXYNOS=m
CONFIG_USB_EHCI_MV=m
CONFIG_USB_OHCI_HCD=y
CONFIG_USB_CHIPIDEA=y
CONFIG_USB_CHIPIDEA_UDC=y
CONFIG_USB_CHIPIDEA_HOST=y
+CONFIG_USB_CHIPIDEA_TEGRA=y
CONFIG_USB_ISP1760=y
CONFIG_USB_HSIC_USB3503=y
CONFIG_AB8500_USB=y
ranges;
status = "disabled";
- usb_dwc3_0: dwc3@38100000 {
+ usb_dwc3_0: usb@38100000 {
compatible = "snps,dwc3";
reg = <0x38100000 0x10000>;
clocks = <&clk IMX8MP_CLK_HSIO_AXI>,
ranges;
status = "disabled";
- usb_dwc3_1: dwc3@38200000 {
+ usb_dwc3_1: usb@38200000 {
compatible = "snps,dwc3";
reg = <0x38200000 0x10000>;
clocks = <&clk IMX8MP_CLK_HSIO_AXI>,
clocks = <&infracfg CLK_INFRA_UNIPRO_SCK>,
<&infracfg CLK_INFRA_USB>;
clock-names = "sys_ck", "ref_ck";
- mediatek,syscon-wakeup = <&pericfg 0x400 0>;
+ mediatek,syscon-wakeup = <&pericfg 0x420 101>;
#address-cells = <2>;
#size-cells = <2>;
ranges;
/* Protocol timeouts in ms */
#define TBNET_LOGIN_DELAY 4500
#define TBNET_LOGIN_TIMEOUT 500
-#define TBNET_LOGOUT_TIMEOUT 100
+#define TBNET_LOGOUT_TIMEOUT 1000
#define TBNET_RING_SIZE 256
-#define TBNET_LOCAL_PATH 0xf
#define TBNET_LOGIN_RETRIES 60
-#define TBNET_LOGOUT_RETRIES 5
+#define TBNET_LOGOUT_RETRIES 10
#define TBNET_MATCH_FRAGS_ID BIT(1)
+#define TBNET_64K_FRAMES BIT(2)
#define TBNET_MAX_MTU SZ_64K
#define TBNET_FRAME_SIZE SZ_4K
#define TBNET_MAX_PAYLOAD_SIZE \
* @login_sent: ThunderboltIP login message successfully sent
* @login_received: ThunderboltIP login message received from the remote
* host
- * @transmit_path: HopID the other end needs to use building the
- * opposite side path.
+ * @local_transmit_path: HopID we are using to send out packets
+ * @remote_transmit_path: HopID the other end is using to send packets to us
* @connection_lock: Lock serializing access to @login_sent,
* @login_received and @transmit_path.
* @login_retries: Number of login retries currently done
atomic_t command_id;
bool login_sent;
bool login_received;
- u32 transmit_path;
+ int local_transmit_path;
+ int remote_transmit_path;
struct mutex connection_lock;
int login_retries;
struct delayed_work login_work;
atomic_inc_return(&net->command_id));
request.proto_version = TBIP_LOGIN_PROTO_VERSION;
- request.transmit_path = TBNET_LOCAL_PATH;
+ request.transmit_path = net->local_transmit_path;
return tb_xdomain_request(xd, &request, sizeof(request),
TB_CFG_PKG_XDOMAIN_RESP, &reply,
mutex_lock(&net->connection_lock);
if (net->login_sent && net->login_received) {
- int retries = TBNET_LOGOUT_RETRIES;
+ int ret, retries = TBNET_LOGOUT_RETRIES;
while (send_logout && retries-- > 0) {
- int ret = tbnet_logout_request(net);
+ ret = tbnet_logout_request(net);
if (ret != -ETIMEDOUT)
break;
}
tbnet_free_buffers(&net->rx_ring);
tbnet_free_buffers(&net->tx_ring);
- if (tb_xdomain_disable_paths(net->xd))
+ ret = tb_xdomain_disable_paths(net->xd,
+ net->local_transmit_path,
+ net->rx_ring.ring->hop,
+ net->remote_transmit_path,
+ net->tx_ring.ring->hop);
+ if (ret)
netdev_warn(net->dev, "failed to disable DMA paths\n");
+
+ tb_xdomain_release_in_hopid(net->xd, net->remote_transmit_path);
+ net->remote_transmit_path = 0;
}
net->login_retries = 0;
if (!ret) {
mutex_lock(&net->connection_lock);
net->login_received = true;
- net->transmit_path = pkg->transmit_path;
+ net->remote_transmit_path = pkg->transmit_path;
/* If we reached the number of max retries or
* previous logout, schedule another round of
if (!connected)
return;
+ ret = tb_xdomain_alloc_in_hopid(net->xd, net->remote_transmit_path);
+ if (ret != net->remote_transmit_path) {
+ netdev_err(net->dev, "failed to allocate Rx HopID\n");
+ return;
+ }
+
/* Both logins successful so enable the high-speed DMA paths and
* start the network device queue.
*/
- ret = tb_xdomain_enable_paths(net->xd, TBNET_LOCAL_PATH,
+ ret = tb_xdomain_enable_paths(net->xd, net->local_transmit_path,
net->rx_ring.ring->hop,
- net->transmit_path,
+ net->remote_transmit_path,
net->tx_ring.ring->hop);
if (ret) {
netdev_err(net->dev, "failed to enable DMA paths\n");
err_stop_rings:
tb_ring_stop(net->rx_ring.ring);
tb_ring_stop(net->tx_ring.ring);
+ tb_xdomain_release_in_hopid(net->xd, net->remote_transmit_path);
}
static void tbnet_login_work(struct work_struct *work)
struct tb_xdomain *xd = net->xd;
u16 sof_mask, eof_mask;
struct tb_ring *ring;
+ int hopid;
netif_carrier_off(dev);
}
net->tx_ring.ring = ring;
+ hopid = tb_xdomain_alloc_out_hopid(xd, -1);
+ if (hopid < 0) {
+ netdev_err(dev, "failed to allocate Tx HopID\n");
+ tb_ring_free(net->tx_ring.ring);
+ net->tx_ring.ring = NULL;
+ return hopid;
+ }
+ net->local_transmit_path = hopid;
+
sof_mask = BIT(TBIP_PDF_FRAME_START);
eof_mask = BIT(TBIP_PDF_FRAME_END);
tb_ring_free(net->rx_ring.ring);
net->rx_ring.ring = NULL;
+
+ tb_xdomain_release_out_hopid(net->xd, net->local_transmit_path);
tb_ring_free(net->tx_ring.ring);
net->tx_ring.ring = NULL;
* the moment.
*/
tb_property_add_immediate(tbnet_dir, "prtcstns",
- TBNET_MATCH_FRAGS_ID);
+ TBNET_MATCH_FRAGS_ID | TBNET_64K_FRAMES);
ret = tb_register_property_dir("network", tbnet_dir);
if (ret) {
.properties = usb_connector_properties,
};
+static const struct software_node altmodes_node = {
+ .name = "altmodes",
+ .parent = &usb_connector_node,
+};
+
+static const struct property_entry dp_altmode_properties[] = {
+ PROPERTY_ENTRY_U32("svid", 0xff01),
+ PROPERTY_ENTRY_U32("vdo", 0x0c0086),
+ { }
+};
+
+static const struct software_node dp_altmode_node = {
+ .name = "displayport-altmode",
+ .parent = &altmodes_node,
+ .properties = dp_altmode_properties,
+};
+
static const struct software_node *node_group[] = {
&fusb302_node,
&max17047_node,
&pi3usb30532_node,
&displayport_node,
&usb_connector_node,
+ &altmodes_node,
+ &dp_altmode_node,
NULL
};
#define TB_CTL_RX_PKG_COUNT 10
-#define TB_CTL_RETRIES 4
+#define TB_CTL_RETRIES 1
/**
* struct tb_ctl - Thunderbolt control channel
* @request_queue_lock: Lock protecting @request_queue
* @request_queue: List of outstanding requests
* @running: Is the control channel running at the moment
+ * @timeout_msec: Default timeout for non-raw control messages
* @callback: Callback called when hotplug message is received
* @callback_data: Data passed to @callback
*/
struct list_head request_queue;
bool running;
+ int timeout_msec;
event_cb callback;
void *callback_data;
};
/**
* tb_ctl_alloc() - allocate a control channel
* @nhi: Pointer to NHI
+ * @timeout_msec: Default timeout used with non-raw control messages
* @cb: Callback called for plug events
* @cb_data: Data passed to @cb
*
*
* Return: Returns a pointer on success or NULL on failure.
*/
-struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, event_cb cb, void *cb_data)
+struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, int timeout_msec, event_cb cb,
+ void *cb_data)
{
int i;
struct tb_ctl *ctl = kzalloc(sizeof(*ctl), GFP_KERNEL);
if (!ctl)
return NULL;
ctl->nhi = nhi;
+ ctl->timeout_msec = timeout_msec;
ctl->callback = cb;
ctl->callback_data = cb_data;
* tb_cfg_reset() - send a reset packet and wait for a response
* @ctl: Control channel pointer
* @route: Router string for the router to send reset
- * @timeout_msec: Timeout in ms how long to wait for the response
*
* If the switch at route is incorrectly configured then we will not receive a
* reply (even though the switch will reset). The caller should check for
* -ETIMEDOUT and attempt to reconfigure the switch.
*/
-struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route,
- int timeout_msec)
+struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route)
{
struct cfg_reset_pkg request = { .header = tb_cfg_make_header(route) };
struct tb_cfg_result res = { 0 };
req->response_size = sizeof(reply);
req->response_type = TB_CFG_PKG_RESET;
- res = tb_cfg_request_sync(ctl, req, timeout_msec);
+ res = tb_cfg_request_sync(ctl, req, ctl->timeout_msec);
tb_cfg_request_put(req);
enum tb_cfg_space space, u32 offset, u32 length)
{
struct tb_cfg_result res = tb_cfg_read_raw(ctl, buffer, route, port,
- space, offset, length, TB_CFG_DEFAULT_TIMEOUT);
+ space, offset, length, ctl->timeout_msec);
switch (res.err) {
case 0:
/* Success */
enum tb_cfg_space space, u32 offset, u32 length)
{
struct tb_cfg_result res = tb_cfg_write_raw(ctl, buffer, route, port,
- space, offset, length, TB_CFG_DEFAULT_TIMEOUT);
+ space, offset, length, ctl->timeout_msec);
switch (res.err) {
case 0:
/* Success */
u32 dummy;
struct tb_cfg_result res = tb_cfg_read_raw(ctl, &dummy, route, 0,
TB_CFG_SWITCH, 0, 1,
- TB_CFG_DEFAULT_TIMEOUT);
+ ctl->timeout_msec);
if (res.err == 1)
return -EIO;
if (res.err)
typedef bool (*event_cb)(void *data, enum tb_cfg_pkg_type type,
const void *buf, size_t size);
-struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, event_cb cb, void *cb_data);
+struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, int timeout_msec, event_cb cb,
+ void *cb_data);
void tb_ctl_start(struct tb_ctl *ctl);
void tb_ctl_stop(struct tb_ctl *ctl);
void tb_ctl_free(struct tb_ctl *ctl);
/* configuration commands */
-#define TB_CFG_DEFAULT_TIMEOUT 5000 /* msec */
-
struct tb_cfg_result {
u64 response_route;
u32 response_port; /*
}
int tb_cfg_ack_plug(struct tb_ctl *ctl, u64 route, u32 port, bool unplug);
-struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route,
- int timeout_msec);
+struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route);
struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer,
u64 route, u32 port,
enum tb_cfg_space space, u32 offset,
return ret < 0 ? ret : count;
}
+static void cap_show_by_dw(struct seq_file *s, struct tb_switch *sw,
+ struct tb_port *port, unsigned int cap,
+ unsigned int offset, u8 cap_id, u8 vsec_id,
+ int dwords)
+{
+ int i, ret;
+ u32 data;
+
+ for (i = 0; i < dwords; i++) {
+ if (port)
+ ret = tb_port_read(port, &data, TB_CFG_PORT, cap + offset + i, 1);
+ else
+ ret = tb_sw_read(sw, &data, TB_CFG_SWITCH, cap + offset + i, 1);
+ if (ret) {
+ seq_printf(s, "0x%04x <not accessible>\n", cap + offset + i);
+ continue;
+ }
+
+ seq_printf(s, "0x%04x %4d 0x%02x 0x%02x 0x%08x\n", cap + offset + i,
+ offset + i, cap_id, vsec_id, data);
+ }
+}
+
static void cap_show(struct seq_file *s, struct tb_switch *sw,
struct tb_port *port, unsigned int cap, u8 cap_id,
u8 vsec_id, int length)
else
ret = tb_sw_read(sw, data, TB_CFG_SWITCH, cap + offset, dwords);
if (ret) {
- seq_printf(s, "0x%04x <not accessible>\n",
- cap + offset);
- if (dwords > 1)
- seq_printf(s, "0x%04x ...\n", cap + offset + 1);
+ cap_show_by_dw(s, sw, port, cap, offset, cap_id, vsec_id, length);
return;
}
} else {
length = header.extended_short.length;
vsec_id = header.extended_short.vsec_id;
- /*
- * Ice Lake and Tiger Lake do not implement the
- * full length of the capability, only first 32
- * dwords so hard-code it here.
- */
- if (!vsec_id &&
- (tb_switch_is_ice_lake(port->sw) ||
- tb_switch_is_tiger_lake(port->sw)))
- length = 32;
}
break;
#include <linux/sizes.h>
#include <linux/thunderbolt.h>
-#define DMA_TEST_HOPID 8
#define DMA_TEST_TX_RING_SIZE 64
#define DMA_TEST_RX_RING_SIZE 256
#define DMA_TEST_FRAME_SIZE SZ_4K
* @svc: XDomain service the driver is bound to
* @xd: XDomain the service belongs to
* @rx_ring: Software ring holding RX frames
+ * @rx_hopid: HopID used for receiving frames
* @tx_ring: Software ring holding TX frames
+ * @tx_hopid: HopID used for sending fames
* @packets_to_send: Number of packets to send
* @packets_to_receive: Number of packets to receive
* @packets_sent: Actual number of packets sent
const struct tb_service *svc;
struct tb_xdomain *xd;
struct tb_ring *rx_ring;
+ int rx_hopid;
struct tb_ring *tx_ring;
+ int tx_hopid;
unsigned int packets_to_send;
unsigned int packets_to_receive;
unsigned int packets_sent;
static void dma_test_free_rings(struct dma_test *dt)
{
if (dt->rx_ring) {
+ tb_xdomain_release_in_hopid(dt->xd, dt->rx_hopid);
tb_ring_free(dt->rx_ring);
dt->rx_ring = NULL;
}
if (dt->tx_ring) {
+ tb_xdomain_release_out_hopid(dt->xd, dt->tx_hopid);
tb_ring_free(dt->tx_ring);
dt->tx_ring = NULL;
}
dt->tx_ring = ring;
e2e_tx_hop = ring->hop;
+
+ ret = tb_xdomain_alloc_out_hopid(xd, -1);
+ if (ret < 0) {
+ dma_test_free_rings(dt);
+ return ret;
+ }
+
+ dt->tx_hopid = ret;
}
if (dt->packets_to_receive) {
}
dt->rx_ring = ring;
+
+ ret = tb_xdomain_alloc_in_hopid(xd, -1);
+ if (ret < 0) {
+ dma_test_free_rings(dt);
+ return ret;
+ }
+
+ dt->rx_hopid = ret;
}
- ret = tb_xdomain_enable_paths(dt->xd, DMA_TEST_HOPID,
+ ret = tb_xdomain_enable_paths(dt->xd, dt->tx_hopid,
dt->tx_ring ? dt->tx_ring->hop : 0,
- DMA_TEST_HOPID,
+ dt->rx_hopid,
dt->rx_ring ? dt->rx_ring->hop : 0);
if (ret) {
dma_test_free_rings(dt);
static void dma_test_stop_rings(struct dma_test *dt)
{
+ int ret;
+
if (dt->rx_ring)
tb_ring_stop(dt->rx_ring);
if (dt->tx_ring)
tb_ring_stop(dt->tx_ring);
- if (tb_xdomain_disable_paths(dt->xd))
+ ret = tb_xdomain_disable_paths(dt->xd, dt->tx_hopid,
+ dt->tx_ring ? dt->tx_ring->hop : 0,
+ dt->rx_hopid,
+ dt->rx_ring ? dt->rx_ring->hop : 0);
+ if (ret)
dev_warn(&dt->svc->dev, "failed to disable DMA paths\n");
dma_test_free_rings(dt);
.release = tb_domain_release,
};
+static bool tb_domain_event_cb(void *data, enum tb_cfg_pkg_type type,
+ const void *buf, size_t size)
+{
+ struct tb *tb = data;
+
+ if (!tb->cm_ops->handle_event) {
+ tb_warn(tb, "domain does not have event handler\n");
+ return true;
+ }
+
+ switch (type) {
+ case TB_CFG_PKG_XDOMAIN_REQ:
+ case TB_CFG_PKG_XDOMAIN_RESP:
+ if (tb_is_xdomain_enabled())
+ return tb_xdomain_handle_request(tb, type, buf, size);
+ break;
+
+ default:
+ tb->cm_ops->handle_event(tb, type, buf, size);
+ }
+
+ return true;
+}
+
/**
* tb_domain_alloc() - Allocate a domain
* @nhi: Pointer to the host controller
+ * @timeout_msec: Control channel timeout for non-raw messages
* @privsize: Size of the connection manager private data
*
* Allocates and initializes a new Thunderbolt domain. Connection
*
* Return: allocated domain structure on %NULL in case of error
*/
-struct tb *tb_domain_alloc(struct tb_nhi *nhi, size_t privsize)
+struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize)
{
struct tb *tb;
if (!tb->wq)
goto err_remove_ida;
+ tb->ctl = tb_ctl_alloc(nhi, timeout_msec, tb_domain_event_cb, tb);
+ if (!tb->ctl)
+ goto err_destroy_wq;
+
tb->dev.parent = &nhi->pdev->dev;
tb->dev.bus = &tb_bus_type;
tb->dev.type = &tb_domain_type;
return tb;
+err_destroy_wq:
+ destroy_workqueue(tb->wq);
err_remove_ida:
ida_simple_remove(&tb_domain_ida, tb->index);
err_free:
return NULL;
}
-static bool tb_domain_event_cb(void *data, enum tb_cfg_pkg_type type,
- const void *buf, size_t size)
-{
- struct tb *tb = data;
-
- if (!tb->cm_ops->handle_event) {
- tb_warn(tb, "domain does not have event handler\n");
- return true;
- }
-
- switch (type) {
- case TB_CFG_PKG_XDOMAIN_REQ:
- case TB_CFG_PKG_XDOMAIN_RESP:
- if (tb_is_xdomain_enabled())
- return tb_xdomain_handle_request(tb, type, buf, size);
- break;
-
- default:
- tb->cm_ops->handle_event(tb, type, buf, size);
- }
-
- return true;
-}
-
/**
* tb_domain_add() - Add domain to the system
* @tb: Domain to add
return -EINVAL;
mutex_lock(&tb->lock);
-
- tb->ctl = tb_ctl_alloc(tb->nhi, tb_domain_event_cb, tb);
- if (!tb->ctl) {
- ret = -ENOMEM;
- goto err_unlock;
- }
-
/*
* tb_schedule_hotplug_handler may be called as soon as the config
* channel is started. Thats why we have to hold the lock here.
device_del(&tb->dev);
err_ctl_stop:
tb_ctl_stop(tb->ctl);
-err_unlock:
mutex_unlock(&tb->lock);
return ret;
* tb_domain_approve_xdomain_paths() - Enable DMA paths for XDomain
* @tb: Domain enabling the DMA paths
* @xd: XDomain DMA paths are created to
+ * @transmit_path: HopID we are using to send out packets
+ * @transmit_ring: DMA ring used to send out packets
+ * @receive_path: HopID the other end is using to send packets to us
+ * @receive_ring: DMA ring used to receive packets from @receive_path
*
* Calls connection manager specific method to enable DMA paths to the
* XDomain in question.
* particular returns %-ENOTSUPP if the connection manager
* implementation does not support XDomains.
*/
-int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
+int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring)
{
if (!tb->cm_ops->approve_xdomain_paths)
return -ENOTSUPP;
- return tb->cm_ops->approve_xdomain_paths(tb, xd);
+ return tb->cm_ops->approve_xdomain_paths(tb, xd, transmit_path,
+ transmit_ring, receive_path, receive_ring);
}
/**
* tb_domain_disconnect_xdomain_paths() - Disable DMA paths for XDomain
* @tb: Domain disabling the DMA paths
* @xd: XDomain whose DMA paths are disconnected
+ * @transmit_path: HopID we are using to send out packets
+ * @transmit_ring: DMA ring used to send out packets
+ * @receive_path: HopID the other end is using to send packets to us
+ * @receive_ring: DMA ring used to receive packets from @receive_path
*
* Calls connection manager specific method to disconnect DMA paths to
* the XDomain in question.
* particular returns %-ENOTSUPP if the connection manager
* implementation does not support XDomains.
*/
-int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
+int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring)
{
if (!tb->cm_ops->disconnect_xdomain_paths)
return -ENOTSUPP;
- return tb->cm_ops->disconnect_xdomain_paths(tb, xd);
+ return tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path,
+ transmit_ring, receive_path, receive_ring);
}
static int disconnect_xdomain(struct device *dev, void *data)
xd = tb_to_xdomain(dev);
if (xd && xd->tb == tb)
- ret = tb_xdomain_disable_paths(xd);
+ ret = tb_xdomain_disable_all_paths(xd);
return ret;
}
u8 unknown4:2;
} __packed;
+/* USB4 product descriptor */
+struct tb_drom_entry_desc {
+ struct tb_drom_entry_header header;
+ u16 bcdUSBSpec;
+ u16 idVendor;
+ u16 idProduct;
+ u16 bcdProductFWRevision;
+ u32 TID;
+ u8 productHWRevision;
+};
/**
* tb_drom_read_uid_only() - Read UID directly from DROM
if (!sw->device_name)
return -ENOMEM;
break;
+ case 9: {
+ const struct tb_drom_entry_desc *desc =
+ (const struct tb_drom_entry_desc *)entry;
+
+ if (!sw->vendor && !sw->device) {
+ sw->vendor = desc->idVendor;
+ sw->device = desc->idProduct;
+ }
+ break;
+ }
}
return 0;
return tb_eeprom_read_n(sw, offset, val, count);
}
+static int tb_drom_parse(struct tb_switch *sw)
+{
+ const struct tb_drom_header *header =
+ (const struct tb_drom_header *)sw->drom;
+ u32 crc;
+
+ crc = tb_crc8((u8 *) &header->uid, 8);
+ if (crc != header->uid_crc8) {
+ tb_sw_warn(sw,
+ "DROM UID CRC8 mismatch (expected: %#x, got: %#x), aborting\n",
+ header->uid_crc8, crc);
+ return -EINVAL;
+ }
+ if (!sw->uid)
+ sw->uid = header->uid;
+ sw->vendor = header->vendor_id;
+ sw->device = header->model_id;
+
+ crc = tb_crc32(sw->drom + TB_DROM_DATA_START, header->data_len);
+ if (crc != header->data_crc32) {
+ tb_sw_warn(sw,
+ "DROM data CRC32 mismatch (expected: %#x, got: %#x), continuing\n",
+ header->data_crc32, crc);
+ }
+
+ return tb_drom_parse_entries(sw);
+}
+
+static int usb4_drom_parse(struct tb_switch *sw)
+{
+ const struct tb_drom_header *header =
+ (const struct tb_drom_header *)sw->drom;
+ u32 crc;
+
+ crc = tb_crc32(sw->drom + TB_DROM_DATA_START, header->data_len);
+ if (crc != header->data_crc32) {
+ tb_sw_warn(sw,
+ "DROM data CRC32 mismatch (expected: %#x, got: %#x), aborting\n",
+ header->data_crc32, crc);
+ return -EINVAL;
+ }
+
+ return tb_drom_parse_entries(sw);
+}
+
/**
* tb_drom_read() - Copy DROM to sw->drom and parse it
* @sw: Router whose DROM to read and parse
int tb_drom_read(struct tb_switch *sw)
{
u16 size;
- u32 crc;
struct tb_drom_header *header;
int res, retries = 1;
goto err;
}
- crc = tb_crc8((u8 *) &header->uid, 8);
- if (crc != header->uid_crc8) {
- tb_sw_warn(sw,
- "drom uid crc8 mismatch (expected: %#x, got: %#x), aborting\n",
- header->uid_crc8, crc);
- goto err;
- }
- if (!sw->uid)
- sw->uid = header->uid;
- sw->vendor = header->vendor_id;
- sw->device = header->model_id;
- tb_check_quirks(sw);
+ tb_sw_dbg(sw, "DROM version: %d\n", header->device_rom_revision);
- crc = tb_crc32(sw->drom + TB_DROM_DATA_START, header->data_len);
- if (crc != header->data_crc32) {
- tb_sw_warn(sw,
- "drom data crc32 mismatch (expected: %#x, got: %#x), continuing\n",
- header->data_crc32, crc);
+ switch (header->device_rom_revision) {
+ case 3:
+ res = usb4_drom_parse(sw);
+ break;
+ default:
+ tb_sw_warn(sw, "DROM device_rom_revision %#x unknown\n",
+ header->device_rom_revision);
+ fallthrough;
+ case 1:
+ res = tb_drom_parse(sw);
+ break;
}
- if (header->device_rom_revision > 2)
- tb_sw_warn(sw, "drom device_rom_revision %#x unknown\n",
- header->device_rom_revision);
-
- res = tb_drom_parse_entries(sw);
/* If the DROM parsing fails, wait a moment and retry once */
if (res == -EILSEQ && retries--) {
tb_sw_warn(sw, "parsing DROM failed, retrying\n");
goto parse;
}
- return res;
+ if (!res)
+ return 0;
+
err:
kfree(sw->drom);
sw->drom = NULL;
return -EIO;
-
}
return 0;
}
-static int icm_fr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
+static int icm_fr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring)
{
struct icm_fr_pkg_approve_xdomain_response reply;
struct icm_fr_pkg_approve_xdomain request;
request.link_info = xd->depth << ICM_LINK_INFO_DEPTH_SHIFT | xd->link;
memcpy(&request.remote_uuid, xd->remote_uuid, sizeof(*xd->remote_uuid));
- request.transmit_path = xd->transmit_path;
- request.transmit_ring = xd->transmit_ring;
- request.receive_path = xd->receive_path;
- request.receive_ring = xd->receive_ring;
+ request.transmit_path = transmit_path;
+ request.transmit_ring = transmit_ring;
+ request.receive_path = receive_path;
+ request.receive_ring = receive_ring;
memset(&reply, 0, sizeof(reply));
ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply),
return 0;
}
-static int icm_fr_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
+static int icm_fr_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring)
{
u8 phy_port;
u8 cmd;
return 0;
}
-static int icm_tr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
+static int icm_tr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring)
{
struct icm_tr_pkg_approve_xdomain_response reply;
struct icm_tr_pkg_approve_xdomain request;
request.hdr.code = ICM_APPROVE_XDOMAIN;
request.route_hi = upper_32_bits(xd->route);
request.route_lo = lower_32_bits(xd->route);
- request.transmit_path = xd->transmit_path;
- request.transmit_ring = xd->transmit_ring;
- request.receive_path = xd->receive_path;
- request.receive_ring = xd->receive_ring;
+ request.transmit_path = transmit_path;
+ request.transmit_ring = transmit_ring;
+ request.receive_path = receive_path;
+ request.receive_ring = receive_ring;
memcpy(&request.remote_uuid, xd->remote_uuid, sizeof(*xd->remote_uuid));
memset(&reply, 0, sizeof(reply));
return 0;
}
-static int icm_tr_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
+static int icm_tr_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring)
{
int ret;
struct icm *icm;
struct tb *tb;
- tb = tb_domain_alloc(nhi, sizeof(struct icm));
+ tb = tb_domain_alloc(nhi, ICM_TIMEOUT, sizeof(struct icm));
if (!tb)
return NULL;
return ret < 0 ? ret : 0;
}
+/**
+ * tb_property_copy_dir() - Take a deep copy of directory
+ * @dir: Directory to copy
+ *
+ * This function takes a deep copy of @dir and returns back the copy. In
+ * case of error returns %NULL. The resulting directory needs to be
+ * released by calling tb_property_free_dir().
+ */
+struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir)
+{
+ struct tb_property *property, *p = NULL;
+ struct tb_property_dir *d;
+
+ if (!dir)
+ return NULL;
+
+ d = tb_property_create_dir(dir->uuid);
+ if (!d)
+ return NULL;
+
+ list_for_each_entry(property, &dir->properties, list) {
+ struct tb_property *p;
+
+ p = tb_property_alloc(property->key, property->type);
+ if (!p)
+ goto err_free;
+
+ p->length = property->length;
+
+ switch (property->type) {
+ case TB_PROPERTY_TYPE_DIRECTORY:
+ p->value.dir = tb_property_copy_dir(property->value.dir);
+ if (!p->value.dir)
+ goto err_free;
+ break;
+
+ case TB_PROPERTY_TYPE_DATA:
+ p->value.data = kmemdup(property->value.data,
+ property->length * 4,
+ GFP_KERNEL);
+ if (!p->value.data)
+ goto err_free;
+ break;
+
+ case TB_PROPERTY_TYPE_TEXT:
+ p->value.text = kzalloc(p->length * 4, GFP_KERNEL);
+ if (!p->value.text)
+ goto err_free;
+ strcpy(p->value.text, property->value.text);
+ break;
+
+ case TB_PROPERTY_TYPE_VALUE:
+ p->value.immediate = property->value.immediate;
+ break;
+
+ default:
+ break;
+ }
+
+ list_add_tail(&p->list, &d->properties);
+ }
+
+ return d;
+
+err_free:
+ kfree(p);
+ tb_property_free_dir(d);
+
+ return NULL;
+}
+
/**
* tb_property_add_immediate() - Add immediate property to directory
* @parent: Directory to add the property
TB_CFG_PORT, ADP_CS_4, 1);
}
-/**
- * tb_port_set_initial_credits() - Set initial port link credits allocated
- * @port: Port to set the initial credits
- * @credits: Number of credits to to allocate
- *
- * Set initial credits value to be used for ingress shared buffering.
- */
-int tb_port_set_initial_credits(struct tb_port *port, u32 credits)
-{
- u32 data;
- int ret;
-
- ret = tb_port_read(port, &data, TB_CFG_PORT, ADP_CS_5, 1);
- if (ret)
- return ret;
-
- data &= ~ADP_CS_5_LCA_MASK;
- data |= (credits << ADP_CS_5_LCA_SHIFT) & ADP_CS_5_LCA_MASK;
-
- return tb_port_write(port, &data, TB_CFG_PORT, ADP_CS_5, 1);
-}
-
/**
* tb_port_clear_counter() - clear a counter in TB_CFG_COUNTER
* @port: Port whose counters to clear
TB_CFG_SWITCH, 2, 2);
if (res.err)
return res.err;
- res = tb_cfg_reset(sw->tb->ctl, tb_route(sw), TB_CFG_DEFAULT_TIMEOUT);
+ res = tb_cfg_reset(sw->tb->ctl, tb_route(sw));
if (res.err > 0)
return -EIO;
return res.err;
NULL,
};
+static bool has_port(const struct tb_switch *sw, enum tb_port_type type)
+{
+ const struct tb_port *port;
+
+ tb_switch_for_each_port(sw, port) {
+ if (!port->disabled && port->config.type == type)
+ return true;
+ }
+
+ return false;
+}
+
static umode_t switch_attr_is_visible(struct kobject *kobj,
struct attribute *attr, int n)
{
if (attr == &dev_attr_authorized.attr) {
if (sw->tb->security_level == TB_SECURITY_NOPCIE ||
- sw->tb->security_level == TB_SECURITY_DPONLY)
+ sw->tb->security_level == TB_SECURITY_DPONLY ||
+ !has_port(sw, TB_TYPE_PCIE_UP))
return 0;
} else if (attr == &dev_attr_device.attr) {
if (!sw->device)
kfree(sw);
}
+static int tb_switch_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ struct tb_switch *sw = tb_to_switch(dev);
+ const char *type;
+
+ if (sw->config.thunderbolt_version == USB4_VERSION_1_0) {
+ if (add_uevent_var(env, "USB4_VERSION=1.0"))
+ return -ENOMEM;
+ }
+
+ if (!tb_route(sw)) {
+ type = "host";
+ } else {
+ const struct tb_port *port;
+ bool hub = false;
+
+ /* Device is hub if it has any downstream ports */
+ tb_switch_for_each_port(sw, port) {
+ if (!port->disabled && !tb_is_upstream_port(port) &&
+ tb_port_is_null(port)) {
+ hub = true;
+ break;
+ }
+ }
+
+ type = hub ? "hub" : "device";
+ }
+
+ if (add_uevent_var(env, "USB4_TYPE=%s", type))
+ return -ENOMEM;
+ return 0;
+}
+
/*
* Currently only need to provide the callbacks. Everything else is handled
* in the connection manager.
struct device_type tb_switch_type = {
.name = "thunderbolt_device",
.release = tb_switch_release,
+ .uevent = tb_switch_uevent,
.pm = &tb_switch_pm_ops,
};
}
tb_sw_dbg(sw, "uid: %#llx\n", sw->uid);
+ tb_check_quirks(sw);
+
ret = tb_switch_set_uuid(sw);
if (ret) {
dev_err(&sw->dev, "failed to set UUID\n");
#include "tb_regs.h"
#include "tunnel.h"
+#define TB_TIMEOUT 100 /* ms */
+
/**
* struct tb_cm - Simple Thunderbolt connection manager
* @tunnel_list: List of active tunnels
return 0;
}
-static int tb_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
+static int tb_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring)
{
struct tb_cm *tcm = tb_priv(tb);
struct tb_port *nhi_port, *dst_port;
nhi_port = tb_switch_find_port(tb->root_switch, TB_TYPE_NHI);
mutex_lock(&tb->lock);
- tunnel = tb_tunnel_alloc_dma(tb, nhi_port, dst_port, xd->transmit_ring,
- xd->transmit_path, xd->receive_ring,
- xd->receive_path);
+ tunnel = tb_tunnel_alloc_dma(tb, nhi_port, dst_port, transmit_path,
+ transmit_ring, receive_path, receive_ring);
if (!tunnel) {
mutex_unlock(&tb->lock);
return -ENOMEM;
return 0;
}
-static void __tb_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
+static void __tb_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring)
{
- struct tb_port *dst_port;
- struct tb_tunnel *tunnel;
+ struct tb_cm *tcm = tb_priv(tb);
+ struct tb_port *nhi_port, *dst_port;
+ struct tb_tunnel *tunnel, *n;
struct tb_switch *sw;
sw = tb_to_switch(xd->dev.parent);
dst_port = tb_port_at(xd->route, sw);
+ nhi_port = tb_switch_find_port(tb->root_switch, TB_TYPE_NHI);
- /*
- * It is possible that the tunnel was already teared down (in
- * case of cable disconnect) so it is fine if we cannot find it
- * here anymore.
- */
- tunnel = tb_find_tunnel(tb, TB_TUNNEL_DMA, NULL, dst_port);
- tb_deactivate_and_free_tunnel(tunnel);
+ list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) {
+ if (!tb_tunnel_is_dma(tunnel))
+ continue;
+ if (tunnel->src_port != nhi_port || tunnel->dst_port != dst_port)
+ continue;
+
+ if (tb_tunnel_match_dma(tunnel, transmit_path, transmit_ring,
+ receive_path, receive_ring))
+ tb_deactivate_and_free_tunnel(tunnel);
+ }
}
-static int tb_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
+static int tb_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring)
{
if (!xd->is_unplugged) {
mutex_lock(&tb->lock);
- __tb_disconnect_xdomain_paths(tb, xd);
+ __tb_disconnect_xdomain_paths(tb, xd, transmit_path,
+ transmit_ring, receive_path,
+ receive_ring);
mutex_unlock(&tb->lock);
}
return 0;
* tb_xdomain_remove() so setting XDomain as
* unplugged here prevents deadlock if they call
* tb_xdomain_disable_paths(). We will tear down
- * the path below.
+ * all the tunnels below.
*/
xd->is_unplugged = true;
tb_xdomain_remove(xd);
port->xdomain = NULL;
- __tb_disconnect_xdomain_paths(tb, xd);
+ __tb_disconnect_xdomain_paths(tb, xd, -1, -1, -1, -1);
tb_xdomain_put(xd);
tb_port_unconfigure_xdomain(port);
} else if (tb_port_is_dpout(port) || tb_port_is_dpin(port)) {
struct tb_cm *tcm;
struct tb *tb;
- tb = tb_domain_alloc(nhi, sizeof(*tcm));
+ tb = tb_domain_alloc(nhi, TB_TIMEOUT, sizeof(*tcm));
if (!tb)
return NULL;
int (*challenge_switch_key)(struct tb *tb, struct tb_switch *sw,
const u8 *challenge, u8 *response);
int (*disconnect_pcie_paths)(struct tb *tb);
- int (*approve_xdomain_paths)(struct tb *tb, struct tb_xdomain *xd);
- int (*disconnect_xdomain_paths)(struct tb *tb, struct tb_xdomain *xd);
+ int (*approve_xdomain_paths)(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring);
+ int (*disconnect_xdomain_paths)(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring);
int (*usb4_switch_op)(struct tb_switch *sw, u16 opcode, u32 *metadata,
u8 *status, const void *tx_data, size_t tx_data_len,
void *rx_data, size_t rx_data_len);
int tb_xdomain_init(void);
void tb_xdomain_exit(void);
-struct tb *tb_domain_alloc(struct tb_nhi *nhi, size_t privsize);
+struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize);
int tb_domain_add(struct tb *tb);
void tb_domain_remove(struct tb *tb);
int tb_domain_suspend_noirq(struct tb *tb);
int tb_domain_approve_switch_key(struct tb *tb, struct tb_switch *sw);
int tb_domain_challenge_switch_key(struct tb *tb, struct tb_switch *sw);
int tb_domain_disconnect_pcie_paths(struct tb *tb);
-int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd);
-int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd);
+int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring);
+int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring);
int tb_domain_disconnect_all_paths(struct tb *tb);
static inline struct tb *tb_domain_get(struct tb *tb)
return false;
}
-static inline bool tb_switch_is_ice_lake(const struct tb_switch *sw)
-{
- if (sw->config.vendor_id == PCI_VENDOR_ID_INTEL) {
- switch (sw->config.device_id) {
- case PCI_DEVICE_ID_INTEL_ICL_NHI0:
- case PCI_DEVICE_ID_INTEL_ICL_NHI1:
- return true;
- }
- }
- return false;
-}
-
-static inline bool tb_switch_is_tiger_lake(const struct tb_switch *sw)
-{
- if (sw->config.vendor_id == PCI_VENDOR_ID_INTEL) {
- switch (sw->config.device_id) {
- case PCI_DEVICE_ID_INTEL_TGL_NHI0:
- case PCI_DEVICE_ID_INTEL_TGL_NHI1:
- case PCI_DEVICE_ID_INTEL_TGL_H_NHI0:
- case PCI_DEVICE_ID_INTEL_TGL_H_NHI1:
- return true;
- }
- }
- return false;
-}
-
/**
* tb_switch_is_usb4() - Is the switch USB4 compliant
* @sw: Switch to check
int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged);
int tb_port_add_nfc_credits(struct tb_port *port, int credits);
-int tb_port_set_initial_credits(struct tb_port *port, u32 credits);
int tb_port_clear_counter(struct tb_port *port, int counter);
int tb_port_unlock(struct tb_port *port);
int tb_port_enable(struct tb_port *port);
sw->ports[7].config.type = TB_TYPE_NHI;
sw->ports[7].config.max_in_hop_id = 11;
sw->ports[7].config.max_out_hop_id = 11;
+ sw->ports[7].config.nfc_credits = 0x41800000;
sw->ports[8].config.type = TB_TYPE_PCIE_DOWN;
sw->ports[8].config.max_in_hop_id = 8;
tb_tunnel_free(dp_tunnel);
}
+static void tb_test_tunnel_dma(struct kunit *test)
+{
+ struct tb_port *nhi, *port;
+ struct tb_tunnel *tunnel;
+ struct tb_switch *host;
+
+ /*
+ * Create DMA tunnel from NHI to port 1 and back.
+ *
+ * [Host 1]
+ * 1 ^ In HopID 1 -> Out HopID 8
+ * |
+ * v In HopID 8 -> Out HopID 1
+ * ............ Domain border
+ * |
+ * [Host 2]
+ */
+ host = alloc_host(test);
+ nhi = &host->ports[7];
+ port = &host->ports[1];
+
+ tunnel = tb_tunnel_alloc_dma(NULL, nhi, port, 8, 1, 8, 1);
+ KUNIT_ASSERT_TRUE(test, tunnel != NULL);
+ KUNIT_EXPECT_EQ(test, tunnel->type, (enum tb_tunnel_type)TB_TUNNEL_DMA);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, nhi);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->dst_port, port);
+ KUNIT_ASSERT_EQ(test, tunnel->npaths, (size_t)2);
+ /* RX path */
+ KUNIT_ASSERT_EQ(test, tunnel->paths[0]->path_length, 1);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[0].in_port, port);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[0]->hops[0].in_hop_index, 8);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[0].out_port, nhi);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[0]->hops[0].next_hop_index, 1);
+ /* TX path */
+ KUNIT_ASSERT_EQ(test, tunnel->paths[1]->path_length, 1);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[1]->hops[0].in_port, nhi);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[1]->hops[0].in_hop_index, 1);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[1]->hops[0].out_port, port);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[1]->hops[0].next_hop_index, 8);
+
+ tb_tunnel_free(tunnel);
+}
+
+static void tb_test_tunnel_dma_rx(struct kunit *test)
+{
+ struct tb_port *nhi, *port;
+ struct tb_tunnel *tunnel;
+ struct tb_switch *host;
+
+ /*
+ * Create DMA RX tunnel from port 1 to NHI.
+ *
+ * [Host 1]
+ * 1 ^
+ * |
+ * | In HopID 15 -> Out HopID 2
+ * ............ Domain border
+ * |
+ * [Host 2]
+ */
+ host = alloc_host(test);
+ nhi = &host->ports[7];
+ port = &host->ports[1];
+
+ tunnel = tb_tunnel_alloc_dma(NULL, nhi, port, -1, -1, 15, 2);
+ KUNIT_ASSERT_TRUE(test, tunnel != NULL);
+ KUNIT_EXPECT_EQ(test, tunnel->type, (enum tb_tunnel_type)TB_TUNNEL_DMA);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, nhi);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->dst_port, port);
+ KUNIT_ASSERT_EQ(test, tunnel->npaths, (size_t)1);
+ /* RX path */
+ KUNIT_ASSERT_EQ(test, tunnel->paths[0]->path_length, 1);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[0].in_port, port);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[0]->hops[0].in_hop_index, 15);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[0].out_port, nhi);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[0]->hops[0].next_hop_index, 2);
+
+ tb_tunnel_free(tunnel);
+}
+
+static void tb_test_tunnel_dma_tx(struct kunit *test)
+{
+ struct tb_port *nhi, *port;
+ struct tb_tunnel *tunnel;
+ struct tb_switch *host;
+
+ /*
+ * Create DMA TX tunnel from NHI to port 1.
+ *
+ * [Host 1]
+ * 1 | In HopID 2 -> Out HopID 15
+ * |
+ * v
+ * ............ Domain border
+ * |
+ * [Host 2]
+ */
+ host = alloc_host(test);
+ nhi = &host->ports[7];
+ port = &host->ports[1];
+
+ tunnel = tb_tunnel_alloc_dma(NULL, nhi, port, 15, 2, -1, -1);
+ KUNIT_ASSERT_TRUE(test, tunnel != NULL);
+ KUNIT_EXPECT_EQ(test, tunnel->type, (enum tb_tunnel_type)TB_TUNNEL_DMA);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, nhi);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->dst_port, port);
+ KUNIT_ASSERT_EQ(test, tunnel->npaths, (size_t)1);
+ /* TX path */
+ KUNIT_ASSERT_EQ(test, tunnel->paths[0]->path_length, 1);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[0].in_port, nhi);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[0]->hops[0].in_hop_index, 2);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[0].out_port, port);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[0]->hops[0].next_hop_index, 15);
+
+ tb_tunnel_free(tunnel);
+}
+
+static void tb_test_tunnel_dma_chain(struct kunit *test)
+{
+ struct tb_switch *host, *dev1, *dev2;
+ struct tb_port *nhi, *port;
+ struct tb_tunnel *tunnel;
+
+ /*
+ * Create DMA tunnel from NHI to Device #2 port 3 and back.
+ *
+ * [Host 1]
+ * 1 ^ In HopID 1 -> Out HopID x
+ * |
+ * 1 | In HopID x -> Out HopID 1
+ * [Device #1]
+ * 7 \
+ * 1 \
+ * [Device #2]
+ * 3 | In HopID x -> Out HopID 8
+ * |
+ * v In HopID 8 -> Out HopID x
+ * ............ Domain border
+ * |
+ * [Host 2]
+ */
+ host = alloc_host(test);
+ dev1 = alloc_dev_default(test, host, 0x1, true);
+ dev2 = alloc_dev_default(test, dev1, 0x701, true);
+
+ nhi = &host->ports[7];
+ port = &dev2->ports[3];
+ tunnel = tb_tunnel_alloc_dma(NULL, nhi, port, 8, 1, 8, 1);
+ KUNIT_ASSERT_TRUE(test, tunnel != NULL);
+ KUNIT_EXPECT_EQ(test, tunnel->type, (enum tb_tunnel_type)TB_TUNNEL_DMA);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, nhi);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->dst_port, port);
+ KUNIT_ASSERT_EQ(test, tunnel->npaths, (size_t)2);
+ /* RX path */
+ KUNIT_ASSERT_EQ(test, tunnel->paths[0]->path_length, 3);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[0].in_port, port);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[0]->hops[0].in_hop_index, 8);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[0].out_port,
+ &dev2->ports[1]);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[1].in_port,
+ &dev1->ports[7]);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[1].out_port,
+ &dev1->ports[1]);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[2].in_port,
+ &host->ports[1]);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[2].out_port, nhi);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[0]->hops[2].next_hop_index, 1);
+ /* TX path */
+ KUNIT_ASSERT_EQ(test, tunnel->paths[1]->path_length, 3);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[1]->hops[0].in_port, nhi);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[1]->hops[0].in_hop_index, 1);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[1]->hops[1].in_port,
+ &dev1->ports[1]);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[1]->hops[1].out_port,
+ &dev1->ports[7]);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[1]->hops[2].in_port,
+ &dev2->ports[1]);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[1]->hops[2].out_port, port);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[1]->hops[2].next_hop_index, 8);
+
+ tb_tunnel_free(tunnel);
+}
+
+static void tb_test_tunnel_dma_match(struct kunit *test)
+{
+ struct tb_port *nhi, *port;
+ struct tb_tunnel *tunnel;
+ struct tb_switch *host;
+
+ host = alloc_host(test);
+ nhi = &host->ports[7];
+ port = &host->ports[1];
+
+ tunnel = tb_tunnel_alloc_dma(NULL, nhi, port, 15, 1, 15, 1);
+ KUNIT_ASSERT_TRUE(test, tunnel != NULL);
+
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, 15, 1, 15, 1));
+ KUNIT_ASSERT_FALSE(test, tb_tunnel_match_dma(tunnel, 8, 1, 15, 1));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, -1, -1, 15, 1));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, 15, 1, -1, -1));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, 15, -1, -1, -1));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, -1, 1, -1, -1));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, -1, -1, 15, -1));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, -1, -1, -1, 1));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, -1, -1, -1, -1));
+ KUNIT_ASSERT_FALSE(test, tb_tunnel_match_dma(tunnel, 8, -1, 8, -1));
+
+ tb_tunnel_free(tunnel);
+
+ tunnel = tb_tunnel_alloc_dma(NULL, nhi, port, 15, 1, -1, -1);
+ KUNIT_ASSERT_TRUE(test, tunnel != NULL);
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, 15, 1, -1, -1));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, 15, -1, -1, -1));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, -1, 1, -1, -1));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, -1, -1, -1, -1));
+ KUNIT_ASSERT_FALSE(test, tb_tunnel_match_dma(tunnel, 15, 1, 15, 1));
+ KUNIT_ASSERT_FALSE(test, tb_tunnel_match_dma(tunnel, -1, -1, 15, 1));
+ KUNIT_ASSERT_FALSE(test, tb_tunnel_match_dma(tunnel, 15, 11, -1, -1));
+
+ tb_tunnel_free(tunnel);
+
+ tunnel = tb_tunnel_alloc_dma(NULL, nhi, port, -1, -1, 15, 11);
+ KUNIT_ASSERT_TRUE(test, tunnel != NULL);
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, -1, -1, 15, 11));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, -1, -1, 15, -1));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, -1, -1, -1, 11));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, -1, -1, -1, -1));
+ KUNIT_ASSERT_FALSE(test, tb_tunnel_match_dma(tunnel, -1, -1, 15, 1));
+ KUNIT_ASSERT_FALSE(test, tb_tunnel_match_dma(tunnel, -1, -1, 10, 11));
+ KUNIT_ASSERT_FALSE(test, tb_tunnel_match_dma(tunnel, 15, 11, -1, -1));
+
+ tb_tunnel_free(tunnel);
+}
+
+static const u32 root_directory[] = {
+ 0x55584401, /* "UXD" v1 */
+ 0x00000018, /* Root directory length */
+ 0x76656e64, /* "vend" */
+ 0x6f726964, /* "orid" */
+ 0x76000001, /* "v" R 1 */
+ 0x00000a27, /* Immediate value, ! Vendor ID */
+ 0x76656e64, /* "vend" */
+ 0x6f726964, /* "orid" */
+ 0x74000003, /* "t" R 3 */
+ 0x0000001a, /* Text leaf offset, (“Apple Inc.”) */
+ 0x64657669, /* "devi" */
+ 0x63656964, /* "ceid" */
+ 0x76000001, /* "v" R 1 */
+ 0x0000000a, /* Immediate value, ! Device ID */
+ 0x64657669, /* "devi" */
+ 0x63656964, /* "ceid" */
+ 0x74000003, /* "t" R 3 */
+ 0x0000001d, /* Text leaf offset, (“Macintosh”) */
+ 0x64657669, /* "devi" */
+ 0x63657276, /* "cerv" */
+ 0x76000001, /* "v" R 1 */
+ 0x80000100, /* Immediate value, Device Revision */
+ 0x6e657477, /* "netw" */
+ 0x6f726b00, /* "ork" */
+ 0x44000014, /* "D" R 20 */
+ 0x00000021, /* Directory data offset, (Network Directory) */
+ 0x4170706c, /* "Appl" */
+ 0x6520496e, /* "e In" */
+ 0x632e0000, /* "c." ! */
+ 0x4d616369, /* "Maci" */
+ 0x6e746f73, /* "ntos" */
+ 0x68000000, /* "h" */
+ 0x00000000, /* padding */
+ 0xca8961c6, /* Directory UUID, Network Directory */
+ 0x9541ce1c, /* Directory UUID, Network Directory */
+ 0x5949b8bd, /* Directory UUID, Network Directory */
+ 0x4f5a5f2e, /* Directory UUID, Network Directory */
+ 0x70727463, /* "prtc" */
+ 0x69640000, /* "id" */
+ 0x76000001, /* "v" R 1 */
+ 0x00000001, /* Immediate value, Network Protocol ID */
+ 0x70727463, /* "prtc" */
+ 0x76657273, /* "vers" */
+ 0x76000001, /* "v" R 1 */
+ 0x00000001, /* Immediate value, Network Protocol Version */
+ 0x70727463, /* "prtc" */
+ 0x72657673, /* "revs" */
+ 0x76000001, /* "v" R 1 */
+ 0x00000001, /* Immediate value, Network Protocol Revision */
+ 0x70727463, /* "prtc" */
+ 0x73746e73, /* "stns" */
+ 0x76000001, /* "v" R 1 */
+ 0x00000000, /* Immediate value, Network Protocol Settings */
+};
+
+static const uuid_t network_dir_uuid =
+ UUID_INIT(0xc66189ca, 0x1cce, 0x4195,
+ 0xbd, 0xb8, 0x49, 0x59, 0x2e, 0x5f, 0x5a, 0x4f);
+
+static void tb_test_property_parse(struct kunit *test)
+{
+ struct tb_property_dir *dir, *network_dir;
+ struct tb_property *p;
+
+ dir = tb_property_parse_dir(root_directory, ARRAY_SIZE(root_directory));
+ KUNIT_ASSERT_TRUE(test, dir != NULL);
+
+ p = tb_property_find(dir, "foo", TB_PROPERTY_TYPE_TEXT);
+ KUNIT_ASSERT_TRUE(test, !p);
+
+ p = tb_property_find(dir, "vendorid", TB_PROPERTY_TYPE_TEXT);
+ KUNIT_ASSERT_TRUE(test, p != NULL);
+ KUNIT_EXPECT_STREQ(test, p->value.text, "Apple Inc.");
+
+ p = tb_property_find(dir, "vendorid", TB_PROPERTY_TYPE_VALUE);
+ KUNIT_ASSERT_TRUE(test, p != NULL);
+ KUNIT_EXPECT_EQ(test, p->value.immediate, (u32)0xa27);
+
+ p = tb_property_find(dir, "deviceid", TB_PROPERTY_TYPE_TEXT);
+ KUNIT_ASSERT_TRUE(test, p != NULL);
+ KUNIT_EXPECT_STREQ(test, p->value.text, "Macintosh");
+
+ p = tb_property_find(dir, "deviceid", TB_PROPERTY_TYPE_VALUE);
+ KUNIT_ASSERT_TRUE(test, p != NULL);
+ KUNIT_EXPECT_EQ(test, p->value.immediate, (u32)0xa);
+
+ p = tb_property_find(dir, "missing", TB_PROPERTY_TYPE_DIRECTORY);
+ KUNIT_ASSERT_TRUE(test, !p);
+
+ p = tb_property_find(dir, "network", TB_PROPERTY_TYPE_DIRECTORY);
+ KUNIT_ASSERT_TRUE(test, p != NULL);
+
+ network_dir = p->value.dir;
+ KUNIT_EXPECT_TRUE(test, uuid_equal(network_dir->uuid, &network_dir_uuid));
+
+ p = tb_property_find(network_dir, "prtcid", TB_PROPERTY_TYPE_VALUE);
+ KUNIT_ASSERT_TRUE(test, p != NULL);
+ KUNIT_EXPECT_EQ(test, p->value.immediate, (u32)0x1);
+
+ p = tb_property_find(network_dir, "prtcvers", TB_PROPERTY_TYPE_VALUE);
+ KUNIT_ASSERT_TRUE(test, p != NULL);
+ KUNIT_EXPECT_EQ(test, p->value.immediate, (u32)0x1);
+
+ p = tb_property_find(network_dir, "prtcrevs", TB_PROPERTY_TYPE_VALUE);
+ KUNIT_ASSERT_TRUE(test, p != NULL);
+ KUNIT_EXPECT_EQ(test, p->value.immediate, (u32)0x1);
+
+ p = tb_property_find(network_dir, "prtcstns", TB_PROPERTY_TYPE_VALUE);
+ KUNIT_ASSERT_TRUE(test, p != NULL);
+ KUNIT_EXPECT_EQ(test, p->value.immediate, (u32)0x0);
+
+ p = tb_property_find(network_dir, "deviceid", TB_PROPERTY_TYPE_VALUE);
+ KUNIT_EXPECT_TRUE(test, !p);
+ p = tb_property_find(network_dir, "deviceid", TB_PROPERTY_TYPE_TEXT);
+ KUNIT_EXPECT_TRUE(test, !p);
+
+ tb_property_free_dir(dir);
+}
+
+static void tb_test_property_format(struct kunit *test)
+{
+ struct tb_property_dir *dir;
+ ssize_t block_len;
+ u32 *block;
+ int ret, i;
+
+ dir = tb_property_parse_dir(root_directory, ARRAY_SIZE(root_directory));
+ KUNIT_ASSERT_TRUE(test, dir != NULL);
+
+ ret = tb_property_format_dir(dir, NULL, 0);
+ KUNIT_ASSERT_EQ(test, ret, (int)ARRAY_SIZE(root_directory));
+
+ block_len = ret;
+
+ block = kunit_kzalloc(test, block_len * sizeof(u32), GFP_KERNEL);
+ KUNIT_ASSERT_TRUE(test, block != NULL);
+
+ ret = tb_property_format_dir(dir, block, block_len);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+
+ for (i = 0; i < ARRAY_SIZE(root_directory); i++)
+ KUNIT_EXPECT_EQ(test, root_directory[i], block[i]);
+
+ tb_property_free_dir(dir);
+}
+
+static void compare_dirs(struct kunit *test, struct tb_property_dir *d1,
+ struct tb_property_dir *d2)
+{
+ struct tb_property *p1, *p2, *tmp;
+ int n1, n2, i;
+
+ if (d1->uuid) {
+ KUNIT_ASSERT_TRUE(test, d2->uuid != NULL);
+ KUNIT_ASSERT_TRUE(test, uuid_equal(d1->uuid, d2->uuid));
+ } else {
+ KUNIT_ASSERT_TRUE(test, d2->uuid == NULL);
+ }
+
+ n1 = 0;
+ tb_property_for_each(d1, tmp)
+ n1++;
+ KUNIT_ASSERT_NE(test, n1, 0);
+
+ n2 = 0;
+ tb_property_for_each(d2, tmp)
+ n2++;
+ KUNIT_ASSERT_NE(test, n2, 0);
+
+ KUNIT_ASSERT_EQ(test, n1, n2);
+
+ p1 = NULL;
+ p2 = NULL;
+ for (i = 0; i < n1; i++) {
+ p1 = tb_property_get_next(d1, p1);
+ KUNIT_ASSERT_TRUE(test, p1 != NULL);
+ p2 = tb_property_get_next(d2, p2);
+ KUNIT_ASSERT_TRUE(test, p2 != NULL);
+
+ KUNIT_ASSERT_STREQ(test, &p1->key[0], &p2->key[0]);
+ KUNIT_ASSERT_EQ(test, p1->type, p2->type);
+ KUNIT_ASSERT_EQ(test, p1->length, p2->length);
+
+ switch (p1->type) {
+ case TB_PROPERTY_TYPE_DIRECTORY:
+ KUNIT_ASSERT_TRUE(test, p1->value.dir != NULL);
+ KUNIT_ASSERT_TRUE(test, p2->value.dir != NULL);
+ compare_dirs(test, p1->value.dir, p2->value.dir);
+ break;
+
+ case TB_PROPERTY_TYPE_DATA:
+ KUNIT_ASSERT_TRUE(test, p1->value.data != NULL);
+ KUNIT_ASSERT_TRUE(test, p2->value.data != NULL);
+ KUNIT_ASSERT_TRUE(test,
+ !memcmp(p1->value.data, p2->value.data,
+ p1->length * 4)
+ );
+ break;
+
+ case TB_PROPERTY_TYPE_TEXT:
+ KUNIT_ASSERT_TRUE(test, p1->value.text != NULL);
+ KUNIT_ASSERT_TRUE(test, p2->value.text != NULL);
+ KUNIT_ASSERT_STREQ(test, p1->value.text, p2->value.text);
+ break;
+
+ case TB_PROPERTY_TYPE_VALUE:
+ KUNIT_ASSERT_EQ(test, p1->value.immediate,
+ p2->value.immediate);
+ break;
+ default:
+ KUNIT_FAIL(test, "unexpected property type");
+ break;
+ }
+ }
+}
+
+static void tb_test_property_copy(struct kunit *test)
+{
+ struct tb_property_dir *src, *dst;
+ u32 *block;
+ int ret, i;
+
+ src = tb_property_parse_dir(root_directory, ARRAY_SIZE(root_directory));
+ KUNIT_ASSERT_TRUE(test, src != NULL);
+
+ dst = tb_property_copy_dir(src);
+ KUNIT_ASSERT_TRUE(test, dst != NULL);
+
+ /* Compare the structures */
+ compare_dirs(test, src, dst);
+
+ /* Compare the resulting property block */
+ ret = tb_property_format_dir(dst, NULL, 0);
+ KUNIT_ASSERT_EQ(test, ret, (int)ARRAY_SIZE(root_directory));
+
+ block = kunit_kzalloc(test, sizeof(root_directory), GFP_KERNEL);
+ KUNIT_ASSERT_TRUE(test, block != NULL);
+
+ ret = tb_property_format_dir(dst, block, ARRAY_SIZE(root_directory));
+ KUNIT_EXPECT_TRUE(test, !ret);
+
+ for (i = 0; i < ARRAY_SIZE(root_directory); i++)
+ KUNIT_EXPECT_EQ(test, root_directory[i], block[i]);
+
+ tb_property_free_dir(dst);
+ tb_property_free_dir(src);
+}
+
static struct kunit_case tb_test_cases[] = {
KUNIT_CASE(tb_test_path_basic),
KUNIT_CASE(tb_test_path_not_connected_walk),
KUNIT_CASE(tb_test_tunnel_dp_max_length),
KUNIT_CASE(tb_test_tunnel_port_on_path),
KUNIT_CASE(tb_test_tunnel_usb3),
+ KUNIT_CASE(tb_test_tunnel_dma),
+ KUNIT_CASE(tb_test_tunnel_dma_rx),
+ KUNIT_CASE(tb_test_tunnel_dma_tx),
+ KUNIT_CASE(tb_test_tunnel_dma_chain),
+ KUNIT_CASE(tb_test_tunnel_dma_match),
+ KUNIT_CASE(tb_test_property_parse),
+ KUNIT_CASE(tb_test_property_format),
+ KUNIT_CASE(tb_test_property_copy),
{ }
};
return min(max_credits, 13U);
}
-static int tb_dma_activate(struct tb_tunnel *tunnel, bool active)
-{
- struct tb_port *nhi = tunnel->src_port;
- u32 credits;
-
- credits = active ? tb_dma_credits(nhi) : 0;
- return tb_port_set_initial_credits(nhi, credits);
-}
-
-static void tb_dma_init_path(struct tb_path *path, unsigned int isb,
- unsigned int efc, u32 credits)
+static void tb_dma_init_path(struct tb_path *path, unsigned int efc, u32 credits)
{
int i;
path->egress_fc_enable = efc;
path->ingress_fc_enable = TB_PATH_ALL;
path->egress_shared_buffer = TB_PATH_NONE;
- path->ingress_shared_buffer = isb;
+ path->ingress_shared_buffer = TB_PATH_NONE;
path->priority = 5;
path->weight = 1;
path->clear_fc = true;
* @tb: Pointer to the domain structure
* @nhi: Host controller port
* @dst: Destination null port which the other domain is connected to
- * @transmit_ring: NHI ring number used to send packets towards the
- * other domain. Set to %0 if TX path is not needed.
* @transmit_path: HopID used for transmitting packets
- * @receive_ring: NHI ring number used to receive packets from the
- * other domain. Set to %0 if RX path is not needed.
+ * @transmit_ring: NHI ring number used to send packets towards the
+ * other domain. Set to %-1 if TX path is not needed.
* @receive_path: HopID used for receiving packets
+ * @receive_ring: NHI ring number used to receive packets from the
+ * other domain. Set to %-1 if RX path is not needed.
*
* Return: Returns a tb_tunnel on success or NULL on failure.
*/
struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi,
- struct tb_port *dst, int transmit_ring,
- int transmit_path, int receive_ring,
- int receive_path)
+ struct tb_port *dst, int transmit_path,
+ int transmit_ring, int receive_path,
+ int receive_ring)
{
struct tb_tunnel *tunnel;
size_t npaths = 0, i = 0;
struct tb_path *path;
u32 credits;
- if (receive_ring)
+ if (receive_ring > 0)
npaths++;
- if (transmit_ring)
+ if (transmit_ring > 0)
npaths++;
if (WARN_ON(!npaths))
if (!tunnel)
return NULL;
- tunnel->activate = tb_dma_activate;
tunnel->src_port = nhi;
tunnel->dst_port = dst;
credits = tb_dma_credits(nhi);
- if (receive_ring) {
+ if (receive_ring > 0) {
path = tb_path_alloc(tb, dst, receive_path, nhi, receive_ring, 0,
"DMA RX");
if (!path) {
tb_tunnel_free(tunnel);
return NULL;
}
- tb_dma_init_path(path, TB_PATH_NONE, TB_PATH_SOURCE | TB_PATH_INTERNAL,
- credits);
+ tb_dma_init_path(path, TB_PATH_SOURCE | TB_PATH_INTERNAL, credits);
tunnel->paths[i++] = path;
}
- if (transmit_ring) {
+ if (transmit_ring > 0) {
path = tb_path_alloc(tb, nhi, transmit_ring, dst, transmit_path, 0,
"DMA TX");
if (!path) {
tb_tunnel_free(tunnel);
return NULL;
}
- tb_dma_init_path(path, TB_PATH_SOURCE, TB_PATH_ALL, credits);
+ tb_dma_init_path(path, TB_PATH_ALL, credits);
tunnel->paths[i++] = path;
}
return tunnel;
}
+/**
+ * tb_tunnel_match_dma() - Match DMA tunnel
+ * @tunnel: Tunnel to match
+ * @transmit_path: HopID used for transmitting packets. Pass %-1 to ignore.
+ * @transmit_ring: NHI ring number used to send packets towards the
+ * other domain. Pass %-1 to ignore.
+ * @receive_path: HopID used for receiving packets. Pass %-1 to ignore.
+ * @receive_ring: NHI ring number used to receive packets from the
+ * other domain. Pass %-1 to ignore.
+ *
+ * This function can be used to match specific DMA tunnel, if there are
+ * multiple DMA tunnels going through the same XDomain connection.
+ * Returns true if there is match and false otherwise.
+ */
+bool tb_tunnel_match_dma(const struct tb_tunnel *tunnel, int transmit_path,
+ int transmit_ring, int receive_path, int receive_ring)
+{
+ const struct tb_path *tx_path = NULL, *rx_path = NULL;
+ int i;
+
+ if (!receive_ring || !transmit_ring)
+ return false;
+
+ for (i = 0; i < tunnel->npaths; i++) {
+ const struct tb_path *path = tunnel->paths[i];
+
+ if (!path)
+ continue;
+
+ if (tb_port_is_nhi(path->hops[0].in_port))
+ tx_path = path;
+ else if (tb_port_is_nhi(path->hops[path->path_length - 1].out_port))
+ rx_path = path;
+ }
+
+ if (transmit_ring > 0 || transmit_path > 0) {
+ if (!tx_path)
+ return false;
+ if (transmit_ring > 0 &&
+ (tx_path->hops[0].in_hop_index != transmit_ring))
+ return false;
+ if (transmit_path > 0 &&
+ (tx_path->hops[tx_path->path_length - 1].next_hop_index != transmit_path))
+ return false;
+ }
+
+ if (receive_ring > 0 || receive_path > 0) {
+ if (!rx_path)
+ return false;
+ if (receive_path > 0 &&
+ (rx_path->hops[0].in_hop_index != receive_path))
+ return false;
+ if (receive_ring > 0 &&
+ (rx_path->hops[rx_path->path_length - 1].next_hop_index != receive_ring))
+ return false;
+ }
+
+ return true;
+}
+
static int tb_usb3_max_link_rate(struct tb_port *up, struct tb_port *down)
{
int ret, up_max_rate, down_max_rate;
struct tb_port *out, int max_up,
int max_down);
struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi,
- struct tb_port *dst, int transmit_ring,
- int transmit_path, int receive_ring,
- int receive_path);
+ struct tb_port *dst, int transmit_path,
+ int transmit_ring, int receive_path,
+ int receive_ring);
+bool tb_tunnel_match_dma(const struct tb_tunnel *tunnel, int transmit_path,
+ int transmit_ring, int receive_path, int receive_ring);
struct tb_tunnel *tb_tunnel_discover_usb3(struct tb *tb, struct tb_port *down);
struct tb_tunnel *tb_tunnel_alloc_usb3(struct tb *tb, struct tb_port *up,
struct tb_port *down, int max_up,
#include <linux/kmod.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
+#include <linux/prandom.h>
#include <linux/utsname.h>
#include <linux/uuid.h>
#include <linux/workqueue.h>
#include "tb.h"
-#define XDOMAIN_DEFAULT_TIMEOUT 5000 /* ms */
+#define XDOMAIN_DEFAULT_TIMEOUT 1000 /* ms */
#define XDOMAIN_UUID_RETRIES 10
-#define XDOMAIN_PROPERTIES_RETRIES 60
+#define XDOMAIN_PROPERTIES_RETRIES 10
#define XDOMAIN_PROPERTIES_CHANGED_RETRIES 10
#define XDOMAIN_BONDING_WAIT 100 /* ms */
+#define XDOMAIN_DEFAULT_MAX_HOPID 15
struct xdomain_request_work {
struct work_struct work;
module_param_named(xdomain, tb_xdomain_enabled, bool, 0444);
MODULE_PARM_DESC(xdomain, "allow XDomain protocol (default: true)");
-/* Serializes access to the properties and protocol handlers below */
+/*
+ * Serializes access to the properties and protocol handlers below. If
+ * you need to take both this lock and the struct tb_xdomain lock, take
+ * this one first.
+ */
static DEFINE_MUTEX(xdomain_lock);
/* Properties exposed to the remote domains */
static struct tb_property_dir *xdomain_property_dir;
-static u32 *xdomain_property_block;
-static u32 xdomain_property_block_len;
static u32 xdomain_property_block_gen;
/* Additional protocol handlers */
}
static int tb_xdp_properties_response(struct tb *tb, struct tb_ctl *ctl,
- u64 route, u8 sequence, const uuid_t *src_uuid,
- const struct tb_xdp_properties *req)
+ struct tb_xdomain *xd, u8 sequence, const struct tb_xdp_properties *req)
{
struct tb_xdp_properties_response *res;
size_t total_size;
* protocol supports forwarding, though which we might add
* support later on.
*/
- if (!uuid_equal(src_uuid, &req->dst_uuid)) {
- tb_xdp_error_response(ctl, route, sequence,
+ if (!uuid_equal(xd->local_uuid, &req->dst_uuid)) {
+ tb_xdp_error_response(ctl, xd->route, sequence,
ERROR_UNKNOWN_DOMAIN);
return 0;
}
- mutex_lock(&xdomain_lock);
+ mutex_lock(&xd->lock);
- if (req->offset >= xdomain_property_block_len) {
- mutex_unlock(&xdomain_lock);
+ if (req->offset >= xd->local_property_block_len) {
+ mutex_unlock(&xd->lock);
return -EINVAL;
}
- len = xdomain_property_block_len - req->offset;
+ len = xd->local_property_block_len - req->offset;
len = min_t(u16, len, TB_XDP_PROPERTIES_MAX_DATA_LENGTH);
total_size = sizeof(*res) + len * 4;
res = kzalloc(total_size, GFP_KERNEL);
if (!res) {
- mutex_unlock(&xdomain_lock);
+ mutex_unlock(&xd->lock);
return -ENOMEM;
}
- tb_xdp_fill_header(&res->hdr, route, sequence, PROPERTIES_RESPONSE,
+ tb_xdp_fill_header(&res->hdr, xd->route, sequence, PROPERTIES_RESPONSE,
total_size);
- res->generation = xdomain_property_block_gen;
- res->data_length = xdomain_property_block_len;
+ res->generation = xd->local_property_block_gen;
+ res->data_length = xd->local_property_block_len;
res->offset = req->offset;
- uuid_copy(&res->src_uuid, src_uuid);
+ uuid_copy(&res->src_uuid, xd->local_uuid);
uuid_copy(&res->dst_uuid, &req->src_uuid);
- memcpy(res->data, &xdomain_property_block[req->offset], len * 4);
+ memcpy(res->data, &xd->local_property_block[req->offset], len * 4);
- mutex_unlock(&xdomain_lock);
+ mutex_unlock(&xd->lock);
ret = __tb_xdomain_response(ctl, res, total_size,
TB_CFG_PKG_XDOMAIN_RESP);
}
EXPORT_SYMBOL_GPL(tb_unregister_protocol_handler);
-static int rebuild_property_block(void)
+static void update_property_block(struct tb_xdomain *xd)
{
- u32 *block, len;
- int ret;
-
- ret = tb_property_format_dir(xdomain_property_dir, NULL, 0);
- if (ret < 0)
- return ret;
-
- len = ret;
-
- block = kcalloc(len, sizeof(u32), GFP_KERNEL);
- if (!block)
- return -ENOMEM;
+ mutex_lock(&xdomain_lock);
+ mutex_lock(&xd->lock);
+ /*
+ * If the local property block is not up-to-date, rebuild it now
+ * based on the global property template.
+ */
+ if (!xd->local_property_block ||
+ xd->local_property_block_gen < xdomain_property_block_gen) {
+ struct tb_property_dir *dir;
+ int ret, block_len;
+ u32 *block;
+
+ dir = tb_property_copy_dir(xdomain_property_dir);
+ if (!dir) {
+ dev_warn(&xd->dev, "failed to copy properties\n");
+ goto out_unlock;
+ }
- ret = tb_property_format_dir(xdomain_property_dir, block, len);
- if (ret) {
- kfree(block);
- return ret;
- }
+ /* Fill in non-static properties now */
+ tb_property_add_text(dir, "deviceid", utsname()->nodename);
+ tb_property_add_immediate(dir, "maxhopid", xd->local_max_hopid);
- kfree(xdomain_property_block);
- xdomain_property_block = block;
- xdomain_property_block_len = len;
- xdomain_property_block_gen++;
+ ret = tb_property_format_dir(dir, NULL, 0);
+ if (ret < 0) {
+ dev_warn(&xd->dev, "local property block creation failed\n");
+ tb_property_free_dir(dir);
+ goto out_unlock;
+ }
- return 0;
-}
+ block_len = ret;
+ block = kcalloc(block_len, sizeof(*block), GFP_KERNEL);
+ if (!block) {
+ tb_property_free_dir(dir);
+ goto out_unlock;
+ }
-static void finalize_property_block(void)
-{
- const struct tb_property *nodename;
+ ret = tb_property_format_dir(dir, block, block_len);
+ if (ret) {
+ dev_warn(&xd->dev, "property block generation failed\n");
+ tb_property_free_dir(dir);
+ kfree(block);
+ goto out_unlock;
+ }
- /*
- * On first XDomain connection we set up the the system
- * nodename. This delayed here because userspace may not have it
- * set when the driver is first probed.
- */
- mutex_lock(&xdomain_lock);
- nodename = tb_property_find(xdomain_property_dir, "deviceid",
- TB_PROPERTY_TYPE_TEXT);
- if (!nodename) {
- tb_property_add_text(xdomain_property_dir, "deviceid",
- utsname()->nodename);
- rebuild_property_block();
+ tb_property_free_dir(dir);
+ /* Release the previous block */
+ kfree(xd->local_property_block);
+ /* Assign new one */
+ xd->local_property_block = block;
+ xd->local_property_block_len = block_len;
+ xd->local_property_block_gen = xdomain_property_block_gen;
}
+
+out_unlock:
+ mutex_unlock(&xd->lock);
mutex_unlock(&xdomain_lock);
}
const struct tb_xdomain_header *xhdr = &pkg->xd_hdr;
struct tb *tb = xw->tb;
struct tb_ctl *ctl = tb->ctl;
+ struct tb_xdomain *xd;
const uuid_t *uuid;
int ret = 0;
u32 sequence;
goto out;
}
- finalize_property_block();
+ tb_dbg(tb, "%llx: received XDomain request %#x\n", route, pkg->type);
+
+ xd = tb_xdomain_find_by_route_locked(tb, route);
+ if (xd)
+ update_property_block(xd);
switch (pkg->type) {
case PROPERTIES_REQUEST:
- ret = tb_xdp_properties_response(tb, ctl, route, sequence, uuid,
- (const struct tb_xdp_properties *)pkg);
+ if (xd) {
+ ret = tb_xdp_properties_response(tb, ctl, xd, sequence,
+ (const struct tb_xdp_properties *)pkg);
+ }
break;
- case PROPERTIES_CHANGED_REQUEST: {
- struct tb_xdomain *xd;
-
+ case PROPERTIES_CHANGED_REQUEST:
ret = tb_xdp_properties_changed_response(ctl, route, sequence);
/*
* the xdomain related to this connection as well in
* case there is a change in services it offers.
*/
- xd = tb_xdomain_find_by_route_locked(tb, route);
- if (xd) {
- if (device_is_registered(&xd->dev)) {
- queue_delayed_work(tb->wq, &xd->get_properties_work,
- msecs_to_jiffies(50));
- }
- tb_xdomain_put(xd);
+ if (xd && device_is_registered(&xd->dev)) {
+ queue_delayed_work(tb->wq, &xd->get_properties_work,
+ msecs_to_jiffies(50));
}
-
break;
- }
case UUID_REQUEST_OLD:
case UUID_REQUEST:
break;
}
+ tb_xdomain_put(xd);
+
if (ret) {
tb_warn(tb, "failed to send XDomain response for %#x\n",
pkg->type);
if (!svc)
return 0;
- if (!tb_property_find(xd->properties, svc->key,
+ if (!tb_property_find(xd->remote_properties, svc->key,
TB_PROPERTY_TYPE_DIRECTORY))
device_unregister(dev);
device_for_each_child_reverse(&xd->dev, xd, remove_missing_service);
/* Then re-enumerate properties creating new services as we go */
- tb_property_for_each(xd->properties, p) {
+ tb_property_for_each(xd->remote_properties, p) {
if (p->type != TB_PROPERTY_TYPE_DIRECTORY)
continue;
return -EINVAL;
xd->vendor = p->value.immediate;
+ p = tb_property_find(dir, "maxhopid", TB_PROPERTY_TYPE_VALUE);
+ /*
+ * USB4 inter-domain spec suggests using 15 as HopID if the
+ * other end does not announce it in a property. This is for
+ * TBT3 compatibility.
+ */
+ xd->remote_max_hopid = p ? p->value.immediate : XDOMAIN_DEFAULT_MAX_HOPID;
+
kfree(xd->device_name);
xd->device_name = NULL;
kfree(xd->vendor_name);
return 0;
}
-/* Called with @xd->lock held */
-static void tb_xdomain_restore_paths(struct tb_xdomain *xd)
-{
- if (!xd->resume)
- return;
-
- xd->resume = false;
- if (xd->transmit_path) {
- dev_dbg(&xd->dev, "re-establishing DMA path\n");
- tb_domain_approve_xdomain_paths(xd->tb, xd);
- }
-}
-
static inline struct tb_switch *tb_xdomain_parent(struct tb_xdomain *xd)
{
return tb_to_switch(xd->dev.parent);
uuid_t uuid;
int ret;
+ dev_dbg(&xd->dev, "requesting remote UUID\n");
+
ret = tb_xdp_uuid_request(tb->ctl, xd->route, xd->uuid_retries, &uuid);
if (ret < 0) {
if (xd->uuid_retries-- > 0) {
+ dev_dbg(&xd->dev, "failed to request UUID, retrying\n");
queue_delayed_work(xd->tb->wq, &xd->get_uuid_work,
msecs_to_jiffies(100));
} else {
return;
}
+ dev_dbg(&xd->dev, "got remote UUID %pUb\n", &uuid);
+
if (uuid_equal(&uuid, xd->local_uuid))
dev_dbg(&xd->dev, "intra-domain loop detected\n");
u32 gen = 0;
int ret;
+ dev_dbg(&xd->dev, "requesting remote properties\n");
+
ret = tb_xdp_properties_request(tb->ctl, xd->route, xd->local_uuid,
xd->remote_uuid, xd->properties_retries,
&block, &gen);
if (ret < 0) {
if (xd->properties_retries-- > 0) {
+ dev_dbg(&xd->dev,
+ "failed to request remote properties, retrying\n");
queue_delayed_work(xd->tb->wq, &xd->get_properties_work,
msecs_to_jiffies(1000));
} else {
mutex_lock(&xd->lock);
/* Only accept newer generation properties */
- if (xd->properties && gen <= xd->property_block_gen) {
- /*
- * On resume it is likely that the properties block is
- * not changed (unless the other end added or removed
- * services). However, we need to make sure the existing
- * DMA paths are restored properly.
- */
- tb_xdomain_restore_paths(xd);
+ if (xd->remote_properties && gen <= xd->remote_property_block_gen)
goto err_free_block;
- }
dir = tb_property_parse_dir(block, ret);
if (!dir) {
}
/* Release the existing one */
- if (xd->properties) {
- tb_property_free_dir(xd->properties);
+ if (xd->remote_properties) {
+ tb_property_free_dir(xd->remote_properties);
update = true;
}
- xd->properties = dir;
- xd->property_block_gen = gen;
+ xd->remote_properties = dir;
+ xd->remote_property_block_gen = gen;
tb_xdomain_update_link_attributes(xd);
- tb_xdomain_restore_paths(xd);
-
mutex_unlock(&xd->lock);
kfree(block);
dev_err(&xd->dev, "failed to add XDomain device\n");
return;
}
+ dev_info(&xd->dev, "new host found, vendor=%#x device=%#x\n",
+ xd->vendor, xd->device);
+ if (xd->vendor_name && xd->device_name)
+ dev_info(&xd->dev, "%s %s\n", xd->vendor_name,
+ xd->device_name);
} else {
kobject_uevent(&xd->dev.kobj, KOBJ_CHANGE);
}
properties_changed_work.work);
int ret;
+ dev_dbg(&xd->dev, "sending properties changed notification\n");
+
ret = tb_xdp_properties_changed_request(xd->tb->ctl, xd->route,
xd->properties_changed_retries, xd->local_uuid);
if (ret) {
- if (xd->properties_changed_retries-- > 0)
+ if (xd->properties_changed_retries-- > 0) {
+ dev_dbg(&xd->dev,
+ "failed to send properties changed notification, retrying\n");
queue_delayed_work(xd->tb->wq,
&xd->properties_changed_work,
msecs_to_jiffies(1000));
+ }
+ dev_err(&xd->dev, "failed to send properties changed notification\n");
return;
}
}
static DEVICE_ATTR_RO(device_name);
+static ssize_t maxhopid_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct tb_xdomain *xd = container_of(dev, struct tb_xdomain, dev);
+
+ return sprintf(buf, "%d\n", xd->remote_max_hopid);
+}
+static DEVICE_ATTR_RO(maxhopid);
+
static ssize_t vendor_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
static struct attribute *xdomain_attrs[] = {
&dev_attr_device.attr,
&dev_attr_device_name.attr,
+ &dev_attr_maxhopid.attr,
&dev_attr_rx_lanes.attr,
&dev_attr_rx_speed.attr,
&dev_attr_tx_lanes.attr,
put_device(xd->dev.parent);
- tb_property_free_dir(xd->properties);
+ kfree(xd->local_property_block);
+ tb_property_free_dir(xd->remote_properties);
+ ida_destroy(&xd->out_hopids);
+ ida_destroy(&xd->in_hopids);
ida_destroy(&xd->service_ids);
kfree(xd->local_uuid);
static int __maybe_unused tb_xdomain_resume(struct device *dev)
{
- struct tb_xdomain *xd = tb_to_xdomain(dev);
-
- /*
- * Ask tb_xdomain_get_properties() restore any existing DMA
- * paths after properties are re-read.
- */
- xd->resume = true;
- start_handshake(xd);
-
+ start_handshake(tb_to_xdomain(dev));
return 0;
}
xd->tb = tb;
xd->route = route;
+ xd->local_max_hopid = down->config.max_in_hop_id;
ida_init(&xd->service_ids);
+ ida_init(&xd->in_hopids);
+ ida_init(&xd->out_hopids);
mutex_init(&xd->lock);
INIT_DELAYED_WORK(&xd->get_uuid_work, tb_xdomain_get_uuid);
INIT_DELAYED_WORK(&xd->get_properties_work, tb_xdomain_get_properties);
xd->dev.groups = xdomain_attr_groups;
dev_set_name(&xd->dev, "%u-%llx", tb->index, route);
+ dev_dbg(&xd->dev, "local UUID %pUb\n", local_uuid);
+ if (remote_uuid)
+ dev_dbg(&xd->dev, "remote UUID %pUb\n", remote_uuid);
+
/*
* This keeps the DMA powered on as long as we have active
* connection to another host.
pm_runtime_put_noidle(&xd->dev);
pm_runtime_set_suspended(&xd->dev);
- if (!device_is_registered(&xd->dev))
+ if (!device_is_registered(&xd->dev)) {
put_device(&xd->dev);
- else
+ } else {
+ dev_info(&xd->dev, "host disconnected\n");
device_unregister(&xd->dev);
+ }
}
/**
EXPORT_SYMBOL_GPL(tb_xdomain_lane_bonding_disable);
/**
- * tb_xdomain_enable_paths() - Enable DMA paths for XDomain connection
+ * tb_xdomain_alloc_in_hopid() - Allocate input HopID for tunneling
* @xd: XDomain connection
- * @transmit_path: HopID of the transmit path the other end is using to
- * send packets
- * @transmit_ring: DMA ring used to receive packets from the other end
- * @receive_path: HopID of the receive path the other end is using to
- * receive packets
- * @receive_ring: DMA ring used to send packets to the other end
+ * @hopid: Preferred HopID or %-1 for next available
*
- * The function enables DMA paths accordingly so that after successful
- * return the caller can send and receive packets using high-speed DMA
- * path.
- *
- * Return: %0 in case of success and negative errno in case of error
+ * Returns allocated HopID or negative errno. Specifically returns
+ * %-ENOSPC if there are no more available HopIDs. Returned HopID is
+ * guaranteed to be within range supported by the input lane adapter.
+ * Call tb_xdomain_release_in_hopid() to release the allocated HopID.
*/
-int tb_xdomain_enable_paths(struct tb_xdomain *xd, u16 transmit_path,
- u16 transmit_ring, u16 receive_path,
- u16 receive_ring)
+int tb_xdomain_alloc_in_hopid(struct tb_xdomain *xd, int hopid)
{
- int ret;
+ if (hopid < 0)
+ hopid = TB_PATH_MIN_HOPID;
+ if (hopid < TB_PATH_MIN_HOPID || hopid > xd->local_max_hopid)
+ return -EINVAL;
- mutex_lock(&xd->lock);
+ return ida_alloc_range(&xd->in_hopids, hopid, xd->local_max_hopid,
+ GFP_KERNEL);
+}
+EXPORT_SYMBOL_GPL(tb_xdomain_alloc_in_hopid);
- if (xd->transmit_path) {
- ret = xd->transmit_path == transmit_path ? 0 : -EBUSY;
- goto exit_unlock;
- }
+/**
+ * tb_xdomain_alloc_out_hopid() - Allocate output HopID for tunneling
+ * @xd: XDomain connection
+ * @hopid: Preferred HopID or %-1 for next available
+ *
+ * Returns allocated HopID or negative errno. Specifically returns
+ * %-ENOSPC if there are no more available HopIDs. Returned HopID is
+ * guaranteed to be within range supported by the output lane adapter.
+ * Call tb_xdomain_release_in_hopid() to release the allocated HopID.
+ */
+int tb_xdomain_alloc_out_hopid(struct tb_xdomain *xd, int hopid)
+{
+ if (hopid < 0)
+ hopid = TB_PATH_MIN_HOPID;
+ if (hopid < TB_PATH_MIN_HOPID || hopid > xd->remote_max_hopid)
+ return -EINVAL;
- xd->transmit_path = transmit_path;
- xd->transmit_ring = transmit_ring;
- xd->receive_path = receive_path;
- xd->receive_ring = receive_ring;
+ return ida_alloc_range(&xd->out_hopids, hopid, xd->remote_max_hopid,
+ GFP_KERNEL);
+}
+EXPORT_SYMBOL_GPL(tb_xdomain_alloc_out_hopid);
- ret = tb_domain_approve_xdomain_paths(xd->tb, xd);
+/**
+ * tb_xdomain_release_in_hopid() - Release input HopID
+ * @xd: XDomain connection
+ * @hopid: HopID to release
+ */
+void tb_xdomain_release_in_hopid(struct tb_xdomain *xd, int hopid)
+{
+ ida_free(&xd->in_hopids, hopid);
+}
+EXPORT_SYMBOL_GPL(tb_xdomain_release_in_hopid);
-exit_unlock:
- mutex_unlock(&xd->lock);
+/**
+ * tb_xdomain_release_out_hopid() - Release output HopID
+ * @xd: XDomain connection
+ * @hopid: HopID to release
+ */
+void tb_xdomain_release_out_hopid(struct tb_xdomain *xd, int hopid)
+{
+ ida_free(&xd->out_hopids, hopid);
+}
+EXPORT_SYMBOL_GPL(tb_xdomain_release_out_hopid);
- return ret;
+/**
+ * tb_xdomain_enable_paths() - Enable DMA paths for XDomain connection
+ * @xd: XDomain connection
+ * @transmit_path: HopID we are using to send out packets
+ * @transmit_ring: DMA ring used to send out packets
+ * @receive_path: HopID the other end is using to send packets to us
+ * @receive_ring: DMA ring used to receive packets from @receive_path
+ *
+ * The function enables DMA paths accordingly so that after successful
+ * return the caller can send and receive packets using high-speed DMA
+ * path. If a transmit or receive path is not needed, pass %-1 for those
+ * parameters.
+ *
+ * Return: %0 in case of success and negative errno in case of error
+ */
+int tb_xdomain_enable_paths(struct tb_xdomain *xd, int transmit_path,
+ int transmit_ring, int receive_path,
+ int receive_ring)
+{
+ return tb_domain_approve_xdomain_paths(xd->tb, xd, transmit_path,
+ transmit_ring, receive_path,
+ receive_ring);
}
EXPORT_SYMBOL_GPL(tb_xdomain_enable_paths);
/**
* tb_xdomain_disable_paths() - Disable DMA paths for XDomain connection
* @xd: XDomain connection
+ * @transmit_path: HopID we are using to send out packets
+ * @transmit_ring: DMA ring used to send out packets
+ * @receive_path: HopID the other end is using to send packets to us
+ * @receive_ring: DMA ring used to receive packets from @receive_path
*
* This does the opposite of tb_xdomain_enable_paths(). After call to
- * this the caller is not expected to use the rings anymore.
+ * this the caller is not expected to use the rings anymore. Passing %-1
+ * as path/ring parameter means don't care. Normally the callers should
+ * pass the same values here as they do when paths are enabled.
*
* Return: %0 in case of success and negative errno in case of error
*/
-int tb_xdomain_disable_paths(struct tb_xdomain *xd)
+int tb_xdomain_disable_paths(struct tb_xdomain *xd, int transmit_path,
+ int transmit_ring, int receive_path,
+ int receive_ring)
{
- int ret = 0;
-
- mutex_lock(&xd->lock);
- if (xd->transmit_path) {
- xd->transmit_path = 0;
- xd->transmit_ring = 0;
- xd->receive_path = 0;
- xd->receive_ring = 0;
-
- ret = tb_domain_disconnect_xdomain_paths(xd->tb, xd);
- }
- mutex_unlock(&xd->lock);
-
- return ret;
+ return tb_domain_disconnect_xdomain_paths(xd->tb, xd, transmit_path,
+ transmit_ring, receive_path,
+ receive_ring);
}
EXPORT_SYMBOL_GPL(tb_xdomain_disable_paths);
if (ret)
goto err_unlock;
- ret = rebuild_property_block();
- if (ret) {
- remove_directory(key, dir);
- goto err_unlock;
- }
+ xdomain_property_block_gen++;
mutex_unlock(&xdomain_lock);
update_all_xdomains();
mutex_lock(&xdomain_lock);
if (remove_directory(key, dir))
- ret = rebuild_property_block();
+ xdomain_property_block_gen++;
mutex_unlock(&xdomain_lock);
if (!ret)
* directories. Those will be added by service drivers
* themselves when they are loaded.
*
- * We also add node name later when first connection is made.
+ * Rest of the properties are filled dynamically based on these
+ * when the P2P connection is made.
*/
tb_property_add_immediate(xdomain_property_dir, "vendorid",
PCI_VENDOR_ID_INTEL);
tb_property_add_immediate(xdomain_property_dir, "deviceid", 0x1);
tb_property_add_immediate(xdomain_property_dir, "devicerv", 0x80000100);
+ xdomain_property_block_gen = prandom_u32();
return 0;
}
void tb_xdomain_exit(void)
{
- kfree(xdomain_property_block);
tb_property_free_dir(xdomain_property_dir);
}
#include <linux/dma-mapping.h>
#include <linux/usb/gadget.h>
#include <linux/module.h>
+#include <linux/dmapool.h>
#include <linux/iopoll.h>
#include "core.h"
return priv_ep->trb_pool_dma + offset;
}
-static int cdns3_ring_size(struct cdns3_endpoint *priv_ep)
-{
- switch (priv_ep->type) {
- case USB_ENDPOINT_XFER_ISOC:
- return TRB_ISO_RING_SIZE;
- case USB_ENDPOINT_XFER_CONTROL:
- return TRB_CTRL_RING_SIZE;
- default:
- if (priv_ep->use_streams)
- return TRB_STREAM_RING_SIZE;
- else
- return TRB_RING_SIZE;
- }
-}
-
static void cdns3_free_trb_pool(struct cdns3_endpoint *priv_ep)
{
struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
if (priv_ep->trb_pool) {
- dma_free_coherent(priv_dev->sysdev,
- cdns3_ring_size(priv_ep),
- priv_ep->trb_pool, priv_ep->trb_pool_dma);
+ dma_pool_free(priv_dev->eps_dma_pool,
+ priv_ep->trb_pool, priv_ep->trb_pool_dma);
priv_ep->trb_pool = NULL;
}
}
int cdns3_allocate_trb_pool(struct cdns3_endpoint *priv_ep)
{
struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
- int ring_size = cdns3_ring_size(priv_ep);
+ int ring_size = TRB_RING_SIZE;
int num_trbs = ring_size / TRB_SIZE;
struct cdns3_trb *link_trb;
cdns3_free_trb_pool(priv_ep);
if (!priv_ep->trb_pool) {
- priv_ep->trb_pool = dma_alloc_coherent(priv_dev->sysdev,
- ring_size,
- &priv_ep->trb_pool_dma,
- GFP_DMA32 | GFP_ATOMIC);
+ priv_ep->trb_pool = dma_pool_alloc(priv_dev->eps_dma_pool,
+ GFP_DMA32 | GFP_ATOMIC,
+ &priv_ep->trb_pool_dma);
+
if (!priv_ep->trb_pool)
return -ENOMEM;
priv_ep->dir);
if ((priv_req->flags & REQUEST_UNALIGNED) &&
- priv_ep->dir == USB_DIR_OUT && !request->status)
+ priv_ep->dir == USB_DIR_OUT && !request->status) {
+ /* Make DMA buffer CPU accessible */
+ dma_sync_single_for_cpu(priv_dev->sysdev,
+ priv_req->aligned_buf->dma,
+ priv_req->aligned_buf->size,
+ priv_req->aligned_buf->dir);
memcpy(request->buf, priv_req->aligned_buf->buf,
request->length);
+ }
priv_req->flags &= ~(REQUEST_PENDING | REQUEST_UNALIGNED);
/* All TRBs have finished, clear the counter */
* interrupts.
*/
spin_unlock_irqrestore(&priv_dev->lock, flags);
- dma_free_coherent(priv_dev->sysdev, buf->size,
- buf->buf, buf->dma);
+ dma_free_noncoherent(priv_dev->sysdev, buf->size,
+ buf->buf, buf->dma, buf->dir);
kfree(buf);
spin_lock_irqsave(&priv_dev->lock, flags);
}
return -ENOMEM;
buf->size = priv_req->request.length;
+ buf->dir = usb_endpoint_dir_in(priv_ep->endpoint.desc) ?
+ DMA_TO_DEVICE : DMA_FROM_DEVICE;
- buf->buf = dma_alloc_coherent(priv_dev->sysdev,
+ buf->buf = dma_alloc_noncoherent(priv_dev->sysdev,
buf->size,
&buf->dma,
+ buf->dir,
GFP_ATOMIC);
if (!buf->buf) {
kfree(buf);
}
if (priv_ep->dir == USB_DIR_IN) {
+ /* Make DMA buffer CPU accessible */
+ dma_sync_single_for_cpu(priv_dev->sysdev,
+ buf->dma, buf->size, buf->dir);
memcpy(buf->buf, priv_req->request.buf,
priv_req->request.length);
}
+ /* Transfer DMA buffer ownership back to device */
+ dma_sync_single_for_device(priv_dev->sysdev,
+ buf->dma, buf->size, buf->dir);
+
priv_req->flags |= REQUEST_UNALIGNED;
trace_cdns3_prepare_aligned_request(priv_req);
struct cdns3_aligned_buf *buf;
buf = cdns3_next_align_buf(&priv_dev->aligned_buf_list);
- dma_free_coherent(priv_dev->sysdev, buf->size,
+ dma_free_noncoherent(priv_dev->sysdev, buf->size,
buf->buf,
- buf->dma);
+ buf->dma,
+ buf->dir);
list_del(&buf->list);
kfree(buf);
dma_free_coherent(priv_dev->sysdev, 8, priv_dev->setup_buf,
priv_dev->setup_dma);
+ dma_pool_destroy(priv_dev->eps_dma_pool);
kfree(priv_dev->zlp_buf);
usb_put_gadget(&priv_dev->gadget);
/* initialize endpoint container */
INIT_LIST_HEAD(&priv_dev->gadget.ep_list);
INIT_LIST_HEAD(&priv_dev->aligned_buf_list);
+ priv_dev->eps_dma_pool = dma_pool_create("cdns3_eps_dma_pool",
+ priv_dev->sysdev,
+ TRB_RING_SIZE, 8, 0);
+ if (!priv_dev->eps_dma_pool) {
+ dev_err(priv_dev->dev, "Failed to create TRB dma pool\n");
+ ret = -ENOMEM;
+ goto err1;
+ }
ret = cdns3_init_eps(priv_dev);
if (ret) {
err2:
cdns3_free_all_eps(priv_dev);
err1:
+ dma_pool_destroy(priv_dev->eps_dma_pool);
+
usb_put_gadget(&priv_dev->gadget);
cdns->gadget_dev = NULL;
return ret;
return 0;
cdns3_gadget_config(priv_dev);
+ if (hibernated)
+ writel(USB_CONF_DEVEN, &priv_dev->regs->usb_conf);
return 0;
}
#ifndef __LINUX_CDNS3_GADGET
#define __LINUX_CDNS3_GADGET
#include <linux/usb/gadget.h>
+#include <linux/dma-direction.h>
/*
* USBSS-DEV register interface.
void *buf;
dma_addr_t dma;
u32 size;
+ enum dma_data_direction dir;
unsigned in_use:1;
struct list_head list;
};
struct cdns3_usb_regs __iomem *regs;
+ struct dma_pool *eps_dma_pool;
struct usb_ctrlrequest *setup_buf;
dma_addr_t setup_dma;
void *zlp_buf;
return 0;
}
+
+
+/* Indicate if the controller was power lost before */
+static inline bool cdns_imx_is_power_lost(struct cdns_imx *data)
+{
+ u32 value;
+
+ value = cdns_imx_readl(data, USB3_CORE_CTRL1);
+ if ((value & SW_RESET_MASK) == ALL_SW_RESET)
+ return true;
+ else
+ return false;
+}
+
+static int __maybe_unused cdns_imx_system_resume(struct device *dev)
+{
+ struct cdns_imx *data = dev_get_drvdata(dev);
+ int ret;
+
+ ret = cdns_imx_resume(dev);
+ if (ret)
+ return ret;
+
+ if (cdns_imx_is_power_lost(data)) {
+ dev_dbg(dev, "resume from power lost\n");
+ ret = cdns_imx_noncore_init(data);
+ if (ret)
+ cdns_imx_suspend(dev);
+ }
+
+ return ret;
+}
+
#else
static int cdns_imx_platform_suspend(struct device *dev,
bool suspend, bool wakeup)
static const struct dev_pm_ops cdns_imx_pm_ops = {
SET_RUNTIME_PM_OPS(cdns_imx_suspend, cdns_imx_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(cdns_imx_suspend, cdns_imx_system_resume)
};
static const struct of_device_id cdns_imx_of_match[] = {
#include "core.h"
#include "gadget-export.h"
+#include "drd.h"
static int set_phy_power_on(struct cdns *cdns)
{
if (!cdns->in_lpm)
return 0;
+ if (cdns_power_is_lost(cdns)) {
+ phy_exit(cdns->usb2_phy);
+ ret = phy_init(cdns->usb2_phy);
+ if (ret)
+ return ret;
+
+ phy_exit(cdns->usb3_phy);
+ ret = phy_init(cdns->usb3_phy);
+ if (ret)
+ return ret;
+ }
+
ret = set_phy_power_on(cdns);
if (ret)
return ret;
static int cdns3_plat_suspend(struct device *dev)
{
struct cdns *cdns = dev_get_drvdata(dev);
+ int ret;
cdns_suspend(cdns);
- return cdns3_controller_suspend(dev, PMSG_SUSPEND);
+ ret = cdns3_controller_suspend(dev, PMSG_SUSPEND);
+ if (ret)
+ return ret;
+
+ if (device_may_wakeup(dev) && cdns->wakeup_irq)
+ enable_irq_wake(cdns->wakeup_irq);
+
+ return ret;
}
static int cdns3_plat_resume(struct device *dev)
__field(int, no_interrupt)
__field(int, start_trb)
__field(int, end_trb)
- __field(struct cdns3_trb *, start_trb_addr)
__field(int, flags)
__field(unsigned int, stream_id)
),
__entry->no_interrupt = req->request.no_interrupt;
__entry->start_trb = req->start_trb;
__entry->end_trb = req->end_trb;
- __entry->start_trb_addr = req->trb;
__entry->flags = req->flags;
__entry->stream_id = req->request.stream_id;
),
TP_printk("%s: req: %p, req buff %p, length: %u/%u %s%s%s, status: %d,"
- " trb: [start:%d, end:%d: virt addr %pa], flags:%x SID: %u",
+ " trb: [start:%d, end:%d], flags:%x SID: %u",
__get_str(name), __entry->req, __entry->buf, __entry->actual,
__entry->length,
__entry->zero ? "Z" : "z",
__entry->status,
__entry->start_trb,
__entry->end_trb,
- __entry->start_trb_addr,
__entry->flags,
__entry->stream_id
)
* are in Disabled state.
*/
for (i = 1; i < CDNSP_ENDPOINTS_NUM; ++i)
- pdev->eps[i].ep_state |= EP_STOPPED;
+ pdev->eps[i].ep_state |= EP_STOPPED | EP_UNCONFIGURED;
trace_cdnsp_handle_cmd_reset_dev(slot_ctx);
pep = to_cdnsp_ep(ep);
pdev = pep->pdev;
+ pep->ep_state &= ~EP_UNCONFIGURED;
if (dev_WARN_ONCE(pdev->dev, pep->ep_state & EP_ENABLED,
"%s is already enabled\n", pep->name))
goto finish;
}
- cdnsp_cmd_stop_ep(pdev, pep);
pep->ep_state |= EP_DIS_IN_RROGRESS;
- cdnsp_cmd_flush_ep(pdev, pep);
+
+ /* Endpoint was unconfigured by Reset Device command. */
+ if (!(pep->ep_state & EP_UNCONFIGURED)) {
+ cdnsp_cmd_stop_ep(pdev, pep);
+ cdnsp_cmd_flush_ep(pdev, pep);
+ }
/* Remove all queued USB requests. */
while (!list_empty(&pep->pending_list)) {
cdnsp_endpoint_zero(pdev, pep);
- ret = cdnsp_update_eps_configuration(pdev, pep);
+ if (!(pep->ep_state & EP_UNCONFIGURED))
+ ret = cdnsp_update_eps_configuration(pdev, pep);
+
cdnsp_free_endpoint_rings(pdev, pep);
- pep->ep_state &= ~EP_ENABLED;
+ pep->ep_state &= ~(EP_ENABLED | EP_UNCONFIGURED);
pep->ep_state |= EP_STOPPED;
finish:
#define EP_WEDGE BIT(4)
#define EP0_HALTED_STATUS BIT(5)
#define EP_HAS_STREAMS BIT(6)
+#define EP_UNCONFIGURED BIT(7)
bool skip;
};
static int cdnsp_alloc_priv_device(struct cdnsp_device *pdev)
{
- int ret = -ENOMEM;
+ int ret;
ret = cdnsp_init_device_ctx(pdev);
if (ret)
if (!pdev->dcbaa)
return -ENOMEM;
- memset(pdev->dcbaa, 0, sizeof(*pdev->dcbaa));
pdev->dcbaa->dma = dma;
cdnsp_write_64(dma, &pdev->op_regs->dcbaa_ptr);
int cdns_resume(struct cdns *cdns, u8 set_active)
{
struct device *dev = cdns->dev;
+ enum usb_role real_role;
+ bool role_changed = false;
+ int ret = 0;
+
+ if (cdns_power_is_lost(cdns)) {
+ if (cdns->role_sw) {
+ cdns->role = cdns_role_get(cdns->role_sw);
+ } else {
+ real_role = cdns_hw_role_state_machine(cdns);
+ if (real_role != cdns->role) {
+ ret = cdns_hw_role_switch(cdns);
+ if (ret)
+ return ret;
+ role_changed = true;
+ }
+ }
+
+ if (!role_changed) {
+ if (cdns->role == USB_ROLE_HOST)
+ ret = cdns_drd_host_on(cdns);
+ else if (cdns->role == USB_ROLE_DEVICE)
+ ret = cdns_drd_gadget_on(cdns);
+
+ if (ret)
+ return ret;
+ }
+ }
if (cdns->roles[cdns->role]->resume)
- cdns->roles[cdns->role]->resume(cdns, false);
+ cdns->roles[cdns->role]->resume(cdns, cdns_power_is_lost(cdns));
if (set_active) {
pm_runtime_disable(dev);
return 0;
}
+
+
+/* Indicate the cdns3 core was power lost before */
+bool cdns_power_is_lost(struct cdns *cdns)
+{
+ if (cdns->version == CDNS3_CONTROLLER_V1) {
+ if (!(readl(&cdns->otg_v1_regs->simulate) & BIT(0)))
+ return true;
+ } else {
+ if (!(readl(&cdns->otg_v0_regs->simulate) & BIT(0)))
+ return true;
+ }
+ return false;
+}
+EXPORT_SYMBOL_GPL(cdns_power_is_lost);
void cdns_drd_gadget_off(struct cdns *cdns);
int cdns_drd_host_on(struct cdns *cdns);
void cdns_drd_host_off(struct cdns *cdns);
-
+bool cdns_power_is_lost(struct cdns *cdns);
#endif /* __LINUX_CDNS3_DRD */
}
usb->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "nvidia,phy", 0);
- if (IS_ERR(usb->phy)) {
- err = PTR_ERR(usb->phy);
- dev_err(&pdev->dev, "failed to get PHY: %d\n", err);
- return err;
- }
+ if (IS_ERR(usb->phy))
+ return dev_err_probe(&pdev->dev, PTR_ERR(usb->phy),
+ "failed to get PHY\n");
usb->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(usb->clk)) {
struct ci_hdrc_dma_aligned_buffer {
void *kmalloc_ptr;
void *old_xfer_buffer;
- u8 data[0];
+ u8 data[];
};
static int ehci_ci_portpower(struct usb_hcd *hcd, int portnum, bool enable)
{
struct acm *acm = tty->driver_data;
- ss->xmit_fifo_size = acm->writesize;
- ss->baud_base = le32_to_cpu(acm->line.dwDTERate);
+ ss->line = acm->minor;
ss->close_delay = jiffies_to_msecs(acm->port.close_delay) / 10;
ss->closing_wait = acm->port.closing_wait == ASYNC_CLOSING_WAIT_NONE ?
ASYNC_CLOSING_WAIT_NONE :
{
struct acm *acm = tty->driver_data;
unsigned int closing_wait, close_delay;
- unsigned int old_closing_wait, old_close_delay;
int retval = 0;
close_delay = msecs_to_jiffies(ss->close_delay * 10);
ASYNC_CLOSING_WAIT_NONE :
msecs_to_jiffies(ss->closing_wait * 10);
- /* we must redo the rounding here, so that the values match */
- old_close_delay = jiffies_to_msecs(acm->port.close_delay) / 10;
- old_closing_wait = acm->port.closing_wait == ASYNC_CLOSING_WAIT_NONE ?
- ASYNC_CLOSING_WAIT_NONE :
- jiffies_to_msecs(acm->port.closing_wait) / 10;
-
mutex_lock(&acm->port.mutex);
if (!capable(CAP_SYS_ADMIN)) {
- if ((ss->close_delay != old_close_delay) ||
- (ss->closing_wait != old_closing_wait))
+ if ((close_delay != acm->port.close_delay) ||
+ (closing_wait != acm->port.closing_wait))
retval = -EPERM;
- else
- retval = -EOPNOTSUPP;
} else {
acm->port.close_delay = close_delay;
acm->port.closing_wait = closing_wait;
struct urb *urb;
int rv = 0;
- acm_unpoison_urbs(acm);
spin_lock_irq(&acm->write_lock);
if (--acm->susp_count)
goto out;
+ acm_unpoison_urbs(acm);
+
if (tty_port_initialized(&acm->port)) {
rv = usb_submit_urb(acm->ctrlurb, GFP_ATOMIC);
#endif
#if IS_ENABLED(CONFIG_USB_SERIAL_XR)
- { USB_DEVICE(0x04e2, 0x1410), /* Ignore XR21V141X USB to Serial converter */
- .driver_info = IGNORE_DEVICE,
- },
+ { USB_DEVICE(0x04e2, 0x1400), .driver_info = IGNORE_DEVICE },
+ { USB_DEVICE(0x04e2, 0x1401), .driver_info = IGNORE_DEVICE },
+ { USB_DEVICE(0x04e2, 0x1402), .driver_info = IGNORE_DEVICE },
+ { USB_DEVICE(0x04e2, 0x1403), .driver_info = IGNORE_DEVICE },
+ { USB_DEVICE(0x04e2, 0x1410), .driver_info = IGNORE_DEVICE },
+ { USB_DEVICE(0x04e2, 0x1411), .driver_info = IGNORE_DEVICE },
+ { USB_DEVICE(0x04e2, 0x1412), .driver_info = IGNORE_DEVICE },
+ { USB_DEVICE(0x04e2, 0x1414), .driver_info = IGNORE_DEVICE },
+ { USB_DEVICE(0x04e2, 0x1420), .driver_info = IGNORE_DEVICE },
+ { USB_DEVICE(0x04e2, 0x1422), .driver_info = IGNORE_DEVICE },
+ { USB_DEVICE(0x04e2, 0x1424), .driver_info = IGNORE_DEVICE },
#endif
/*Samsung phone in firmware update mode */
[USB_ENDPOINT_XFER_INT] = "intr",
};
+/**
+ * usb_ep_type_string() - Returns human readable-name of the endpoint type.
+ * @ep_type: The endpoint type to return human-readable name for. If it's not
+ * any of the types: USB_ENDPOINT_XFER_{CONTROL, ISOC, BULK, INT},
+ * usually got by usb_endpoint_type(), the string 'unknown' will be returned.
+ */
const char *usb_ep_type_string(int ep_type)
{
if (ep_type < 0 || ep_type >= ARRAY_SIZE(ep_type_names))
[USB_SSP_GEN_2x2] = "super-speed-plus-gen2x2",
};
+/**
+ * usb_speed_string() - Returns human readable-name of the speed.
+ * @speed: The speed to return human-readable name for. If it's not
+ * any of the speeds defined in usb_device_speed enum, string for
+ * USB_SPEED_UNKNOWN will be returned.
+ */
const char *usb_speed_string(enum usb_device_speed speed)
{
if (speed < 0 || speed >= ARRAY_SIZE(speed_names))
}
EXPORT_SYMBOL_GPL(usb_speed_string);
+/**
+ * usb_get_maximum_speed - Get maximum requested speed for a given USB
+ * controller.
+ * @dev: Pointer to the given USB controller device
+ *
+ * The function gets the maximum speed string from property "maximum-speed",
+ * and returns the corresponding enum usb_device_speed.
+ */
enum usb_device_speed usb_get_maximum_speed(struct device *dev)
{
const char *maximum_speed;
}
EXPORT_SYMBOL_GPL(usb_get_maximum_speed);
+/**
+ * usb_get_maximum_ssp_rate - Get the signaling rate generation and lane count
+ * of a SuperSpeed Plus capable device.
+ * @dev: Pointer to the given USB controller device
+ *
+ * If the string from "maximum-speed" property is super-speed-plus-genXxY where
+ * 'X' is the generation number and 'Y' is the number of lanes, then this
+ * function returns the corresponding enum usb_ssp_rate.
+ */
enum usb_ssp_rate usb_get_maximum_ssp_rate(struct device *dev)
{
const char *maximum_speed;
}
EXPORT_SYMBOL_GPL(usb_get_maximum_ssp_rate);
+/**
+ * usb_state_string - Returns human readable name for the state.
+ * @state: The state to return a human-readable name for. If it's not
+ * any of the states devices in usb_device_state_string enum,
+ * the string UNKNOWN will be returned.
+ */
const char *usb_state_string(enum usb_device_state state)
{
static const char *const names[] = {
}
EXPORT_SYMBOL_GPL(usb_get_dr_mode);
+/**
+ * usb_decode_interval - Decode bInterval into the time expressed in 1us unit
+ * @epd: The descriptor of the endpoint
+ * @speed: The speed that the endpoint works as
+ *
+ * Function returns the interval expressed in 1us unit for servicing
+ * endpoint for data transfers.
+ */
+unsigned int usb_decode_interval(const struct usb_endpoint_descriptor *epd,
+ enum usb_device_speed speed)
+{
+ unsigned int interval = 0;
+
+ switch (usb_endpoint_type(epd)) {
+ case USB_ENDPOINT_XFER_CONTROL:
+ /* uframes per NAK */
+ if (speed == USB_SPEED_HIGH)
+ interval = epd->bInterval;
+ break;
+ case USB_ENDPOINT_XFER_ISOC:
+ interval = 1 << (epd->bInterval - 1);
+ break;
+ case USB_ENDPOINT_XFER_BULK:
+ /* uframes per NAK */
+ if (speed == USB_SPEED_HIGH && usb_endpoint_dir_out(epd))
+ interval = epd->bInterval;
+ break;
+ case USB_ENDPOINT_XFER_INT:
+ if (speed >= USB_SPEED_HIGH)
+ interval = 1 << (epd->bInterval - 1);
+ else
+ interval = epd->bInterval;
+ break;
+ }
+
+ interval *= (speed >= USB_SPEED_HIGH) ? 125 : 1000;
+
+ return interval;
+}
+EXPORT_SYMBOL_GPL(usb_decode_interval);
+
#ifdef CONFIG_OF
/**
* of_usb_get_dr_mode_by_phy - Get dual role mode for the controller device
snprintf(str, size, "Set Isochronous Delay(Delay = %d ns)", wValue);
}
-/*
- * usb_decode_ctrl - returns a string representation of ctrl request
+/**
+ * usb_decode_ctrl - Returns human readable representation of control request.
+ * @str: buffer to return a human-readable representation of control request.
+ * This buffer should have about 200 bytes.
+ * @size: size of str buffer.
+ * @bRequestType: matches the USB bmRequestType field
+ * @bRequest: matches the USB bRequest field
+ * @wValue: matches the USB wValue field (CPU byte order)
+ * @wIndex: matches the USB wIndex field (CPU byte order)
+ * @wLength: matches the USB wLength field (CPU byte order)
+ *
+ * Function returns decoded, formatted and human-readable description of
+ * control request packet.
+ *
+ * The usage scenario for this is for tracepoints, so function as a return
+ * use the same value as in parameters. This approach allows to use this
+ * function in TP_printk
+ *
+ * Important: wValue, wIndex, wLength parameters before invoking this function
+ * should be processed by le16_to_cpu macro.
*/
const char *usb_decode_ctrl(char *str, size_t size, __u8 bRequestType,
__u8 bRequest, __u16 wValue, __u16 wIndex,
switch (usb_endpoint_type(desc)) {
case USB_ENDPOINT_XFER_CONTROL:
type = "Ctrl";
- if (speed == USB_SPEED_HIGH) /* uframes per NAK */
- interval = desc->bInterval;
- else
- interval = 0;
dir = 'B'; /* ctrl is bidirectional */
break;
case USB_ENDPOINT_XFER_ISOC:
type = "Isoc";
- interval = 1 << (desc->bInterval - 1);
break;
case USB_ENDPOINT_XFER_BULK:
type = "Bulk";
- if (speed == USB_SPEED_HIGH && dir == 'O') /* uframes per NAK */
- interval = desc->bInterval;
- else
- interval = 0;
break;
case USB_ENDPOINT_XFER_INT:
type = "Int.";
- if (speed == USB_SPEED_HIGH || speed >= USB_SPEED_SUPER)
- interval = 1 << (desc->bInterval - 1);
- else
- interval = desc->bInterval;
break;
default: /* "can't happen" */
return start;
}
- interval *= (speed == USB_SPEED_HIGH ||
- speed >= USB_SPEED_SUPER) ? 125 : 1000;
- if (interval % 1000)
+
+ interval = usb_decode_interval(desc, speed);
+ if (interval % 1000) {
unit = 'u';
- else {
+ } else {
unit = 'm';
interval /= 1000;
}
* @driver: the driver to be bound
* @iface: the interface to which it will be bound; must be in the
* usb device's active configuration
- * @priv: driver data associated with that interface
+ * @data: driver data associated with that interface
*
* This is used by usb device drivers that need to claim more than one
* interface on a device when probing (audio and acm are current examples).
* No device driver should directly modify internal usb_interface or
* usb_device structure members.
*
- * Few drivers should need to use this routine, since the most natural
- * way to bind to an interface is to return the private data from
- * the driver's probe() method.
- *
* Callers must own the device lock, so driver probe() entries don't need
* extra locking, but other call contexts may need to explicitly claim that
* lock.
* Return: 0 on success.
*/
int usb_driver_claim_interface(struct usb_driver *driver,
- struct usb_interface *iface, void *priv)
+ struct usb_interface *iface, void *data)
{
struct device *dev;
int retval = 0;
return -ENODEV;
dev->driver = &driver->drvwrap.driver;
- usb_set_intfdata(iface, priv);
+ usb_set_intfdata(iface, data);
iface->needs_binding = 0;
iface->condition = USB_INTERFACE_BOUND;
char *buf)
{
struct ep_device *ep = to_ep_device(dev);
+ unsigned int interval;
char unit;
- unsigned interval = 0;
- unsigned in;
- in = (ep->desc->bEndpointAddress & USB_DIR_IN);
-
- switch (usb_endpoint_type(ep->desc)) {
- case USB_ENDPOINT_XFER_CONTROL:
- if (ep->udev->speed == USB_SPEED_HIGH)
- /* uframes per NAK */
- interval = ep->desc->bInterval;
- break;
-
- case USB_ENDPOINT_XFER_ISOC:
- interval = 1 << (ep->desc->bInterval - 1);
- break;
-
- case USB_ENDPOINT_XFER_BULK:
- if (ep->udev->speed == USB_SPEED_HIGH && !in)
- /* uframes per NAK */
- interval = ep->desc->bInterval;
- break;
-
- case USB_ENDPOINT_XFER_INT:
- if (ep->udev->speed == USB_SPEED_HIGH)
- interval = 1 << (ep->desc->bInterval - 1);
- else
- interval = ep->desc->bInterval;
- break;
- }
- interval *= (ep->udev->speed == USB_SPEED_HIGH) ? 125 : 1000;
- if (interval % 1000)
+ interval = usb_decode_interval(ep->desc, ep->udev->speed);
+ if (interval % 1000) {
unit = 'u';
- else {
+ } else {
unit = 'm';
interval /= 1000;
}
rhdev->rx_lanes = 1;
rhdev->tx_lanes = 1;
+ rhdev->ssp_rate = USB_SSP_GEN_UNKNOWN;
switch (hcd->speed) {
case HCD_USB11:
case HCD_USB32:
rhdev->rx_lanes = 2;
rhdev->tx_lanes = 2;
- fallthrough;
+ rhdev->ssp_rate = USB_SSP_GEN_2x2;
+ rhdev->speed = USB_SPEED_SUPER_PLUS;
+ break;
case HCD_USB31:
+ rhdev->ssp_rate = USB_SSP_GEN_2x1;
rhdev->speed = USB_SPEED_SUPER_PLUS;
break;
default:
#include <linux/pm_qos.h>
#include <linux/kobject.h>
+#include <linux/bitfield.h>
#include <linux/uaccess.h>
#include <asm/byteorder.h>
return result;
}
-/*
- * Return 1 if port speed is SuperSpeedPlus, 0 otherwise
- * check it from the link protocol field of the current speed ID attribute.
- * current speed ID is got from ext port status request. Sublink speed attribute
- * table is returned with the hub BOS SSP device capability descriptor
+/**
+ * get_port_ssp_rate - Match the extended port status to SSP rate
+ * @hdev: The hub device
+ * @ext_portstatus: extended port status
+ *
+ * Match the extended port status speed id to the SuperSpeed Plus sublink speed
+ * capability attributes. Base on the number of connected lanes and speed,
+ * return the corresponding enum usb_ssp_rate.
*/
-static int port_speed_is_ssp(struct usb_device *hdev, int speed_id)
+static enum usb_ssp_rate get_port_ssp_rate(struct usb_device *hdev,
+ u32 ext_portstatus)
{
- int ssa_count;
- u32 ss_attr;
- int i;
struct usb_ssp_cap_descriptor *ssp_cap = hdev->bos->ssp_cap;
+ u32 attr;
+ u8 speed_id;
+ u8 ssac;
+ u8 lanes;
+ int i;
if (!ssp_cap)
- return 0;
+ goto out;
- ssa_count = le32_to_cpu(ssp_cap->bmAttributes) &
+ speed_id = ext_portstatus & USB_EXT_PORT_STAT_RX_SPEED_ID;
+ lanes = USB_EXT_PORT_RX_LANES(ext_portstatus) + 1;
+
+ ssac = le32_to_cpu(ssp_cap->bmAttributes) &
USB_SSP_SUBLINK_SPEED_ATTRIBS;
- for (i = 0; i <= ssa_count; i++) {
- ss_attr = le32_to_cpu(ssp_cap->bmSublinkSpeedAttr[i]);
- if (speed_id == (ss_attr & USB_SSP_SUBLINK_SPEED_SSID))
- return !!(ss_attr & USB_SSP_SUBLINK_SPEED_LP);
+ for (i = 0; i <= ssac; i++) {
+ u8 ssid;
+
+ attr = le32_to_cpu(ssp_cap->bmSublinkSpeedAttr[i]);
+ ssid = FIELD_GET(USB_SSP_SUBLINK_SPEED_SSID, attr);
+ if (speed_id == ssid) {
+ u16 mantissa;
+ u8 lse;
+ u8 type;
+
+ /*
+ * Note: currently asymmetric lane types are only
+ * applicable for SSIC operate in SuperSpeed protocol
+ */
+ type = FIELD_GET(USB_SSP_SUBLINK_SPEED_ST, attr);
+ if (type == USB_SSP_SUBLINK_SPEED_ST_ASYM_RX ||
+ type == USB_SSP_SUBLINK_SPEED_ST_ASYM_TX)
+ goto out;
+
+ if (FIELD_GET(USB_SSP_SUBLINK_SPEED_LP, attr) !=
+ USB_SSP_SUBLINK_SPEED_LP_SSP)
+ goto out;
+
+ lse = FIELD_GET(USB_SSP_SUBLINK_SPEED_LSE, attr);
+ mantissa = FIELD_GET(USB_SSP_SUBLINK_SPEED_LSM, attr);
+
+ /* Convert to Gbps */
+ for (; lse < USB_SSP_SUBLINK_SPEED_LSE_GBPS; lse++)
+ mantissa /= 1000;
+
+ if (mantissa >= 10 && lanes == 1)
+ return USB_SSP_GEN_2x1;
+
+ if (mantissa >= 10 && lanes == 2)
+ return USB_SSP_GEN_2x2;
+
+ if (mantissa >= 5 && lanes == 2)
+ return USB_SSP_GEN_1x2;
+
+ goto out;
+ }
}
- return 0;
+
+out:
+ return USB_SSP_GEN_UNKNOWN;
}
/* Returns 1 if @hub is a WUSB root hub, 0 otherwise */
/* extended portstatus Rx and Tx lane count are zero based */
udev->rx_lanes = USB_EXT_PORT_RX_LANES(ext_portstatus) + 1;
udev->tx_lanes = USB_EXT_PORT_TX_LANES(ext_portstatus) + 1;
+ udev->ssp_rate = get_port_ssp_rate(hub->hdev, ext_portstatus);
} else {
udev->rx_lanes = 1;
udev->tx_lanes = 1;
+ udev->ssp_rate = USB_SSP_GEN_UNKNOWN;
}
if (hub_is_wusb(hub))
udev->speed = USB_SPEED_WIRELESS;
- else if (hub_is_superspeedplus(hub->hdev) &&
- port_speed_is_ssp(hub->hdev, ext_portstatus &
- USB_EXT_PORT_STAT_RX_SPEED_ID))
+ else if (udev->ssp_rate != USB_SSP_GEN_UNKNOWN)
udev->speed = USB_SPEED_SUPER_PLUS;
else if (hub_is_superspeed(hub->hdev))
udev->speed = USB_SPEED_SUPER;
u16 portchange, portstatus;
if (!test_and_set_bit(port1, hub->child_usage_bits)) {
- status = pm_runtime_get_sync(&port_dev->dev);
+ status = pm_runtime_resume_and_get(&port_dev->dev);
if (status < 0) {
dev_dbg(&udev->dev, "can't resume usb port, status %d\n",
status);
"%s SuperSpeed%s%s USB device number %d using %s\n",
(udev->config) ? "reset" : "new",
(udev->speed == USB_SPEED_SUPER_PLUS) ?
- "Plus Gen 2" : " Gen 1",
- (udev->rx_lanes == 2 && udev->tx_lanes == 2) ?
- "x2" : "",
+ " Plus" : "",
+ (udev->ssp_rate == USB_SSP_GEN_2x2) ?
+ " Gen 2x2" :
+ (udev->ssp_rate == USB_SSP_GEN_2x1) ?
+ " Gen 2x1" :
+ (udev->ssp_rate == USB_SSP_GEN_1x2) ?
+ " Gen 1x2" : "",
devnum, driver_name);
}
{
unsigned delay = hub->descriptor->bPwrOn2PwrGood * 2;
- /* Wait at least 100 msec for power to become stable */
- return max(delay, 100U);
+ if (!hub->hdev->parent) /* root hub */
+ return delay;
+ else /* Wait at least 100 msec for power to become stable */
+ return max(delay, 100U);
}
static inline int hub_port_debounce_be_connected(struct usb_hub *hub,
/* Realtek hub in Dell WD19 (Type-C) */
{ USB_DEVICE(0x0bda, 0x0487), .driver_info = USB_QUIRK_NO_LPM },
+ { USB_DEVICE(0x0bda, 0x5487), .driver_info = USB_QUIRK_RESET_RESUME },
/* Generic RTL8153 based ethernet adapters */
{ USB_DEVICE(0x0bda, 0x8153), .driver_info = USB_QUIRK_NO_LPM },
{ USB_DEVICE(0x17ef, 0xa012), .driver_info =
USB_QUIRK_DISCONNECT_SUSPEND },
+ /* Lenovo ThinkPad USB-C Dock Gen2 Ethernet (RTL8153 GigE) */
+ { USB_DEVICE(0x17ef, 0xa387), .driver_info = USB_QUIRK_NO_LPM },
+
/* BUILDWIN Photo Frame */
{ USB_DEVICE(0x1908, 0x1315), .driver_info =
USB_QUIRK_HONOR_BNUMINTERFACES },
speed = "5000";
break;
case USB_SPEED_SUPER_PLUS:
- speed = "10000";
+ if (udev->ssp_rate == USB_SSP_GEN_2x2)
+ speed = "20000";
+ else
+ speed = "10000";
break;
default:
speed = "unknown";
}
EXPORT_SYMBOL_GPL(usb_for_each_dev);
+struct each_hub_arg {
+ void *data;
+ int (*fn)(struct device *, void *);
+};
+
+static int __each_hub(struct usb_device *hdev, void *data)
+{
+ struct each_hub_arg *arg = (struct each_hub_arg *)data;
+ struct usb_hub *hub;
+ int ret = 0;
+ int i;
+
+ hub = usb_hub_to_struct_hub(hdev);
+ if (!hub)
+ return 0;
+
+ mutex_lock(&usb_port_peer_mutex);
+
+ for (i = 0; i < hdev->maxchild; i++) {
+ ret = arg->fn(&hub->ports[i]->dev, arg->data);
+ if (ret)
+ break;
+ }
+
+ mutex_unlock(&usb_port_peer_mutex);
+
+ return ret;
+}
+
+/**
+ * usb_for_each_port - interate over all USB ports in the system
+ * @data: data pointer that will be handed to the callback function
+ * @fn: callback function to be called for each USB port
+ *
+ * Iterate over all USB ports and call @fn for each, passing it @data. If it
+ * returns anything other than 0, we break the iteration prematurely and return
+ * that value.
+ */
+int usb_for_each_port(void *data, int (*fn)(struct device *, void *))
+{
+ struct each_hub_arg arg = {data, fn};
+
+ return usb_for_each_dev(&arg, __each_hub);
+}
+EXPORT_SYMBOL_GPL(usb_for_each_port);
+
/**
* usb_release_dev - free a usb device structure when all users of it are finished.
* @dev: device that's been disconnected
.notifier_call = usb_bus_notify,
};
-static struct dentry *usb_devices_root;
-
static void usb_debugfs_init(void)
{
- usb_devices_root = debugfs_create_file("devices", 0444, usb_debug_root,
- NULL, &usbfs_devices_fops);
+ debugfs_create_file("devices", 0444, usb_debug_root, NULL,
+ &usbfs_devices_fops);
}
static void usb_debugfs_cleanup(void)
{
- debugfs_remove(usb_devices_root);
+ debugfs_remove(debugfs_lookup("devices", usb_debug_root));
}
/*
* dwc2_exit_partial_power_down() - Exit controller from Partial Power Down.
*
* @hsotg: Programming view of the DWC_otg controller
+ * @rem_wakeup: indicates whether resume is initiated by Reset.
* @restore: Controller registers need to be restored
*/
-int dwc2_exit_partial_power_down(struct dwc2_hsotg *hsotg, bool restore)
+int dwc2_exit_partial_power_down(struct dwc2_hsotg *hsotg, int rem_wakeup,
+ bool restore)
{
- u32 pcgcctl;
- int ret = 0;
-
- if (hsotg->params.power_down != DWC2_POWER_DOWN_PARAM_PARTIAL)
- return -ENOTSUPP;
-
- pcgcctl = dwc2_readl(hsotg, PCGCTL);
- pcgcctl &= ~PCGCTL_STOPPCLK;
- dwc2_writel(hsotg, pcgcctl, PCGCTL);
-
- pcgcctl = dwc2_readl(hsotg, PCGCTL);
- pcgcctl &= ~PCGCTL_PWRCLMP;
- dwc2_writel(hsotg, pcgcctl, PCGCTL);
-
- pcgcctl = dwc2_readl(hsotg, PCGCTL);
- pcgcctl &= ~PCGCTL_RSTPDWNMODULE;
- dwc2_writel(hsotg, pcgcctl, PCGCTL);
+ struct dwc2_gregs_backup *gr;
- udelay(100);
- if (restore) {
- ret = dwc2_restore_global_registers(hsotg);
- if (ret) {
- dev_err(hsotg->dev, "%s: failed to restore registers\n",
- __func__);
- return ret;
- }
- if (dwc2_is_host_mode(hsotg)) {
- ret = dwc2_restore_host_registers(hsotg);
- if (ret) {
- dev_err(hsotg->dev, "%s: failed to restore host registers\n",
- __func__);
- return ret;
- }
- } else {
- ret = dwc2_restore_device_registers(hsotg, 0);
- if (ret) {
- dev_err(hsotg->dev, "%s: failed to restore device registers\n",
- __func__);
- return ret;
- }
- }
- }
+ gr = &hsotg->gr_backup;
- return ret;
+ /*
+ * Restore host or device regisers with the same mode core enterted
+ * to partial power down by checking "GOTGCTL_CURMODE_HOST" backup
+ * value of the "gotgctl" register.
+ */
+ if (gr->gotgctl & GOTGCTL_CURMODE_HOST)
+ return dwc2_host_exit_partial_power_down(hsotg, rem_wakeup,
+ restore);
+ else
+ return dwc2_gadget_exit_partial_power_down(hsotg, restore);
}
/**
*/
int dwc2_enter_partial_power_down(struct dwc2_hsotg *hsotg)
{
- u32 pcgcctl;
- int ret = 0;
-
- if (!hsotg->params.power_down)
- return -ENOTSUPP;
-
- /* Backup all registers */
- ret = dwc2_backup_global_registers(hsotg);
- if (ret) {
- dev_err(hsotg->dev, "%s: failed to backup global registers\n",
- __func__);
- return ret;
- }
-
- if (dwc2_is_host_mode(hsotg)) {
- ret = dwc2_backup_host_registers(hsotg);
- if (ret) {
- dev_err(hsotg->dev, "%s: failed to backup host registers\n",
- __func__);
- return ret;
- }
- } else {
- ret = dwc2_backup_device_registers(hsotg);
- if (ret) {
- dev_err(hsotg->dev, "%s: failed to backup device registers\n",
- __func__);
- return ret;
- }
- }
-
- /*
- * Clear any pending interrupts since dwc2 will not be able to
- * clear them after entering partial_power_down.
- */
- dwc2_writel(hsotg, 0xffffffff, GINTSTS);
-
- /* Put the controller in low power state */
- pcgcctl = dwc2_readl(hsotg, PCGCTL);
-
- pcgcctl |= PCGCTL_PWRCLMP;
- dwc2_writel(hsotg, pcgcctl, PCGCTL);
- ndelay(20);
-
- pcgcctl |= PCGCTL_RSTPDWNMODULE;
- dwc2_writel(hsotg, pcgcctl, PCGCTL);
- ndelay(20);
-
- pcgcctl |= PCGCTL_STOPPCLK;
- dwc2_writel(hsotg, pcgcctl, PCGCTL);
-
- return ret;
+ if (dwc2_is_host_mode(hsotg))
+ return dwc2_host_enter_partial_power_down(hsotg);
+ else
+ return dwc2_gadget_enter_partial_power_down(hsotg);
}
/**
__func__);
} else {
dev_dbg(hsotg->dev, "restore done generated here\n");
+
+ /*
+ * To avoid restore done interrupt storm after restore is
+ * generated clear GINTSTS_RESTOREDONE bit.
+ */
+ dwc2_writel(hsotg, GINTSTS_RESTOREDONE, GINTSTS);
}
}
*/
int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg, int is_host)
{
- if (hsotg->params.power_down != DWC2_POWER_DOWN_PARAM_HIBERNATION)
- return -ENOTSUPP;
-
if (is_host)
return dwc2_host_enter_hibernation(hsotg);
else
dwc2_writel(hsotg, greset, GRSTCTL);
}
+ /*
+ * Switching from device mode to host mode by disconnecting
+ * device cable core enters and exits form hibernation.
+ * However, the fifo map remains not cleared. It results
+ * to a WARNING (WARNING: CPU: 5 PID: 0 at drivers/usb/dwc2/
+ * gadget.c:307 dwc2_hsotg_init_fifo+0x12/0x152 [dwc2])
+ * if in host mode we disconnect the micro a to b host
+ * cable. Because core reset occurs.
+ * To avoid the WARNING, fifo_map should be cleared
+ * in dwc2_core_reset() function by taking into account configs.
+ * fifo_map must be cleared only if driver is configured in
+ * "CONFIG_USB_DWC2_PERIPHERAL" or "CONFIG_USB_DWC2_DUAL_ROLE"
+ * mode.
+ */
+ dwc2_clear_fifo_map(hsotg);
+
/* Wait for AHB master IDLE state */
if (dwc2_hsotg_wait_bit_set(hsotg, GRSTCTL, GRSTCTL_AHBIDLE, 10000)) {
dev_warn(hsotg->dev, "%s: HANG! AHB Idle timeout GRSTCTL GRSTCTL_AHBIDLE\n",
#ifndef __DWC2_CORE_H__
#define __DWC2_CORE_H__
+#include <linux/acpi.h>
#include <linux/phy/phy.h>
#include <linux/regulator/consumer.h>
#include <linux/usb/gadget.h>
* @g_tx_fifo_size: An array of TX fifo sizes in dedicated fifo
* mode. Each value corresponds to one EP
* starting from EP1 (max 15 values). Sizes are
- * in DWORDS with possible values from from
+ * in DWORDS with possible values from
* 16-32768 (default: 256, 256, 256, 256, 768,
* 768, 768, 768, 0, 0, 0, 0, 0, 0, 0).
* @change_speed_quirk: Change speed configuration to DWC2_SPEED_PARAM_FULL
* @gadget_enabled: Peripheral mode sub-driver initialization indicator.
* @ll_hw_enabled: Status of low-level hardware resources.
* @hibernated: True if core is hibernated
+ * @in_ppd: True if core is partial power down mode.
+ * @bus_suspended: True if bus is suspended
* @reset_phy_on_wake: Quirk saying that we should assert PHY reset on a
* remote wakeup.
* @phy_off_for_suspend: Status of whether we turned the PHY off at suspend.
* a pointer to an array of register definitions, the
* array size and the base address where the register bank
* is to be found.
- * @bus_suspended: True if bus is suspended
* @last_frame_num: Number of last frame. Range from 0 to 32768
* @frame_num_array: Used only if CONFIG_USB_DWC2_TRACK_MISSED_SOFS is
* defined, for missed SOFs tracking. Array holds that
unsigned int gadget_enabled:1;
unsigned int ll_hw_enabled:1;
unsigned int hibernated:1;
+ unsigned int in_ppd:1;
+ bool bus_suspended;
unsigned int reset_phy_on_wake:1;
unsigned int need_phy_for_wake:1;
unsigned int phy_off_for_suspend:1;
unsigned long hs_periodic_bitmap[
DIV_ROUND_UP(DWC2_HS_SCHEDULE_US, BITS_PER_LONG)];
u16 periodic_qh_count;
- bool bus_suspended;
bool new_connection;
u16 last_frame_num;
*/
int dwc2_core_reset(struct dwc2_hsotg *hsotg, bool skip_wait);
int dwc2_enter_partial_power_down(struct dwc2_hsotg *hsotg);
-int dwc2_exit_partial_power_down(struct dwc2_hsotg *hsotg, bool restore);
+int dwc2_exit_partial_power_down(struct dwc2_hsotg *hsotg, int rem_wakeup,
+ bool restore);
int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg, int is_host);
int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup,
int reset, int is_host);
/* The device ID match table */
extern const struct of_device_id dwc2_of_match_table[];
+extern const struct acpi_device_id dwc2_acpi_match[];
int dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg);
int dwc2_lowlevel_hw_disable(struct dwc2_hsotg *hsotg);
int dwc2_gadget_enter_hibernation(struct dwc2_hsotg *hsotg);
int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg,
int rem_wakeup, int reset);
+int dwc2_gadget_enter_partial_power_down(struct dwc2_hsotg *hsotg);
+int dwc2_gadget_exit_partial_power_down(struct dwc2_hsotg *hsotg,
+ bool restore);
+void dwc2_gadget_enter_clock_gating(struct dwc2_hsotg *hsotg);
+void dwc2_gadget_exit_clock_gating(struct dwc2_hsotg *hsotg,
+ int rem_wakeup);
int dwc2_hsotg_tx_fifo_count(struct dwc2_hsotg *hsotg);
int dwc2_hsotg_tx_fifo_total_depth(struct dwc2_hsotg *hsotg);
int dwc2_hsotg_tx_fifo_average_depth(struct dwc2_hsotg *hsotg);
void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg);
void dwc2_gadget_program_ref_clk(struct dwc2_hsotg *hsotg);
+static inline void dwc2_clear_fifo_map(struct dwc2_hsotg *hsotg)
+{ hsotg->fifo_map = 0; }
#else
static inline int dwc2_hsotg_remove(struct dwc2_hsotg *dwc2)
{ return 0; }
static inline int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg,
int rem_wakeup, int reset)
{ return 0; }
+static inline int dwc2_gadget_enter_partial_power_down(struct dwc2_hsotg *hsotg)
+{ return 0; }
+static inline int dwc2_gadget_exit_partial_power_down(struct dwc2_hsotg *hsotg,
+ bool restore)
+{ return 0; }
+static inline void dwc2_gadget_enter_clock_gating(struct dwc2_hsotg *hsotg) {}
+static inline void dwc2_gadget_exit_clock_gating(struct dwc2_hsotg *hsotg,
+ int rem_wakeup) {}
static inline int dwc2_hsotg_tx_fifo_count(struct dwc2_hsotg *hsotg)
{ return 0; }
static inline int dwc2_hsotg_tx_fifo_total_depth(struct dwc2_hsotg *hsotg)
{ return 0; }
static inline void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg) {}
static inline void dwc2_gadget_program_ref_clk(struct dwc2_hsotg *hsotg) {}
+static inline void dwc2_clear_fifo_map(struct dwc2_hsotg *hsotg) {}
#endif
#if IS_ENABLED(CONFIG_USB_DWC2_HOST) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE)
void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg, bool force);
void dwc2_hcd_start(struct dwc2_hsotg *hsotg);
int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup);
+int dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex);
+int dwc2_port_resume(struct dwc2_hsotg *hsotg);
int dwc2_backup_host_registers(struct dwc2_hsotg *hsotg);
int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg);
int dwc2_host_enter_hibernation(struct dwc2_hsotg *hsotg);
int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg,
int rem_wakeup, int reset);
+int dwc2_host_enter_partial_power_down(struct dwc2_hsotg *hsotg);
+int dwc2_host_exit_partial_power_down(struct dwc2_hsotg *hsotg,
+ int rem_wakeup, bool restore);
+void dwc2_host_enter_clock_gating(struct dwc2_hsotg *hsotg);
+void dwc2_host_exit_clock_gating(struct dwc2_hsotg *hsotg, int rem_wakeup);
bool dwc2_host_can_poweroff_phy(struct dwc2_hsotg *dwc2);
static inline void dwc2_host_schedule_phy_reset(struct dwc2_hsotg *hsotg)
{ schedule_work(&hsotg->phy_reset_work); }
static inline void dwc2_hcd_remove(struct dwc2_hsotg *hsotg) {}
static inline int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup)
{ return 0; }
+static inline int dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex)
+{ return 0; }
+static inline int dwc2_port_resume(struct dwc2_hsotg *hsotg)
+{ return 0; }
static inline int dwc2_hcd_init(struct dwc2_hsotg *hsotg)
{ return 0; }
static inline int dwc2_backup_host_registers(struct dwc2_hsotg *hsotg)
static inline int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg,
int rem_wakeup, int reset)
{ return 0; }
+static inline int dwc2_host_enter_partial_power_down(struct dwc2_hsotg *hsotg)
+{ return 0; }
+static inline int dwc2_host_exit_partial_power_down(struct dwc2_hsotg *hsotg,
+ int rem_wakeup, bool restore)
+{ return 0; }
+static inline void dwc2_host_enter_clock_gating(struct dwc2_hsotg *hsotg) {}
+static inline void dwc2_host_exit_clock_gating(struct dwc2_hsotg *hsotg,
+ int rem_wakeup) {}
static inline bool dwc2_host_can_poweroff_phy(struct dwc2_hsotg *dwc2)
{ return false; }
static inline void dwc2_host_schedule_phy_reset(struct dwc2_hsotg *hsotg) {}
static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg)
{
int ret;
+ u32 hprt0;
/* Clear interrupt */
dwc2_writel(hsotg, GINTSTS_SESSREQINT, GINTSTS);
if (dwc2_is_device_mode(hsotg)) {
if (hsotg->lx_state == DWC2_L2) {
- ret = dwc2_exit_partial_power_down(hsotg, true);
- if (ret && (ret != -ENOTSUPP))
- dev_err(hsotg->dev,
- "exit power_down failed\n");
+ if (hsotg->in_ppd) {
+ ret = dwc2_exit_partial_power_down(hsotg, 0,
+ true);
+ if (ret)
+ dev_err(hsotg->dev,
+ "exit power_down failed\n");
+ }
+
+ /* Exit gadget mode clock gating. */
+ if (hsotg->params.power_down ==
+ DWC2_POWER_DOWN_PARAM_NONE && hsotg->bus_suspended)
+ dwc2_gadget_exit_clock_gating(hsotg, 0);
}
/*
* established
*/
dwc2_hsotg_disconnect(hsotg);
+ } else {
+ /* Turn on the port power bit. */
+ hprt0 = dwc2_read_hprt0(hsotg);
+ hprt0 |= HPRT0_PWR;
+ dwc2_writel(hsotg, hprt0, HPRT0);
+ /* Connect hcd after port power is set. */
+ dwc2_hcd_connect(hsotg);
}
}
dev_dbg(hsotg->dev, "DSTS=0x%0x\n",
dwc2_readl(hsotg, DSTS));
if (hsotg->lx_state == DWC2_L2) {
- u32 dctl = dwc2_readl(hsotg, DCTL);
-
- /* Clear Remote Wakeup Signaling */
- dctl &= ~DCTL_RMTWKUPSIG;
- dwc2_writel(hsotg, dctl, DCTL);
- ret = dwc2_exit_partial_power_down(hsotg, true);
- if (ret && (ret != -ENOTSUPP))
- dev_err(hsotg->dev, "exit power_down failed\n");
+ if (hsotg->in_ppd) {
+ u32 dctl = dwc2_readl(hsotg, DCTL);
+ /* Clear Remote Wakeup Signaling */
+ dctl &= ~DCTL_RMTWKUPSIG;
+ dwc2_writel(hsotg, dctl, DCTL);
+ ret = dwc2_exit_partial_power_down(hsotg, 1,
+ true);
+ if (ret)
+ dev_err(hsotg->dev,
+ "exit partial_power_down failed\n");
+ call_gadget(hsotg, resume);
+ }
- /* Change to L0 state */
- hsotg->lx_state = DWC2_L0;
- call_gadget(hsotg, resume);
+ /* Exit gadget mode clock gating. */
+ if (hsotg->params.power_down ==
+ DWC2_POWER_DOWN_PARAM_NONE && hsotg->bus_suspended)
+ dwc2_gadget_exit_clock_gating(hsotg, 0);
} else {
/* Change to L0 state */
hsotg->lx_state = DWC2_L0;
}
} else {
- if (hsotg->params.power_down)
- return;
-
- if (hsotg->lx_state != DWC2_L1) {
- u32 pcgcctl = dwc2_readl(hsotg, PCGCTL);
+ if (hsotg->lx_state == DWC2_L2) {
+ if (hsotg->in_ppd) {
+ ret = dwc2_exit_partial_power_down(hsotg, 1,
+ true);
+ if (ret)
+ dev_err(hsotg->dev,
+ "exit partial_power_down failed\n");
+ }
- /* Restart the Phy Clock */
- pcgcctl &= ~PCGCTL_STOPPCLK;
- dwc2_writel(hsotg, pcgcctl, PCGCTL);
+ if (hsotg->params.power_down ==
+ DWC2_POWER_DOWN_PARAM_NONE && hsotg->bus_suspended)
+ dwc2_host_exit_clock_gating(hsotg, 1);
/*
* If we've got this quirk then the PHY is stuck upon
return;
}
if (dsts & DSTS_SUSPSTS) {
- if (hsotg->hw_params.power_optimized) {
+ switch (hsotg->params.power_down) {
+ case DWC2_POWER_DOWN_PARAM_PARTIAL:
ret = dwc2_enter_partial_power_down(hsotg);
- if (ret) {
- if (ret != -ENOTSUPP)
- dev_err(hsotg->dev,
- "%s: enter partial_power_down failed\n",
- __func__);
- goto skip_power_saving;
- }
+ if (ret)
+ dev_err(hsotg->dev,
+ "enter partial_power_down failed\n");
udelay(100);
/* Ask phy to be suspended */
if (!IS_ERR_OR_NULL(hsotg->uphy))
usb_phy_set_suspend(hsotg->uphy, true);
- }
-
- if (hsotg->hw_params.hibernation) {
+ break;
+ case DWC2_POWER_DOWN_PARAM_HIBERNATION:
ret = dwc2_enter_hibernation(hsotg, 0);
- if (ret && ret != -ENOTSUPP)
+ if (ret)
dev_err(hsotg->dev,
- "%s: enter hibernation failed\n",
- __func__);
+ "enter hibernation failed\n");
+ break;
+ case DWC2_POWER_DOWN_PARAM_NONE:
+ /*
+ * If neither hibernation nor partial power down are supported,
+ * clock gating is used to save power.
+ */
+ dwc2_gadget_enter_clock_gating(hsotg);
}
-skip_power_saving:
+
/*
* Change to L2 (suspend) state before releasing
* spinlock
return 0;
}
+/**
+ * dwc_handle_gpwrdn_disc_det() - Handles the gpwrdn disconnect detect.
+ * Exits hibernation without restoring registers.
+ *
+ * @hsotg: Programming view of DWC_otg controller
+ * @gpwrdn: GPWRDN register
+ */
+static inline void dwc_handle_gpwrdn_disc_det(struct dwc2_hsotg *hsotg,
+ u32 gpwrdn)
+{
+ u32 gpwrdn_tmp;
+
+ /* Switch-on voltage to the core */
+ gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
+ gpwrdn_tmp &= ~GPWRDN_PWRDNSWTCH;
+ dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
+ udelay(5);
+
+ /* Reset core */
+ gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
+ gpwrdn_tmp &= ~GPWRDN_PWRDNRSTN;
+ dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
+ udelay(5);
+
+ /* Disable Power Down Clamp */
+ gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
+ gpwrdn_tmp &= ~GPWRDN_PWRDNCLMP;
+ dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
+ udelay(5);
+
+ /* Deassert reset core */
+ gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
+ gpwrdn_tmp |= GPWRDN_PWRDNRSTN;
+ dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
+ udelay(5);
+
+ /* Disable PMU interrupt */
+ gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
+ gpwrdn_tmp &= ~GPWRDN_PMUINTSEL;
+ dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
+
+ /* De-assert Wakeup Logic */
+ gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
+ gpwrdn_tmp &= ~GPWRDN_PMUACTV;
+ dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
+
+ hsotg->hibernated = 0;
+ hsotg->bus_suspended = 0;
+
+ if (gpwrdn & GPWRDN_IDSTS) {
+ hsotg->op_state = OTG_STATE_B_PERIPHERAL;
+ dwc2_core_init(hsotg, false);
+ dwc2_enable_global_interrupts(hsotg);
+ dwc2_hsotg_core_init_disconnected(hsotg, false);
+ dwc2_hsotg_core_connect(hsotg);
+ } else {
+ hsotg->op_state = OTG_STATE_A_HOST;
+
+ /* Initialize the Core for Host mode */
+ dwc2_core_init(hsotg, false);
+ dwc2_enable_global_interrupts(hsotg);
+ dwc2_hcd_start(hsotg);
+ }
+}
+
/*
* GPWRDN interrupt handler.
*
* The GPWRDN interrupts are those that occur in both Host and
* Device mode while core is in hibernated state.
*/
-static void dwc2_handle_gpwrdn_intr(struct dwc2_hsotg *hsotg)
+static int dwc2_handle_gpwrdn_intr(struct dwc2_hsotg *hsotg)
{
u32 gpwrdn;
int linestate;
+ int ret = 0;
gpwrdn = dwc2_readl(hsotg, GPWRDN);
/* clear all interrupt */
if ((gpwrdn & GPWRDN_DISCONN_DET) &&
(gpwrdn & GPWRDN_DISCONN_DET_MSK) && !linestate) {
- u32 gpwrdn_tmp;
-
dev_dbg(hsotg->dev, "%s: GPWRDN_DISCONN_DET\n", __func__);
-
- /* Switch-on voltage to the core */
- gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
- gpwrdn_tmp &= ~GPWRDN_PWRDNSWTCH;
- dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
- udelay(10);
-
- /* Reset core */
- gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
- gpwrdn_tmp &= ~GPWRDN_PWRDNRSTN;
- dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
- udelay(10);
-
- /* Disable Power Down Clamp */
- gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
- gpwrdn_tmp &= ~GPWRDN_PWRDNCLMP;
- dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
- udelay(10);
-
- /* Deassert reset core */
- gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
- gpwrdn_tmp |= GPWRDN_PWRDNRSTN;
- dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
- udelay(10);
-
- /* Disable PMU interrupt */
- gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
- gpwrdn_tmp &= ~GPWRDN_PMUINTSEL;
- dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
-
- /* De-assert Wakeup Logic */
- gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN);
- gpwrdn_tmp &= ~GPWRDN_PMUACTV;
- dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN);
-
- hsotg->hibernated = 0;
-
- if (gpwrdn & GPWRDN_IDSTS) {
- hsotg->op_state = OTG_STATE_B_PERIPHERAL;
- dwc2_core_init(hsotg, false);
- dwc2_enable_global_interrupts(hsotg);
- dwc2_hsotg_core_init_disconnected(hsotg, false);
- dwc2_hsotg_core_connect(hsotg);
- } else {
- hsotg->op_state = OTG_STATE_A_HOST;
-
- /* Initialize the Core for Host mode */
- dwc2_core_init(hsotg, false);
- dwc2_enable_global_interrupts(hsotg);
- dwc2_hcd_start(hsotg);
- }
- }
-
- if ((gpwrdn & GPWRDN_LNSTSCHG) &&
- (gpwrdn & GPWRDN_LNSTSCHG_MSK) && linestate) {
+ /*
+ * Call disconnect detect function to exit from
+ * hibernation
+ */
+ dwc_handle_gpwrdn_disc_det(hsotg, gpwrdn);
+ } else if ((gpwrdn & GPWRDN_LNSTSCHG) &&
+ (gpwrdn & GPWRDN_LNSTSCHG_MSK) && linestate) {
dev_dbg(hsotg->dev, "%s: GPWRDN_LNSTSCHG\n", __func__);
if (hsotg->hw_params.hibernation &&
hsotg->hibernated) {
if (gpwrdn & GPWRDN_IDSTS) {
- dwc2_exit_hibernation(hsotg, 0, 0, 0);
+ ret = dwc2_exit_hibernation(hsotg, 0, 0, 0);
+ if (ret)
+ dev_err(hsotg->dev,
+ "exit hibernation failed.\n");
call_gadget(hsotg, resume);
} else {
- dwc2_exit_hibernation(hsotg, 1, 0, 1);
+ ret = dwc2_exit_hibernation(hsotg, 1, 0, 1);
+ if (ret)
+ dev_err(hsotg->dev,
+ "exit hibernation failed.\n");
}
}
- }
- if ((gpwrdn & GPWRDN_RST_DET) && (gpwrdn & GPWRDN_RST_DET_MSK)) {
+ } else if ((gpwrdn & GPWRDN_RST_DET) &&
+ (gpwrdn & GPWRDN_RST_DET_MSK)) {
dev_dbg(hsotg->dev, "%s: GPWRDN_RST_DET\n", __func__);
- if (!linestate && (gpwrdn & GPWRDN_BSESSVLD))
- dwc2_exit_hibernation(hsotg, 0, 1, 0);
- }
- if ((gpwrdn & GPWRDN_STS_CHGINT) &&
- (gpwrdn & GPWRDN_STS_CHGINT_MSK) && linestate) {
- dev_dbg(hsotg->dev, "%s: GPWRDN_STS_CHGINT\n", __func__);
- if (hsotg->hw_params.hibernation &&
- hsotg->hibernated) {
- if (gpwrdn & GPWRDN_IDSTS) {
- dwc2_exit_hibernation(hsotg, 0, 0, 0);
- call_gadget(hsotg, resume);
- } else {
- dwc2_exit_hibernation(hsotg, 1, 0, 1);
- }
+ if (!linestate) {
+ ret = dwc2_exit_hibernation(hsotg, 0, 1, 0);
+ if (ret)
+ dev_err(hsotg->dev,
+ "exit hibernation failed.\n");
}
+ } else if ((gpwrdn & GPWRDN_STS_CHGINT) &&
+ (gpwrdn & GPWRDN_STS_CHGINT_MSK)) {
+ dev_dbg(hsotg->dev, "%s: GPWRDN_STS_CHGINT\n", __func__);
+ /*
+ * As GPWRDN_STS_CHGINT exit from hibernation flow is
+ * the same as in GPWRDN_DISCONN_DET flow. Call
+ * disconnect detect helper function to exit from
+ * hibernation.
+ */
+ dwc_handle_gpwrdn_disc_det(hsotg, gpwrdn);
}
+
+ return ret;
}
/*
print_param(seq, p, ulpi_fs_ls);
print_param(seq, p, host_support_fs_ls_low_power);
print_param(seq, p, host_ls_low_power_phy_clk);
+ print_param(seq, p, activate_stm_fs_transceiver);
+ print_param(seq, p, activate_stm_id_vb_detection);
print_param(seq, p, ts_dline);
print_param(seq, p, reload_ctl);
print_param_hex(seq, p, ahbcfg);
dwc2_writel(hsotg, GINTSTS_RESETDET, GINTSTS);
/* This event must be used only if controller is suspended */
- if (hsotg->lx_state == DWC2_L2) {
- dwc2_exit_partial_power_down(hsotg, true);
- hsotg->lx_state = DWC2_L0;
- }
+ if (hsotg->in_ppd && hsotg->lx_state == DWC2_L2)
+ dwc2_exit_partial_power_down(hsotg, 0, true);
+
+ hsotg->lx_state = DWC2_L0;
}
if (gintsts & (GINTSTS_USBRST | GINTSTS_RESETDET)) {
spin_lock_irqsave(&hsotg->lock, flags);
/*
- * If controller is hibernated, it must exit from power_down
- * before being initialized / de-initialized
+ * If controller is in partial power down state, it must exit from
+ * that state before being initialized / de-initialized
*/
- if (hsotg->lx_state == DWC2_L2)
- dwc2_exit_partial_power_down(hsotg, false);
+ if (hsotg->lx_state == DWC2_L2 && hsotg->in_ppd)
+ /*
+ * No need to check the return value as
+ * registers are not being restored.
+ */
+ dwc2_exit_partial_power_down(hsotg, 0, false);
if (is_active) {
hsotg->op_state = OTG_STATE_B_PERIPHERAL;
dwc2_writel(hsotg, dr->dcfg, DCFG);
dwc2_writel(hsotg, dr->dctl, DCTL);
+ /* On USB Reset, reset device address to zero */
+ if (reset)
+ dwc2_clear_bit(hsotg, DCFG, DCFG_DEVADDR_MASK);
+
/* De-assert Wakeup Logic */
gpwrdn = dwc2_readl(hsotg, GPWRDN);
gpwrdn &= ~GPWRDN_PMUACTV;
return ret;
}
+
+/**
+ * dwc2_gadget_enter_partial_power_down() - Put controller in partial
+ * power down.
+ *
+ * @hsotg: Programming view of the DWC_otg controller
+ *
+ * Return: non-zero if failed to enter device partial power down.
+ *
+ * This function is for entering device mode partial power down.
+ */
+int dwc2_gadget_enter_partial_power_down(struct dwc2_hsotg *hsotg)
+{
+ u32 pcgcctl;
+ int ret = 0;
+
+ dev_dbg(hsotg->dev, "Entering device partial power down started.\n");
+
+ /* Backup all registers */
+ ret = dwc2_backup_global_registers(hsotg);
+ if (ret) {
+ dev_err(hsotg->dev, "%s: failed to backup global registers\n",
+ __func__);
+ return ret;
+ }
+
+ ret = dwc2_backup_device_registers(hsotg);
+ if (ret) {
+ dev_err(hsotg->dev, "%s: failed to backup device registers\n",
+ __func__);
+ return ret;
+ }
+
+ /*
+ * Clear any pending interrupts since dwc2 will not be able to
+ * clear them after entering partial_power_down.
+ */
+ dwc2_writel(hsotg, 0xffffffff, GINTSTS);
+
+ /* Put the controller in low power state */
+ pcgcctl = dwc2_readl(hsotg, PCGCTL);
+
+ pcgcctl |= PCGCTL_PWRCLMP;
+ dwc2_writel(hsotg, pcgcctl, PCGCTL);
+ udelay(5);
+
+ pcgcctl |= PCGCTL_RSTPDWNMODULE;
+ dwc2_writel(hsotg, pcgcctl, PCGCTL);
+ udelay(5);
+
+ pcgcctl |= PCGCTL_STOPPCLK;
+ dwc2_writel(hsotg, pcgcctl, PCGCTL);
+
+ /* Set in_ppd flag to 1 as here core enters suspend. */
+ hsotg->in_ppd = 1;
+ hsotg->lx_state = DWC2_L2;
+
+ dev_dbg(hsotg->dev, "Entering device partial power down completed.\n");
+
+ return ret;
+}
+
+/*
+ * dwc2_gadget_exit_partial_power_down() - Exit controller from device partial
+ * power down.
+ *
+ * @hsotg: Programming view of the DWC_otg controller
+ * @restore: indicates whether need to restore the registers or not.
+ *
+ * Return: non-zero if failed to exit device partial power down.
+ *
+ * This function is for exiting from device mode partial power down.
+ */
+int dwc2_gadget_exit_partial_power_down(struct dwc2_hsotg *hsotg,
+ bool restore)
+{
+ u32 pcgcctl;
+ u32 dctl;
+ struct dwc2_dregs_backup *dr;
+ int ret = 0;
+
+ dr = &hsotg->dr_backup;
+
+ dev_dbg(hsotg->dev, "Exiting device partial Power Down started.\n");
+
+ pcgcctl = dwc2_readl(hsotg, PCGCTL);
+ pcgcctl &= ~PCGCTL_STOPPCLK;
+ dwc2_writel(hsotg, pcgcctl, PCGCTL);
+
+ pcgcctl = dwc2_readl(hsotg, PCGCTL);
+ pcgcctl &= ~PCGCTL_PWRCLMP;
+ dwc2_writel(hsotg, pcgcctl, PCGCTL);
+
+ pcgcctl = dwc2_readl(hsotg, PCGCTL);
+ pcgcctl &= ~PCGCTL_RSTPDWNMODULE;
+ dwc2_writel(hsotg, pcgcctl, PCGCTL);
+
+ udelay(100);
+ if (restore) {
+ ret = dwc2_restore_global_registers(hsotg);
+ if (ret) {
+ dev_err(hsotg->dev, "%s: failed to restore registers\n",
+ __func__);
+ return ret;
+ }
+ /* Restore DCFG */
+ dwc2_writel(hsotg, dr->dcfg, DCFG);
+
+ ret = dwc2_restore_device_registers(hsotg, 0);
+ if (ret) {
+ dev_err(hsotg->dev, "%s: failed to restore device registers\n",
+ __func__);
+ return ret;
+ }
+ }
+
+ /* Set the Power-On Programming done bit */
+ dctl = dwc2_readl(hsotg, DCTL);
+ dctl |= DCTL_PWRONPRGDONE;
+ dwc2_writel(hsotg, dctl, DCTL);
+
+ /* Set in_ppd flag to 0 as here core exits from suspend. */
+ hsotg->in_ppd = 0;
+ hsotg->lx_state = DWC2_L0;
+
+ dev_dbg(hsotg->dev, "Exiting device partial Power Down completed.\n");
+ return ret;
+}
+
+/**
+ * dwc2_gadget_enter_clock_gating() - Put controller in clock gating.
+ *
+ * @hsotg: Programming view of the DWC_otg controller
+ *
+ * Return: non-zero if failed to enter device partial power down.
+ *
+ * This function is for entering device mode clock gating.
+ */
+void dwc2_gadget_enter_clock_gating(struct dwc2_hsotg *hsotg)
+{
+ u32 pcgctl;
+
+ dev_dbg(hsotg->dev, "Entering device clock gating.\n");
+
+ /* Set the Phy Clock bit as suspend is received. */
+ pcgctl = dwc2_readl(hsotg, PCGCTL);
+ pcgctl |= PCGCTL_STOPPCLK;
+ dwc2_writel(hsotg, pcgctl, PCGCTL);
+ udelay(5);
+
+ /* Set the Gate hclk as suspend is received. */
+ pcgctl = dwc2_readl(hsotg, PCGCTL);
+ pcgctl |= PCGCTL_GATEHCLK;
+ dwc2_writel(hsotg, pcgctl, PCGCTL);
+ udelay(5);
+
+ hsotg->lx_state = DWC2_L2;
+ hsotg->bus_suspended = true;
+}
+
+/*
+ * dwc2_gadget_exit_clock_gating() - Exit controller from device clock gating.
+ *
+ * @hsotg: Programming view of the DWC_otg controller
+ * @rem_wakeup: indicates whether remote wake up is enabled.
+ *
+ * This function is for exiting from device mode clock gating.
+ */
+void dwc2_gadget_exit_clock_gating(struct dwc2_hsotg *hsotg, int rem_wakeup)
+{
+ u32 pcgctl;
+ u32 dctl;
+
+ dev_dbg(hsotg->dev, "Exiting device clock gating.\n");
+
+ /* Clear the Gate hclk. */
+ pcgctl = dwc2_readl(hsotg, PCGCTL);
+ pcgctl &= ~PCGCTL_GATEHCLK;
+ dwc2_writel(hsotg, pcgctl, PCGCTL);
+ udelay(5);
+
+ /* Phy Clock bit. */
+ pcgctl = dwc2_readl(hsotg, PCGCTL);
+ pcgctl &= ~PCGCTL_STOPPCLK;
+ dwc2_writel(hsotg, pcgctl, PCGCTL);
+ udelay(5);
+
+ if (rem_wakeup) {
+ /* Set Remote Wakeup Signaling */
+ dctl = dwc2_readl(hsotg, DCTL);
+ dctl |= DCTL_RMTWKUPSIG;
+ dwc2_writel(hsotg, dctl, DCTL);
+ }
+
+ /* Change to L0 state */
+ call_gadget(hsotg, resume);
+ hsotg->lx_state = DWC2_L0;
+ hsotg->bus_suspended = false;
+}
#include "core.h"
#include "hcd.h"
-static void dwc2_port_resume(struct dwc2_hsotg *hsotg);
-
/*
* =========================================================================
* Host Core Layer Functions
if (count > 250)
dev_err(hsotg->dev,
"Connection id status change timed out\n");
+
+ /*
+ * Exit Partial Power Down without restoring registers.
+ * No need to check the return value as registers
+ * are not being restored.
+ */
+ if (hsotg->in_ppd && hsotg->lx_state == DWC2_L2)
+ dwc2_exit_partial_power_down(hsotg, 0, false);
+
hsotg->op_state = OTG_STATE_B_PERIPHERAL;
dwc2_core_init(hsotg, false);
dwc2_enable_global_interrupts(hsotg);
return hcd->self.b_hnp_enable;
}
-/* Must NOT be called with interrupt disabled or spinlock held */
-static void dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex)
+/**
+ * dwc2_port_suspend() - Put controller in suspend mode for host.
+ *
+ * @hsotg: Programming view of the DWC_otg controller
+ * @windex: The control request wIndex field
+ *
+ * Return: non-zero if failed to enter suspend mode for host.
+ *
+ * This function is for entering Host mode suspend.
+ * Must NOT be called with interrupt disabled or spinlock held.
+ */
+int dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex)
{
unsigned long flags;
- u32 hprt0;
u32 pcgctl;
u32 gotgctl;
+ int ret = 0;
dev_dbg(hsotg->dev, "%s()\n", __func__);
hsotg->op_state = OTG_STATE_A_SUSPEND;
}
- hprt0 = dwc2_read_hprt0(hsotg);
- hprt0 |= HPRT0_SUSP;
- dwc2_writel(hsotg, hprt0, HPRT0);
-
- hsotg->bus_suspended = true;
-
- /*
- * If power_down is supported, Phy clock will be suspended
- * after registers are backuped.
- */
- if (!hsotg->params.power_down) {
- /* Suspend the Phy Clock */
- pcgctl = dwc2_readl(hsotg, PCGCTL);
- pcgctl |= PCGCTL_STOPPCLK;
- dwc2_writel(hsotg, pcgctl, PCGCTL);
- udelay(10);
+ switch (hsotg->params.power_down) {
+ case DWC2_POWER_DOWN_PARAM_PARTIAL:
+ ret = dwc2_enter_partial_power_down(hsotg);
+ if (ret)
+ dev_err(hsotg->dev,
+ "enter partial_power_down failed.\n");
+ break;
+ case DWC2_POWER_DOWN_PARAM_HIBERNATION:
+ /*
+ * Perform spin unlock and lock because in
+ * "dwc2_host_enter_hibernation()" function there is a spinlock
+ * logic which prevents servicing of any IRQ during entering
+ * hibernation.
+ */
+ spin_unlock_irqrestore(&hsotg->lock, flags);
+ ret = dwc2_enter_hibernation(hsotg, 1);
+ if (ret)
+ dev_err(hsotg->dev, "enter hibernation failed.\n");
+ spin_lock_irqsave(&hsotg->lock, flags);
+ break;
+ case DWC2_POWER_DOWN_PARAM_NONE:
+ /*
+ * If not hibernation nor partial power down are supported,
+ * clock gating is used to save power.
+ */
+ dwc2_host_enter_clock_gating(hsotg);
+ break;
}
/* For HNP the bus must be suspended for at least 200ms */
} else {
spin_unlock_irqrestore(&hsotg->lock, flags);
}
+
+ return ret;
}
-/* Must NOT be called with interrupt disabled or spinlock held */
-static void dwc2_port_resume(struct dwc2_hsotg *hsotg)
+/**
+ * dwc2_port_resume() - Exit controller from suspend mode for host.
+ *
+ * @hsotg: Programming view of the DWC_otg controller
+ *
+ * Return: non-zero if failed to exit suspend mode for host.
+ *
+ * This function is for exiting Host mode suspend.
+ * Must NOT be called with interrupt disabled or spinlock held.
+ */
+int dwc2_port_resume(struct dwc2_hsotg *hsotg)
{
unsigned long flags;
- u32 hprt0;
- u32 pcgctl;
+ int ret = 0;
spin_lock_irqsave(&hsotg->lock, flags);
- /*
- * If power_down is supported, Phy clock is already resumed
- * after registers restore.
- */
- if (!hsotg->params.power_down) {
- pcgctl = dwc2_readl(hsotg, PCGCTL);
- pcgctl &= ~PCGCTL_STOPPCLK;
- dwc2_writel(hsotg, pcgctl, PCGCTL);
+ switch (hsotg->params.power_down) {
+ case DWC2_POWER_DOWN_PARAM_PARTIAL:
+ ret = dwc2_exit_partial_power_down(hsotg, 0, true);
+ if (ret)
+ dev_err(hsotg->dev,
+ "exit partial_power_down failed.\n");
+ break;
+ case DWC2_POWER_DOWN_PARAM_HIBERNATION:
+ /* Exit host hibernation. */
+ ret = dwc2_exit_hibernation(hsotg, 0, 0, 1);
+ if (ret)
+ dev_err(hsotg->dev, "exit hibernation failed.\n");
+ break;
+ case DWC2_POWER_DOWN_PARAM_NONE:
+ /*
+ * If not hibernation nor partial power down are supported,
+ * port resume is done using the clock gating programming flow.
+ */
spin_unlock_irqrestore(&hsotg->lock, flags);
- msleep(20);
+ dwc2_host_exit_clock_gating(hsotg, 0);
spin_lock_irqsave(&hsotg->lock, flags);
+ break;
}
- hprt0 = dwc2_read_hprt0(hsotg);
- hprt0 |= HPRT0_RES;
- hprt0 &= ~HPRT0_SUSP;
- dwc2_writel(hsotg, hprt0, HPRT0);
spin_unlock_irqrestore(&hsotg->lock, flags);
- msleep(USB_RESUME_TIMEOUT);
-
- spin_lock_irqsave(&hsotg->lock, flags);
- hprt0 = dwc2_read_hprt0(hsotg);
- hprt0 &= ~(HPRT0_RES | HPRT0_SUSP);
- dwc2_writel(hsotg, hprt0, HPRT0);
- hsotg->bus_suspended = false;
- spin_unlock_irqrestore(&hsotg->lock, flags);
+ return ret;
}
/* Handles hub class-specific requests */
dev_dbg(hsotg->dev,
"ClearPortFeature USB_PORT_FEAT_SUSPEND\n");
- if (hsotg->bus_suspended) {
- if (hsotg->hibernated)
- dwc2_exit_hibernation(hsotg, 0, 0, 1);
- else
- dwc2_port_resume(hsotg);
- }
+ if (hsotg->bus_suspended)
+ retval = dwc2_port_resume(hsotg);
break;
case USB_PORT_FEAT_POWER:
"SetPortFeature - USB_PORT_FEAT_SUSPEND\n");
if (windex != hsotg->otg_port)
goto error;
- if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_HIBERNATION)
- dwc2_enter_hibernation(hsotg, 1);
- else
- dwc2_port_suspend(hsotg, windex);
+ if (!hsotg->bus_suspended)
+ retval = dwc2_port_suspend(hsotg, windex);
break;
case USB_PORT_FEAT_POWER:
break;
case USB_PORT_FEAT_RESET:
- if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_HIBERNATION &&
- hsotg->hibernated)
- dwc2_exit_hibernation(hsotg, 0, 1, 1);
- hprt0 = dwc2_read_hprt0(hsotg);
dev_dbg(hsotg->dev,
"SetPortFeature - USB_PORT_FEAT_RESET\n");
+
+ hprt0 = dwc2_read_hprt0(hsotg);
+
+ if (hsotg->hibernated) {
+ retval = dwc2_exit_hibernation(hsotg, 0, 1, 1);
+ if (retval)
+ dev_err(hsotg->dev,
+ "exit hibernation failed\n");
+ }
+
+ if (hsotg->in_ppd) {
+ retval = dwc2_exit_partial_power_down(hsotg, 1,
+ true);
+ if (retval)
+ dev_err(hsotg->dev,
+ "exit partial_power_down failed\n");
+ }
+
+ if (hsotg->params.power_down ==
+ DWC2_POWER_DOWN_PARAM_NONE && hsotg->bus_suspended)
+ dwc2_host_exit_clock_gating(hsotg, 0);
+
pcgctl = dwc2_readl(hsotg, PCGCTL);
pcgctl &= ~(PCGCTL_ENBL_SLEEP_GATING | PCGCTL_STOPPCLK);
dwc2_writel(hsotg, pcgctl, PCGCTL);
struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd);
unsigned long flags;
int ret = 0;
- u32 hprt0;
- u32 pcgctl;
spin_lock_irqsave(&hsotg->lock, flags);
if (hsotg->op_state == OTG_STATE_B_PERIPHERAL)
goto unlock;
- if (hsotg->params.power_down != DWC2_POWER_DOWN_PARAM_PARTIAL ||
- hsotg->flags.b.port_connect_status == 0)
+ if (hsotg->bus_suspended)
goto skip_power_saving;
- /*
- * Drive USB suspend and disable port Power
- * if usb bus is not suspended.
- */
- if (!hsotg->bus_suspended) {
- hprt0 = dwc2_read_hprt0(hsotg);
- if (hprt0 & HPRT0_CONNSTS) {
- hprt0 |= HPRT0_SUSP;
- if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_PARTIAL)
- hprt0 &= ~HPRT0_PWR;
- dwc2_writel(hsotg, hprt0, HPRT0);
- }
- if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_PARTIAL) {
- spin_unlock_irqrestore(&hsotg->lock, flags);
- dwc2_vbus_supply_exit(hsotg);
- spin_lock_irqsave(&hsotg->lock, flags);
- } else {
- pcgctl = readl(hsotg->regs + PCGCTL);
- pcgctl |= PCGCTL_STOPPCLK;
- writel(pcgctl, hsotg->regs + PCGCTL);
- }
- }
+ if (hsotg->flags.b.port_connect_status == 0)
+ goto skip_power_saving;
- if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_PARTIAL) {
+ switch (hsotg->params.power_down) {
+ case DWC2_POWER_DOWN_PARAM_PARTIAL:
/* Enter partial_power_down */
ret = dwc2_enter_partial_power_down(hsotg);
- if (ret) {
- if (ret != -ENOTSUPP)
- dev_err(hsotg->dev,
- "enter partial_power_down failed\n");
- goto skip_power_saving;
- }
+ if (ret)
+ dev_err(hsotg->dev,
+ "enter partial_power_down failed\n");
+ /* After entering suspend, hardware is not accessible */
+ clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+ break;
+ case DWC2_POWER_DOWN_PARAM_HIBERNATION:
+ /* Enter hibernation */
+ spin_unlock_irqrestore(&hsotg->lock, flags);
+ ret = dwc2_enter_hibernation(hsotg, 1);
+ if (ret)
+ dev_err(hsotg->dev, "enter hibernation failed\n");
+ spin_lock_irqsave(&hsotg->lock, flags);
+
+ /* After entering suspend, hardware is not accessible */
+ clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+ break;
+ case DWC2_POWER_DOWN_PARAM_NONE:
+ /*
+ * If not hibernation nor partial power down are supported,
+ * clock gating is used to save power.
+ */
+ dwc2_host_enter_clock_gating(hsotg);
- /* After entering partial_power_down, hardware is no more accessible */
+ /* After entering suspend, hardware is not accessible */
clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+ break;
+ default:
+ goto skip_power_saving;
}
+ spin_unlock_irqrestore(&hsotg->lock, flags);
+ dwc2_vbus_supply_exit(hsotg);
+ spin_lock_irqsave(&hsotg->lock, flags);
+
/* Ask phy to be suspended */
if (!IS_ERR_OR_NULL(hsotg->uphy)) {
spin_unlock_irqrestore(&hsotg->lock, flags);
{
struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd);
unsigned long flags;
- u32 pcgctl;
+ u32 hprt0;
int ret = 0;
spin_lock_irqsave(&hsotg->lock, flags);
if (hsotg->lx_state != DWC2_L2)
goto unlock;
- if (hsotg->params.power_down > DWC2_POWER_DOWN_PARAM_PARTIAL) {
+ hprt0 = dwc2_read_hprt0(hsotg);
+
+ /*
+ * Added port connection status checking which prevents exiting from
+ * Partial Power Down mode from _dwc2_hcd_resume() if not in Partial
+ * Power Down mode.
+ */
+ if (hprt0 & HPRT0_CONNSTS) {
hsotg->lx_state = DWC2_L0;
goto unlock;
}
- /*
- * Enable power if not already done.
- * This must not be spinlocked since duration
- * of this call is unknown.
- */
- if (!IS_ERR_OR_NULL(hsotg->uphy)) {
+ switch (hsotg->params.power_down) {
+ case DWC2_POWER_DOWN_PARAM_PARTIAL:
+ ret = dwc2_exit_partial_power_down(hsotg, 0, true);
+ if (ret)
+ dev_err(hsotg->dev,
+ "exit partial_power_down failed\n");
+ /*
+ * Set HW accessible bit before powering on the controller
+ * since an interrupt may rise.
+ */
+ set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+ break;
+ case DWC2_POWER_DOWN_PARAM_HIBERNATION:
+ ret = dwc2_exit_hibernation(hsotg, 0, 0, 1);
+ if (ret)
+ dev_err(hsotg->dev, "exit hibernation failed.\n");
+
+ /*
+ * Set HW accessible bit before powering on the controller
+ * since an interrupt may rise.
+ */
+ set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+ break;
+ case DWC2_POWER_DOWN_PARAM_NONE:
+ /*
+ * If not hibernation nor partial power down are supported,
+ * port resume is done using the clock gating programming flow.
+ */
spin_unlock_irqrestore(&hsotg->lock, flags);
- usb_phy_set_suspend(hsotg->uphy, false);
+ dwc2_host_exit_clock_gating(hsotg, 0);
+
+ /*
+ * Initialize the Core for Host mode, as after system resume
+ * the global interrupts are disabled.
+ */
+ dwc2_core_init(hsotg, false);
+ dwc2_enable_global_interrupts(hsotg);
+ dwc2_hcd_reinit(hsotg);
spin_lock_irqsave(&hsotg->lock, flags);
- }
- if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_PARTIAL) {
/*
* Set HW accessible bit before powering on the controller
* since an interrupt may rise.
*/
set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+ break;
+ default:
+ hsotg->lx_state = DWC2_L0;
+ goto unlock;
+ }
+ /* Change Root port status, as port status change occurred after resume.*/
+ hsotg->flags.b.port_suspend_change = 1;
- /* Exit partial_power_down */
- ret = dwc2_exit_partial_power_down(hsotg, true);
- if (ret && (ret != -ENOTSUPP))
- dev_err(hsotg->dev, "exit partial_power_down failed\n");
- } else {
- pcgctl = readl(hsotg->regs + PCGCTL);
- pcgctl &= ~PCGCTL_STOPPCLK;
- writel(pcgctl, hsotg->regs + PCGCTL);
+ /*
+ * Enable power if not already done.
+ * This must not be spinlocked since duration
+ * of this call is unknown.
+ */
+ if (!IS_ERR_OR_NULL(hsotg->uphy)) {
+ spin_unlock_irqrestore(&hsotg->lock, flags);
+ usb_phy_set_suspend(hsotg->uphy, false);
+ spin_lock_irqsave(&hsotg->lock, flags);
}
- hsotg->lx_state = DWC2_L0;
-
+ /* Enable external vbus supply after resuming the port. */
spin_unlock_irqrestore(&hsotg->lock, flags);
+ dwc2_vbus_supply_init(hsotg);
- if (hsotg->bus_suspended) {
- spin_lock_irqsave(&hsotg->lock, flags);
- hsotg->flags.b.port_suspend_change = 1;
- spin_unlock_irqrestore(&hsotg->lock, flags);
- dwc2_port_resume(hsotg);
- } else {
- if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_PARTIAL) {
- dwc2_vbus_supply_init(hsotg);
-
- /* Wait for controller to correctly update D+/D- level */
- usleep_range(3000, 5000);
- }
+ /* Wait for controller to correctly update D+/D- level */
+ usleep_range(3000, 5000);
+ spin_lock_irqsave(&hsotg->lock, flags);
- /*
- * Clear Port Enable and Port Status changes.
- * Enable Port Power.
- */
- dwc2_writel(hsotg, HPRT0_PWR | HPRT0_CONNDET |
- HPRT0_ENACHG, HPRT0);
- /* Wait for controller to detect Port Connect */
- usleep_range(5000, 7000);
- }
+ /*
+ * Clear Port Enable and Port Status changes.
+ * Enable Port Power.
+ */
+ dwc2_writel(hsotg, HPRT0_PWR | HPRT0_CONNDET |
+ HPRT0_ENACHG, HPRT0);
- return ret;
+ /* Wait for controller to detect Port Connect */
+ spin_unlock_irqrestore(&hsotg->lock, flags);
+ usleep_range(5000, 7000);
+ spin_lock_irqsave(&hsotg->lock, flags);
unlock:
spin_unlock_irqrestore(&hsotg->lock, flags);
struct dwc2_qh *qh;
bool qh_allocated = false;
struct dwc2_qtd *qtd;
+ struct dwc2_gregs_backup *gr;
+
+ gr = &hsotg->gr_backup;
if (dbg_urb(urb)) {
dev_vdbg(hsotg->dev, "DWC OTG HCD URB Enqueue\n");
dwc2_dump_urb_info(hcd, urb, "urb_enqueue");
}
+ if (hsotg->hibernated) {
+ if (gr->gotgctl & GOTGCTL_CURMODE_HOST)
+ retval = dwc2_exit_hibernation(hsotg, 0, 0, 1);
+ else
+ retval = dwc2_exit_hibernation(hsotg, 0, 0, 0);
+
+ if (retval)
+ dev_err(hsotg->dev,
+ "exit hibernation failed.\n");
+ }
+
+ if (hsotg->in_ppd) {
+ retval = dwc2_exit_partial_power_down(hsotg, 0, true);
+ if (retval)
+ dev_err(hsotg->dev,
+ "exit partial_power_down failed\n");
+ }
+
+ if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_NONE &&
+ hsotg->bus_suspended) {
+ if (dwc2_is_device_mode(hsotg))
+ dwc2_gadget_exit_clock_gating(hsotg, 0);
+ else
+ dwc2_host_exit_clock_gating(hsotg, 0);
+ }
+
if (!ep)
return -EINVAL;
return ret;
}
- dwc2_hcd_rem_wakeup(hsotg);
+ if (rem_wakeup) {
+ dwc2_hcd_rem_wakeup(hsotg);
+ /*
+ * Change "port_connect_status_change" flag to re-enumerate,
+ * because after exit from hibernation port connection status
+ * is not detected.
+ */
+ hsotg->flags.b.port_connect_status_change = 1;
+ }
hsotg->hibernated = 0;
hsotg->bus_suspended = 0;
/* No reason to keep the PHY powered, so allow poweroff */
return true;
}
+
+/**
+ * dwc2_host_enter_partial_power_down() - Put controller in partial
+ * power down.
+ *
+ * @hsotg: Programming view of the DWC_otg controller
+ *
+ * Return: non-zero if failed to enter host partial power down.
+ *
+ * This function is for entering Host mode partial power down.
+ */
+int dwc2_host_enter_partial_power_down(struct dwc2_hsotg *hsotg)
+{
+ u32 pcgcctl;
+ u32 hprt0;
+ int ret = 0;
+
+ dev_dbg(hsotg->dev, "Entering host partial power down started.\n");
+
+ /* Put this port in suspend mode. */
+ hprt0 = dwc2_read_hprt0(hsotg);
+ hprt0 |= HPRT0_SUSP;
+ dwc2_writel(hsotg, hprt0, HPRT0);
+ udelay(5);
+
+ /* Wait for the HPRT0.PrtSusp register field to be set */
+ if (dwc2_hsotg_wait_bit_set(hsotg, HPRT0, HPRT0_SUSP, 3000))
+ dev_warn(hsotg->dev, "Suspend wasn't generated\n");
+
+ /* Backup all registers */
+ ret = dwc2_backup_global_registers(hsotg);
+ if (ret) {
+ dev_err(hsotg->dev, "%s: failed to backup global registers\n",
+ __func__);
+ return ret;
+ }
+
+ ret = dwc2_backup_host_registers(hsotg);
+ if (ret) {
+ dev_err(hsotg->dev, "%s: failed to backup host registers\n",
+ __func__);
+ return ret;
+ }
+
+ /*
+ * Clear any pending interrupts since dwc2 will not be able to
+ * clear them after entering partial_power_down.
+ */
+ dwc2_writel(hsotg, 0xffffffff, GINTSTS);
+
+ /* Put the controller in low power state */
+ pcgcctl = dwc2_readl(hsotg, PCGCTL);
+
+ pcgcctl |= PCGCTL_PWRCLMP;
+ dwc2_writel(hsotg, pcgcctl, PCGCTL);
+ udelay(5);
+
+ pcgcctl |= PCGCTL_RSTPDWNMODULE;
+ dwc2_writel(hsotg, pcgcctl, PCGCTL);
+ udelay(5);
+
+ pcgcctl |= PCGCTL_STOPPCLK;
+ dwc2_writel(hsotg, pcgcctl, PCGCTL);
+
+ /* Set in_ppd flag to 1 as here core enters suspend. */
+ hsotg->in_ppd = 1;
+ hsotg->lx_state = DWC2_L2;
+ hsotg->bus_suspended = true;
+
+ dev_dbg(hsotg->dev, "Entering host partial power down completed.\n");
+
+ return ret;
+}
+
+/*
+ * dwc2_host_exit_partial_power_down() - Exit controller from host partial
+ * power down.
+ *
+ * @hsotg: Programming view of the DWC_otg controller
+ * @rem_wakeup: indicates whether resume is initiated by Reset.
+ * @restore: indicates whether need to restore the registers or not.
+ *
+ * Return: non-zero if failed to exit host partial power down.
+ *
+ * This function is for exiting from Host mode partial power down.
+ */
+int dwc2_host_exit_partial_power_down(struct dwc2_hsotg *hsotg,
+ int rem_wakeup, bool restore)
+{
+ u32 pcgcctl;
+ int ret = 0;
+ u32 hprt0;
+
+ dev_dbg(hsotg->dev, "Exiting host partial power down started.\n");
+
+ pcgcctl = dwc2_readl(hsotg, PCGCTL);
+ pcgcctl &= ~PCGCTL_STOPPCLK;
+ dwc2_writel(hsotg, pcgcctl, PCGCTL);
+ udelay(5);
+
+ pcgcctl = dwc2_readl(hsotg, PCGCTL);
+ pcgcctl &= ~PCGCTL_PWRCLMP;
+ dwc2_writel(hsotg, pcgcctl, PCGCTL);
+ udelay(5);
+
+ pcgcctl = dwc2_readl(hsotg, PCGCTL);
+ pcgcctl &= ~PCGCTL_RSTPDWNMODULE;
+ dwc2_writel(hsotg, pcgcctl, PCGCTL);
+
+ udelay(100);
+ if (restore) {
+ ret = dwc2_restore_global_registers(hsotg);
+ if (ret) {
+ dev_err(hsotg->dev, "%s: failed to restore registers\n",
+ __func__);
+ return ret;
+ }
+
+ ret = dwc2_restore_host_registers(hsotg);
+ if (ret) {
+ dev_err(hsotg->dev, "%s: failed to restore host registers\n",
+ __func__);
+ return ret;
+ }
+ }
+
+ /* Drive resume signaling and exit suspend mode on the port. */
+ hprt0 = dwc2_read_hprt0(hsotg);
+ hprt0 |= HPRT0_RES;
+ hprt0 &= ~HPRT0_SUSP;
+ dwc2_writel(hsotg, hprt0, HPRT0);
+ udelay(5);
+
+ if (!rem_wakeup) {
+ /* Stop driveing resume signaling on the port. */
+ hprt0 = dwc2_read_hprt0(hsotg);
+ hprt0 &= ~HPRT0_RES;
+ dwc2_writel(hsotg, hprt0, HPRT0);
+
+ hsotg->bus_suspended = false;
+ } else {
+ /* Turn on the port power bit. */
+ hprt0 = dwc2_read_hprt0(hsotg);
+ hprt0 |= HPRT0_PWR;
+ dwc2_writel(hsotg, hprt0, HPRT0);
+
+ /* Connect hcd. */
+ dwc2_hcd_connect(hsotg);
+
+ mod_timer(&hsotg->wkp_timer,
+ jiffies + msecs_to_jiffies(71));
+ }
+
+ /* Set lx_state to and in_ppd to 0 as here core exits from suspend. */
+ hsotg->in_ppd = 0;
+ hsotg->lx_state = DWC2_L0;
+
+ dev_dbg(hsotg->dev, "Exiting host partial power down completed.\n");
+ return ret;
+}
+
+/**
+ * dwc2_host_enter_clock_gating() - Put controller in clock gating.
+ *
+ * @hsotg: Programming view of the DWC_otg controller
+ *
+ * This function is for entering Host mode clock gating.
+ */
+void dwc2_host_enter_clock_gating(struct dwc2_hsotg *hsotg)
+{
+ u32 hprt0;
+ u32 pcgctl;
+
+ dev_dbg(hsotg->dev, "Entering host clock gating.\n");
+
+ /* Put this port in suspend mode. */
+ hprt0 = dwc2_read_hprt0(hsotg);
+ hprt0 |= HPRT0_SUSP;
+ dwc2_writel(hsotg, hprt0, HPRT0);
+
+ /* Set the Phy Clock bit as suspend is received. */
+ pcgctl = dwc2_readl(hsotg, PCGCTL);
+ pcgctl |= PCGCTL_STOPPCLK;
+ dwc2_writel(hsotg, pcgctl, PCGCTL);
+ udelay(5);
+
+ /* Set the Gate hclk as suspend is received. */
+ pcgctl = dwc2_readl(hsotg, PCGCTL);
+ pcgctl |= PCGCTL_GATEHCLK;
+ dwc2_writel(hsotg, pcgctl, PCGCTL);
+ udelay(5);
+
+ hsotg->bus_suspended = true;
+ hsotg->lx_state = DWC2_L2;
+}
+
+/**
+ * dwc2_host_exit_clock_gating() - Exit controller from clock gating.
+ *
+ * @hsotg: Programming view of the DWC_otg controller
+ * @rem_wakeup: indicates whether resume is initiated by remote wakeup
+ *
+ * This function is for exiting Host mode clock gating.
+ */
+void dwc2_host_exit_clock_gating(struct dwc2_hsotg *hsotg, int rem_wakeup)
+{
+ u32 hprt0;
+ u32 pcgctl;
+
+ dev_dbg(hsotg->dev, "Exiting host clock gating.\n");
+
+ /* Clear the Gate hclk. */
+ pcgctl = dwc2_readl(hsotg, PCGCTL);
+ pcgctl &= ~PCGCTL_GATEHCLK;
+ dwc2_writel(hsotg, pcgctl, PCGCTL);
+ udelay(5);
+
+ /* Phy Clock bit. */
+ pcgctl = dwc2_readl(hsotg, PCGCTL);
+ pcgctl &= ~PCGCTL_STOPPCLK;
+ dwc2_writel(hsotg, pcgctl, PCGCTL);
+ udelay(5);
+
+ /* Drive resume signaling and exit suspend mode on the port. */
+ hprt0 = dwc2_read_hprt0(hsotg);
+ hprt0 |= HPRT0_RES;
+ hprt0 &= ~HPRT0_SUSP;
+ dwc2_writel(hsotg, hprt0, HPRT0);
+ udelay(5);
+
+ if (!rem_wakeup) {
+ /* In case of port resume need to wait for 40 ms */
+ msleep(USB_RESUME_TIMEOUT);
+
+ /* Stop driveing resume signaling on the port. */
+ hprt0 = dwc2_read_hprt0(hsotg);
+ hprt0 &= ~HPRT0_RES;
+ dwc2_writel(hsotg, hprt0, HPRT0);
+
+ hsotg->bus_suspended = false;
+ hsotg->lx_state = DWC2_L0;
+ } else {
+ mod_timer(&hsotg->wkp_timer,
+ jiffies + msecs_to_jiffies(71));
+ }
+}
#define DWC2_UNRESERVE_DELAY (msecs_to_jiffies(5))
/* If we get a NAK, wait this long before retrying */
-#define DWC2_RETRY_WAIT_DELAY 1*1E6L
+#define DWC2_RETRY_WAIT_DELAY (1 * 1E6L)
/**
* dwc2_periodic_channel_available() - Checks that a channel is available for a
#define GOTGCTL_CHIRPEN BIT(27)
#define GOTGCTL_MULT_VALID_BC_MASK (0x1f << 22)
#define GOTGCTL_MULT_VALID_BC_SHIFT 22
+#define GOTGCTL_CURMODE_HOST BIT(21)
#define GOTGCTL_OTGVER BIT(20)
#define GOTGCTL_BSESVLD BIT(19)
#define GOTGCTL_ASESVLD BIT(18)
};
MODULE_DEVICE_TABLE(of, dwc2_of_match_table);
+const struct acpi_device_id dwc2_acpi_match[] = {
+ { "BCM2848", (kernel_ulong_t)dwc2_set_bcm_params },
+ { },
+};
+MODULE_DEVICE_TABLE(acpi, dwc2_acpi_match);
+
static void dwc2_set_param_otg_cap(struct dwc2_hsotg *hsotg)
{
u8 val;
return 0;
}
+typedef void (*set_params_cb)(struct dwc2_hsotg *data);
+
int dwc2_init_params(struct dwc2_hsotg *hsotg)
{
const struct of_device_id *match;
- void (*set_params)(struct dwc2_hsotg *data);
+ set_params_cb set_params;
dwc2_set_default_params(hsotg);
dwc2_get_device_properties(hsotg);
if (match && match->data) {
set_params = match->data;
set_params(hsotg);
+ } else {
+ const struct acpi_device_id *amatch;
+
+ amatch = acpi_match_device(dwc2_acpi_match, hsotg->dev);
+ if (amatch && amatch->driver_data) {
+ set_params = (set_params_cb)amatch->driver_data;
+ set_params(hsotg);
+ }
}
dwc2_check_params(hsotg);
static int dwc2_driver_remove(struct platform_device *dev)
{
struct dwc2_hsotg *hsotg = platform_get_drvdata(dev);
+ struct dwc2_gregs_backup *gr;
+ int ret = 0;
+
+ gr = &hsotg->gr_backup;
+
+ /* Exit Hibernation when driver is removed. */
+ if (hsotg->hibernated) {
+ if (gr->gotgctl & GOTGCTL_CURMODE_HOST)
+ ret = dwc2_exit_hibernation(hsotg, 0, 0, 1);
+ else
+ ret = dwc2_exit_hibernation(hsotg, 0, 0, 0);
+
+ if (ret)
+ dev_err(hsotg->dev,
+ "exit hibernation failed.\n");
+ }
+
+ /* Exit Partial Power Down when driver is removed. */
+ if (hsotg->in_ppd) {
+ ret = dwc2_exit_partial_power_down(hsotg, 0, true);
+ if (ret)
+ dev_err(hsotg->dev,
+ "exit partial_power_down failed\n");
+ }
+
+ /* Exit clock gating when driver is removed. */
+ if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_NONE &&
+ hsotg->bus_suspended) {
+ if (dwc2_is_device_mode(hsotg))
+ dwc2_gadget_exit_clock_gating(hsotg, 0);
+ else
+ dwc2_host_exit_clock_gating(hsotg, 0);
+ }
dwc2_debugfs_exit(hsotg);
if (hsotg->hcd_enabled)
reset_control_assert(hsotg->reset);
reset_control_assert(hsotg->reset_ecc);
- return 0;
+ return ret;
}
/**
.driver = {
.name = dwc2_driver_name,
.of_match_table = dwc2_of_match_table,
+ .acpi_match_table = ACPI_PTR(dwc2_acpi_match),
.pm = &dwc2_dev_pm_ops,
},
.probe = dwc2_driver_probe,
functionality.
Say 'Y' or 'M' if you have one such device.
+config USB_DWC3_XILINX
+ tristate "Xilinx Platforms"
+ depends on (ARCH_ZYNQMP || ARCH_VERSAL) && OF
+ default USB_DWC3
+ help
+ Support Xilinx SoCs with DesignWare Core USB3 IP.
+ This driver handles both ZynqMP and Versal SoC operations.
+ Say 'Y' or 'M' if you have one such device.
+
endif
obj-$(CONFIG_USB_DWC3_ST) += dwc3-st.o
obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom.o
obj-$(CONFIG_USB_DWC3_IMX8MP) += dwc3-imx8mp.o
+obj-$(CONFIG_USB_DWC3_XILINX) += dwc3-xilinx.o
dwc->current_dr_role = mode;
}
+static int dwc3_core_soft_reset(struct dwc3 *dwc);
+
static void __dwc3_set_mode(struct work_struct *work)
{
struct dwc3 *dwc = work_to_dwc(work);
int ret;
u32 reg;
+ mutex_lock(&dwc->mutex);
+
pm_runtime_get_sync(dwc->dev);
if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_OTG)
break;
}
+ /* For DRD host or device mode only */
+ if (dwc->desired_dr_role != DWC3_GCTL_PRTCAP_OTG) {
+ reg = dwc3_readl(dwc->regs, DWC3_GCTL);
+ reg |= DWC3_GCTL_CORESOFTRESET;
+ dwc3_writel(dwc->regs, DWC3_GCTL, reg);
+
+ /*
+ * Wait for internal clocks to synchronized. DWC_usb31 and
+ * DWC_usb32 may need at least 50ms (less for DWC_usb3). To
+ * keep it consistent across different IPs, let's wait up to
+ * 100ms before clearing GCTL.CORESOFTRESET.
+ */
+ msleep(100);
+
+ reg = dwc3_readl(dwc->regs, DWC3_GCTL);
+ reg &= ~DWC3_GCTL_CORESOFTRESET;
+ dwc3_writel(dwc->regs, DWC3_GCTL, reg);
+ }
+
spin_lock_irqsave(&dwc->lock, flags);
dwc3_set_prtcap(dwc, dwc->desired_dr_role);
}
break;
case DWC3_GCTL_PRTCAP_DEVICE:
+ dwc3_core_soft_reset(dwc);
+
dwc3_event_buffers_setup(dwc);
if (dwc->usb2_phy)
out:
pm_runtime_mark_last_busy(dwc->dev);
pm_runtime_put_autosuspend(dwc->dev);
+ mutex_unlock(&dwc->mutex);
}
void dwc3_set_mode(struct dwc3 *dwc, u32 mode)
parms->hwparams6 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS6);
parms->hwparams7 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS7);
parms->hwparams8 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS8);
+
+ if (DWC3_IP_IS(DWC32))
+ parms->hwparams9 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS9);
}
static int dwc3_core_ulpi_init(struct dwc3 *dwc)
u8 rx_max_burst_prd;
u8 tx_thr_num_pkt_prd;
u8 tx_max_burst_prd;
+ const char *usb_psy_name;
+ int ret;
/* default to highest possible threshold */
lpm_nyet_threshold = 0xf;
else
dwc->sysdev = dwc->dev;
+ ret = device_property_read_string(dev, "usb-psy-name", &usb_psy_name);
+ if (ret >= 0) {
+ dwc->usb_psy = power_supply_get_by_name(usb_psy_name);
+ if (!dwc->usb_psy)
+ dev_err(dev, "couldn't get usb power supply\n");
+ }
+
dwc->has_lpm_erratum = device_property_read_bool(dev,
"snps,has-lpm-erratum");
device_property_read_u8(dev, "snps,lpm-nyet-threshold",
"snps,usb3_lpm_capable");
dwc->usb2_lpm_disable = device_property_read_bool(dev,
"snps,usb2-lpm-disable");
+ dwc->usb2_gadget_lpm_disable = device_property_read_bool(dev,
+ "snps,usb2-gadget-lpm-disable");
device_property_read_u8(dev, "snps,rx-thr-num-pkt-prd",
&rx_thr_num_pkt_prd);
device_property_read_u8(dev, "snps,rx-max-burst-prd",
/* Check the maximum_speed parameter */
switch (dwc->maximum_speed) {
- case USB_SPEED_LOW:
case USB_SPEED_FULL:
case USB_SPEED_HIGH:
break;
dwc3_cache_hwparams(dwc);
spin_lock_init(&dwc->lock);
+ mutex_init(&dwc->mutex);
pm_runtime_set_active(dev);
pm_runtime_use_autosuspend(dev);
assert_reset:
reset_control_assert(dwc->reset);
+ if (dwc->usb_psy)
+ power_supply_put(dwc->usb_psy);
+
return ret;
}
dwc3_free_event_buffers(dwc);
dwc3_free_scratch_buffers(dwc);
+ if (dwc->usb_psy)
+ power_supply_put(dwc->usb_psy);
+
return 0;
}
+static void dwc3_shutdown(struct platform_device *pdev)
+{
+ dwc3_remove(pdev);
+}
+
#ifdef CONFIG_PM
static int dwc3_core_init_for_resume(struct dwc3 *dwc)
{
static struct platform_driver dwc3_driver = {
.probe = dwc3_probe,
.remove = dwc3_remove,
+ .shutdown = dwc3_shutdown,
.driver = {
.name = "dwc3",
.of_match_table = of_match_ptr(of_dwc3_match),
#include <linux/device.h>
#include <linux/spinlock.h>
+#include <linux/mutex.h>
#include <linux/ioport.h>
#include <linux/list.h>
#include <linux/bitops.h>
#include <linux/phy/phy.h>
+#include <linux/power_supply.h>
+
#define DWC3_MSG_MAX 500
/* Global constants */
#define DWC3_GHWPARAMS8 0xc600
#define DWC3_GUCTL3 0xc60c
#define DWC3_GFLADJ 0xc630
+#define DWC3_GHWPARAMS9 0xc680
/* Device Registers */
#define DWC3_DCFG 0xc700
#define DWC3_GHWPARAMS7_RAM1_DEPTH(n) ((n) & 0xffff)
#define DWC3_GHWPARAMS7_RAM2_DEPTH(n) (((n) >> 16) & 0xffff)
+/* Global HWPARAMS9 Register */
+#define DWC3_GHWPARAMS9_DEV_TXF_FLUSH_BYPASS BIT(0)
+
/* Global Frame Length Adjustment Register */
#define DWC3_GFLADJ_30MHZ_SDBND_SEL BIT(7)
#define DWC3_GFLADJ_30MHZ_MASK 0x3f
#define DWC3_DCFG_SUPERSPEED (4 << 0)
#define DWC3_DCFG_HIGHSPEED (0 << 0)
#define DWC3_DCFG_FULLSPEED BIT(0)
-#define DWC3_DCFG_LOWSPEED (2 << 0)
#define DWC3_DCFG_NUMP_SHIFT 17
#define DWC3_DCFG_NUMP(n) (((n) >> DWC3_DCFG_NUMP_SHIFT) & 0x1f)
#define DWC3_DCFG_NUMP_MASK (0x1f << DWC3_DCFG_NUMP_SHIFT)
#define DWC3_DCFG_LPM_CAP BIT(22)
+#define DWC3_DCFG_IGNSTRMPP BIT(23)
/* Device Control Register */
#define DWC3_DCTL_RUN_STOP BIT(31)
#define DWC3_DSTS_SUPERSPEED (4 << 0)
#define DWC3_DSTS_HIGHSPEED (0 << 0)
#define DWC3_DSTS_FULLSPEED BIT(0)
-#define DWC3_DSTS_LOWSPEED (2 << 0)
/* Device Generic Command Register */
#define DWC3_DGCMD_SET_LMP 0x01
u32 hwparams6;
u32 hwparams7;
u32 hwparams8;
+ u32 hwparams9;
};
/* HWPARAMS0 */
#define DWC3_MODE(n) ((n) & 0x7)
-#define DWC3_MDWIDTH(n) (((n) & 0xff00) >> 8)
-
/* HWPARAMS1 */
#define DWC3_NUM_INT(n) (((n) & (0x3f << 15)) >> 15)
unsigned int remaining;
unsigned int status;
-#define DWC3_REQUEST_STATUS_QUEUED 0
-#define DWC3_REQUEST_STATUS_STARTED 1
-#define DWC3_REQUEST_STATUS_CANCELLED 2
-#define DWC3_REQUEST_STATUS_COMPLETED 3
-#define DWC3_REQUEST_STATUS_UNKNOWN -1
+#define DWC3_REQUEST_STATUS_QUEUED 0
+#define DWC3_REQUEST_STATUS_STARTED 1
+#define DWC3_REQUEST_STATUS_DISCONNECTED 2
+#define DWC3_REQUEST_STATUS_DEQUEUED 3
+#define DWC3_REQUEST_STATUS_STALLED 4
+#define DWC3_REQUEST_STATUS_COMPLETED 5
+#define DWC3_REQUEST_STATUS_UNKNOWN -1
u8 epnum;
struct dwc3_trb *trb;
* @scratch_addr: dma address of scratchbuf
* @ep0_in_setup: one control transfer is completed and enter setup phase
* @lock: for synchronizing
+ * @mutex: for mode switching
* @dev: pointer to our struct device
* @sysdev: pointer to the DMA-capable device
* @xhci: pointer to our xHCI child
* @role_sw: usb_role_switch handle
* @role_switch_default_mode: default operation mode of controller while
* usb role is USB_ROLE_NONE.
+ * @usb_psy: pointer to power supply interface.
* @usb2_phy: pointer to USB2 PHY
* @usb3_phy: pointer to USB3 PHY
* @usb2_generic_phy: pointer to USB2 PHY
* @dis_start_transfer_quirk: set if start_transfer failure SW workaround is
* not needed for DWC_usb31 version 1.70a-ea06 and below
* @usb3_lpm_capable: set if hadrware supports Link Power Management
- * @usb2_lpm_disable: set to disable usb2 lpm
+ * @usb2_lpm_disable: set to disable usb2 lpm for host
+ * @usb2_gadget_lpm_disable: set to disable usb2 lpm for gadget
* @disable_scramble_quirk: set if we enable the disable scramble quirk
* @u2exit_lfps_quirk: set if we enable u2exit lfps quirk
* @u2ss_inp3_quirk: set if we enable P3 OK for U2/SS Inactive quirk
/* device lock */
spinlock_t lock;
+ /* mode switching lock */
+ struct mutex mutex;
+
struct device *dev;
struct device *sysdev;
struct usb_role_switch *role_sw;
enum usb_dr_mode role_switch_default_mode;
+ struct power_supply *usb_psy;
+
u32 fladj;
u32 irq_gadget;
u32 otg_irq;
unsigned dis_start_transfer_quirk:1;
unsigned usb3_lpm_capable:1;
unsigned usb2_lpm_disable:1;
+ unsigned usb2_gadget_lpm_disable:1;
unsigned disable_scramble_quirk:1;
unsigned u2exit_lfps_quirk:1;
(!(_ip##_VERSIONTYPE_##_to) || \
dwc->version_type <= _ip##_VERSIONTYPE_##_to))
+/**
+ * dwc3_mdwidth - get MDWIDTH value in bits
+ * @dwc: pointer to our context structure
+ *
+ * Return MDWIDTH configuration value in bits.
+ */
+static inline u32 dwc3_mdwidth(struct dwc3 *dwc)
+{
+ u32 mdwidth;
+
+ mdwidth = DWC3_GHWPARAMS0_MDWIDTH(dwc->hwparams.hwparams0);
+ if (DWC3_IP_IS(DWC32))
+ mdwidth += DWC3_GHWPARAMS6_MDWIDTH(dwc->hwparams.hwparams6);
+
+ return mdwidth;
+}
+
bool dwc3_has_imod(struct dwc3 *dwc);
int dwc3_event_buffers_setup(struct dwc3 *dwc);
/* SPDX-License-Identifier: GPL-2.0 */
-/**
+/*
* debug.h - DesignWare USB3 DRD Controller Debug Header
*
* Copyright (C) 2010-2011 Texas Instruments Incorporated - https://www.ti.com
// SPDX-License-Identifier: GPL-2.0
-/**
+/*
* debugfs.c - DesignWare USB3 DRD Controller DebugFS file
*
* Copyright (C) 2010-2011 Texas Instruments Incorporated - https://www.ti.com
struct dwc3_ep *dep = s->private;
struct dwc3 *dwc = dep->dwc;
unsigned long flags;
- int mdwidth;
+ u32 mdwidth;
u32 val;
spin_lock_irqsave(&dwc->lock, flags);
val = dwc3_core_fifo_space(dep, DWC3_TXFIFO);
/* Convert to bytes */
- mdwidth = DWC3_MDWIDTH(dwc->hwparams.hwparams0);
- if (DWC3_IP_IS(DWC32))
- mdwidth += DWC3_GHWPARAMS6_MDWIDTH(dwc->hwparams.hwparams6);
+ mdwidth = dwc3_mdwidth(dwc);
val *= mdwidth;
val >>= 3;
struct dwc3_ep *dep = s->private;
struct dwc3 *dwc = dep->dwc;
unsigned long flags;
- int mdwidth;
+ u32 mdwidth;
u32 val;
spin_lock_irqsave(&dwc->lock, flags);
val = dwc3_core_fifo_space(dep, DWC3_RXFIFO);
/* Convert to bytes */
- mdwidth = DWC3_MDWIDTH(dwc->hwparams.hwparams0);
- if (DWC3_IP_IS(DWC32))
- mdwidth += DWC3_GHWPARAMS6_MDWIDTH(dwc->hwparams.hwparams6);
+ mdwidth = dwc3_mdwidth(dwc);
val *= mdwidth;
val >>= 3;
// SPDX-License-Identifier: GPL-2.0
-/**
+/*
* dwc3-exynos.c - Samsung Exynos DWC3 Specific Glue layer
*
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
// SPDX-License-Identifier: GPL-2.0
-/**
+/*
* dwc3-imx8mp.c - NXP imx8mp Specific Glue layer
*
* Copyright (c) 2020 NXP.
// SPDX-License-Identifier: GPL-2.0
-/**
+/*
* dwc3-keystone.c - Keystone Specific Glue layer
*
* Copyright (C) 2010-2013 Texas Instruments Incorporated - https://www.ti.com
static const struct of_device_id of_dwc3_simple_match[] = {
{ .compatible = "rockchip,rk3399-dwc3" },
- { .compatible = "xlnx,zynqmp-dwc3" },
{ .compatible = "cavium,octeon-7130-usb-uctl" },
{ .compatible = "sprd,sc9860-dwc3" },
{ .compatible = "allwinner,sun50i-h6-dwc3" },
// SPDX-License-Identifier: GPL-2.0
-/**
+/*
* dwc3-pci.c - PCI Specific glue layer
*
* Copyright (C) 2010-2011 Texas Instruments Incorporated - https://www.ti.com
#define PCI_DEVICE_ID_INTEL_TGPH 0x43ee
#define PCI_DEVICE_ID_INTEL_JSP 0x4dee
#define PCI_DEVICE_ID_INTEL_ADLP 0x51ee
+#define PCI_DEVICE_ID_INTEL_ADLM 0x54ee
#define PCI_DEVICE_ID_INTEL_ADLS 0x7ae1
#define PCI_DEVICE_ID_INTEL_TGL 0x9a15
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADLP),
(kernel_ulong_t) &dwc3_pci_intel_swnode, },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADLM),
+ (kernel_ulong_t) &dwc3_pci_intel_swnode, },
+
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADLS),
(kernel_ulong_t) &dwc3_pci_intel_swnode, },
/**
* dwc3_qcom_interconnect_init() - Get interconnect path handles
- * and set bandwidhth.
+ * and set bandwidth.
* @qcom: Pointer to the concerned usb core.
*
*/
struct device *dev = &pdev->dev;
int ret;
- dwc3_np = of_get_child_by_name(np, "dwc3");
+ dwc3_np = of_get_compatible_child(np, "snps,dwc3");
if (!dwc3_np) {
dev_err(dev, "failed to find dwc3 core child\n");
return -ENODEV;
qcom->qscratch_base = devm_ioremap_resource(dev, parent_res);
if (IS_ERR(qcom->qscratch_base)) {
- dev_err(dev, "failed to map qscratch, err=%d\n", ret);
ret = PTR_ERR(qcom->qscratch_base);
goto clk_disable;
}
// SPDX-License-Identifier: GPL-2.0+
-/**
+/*
* dwc3-st.c Support for dwc3 platform devices on ST Microelectronics platforms
*
* This is a small driver for the dwc3 to provide the glue logic
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * dwc3-xilinx.c - Xilinx DWC3 controller specific glue driver
+ *
+ * Authors: Manish Narani <manish.narani@xilinx.com>
+ * Anurag Kumar Vulisha <anurag.kumar.vulisha@xilinx.com>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+#include <linux/of_address.h>
+#include <linux/delay.h>
+#include <linux/firmware/xlnx-zynqmp.h>
+#include <linux/io.h>
+
+#include <linux/phy/phy.h>
+
+/* USB phy reset mask register */
+#define XLNX_USB_PHY_RST_EN 0x001C
+#define XLNX_PHY_RST_MASK 0x1
+
+/* Xilinx USB 3.0 IP Register */
+#define XLNX_USB_TRAFFIC_ROUTE_CONFIG 0x005C
+#define XLNX_USB_TRAFFIC_ROUTE_FPD 0x1
+
+/* Versal USB Reset ID */
+#define VERSAL_USB_RESET_ID 0xC104036
+
+#define XLNX_USB_FPD_PIPE_CLK 0x7c
+#define PIPE_CLK_DESELECT 1
+#define PIPE_CLK_SELECT 0
+#define XLNX_USB_FPD_POWER_PRSNT 0x80
+#define FPD_POWER_PRSNT_OPTION BIT(0)
+
+struct dwc3_xlnx {
+ int num_clocks;
+ struct clk_bulk_data *clks;
+ struct device *dev;
+ void __iomem *regs;
+ int (*pltfm_init)(struct dwc3_xlnx *data);
+};
+
+static void dwc3_xlnx_mask_phy_rst(struct dwc3_xlnx *priv_data, bool mask)
+{
+ u32 reg;
+
+ /*
+ * Enable or disable ULPI PHY reset from USB Controller.
+ * This does not actually reset the phy, but just controls
+ * whether USB controller can or cannot reset ULPI PHY.
+ */
+ reg = readl(priv_data->regs + XLNX_USB_PHY_RST_EN);
+
+ if (mask)
+ reg &= ~XLNX_PHY_RST_MASK;
+ else
+ reg |= XLNX_PHY_RST_MASK;
+
+ writel(reg, priv_data->regs + XLNX_USB_PHY_RST_EN);
+}
+
+static int dwc3_xlnx_init_versal(struct dwc3_xlnx *priv_data)
+{
+ struct device *dev = priv_data->dev;
+ int ret;
+
+ dwc3_xlnx_mask_phy_rst(priv_data, false);
+
+ /* Assert and De-assert reset */
+ ret = zynqmp_pm_reset_assert(VERSAL_USB_RESET_ID,
+ PM_RESET_ACTION_ASSERT);
+ if (ret < 0) {
+ dev_err_probe(dev, ret, "failed to assert Reset\n");
+ return ret;
+ }
+
+ ret = zynqmp_pm_reset_assert(VERSAL_USB_RESET_ID,
+ PM_RESET_ACTION_RELEASE);
+ if (ret < 0) {
+ dev_err_probe(dev, ret, "failed to De-assert Reset\n");
+ return ret;
+ }
+
+ dwc3_xlnx_mask_phy_rst(priv_data, true);
+
+ return 0;
+}
+
+static int dwc3_xlnx_init_zynqmp(struct dwc3_xlnx *priv_data)
+{
+ struct device *dev = priv_data->dev;
+ struct reset_control *crst, *hibrst, *apbrst;
+ struct phy *usb3_phy;
+ int ret;
+ u32 reg;
+
+ usb3_phy = devm_phy_get(dev, "usb3-phy");
+ if (PTR_ERR(usb3_phy) == -EPROBE_DEFER) {
+ ret = -EPROBE_DEFER;
+ goto err;
+ } else if (IS_ERR(usb3_phy)) {
+ usb3_phy = NULL;
+ }
+
+ crst = devm_reset_control_get_exclusive(dev, "usb_crst");
+ if (IS_ERR(crst)) {
+ ret = PTR_ERR(crst);
+ dev_err_probe(dev, ret,
+ "failed to get core reset signal\n");
+ goto err;
+ }
+
+ hibrst = devm_reset_control_get_exclusive(dev, "usb_hibrst");
+ if (IS_ERR(hibrst)) {
+ ret = PTR_ERR(hibrst);
+ dev_err_probe(dev, ret,
+ "failed to get hibernation reset signal\n");
+ goto err;
+ }
+
+ apbrst = devm_reset_control_get_exclusive(dev, "usb_apbrst");
+ if (IS_ERR(apbrst)) {
+ ret = PTR_ERR(apbrst);
+ dev_err_probe(dev, ret,
+ "failed to get APB reset signal\n");
+ goto err;
+ }
+
+ ret = reset_control_assert(crst);
+ if (ret < 0) {
+ dev_err(dev, "Failed to assert core reset\n");
+ goto err;
+ }
+
+ ret = reset_control_assert(hibrst);
+ if (ret < 0) {
+ dev_err(dev, "Failed to assert hibernation reset\n");
+ goto err;
+ }
+
+ ret = reset_control_assert(apbrst);
+ if (ret < 0) {
+ dev_err(dev, "Failed to assert APB reset\n");
+ goto err;
+ }
+
+ ret = phy_init(usb3_phy);
+ if (ret < 0) {
+ phy_exit(usb3_phy);
+ goto err;
+ }
+
+ ret = reset_control_deassert(apbrst);
+ if (ret < 0) {
+ dev_err(dev, "Failed to release APB reset\n");
+ goto err;
+ }
+
+ /* Set PIPE Power Present signal in FPD Power Present Register*/
+ writel(FPD_POWER_PRSNT_OPTION, priv_data->regs + XLNX_USB_FPD_POWER_PRSNT);
+
+ /* Set the PIPE Clock Select bit in FPD PIPE Clock register */
+ writel(PIPE_CLK_SELECT, priv_data->regs + XLNX_USB_FPD_PIPE_CLK);
+
+ ret = reset_control_deassert(crst);
+ if (ret < 0) {
+ dev_err(dev, "Failed to release core reset\n");
+ goto err;
+ }
+
+ ret = reset_control_deassert(hibrst);
+ if (ret < 0) {
+ dev_err(dev, "Failed to release hibernation reset\n");
+ goto err;
+ }
+
+ ret = phy_power_on(usb3_phy);
+ if (ret < 0) {
+ phy_exit(usb3_phy);
+ goto err;
+ }
+
+ /*
+ * This routes the USB DMA traffic to go through FPD path instead
+ * of reaching DDR directly. This traffic routing is needed to
+ * make SMMU and CCI work with USB DMA.
+ */
+ if (of_dma_is_coherent(dev->of_node) || device_iommu_mapped(dev)) {
+ reg = readl(priv_data->regs + XLNX_USB_TRAFFIC_ROUTE_CONFIG);
+ reg |= XLNX_USB_TRAFFIC_ROUTE_FPD;
+ writel(reg, priv_data->regs + XLNX_USB_TRAFFIC_ROUTE_CONFIG);
+ }
+
+err:
+ return ret;
+}
+
+static const struct of_device_id dwc3_xlnx_of_match[] = {
+ {
+ .compatible = "xlnx,zynqmp-dwc3",
+ .data = &dwc3_xlnx_init_zynqmp,
+ },
+ {
+ .compatible = "xlnx,versal-dwc3",
+ .data = &dwc3_xlnx_init_versal,
+ },
+ { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dwc3_xlnx_of_match);
+
+static int dwc3_xlnx_probe(struct platform_device *pdev)
+{
+ struct dwc3_xlnx *priv_data;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ const struct of_device_id *match;
+ void __iomem *regs;
+ int ret;
+
+ priv_data = devm_kzalloc(dev, sizeof(*priv_data), GFP_KERNEL);
+ if (!priv_data)
+ return -ENOMEM;
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs)) {
+ ret = PTR_ERR(regs);
+ dev_err_probe(dev, ret, "failed to map registers\n");
+ return ret;
+ }
+
+ match = of_match_node(dwc3_xlnx_of_match, pdev->dev.of_node);
+
+ priv_data->pltfm_init = match->data;
+ priv_data->regs = regs;
+ priv_data->dev = dev;
+
+ platform_set_drvdata(pdev, priv_data);
+
+ ret = devm_clk_bulk_get_all(priv_data->dev, &priv_data->clks);
+ if (ret < 0)
+ return ret;
+
+ priv_data->num_clocks = ret;
+
+ ret = clk_bulk_prepare_enable(priv_data->num_clocks, priv_data->clks);
+ if (ret)
+ return ret;
+
+ ret = priv_data->pltfm_init(priv_data);
+ if (ret)
+ goto err_clk_put;
+
+ ret = of_platform_populate(np, NULL, NULL, dev);
+ if (ret)
+ goto err_clk_put;
+
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ pm_suspend_ignore_children(dev, false);
+ pm_runtime_get_sync(dev);
+
+ return 0;
+
+err_clk_put:
+ clk_bulk_disable_unprepare(priv_data->num_clocks, priv_data->clks);
+
+ return ret;
+}
+
+static int dwc3_xlnx_remove(struct platform_device *pdev)
+{
+ struct dwc3_xlnx *priv_data = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+
+ of_platform_depopulate(dev);
+
+ clk_bulk_disable_unprepare(priv_data->num_clocks, priv_data->clks);
+ priv_data->num_clocks = 0;
+
+ pm_runtime_disable(dev);
+ pm_runtime_put_noidle(dev);
+ pm_runtime_set_suspended(dev);
+
+ return 0;
+}
+
+static int __maybe_unused dwc3_xlnx_suspend_common(struct device *dev)
+{
+ struct dwc3_xlnx *priv_data = dev_get_drvdata(dev);
+
+ clk_bulk_disable(priv_data->num_clocks, priv_data->clks);
+
+ return 0;
+}
+
+static int __maybe_unused dwc3_xlnx_resume_common(struct device *dev)
+{
+ struct dwc3_xlnx *priv_data = dev_get_drvdata(dev);
+
+ return clk_bulk_enable(priv_data->num_clocks, priv_data->clks);
+}
+
+static int __maybe_unused dwc3_xlnx_runtime_idle(struct device *dev)
+{
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_autosuspend(dev);
+
+ return 0;
+}
+
+static UNIVERSAL_DEV_PM_OPS(dwc3_xlnx_dev_pm_ops, dwc3_xlnx_suspend_common,
+ dwc3_xlnx_resume_common, dwc3_xlnx_runtime_idle);
+
+static struct platform_driver dwc3_xlnx_driver = {
+ .probe = dwc3_xlnx_probe,
+ .remove = dwc3_xlnx_remove,
+ .driver = {
+ .name = "dwc3-xilinx",
+ .of_match_table = dwc3_xlnx_of_match,
+ .pm = &dwc3_xlnx_dev_pm_ops,
+ },
+};
+
+module_platform_driver(dwc3_xlnx_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Xilinx DWC3 controller specific glue driver");
+MODULE_AUTHOR("Manish Narani <manish.narani@xilinx.com>");
+MODULE_AUTHOR("Anurag Kumar Vulisha <anurag.kumar.vulisha@xilinx.com>");
}
if (DWC3_DEPCMD_CMD(cmd) == DWC3_DEPCMD_STARTTRANSFER) {
- int needs_wakeup;
+ int link_state;
- needs_wakeup = (dwc->link_state == DWC3_LINK_STATE_U1 ||
- dwc->link_state == DWC3_LINK_STATE_U2 ||
- dwc->link_state == DWC3_LINK_STATE_U3);
-
- if (unlikely(needs_wakeup)) {
+ link_state = dwc3_gadget_get_link_state(dwc);
+ if (link_state == DWC3_LINK_STATE_U1 ||
+ link_state == DWC3_LINK_STATE_U2 ||
+ link_state == DWC3_LINK_STATE_U3) {
ret = __dwc3_gadget_wakeup(dwc);
dev_WARN_ONCE(dwc->dev, ret, "wakeup failed --> %d\n",
ret);
u8 bInterval_m1;
/*
- * Valid range for DEPCFG.bInterval_m1 is from 0 to 13, and it
- * must be set to 0 when the controller operates in full-speed.
+ * Valid range for DEPCFG.bInterval_m1 is from 0 to 13.
+ *
+ * NOTE: The programming guide incorrectly stated bInterval_m1
+ * must be set to 0 when operating in fullspeed. Internally the
+ * controller does not have this limitation. See DWC_usb3x
+ * programming guide section 3.2.2.1.
*/
bInterval_m1 = min_t(u8, desc->bInterval - 1, 13);
- if (dwc->gadget->speed == USB_SPEED_FULL)
- bInterval_m1 = 0;
if (usb_endpoint_type(desc) == USB_ENDPOINT_XFER_INT &&
dwc->gadget->speed == USB_SPEED_FULL)
* All stream eps will reinitiate stream on NoStream
* rejection until we can determine that the host can
* prime after the first transfer.
+ *
+ * However, if the controller is capable of
+ * TXF_FLUSH_BYPASS, then IN direction endpoints will
+ * automatically restart the stream without the driver
+ * initiation.
*/
- dep->flags |= DWC3_EP_FORCE_RESTART_STREAM;
+ if (!dep->direction ||
+ !(dwc->hwparams.hwparams9 &
+ DWC3_GHWPARAMS9_DEV_TXF_FLUSH_BYPASS))
+ dep->flags |= DWC3_EP_FORCE_RESTART_STREAM;
}
}
dwc3_stop_active_transfer(dep, true, true);
list_for_each_entry_safe(req, tmp, &dep->started_list, list)
- dwc3_gadget_move_cancelled_request(req);
+ dwc3_gadget_move_cancelled_request(req, DWC3_REQUEST_STATUS_DEQUEUED);
/* If ep isn't started, then there's no end transfer pending */
if (!(dep->flags & DWC3_EP_END_TRANSFER_PENDING))
{
struct dwc3_request *req;
struct dwc3_request *tmp;
+ struct dwc3 *dwc = dep->dwc;
list_for_each_entry_safe(req, tmp, &dep->cancelled_list, list) {
dwc3_gadget_ep_skip_trbs(dep, req);
- dwc3_gadget_giveback(dep, req, -ECONNRESET);
+ switch (req->status) {
+ case DWC3_REQUEST_STATUS_DISCONNECTED:
+ dwc3_gadget_giveback(dep, req, -ESHUTDOWN);
+ break;
+ case DWC3_REQUEST_STATUS_DEQUEUED:
+ dwc3_gadget_giveback(dep, req, -ECONNRESET);
+ break;
+ case DWC3_REQUEST_STATUS_STALLED:
+ dwc3_gadget_giveback(dep, req, -EPIPE);
+ break;
+ default:
+ dev_err(dwc->dev, "request cancelled with wrong reason:%d\n", req->status);
+ dwc3_gadget_giveback(dep, req, -ECONNRESET);
+ break;
+ }
}
}
* cancelled.
*/
list_for_each_entry_safe(r, t, &dep->started_list, list)
- dwc3_gadget_move_cancelled_request(r);
+ dwc3_gadget_move_cancelled_request(r,
+ DWC3_REQUEST_STATUS_DEQUEUED);
dep->flags &= ~DWC3_EP_WAIT_TRANSFER_COMPLETE;
dwc3_stop_active_transfer(dep, true, true);
list_for_each_entry_safe(req, tmp, &dep->started_list, list)
- dwc3_gadget_move_cancelled_request(req);
+ dwc3_gadget_move_cancelled_request(req, DWC3_REQUEST_STATUS_STALLED);
if (dep->flags & DWC3_EP_END_TRANSFER_PENDING) {
dep->flags |= DWC3_EP_PENDING_CLEAR_STALL;
case DWC3_LINK_STATE_RESET:
case DWC3_LINK_STATE_RX_DET: /* in HS, means Early Suspend */
case DWC3_LINK_STATE_U3: /* in HS, means SUSPEND */
+ case DWC3_LINK_STATE_U2: /* in HS, means Sleep (L1) */
+ case DWC3_LINK_STATE_U1:
case DWC3_LINK_STATE_RESUME:
break;
default:
reg |= DWC3_DCFG_SUPERSPEED;
} else {
switch (speed) {
- case USB_SPEED_LOW:
- reg |= DWC3_DCFG_LOWSPEED;
- break;
case USB_SPEED_FULL:
reg |= DWC3_DCFG_FULLSPEED;
break;
u32 reg;
ram2_depth = DWC3_GHWPARAMS7_RAM2_DEPTH(dwc->hwparams.hwparams7);
- mdwidth = DWC3_GHWPARAMS0_MDWIDTH(dwc->hwparams.hwparams0);
- if (DWC3_IP_IS(DWC32))
- mdwidth += DWC3_GHWPARAMS6_MDWIDTH(dwc->hwparams.hwparams6);
+ mdwidth = dwc3_mdwidth(dwc);
nump = ((ram2_depth * mdwidth / 8) - 24 - 16) / 1024;
nump = min_t(u32, nump, 16);
dwc3_gadget_setup_nump(dwc);
+ /*
+ * Currently the controller handles single stream only. So, Ignore
+ * Packet Pending bit for stream selection and don't search for another
+ * stream if the host sends Data Packet with PP=0 (for OUT direction) or
+ * ACK with NumP=0 and PP=0 (for IN direction). This slightly improves
+ * the stream performance.
+ */
+ reg = dwc3_readl(dwc->regs, DWC3_DCFG);
+ reg |= DWC3_DCFG_IGNSTRMPP;
+ dwc3_writel(dwc->regs, DWC3_DCFG, reg);
+
/* Start with SuperSpeed Default */
dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512);
static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned int mA)
{
struct dwc3 *dwc = gadget_to_dwc(g);
+ union power_supply_propval val = {0};
+ int ret;
if (dwc->usb2_phy)
return usb_phy_set_power(dwc->usb2_phy, mA);
- return 0;
+ if (!dwc->usb_psy)
+ return -EOPNOTSUPP;
+
+ val.intval = 1000 * mA;
+ ret = power_supply_set_property(dwc->usb_psy, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &val);
+
+ return ret;
}
static const struct usb_gadget_ops dwc3_gadget_ops = {
static int dwc3_gadget_init_in_endpoint(struct dwc3_ep *dep)
{
struct dwc3 *dwc = dep->dwc;
- int mdwidth;
+ u32 mdwidth;
int size;
- mdwidth = DWC3_MDWIDTH(dwc->hwparams.hwparams0);
- if (DWC3_IP_IS(DWC32))
- mdwidth += DWC3_GHWPARAMS6_MDWIDTH(dwc->hwparams.hwparams6);
+ mdwidth = dwc3_mdwidth(dwc);
/* MDWIDTH is represented in bits, we need it in bytes */
mdwidth /= 8;
static int dwc3_gadget_init_out_endpoint(struct dwc3_ep *dep)
{
struct dwc3 *dwc = dep->dwc;
- int mdwidth;
+ u32 mdwidth;
int size;
- mdwidth = DWC3_MDWIDTH(dwc->hwparams.hwparams0);
- if (DWC3_IP_IS(DWC32))
- mdwidth += DWC3_GHWPARAMS6_MDWIDTH(dwc->hwparams.hwparams6);
+ mdwidth = dwc3_mdwidth(dwc);
/* MDWIDTH is represented in bits, convert to bytes */
mdwidth /= 8;
static bool dwc3_gadget_ep_should_continue(struct dwc3_ep *dep)
{
struct dwc3_request *req;
+ struct dwc3 *dwc = dep->dwc;
+
+ if (!dep->endpoint.desc || !dwc->pullups_connected ||
+ !dwc->connected)
+ return false;
if (!list_empty(&dep->pending_list))
return true;
{
u32 reg;
+ /*
+ * Ideally, dwc3_reset_gadget() would trigger the function
+ * drivers to stop any active transfers through ep disable.
+ * However, for functions which defer ep disable, such as mass
+ * storage, we will need to rely on the call to stop active
+ * transfers here, and avoid allowing of request queuing.
+ */
+ dwc->connected = false;
+
/*
* WORKAROUND: DWC3 revisions <1.88a have an issue which
* would cause a missing Disconnect Event if there's a
dwc->gadget->ep0->maxpacket = 64;
dwc->gadget->speed = USB_SPEED_FULL;
break;
- case DWC3_DSTS_LOWSPEED:
- dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(8);
- dwc->gadget->ep0->maxpacket = 8;
- dwc->gadget->speed = USB_SPEED_LOW;
- break;
}
dwc->eps[1]->endpoint.maxpacket = dwc->gadget->ep0->maxpacket;
/* Enable USB2 LPM Capability */
if (!DWC3_VER_IS_WITHIN(DWC3, ANY, 194A) &&
+ !dwc->usb2_gadget_lpm_disable &&
(speed != DWC3_DSTS_SUPERSPEED) &&
(speed != DWC3_DSTS_SUPERSPEED_PLUS)) {
reg = dwc3_readl(dwc->regs, DWC3_DCFG);
dwc3_gadget_dctl_write_safe(dwc, reg);
} else {
+ if (dwc->usb2_gadget_lpm_disable) {
+ reg = dwc3_readl(dwc->regs, DWC3_DCFG);
+ reg &= ~DWC3_DCFG_LPM_CAP;
+ dwc3_writel(dwc->regs, DWC3_DCFG, reg);
+ }
+
reg = dwc3_readl(dwc->regs, DWC3_DCTL);
reg &= ~DWC3_DCTL_HIRD_THRES_MASK;
dwc3_gadget_dctl_write_safe(dwc, reg);
dwc->gadget->ssp_rate = USB_SSP_GEN_UNKNOWN;
dwc->gadget->sg_supported = true;
dwc->gadget->name = "dwc3-gadget";
- dwc->gadget->lpm_capable = true;
+ dwc->gadget->lpm_capable = !dwc->usb2_gadget_lpm_disable;
/*
* FIXME We might be setting max_speed to <SUPER, however versions
/**
* dwc3_gadget_move_cancelled_request - move @req to the cancelled_list
* @req: the request to be moved
+ * @reason: cancelled reason for the dwc3 request
*
* Caller should take care of locking. This function will move @req from its
* current list to the endpoint's cancelled_list.
*/
-static inline void dwc3_gadget_move_cancelled_request(struct dwc3_request *req)
+static inline void dwc3_gadget_move_cancelled_request(struct dwc3_request *req,
+ unsigned int reason)
{
struct dwc3_ep *dep = req->dep;
- req->status = DWC3_REQUEST_STATUS_CANCELLED;
+ req->status = reason;
list_move_tail(&req->list, &dep->cancelled_list);
}
/* SPDX-License-Identifier: GPL-2.0 */
-/**
+/*
* io.h - DesignWare USB3 DRD IO Header
*
* Copyright (C) 2010-2011 Texas Instruments Incorporated - https://www.ti.com
// SPDX-License-Identifier: GPL-2.0
-/**
+/*
* trace.c - DesignWare USB3 DRD Controller Trace Support
*
* Copyright (C) 2014 Texas Instruments Incorporated - https://www.ti.com
/* SPDX-License-Identifier: GPL-2.0 */
-/**
+/*
* trace.h - DesignWare USB3 DRD Controller Trace Support
*
* Copyright (C) 2014 Texas Instruments Incorporated - https://www.ti.com
__entry->offset = offset;
__entry->value = value;
),
- TP_printk("addr %p value %08x", __entry->base + __entry->offset,
- __entry->value)
+ TP_printk("addr %p offset %04x value %08x",
+ __entry->base + __entry->offset,
+ __entry->offset,
+ __entry->value)
);
DEFINE_EVENT(dwc3_log_io, dwc3_readl,
void usb_free_all_descriptors(struct usb_function *f)
{
usb_free_descriptors(f->fs_descriptors);
+ f->fs_descriptors = NULL;
usb_free_descriptors(f->hs_descriptors);
+ f->hs_descriptors = NULL;
usb_free_descriptors(f->ss_descriptors);
+ f->ss_descriptors = NULL;
usb_free_descriptors(f->ssp_descriptors);
+ f->ssp_descriptors = NULL;
}
EXPORT_SYMBOL_GPL(usb_free_all_descriptors);
do { /* lang_count > 0 so we can use do-while */
unsigned needed = needed_count;
+ u32 str_per_lang = str_count;
if (len < 3)
goto error_free;
data += length + 1;
len -= length + 1;
- } while (--str_count);
+ } while (--str_per_lang);
s->id = 0; /* terminator */
s->s = NULL;
if (!len)
return NULL;
- data = kmalloc(len, GFP_KERNEL);
- if (!data)
- return ERR_PTR(-ENOMEM);
-
- if (copy_from_user(data, buf, len)) {
- kfree(data);
- return ERR_PTR(-EFAULT);
- }
+ data = memdup_user(buf, len);
+ if (IS_ERR(data))
+ return ERR_PTR(PTR_ERR(data));
pr_vdebug("Buffer from user space:\n");
ffs_dump_mem("", data, len);
return container_of(f, struct fsg_dev, function);
}
-typedef void (*fsg_routine_t)(struct fsg_dev *);
-
static int exception_in_progress(struct fsg_common *common)
{
return common->state > FSG_STATE_NORMAL;
result = usb_ep_enable(dev->out_ep);
if (result != 0) {
- DBG(dev, "enable %s --> %d\n", dev->in_ep->name, result);
+ DBG(dev, "enable %s --> %d\n", dev->out_ep->name, result);
goto done;
}
#include "u_audio.h"
#include "u_uac1.h"
+/* UAC1 spec: 3.7.2.3 Audio Channel Cluster Format */
+#define UAC1_CHANNEL_MASK 0x0FFF
+
+#define EPIN_EN(_opts) ((_opts)->p_chmask != 0)
+#define EPOUT_EN(_opts) ((_opts)->c_chmask != 0)
+
struct f_uac1 {
struct g_audio g_audio;
u8 ac_intf, as_in_intf, as_out_intf;
return container_of(f, struct f_uac1, g_audio.func);
}
+static inline struct f_uac1_opts *g_audio_to_uac1_opts(struct g_audio *audio)
+{
+ return container_of(audio->func.fi, struct f_uac1_opts, func_inst);
+}
+
/*
* DESCRIPTORS ... most are static, but strings and full
* configuration descriptors are built on demand.
* USB-OUT -> IT_1 -> OT_2 -> ALSA_Capture
* ALSA_Playback -> IT_3 -> OT_4 -> USB-IN
*/
-#define F_AUDIO_AC_INTERFACE 0
-#define F_AUDIO_AS_OUT_INTERFACE 1
-#define F_AUDIO_AS_IN_INTERFACE 2
-/* Number of streaming interfaces */
-#define F_AUDIO_NUM_INTERFACES 2
/* B.3.1 Standard AC Interface Descriptor */
static struct usb_interface_descriptor ac_interface_desc = {
.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
};
-/*
- * The number of AudioStreaming and MIDIStreaming interfaces
- * in the Audio Interface Collection
- */
-DECLARE_UAC_AC_HEADER_DESCRIPTOR(2);
-
-#define UAC_DT_AC_HEADER_LENGTH UAC_DT_AC_HEADER_SIZE(F_AUDIO_NUM_INTERFACES)
-/* 2 input terminals and 2 output terminals */
-#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH \
- + 2*UAC_DT_INPUT_TERMINAL_SIZE + 2*UAC_DT_OUTPUT_TERMINAL_SIZE)
/* B.3.2 Class-Specific AC Interface Descriptor */
-static struct uac1_ac_header_descriptor_2 ac_header_desc = {
- .bLength = UAC_DT_AC_HEADER_LENGTH,
- .bDescriptorType = USB_DT_CS_INTERFACE,
- .bDescriptorSubtype = UAC_HEADER,
- .bcdADC = cpu_to_le16(0x0100),
- .wTotalLength = cpu_to_le16(UAC_DT_TOTAL_LENGTH),
- .bInCollection = F_AUDIO_NUM_INTERFACES,
- .baInterfaceNr = {
- /* Interface number of the AudioStream interfaces */
- [0] = 1,
- [1] = 2,
- }
-};
+static struct uac1_ac_header_descriptor *ac_header_desc;
-#define USB_OUT_IT_ID 1
static struct uac_input_terminal_descriptor usb_out_it_desc = {
.bLength = UAC_DT_INPUT_TERMINAL_SIZE,
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_INPUT_TERMINAL,
- .bTerminalID = USB_OUT_IT_ID,
+ /* .bTerminalID = DYNAMIC */
.wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING),
.bAssocTerminal = 0,
.wChannelConfig = cpu_to_le16(0x3),
};
-#define IO_OUT_OT_ID 2
static struct uac1_output_terminal_descriptor io_out_ot_desc = {
.bLength = UAC_DT_OUTPUT_TERMINAL_SIZE,
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
- .bTerminalID = IO_OUT_OT_ID,
+ /* .bTerminalID = DYNAMIC */
.wTerminalType = cpu_to_le16(UAC_OUTPUT_TERMINAL_SPEAKER),
.bAssocTerminal = 0,
- .bSourceID = USB_OUT_IT_ID,
+ /* .bSourceID = DYNAMIC */
};
-#define IO_IN_IT_ID 3
static struct uac_input_terminal_descriptor io_in_it_desc = {
.bLength = UAC_DT_INPUT_TERMINAL_SIZE,
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_INPUT_TERMINAL,
- .bTerminalID = IO_IN_IT_ID,
+ /* .bTerminalID = DYNAMIC */
.wTerminalType = cpu_to_le16(UAC_INPUT_TERMINAL_MICROPHONE),
.bAssocTerminal = 0,
.wChannelConfig = cpu_to_le16(0x3),
};
-#define USB_IN_OT_ID 4
static struct uac1_output_terminal_descriptor usb_in_ot_desc = {
.bLength = UAC_DT_OUTPUT_TERMINAL_SIZE,
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
- .bTerminalID = USB_IN_OT_ID,
+ /* .bTerminalID = DYNAMIC */
.wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING),
.bAssocTerminal = 0,
- .bSourceID = IO_IN_IT_ID,
+ /* .bSourceID = DYNAMIC */
};
/* B.4.1 Standard AS Interface Descriptor */
.bLength = UAC_DT_AS_HEADER_SIZE,
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_AS_GENERAL,
- .bTerminalLink = USB_OUT_IT_ID,
+ /* .bTerminalLink = DYNAMIC */
.bDelay = 1,
.wFormatTag = cpu_to_le16(UAC_FORMAT_TYPE_I_PCM),
};
.bLength = UAC_DT_AS_HEADER_SIZE,
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_AS_GENERAL,
- .bTerminalLink = USB_IN_OT_ID,
+ /* .bTerminalLink = DYNAMIC */
.bDelay = 1,
.wFormatTag = cpu_to_le16(UAC_FORMAT_TYPE_I_PCM),
};
/*-------------------------------------------------------------------------*/
+static struct
+uac1_ac_header_descriptor *build_ac_header_desc(struct f_uac1_opts *opts)
+{
+ struct uac1_ac_header_descriptor *ac_desc;
+ int ac_header_desc_size;
+ int num_ifaces = 0;
+
+ if (EPOUT_EN(opts))
+ num_ifaces++;
+ if (EPIN_EN(opts))
+ num_ifaces++;
+
+ ac_header_desc_size = UAC_DT_AC_HEADER_SIZE(num_ifaces);
+
+ ac_desc = kzalloc(ac_header_desc_size, GFP_KERNEL);
+ if (!ac_desc)
+ return NULL;
+
+ ac_desc->bLength = ac_header_desc_size;
+ ac_desc->bDescriptorType = USB_DT_CS_INTERFACE;
+ ac_desc->bDescriptorSubtype = UAC_HEADER;
+ ac_desc->bcdADC = cpu_to_le16(0x0100);
+ ac_desc->bInCollection = num_ifaces;
+
+ /* wTotalLength and baInterfaceNr will be defined later */
+
+ return ac_desc;
+}
+
+/* Use macro to overcome line length limitation */
+#define USBDHDR(p) (struct usb_descriptor_header *)(p)
+
+static void setup_descriptor(struct f_uac1_opts *opts)
+{
+ /* patch descriptors */
+ int i = 1; /* ID's start with 1 */
+
+ if (EPOUT_EN(opts))
+ usb_out_it_desc.bTerminalID = i++;
+ if (EPIN_EN(opts))
+ io_in_it_desc.bTerminalID = i++;
+ if (EPOUT_EN(opts))
+ io_out_ot_desc.bTerminalID = i++;
+ if (EPIN_EN(opts))
+ usb_in_ot_desc.bTerminalID = i++;
+
+ usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID;
+ io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID;
+
+ as_out_header_desc.bTerminalLink = usb_out_it_desc.bTerminalID;
+ as_in_header_desc.bTerminalLink = usb_in_ot_desc.bTerminalID;
+
+ ac_header_desc->wTotalLength = cpu_to_le16(ac_header_desc->bLength);
+
+ if (EPIN_EN(opts)) {
+ u16 len = le16_to_cpu(ac_header_desc->wTotalLength);
+
+ len += sizeof(usb_in_ot_desc);
+ len += sizeof(io_in_it_desc);
+ ac_header_desc->wTotalLength = cpu_to_le16(len);
+ }
+ if (EPOUT_EN(opts)) {
+ u16 len = le16_to_cpu(ac_header_desc->wTotalLength);
+
+ len += sizeof(usb_out_it_desc);
+ len += sizeof(io_out_ot_desc);
+ ac_header_desc->wTotalLength = cpu_to_le16(len);
+ }
+
+ i = 0;
+ f_audio_desc[i++] = USBDHDR(&ac_interface_desc);
+ f_audio_desc[i++] = USBDHDR(ac_header_desc);
+
+ if (EPOUT_EN(opts)) {
+ f_audio_desc[i++] = USBDHDR(&usb_out_it_desc);
+ f_audio_desc[i++] = USBDHDR(&io_out_ot_desc);
+ }
+
+ if (EPIN_EN(opts)) {
+ f_audio_desc[i++] = USBDHDR(&io_in_it_desc);
+ f_audio_desc[i++] = USBDHDR(&usb_in_ot_desc);
+ }
+
+ if (EPOUT_EN(opts)) {
+ f_audio_desc[i++] = USBDHDR(&as_out_interface_alt_0_desc);
+ f_audio_desc[i++] = USBDHDR(&as_out_interface_alt_1_desc);
+ f_audio_desc[i++] = USBDHDR(&as_out_header_desc);
+ f_audio_desc[i++] = USBDHDR(&as_out_type_i_desc);
+ f_audio_desc[i++] = USBDHDR(&as_out_ep_desc);
+ f_audio_desc[i++] = USBDHDR(&as_iso_out_desc);
+ }
+ if (EPIN_EN(opts)) {
+ f_audio_desc[i++] = USBDHDR(&as_in_interface_alt_0_desc);
+ f_audio_desc[i++] = USBDHDR(&as_in_interface_alt_1_desc);
+ f_audio_desc[i++] = USBDHDR(&as_in_header_desc);
+ f_audio_desc[i++] = USBDHDR(&as_in_type_i_desc);
+ f_audio_desc[i++] = USBDHDR(&as_in_ep_desc);
+ f_audio_desc[i++] = USBDHDR(&as_iso_in_desc);
+ }
+ f_audio_desc[i] = NULL;
+}
+
+static int f_audio_validate_opts(struct g_audio *audio, struct device *dev)
+{
+ struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio);
+
+ if (!opts->p_chmask && !opts->c_chmask) {
+ dev_err(dev, "Error: no playback and capture channels\n");
+ return -EINVAL;
+ } else if (opts->p_chmask & ~UAC1_CHANNEL_MASK) {
+ dev_err(dev, "Error: unsupported playback channels mask\n");
+ return -EINVAL;
+ } else if (opts->c_chmask & ~UAC1_CHANNEL_MASK) {
+ dev_err(dev, "Error: unsupported capture channels mask\n");
+ return -EINVAL;
+ } else if ((opts->p_ssize < 1) || (opts->p_ssize > 4)) {
+ dev_err(dev, "Error: incorrect playback sample size\n");
+ return -EINVAL;
+ } else if ((opts->c_ssize < 1) || (opts->c_ssize > 4)) {
+ dev_err(dev, "Error: incorrect capture sample size\n");
+ return -EINVAL;
+ } else if (!opts->p_srate) {
+ dev_err(dev, "Error: incorrect playback sampling rate\n");
+ return -EINVAL;
+ } else if (!opts->c_srate) {
+ dev_err(dev, "Error: incorrect capture sampling rate\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
/* audio function driver setup/binding */
static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
{
struct usb_composite_dev *cdev = c->cdev;
struct usb_gadget *gadget = cdev->gadget;
+ struct device *dev = &gadget->dev;
struct f_uac1 *uac1 = func_to_uac1(f);
struct g_audio *audio = func_to_g_audio(f);
struct f_uac1_opts *audio_opts;
struct usb_string *us;
u8 *sam_freq;
int rate;
+ int ba_iface_id;
int status;
+ status = f_audio_validate_opts(audio, dev);
+ if (status)
+ return status;
+
audio_opts = container_of(f->fi, struct f_uac1_opts, func_inst);
us = usb_gstrings_attach(cdev, uac1_strings, ARRAY_SIZE(strings_uac1));
if (IS_ERR(us))
return PTR_ERR(us);
+
+ ac_header_desc = build_ac_header_desc(audio_opts);
+ if (!ac_header_desc)
+ return -ENOMEM;
+
ac_interface_desc.iInterface = us[STR_AC_IF].id;
usb_out_it_desc.iTerminal = us[STR_USB_OUT_IT].id;
usb_out_it_desc.iChannelNames = us[STR_USB_OUT_IT_CH_NAMES].id;
uac1->ac_intf = status;
uac1->ac_alt = 0;
- status = usb_interface_id(c, f);
- if (status < 0)
- goto fail;
- as_out_interface_alt_0_desc.bInterfaceNumber = status;
- as_out_interface_alt_1_desc.bInterfaceNumber = status;
- ac_header_desc.baInterfaceNr[0] = status;
- uac1->as_out_intf = status;
- uac1->as_out_alt = 0;
+ ba_iface_id = 0;
+
+ if (EPOUT_EN(audio_opts)) {
+ status = usb_interface_id(c, f);
+ if (status < 0)
+ goto fail;
+ as_out_interface_alt_0_desc.bInterfaceNumber = status;
+ as_out_interface_alt_1_desc.bInterfaceNumber = status;
+ ac_header_desc->baInterfaceNr[ba_iface_id++] = status;
+ uac1->as_out_intf = status;
+ uac1->as_out_alt = 0;
+ }
- status = usb_interface_id(c, f);
- if (status < 0)
- goto fail;
- as_in_interface_alt_0_desc.bInterfaceNumber = status;
- as_in_interface_alt_1_desc.bInterfaceNumber = status;
- ac_header_desc.baInterfaceNr[1] = status;
- uac1->as_in_intf = status;
- uac1->as_in_alt = 0;
+ if (EPIN_EN(audio_opts)) {
+ status = usb_interface_id(c, f);
+ if (status < 0)
+ goto fail;
+ as_in_interface_alt_0_desc.bInterfaceNumber = status;
+ as_in_interface_alt_1_desc.bInterfaceNumber = status;
+ ac_header_desc->baInterfaceNr[ba_iface_id++] = status;
+ uac1->as_in_intf = status;
+ uac1->as_in_alt = 0;
+ }
audio->gadget = gadget;
status = -ENODEV;
/* allocate instance-specific endpoints */
- ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc);
- if (!ep)
- goto fail;
- audio->out_ep = ep;
- audio->out_ep->desc = &as_out_ep_desc;
+ if (EPOUT_EN(audio_opts)) {
+ ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc);
+ if (!ep)
+ goto fail;
+ audio->out_ep = ep;
+ audio->out_ep->desc = &as_out_ep_desc;
+ }
- ep = usb_ep_autoconfig(cdev->gadget, &as_in_ep_desc);
- if (!ep)
- goto fail;
- audio->in_ep = ep;
- audio->in_ep->desc = &as_in_ep_desc;
+ if (EPIN_EN(audio_opts)) {
+ ep = usb_ep_autoconfig(cdev->gadget, &as_in_ep_desc);
+ if (!ep)
+ goto fail;
+ audio->in_ep = ep;
+ audio->in_ep->desc = &as_in_ep_desc;
+ }
+
+ setup_descriptor(audio_opts);
/* copy descriptors, and track endpoint copies */
status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL,
err_card_register:
usb_free_all_descriptors(f);
fail:
+ kfree(ac_header_desc);
+ ac_header_desc = NULL;
return status;
}
g_audio_cleanup(audio);
usb_free_all_descriptors(f);
+ kfree(ac_header_desc);
+ ac_header_desc = NULL;
+
audio->gadget = NULL;
}
#include "u_audio.h"
#include "u_uac2.h"
+/* UAC2 spec: 4.1 Audio Channel Cluster Descriptor */
+#define UAC2_CHANNEL_MASK 0x07FFFFFF
+
/*
* The driver implements a simple UAC_2 topology.
* USB-OUT -> IT_1 -> OT_3 -> ALSA_Capture
.bInterval = 4,
};
+static struct usb_endpoint_descriptor ss_epout_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
+ /* .wMaxPacketSize = DYNAMIC */
+ .bInterval = 4,
+};
+
+static struct usb_ss_ep_comp_descriptor ss_epout_desc_comp = {
+ .bLength = sizeof(ss_epout_desc_comp),
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+ .bMaxBurst = 0,
+ .bmAttributes = 0,
+ /* wBytesPerInterval = DYNAMIC */
+};
+
/* CS AS ISO OUT Endpoint */
static struct uac2_iso_endpoint_descriptor as_iso_out_desc = {
.bLength = sizeof as_iso_out_desc,
.bInterval = 4,
};
+static struct usb_endpoint_descriptor ss_epin_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
+ /* .wMaxPacketSize = DYNAMIC */
+ .bInterval = 4,
+};
+
+static struct usb_ss_ep_comp_descriptor ss_epin_desc_comp = {
+ .bLength = sizeof(ss_epin_desc_comp),
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+ .bMaxBurst = 0,
+ .bmAttributes = 0,
+ /* wBytesPerInterval = DYNAMIC */
+};
+
/* CS AS ISO IN Endpoint */
static struct uac2_iso_endpoint_descriptor as_iso_in_desc = {
.bLength = sizeof as_iso_in_desc,
NULL,
};
+static struct usb_descriptor_header *ss_audio_desc[] = {
+ (struct usb_descriptor_header *)&iad_desc,
+ (struct usb_descriptor_header *)&std_ac_if_desc,
+
+ (struct usb_descriptor_header *)&ac_hdr_desc,
+ (struct usb_descriptor_header *)&in_clk_src_desc,
+ (struct usb_descriptor_header *)&out_clk_src_desc,
+ (struct usb_descriptor_header *)&usb_out_it_desc,
+ (struct usb_descriptor_header *)&io_in_it_desc,
+ (struct usb_descriptor_header *)&usb_in_ot_desc,
+ (struct usb_descriptor_header *)&io_out_ot_desc,
+
+ (struct usb_descriptor_header *)&std_as_out_if0_desc,
+ (struct usb_descriptor_header *)&std_as_out_if1_desc,
+
+ (struct usb_descriptor_header *)&as_out_hdr_desc,
+ (struct usb_descriptor_header *)&as_out_fmt1_desc,
+ (struct usb_descriptor_header *)&ss_epout_desc,
+ (struct usb_descriptor_header *)&ss_epout_desc_comp,
+ (struct usb_descriptor_header *)&as_iso_out_desc,
+
+ (struct usb_descriptor_header *)&std_as_in_if0_desc,
+ (struct usb_descriptor_header *)&std_as_in_if1_desc,
+
+ (struct usb_descriptor_header *)&as_in_hdr_desc,
+ (struct usb_descriptor_header *)&as_in_fmt1_desc,
+ (struct usb_descriptor_header *)&ss_epin_desc,
+ (struct usb_descriptor_header *)&ss_epin_desc_comp,
+ (struct usb_descriptor_header *)&as_iso_in_desc,
+ NULL,
+};
+
struct cntrl_cur_lay3 {
__le32 dCUR;
};
break;
case USB_SPEED_HIGH:
+ case USB_SPEED_SUPER:
max_size_ep = 1024;
factor = 8000;
break;
/* Use macro to overcome line length limitation */
#define USBDHDR(p) (struct usb_descriptor_header *)(p)
+static void setup_headers(struct f_uac2_opts *opts,
+ struct usb_descriptor_header **headers,
+ enum usb_device_speed speed)
+{
+ struct usb_ss_ep_comp_descriptor *epout_desc_comp = NULL;
+ struct usb_ss_ep_comp_descriptor *epin_desc_comp = NULL;
+ struct usb_endpoint_descriptor *epout_desc;
+ struct usb_endpoint_descriptor *epin_desc;
+ int i;
+
+ switch (speed) {
+ case USB_SPEED_FULL:
+ epout_desc = &fs_epout_desc;
+ epin_desc = &fs_epin_desc;
+ break;
+ case USB_SPEED_HIGH:
+ epout_desc = &hs_epout_desc;
+ epin_desc = &hs_epin_desc;
+ break;
+ default:
+ epout_desc = &ss_epout_desc;
+ epin_desc = &ss_epin_desc;
+ epout_desc_comp = &ss_epout_desc_comp;
+ epin_desc_comp = &ss_epin_desc_comp;
+ }
+
+ i = 0;
+ headers[i++] = USBDHDR(&iad_desc);
+ headers[i++] = USBDHDR(&std_ac_if_desc);
+ headers[i++] = USBDHDR(&ac_hdr_desc);
+ if (EPIN_EN(opts))
+ headers[i++] = USBDHDR(&in_clk_src_desc);
+ if (EPOUT_EN(opts)) {
+ headers[i++] = USBDHDR(&out_clk_src_desc);
+ headers[i++] = USBDHDR(&usb_out_it_desc);
+ }
+ if (EPIN_EN(opts)) {
+ headers[i++] = USBDHDR(&io_in_it_desc);
+ headers[i++] = USBDHDR(&usb_in_ot_desc);
+ }
+ if (EPOUT_EN(opts)) {
+ headers[i++] = USBDHDR(&io_out_ot_desc);
+ headers[i++] = USBDHDR(&std_as_out_if0_desc);
+ headers[i++] = USBDHDR(&std_as_out_if1_desc);
+ headers[i++] = USBDHDR(&as_out_hdr_desc);
+ headers[i++] = USBDHDR(&as_out_fmt1_desc);
+ headers[i++] = USBDHDR(epout_desc);
+ if (epout_desc_comp)
+ headers[i++] = USBDHDR(epout_desc_comp);
+
+ headers[i++] = USBDHDR(&as_iso_out_desc);
+ }
+ if (EPIN_EN(opts)) {
+ headers[i++] = USBDHDR(&std_as_in_if0_desc);
+ headers[i++] = USBDHDR(&std_as_in_if1_desc);
+ headers[i++] = USBDHDR(&as_in_hdr_desc);
+ headers[i++] = USBDHDR(&as_in_fmt1_desc);
+ headers[i++] = USBDHDR(epin_desc);
+ if (epin_desc_comp)
+ headers[i++] = USBDHDR(epin_desc_comp);
+
+ headers[i++] = USBDHDR(&as_iso_in_desc);
+ }
+ headers[i] = NULL;
+}
+
static void setup_descriptor(struct f_uac2_opts *opts)
{
/* patch descriptors */
iad_desc.bInterfaceCount++;
}
- i = 0;
- fs_audio_desc[i++] = USBDHDR(&iad_desc);
- fs_audio_desc[i++] = USBDHDR(&std_ac_if_desc);
- fs_audio_desc[i++] = USBDHDR(&ac_hdr_desc);
- if (EPIN_EN(opts))
- fs_audio_desc[i++] = USBDHDR(&in_clk_src_desc);
- if (EPOUT_EN(opts)) {
- fs_audio_desc[i++] = USBDHDR(&out_clk_src_desc);
- fs_audio_desc[i++] = USBDHDR(&usb_out_it_desc);
- }
- if (EPIN_EN(opts)) {
- fs_audio_desc[i++] = USBDHDR(&io_in_it_desc);
- fs_audio_desc[i++] = USBDHDR(&usb_in_ot_desc);
- }
- if (EPOUT_EN(opts)) {
- fs_audio_desc[i++] = USBDHDR(&io_out_ot_desc);
- fs_audio_desc[i++] = USBDHDR(&std_as_out_if0_desc);
- fs_audio_desc[i++] = USBDHDR(&std_as_out_if1_desc);
- fs_audio_desc[i++] = USBDHDR(&as_out_hdr_desc);
- fs_audio_desc[i++] = USBDHDR(&as_out_fmt1_desc);
- fs_audio_desc[i++] = USBDHDR(&fs_epout_desc);
- fs_audio_desc[i++] = USBDHDR(&as_iso_out_desc);
- }
- if (EPIN_EN(opts)) {
- fs_audio_desc[i++] = USBDHDR(&std_as_in_if0_desc);
- fs_audio_desc[i++] = USBDHDR(&std_as_in_if1_desc);
- fs_audio_desc[i++] = USBDHDR(&as_in_hdr_desc);
- fs_audio_desc[i++] = USBDHDR(&as_in_fmt1_desc);
- fs_audio_desc[i++] = USBDHDR(&fs_epin_desc);
- fs_audio_desc[i++] = USBDHDR(&as_iso_in_desc);
- }
- fs_audio_desc[i] = NULL;
+ setup_headers(opts, fs_audio_desc, USB_SPEED_FULL);
+ setup_headers(opts, hs_audio_desc, USB_SPEED_HIGH);
+ setup_headers(opts, ss_audio_desc, USB_SPEED_SUPER);
+}
- i = 0;
- hs_audio_desc[i++] = USBDHDR(&iad_desc);
- hs_audio_desc[i++] = USBDHDR(&std_ac_if_desc);
- hs_audio_desc[i++] = USBDHDR(&ac_hdr_desc);
- if (EPIN_EN(opts))
- hs_audio_desc[i++] = USBDHDR(&in_clk_src_desc);
- if (EPOUT_EN(opts)) {
- hs_audio_desc[i++] = USBDHDR(&out_clk_src_desc);
- hs_audio_desc[i++] = USBDHDR(&usb_out_it_desc);
- }
- if (EPIN_EN(opts)) {
- hs_audio_desc[i++] = USBDHDR(&io_in_it_desc);
- hs_audio_desc[i++] = USBDHDR(&usb_in_ot_desc);
- }
- if (EPOUT_EN(opts)) {
- hs_audio_desc[i++] = USBDHDR(&io_out_ot_desc);
- hs_audio_desc[i++] = USBDHDR(&std_as_out_if0_desc);
- hs_audio_desc[i++] = USBDHDR(&std_as_out_if1_desc);
- hs_audio_desc[i++] = USBDHDR(&as_out_hdr_desc);
- hs_audio_desc[i++] = USBDHDR(&as_out_fmt1_desc);
- hs_audio_desc[i++] = USBDHDR(&hs_epout_desc);
- hs_audio_desc[i++] = USBDHDR(&as_iso_out_desc);
- }
- if (EPIN_EN(opts)) {
- hs_audio_desc[i++] = USBDHDR(&std_as_in_if0_desc);
- hs_audio_desc[i++] = USBDHDR(&std_as_in_if1_desc);
- hs_audio_desc[i++] = USBDHDR(&as_in_hdr_desc);
- hs_audio_desc[i++] = USBDHDR(&as_in_fmt1_desc);
- hs_audio_desc[i++] = USBDHDR(&hs_epin_desc);
- hs_audio_desc[i++] = USBDHDR(&as_iso_in_desc);
+static int afunc_validate_opts(struct g_audio *agdev, struct device *dev)
+{
+ struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev);
+
+ if (!opts->p_chmask && !opts->c_chmask) {
+ dev_err(dev, "Error: no playback and capture channels\n");
+ return -EINVAL;
+ } else if (opts->p_chmask & ~UAC2_CHANNEL_MASK) {
+ dev_err(dev, "Error: unsupported playback channels mask\n");
+ return -EINVAL;
+ } else if (opts->c_chmask & ~UAC2_CHANNEL_MASK) {
+ dev_err(dev, "Error: unsupported capture channels mask\n");
+ return -EINVAL;
+ } else if ((opts->p_ssize < 1) || (opts->p_ssize > 4)) {
+ dev_err(dev, "Error: incorrect playback sample size\n");
+ return -EINVAL;
+ } else if ((opts->c_ssize < 1) || (opts->c_ssize > 4)) {
+ dev_err(dev, "Error: incorrect capture sample size\n");
+ return -EINVAL;
+ } else if (!opts->p_srate) {
+ dev_err(dev, "Error: incorrect playback sampling rate\n");
+ return -EINVAL;
+ } else if (!opts->c_srate) {
+ dev_err(dev, "Error: incorrect capture sampling rate\n");
+ return -EINVAL;
}
- hs_audio_desc[i] = NULL;
+
+ return 0;
}
static int
struct usb_composite_dev *cdev = cfg->cdev;
struct usb_gadget *gadget = cdev->gadget;
struct device *dev = &gadget->dev;
- struct f_uac2_opts *uac2_opts;
+ struct f_uac2_opts *uac2_opts = g_audio_to_uac2_opts(agdev);
struct usb_string *us;
int ret;
- uac2_opts = container_of(fn->fi, struct f_uac2_opts, func_inst);
+ ret = afunc_validate_opts(agdev, dev);
+ if (ret)
+ return ret;
us = usb_gstrings_attach(cdev, fn_strings, ARRAY_SIZE(strings_fn));
if (IS_ERR(us))
return ret;
}
+ ret = set_ep_max_packet_size(uac2_opts, &ss_epin_desc, USB_SPEED_SUPER,
+ true);
+ if (ret < 0) {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ return ret;
+ }
+
+ ret = set_ep_max_packet_size(uac2_opts, &ss_epout_desc, USB_SPEED_SUPER,
+ false);
+ if (ret < 0) {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ return ret;
+ }
+
if (EPOUT_EN(uac2_opts)) {
agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc);
if (!agdev->out_ep) {
le16_to_cpu(fs_epout_desc.wMaxPacketSize),
le16_to_cpu(hs_epout_desc.wMaxPacketSize));
+ agdev->in_ep_maxpsize = max_t(u16, agdev->in_ep_maxpsize,
+ le16_to_cpu(ss_epin_desc.wMaxPacketSize));
+ agdev->out_ep_maxpsize = max_t(u16, agdev->out_ep_maxpsize,
+ le16_to_cpu(ss_epout_desc.wMaxPacketSize));
+
hs_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress;
hs_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress;
+ ss_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress;
+ ss_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress;
setup_descriptor(uac2_opts);
- ret = usb_assign_descriptors(fn, fs_audio_desc, hs_audio_desc, NULL,
- NULL);
+ ret = usb_assign_descriptors(fn, fs_audio_desc, hs_audio_desc, ss_audio_desc,
+ ss_audio_desc);
if (ret)
return ret;
uvc_hs_streaming_ep.wMaxPacketSize =
cpu_to_le16(max_packet_size | ((max_packet_mult - 1) << 11));
- uvc_hs_streaming_ep.bInterval = opts->streaming_interval;
+
+ /* A high-bandwidth endpoint must specify a bInterval value of 1 */
+ if (max_packet_mult > 1)
+ uvc_hs_streaming_ep.bInterval = 1;
+ else
+ uvc_hs_streaming_ep.bInterval = opts->streaming_interval;
uvc_ss_streaming_ep.wMaxPacketSize = cpu_to_le16(max_packet_size);
uvc_ss_streaming_ep.bInterval = opts->streaming_interval;
pd->bmControls[0] = 1;
pd->bmControls[1] = 0;
pd->iProcessing = 0;
+ pd->bmVideoStandards = 0;
od = &opts->uvc_output_terminal;
od->bLength = UVC_DT_OUTPUT_TERMINAL_SIZE;
if (err < 0)
goto snd_fail;
- strlcpy(pcm->name, pcm_name, sizeof(pcm->name));
+ strscpy(pcm->name, pcm_name, sizeof(pcm->name));
pcm->private_data = uac;
uac->pcm = pcm;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac_pcm_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac_pcm_ops);
- strlcpy(card->driver, card_name, sizeof(card->driver));
- strlcpy(card->shortname, card_name, sizeof(card->shortname));
+ strscpy(card->driver, card_name, sizeof(card->driver));
+ strscpy(card->shortname, card_name, sizeof(card->shortname));
sprintf(card->longname, "%s %i", card_name, card->dev->id);
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
h->desc.bLength = UVC_DT_HEADER_SIZE(1);
h->desc.bDescriptorType = USB_DT_CS_INTERFACE;
h->desc.bDescriptorSubType = UVC_VC_HEADER;
- h->desc.bcdUVC = cpu_to_le16(0x0100);
+ h->desc.bcdUVC = cpu_to_le16(0x0110);
h->desc.dwClockFrequency = cpu_to_le32(48000000);
config_item_init_type_name(&h->item, name, &uvcg_control_header_type);
iocb->private = NULL;
/* aio_complete() reports bytes-transferred _and_ faults */
- iocb->ki_complete(iocb, req->actual ? req->actual : req->status,
+ iocb->ki_complete(iocb,
+ req->actual ? req->actual : (long)req->status,
req->status);
} else {
/* ep_copy_to_user() won't report both; we hide some faults */
struct usb_descriptor_header *usb_desc;
usb_desc = usb_otg_descriptor_alloc(cdev->gadget);
- if (!usb_desc)
+ if (!usb_desc) {
+ status = -ENOMEM;
goto fail_string_ids;
+ }
usb_otg_descriptor_init(cdev->gadget, usb_desc);
otg_desc[0] = usb_desc;
otg_desc[1] = NULL;
return ret;
}
-static __ref int rndis_config_register(struct usb_composite_dev *cdev)
+static int rndis_config_register(struct usb_composite_dev *cdev)
{
static struct usb_configuration config = {
.bConfigurationValue = MULTI_RNDIS_CONFIG_NUM,
#else
-static __ref int rndis_config_register(struct usb_composite_dev *cdev)
+static int rndis_config_register(struct usb_composite_dev *cdev)
{
return 0;
}
return ret;
}
-static __ref int cdc_config_register(struct usb_composite_dev *cdev)
+static int cdc_config_register(struct usb_composite_dev *cdev)
{
static struct usb_configuration config = {
.bConfigurationValue = MULTI_CDC_CONFIG_NUM,
#else
-static __ref int cdc_config_register(struct usb_composite_dev *cdev)
+static int cdc_config_register(struct usb_composite_dev *cdev)
{
return 0;
}
/****************************** Gadget Bind ******************************/
-static int __ref multi_bind(struct usb_composite_dev *cdev)
+static int multi_bind(struct usb_composite_dev *cdev)
{
struct usb_gadget *gadget = cdev->gadget;
#ifdef CONFIG_USB_G_MULTI_CDC
struct usb_descriptor_header *usb_desc;
usb_desc = usb_otg_descriptor_alloc(gadget);
- if (!usb_desc)
+ if (!usb_desc) {
+ status = -ENOMEM;
goto fail_string_ids;
+ }
usb_otg_descriptor_init(gadget, usb_desc);
otg_desc[0] = usb_desc;
otg_desc[1] = NULL;
.bLength = UVC_DT_HEADER_SIZE(1),
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubType = UVC_VC_HEADER,
- .bcdUVC = cpu_to_le16(0x0100),
+ .bcdUVC = cpu_to_le16(0x0110),
.wTotalLength = 0, /* dynamic */
.dwClockFrequency = cpu_to_le32(48000000),
.bInCollection = 0, /* dynamic */
.bmControls[0] = 1,
.bmControls[1] = 0,
.iProcessing = 0,
+ .bmVideoStandards = 0,
};
static const struct uvc_output_terminal_descriptor uvc_output_terminal = {
int status)
{
bool internal = req->internal;
+ struct ast_vhub *vhub = ep->vhub;
EPVDBG(ep, "completing request @%p, status %d\n", req, status);
if (req->req.dma) {
if (!WARN_ON(!ep->dev))
- usb_gadget_unmap_request(&ep->dev->gadget,
+ usb_gadget_unmap_request_by_dev(&vhub->pdev->dev,
&req->req, ep->epn.is_in);
req->req.dma = 0;
}
if (ep->epn.desc_mode ||
((((unsigned long)u_req->buf & 7) == 0) &&
(ep->epn.is_in || !(u_req->length & (u_ep->maxpacket - 1))))) {
- rc = usb_gadget_map_request(&ep->dev->gadget, u_req,
+ rc = usb_gadget_map_request_by_dev(&vhub->pdev->dev, u_req,
ep->epn.is_in);
if (rc) {
dev_warn(&vhub->pdev->dev,
spin_lock_irqsave(&dum->lock, flags);
dum->pullup = (value != 0);
set_link_state(dum_hcd);
+ if (value == 0) {
+ /*
+ * Emulate synchronize_irq(): wait for callbacks to finish.
+ * This seems to be the best place to emulate the call to
+ * synchronize_irq() that's in usb_gadget_remove_driver().
+ * Doing it in dummy_udc_stop() would be too late since it
+ * is called after the unbind callback and unbind shouldn't
+ * be invoked until all the other callbacks are finished.
+ */
+ while (dum->callback_usage > 0) {
+ spin_unlock_irqrestore(&dum->lock, flags);
+ usleep_range(1000, 2000);
+ spin_lock_irqsave(&dum->lock, flags);
+ }
+ }
spin_unlock_irqrestore(&dum->lock, flags);
usb_hcd_poll_rh_status(dummy_hcd_to_hcd(dum_hcd));
spin_lock_irq(&dum->lock);
dum->ints_enabled = 0;
stop_activity(dum);
-
- /* emulate synchronize_irq(): wait for callbacks to finish */
- while (dum->callback_usage > 0) {
- spin_unlock_irq(&dum->lock);
- usleep_range(1000, 2000);
- spin_lock_irq(&dum->lock);
- }
-
dum->driver = NULL;
spin_unlock_irq(&dum->lock);
/* handle control requests */
if (ep == &dum->ep[0] && ep->setup_stage) {
struct usb_ctrlrequest setup;
- int value = 1;
+ int value;
setup = *(struct usb_ctrlrequest *) urb->setup_packet;
/* paranoia, in case of stale queued data */
} else {
buffer = req->req.buf + req->req.actual;
length = ioread32(ep->fotg210->reg +
- FOTG210_FIBCR(ep->epnum - 1));
- length &= FIBCR_BCFX;
+ FOTG210_FIBCR(ep->epnum - 1)) & FIBCR_BCFX;
+ if (length > req->req.length - req->req.actual)
+ length = req->req.length - req->req.actual;
}
} else {
buffer = req->req.buf + req->req.actual;
if (req->req.length - req->req.actual > ep->ep.maxpacket)
length = ep->ep.maxpacket;
else
- length = req->req.length;
+ length = req->req.length - req->req.actual;
}
d = dma_map_single(dev, buffer, length,
}
if (ep->dir_in) { /* if IN */
fotg210_start_dma(ep, req);
- if ((req->req.length == req->req.actual) ||
- (req->req.actual < ep->ep.maxpacket))
+ if (req->req.length == req->req.actual)
fotg210_done(ep, req, 0);
} else { /* OUT */
u32 value = ioread32(ep->fotg210->reg + FOTG210_DMISGR0);
if (req->req.length)
fotg210_start_dma(ep, req);
- if ((req->req.length - req->req.actual) < ep->ep.maxpacket)
+ if (req->req.actual == req->req.length)
fotg210_done(ep, req, 0);
} else {
fotg210_set_cxdone(fotg210);
{
struct fotg210_request *req = list_entry(ep->queue.next,
struct fotg210_request, queue);
+ int disgr1 = ioread32(ep->fotg210->reg + FOTG210_DISGR1);
fotg210_start_dma(ep, req);
- /* finish out transfer */
+ /* Complete the request when it's full or a short packet arrived.
+ * Like other drivers, short_not_ok isn't handled.
+ */
+
if (req->req.length == req->req.actual ||
- req->req.actual < ep->ep.maxpacket)
+ (disgr1 & DISGR1_SPK_INT(ep->epnum - 1)))
fotg210_done(ep, req, 0);
}
int_grp2 &= ~int_msk2;
if (int_grp2 & DISGR2_USBRST_INT) {
+ usb_gadget_udc_reset(&fotg210->gadget,
+ fotg210->driver);
value = ioread32(reg);
value &= ~DISGR2_USBRST_INT;
iowrite32(value, reg);
value &= ~DMCR_GLINT_EN;
iowrite32(value, fotg210->reg + FOTG210_DMCR);
+ /* enable only grp2 irqs we handle */
+ iowrite32(~(DISGR2_DMA_ERROR | DISGR2_RX0BYTE_INT | DISGR2_TX0BYTE_INT
+ | DISGR2_ISO_SEQ_ABORT_INT | DISGR2_ISO_SEQ_ERR_INT
+ | DISGR2_RESM_INT | DISGR2_SUSP_INT | DISGR2_USBRST_INT),
+ fotg210->reg + FOTG210_DMISGR2);
+
/* disable all fifo interrupt */
iowrite32(~(u32)0, fotg210->reg + FOTG210_DMISGR1);
int count;
int tmp;
int cleanup = 0;
- int status = -1;
dev_vdbg(ep->dev->dev, "read_fifo %s actual %d len %d\n",
ep->ep.name, req->req.actual, req->req.length);
}
if (!list_empty(&ep->queue)) {
+ int status;
+
req = list_entry(ep->queue.next,
struct net2272_request, queue);
status = net2272_kick_dma(ep, req);
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/errno.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/machine.h>
#include <linux/list.h>
#include <linux/interrupt.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
-#include <linux/gpio/consumer.h>
#include <linux/irq.h>
#define PCH_VBUS_PERIOD 3000 /* VBUS polling period (msec) */
* @dma_addr: DMA pool for received
* @setup_data: Received setup data
* @base_addr: for mapped device memory
+ * @bar: PCI BAR used for mapped device memory
* @cfg_data: current cfg, intf, and alt in use
* @vbus_gpio: GPIO informaton for detecting VBUS
*/
dma_addr_t dma_addr;
struct usb_ctrlrequest setup_data;
void __iomem *base_addr;
+ unsigned short bar;
struct pch_udc_cfg_data cfg_data;
struct pch_vbus_gpio_data vbus_gpio;
};
* @td_data_last: last dma desc. of chain
* @queue: associated queue
* @dma_going: DMA in progress for request
- * @dma_mapped: DMA memory mapped for request
* @dma_done: DMA completed for request
* @chain_len: chain length
- * @buf: Buffer memory for align adjustment
- * @dma: DMA memory for align adjustment
*/
struct pch_udc_request {
struct usb_request req;
struct pch_udc_data_dma_desc *td_data_last;
struct list_head queue;
unsigned dma_going:1,
- dma_mapped:1,
dma_done:1;
unsigned chain_len;
- void *buf;
- dma_addr_t dma;
};
static inline u32 pch_udc_readl(struct pch_udc_dev *dev, unsigned long reg)
pch_udc_bit_clr(dev, UDC_DEVCTL_ADDR, UDC_DEVCTL_RES);
}
+static void pch_udc_init(struct pch_udc_dev *dev);
+
/**
* pch_udc_reconnect() - This API initializes usb device controller,
* and clear the disconnect status.
* @dev: Reference to pch_udc_regs structure
*/
-static void pch_udc_init(struct pch_udc_dev *dev);
static void pch_udc_reconnect(struct pch_udc_dev *dev)
{
pch_udc_init(dev);
static inline void pch_udc_vbus_session(struct pch_udc_dev *dev,
int is_active)
{
+ unsigned long iflags;
+
+ spin_lock_irqsave(&dev->lock, iflags);
if (is_active) {
pch_udc_reconnect(dev);
dev->vbus_session = 1;
} else {
if (dev->driver && dev->driver->disconnect) {
- spin_lock(&dev->lock);
+ spin_unlock_irqrestore(&dev->lock, iflags);
dev->driver->disconnect(&dev->gadget);
- spin_unlock(&dev->lock);
+ spin_lock_irqsave(&dev->lock, iflags);
}
pch_udc_set_disconnect(dev);
dev->vbus_session = 0;
}
+ spin_unlock_irqrestore(&dev->lock, iflags);
}
/**
static int pch_udc_pcd_pullup(struct usb_gadget *gadget, int is_on)
{
struct pch_udc_dev *dev;
+ unsigned long iflags;
if (!gadget)
return -EINVAL;
+
dev = container_of(gadget, struct pch_udc_dev, gadget);
+
+ spin_lock_irqsave(&dev->lock, iflags);
if (is_on) {
pch_udc_reconnect(dev);
} else {
if (dev->driver && dev->driver->disconnect) {
- spin_lock(&dev->lock);
+ spin_unlock_irqrestore(&dev->lock, iflags);
dev->driver->disconnect(&dev->gadget);
- spin_unlock(&dev->lock);
+ spin_lock_irqsave(&dev->lock, iflags);
}
pch_udc_set_disconnect(dev);
}
+ spin_unlock_irqrestore(&dev->lock, iflags);
return 0;
}
*/
static int pch_vbus_gpio_init(struct pch_udc_dev *dev)
{
+ struct device *d = &dev->pdev->dev;
int err;
int irq_num = 0;
struct gpio_desc *gpiod;
dev->vbus_gpio.intr = 0;
/* Retrieve the GPIO line from the USB gadget device */
- gpiod = devm_gpiod_get(dev->gadget.dev.parent, NULL, GPIOD_IN);
+ gpiod = devm_gpiod_get_optional(d, NULL, GPIOD_IN);
if (IS_ERR(gpiod))
return PTR_ERR(gpiod);
gpiod_set_consumer_name(gpiod, "pch_vbus");
status = req->req.status;
dev = ep->dev;
- if (req->dma_mapped) {
- if (req->dma == DMA_ADDR_INVALID) {
- if (ep->in)
- dma_unmap_single(&dev->pdev->dev, req->req.dma,
- req->req.length,
- DMA_TO_DEVICE);
- else
- dma_unmap_single(&dev->pdev->dev, req->req.dma,
- req->req.length,
- DMA_FROM_DEVICE);
- req->req.dma = DMA_ADDR_INVALID;
- } else {
- if (ep->in)
- dma_unmap_single(&dev->pdev->dev, req->dma,
- req->req.length,
- DMA_TO_DEVICE);
- else {
- dma_unmap_single(&dev->pdev->dev, req->dma,
- req->req.length,
- DMA_FROM_DEVICE);
- memcpy(req->req.buf, req->buf, req->req.length);
- }
- kfree(req->buf);
- req->dma = DMA_ADDR_INVALID;
- }
- req->dma_mapped = 0;
- }
+ usb_gadget_unmap_request(&dev->gadget, &req->req, ep->in);
ep->halted = 1;
spin_unlock(&dev->lock);
if (!ep->in)
if (req->chain_len > 1)
pch_udc_free_dma_chain(ep->dev, req);
- if (req->dma == DMA_ADDR_INVALID)
- td->dataptr = req->req.dma;
- else
- td->dataptr = req->dma;
-
+ td->dataptr = req->req.dma;
td->status = PCH_UDC_BS_HST_BSY;
+
for (; ; bytes -= buf_len, ++len) {
td->status = PCH_UDC_BS_HST_BSY | min(buf_len, bytes);
if (bytes <= buf_len)
if (!req)
return NULL;
req->req.dma = DMA_ADDR_INVALID;
- req->dma = DMA_ADDR_INVALID;
INIT_LIST_HEAD(&req->queue);
if (!ep->dev->dma_addr)
return &req->req;
}
/* prevent from using desc. - set HOST BUSY */
dma_desc->status |= PCH_UDC_BS_HST_BSY;
- dma_desc->dataptr = cpu_to_le32(DMA_ADDR_INVALID);
+ dma_desc->dataptr = lower_32_bits(DMA_ADDR_INVALID);
req->td_data = dma_desc;
req->td_data_last = dma_desc;
req->chain_len = 1;
return -ESHUTDOWN;
spin_lock_irqsave(&dev->lock, iflags);
/* map the buffer for dma */
- if (usbreq->length &&
- ((usbreq->dma == DMA_ADDR_INVALID) || !usbreq->dma)) {
- if (!((unsigned long)(usbreq->buf) & 0x03)) {
- if (ep->in)
- usbreq->dma = dma_map_single(&dev->pdev->dev,
- usbreq->buf,
- usbreq->length,
- DMA_TO_DEVICE);
- else
- usbreq->dma = dma_map_single(&dev->pdev->dev,
- usbreq->buf,
- usbreq->length,
- DMA_FROM_DEVICE);
- } else {
- req->buf = kzalloc(usbreq->length, GFP_ATOMIC);
- if (!req->buf) {
- retval = -ENOMEM;
- goto probe_end;
- }
- if (ep->in) {
- memcpy(req->buf, usbreq->buf, usbreq->length);
- req->dma = dma_map_single(&dev->pdev->dev,
- req->buf,
- usbreq->length,
- DMA_TO_DEVICE);
- } else
- req->dma = dma_map_single(&dev->pdev->dev,
- req->buf,
- usbreq->length,
- DMA_FROM_DEVICE);
- }
- req->dma_mapped = 1;
- }
+ retval = usb_gadget_map_request(&dev->gadget, usbreq, ep->in);
+ if (retval)
+ goto probe_end;
if (usbreq->length > 0) {
retval = prepare_dma(ep, req, GFP_ATOMIC);
if (retval)
pch_udc_set_dma(dev, DMA_DIR_RX);
}
+static int pch_udc_gadget_setup(struct pch_udc_dev *dev)
+ __must_hold(&dev->lock)
+{
+ int rc;
+
+ /* In some cases we can get an interrupt before driver gets setup */
+ if (!dev->driver)
+ return -ESHUTDOWN;
+
+ spin_unlock(&dev->lock);
+ rc = dev->driver->setup(&dev->gadget, &dev->setup_data);
+ spin_lock(&dev->lock);
+ return rc;
+}
+
/**
* pch_udc_svc_control_in() - Handle Control IN endpoint interrupts
* @dev: Reference to the device structure
dev->gadget.ep0 = &dev->ep[UDC_EP0IN_IDX].ep;
else /* OUT */
dev->gadget.ep0 = &ep->ep;
- spin_lock(&dev->lock);
/* If Mass storage Reset */
if ((dev->setup_data.bRequestType == 0x21) &&
(dev->setup_data.bRequest == 0xFF))
dev->prot_stall = 0;
/* call gadget with setup data received */
- setup_supported = dev->driver->setup(&dev->gadget,
- &dev->setup_data);
- spin_unlock(&dev->lock);
+ setup_supported = pch_udc_gadget_setup(dev);
if (dev->setup_data.bRequestType & USB_DIR_IN) {
ep->td_data->status = (ep->td_data->status &
dev->ep[i].halted = 0;
}
dev->stall = 0;
- spin_unlock(&dev->lock);
- dev->driver->setup(&dev->gadget, &dev->setup_data);
- spin_lock(&dev->lock);
+ pch_udc_gadget_setup(dev);
}
/**
dev->stall = 0;
/* call gadget zero with setup data received */
- spin_unlock(&dev->lock);
- dev->driver->setup(&dev->gadget, &dev->setup_data);
- spin_lock(&dev->lock);
+ pch_udc_gadget_setup(dev);
}
/**
* @dev: Reference to the driver structure
*
* Return codes:
- * 0: Success
+ * 0: Success
+ * -ERRNO: All kind of errors when retrieving VBUS GPIO
*/
static int pch_udc_pcd_init(struct pch_udc_dev *dev)
{
+ int ret;
+
pch_udc_init(dev);
pch_udc_pcd_reinit(dev);
- pch_vbus_gpio_init(dev);
- return 0;
+
+ ret = pch_vbus_gpio_init(dev);
+ if (ret)
+ pch_udc_exit(dev);
+ return ret;
}
/**
dev->dma_addr = dma_map_single(&dev->pdev->dev, ep0out_buf,
UDC_EP0OUT_BUFF_SIZE * 4,
DMA_FROM_DEVICE);
- return 0;
+ return dma_mapping_error(&dev->pdev->dev, dev->dma_addr);
}
static int pch_udc_start(struct usb_gadget *g,
return 0;
}
+static void pch_vbus_gpio_remove_table(void *table)
+{
+ gpiod_remove_lookup_table(table);
+}
+
+static int pch_vbus_gpio_add_table(struct device *d, void *table)
+{
+ gpiod_add_lookup_table(table);
+ return devm_add_action_or_reset(d, pch_vbus_gpio_remove_table, table);
+}
+
+static struct gpiod_lookup_table pch_udc_minnow_vbus_gpio_table = {
+ .dev_id = "0000:02:02.4",
+ .table = {
+ GPIO_LOOKUP("sch_gpio.33158", 12, NULL, GPIO_ACTIVE_HIGH),
+ {}
+ },
+};
+
+static int pch_udc_minnow_platform_init(struct device *d)
+{
+ return pch_vbus_gpio_add_table(d, &pch_udc_minnow_vbus_gpio_table);
+}
+
+static int pch_udc_quark_platform_init(struct device *d)
+{
+ struct pch_udc_dev *dev = dev_get_drvdata(d);
+
+ dev->bar = PCH_UDC_PCI_BAR_QUARK_X1000;
+ return 0;
+}
+
static void pch_udc_shutdown(struct pci_dev *pdev)
{
struct pch_udc_dev *dev = pci_get_drvdata(pdev);
pch_udc_exit(dev);
}
-#ifdef CONFIG_PM_SLEEP
-static int pch_udc_suspend(struct device *d)
+static int __maybe_unused pch_udc_suspend(struct device *d)
{
struct pch_udc_dev *dev = dev_get_drvdata(d);
return 0;
}
-static int pch_udc_resume(struct device *d)
+static int __maybe_unused pch_udc_resume(struct device *d)
{
return 0;
}
static SIMPLE_DEV_PM_OPS(pch_udc_pm, pch_udc_suspend, pch_udc_resume);
-#define PCH_UDC_PM_OPS (&pch_udc_pm)
-#else
-#define PCH_UDC_PM_OPS NULL
-#endif /* CONFIG_PM_SLEEP */
-static int pch_udc_probe(struct pci_dev *pdev,
- const struct pci_device_id *id)
+typedef int (*platform_init_fn)(struct device *);
+
+static int pch_udc_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
- int bar;
+ platform_init_fn platform_init = (platform_init_fn)id->driver_data;
int retval;
struct pch_udc_dev *dev;
if (retval)
return retval;
+ dev->bar = PCH_UDC_PCI_BAR;
+ dev->pdev = pdev;
pci_set_drvdata(pdev, dev);
- /* Determine BAR based on PCI ID */
- if (id->device == PCI_DEVICE_ID_INTEL_QUARK_X1000_UDC)
- bar = PCH_UDC_PCI_BAR_QUARK_X1000;
- else
- bar = PCH_UDC_PCI_BAR;
+ /* Platform specific hook */
+ if (platform_init) {
+ retval = platform_init(&pdev->dev);
+ if (retval)
+ return retval;
+ }
/* PCI resource allocation */
- retval = pcim_iomap_regions(pdev, 1 << bar, pci_name(pdev));
+ retval = pcim_iomap_regions(pdev, BIT(dev->bar), pci_name(pdev));
if (retval)
return retval;
- dev->base_addr = pcim_iomap_table(pdev)[bar];
-
- /*
- * FIXME: add a GPIO descriptor table to pdev.dev using
- * gpiod_add_descriptor_table() from <linux/gpio/machine.h> based on
- * the PCI subsystem ID. The system-dependent GPIO is necessary for
- * VBUS operation.
- */
+ dev->base_addr = pcim_iomap_table(pdev)[dev->bar];
/* initialize the hardware */
- if (pch_udc_pcd_init(dev))
- return -ENODEV;
+ retval = pch_udc_pcd_init(dev);
+ if (retval)
+ return retval;
pci_enable_msi(pdev);
/* device struct setup */
spin_lock_init(&dev->lock);
- dev->pdev = pdev;
dev->gadget.ops = &pch_udc_ops;
retval = init_dma_pools(dev);
static const struct pci_device_id pch_udc_pcidev_id[] = {
{
- PCI_DEVICE(PCI_VENDOR_ID_INTEL,
- PCI_DEVICE_ID_INTEL_QUARK_X1000_UDC),
+ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_QUARK_X1000_UDC),
+ .class = PCI_CLASS_SERIAL_USB_DEVICE,
+ .class_mask = 0xffffffff,
+ .driver_data = (kernel_ulong_t)&pch_udc_quark_platform_init,
+ },
+ {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_EG20T_UDC,
+ PCI_VENDOR_ID_CIRCUITCO, PCI_SUBSYSTEM_ID_CIRCUITCO_MINNOWBOARD),
.class = PCI_CLASS_SERIAL_USB_DEVICE,
.class_mask = 0xffffffff,
+ .driver_data = (kernel_ulong_t)&pch_udc_minnow_platform_init,
},
{
PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_EG20T_UDC),
.remove = pch_udc_remove,
.shutdown = pch_udc_shutdown,
.driver = {
- .pm = PCH_UDC_PM_OPS,
+ .pm = &pch_udc_pm,
},
};
return PTR_ERR(reg);
ires = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!ires)
+ return -EINVAL;
irq = ires->start;
irq_trigger = ires->flags & IRQF_TRIGGER_MASK;
static struct clk *usb_bus_clock;
static void __iomem *base_addr;
static int irq_usbd;
-static u64 rsrc_start;
-static u64 rsrc_len;
static struct dentry *s3c2410_udc_debugfs_root;
static inline u32 udc_read(u32 reg)
udc_clock = clk_get(NULL, "usb-device");
if (IS_ERR(udc_clock)) {
dev_err(dev, "failed to get udc clock source\n");
- return PTR_ERR(udc_clock);
+ retval = PTR_ERR(udc_clock);
+ goto err_usb_bus_clk;
}
clk_prepare_enable(udc_clock);
base_addr = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base_addr)) {
retval = PTR_ERR(base_addr);
- goto err_mem;
+ goto err_udc_clk;
}
the_controller = udc;
if (retval != 0) {
dev_err(dev, "cannot get irq %i, err %d\n", irq_usbd, retval);
retval = -EBUSY;
- goto err_map;
+ goto err_udc_clk;
}
dev_dbg(dev, "got irq %i\n", irq_usbd);
gpio_free(udc_info->vbus_pin);
err_int:
free_irq(irq_usbd, udc);
-err_map:
- iounmap(base_addr);
-err_mem:
- release_mem_region(rsrc_start, rsrc_len);
+err_udc_clk:
+ clk_disable_unprepare(udc_clock);
+ clk_put(udc_clock);
+ udc_clock = NULL;
+err_usb_bus_clk:
+ clk_disable_unprepare(usb_bus_clock);
+ clk_put(usb_bus_clock);
+ usb_bus_clock = NULL;
return retval;
}
free_irq(irq_usbd, udc);
- iounmap(base_addr);
- release_mem_region(rsrc_start, rsrc_len);
-
if (!IS_ERR(udc_clock) && udc_clock != NULL) {
clk_disable_unprepare(udc_clock);
clk_put(udc_clock);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
udc->virt_addr = devm_ioremap_resource(dev, res);
- if (IS_ERR(udc->regs))
- return PTR_ERR(udc->regs);
+ if (IS_ERR(udc->virt_addr))
+ return PTR_ERR(udc->virt_addr);
/* udc csr registers base */
udc->csr = udc->virt_addr + UDC_CSR_ADDR;
pm_runtime_get_sync(xudc->dev);
- cancel_delayed_work(&xudc->plc_reset_work);
+ cancel_delayed_work_sync(&xudc->plc_reset_work);
cancel_work_sync(&xudc->usb_role_sw_work);
usb_del_gadget_udc(&xudc->gadget);
select USB_CHIPIDEA
select USB_CHIPIDEA_HOST
select USB_CHIPIDEA_TEGRA
+ select USB_GADGET
help
This option is deprecated now and the driver was removed, use
USB_CHIPIDEA_TEGRA instead.
xhci-hcd-y += xhci-dbgcap.o xhci-dbgtty.o
endif
-ifneq ($(CONFIG_USB_XHCI_MTK), )
- xhci-hcd-y += xhci-mtk-sch.o
-endif
+xhci-mtk-hcd-y := xhci-mtk.o xhci-mtk-sch.o
xhci-plat-hcd-y := xhci-plat.o
ifneq ($(CONFIG_USB_XHCI_MVEBU), )
obj-$(CONFIG_USB_XHCI_PCI_RENESAS) += xhci-pci-renesas.o
obj-$(CONFIG_USB_XHCI_PLATFORM) += xhci-plat-hcd.o
obj-$(CONFIG_USB_XHCI_HISTB) += xhci-histb.o
-obj-$(CONFIG_USB_XHCI_MTK) += xhci-mtk.o
+obj-$(CONFIG_USB_XHCI_MTK) += xhci-mtk-hcd.o
obj-$(CONFIG_USB_XHCI_TEGRA) += xhci-tegra.o
obj-$(CONFIG_USB_SL811_HCD) += sl811-hcd.o
obj-$(CONFIG_USB_SL811_CS) += sl811_cs.o
"USB %x.%x started, EHCI %x.%02x%s\n",
((ehci->sbrn & 0xf0)>>4), (ehci->sbrn & 0x0f),
temp >> 8, temp & 0xff,
- ignore_oc ? ", overcurrent ignored" : "");
+ (ignore_oc || ehci->spurious_oc) ? ", overcurrent ignored" : "");
ehci_writel(ehci, INTR_MASK,
&ehci->regs->intr_enable); /* Turn On Interrupts */
struct ehci_hcd *ehci = hcd_to_ehci (hcd);
u32 status, masked_status, pcd_status = 0, cmd;
int bh;
- unsigned long flags;
- /*
- * For threadirqs option we use spin_lock_irqsave() variant to prevent
- * deadlock with ehci hrtimer callback, because hrtimer callbacks run
- * in interrupt context even when threadirqs is specified. We can go
- * back to spin_lock() variant when hrtimer callbacks become threaded.
- */
- spin_lock_irqsave(&ehci->lock, flags);
+ spin_lock(&ehci->lock);
status = ehci_readl(ehci, &ehci->regs->status);
/* Shared IRQ? */
if (!masked_status || unlikely(ehci->rh_state == EHCI_RH_HALTED)) {
- spin_unlock_irqrestore(&ehci->lock, flags);
+ spin_unlock(&ehci->lock);
return IRQ_NONE;
}
if (bh)
ehci_work (ehci);
- spin_unlock_irqrestore(&ehci->lock, flags);
+ spin_unlock(&ehci->lock);
if (pcd_status)
usb_hcd_poll_rh_status(hcd);
return IRQ_HANDLED;
* always set, seem to clear PORT_OCC and PORT_CSC when writing to
* PORT_POWER; that's surprising, but maybe within-spec.
*/
- if (!ignore_oc)
+ if (!ignore_oc && !ehci->spurious_oc)
mask = PORT_CSC | PORT_PEC | PORT_OCC;
else
mask = PORT_CSC | PORT_PEC;
if (temp & PORT_PEC)
status |= USB_PORT_STAT_C_ENABLE << 16;
- if ((temp & PORT_OCC) && !ignore_oc){
+ if ((temp & PORT_OCC) && (!ignore_oc && !ehci->spurious_oc)){
status |= USB_PORT_STAT_C_OVERCURRENT << 16;
/*
if (pdev->vendor == PCI_VENDOR_ID_STMICRO
&& pdev->device == PCI_DEVICE_ID_STMICRO_USB_HOST)
; /* ConneXT has no sbrn register */
+ else if (pdev->vendor == PCI_VENDOR_ID_HUAWEI
+ && pdev->device == 0xa239)
+ ; /* HUAWEI Kunpeng920 USB EHCI has no sbrn register */
else
pci_read_config_byte(pdev, 0x60, &ehci->sbrn);
if (of_property_read_bool(dev->dev.of_node, "big-endian"))
ehci->big_endian_mmio = ehci->big_endian_desc = 1;
+ if (of_property_read_bool(dev->dev.of_node, "spurious-oc"))
+ ehci->spurious_oc = 1;
+
if (of_property_read_bool(dev->dev.of_node,
"needs-reset-on-resume"))
priv->reset_on_resume = true;
hcd->has_tt = 1;
if (pdata->reset_on_resume)
priv->reset_on_resume = true;
+ if (pdata->spurious_oc)
+ ehci->spurious_oc = 1;
#ifndef CONFIG_USB_EHCI_BIG_ENDIAN_MMIO
if (ehci->big_endian_mmio) {
unsigned frame_index_bug:1; /* MosChip (AKA NetMos) */
unsigned need_oc_pp_cycle:1; /* MPC834X port power */
unsigned imx28_write_fix:1; /* For Freescale i.MX28 */
+ unsigned spurious_oc:1;
/* required for usb32 quirk */
#define OHCI_CTRL_HCFS (3 << 6)
temp = snprintf(next, size,
"\n\t%p%c%s len=%d %08x urb %p",
td, mark, ({ char *tmp;
- switch ((scratch>>8)&0x03) {
- case 0:
+ switch ((scratch>>8)&0x03) {
+ case 0:
tmp = "out";
break;
- case 1:
+ case 1:
tmp = "in";
break;
- case 2:
+ case 2:
tmp = "setup";
break;
- default:
+ default:
tmp = "?";
break;
} tmp; }),
* any previous qh and cancel its urbs first; endpoints are
* implicitly reset then (data toggle too).
* That'd mean updating how usbcore talks to HCDs. (2.7?)
-*/
+ */
/* Each QH holds a qtd list; a QH is used for everything except iso.
static void create_debug_file(struct isp116x *isp116x)
{
- isp116x->dentry = debugfs_create_file(hcd_name,
- S_IRUGO, NULL, isp116x,
- &isp116x_debug_fops);
+ debugfs_create_file(hcd_name, S_IRUGO, usb_debug_root, isp116x,
+ &isp116x_debug_fops);
}
static void remove_debug_file(struct isp116x *isp116x)
{
- debugfs_remove(isp116x->dentry);
+ debugfs_remove(debugfs_lookup(hcd_name, usb_debug_root));
}
#else
struct isp116x_platform_data *board;
- struct dentry *dentry;
unsigned long stat1, stat2, stat4, stat8, stat16;
/* HC registers */
/* expect just one isp1362_hcd per system */
static void create_debug_file(struct isp1362_hcd *isp1362_hcd)
{
- isp1362_hcd->debug_file = debugfs_create_file("isp1362", S_IRUGO,
- usb_debug_root,
- isp1362_hcd,
- &isp1362_fops);
+ debugfs_create_file("isp1362", S_IRUGO, usb_debug_root, isp1362_hcd,
+ &isp1362_fops);
}
static void remove_debug_file(struct isp1362_hcd *isp1362_hcd)
{
- debugfs_remove(isp1362_hcd->debug_file);
+ debugfs_remove(debugfs_lookup("isp1362", usb_debug_root));
}
/*-------------------------------------------------------------------------*/
struct isp1362_platform_data *board;
- struct dentry *debug_file;
unsigned long stat1, stat2, stat4, stat8, stat16;
/* HC registers */
goto error;
put_unaligned_le32(sl811->port1, buf);
-#ifndef VERBOSE
- if (*(u16*)(buf+2)) /* only if wPortChange is interesting */
-#endif
- dev_dbg(hcd->self.controller, "GetPortStatus %08x\n",
- sl811->port1);
+ if (__is_defined(VERBOSE) ||
+ *(u16*)(buf+2)) /* only if wPortChange is interesting */
+ dev_dbg(hcd->self.controller, "GetPortStatus %08x\n",
+ sl811->port1);
break;
case SetPortFeature:
if (wIndex != 1 || wLength != 0)
/* expect just one sl811 per system */
static void create_debug_file(struct sl811 *sl811)
{
- sl811->debug_file = debugfs_create_file("sl811h", S_IRUGO,
- usb_debug_root, sl811,
- &sl811h_debug_fops);
+ debugfs_create_file("sl811h", S_IRUGO, usb_debug_root, sl811,
+ &sl811h_debug_fops);
}
static void remove_debug_file(struct sl811 *sl811)
{
- debugfs_remove(sl811->debug_file);
+ debugfs_remove(debugfs_lookup("sl811h", usb_debug_root));
}
/*-------------------------------------------------------------------------*/
void __iomem *addr_reg;
void __iomem *data_reg;
struct sl811_platform_data *board;
- struct dentry *debug_file;
unsigned long stat_insrmv;
unsigned long stat_wake;
return -EBUSY;
platform_dev.dev.parent = parent;
- /* finish seting up the platform device */
+ /* finish setting up the platform device */
resources[0].start = irq;
resources[1].start = base_addr;
* u132_module_lock exists to protect access to global variables
*
*/
-static struct mutex u132_module_lock;
+static DEFINE_MUTEX(u132_module_lock);
static int u132_exiting;
static int u132_instances;
/*
int retval;
u132_instances = 0;
u132_exiting = 0;
- mutex_init(&u132_module_lock);
if (usb_disabled())
return -ENODEV;
printk(KERN_INFO "driver %s\n", hcd_name);
uhci->is_initialized = 0;
spin_unlock_irq(&uhci->lock);
- debugfs_remove(uhci->dentry);
+ debugfs_remove(debugfs_lookup(uhci_to_hcd(uhci)->self.bus_name,
+ uhci_debugfs_root));
for (i = 0; i < UHCI_NUM_SKELQH; i++)
uhci_free_qh(uhci, uhci->skelqh[i]);
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
int retval = -EBUSY;
int i;
- struct dentry __maybe_unused *dentry;
hcd->uses_new_polling = 1;
/* Accept arbitrarily long scatter-gather lists */
init_waitqueue_head(&uhci->waitqh);
#ifdef UHCI_DEBUG_OPS
- uhci->dentry = debugfs_create_file(hcd->self.bus_name,
- S_IFREG|S_IRUGO|S_IWUSR,
- uhci_debugfs_root, uhci,
- &uhci_debug_operations);
+ debugfs_create_file(hcd->self.bus_name, S_IFREG|S_IRUGO|S_IWUSR,
+ uhci_debugfs_root, uhci, &uhci_debug_operations);
#endif
uhci->frame = dma_alloc_coherent(uhci_dev(uhci),
uhci->frame, uhci->frame_dma_handle);
err_alloc_frame:
- debugfs_remove(uhci->dentry);
+ debugfs_remove(debugfs_lookup(hcd->self.bus_name, uhci_debugfs_root));
return retval;
}
* The full UHCI controller information:
*/
struct uhci_hcd {
-
- /* debugfs */
- struct dentry *dentry;
-
/* Grabbed from PCI */
unsigned long io_addr;
#include <linux/slab.h>
#include <asm/unaligned.h>
+#include <linux/bitfield.h>
#include "xhci.h"
#include "xhci-trace.h"
#define PORT_RWC_BITS (PORT_CSC | PORT_PEC | PORT_WRC | PORT_OCC | \
PORT_RC | PORT_PLC | PORT_PE)
-/* USB 3 BOS descriptor and a capability descriptors, combined.
- * Fields will be adjusted and added later in xhci_create_usb3_bos_desc()
- */
-static u8 usb_bos_descriptor [] = {
- USB_DT_BOS_SIZE, /* __u8 bLength, 5 bytes */
- USB_DT_BOS, /* __u8 bDescriptorType */
- 0x0F, 0x00, /* __le16 wTotalLength, 15 bytes */
- 0x1, /* __u8 bNumDeviceCaps */
- /* First device capability, SuperSpeed */
- USB_DT_USB_SS_CAP_SIZE, /* __u8 bLength, 10 bytes */
- USB_DT_DEVICE_CAPABILITY, /* Device Capability */
- USB_SS_CAP_TYPE, /* bDevCapabilityType, SUPERSPEED_USB */
- 0x00, /* bmAttributes, LTM off by default */
- USB_5GBPS_OPERATION, 0x00, /* wSpeedsSupported, 5Gbps only */
- 0x03, /* bFunctionalitySupport,
- USB 3.0 speed only */
- 0x00, /* bU1DevExitLat, set later. */
- 0x00, 0x00, /* __le16 bU2DevExitLat, set later. */
- /* Second device capability, SuperSpeedPlus */
- 0x1c, /* bLength 28, will be adjusted later */
- USB_DT_DEVICE_CAPABILITY, /* Device Capability */
- USB_SSP_CAP_TYPE, /* bDevCapabilityType SUPERSPEED_PLUS */
- 0x00, /* bReserved 0 */
- 0x23, 0x00, 0x00, 0x00, /* bmAttributes, SSAC=3 SSIC=1 */
- 0x01, 0x00, /* wFunctionalitySupport */
- 0x00, 0x00, /* wReserved 0 */
- /* Default Sublink Speed Attributes, overwrite if custom PSI exists */
- 0x34, 0x00, 0x05, 0x00, /* 5Gbps, symmetric, rx, ID = 4 */
- 0xb4, 0x00, 0x05, 0x00, /* 5Gbps, symmetric, tx, ID = 4 */
- 0x35, 0x40, 0x0a, 0x00, /* 10Gbps, SSP, symmetric, rx, ID = 5 */
- 0xb5, 0x40, 0x0a, 0x00, /* 10Gbps, SSP, symmetric, tx, ID = 5 */
+/* Default sublink speed attribute of each lane */
+static u32 ssp_cap_default_ssa[] = {
+ 0x00050034, /* USB 3.0 SS Gen1x1 id:4 symmetric rx 5Gbps */
+ 0x000500b4, /* USB 3.0 SS Gen1x1 id:4 symmetric tx 5Gbps */
+ 0x000a4035, /* USB 3.1 SSP Gen2x1 id:5 symmetric rx 10Gbps */
+ 0x000a40b5, /* USB 3.1 SSP Gen2x1 id:5 symmetric tx 10Gbps */
+ 0x00054036, /* USB 3.2 SSP Gen1x2 id:6 symmetric rx 5Gbps */
+ 0x000540b6, /* USB 3.2 SSP Gen1x2 id:6 symmetric tx 5Gbps */
+ 0x000a4037, /* USB 3.2 SSP Gen2x2 id:7 symmetric rx 10Gbps */
+ 0x000a40b7, /* USB 3.2 SSP Gen2x2 id:7 symmetric tx 10Gbps */
};
-static int xhci_create_usb3_bos_desc(struct xhci_hcd *xhci, char *buf,
- u16 wLength)
+static int xhci_create_usb3x_bos_desc(struct xhci_hcd *xhci, char *buf,
+ u16 wLength)
{
- struct xhci_port_cap *port_cap = NULL;
- int i, ssa_count;
- u32 temp;
- u16 desc_size, ssp_cap_size, ssa_size = 0;
- bool usb3_1 = false;
-
- desc_size = USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE;
- ssp_cap_size = sizeof(usb_bos_descriptor) - desc_size;
-
- /* does xhci support USB 3.1 Enhanced SuperSpeed */
+ struct usb_bos_descriptor *bos;
+ struct usb_ss_cap_descriptor *ss_cap;
+ struct usb_ssp_cap_descriptor *ssp_cap;
+ struct xhci_port_cap *port_cap = NULL;
+ u16 bcdUSB;
+ u32 reg;
+ u32 min_rate = 0;
+ u8 min_ssid;
+ u8 ssac;
+ u8 ssic;
+ int offset;
+ int i;
+
+ /* BOS descriptor */
+ bos = (struct usb_bos_descriptor *)buf;
+ bos->bLength = USB_DT_BOS_SIZE;
+ bos->bDescriptorType = USB_DT_BOS;
+ bos->wTotalLength = cpu_to_le16(USB_DT_BOS_SIZE +
+ USB_DT_USB_SS_CAP_SIZE);
+ bos->bNumDeviceCaps = 1;
+
+ /* Create the descriptor for port with the highest revision */
for (i = 0; i < xhci->num_port_caps; i++) {
- if (xhci->port_caps[i].maj_rev == 0x03 &&
- xhci->port_caps[i].min_rev >= 0x01) {
- usb3_1 = true;
+ u8 major = xhci->port_caps[i].maj_rev;
+ u8 minor = xhci->port_caps[i].min_rev;
+ u16 rev = (major << 8) | minor;
+
+ if (i == 0 || bcdUSB < rev) {
+ bcdUSB = rev;
port_cap = &xhci->port_caps[i];
- break;
}
}
- if (usb3_1) {
- /* does xhci provide a PSI table for SSA speed attributes? */
+ if (bcdUSB >= 0x0310) {
if (port_cap->psi_count) {
- /* two SSA entries for each unique PSI ID, RX and TX */
- ssa_count = port_cap->psi_uid_count * 2;
- ssa_size = ssa_count * sizeof(u32);
- ssp_cap_size -= 16; /* skip copying the default SSA */
+ u8 num_sym_ssa = 0;
+
+ for (i = 0; i < port_cap->psi_count; i++) {
+ if ((port_cap->psi[i] & PLT_MASK) == PLT_SYM)
+ num_sym_ssa++;
+ }
+
+ ssac = port_cap->psi_count + num_sym_ssa - 1;
+ ssic = port_cap->psi_uid_count - 1;
+ } else {
+ if (bcdUSB >= 0x0320)
+ ssac = 7;
+ else
+ ssac = 3;
+
+ ssic = (ssac + 1) / 2 - 1;
}
- desc_size += ssp_cap_size;
- }
- memcpy(buf, &usb_bos_descriptor, min(desc_size, wLength));
- if (usb3_1) {
- /* modify bos descriptor bNumDeviceCaps and wTotalLength */
- buf[4] += 1;
- put_unaligned_le16(desc_size + ssa_size, &buf[2]);
+ bos->bNumDeviceCaps++;
+ bos->wTotalLength = cpu_to_le16(USB_DT_BOS_SIZE +
+ USB_DT_USB_SS_CAP_SIZE +
+ USB_DT_USB_SSP_CAP_SIZE(ssac));
}
if (wLength < USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE)
return wLength;
- /* Indicate whether the host has LTM support. */
- temp = readl(&xhci->cap_regs->hcc_params);
- if (HCC_LTC(temp))
- buf[8] |= USB_LTM_SUPPORT;
+ /* SuperSpeed USB Device Capability */
+ ss_cap = (struct usb_ss_cap_descriptor *)&buf[USB_DT_BOS_SIZE];
+ ss_cap->bLength = USB_DT_USB_SS_CAP_SIZE;
+ ss_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
+ ss_cap->bDevCapabilityType = USB_SS_CAP_TYPE;
+ ss_cap->bmAttributes = 0; /* set later */
+ ss_cap->wSpeedSupported = cpu_to_le16(USB_5GBPS_OPERATION);
+ ss_cap->bFunctionalitySupport = USB_LOW_SPEED_OPERATION;
+ ss_cap->bU1devExitLat = 0; /* set later */
+ ss_cap->bU2DevExitLat = 0; /* set later */
+
+ reg = readl(&xhci->cap_regs->hcc_params);
+ if (HCC_LTC(reg))
+ ss_cap->bmAttributes |= USB_LTM_SUPPORT;
- /* Set the U1 and U2 exit latencies. */
if ((xhci->quirks & XHCI_LPM_SUPPORT)) {
- temp = readl(&xhci->cap_regs->hcs_params3);
- buf[12] = HCS_U1_LATENCY(temp);
- put_unaligned_le16(HCS_U2_LATENCY(temp), &buf[13]);
+ reg = readl(&xhci->cap_regs->hcs_params3);
+ ss_cap->bU1devExitLat = HCS_U1_LATENCY(reg);
+ ss_cap->bU2DevExitLat = cpu_to_le16(HCS_U2_LATENCY(reg));
}
- /* If PSI table exists, add the custom speed attributes from it */
- if (usb3_1 && port_cap->psi_count) {
- u32 ssp_cap_base, bm_attrib, psi, psi_mant, psi_exp;
- int offset;
+ if (wLength < le16_to_cpu(bos->wTotalLength))
+ return wLength;
- ssp_cap_base = USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE;
+ if (bcdUSB < 0x0310)
+ return le16_to_cpu(bos->wTotalLength);
+
+ ssp_cap = (struct usb_ssp_cap_descriptor *)&buf[USB_DT_BOS_SIZE +
+ USB_DT_USB_SS_CAP_SIZE];
+ ssp_cap->bLength = USB_DT_USB_SSP_CAP_SIZE(ssac);
+ ssp_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
+ ssp_cap->bDevCapabilityType = USB_SSP_CAP_TYPE;
+ ssp_cap->bReserved = 0;
+ ssp_cap->wReserved = 0;
+ ssp_cap->bmAttributes =
+ cpu_to_le32(FIELD_PREP(USB_SSP_SUBLINK_SPEED_ATTRIBS, ssac) |
+ FIELD_PREP(USB_SSP_SUBLINK_SPEED_IDS, ssic));
+
+ if (!port_cap->psi_count) {
+ for (i = 0; i < ssac + 1; i++)
+ ssp_cap->bmSublinkSpeedAttr[i] =
+ cpu_to_le32(ssp_cap_default_ssa[i]);
+
+ min_ssid = 4;
+ goto out;
+ }
- if (wLength < desc_size)
- return wLength;
- buf[ssp_cap_base] = ssp_cap_size + ssa_size;
+ offset = 0;
+ for (i = 0; i < port_cap->psi_count; i++) {
+ u32 psi;
+ u32 attr;
+ u8 ssid;
+ u8 lp;
+ u8 lse;
+ u8 psie;
+ u16 lane_mantissa;
+ u16 psim;
+ u16 plt;
+
+ psi = port_cap->psi[i];
+ ssid = XHCI_EXT_PORT_PSIV(psi);
+ lp = XHCI_EXT_PORT_LP(psi);
+ psie = XHCI_EXT_PORT_PSIE(psi);
+ psim = XHCI_EXT_PORT_PSIM(psi);
+ plt = psi & PLT_MASK;
+
+ lse = psie;
+ lane_mantissa = psim;
+
+ /* Shift to Gbps and set SSP Link Protocol if 10Gpbs */
+ for (; psie < USB_SSP_SUBLINK_SPEED_LSE_GBPS; psie++)
+ psim /= 1000;
+
+ if (!min_rate || psim < min_rate) {
+ min_ssid = ssid;
+ min_rate = psim;
+ }
- /* attribute count SSAC bits 4:0 and ID count SSIC bits 8:5 */
- bm_attrib = (ssa_count - 1) & 0x1f;
- bm_attrib |= (port_cap->psi_uid_count - 1) << 5;
- put_unaligned_le32(bm_attrib, &buf[ssp_cap_base + 4]);
+ /* Some host controllers don't set the link protocol for SSP */
+ if (psim >= 10)
+ lp = USB_SSP_SUBLINK_SPEED_LP_SSP;
- if (wLength < desc_size + ssa_size)
- return wLength;
/*
- * Create the Sublink Speed Attributes (SSA) array.
- * The xhci PSI field and USB 3.1 SSA fields are very similar,
- * but link type bits 7:6 differ for values 01b and 10b.
- * xhci has also only one PSI entry for a symmetric link when
- * USB 3.1 requires two SSA entries (RX and TX) for every link
+ * PSIM and PSIE represent the total speed of PSI. The BOS
+ * descriptor SSP sublink speed attribute lane mantissa
+ * describes the lane speed. E.g. PSIM and PSIE for gen2x2
+ * is 20Gbps, but the BOS descriptor lane speed mantissa is
+ * 10Gbps. Check and modify the mantissa value to match the
+ * lane speed.
*/
- offset = desc_size;
- for (i = 0; i < port_cap->psi_count; i++) {
- psi = port_cap->psi[i];
- psi &= ~USB_SSP_SUBLINK_SPEED_RSVD;
- psi_exp = XHCI_EXT_PORT_PSIE(psi);
- psi_mant = XHCI_EXT_PORT_PSIM(psi);
-
- /* Shift to Gbps and set SSP Link BIT(14) if 10Gpbs */
- for (; psi_exp < 3; psi_exp++)
- psi_mant /= 1000;
- if (psi_mant >= 10)
- psi |= BIT(14);
-
- if ((psi & PLT_MASK) == PLT_SYM) {
- /* Symmetric, create SSA RX and TX from one PSI entry */
- put_unaligned_le32(psi, &buf[offset]);
- psi |= 1 << 7; /* turn entry to TX */
- offset += 4;
- if (offset >= desc_size + ssa_size)
- return desc_size + ssa_size;
- } else if ((psi & PLT_MASK) == PLT_ASYM_RX) {
- /* Asymetric RX, flip bits 7:6 for SSA */
- psi ^= PLT_MASK;
+ if (bcdUSB == 0x0320 && plt == PLT_SYM) {
+ /*
+ * The PSI dword for gen1x2 and gen2x1 share the same
+ * values. But the lane speed for gen1x2 is 5Gbps while
+ * gen2x1 is 10Gbps. If the previous PSI dword SSID is
+ * 5 and the PSIE and PSIM match with SSID 6, let's
+ * assume that the controller follows the default speed
+ * id with SSID 6 for gen1x2.
+ */
+ if (ssid == 6 && psie == 3 && psim == 10 && i) {
+ u32 prev = port_cap->psi[i - 1];
+
+ if ((prev & PLT_MASK) == PLT_SYM &&
+ XHCI_EXT_PORT_PSIV(prev) == 5 &&
+ XHCI_EXT_PORT_PSIE(prev) == 3 &&
+ XHCI_EXT_PORT_PSIM(prev) == 10) {
+ lse = USB_SSP_SUBLINK_SPEED_LSE_GBPS;
+ lane_mantissa = 5;
+ }
+ }
+
+ if (psie == 3 && psim > 10) {
+ lse = USB_SSP_SUBLINK_SPEED_LSE_GBPS;
+ lane_mantissa = 10;
}
- put_unaligned_le32(psi, &buf[offset]);
- offset += 4;
- if (offset >= desc_size + ssa_size)
- return desc_size + ssa_size;
+ }
+
+ attr = (FIELD_PREP(USB_SSP_SUBLINK_SPEED_SSID, ssid) |
+ FIELD_PREP(USB_SSP_SUBLINK_SPEED_LP, lp) |
+ FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSE, lse) |
+ FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSM, lane_mantissa));
+
+ switch (plt) {
+ case PLT_SYM:
+ attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST,
+ USB_SSP_SUBLINK_SPEED_ST_SYM_RX);
+ ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr);
+
+ attr &= ~USB_SSP_SUBLINK_SPEED_ST;
+ attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST,
+ USB_SSP_SUBLINK_SPEED_ST_SYM_TX);
+ ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr);
+ break;
+ case PLT_ASYM_RX:
+ attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST,
+ USB_SSP_SUBLINK_SPEED_ST_ASYM_RX);
+ ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr);
+ break;
+ case PLT_ASYM_TX:
+ attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST,
+ USB_SSP_SUBLINK_SPEED_ST_ASYM_TX);
+ ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr);
+ break;
}
}
- /* ssa_size is 0 for other than usb 3.1 hosts */
- return desc_size + ssa_size;
+out:
+ ssp_cap->wFunctionalitySupport =
+ cpu_to_le16(FIELD_PREP(USB_SSP_MIN_SUBLINK_SPEED_ATTRIBUTE_ID,
+ min_ssid) |
+ FIELD_PREP(USB_SSP_MIN_RX_LANE_COUNT, 1) |
+ FIELD_PREP(USB_SSP_MIN_TX_LANE_COUNT, 1));
+
+ return le16_to_cpu(bos->wTotalLength);
}
static void xhci_common_hub_descriptor(struct xhci_hcd *xhci,
if (hcd->speed < HCD_USB3)
goto error;
- retval = xhci_create_usb3_bos_desc(xhci, buf, wLength);
+ retval = xhci_create_usb3x_bos_desc(xhci, buf, wLength);
spin_unlock_irqrestore(&xhci->lock, flags);
return retval;
case GetPortStatus:
return (struct xhci_ep_ctx *)
(ctx->bytes + (ep_index * CTX_SIZE(xhci->hcc_params)));
}
-
+EXPORT_SYMBOL_GPL(xhci_get_ep_ctx);
/***************** Streams structures manipulation *************************/
if (major_revision == 0x03) {
rhub = &xhci->usb3_rhub;
+ /*
+ * Some hosts incorrectly use sub-minor version for minor
+ * version (i.e. 0x02 instead of 0x20 for bcdUSB 0x320 and 0x01
+ * for bcdUSB 0x310). Since there is no USB release with sub
+ * minor version 0x301 to 0x309, we can assume that they are
+ * incorrect and fix it here.
+ */
+ if (minor_revision > 0x00 && minor_revision < 0x10)
+ minor_revision <<= 4;
} else if (major_revision <= 0x02) {
rhub = &xhci->usb2_rhub;
} else {
return;
rhub->ports = kcalloc_node(rhub->num_ports, sizeof(*rhub->ports),
flags, dev_to_node(dev));
+ if (!rhub->ports)
+ return;
+
for (i = 0; i < HCS_MAX_PORTS(xhci->hcs_params1); i++) {
if (xhci->hw_ports[i].rhub != rhub ||
xhci->hw_ports[i].hcd_portnum == DUPLICATE_ENTRY)
*/
#define TT_MICROFRAMES_MAX 9
+#define DBG_BUF_EN 64
+
+/* schedule error type */
+#define ESCH_SS_Y6 1001
+#define ESCH_SS_OVERLAP 1002
+#define ESCH_CS_OVERFLOW 1003
+#define ESCH_BW_OVERFLOW 1004
+#define ESCH_FIXME 1005
+
/* mtk scheduler bitmasks */
#define EP_BPKTS(p) ((p) & 0x7f)
#define EP_BCSCOUNT(p) (((p) & 0x7) << 8)
#define EP_BOFFSET(p) ((p) & 0x3fff)
#define EP_BREPEAT(p) (((p) & 0x7fff) << 16)
+static char *sch_error_string(int err_num)
+{
+ switch (err_num) {
+ case ESCH_SS_Y6:
+ return "Can't schedule Start-Split in Y6";
+ case ESCH_SS_OVERLAP:
+ return "Can't find a suitable Start-Split location";
+ case ESCH_CS_OVERFLOW:
+ return "The last Complete-Split is greater than 7";
+ case ESCH_BW_OVERFLOW:
+ return "Bandwidth exceeds the maximum limit";
+ case ESCH_FIXME:
+ return "FIXME, to be resolved";
+ default:
+ return "Unknown";
+ }
+}
+
static int is_fs_or_ls(enum usb_device_speed speed)
{
return speed == USB_SPEED_FULL || speed == USB_SPEED_LOW;
}
+static const char *
+decode_ep(struct usb_host_endpoint *ep, enum usb_device_speed speed)
+{
+ static char buf[DBG_BUF_EN];
+ struct usb_endpoint_descriptor *epd = &ep->desc;
+ unsigned int interval;
+ const char *unit;
+
+ interval = usb_decode_interval(epd, speed);
+ if (interval % 1000) {
+ unit = "us";
+ } else {
+ unit = "ms";
+ interval /= 1000;
+ }
+
+ snprintf(buf, DBG_BUF_EN, "%s ep%d%s %s, mpkt:%d, interval:%d/%d%s\n",
+ usb_speed_string(speed), usb_endpoint_num(epd),
+ usb_endpoint_dir_in(epd) ? "in" : "out",
+ usb_ep_type_string(usb_endpoint_type(epd)),
+ usb_endpoint_maxp(epd), epd->bInterval, interval, unit);
+
+ return buf;
+}
+
+static u32 get_bw_boundary(enum usb_device_speed speed)
+{
+ u32 boundary;
+
+ switch (speed) {
+ case USB_SPEED_SUPER_PLUS:
+ boundary = SSP_BW_BOUNDARY;
+ break;
+ case USB_SPEED_SUPER:
+ boundary = SS_BW_BOUNDARY;
+ break;
+ default:
+ boundary = HS_BW_BOUNDARY;
+ break;
+ }
+
+ return boundary;
+}
+
/*
-* get the index of bandwidth domains array which @ep belongs to.
+* get the bandwidth domain which @ep belongs to.
*
* the bandwidth domain array is saved to @sch_array of struct xhci_hcd_mtk,
* each HS root port is treated as a single bandwidth domain,
* so the bandwidth domain array is organized as follow for simplification:
* SSport0-OUT, SSport0-IN, ..., SSportX-OUT, SSportX-IN, HSport0, ..., HSportY
*/
-static int get_bw_index(struct xhci_hcd *xhci, struct usb_device *udev,
- struct usb_host_endpoint *ep)
+static struct mu3h_sch_bw_info *
+get_bw_info(struct xhci_hcd_mtk *mtk, struct usb_device *udev,
+ struct usb_host_endpoint *ep)
{
+ struct xhci_hcd *xhci = hcd_to_xhci(mtk->hcd);
struct xhci_virt_device *virt_dev;
int bw_index;
bw_index = virt_dev->real_port + xhci->usb3_rhub.num_ports - 1;
}
- return bw_index;
+ return &mtk->sch_array[bw_index];
}
static u32 get_esit(struct xhci_ep_ctx *ep_ctx)
{
struct usb_tt *utt = udev->tt;
struct mu3h_sch_tt *tt, **tt_index, **ptt;
- unsigned int port;
bool allocated_index = false;
if (!utt)
utt->hcpriv = tt_index;
allocated_index = true;
}
- port = udev->ttport - 1;
- ptt = &tt_index[port];
+ ptt = &tt_index[udev->ttport - 1];
} else {
- port = 0;
ptt = (struct mu3h_sch_tt **) &utt->hcpriv;
}
return ERR_PTR(-ENOMEM);
}
INIT_LIST_HEAD(&tt->ep_list);
- tt->usb_tt = utt;
- tt->tt_port = port;
*ptt = tt;
}
sch_ep->sch_tt = tt;
sch_ep->ep = ep;
+ sch_ep->speed = udev->speed;
INIT_LIST_HEAD(&sch_ep->endpoint);
INIT_LIST_HEAD(&sch_ep->tt_endpoint);
return sch_ep;
}
-static void setup_sch_info(struct usb_device *udev,
- struct xhci_ep_ctx *ep_ctx, struct mu3h_sch_ep_info *sch_ep)
+static void setup_sch_info(struct xhci_ep_ctx *ep_ctx,
+ struct mu3h_sch_ep_info *sch_ep)
{
u32 ep_type;
u32 maxpkt;
sch_ep->burst_mode = 0;
sch_ep->repeat = 0;
- if (udev->speed == USB_SPEED_HIGH) {
+ if (sch_ep->speed == USB_SPEED_HIGH) {
sch_ep->cs_count = 0;
/*
sch_ep->pkts = max_burst + 1;
sch_ep->bw_cost_per_microframe = maxpkt * sch_ep->pkts;
bwb_table[0] = sch_ep->bw_cost_per_microframe;
- } else if (udev->speed >= USB_SPEED_SUPER) {
+ } else if (sch_ep->speed >= USB_SPEED_SUPER) {
/* usb3_r1 spec section4.4.7 & 4.4.8 */
sch_ep->cs_count = 0;
sch_ep->burst_mode = 1;
}
if (ep_type == ISOC_IN_EP || ep_type == ISOC_OUT_EP) {
- u32 remainder;
if (sch_ep->esit == 1)
sch_ep->pkts = esit_pkts;
sch_ep->repeat = !!(sch_ep->num_budget_microframes > 1);
sch_ep->bw_cost_per_microframe = maxpkt * sch_ep->pkts;
- remainder = sch_ep->bw_cost_per_microframe;
- remainder *= sch_ep->num_budget_microframes;
- remainder -= (maxpkt * esit_pkts);
for (i = 0; i < sch_ep->num_budget_microframes - 1; i++)
bwb_table[i] = sch_ep->bw_cost_per_microframe;
/* last one <= bw_cost_per_microframe */
- bwb_table[i] = remainder;
+ bwb_table[i] = maxpkt * esit_pkts
+ - i * sch_ep->bw_cost_per_microframe;
}
- } else if (is_fs_or_ls(udev->speed)) {
+ } else if (is_fs_or_ls(sch_ep->speed)) {
sch_ep->pkts = 1; /* at most one packet for each microframe */
/*
sch_ep->bw_budget_table[j];
}
}
- sch_ep->allocated = used;
}
-static int check_sch_tt(struct usb_device *udev,
- struct mu3h_sch_ep_info *sch_ep, u32 offset)
+static int check_fs_bus_bw(struct mu3h_sch_ep_info *sch_ep, int offset)
+{
+ struct mu3h_sch_tt *tt = sch_ep->sch_tt;
+ u32 num_esit, tmp;
+ int base;
+ int i, j;
+
+ num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit;
+ for (i = 0; i < num_esit; i++) {
+ base = offset + i * sch_ep->esit;
+
+ /*
+ * Compared with hs bus, no matter what ep type,
+ * the hub will always delay one uframe to send data
+ */
+ for (j = 0; j < sch_ep->cs_count; j++) {
+ tmp = tt->fs_bus_bw[base + j] + sch_ep->bw_cost_per_microframe;
+ if (tmp > FS_PAYLOAD_MAX)
+ return -ESCH_BW_OVERFLOW;
+ }
+ }
+
+ return 0;
+}
+
+static int check_sch_tt(struct mu3h_sch_ep_info *sch_ep, u32 offset)
{
struct mu3h_sch_tt *tt = sch_ep->sch_tt;
u32 extra_cs_count;
- u32 fs_budget_start;
u32 start_ss, last_ss;
u32 start_cs, last_cs;
int i;
start_ss = offset % 8;
- fs_budget_start = (start_ss + 1) % 8;
if (sch_ep->ep_type == ISOC_OUT_EP) {
last_ss = start_ss + sch_ep->cs_count - 1;
* must never schedule Start-Split in Y6
*/
if (!(start_ss == 7 || last_ss < 6))
- return -ERANGE;
+ return -ESCH_SS_Y6;
for (i = 0; i < sch_ep->cs_count; i++)
- if (test_bit(offset + i, tt->split_bit_map))
- return -ERANGE;
+ if (test_bit(offset + i, tt->ss_bit_map))
+ return -ESCH_SS_OVERLAP;
} else {
u32 cs_count = DIV_ROUND_UP(sch_ep->maxpkt, FS_PAYLOAD_MAX);
* must never schedule Start-Split in Y6
*/
if (start_ss == 6)
- return -ERANGE;
+ return -ESCH_SS_Y6;
/* one uframe for ss + one uframe for idle */
start_cs = (start_ss + 2) % 8;
last_cs = start_cs + cs_count - 1;
if (last_cs > 7)
- return -ERANGE;
+ return -ESCH_CS_OVERFLOW;
if (sch_ep->ep_type == ISOC_IN_EP)
extra_cs_count = (last_cs == 7) ? 1 : 2;
else /* ep_type : INTR IN / INTR OUT */
- extra_cs_count = (fs_budget_start == 6) ? 1 : 2;
+ extra_cs_count = 1;
cs_count += extra_cs_count;
if (cs_count > 7)
cs_count = 7; /* HW limit */
- for (i = 0; i < cs_count + 2; i++) {
- if (test_bit(offset + i, tt->split_bit_map))
- return -ERANGE;
- }
+ if (test_bit(offset, tt->ss_bit_map))
+ return -ESCH_SS_OVERLAP;
sch_ep->cs_count = cs_count;
/* one for ss, the other for idle */
sch_ep->num_budget_microframes = sch_ep->esit;
}
- return 0;
+ return check_fs_bus_bw(sch_ep, offset);
}
-static void update_sch_tt(struct usb_device *udev,
- struct mu3h_sch_ep_info *sch_ep)
+static void update_sch_tt(struct mu3h_sch_ep_info *sch_ep, bool used)
{
struct mu3h_sch_tt *tt = sch_ep->sch_tt;
u32 base, num_esit;
+ int bw_updated;
+ int bits;
int i, j;
num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit;
+ bits = (sch_ep->ep_type == ISOC_OUT_EP) ? sch_ep->cs_count : 1;
+
+ if (used)
+ bw_updated = sch_ep->bw_cost_per_microframe;
+ else
+ bw_updated = -sch_ep->bw_cost_per_microframe;
+
for (i = 0; i < num_esit; i++) {
base = sch_ep->offset + i * sch_ep->esit;
- for (j = 0; j < sch_ep->num_budget_microframes; j++)
- set_bit(base + j, tt->split_bit_map);
+
+ for (j = 0; j < bits; j++) {
+ if (used)
+ set_bit(base + j, tt->ss_bit_map);
+ else
+ clear_bit(base + j, tt->ss_bit_map);
+ }
+
+ for (j = 0; j < sch_ep->cs_count; j++)
+ tt->fs_bus_bw[base + j] += bw_updated;
}
- list_add_tail(&sch_ep->tt_endpoint, &tt->ep_list);
+ if (used)
+ list_add_tail(&sch_ep->tt_endpoint, &tt->ep_list);
+ else
+ list_del(&sch_ep->tt_endpoint);
}
-static int check_sch_bw(struct usb_device *udev,
- struct mu3h_sch_bw_info *sch_bw, struct mu3h_sch_ep_info *sch_ep)
+static int load_ep_bw(struct mu3h_sch_bw_info *sch_bw,
+ struct mu3h_sch_ep_info *sch_ep, bool loaded)
+{
+ if (sch_ep->sch_tt)
+ update_sch_tt(sch_ep, loaded);
+
+ /* update bus bandwidth info */
+ update_bus_bw(sch_bw, sch_ep, loaded);
+ sch_ep->allocated = loaded;
+
+ return 0;
+}
+
+static u32 get_esit_boundary(struct mu3h_sch_ep_info *sch_ep)
+{
+ u32 boundary = sch_ep->esit;
+
+ if (sch_ep->sch_tt) { /* LS/FS with TT */
+ /* tune for CS */
+ if (sch_ep->ep_type != ISOC_OUT_EP)
+ boundary++;
+ else if (boundary > 1) /* normally esit >= 8 for FS/LS */
+ boundary--;
+ }
+
+ return boundary;
+}
+
+static int check_sch_bw(struct mu3h_sch_bw_info *sch_bw,
+ struct mu3h_sch_ep_info *sch_ep)
{
u32 offset;
- u32 esit;
u32 min_bw;
u32 min_index;
u32 worst_bw;
u32 bw_boundary;
+ u32 esit_boundary;
u32 min_num_budget;
u32 min_cs_count;
- bool tt_offset_ok = false;
- int ret;
-
- esit = sch_ep->esit;
+ int ret = 0;
/*
* Search through all possible schedule microframes.
min_index = 0;
min_cs_count = sch_ep->cs_count;
min_num_budget = sch_ep->num_budget_microframes;
- for (offset = 0; offset < esit; offset++) {
- if (is_fs_or_ls(udev->speed)) {
- ret = check_sch_tt(udev, sch_ep, offset);
+ esit_boundary = get_esit_boundary(sch_ep);
+ for (offset = 0; offset < sch_ep->esit; offset++) {
+ if (sch_ep->sch_tt) {
+ ret = check_sch_tt(sch_ep, offset);
if (ret)
continue;
- else
- tt_offset_ok = true;
}
- if ((offset + sch_ep->num_budget_microframes) > sch_ep->esit)
+ if ((offset + sch_ep->num_budget_microframes) > esit_boundary)
break;
worst_bw = get_max_bw(sch_bw, sch_ep, offset);
break;
}
- if (udev->speed == USB_SPEED_SUPER_PLUS)
- bw_boundary = SSP_BW_BOUNDARY;
- else if (udev->speed == USB_SPEED_SUPER)
- bw_boundary = SS_BW_BOUNDARY;
- else
- bw_boundary = HS_BW_BOUNDARY;
-
+ bw_boundary = get_bw_boundary(sch_ep->speed);
/* check bandwidth */
if (min_bw > bw_boundary)
- return -ERANGE;
+ return ret ? ret : -ESCH_BW_OVERFLOW;
sch_ep->offset = min_index;
sch_ep->cs_count = min_cs_count;
sch_ep->num_budget_microframes = min_num_budget;
- if (is_fs_or_ls(udev->speed)) {
- /* all offset for tt is not ok*/
- if (!tt_offset_ok)
- return -ERANGE;
-
- update_sch_tt(udev, sch_ep);
- }
-
- /* update bus bandwidth info */
- update_bus_bw(sch_bw, sch_ep, 1);
-
- return 0;
+ return load_ep_bw(sch_bw, sch_ep, true);
}
static void destroy_sch_ep(struct usb_device *udev,
{
/* only release ep bw check passed by check_sch_bw() */
if (sch_ep->allocated)
- update_bus_bw(sch_bw, sch_ep, 0);
+ load_ep_bw(sch_bw, sch_ep, false);
- list_del(&sch_ep->endpoint);
-
- if (sch_ep->sch_tt) {
- list_del(&sch_ep->tt_endpoint);
+ if (sch_ep->sch_tt)
drop_tt(udev);
- }
+
+ list_del(&sch_ep->endpoint);
kfree(sch_ep);
}
return 0;
}
-EXPORT_SYMBOL_GPL(xhci_mtk_sch_init);
void xhci_mtk_sch_exit(struct xhci_hcd_mtk *mtk)
{
kfree(mtk->sch_array);
}
-EXPORT_SYMBOL_GPL(xhci_mtk_sch_exit);
-int xhci_mtk_add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
- struct usb_host_endpoint *ep)
+static int add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep)
{
struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd);
- struct xhci_hcd *xhci;
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
struct xhci_ep_ctx *ep_ctx;
- struct xhci_slot_ctx *slot_ctx;
struct xhci_virt_device *virt_dev;
struct mu3h_sch_ep_info *sch_ep;
unsigned int ep_index;
- xhci = hcd_to_xhci(hcd);
virt_dev = xhci->devs[udev->slot_id];
ep_index = xhci_get_endpoint_index(&ep->desc);
- slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->in_ctx);
ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index);
- xhci_dbg(xhci, "%s() type:%d, speed:%d, mpkt:%d, dir:%d, ep:%p\n",
- __func__, usb_endpoint_type(&ep->desc), udev->speed,
- usb_endpoint_maxp(&ep->desc),
- usb_endpoint_dir_in(&ep->desc), ep);
+ xhci_dbg(xhci, "%s %s\n", __func__, decode_ep(ep, udev->speed));
- if (!need_bw_sch(ep, udev->speed, slot_ctx->tt_info & TT_SLOT)) {
+ if (!need_bw_sch(ep, udev->speed, !!virt_dev->tt_info)) {
/*
* set @bpkts to 1 if it is LS or FS periodic endpoint, and its
* device does not connected through an external HS hub
*/
if (usb_endpoint_xfer_int(&ep->desc)
|| usb_endpoint_xfer_isoc(&ep->desc))
- ep_ctx->reserved[0] |= cpu_to_le32(EP_BPKTS(1));
+ ep_ctx->reserved[0] = cpu_to_le32(EP_BPKTS(1));
return 0;
}
if (IS_ERR_OR_NULL(sch_ep))
return -ENOMEM;
- setup_sch_info(udev, ep_ctx, sch_ep);
+ setup_sch_info(ep_ctx, sch_ep);
list_add_tail(&sch_ep->endpoint, &mtk->bw_ep_chk_list);
return 0;
}
-EXPORT_SYMBOL_GPL(xhci_mtk_add_ep_quirk);
-void xhci_mtk_drop_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
- struct usb_host_endpoint *ep)
+static void drop_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep)
{
struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd);
- struct xhci_hcd *xhci;
- struct xhci_slot_ctx *slot_ctx;
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
struct xhci_virt_device *virt_dev;
- struct mu3h_sch_bw_info *sch_array;
struct mu3h_sch_bw_info *sch_bw;
struct mu3h_sch_ep_info *sch_ep, *tmp;
- int bw_index;
- xhci = hcd_to_xhci(hcd);
virt_dev = xhci->devs[udev->slot_id];
- slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->in_ctx);
- sch_array = mtk->sch_array;
- xhci_dbg(xhci, "%s() type:%d, speed:%d, mpks:%d, dir:%d, ep:%p\n",
- __func__, usb_endpoint_type(&ep->desc), udev->speed,
- usb_endpoint_maxp(&ep->desc),
- usb_endpoint_dir_in(&ep->desc), ep);
+ xhci_dbg(xhci, "%s %s\n", __func__, decode_ep(ep, udev->speed));
- if (!need_bw_sch(ep, udev->speed, slot_ctx->tt_info & TT_SLOT))
+ if (!need_bw_sch(ep, udev->speed, !!virt_dev->tt_info))
return;
- bw_index = get_bw_index(xhci, udev, ep);
- sch_bw = &sch_array[bw_index];
+ sch_bw = get_bw_info(mtk, udev, ep);
list_for_each_entry_safe(sch_ep, tmp, &sch_bw->bw_ep_list, endpoint) {
if (sch_ep->ep == ep) {
}
}
}
-EXPORT_SYMBOL_GPL(xhci_mtk_drop_ep_quirk);
int xhci_mtk_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev)
{
struct xhci_virt_device *virt_dev = xhci->devs[udev->slot_id];
struct mu3h_sch_bw_info *sch_bw;
struct mu3h_sch_ep_info *sch_ep, *tmp;
- int bw_index, ret;
+ int ret;
xhci_dbg(xhci, "%s() udev %s\n", __func__, dev_name(&udev->dev));
list_for_each_entry(sch_ep, &mtk->bw_ep_chk_list, endpoint) {
- bw_index = get_bw_index(xhci, udev, sch_ep->ep);
- sch_bw = &mtk->sch_array[bw_index];
+ sch_bw = get_bw_info(mtk, udev, sch_ep->ep);
- ret = check_sch_bw(udev, sch_bw, sch_ep);
+ ret = check_sch_bw(sch_bw, sch_ep);
if (ret) {
- xhci_err(xhci, "Not enough bandwidth!\n");
+ xhci_err(xhci, "Not enough bandwidth! (%s)\n",
+ sch_error_string(-ret));
return -ENOSPC;
}
}
struct usb_host_endpoint *ep = sch_ep->ep;
unsigned int ep_index = xhci_get_endpoint_index(&ep->desc);
- bw_index = get_bw_index(xhci, udev, ep);
- sch_bw = &mtk->sch_array[bw_index];
-
+ sch_bw = get_bw_info(mtk, udev, ep);
list_move_tail(&sch_ep->endpoint, &sch_bw->bw_ep_list);
ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index);
- ep_ctx->reserved[0] |= cpu_to_le32(EP_BPKTS(sch_ep->pkts)
+ ep_ctx->reserved[0] = cpu_to_le32(EP_BPKTS(sch_ep->pkts)
| EP_BCSCOUNT(sch_ep->cs_count)
| EP_BBM(sch_ep->burst_mode));
- ep_ctx->reserved[1] |= cpu_to_le32(EP_BOFFSET(sch_ep->offset)
+ ep_ctx->reserved[1] = cpu_to_le32(EP_BOFFSET(sch_ep->offset)
| EP_BREPEAT(sch_ep->repeat));
xhci_dbg(xhci, " PKTS:%x, CSCOUNT:%x, BM:%x, OFFSET:%x, REPEAT:%x\n",
return xhci_check_bandwidth(hcd, udev);
}
-EXPORT_SYMBOL_GPL(xhci_mtk_check_bandwidth);
void xhci_mtk_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev)
{
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
struct mu3h_sch_bw_info *sch_bw;
struct mu3h_sch_ep_info *sch_ep, *tmp;
- int bw_index;
xhci_dbg(xhci, "%s() udev %s\n", __func__, dev_name(&udev->dev));
list_for_each_entry_safe(sch_ep, tmp, &mtk->bw_ep_chk_list, endpoint) {
- bw_index = get_bw_index(xhci, udev, sch_ep->ep);
- sch_bw = &mtk->sch_array[bw_index];
+ sch_bw = get_bw_info(mtk, udev, sch_ep->ep);
destroy_sch_ep(udev, sch_bw, sch_ep);
}
xhci_reset_bandwidth(hcd, udev);
}
-EXPORT_SYMBOL_GPL(xhci_mtk_reset_bandwidth);
+
+int xhci_mtk_add_ep(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep)
+{
+ int ret;
+
+ ret = xhci_add_endpoint(hcd, udev, ep);
+ if (ret)
+ return ret;
+
+ if (ep->hcpriv)
+ ret = add_ep_quirk(hcd, udev, ep);
+
+ return ret;
+}
+
+int xhci_mtk_drop_ep(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep)
+{
+ int ret;
+
+ ret = xhci_drop_endpoint(hcd, udev, ep);
+ if (ret)
+ return ret;
+
+ if (ep->hcpriv)
+ drop_ep_quirk(hcd, udev, ep);
+
+ return 0;
+}
* Chunfeng Yun <chunfeng.yun@mediatek.com>
*/
-#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
+#include <linux/pm_wakeirq.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#define CTRL_U2_FORCE_PLL_STB BIT(28)
/* usb remote wakeup registers in syscon */
+
/* mt8173 etc */
#define PERI_WK_CTRL1 0x4
#define WC1_IS_C(x) (((x) & 0xf) << 26) /* cycle debounce */
#define WC1_IS_EN BIT(25)
#define WC1_IS_P BIT(6) /* polarity for ip sleep */
+/* mt8183 */
+#define PERI_WK_CTRL0 0x0
+#define WC0_IS_C(x) ((u32)(((x) & 0xf) << 28)) /* cycle debounce */
+#define WC0_IS_P BIT(12) /* polarity */
+#define WC0_IS_EN BIT(6)
+
+/* mt8192 */
+#define WC0_SSUSB0_CDEN BIT(6)
+#define WC0_IS_SPM_EN BIT(1)
+
/* mt2712 etc */
#define PERI_SSUSB_SPM_CTRL 0x0
#define SSC_IP_SLEEP_EN BIT(4)
enum ssusb_uwk_vers {
SSUSB_UWK_V1 = 1,
SSUSB_UWK_V2,
+ SSUSB_UWK_V1_1 = 101, /* specific revision 1.01 */
+ SSUSB_UWK_V1_2, /* specific revision 1.2 */
};
static int xhci_mtk_host_enable(struct xhci_hcd_mtk *mtk)
return xhci_mtk_host_enable(mtk);
}
-static int xhci_mtk_clks_get(struct xhci_hcd_mtk *mtk)
-{
- struct device *dev = mtk->dev;
-
- mtk->sys_clk = devm_clk_get(dev, "sys_ck");
- if (IS_ERR(mtk->sys_clk)) {
- dev_err(dev, "fail to get sys_ck\n");
- return PTR_ERR(mtk->sys_clk);
- }
-
- mtk->xhci_clk = devm_clk_get_optional(dev, "xhci_ck");
- if (IS_ERR(mtk->xhci_clk))
- return PTR_ERR(mtk->xhci_clk);
-
- mtk->ref_clk = devm_clk_get_optional(dev, "ref_ck");
- if (IS_ERR(mtk->ref_clk))
- return PTR_ERR(mtk->ref_clk);
-
- mtk->mcu_clk = devm_clk_get_optional(dev, "mcu_ck");
- if (IS_ERR(mtk->mcu_clk))
- return PTR_ERR(mtk->mcu_clk);
-
- mtk->dma_clk = devm_clk_get_optional(dev, "dma_ck");
- return PTR_ERR_OR_ZERO(mtk->dma_clk);
-}
-
-static int xhci_mtk_clks_enable(struct xhci_hcd_mtk *mtk)
-{
- int ret;
-
- ret = clk_prepare_enable(mtk->ref_clk);
- if (ret) {
- dev_err(mtk->dev, "failed to enable ref_clk\n");
- goto ref_clk_err;
- }
-
- ret = clk_prepare_enable(mtk->sys_clk);
- if (ret) {
- dev_err(mtk->dev, "failed to enable sys_clk\n");
- goto sys_clk_err;
- }
-
- ret = clk_prepare_enable(mtk->xhci_clk);
- if (ret) {
- dev_err(mtk->dev, "failed to enable xhci_clk\n");
- goto xhci_clk_err;
- }
-
- ret = clk_prepare_enable(mtk->mcu_clk);
- if (ret) {
- dev_err(mtk->dev, "failed to enable mcu_clk\n");
- goto mcu_clk_err;
- }
-
- ret = clk_prepare_enable(mtk->dma_clk);
- if (ret) {
- dev_err(mtk->dev, "failed to enable dma_clk\n");
- goto dma_clk_err;
- }
-
- return 0;
-
-dma_clk_err:
- clk_disable_unprepare(mtk->mcu_clk);
-mcu_clk_err:
- clk_disable_unprepare(mtk->xhci_clk);
-xhci_clk_err:
- clk_disable_unprepare(mtk->sys_clk);
-sys_clk_err:
- clk_disable_unprepare(mtk->ref_clk);
-ref_clk_err:
- return ret;
-}
-
-static void xhci_mtk_clks_disable(struct xhci_hcd_mtk *mtk)
-{
- clk_disable_unprepare(mtk->dma_clk);
- clk_disable_unprepare(mtk->mcu_clk);
- clk_disable_unprepare(mtk->xhci_clk);
- clk_disable_unprepare(mtk->sys_clk);
- clk_disable_unprepare(mtk->ref_clk);
-}
-
/* only clocks can be turn off for ip-sleep wakeup mode */
static void usb_wakeup_ip_sleep_set(struct xhci_hcd_mtk *mtk, bool enable)
{
msk = WC1_IS_EN | WC1_IS_C(0xf) | WC1_IS_P;
val = enable ? (WC1_IS_EN | WC1_IS_C(0x8)) : 0;
break;
+ case SSUSB_UWK_V1_1:
+ reg = mtk->uwk_reg_base + PERI_WK_CTRL0;
+ msk = WC0_IS_EN | WC0_IS_C(0xf) | WC0_IS_P;
+ val = enable ? (WC0_IS_EN | WC0_IS_C(0x8)) : 0;
+ break;
+ case SSUSB_UWK_V1_2:
+ reg = mtk->uwk_reg_base + PERI_WK_CTRL0;
+ msk = WC0_SSUSB0_CDEN | WC0_IS_SPM_EN;
+ val = enable ? msk : 0;
+ break;
case SSUSB_UWK_V2:
reg = mtk->uwk_reg_base + PERI_SSUSB_SPM_CTRL;
msk = SSC_IP_SLEEP_EN | SSC_SPM_INT_EN;
mtk->uwk_reg_base, mtk->uwk_vers);
return PTR_ERR_OR_ZERO(mtk->uwk);
-
}
static void usb_wakeup_set(struct xhci_hcd_mtk *mtk, bool enable)
usb_wakeup_ip_sleep_set(mtk, enable);
}
-static int xhci_mtk_setup(struct usb_hcd *hcd);
-static const struct xhci_driver_overrides xhci_mtk_overrides __initconst = {
- .reset = xhci_mtk_setup,
- .check_bandwidth = xhci_mtk_check_bandwidth,
- .reset_bandwidth = xhci_mtk_reset_bandwidth,
-};
+static int xhci_mtk_clks_get(struct xhci_hcd_mtk *mtk)
+{
+ struct clk_bulk_data *clks = mtk->clks;
-static struct hc_driver __read_mostly xhci_mtk_hc_driver;
+ clks[0].id = "sys_ck";
+ clks[1].id = "xhci_ck";
+ clks[2].id = "ref_ck";
+ clks[3].id = "mcu_ck";
+ clks[4].id = "dma_ck";
+
+ return devm_clk_bulk_get_optional(mtk->dev, BULK_CLKS_NUM, clks);
+}
static int xhci_mtk_ldos_enable(struct xhci_hcd_mtk *mtk)
{
xhci->quirks |= XHCI_SPURIOUS_SUCCESS;
if (mtk->lpm_support)
xhci->quirks |= XHCI_LPM_SUPPORT;
+ if (mtk->u2_lpm_disable)
+ xhci->quirks |= XHCI_HW_LPM_DISABLE;
/*
* MTK xHCI 0.96: PSA is 1 by default even if doesn't support stream,
return ret;
}
+static const struct xhci_driver_overrides xhci_mtk_overrides __initconst = {
+ .reset = xhci_mtk_setup,
+ .add_endpoint = xhci_mtk_add_ep,
+ .drop_endpoint = xhci_mtk_drop_ep,
+ .check_bandwidth = xhci_mtk_check_bandwidth,
+ .reset_bandwidth = xhci_mtk_reset_bandwidth,
+};
+
+static struct hc_driver __read_mostly xhci_mtk_hc_driver;
+
static int xhci_mtk_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *res;
struct usb_hcd *hcd;
int ret = -ENODEV;
+ int wakeup_irq;
int irq;
if (usb_disabled())
if (ret)
return ret;
+ irq = platform_get_irq_byname_optional(pdev, "host");
+ if (irq < 0) {
+ if (irq == -EPROBE_DEFER)
+ return irq;
+
+ /* for backward compatibility */
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+ }
+
+ wakeup_irq = platform_get_irq_byname_optional(pdev, "wakeup");
+ if (wakeup_irq == -EPROBE_DEFER)
+ return wakeup_irq;
+
mtk->lpm_support = of_property_read_bool(node, "usb3-lpm-capable");
+ mtk->u2_lpm_disable = of_property_read_bool(node, "usb2-lpm-disable");
/* optional property, ignore the error if it does not exist */
of_property_read_u32(node, "mediatek,u3p-dis-msk",
&mtk->u3p_dis_msk);
return ret;
}
+ pm_runtime_set_active(dev);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_set_autosuspend_delay(dev, 4000);
pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
- device_enable_async_suspend(dev);
ret = xhci_mtk_ldos_enable(mtk);
if (ret)
goto disable_pm;
- ret = xhci_mtk_clks_enable(mtk);
+ ret = clk_bulk_prepare_enable(BULK_CLKS_NUM, mtk->clks);
if (ret)
goto disable_ldos;
- irq = platform_get_irq(pdev, 0);
- if (irq < 0) {
- ret = irq;
- goto disable_clk;
- }
-
hcd = usb_create_hcd(driver, dev, dev_name(dev));
if (!hcd) {
ret = -ENOMEM;
if (ret)
goto dealloc_usb2_hcd;
+ if (wakeup_irq > 0) {
+ ret = dev_pm_set_dedicated_wake_irq(dev, wakeup_irq);
+ if (ret) {
+ dev_err(dev, "set wakeup irq %d failed\n", wakeup_irq);
+ goto dealloc_usb3_hcd;
+ }
+ dev_info(dev, "wakeup irq %d\n", wakeup_irq);
+ }
+
+ device_enable_async_suspend(dev);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+ pm_runtime_forbid(dev);
+
return 0;
+dealloc_usb3_hcd:
+ usb_remove_hcd(xhci->shared_hcd);
+ xhci->shared_hcd = NULL;
+
dealloc_usb2_hcd:
usb_remove_hcd(hcd);
usb_put_hcd(hcd);
disable_clk:
- xhci_mtk_clks_disable(mtk);
+ clk_bulk_disable_unprepare(BULK_CLKS_NUM, mtk->clks);
disable_ldos:
xhci_mtk_ldos_disable(mtk);
disable_pm:
- pm_runtime_put_sync(dev);
+ pm_runtime_put_sync_autosuspend(dev);
pm_runtime_disable(dev);
return ret;
}
-static int xhci_mtk_remove(struct platform_device *dev)
+static int xhci_mtk_remove(struct platform_device *pdev)
{
- struct xhci_hcd_mtk *mtk = platform_get_drvdata(dev);
+ struct xhci_hcd_mtk *mtk = platform_get_drvdata(pdev);
struct usb_hcd *hcd = mtk->hcd;
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
struct usb_hcd *shared_hcd = xhci->shared_hcd;
+ struct device *dev = &pdev->dev;
- pm_runtime_put_noidle(&dev->dev);
- pm_runtime_disable(&dev->dev);
+ pm_runtime_get_sync(dev);
+ xhci->xhc_state |= XHCI_STATE_REMOVING;
+ dev_pm_clear_wake_irq(dev);
+ device_init_wakeup(dev, false);
usb_remove_hcd(shared_hcd);
xhci->shared_hcd = NULL;
- device_init_wakeup(&dev->dev, false);
-
usb_remove_hcd(hcd);
usb_put_hcd(shared_hcd);
usb_put_hcd(hcd);
xhci_mtk_sch_exit(mtk);
- xhci_mtk_clks_disable(mtk);
+ clk_bulk_disable_unprepare(BULK_CLKS_NUM, mtk->clks);
xhci_mtk_ldos_disable(mtk);
+ pm_runtime_disable(dev);
+ pm_runtime_put_noidle(dev);
+ pm_runtime_set_suspended(dev);
+
return 0;
}
-/*
- * if ip sleep fails, and all clocks are disabled, access register will hang
- * AHB bus, so stop polling roothubs to avoid regs access on bus suspend.
- * and no need to check whether ip sleep failed or not; this will cause SPM
- * to wake up system immediately after system suspend complete if ip sleep
- * fails, it is what we wanted.
- */
static int __maybe_unused xhci_mtk_suspend(struct device *dev)
{
struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev);
struct usb_hcd *hcd = mtk->hcd;
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ int ret;
xhci_dbg(xhci, "%s: stop port polling\n", __func__);
clear_bit(HCD_FLAG_POLL_RH, &hcd->flags);
clear_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags);
del_timer_sync(&xhci->shared_hcd->rh_timer);
- xhci_mtk_host_disable(mtk);
- xhci_mtk_clks_disable(mtk);
+ ret = xhci_mtk_host_disable(mtk);
+ if (ret)
+ goto restart_poll_rh;
+
+ clk_bulk_disable_unprepare(BULK_CLKS_NUM, mtk->clks);
usb_wakeup_set(mtk, true);
return 0;
+
+restart_poll_rh:
+ xhci_dbg(xhci, "%s: restart port polling\n", __func__);
+ set_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags);
+ usb_hcd_poll_rh_status(xhci->shared_hcd);
+ set_bit(HCD_FLAG_POLL_RH, &hcd->flags);
+ usb_hcd_poll_rh_status(hcd);
+ return ret;
}
static int __maybe_unused xhci_mtk_resume(struct device *dev)
struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev);
struct usb_hcd *hcd = mtk->hcd;
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ int ret;
usb_wakeup_set(mtk, false);
- xhci_mtk_clks_enable(mtk);
- xhci_mtk_host_enable(mtk);
+ ret = clk_bulk_prepare_enable(BULK_CLKS_NUM, mtk->clks);
+ if (ret)
+ goto enable_wakeup;
+
+ ret = xhci_mtk_host_enable(mtk);
+ if (ret)
+ goto disable_clks;
xhci_dbg(xhci, "%s: restart port polling\n", __func__);
set_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags);
set_bit(HCD_FLAG_POLL_RH, &hcd->flags);
usb_hcd_poll_rh_status(hcd);
return 0;
+
+disable_clks:
+ clk_bulk_disable_unprepare(BULK_CLKS_NUM, mtk->clks);
+enable_wakeup:
+ usb_wakeup_set(mtk, true);
+ return ret;
+}
+
+static int __maybe_unused xhci_mtk_runtime_suspend(struct device *dev)
+{
+ struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev);
+ struct xhci_hcd *xhci = hcd_to_xhci(mtk->hcd);
+ int ret = 0;
+
+ if (xhci->xhc_state)
+ return -ESHUTDOWN;
+
+ if (device_may_wakeup(dev))
+ ret = xhci_mtk_suspend(dev);
+
+ /* -EBUSY: let PM automatically reschedule another autosuspend */
+ return ret ? -EBUSY : 0;
+}
+
+static int __maybe_unused xhci_mtk_runtime_resume(struct device *dev)
+{
+ struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev);
+ struct xhci_hcd *xhci = hcd_to_xhci(mtk->hcd);
+ int ret = 0;
+
+ if (xhci->xhc_state)
+ return -ESHUTDOWN;
+
+ if (device_may_wakeup(dev))
+ ret = xhci_mtk_resume(dev);
+
+ return ret;
}
static const struct dev_pm_ops xhci_mtk_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(xhci_mtk_suspend, xhci_mtk_resume)
+ SET_RUNTIME_PM_OPS(xhci_mtk_runtime_suspend,
+ xhci_mtk_runtime_resume, NULL)
};
-#define DEV_PM_OPS IS_ENABLED(CONFIG_PM) ? &xhci_mtk_pm_ops : NULL
-#ifdef CONFIG_OF
+#define DEV_PM_OPS (IS_ENABLED(CONFIG_PM) ? &xhci_mtk_pm_ops : NULL)
+
static const struct of_device_id mtk_xhci_of_match[] = {
{ .compatible = "mediatek,mt8173-xhci"},
{ .compatible = "mediatek,mtk-xhci"},
{ },
};
MODULE_DEVICE_TABLE(of, mtk_xhci_of_match);
-#endif
static struct platform_driver mtk_xhci_driver = {
.probe = xhci_mtk_probe,
.driver = {
.name = "xhci-mtk",
.pm = DEV_PM_OPS,
- .of_match_table = of_match_ptr(mtk_xhci_of_match),
+ .of_match_table = mtk_xhci_of_match,
},
};
-MODULE_ALIAS("platform:xhci-mtk");
static int __init xhci_mtk_init(void)
{
#ifndef _XHCI_MTK_H_
#define _XHCI_MTK_H_
+#include <linux/clk.h>
+
#include "xhci.h"
+#define BULK_CLKS_NUM 5
+
/**
* To simplify scheduler algorithm, set a upper limit for ESIT,
* if a synchromous ep's ESIT is larger than @XHCI_MTK_MAX_ESIT,
#define XHCI_MTK_MAX_ESIT 64
/**
- * @split_bit_map: used to avoid split microframes overlay
+ * @ss_bit_map: used to avoid start split microframes overlay
+ * @fs_bus_bw: array to keep track of bandwidth already used for FS
* @ep_list: Endpoints using this TT
- * @usb_tt: usb TT related
- * @tt_port: TT port number
*/
struct mu3h_sch_tt {
- DECLARE_BITMAP(split_bit_map, XHCI_MTK_MAX_ESIT);
+ DECLARE_BITMAP(ss_bit_map, XHCI_MTK_MAX_ESIT);
+ u32 fs_bus_bw[XHCI_MTK_MAX_ESIT];
struct list_head ep_list;
- struct usb_tt *usb_tt;
- int tt_port;
};
/**
struct mu3h_sch_tt *sch_tt;
u32 ep_type;
u32 maxpkt;
- void *ep;
+ struct usb_host_endpoint *ep;
+ enum usb_device_speed speed;
bool allocated;
/*
* mtk xHCI scheduling information put into reserved DWs
int u3p_dis_msk;
struct regulator *vusb33;
struct regulator *vbus;
- struct clk *sys_clk; /* sys and mac clock */
- struct clk *xhci_clk;
- struct clk *ref_clk;
- struct clk *mcu_clk;
- struct clk *dma_clk;
- struct regmap *pericfg;
- struct phy **phys;
- int num_phys;
+ struct clk_bulk_data clks[BULK_CLKS_NUM];
bool lpm_support;
+ bool u2_lpm_disable;
/* usb remote wakeup */
bool uwk_en;
struct regmap *uwk;
return dev_get_drvdata(hcd->self.controller);
}
-#if IS_ENABLED(CONFIG_USB_XHCI_MTK)
int xhci_mtk_sch_init(struct xhci_hcd_mtk *mtk);
void xhci_mtk_sch_exit(struct xhci_hcd_mtk *mtk);
-int xhci_mtk_add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
- struct usb_host_endpoint *ep);
-void xhci_mtk_drop_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
- struct usb_host_endpoint *ep);
+int xhci_mtk_add_ep(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep);
+int xhci_mtk_drop_ep(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep);
int xhci_mtk_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev);
void xhci_mtk_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev);
-#else
-static inline int xhci_mtk_add_ep_quirk(struct usb_hcd *hcd,
- struct usb_device *udev, struct usb_host_endpoint *ep)
-{
- return 0;
-}
-
-static inline void xhci_mtk_drop_ep_quirk(struct usb_hcd *hcd,
- struct usb_device *udev, struct usb_host_endpoint *ep)
-{
-}
-
-static inline int xhci_mtk_check_bandwidth(struct usb_hcd *hcd,
- struct usb_device *udev)
-{
- return 0;
-}
-
-static inline void xhci_mtk_reset_bandwidth(struct usb_hcd *hcd,
- struct usb_device *udev)
-{
-}
-#endif
-
#endif /* _XHCI_MTK_H_ */
#include <linux/dma-mapping.h>
#include "xhci.h"
#include "xhci-trace.h"
-#include "xhci-mtk.h"
static int queue_command(struct xhci_hcd *xhci, struct xhci_command *cmd,
u32 field1, u32 field2,
return 0;
}
-static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td,
- struct xhci_transfer_event *event, struct xhci_virt_ep *ep)
+static int finish_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
+ struct xhci_ring *ep_ring, struct xhci_td *td,
+ u32 trb_comp_code)
{
struct xhci_ep_ctx *ep_ctx;
- struct xhci_ring *ep_ring;
- u32 trb_comp_code;
- ep_ring = xhci_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer));
ep_ctx = xhci_get_ep_ctx(xhci, ep->vdev->out_ctx, ep->ep_index);
- trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len));
switch (trb_comp_code) {
case COMP_STOPPED_LENGTH_INVALID:
/*
* Process control tds, update urb status and actual_length.
*/
-static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td,
- union xhci_trb *ep_trb, struct xhci_transfer_event *event,
- struct xhci_virt_ep *ep)
+static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
+ struct xhci_ring *ep_ring, struct xhci_td *td,
+ union xhci_trb *ep_trb, struct xhci_transfer_event *event)
{
struct xhci_ep_ctx *ep_ctx;
u32 trb_comp_code;
td->urb->actual_length = requested;
finish_td:
- return finish_td(xhci, td, event, ep);
+ return finish_td(xhci, ep, ep_ring, td, trb_comp_code);
}
/*
* Process isochronous tds, update urb packet status and actual_length.
*/
-static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td,
- union xhci_trb *ep_trb, struct xhci_transfer_event *event,
- struct xhci_virt_ep *ep)
+static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
+ struct xhci_ring *ep_ring, struct xhci_td *td,
+ union xhci_trb *ep_trb, struct xhci_transfer_event *event)
{
struct urb_priv *urb_priv;
int idx;
td->urb->actual_length += frame->actual_length;
- return finish_td(xhci, td, event, ep);
+ return finish_td(xhci, ep, ep_ring, td, trb_comp_code);
}
static int skip_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td,
/*
* Process bulk and interrupt tds, update urb status and actual_length.
*/
-static int process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_td *td,
- union xhci_trb *ep_trb, struct xhci_transfer_event *event,
- struct xhci_virt_ep *ep)
+static int process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
+ struct xhci_ring *ep_ring, struct xhci_td *td,
+ union xhci_trb *ep_trb, struct xhci_transfer_event *event)
{
struct xhci_slot_ctx *slot_ctx;
- struct xhci_ring *ep_ring;
u32 trb_comp_code;
u32 remaining, requested, ep_trb_len;
slot_ctx = xhci_get_slot_ctx(xhci, ep->vdev->out_ctx);
- ep_ring = xhci_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer));
trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len));
remaining = EVENT_TRB_LEN(le32_to_cpu(event->transfer_len));
ep_trb_len = TRB_LEN(le32_to_cpu(ep_trb->generic.field[2]));
remaining);
td->urb->actual_length = 0;
}
- return finish_td(xhci, td, event, ep);
+
+ return finish_td(xhci, ep, ep_ring, td, trb_comp_code);
}
/*
/* update the urb's actual_length and give back to the core */
if (usb_endpoint_xfer_control(&td->urb->ep->desc))
- process_ctrl_td(xhci, td, ep_trb, event, ep);
+ process_ctrl_td(xhci, ep, ep_ring, td, ep_trb, event);
else if (usb_endpoint_xfer_isoc(&td->urb->ep->desc))
- process_isoc_td(xhci, td, ep_trb, event, ep);
+ process_isoc_td(xhci, ep, ep_ring, td, ep_trb, event);
else
- process_bulk_intr_td(xhci, td, ep_trb, event, ep);
+ process_bulk_intr_td(xhci, ep, ep_ring, td, ep_trb, event);
cleanup:
handling_skipped_tds = ep->skip &&
trb_comp_code != COMP_MISSED_SERVICE_ERROR &&
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
union xhci_trb *event_ring_deq;
irqreturn_t ret = IRQ_NONE;
- unsigned long flags;
u64 temp_64;
u32 status;
int event_loop = 0;
- spin_lock_irqsave(&xhci->lock, flags);
+ spin_lock(&xhci->lock);
/* Check if the xHC generated the interrupt, or the irq is shared */
status = readl(&xhci->op_regs->status);
if (status == ~(u32)0) {
ret = IRQ_HANDLED;
out:
- spin_unlock_irqrestore(&xhci->lock, flags);
+ spin_unlock(&xhci->lock);
return ret;
}
#include "xhci.h"
#include "xhci-trace.h"
-#include "xhci-mtk.h"
#include "xhci-debugfs.h"
#include "xhci-dbgcap.h"
struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
int err, i;
u64 val;
+ u32 intrs;
/*
* Some Renesas controllers get into a weird state if they are
if (upper_32_bits(val))
xhci_write_64(xhci, 0, &xhci->op_regs->cmd_ring);
- for (i = 0; i < HCS_MAX_INTRS(xhci->hcs_params1); i++) {
+ intrs = min_t(u32, HCS_MAX_INTRS(xhci->hcs_params1),
+ ARRAY_SIZE(xhci->run_regs->ir_set));
+
+ for (i = 0; i < intrs; i++) {
struct xhci_intr_reg __iomem *ir;
ir = &xhci->run_regs->ir_set[i];
(usb_endpoint_dir_in(desc) ? 1 : 0) - 1;
return index;
}
+EXPORT_SYMBOL_GPL(xhci_get_endpoint_index);
/* The reverse operation to xhci_get_endpoint_index. Calculate the USB endpoint
* address from the XHCI endpoint index.
* disabled, so there's no need for mutual exclusion to protect
* the xhci->devs[slot_id] structure.
*/
-static int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev,
- struct usb_host_endpoint *ep)
+int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep)
{
struct xhci_hcd *xhci;
struct xhci_container_ctx *in_ctx, *out_ctx;
xhci_endpoint_zero(xhci, xhci->devs[udev->slot_id], ep);
- if (xhci->quirks & XHCI_MTK_HOST)
- xhci_mtk_drop_ep_quirk(hcd, udev, ep);
-
xhci_dbg(xhci, "drop ep 0x%x, slot id %d, new drop flags = %#x, new add flags = %#x\n",
(unsigned int) ep->desc.bEndpointAddress,
udev->slot_id,
(unsigned int) new_add_flags);
return 0;
}
+EXPORT_SYMBOL_GPL(xhci_drop_endpoint);
/* Add an endpoint to a new possible bandwidth configuration for this device.
* Only one call to this function is allowed per endpoint before
* configuration or alt setting is installed in the device, so there's no need
* for mutual exclusion to protect the xhci->devs[slot_id] structure.
*/
-static int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev,
- struct usb_host_endpoint *ep)
+int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep)
{
struct xhci_hcd *xhci;
struct xhci_container_ctx *in_ctx;
return -ENOMEM;
}
- if (xhci->quirks & XHCI_MTK_HOST) {
- ret = xhci_mtk_add_ep_quirk(hcd, udev, ep);
- if (ret < 0) {
- xhci_ring_free(xhci, virt_dev->eps[ep_index].new_ring);
- virt_dev->eps[ep_index].new_ring = NULL;
- return ret;
- }
- }
-
ctrl_ctx->add_flags |= cpu_to_le32(added_ctxs);
new_add_flags = le32_to_cpu(ctrl_ctx->add_flags);
(unsigned int) new_add_flags);
return 0;
}
+EXPORT_SYMBOL_GPL(xhci_add_endpoint);
static void xhci_zero_in_ctx(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev)
{
return ret;
}
+EXPORT_SYMBOL_GPL(xhci_check_bandwidth);
void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev)
{
}
xhci_zero_in_ctx(xhci, virt_dev);
}
+EXPORT_SYMBOL_GPL(xhci_reset_bandwidth);
static void xhci_setup_input_ctx_for_config_ep(struct xhci_hcd *xhci,
struct xhci_container_ctx *in_ctx,
/* config ep command clears toggle if add and drop ep flags are set */
ctrl_ctx = xhci_get_input_control_ctx(cfg_cmd->in_ctx);
+ if (!ctrl_ctx) {
+ spin_unlock_irqrestore(&xhci->lock, flags);
+ xhci_free_command(xhci, cfg_cmd);
+ xhci_warn(xhci, "%s: Could not get input context, bad type.\n",
+ __func__);
+ goto cleanup;
+ }
+
xhci_setup_input_ctx_for_config_ep(xhci, cfg_cmd->in_ctx, vdev->out_ctx,
ctrl_ctx, ep_flag, ep_flag);
xhci_endpoint_copy(xhci, cfg_cmd->in_ctx, vdev->out_ctx, ep_index);
hcd->self.root_hub->speed = USB_SPEED_SUPER_PLUS;
hcd->self.root_hub->rx_lanes = 2;
hcd->self.root_hub->tx_lanes = 2;
+ hcd->self.root_hub->ssp_rate = USB_SSP_GEN_2x2;
break;
case 1:
hcd->speed = HCD_USB31;
hcd->self.root_hub->speed = USB_SPEED_SUPER_PLUS;
+ hcd->self.root_hub->ssp_rate = USB_SSP_GEN_2x1;
break;
}
xhci_info(xhci, "Host supports USB 3.%x %sSuperSpeed\n",
drv->reset = over->reset;
if (over->start)
drv->start = over->start;
+ if (over->add_endpoint)
+ drv->add_endpoint = over->add_endpoint;
+ if (over->drop_endpoint)
+ drv->drop_endpoint = over->drop_endpoint;
if (over->check_bandwidth)
drv->check_bandwidth = over->check_bandwidth;
if (over->reset_bandwidth)
size_t extra_priv_size;
int (*reset)(struct usb_hcd *hcd);
int (*start)(struct usb_hcd *hcd);
+ int (*add_endpoint)(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep);
+ int (*drop_endpoint)(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep);
int (*check_bandwidth)(struct usb_hcd *, struct usb_device *);
void (*reset_bandwidth)(struct usb_hcd *, struct usb_device *);
};
void xhci_shutdown(struct usb_hcd *hcd);
void xhci_init_driver(struct hc_driver *drv,
const struct xhci_driver_overrides *over);
+int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep);
+int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep);
int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev);
void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev);
int xhci_disable_slot(struct xhci_hcd *xhci, u32 slot_id);
dev->interrupt_in_buffer, urb->actual_length);
dev->read_buffer_length += urb->actual_length;
- dev_dbg(&dev->udev->dev,"%s reading %d\n", __func__,
+ dev_dbg(&dev->udev->dev, "%s reading %d\n", __func__,
urb->actual_length);
} else {
- dev_dbg(&dev->udev->dev,"%s : read_buffer overflow\n",
+ dev_dbg(&dev->udev->dev, "%s : read_buffer overflow\n",
__func__);
}
}
retval = -EIO;
goto error;
}
- dev_dbg(&interface->dev,"serial_number=%s", dev->serial_number);
+ dev_dbg(&interface->dev, "serial_number=%s", dev->serial_number);
/* we can register the device now, as it is ready */
usb_set_intfdata(interface, dev);
int ret = -EINVAL;
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_device *hub_udev = dev->parent;
- struct usb_device_descriptor *buf;
+ struct usb_device_descriptor buf;
u8 portnum = dev->portnum;
u16 test_pid = le16_to_cpu(dev->descriptor.idProduct);
switch (test_pid) {
case TEST_SE0_NAK_PID:
- ret = usb_control_msg(hub_udev, usb_sndctrlpipe(hub_udev, 0),
- USB_REQ_SET_FEATURE, USB_RT_PORT,
- USB_PORT_FEAT_TEST,
- (USB_TEST_SE0_NAK << 8) | portnum,
- NULL, 0, 1000);
+ ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
+ USB_RT_PORT, USB_PORT_FEAT_TEST,
+ (USB_TEST_SE0_NAK << 8) | portnum,
+ NULL, 0, 1000, GFP_KERNEL);
break;
case TEST_J_PID:
- ret = usb_control_msg(hub_udev, usb_sndctrlpipe(hub_udev, 0),
- USB_REQ_SET_FEATURE, USB_RT_PORT,
- USB_PORT_FEAT_TEST,
- (USB_TEST_J << 8) | portnum,
- NULL, 0, 1000);
+ ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
+ USB_RT_PORT, USB_PORT_FEAT_TEST,
+ (USB_TEST_J << 8) | portnum, NULL, 0,
+ 1000, GFP_KERNEL);
break;
case TEST_K_PID:
- ret = usb_control_msg(hub_udev, usb_sndctrlpipe(hub_udev, 0),
- USB_REQ_SET_FEATURE, USB_RT_PORT,
- USB_PORT_FEAT_TEST,
- (USB_TEST_K << 8) | portnum,
- NULL, 0, 1000);
+ ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
+ USB_RT_PORT, USB_PORT_FEAT_TEST,
+ (USB_TEST_K << 8) | portnum, NULL, 0,
+ 1000, GFP_KERNEL);
break;
case TEST_PACKET_PID:
- ret = usb_control_msg(hub_udev, usb_sndctrlpipe(hub_udev, 0),
- USB_REQ_SET_FEATURE, USB_RT_PORT,
- USB_PORT_FEAT_TEST,
- (USB_TEST_PACKET << 8) | portnum,
- NULL, 0, 1000);
+ ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
+ USB_RT_PORT, USB_PORT_FEAT_TEST,
+ (USB_TEST_PACKET << 8) | portnum,
+ NULL, 0, 1000, GFP_KERNEL);
break;
case TEST_HS_HOST_PORT_SUSPEND_RESUME:
/* Test: wait for 15secs -> suspend -> 15secs delay -> resume */
msleep(15 * 1000);
- ret = usb_control_msg(hub_udev, usb_sndctrlpipe(hub_udev, 0),
- USB_REQ_SET_FEATURE, USB_RT_PORT,
- USB_PORT_FEAT_SUSPEND, portnum,
- NULL, 0, 1000);
+ ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
+ USB_RT_PORT, USB_PORT_FEAT_SUSPEND,
+ portnum, NULL, 0, 1000, GFP_KERNEL);
if (ret < 0)
break;
msleep(15 * 1000);
- ret = usb_control_msg(hub_udev, usb_sndctrlpipe(hub_udev, 0),
- USB_REQ_CLEAR_FEATURE, USB_RT_PORT,
- USB_PORT_FEAT_SUSPEND, portnum,
- NULL, 0, 1000);
+ ret = usb_control_msg_send(hub_udev, 0, USB_REQ_CLEAR_FEATURE,
+ USB_RT_PORT, USB_PORT_FEAT_SUSPEND,
+ portnum, NULL, 0, 1000, GFP_KERNEL);
break;
case TEST_SINGLE_STEP_GET_DEV_DESC:
/* Test: wait for 15secs -> GetDescriptor request */
msleep(15 * 1000);
- buf = kmalloc(USB_DT_DEVICE_SIZE, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
- ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
- USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
- USB_DT_DEVICE << 8, 0,
- buf, USB_DT_DEVICE_SIZE,
- USB_CTRL_GET_TIMEOUT);
- kfree(buf);
+ ret = usb_control_msg_recv(dev, 0, USB_REQ_GET_DESCRIPTOR,
+ USB_DIR_IN, USB_DT_DEVICE << 8, 0,
+ &buf, USB_DT_DEVICE_SIZE,
+ USB_CTRL_GET_TIMEOUT, GFP_KERNEL);
break;
case TEST_SINGLE_STEP_SET_FEATURE:
/*
break;
}
- ret = usb_control_msg(hub_udev, usb_sndctrlpipe(hub_udev, 0),
- USB_REQ_SET_FEATURE, USB_RT_PORT,
- USB_PORT_FEAT_TEST,
- (6 << 8) | portnum,
- NULL, 0, 60 * 1000);
+ ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
+ USB_RT_PORT, USB_PORT_FEAT_TEST,
+ (6 << 8) | portnum, NULL, 0,
+ 60 * 1000, GFP_KERNEL);
break;
default:
__func__, test_pid);
}
- return (ret < 0) ? ret : 0;
+ return ret;
}
static void ehset_disconnect(struct usb_interface *intf)
static int ezusb_writememory(struct usb_device *dev, int address,
unsigned char *data, int length, __u8 request)
{
- int result;
- unsigned char *transfer_buffer;
-
if (!dev)
return -ENODEV;
- transfer_buffer = kmemdup(data, length, GFP_KERNEL);
- if (!transfer_buffer) {
- dev_err(&dev->dev, "%s - kmalloc(%d) failed.\n",
- __func__, length);
- return -ENOMEM;
- }
- result = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), request,
+ return usb_control_msg_send(dev, 0, request,
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
- address, 0, transfer_buffer, length, 3000);
-
- kfree(transfer_buffer);
- return result;
+ address, 0, data, length, 3000, GFP_KERNEL);
}
static int ezusb_set_reset(struct usb_device *dev, unsigned short cpucs_reg,
if (mydev->shadow_power != 1)
return;
- rc = usb_control_msg(mydev->udev,
- usb_sndctrlpipe(mydev->udev, 0),
- 0x12,
- 0x48,
- (80 * 0x100) + 10, /* (power mode) */
- (0x00 * 0x100) + (mydev->powered ? 1 : 0),
- NULL,
- 0,
- 2000);
+ rc = usb_control_msg_send(mydev->udev, 0, 0x12, 0x48,
+ (80 * 0x100) + 10, /* (power mode) */
+ (0x00 * 0x100) + (mydev->powered ? 1 : 0),
+ NULL, 0, 2000, GFP_KERNEL);
if (rc < 0)
dev_dbg(&mydev->udev->dev, "power retval = %d\n", rc);
if(mydev->shadow_power != 1)
return;
- rc = usb_control_msg(mydev->udev,
- usb_sndctrlpipe(mydev->udev, 0),
- 0x12,
- 0x48,
- (82 * 0x100) + 10, /* (set mode) */
- (mydev->mode_msb * 0x100) + mydev->mode_lsb,
- NULL,
- 0,
- 2000);
+ rc = usb_control_msg_send(mydev->udev, 0, 0x12, 0x48,
+ (82 * 0x100) + 10, /* (set mode) */
+ (mydev->mode_msb * 0x100) + mydev->mode_lsb,
+ NULL, 0, 2000, GFP_NOIO);
if (rc < 0)
dev_dbg(&mydev->udev->dev, "mode retval = %d\n", rc);
{
int rc;
int i;
- unsigned char *buffer;
+ unsigned char buffer[MAXLEN] = {0};
u8 decimals = 0;
if(mydev->shadow_power != 1)
return;
- buffer = kzalloc(MAXLEN, mf);
- if (!buffer)
- return;
-
/* The device is right to left, where as you write left to right */
for (i = 0; i < mydev->textlength; i++)
buffer[i] = mydev->text[mydev->textlength-1-i];
- rc = usb_control_msg(mydev->udev,
- usb_sndctrlpipe(mydev->udev, 0),
- 0x12,
- 0x48,
- (85 * 0x100) + 10, /* (write text) */
- (0 * 0x100) + mydev->textmode, /* mode */
- buffer,
- mydev->textlength,
- 2000);
+ rc = usb_control_msg_send(mydev->udev, 0, 0x12, 0x48,
+ (85 * 0x100) + 10, /* (write text) */
+ (0 * 0x100) + mydev->textmode, /* mode */
+ &buffer, mydev->textlength, 2000, mf);
if (rc < 0)
dev_dbg(&mydev->udev->dev, "write retval = %d\n", rc);
- kfree(buffer);
-
/* The device is right to left, where as you write left to right */
for (i = 0; i < sizeof(mydev->decimals); i++)
decimals |= mydev->decimals[i] << i;
- rc = usb_control_msg(mydev->udev,
- usb_sndctrlpipe(mydev->udev, 0),
- 0x12,
- 0x48,
- (86 * 0x100) + 10, /* (set decimal) */
- (0 * 0x100) + decimals, /* decimals */
- NULL,
- 0,
- 2000);
+ rc = usb_control_msg_send(mydev->udev, 0, 0x12, 0x48,
+ (86 * 0x100) + 10, /* (set decimal) */
+ (0 * 0x100) + decimals, /* decimals */
+ NULL, 0, 2000, mf);
if (rc < 0)
dev_dbg(&mydev->udev->dev, "decimal retval = %d\n", rc);
#define WC1_IS_EN BIT(25)
#define WC1_IS_P BIT(6) /* polarity for ip sleep */
+/* mt8183 */
+#define PERI_WK_CTRL0 0x0
+#define WC0_IS_C(x) ((u32)(((x) & 0xf) << 28)) /* cycle debounce */
+#define WC0_IS_P BIT(12) /* polarity */
+#define WC0_IS_EN BIT(6)
+
+/* mt8192 */
+#define WC0_SSUSB0_CDEN BIT(6)
+#define WC0_IS_SPM_EN BIT(1)
+
/* mt2712 etc */
#define PERI_SSUSB_SPM_CTRL 0x0
#define SSC_IP_SLEEP_EN BIT(4)
enum ssusb_uwk_vers {
SSUSB_UWK_V1 = 1,
SSUSB_UWK_V2,
+ SSUSB_UWK_V1_1 = 101, /* specific revision 1.01 */
+ SSUSB_UWK_V1_2, /* specific revision 1.02 */
};
/*
msk = WC1_IS_EN | WC1_IS_C(0xf) | WC1_IS_P;
val = enable ? (WC1_IS_EN | WC1_IS_C(0x8)) : 0;
break;
+ case SSUSB_UWK_V1_1:
+ reg = ssusb->uwk_reg_base + PERI_WK_CTRL0;
+ msk = WC0_IS_EN | WC0_IS_C(0xf) | WC0_IS_P;
+ val = enable ? (WC0_IS_EN | WC0_IS_C(0x8)) : 0;
+ break;
+ case SSUSB_UWK_V1_2:
+ reg = ssusb->uwk_reg_base + PERI_WK_CTRL0;
+ msk = WC0_SSUSB0_CDEN | WC0_IS_SPM_EN;
+ val = enable ? msk : 0;
+ break;
case SSUSB_UWK_V2:
reg = ssusb->uwk_reg_base + PERI_SSUSB_SPM_CTRL;
msk = SSC_IP_SLEEP_EN | SSC_SPM_INT_EN;
void __iomem *ibase = ssusb->ippc_base;
int num_u3p = ssusb->u3_ports;
int num_u2p = ssusb->u2_ports;
- int u3_ports_disabed;
+ int u3_ports_disabled;
u32 check_clk;
u32 value;
int i;
mtu3_clrbits(ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN);
/* power on and enable u3 ports except skipped ones */
- u3_ports_disabed = 0;
+ u3_ports_disabled = 0;
for (i = 0; i < num_u3p; i++) {
if ((0x1 << i) & ssusb->u3p_dis_msk) {
- u3_ports_disabed++;
+ u3_ports_disabled++;
continue;
}
}
check_clk = SSUSB_XHCI_RST_B_STS;
- if (num_u3p > u3_ports_disabed)
+ if (num_u3p > u3_ports_disabled)
check_clk = SSUSB_U3_MAC_RST_B_STS;
return ssusb_check_clocks(ssusb, check_clk);
#define DEV_PM_OPS (IS_ENABLED(CONFIG_PM) ? &mtu3_pm_ops : NULL)
-#ifdef CONFIG_OF
-
static const struct of_device_id mtu3_of_match[] = {
{.compatible = "mediatek,mt8173-mtu3",},
{.compatible = "mediatek,mtu3",},
{},
};
-
MODULE_DEVICE_TABLE(of, mtu3_of_match);
-#endif
-
static struct platform_driver mtu3_driver = {
.probe = mtu3_probe,
.remove = mtu3_remove,
.driver = {
.name = MTU3_DRIVER_NAME,
.pm = DEV_PM_OPS,
- .of_match_table = of_match_ptr(mtu3_of_match),
+ .of_match_table = mtu3_of_match,
},
};
module_platform_driver(mtu3_driver);
struct musb *musb = container_of(data, struct musb, irq_work.work);
int error;
- error = pm_runtime_get_sync(musb->controller);
+ error = pm_runtime_resume_and_get(musb->controller);
if (error < 0) {
dev_err(musb->controller, "Could not enable: %i\n", error);
unsigned dyn_fifo:1; /* dynamic FIFO supported? */
unsigned bulk_split:1;
-#define can_bulk_split(musb,type) \
+#define can_bulk_split(musb, type) \
(((type) == USB_ENDPOINT_XFER_BULK) && (musb)->bulk_split)
unsigned bulk_combine:1;
-#define can_bulk_combine(musb,type) \
+#define can_bulk_combine(musb, type) \
(((type) == USB_ENDPOINT_XFER_BULK) && (musb)->bulk_combine)
/* is_suspended means USB B_PERIPHERAL suspend */
usbhs_lock(priv, flags);
pkt = __usbhsf_pkt_get(pipe);
- if (!pkt)
+ if (!pkt) {
+ ret = -EINVAL;
goto __usbhs_pkt_handler_end;
+ }
switch (type) {
case USBHSF_PKT_PREPARE:
return NULL;
dev = class_find_device_by_fwnode(role_class, fwnode);
+ if (dev)
+ WARN_ON(!try_module_get(dev->parent->driver->owner));
return dev ? to_role_switch(dev) : NULL;
}
return result;
}
-static int ark3116_get_serial_info(struct tty_struct *tty,
- struct serial_struct *ss)
-{
- struct usb_serial_port *port = tty->driver_data;
-
- ss->type = PORT_16654;
- ss->line = port->minor;
- ss->port = port->port_number;
- ss->baud_base = 460800;
- return 0;
-}
-
static int ark3116_tiocmget(struct tty_struct *tty)
{
struct usb_serial_port *port = tty->driver_data;
.port_probe = ark3116_port_probe,
.port_remove = ark3116_port_remove,
.set_termios = ark3116_set_termios,
- .get_serial = ark3116_get_serial_info,
.tiocmget = ark3116_tiocmget,
.tiocmset = ark3116_tiocmset,
.tiocmiwait = usb_serial_generic_tiocmiwait,
}
#ifdef CONFIG_GPIOLIB
-static int cp210x_gpio_request(struct gpio_chip *gc, unsigned int offset)
-{
- struct usb_serial *serial = gpiochip_get_data(gc);
- struct cp210x_serial_private *priv = usb_get_serial_data(serial);
-
- if (priv->gpio_altfunc & BIT(offset))
- return -ENODEV;
-
- return 0;
-}
-
static int cp210x_gpio_get(struct gpio_chip *gc, unsigned int gpio)
{
struct usb_serial *serial = gpiochip_get_data(gc);
return -ENOTSUPP;
}
+static int cp210x_gpio_init_valid_mask(struct gpio_chip *gc,
+ unsigned long *valid_mask, unsigned int ngpios)
+{
+ struct usb_serial *serial = gpiochip_get_data(gc);
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+ struct device *dev = &serial->interface->dev;
+ unsigned long altfunc_mask = priv->gpio_altfunc;
+
+ bitmap_complement(valid_mask, &altfunc_mask, ngpios);
+
+ if (bitmap_empty(valid_mask, ngpios))
+ dev_dbg(dev, "no pin configured for GPIO\n");
+ else
+ dev_dbg(dev, "GPIO.%*pbl configured for GPIO\n", ngpios,
+ valid_mask);
+ return 0;
+}
+
/*
* This function is for configuring GPIO using shared pins, where other signals
* are made unavailable by configuring the use of GPIO. This is believed to be
return result;
priv->gc.label = "cp210x";
- priv->gc.request = cp210x_gpio_request;
priv->gc.get_direction = cp210x_gpio_direction_get;
priv->gc.direction_input = cp210x_gpio_direction_input;
priv->gc.direction_output = cp210x_gpio_direction_output;
priv->gc.get = cp210x_gpio_get;
priv->gc.set = cp210x_gpio_set;
priv->gc.set_config = cp210x_gpio_set_config;
+ priv->gc.init_valid_mask = cp210x_gpio_init_valid_mask;
priv->gc.owner = THIS_MODULE;
priv->gc.parent = &serial->interface->dev;
priv->gc.base = -1;
return 0;
}
-static int f81232_get_serial_info(struct tty_struct *tty,
- struct serial_struct *ss)
+static void f81232_get_serial(struct tty_struct *tty, struct serial_struct *ss)
{
struct usb_serial_port *port = tty->driver_data;
struct f81232_private *priv = usb_get_serial_port_data(port);
- ss->type = PORT_16550A;
- ss->line = port->minor;
- ss->port = port->port_number;
ss->baud_base = priv->baud_base;
- return 0;
}
static void f81232_interrupt_work(struct work_struct *work)
usb_set_serial_port_data(port, priv);
- port->port.drain_delay = 256;
priv->port = port;
return 0;
.close = f81232_close,
.dtr_rts = f81232_dtr_rts,
.carrier_raised = f81232_carrier_raised,
- .get_serial = f81232_get_serial_info,
+ .get_serial = f81232_get_serial,
.break_ctl = f81232_break_ctl,
.set_termios = f81232_set_termios,
.tiocmget = f81232_tiocmget,
.close = f81232_close,
.dtr_rts = f81232_dtr_rts,
.carrier_raised = f81232_carrier_raised,
- .get_serial = f81232_get_serial_info,
+ .get_serial = f81232_get_serial,
.break_ctl = f81232_break_ctl,
.set_termios = f81232_set_termios,
.tiocmget = f81232_tiocmget,
mutex_unlock(&serial_priv->urb_mutex);
}
-static int f81534_get_serial_info(struct tty_struct *tty,
- struct serial_struct *ss)
+static void f81534_get_serial_info(struct tty_struct *tty, struct serial_struct *ss)
{
struct usb_serial_port *port = tty->driver_data;
struct f81534_port_private *port_priv;
port_priv = usb_get_serial_port_data(port);
- ss->type = PORT_16550A;
- ss->port = port->port_number;
- ss->line = port->minor;
ss->baud_base = port_priv->baud_base;
- return 0;
}
static void f81534_process_per_serial_block(struct usb_serial_port *port,
unsigned int set, unsigned int clear);
static int ftdi_ioctl(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
-static int get_serial_info(struct tty_struct *tty,
- struct serial_struct *ss);
+static void get_serial_info(struct tty_struct *tty, struct serial_struct *ss);
static int set_serial_info(struct tty_struct *tty,
struct serial_struct *ss);
static void ftdi_break_ctl(struct tty_struct *tty, int break_state);
return 0;
}
-static int get_serial_info(struct tty_struct *tty,
- struct serial_struct *ss)
+static void get_serial_info(struct tty_struct *tty, struct serial_struct *ss)
{
struct usb_serial_port *port = tty->driver_data;
struct ftdi_private *priv = usb_get_serial_port_data(port);
ss->flags = priv->flags;
ss->baud_base = priv->baud_base;
ss->custom_divisor = priv->custom_divisor;
- return 0;
}
-static int set_serial_info(struct tty_struct *tty,
- struct serial_struct *ss)
+static int set_serial_info(struct tty_struct *tty, struct serial_struct *ss)
{
struct usb_serial_port *port = tty->driver_data;
struct ftdi_private *priv = usb_get_serial_port_data(port);
- struct ftdi_private old_priv;
+ int old_flags, old_divisor;
mutex_lock(&priv->cfg_lock);
- old_priv = *priv;
-
- /* Do error checking and permission checking */
if (!capable(CAP_SYS_ADMIN)) {
if ((ss->flags ^ priv->flags) & ~ASYNC_USR_MASK) {
mutex_unlock(&priv->cfg_lock);
return -EPERM;
}
- priv->flags = ((priv->flags & ~ASYNC_USR_MASK) |
- (ss->flags & ASYNC_USR_MASK));
- priv->custom_divisor = ss->custom_divisor;
- goto check_and_exit;
- }
-
- if (ss->baud_base != priv->baud_base) {
- mutex_unlock(&priv->cfg_lock);
- return -EINVAL;
}
- /* Make the changes - these are privileged changes! */
+ old_flags = priv->flags;
+ old_divisor = priv->custom_divisor;
- priv->flags = ((priv->flags & ~ASYNC_FLAGS) |
- (ss->flags & ASYNC_FLAGS));
+ priv->flags = ss->flags & ASYNC_FLAGS;
priv->custom_divisor = ss->custom_divisor;
-check_and_exit:
write_latency_timer(port);
- if ((priv->flags ^ old_priv.flags) & ASYNC_SPD_MASK ||
+ if ((priv->flags ^ old_flags) & ASYNC_SPD_MASK ||
((priv->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST &&
- priv->custom_divisor != old_priv.custom_divisor)) {
+ priv->custom_divisor != old_divisor)) {
/* warn about deprecation unless clearing */
if (priv->flags & ASYNC_SPD_MASK)
static atomic_t CmdUrbs = ATOMIC_INIT(0);
-/* local function prototypes */
+/* function prototypes */
-/* function prototypes for all URB callbacks */
-static void edge_interrupt_callback(struct urb *urb);
-static void edge_bulk_in_callback(struct urb *urb);
-static void edge_bulk_out_data_callback(struct urb *urb);
-static void edge_bulk_out_cmd_callback(struct urb *urb);
-
-/* function prototypes for the usbserial callbacks */
-static int edge_open(struct tty_struct *tty, struct usb_serial_port *port);
static void edge_close(struct usb_serial_port *port);
-static int edge_write(struct tty_struct *tty, struct usb_serial_port *port,
- const unsigned char *buf, int count);
-static int edge_write_room(struct tty_struct *tty);
-static int edge_chars_in_buffer(struct tty_struct *tty);
-static void edge_throttle(struct tty_struct *tty);
-static void edge_unthrottle(struct tty_struct *tty);
-static void edge_set_termios(struct tty_struct *tty,
- struct usb_serial_port *port,
- struct ktermios *old_termios);
-static int edge_ioctl(struct tty_struct *tty,
- unsigned int cmd, unsigned long arg);
-static void edge_break(struct tty_struct *tty, int break_state);
-static int edge_tiocmget(struct tty_struct *tty);
-static int edge_tiocmset(struct tty_struct *tty,
- unsigned int set, unsigned int clear);
-static int edge_startup(struct usb_serial *serial);
-static void edge_disconnect(struct usb_serial *serial);
-static void edge_release(struct usb_serial *serial);
-static int edge_port_probe(struct usb_serial_port *port);
-static void edge_port_remove(struct usb_serial_port *port);
-
-/* function prototypes for all of our local functions */
static void process_rcvd_data(struct edgeport_serial *edge_serial,
unsigned char *buffer, __u16 bufferLength);
static int send_iosp_ext_cmd(struct edgeport_port *edge_port, __u8 command,
__u8 param);
static int calc_baud_rate_divisor(struct device *dev, int baud_rate, int *divisor);
-static int send_cmd_write_baud_rate(struct edgeport_port *edge_port,
- int baudRate);
static void change_port_settings(struct tty_struct *tty,
struct edgeport_port *edge_port,
struct ktermios *old_termios);
static void send_more_port_data(struct edgeport_serial *edge_serial,
struct edgeport_port *edge_port);
-static int sram_write(struct usb_serial *serial, __u16 extAddr, __u16 addr,
- __u16 length, const __u8 *data);
-static int rom_read(struct usb_serial *serial, __u16 extAddr, __u16 addr,
- __u16 length, __u8 *data);
static int rom_write(struct usb_serial *serial, __u16 extAddr, __u16 addr,
__u16 length, const __u8 *data);
-static void get_manufacturing_desc(struct edgeport_serial *edge_serial);
-static void get_boot_desc(struct edgeport_serial *edge_serial);
-static void load_application_firmware(struct edgeport_serial *edge_serial);
-
-static void unicode_to_ascii(char *string, int buflen,
- __le16 *unicode, int unicode_size);
-
/* ************************************************************************ */
/* ************************************************************************ */
return result;
}
-static int get_serial_info(struct tty_struct *tty,
- struct serial_struct *ss)
-{
- struct usb_serial_port *port = tty->driver_data;
- struct edgeport_port *edge_port = usb_get_serial_port_data(port);
-
- ss->type = PORT_16550A;
- ss->line = edge_port->port->minor;
- ss->port = edge_port->port->port_number;
- ss->irq = 0;
- ss->xmit_fifo_size = edge_port->maxTxCredits;
- ss->baud_base = 9600;
- ss->close_delay = 5*HZ;
- ss->closing_wait = 30*HZ;
- return 0;
-}
-
-
/*****************************************************************************
* SerialIoctl
* this function handles any ioctl calls to the driver
.set_termios = edge_set_termios,
.tiocmget = edge_tiocmget,
.tiocmset = edge_tiocmset,
- .get_serial = get_serial_info,
.tiocmiwait = usb_serial_generic_tiocmiwait,
.get_icount = usb_serial_generic_get_icount,
.write = edge_write,
.set_termios = edge_set_termios,
.tiocmget = edge_tiocmget,
.tiocmset = edge_tiocmset,
- .get_serial = get_serial_info,
.tiocmiwait = usb_serial_generic_tiocmiwait,
.get_icount = usb_serial_generic_get_icount,
.write = edge_write,
.set_termios = edge_set_termios,
.tiocmget = edge_tiocmget,
.tiocmset = edge_tiocmset,
- .get_serial = get_serial_info,
.tiocmiwait = usb_serial_generic_tiocmiwait,
.get_icount = usb_serial_generic_get_icount,
.write = edge_write,
.set_termios = edge_set_termios,
.tiocmget = edge_tiocmget,
.tiocmset = edge_tiocmset,
- .get_serial = get_serial_info,
.tiocmiwait = usb_serial_generic_tiocmiwait,
.get_icount = usb_serial_generic_get_icount,
.write = edge_write,
#if !defined(_IO_EDGEPORT_H_)
#define _IO_EDGEPORT_H_
-
#define MAX_RS232_PORTS 8 /* Max # of RS-232 ports per device */
/* typedefs that the insideout headers need */
#define HIGH8(a) ((unsigned char)((a & 0xff00) >> 8))
#endif
-#ifndef __KERNEL__
-#define __KERNEL__
-#endif
-
#include "io_usbvend.h"
-
-
-/* The following table is used to map the USBx port number to
- * the device serial number (or physical USB path), */
-#define MAX_EDGEPORTS 64
-
-struct comMapper {
- char SerialNumber[MAX_SERIALNUMBER_LEN+1]; /* Serial number/usb path */
- int numPorts; /* Number of ports */
- int Original[MAX_RS232_PORTS]; /* Port numbers set by IOCTL */
- int Port[MAX_RS232_PORTS]; /* Actual used port numbers */
-};
-
-
-#define EDGEPORT_CONFIG_DEVICE "/proc/edgeport"
-
-/* /proc/edgeport Interface
- * This interface uses read/write/lseek interface to talk to the edgeport driver
- * the following read functions are supported: */
-#define PROC_GET_MAPPING_TO_PATH 1
-#define PROC_GET_COM_ENTRY 2
-#define PROC_GET_EDGE_MANUF_DESCRIPTOR 3
-#define PROC_GET_BOOT_DESCRIPTOR 4
-#define PROC_GET_PRODUCT_INFO 5
-#define PROC_GET_STRINGS 6
-#define PROC_GET_CURRENT_COM_MAPPING 7
-
-/* The parameters to the lseek() for the read is: */
-#define PROC_READ_SETUP(Command, Argument) ((Command) + ((Argument)<<8))
-
-
-/* the following write functions are supported: */
-#define PROC_SET_COM_MAPPING 1
-#define PROC_SET_COM_ENTRY 2
-
-
-/* The following structure is passed to the write */
-struct procWrite {
- int Command;
- union {
- struct comMapper Entry;
- int ComMappingBasedOnUSBPort; /* Boolean value */
- } u;
-};
-
/*
* Product information read from the Edgeport
*/
struct edge_compatibility_bits Epic;
};
-/*
- * Edgeport Stringblock String locations
- */
-#define EDGESTRING_MANUFNAME 1 /* Manufacture Name */
-#define EDGESTRING_PRODNAME 2 /* Product Name */
-#define EDGESTRING_SERIALNUM 3 /* Serial Number */
-#define EDGESTRING_ASSEMNUM 4 /* Assembly Number */
-#define EDGESTRING_OEMASSEMNUM 5 /* OEM Assembly Number */
-#define EDGESTRING_MANUFDATE 6 /* Manufacture Date */
-#define EDGESTRING_ORIGSERIALNUM 7 /* Serial Number */
-
-struct string_block {
- __u16 NumStrings; /* Number of strings in block */
- __u16 Strings[1]; /* Start of string block */
-};
-
-
-
#endif
#define EDGE_READ_URB_STOPPING 1
#define EDGE_READ_URB_STOPPED 2
-#define EDGE_CLOSING_WAIT 4000 /* in .01 sec */
-
/* Product information read from the Edgeport */
struct product_info {
int TiMode; /* Current TI Mode */
- __u8 hardware_type; /* Type of hardware */
-} __attribute__((packed));
+ u8 hardware_type; /* Type of hardware */
+} __packed;
/*
* Edgeport firmware header
} __packed;
struct edgeport_port {
- __u16 uart_base;
- __u16 dma_address;
- __u8 shadow_msr;
- __u8 shadow_mcr;
- __u8 shadow_lsr;
- __u8 lsr_mask;
- __u32 ump_read_timeout; /*
+ u16 uart_base;
+ u16 dma_address;
+ u8 shadow_msr;
+ u8 shadow_mcr;
+ u8 shadow_lsr;
+ u8 lsr_mask;
+ u32 ump_read_timeout; /*
* Number of milliseconds the UMP will
* wait without data before completing
* a read short
struct edgeport_serial *edge_serial;
struct usb_serial_port *port;
- __u8 bUartMode; /* Port type, 0: RS232, etc. */
+ u8 bUartMode; /* Port type, 0: RS232, etc. */
spinlock_t ep_lock;
int ep_read_urb_state;
int ep_write_urb_in_use;
MODULE_DEVICE_TABLE(usb, id_table_combined);
-static int closing_wait = EDGE_CLOSING_WAIT;
static bool ignore_cpu_rev;
static int default_uart_mode; /* RS232 */
#define TI_VSEND_TIMEOUT_DEFAULT 1000
#define TI_VSEND_TIMEOUT_FW_DOWNLOAD 10000
-static int ti_vread_sync(struct usb_device *dev, __u8 request,
- __u16 value, __u16 index, u8 *data, int size)
+static int ti_vread_sync(struct usb_device *dev, u8 request, u16 value,
+ u16 index, void *data, int size)
{
int status;
}
static int ti_vsend_sync(struct usb_device *dev, u8 request, u16 value,
- u16 index, u8 *data, int size, int timeout)
+ u16 index, void *data, int size, int timeout)
{
int status;
return 0;
}
-static int send_cmd(struct usb_device *dev, __u8 command,
- __u8 moduleid, __u16 value, u8 *data,
- int size)
+static int read_port_cmd(struct usb_serial_port *port, u8 command, u16 value,
+ void *data, int size)
+{
+ return ti_vread_sync(port->serial->dev, command, value,
+ UMPM_UART1_PORT + port->port_number,
+ data, size);
+}
+
+static int send_port_cmd(struct usb_serial_port *port, u8 command, u16 value,
+ void *data, int size)
{
- return ti_vsend_sync(dev, command, value, moduleid, data, size,
- TI_VSEND_TIMEOUT_DEFAULT);
+ return ti_vsend_sync(port->serial->dev, command, value,
+ UMPM_UART1_PORT + port->port_number,
+ data, size, TI_VSEND_TIMEOUT_DEFAULT);
}
/* clear tx/rx buffers and fifo in TI UMP */
-static int purge_port(struct usb_serial_port *port, __u16 mask)
+static int purge_port(struct usb_serial_port *port, u16 mask)
{
int port_number = port->port_number;
dev_dbg(&port->dev, "%s - port %d, mask %x\n", __func__, port_number, mask);
- return send_cmd(port->serial->dev,
- UMPC_PURGE_PORT,
- (__u8)(UMPM_UART1_PORT + port_number),
- mask,
- NULL,
- 0);
+ return send_port_cmd(port, UMPC_PURGE_PORT, mask, NULL, 0);
}
/**
* @buffer: pointer to input data buffer
*/
static int read_download_mem(struct usb_device *dev, int start_address,
- int length, __u8 address_type, __u8 *buffer)
+ int length, u8 address_type, u8 *buffer)
{
int status = 0;
- __u8 read_length;
+ u8 read_length;
u16 be_start_address;
dev_dbg(&dev->dev, "%s - @ %x for %d\n", __func__, start_address, length);
if (length > 64)
read_length = 64;
else
- read_length = (__u8)length;
+ read_length = (u8)length;
if (read_length > 1) {
dev_dbg(&dev->dev, "%s - @ %x for %d\n", __func__, start_address, read_length);
*/
be_start_address = swab16((u16)start_address);
status = ti_vread_sync(dev, UMPC_MEMORY_READ,
- (__u16)address_type,
+ (u16)address_type,
be_start_address,
buffer, read_length);
}
static int read_ram(struct usb_device *dev, int start_address,
- int length, __u8 *buffer)
+ int length, u8 *buffer)
{
return read_download_mem(dev, start_address, length,
DTK_ADDR_SPACE_XDATA, buffer);
/* Read edgeport memory to a given block */
static int read_boot_mem(struct edgeport_serial *serial,
- int start_address, int length, __u8 *buffer)
+ int start_address, int length, u8 *buffer)
{
int status = 0;
int i;
for (i = 0; i < length; i++) {
status = ti_vread_sync(serial->serial->dev,
UMPC_MEMORY_READ, serial->TI_I2C_Type,
- (__u16)(start_address+i), &buffer[i], 0x01);
+ (u16)(start_address+i), &buffer[i], 0x01);
if (status) {
dev_dbg(&serial->serial->dev->dev, "%s - ERROR %x\n", __func__, status);
return status;
/* Write given block to TI EPROM memory */
static int write_boot_mem(struct edgeport_serial *serial,
- int start_address, int length, __u8 *buffer)
+ int start_address, int length, u8 *buffer)
{
int status = 0;
int i;
/* Write edgeport I2C memory to TI chip */
static int write_i2c_mem(struct edgeport_serial *serial,
- int start_address, int length, __u8 address_type, __u8 *buffer)
+ int start_address, int length, u8 address_type, u8 *buffer)
{
struct device *dev = &serial->serial->dev->dev;
int status = 0;
{
int status;
struct out_endpoint_desc_block *oedb;
- __u8 *lsr;
+ u8 *lsr;
int bytes_left = 0;
oedb = kmalloc(sizeof(*oedb), GFP_KERNEL);
}
static int read_rom(struct edgeport_serial *serial,
- int start_address, int length, __u8 *buffer)
+ int start_address, int length, u8 *buffer)
{
int status;
}
static int write_rom(struct edgeport_serial *serial, int start_address,
- int length, __u8 *buffer)
+ int length, u8 *buffer)
{
if (serial->product_info.TiMode == TI_MODE_BOOT)
return write_boot_mem(serial, start_address, length,
status = read_rom(serial,
start_address,
sizeof(struct ti_i2c_desc),
- (__u8 *)rom_desc);
+ (u8 *)rom_desc);
if (status)
return 0;
}
/* Validate descriptor checksum */
-static int valid_csum(struct ti_i2c_desc *rom_desc, __u8 *buffer)
+static int valid_csum(struct ti_i2c_desc *rom_desc, u8 *buffer)
{
- __u16 i;
- __u8 cs = 0;
+ u16 i;
+ u8 cs = 0;
for (i = 0; i < le16_to_cpu(rom_desc->Size); i++)
- cs = (__u8)(cs + buffer[i]);
+ cs = (u8)(cs + buffer[i]);
if (cs != rom_desc->CheckSum) {
pr_debug("%s - Mismatch %x - %x", __func__, rom_desc->CheckSum, cs);
int status = 0;
struct ti_i2c_desc *rom_desc;
int start_address = 2;
- __u8 *buffer;
- __u16 ttype;
+ u8 *buffer;
+ u16 ttype;
rom_desc = kmalloc(sizeof(*rom_desc), GFP_KERNEL);
if (!rom_desc)
status = read_rom(serial,
start_address,
sizeof(struct ti_i2c_desc),
- (__u8 *)rom_desc);
+ (u8 *)rom_desc);
if (status)
break;
return status;
}
-static int get_manuf_info(struct edgeport_serial *serial, __u8 *buffer)
+static int get_manuf_info(struct edgeport_serial *serial, u8 *buffer)
{
int status;
int start_address;
/* Build firmware header used for firmware update */
static int build_i2c_fw_hdr(u8 *header, const struct firmware *fw)
{
- __u8 *buffer;
+ u8 *buffer;
int buffer_size;
int i;
- __u8 cs = 0;
+ u8 cs = 0;
struct ti_i2c_desc *i2c_header;
struct ti_i2c_image_header *img_header;
struct ti_i2c_firmware_rec *firmware_rec;
le16_to_cpu(img_header->Length));
for (i=0; i < buffer_size; i++) {
- cs = (__u8)(cs + buffer[i]);
+ cs = (u8)(cs + buffer[i]);
}
kfree(buffer);
}
/* Download given firmware image to the device (IN BOOT MODE) */
-static int download_code(struct edgeport_serial *serial, __u8 *image,
+static int download_code(struct edgeport_serial *serial, u8 *image,
int image_length)
{
int status = 0;
if (!ti_manuf_desc)
return -ENOMEM;
- status = get_manuf_info(serial, (__u8 *)ti_manuf_desc);
+ status = get_manuf_info(serial, (u8 *)ti_manuf_desc);
if (status) {
kfree(ti_manuf_desc);
return status;
status = read_rom(serial, start_address +
sizeof(struct ti_i2c_desc),
sizeof(struct ti_i2c_firmware_rec),
- (__u8 *)firmware_version);
+ (u8 *)firmware_version);
if (status) {
kfree(firmware_version);
kfree(rom_desc);
if (start_address != 0) {
#define HEADER_SIZE (sizeof(struct ti_i2c_desc) + \
sizeof(struct ti_i2c_firmware_rec))
- __u8 *header;
- __u8 *vheader;
+ u8 *header;
+ u8 *vheader;
header = kmalloc(HEADER_SIZE, GFP_KERNEL);
if (!header) {
if (!check_i2c_image(serial)) {
struct ti_i2c_image_header *header;
int i;
- __u8 cs = 0;
- __u8 *buffer;
+ u8 cs = 0;
+ u8 *buffer;
int buffer_size;
/*
if (!ti_manuf_desc)
return -ENOMEM;
- status = get_manuf_info(serial, (__u8 *)ti_manuf_desc);
+ status = get_manuf_info(serial, (u8 *)ti_manuf_desc);
if (status) {
kfree(ti_manuf_desc);
goto stayinbootmode;
for (i = sizeof(struct ti_i2c_image_header);
i < buffer_size; i++) {
- cs = (__u8)(cs + buffer[i]);
+ cs = (u8)(cs + buffer[i]);
}
header = (struct ti_i2c_image_header *)buffer;
/* update length and checksum after padding */
- header->Length = cpu_to_le16((__u16)(buffer_size -
+ header->Length = cpu_to_le16((u16)(buffer_size -
sizeof(struct ti_i2c_image_header)));
header->CheckSum = cs;
static int ti_do_config(struct edgeport_port *port, int feature, int on)
{
- int port_number = port->port->port_number;
-
on = !!on; /* 1 or 0 not bitmask */
- return send_cmd(port->port->serial->dev,
- feature, (__u8)(UMPM_UART1_PORT + port_number),
- on, NULL, 0);
+
+ return send_port_cmd(port->port, feature, on, NULL, 0);
}
-static int restore_mcr(struct edgeport_port *port, __u8 mcr)
+static int restore_mcr(struct edgeport_port *port, u8 mcr)
{
int status = 0;
}
/* Convert TI LSR to standard UART flags */
-static __u8 map_line_status(__u8 ti_lsr)
+static u8 map_line_status(u8 ti_lsr)
{
- __u8 lsr = 0;
+ u8 lsr = 0;
#define MAP_FLAG(flagUmp, flagUart) \
if (ti_lsr & flagUmp) \
return lsr;
}
-static void handle_new_msr(struct edgeport_port *edge_port, __u8 msr)
+static void handle_new_msr(struct edgeport_port *edge_port, u8 msr)
{
struct async_icount *icount;
struct tty_struct *tty;
}
static void handle_new_lsr(struct edgeport_port *edge_port, int lsr_data,
- __u8 lsr, __u8 data)
+ u8 lsr, u8 data)
{
struct async_icount *icount;
- __u8 new_lsr = (__u8)(lsr & (__u8)(LSR_OVER_ERR | LSR_PAR_ERR |
+ u8 new_lsr = (u8)(lsr & (u8)(LSR_OVER_ERR | LSR_PAR_ERR |
LSR_FRM_ERR | LSR_BREAK));
dev_dbg(&edge_port->port->dev, "%s - %02x\n", __func__, new_lsr);
* Parity and Framing errors only count if they
* occur exclusive of a break being received.
*/
- new_lsr &= (__u8)(LSR_OVER_ERR | LSR_BREAK);
+ new_lsr &= (u8)(LSR_OVER_ERR | LSR_BREAK);
/* Place LSR data byte into Rx buffer */
if (lsr_data)
int port_number;
int function;
int retval;
- __u8 lsr;
- __u8 msr;
+ u8 lsr;
+ u8 msr;
int status = urb->status;
switch (status) {
struct edgeport_serial *edge_serial;
struct usb_device *dev;
struct urb *urb;
- int port_number;
int status;
u16 open_settings;
u8 transaction_timeout;
if (edge_port == NULL)
return -ENODEV;
- port_number = port->port_number;
-
dev = port->serial->dev;
/* turn off loopback */
dev_dbg(&port->dev, "%s - Sending UMPC_OPEN_PORT\n", __func__);
/* Tell TI to open and start the port */
- status = send_cmd(dev, UMPC_OPEN_PORT,
- (u8)(UMPM_UART1_PORT + port_number), open_settings, NULL, 0);
+ status = send_port_cmd(port, UMPC_OPEN_PORT, open_settings, NULL, 0);
if (status) {
dev_err(&port->dev, "%s - cannot send open command, %d\n",
__func__, status);
}
/* Start the DMA? */
- status = send_cmd(dev, UMPC_START_PORT,
- (u8)(UMPM_UART1_PORT + port_number), 0, NULL, 0);
+ status = send_port_cmd(port, UMPC_START_PORT, 0, NULL, 0);
if (status) {
dev_err(&port->dev, "%s - cannot send start DMA command, %d\n",
__func__, status);
}
/* Read Initial MSR */
- status = ti_vread_sync(dev, UMPC_READ_MSR, 0,
- (__u16)(UMPM_UART1_PORT + port_number),
- &edge_port->shadow_msr, 1);
+ status = read_port_cmd(port, UMPC_READ_MSR, 0, &edge_port->shadow_msr, 1);
if (status) {
dev_err(&port->dev, "%s - cannot send read MSR command, %d\n",
__func__, status);
{
struct edgeport_serial *edge_serial;
struct edgeport_port *edge_port;
- struct usb_serial *serial = port->serial;
unsigned long flags;
- int port_number;
edge_serial = usb_get_serial_data(port->serial);
edge_port = usb_get_serial_port_data(port);
spin_unlock_irqrestore(&edge_port->ep_lock, flags);
dev_dbg(&port->dev, "%s - send umpc_close_port\n", __func__);
- port_number = port->port_number;
- send_cmd(serial->dev, UMPC_CLOSE_PORT,
- (__u8)(UMPM_UART1_PORT + port_number), 0, NULL, 0);
+ send_port_cmd(port, UMPC_CLOSE_PORT, 0, NULL, 0);
mutex_lock(&edge_serial->es_lock);
--edge_port->edge_serial->num_ports_open;
int baud;
unsigned cflag;
int status;
- int port_number = edge_port->port->port_number;
config = kmalloc (sizeof (*config), GFP_KERNEL);
if (!config) {
/* These flags must be set */
config->wFlags |= UMP_MASK_UART_FLAGS_RECEIVE_MS_INT;
config->wFlags |= UMP_MASK_UART_FLAGS_AUTO_START_ON_ERR;
- config->bUartMode = (__u8)(edge_port->bUartMode);
+ config->bUartMode = (u8)(edge_port->bUartMode);
switch (cflag & CSIZE) {
case CS5:
}
edge_port->baud_rate = baud;
- config->wBaudRate = (__u16)((461550L + baud/2) / baud);
+ config->wBaudRate = (u16)((461550L + baud/2) / baud);
/* FIXME: Recompute actual baud from divisor here */
cpu_to_be16s(&config->wFlags);
cpu_to_be16s(&config->wBaudRate);
- status = send_cmd(edge_port->port->serial->dev, UMPC_SET_CONFIG,
- (__u8)(UMPM_UART1_PORT + port_number),
- 0, (__u8 *)config, sizeof(*config));
+ status = send_port_cmd(edge_port->port, UMPC_SET_CONFIG, 0, config,
+ sizeof(*config));
if (status)
dev_dbg(dev, "%s - error %d when trying to write config to device\n",
__func__, status);
return result;
}
-static int get_serial_info(struct tty_struct *tty,
- struct serial_struct *ss)
-{
- struct usb_serial_port *port = tty->driver_data;
- struct edgeport_port *edge_port = usb_get_serial_port_data(port);
- unsigned cwait;
-
- cwait = edge_port->port->port.closing_wait;
- if (cwait != ASYNC_CLOSING_WAIT_NONE)
- cwait = jiffies_to_msecs(cwait) / 10;
-
- ss->type = PORT_16550A;
- ss->line = edge_port->port->minor;
- ss->port = edge_port->port->port_number;
- ss->irq = 0;
- ss->xmit_fifo_size = edge_port->port->bulk_out_size;
- ss->baud_base = 9600;
- ss->close_delay = 5*HZ;
- ss->closing_wait = cwait;
- return 0;
-}
-
static void edge_break(struct tty_struct *tty, int break_state)
{
struct usb_serial_port *port = tty->driver_data;
if (ret)
goto err;
- port->port.closing_wait = msecs_to_jiffies(closing_wait * 10);
+ /*
+ * The LSR does not tell when the transmitter shift register has
+ * emptied so add a one-character drain delay.
+ */
port->port.drain_delay = 1;
return 0;
.release = edge_release,
.port_probe = edge_port_probe,
.port_remove = edge_port_remove,
- .get_serial = get_serial_info,
.set_termios = edge_set_termios,
.tiocmget = edge_tiocmget,
.tiocmset = edge_tiocmset,
.release = edge_release,
.port_probe = edge_port_probe,
.port_remove = edge_port_remove,
- .get_serial = get_serial_info,
.set_termios = edge_set_termios,
.tiocmget = edge_tiocmget,
.tiocmset = edge_tiocmset,
MODULE_LICENSE("GPL");
MODULE_FIRMWARE("edgeport/down3.bin");
-module_param(closing_wait, int, S_IRUGO | S_IWUSR);
-MODULE_PARM_DESC(closing_wait, "Maximum wait for data to drain, in .01 secs");
-
module_param(ignore_cpu_rev, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(ignore_cpu_rev,
"Ignore the cpu revision when connecting to a device");
#define UMPD_OEDB2_ADDRESS 0xFF10
struct out_endpoint_desc_block {
- __u8 Configuration;
- __u8 XBufAddr;
- __u8 XByteCount;
- __u8 Unused1;
- __u8 Unused2;
- __u8 YBufAddr;
- __u8 YByteCount;
- __u8 BufferSize;
-} __attribute__((packed));
+ u8 Configuration;
+ u8 XBufAddr;
+ u8 XByteCount;
+ u8 Unused1;
+ u8 Unused2;
+ u8 YBufAddr;
+ u8 YByteCount;
+ u8 BufferSize;
+};
/*
*/
/* UART settings */
struct ump_uart_config {
- __u16 wBaudRate; /* Baud rate */
- __u16 wFlags; /* Bitmap mask of flags */
- __u8 bDataBits; /* 5..8 - data bits per character */
- __u8 bParity; /* Parity settings */
- __u8 bStopBits; /* Stop bits settings */
+ u16 wBaudRate; /* Baud rate */
+ u16 wFlags; /* Bitmap mask of flags */
+ u8 bDataBits; /* 5..8 - data bits per character */
+ u8 bParity; /* Parity settings */
+ u8 bStopBits; /* Stop bits settings */
char cXon; /* XON character */
char cXoff; /* XOFF character */
- __u8 bUartMode; /* Will be updated when a user */
+ u8 bUartMode; /* Will be updated when a user */
/* interface is defined */
-} __attribute__((packed));
+};
/*
*/
/* Interrupt packet structure */
struct ump_interrupt {
- __u8 bICode; /* Interrupt code (interrupt num) */
- __u8 bIInfo; /* Interrupt information */
-} __attribute__((packed));
+ u8 bICode; /* Interrupt code (interrupt num) */
+ u8 bIInfo; /* Interrupt information */
+};
#define TIUMP_GET_PORT_FROM_CODE(c) (((c) >> 6) & 0x01)
struct iuu_private *priv = usb_get_serial_port_data(port);
unsigned long flags;
int status = urb->status;
- int error = 0;
int len = 0;
unsigned char *data = urb->transfer_buffer;
priv->poll++;
if (urb->actual_length > 1) {
dev_dbg(&port->dev, "%s - urb->actual_length = %i\n", __func__,
urb->actual_length);
- error = 1;
return;
}
/* if len > 0 call readbuf */
- if (len > 0 && error == 0) {
+ if (len > 0) {
dev_dbg(&port->dev, "%s - call read buf - len to read is %i\n",
__func__, len);
status = iuu_read_buf(port, len);
#define DRIVER_AUTHOR "Hugh Blemings <hugh@misc.nu"
#define DRIVER_DESC "Keyspan USB to Serial Converter Driver"
-/* Function prototypes for Keyspan serial converter */
-static int keyspan_open(struct tty_struct *tty, struct usb_serial_port *port);
-static void keyspan_close(struct usb_serial_port *port);
-static void keyspan_dtr_rts(struct usb_serial_port *port, int on);
-static int keyspan_startup(struct usb_serial *serial);
-static void keyspan_disconnect(struct usb_serial *serial);
-static void keyspan_release(struct usb_serial *serial);
-static int keyspan_port_probe(struct usb_serial_port *port);
-static void keyspan_port_remove(struct usb_serial_port *port);
-static int keyspan_write_room(struct tty_struct *tty);
-static int keyspan_write(struct tty_struct *tty, struct usb_serial_port *port,
- const unsigned char *buf, int count);
static void keyspan_send_setup(struct usb_serial_port *port, int reset_port);
-static void keyspan_set_termios(struct tty_struct *tty,
- struct usb_serial_port *port,
- struct ktermios *old);
-static void keyspan_break_ctl(struct tty_struct *tty, int break_state);
-static int keyspan_tiocmget(struct tty_struct *tty);
-static int keyspan_tiocmset(struct tty_struct *tty, unsigned int set,
- unsigned int clear);
-static int keyspan_fake_startup(struct usb_serial *serial);
static int keyspan_usa19_calc_baud(struct usb_serial_port *port,
u32 baud_rate, u32 baudclk,
unsigned long flags = 0;
unsigned long control_state = 0;
- dev_dbg(tty->dev, "%s - set=%d, clear=%d\n", __func__, set, clear);
+ dev_dbg(&port->dev, "%s - set=%d, clear=%d\n", __func__, set, clear);
spin_lock_irqsave(&metro_priv->lock, flags);
control_state = metro_priv->control_state;
/* Submit the urb to read from the port. */
result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC);
if (result)
- dev_err(tty->dev,
+ dev_err(&port->dev,
"failed submitting interrupt in urb error code=%d\n",
result);
}
return 0;
}
-static int get_serial_info(struct tty_struct *tty,
- struct serial_struct *ss)
-{
- struct usb_serial_port *port = tty->driver_data;
- struct moschip_port *mos7720_port = usb_get_serial_port_data(port);
-
- ss->type = PORT_16550A;
- ss->line = mos7720_port->port->minor;
- ss->port = mos7720_port->port->port_number;
- ss->irq = 0;
- ss->xmit_fifo_size = NUM_URBS * URB_TRANSFER_BUFFER_SIZE;
- ss->baud_base = 9600;
- ss->close_delay = 5*HZ;
- ss->closing_wait = 30*HZ;
- return 0;
-}
-
static int mos7720_ioctl(struct tty_struct *tty,
unsigned int cmd, unsigned long arg)
{
.ioctl = mos7720_ioctl,
.tiocmget = mos7720_tiocmget,
.tiocmset = mos7720_tiocmset,
- .get_serial = get_serial_info,
.set_termios = mos7720_set_termios,
.write = mos7720_write,
.write_room = mos7720_write_room,
return 0;
}
-/*****************************************************************************
- * mos7840_get_serial_info
- * function to get information about serial port
- *****************************************************************************/
-
-static int mos7840_get_serial_info(struct tty_struct *tty,
- struct serial_struct *ss)
-{
- struct usb_serial_port *port = tty->driver_data;
- struct moschip_port *mos7840_port = usb_get_serial_port_data(port);
-
- ss->type = PORT_16550A;
- ss->line = mos7840_port->port->minor;
- ss->port = mos7840_port->port->port_number;
- ss->irq = 0;
- ss->xmit_fifo_size = NUM_URBS * URB_TRANSFER_BUFFER_SIZE;
- ss->baud_base = 9600;
- ss->close_delay = 5 * HZ;
- ss->closing_wait = 30 * HZ;
- return 0;
-}
-
/*****************************************************************************
* SerialIoctl
* this function handles any ioctl calls to the driver
.probe = mos7840_probe,
.attach = mos7840_attach,
.ioctl = mos7840_ioctl,
- .get_serial = mos7840_get_serial_info,
.set_termios = mos7840_set_termios,
.break_ctl = mos7840_break,
.tiocmget = mos7840_tiocmget,
return 0;
}
-static int get_serial_info(struct tty_struct *tty,
- struct serial_struct *ss)
-{
- struct usb_serial_port *port = tty->driver_data;
-
- /* fake emulate a 16550 uart to make userspace code happy */
- ss->type = PORT_16550A;
- ss->line = port->minor;
- ss->port = 0;
- ss->irq = 0;
- ss->xmit_fifo_size = 1024;
- ss->baud_base = 9600;
- ss->close_delay = 5*HZ;
- ss->closing_wait = 30*HZ;
- return 0;
-}
-
static int opticon_port_probe(struct usb_serial_port *port)
{
struct opticon_private *priv;
.chars_in_buffer = opticon_chars_in_buffer,
.throttle = usb_serial_generic_throttle,
.unthrottle = usb_serial_generic_unthrottle,
- .get_serial = get_serial_info,
.tiocmget = opticon_tiocmget,
.tiocmset = opticon_tiocmset,
.process_read_urb = opticon_process_read_urb,
.chars_in_buffer = usb_wwan_chars_in_buffer,
.tiocmget = usb_wwan_tiocmget,
.tiocmset = usb_wwan_tiocmset,
- .get_serial = usb_wwan_get_serial_info,
- .set_serial = usb_wwan_set_serial_info,
.attach = option_attach,
.release = option_release,
.port_probe = usb_wwan_port_probe,
static void pl2303_set_break(struct usb_serial_port *port, bool enable);
enum pl2303_type {
- TYPE_01, /* Type 0 and 1 (difference unknown) */
- TYPE_HX, /* HX version of the pl2303 chip */
- TYPE_HXN, /* HXN version of the pl2303 chip */
+ TYPE_H,
+ TYPE_HX,
+ TYPE_TA,
+ TYPE_TB,
+ TYPE_HXD,
+ TYPE_HXN,
TYPE_COUNT
};
struct pl2303_type_data {
+ const char *name;
speed_t max_baud_rate;
unsigned long quirks;
unsigned int no_autoxonxoff:1;
unsigned int no_divisors:1;
+ unsigned int alt_divisors:1;
};
struct pl2303_serial_private {
};
static const struct pl2303_type_data pl2303_type_data[TYPE_COUNT] = {
- [TYPE_01] = {
+ [TYPE_H] = {
+ .name = "H",
.max_baud_rate = 1228800,
.quirks = PL2303_QUIRK_LEGACY,
.no_autoxonxoff = true,
},
[TYPE_HX] = {
+ .name = "HX",
+ .max_baud_rate = 6000000,
+ },
+ [TYPE_TA] = {
+ .name = "TA",
+ .max_baud_rate = 6000000,
+ .alt_divisors = true,
+ },
+ [TYPE_TB] = {
+ .name = "TB",
+ .max_baud_rate = 12000000,
+ .alt_divisors = true,
+ },
+ [TYPE_HXD] = {
+ .name = "HXD",
.max_baud_rate = 12000000,
},
[TYPE_HXN] = {
+ .name = "G",
.max_baud_rate = 12000000,
.no_divisors = true,
},
return 1;
}
+static bool pl2303_supports_hx_status(struct usb_serial *serial)
+{
+ int ret;
+ u8 buf;
+
+ ret = usb_control_msg_recv(serial->dev, 0, VENDOR_READ_REQUEST,
+ VENDOR_READ_REQUEST_TYPE, PL2303_READ_TYPE_HX_STATUS,
+ 0, &buf, 1, 100, GFP_KERNEL);
+
+ return ret == 0;
+}
+
+static int pl2303_detect_type(struct usb_serial *serial)
+{
+ struct usb_device_descriptor *desc = &serial->dev->descriptor;
+ u16 bcdDevice, bcdUSB;
+
+ /*
+ * Legacy PL2303H, variants 0 and 1 (difference unknown).
+ */
+ if (desc->bDeviceClass == 0x02)
+ return TYPE_H; /* variant 0 */
+
+ if (desc->bMaxPacketSize0 != 0x40) {
+ if (desc->bDeviceClass == 0x00 || desc->bDeviceClass == 0xff)
+ return TYPE_H; /* variant 1 */
+
+ return TYPE_H; /* variant 0 */
+ }
+
+ bcdDevice = le16_to_cpu(desc->bcdDevice);
+ bcdUSB = le16_to_cpu(desc->bcdUSB);
+
+ switch (bcdDevice) {
+ case 0x100:
+ /*
+ * Assume it's an HXN-type if the device doesn't support the old read
+ * request value.
+ */
+ if (bcdUSB == 0x200 && !pl2303_supports_hx_status(serial))
+ return TYPE_HXN;
+ break;
+ case 0x300:
+ if (bcdUSB == 0x200)
+ return TYPE_TA;
+
+ return TYPE_HX;
+ case 0x400:
+ return TYPE_HXD;
+ case 0x500:
+ return TYPE_TB;
+ }
+
+ dev_err(&serial->interface->dev,
+ "unknown device type, please report to linux-usb@vger.kernel.org\n");
+ return -ENODEV;
+}
+
static int pl2303_startup(struct usb_serial *serial)
{
struct pl2303_serial_private *spriv;
- enum pl2303_type type = TYPE_01;
+ enum pl2303_type type;
unsigned char *buf;
- int res;
+ int ret;
+
+ ret = pl2303_detect_type(serial);
+ if (ret < 0)
+ return ret;
+
+ type = ret;
+ dev_dbg(&serial->interface->dev, "device type: %s\n", pl2303_type_data[type].name);
spriv = kzalloc(sizeof(*spriv), GFP_KERNEL);
if (!spriv)
return -ENOMEM;
- buf = kmalloc(1, GFP_KERNEL);
- if (!buf) {
- kfree(spriv);
- return -ENOMEM;
- }
-
- if (serial->dev->descriptor.bDeviceClass == 0x02)
- type = TYPE_01; /* type 0 */
- else if (serial->dev->descriptor.bMaxPacketSize0 == 0x40)
- type = TYPE_HX;
- else if (serial->dev->descriptor.bDeviceClass == 0x00)
- type = TYPE_01; /* type 1 */
- else if (serial->dev->descriptor.bDeviceClass == 0xFF)
- type = TYPE_01; /* type 1 */
- dev_dbg(&serial->interface->dev, "device type: %d\n", type);
-
- if (type == TYPE_HX) {
- res = usb_control_msg(serial->dev,
- usb_rcvctrlpipe(serial->dev, 0),
- VENDOR_READ_REQUEST, VENDOR_READ_REQUEST_TYPE,
- PL2303_READ_TYPE_HX_STATUS, 0, buf, 1, 100);
- if (res != 1)
- type = TYPE_HXN;
- }
-
spriv->type = &pl2303_type_data[type];
spriv->quirks = (unsigned long)usb_get_serial_data(serial);
spriv->quirks |= spriv->type->quirks;
usb_set_serial_data(serial, spriv);
if (type != TYPE_HXN) {
+ buf = kmalloc(1, GFP_KERNEL);
+ if (!buf) {
+ kfree(spriv);
+ return -ENOMEM;
+ }
+
pl2303_vendor_read(serial, 0x8484, buf);
pl2303_vendor_write(serial, 0x0404, 0);
pl2303_vendor_read(serial, 0x8484, buf);
pl2303_vendor_write(serial, 2, 0x24);
else
pl2303_vendor_write(serial, 2, 0x44);
- }
- kfree(buf);
+ kfree(buf);
+ }
return 0;
}
return baud;
}
+static speed_t pl2303_encode_baud_rate_divisor_alt(unsigned char buf[4],
+ speed_t baud)
+{
+ unsigned int baseline, mantissa, exponent;
+
+ /*
+ * Apparently, for the TA version the formula is:
+ * baudrate = 12M * 32 / (mantissa * 2^exponent)
+ * where
+ * mantissa = buf[10:0]
+ * exponent = buf[15:13 16]
+ */
+ baseline = 12000000 * 32;
+ mantissa = baseline / baud;
+ if (mantissa == 0)
+ mantissa = 1; /* Avoid dividing by zero if baud > 32*12M. */
+ exponent = 0;
+ while (mantissa >= 2048) {
+ if (exponent < 15) {
+ mantissa >>= 1; /* divide by 2 */
+ exponent++;
+ } else {
+ /* Exponent is maxed. Trim mantissa and leave. */
+ mantissa = 2047;
+ break;
+ }
+ }
+
+ buf[3] = 0x80;
+ buf[2] = exponent & 0x01;
+ buf[1] = (exponent & ~0x01) << 4 | mantissa >> 8;
+ buf[0] = mantissa & 0xff;
+
+ /* Calculate and return the exact baud rate. */
+ baud = (baseline / mantissa) >> exponent;
+
+ return baud;
+}
+
static void pl2303_encode_baud_rate(struct tty_struct *tty,
struct usb_serial_port *port,
u8 buf[4])
if (baud == baud_sup)
baud = pl2303_encode_baud_rate_direct(buf, baud);
+ else if (spriv->type->alt_divisors)
+ baud = pl2303_encode_baud_rate_divisor_alt(buf, baud);
else
baud = pl2303_encode_baud_rate_divisor(buf, baud);
return 0;
}
-static int pl2303_get_serial(struct tty_struct *tty,
- struct serial_struct *ss)
-{
- struct usb_serial_port *port = tty->driver_data;
-
- ss->type = PORT_16654;
- ss->line = port->minor;
- ss->port = port->port_number;
- ss->baud_base = 460800;
- return 0;
-}
-
static void pl2303_set_break(struct usb_serial_port *port, bool enable)
{
struct usb_serial *serial = port->serial;
.close = pl2303_close,
.dtr_rts = pl2303_dtr_rts,
.carrier_raised = pl2303_carrier_raised,
- .get_serial = pl2303_get_serial,
.break_ctl = pl2303_break_ctl,
.set_termios = pl2303_set_termios,
.tiocmget = pl2303_tiocmget,
usb_kill_urb(serial_priv->read_urb);
}
-static int get_serial_info(struct tty_struct *tty,
- struct serial_struct *ss)
-{
- struct usb_serial_port *port = tty->driver_data;
-
- ss->line = port->minor;
- ss->port = 0;
- ss->irq = 0;
- ss->xmit_fifo_size = port->bulk_out_size;
- ss->baud_base = 9600;
- ss->close_delay = 5*HZ;
- ss->closing_wait = 30*HZ;
- return 0;
-}
-
static void qt2_process_status(struct usb_serial_port *port, unsigned char *ch)
{
switch (*ch) {
.tiocmset = qt2_tiocmset,
.tiocmiwait = usb_serial_generic_tiocmiwait,
.get_icount = usb_serial_generic_get_icount,
- .get_serial = get_serial_info,
.set_termios = qt2_set_termios,
};
return usb_serial_generic_open(tty, port);
}
-static int get_serial_info(struct tty_struct *tty,
- struct serial_struct *ss)
-{
- struct usb_serial_port *port = tty->driver_data;
-
- ss->line = port->minor;
- ss->port = 0;
- ss->irq = 0;
- ss->xmit_fifo_size = port->bulk_out_size;
- ss->baud_base = 9600;
- ss->close_delay = 5*HZ;
- ss->closing_wait = 30*HZ;
- return 0;
-}
-
static int ssu100_attach(struct usb_serial *serial)
{
return ssu100_initdevice(serial->dev);
.tiocmset = ssu100_tiocmset,
.tiocmiwait = usb_serial_generic_tiocmiwait,
.get_icount = usb_serial_generic_get_icount,
- .get_serial = get_serial_info,
.set_termios = ssu100_set_termios,
};
#define TI_LSR_ERROR 0x0F
#define TI_LSR_RX_FULL 0x10
#define TI_LSR_TX_EMPTY 0x20
+#define TI_LSR_TX_EMPTY_BOTH 0x40
/* Line control */
#define TI_LCR_BREAK 0x40
char cXon;
char cXoff;
u8 bUartMode;
-} __packed;
+};
/* Get port status */
struct ti_port_status {
u8 bErrorCode;
u8 bMSR;
u8 bLSR;
-} __packed;
+};
/* Purge modes */
#define TI_PURGE_OUTPUT 0x00
} __packed;
struct ti_read_data_request {
- __u8 bAddrType;
- __u8 bDataType;
- __u8 bDataCounter;
+ u8 bAddrType;
+ u8 bDataType;
+ u8 bDataCounter;
__be16 wBaseAddrHi;
__be16 wBaseAddrLo;
} __packed;
struct ti_read_data_bytes {
- __u8 bCmdCode;
- __u8 bModuleId;
- __u8 bErrorCode;
- __u8 bData[];
-} __packed;
+ u8 bCmdCode;
+ u8 bModuleId;
+ u8 bErrorCode;
+ u8 bData[];
+};
/* Interrupt struct */
struct ti_interrupt {
- __u8 bICode;
- __u8 bIInfo;
-} __packed;
+ u8 bICode;
+ u8 bIInfo;
+};
/* Interrupt codes */
#define TI_CODE_HARDWARE_ERROR 0xFF
#define TI_TRANSFER_TIMEOUT 2
-#define TI_DEFAULT_CLOSING_WAIT 4000 /* in .01 secs */
-
/* read urb states */
#define TI_READ_URB_RUNNING 0
#define TI_READ_URB_STOPPING 1
static void ti_send(struct ti_port *tport);
static int ti_set_mcr(struct ti_port *tport, unsigned int mcr);
static int ti_get_lsr(struct ti_port *tport, u8 *lsr);
-static int ti_get_serial_info(struct tty_struct *tty,
- struct serial_struct *ss);
-static int ti_set_serial_info(struct tty_struct *tty,
- struct serial_struct *ss);
+static void ti_get_serial_info(struct tty_struct *tty, struct serial_struct *ss);
static void ti_handle_new_msr(struct ti_port *tport, u8 msr);
static void ti_stop_read(struct ti_port *tport, struct tty_struct *tty);
static int ti_restart_read(struct ti_port *tport, struct tty_struct *tty);
-static int ti_command_out_sync(struct ti_device *tdev, __u8 command,
- __u16 moduleid, __u16 value, __u8 *data, int size);
-static int ti_command_in_sync(struct ti_device *tdev, __u8 command,
- __u16 moduleid, __u16 value, __u8 *data, int size);
+static int ti_command_out_sync(struct usb_device *udev, u8 command,
+ u16 moduleid, u16 value, void *data, int size);
+static int ti_command_in_sync(struct usb_device *udev, u8 command,
+ u16 moduleid, u16 value, void *data, int size);
+static int ti_port_cmd_out(struct usb_serial_port *port, u8 command,
+ u16 value, void *data, int size);
+static int ti_port_cmd_in(struct usb_serial_port *port, u8 command,
+ u16 value, void *data, int size);
static int ti_write_byte(struct usb_serial_port *port, struct ti_device *tdev,
unsigned long addr, u8 mask, u8 byte);
static int ti_download_firmware(struct ti_device *tdev);
-static int closing_wait = TI_DEFAULT_CLOSING_WAIT;
-
static const struct usb_device_id ti_id_table_3410[] = {
{ USB_DEVICE(TI_VENDOR_ID, TI_3410_PRODUCT_ID) },
{ USB_DEVICE(TI_VENDOR_ID, TI_3410_EZ430_ID) },
.throttle = ti_throttle,
.unthrottle = ti_unthrottle,
.get_serial = ti_get_serial_info,
- .set_serial = ti_set_serial_info,
.set_termios = ti_set_termios,
.tiocmget = ti_tiocmget,
.tiocmset = ti_tiocmset,
.throttle = ti_throttle,
.unthrottle = ti_unthrottle,
.get_serial = ti_get_serial_info,
- .set_serial = ti_set_serial_info,
.set_termios = ti_set_termios,
.tiocmget = ti_tiocmget,
.tiocmset = ti_tiocmset,
MODULE_FIRMWARE("moxa/moxa-1150.fw");
MODULE_FIRMWARE("moxa/moxa-1151.fw");
-module_param(closing_wait, int, S_IRUGO | S_IWUSR);
-MODULE_PARM_DESC(closing_wait,
- "Maximum wait for data to drain in close, in .01 secs, default is 4000");
-
MODULE_DEVICE_TABLE(usb, ti_id_table_combined);
module_usb_serial_driver(serial_drivers, ti_id_table_combined);
tport->tp_uart_base_addr = TI_UART1_BASE_ADDR;
else
tport->tp_uart_base_addr = TI_UART2_BASE_ADDR;
- port->port.closing_wait = msecs_to_jiffies(10 * closing_wait);
tport->tp_port = port;
tport->tp_tdev = usb_get_serial_data(port->serial);
usb_set_serial_port_data(port, tport);
- port->port.drain_delay = 3;
+ /*
+ * The TUSB5052 LSR does not tell when the transmitter shift register
+ * has emptied so add a one-character drain delay.
+ */
+ if (!tport->tp_tdev->td_is_3410)
+ port->port.drain_delay = 1;
return 0;
}
struct ti_device *tdev;
struct usb_device *dev;
struct urb *urb;
- int port_number;
int status;
u16 open_settings;
if (mutex_lock_interruptible(&tdev->td_open_close_lock))
return -ERESTARTSYS;
- port_number = port->port_number;
-
tport->tp_msr = 0;
tport->tp_shadow_mcr |= (TI_MCR_RTS | TI_MCR_DTR);
if (tty)
ti_set_termios(tty, port, &tty->termios);
- status = ti_command_out_sync(tdev, TI_OPEN_PORT,
- (__u8)(TI_UART1_PORT + port_number), open_settings, NULL, 0);
+ status = ti_port_cmd_out(port, TI_OPEN_PORT, open_settings, NULL, 0);
if (status) {
dev_err(&port->dev, "%s - cannot send open command, %d\n",
__func__, status);
goto unlink_int_urb;
}
- status = ti_command_out_sync(tdev, TI_START_PORT,
- (__u8)(TI_UART1_PORT + port_number), 0, NULL, 0);
+ status = ti_port_cmd_out(port, TI_START_PORT, 0, NULL, 0);
if (status) {
dev_err(&port->dev, "%s - cannot send start command, %d\n",
__func__, status);
goto unlink_int_urb;
}
- status = ti_command_out_sync(tdev, TI_PURGE_PORT,
- (__u8)(TI_UART1_PORT + port_number), TI_PURGE_INPUT, NULL, 0);
+ status = ti_port_cmd_out(port, TI_PURGE_PORT, TI_PURGE_INPUT, NULL, 0);
if (status) {
dev_err(&port->dev, "%s - cannot clear input buffers, %d\n",
__func__, status);
goto unlink_int_urb;
}
- status = ti_command_out_sync(tdev, TI_PURGE_PORT,
- (__u8)(TI_UART1_PORT + port_number), TI_PURGE_OUTPUT, NULL, 0);
+ status = ti_port_cmd_out(port, TI_PURGE_PORT, TI_PURGE_OUTPUT, NULL, 0);
if (status) {
dev_err(&port->dev, "%s - cannot clear output buffers, %d\n",
__func__, status);
if (tty)
ti_set_termios(tty, port, &tty->termios);
- status = ti_command_out_sync(tdev, TI_OPEN_PORT,
- (__u8)(TI_UART1_PORT + port_number), open_settings, NULL, 0);
+ status = ti_port_cmd_out(port, TI_OPEN_PORT, open_settings, NULL, 0);
if (status) {
dev_err(&port->dev, "%s - cannot send open command (2), %d\n",
__func__, status);
goto unlink_int_urb;
}
- status = ti_command_out_sync(tdev, TI_START_PORT,
- (__u8)(TI_UART1_PORT + port_number), 0, NULL, 0);
+ status = ti_port_cmd_out(port, TI_START_PORT, 0, NULL, 0);
if (status) {
dev_err(&port->dev, "%s - cannot send start command (2), %d\n",
__func__, status);
{
struct ti_device *tdev;
struct ti_port *tport;
- int port_number;
int status;
unsigned long flags;
kfifo_reset_out(&port->write_fifo);
spin_unlock_irqrestore(&tport->tp_lock, flags);
- port_number = port->port_number;
-
- status = ti_command_out_sync(tdev, TI_CLOSE_PORT,
- (__u8)(TI_UART1_PORT + port_number), 0, NULL, 0);
+ status = ti_port_cmd_out(port, TI_CLOSE_PORT, 0, NULL, 0);
if (status)
dev_err(&port->dev,
"%s - cannot send close port command, %d\n"
static bool ti_tx_empty(struct usb_serial_port *port)
{
struct ti_port *tport = usb_get_serial_port_data(port);
+ u8 lsr, mask;
int ret;
- u8 lsr;
+
+ /*
+ * TUSB5052 does not have the TEMT bit to tell if the shift register
+ * is empty.
+ */
+ if (tport->tp_tdev->td_is_3410)
+ mask = TI_LSR_TX_EMPTY_BOTH;
+ else
+ mask = TI_LSR_TX_EMPTY;
ret = ti_get_lsr(tport, &lsr);
- if (!ret && !(lsr & TI_LSR_TX_EMPTY))
+ if (!ret && !(lsr & mask))
return false;
return true;
struct ti_uart_config *config;
int baud;
int status;
- int port_number = port->port_number;
unsigned int mcr;
u16 wbaudrate;
u16 wflags = 0;
switch (C_CSIZE(tty)) {
case CS5:
- config->bDataBits = TI_UART_5_DATA_BITS;
- break;
+ config->bDataBits = TI_UART_5_DATA_BITS;
+ break;
case CS6:
- config->bDataBits = TI_UART_6_DATA_BITS;
- break;
+ config->bDataBits = TI_UART_6_DATA_BITS;
+ break;
case CS7:
- config->bDataBits = TI_UART_7_DATA_BITS;
- break;
+ config->bDataBits = TI_UART_7_DATA_BITS;
+ break;
default:
case CS8:
- config->bDataBits = TI_UART_8_DATA_BITS;
- break;
+ config->bDataBits = TI_UART_8_DATA_BITS;
+ break;
}
/* CMSPAR isn't supported by this driver */
config->wBaudRate = cpu_to_be16(wbaudrate);
config->wFlags = cpu_to_be16(wflags);
- status = ti_command_out_sync(tport->tp_tdev, TI_SET_CONFIG,
- (__u8)(TI_UART1_PORT + port_number), 0, (__u8 *)config,
- sizeof(*config));
+ status = ti_port_cmd_out(port, TI_SET_CONFIG, 0, config,
+ sizeof(*config));
if (status)
dev_err(&port->dev, "%s - cannot set config on port %d, %d\n",
- __func__, port_number, status);
+ __func__, port->port_number, status);
/* SET_CONFIG asserts RTS and DTR, reset them correctly */
mcr = tport->tp_shadow_mcr;
mcr &= ~(TI_MCR_DTR | TI_MCR_RTS);
status = ti_set_mcr(tport, mcr);
if (status)
- dev_err(&port->dev,
- "%s - cannot set modem control on port %d, %d\n",
- __func__, port_number, status);
+ dev_err(&port->dev, "%s - cannot set modem control on port %d, %d\n",
+ __func__, port->port_number, status);
kfree(config);
}
static int ti_get_lsr(struct ti_port *tport, u8 *lsr)
{
int size, status;
- struct ti_device *tdev = tport->tp_tdev;
struct usb_serial_port *port = tport->tp_port;
- int port_number = port->port_number;
struct ti_port_status *data;
size = sizeof(struct ti_port_status);
if (!data)
return -ENOMEM;
- status = ti_command_in_sync(tdev, TI_GET_PORT_STATUS,
- (__u8)(TI_UART1_PORT+port_number), 0, (__u8 *)data, size);
+ status = ti_port_cmd_in(port, TI_GET_PORT_STATUS, 0, data, size);
if (status) {
dev_err(&port->dev,
"%s - get port status command failed, %d\n",
}
-static int ti_get_serial_info(struct tty_struct *tty,
- struct serial_struct *ss)
+static void ti_get_serial_info(struct tty_struct *tty, struct serial_struct *ss)
{
struct usb_serial_port *port = tty->driver_data;
struct ti_port *tport = usb_get_serial_port_data(port);
- unsigned cwait;
-
- cwait = port->port.closing_wait;
- if (cwait != ASYNC_CLOSING_WAIT_NONE)
- cwait = jiffies_to_msecs(cwait) / 10;
- ss->type = PORT_16550A;
- ss->line = port->minor;
- ss->port = port->port_number;
- ss->xmit_fifo_size = kfifo_size(&port->write_fifo);
ss->baud_base = tport->tp_tdev->td_is_3410 ? 921600 : 460800;
- ss->closing_wait = cwait;
- return 0;
-}
-
-
-static int ti_set_serial_info(struct tty_struct *tty,
- struct serial_struct *ss)
-{
- struct usb_serial_port *port = tty->driver_data;
- struct ti_port *tport = usb_get_serial_port_data(port);
- unsigned cwait;
-
- cwait = ss->closing_wait;
- if (cwait != ASYNC_CLOSING_WAIT_NONE)
- cwait = msecs_to_jiffies(10 * ss->closing_wait);
-
- tport->tp_port->port.closing_wait = cwait;
-
- return 0;
}
return status;
}
-
-static int ti_command_out_sync(struct ti_device *tdev, __u8 command,
- __u16 moduleid, __u16 value, __u8 *data, int size)
+static int ti_command_out_sync(struct usb_device *udev, u8 command,
+ u16 moduleid, u16 value, void *data, int size)
{
int status;
- status = usb_control_msg(tdev->td_serial->dev,
- usb_sndctrlpipe(tdev->td_serial->dev, 0), command,
- (USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT),
- value, moduleid, data, size, 1000);
-
+ status = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), command,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+ value, moduleid, data, size, 1000);
if (status < 0)
return status;
return 0;
}
-
-static int ti_command_in_sync(struct ti_device *tdev, __u8 command,
- __u16 moduleid, __u16 value, __u8 *data, int size)
+static int ti_command_in_sync(struct usb_device *udev, u8 command,
+ u16 moduleid, u16 value, void *data, int size)
{
int status;
- status = usb_control_msg(tdev->td_serial->dev,
- usb_rcvctrlpipe(tdev->td_serial->dev, 0), command,
- (USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN),
- value, moduleid, data, size, 1000);
-
+ status = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), command,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ value, moduleid, data, size, 1000);
if (status == size)
status = 0;
else if (status >= 0)
return status;
}
+static int ti_port_cmd_out(struct usb_serial_port *port, u8 command,
+ u16 value, void *data, int size)
+{
+ return ti_command_out_sync(port->serial->dev, command,
+ TI_UART1_PORT + port->port_number,
+ value, data, size);
+}
+
+static int ti_port_cmd_in(struct usb_serial_port *port, u8 command,
+ u16 value, void *data, int size)
+{
+ return ti_command_in_sync(port->serial->dev, command,
+ TI_UART1_PORT + port->port_number,
+ value, data, size);
+}
static int ti_write_byte(struct usb_serial_port *port,
struct ti_device *tdev, unsigned long addr,
data->bData[0] = mask;
data->bData[1] = byte;
- status = ti_command_out_sync(tdev, TI_WRITE_DATA, TI_RAM_PORT, 0,
- (__u8 *)data, size);
-
+ status = ti_command_out_sync(port->serial->dev, TI_WRITE_DATA,
+ TI_RAM_PORT, 0, data, size);
if (status < 0)
dev_err(&port->dev, "%s - failed, %d\n", __func__, status);
static int upd78f0730_tiocmget(struct tty_struct *tty)
{
- struct device *dev = tty->dev;
struct upd78f0730_port_private *private;
struct usb_serial_port *port = tty->driver_data;
int signals;
res = ((signals & UPD78F0730_DTR) ? TIOCM_DTR : 0) |
((signals & UPD78F0730_RTS) ? TIOCM_RTS : 0);
- dev_dbg(dev, "%s - res = %x\n", __func__, res);
+ dev_dbg(&port->dev, "%s - res = %x\n", __func__, res);
return res;
}
static int upd78f0730_tiocmset(struct tty_struct *tty,
unsigned int set, unsigned int clear)
{
- struct device *dev = tty->dev;
struct usb_serial_port *port = tty->driver_data;
struct upd78f0730_port_private *private;
struct upd78f0730_set_dtr_rts request;
+ struct device *dev = &port->dev;
int res;
private = usb_get_serial_port_data(port);
static void upd78f0730_break_ctl(struct tty_struct *tty, int break_state)
{
- struct device *dev = tty->dev;
struct upd78f0730_port_private *private;
struct usb_serial_port *port = tty->driver_data;
struct upd78f0730_set_dtr_rts request;
+ struct device *dev = &port->dev;
private = usb_get_serial_port_data(port);
serial->minors_reserved = 0;
}
+int usb_serial_claim_interface(struct usb_serial *serial, struct usb_interface *intf)
+{
+ struct usb_driver *driver = serial->type->usb_driver;
+ int ret;
+
+ if (serial->sibling)
+ return -EBUSY;
+
+ ret = usb_driver_claim_interface(driver, intf, serial);
+ if (ret) {
+ dev_err(&serial->interface->dev,
+ "failed to claim sibling interface: %d\n", ret);
+ return ret;
+ }
+
+ serial->sibling = intf;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(usb_serial_claim_interface);
+
+static void release_sibling(struct usb_serial *serial, struct usb_interface *intf)
+{
+ struct usb_driver *driver = serial->type->usb_driver;
+ struct usb_interface *sibling;
+
+ if (!serial->sibling)
+ return;
+
+ if (intf == serial->sibling)
+ sibling = serial->interface;
+ else
+ sibling = serial->sibling;
+
+ usb_set_intfdata(sibling, NULL);
+ usb_driver_release_interface(driver, sibling);
+}
+
static void destroy_serial(struct kref *kref)
{
struct usb_serial *serial;
{
struct usb_serial_port *port = tty->driver_data;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
return tty_port_open(&port->port, tty, filp);
}
{
struct usb_serial_port *port = tty->driver_data;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
tty_port_hangup(&port->port);
}
{
struct usb_serial_port *port = tty->driver_data;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
tty_port_close(&port->port, tty, filp);
}
struct usb_serial *serial;
struct module *owner;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
/* The console is magical. Do not hang up the console hardware
* or there will be tears.
if (port->serial->dev->state == USB_STATE_NOTATTACHED)
goto exit;
- dev_dbg(tty->dev, "%s - %d byte(s)\n", __func__, count);
+ dev_dbg(&port->dev, "%s - %d byte(s)\n", __func__, count);
retval = port->serial->type->write(tty, port, buf, count);
if (retval < 0)
{
struct usb_serial_port *port = tty->driver_data;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
return port->serial->type->write_room(tty);
}
struct usb_serial_port *port = tty->driver_data;
struct usb_serial *serial = port->serial;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
if (serial->disconnected)
return 0;
struct usb_serial_port *port = tty->driver_data;
struct usb_serial *serial = port->serial;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
if (!port->serial->type->wait_until_sent)
return;
{
struct usb_serial_port *port = tty->driver_data;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
if (port->serial->type->throttle)
port->serial->type->throttle(tty);
{
struct usb_serial_port *port = tty->driver_data;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
if (port->serial->type->unthrottle)
port->serial->type->unthrottle(tty);
static int serial_get_serial(struct tty_struct *tty, struct serial_struct *ss)
{
struct usb_serial_port *port = tty->driver_data;
+ struct tty_port *tport = &port->port;
+ unsigned int close_delay, closing_wait;
+
+ mutex_lock(&tport->mutex);
+
+ close_delay = jiffies_to_msecs(tport->close_delay) / 10;
+ closing_wait = tport->closing_wait;
+ if (closing_wait != ASYNC_CLOSING_WAIT_NONE)
+ closing_wait = jiffies_to_msecs(closing_wait) / 10;
+
+ ss->line = port->minor;
+ ss->close_delay = close_delay;
+ ss->closing_wait = closing_wait;
if (port->serial->type->get_serial)
- return port->serial->type->get_serial(tty, ss);
- return -ENOTTY;
+ port->serial->type->get_serial(tty, ss);
+
+ mutex_unlock(&tport->mutex);
+
+ return 0;
}
static int serial_set_serial(struct tty_struct *tty, struct serial_struct *ss)
{
struct usb_serial_port *port = tty->driver_data;
+ struct tty_port *tport = &port->port;
+ unsigned int close_delay, closing_wait;
+ int ret = 0;
+
+ close_delay = msecs_to_jiffies(ss->close_delay * 10);
+ closing_wait = ss->closing_wait;
+ if (closing_wait != ASYNC_CLOSING_WAIT_NONE)
+ closing_wait = msecs_to_jiffies(closing_wait * 10);
+
+ mutex_lock(&tport->mutex);
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ if (close_delay != tport->close_delay ||
+ closing_wait != tport->closing_wait) {
+ ret = -EPERM;
+ goto out_unlock;
+ }
+ }
- if (port->serial->type->set_serial)
- return port->serial->type->set_serial(tty, ss);
- return -ENOTTY;
+ if (port->serial->type->set_serial) {
+ ret = port->serial->type->set_serial(tty, ss);
+ if (ret)
+ goto out_unlock;
+ }
+
+ tport->close_delay = close_delay;
+ tport->closing_wait = closing_wait;
+out_unlock:
+ mutex_unlock(&tport->mutex);
+
+ return ret;
}
static int serial_ioctl(struct tty_struct *tty,
struct usb_serial_port *port = tty->driver_data;
int retval = -ENOIOCTLCMD;
- dev_dbg(tty->dev, "%s - cmd 0x%04x\n", __func__, cmd);
+ dev_dbg(&port->dev, "%s - cmd 0x%04x\n", __func__, cmd);
switch (cmd) {
case TIOCMIWAIT:
{
struct usb_serial_port *port = tty->driver_data;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
if (port->serial->type->set_termios)
port->serial->type->set_termios(tty, port, old);
{
struct usb_serial_port *port = tty->driver_data;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
if (port->serial->type->break_ctl)
port->serial->type->break_ctl(tty, break_state);
{
struct usb_serial_port *port = tty->driver_data;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
if (port->serial->type->tiocmget)
return port->serial->type->tiocmget(tty);
- return -EINVAL;
+ return -ENOTTY;
}
static int serial_tiocmset(struct tty_struct *tty,
{
struct usb_serial_port *port = tty->driver_data;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
if (port->serial->type->tiocmset)
return port->serial->type->tiocmset(tty, set, clear);
- return -EINVAL;
+ return -ENOTTY;
}
static int serial_get_icount(struct tty_struct *tty,
{
struct usb_serial_port *port = tty->driver_data;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
if (port->serial->type->get_icount)
return port->serial->type->get_icount(tty, icount);
- return -EINVAL;
+ return -ENOTTY;
}
/*
.shutdown = serial_port_shutdown,
};
-static void find_endpoints(struct usb_serial *serial,
- struct usb_serial_endpoints *epds)
+static void store_endpoint(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds,
+ struct usb_endpoint_descriptor *epd)
{
struct device *dev = &serial->interface->dev;
+ u8 addr = epd->bEndpointAddress;
+
+ if (usb_endpoint_is_bulk_in(epd)) {
+ if (epds->num_bulk_in == ARRAY_SIZE(epds->bulk_in))
+ return;
+ dev_dbg(dev, "found bulk in endpoint %02x\n", addr);
+ epds->bulk_in[epds->num_bulk_in++] = epd;
+ } else if (usb_endpoint_is_bulk_out(epd)) {
+ if (epds->num_bulk_out == ARRAY_SIZE(epds->bulk_out))
+ return;
+ dev_dbg(dev, "found bulk out endpoint %02x\n", addr);
+ epds->bulk_out[epds->num_bulk_out++] = epd;
+ } else if (usb_endpoint_is_int_in(epd)) {
+ if (epds->num_interrupt_in == ARRAY_SIZE(epds->interrupt_in))
+ return;
+ dev_dbg(dev, "found interrupt in endpoint %02x\n", addr);
+ epds->interrupt_in[epds->num_interrupt_in++] = epd;
+ } else if (usb_endpoint_is_int_out(epd)) {
+ if (epds->num_interrupt_out == ARRAY_SIZE(epds->interrupt_out))
+ return;
+ dev_dbg(dev, "found interrupt out endpoint %02x\n", addr);
+ epds->interrupt_out[epds->num_interrupt_out++] = epd;
+ }
+}
+
+static void find_endpoints(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds,
+ struct usb_interface *intf)
+{
struct usb_host_interface *iface_desc;
struct usb_endpoint_descriptor *epd;
unsigned int i;
- BUILD_BUG_ON(ARRAY_SIZE(epds->bulk_in) < USB_MAXENDPOINTS / 2);
- BUILD_BUG_ON(ARRAY_SIZE(epds->bulk_out) < USB_MAXENDPOINTS / 2);
- BUILD_BUG_ON(ARRAY_SIZE(epds->interrupt_in) < USB_MAXENDPOINTS / 2);
- BUILD_BUG_ON(ARRAY_SIZE(epds->interrupt_out) < USB_MAXENDPOINTS / 2);
-
- iface_desc = serial->interface->cur_altsetting;
+ iface_desc = intf->cur_altsetting;
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
epd = &iface_desc->endpoint[i].desc;
-
- if (usb_endpoint_is_bulk_in(epd)) {
- dev_dbg(dev, "found bulk in on endpoint %u\n", i);
- epds->bulk_in[epds->num_bulk_in++] = epd;
- } else if (usb_endpoint_is_bulk_out(epd)) {
- dev_dbg(dev, "found bulk out on endpoint %u\n", i);
- epds->bulk_out[epds->num_bulk_out++] = epd;
- } else if (usb_endpoint_is_int_in(epd)) {
- dev_dbg(dev, "found interrupt in on endpoint %u\n", i);
- epds->interrupt_in[epds->num_interrupt_in++] = epd;
- } else if (usb_endpoint_is_int_out(epd)) {
- dev_dbg(dev, "found interrupt out on endpoint %u\n", i);
- epds->interrupt_out[epds->num_interrupt_out++] = epd;
- }
+ store_endpoint(serial, epds, epd);
}
}
if (retval) {
dev_dbg(ddev, "sub driver rejected device\n");
- goto err_put_serial;
+ goto err_release_sibling;
}
}
epds = kzalloc(sizeof(*epds), GFP_KERNEL);
if (!epds) {
retval = -ENOMEM;
- goto err_put_serial;
+ goto err_release_sibling;
}
- find_endpoints(serial, epds);
+ find_endpoints(serial, epds, interface);
+ if (serial->sibling)
+ find_endpoints(serial, epds, serial->sibling);
if (epds->num_bulk_in < type->num_bulk_in ||
epds->num_bulk_out < type->num_bulk_out ||
err_free_epds:
kfree(epds);
-err_put_serial:
+err_release_sibling:
+ release_sibling(serial, interface);
usb_serial_put(serial);
err_put_module:
module_put(type->driver.owner);
struct usb_serial_port *port;
struct tty_struct *tty;
+ /* sibling interface is cleaning up */
+ if (!serial)
+ return;
+
usb_serial_console_disconnect(serial);
mutex_lock(&serial->disc_mutex);
if (serial->type->disconnect)
serial->type->disconnect(serial);
+ release_sibling(serial, interface);
+
/* let the last holder of this object cause it to be cleaned up */
usb_serial_put(serial);
dev_info(dev, "device disconnected\n");
int usb_serial_suspend(struct usb_interface *intf, pm_message_t message)
{
struct usb_serial *serial = usb_get_intfdata(intf);
- int i, r = 0;
+ int i, r;
- serial->suspending = 1;
+ /* suspend when called for first sibling interface */
+ if (serial->suspend_count++)
+ return 0;
/*
* serial->type->suspend() MUST return 0 in system sleep context,
if (serial->type->suspend) {
r = serial->type->suspend(serial, message);
if (r < 0) {
- serial->suspending = 0;
- goto err_out;
+ serial->suspend_count--;
+ return r;
}
}
for (i = 0; i < serial->num_ports; ++i)
usb_serial_port_poison_urbs(serial->port[i]);
-err_out:
- return r;
+
+ return 0;
}
EXPORT_SYMBOL(usb_serial_suspend);
struct usb_serial *serial = usb_get_intfdata(intf);
int rv;
+ /* resume when called for last sibling interface */
+ if (--serial->suspend_count)
+ return 0;
+
usb_serial_unpoison_port_urbs(serial);
- serial->suspending = 0;
if (serial->type->resume)
rv = serial->type->resume(serial);
else
struct usb_serial *serial = usb_get_intfdata(intf);
int rv;
+ /* resume when called for last sibling interface */
+ if (--serial->suspend_count)
+ return 0;
+
usb_serial_unpoison_port_urbs(serial);
- serial->suspending = 0;
if (serial->type->reset_resume) {
rv = serial->type->reset_resume(serial);
} else {
extern int usb_wwan_tiocmget(struct tty_struct *tty);
extern int usb_wwan_tiocmset(struct tty_struct *tty,
unsigned int set, unsigned int clear);
-extern int usb_wwan_get_serial_info(struct tty_struct *tty,
- struct serial_struct *ss);
-extern int usb_wwan_set_serial_info(struct tty_struct *tty,
- struct serial_struct *ss);
extern int usb_wwan_write(struct tty_struct *tty, struct usb_serial_port *port,
const unsigned char *buf, int count);
extern int usb_wwan_chars_in_buffer(struct tty_struct *tty);
}
EXPORT_SYMBOL(usb_wwan_tiocmset);
-int usb_wwan_get_serial_info(struct tty_struct *tty,
- struct serial_struct *ss)
-{
- struct usb_serial_port *port = tty->driver_data;
-
- ss->line = port->minor;
- ss->port = port->port_number;
- ss->baud_base = tty_get_baud_rate(port->port.tty);
- ss->close_delay = port->port.close_delay / 10;
- ss->closing_wait = port->port.closing_wait == ASYNC_CLOSING_WAIT_NONE ?
- ASYNC_CLOSING_WAIT_NONE :
- port->port.closing_wait / 10;
- return 0;
-}
-EXPORT_SYMBOL(usb_wwan_get_serial_info);
-
-int usb_wwan_set_serial_info(struct tty_struct *tty,
- struct serial_struct *ss)
-{
- struct usb_serial_port *port = tty->driver_data;
- unsigned int closing_wait, close_delay;
- int retval = 0;
-
- close_delay = ss->close_delay * 10;
- closing_wait = ss->closing_wait == ASYNC_CLOSING_WAIT_NONE ?
- ASYNC_CLOSING_WAIT_NONE : ss->closing_wait * 10;
-
- mutex_lock(&port->port.mutex);
-
- if (!capable(CAP_SYS_ADMIN)) {
- if ((close_delay != port->port.close_delay) ||
- (closing_wait != port->port.closing_wait))
- retval = -EPERM;
- else
- retval = -EOPNOTSUPP;
- } else {
- port->port.close_delay = close_delay;
- port->port.closing_wait = closing_wait;
- }
-
- mutex_unlock(&port->port.mutex);
- return retval;
-}
-EXPORT_SYMBOL(usb_wwan_set_serial_info);
-
int usb_wwan_write(struct tty_struct *tty, struct usb_serial_port *port,
const unsigned char *buf, int count)
{
static int whiteheat_open(struct tty_struct *tty,
struct usb_serial_port *port);
static void whiteheat_close(struct usb_serial_port *port);
-static int whiteheat_get_serial(struct tty_struct *tty,
+static void whiteheat_get_serial(struct tty_struct *tty,
struct serial_struct *ss);
static void whiteheat_set_termios(struct tty_struct *tty,
struct usb_serial_port *port, struct ktermios *old);
#define COMMAND_PORT 4
#define COMMAND_TIMEOUT (2*HZ) /* 2 second timeout for a command */
#define COMMAND_TIMEOUT_MS 2000
-#define CLOSING_DELAY (30 * HZ)
/*****************************************************************************
}
-static int whiteheat_get_serial(struct tty_struct *tty,
- struct serial_struct *ss)
+static void whiteheat_get_serial(struct tty_struct *tty, struct serial_struct *ss)
{
- struct usb_serial_port *port = tty->driver_data;
-
- ss->type = PORT_16654;
- ss->line = port->minor;
- ss->port = port->port_number;
- ss->xmit_fifo_size = kfifo_size(&port->write_fifo);
- ss->custom_divisor = 0;
ss->baud_base = 460800;
- ss->close_delay = CLOSING_DELAY;
- ss->closing_wait = CLOSING_DELAY;
-
- return 0;
}
* MaxLinear/Exar USB to Serial driver
*
* Copyright (c) 2020 Manivannan Sadhasivam <mani@kernel.org>
+ * Copyright (c) 2021 Johan Hovold <johan@kernel.org>
*
* Based on the initial driver written by Patong Yang:
*
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/usb.h>
+#include <linux/usb/cdc.h>
#include <linux/usb/serial.h>
struct xr_txrx_clk_mask {
#define XR21V141X_MIN_SPEED 46U
#define XR21V141X_MAX_SPEED XR_INT_OSC_HZ
-/* USB Requests */
-#define XR21V141X_SET_REQ 0
-#define XR21V141X_GET_REQ 1
+/* XR21V141X register blocks */
+#define XR21V141X_UART_REG_BLOCK 0
+#define XR21V141X_UM_REG_BLOCK 4
+#define XR21V141X_UART_CUSTOM_BLOCK 0x66
+/* XR21V141X UART registers */
#define XR21V141X_CLOCK_DIVISOR_0 0x04
#define XR21V141X_CLOCK_DIVISOR_1 0x05
#define XR21V141X_CLOCK_DIVISOR_2 0x06
#define XR21V141X_TX_CLOCK_MASK_1 0x08
#define XR21V141X_RX_CLOCK_MASK_0 0x09
#define XR21V141X_RX_CLOCK_MASK_1 0x0a
+#define XR21V141X_REG_FORMAT 0x0b
-/* XR21V141X register blocks */
-#define XR21V141X_UART_REG_BLOCK 0
-#define XR21V141X_UM_REG_BLOCK 4
-#define XR21V141X_UART_CUSTOM_BLOCK 0x66
-
-/* XR21V141X UART Manager Registers */
+/* XR21V141X UART Manager registers */
#define XR21V141X_UM_FIFO_ENABLE_REG 0x10
#define XR21V141X_UM_ENABLE_TX_FIFO 0x01
#define XR21V141X_UM_ENABLE_RX_FIFO 0x02
#define XR21V141X_UM_RX_FIFO_RESET 0x18
#define XR21V141X_UM_TX_FIFO_RESET 0x1c
-#define XR21V141X_UART_ENABLE_TX 0x1
-#define XR21V141X_UART_ENABLE_RX 0x2
-
-#define XR21V141X_UART_MODE_RI BIT(0)
-#define XR21V141X_UART_MODE_CD BIT(1)
-#define XR21V141X_UART_MODE_DSR BIT(2)
-#define XR21V141X_UART_MODE_DTR BIT(3)
-#define XR21V141X_UART_MODE_CTS BIT(4)
-#define XR21V141X_UART_MODE_RTS BIT(5)
-
-#define XR21V141X_UART_BREAK_ON 0xff
-#define XR21V141X_UART_BREAK_OFF 0
-
-#define XR21V141X_UART_DATA_MASK GENMASK(3, 0)
-#define XR21V141X_UART_DATA_7 0x7
-#define XR21V141X_UART_DATA_8 0x8
-
-#define XR21V141X_UART_PARITY_MASK GENMASK(6, 4)
-#define XR21V141X_UART_PARITY_SHIFT 4
-#define XR21V141X_UART_PARITY_NONE (0x0 << XR21V141X_UART_PARITY_SHIFT)
-#define XR21V141X_UART_PARITY_ODD (0x1 << XR21V141X_UART_PARITY_SHIFT)
-#define XR21V141X_UART_PARITY_EVEN (0x2 << XR21V141X_UART_PARITY_SHIFT)
-#define XR21V141X_UART_PARITY_MARK (0x3 << XR21V141X_UART_PARITY_SHIFT)
-#define XR21V141X_UART_PARITY_SPACE (0x4 << XR21V141X_UART_PARITY_SHIFT)
-
-#define XR21V141X_UART_STOP_MASK BIT(7)
-#define XR21V141X_UART_STOP_SHIFT 7
-#define XR21V141X_UART_STOP_1 (0x0 << XR21V141X_UART_STOP_SHIFT)
-#define XR21V141X_UART_STOP_2 (0x1 << XR21V141X_UART_STOP_SHIFT)
-
-#define XR21V141X_UART_FLOW_MODE_NONE 0x0
-#define XR21V141X_UART_FLOW_MODE_HW 0x1
-#define XR21V141X_UART_FLOW_MODE_SW 0x2
-
-#define XR21V141X_UART_MODE_GPIO_MASK GENMASK(2, 0)
-#define XR21V141X_UART_MODE_RTS_CTS 0x1
-#define XR21V141X_UART_MODE_DTR_DSR 0x2
-#define XR21V141X_UART_MODE_RS485 0x3
-#define XR21V141X_UART_MODE_RS485_ADDR 0x4
-
-#define XR21V141X_REG_ENABLE 0x03
-#define XR21V141X_REG_FORMAT 0x0b
-#define XR21V141X_REG_FLOW_CTRL 0x0c
-#define XR21V141X_REG_XON_CHAR 0x10
-#define XR21V141X_REG_XOFF_CHAR 0x11
-#define XR21V141X_REG_LOOPBACK 0x12
-#define XR21V141X_REG_TX_BREAK 0x14
-#define XR21V141X_REG_RS845_DELAY 0x15
-#define XR21V141X_REG_GPIO_MODE 0x1a
-#define XR21V141X_REG_GPIO_DIR 0x1b
-#define XR21V141X_REG_GPIO_INT_MASK 0x1c
-#define XR21V141X_REG_GPIO_SET 0x1d
-#define XR21V141X_REG_GPIO_CLR 0x1e
-#define XR21V141X_REG_GPIO_STATUS 0x1f
-
-static int xr_set_reg(struct usb_serial_port *port, u8 block, u8 reg, u8 val)
+#define XR_UART_ENABLE_TX 0x1
+#define XR_UART_ENABLE_RX 0x2
+
+#define XR_GPIO_RI BIT(0)
+#define XR_GPIO_CD BIT(1)
+#define XR_GPIO_DSR BIT(2)
+#define XR_GPIO_DTR BIT(3)
+#define XR_GPIO_CTS BIT(4)
+#define XR_GPIO_RTS BIT(5)
+#define XR_GPIO_CLK BIT(6)
+#define XR_GPIO_XEN BIT(7)
+#define XR_GPIO_TXT BIT(8)
+#define XR_GPIO_RXT BIT(9)
+
+#define XR_UART_DATA_MASK GENMASK(3, 0)
+#define XR_UART_DATA_7 0x7
+#define XR_UART_DATA_8 0x8
+
+#define XR_UART_PARITY_MASK GENMASK(6, 4)
+#define XR_UART_PARITY_SHIFT 4
+#define XR_UART_PARITY_NONE (0x0 << XR_UART_PARITY_SHIFT)
+#define XR_UART_PARITY_ODD (0x1 << XR_UART_PARITY_SHIFT)
+#define XR_UART_PARITY_EVEN (0x2 << XR_UART_PARITY_SHIFT)
+#define XR_UART_PARITY_MARK (0x3 << XR_UART_PARITY_SHIFT)
+#define XR_UART_PARITY_SPACE (0x4 << XR_UART_PARITY_SHIFT)
+
+#define XR_UART_STOP_MASK BIT(7)
+#define XR_UART_STOP_SHIFT 7
+#define XR_UART_STOP_1 (0x0 << XR_UART_STOP_SHIFT)
+#define XR_UART_STOP_2 (0x1 << XR_UART_STOP_SHIFT)
+
+#define XR_UART_FLOW_MODE_NONE 0x0
+#define XR_UART_FLOW_MODE_HW 0x1
+#define XR_UART_FLOW_MODE_SW 0x2
+
+#define XR_GPIO_MODE_SEL_MASK GENMASK(2, 0)
+#define XR_GPIO_MODE_SEL_RTS_CTS 0x1
+#define XR_GPIO_MODE_SEL_DTR_DSR 0x2
+#define XR_GPIO_MODE_SEL_RS485 0x3
+#define XR_GPIO_MODE_SEL_RS485_ADDR 0x4
+#define XR_GPIO_MODE_TX_TOGGLE 0x100
+#define XR_GPIO_MODE_RX_TOGGLE 0x200
+
+#define XR_FIFO_RESET 0x1
+
+#define XR_CUSTOM_DRIVER_ACTIVE 0x1
+
+static int xr21v141x_uart_enable(struct usb_serial_port *port);
+static int xr21v141x_uart_disable(struct usb_serial_port *port);
+static int xr21v141x_fifo_reset(struct usb_serial_port *port);
+static void xr21v141x_set_line_settings(struct tty_struct *tty,
+ struct usb_serial_port *port, struct ktermios *old_termios);
+
+struct xr_type {
+ int reg_width;
+ u8 reg_recipient;
+ u8 set_reg;
+ u8 get_reg;
+
+ u16 uart_enable;
+ u16 flow_control;
+ u16 xon_char;
+ u16 xoff_char;
+ u16 tx_break;
+ u16 gpio_mode;
+ u16 gpio_direction;
+ u16 gpio_set;
+ u16 gpio_clear;
+ u16 gpio_status;
+ u16 tx_fifo_reset;
+ u16 rx_fifo_reset;
+ u16 custom_driver;
+
+ bool have_5_6_bit_mode;
+ bool have_xmit_toggle;
+
+ int (*enable)(struct usb_serial_port *port);
+ int (*disable)(struct usb_serial_port *port);
+ int (*fifo_reset)(struct usb_serial_port *port);
+ void (*set_line_settings)(struct tty_struct *tty,
+ struct usb_serial_port *port,
+ struct ktermios *old_termios);
+};
+
+enum xr_type_id {
+ XR21V141X,
+ XR21B142X,
+ XR21B1411,
+ XR2280X,
+ XR_TYPE_COUNT,
+};
+
+static const struct xr_type xr_types[] = {
+ [XR21V141X] = {
+ .reg_width = 8,
+ .reg_recipient = USB_RECIP_DEVICE,
+ .set_reg = 0x00,
+ .get_reg = 0x01,
+
+ .uart_enable = 0x03,
+ .flow_control = 0x0c,
+ .xon_char = 0x10,
+ .xoff_char = 0x11,
+ .tx_break = 0x14,
+ .gpio_mode = 0x1a,
+ .gpio_direction = 0x1b,
+ .gpio_set = 0x1d,
+ .gpio_clear = 0x1e,
+ .gpio_status = 0x1f,
+
+ .enable = xr21v141x_uart_enable,
+ .disable = xr21v141x_uart_disable,
+ .fifo_reset = xr21v141x_fifo_reset,
+ .set_line_settings = xr21v141x_set_line_settings,
+ },
+ [XR21B142X] = {
+ .reg_width = 16,
+ .reg_recipient = USB_RECIP_INTERFACE,
+ .set_reg = 0x00,
+ .get_reg = 0x00,
+
+ .uart_enable = 0x00,
+ .flow_control = 0x06,
+ .xon_char = 0x07,
+ .xoff_char = 0x08,
+ .tx_break = 0x0a,
+ .gpio_mode = 0x0c,
+ .gpio_direction = 0x0d,
+ .gpio_set = 0x0e,
+ .gpio_clear = 0x0f,
+ .gpio_status = 0x10,
+ .tx_fifo_reset = 0x40,
+ .rx_fifo_reset = 0x43,
+ .custom_driver = 0x60,
+
+ .have_5_6_bit_mode = true,
+ .have_xmit_toggle = true,
+ },
+ [XR21B1411] = {
+ .reg_width = 12,
+ .reg_recipient = USB_RECIP_DEVICE,
+ .set_reg = 0x00,
+ .get_reg = 0x01,
+
+ .uart_enable = 0xc00,
+ .flow_control = 0xc06,
+ .xon_char = 0xc07,
+ .xoff_char = 0xc08,
+ .tx_break = 0xc0a,
+ .gpio_mode = 0xc0c,
+ .gpio_direction = 0xc0d,
+ .gpio_set = 0xc0e,
+ .gpio_clear = 0xc0f,
+ .gpio_status = 0xc10,
+ .tx_fifo_reset = 0xc80,
+ .rx_fifo_reset = 0xcc0,
+ .custom_driver = 0x20d,
+ },
+ [XR2280X] = {
+ .reg_width = 16,
+ .reg_recipient = USB_RECIP_DEVICE,
+ .set_reg = 0x05,
+ .get_reg = 0x05,
+
+ .uart_enable = 0x40,
+ .flow_control = 0x46,
+ .xon_char = 0x47,
+ .xoff_char = 0x48,
+ .tx_break = 0x4a,
+ .gpio_mode = 0x4c,
+ .gpio_direction = 0x4d,
+ .gpio_set = 0x4e,
+ .gpio_clear = 0x4f,
+ .gpio_status = 0x50,
+ .tx_fifo_reset = 0x60,
+ .rx_fifo_reset = 0x63,
+ .custom_driver = 0x81,
+ },
+};
+
+struct xr_data {
+ const struct xr_type *type;
+ u8 channel; /* zero-based index or interface number */
+};
+
+static int xr_set_reg(struct usb_serial_port *port, u8 channel, u16 reg, u16 val)
{
+ struct xr_data *data = usb_get_serial_port_data(port);
+ const struct xr_type *type = data->type;
struct usb_serial *serial = port->serial;
int ret;
- ret = usb_control_msg(serial->dev,
- usb_sndctrlpipe(serial->dev, 0),
- XR21V141X_SET_REQ,
- USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
- val, reg | (block << 8), NULL, 0,
- USB_CTRL_SET_TIMEOUT);
+ ret = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+ type->set_reg,
+ USB_DIR_OUT | USB_TYPE_VENDOR | type->reg_recipient,
+ val, (channel << 8) | reg, NULL, 0,
+ USB_CTRL_SET_TIMEOUT);
if (ret < 0) {
dev_err(&port->dev, "Failed to set reg 0x%02x: %d\n", reg, ret);
return ret;
return 0;
}
-static int xr_get_reg(struct usb_serial_port *port, u8 block, u8 reg, u8 *val)
+static int xr_get_reg(struct usb_serial_port *port, u8 channel, u16 reg, u16 *val)
{
+ struct xr_data *data = usb_get_serial_port_data(port);
+ const struct xr_type *type = data->type;
struct usb_serial *serial = port->serial;
u8 *dmabuf;
- int ret;
+ int ret, len;
+
+ if (type->reg_width == 8)
+ len = 1;
+ else
+ len = 2;
- dmabuf = kmalloc(1, GFP_KERNEL);
+ dmabuf = kmalloc(len, GFP_KERNEL);
if (!dmabuf)
return -ENOMEM;
- ret = usb_control_msg(serial->dev,
- usb_rcvctrlpipe(serial->dev, 0),
- XR21V141X_GET_REQ,
- USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
- 0, reg | (block << 8), dmabuf, 1,
- USB_CTRL_GET_TIMEOUT);
- if (ret == 1) {
- *val = *dmabuf;
+ ret = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
+ type->get_reg,
+ USB_DIR_IN | USB_TYPE_VENDOR | type->reg_recipient,
+ 0, (channel << 8) | reg, dmabuf, len,
+ USB_CTRL_GET_TIMEOUT);
+ if (ret == len) {
+ if (len == 2)
+ *val = le16_to_cpup((__le16 *)dmabuf);
+ else
+ *val = *dmabuf;
ret = 0;
} else {
dev_err(&port->dev, "Failed to get reg 0x%02x: %d\n", reg, ret);
return ret;
}
-static int xr_set_reg_uart(struct usb_serial_port *port, u8 reg, u8 val)
+static int xr_set_reg_uart(struct usb_serial_port *port, u16 reg, u16 val)
{
- return xr_set_reg(port, XR21V141X_UART_REG_BLOCK, reg, val);
+ struct xr_data *data = usb_get_serial_port_data(port);
+
+ return xr_set_reg(port, data->channel, reg, val);
}
-static int xr_get_reg_uart(struct usb_serial_port *port, u8 reg, u8 *val)
+static int xr_get_reg_uart(struct usb_serial_port *port, u16 reg, u16 *val)
{
- return xr_get_reg(port, XR21V141X_UART_REG_BLOCK, reg, val);
+ struct xr_data *data = usb_get_serial_port_data(port);
+
+ return xr_get_reg(port, data->channel, reg, val);
}
-static int xr_set_reg_um(struct usb_serial_port *port, u8 reg, u8 val)
+static int xr_set_reg_um(struct usb_serial_port *port, u8 reg_base, u8 val)
{
+ struct xr_data *data = usb_get_serial_port_data(port);
+ u8 reg;
+
+ reg = reg_base + data->channel;
+
return xr_set_reg(port, XR21V141X_UM_REG_BLOCK, reg, val);
}
+static int __xr_uart_enable(struct usb_serial_port *port)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+
+ return xr_set_reg_uart(port, data->type->uart_enable,
+ XR_UART_ENABLE_TX | XR_UART_ENABLE_RX);
+}
+
+static int __xr_uart_disable(struct usb_serial_port *port)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+
+ return xr_set_reg_uart(port, data->type->uart_enable, 0);
+}
+
/*
* According to datasheet, below is the recommended sequence for enabling UART
* module in XR21V141X:
* Enable Tx and Rx
* Enable Rx FIFO
*/
-static int xr_uart_enable(struct usb_serial_port *port)
+static int xr21v141x_uart_enable(struct usb_serial_port *port)
{
int ret;
if (ret)
return ret;
- ret = xr_set_reg_uart(port, XR21V141X_REG_ENABLE,
- XR21V141X_UART_ENABLE_TX | XR21V141X_UART_ENABLE_RX);
+ ret = __xr_uart_enable(port);
if (ret)
return ret;
ret = xr_set_reg_um(port, XR21V141X_UM_FIFO_ENABLE_REG,
XR21V141X_UM_ENABLE_TX_FIFO | XR21V141X_UM_ENABLE_RX_FIFO);
-
if (ret)
- xr_set_reg_uart(port, XR21V141X_REG_ENABLE, 0);
+ __xr_uart_disable(port);
return ret;
}
-static int xr_uart_disable(struct usb_serial_port *port)
+static int xr21v141x_uart_disable(struct usb_serial_port *port)
{
int ret;
- ret = xr_set_reg_uart(port, XR21V141X_REG_ENABLE, 0);
+ ret = __xr_uart_disable(port);
if (ret)
return ret;
return ret;
}
+static int xr_uart_enable(struct usb_serial_port *port)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+
+ if (data->type->enable)
+ return data->type->enable(port);
+
+ return __xr_uart_enable(port);
+}
+
+static int xr_uart_disable(struct usb_serial_port *port)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+
+ if (data->type->disable)
+ return data->type->disable(port);
+
+ return __xr_uart_disable(port);
+}
+
+static int xr21v141x_fifo_reset(struct usb_serial_port *port)
+{
+ int ret;
+
+ ret = xr_set_reg_um(port, XR21V141X_UM_TX_FIFO_RESET, XR_FIFO_RESET);
+ if (ret)
+ return ret;
+
+ ret = xr_set_reg_um(port, XR21V141X_UM_RX_FIFO_RESET, XR_FIFO_RESET);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int xr_fifo_reset(struct usb_serial_port *port)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+ int ret;
+
+ if (data->type->fifo_reset)
+ return data->type->fifo_reset(port);
+
+ ret = xr_set_reg_uart(port, data->type->tx_fifo_reset, XR_FIFO_RESET);
+ if (ret)
+ return ret;
+
+ ret = xr_set_reg_uart(port, data->type->rx_fifo_reset, XR_FIFO_RESET);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
static int xr_tiocmget(struct tty_struct *tty)
{
struct usb_serial_port *port = tty->driver_data;
- u8 status;
+ struct xr_data *data = usb_get_serial_port_data(port);
+ u16 status;
int ret;
- ret = xr_get_reg_uart(port, XR21V141X_REG_GPIO_STATUS, &status);
+ ret = xr_get_reg_uart(port, data->type->gpio_status, &status);
if (ret)
return ret;
* Modem control pins are active low, so reading '0' means it is active
* and '1' means not active.
*/
- ret = ((status & XR21V141X_UART_MODE_DTR) ? 0 : TIOCM_DTR) |
- ((status & XR21V141X_UART_MODE_RTS) ? 0 : TIOCM_RTS) |
- ((status & XR21V141X_UART_MODE_CTS) ? 0 : TIOCM_CTS) |
- ((status & XR21V141X_UART_MODE_DSR) ? 0 : TIOCM_DSR) |
- ((status & XR21V141X_UART_MODE_RI) ? 0 : TIOCM_RI) |
- ((status & XR21V141X_UART_MODE_CD) ? 0 : TIOCM_CD);
+ ret = ((status & XR_GPIO_DTR) ? 0 : TIOCM_DTR) |
+ ((status & XR_GPIO_RTS) ? 0 : TIOCM_RTS) |
+ ((status & XR_GPIO_CTS) ? 0 : TIOCM_CTS) |
+ ((status & XR_GPIO_DSR) ? 0 : TIOCM_DSR) |
+ ((status & XR_GPIO_RI) ? 0 : TIOCM_RI) |
+ ((status & XR_GPIO_CD) ? 0 : TIOCM_CD);
return ret;
}
static int xr_tiocmset_port(struct usb_serial_port *port,
unsigned int set, unsigned int clear)
{
- u8 gpio_set = 0;
- u8 gpio_clr = 0;
+ struct xr_data *data = usb_get_serial_port_data(port);
+ const struct xr_type *type = data->type;
+ u16 gpio_set = 0;
+ u16 gpio_clr = 0;
int ret = 0;
/* Modem control pins are active low, so set & clr are swapped */
if (set & TIOCM_RTS)
- gpio_clr |= XR21V141X_UART_MODE_RTS;
+ gpio_clr |= XR_GPIO_RTS;
if (set & TIOCM_DTR)
- gpio_clr |= XR21V141X_UART_MODE_DTR;
+ gpio_clr |= XR_GPIO_DTR;
if (clear & TIOCM_RTS)
- gpio_set |= XR21V141X_UART_MODE_RTS;
+ gpio_set |= XR_GPIO_RTS;
if (clear & TIOCM_DTR)
- gpio_set |= XR21V141X_UART_MODE_DTR;
+ gpio_set |= XR_GPIO_DTR;
/* Writing '0' to gpio_{set/clr} bits has no effect, so no need to do */
if (gpio_clr)
- ret = xr_set_reg_uart(port, XR21V141X_REG_GPIO_CLR, gpio_clr);
+ ret = xr_set_reg_uart(port, type->gpio_clear, gpio_clr);
if (gpio_set)
- ret = xr_set_reg_uart(port, XR21V141X_REG_GPIO_SET, gpio_set);
+ ret = xr_set_reg_uart(port, type->gpio_set, gpio_set);
return ret;
}
static void xr_break_ctl(struct tty_struct *tty, int break_state)
{
struct usb_serial_port *port = tty->driver_data;
- u8 state;
+ struct xr_data *data = usb_get_serial_port_data(port);
+ const struct xr_type *type = data->type;
+ u16 state;
if (break_state == 0)
- state = XR21V141X_UART_BREAK_OFF;
+ state = 0;
else
- state = XR21V141X_UART_BREAK_ON;
+ state = GENMASK(type->reg_width - 1, 0);
+
+ dev_dbg(&port->dev, "Turning break %s\n", state == 0 ? "off" : "on");
- dev_dbg(&port->dev, "Turning break %s\n",
- state == XR21V141X_UART_BREAK_OFF ? "off" : "on");
- xr_set_reg_uart(port, XR21V141X_REG_TX_BREAK, state);
+ xr_set_reg_uart(port, type->tx_break, state);
}
/* Tx and Rx clock mask values obtained from section 3.3.4 of datasheet */
{ 0xfff, 0xffe, 0xffd },
};
-static int xr_set_baudrate(struct tty_struct *tty,
- struct usb_serial_port *port)
+static int xr21v141x_set_baudrate(struct tty_struct *tty, struct usb_serial_port *port)
{
u32 divisor, baud, idx;
u16 tx_mask, rx_mask;
struct usb_serial_port *port,
struct ktermios *old_termios)
{
- u8 flow, gpio_mode;
+ struct xr_data *data = usb_get_serial_port_data(port);
+ const struct xr_type *type = data->type;
+ u16 flow, gpio_mode;
int ret;
- ret = xr_get_reg_uart(port, XR21V141X_REG_GPIO_MODE, &gpio_mode);
+ ret = xr_get_reg_uart(port, type->gpio_mode, &gpio_mode);
if (ret)
return;
+ /*
+ * According to the datasheets, the UART needs to be disabled while
+ * writing to the FLOW_CONTROL register (XR21V141X), or any register
+ * but GPIO_SET, GPIO_CLEAR, TX_BREAK and ERROR_STATUS (XR21B142X).
+ */
+ xr_uart_disable(port);
+
/* Set GPIO mode for controlling the pins manually by default. */
- gpio_mode &= ~XR21V141X_UART_MODE_GPIO_MASK;
+ gpio_mode &= ~XR_GPIO_MODE_SEL_MASK;
if (C_CRTSCTS(tty) && C_BAUD(tty) != B0) {
dev_dbg(&port->dev, "Enabling hardware flow ctrl\n");
- gpio_mode |= XR21V141X_UART_MODE_RTS_CTS;
- flow = XR21V141X_UART_FLOW_MODE_HW;
+ gpio_mode |= XR_GPIO_MODE_SEL_RTS_CTS;
+ flow = XR_UART_FLOW_MODE_HW;
} else if (I_IXON(tty)) {
u8 start_char = START_CHAR(tty);
u8 stop_char = STOP_CHAR(tty);
dev_dbg(&port->dev, "Enabling sw flow ctrl\n");
- flow = XR21V141X_UART_FLOW_MODE_SW;
+ flow = XR_UART_FLOW_MODE_SW;
- xr_set_reg_uart(port, XR21V141X_REG_XON_CHAR, start_char);
- xr_set_reg_uart(port, XR21V141X_REG_XOFF_CHAR, stop_char);
+ xr_set_reg_uart(port, type->xon_char, start_char);
+ xr_set_reg_uart(port, type->xoff_char, stop_char);
} else {
dev_dbg(&port->dev, "Disabling flow ctrl\n");
- flow = XR21V141X_UART_FLOW_MODE_NONE;
+ flow = XR_UART_FLOW_MODE_NONE;
}
- /*
- * As per the datasheet, UART needs to be disabled while writing to
- * FLOW_CONTROL register.
- */
- xr_uart_disable(port);
- xr_set_reg_uart(port, XR21V141X_REG_FLOW_CTRL, flow);
- xr_uart_enable(port);
+ xr_set_reg_uart(port, type->flow_control, flow);
+ xr_set_reg_uart(port, type->gpio_mode, gpio_mode);
- xr_set_reg_uart(port, XR21V141X_REG_GPIO_MODE, gpio_mode);
+ xr_uart_enable(port);
if (C_BAUD(tty) == B0)
xr_dtr_rts(port, 0);
xr_dtr_rts(port, 1);
}
-static void xr_set_termios(struct tty_struct *tty,
- struct usb_serial_port *port,
- struct ktermios *old_termios)
+static void xr21v141x_set_line_settings(struct tty_struct *tty,
+ struct usb_serial_port *port, struct ktermios *old_termios)
{
struct ktermios *termios = &tty->termios;
u8 bits = 0;
int ret;
if (!old_termios || (tty->termios.c_ospeed != old_termios->c_ospeed))
- xr_set_baudrate(tty, port);
+ xr21v141x_set_baudrate(tty, port);
switch (C_CSIZE(tty)) {
case CS5:
if (old_termios)
termios->c_cflag |= old_termios->c_cflag & CSIZE;
else
- bits |= XR21V141X_UART_DATA_8;
+ termios->c_cflag |= CS8;
+
+ if (C_CSIZE(tty) == CS7)
+ bits |= XR_UART_DATA_7;
+ else
+ bits |= XR_UART_DATA_8;
break;
case CS7:
- bits |= XR21V141X_UART_DATA_7;
+ bits |= XR_UART_DATA_7;
break;
case CS8:
default:
- bits |= XR21V141X_UART_DATA_8;
+ bits |= XR_UART_DATA_8;
break;
}
if (C_PARENB(tty)) {
if (C_CMSPAR(tty)) {
if (C_PARODD(tty))
- bits |= XR21V141X_UART_PARITY_MARK;
+ bits |= XR_UART_PARITY_MARK;
else
- bits |= XR21V141X_UART_PARITY_SPACE;
+ bits |= XR_UART_PARITY_SPACE;
} else {
if (C_PARODD(tty))
- bits |= XR21V141X_UART_PARITY_ODD;
+ bits |= XR_UART_PARITY_ODD;
else
- bits |= XR21V141X_UART_PARITY_EVEN;
+ bits |= XR_UART_PARITY_EVEN;
}
}
if (C_CSTOPB(tty))
- bits |= XR21V141X_UART_STOP_2;
+ bits |= XR_UART_STOP_2;
else
- bits |= XR21V141X_UART_STOP_1;
+ bits |= XR_UART_STOP_1;
ret = xr_set_reg_uart(port, XR21V141X_REG_FORMAT, bits);
if (ret)
return;
+}
+
+static void xr_cdc_set_line_coding(struct tty_struct *tty,
+ struct usb_serial_port *port, struct ktermios *old_termios)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+ struct usb_host_interface *alt = port->serial->interface->cur_altsetting;
+ struct usb_device *udev = port->serial->dev;
+ struct usb_cdc_line_coding *lc;
+ int ret;
+
+ lc = kzalloc(sizeof(*lc), GFP_KERNEL);
+ if (!lc)
+ return;
+
+ if (tty->termios.c_ospeed)
+ lc->dwDTERate = cpu_to_le32(tty->termios.c_ospeed);
+ else if (old_termios)
+ lc->dwDTERate = cpu_to_le32(old_termios->c_ospeed);
+ else
+ lc->dwDTERate = cpu_to_le32(9600);
+
+ if (C_CSTOPB(tty))
+ lc->bCharFormat = USB_CDC_2_STOP_BITS;
+ else
+ lc->bCharFormat = USB_CDC_1_STOP_BITS;
+
+ if (C_PARENB(tty)) {
+ if (C_CMSPAR(tty)) {
+ if (C_PARODD(tty))
+ lc->bParityType = USB_CDC_MARK_PARITY;
+ else
+ lc->bParityType = USB_CDC_SPACE_PARITY;
+ } else {
+ if (C_PARODD(tty))
+ lc->bParityType = USB_CDC_ODD_PARITY;
+ else
+ lc->bParityType = USB_CDC_EVEN_PARITY;
+ }
+ } else {
+ lc->bParityType = USB_CDC_NO_PARITY;
+ }
+
+ if (!data->type->have_5_6_bit_mode &&
+ (C_CSIZE(tty) == CS5 || C_CSIZE(tty) == CS6)) {
+ tty->termios.c_cflag &= ~CSIZE;
+ if (old_termios)
+ tty->termios.c_cflag |= old_termios->c_cflag & CSIZE;
+ else
+ tty->termios.c_cflag |= CS8;
+ }
+
+ switch (C_CSIZE(tty)) {
+ case CS5:
+ lc->bDataBits = 5;
+ break;
+ case CS6:
+ lc->bDataBits = 6;
+ break;
+ case CS7:
+ lc->bDataBits = 7;
+ break;
+ case CS8:
+ default:
+ lc->bDataBits = 8;
+ break;
+ }
+
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ USB_CDC_REQ_SET_LINE_CODING,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, alt->desc.bInterfaceNumber,
+ lc, sizeof(*lc), USB_CTRL_SET_TIMEOUT);
+ if (ret < 0)
+ dev_err(&port->dev, "Failed to set line coding: %d\n", ret);
+
+ kfree(lc);
+}
+
+static void xr_set_termios(struct tty_struct *tty,
+ struct usb_serial_port *port, struct ktermios *old_termios)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+
+ /*
+ * XR21V141X does not have a CUSTOM_DRIVER flag and always enters CDC
+ * mode upon receiving CDC requests.
+ */
+ if (data->type->set_line_settings)
+ data->type->set_line_settings(tty, port, old_termios);
+ else
+ xr_cdc_set_line_coding(tty, port, old_termios);
xr_set_flow_mode(tty, port, old_termios);
}
static int xr_open(struct tty_struct *tty, struct usb_serial_port *port)
{
- u8 gpio_dir;
int ret;
+ ret = xr_fifo_reset(port);
+ if (ret)
+ return ret;
+
ret = xr_uart_enable(port);
if (ret) {
dev_err(&port->dev, "Failed to enable UART\n");
return ret;
}
- /*
- * Configure DTR and RTS as outputs and RI, CD, DSR and CTS as
- * inputs.
- */
- gpio_dir = XR21V141X_UART_MODE_DTR | XR21V141X_UART_MODE_RTS;
- xr_set_reg_uart(port, XR21V141X_REG_GPIO_DIR, gpio_dir);
-
/* Setup termios */
if (tty)
xr_set_termios(tty, port, NULL);
static int xr_probe(struct usb_serial *serial, const struct usb_device_id *id)
{
- /* Don't bind to control interface */
- if (serial->interface->cur_altsetting->desc.bInterfaceNumber == 0)
+ struct usb_interface *control = serial->interface;
+ struct usb_host_interface *alt = control->cur_altsetting;
+ struct usb_cdc_parsed_header hdrs;
+ struct usb_cdc_union_desc *desc;
+ struct usb_interface *data;
+ int ret;
+
+ ret = cdc_parse_cdc_header(&hdrs, control, alt->extra, alt->extralen);
+ if (ret < 0)
+ return -ENODEV;
+
+ desc = hdrs.usb_cdc_union_desc;
+ if (!desc)
+ return -ENODEV;
+
+ data = usb_ifnum_to_if(serial->dev, desc->bSlaveInterface0);
+ if (!data)
return -ENODEV;
+ ret = usb_serial_claim_interface(serial, data);
+ if (ret)
+ return ret;
+
+ usb_set_serial_data(serial, (void *)id->driver_info);
+
+ return 0;
+}
+
+static int xr_gpio_init(struct usb_serial_port *port, const struct xr_type *type)
+{
+ u16 mask, mode;
+ int ret;
+
+ /*
+ * Configure all pins as GPIO except for Receive and Transmit Toggle.
+ */
+ mode = 0;
+ if (type->have_xmit_toggle)
+ mode |= XR_GPIO_MODE_RX_TOGGLE | XR_GPIO_MODE_TX_TOGGLE;
+
+ ret = xr_set_reg_uart(port, type->gpio_mode, mode);
+ if (ret)
+ return ret;
+
+ /*
+ * Configure DTR and RTS as outputs and make sure they are deasserted
+ * (active low), and configure RI, CD, DSR and CTS as inputs.
+ */
+ mask = XR_GPIO_DTR | XR_GPIO_RTS;
+ ret = xr_set_reg_uart(port, type->gpio_direction, mask);
+ if (ret)
+ return ret;
+
+ ret = xr_set_reg_uart(port, type->gpio_set, mask);
+ if (ret)
+ return ret;
+
return 0;
}
+static int xr_port_probe(struct usb_serial_port *port)
+{
+ struct usb_interface_descriptor *desc;
+ const struct xr_type *type;
+ struct xr_data *data;
+ enum xr_type_id type_id;
+ int ret;
+
+ type_id = (int)(unsigned long)usb_get_serial_data(port->serial);
+ type = &xr_types[type_id];
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->type = type;
+
+ desc = &port->serial->interface->cur_altsetting->desc;
+ if (type_id == XR21V141X)
+ data->channel = desc->bInterfaceNumber / 2;
+ else
+ data->channel = desc->bInterfaceNumber;
+
+ usb_set_serial_port_data(port, data);
+
+ if (type->custom_driver) {
+ ret = xr_set_reg_uart(port, type->custom_driver,
+ XR_CUSTOM_DRIVER_ACTIVE);
+ if (ret)
+ goto err_free;
+ }
+
+ ret = xr_gpio_init(port, type);
+ if (ret)
+ goto err_free;
+
+ return 0;
+
+err_free:
+ kfree(data);
+
+ return ret;
+}
+
+static void xr_port_remove(struct usb_serial_port *port)
+{
+ struct xr_data *data = usb_get_serial_port_data(port);
+
+ kfree(data);
+}
+
+#define XR_DEVICE(vid, pid, type) \
+ USB_DEVICE_INTERFACE_CLASS((vid), (pid), USB_CLASS_COMM), \
+ .driver_info = (type)
+
static const struct usb_device_id id_table[] = {
- { USB_DEVICE(0x04e2, 0x1410) }, /* XR21V141X */
+ { XR_DEVICE(0x04e2, 0x1400, XR2280X) },
+ { XR_DEVICE(0x04e2, 0x1401, XR2280X) },
+ { XR_DEVICE(0x04e2, 0x1402, XR2280X) },
+ { XR_DEVICE(0x04e2, 0x1403, XR2280X) },
+ { XR_DEVICE(0x04e2, 0x1410, XR21V141X) },
+ { XR_DEVICE(0x04e2, 0x1411, XR21B1411) },
+ { XR_DEVICE(0x04e2, 0x1412, XR21V141X) },
+ { XR_DEVICE(0x04e2, 0x1414, XR21V141X) },
+ { XR_DEVICE(0x04e2, 0x1420, XR21B142X) },
+ { XR_DEVICE(0x04e2, 0x1422, XR21B142X) },
+ { XR_DEVICE(0x04e2, 0x1424, XR21B142X) },
{ }
};
MODULE_DEVICE_TABLE(usb, id_table);
.id_table = id_table,
.num_ports = 1,
.probe = xr_probe,
+ .port_probe = xr_port_probe,
+ .port_remove = xr_port_remove,
.open = xr_open,
.close = xr_close,
.break_ctl = xr_break_ctl,
if (reply[0] != 0x50 && reply[1] != 0) {
usb_stor_dbg(us, "Gah! write return code: %02x %02x\n",
reply[0], reply[1]);
- result = USB_STOR_TRANSPORT_ERROR;
goto leave;
}
source "drivers/usb/typec/ucsi/Kconfig"
+source "drivers/usb/typec/tipd/Kconfig"
+
config TYPEC_HD3SS3220
tristate "TI HD3SS3220 Type-C DRP Port controller driver"
depends on I2C
If you choose to build this driver as a dynamically linked module, the
module will be called hd3ss3220.ko.
-config TYPEC_TPS6598X
- tristate "TI TPS6598x USB Power Delivery controller driver"
- depends on I2C
- select POWER_SUPPLY
- select REGMAP_I2C
- select USB_ROLE_SWITCH
- help
- Say Y or M here if your system has TI TPS65982 or TPS65983 USB Power
- Delivery controller.
-
- If you choose to build this driver as a dynamically linked module, the
- module will be called tps6598x.ko.
-
config TYPEC_STUSB160X
tristate "STMicroelectronics STUSB160x Type-C controller driver"
depends on I2C
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_TYPEC) += typec.o
-typec-y := class.o mux.o bus.o
+typec-y := class.o mux.o bus.o port-mapper.o
obj-$(CONFIG_TYPEC) += altmodes/
obj-$(CONFIG_TYPEC_TCPM) += tcpm/
obj-$(CONFIG_TYPEC_UCSI) += ucsi/
+obj-$(CONFIG_TYPEC_TPS6598X) += tipd/
obj-$(CONFIG_TYPEC_HD3SS3220) += hd3ss3220.o
-obj-$(CONFIG_TYPEC_TPS6598X) += tps6598x.o
obj-$(CONFIG_TYPEC_QCOM_PMIC) += qcom-pmic-typec.o
obj-$(CONFIG_TYPEC_STUSB160X) += stusb160x.o
obj-$(CONFIG_TYPEC) += mux/
#include <linux/usb/pd_vdo.h>
#include "bus.h"
+#include "class.h"
+#include "mux.h"
static inline int
typec_altmode_set_mux(struct altmode *alt, unsigned long conf, void *data)
#define __USB_TYPEC_ALTMODE_H__
#include <linux/usb/typec_altmode.h>
-#include <linux/usb/typec_mux.h>
struct bus_type;
+struct typec_mux;
struct altmode {
unsigned int id;
extern struct bus_type typec_bus;
extern const struct device_type typec_altmode_dev_type;
-extern const struct device_type typec_port_dev_type;
#define is_typec_altmode(_dev_) (_dev_->type == &typec_altmode_dev_type)
-#define is_typec_port(_dev_) (_dev_->type == &typec_port_dev_type)
-
-extern struct class typec_mux_class;
-
-struct typec_switch {
- struct device dev;
- typec_switch_set_fn_t set;
-};
-
-struct typec_mux {
- struct device dev;
- typec_mux_set_fn_t set;
-};
-
-#define to_typec_switch(_dev_) container_of(_dev_, struct typec_switch, dev)
-#define to_typec_mux(_dev_) container_of(_dev_, struct typec_mux, dev)
#endif /* __USB_TYPEC_ALTMODE_H__ */
* Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
*/
-#include <linux/device.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/usb/pd_vdo.h>
+#include <linux/usb/typec_mux.h>
#include "bus.h"
+#include "class.h"
-struct typec_plug {
- struct device dev;
- enum typec_plug_index index;
- struct ida mode_ids;
- int num_altmodes;
-};
-
-struct typec_cable {
- struct device dev;
- enum typec_plug_type type;
- struct usb_pd_identity *identity;
- unsigned int active:1;
- u16 pd_revision; /* 0300H = "3.0" */
-};
-
-struct typec_partner {
- struct device dev;
- unsigned int usb_pd:1;
- struct usb_pd_identity *identity;
- enum typec_accessory accessory;
- struct ida mode_ids;
- int num_altmodes;
- u16 pd_revision; /* 0300H = "3.0" */
- enum usb_pd_svdm_ver svdm_version;
-};
+static DEFINE_IDA(typec_index_ida);
-struct typec_port {
- unsigned int id;
- struct device dev;
- struct ida mode_ids;
-
- int prefer_role;
- enum typec_data_role data_role;
- enum typec_role pwr_role;
- enum typec_role vconn_role;
- enum typec_pwr_opmode pwr_opmode;
- enum typec_port_type port_type;
- struct mutex port_type_lock;
-
- enum typec_orientation orientation;
- struct typec_switch *sw;
- struct typec_mux *mux;
-
- const struct typec_capability *cap;
- const struct typec_operations *ops;
+struct class typec_class = {
+ .name = "typec",
+ .owner = THIS_MODULE,
};
-#define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev)
-#define to_typec_plug(_dev_) container_of(_dev_, struct typec_plug, dev)
-#define to_typec_cable(_dev_) container_of(_dev_, struct typec_cable, dev)
-#define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, dev)
-
-static const struct device_type typec_partner_dev_type;
-static const struct device_type typec_cable_dev_type;
-static const struct device_type typec_plug_dev_type;
-
-#define is_typec_partner(_dev_) (_dev_->type == &typec_partner_dev_type)
-#define is_typec_cable(_dev_) (_dev_->type == &typec_cable_dev_type)
-#define is_typec_plug(_dev_) (_dev_->type == &typec_plug_dev_type)
-
-static DEFINE_IDA(typec_index_ida);
-static struct class *typec_class;
-
/* ------------------------------------------------------------------------- */
/* Common attributes */
/* Plug alt modes need a class to generate udev events. */
if (is_typec_plug(parent))
- alt->adev.dev.class = typec_class;
+ alt->adev.dev.class = &typec_class;
ret = device_register(&alt->adev.dev);
if (ret) {
kfree(partner);
}
-static const struct device_type typec_partner_dev_type = {
+const struct device_type typec_partner_dev_type = {
.name = "typec_partner",
.groups = typec_partner_groups,
.release = typec_partner_release,
partner->identity = desc->identity;
}
- partner->dev.class = typec_class;
+ partner->dev.class = &typec_class;
partner->dev.parent = &port->dev;
partner->dev.type = &typec_partner_dev_type;
dev_set_name(&partner->dev, "%s-partner", dev_name(&port->dev));
NULL
};
-static const struct device_type typec_plug_dev_type = {
+const struct device_type typec_plug_dev_type = {
.name = "typec_plug",
.groups = typec_plug_groups,
.release = typec_plug_release,
ida_init(&plug->mode_ids);
plug->num_altmodes = -1;
plug->index = desc->index;
- plug->dev.class = typec_class;
+ plug->dev.class = &typec_class;
plug->dev.parent = &cable->dev;
plug->dev.type = &typec_plug_dev_type;
dev_set_name(&plug->dev, "%s-%s", dev_name(cable->dev.parent), name);
kfree(cable);
}
-static const struct device_type typec_cable_dev_type = {
+const struct device_type typec_cable_dev_type = {
.name = "typec_cable",
.groups = typec_cable_groups,
.release = typec_cable_release,
cable->identity = desc->identity;
}
- cable->dev.class = typec_class;
+ cable->dev.class = &typec_class;
cable->dev.parent = &port->dev;
cable->dev.type = &typec_cable_dev_type;
dev_set_name(&cable->dev, "%s-cable", dev_name(&port->dev));
}
EXPORT_SYMBOL_GPL(typec_port_register_altmode);
+void typec_port_register_altmodes(struct typec_port *port,
+ const struct typec_altmode_ops *ops, void *drvdata,
+ struct typec_altmode **altmodes, size_t n)
+{
+ struct fwnode_handle *altmodes_node, *child;
+ struct typec_altmode_desc desc;
+ struct typec_altmode *alt;
+ size_t index = 0;
+ u32 svid, vdo;
+ int ret;
+
+ altmodes_node = device_get_named_child_node(&port->dev, "altmodes");
+ if (!altmodes_node)
+ return; /* No altmodes specified */
+
+ fwnode_for_each_child_node(altmodes_node, child) {
+ ret = fwnode_property_read_u32(child, "svid", &svid);
+ if (ret) {
+ dev_err(&port->dev, "Error reading svid for altmode %s\n",
+ fwnode_get_name(child));
+ continue;
+ }
+
+ ret = fwnode_property_read_u32(child, "vdo", &vdo);
+ if (ret) {
+ dev_err(&port->dev, "Error reading vdo for altmode %s\n",
+ fwnode_get_name(child));
+ continue;
+ }
+
+ if (index >= n) {
+ dev_err(&port->dev, "Error not enough space for altmode %s\n",
+ fwnode_get_name(child));
+ continue;
+ }
+
+ desc.svid = svid;
+ desc.vdo = vdo;
+ desc.mode = index + 1;
+ alt = typec_port_register_altmode(port, &desc);
+ if (IS_ERR(alt)) {
+ dev_err(&port->dev, "Error registering altmode %s\n",
+ fwnode_get_name(child));
+ continue;
+ }
+
+ alt->ops = ops;
+ typec_altmode_set_drvdata(alt, drvdata);
+ altmodes[index] = alt;
+ index++;
+ }
+}
+EXPORT_SYMBOL_GPL(typec_port_register_altmodes);
+
/**
* typec_register_port - Register a USB Type-C Port
* @parent: Parent device
ida_init(&port->mode_ids);
mutex_init(&port->port_type_lock);
+ mutex_init(&port->port_list_lock);
+ INIT_LIST_HEAD(&port->port_list);
port->id = id;
port->ops = cap->ops;
port->prefer_role = cap->prefer_role;
device_initialize(&port->dev);
- port->dev.class = typec_class;
+ port->dev.class = &typec_class;
port->dev.parent = parent;
port->dev.fwnode = cap->fwnode;
port->dev.type = &typec_port_dev_type;
return ERR_PTR(ret);
}
+ ret = typec_link_ports(port);
+ if (ret)
+ dev_warn(&port->dev, "failed to create symlinks (%d)\n", ret);
+
return port;
}
EXPORT_SYMBOL_GPL(typec_register_port);
*/
void typec_unregister_port(struct typec_port *port)
{
- if (!IS_ERR_OR_NULL(port))
+ if (!IS_ERR_OR_NULL(port)) {
+ typec_unlink_ports(port);
device_unregister(&port->dev);
+ }
}
EXPORT_SYMBOL_GPL(typec_unregister_port);
if (ret)
goto err_unregister_bus;
- typec_class = class_create(THIS_MODULE, "typec");
- if (IS_ERR(typec_class)) {
- ret = PTR_ERR(typec_class);
+ ret = class_register(&typec_class);
+ if (ret)
goto err_unregister_mux_class;
- }
return 0;
static void __exit typec_exit(void)
{
- class_destroy(typec_class);
+ class_unregister(&typec_class);
ida_destroy(&typec_index_ida);
bus_unregister(&typec_bus);
class_unregister(&typec_mux_class);
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __USB_TYPEC_CLASS__
+#define __USB_TYPEC_CLASS__
+
+#include <linux/device.h>
+#include <linux/usb/typec.h>
+
+struct typec_mux;
+struct typec_switch;
+
+struct typec_plug {
+ struct device dev;
+ enum typec_plug_index index;
+ struct ida mode_ids;
+ int num_altmodes;
+};
+
+struct typec_cable {
+ struct device dev;
+ enum typec_plug_type type;
+ struct usb_pd_identity *identity;
+ unsigned int active:1;
+ u16 pd_revision; /* 0300H = "3.0" */
+};
+
+struct typec_partner {
+ struct device dev;
+ unsigned int usb_pd:1;
+ struct usb_pd_identity *identity;
+ enum typec_accessory accessory;
+ struct ida mode_ids;
+ int num_altmodes;
+ u16 pd_revision; /* 0300H = "3.0" */
+ enum usb_pd_svdm_ver svdm_version;
+};
+
+struct typec_port {
+ unsigned int id;
+ struct device dev;
+ struct ida mode_ids;
+
+ int prefer_role;
+ enum typec_data_role data_role;
+ enum typec_role pwr_role;
+ enum typec_role vconn_role;
+ enum typec_pwr_opmode pwr_opmode;
+ enum typec_port_type port_type;
+ struct mutex port_type_lock;
+
+ enum typec_orientation orientation;
+ struct typec_switch *sw;
+ struct typec_mux *mux;
+
+ const struct typec_capability *cap;
+ const struct typec_operations *ops;
+
+ struct list_head port_list;
+ struct mutex port_list_lock; /* Port list lock */
+
+ void *pld;
+};
+
+#define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev)
+#define to_typec_plug(_dev_) container_of(_dev_, struct typec_plug, dev)
+#define to_typec_cable(_dev_) container_of(_dev_, struct typec_cable, dev)
+#define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, dev)
+
+extern const struct device_type typec_partner_dev_type;
+extern const struct device_type typec_cable_dev_type;
+extern const struct device_type typec_plug_dev_type;
+extern const struct device_type typec_port_dev_type;
+
+#define is_typec_partner(dev) ((dev)->type == &typec_partner_dev_type)
+#define is_typec_cable(dev) ((dev)->type == &typec_cable_dev_type)
+#define is_typec_plug(dev) ((dev)->type == &typec_plug_dev_type)
+#define is_typec_port(dev) ((dev)->type == &typec_port_dev_type)
+
+extern struct class typec_mux_class;
+extern struct class typec_class;
+
+int typec_link_ports(struct typec_port *connector);
+void typec_unlink_ports(struct typec_port *connector);
+
+#endif /* __USB_TYPEC_CLASS__ */
#include <linux/mutex.h>
#include <linux/property.h>
#include <linux/slab.h>
-#include <linux/usb/typec_mux.h>
-#include "bus.h"
+#include "class.h"
+#include "mux.h"
static bool dev_name_ends_with(struct device *dev, const char *suffix)
{
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __USB_TYPEC_MUX__
+#define __USB_TYPEC_MUX__
+
+#include <linux/usb/typec_mux.h>
+
+struct typec_switch {
+ struct device dev;
+ typec_switch_set_fn_t set;
+};
+
+struct typec_mux {
+ struct device dev;
+ typec_mux_set_fn_t set;
+};
+
+#define to_typec_switch(_dev_) container_of(_dev_, struct typec_switch, dev)
+#define to_typec_mux(_dev_) container_of(_dev_, struct typec_mux, dev)
+
+#endif /* __USB_TYPEC_MUX__ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB Type-C Connector Class Port Mapping Utility
+ *
+ * Copyright (C) 2021, Intel Corporation
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/usb.h>
+#include <linux/usb/typec.h>
+
+#include "class.h"
+
+struct port_node {
+ struct list_head list;
+ struct device *dev;
+ void *pld;
+};
+
+static int acpi_pld_match(const struct acpi_pld_info *pld1,
+ const struct acpi_pld_info *pld2)
+{
+ if (!pld1 || !pld2)
+ return 0;
+
+ /*
+ * To speed things up, first checking only the group_position. It seems
+ * to often have the first unique value in the _PLD.
+ */
+ if (pld1->group_position == pld2->group_position)
+ return !memcmp(pld1, pld2, sizeof(struct acpi_pld_info));
+
+ return 0;
+}
+
+static void *get_pld(struct device *dev)
+{
+#ifdef CONFIG_ACPI
+ struct acpi_pld_info *pld;
+ acpi_status status;
+
+ if (!has_acpi_companion(dev))
+ return NULL;
+
+ status = acpi_get_physical_device_location(ACPI_HANDLE(dev), &pld);
+ if (ACPI_FAILURE(status))
+ return NULL;
+
+ return pld;
+#else
+ return NULL;
+#endif
+}
+
+static void free_pld(void *pld)
+{
+#ifdef CONFIG_ACPI
+ ACPI_FREE(pld);
+#endif
+}
+
+static int __link_port(struct typec_port *con, struct port_node *node)
+{
+ int ret;
+
+ ret = sysfs_create_link(&node->dev->kobj, &con->dev.kobj, "connector");
+ if (ret)
+ return ret;
+
+ ret = sysfs_create_link(&con->dev.kobj, &node->dev->kobj,
+ dev_name(node->dev));
+ if (ret) {
+ sysfs_remove_link(&node->dev->kobj, "connector");
+ return ret;
+ }
+
+ list_add_tail(&node->list, &con->port_list);
+
+ return 0;
+}
+
+static int link_port(struct typec_port *con, struct port_node *node)
+{
+ int ret;
+
+ mutex_lock(&con->port_list_lock);
+ ret = __link_port(con, node);
+ mutex_unlock(&con->port_list_lock);
+
+ return ret;
+}
+
+static void __unlink_port(struct typec_port *con, struct port_node *node)
+{
+ sysfs_remove_link(&con->dev.kobj, dev_name(node->dev));
+ sysfs_remove_link(&node->dev->kobj, "connector");
+ list_del(&node->list);
+}
+
+static void unlink_port(struct typec_port *con, struct port_node *node)
+{
+ mutex_lock(&con->port_list_lock);
+ __unlink_port(con, node);
+ mutex_unlock(&con->port_list_lock);
+}
+
+static struct port_node *create_port_node(struct device *port)
+{
+ struct port_node *node;
+
+ node = kzalloc(sizeof(*node), GFP_KERNEL);
+ if (!node)
+ return ERR_PTR(-ENOMEM);
+
+ node->dev = get_device(port);
+ node->pld = get_pld(port);
+
+ return node;
+}
+
+static void remove_port_node(struct port_node *node)
+{
+ put_device(node->dev);
+ free_pld(node->pld);
+ kfree(node);
+}
+
+static int connector_match(struct device *dev, const void *data)
+{
+ const struct port_node *node = data;
+
+ if (!is_typec_port(dev))
+ return 0;
+
+ return acpi_pld_match(to_typec_port(dev)->pld, node->pld);
+}
+
+static struct device *find_connector(struct port_node *node)
+{
+ if (!node->pld)
+ return NULL;
+
+ return class_find_device(&typec_class, NULL, node, connector_match);
+}
+
+/**
+ * typec_link_port - Link a port to its connector
+ * @port: The port device
+ *
+ * Find the connector of @port and create symlink named "connector" for it.
+ * Returns 0 on success, or errno in case of a failure.
+ *
+ * NOTE. The function increments the reference count of @port on success.
+ */
+int typec_link_port(struct device *port)
+{
+ struct device *connector;
+ struct port_node *node;
+ int ret;
+
+ node = create_port_node(port);
+ if (IS_ERR(node))
+ return PTR_ERR(node);
+
+ connector = find_connector(node);
+ if (!connector) {
+ ret = 0;
+ goto remove_node;
+ }
+
+ ret = link_port(to_typec_port(connector), node);
+ if (ret)
+ goto put_connector;
+
+ return 0;
+
+put_connector:
+ put_device(connector);
+remove_node:
+ remove_port_node(node);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(typec_link_port);
+
+static int port_match_and_unlink(struct device *connector, void *port)
+{
+ struct port_node *node;
+ struct port_node *tmp;
+ int ret = 0;
+
+ if (!is_typec_port(connector))
+ return 0;
+
+ mutex_lock(&to_typec_port(connector)->port_list_lock);
+ list_for_each_entry_safe(node, tmp, &to_typec_port(connector)->port_list, list) {
+ ret = node->dev == port;
+ if (ret) {
+ unlink_port(to_typec_port(connector), node);
+ remove_port_node(node);
+ put_device(connector);
+ break;
+ }
+ }
+ mutex_unlock(&to_typec_port(connector)->port_list_lock);
+
+ return ret;
+}
+
+/**
+ * typec_unlink_port - Unlink port from its connector
+ * @port: The port device
+ *
+ * Removes the symlink "connector" and decrements the reference count of @port.
+ */
+void typec_unlink_port(struct device *port)
+{
+ class_for_each_device(&typec_class, NULL, port, port_match_and_unlink);
+}
+EXPORT_SYMBOL_GPL(typec_unlink_port);
+
+static int each_port(struct device *port, void *connector)
+{
+ struct port_node *node;
+ int ret;
+
+ node = create_port_node(port);
+ if (IS_ERR(node))
+ return PTR_ERR(node);
+
+ if (!connector_match(connector, node)) {
+ remove_port_node(node);
+ return 0;
+ }
+
+ ret = link_port(to_typec_port(connector), node);
+ if (ret) {
+ remove_port_node(node->pld);
+ return ret;
+ }
+
+ get_device(connector);
+
+ return 0;
+}
+
+int typec_link_ports(struct typec_port *con)
+{
+ int ret = 0;
+
+ con->pld = get_pld(&con->dev);
+ if (!con->pld)
+ return 0;
+
+ ret = usb_for_each_port(&con->dev, each_port);
+ if (ret)
+ typec_unlink_ports(con);
+
+ return ret;
+}
+
+void typec_unlink_ports(struct typec_port *con)
+{
+ struct port_node *node;
+ struct port_node *tmp;
+
+ mutex_lock(&con->port_list_lock);
+
+ list_for_each_entry_safe(node, tmp, &con->port_list, list) {
+ __unlink_port(con, node);
+ remove_port_node(node);
+ put_device(&con->dev);
+ }
+
+ mutex_unlock(&con->port_list_lock);
+
+ free_pld(con->pld);
+}
}
fwnode = device_get_named_child_node(chip->dev, "connector");
- if (IS_ERR(fwnode))
- return PTR_ERR(fwnode);
+ if (!fwnode)
+ return -ENODEV;
/*
* When both VDD and VSYS power supplies are present, the low power
mutex_init(&chip->logbuffer_lock);
snprintf(name, NAME_MAX, "fusb302-%s", dev_name(chip->dev));
- chip->dentry = debugfs_create_file(name, S_IFREG | 0444, usb_debug_root,
- chip, &fusb302_debug_fops);
+ chip->dentry = debugfs_create_dir(name, usb_debug_root);
+ debugfs_create_file("log", S_IFREG | 0444, chip->dentry, chip,
+ &fusb302_debug_fops);
}
static void fusb302_debugfs_exit(struct fusb302_chip *chip)
#define AUTO_DISCHARGE_PD_HEADROOM_MV 850
#define AUTO_DISCHARGE_PPS_HEADROOM_MV 1250
+#define tcpc_presenting_rd(reg, cc) \
+ (!(TCPC_ROLE_CTRL_DRP & (reg)) && \
+ (((reg) & (TCPC_ROLE_CTRL_## cc ##_MASK << TCPC_ROLE_CTRL_## cc ##_SHIFT)) == \
+ (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_## cc ##_SHIFT)))
+
struct tcpci {
struct device *dev;
enum typec_cc_status *cc1, enum typec_cc_status *cc2)
{
struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
- unsigned int reg;
+ unsigned int reg, role_control;
int ret;
+ ret = regmap_read(tcpci->regmap, TCPC_ROLE_CTRL, &role_control);
+ if (ret < 0)
+ return ret;
+
ret = regmap_read(tcpci->regmap, TCPC_CC_STATUS, ®);
if (ret < 0)
return ret;
*cc1 = tcpci_to_typec_cc((reg >> TCPC_CC_STATUS_CC1_SHIFT) &
TCPC_CC_STATUS_CC1_MASK,
- reg & TCPC_CC_STATUS_TERM);
+ reg & TCPC_CC_STATUS_TERM ||
+ tcpc_presenting_rd(role_control, CC1));
*cc2 = tcpci_to_typec_cc((reg >> TCPC_CC_STATUS_CC2_SHIFT) &
TCPC_CC_STATUS_CC2_MASK,
- reg & TCPC_CC_STATUS_TERM);
+ reg & TCPC_CC_STATUS_TERM ||
+ tcpc_presenting_rd(role_control, CC2));
return 0;
}
#define TCPC_TCPC_CTRL 0x19
#define TCPC_TCPC_CTRL_ORIENTATION BIT(0)
+#define PLUG_ORNT_CC1 0
+#define PLUG_ORNT_CC2 1
#define TCPC_TCPC_CTRL_BIST_TM BIT(1)
+#define TCPC_TCPC_CTRL_EN_LK4CONN_ALRT BIT(6)
#define TCPC_EXTENDED_STATUS 0x20
#define TCPC_EXTENDED_STATUS_VSAFE0V BIT(0)
#define TCPC_POWER_CTRL_VCONN_ENABLE BIT(0)
#define TCPC_POWER_CTRL_BLEED_DISCHARGE BIT(3)
#define TCPC_POWER_CTRL_AUTO_DISCHARGE BIT(4)
+#define TCPC_DIS_VOLT_ALRM BIT(5)
+#define TCPC_POWER_CTRL_VBUS_VOLT_MON BIT(6)
#define TCPC_FAST_ROLE_SWAP_EN BIT(7)
#define TCPC_CC_STATUS 0x1d
#define TCPC_CC_STATUS_TOGGLING BIT(5)
#define TCPC_CC_STATUS_TERM BIT(4)
+#define TCPC_CC_STATUS_TERM_RP 0
+#define TCPC_CC_STATUS_TERM_RD 1
+#define TCPC_CC_STATE_SRC_OPEN 0
#define TCPC_CC_STATUS_CC2_SHIFT 2
#define TCPC_CC_STATUS_CC2_MASK 0x3
#define TCPC_CC_STATUS_CC1_SHIFT 0
#define TCPC_CC_STATUS_CC1_MASK 0x3
#define TCPC_POWER_STATUS 0x1e
+#define TCPC_POWER_STATUS_DBG_ACC_CON BIT(7)
#define TCPC_POWER_STATUS_UNINIT BIT(6)
#define TCPC_POWER_STATUS_SOURCING_VBUS BIT(4)
#define TCPC_POWER_STATUS_VBUS_DET BIT(3)
#define TCPC_POWER_STATUS_VBUS_PRES BIT(2)
+#define TCPC_POWER_STATUS_SINKING_VBUS BIT(0)
#define TCPC_FAULT_STATUS 0x1f
#define TCPC_RX_DETECT 0x2f
#define TCPC_RX_DETECT_HARD_RESET BIT(5)
#define TCPC_RX_DETECT_SOP BIT(0)
+#define TCPC_RX_DETECT_SOP1 BIT(1)
+#define TCPC_RX_DETECT_SOP2 BIT(2)
+#define TCPC_RX_DETECT_DBG1 BIT(3)
+#define TCPC_RX_DETECT_DBG2 BIT(4)
#define TCPC_RX_BYTE_CNT 0x30
#define TCPC_RX_BUF_FRAME_TYPE 0x31
#define TCPC_TX_DATA 0x54 /* through 0x6f */
#define TCPC_VBUS_VOLTAGE 0x70
+#define TCPC_VBUS_VOLTAGE_MASK 0x3ff
+#define TCPC_VBUS_VOLTAGE_LSB_MV 25
#define TCPC_VBUS_SINK_DISCONNECT_THRESH 0x72
#define TCPC_VBUS_SINK_DISCONNECT_THRESH_LSB_MV 25
#define TCPC_VBUS_SINK_DISCONNECT_THRESH_MAX 0x3ff
regmap_reg_range(0x00, 0x95)
};
-const struct regmap_access_table max_tcpci_tcpci_write_table = {
+static const struct regmap_access_table max_tcpci_tcpci_write_table = {
.yes_ranges = max_tcpci_tcpci_range,
.n_yes_ranges = ARRAY_SIZE(max_tcpci_tcpci_range),
};
struct typec_altmode_desc altmode_desc[ALTMODE_DISCOVERY_MAX];
};
+/*
+ * @min_volt: Actual min voltage at the local port
+ * @req_min_volt: Requested min voltage to the port partner
+ * @max_volt: Actual max voltage at the local port
+ * @req_max_volt: Requested max voltage to the port partner
+ * @max_curr: Actual max current at the local port
+ * @req_max_curr: Requested max current of the port partner
+ * @req_out_volt: Requested output voltage to the port partner
+ * @req_op_curr: Requested operating current to the port partner
+ * @supported: Parter has atleast one APDO hence supports PPS
+ * @active: PPS mode is active
+ */
struct pd_pps_data {
u32 min_volt;
+ u32 req_min_volt;
u32 max_volt;
+ u32 req_max_volt;
u32 max_curr;
- u32 out_volt;
- u32 op_curr;
+ u32 req_max_curr;
+ u32 req_out_volt;
+ u32 req_op_curr;
bool supported;
bool active;
};
unsigned int operating_snk_mw;
bool update_sink_caps;
- /* Requested current / voltage */
+ /* Requested current / voltage to the port partner */
+ u32 req_current_limit;
+ u32 req_supply_voltage;
+ /* Actual current / voltage limit of the local port */
u32 current_limit;
u32 supply_voltage;
enum tcpm_ams next_ams;
bool in_ams;
+ /* Auto vbus discharge status */
+ bool auto_vbus_discharge_enabled;
+
+ /*
+ * When set, port requests PD_P_SNK_STDBY_MW upon entering SNK_DISCOVERY and
+ * the actual currrent limit after RX of PD_CTRL_PSRDY for PD link,
+ * SNK_READY for non-pd link.
+ */
+ bool slow_charger_loop;
#ifdef CONFIG_DEBUG_FS
struct dentry *dentry;
struct mutex logbuffer_lock; /* log buffer access lock */
(tcpm_port_is_sink(port) && \
((port)->cc1 == TYPEC_CC_RP_3_0 || (port)->cc2 == TYPEC_CC_RP_3_0))
+#define tcpm_wait_for_discharge(port) \
+ (((port)->auto_vbus_discharge_enabled && !(port)->vbus_vsafe0v) ? PD_T_SAFE_0V : 0)
+
static enum tcpm_state tcpm_default_state(struct tcpm_port *port)
{
if (port->port_type == TYPEC_PORT_DRP) {
mutex_init(&port->logbuffer_lock);
snprintf(name, NAME_MAX, "tcpm-%s", dev_name(port->dev));
- port->dentry = debugfs_create_file(name, S_IFREG | 0444, usb_debug_root,
- port, &tcpm_debug_fops);
+ port->dentry = debugfs_create_dir(name, usb_debug_root);
+ debugfs_create_file("log", S_IFREG | 0444, port->dentry, port,
+ &tcpm_debug_fops);
}
static void tcpm_debugfs_exit(struct tcpm_port *port)
return TYPEC_CC_RP_DEF;
}
-static int tcpm_ams_finish(struct tcpm_port *port)
+static void tcpm_ams_finish(struct tcpm_port *port)
{
- int ret = 0;
-
tcpm_log(port, "AMS %s finished", tcpm_ams_str[port->ams]);
if (port->pd_capable && port->pwr_role == TYPEC_SOURCE) {
port->in_ams = false;
port->ams = NONE_AMS;
-
- return ret;
}
static int tcpm_pd_transmit(struct tcpm_port *port,
case SNK_TRANSITION_SINK:
if (port->vbus_present) {
tcpm_set_current_limit(port,
- port->current_limit,
- port->supply_voltage);
+ port->req_current_limit,
+ port->req_supply_voltage);
port->explicit_contract = true;
tcpm_set_auto_vbus_discharge_threshold(port,
TYPEC_PWR_MODE_PD,
break;
case SNK_NEGOTIATE_PPS_CAPABILITIES:
/* Revert data back from any requested PPS updates */
- port->pps_data.out_volt = port->supply_voltage;
- port->pps_data.op_curr = port->current_limit;
+ port->pps_data.req_out_volt = port->supply_voltage;
+ port->pps_data.req_op_curr = port->current_limit;
port->pps_status = (type == PD_CTRL_WAIT ?
-EAGAIN : -EOPNOTSUPP);
break;
case SNK_NEGOTIATE_PPS_CAPABILITIES:
port->pps_data.active = true;
- port->supply_voltage = port->pps_data.out_volt;
- port->current_limit = port->pps_data.op_curr;
+ port->pps_data.min_volt = port->pps_data.req_min_volt;
+ port->pps_data.max_volt = port->pps_data.req_max_volt;
+ port->pps_data.max_curr = port->pps_data.req_max_curr;
+ port->req_supply_voltage = port->pps_data.req_out_volt;
+ port->req_current_limit = port->pps_data.req_op_curr;
+ power_supply_changed(port->psy);
tcpm_set_state(port, SNK_TRANSITION_SINK, 0);
break;
case SOFT_RESET_SEND:
src = port->source_caps[src_pdo];
snk = port->snk_pdo[snk_pdo];
- port->pps_data.min_volt = max(pdo_pps_apdo_min_voltage(src),
- pdo_pps_apdo_min_voltage(snk));
- port->pps_data.max_volt = min(pdo_pps_apdo_max_voltage(src),
- pdo_pps_apdo_max_voltage(snk));
- port->pps_data.max_curr = min_pps_apdo_current(src, snk);
- port->pps_data.out_volt = min(port->pps_data.max_volt,
- max(port->pps_data.min_volt,
- port->pps_data.out_volt));
- port->pps_data.op_curr = min(port->pps_data.max_curr,
- port->pps_data.op_curr);
- power_supply_changed(port->psy);
+ port->pps_data.req_min_volt = max(pdo_pps_apdo_min_voltage(src),
+ pdo_pps_apdo_min_voltage(snk));
+ port->pps_data.req_max_volt = min(pdo_pps_apdo_max_voltage(src),
+ pdo_pps_apdo_max_voltage(snk));
+ port->pps_data.req_max_curr = min_pps_apdo_current(src, snk);
+ port->pps_data.req_out_volt = min(port->pps_data.req_max_volt,
+ max(port->pps_data.req_min_volt,
+ port->pps_data.req_out_volt));
+ port->pps_data.req_op_curr = min(port->pps_data.req_max_curr,
+ port->pps_data.req_op_curr);
}
return src_pdo;
flags & RDO_CAP_MISMATCH ? " [mismatch]" : "");
}
- port->current_limit = ma;
- port->supply_voltage = mv;
+ port->req_current_limit = ma;
+ port->req_supply_voltage = mv;
return 0;
}
tcpm_log(port, "Invalid APDO selected!");
return -EINVAL;
}
- max_mv = port->pps_data.max_volt;
- max_ma = port->pps_data.max_curr;
- out_mv = port->pps_data.out_volt;
- op_ma = port->pps_data.op_curr;
+ max_mv = port->pps_data.req_max_volt;
+ max_ma = port->pps_data.req_max_curr;
+ out_mv = port->pps_data.req_out_volt;
+ op_ma = port->pps_data.req_op_curr;
break;
default:
tcpm_log(port, "Invalid PDO selected!");
tcpm_log(port, "Requesting APDO %d: %u mV, %u mA",
src_pdo_index, out_mv, op_ma);
- port->pps_data.op_curr = op_ma;
- port->pps_data.out_volt = out_mv;
+ port->pps_data.req_op_curr = op_ma;
+ port->pps_data.req_out_volt = out_mv;
return 0;
}
if (port->tcpc->enable_auto_vbus_discharge) {
ret = port->tcpc->enable_auto_vbus_discharge(port->tcpc, true);
tcpm_log_force(port, "enable vbus discharge ret:%d", ret);
+ if (!ret)
+ port->auto_vbus_discharge_enabled = true;
}
ret = tcpm_set_roles(port, true, TYPEC_SOURCE, tcpm_data_role_for_source(port));
if (port->tcpc->enable_auto_vbus_discharge) {
ret = port->tcpc->enable_auto_vbus_discharge(port->tcpc, false);
tcpm_log_force(port, "Disable vbus discharge ret:%d", ret);
+ if (!ret)
+ port->auto_vbus_discharge_enabled = false;
}
port->in_ams = false;
port->ams = NONE_AMS;
port->sink_cap_done = false;
if (port->tcpc->enable_frs)
port->tcpc->enable_frs(port->tcpc, false);
-
- power_supply_changed(port->psy);
}
static void tcpm_detach(struct tcpm_port *port)
tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB, false, VSAFE5V);
ret = port->tcpc->enable_auto_vbus_discharge(port->tcpc, true);
tcpm_log_force(port, "enable vbus discharge ret:%d", ret);
+ if (!ret)
+ port->auto_vbus_discharge_enabled = true;
}
ret = tcpm_set_roles(port, true, TYPEC_SINK, tcpm_data_role_for_sink(port));
static void tcpm_check_send_discover(struct tcpm_port *port)
{
- if (port->data_role == TYPEC_HOST && port->send_discover &&
- port->pd_capable)
+ if ((port->data_role == TYPEC_HOST || port->negotiated_rev > PD_REV20) &&
+ port->send_discover && port->pd_capable)
tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0);
port->send_discover = false;
}
break;
case SNK_DISCOVERY:
if (port->vbus_present) {
- tcpm_set_current_limit(port,
- tcpm_get_current_limit(port),
- 5000);
+ u32 current_lim = tcpm_get_current_limit(port);
+
+ if (port->slow_charger_loop || (current_lim > PD_P_SNK_STDBY_MW / 5))
+ current_lim = PD_P_SNK_STDBY_MW / 5;
+ tcpm_set_current_limit(port, current_lim, 5000);
tcpm_set_charge(port, true);
tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0);
break;
}
break;
case SNK_TRANSITION_SINK:
+ /* From the USB PD spec:
+ * "The Sink Shall transition to Sink Standby before a positive or
+ * negative voltage transition of VBUS. During Sink Standby
+ * the Sink Shall reduce its power draw to pSnkStdby."
+ *
+ * This is not applicable to PPS though as the port can continue
+ * to draw negotiated power without switching to standby.
+ */
+ if (port->supply_voltage != port->req_supply_voltage && !port->pps_data.active &&
+ port->current_limit * port->supply_voltage / 1000 > PD_P_SNK_STDBY_MW) {
+ u32 stdby_ma = PD_P_SNK_STDBY_MW * 1000 / port->supply_voltage;
+
+ tcpm_log(port, "Setting standby current %u mV @ %u mA",
+ port->supply_voltage, stdby_ma);
+ tcpm_set_current_limit(port, stdby_ma, port->supply_voltage);
+ }
+ fallthrough;
case SNK_TRANSITION_SINK_VBUS:
tcpm_set_state(port, hard_reset_state(port),
PD_T_PS_TRANSITION);
port->pwr_opmode = TYPEC_PWR_MODE_PD;
}
+ if (!port->pd_capable && port->slow_charger_loop)
+ tcpm_set_current_limit(port, tcpm_get_current_limit(port), 5000);
tcpm_swap_complete(port, 0);
tcpm_typec_connect(port);
mod_enable_frs_delayed_work(port, 0);
if (tcpm_port_is_disconnected(port) ||
!tcpm_port_is_source(port)) {
if (port->port_type == TYPEC_PORT_SRC)
- tcpm_set_state(port, SRC_UNATTACHED, 0);
+ tcpm_set_state(port, SRC_UNATTACHED, tcpm_wait_for_discharge(port));
else
- tcpm_set_state(port, SNK_UNATTACHED, 0);
+ tcpm_set_state(port, SNK_UNATTACHED, tcpm_wait_for_discharge(port));
}
break;
case SNK_UNATTACHED:
tcpm_set_state(port, SNK_DEBOUNCED, 0);
break;
case SNK_READY:
- if (tcpm_port_is_disconnected(port))
+ /*
+ * EXIT condition is based primarily on vbus disconnect and CC is secondary.
+ * "A port that has entered into USB PD communications with the Source and
+ * has seen the CC voltage exceed vRd-USB may monitor the CC pin to detect
+ * cable disconnect in addition to monitoring VBUS.
+ *
+ * A port that is monitoring the CC voltage for disconnect (but is not in
+ * the process of a USB PD PR_Swap or USB PD FR_Swap) shall transition to
+ * Unattached.SNK within tSinkDisconnect after the CC voltage remains below
+ * vRd-USB for tPDDebounce."
+ *
+ * When set_auto_vbus_discharge_threshold is enabled, CC pins go
+ * away before vbus decays to disconnect threshold. Allow
+ * disconnect to be driven by vbus disconnect when auto vbus
+ * discharge is enabled.
+ */
+ if (!port->auto_vbus_discharge_enabled && tcpm_port_is_disconnected(port))
tcpm_set_state(port, unattached_state(port), 0);
else if (!port->pd_capable &&
(cc1 != old_cc1 || cc2 != old_cc2))
* Ignore CC changes here.
*/
break;
-
default:
- if (tcpm_port_is_disconnected(port))
+ /*
+ * While acting as sink and auto vbus discharge is enabled, Allow disconnect
+ * to be driven by vbus disconnect.
+ */
+ if (tcpm_port_is_disconnected(port) && !(port->pwr_role == TYPEC_SINK &&
+ port->auto_vbus_discharge_enabled))
tcpm_set_state(port, unattached_state(port), 0);
break;
}
case SRC_TRANSITION_SUPPLY:
case SRC_READY:
case SRC_WAIT_NEW_CAPABILITIES:
- /* Force to unattached state to re-initiate connection */
- tcpm_set_state(port, SRC_UNATTACHED, 0);
+ /*
+ * Force to unattached state to re-initiate connection.
+ * DRP port should move to Unattached.SNK instead of Unattached.SRC if
+ * sink removed. Although sink removal here is due to source's vbus collapse,
+ * treat it the same way for consistency.
+ */
+ if (port->port_type == TYPEC_PORT_SRC)
+ tcpm_set_state(port, SRC_UNATTACHED, tcpm_wait_for_discharge(port));
+ else
+ tcpm_set_state(port, SNK_UNATTACHED, tcpm_wait_for_discharge(port));
break;
case PORT_RESET:
break;
default:
- if (port->pwr_role == TYPEC_SINK &&
- port->attached)
- tcpm_set_state(port, SNK_UNATTACHED, 0);
+ if (port->pwr_role == TYPEC_SINK && port->attached)
+ tcpm_set_state(port, SNK_UNATTACHED, tcpm_wait_for_discharge(port));
break;
}
}
tcpm_set_state(port, tcpm_try_snk(port) ? SNK_TRY : SRC_ATTACHED,
PD_T_CC_DEBOUNCE);
break;
+ case SRC_STARTUP:
+ case SRC_SEND_CAPABILITIES:
+ case SRC_SEND_CAPABILITIES_TIMEOUT:
+ case SRC_NEGOTIATE_CAPABILITIES:
+ case SRC_TRANSITION_SUPPLY:
+ case SRC_READY:
+ case SRC_WAIT_NEW_CAPABILITIES:
+ if (port->auto_vbus_discharge_enabled) {
+ if (port->port_type == TYPEC_PORT_SRC)
+ tcpm_set_state(port, SRC_UNATTACHED, 0);
+ else
+ tcpm_set_state(port, SNK_UNATTACHED, 0);
+ }
+ break;
default:
+ if (port->pwr_role == TYPEC_SINK && port->auto_vbus_discharge_enabled)
+ tcpm_set_state(port, SNK_UNATTACHED, 0);
break;
}
}
return ret;
}
-static int tcpm_pps_set_op_curr(struct tcpm_port *port, u16 op_curr)
+static int tcpm_pps_set_op_curr(struct tcpm_port *port, u16 req_op_curr)
{
unsigned int target_mw;
int ret;
goto port_unlock;
}
- if (op_curr > port->pps_data.max_curr) {
+ if (req_op_curr > port->pps_data.max_curr) {
ret = -EINVAL;
goto port_unlock;
}
- target_mw = (op_curr * port->pps_data.out_volt) / 1000;
+ target_mw = (req_op_curr * port->supply_voltage) / 1000;
if (target_mw < port->operating_snk_mw) {
ret = -EINVAL;
goto port_unlock;
}
/* Round down operating current to align with PPS valid steps */
- op_curr = op_curr - (op_curr % RDO_PROG_CURR_MA_STEP);
+ req_op_curr = req_op_curr - (req_op_curr % RDO_PROG_CURR_MA_STEP);
reinit_completion(&port->pps_complete);
- port->pps_data.op_curr = op_curr;
+ port->pps_data.req_op_curr = req_op_curr;
port->pps_status = 0;
port->pps_pending = true;
mutex_unlock(&port->lock);
return ret;
}
-static int tcpm_pps_set_out_volt(struct tcpm_port *port, u16 out_volt)
+static int tcpm_pps_set_out_volt(struct tcpm_port *port, u16 req_out_volt)
{
unsigned int target_mw;
int ret;
goto port_unlock;
}
- if (out_volt < port->pps_data.min_volt ||
- out_volt > port->pps_data.max_volt) {
+ if (req_out_volt < port->pps_data.min_volt ||
+ req_out_volt > port->pps_data.max_volt) {
ret = -EINVAL;
goto port_unlock;
}
- target_mw = (port->pps_data.op_curr * out_volt) / 1000;
+ target_mw = (port->current_limit * req_out_volt) / 1000;
if (target_mw < port->operating_snk_mw) {
ret = -EINVAL;
goto port_unlock;
}
/* Round down output voltage to align with PPS valid steps */
- out_volt = out_volt - (out_volt % RDO_PROG_VOLT_MV_STEP);
+ req_out_volt = req_out_volt - (req_out_volt % RDO_PROG_VOLT_MV_STEP);
reinit_completion(&port->pps_complete);
- port->pps_data.out_volt = out_volt;
+ port->pps_data.req_out_volt = req_out_volt;
port->pps_status = 0;
port->pps_pending = true;
mutex_unlock(&port->lock);
/* Trigger PPS request or move back to standard PDO contract */
if (activate) {
- port->pps_data.out_volt = port->supply_voltage;
- port->pps_data.op_curr = port->current_limit;
+ port->pps_data.req_out_volt = port->supply_voltage;
+ port->pps_data.req_op_curr = port->current_limit;
}
mutex_unlock(&port->lock);
port->typec_caps.type = ret;
port->port_type = port->typec_caps.type;
+ port->slow_charger_loop = fwnode_property_read_bool(fwnode, "slow-charger-loop");
if (port->port_type == TYPEC_PORT_SNK)
goto sink;
goto out_role_sw_put;
}
+ typec_port_register_altmodes(port->typec_port,
+ &tcpm_altmode_ops, port,
+ port->port_altmode, ALTMODE_DISCOVERY_MAX);
+
mutex_lock(&port->lock);
tcpm_init(port);
mutex_unlock(&port->lock);
--- /dev/null
+config TYPEC_TPS6598X
+ tristate "TI TPS6598x USB Power Delivery controller driver"
+ depends on I2C
+ select POWER_SUPPLY
+ select REGMAP_I2C
+ select USB_ROLE_SWITCH
+ help
+ Say Y or M here if your system has TI TPS65982 or TPS65983 USB Power
+ Delivery controller.
+
+ If you choose to build this driver as a dynamically linked module, the
+ module will be called tps6598x.ko.
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+CFLAGS_trace.o := -I$(src)
+
+obj-$(CONFIG_TYPEC_TPS6598X) += tps6598x.o
+tps6598x-y := core.o
+tps6598x-$(CONFIG_TRACING) += trace.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for TI TPS6598x USB Power Delivery controller family
+ *
+ * Copyright (C) 2017, Intel Corporation
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+ */
+
+#include <linux/i2c.h>
+#include <linux/acpi.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/interrupt.h>
+#include <linux/usb/typec.h>
+#include <linux/usb/role.h>
+
+#include "tps6598x.h"
+#include "trace.h"
+
+/* Register offsets */
+#define TPS_REG_VID 0x00
+#define TPS_REG_MODE 0x03
+#define TPS_REG_CMD1 0x08
+#define TPS_REG_DATA1 0x09
+#define TPS_REG_INT_EVENT1 0x14
+#define TPS_REG_INT_EVENT2 0x15
+#define TPS_REG_INT_MASK1 0x16
+#define TPS_REG_INT_MASK2 0x17
+#define TPS_REG_INT_CLEAR1 0x18
+#define TPS_REG_INT_CLEAR2 0x19
+#define TPS_REG_STATUS 0x1a
+#define TPS_REG_SYSTEM_CONF 0x28
+#define TPS_REG_CTRL_CONF 0x29
+#define TPS_REG_POWER_STATUS 0x3f
+#define TPS_REG_RX_IDENTITY_SOP 0x48
+#define TPS_REG_DATA_STATUS 0x5f
+
+/* TPS_REG_SYSTEM_CONF bits */
+#define TPS_SYSCONF_PORTINFO(c) ((c) & 7)
+
+enum {
+ TPS_PORTINFO_SINK,
+ TPS_PORTINFO_SINK_ACCESSORY,
+ TPS_PORTINFO_DRP_UFP,
+ TPS_PORTINFO_DRP_UFP_DRD,
+ TPS_PORTINFO_DRP_DFP,
+ TPS_PORTINFO_DRP_DFP_DRD,
+ TPS_PORTINFO_SOURCE,
+};
+
+/* TPS_REG_RX_IDENTITY_SOP */
+struct tps6598x_rx_identity_reg {
+ u8 status;
+ struct usb_pd_identity identity;
+} __packed;
+
+/* Standard Task return codes */
+#define TPS_TASK_TIMEOUT 1
+#define TPS_TASK_REJECTED 3
+
+enum {
+ TPS_MODE_APP,
+ TPS_MODE_BOOT,
+ TPS_MODE_BIST,
+ TPS_MODE_DISC,
+};
+
+static const char *const modes[] = {
+ [TPS_MODE_APP] = "APP ",
+ [TPS_MODE_BOOT] = "BOOT",
+ [TPS_MODE_BIST] = "BIST",
+ [TPS_MODE_DISC] = "DISC",
+};
+
+/* Unrecognized commands will be replaced with "!CMD" */
+#define INVALID_CMD(_cmd_) (_cmd_ == 0x444d4321)
+
+struct tps6598x {
+ struct device *dev;
+ struct regmap *regmap;
+ struct mutex lock; /* device lock */
+ u8 i2c_protocol:1;
+
+ struct typec_port *port;
+ struct typec_partner *partner;
+ struct usb_pd_identity partner_identity;
+ struct usb_role_switch *role_sw;
+ struct typec_capability typec_cap;
+
+ struct power_supply *psy;
+ struct power_supply_desc psy_desc;
+ enum power_supply_usb_type usb_type;
+};
+
+static enum power_supply_property tps6598x_psy_props[] = {
+ POWER_SUPPLY_PROP_USB_TYPE,
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static enum power_supply_usb_type tps6598x_psy_usb_types[] = {
+ POWER_SUPPLY_USB_TYPE_C,
+ POWER_SUPPLY_USB_TYPE_PD,
+};
+
+static const char *tps6598x_psy_name_prefix = "tps6598x-source-psy-";
+
+/*
+ * Max data bytes for Data1, Data2, and other registers. See ch 1.3.2:
+ * https://www.ti.com/lit/ug/slvuan1a/slvuan1a.pdf
+ */
+#define TPS_MAX_LEN 64
+
+static int
+tps6598x_block_read(struct tps6598x *tps, u8 reg, void *val, size_t len)
+{
+ u8 data[TPS_MAX_LEN + 1];
+ int ret;
+
+ if (WARN_ON(len + 1 > sizeof(data)))
+ return -EINVAL;
+
+ if (!tps->i2c_protocol)
+ return regmap_raw_read(tps->regmap, reg, val, len);
+
+ ret = regmap_raw_read(tps->regmap, reg, data, sizeof(data));
+ if (ret)
+ return ret;
+
+ if (data[0] < len)
+ return -EIO;
+
+ memcpy(val, &data[1], len);
+ return 0;
+}
+
+static int tps6598x_block_write(struct tps6598x *tps, u8 reg,
+ const void *val, size_t len)
+{
+ u8 data[TPS_MAX_LEN + 1];
+
+ if (!tps->i2c_protocol)
+ return regmap_raw_write(tps->regmap, reg, val, len);
+
+ data[0] = len;
+ memcpy(&data[1], val, len);
+
+ return regmap_raw_write(tps->regmap, reg, data, sizeof(data));
+}
+
+static inline int tps6598x_read16(struct tps6598x *tps, u8 reg, u16 *val)
+{
+ return tps6598x_block_read(tps, reg, val, sizeof(u16));
+}
+
+static inline int tps6598x_read32(struct tps6598x *tps, u8 reg, u32 *val)
+{
+ return tps6598x_block_read(tps, reg, val, sizeof(u32));
+}
+
+static inline int tps6598x_read64(struct tps6598x *tps, u8 reg, u64 *val)
+{
+ return tps6598x_block_read(tps, reg, val, sizeof(u64));
+}
+
+static inline int tps6598x_write16(struct tps6598x *tps, u8 reg, u16 val)
+{
+ return tps6598x_block_write(tps, reg, &val, sizeof(u16));
+}
+
+static inline int tps6598x_write32(struct tps6598x *tps, u8 reg, u32 val)
+{
+ return tps6598x_block_write(tps, reg, &val, sizeof(u32));
+}
+
+static inline int tps6598x_write64(struct tps6598x *tps, u8 reg, u64 val)
+{
+ return tps6598x_block_write(tps, reg, &val, sizeof(u64));
+}
+
+static inline int
+tps6598x_write_4cc(struct tps6598x *tps, u8 reg, const char *val)
+{
+ return tps6598x_block_write(tps, reg, val, 4);
+}
+
+static int tps6598x_read_partner_identity(struct tps6598x *tps)
+{
+ struct tps6598x_rx_identity_reg id;
+ int ret;
+
+ ret = tps6598x_block_read(tps, TPS_REG_RX_IDENTITY_SOP,
+ &id, sizeof(id));
+ if (ret)
+ return ret;
+
+ tps->partner_identity = id.identity;
+
+ return 0;
+}
+
+static void tps6598x_set_data_role(struct tps6598x *tps,
+ enum typec_data_role role, bool connected)
+{
+ enum usb_role role_val;
+
+ if (role == TYPEC_HOST)
+ role_val = USB_ROLE_HOST;
+ else
+ role_val = USB_ROLE_DEVICE;
+
+ if (!connected)
+ role_val = USB_ROLE_NONE;
+
+ usb_role_switch_set_role(tps->role_sw, role_val);
+ typec_set_data_role(tps->port, role);
+}
+
+static int tps6598x_connect(struct tps6598x *tps, u32 status)
+{
+ struct typec_partner_desc desc;
+ enum typec_pwr_opmode mode;
+ u16 pwr_status;
+ int ret;
+
+ if (tps->partner)
+ return 0;
+
+ ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &pwr_status);
+ if (ret < 0)
+ return ret;
+
+ mode = TPS_POWER_STATUS_PWROPMODE(pwr_status);
+
+ desc.usb_pd = mode == TYPEC_PWR_MODE_PD;
+ desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */
+ desc.identity = NULL;
+
+ if (desc.usb_pd) {
+ ret = tps6598x_read_partner_identity(tps);
+ if (ret)
+ return ret;
+ desc.identity = &tps->partner_identity;
+ }
+
+ typec_set_pwr_opmode(tps->port, mode);
+ typec_set_pwr_role(tps->port, TPS_STATUS_TO_TYPEC_PORTROLE(status));
+ typec_set_vconn_role(tps->port, TPS_STATUS_TO_TYPEC_VCONN(status));
+ tps6598x_set_data_role(tps, TPS_STATUS_TO_TYPEC_DATAROLE(status), true);
+
+ tps->partner = typec_register_partner(tps->port, &desc);
+ if (IS_ERR(tps->partner))
+ return PTR_ERR(tps->partner);
+
+ if (desc.identity)
+ typec_partner_set_identity(tps->partner);
+
+ power_supply_changed(tps->psy);
+
+ return 0;
+}
+
+static void tps6598x_disconnect(struct tps6598x *tps, u32 status)
+{
+ if (!IS_ERR(tps->partner))
+ typec_unregister_partner(tps->partner);
+ tps->partner = NULL;
+ typec_set_pwr_opmode(tps->port, TYPEC_PWR_MODE_USB);
+ typec_set_pwr_role(tps->port, TPS_STATUS_TO_TYPEC_PORTROLE(status));
+ typec_set_vconn_role(tps->port, TPS_STATUS_TO_TYPEC_VCONN(status));
+ tps6598x_set_data_role(tps, TPS_STATUS_TO_TYPEC_DATAROLE(status), false);
+
+ power_supply_changed(tps->psy);
+}
+
+static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd,
+ size_t in_len, u8 *in_data,
+ size_t out_len, u8 *out_data)
+{
+ unsigned long timeout;
+ u32 val;
+ int ret;
+
+ ret = tps6598x_read32(tps, TPS_REG_CMD1, &val);
+ if (ret)
+ return ret;
+ if (val && !INVALID_CMD(val))
+ return -EBUSY;
+
+ if (in_len) {
+ ret = tps6598x_block_write(tps, TPS_REG_DATA1,
+ in_data, in_len);
+ if (ret)
+ return ret;
+ }
+
+ ret = tps6598x_write_4cc(tps, TPS_REG_CMD1, cmd);
+ if (ret < 0)
+ return ret;
+
+ /* XXX: Using 1s for now, but it may not be enough for every command. */
+ timeout = jiffies + msecs_to_jiffies(1000);
+
+ do {
+ ret = tps6598x_read32(tps, TPS_REG_CMD1, &val);
+ if (ret)
+ return ret;
+ if (INVALID_CMD(val))
+ return -EINVAL;
+
+ if (time_is_before_jiffies(timeout))
+ return -ETIMEDOUT;
+ } while (val);
+
+ if (out_len) {
+ ret = tps6598x_block_read(tps, TPS_REG_DATA1,
+ out_data, out_len);
+ if (ret)
+ return ret;
+ val = out_data[0];
+ } else {
+ ret = tps6598x_block_read(tps, TPS_REG_DATA1, &val, sizeof(u8));
+ if (ret)
+ return ret;
+ }
+
+ switch (val) {
+ case TPS_TASK_TIMEOUT:
+ return -ETIMEDOUT;
+ case TPS_TASK_REJECTED:
+ return -EPERM;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int tps6598x_dr_set(struct typec_port *port, enum typec_data_role role)
+{
+ const char *cmd = (role == TYPEC_DEVICE) ? "SWUF" : "SWDF";
+ struct tps6598x *tps = typec_get_drvdata(port);
+ u32 status;
+ int ret;
+
+ mutex_lock(&tps->lock);
+
+ ret = tps6598x_exec_cmd(tps, cmd, 0, NULL, 0, NULL);
+ if (ret)
+ goto out_unlock;
+
+ ret = tps6598x_read32(tps, TPS_REG_STATUS, &status);
+ if (ret)
+ goto out_unlock;
+
+ if (role != TPS_STATUS_TO_TYPEC_DATAROLE(status)) {
+ ret = -EPROTO;
+ goto out_unlock;
+ }
+
+ tps6598x_set_data_role(tps, role, true);
+
+out_unlock:
+ mutex_unlock(&tps->lock);
+
+ return ret;
+}
+
+static int tps6598x_pr_set(struct typec_port *port, enum typec_role role)
+{
+ const char *cmd = (role == TYPEC_SINK) ? "SWSk" : "SWSr";
+ struct tps6598x *tps = typec_get_drvdata(port);
+ u32 status;
+ int ret;
+
+ mutex_lock(&tps->lock);
+
+ ret = tps6598x_exec_cmd(tps, cmd, 0, NULL, 0, NULL);
+ if (ret)
+ goto out_unlock;
+
+ ret = tps6598x_read32(tps, TPS_REG_STATUS, &status);
+ if (ret)
+ goto out_unlock;
+
+ if (role != TPS_STATUS_TO_TYPEC_PORTROLE(status)) {
+ ret = -EPROTO;
+ goto out_unlock;
+ }
+
+ typec_set_pwr_role(tps->port, role);
+
+out_unlock:
+ mutex_unlock(&tps->lock);
+
+ return ret;
+}
+
+static const struct typec_operations tps6598x_ops = {
+ .dr_set = tps6598x_dr_set,
+ .pr_set = tps6598x_pr_set,
+};
+
+static irqreturn_t tps6598x_interrupt(int irq, void *data)
+{
+ struct tps6598x *tps = data;
+ u64 event1;
+ u64 event2;
+ u32 status, data_status;
+ u16 pwr_status;
+ int ret;
+
+ mutex_lock(&tps->lock);
+
+ ret = tps6598x_read64(tps, TPS_REG_INT_EVENT1, &event1);
+ ret |= tps6598x_read64(tps, TPS_REG_INT_EVENT2, &event2);
+ if (ret) {
+ dev_err(tps->dev, "%s: failed to read events\n", __func__);
+ goto err_unlock;
+ }
+ trace_tps6598x_irq(event1, event2);
+
+ ret = tps6598x_read32(tps, TPS_REG_STATUS, &status);
+ if (ret) {
+ dev_err(tps->dev, "%s: failed to read status\n", __func__);
+ goto err_clear_ints;
+ }
+ trace_tps6598x_status(status);
+
+ if ((event1 | event2) & TPS_REG_INT_POWER_STATUS_UPDATE) {
+ ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &pwr_status);
+ if (ret < 0) {
+ dev_err(tps->dev, "failed to read power status: %d\n", ret);
+ goto err_clear_ints;
+ }
+ trace_tps6598x_power_status(pwr_status);
+ }
+
+ if ((event1 | event2) & TPS_REG_INT_DATA_STATUS_UPDATE) {
+ ret = tps6598x_read32(tps, TPS_REG_DATA_STATUS, &data_status);
+ if (ret < 0) {
+ dev_err(tps->dev, "failed to read data status: %d\n", ret);
+ goto err_clear_ints;
+ }
+ trace_tps6598x_data_status(data_status);
+ }
+
+ /* Handle plug insert or removal */
+ if ((event1 | event2) & TPS_REG_INT_PLUG_EVENT) {
+ if (status & TPS_STATUS_PLUG_PRESENT) {
+ ret = tps6598x_connect(tps, status);
+ if (ret)
+ dev_err(tps->dev,
+ "failed to register partner\n");
+ } else {
+ tps6598x_disconnect(tps, status);
+ }
+ }
+
+err_clear_ints:
+ tps6598x_write64(tps, TPS_REG_INT_CLEAR1, event1);
+ tps6598x_write64(tps, TPS_REG_INT_CLEAR2, event2);
+
+err_unlock:
+ mutex_unlock(&tps->lock);
+
+ return IRQ_HANDLED;
+}
+
+static int tps6598x_check_mode(struct tps6598x *tps)
+{
+ char mode[5] = { };
+ int ret;
+
+ ret = tps6598x_read32(tps, TPS_REG_MODE, (void *)mode);
+ if (ret)
+ return ret;
+
+ switch (match_string(modes, ARRAY_SIZE(modes), mode)) {
+ case TPS_MODE_APP:
+ return 0;
+ case TPS_MODE_BOOT:
+ dev_warn(tps->dev, "dead-battery condition\n");
+ return 0;
+ case TPS_MODE_BIST:
+ case TPS_MODE_DISC:
+ default:
+ dev_err(tps->dev, "controller in unsupported mode \"%s\"\n",
+ mode);
+ break;
+ }
+
+ return -ENODEV;
+}
+
+static const struct regmap_config tps6598x_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0x7F,
+};
+
+static int tps6598x_psy_get_online(struct tps6598x *tps,
+ union power_supply_propval *val)
+{
+ int ret;
+ u16 pwr_status;
+
+ ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &pwr_status);
+ if (ret < 0)
+ return ret;
+
+ if (TPS_POWER_STATUS_CONNECTION(pwr_status) &&
+ TPS_POWER_STATUS_SOURCESINK(pwr_status)) {
+ val->intval = 1;
+ } else {
+ val->intval = 0;
+ }
+ return 0;
+}
+
+static int tps6598x_psy_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct tps6598x *tps = power_supply_get_drvdata(psy);
+ u16 pwr_status;
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_USB_TYPE:
+ ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &pwr_status);
+ if (ret < 0)
+ return ret;
+ if (TPS_POWER_STATUS_PWROPMODE(pwr_status) == TYPEC_PWR_MODE_PD)
+ val->intval = POWER_SUPPLY_USB_TYPE_PD;
+ else
+ val->intval = POWER_SUPPLY_USB_TYPE_C;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = tps6598x_psy_get_online(tps, val);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int devm_tps6598_psy_register(struct tps6598x *tps)
+{
+ struct power_supply_config psy_cfg = {};
+ const char *port_dev_name = dev_name(tps->dev);
+ char *psy_name;
+
+ psy_cfg.drv_data = tps;
+ psy_cfg.fwnode = dev_fwnode(tps->dev);
+
+ psy_name = devm_kasprintf(tps->dev, GFP_KERNEL, "%s%s", tps6598x_psy_name_prefix,
+ port_dev_name);
+ if (!psy_name)
+ return -ENOMEM;
+
+ tps->psy_desc.name = psy_name;
+ tps->psy_desc.type = POWER_SUPPLY_TYPE_USB;
+ tps->psy_desc.usb_types = tps6598x_psy_usb_types;
+ tps->psy_desc.num_usb_types = ARRAY_SIZE(tps6598x_psy_usb_types);
+ tps->psy_desc.properties = tps6598x_psy_props;
+ tps->psy_desc.num_properties = ARRAY_SIZE(tps6598x_psy_props);
+ tps->psy_desc.get_property = tps6598x_psy_get_prop;
+
+ tps->usb_type = POWER_SUPPLY_USB_TYPE_C;
+
+ tps->psy = devm_power_supply_register(tps->dev, &tps->psy_desc,
+ &psy_cfg);
+ return PTR_ERR_OR_ZERO(tps->psy);
+}
+
+static int tps6598x_probe(struct i2c_client *client)
+{
+ struct typec_capability typec_cap = { };
+ struct tps6598x *tps;
+ struct fwnode_handle *fwnode;
+ u32 status;
+ u32 conf;
+ u32 vid;
+ int ret;
+
+ tps = devm_kzalloc(&client->dev, sizeof(*tps), GFP_KERNEL);
+ if (!tps)
+ return -ENOMEM;
+
+ mutex_init(&tps->lock);
+ tps->dev = &client->dev;
+
+ tps->regmap = devm_regmap_init_i2c(client, &tps6598x_regmap_config);
+ if (IS_ERR(tps->regmap))
+ return PTR_ERR(tps->regmap);
+
+ ret = tps6598x_read32(tps, TPS_REG_VID, &vid);
+ if (ret < 0 || !vid)
+ return -ENODEV;
+
+ /*
+ * Checking can the adapter handle SMBus protocol. If it can not, the
+ * driver needs to take care of block reads separately.
+ *
+ * FIXME: Testing with I2C_FUNC_I2C. regmap-i2c uses I2C protocol
+ * unconditionally if the adapter has I2C_FUNC_I2C set.
+ */
+ if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ tps->i2c_protocol = true;
+
+ /* Make sure the controller has application firmware running */
+ ret = tps6598x_check_mode(tps);
+ if (ret)
+ return ret;
+
+ ret = tps6598x_read32(tps, TPS_REG_STATUS, &status);
+ if (ret < 0)
+ return ret;
+ trace_tps6598x_status(status);
+
+ ret = tps6598x_read32(tps, TPS_REG_SYSTEM_CONF, &conf);
+ if (ret < 0)
+ return ret;
+
+ fwnode = device_get_named_child_node(&client->dev, "connector");
+ if (!fwnode)
+ return -ENODEV;
+
+ tps->role_sw = fwnode_usb_role_switch_get(fwnode);
+ if (IS_ERR(tps->role_sw)) {
+ ret = PTR_ERR(tps->role_sw);
+ goto err_fwnode_put;
+ }
+
+ typec_cap.revision = USB_TYPEC_REV_1_2;
+ typec_cap.pd_revision = 0x200;
+ typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE;
+ typec_cap.driver_data = tps;
+ typec_cap.ops = &tps6598x_ops;
+ typec_cap.fwnode = fwnode;
+
+ switch (TPS_SYSCONF_PORTINFO(conf)) {
+ case TPS_PORTINFO_SINK_ACCESSORY:
+ case TPS_PORTINFO_SINK:
+ typec_cap.type = TYPEC_PORT_SNK;
+ typec_cap.data = TYPEC_PORT_UFP;
+ break;
+ case TPS_PORTINFO_DRP_UFP_DRD:
+ case TPS_PORTINFO_DRP_DFP_DRD:
+ typec_cap.type = TYPEC_PORT_DRP;
+ typec_cap.data = TYPEC_PORT_DRD;
+ break;
+ case TPS_PORTINFO_DRP_UFP:
+ typec_cap.type = TYPEC_PORT_DRP;
+ typec_cap.data = TYPEC_PORT_UFP;
+ break;
+ case TPS_PORTINFO_DRP_DFP:
+ typec_cap.type = TYPEC_PORT_DRP;
+ typec_cap.data = TYPEC_PORT_DFP;
+ break;
+ case TPS_PORTINFO_SOURCE:
+ typec_cap.type = TYPEC_PORT_SRC;
+ typec_cap.data = TYPEC_PORT_DFP;
+ break;
+ default:
+ ret = -ENODEV;
+ goto err_role_put;
+ }
+
+ ret = devm_tps6598_psy_register(tps);
+ if (ret)
+ return ret;
+
+ tps->port = typec_register_port(&client->dev, &typec_cap);
+ if (IS_ERR(tps->port)) {
+ ret = PTR_ERR(tps->port);
+ goto err_role_put;
+ }
+ fwnode_handle_put(fwnode);
+
+ if (status & TPS_STATUS_PLUG_PRESENT) {
+ ret = tps6598x_connect(tps, status);
+ if (ret)
+ dev_err(&client->dev, "failed to register partner\n");
+ }
+
+ ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+ tps6598x_interrupt,
+ IRQF_SHARED | IRQF_ONESHOT,
+ dev_name(&client->dev), tps);
+ if (ret) {
+ tps6598x_disconnect(tps, 0);
+ typec_unregister_port(tps->port);
+ goto err_role_put;
+ }
+
+ i2c_set_clientdata(client, tps);
+
+ return 0;
+
+err_role_put:
+ usb_role_switch_put(tps->role_sw);
+err_fwnode_put:
+ fwnode_handle_put(fwnode);
+
+ return ret;
+}
+
+static int tps6598x_remove(struct i2c_client *client)
+{
+ struct tps6598x *tps = i2c_get_clientdata(client);
+
+ tps6598x_disconnect(tps, 0);
+ typec_unregister_port(tps->port);
+ usb_role_switch_put(tps->role_sw);
+
+ return 0;
+}
+
+static const struct of_device_id tps6598x_of_match[] = {
+ { .compatible = "ti,tps6598x", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, tps6598x_of_match);
+
+static const struct i2c_device_id tps6598x_id[] = {
+ { "tps6598x" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, tps6598x_id);
+
+static struct i2c_driver tps6598x_i2c_driver = {
+ .driver = {
+ .name = "tps6598x",
+ .of_match_table = tps6598x_of_match,
+ },
+ .probe_new = tps6598x_probe,
+ .remove = tps6598x_remove,
+ .id_table = tps6598x_id,
+};
+module_i2c_driver(tps6598x_i2c_driver);
+
+MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("TI TPS6598x USB Power Delivery Controller Driver");
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Driver for TI TPS6598x USB Power Delivery controller family
+ *
+ * Copyright (C) 2017, Intel Corporation
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+ */
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+
+#ifndef __TPS6598X_H__
+#define __TPS6598X_H__
+
+#define TPS_FIELD_GET(_mask, _reg) ((typeof(_mask))(((_reg) & (_mask)) >> __bf_shf(_mask)))
+
+/* TPS_REG_STATUS bits */
+#define TPS_STATUS_PLUG_PRESENT BIT(0)
+#define TPS_STATUS_PLUG_UPSIDE_DOWN BIT(4)
+#define TPS_STATUS_PORTROLE BIT(5)
+#define TPS_STATUS_TO_TYPEC_PORTROLE(s) (!!((s) & TPS_STATUS_PORTROLE))
+#define TPS_STATUS_DATAROLE BIT(6)
+#define TPS_STATUS_TO_TYPEC_DATAROLE(s) (!!((s) & TPS_STATUS_DATAROLE))
+#define TPS_STATUS_VCONN BIT(7)
+#define TPS_STATUS_TO_TYPEC_VCONN(s) (!!((s) & TPS_STATUS_VCONN))
+#define TPS_STATUS_OVERCURRENT BIT(16)
+#define TPS_STATUS_GOTO_MIN_ACTIVE BIT(26)
+#define TPS_STATUS_BIST BIT(27)
+#define TPS_STATUS_HIGH_VOLAGE_WARNING BIT(28)
+#define TPS_STATUS_HIGH_LOW_VOLTAGE_WARNING BIT(29)
+
+#define TPS_STATUS_CONN_STATE_MASK GENMASK(3, 1)
+#define TPS_STATUS_CONN_STATE(x) TPS_FIELD_GET(TPS_STATUS_CONN_STATE_MASK, (x))
+#define TPS_STATUS_PP_5V0_SWITCH_MASK GENMASK(9, 8)
+#define TPS_STATUS_PP_5V0_SWITCH(x) TPS_FIELD_GET(TPS_STATUS_PP_5V0_SWITCH_MASK, (x))
+#define TPS_STATUS_PP_HV_SWITCH_MASK GENMASK(11, 10)
+#define TPS_STATUS_PP_HV_SWITCH(x) TPS_FIELD_GET(TPS_STATUS_PP_HV_SWITCH_MASK, (x))
+#define TPS_STATUS_PP_EXT_SWITCH_MASK GENMASK(13, 12)
+#define TPS_STATUS_PP_EXT_SWITCH(x) TPS_FIELD_GET(TPS_STATUS_PP_EXT_SWITCH_MASK, (x))
+#define TPS_STATUS_PP_CABLE_SWITCH_MASK GENMASK(15, 14)
+#define TPS_STATUS_PP_CABLE_SWITCH(x) TPS_FIELD_GET(TPS_STATUS_PP_CABLE_SWITCH_MASK, (x))
+#define TPS_STATUS_POWER_SOURCE_MASK GENMASK(19, 18)
+#define TPS_STATUS_POWER_SOURCE(x) TPS_FIELD_GET(TPS_STATUS_POWER_SOURCE_MASK, (x))
+#define TPS_STATUS_VBUS_STATUS_MASK GENMASK(21, 20)
+#define TPS_STATUS_VBUS_STATUS(x) TPS_FIELD_GET(TPS_STATUS_VBUS_STATUS_MASK, (x))
+#define TPS_STATUS_USB_HOST_PRESENT_MASK GENMASK(23, 22)
+#define TPS_STATUS_USB_HOST_PRESENT(x) TPS_FIELD_GET(TPS_STATUS_USB_HOST_PRESENT_MASK, (x))
+#define TPS_STATUS_LEGACY_MASK GENMASK(25, 24)
+#define TPS_STATUS_LEGACY(x) TPS_FIELD_GET(TPS_STATUS_LEGACY_MASK, (x))
+
+#define TPS_STATUS_CONN_STATE_NO_CONN 0
+#define TPS_STATUS_CONN_STATE_DISABLED 1
+#define TPS_STATUS_CONN_STATE_AUDIO_CONN 2
+#define TPS_STATUS_CONN_STATE_DEBUG_CONN 3
+#define TPS_STATUS_CONN_STATE_NO_CONN_R_A 4
+#define TPS_STATUS_CONN_STATE_RESERVED 5
+#define TPS_STATUS_CONN_STATE_CONN_NO_R_A 6
+#define TPS_STATUS_CONN_STATE_CONN_WITH_R_A 7
+
+#define TPS_STATUS_PP_SWITCH_STATE_DISABLED 0
+#define TPS_STATUS_PP_SWITCH_STATE_FAULT 1
+#define TPS_STATUS_PP_SWITCH_STATE_OUT 2
+#define TPS_STATUS_PP_SWITCH_STATE_IN 3
+
+#define TPS_STATUS_POWER_SOURCE_UNKNOWN 0
+#define TPS_STATUS_POWER_SOURCE_VIN_3P3 1
+#define TPS_STATUS_POWER_SOURCE_DEAD_BAT 2
+#define TPS_STATUS_POWER_SOURCE_VBUS 3
+
+#define TPS_STATUS_VBUS_STATUS_VSAFE0V 0
+#define TPS_STATUS_VBUS_STATUS_VSAFE5V 1
+#define TPS_STATUS_VBUS_STATUS_PD 2
+#define TPS_STATUS_VBUS_STATUS_FAULT 3
+
+#define TPS_STATUS_USB_HOST_PRESENT_NO 0
+#define TPS_STATUS_USB_HOST_PRESENT_PD_NO_USB 1
+#define TPS_STATUS_USB_HOST_PRESENT_NO_PD 2
+#define TPS_STATUS_USB_HOST_PRESENT_PD_USB 3
+
+#define TPS_STATUS_LEGACY_NO 0
+#define TPS_STATUS_LEGACY_SINK 1
+#define TPS_STATUS_LEGACY_SOURCE 2
+
+/* TPS_REG_INT_* bits */
+#define TPS_REG_INT_USER_VID_ALT_MODE_OTHER_VDM BIT_ULL(27+32)
+#define TPS_REG_INT_USER_VID_ALT_MODE_ATTN_VDM BIT_ULL(26+32)
+#define TPS_REG_INT_USER_VID_ALT_MODE_EXIT BIT_ULL(25+32)
+#define TPS_REG_INT_USER_VID_ALT_MODE_ENTERED BIT_ULL(24+32)
+#define TPS_REG_INT_EXIT_MODES_COMPLETE BIT_ULL(20+32)
+#define TPS_REG_INT_DISCOVER_MODES_COMPLETE BIT_ULL(19+32)
+#define TPS_REG_INT_VDM_MSG_SENT BIT_ULL(18+32)
+#define TPS_REG_INT_VDM_ENTERED_MODE BIT_ULL(17+32)
+#define TPS_REG_INT_ERROR_UNABLE_TO_SOURCE BIT_ULL(14+32)
+#define TPS_REG_INT_SRC_TRANSITION BIT_ULL(10+32)
+#define TPS_REG_INT_ERROR_DISCHARGE_FAILED BIT_ULL(9+32)
+#define TPS_REG_INT_ERROR_MESSAGE_DATA BIT_ULL(7+32)
+#define TPS_REG_INT_ERROR_PROTOCOL_ERROR BIT_ULL(6+32)
+#define TPS_REG_INT_ERROR_MISSING_GET_CAP_MESSAGE BIT_ULL(4+32)
+#define TPS_REG_INT_ERROR_POWER_EVENT_OCCURRED BIT_ULL(3+32)
+#define TPS_REG_INT_ERROR_CAN_PROVIDE_PWR_LATER BIT_ULL(2+32)
+#define TPS_REG_INT_ERROR_CANNOT_PROVIDE_PWR BIT_ULL(1+32)
+#define TPS_REG_INT_ERROR_DEVICE_INCOMPATIBLE BIT_ULL(0+32)
+#define TPS_REG_INT_CMD2_COMPLETE BIT(31)
+#define TPS_REG_INT_CMD1_COMPLETE BIT(30)
+#define TPS_REG_INT_ADC_HIGH_THRESHOLD BIT(29)
+#define TPS_REG_INT_ADC_LOW_THRESHOLD BIT(28)
+#define TPS_REG_INT_PD_STATUS_UPDATE BIT(27)
+#define TPS_REG_INT_STATUS_UPDATE BIT(26)
+#define TPS_REG_INT_DATA_STATUS_UPDATE BIT(25)
+#define TPS_REG_INT_POWER_STATUS_UPDATE BIT(24)
+#define TPS_REG_INT_PP_SWITCH_CHANGED BIT(23)
+#define TPS_REG_INT_HIGH_VOLTAGE_WARNING BIT(22)
+#define TPS_REG_INT_USB_HOST_PRESENT_NO_LONGER BIT(21)
+#define TPS_REG_INT_USB_HOST_PRESENT BIT(20)
+#define TPS_REG_INT_GOTO_MIN_RECEIVED BIT(19)
+#define TPS_REG_INT_PR_SWAP_REQUESTED BIT(17)
+#define TPS_REG_INT_SINK_CAP_MESSAGE_READY BIT(15)
+#define TPS_REG_INT_SOURCE_CAP_MESSAGE_READY BIT(14)
+#define TPS_REG_INT_NEW_CONTRACT_AS_PROVIDER BIT(13)
+#define TPS_REG_INT_NEW_CONTRACT_AS_CONSUMER BIT(12)
+#define TPS_REG_INT_VDM_RECEIVED BIT(11)
+#define TPS_REG_INT_ATTENTION_RECEIVED BIT(10)
+#define TPS_REG_INT_OVERCURRENT BIT(9)
+#define TPS_REG_INT_BIST BIT(8)
+#define TPS_REG_INT_RDO_RECEIVED_FROM_SINK BIT(7)
+#define TPS_REG_INT_DR_SWAP_COMPLETE BIT(5)
+#define TPS_REG_INT_PR_SWAP_COMPLETE BIT(4)
+#define TPS_REG_INT_PLUG_EVENT BIT(3)
+#define TPS_REG_INT_HARD_RESET BIT(1)
+#define TPS_REG_INT_PD_SOFT_RESET BIT(0)
+
+/* TPS_REG_POWER_STATUS bits */
+#define TPS_POWER_STATUS_CONNECTION(x) TPS_FIELD_GET(BIT(0), (x))
+#define TPS_POWER_STATUS_SOURCESINK(x) TPS_FIELD_GET(BIT(1), (x))
+#define TPS_POWER_STATUS_BC12_DET(x) TPS_FIELD_GET(BIT(2), (x))
+
+#define TPS_POWER_STATUS_TYPEC_CURRENT_MASK GENMASK(3, 2)
+#define TPS_POWER_STATUS_PWROPMODE(p) TPS_FIELD_GET(TPS_POWER_STATUS_TYPEC_CURRENT_MASK, (p))
+#define TPS_POWER_STATUS_BC12_STATUS_MASK GENMASK(6, 5)
+#define TPS_POWER_STATUS_BC12_STATUS(p) TPS_FIELD_GET(TPS_POWER_STATUS_BC12_STATUS_MASK, (p))
+
+#define TPS_POWER_STATUS_TYPEC_CURRENT_USB 0
+#define TPS_POWER_STATUS_TYPEC_CURRENT_1A5 1
+#define TPS_POWER_STATUS_TYPEC_CURRENT_3A0 2
+#define TPS_POWER_STATUS_TYPEC_CURRENT_PD 3
+
+#define TPS_POWER_STATUS_BC12_STATUS_SDP 0
+#define TPS_POWER_STATUS_BC12_STATUS_CDP 2
+#define TPS_POWER_STATUS_BC12_STATUS_DCP 3
+
+/* TPS_REG_DATA_STATUS bits */
+#define TPS_DATA_STATUS_DATA_CONNECTION BIT(0)
+#define TPS_DATA_STATUS_UPSIDE_DOWN BIT(1)
+#define TPS_DATA_STATUS_ACTIVE_CABLE BIT(2)
+#define TPS_DATA_STATUS_USB2_CONNECTION BIT(4)
+#define TPS_DATA_STATUS_USB3_CONNECTION BIT(5)
+#define TPS_DATA_STATUS_USB3_GEN2 BIT(6)
+#define TPS_DATA_STATUS_USB_DATA_ROLE BIT(7)
+#define TPS_DATA_STATUS_DP_CONNECTION BIT(8)
+#define TPS_DATA_STATUS_DP_SINK BIT(9)
+#define TPS_DATA_STATUS_TBT_CONNECTION BIT(16)
+#define TPS_DATA_STATUS_TBT_TYPE BIT(17)
+#define TPS_DATA_STATUS_OPTICAL_CABLE BIT(18)
+#define TPS_DATA_STATUS_ACTIVE_LINK_TRAIN BIT(20)
+#define TPS_DATA_STATUS_FORCE_LSX BIT(23)
+#define TPS_DATA_STATUS_POWER_MISMATCH BIT(24)
+
+#define TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK GENMASK(11, 10)
+#define TPS_DATA_STATUS_DP_PIN_ASSIGNMENT(x) \
+ TPS_FIELD_GET(TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK, (x))
+#define TPS_DATA_STATUS_TBT_CABLE_SPEED_MASK GENMASK(27, 25)
+#define TPS_DATA_STATUS_TBT_CABLE_SPEED \
+ TPS_FIELD_GET(TPS_DATA_STATUS_TBT_CABLE_SPEED_MASK, (x))
+#define TPS_DATA_STATUS_TBT_CABLE_GEN_MASK GENMASK(29, 28)
+#define TPS_DATA_STATUS_TBT_CABLE_GEN \
+ TPS_FIELD_GET(TPS_DATA_STATUS_TBT_CABLE_GEN_MASK, (x))
+
+/* Map data status to DP spec assignments */
+#define TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT(x) \
+ ((TPS_DATA_STATUS_DP_PIN_ASSIGNMENT(x) << 1) | \
+ TPS_FIELD_GET(TPS_DATA_STATUS_USB3_CONNECTION, (x)))
+#define TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_E 0
+#define TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_F BIT(0)
+#define TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_C BIT(1)
+#define TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_D (BIT(1) | BIT(0))
+#define TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_A BIT(2)
+#define TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_B (BIT(2) | BIT(1))
+
+#endif /* __TPS6598X_H__ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TI TPS6598x USB Power Delivery Controller Trace Support
+ *
+ * Copyright (C) 2021, Intel Corporation
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+ */
+#define CREATE_TRACE_POINTS
+#include "trace.h"
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Driver for TI TPS6598x USB Power Delivery controller family
+ *
+ * Copyright (C) 2020 Purism SPC
+ * Author: Guido Günther <agx@sigxcpu.org>
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM tps6598x
+
+#if !defined(_TPS6598X_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ)
+#define _TPS6598X_TRACE_H_
+
+#include "tps6598x.h"
+
+#include <linux/stringify.h>
+#include <linux/types.h>
+#include <linux/tracepoint.h>
+
+#define show_irq_flags(flags) \
+ __print_flags_u64(flags, "|", \
+ { TPS_REG_INT_PD_SOFT_RESET, "PD_SOFT_RESET" }, \
+ { TPS_REG_INT_HARD_RESET, "HARD_RESET" }, \
+ { TPS_REG_INT_PLUG_EVENT, "PLUG_EVENT" }, \
+ { TPS_REG_INT_PR_SWAP_COMPLETE, "PR_SWAP_COMPLETE" }, \
+ { TPS_REG_INT_DR_SWAP_COMPLETE, "DR_SWAP_COMPLETE" }, \
+ { TPS_REG_INT_RDO_RECEIVED_FROM_SINK, "RDO_RECEIVED_FROM_SINK" }, \
+ { TPS_REG_INT_BIST, "BIST" }, \
+ { TPS_REG_INT_OVERCURRENT, "OVERCURRENT" }, \
+ { TPS_REG_INT_ATTENTION_RECEIVED, "ATTENTION_RECEIVED" }, \
+ { TPS_REG_INT_VDM_RECEIVED, "VDM_RECEIVED" }, \
+ { TPS_REG_INT_NEW_CONTRACT_AS_CONSUMER, "NEW_CONTRACT_AS_CONSUMER" }, \
+ { TPS_REG_INT_NEW_CONTRACT_AS_PROVIDER, "NEW_CONTRACT_AS_PROVIDER" }, \
+ { TPS_REG_INT_SOURCE_CAP_MESSAGE_READY, "SOURCE_CAP_MESSAGE_READY" }, \
+ { TPS_REG_INT_SINK_CAP_MESSAGE_READY, "SINK_CAP_MESSAGE_READY" }, \
+ { TPS_REG_INT_PR_SWAP_REQUESTED, "PR_SWAP_REQUESTED" }, \
+ { TPS_REG_INT_GOTO_MIN_RECEIVED, "GOTO_MIN_RECEIVED" }, \
+ { TPS_REG_INT_USB_HOST_PRESENT, "USB_HOST_PRESENT" }, \
+ { TPS_REG_INT_USB_HOST_PRESENT_NO_LONGER, "USB_HOST_PRESENT_NO_LONGER" }, \
+ { TPS_REG_INT_HIGH_VOLTAGE_WARNING, "HIGH_VOLTAGE_WARNING" }, \
+ { TPS_REG_INT_PP_SWITCH_CHANGED, "PP_SWITCH_CHANGED" }, \
+ { TPS_REG_INT_POWER_STATUS_UPDATE, "POWER_STATUS_UPDATE" }, \
+ { TPS_REG_INT_DATA_STATUS_UPDATE, "DATA_STATUS_UPDATE" }, \
+ { TPS_REG_INT_STATUS_UPDATE, "STATUS_UPDATE" }, \
+ { TPS_REG_INT_PD_STATUS_UPDATE, "PD_STATUS_UPDATE" }, \
+ { TPS_REG_INT_ADC_LOW_THRESHOLD, "ADC_LOW_THRESHOLD" }, \
+ { TPS_REG_INT_ADC_HIGH_THRESHOLD, "ADC_HIGH_THRESHOLD" }, \
+ { TPS_REG_INT_CMD1_COMPLETE, "CMD1_COMPLETE" }, \
+ { TPS_REG_INT_CMD2_COMPLETE, "CMD2_COMPLETE" }, \
+ { TPS_REG_INT_ERROR_DEVICE_INCOMPATIBLE, "ERROR_DEVICE_INCOMPATIBLE" }, \
+ { TPS_REG_INT_ERROR_CANNOT_PROVIDE_PWR, "ERROR_CANNOT_PROVIDE_PWR" }, \
+ { TPS_REG_INT_ERROR_CAN_PROVIDE_PWR_LATER, "ERROR_CAN_PROVIDE_PWR_LATER" }, \
+ { TPS_REG_INT_ERROR_POWER_EVENT_OCCURRED, "ERROR_POWER_EVENT_OCCURRED" }, \
+ { TPS_REG_INT_ERROR_MISSING_GET_CAP_MESSAGE, "ERROR_MISSING_GET_CAP_MESSAGE" }, \
+ { TPS_REG_INT_ERROR_PROTOCOL_ERROR, "ERROR_PROTOCOL_ERROR" }, \
+ { TPS_REG_INT_ERROR_MESSAGE_DATA, "ERROR_MESSAGE_DATA" }, \
+ { TPS_REG_INT_ERROR_DISCHARGE_FAILED, "ERROR_DISCHARGE_FAILED" }, \
+ { TPS_REG_INT_SRC_TRANSITION, "SRC_TRANSITION" }, \
+ { TPS_REG_INT_ERROR_UNABLE_TO_SOURCE, "ERROR_UNABLE_TO_SOURCE" }, \
+ { TPS_REG_INT_VDM_ENTERED_MODE, "VDM_ENTERED_MODE" }, \
+ { TPS_REG_INT_VDM_MSG_SENT, "VDM_MSG_SENT" }, \
+ { TPS_REG_INT_DISCOVER_MODES_COMPLETE, "DISCOVER_MODES_COMPLETE" }, \
+ { TPS_REG_INT_EXIT_MODES_COMPLETE, "EXIT_MODES_COMPLETE" }, \
+ { TPS_REG_INT_USER_VID_ALT_MODE_ENTERED, "USER_VID_ALT_MODE_ENTERED" }, \
+ { TPS_REG_INT_USER_VID_ALT_MODE_EXIT, "USER_VID_ALT_MODE_EXIT" }, \
+ { TPS_REG_INT_USER_VID_ALT_MODE_ATTN_VDM, "USER_VID_ALT_MODE_ATTN_VDM" }, \
+ { TPS_REG_INT_USER_VID_ALT_MODE_OTHER_VDM, "USER_VID_ALT_MODE_OTHER_VDM" })
+
+#define TPS6598X_STATUS_FLAGS_MASK (GENMASK(31, 0) ^ (TPS_STATUS_CONN_STATE_MASK | \
+ TPS_STATUS_PP_5V0_SWITCH_MASK | \
+ TPS_STATUS_PP_HV_SWITCH_MASK | \
+ TPS_STATUS_PP_EXT_SWITCH_MASK | \
+ TPS_STATUS_PP_CABLE_SWITCH_MASK | \
+ TPS_STATUS_POWER_SOURCE_MASK | \
+ TPS_STATUS_VBUS_STATUS_MASK | \
+ TPS_STATUS_USB_HOST_PRESENT_MASK | \
+ TPS_STATUS_LEGACY_MASK))
+
+#define show_status_conn_state(status) \
+ __print_symbolic(TPS_STATUS_CONN_STATE((status)), \
+ { TPS_STATUS_CONN_STATE_CONN_WITH_R_A, "conn-Ra" }, \
+ { TPS_STATUS_CONN_STATE_CONN_NO_R_A, "conn-no-Ra" }, \
+ { TPS_STATUS_CONN_STATE_NO_CONN_R_A, "no-conn-Ra" }, \
+ { TPS_STATUS_CONN_STATE_DEBUG_CONN, "debug" }, \
+ { TPS_STATUS_CONN_STATE_AUDIO_CONN, "audio" }, \
+ { TPS_STATUS_CONN_STATE_DISABLED, "disabled" }, \
+ { TPS_STATUS_CONN_STATE_NO_CONN, "no-conn" })
+
+#define show_status_pp_switch_state(status) \
+ __print_symbolic(status, \
+ { TPS_STATUS_PP_SWITCH_STATE_IN, "in" }, \
+ { TPS_STATUS_PP_SWITCH_STATE_OUT, "out" }, \
+ { TPS_STATUS_PP_SWITCH_STATE_FAULT, "fault" }, \
+ { TPS_STATUS_PP_SWITCH_STATE_DISABLED, "off" })
+
+#define show_status_power_sources(status) \
+ __print_symbolic(TPS_STATUS_POWER_SOURCE(status), \
+ { TPS_STATUS_POWER_SOURCE_VBUS, "vbus" }, \
+ { TPS_STATUS_POWER_SOURCE_VIN_3P3, "vin-3p3" }, \
+ { TPS_STATUS_POWER_SOURCE_DEAD_BAT, "dead-battery" }, \
+ { TPS_STATUS_POWER_SOURCE_UNKNOWN, "unknown" })
+
+#define show_status_vbus_status(status) \
+ __print_symbolic(TPS_STATUS_VBUS_STATUS(status), \
+ { TPS_STATUS_VBUS_STATUS_VSAFE0V, "vSafe0V" }, \
+ { TPS_STATUS_VBUS_STATUS_VSAFE5V, "vSafe5V" }, \
+ { TPS_STATUS_VBUS_STATUS_PD, "pd" }, \
+ { TPS_STATUS_VBUS_STATUS_FAULT, "fault" })
+
+#define show_status_usb_host_present(status) \
+ __print_symbolic(TPS_STATUS_USB_HOST_PRESENT(status), \
+ { TPS_STATUS_USB_HOST_PRESENT_PD_USB, "pd-usb" }, \
+ { TPS_STATUS_USB_HOST_PRESENT_NO_PD, "no-pd" }, \
+ { TPS_STATUS_USB_HOST_PRESENT_PD_NO_USB, "pd-no-usb" }, \
+ { TPS_STATUS_USB_HOST_PRESENT_NO, "no" })
+
+#define show_status_legacy(status) \
+ __print_symbolic(TPS_STATUS_LEGACY(status), \
+ { TPS_STATUS_LEGACY_SOURCE, "source" }, \
+ { TPS_STATUS_LEGACY_SINK, "sink" }, \
+ { TPS_STATUS_LEGACY_NO, "no" })
+
+#define show_status_flags(flags) \
+ __print_flags((flags & TPS6598X_STATUS_FLAGS_MASK), "|", \
+ { TPS_STATUS_PLUG_PRESENT, "PLUG_PRESENT" }, \
+ { TPS_STATUS_PLUG_UPSIDE_DOWN, "UPSIDE_DOWN" }, \
+ { TPS_STATUS_PORTROLE, "PORTROLE" }, \
+ { TPS_STATUS_DATAROLE, "DATAROLE" }, \
+ { TPS_STATUS_VCONN, "VCONN" }, \
+ { TPS_STATUS_OVERCURRENT, "OVERCURRENT" }, \
+ { TPS_STATUS_GOTO_MIN_ACTIVE, "GOTO_MIN_ACTIVE" }, \
+ { TPS_STATUS_BIST, "BIST" }, \
+ { TPS_STATUS_HIGH_VOLAGE_WARNING, "HIGH_VOLAGE_WARNING" }, \
+ { TPS_STATUS_HIGH_LOW_VOLTAGE_WARNING, "HIGH_LOW_VOLTAGE_WARNING" })
+
+#define show_power_status_source_sink(power_status) \
+ __print_symbolic(TPS_POWER_STATUS_SOURCESINK(power_status), \
+ { 1, "sink" }, \
+ { 0, "source" })
+
+#define show_power_status_typec_status(power_status) \
+ __print_symbolic(TPS_POWER_STATUS_PWROPMODE(power_status), \
+ { TPS_POWER_STATUS_TYPEC_CURRENT_PD, "pd" }, \
+ { TPS_POWER_STATUS_TYPEC_CURRENT_3A0, "3.0A" }, \
+ { TPS_POWER_STATUS_TYPEC_CURRENT_1A5, "1.5A" }, \
+ { TPS_POWER_STATUS_TYPEC_CURRENT_USB, "usb" })
+
+#define show_power_status_bc12_status(power_status) \
+ __print_symbolic(TPS_POWER_STATUS_BC12_STATUS(power_status), \
+ { TPS_POWER_STATUS_BC12_STATUS_DCP, "dcp" }, \
+ { TPS_POWER_STATUS_BC12_STATUS_CDP, "cdp" }, \
+ { TPS_POWER_STATUS_BC12_STATUS_SDP, "sdp" })
+
+#define TPS_DATA_STATUS_FLAGS_MASK (GENMASK(31, 0) ^ (TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK | \
+ TPS_DATA_STATUS_TBT_CABLE_SPEED_MASK | \
+ TPS_DATA_STATUS_TBT_CABLE_GEN_MASK))
+
+#define show_data_status_flags(data_status) \
+ __print_flags(data_status & TPS_DATA_STATUS_FLAGS_MASK, "|", \
+ { TPS_DATA_STATUS_DATA_CONNECTION, "DATA_CONNECTION" }, \
+ { TPS_DATA_STATUS_UPSIDE_DOWN, "DATA_UPSIDE_DOWN" }, \
+ { TPS_DATA_STATUS_ACTIVE_CABLE, "ACTIVE_CABLE" }, \
+ { TPS_DATA_STATUS_USB2_CONNECTION, "USB2_CONNECTION" }, \
+ { TPS_DATA_STATUS_USB3_CONNECTION, "USB3_CONNECTION" }, \
+ { TPS_DATA_STATUS_USB3_GEN2, "USB3_GEN2" }, \
+ { TPS_DATA_STATUS_USB_DATA_ROLE, "USB_DATA_ROLE" }, \
+ { TPS_DATA_STATUS_DP_CONNECTION, "DP_CONNECTION" }, \
+ { TPS_DATA_STATUS_DP_SINK, "DP_SINK" }, \
+ { TPS_DATA_STATUS_TBT_CONNECTION, "TBT_CONNECTION" }, \
+ { TPS_DATA_STATUS_TBT_TYPE, "TBT_TYPE" }, \
+ { TPS_DATA_STATUS_OPTICAL_CABLE, "OPTICAL_CABLE" }, \
+ { TPS_DATA_STATUS_ACTIVE_LINK_TRAIN, "ACTIVE_LINK_TRAIN" }, \
+ { TPS_DATA_STATUS_FORCE_LSX, "FORCE_LSX" }, \
+ { TPS_DATA_STATUS_POWER_MISMATCH, "POWER_MISMATCH" })
+
+#define show_data_status_dp_pin_assignment(data_status) \
+ __print_symbolic(TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT(data_status), \
+ { TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_E, "E" }, \
+ { TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_F, "F" }, \
+ { TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_C, "C" }, \
+ { TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_D, "D" }, \
+ { TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_A, "A" }, \
+ { TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_B, "B" })
+
+#define maybe_show_data_status_dp_pin_assignment(data_status) \
+ (data_status & TPS_DATA_STATUS_DP_CONNECTION ? \
+ show_data_status_dp_pin_assignment(data_status) : "")
+
+TRACE_EVENT(tps6598x_irq,
+ TP_PROTO(u64 event1,
+ u64 event2),
+ TP_ARGS(event1, event2),
+
+ TP_STRUCT__entry(
+ __field(u64, event1)
+ __field(u64, event2)
+ ),
+
+ TP_fast_assign(
+ __entry->event1 = event1;
+ __entry->event2 = event2;
+ ),
+
+ TP_printk("event1=%s, event2=%s",
+ show_irq_flags(__entry->event1),
+ show_irq_flags(__entry->event2))
+);
+
+TRACE_EVENT(tps6598x_status,
+ TP_PROTO(u32 status),
+ TP_ARGS(status),
+
+ TP_STRUCT__entry(
+ __field(u32, status)
+ ),
+
+ TP_fast_assign(
+ __entry->status = status;
+ ),
+
+ TP_printk("conn: %s, pp_5v0: %s, pp_hv: %s, pp_ext: %s, pp_cable: %s, "
+ "pwr-src: %s, vbus: %s, usb-host: %s, legacy: %s, flags: %s",
+ show_status_conn_state(__entry->status),
+ show_status_pp_switch_state(TPS_STATUS_PP_5V0_SWITCH(__entry->status)),
+ show_status_pp_switch_state(TPS_STATUS_PP_HV_SWITCH(__entry->status)),
+ show_status_pp_switch_state(TPS_STATUS_PP_EXT_SWITCH(__entry->status)),
+ show_status_pp_switch_state(TPS_STATUS_PP_CABLE_SWITCH(__entry->status)),
+ show_status_power_sources(__entry->status),
+ show_status_vbus_status(__entry->status),
+ show_status_usb_host_present(__entry->status),
+ show_status_legacy(__entry->status),
+ show_status_flags(__entry->status)
+ )
+);
+
+TRACE_EVENT(tps6598x_power_status,
+ TP_PROTO(u16 power_status),
+ TP_ARGS(power_status),
+
+ TP_STRUCT__entry(
+ __field(u16, power_status)
+ ),
+
+ TP_fast_assign(
+ __entry->power_status = power_status;
+ ),
+
+ TP_printk("conn: %d, pwr-role: %s, typec: %s, bc: %s",
+ !!TPS_POWER_STATUS_CONNECTION(__entry->power_status),
+ show_power_status_source_sink(__entry->power_status),
+ show_power_status_typec_status(__entry->power_status),
+ show_power_status_bc12_status(__entry->power_status)
+ )
+);
+
+TRACE_EVENT(tps6598x_data_status,
+ TP_PROTO(u32 data_status),
+ TP_ARGS(data_status),
+
+ TP_STRUCT__entry(
+ __field(u32, data_status)
+ ),
+
+ TP_fast_assign(
+ __entry->data_status = data_status;
+ ),
+
+ TP_printk("%s%s%s",
+ show_data_status_flags(__entry->data_status),
+ __entry->data_status & TPS_DATA_STATUS_DP_CONNECTION ? ", DP pinout " : "",
+ maybe_show_data_status_dp_pin_assignment(__entry->data_status)
+ )
+);
+
+#endif /* _TPS6598X_TRACE_H_ */
+
+/* This part must be outside protection */
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE trace
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#include <trace/define_trace.h>
+++ /dev/null
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Driver for TI TPS6598x USB Power Delivery controller family
- *
- * Copyright (C) 2017, Intel Corporation
- * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
- */
-
-#include <linux/i2c.h>
-#include <linux/acpi.h>
-#include <linux/module.h>
-#include <linux/power_supply.h>
-#include <linux/regmap.h>
-#include <linux/interrupt.h>
-#include <linux/usb/typec.h>
-#include <linux/usb/role.h>
-
-/* Register offsets */
-#define TPS_REG_VID 0x00
-#define TPS_REG_MODE 0x03
-#define TPS_REG_CMD1 0x08
-#define TPS_REG_DATA1 0x09
-#define TPS_REG_INT_EVENT1 0x14
-#define TPS_REG_INT_EVENT2 0x15
-#define TPS_REG_INT_MASK1 0x16
-#define TPS_REG_INT_MASK2 0x17
-#define TPS_REG_INT_CLEAR1 0x18
-#define TPS_REG_INT_CLEAR2 0x19
-#define TPS_REG_STATUS 0x1a
-#define TPS_REG_SYSTEM_CONF 0x28
-#define TPS_REG_CTRL_CONF 0x29
-#define TPS_REG_POWER_STATUS 0x3f
-#define TPS_REG_RX_IDENTITY_SOP 0x48
-
-/* TPS_REG_INT_* bits */
-#define TPS_REG_INT_PLUG_EVENT BIT(3)
-
-/* TPS_REG_STATUS bits */
-#define TPS_STATUS_PLUG_PRESENT BIT(0)
-#define TPS_STATUS_ORIENTATION BIT(4)
-#define TPS_STATUS_PORTROLE(s) (!!((s) & BIT(5)))
-#define TPS_STATUS_DATAROLE(s) (!!((s) & BIT(6)))
-#define TPS_STATUS_VCONN(s) (!!((s) & BIT(7)))
-
-/* TPS_REG_SYSTEM_CONF bits */
-#define TPS_SYSCONF_PORTINFO(c) ((c) & 7)
-
-enum {
- TPS_PORTINFO_SINK,
- TPS_PORTINFO_SINK_ACCESSORY,
- TPS_PORTINFO_DRP_UFP,
- TPS_PORTINFO_DRP_UFP_DRD,
- TPS_PORTINFO_DRP_DFP,
- TPS_PORTINFO_DRP_DFP_DRD,
- TPS_PORTINFO_SOURCE,
-};
-
-/* TPS_REG_POWER_STATUS bits */
-#define TPS_POWER_STATUS_CONNECTION BIT(0)
-#define TPS_POWER_STATUS_SOURCESINK BIT(1)
-#define TPS_POWER_STATUS_PWROPMODE(p) (((p) & GENMASK(3, 2)) >> 2)
-
-/* TPS_REG_RX_IDENTITY_SOP */
-struct tps6598x_rx_identity_reg {
- u8 status;
- struct usb_pd_identity identity;
-} __packed;
-
-/* Standard Task return codes */
-#define TPS_TASK_TIMEOUT 1
-#define TPS_TASK_REJECTED 3
-
-enum {
- TPS_MODE_APP,
- TPS_MODE_BOOT,
- TPS_MODE_BIST,
- TPS_MODE_DISC,
-};
-
-static const char *const modes[] = {
- [TPS_MODE_APP] = "APP ",
- [TPS_MODE_BOOT] = "BOOT",
- [TPS_MODE_BIST] = "BIST",
- [TPS_MODE_DISC] = "DISC",
-};
-
-/* Unrecognized commands will be replaced with "!CMD" */
-#define INVALID_CMD(_cmd_) (_cmd_ == 0x444d4321)
-
-struct tps6598x {
- struct device *dev;
- struct regmap *regmap;
- struct mutex lock; /* device lock */
- u8 i2c_protocol:1;
-
- struct typec_port *port;
- struct typec_partner *partner;
- struct usb_pd_identity partner_identity;
- struct usb_role_switch *role_sw;
- struct typec_capability typec_cap;
-
- struct power_supply *psy;
- struct power_supply_desc psy_desc;
- enum power_supply_usb_type usb_type;
-};
-
-static enum power_supply_property tps6598x_psy_props[] = {
- POWER_SUPPLY_PROP_USB_TYPE,
- POWER_SUPPLY_PROP_ONLINE,
-};
-
-static enum power_supply_usb_type tps6598x_psy_usb_types[] = {
- POWER_SUPPLY_USB_TYPE_C,
- POWER_SUPPLY_USB_TYPE_PD,
-};
-
-static const char *tps6598x_psy_name_prefix = "tps6598x-source-psy-";
-
-/*
- * Max data bytes for Data1, Data2, and other registers. See ch 1.3.2:
- * https://www.ti.com/lit/ug/slvuan1a/slvuan1a.pdf
- */
-#define TPS_MAX_LEN 64
-
-static int
-tps6598x_block_read(struct tps6598x *tps, u8 reg, void *val, size_t len)
-{
- u8 data[TPS_MAX_LEN + 1];
- int ret;
-
- if (WARN_ON(len + 1 > sizeof(data)))
- return -EINVAL;
-
- if (!tps->i2c_protocol)
- return regmap_raw_read(tps->regmap, reg, val, len);
-
- ret = regmap_raw_read(tps->regmap, reg, data, sizeof(data));
- if (ret)
- return ret;
-
- if (data[0] < len)
- return -EIO;
-
- memcpy(val, &data[1], len);
- return 0;
-}
-
-static int tps6598x_block_write(struct tps6598x *tps, u8 reg,
- const void *val, size_t len)
-{
- u8 data[TPS_MAX_LEN + 1];
-
- if (!tps->i2c_protocol)
- return regmap_raw_write(tps->regmap, reg, val, len);
-
- data[0] = len;
- memcpy(&data[1], val, len);
-
- return regmap_raw_write(tps->regmap, reg, data, sizeof(data));
-}
-
-static inline int tps6598x_read16(struct tps6598x *tps, u8 reg, u16 *val)
-{
- return tps6598x_block_read(tps, reg, val, sizeof(u16));
-}
-
-static inline int tps6598x_read32(struct tps6598x *tps, u8 reg, u32 *val)
-{
- return tps6598x_block_read(tps, reg, val, sizeof(u32));
-}
-
-static inline int tps6598x_read64(struct tps6598x *tps, u8 reg, u64 *val)
-{
- return tps6598x_block_read(tps, reg, val, sizeof(u64));
-}
-
-static inline int tps6598x_write16(struct tps6598x *tps, u8 reg, u16 val)
-{
- return tps6598x_block_write(tps, reg, &val, sizeof(u16));
-}
-
-static inline int tps6598x_write32(struct tps6598x *tps, u8 reg, u32 val)
-{
- return tps6598x_block_write(tps, reg, &val, sizeof(u32));
-}
-
-static inline int tps6598x_write64(struct tps6598x *tps, u8 reg, u64 val)
-{
- return tps6598x_block_write(tps, reg, &val, sizeof(u64));
-}
-
-static inline int
-tps6598x_write_4cc(struct tps6598x *tps, u8 reg, const char *val)
-{
- return tps6598x_block_write(tps, reg, val, 4);
-}
-
-static int tps6598x_read_partner_identity(struct tps6598x *tps)
-{
- struct tps6598x_rx_identity_reg id;
- int ret;
-
- ret = tps6598x_block_read(tps, TPS_REG_RX_IDENTITY_SOP,
- &id, sizeof(id));
- if (ret)
- return ret;
-
- tps->partner_identity = id.identity;
-
- return 0;
-}
-
-static void tps6598x_set_data_role(struct tps6598x *tps,
- enum typec_data_role role, bool connected)
-{
- enum usb_role role_val;
-
- if (role == TYPEC_HOST)
- role_val = USB_ROLE_HOST;
- else
- role_val = USB_ROLE_DEVICE;
-
- if (!connected)
- role_val = USB_ROLE_NONE;
-
- usb_role_switch_set_role(tps->role_sw, role_val);
- typec_set_data_role(tps->port, role);
-}
-
-static int tps6598x_connect(struct tps6598x *tps, u32 status)
-{
- struct typec_partner_desc desc;
- enum typec_pwr_opmode mode;
- u16 pwr_status;
- int ret;
-
- if (tps->partner)
- return 0;
-
- ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &pwr_status);
- if (ret < 0)
- return ret;
-
- mode = TPS_POWER_STATUS_PWROPMODE(pwr_status);
-
- desc.usb_pd = mode == TYPEC_PWR_MODE_PD;
- desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */
- desc.identity = NULL;
-
- if (desc.usb_pd) {
- ret = tps6598x_read_partner_identity(tps);
- if (ret)
- return ret;
- desc.identity = &tps->partner_identity;
- }
-
- typec_set_pwr_opmode(tps->port, mode);
- typec_set_pwr_role(tps->port, TPS_STATUS_PORTROLE(status));
- typec_set_vconn_role(tps->port, TPS_STATUS_VCONN(status));
- tps6598x_set_data_role(tps, TPS_STATUS_DATAROLE(status), true);
-
- tps->partner = typec_register_partner(tps->port, &desc);
- if (IS_ERR(tps->partner))
- return PTR_ERR(tps->partner);
-
- if (desc.identity)
- typec_partner_set_identity(tps->partner);
-
- power_supply_changed(tps->psy);
-
- return 0;
-}
-
-static void tps6598x_disconnect(struct tps6598x *tps, u32 status)
-{
- if (!IS_ERR(tps->partner))
- typec_unregister_partner(tps->partner);
- tps->partner = NULL;
- typec_set_pwr_opmode(tps->port, TYPEC_PWR_MODE_USB);
- typec_set_pwr_role(tps->port, TPS_STATUS_PORTROLE(status));
- typec_set_vconn_role(tps->port, TPS_STATUS_VCONN(status));
- tps6598x_set_data_role(tps, TPS_STATUS_DATAROLE(status), false);
- power_supply_changed(tps->psy);
-}
-
-static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd,
- size_t in_len, u8 *in_data,
- size_t out_len, u8 *out_data)
-{
- unsigned long timeout;
- u32 val;
- int ret;
-
- ret = tps6598x_read32(tps, TPS_REG_CMD1, &val);
- if (ret)
- return ret;
- if (val && !INVALID_CMD(val))
- return -EBUSY;
-
- if (in_len) {
- ret = tps6598x_block_write(tps, TPS_REG_DATA1,
- in_data, in_len);
- if (ret)
- return ret;
- }
-
- ret = tps6598x_write_4cc(tps, TPS_REG_CMD1, cmd);
- if (ret < 0)
- return ret;
-
- /* XXX: Using 1s for now, but it may not be enough for every command. */
- timeout = jiffies + msecs_to_jiffies(1000);
-
- do {
- ret = tps6598x_read32(tps, TPS_REG_CMD1, &val);
- if (ret)
- return ret;
- if (INVALID_CMD(val))
- return -EINVAL;
-
- if (time_is_before_jiffies(timeout))
- return -ETIMEDOUT;
- } while (val);
-
- if (out_len) {
- ret = tps6598x_block_read(tps, TPS_REG_DATA1,
- out_data, out_len);
- if (ret)
- return ret;
- val = out_data[0];
- } else {
- ret = tps6598x_block_read(tps, TPS_REG_DATA1, &val, sizeof(u8));
- if (ret)
- return ret;
- }
-
- switch (val) {
- case TPS_TASK_TIMEOUT:
- return -ETIMEDOUT;
- case TPS_TASK_REJECTED:
- return -EPERM;
- default:
- break;
- }
-
- return 0;
-}
-
-static int tps6598x_dr_set(struct typec_port *port, enum typec_data_role role)
-{
- const char *cmd = (role == TYPEC_DEVICE) ? "SWUF" : "SWDF";
- struct tps6598x *tps = typec_get_drvdata(port);
- u32 status;
- int ret;
-
- mutex_lock(&tps->lock);
-
- ret = tps6598x_exec_cmd(tps, cmd, 0, NULL, 0, NULL);
- if (ret)
- goto out_unlock;
-
- ret = tps6598x_read32(tps, TPS_REG_STATUS, &status);
- if (ret)
- goto out_unlock;
-
- if (role != TPS_STATUS_DATAROLE(status)) {
- ret = -EPROTO;
- goto out_unlock;
- }
-
- tps6598x_set_data_role(tps, role, true);
-
-out_unlock:
- mutex_unlock(&tps->lock);
-
- return ret;
-}
-
-static int tps6598x_pr_set(struct typec_port *port, enum typec_role role)
-{
- const char *cmd = (role == TYPEC_SINK) ? "SWSk" : "SWSr";
- struct tps6598x *tps = typec_get_drvdata(port);
- u32 status;
- int ret;
-
- mutex_lock(&tps->lock);
-
- ret = tps6598x_exec_cmd(tps, cmd, 0, NULL, 0, NULL);
- if (ret)
- goto out_unlock;
-
- ret = tps6598x_read32(tps, TPS_REG_STATUS, &status);
- if (ret)
- goto out_unlock;
-
- if (role != TPS_STATUS_PORTROLE(status)) {
- ret = -EPROTO;
- goto out_unlock;
- }
-
- typec_set_pwr_role(tps->port, role);
-
-out_unlock:
- mutex_unlock(&tps->lock);
-
- return ret;
-}
-
-static const struct typec_operations tps6598x_ops = {
- .dr_set = tps6598x_dr_set,
- .pr_set = tps6598x_pr_set,
-};
-
-static irqreturn_t tps6598x_interrupt(int irq, void *data)
-{
- struct tps6598x *tps = data;
- u64 event1;
- u64 event2;
- u32 status;
- int ret;
-
- mutex_lock(&tps->lock);
-
- ret = tps6598x_read64(tps, TPS_REG_INT_EVENT1, &event1);
- ret |= tps6598x_read64(tps, TPS_REG_INT_EVENT2, &event2);
- if (ret) {
- dev_err(tps->dev, "%s: failed to read events\n", __func__);
- goto err_unlock;
- }
-
- ret = tps6598x_read32(tps, TPS_REG_STATUS, &status);
- if (ret) {
- dev_err(tps->dev, "%s: failed to read status\n", __func__);
- goto err_clear_ints;
- }
-
- /* Handle plug insert or removal */
- if ((event1 | event2) & TPS_REG_INT_PLUG_EVENT) {
- if (status & TPS_STATUS_PLUG_PRESENT) {
- ret = tps6598x_connect(tps, status);
- if (ret)
- dev_err(tps->dev,
- "failed to register partner\n");
- } else {
- tps6598x_disconnect(tps, status);
- }
- }
-
-err_clear_ints:
- tps6598x_write64(tps, TPS_REG_INT_CLEAR1, event1);
- tps6598x_write64(tps, TPS_REG_INT_CLEAR2, event2);
-
-err_unlock:
- mutex_unlock(&tps->lock);
-
- return IRQ_HANDLED;
-}
-
-static int tps6598x_check_mode(struct tps6598x *tps)
-{
- char mode[5] = { };
- int ret;
-
- ret = tps6598x_read32(tps, TPS_REG_MODE, (void *)mode);
- if (ret)
- return ret;
-
- switch (match_string(modes, ARRAY_SIZE(modes), mode)) {
- case TPS_MODE_APP:
- return 0;
- case TPS_MODE_BOOT:
- dev_warn(tps->dev, "dead-battery condition\n");
- return 0;
- case TPS_MODE_BIST:
- case TPS_MODE_DISC:
- default:
- dev_err(tps->dev, "controller in unsupported mode \"%s\"\n",
- mode);
- break;
- }
-
- return -ENODEV;
-}
-
-static const struct regmap_config tps6598x_regmap_config = {
- .reg_bits = 8,
- .val_bits = 8,
- .max_register = 0x7F,
-};
-
-static int tps6598x_psy_get_online(struct tps6598x *tps,
- union power_supply_propval *val)
-{
- int ret;
- u16 pwr_status;
-
- ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &pwr_status);
- if (ret < 0)
- return ret;
-
- if ((pwr_status & TPS_POWER_STATUS_CONNECTION) &&
- (pwr_status & TPS_POWER_STATUS_SOURCESINK)) {
- val->intval = 1;
- } else {
- val->intval = 0;
- }
- return 0;
-}
-
-static int tps6598x_psy_get_prop(struct power_supply *psy,
- enum power_supply_property psp,
- union power_supply_propval *val)
-{
- struct tps6598x *tps = power_supply_get_drvdata(psy);
- u16 pwr_status;
- int ret = 0;
-
- switch (psp) {
- case POWER_SUPPLY_PROP_USB_TYPE:
- ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &pwr_status);
- if (ret < 0)
- return ret;
- if (TPS_POWER_STATUS_PWROPMODE(pwr_status) == TYPEC_PWR_MODE_PD)
- val->intval = POWER_SUPPLY_USB_TYPE_PD;
- else
- val->intval = POWER_SUPPLY_USB_TYPE_C;
- break;
- case POWER_SUPPLY_PROP_ONLINE:
- ret = tps6598x_psy_get_online(tps, val);
- break;
- default:
- ret = -EINVAL;
- break;
- }
-
- return ret;
-}
-
-static int devm_tps6598_psy_register(struct tps6598x *tps)
-{
- struct power_supply_config psy_cfg = {};
- const char *port_dev_name = dev_name(tps->dev);
- char *psy_name;
-
- psy_cfg.drv_data = tps;
- psy_cfg.fwnode = dev_fwnode(tps->dev);
-
- psy_name = devm_kasprintf(tps->dev, GFP_KERNEL, "%s%s", tps6598x_psy_name_prefix,
- port_dev_name);
- if (!psy_name)
- return -ENOMEM;
-
- tps->psy_desc.name = psy_name;
- tps->psy_desc.type = POWER_SUPPLY_TYPE_USB;
- tps->psy_desc.usb_types = tps6598x_psy_usb_types;
- tps->psy_desc.num_usb_types = ARRAY_SIZE(tps6598x_psy_usb_types);
- tps->psy_desc.properties = tps6598x_psy_props;
- tps->psy_desc.num_properties = ARRAY_SIZE(tps6598x_psy_props);
- tps->psy_desc.get_property = tps6598x_psy_get_prop;
-
- tps->usb_type = POWER_SUPPLY_USB_TYPE_C;
-
- tps->psy = devm_power_supply_register(tps->dev, &tps->psy_desc,
- &psy_cfg);
- return PTR_ERR_OR_ZERO(tps->psy);
-}
-
-static int tps6598x_probe(struct i2c_client *client)
-{
- struct typec_capability typec_cap = { };
- struct tps6598x *tps;
- struct fwnode_handle *fwnode;
- u32 status;
- u32 conf;
- u32 vid;
- int ret;
-
- tps = devm_kzalloc(&client->dev, sizeof(*tps), GFP_KERNEL);
- if (!tps)
- return -ENOMEM;
-
- mutex_init(&tps->lock);
- tps->dev = &client->dev;
-
- tps->regmap = devm_regmap_init_i2c(client, &tps6598x_regmap_config);
- if (IS_ERR(tps->regmap))
- return PTR_ERR(tps->regmap);
-
- ret = tps6598x_read32(tps, TPS_REG_VID, &vid);
- if (ret < 0 || !vid)
- return -ENODEV;
-
- /*
- * Checking can the adapter handle SMBus protocol. If it can not, the
- * driver needs to take care of block reads separately.
- *
- * FIXME: Testing with I2C_FUNC_I2C. regmap-i2c uses I2C protocol
- * unconditionally if the adapter has I2C_FUNC_I2C set.
- */
- if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
- tps->i2c_protocol = true;
-
- /* Make sure the controller has application firmware running */
- ret = tps6598x_check_mode(tps);
- if (ret)
- return ret;
-
- ret = tps6598x_read32(tps, TPS_REG_STATUS, &status);
- if (ret < 0)
- return ret;
-
- ret = tps6598x_read32(tps, TPS_REG_SYSTEM_CONF, &conf);
- if (ret < 0)
- return ret;
-
- fwnode = device_get_named_child_node(&client->dev, "connector");
- if (IS_ERR(fwnode))
- return PTR_ERR(fwnode);
-
- tps->role_sw = fwnode_usb_role_switch_get(fwnode);
- if (IS_ERR(tps->role_sw)) {
- ret = PTR_ERR(tps->role_sw);
- goto err_fwnode_put;
- }
-
- typec_cap.revision = USB_TYPEC_REV_1_2;
- typec_cap.pd_revision = 0x200;
- typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE;
- typec_cap.driver_data = tps;
- typec_cap.ops = &tps6598x_ops;
- typec_cap.fwnode = fwnode;
-
- switch (TPS_SYSCONF_PORTINFO(conf)) {
- case TPS_PORTINFO_SINK_ACCESSORY:
- case TPS_PORTINFO_SINK:
- typec_cap.type = TYPEC_PORT_SNK;
- typec_cap.data = TYPEC_PORT_UFP;
- break;
- case TPS_PORTINFO_DRP_UFP_DRD:
- case TPS_PORTINFO_DRP_DFP_DRD:
- typec_cap.type = TYPEC_PORT_DRP;
- typec_cap.data = TYPEC_PORT_DRD;
- break;
- case TPS_PORTINFO_DRP_UFP:
- typec_cap.type = TYPEC_PORT_DRP;
- typec_cap.data = TYPEC_PORT_UFP;
- break;
- case TPS_PORTINFO_DRP_DFP:
- typec_cap.type = TYPEC_PORT_DRP;
- typec_cap.data = TYPEC_PORT_DFP;
- break;
- case TPS_PORTINFO_SOURCE:
- typec_cap.type = TYPEC_PORT_SRC;
- typec_cap.data = TYPEC_PORT_DFP;
- break;
- default:
- ret = -ENODEV;
- goto err_role_put;
- }
-
- ret = devm_tps6598_psy_register(tps);
- if (ret)
- return ret;
-
- tps->port = typec_register_port(&client->dev, &typec_cap);
- if (IS_ERR(tps->port)) {
- ret = PTR_ERR(tps->port);
- goto err_role_put;
- }
- fwnode_handle_put(fwnode);
-
- if (status & TPS_STATUS_PLUG_PRESENT) {
- ret = tps6598x_connect(tps, status);
- if (ret)
- dev_err(&client->dev, "failed to register partner\n");
- }
-
- ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
- tps6598x_interrupt,
- IRQF_SHARED | IRQF_ONESHOT,
- dev_name(&client->dev), tps);
- if (ret) {
- tps6598x_disconnect(tps, 0);
- typec_unregister_port(tps->port);
- goto err_role_put;
- }
-
- i2c_set_clientdata(client, tps);
-
- return 0;
-
-err_role_put:
- usb_role_switch_put(tps->role_sw);
-err_fwnode_put:
- fwnode_handle_put(fwnode);
-
- return ret;
-}
-
-static int tps6598x_remove(struct i2c_client *client)
-{
- struct tps6598x *tps = i2c_get_clientdata(client);
-
- tps6598x_disconnect(tps, 0);
- typec_unregister_port(tps->port);
- usb_role_switch_put(tps->role_sw);
-
- return 0;
-}
-
-static const struct of_device_id tps6598x_of_match[] = {
- { .compatible = "ti,tps6598x", },
- {}
-};
-MODULE_DEVICE_TABLE(of, tps6598x_of_match);
-
-static const struct i2c_device_id tps6598x_id[] = {
- { "tps6598x" },
- { }
-};
-MODULE_DEVICE_TABLE(i2c, tps6598x_id);
-
-static struct i2c_driver tps6598x_i2c_driver = {
- .driver = {
- .name = "tps6598x",
- .of_match_table = tps6598x_of_match,
- },
- .probe_new = tps6598x_probe,
- .remove = tps6598x_remove,
- .id_table = tps6598x_id,
-};
-module_i2c_driver(tps6598x_i2c_driver);
-
-MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
-MODULE_LICENSE("GPL v2");
-MODULE_DESCRIPTION("TI TPS6598x USB Power Delivery Controller Driver");
u16 error;
int ret;
- /* Acknowlege the command that failed */
+ /* Acknowledge the command that failed */
ret = ucsi_acknowledge_command(ucsi);
if (ret)
return ret;
tcp_rx = kthread_create(&v_rx_loop, &udc->ud, "vudc_rx");
if (IS_ERR(tcp_rx)) {
sockfd_put(socket);
+ mutex_unlock(&udc->ud.sysfs_lock);
return -EINVAL;
}
tcp_tx = kthread_create(&v_tx_loop, &udc->ud, "vudc_tx");
if (IS_ERR(tcp_tx)) {
kthread_stop(tcp_rx);
sockfd_put(socket);
+ mutex_unlock(&udc->ud.sysfs_lock);
return -EINVAL;
}
+++ /dev/null
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2009 Martin Fuzzey <mfuzzey@gmail.com>
- */
-
-#ifndef __ASM_ARCH_MX21_USBH
-#define __ASM_ARCH_MX21_USBH
-
-enum mx21_usbh_xcvr {
- /* Values below as used by hardware (HWMODE register) */
- MX21_USBXCVR_TXDIF_RXDIF = 0,
- MX21_USBXCVR_TXDIF_RXSE = 1,
- MX21_USBXCVR_TXSE_RXDIF = 2,
- MX21_USBXCVR_TXSE_RXSE = 3,
-};
-
-struct mx21_usbh_platform_data {
- enum mx21_usbh_xcvr host_xcvr; /* tranceiver mode host 1,2 ports */
- enum mx21_usbh_xcvr otg_xcvr; /* tranceiver mode otg (as host) port */
- u16 enable_host1:1,
- enable_host2:1,
- enable_otg_host:1, /* enable "OTG" port (as host) */
- host1_xcverless:1, /* traceiverless host1 port */
- host1_txenoe:1, /* output enable host1 transmit enable */
- otg_ext_xcvr:1, /* external tranceiver for OTG port */
- unused:10;
-};
-
-#endif /* __ASM_ARCH_MX21_USBH */
extern int power_supply_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val);
+#if IS_ENABLED(CONFIG_POWER_SUPPLY)
extern int power_supply_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val);
+#else
+static inline int power_supply_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{ return 0; }
+#endif
extern int power_supply_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp);
extern void power_supply_external_power_changed(struct power_supply *psy);
size_t block_len);
ssize_t tb_property_format_dir(const struct tb_property_dir *dir, u32 *block,
size_t block_len);
+struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir);
struct tb_property_dir *tb_property_create_dir(const uuid_t *uuid);
void tb_property_free_dir(struct tb_property_dir *dir);
int tb_property_add_immediate(struct tb_property_dir *parent, const char *key,
* @route: Route string the other domain can be reached
* @vendor: Vendor ID of the remote domain
* @device: Device ID of the demote domain
+ * @local_max_hopid: Maximum input HopID of this host
+ * @remote_max_hopid: Maximum input HopID of the remote host
* @lock: Lock to serialize access to the following fields of this structure
* @vendor_name: Name of the vendor (or %NULL if not known)
* @device_name: Name of the device (or %NULL if not known)
* @link_speed: Speed of the link in Gb/s
* @link_width: Width of the link (1 or 2)
* @is_unplugged: The XDomain is unplugged
- * @resume: The XDomain is being resumed
* @needs_uuid: If the XDomain does not have @remote_uuid it will be
* queried first
- * @transmit_path: HopID which the remote end expects us to transmit
- * @transmit_ring: Local ring (hop) where outgoing packets are pushed
- * @receive_path: HopID which we expect the remote end to transmit
- * @receive_ring: Local ring (hop) where incoming packets arrive
* @service_ids: Used to generate IDs for the services
- * @properties: Properties exported by the remote domain
- * @property_block_gen: Generation of @properties
- * @properties_lock: Lock protecting @properties.
+ * @in_hopids: Input HopIDs for DMA tunneling
+ * @out_hopids; Output HopIDs for DMA tunneling
+ * @local_property_block: Local block of properties
+ * @local_property_block_gen: Generation of @local_property_block
+ * @local_property_block_len: Length of the @local_property_block in dwords
+ * @remote_properties: Properties exported by the remote domain
+ * @remote_property_block_gen: Generation of @remote_properties
* @get_uuid_work: Work used to retrieve @remote_uuid
* @uuid_retries: Number of times left @remote_uuid is requested before
* giving up
u64 route;
u16 vendor;
u16 device;
+ unsigned int local_max_hopid;
+ unsigned int remote_max_hopid;
struct mutex lock;
const char *vendor_name;
const char *device_name;
unsigned int link_speed;
unsigned int link_width;
bool is_unplugged;
- bool resume;
bool needs_uuid;
- u16 transmit_path;
- u16 transmit_ring;
- u16 receive_path;
- u16 receive_ring;
struct ida service_ids;
- struct tb_property_dir *properties;
- u32 property_block_gen;
+ struct ida in_hopids;
+ struct ida out_hopids;
+ u32 *local_property_block;
+ u32 local_property_block_gen;
+ u32 local_property_block_len;
+ struct tb_property_dir *remote_properties;
+ u32 remote_property_block_gen;
struct delayed_work get_uuid_work;
int uuid_retries;
struct delayed_work get_properties_work;
int tb_xdomain_lane_bonding_enable(struct tb_xdomain *xd);
void tb_xdomain_lane_bonding_disable(struct tb_xdomain *xd);
-int tb_xdomain_enable_paths(struct tb_xdomain *xd, u16 transmit_path,
- u16 transmit_ring, u16 receive_path,
- u16 receive_ring);
-int tb_xdomain_disable_paths(struct tb_xdomain *xd);
+int tb_xdomain_alloc_in_hopid(struct tb_xdomain *xd, int hopid);
+void tb_xdomain_release_in_hopid(struct tb_xdomain *xd, int hopid);
+int tb_xdomain_alloc_out_hopid(struct tb_xdomain *xd, int hopid);
+void tb_xdomain_release_out_hopid(struct tb_xdomain *xd, int hopid);
+int tb_xdomain_enable_paths(struct tb_xdomain *xd, int transmit_path,
+ int transmit_ring, int receive_path,
+ int receive_ring);
+int tb_xdomain_disable_paths(struct tb_xdomain *xd, int transmit_path,
+ int transmit_ring, int receive_path,
+ int receive_ring);
+
+static inline int tb_xdomain_disable_all_paths(struct tb_xdomain *xd)
+{
+ return tb_xdomain_disable_paths(xd, -1, -1, -1, -1);
+}
+
struct tb_xdomain *tb_xdomain_find_by_uuid(struct tb *tb, const uuid_t *uuid);
struct tb_xdomain *tb_xdomain_find_by_route(struct tb *tb, u64 route);
* @speed: device speed: high/full/low (or error)
* @rx_lanes: number of rx lanes in use, USB 3.2 adds dual-lane support
* @tx_lanes: number of tx lanes in use, USB 3.2 adds dual-lane support
+ * @ssp_rate: SuperSpeed Plus phy signaling rate and lane count
* @tt: Transaction Translator info; used with low/full speed dev, highspeed hub
* @ttport: device port on that tt hub
* @toggle: one bit for each endpoint, with ([0] = IN, [1] = OUT) endpoints
enum usb_device_speed speed;
unsigned int rx_lanes;
unsigned int tx_lanes;
+ enum usb_ssp_rate ssp_rate;
struct usb_tt *tt;
int ttport;
/* used these for multi-interface device registration */
extern int usb_driver_claim_interface(struct usb_driver *driver,
- struct usb_interface *iface, void *priv);
+ struct usb_interface *iface, void *data);
/**
* usb_interface_claimed - returns true iff an interface is claimed
unsigned int iface_num,
unsigned int alt_num);
+#if IS_REACHABLE(CONFIG_USB)
+int usb_for_each_port(void *data, int (*fn)(struct device *, void *));
+#else
+static inline int usb_for_each_port(void *data, int (*fn)(struct device *, void *))
+{
+ return 0;
+}
+#endif
+
/* port claiming functions */
int usb_hub_claim_port(struct usb_device *hdev, unsigned port1,
struct usb_dev_state *owner);
#define to_usb_device_driver(d) container_of(d, struct usb_device_driver, \
drvwrap.driver)
-extern struct bus_type usb_bus_type;
-
/**
* struct usb_class_driver - identifies a USB driver that wants to use the USB major number
* @name: the usb class device name for this driver. Will show up in sysfs.
USB_SSP_GEN_2x2,
};
-/**
- * usb_ep_type_string() - Returns human readable-name of the endpoint type.
- * @ep_type: The endpoint type to return human-readable name for. If it's not
- * any of the types: USB_ENDPOINT_XFER_{CONTROL, ISOC, BULK, INT},
- * usually got by usb_endpoint_type(), the string 'unknown' will be returned.
- */
extern const char *usb_ep_type_string(int ep_type);
-
-/**
- * usb_speed_string() - Returns human readable-name of the speed.
- * @speed: The speed to return human-readable name for. If it's not
- * any of the speeds defined in usb_device_speed enum, string for
- * USB_SPEED_UNKNOWN will be returned.
- */
extern const char *usb_speed_string(enum usb_device_speed speed);
-
-/**
- * usb_get_maximum_speed - Get maximum requested speed for a given USB
- * controller.
- * @dev: Pointer to the given USB controller device
- *
- * The function gets the maximum speed string from property "maximum-speed",
- * and returns the corresponding enum usb_device_speed.
- */
extern enum usb_device_speed usb_get_maximum_speed(struct device *dev);
-
-/**
- * usb_get_maximum_ssp_rate - Get the signaling rate generation and lane count
- * of a SuperSpeed Plus capable device.
- * @dev: Pointer to the given USB controller device
- *
- * If the string from "maximum-speed" property is super-speed-plus-genXxY where
- * 'X' is the generation number and 'Y' is the number of lanes, then this
- * function returns the corresponding enum usb_ssp_rate.
- */
extern enum usb_ssp_rate usb_get_maximum_ssp_rate(struct device *dev);
-
-/**
- * usb_state_string - Returns human readable name for the state.
- * @state: The state to return a human-readable name for. If it's not
- * any of the states devices in usb_device_state_string enum,
- * the string UNKNOWN will be returned.
- */
extern const char *usb_state_string(enum usb_device_state state);
+unsigned int usb_decode_interval(const struct usb_endpoint_descriptor *epd,
+ enum usb_device_speed speed);
#ifdef CONFIG_TRACING
-/**
- * usb_decode_ctrl - Returns human readable representation of control request.
- * @str: buffer to return a human-readable representation of control request.
- * This buffer should have about 200 bytes.
- * @size: size of str buffer.
- * @bRequestType: matches the USB bmRequestType field
- * @bRequest: matches the USB bRequest field
- * @wValue: matches the USB wValue field (CPU byte order)
- * @wIndex: matches the USB wIndex field (CPU byte order)
- * @wLength: matches the USB wLength field (CPU byte order)
- *
- * Function returns decoded, formatted and human-readable description of
- * control request packet.
- *
- * The usage scenario for this is for tracepoints, so function as a return
- * use the same value as in parameters. This approach allows to use this
- * function in TP_printk
- *
- * Important: wValue, wIndex, wLength parameters before invoking this function
- * should be processed by le16_to_cpu macro.
- */
extern const char *usb_decode_ctrl(char *str, size_t size, __u8 bRequestType,
__u8 bRequest, __u16 wValue, __u16 wIndex,
__u16 wLength);
unsigned no_io_watchdog:1;
unsigned reset_on_resume:1;
unsigned dma_mask_64:1;
+ unsigned spurious_oc:1;
/* Turn on all power and clocks */
int (*power_on)(struct platform_device *pdev);
#define PD_N_CAPS_COUNT (PD_T_NO_RESPONSE / PD_T_SEND_SOURCE_CAP)
#define PD_N_HARD_RESET_COUNT 2
+#define PD_P_SNK_STDBY_MW 2500 /* 2500 mW */
+
#endif /* __LINUX_USB_PD_H */
* @dev: pointer to the struct usb_device for this device
* @type: pointer to the struct usb_serial_driver for this device
* @interface: pointer to the struct usb_interface for this device
+ * @sibling: pointer to the struct usb_interface of any sibling interface
+ * @suspend_count: number of suspended (sibling) interfaces
* @num_ports: the number of ports this device has
* @num_interrupt_in: number of interrupt in endpoints we have
* @num_interrupt_out: number of interrupt out endpoints we have
struct usb_device *dev;
struct usb_serial_driver *type;
struct usb_interface *interface;
+ struct usb_interface *sibling;
+ unsigned int suspend_count;
unsigned char disconnected:1;
- unsigned char suspending:1;
unsigned char attached:1;
unsigned char minors_reserved:1;
unsigned char num_ports;
int (*write_room)(struct tty_struct *tty);
int (*ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
- int (*get_serial)(struct tty_struct *tty, struct serial_struct *ss);
+ void (*get_serial)(struct tty_struct *tty, struct serial_struct *ss);
int (*set_serial)(struct tty_struct *tty, struct serial_struct *ss);
void (*set_termios)(struct tty_struct *tty,
struct usb_serial_port *port, struct ktermios *old);
/* Functions needed by other parts of the usbserial core */
struct usb_serial_port *usb_serial_port_get_by_minor(unsigned int minor);
void usb_serial_put(struct usb_serial *serial);
+
+int usb_serial_claim_interface(struct usb_serial *serial, struct usb_interface *intf);
+
int usb_serial_generic_open(struct tty_struct *tty, struct usb_serial_port *port);
int usb_serial_generic_write_start(struct usb_serial_port *port, gfp_t mem_flags);
int usb_serial_generic_write(struct tty_struct *tty, struct usb_serial_port *port,
struct typec_cable;
struct typec_plug;
struct typec_port;
+struct typec_altmode_ops;
struct fwnode_handle;
struct device;
struct typec_altmode
*typec_port_register_altmode(struct typec_port *port,
const struct typec_altmode_desc *desc);
+
+void typec_port_register_altmodes(struct typec_port *port,
+ const struct typec_altmode_ops *ops, void *drvdata,
+ struct typec_altmode **altmodes, size_t n);
+
void typec_unregister_altmode(struct typec_altmode *altmode);
struct typec_port *typec_altmode2port(struct typec_altmode *alt);
void typec_partner_set_svdm_version(struct typec_partner *partner,
enum usb_pd_svdm_ver svdm_version);
int typec_get_negotiated_svdm_version(struct typec_port *port);
+
+#if IS_REACHABLE(CONFIG_TYPEC)
+int typec_link_port(struct device *port);
+void typec_unlink_port(struct device *port);
+#else
+static inline int typec_link_port(struct device *port)
+{
+ return 0;
+}
+
+static inline void typec_unlink_port(struct device *port) { }
+#endif
+
#endif /* __LINUX_USB_TYPEC_H */
__u8 bControlSize;
__u8 bmControls[2];
__u8 iProcessing;
+ __u8 bmVideoStandards;
} __attribute__((__packed__));
-#define UVC_DT_PROCESSING_UNIT_SIZE(n) (9+(n))
+#define UVC_DT_PROCESSING_UNIT_SIZE(n) (10+(n))
/* 3.7.2.6. Extension Unit Descriptor */
struct uvc_extension_unit_descriptor {
Attach a remote USB device.
.PP
+.HP
+\fBattach\fR \-\-remote=<\fIhost\fR> \-\-device=<\fIdev_id\fR>
+.IP
+Attach a remote USB gadget.
+Only used when the remote usbipd is in device mode.
+.PP
+
.HP
\fBdetach\fR \-\-port=<\fIport\fR>
.IP
-Detach an imported USB device.
+Detach an imported USB device/gadget.
.PP
.HP
List USB devices exported by a remote host.
.PP
+.HP
+\fBlist\fR \-\-device
+.IP
+List USB gadgets of local usbip-vudc.
+Only used when the local usbipd is in device mode.
+Note that this can not list usbip-vudc USB gadgets of the remote device mode usbipd.
+.PP
+
.HP
\fBlist\fR \-\-local
.IP
List local USB devices.
.PP
+.HP
+\fBport\fR
+.IP
+List imported devices/gadgets.
+.PP
+
.SH EXAMPLES
client:# usbip attach --remote=server --busid=1-2
- Connect the remote USB device.
+ client:# usbip port
+ - List imported devices/gadgets.
+
client:# usbip detach --port=0
- Detach the usb device.
+The following example shows the usage of device mode
+
+ server:# usbip list --device
+ - List gadgets exported by local usbipd server.
+
+ client:# modprobe vhci-hcd
+
+ client:# usbip attach --remote=server --device=usbip-vudc.0
+ - Connect the remote USB gadget.
+
+ client:# usbip port
+ - List imported devices/gadgets.
+
+ client:# usbip detach --port=0
+ - Detach the usb gadget.
+
.SH "SEE ALSO"
\fBusbipd\fP\fB(8)\fB\fP
Bind to IPv6. Default is both.
.PP
+.HP
+\fB\-e\fR, \fB\-\-device\fR
+.IP
+Run in device mode. Rather than drive an attached device, create a virtual UDC to bind gadgets to.
+.PP
+
.HP
\fB\-D\fR, \fB\-\-daemon\fR
.IP
- A usb device 1-2 is now exportable to other hosts!
- Use 'usbip unbind --busid=1-2' when you want to shutdown exporting and use the device locally.
+The following example shows the usage of device mode
+
+ server:# modprobe usbip-vudc
+ - Use /sys/class/udc/ interface.
+ - usbip-host is independent of this module.
+
+ server:# usbipd -e -D
+ - Start usbip daemon in device mode.
+
+ server:# modprobe g_mass_storage file=/tmp/tmp.img
+ - Bind a gadget to usbip-vudc.
+ - in this example, a mass storage gadget is bound.
+
+ server:# usbip list --device
+ - List gadgets exported by local usbipd server.
+
+ server:# modprobe -r g_mass_storage
+ - Unbind a gadget from usbip-vudc.
+ - in this example, the previous mass storage gadget is unbound.
+
.SH "SEE ALSO"
\fBusbip\fP\fB(8)\fB\fP
#define LIST_POISON1 ((void *) 0x00100100 + POISON_POINTER_DELTA)
#define LIST_POISON2 ((void *) 0x00200200 + POISON_POINTER_DELTA)
+static inline void __list_del_entry(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+}
+
/**
* list_del - deletes entry from list.
* @entry: the element to delete from the list.
* Note: list_empty() on entry does not return true after this, the entry is
* in an undefined state.
*/
-static inline void __list_del_entry(struct list_head *entry)
-{
- __list_del(entry->prev, entry->next);
-}
-
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
"usbip list [-p|--parsable] <args>\n"
" -p, --parsable Parsable list format\n"
" -r, --remote=<host> List the exportable USB devices on <host>\n"
- " -l, --local List the local USB devices\n";
+ " -l, --local List the local USB devices\n"
+ " -d, --device List the local USB gadgets bound to usbip-vudc\n";
void usbip_list_usage(void)
{