2
0
mirror of https://github.com/edk2-porting/linux-next.git synced 2024-12-23 12:43:55 +08:00

phy: for 5.2

*) Add a new *release* phy_ops invoked when the consumer relinquishes PHY
      that can be used to undo the operation performed in xlate
   *) Add new driver to support USB2 PHY and shared USB3 + PCIE PHY in Amlogic
      G12A SoC Family.
   *) Add new driver to support for Broadcom's Stingray USB PHY (Type 1 has
      one super speed PHY and one high speed PHY, Type 2 has one high speed PHY)
   *) Add new driver to support USB PHY in hi3660 SoC of Hisilicon
   *) Add new driver to support UFS M-PHY in MediaTek SoC
   *) Add new driver to support XUSB pad controller in Tegra186 SoCs
   *) Add new driver to support SERDES in TI's AM654 platform
   *) Add support for generation 2 USB2 PHY and gneration 3 USB2 PHY in r8a77470
      to phy-rcar-gen2.c and phy-rcar-gen3-usb2.c respectively
   *) Add support for PCIe QMP PHY support in msm8998 to phy-qcom-qmp.c
   *) Add support for SERDES6G in phy-ocelot-serdes.c
   *) Add support to set drive impedance from device tree in phy-rockchip-emmc.c
   *) Add support to power up/down the VBUS voltage rail in phy-fsl-imx8mq-usb.c
   *) Add support to shut off regulators that power UFS during system suspend
   *) Re-design phy-rcar-gen3-usb2.c to create separate PHY instances for each
      channel which helps to enable/disable interrupts for each instance
      independently
   *) Fix PCIe power up sequence to follow the TRM in order to ensure the DPLL &
      PHY operates correctly over the entire temperature range.
   *) Use devm_clk_get_optional to get optional clocks instead of adding
      custom error checks
 
 Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
 -----BEGIN PGP SIGNATURE-----
 
 iQJCBAABCgAsFiEEUXMr/TfP2p4suIY5Dlx4XIBNgtkFAly+xBgOHGtpc2hvbkB0
 aS5jb20ACgkQDlx4XIBNgtn4QQ/+LwamWUGecuhxeq2P7FmXPwIaRnboRKkqGsuv
 /sQI1QWWx1R8iAhbqtefpmaCfRnMnZwBkwQG71NU8GMHUylz+WhgrD6NW5fkBTS+
 CM7GGEtyPyyZYYmR0A7CQbdvOlOD5huNWXXFwleXGfZj0AfehFI5GBebWXd36Pzf
 g26+q9TZEkTiHuJP/1Xqm87PVpdOpEJLpctfYome1TRBiM5U/63QXOfuFO1LcD3F
 VdKU/Ls6dWbZElGQynDQpYtQK7YgyjOrrK2dWKCxzLC90IDPGQmbq+hTDzC30YsM
 6ePxeoIp3ojla3HGML2uJE9LdQQHsiz6+m64+K3+3uG9Mls04vU2Cp817vFKRkry
 WNz8obkMzf8h6/smtWTpTXADuFf+sMXJW3d77TxjdwfsnzqjwH3kgVv4HACHtYDf
 dv89aA3V+z+oW67MqFBydFq7FDOFF9emlGB/ACiWHor98InAM0N/qTFW91NAWNdo
 HhZHiJd6Vv4ik+V4ajHYcHdfFeXawmsRQNsfNRlABTM0sXFwCvAiPUUU0CaYferd
 FBrRZilO9XxZf6ybGPvAJiycvtJ3qUJuSVdyOLXckOxpN2L0Zb9CTOrbYDuLuOeE
 tI81IPkzv0yaHbe0XCssd1aIbhgnoWOM+keLD0rh9+mCWDR6zyD1Kw7WRAfNu2PS
 HtTZTso=
 =BAzr
 -----END PGP SIGNATURE-----

Merge tag 'phy-for-5.2' of git://git.kernel.org/pub/scm/linux/kernel/git/kishon/linux-phy into usb-next

Kishon writes:

phy: for 5.2

  *) Add a new *release* phy_ops invoked when the consumer relinquishes PHY
     that can be used to undo the operation performed in xlate
  *) Add new driver to support USB2 PHY and shared USB3 + PCIE PHY in Amlogic
     G12A SoC Family.
  *) Add new driver to support for Broadcom's Stingray USB PHY (Type 1 has
     one super speed PHY and one high speed PHY, Type 2 has one high speed PHY)
  *) Add new driver to support USB PHY in hi3660 SoC of Hisilicon
  *) Add new driver to support UFS M-PHY in MediaTek SoC
  *) Add new driver to support XUSB pad controller in Tegra186 SoCs
  *) Add new driver to support SERDES in TI's AM654 platform
  *) Add support for generation 2 USB2 PHY and gneration 3 USB2 PHY in r8a77470
     to phy-rcar-gen2.c and phy-rcar-gen3-usb2.c respectively
  *) Add support for PCIe QMP PHY support in msm8998 to phy-qcom-qmp.c
  *) Add support for SERDES6G in phy-ocelot-serdes.c
  *) Add support to set drive impedance from device tree in phy-rockchip-emmc.c
  *) Add support to power up/down the VBUS voltage rail in phy-fsl-imx8mq-usb.c
  *) Add support to shut off regulators that power UFS during system suspend
  *) Re-design phy-rcar-gen3-usb2.c to create separate PHY instances for each
     channel which helps to enable/disable interrupts for each instance
     independently
  *) Fix PCIe power up sequence to follow the TRM in order to ensure the DPLL &
     PHY operates correctly over the entire temperature range.
  *) Use devm_clk_get_optional to get optional clocks instead of adding
     custom error checks

Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>

* tag 'phy-for-5.2' of git://git.kernel.org/pub/scm/linux/kernel/git/kishon/linux-phy: (51 commits)
  dt-bindings: phy-qcom-qmp: Tweak qcom,msm8998-qmp-ufs-phy
  dt-bindings: phy-qcom-qmp: Add qcom,msm8998-qmp-pcie-phy
  phy: Add usb phy support for hi3660 Soc of Hisilicon
  dt-bindings: phy: Add support for HiSilicon's hi3660 USB PHY
  scsi: phy: mediatek: fix typo in author's email address
  phy: ocelot-serdes: Add support for SERDES6G muxing
  phy: fsl-imx8mq-usb: add support for VBUS power control
  dt-bindings: phy-imx8mq-usb: add optional vbus supply regulator
  phy: qcom-qmp: Add msm8998 PCIe QMP PHY support
  phy: ti: am654-serdes: Support all clksel values
  phy: ti: Add a new SERDES driver for TI's AM654x SoC
  dt-bindings: phy: ti: Add dt binding documentation for SERDES in AM654x SoC
  phy: core: Invoke pm_runtime_get_*/pm_runtime_put_* before invoking reset callback
  phy: core: Add *release* phy_ops invoked when the consumer relinquishes PHY
  phy: phy-meson-gxl-usb2: get optional clock by devm_clk_get_optional()
  phy: socionext: get optional clock by devm_clk_get_optional()
  phy: qcom-qusb2: get optional clock by devm_clk_get_optional()
  phy: phy-mtk-tphy: get optional clock by devm_clk_get_optional()
  phy: renesas: rcar-gen3-usb2: enable/disable independent irqs
  phy: renesas: rcar-gen3-usb2: Use pdev's device pointer on dev_vdbg()
  ...
This commit is contained in:
Greg Kroah-Hartman 2019-04-25 10:49:34 +02:00
commit d30e413fa4
59 changed files with 4884 additions and 362 deletions

View File

@ -0,0 +1,32 @@
Broadcom Stingray USB PHY
Required properties:
- compatible : should be one of the listed compatibles
- "brcm,sr-usb-combo-phy" is combo PHY has two PHYs, one SS and one HS.
- "brcm,sr-usb-hs-phy" is a single HS PHY.
- reg: offset and length of the PHY blocks registers
- #phy-cells:
- Must be 1 for brcm,sr-usb-combo-phy as it expects one argument to indicate
the PHY number of two PHYs. 0 for HS PHY and 1 for SS PHY.
- Must be 0 for brcm,sr-usb-hs-phy.
Refer to phy/phy-bindings.txt for the generic PHY binding properties
Example:
usbphy0: usb-phy@0 {
compatible = "brcm,sr-usb-combo-phy";
reg = <0x00000000 0x100>;
#phy-cells = <1>;
};
usbphy1: usb-phy@10000 {
compatible = "brcm,sr-usb-combo-phy";
reg = <0x00010000 0x100>,
#phy-cells = <1>;
};
usbphy2: usb-phy@20000 {
compatible = "brcm,sr-usb-hs-phy";
reg = <0x00020000 0x100>,
#phy-cells = <0>;
};

View File

@ -7,6 +7,9 @@ Required properties:
- clocks: phandles to the clocks for each clock listed in clock-names
- clock-names: must contain "phy"
Optional properties:
- vbus-supply: A phandle to the regulator for USB VBUS.
Example:
usb3_phy0: phy@381f0040 {
compatible = "fsl,imx8mq-usb-phy";

View File

@ -0,0 +1,22 @@
* Amlogic G12A USB2 PHY binding
Required properties:
- compatible: Should be "amlogic,meson-g12a-usb2-phy"
- reg: The base address and length of the registers
- #phys-cells: must be 0 (see phy-bindings.txt in this directory)
- clocks: a phandle to the clock of this PHY
- clock-names: must be "xtal"
- resets: a phandle to the reset line of this PHY
- reset-names: must be "phy"
- phy-supply: see phy-bindings.txt in this directory
Example:
usb2_phy0: phy@36000 {
compatible = "amlogic,g12a-usb2-phy";
reg = <0x0 0x36000 0x0 0x2000>;
clocks = <&xtal>;
clock-names = "xtal";
resets = <&reset RESET_USB_PHY21>;
reset-names = "phy";
#phy-cells = <0>;
};

View File

@ -0,0 +1,22 @@
* Amlogic G12A USB3 + PCIE Combo PHY binding
Required properties:
- compatible: Should be "amlogic,meson-g12a-usb3-pcie-phy"
- #phys-cells: must be 1. The cell number is used to select the phy mode
as defined in <dt-bindings/phy/phy.h> between PHY_TYPE_USB3 and PHY_TYPE_PCIE
- reg: The base address and length of the registers
- clocks: a phandle to the 100MHz reference clock of this PHY
- clock-names: must be "ref_clk"
- resets: phandle to the reset lines for the PHY control
- reset-names: must be "phy"
Example:
usb3_pcie_phy: phy@46000 {
compatible = "amlogic,g12a-usb3-pcie-phy";
reg = <0x0 0x46000 0x0 0x2000>;
clocks = <&clkc CLKID_PCIE_PLL>;
clock-names = "ref_clk";
resets = <&reset RESET_PCIE_PHY>;
reset-names = "phy";
#phy-cells = <1>;
};

View File

@ -36,11 +36,20 @@ Required properties:
- Tegra124: "nvidia,tegra124-xusb-padctl"
- Tegra132: "nvidia,tegra132-xusb-padctl", "nvidia,tegra124-xusb-padctl"
- Tegra210: "nvidia,tegra210-xusb-padctl"
- Tegra186: "nvidia,tegra186-xusb-padctl"
- reg: Physical base address and length of the controller's registers.
- resets: Must contain an entry for each entry in reset-names.
- reset-names: Must include the following entries:
- "padctl"
For Tegra186:
- avdd-pll-erefeut-supply: UPHY brick and reference clock as well as UTMI PHY
power supply. Must supply 1.8 V.
- avdd-usb-supply: USB I/Os, VBUS, ID, REXT, D+/D- power supply. Must supply
3.3 V.
- vclamp-usb-supply: Bias rail for USB pad. Must supply 1.8 V.
- vddio-hsic-supply: HSIC PHY power supply. Must supply 1.2 V.
Pad nodes:
==========

View File

@ -0,0 +1,26 @@
Hisilicon hi3660 USB PHY
-----------------------
Required properties:
- compatible: should be "hisilicon,hi3660-usb-phy"
- #phy-cells: must be 0
- hisilicon,pericrg-syscon: phandle of syscon used to control phy.
- hisilicon,pctrl-syscon: phandle of syscon used to control phy.
- hisilicon,eye-diagram-param: parameter set for phy
Refer to phy/phy-bindings.txt for the generic PHY binding properties
This is a subnode of usb3_otg_bc register node.
Example:
usb3_otg_bc: usb3_otg_bc@ff200000 {
compatible = "syscon", "simple-mfd";
reg = <0x0 0xff200000 0x0 0x1000>;
usb-phy {
compatible = "hisilicon,hi3660-usb-phy";
#phy-cells = <0>;
hisilicon,pericrg-syscon = <&crg_ctrl>;
hisilicon,pctrl-syscon = <&pctrl>;
hisilicon,eye-diagram-param = <0x22466e4>;
};
};

View File

@ -0,0 +1,38 @@
MediaTek Universal Flash Storage (UFS) M-PHY binding
--------------------------------------------------------
UFS M-PHY nodes are defined to describe on-chip UFS M-PHY hardware macro.
Each UFS M-PHY node should have its own node.
To bind UFS M-PHY with UFS host controller, the controller node should
contain a phandle reference to UFS M-PHY node.
Required properties for UFS M-PHY nodes:
- compatible : Compatible list, contains the following controller:
"mediatek,mt8183-ufsphy" for ufs phy
persent on MT81xx chipsets.
- reg : Address and length of the UFS M-PHY register set.
- #phy-cells : This property shall be set to 0.
- clocks : List of phandle and clock specifier pairs.
- clock-names : List of clock input name strings sorted in the same
order as the clocks property. Following clocks are
mandatory.
"unipro": Unipro core control clock.
"mp": M-PHY core control clock.
Example:
ufsphy: phy@11fa0000 {
compatible = "mediatek,mt8183-ufsphy";
reg = <0 0x11fa0000 0 0xc000>;
#phy-cells = <0>;
clocks = <&infracfg_ao INFRACFG_AO_UNIPRO_SCK_CG>,
<&infracfg_ao INFRACFG_AO_UFS_MP_SAP_BCLK_CG>;
clock-names = "unipro", "mp";
};
ufshci@11270000 {
...
phys = <&ufsphy>;
};

View File

@ -11,6 +11,7 @@ Required properties:
"qcom,msm8996-qmp-usb3-phy" for 14nm USB3 phy on msm8996,
"qcom,msm8998-qmp-usb3-phy" for USB3 QMP V3 phy on msm8998,
"qcom,msm8998-qmp-ufs-phy" for UFS QMP phy on msm8998,
"qcom,msm8998-qmp-pcie-phy" for PCIe QMP phy on msm8998,
"qcom,sdm845-qmp-usb3-phy" for USB3 QMP V3 phy on sdm845,
"qcom,sdm845-qmp-usb3-uni-phy" for USB3 QMP V3 UNI phy on sdm845,
"qcom,sdm845-qmp-ufs-phy" for UFS QMP phy on sdm845.
@ -48,6 +49,8 @@ Required properties:
"aux", "cfg_ahb", "ref".
For "qcom,msm8998-qmp-ufs-phy" must contain:
"ref", "ref_aux".
For "qcom,msm8998-qmp-pcie-phy" must contain:
"aux", "cfg_ahb", "ref".
For "qcom,sdm845-qmp-usb3-phy" must contain:
"aux", "cfg_ahb", "ref", "com_aux".
For "qcom,sdm845-qmp-usb3-uni-phy" must contain:
@ -59,7 +62,8 @@ Required properties:
one for each entry in reset-names.
- reset-names: "phy" for reset of phy block,
"common" for phy common block reset,
"cfg" for phy's ahb cfg block reset.
"cfg" for phy's ahb cfg block reset,
"ufsphy" for the PHY reset in the UFS controller.
For "qcom,ipq8074-qmp-pcie-phy" must contain:
"phy", "common".
@ -69,12 +73,16 @@ Required properties:
"phy", "common".
For "qcom,msm8998-qmp-usb3-phy" must contain
"phy", "common".
For "qcom,msm8998-qmp-ufs-phy": no resets are listed.
For "qcom,msm8998-qmp-ufs-phy": must contain:
"ufsphy".
For "qcom,msm8998-qmp-pcie-phy" must contain:
"phy", "common".
For "qcom,sdm845-qmp-usb3-phy" must contain:
"phy", "common".
For "qcom,sdm845-qmp-usb3-uni-phy" must contain:
"phy", "common".
For "qcom,sdm845-qmp-ufs-phy": no resets are listed.
For "qcom,sdm845-qmp-ufs-phy": must contain:
"ufsphy".
- vdda-phy-supply: Phandle to a regulator supply to PHY core block.
- vdda-pll-supply: Phandle to 1.8V regulator supply to PHY refclk pll block.

View File

@ -7,6 +7,7 @@ Required properties:
- compatible: "renesas,usb-phy-r8a7743" if the device is a part of R8A7743 SoC.
"renesas,usb-phy-r8a7744" if the device is a part of R8A7744 SoC.
"renesas,usb-phy-r8a7745" if the device is a part of R8A7745 SoC.
"renesas,usb-phy-r8a77470" if the device is a part of R8A77470 SoC.
"renesas,usb-phy-r8a7790" if the device is a part of R8A7790 SoC.
"renesas,usb-phy-r8a7791" if the device is a part of R8A7791 SoC.
"renesas,usb-phy-r8a7794" if the device is a part of R8A7794 SoC.
@ -30,7 +31,7 @@ channels. These subnodes must contain the following properties:
- #phy-cells: see phy-bindings.txt in the same directory, must be <1>.
The phandle's argument in the PHY specifier is the USB controller selector for
the USB channel; see the selector meanings below:
the USB channel other than r8a77470 SoC; see the selector meanings below:
+-----------+---------------+---------------+
|\ Selector | | |
@ -41,6 +42,16 @@ the USB channel; see the selector meanings below:
| 2 | PCI EHCI/OHCI | xHCI |
+-----------+---------------+---------------+
For r8a77470 SoC;see the selector meaning below:
+-----------+---------------+---------------+
|\ Selector | | |
+ --------- + 0 | 1 |
| Channel \| | |
+-----------+---------------+---------------+
| 0 | EHCI/OHCI | HS-USB |
+-----------+---------------+---------------+
Example (Lager board):
usb-phy@e6590100 {
@ -48,15 +59,53 @@ Example (Lager board):
reg = <0 0xe6590100 0 0x100>;
#address-cells = <1>;
#size-cells = <0>;
clocks = <&mstp7_clks R8A7790_CLK_HSUSB>;
clocks = <&cpg CPG_MOD 704>;
clock-names = "usbhs";
power-domains = <&sysc R8A7790_PD_ALWAYS_ON>;
resets = <&cpg 704>;
usb-channel@0 {
usb0: usb-channel@0 {
reg = <0>;
#phy-cells = <1>;
};
usb-channel@2 {
usb2: usb-channel@2 {
reg = <2>;
#phy-cells = <1>;
};
};
Example (iWave RZ/G1C sbc):
usbphy0: usb-phy0@e6590100 {
compatible = "renesas,usb-phy-r8a77470",
"renesas,rcar-gen2-usb-phy";
reg = <0 0xe6590100 0 0x100>;
#address-cells = <1>;
#size-cells = <0>;
clocks = <&cpg CPG_MOD 704>;
clock-names = "usbhs";
power-domains = <&sysc R8A77470_PD_ALWAYS_ON>;
resets = <&cpg 704>;
usb0: usb-channel@0 {
reg = <0>;
#phy-cells = <1>;
};
};
usbphy1: usb-phy@e6598100 {
compatible = "renesas,usb-phy-r8a77470",
"renesas,rcar-gen2-usb-phy";
reg = <0 0xe6598100 0 0x100>;
#address-cells = <1>;
#size-cells = <0>;
clocks = <&cpg CPG_MOD 706>;
clock-names = "usbhs";
power-domains = <&sysc R8A77470_PD_ALWAYS_ON>;
resets = <&cpg 706>;
usb1: usb-channel@0 {
reg = <0>;
#phy-cells = <1>;
};
};

View File

@ -1,10 +1,12 @@
* Renesas R-Car generation 3 USB 2.0 PHY
This file provides information on what the device node for the R-Car generation
3 and RZ/G2 USB 2.0 PHY contain.
3, RZ/G1C and RZ/G2 USB 2.0 PHY contain.
Required properties:
- compatible: "renesas,usb2-phy-r8a774a1" if the device is a part of an R8A774A1
- compatible: "renesas,usb2-phy-r8a77470" if the device is a part of an R8A77470
SoC.
"renesas,usb2-phy-r8a774a1" if the device is a part of an R8A774A1
SoC.
"renesas,usb2-phy-r8a774c0" if the device is a part of an R8A774C0
SoC.
@ -27,7 +29,13 @@ Required properties:
- reg: offset and length of the partial USB 2.0 Host register block.
- clocks: clock phandle and specifier pair(s).
- #phy-cells: see phy-bindings.txt in the same directory, must be <0>.
- #phy-cells: see phy-bindings.txt in the same directory, must be <1> (and
using <0> is deprecated).
The phandle's argument in the PHY specifier is the INT_STATUS bit of controller:
- 1 = USBH_INTA (OHCI)
- 2 = USBH_INTB (EHCI)
- 3 = UCOM_INT (OTG and BC)
Optional properties:
To use a USB channel where USB 2.0 Host and HSUSB (USB 2.0 Peripheral) are

View File

@ -7,12 +7,15 @@ Required properties:
- reg: PHY register address offset and length in "general
register files"
Optional clocks using the clock bindings (see ../clock/clock-bindings.txt),
specified by name:
Optional properties:
- clock-names: Should contain "emmcclk". Although this is listed as optional
(because most boards can get basic functionality without having
access to it), it is strongly suggested.
See ../clock/clock-bindings.txt for details.
- clocks: Should have a phandle to the card clock exported by the SDHCI driver.
- drive-impedance-ohm: Specifies the drive impedance in Ohm.
Possible values are 33, 40, 50, 66 and 100.
If not set, the default value of 50 will be applied.
Example:
@ -29,6 +32,7 @@ grf: syscon@ff770000 {
reg = <0xf780 0x20>;
clocks = <&sdhci>;
clock-names = "emmcclk";
drive-impedance-ohm = <50>;
#phy-cells = <0>;
};
};

View File

@ -0,0 +1,82 @@
TI AM654 SERDES
Required properties:
- compatible: Should be "ti,phy-am654-serdes"
- 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. Should be "2". The 1st cell
corresponds to the phy type (should be one of the types specified in
include/dt-bindings/phy/phy.h) and the 2nd cell should be the serdes
lane function.
If SERDES0 is referenced 2nd cell should be:
0 - USB3
1 - PCIe0 Lane0
2 - ICSS2 SGMII Lane0
If SERDES1 is referenced 2nd cell should be:
0 - PCIe1 Lane0
1 - PCIe0 Lane1
2 - ICSS2 SGMII Lane1
- power-domains: As documented by the generic PM domain bindings in
Documentation/devicetree/bindings/power/power_domain.txt.
- clocks: List of clock-specifiers representing the input to the SERDES.
Should have 3 items representing the left input clock, external
reference clock and right input clock in that order.
- clock-output-names: List of clock names for each of the clock outputs of
SERDES. Should have 3 items for CMU reference clock,
left output clock and right output clock in that order.
- assigned-clocks: As defined in
Documentation/devicetree/bindings/clock/clock-bindings.txt
- assigned-clock-parents: As defined in
Documentation/devicetree/bindings/clock/clock-bindings.txt
- #clock-cells: Should be <1> to choose between the 3 output clocks.
Defined in Documentation/devicetree/bindings/clock/clock-bindings.txt
The following macros are defined in dt-bindings/phy/phy-am654-serdes.h
for selecting the correct reference clock. This can be used while
specifying the clocks created by SERDES.
=> AM654_SERDES_CMU_REFCLK
=> AM654_SERDES_LO_REFCLK
=> AM654_SERDES_RO_REFCLK
- mux-controls: Phandle to the multiplexer that is used to select the lane
function. See #phy-cells above to see the multiplex values.
Example:
Example for SERDES0 is given below. It has 3 clock inputs;
left input reference clock as indicated by <&k3_clks 153 4>, external
reference clock as indicated by <&k3_clks 153 1> and right input
reference clock as indicated by <&serdes1 AM654_SERDES_LO_REFCLK>. (The
right input of SERDES0 is connected to the left output of SERDES1).
SERDES0 registers 3 clock outputs as indicated in clock-output-names. The
first refers to the CMU reference clock, second refers to the left output
reference clock and the third refers to the right output reference clock.
The assigned-clocks and assigned-clock-parents is used here to set the
parent of left input reference clock to MAINHSDIV_CLKOUT4 and parent of
CMU reference clock to left input reference clock.
serdes0: serdes@900000 {
compatible = "ti,phy-am654-serdes";
reg = <0x0 0x900000 0x0 0x2000>;
reg-names = "serdes";
#phy-cells = <2>;
power-domains = <&k3_pds 153>;
clocks = <&k3_clks 153 4>, <&k3_clks 153 1>,
<&serdes1 AM654_SERDES_LO_REFCLK>;
clock-output-names = "serdes0_cmu_refclk", "serdes0_lo_refclk",
"serdes0_ro_refclk";
assigned-clocks = <&k3_clks 153 4>, <&serdes0 AM654_SERDES_CMU_REFCLK>;
assigned-clock-parents = <&k3_clks 153 8>, <&k3_clks 153 4>;
ti,serdes-clk = <&serdes0_clk>;
mux-controls = <&serdes_mux 0>;
#clock-cells = <1>;
};
Example for PCIe consumer node using the SERDES PHY specifier is given below.
&pcie0_rc {
num-lanes = <2>;
phys = <&serdes0 PHY_TYPE_PCIE 1>, <&serdes1 PHY_TYPE_PCIE 1>;
phy-names = "pcie-phy0", "pcie-phy1";
};

View File

@ -29,6 +29,7 @@ Optional properties:
- vdda-pll-max-microamp : specifies max. load that can be drawn from pll supply
- vddp-ref-clk-supply : phandle to UFS device ref_clk pad power supply
- vddp-ref-clk-max-microamp : specifies max. load that can be drawn from this supply
- resets : specifies the PHY reset in the UFS controller
Example:
@ -51,9 +52,11 @@ Example:
<&clock_gcc clk_ufs_phy_ldo>,
<&clock_gcc clk_gcc_ufs_tx_cfg_clk>,
<&clock_gcc clk_gcc_ufs_rx_cfg_clk>;
resets = <&ufshc 0>;
};
ufshc@fc598000 {
ufshc: ufshc@fc598000 {
#reset-cells = <1>;
...
phys = <&ufsphy1>;
phy-names = "ufsphy";

View File

@ -50,6 +50,8 @@ Optional properties:
-lanes-per-direction : number of lanes available per direction - either 1 or 2.
Note that it is assume same number of lanes is used both
directions at once. If not specified, default is 2 lanes per direction.
- #reset-cells : Must be <1> for Qualcomm UFS controllers that expose
PHY reset from the UFS controller.
- resets : reset node register
- reset-names : describe reset node register, the "rst" corresponds to reset the whole UFS IP.
@ -79,4 +81,5 @@ Example:
reset-names = "rst";
phys = <&ufsphy1>;
phy-names = "ufsphy";
#reset-cells = <1>;
};

View File

@ -15313,6 +15313,11 @@ M: Laxman Dewangan <ldewangan@nvidia.com>
S: Supported
F: drivers/spi/spi-tegra*
TEGRA XUSB PADCTL DRIVER
M: JC Kuo <jckuo@nvidia.com>
S: Supported
F: drivers/phy/tegra/xusb*
TEHUTI ETHERNET DRIVER
M: Andy Gospodarek <andy@greyhouse.net>
L: netdev@vger.kernel.org
@ -16083,6 +16088,14 @@ L: linux-usb@vger.kernel.org
S: Maintained
F: drivers/usb/roles/intel-xhci-usb-role-switch.c
USB IP DRIVER FOR HISILICON KIRIN
M: Yu Chen <chenyu56@huawei.com>
M: Binghui Wang <wangbinghui@hisilicon.com>
L: linux-usb@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/phy/phy-hi3660-usb3.txt
F: drivers/phy/hisilicon/phy-hi3660-usb3.c
USB ISP116X DRIVER
M: Olav Kongas <ok@artecdesign.ee>
L: linux-usb@vger.kernel.org

View File

@ -36,3 +36,25 @@ config PHY_MESON_GXL_USB3
Enable this to support the Meson USB3 PHY and OTG detection
IP block found in Meson GXL and GXM SoCs.
If unsure, say N.
config PHY_MESON_G12A_USB2
tristate "Meson G12A USB2 PHY driver"
default ARCH_MESON
depends on OF && (ARCH_MESON || COMPILE_TEST)
select GENERIC_PHY
select REGMAP_MMIO
help
Enable this to support the Meson USB2 PHYs found in Meson
G12A SoCs.
If unsure, say N.
config PHY_MESON_G12A_USB3_PCIE
tristate "Meson G12A USB3+PCIE Combo PHY driver"
default ARCH_MESON
depends on OF && (ARCH_MESON || COMPILE_TEST)
select GENERIC_PHY
select REGMAP_MMIO
help
Enable this to support the Meson USB3 + PCIE Combo PHY found
in Meson G12A SoCs.
If unsure, say N.

View File

@ -1,3 +1,5 @@
obj-$(CONFIG_PHY_MESON8B_USB2) += phy-meson8b-usb2.o
obj-$(CONFIG_PHY_MESON_GXL_USB2) += phy-meson-gxl-usb2.o
obj-$(CONFIG_PHY_MESON_G12A_USB2) += phy-meson-g12a-usb2.o
obj-$(CONFIG_PHY_MESON_GXL_USB3) += phy-meson-gxl-usb3.o
obj-$(CONFIG_PHY_MESON_G12A_USB3_PCIE) += phy-meson-g12a-usb3-pcie.o

View File

@ -0,0 +1,341 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Meson G12A USB2 PHY driver
*
* Copyright (C) 2017 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
* Copyright (C) 2017 Amlogic, Inc. All rights reserved
* Copyright (C) 2019 BayLibre, SAS
* Author: Neil Armstrong <narmstrong@baylibre.com>
*/
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#define PHY_CTRL_R0 0x0
#define PHY_CTRL_R1 0x4
#define PHY_CTRL_R2 0x8
#define PHY_CTRL_R3 0xc
#define PHY_CTRL_R3_SQUELCH_REF GENMASK(1, 0)
#define PHY_CTRL_R3_HSDIC_REF GENMASK(3, 2)
#define PHY_CTRL_R3_DISC_THRESH GENMASK(7, 4)
#define PHY_CTRL_R4 0x10
#define PHY_CTRL_R4_CALIB_CODE_7_0 GENMASK(7, 0)
#define PHY_CTRL_R4_CALIB_CODE_15_8 GENMASK(15, 8)
#define PHY_CTRL_R4_CALIB_CODE_23_16 GENMASK(23, 16)
#define PHY_CTRL_R4_I_C2L_CAL_EN BIT(24)
#define PHY_CTRL_R4_I_C2L_CAL_RESET_N BIT(25)
#define PHY_CTRL_R4_I_C2L_CAL_DONE BIT(26)
#define PHY_CTRL_R4_TEST_BYPASS_MODE_EN BIT(27)
#define PHY_CTRL_R4_I_C2L_BIAS_TRIM_1_0 GENMASK(29, 28)
#define PHY_CTRL_R4_I_C2L_BIAS_TRIM_3_2 GENMASK(31, 30)
#define PHY_CTRL_R5 0x14
#define PHY_CTRL_R6 0x18
#define PHY_CTRL_R7 0x1c
#define PHY_CTRL_R8 0x20
#define PHY_CTRL_R9 0x24
#define PHY_CTRL_R10 0x28
#define PHY_CTRL_R11 0x2c
#define PHY_CTRL_R12 0x30
#define PHY_CTRL_R13 0x34
#define PHY_CTRL_R13_CUSTOM_PATTERN_19 GENMASK(7, 0)
#define PHY_CTRL_R13_LOAD_STAT BIT(14)
#define PHY_CTRL_R13_UPDATE_PMA_SIGNALS BIT(15)
#define PHY_CTRL_R13_MIN_COUNT_FOR_SYNC_DET GENMASK(20, 16)
#define PHY_CTRL_R13_CLEAR_HOLD_HS_DISCONNECT BIT(21)
#define PHY_CTRL_R13_BYPASS_HOST_DISCONNECT_VAL BIT(22)
#define PHY_CTRL_R13_BYPASS_HOST_DISCONNECT_EN BIT(23)
#define PHY_CTRL_R13_I_C2L_HS_EN BIT(24)
#define PHY_CTRL_R13_I_C2L_FS_EN BIT(25)
#define PHY_CTRL_R13_I_C2L_LS_EN BIT(26)
#define PHY_CTRL_R13_I_C2L_HS_OE BIT(27)
#define PHY_CTRL_R13_I_C2L_FS_OE BIT(28)
#define PHY_CTRL_R13_I_C2L_HS_RX_EN BIT(29)
#define PHY_CTRL_R13_I_C2L_FSLS_RX_EN BIT(30)
#define PHY_CTRL_R14 0x38
#define PHY_CTRL_R14_I_RDP_EN BIT(0)
#define PHY_CTRL_R14_I_RPU_SW1_EN BIT(1)
#define PHY_CTRL_R14_I_RPU_SW2_EN GENMASK(2, 3)
#define PHY_CTRL_R14_PG_RSTN BIT(4)
#define PHY_CTRL_R14_I_C2L_DATA_16_8 BIT(5)
#define PHY_CTRL_R14_I_C2L_ASSERT_SINGLE_EN_ZERO BIT(6)
#define PHY_CTRL_R14_BYPASS_CTRL_7_0 GENMASK(15, 8)
#define PHY_CTRL_R14_BYPASS_CTRL_15_8 GENMASK(23, 16)
#define PHY_CTRL_R15 0x3c
#define PHY_CTRL_R16 0x40
#define PHY_CTRL_R16_MPLL_M GENMASK(8, 0)
#define PHY_CTRL_R16_MPLL_N GENMASK(14, 10)
#define PHY_CTRL_R16_MPLL_TDC_MODE BIT(20)
#define PHY_CTRL_R16_MPLL_SDM_EN BIT(21)
#define PHY_CTRL_R16_MPLL_LOAD BIT(22)
#define PHY_CTRL_R16_MPLL_DCO_SDM_EN BIT(23)
#define PHY_CTRL_R16_MPLL_LOCK_LONG GENMASK(25, 24)
#define PHY_CTRL_R16_MPLL_LOCK_F BIT(26)
#define PHY_CTRL_R16_MPLL_FAST_LOCK BIT(27)
#define PHY_CTRL_R16_MPLL_EN BIT(28)
#define PHY_CTRL_R16_MPLL_RESET BIT(29)
#define PHY_CTRL_R16_MPLL_LOCK BIT(30)
#define PHY_CTRL_R16_MPLL_LOCK_DIG BIT(31)
#define PHY_CTRL_R17 0x44
#define PHY_CTRL_R17_MPLL_FRAC_IN GENMASK(13, 0)
#define PHY_CTRL_R17_MPLL_FIX_EN BIT(16)
#define PHY_CTRL_R17_MPLL_LAMBDA1 GENMASK(19, 17)
#define PHY_CTRL_R17_MPLL_LAMBDA0 GENMASK(22, 20)
#define PHY_CTRL_R17_MPLL_FILTER_MODE BIT(23)
#define PHY_CTRL_R17_MPLL_FILTER_PVT2 GENMASK(27, 24)
#define PHY_CTRL_R17_MPLL_FILTER_PVT1 GENMASK(31, 28)
#define PHY_CTRL_R18 0x48
#define PHY_CTRL_R18_MPLL_LKW_SEL GENMASK(1, 0)
#define PHY_CTRL_R18_MPLL_LK_W GENMASK(5, 2)
#define PHY_CTRL_R18_MPLL_LK_S GENMASK(11, 6)
#define PHY_CTRL_R18_MPLL_DCO_M_EN BIT(12)
#define PHY_CTRL_R18_MPLL_DCO_CLK_SEL BIT(13)
#define PHY_CTRL_R18_MPLL_PFD_GAIN GENMASK(15, 14)
#define PHY_CTRL_R18_MPLL_ROU GENMASK(18, 16)
#define PHY_CTRL_R18_MPLL_DATA_SEL GENMASK(21, 19)
#define PHY_CTRL_R18_MPLL_BIAS_ADJ GENMASK(23, 22)
#define PHY_CTRL_R18_MPLL_BB_MODE GENMASK(25, 24)
#define PHY_CTRL_R18_MPLL_ALPHA GENMASK(28, 26)
#define PHY_CTRL_R18_MPLL_ADJ_LDO GENMASK(30, 29)
#define PHY_CTRL_R18_MPLL_ACG_RANGE BIT(31)
#define PHY_CTRL_R19 0x4c
#define PHY_CTRL_R20 0x50
#define PHY_CTRL_R20_USB2_IDDET_EN BIT(0)
#define PHY_CTRL_R20_USB2_OTG_VBUS_TRIM_2_0 GENMASK(3, 1)
#define PHY_CTRL_R20_USB2_OTG_VBUSDET_EN BIT(4)
#define PHY_CTRL_R20_USB2_AMON_EN BIT(5)
#define PHY_CTRL_R20_USB2_CAL_CODE_R5 BIT(6)
#define PHY_CTRL_R20_BYPASS_OTG_DET BIT(7)
#define PHY_CTRL_R20_USB2_DMON_EN BIT(8)
#define PHY_CTRL_R20_USB2_DMON_SEL_3_0 GENMASK(12, 9)
#define PHY_CTRL_R20_USB2_EDGE_DRV_EN BIT(13)
#define PHY_CTRL_R20_USB2_EDGE_DRV_TRIM_1_0 GENMASK(15, 14)
#define PHY_CTRL_R20_USB2_BGR_ADJ_4_0 GENMASK(20, 16)
#define PHY_CTRL_R20_USB2_BGR_START BIT(21)
#define PHY_CTRL_R20_USB2_BGR_VREF_4_0 GENMASK(28, 24)
#define PHY_CTRL_R20_USB2_BGR_DBG_1_0 GENMASK(30, 29)
#define PHY_CTRL_R20_BYPASS_CAL_DONE_R5 BIT(31)
#define PHY_CTRL_R21 0x54
#define PHY_CTRL_R21_USB2_BGR_FORCE BIT(0)
#define PHY_CTRL_R21_USB2_CAL_ACK_EN BIT(1)
#define PHY_CTRL_R21_USB2_OTG_ACA_EN BIT(2)
#define PHY_CTRL_R21_USB2_TX_STRG_PD BIT(3)
#define PHY_CTRL_R21_USB2_OTG_ACA_TRIM_1_0 GENMASK(5, 4)
#define PHY_CTRL_R21_BYPASS_UTMI_CNTR GENMASK(15, 6)
#define PHY_CTRL_R21_BYPASS_UTMI_REG GENMASK(25, 20)
#define PHY_CTRL_R22 0x58
#define PHY_CTRL_R23 0x5c
#define RESET_COMPLETE_TIME 1000
#define PLL_RESET_COMPLETE_TIME 100
struct phy_meson_g12a_usb2_priv {
struct device *dev;
struct regmap *regmap;
struct clk *clk;
struct reset_control *reset;
};
static const struct regmap_config phy_meson_g12a_usb2_regmap_conf = {
.reg_bits = 8,
.val_bits = 32,
.reg_stride = 4,
.max_register = PHY_CTRL_R23,
};
static int phy_meson_g12a_usb2_init(struct phy *phy)
{
struct phy_meson_g12a_usb2_priv *priv = phy_get_drvdata(phy);
int ret;
ret = reset_control_reset(priv->reset);
if (ret)
return ret;
udelay(RESET_COMPLETE_TIME);
/* usb2_otg_aca_en == 0 */
regmap_update_bits(priv->regmap, PHY_CTRL_R21,
PHY_CTRL_R21_USB2_OTG_ACA_EN, 0);
/* PLL Setup : 24MHz * 20 / 1 = 480MHz */
regmap_write(priv->regmap, PHY_CTRL_R16,
FIELD_PREP(PHY_CTRL_R16_MPLL_M, 20) |
FIELD_PREP(PHY_CTRL_R16_MPLL_N, 1) |
PHY_CTRL_R16_MPLL_LOAD |
FIELD_PREP(PHY_CTRL_R16_MPLL_LOCK_LONG, 1) |
PHY_CTRL_R16_MPLL_FAST_LOCK |
PHY_CTRL_R16_MPLL_EN |
PHY_CTRL_R16_MPLL_RESET);
regmap_write(priv->regmap, PHY_CTRL_R17,
FIELD_PREP(PHY_CTRL_R17_MPLL_FRAC_IN, 0) |
FIELD_PREP(PHY_CTRL_R17_MPLL_LAMBDA1, 7) |
FIELD_PREP(PHY_CTRL_R17_MPLL_LAMBDA0, 7) |
FIELD_PREP(PHY_CTRL_R17_MPLL_FILTER_PVT2, 2) |
FIELD_PREP(PHY_CTRL_R17_MPLL_FILTER_PVT1, 9));
regmap_write(priv->regmap, PHY_CTRL_R18,
FIELD_PREP(PHY_CTRL_R18_MPLL_LKW_SEL, 1) |
FIELD_PREP(PHY_CTRL_R18_MPLL_LK_W, 9) |
FIELD_PREP(PHY_CTRL_R18_MPLL_LK_S, 0x27) |
FIELD_PREP(PHY_CTRL_R18_MPLL_PFD_GAIN, 1) |
FIELD_PREP(PHY_CTRL_R18_MPLL_ROU, 7) |
FIELD_PREP(PHY_CTRL_R18_MPLL_DATA_SEL, 3) |
FIELD_PREP(PHY_CTRL_R18_MPLL_BIAS_ADJ, 1) |
FIELD_PREP(PHY_CTRL_R18_MPLL_BB_MODE, 0) |
FIELD_PREP(PHY_CTRL_R18_MPLL_ALPHA, 3) |
FIELD_PREP(PHY_CTRL_R18_MPLL_ADJ_LDO, 1) |
PHY_CTRL_R18_MPLL_ACG_RANGE);
udelay(PLL_RESET_COMPLETE_TIME);
/* UnReset PLL */
regmap_write(priv->regmap, PHY_CTRL_R16,
FIELD_PREP(PHY_CTRL_R16_MPLL_M, 20) |
FIELD_PREP(PHY_CTRL_R16_MPLL_N, 1) |
PHY_CTRL_R16_MPLL_LOAD |
FIELD_PREP(PHY_CTRL_R16_MPLL_LOCK_LONG, 1) |
PHY_CTRL_R16_MPLL_FAST_LOCK |
PHY_CTRL_R16_MPLL_EN);
/* PHY Tuning */
regmap_write(priv->regmap, PHY_CTRL_R20,
FIELD_PREP(PHY_CTRL_R20_USB2_OTG_VBUS_TRIM_2_0, 4) |
PHY_CTRL_R20_USB2_OTG_VBUSDET_EN |
FIELD_PREP(PHY_CTRL_R20_USB2_DMON_SEL_3_0, 15) |
PHY_CTRL_R20_USB2_EDGE_DRV_EN |
FIELD_PREP(PHY_CTRL_R20_USB2_EDGE_DRV_TRIM_1_0, 3) |
FIELD_PREP(PHY_CTRL_R20_USB2_BGR_ADJ_4_0, 0) |
FIELD_PREP(PHY_CTRL_R20_USB2_BGR_VREF_4_0, 0) |
FIELD_PREP(PHY_CTRL_R20_USB2_BGR_DBG_1_0, 0));
regmap_write(priv->regmap, PHY_CTRL_R4,
FIELD_PREP(PHY_CTRL_R4_CALIB_CODE_7_0, 0xf) |
FIELD_PREP(PHY_CTRL_R4_CALIB_CODE_15_8, 0xf) |
FIELD_PREP(PHY_CTRL_R4_CALIB_CODE_23_16, 0xf) |
PHY_CTRL_R4_TEST_BYPASS_MODE_EN |
FIELD_PREP(PHY_CTRL_R4_I_C2L_BIAS_TRIM_1_0, 0) |
FIELD_PREP(PHY_CTRL_R4_I_C2L_BIAS_TRIM_3_2, 0));
/* Tuning Disconnect Threshold */
regmap_write(priv->regmap, PHY_CTRL_R3,
FIELD_PREP(PHY_CTRL_R3_SQUELCH_REF, 0) |
FIELD_PREP(PHY_CTRL_R3_HSDIC_REF, 1) |
FIELD_PREP(PHY_CTRL_R3_DISC_THRESH, 3));
/* Analog Settings */
regmap_write(priv->regmap, PHY_CTRL_R14, 0);
regmap_write(priv->regmap, PHY_CTRL_R13,
PHY_CTRL_R13_UPDATE_PMA_SIGNALS |
FIELD_PREP(PHY_CTRL_R13_MIN_COUNT_FOR_SYNC_DET, 7));
return 0;
}
static int phy_meson_g12a_usb2_exit(struct phy *phy)
{
struct phy_meson_g12a_usb2_priv *priv = phy_get_drvdata(phy);
return reset_control_reset(priv->reset);
}
/* set_mode is not needed, mode setting is handled via the UTMI bus */
static const struct phy_ops phy_meson_g12a_usb2_ops = {
.init = phy_meson_g12a_usb2_init,
.exit = phy_meson_g12a_usb2_exit,
.owner = THIS_MODULE,
};
static int phy_meson_g12a_usb2_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct phy_provider *phy_provider;
struct resource *res;
struct phy_meson_g12a_usb2_priv *priv;
struct phy *phy;
void __iomem *base;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
platform_set_drvdata(pdev, priv);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
priv->regmap = devm_regmap_init_mmio(dev, base,
&phy_meson_g12a_usb2_regmap_conf);
if (IS_ERR(priv->regmap))
return PTR_ERR(priv->regmap);
priv->clk = devm_clk_get(dev, "xtal");
if (IS_ERR(priv->clk))
return PTR_ERR(priv->clk);
priv->reset = devm_reset_control_get(dev, "phy");
if (IS_ERR(priv->reset))
return PTR_ERR(priv->reset);
ret = reset_control_deassert(priv->reset);
if (ret)
return ret;
phy = devm_phy_create(dev, NULL, &phy_meson_g12a_usb2_ops);
if (IS_ERR(phy)) {
ret = PTR_ERR(phy);
if (ret != -EPROBE_DEFER)
dev_err(dev, "failed to create PHY\n");
return ret;
}
phy_set_bus_width(phy, 8);
phy_set_drvdata(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 phy_meson_g12a_usb2_of_match[] = {
{ .compatible = "amlogic,g12a-usb2-phy", },
{ },
};
MODULE_DEVICE_TABLE(of, phy_meson_g12a_usb2_of_match);
static struct platform_driver phy_meson_g12a_usb2_driver = {
.probe = phy_meson_g12a_usb2_probe,
.driver = {
.name = "phy-meson-g12a-usb2",
.of_match_table = phy_meson_g12a_usb2_of_match,
},
};
module_platform_driver(phy_meson_g12a_usb2_driver);
MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
MODULE_DESCRIPTION("Meson G12A USB2 PHY driver");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,413 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Amlogic G12A USB3 + PCIE Combo PHY driver
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved
* Copyright (C) 2019 BayLibre, SAS
* Author: Neil Armstrong <narmstrong@baylibre.com>
*/
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/phy/phy.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/platform_device.h>
#include <dt-bindings/phy/phy.h>
#define PHY_R0 0x00
#define PHY_R0_PCIE_POWER_STATE GENMASK(4, 0)
#define PHY_R0_PCIE_USB3_SWITCH GENMASK(6, 5)
#define PHY_R1 0x04
#define PHY_R1_PHY_TX1_TERM_OFFSET GENMASK(4, 0)
#define PHY_R1_PHY_TX0_TERM_OFFSET GENMASK(9, 5)
#define PHY_R1_PHY_RX1_EQ GENMASK(12, 10)
#define PHY_R1_PHY_RX0_EQ GENMASK(15, 13)
#define PHY_R1_PHY_LOS_LEVEL GENMASK(20, 16)
#define PHY_R1_PHY_LOS_BIAS GENMASK(23, 21)
#define PHY_R1_PHY_REF_CLKDIV2 BIT(24)
#define PHY_R1_PHY_MPLL_MULTIPLIER GENMASK(31, 25)
#define PHY_R2 0x08
#define PHY_R2_PCS_TX_DEEMPH_GEN2_6DB GENMASK(5, 0)
#define PHY_R2_PCS_TX_DEEMPH_GEN2_3P5DB GENMASK(11, 6)
#define PHY_R2_PCS_TX_DEEMPH_GEN1 GENMASK(17, 12)
#define PHY_R2_PHY_TX_VBOOST_LVL GENMASK(20, 18)
#define PHY_R4 0x10
#define PHY_R4_PHY_CR_WRITE BIT(0)
#define PHY_R4_PHY_CR_READ BIT(1)
#define PHY_R4_PHY_CR_DATA_IN GENMASK(17, 2)
#define PHY_R4_PHY_CR_CAP_DATA BIT(18)
#define PHY_R4_PHY_CR_CAP_ADDR BIT(19)
#define PHY_R5 0x14
#define PHY_R5_PHY_CR_DATA_OUT GENMASK(15, 0)
#define PHY_R5_PHY_CR_ACK BIT(16)
#define PHY_R5_PHY_BS_OUT BIT(17)
struct phy_g12a_usb3_pcie_priv {
struct regmap *regmap;
struct regmap *regmap_cr;
struct clk *clk_ref;
struct reset_control *reset;
struct phy *phy;
unsigned int mode;
};
static const struct regmap_config phy_g12a_usb3_pcie_regmap_conf = {
.reg_bits = 8,
.val_bits = 32,
.reg_stride = 4,
.max_register = PHY_R5,
};
static int phy_g12a_usb3_pcie_cr_bus_addr(struct phy_g12a_usb3_pcie_priv *priv,
unsigned int addr)
{
unsigned int val, reg;
int ret;
reg = FIELD_PREP(PHY_R4_PHY_CR_DATA_IN, addr);
regmap_write(priv->regmap, PHY_R4, reg);
regmap_write(priv->regmap, PHY_R4, reg);
regmap_write(priv->regmap, PHY_R4, reg | PHY_R4_PHY_CR_CAP_ADDR);
ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
(val & PHY_R5_PHY_CR_ACK),
5, 1000);
if (ret)
return ret;
regmap_write(priv->regmap, PHY_R4, reg);
ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
!(val & PHY_R5_PHY_CR_ACK),
5, 1000);
if (ret)
return ret;
return 0;
}
static int phy_g12a_usb3_pcie_cr_bus_read(void *context, unsigned int addr,
unsigned int *data)
{
struct phy_g12a_usb3_pcie_priv *priv = context;
unsigned int val;
int ret;
ret = phy_g12a_usb3_pcie_cr_bus_addr(priv, addr);
if (ret)
return ret;
regmap_write(priv->regmap, PHY_R4, 0);
regmap_write(priv->regmap, PHY_R4, PHY_R4_PHY_CR_READ);
ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
(val & PHY_R5_PHY_CR_ACK),
5, 1000);
if (ret)
return ret;
*data = FIELD_GET(PHY_R5_PHY_CR_DATA_OUT, val);
regmap_write(priv->regmap, PHY_R4, 0);
ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
!(val & PHY_R5_PHY_CR_ACK),
5, 1000);
if (ret)
return ret;
return 0;
}
static int phy_g12a_usb3_pcie_cr_bus_write(void *context, unsigned int addr,
unsigned int data)
{
struct phy_g12a_usb3_pcie_priv *priv = context;
unsigned int val, reg;
int ret;
ret = phy_g12a_usb3_pcie_cr_bus_addr(priv, addr);
if (ret)
return ret;
reg = FIELD_PREP(PHY_R4_PHY_CR_DATA_IN, data);
regmap_write(priv->regmap, PHY_R4, reg);
regmap_write(priv->regmap, PHY_R4, reg);
regmap_write(priv->regmap, PHY_R4, reg | PHY_R4_PHY_CR_CAP_DATA);
ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
(val & PHY_R5_PHY_CR_ACK),
5, 1000);
if (ret)
return ret;
regmap_write(priv->regmap, PHY_R4, reg);
ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
(val & PHY_R5_PHY_CR_ACK) == 0,
5, 1000);
if (ret)
return ret;
regmap_write(priv->regmap, PHY_R4, reg);
regmap_write(priv->regmap, PHY_R4, reg | PHY_R4_PHY_CR_WRITE);
ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
(val & PHY_R5_PHY_CR_ACK),
5, 1000);
if (ret)
return ret;
regmap_write(priv->regmap, PHY_R4, reg);
ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
(val & PHY_R5_PHY_CR_ACK) == 0,
5, 1000);
if (ret)
return ret;
return 0;
}
static const struct regmap_config phy_g12a_usb3_pcie_cr_regmap_conf = {
.reg_bits = 16,
.val_bits = 16,
.reg_read = phy_g12a_usb3_pcie_cr_bus_read,
.reg_write = phy_g12a_usb3_pcie_cr_bus_write,
.max_register = 0xffff,
.fast_io = true,
};
static int phy_g12a_usb3_init(struct phy *phy)
{
struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy);
int data, ret;
/* Switch PHY to USB3 */
/* TODO figure out how to handle when PCIe was set in the bootloader */
regmap_update_bits(priv->regmap, PHY_R0,
PHY_R0_PCIE_USB3_SWITCH,
PHY_R0_PCIE_USB3_SWITCH);
/*
* WORKAROUND: There is SSPHY suspend bug due to
* which USB enumerates
* in HS mode instead of SS mode. Workaround it by asserting
* LANE0.TX_ALT_BLOCK.EN_ALT_BUS to enable TX to use alt bus
* mode
*/
ret = regmap_update_bits(priv->regmap_cr, 0x102d, BIT(7), BIT(7));
if (ret)
return ret;
ret = regmap_update_bits(priv->regmap_cr, 0x1010, 0xff0, 20);
if (ret)
return ret;
/*
* Fix RX Equalization setting as follows
* LANE0.RX_OVRD_IN_HI. RX_EQ_EN set to 0
* LANE0.RX_OVRD_IN_HI.RX_EQ_EN_OVRD set to 1
* LANE0.RX_OVRD_IN_HI.RX_EQ set to 3
* LANE0.RX_OVRD_IN_HI.RX_EQ_OVRD set to 1
*/
ret = regmap_read(priv->regmap_cr, 0x1006, &data);
if (ret)
return ret;
data &= ~BIT(6);
data |= BIT(7);
data &= ~(0x7 << 8);
data |= (0x3 << 8);
data |= (1 << 11);
ret = regmap_write(priv->regmap_cr, 0x1006, data);
if (ret)
return ret;
/*
* Set EQ and TX launch amplitudes as follows
* LANE0.TX_OVRD_DRV_LO.PREEMPH set to 22
* LANE0.TX_OVRD_DRV_LO.AMPLITUDE set to 127
* LANE0.TX_OVRD_DRV_LO.EN set to 1.
*/
ret = regmap_read(priv->regmap_cr, 0x1002, &data);
if (ret)
return ret;
data &= ~0x3f80;
data |= (0x16 << 7);
data &= ~0x7f;
data |= (0x7f | BIT(14));
ret = regmap_write(priv->regmap_cr, 0x1002, data);
if (ret)
return ret;
/* MPLL_LOOP_CTL.PROP_CNTRL = 8 */
ret = regmap_update_bits(priv->regmap_cr, 0x30, 0xf << 4, 8 << 4);
if (ret)
return ret;
regmap_update_bits(priv->regmap, PHY_R2,
PHY_R2_PHY_TX_VBOOST_LVL,
FIELD_PREP(PHY_R2_PHY_TX_VBOOST_LVL, 0x4));
regmap_update_bits(priv->regmap, PHY_R1,
PHY_R1_PHY_LOS_BIAS | PHY_R1_PHY_LOS_LEVEL,
FIELD_PREP(PHY_R1_PHY_LOS_BIAS, 4) |
FIELD_PREP(PHY_R1_PHY_LOS_LEVEL, 9));
return 0;
}
static int phy_g12a_usb3_pcie_init(struct phy *phy)
{
struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy);
int ret;
ret = reset_control_reset(priv->reset);
if (ret)
return ret;
if (priv->mode == PHY_TYPE_USB3)
return phy_g12a_usb3_init(phy);
/* Power UP PCIE */
/* TODO figure out when the bootloader has set USB3 mode before */
regmap_update_bits(priv->regmap, PHY_R0,
PHY_R0_PCIE_POWER_STATE,
FIELD_PREP(PHY_R0_PCIE_POWER_STATE, 0x1c));
return 0;
}
static int phy_g12a_usb3_pcie_exit(struct phy *phy)
{
struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy);
return reset_control_reset(priv->reset);
}
static struct phy *phy_g12a_usb3_pcie_xlate(struct device *dev,
struct of_phandle_args *args)
{
struct phy_g12a_usb3_pcie_priv *priv = dev_get_drvdata(dev);
unsigned int mode;
if (args->args_count < 1) {
dev_err(dev, "invalid number of arguments\n");
return ERR_PTR(-EINVAL);
}
mode = args->args[0];
if (mode != PHY_TYPE_USB3 && mode != PHY_TYPE_PCIE) {
dev_err(dev, "invalid phy mode select argument\n");
return ERR_PTR(-EINVAL);
}
priv->mode = mode;
return priv->phy;
}
static const struct phy_ops phy_g12a_usb3_pcie_ops = {
.init = phy_g12a_usb3_pcie_init,
.exit = phy_g12a_usb3_pcie_exit,
.owner = THIS_MODULE,
};
static int phy_g12a_usb3_pcie_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct phy_g12a_usb3_pcie_priv *priv;
struct resource *res;
struct phy_provider *phy_provider;
void __iomem *base;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
priv->regmap = devm_regmap_init_mmio(dev, base,
&phy_g12a_usb3_pcie_regmap_conf);
if (IS_ERR(priv->regmap))
return PTR_ERR(priv->regmap);
priv->regmap_cr = devm_regmap_init(dev, NULL, priv,
&phy_g12a_usb3_pcie_cr_regmap_conf);
if (IS_ERR(priv->regmap_cr))
return PTR_ERR(priv->regmap_cr);
priv->clk_ref = devm_clk_get(dev, "ref_clk");
if (IS_ERR(priv->clk_ref))
return PTR_ERR(priv->clk_ref);
ret = clk_prepare_enable(priv->clk_ref);
if (ret)
goto err_disable_clk_ref;
priv->reset = devm_reset_control_array_get(dev, false, false);
if (IS_ERR(priv->reset))
return PTR_ERR(priv->reset);
priv->phy = devm_phy_create(dev, np, &phy_g12a_usb3_pcie_ops);
if (IS_ERR(priv->phy)) {
ret = PTR_ERR(priv->phy);
if (ret != -EPROBE_DEFER)
dev_err(dev, "failed to create PHY\n");
return ret;
}
phy_set_drvdata(priv->phy, priv);
dev_set_drvdata(dev, priv);
phy_provider = devm_of_phy_provider_register(dev,
phy_g12a_usb3_pcie_xlate);
return PTR_ERR_OR_ZERO(phy_provider);
err_disable_clk_ref:
clk_disable_unprepare(priv->clk_ref);
return ret;
}
static const struct of_device_id phy_g12a_usb3_pcie_of_match[] = {
{ .compatible = "amlogic,g12a-usb3-pcie-phy", },
{ },
};
MODULE_DEVICE_TABLE(of, phy_g12a_usb3_pcie_of_match);
static struct platform_driver phy_g12a_usb3_pcie_driver = {
.probe = phy_g12a_usb3_pcie_probe,
.driver = {
.name = "phy-g12a-usb3-pcie",
.of_match_table = phy_g12a_usb3_pcie_of_match,
},
};
module_platform_driver(phy_g12a_usb3_pcie_driver);
MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
MODULE_DESCRIPTION("Amlogic G12A USB3 + PCIE Combo PHY driver");
MODULE_LICENSE("GPL v2");

View File

@ -261,14 +261,9 @@ static int phy_meson_gxl_usb2_probe(struct platform_device *pdev)
if (IS_ERR(priv->regmap))
return PTR_ERR(priv->regmap);
priv->clk = devm_clk_get(dev, "phy");
if (IS_ERR(priv->clk)) {
ret = PTR_ERR(priv->clk);
if (ret == -ENOENT)
priv->clk = NULL;
else
return ret;
}
priv->clk = devm_clk_get_optional(dev, "phy");
if (IS_ERR(priv->clk))
return PTR_ERR(priv->clk);
priv->reset = devm_reset_control_get_optional_shared(dev, "phy");
if (IS_ERR(priv->reset))

View File

@ -10,6 +10,17 @@ config PHY_CYGNUS_PCIE
Enable this to support the Broadcom Cygnus PCIe PHY.
If unsure, say N.
config PHY_BCM_SR_USB
tristate "Broadcom Stingray USB PHY driver"
depends on OF && (ARCH_BCM_IPROC || COMPILE_TEST)
select GENERIC_PHY
default ARCH_BCM_IPROC
help
Enable this to support the Broadcom Stingray USB PHY
driver. It supports all versions of Superspeed and
Highspeed PHYs.
If unsure, say N.
config BCM_KONA_USB2_PHY
tristate "Broadcom Kona USB2 PHY Driver"
depends on HAS_IOMEM

View File

@ -11,3 +11,4 @@ obj-$(CONFIG_PHY_BRCM_USB) += phy-brcm-usb-dvr.o
phy-brcm-usb-dvr-objs := phy-brcm-usb.o phy-brcm-usb-init.o
obj-$(CONFIG_PHY_BCM_SR_PCIE) += phy-bcm-sr-pcie.o
obj-$(CONFIG_PHY_BCM_SR_USB) += phy-bcm-sr-usb.o

View File

@ -0,0 +1,394 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2016-2018 Broadcom
*/
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
enum bcm_usb_phy_version {
BCM_SR_USB_COMBO_PHY,
BCM_SR_USB_HS_PHY,
};
enum bcm_usb_phy_reg {
PLL_NDIV_FRAC,
PLL_NDIV_INT,
PLL_CTRL,
PHY_CTRL,
PHY_PLL_CTRL,
};
/* USB PHY registers */
static const u8 bcm_usb_combo_phy_ss[] = {
[PLL_CTRL] = 0x18,
[PHY_CTRL] = 0x14,
};
static const u8 bcm_usb_combo_phy_hs[] = {
[PLL_NDIV_FRAC] = 0x04,
[PLL_NDIV_INT] = 0x08,
[PLL_CTRL] = 0x0c,
[PHY_CTRL] = 0x10,
};
#define HSPLL_NDIV_INT_VAL 0x13
#define HSPLL_NDIV_FRAC_VAL 0x1005
static const u8 bcm_usb_hs_phy[] = {
[PLL_NDIV_FRAC] = 0x0,
[PLL_NDIV_INT] = 0x4,
[PLL_CTRL] = 0x8,
[PHY_CTRL] = 0xc,
};
enum pll_ctrl_bits {
PLL_RESETB,
SSPLL_SUSPEND_EN,
PLL_SEQ_START,
PLL_LOCK,
PLL_PDIV,
};
static const u8 u3pll_ctrl[] = {
[PLL_RESETB] = 0,
[SSPLL_SUSPEND_EN] = 1,
[PLL_SEQ_START] = 2,
[PLL_LOCK] = 3,
};
#define HSPLL_PDIV_MASK 0xF
#define HSPLL_PDIV_VAL 0x1
static const u8 u2pll_ctrl[] = {
[PLL_PDIV] = 1,
[PLL_RESETB] = 5,
[PLL_LOCK] = 6,
};
enum bcm_usb_phy_ctrl_bits {
CORERDY,
AFE_LDO_PWRDWNB,
AFE_PLL_PWRDWNB,
AFE_BG_PWRDWNB,
PHY_ISO,
PHY_RESETB,
PHY_PCTL,
};
#define PHY_PCTL_MASK 0xffff
/*
* 0x0806 of PCTL_VAL has below bits set
* BIT-8 : refclk divider 1
* BIT-3:2: device mode; mode is not effect
* BIT-1: soft reset active low
*/
#define HSPHY_PCTL_VAL 0x0806
#define SSPHY_PCTL_VAL 0x0006
static const u8 u3phy_ctrl[] = {
[PHY_RESETB] = 1,
[PHY_PCTL] = 2,
};
static const u8 u2phy_ctrl[] = {
[CORERDY] = 0,
[AFE_LDO_PWRDWNB] = 1,
[AFE_PLL_PWRDWNB] = 2,
[AFE_BG_PWRDWNB] = 3,
[PHY_ISO] = 4,
[PHY_RESETB] = 5,
[PHY_PCTL] = 6,
};
struct bcm_usb_phy_cfg {
uint32_t type;
uint32_t version;
void __iomem *regs;
struct phy *phy;
const u8 *offset;
};
#define PLL_LOCK_RETRY_COUNT 1000
enum bcm_usb_phy_type {
USB_HS_PHY,
USB_SS_PHY,
};
#define NUM_BCM_SR_USB_COMBO_PHYS 2
static inline void bcm_usb_reg32_clrbits(void __iomem *addr, uint32_t clear)
{
writel(readl(addr) & ~clear, addr);
}
static inline void bcm_usb_reg32_setbits(void __iomem *addr, uint32_t set)
{
writel(readl(addr) | set, addr);
}
static int bcm_usb_pll_lock_check(void __iomem *addr, u32 bit)
{
int retry;
u32 rd_data;
retry = PLL_LOCK_RETRY_COUNT;
do {
rd_data = readl(addr);
if (rd_data & bit)
return 0;
udelay(1);
} while (--retry > 0);
pr_err("%s: FAIL\n", __func__);
return -ETIMEDOUT;
}
static int bcm_usb_ss_phy_init(struct bcm_usb_phy_cfg *phy_cfg)
{
int ret = 0;
void __iomem *regs = phy_cfg->regs;
const u8 *offset;
u32 rd_data;
offset = phy_cfg->offset;
/* Set pctl with mode and soft reset */
rd_data = readl(regs + offset[PHY_CTRL]);
rd_data &= ~(PHY_PCTL_MASK << u3phy_ctrl[PHY_PCTL]);
rd_data |= (SSPHY_PCTL_VAL << u3phy_ctrl[PHY_PCTL]);
writel(rd_data, regs + offset[PHY_CTRL]);
bcm_usb_reg32_clrbits(regs + offset[PLL_CTRL],
BIT(u3pll_ctrl[SSPLL_SUSPEND_EN]));
bcm_usb_reg32_setbits(regs + offset[PLL_CTRL],
BIT(u3pll_ctrl[PLL_SEQ_START]));
bcm_usb_reg32_setbits(regs + offset[PLL_CTRL],
BIT(u3pll_ctrl[PLL_RESETB]));
/* Maximum timeout for PLL reset done */
msleep(30);
ret = bcm_usb_pll_lock_check(regs + offset[PLL_CTRL],
BIT(u3pll_ctrl[PLL_LOCK]));
return ret;
}
static int bcm_usb_hs_phy_init(struct bcm_usb_phy_cfg *phy_cfg)
{
int ret = 0;
void __iomem *regs = phy_cfg->regs;
const u8 *offset;
u32 rd_data;
offset = phy_cfg->offset;
writel(HSPLL_NDIV_INT_VAL, regs + offset[PLL_NDIV_INT]);
writel(HSPLL_NDIV_FRAC_VAL, regs + offset[PLL_NDIV_FRAC]);
rd_data = readl(regs + offset[PLL_CTRL]);
rd_data &= ~(HSPLL_PDIV_MASK << u2pll_ctrl[PLL_PDIV]);
rd_data |= (HSPLL_PDIV_VAL << u2pll_ctrl[PLL_PDIV]);
writel(rd_data, regs + offset[PLL_CTRL]);
/* Set Core Ready high */
bcm_usb_reg32_setbits(regs + offset[PHY_CTRL],
BIT(u2phy_ctrl[CORERDY]));
/* Maximum timeout for Core Ready done */
msleep(30);
bcm_usb_reg32_setbits(regs + offset[PLL_CTRL],
BIT(u2pll_ctrl[PLL_RESETB]));
bcm_usb_reg32_setbits(regs + offset[PHY_CTRL],
BIT(u2phy_ctrl[PHY_RESETB]));
rd_data = readl(regs + offset[PHY_CTRL]);
rd_data &= ~(PHY_PCTL_MASK << u2phy_ctrl[PHY_PCTL]);
rd_data |= (HSPHY_PCTL_VAL << u2phy_ctrl[PHY_PCTL]);
writel(rd_data, regs + offset[PHY_CTRL]);
/* Maximum timeout for PLL reset done */
msleep(30);
ret = bcm_usb_pll_lock_check(regs + offset[PLL_CTRL],
BIT(u2pll_ctrl[PLL_LOCK]));
return ret;
}
static int bcm_usb_phy_reset(struct phy *phy)
{
struct bcm_usb_phy_cfg *phy_cfg = phy_get_drvdata(phy);
void __iomem *regs = phy_cfg->regs;
const u8 *offset;
offset = phy_cfg->offset;
if (phy_cfg->type == USB_HS_PHY) {
bcm_usb_reg32_clrbits(regs + offset[PHY_CTRL],
BIT(u2phy_ctrl[CORERDY]));
bcm_usb_reg32_setbits(regs + offset[PHY_CTRL],
BIT(u2phy_ctrl[CORERDY]));
}
return 0;
}
static int bcm_usb_phy_init(struct phy *phy)
{
struct bcm_usb_phy_cfg *phy_cfg = phy_get_drvdata(phy);
int ret = -EINVAL;
if (phy_cfg->type == USB_SS_PHY)
ret = bcm_usb_ss_phy_init(phy_cfg);
else if (phy_cfg->type == USB_HS_PHY)
ret = bcm_usb_hs_phy_init(phy_cfg);
return ret;
}
static struct phy_ops sr_phy_ops = {
.init = bcm_usb_phy_init,
.reset = bcm_usb_phy_reset,
.owner = THIS_MODULE,
};
static struct phy *bcm_usb_phy_xlate(struct device *dev,
struct of_phandle_args *args)
{
struct bcm_usb_phy_cfg *phy_cfg;
int phy_idx;
phy_cfg = dev_get_drvdata(dev);
if (!phy_cfg)
return ERR_PTR(-EINVAL);
if (phy_cfg->version == BCM_SR_USB_COMBO_PHY) {
phy_idx = args->args[0];
if (WARN_ON(phy_idx > 1))
return ERR_PTR(-ENODEV);
return phy_cfg[phy_idx].phy;
} else
return phy_cfg->phy;
}
static int bcm_usb_phy_create(struct device *dev, struct device_node *node,
void __iomem *regs, uint32_t version)
{
struct bcm_usb_phy_cfg *phy_cfg;
int idx;
if (version == BCM_SR_USB_COMBO_PHY) {
phy_cfg = devm_kzalloc(dev, NUM_BCM_SR_USB_COMBO_PHYS *
sizeof(struct bcm_usb_phy_cfg),
GFP_KERNEL);
if (!phy_cfg)
return -ENOMEM;
for (idx = 0; idx < NUM_BCM_SR_USB_COMBO_PHYS; idx++) {
phy_cfg[idx].regs = regs;
phy_cfg[idx].version = version;
if (idx == 0) {
phy_cfg[idx].offset = bcm_usb_combo_phy_hs;
phy_cfg[idx].type = USB_HS_PHY;
} else {
phy_cfg[idx].offset = bcm_usb_combo_phy_ss;
phy_cfg[idx].type = USB_SS_PHY;
}
phy_cfg[idx].phy = devm_phy_create(dev, node,
&sr_phy_ops);
if (IS_ERR(phy_cfg[idx].phy))
return PTR_ERR(phy_cfg[idx].phy);
phy_set_drvdata(phy_cfg[idx].phy, &phy_cfg[idx]);
}
} else if (version == BCM_SR_USB_HS_PHY) {
phy_cfg = devm_kzalloc(dev, sizeof(struct bcm_usb_phy_cfg),
GFP_KERNEL);
if (!phy_cfg)
return -ENOMEM;
phy_cfg->regs = regs;
phy_cfg->version = version;
phy_cfg->offset = bcm_usb_hs_phy;
phy_cfg->type = USB_HS_PHY;
phy_cfg->phy = devm_phy_create(dev, node, &sr_phy_ops);
if (IS_ERR(phy_cfg->phy))
return PTR_ERR(phy_cfg->phy);
phy_set_drvdata(phy_cfg->phy, phy_cfg);
} else
return -ENODEV;
dev_set_drvdata(dev, phy_cfg);
return 0;
}
static const struct of_device_id bcm_usb_phy_of_match[] = {
{
.compatible = "brcm,sr-usb-combo-phy",
.data = (void *)BCM_SR_USB_COMBO_PHY,
},
{
.compatible = "brcm,sr-usb-hs-phy",
.data = (void *)BCM_SR_USB_HS_PHY,
},
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, bcm_usb_phy_of_match);
static int bcm_usb_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *dn = dev->of_node;
const struct of_device_id *of_id;
struct resource *res;
void __iomem *regs;
int ret;
enum bcm_usb_phy_version version;
struct phy_provider *phy_provider;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
regs = devm_ioremap_resource(dev, res);
if (IS_ERR(regs))
return PTR_ERR(regs);
of_id = of_match_node(bcm_usb_phy_of_match, dn);
if (of_id)
version = (enum bcm_usb_phy_version)of_id->data;
else
return -ENODEV;
ret = bcm_usb_phy_create(dev, dn, regs, version);
if (ret)
return ret;
phy_provider = devm_of_phy_provider_register(dev, bcm_usb_phy_xlate);
return PTR_ERR_OR_ZERO(phy_provider);
}
static struct platform_driver bcm_usb_phy_driver = {
.driver = {
.name = "phy-bcm-sr-usb",
.of_match_table = bcm_usb_phy_of_match,
},
.probe = bcm_usb_phy_probe,
};
module_platform_driver(bcm_usb_phy_driver);
MODULE_AUTHOR("Broadcom");
MODULE_DESCRIPTION("Broadcom stingray USB Phy driver");
MODULE_LICENSE("GPL v2");

View File

@ -6,6 +6,7 @@
#include <linux/module.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)
@ -24,6 +25,7 @@ struct imx8mq_usb_phy {
struct phy *phy;
struct clk *clk;
void __iomem *base;
struct regulator *vbus;
};
static int imx8mq_usb_phy_init(struct phy *phy)
@ -55,6 +57,11 @@ static int imx8mq_usb_phy_init(struct phy *phy)
static int imx8mq_phy_power_on(struct phy *phy)
{
struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy);
int ret;
ret = regulator_enable(imx_phy->vbus);
if (ret)
return ret;
return clk_prepare_enable(imx_phy->clk);
}
@ -64,6 +71,7 @@ static int imx8mq_phy_power_off(struct phy *phy)
struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy);
clk_disable_unprepare(imx_phy->clk);
regulator_disable(imx_phy->vbus);
return 0;
}
@ -101,6 +109,10 @@ static int imx8mq_usb_phy_probe(struct platform_device *pdev)
if (IS_ERR(imx_phy->phy))
return PTR_ERR(imx_phy->phy);
imx_phy->vbus = devm_regulator_get(dev, "vbus");
if (IS_ERR(imx_phy->vbus))
return PTR_ERR(imx_phy->vbus);
phy_set_drvdata(imx_phy->phy, imx_phy);
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);

View File

@ -12,6 +12,16 @@ config PHY_HI6220_USB
To compile this driver as a module, choose M here.
config PHY_HI3660_USB
tristate "hi3660 USB PHY support"
depends on (ARCH_HISI && ARM64) || COMPILE_TEST
select GENERIC_PHY
select MFD_SYSCON
help
Enable this to support the HISILICON HI3660 USB PHY.
To compile this driver as a module, choose M here.
config PHY_HISTB_COMBPHY
tristate "HiSilicon STB SoCs COMBPHY support"
depends on (ARCH_HISI && ARM64) || COMPILE_TEST

View File

@ -1,4 +1,5 @@
obj-$(CONFIG_PHY_HI6220_USB) += phy-hi6220-usb.o
obj-$(CONFIG_PHY_HI3660_USB) += phy-hi3660-usb3.o
obj-$(CONFIG_PHY_HISTB_COMBPHY) += phy-histb-combphy.o
obj-$(CONFIG_PHY_HISI_INNO_USB2) += phy-hisi-inno-usb2.o
obj-$(CONFIG_PHY_HIX5HD2_SATA) += phy-hix5hd2-sata.o

View File

@ -0,0 +1,233 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Phy provider for USB 3.0 controller on HiSilicon 3660 platform
*
* Copyright (C) 2017-2018 Hilisicon Electronics Co., Ltd.
* http://www.huawei.com
*
* Authors: Yu Chen <chenyu56@huawei.com>
*/
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#define PERI_CRG_CLK_EN4 0x40
#define PERI_CRG_CLK_DIS4 0x44
#define GT_CLK_USB3OTG_REF BIT(0)
#define GT_ACLK_USB3OTG BIT(1)
#define PERI_CRG_RSTEN4 0x90
#define PERI_CRG_RSTDIS4 0x94
#define IP_RST_USB3OTGPHY_POR BIT(3)
#define IP_RST_USB3OTG BIT(5)
#define PERI_CRG_ISODIS 0x148
#define USB_REFCLK_ISO_EN BIT(25)
#define PCTRL_PERI_CTRL3 0x10
#define PCTRL_PERI_CTRL3_MSK_START 16
#define USB_TCXO_EN BIT(1)
#define PCTRL_PERI_CTRL24 0x64
#define SC_CLK_USB3PHY_3MUX1_SEL BIT(25)
#define USBOTG3_CTRL0 0x00
#define SC_USB3PHY_ABB_GT_EN BIT(15)
#define USBOTG3_CTRL2 0x08
#define USBOTG3CTRL2_POWERDOWN_HSP BIT(0)
#define USBOTG3CTRL2_POWERDOWN_SSP BIT(1)
#define USBOTG3_CTRL3 0x0C
#define USBOTG3_CTRL3_VBUSVLDEXT BIT(6)
#define USBOTG3_CTRL3_VBUSVLDEXTSEL BIT(5)
#define USBOTG3_CTRL4 0x10
#define USBOTG3_CTRL7 0x1c
#define REF_SSP_EN BIT(16)
/* This value config the default txtune parameter of the usb 2.0 phy */
#define HI3660_USB_DEFAULT_PHY_PARAM 0x1c466e3
struct hi3660_priv {
struct device *dev;
struct regmap *peri_crg;
struct regmap *pctrl;
struct regmap *otg_bc;
u32 eye_diagram_param;
};
static int hi3660_phy_init(struct phy *phy)
{
struct hi3660_priv *priv = phy_get_drvdata(phy);
u32 val, mask;
int ret;
/* usb refclk iso disable */
ret = regmap_write(priv->peri_crg, PERI_CRG_ISODIS, USB_REFCLK_ISO_EN);
if (ret)
goto out;
/* enable usb_tcxo_en */
val = USB_TCXO_EN | (USB_TCXO_EN << PCTRL_PERI_CTRL3_MSK_START);
ret = regmap_write(priv->pctrl, PCTRL_PERI_CTRL3, val);
if (ret)
goto out;
/* assert phy */
val = IP_RST_USB3OTGPHY_POR | IP_RST_USB3OTG;
ret = regmap_write(priv->peri_crg, PERI_CRG_RSTEN4, val);
if (ret)
goto out;
/* enable phy ref clk */
val = SC_USB3PHY_ABB_GT_EN;
mask = val;
ret = regmap_update_bits(priv->otg_bc, USBOTG3_CTRL0, mask, val);
if (ret)
goto out;
val = REF_SSP_EN;
mask = val;
ret = regmap_update_bits(priv->otg_bc, USBOTG3_CTRL7, mask, val);
if (ret)
goto out;
/* exit from IDDQ mode */
mask = USBOTG3CTRL2_POWERDOWN_HSP | USBOTG3CTRL2_POWERDOWN_SSP;
ret = regmap_update_bits(priv->otg_bc, USBOTG3_CTRL2, mask, 0);
if (ret)
goto out;
/* delay for exit from IDDQ mode */
usleep_range(100, 120);
/* deassert phy */
val = IP_RST_USB3OTGPHY_POR | IP_RST_USB3OTG;
ret = regmap_write(priv->peri_crg, PERI_CRG_RSTDIS4, val);
if (ret)
goto out;
/* delay for phy deasserted */
usleep_range(10000, 15000);
/* fake vbus valid signal */
val = USBOTG3_CTRL3_VBUSVLDEXT | USBOTG3_CTRL3_VBUSVLDEXTSEL;
mask = val;
ret = regmap_update_bits(priv->otg_bc, USBOTG3_CTRL3, mask, val);
if (ret)
goto out;
/* delay for vbus valid */
usleep_range(100, 120);
ret = regmap_write(priv->otg_bc, USBOTG3_CTRL4,
priv->eye_diagram_param);
if (ret)
goto out;
return 0;
out:
dev_err(priv->dev, "failed to init phy ret: %d\n", ret);
return ret;
}
static int hi3660_phy_exit(struct phy *phy)
{
struct hi3660_priv *priv = phy_get_drvdata(phy);
u32 val;
int ret;
/* assert phy */
val = IP_RST_USB3OTGPHY_POR;
ret = regmap_write(priv->peri_crg, PERI_CRG_RSTEN4, val);
if (ret)
goto out;
/* disable usb_tcxo_en */
val = USB_TCXO_EN << PCTRL_PERI_CTRL3_MSK_START;
ret = regmap_write(priv->pctrl, PCTRL_PERI_CTRL3, val);
if (ret)
goto out;
return 0;
out:
dev_err(priv->dev, "failed to exit phy ret: %d\n", ret);
return ret;
}
static struct phy_ops hi3660_phy_ops = {
.init = hi3660_phy_init,
.exit = hi3660_phy_exit,
.owner = THIS_MODULE,
};
static int hi3660_phy_probe(struct platform_device *pdev)
{
struct phy_provider *phy_provider;
struct device *dev = &pdev->dev;
struct phy *phy;
struct hi3660_priv *priv;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
priv->peri_crg = syscon_regmap_lookup_by_phandle(dev->of_node,
"hisilicon,pericrg-syscon");
if (IS_ERR(priv->peri_crg)) {
dev_err(dev, "no hisilicon,pericrg-syscon\n");
return PTR_ERR(priv->peri_crg);
}
priv->pctrl = syscon_regmap_lookup_by_phandle(dev->of_node,
"hisilicon,pctrl-syscon");
if (IS_ERR(priv->pctrl)) {
dev_err(dev, "no hisilicon,pctrl-syscon\n");
return PTR_ERR(priv->pctrl);
}
/* node of hi3660 phy is a sub-node of usb3_otg_bc */
priv->otg_bc = syscon_node_to_regmap(dev->parent->of_node);
if (IS_ERR(priv->otg_bc)) {
dev_err(dev, "no hisilicon,usb3-otg-bc-syscon\n");
return PTR_ERR(priv->otg_bc);
}
if (of_property_read_u32(dev->of_node, "hisilicon,eye-diagram-param",
&(priv->eye_diagram_param)))
priv->eye_diagram_param = HI3660_USB_DEFAULT_PHY_PARAM;
phy = devm_phy_create(dev, NULL, &hi3660_phy_ops);
if (IS_ERR(phy))
return PTR_ERR(phy);
phy_set_drvdata(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 hi3660_phy_of_match[] = {
{.compatible = "hisilicon,hi3660-usb-phy",},
{ }
};
MODULE_DEVICE_TABLE(of, hi3660_phy_of_match);
static struct platform_driver hi3660_phy_driver = {
.probe = hi3660_phy_probe,
.driver = {
.name = "hi3660-usb-phy",
.of_match_table = hi3660_phy_of_match,
}
};
module_platform_driver(hi3660_phy_driver);
MODULE_AUTHOR("Yu Chen <chenyu56@huawei.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Hilisicon Hi3660 USB3 PHY Driver");

View File

@ -13,6 +13,16 @@ config PHY_MTK_TPHY
multi-ports is first version, otherwise is second veriosn,
so you can easily distinguish them by banks layout.
config PHY_MTK_UFS
tristate "MediaTek UFS M-PHY driver"
depends on ARCH_MEDIATEK && OF
select GENERIC_PHY
help
Support for UFS M-PHY on MediaTek chipsets.
Enable this to provide vendor-specific probing,
initialization, power on and power off flow of
specified M-PHYs.
config PHY_MTK_XSPHY
tristate "MediaTek XS-PHY Driver"
depends on ARCH_MEDIATEK && OF

View File

@ -4,4 +4,5 @@
#
obj-$(CONFIG_PHY_MTK_TPHY) += phy-mtk-tphy.o
obj-$(CONFIG_PHY_MTK_UFS) += phy-mtk-ufs.o
obj-$(CONFIG_PHY_MTK_XSPHY) += phy-mtk-xsphy.o

View File

@ -1103,13 +1103,9 @@ static int mtk_tphy_probe(struct platform_device *pdev)
}
/* it's deprecated, make it optional for backward compatibility */
tphy->u3phya_ref = devm_clk_get(dev, "u3phya_ref");
if (IS_ERR(tphy->u3phya_ref)) {
if (PTR_ERR(tphy->u3phya_ref) == -EPROBE_DEFER)
return -EPROBE_DEFER;
tphy->u3phya_ref = NULL;
}
tphy->u3phya_ref = devm_clk_get_optional(dev, "u3phya_ref");
if (IS_ERR(tphy->u3phya_ref))
return PTR_ERR(tphy->u3phya_ref);
tphy->src_ref_clk = U3P_REF_CLK;
tphy->src_coef = U3P_SLEW_RATE_COEF;

View File

@ -0,0 +1,245 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2019 MediaTek Inc.
* Author: Stanley Chu <stanley.chu@mediatek.com>
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
/* mphy register and offsets */
#define MP_GLB_DIG_8C 0x008C
#define FRC_PLL_ISO_EN BIT(8)
#define PLL_ISO_EN BIT(9)
#define FRC_FRC_PWR_ON BIT(10)
#define PLL_PWR_ON BIT(11)
#define MP_LN_DIG_RX_9C 0xA09C
#define FSM_DIFZ_FRC BIT(18)
#define MP_LN_DIG_RX_AC 0xA0AC
#define FRC_RX_SQ_EN BIT(0)
#define RX_SQ_EN BIT(1)
#define MP_LN_RX_44 0xB044
#define FRC_CDR_PWR_ON BIT(17)
#define CDR_PWR_ON BIT(18)
#define FRC_CDR_ISO_EN BIT(19)
#define CDR_ISO_EN BIT(20)
struct ufs_mtk_phy {
struct device *dev;
void __iomem *mmio;
struct clk *mp_clk;
struct clk *unipro_clk;
};
static inline u32 mphy_readl(struct ufs_mtk_phy *phy, u32 reg)
{
return readl(phy->mmio + reg);
}
static inline void mphy_writel(struct ufs_mtk_phy *phy, u32 val, u32 reg)
{
writel(val, phy->mmio + reg);
}
static void mphy_set_bit(struct ufs_mtk_phy *phy, u32 reg, u32 bit)
{
u32 val;
val = mphy_readl(phy, reg);
val |= bit;
mphy_writel(phy, val, reg);
}
static void mphy_clr_bit(struct ufs_mtk_phy *phy, u32 reg, u32 bit)
{
u32 val;
val = mphy_readl(phy, reg);
val &= ~bit;
mphy_writel(phy, val, reg);
}
static struct ufs_mtk_phy *get_ufs_mtk_phy(struct phy *generic_phy)
{
return (struct ufs_mtk_phy *)phy_get_drvdata(generic_phy);
}
static int ufs_mtk_phy_clk_init(struct ufs_mtk_phy *phy)
{
struct device *dev = phy->dev;
phy->unipro_clk = devm_clk_get(dev, "unipro");
if (IS_ERR(phy->unipro_clk)) {
dev_err(dev, "failed to get clock: unipro");
return PTR_ERR(phy->unipro_clk);
}
phy->mp_clk = devm_clk_get(dev, "mp");
if (IS_ERR(phy->mp_clk)) {
dev_err(dev, "failed to get clock: mp");
return PTR_ERR(phy->mp_clk);
}
return 0;
}
static void ufs_mtk_phy_set_active(struct ufs_mtk_phy *phy)
{
/* release DA_MP_PLL_PWR_ON */
mphy_set_bit(phy, MP_GLB_DIG_8C, PLL_PWR_ON);
mphy_clr_bit(phy, MP_GLB_DIG_8C, FRC_FRC_PWR_ON);
/* release DA_MP_PLL_ISO_EN */
mphy_clr_bit(phy, MP_GLB_DIG_8C, PLL_ISO_EN);
mphy_clr_bit(phy, MP_GLB_DIG_8C, FRC_PLL_ISO_EN);
/* release DA_MP_CDR_PWR_ON */
mphy_set_bit(phy, MP_LN_RX_44, CDR_PWR_ON);
mphy_clr_bit(phy, MP_LN_RX_44, FRC_CDR_PWR_ON);
/* release DA_MP_CDR_ISO_EN */
mphy_clr_bit(phy, MP_LN_RX_44, CDR_ISO_EN);
mphy_clr_bit(phy, MP_LN_RX_44, FRC_CDR_ISO_EN);
/* release DA_MP_RX0_SQ_EN */
mphy_set_bit(phy, MP_LN_DIG_RX_AC, RX_SQ_EN);
mphy_clr_bit(phy, MP_LN_DIG_RX_AC, FRC_RX_SQ_EN);
/* delay 1us to wait DIFZ stable */
udelay(1);
/* release DIFZ */
mphy_clr_bit(phy, MP_LN_DIG_RX_9C, FSM_DIFZ_FRC);
}
static void ufs_mtk_phy_set_deep_hibern(struct ufs_mtk_phy *phy)
{
/* force DIFZ */
mphy_set_bit(phy, MP_LN_DIG_RX_9C, FSM_DIFZ_FRC);
/* force DA_MP_RX0_SQ_EN */
mphy_set_bit(phy, MP_LN_DIG_RX_AC, FRC_RX_SQ_EN);
mphy_clr_bit(phy, MP_LN_DIG_RX_AC, RX_SQ_EN);
/* force DA_MP_CDR_ISO_EN */
mphy_set_bit(phy, MP_LN_RX_44, FRC_CDR_ISO_EN);
mphy_set_bit(phy, MP_LN_RX_44, CDR_ISO_EN);
/* force DA_MP_CDR_PWR_ON */
mphy_set_bit(phy, MP_LN_RX_44, FRC_CDR_PWR_ON);
mphy_clr_bit(phy, MP_LN_RX_44, CDR_PWR_ON);
/* force DA_MP_PLL_ISO_EN */
mphy_set_bit(phy, MP_GLB_DIG_8C, FRC_PLL_ISO_EN);
mphy_set_bit(phy, MP_GLB_DIG_8C, PLL_ISO_EN);
/* force DA_MP_PLL_PWR_ON */
mphy_set_bit(phy, MP_GLB_DIG_8C, FRC_FRC_PWR_ON);
mphy_clr_bit(phy, MP_GLB_DIG_8C, PLL_PWR_ON);
}
static int ufs_mtk_phy_power_on(struct phy *generic_phy)
{
struct ufs_mtk_phy *phy = get_ufs_mtk_phy(generic_phy);
int ret;
ret = clk_prepare_enable(phy->unipro_clk);
if (ret) {
dev_err(phy->dev, "unipro_clk enable failed %d\n", ret);
goto out;
}
ret = clk_prepare_enable(phy->mp_clk);
if (ret) {
dev_err(phy->dev, "mp_clk enable failed %d\n", ret);
goto out_unprepare_unipro_clk;
}
ufs_mtk_phy_set_active(phy);
return 0;
out_unprepare_unipro_clk:
clk_disable_unprepare(phy->unipro_clk);
out:
return ret;
}
static int ufs_mtk_phy_power_off(struct phy *generic_phy)
{
struct ufs_mtk_phy *phy = get_ufs_mtk_phy(generic_phy);
ufs_mtk_phy_set_deep_hibern(phy);
clk_disable_unprepare(phy->unipro_clk);
clk_disable_unprepare(phy->mp_clk);
return 0;
}
static const struct phy_ops ufs_mtk_phy_ops = {
.power_on = ufs_mtk_phy_power_on,
.power_off = ufs_mtk_phy_power_off,
.owner = THIS_MODULE,
};
static int ufs_mtk_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct phy *generic_phy;
struct phy_provider *phy_provider;
struct resource *res;
struct ufs_mtk_phy *phy;
int ret;
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
if (!phy)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
phy->mmio = devm_ioremap_resource(dev, res);
if (IS_ERR(phy->mmio))
return PTR_ERR(phy->mmio);
phy->dev = dev;
ret = ufs_mtk_phy_clk_init(phy);
if (ret)
return ret;
generic_phy = devm_phy_create(dev, NULL, &ufs_mtk_phy_ops);
if (IS_ERR(generic_phy))
return PTR_ERR(generic_phy);
phy_set_drvdata(generic_phy, phy);
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 ufs_mtk_phy_of_match[] = {
{.compatible = "mediatek,mt8183-ufsphy"},
{},
};
MODULE_DEVICE_TABLE(of, ufs_mtk_phy_of_match);
static struct platform_driver ufs_mtk_phy_driver = {
.probe = ufs_mtk_phy_probe,
.driver = {
.of_match_table = ufs_mtk_phy_of_match,
.name = "ufs_mtk_phy",
},
};
module_platform_driver(ufs_mtk_phy_driver);
MODULE_DESCRIPTION("Universal Flash Storage (UFS) MediaTek MPHY");
MODULE_AUTHOR("Stanley Chu <stanley.chu@mediatek.com>");
MODULE_LICENSE("GPL v2");

View File

@ -31,6 +31,238 @@ struct serdes_macro {
struct serdes_ctrl *ctrl;
};
#define MCB_S6G_CFG_TIMEOUT 50
static int __serdes_write_mcb_s6g(struct regmap *regmap, u8 macro, u32 op)
{
unsigned int regval = 0;
regmap_write(regmap, HSIO_MCB_S6G_ADDR_CFG, op |
HSIO_MCB_S6G_ADDR_CFG_SERDES6G_ADDR(BIT(macro)));
return regmap_read_poll_timeout(regmap, HSIO_MCB_S6G_ADDR_CFG, regval,
(regval & op) != op, 100,
MCB_S6G_CFG_TIMEOUT * 1000);
}
static int serdes_commit_mcb_s6g(struct regmap *regmap, u8 macro)
{
return __serdes_write_mcb_s6g(regmap, macro,
HSIO_MCB_S6G_ADDR_CFG_SERDES6G_WR_ONE_SHOT);
}
static int serdes_update_mcb_s6g(struct regmap *regmap, u8 macro)
{
return __serdes_write_mcb_s6g(regmap, macro,
HSIO_MCB_S6G_ADDR_CFG_SERDES6G_RD_ONE_SHOT);
}
static int serdes_init_s6g(struct regmap *regmap, u8 serdes, int mode)
{
u32 pll_fsm_ctrl_data;
u32 ob_ena1v_mode;
u32 des_bw_ana;
u32 ob_ena_cas;
u32 if_mode;
u32 ob_lev;
u32 qrate;
int ret;
if (mode == PHY_INTERFACE_MODE_QSGMII) {
pll_fsm_ctrl_data = 120;
ob_ena1v_mode = 0;
ob_ena_cas = 0;
des_bw_ana = 5;
ob_lev = 24;
if_mode = 3;
qrate = 0;
} else {
pll_fsm_ctrl_data = 60;
ob_ena1v_mode = 1;
ob_ena_cas = 2;
des_bw_ana = 3;
ob_lev = 48;
if_mode = 1;
qrate = 1;
}
ret = serdes_update_mcb_s6g(regmap, serdes);
if (ret)
return ret;
/* Test pattern */
regmap_update_bits(regmap, HSIO_S6G_COMMON_CFG,
HSIO_S6G_COMMON_CFG_SYS_RST, 0);
regmap_update_bits(regmap, HSIO_S6G_PLL_CFG,
HSIO_S6G_PLL_CFG_PLL_FSM_ENA, 0);
regmap_update_bits(regmap, HSIO_S6G_IB_CFG,
HSIO_S6G_IB_CFG_IB_SIG_DET_ENA |
HSIO_S6G_IB_CFG_IB_REG_ENA |
HSIO_S6G_IB_CFG_IB_SAM_ENA |
HSIO_S6G_IB_CFG_IB_EQZ_ENA |
HSIO_S6G_IB_CFG_IB_CONCUR |
HSIO_S6G_IB_CFG_IB_CAL_ENA,
HSIO_S6G_IB_CFG_IB_SIG_DET_ENA |
HSIO_S6G_IB_CFG_IB_REG_ENA |
HSIO_S6G_IB_CFG_IB_SAM_ENA |
HSIO_S6G_IB_CFG_IB_EQZ_ENA |
HSIO_S6G_IB_CFG_IB_CONCUR);
regmap_update_bits(regmap, HSIO_S6G_IB_CFG1,
HSIO_S6G_IB_CFG1_IB_FRC_OFFSET |
HSIO_S6G_IB_CFG1_IB_FRC_LP |
HSIO_S6G_IB_CFG1_IB_FRC_MID |
HSIO_S6G_IB_CFG1_IB_FRC_HP |
HSIO_S6G_IB_CFG1_IB_FILT_OFFSET |
HSIO_S6G_IB_CFG1_IB_FILT_LP |
HSIO_S6G_IB_CFG1_IB_FILT_MID |
HSIO_S6G_IB_CFG1_IB_FILT_HP,
HSIO_S6G_IB_CFG1_IB_FILT_OFFSET |
HSIO_S6G_IB_CFG1_IB_FILT_HP |
HSIO_S6G_IB_CFG1_IB_FILT_LP |
HSIO_S6G_IB_CFG1_IB_FILT_MID);
regmap_update_bits(regmap, HSIO_S6G_IB_CFG2,
HSIO_S6G_IB_CFG2_IB_UREG_M,
HSIO_S6G_IB_CFG2_IB_UREG(4));
regmap_update_bits(regmap, HSIO_S6G_IB_CFG3,
HSIO_S6G_IB_CFG3_IB_INI_OFFSET_M |
HSIO_S6G_IB_CFG3_IB_INI_LP_M |
HSIO_S6G_IB_CFG3_IB_INI_MID_M |
HSIO_S6G_IB_CFG3_IB_INI_HP_M,
HSIO_S6G_IB_CFG3_IB_INI_OFFSET(31) |
HSIO_S6G_IB_CFG3_IB_INI_LP(1) |
HSIO_S6G_IB_CFG3_IB_INI_MID(31) |
HSIO_S6G_IB_CFG3_IB_INI_HP(0));
regmap_update_bits(regmap, HSIO_S6G_MISC_CFG,
HSIO_S6G_MISC_CFG_LANE_RST,
HSIO_S6G_MISC_CFG_LANE_RST);
ret = serdes_commit_mcb_s6g(regmap, serdes);
if (ret)
return ret;
/* OB + DES + IB + SER CFG */
regmap_update_bits(regmap, HSIO_S6G_OB_CFG,
HSIO_S6G_OB_CFG_OB_IDLE |
HSIO_S6G_OB_CFG_OB_ENA1V_MODE |
HSIO_S6G_OB_CFG_OB_POST0_M |
HSIO_S6G_OB_CFG_OB_PREC_M,
(ob_ena1v_mode ? HSIO_S6G_OB_CFG_OB_ENA1V_MODE : 0) |
HSIO_S6G_OB_CFG_OB_POST0(0) |
HSIO_S6G_OB_CFG_OB_PREC(0));
regmap_update_bits(regmap, HSIO_S6G_OB_CFG1,
HSIO_S6G_OB_CFG1_OB_ENA_CAS_M |
HSIO_S6G_OB_CFG1_OB_LEV_M,
HSIO_S6G_OB_CFG1_OB_LEV(ob_lev) |
HSIO_S6G_OB_CFG1_OB_ENA_CAS(ob_ena_cas));
regmap_update_bits(regmap, HSIO_S6G_DES_CFG,
HSIO_S6G_DES_CFG_DES_PHS_CTRL_M |
HSIO_S6G_DES_CFG_DES_CPMD_SEL_M |
HSIO_S6G_DES_CFG_DES_BW_ANA_M,
HSIO_S6G_DES_CFG_DES_PHS_CTRL(2) |
HSIO_S6G_DES_CFG_DES_CPMD_SEL(0) |
HSIO_S6G_DES_CFG_DES_BW_ANA(des_bw_ana));
regmap_update_bits(regmap, HSIO_S6G_IB_CFG,
HSIO_S6G_IB_CFG_IB_SIG_DET_CLK_SEL_M |
HSIO_S6G_IB_CFG_IB_REG_PAT_SEL_OFFSET_M,
HSIO_S6G_IB_CFG_IB_REG_PAT_SEL_OFFSET(0) |
HSIO_S6G_IB_CFG_IB_SIG_DET_CLK_SEL(0));
regmap_update_bits(regmap, HSIO_S6G_IB_CFG1,
HSIO_S6G_IB_CFG1_IB_TSDET_M,
HSIO_S6G_IB_CFG1_IB_TSDET(16));
regmap_update_bits(regmap, HSIO_S6G_SER_CFG,
HSIO_S6G_SER_CFG_SER_ALISEL_M |
HSIO_S6G_SER_CFG_SER_ENALI,
HSIO_S6G_SER_CFG_SER_ALISEL(0));
regmap_update_bits(regmap, HSIO_S6G_PLL_CFG,
HSIO_S6G_PLL_CFG_PLL_DIV4 |
HSIO_S6G_PLL_CFG_PLL_ENA_ROT |
HSIO_S6G_PLL_CFG_PLL_FSM_CTRL_DATA_M |
HSIO_S6G_PLL_CFG_PLL_ROT_DIR |
HSIO_S6G_PLL_CFG_PLL_ROT_FRQ,
HSIO_S6G_PLL_CFG_PLL_FSM_CTRL_DATA
(pll_fsm_ctrl_data));
regmap_update_bits(regmap, HSIO_S6G_COMMON_CFG,
HSIO_S6G_COMMON_CFG_SYS_RST |
HSIO_S6G_COMMON_CFG_ENA_LANE |
HSIO_S6G_COMMON_CFG_PWD_RX |
HSIO_S6G_COMMON_CFG_PWD_TX |
HSIO_S6G_COMMON_CFG_HRATE |
HSIO_S6G_COMMON_CFG_QRATE |
HSIO_S6G_COMMON_CFG_ENA_ELOOP |
HSIO_S6G_COMMON_CFG_ENA_FLOOP |
HSIO_S6G_COMMON_CFG_IF_MODE_M,
HSIO_S6G_COMMON_CFG_SYS_RST |
HSIO_S6G_COMMON_CFG_ENA_LANE |
(qrate ? HSIO_S6G_COMMON_CFG_QRATE : 0) |
HSIO_S6G_COMMON_CFG_IF_MODE(if_mode));
regmap_update_bits(regmap, HSIO_S6G_MISC_CFG,
HSIO_S6G_MISC_CFG_LANE_RST |
HSIO_S6G_MISC_CFG_DES_100FX_CPMD_ENA |
HSIO_S6G_MISC_CFG_RX_LPI_MODE_ENA |
HSIO_S6G_MISC_CFG_TX_LPI_MODE_ENA,
HSIO_S6G_MISC_CFG_LANE_RST |
HSIO_S6G_MISC_CFG_RX_LPI_MODE_ENA);
ret = serdes_commit_mcb_s6g(regmap, serdes);
if (ret)
return ret;
regmap_update_bits(regmap, HSIO_S6G_PLL_CFG,
HSIO_S6G_PLL_CFG_PLL_FSM_ENA,
HSIO_S6G_PLL_CFG_PLL_FSM_ENA);
ret = serdes_commit_mcb_s6g(regmap, serdes);
if (ret)
return ret;
/* Wait for PLL bringup */
msleep(20);
regmap_update_bits(regmap, HSIO_S6G_IB_CFG,
HSIO_S6G_IB_CFG_IB_CAL_ENA,
HSIO_S6G_IB_CFG_IB_CAL_ENA);
regmap_update_bits(regmap, HSIO_S6G_MISC_CFG,
HSIO_S6G_MISC_CFG_LANE_RST, 0);
ret = serdes_commit_mcb_s6g(regmap, serdes);
if (ret)
return ret;
/* Wait for calibration */
msleep(60);
regmap_update_bits(regmap, HSIO_S6G_IB_CFG,
HSIO_S6G_IB_CFG_IB_REG_PAT_SEL_OFFSET_M |
HSIO_S6G_IB_CFG_IB_SIG_DET_CLK_SEL_M,
HSIO_S6G_IB_CFG_IB_REG_PAT_SEL_OFFSET(0) |
HSIO_S6G_IB_CFG_IB_SIG_DET_CLK_SEL(7));
regmap_update_bits(regmap, HSIO_S6G_IB_CFG1,
HSIO_S6G_IB_CFG1_IB_TSDET_M,
HSIO_S6G_IB_CFG1_IB_TSDET(3));
/* IB CFG */
return 0;
}
#define MCB_S1G_CFG_TIMEOUT 50
static int __serdes_write_mcb_s1g(struct regmap *regmap, u8 macro, u32 op)
@ -110,7 +342,7 @@ struct serdes_mux {
u32 mux;
};
#define SERDES_MUX(_idx, _port, _mode, _submode, _mask, _mux) { \
#define SERDES_MUX(_idx, _port, _mode, _submode, _mask, _mux) { \
.idx = _idx, \
.port = _port, \
.mode = _mode, \
@ -191,8 +423,12 @@ static int serdes_set_mode(struct phy *phy, enum phy_mode mode, int submode)
if (macro->idx <= SERDES1G_MAX)
return serdes_init_s1g(macro->ctrl->regs, macro->idx);
else if (macro->idx <= SERDES6G_MAX)
return serdes_init_s6g(macro->ctrl->regs,
macro->idx - (SERDES1G_MAX + 1),
ocelot_serdes_muxes[i].submode);
/* SERDES6G and PCIe not supported yet */
/* PCIe not supported yet */
return -EOPNOTSUPP;
}

View File

@ -384,10 +384,16 @@ int phy_reset(struct phy *phy)
if (!phy || !phy->ops->reset)
return 0;
ret = phy_pm_runtime_get_sync(phy);
if (ret < 0 && ret != -ENOTSUPP)
return ret;
mutex_lock(&phy->mutex);
ret = phy->ops->reset(phy);
mutex_unlock(&phy->mutex);
phy_pm_runtime_put(phy);
return ret;
}
EXPORT_SYMBOL_GPL(phy_reset);
@ -564,6 +570,11 @@ void phy_put(struct phy *phy)
if (!phy || IS_ERR(phy))
return;
mutex_lock(&phy->mutex);
if (phy->ops->release)
phy->ops->release(phy);
mutex_unlock(&phy->mutex);
module_put(phy->ops->owner);
put_device(&phy->dev);
}

View File

@ -242,6 +242,88 @@ static const struct qmp_phy_init_tbl msm8996_pcie_pcs_tbl[] = {
QMP_PHY_INIT_CFG(QPHY_TXDEEMPH_M3P5DB_V0, 0x0e),
};
static const struct qmp_phy_init_tbl msm8998_pcie_serdes_tbl[] = {
QMP_PHY_INIT_CFG(QSERDES_V3_COM_BIAS_EN_CLKBUFLR_EN, 0x14),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_CLK_SELECT, 0x30),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_IVCO, 0x0f),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_CMN_CONFIG, 0x06),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP_EN, 0x01),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_RESETSM_CNTRL, 0x20),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE_MAP, 0x00),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE2_MODE0, 0x01),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE1_MODE0, 0xc9),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE_TIMER1, 0xff),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE_TIMER2, 0x3f),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_SVS_MODE_CLK_SEL, 0x01),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_CORE_CLK_EN, 0x00),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_CORECLK_DIV_MODE0, 0x0a),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_CLK_EP_DIV, 0x19),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_CLK_ENABLE1, 0x90),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_DEC_START_MODE0, 0x82),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START3_MODE0, 0x03),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START2_MODE0, 0x55),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START1_MODE0, 0x55),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP3_MODE0, 0x00),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP2_MODE0, 0x0d),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP1_MODE0, 0x04),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_HSCLK_SEL, 0x00),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_CP_CTRL_MODE0, 0x08),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_RCTRL_MODE0, 0x16),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_CCTRL_MODE0, 0x34),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_CMN_CONFIG, 0x06),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_CLK_SELECT, 0x33),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYS_CLK_CTRL, 0x02),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYSCLK_BUF_ENABLE, 0x07),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYSCLK_EN_SEL, 0x04),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_INTEGLOOP_GAIN1_MODE0, 0x00),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_INTEGLOOP_GAIN0_MODE0, 0x3f),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_BG_TIMER, 0x09),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_EN_CENTER, 0x01),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_PER1, 0x40),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_PER2, 0x01),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_ADJ_PER1, 0x02),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_ADJ_PER2, 0x00),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_STEP_SIZE1, 0x7e),
QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_STEP_SIZE2, 0x15),
};
static const struct qmp_phy_init_tbl msm8998_pcie_tx_tbl[] = {
QMP_PHY_INIT_CFG(QSERDES_V3_TX_RES_CODE_LANE_OFFSET_TX, 0x02),
QMP_PHY_INIT_CFG(QSERDES_V3_TX_RCV_DETECT_LVL_2, 0x12),
QMP_PHY_INIT_CFG(QSERDES_V3_TX_HIGHZ_DRVR_EN, 0x10),
QMP_PHY_INIT_CFG(QSERDES_V3_TX_LANE_MODE_1, 0x06),
};
static const struct qmp_phy_init_tbl msm8998_pcie_rx_tbl[] = {
QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_CNTRL, 0x03),
QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_ENABLES, 0x1c),
QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_DEGLITCH_CNTRL, 0x14),
QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0a),
QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL3, 0x04),
QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL4, 0x1a),
QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x4b),
QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SO_GAIN, 0x04),
QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SO_GAIN_HALF, 0x04),
QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1, 0x00),
QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_OFFSET_ADAPTOR_CNTRL2, 0x80),
QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_INTERFACE_MODE, 0x40),
QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_PI_CONTROLS, 0x71),
QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_FASTLOCK_COUNT_LOW, 0x40),
};
static const struct qmp_phy_init_tbl msm8998_pcie_pcs_tbl[] = {
QMP_PHY_INIT_CFG(QPHY_V3_PCS_ENDPOINT_REFCLK_DRIVE, 0x04),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_OSC_DTCT_ACTIONS, 0x00),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_PWRUP_RESET_DLY_TIME_AUXCLK, 0x01),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_L1SS_WAKEUP_DLY_TIME_AUXCLK_MSB, 0x00),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_L1SS_WAKEUP_DLY_TIME_AUXCLK_LSB, 0x20),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_LP_WAKEUP_DLY_TIME_AUXCLK_MSB, 0x00),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_LP_WAKEUP_DLY_TIME_AUXCLK, 0x01),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_PLL_LOCK_CHK_DLY_TIME, 0x73),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_RX_SIGDET_LVL, 0x99),
QMP_PHY_INIT_CFG(QPHY_V3_PCS_SIGDET_CNTRL, 0x03),
};
static const struct qmp_phy_init_tbl msm8996_usb3_serdes_tbl[] = {
QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0x14),
QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08),
@ -897,6 +979,7 @@ struct qmp_phy {
* @init_count: phy common block initialization count
* @phy_initialized: indicate if PHY has been initialized
* @mode: current PHY mode
* @ufs_reset: optional UFS PHY reset handle
*/
struct qcom_qmp {
struct device *dev;
@ -914,6 +997,8 @@ struct qcom_qmp {
int init_count;
bool phy_initialized;
enum phy_mode mode;
struct reset_control *ufs_reset;
};
static inline void qphy_setbits(void __iomem *base, u32 offset, u32 val)
@ -1146,6 +1231,31 @@ static const struct qmp_phy_cfg sdm845_ufsphy_cfg = {
.no_pcs_sw_reset = true,
};
static const struct qmp_phy_cfg msm8998_pciephy_cfg = {
.type = PHY_TYPE_PCIE,
.nlanes = 1,
.serdes_tbl = msm8998_pcie_serdes_tbl,
.serdes_tbl_num = ARRAY_SIZE(msm8998_pcie_serdes_tbl),
.tx_tbl = msm8998_pcie_tx_tbl,
.tx_tbl_num = ARRAY_SIZE(msm8998_pcie_tx_tbl),
.rx_tbl = msm8998_pcie_rx_tbl,
.rx_tbl_num = ARRAY_SIZE(msm8998_pcie_rx_tbl),
.pcs_tbl = msm8998_pcie_pcs_tbl,
.pcs_tbl_num = ARRAY_SIZE(msm8998_pcie_pcs_tbl),
.clk_list = msm8996_phy_clk_l,
.num_clks = ARRAY_SIZE(msm8996_phy_clk_l),
.reset_list = ipq8074_pciephy_reset_l,
.num_resets = ARRAY_SIZE(ipq8074_pciephy_reset_l),
.vreg_list = qmp_phy_vreg_l,
.num_vregs = ARRAY_SIZE(qmp_phy_vreg_l),
.regs = pciephy_regs_layout,
.start_ctrl = SERDES_START | PCS_START,
.pwrdn_ctrl = SW_PWRDN | REFCLK_DRV_DSBL,
.mask_com_pcs_ready = PCS_READY,
};
static const struct qmp_phy_cfg msm8998_usb3phy_cfg = {
.type = PHY_TYPE_USB3,
.nlanes = 1,
@ -1314,6 +1424,7 @@ static int qcom_qmp_phy_com_exit(struct qcom_qmp *qmp)
return 0;
}
reset_control_assert(qmp->ufs_reset);
if (cfg->has_phy_com_ctrl) {
qphy_setbits(serdes, cfg->regs[QPHY_COM_START_CONTROL],
SERDES_START | PCS_START);
@ -1335,8 +1446,7 @@ static int qcom_qmp_phy_com_exit(struct qcom_qmp *qmp)
return 0;
}
/* PHY Initialization */
static int qcom_qmp_phy_init(struct phy *phy)
static int qcom_qmp_phy_enable(struct phy *phy)
{
struct qmp_phy *qphy = phy_get_drvdata(phy);
struct qcom_qmp *qmp = qphy->qmp;
@ -1351,6 +1461,33 @@ static int qcom_qmp_phy_init(struct phy *phy)
dev_vdbg(qmp->dev, "Initializing QMP phy\n");
if (cfg->no_pcs_sw_reset) {
/*
* Get UFS reset, which is delayed until now to avoid a
* circular dependency where UFS needs its PHY, but the PHY
* needs this UFS reset.
*/
if (!qmp->ufs_reset) {
qmp->ufs_reset =
devm_reset_control_get_exclusive(qmp->dev,
"ufsphy");
if (IS_ERR(qmp->ufs_reset)) {
ret = PTR_ERR(qmp->ufs_reset);
dev_err(qmp->dev,
"failed to get UFS reset: %d\n",
ret);
qmp->ufs_reset = NULL;
return ret;
}
}
ret = reset_control_assert(qmp->ufs_reset);
if (ret)
goto err_lane_rst;
}
ret = qcom_qmp_phy_com_init(qphy);
if (ret)
return ret;
@ -1383,14 +1520,9 @@ static int qcom_qmp_phy_init(struct phy *phy)
cfg->rx_tbl, cfg->rx_tbl_num);
qcom_qmp_phy_configure(pcs, cfg->regs, cfg->pcs_tbl, cfg->pcs_tbl_num);
/*
* UFS PHY requires the deassert of software reset before serdes start.
* For UFS PHYs that do not have software reset control bits, defer
* starting serdes until the power on callback.
*/
if ((cfg->type == PHY_TYPE_UFS) && cfg->no_pcs_sw_reset)
goto out;
ret = reset_control_deassert(qmp->ufs_reset);
if (ret)
goto err_lane_rst;
/*
* Pull out PHY from POWER DOWN state.
@ -1403,7 +1535,9 @@ static int qcom_qmp_phy_init(struct phy *phy)
usleep_range(cfg->pwrdn_delay_min, cfg->pwrdn_delay_max);
/* Pull PHY out of reset state */
qphy_clrbits(pcs, cfg->regs[QPHY_SW_RESET], SW_RESET);
if (!cfg->no_pcs_sw_reset)
qphy_clrbits(pcs, cfg->regs[QPHY_SW_RESET], SW_RESET);
if (cfg->has_phy_dp_com_ctrl)
qphy_clrbits(dp_com, QPHY_V3_DP_COM_SW_RESET, SW_RESET);
@ -1420,11 +1554,10 @@ static int qcom_qmp_phy_init(struct phy *phy)
goto err_pcs_ready;
}
qmp->phy_initialized = true;
out:
return ret;
return 0;
err_pcs_ready:
reset_control_assert(qmp->ufs_reset);
clk_disable_unprepare(qphy->pipe_clk);
err_clk_enable:
if (cfg->has_lane_rst)
@ -1435,7 +1568,7 @@ err_lane_rst:
return ret;
}
static int qcom_qmp_phy_exit(struct phy *phy)
static int qcom_qmp_phy_disable(struct phy *phy)
{
struct qmp_phy *qphy = phy_get_drvdata(phy);
struct qcom_qmp *qmp = qphy->qmp;
@ -1463,44 +1596,6 @@ static int qcom_qmp_phy_exit(struct phy *phy)
return 0;
}
static int qcom_qmp_phy_poweron(struct phy *phy)
{
struct qmp_phy *qphy = phy_get_drvdata(phy);
struct qcom_qmp *qmp = qphy->qmp;
const struct qmp_phy_cfg *cfg = qmp->cfg;
void __iomem *pcs = qphy->pcs;
void __iomem *status;
unsigned int mask, val;
int ret = 0;
if (cfg->type != PHY_TYPE_UFS)
return 0;
/*
* For UFS PHY that has not software reset control, serdes start
* should only happen when UFS driver explicitly calls phy_power_on
* after it deasserts software reset.
*/
if (cfg->no_pcs_sw_reset && !qmp->phy_initialized &&
(qmp->init_count != 0)) {
/* start SerDes and Phy-Coding-Sublayer */
qphy_setbits(pcs, cfg->regs[QPHY_START_CTRL], cfg->start_ctrl);
status = pcs + cfg->regs[QPHY_PCS_READY_STATUS];
mask = cfg->mask_pcs_ready;
ret = readl_poll_timeout(status, val, !(val & mask), 1,
PHY_INIT_COMPLETE_TIMEOUT);
if (ret) {
dev_err(qmp->dev, "phy initialization timed-out\n");
return ret;
}
qmp->phy_initialized = true;
}
return ret;
}
static int qcom_qmp_phy_set_mode(struct phy *phy,
enum phy_mode mode, int submode)
{
@ -1750,9 +1845,15 @@ static int phy_pipe_clk_register(struct qcom_qmp *qmp, struct device_node *np)
}
static const struct phy_ops qcom_qmp_phy_gen_ops = {
.init = qcom_qmp_phy_init,
.exit = qcom_qmp_phy_exit,
.power_on = qcom_qmp_phy_poweron,
.init = qcom_qmp_phy_enable,
.exit = qcom_qmp_phy_disable,
.set_mode = qcom_qmp_phy_set_mode,
.owner = THIS_MODULE,
};
static const struct phy_ops qcom_qmp_ufs_ops = {
.power_on = qcom_qmp_phy_enable,
.power_off = qcom_qmp_phy_disable,
.set_mode = qcom_qmp_phy_set_mode,
.owner = THIS_MODULE,
};
@ -1763,6 +1864,7 @@ int qcom_qmp_phy_create(struct device *dev, struct device_node *np, int id)
struct qcom_qmp *qmp = dev_get_drvdata(dev);
struct phy *generic_phy;
struct qmp_phy *qphy;
const struct phy_ops *ops = &qcom_qmp_phy_gen_ops;
char prop_name[MAX_PROP_NAME];
int ret;
@ -1849,7 +1951,10 @@ int qcom_qmp_phy_create(struct device *dev, struct device_node *np, int id)
}
}
generic_phy = devm_phy_create(dev, np, &qcom_qmp_phy_gen_ops);
if (qmp->cfg->type == PHY_TYPE_UFS)
ops = &qcom_qmp_ufs_ops;
generic_phy = devm_phy_create(dev, np, ops);
if (IS_ERR(generic_phy)) {
ret = PTR_ERR(generic_phy);
dev_err(dev, "failed to create qphy %d\n", ret);
@ -1872,6 +1977,9 @@ static const struct of_device_id qcom_qmp_phy_of_match_table[] = {
}, {
.compatible = "qcom,msm8996-qmp-usb3-phy",
.data = &msm8996_usb3phy_cfg,
}, {
.compatible = "qcom,msm8998-qmp-pcie-phy",
.data = &msm8998_pciephy_cfg,
}, {
.compatible = "qcom,msm8998-qmp-ufs-phy",
.data = &sdm845_ufsphy_cfg,

View File

@ -241,6 +241,7 @@
#define QSERDES_V3_RX_RX_BAND 0x110
#define QSERDES_V3_RX_RX_INTERFACE_MODE 0x11c
#define QSERDES_V3_RX_RX_MODE_00 0x164
#define QSERDES_V3_RX_RX_MODE_01 0x168
/* Only for QMP V3 PHY - PCS registers */
#define QPHY_V3_PCS_POWER_DOWN_CONTROL 0x004
@ -280,6 +281,7 @@
#define QPHY_V3_PCS_TSYNC_RSYNC_TIME 0x08c
#define QPHY_V3_PCS_PWRUP_RESET_DLY_TIME_AUXCLK 0x0a0
#define QPHY_V3_PCS_LP_WAKEUP_DLY_TIME_AUXCLK 0x0a4
#define QPHY_V3_PCS_PLL_LOCK_CHK_DLY_TIME 0x0a8
#define QPHY_V3_PCS_LFPS_TX_ECSTART_EQTLOCK 0x0b0
#define QPHY_V3_PCS_RXEQTRAINING_WAIT_TIME 0x0b8
#define QPHY_V3_PCS_RXEQTRAINING_RUN_TIME 0x0bc
@ -292,13 +294,23 @@
#define QPHY_V3_PCS_RX_MIN_HIBERN8_TIME 0x138
#define QPHY_V3_PCS_RX_SIGDET_CTRL1 0x13c
#define QPHY_V3_PCS_RX_SIGDET_CTRL2 0x140
#define QPHY_V3_PCS_LP_WAKEUP_DLY_TIME_AUXCLK_MSB 0x1a8
#define QPHY_V3_PCS_OSC_DTCT_ACTIONS 0x1ac
#define QPHY_V3_PCS_SIGDET_CNTRL 0x1b0
#define QPHY_V3_PCS_TX_MID_TERM_CTRL1 0x1bc
#define QPHY_V3_PCS_MULTI_LANE_CTRL1 0x1c4
#define QPHY_V3_PCS_RX_SIGDET_LVL 0x1d8
#define QPHY_V3_PCS_L1SS_WAKEUP_DLY_TIME_AUXCLK_LSB 0x1dc
#define QPHY_V3_PCS_L1SS_WAKEUP_DLY_TIME_AUXCLK_MSB 0x1e0
#define QPHY_V3_PCS_REFGEN_REQ_CONFIG1 0x20c
#define QPHY_V3_PCS_REFGEN_REQ_CONFIG2 0x210
/* Only for QMP V3 PHY - PCS_MISC registers */
#define QPHY_V3_PCS_MISC_CLAMP_ENABLE 0x0c
#define QPHY_V3_PCS_MISC_OSC_DTCT_CONFIG2 0x2c
#define QPHY_V3_PCS_MISC_PCIE_INT_AUX_CLK_CONFIG1 0x44
#define QPHY_V3_PCS_MISC_OSC_DTCT_MODE2_CONFIG2 0x54
#define QPHY_V3_PCS_MISC_OSC_DTCT_MODE2_CONFIG4 0x5c
#define QPHY_V3_PCS_MISC_OSC_DTCT_MODE2_CONFIG5 0x60
#endif

View File

@ -822,14 +822,9 @@ static int qusb2_phy_probe(struct platform_device *pdev)
return ret;
}
qphy->iface_clk = devm_clk_get(dev, "iface");
if (IS_ERR(qphy->iface_clk)) {
ret = PTR_ERR(qphy->iface_clk);
if (ret == -EPROBE_DEFER)
return ret;
qphy->iface_clk = NULL;
dev_dbg(dev, "failed to get iface clk, %d\n", ret);
}
qphy->iface_clk = devm_clk_get_optional(dev, "iface");
if (IS_ERR(qphy->iface_clk))
return PTR_ERR(qphy->iface_clk);
qphy->phy_reset = devm_reset_control_get_by_index(&pdev->dev, 0);
if (IS_ERR(qphy->phy_reset)) {

View File

@ -19,6 +19,7 @@
#include <linux/clk.h>
#include <linux/phy/phy.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/io.h>
@ -96,11 +97,10 @@ struct ufs_qcom_phy {
char name[UFS_QCOM_PHY_NAME_LEN];
struct ufs_qcom_phy_calibration *cached_regs;
int cached_regs_table_size;
bool is_powered_on;
bool is_started;
struct ufs_qcom_phy_specific_ops *phy_spec_ops;
enum phy_mode mode;
struct reset_control *ufs_reset;
};
/**
@ -115,6 +115,7 @@ struct ufs_qcom_phy {
* and writes to QSERDES_RX_SIGDET_CNTRL attribute
*/
struct ufs_qcom_phy_specific_ops {
int (*calibrate)(struct ufs_qcom_phy *ufs_qcom_phy, bool is_rate_B);
void (*start_serdes)(struct ufs_qcom_phy *phy);
int (*is_physical_coding_sublayer_ready)(struct ufs_qcom_phy *phy);
void (*set_tx_lane_enable)(struct ufs_qcom_phy *phy, u32 val);

View File

@ -42,28 +42,6 @@ void ufs_qcom_phy_qmp_14nm_advertise_quirks(struct ufs_qcom_phy *phy_common)
UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
}
static int ufs_qcom_phy_qmp_14nm_init(struct phy *generic_phy)
{
struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
bool is_rate_B = false;
int ret;
if (phy_common->mode == PHY_MODE_UFS_HS_B)
is_rate_B = true;
ret = ufs_qcom_phy_qmp_14nm_phy_calibrate(phy_common, is_rate_B);
if (!ret)
/* phy calibrated, but yet to be started */
phy_common->is_started = false;
return ret;
}
static int ufs_qcom_phy_qmp_14nm_exit(struct phy *generic_phy)
{
return 0;
}
static
int ufs_qcom_phy_qmp_14nm_set_mode(struct phy *generic_phy,
enum phy_mode mode, int submode)
@ -124,8 +102,6 @@ static int ufs_qcom_phy_qmp_14nm_is_pcs_ready(struct ufs_qcom_phy *phy_common)
}
static const struct phy_ops ufs_qcom_phy_qmp_14nm_phy_ops = {
.init = ufs_qcom_phy_qmp_14nm_init,
.exit = ufs_qcom_phy_qmp_14nm_exit,
.power_on = ufs_qcom_phy_power_on,
.power_off = ufs_qcom_phy_power_off,
.set_mode = ufs_qcom_phy_qmp_14nm_set_mode,
@ -133,6 +109,7 @@ static const struct phy_ops ufs_qcom_phy_qmp_14nm_phy_ops = {
};
static struct ufs_qcom_phy_specific_ops phy_14nm_ops = {
.calibrate = ufs_qcom_phy_qmp_14nm_phy_calibrate,
.start_serdes = ufs_qcom_phy_qmp_14nm_start_serdes,
.is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_14nm_is_pcs_ready,
.set_tx_lane_enable = ufs_qcom_phy_qmp_14nm_set_tx_lane_enable,

View File

@ -61,28 +61,6 @@ void ufs_qcom_phy_qmp_20nm_advertise_quirks(struct ufs_qcom_phy *phy_common)
UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
}
static int ufs_qcom_phy_qmp_20nm_init(struct phy *generic_phy)
{
struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
bool is_rate_B = false;
int ret;
if (phy_common->mode == PHY_MODE_UFS_HS_B)
is_rate_B = true;
ret = ufs_qcom_phy_qmp_20nm_phy_calibrate(phy_common, is_rate_B);
if (!ret)
/* phy calibrated, but yet to be started */
phy_common->is_started = false;
return ret;
}
static int ufs_qcom_phy_qmp_20nm_exit(struct phy *generic_phy)
{
return 0;
}
static
int ufs_qcom_phy_qmp_20nm_set_mode(struct phy *generic_phy,
enum phy_mode mode, int submode)
@ -182,8 +160,6 @@ static int ufs_qcom_phy_qmp_20nm_is_pcs_ready(struct ufs_qcom_phy *phy_common)
}
static const struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
.init = ufs_qcom_phy_qmp_20nm_init,
.exit = ufs_qcom_phy_qmp_20nm_exit,
.power_on = ufs_qcom_phy_power_on,
.power_off = ufs_qcom_phy_power_off,
.set_mode = ufs_qcom_phy_qmp_20nm_set_mode,
@ -191,6 +167,7 @@ static const struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
};
static struct ufs_qcom_phy_specific_ops phy_20nm_ops = {
.calibrate = ufs_qcom_phy_qmp_20nm_phy_calibrate,
.start_serdes = ufs_qcom_phy_qmp_20nm_start_serdes,
.is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_20nm_is_pcs_ready,
.set_tx_lane_enable = ufs_qcom_phy_qmp_20nm_set_tx_lane_enable,

View File

@ -147,6 +147,21 @@ out:
}
EXPORT_SYMBOL_GPL(ufs_qcom_phy_generic_probe);
static int ufs_qcom_phy_get_reset(struct ufs_qcom_phy *phy_common)
{
struct reset_control *reset;
if (phy_common->ufs_reset)
return 0;
reset = devm_reset_control_get_exclusive_by_index(phy_common->dev, 0);
if (IS_ERR(reset))
return PTR_ERR(reset);
phy_common->ufs_reset = reset;
return 0;
}
static int __ufs_qcom_phy_clk_get(struct device *dev,
const char *name, struct clk **clk_out, bool err_print)
{
@ -528,23 +543,38 @@ int ufs_qcom_phy_power_on(struct phy *generic_phy)
{
struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
struct device *dev = phy_common->dev;
bool is_rate_B = false;
int err;
if (phy_common->is_powered_on)
return 0;
err = ufs_qcom_phy_get_reset(phy_common);
if (err)
return err;
if (!phy_common->is_started) {
err = ufs_qcom_phy_start_serdes(phy_common);
if (err)
return err;
err = reset_control_assert(phy_common->ufs_reset);
if (err)
return err;
err = ufs_qcom_phy_is_pcs_ready(phy_common);
if (err)
return err;
if (phy_common->mode == PHY_MODE_UFS_HS_B)
is_rate_B = true;
phy_common->is_started = true;
err = phy_common->phy_spec_ops->calibrate(phy_common, is_rate_B);
if (err)
return err;
err = reset_control_deassert(phy_common->ufs_reset);
if (err) {
dev_err(dev, "Failed to assert UFS PHY reset");
return err;
}
err = ufs_qcom_phy_start_serdes(phy_common);
if (err)
return err;
err = ufs_qcom_phy_is_pcs_ready(phy_common);
if (err)
return err;
err = ufs_qcom_phy_enable_vreg(dev, &phy_common->vdda_phy);
if (err) {
dev_err(dev, "%s enable vdda_phy failed, err=%d\n",
@ -587,7 +617,6 @@ int ufs_qcom_phy_power_on(struct phy *generic_phy)
}
}
phy_common->is_powered_on = true;
goto out;
out_disable_ref_clk:
@ -607,9 +636,6 @@ int ufs_qcom_phy_power_off(struct phy *generic_phy)
{
struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
if (!phy_common->is_powered_on)
return 0;
phy_common->phy_spec_ops->power_control(phy_common, false);
if (phy_common->vddp_ref_clk.reg)
@ -620,8 +646,7 @@ int ufs_qcom_phy_power_off(struct phy *generic_phy)
ufs_qcom_phy_disable_vreg(phy_common->dev, &phy_common->vdda_pll);
ufs_qcom_phy_disable_vreg(phy_common->dev, &phy_common->vdda_phy);
phy_common->is_powered_on = false;
reset_control_assert(phy_common->ufs_reset);
return 0;
}
EXPORT_SYMBOL_GPL(ufs_qcom_phy_power_off);

View File

@ -19,7 +19,7 @@ config PHY_RCAR_GEN3_PCIE
config PHY_RCAR_GEN3_USB2
tristate "Renesas R-Car generation 3 USB 2.0 PHY driver"
depends on ARCH_RENESAS
depends on EXTCON
depends on EXTCON || !EXTCON # if EXTCON=m, this cannot be built-in
depends on USB_SUPPORT
select GENERIC_PHY
select USB_COMMON

View File

@ -4,6 +4,7 @@
*
* Copyright (C) 2014 Renesas Solutions Corp.
* Copyright (C) 2014 Cogent Embedded, Inc.
* Copyright (C) 2019 Renesas Electronics Corp.
*/
#include <linux/clk.h>
@ -15,6 +16,7 @@
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/atomic.h>
#include <linux/of_device.h>
#define USBHS_LPSTS 0x02
#define USBHS_UGCTRL 0x80
@ -35,6 +37,8 @@
#define USBHS_UGCTRL2_USB0SEL 0x00000030
#define USBHS_UGCTRL2_USB0SEL_PCI 0x00000010
#define USBHS_UGCTRL2_USB0SEL_HS_USB 0x00000030
#define USBHS_UGCTRL2_USB0SEL_USB20 0x00000010
#define USBHS_UGCTRL2_USB0SEL_HS_USB20 0x00000020
/* USB General status register (UGSTS) */
#define USBHS_UGSTS_LOCK 0x00000100 /* From technical update */
@ -64,6 +68,11 @@ struct rcar_gen2_phy_driver {
struct rcar_gen2_channel *channels;
};
struct rcar_gen2_phy_data {
const struct phy_ops *gen2_phy_ops;
const u32 (*select_value)[PHYS_PER_CHANNEL];
};
static int rcar_gen2_phy_init(struct phy *p)
{
struct rcar_gen2_phy *phy = phy_get_drvdata(p);
@ -180,6 +189,60 @@ static int rcar_gen2_phy_power_off(struct phy *p)
return 0;
}
static int rz_g1c_phy_power_on(struct phy *p)
{
struct rcar_gen2_phy *phy = phy_get_drvdata(p);
struct rcar_gen2_phy_driver *drv = phy->channel->drv;
void __iomem *base = drv->base;
unsigned long flags;
u32 value;
spin_lock_irqsave(&drv->lock, flags);
/* Power on USBHS PHY */
value = readl(base + USBHS_UGCTRL);
value &= ~USBHS_UGCTRL_PLLRESET;
writel(value, base + USBHS_UGCTRL);
/* As per the data sheet wait 340 micro sec for power stable */
udelay(340);
if (phy->select_value == USBHS_UGCTRL2_USB0SEL_HS_USB20) {
value = readw(base + USBHS_LPSTS);
value |= USBHS_LPSTS_SUSPM;
writew(value, base + USBHS_LPSTS);
}
spin_unlock_irqrestore(&drv->lock, flags);
return 0;
}
static int rz_g1c_phy_power_off(struct phy *p)
{
struct rcar_gen2_phy *phy = phy_get_drvdata(p);
struct rcar_gen2_phy_driver *drv = phy->channel->drv;
void __iomem *base = drv->base;
unsigned long flags;
u32 value;
spin_lock_irqsave(&drv->lock, flags);
/* Power off USBHS PHY */
if (phy->select_value == USBHS_UGCTRL2_USB0SEL_HS_USB20) {
value = readw(base + USBHS_LPSTS);
value &= ~USBHS_LPSTS_SUSPM;
writew(value, base + USBHS_LPSTS);
}
value = readl(base + USBHS_UGCTRL);
value |= USBHS_UGCTRL_PLLRESET;
writel(value, base + USBHS_UGCTRL);
spin_unlock_irqrestore(&drv->lock, flags);
return 0;
}
static const struct phy_ops rcar_gen2_phy_ops = {
.init = rcar_gen2_phy_init,
.exit = rcar_gen2_phy_exit,
@ -188,12 +251,55 @@ static const struct phy_ops rcar_gen2_phy_ops = {
.owner = THIS_MODULE,
};
static const struct phy_ops rz_g1c_phy_ops = {
.init = rcar_gen2_phy_init,
.exit = rcar_gen2_phy_exit,
.power_on = rz_g1c_phy_power_on,
.power_off = rz_g1c_phy_power_off,
.owner = THIS_MODULE,
};
static const u32 pci_select_value[][PHYS_PER_CHANNEL] = {
[0] = { USBHS_UGCTRL2_USB0SEL_PCI, USBHS_UGCTRL2_USB0SEL_HS_USB },
[2] = { USBHS_UGCTRL2_USB2SEL_PCI, USBHS_UGCTRL2_USB2SEL_USB30 },
};
static const u32 usb20_select_value[][PHYS_PER_CHANNEL] = {
{ USBHS_UGCTRL2_USB0SEL_USB20, USBHS_UGCTRL2_USB0SEL_HS_USB20 },
};
static const struct rcar_gen2_phy_data rcar_gen2_usb_phy_data = {
.gen2_phy_ops = &rcar_gen2_phy_ops,
.select_value = pci_select_value,
};
static const struct rcar_gen2_phy_data rz_g1c_usb_phy_data = {
.gen2_phy_ops = &rz_g1c_phy_ops,
.select_value = usb20_select_value,
};
static const struct of_device_id rcar_gen2_phy_match_table[] = {
{ .compatible = "renesas,usb-phy-r8a7790" },
{ .compatible = "renesas,usb-phy-r8a7791" },
{ .compatible = "renesas,usb-phy-r8a7794" },
{ .compatible = "renesas,rcar-gen2-usb-phy" },
{ }
{
.compatible = "renesas,usb-phy-r8a77470",
.data = &rz_g1c_usb_phy_data,
},
{
.compatible = "renesas,usb-phy-r8a7790",
.data = &rcar_gen2_usb_phy_data,
},
{
.compatible = "renesas,usb-phy-r8a7791",
.data = &rcar_gen2_usb_phy_data,
},
{
.compatible = "renesas,usb-phy-r8a7794",
.data = &rcar_gen2_usb_phy_data,
},
{
.compatible = "renesas,rcar-gen2-usb-phy",
.data = &rcar_gen2_usb_phy_data,
},
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, rcar_gen2_phy_match_table);
@ -224,11 +330,6 @@ static const u32 select_mask[] = {
[2] = USBHS_UGCTRL2_USB2SEL,
};
static const u32 select_value[][PHYS_PER_CHANNEL] = {
[0] = { USBHS_UGCTRL2_USB0SEL_PCI, USBHS_UGCTRL2_USB0SEL_HS_USB },
[2] = { USBHS_UGCTRL2_USB2SEL_PCI, USBHS_UGCTRL2_USB2SEL_USB30 },
};
static int rcar_gen2_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@ -238,6 +339,7 @@ static int rcar_gen2_phy_probe(struct platform_device *pdev)
struct resource *res;
void __iomem *base;
struct clk *clk;
const struct rcar_gen2_phy_data *data;
int i = 0;
if (!dev->of_node) {
@ -266,6 +368,10 @@ static int rcar_gen2_phy_probe(struct platform_device *pdev)
drv->clk = clk;
drv->base = base;
data = of_device_get_match_data(dev);
if (!data)
return -EINVAL;
drv->num_channels = of_get_child_count(dev->of_node);
drv->channels = devm_kcalloc(dev, drv->num_channels,
sizeof(struct rcar_gen2_channel),
@ -294,10 +400,10 @@ static int rcar_gen2_phy_probe(struct platform_device *pdev)
phy->channel = channel;
phy->number = n;
phy->select_value = select_value[channel_num][n];
phy->select_value = data->select_value[channel_num][n];
phy->phy = devm_phy_create(dev, NULL,
&rcar_gen2_phy_ops);
data->gen2_phy_ops);
if (IS_ERR(phy->phy)) {
dev_err(dev, "Failed to create PHY\n");
return PTR_ERR(phy->phy);

View File

@ -37,11 +37,8 @@
/* INT_ENABLE */
#define USB2_INT_ENABLE_UCOM_INTEN BIT(3)
#define USB2_INT_ENABLE_USBH_INTB_EN BIT(2)
#define USB2_INT_ENABLE_USBH_INTA_EN BIT(1)
#define USB2_INT_ENABLE_INIT (USB2_INT_ENABLE_UCOM_INTEN | \
USB2_INT_ENABLE_USBH_INTB_EN | \
USB2_INT_ENABLE_USBH_INTA_EN)
#define USB2_INT_ENABLE_USBH_INTB_EN BIT(2) /* For EHCI */
#define USB2_INT_ENABLE_USBH_INTA_EN BIT(1) /* For OHCI */
/* USBCTR */
#define USB2_USBCTR_DIRPD BIT(2)
@ -78,10 +75,35 @@
#define USB2_ADPCTRL_IDPULLUP BIT(5) /* 1 = ID sampling is enabled */
#define USB2_ADPCTRL_DRVVBUS BIT(4)
#define NUM_OF_PHYS 4
enum rcar_gen3_phy_index {
PHY_INDEX_BOTH_HC,
PHY_INDEX_OHCI,
PHY_INDEX_EHCI,
PHY_INDEX_HSUSB
};
static const u32 rcar_gen3_int_enable[NUM_OF_PHYS] = {
USB2_INT_ENABLE_USBH_INTB_EN | USB2_INT_ENABLE_USBH_INTA_EN,
USB2_INT_ENABLE_USBH_INTA_EN,
USB2_INT_ENABLE_USBH_INTB_EN,
0
};
struct rcar_gen3_phy {
struct phy *phy;
struct rcar_gen3_chan *ch;
u32 int_enable_bits;
bool initialized;
bool otg_initialized;
bool powered;
};
struct rcar_gen3_chan {
void __iomem *base;
struct device *dev; /* platform_device's device */
struct extcon_dev *extcon;
struct phy *phy;
struct rcar_gen3_phy rphys[NUM_OF_PHYS];
struct regulator *vbus;
struct work_struct work;
enum usb_dr_mode dr_mode;
@ -120,7 +142,7 @@ static void rcar_gen3_set_host_mode(struct rcar_gen3_chan *ch, int host)
void __iomem *usb2_base = ch->base;
u32 val = readl(usb2_base + USB2_COMMCTRL);
dev_vdbg(&ch->phy->dev, "%s: %08x, %d\n", __func__, val, host);
dev_vdbg(ch->dev, "%s: %08x, %d\n", __func__, val, host);
if (host)
val &= ~USB2_COMMCTRL_OTG_PERI;
else
@ -133,7 +155,7 @@ static void rcar_gen3_set_linectrl(struct rcar_gen3_chan *ch, int dp, int dm)
void __iomem *usb2_base = ch->base;
u32 val = readl(usb2_base + USB2_LINECTRL1);
dev_vdbg(&ch->phy->dev, "%s: %08x, %d, %d\n", __func__, val, dp, dm);
dev_vdbg(ch->dev, "%s: %08x, %d, %d\n", __func__, val, dp, dm);
val &= ~(USB2_LINECTRL1_DP_RPD | USB2_LINECTRL1_DM_RPD);
if (dp)
val |= USB2_LINECTRL1_DP_RPD;
@ -147,7 +169,7 @@ static void rcar_gen3_enable_vbus_ctrl(struct rcar_gen3_chan *ch, int vbus)
void __iomem *usb2_base = ch->base;
u32 val = readl(usb2_base + USB2_ADPCTRL);
dev_vdbg(&ch->phy->dev, "%s: %08x, %d\n", __func__, val, vbus);
dev_vdbg(ch->dev, "%s: %08x, %d\n", __func__, val, vbus);
if (vbus)
val |= USB2_ADPCTRL_DRVVBUS;
else
@ -249,6 +271,42 @@ static enum phy_mode rcar_gen3_get_phy_mode(struct rcar_gen3_chan *ch)
return PHY_MODE_USB_DEVICE;
}
static bool rcar_gen3_is_any_rphy_initialized(struct rcar_gen3_chan *ch)
{
int i;
for (i = 0; i < NUM_OF_PHYS; i++) {
if (ch->rphys[i].initialized)
return true;
}
return false;
}
static bool rcar_gen3_needs_init_otg(struct rcar_gen3_chan *ch)
{
int i;
for (i = 0; i < NUM_OF_PHYS; i++) {
if (ch->rphys[i].otg_initialized)
return false;
}
return true;
}
static bool rcar_gen3_are_all_rphys_power_off(struct rcar_gen3_chan *ch)
{
int i;
for (i = 0; i < NUM_OF_PHYS; i++) {
if (ch->rphys[i].powered)
return false;
}
return true;
}
static ssize_t role_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
@ -256,7 +314,7 @@ static ssize_t role_store(struct device *dev, struct device_attribute *attr,
bool is_b_device;
enum phy_mode cur_mode, new_mode;
if (!ch->is_otg_channel || !ch->phy->init_count)
if (!ch->is_otg_channel || !rcar_gen3_is_any_rphy_initialized(ch))
return -EIO;
if (!strncmp(buf, "host", strlen("host")))
@ -294,7 +352,7 @@ static ssize_t role_show(struct device *dev, struct device_attribute *attr,
{
struct rcar_gen3_chan *ch = dev_get_drvdata(dev);
if (!ch->is_otg_channel || !ch->phy->init_count)
if (!ch->is_otg_channel || !rcar_gen3_is_any_rphy_initialized(ch))
return -EIO;
return sprintf(buf, "%s\n", rcar_gen3_is_host(ch) ? "host" :
@ -328,37 +386,62 @@ static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch)
static int rcar_gen3_phy_usb2_init(struct phy *p)
{
struct rcar_gen3_chan *channel = phy_get_drvdata(p);
struct rcar_gen3_phy *rphy = phy_get_drvdata(p);
struct rcar_gen3_chan *channel = rphy->ch;
void __iomem *usb2_base = channel->base;
u32 val;
/* Initialize USB2 part */
writel(USB2_INT_ENABLE_INIT, usb2_base + USB2_INT_ENABLE);
val = readl(usb2_base + USB2_INT_ENABLE);
val |= USB2_INT_ENABLE_UCOM_INTEN | rphy->int_enable_bits;
writel(val, usb2_base + USB2_INT_ENABLE);
writel(USB2_SPD_RSM_TIMSET_INIT, usb2_base + USB2_SPD_RSM_TIMSET);
writel(USB2_OC_TIMSET_INIT, usb2_base + USB2_OC_TIMSET);
/* Initialize otg part */
if (channel->is_otg_channel)
rcar_gen3_init_otg(channel);
if (channel->is_otg_channel) {
if (rcar_gen3_needs_init_otg(channel))
rcar_gen3_init_otg(channel);
rphy->otg_initialized = true;
}
rphy->initialized = true;
return 0;
}
static int rcar_gen3_phy_usb2_exit(struct phy *p)
{
struct rcar_gen3_chan *channel = phy_get_drvdata(p);
struct rcar_gen3_phy *rphy = phy_get_drvdata(p);
struct rcar_gen3_chan *channel = rphy->ch;
void __iomem *usb2_base = channel->base;
u32 val;
writel(0, channel->base + USB2_INT_ENABLE);
rphy->initialized = false;
if (channel->is_otg_channel)
rphy->otg_initialized = false;
val = readl(usb2_base + USB2_INT_ENABLE);
val &= ~rphy->int_enable_bits;
if (!rcar_gen3_is_any_rphy_initialized(channel))
val &= ~USB2_INT_ENABLE_UCOM_INTEN;
writel(val, usb2_base + USB2_INT_ENABLE);
return 0;
}
static int rcar_gen3_phy_usb2_power_on(struct phy *p)
{
struct rcar_gen3_chan *channel = phy_get_drvdata(p);
struct rcar_gen3_phy *rphy = phy_get_drvdata(p);
struct rcar_gen3_chan *channel = rphy->ch;
void __iomem *usb2_base = channel->base;
u32 val;
int ret;
if (!rcar_gen3_are_all_rphys_power_off(channel))
return 0;
if (channel->vbus) {
ret = regulator_enable(channel->vbus);
if (ret)
@ -371,14 +454,22 @@ static int rcar_gen3_phy_usb2_power_on(struct phy *p)
val &= ~USB2_USBCTR_PLL_RST;
writel(val, usb2_base + USB2_USBCTR);
rphy->powered = true;
return 0;
}
static int rcar_gen3_phy_usb2_power_off(struct phy *p)
{
struct rcar_gen3_chan *channel = phy_get_drvdata(p);
struct rcar_gen3_phy *rphy = phy_get_drvdata(p);
struct rcar_gen3_chan *channel = rphy->ch;
int ret = 0;
rphy->powered = false;
if (!rcar_gen3_are_all_rphys_power_off(channel))
return 0;
if (channel->vbus)
ret = regulator_disable(channel->vbus);
@ -393,6 +484,12 @@ static const struct phy_ops rcar_gen3_phy_usb2_ops = {
.owner = THIS_MODULE,
};
static const struct phy_ops rz_g1c_phy_usb2_ops = {
.init = rcar_gen3_phy_usb2_init,
.exit = rcar_gen3_phy_usb2_exit,
.owner = THIS_MODULE,
};
static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch)
{
struct rcar_gen3_chan *ch = _ch;
@ -401,7 +498,7 @@ static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch)
irqreturn_t ret = IRQ_NONE;
if (status & USB2_OBINT_BITS) {
dev_vdbg(&ch->phy->dev, "%s: %08x\n", __func__, status);
dev_vdbg(ch->dev, "%s: %08x\n", __func__, status);
writel(USB2_OBINT_BITS, usb2_base + USB2_OBINTSTA);
rcar_gen3_device_recognition(ch);
ret = IRQ_HANDLED;
@ -411,11 +508,27 @@ static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch)
}
static const struct of_device_id rcar_gen3_phy_usb2_match_table[] = {
{ .compatible = "renesas,usb2-phy-r8a7795" },
{ .compatible = "renesas,usb2-phy-r8a7796" },
{ .compatible = "renesas,usb2-phy-r8a77965" },
{ .compatible = "renesas,rcar-gen3-usb2-phy" },
{ }
{
.compatible = "renesas,usb2-phy-r8a77470",
.data = &rz_g1c_phy_usb2_ops,
},
{
.compatible = "renesas,usb2-phy-r8a7795",
.data = &rcar_gen3_phy_usb2_ops,
},
{
.compatible = "renesas,usb2-phy-r8a7796",
.data = &rcar_gen3_phy_usb2_ops,
},
{
.compatible = "renesas,usb2-phy-r8a77965",
.data = &rcar_gen3_phy_usb2_ops,
},
{
.compatible = "renesas,rcar-gen3-usb2-phy",
.data = &rcar_gen3_phy_usb2_ops,
},
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, rcar_gen3_phy_usb2_match_table);
@ -425,13 +538,54 @@ static const unsigned int rcar_gen3_phy_cable[] = {
EXTCON_NONE,
};
static struct phy *rcar_gen3_phy_usb2_xlate(struct device *dev,
struct of_phandle_args *args)
{
struct rcar_gen3_chan *ch = dev_get_drvdata(dev);
if (args->args_count == 0) /* For old version dts */
return ch->rphys[PHY_INDEX_BOTH_HC].phy;
else if (args->args_count > 1) /* Prevent invalid args count */
return ERR_PTR(-ENODEV);
if (args->args[0] >= NUM_OF_PHYS)
return ERR_PTR(-ENODEV);
return ch->rphys[args->args[0]].phy;
}
static enum usb_dr_mode rcar_gen3_get_dr_mode(struct device_node *np)
{
enum usb_dr_mode candidate = USB_DR_MODE_UNKNOWN;
int i;
/*
* If one of device nodes has other dr_mode except UNKNOWN,
* this function returns UNKNOWN. To achieve backward compatibility,
* this loop starts the index as 0.
*/
for (i = 0; i < NUM_OF_PHYS; i++) {
enum usb_dr_mode mode = of_usb_get_dr_mode_by_phy(np, i);
if (mode != USB_DR_MODE_UNKNOWN) {
if (candidate == USB_DR_MODE_UNKNOWN)
candidate = mode;
else if (candidate != mode)
return USB_DR_MODE_UNKNOWN;
}
}
return candidate;
}
static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct rcar_gen3_chan *channel;
struct phy_provider *provider;
struct resource *res;
int irq, ret = 0;
const struct phy_ops *phy_usb2_ops;
int irq, ret = 0, i;
if (!dev->of_node) {
dev_err(dev, "This driver needs device tree\n");
@ -457,7 +611,7 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev)
dev_err(dev, "No irq handler (%d)\n", irq);
}
channel->dr_mode = of_usb_get_dr_mode_by_phy(dev->of_node, 0);
channel->dr_mode = rcar_gen3_get_dr_mode(dev->of_node);
if (channel->dr_mode != USB_DR_MODE_UNKNOWN) {
int ret;
@ -481,11 +635,21 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev)
* And then, phy-core will manage runtime pm for this device.
*/
pm_runtime_enable(dev);
channel->phy = devm_phy_create(dev, NULL, &rcar_gen3_phy_usb2_ops);
if (IS_ERR(channel->phy)) {
dev_err(dev, "Failed to create USB2 PHY\n");
ret = PTR_ERR(channel->phy);
goto error;
phy_usb2_ops = of_device_get_match_data(dev);
if (!phy_usb2_ops)
return -EINVAL;
for (i = 0; i < NUM_OF_PHYS; i++) {
channel->rphys[i].phy = devm_phy_create(dev, NULL,
phy_usb2_ops);
if (IS_ERR(channel->rphys[i].phy)) {
dev_err(dev, "Failed to create USB2 PHY\n");
ret = PTR_ERR(channel->rphys[i].phy);
goto error;
}
channel->rphys[i].ch = channel;
channel->rphys[i].int_enable_bits = rcar_gen3_int_enable[i];
phy_set_drvdata(channel->rphys[i].phy, &channel->rphys[i]);
}
channel->vbus = devm_regulator_get_optional(dev, "vbus");
@ -498,9 +662,9 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev)
}
platform_set_drvdata(pdev, channel);
phy_set_drvdata(channel->phy, channel);
channel->dev = dev;
provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
provider = devm_of_phy_provider_register(dev, rcar_gen3_phy_usb2_xlate);
if (IS_ERR(provider)) {
dev_err(dev, "Failed to register PHY provider\n");
ret = PTR_ERR(provider);

View File

@ -87,6 +87,7 @@ struct rockchip_emmc_phy {
unsigned int reg_offset;
struct regmap *reg_base;
struct clk *emmcclk;
unsigned int drive_impedance;
};
static int rockchip_emmc_phy_power(struct phy *phy, bool on_off)
@ -281,10 +282,10 @@ static int rockchip_emmc_phy_power_on(struct phy *phy)
{
struct rockchip_emmc_phy *rk_phy = phy_get_drvdata(phy);
/* Drive impedance: 50 Ohm */
/* Drive impedance: from DTS */
regmap_write(rk_phy->reg_base,
rk_phy->reg_offset + GRF_EMMCPHY_CON6,
HIWORD_UPDATE(PHYCTRL_DR_50OHM,
HIWORD_UPDATE(rk_phy->drive_impedance,
PHYCTRL_DR_MASK,
PHYCTRL_DR_SHIFT));
@ -314,6 +315,26 @@ static const struct phy_ops ops = {
.owner = THIS_MODULE,
};
static u32 convert_drive_impedance_ohm(struct platform_device *pdev, u32 dr_ohm)
{
switch (dr_ohm) {
case 100:
return PHYCTRL_DR_100OHM;
case 66:
return PHYCTRL_DR_66OHM;
case 50:
return PHYCTRL_DR_50OHM;
case 40:
return PHYCTRL_DR_40OHM;
case 33:
return PHYCTRL_DR_33OHM;
}
dev_warn(&pdev->dev, "Invalid value %u for drive-impedance-ohm.\n",
dr_ohm);
return PHYCTRL_DR_50OHM;
}
static int rockchip_emmc_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@ -322,6 +343,7 @@ static int rockchip_emmc_phy_probe(struct platform_device *pdev)
struct phy_provider *phy_provider;
struct regmap *grf;
unsigned int reg_offset;
u32 val;
if (!dev->parent || !dev->parent->of_node)
return -ENODEV;
@ -344,6 +366,10 @@ static int rockchip_emmc_phy_probe(struct platform_device *pdev)
rk_phy->reg_offset = reg_offset;
rk_phy->reg_base = grf;
rk_phy->drive_impedance = PHYCTRL_DR_50OHM;
if (!of_property_read_u32(dev->of_node, "drive-impedance-ohm", &val))
rk_phy->drive_impedance = convert_drive_impedance_ohm(pdev, val);
generic_phy = devm_phy_create(dev, dev->of_node, &ops);
if (IS_ERR(generic_phy)) {

View File

@ -335,13 +335,9 @@ static int uniphier_u3hsphy_probe(struct platform_device *pdev)
if (IS_ERR(priv->clk_parent))
return PTR_ERR(priv->clk_parent);
priv->clk_ext = devm_clk_get(dev, "phy-ext");
if (IS_ERR(priv->clk_ext)) {
if (PTR_ERR(priv->clk_ext) == -ENOENT)
priv->clk_ext = NULL;
else
return PTR_ERR(priv->clk_ext);
}
priv->clk_ext = devm_clk_get_optional(dev, "phy-ext");
if (IS_ERR(priv->clk_ext))
return PTR_ERR(priv->clk_ext);
priv->rst = devm_reset_control_get_shared(dev, "phy");
if (IS_ERR(priv->rst))

View File

@ -238,13 +238,9 @@ static int uniphier_u3ssphy_probe(struct platform_device *pdev)
if (IS_ERR(priv->clk))
return PTR_ERR(priv->clk);
priv->clk_ext = devm_clk_get(dev, "phy-ext");
if (IS_ERR(priv->clk_ext)) {
if (PTR_ERR(priv->clk_ext) == -ENOENT)
priv->clk_ext = NULL;
else
return PTR_ERR(priv->clk_ext);
}
priv->clk_ext = devm_clk_get_optional(dev, "phy-ext");
if (IS_ERR(priv->clk_ext))
return PTR_ERR(priv->clk_ext);
priv->rst = devm_reset_control_get_shared(dev, "phy");
if (IS_ERR(priv->rst))

View File

@ -4,3 +4,4 @@ phy-tegra-xusb-y += xusb.o
phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_124_SOC) += xusb-tegra124.o
phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_132_SOC) += xusb-tegra124.o
phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_210_SOC) += xusb-tegra210.o
phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_186_SOC) += xusb-tegra186.o

View File

@ -0,0 +1,899 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2016-2019, NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/regulator/consumer.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/slab.h>
#include <soc/tegra/fuse.h>
#include "xusb.h"
/* FUSE USB_CALIB registers */
#define HS_CURR_LEVEL_PADX_SHIFT(x) ((x) ? (11 + (x - 1) * 6) : 0)
#define HS_CURR_LEVEL_PAD_MASK 0x3f
#define HS_TERM_RANGE_ADJ_SHIFT 7
#define HS_TERM_RANGE_ADJ_MASK 0xf
#define HS_SQUELCH_SHIFT 29
#define HS_SQUELCH_MASK 0x7
#define RPD_CTRL_SHIFT 0
#define RPD_CTRL_MASK 0x1f
/* XUSB PADCTL registers */
#define XUSB_PADCTL_USB2_PAD_MUX 0x4
#define USB2_PORT_SHIFT(x) ((x) * 2)
#define USB2_PORT_MASK 0x3
#define PORT_XUSB 1
#define HSIC_PORT_SHIFT(x) ((x) + 20)
#define HSIC_PORT_MASK 0x1
#define PORT_HSIC 0
#define XUSB_PADCTL_USB2_PORT_CAP 0x8
#define XUSB_PADCTL_SS_PORT_CAP 0xc
#define PORTX_CAP_SHIFT(x) ((x) * 4)
#define PORT_CAP_MASK 0x3
#define PORT_CAP_DISABLED 0x0
#define PORT_CAP_HOST 0x1
#define PORT_CAP_DEVICE 0x2
#define PORT_CAP_OTG 0x3
#define XUSB_PADCTL_ELPG_PROGRAM 0x20
#define USB2_PORT_WAKE_INTERRUPT_ENABLE(x) BIT(x)
#define USB2_PORT_WAKEUP_EVENT(x) BIT((x) + 7)
#define SS_PORT_WAKE_INTERRUPT_ENABLE(x) BIT((x) + 14)
#define SS_PORT_WAKEUP_EVENT(x) BIT((x) + 21)
#define USB2_HSIC_PORT_WAKE_INTERRUPT_ENABLE(x) BIT((x) + 28)
#define USB2_HSIC_PORT_WAKEUP_EVENT(x) BIT((x) + 30)
#define ALL_WAKE_EVENTS \
(USB2_PORT_WAKEUP_EVENT(0) | USB2_PORT_WAKEUP_EVENT(1) | \
USB2_PORT_WAKEUP_EVENT(2) | SS_PORT_WAKEUP_EVENT(0) | \
SS_PORT_WAKEUP_EVENT(1) | SS_PORT_WAKEUP_EVENT(2) | \
USB2_HSIC_PORT_WAKEUP_EVENT(0))
#define XUSB_PADCTL_ELPG_PROGRAM_1 0x24
#define SSPX_ELPG_CLAMP_EN(x) BIT(0 + (x) * 3)
#define SSPX_ELPG_CLAMP_EN_EARLY(x) BIT(1 + (x) * 3)
#define SSPX_ELPG_VCORE_DOWN(x) BIT(2 + (x) * 3)
#define XUSB_PADCTL_USB2_OTG_PADX_CTL0(x) (0x88 + (x) * 0x40)
#define HS_CURR_LEVEL(x) ((x) & 0x3f)
#define TERM_SEL BIT(25)
#define USB2_OTG_PD BIT(26)
#define USB2_OTG_PD2 BIT(27)
#define USB2_OTG_PD2_OVRD_EN BIT(28)
#define USB2_OTG_PD_ZI BIT(29)
#define XUSB_PADCTL_USB2_OTG_PADX_CTL1(x) (0x8c + (x) * 0x40)
#define USB2_OTG_PD_DR BIT(2)
#define TERM_RANGE_ADJ(x) (((x) & 0xf) << 3)
#define RPD_CTRL(x) (((x) & 0x1f) << 26)
#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0 0x284
#define BIAS_PAD_PD BIT(11)
#define HS_SQUELCH_LEVEL(x) (((x) & 0x7) << 0)
#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1 0x288
#define USB2_TRK_START_TIMER(x) (((x) & 0x7f) << 12)
#define USB2_TRK_DONE_RESET_TIMER(x) (((x) & 0x7f) << 19)
#define USB2_PD_TRK BIT(26)
#define XUSB_PADCTL_HSIC_PADX_CTL0(x) (0x300 + (x) * 0x20)
#define HSIC_PD_TX_DATA0 BIT(1)
#define HSIC_PD_TX_STROBE BIT(3)
#define HSIC_PD_RX_DATA0 BIT(4)
#define HSIC_PD_RX_STROBE BIT(6)
#define HSIC_PD_ZI_DATA0 BIT(7)
#define HSIC_PD_ZI_STROBE BIT(9)
#define HSIC_RPD_DATA0 BIT(13)
#define HSIC_RPD_STROBE BIT(15)
#define HSIC_RPU_DATA0 BIT(16)
#define HSIC_RPU_STROBE BIT(18)
#define XUSB_PADCTL_HSIC_PAD_TRK_CTL0 0x340
#define HSIC_TRK_START_TIMER(x) (((x) & 0x7f) << 5)
#define HSIC_TRK_DONE_RESET_TIMER(x) (((x) & 0x7f) << 12)
#define HSIC_PD_TRK BIT(19)
#define USB2_VBUS_ID 0x360
#define VBUS_OVERRIDE BIT(14)
#define ID_OVERRIDE(x) (((x) & 0xf) << 18)
#define ID_OVERRIDE_FLOATING ID_OVERRIDE(8)
#define ID_OVERRIDE_GROUNDED ID_OVERRIDE(0)
#define TEGRA186_LANE(_name, _offset, _shift, _mask, _type) \
{ \
.name = _name, \
.offset = _offset, \
.shift = _shift, \
.mask = _mask, \
.num_funcs = ARRAY_SIZE(tegra186_##_type##_functions), \
.funcs = tegra186_##_type##_functions, \
}
struct tegra_xusb_fuse_calibration {
u32 *hs_curr_level;
u32 hs_squelch;
u32 hs_term_range_adj;
u32 rpd_ctrl;
};
struct tegra186_xusb_padctl {
struct tegra_xusb_padctl base;
struct tegra_xusb_fuse_calibration calib;
/* UTMI bias and tracking */
struct clk *usb2_trk_clk;
unsigned int bias_pad_enable;
};
static inline struct tegra186_xusb_padctl *
to_tegra186_xusb_padctl(struct tegra_xusb_padctl *padctl)
{
return container_of(padctl, struct tegra186_xusb_padctl, base);
}
/* USB 2.0 UTMI PHY support */
static struct tegra_xusb_lane *
tegra186_usb2_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np,
unsigned int index)
{
struct tegra_xusb_usb2_lane *usb2;
int err;
usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL);
if (!usb2)
return ERR_PTR(-ENOMEM);
INIT_LIST_HEAD(&usb2->base.list);
usb2->base.soc = &pad->soc->lanes[index];
usb2->base.index = index;
usb2->base.pad = pad;
usb2->base.np = np;
err = tegra_xusb_lane_parse_dt(&usb2->base, np);
if (err < 0) {
kfree(usb2);
return ERR_PTR(err);
}
return &usb2->base;
}
static void tegra186_usb2_lane_remove(struct tegra_xusb_lane *lane)
{
struct tegra_xusb_usb2_lane *usb2 = to_usb2_lane(lane);
kfree(usb2);
}
static const struct tegra_xusb_lane_ops tegra186_usb2_lane_ops = {
.probe = tegra186_usb2_lane_probe,
.remove = tegra186_usb2_lane_remove,
};
static void tegra186_utmi_bias_pad_power_on(struct tegra_xusb_padctl *padctl)
{
struct tegra186_xusb_padctl *priv = to_tegra186_xusb_padctl(padctl);
struct device *dev = padctl->dev;
u32 value;
int err;
mutex_lock(&padctl->lock);
if (priv->bias_pad_enable++ > 0) {
mutex_unlock(&padctl->lock);
return;
}
err = clk_prepare_enable(priv->usb2_trk_clk);
if (err < 0)
dev_warn(dev, "failed to enable USB2 trk clock: %d\n", err);
value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
value &= ~USB2_TRK_START_TIMER(~0);
value |= USB2_TRK_START_TIMER(0x1e);
value &= ~USB2_TRK_DONE_RESET_TIMER(~0);
value |= USB2_TRK_DONE_RESET_TIMER(0xa);
padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
value &= ~BIAS_PAD_PD;
value &= ~HS_SQUELCH_LEVEL(~0);
value |= HS_SQUELCH_LEVEL(priv->calib.hs_squelch);
padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
udelay(1);
value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
value &= ~USB2_PD_TRK;
padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
mutex_unlock(&padctl->lock);
}
static void tegra186_utmi_bias_pad_power_off(struct tegra_xusb_padctl *padctl)
{
struct tegra186_xusb_padctl *priv = to_tegra186_xusb_padctl(padctl);
u32 value;
mutex_lock(&padctl->lock);
if (WARN_ON(priv->bias_pad_enable == 0)) {
mutex_unlock(&padctl->lock);
return;
}
if (--priv->bias_pad_enable > 0) {
mutex_unlock(&padctl->lock);
return;
}
value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
value |= USB2_PD_TRK;
padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
clk_disable_unprepare(priv->usb2_trk_clk);
mutex_unlock(&padctl->lock);
}
static void tegra_phy_xusb_utmi_pad_power_on(struct phy *phy)
{
struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
struct tegra_xusb_padctl *padctl = lane->pad->padctl;
struct tegra_xusb_usb2_port *port;
struct device *dev = padctl->dev;
unsigned int index = lane->index;
u32 value;
if (!phy)
return;
port = tegra_xusb_find_usb2_port(padctl, index);
if (!port) {
dev_err(dev, "no port found for USB2 lane %u\n", index);
return;
}
tegra186_utmi_bias_pad_power_on(padctl);
udelay(2);
value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index));
value &= ~USB2_OTG_PD;
padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index));
value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
value &= ~USB2_OTG_PD_DR;
padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
}
static void tegra_phy_xusb_utmi_pad_power_down(struct phy *phy)
{
struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
struct tegra_xusb_padctl *padctl = lane->pad->padctl;
unsigned int index = lane->index;
u32 value;
if (!phy)
return;
value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index));
value |= USB2_OTG_PD;
padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index));
value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
value |= USB2_OTG_PD_DR;
padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
udelay(2);
tegra186_utmi_bias_pad_power_off(padctl);
}
static int tegra186_utmi_phy_power_on(struct phy *phy)
{
struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
struct tegra_xusb_usb2_lane *usb2 = to_usb2_lane(lane);
struct tegra_xusb_padctl *padctl = lane->pad->padctl;
struct tegra186_xusb_padctl *priv = to_tegra186_xusb_padctl(padctl);
struct tegra_xusb_usb2_port *port;
unsigned int index = lane->index;
struct device *dev = padctl->dev;
u32 value;
port = tegra_xusb_find_usb2_port(padctl, index);
if (!port) {
dev_err(dev, "no port found for USB2 lane %u\n", index);
return -ENODEV;
}
value = padctl_readl(padctl, XUSB_PADCTL_USB2_PAD_MUX);
value &= ~(USB2_PORT_MASK << USB2_PORT_SHIFT(index));
value |= (PORT_XUSB << USB2_PORT_SHIFT(index));
padctl_writel(padctl, value, XUSB_PADCTL_USB2_PAD_MUX);
value = padctl_readl(padctl, XUSB_PADCTL_USB2_PORT_CAP);
value &= ~(PORT_CAP_MASK << PORTX_CAP_SHIFT(index));
if (port->mode == USB_DR_MODE_UNKNOWN)
value |= (PORT_CAP_DISABLED << PORTX_CAP_SHIFT(index));
else if (port->mode == USB_DR_MODE_PERIPHERAL)
value |= (PORT_CAP_DEVICE << PORTX_CAP_SHIFT(index));
else if (port->mode == USB_DR_MODE_HOST)
value |= (PORT_CAP_HOST << PORTX_CAP_SHIFT(index));
else if (port->mode == USB_DR_MODE_OTG)
value |= (PORT_CAP_OTG << PORTX_CAP_SHIFT(index));
padctl_writel(padctl, value, XUSB_PADCTL_USB2_PORT_CAP);
value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index));
value &= ~USB2_OTG_PD_ZI;
value |= TERM_SEL;
value &= ~HS_CURR_LEVEL(~0);
if (usb2->hs_curr_level_offset) {
int hs_current_level;
hs_current_level = (int)priv->calib.hs_curr_level[index] +
usb2->hs_curr_level_offset;
if (hs_current_level < 0)
hs_current_level = 0;
if (hs_current_level > 0x3f)
hs_current_level = 0x3f;
value |= HS_CURR_LEVEL(hs_current_level);
} else {
value |= HS_CURR_LEVEL(priv->calib.hs_curr_level[index]);
}
padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index));
value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
value &= ~TERM_RANGE_ADJ(~0);
value |= TERM_RANGE_ADJ(priv->calib.hs_term_range_adj);
value &= ~RPD_CTRL(~0);
value |= RPD_CTRL(priv->calib.rpd_ctrl);
padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
/* TODO: pad power saving */
tegra_phy_xusb_utmi_pad_power_on(phy);
return 0;
}
static int tegra186_utmi_phy_power_off(struct phy *phy)
{
/* TODO: pad power saving */
tegra_phy_xusb_utmi_pad_power_down(phy);
return 0;
}
static int tegra186_utmi_phy_init(struct phy *phy)
{
struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
struct tegra_xusb_padctl *padctl = lane->pad->padctl;
struct tegra_xusb_usb2_port *port;
unsigned int index = lane->index;
struct device *dev = padctl->dev;
int err;
port = tegra_xusb_find_usb2_port(padctl, index);
if (!port) {
dev_err(dev, "no port found for USB2 lane %u\n", index);
return -ENODEV;
}
if (port->supply && port->mode == USB_DR_MODE_HOST) {
err = regulator_enable(port->supply);
if (err) {
dev_err(dev, "failed to enable port %u VBUS: %d\n",
index, err);
return err;
}
}
return 0;
}
static int tegra186_utmi_phy_exit(struct phy *phy)
{
struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
struct tegra_xusb_padctl *padctl = lane->pad->padctl;
struct tegra_xusb_usb2_port *port;
unsigned int index = lane->index;
struct device *dev = padctl->dev;
int err;
port = tegra_xusb_find_usb2_port(padctl, index);
if (!port) {
dev_err(dev, "no port found for USB2 lane %u\n", index);
return -ENODEV;
}
if (port->supply && port->mode == USB_DR_MODE_HOST) {
err = regulator_disable(port->supply);
if (err) {
dev_err(dev, "failed to disable port %u VBUS: %d\n",
index, err);
return err;
}
}
return 0;
}
static const struct phy_ops utmi_phy_ops = {
.init = tegra186_utmi_phy_init,
.exit = tegra186_utmi_phy_exit,
.power_on = tegra186_utmi_phy_power_on,
.power_off = tegra186_utmi_phy_power_off,
.owner = THIS_MODULE,
};
static struct tegra_xusb_pad *
tegra186_usb2_pad_probe(struct tegra_xusb_padctl *padctl,
const struct tegra_xusb_pad_soc *soc,
struct device_node *np)
{
struct tegra186_xusb_padctl *priv = to_tegra186_xusb_padctl(padctl);
struct tegra_xusb_usb2_pad *usb2;
struct tegra_xusb_pad *pad;
int err;
usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL);
if (!usb2)
return ERR_PTR(-ENOMEM);
pad = &usb2->base;
pad->ops = &tegra186_usb2_lane_ops;
pad->soc = soc;
err = tegra_xusb_pad_init(pad, padctl, np);
if (err < 0) {
kfree(usb2);
goto out;
}
priv->usb2_trk_clk = devm_clk_get(&pad->dev, "trk");
if (IS_ERR(priv->usb2_trk_clk)) {
err = PTR_ERR(priv->usb2_trk_clk);
dev_dbg(&pad->dev, "failed to get usb2 trk clock: %d\n", err);
goto unregister;
}
err = tegra_xusb_pad_register(pad, &utmi_phy_ops);
if (err < 0)
goto unregister;
dev_set_drvdata(&pad->dev, pad);
return pad;
unregister:
device_unregister(&pad->dev);
out:
return ERR_PTR(err);
}
static void tegra186_usb2_pad_remove(struct tegra_xusb_pad *pad)
{
struct tegra_xusb_usb2_pad *usb2 = to_usb2_pad(pad);
kfree(usb2);
}
static const struct tegra_xusb_pad_ops tegra186_usb2_pad_ops = {
.probe = tegra186_usb2_pad_probe,
.remove = tegra186_usb2_pad_remove,
};
static const char * const tegra186_usb2_functions[] = {
"xusb",
};
static const struct tegra_xusb_lane_soc tegra186_usb2_lanes[] = {
TEGRA186_LANE("usb2-0", 0, 0, 0, usb2),
TEGRA186_LANE("usb2-1", 0, 0, 0, usb2),
TEGRA186_LANE("usb2-2", 0, 0, 0, usb2),
};
static const struct tegra_xusb_pad_soc tegra186_usb2_pad = {
.name = "usb2",
.num_lanes = ARRAY_SIZE(tegra186_usb2_lanes),
.lanes = tegra186_usb2_lanes,
.ops = &tegra186_usb2_pad_ops,
};
static int tegra186_usb2_port_enable(struct tegra_xusb_port *port)
{
return 0;
}
static void tegra186_usb2_port_disable(struct tegra_xusb_port *port)
{
}
static struct tegra_xusb_lane *
tegra186_usb2_port_map(struct tegra_xusb_port *port)
{
return tegra_xusb_find_lane(port->padctl, "usb2", port->index);
}
static const struct tegra_xusb_port_ops tegra186_usb2_port_ops = {
.enable = tegra186_usb2_port_enable,
.disable = tegra186_usb2_port_disable,
.map = tegra186_usb2_port_map,
};
/* SuperSpeed PHY support */
static struct tegra_xusb_lane *
tegra186_usb3_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np,
unsigned int index)
{
struct tegra_xusb_usb3_lane *usb3;
int err;
usb3 = kzalloc(sizeof(*usb3), GFP_KERNEL);
if (!usb3)
return ERR_PTR(-ENOMEM);
INIT_LIST_HEAD(&usb3->base.list);
usb3->base.soc = &pad->soc->lanes[index];
usb3->base.index = index;
usb3->base.pad = pad;
usb3->base.np = np;
err = tegra_xusb_lane_parse_dt(&usb3->base, np);
if (err < 0) {
kfree(usb3);
return ERR_PTR(err);
}
return &usb3->base;
}
static void tegra186_usb3_lane_remove(struct tegra_xusb_lane *lane)
{
struct tegra_xusb_usb3_lane *usb3 = to_usb3_lane(lane);
kfree(usb3);
}
static const struct tegra_xusb_lane_ops tegra186_usb3_lane_ops = {
.probe = tegra186_usb3_lane_probe,
.remove = tegra186_usb3_lane_remove,
};
static int tegra186_usb3_port_enable(struct tegra_xusb_port *port)
{
return 0;
}
static void tegra186_usb3_port_disable(struct tegra_xusb_port *port)
{
}
static struct tegra_xusb_lane *
tegra186_usb3_port_map(struct tegra_xusb_port *port)
{
return tegra_xusb_find_lane(port->padctl, "usb3", port->index);
}
static const struct tegra_xusb_port_ops tegra186_usb3_port_ops = {
.enable = tegra186_usb3_port_enable,
.disable = tegra186_usb3_port_disable,
.map = tegra186_usb3_port_map,
};
static int tegra186_usb3_phy_power_on(struct phy *phy)
{
struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
struct tegra_xusb_padctl *padctl = lane->pad->padctl;
struct tegra_xusb_usb3_port *port;
struct tegra_xusb_usb2_port *usb2;
unsigned int index = lane->index;
struct device *dev = padctl->dev;
u32 value;
port = tegra_xusb_find_usb3_port(padctl, index);
if (!port) {
dev_err(dev, "no port found for USB3 lane %u\n", index);
return -ENODEV;
}
usb2 = tegra_xusb_find_usb2_port(padctl, port->port);
if (!usb2) {
dev_err(dev, "no companion port found for USB3 lane %u\n",
index);
return -ENODEV;
}
mutex_lock(&padctl->lock);
value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_CAP);
value &= ~(PORT_CAP_MASK << PORTX_CAP_SHIFT(index));
if (usb2->mode == USB_DR_MODE_UNKNOWN)
value |= (PORT_CAP_DISABLED << PORTX_CAP_SHIFT(index));
else if (usb2->mode == USB_DR_MODE_PERIPHERAL)
value |= (PORT_CAP_DEVICE << PORTX_CAP_SHIFT(index));
else if (usb2->mode == USB_DR_MODE_HOST)
value |= (PORT_CAP_HOST << PORTX_CAP_SHIFT(index));
else if (usb2->mode == USB_DR_MODE_OTG)
value |= (PORT_CAP_OTG << PORTX_CAP_SHIFT(index));
padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_CAP);
value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1);
value &= ~SSPX_ELPG_VCORE_DOWN(index);
padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1);
usleep_range(100, 200);
value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1);
value &= ~SSPX_ELPG_CLAMP_EN_EARLY(index);
padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1);
usleep_range(100, 200);
value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1);
value &= ~SSPX_ELPG_CLAMP_EN(index);
padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1);
mutex_unlock(&padctl->lock);
return 0;
}
static int tegra186_usb3_phy_power_off(struct phy *phy)
{
struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
struct tegra_xusb_padctl *padctl = lane->pad->padctl;
struct tegra_xusb_usb3_port *port;
unsigned int index = lane->index;
struct device *dev = padctl->dev;
u32 value;
port = tegra_xusb_find_usb3_port(padctl, index);
if (!port) {
dev_err(dev, "no port found for USB3 lane %u\n", index);
return -ENODEV;
}
mutex_lock(&padctl->lock);
value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1);
value |= SSPX_ELPG_CLAMP_EN_EARLY(index);
padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1);
usleep_range(100, 200);
value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1);
value |= SSPX_ELPG_CLAMP_EN(index);
padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1);
usleep_range(250, 350);
value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1);
value |= SSPX_ELPG_VCORE_DOWN(index);
padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1);
mutex_unlock(&padctl->lock);
return 0;
}
static int tegra186_usb3_phy_init(struct phy *phy)
{
return 0;
}
static int tegra186_usb3_phy_exit(struct phy *phy)
{
return 0;
}
static const struct phy_ops usb3_phy_ops = {
.init = tegra186_usb3_phy_init,
.exit = tegra186_usb3_phy_exit,
.power_on = tegra186_usb3_phy_power_on,
.power_off = tegra186_usb3_phy_power_off,
.owner = THIS_MODULE,
};
static struct tegra_xusb_pad *
tegra186_usb3_pad_probe(struct tegra_xusb_padctl *padctl,
const struct tegra_xusb_pad_soc *soc,
struct device_node *np)
{
struct tegra_xusb_usb3_pad *usb3;
struct tegra_xusb_pad *pad;
int err;
usb3 = kzalloc(sizeof(*usb3), GFP_KERNEL);
if (!usb3)
return ERR_PTR(-ENOMEM);
pad = &usb3->base;
pad->ops = &tegra186_usb3_lane_ops;
pad->soc = soc;
err = tegra_xusb_pad_init(pad, padctl, np);
if (err < 0) {
kfree(usb3);
goto out;
}
err = tegra_xusb_pad_register(pad, &usb3_phy_ops);
if (err < 0)
goto unregister;
dev_set_drvdata(&pad->dev, pad);
return pad;
unregister:
device_unregister(&pad->dev);
out:
return ERR_PTR(err);
}
static void tegra186_usb3_pad_remove(struct tegra_xusb_pad *pad)
{
struct tegra_xusb_usb2_pad *usb2 = to_usb2_pad(pad);
kfree(usb2);
}
static const struct tegra_xusb_pad_ops tegra186_usb3_pad_ops = {
.probe = tegra186_usb3_pad_probe,
.remove = tegra186_usb3_pad_remove,
};
static const char * const tegra186_usb3_functions[] = {
"xusb",
};
static const struct tegra_xusb_lane_soc tegra186_usb3_lanes[] = {
TEGRA186_LANE("usb3-0", 0, 0, 0, usb3),
TEGRA186_LANE("usb3-1", 0, 0, 0, usb3),
TEGRA186_LANE("usb3-2", 0, 0, 0, usb3),
};
static const struct tegra_xusb_pad_soc tegra186_usb3_pad = {
.name = "usb3",
.num_lanes = ARRAY_SIZE(tegra186_usb3_lanes),
.lanes = tegra186_usb3_lanes,
.ops = &tegra186_usb3_pad_ops,
};
static const struct tegra_xusb_pad_soc * const tegra186_pads[] = {
&tegra186_usb2_pad,
&tegra186_usb3_pad,
#if 0 /* TODO implement */
&tegra186_hsic_pad,
#endif
};
static int
tegra186_xusb_read_fuse_calibration(struct tegra186_xusb_padctl *padctl)
{
struct device *dev = padctl->base.dev;
unsigned int i, count;
u32 value, *level;
int err;
count = padctl->base.soc->ports.usb2.count;
level = devm_kcalloc(dev, count, sizeof(u32), GFP_KERNEL);
if (!level)
return -ENOMEM;
err = tegra_fuse_readl(TEGRA_FUSE_SKU_CALIB_0, &value);
if (err) {
dev_err(dev, "failed to read calibration fuse: %d\n", err);
return err;
}
dev_dbg(dev, "FUSE_USB_CALIB_0 %#x\n", value);
for (i = 0; i < count; i++)
level[i] = (value >> HS_CURR_LEVEL_PADX_SHIFT(i)) &
HS_CURR_LEVEL_PAD_MASK;
padctl->calib.hs_curr_level = level;
padctl->calib.hs_squelch = (value >> HS_SQUELCH_SHIFT) &
HS_SQUELCH_MASK;
padctl->calib.hs_term_range_adj = (value >> HS_TERM_RANGE_ADJ_SHIFT) &
HS_TERM_RANGE_ADJ_MASK;
err = tegra_fuse_readl(TEGRA_FUSE_USB_CALIB_EXT_0, &value);
if (err) {
dev_err(dev, "failed to read calibration fuse: %d\n", err);
return err;
}
dev_dbg(dev, "FUSE_USB_CALIB_EXT_0 %#x\n", value);
padctl->calib.rpd_ctrl = (value >> RPD_CTRL_SHIFT) & RPD_CTRL_MASK;
return 0;
}
static struct tegra_xusb_padctl *
tegra186_xusb_padctl_probe(struct device *dev,
const struct tegra_xusb_padctl_soc *soc)
{
struct tegra186_xusb_padctl *priv;
int err;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return ERR_PTR(-ENOMEM);
priv->base.dev = dev;
priv->base.soc = soc;
err = tegra186_xusb_read_fuse_calibration(priv);
if (err < 0)
return ERR_PTR(err);
return &priv->base;
}
static void tegra186_xusb_padctl_remove(struct tegra_xusb_padctl *padctl)
{
}
static const struct tegra_xusb_padctl_ops tegra186_xusb_padctl_ops = {
.probe = tegra186_xusb_padctl_probe,
.remove = tegra186_xusb_padctl_remove,
};
static const char * const tegra186_xusb_padctl_supply_names[] = {
"avdd-pll-erefeut",
"avdd-usb",
"vclamp-usb",
"vddio-hsic",
};
const struct tegra_xusb_padctl_soc tegra186_xusb_padctl_soc = {
.num_pads = ARRAY_SIZE(tegra186_pads),
.pads = tegra186_pads,
.ports = {
.usb2 = {
.ops = &tegra186_usb2_port_ops,
.count = 3,
},
#if 0 /* TODO implement */
.hsic = {
.ops = &tegra186_hsic_port_ops,
.count = 1,
},
#endif
.usb3 = {
.ops = &tegra186_usb3_port_ops,
.count = 3,
},
},
.ops = &tegra186_xusb_padctl_ops,
.supply_names = tegra186_xusb_padctl_supply_names,
.num_supplies = ARRAY_SIZE(tegra186_xusb_padctl_supply_names),
};
EXPORT_SYMBOL_GPL(tegra186_xusb_padctl_soc);
MODULE_AUTHOR("JC Kuo <jckuo@nvidia.com>");
MODULE_DESCRIPTION("NVIDIA Tegra186 XUSB Pad Controller driver");
MODULE_LICENSE("GPL v2");

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014-2015, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2014-2016, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
@ -67,6 +67,12 @@ static const struct of_device_id tegra_xusb_padctl_of_match[] = {
.compatible = "nvidia,tegra210-xusb-padctl",
.data = &tegra210_xusb_padctl_soc,
},
#endif
#if defined(CONFIG_ARCH_TEGRA_186_SOC)
{
.compatible = "nvidia,tegra186-xusb-padctl",
.data = &tegra186_xusb_padctl_soc,
},
#endif
{ }
};
@ -313,6 +319,10 @@ static void tegra_xusb_lane_program(struct tegra_xusb_lane *lane)
const struct tegra_xusb_lane_soc *soc = lane->soc;
u32 value;
/* skip single function lanes */
if (soc->num_funcs < 2)
return;
/* choose function */
value = padctl_readl(padctl, soc->offset);
value &= ~(soc->mask << soc->shift);
@ -542,13 +552,34 @@ static void tegra_xusb_port_unregister(struct tegra_xusb_port *port)
device_unregister(&port->dev);
}
static const char *const modes[] = {
[USB_DR_MODE_UNKNOWN] = "",
[USB_DR_MODE_HOST] = "host",
[USB_DR_MODE_PERIPHERAL] = "peripheral",
[USB_DR_MODE_OTG] = "otg",
};
static int tegra_xusb_usb2_port_parse_dt(struct tegra_xusb_usb2_port *usb2)
{
struct tegra_xusb_port *port = &usb2->base;
struct device_node *np = port->dev.of_node;
const char *mode;
usb2->internal = of_property_read_bool(np, "nvidia,internal");
if (!of_property_read_string(np, "mode", &mode)) {
int err = match_string(modes, ARRAY_SIZE(modes), mode);
if (err < 0) {
dev_err(&port->dev, "invalid value %s for \"mode\"\n",
mode);
usb2->mode = USB_DR_MODE_UNKNOWN;
} else {
usb2->mode = err;
}
} else {
usb2->mode = USB_DR_MODE_HOST;
}
usb2->supply = devm_regulator_get(&port->dev, "vbus");
return PTR_ERR_OR_ZERO(usb2->supply);
}
@ -839,6 +870,7 @@ static int tegra_xusb_padctl_probe(struct platform_device *pdev)
struct tegra_xusb_padctl *padctl;
const struct of_device_id *match;
struct resource *res;
unsigned int i;
int err;
/* for backwards compatibility with old device trees */
@ -876,14 +908,38 @@ static int tegra_xusb_padctl_probe(struct platform_device *pdev)
goto remove;
}
padctl->supplies = devm_kcalloc(&pdev->dev, padctl->soc->num_supplies,
sizeof(*padctl->supplies), GFP_KERNEL);
if (!padctl->supplies) {
err = -ENOMEM;
goto remove;
}
for (i = 0; i < padctl->soc->num_supplies; i++)
padctl->supplies[i].supply = padctl->soc->supply_names[i];
err = devm_regulator_bulk_get(&pdev->dev, padctl->soc->num_supplies,
padctl->supplies);
if (err < 0) {
dev_err(&pdev->dev, "failed to get regulators: %d\n", err);
goto remove;
}
err = reset_control_deassert(padctl->rst);
if (err < 0)
goto remove;
err = regulator_bulk_enable(padctl->soc->num_supplies,
padctl->supplies);
if (err < 0) {
dev_err(&pdev->dev, "failed to enable supplies: %d\n", err);
goto reset;
}
err = tegra_xusb_setup_pads(padctl);
if (err < 0) {
dev_err(&pdev->dev, "failed to setup pads: %d\n", err);
goto reset;
goto power_down;
}
err = tegra_xusb_setup_ports(padctl);
@ -896,6 +952,8 @@ static int tegra_xusb_padctl_probe(struct platform_device *pdev)
remove_pads:
tegra_xusb_remove_pads(padctl);
power_down:
regulator_bulk_disable(padctl->soc->num_supplies, padctl->supplies);
reset:
reset_control_assert(padctl->rst);
remove:
@ -911,6 +969,11 @@ static int tegra_xusb_padctl_remove(struct platform_device *pdev)
tegra_xusb_remove_ports(padctl);
tegra_xusb_remove_pads(padctl);
err = regulator_bulk_disable(padctl->soc->num_supplies,
padctl->supplies);
if (err < 0)
dev_err(&pdev->dev, "failed to disable supplies: %d\n", err);
err = reset_control_assert(padctl->rst);
if (err < 0)
dev_err(&pdev->dev, "failed to assert reset: %d\n", err);

View File

@ -19,6 +19,8 @@
#include <linux/mutex.h>
#include <linux/workqueue.h>
#include <linux/usb/otg.h>
/* legacy entry points for backwards-compatibility */
int tegra_xusb_padctl_legacy_probe(struct platform_device *pdev);
int tegra_xusb_padctl_legacy_remove(struct platform_device *pdev);
@ -54,10 +56,21 @@ struct tegra_xusb_lane {
int tegra_xusb_lane_parse_dt(struct tegra_xusb_lane *lane,
struct device_node *np);
struct tegra_xusb_usb3_lane {
struct tegra_xusb_lane base;
};
static inline struct tegra_xusb_usb3_lane *
to_usb3_lane(struct tegra_xusb_lane *lane)
{
return container_of(lane, struct tegra_xusb_usb3_lane, base);
}
struct tegra_xusb_usb2_lane {
struct tegra_xusb_lane base;
u32 hs_curr_level_offset;
bool powered_on;
};
static inline struct tegra_xusb_usb2_lane *
@ -168,6 +181,19 @@ int tegra_xusb_pad_register(struct tegra_xusb_pad *pad,
const struct phy_ops *ops);
void tegra_xusb_pad_unregister(struct tegra_xusb_pad *pad);
struct tegra_xusb_usb3_pad {
struct tegra_xusb_pad base;
unsigned int enable;
struct mutex lock;
};
static inline struct tegra_xusb_usb3_pad *
to_usb3_pad(struct tegra_xusb_pad *pad)
{
return container_of(pad, struct tegra_xusb_usb3_pad, base);
}
struct tegra_xusb_usb2_pad {
struct tegra_xusb_pad base;
@ -271,6 +297,7 @@ struct tegra_xusb_usb2_port {
struct tegra_xusb_port base;
struct regulator *supply;
enum usb_dr_mode mode;
bool internal;
};
@ -367,6 +394,9 @@ struct tegra_xusb_padctl_soc {
} ports;
const struct tegra_xusb_padctl_ops *ops;
const char * const *supply_names;
unsigned int num_supplies;
};
struct tegra_xusb_padctl {
@ -390,6 +420,8 @@ struct tegra_xusb_padctl {
unsigned int enable;
struct clk *clk;
struct regulator_bulk_data *supplies;
};
static inline void padctl_writel(struct tegra_xusb_padctl *padctl, u32 value,
@ -417,5 +449,8 @@ extern const struct tegra_xusb_padctl_soc tegra124_xusb_padctl_soc;
#if defined(CONFIG_ARCH_TEGRA_210_SOC)
extern const struct tegra_xusb_padctl_soc tegra210_xusb_padctl_soc;
#endif
#if defined(CONFIG_ARCH_TEGRA_186_SOC)
extern const struct tegra_xusb_padctl_soc tegra186_xusb_padctl_soc;
#endif
#endif /* __PHY_TEGRA_XUSB_H */

View File

@ -20,6 +20,18 @@ config PHY_DM816X_USB
help
Enable this for dm816x USB to work.
config PHY_AM654_SERDES
tristate "TI AM654 SERDES support"
depends on OF && ARCH_K3 || COMPILE_TEST
depends on COMMON_CLK
select GENERIC_PHY
select MULTIPLEXER
select REGMAP_MMIO
select MUX_MMIO
help
This option enables support for TI AM654 SerDes PHY used for
PCIe.
config OMAP_CONTROL_PHY
tristate "OMAP CONTROL PHY Driver"
depends on ARCH_OMAP2PLUS || COMPILE_TEST

View File

@ -6,4 +6,5 @@ obj-$(CONFIG_OMAP_USB2) += phy-omap-usb2.o
obj-$(CONFIG_TI_PIPE3) += phy-ti-pipe3.o
obj-$(CONFIG_PHY_TUSB1210) += phy-tusb1210.o
obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o
obj-$(CONFIG_PHY_AM654_SERDES) += phy-am654-serdes.o
obj-$(CONFIG_PHY_TI_GMII_SEL) += phy-gmii-sel.o

View File

@ -0,0 +1,658 @@
// SPDX-License-Identifier: GPL-2.0
/**
* PCIe SERDES driver for AM654x SoC
*
* Copyright (C) 2018 - 2019 Texas Instruments Incorporated - http://www.ti.com/
* Author: Kishon Vijay Abraham I <kishon@ti.com>
*/
#include <dt-bindings/phy/phy.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/mfd/syscon.h>
#include <linux/mux/consumer.h>
#include <linux/of_address.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#define CMU_R07C 0x7c
#define COMLANE_R138 0xb38
#define VERSION 0x70
#define COMLANE_R190 0xb90
#define COMLANE_R194 0xb94
#define SERDES_CTRL 0x1fd0
#define WIZ_LANEXCTL_STS 0x1fe0
#define TX0_DISABLE_STATE 0x4
#define TX0_SLEEP_STATE 0x5
#define TX0_SNOOZE_STATE 0x6
#define TX0_ENABLE_STATE 0x7
#define RX0_DISABLE_STATE 0x4
#define RX0_SLEEP_STATE 0x5
#define RX0_SNOOZE_STATE 0x6
#define RX0_ENABLE_STATE 0x7
#define WIZ_PLL_CTRL 0x1ff4
#define PLL_DISABLE_STATE 0x4
#define PLL_SLEEP_STATE 0x5
#define PLL_SNOOZE_STATE 0x6
#define PLL_ENABLE_STATE 0x7
#define PLL_LOCK_TIME 100000 /* in microseconds */
#define SLEEP_TIME 100 /* in microseconds */
#define LANE_USB3 0x0
#define LANE_PCIE0_LANE0 0x1
#define LANE_PCIE1_LANE0 0x0
#define LANE_PCIE0_LANE1 0x1
#define SERDES_NUM_CLOCKS 3
#define AM654_SERDES_CTRL_CLKSEL_MASK GENMASK(7, 4)
#define AM654_SERDES_CTRL_CLKSEL_SHIFT 4
struct serdes_am654_clk_mux {
struct clk_hw hw;
struct regmap *regmap;
unsigned int reg;
int clk_id;
struct clk_init_data clk_data;
};
#define to_serdes_am654_clk_mux(_hw) \
container_of(_hw, struct serdes_am654_clk_mux, hw)
static struct regmap_config serdes_am654_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.fast_io = true,
};
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);
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 device *dev;
struct mux_control *control;
bool busy;
u32 type;
struct device_node *of_node;
struct clk_onecell_data clk_data;
struct clk *clks[SERDES_NUM_CLOCKS];
};
static int serdes_am654_enable_pll(struct serdes_am654 *phy)
{
int ret;
u32 val;
ret = regmap_field_write(phy->pll_enable, PLL_ENABLE_STATE);
if (ret)
return ret;
return regmap_field_read_poll_timeout(phy->pll_ok, val, val, 1000,
PLL_LOCK_TIME);
}
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);
if (ret)
dev_err(dev, "Failed to disable PLL\n");
}
static int serdes_am654_enable_txrx(struct serdes_am654 *phy)
{
int ret;
/* Enable TX */
ret = regmap_field_write(phy->tx0_enable, TX0_ENABLE_STATE);
if (ret)
return ret;
/* Enable RX */
ret = regmap_field_write(phy->rx0_enable, RX0_ENABLE_STATE);
if (ret)
return ret;
return 0;
}
static int serdes_am654_disable_txrx(struct serdes_am654 *phy)
{
int ret;
/* Disable TX */
ret = regmap_field_write(phy->tx0_enable, TX0_DISABLE_STATE);
if (ret)
return ret;
/* Disable RX */
ret = regmap_field_write(phy->rx0_enable, RX0_DISABLE_STATE);
if (ret)
return ret;
return 0;
}
static int serdes_am654_power_on(struct phy *x)
{
struct serdes_am654 *phy = phy_get_drvdata(x);
struct device *dev = phy->dev;
int ret;
u32 val;
ret = serdes_am654_enable_pll(phy);
if (ret) {
dev_err(dev, "Failed to enable PLL\n");
return ret;
}
ret = serdes_am654_enable_txrx(phy);
if (ret) {
dev_err(dev, "Failed to enable TX RX\n");
return ret;
}
return regmap_field_read_poll_timeout(phy->cmu_ok_i_0, val, val,
SLEEP_TIME, PLL_LOCK_TIME);
}
static int serdes_am654_power_off(struct phy *x)
{
struct serdes_am654 *phy = phy_get_drvdata(x);
serdes_am654_disable_txrx(phy);
serdes_am654_disable_pll(phy);
return 0;
}
static int serdes_am654_init(struct phy *x)
{
struct serdes_am654 *phy = phy_get_drvdata(x);
int ret;
ret = regmap_field_write(phy->config_version, VERSION);
if (ret)
return ret;
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 0;
}
static int serdes_am654_reset(struct phy *x)
{
struct serdes_am654 *phy = phy_get_drvdata(x);
int ret;
ret = regmap_field_write(phy->por_en, 0x1);
if (ret)
return ret;
mdelay(1);
ret = regmap_field_write(phy->por_en, 0x0);
if (ret)
return ret;
return 0;
}
static void serdes_am654_release(struct phy *x)
{
struct serdes_am654 *phy = phy_get_drvdata(x);
phy->type = PHY_NONE;
phy->busy = false;
mux_control_deselect(phy->control);
}
struct phy *serdes_am654_xlate(struct device *dev, struct of_phandle_args
*args)
{
struct serdes_am654 *am654_phy;
struct phy *phy;
int ret;
phy = of_phy_simple_xlate(dev, args);
if (IS_ERR(phy))
return phy;
am654_phy = phy_get_drvdata(phy);
if (am654_phy->busy)
return ERR_PTR(-EBUSY);
ret = mux_control_select(am654_phy->control, args->args[1]);
if (ret) {
dev_err(dev, "Failed to select SERDES Lane Function\n");
return ERR_PTR(ret);
}
am654_phy->busy = true;
am654_phy->type = args->args[0];
return phy;
}
static const struct phy_ops ops = {
.reset = serdes_am654_reset,
.init = serdes_am654_init,
.power_on = serdes_am654_power_on,
.power_off = serdes_am654_power_off,
.release = serdes_am654_release,
.owner = THIS_MODULE,
};
#define SERDES_NUM_MUX_COMBINATIONS 16
#define LICLK 0
#define EXT_REFCLK 1
#define RICLK 2
static const int
serdes_am654_mux_table[SERDES_NUM_MUX_COMBINATIONS][SERDES_NUM_CLOCKS] = {
/*
* Each combination maps to one of
* "Figure 12-1986. SerDes Reference Clock Distribution"
* in TRM.
*/
/* Parent of CMU refclk, Left output, Right output
* either of EXT_REFCLK, LICLK, RICLK
*/
{ EXT_REFCLK, EXT_REFCLK, EXT_REFCLK }, /* 0000 */
{ RICLK, EXT_REFCLK, EXT_REFCLK }, /* 0001 */
{ EXT_REFCLK, RICLK, LICLK }, /* 0010 */
{ RICLK, RICLK, EXT_REFCLK }, /* 0011 */
{ LICLK, EXT_REFCLK, EXT_REFCLK }, /* 0100 */
{ EXT_REFCLK, EXT_REFCLK, EXT_REFCLK }, /* 0101 */
{ LICLK, RICLK, LICLK }, /* 0110 */
{ EXT_REFCLK, RICLK, LICLK }, /* 0111 */
{ EXT_REFCLK, EXT_REFCLK, LICLK }, /* 1000 */
{ RICLK, EXT_REFCLK, LICLK }, /* 1001 */
{ EXT_REFCLK, RICLK, EXT_REFCLK }, /* 1010 */
{ RICLK, RICLK, EXT_REFCLK }, /* 1011 */
{ LICLK, EXT_REFCLK, LICLK }, /* 1100 */
{ EXT_REFCLK, EXT_REFCLK, LICLK }, /* 1101 */
{ LICLK, RICLK, EXT_REFCLK }, /* 1110 */
{ EXT_REFCLK, RICLK, EXT_REFCLK }, /* 1111 */
};
static u8 serdes_am654_clk_mux_get_parent(struct clk_hw *hw)
{
struct serdes_am654_clk_mux *mux = to_serdes_am654_clk_mux(hw);
struct regmap *regmap = mux->regmap;
unsigned int reg = mux->reg;
unsigned int val;
regmap_read(regmap, reg, &val);
val &= AM654_SERDES_CTRL_CLKSEL_MASK;
val >>= AM654_SERDES_CTRL_CLKSEL_SHIFT;
return serdes_am654_mux_table[val][mux->clk_id];
}
static int serdes_am654_clk_mux_set_parent(struct clk_hw *hw, u8 index)
{
struct serdes_am654_clk_mux *mux = to_serdes_am654_clk_mux(hw);
struct regmap *regmap = mux->regmap;
unsigned int reg = mux->reg;
int clk_id = mux->clk_id;
int parents[SERDES_NUM_CLOCKS];
const int *p;
u32 val;
int found, i;
int ret;
/* get existing setting */
regmap_read(regmap, reg, &val);
val &= AM654_SERDES_CTRL_CLKSEL_MASK;
val >>= AM654_SERDES_CTRL_CLKSEL_SHIFT;
for (i = 0; i < SERDES_NUM_CLOCKS; i++)
parents[i] = serdes_am654_mux_table[val][i];
/* change parent of this clock. others left intact */
parents[clk_id] = index;
/* Find the match */
for (val = 0; val < SERDES_NUM_MUX_COMBINATIONS; val++) {
p = serdes_am654_mux_table[val];
found = 1;
for (i = 0; i < SERDES_NUM_CLOCKS; i++) {
if (parents[i] != p[i]) {
found = 0;
break;
}
}
if (found)
break;
}
if (!found) {
/*
* This can never happen, unless we missed
* a valid combination in serdes_am654_mux_table.
*/
WARN(1, "Failed to find the parent of %s clock\n",
hw->init->name);
return -EINVAL;
}
val <<= AM654_SERDES_CTRL_CLKSEL_SHIFT;
ret = regmap_update_bits(regmap, reg, AM654_SERDES_CTRL_CLKSEL_MASK,
val);
return ret;
}
static const struct clk_ops serdes_am654_clk_mux_ops = {
.set_parent = serdes_am654_clk_mux_set_parent,
.get_parent = serdes_am654_clk_mux_get_parent,
};
static int serdes_am654_clk_register(struct serdes_am654 *am654_phy,
const char *clock_name, int clock_num)
{
struct device_node *node = am654_phy->of_node;
struct device *dev = am654_phy->dev;
struct serdes_am654_clk_mux *mux;
struct device_node *regmap_node;
const char **parent_names;
struct clk_init_data *init;
unsigned int num_parents;
struct regmap *regmap;
const __be32 *addr;
unsigned int reg;
struct clk *clk;
mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
if (!mux)
return -ENOMEM;
init = &mux->clk_data;
regmap_node = of_parse_phandle(node, "ti,serdes-clk", 0);
of_node_put(regmap_node);
if (!regmap_node) {
dev_err(dev, "Fail to get serdes-clk node\n");
return -ENODEV;
}
regmap = syscon_node_to_regmap(regmap_node->parent);
if (IS_ERR(regmap)) {
dev_err(dev, "Fail to get Syscon regmap\n");
return PTR_ERR(regmap);
}
num_parents = of_clk_get_parent_count(node);
if (num_parents < 2) {
dev_err(dev, "SERDES clock must have parents\n");
return -EINVAL;
}
parent_names = devm_kzalloc(dev, (sizeof(char *) * num_parents),
GFP_KERNEL);
if (!parent_names)
return -ENOMEM;
of_clk_parent_fill(node, parent_names, num_parents);
addr = of_get_address(regmap_node, 0, NULL, NULL);
if (!addr)
return -EINVAL;
reg = be32_to_cpu(*addr);
init->ops = &serdes_am654_clk_mux_ops;
init->flags = CLK_SET_RATE_NO_REPARENT;
init->parent_names = parent_names;
init->num_parents = num_parents;
init->name = clock_name;
mux->regmap = regmap;
mux->reg = reg;
mux->clk_id = clock_num;
mux->hw.init = init;
clk = devm_clk_register(dev, &mux->hw);
if (IS_ERR(clk))
return PTR_ERR(clk);
am654_phy->clks[clock_num] = clk;
return 0;
}
static const struct of_device_id serdes_am654_id_table[] = {
{
.compatible = "ti,phy-am654-serdes",
},
{}
};
MODULE_DEVICE_TABLE(of, serdes_am654_id_table);
static int serdes_am654_regfield_init(struct serdes_am654 *am654_phy)
{
struct regmap *regmap = am654_phy->regmap;
struct device *dev = am654_phy->dev;
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);
}
return 0;
}
static int serdes_am654_probe(struct platform_device *pdev)
{
struct phy_provider *phy_provider;
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct clk_onecell_data *clk_data;
struct serdes_am654 *am654_phy;
struct mux_control *control;
const char *clock_name;
struct regmap *regmap;
void __iomem *base;
struct phy *phy;
int ret;
int i;
am654_phy = devm_kzalloc(dev, sizeof(*am654_phy), GFP_KERNEL);
if (!am654_phy)
return -ENOMEM;
base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base))
return PTR_ERR(base);
regmap = devm_regmap_init_mmio(dev, base, &serdes_am654_regmap_config);
if (IS_ERR(regmap)) {
dev_err(dev, "Failed to initialize regmap\n");
return PTR_ERR(regmap);
}
control = devm_mux_control_get(dev, NULL);
if (IS_ERR(control))
return PTR_ERR(control);
am654_phy->dev = dev;
am654_phy->of_node = node;
am654_phy->regmap = regmap;
am654_phy->control = control;
am654_phy->type = PHY_NONE;
ret = serdes_am654_regfield_init(am654_phy);
if (ret) {
dev_err(dev, "Failed to initialize regfields\n");
return ret;
}
platform_set_drvdata(pdev, am654_phy);
for (i = 0; i < SERDES_NUM_CLOCKS; i++) {
ret = of_property_read_string_index(node, "clock-output-names",
i, &clock_name);
if (ret) {
dev_err(dev, "Failed to get clock name\n");
return ret;
}
ret = serdes_am654_clk_register(am654_phy, clock_name, i);
if (ret) {
dev_err(dev, "Failed to initialize clock %s\n",
clock_name);
return ret;
}
}
clk_data = &am654_phy->clk_data;
clk_data->clks = am654_phy->clks;
clk_data->clk_num = SERDES_NUM_CLOCKS;
ret = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
if (ret)
return ret;
pm_runtime_enable(dev);
phy = devm_phy_create(dev, NULL, &ops);
if (IS_ERR(phy))
return PTR_ERR(phy);
phy_set_drvdata(phy, am654_phy);
phy_provider = devm_of_phy_provider_register(dev, serdes_am654_xlate);
if (IS_ERR(phy_provider)) {
ret = PTR_ERR(phy_provider);
goto clk_err;
}
return 0;
clk_err:
of_clk_del_provider(node);
return ret;
}
static int serdes_am654_remove(struct platform_device *pdev)
{
struct serdes_am654 *am654_phy = platform_get_drvdata(pdev);
struct device_node *node = am654_phy->of_node;
pm_runtime_disable(&pdev->dev);
of_clk_del_provider(node);
return 0;
}
static struct platform_driver serdes_am654_driver = {
.probe = serdes_am654_probe,
.remove = serdes_am654_remove,
.driver = {
.name = "phy-am654",
.of_match_table = serdes_am654_id_table,
},
};
module_platform_driver(serdes_am654_driver);
MODULE_AUTHOR("Texas Instruments Inc.");
MODULE_DESCRIPTION("TI AM654x SERDES driver");
MODULE_LICENSE("GPL v2");

View File

@ -56,51 +56,73 @@
#define SATA_PLL_SOFT_RESET BIT(18)
#define PIPE3_PHY_PWRCTL_CLK_CMD_MASK 0x003FC000
#define PIPE3_PHY_PWRCTL_CLK_CMD_MASK GENMASK(21, 14)
#define PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT 14
#define PIPE3_PHY_PWRCTL_CLK_FREQ_MASK 0xFFC00000
#define PIPE3_PHY_PWRCTL_CLK_FREQ_MASK GENMASK(31, 22)
#define PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT 22
#define PIPE3_PHY_TX_RX_POWERON 0x3
#define PIPE3_PHY_TX_RX_POWEROFF 0x0
#define PIPE3_PHY_RX_POWERON (0x1 << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT)
#define PIPE3_PHY_TX_POWERON (0x2 << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT)
#define PCIE_PCS_MASK 0xFF0000
#define PCIE_PCS_DELAY_COUNT_SHIFT 0x10
#define PCIEPHYRX_ANA_PROGRAMMABILITY 0x0000000C
#define PIPE3_PHY_RX_ANA_PROGRAMMABILITY 0x0000000C
#define INTERFACE_MASK GENMASK(31, 27)
#define INTERFACE_SHIFT 27
#define INTERFACE_MODE_USBSS BIT(4)
#define INTERFACE_MODE_SATA_1P5 BIT(3)
#define INTERFACE_MODE_SATA_3P0 BIT(2)
#define INTERFACE_MODE_PCIE BIT(0)
#define LOSD_MASK GENMASK(17, 14)
#define LOSD_SHIFT 14
#define MEM_PLLDIV GENMASK(6, 5)
#define PCIEPHYRX_TRIM 0x0000001C
#define MEM_DLL_TRIM_SEL GENMASK(31, 30)
#define PIPE3_PHY_RX_TRIM 0x0000001C
#define MEM_DLL_TRIM_SEL_MASK GENMASK(31, 30)
#define MEM_DLL_TRIM_SHIFT 30
#define PCIEPHYRX_DLL 0x00000024
#define MEM_DLL_PHINT_RATE GENMASK(31, 30)
#define PIPE3_PHY_RX_DLL 0x00000024
#define MEM_DLL_PHINT_RATE_MASK GENMASK(31, 30)
#define MEM_DLL_PHINT_RATE_SHIFT 30
#define PCIEPHYRX_DIGITAL_MODES 0x00000028
#define PIPE3_PHY_RX_DIGITAL_MODES 0x00000028
#define MEM_HS_RATE_MASK GENMASK(28, 27)
#define MEM_HS_RATE_SHIFT 27
#define MEM_OVRD_HS_RATE BIT(26)
#define MEM_OVRD_HS_RATE_SHIFT 26
#define MEM_CDR_FASTLOCK BIT(23)
#define MEM_CDR_LBW GENMASK(22, 21)
#define MEM_CDR_STEPCNT GENMASK(20, 19)
#define MEM_CDR_FASTLOCK_SHIFT 23
#define MEM_CDR_LBW_MASK GENMASK(22, 21)
#define MEM_CDR_LBW_SHIFT 21
#define MEM_CDR_STEPCNT_MASK GENMASK(20, 19)
#define MEM_CDR_STEPCNT_SHIFT 19
#define MEM_CDR_STL_MASK GENMASK(18, 16)
#define MEM_CDR_STL_SHIFT 16
#define MEM_CDR_THR_MASK GENMASK(15, 13)
#define MEM_CDR_THR_SHIFT 13
#define MEM_CDR_THR_MODE BIT(12)
#define MEM_CDR_CDR_2NDO_SDM_MODE BIT(11)
#define MEM_OVRD_HS_RATE BIT(26)
#define MEM_CDR_THR_MODE_SHIFT 12
#define MEM_CDR_2NDO_SDM_MODE BIT(11)
#define MEM_CDR_2NDO_SDM_MODE_SHIFT 11
#define PCIEPHYRX_EQUALIZER 0x00000038
#define MEM_EQLEV GENMASK(31, 16)
#define MEM_EQFTC GENMASK(15, 11)
#define MEM_EQCTL GENMASK(10, 7)
#define PIPE3_PHY_RX_EQUALIZER 0x00000038
#define MEM_EQLEV_MASK GENMASK(31, 16)
#define MEM_EQLEV_SHIFT 16
#define MEM_EQFTC_MASK GENMASK(15, 11)
#define MEM_EQFTC_SHIFT 11
#define MEM_EQCTL_MASK GENMASK(10, 7)
#define MEM_EQCTL_SHIFT 7
#define MEM_OVRD_EQLEV BIT(2)
#define MEM_OVRD_EQLEV_SHIFT 2
#define MEM_OVRD_EQFTC BIT(1)
#define MEM_OVRD_EQFTC_SHIFT 1
#define SATA_PHY_RX_IO_AND_A2D_OVERRIDES 0x44
#define MEM_CDR_LOS_SOURCE_MASK GENMASK(10, 9)
#define MEM_CDR_LOS_SOURCE_SHIFT 9
/*
* This is an Empirical value that works, need to confirm the actual
@ -110,6 +132,10 @@
#define PLL_IDLE_TIME 100 /* in milliseconds */
#define PLL_LOCK_TIME 100 /* in milliseconds */
enum pipe3_mode { PIPE3_MODE_PCIE = 1,
PIPE3_MODE_SATA,
PIPE3_MODE_USBSS };
struct pipe3_dpll_params {
u16 m;
u8 n;
@ -123,6 +149,27 @@ struct pipe3_dpll_map {
struct pipe3_dpll_params params;
};
struct pipe3_settings {
u8 ana_interface;
u8 ana_losd;
u8 dig_fastlock;
u8 dig_lbw;
u8 dig_stepcnt;
u8 dig_stl;
u8 dig_thr;
u8 dig_thr_mode;
u8 dig_2ndo_sdm_mode;
u8 dig_hs_rate;
u8 dig_ovrd_hs_rate;
u8 dll_trim_sel;
u8 dll_phint_rate;
u8 eq_lev;
u8 eq_ftc;
u8 eq_ctl;
u8 eq_ovrd_lev;
u8 eq_ovrd_ftc;
};
struct ti_pipe3 {
void __iomem *pll_ctrl_base;
void __iomem *phy_rx;
@ -141,6 +188,8 @@ struct ti_pipe3 {
unsigned int power_reg; /* power reg. index within syscon */
unsigned int pcie_pcs_reg; /* pcs reg. index in syscon */
bool sata_refclk_enabled;
enum pipe3_mode mode;
struct pipe3_settings settings;
};
static struct pipe3_dpll_map dpll_map_usb[] = {
@ -163,6 +212,89 @@ static struct pipe3_dpll_map dpll_map_sata[] = {
{ }, /* Terminator */
};
struct pipe3_data {
enum pipe3_mode mode;
struct pipe3_dpll_map *dpll_map;
struct pipe3_settings settings;
};
static struct pipe3_data data_usb = {
.mode = PIPE3_MODE_USBSS,
.dpll_map = dpll_map_usb,
.settings = {
/* DRA75x TRM Table 26-17 Preferred USB3_PHY_RX SCP Register Settings */
.ana_interface = INTERFACE_MODE_USBSS,
.ana_losd = 0xa,
.dig_fastlock = 1,
.dig_lbw = 3,
.dig_stepcnt = 0,
.dig_stl = 0x3,
.dig_thr = 1,
.dig_thr_mode = 1,
.dig_2ndo_sdm_mode = 0,
.dig_hs_rate = 0,
.dig_ovrd_hs_rate = 1,
.dll_trim_sel = 0x2,
.dll_phint_rate = 0x3,
.eq_lev = 0,
.eq_ftc = 0,
.eq_ctl = 0x9,
.eq_ovrd_lev = 0,
.eq_ovrd_ftc = 0,
},
};
static struct pipe3_data data_sata = {
.mode = PIPE3_MODE_SATA,
.dpll_map = dpll_map_sata,
.settings = {
/* DRA75x TRM Table 26-9 Preferred SATA_PHY_RX SCP Register Settings */
.ana_interface = INTERFACE_MODE_SATA_3P0,
.ana_losd = 0x5,
.dig_fastlock = 1,
.dig_lbw = 3,
.dig_stepcnt = 0,
.dig_stl = 0x3,
.dig_thr = 1,
.dig_thr_mode = 1,
.dig_2ndo_sdm_mode = 0,
.dig_hs_rate = 0, /* Not in TRM preferred settings */
.dig_ovrd_hs_rate = 0, /* Not in TRM preferred settings */
.dll_trim_sel = 0x1,
.dll_phint_rate = 0x2, /* for 1.5 GHz DPLL clock */
.eq_lev = 0,
.eq_ftc = 0x1f,
.eq_ctl = 0,
.eq_ovrd_lev = 1,
.eq_ovrd_ftc = 1,
},
};
static struct pipe3_data data_pcie = {
.mode = PIPE3_MODE_PCIE,
.settings = {
/* DRA75x TRM Table 26-62 Preferred PCIe_PHY_RX SCP Register Settings */
.ana_interface = INTERFACE_MODE_PCIE,
.ana_losd = 0xa,
.dig_fastlock = 1,
.dig_lbw = 3,
.dig_stepcnt = 0,
.dig_stl = 0x3,
.dig_thr = 1,
.dig_thr_mode = 1,
.dig_2ndo_sdm_mode = 0,
.dig_hs_rate = 0,
.dig_ovrd_hs_rate = 0,
.dll_trim_sel = 0x2,
.dll_phint_rate = 0x3,
.eq_lev = 0,
.eq_ftc = 0x1f,
.eq_ctl = 1,
.eq_ovrd_lev = 0,
.eq_ovrd_ftc = 0,
},
};
static inline u32 ti_pipe3_readl(void __iomem *addr, unsigned offset)
{
return __raw_readl(addr + offset);
@ -196,7 +328,6 @@ static void ti_pipe3_disable_clocks(struct ti_pipe3 *phy);
static int ti_pipe3_power_off(struct phy *x)
{
u32 val;
int ret;
struct ti_pipe3 *phy = phy_get_drvdata(x);
@ -205,13 +336,13 @@ static int ti_pipe3_power_off(struct phy *x)
return 0;
}
val = PIPE3_PHY_TX_RX_POWEROFF << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT;
ret = regmap_update_bits(phy->phy_power_syscon, phy->power_reg,
PIPE3_PHY_PWRCTL_CLK_CMD_MASK, val);
PIPE3_PHY_PWRCTL_CLK_CMD_MASK, 0);
return ret;
}
static void ti_pipe3_calibrate(struct ti_pipe3 *phy);
static int ti_pipe3_power_on(struct phy *x)
{
u32 val;
@ -219,6 +350,7 @@ static int ti_pipe3_power_on(struct phy *x)
int ret;
unsigned long rate;
struct ti_pipe3 *phy = phy_get_drvdata(x);
bool rx_pending = false;
if (!phy->phy_power_syscon) {
omap_control_phy_power(phy->control_dev, 1);
@ -231,14 +363,35 @@ static int ti_pipe3_power_on(struct phy *x)
return -EINVAL;
}
rate = rate / 1000000;
mask = OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK |
OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_MASK;
val = PIPE3_PHY_TX_RX_POWERON << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT;
val |= rate << OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT;
mask = OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_MASK;
val = rate << OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT;
ret = regmap_update_bits(phy->phy_power_syscon, phy->power_reg,
mask, val);
return ret;
/*
* For PCIe, TX and RX must be powered on simultaneously.
* For USB and SATA, TX must be powered on before RX
*/
mask = OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK;
if (phy->mode == PIPE3_MODE_SATA || phy->mode == PIPE3_MODE_USBSS) {
val = PIPE3_PHY_TX_POWERON;
rx_pending = true;
} else {
val = PIPE3_PHY_TX_POWERON | PIPE3_PHY_RX_POWERON;
}
regmap_update_bits(phy->phy_power_syscon, phy->power_reg,
mask, val);
if (rx_pending) {
val = PIPE3_PHY_TX_POWERON | PIPE3_PHY_RX_POWERON;
regmap_update_bits(phy->phy_power_syscon, phy->power_reg,
mask, val);
}
if (phy->mode == PIPE3_MODE_PCIE)
ti_pipe3_calibrate(phy);
return 0;
}
static int ti_pipe3_dpll_wait_lock(struct ti_pipe3 *phy)
@ -300,32 +453,55 @@ static int ti_pipe3_dpll_program(struct ti_pipe3 *phy)
static void ti_pipe3_calibrate(struct ti_pipe3 *phy)
{
u32 val;
struct pipe3_settings *s = &phy->settings;
val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_ANA_PROGRAMMABILITY);
val = ti_pipe3_readl(phy->phy_rx, PIPE3_PHY_RX_ANA_PROGRAMMABILITY);
val &= ~(INTERFACE_MASK | LOSD_MASK | MEM_PLLDIV);
val = (0x1 << INTERFACE_SHIFT | 0xA << LOSD_SHIFT);
ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_ANA_PROGRAMMABILITY, val);
val |= (s->ana_interface << INTERFACE_SHIFT | s->ana_losd << LOSD_SHIFT);
ti_pipe3_writel(phy->phy_rx, PIPE3_PHY_RX_ANA_PROGRAMMABILITY, val);
val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_DIGITAL_MODES);
val &= ~(MEM_CDR_STEPCNT | MEM_CDR_STL_MASK | MEM_CDR_THR_MASK |
MEM_CDR_CDR_2NDO_SDM_MODE | MEM_OVRD_HS_RATE);
val |= (MEM_CDR_FASTLOCK | MEM_CDR_LBW | 0x3 << MEM_CDR_STL_SHIFT |
0x1 << MEM_CDR_THR_SHIFT | MEM_CDR_THR_MODE);
ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_DIGITAL_MODES, val);
val = ti_pipe3_readl(phy->phy_rx, PIPE3_PHY_RX_DIGITAL_MODES);
val &= ~(MEM_HS_RATE_MASK | MEM_OVRD_HS_RATE | MEM_CDR_FASTLOCK |
MEM_CDR_LBW_MASK | MEM_CDR_STEPCNT_MASK | MEM_CDR_STL_MASK |
MEM_CDR_THR_MASK | MEM_CDR_THR_MODE | MEM_CDR_2NDO_SDM_MODE);
val |= s->dig_hs_rate << MEM_HS_RATE_SHIFT |
s->dig_ovrd_hs_rate << MEM_OVRD_HS_RATE_SHIFT |
s->dig_fastlock << MEM_CDR_FASTLOCK_SHIFT |
s->dig_lbw << MEM_CDR_LBW_SHIFT |
s->dig_stepcnt << MEM_CDR_STEPCNT_SHIFT |
s->dig_stl << MEM_CDR_STL_SHIFT |
s->dig_thr << MEM_CDR_THR_SHIFT |
s->dig_thr_mode << MEM_CDR_THR_MODE_SHIFT |
s->dig_2ndo_sdm_mode << MEM_CDR_2NDO_SDM_MODE_SHIFT;
ti_pipe3_writel(phy->phy_rx, PIPE3_PHY_RX_DIGITAL_MODES, val);
val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_TRIM);
val &= ~MEM_DLL_TRIM_SEL;
val |= 0x2 << MEM_DLL_TRIM_SHIFT;
ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_TRIM, val);
val = ti_pipe3_readl(phy->phy_rx, PIPE3_PHY_RX_TRIM);
val &= ~MEM_DLL_TRIM_SEL_MASK;
val |= s->dll_trim_sel << MEM_DLL_TRIM_SHIFT;
ti_pipe3_writel(phy->phy_rx, PIPE3_PHY_RX_TRIM, val);
val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_DLL);
val |= MEM_DLL_PHINT_RATE;
ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_DLL, val);
val = ti_pipe3_readl(phy->phy_rx, PIPE3_PHY_RX_DLL);
val &= ~MEM_DLL_PHINT_RATE_MASK;
val |= s->dll_phint_rate << MEM_DLL_PHINT_RATE_SHIFT;
ti_pipe3_writel(phy->phy_rx, PIPE3_PHY_RX_DLL, val);
val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_EQUALIZER);
val &= ~(MEM_EQLEV | MEM_EQCTL | MEM_OVRD_EQLEV | MEM_OVRD_EQFTC);
val |= MEM_EQFTC | 0x1 << MEM_EQCTL_SHIFT;
ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_EQUALIZER, val);
val = ti_pipe3_readl(phy->phy_rx, PIPE3_PHY_RX_EQUALIZER);
val &= ~(MEM_EQLEV_MASK | MEM_EQFTC_MASK | MEM_EQCTL_MASK |
MEM_OVRD_EQLEV | MEM_OVRD_EQFTC);
val |= s->eq_lev << MEM_EQLEV_SHIFT |
s->eq_ftc << MEM_EQFTC_SHIFT |
s->eq_ctl << MEM_EQCTL_SHIFT |
s->eq_ovrd_lev << MEM_OVRD_EQLEV_SHIFT |
s->eq_ovrd_ftc << MEM_OVRD_EQFTC_SHIFT;
ti_pipe3_writel(phy->phy_rx, PIPE3_PHY_RX_EQUALIZER, val);
if (phy->mode == PIPE3_MODE_SATA) {
val = ti_pipe3_readl(phy->phy_rx,
SATA_PHY_RX_IO_AND_A2D_OVERRIDES);
val &= ~MEM_CDR_LOS_SOURCE_MASK;
ti_pipe3_writel(phy->phy_rx, SATA_PHY_RX_IO_AND_A2D_OVERRIDES,
val);
}
}
static int ti_pipe3_init(struct phy *x)
@ -340,7 +516,7 @@ static int ti_pipe3_init(struct phy *x)
* as recommended in AM572x TRM SPRUHZ6, section 18.5.2.2, table
* 18-1804.
*/
if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-pcie")) {
if (phy->mode == PIPE3_MODE_PCIE) {
if (!phy->pcs_syscon) {
omap_control_pcie_pcs(phy->control_dev, 0x96);
return 0;
@ -349,12 +525,7 @@ static int ti_pipe3_init(struct phy *x)
val = 0x96 << OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT;
ret = regmap_update_bits(phy->pcs_syscon, phy->pcie_pcs_reg,
PCIE_PCS_MASK, val);
if (ret)
return ret;
ti_pipe3_calibrate(phy);
return 0;
return ret;
}
/* Bring it out of IDLE if it is IDLE */
@ -367,8 +538,7 @@ static int ti_pipe3_init(struct phy *x)
/* SATA has issues if re-programmed when locked */
val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS);
if ((val & PLL_LOCK) && of_device_is_compatible(phy->dev->of_node,
"ti,phy-pipe3-sata"))
if ((val & PLL_LOCK) && phy->mode == PIPE3_MODE_SATA)
return ret;
/* Program the DPLL */
@ -378,6 +548,8 @@ static int ti_pipe3_init(struct phy *x)
return -EINVAL;
}
ti_pipe3_calibrate(phy);
return ret;
}
@ -390,12 +562,11 @@ static int ti_pipe3_exit(struct phy *x)
/* If dpll_reset_syscon is not present we wont power down SATA DPLL
* due to Errata i783
*/
if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-sata") &&
!phy->dpll_reset_syscon)
if (phy->mode == PIPE3_MODE_SATA && !phy->dpll_reset_syscon)
return 0;
/* PCIe doesn't have internal DPLL */
if (!of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-pcie")) {
if (phy->mode != PIPE3_MODE_PCIE) {
/* Put DPLL in IDLE mode */
val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2);
val |= PLL_IDLE;
@ -418,7 +589,7 @@ static int ti_pipe3_exit(struct phy *x)
}
/* i783: SATA needs control bit toggle after PLL unlock */
if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-sata")) {
if (phy->mode == PIPE3_MODE_SATA) {
regmap_update_bits(phy->dpll_reset_syscon, phy->dpll_reset_reg,
SATA_PLL_SOFT_RESET, SATA_PLL_SOFT_RESET);
regmap_update_bits(phy->dpll_reset_syscon, phy->dpll_reset_reg,
@ -443,7 +614,6 @@ static int ti_pipe3_get_clk(struct ti_pipe3 *phy)
{
struct clk *clk;
struct device *dev = phy->dev;
struct device_node *node = dev->of_node;
phy->refclk = devm_clk_get(dev, "refclk");
if (IS_ERR(phy->refclk)) {
@ -451,11 +621,11 @@ static int ti_pipe3_get_clk(struct ti_pipe3 *phy)
/* older DTBs have missing refclk in SATA PHY
* so don't bail out in case of SATA PHY.
*/
if (!of_device_is_compatible(node, "ti,phy-pipe3-sata"))
if (phy->mode != PIPE3_MODE_SATA)
return PTR_ERR(phy->refclk);
}
if (!of_device_is_compatible(node, "ti,phy-pipe3-sata")) {
if (phy->mode != PIPE3_MODE_SATA) {
phy->wkupclk = devm_clk_get(dev, "wkupclk");
if (IS_ERR(phy->wkupclk)) {
dev_err(dev, "unable to get wkupclk\n");
@ -465,8 +635,7 @@ static int ti_pipe3_get_clk(struct ti_pipe3 *phy)
phy->wkupclk = ERR_PTR(-ENODEV);
}
if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie") ||
phy->phy_power_syscon) {
if (phy->mode != PIPE3_MODE_PCIE || phy->phy_power_syscon) {
phy->sys_clk = devm_clk_get(dev, "sysclk");
if (IS_ERR(phy->sys_clk)) {
dev_err(dev, "unable to get sysclk\n");
@ -474,7 +643,7 @@ static int ti_pipe3_get_clk(struct ti_pipe3 *phy)
}
}
if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) {
if (phy->mode == PIPE3_MODE_PCIE) {
clk = devm_clk_get(dev, "dpll_ref");
if (IS_ERR(clk)) {
dev_err(dev, "unable to get dpll ref clk\n");
@ -546,7 +715,7 @@ static int ti_pipe3_get_sysctrl(struct ti_pipe3 *phy)
phy->control_dev = &control_pdev->dev;
}
if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) {
if (phy->mode == PIPE3_MODE_PCIE) {
phy->pcs_syscon = syscon_regmap_lookup_by_phandle(node,
"syscon-pcs");
if (IS_ERR(phy->pcs_syscon)) {
@ -564,7 +733,7 @@ static int ti_pipe3_get_sysctrl(struct ti_pipe3 *phy)
}
}
if (of_device_is_compatible(node, "ti,phy-pipe3-sata")) {
if (phy->mode == PIPE3_MODE_SATA) {
phy->dpll_reset_syscon = syscon_regmap_lookup_by_phandle(node,
"syscon-pllreset");
if (IS_ERR(phy->dpll_reset_syscon)) {
@ -589,12 +758,8 @@ static int ti_pipe3_get_tx_rx_base(struct ti_pipe3 *phy)
{
struct resource *res;
struct device *dev = phy->dev;
struct device_node *node = dev->of_node;
struct platform_device *pdev = to_platform_device(dev);
if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie"))
return 0;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"phy_rx");
phy->phy_rx = devm_ioremap_resource(dev, res);
@ -611,24 +776,12 @@ static int ti_pipe3_get_tx_rx_base(struct ti_pipe3 *phy)
static int ti_pipe3_get_pll_base(struct ti_pipe3 *phy)
{
struct resource *res;
const struct of_device_id *match;
struct device *dev = phy->dev;
struct device_node *node = dev->of_node;
struct platform_device *pdev = to_platform_device(dev);
if (of_device_is_compatible(node, "ti,phy-pipe3-pcie"))
if (phy->mode == PIPE3_MODE_PCIE)
return 0;
match = of_match_device(ti_pipe3_id_table, dev);
if (!match)
return -EINVAL;
phy->dpll_map = (struct pipe3_dpll_map *)match->data;
if (!phy->dpll_map) {
dev_err(dev, "no DPLL data\n");
return -EINVAL;
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"pll_ctrl");
phy->pll_ctrl_base = devm_ioremap_resource(dev, res);
@ -640,15 +793,29 @@ static int ti_pipe3_probe(struct platform_device *pdev)
struct ti_pipe3 *phy;
struct phy *generic_phy;
struct phy_provider *phy_provider;
struct device_node *node = pdev->dev.of_node;
struct device *dev = &pdev->dev;
int ret;
const struct of_device_id *match;
struct pipe3_data *data;
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
if (!phy)
return -ENOMEM;
phy->dev = dev;
match = of_match_device(ti_pipe3_id_table, dev);
if (!match)
return -EINVAL;
data = (struct pipe3_data *)match->data;
if (!data) {
dev_err(dev, "no driver data\n");
return -EINVAL;
}
phy->dev = dev;
phy->mode = data->mode;
phy->dpll_map = data->dpll_map;
phy->settings = data->settings;
ret = ti_pipe3_get_pll_base(phy);
if (ret)
@ -672,7 +839,7 @@ static int ti_pipe3_probe(struct platform_device *pdev)
/*
* Prevent auto-disable of refclk for SATA PHY due to Errata i783
*/
if (of_device_is_compatible(node, "ti,phy-pipe3-sata")) {
if (phy->mode == PIPE3_MODE_SATA) {
if (!IS_ERR(phy->refclk)) {
clk_prepare_enable(phy->refclk);
phy->sata_refclk_enabled = true;
@ -762,18 +929,19 @@ static void ti_pipe3_disable_clocks(struct ti_pipe3 *phy)
static const struct of_device_id ti_pipe3_id_table[] = {
{
.compatible = "ti,phy-usb3",
.data = dpll_map_usb,
.data = &data_usb,
},
{
.compatible = "ti,omap-usb3",
.data = dpll_map_usb,
.data = &data_usb,
},
{
.compatible = "ti,phy-pipe3-sata",
.data = dpll_map_sata,
.data = &data_sata,
},
{
.compatible = "ti,phy-pipe3-pcie",
.data = &data_pcie,
},
{}
};

View File

@ -99,6 +99,7 @@ config SCSI_UFS_DWC_TC_PLATFORM
config SCSI_UFS_QCOM
tristate "QCOM specific hooks to UFS controller platform driver"
depends on SCSI_UFSHCD_PLATFORM && ARCH_QCOM
select RESET_CONTROLLER
help
This selects the QCOM specific additions to UFSHCD platform driver.
UFS host on QCOM needs some vendor specific configuration before

View File

@ -16,6 +16,7 @@
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
#include <linux/reset-controller.h>
#include "ufshcd.h"
#include "ufshcd-pltfrm.h"
@ -49,6 +50,11 @@ static void ufs_qcom_get_default_testbus_cfg(struct ufs_qcom_host *host);
static int ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(struct ufs_hba *hba,
u32 clk_cycles);
static struct ufs_qcom_host *rcdev_to_ufs_host(struct reset_controller_dev *rcd)
{
return container_of(rcd, struct ufs_qcom_host, rcdev);
}
static void ufs_qcom_dump_regs_wrapper(struct ufs_hba *hba, int offset, int len,
const char *prefix, void *priv)
{
@ -255,11 +261,6 @@ static int ufs_qcom_power_up_sequence(struct ufs_hba *hba)
if (is_rate_B)
phy_set_mode(phy, PHY_MODE_UFS_HS_B);
/* Assert PHY reset and apply PHY calibration values */
ufs_qcom_assert_reset(hba);
/* provide 1ms delay to let the reset pulse propagate */
usleep_range(1000, 1100);
/* phy initialization - calibrate the phy */
ret = phy_init(phy);
if (ret) {
@ -268,15 +269,6 @@ static int ufs_qcom_power_up_sequence(struct ufs_hba *hba)
goto out;
}
/* De-assert PHY reset and start serdes */
ufs_qcom_deassert_reset(hba);
/*
* after reset deassertion, phy will need all ref clocks,
* voltage, current to settle down before starting serdes.
*/
usleep_range(1000, 1100);
/* power on phy - start serdes and phy's power and clocks */
ret = phy_power_on(phy);
if (ret) {
@ -290,7 +282,6 @@ static int ufs_qcom_power_up_sequence(struct ufs_hba *hba)
return 0;
out_disable_phy:
ufs_qcom_assert_reset(hba);
phy_exit(phy);
out:
return ret;
@ -554,21 +545,10 @@ static int ufs_qcom_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
ufs_qcom_disable_lane_clks(host);
phy_power_off(phy);
/* Assert PHY soft reset */
ufs_qcom_assert_reset(hba);
goto out;
}
/*
* If UniPro link is not active, PHY ref_clk, main PHY analog power
* rail and low noise analog power rail for PLL can be switched off.
*/
if (!ufs_qcom_is_link_active(hba)) {
} else if (!ufs_qcom_is_link_active(hba)) {
ufs_qcom_disable_lane_clks(host);
phy_power_off(phy);
}
out:
return ret;
}
@ -578,21 +558,26 @@ static int ufs_qcom_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
struct phy *phy = host->generic_phy;
int err;
err = phy_power_on(phy);
if (err) {
dev_err(hba->dev, "%s: failed enabling regs, err = %d\n",
__func__, err);
goto out;
if (ufs_qcom_is_link_off(hba)) {
err = phy_power_on(phy);
if (err) {
dev_err(hba->dev, "%s: failed PHY power on: %d\n",
__func__, err);
return err;
}
err = ufs_qcom_enable_lane_clks(host);
if (err)
return err;
} else if (!ufs_qcom_is_link_active(hba)) {
err = ufs_qcom_enable_lane_clks(host);
if (err)
return err;
}
err = ufs_qcom_enable_lane_clks(host);
if (err)
goto out;
hba->is_sys_suspended = false;
out:
return err;
return 0;
}
struct ufs_qcom_dev_params {
@ -1118,8 +1103,6 @@ static int ufs_qcom_setup_clocks(struct ufs_hba *hba, bool on,
return 0;
if (on && (status == POST_CHANGE)) {
phy_power_on(host->generic_phy);
/* enable the device ref clock for HS mode*/
if (ufshcd_is_hs_mode(&hba->pwr_info))
ufs_qcom_dev_ref_clk_ctrl(host, true);
@ -1131,9 +1114,6 @@ static int ufs_qcom_setup_clocks(struct ufs_hba *hba, bool on,
if (!ufs_qcom_is_link_active(hba)) {
/* disable device ref_clk */
ufs_qcom_dev_ref_clk_ctrl(host, false);
/* powering off PHY during aggressive clk gating */
phy_power_off(host->generic_phy);
}
vote = host->bus_vote.min_bw_vote;
@ -1147,6 +1127,41 @@ static int ufs_qcom_setup_clocks(struct ufs_hba *hba, bool on,
return err;
}
static int
ufs_qcom_reset_assert(struct reset_controller_dev *rcdev, unsigned long id)
{
struct ufs_qcom_host *host = rcdev_to_ufs_host(rcdev);
/* Currently this code only knows about a single reset. */
WARN_ON(id);
ufs_qcom_assert_reset(host->hba);
/* provide 1ms delay to let the reset pulse propagate. */
usleep_range(1000, 1100);
return 0;
}
static int
ufs_qcom_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id)
{
struct ufs_qcom_host *host = rcdev_to_ufs_host(rcdev);
/* Currently this code only knows about a single reset. */
WARN_ON(id);
ufs_qcom_deassert_reset(host->hba);
/*
* after reset deassertion, phy will need all ref clocks,
* voltage, current to settle down before starting serdes.
*/
usleep_range(1000, 1100);
return 0;
}
static const struct reset_control_ops ufs_qcom_reset_ops = {
.assert = ufs_qcom_reset_assert,
.deassert = ufs_qcom_reset_deassert,
};
#define ANDROID_BOOT_DEV_MAX 30
static char android_boot_dev[ANDROID_BOOT_DEV_MAX];
@ -1191,6 +1206,17 @@ static int ufs_qcom_init(struct ufs_hba *hba)
host->hba = hba;
ufshcd_set_variant(hba, host);
/* Fire up the reset controller. Failure here is non-fatal. */
host->rcdev.of_node = dev->of_node;
host->rcdev.ops = &ufs_qcom_reset_ops;
host->rcdev.owner = dev->driver->owner;
host->rcdev.nr_resets = 1;
err = devm_reset_controller_register(dev, &host->rcdev);
if (err) {
dev_warn(dev, "Failed to register reset controller\n");
err = 0;
}
/*
* voting/devoting device ref_clk source is time consuming hence
* skip devoting it during aggressive clock gating. This clock

View File

@ -14,6 +14,8 @@
#ifndef UFS_QCOM_H_
#define UFS_QCOM_H_
#include <linux/reset-controller.h>
#define MAX_UFS_QCOM_HOSTS 1
#define MAX_U32 (~(u32)0)
#define MPHY_TX_FSM_STATE 0x41
@ -237,6 +239,8 @@ struct ufs_qcom_host {
/* Bitmask for enabling debug prints */
u32 dbg_print_en;
struct ufs_qcom_testbus testbus;
struct reset_controller_dev rcdev;
};
static inline u32

View File

@ -0,0 +1,13 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* This header provides constants for AM654 SERDES.
*/
#ifndef _DT_BINDINGS_AM654_SERDES
#define _DT_BINDINGS_AM654_SERDES
#define AM654_SERDES_CMU_REFCLK 0
#define AM654_SERDES_LO_REFCLK 1
#define AM654_SERDES_RO_REFCLK 2
#endif /* _DT_BINDINGS_AM654_SERDES */

View File

@ -64,6 +64,7 @@ union phy_configure_opts {
* @set_mode: set the mode of the phy
* @reset: resetting the phy
* @calibrate: calibrate the phy
* @release: ops to be performed while the consumer relinquishes the PHY
* @owner: the module owner containing the ops
*/
struct phy_ops {
@ -105,6 +106,7 @@ struct phy_ops {
union phy_configure_opts *opts);
int (*reset)(struct phy *phy);
int (*calibrate)(struct phy *phy);
void (*release)(struct phy *phy);
struct module *owner;
};