USB/PHY/Thunderbolt driver patches for 5.10-rc1

Here is the big set of USB, PHY, and Thunderbolt driver updates for
 5.10-rc1.
 
 Lots of tiny different things for these subsystems are in here,
 including:
 	- phy driver updates
 	- thunderbolt / USB 4 updates and additions
 	- USB gadget driver updates
 	- xhci fixes and updates
 	- typec driver additions and updates
 	- api conversions to various drivers for core kernel api changes
 	- new USB control message functions to make it harder to get
 	  wrong, as found by syzbot (took 2 tries to get it right)
 	- lots of tiny USB driver fixes and updates all over the place
 
 All of these have been in linux-next for a while, with the exception of
 the last "obviously correct" patch that updated a FALLTHROUGH comment
 that got merged last weekend.
 
 Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
 -----BEGIN PGP SIGNATURE-----
 
 iG0EABECAC0WIQT0tgzFv3jCIUoxPcsxR9QN2y37KQUCX4hAAg8cZ3JlZ0Brcm9h
 aC5jb20ACgkQMUfUDdst+ykfRACcCp48StLg4V7XcZ41eQYES/DVwxkAnjnZs+La
 Y7F+o2p8DiuLLQamdEyB
 =lHr1
 -----END PGP SIGNATURE-----

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

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

  Lots of tiny different things for these subsystems are in here,
  including:

   - phy driver updates

   - thunderbolt / USB 4 updates and additions

   - USB gadget driver updates

   - xhci fixes and updates

   - typec driver additions and updates

   - api conversions to various drivers for core kernel api changes

   - new USB control message functions to make it harder to get wrong,
     as found by syzbot (took 2 tries to get it right)

   - lots of tiny USB driver fixes and updates all over the place

  All of these have been in linux-next for a while, with the exception
  of the last "obviously correct" patch that updated a FALLTHROUGH
  comment that got merged last weekend"

* tag 'usb-5.10-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (374 commits)
  usb: musb: gadget: Use fallthrough pseudo-keyword
  usb: typec: Add QCOM PMIC typec detection driver
  USB: serial: option: add Cellient MPL200 card
  usb: typec: tcpci_maxim: Add support for Sink FRS
  usb: typec: tcpci: Implement callbacks for FRS
  usb: typec: tcpm: Add support for Sink Fast Role SWAP(FRS)
  usb: typec: tcpci_maxim: Chip level TCPC driver
  usb: typec: tcpci: Add set_vbus tcpci callback
  usb: typec: tcpci: Add a getter method to retrieve tcpm_port reference
  usbip: vhci_hcd: fix calling usb_hcd_giveback_urb() with irqs enabled
  usb: cdc-acm: add quirk to blacklist ETAS ES58X devices
  USB: serial: ftdi_sio: use cur_altsetting for consistency
  USB: serial: option: Add Telit FT980-KS composition
  USB: core: remove polling for /sys/kernel/debug/usb/devices
  usb: typec: add support for STUSB160x Type-C controller family
  usb: typec: add typec_find_pwr_opmode
  usb: typec: hd3ss3220: Use OF graph API to get the connector fwnode
  dt-bindings: usb: renesas,usb3-peri: Document HS and SS data bus
  dt-bindings: usb: convert ti,hd3ss3220 bindings to json-schema
  usb: dwc2: Fix INTR OUT transfers in DDMA mode.
  ...
This commit is contained in:
Linus Torvalds 2020-10-15 09:51:18 -07:00
commit c6dbef7307
260 changed files with 12967 additions and 2908 deletions

View File

@ -48,6 +48,22 @@ properties:
- compatible
- "#clock-cells"
reset:
type: object
properties:
compatible:
const: raspberrypi,firmware-reset
"#reset-cells":
const: 1
description: >
The argument is the ID of the firmware reset line to affect.
required:
- compatible
- "#reset-cells"
additionalProperties: false
required:
@ -66,5 +82,10 @@ examples:
compatible = "raspberrypi,firmware-clocks";
#clock-cells = <1>;
};
reset: reset {
compatible = "raspberrypi,firmware-reset";
#reset-cells = <1>;
};
};
...

View File

@ -1,7 +1,7 @@
* Freescale i.MX8MQ USB3 PHY binding
Required properties:
- compatible: Should be "fsl,imx8mq-usb-phy"
- compatible: Should be "fsl,imx8mq-usb-phy" or "fsl,imx8mp-usb-phy"
- #phys-cells: must be 0 (see phy-bindings.txt in this directory)
- reg: The base address and length of the registers
- clocks: phandles to the clocks for each clock listed in clock-names

View File

@ -23,7 +23,9 @@ description: |+
properties:
compatible:
const: intel,lgm-emmc-phy
oneOf:
- const: intel,lgm-emmc-phy
- const: intel,keembay-emmc-phy
"#phy-cells":
const: 0
@ -34,6 +36,10 @@ properties:
clocks:
maxItems: 1
clock-names:
items:
- const: emmcclk
required:
- "#phy-cells"
- compatible
@ -57,4 +63,13 @@ examples:
#phy-cells = <0>;
};
};
- |
phy@20290000 {
compatible = "intel,keembay-emmc-phy";
reg = <0x20290000 0x54>;
clocks = <&emmc>;
clock-names = "emmcclk";
#phy-cells = <0>;
};
...

View File

@ -0,0 +1,58 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/phy/intel,lgm-usb-phy.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Intel LGM USB PHY Device Tree Bindings
maintainers:
- Vadivel Murugan Ramuthevar <vadivel.muruganx.ramuthevar@linux.intel.com>
properties:
compatible:
const: intel,lgm-usb-phy
reg:
maxItems: 1
clocks:
maxItems: 1
resets:
items:
- description: USB PHY and Host controller reset
- description: APB BUS reset
- description: General Hardware reset
reset-names:
items:
- const: phy
- const: apb
- const: phy31
"#phy-cells":
const: 0
required:
- compatible
- clocks
- reg
- resets
- reset-names
- "#phy-cells"
additionalProperties: false
examples:
- |
usb-phy@e7e00000 {
compatible = "intel,lgm-usb-phy";
reg = <0xe7e00000 0x10000>;
clocks = <&cgu0 153>;
resets = <&rcu 0x70 0x24>,
<&rcu 0x70 0x26>,
<&rcu 0x70 0x28>;
reset-names = "phy", "apb", "phy31";
#phy-cells = <0>;
};

View File

@ -4,11 +4,13 @@
$id: "http://devicetree.org/schemas/phy/phy-cadence-torrent.yaml#"
$schema: "http://devicetree.org/meta-schemas/core.yaml#"
title: Cadence Torrent SD0801 PHY binding for DisplayPort
title: Cadence Torrent SD0801 PHY binding
description:
This binding describes the Cadence SD0801 PHY (also known as Torrent PHY)
hardware included with the Cadence MHDP DisplayPort controller.
hardware included with the Cadence MHDP DisplayPort controller. Torrent
PHY also supports multilink multiprotocol combinations including protocols
such as PCIe, USB, SGMII, QSGMII etc.
maintainers:
- Swapnil Jakhade <sjakhade@cadence.com>
@ -49,13 +51,21 @@ properties:
- const: dptx_phy
resets:
maxItems: 1
description:
Torrent PHY reset.
See Documentation/devicetree/bindings/reset/reset.txt
minItems: 1
maxItems: 2
items:
- description: Torrent PHY reset.
- description: Torrent APB reset. This is optional.
reset-names:
minItems: 1
maxItems: 2
items:
- const: torrent_reset
- const: torrent_apb
patternProperties:
'^phy@[0-7]+$':
'^phy@[0-3]$':
type: object
description:
Each group of PHY lanes with a single master lane should be represented as a sub-node.
@ -63,6 +73,8 @@ patternProperties:
reg:
description:
The master lane number. This is the lowest numbered lane in the lane group.
minimum: 0
maximum: 3
resets:
minItems: 1
@ -78,15 +90,25 @@ patternProperties:
Specifies the type of PHY for which the group of PHY lanes is used.
Refer include/dt-bindings/phy/phy.h. Constants from the header should be used.
$ref: /schemas/types.yaml#/definitions/uint32
enum: [1, 2, 3, 4, 5, 6]
minimum: 1
maximum: 9
cdns,num-lanes:
description:
Number of DisplayPort lanes.
Number of lanes.
$ref: /schemas/types.yaml#/definitions/uint32
enum: [1, 2, 4]
enum: [1, 2, 3, 4]
default: 4
cdns,ssc-mode:
description:
Specifies the Spread Spectrum Clocking mode used. It can be NO_SSC,
EXTERNAL_SSC or INTERNAL_SSC.
Refer include/dt-bindings/phy/phy-cadence-torrent.h for the constants to be used.
$ref: /schemas/types.yaml#/definitions/uint32
enum: [0, 1, 2]
default: 0
cdns,max-bit-rate:
description:
Maximum DisplayPort link bit rate to use, in Mbps
@ -99,6 +121,7 @@ patternProperties:
- resets
- "#phy-cells"
- cdns,phy-type
- cdns,num-lanes
additionalProperties: false
@ -111,6 +134,7 @@ required:
- reg
- reg-names
- resets
- reset-names
additionalProperties: false
@ -128,18 +152,56 @@ examples:
<0xf0 0xfb030a00 0x0 0x00000040>;
reg-names = "torrent_phy", "dptx_phy";
resets = <&phyrst 0>;
reset-names = "torrent_reset";
clocks = <&ref_clk>;
clock-names = "refclk";
#address-cells = <1>;
#size-cells = <0>;
phy@0 {
reg = <0>;
resets = <&phyrst 1>, <&phyrst 2>,
<&phyrst 3>, <&phyrst 4>;
#phy-cells = <0>;
cdns,phy-type = <PHY_TYPE_DP>;
cdns,num-lanes = <4>;
cdns,max-bit-rate = <8100>;
reg = <0>;
resets = <&phyrst 1>, <&phyrst 2>,
<&phyrst 3>, <&phyrst 4>;
#phy-cells = <0>;
cdns,phy-type = <PHY_TYPE_DP>;
cdns,num-lanes = <4>;
cdns,max-bit-rate = <8100>;
};
};
};
- |
#include <dt-bindings/phy/phy.h>
#include <dt-bindings/phy/phy-cadence-torrent.h>
bus {
#address-cells = <2>;
#size-cells = <2>;
torrent-phy@f0fb500000 {
compatible = "cdns,torrent-phy";
reg = <0xf0 0xfb500000 0x0 0x00100000>;
reg-names = "torrent_phy";
resets = <&phyrst 0>, <&phyrst 1>;
reset-names = "torrent_reset", "torrent_apb";
clocks = <&ref_clk>;
clock-names = "refclk";
#address-cells = <1>;
#size-cells = <0>;
phy@0 {
reg = <0>;
resets = <&phyrst 2>, <&phyrst 3>;
#phy-cells = <0>;
cdns,phy-type = <PHY_TYPE_PCIE>;
cdns,num-lanes = <2>;
cdns,ssc-mode = <TORRENT_SERDES_NO_SSC>;
};
phy@2 {
reg = <2>;
resets = <&phyrst 4>;
#phy-cells = <0>;
cdns,phy-type = <PHY_TYPE_SGMII>;
cdns,num-lanes = <1>;
cdns,ssc-mode = <TORRENT_SERDES_NO_SSC>;
};
};
};

View File

@ -13,17 +13,21 @@ maintainers:
properties:
compatible:
enum:
- qcom,sc7180-qmp-usb3-dp-phy
- qcom,sc7180-qmp-usb3-phy
- qcom,sdm845-qmp-usb3-dp-phy
- qcom,sdm845-qmp-usb3-phy
reg:
items:
- description: Address and length of PHY's common serdes block.
- description: Address and length of PHY's USB serdes block.
- description: Address and length of the DP_COM control block.
- description: Address and length of PHY's DP serdes block.
reg-names:
items:
- const: reg-base
- const: usb
- const: dp_com
- const: dp
"#clock-cells":
enum: [ 1, 2 ]
@ -74,16 +78,74 @@ properties:
#Required nodes:
patternProperties:
"^phy@[0-9a-f]+$":
"^usb3-phy@[0-9a-f]+$":
type: object
description:
Each device node of QMP phy is required to have as many child nodes as
the number of lanes the PHY has.
The USB3 PHY.
properties:
reg:
items:
- description: Address and length of TX.
- description: Address and length of RX.
- description: Address and length of PCS.
- description: Address and length of TX2.
- description: Address and length of RX2.
- description: Address and length of pcs_misc.
clocks:
items:
- description: pipe clock
clock-names:
items:
- const: pipe0
clock-output-names:
items:
- const: usb3_phy_pipe_clk_src
'#clock-cells':
const: 0
'#phy-cells':
const: 0
required:
- reg
- clocks
- clock-names
- '#clock-cells'
- '#phy-cells'
"^dp-phy@[0-9a-f]+$":
type: object
description:
The DP PHY.
properties:
reg:
items:
- description: Address and length of TX.
- description: Address and length of RX.
- description: Address and length of PCS.
- description: Address and length of TX2.
- description: Address and length of RX2.
'#clock-cells':
const: 1
'#phy-cells':
const: 0
required:
- reg
- '#clock-cells'
- '#phy-cells'
required:
- compatible
- reg
- reg-names
- "#clock-cells"
- "#address-cells"
- "#size-cells"
@ -101,14 +163,15 @@ examples:
- |
#include <dt-bindings/clock/qcom,gcc-sdm845.h>
usb_1_qmpphy: phy-wrapper@88e9000 {
compatible = "qcom,sdm845-qmp-usb3-phy";
compatible = "qcom,sdm845-qmp-usb3-dp-phy";
reg = <0x088e9000 0x18c>,
<0x088e8000 0x10>;
reg-names = "reg-base", "dp_com";
<0x088e8000 0x10>,
<0x088ea000 0x40>;
reg-names = "usb", "dp_com", "dp";
#clock-cells = <1>;
#address-cells = <1>;
#size-cells = <1>;
ranges = <0x0 0x088e9000 0x1000>;
ranges = <0x0 0x088e9000 0x2000>;
clocks = <&gcc GCC_USB3_PRIM_PHY_AUX_CLK>,
<&gcc GCC_USB_PHY_CFG_AHB2PHY_CLK>,
@ -123,7 +186,7 @@ examples:
vdda-phy-supply = <&vdda_usb2_ss_1p2>;
vdda-pll-supply = <&vdda_usb2_ss_core>;
phy@200 {
usb3-phy@200 {
reg = <0x200 0x128>,
<0x400 0x200>,
<0xc00 0x218>,
@ -136,4 +199,14 @@ examples:
clock-names = "pipe0";
clock-output-names = "usb3_phy_pipe_clk_src";
};
dp-phy@88ea200 {
reg = <0xa200 0x200>,
<0xa400 0x200>,
<0xaa00 0x200>,
<0xa600 0x200>,
<0xa800 0x200>;
#clock-cells = <1>;
#phy-cells = <0>;
};
};

View File

@ -0,0 +1,76 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/phy/socionext,uniphier-ahci-phy.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Socionext UniPhier AHCI PHY
description: |
This describes the deivcetree bindings for PHY interfaces built into
AHCI controller implemented on Socionext UniPhier SoCs.
maintainers:
- Kunihiko Hayashi <hayashi.kunihiko@socionext.com>
properties:
compatible:
enum:
- socionext,uniphier-pxs2-ahci-phy
- socionext,uniphier-pxs3-ahci-phy
reg:
description: PHY register region (offset and length)
"#phy-cells":
const: 0
clocks:
maxItems: 2
clock-names:
oneOf:
- items: # for PXs2
- const: link
- items: # for others
- const: link
- const: phy
resets:
maxItems: 2
reset-names:
items:
- const: link
- const: phy
required:
- compatible
- reg
- "#phy-cells"
- clocks
- clock-names
- resets
- reset-names
additionalProperties: false
examples:
- |
ahci-glue@65700000 {
compatible = "socionext,uniphier-pxs3-ahci-glue",
"simple-mfd";
#address-cells = <1>;
#size-cells = <1>;
ranges = <0 0x65700000 0x100>;
ahci_phy: phy@10 {
compatible = "socionext,uniphier-pxs3-ahci-phy";
reg = <0x10 0x10>;
#phy-cells = <0>;
clock-names = "link", "phy";
clocks = <&sys_clk 28>, <&sys_clk 30>;
reset-names = "link", "phy";
resets = <&sys_rst 28>, <&sys_rst 30>;
};
};

View File

@ -0,0 +1,74 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/phy/ti,omap-usb2.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: OMAP USB2 PHY
maintainers:
- Kishon Vijay Abraham I <kishon@ti.com>
- Roger Quadros <rogerq@ti.com>
properties:
compatible:
oneOf:
- items:
- enum:
- ti,dra7x-usb2
- ti,dra7x-usb2-phy2
- ti,am654-usb2
- enum:
- ti,omap-usb2
- items:
- const: ti,am437x-usb2
- items:
- const: ti,omap-usb2
reg:
maxItems: 1
"#phy-cells":
const: 0
clocks:
minItems: 1
items:
- description: wakeup clock
- description: reference clock
clock-names:
minItems: 1
items:
- const: wkupclk
- const: refclk
syscon-phy-power:
$ref: /schemas/types.yaml#definitions/phandle-array
description:
phandle/offset pair. Phandle to the system control module and
register offset to power on/off the PHY.
ctrl-module:
$ref: /schemas/types.yaml#definitions/phandle
description:
(deprecated) phandle of the control module used by PHY driver
to power on the PHY. Use syscon-phy-power instead.
required:
- compatible
- reg
- "#phy-cells"
- clocks
- clock-names
examples:
- |
usb0_phy: phy@4100000 {
compatible = "ti,am654-usb2", "ti,omap-usb2";
reg = <0x4100000 0x54>;
syscon-phy-power = <&scm_conf 0x4000>;
clocks = <&k3_clks 151 0>, <&k3_clks 151 1>;
clock-names = "wkupclk", "refclk";
#phy-cells = <0>;
};

View File

@ -45,9 +45,15 @@ properties:
ranges: true
assigned-clocks:
minItems: 1
maxItems: 2
assigned-clock-parents:
minItems: 1
maxItems: 2
assigned-clock-rates:
minItems: 1
maxItems: 2
typec-dir-gpios:
@ -119,9 +125,10 @@ patternProperties:
logic.
properties:
clocks:
minItems: 2
maxItems: 4
description: Phandle to four clock nodes representing the inputs to
refclk_dig
description: Phandle to two (Torrent) or four (Sierra) clock nodes representing
the inputs to refclk_dig
"#clock-cells":
const: 0
@ -203,7 +210,7 @@ examples:
};
refclk-dig {
clocks = <&k3_clks 292 11>, <&k3_clks 292 0>,
clocks = <&k3_clks 292 11>, <&k3_clks 292 0>,
<&dummy_cmn_refclk>, <&dummy_cmn_refclk1>;
#clock-cells = <0>;
assigned-clocks = <&wiz0_refclk_dig>;

View File

@ -27,43 +27,6 @@ omap_control_usb: omap-control-usb@4a002300 {
reg-names = "otghs_control";
};
OMAP USB2 PHY
Required properties:
- compatible: Should be "ti,omap-usb2"
Should be "ti,dra7x-usb2" for the 1st instance of USB2 PHY on
DRA7x
Should be "ti,dra7x-usb2-phy2" for the 2nd instance of USB2 PHY
in DRA7x
Should be "ti,am654-usb2" for the USB2 PHYs on AM654.
- reg : Address and length of the register set for the device.
- #phy-cells: determine the number of cells that should be given in the
phandle while referencing this phy.
- clocks: a list of phandles and clock-specifier pairs, one for each entry in
clock-names.
- clock-names: should include:
* "wkupclk" - wakeup clock.
* "refclk" - reference clock (optional).
Deprecated properties:
- ctrl-module : phandle of the control module used by PHY driver to power on
the PHY.
Recommended properies:
- syscon-phy-power : phandle/offset pair. Phandle to the system control
module and the register offset to power on/off the PHY.
This is usually a subnode of ocp2scp to which it is connected.
usb2phy@4a0ad080 {
compatible = "ti,omap-usb2";
reg = <0x4a0ad080 0x58>;
ctrl-module = <&omap_control_usb>;
#phy-cells = <0>;
clocks = <&usb_phy_cm_clk32k>, <&usb_otg_ss_refclk960m>;
clock-names = "wkupclk", "refclk";
};
TI PIPE3 PHY
Required properties:

View File

@ -25,13 +25,14 @@ description: |
The Amlogic A1 embeds a DWC3 USB IP Core configured for USB2 in
host-only mode.
The Amlogic GXL & GXM SoCs doesn't embed an USB3 PHY.
The Amlogic GXL, GXM & AXG SoCs doesn't embed an USB3 PHY.
properties:
compatible:
enum:
- amlogic,meson-gxl-usb-ctrl
- amlogic,meson-gxm-usb-ctrl
- amlogic,meson-axg-usb-ctrl
- amlogic,meson-g12a-usb-ctrl
- amlogic,meson-a1-usb-ctrl
@ -151,6 +152,25 @@ allOf:
required:
- clock-names
- if:
properties:
compatible:
enum:
- amlogic,meson-axg-usb-ctrl
then:
properties:
phy-names:
items:
- const: usb2-phy1 # USB2 PHY1 if USBOTG_B port is used
clocks:
minItems: 2
clock-names:
items:
- const: usb_ctrl
- const: ddr
required:
- clock-names
- if:
properties:
compatible:

View File

@ -82,6 +82,7 @@ Required properties:
"atmel,at91sam9rl-udc"
"atmel,at91sam9g45-udc"
"atmel,sama5d3-udc"
"microchip,sam9x60-udc"
- reg: Address and length of the register set for the device
- interrupts: Should contain usba interrupt
- clocks: Should reference the peripheral and host clocks

View File

@ -0,0 +1,96 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/usb/cdns,usb3.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Cadence USBSS-DRD controller bindings
maintainers:
- Pawel Laszczak <pawell@cadence.com>
properties:
compatible:
const: cdns,usb3
reg:
items:
- description: OTG controller registers
- description: XHCI Host controller registers
- description: DEVICE controller registers
reg-names:
items:
- const: otg
- const: xhci
- const: dev
interrupts:
items:
- description: OTG/DRD controller interrupt
- description: XHCI host controller interrupt
- description: Device controller interrupt
interrupt-names:
items:
- const: host
- const: peripheral
- const: otg
dr_mode:
enum: [host, otg, peripheral]
maximum-speed:
enum: [super-speed, high-speed, full-speed]
phys:
minItems: 1
maxItems: 2
phy-names:
minItems: 1
maxItems: 2
items:
anyOf:
- const: cdns3,usb2-phy
- const: cdns3,usb3-phy
cdns,on-chip-buff-size:
description:
size of memory intended as internal memory for endpoints
buffers expressed in KB
$ref: /schemas/types.yaml#/definitions/uint32
cdns,phyrst-a-enable:
description: Enable resetting of PHY if Rx fail is detected
type: boolean
required:
- compatible
- reg
- reg-names
- interrupts
additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/arm-gic.h>
bus {
#address-cells = <2>;
#size-cells = <2>;
usb@6000000 {
compatible = "cdns,usb3";
reg = <0x00 0x6000000 0x00 0x10000>,
<0x00 0x6010000 0x00 0x10000>,
<0x00 0x6020000 0x00 0x10000>;
reg-names = "otg", "xhci", "dev";
interrupts = <GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 102 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "host", "peripheral", "otg";
maximum-speed = "super-speed";
dr_mode = "otg";
};
};

View File

@ -1,45 +0,0 @@
Binding for the Cadence USBSS-DRD controller
Required properties:
- reg: Physical base address and size of the controller's register areas.
Controller has 3 different regions:
- HOST registers area
- DEVICE registers area
- OTG/DRD registers area
- reg-names - register memory area names:
"xhci" - for HOST registers space
"dev" - for DEVICE registers space
"otg" - for OTG/DRD registers space
- compatible: Should contain: "cdns,usb3"
- interrupts: Interrupts used by cdns3 controller:
"host" - interrupt used by XHCI driver.
"peripheral" - interrupt used by device driver
"otg" - interrupt used by DRD/OTG part of driver
Optional properties:
- maximum-speed : valid arguments are "super-speed", "high-speed" and
"full-speed"; refer to usb/generic.txt
- dr_mode: Should be one of "host", "peripheral" or "otg".
- phys: reference to the USB PHY
- phy-names: from the *Generic PHY* bindings;
Supported names are:
- cdns3,usb2-phy
- cdns3,usb3-phy
- cdns,on-chip-buff-size : size of memory intended as internal memory for endpoints
buffers expressed in KB
Example:
usb@f3000000 {
compatible = "cdns,usb3";
interrupts = <GIC_USB_IRQ 7 IRQ_TYPE_LEVEL_HIGH>,
<GIC_USB_IRQ 7 IRQ_TYPE_LEVEL_HIGH>,
<GIC_USB_IRQ 8 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "host", "peripheral", "otg";
reg = <0xf3000000 0x10000>, /* memory area for HOST registers */
<0xf3010000 0x10000>, /* memory area for DEVICE registers */
<0xf3020000 0x10000>; /* memory area for OTG/DRD registers */
reg-names = "xhci", "dev", "otg";
phys = <&usb2_phy>, <&usb3_phy>;
phy-names = "cdns3,usb2-phy", "cnds3,usb3-phy";
};

View File

@ -100,6 +100,15 @@ i.mx specific properties
It's recommended to specify the over current polarity.
- power-active-high: power signal polarity is active high
- external-vbus-divider: enables off-chip resistor divider for Vbus
- samsung,picophy-pre-emp-curr-control: HS Transmitter Pre-Emphasis Current
Control. This signal controls the amount of current sourced to the
USB_OTG*_DP and USB_OTG*_DN pins after a J-to-K or K-to-J transition.
The range is from 0x0 to 0x3, the default value is 0x1.
Details can refer to TXPREEMPAMPTUNE0 bits of USBNC_n_PHY_CFG1.
- samsung,picophy-dc-vol-level-adjust: HS DC Voltage Level Adjustment.
Adjust the high-speed transmitter DC level voltage.
The range is from 0x0 to 0xf, the default value is 0x3.
Details can refer to TXVREFTUNE0 bits of USBNC_n_PHY_CFG1.
Example:

View File

@ -39,6 +39,7 @@ properties:
- amlogic,meson-g12a-usb
- const: snps,dwc2
- const: amcc,dwc-otg
- const: apm,apm82181-dwc-otg
- const: snps,dwc2
- const: st,stm32f4x9-fsotg
- const: st,stm32f4x9-hsotg
@ -102,6 +103,10 @@ properties:
dr_mode:
enum: [host, peripheral, otg]
usb-role-switch:
$ref: /schemas/types.yaml#/definitions/flag
description: Support role switch.
g-rx-fifo-size:
$ref: /schemas/types.yaml#/definitions/uint32
description: size of rx fifo size in gadget mode.

View File

@ -78,6 +78,9 @@ Optional properties:
park mode are disabled.
- snps,dis_metastability_quirk: when set, disable metastability workaround.
CAUTION: use only if you are absolutely sure of it.
- snps,dis-split-quirk: when set, change the way URBs are handled by the
driver. Needed to avoid -EPROTO errors with usbhid
on some devices (Hikey 970).
- snps,is-utmi-l1-suspend: true when DWC3 asserts output signal
utmi_l1_suspend_n, false when asserts utmi_sleep_n
- snps,hird-threshold: HIRD threshold

View File

@ -0,0 +1,77 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/usb/intel,keembay-dwc3.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Intel Keem Bay DWC3 USB controller
maintainers:
- Wan Ahmad Zainie <wan.ahmad.zainie.wan.mohamad@intel.com>
properties:
compatible:
const: intel,keembay-dwc3
clocks:
maxItems: 4
clock-names:
items:
- const: async_master
- const: ref
- const: alt_ref
- const: suspend
ranges: true
'#address-cells':
enum: [ 1, 2 ]
'#size-cells':
enum: [ 1, 2 ]
# 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.
required:
- compatible
- clocks
- clock-names
- ranges
additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/interrupt-controller/irq.h>
#define KEEM_BAY_A53_AUX_USB
#define KEEM_BAY_A53_AUX_USB_REF
#define KEEM_BAY_A53_AUX_USB_ALT_REF
#define KEEM_BAY_A53_AUX_USB_SUSPEND
usb {
compatible = "intel,keembay-dwc3";
clocks = <&scmi_clk KEEM_BAY_A53_AUX_USB>,
<&scmi_clk KEEM_BAY_A53_AUX_USB_REF>,
<&scmi_clk KEEM_BAY_A53_AUX_USB_ALT_REF>,
<&scmi_clk KEEM_BAY_A53_AUX_USB_SUSPEND>;
clock-names = "async_master", "ref", "alt_ref", "suspend";
ranges;
#address-cells = <1>;
#size-cells = <1>;
dwc3@34000000 {
compatible = "snps,dwc3";
reg = <0x34000000 0x10000>;
interrupts = <GIC_SPI 91 IRQ_TYPE_LEVEL_HIGH>;
dr_mode = "peripheral";
};
};

View File

@ -0,0 +1,95 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: "http://devicetree.org/schemas/usb/mediatek,mt6360-tcpc.yaml#"
$schema: "http://devicetree.org/meta-schemas/core.yaml#"
title: Mediatek MT6360 Type-C Port Switch and Power Delivery controller DT bindings
maintainers:
- ChiYuan Huang <cy_huang@richtek.com>
description: |
Mediatek MT6360 is a multi-functional device. It integrates charger, ADC, flash, RGB indicators,
regulators (BUCKs/LDOs), and TypeC Port Switch with Power Delivery controller.
This document only describes MT6360 Type-C Port Switch and Power Delivery controller.
properties:
compatible:
enum:
- mediatek,mt6360-tcpc
interrupts:
maxItems: 1
interrupt-names:
items:
- const: PD_IRQB
connector:
type: object
$ref: ../connector/usb-connector.yaml#
description:
Properties for usb c connector.
additionalProperties: false
required:
- compatible
- interrupts
- interrupt-names
examples:
- |
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/usb/pd.h>
i2c0 {
#address-cells = <1>;
#size-cells = <0>;
mt6360@34 {
compatible = "mediatek,mt6360";
reg = <0x34>;
tcpc {
compatible = "mediatek,mt6360-tcpc";
interrupts-extended = <&gpio26 3 IRQ_TYPE_LEVEL_LOW>;
interrupt-names = "PD_IRQB";
connector {
compatible = "usb-c-connector";
label = "USB-C";
data-role = "dual";
power-role = "dual";
try-power-role = "sink";
source-pdos = <PDO_FIXED(5000, 1000, PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP)>;
sink-pdos = <PDO_FIXED(5000, 2000, PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP)>;
op-sink-microwatt = <10000000>;
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
endpoint {
remote-endpoint = <&usb_hs>;
};
};
port@1 {
reg = <1>;
endpoint {
remote-endpoint = <&usb_ss>;
};
};
port@2 {
reg = <2>;
endpoint {
remote-endpoint = <&dp_aux>;
};
};
};
};
};
};
};
...

View File

@ -30,6 +30,7 @@ properties:
- renesas,xhci-r8a774a1 # RZ/G2M
- renesas,xhci-r8a774b1 # RZ/G2N
- renesas,xhci-r8a774c0 # RZ/G2E
- renesas,xhci-r8a774e1 # RZ/G2H
- renesas,xhci-r8a7795 # R-Car H3
- renesas,xhci-r8a7796 # R-Car M3-W
- renesas,xhci-r8a77961 # R-Car M3-W+

View File

@ -16,6 +16,7 @@ properties:
- renesas,r8a774a1-usb3-peri # RZ/G2M
- renesas,r8a774b1-usb3-peri # RZ/G2N
- renesas,r8a774c0-usb3-peri # RZ/G2E
- renesas,r8a774e1-usb3-peri # RZ/G2H
- renesas,r8a7795-usb3-peri # R-Car H3
- renesas,r8a7796-usb3-peri # R-Car M3-W
- renesas,r8a77961-usb3-peri # R-Car M3-W+
@ -52,11 +53,24 @@ properties:
$ref: /schemas/types.yaml#/definitions/phandle
description: phandle of a companion.
port:
ports:
description: |
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.
type: object
properties:
port@0:
type: object
description: High Speed (HS) data bus.
port@1:
type: object
description: Super Speed (SS) data bus.
required:
- port@0
- port@1
required:
- compatible
@ -79,9 +93,20 @@ examples:
companion = <&xhci0>;
usb-role-switch;
port {
usb3_role_switch: endpoint {
remote-endpoint = <&hd3ss3220_ep>;
};
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
usb3_hs_ep: endpoint {
remote-endpoint = <&hs_ep>;
};
};
port@1 {
reg = <1>;
usb3_role_switch: endpoint {
remote-endpoint = <&hd3ss3220_out_ep>;
};
};
};
};

View File

@ -39,6 +39,7 @@ properties:
- renesas,usbhs-r8a774a1 # RZ/G2M
- renesas,usbhs-r8a774b1 # RZ/G2N
- renesas,usbhs-r8a774c0 # RZ/G2E
- renesas,usbhs-r8a774e1 # RZ/G2H
- renesas,usbhs-r8a7795 # R-Car H3
- renesas,usbhs-r8a7796 # R-Car M3-W
- renesas,usbhs-r8a77961 # R-Car M3-W+

View File

@ -1,38 +0,0 @@
TI HD3SS3220 TypeC DRP Port Controller.
Required properties:
- compatible: Must be "ti,hd3ss3220".
- reg: I2C slave address, must be 0x47 or 0x67 based on ADDR pin.
- interrupts: An interrupt specifier.
Required sub-node:
- connector: The "usb-c-connector" attached to the hd3ss3220 chip. The
bindings of the connector node are specified in:
Documentation/devicetree/bindings/connector/usb-connector.yaml
Example:
hd3ss3220@47 {
compatible = "ti,hd3ss3220";
reg = <0x47>;
interrupt-parent = <&gpio6>;
interrupts = <3 IRQ_TYPE_LEVEL_LOW>;
connector {
compatible = "usb-c-connector";
label = "USB-C";
data-role = "dual";
ports {
#address-cells = <1>;
#size-cells = <0>;
port@1 {
reg = <1>;
hd3ss3220_ep: endpoint {
remote-endpoint = <&usb3_role_switch>;
};
};
};
};
};

View File

@ -0,0 +1,82 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/usb/ti,hd3ss3220.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: TI HD3SS3220 TypeC DRP Port Controller
maintainers:
- Biju Das <biju.das.jz@bp.renesas.com>
description: |-
HD3SS3220 is a USB SuperSpeed (SS) 2:1 mux with DRP port controller. The device provides Channel
Configuration (CC) logic and 5V VCONN sourcing for ecosystems implementing USB Type-C. The
HD3SS3220 can be configured as a Downstream Facing Port (DFP), Upstream Facing Port (UFP) or a
Dual Role Port (DRP) making it ideal for any application.
properties:
compatible:
const: ti,hd3ss3220
reg:
maxItems: 1
interrupts:
maxItems: 1
ports:
description: OF graph bindings (specified in bindings/graph.txt) that model
SS data bus to the SS capable connector.
type: object
properties:
port@0:
type: object
description: Super Speed (SS) MUX inputs connected to SS capable connector.
$ref: /connector/usb-connector.yaml#/properties/ports/properties/port@1
port@1:
type: object
description: Output of 2:1 MUX connected to Super Speed (SS) data bus.
required:
- port@0
- port@1
required:
- compatible
- reg
- interrupts
additionalProperties: false
examples:
- |
i2c0 {
#address-cells = <1>;
#size-cells = <0>;
hd3ss3220@47 {
compatible = "ti,hd3ss3220";
reg = <0x47>;
interrupt-parent = <&gpio6>;
interrupts = <3>;
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
hd3ss3220_in_ep: endpoint {
remote-endpoint = <&ss_ep>;
};
};
port@1 {
reg = <1>;
hd3ss3220_out_ep: endpoint {
remote-endpoint = <&usb3_role_switch>;
};
};
};
};
};

View File

@ -3454,6 +3454,14 @@ F: drivers/bus/brcmstb_gisb.c
F: drivers/pci/controller/pcie-brcmstb.c
N: brcmstb
BROADCOM BDC DRIVER
M: Al Cooper <alcooperx@gmail.com>
L: linux-usb@vger.kernel.org
L: bcm-kernel-feedback-list@broadcom.com
S: Maintained
F: Documentation/devicetree/bindings/usb/brcm,bdc.txt
F: drivers/usb/gadget/udc/bdc/
BROADCOM BMIPS CPUFREQ DRIVER
M: Markus Mayer <mmayer@broadcom.com>
M: bcm-kernel-feedback-list@broadcom.com
@ -3831,6 +3839,16 @@ S: Orphan
F: Documentation/devicetree/bindings/mtd/cadence-nand-controller.txt
F: drivers/mtd/nand/raw/cadence-nand-controller.c
CADENCE USB3 DRD IP DRIVER
M: Peter Chen <peter.chen@nxp.com>
M: Pawel Laszczak <pawell@cadence.com>
M: Roger Quadros <rogerq@ti.com>
L: linux-usb@vger.kernel.org
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/usb.git
F: Documentation/devicetree/bindings/usb/cdns-usb3.txt
F: drivers/usb/cdns3/
CADET FM/AM RADIO RECEIVER DRIVER
M: Hans Verkuil <hverkuil@xs4all.nl>
L: linux-media@vger.kernel.org
@ -11037,7 +11055,7 @@ F: net/dsa/tag_mtk.c
MEDIATEK USB3 DRD IP DRIVER
M: Chunfeng Yun <chunfeng.yun@mediatek.com>
L: linux-usb@vger.kernel.org (moderated for non-subscribers)
L: linux-usb@vger.kernel.org
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
L: linux-mediatek@lists.infradead.org (moderated for non-subscribers)
S: Maintained

View File

@ -563,6 +563,12 @@
atmel,pins = <AT91_PIOD 18 AT91_PERIPH_GPIO AT91_PINCTRL_NONE>;
};
};
usb0 {
pinctrl_usba_vbus: usba_vbus {
atmel,pins = <AT91_PIOB 16 AT91_PERIPH_GPIO AT91_PINCTRL_NONE>;
};
};
}; /* pinctrl */
&pmc {
@ -666,6 +672,13 @@
};
};
&usb0 {
atmel,vbus-gpio = <&pioB 16 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_usba_vbus>;
status = "okay";
};
&usb1 {
num-ports = <3>;
atmel,vbus-gpio = <0

View File

@ -4,6 +4,8 @@
#include "bcm2835-rpi.dtsi"
#include "bcm283x-rpi-usb-peripheral.dtsi"
#include <dt-bindings/reset/raspberrypi,firmware-reset.h>
/ {
compatible = "raspberrypi,4-model-b", "brcm,bcm2711";
model = "Raspberry Pi 4 Model B";
@ -88,6 +90,11 @@
"";
status = "okay";
};
reset: reset {
compatible = "raspberrypi,firmware-reset";
#reset-cells = <1>;
};
};
&gpio {
@ -207,6 +214,21 @@
};
};
&pcie0 {
pci@1,0 {
#address-cells = <3>;
#size-cells = <2>;
ranges;
reg = <0 0 0 0 0>;
usb@1,0 {
reg = <0x10000 0 0 0 0>;
resets = <&reset RASPBERRYPI_FIRMWARE_RESET_ID_USB>;
};
};
};
/* uart0 communicates with the BT module */
&uart0 {
pinctrl-names = "default";

View File

@ -69,6 +69,20 @@
#size-cells = <1>;
ranges;
usb0: gadget@500000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "microchip,sam9x60-udc";
reg = <0x00500000 0x100000
0xf803c000 0x400>;
interrupts = <23 IRQ_TYPE_LEVEL_HIGH 2>;
clocks = <&pmc PMC_TYPE_PERIPHERAL 23>, <&pmc PMC_TYPE_CORE PMC_UTMI>;
clock-names = "pclk", "hclk";
assigned-clocks = <&pmc PMC_TYPE_CORE PMC_UTMI>;
assigned-clock-rates = <480000000>;
status = "disabled";
};
usb1: ohci@600000 {
compatible = "atmel,at91rm9200-ohci", "usb-ohci";
reg = <0x00600000 0x100000>;

View File

@ -555,6 +555,7 @@ struct device *acpi_get_first_physical_node(struct acpi_device *adev)
mutex_unlock(physical_node_lock);
return phys_dev;
}
EXPORT_SYMBOL_GPL(acpi_get_first_physical_node);
static struct acpi_device *acpi_primary_dev_companion(struct acpi_device *adev,
const struct device *dev)

View File

@ -212,19 +212,16 @@ static int ath3k_load_firmware(struct usb_device *udev,
BT_DBG("udev %p", udev);
pipe = usb_sndctrlpipe(udev, 0);
send_buf = kmalloc(BULK_SIZE, GFP_KERNEL);
if (!send_buf) {
BT_ERR("Can't allocate memory chunk for firmware");
return -ENOMEM;
}
memcpy(send_buf, firmware->data, FW_HDR_SIZE);
err = usb_control_msg(udev, pipe, USB_REQ_DFU_DNLOAD, USB_TYPE_VENDOR,
0, 0, send_buf, FW_HDR_SIZE,
USB_CTRL_SET_TIMEOUT);
if (err < 0) {
err = usb_control_msg_send(udev, 0, USB_REQ_DFU_DNLOAD, USB_TYPE_VENDOR,
0, 0, firmware->data, FW_HDR_SIZE,
USB_CTRL_SET_TIMEOUT, GFP_KERNEL);
if (err) {
BT_ERR("Can't change to loading configuration err");
goto error;
}
@ -259,44 +256,19 @@ error:
static int ath3k_get_state(struct usb_device *udev, unsigned char *state)
{
int ret, pipe = 0;
char *buf;
buf = kmalloc(sizeof(*buf), GFP_KERNEL);
if (!buf)
return -ENOMEM;
pipe = usb_rcvctrlpipe(udev, 0);
ret = usb_control_msg(udev, pipe, ATH3K_GETSTATE,
USB_TYPE_VENDOR | USB_DIR_IN, 0, 0,
buf, sizeof(*buf), USB_CTRL_SET_TIMEOUT);
*state = *buf;
kfree(buf);
return ret;
return usb_control_msg_recv(udev, 0, ATH3K_GETSTATE,
USB_TYPE_VENDOR | USB_DIR_IN, 0, 0,
state, 1, USB_CTRL_SET_TIMEOUT,
GFP_KERNEL);
}
static int ath3k_get_version(struct usb_device *udev,
struct ath3k_version *version)
{
int ret, pipe = 0;
struct ath3k_version *buf;
const int size = sizeof(*buf);
buf = kmalloc(size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
pipe = usb_rcvctrlpipe(udev, 0);
ret = usb_control_msg(udev, pipe, ATH3K_GETVERSION,
USB_TYPE_VENDOR | USB_DIR_IN, 0, 0,
buf, size, USB_CTRL_SET_TIMEOUT);
memcpy(version, buf, size);
kfree(buf);
return ret;
return usb_control_msg_recv(udev, 0, ATH3K_GETVERSION,
USB_TYPE_VENDOR | USB_DIR_IN, 0, 0,
version, sizeof(*version), USB_CTRL_SET_TIMEOUT,
GFP_KERNEL);
}
static int ath3k_load_fwfile(struct usb_device *udev,
@ -316,13 +288,11 @@ static int ath3k_load_fwfile(struct usb_device *udev,
}
size = min_t(uint, count, FW_HDR_SIZE);
memcpy(send_buf, firmware->data, size);
pipe = usb_sndctrlpipe(udev, 0);
ret = usb_control_msg(udev, pipe, ATH3K_DNLOAD,
USB_TYPE_VENDOR, 0, 0, send_buf,
size, USB_CTRL_SET_TIMEOUT);
if (ret < 0) {
ret = usb_control_msg_send(udev, 0, ATH3K_DNLOAD, USB_TYPE_VENDOR, 0, 0,
firmware->data, size, USB_CTRL_SET_TIMEOUT,
GFP_KERNEL);
if (ret) {
BT_ERR("Can't change to loading configuration err");
kfree(send_buf);
return ret;
@ -355,23 +325,19 @@ static int ath3k_load_fwfile(struct usb_device *udev,
return 0;
}
static int ath3k_switch_pid(struct usb_device *udev)
static void ath3k_switch_pid(struct usb_device *udev)
{
int pipe = 0;
pipe = usb_sndctrlpipe(udev, 0);
return usb_control_msg(udev, pipe, USB_REG_SWITCH_VID_PID,
USB_TYPE_VENDOR, 0, 0,
NULL, 0, USB_CTRL_SET_TIMEOUT);
usb_control_msg_send(udev, 0, USB_REG_SWITCH_VID_PID, USB_TYPE_VENDOR,
0, 0, NULL, 0, USB_CTRL_SET_TIMEOUT, GFP_KERNEL);
}
static int ath3k_set_normal_mode(struct usb_device *udev)
{
unsigned char fw_state;
int pipe = 0, ret;
int ret;
ret = ath3k_get_state(udev, &fw_state);
if (ret < 0) {
if (ret) {
BT_ERR("Can't get state to change to normal mode err");
return ret;
}
@ -381,10 +347,9 @@ static int ath3k_set_normal_mode(struct usb_device *udev)
return 0;
}
pipe = usb_sndctrlpipe(udev, 0);
return usb_control_msg(udev, pipe, ATH3K_SET_NORMAL_MODE,
USB_TYPE_VENDOR, 0, 0,
NULL, 0, USB_CTRL_SET_TIMEOUT);
return usb_control_msg_send(udev, 0, ATH3K_SET_NORMAL_MODE,
USB_TYPE_VENDOR, 0, 0, NULL, 0,
USB_CTRL_SET_TIMEOUT, GFP_KERNEL);
}
static int ath3k_load_patch(struct usb_device *udev)
@ -397,7 +362,7 @@ static int ath3k_load_patch(struct usb_device *udev)
int ret;
ret = ath3k_get_state(udev, &fw_state);
if (ret < 0) {
if (ret) {
BT_ERR("Can't get state to change to load ram patch err");
return ret;
}
@ -408,7 +373,7 @@ static int ath3k_load_patch(struct usb_device *udev)
}
ret = ath3k_get_version(udev, &fw_version);
if (ret < 0) {
if (ret) {
BT_ERR("Can't get version to change to load ram patch err");
return ret;
}
@ -449,13 +414,13 @@ static int ath3k_load_syscfg(struct usb_device *udev)
int clk_value, ret;
ret = ath3k_get_state(udev, &fw_state);
if (ret < 0) {
if (ret) {
BT_ERR("Can't get state to change to load configuration err");
return -EBUSY;
}
ret = ath3k_get_version(udev, &fw_version);
if (ret < 0) {
if (ret) {
BT_ERR("Can't get version to change to load ram patch err");
return ret;
}
@ -529,7 +494,7 @@ static int ath3k_probe(struct usb_interface *intf,
return ret;
}
ret = ath3k_set_normal_mode(udev);
if (ret < 0) {
if (ret) {
BT_ERR("Set normal mode failed");
return ret;
}

View File

@ -178,9 +178,8 @@ config ISCSI_IBFT
Otherwise, say N.
config RASPBERRYPI_FIRMWARE
bool "Raspberry Pi Firmware Driver"
tristate "Raspberry Pi Firmware Driver"
depends on BCM2835_MBOX
default USB_PCI
help
This option enables support for communicating with the firmware on the
Raspberry Pi.

View File

@ -12,8 +12,6 @@
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <soc/bcm2835/raspberrypi-firmware.h>
#define MBOX_MSG(chan, data28) (((data28) & ~0xf) | ((chan) & 0xf))
@ -21,8 +19,6 @@
#define MBOX_DATA28(msg) ((msg) & ~0xf)
#define MBOX_CHAN_PROPERTY 8
#define VL805_PCI_CONFIG_VERSION_OFFSET 0x50
static struct platform_device *rpi_hwmon;
static struct platform_device *rpi_clk;
@ -301,63 +297,6 @@ struct rpi_firmware *rpi_firmware_get(struct device_node *firmware_node)
}
EXPORT_SYMBOL_GPL(rpi_firmware_get);
/*
* The Raspberry Pi 4 gets its USB functionality from VL805, a PCIe chip that
* implements xHCI. After a PCI reset, VL805's firmware may either be loaded
* directly from an EEPROM or, if not present, by the SoC's co-processor,
* VideoCore. RPi4's VideoCore OS contains both the non public firmware load
* logic and the VL805 firmware blob. This function triggers the aforementioned
* process.
*/
int rpi_firmware_init_vl805(struct pci_dev *pdev)
{
struct device_node *fw_np;
struct rpi_firmware *fw;
u32 dev_addr, version;
int ret;
fw_np = of_find_compatible_node(NULL, NULL,
"raspberrypi,bcm2835-firmware");
if (!fw_np)
return 0;
fw = rpi_firmware_get(fw_np);
of_node_put(fw_np);
if (!fw)
return -ENODEV;
/*
* Make sure we don't trigger a firmware load unnecessarily.
*
* If something went wrong with PCI, this whole exercise would be
* futile as VideoCore expects from us a configured PCI bus. Just take
* the faulty version (likely ~0) and let xHCI's registration fail
* further down the line.
*/
pci_read_config_dword(pdev, VL805_PCI_CONFIG_VERSION_OFFSET, &version);
if (version)
goto exit;
dev_addr = pdev->bus->number << 20 | PCI_SLOT(pdev->devfn) << 15 |
PCI_FUNC(pdev->devfn) << 12;
ret = rpi_firmware_property(fw, RPI_FIRMWARE_NOTIFY_XHCI_RESET,
&dev_addr, sizeof(dev_addr));
if (ret)
return ret;
/* Wait for vl805 to startup */
usleep_range(200, 1000);
pci_read_config_dword(pdev, VL805_PCI_CONFIG_VERSION_OFFSET,
&version);
exit:
pci_info(pdev, "VL805 firmware version %08x\n", version);
return 0;
}
EXPORT_SYMBOL_GPL(rpi_firmware_init_vl805);
static const struct of_device_id rpi_firmware_of_match[] = {
{ .compatible = "raspberrypi,bcm2835-firmware", },
{},

View File

@ -124,62 +124,31 @@ static void async_ctrl_callback(struct urb *urb)
static int get_registers(pegasus_t *pegasus, __u16 indx, __u16 size, void *data)
{
u8 *buf;
int ret;
buf = kmalloc(size, GFP_NOIO);
if (!buf)
return -ENOMEM;
ret = usb_control_msg(pegasus->usb, usb_rcvctrlpipe(pegasus->usb, 0),
PEGASUS_REQ_GET_REGS, PEGASUS_REQT_READ, 0,
indx, buf, size, 1000);
if (ret < 0)
netif_dbg(pegasus, drv, pegasus->net,
"%s returned %d\n", __func__, ret);
else if (ret <= size)
memcpy(data, buf, ret);
kfree(buf);
return ret;
return usb_control_msg_recv(pegasus->usb, 0, PEGASUS_REQ_GET_REGS,
PEGASUS_REQT_READ, 0, indx, data, size,
1000, GFP_NOIO);
}
static int set_registers(pegasus_t *pegasus, __u16 indx, __u16 size,
const void *data)
{
u8 *buf;
int ret;
buf = kmemdup(data, size, GFP_NOIO);
if (!buf)
return -ENOMEM;
ret = usb_control_msg(pegasus->usb, usb_sndctrlpipe(pegasus->usb, 0),
PEGASUS_REQ_SET_REGS, PEGASUS_REQT_WRITE, 0,
indx, buf, size, 100);
if (ret < 0)
netif_dbg(pegasus, drv, pegasus->net,
"%s returned %d\n", __func__, ret);
kfree(buf);
return ret;
return usb_control_msg_send(pegasus->usb, 0, PEGASUS_REQ_SET_REGS,
PEGASUS_REQT_WRITE, 0, indx, data, size,
1000, GFP_NOIO);
}
/*
* There is only one way to write to a single ADM8511 register and this is via
* specific control request. 'data' is ignored by the device, but it is here to
* not break the API.
*/
static int set_register(pegasus_t *pegasus, __u16 indx, __u8 data)
{
u8 *buf;
int ret;
void *buf = &data;
buf = kmemdup(&data, 1, GFP_NOIO);
if (!buf)
return -ENOMEM;
ret = usb_control_msg(pegasus->usb, usb_sndctrlpipe(pegasus->usb, 0),
PEGASUS_REQ_SET_REG, PEGASUS_REQT_WRITE, data,
indx, buf, 1, 1000);
if (ret < 0)
netif_dbg(pegasus, drv, pegasus->net,
"%s returned %d\n", __func__, ret);
kfree(buf);
return ret;
return usb_control_msg_send(pegasus->usb, 0, PEGASUS_REQ_SET_REG,
PEGASUS_REQT_WRITE, data, indx, buf, 1,
1000, GFP_NOIO);
}
static int update_eth_regs_async(pegasus_t *pegasus)

View File

@ -152,36 +152,16 @@ static const char driver_name [] = "rtl8150";
*/
static int get_registers(rtl8150_t * dev, u16 indx, u16 size, void *data)
{
void *buf;
int ret;
buf = kmalloc(size, GFP_NOIO);
if (!buf)
return -ENOMEM;
ret = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0),
RTL8150_REQ_GET_REGS, RTL8150_REQT_READ,
indx, 0, buf, size, 500);
if (ret > 0 && ret <= size)
memcpy(data, buf, ret);
kfree(buf);
return ret;
return usb_control_msg_recv(dev->udev, 0, RTL8150_REQ_GET_REGS,
RTL8150_REQT_READ, indx, 0, data, size,
1000, GFP_NOIO);
}
static int set_registers(rtl8150_t * dev, u16 indx, u16 size, const void *data)
{
void *buf;
int ret;
buf = kmemdup(data, size, GFP_NOIO);
if (!buf)
return -ENOMEM;
ret = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0),
RTL8150_REQ_SET_REGS, RTL8150_REQT_WRITE,
indx, 0, buf, size, 500);
kfree(buf);
return ret;
return usb_control_msg_send(dev->udev, 0, RTL8150_REQ_SET_REGS,
RTL8150_REQT_WRITE, indx, 0, data, size,
1000, GFP_NOIO);
}
static void async_set_reg_cb(struct urb *urb)

View File

@ -28,8 +28,6 @@
#include <linux/string.h>
#include <linux/types.h>
#include <soc/bcm2835/raspberrypi-firmware.h>
#include "../pci.h"
/* BRCM_PCIE_CAP_REGS - Offset for the mandatory capability config regs */
@ -931,24 +929,9 @@ static int brcm_pcie_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node, *msi_np;
struct pci_host_bridge *bridge;
struct device_node *fw_np;
struct brcm_pcie *pcie;
int ret;
/*
* We have to wait for Raspberry Pi's firmware interface to be up as a
* PCI fixup, rpi_firmware_init_vl805(), depends on it. This driver's
* probe can race with the firmware interface's (see
* drivers/firmware/raspberrypi.c) and potentially break the PCI fixup.
*/
fw_np = of_find_compatible_node(NULL, NULL,
"raspberrypi,bcm2835-firmware");
if (fw_np && !rpi_firmware_get(fw_np)) {
of_node_put(fw_np);
return -EPROBE_DEFER;
}
of_node_put(fw_np);
bridge = devm_pci_alloc_host_bridge(&pdev->dev, sizeof(*pcie));
if (!bridge)
return -ENOMEM;

View File

@ -3673,63 +3673,6 @@ static void quirk_apple_poweroff_thunderbolt(struct pci_dev *dev)
DECLARE_PCI_FIXUP_SUSPEND_LATE(PCI_VENDOR_ID_INTEL,
PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C,
quirk_apple_poweroff_thunderbolt);
/*
* Apple: Wait for the Thunderbolt controller to reestablish PCI tunnels
*
* During suspend the Thunderbolt controller is reset and all PCI
* tunnels are lost. The NHI driver will try to reestablish all tunnels
* during resume. We have to manually wait for the NHI since there is
* no parent child relationship between the NHI and the tunneled
* bridges.
*/
static void quirk_apple_wait_for_thunderbolt(struct pci_dev *dev)
{
struct pci_dev *sibling = NULL;
struct pci_dev *nhi = NULL;
if (!x86_apple_machine)
return;
if (pci_pcie_type(dev) != PCI_EXP_TYPE_DOWNSTREAM)
return;
/*
* Find the NHI and confirm that we are a bridge on the Thunderbolt
* host controller and not on a Thunderbolt endpoint.
*/
sibling = pci_get_slot(dev->bus, 0x0);
if (sibling == dev)
goto out; /* we are the downstream bridge to the NHI */
if (!sibling || !sibling->subordinate)
goto out;
nhi = pci_get_slot(sibling->subordinate, 0x0);
if (!nhi)
goto out;
if (nhi->vendor != PCI_VENDOR_ID_INTEL
|| (nhi->device != PCI_DEVICE_ID_INTEL_LIGHT_RIDGE &&
nhi->device != PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C &&
nhi->device != PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI &&
nhi->device != PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI)
|| nhi->class != PCI_CLASS_SYSTEM_OTHER << 8)
goto out;
pci_info(dev, "quirk: waiting for Thunderbolt to reestablish PCI tunnels...\n");
device_pm_wait_for_dev(&dev->dev, &nhi->dev);
out:
pci_dev_put(nhi);
pci_dev_put(sibling);
}
DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_INTEL,
PCI_DEVICE_ID_INTEL_LIGHT_RIDGE,
quirk_apple_wait_for_thunderbolt);
DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_INTEL,
PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C,
quirk_apple_wait_for_thunderbolt);
DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_INTEL,
PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_BRIDGE,
quirk_apple_wait_for_thunderbolt);
DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_INTEL,
PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_BRIDGE,
quirk_apple_wait_for_thunderbolt);
#endif
/*

View File

@ -49,6 +49,17 @@ config PHY_XGENE
help
This option enables support for APM X-Gene SoC multi-purpose PHY.
config USB_LGM_PHY
tristate "INTEL Lightning Mountain USB PHY Driver"
depends on USB_SUPPORT
select USB_PHY
select REGULATOR
select REGULATOR_FIXED_VOLTAGE
help
Enable this to support Intel DWC3 PHY USB phy. This driver provides
interface to interact with USB GEN-II and USB 3.x PHY that is part
of the Intel network SOC.
source "drivers/phy/allwinner/Kconfig"
source "drivers/phy/amlogic/Kconfig"
source "drivers/phy/broadcom/Kconfig"

View File

@ -8,6 +8,7 @@ obj-$(CONFIG_GENERIC_PHY_MIPI_DPHY) += phy-core-mipi-dphy.o
obj-$(CONFIG_PHY_LPC18XX_USB_OTG) += phy-lpc18xx-usb-otg.o
obj-$(CONFIG_PHY_XGENE) += phy-xgene.o
obj-$(CONFIG_PHY_PISTACHIO_USB) += phy-pistachio-usb.o
obj-$(CONFIG_USB_LGM_PHY) += phy-lgm-usb.o
obj-y += allwinner/ \
amlogic/ \
broadcom/ \

View File

@ -13,6 +13,7 @@
#include <linux/bcma/bcma.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/iopoll.h>
#include <linux/mdio.h>
#include <linux/module.h>
#include <linux/of_address.h>
@ -258,29 +259,24 @@ static struct mdio_driver bcm_ns_usb3_mdio_driver = {
**************************************************/
static int bcm_ns_usb3_wait_reg(struct bcm_ns_usb3 *usb3, void __iomem *addr,
u32 mask, u32 value, unsigned long timeout)
u32 mask, u32 value, int usec)
{
unsigned long deadline = jiffies + timeout;
u32 val;
int ret;
do {
val = readl(addr);
if ((val & mask) == value)
return 0;
cpu_relax();
udelay(10);
} while (!time_after_eq(jiffies, deadline));
ret = readl_poll_timeout_atomic(addr, val, ((val & mask) == value),
10, usec);
if (ret)
dev_err(usb3->dev, "Timeout waiting for register %p\n", addr);
dev_err(usb3->dev, "Timeout waiting for register %p\n", addr);
return -EBUSY;
return ret;
}
static inline int bcm_ns_usb3_mii_mng_wait_idle(struct bcm_ns_usb3 *usb3)
{
return bcm_ns_usb3_wait_reg(usb3, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL,
0x0100, 0x0000,
usecs_to_jiffies(BCM_NS_USB3_MII_MNG_TIMEOUT_US));
BCM_NS_USB3_MII_MNG_TIMEOUT_US);
}
static int bcm_ns_usb3_platform_phy_write(struct bcm_ns_usb3 *usb3, u16 reg,

View File

@ -18,6 +18,7 @@
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/irq.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
@ -87,17 +88,11 @@ static const unsigned int usb_extcon_cable[] = {
static inline int pll_lock_stat(u32 usb_reg, int reg_mask,
struct ns2_phy_driver *driver)
{
int retry = PLL_LOCK_RETRY;
u32 val;
do {
udelay(1);
val = readl(driver->icfgdrd_regs + usb_reg);
if (val & reg_mask)
return 0;
} while (--retry > 0);
return -EBUSY;
return readl_poll_timeout_atomic(driver->icfgdrd_regs + usb_reg,
val, (val & reg_mask), 1,
PLL_LOCK_RETRY);
}
static int ns2_drd_phy_init(struct phy *phy)

View File

@ -5,6 +5,7 @@
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
@ -109,19 +110,15 @@ static inline void bcm_usb_reg32_setbits(void __iomem *addr, uint32_t set)
static int bcm_usb_pll_lock_check(void __iomem *addr, u32 bit)
{
int retry;
u32 rd_data;
u32 data;
int ret;
retry = PLL_LOCK_RETRY_COUNT;
do {
rd_data = readl(addr);
if (rd_data & bit)
return 0;
udelay(1);
} while (--retry > 0);
ret = readl_poll_timeout_atomic(addr, data, (data & bit), 1,
PLL_LOCK_RETRY_COUNT);
if (ret)
pr_err("%s: FAIL\n", __func__);
pr_err("%s: FAIL\n", __func__);
return -ETIMEDOUT;
return ret;
}
static int bcm_usb_ss_phy_init(struct bcm_usb_phy_cfg *phy_cfg)

View File

@ -97,7 +97,7 @@ struct cdns_reg_pairs {
struct cdns_salvo_data {
u8 reg_offset_shift;
struct cdns_reg_pairs *init_sequence_val;
const struct cdns_reg_pairs *init_sequence_val;
u8 init_sequence_length;
};
@ -126,7 +126,7 @@ static void cdns_salvo_write(struct cdns_salvo_phy *salvo_phy,
* Below bringup sequence pair are from Cadence PHY's User Guide
* and NXP platform tuning results.
*/
static struct cdns_reg_pairs cdns_nxp_sequence_pair[] = {
static const struct cdns_reg_pairs cdns_nxp_sequence_pair[] = {
{0x0830, PHY_PMA_CMN_CTRL1},
{0x0010, TB_ADDR_CMN_DIAG_HSCLK_SEL},
{0x00f0, TB_ADDR_CMN_PLL0_VCOCAL_INIT_TMR},
@ -217,7 +217,7 @@ static int cdns_salvo_phy_init(struct phy *phy)
return ret;
for (i = 0; i < data->init_sequence_length; i++) {
struct cdns_reg_pairs *reg_pair = data->init_sequence_val + i;
const struct cdns_reg_pairs *reg_pair = data->init_sequence_val + i;
cdns_salvo_write(salvo_phy, reg_pair->off, reg_pair->val);
}
@ -251,7 +251,7 @@ static int cdns_salvo_phy_power_off(struct phy *phy)
return 0;
}
static struct phy_ops cdns_salvo_phy_ops = {
static const struct phy_ops cdns_salvo_phy_ops = {
.init = cdns_salvo_phy_init,
.power_on = cdns_salvo_phy_power_on,
.power_off = cdns_salvo_phy_power_off,

View File

@ -172,10 +172,10 @@ struct cdns_sierra_data {
u32 pcie_ln_regs;
u32 usb_cmn_regs;
u32 usb_ln_regs;
struct cdns_reg_pairs *pcie_cmn_vals;
struct cdns_reg_pairs *pcie_ln_vals;
struct cdns_reg_pairs *usb_cmn_vals;
struct cdns_reg_pairs *usb_ln_vals;
const struct cdns_reg_pairs *pcie_cmn_vals;
const struct cdns_reg_pairs *pcie_ln_vals;
const struct cdns_reg_pairs *usb_cmn_vals;
const struct cdns_reg_pairs *usb_ln_vals;
};
struct cdns_regmap_cdb_context {
@ -233,7 +233,7 @@ static int cdns_regmap_read(void *context, unsigned int reg, unsigned int *val)
.reg_read = cdns_regmap_read, \
}
static struct regmap_config cdns_sierra_lane_cdb_config[] = {
static const struct regmap_config cdns_sierra_lane_cdb_config[] = {
SIERRA_LANE_CDB_REGMAP_CONF("0"),
SIERRA_LANE_CDB_REGMAP_CONF("1"),
SIERRA_LANE_CDB_REGMAP_CONF("2"),
@ -252,7 +252,7 @@ static struct regmap_config cdns_sierra_lane_cdb_config[] = {
SIERRA_LANE_CDB_REGMAP_CONF("15"),
};
static struct regmap_config cdns_sierra_common_cdb_config = {
static const struct regmap_config cdns_sierra_common_cdb_config = {
.name = "sierra_common_cdb",
.reg_stride = 1,
.fast_io = true,
@ -260,7 +260,7 @@ static struct regmap_config cdns_sierra_common_cdb_config = {
.reg_read = cdns_regmap_read,
};
static struct regmap_config cdns_sierra_phy_config_ctrl_config = {
static const struct regmap_config cdns_sierra_phy_config_ctrl_config = {
.name = "sierra_phy_config_ctrl",
.reg_stride = 1,
.fast_io = true,
@ -274,7 +274,7 @@ static int cdns_sierra_phy_init(struct phy *gphy)
struct cdns_sierra_phy *phy = dev_get_drvdata(gphy->dev.parent);
struct regmap *regmap;
int i, j;
struct cdns_reg_pairs *cmn_vals, *ln_vals;
const struct cdns_reg_pairs *cmn_vals, *ln_vals;
u32 num_cmn_regs, num_ln_regs;
/* Initialise the PHY registers, unless auto configured */
@ -654,7 +654,7 @@ static int cdns_sierra_phy_remove(struct platform_device *pdev)
}
/* refclk100MHz_32b_PCIe_cmn_pll_ext_ssc */
static struct cdns_reg_pairs cdns_pcie_cmn_regs_ext_ssc[] = {
static const struct cdns_reg_pairs cdns_pcie_cmn_regs_ext_ssc[] = {
{0x2106, SIERRA_CMN_PLLLC_LF_COEFF_MODE1_PREG},
{0x2106, SIERRA_CMN_PLLLC_LF_COEFF_MODE0_PREG},
{0x8A06, SIERRA_CMN_PLLLC_BWCAL_MODE1_PREG},
@ -663,7 +663,7 @@ static struct cdns_reg_pairs cdns_pcie_cmn_regs_ext_ssc[] = {
};
/* refclk100MHz_32b_PCIe_ln_ext_ssc */
static struct cdns_reg_pairs cdns_pcie_ln_regs_ext_ssc[] = {
static const struct cdns_reg_pairs cdns_pcie_ln_regs_ext_ssc[] = {
{0x813E, SIERRA_CLKPATHCTRL_TMR_PREG},
{0x8047, SIERRA_RX_CREQ_FLTR_A_MODE3_PREG},
{0x808F, SIERRA_RX_CREQ_FLTR_A_MODE2_PREG},
@ -674,7 +674,7 @@ static struct cdns_reg_pairs cdns_pcie_ln_regs_ext_ssc[] = {
};
/* refclk100MHz_20b_USB_cmn_pll_ext_ssc */
static struct cdns_reg_pairs cdns_usb_cmn_regs_ext_ssc[] = {
static const struct cdns_reg_pairs cdns_usb_cmn_regs_ext_ssc[] = {
{0x2085, SIERRA_CMN_PLLLC_LF_COEFF_MODE1_PREG},
{0x2085, SIERRA_CMN_PLLLC_LF_COEFF_MODE0_PREG},
{0x0000, SIERRA_CMN_PLLLC_BWCAL_MODE0_PREG},
@ -682,7 +682,7 @@ static struct cdns_reg_pairs cdns_usb_cmn_regs_ext_ssc[] = {
};
/* refclk100MHz_20b_USB_ln_ext_ssc */
static struct cdns_reg_pairs cdns_usb_ln_regs_ext_ssc[] = {
static const struct cdns_reg_pairs cdns_usb_ln_regs_ext_ssc[] = {
{0xFE0A, SIERRA_DET_STANDEC_A_PREG},
{0x000F, SIERRA_DET_STANDEC_B_PREG},
{0x55A5, SIERRA_DET_STANDEC_C_PREG},

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,20 @@
// SPDX-License-Identifier: GPL-2.0+
/* Copyright (c) 2017 NXP. */
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#define PHY_CTRL0 0x0
#define PHY_CTRL0_REF_SSP_EN BIT(2)
#define PHY_CTRL0_FSEL_MASK GENMASK(10, 5)
#define PHY_CTRL0_FSEL_24M 0x2a
#define PHY_CTRL1 0x4
#define PHY_CTRL1_RESET BIT(0)
@ -20,6 +25,11 @@
#define PHY_CTRL2 0x8
#define PHY_CTRL2_TXENABLEN0 BIT(8)
#define PHY_CTRL2_OTG_DISABLE BIT(9)
#define PHY_CTRL6 0x18
#define PHY_CTRL6_ALT_CLK_EN BIT(1)
#define PHY_CTRL6_ALT_CLK_SEL BIT(0)
struct imx8mq_usb_phy {
struct phy *phy;
@ -54,6 +64,44 @@ static int imx8mq_usb_phy_init(struct phy *phy)
return 0;
}
static int imx8mp_usb_phy_init(struct phy *phy)
{
struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy);
u32 value;
/* USB3.0 PHY signal fsel for 24M ref */
value = readl(imx_phy->base + PHY_CTRL0);
value &= ~PHY_CTRL0_FSEL_MASK;
value |= FIELD_PREP(PHY_CTRL0_FSEL_MASK, PHY_CTRL0_FSEL_24M);
writel(value, imx_phy->base + PHY_CTRL0);
/* Disable alt_clk_en and use internal MPLL clocks */
value = readl(imx_phy->base + PHY_CTRL6);
value &= ~(PHY_CTRL6_ALT_CLK_SEL | PHY_CTRL6_ALT_CLK_EN);
writel(value, imx_phy->base + PHY_CTRL6);
value = readl(imx_phy->base + PHY_CTRL1);
value &= ~(PHY_CTRL1_VDATSRCENB0 | PHY_CTRL1_VDATDETENB0);
value |= PHY_CTRL1_RESET | PHY_CTRL1_ATERESET;
writel(value, imx_phy->base + PHY_CTRL1);
value = readl(imx_phy->base + PHY_CTRL0);
value |= PHY_CTRL0_REF_SSP_EN;
writel(value, imx_phy->base + PHY_CTRL0);
value = readl(imx_phy->base + PHY_CTRL2);
value |= PHY_CTRL2_TXENABLEN0 | PHY_CTRL2_OTG_DISABLE;
writel(value, imx_phy->base + PHY_CTRL2);
udelay(10);
value = readl(imx_phy->base + PHY_CTRL1);
value &= ~(PHY_CTRL1_RESET | PHY_CTRL1_ATERESET);
writel(value, imx_phy->base + PHY_CTRL1);
return 0;
}
static int imx8mq_phy_power_on(struct phy *phy)
{
struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy);
@ -76,19 +124,36 @@ static int imx8mq_phy_power_off(struct phy *phy)
return 0;
}
static struct phy_ops imx8mq_usb_phy_ops = {
static const struct phy_ops imx8mq_usb_phy_ops = {
.init = imx8mq_usb_phy_init,
.power_on = imx8mq_phy_power_on,
.power_off = imx8mq_phy_power_off,
.owner = THIS_MODULE,
};
static struct phy_ops imx8mp_usb_phy_ops = {
.init = imx8mp_usb_phy_init,
.power_on = imx8mq_phy_power_on,
.power_off = imx8mq_phy_power_off,
.owner = THIS_MODULE,
};
static const struct of_device_id imx8mq_usb_phy_of_match[] = {
{.compatible = "fsl,imx8mq-usb-phy",
.data = &imx8mq_usb_phy_ops,},
{.compatible = "fsl,imx8mp-usb-phy",
.data = &imx8mp_usb_phy_ops,},
{ }
};
MODULE_DEVICE_TABLE(of, imx8mq_usb_phy_of_match);
static int imx8mq_usb_phy_probe(struct platform_device *pdev)
{
struct phy_provider *phy_provider;
struct device *dev = &pdev->dev;
struct imx8mq_usb_phy *imx_phy;
struct resource *res;
const struct phy_ops *phy_ops;
imx_phy = devm_kzalloc(dev, sizeof(*imx_phy), GFP_KERNEL);
if (!imx_phy)
@ -105,7 +170,11 @@ static int imx8mq_usb_phy_probe(struct platform_device *pdev)
if (IS_ERR(imx_phy->base))
return PTR_ERR(imx_phy->base);
imx_phy->phy = devm_phy_create(dev, NULL, &imx8mq_usb_phy_ops);
phy_ops = of_device_get_match_data(dev);
if (!phy_ops)
return -EINVAL;
imx_phy->phy = devm_phy_create(dev, NULL, phy_ops);
if (IS_ERR(imx_phy->phy))
return PTR_ERR(imx_phy->phy);
@ -120,12 +189,6 @@ static int imx8mq_usb_phy_probe(struct platform_device *pdev)
return PTR_ERR_OR_ZERO(phy_provider);
}
static const struct of_device_id imx8mq_usb_phy_of_match[] = {
{.compatible = "fsl,imx8mq-usb-phy",},
{ },
};
MODULE_DEVICE_TABLE(of, imx8mq_usb_phy_of_match);
static struct platform_driver imx8mq_usb_phy_driver = {
.probe = imx8mq_usb_phy_probe,
.driver = {

View File

@ -161,7 +161,7 @@ out:
return ret;
}
static struct phy_ops hi3660_phy_ops = {
static const struct phy_ops hi3660_phy_ops = {
.init = hi3660_phy_init,
.exit = hi3660_phy_exit,
.owner = THIS_MODULE,

View File

@ -1,9 +1,21 @@
# SPDX-License-Identifier: GPL-2.0
#
# Phy drivers for Intel Lightning Mountain(LGM) platform
# Phy drivers for Intel platforms
#
config PHY_INTEL_COMBO
bool "Intel ComboPHY driver"
config PHY_INTEL_KEEMBAY_EMMC
tristate "Intel Keem Bay EMMC PHY driver"
depends on (OF && ARM64) || COMPILE_TEST
depends on HAS_IOMEM
select GENERIC_PHY
select REGMAP_MMIO
help
Choose this option if you have an Intel Keem Bay SoC.
To compile this driver as a module, choose M here: the module
will be called phy-keembay-emmc.ko.
config PHY_INTEL_LGM_COMBO
bool "Intel Lightning Mountain ComboPHY driver"
depends on X86 || COMPILE_TEST
depends on OF && HAS_IOMEM
select MFD_SYSCON
@ -16,8 +28,8 @@ config PHY_INTEL_COMBO
chipsets which provides PHYs for various controllers, EMAC,
SATA and PCIe.
config PHY_INTEL_EMMC
tristate "Intel EMMC PHY driver"
config PHY_INTEL_LGM_EMMC
tristate "Intel Lightning Mountain EMMC PHY driver"
depends on X86 || COMPILE_TEST
select GENERIC_PHY
help

View File

@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_PHY_INTEL_COMBO) += phy-intel-combo.o
obj-$(CONFIG_PHY_INTEL_EMMC) += phy-intel-emmc.o
obj-$(CONFIG_PHY_INTEL_KEEMBAY_EMMC) += phy-intel-keembay-emmc.o
obj-$(CONFIG_PHY_INTEL_LGM_COMBO) += phy-intel-lgm-combo.o
obj-$(CONFIG_PHY_INTEL_LGM_EMMC) += phy-intel-lgm-emmc.o

View File

@ -0,0 +1,307 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Intel Keem Bay eMMC PHY driver
* Copyright (C) 2020 Intel Corporation
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
/* eMMC/SD/SDIO core/phy configuration registers */
#define PHY_CFG_0 0x24
#define SEL_DLY_TXCLK_MASK BIT(29)
#define OTAP_DLY_ENA_MASK BIT(27)
#define OTAP_DLY_SEL_MASK GENMASK(26, 23)
#define DLL_EN_MASK BIT(10)
#define PWR_DOWN_MASK BIT(0)
#define PHY_CFG_2 0x2c
#define SEL_FREQ_MASK GENMASK(12, 10)
#define PHY_STAT 0x40
#define CAL_DONE_MASK BIT(6)
#define IS_CALDONE(x) ((x) & CAL_DONE_MASK)
#define DLL_RDY_MASK BIT(5)
#define IS_DLLRDY(x) ((x) & DLL_RDY_MASK)
/* From ACS_eMMC51_16nFFC_RO1100_Userguide_v1p0.pdf p17 */
#define FREQSEL_200M_170M 0x0
#define FREQSEL_170M_140M 0x1
#define FREQSEL_140M_110M 0x2
#define FREQSEL_110M_80M 0x3
#define FREQSEL_80M_50M 0x4
struct keembay_emmc_phy {
struct regmap *syscfg;
struct clk *emmcclk;
};
static const struct regmap_config keembay_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
};
static int keembay_emmc_phy_power(struct phy *phy, bool on_off)
{
struct keembay_emmc_phy *priv = phy_get_drvdata(phy);
unsigned int caldone;
unsigned int dllrdy;
unsigned int freqsel;
unsigned int mhz;
int ret;
/*
* Keep phyctrl_pdb and phyctrl_endll low to allow
* initialization of CALIO state M/C DFFs
*/
ret = regmap_update_bits(priv->syscfg, PHY_CFG_0, PWR_DOWN_MASK,
FIELD_PREP(PWR_DOWN_MASK, 0));
if (ret) {
dev_err(&phy->dev, "CALIO power down bar failed: %d\n", ret);
return ret;
}
ret = regmap_update_bits(priv->syscfg, PHY_CFG_0, DLL_EN_MASK,
FIELD_PREP(DLL_EN_MASK, 0));
if (ret) {
dev_err(&phy->dev, "turn off the dll failed: %d\n", ret);
return ret;
}
/* Already finish power off above */
if (!on_off)
return 0;
mhz = DIV_ROUND_CLOSEST(clk_get_rate(priv->emmcclk), 1000000);
if (mhz <= 200 && mhz >= 170)
freqsel = FREQSEL_200M_170M;
else if (mhz <= 170 && mhz >= 140)
freqsel = FREQSEL_170M_140M;
else if (mhz <= 140 && mhz >= 110)
freqsel = FREQSEL_140M_110M;
else if (mhz <= 110 && mhz >= 80)
freqsel = FREQSEL_110M_80M;
else if (mhz <= 80 && mhz >= 50)
freqsel = FREQSEL_80M_50M;
else
freqsel = 0x0;
if (mhz < 50 || mhz > 200)
dev_warn(&phy->dev, "Unsupported rate: %d MHz\n", mhz);
/*
* According to the user manual, calpad calibration
* cycle takes more than 2us without the minimal recommended
* value, so we may need a little margin here
*/
udelay(5);
ret = regmap_update_bits(priv->syscfg, PHY_CFG_0, PWR_DOWN_MASK,
FIELD_PREP(PWR_DOWN_MASK, 1));
if (ret) {
dev_err(&phy->dev, "CALIO power down bar failed: %d\n", ret);
return ret;
}
/*
* According to the user manual, it asks driver to wait 5us for
* calpad busy trimming. However it is documented that this value is
* PVT(A.K.A. process, voltage and temperature) relevant, so some
* failure cases are found which indicates we should be more tolerant
* to calpad busy trimming.
*/
ret = regmap_read_poll_timeout(priv->syscfg, PHY_STAT,
caldone, IS_CALDONE(caldone),
0, 50);
if (ret) {
dev_err(&phy->dev, "caldone failed, ret=%d\n", ret);
return ret;
}
/* Set the frequency of the DLL operation */
ret = regmap_update_bits(priv->syscfg, PHY_CFG_2, SEL_FREQ_MASK,
FIELD_PREP(SEL_FREQ_MASK, freqsel));
if (ret) {
dev_err(&phy->dev, "set the frequency of dll failed:%d\n", ret);
return ret;
}
/* Turn on the DLL */
ret = regmap_update_bits(priv->syscfg, PHY_CFG_0, DLL_EN_MASK,
FIELD_PREP(DLL_EN_MASK, 1));
if (ret) {
dev_err(&phy->dev, "turn on the dll failed: %d\n", ret);
return ret;
}
/*
* We turned on the DLL even though the rate was 0 because we the
* clock might be turned on later. ...but we can't wait for the DLL
* to lock when the rate is 0 because it will never lock with no
* input clock.
*
* Technically we should be checking the lock later when the clock
* is turned on, but for now we won't.
*/
if (mhz == 0)
return 0;
/*
* After enabling analog DLL circuits docs say that we need 10.2 us if
* our source clock is at 50 MHz and that lock time scales linearly
* with clock speed. If we are powering on the PHY and the card clock
* is super slow (like 100kHz) this could take as long as 5.1 ms as
* per the math: 10.2 us * (50000000 Hz / 100000 Hz) => 5.1 ms
* hopefully we won't be running at 100 kHz, but we should still make
* sure we wait long enough.
*
* NOTE: There appear to be corner cases where the DLL seems to take
* extra long to lock for reasons that aren't understood. In some
* extreme cases we've seen it take up to over 10ms (!). We'll be
* generous and give it 50ms.
*/
ret = regmap_read_poll_timeout(priv->syscfg, PHY_STAT,
dllrdy, IS_DLLRDY(dllrdy),
0, 50 * USEC_PER_MSEC);
if (ret)
dev_err(&phy->dev, "dllrdy failed, ret=%d\n", ret);
return ret;
}
static int keembay_emmc_phy_init(struct phy *phy)
{
struct keembay_emmc_phy *priv = phy_get_drvdata(phy);
/*
* We purposely get the clock here and not in probe to avoid the
* circular dependency problem. We expect:
* - PHY driver to probe
* - SDHCI driver to start probe
* - SDHCI driver to register it's clock
* - SDHCI driver to get the PHY
* - SDHCI driver to init the PHY
*
* The clock is optional, so upon any error just return it like
* any other error to user.
*/
priv->emmcclk = clk_get_optional(&phy->dev, "emmcclk");
return PTR_ERR_OR_ZERO(priv->emmcclk);
}
static int keembay_emmc_phy_exit(struct phy *phy)
{
struct keembay_emmc_phy *priv = phy_get_drvdata(phy);
clk_put(priv->emmcclk);
return 0;
};
static int keembay_emmc_phy_power_on(struct phy *phy)
{
struct keembay_emmc_phy *priv = phy_get_drvdata(phy);
int ret;
/* Delay chain based txclk: enable */
ret = regmap_update_bits(priv->syscfg, PHY_CFG_0, SEL_DLY_TXCLK_MASK,
FIELD_PREP(SEL_DLY_TXCLK_MASK, 1));
if (ret) {
dev_err(&phy->dev, "ERROR: delay chain txclk set: %d\n", ret);
return ret;
}
/* Output tap delay: enable */
ret = regmap_update_bits(priv->syscfg, PHY_CFG_0, OTAP_DLY_ENA_MASK,
FIELD_PREP(OTAP_DLY_ENA_MASK, 1));
if (ret) {
dev_err(&phy->dev, "ERROR: output tap delay set: %d\n", ret);
return ret;
}
/* Output tap delay */
ret = regmap_update_bits(priv->syscfg, PHY_CFG_0, OTAP_DLY_SEL_MASK,
FIELD_PREP(OTAP_DLY_SEL_MASK, 2));
if (ret) {
dev_err(&phy->dev, "ERROR: output tap delay select: %d\n", ret);
return ret;
}
/* Power up eMMC phy analog blocks */
return keembay_emmc_phy_power(phy, true);
}
static int keembay_emmc_phy_power_off(struct phy *phy)
{
/* Power down eMMC phy analog blocks */
return keembay_emmc_phy_power(phy, false);
}
static const struct phy_ops ops = {
.init = keembay_emmc_phy_init,
.exit = keembay_emmc_phy_exit,
.power_on = keembay_emmc_phy_power_on,
.power_off = keembay_emmc_phy_power_off,
.owner = THIS_MODULE,
};
static int keembay_emmc_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct keembay_emmc_phy *priv;
struct phy *generic_phy;
struct phy_provider *phy_provider;
void __iomem *base;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base))
return PTR_ERR(base);
priv->syscfg = devm_regmap_init_mmio(dev, base, &keembay_regmap_config);
if (IS_ERR(priv->syscfg))
return PTR_ERR(priv->syscfg);
generic_phy = devm_phy_create(dev, np, &ops);
if (IS_ERR(generic_phy))
return dev_err_probe(dev, PTR_ERR(generic_phy),
"failed to create PHY\n");
phy_set_drvdata(generic_phy, priv);
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
return PTR_ERR_OR_ZERO(phy_provider);
}
static const struct of_device_id keembay_emmc_phy_dt_ids[] = {
{ .compatible = "intel,keembay-emmc-phy" },
{}
};
MODULE_DEVICE_TABLE(of, keembay_emmc_phy_dt_ids);
static struct platform_driver keembay_emmc_phy_driver = {
.probe = keembay_emmc_phy_probe,
.driver = {
.name = "keembay-emmc-phy",
.of_match_table = keembay_emmc_phy_dt_ids,
},
};
module_platform_driver(keembay_emmc_phy_driver);
MODULE_AUTHOR("Wan Ahmad Zainie <wan.ahmad.zainie.wan.mohamad@intel.com>");
MODULE_DESCRIPTION("Intel Keem Bay eMMC PHY driver");
MODULE_LICENSE("GPL v2");

View File

@ -141,7 +141,7 @@ static int ltq_rcu_usb2_phy_power_off(struct phy *phy)
return 0;
}
static struct phy_ops ltq_rcu_usb2_phy_ops = {
static const struct phy_ops ltq_rcu_usb2_phy_ops = {
.init = ltq_rcu_usb2_phy_init,
.power_on = ltq_rcu_usb2_phy_power_on,
.power_off = ltq_rcu_usb2_phy_power_off,

View File

@ -349,7 +349,7 @@ static int ltq_vrx200_pcie_phy_power_off(struct phy *phy)
return 0;
}
static struct phy_ops ltq_vrx200_pcie_phy_ops = {
static const struct phy_ops ltq_vrx200_pcie_phy_ops = {
.init = ltq_vrx200_pcie_phy_init,
.exit = ltq_vrx200_pcie_phy_exit,
.power_on = ltq_vrx200_pcie_phy_power_on,

View File

@ -12,6 +12,7 @@
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/module.h>
@ -44,15 +45,12 @@ struct mv_hsic_phy {
struct clk *clk;
};
static bool wait_for_reg(void __iomem *reg, u32 mask, unsigned long timeout)
static int wait_for_reg(void __iomem *reg, u32 mask, u32 ms)
{
timeout += jiffies;
while (time_is_after_eq_jiffies(timeout)) {
if ((readl(reg) & mask) == mask)
return true;
msleep(1);
}
return false;
u32 val;
return readl_poll_timeout(reg, val, ((val & mask) == mask),
1000, 1000 * ms);
}
static int mv_hsic_phy_init(struct phy *phy)
@ -60,6 +58,7 @@ static int mv_hsic_phy_init(struct phy *phy)
struct mv_hsic_phy *mv_phy = phy_get_drvdata(phy);
struct platform_device *pdev = mv_phy->pdev;
void __iomem *base = mv_phy->base;
int ret;
clk_prepare_enable(mv_phy->clk);
@ -75,14 +74,14 @@ static int mv_hsic_phy_init(struct phy *phy)
base + PHY_28NM_HSIC_PLL_CTRL2);
/* Make sure PHY PLL is locked */
if (!wait_for_reg(base + PHY_28NM_HSIC_PLL_CTRL2,
PHY_28NM_HSIC_H2S_PLL_LOCK, HZ / 10)) {
ret = wait_for_reg(base + PHY_28NM_HSIC_PLL_CTRL2,
PHY_28NM_HSIC_H2S_PLL_LOCK, 100);
if (ret) {
dev_err(&pdev->dev, "HSIC PHY PLL not locked after 100mS.");
clk_disable_unprepare(mv_phy->clk);
return -ETIMEDOUT;
}
return 0;
return ret;
}
static int mv_hsic_phy_power_on(struct phy *phy)
@ -91,6 +90,7 @@ static int mv_hsic_phy_power_on(struct phy *phy)
struct platform_device *pdev = mv_phy->pdev;
void __iomem *base = mv_phy->base;
u32 reg;
int ret;
reg = readl(base + PHY_28NM_HSIC_CTRL);
/* Avoid SE0 state when resume for some device will take it as reset */
@ -108,20 +108,20 @@ static int mv_hsic_phy_power_on(struct phy *phy)
*/
/* Make sure PHY Calibration is ready */
if (!wait_for_reg(base + PHY_28NM_HSIC_IMPCAL_CAL,
PHY_28NM_HSIC_H2S_IMPCAL_DONE, HZ / 10)) {
ret = wait_for_reg(base + PHY_28NM_HSIC_IMPCAL_CAL,
PHY_28NM_HSIC_H2S_IMPCAL_DONE, 100);
if (ret) {
dev_warn(&pdev->dev, "HSIC PHY READY not set after 100mS.");
return -ETIMEDOUT;
return ret;
}
/* Waiting for HSIC connect int*/
if (!wait_for_reg(base + PHY_28NM_HSIC_INT,
PHY_28NM_HSIC_CONNECT_INT, HZ / 5)) {
ret = wait_for_reg(base + PHY_28NM_HSIC_INT,
PHY_28NM_HSIC_CONNECT_INT, 200);
if (ret)
dev_warn(&pdev->dev, "HSIC wait for connect interrupt timeout.");
return -ETIMEDOUT;
}
return 0;
return ret;
}
static int mv_hsic_phy_power_off(struct phy *phy)

View File

@ -13,6 +13,7 @@
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/module.h>
@ -138,15 +139,12 @@ struct mv_usb2_phy {
struct clk *clk;
};
static bool wait_for_reg(void __iomem *reg, u32 mask, unsigned long timeout)
static int wait_for_reg(void __iomem *reg, u32 mask, u32 ms)
{
timeout += jiffies;
while (time_is_after_eq_jiffies(timeout)) {
if ((readl(reg) & mask) == mask)
return true;
msleep(1);
}
return false;
u32 val;
return readl_poll_timeout(reg, val, ((val & mask) == mask),
1000, 1000 * ms);
}
static int mv_usb2_phy_28nm_init(struct phy *phy)
@ -208,24 +206,23 @@ static int mv_usb2_phy_28nm_init(struct phy *phy)
*/
/* Make sure PHY Calibration is ready */
if (!wait_for_reg(base + PHY_28NM_CAL_REG,
PHY_28NM_PLL_PLLCAL_DONE | PHY_28NM_PLL_IMPCAL_DONE,
HZ / 10)) {
ret = wait_for_reg(base + PHY_28NM_CAL_REG,
PHY_28NM_PLL_PLLCAL_DONE | PHY_28NM_PLL_IMPCAL_DONE,
100);
if (ret) {
dev_warn(&pdev->dev, "USB PHY PLL calibrate not done after 100mS.");
ret = -ETIMEDOUT;
goto err_clk;
}
if (!wait_for_reg(base + PHY_28NM_RX_REG1,
PHY_28NM_RX_SQCAL_DONE, HZ / 10)) {
ret = wait_for_reg(base + PHY_28NM_RX_REG1,
PHY_28NM_RX_SQCAL_DONE, 100);
if (ret) {
dev_warn(&pdev->dev, "USB PHY RX SQ calibrate not done after 100mS.");
ret = -ETIMEDOUT;
goto err_clk;
}
/* Make sure PHY PLL is ready */
if (!wait_for_reg(base + PHY_28NM_PLL_REG0,
PHY_28NM_PLL_READY, HZ / 10)) {
ret = wait_for_reg(base + PHY_28NM_PLL_REG0, PHY_28NM_PLL_READY, 100);
if (ret) {
dev_warn(&pdev->dev, "PLL_READY not set after 100mS.");
ret = -ETIMEDOUT;
goto err_clk;
}

284
drivers/phy/phy-lgm-usb.c Normal file
View File

@ -0,0 +1,284 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Intel LGM USB PHY driver
*
* Copyright (C) 2020 Intel Corporation.
*/
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
#include <linux/usb/phy.h>
#include <linux/workqueue.h>
#define CTRL1_OFFSET 0x14
#define SRAM_EXT_LD_DONE BIT(25)
#define SRAM_INIT_DONE BIT(26)
#define TCPC_OFFSET 0x1014
#define TCPC_MUX_CTL GENMASK(1, 0)
#define MUX_NC 0
#define MUX_USB 1
#define MUX_DP 2
#define MUX_USBDP 3
#define TCPC_FLIPPED BIT(2)
#define TCPC_LOW_POWER_EN BIT(3)
#define TCPC_VALID BIT(4)
#define TCPC_CONN \
(TCPC_VALID | FIELD_PREP(TCPC_MUX_CTL, MUX_USB))
#define TCPC_DISCONN \
(TCPC_VALID | FIELD_PREP(TCPC_MUX_CTL, MUX_NC) | TCPC_LOW_POWER_EN)
static const char *const PHY_RESETS[] = { "phy31", "phy", };
static const char *const CTL_RESETS[] = { "apb", "ctrl", };
struct tca_apb {
struct reset_control *resets[ARRAY_SIZE(PHY_RESETS)];
struct regulator *vbus;
struct work_struct wk;
struct usb_phy phy;
bool regulator_enabled;
bool phy_initialized;
bool connected;
};
static int get_flipped(struct tca_apb *ta, bool *flipped)
{
union extcon_property_value property;
int ret;
ret = extcon_get_property(ta->phy.edev, EXTCON_USB_HOST,
EXTCON_PROP_USB_TYPEC_POLARITY, &property);
if (ret) {
dev_err(ta->phy.dev, "no polarity property from extcon\n");
return ret;
}
*flipped = property.intval;
return 0;
}
static int phy_init(struct usb_phy *phy)
{
struct tca_apb *ta = container_of(phy, struct tca_apb, phy);
void __iomem *ctrl1 = phy->io_priv + CTRL1_OFFSET;
int val, ret, i;
if (ta->phy_initialized)
return 0;
for (i = 0; i < ARRAY_SIZE(PHY_RESETS); i++)
reset_control_deassert(ta->resets[i]);
ret = readl_poll_timeout(ctrl1, val, val & SRAM_INIT_DONE, 10, 10 * 1000);
if (ret) {
dev_err(ta->phy.dev, "SRAM init failed, 0x%x\n", val);
return ret;
}
writel(readl(ctrl1) | SRAM_EXT_LD_DONE, ctrl1);
ta->phy_initialized = true;
if (!ta->phy.edev) {
writel(TCPC_CONN, ta->phy.io_priv + TCPC_OFFSET);
return phy->set_vbus(phy, true);
}
schedule_work(&ta->wk);
return ret;
}
static void phy_shutdown(struct usb_phy *phy)
{
struct tca_apb *ta = container_of(phy, struct tca_apb, phy);
int i;
if (!ta->phy_initialized)
return;
ta->phy_initialized = false;
flush_work(&ta->wk);
ta->phy.set_vbus(&ta->phy, false);
ta->connected = false;
writel(TCPC_DISCONN, ta->phy.io_priv + TCPC_OFFSET);
for (i = 0; i < ARRAY_SIZE(PHY_RESETS); i++)
reset_control_assert(ta->resets[i]);
}
static int phy_set_vbus(struct usb_phy *phy, int on)
{
struct tca_apb *ta = container_of(phy, struct tca_apb, phy);
int ret;
if (!!on == ta->regulator_enabled)
return 0;
if (on)
ret = regulator_enable(ta->vbus);
else
ret = regulator_disable(ta->vbus);
if (!ret)
ta->regulator_enabled = on;
dev_dbg(ta->phy.dev, "set vbus: %d\n", on);
return ret;
}
static void tca_work(struct work_struct *work)
{
struct tca_apb *ta = container_of(work, struct tca_apb, wk);
bool connected;
bool flipped = false;
u32 val;
int ret;
ret = get_flipped(ta, &flipped);
if (ret)
return;
connected = extcon_get_state(ta->phy.edev, EXTCON_USB_HOST);
if (connected == ta->connected)
return;
ta->connected = connected;
if (connected) {
val = TCPC_CONN;
if (flipped)
val |= TCPC_FLIPPED;
dev_dbg(ta->phy.dev, "connected%s\n", flipped ? " flipped" : "");
} else {
val = TCPC_DISCONN;
dev_dbg(ta->phy.dev, "disconnected\n");
}
writel(val, ta->phy.io_priv + TCPC_OFFSET);
ret = ta->phy.set_vbus(&ta->phy, connected);
if (ret)
dev_err(ta->phy.dev, "failed to set VBUS\n");
}
static int id_notifier(struct notifier_block *nb, unsigned long event, void *ptr)
{
struct tca_apb *ta = container_of(nb, struct tca_apb, phy.id_nb);
if (ta->phy_initialized)
schedule_work(&ta->wk);
return NOTIFY_DONE;
}
static int vbus_notifier(struct notifier_block *nb, unsigned long evnt, void *ptr)
{
return NOTIFY_DONE;
}
static int phy_probe(struct platform_device *pdev)
{
struct reset_control *resets[ARRAY_SIZE(CTL_RESETS)];
struct device *dev = &pdev->dev;
struct usb_phy *phy;
struct tca_apb *ta;
int i;
ta = devm_kzalloc(dev, sizeof(*ta), GFP_KERNEL);
if (!ta)
return -ENOMEM;
platform_set_drvdata(pdev, ta);
INIT_WORK(&ta->wk, tca_work);
phy = &ta->phy;
phy->dev = dev;
phy->label = dev_name(dev);
phy->type = USB_PHY_TYPE_USB3;
phy->init = phy_init;
phy->shutdown = phy_shutdown;
phy->set_vbus = phy_set_vbus;
phy->id_nb.notifier_call = id_notifier;
phy->vbus_nb.notifier_call = vbus_notifier;
phy->io_priv = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(phy->io_priv))
return PTR_ERR(phy->io_priv);
ta->vbus = devm_regulator_get(dev, "vbus");
if (IS_ERR(ta->vbus))
return PTR_ERR(ta->vbus);
for (i = 0; i < ARRAY_SIZE(CTL_RESETS); i++) {
resets[i] = devm_reset_control_get_exclusive(dev, CTL_RESETS[i]);
if (IS_ERR(resets[i])) {
dev_err(dev, "%s reset not found\n", CTL_RESETS[i]);
return PTR_ERR(resets[i]);
}
}
for (i = 0; i < ARRAY_SIZE(PHY_RESETS); i++) {
ta->resets[i] = devm_reset_control_get_exclusive(dev, PHY_RESETS[i]);
if (IS_ERR(ta->resets[i])) {
dev_err(dev, "%s reset not found\n", PHY_RESETS[i]);
return PTR_ERR(ta->resets[i]);
}
}
for (i = 0; i < ARRAY_SIZE(CTL_RESETS); i++)
reset_control_assert(resets[i]);
for (i = 0; i < ARRAY_SIZE(PHY_RESETS); i++)
reset_control_assert(ta->resets[i]);
/*
* Out-of-band reset of the controller after PHY reset will cause
* controller malfunctioning, so we should use in-band controller
* reset only and leave the controller de-asserted here.
*/
for (i = 0; i < ARRAY_SIZE(CTL_RESETS); i++)
reset_control_deassert(resets[i]);
/* Need to wait at least 20us after de-assert the controller */
usleep_range(20, 100);
return usb_add_phy_dev(phy);
}
static int phy_remove(struct platform_device *pdev)
{
struct tca_apb *ta = platform_get_drvdata(pdev);
usb_remove_phy(&ta->phy);
return 0;
}
static const struct of_device_id intel_usb_phy_dt_ids[] = {
{ .compatible = "intel,lgm-usb-phy" },
{ }
};
MODULE_DEVICE_TABLE(of, intel_usb_phy_dt_ids);
static struct platform_driver lgm_phy_driver = {
.driver = {
.name = "lgm-usb-phy",
.of_match_table = intel_usb_phy_dt_ids,
},
.probe = phy_probe,
.remove = phy_remove,
};
module_platform_driver(lgm_phy_driver);
MODULE_DESCRIPTION("Intel LGM USB PHY driver");
MODULE_AUTHOR("Li Yin <yin1.li@intel.com>");
MODULE_AUTHOR("Vadivel Murugan R <vadivel.muruganx.ramuthevar@linux.intel.com>");
MODULE_LICENSE("GPL v2");

View File

@ -4,6 +4,7 @@
*/
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
@ -72,18 +73,12 @@ struct qcom_apq8064_sata_phy {
};
/* Helper function to do poll and timeout */
static int read_poll_timeout(void __iomem *addr, u32 mask)
static int poll_timeout(void __iomem *addr, u32 mask)
{
unsigned long timeout = jiffies + msecs_to_jiffies(TIMEOUT_MS);
u32 val;
do {
if (readl_relaxed(addr) & mask)
return 0;
usleep_range(DELAY_INTERVAL_US, DELAY_INTERVAL_US + 50);
} while (!time_after(jiffies, timeout));
return (readl_relaxed(addr) & mask) ? 0 : -ETIMEDOUT;
return readl_relaxed_poll_timeout(addr, val, (val & mask),
DELAY_INTERVAL_US, TIMEOUT_MS * 1000);
}
static int qcom_apq8064_sata_phy_init(struct phy *generic_phy)
@ -137,21 +132,21 @@ static int qcom_apq8064_sata_phy_init(struct phy *generic_phy)
writel_relaxed(0x05, base + UNIPHY_PLL_LKDET_CFG2);
/* PLL Lock wait */
ret = read_poll_timeout(base + UNIPHY_PLL_STATUS, UNIPHY_PLL_LOCK);
ret = poll_timeout(base + UNIPHY_PLL_STATUS, UNIPHY_PLL_LOCK);
if (ret) {
dev_err(phy->dev, "poll timeout UNIPHY_PLL_STATUS\n");
return ret;
}
/* TX Calibration */
ret = read_poll_timeout(base + SATA_PHY_TX_IMCAL_STAT, SATA_PHY_TX_CAL);
ret = poll_timeout(base + SATA_PHY_TX_IMCAL_STAT, SATA_PHY_TX_CAL);
if (ret) {
dev_err(phy->dev, "poll timeout SATA_PHY_TX_IMCAL_STAT\n");
return ret;
}
/* RX Calibration */
ret = read_poll_timeout(base + SATA_PHY_RX_IMCAL_STAT, SATA_PHY_RX_CAL);
ret = poll_timeout(base + SATA_PHY_RX_IMCAL_STAT, SATA_PHY_RX_CAL);
if (ret) {
dev_err(phy->dev, "poll timeout SATA_PHY_RX_IMCAL_STAT\n");
return ret;

View File

@ -48,7 +48,7 @@ static int ipq4019_ss_phy_power_on(struct phy *_phy)
return 0;
}
static struct phy_ops ipq4019_usb_ss_phy_ops = {
static const struct phy_ops ipq4019_usb_ss_phy_ops = {
.power_on = ipq4019_ss_phy_power_on,
.power_off = ipq4019_ss_phy_power_off,
};
@ -80,7 +80,7 @@ static int ipq4019_hs_phy_power_on(struct phy *_phy)
return 0;
}
static struct phy_ops ipq4019_usb_hs_phy_ops = {
static const struct phy_ops ipq4019_usb_hs_phy_ops = {
.power_on = ipq4019_hs_phy_power_on,
.power_off = ipq4019_hs_phy_power_off,
};

File diff suppressed because it is too large Load Diff

View File

@ -137,6 +137,9 @@
#define QPHY_V3_DP_COM_RESET_OVRD_CTRL 0x1c
/* Only for QMP V3 PHY - QSERDES COM registers */
#define QSERDES_V3_COM_ATB_SEL1 0x000
#define QSERDES_V3_COM_ATB_SEL2 0x004
#define QSERDES_V3_COM_FREQ_UPDATE 0x008
#define QSERDES_V3_COM_BG_TIMER 0x00c
#define QSERDES_V3_COM_SSC_EN_CENTER 0x010
#define QSERDES_V3_COM_SSC_ADJ_PER1 0x014
@ -146,6 +149,13 @@
#define QSERDES_V3_COM_SSC_STEP_SIZE1 0x024
#define QSERDES_V3_COM_SSC_STEP_SIZE2 0x028
#define QSERDES_V3_COM_BIAS_EN_CLKBUFLR_EN 0x034
# define QSERDES_V3_COM_BIAS_EN 0x0001
# define QSERDES_V3_COM_BIAS_EN_MUX 0x0002
# define QSERDES_V3_COM_CLKBUF_R_EN 0x0004
# define QSERDES_V3_COM_CLKBUF_L_EN 0x0008
# define QSERDES_V3_COM_EN_SYSCLK_TX_SEL 0x0010
# define QSERDES_V3_COM_CLKBUF_RX_DRIVE_L 0x0020
# define QSERDES_V3_COM_CLKBUF_RX_DRIVE_R 0x0040
#define QSERDES_V3_COM_CLK_ENABLE1 0x038
#define QSERDES_V3_COM_SYS_CLK_CTRL 0x03c
#define QSERDES_V3_COM_SYSCLK_BUF_ENABLE 0x040
@ -207,12 +217,36 @@
#define QSERDES_V3_COM_CMN_MODE 0x184
/* Only for QMP V3 PHY - TX registers */
#define QSERDES_V3_TX_BIST_MODE_LANENO 0x000
#define QSERDES_V3_TX_CLKBUF_ENABLE 0x008
#define QSERDES_V3_TX_TX_EMP_POST1_LVL 0x00c
# define DP_PHY_TXn_TX_EMP_POST1_LVL_MASK 0x001f
# define DP_PHY_TXn_TX_EMP_POST1_LVL_MUX_EN 0x0020
#define QSERDES_V3_TX_TX_DRV_LVL 0x01c
# define DP_PHY_TXn_TX_DRV_LVL_MASK 0x001f
# define DP_PHY_TXn_TX_DRV_LVL_MUX_EN 0x0020
#define QSERDES_V3_TX_RESET_TSYNC_EN 0x024
#define QSERDES_V3_TX_PRE_STALL_LDO_BOOST_EN 0x028
#define QSERDES_V3_TX_TX_BAND 0x02c
#define QSERDES_V3_TX_SLEW_CNTL 0x030
#define QSERDES_V3_TX_INTERFACE_SELECT 0x034
#define QSERDES_V3_TX_RES_CODE_LANE_TX 0x03c
#define QSERDES_V3_TX_RES_CODE_LANE_RX 0x040
#define QSERDES_V3_TX_RES_CODE_LANE_OFFSET_TX 0x044
#define QSERDES_V3_TX_RES_CODE_LANE_OFFSET_RX 0x048
#define QSERDES_V3_TX_DEBUG_BUS_SEL 0x058
#define QSERDES_V3_TX_TRANSCEIVER_BIAS_EN 0x05c
#define QSERDES_V3_TX_HIGHZ_DRVR_EN 0x060
#define QSERDES_V3_TX_TX_POL_INV 0x064
#define QSERDES_V3_TX_PARRATE_REC_DETECT_IDLE_EN 0x068
#define QSERDES_V3_TX_LANE_MODE_1 0x08c
#define QSERDES_V3_TX_RCV_DETECT_LVL_2 0x0a4
#define QSERDES_V3_TX_TRAN_DRVR_EMP_EN 0x0c0
#define QSERDES_V3_TX_TX_INTERFACE_MODE 0x0c4
#define QSERDES_V3_TX_VMODE_CTRL1 0x0f0
/* Only for QMP V3 PHY - RX registers */
#define QSERDES_V3_RX_UCDR_FO_GAIN 0x008
@ -315,6 +349,52 @@
#define QPHY_V3_PCS_MISC_OSC_DTCT_MODE2_CONFIG4 0x5c
#define QPHY_V3_PCS_MISC_OSC_DTCT_MODE2_CONFIG5 0x60
/* Only for QMP V3 PHY - DP PHY registers */
#define QSERDES_V3_DP_PHY_REVISION_ID0 0x000
#define QSERDES_V3_DP_PHY_REVISION_ID1 0x004
#define QSERDES_V3_DP_PHY_REVISION_ID2 0x008
#define QSERDES_V3_DP_PHY_REVISION_ID3 0x00c
#define QSERDES_V3_DP_PHY_CFG 0x010
#define QSERDES_V3_DP_PHY_PD_CTL 0x018
# define DP_PHY_PD_CTL_PWRDN 0x001
# define DP_PHY_PD_CTL_PSR_PWRDN 0x002
# define DP_PHY_PD_CTL_AUX_PWRDN 0x004
# define DP_PHY_PD_CTL_LANE_0_1_PWRDN 0x008
# define DP_PHY_PD_CTL_LANE_2_3_PWRDN 0x010
# define DP_PHY_PD_CTL_PLL_PWRDN 0x020
# define DP_PHY_PD_CTL_DP_CLAMP_EN 0x040
#define QSERDES_V3_DP_PHY_MODE 0x01c
#define QSERDES_V3_DP_PHY_AUX_CFG0 0x020
#define QSERDES_V3_DP_PHY_AUX_CFG1 0x024
#define QSERDES_V3_DP_PHY_AUX_CFG2 0x028
#define QSERDES_V3_DP_PHY_AUX_CFG3 0x02c
#define QSERDES_V3_DP_PHY_AUX_CFG4 0x030
#define QSERDES_V3_DP_PHY_AUX_CFG5 0x034
#define QSERDES_V3_DP_PHY_AUX_CFG6 0x038
#define QSERDES_V3_DP_PHY_AUX_CFG7 0x03c
#define QSERDES_V3_DP_PHY_AUX_CFG8 0x040
#define QSERDES_V3_DP_PHY_AUX_CFG9 0x044
#define QSERDES_V3_DP_PHY_AUX_INTERRUPT_MASK 0x048
# define PHY_AUX_STOP_ERR_MASK 0x01
# define PHY_AUX_DEC_ERR_MASK 0x02
# define PHY_AUX_SYNC_ERR_MASK 0x04
# define PHY_AUX_ALIGN_ERR_MASK 0x08
# define PHY_AUX_REQ_ERR_MASK 0x10
#define QSERDES_V3_DP_PHY_AUX_INTERRUPT_CLEAR 0x04c
#define QSERDES_V3_DP_PHY_AUX_BIST_CFG 0x050
#define QSERDES_V3_DP_PHY_VCO_DIV 0x064
#define QSERDES_V3_DP_PHY_TX0_TX1_LANE_CTL 0x06c
#define QSERDES_V3_DP_PHY_TX2_TX3_LANE_CTL 0x088
#define QSERDES_V3_DP_PHY_SPARE0 0x0ac
#define DP_PHY_SPARE0_MASK 0x0f
#define DP_PHY_SPARE0_ORIENTATION_INFO_SHIFT 0x04(0x0004)
#define QSERDES_V3_DP_PHY_STATUS 0x0c0
/* Only for QMP V4 PHY - QSERDES COM registers */
#define QSERDES_V4_COM_SSC_EN_CENTER 0x010
#define QSERDES_V4_COM_SSC_PER1 0x01c

View File

@ -142,7 +142,7 @@ static int ralink_usb_phy_power_off(struct phy *_phy)
return 0;
}
static struct phy_ops ralink_usb_phy_ops = {
static const struct phy_ops ralink_usb_phy_ops = {
.power_on = ralink_usb_phy_power_on,
.power_off = ralink_usb_phy_power_off,
.owner = THIS_MODULE,

View File

@ -9,6 +9,18 @@ config PHY_ROCKCHIP_DP
help
Enable this to support the Rockchip Display Port PHY.
config PHY_ROCKCHIP_DPHY_RX0
tristate "Rockchip MIPI Synopsys DPHY RX0 driver"
depends on ARCH_ROCKCHIP || COMPILE_TEST
select GENERIC_PHY_MIPI_DPHY
select GENERIC_PHY
help
Enable this to support the Rockchip MIPI Synopsys DPHY RX0
associated to the Rockchip ISP module present in RK3399 SoCs.
To compile this driver as a module, choose M here: the module
will be called phy-rockchip-dphy-rx0.
config PHY_ROCKCHIP_EMMC
tristate "Rockchip EMMC PHY Driver"
depends on ARCH_ROCKCHIP && OF

View File

@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_PHY_ROCKCHIP_DP) += phy-rockchip-dp.o
obj-$(CONFIG_PHY_ROCKCHIP_DPHY_RX0) += phy-rockchip-dphy-rx0.o
obj-$(CONFIG_PHY_ROCKCHIP_EMMC) += phy-rockchip-emmc.o
obj-$(CONFIG_PHY_ROCKCHIP_INNO_DSIDPHY) += phy-rockchip-inno-dsidphy.o
obj-$(CONFIG_PHY_ROCKCHIP_INNO_HDMI) += phy-rockchip-inno-hdmi.o

View File

@ -16,6 +16,7 @@
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>

View File

@ -16,6 +16,7 @@
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/iopoll.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
@ -556,41 +557,25 @@ static int exynos5_usbdrd_phy_power_off(struct phy *phy)
static int crport_handshake(struct exynos5_usbdrd_phy *phy_drd,
u32 val, u32 cmd)
{
u32 usec = 100;
unsigned int result;
int err;
writel(val | cmd, phy_drd->reg_phy + EXYNOS5_DRD_PHYREG0);
do {
result = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYREG1);
if (result & PHYREG1_CR_ACK)
break;
udelay(1);
} while (usec-- > 0);
if (!usec) {
dev_err(phy_drd->dev,
"CRPORT handshake timeout1 (0x%08x)\n", val);
return -ETIME;
err = readl_poll_timeout(phy_drd->reg_phy + EXYNOS5_DRD_PHYREG1,
result, (result & PHYREG1_CR_ACK), 1, 100);
if (err == -ETIMEDOUT) {
dev_err(phy_drd->dev, "CRPORT handshake timeout1 (0x%08x)\n", val);
return err;
}
usec = 100;
writel(val, phy_drd->reg_phy + EXYNOS5_DRD_PHYREG0);
do {
result = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYREG1);
if (!(result & PHYREG1_CR_ACK))
break;
udelay(1);
} while (usec-- > 0);
if (!usec) {
dev_err(phy_drd->dev,
"CRPORT handshake timeout2 (0x%08x)\n", val);
return -ETIME;
err = readl_poll_timeout(phy_drd->reg_phy + EXYNOS5_DRD_PHYREG1,
result, !(result & PHYREG1_CR_ACK), 1, 100);
if (err == -ETIMEDOUT) {
dev_err(phy_drd->dev, "CRPORT handshake timeout2 (0x%08x)\n", val);
return err;
}
return 0;

View File

@ -268,7 +268,7 @@ static int samsung_ufs_phy_exit(struct phy *phy)
return 0;
}
static struct phy_ops samsung_ufs_phy_ops = {
static const struct phy_ops samsung_ufs_phy_ops = {
.init = samsung_ufs_phy_init,
.exit = samsung_ufs_phy_exit,
.power_on = samsung_ufs_phy_power_on,

View File

@ -34,3 +34,13 @@ config PHY_UNIPHIER_PCIE
help
Enable this to support PHY implemented in PCIe controller
on UniPhier SoCs. This driver supports LD20 and PXs3 SoCs.
config PHY_UNIPHIER_AHCI
tristate "UniPhier AHCI PHY driver"
depends on ARCH_UNIPHIER || COMPILE_TEST
depends on OF && HAS_IOMEM
default SATA_AHCI_PLATFORM
select GENERIC_PHY
help
Enable this to support PHY implemented in AHCI controller
on UniPhier SoCs. This driver supports PXs2 and PXs3 SoCs.

View File

@ -6,3 +6,4 @@
obj-$(CONFIG_PHY_UNIPHIER_USB2) += phy-uniphier-usb2.o
obj-$(CONFIG_PHY_UNIPHIER_USB3) += phy-uniphier-usb3hs.o phy-uniphier-usb3ss.o
obj-$(CONFIG_PHY_UNIPHIER_PCIE) += phy-uniphier-pcie.o
obj-$(CONFIG_PHY_UNIPHIER_AHCI) += phy-uniphier-ahci.o

View File

@ -0,0 +1,321 @@
// SPDX-License-Identifier: GPL-2.0
/*
* phy-uniphier-ahci.c - PHY driver for UniPhier AHCI controller
* Copyright 2016-2020, Socionext Inc.
* Author: Kunihiko Hayashi <hayashi.kunihiko@socionext.com>
*/
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
struct uniphier_ahciphy_priv {
struct device *dev;
void __iomem *base;
struct clk *clk, *clk_parent;
struct reset_control *rst, *rst_parent;
const struct uniphier_ahciphy_soc_data *data;
};
struct uniphier_ahciphy_soc_data {
int (*init)(struct uniphier_ahciphy_priv *priv);
int (*power_on)(struct uniphier_ahciphy_priv *priv);
int (*power_off)(struct uniphier_ahciphy_priv *priv);
bool is_ready_high;
bool is_phy_clk;
};
/* for PXs2/PXs3 */
#define CKCTRL 0x0
#define CKCTRL_P0_READY BIT(15)
#define CKCTRL_P0_RESET BIT(10)
#define CKCTRL_REF_SSP_EN BIT(9)
#define TXCTRL0 0x4
#define TXCTRL0_AMP_G3_MASK GENMASK(22, 16)
#define TXCTRL0_AMP_G2_MASK GENMASK(14, 8)
#define TXCTRL0_AMP_G1_MASK GENMASK(6, 0)
#define TXCTRL1 0x8
#define TXCTRL1_DEEMPH_G3_MASK GENMASK(21, 16)
#define TXCTRL1_DEEMPH_G2_MASK GENMASK(13, 8)
#define TXCTRL1_DEEMPH_G1_MASK GENMASK(5, 0)
#define RXCTRL 0xc
#define RXCTRL_LOS_LVL_MASK GENMASK(20, 16)
#define RXCTRL_LOS_BIAS_MASK GENMASK(10, 8)
#define RXCTRL_RX_EQ_MASK GENMASK(2, 0)
static void uniphier_ahciphy_pxs2_enable(struct uniphier_ahciphy_priv *priv,
bool enable)
{
u32 val;
val = readl(priv->base + CKCTRL);
if (enable) {
val |= CKCTRL_REF_SSP_EN;
writel(val, priv->base + CKCTRL);
val &= ~CKCTRL_P0_RESET;
writel(val, priv->base + CKCTRL);
} else {
val |= CKCTRL_P0_RESET;
writel(val, priv->base + CKCTRL);
val &= ~CKCTRL_REF_SSP_EN;
writel(val, priv->base + CKCTRL);
}
}
static int uniphier_ahciphy_pxs2_power_on(struct uniphier_ahciphy_priv *priv)
{
int ret;
u32 val;
uniphier_ahciphy_pxs2_enable(priv, true);
/* wait until PLL is ready */
if (priv->data->is_ready_high)
ret = readl_poll_timeout(priv->base + CKCTRL, val,
(val & CKCTRL_P0_READY), 200, 400);
else
ret = readl_poll_timeout(priv->base + CKCTRL, val,
!(val & CKCTRL_P0_READY), 200, 400);
if (ret) {
dev_err(priv->dev, "Failed to check whether PHY PLL is ready\n");
uniphier_ahciphy_pxs2_enable(priv, false);
}
return ret;
}
static int uniphier_ahciphy_pxs2_power_off(struct uniphier_ahciphy_priv *priv)
{
uniphier_ahciphy_pxs2_enable(priv, false);
return 0;
}
static int uniphier_ahciphy_pxs3_init(struct uniphier_ahciphy_priv *priv)
{
int i;
u32 val;
/* setup port parameter */
val = readl(priv->base + TXCTRL0);
val &= ~TXCTRL0_AMP_G3_MASK;
val |= FIELD_PREP(TXCTRL0_AMP_G3_MASK, 0x73);
val &= ~TXCTRL0_AMP_G2_MASK;
val |= FIELD_PREP(TXCTRL0_AMP_G2_MASK, 0x46);
val &= ~TXCTRL0_AMP_G1_MASK;
val |= FIELD_PREP(TXCTRL0_AMP_G1_MASK, 0x42);
writel(val, priv->base + TXCTRL0);
val = readl(priv->base + TXCTRL1);
val &= ~TXCTRL1_DEEMPH_G3_MASK;
val |= FIELD_PREP(TXCTRL1_DEEMPH_G3_MASK, 0x23);
val &= ~TXCTRL1_DEEMPH_G2_MASK;
val |= FIELD_PREP(TXCTRL1_DEEMPH_G2_MASK, 0x05);
val &= ~TXCTRL1_DEEMPH_G1_MASK;
val |= FIELD_PREP(TXCTRL1_DEEMPH_G1_MASK, 0x05);
val = readl(priv->base + RXCTRL);
val &= ~RXCTRL_LOS_LVL_MASK;
val |= FIELD_PREP(RXCTRL_LOS_LVL_MASK, 0x9);
val &= ~RXCTRL_LOS_BIAS_MASK;
val |= FIELD_PREP(RXCTRL_LOS_BIAS_MASK, 0x2);
val &= ~RXCTRL_RX_EQ_MASK;
val |= FIELD_PREP(RXCTRL_RX_EQ_MASK, 0x1);
/* dummy read 25 times to make a wait time for the phy to stabilize */
for (i = 0; i < 25; i++)
readl(priv->base + CKCTRL);
return 0;
}
static int uniphier_ahciphy_init(struct phy *phy)
{
struct uniphier_ahciphy_priv *priv = phy_get_drvdata(phy);
int ret;
ret = clk_prepare_enable(priv->clk_parent);
if (ret)
return ret;
ret = reset_control_deassert(priv->rst_parent);
if (ret)
goto out_clk_disable;
if (priv->data->init) {
ret = priv->data->init(priv);
if (ret)
goto out_rst_assert;
}
return 0;
out_rst_assert:
reset_control_assert(priv->rst_parent);
out_clk_disable:
clk_disable_unprepare(priv->clk_parent);
return ret;
}
static int uniphier_ahciphy_exit(struct phy *phy)
{
struct uniphier_ahciphy_priv *priv = phy_get_drvdata(phy);
reset_control_assert(priv->rst_parent);
clk_disable_unprepare(priv->clk_parent);
return 0;
}
static int uniphier_ahciphy_power_on(struct phy *phy)
{
struct uniphier_ahciphy_priv *priv = phy_get_drvdata(phy);
int ret = 0;
ret = clk_prepare_enable(priv->clk);
if (ret)
return ret;
ret = reset_control_deassert(priv->rst);
if (ret)
goto out_clk_disable;
if (priv->data->power_on) {
ret = priv->data->power_on(priv);
if (ret)
goto out_reset_assert;
}
return 0;
out_reset_assert:
reset_control_assert(priv->rst);
out_clk_disable:
clk_disable_unprepare(priv->clk);
return ret;
}
static int uniphier_ahciphy_power_off(struct phy *phy)
{
struct uniphier_ahciphy_priv *priv = phy_get_drvdata(phy);
int ret = 0;
if (priv->data->power_off)
ret = priv->data->power_off(priv);
reset_control_assert(priv->rst);
clk_disable_unprepare(priv->clk);
return ret;
}
static const struct phy_ops uniphier_ahciphy_ops = {
.init = uniphier_ahciphy_init,
.exit = uniphier_ahciphy_exit,
.power_on = uniphier_ahciphy_power_on,
.power_off = uniphier_ahciphy_power_off,
.owner = THIS_MODULE,
};
static int uniphier_ahciphy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct uniphier_ahciphy_priv *priv;
struct phy *phy;
struct phy_provider *phy_provider;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
priv->data = of_device_get_match_data(dev);
if (WARN_ON(!priv->data))
return -EINVAL;
priv->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
priv->clk_parent = devm_clk_get(dev, "link");
if (IS_ERR(priv->clk_parent))
return PTR_ERR(priv->clk_parent);
if (priv->data->is_phy_clk) {
priv->clk = devm_clk_get(dev, "phy");
if (IS_ERR(priv->clk))
return PTR_ERR(priv->clk);
}
priv->rst_parent = devm_reset_control_get_shared(dev, "link");
if (IS_ERR(priv->rst_parent))
return PTR_ERR(priv->rst_parent);
priv->rst = devm_reset_control_get_shared(dev, "phy");
if (IS_ERR(priv->rst))
return PTR_ERR(priv->rst);
phy = devm_phy_create(dev, dev->of_node, &uniphier_ahciphy_ops);
if (IS_ERR(phy)) {
dev_err(dev, "failed to create phy\n");
return PTR_ERR(phy);
}
phy_set_drvdata(phy, priv);
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
if (IS_ERR(phy_provider))
return PTR_ERR(phy_provider);
return 0;
}
static const struct uniphier_ahciphy_soc_data uniphier_pxs2_data = {
.power_on = uniphier_ahciphy_pxs2_power_on,
.power_off = uniphier_ahciphy_pxs2_power_off,
.is_ready_high = false,
.is_phy_clk = false,
};
static const struct uniphier_ahciphy_soc_data uniphier_pxs3_data = {
.init = uniphier_ahciphy_pxs3_init,
.power_on = uniphier_ahciphy_pxs2_power_on,
.power_off = uniphier_ahciphy_pxs2_power_off,
.is_ready_high = true,
.is_phy_clk = true,
};
static const struct of_device_id uniphier_ahciphy_match[] = {
{
.compatible = "socionext,uniphier-pxs2-ahci-phy",
.data = &uniphier_pxs2_data,
},
{
.compatible = "socionext,uniphier-pxs3-ahci-phy",
.data = &uniphier_pxs3_data,
},
{ /* Sentinel */ },
};
MODULE_DEVICE_TABLE(of, uniphier_ahciphy_match);
static struct platform_driver uniphier_ahciphy_driver = {
.probe = uniphier_ahciphy_probe,
.driver = {
.name = "uniphier-ahci-phy",
.of_match_table = uniphier_ahciphy_match,
},
};
module_platform_driver(uniphier_ahciphy_driver);
MODULE_AUTHOR("Kunihiko Hayashi <hayashi.kunihiko@socionext.com>");
MODULE_DESCRIPTION("UniPhier PHY driver for AHCI controller");
MODULE_LICENSE("GPL v2");

View File

@ -19,15 +19,38 @@
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#define CMU_R004 0x4
#define CMU_R060 0x60
#define CMU_R07C 0x7c
#define CMU_R088 0x88
#define CMU_R0D0 0xd0
#define CMU_R0E8 0xe8
#define LANE_R048 0x248
#define LANE_R058 0x258
#define LANE_R06c 0x26c
#define LANE_R070 0x270
#define LANE_R070 0x270
#define LANE_R19C 0x39c
#define COMLANE_R004 0xa04
#define COMLANE_R138 0xb38
#define VERSION 0x70
#define VERSION_VAL 0x70
#define COMLANE_R190 0xb90
#define COMLANE_R194 0xb94
#define COMRXEQ_R004 0x1404
#define COMRXEQ_R008 0x1408
#define COMRXEQ_R00C 0x140c
#define COMRXEQ_R014 0x1414
#define COMRXEQ_R018 0x1418
#define COMRXEQ_R01C 0x141c
#define COMRXEQ_R04C 0x144c
#define COMRXEQ_R088 0x1488
#define COMRXEQ_R094 0x1494
#define COMRXEQ_R098 0x1498
#define SERDES_CTRL 0x1fd0
#define WIZ_LANEXCTL_STS 0x1fe0
@ -80,27 +103,136 @@ static const struct regmap_config serdes_am654_regmap_config = {
.max_register = 0x1ffc,
};
static const struct reg_field cmu_master_cdn_o = REG_FIELD(CMU_R07C, 24, 24);
static const struct reg_field config_version = REG_FIELD(COMLANE_R138, 16, 23);
static const struct reg_field l1_master_cdn_o = REG_FIELD(COMLANE_R190, 9, 9);
static const struct reg_field cmu_ok_i_0 = REG_FIELD(COMLANE_R194, 19, 19);
static const struct reg_field por_en = REG_FIELD(SERDES_CTRL, 29, 29);
static const struct reg_field tx0_enable = REG_FIELD(WIZ_LANEXCTL_STS, 29, 31);
static const struct reg_field rx0_enable = REG_FIELD(WIZ_LANEXCTL_STS, 13, 15);
static const struct reg_field pll_enable = REG_FIELD(WIZ_PLL_CTRL, 29, 31);
static const struct reg_field pll_ok = REG_FIELD(WIZ_PLL_CTRL, 28, 28);
enum serdes_am654_fields {
/* CMU PLL Control */
CMU_PLL_CTRL,
LANE_PLL_CTRL_RXEQ_RXIDLE,
/* CMU VCO bias current and VREG setting */
AHB_PMA_CM_VCO_VBIAS_VREG,
AHB_PMA_CM_VCO_BIAS_VREG,
AHB_PMA_CM_SR,
AHB_SSC_GEN_Z_O_20_13,
/* AHB PMA Lane Configuration */
AHB_PMA_LN_AGC_THSEL_VREGH,
/* AGC and Signal detect threshold for Gen3 */
AHB_PMA_LN_GEN3_AGC_SD_THSEL,
AHB_PMA_LN_RX_SELR_GEN3,
AHB_PMA_LN_TX_DRV,
/* CMU Master Reset */
CMU_MASTER_CDN,
/* P2S ring buffer initial startup pointer difference */
P2S_RBUF_PTR_DIFF,
CONFIG_VERSION,
/* Lane 1 Master Reset */
L1_MASTER_CDN,
/* CMU OK Status */
CMU_OK_I_0,
/* Mid-speed initial calibration control */
COMRXEQ_MS_INIT_CTRL_7_0,
/* High-speed initial calibration control */
COMRXEQ_HS_INIT_CAL_7_0,
/* Mid-speed recalibration control */
COMRXEQ_MS_RECAL_CTRL_7_0,
/* High-speed recalibration control */
COMRXEQ_HS_RECAL_CTRL_7_0,
/* ATT configuration */
COMRXEQ_CSR_ATT_CONFIG,
/* Edge based boost adaptation window length */
COMRXEQ_CSR_EBSTADAPT_WIN_LEN,
/* COMRXEQ control 3 & 4 */
COMRXEQ_CTRL_3_4,
/* COMRXEQ control 14, 15 and 16*/
COMRXEQ_CTRL_14_15_16,
/* Threshold for errors in pattern data */
COMRXEQ_CSR_DLEV_ERR_THRESH,
/* COMRXEQ control 25 */
COMRXEQ_CTRL_25,
/* Mid-speed rate change calibration control */
CSR_RXEQ_RATE_CHANGE_CAL_RUN_RATE2_O,
/* High-speed rate change calibration control */
COMRXEQ_HS_RCHANGE_CTRL_7_0,
/* Serdes reset */
POR_EN,
/* Tx Enable Value */
TX0_ENABLE,
/* Rx Enable Value */
RX0_ENABLE,
/* PLL Enable Value */
PLL_ENABLE,
/* PLL ready for use */
PLL_OK,
/* sentinel */
MAX_FIELDS
};
static const struct reg_field serdes_am654_reg_fields[] = {
[CMU_PLL_CTRL] = REG_FIELD(CMU_R004, 8, 15),
[AHB_PMA_CM_VCO_VBIAS_VREG] = REG_FIELD(CMU_R060, 8, 15),
[CMU_MASTER_CDN] = REG_FIELD(CMU_R07C, 24, 31),
[AHB_PMA_CM_VCO_BIAS_VREG] = REG_FIELD(CMU_R088, 24, 31),
[AHB_PMA_CM_SR] = REG_FIELD(CMU_R0D0, 24, 31),
[AHB_SSC_GEN_Z_O_20_13] = REG_FIELD(CMU_R0E8, 8, 15),
[LANE_PLL_CTRL_RXEQ_RXIDLE] = REG_FIELD(LANE_R048, 8, 15),
[AHB_PMA_LN_AGC_THSEL_VREGH] = REG_FIELD(LANE_R058, 16, 23),
[AHB_PMA_LN_GEN3_AGC_SD_THSEL] = REG_FIELD(LANE_R06c, 0, 7),
[AHB_PMA_LN_RX_SELR_GEN3] = REG_FIELD(LANE_R070, 16, 23),
[AHB_PMA_LN_TX_DRV] = REG_FIELD(LANE_R19C, 16, 23),
[P2S_RBUF_PTR_DIFF] = REG_FIELD(COMLANE_R004, 0, 7),
[CONFIG_VERSION] = REG_FIELD(COMLANE_R138, 16, 23),
[L1_MASTER_CDN] = REG_FIELD(COMLANE_R190, 8, 15),
[CMU_OK_I_0] = REG_FIELD(COMLANE_R194, 19, 19),
[COMRXEQ_MS_INIT_CTRL_7_0] = REG_FIELD(COMRXEQ_R004, 24, 31),
[COMRXEQ_HS_INIT_CAL_7_0] = REG_FIELD(COMRXEQ_R008, 0, 7),
[COMRXEQ_MS_RECAL_CTRL_7_0] = REG_FIELD(COMRXEQ_R00C, 8, 15),
[COMRXEQ_HS_RECAL_CTRL_7_0] = REG_FIELD(COMRXEQ_R00C, 16, 23),
[COMRXEQ_CSR_ATT_CONFIG] = REG_FIELD(COMRXEQ_R014, 16, 23),
[COMRXEQ_CSR_EBSTADAPT_WIN_LEN] = REG_FIELD(COMRXEQ_R018, 16, 23),
[COMRXEQ_CTRL_3_4] = REG_FIELD(COMRXEQ_R01C, 8, 15),
[COMRXEQ_CTRL_14_15_16] = REG_FIELD(COMRXEQ_R04C, 0, 7),
[COMRXEQ_CSR_DLEV_ERR_THRESH] = REG_FIELD(COMRXEQ_R088, 16, 23),
[COMRXEQ_CTRL_25] = REG_FIELD(COMRXEQ_R094, 24, 31),
[CSR_RXEQ_RATE_CHANGE_CAL_RUN_RATE2_O] = REG_FIELD(COMRXEQ_R098, 8, 15),
[COMRXEQ_HS_RCHANGE_CTRL_7_0] = REG_FIELD(COMRXEQ_R098, 16, 23),
[POR_EN] = REG_FIELD(SERDES_CTRL, 29, 29),
[TX0_ENABLE] = REG_FIELD(WIZ_LANEXCTL_STS, 29, 31),
[RX0_ENABLE] = REG_FIELD(WIZ_LANEXCTL_STS, 13, 15),
[PLL_ENABLE] = REG_FIELD(WIZ_PLL_CTRL, 29, 31),
[PLL_OK] = REG_FIELD(WIZ_PLL_CTRL, 28, 28),
};
struct serdes_am654 {
struct regmap *regmap;
struct regmap_field *cmu_master_cdn_o;
struct regmap_field *config_version;
struct regmap_field *l1_master_cdn_o;
struct regmap_field *cmu_ok_i_0;
struct regmap_field *por_en;
struct regmap_field *tx0_enable;
struct regmap_field *rx0_enable;
struct regmap_field *pll_enable;
struct regmap_field *pll_ok;
struct regmap_field *fields[MAX_FIELDS];
struct device *dev;
struct mux_control *control;
@ -116,12 +248,12 @@ static int serdes_am654_enable_pll(struct serdes_am654 *phy)
int ret;
u32 val;
ret = regmap_field_write(phy->pll_enable, PLL_ENABLE_STATE);
ret = regmap_field_write(phy->fields[PLL_ENABLE], PLL_ENABLE_STATE);
if (ret)
return ret;
return regmap_field_read_poll_timeout(phy->pll_ok, val, val, 1000,
PLL_LOCK_TIME);
return regmap_field_read_poll_timeout(phy->fields[PLL_OK], val, val,
1000, PLL_LOCK_TIME);
}
static void serdes_am654_disable_pll(struct serdes_am654 *phy)
@ -129,41 +261,39 @@ static void serdes_am654_disable_pll(struct serdes_am654 *phy)
struct device *dev = phy->dev;
int ret;
ret = regmap_field_write(phy->pll_enable, PLL_DISABLE_STATE);
ret = regmap_field_write(phy->fields[PLL_ENABLE], PLL_DISABLE_STATE);
if (ret)
dev_err(dev, "Failed to disable PLL\n");
}
static int serdes_am654_enable_txrx(struct serdes_am654 *phy)
{
int ret;
int ret = 0;
/* Enable TX */
ret = regmap_field_write(phy->tx0_enable, TX0_ENABLE_STATE);
if (ret)
return ret;
ret |= regmap_field_write(phy->fields[TX0_ENABLE], TX0_ENABLE_STATE);
/* Enable RX */
ret = regmap_field_write(phy->rx0_enable, RX0_ENABLE_STATE);
ret |= regmap_field_write(phy->fields[RX0_ENABLE], RX0_ENABLE_STATE);
if (ret)
return ret;
return -EIO;
return 0;
}
static int serdes_am654_disable_txrx(struct serdes_am654 *phy)
{
int ret;
int ret = 0;
/* Disable TX */
ret = regmap_field_write(phy->tx0_enable, TX0_DISABLE_STATE);
if (ret)
return ret;
ret |= regmap_field_write(phy->fields[TX0_ENABLE], TX0_DISABLE_STATE);
/* Disable RX */
ret = regmap_field_write(phy->rx0_enable, RX0_DISABLE_STATE);
ret |= regmap_field_write(phy->fields[RX0_ENABLE], RX0_DISABLE_STATE);
if (ret)
return ret;
return -EIO;
return 0;
}
@ -187,8 +317,8 @@ static int serdes_am654_power_on(struct phy *x)
return ret;
}
return regmap_field_read_poll_timeout(phy->cmu_ok_i_0, val, val,
SLEEP_TIME, PLL_LOCK_TIME);
return regmap_field_read_poll_timeout(phy->fields[CMU_OK_I_0], val,
val, SLEEP_TIME, PLL_LOCK_TIME);
}
static int serdes_am654_power_off(struct phy *x)
@ -286,19 +416,37 @@ static int serdes_am654_usb3_init(struct serdes_am654 *phy)
static int serdes_am654_pcie_init(struct serdes_am654 *phy)
{
int ret;
int ret = 0;
ret = regmap_field_write(phy->config_version, VERSION);
if (ret)
return ret;
ret |= regmap_field_write(phy->fields[CMU_PLL_CTRL], 0x2);
ret |= regmap_field_write(phy->fields[AHB_PMA_CM_VCO_VBIAS_VREG], 0x98);
ret |= regmap_field_write(phy->fields[AHB_PMA_CM_VCO_BIAS_VREG], 0x98);
ret |= regmap_field_write(phy->fields[AHB_PMA_CM_SR], 0x45);
ret |= regmap_field_write(phy->fields[AHB_SSC_GEN_Z_O_20_13], 0xe);
ret |= regmap_field_write(phy->fields[LANE_PLL_CTRL_RXEQ_RXIDLE], 0x5);
ret |= regmap_field_write(phy->fields[AHB_PMA_LN_AGC_THSEL_VREGH], 0x83);
ret |= regmap_field_write(phy->fields[AHB_PMA_LN_GEN3_AGC_SD_THSEL], 0x83);
ret |= regmap_field_write(phy->fields[AHB_PMA_LN_RX_SELR_GEN3], 0x81);
ret |= regmap_field_write(phy->fields[AHB_PMA_LN_TX_DRV], 0x3b);
ret |= regmap_field_write(phy->fields[P2S_RBUF_PTR_DIFF], 0x3);
ret |= regmap_field_write(phy->fields[CONFIG_VERSION], VERSION_VAL);
ret |= regmap_field_write(phy->fields[COMRXEQ_MS_INIT_CTRL_7_0], 0xf);
ret |= regmap_field_write(phy->fields[COMRXEQ_HS_INIT_CAL_7_0], 0x4f);
ret |= regmap_field_write(phy->fields[COMRXEQ_MS_RECAL_CTRL_7_0], 0xf);
ret |= regmap_field_write(phy->fields[COMRXEQ_HS_RECAL_CTRL_7_0], 0x4f);
ret |= regmap_field_write(phy->fields[COMRXEQ_CSR_ATT_CONFIG], 0x7);
ret |= regmap_field_write(phy->fields[COMRXEQ_CSR_EBSTADAPT_WIN_LEN], 0x7f);
ret |= regmap_field_write(phy->fields[COMRXEQ_CTRL_3_4], 0xf);
ret |= regmap_field_write(phy->fields[COMRXEQ_CTRL_14_15_16], 0x9a);
ret |= regmap_field_write(phy->fields[COMRXEQ_CSR_DLEV_ERR_THRESH], 0x32);
ret |= regmap_field_write(phy->fields[COMRXEQ_CTRL_25], 0x80);
ret |= regmap_field_write(phy->fields[CSR_RXEQ_RATE_CHANGE_CAL_RUN_RATE2_O], 0xf);
ret |= regmap_field_write(phy->fields[COMRXEQ_HS_RCHANGE_CTRL_7_0], 0x4f);
ret |= regmap_field_write(phy->fields[CMU_MASTER_CDN], 0x1);
ret |= regmap_field_write(phy->fields[L1_MASTER_CDN], 0x2);
ret = regmap_field_write(phy->cmu_master_cdn_o, 0x1);
if (ret)
return ret;
ret = regmap_field_write(phy->l1_master_cdn_o, 0x1);
if (ret)
return ret;
return -EIO;
return 0;
}
@ -320,20 +468,19 @@ static int serdes_am654_init(struct phy *x)
static int serdes_am654_reset(struct phy *x)
{
struct serdes_am654 *phy = phy_get_drvdata(x);
int ret;
int ret = 0;
serdes_am654_disable_pll(phy);
serdes_am654_disable_txrx(phy);
ret = regmap_field_write(phy->por_en, 0x1);
if (ret)
return ret;
ret |= regmap_field_write(phy->fields[POR_EN], 0x1);
mdelay(1);
ret = regmap_field_write(phy->por_en, 0x0);
ret |= regmap_field_write(phy->fields[POR_EN], 0x0);
if (ret)
return ret;
return -EIO;
return 0;
}
@ -587,66 +734,16 @@ static int serdes_am654_regfield_init(struct serdes_am654 *am654_phy)
{
struct regmap *regmap = am654_phy->regmap;
struct device *dev = am654_phy->dev;
int i;
am654_phy->cmu_master_cdn_o = devm_regmap_field_alloc(dev, regmap,
cmu_master_cdn_o);
if (IS_ERR(am654_phy->cmu_master_cdn_o)) {
dev_err(dev, "CMU_MASTER_CDN_O reg field init failed\n");
return PTR_ERR(am654_phy->cmu_master_cdn_o);
}
am654_phy->config_version = devm_regmap_field_alloc(dev, regmap,
config_version);
if (IS_ERR(am654_phy->config_version)) {
dev_err(dev, "CONFIG_VERSION reg field init failed\n");
return PTR_ERR(am654_phy->config_version);
}
am654_phy->l1_master_cdn_o = devm_regmap_field_alloc(dev, regmap,
l1_master_cdn_o);
if (IS_ERR(am654_phy->l1_master_cdn_o)) {
dev_err(dev, "L1_MASTER_CDN_O reg field init failed\n");
return PTR_ERR(am654_phy->l1_master_cdn_o);
}
am654_phy->cmu_ok_i_0 = devm_regmap_field_alloc(dev, regmap,
cmu_ok_i_0);
if (IS_ERR(am654_phy->cmu_ok_i_0)) {
dev_err(dev, "CMU_OK_I_0 reg field init failed\n");
return PTR_ERR(am654_phy->cmu_ok_i_0);
}
am654_phy->por_en = devm_regmap_field_alloc(dev, regmap, por_en);
if (IS_ERR(am654_phy->por_en)) {
dev_err(dev, "POR_EN reg field init failed\n");
return PTR_ERR(am654_phy->por_en);
}
am654_phy->tx0_enable = devm_regmap_field_alloc(dev, regmap,
tx0_enable);
if (IS_ERR(am654_phy->tx0_enable)) {
dev_err(dev, "TX0_ENABLE reg field init failed\n");
return PTR_ERR(am654_phy->tx0_enable);
}
am654_phy->rx0_enable = devm_regmap_field_alloc(dev, regmap,
rx0_enable);
if (IS_ERR(am654_phy->rx0_enable)) {
dev_err(dev, "RX0_ENABLE reg field init failed\n");
return PTR_ERR(am654_phy->rx0_enable);
}
am654_phy->pll_enable = devm_regmap_field_alloc(dev, regmap,
pll_enable);
if (IS_ERR(am654_phy->pll_enable)) {
dev_err(dev, "PLL_ENABLE reg field init failed\n");
return PTR_ERR(am654_phy->pll_enable);
}
am654_phy->pll_ok = devm_regmap_field_alloc(dev, regmap, pll_ok);
if (IS_ERR(am654_phy->pll_ok)) {
dev_err(dev, "PLL_OK reg field init failed\n");
return PTR_ERR(am654_phy->pll_ok);
for (i = 0; i < MAX_FIELDS; i++) {
am654_phy->fields[i] = devm_regmap_field_alloc(dev,
regmap,
serdes_am654_reg_fields[i]);
if (IS_ERR(am654_phy->fields[i])) {
dev_err(dev, "Unable to allocate regmap field %d\n", i);
return PTR_ERR(am654_phy->fields[i]);
}
}
return 0;

View File

@ -11,6 +11,7 @@
#include <linux/module.h>
#include <linux/mfd/syscon.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_net.h>
#include <linux/phy.h>
#include <linux/phy/phy.h>
@ -22,7 +23,7 @@
#define AM33XX_GMII_SEL_MODE_RGMII 2
enum {
PHY_GMII_SEL_PORT_MODE,
PHY_GMII_SEL_PORT_MODE = 0,
PHY_GMII_SEL_RGMII_ID_MODE,
PHY_GMII_SEL_RMII_IO_CLK_EN,
PHY_GMII_SEL_LAST,
@ -41,6 +42,7 @@ struct phy_gmii_sel_soc_data {
u32 num_ports;
u32 features;
const struct reg_field (*regfields)[PHY_GMII_SEL_LAST];
bool use_of_data;
};
struct phy_gmii_sel_priv {
@ -49,6 +51,8 @@ struct phy_gmii_sel_priv {
struct regmap *regmap;
struct phy_provider *phy_provider;
struct phy_gmii_sel_phy_priv *if_phys;
u32 num_ports;
u32 reg_offset;
};
static int phy_gmii_sel_mode(struct phy *phy, enum phy_mode mode, int submode)
@ -147,13 +151,9 @@ static const
struct reg_field phy_gmii_sel_fields_dra7[][PHY_GMII_SEL_LAST] = {
{
[PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x554, 0, 1),
[PHY_GMII_SEL_RGMII_ID_MODE] = REG_FIELD((~0), 0, 0),
[PHY_GMII_SEL_RMII_IO_CLK_EN] = REG_FIELD((~0), 0, 0),
},
{
[PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x554, 4, 5),
[PHY_GMII_SEL_RGMII_ID_MODE] = REG_FIELD((~0), 0, 0),
[PHY_GMII_SEL_RMII_IO_CLK_EN] = REG_FIELD((~0), 0, 0),
},
};
@ -172,16 +172,19 @@ struct phy_gmii_sel_soc_data phy_gmii_sel_soc_dm814 = {
static const
struct reg_field phy_gmii_sel_fields_am654[][PHY_GMII_SEL_LAST] = {
{
[PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x4040, 0, 1),
[PHY_GMII_SEL_RGMII_ID_MODE] = REG_FIELD((~0), 0, 0),
[PHY_GMII_SEL_RMII_IO_CLK_EN] = REG_FIELD((~0), 0, 0),
},
{ [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x0, 0, 2), },
{ [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x4, 0, 2), },
{ [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x8, 0, 2), },
{ [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0xC, 0, 2), },
{ [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x10, 0, 2), },
{ [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x14, 0, 2), },
{ [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x18, 0, 2), },
{ [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x1C, 0, 2), },
};
static const
struct phy_gmii_sel_soc_data phy_gmii_sel_soc_am654 = {
.num_ports = 1,
.use_of_data = true,
.regfields = phy_gmii_sel_fields_am654,
};
@ -228,7 +231,7 @@ static struct phy *phy_gmii_sel_of_xlate(struct device *dev,
if (priv->soc_data->features & BIT(PHY_GMII_SEL_RMII_IO_CLK_EN) &&
args->args_count < 2)
return ERR_PTR(-EINVAL);
if (phy_id > priv->soc_data->num_ports)
if (phy_id > priv->num_ports)
return ERR_PTR(-EINVAL);
if (phy_id != priv->if_phys[phy_id - 1].id)
return ERR_PTR(-EINVAL);
@ -242,68 +245,97 @@ static struct phy *phy_gmii_sel_of_xlate(struct device *dev,
return priv->if_phys[phy_id].if_phy;
}
static int phy_gmii_sel_init_ports(struct phy_gmii_sel_priv *priv)
static int phy_gmii_init_phy(struct phy_gmii_sel_priv *priv, int port,
struct phy_gmii_sel_phy_priv *if_phy)
{
const struct phy_gmii_sel_soc_data *soc_data = priv->soc_data;
struct device *dev = priv->dev;
const struct reg_field *fields;
struct regmap_field *regfield;
struct reg_field field;
int ret;
if_phy->id = port;
if_phy->priv = priv;
fields = soc_data->regfields[port - 1];
field = *fields++;
field.reg += priv->reg_offset;
dev_dbg(dev, "%s field %x %d %d\n", __func__,
field.reg, field.msb, field.lsb);
regfield = devm_regmap_field_alloc(dev, priv->regmap, field);
if (IS_ERR(regfield))
return PTR_ERR(regfield);
if_phy->fields[PHY_GMII_SEL_PORT_MODE] = regfield;
field = *fields++;
field.reg += priv->reg_offset;
if (soc_data->features & BIT(PHY_GMII_SEL_RGMII_ID_MODE)) {
regfield = devm_regmap_field_alloc(dev,
priv->regmap,
field);
if (IS_ERR(regfield))
return PTR_ERR(regfield);
if_phy->fields[PHY_GMII_SEL_RGMII_ID_MODE] = regfield;
dev_dbg(dev, "%s field %x %d %d\n", __func__,
field.reg, field.msb, field.lsb);
}
field = *fields;
field.reg += priv->reg_offset;
if (soc_data->features & BIT(PHY_GMII_SEL_RMII_IO_CLK_EN)) {
regfield = devm_regmap_field_alloc(dev,
priv->regmap,
field);
if (IS_ERR(regfield))
return PTR_ERR(regfield);
if_phy->fields[PHY_GMII_SEL_RMII_IO_CLK_EN] = regfield;
dev_dbg(dev, "%s field %x %d %d\n", __func__,
field.reg, field.msb, field.lsb);
}
if_phy->if_phy = devm_phy_create(dev,
priv->dev->of_node,
&phy_gmii_sel_ops);
if (IS_ERR(if_phy->if_phy)) {
ret = PTR_ERR(if_phy->if_phy);
dev_err(dev, "Failed to create phy%d %d\n", port, ret);
return ret;
}
phy_set_drvdata(if_phy->if_phy, if_phy);
return 0;
}
static int phy_gmii_sel_init_ports(struct phy_gmii_sel_priv *priv)
{
const struct phy_gmii_sel_soc_data *soc_data = priv->soc_data;
struct phy_gmii_sel_phy_priv *if_phys;
int i, num_ports, ret;
struct device *dev = priv->dev;
int i, ret;
num_ports = priv->soc_data->num_ports;
if (soc_data->use_of_data) {
const __be32 *offset;
u64 size;
if_phys = devm_kcalloc(priv->dev, num_ports,
offset = of_get_address(dev->of_node, 0, &size, NULL);
priv->num_ports = size / sizeof(u32);
if (!priv->num_ports)
return -EINVAL;
priv->reg_offset = __be32_to_cpu(*offset);
}
if_phys = devm_kcalloc(dev, priv->num_ports,
sizeof(*if_phys), GFP_KERNEL);
if (!if_phys)
return -ENOMEM;
dev_dbg(dev, "%s %d\n", __func__, num_ports);
dev_dbg(dev, "%s %d\n", __func__, priv->num_ports);
for (i = 0; i < num_ports; i++) {
const struct reg_field *field;
struct regmap_field *regfield;
if_phys[i].id = i + 1;
if_phys[i].priv = priv;
field = &soc_data->regfields[i][PHY_GMII_SEL_PORT_MODE];
dev_dbg(dev, "%s field %x %d %d\n", __func__,
field->reg, field->msb, field->lsb);
regfield = devm_regmap_field_alloc(dev, priv->regmap, *field);
if (IS_ERR(regfield))
return PTR_ERR(regfield);
if_phys[i].fields[PHY_GMII_SEL_PORT_MODE] = regfield;
field = &soc_data->regfields[i][PHY_GMII_SEL_RGMII_ID_MODE];
if (field->reg != (~0)) {
regfield = devm_regmap_field_alloc(dev,
priv->regmap,
*field);
if (IS_ERR(regfield))
return PTR_ERR(regfield);
if_phys[i].fields[PHY_GMII_SEL_RGMII_ID_MODE] =
regfield;
}
field = &soc_data->regfields[i][PHY_GMII_SEL_RMII_IO_CLK_EN];
if (field->reg != (~0)) {
regfield = devm_regmap_field_alloc(dev,
priv->regmap,
*field);
if (IS_ERR(regfield))
return PTR_ERR(regfield);
if_phys[i].fields[PHY_GMII_SEL_RMII_IO_CLK_EN] =
regfield;
}
if_phys[i].if_phy = devm_phy_create(dev,
priv->dev->of_node,
&phy_gmii_sel_ops);
if (IS_ERR(if_phys[i].if_phy)) {
ret = PTR_ERR(if_phys[i].if_phy);
dev_err(dev, "Failed to create phy%d %d\n", i, ret);
for (i = 0; i < priv->num_ports; i++) {
ret = phy_gmii_init_phy(priv, i + 1, &if_phys[i]);
if (ret)
return ret;
}
phy_set_drvdata(if_phys[i].if_phy, &if_phys[i]);
}
priv->if_phys = if_phys;
@ -328,6 +360,7 @@ static int phy_gmii_sel_probe(struct platform_device *pdev)
priv->dev = &pdev->dev;
priv->soc_data = of_id->data;
priv->num_ports = priv->soc_data->num_ports;
priv->regmap = syscon_node_to_regmap(node->parent);
if (IS_ERR(priv->regmap)) {

View File

@ -20,7 +20,6 @@
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/reset-controller.h>
#include <dt-bindings/phy/phy.h>
#define WIZ_SERDES_CTRL 0x404
#define WIZ_SERDES_TOP_CTRL 0x408

View File

@ -6,23 +6,23 @@
* Author: Kishon Vijay Abraham I <kishon@ti.com>
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/phy/omap_usb.h>
#include <linux/usb/phy_companion.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/pm_runtime.h>
#include <linux/delay.h>
#include <linux/phy/omap_control_phy.h>
#include <linux/phy/phy.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/regmap.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/phy/omap_control_phy.h>
#include <linux/phy/omap_usb.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/sys_soc.h>
#include <linux/usb/phy_companion.h>
#define USB2PHY_ANA_CONFIG1 0x4c
#define USB2PHY_DISCON_BYP_LATCH BIT(31)
@ -89,7 +89,7 @@ static inline void omap_usb_writel(void __iomem *addr, unsigned int offset,
}
/**
* omap_usb2_set_comparator - links the comparator present in the sytem with
* omap_usb2_set_comparator - links the comparator present in the system with
* this phy
* @comparator - the companion phy(comparator) for this phy
*
@ -142,7 +142,7 @@ static int omap_usb_set_host(struct usb_otg *otg, struct usb_bus *host)
}
static int omap_usb_set_peripheral(struct usb_otg *otg,
struct usb_gadget *gadget)
struct usb_gadget *gadget)
{
otg->gadget = gadget;
if (!gadget)
@ -409,7 +409,7 @@ static int omap_usb2_probe(struct platform_device *pdev)
return PTR_ERR(phy->phy_base);
phy->syscon_phy_power = syscon_regmap_lookup_by_phandle(node,
"syscon-phy-power");
"syscon-phy-power");
if (IS_ERR(phy->syscon_phy_power)) {
dev_dbg(&pdev->dev,
"can't get syscon-phy-power, using control device\n");
@ -438,7 +438,6 @@ static int omap_usb2_probe(struct platform_device *pdev)
}
}
phy->wkupclk = devm_clk_get(phy->dev, "wkupclk");
if (IS_ERR(phy->wkupclk)) {
if (PTR_ERR(phy->wkupclk) == -EPROBE_DEFER)
@ -452,10 +451,10 @@ static int omap_usb2_probe(struct platform_device *pdev)
if (PTR_ERR(phy->wkupclk) != -EPROBE_DEFER)
dev_err(&pdev->dev, "unable to get usb_phy_cm_clk32k\n");
return PTR_ERR(phy->wkupclk);
} else {
dev_warn(&pdev->dev,
"found usb_phy_cm_clk32k, please fix DTS\n");
}
dev_warn(&pdev->dev,
"found usb_phy_cm_clk32k, please fix DTS\n");
}
phy->optclk = devm_clk_get(phy->dev, "refclk");
@ -504,7 +503,6 @@ static int omap_usb2_probe(struct platform_device *pdev)
return PTR_ERR(phy_provider);
}
usb_add_phy_dev(&phy->phy);
return 0;

View File

@ -140,6 +140,17 @@ config RESET_QCOM_PDC
to control reset signals provided by PDC for Modem, Compute,
Display, GPU, Debug, AOP, Sensors, Audio, SP and APPS.
config RESET_RASPBERRYPI
tristate "Raspberry Pi 4 Firmware Reset Driver"
depends on RASPBERRYPI_FIRMWARE || (RASPBERRYPI_FIRMWARE=n && COMPILE_TEST)
default USB_XHCI_PCI
help
Raspberry Pi 4's co-processor controls some of the board's HW
initialization process, but it's up to Linux to trigger it when
relevant. This driver provides a reset controller capable of
interfacing with RPi4's co-processor and model these firmware
initialization routines as reset lines.
config RESET_SCMI
tristate "Reset driver controlled via ARM SCMI interface"
depends on ARM_SCMI_PROTOCOL || COMPILE_TEST

View File

@ -21,6 +21,7 @@ obj-$(CONFIG_RESET_OXNAS) += reset-oxnas.o
obj-$(CONFIG_RESET_PISTACHIO) += reset-pistachio.o
obj-$(CONFIG_RESET_QCOM_AOSS) += reset-qcom-aoss.o
obj-$(CONFIG_RESET_QCOM_PDC) += reset-qcom-pdc.o
obj-$(CONFIG_RESET_RASPBERRYPI) += reset-raspberrypi.o
obj-$(CONFIG_RESET_SCMI) += reset-scmi.o
obj-$(CONFIG_RESET_SIMPLE) += reset-simple.o
obj-$(CONFIG_RESET_STM32MP157) += reset-stm32mp1.o

View File

@ -0,0 +1,122 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Raspberry Pi 4 firmware reset driver
*
* Copyright (C) 2020 Nicolas Saenz Julienne <nsaenzjulienne@suse.de>
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/reset-controller.h>
#include <soc/bcm2835/raspberrypi-firmware.h>
#include <dt-bindings/reset/raspberrypi,firmware-reset.h>
struct rpi_reset {
struct reset_controller_dev rcdev;
struct rpi_firmware *fw;
};
static inline struct rpi_reset *to_rpi(struct reset_controller_dev *rcdev)
{
return container_of(rcdev, struct rpi_reset, rcdev);
}
static int rpi_reset_reset(struct reset_controller_dev *rcdev, unsigned long id)
{
struct rpi_reset *priv = to_rpi(rcdev);
u32 dev_addr;
int ret;
switch (id) {
case RASPBERRYPI_FIRMWARE_RESET_ID_USB:
/*
* The Raspberry Pi 4 gets its USB functionality from VL805, a
* PCIe chip that implements xHCI. After a PCI reset, VL805's
* firmware may either be loaded directly from an EEPROM or, if
* not present, by the SoC's co-processor, VideoCore. rpi's
* VideoCore OS contains both the non public firmware load
* logic and the VL805 firmware blob. This triggers the
* aforementioned process.
*
* The pci device address is expected is expected by the
* firmware encoded like this:
*
* PCI_BUS << 20 | PCI_SLOT << 15 | PCI_FUNC << 12
*
* But since rpi's PCIe is hardwired, we know the address in
* advance.
*/
dev_addr = 0x100000;
ret = rpi_firmware_property(priv->fw, RPI_FIRMWARE_NOTIFY_XHCI_RESET,
&dev_addr, sizeof(dev_addr));
if (ret)
return ret;
/* Wait for vl805 to startup */
usleep_range(200, 1000);
break;
default:
return -EINVAL;
}
return 0;
}
static const struct reset_control_ops rpi_reset_ops = {
.reset = rpi_reset_reset,
};
static int rpi_reset_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct rpi_firmware *fw;
struct device_node *np;
struct rpi_reset *priv;
np = of_get_parent(dev->of_node);
if (!np) {
dev_err(dev, "Missing firmware node\n");
return -ENOENT;
}
fw = rpi_firmware_get(np);
of_node_put(np);
if (!fw)
return -EPROBE_DEFER;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
dev_set_drvdata(dev, priv);
priv->fw = fw;
priv->rcdev.owner = THIS_MODULE;
priv->rcdev.nr_resets = RASPBERRYPI_FIRMWARE_RESET_NUM_IDS;
priv->rcdev.ops = &rpi_reset_ops;
priv->rcdev.of_node = dev->of_node;
return devm_reset_controller_register(dev, &priv->rcdev);
}
static const struct of_device_id rpi_reset_of_match[] = {
{ .compatible = "raspberrypi,firmware-reset" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rpi_reset_of_match);
static struct platform_driver rpi_reset_driver = {
.probe = rpi_reset_probe,
.driver = {
.name = "raspberrypi-reset",
.of_match_table = rpi_reset_of_match,
},
};
module_platform_driver(rpi_reset_driver);
MODULE_AUTHOR("Nicolas Saenz Julienne <nsaenzjulienne@suse.de>");
MODULE_DESCRIPTION("Raspberry Pi 4 firmware reset driver");
MODULE_LICENSE("GPL");

View File

@ -44,8 +44,6 @@ source "drivers/staging/media/tegra-video/Kconfig"
source "drivers/staging/media/ipu3/Kconfig"
source "drivers/staging/media/phy-rockchip-dphy-rx0/Kconfig"
source "drivers/staging/media/rkisp1/Kconfig"
endif

View File

@ -10,6 +10,5 @@ obj-$(CONFIG_VIDEO_TEGRA) += tegra-video/
obj-$(CONFIG_TEGRA_VDE) += tegra-vde/
obj-$(CONFIG_VIDEO_HANTRO) += hantro/
obj-$(CONFIG_VIDEO_IPU3_IMGU) += ipu3/
obj-$(CONFIG_PHY_ROCKCHIP_DPHY_RX0) += phy-rockchip-dphy-rx0/
obj-$(CONFIG_VIDEO_ROCKCHIP_ISP1) += rkisp1/
obj-$(CONFIG_VIDEO_ZORAN) += zoran/

View File

@ -1,13 +0,0 @@
# SPDX-License-Identifier: GPL-2.0-only
config PHY_ROCKCHIP_DPHY_RX0
tristate "Rockchip MIPI Synopsys DPHY RX0 driver"
depends on ARCH_ROCKCHIP || COMPILE_TEST
select GENERIC_PHY_MIPI_DPHY
select GENERIC_PHY
help
Enable this to support the Rockchip MIPI Synopsys DPHY RX0
associated to the Rockchip ISP module present in RK3399 SoCs.
To compile this driver as a module, choose M here: the module
will be called phy-rockchip-dphy-rx0.

View File

@ -1,2 +0,0 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_PHY_ROCKCHIP_DPHY_RX0) += phy-rockchip-dphy-rx0.o

View File

@ -1,6 +0,0 @@
The main reason for keeping this in staging is because the only driver
that uses this is rkisp1, which is also in staging. It should be moved together
with rkisp1.
Please CC patches to Linux Media <linux-media@vger.kernel.org> and
Helen Koike <helen.koike@collabora.com>.

View File

@ -16,7 +16,19 @@ menuconfig USB4
To compile this driver a module, choose M here. The module will be
called thunderbolt.
if USB4
config USB4_DEBUGFS_WRITE
bool "Enable write by debugfs to configuration spaces (DANGEROUS)"
help
Enables writing to device configuration registers through
debugfs interface.
Only enable this if you know what you are doing! Never enable
this for production systems or distro kernels.
config USB4_KUNIT_TEST
bool "KUnit tests"
depends on KUNIT=y
depends on USB4=y
endif # USB4

View File

@ -4,4 +4,6 @@ thunderbolt-objs := nhi.o nhi_ops.o ctl.o tb.o switch.o cap.o path.o tunnel.o ee
thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o lc.o tmu.o usb4.o
thunderbolt-objs += nvm.o retimer.o quirks.o
obj-${CONFIG_USB4_KUNIT_TEST} += test.o
thunderbolt-${CONFIG_ACPI} += acpi.o
thunderbolt-$(CONFIG_DEBUG_FS) += debugfs.o
thunderbolt-${CONFIG_USB4_KUNIT_TEST} += test.o

117
drivers/thunderbolt/acpi.c Normal file
View File

@ -0,0 +1,117 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ACPI support
*
* Copyright (C) 2020, Intel Corporation
* Author: Mika Westerberg <mika.westerberg@linux.intel.com>
*/
#include <linux/acpi.h>
#include "tb.h"
static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data,
void **return_value)
{
struct fwnode_reference_args args;
struct fwnode_handle *fwnode;
struct tb_nhi *nhi = data;
struct acpi_device *adev;
struct pci_dev *pdev;
struct device *dev;
int ret;
if (acpi_bus_get_device(handle, &adev))
return AE_OK;
fwnode = acpi_fwnode_handle(adev);
ret = fwnode_property_get_reference_args(fwnode, "usb4-host-interface",
NULL, 0, 0, &args);
if (ret)
return AE_OK;
/* It needs to reference this NHI */
if (nhi->pdev->dev.fwnode != args.fwnode)
goto out_put;
/*
* Try to find physical device walking upwards to the hierarcy.
* We need to do this because the xHCI driver might not yet be
* bound so the USB3 SuperSpeed ports are not yet created.
*/
dev = acpi_get_first_physical_node(adev);
while (!dev) {
adev = adev->parent;
if (!adev)
break;
dev = acpi_get_first_physical_node(adev);
}
if (!dev)
goto out_put;
/*
* Check that the device is PCIe. This is because USB3
* SuperSpeed ports have this property and they are not power
* managed with the xHCI and the SuperSpeed hub so we create the
* link from xHCI instead.
*/
while (!dev_is_pci(dev))
dev = dev->parent;
if (!dev)
goto out_put;
/*
* Check that this actually matches the type of device we
* expect. It should either be xHCI or PCIe root/downstream
* port.
*/
pdev = to_pci_dev(dev);
if (pdev->class == PCI_CLASS_SERIAL_USB_XHCI ||
(pci_is_pcie(pdev) &&
(pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT ||
pci_pcie_type(pdev) == PCI_EXP_TYPE_DOWNSTREAM))) {
const struct device_link *link;
link = device_link_add(&pdev->dev, &nhi->pdev->dev,
DL_FLAG_AUTOREMOVE_SUPPLIER |
DL_FLAG_PM_RUNTIME);
if (link) {
dev_dbg(&nhi->pdev->dev, "created link from %s\n",
dev_name(&pdev->dev));
} else {
dev_warn(&nhi->pdev->dev, "device link creation from %s failed\n",
dev_name(&pdev->dev));
}
}
out_put:
fwnode_handle_put(args.fwnode);
return AE_OK;
}
/**
* tb_acpi_add_links() - Add device links based on ACPI description
* @nhi: Pointer to NHI
*
* Goes over ACPI namespace finding tunneled ports that reference to
* @nhi ACPI node. For each reference a device link is added. The link
* is automatically removed by the driver core.
*/
void tb_acpi_add_links(struct tb_nhi *nhi)
{
acpi_status status;
if (!has_acpi_companion(&nhi->pdev->dev))
return;
/*
* Find all devices that have usb4-host-controller interface
* property that references to this NHI.
*/
status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, 32,
tb_acpi_add_link, NULL, nhi, NULL);
if (ACPI_FAILURE(status))
dev_warn(&nhi->pdev->dev, "failed to enumerate tunneled ports\n");
}

View File

@ -15,14 +15,6 @@
#define VSE_CAP_OFFSET_MAX 0xffff
#define TMU_ACCESS_EN BIT(20)
struct tb_cap_any {
union {
struct tb_cap_basic basic;
struct tb_cap_extended_short extended_short;
struct tb_cap_extended_long extended_long;
};
} __packed;
static int tb_port_enable_tmu(struct tb_port *port, bool enable)
{
struct tb_switch *sw = port->sw;
@ -67,23 +59,50 @@ static void tb_port_dummy_read(struct tb_port *port)
}
}
/**
* tb_port_next_cap() - Return next capability in the linked list
* @port: Port to find the capability for
* @offset: Previous capability offset (%0 for start)
*
* Returns dword offset of the next capability in port config space
* capability list and returns it. Passing %0 returns the first entry in
* the capability list. If no next capability is found returns %0. In case
* of failure returns negative errno.
*/
int tb_port_next_cap(struct tb_port *port, unsigned int offset)
{
struct tb_cap_any header;
int ret;
if (!offset)
return port->config.first_cap_offset;
ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1);
if (ret)
return ret;
return header.basic.next;
}
static int __tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap)
{
u32 offset = 1;
int offset = 0;
do {
struct tb_cap_any header;
int ret;
offset = tb_port_next_cap(port, offset);
if (offset < 0)
return offset;
ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1);
if (ret)
return ret;
if (header.basic.cap == cap)
return offset;
offset = header.basic.next;
} while (offset);
} while (offset > 0);
return -ENOENT;
}
@ -113,6 +132,50 @@ int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap)
return ret;
}
/**
* tb_switch_next_cap() - Return next capability in the linked list
* @sw: Switch to find the capability for
* @offset: Previous capability offset (%0 for start)
*
* Finds dword offset of the next capability in router config space
* capability list and returns it. Passing %0 returns the first entry in
* the capability list. If no next capability is found returns %0. In case
* of failure returns negative errno.
*/
int tb_switch_next_cap(struct tb_switch *sw, unsigned int offset)
{
struct tb_cap_any header;
int ret;
if (!offset)
return sw->config.first_cap_offset;
ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 2);
if (ret)
return ret;
switch (header.basic.cap) {
case TB_SWITCH_CAP_TMU:
ret = header.basic.next;
break;
case TB_SWITCH_CAP_VSE:
if (!header.extended_short.length)
ret = header.extended_long.next;
else
ret = header.extended_short.next;
break;
default:
tb_sw_dbg(sw, "unknown capability %#x at %#x\n",
header.basic.cap, offset);
ret = -EINVAL;
break;
}
return ret >= VSE_CAP_OFFSET_MAX ? 0 : ret;
}
/**
* tb_switch_find_cap() - Find switch capability
* @sw Switch to find the capability for
@ -124,21 +187,23 @@ int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap)
*/
int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap)
{
int offset = sw->config.first_cap_offset;
int offset = 0;
while (offset > 0 && offset < CAP_OFFSET_MAX) {
do {
struct tb_cap_any header;
int ret;
offset = tb_switch_next_cap(sw, offset);
if (offset < 0)
return offset;
ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1);
if (ret)
return ret;
if (header.basic.cap == cap)
return offset;
offset = header.basic.next;
}
} while (offset);
return -ENOENT;
}
@ -155,37 +220,24 @@ int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap)
*/
int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec)
{
struct tb_cap_any header;
int offset;
int offset = 0;
offset = tb_switch_find_cap(sw, TB_SWITCH_CAP_VSE);
if (offset < 0)
return offset;
while (offset > 0 && offset < VSE_CAP_OFFSET_MAX) {
do {
struct tb_cap_any header;
int ret;
ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 2);
offset = tb_switch_next_cap(sw, offset);
if (offset < 0)
return offset;
ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1);
if (ret)
return ret;
/*
* Extended vendor specific capabilities come in two
* flavors: short and long. The latter is used when
* offset is over 0xff.
*/
if (offset >= CAP_OFFSET_MAX) {
if (header.extended_long.vsec_id == vsec)
return offset;
offset = header.extended_long.next;
} else {
if (header.extended_short.vsec_id == vsec)
return offset;
if (!header.extended_short.length)
return -ENOENT;
offset = header.extended_short.next;
}
}
if (header.extended_short.cap == TB_SWITCH_CAP_VSE &&
header.extended_short.vsec_id == vsec)
return offset;
} while (offset);
return -ENOENT;
}

View File

@ -219,6 +219,7 @@ static int check_config_address(struct tb_cfg_address addr,
static struct tb_cfg_result decode_error(const struct ctl_pkg *response)
{
struct cfg_error_pkg *pkg = response->buffer;
struct tb_ctl *ctl = response->ctl;
struct tb_cfg_result res = { 0 };
res.response_route = tb_cfg_get_route(&pkg->header);
res.response_port = 0;
@ -227,9 +228,13 @@ static struct tb_cfg_result decode_error(const struct ctl_pkg *response)
if (res.err)
return res;
WARN(pkg->zero1, "pkg->zero1 is %#x\n", pkg->zero1);
WARN(pkg->zero2, "pkg->zero1 is %#x\n", pkg->zero1);
WARN(pkg->zero3, "pkg->zero1 is %#x\n", pkg->zero1);
if (pkg->zero1)
tb_ctl_warn(ctl, "pkg->zero1 is %#x\n", pkg->zero1);
if (pkg->zero2)
tb_ctl_warn(ctl, "pkg->zero2 is %#x\n", pkg->zero2);
if (pkg->zero3)
tb_ctl_warn(ctl, "pkg->zero3 is %#x\n", pkg->zero3);
res.err = 1;
res.tb_error = pkg->error;
res.response_port = pkg->port;
@ -266,9 +271,8 @@ static void tb_cfg_print_error(struct tb_ctl *ctl,
* Invalid cfg_space/offset/length combination in
* cfg_read/cfg_write.
*/
tb_ctl_WARN(ctl,
"CFG_ERROR(%llx:%x): Invalid config space or offset\n",
res->response_route, res->response_port);
tb_ctl_dbg(ctl, "%llx:%x: invalid config space or offset\n",
res->response_route, res->response_port);
return;
case TB_CFG_ERROR_NO_SUCH_PORT:
/*
@ -283,6 +287,10 @@ static void tb_cfg_print_error(struct tb_ctl *ctl,
tb_ctl_WARN(ctl, "CFG_ERROR(%llx:%x): Route contains a loop\n",
res->response_route, res->response_port);
return;
case TB_CFG_ERROR_LOCK:
tb_ctl_warn(ctl, "%llx:%x: downstream port is locked\n",
res->response_route, res->response_port);
return;
default:
/* 5,6,7,9 and 11 are also valid error codes */
tb_ctl_WARN(ctl, "CFG_ERROR(%llx:%x): Unknown error\n",
@ -951,6 +959,9 @@ static int tb_cfg_get_error(struct tb_ctl *ctl, enum tb_cfg_space space,
return -ENODEV;
tb_cfg_print_error(ctl, res);
if (res->tb_error == TB_CFG_ERROR_LOCK)
return -EACCES;
return -EIO;
}

View File

@ -0,0 +1,701 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Debugfs interface
*
* Copyright (C) 2020, Intel Corporation
* Authors: Gil Fine <gil.fine@intel.com>
* Mika Westerberg <mika.westerberg@linux.intel.com>
*/
#include <linux/debugfs.h>
#include <linux/pm_runtime.h>
#include "tb.h"
#define PORT_CAP_PCIE_LEN 1
#define PORT_CAP_POWER_LEN 2
#define PORT_CAP_LANE_LEN 3
#define PORT_CAP_USB3_LEN 5
#define PORT_CAP_DP_LEN 8
#define PORT_CAP_TMU_LEN 8
#define PORT_CAP_BASIC_LEN 9
#define PORT_CAP_USB4_LEN 20
#define SWITCH_CAP_TMU_LEN 26
#define SWITCH_CAP_BASIC_LEN 27
#define PATH_LEN 2
#define COUNTER_SET_LEN 3
#define DEBUGFS_ATTR(__space, __write) \
static int __space ## _open(struct inode *inode, struct file *file) \
{ \
return single_open(file, __space ## _show, inode->i_private); \
} \
\
static const struct file_operations __space ## _fops = { \
.owner = THIS_MODULE, \
.open = __space ## _open, \
.release = single_release, \
.read = seq_read, \
.write = __write, \
.llseek = seq_lseek, \
}
#define DEBUGFS_ATTR_RO(__space) \
DEBUGFS_ATTR(__space, NULL)
#define DEBUGFS_ATTR_RW(__space) \
DEBUGFS_ATTR(__space, __space ## _write)
static struct dentry *tb_debugfs_root;
static void *validate_and_copy_from_user(const void __user *user_buf,
size_t *count)
{
size_t nbytes;
void *buf;
if (!*count)
return ERR_PTR(-EINVAL);
if (!access_ok(user_buf, *count))
return ERR_PTR(-EFAULT);
buf = (void *)get_zeroed_page(GFP_KERNEL);
if (!buf)
return ERR_PTR(-ENOMEM);
nbytes = min_t(size_t, *count, PAGE_SIZE);
if (copy_from_user(buf, user_buf, nbytes)) {
free_page((unsigned long)buf);
return ERR_PTR(-EFAULT);
}
*count = nbytes;
return buf;
}
static bool parse_line(char **line, u32 *offs, u32 *val, int short_fmt_len,
int long_fmt_len)
{
char *token;
u32 v[5];
int ret;
token = strsep(line, "\n");
if (!token)
return false;
/*
* For Adapter/Router configuration space:
* Short format is: offset value\n
* v[0] v[1]
* Long format as produced from the read side:
* offset relative_offset cap_id vs_cap_id value\n
* v[0] v[1] v[2] v[3] v[4]
*
* For Counter configuration space:
* Short format is: offset\n
* v[0]
* Long format as produced from the read side:
* offset relative_offset counter_id value\n
* v[0] v[1] v[2] v[3]
*/
ret = sscanf(token, "%i %i %i %i %i", &v[0], &v[1], &v[2], &v[3], &v[4]);
/* In case of Counters, clear counter, "val" content is NA */
if (ret == short_fmt_len) {
*offs = v[0];
*val = v[short_fmt_len - 1];
return true;
} else if (ret == long_fmt_len) {
*offs = v[0];
*val = v[long_fmt_len - 1];
return true;
}
return false;
}
#if IS_ENABLED(CONFIG_USB4_DEBUGFS_WRITE)
static ssize_t regs_write(struct tb_switch *sw, struct tb_port *port,
const char __user *user_buf, size_t count,
loff_t *ppos)
{
struct tb *tb = sw->tb;
char *line, *buf;
u32 val, offset;
int ret = 0;
buf = validate_and_copy_from_user(user_buf, &count);
if (IS_ERR(buf))
return PTR_ERR(buf);
pm_runtime_get_sync(&sw->dev);
if (mutex_lock_interruptible(&tb->lock)) {
ret = -ERESTARTSYS;
goto out;
}
/* User did hardware changes behind the driver's back */
add_taint(TAINT_USER, LOCKDEP_STILL_OK);
line = buf;
while (parse_line(&line, &offset, &val, 2, 5)) {
if (port)
ret = tb_port_write(port, &val, TB_CFG_PORT, offset, 1);
else
ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, offset, 1);
if (ret)
break;
}
mutex_unlock(&tb->lock);
out:
pm_runtime_mark_last_busy(&sw->dev);
pm_runtime_put_autosuspend(&sw->dev);
free_page((unsigned long)buf);
return ret < 0 ? ret : count;
}
static ssize_t port_regs_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct seq_file *s = file->private_data;
struct tb_port *port = s->private;
return regs_write(port->sw, port, user_buf, count, ppos);
}
static ssize_t switch_regs_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct seq_file *s = file->private_data;
struct tb_switch *sw = s->private;
return regs_write(sw, NULL, user_buf, count, ppos);
}
#define DEBUGFS_MODE 0600
#else
#define port_regs_write NULL
#define switch_regs_write NULL
#define DEBUGFS_MODE 0400
#endif
static int port_clear_all_counters(struct tb_port *port)
{
u32 *buf;
int ret;
buf = kcalloc(COUNTER_SET_LEN * port->config.max_counters, sizeof(u32),
GFP_KERNEL);
if (!buf)
return -ENOMEM;
ret = tb_port_write(port, buf, TB_CFG_COUNTERS, 0,
COUNTER_SET_LEN * port->config.max_counters);
kfree(buf);
return ret;
}
static ssize_t counters_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct seq_file *s = file->private_data;
struct tb_port *port = s->private;
struct tb_switch *sw = port->sw;
struct tb *tb = port->sw->tb;
char *buf;
int ret;
buf = validate_and_copy_from_user(user_buf, &count);
if (IS_ERR(buf))
return PTR_ERR(buf);
pm_runtime_get_sync(&sw->dev);
if (mutex_lock_interruptible(&tb->lock)) {
ret = -ERESTARTSYS;
goto out;
}
/* If written delimiter only, clear all counters in one shot */
if (buf[0] == '\n') {
ret = port_clear_all_counters(port);
} else {
char *line = buf;
u32 val, offset;
ret = -EINVAL;
while (parse_line(&line, &offset, &val, 1, 4)) {
ret = tb_port_write(port, &val, TB_CFG_COUNTERS,
offset, 1);
if (ret)
break;
}
}
mutex_unlock(&tb->lock);
out:
pm_runtime_mark_last_busy(&sw->dev);
pm_runtime_put_autosuspend(&sw->dev);
free_page((unsigned long)buf);
return ret < 0 ? ret : count;
}
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)
{
int ret, offset = 0;
while (length > 0) {
int i, dwords = min(length, TB_MAX_CONFIG_RW_LENGTH);
u32 data[TB_MAX_CONFIG_RW_LENGTH];
if (port)
ret = tb_port_read(port, data, TB_CFG_PORT, cap + offset,
dwords);
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);
return;
}
for (i = 0; i < dwords; i++) {
seq_printf(s, "0x%04x %4d 0x%02x 0x%02x 0x%08x\n",
cap + offset + i, offset + i,
cap_id, vsec_id, data[i]);
}
length -= dwords;
offset += dwords;
}
}
static void port_cap_show(struct tb_port *port, struct seq_file *s,
unsigned int cap)
{
struct tb_cap_any header;
u8 vsec_id = 0;
size_t length;
int ret;
ret = tb_port_read(port, &header, TB_CFG_PORT, cap, 1);
if (ret) {
seq_printf(s, "0x%04x <capability read failed>\n", cap);
return;
}
switch (header.basic.cap) {
case TB_PORT_CAP_PHY:
length = PORT_CAP_LANE_LEN;
break;
case TB_PORT_CAP_TIME1:
length = PORT_CAP_TMU_LEN;
break;
case TB_PORT_CAP_POWER:
length = PORT_CAP_POWER_LEN;
break;
case TB_PORT_CAP_ADAP:
if (tb_port_is_pcie_down(port) || tb_port_is_pcie_up(port)) {
length = PORT_CAP_PCIE_LEN;
} else if (tb_port_is_dpin(port) || tb_port_is_dpout(port)) {
length = PORT_CAP_DP_LEN;
} else if (tb_port_is_usb3_down(port) ||
tb_port_is_usb3_up(port)) {
length = PORT_CAP_USB3_LEN;
} else {
seq_printf(s, "0x%04x <unsupported capability 0x%02x>\n",
cap, header.basic.cap);
return;
}
break;
case TB_PORT_CAP_VSE:
if (!header.extended_short.length) {
ret = tb_port_read(port, (u32 *)&header + 1, TB_CFG_PORT,
cap + 1, 1);
if (ret) {
seq_printf(s, "0x%04x <capability read failed>\n",
cap + 1);
return;
}
length = header.extended_long.length;
vsec_id = header.extended_short.vsec_id;
} 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;
case TB_PORT_CAP_USB4:
length = PORT_CAP_USB4_LEN;
break;
default:
seq_printf(s, "0x%04x <unsupported capability 0x%02x>\n",
cap, header.basic.cap);
return;
}
cap_show(s, NULL, port, cap, header.basic.cap, vsec_id, length);
}
static void port_caps_show(struct tb_port *port, struct seq_file *s)
{
int cap;
cap = tb_port_next_cap(port, 0);
while (cap > 0) {
port_cap_show(port, s, cap);
cap = tb_port_next_cap(port, cap);
}
}
static int port_basic_regs_show(struct tb_port *port, struct seq_file *s)
{
u32 data[PORT_CAP_BASIC_LEN];
int ret, i;
ret = tb_port_read(port, data, TB_CFG_PORT, 0, ARRAY_SIZE(data));
if (ret)
return ret;
for (i = 0; i < ARRAY_SIZE(data); i++)
seq_printf(s, "0x%04x %4d 0x00 0x00 0x%08x\n", i, i, data[i]);
return 0;
}
static int port_regs_show(struct seq_file *s, void *not_used)
{
struct tb_port *port = s->private;
struct tb_switch *sw = port->sw;
struct tb *tb = sw->tb;
int ret;
pm_runtime_get_sync(&sw->dev);
if (mutex_lock_interruptible(&tb->lock)) {
ret = -ERESTARTSYS;
goto out_rpm_put;
}
seq_puts(s, "# offset relative_offset cap_id vs_cap_id value\n");
ret = port_basic_regs_show(port, s);
if (ret)
goto out_unlock;
port_caps_show(port, s);
out_unlock:
mutex_unlock(&tb->lock);
out_rpm_put:
pm_runtime_mark_last_busy(&sw->dev);
pm_runtime_put_autosuspend(&sw->dev);
return ret;
}
DEBUGFS_ATTR_RW(port_regs);
static void switch_cap_show(struct tb_switch *sw, struct seq_file *s,
unsigned int cap)
{
struct tb_cap_any header;
int ret, length;
u8 vsec_id = 0;
ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, cap, 1);
if (ret) {
seq_printf(s, "0x%04x <capability read failed>\n", cap);
return;
}
if (header.basic.cap == TB_SWITCH_CAP_VSE) {
if (!header.extended_short.length) {
ret = tb_sw_read(sw, (u32 *)&header + 1, TB_CFG_SWITCH,
cap + 1, 1);
if (ret) {
seq_printf(s, "0x%04x <capability read failed>\n",
cap + 1);
return;
}
length = header.extended_long.length;
} else {
length = header.extended_short.length;
}
vsec_id = header.extended_short.vsec_id;
} else {
if (header.basic.cap == TB_SWITCH_CAP_TMU) {
length = SWITCH_CAP_TMU_LEN;
} else {
seq_printf(s, "0x%04x <unknown capability 0x%02x>\n",
cap, header.basic.cap);
return;
}
}
cap_show(s, sw, NULL, cap, header.basic.cap, vsec_id, length);
}
static void switch_caps_show(struct tb_switch *sw, struct seq_file *s)
{
int cap;
cap = tb_switch_next_cap(sw, 0);
while (cap > 0) {
switch_cap_show(sw, s, cap);
cap = tb_switch_next_cap(sw, cap);
}
}
static int switch_basic_regs_show(struct tb_switch *sw, struct seq_file *s)
{
u32 data[SWITCH_CAP_BASIC_LEN];
size_t dwords;
int ret, i;
/* Only USB4 has the additional registers */
if (tb_switch_is_usb4(sw))
dwords = ARRAY_SIZE(data);
else
dwords = 7;
ret = tb_sw_read(sw, data, TB_CFG_SWITCH, 0, dwords);
if (ret)
return ret;
for (i = 0; i < dwords; i++)
seq_printf(s, "0x%04x %4d 0x00 0x00 0x%08x\n", i, i, data[i]);
return 0;
}
static int switch_regs_show(struct seq_file *s, void *not_used)
{
struct tb_switch *sw = s->private;
struct tb *tb = sw->tb;
int ret;
pm_runtime_get_sync(&sw->dev);
if (mutex_lock_interruptible(&tb->lock)) {
ret = -ERESTARTSYS;
goto out_rpm_put;
}
seq_puts(s, "# offset relative_offset cap_id vs_cap_id value\n");
ret = switch_basic_regs_show(sw, s);
if (ret)
goto out_unlock;
switch_caps_show(sw, s);
out_unlock:
mutex_unlock(&tb->lock);
out_rpm_put:
pm_runtime_mark_last_busy(&sw->dev);
pm_runtime_put_autosuspend(&sw->dev);
return ret;
}
DEBUGFS_ATTR_RW(switch_regs);
static int path_show_one(struct tb_port *port, struct seq_file *s, int hopid)
{
u32 data[PATH_LEN];
int ret, i;
ret = tb_port_read(port, data, TB_CFG_HOPS, hopid * PATH_LEN,
ARRAY_SIZE(data));
if (ret) {
seq_printf(s, "0x%04x <not accessible>\n", hopid * PATH_LEN);
return ret;
}
for (i = 0; i < ARRAY_SIZE(data); i++) {
seq_printf(s, "0x%04x %4d 0x%02x 0x%08x\n",
hopid * PATH_LEN + i, i, hopid, data[i]);
}
return 0;
}
static int path_show(struct seq_file *s, void *not_used)
{
struct tb_port *port = s->private;
struct tb_switch *sw = port->sw;
struct tb *tb = sw->tb;
int start, i, ret = 0;
pm_runtime_get_sync(&sw->dev);
if (mutex_lock_interruptible(&tb->lock)) {
ret = -ERESTARTSYS;
goto out_rpm_put;
}
seq_puts(s, "# offset relative_offset in_hop_id value\n");
/* NHI and lane adapters have entry for path 0 */
if (tb_port_is_null(port) || tb_port_is_nhi(port)) {
ret = path_show_one(port, s, 0);
if (ret)
goto out_unlock;
}
start = tb_port_is_nhi(port) ? 1 : TB_PATH_MIN_HOPID;
for (i = start; i <= port->config.max_in_hop_id; i++) {
ret = path_show_one(port, s, i);
if (ret)
break;
}
out_unlock:
mutex_unlock(&tb->lock);
out_rpm_put:
pm_runtime_mark_last_busy(&sw->dev);
pm_runtime_put_autosuspend(&sw->dev);
return ret;
}
DEBUGFS_ATTR_RO(path);
static int counter_set_regs_show(struct tb_port *port, struct seq_file *s,
int counter)
{
u32 data[COUNTER_SET_LEN];
int ret, i;
ret = tb_port_read(port, data, TB_CFG_COUNTERS,
counter * COUNTER_SET_LEN, ARRAY_SIZE(data));
if (ret) {
seq_printf(s, "0x%04x <not accessible>\n",
counter * COUNTER_SET_LEN);
return ret;
}
for (i = 0; i < ARRAY_SIZE(data); i++) {
seq_printf(s, "0x%04x %4d 0x%02x 0x%08x\n",
counter * COUNTER_SET_LEN + i, i, counter, data[i]);
}
return 0;
}
static int counters_show(struct seq_file *s, void *not_used)
{
struct tb_port *port = s->private;
struct tb_switch *sw = port->sw;
struct tb *tb = sw->tb;
int i, ret = 0;
pm_runtime_get_sync(&sw->dev);
if (mutex_lock_interruptible(&tb->lock)) {
ret = -ERESTARTSYS;
goto out;
}
seq_puts(s, "# offset relative_offset counter_id value\n");
for (i = 0; i < port->config.max_counters; i++) {
ret = counter_set_regs_show(port, s, i);
if (ret)
break;
}
mutex_unlock(&tb->lock);
out:
pm_runtime_mark_last_busy(&sw->dev);
pm_runtime_put_autosuspend(&sw->dev);
return ret;
}
DEBUGFS_ATTR_RW(counters);
/**
* tb_switch_debugfs_init() - Add debugfs entries for router
* @sw: Pointer to the router
*
* Adds debugfs directories and files for given router.
*/
void tb_switch_debugfs_init(struct tb_switch *sw)
{
struct dentry *debugfs_dir;
struct tb_port *port;
debugfs_dir = debugfs_create_dir(dev_name(&sw->dev), tb_debugfs_root);
sw->debugfs_dir = debugfs_dir;
debugfs_create_file("regs", DEBUGFS_MODE, debugfs_dir, sw,
&switch_regs_fops);
tb_switch_for_each_port(sw, port) {
struct dentry *debugfs_dir;
char dir_name[10];
if (port->disabled)
continue;
if (port->config.type == TB_TYPE_INACTIVE)
continue;
snprintf(dir_name, sizeof(dir_name), "port%d", port->port);
debugfs_dir = debugfs_create_dir(dir_name, sw->debugfs_dir);
debugfs_create_file("regs", DEBUGFS_MODE, debugfs_dir,
port, &port_regs_fops);
debugfs_create_file("path", 0400, debugfs_dir, port,
&path_fops);
if (port->config.counters_support)
debugfs_create_file("counters", 0600, debugfs_dir, port,
&counters_fops);
}
}
/**
* tb_switch_debugfs_remove() - Remove all router debugfs entries
* @sw: Pointer to the router
*
* Removes all previously added debugfs entries under this router.
*/
void tb_switch_debugfs_remove(struct tb_switch *sw)
{
debugfs_remove_recursive(sw->debugfs_dir);
}
void tb_debugfs_init(void)
{
tb_debugfs_root = debugfs_create_dir("thunderbolt", NULL);
}
void tb_debugfs_exit(void)
{
debugfs_remove_recursive(tb_debugfs_root);
}

View File

@ -275,7 +275,7 @@ static struct attribute *domain_attrs[] = {
static umode_t domain_attr_is_visible(struct kobject *kobj,
struct attribute *attr, int n)
{
struct device *dev = container_of(kobj, struct device, kobj);
struct device *dev = kobj_to_dev(kobj);
struct tb *tb = container_of(dev, struct tb, dev);
if (attr == &dev_attr_boot_acl.attr) {
@ -455,6 +455,8 @@ int tb_domain_add(struct tb *tb)
/* This starts event processing */
mutex_unlock(&tb->lock);
device_init_wakeup(&tb->dev, true);
pm_runtime_no_callbacks(&tb->dev);
pm_runtime_set_active(&tb->dev);
pm_runtime_enable(&tb->dev);
@ -544,6 +546,33 @@ int tb_domain_suspend(struct tb *tb)
return tb->cm_ops->suspend ? tb->cm_ops->suspend(tb) : 0;
}
int tb_domain_freeze_noirq(struct tb *tb)
{
int ret = 0;
mutex_lock(&tb->lock);
if (tb->cm_ops->freeze_noirq)
ret = tb->cm_ops->freeze_noirq(tb);
if (!ret)
tb_ctl_stop(tb->ctl);
mutex_unlock(&tb->lock);
return ret;
}
int tb_domain_thaw_noirq(struct tb *tb)
{
int ret = 0;
mutex_lock(&tb->lock);
tb_ctl_start(tb->ctl);
if (tb->cm_ops->thaw_noirq)
ret = tb->cm_ops->thaw_noirq(tb);
mutex_unlock(&tb->lock);
return ret;
}
void tb_domain_complete(struct tb *tb)
{
if (tb->cm_ops->complete)
@ -798,12 +827,23 @@ int tb_domain_init(void)
{
int ret;
tb_test_init();
tb_debugfs_init();
ret = tb_xdomain_init();
if (ret)
return ret;
goto err_debugfs;
ret = bus_register(&tb_bus_type);
if (ret)
tb_xdomain_exit();
goto err_xdomain;
return 0;
err_xdomain:
tb_xdomain_exit();
err_debugfs:
tb_debugfs_exit();
tb_test_exit();
return ret;
}
@ -814,4 +854,6 @@ void tb_domain_exit(void)
ida_destroy(&tb_domain_ida);
tb_nvm_exit();
tb_xdomain_exit();
tb_debugfs_exit();
tb_test_exit();
}

View File

@ -1635,11 +1635,14 @@ static void icm_icl_rtd3_veto(struct tb *tb, const struct icm_pkg_header *hdr)
static bool icm_tgl_is_supported(struct tb *tb)
{
u32 val;
/*
* If the firmware is not running use software CM. This platform
* should fully support both.
*/
return icm_firmware_running(tb->nhi);
val = ioread32(tb->nhi->iobase + REG_FW_STS);
return !!(val & REG_FW_STS_NVM_AUTH_DONE);
}
static void icm_handle_notification(struct work_struct *work)

View File

@ -45,7 +45,7 @@ static int find_port_lc_cap(struct tb_port *port)
return sw->cap_lc + start + phys * size;
}
static int tb_lc_configure_lane(struct tb_port *port, bool configure)
static int tb_lc_set_port_configured(struct tb_port *port, bool configured)
{
bool upstream = tb_is_upstream_port(port);
struct tb_switch *sw = port->sw;
@ -69,7 +69,7 @@ static int tb_lc_configure_lane(struct tb_port *port, bool configure)
else
lane = TB_LC_SX_CTRL_L2C;
if (configure) {
if (configured) {
ctrl |= lane;
if (upstream)
ctrl |= TB_LC_SX_CTRL_UPSTREAM;
@ -83,55 +83,146 @@ static int tb_lc_configure_lane(struct tb_port *port, bool configure)
}
/**
* tb_lc_configure_link() - Let LC know about configured link
* @sw: Switch that is being added
* tb_lc_configure_port() - Let LC know about configured port
* @port: Port that is set as configured
*
* Informs LC of both parent switch and @sw that there is established
* link between the two.
* Sets the port configured for power management purposes.
*/
int tb_lc_configure_link(struct tb_switch *sw)
int tb_lc_configure_port(struct tb_port *port)
{
struct tb_port *up, *down;
int ret;
if (!tb_route(sw) || tb_switch_is_icm(sw))
return 0;
up = tb_upstream_port(sw);
down = tb_port_at(tb_route(sw), tb_to_switch(sw->dev.parent));
/* Configure parent link toward this switch */
ret = tb_lc_configure_lane(down, true);
if (ret)
return ret;
/* Configure upstream link from this switch to the parent */
ret = tb_lc_configure_lane(up, true);
if (ret)
tb_lc_configure_lane(down, false);
return ret;
return tb_lc_set_port_configured(port, true);
}
/**
* tb_lc_unconfigure_link() - Let LC know about unconfigured link
* @sw: Switch to unconfigure
* tb_lc_unconfigure_port() - Let LC know about unconfigured port
* @port: Port that is set as configured
*
* Informs LC of both parent switch and @sw that the link between the
* two does not exist anymore.
* Sets the port unconfigured for power management purposes.
*/
void tb_lc_unconfigure_link(struct tb_switch *sw)
void tb_lc_unconfigure_port(struct tb_port *port)
{
struct tb_port *up, *down;
tb_lc_set_port_configured(port, false);
}
if (sw->is_unplugged || !tb_route(sw) || tb_switch_is_icm(sw))
return;
static int tb_lc_set_xdomain_configured(struct tb_port *port, bool configure)
{
struct tb_switch *sw = port->sw;
u32 ctrl, lane;
int cap, ret;
up = tb_upstream_port(sw);
down = tb_port_at(tb_route(sw), tb_to_switch(sw->dev.parent));
if (sw->generation < 2)
return 0;
tb_lc_configure_lane(up, false);
tb_lc_configure_lane(down, false);
cap = find_port_lc_cap(port);
if (cap < 0)
return cap;
ret = tb_sw_read(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1);
if (ret)
return ret;
/* Resolve correct lane */
if (port->port % 2)
lane = TB_LC_SX_CTRL_L1D;
else
lane = TB_LC_SX_CTRL_L2D;
if (configure)
ctrl |= lane;
else
ctrl &= ~lane;
return tb_sw_write(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1);
}
/**
* tb_lc_configure_xdomain() - Inform LC that the link is XDomain
* @port: Switch downstream port connected to another host
*
* Sets the lane configured for XDomain accordingly so that the LC knows
* about this. Returns %0 in success and negative errno in failure.
*/
int tb_lc_configure_xdomain(struct tb_port *port)
{
return tb_lc_set_xdomain_configured(port, true);
}
/**
* tb_lc_unconfigure_xdomain() - Unconfigure XDomain from port
* @port: Switch downstream port that was connected to another host
*
* Unsets the lane XDomain configuration.
*/
void tb_lc_unconfigure_xdomain(struct tb_port *port)
{
tb_lc_set_xdomain_configured(port, false);
}
static int tb_lc_set_wake_one(struct tb_switch *sw, unsigned int offset,
unsigned int flags)
{
u32 ctrl;
int ret;
/*
* Enable wake on PCIe and USB4 (wake coming from another
* router).
*/
ret = tb_sw_read(sw, &ctrl, TB_CFG_SWITCH,
offset + TB_LC_SX_CTRL, 1);
if (ret)
return ret;
ctrl &= ~(TB_LC_SX_CTRL_WOC | TB_LC_SX_CTRL_WOD | TB_LC_SX_CTRL_WOP |
TB_LC_SX_CTRL_WOU4);
if (flags & TB_WAKE_ON_CONNECT)
ctrl |= TB_LC_SX_CTRL_WOC | TB_LC_SX_CTRL_WOD;
if (flags & TB_WAKE_ON_USB4)
ctrl |= TB_LC_SX_CTRL_WOU4;
if (flags & TB_WAKE_ON_PCIE)
ctrl |= TB_LC_SX_CTRL_WOP;
return tb_sw_write(sw, &ctrl, TB_CFG_SWITCH, offset + TB_LC_SX_CTRL, 1);
}
/**
* tb_lc_set_wake() - Enable/disable wake
* @sw: Switch whose wakes to configure
* @flags: Wakeup flags (%0 to disable)
*
* For each LC sets wake bits accordingly.
*/
int tb_lc_set_wake(struct tb_switch *sw, unsigned int flags)
{
int start, size, nlc, ret, i;
u32 desc;
if (sw->generation < 2)
return 0;
if (!tb_route(sw))
return 0;
ret = read_lc_desc(sw, &desc);
if (ret)
return ret;
/* Figure out number of link controllers */
nlc = desc & TB_LC_DESC_NLC_MASK;
start = (desc & TB_LC_DESC_SIZE_MASK) >> TB_LC_DESC_SIZE_SHIFT;
size = (desc & TB_LC_DESC_PORT_SIZE_MASK) >> TB_LC_DESC_PORT_SIZE_SHIFT;
/* For each link controller set sleep bit */
for (i = 0; i < nlc; i++) {
unsigned int offset = sw->cap_lc + start + i * size;
ret = tb_lc_set_wake_one(sw, offset, flags);
if (ret)
return ret;
}
return 0;
}
/**

View File

@ -17,6 +17,7 @@
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/property.h>
#include <linux/platform_data/x86/apple.h>
#include "nhi.h"
#include "nhi_regs.h"
@ -863,6 +864,22 @@ static int nhi_suspend_noirq(struct device *dev)
return __nhi_suspend_noirq(dev, device_may_wakeup(dev));
}
static int nhi_freeze_noirq(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct tb *tb = pci_get_drvdata(pdev);
return tb_domain_freeze_noirq(tb);
}
static int nhi_thaw_noirq(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct tb *tb = pci_get_drvdata(pdev);
return tb_domain_thaw_noirq(tb);
}
static bool nhi_wake_supported(struct pci_dev *pdev)
{
u8 val;
@ -1069,6 +1086,69 @@ static bool nhi_imr_valid(struct pci_dev *pdev)
return true;
}
/*
* During suspend the Thunderbolt controller is reset and all PCIe
* tunnels are lost. The NHI driver will try to reestablish all tunnels
* during resume. This adds device links between the tunneled PCIe
* downstream ports and the NHI so that the device core will make sure
* NHI is resumed first before the rest.
*/
static void tb_apple_add_links(struct tb_nhi *nhi)
{
struct pci_dev *upstream, *pdev;
if (!x86_apple_machine)
return;
switch (nhi->pdev->device) {
case PCI_DEVICE_ID_INTEL_LIGHT_RIDGE:
case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C:
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI:
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI:
break;
default:
return;
}
upstream = pci_upstream_bridge(nhi->pdev);
while (upstream) {
if (!pci_is_pcie(upstream))
return;
if (pci_pcie_type(upstream) == PCI_EXP_TYPE_UPSTREAM)
break;
upstream = pci_upstream_bridge(upstream);
}
if (!upstream)
return;
/*
* For each hotplug downstream port, create add device link
* back to NHI so that PCIe tunnels can be re-established after
* sleep.
*/
for_each_pci_bridge(pdev, upstream->subordinate) {
const struct device_link *link;
if (!pci_is_pcie(pdev))
continue;
if (pci_pcie_type(pdev) != PCI_EXP_TYPE_DOWNSTREAM ||
!pdev->is_hotplug_bridge)
continue;
link = device_link_add(&pdev->dev, &nhi->pdev->dev,
DL_FLAG_AUTOREMOVE_SUPPLIER |
DL_FLAG_PM_RUNTIME);
if (link) {
dev_dbg(&nhi->pdev->dev, "created link from %s\n",
dev_name(&pdev->dev));
} else {
dev_warn(&nhi->pdev->dev, "device link creation from %s failed\n",
dev_name(&pdev->dev));
}
}
}
static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct tb_nhi *nhi;
@ -1134,6 +1214,9 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
return res;
}
tb_apple_add_links(nhi);
tb_acpi_add_links(nhi);
tb = icm_probe(nhi);
if (!tb)
tb = tb_probe(nhi);
@ -1157,6 +1240,8 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
}
pci_set_drvdata(pdev, tb);
device_wakeup_enable(&pdev->dev);
pm_runtime_allow(&pdev->dev);
pm_runtime_set_autosuspend_delay(&pdev->dev, TB_AUTOSUSPEND_DELAY);
pm_runtime_use_autosuspend(&pdev->dev);
@ -1186,14 +1271,13 @@ static void nhi_remove(struct pci_dev *pdev)
static const struct dev_pm_ops nhi_pm_ops = {
.suspend_noirq = nhi_suspend_noirq,
.resume_noirq = nhi_resume_noirq,
.freeze_noirq = nhi_suspend_noirq, /*
.freeze_noirq = nhi_freeze_noirq, /*
* we just disable hotplug, the
* pci-tunnels stay alive.
*/
.thaw_noirq = nhi_resume_noirq,
.thaw_noirq = nhi_thaw_noirq,
.restore_noirq = nhi_resume_noirq,
.suspend = nhi_suspend,
.freeze = nhi_suspend,
.poweroff_noirq = nhi_poweroff_noirq,
.poweroff = nhi_suspend,
.complete = nhi_complete,

View File

@ -59,7 +59,7 @@ static int icl_nhi_force_power(struct tb_nhi *nhi, bool power)
pci_write_config_dword(nhi->pdev, VS_CAP_22, vs_cap);
if (power) {
unsigned int retries = 10;
unsigned int retries = 350;
u32 val;
/* Wait until the firmware tells it is up and running */
@ -67,7 +67,7 @@ static int icl_nhi_force_power(struct tb_nhi *nhi, bool power)
pci_read_config_dword(nhi->pdev, VS_CAP_9, &val);
if (val & VS_CAP_9_FW_READY)
return 0;
msleep(250);
usleep_range(3000, 3100);
} while (--retries);
return -ETIMEDOUT;
@ -97,7 +97,7 @@ static int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout)
pci_read_config_dword(nhi->pdev, VS_CAP_18, &data);
if (data & VS_CAP_18_DONE)
goto clear;
msleep(100);
usleep_range(1000, 1100);
} while (time_before(jiffies, end));
return -ETIMEDOUT;
@ -121,31 +121,38 @@ static void icl_nhi_set_ltr(struct tb_nhi *nhi)
static int icl_nhi_suspend(struct tb_nhi *nhi)
{
struct tb *tb = pci_get_drvdata(nhi->pdev);
int ret;
if (icl_nhi_is_device_connected(nhi))
return 0;
/*
* If there is no device connected we need to perform both: a
* handshake through LC mailbox and force power down before
* entering D3.
*/
icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET);
ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
if (ret)
return ret;
if (tb_switch_is_icm(tb->root_switch)) {
/*
* If there is no device connected we need to perform
* both: a handshake through LC mailbox and force power
* down before entering D3.
*/
icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET);
ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
if (ret)
return ret;
}
return icl_nhi_force_power(nhi, false);
}
static int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup)
{
struct tb *tb = pci_get_drvdata(nhi->pdev);
enum icl_lc_mailbox_cmd cmd;
if (!pm_suspend_via_firmware())
return icl_nhi_suspend(nhi);
if (!tb_switch_is_icm(tb->root_switch))
return 0;
cmd = wakeup ? ICL_LC_GO2SX : ICL_LC_GO2SX_NO_WAKE;
icl_nhi_lc_mailbox_cmd(nhi, cmd);
return icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);

View File

@ -27,7 +27,7 @@ static const struct tb_quirk tb_quirks[] = {
* tb_check_quirks() - Check for quirks to apply
* @sw: Thunderbolt switch
*
* Apply any quirks for the Thunderbolt controller
* Apply any quirks for the Thunderbolt controller.
*/
void tb_check_quirks(struct tb_switch *sw)
{

View File

@ -601,6 +601,13 @@ int tb_port_add_nfc_credits(struct tb_port *port, int credits)
if (credits == 0 || port->sw->is_unplugged)
return 0;
/*
* USB4 restricts programming NFC buffers to lane adapters only
* so skip other ports.
*/
if (tb_switch_is_usb4(port->sw) && !tb_port_is_null(port))
return 0;
nfc_credits = port->config.nfc_credits & ADP_CS_4_NFC_BUFFERS_MASK;
nfc_credits += credits;
@ -666,6 +673,50 @@ int tb_port_unlock(struct tb_port *port)
return 0;
}
static int __tb_port_enable(struct tb_port *port, bool enable)
{
int ret;
u32 phy;
if (!tb_port_is_null(port))
return -EINVAL;
ret = tb_port_read(port, &phy, TB_CFG_PORT,
port->cap_phy + LANE_ADP_CS_1, 1);
if (ret)
return ret;
if (enable)
phy &= ~LANE_ADP_CS_1_LD;
else
phy |= LANE_ADP_CS_1_LD;
return tb_port_write(port, &phy, TB_CFG_PORT,
port->cap_phy + LANE_ADP_CS_1, 1);
}
/**
* tb_port_enable() - Enable lane adapter
* @port: Port to enable (can be %NULL)
*
* This is used for lane 0 and 1 adapters to enable it.
*/
int tb_port_enable(struct tb_port *port)
{
return __tb_port_enable(port, true);
}
/**
* tb_port_disable() - Disable lane adapter
* @port: Port to disable (can be %NULL)
*
* This is used for lane 0 and 1 adapters to disable it.
*/
int tb_port_disable(struct tb_port *port)
{
return __tb_port_enable(port, false);
}
/**
* tb_init_port() - initialize a port
*
@ -739,7 +790,7 @@ static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid,
* NHI can use HopIDs 1-max for other adapters HopIDs 0-7 are
* reserved.
*/
if (port->config.type != TB_TYPE_NHI && min_hopid < TB_PATH_MIN_HOPID)
if (!tb_port_is_nhi(port) && min_hopid < TB_PATH_MIN_HOPID)
min_hopid = TB_PATH_MIN_HOPID;
if (max_hopid < 0 || max_hopid > port_max_hopid)
@ -1227,23 +1278,24 @@ static void tb_dump_switch(const struct tb *tb, const struct tb_switch *sw)
/**
* reset_switch() - reconfigure route, enable and send TB_CFG_PKG_RESET
* @sw: Switch to reset
*
* Return: Returns 0 on success or an error code on failure.
*/
int tb_switch_reset(struct tb *tb, u64 route)
int tb_switch_reset(struct tb_switch *sw)
{
struct tb_cfg_result res;
struct tb_regs_switch_header header = {
header.route_hi = route >> 32,
header.route_lo = route,
header.enabled = true,
};
tb_dbg(tb, "resetting switch at %llx\n", route);
res.err = tb_cfg_write(tb->ctl, ((u32 *) &header) + 2, route,
0, 2, 2, 2);
if (sw->generation > 1)
return 0;
tb_sw_dbg(sw, "resetting switch\n");
res.err = tb_sw_write(sw, ((u32 *) &sw->config) + 2,
TB_CFG_SWITCH, 2, 2);
if (res.err)
return res.err;
res = tb_cfg_reset(tb->ctl, route, TB_CFG_DEFAULT_TIMEOUT);
res = tb_cfg_reset(sw->tb->ctl, tb_route(sw), TB_CFG_DEFAULT_TIMEOUT);
if (res.err > 0)
return -EIO;
return res.err;
@ -1261,7 +1313,7 @@ static int tb_plug_events_active(struct tb_switch *sw, bool active)
u32 data;
int res;
if (tb_switch_is_icm(sw))
if (tb_switch_is_icm(sw) || tb_switch_is_usb4(sw))
return 0;
sw->config.plug_events_delay = 0xff;
@ -1269,10 +1321,6 @@ static int tb_plug_events_active(struct tb_switch *sw, bool active)
if (res)
return res;
/* Plug events are always enabled in USB4 */
if (tb_switch_is_usb4(sw))
return 0;
res = tb_sw_read(sw, &data, TB_CFG_SWITCH, sw->cap_plug_events + 1, 1);
if (res)
return res;
@ -1649,7 +1697,7 @@ static struct attribute *switch_attrs[] = {
static umode_t switch_attr_is_visible(struct kobject *kobj,
struct attribute *attr, int n)
{
struct device *dev = container_of(kobj, struct device, kobj);
struct device *dev = kobj_to_dev(kobj);
struct tb_switch *sw = tb_to_switch(dev);
if (attr == &dev_attr_device.attr) {
@ -1988,7 +2036,7 @@ int tb_switch_configure(struct tb_switch *sw)
route = tb_route(sw);
tb_dbg(tb, "%s Switch at %#llx (depth: %d, up port: %d)\n",
sw->config.enabled ? "restoring " : "initializing", route,
sw->config.enabled ? "restoring" : "initializing", route,
tb_route_length(route), sw->config.upstream_port_number);
sw->config.enabled = 1;
@ -2008,10 +2056,6 @@ int tb_switch_configure(struct tb_switch *sw)
return ret;
ret = usb4_switch_setup(sw);
if (ret)
return ret;
ret = usb4_switch_configure_link(sw);
} else {
if (sw->config.vendor_id != PCI_VENDOR_ID_INTEL)
tb_sw_warn(sw, "unknown switch vendor id %#x\n",
@ -2025,10 +2069,6 @@ int tb_switch_configure(struct tb_switch *sw)
/* Enumerate the switch */
ret = tb_sw_write(sw, (u32 *)&sw->config + 1, TB_CFG_SWITCH,
ROUTER_CS_1, 3);
if (ret)
return ret;
ret = tb_lc_configure_link(sw);
}
if (ret)
return ret;
@ -2311,6 +2351,69 @@ void tb_switch_lane_bonding_disable(struct tb_switch *sw)
tb_sw_dbg(sw, "lane bonding disabled\n");
}
/**
* tb_switch_configure_link() - Set link configured
* @sw: Switch whose link is configured
*
* Sets the link upstream from @sw configured (from both ends) so that
* it will not be disconnected when the domain exits sleep. Can be
* called for any switch.
*
* It is recommended that this is called after lane bonding is enabled.
*
* Returns %0 on success and negative errno in case of error.
*/
int tb_switch_configure_link(struct tb_switch *sw)
{
struct tb_port *up, *down;
int ret;
if (!tb_route(sw) || tb_switch_is_icm(sw))
return 0;
up = tb_upstream_port(sw);
if (tb_switch_is_usb4(up->sw))
ret = usb4_port_configure(up);
else
ret = tb_lc_configure_port(up);
if (ret)
return ret;
down = up->remote;
if (tb_switch_is_usb4(down->sw))
return usb4_port_configure(down);
return tb_lc_configure_port(down);
}
/**
* tb_switch_unconfigure_link() - Unconfigure link
* @sw: Switch whose link is unconfigured
*
* Sets the link unconfigured so the @sw will be disconnected if the
* domain exists sleep.
*/
void tb_switch_unconfigure_link(struct tb_switch *sw)
{
struct tb_port *up, *down;
if (sw->is_unplugged)
return;
if (!tb_route(sw) || tb_switch_is_icm(sw))
return;
up = tb_upstream_port(sw);
if (tb_switch_is_usb4(up->sw))
usb4_port_unconfigure(up);
else
tb_lc_unconfigure_port(up);
down = up->remote;
if (tb_switch_is_usb4(down->sw))
usb4_port_unconfigure(down);
else
tb_lc_unconfigure_port(down);
}
/**
* tb_switch_add() - Add a switch to the domain
* @sw: Switch to add
@ -2399,6 +2502,13 @@ int tb_switch_add(struct tb_switch *sw)
return ret;
}
/*
* Thunderbolt routers do not generate wakeups themselves but
* they forward wakeups from tunneled protocols, so enable it
* here.
*/
device_init_wakeup(&sw->dev, true);
pm_runtime_set_active(&sw->dev);
if (sw->rpm) {
pm_runtime_set_autosuspend_delay(&sw->dev, TB_AUTOSUSPEND_DELAY);
@ -2408,6 +2518,7 @@ int tb_switch_add(struct tb_switch *sw)
pm_request_autosuspend(&sw->dev);
}
tb_switch_debugfs_init(sw);
return 0;
}
@ -2423,6 +2534,8 @@ void tb_switch_remove(struct tb_switch *sw)
{
struct tb_port *port;
tb_switch_debugfs_remove(sw);
if (sw->rpm) {
pm_runtime_get_sync(&sw->dev);
pm_runtime_disable(&sw->dev);
@ -2445,11 +2558,6 @@ void tb_switch_remove(struct tb_switch *sw)
if (!sw->is_unplugged)
tb_plug_events_active(sw, false);
if (tb_switch_is_usb4(sw))
usb4_switch_unconfigure_link(sw);
else
tb_lc_unconfigure_link(sw);
tb_switch_nvm_remove(sw);
if (tb_route(sw))
@ -2481,6 +2589,18 @@ void tb_sw_set_unplugged(struct tb_switch *sw)
}
}
static int tb_switch_set_wake(struct tb_switch *sw, unsigned int flags)
{
if (flags)
tb_sw_dbg(sw, "enabling wakeup: %#x\n", flags);
else
tb_sw_dbg(sw, "disabling wakeup\n");
if (tb_switch_is_usb4(sw))
return usb4_switch_set_wake(sw, flags);
return tb_lc_set_wake(sw, flags);
}
int tb_switch_resume(struct tb_switch *sw)
{
struct tb_port *port;
@ -2526,6 +2646,13 @@ int tb_switch_resume(struct tb_switch *sw)
if (err)
return err;
/* Disable wakes */
tb_switch_set_wake(sw, 0);
err = tb_switch_tmu_init(sw);
if (err)
return err;
/* check for surviving downstream switches */
tb_switch_for_each_port(sw, port) {
if (!tb_port_has_remote(port) && !port->xdomain)
@ -2555,20 +2682,43 @@ int tb_switch_resume(struct tb_switch *sw)
return 0;
}
void tb_switch_suspend(struct tb_switch *sw)
/**
* tb_switch_suspend() - Put a switch to sleep
* @sw: Switch to suspend
* @runtime: Is this runtime suspend or system sleep
*
* Suspends router and all its children. Enables wakes according to
* value of @runtime and then sets sleep bit for the router. If @sw is
* host router the domain is ready to go to sleep once this function
* returns.
*/
void tb_switch_suspend(struct tb_switch *sw, bool runtime)
{
unsigned int flags = 0;
struct tb_port *port;
int err;
tb_sw_dbg(sw, "suspending switch\n");
err = tb_plug_events_active(sw, false);
if (err)
return;
tb_switch_for_each_port(sw, port) {
if (tb_port_has_remote(port))
tb_switch_suspend(port->remote->sw);
tb_switch_suspend(port->remote->sw, runtime);
}
if (runtime) {
/* Trigger wake when something is plugged in/out */
flags |= TB_WAKE_ON_CONNECT | TB_WAKE_ON_DISCONNECT;
flags |= TB_WAKE_ON_USB4 | TB_WAKE_ON_USB3 | TB_WAKE_ON_PCIE;
} else if (device_may_wakeup(&sw->dev)) {
flags |= TB_WAKE_ON_USB4 | TB_WAKE_ON_USB3 | TB_WAKE_ON_PCIE;
}
tb_switch_set_wake(sw, flags);
if (tb_switch_is_usb4(sw))
usb4_switch_set_sleep(sw);
else

View File

@ -9,6 +9,7 @@
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/pm_runtime.h>
#include "tb.h"
#include "tb_regs.h"
@ -22,13 +23,21 @@
* events and exit if this is not set (it needs to
* acquire the lock one more time). Used to drain wq
* after cfg has been paused.
* @remove_work: Work used to remove any unplugged routers after
* runtime resume
*/
struct tb_cm {
struct list_head tunnel_list;
struct list_head dp_resources;
bool hotplug_active;
struct delayed_work remove_work;
};
static inline struct tb *tcm_to_tb(struct tb_cm *tcm)
{
return ((void *)tcm - sizeof(struct tb));
}
struct tb_hotplug_event {
struct work_struct work;
struct tb *tb;
@ -140,6 +149,29 @@ static void tb_discover_tunnels(struct tb_switch *sw)
}
}
static int tb_port_configure_xdomain(struct tb_port *port)
{
/*
* XDomain paths currently only support single lane so we must
* disable the other lane according to USB4 spec.
*/
tb_port_disable(port->dual_link_port);
if (tb_switch_is_usb4(port->sw))
return usb4_port_configure_xdomain(port);
return tb_lc_configure_xdomain(port);
}
static void tb_port_unconfigure_xdomain(struct tb_port *port)
{
if (tb_switch_is_usb4(port->sw))
usb4_port_unconfigure_xdomain(port);
else
tb_lc_unconfigure_xdomain(port);
tb_port_enable(port->dual_link_port);
}
static void tb_scan_xdomain(struct tb_port *port)
{
struct tb_switch *sw = port->sw;
@ -158,6 +190,7 @@ static void tb_scan_xdomain(struct tb_port *port)
NULL);
if (xd) {
tb_port_at(route, sw)->xdomain = xd;
tb_port_configure_xdomain(port);
tb_xdomain_add(xd);
}
}
@ -502,8 +535,13 @@ static void tb_scan_switch(struct tb_switch *sw)
{
struct tb_port *port;
pm_runtime_get_sync(&sw->dev);
tb_switch_for_each_port(sw, port)
tb_scan_port(port);
pm_runtime_mark_last_busy(&sw->dev);
pm_runtime_put_autosuspend(&sw->dev);
}
/**
@ -566,6 +604,7 @@ static void tb_scan_port(struct tb_port *port)
*/
if (port->xdomain) {
tb_xdomain_remove(port->xdomain);
tb_port_unconfigure_xdomain(port);
port->xdomain = NULL;
}
@ -577,6 +616,12 @@ static void tb_scan_port(struct tb_port *port)
if (!tcm->hotplug_active)
dev_set_uevent_suppress(&sw->dev, true);
/*
* At the moment Thunderbolt 2 and beyond (devices with LC) we
* can support runtime PM.
*/
sw->rpm = sw->generation > 1;
if (tb_switch_add(sw)) {
tb_switch_put(sw);
return;
@ -592,8 +637,9 @@ static void tb_scan_port(struct tb_port *port)
}
/* Enable lane bonding if supported */
if (tb_switch_lane_bonding_enable(sw))
tb_sw_warn(sw, "failed to enable lane bonding\n");
tb_switch_lane_bonding_enable(sw);
/* Set the link configured */
tb_switch_configure_link(sw);
if (tb_enable_tmu(sw))
tb_sw_warn(sw, "failed to enable TMU\n");
@ -636,6 +682,11 @@ static void tb_deactivate_and_free_tunnel(struct tb_tunnel *tunnel)
* deallocated properly.
*/
tb_switch_dealloc_dp_resource(src_port->sw, src_port);
/* Now we can allow the domain to runtime suspend again */
pm_runtime_mark_last_busy(&dst_port->sw->dev);
pm_runtime_put_autosuspend(&dst_port->sw->dev);
pm_runtime_mark_last_busy(&src_port->sw->dev);
pm_runtime_put_autosuspend(&src_port->sw->dev);
fallthrough;
case TB_TUNNEL_USB3:
@ -682,6 +733,7 @@ static void tb_free_unplugged_children(struct tb_switch *sw)
if (port->remote->sw->is_unplugged) {
tb_retimer_remove_all(port);
tb_remove_dp_resources(port->remote->sw);
tb_switch_unconfigure_link(port->remote->sw);
tb_switch_lane_bonding_disable(port->remote->sw);
tb_switch_remove(port->remote->sw);
port->remote = NULL;
@ -821,9 +873,20 @@ static void tb_tunnel_dp(struct tb *tb)
return;
}
/*
* DP stream needs the domain to be active so runtime resume
* both ends of the tunnel.
*
* This should bring the routers in the middle active as well
* and keeps the domain from runtime suspending while the DP
* tunnel is active.
*/
pm_runtime_get_sync(&in->sw->dev);
pm_runtime_get_sync(&out->sw->dev);
if (tb_switch_alloc_dp_resource(in->sw, in)) {
tb_port_dbg(in, "no resource available for DP IN, not tunneling\n");
return;
goto err_rpm_put;
}
/* Make all unused USB3 bandwidth available for the new DP tunnel */
@ -862,6 +925,11 @@ err_reclaim:
tb_reclaim_usb3_bandwidth(tb, in, out);
err_dealloc_dp:
tb_switch_dealloc_dp_resource(in->sw, in);
err_rpm_put:
pm_runtime_mark_last_busy(&out->sw->dev);
pm_runtime_put_autosuspend(&out->sw->dev);
pm_runtime_mark_last_busy(&in->sw->dev);
pm_runtime_put_autosuspend(&in->sw->dev);
}
static void tb_dp_resource_unavailable(struct tb *tb, struct tb_port *port)
@ -911,6 +979,29 @@ static void tb_dp_resource_available(struct tb *tb, struct tb_port *port)
tb_tunnel_dp(tb);
}
static void tb_disconnect_and_release_dp(struct tb *tb)
{
struct tb_cm *tcm = tb_priv(tb);
struct tb_tunnel *tunnel, *n;
/*
* Tear down all DP tunnels and release their resources. They
* will be re-established after resume based on plug events.
*/
list_for_each_entry_safe_reverse(tunnel, n, &tcm->tunnel_list, list) {
if (tb_tunnel_is_dp(tunnel))
tb_deactivate_and_free_tunnel(tunnel);
}
while (!list_empty(&tcm->dp_resources)) {
struct tb_port *port;
port = list_first_entry(&tcm->dp_resources,
struct tb_port, list);
list_del_init(&port->list);
}
}
static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw)
{
struct tb_port *up, *down, *port;
@ -1022,6 +1113,10 @@ static void tb_handle_hotplug(struct work_struct *work)
struct tb_cm *tcm = tb_priv(tb);
struct tb_switch *sw;
struct tb_port *port;
/* Bring the domain back from sleep if it was suspended */
pm_runtime_get_sync(&tb->dev);
mutex_lock(&tb->lock);
if (!tcm->hotplug_active)
goto out; /* during init, suspend or shutdown */
@ -1045,6 +1140,9 @@ static void tb_handle_hotplug(struct work_struct *work)
ev->route, ev->port, ev->unplug);
goto put_sw;
}
pm_runtime_get_sync(&sw->dev);
if (ev->unplug) {
tb_retimer_remove_all(port);
@ -1054,6 +1152,7 @@ static void tb_handle_hotplug(struct work_struct *work)
tb_free_invalid_tunnels(tb);
tb_remove_dp_resources(port->remote->sw);
tb_switch_tmu_disable(port->remote->sw);
tb_switch_unconfigure_link(port->remote->sw);
tb_switch_lane_bonding_disable(port->remote->sw);
tb_switch_remove(port->remote->sw);
port->remote = NULL;
@ -1077,6 +1176,7 @@ static void tb_handle_hotplug(struct work_struct *work)
port->xdomain = NULL;
__tb_disconnect_xdomain_paths(tb, xd);
tb_xdomain_put(xd);
tb_port_unconfigure_xdomain(port);
} else if (tb_port_is_dpout(port) || tb_port_is_dpin(port)) {
tb_dp_resource_unavailable(tb, port);
} else {
@ -1096,10 +1196,17 @@ static void tb_handle_hotplug(struct work_struct *work)
}
}
pm_runtime_mark_last_busy(&sw->dev);
pm_runtime_put_autosuspend(&sw->dev);
put_sw:
tb_switch_put(sw);
out:
mutex_unlock(&tb->lock);
pm_runtime_mark_last_busy(&tb->dev);
pm_runtime_put_autosuspend(&tb->dev);
kfree(ev);
}
@ -1135,6 +1242,7 @@ static void tb_stop(struct tb *tb)
struct tb_tunnel *tunnel;
struct tb_tunnel *n;
cancel_delayed_work(&tcm->remove_work);
/* tunnels are only present after everything has been initialized */
list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) {
/*
@ -1186,6 +1294,8 @@ static int tb_start(struct tb *tb)
* root switch.
*/
tb->root_switch->no_nvm_upgrade = true;
/* All USB4 routers support runtime PM */
tb->root_switch->rpm = tb_switch_is_usb4(tb->root_switch);
ret = tb_switch_configure(tb->root_switch);
if (ret) {
@ -1227,7 +1337,8 @@ static int tb_suspend_noirq(struct tb *tb)
struct tb_cm *tcm = tb_priv(tb);
tb_dbg(tb, "suspending...\n");
tb_switch_suspend(tb->root_switch);
tb_disconnect_and_release_dp(tb);
tb_switch_suspend(tb->root_switch, false);
tcm->hotplug_active = false; /* signal tb_handle_hotplug to quit */
tb_dbg(tb, "suspend finished\n");
@ -1238,17 +1349,25 @@ static void tb_restore_children(struct tb_switch *sw)
{
struct tb_port *port;
/* No need to restore if the router is already unplugged */
if (sw->is_unplugged)
return;
if (tb_enable_tmu(sw))
tb_sw_warn(sw, "failed to restore TMU configuration\n");
tb_switch_for_each_port(sw, port) {
if (!tb_port_has_remote(port))
if (!tb_port_has_remote(port) && !port->xdomain)
continue;
if (tb_switch_lane_bonding_enable(port->remote->sw))
dev_warn(&sw->dev, "failed to restore lane bonding\n");
if (port->remote) {
tb_switch_lane_bonding_enable(port->remote->sw);
tb_switch_configure_link(port->remote->sw);
tb_restore_children(port->remote->sw);
tb_restore_children(port->remote->sw);
} else if (port->xdomain) {
tb_port_configure_xdomain(port);
}
}
}
@ -1260,7 +1379,7 @@ static int tb_resume_noirq(struct tb *tb)
tb_dbg(tb, "resuming...\n");
/* remove any pci devices the firmware might have setup */
tb_switch_reset(tb, 0);
tb_switch_reset(tb->root_switch);
tb_switch_resume(tb->root_switch);
tb_free_invalid_tunnels(tb);
@ -1294,6 +1413,7 @@ static int tb_free_unplugged_xdomains(struct tb_switch *sw)
if (port->xdomain && port->xdomain->is_unplugged) {
tb_retimer_remove_all(port);
tb_xdomain_remove(port->xdomain);
tb_port_unconfigure_xdomain(port);
port->xdomain = NULL;
ret++;
} else if (port->remote) {
@ -1304,6 +1424,22 @@ static int tb_free_unplugged_xdomains(struct tb_switch *sw)
return ret;
}
static int tb_freeze_noirq(struct tb *tb)
{
struct tb_cm *tcm = tb_priv(tb);
tcm->hotplug_active = false;
return 0;
}
static int tb_thaw_noirq(struct tb *tb)
{
struct tb_cm *tcm = tb_priv(tb);
tcm->hotplug_active = true;
return 0;
}
static void tb_complete(struct tb *tb)
{
/*
@ -1317,12 +1453,64 @@ static void tb_complete(struct tb *tb)
mutex_unlock(&tb->lock);
}
static int tb_runtime_suspend(struct tb *tb)
{
struct tb_cm *tcm = tb_priv(tb);
mutex_lock(&tb->lock);
tb_switch_suspend(tb->root_switch, true);
tcm->hotplug_active = false;
mutex_unlock(&tb->lock);
return 0;
}
static void tb_remove_work(struct work_struct *work)
{
struct tb_cm *tcm = container_of(work, struct tb_cm, remove_work.work);
struct tb *tb = tcm_to_tb(tcm);
mutex_lock(&tb->lock);
if (tb->root_switch) {
tb_free_unplugged_children(tb->root_switch);
tb_free_unplugged_xdomains(tb->root_switch);
}
mutex_unlock(&tb->lock);
}
static int tb_runtime_resume(struct tb *tb)
{
struct tb_cm *tcm = tb_priv(tb);
struct tb_tunnel *tunnel, *n;
mutex_lock(&tb->lock);
tb_switch_resume(tb->root_switch);
tb_free_invalid_tunnels(tb);
tb_restore_children(tb->root_switch);
list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list)
tb_tunnel_restart(tunnel);
tcm->hotplug_active = true;
mutex_unlock(&tb->lock);
/*
* Schedule cleanup of any unplugged devices. Run this in a
* separate thread to avoid possible deadlock if the device
* removal runtime resumes the unplugged device.
*/
queue_delayed_work(tb->wq, &tcm->remove_work, msecs_to_jiffies(50));
return 0;
}
static const struct tb_cm_ops tb_cm_ops = {
.start = tb_start,
.stop = tb_stop,
.suspend_noirq = tb_suspend_noirq,
.resume_noirq = tb_resume_noirq,
.freeze_noirq = tb_freeze_noirq,
.thaw_noirq = tb_thaw_noirq,
.complete = tb_complete,
.runtime_suspend = tb_runtime_suspend,
.runtime_resume = tb_runtime_resume,
.handle_event = tb_handle_event,
.approve_switch = tb_tunnel_pci,
.approve_xdomain_paths = tb_approve_xdomain_paths,
@ -1344,6 +1532,7 @@ struct tb *tb_probe(struct tb_nhi *nhi)
tcm = tb_priv(tb);
INIT_LIST_HEAD(&tcm->tunnel_list);
INIT_LIST_HEAD(&tcm->dp_resources);
INIT_DELAYED_WORK(&tcm->remove_work, tb_remove_work);
return tb;
}

View File

@ -125,6 +125,7 @@ struct tb_switch_tmu {
* @rpm: The switch supports runtime PM
* @authorized: Whether the switch is authorized by user or policy
* @security_level: Switch supported security level
* @debugfs_dir: Pointer to the debugfs structure
* @key: Contains the key used to challenge the device or %NULL if not
* supported. Size of the key is %TB_SWITCH_KEY_SIZE.
* @connection_id: Connection ID used with ICM messaging
@ -166,6 +167,7 @@ struct tb_switch {
bool rpm;
unsigned int authorized;
enum tb_security_level security_level;
struct dentry *debugfs_dir;
u8 *key;
u8 connection_id;
u8 connection_key;
@ -333,6 +335,13 @@ struct tb_path {
*/
#define TB_PATH_MAX_HOPS (7 * 2)
/* Possible wake types */
#define TB_WAKE_ON_CONNECT BIT(0)
#define TB_WAKE_ON_DISCONNECT BIT(1)
#define TB_WAKE_ON_USB4 BIT(2)
#define TB_WAKE_ON_USB3 BIT(3)
#define TB_WAKE_ON_PCIE BIT(4)
/**
* struct tb_cm_ops - Connection manager specific operations vector
* @driver_ready: Called right after control channel is started. Used by
@ -342,6 +351,8 @@ struct tb_path {
* @suspend_noirq: Connection manager specific suspend_noirq
* @resume_noirq: Connection manager specific resume_noirq
* @suspend: Connection manager specific suspend
* @freeze_noirq: Connection manager specific freeze_noirq
* @thaw_noirq: Connection manager specific thaw_noirq
* @complete: Connection manager specific complete
* @runtime_suspend: Connection manager specific runtime_suspend
* @runtime_resume: Connection manager specific runtime_resume
@ -364,6 +375,8 @@ struct tb_cm_ops {
int (*suspend_noirq)(struct tb *tb);
int (*resume_noirq)(struct tb *tb);
int (*suspend)(struct tb *tb);
int (*freeze_noirq)(struct tb *tb);
int (*thaw_noirq)(struct tb *tb);
void (*complete)(struct tb *tb);
int (*runtime_suspend)(struct tb *tb);
int (*runtime_resume)(struct tb *tb);
@ -457,6 +470,11 @@ static inline bool tb_port_is_null(const struct tb_port *port)
return port && port->port && port->config.type == TB_TYPE_PORT;
}
static inline bool tb_port_is_nhi(const struct tb_port *port)
{
return port && port->config.type == TB_TYPE_NHI;
}
static inline bool tb_port_is_pcie_down(const struct tb_port *port)
{
return port && port->config.type == TB_TYPE_PCIE_DOWN;
@ -593,6 +611,8 @@ void tb_domain_remove(struct tb *tb);
int tb_domain_suspend_noirq(struct tb *tb);
int tb_domain_resume_noirq(struct tb *tb);
int tb_domain_suspend(struct tb *tb);
int tb_domain_freeze_noirq(struct tb *tb);
int tb_domain_thaw_noirq(struct tb *tb);
void tb_domain_complete(struct tb *tb);
int tb_domain_runtime_suspend(struct tb *tb);
int tb_domain_runtime_resume(struct tb *tb);
@ -632,9 +652,9 @@ struct tb_switch *tb_switch_alloc_safe_mode(struct tb *tb,
int tb_switch_configure(struct tb_switch *sw);
int tb_switch_add(struct tb_switch *sw);
void tb_switch_remove(struct tb_switch *sw);
void tb_switch_suspend(struct tb_switch *sw);
void tb_switch_suspend(struct tb_switch *sw, bool runtime);
int tb_switch_resume(struct tb_switch *sw);
int tb_switch_reset(struct tb *tb, u64 route);
int tb_switch_reset(struct tb_switch *sw);
void tb_sw_set_unplugged(struct tb_switch *sw);
struct tb_port *tb_switch_find_port(struct tb_switch *sw,
enum tb_port_type type);
@ -685,59 +705,89 @@ static inline struct tb_switch *tb_switch_parent(struct tb_switch *sw)
static inline bool tb_switch_is_light_ridge(const struct tb_switch *sw)
{
return sw->config.device_id == PCI_DEVICE_ID_INTEL_LIGHT_RIDGE;
return sw->config.vendor_id == PCI_VENDOR_ID_INTEL &&
sw->config.device_id == PCI_DEVICE_ID_INTEL_LIGHT_RIDGE;
}
static inline bool tb_switch_is_eagle_ridge(const struct tb_switch *sw)
{
return sw->config.device_id == PCI_DEVICE_ID_INTEL_EAGLE_RIDGE;
return sw->config.vendor_id == PCI_VENDOR_ID_INTEL &&
sw->config.device_id == PCI_DEVICE_ID_INTEL_EAGLE_RIDGE;
}
static inline bool tb_switch_is_cactus_ridge(const struct tb_switch *sw)
{
switch (sw->config.device_id) {
case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_2C:
case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C:
return true;
default:
return false;
if (sw->config.vendor_id == PCI_VENDOR_ID_INTEL) {
switch (sw->config.device_id) {
case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_2C:
case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C:
return true;
}
}
return false;
}
static inline bool tb_switch_is_falcon_ridge(const struct tb_switch *sw)
{
switch (sw->config.device_id) {
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_BRIDGE:
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_BRIDGE:
return true;
default:
return false;
if (sw->config.vendor_id == PCI_VENDOR_ID_INTEL) {
switch (sw->config.device_id) {
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_BRIDGE:
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_BRIDGE:
return true;
}
}
return false;
}
static inline bool tb_switch_is_alpine_ridge(const struct tb_switch *sw)
{
switch (sw->config.device_id) {
case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_BRIDGE:
case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_BRIDGE:
case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_BRIDGE:
case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_BRIDGE:
return true;
default:
return false;
if (sw->config.vendor_id == PCI_VENDOR_ID_INTEL) {
switch (sw->config.device_id) {
case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_BRIDGE:
case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_BRIDGE:
case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_BRIDGE:
case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_BRIDGE:
return true;
}
}
return false;
}
static inline bool tb_switch_is_titan_ridge(const struct tb_switch *sw)
{
switch (sw->config.device_id) {
case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_BRIDGE:
case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_BRIDGE:
case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_DD_BRIDGE:
return true;
default:
return false;
if (sw->config.vendor_id == PCI_VENDOR_ID_INTEL) {
switch (sw->config.device_id) {
case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_BRIDGE:
case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_BRIDGE:
case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_DD_BRIDGE:
return true;
}
}
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:
return true;
}
}
return false;
}
/**
@ -767,6 +817,8 @@ static inline bool tb_switch_is_icm(const struct tb_switch *sw)
int tb_switch_lane_bonding_enable(struct tb_switch *sw);
void tb_switch_lane_bonding_disable(struct tb_switch *sw);
int tb_switch_configure_link(struct tb_switch *sw);
void tb_switch_unconfigure_link(struct tb_switch *sw);
bool tb_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in);
int tb_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in);
@ -788,6 +840,8 @@ 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);
int tb_port_disable(struct tb_port *port);
int tb_port_alloc_in_hopid(struct tb_port *port, int hopid, int max_hopid);
void tb_port_release_in_hopid(struct tb_port *port, int hopid);
int tb_port_alloc_out_hopid(struct tb_port *port, int hopid, int max_hopid);
@ -811,7 +865,9 @@ int tb_port_get_link_speed(struct tb_port *port);
int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec);
int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap);
int tb_switch_next_cap(struct tb_switch *sw, unsigned int offset);
int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap);
int tb_port_next_cap(struct tb_port *port, unsigned int offset);
bool tb_port_is_enabled(struct tb_port *port);
bool tb_usb3_port_is_enabled(struct tb_port *port);
@ -844,8 +900,11 @@ int tb_drom_read(struct tb_switch *sw);
int tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid);
int tb_lc_read_uuid(struct tb_switch *sw, u32 *uuid);
int tb_lc_configure_link(struct tb_switch *sw);
void tb_lc_unconfigure_link(struct tb_switch *sw);
int tb_lc_configure_port(struct tb_port *port);
void tb_lc_unconfigure_port(struct tb_port *port);
int tb_lc_configure_xdomain(struct tb_port *port);
void tb_lc_unconfigure_xdomain(struct tb_port *port);
int tb_lc_set_wake(struct tb_switch *sw, unsigned int flags);
int tb_lc_set_sleep(struct tb_switch *sw);
bool tb_lc_lane_bonding_possible(struct tb_switch *sw);
bool tb_lc_dp_sink_query(struct tb_switch *sw, struct tb_port *in);
@ -900,9 +959,8 @@ int usb4_switch_setup(struct tb_switch *sw);
int usb4_switch_read_uid(struct tb_switch *sw, u64 *uid);
int usb4_switch_drom_read(struct tb_switch *sw, unsigned int address, void *buf,
size_t size);
int usb4_switch_configure_link(struct tb_switch *sw);
void usb4_switch_unconfigure_link(struct tb_switch *sw);
bool usb4_switch_lane_bonding_possible(struct tb_switch *sw);
int usb4_switch_set_wake(struct tb_switch *sw, unsigned int flags);
int usb4_switch_set_sleep(struct tb_switch *sw);
int usb4_switch_nvm_sector_size(struct tb_switch *sw);
int usb4_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf,
@ -919,6 +977,10 @@ struct tb_port *usb4_switch_map_usb3_down(struct tb_switch *sw,
const struct tb_port *port);
int usb4_port_unlock(struct tb_port *port);
int usb4_port_configure(struct tb_port *port);
void usb4_port_unconfigure(struct tb_port *port);
int usb4_port_configure_xdomain(struct tb_port *port);
void usb4_port_unconfigure_xdomain(struct tb_port *port);
int usb4_port_enumerate_retimers(struct tb_port *port);
int usb4_port_retimer_read(struct tb_port *port, u8 index, u8 reg, void *buf,
@ -945,9 +1007,35 @@ int usb4_usb3_port_allocate_bandwidth(struct tb_port *port, int *upstream_bw,
int usb4_usb3_port_release_bandwidth(struct tb_port *port, int *upstream_bw,
int *downstream_bw);
/* keep link controller awake during update */
/* Keep link controller awake during update */
#define QUIRK_FORCE_POWER_LINK_CONTROLLER BIT(0)
void tb_check_quirks(struct tb_switch *sw);
#ifdef CONFIG_ACPI
void tb_acpi_add_links(struct tb_nhi *nhi);
#else
static inline void tb_acpi_add_links(struct tb_nhi *nhi) { }
#endif
#ifdef CONFIG_DEBUG_FS
void tb_debugfs_init(void);
void tb_debugfs_exit(void);
void tb_switch_debugfs_init(struct tb_switch *sw);
void tb_switch_debugfs_remove(struct tb_switch *sw);
#else
static inline void tb_debugfs_init(void) { }
static inline void tb_debugfs_exit(void) { }
static inline void tb_switch_debugfs_init(struct tb_switch *sw) { }
static inline void tb_switch_debugfs_remove(struct tb_switch *sw) { }
#endif
#ifdef CONFIG_USB4_KUNIT_TEST
int tb_test_init(void);
void tb_test_exit(void);
#else
static inline int tb_test_init(void) { return 0; }
static inline void tb_test_exit(void) { }
#endif
#endif

View File

@ -28,6 +28,7 @@ enum tb_cfg_error {
TB_CFG_ERROR_LOOP = 8,
TB_CFG_ERROR_HEC_ERROR_DETECTED = 12,
TB_CFG_ERROR_FLOW_CONTROL_ERROR = 13,
TB_CFG_ERROR_LOCK = 15,
};
/* common header */

View File

@ -39,6 +39,7 @@ enum tb_switch_vse_cap {
enum tb_port_cap {
TB_PORT_CAP_PHY = 0x01,
TB_PORT_CAP_POWER = 0x02,
TB_PORT_CAP_TIME1 = 0x03,
TB_PORT_CAP_ADAP = 0x04,
TB_PORT_CAP_VSE = 0x05,
@ -93,6 +94,20 @@ struct tb_cap_extended_long {
u16 length;
} __packed;
/**
* struct tb_cap_any - Structure capable of hold every capability
* @basic: Basic capability
* @extended_short: Vendor specific capability
* @extended_long: Vendor specific extended capability
*/
struct tb_cap_any {
union {
struct tb_cap_basic basic;
struct tb_cap_extended_short extended_short;
struct tb_cap_extended_long extended_long;
};
} __packed;
/* capabilities */
struct tb_cap_link_controller {
@ -178,6 +193,8 @@ struct tb_regs_switch_header {
#define ROUTER_CS_4 0x04
#define ROUTER_CS_5 0x05
#define ROUTER_CS_5_SLP BIT(0)
#define ROUTER_CS_5_WOP BIT(1)
#define ROUTER_CS_5_WOU BIT(2)
#define ROUTER_CS_5_C3S BIT(23)
#define ROUTER_CS_5_PTO BIT(24)
#define ROUTER_CS_5_UTO BIT(25)
@ -186,6 +203,8 @@ struct tb_regs_switch_header {
#define ROUTER_CS_6 0x06
#define ROUTER_CS_6_SLPR BIT(0)
#define ROUTER_CS_6_TNS BIT(1)
#define ROUTER_CS_6_WOPS BIT(2)
#define ROUTER_CS_6_WOUS BIT(3)
#define ROUTER_CS_6_HCI BIT(18)
#define ROUTER_CS_6_CR BIT(25)
#define ROUTER_CS_7 0x07
@ -234,7 +253,8 @@ struct tb_regs_port_header {
/* DWORD 1 */
u32 first_cap_offset:8;
u32 max_counters:11;
u32 __unknown1:5;
u32 counters_support:1;
u32 __unknown1:4;
u32 revision:8;
/* DWORD 2 */
enum tb_port_type type:24;
@ -279,6 +299,7 @@ struct tb_regs_port_header {
#define LANE_ADP_CS_1_TARGET_WIDTH_SHIFT 4
#define LANE_ADP_CS_1_TARGET_WIDTH_SINGLE 0x1
#define LANE_ADP_CS_1_TARGET_WIDTH_DUAL 0x3
#define LANE_ADP_CS_1_LD BIT(14)
#define LANE_ADP_CS_1_LB BIT(15)
#define LANE_ADP_CS_1_CURRENT_SPEED_MASK GENMASK(19, 16)
#define LANE_ADP_CS_1_CURRENT_SPEED_SHIFT 16
@ -301,8 +322,13 @@ struct tb_regs_port_header {
#define PORT_CS_18 0x12
#define PORT_CS_18_BE BIT(8)
#define PORT_CS_18_TCM BIT(9)
#define PORT_CS_18_WOU4S BIT(18)
#define PORT_CS_19 0x13
#define PORT_CS_19_PC BIT(3)
#define PORT_CS_19_PID BIT(4)
#define PORT_CS_19_WOC BIT(16)
#define PORT_CS_19_WOD BIT(17)
#define PORT_CS_19_WOU4 BIT(18)
/* Display Port adapter registers */
#define ADP_DP_CS_0 0x00
@ -416,8 +442,14 @@ struct tb_regs_hop {
#define TB_LC_PORT_ATTR_BE BIT(12)
#define TB_LC_SX_CTRL 0x96
#define TB_LC_SX_CTRL_WOC BIT(1)
#define TB_LC_SX_CTRL_WOD BIT(2)
#define TB_LC_SX_CTRL_WOU4 BIT(5)
#define TB_LC_SX_CTRL_WOP BIT(6)
#define TB_LC_SX_CTRL_L1C BIT(16)
#define TB_LC_SX_CTRL_L1D BIT(17)
#define TB_LC_SX_CTRL_L2C BIT(20)
#define TB_LC_SX_CTRL_L2D BIT(21)
#define TB_LC_SX_CTRL_UPSTREAM BIT(30)
#define TB_LC_SX_CTRL_SLP BIT(31)

View File

@ -1623,4 +1623,15 @@ static struct kunit_suite tb_test_suite = {
.name = "thunderbolt",
.test_cases = tb_test_cases,
};
kunit_test_suite(tb_test_suite);
static struct kunit_suite *tb_test_suites[] = { &tb_test_suite, NULL };
int tb_test_init(void)
{
return __kunit_test_suites_init(tb_test_suites);
}
void tb_test_exit(void)
{
return __kunit_test_suites_exit(tb_test_suites);
}

View File

@ -196,6 +196,46 @@ static int usb4_switch_op(struct tb_switch *sw, u16 opcode, u8 *status)
return 0;
}
static void usb4_switch_check_wakes(struct tb_switch *sw)
{
struct tb_port *port;
bool wakeup = false;
u32 val;
if (!device_may_wakeup(&sw->dev))
return;
if (tb_route(sw)) {
if (tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_6, 1))
return;
tb_sw_dbg(sw, "PCIe wake: %s, USB3 wake: %s\n",
(val & ROUTER_CS_6_WOPS) ? "yes" : "no",
(val & ROUTER_CS_6_WOUS) ? "yes" : "no");
wakeup = val & (ROUTER_CS_6_WOPS | ROUTER_CS_6_WOUS);
}
/* Check for any connected downstream ports for USB4 wake */
tb_switch_for_each_port(sw, port) {
if (!tb_port_has_remote(port))
continue;
if (tb_port_read(port, &val, TB_CFG_PORT,
port->cap_usb4 + PORT_CS_18, 1))
break;
tb_port_dbg(port, "USB4 wake: %s\n",
(val & PORT_CS_18_WOU4S) ? "yes" : "no");
if (val & PORT_CS_18_WOU4S)
wakeup = true;
}
if (wakeup)
pm_wakeup_event(&sw->dev, 0);
}
static bool link_is_usb4(struct tb_port *port)
{
u32 val;
@ -229,6 +269,8 @@ int usb4_switch_setup(struct tb_switch *sw)
u32 val = 0;
int ret;
usb4_switch_check_wakes(sw);
if (!tb_route(sw))
return 0;
@ -338,60 +380,6 @@ int usb4_switch_drom_read(struct tb_switch *sw, unsigned int address, void *buf,
usb4_switch_drom_read_block, sw);
}
static int usb4_set_port_configured(struct tb_port *port, bool configured)
{
int ret;
u32 val;
ret = tb_port_read(port, &val, TB_CFG_PORT,
port->cap_usb4 + PORT_CS_19, 1);
if (ret)
return ret;
if (configured)
val |= PORT_CS_19_PC;
else
val &= ~PORT_CS_19_PC;
return tb_port_write(port, &val, TB_CFG_PORT,
port->cap_usb4 + PORT_CS_19, 1);
}
/**
* usb4_switch_configure_link() - Set upstream USB4 link configured
* @sw: USB4 router
*
* Sets the upstream USB4 link to be configured for power management
* purposes.
*/
int usb4_switch_configure_link(struct tb_switch *sw)
{
struct tb_port *up;
if (!tb_route(sw))
return 0;
up = tb_upstream_port(sw);
return usb4_set_port_configured(up, true);
}
/**
* usb4_switch_unconfigure_link() - Un-set upstream USB4 link configuration
* @sw: USB4 router
*
* Reverse of usb4_switch_configure_link().
*/
void usb4_switch_unconfigure_link(struct tb_switch *sw)
{
struct tb_port *up;
if (sw->is_unplugged || !tb_route(sw))
return;
up = tb_upstream_port(sw);
usb4_set_port_configured(up, false);
}
/**
* usb4_switch_lane_bonding_possible() - Are conditions met for lane bonding
* @sw: USB4 router
@ -413,12 +401,78 @@ bool usb4_switch_lane_bonding_possible(struct tb_switch *sw)
return !!(val & PORT_CS_18_BE);
}
/**
* usb4_switch_set_wake() - Enabled/disable wake
* @sw: USB4 router
* @flags: Wakeup flags (%0 to disable)
*
* Enables/disables router to wake up from sleep.
*/
int usb4_switch_set_wake(struct tb_switch *sw, unsigned int flags)
{
struct tb_port *port;
u64 route = tb_route(sw);
u32 val;
int ret;
/*
* Enable wakes coming from all USB4 downstream ports (from
* child routers). For device routers do this also for the
* upstream USB4 port.
*/
tb_switch_for_each_port(sw, port) {
if (!route && tb_is_upstream_port(port))
continue;
ret = tb_port_read(port, &val, TB_CFG_PORT,
port->cap_usb4 + PORT_CS_19, 1);
if (ret)
return ret;
val &= ~(PORT_CS_19_WOC | PORT_CS_19_WOD | PORT_CS_19_WOU4);
if (flags & TB_WAKE_ON_CONNECT)
val |= PORT_CS_19_WOC;
if (flags & TB_WAKE_ON_DISCONNECT)
val |= PORT_CS_19_WOD;
if (flags & TB_WAKE_ON_USB4)
val |= PORT_CS_19_WOU4;
ret = tb_port_write(port, &val, TB_CFG_PORT,
port->cap_usb4 + PORT_CS_19, 1);
if (ret)
return ret;
}
/*
* Enable wakes from PCIe and USB 3.x on this router. Only
* needed for device routers.
*/
if (route) {
ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
if (ret)
return ret;
val &= ~(ROUTER_CS_5_WOP | ROUTER_CS_5_WOU);
if (flags & TB_WAKE_ON_USB3)
val |= ROUTER_CS_5_WOU;
if (flags & TB_WAKE_ON_PCIE)
val |= ROUTER_CS_5_WOP;
ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
if (ret)
return ret;
}
return 0;
}
/**
* usb4_switch_set_sleep() - Prepare the router to enter sleep
* @sw: USB4 router
*
* Enables wakes and sets sleep bit for the router. Returns when the
* router sleep ready bit has been asserted.
* Sets sleep bit for the router. Returns when the router sleep ready
* bit has been asserted.
*/
int usb4_switch_set_sleep(struct tb_switch *sw)
{
@ -795,6 +849,95 @@ int usb4_port_unlock(struct tb_port *port)
return tb_port_write(port, &val, TB_CFG_PORT, ADP_CS_4, 1);
}
static int usb4_port_set_configured(struct tb_port *port, bool configured)
{
int ret;
u32 val;
if (!port->cap_usb4)
return -EINVAL;
ret = tb_port_read(port, &val, TB_CFG_PORT,
port->cap_usb4 + PORT_CS_19, 1);
if (ret)
return ret;
if (configured)
val |= PORT_CS_19_PC;
else
val &= ~PORT_CS_19_PC;
return tb_port_write(port, &val, TB_CFG_PORT,
port->cap_usb4 + PORT_CS_19, 1);
}
/**
* usb4_port_configure() - Set USB4 port configured
* @port: USB4 router
*
* Sets the USB4 link to be configured for power management purposes.
*/
int usb4_port_configure(struct tb_port *port)
{
return usb4_port_set_configured(port, true);
}
/**
* usb4_port_unconfigure() - Set USB4 port unconfigured
* @port: USB4 router
*
* Sets the USB4 link to be unconfigured for power management purposes.
*/
void usb4_port_unconfigure(struct tb_port *port)
{
usb4_port_set_configured(port, false);
}
static int usb4_set_xdomain_configured(struct tb_port *port, bool configured)
{
int ret;
u32 val;
if (!port->cap_usb4)
return -EINVAL;
ret = tb_port_read(port, &val, TB_CFG_PORT,
port->cap_usb4 + PORT_CS_19, 1);
if (ret)
return ret;
if (configured)
val |= PORT_CS_19_PID;
else
val &= ~PORT_CS_19_PID;
return tb_port_write(port, &val, TB_CFG_PORT,
port->cap_usb4 + PORT_CS_19, 1);
}
/**
* usb4_port_configure_xdomain() - Configure port for XDomain
* @port: USB4 port connected to another host
*
* Marks the USB4 port as being connected to another host. Returns %0 in
* success and negative errno in failure.
*/
int usb4_port_configure_xdomain(struct tb_port *port)
{
return usb4_set_xdomain_configured(port, true);
}
/**
* usb4_port_unconfigure_xdomain() - Unconfigure port for XDomain
* @port: USB4 port that was connected to another host
*
* Clears USB4 port from being marked as XDomain.
*/
void usb4_port_unconfigure_xdomain(struct tb_port *port)
{
usb4_set_xdomain_configured(port, false);
}
static int usb4_port_wait_for_bit(struct tb_port *port, u32 offset, u32 bit,
u32 value, int timeout_msec)
{

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