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:
commit
d30e413fa4
|
@ -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>;
|
||||||
|
};
|
|
@ -7,6 +7,9 @@ Required properties:
|
||||||
- clocks: phandles to the clocks for each clock listed in clock-names
|
- clocks: phandles to the clocks for each clock listed in clock-names
|
||||||
- clock-names: must contain "phy"
|
- clock-names: must contain "phy"
|
||||||
|
|
||||||
|
Optional properties:
|
||||||
|
- vbus-supply: A phandle to the regulator for USB VBUS.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
usb3_phy0: phy@381f0040 {
|
usb3_phy0: phy@381f0040 {
|
||||||
compatible = "fsl,imx8mq-usb-phy";
|
compatible = "fsl,imx8mq-usb-phy";
|
||||||
|
|
|
@ -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>;
|
||||||
|
};
|
|
@ -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>;
|
||||||
|
};
|
|
@ -36,11 +36,20 @@ Required properties:
|
||||||
- Tegra124: "nvidia,tegra124-xusb-padctl"
|
- Tegra124: "nvidia,tegra124-xusb-padctl"
|
||||||
- Tegra132: "nvidia,tegra132-xusb-padctl", "nvidia,tegra124-xusb-padctl"
|
- Tegra132: "nvidia,tegra132-xusb-padctl", "nvidia,tegra124-xusb-padctl"
|
||||||
- Tegra210: "nvidia,tegra210-xusb-padctl"
|
- Tegra210: "nvidia,tegra210-xusb-padctl"
|
||||||
|
- Tegra186: "nvidia,tegra186-xusb-padctl"
|
||||||
- reg: Physical base address and length of the controller's registers.
|
- reg: Physical base address and length of the controller's registers.
|
||||||
- resets: Must contain an entry for each entry in reset-names.
|
- resets: Must contain an entry for each entry in reset-names.
|
||||||
- reset-names: Must include the following entries:
|
- reset-names: Must include the following entries:
|
||||||
- "padctl"
|
- "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:
|
Pad nodes:
|
||||||
==========
|
==========
|
||||||
|
|
|
@ -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>;
|
||||||
|
};
|
||||||
|
};
|
|
@ -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>;
|
||||||
|
};
|
|
@ -11,6 +11,7 @@ Required properties:
|
||||||
"qcom,msm8996-qmp-usb3-phy" for 14nm USB3 phy on msm8996,
|
"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-usb3-phy" for USB3 QMP V3 phy on msm8998,
|
||||||
"qcom,msm8998-qmp-ufs-phy" for UFS QMP 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-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-usb3-uni-phy" for USB3 QMP V3 UNI phy on sdm845,
|
||||||
"qcom,sdm845-qmp-ufs-phy" for UFS QMP phy on sdm845.
|
"qcom,sdm845-qmp-ufs-phy" for UFS QMP phy on sdm845.
|
||||||
|
@ -48,6 +49,8 @@ Required properties:
|
||||||
"aux", "cfg_ahb", "ref".
|
"aux", "cfg_ahb", "ref".
|
||||||
For "qcom,msm8998-qmp-ufs-phy" must contain:
|
For "qcom,msm8998-qmp-ufs-phy" must contain:
|
||||||
"ref", "ref_aux".
|
"ref", "ref_aux".
|
||||||
|
For "qcom,msm8998-qmp-pcie-phy" must contain:
|
||||||
|
"aux", "cfg_ahb", "ref".
|
||||||
For "qcom,sdm845-qmp-usb3-phy" must contain:
|
For "qcom,sdm845-qmp-usb3-phy" must contain:
|
||||||
"aux", "cfg_ahb", "ref", "com_aux".
|
"aux", "cfg_ahb", "ref", "com_aux".
|
||||||
For "qcom,sdm845-qmp-usb3-uni-phy" must contain:
|
For "qcom,sdm845-qmp-usb3-uni-phy" must contain:
|
||||||
|
@ -59,7 +62,8 @@ Required properties:
|
||||||
one for each entry in reset-names.
|
one for each entry in reset-names.
|
||||||
- reset-names: "phy" for reset of phy block,
|
- reset-names: "phy" for reset of phy block,
|
||||||
"common" for phy common block reset,
|
"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:
|
For "qcom,ipq8074-qmp-pcie-phy" must contain:
|
||||||
"phy", "common".
|
"phy", "common".
|
||||||
|
@ -69,12 +73,16 @@ Required properties:
|
||||||
"phy", "common".
|
"phy", "common".
|
||||||
For "qcom,msm8998-qmp-usb3-phy" must contain
|
For "qcom,msm8998-qmp-usb3-phy" must contain
|
||||||
"phy", "common".
|
"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:
|
For "qcom,sdm845-qmp-usb3-phy" must contain:
|
||||||
"phy", "common".
|
"phy", "common".
|
||||||
For "qcom,sdm845-qmp-usb3-uni-phy" must contain:
|
For "qcom,sdm845-qmp-usb3-uni-phy" must contain:
|
||||||
"phy", "common".
|
"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-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.
|
- vdda-pll-supply: Phandle to 1.8V regulator supply to PHY refclk pll block.
|
||||||
|
|
|
@ -7,6 +7,7 @@ Required properties:
|
||||||
- compatible: "renesas,usb-phy-r8a7743" if the device is a part of R8A7743 SoC.
|
- 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-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-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-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-r8a7791" if the device is a part of R8A7791 SoC.
|
||||||
"renesas,usb-phy-r8a7794" if the device is a part of R8A7794 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>.
|
- #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 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 | | |
|
|\ Selector | | |
|
||||||
|
@ -41,6 +42,16 @@ the USB channel; see the selector meanings below:
|
||||||
| 2 | PCI EHCI/OHCI | xHCI |
|
| 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):
|
Example (Lager board):
|
||||||
|
|
||||||
usb-phy@e6590100 {
|
usb-phy@e6590100 {
|
||||||
|
@ -48,15 +59,53 @@ Example (Lager board):
|
||||||
reg = <0 0xe6590100 0 0x100>;
|
reg = <0 0xe6590100 0 0x100>;
|
||||||
#address-cells = <1>;
|
#address-cells = <1>;
|
||||||
#size-cells = <0>;
|
#size-cells = <0>;
|
||||||
clocks = <&mstp7_clks R8A7790_CLK_HSUSB>;
|
clocks = <&cpg CPG_MOD 704>;
|
||||||
clock-names = "usbhs";
|
clock-names = "usbhs";
|
||||||
|
power-domains = <&sysc R8A7790_PD_ALWAYS_ON>;
|
||||||
|
resets = <&cpg 704>;
|
||||||
|
|
||||||
usb-channel@0 {
|
usb0: usb-channel@0 {
|
||||||
reg = <0>;
|
reg = <0>;
|
||||||
#phy-cells = <1>;
|
#phy-cells = <1>;
|
||||||
};
|
};
|
||||||
usb-channel@2 {
|
usb2: usb-channel@2 {
|
||||||
reg = <2>;
|
reg = <2>;
|
||||||
#phy-cells = <1>;
|
#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>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
* Renesas R-Car generation 3 USB 2.0 PHY
|
* Renesas R-Car generation 3 USB 2.0 PHY
|
||||||
|
|
||||||
This file provides information on what the device node for the R-Car generation
|
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:
|
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.
|
SoC.
|
||||||
"renesas,usb2-phy-r8a774c0" if the device is a part of an R8A774C0
|
"renesas,usb2-phy-r8a774c0" if the device is a part of an R8A774C0
|
||||||
SoC.
|
SoC.
|
||||||
|
@ -27,7 +29,13 @@ Required properties:
|
||||||
|
|
||||||
- reg: offset and length of the partial USB 2.0 Host register block.
|
- reg: offset and length of the partial USB 2.0 Host register block.
|
||||||
- clocks: clock phandle and specifier pair(s).
|
- 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:
|
Optional properties:
|
||||||
To use a USB channel where USB 2.0 Host and HSUSB (USB 2.0 Peripheral) are
|
To use a USB channel where USB 2.0 Host and HSUSB (USB 2.0 Peripheral) are
|
||||||
|
|
|
@ -7,12 +7,15 @@ Required properties:
|
||||||
- reg: PHY register address offset and length in "general
|
- reg: PHY register address offset and length in "general
|
||||||
register files"
|
register files"
|
||||||
|
|
||||||
Optional clocks using the clock bindings (see ../clock/clock-bindings.txt),
|
Optional properties:
|
||||||
specified by name:
|
|
||||||
- clock-names: Should contain "emmcclk". Although this is listed as optional
|
- clock-names: Should contain "emmcclk". Although this is listed as optional
|
||||||
(because most boards can get basic functionality without having
|
(because most boards can get basic functionality without having
|
||||||
access to it), it is strongly suggested.
|
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.
|
- 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:
|
Example:
|
||||||
|
|
||||||
|
@ -29,6 +32,7 @@ grf: syscon@ff770000 {
|
||||||
reg = <0xf780 0x20>;
|
reg = <0xf780 0x20>;
|
||||||
clocks = <&sdhci>;
|
clocks = <&sdhci>;
|
||||||
clock-names = "emmcclk";
|
clock-names = "emmcclk";
|
||||||
|
drive-impedance-ohm = <50>;
|
||||||
#phy-cells = <0>;
|
#phy-cells = <0>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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";
|
||||||
|
};
|
|
@ -29,6 +29,7 @@ Optional properties:
|
||||||
- vdda-pll-max-microamp : specifies max. load that can be drawn from pll supply
|
- 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-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
|
- 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:
|
Example:
|
||||||
|
|
||||||
|
@ -51,9 +52,11 @@ Example:
|
||||||
<&clock_gcc clk_ufs_phy_ldo>,
|
<&clock_gcc clk_ufs_phy_ldo>,
|
||||||
<&clock_gcc clk_gcc_ufs_tx_cfg_clk>,
|
<&clock_gcc clk_gcc_ufs_tx_cfg_clk>,
|
||||||
<&clock_gcc clk_gcc_ufs_rx_cfg_clk>;
|
<&clock_gcc clk_gcc_ufs_rx_cfg_clk>;
|
||||||
|
resets = <&ufshc 0>;
|
||||||
};
|
};
|
||||||
|
|
||||||
ufshc@fc598000 {
|
ufshc: ufshc@fc598000 {
|
||||||
|
#reset-cells = <1>;
|
||||||
...
|
...
|
||||||
phys = <&ufsphy1>;
|
phys = <&ufsphy1>;
|
||||||
phy-names = "ufsphy";
|
phy-names = "ufsphy";
|
||||||
|
|
|
@ -50,6 +50,8 @@ Optional properties:
|
||||||
-lanes-per-direction : number of lanes available per direction - either 1 or 2.
|
-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
|
Note that it is assume same number of lanes is used both
|
||||||
directions at once. If not specified, default is 2 lanes per direction.
|
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
|
- resets : reset node register
|
||||||
- reset-names : describe reset node register, the "rst" corresponds to reset the whole UFS IP.
|
- reset-names : describe reset node register, the "rst" corresponds to reset the whole UFS IP.
|
||||||
|
|
||||||
|
@ -79,4 +81,5 @@ Example:
|
||||||
reset-names = "rst";
|
reset-names = "rst";
|
||||||
phys = <&ufsphy1>;
|
phys = <&ufsphy1>;
|
||||||
phy-names = "ufsphy";
|
phy-names = "ufsphy";
|
||||||
|
#reset-cells = <1>;
|
||||||
};
|
};
|
||||||
|
|
13
MAINTAINERS
13
MAINTAINERS
|
@ -15313,6 +15313,11 @@ M: Laxman Dewangan <ldewangan@nvidia.com>
|
||||||
S: Supported
|
S: Supported
|
||||||
F: drivers/spi/spi-tegra*
|
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
|
TEHUTI ETHERNET DRIVER
|
||||||
M: Andy Gospodarek <andy@greyhouse.net>
|
M: Andy Gospodarek <andy@greyhouse.net>
|
||||||
L: netdev@vger.kernel.org
|
L: netdev@vger.kernel.org
|
||||||
|
@ -16083,6 +16088,14 @@ L: linux-usb@vger.kernel.org
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: drivers/usb/roles/intel-xhci-usb-role-switch.c
|
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
|
USB ISP116X DRIVER
|
||||||
M: Olav Kongas <ok@artecdesign.ee>
|
M: Olav Kongas <ok@artecdesign.ee>
|
||||||
L: linux-usb@vger.kernel.org
|
L: linux-usb@vger.kernel.org
|
||||||
|
|
|
@ -36,3 +36,25 @@ config PHY_MESON_GXL_USB3
|
||||||
Enable this to support the Meson USB3 PHY and OTG detection
|
Enable this to support the Meson USB3 PHY and OTG detection
|
||||||
IP block found in Meson GXL and GXM SoCs.
|
IP block found in Meson GXL and GXM SoCs.
|
||||||
If unsure, say N.
|
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.
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
obj-$(CONFIG_PHY_MESON8B_USB2) += phy-meson8b-usb2.o
|
obj-$(CONFIG_PHY_MESON8B_USB2) += phy-meson8b-usb2.o
|
||||||
obj-$(CONFIG_PHY_MESON_GXL_USB2) += phy-meson-gxl-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_GXL_USB3) += phy-meson-gxl-usb3.o
|
||||||
|
obj-$(CONFIG_PHY_MESON_G12A_USB3_PCIE) += phy-meson-g12a-usb3-pcie.o
|
||||||
|
|
|
@ -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");
|
|
@ -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");
|
|
@ -261,14 +261,9 @@ static int phy_meson_gxl_usb2_probe(struct platform_device *pdev)
|
||||||
if (IS_ERR(priv->regmap))
|
if (IS_ERR(priv->regmap))
|
||||||
return PTR_ERR(priv->regmap);
|
return PTR_ERR(priv->regmap);
|
||||||
|
|
||||||
priv->clk = devm_clk_get(dev, "phy");
|
priv->clk = devm_clk_get_optional(dev, "phy");
|
||||||
if (IS_ERR(priv->clk)) {
|
if (IS_ERR(priv->clk))
|
||||||
ret = PTR_ERR(priv->clk);
|
return PTR_ERR(priv->clk);
|
||||||
if (ret == -ENOENT)
|
|
||||||
priv->clk = NULL;
|
|
||||||
else
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
priv->reset = devm_reset_control_get_optional_shared(dev, "phy");
|
priv->reset = devm_reset_control_get_optional_shared(dev, "phy");
|
||||||
if (IS_ERR(priv->reset))
|
if (IS_ERR(priv->reset))
|
||||||
|
|
|
@ -10,6 +10,17 @@ config PHY_CYGNUS_PCIE
|
||||||
Enable this to support the Broadcom Cygnus PCIe PHY.
|
Enable this to support the Broadcom Cygnus PCIe PHY.
|
||||||
If unsure, say N.
|
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
|
config BCM_KONA_USB2_PHY
|
||||||
tristate "Broadcom Kona USB2 PHY Driver"
|
tristate "Broadcom Kona USB2 PHY Driver"
|
||||||
depends on HAS_IOMEM
|
depends on HAS_IOMEM
|
||||||
|
|
|
@ -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
|
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_PCIE) += phy-bcm-sr-pcie.o
|
||||||
|
obj-$(CONFIG_PHY_BCM_SR_USB) += phy-bcm-sr-usb.o
|
||||||
|
|
|
@ -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");
|
|
@ -6,6 +6,7 @@
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/phy/phy.h>
|
#include <linux/phy/phy.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/regulator/consumer.h>
|
||||||
|
|
||||||
#define PHY_CTRL0 0x0
|
#define PHY_CTRL0 0x0
|
||||||
#define PHY_CTRL0_REF_SSP_EN BIT(2)
|
#define PHY_CTRL0_REF_SSP_EN BIT(2)
|
||||||
|
@ -24,6 +25,7 @@ struct imx8mq_usb_phy {
|
||||||
struct phy *phy;
|
struct phy *phy;
|
||||||
struct clk *clk;
|
struct clk *clk;
|
||||||
void __iomem *base;
|
void __iomem *base;
|
||||||
|
struct regulator *vbus;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int imx8mq_usb_phy_init(struct phy *phy)
|
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)
|
static int imx8mq_phy_power_on(struct phy *phy)
|
||||||
{
|
{
|
||||||
struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(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);
|
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);
|
struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy);
|
||||||
|
|
||||||
clk_disable_unprepare(imx_phy->clk);
|
clk_disable_unprepare(imx_phy->clk);
|
||||||
|
regulator_disable(imx_phy->vbus);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -101,6 +109,10 @@ static int imx8mq_usb_phy_probe(struct platform_device *pdev)
|
||||||
if (IS_ERR(imx_phy->phy))
|
if (IS_ERR(imx_phy->phy))
|
||||||
return PTR_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_set_drvdata(imx_phy->phy, imx_phy);
|
||||||
|
|
||||||
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
|
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
|
||||||
|
|
|
@ -12,6 +12,16 @@ config PHY_HI6220_USB
|
||||||
|
|
||||||
To compile this driver as a module, choose M here.
|
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
|
config PHY_HISTB_COMBPHY
|
||||||
tristate "HiSilicon STB SoCs COMBPHY support"
|
tristate "HiSilicon STB SoCs COMBPHY support"
|
||||||
depends on (ARCH_HISI && ARM64) || COMPILE_TEST
|
depends on (ARCH_HISI && ARM64) || COMPILE_TEST
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
obj-$(CONFIG_PHY_HI6220_USB) += phy-hi6220-usb.o
|
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_HISTB_COMBPHY) += phy-histb-combphy.o
|
||||||
obj-$(CONFIG_PHY_HISI_INNO_USB2) += phy-hisi-inno-usb2.o
|
obj-$(CONFIG_PHY_HISI_INNO_USB2) += phy-hisi-inno-usb2.o
|
||||||
obj-$(CONFIG_PHY_HIX5HD2_SATA) += phy-hix5hd2-sata.o
|
obj-$(CONFIG_PHY_HIX5HD2_SATA) += phy-hix5hd2-sata.o
|
||||||
|
|
|
@ -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");
|
|
@ -13,6 +13,16 @@ config PHY_MTK_TPHY
|
||||||
multi-ports is first version, otherwise is second veriosn,
|
multi-ports is first version, otherwise is second veriosn,
|
||||||
so you can easily distinguish them by banks layout.
|
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
|
config PHY_MTK_XSPHY
|
||||||
tristate "MediaTek XS-PHY Driver"
|
tristate "MediaTek XS-PHY Driver"
|
||||||
depends on ARCH_MEDIATEK && OF
|
depends on ARCH_MEDIATEK && OF
|
||||||
|
|
|
@ -4,4 +4,5 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
obj-$(CONFIG_PHY_MTK_TPHY) += phy-mtk-tphy.o
|
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
|
obj-$(CONFIG_PHY_MTK_XSPHY) += phy-mtk-xsphy.o
|
||||||
|
|
|
@ -1103,13 +1103,9 @@ static int mtk_tphy_probe(struct platform_device *pdev)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* it's deprecated, make it optional for backward compatibility */
|
/* it's deprecated, make it optional for backward compatibility */
|
||||||
tphy->u3phya_ref = devm_clk_get(dev, "u3phya_ref");
|
tphy->u3phya_ref = devm_clk_get_optional(dev, "u3phya_ref");
|
||||||
if (IS_ERR(tphy->u3phya_ref)) {
|
if (IS_ERR(tphy->u3phya_ref))
|
||||||
if (PTR_ERR(tphy->u3phya_ref) == -EPROBE_DEFER)
|
return PTR_ERR(tphy->u3phya_ref);
|
||||||
return -EPROBE_DEFER;
|
|
||||||
|
|
||||||
tphy->u3phya_ref = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
tphy->src_ref_clk = U3P_REF_CLK;
|
tphy->src_ref_clk = U3P_REF_CLK;
|
||||||
tphy->src_coef = U3P_SLEW_RATE_COEF;
|
tphy->src_coef = U3P_SLEW_RATE_COEF;
|
||||||
|
|
|
@ -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");
|
|
@ -31,6 +31,238 @@ struct serdes_macro {
|
||||||
struct serdes_ctrl *ctrl;
|
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
|
#define MCB_S1G_CFG_TIMEOUT 50
|
||||||
|
|
||||||
static int __serdes_write_mcb_s1g(struct regmap *regmap, u8 macro, u32 op)
|
static int __serdes_write_mcb_s1g(struct regmap *regmap, u8 macro, u32 op)
|
||||||
|
@ -110,7 +342,7 @@ struct serdes_mux {
|
||||||
u32 mux;
|
u32 mux;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SERDES_MUX(_idx, _port, _mode, _submode, _mask, _mux) { \
|
#define SERDES_MUX(_idx, _port, _mode, _submode, _mask, _mux) { \
|
||||||
.idx = _idx, \
|
.idx = _idx, \
|
||||||
.port = _port, \
|
.port = _port, \
|
||||||
.mode = _mode, \
|
.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)
|
if (macro->idx <= SERDES1G_MAX)
|
||||||
return serdes_init_s1g(macro->ctrl->regs, macro->idx);
|
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;
|
return -EOPNOTSUPP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -384,10 +384,16 @@ int phy_reset(struct phy *phy)
|
||||||
if (!phy || !phy->ops->reset)
|
if (!phy || !phy->ops->reset)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
ret = phy_pm_runtime_get_sync(phy);
|
||||||
|
if (ret < 0 && ret != -ENOTSUPP)
|
||||||
|
return ret;
|
||||||
|
|
||||||
mutex_lock(&phy->mutex);
|
mutex_lock(&phy->mutex);
|
||||||
ret = phy->ops->reset(phy);
|
ret = phy->ops->reset(phy);
|
||||||
mutex_unlock(&phy->mutex);
|
mutex_unlock(&phy->mutex);
|
||||||
|
|
||||||
|
phy_pm_runtime_put(phy);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(phy_reset);
|
EXPORT_SYMBOL_GPL(phy_reset);
|
||||||
|
@ -564,6 +570,11 @@ void phy_put(struct phy *phy)
|
||||||
if (!phy || IS_ERR(phy))
|
if (!phy || IS_ERR(phy))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
mutex_lock(&phy->mutex);
|
||||||
|
if (phy->ops->release)
|
||||||
|
phy->ops->release(phy);
|
||||||
|
mutex_unlock(&phy->mutex);
|
||||||
|
|
||||||
module_put(phy->ops->owner);
|
module_put(phy->ops->owner);
|
||||||
put_device(&phy->dev);
|
put_device(&phy->dev);
|
||||||
}
|
}
|
||||||
|
|
|
@ -242,6 +242,88 @@ static const struct qmp_phy_init_tbl msm8996_pcie_pcs_tbl[] = {
|
||||||
QMP_PHY_INIT_CFG(QPHY_TXDEEMPH_M3P5DB_V0, 0x0e),
|
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[] = {
|
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_SYSCLK_EN_SEL, 0x14),
|
||||||
QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08),
|
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
|
* @init_count: phy common block initialization count
|
||||||
* @phy_initialized: indicate if PHY has been initialized
|
* @phy_initialized: indicate if PHY has been initialized
|
||||||
* @mode: current PHY mode
|
* @mode: current PHY mode
|
||||||
|
* @ufs_reset: optional UFS PHY reset handle
|
||||||
*/
|
*/
|
||||||
struct qcom_qmp {
|
struct qcom_qmp {
|
||||||
struct device *dev;
|
struct device *dev;
|
||||||
|
@ -914,6 +997,8 @@ struct qcom_qmp {
|
||||||
int init_count;
|
int init_count;
|
||||||
bool phy_initialized;
|
bool phy_initialized;
|
||||||
enum phy_mode mode;
|
enum phy_mode mode;
|
||||||
|
|
||||||
|
struct reset_control *ufs_reset;
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline void qphy_setbits(void __iomem *base, u32 offset, u32 val)
|
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,
|
.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 = {
|
static const struct qmp_phy_cfg msm8998_usb3phy_cfg = {
|
||||||
.type = PHY_TYPE_USB3,
|
.type = PHY_TYPE_USB3,
|
||||||
.nlanes = 1,
|
.nlanes = 1,
|
||||||
|
@ -1314,6 +1424,7 @@ static int qcom_qmp_phy_com_exit(struct qcom_qmp *qmp)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reset_control_assert(qmp->ufs_reset);
|
||||||
if (cfg->has_phy_com_ctrl) {
|
if (cfg->has_phy_com_ctrl) {
|
||||||
qphy_setbits(serdes, cfg->regs[QPHY_COM_START_CONTROL],
|
qphy_setbits(serdes, cfg->regs[QPHY_COM_START_CONTROL],
|
||||||
SERDES_START | PCS_START);
|
SERDES_START | PCS_START);
|
||||||
|
@ -1335,8 +1446,7 @@ static int qcom_qmp_phy_com_exit(struct qcom_qmp *qmp)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* PHY Initialization */
|
static int qcom_qmp_phy_enable(struct phy *phy)
|
||||||
static int qcom_qmp_phy_init(struct phy *phy)
|
|
||||||
{
|
{
|
||||||
struct qmp_phy *qphy = phy_get_drvdata(phy);
|
struct qmp_phy *qphy = phy_get_drvdata(phy);
|
||||||
struct qcom_qmp *qmp = qphy->qmp;
|
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");
|
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);
|
ret = qcom_qmp_phy_com_init(qphy);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -1383,14 +1520,9 @@ static int qcom_qmp_phy_init(struct phy *phy)
|
||||||
cfg->rx_tbl, cfg->rx_tbl_num);
|
cfg->rx_tbl, cfg->rx_tbl_num);
|
||||||
|
|
||||||
qcom_qmp_phy_configure(pcs, cfg->regs, cfg->pcs_tbl, cfg->pcs_tbl_num);
|
qcom_qmp_phy_configure(pcs, cfg->regs, cfg->pcs_tbl, cfg->pcs_tbl_num);
|
||||||
|
ret = reset_control_deassert(qmp->ufs_reset);
|
||||||
/*
|
if (ret)
|
||||||
* UFS PHY requires the deassert of software reset before serdes start.
|
goto err_lane_rst;
|
||||||
* 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;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Pull out PHY from POWER DOWN state.
|
* 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);
|
usleep_range(cfg->pwrdn_delay_min, cfg->pwrdn_delay_max);
|
||||||
|
|
||||||
/* Pull PHY out of reset state */
|
/* 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)
|
if (cfg->has_phy_dp_com_ctrl)
|
||||||
qphy_clrbits(dp_com, QPHY_V3_DP_COM_SW_RESET, SW_RESET);
|
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;
|
goto err_pcs_ready;
|
||||||
}
|
}
|
||||||
qmp->phy_initialized = true;
|
qmp->phy_initialized = true;
|
||||||
|
return 0;
|
||||||
out:
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
err_pcs_ready:
|
err_pcs_ready:
|
||||||
|
reset_control_assert(qmp->ufs_reset);
|
||||||
clk_disable_unprepare(qphy->pipe_clk);
|
clk_disable_unprepare(qphy->pipe_clk);
|
||||||
err_clk_enable:
|
err_clk_enable:
|
||||||
if (cfg->has_lane_rst)
|
if (cfg->has_lane_rst)
|
||||||
|
@ -1435,7 +1568,7 @@ err_lane_rst:
|
||||||
return ret;
|
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 qmp_phy *qphy = phy_get_drvdata(phy);
|
||||||
struct qcom_qmp *qmp = qphy->qmp;
|
struct qcom_qmp *qmp = qphy->qmp;
|
||||||
|
@ -1463,44 +1596,6 @@ static int qcom_qmp_phy_exit(struct phy *phy)
|
||||||
return 0;
|
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,
|
static int qcom_qmp_phy_set_mode(struct phy *phy,
|
||||||
enum phy_mode mode, int submode)
|
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 = {
|
static const struct phy_ops qcom_qmp_phy_gen_ops = {
|
||||||
.init = qcom_qmp_phy_init,
|
.init = qcom_qmp_phy_enable,
|
||||||
.exit = qcom_qmp_phy_exit,
|
.exit = qcom_qmp_phy_disable,
|
||||||
.power_on = qcom_qmp_phy_poweron,
|
.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,
|
.set_mode = qcom_qmp_phy_set_mode,
|
||||||
.owner = THIS_MODULE,
|
.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 qcom_qmp *qmp = dev_get_drvdata(dev);
|
||||||
struct phy *generic_phy;
|
struct phy *generic_phy;
|
||||||
struct qmp_phy *qphy;
|
struct qmp_phy *qphy;
|
||||||
|
const struct phy_ops *ops = &qcom_qmp_phy_gen_ops;
|
||||||
char prop_name[MAX_PROP_NAME];
|
char prop_name[MAX_PROP_NAME];
|
||||||
int ret;
|
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)) {
|
if (IS_ERR(generic_phy)) {
|
||||||
ret = PTR_ERR(generic_phy);
|
ret = PTR_ERR(generic_phy);
|
||||||
dev_err(dev, "failed to create qphy %d\n", ret);
|
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",
|
.compatible = "qcom,msm8996-qmp-usb3-phy",
|
||||||
.data = &msm8996_usb3phy_cfg,
|
.data = &msm8996_usb3phy_cfg,
|
||||||
|
}, {
|
||||||
|
.compatible = "qcom,msm8998-qmp-pcie-phy",
|
||||||
|
.data = &msm8998_pciephy_cfg,
|
||||||
}, {
|
}, {
|
||||||
.compatible = "qcom,msm8998-qmp-ufs-phy",
|
.compatible = "qcom,msm8998-qmp-ufs-phy",
|
||||||
.data = &sdm845_ufsphy_cfg,
|
.data = &sdm845_ufsphy_cfg,
|
||||||
|
|
|
@ -241,6 +241,7 @@
|
||||||
#define QSERDES_V3_RX_RX_BAND 0x110
|
#define QSERDES_V3_RX_RX_BAND 0x110
|
||||||
#define QSERDES_V3_RX_RX_INTERFACE_MODE 0x11c
|
#define QSERDES_V3_RX_RX_INTERFACE_MODE 0x11c
|
||||||
#define QSERDES_V3_RX_RX_MODE_00 0x164
|
#define QSERDES_V3_RX_RX_MODE_00 0x164
|
||||||
|
#define QSERDES_V3_RX_RX_MODE_01 0x168
|
||||||
|
|
||||||
/* Only for QMP V3 PHY - PCS registers */
|
/* Only for QMP V3 PHY - PCS registers */
|
||||||
#define QPHY_V3_PCS_POWER_DOWN_CONTROL 0x004
|
#define QPHY_V3_PCS_POWER_DOWN_CONTROL 0x004
|
||||||
|
@ -280,6 +281,7 @@
|
||||||
#define QPHY_V3_PCS_TSYNC_RSYNC_TIME 0x08c
|
#define QPHY_V3_PCS_TSYNC_RSYNC_TIME 0x08c
|
||||||
#define QPHY_V3_PCS_PWRUP_RESET_DLY_TIME_AUXCLK 0x0a0
|
#define QPHY_V3_PCS_PWRUP_RESET_DLY_TIME_AUXCLK 0x0a0
|
||||||
#define QPHY_V3_PCS_LP_WAKEUP_DLY_TIME_AUXCLK 0x0a4
|
#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_LFPS_TX_ECSTART_EQTLOCK 0x0b0
|
||||||
#define QPHY_V3_PCS_RXEQTRAINING_WAIT_TIME 0x0b8
|
#define QPHY_V3_PCS_RXEQTRAINING_WAIT_TIME 0x0b8
|
||||||
#define QPHY_V3_PCS_RXEQTRAINING_RUN_TIME 0x0bc
|
#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_MIN_HIBERN8_TIME 0x138
|
||||||
#define QPHY_V3_PCS_RX_SIGDET_CTRL1 0x13c
|
#define QPHY_V3_PCS_RX_SIGDET_CTRL1 0x13c
|
||||||
#define QPHY_V3_PCS_RX_SIGDET_CTRL2 0x140
|
#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_TX_MID_TERM_CTRL1 0x1bc
|
||||||
#define QPHY_V3_PCS_MULTI_LANE_CTRL1 0x1c4
|
#define QPHY_V3_PCS_MULTI_LANE_CTRL1 0x1c4
|
||||||
#define QPHY_V3_PCS_RX_SIGDET_LVL 0x1d8
|
#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_CONFIG1 0x20c
|
||||||
#define QPHY_V3_PCS_REFGEN_REQ_CONFIG2 0x210
|
#define QPHY_V3_PCS_REFGEN_REQ_CONFIG2 0x210
|
||||||
|
|
||||||
/* Only for QMP V3 PHY - PCS_MISC registers */
|
/* Only for QMP V3 PHY - PCS_MISC registers */
|
||||||
#define QPHY_V3_PCS_MISC_CLAMP_ENABLE 0x0c
|
#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
|
#endif
|
||||||
|
|
|
@ -822,14 +822,9 @@ static int qusb2_phy_probe(struct platform_device *pdev)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
qphy->iface_clk = devm_clk_get(dev, "iface");
|
qphy->iface_clk = devm_clk_get_optional(dev, "iface");
|
||||||
if (IS_ERR(qphy->iface_clk)) {
|
if (IS_ERR(qphy->iface_clk))
|
||||||
ret = PTR_ERR(qphy->iface_clk);
|
return 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->phy_reset = devm_reset_control_get_by_index(&pdev->dev, 0);
|
qphy->phy_reset = devm_reset_control_get_by_index(&pdev->dev, 0);
|
||||||
if (IS_ERR(qphy->phy_reset)) {
|
if (IS_ERR(qphy->phy_reset)) {
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include <linux/clk.h>
|
#include <linux/clk.h>
|
||||||
#include <linux/phy/phy.h>
|
#include <linux/phy/phy.h>
|
||||||
#include <linux/regulator/consumer.h>
|
#include <linux/regulator/consumer.h>
|
||||||
|
#include <linux/reset.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
#include <linux/io.h>
|
#include <linux/io.h>
|
||||||
|
@ -96,11 +97,10 @@ struct ufs_qcom_phy {
|
||||||
char name[UFS_QCOM_PHY_NAME_LEN];
|
char name[UFS_QCOM_PHY_NAME_LEN];
|
||||||
struct ufs_qcom_phy_calibration *cached_regs;
|
struct ufs_qcom_phy_calibration *cached_regs;
|
||||||
int cached_regs_table_size;
|
int cached_regs_table_size;
|
||||||
bool is_powered_on;
|
|
||||||
bool is_started;
|
|
||||||
struct ufs_qcom_phy_specific_ops *phy_spec_ops;
|
struct ufs_qcom_phy_specific_ops *phy_spec_ops;
|
||||||
|
|
||||||
enum phy_mode mode;
|
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
|
* and writes to QSERDES_RX_SIGDET_CNTRL attribute
|
||||||
*/
|
*/
|
||||||
struct ufs_qcom_phy_specific_ops {
|
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);
|
void (*start_serdes)(struct ufs_qcom_phy *phy);
|
||||||
int (*is_physical_coding_sublayer_ready)(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);
|
void (*set_tx_lane_enable)(struct ufs_qcom_phy *phy, u32 val);
|
||||||
|
|
|
@ -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;
|
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
|
static
|
||||||
int ufs_qcom_phy_qmp_14nm_set_mode(struct phy *generic_phy,
|
int ufs_qcom_phy_qmp_14nm_set_mode(struct phy *generic_phy,
|
||||||
enum phy_mode mode, int submode)
|
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 = {
|
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_on = ufs_qcom_phy_power_on,
|
||||||
.power_off = ufs_qcom_phy_power_off,
|
.power_off = ufs_qcom_phy_power_off,
|
||||||
.set_mode = ufs_qcom_phy_qmp_14nm_set_mode,
|
.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 = {
|
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,
|
.start_serdes = ufs_qcom_phy_qmp_14nm_start_serdes,
|
||||||
.is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_14nm_is_pcs_ready,
|
.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,
|
.set_tx_lane_enable = ufs_qcom_phy_qmp_14nm_set_tx_lane_enable,
|
||||||
|
|
|
@ -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;
|
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
|
static
|
||||||
int ufs_qcom_phy_qmp_20nm_set_mode(struct phy *generic_phy,
|
int ufs_qcom_phy_qmp_20nm_set_mode(struct phy *generic_phy,
|
||||||
enum phy_mode mode, int submode)
|
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 = {
|
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_on = ufs_qcom_phy_power_on,
|
||||||
.power_off = ufs_qcom_phy_power_off,
|
.power_off = ufs_qcom_phy_power_off,
|
||||||
.set_mode = ufs_qcom_phy_qmp_20nm_set_mode,
|
.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 = {
|
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,
|
.start_serdes = ufs_qcom_phy_qmp_20nm_start_serdes,
|
||||||
.is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_20nm_is_pcs_ready,
|
.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,
|
.set_tx_lane_enable = ufs_qcom_phy_qmp_20nm_set_tx_lane_enable,
|
||||||
|
|
|
@ -147,6 +147,21 @@ out:
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(ufs_qcom_phy_generic_probe);
|
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,
|
static int __ufs_qcom_phy_clk_get(struct device *dev,
|
||||||
const char *name, struct clk **clk_out, bool err_print)
|
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 ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
|
||||||
struct device *dev = phy_common->dev;
|
struct device *dev = phy_common->dev;
|
||||||
|
bool is_rate_B = false;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (phy_common->is_powered_on)
|
err = ufs_qcom_phy_get_reset(phy_common);
|
||||||
return 0;
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
if (!phy_common->is_started) {
|
err = reset_control_assert(phy_common->ufs_reset);
|
||||||
err = ufs_qcom_phy_start_serdes(phy_common);
|
if (err)
|
||||||
if (err)
|
return err;
|
||||||
return err;
|
|
||||||
|
|
||||||
err = ufs_qcom_phy_is_pcs_ready(phy_common);
|
if (phy_common->mode == PHY_MODE_UFS_HS_B)
|
||||||
if (err)
|
is_rate_B = true;
|
||||||
return err;
|
|
||||||
|
|
||||||
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);
|
err = ufs_qcom_phy_enable_vreg(dev, &phy_common->vdda_phy);
|
||||||
if (err) {
|
if (err) {
|
||||||
dev_err(dev, "%s enable vdda_phy failed, err=%d\n",
|
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;
|
goto out;
|
||||||
|
|
||||||
out_disable_ref_clk:
|
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);
|
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);
|
phy_common->phy_spec_ops->power_control(phy_common, false);
|
||||||
|
|
||||||
if (phy_common->vddp_ref_clk.reg)
|
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_pll);
|
||||||
ufs_qcom_phy_disable_vreg(phy_common->dev, &phy_common->vdda_phy);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(ufs_qcom_phy_power_off);
|
EXPORT_SYMBOL_GPL(ufs_qcom_phy_power_off);
|
||||||
|
|
|
@ -19,7 +19,7 @@ config PHY_RCAR_GEN3_PCIE
|
||||||
config PHY_RCAR_GEN3_USB2
|
config PHY_RCAR_GEN3_USB2
|
||||||
tristate "Renesas R-Car generation 3 USB 2.0 PHY driver"
|
tristate "Renesas R-Car generation 3 USB 2.0 PHY driver"
|
||||||
depends on ARCH_RENESAS
|
depends on ARCH_RENESAS
|
||||||
depends on EXTCON
|
depends on EXTCON || !EXTCON # if EXTCON=m, this cannot be built-in
|
||||||
depends on USB_SUPPORT
|
depends on USB_SUPPORT
|
||||||
select GENERIC_PHY
|
select GENERIC_PHY
|
||||||
select USB_COMMON
|
select USB_COMMON
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
*
|
*
|
||||||
* Copyright (C) 2014 Renesas Solutions Corp.
|
* Copyright (C) 2014 Renesas Solutions Corp.
|
||||||
* Copyright (C) 2014 Cogent Embedded, Inc.
|
* Copyright (C) 2014 Cogent Embedded, Inc.
|
||||||
|
* Copyright (C) 2019 Renesas Electronics Corp.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/clk.h>
|
#include <linux/clk.h>
|
||||||
|
@ -15,6 +16,7 @@
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
#include <linux/spinlock.h>
|
#include <linux/spinlock.h>
|
||||||
#include <linux/atomic.h>
|
#include <linux/atomic.h>
|
||||||
|
#include <linux/of_device.h>
|
||||||
|
|
||||||
#define USBHS_LPSTS 0x02
|
#define USBHS_LPSTS 0x02
|
||||||
#define USBHS_UGCTRL 0x80
|
#define USBHS_UGCTRL 0x80
|
||||||
|
@ -35,6 +37,8 @@
|
||||||
#define USBHS_UGCTRL2_USB0SEL 0x00000030
|
#define USBHS_UGCTRL2_USB0SEL 0x00000030
|
||||||
#define USBHS_UGCTRL2_USB0SEL_PCI 0x00000010
|
#define USBHS_UGCTRL2_USB0SEL_PCI 0x00000010
|
||||||
#define USBHS_UGCTRL2_USB0SEL_HS_USB 0x00000030
|
#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) */
|
/* USB General status register (UGSTS) */
|
||||||
#define USBHS_UGSTS_LOCK 0x00000100 /* From technical update */
|
#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_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)
|
static int rcar_gen2_phy_init(struct phy *p)
|
||||||
{
|
{
|
||||||
struct rcar_gen2_phy *phy = phy_get_drvdata(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;
|
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 = {
|
static const struct phy_ops rcar_gen2_phy_ops = {
|
||||||
.init = rcar_gen2_phy_init,
|
.init = rcar_gen2_phy_init,
|
||||||
.exit = rcar_gen2_phy_exit,
|
.exit = rcar_gen2_phy_exit,
|
||||||
|
@ -188,12 +251,55 @@ static const struct phy_ops rcar_gen2_phy_ops = {
|
||||||
.owner = THIS_MODULE,
|
.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[] = {
|
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-r8a77470",
|
||||||
{ .compatible = "renesas,usb-phy-r8a7794" },
|
.data = &rz_g1c_usb_phy_data,
|
||||||
{ .compatible = "renesas,rcar-gen2-usb-phy" },
|
},
|
||||||
{ }
|
{
|
||||||
|
.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);
|
MODULE_DEVICE_TABLE(of, rcar_gen2_phy_match_table);
|
||||||
|
|
||||||
|
@ -224,11 +330,6 @@ static const u32 select_mask[] = {
|
||||||
[2] = USBHS_UGCTRL2_USB2SEL,
|
[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)
|
static int rcar_gen2_phy_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct device *dev = &pdev->dev;
|
struct device *dev = &pdev->dev;
|
||||||
|
@ -238,6 +339,7 @@ static int rcar_gen2_phy_probe(struct platform_device *pdev)
|
||||||
struct resource *res;
|
struct resource *res;
|
||||||
void __iomem *base;
|
void __iomem *base;
|
||||||
struct clk *clk;
|
struct clk *clk;
|
||||||
|
const struct rcar_gen2_phy_data *data;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
if (!dev->of_node) {
|
if (!dev->of_node) {
|
||||||
|
@ -266,6 +368,10 @@ static int rcar_gen2_phy_probe(struct platform_device *pdev)
|
||||||
drv->clk = clk;
|
drv->clk = clk;
|
||||||
drv->base = base;
|
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->num_channels = of_get_child_count(dev->of_node);
|
||||||
drv->channels = devm_kcalloc(dev, drv->num_channels,
|
drv->channels = devm_kcalloc(dev, drv->num_channels,
|
||||||
sizeof(struct rcar_gen2_channel),
|
sizeof(struct rcar_gen2_channel),
|
||||||
|
@ -294,10 +400,10 @@ static int rcar_gen2_phy_probe(struct platform_device *pdev)
|
||||||
|
|
||||||
phy->channel = channel;
|
phy->channel = channel;
|
||||||
phy->number = n;
|
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,
|
phy->phy = devm_phy_create(dev, NULL,
|
||||||
&rcar_gen2_phy_ops);
|
data->gen2_phy_ops);
|
||||||
if (IS_ERR(phy->phy)) {
|
if (IS_ERR(phy->phy)) {
|
||||||
dev_err(dev, "Failed to create PHY\n");
|
dev_err(dev, "Failed to create PHY\n");
|
||||||
return PTR_ERR(phy->phy);
|
return PTR_ERR(phy->phy);
|
||||||
|
|
|
@ -37,11 +37,8 @@
|
||||||
|
|
||||||
/* INT_ENABLE */
|
/* INT_ENABLE */
|
||||||
#define USB2_INT_ENABLE_UCOM_INTEN BIT(3)
|
#define USB2_INT_ENABLE_UCOM_INTEN BIT(3)
|
||||||
#define USB2_INT_ENABLE_USBH_INTB_EN BIT(2)
|
#define USB2_INT_ENABLE_USBH_INTB_EN BIT(2) /* For EHCI */
|
||||||
#define USB2_INT_ENABLE_USBH_INTA_EN BIT(1)
|
#define USB2_INT_ENABLE_USBH_INTA_EN BIT(1) /* For OHCI */
|
||||||
#define USB2_INT_ENABLE_INIT (USB2_INT_ENABLE_UCOM_INTEN | \
|
|
||||||
USB2_INT_ENABLE_USBH_INTB_EN | \
|
|
||||||
USB2_INT_ENABLE_USBH_INTA_EN)
|
|
||||||
|
|
||||||
/* USBCTR */
|
/* USBCTR */
|
||||||
#define USB2_USBCTR_DIRPD BIT(2)
|
#define USB2_USBCTR_DIRPD BIT(2)
|
||||||
|
@ -78,10 +75,35 @@
|
||||||
#define USB2_ADPCTRL_IDPULLUP BIT(5) /* 1 = ID sampling is enabled */
|
#define USB2_ADPCTRL_IDPULLUP BIT(5) /* 1 = ID sampling is enabled */
|
||||||
#define USB2_ADPCTRL_DRVVBUS BIT(4)
|
#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 {
|
struct rcar_gen3_chan {
|
||||||
void __iomem *base;
|
void __iomem *base;
|
||||||
|
struct device *dev; /* platform_device's device */
|
||||||
struct extcon_dev *extcon;
|
struct extcon_dev *extcon;
|
||||||
struct phy *phy;
|
struct rcar_gen3_phy rphys[NUM_OF_PHYS];
|
||||||
struct regulator *vbus;
|
struct regulator *vbus;
|
||||||
struct work_struct work;
|
struct work_struct work;
|
||||||
enum usb_dr_mode dr_mode;
|
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;
|
void __iomem *usb2_base = ch->base;
|
||||||
u32 val = readl(usb2_base + USB2_COMMCTRL);
|
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)
|
if (host)
|
||||||
val &= ~USB2_COMMCTRL_OTG_PERI;
|
val &= ~USB2_COMMCTRL_OTG_PERI;
|
||||||
else
|
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;
|
void __iomem *usb2_base = ch->base;
|
||||||
u32 val = readl(usb2_base + USB2_LINECTRL1);
|
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);
|
val &= ~(USB2_LINECTRL1_DP_RPD | USB2_LINECTRL1_DM_RPD);
|
||||||
if (dp)
|
if (dp)
|
||||||
val |= USB2_LINECTRL1_DP_RPD;
|
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;
|
void __iomem *usb2_base = ch->base;
|
||||||
u32 val = readl(usb2_base + USB2_ADPCTRL);
|
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)
|
if (vbus)
|
||||||
val |= USB2_ADPCTRL_DRVVBUS;
|
val |= USB2_ADPCTRL_DRVVBUS;
|
||||||
else
|
else
|
||||||
|
@ -249,6 +271,42 @@ static enum phy_mode rcar_gen3_get_phy_mode(struct rcar_gen3_chan *ch)
|
||||||
return PHY_MODE_USB_DEVICE;
|
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,
|
static ssize_t role_store(struct device *dev, struct device_attribute *attr,
|
||||||
const char *buf, size_t count)
|
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;
|
bool is_b_device;
|
||||||
enum phy_mode cur_mode, new_mode;
|
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;
|
return -EIO;
|
||||||
|
|
||||||
if (!strncmp(buf, "host", strlen("host")))
|
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);
|
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 -EIO;
|
||||||
|
|
||||||
return sprintf(buf, "%s\n", rcar_gen3_is_host(ch) ? "host" :
|
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)
|
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;
|
void __iomem *usb2_base = channel->base;
|
||||||
|
u32 val;
|
||||||
|
|
||||||
/* Initialize USB2 part */
|
/* 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_SPD_RSM_TIMSET_INIT, usb2_base + USB2_SPD_RSM_TIMSET);
|
||||||
writel(USB2_OC_TIMSET_INIT, usb2_base + USB2_OC_TIMSET);
|
writel(USB2_OC_TIMSET_INIT, usb2_base + USB2_OC_TIMSET);
|
||||||
|
|
||||||
/* Initialize otg part */
|
/* Initialize otg part */
|
||||||
if (channel->is_otg_channel)
|
if (channel->is_otg_channel) {
|
||||||
rcar_gen3_init_otg(channel);
|
if (rcar_gen3_needs_init_otg(channel))
|
||||||
|
rcar_gen3_init_otg(channel);
|
||||||
|
rphy->otg_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
rphy->initialized = true;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rcar_gen3_phy_usb2_exit(struct phy *p)
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rcar_gen3_phy_usb2_power_on(struct phy *p)
|
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;
|
void __iomem *usb2_base = channel->base;
|
||||||
u32 val;
|
u32 val;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
if (!rcar_gen3_are_all_rphys_power_off(channel))
|
||||||
|
return 0;
|
||||||
|
|
||||||
if (channel->vbus) {
|
if (channel->vbus) {
|
||||||
ret = regulator_enable(channel->vbus);
|
ret = regulator_enable(channel->vbus);
|
||||||
if (ret)
|
if (ret)
|
||||||
|
@ -371,14 +454,22 @@ static int rcar_gen3_phy_usb2_power_on(struct phy *p)
|
||||||
val &= ~USB2_USBCTR_PLL_RST;
|
val &= ~USB2_USBCTR_PLL_RST;
|
||||||
writel(val, usb2_base + USB2_USBCTR);
|
writel(val, usb2_base + USB2_USBCTR);
|
||||||
|
|
||||||
|
rphy->powered = true;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rcar_gen3_phy_usb2_power_off(struct phy *p)
|
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;
|
int ret = 0;
|
||||||
|
|
||||||
|
rphy->powered = false;
|
||||||
|
|
||||||
|
if (!rcar_gen3_are_all_rphys_power_off(channel))
|
||||||
|
return 0;
|
||||||
|
|
||||||
if (channel->vbus)
|
if (channel->vbus)
|
||||||
ret = regulator_disable(channel->vbus);
|
ret = regulator_disable(channel->vbus);
|
||||||
|
|
||||||
|
@ -393,6 +484,12 @@ static const struct phy_ops rcar_gen3_phy_usb2_ops = {
|
||||||
.owner = THIS_MODULE,
|
.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)
|
static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch)
|
||||||
{
|
{
|
||||||
struct rcar_gen3_chan *ch = _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;
|
irqreturn_t ret = IRQ_NONE;
|
||||||
|
|
||||||
if (status & USB2_OBINT_BITS) {
|
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);
|
writel(USB2_OBINT_BITS, usb2_base + USB2_OBINTSTA);
|
||||||
rcar_gen3_device_recognition(ch);
|
rcar_gen3_device_recognition(ch);
|
||||||
ret = IRQ_HANDLED;
|
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[] = {
|
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-r8a77470",
|
||||||
{ .compatible = "renesas,usb2-phy-r8a77965" },
|
.data = &rz_g1c_phy_usb2_ops,
|
||||||
{ .compatible = "renesas,rcar-gen3-usb2-phy" },
|
},
|
||||||
{ }
|
{
|
||||||
|
.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);
|
MODULE_DEVICE_TABLE(of, rcar_gen3_phy_usb2_match_table);
|
||||||
|
|
||||||
|
@ -425,13 +538,54 @@ static const unsigned int rcar_gen3_phy_cable[] = {
|
||||||
EXTCON_NONE,
|
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)
|
static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct device *dev = &pdev->dev;
|
struct device *dev = &pdev->dev;
|
||||||
struct rcar_gen3_chan *channel;
|
struct rcar_gen3_chan *channel;
|
||||||
struct phy_provider *provider;
|
struct phy_provider *provider;
|
||||||
struct resource *res;
|
struct resource *res;
|
||||||
int irq, ret = 0;
|
const struct phy_ops *phy_usb2_ops;
|
||||||
|
int irq, ret = 0, i;
|
||||||
|
|
||||||
if (!dev->of_node) {
|
if (!dev->of_node) {
|
||||||
dev_err(dev, "This driver needs device tree\n");
|
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);
|
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) {
|
if (channel->dr_mode != USB_DR_MODE_UNKNOWN) {
|
||||||
int ret;
|
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.
|
* And then, phy-core will manage runtime pm for this device.
|
||||||
*/
|
*/
|
||||||
pm_runtime_enable(dev);
|
pm_runtime_enable(dev);
|
||||||
channel->phy = devm_phy_create(dev, NULL, &rcar_gen3_phy_usb2_ops);
|
phy_usb2_ops = of_device_get_match_data(dev);
|
||||||
if (IS_ERR(channel->phy)) {
|
if (!phy_usb2_ops)
|
||||||
dev_err(dev, "Failed to create USB2 PHY\n");
|
return -EINVAL;
|
||||||
ret = PTR_ERR(channel->phy);
|
|
||||||
goto error;
|
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");
|
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);
|
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)) {
|
if (IS_ERR(provider)) {
|
||||||
dev_err(dev, "Failed to register PHY provider\n");
|
dev_err(dev, "Failed to register PHY provider\n");
|
||||||
ret = PTR_ERR(provider);
|
ret = PTR_ERR(provider);
|
||||||
|
|
|
@ -87,6 +87,7 @@ struct rockchip_emmc_phy {
|
||||||
unsigned int reg_offset;
|
unsigned int reg_offset;
|
||||||
struct regmap *reg_base;
|
struct regmap *reg_base;
|
||||||
struct clk *emmcclk;
|
struct clk *emmcclk;
|
||||||
|
unsigned int drive_impedance;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int rockchip_emmc_phy_power(struct phy *phy, bool on_off)
|
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);
|
struct rockchip_emmc_phy *rk_phy = phy_get_drvdata(phy);
|
||||||
|
|
||||||
/* Drive impedance: 50 Ohm */
|
/* Drive impedance: from DTS */
|
||||||
regmap_write(rk_phy->reg_base,
|
regmap_write(rk_phy->reg_base,
|
||||||
rk_phy->reg_offset + GRF_EMMCPHY_CON6,
|
rk_phy->reg_offset + GRF_EMMCPHY_CON6,
|
||||||
HIWORD_UPDATE(PHYCTRL_DR_50OHM,
|
HIWORD_UPDATE(rk_phy->drive_impedance,
|
||||||
PHYCTRL_DR_MASK,
|
PHYCTRL_DR_MASK,
|
||||||
PHYCTRL_DR_SHIFT));
|
PHYCTRL_DR_SHIFT));
|
||||||
|
|
||||||
|
@ -314,6 +315,26 @@ static const struct phy_ops ops = {
|
||||||
.owner = THIS_MODULE,
|
.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)
|
static int rockchip_emmc_phy_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct device *dev = &pdev->dev;
|
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 phy_provider *phy_provider;
|
||||||
struct regmap *grf;
|
struct regmap *grf;
|
||||||
unsigned int reg_offset;
|
unsigned int reg_offset;
|
||||||
|
u32 val;
|
||||||
|
|
||||||
if (!dev->parent || !dev->parent->of_node)
|
if (!dev->parent || !dev->parent->of_node)
|
||||||
return -ENODEV;
|
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_offset = reg_offset;
|
||||||
rk_phy->reg_base = grf;
|
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);
|
generic_phy = devm_phy_create(dev, dev->of_node, &ops);
|
||||||
if (IS_ERR(generic_phy)) {
|
if (IS_ERR(generic_phy)) {
|
||||||
|
|
|
@ -335,13 +335,9 @@ static int uniphier_u3hsphy_probe(struct platform_device *pdev)
|
||||||
if (IS_ERR(priv->clk_parent))
|
if (IS_ERR(priv->clk_parent))
|
||||||
return PTR_ERR(priv->clk_parent);
|
return PTR_ERR(priv->clk_parent);
|
||||||
|
|
||||||
priv->clk_ext = devm_clk_get(dev, "phy-ext");
|
priv->clk_ext = devm_clk_get_optional(dev, "phy-ext");
|
||||||
if (IS_ERR(priv->clk_ext)) {
|
if (IS_ERR(priv->clk_ext))
|
||||||
if (PTR_ERR(priv->clk_ext) == -ENOENT)
|
return PTR_ERR(priv->clk_ext);
|
||||||
priv->clk_ext = NULL;
|
|
||||||
else
|
|
||||||
return PTR_ERR(priv->clk_ext);
|
|
||||||
}
|
|
||||||
|
|
||||||
priv->rst = devm_reset_control_get_shared(dev, "phy");
|
priv->rst = devm_reset_control_get_shared(dev, "phy");
|
||||||
if (IS_ERR(priv->rst))
|
if (IS_ERR(priv->rst))
|
||||||
|
|
|
@ -238,13 +238,9 @@ static int uniphier_u3ssphy_probe(struct platform_device *pdev)
|
||||||
if (IS_ERR(priv->clk))
|
if (IS_ERR(priv->clk))
|
||||||
return PTR_ERR(priv->clk);
|
return PTR_ERR(priv->clk);
|
||||||
|
|
||||||
priv->clk_ext = devm_clk_get(dev, "phy-ext");
|
priv->clk_ext = devm_clk_get_optional(dev, "phy-ext");
|
||||||
if (IS_ERR(priv->clk_ext)) {
|
if (IS_ERR(priv->clk_ext))
|
||||||
if (PTR_ERR(priv->clk_ext) == -ENOENT)
|
return PTR_ERR(priv->clk_ext);
|
||||||
priv->clk_ext = NULL;
|
|
||||||
else
|
|
||||||
return PTR_ERR(priv->clk_ext);
|
|
||||||
}
|
|
||||||
|
|
||||||
priv->rst = devm_reset_control_get_shared(dev, "phy");
|
priv->rst = devm_reset_control_get_shared(dev, "phy");
|
||||||
if (IS_ERR(priv->rst))
|
if (IS_ERR(priv->rst))
|
||||||
|
|
|
@ -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_124_SOC) += xusb-tegra124.o
|
||||||
phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_132_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_210_SOC) += xusb-tegra210.o
|
||||||
|
phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_186_SOC) += xusb-tegra186.o
|
||||||
|
|
|
@ -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");
|
|
@ -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
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
* under the terms and conditions of the GNU General Public License,
|
* 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",
|
.compatible = "nvidia,tegra210-xusb-padctl",
|
||||||
.data = &tegra210_xusb_padctl_soc,
|
.data = &tegra210_xusb_padctl_soc,
|
||||||
},
|
},
|
||||||
|
#endif
|
||||||
|
#if defined(CONFIG_ARCH_TEGRA_186_SOC)
|
||||||
|
{
|
||||||
|
.compatible = "nvidia,tegra186-xusb-padctl",
|
||||||
|
.data = &tegra186_xusb_padctl_soc,
|
||||||
|
},
|
||||||
#endif
|
#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;
|
const struct tegra_xusb_lane_soc *soc = lane->soc;
|
||||||
u32 value;
|
u32 value;
|
||||||
|
|
||||||
|
/* skip single function lanes */
|
||||||
|
if (soc->num_funcs < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
/* choose function */
|
/* choose function */
|
||||||
value = padctl_readl(padctl, soc->offset);
|
value = padctl_readl(padctl, soc->offset);
|
||||||
value &= ~(soc->mask << soc->shift);
|
value &= ~(soc->mask << soc->shift);
|
||||||
|
@ -542,13 +552,34 @@ static void tegra_xusb_port_unregister(struct tegra_xusb_port *port)
|
||||||
device_unregister(&port->dev);
|
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)
|
static int tegra_xusb_usb2_port_parse_dt(struct tegra_xusb_usb2_port *usb2)
|
||||||
{
|
{
|
||||||
struct tegra_xusb_port *port = &usb2->base;
|
struct tegra_xusb_port *port = &usb2->base;
|
||||||
struct device_node *np = port->dev.of_node;
|
struct device_node *np = port->dev.of_node;
|
||||||
|
const char *mode;
|
||||||
|
|
||||||
usb2->internal = of_property_read_bool(np, "nvidia,internal");
|
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");
|
usb2->supply = devm_regulator_get(&port->dev, "vbus");
|
||||||
return PTR_ERR_OR_ZERO(usb2->supply);
|
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;
|
struct tegra_xusb_padctl *padctl;
|
||||||
const struct of_device_id *match;
|
const struct of_device_id *match;
|
||||||
struct resource *res;
|
struct resource *res;
|
||||||
|
unsigned int i;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
/* for backwards compatibility with old device trees */
|
/* for backwards compatibility with old device trees */
|
||||||
|
@ -876,14 +908,38 @@ static int tegra_xusb_padctl_probe(struct platform_device *pdev)
|
||||||
goto remove;
|
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);
|
err = reset_control_deassert(padctl->rst);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto remove;
|
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);
|
err = tegra_xusb_setup_pads(padctl);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
dev_err(&pdev->dev, "failed to setup pads: %d\n", err);
|
dev_err(&pdev->dev, "failed to setup pads: %d\n", err);
|
||||||
goto reset;
|
goto power_down;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tegra_xusb_setup_ports(padctl);
|
err = tegra_xusb_setup_ports(padctl);
|
||||||
|
@ -896,6 +952,8 @@ static int tegra_xusb_padctl_probe(struct platform_device *pdev)
|
||||||
|
|
||||||
remove_pads:
|
remove_pads:
|
||||||
tegra_xusb_remove_pads(padctl);
|
tegra_xusb_remove_pads(padctl);
|
||||||
|
power_down:
|
||||||
|
regulator_bulk_disable(padctl->soc->num_supplies, padctl->supplies);
|
||||||
reset:
|
reset:
|
||||||
reset_control_assert(padctl->rst);
|
reset_control_assert(padctl->rst);
|
||||||
remove:
|
remove:
|
||||||
|
@ -911,6 +969,11 @@ static int tegra_xusb_padctl_remove(struct platform_device *pdev)
|
||||||
tegra_xusb_remove_ports(padctl);
|
tegra_xusb_remove_ports(padctl);
|
||||||
tegra_xusb_remove_pads(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);
|
err = reset_control_assert(padctl->rst);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
dev_err(&pdev->dev, "failed to assert reset: %d\n", err);
|
dev_err(&pdev->dev, "failed to assert reset: %d\n", err);
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
#include <linux/workqueue.h>
|
#include <linux/workqueue.h>
|
||||||
|
|
||||||
|
#include <linux/usb/otg.h>
|
||||||
|
|
||||||
/* legacy entry points for backwards-compatibility */
|
/* legacy entry points for backwards-compatibility */
|
||||||
int tegra_xusb_padctl_legacy_probe(struct platform_device *pdev);
|
int tegra_xusb_padctl_legacy_probe(struct platform_device *pdev);
|
||||||
int tegra_xusb_padctl_legacy_remove(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,
|
int tegra_xusb_lane_parse_dt(struct tegra_xusb_lane *lane,
|
||||||
struct device_node *np);
|
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_usb2_lane {
|
||||||
struct tegra_xusb_lane base;
|
struct tegra_xusb_lane base;
|
||||||
|
|
||||||
u32 hs_curr_level_offset;
|
u32 hs_curr_level_offset;
|
||||||
|
bool powered_on;
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline struct tegra_xusb_usb2_lane *
|
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);
|
const struct phy_ops *ops);
|
||||||
void tegra_xusb_pad_unregister(struct tegra_xusb_pad *pad);
|
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_usb2_pad {
|
||||||
struct tegra_xusb_pad base;
|
struct tegra_xusb_pad base;
|
||||||
|
|
||||||
|
@ -271,6 +297,7 @@ struct tegra_xusb_usb2_port {
|
||||||
struct tegra_xusb_port base;
|
struct tegra_xusb_port base;
|
||||||
|
|
||||||
struct regulator *supply;
|
struct regulator *supply;
|
||||||
|
enum usb_dr_mode mode;
|
||||||
bool internal;
|
bool internal;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -367,6 +394,9 @@ struct tegra_xusb_padctl_soc {
|
||||||
} ports;
|
} ports;
|
||||||
|
|
||||||
const struct tegra_xusb_padctl_ops *ops;
|
const struct tegra_xusb_padctl_ops *ops;
|
||||||
|
|
||||||
|
const char * const *supply_names;
|
||||||
|
unsigned int num_supplies;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct tegra_xusb_padctl {
|
struct tegra_xusb_padctl {
|
||||||
|
@ -390,6 +420,8 @@ struct tegra_xusb_padctl {
|
||||||
unsigned int enable;
|
unsigned int enable;
|
||||||
|
|
||||||
struct clk *clk;
|
struct clk *clk;
|
||||||
|
|
||||||
|
struct regulator_bulk_data *supplies;
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline void padctl_writel(struct tegra_xusb_padctl *padctl, u32 value,
|
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)
|
#if defined(CONFIG_ARCH_TEGRA_210_SOC)
|
||||||
extern const struct tegra_xusb_padctl_soc tegra210_xusb_padctl_soc;
|
extern const struct tegra_xusb_padctl_soc tegra210_xusb_padctl_soc;
|
||||||
#endif
|
#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 */
|
#endif /* __PHY_TEGRA_XUSB_H */
|
||||||
|
|
|
@ -20,6 +20,18 @@ config PHY_DM816X_USB
|
||||||
help
|
help
|
||||||
Enable this for dm816x USB to work.
|
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
|
config OMAP_CONTROL_PHY
|
||||||
tristate "OMAP CONTROL PHY Driver"
|
tristate "OMAP CONTROL PHY Driver"
|
||||||
depends on ARCH_OMAP2PLUS || COMPILE_TEST
|
depends on ARCH_OMAP2PLUS || COMPILE_TEST
|
||||||
|
|
|
@ -6,4 +6,5 @@ obj-$(CONFIG_OMAP_USB2) += phy-omap-usb2.o
|
||||||
obj-$(CONFIG_TI_PIPE3) += phy-ti-pipe3.o
|
obj-$(CONFIG_TI_PIPE3) += phy-ti-pipe3.o
|
||||||
obj-$(CONFIG_PHY_TUSB1210) += phy-tusb1210.o
|
obj-$(CONFIG_PHY_TUSB1210) += phy-tusb1210.o
|
||||||
obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.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
|
obj-$(CONFIG_PHY_TI_GMII_SEL) += phy-gmii-sel.o
|
||||||
|
|
|
@ -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");
|
|
@ -56,51 +56,73 @@
|
||||||
|
|
||||||
#define SATA_PLL_SOFT_RESET BIT(18)
|
#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_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_PWRCTL_CLK_FREQ_SHIFT 22
|
||||||
|
|
||||||
#define PIPE3_PHY_TX_RX_POWERON 0x3
|
#define PIPE3_PHY_RX_POWERON (0x1 << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT)
|
||||||
#define PIPE3_PHY_TX_RX_POWEROFF 0x0
|
#define PIPE3_PHY_TX_POWERON (0x2 << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT)
|
||||||
|
|
||||||
#define PCIE_PCS_MASK 0xFF0000
|
#define PCIE_PCS_MASK 0xFF0000
|
||||||
#define PCIE_PCS_DELAY_COUNT_SHIFT 0x10
|
#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_MASK GENMASK(31, 27)
|
||||||
#define INTERFACE_SHIFT 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_MASK GENMASK(17, 14)
|
||||||
#define LOSD_SHIFT 14
|
#define LOSD_SHIFT 14
|
||||||
#define MEM_PLLDIV GENMASK(6, 5)
|
#define MEM_PLLDIV GENMASK(6, 5)
|
||||||
|
|
||||||
#define PCIEPHYRX_TRIM 0x0000001C
|
#define PIPE3_PHY_RX_TRIM 0x0000001C
|
||||||
#define MEM_DLL_TRIM_SEL GENMASK(31, 30)
|
#define MEM_DLL_TRIM_SEL_MASK GENMASK(31, 30)
|
||||||
#define MEM_DLL_TRIM_SHIFT 30
|
#define MEM_DLL_TRIM_SHIFT 30
|
||||||
|
|
||||||
#define PCIEPHYRX_DLL 0x00000024
|
#define PIPE3_PHY_RX_DLL 0x00000024
|
||||||
#define MEM_DLL_PHINT_RATE GENMASK(31, 30)
|
#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_FASTLOCK BIT(23)
|
||||||
#define MEM_CDR_LBW GENMASK(22, 21)
|
#define MEM_CDR_FASTLOCK_SHIFT 23
|
||||||
#define MEM_CDR_STEPCNT GENMASK(20, 19)
|
#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_MASK GENMASK(18, 16)
|
||||||
#define MEM_CDR_STL_SHIFT 16
|
#define MEM_CDR_STL_SHIFT 16
|
||||||
#define MEM_CDR_THR_MASK GENMASK(15, 13)
|
#define MEM_CDR_THR_MASK GENMASK(15, 13)
|
||||||
#define MEM_CDR_THR_SHIFT 13
|
#define MEM_CDR_THR_SHIFT 13
|
||||||
#define MEM_CDR_THR_MODE BIT(12)
|
#define MEM_CDR_THR_MODE BIT(12)
|
||||||
#define MEM_CDR_CDR_2NDO_SDM_MODE BIT(11)
|
#define MEM_CDR_THR_MODE_SHIFT 12
|
||||||
#define MEM_OVRD_HS_RATE BIT(26)
|
#define MEM_CDR_2NDO_SDM_MODE BIT(11)
|
||||||
|
#define MEM_CDR_2NDO_SDM_MODE_SHIFT 11
|
||||||
|
|
||||||
#define PCIEPHYRX_EQUALIZER 0x00000038
|
#define PIPE3_PHY_RX_EQUALIZER 0x00000038
|
||||||
#define MEM_EQLEV GENMASK(31, 16)
|
#define MEM_EQLEV_MASK GENMASK(31, 16)
|
||||||
#define MEM_EQFTC GENMASK(15, 11)
|
#define MEM_EQLEV_SHIFT 16
|
||||||
#define MEM_EQCTL GENMASK(10, 7)
|
#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_EQCTL_SHIFT 7
|
||||||
#define MEM_OVRD_EQLEV BIT(2)
|
#define MEM_OVRD_EQLEV BIT(2)
|
||||||
|
#define MEM_OVRD_EQLEV_SHIFT 2
|
||||||
#define MEM_OVRD_EQFTC BIT(1)
|
#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
|
* 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_IDLE_TIME 100 /* in milliseconds */
|
||||||
#define PLL_LOCK_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 {
|
struct pipe3_dpll_params {
|
||||||
u16 m;
|
u16 m;
|
||||||
u8 n;
|
u8 n;
|
||||||
|
@ -123,6 +149,27 @@ struct pipe3_dpll_map {
|
||||||
struct pipe3_dpll_params params;
|
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 {
|
struct ti_pipe3 {
|
||||||
void __iomem *pll_ctrl_base;
|
void __iomem *pll_ctrl_base;
|
||||||
void __iomem *phy_rx;
|
void __iomem *phy_rx;
|
||||||
|
@ -141,6 +188,8 @@ struct ti_pipe3 {
|
||||||
unsigned int power_reg; /* power reg. index within syscon */
|
unsigned int power_reg; /* power reg. index within syscon */
|
||||||
unsigned int pcie_pcs_reg; /* pcs reg. index in syscon */
|
unsigned int pcie_pcs_reg; /* pcs reg. index in syscon */
|
||||||
bool sata_refclk_enabled;
|
bool sata_refclk_enabled;
|
||||||
|
enum pipe3_mode mode;
|
||||||
|
struct pipe3_settings settings;
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct pipe3_dpll_map dpll_map_usb[] = {
|
static struct pipe3_dpll_map dpll_map_usb[] = {
|
||||||
|
@ -163,6 +212,89 @@ static struct pipe3_dpll_map dpll_map_sata[] = {
|
||||||
{ }, /* Terminator */
|
{ }, /* 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)
|
static inline u32 ti_pipe3_readl(void __iomem *addr, unsigned offset)
|
||||||
{
|
{
|
||||||
return __raw_readl(addr + 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)
|
static int ti_pipe3_power_off(struct phy *x)
|
||||||
{
|
{
|
||||||
u32 val;
|
|
||||||
int ret;
|
int ret;
|
||||||
struct ti_pipe3 *phy = phy_get_drvdata(x);
|
struct ti_pipe3 *phy = phy_get_drvdata(x);
|
||||||
|
|
||||||
|
@ -205,13 +336,13 @@ static int ti_pipe3_power_off(struct phy *x)
|
||||||
return 0;
|
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,
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ti_pipe3_calibrate(struct ti_pipe3 *phy);
|
||||||
|
|
||||||
static int ti_pipe3_power_on(struct phy *x)
|
static int ti_pipe3_power_on(struct phy *x)
|
||||||
{
|
{
|
||||||
u32 val;
|
u32 val;
|
||||||
|
@ -219,6 +350,7 @@ static int ti_pipe3_power_on(struct phy *x)
|
||||||
int ret;
|
int ret;
|
||||||
unsigned long rate;
|
unsigned long rate;
|
||||||
struct ti_pipe3 *phy = phy_get_drvdata(x);
|
struct ti_pipe3 *phy = phy_get_drvdata(x);
|
||||||
|
bool rx_pending = false;
|
||||||
|
|
||||||
if (!phy->phy_power_syscon) {
|
if (!phy->phy_power_syscon) {
|
||||||
omap_control_phy_power(phy->control_dev, 1);
|
omap_control_phy_power(phy->control_dev, 1);
|
||||||
|
@ -231,14 +363,35 @@ static int ti_pipe3_power_on(struct phy *x)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
rate = rate / 1000000;
|
rate = rate / 1000000;
|
||||||
mask = OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK |
|
mask = OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_MASK;
|
||||||
OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_MASK;
|
val = rate << OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT;
|
||||||
val = PIPE3_PHY_TX_RX_POWERON << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT;
|
|
||||||
val |= rate << OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT;
|
|
||||||
|
|
||||||
ret = regmap_update_bits(phy->phy_power_syscon, phy->power_reg,
|
ret = regmap_update_bits(phy->phy_power_syscon, phy->power_reg,
|
||||||
mask, val);
|
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)
|
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)
|
static void ti_pipe3_calibrate(struct ti_pipe3 *phy)
|
||||||
{
|
{
|
||||||
u32 val;
|
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 &= ~(INTERFACE_MASK | LOSD_MASK | MEM_PLLDIV);
|
||||||
val = (0x1 << INTERFACE_SHIFT | 0xA << LOSD_SHIFT);
|
val |= (s->ana_interface << INTERFACE_SHIFT | s->ana_losd << LOSD_SHIFT);
|
||||||
ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_ANA_PROGRAMMABILITY, val);
|
ti_pipe3_writel(phy->phy_rx, PIPE3_PHY_RX_ANA_PROGRAMMABILITY, val);
|
||||||
|
|
||||||
val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_DIGITAL_MODES);
|
val = ti_pipe3_readl(phy->phy_rx, PIPE3_PHY_RX_DIGITAL_MODES);
|
||||||
val &= ~(MEM_CDR_STEPCNT | MEM_CDR_STL_MASK | MEM_CDR_THR_MASK |
|
val &= ~(MEM_HS_RATE_MASK | MEM_OVRD_HS_RATE | MEM_CDR_FASTLOCK |
|
||||||
MEM_CDR_CDR_2NDO_SDM_MODE | MEM_OVRD_HS_RATE);
|
MEM_CDR_LBW_MASK | MEM_CDR_STEPCNT_MASK | MEM_CDR_STL_MASK |
|
||||||
val |= (MEM_CDR_FASTLOCK | MEM_CDR_LBW | 0x3 << MEM_CDR_STL_SHIFT |
|
MEM_CDR_THR_MASK | MEM_CDR_THR_MODE | MEM_CDR_2NDO_SDM_MODE);
|
||||||
0x1 << MEM_CDR_THR_SHIFT | MEM_CDR_THR_MODE);
|
val |= s->dig_hs_rate << MEM_HS_RATE_SHIFT |
|
||||||
ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_DIGITAL_MODES, val);
|
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 = ti_pipe3_readl(phy->phy_rx, PIPE3_PHY_RX_TRIM);
|
||||||
val &= ~MEM_DLL_TRIM_SEL;
|
val &= ~MEM_DLL_TRIM_SEL_MASK;
|
||||||
val |= 0x2 << MEM_DLL_TRIM_SHIFT;
|
val |= s->dll_trim_sel << MEM_DLL_TRIM_SHIFT;
|
||||||
ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_TRIM, val);
|
ti_pipe3_writel(phy->phy_rx, PIPE3_PHY_RX_TRIM, val);
|
||||||
|
|
||||||
val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_DLL);
|
val = ti_pipe3_readl(phy->phy_rx, PIPE3_PHY_RX_DLL);
|
||||||
val |= MEM_DLL_PHINT_RATE;
|
val &= ~MEM_DLL_PHINT_RATE_MASK;
|
||||||
ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_DLL, val);
|
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 = ti_pipe3_readl(phy->phy_rx, PIPE3_PHY_RX_EQUALIZER);
|
||||||
val &= ~(MEM_EQLEV | MEM_EQCTL | MEM_OVRD_EQLEV | MEM_OVRD_EQFTC);
|
val &= ~(MEM_EQLEV_MASK | MEM_EQFTC_MASK | MEM_EQCTL_MASK |
|
||||||
val |= MEM_EQFTC | 0x1 << MEM_EQCTL_SHIFT;
|
MEM_OVRD_EQLEV | MEM_OVRD_EQFTC);
|
||||||
ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_EQUALIZER, val);
|
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)
|
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
|
* as recommended in AM572x TRM SPRUHZ6, section 18.5.2.2, table
|
||||||
* 18-1804.
|
* 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) {
|
if (!phy->pcs_syscon) {
|
||||||
omap_control_pcie_pcs(phy->control_dev, 0x96);
|
omap_control_pcie_pcs(phy->control_dev, 0x96);
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -349,12 +525,7 @@ static int ti_pipe3_init(struct phy *x)
|
||||||
val = 0x96 << OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT;
|
val = 0x96 << OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT;
|
||||||
ret = regmap_update_bits(phy->pcs_syscon, phy->pcie_pcs_reg,
|
ret = regmap_update_bits(phy->pcs_syscon, phy->pcie_pcs_reg,
|
||||||
PCIE_PCS_MASK, val);
|
PCIE_PCS_MASK, val);
|
||||||
if (ret)
|
return ret;
|
||||||
return ret;
|
|
||||||
|
|
||||||
ti_pipe3_calibrate(phy);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bring it out of IDLE if it is IDLE */
|
/* 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 */
|
/* SATA has issues if re-programmed when locked */
|
||||||
val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS);
|
val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS);
|
||||||
if ((val & PLL_LOCK) && of_device_is_compatible(phy->dev->of_node,
|
if ((val & PLL_LOCK) && phy->mode == PIPE3_MODE_SATA)
|
||||||
"ti,phy-pipe3-sata"))
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
/* Program the DPLL */
|
/* Program the DPLL */
|
||||||
|
@ -378,6 +548,8 @@ static int ti_pipe3_init(struct phy *x)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ti_pipe3_calibrate(phy);
|
||||||
|
|
||||||
return ret;
|
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
|
/* If dpll_reset_syscon is not present we wont power down SATA DPLL
|
||||||
* due to Errata i783
|
* due to Errata i783
|
||||||
*/
|
*/
|
||||||
if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-sata") &&
|
if (phy->mode == PIPE3_MODE_SATA && !phy->dpll_reset_syscon)
|
||||||
!phy->dpll_reset_syscon)
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* PCIe doesn't have internal DPLL */
|
/* 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 */
|
/* Put DPLL in IDLE mode */
|
||||||
val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2);
|
val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2);
|
||||||
val |= PLL_IDLE;
|
val |= PLL_IDLE;
|
||||||
|
@ -418,7 +589,7 @@ static int ti_pipe3_exit(struct phy *x)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* i783: SATA needs control bit toggle after PLL unlock */
|
/* 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,
|
regmap_update_bits(phy->dpll_reset_syscon, phy->dpll_reset_reg,
|
||||||
SATA_PLL_SOFT_RESET, SATA_PLL_SOFT_RESET);
|
SATA_PLL_SOFT_RESET, SATA_PLL_SOFT_RESET);
|
||||||
regmap_update_bits(phy->dpll_reset_syscon, phy->dpll_reset_reg,
|
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 clk *clk;
|
||||||
struct device *dev = phy->dev;
|
struct device *dev = phy->dev;
|
||||||
struct device_node *node = dev->of_node;
|
|
||||||
|
|
||||||
phy->refclk = devm_clk_get(dev, "refclk");
|
phy->refclk = devm_clk_get(dev, "refclk");
|
||||||
if (IS_ERR(phy->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
|
/* older DTBs have missing refclk in SATA PHY
|
||||||
* so don't bail out in case of 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);
|
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");
|
phy->wkupclk = devm_clk_get(dev, "wkupclk");
|
||||||
if (IS_ERR(phy->wkupclk)) {
|
if (IS_ERR(phy->wkupclk)) {
|
||||||
dev_err(dev, "unable to get wkupclk\n");
|
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);
|
phy->wkupclk = ERR_PTR(-ENODEV);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie") ||
|
if (phy->mode != PIPE3_MODE_PCIE || phy->phy_power_syscon) {
|
||||||
phy->phy_power_syscon) {
|
|
||||||
phy->sys_clk = devm_clk_get(dev, "sysclk");
|
phy->sys_clk = devm_clk_get(dev, "sysclk");
|
||||||
if (IS_ERR(phy->sys_clk)) {
|
if (IS_ERR(phy->sys_clk)) {
|
||||||
dev_err(dev, "unable to get sysclk\n");
|
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");
|
clk = devm_clk_get(dev, "dpll_ref");
|
||||||
if (IS_ERR(clk)) {
|
if (IS_ERR(clk)) {
|
||||||
dev_err(dev, "unable to get dpll ref clk\n");
|
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;
|
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,
|
phy->pcs_syscon = syscon_regmap_lookup_by_phandle(node,
|
||||||
"syscon-pcs");
|
"syscon-pcs");
|
||||||
if (IS_ERR(phy->pcs_syscon)) {
|
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,
|
phy->dpll_reset_syscon = syscon_regmap_lookup_by_phandle(node,
|
||||||
"syscon-pllreset");
|
"syscon-pllreset");
|
||||||
if (IS_ERR(phy->dpll_reset_syscon)) {
|
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 resource *res;
|
||||||
struct device *dev = phy->dev;
|
struct device *dev = phy->dev;
|
||||||
struct device_node *node = dev->of_node;
|
|
||||||
struct platform_device *pdev = to_platform_device(dev);
|
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,
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
||||||
"phy_rx");
|
"phy_rx");
|
||||||
phy->phy_rx = devm_ioremap_resource(dev, res);
|
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)
|
static int ti_pipe3_get_pll_base(struct ti_pipe3 *phy)
|
||||||
{
|
{
|
||||||
struct resource *res;
|
struct resource *res;
|
||||||
const struct of_device_id *match;
|
|
||||||
struct device *dev = phy->dev;
|
struct device *dev = phy->dev;
|
||||||
struct device_node *node = dev->of_node;
|
|
||||||
struct platform_device *pdev = to_platform_device(dev);
|
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;
|
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,
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
||||||
"pll_ctrl");
|
"pll_ctrl");
|
||||||
phy->pll_ctrl_base = devm_ioremap_resource(dev, res);
|
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 ti_pipe3 *phy;
|
||||||
struct phy *generic_phy;
|
struct phy *generic_phy;
|
||||||
struct phy_provider *phy_provider;
|
struct phy_provider *phy_provider;
|
||||||
struct device_node *node = pdev->dev.of_node;
|
|
||||||
struct device *dev = &pdev->dev;
|
struct device *dev = &pdev->dev;
|
||||||
int ret;
|
int ret;
|
||||||
|
const struct of_device_id *match;
|
||||||
|
struct pipe3_data *data;
|
||||||
|
|
||||||
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
|
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
|
||||||
if (!phy)
|
if (!phy)
|
||||||
return -ENOMEM;
|
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);
|
ret = ti_pipe3_get_pll_base(phy);
|
||||||
if (ret)
|
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
|
* 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)) {
|
if (!IS_ERR(phy->refclk)) {
|
||||||
clk_prepare_enable(phy->refclk);
|
clk_prepare_enable(phy->refclk);
|
||||||
phy->sata_refclk_enabled = true;
|
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[] = {
|
static const struct of_device_id ti_pipe3_id_table[] = {
|
||||||
{
|
{
|
||||||
.compatible = "ti,phy-usb3",
|
.compatible = "ti,phy-usb3",
|
||||||
.data = dpll_map_usb,
|
.data = &data_usb,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.compatible = "ti,omap-usb3",
|
.compatible = "ti,omap-usb3",
|
||||||
.data = dpll_map_usb,
|
.data = &data_usb,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.compatible = "ti,phy-pipe3-sata",
|
.compatible = "ti,phy-pipe3-sata",
|
||||||
.data = dpll_map_sata,
|
.data = &data_sata,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.compatible = "ti,phy-pipe3-pcie",
|
.compatible = "ti,phy-pipe3-pcie",
|
||||||
|
.data = &data_pcie,
|
||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
|
@ -99,6 +99,7 @@ config SCSI_UFS_DWC_TC_PLATFORM
|
||||||
config SCSI_UFS_QCOM
|
config SCSI_UFS_QCOM
|
||||||
tristate "QCOM specific hooks to UFS controller platform driver"
|
tristate "QCOM specific hooks to UFS controller platform driver"
|
||||||
depends on SCSI_UFSHCD_PLATFORM && ARCH_QCOM
|
depends on SCSI_UFSHCD_PLATFORM && ARCH_QCOM
|
||||||
|
select RESET_CONTROLLER
|
||||||
help
|
help
|
||||||
This selects the QCOM specific additions to UFSHCD platform driver.
|
This selects the QCOM specific additions to UFSHCD platform driver.
|
||||||
UFS host on QCOM needs some vendor specific configuration before
|
UFS host on QCOM needs some vendor specific configuration before
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include <linux/of.h>
|
#include <linux/of.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
#include <linux/phy/phy.h>
|
#include <linux/phy/phy.h>
|
||||||
|
#include <linux/reset-controller.h>
|
||||||
|
|
||||||
#include "ufshcd.h"
|
#include "ufshcd.h"
|
||||||
#include "ufshcd-pltfrm.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,
|
static int ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(struct ufs_hba *hba,
|
||||||
u32 clk_cycles);
|
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,
|
static void ufs_qcom_dump_regs_wrapper(struct ufs_hba *hba, int offset, int len,
|
||||||
const char *prefix, void *priv)
|
const char *prefix, void *priv)
|
||||||
{
|
{
|
||||||
|
@ -255,11 +261,6 @@ static int ufs_qcom_power_up_sequence(struct ufs_hba *hba)
|
||||||
if (is_rate_B)
|
if (is_rate_B)
|
||||||
phy_set_mode(phy, PHY_MODE_UFS_HS_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 */
|
/* phy initialization - calibrate the phy */
|
||||||
ret = phy_init(phy);
|
ret = phy_init(phy);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
@ -268,15 +269,6 @@ static int ufs_qcom_power_up_sequence(struct ufs_hba *hba)
|
||||||
goto out;
|
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 */
|
/* power on phy - start serdes and phy's power and clocks */
|
||||||
ret = phy_power_on(phy);
|
ret = phy_power_on(phy);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
@ -290,7 +282,6 @@ static int ufs_qcom_power_up_sequence(struct ufs_hba *hba)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
out_disable_phy:
|
out_disable_phy:
|
||||||
ufs_qcom_assert_reset(hba);
|
|
||||||
phy_exit(phy);
|
phy_exit(phy);
|
||||||
out:
|
out:
|
||||||
return ret;
|
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);
|
ufs_qcom_disable_lane_clks(host);
|
||||||
phy_power_off(phy);
|
phy_power_off(phy);
|
||||||
|
|
||||||
/* Assert PHY soft reset */
|
} else if (!ufs_qcom_is_link_active(hba)) {
|
||||||
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)) {
|
|
||||||
ufs_qcom_disable_lane_clks(host);
|
ufs_qcom_disable_lane_clks(host);
|
||||||
phy_power_off(phy);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
out:
|
|
||||||
return ret;
|
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;
|
struct phy *phy = host->generic_phy;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
err = phy_power_on(phy);
|
if (ufs_qcom_is_link_off(hba)) {
|
||||||
if (err) {
|
err = phy_power_on(phy);
|
||||||
dev_err(hba->dev, "%s: failed enabling regs, err = %d\n",
|
if (err) {
|
||||||
__func__, err);
|
dev_err(hba->dev, "%s: failed PHY power on: %d\n",
|
||||||
goto out;
|
__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;
|
hba->is_sys_suspended = false;
|
||||||
|
return 0;
|
||||||
out:
|
|
||||||
return err;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ufs_qcom_dev_params {
|
struct ufs_qcom_dev_params {
|
||||||
|
@ -1118,8 +1103,6 @@ static int ufs_qcom_setup_clocks(struct ufs_hba *hba, bool on,
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (on && (status == POST_CHANGE)) {
|
if (on && (status == POST_CHANGE)) {
|
||||||
phy_power_on(host->generic_phy);
|
|
||||||
|
|
||||||
/* enable the device ref clock for HS mode*/
|
/* enable the device ref clock for HS mode*/
|
||||||
if (ufshcd_is_hs_mode(&hba->pwr_info))
|
if (ufshcd_is_hs_mode(&hba->pwr_info))
|
||||||
ufs_qcom_dev_ref_clk_ctrl(host, true);
|
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)) {
|
if (!ufs_qcom_is_link_active(hba)) {
|
||||||
/* disable device ref_clk */
|
/* disable device ref_clk */
|
||||||
ufs_qcom_dev_ref_clk_ctrl(host, false);
|
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;
|
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;
|
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
|
#define ANDROID_BOOT_DEV_MAX 30
|
||||||
static char android_boot_dev[ANDROID_BOOT_DEV_MAX];
|
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;
|
host->hba = hba;
|
||||||
ufshcd_set_variant(hba, host);
|
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
|
* voting/devoting device ref_clk source is time consuming hence
|
||||||
* skip devoting it during aggressive clock gating. This clock
|
* skip devoting it during aggressive clock gating. This clock
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
#ifndef UFS_QCOM_H_
|
#ifndef UFS_QCOM_H_
|
||||||
#define UFS_QCOM_H_
|
#define UFS_QCOM_H_
|
||||||
|
|
||||||
|
#include <linux/reset-controller.h>
|
||||||
|
|
||||||
#define MAX_UFS_QCOM_HOSTS 1
|
#define MAX_UFS_QCOM_HOSTS 1
|
||||||
#define MAX_U32 (~(u32)0)
|
#define MAX_U32 (~(u32)0)
|
||||||
#define MPHY_TX_FSM_STATE 0x41
|
#define MPHY_TX_FSM_STATE 0x41
|
||||||
|
@ -237,6 +239,8 @@ struct ufs_qcom_host {
|
||||||
/* Bitmask for enabling debug prints */
|
/* Bitmask for enabling debug prints */
|
||||||
u32 dbg_print_en;
|
u32 dbg_print_en;
|
||||||
struct ufs_qcom_testbus testbus;
|
struct ufs_qcom_testbus testbus;
|
||||||
|
|
||||||
|
struct reset_controller_dev rcdev;
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline u32
|
static inline u32
|
||||||
|
|
|
@ -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 */
|
|
@ -64,6 +64,7 @@ union phy_configure_opts {
|
||||||
* @set_mode: set the mode of the phy
|
* @set_mode: set the mode of the phy
|
||||||
* @reset: resetting the phy
|
* @reset: resetting the phy
|
||||||
* @calibrate: calibrate the phy
|
* @calibrate: calibrate the phy
|
||||||
|
* @release: ops to be performed while the consumer relinquishes the PHY
|
||||||
* @owner: the module owner containing the ops
|
* @owner: the module owner containing the ops
|
||||||
*/
|
*/
|
||||||
struct phy_ops {
|
struct phy_ops {
|
||||||
|
@ -105,6 +106,7 @@ struct phy_ops {
|
||||||
union phy_configure_opts *opts);
|
union phy_configure_opts *opts);
|
||||||
int (*reset)(struct phy *phy);
|
int (*reset)(struct phy *phy);
|
||||||
int (*calibrate)(struct phy *phy);
|
int (*calibrate)(struct phy *phy);
|
||||||
|
void (*release)(struct phy *phy);
|
||||||
struct module *owner;
|
struct module *owner;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue